--- /dev/null
+README.txt for dilimesh
+David Rowe
+Dec 7 2010
+
+
+Introduction
+------------
+
+Simple mesh network mapping application (web app) used as a test bed
+for link debugging techniques in the Dili Village Telco.
+
+* Screen shot: dilimesh_screenshot.png
+
+* Currently displays packet loss stats as an alternative to batman
+ scores. The purpose of this project is to experiment with the
+ display of different information, for example display signal
+ strength or even a spectrum of local Wifi activity for each nodes.
+ The goal is gather information to debug problem links.
+
+* Based on Google maps V3 API.
+
+* Light weight: On the server we have just a few CGIs and a text file
+ database. A browser cookie is used for storing configuration
+ information. The CGIs are a few lines of shell script, so can run
+ on any machine. Installation involves copying a few files and
+ setting a few permissions.
+
+* Loosely coupled design: Can work without any additional software on
+ each mesh mode, just needs a Visualisation server somewhere in the
+ mesh. Doesn't affct Mesh Potato operation, or the operation of
+ other software on the mesh.
+
+
+Using Dilimesh
+--------------
+
+1/ Point your browser at http://server/dilimesh/dilimesh/html
+
+2/ Set "Visualisation Server IP" on RHS and reload page.
+
+3/ Dilimesh will find new nodes automatically. Drag bouncing nodes
+ to the correct position on map.
+
+4/ Mouse over or click on a node to get IP and packet loss. If nodes
+ are running signal strength daemon signal strengths of adjacent
+ nodes will also be displayed.
+
+5/ Click on links to get "Distance" stats on lower right hand side.
+
+5/ Node colours:
+ * blue - packet loss < 10%
+ * red - packet loss between 10% and 90%
+ * black - packet loss > 90%
+
+5/ "Update Enable" will update packet loss, network links and signal
+ strength automatically.
+
+
+Status
+------
+
+* Alpha
+* Tested on Firefox 3.5.3 with Apache2 servers running Ubuntu 9 & 10.
+* Tested on small 5 node mesh network, might needs tweaks (e.g. fping
+ arguments) for larger networks
+
+
+Implementation Notes
+--------------------
+
+Dilimesh is implemented in Javascript (dilimesh.html). A simple text
+database file /var/www/dilimesh/nodes.txt is used to store node
+locations and IPs. Browser cookies are used to store config
+information like our last map location and vis server IP. Very simple
+1 page CGIs written in shell script are used to access the node
+database.
+
+The 'Set' buttons on the RHS cause dilimesh.html to reload with CGI
+type variables passed to it. However these variables are not used, we
+use onclick() methods to change the state variables which are stored
+in a cookie when the page exits.
+
+All the CGI scripts assume a hard coded path of /var/www/dilimesh for
+the nodes.txt database.
+
+
+Directories
+-----------
+
+"dilimesh" directory:
+
+ dilimesh.html - all the Javascript for Dilimesh
+ dilimesh.css - style sheet
+ node_good.png
+ node_packet_loss.png
+ node_dead.png - node icon PNGs, changes with packet loss
+
+"cgi-bin" directory:
+
+ addnode.cgi - adds a node to nodes.txt
+ delnode.cgi - deletes a node from nodes.txt
+ getnodes.cgi - reads nodes.txt database
+ getvis.cgi - reads vis server data
+ fpingnodes.cgi - fires off pings for all nodes, returns results
+ getsig.cgi - reads signal strength of surrounding nodes from one node,
+ requires sigstr.sh telnet daemon running on node.
+
+"scripts" directory:
+
+ sigstr.sh - script that runs on MP as a telnet daemon to return signal
+ strength of each node to getsig.cgi. Experimental.
+ S99sigstr - /etc/init.d Mesh Potato start upscript for sigstr.sh telnet
+ daemon
+
+Software
+--------
+
+You need:
+
+1/ A web server. Apache is assumed in the /usr/lib/cgi-bin path below.
+ The paths may be different for other web servers. Note the path
+ to /var/www/dilimesh is hard coded in the CGI scripts so it's
+ best not to change that.
+
+2/ netcat (nc) and fping installed on the server. Note that on Ubuntu
+ we found that netcat 1.10.38 was required, netcat-openbsd would not
+ work.
+
+3/ A Batman mesh network with the vis server on one of the nodes, this
+ may already be running on your supernode. Batman on each node should
+ be configured for the vis server (e.g. -s 10.130.1.1)
+
+4/ A route from the web server PC to the mesh network. On the Ubuntu
+ machine I use as a server I put this script in the file
+ /etc/network/if-up.d/potato:
+
+ !/bin/sh
+ # Add interface and routes to connect to potato mesh network
+
+ ifconfig eth0 10.30.1.3 netmask 255.255.255.0
+ route add -net 10.130.1.0/24 gw 10.30.1.1
+
+ This file runs when the machine boots and connects to the Supernode
+ via Ethernet.
+
+Installation
+------------
+
+1/ Server PC
+
+ $ svn co https://freetel.svn.sourceforge.net/svnroot/freetel/dilimesh
+ $ cd dilimesh
+ $ sudo mkdir /var/www/dilimesh
+ $ sudo chmod 777 /var/www/dilimesh
+ $ cp dilimesh/* /var/www/dilimesh
+ $ sudo cp cgi-bin/* /usr/lib/cgi-bin
+
+2/ Supernode
+
+ Configure vis server to work on Wifi and Ethernet interfaces:
+
+ # root@OpenWrt:~# cat /etc/config/vis
+ config vis general
+ option 'interface' 'ath0 eth0'
+
+ If it's running OK you should see a process like:
+
+ # ps | grep vis
+ 711 root 1508 S vis -j ath0 eth0
+
+3/ Batman nodes
+
+ Configure you nodes to use the visualisation server:
+
+ # cat /etc/config/batman
+ config batmand general
+ option interface ath0
+ option announce
+ option gateway_class
+ option originator_interval
+ option preferred_gateway
+ option routing_class
+ option visualisation_srv 10.130.1.1
+ option policy_routing_script
+
+ When batman is running with the vis server it should look like:
+
+ # ps | grep batman
+ 552 root 1564 S batmand -s 10.130.1.1 ath0
+
+4/ (Optional) Signal strength daemon:
+
+ Copy these scripts to each MP in your mesh that you wish to read sig
+ strength from:
+
+ $ cd dilimesh/script
+ $ scp sigstr.sh root@10.130.1.36:/usr/sbin
+ $ scp S99sigstr root@10.130.1.36:/etc/rc.d
+
+ Now start daemon on remote machine:
+
+ $ ssh root@10.130.1.36 '/etc/rc.d/S99sigstr'
+
+ Test if daemon is running
+
+ $ telnet 10.130.1.36 4950
+
+Tests
+-----
+
+1/ Server PC can reach node with vis server:
+
+ $ ping 192.168.1.100
+
+2/ Server PC has route to mesh nodes:
+
+ $ ping 10.130.1.1
+ $ ping 10.130.1.2
+
+3/ Test you have installed fping:
+
+ $ fping 10.130.1.1
+
+4/ Test netcat and vis server:
+
+ $ nc 192.168.1.100 2005
+
+ HTTP/1.0 200 OK
+ Content-type: application/json
+
+ [
+ { router : "10.130.1.1", neighbour : "10.130.1.23", label : 1.02 },
+ { router : "10.130.1.14", neighbour : "10.130.1.56", label : 1.76 }
+ ]
+
+5/ Test CGIs:
+
+ Point your browser at:
+
+ http://localhost/cgi-bin/fpingnodes.cgi?ip=10.130.1.1
+
+6/ Test reading and writing nodes.txt database with your browser:
+
+ http://localhost/cgi-bin/addnode.cgi?lat=123&lng=456&ip=10.130.1.1
+
+ $ cat /var/www/dilimesh/nodes.txt
+ 123,456,10.130.1.1
+
+ http://localhost/cgi-bin/delnode.cgi?lat=123&lng=456&ip=10.130.1.1
+
+ $ cat /var/www/dilimesh/nodes.txt
+ (empty file)
+
+7/ Test signal strength daemon on each node:
+
+ $ telnet 10.130.1.1 4950
+ Trying 10.130.1.36...
+ Connected to 10.130.1.36.
+ Escape character is '^]'.
+
+ 10.130.1.1 10.130.1.56 10.130.1.14
+ -64 -90 -75
+
+
+Debugging
+---------
+
+1/ Monitor Apache log:
+
+ $ tail -f /var/log/apache2/access.log
+
+2/ Use Firebug on Firefox to single step, set breakpoints etc.
+
+3/ Check nodes.txt database, each line is (lat, lng, IP):
+
+ # cat /var/www/dilimesh/nodes.txt
+
+ -34.88548650005714,138.55225324630737,10.130.1.56
+ -34.88006501016277,138.55394840240479,10.130.1.1
+ -34.87893842193011,138.55278968811035,10.130.1.23
+ -34.882511765792316,138.55210304260254,10.130.1.14
+ -34.880364257566484,138.5518455505371,10.130.1.36
+
+4/ The "dilimesh" cookie stores a lot of our state. On Firefox 3.5 you
+ can remove the dilimesh cookie using Edit-Preferences-Privacy, then
+ click on "remove individual cookies".
+
+5/ To reset to defaults:
+
+ * move to another page (cookie is svaed when we exit page)
+ * rm -f /var/www/dilimesh/nodes.txt
+ * Delete cookie using setp (4) above
--- /dev/null
+#!/bin/sh
+# addnode.cgi
+# David Rowe 4 Dec 2010
+#
+# CGI to add a new node
+
+cat <<EOF
+Content-type: text/html
+
+<html>
+<head>
+<meta http-equiv="cache-control" content="no-cache">
+<meta http-equiv="pragma" content="no-cache">
+<meta http-equiv="expires" content="-1"></head></head>
+<body>
+EOF
+
+lat=`echo "$QUERY_STRING" | sed -n "s/.*lat=\(.*\)&lng.*/\1/p"`
+lng=`echo "$QUERY_STRING" | sed -n "s/.*lng=\(.*\)&ip.*/\1/p"`
+ip=`echo "$QUERY_STRING" | sed -n "s/.*ip=\(.*\)/\1/p"`
+
+echo $lat,$lng,$ip >> /var/www/dilimesh/nodes.txt
+
+echo $QUERY_STRING "<br>"
+echo "<br>"
+echo $lat $lng $ip
+
+cat <<EOF
+</body>
+</html>
+EOF
--- /dev/null
+#!/bin/sh
+# cleardb.cgi
+# David Rowe 15 Dec 2010
+#
+# CGI to delete entire node database
+
+cat <<EOF
+Content-type: text/html
+
+<html>
+<head>
+<meta http-equiv="cache-control" content="no-cache">
+<meta http-equiv="pragma" content="no-cache">
+<meta http-equiv="expires" content="-1"></head></head>
+<body>
+EOF
+
+# path to node database text file
+
+P=/var/www/dilimesh
+rm $P/nodes.txt
+
+cat <<EOF
+</body>
+</html>
+EOF
--- /dev/null
+#!/bin/sh
+# delnode.cgi
+# David Rowe 4 Dec 2010
+#
+# CGI to delete a node
+
+cat <<EOF
+Content-type: text/html
+
+<html>
+<head>
+<meta http-equiv="cache-control" content="no-cache">
+<meta http-equiv="pragma" content="no-cache">
+<meta http-equiv="expires" content="-1"></head></head>
+<body>
+EOF
+
+lat=`echo "$QUERY_STRING" | sed -n "s/lat=\(.*\)&.*/\1/pg"`
+lng=`echo "$QUERY_STRING" | sed -n "s/.*lng=\(.*\)/\1/pg"`
+
+# path to node database text file
+
+P=/var/www/dilimesh
+
+cat $P/nodes.txt | sed "/$lat,$lng.*/ d" > $P/nodes.tmp
+cp $P/nodes.tmp $P/nodes.txt
+rm $P/nodes.tmp
+
+#echo $QUERY_STRING "<br>"
+#echo "<br>"
+#echo $lat $lng
+
+cat <<EOF
+</body>
+</html>
+EOF
--- /dev/null
+#!/bin/sh
+# fpingnodes.cgi
+# David Rowe 5 Dec 2010
+#
+# CGI to fping a bunch of nodes
+
+cat <<EOF
+Content-type: text/html
+
+<html>
+<head>
+<meta http-equiv="cache-control" content="no-cache">
+<meta http-equiv="pragma" content="no-cache">
+<meta http-equiv="expires" content="-1"></head></head>
+<body>
+EOF
+
+ip=`echo "$QUERY_STRING" | sed -n "s/.*ip=//p" | sed "s/,/ /g"`
+fping $ip -a -b1400 -r1 2>/dev/null
+
+cat <<EOF
+</body>
+</html>
+EOF
--- /dev/null
+#!/bin/sh
+# getnodes.cgi
+# David Rowe 5 Dec 2010
+#
+# CGI to return node list from database text file
+# We use a CGI rather than fetching the text file directly
+# so we can control Firefox from caching
+
+cat <<EOF
+Content-type: text/html
+
+<html>
+<head>
+<meta http-equiv="cache-control" content="no-cache">
+<meta http-equiv="pragma" content="no-cache">
+<meta http-equiv="expires" content="-1">
+</head>
+<body>
+EOF
+
+NODES=/var/www/dilimesh/nodes.txt
+
+if [ -f $NODES ] ; then
+ cat $NODES
+fi
+
+cat <<EOF
+</body>
+</html>
+EOF
--- /dev/null
+#!/bin/sh
+# getsig.cgi
+# David Rowe 6 Dec 2010
+#
+# CGI to fetch the signal strength from a node
+# Requires the node to be running sigstr.sh script as
+# a telnet dameon port 4950
+
+cat <<EOF
+Content-type: text/html
+
+<html>
+<head>
+<meta http-equiv="cache-control" content="no-cache">
+<meta http-equiv="pragma" content="no-cache">
+<meta http-equiv="expires" content="-1"></head></head>
+<body>
+EOF
+
+ip=`echo "$QUERY_STRING" | sed -n "s/.*ip=//p" | sed "s/,/ /g"`
+nc $ip 4950
+echo $ip
+
+cat <<EOF
+</body>
+</html>
+EOF
--- /dev/null
+#!/bin/sh
+# getvis.cgi
+# David Rowe 4 Dec 2010
+#
+# CGI to fetches vis server data
+# I am sure it is possible to do this directly from Javascript
+# but I am too much of a noob.
+
+cat <<EOF
+Content-type: text/html
+
+<html>
+<head>
+<meta http-equiv="cache-control" content="no-cache">
+<meta http-equiv="pragma" content="no-cache">
+<meta http-equiv="expires" content="-1"></head>
+<body>
+EOF
+
+VIS_SERVER_HOST=`echo "$QUERY_STRING" | sed -n "s/.*ip=//p"`
+
+nc $VIS_SERVER_HOST 2005 2>/dev/null >/dev/null
+if [ $? -eq 0 ] ; then
+ nc $VIS_SERVER_HOST 2005 | sed -n "/{/ p"
+else
+ echo "Can't connect to Visualisation Server on $VIS_SERVER_HOST"
+fi
+
+cat <<EOF
+</body>
+</html>
+EOF
+
--- /dev/null
+html,body{
+ margin:0;
+ padding:0;
+ width:100%;
+ height:100%;
+ font-family:Arial, Helvetica, sans-serif;
+}
+
+div#header{
+ vertical-align:middle;
+ border-bottom:1px solid #000;
+}
+div#main-map{
+ width:70%;
+ height:100%;
+ float:left;
+}
+div#side{
+ width:30%;
+ float:left;
+}
+
+div#dataPanel{
+ width:90%;
+ height:50%;
+ overflow:auto;
+ border:2px solid #DDDDDD;
+}
+input{
+ width:90%;
+}
+
+input.navi{
+ font-size:12px;
+ height:30px;
+ margin-bottom:10px;
+}
+div ul{
+ margin-top:30px;
+ margin-bottom:30px;
+}
+div ul li{
+ display: inline;
+ list-style-type: none;
+ padding-right: 40px;
+ font-size:18px;
+ font-weight:bold;
+}
+
+div ul li.title{
+ font-size:22px;
+ color:#888;
+}
+
+div#header p{
+ color:#888;
+ font-size:14px;
+ padding-left:20px;
+}
+span.instruction{
+ font-weight:bold;
+
+}
+
+.message-box { text-align: center; padding: 5px; color:#545454; width:80%; margin:5px auto; font-size:12px;}
+.clean { background-color: #efefef; border-top: 2px solid #dedede; border-bottom: 2px solid #dedede; }
+.info { background-color: #f7fafd; border-top: 2px solid #b5d3ff; border-bottom: 2px solid #b5d3ff; }
+.ok { background-color: #d7f7c4; border-top: 2px solid #82cb2f; border-bottom: 2px solid #82cb2f; }
+.alert { background-color: #fef5be; border-top: 2px solid #fdd425; border-bottom: 2px solid #fdd425; }
+.error { background-color: #ffcdd1; border-top: 2px solid #e10c0c; border-bottom: 2px solid #e10c0c; }
\ No newline at end of file
--- /dev/null
+<!DOCTYPE html>
+<html>
+<head>
+<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
+<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
+<style type="text/css">
+ html { height: 100% }
+ body { height: 100%; margin: 0px; padding: 0px }
+ #map_canvas { height: 100% }
+</style>
+<title>Dili Mesh</title>
+
+<link rel="stylesheet" type="text/css" href="dilimesh.css">
+<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
+
+<script type="text/javascript">
+
+ /*
+ dilimesh.html
+ David Rowe
+ Dec 4 2010
+
+ Simple mesh network mapping application used as a test bed for debugging
+ problem links in the Dili Village Telco.
+
+ See dilimesh README.txt for more information.
+ */
+
+ var map;
+
+ // cookie params
+
+ var cookiename = "dilimesh0001";
+ var cookieexpy = 7;
+
+ // these are stored in cookie, with default values
+
+ var lat = 0;
+ var lng = 0;
+ var myzoom = 2;
+ var vis_server_ip = "192.168.1.100";
+ var update_enable = 0;
+ var update_time = 5;
+ var debug_enable = 0;
+ var known_location = true;
+
+ // state variables we use while dragging
+
+ var dragIp;
+ var drag_packet_loss;
+ var drag_in_progress;
+
+ // pathes, vis server data, node list
+
+ var pathes = []; // array of lines, used to delete them
+ var vis = []; // array of vis server information
+ var nodes = []; // database of nodes we keep in memory
+ // mirrors nodes.txt but also store marker locations
+
+ // states for polling sig strength
+
+ var sigstr_node_index = 0;
+
+ // Called when we load page
+
+ function initialize() {
+
+ // we use our cookie as a mini-databse for map location,
+ // control panel values etc
+
+ getCookie();
+
+ var myLatlng = new google.maps.LatLng(lat, lng);
+ var myOptions = {
+ zoom: myzoom,
+ center: myLatlng,
+ scaleControl: true,
+ mapTypeId: google.maps.MapTypeId.ROADMAP
+ }
+ map = new google.maps.Map(document.getElementById("main-map"), myOptions);
+
+ // we don't want manual placement of nodes, use vis server to pop
+ // them up
+ //google.maps.event.addListener(map, 'click', function(event) {
+ // placeMarker(event.latLng);
+ //});
+
+ // set up initial form values
+
+ document.control_panel.vis_server_ip.value = vis_server_ip;
+ document.control_panel.update_time.value = update_time;
+ document.control_panel.update_enable.checked = update_enable;
+ document.control_panel.debug_enable.checked = debug_enable;
+ var html = '<a href="/cgi-bin/getvis.cgi?ip=' + vis_server_ip + ' ">Test Vis Server Connection</a>';
+ document.getElementById('test_vis_server').innerHTML = html;
+
+ // read from nodes.txt database text file on server and init map
+
+ downloadUrl("/cgi-bin/getnodes.cgi", processMarkers);
+ }
+
+
+ // Slurp up nodes.txt when web pages loads
+
+ processMarkers = function(doc, status) {
+ log("processMarkers");
+
+ // split the document into lines
+
+ var lines = doc.split("\n");
+ for (var i=0; i<lines.length; i++) {
+ if (lines[i].length > 1) {
+
+ // split each line into parts separated by "," and use the contents
+
+ parts = lines[i].split(",");
+ if (parts.length > 1) {
+ var lat = parseFloat(parts[0]);
+ var lng = parseFloat(parts[1]);
+ var ip = parts[2];
+ if (ip == undefined) {
+ ip = "";
+ }
+
+ // sanity check, make sure no duplicate IPs
+
+ if (dupIp(ip))
+ log("Error - Duplicate IP " + ip + " in nodes.txt");
+ else
+ addNewNode(lat, lng, ip, false);
+ }
+ }
+ }
+
+ // initial map display, only if we have a sensible map location. Otherwise
+ // new nodes will be placed in middle of current default location
+
+ if (known_location)
+ poll();
+
+ // kick off timer process that periodically fires off vis and fping CGIs
+
+ timer();
+ }
+
+
+ // check for duplicate IPs
+
+ function dupIp(ip) {
+
+ if ( (ip == undefined) || (ip == ""))
+ return false;
+
+ // walk nodes list looking for this IP
+
+ for (var j=0; j<nodes.length; j++) {
+ if (nodes[j].ip == ip) {
+ return true;
+ }
+ }
+
+ return false;
+
+ }
+
+
+ // Process the Vis server information
+
+ processVis = function(doc, status) {
+ log("processVis");
+
+ if (drag_in_progress)
+ return;
+
+ // extract the JSON lines from doc returned by getvis.cgi
+ // there is surely a better way to do this but I am a JSON noob
+
+ var lines = doc.split("\n");
+ var vis_str = "vis = {data: [\n";
+ for (var i=0; i<lines.length; i++) {
+ if (lines[i].length > 1) {
+ if (lines[i].indexOf('{') != -1) {
+ vis_str = vis_str + lines[i] + "\n";
+ }
+ }
+ }
+
+ vis_str = vis_str + "]\n};"
+ eval(vis_str); // run time compilation of JSON string
+
+ // OK now we have an array that we can parse for new nodes
+
+ // walk vis list by router
+
+ var new_nodes = 0;
+ for (var i=0; i<vis.data.length; i++) {
+
+ // see if this node exists in our database
+
+ var node_exists = 0;
+ for (var j=0; j<nodes.length; j++) {
+
+ if (vis.data[i].router == nodes[j].ip) {
+ node_exists = 1;
+ }
+ }
+
+ // create node if it doesn't exists already
+
+ if (node_exists == 0) {
+ // small offset makes sure we don't create them all on one spot
+ var location = new google.maps.LatLng(lat + 1E-4*nodes.length, lng);
+ placeMarker(location, vis.data[i].router, true);
+ }
+ }
+
+ // walk vis list by neighbour
+
+ for (var i=0; i<vis.data.length; i++) {
+
+ if (vis.data[i].neighbour != undefined) {
+
+ // see if this node exists in our database
+
+ var node_exists = 0;
+ for (var j=0; j<nodes.length; j++) {
+
+ if (vis.data[i].neighbour == nodes[j].ip) {
+ node_exists = 1;
+ }
+ }
+
+ // create node if it doesn't exists already
+
+ if (node_exists == 0) {
+ // small offset makes sure we don't create them all on one spot
+ var location = new google.maps.LatLng(lat + 1E-4*nodes.length, lng);
+ placeMarker(location, vis.data[i].neighbour, true);
+ }
+ }
+ }
+
+ // now all nodes are logged in text file and nodes database
+ // lets see if we any have routes between them
+
+ drawPathesBetweenNodes();
+ }
+
+
+ function poll() {
+
+ // fire off vis server CGI
+
+ downloadUrl("/cgi-bin/getvis.cgi?ip=" + vis_server_ip, processVis);
+
+ // fire off fping CGI
+
+ var ips = "ip=";
+ for (var j=0; j<nodes.length; j++) {
+ if (nodes[j].ip != "") {
+ ips = ips + nodes[j].ip;
+ if (j != (nodes.length-1)) {
+ ips = ips + ",";
+ }
+ }
+ }
+
+ if (ips != "") {
+ downloadUrl("/cgi-bin/fpingnodes.cgi?" + ips, processFping);
+
+ // set up HTML for manual ping test
+
+ var html = '<a href="/cgi-bin/fpingnodes.cgi?' + ips + ' ">Ping nodes</a>';
+ document.getElementById('ping_nodes').innerHTML = html;
+ }
+
+ // fire off next sig strength CGI, we iterate these over time to keep
+ // network load nominal
+
+ if (nodes[sigstr_node_index] != undefined) {
+ downloadUrl("/cgi-bin/getsig.cgi?ip=" + nodes[sigstr_node_index].ip, processgetSig);
+ document.getElementById('sigstr_current_node').innerHTML = nodes[sigstr_node_index].ip;
+ }
+
+ sigstr_node_index++;
+ if (sigstr_node_index >= nodes.length)
+ sigstr_node_index = 0;
+ }
+
+
+ // this function calls itself every update_time seconds
+ // used for peridoic updates (polling) of network stats
+
+ function timer() {
+ var today=new Date();
+ var h=today.getHours();
+ var m=today.getMinutes();
+ var s=today.getSeconds();
+
+ // set time, add a zero in front of numbers < 10
+
+ m=checkTime(m);
+ s=checkTime(s);
+ document.getElementById('time').innerHTML=h+":"+m+":"+s;
+
+ if (update_enable)
+ poll();
+
+ // restart this timer after update_time seconds
+
+ t=setTimeout('timer()',update_time*1000);
+ }
+
+
+ function checkTime(i) {
+ if (i<10) {
+ i="0" + i;
+ }
+ return i;
+ }
+
+
+ // called when Fping CGI returns
+
+ function processFping(doc, status) {
+
+ //log("processFping");
+
+ if (drag_in_progress)
+ return;
+
+ // walk node list and set colour depending on ping stats
+
+ var nodes_reachable = 0;
+ var nodes_poor_packet_loss = 0;
+ for (var j=0; j<nodes.length; j++) {
+ if (doc.indexOf(nodes[j].ip) == -1) {
+
+ // no ping from node
+
+ nodes[j].marker.setIcon("node_dead.png");
+
+ // update IIR averaging filter
+
+ nodes[j].packet_loss = 0.875*nodes[j].packet_loss + 0.125;
+
+ }
+ else {
+
+ // update IIR averaging filter
+
+ nodes[j].packet_loss = 0.875*nodes[j].packet_loss;
+ nodes_reachable++;
+ }
+
+ // Change icon based on packet loss
+
+ if (nodes[j].packet_loss < 0.1) {
+ nodes[j].marker.setIcon("node_good.png");
+ }
+ if ((nodes[j].packet_loss > 0.1) && (nodes[j].packet_loss < 0.9)) {
+ nodes[j].marker.setIcon("node_packet_loss.png");
+ nodes_poor_packet_loss++;
+ }
+ if (nodes[j].packet_loss > 0.9) {
+ nodes[j].marker.setIcon("node_dead.png");
+ nodes_poor_packet_loss++;
+ }
+
+ //log(" nodes[j].packet_loss");
+
+ var packet_loss_formatted = Math.round(nodes[j].packet_loss*100);
+
+ nodes[j].marker.setTitle(nodes[j].ip + ' Packet Loss: ' + packet_loss_formatted + '%');
+ var html = '<table>';
+ html += '<tr><th>' + nodes[j].ip + '</th></tr>';
+ html += '<tr><td>Packet Loss</td><td>' + packet_loss_formatted + '%</td></tr>';
+ if (nodes[j].neighbour_ips != undefined) {
+ for(var i=0; i<nodes[j].neighbour_ips.length; i++) {
+ html += '<tr><td>' + trim(nodes[j].neighbour_ips[i]) + '</td><td>';
+ html += trim(nodes[j].neighbour_sigs[i]) + ' dBm</td><td>';
+ }
+ }
+ html += '</table>';
+
+ nodes[j].infowindow.setContent(html);
+ }
+
+ document.getElementById('nodes_total').innerHTML = nodes.length;
+ document.getElementById('nodes_reachable').innerHTML = nodes_reachable;
+ document.getElementById('nodes_poor_packet_loss').innerHTML = nodes_poor_packet_loss;
+ }
+
+
+ // remove leading and trailing whitespace from a string
+
+ function trim(stringToTrim) {
+ return stringToTrim.replace(/^\s+|\s+$/g,"");
+ }
+
+
+ // called when getsig.cgi returns to grok signal
+ // strength information
+
+ function processgetSig(doc, status) {
+ log("processgetSig");
+
+ // extract our lines from the HTML using state machine
+
+ var lines = doc.split("\n");
+ var ip;
+ var neighour_ips;
+ var neighour_sigs
+ var state = "start";
+ var next_state;
+ for (var j=0; j<lines.length; j++) {
+
+ next_state = state;
+ if (state == "start") {
+ if (lines[j].indexOf('sigstr') != -1)
+ next_state = "read ips";
+ }
+ if (state == "read ips") {
+ neighbour_ips = lines[j].split(" ");
+ next_state = "read sigs";
+ }
+ if (state == "read sigs") {
+ neighbour_sigs = trim(lines[j]).split(" ");
+ next_state = "read ip";
+ }
+ if (state == "read ip") {
+ ip =lines[j];
+ next_state = "finished";
+ }
+ state = next_state;
+ }
+
+ if (state == "finished") {
+
+ // walk nodes list to match ip
+
+ for (var j=0; j<nodes.length; j++) {
+ if (nodes[j].ip == ip) {
+ nodes[j].neighbour_ips = neighbour_ips;
+ nodes[j].neighbour_sigs = neighbour_sigs;
+ var html="<table>";
+ for(var i=0; i<neighbour_ips.length; i++) {
+ html += '<tr><td>' + trim(neighbour_ips[i]) + '</td><td>' + trim(neighbour_sigs[i]) + ' dBm</td></tr>';
+ }
+ html += '</table>';
+ document.getElementById('sigstr_last_node').innerHTML = ip;
+ document.getElementById('sigstr_results').innerHTML = html;
+ }
+ }
+ }
+ }
+
+
+ // calculate distance between two points
+
+ rad = function(x) {return x*Math.PI/180;}
+
+ distHaversine = function(p1, p2) {
+ var R = 6371; // earth's mean radius in km
+ var dLat = rad(p2.lat() - p1.lat());
+ var dLong = rad(p2.lng() - p1.lng());
+
+ var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
+ Math.cos(rad(p1.lat())) * Math.cos(rad(p2.lat())) * Math.sin(dLong/2) * Math.sin(dLong/2);
+ var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
+ var d = R * c;
+
+ return d.toFixed(3);
+ }
+
+
+ // draw lines indicating links between nodes
+
+ function drawPathesBetweenNodes() {
+
+ // delete all existing paths, this is inefficient but is OK for a first pass
+
+ if (pathes) {
+ for (j in pathes) {
+ pathes[j].setMap(null);
+ }
+ pathes.length = 0;
+ }
+
+ // walk vis array looking for routes, I think this actually draws
+ // routes twice
+
+ for (var i=0; i<vis.data.length; i++) {
+
+ if (vis.data[i].neighbour != undefined) {
+
+ // this entry in vis array has a route and a neighbour
+
+ router_ip = vis.data[i].router;
+ neighbour_ip = vis.data[i].neighbour;
+
+ // walk nodes list to find router location on map
+
+ var router_location;
+ for (var j=0; j<nodes.length; j++) {
+
+ if (router_ip == nodes[j].ip) {
+ router_location = nodes[j].marker.position;
+ }
+ }
+
+ // walk nodes list to find neighbour location on map
+
+ var neighbour_location;
+ for (var j=0; j<nodes.length; j++) {
+
+ if (neighbour_ip == nodes[j].ip) {
+ neighbour_location = nodes[j].marker.position;
+ }
+ }
+
+ if ((router_location != undefined) && (neighbour_location != undefined)) {
+
+ // draw polyline between locations
+
+ var coords = [
+ router_location,
+ neighbour_location
+ ];
+ var path = new google.maps.Polyline({
+ path: coords,
+ strokeColor: "#008000",
+ strokeOpacity: 0.5,
+ strokeWeight: 5
+ });
+ path.setMap(map);
+
+ // click on line to get distance and path loss estimates
+
+ google.maps.event.addListener(path, "click", function() {
+ var path = this.getPath();
+ var dist = distHaversine(path.b[0], path.b[1]) * 1000;
+
+ // est rx power:
+ // MP tx pwr between 15 and 20dB depending on rate, so use 17dB
+ // roughly 2dB tx and rx antennas
+ // 100dB loss for 1000m, 6dB more for double distance
+
+ var path_loss = 100.0 + 20*Math.log(dist/1000)/Math.log(10);
+ path_loss = Math.round(path_loss);
+ var est_rx_sig = 17 + 2 + 2 - path_loss;
+
+ document.getElementById('distance').innerHTML = dist + ' m';
+ document.getElementById('path_loss').innerHTML = -path_loss + ' dBm';
+ document.getElementById('est_rx_sig').innerHTML = est_rx_sig + ' dBm';
+ });
+
+ pathes.push(path);
+ }
+ }
+ }
+ }
+
+
+ // sets up map for a new Node
+ // adds node to nodes[] array but doesn't write to text file
+
+ function addNewNode(lat, lng, newIp, isNewNode) {
+
+ // create the marker
+
+ var location = new google.maps.LatLng(lat, lng);
+ var newMarker = new google.maps.Marker(
+ {position: location,
+ map: map,
+ draggable: true,
+ title: newIp,
+ icon: "node_good.png" });
+
+ if (isNewNode) {
+ newMarker.setAnimation(google.maps.Animation.BOUNCE);
+ }
+
+ // add to nodes array
+
+ var newInfoWindow = new google.maps.InfoWindow(
+ { content: newIp
+ });
+ var newNode = {marker : newMarker,
+ ip: newIp,
+ infowindow: newInfoWindow,
+ packet_loss: 0.0};
+ nodes.push(newNode);
+
+ // add listeners
+
+ google.maps.event.addListener(newMarker, "dragstart", function() {
+
+ drag_in_progress = 1;
+
+ // remove node from nodes array
+
+ var new_nodes = [];
+ for (var j=0; j<nodes.length; j++) {
+ if (nodes[j].marker.position != newMarker.position) {
+ new_nodes.push(nodes[j]);
+ }
+ else {
+ // preserve some states for when we drop it
+ dragIp = nodes[j].ip;
+ drag_packet_loss = nodes[j].packet_loss;
+ }
+ }
+ nodes = new_nodes;
+
+ // delete node from text file
+
+ var url;
+ url = "/cgi-bin/delnode.cgi?" + "lat=" + this.position.lat() + "&" + "lng=" +
+ this.position.lng();
+ downloadUrl(url, function(doc) { });
+ });
+
+ google.maps.event.addListener(newMarker, "dragend", function() {
+
+ // stop any bouncing
+ newMarker.setAnimation(null);
+
+ // add node to nodes array
+
+ var newInfoWindow = new google.maps.InfoWindow(
+ { content: dragIp
+ });
+ var newNode = {marker : newMarker,
+ ip: dragIp,
+ infowindow: newInfoWindow,
+ packet_loss: drag_packet_loss
+ };
+ nodes.push(newNode);
+
+ // add node to text file
+
+ var url;
+ url = "/cgi-bin/addnode.cgi?" + "lat=" + this.position.lat() + "&" + "lng=" +
+ this.position.lng() + "&" + "ip=" + dragIp;
+ downloadUrl(url, function(doc) { });
+
+ drawPathesBetweenNodes();
+
+ drag_in_progress = 0;
+ });
+
+ google.maps.event.addListener(newMarker, "click", function() {
+
+ // walk node list to find our InfoWindow
+
+ for (var j=0; j<nodes.length; j++) {
+ if (nodes[j].marker.position == newMarker.position) {
+ nodes[j].infowindow.open(map, newMarker);
+ }
+ }
+
+ });
+
+ google.maps.event.addListener(newMarker, "rightclick", function() {
+
+ // remove node from nodes array
+
+ var new_nodes = [];
+ for (var j=0; j<nodes.length; j++) {
+ if (nodes[j].marker.position != newMarker.position) {
+ new_nodes.push(nodes[j]);
+ }
+ }
+ nodes = new_nodes;
+
+ // delete node from text file
+
+ var url;
+ url = "/cgi-bin/delnode.cgi?" + "lat=" + this.position.lat() + "&" + "lng=" + this.position.lng();
+ downloadUrl(url, function(doc) { });
+ this.setMap(null);
+
+ drawPathesBetweenNodes();
+
+ });
+
+ }
+
+
+ // save state (config info) to our cookie
+
+ function setCookie() {
+ var cookietext = cookiename;
+ cookietext += "=" + map.getCenter().lat();
+ cookietext += "|" + map.getCenter().lng() + "|" + map.getZoom();
+ cookietext += "|" + vis_server_ip + "|" + update_time;
+ cookietext += "|" + update_enable + "|" + debug_enable;
+ if (cookieexpy) {
+ var exdate=new Date();
+ exdate.setDate(exdate.getDate()+cookieexpy);
+ cookietext += ";expires="+exdate.toGMTString();
+ }
+
+ // write the cookie
+
+ document.cookie = cookietext;
+ }
+
+
+ // Grab a bunch of config info from our cookie
+
+ function getCookie() {
+
+ if (document.cookie.length > 0) {
+ cookieStart = document.cookie.indexOf(cookiename + "=");
+
+ // lets see if our cookie exists
+
+ if (cookieStart!=-1) {
+ cookieStart += cookiename.length+1;
+ cookieEnd=document.cookie.indexOf(";",cookieStart);
+ if (cookieEnd==-1) {
+ cookieEnd=document.cookie.length;
+ }
+ cookietext = document.cookie.substring(cookieStart,cookieEnd);
+
+ // split the cookie text and create the variables
+
+ bits = cookietext.split("|");
+ lat = parseFloat(bits[0]);
+ lng = parseFloat(bits[1]);
+ myzoom = parseInt(bits[2]);
+ vis_server_ip = (bits[3]);
+ update_time = parseInt(bits[4]);
+ update_enable = parseInt(bits[5]);
+ debug_enable = parseInt(bits[6]);
+ }
+ }
+ else {
+ known_location = false; // don't detect and display new nodes
+ alert("Move to your " +
+ "current location then refresh to detect nodes.");
+
+ }
+
+ }
+
+
+ // clear database and set cookie to defaults
+
+ function clearDatabase() {
+
+ // call CGI to rm nodes.txt
+
+ downloadUrl("/cgi-bin/cleardb.cgi", function(doc) { });
+ nodes = [];
+
+ // I would really like to clear cookie but can't work out how, so
+ // set to defaults instead,
+
+ var myLatLng = new google.maps.LatLng(0, 0);
+ map.setCenter(myLatLng);
+ map.setZoom(2);
+ update_enable = 0;
+ }
+
+
+ // called when we click to add a node on map
+
+ function placeMarker(location, ip, isNewNode) {
+ if (ip == undefined) {
+ ip = "";
+ }
+ if (isNewNode == undefined) {
+ isNewNode = false;
+ }
+
+ addNewNode(location.lat(), location.lng(), ip, isNewNode);
+
+ // save to marker file by calling a CGI
+
+ var url;
+ url = "/cgi-bin/addnode.cgi?" + "lat=" + location.lat() + "&" + "lng=" + location.lng() + "&" + "ip=" + ip;
+ downloadUrl(url, function(doc) { });
+ }
+
+
+ /**
+ * Returns an XMLHttp instance to use for asynchronous
+ * downloading. This method will never throw an exception, but will
+ * return NULL if the browser does not support XmlHttp for any reason.
+ * @return {XMLHttpRequest|Null}
+ */
+ function createXmlHttpRequest() {
+ try {
+ if (typeof ActiveXObject != 'undefined') {
+ return new ActiveXObject('Microsoft.XMLHTTP');
+ } else if (window["XMLHttpRequest"]) {
+ return new XMLHttpRequest();
+ }
+ } catch (e) {
+ changeStatus(e);
+ }
+ return null;
+ };
+
+ /**
+ * This functions wraps XMLHttpRequest open/send function.
+ * It lets you specify a URL and will call the callback if
+ * it gets a status code of 200.
+ * @param {String} url The URL to retrieve
+ * @param {Function} callback The function to call once retrieved.
+ */
+ function downloadUrl(url, callback) {
+ var status = -1;
+ var request = createXmlHttpRequest();
+ if (!request) {
+ return false;
+ }
+
+ request.onreadystatechange = function() {
+ if (request.readyState == 4) {
+ try {
+ status = request.status;
+ } catch (e) {
+ // Usually indicates request timed out in FF.
+ }
+ if (status == 200) {
+ callback(request.responseText, request.status);
+ request.onreadystatechange = function() {};
+ }
+ }
+ }
+ request.open('GET', url, true);
+ try {
+ request.send(null);
+ } catch (e) {
+ changeStatus(e);
+ }
+ };
+
+ function log(message) {
+ if (debug_enable == 1) {
+ message_list = document.getElementById("debugging_messages");
+ log_message = document.createTextNode(message);
+ log_list_item = document.createElement('li');
+ log_list_item.appendChild(log_message);
+ message_list.appendChild(log_list_item);
+ }
+ }
+
+ function debugClicked() {
+ if (document.control_panel.debug_enable.checked)
+ debug_enable = 1;
+ else
+ debug_enable = 0;
+ }
+
+ function updateEnableClicked() {
+ if (document.control_panel.update_enable.checked)
+ update_enable = 1;
+ else
+ update_enable = 0;
+ }
+
+ function setVisServerIp() {
+ vis_server_ip = document.control_panel.vis_server_ip.value;
+ var html = '<a href="/cgi-bin/getvis.cgi?ip=' + vis_server_ip + ' ">Test Vis Server Connection</a>';
+ document.getElementById('test_vis_server').innerHTML = html;
+ }
+
+ function setUpdateTime() {
+ update_time = document.control_panel.update_time.value;
+ }
+
+</script>
+
+</head>
+<body onload="initialize()" onunload="setCookie()">
+
+ <div id="main-map">
+ </div>
+
+ <div id="side">
+
+ <h3>Help</h3>
+ <table>
+ <tr><td width="5%"></td><td><a href="https://freetel.svn.sourceforge.net/svnroot/freetel/dilimesh/README.txt">DiliMesh README</a></td></tr>
+ </table>
+
+ <div id="stats" style="width: 100%;">
+ <h3>Network Stats</h3>
+ <table>
+ <tr><td width="5%"></td><td>Time</td><td><div id="time"></div></td></tr>
+ <tr><td></td><td>Nodes Reachable</td><td><div id="nodes_reachable"></div></td></tr>
+ <tr><td></td><td>Nodes Poor Packet Loss</td><td><div id="nodes_poor_packet_loss"></div></td></tr>
+ <tr><td></td><td>Nodes Total</td><td><div id="nodes_total"></div></td></tr>
+ </table>
+ </div>
+
+ <div id="control" style="width: 100%;">
+ <h3>Contol Panel</h3>
+ <form name="control_panel">
+ <table>
+ <tr>
+ <td width="5%">
+ <td>Visualisation Server IP</td>
+ <td colspan="2"><input type="text" name="vis_server_ip" size="15"></td>
+ <td width="50"><input type="submit" value="Set" onclick="setVisServerIp()"></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>Update Enable</td>
+ <td><input type="checkbox" name="update_enable" value="1" onclick="updateEnableClicked()"></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>Update Time (s)</td>
+ <td><input type="text" name="update_time" size="3"></td>
+ <td width="30%"></td>
+ <td width="50"><input type="submit" value="Set" onclick="setUpdateTime()"></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>Clear Database</td>
+ <td></td>
+ <td width="30%"></td>
+ <td width="60"><input type="submit" value="Clear" onclick="clearDatabase()"></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>Debug Messages</td>
+ <td><input type="checkbox" name="debug_enable" onclick="debugClicked()"></td>
+ </tr>
+ </table>
+ </form>
+
+ <h3>Signal Strength</h3>
+ <table>
+ <tr>
+ <td width="5%"></td>
+ <td>Currently Polling</td>
+ <td><div id="sigstr_current_node"></div></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>Last Node that<br>Responded</td>
+ <td><div id="sigstr_last_node"></div></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td valign=top>Results</td>
+ <td><div id="sigstr_results"></div></td>
+ </tr>
+ </table>
+
+ <h3>Distance</h3>
+ <table>
+ <tr>
+ <td width="5%"></td>
+ <td valign=top>Distance</td>
+ <td><div id="distance"></div></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td valign=top>Path Loss</td>
+ <td><div id="path_loss"></div></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td valign=top>Est Rx Signal</td>
+ <td><div id="est_rx_sig"></div></td>
+ </tr>
+ </table>
+ </div>
+
+ <div id="tests" style="width: 100%;">
+ <h3>Tests</h3>
+ <ol>
+ <li><a href="/cgi-bin/getnodes.cgi">Test nodes.txt database</a><br>
+ <li><div id="test_vis_server"> </div>
+ <li><div id="ping_nodes"> </div>
+ </ol>
+ </div>
+
+ <div id="debugging_output" style="width: 100%;">
+ <h3>Debugging Output</h3>
+ <ol id="debugging_messages">
+ </ol>
+ </div>
+ </div>
+
+
+</body>
+</html>
--- /dev/null
+#!/bin/sh
+telnetd -l /usr/sbin/sigstr.sh -p 4950
--- /dev/null
+#!/bin/sh
+# node_tune.sh
+# David Rowe 13 April 2010
+#
+# Prints information useful for tuning a mesh node
+#
+# Useful to run as a telnet login, e.g.
+# telnetd -l /usr/sbin/node_tune.sh -p 4950
+# will let you plug a laptop into eth0 and "telnet 192.168.1.20 4950"
+# to run this script
+
+ # list of nodes this node can see in IP and MAC order
+
+ IP=`cat /proc/net/arp | grep "10.130.1" | awk '{ print $1 }'`
+ MAC=`cat /proc/net/arp | grep "10.130.1" | awk '{ print $4 }'`
+
+ echo
+ echo "sigstr"
+ echo $IP
+ for m in $MAC
+ do
+ wlanconfig ath0 list | grep $m | awk '{ printf " %s",$6 }'
+ done
+ echo
+
+
+
+
+++ /dev/null
-README.txt for dilimesh
-David Rowe
-Dec 7 2010
-
-
-Introduction
-------------
-
-Simple mesh network mapping application (web app) used as a test bed
-for link debugging techniques in the Dili Village Telco.
-
-* Screen shot: dilimesh_screenshot.png
-
-* Currently displays packet loss stats as an alternative to batman
- scores. The purpose of this project is to experiment with the
- display of different information, for example display signal
- strength or even a spectrum of local Wifi activity for each nodes.
- The goal is gather information to debug problem links.
-
-* Based on Google maps V3 API.
-
-* Light weight: On the server we have just a few CGIs and a text file
- database. A browser cookie is used for storing configuration
- information. The CGIs are a few lines of shell script, so can run
- on any machine. Installation involves copying a few files and
- setting a few permissions.
-
-* Loosely coupled design: Can work without any additional software on
- each mesh mode, just needs a Visualisation server somewhere in the
- mesh. Doesn't affct Mesh Potato operation, or the operation of
- other software on the mesh.
-
-
-Using Dilimesh
---------------
-
-1/ Point your browser at http://server/dilimesh/dilimesh/html
-
-2/ Set "Visualisation Server IP" on RHS and reload page.
-
-3/ Dilimesh will find new nodes automatically. Drag bouncing nodes
- to the correct position on map.
-
-4/ Mouse over or click on a node to get IP and packet loss. If nodes
- are running signal strength daemon signal strengths of adjacent
- nodes will also be displayed.
-
-5/ Click on links to get "Distance" stats on lower right hand side.
-
-5/ Node colours:
- * blue - packet loss < 10%
- * red - packet loss between 10% and 90%
- * black - packet loss > 90%
-
-5/ "Update Enable" will update packet loss, network links and signal
- strength automatically.
-
-
-Status
-------
-
-* Alpha
-* Tested on Firefox 3.5.3 with Apache2 servers running Ubuntu 9 & 10.
-* Tested on small 5 node mesh network, might needs tweaks (e.g. fping
- arguments) for larger networks
-
-
-Implementation Notes
---------------------
-
-Dilimesh is implemented in Javascript (dilimesh.html). A simple text
-database file /var/www/dilimesh/nodes.txt is used to store node
-locations and IPs. Browser cookies are used to store config
-information like our last map location and vis server IP. Very simple
-1 page CGIs written in shell script are used to access the node
-database.
-
-The 'Set' buttons on the RHS cause dilimesh.html to reload with CGI
-type variables passed to it. However these variables are not used, we
-use onclick() methods to change the state variables which are stored
-in a cookie when the page exits.
-
-All the CGI scripts assume a hard coded path of /var/www/dilimesh for
-the nodes.txt database.
-
-
-Directories
------------
-
-"dilimesh" directory:
-
- dilimesh.html - all the Javascript for Dilimesh
- dilimesh.css - style sheet
- node_good.png
- node_packet_loss.png
- node_dead.png - node icon PNGs, changes with packet loss
-
-"cgi-bin" directory:
-
- addnode.cgi - adds a node to nodes.txt
- delnode.cgi - deletes a node from nodes.txt
- getnodes.cgi - reads nodes.txt database
- getvis.cgi - reads vis server data
- fpingnodes.cgi - fires off pings for all nodes, returns results
- getsig.cgi - reads signal strength of surrounding nodes from one node,
- requires sigstr.sh telnet daemon running on node.
-
-"scripts" directory:
-
- sigstr.sh - script that runs on MP as a telnet daemon to return signal
- strength of each node to getsig.cgi. Experimental.
- S99sigstr - /etc/init.d Mesh Potato start upscript for sigstr.sh telnet
- daemon
-
-Software
---------
-
-You need:
-
-1/ A web server. Apache is assumed in the /usr/lib/cgi-bin path below.
- The paths may be different for other web servers. Note the path
- to /var/www/dilimesh is hard coded in the CGI scripts so it's
- best not to change that.
-
-2/ netcat (nc) and fping installed on the server. Note that on Ubuntu
- we found that netcat 1.10.38 was required, netcat-openbsd would not
- work.
-
-3/ A Batman mesh network with the vis server on one of the nodes, this
- may already be running on your supernode. Batman on each node should
- be configured for the vis server (e.g. -s 10.130.1.1)
-
-4/ A route from the web server PC to the mesh network. On the Ubuntu
- machine I use as a server I put this script in the file
- /etc/network/if-up.d/potato:
-
- !/bin/sh
- # Add interface and routes to connect to potato mesh network
-
- ifconfig eth0 10.30.1.3 netmask 255.255.255.0
- route add -net 10.130.1.0/24 gw 10.30.1.1
-
- This file runs when the machine boots and connects to the Supernode
- via Ethernet.
-
-Installation
-------------
-
-1/ Server PC
-
- $ svn co https://freetel.svn.sourceforge.net/svnroot/freetel/dilimesh
- $ cd dilimesh
- $ sudo mkdir /var/www/dilimesh
- $ sudo chmod 777 /var/www/dilimesh
- $ cp dilimesh/* /var/www/dilimesh
- $ sudo cp cgi-bin/* /usr/lib/cgi-bin
-
-2/ Supernode
-
- Configure vis server to work on Wifi and Ethernet interfaces:
-
- # root@OpenWrt:~# cat /etc/config/vis
- config vis general
- option 'interface' 'ath0 eth0'
-
- If it's running OK you should see a process like:
-
- # ps | grep vis
- 711 root 1508 S vis -j ath0 eth0
-
-3/ Batman nodes
-
- Configure you nodes to use the visualisation server:
-
- # cat /etc/config/batman
- config batmand general
- option interface ath0
- option announce
- option gateway_class
- option originator_interval
- option preferred_gateway
- option routing_class
- option visualisation_srv 10.130.1.1
- option policy_routing_script
-
- When batman is running with the vis server it should look like:
-
- # ps | grep batman
- 552 root 1564 S batmand -s 10.130.1.1 ath0
-
-4/ (Optional) Signal strength daemon:
-
- Copy these scripts to each MP in your mesh that you wish to read sig
- strength from:
-
- $ cd dilimesh/script
- $ scp sigstr.sh root@10.130.1.36:/usr/sbin
- $ scp S99sigstr root@10.130.1.36:/etc/rc.d
-
- Now start daemon on remote machine:
-
- $ ssh root@10.130.1.36 '/etc/rc.d/S99sigstr'
-
- Test if daemon is running
-
- $ telnet 10.130.1.36 4950
-
-Tests
------
-
-1/ Server PC can reach node with vis server:
-
- $ ping 192.168.1.100
-
-2/ Server PC has route to mesh nodes:
-
- $ ping 10.130.1.1
- $ ping 10.130.1.2
-
-3/ Test you have installed fping:
-
- $ fping 10.130.1.1
-
-4/ Test netcat and vis server:
-
- $ nc 192.168.1.100 2005
-
- HTTP/1.0 200 OK
- Content-type: application/json
-
- [
- { router : "10.130.1.1", neighbour : "10.130.1.23", label : 1.02 },
- { router : "10.130.1.14", neighbour : "10.130.1.56", label : 1.76 }
- ]
-
-5/ Test CGIs:
-
- Point your browser at:
-
- http://localhost/cgi-bin/fpingnodes.cgi?ip=10.130.1.1
-
-6/ Test reading and writing nodes.txt database with your browser:
-
- http://localhost/cgi-bin/addnode.cgi?lat=123&lng=456&ip=10.130.1.1
-
- $ cat /var/www/dilimesh/nodes.txt
- 123,456,10.130.1.1
-
- http://localhost/cgi-bin/delnode.cgi?lat=123&lng=456&ip=10.130.1.1
-
- $ cat /var/www/dilimesh/nodes.txt
- (empty file)
-
-7/ Test signal strength daemon on each node:
-
- $ telnet 10.130.1.1 4950
- Trying 10.130.1.36...
- Connected to 10.130.1.36.
- Escape character is '^]'.
-
- 10.130.1.1 10.130.1.56 10.130.1.14
- -64 -90 -75
-
-
-Debugging
----------
-
-1/ Monitor Apache log:
-
- $ tail -f /var/log/apache2/access.log
-
-2/ Use Firebug on Firefox to single step, set breakpoints etc.
-
-3/ Check nodes.txt database, each line is (lat, lng, IP):
-
- # cat /var/www/dilimesh/nodes.txt
-
- -34.88548650005714,138.55225324630737,10.130.1.56
- -34.88006501016277,138.55394840240479,10.130.1.1
- -34.87893842193011,138.55278968811035,10.130.1.23
- -34.882511765792316,138.55210304260254,10.130.1.14
- -34.880364257566484,138.5518455505371,10.130.1.36
-
-4/ The "dilimesh" cookie stores a lot of our state. On Firefox 3.5 you
- can remove the dilimesh cookie using Edit-Preferences-Privacy, then
- click on "remove individual cookies".
-
-5/ To reset to defaults:
-
- * move to another page (cookie is svaed when we exit page)
- * rm -f /var/www/dilimesh/nodes.txt
- * Delete cookie using setp (4) above
+++ /dev/null
-#!/bin/sh
-# addnode.cgi
-# David Rowe 4 Dec 2010
-#
-# CGI to add a new node
-
-cat <<EOF
-Content-type: text/html
-
-<html>
-<head>
-<meta http-equiv="cache-control" content="no-cache">
-<meta http-equiv="pragma" content="no-cache">
-<meta http-equiv="expires" content="-1"></head></head>
-<body>
-EOF
-
-lat=`echo "$QUERY_STRING" | sed -n "s/.*lat=\(.*\)&lng.*/\1/p"`
-lng=`echo "$QUERY_STRING" | sed -n "s/.*lng=\(.*\)&ip.*/\1/p"`
-ip=`echo "$QUERY_STRING" | sed -n "s/.*ip=\(.*\)/\1/p"`
-
-echo $lat,$lng,$ip >> /var/www/dilimesh/nodes.txt
-
-echo $QUERY_STRING "<br>"
-echo "<br>"
-echo $lat $lng $ip
-
-cat <<EOF
-</body>
-</html>
-EOF
+++ /dev/null
-#!/bin/sh
-# cleardb.cgi
-# David Rowe 15 Dec 2010
-#
-# CGI to delete entire node database
-
-cat <<EOF
-Content-type: text/html
-
-<html>
-<head>
-<meta http-equiv="cache-control" content="no-cache">
-<meta http-equiv="pragma" content="no-cache">
-<meta http-equiv="expires" content="-1"></head></head>
-<body>
-EOF
-
-# path to node database text file
-
-P=/var/www/dilimesh
-rm $P/nodes.txt
-
-cat <<EOF
-</body>
-</html>
-EOF
+++ /dev/null
-#!/bin/sh
-# delnode.cgi
-# David Rowe 4 Dec 2010
-#
-# CGI to delete a node
-
-cat <<EOF
-Content-type: text/html
-
-<html>
-<head>
-<meta http-equiv="cache-control" content="no-cache">
-<meta http-equiv="pragma" content="no-cache">
-<meta http-equiv="expires" content="-1"></head></head>
-<body>
-EOF
-
-lat=`echo "$QUERY_STRING" | sed -n "s/lat=\(.*\)&.*/\1/pg"`
-lng=`echo "$QUERY_STRING" | sed -n "s/.*lng=\(.*\)/\1/pg"`
-
-# path to node database text file
-
-P=/var/www/dilimesh
-
-cat $P/nodes.txt | sed "/$lat,$lng.*/ d" > $P/nodes.tmp
-cp $P/nodes.tmp $P/nodes.txt
-rm $P/nodes.tmp
-
-#echo $QUERY_STRING "<br>"
-#echo "<br>"
-#echo $lat $lng
-
-cat <<EOF
-</body>
-</html>
-EOF
+++ /dev/null
-#!/bin/sh
-# fpingnodes.cgi
-# David Rowe 5 Dec 2010
-#
-# CGI to fping a bunch of nodes
-
-cat <<EOF
-Content-type: text/html
-
-<html>
-<head>
-<meta http-equiv="cache-control" content="no-cache">
-<meta http-equiv="pragma" content="no-cache">
-<meta http-equiv="expires" content="-1"></head></head>
-<body>
-EOF
-
-ip=`echo "$QUERY_STRING" | sed -n "s/.*ip=//p" | sed "s/,/ /g"`
-fping $ip -a -b1400 -r1 2>/dev/null
-
-cat <<EOF
-</body>
-</html>
-EOF
+++ /dev/null
-#!/bin/sh
-# getnodes.cgi
-# David Rowe 5 Dec 2010
-#
-# CGI to return node list from database text file
-# We use a CGI rather than fetching the text file directly
-# so we can control Firefox from caching
-
-cat <<EOF
-Content-type: text/html
-
-<html>
-<head>
-<meta http-equiv="cache-control" content="no-cache">
-<meta http-equiv="pragma" content="no-cache">
-<meta http-equiv="expires" content="-1">
-</head>
-<body>
-EOF
-
-NODES=/var/www/dilimesh/nodes.txt
-
-if [ -f $NODES ] ; then
- cat $NODES
-fi
-
-cat <<EOF
-</body>
-</html>
-EOF
+++ /dev/null
-#!/bin/sh
-# getsig.cgi
-# David Rowe 6 Dec 2010
-#
-# CGI to fetch the signal strength from a node
-# Requires the node to be running sigstr.sh script as
-# a telnet dameon port 4950
-
-cat <<EOF
-Content-type: text/html
-
-<html>
-<head>
-<meta http-equiv="cache-control" content="no-cache">
-<meta http-equiv="pragma" content="no-cache">
-<meta http-equiv="expires" content="-1"></head></head>
-<body>
-EOF
-
-ip=`echo "$QUERY_STRING" | sed -n "s/.*ip=//p" | sed "s/,/ /g"`
-nc $ip 4950
-echo $ip
-
-cat <<EOF
-</body>
-</html>
-EOF
+++ /dev/null
-#!/bin/sh
-# getvis.cgi
-# David Rowe 4 Dec 2010
-#
-# CGI to fetches vis server data
-# I am sure it is possible to do this directly from Javascript
-# but I am too much of a noob.
-
-cat <<EOF
-Content-type: text/html
-
-<html>
-<head>
-<meta http-equiv="cache-control" content="no-cache">
-<meta http-equiv="pragma" content="no-cache">
-<meta http-equiv="expires" content="-1"></head>
-<body>
-EOF
-
-VIS_SERVER_HOST=`echo "$QUERY_STRING" | sed -n "s/.*ip=//p"`
-
-nc $VIS_SERVER_HOST 2005 2>/dev/null >/dev/null
-if [ $? -eq 0 ] ; then
- nc $VIS_SERVER_HOST 2005 | sed -n "/{/ p"
-else
- echo "Can't connect to Visualisation Server on $VIS_SERVER_HOST"
-fi
-
-cat <<EOF
-</body>
-</html>
-EOF
-
+++ /dev/null
-html,body{
- margin:0;
- padding:0;
- width:100%;
- height:100%;
- font-family:Arial, Helvetica, sans-serif;
-}
-
-div#header{
- vertical-align:middle;
- border-bottom:1px solid #000;
-}
-div#main-map{
- width:70%;
- height:100%;
- float:left;
-}
-div#side{
- width:30%;
- float:left;
-}
-
-div#dataPanel{
- width:90%;
- height:50%;
- overflow:auto;
- border:2px solid #DDDDDD;
-}
-input{
- width:90%;
-}
-
-input.navi{
- font-size:12px;
- height:30px;
- margin-bottom:10px;
-}
-div ul{
- margin-top:30px;
- margin-bottom:30px;
-}
-div ul li{
- display: inline;
- list-style-type: none;
- padding-right: 40px;
- font-size:18px;
- font-weight:bold;
-}
-
-div ul li.title{
- font-size:22px;
- color:#888;
-}
-
-div#header p{
- color:#888;
- font-size:14px;
- padding-left:20px;
-}
-span.instruction{
- font-weight:bold;
-
-}
-
-.message-box { text-align: center; padding: 5px; color:#545454; width:80%; margin:5px auto; font-size:12px;}
-.clean { background-color: #efefef; border-top: 2px solid #dedede; border-bottom: 2px solid #dedede; }
-.info { background-color: #f7fafd; border-top: 2px solid #b5d3ff; border-bottom: 2px solid #b5d3ff; }
-.ok { background-color: #d7f7c4; border-top: 2px solid #82cb2f; border-bottom: 2px solid #82cb2f; }
-.alert { background-color: #fef5be; border-top: 2px solid #fdd425; border-bottom: 2px solid #fdd425; }
-.error { background-color: #ffcdd1; border-top: 2px solid #e10c0c; border-bottom: 2px solid #e10c0c; }
\ No newline at end of file
+++ /dev/null
-<!DOCTYPE html>
-<html>
-<head>
-<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
-<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
-<style type="text/css">
- html { height: 100% }
- body { height: 100%; margin: 0px; padding: 0px }
- #map_canvas { height: 100% }
-</style>
-<title>Dili Mesh</title>
-
-<link rel="stylesheet" type="text/css" href="dilimesh.css">
-<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
-
-<script type="text/javascript">
-
- /*
- dilimesh.html
- David Rowe
- Dec 4 2010
-
- Simple mesh network mapping application used as a test bed for debugging
- problem links in the Dili Village Telco.
-
- See dilimesh README.txt for more information.
- */
-
- var map;
-
- // cookie params
-
- var cookiename = "dilimesh0001";
- var cookieexpy = 7;
-
- // these are stored in cookie, with default values
-
- var lat = 0;
- var lng = 0;
- var myzoom = 2;
- var vis_server_ip = "192.168.1.100";
- var update_enable = 0;
- var update_time = 5;
- var debug_enable = 0;
- var known_location = true;
-
- // state variables we use while dragging
-
- var dragIp;
- var drag_packet_loss;
- var drag_in_progress;
-
- // pathes, vis server data, node list
-
- var pathes = []; // array of lines, used to delete them
- var vis = []; // array of vis server information
- var nodes = []; // database of nodes we keep in memory
- // mirrors nodes.txt but also store marker locations
-
- // states for polling sig strength
-
- var sigstr_node_index = 0;
-
- // Called when we load page
-
- function initialize() {
-
- // we use our cookie as a mini-databse for map location,
- // control panel values etc
-
- getCookie();
-
- var myLatlng = new google.maps.LatLng(lat, lng);
- var myOptions = {
- zoom: myzoom,
- center: myLatlng,
- scaleControl: true,
- mapTypeId: google.maps.MapTypeId.ROADMAP
- }
- map = new google.maps.Map(document.getElementById("main-map"), myOptions);
-
- // we don't want manual placement of nodes, use vis server to pop
- // them up
- //google.maps.event.addListener(map, 'click', function(event) {
- // placeMarker(event.latLng);
- //});
-
- // set up initial form values
-
- document.control_panel.vis_server_ip.value = vis_server_ip;
- document.control_panel.update_time.value = update_time;
- document.control_panel.update_enable.checked = update_enable;
- document.control_panel.debug_enable.checked = debug_enable;
- var html = '<a href="/cgi-bin/getvis.cgi?ip=' + vis_server_ip + ' ">Test Vis Server Connection</a>';
- document.getElementById('test_vis_server').innerHTML = html;
-
- // read from nodes.txt database text file on server and init map
-
- downloadUrl("/cgi-bin/getnodes.cgi", processMarkers);
- }
-
-
- // Slurp up nodes.txt when web pages loads
-
- processMarkers = function(doc, status) {
- log("processMarkers");
-
- // split the document into lines
-
- var lines = doc.split("\n");
- for (var i=0; i<lines.length; i++) {
- if (lines[i].length > 1) {
-
- // split each line into parts separated by "," and use the contents
-
- parts = lines[i].split(",");
- if (parts.length > 1) {
- var lat = parseFloat(parts[0]);
- var lng = parseFloat(parts[1]);
- var ip = parts[2];
- if (ip == undefined) {
- ip = "";
- }
-
- // sanity check, make sure no duplicate IPs
-
- if (dupIp(ip))
- log("Error - Duplicate IP " + ip + " in nodes.txt");
- else
- addNewNode(lat, lng, ip, false);
- }
- }
- }
-
- // initial map display, only if we have a sensible map location. Otherwise
- // new nodes will be placed in middle of current default location
-
- if (known_location)
- poll();
-
- // kick off timer process that periodically fires off vis and fping CGIs
-
- timer();
- }
-
-
- // check for duplicate IPs
-
- function dupIp(ip) {
-
- if ( (ip == undefined) || (ip == ""))
- return false;
-
- // walk nodes list looking for this IP
-
- for (var j=0; j<nodes.length; j++) {
- if (nodes[j].ip == ip) {
- return true;
- }
- }
-
- return false;
-
- }
-
-
- // Process the Vis server information
-
- processVis = function(doc, status) {
- log("processVis");
-
- if (drag_in_progress)
- return;
-
- // extract the JSON lines from doc returned by getvis.cgi
- // there is surely a better way to do this but I am a JSON noob
-
- var lines = doc.split("\n");
- var vis_str = "vis = {data: [\n";
- for (var i=0; i<lines.length; i++) {
- if (lines[i].length > 1) {
- if (lines[i].indexOf('{') != -1) {
- vis_str = vis_str + lines[i] + "\n";
- }
- }
- }
-
- vis_str = vis_str + "]\n};"
- eval(vis_str); // run time compilation of JSON string
-
- // OK now we have an array that we can parse for new nodes
-
- // walk vis list by router
-
- var new_nodes = 0;
- for (var i=0; i<vis.data.length; i++) {
-
- // see if this node exists in our database
-
- var node_exists = 0;
- for (var j=0; j<nodes.length; j++) {
-
- if (vis.data[i].router == nodes[j].ip) {
- node_exists = 1;
- }
- }
-
- // create node if it doesn't exists already
-
- if (node_exists == 0) {
- // small offset makes sure we don't create them all on one spot
- var location = new google.maps.LatLng(lat + 1E-4*nodes.length, lng);
- placeMarker(location, vis.data[i].router, true);
- }
- }
-
- // walk vis list by neighbour
-
- for (var i=0; i<vis.data.length; i++) {
-
- if (vis.data[i].neighbour != undefined) {
-
- // see if this node exists in our database
-
- var node_exists = 0;
- for (var j=0; j<nodes.length; j++) {
-
- if (vis.data[i].neighbour == nodes[j].ip) {
- node_exists = 1;
- }
- }
-
- // create node if it doesn't exists already
-
- if (node_exists == 0) {
- // small offset makes sure we don't create them all on one spot
- var location = new google.maps.LatLng(lat + 1E-4*nodes.length, lng);
- placeMarker(location, vis.data[i].neighbour, true);
- }
- }
- }
-
- // now all nodes are logged in text file and nodes database
- // lets see if we any have routes between them
-
- drawPathesBetweenNodes();
- }
-
-
- function poll() {
-
- // fire off vis server CGI
-
- downloadUrl("/cgi-bin/getvis.cgi?ip=" + vis_server_ip, processVis);
-
- // fire off fping CGI
-
- var ips = "ip=";
- for (var j=0; j<nodes.length; j++) {
- if (nodes[j].ip != "") {
- ips = ips + nodes[j].ip;
- if (j != (nodes.length-1)) {
- ips = ips + ",";
- }
- }
- }
-
- if (ips != "") {
- downloadUrl("/cgi-bin/fpingnodes.cgi?" + ips, processFping);
-
- // set up HTML for manual ping test
-
- var html = '<a href="/cgi-bin/fpingnodes.cgi?' + ips + ' ">Ping nodes</a>';
- document.getElementById('ping_nodes').innerHTML = html;
- }
-
- // fire off next sig strength CGI, we iterate these over time to keep
- // network load nominal
-
- if (nodes[sigstr_node_index] != undefined) {
- downloadUrl("/cgi-bin/getsig.cgi?ip=" + nodes[sigstr_node_index].ip, processgetSig);
- document.getElementById('sigstr_current_node').innerHTML = nodes[sigstr_node_index].ip;
- }
-
- sigstr_node_index++;
- if (sigstr_node_index >= nodes.length)
- sigstr_node_index = 0;
- }
-
-
- // this function calls itself every update_time seconds
- // used for peridoic updates (polling) of network stats
-
- function timer() {
- var today=new Date();
- var h=today.getHours();
- var m=today.getMinutes();
- var s=today.getSeconds();
-
- // set time, add a zero in front of numbers < 10
-
- m=checkTime(m);
- s=checkTime(s);
- document.getElementById('time').innerHTML=h+":"+m+":"+s;
-
- if (update_enable)
- poll();
-
- // restart this timer after update_time seconds
-
- t=setTimeout('timer()',update_time*1000);
- }
-
-
- function checkTime(i) {
- if (i<10) {
- i="0" + i;
- }
- return i;
- }
-
-
- // called when Fping CGI returns
-
- function processFping(doc, status) {
-
- //log("processFping");
-
- if (drag_in_progress)
- return;
-
- // walk node list and set colour depending on ping stats
-
- var nodes_reachable = 0;
- var nodes_poor_packet_loss = 0;
- for (var j=0; j<nodes.length; j++) {
- if (doc.indexOf(nodes[j].ip) == -1) {
-
- // no ping from node
-
- nodes[j].marker.setIcon("node_dead.png");
-
- // update IIR averaging filter
-
- nodes[j].packet_loss = 0.875*nodes[j].packet_loss + 0.125;
-
- }
- else {
-
- // update IIR averaging filter
-
- nodes[j].packet_loss = 0.875*nodes[j].packet_loss;
- nodes_reachable++;
- }
-
- // Change icon based on packet loss
-
- if (nodes[j].packet_loss < 0.1) {
- nodes[j].marker.setIcon("node_good.png");
- }
- if ((nodes[j].packet_loss > 0.1) && (nodes[j].packet_loss < 0.9)) {
- nodes[j].marker.setIcon("node_packet_loss.png");
- nodes_poor_packet_loss++;
- }
- if (nodes[j].packet_loss > 0.9) {
- nodes[j].marker.setIcon("node_dead.png");
- nodes_poor_packet_loss++;
- }
-
- //log(" nodes[j].packet_loss");
-
- var packet_loss_formatted = Math.round(nodes[j].packet_loss*100);
-
- nodes[j].marker.setTitle(nodes[j].ip + ' Packet Loss: ' + packet_loss_formatted + '%');
- var html = '<table>';
- html += '<tr><th>' + nodes[j].ip + '</th></tr>';
- html += '<tr><td>Packet Loss</td><td>' + packet_loss_formatted + '%</td></tr>';
- if (nodes[j].neighbour_ips != undefined) {
- for(var i=0; i<nodes[j].neighbour_ips.length; i++) {
- html += '<tr><td>' + trim(nodes[j].neighbour_ips[i]) + '</td><td>';
- html += trim(nodes[j].neighbour_sigs[i]) + ' dBm</td><td>';
- }
- }
- html += '</table>';
-
- nodes[j].infowindow.setContent(html);
- }
-
- document.getElementById('nodes_total').innerHTML = nodes.length;
- document.getElementById('nodes_reachable').innerHTML = nodes_reachable;
- document.getElementById('nodes_poor_packet_loss').innerHTML = nodes_poor_packet_loss;
- }
-
-
- // remove leading and trailing whitespace from a string
-
- function trim(stringToTrim) {
- return stringToTrim.replace(/^\s+|\s+$/g,"");
- }
-
-
- // called when getsig.cgi returns to grok signal
- // strength information
-
- function processgetSig(doc, status) {
- log("processgetSig");
-
- // extract our lines from the HTML using state machine
-
- var lines = doc.split("\n");
- var ip;
- var neighour_ips;
- var neighour_sigs
- var state = "start";
- var next_state;
- for (var j=0; j<lines.length; j++) {
-
- next_state = state;
- if (state == "start") {
- if (lines[j].indexOf('sigstr') != -1)
- next_state = "read ips";
- }
- if (state == "read ips") {
- neighbour_ips = lines[j].split(" ");
- next_state = "read sigs";
- }
- if (state == "read sigs") {
- neighbour_sigs = trim(lines[j]).split(" ");
- next_state = "read ip";
- }
- if (state == "read ip") {
- ip =lines[j];
- next_state = "finished";
- }
- state = next_state;
- }
-
- if (state == "finished") {
-
- // walk nodes list to match ip
-
- for (var j=0; j<nodes.length; j++) {
- if (nodes[j].ip == ip) {
- nodes[j].neighbour_ips = neighbour_ips;
- nodes[j].neighbour_sigs = neighbour_sigs;
- var html="<table>";
- for(var i=0; i<neighbour_ips.length; i++) {
- html += '<tr><td>' + trim(neighbour_ips[i]) + '</td><td>' + trim(neighbour_sigs[i]) + ' dBm</td></tr>';
- }
- html += '</table>';
- document.getElementById('sigstr_last_node').innerHTML = ip;
- document.getElementById('sigstr_results').innerHTML = html;
- }
- }
- }
- }
-
-
- // calculate distance between two points
-
- rad = function(x) {return x*Math.PI/180;}
-
- distHaversine = function(p1, p2) {
- var R = 6371; // earth's mean radius in km
- var dLat = rad(p2.lat() - p1.lat());
- var dLong = rad(p2.lng() - p1.lng());
-
- var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
- Math.cos(rad(p1.lat())) * Math.cos(rad(p2.lat())) * Math.sin(dLong/2) * Math.sin(dLong/2);
- var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
- var d = R * c;
-
- return d.toFixed(3);
- }
-
-
- // draw lines indicating links between nodes
-
- function drawPathesBetweenNodes() {
-
- // delete all existing paths, this is inefficient but is OK for a first pass
-
- if (pathes) {
- for (j in pathes) {
- pathes[j].setMap(null);
- }
- pathes.length = 0;
- }
-
- // walk vis array looking for routes, I think this actually draws
- // routes twice
-
- for (var i=0; i<vis.data.length; i++) {
-
- if (vis.data[i].neighbour != undefined) {
-
- // this entry in vis array has a route and a neighbour
-
- router_ip = vis.data[i].router;
- neighbour_ip = vis.data[i].neighbour;
-
- // walk nodes list to find router location on map
-
- var router_location;
- for (var j=0; j<nodes.length; j++) {
-
- if (router_ip == nodes[j].ip) {
- router_location = nodes[j].marker.position;
- }
- }
-
- // walk nodes list to find neighbour location on map
-
- var neighbour_location;
- for (var j=0; j<nodes.length; j++) {
-
- if (neighbour_ip == nodes[j].ip) {
- neighbour_location = nodes[j].marker.position;
- }
- }
-
- if ((router_location != undefined) && (neighbour_location != undefined)) {
-
- // draw polyline between locations
-
- var coords = [
- router_location,
- neighbour_location
- ];
- var path = new google.maps.Polyline({
- path: coords,
- strokeColor: "#008000",
- strokeOpacity: 0.5,
- strokeWeight: 5
- });
- path.setMap(map);
-
- // click on line to get distance and path loss estimates
-
- google.maps.event.addListener(path, "click", function() {
- var path = this.getPath();
- var dist = distHaversine(path.b[0], path.b[1]) * 1000;
-
- // est rx power:
- // MP tx pwr between 15 and 20dB depending on rate, so use 17dB
- // roughly 2dB tx and rx antennas
- // 100dB loss for 1000m, 6dB more for double distance
-
- var path_loss = 100.0 + 20*Math.log(dist/1000)/Math.log(10);
- path_loss = Math.round(path_loss);
- var est_rx_sig = 17 + 2 + 2 - path_loss;
-
- document.getElementById('distance').innerHTML = dist + ' m';
- document.getElementById('path_loss').innerHTML = -path_loss + ' dBm';
- document.getElementById('est_rx_sig').innerHTML = est_rx_sig + ' dBm';
- });
-
- pathes.push(path);
- }
- }
- }
- }
-
-
- // sets up map for a new Node
- // adds node to nodes[] array but doesn't write to text file
-
- function addNewNode(lat, lng, newIp, isNewNode) {
-
- // create the marker
-
- var location = new google.maps.LatLng(lat, lng);
- var newMarker = new google.maps.Marker(
- {position: location,
- map: map,
- draggable: true,
- title: newIp,
- icon: "node_good.png" });
-
- if (isNewNode) {
- newMarker.setAnimation(google.maps.Animation.BOUNCE);
- }
-
- // add to nodes array
-
- var newInfoWindow = new google.maps.InfoWindow(
- { content: newIp
- });
- var newNode = {marker : newMarker,
- ip: newIp,
- infowindow: newInfoWindow,
- packet_loss: 0.0};
- nodes.push(newNode);
-
- // add listeners
-
- google.maps.event.addListener(newMarker, "dragstart", function() {
-
- drag_in_progress = 1;
-
- // remove node from nodes array
-
- var new_nodes = [];
- for (var j=0; j<nodes.length; j++) {
- if (nodes[j].marker.position != newMarker.position) {
- new_nodes.push(nodes[j]);
- }
- else {
- // preserve some states for when we drop it
- dragIp = nodes[j].ip;
- drag_packet_loss = nodes[j].packet_loss;
- }
- }
- nodes = new_nodes;
-
- // delete node from text file
-
- var url;
- url = "/cgi-bin/delnode.cgi?" + "lat=" + this.position.lat() + "&" + "lng=" +
- this.position.lng();
- downloadUrl(url, function(doc) { });
- });
-
- google.maps.event.addListener(newMarker, "dragend", function() {
-
- // stop any bouncing
- newMarker.setAnimation(null);
-
- // add node to nodes array
-
- var newInfoWindow = new google.maps.InfoWindow(
- { content: dragIp
- });
- var newNode = {marker : newMarker,
- ip: dragIp,
- infowindow: newInfoWindow,
- packet_loss: drag_packet_loss
- };
- nodes.push(newNode);
-
- // add node to text file
-
- var url;
- url = "/cgi-bin/addnode.cgi?" + "lat=" + this.position.lat() + "&" + "lng=" +
- this.position.lng() + "&" + "ip=" + dragIp;
- downloadUrl(url, function(doc) { });
-
- drawPathesBetweenNodes();
-
- drag_in_progress = 0;
- });
-
- google.maps.event.addListener(newMarker, "click", function() {
-
- // walk node list to find our InfoWindow
-
- for (var j=0; j<nodes.length; j++) {
- if (nodes[j].marker.position == newMarker.position) {
- nodes[j].infowindow.open(map, newMarker);
- }
- }
-
- });
-
- google.maps.event.addListener(newMarker, "rightclick", function() {
-
- // remove node from nodes array
-
- var new_nodes = [];
- for (var j=0; j<nodes.length; j++) {
- if (nodes[j].marker.position != newMarker.position) {
- new_nodes.push(nodes[j]);
- }
- }
- nodes = new_nodes;
-
- // delete node from text file
-
- var url;
- url = "/cgi-bin/delnode.cgi?" + "lat=" + this.position.lat() + "&" + "lng=" + this.position.lng();
- downloadUrl(url, function(doc) { });
- this.setMap(null);
-
- drawPathesBetweenNodes();
-
- });
-
- }
-
-
- // save state (config info) to our cookie
-
- function setCookie() {
- var cookietext = cookiename;
- cookietext += "=" + map.getCenter().lat();
- cookietext += "|" + map.getCenter().lng() + "|" + map.getZoom();
- cookietext += "|" + vis_server_ip + "|" + update_time;
- cookietext += "|" + update_enable + "|" + debug_enable;
- if (cookieexpy) {
- var exdate=new Date();
- exdate.setDate(exdate.getDate()+cookieexpy);
- cookietext += ";expires="+exdate.toGMTString();
- }
-
- // write the cookie
-
- document.cookie = cookietext;
- }
-
-
- // Grab a bunch of config info from our cookie
-
- function getCookie() {
-
- if (document.cookie.length > 0) {
- cookieStart = document.cookie.indexOf(cookiename + "=");
-
- // lets see if our cookie exists
-
- if (cookieStart!=-1) {
- cookieStart += cookiename.length+1;
- cookieEnd=document.cookie.indexOf(";",cookieStart);
- if (cookieEnd==-1) {
- cookieEnd=document.cookie.length;
- }
- cookietext = document.cookie.substring(cookieStart,cookieEnd);
-
- // split the cookie text and create the variables
-
- bits = cookietext.split("|");
- lat = parseFloat(bits[0]);
- lng = parseFloat(bits[1]);
- myzoom = parseInt(bits[2]);
- vis_server_ip = (bits[3]);
- update_time = parseInt(bits[4]);
- update_enable = parseInt(bits[5]);
- debug_enable = parseInt(bits[6]);
- }
- }
- else {
- known_location = false; // don't detect and display new nodes
- alert("Move to your " +
- "current location then refresh to detect nodes.");
-
- }
-
- }
-
-
- // clear database and set cookie to defaults
-
- function clearDatabase() {
-
- // call CGI to rm nodes.txt
-
- downloadUrl("/cgi-bin/cleardb.cgi", function(doc) { });
- nodes = [];
-
- // I would really like to clear cookie but can't work out how, so
- // set to defaults instead,
-
- var myLatLng = new google.maps.LatLng(0, 0);
- map.setCenter(myLatLng);
- map.setZoom(2);
- update_enable = 0;
- }
-
-
- // called when we click to add a node on map
-
- function placeMarker(location, ip, isNewNode) {
- if (ip == undefined) {
- ip = "";
- }
- if (isNewNode == undefined) {
- isNewNode = false;
- }
-
- addNewNode(location.lat(), location.lng(), ip, isNewNode);
-
- // save to marker file by calling a CGI
-
- var url;
- url = "/cgi-bin/addnode.cgi?" + "lat=" + location.lat() + "&" + "lng=" + location.lng() + "&" + "ip=" + ip;
- downloadUrl(url, function(doc) { });
- }
-
-
- /**
- * Returns an XMLHttp instance to use for asynchronous
- * downloading. This method will never throw an exception, but will
- * return NULL if the browser does not support XmlHttp for any reason.
- * @return {XMLHttpRequest|Null}
- */
- function createXmlHttpRequest() {
- try {
- if (typeof ActiveXObject != 'undefined') {
- return new ActiveXObject('Microsoft.XMLHTTP');
- } else if (window["XMLHttpRequest"]) {
- return new XMLHttpRequest();
- }
- } catch (e) {
- changeStatus(e);
- }
- return null;
- };
-
- /**
- * This functions wraps XMLHttpRequest open/send function.
- * It lets you specify a URL and will call the callback if
- * it gets a status code of 200.
- * @param {String} url The URL to retrieve
- * @param {Function} callback The function to call once retrieved.
- */
- function downloadUrl(url, callback) {
- var status = -1;
- var request = createXmlHttpRequest();
- if (!request) {
- return false;
- }
-
- request.onreadystatechange = function() {
- if (request.readyState == 4) {
- try {
- status = request.status;
- } catch (e) {
- // Usually indicates request timed out in FF.
- }
- if (status == 200) {
- callback(request.responseText, request.status);
- request.onreadystatechange = function() {};
- }
- }
- }
- request.open('GET', url, true);
- try {
- request.send(null);
- } catch (e) {
- changeStatus(e);
- }
- };
-
- function log(message) {
- if (debug_enable == 1) {
- message_list = document.getElementById("debugging_messages");
- log_message = document.createTextNode(message);
- log_list_item = document.createElement('li');
- log_list_item.appendChild(log_message);
- message_list.appendChild(log_list_item);
- }
- }
-
- function debugClicked() {
- if (document.control_panel.debug_enable.checked)
- debug_enable = 1;
- else
- debug_enable = 0;
- }
-
- function updateEnableClicked() {
- if (document.control_panel.update_enable.checked)
- update_enable = 1;
- else
- update_enable = 0;
- }
-
- function setVisServerIp() {
- vis_server_ip = document.control_panel.vis_server_ip.value;
- var html = '<a href="/cgi-bin/getvis.cgi?ip=' + vis_server_ip + ' ">Test Vis Server Connection</a>';
- document.getElementById('test_vis_server').innerHTML = html;
- }
-
- function setUpdateTime() {
- update_time = document.control_panel.update_time.value;
- }
-
-</script>
-
-</head>
-<body onload="initialize()" onunload="setCookie()">
-
- <div id="main-map">
- </div>
-
- <div id="side">
-
- <h3>Help</h3>
- <table>
- <tr><td width="5%"></td><td><a href="https://freetel.svn.sourceforge.net/svnroot/freetel/dilimesh/README.txt">DiliMesh README</a></td></tr>
- </table>
-
- <div id="stats" style="width: 100%;">
- <h3>Network Stats</h3>
- <table>
- <tr><td width="5%"></td><td>Time</td><td><div id="time"></div></td></tr>
- <tr><td></td><td>Nodes Reachable</td><td><div id="nodes_reachable"></div></td></tr>
- <tr><td></td><td>Nodes Poor Packet Loss</td><td><div id="nodes_poor_packet_loss"></div></td></tr>
- <tr><td></td><td>Nodes Total</td><td><div id="nodes_total"></div></td></tr>
- </table>
- </div>
-
- <div id="control" style="width: 100%;">
- <h3>Contol Panel</h3>
- <form name="control_panel">
- <table>
- <tr>
- <td width="5%">
- <td>Visualisation Server IP</td>
- <td colspan="2"><input type="text" name="vis_server_ip" size="15"></td>
- <td width="50"><input type="submit" value="Set" onclick="setVisServerIp()"></td>
- </tr>
- <tr>
- <td></td>
- <td>Update Enable</td>
- <td><input type="checkbox" name="update_enable" value="1" onclick="updateEnableClicked()"></td>
- </tr>
- <tr>
- <td></td>
- <td>Update Time (s)</td>
- <td><input type="text" name="update_time" size="3"></td>
- <td width="30%"></td>
- <td width="50"><input type="submit" value="Set" onclick="setUpdateTime()"></td>
- </tr>
- <tr>
- <td></td>
- <td>Clear Database</td>
- <td></td>
- <td width="30%"></td>
- <td width="60"><input type="submit" value="Clear" onclick="clearDatabase()"></td>
- </tr>
- <tr>
- <td></td>
- <td>Debug Messages</td>
- <td><input type="checkbox" name="debug_enable" onclick="debugClicked()"></td>
- </tr>
- </table>
- </form>
-
- <h3>Signal Strength</h3>
- <table>
- <tr>
- <td width="5%"></td>
- <td>Currently Polling</td>
- <td><div id="sigstr_current_node"></div></td>
- </tr>
- <tr>
- <td></td>
- <td>Last Node that<br>Responded</td>
- <td><div id="sigstr_last_node"></div></td>
- </tr>
- <tr>
- <td></td>
- <td valign=top>Results</td>
- <td><div id="sigstr_results"></div></td>
- </tr>
- </table>
-
- <h3>Distance</h3>
- <table>
- <tr>
- <td width="5%"></td>
- <td valign=top>Distance</td>
- <td><div id="distance"></div></td>
- </tr>
- <tr>
- <td></td>
- <td valign=top>Path Loss</td>
- <td><div id="path_loss"></div></td>
- </tr>
- <tr>
- <td></td>
- <td valign=top>Est Rx Signal</td>
- <td><div id="est_rx_sig"></div></td>
- </tr>
- </table>
- </div>
-
- <div id="tests" style="width: 100%;">
- <h3>Tests</h3>
- <ol>
- <li><a href="/cgi-bin/getnodes.cgi">Test nodes.txt database</a><br>
- <li><div id="test_vis_server"> </div>
- <li><div id="ping_nodes"> </div>
- </ol>
- </div>
-
- <div id="debugging_output" style="width: 100%;">
- <h3>Debugging Output</h3>
- <ol id="debugging_messages">
- </ol>
- </div>
- </div>
-
-
-</body>
-</html>
+++ /dev/null
-#!/bin/sh
-telnetd -l /usr/sbin/sigstr.sh -p 4950
+++ /dev/null
-#!/bin/sh
-# node_tune.sh
-# David Rowe 13 April 2010
-#
-# Prints information useful for tuning a mesh node
-#
-# Useful to run as a telnet login, e.g.
-# telnetd -l /usr/sbin/node_tune.sh -p 4950
-# will let you plug a laptop into eth0 and "telnet 192.168.1.20 4950"
-# to run this script
-
- # list of nodes this node can see in IP and MAC order
-
- IP=`cat /proc/net/arp | grep "10.130.1" | awk '{ print $1 }'`
- MAC=`cat /proc/net/arp | grep "10.130.1" | awk '{ print $4 }'`
-
- echo
- echo "sigstr"
- echo $IP
- for m in $MAC
- do
- wlanconfig ath0 list | grep $m | awk '{ printf " %s",$6 }'
- done
- echo
-
-
-
-