Merge "gfch100: add leds and RSL chart to UI"
diff --git a/craftui/HOW.updatesim b/craftui/HOW.updatesim
index 5789b04..00d1b8e 100644
--- a/craftui/HOW.updatesim
+++ b/craftui/HOW.updatesim
@@ -28,6 +28,8 @@
 		tmp/ssl \
 		tmp/platform \
 		tmp/gpio \
+		tmp/cpss_ready \
+		tmp/peer-"*" \
 		tmp/sim \
 		> sim$suffix.tgz
 
diff --git a/craftui/craftui.py b/craftui/craftui.py
index f34e424..f726bf2 100755
--- a/craftui/craftui.py
+++ b/craftui/craftui.py
@@ -431,12 +431,54 @@
 
     return text
 
+  def GetValue(self, data, path):
+    """Walk down the dicts to get a value."""
+    keys = path.split('/')
+    v = data
+    for key in keys:
+      if v:
+        v = v.get(key, None)
+    return v
+
+  def AddLeds(self, data):
+    """Add status leds to data."""
+    red = 'red.gif'
+    green = 'green.gif'
+    leds = {
+        'ACS': red,
+        'Switch': red,
+        'Modem': red,
+        'Radio': red,
+        'RSSI': red,
+        'MSE': red,
+        'Peer': red
+    }
+    if self.GetValue(data, 'platform/ledstate') is 'ACSCONTACT':
+      leds['ACS'] = green
+    if self.GetValue(data, 'platform/cpss_ready'):
+      leds['Switch'] = green
+    if self.GetValue(data, 'modem/status/acquireStatus') == 1:
+      leds['Modem'] = green
+    if self.GetValue(data, 'radio/paLnaPowerEnabled'):
+      leds['Radio'] = green
+    rssi = self.GetValue(data, 'radio/rx/rssi')
+    if rssi >= 1500 and rssi <= 2000:
+      leds['RSSI'] = green
+    mse = self.GetValue(data, 'modem/status/normalizedMse')
+    if mse is not None and mse <= -180:
+      leds['MSE'] = green
+    if self.GetValue(data, 'platform/peer_up'):
+      leds['Peer'] = green
+    data['leds'] = leds
+
   def GetData(self):
     """Get system data, return a json string."""
-    pj = self.GetPlatformData()
-    mj = self.GetModemData()
-    rj = self.GetRadioData()
-    js = '{"platform":' + pj + ',"modem":' + mj + ',"radio":' + rj + '}'
+    data = {}
+    data['platform'] = self.GetPlatformData()
+    data['modem'] = self.GetModemData()
+    data['radio'] = self.GetRadioData()
+    self.AddLeds(data)
+    js = json.dumps(data)
     return js
 
   def AddIpAddr(self, data):
@@ -505,7 +547,7 @@
       data[kdata] = vdata
 
   def GetPlatformData(self):
-    """Get platform data, return a json string."""
+    """Get platform data."""
     data = self.data
     sim = self.sim
 
@@ -519,6 +561,7 @@
     data['ledstate'] = self.ReadFile(sim + '/tmp/gpio/ledstate')
     data['cpu_temperature'] = self.ReadFile(sim + '/tmp/gpio/cpu_temperature')
     data['peer_up'] = os.path.exists(sim + '/tmp/peer-up')
+    data['cpss_ready'] = os.path.exists(sim + '/tmp/cpss_ready')
     cs = '/config/settings/'
     data['craft_ipaddr'] = self.ReadFile(sim + cs + 'craft_ipaddr')
     data['link_ipaddr'] = self.ReadFile(sim + cs + 'local_ipaddr')
@@ -530,10 +573,11 @@
     self.AddInterfaceStats(data)
     self.AddSwitchStats(data)
     self.AddVlans(data)
-    return json.dumps(data)
+    return data
 
   def GetModemData(self):
-    """Get modem data, return a json string."""
+    """Get modem data."""
+    data = {}
     response = '{}'
     if self.sim:
       response = self.ReadFile(self.sim + '/tmp/glaukus/modem.json')
@@ -544,10 +588,15 @@
         response = handle.read()
       except urllib2.URLError as ex:
         print 'Connection to %s failed: %s' % (url, ex.reason)
-    return response
+    try:
+      data = json.loads(response)
+    except ValueError as e:
+      print 'json format error: %s' % e
+    return data
 
   def GetRadioData(self):
     """Get radio data, return a json string."""
+    data = {}
     response = '{}'
     if self.sim:
       response = self.ReadFile(self.sim + '/tmp/glaukus/radio.json')
@@ -558,7 +607,11 @@
         response = handle.read()
       except urllib2.URLError as ex:
         print 'Connection to %s failed: %s' % (url, ex.reason)
-    return response
+    try:
+      data = json.loads(response)
+    except ValueError as e:
+      print 'json format error: %s' % e
+    return data
 
   def GetIQPNG(self, path):
     """Get IQ points and render as PNG."""
@@ -573,7 +626,12 @@
       except urllib2.URLError as ex:
         print 'Connection to %s failed: %s' % (url, ex.reason)
 
-    coords = json.loads(response)
+    coords = [0, 0]
+    try:
+      coords = json.loads(response)
+    except ValueError as e:
+      print 'json format error: %s' % e
+
     # owh is original width/height of data (-1200 to 1200)
     owh = (2400, 2400)
     # wh is display size (400x400)
diff --git a/craftui/sim1.tgz b/craftui/sim1.tgz
index 23a1fa6..b414105 100644
--- a/craftui/sim1.tgz
+++ b/craftui/sim1.tgz
Binary files differ
diff --git a/craftui/sim2.tgz b/craftui/sim2.tgz
index f5dd5e9..fff9aae 100644
--- a/craftui/sim2.tgz
+++ b/craftui/sim2.tgz
Binary files differ
diff --git a/craftui/www/config.thtml b/craftui/www/config.thtml
index cda05b9..3d2a3c9 100644
--- a/craftui/www/config.thtml
+++ b/craftui/www/config.thtml
@@ -3,6 +3,7 @@
   <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
   <meta content="utf-8" http-equiv="encoding">
   <script src="static/jquery-2.1.4.min.js"></script>
+  <script src="static/dygraph-combined.js"></script>
   <link rel="stylesheet" type="text/css" href="static/craft.css">
   <link rel=icon href=static/favicon.ico>
   <link rel=stylesheet href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700&amp;lang=en">
@@ -22,7 +23,6 @@
       </nav>
     </section>
   </header>
-  <br>
   <div hidden>
     <input id=hidden_on_https value="{{hidden_on_https}}">
     <input id=hidden_on_peer value="{{hidden_on_peer}}">
@@ -31,6 +31,27 @@
     <input id=peer_arg_on_peer value="{{peer_arg_on_peer}}">
   </div>
   <div {{shown_on_peer}}><font color="red"><b>This is the Peer</b></font></div>
+  <div>
+    <table class="leds">
+      <tr>
+        <td width=50 align=center>Craft<br>
+          <img id="leds/Craft" width=20 height=20 src=/static/grey.gif></td>
+        <td width=50 align=center>ACS<br>
+          <img id="leds/ACS" width=20 height=20 src=/static/grey.gif></td>
+        <td width=50 align=center>Switch<br>
+          <img id="leds/Switch" width=20 height=20 src=/static/grey.gif></td>
+        <td width=50 align=center>Modem<br>
+          <img id="leds/Modem" width=20 height=20 src=/static/grey.gif></td>
+        <td width=50 align=center>Radio<br>
+          <img id="leds/Radio" width=20 height=20 src=/static/grey.gif></td>
+        <td width=50 align=center>RSSI<br>
+          <img id="leds/RSSI" width=20 height=20 src=/static/grey.gif></td>
+        <td width=50 align=center>MSE<br>
+          <img id="leds/MSE" width=20 height=20 src=/static/grey.gif></td>
+        <td width=50 align=center>Peer<br>
+          <img id="leds/Peer" width=20 height=20 src=/static/grey.gif></td>
+    </table>
+  </div>
   <div class="tabs">
     <div class="tab">
       <input type="radio" id="tab-1" name="tab-group-1" checked>
@@ -38,7 +59,7 @@
       <div class="content">
         <b>Running Configuration:</b><br>
         <textarea id=configuration cols=60 rows=30>...</textarea><br>
-        <input type=submit value=Apply onclick="CraftUI.config('password_admin', 0, 1)">
+        <input type=submit value=Apply onclick="craftUI.config('password_admin', 0, 1)">
       </div>
     </div>
 
@@ -60,7 +81,7 @@
               Current <b>Admin</b> Password: <input id=password_admin_admin type=password value=""><br>
               New Admin Password: <input id=password_admin_new type=password value=""><br>
               Confirm: <input id=password_admin_confirm type=password value=""><br>
-              <input type=submit value="Apply Now" onclick="CraftUI.config('password_admin', 0, 1)">
+              <input type=submit value="Apply Now" onclick="craftUI.config('password_admin', 0, 1)">
             <td>
               <span id=password_admin_result>...</span>
 
@@ -71,7 +92,7 @@
               Current <b>Admin</b> Password: <input id=password_guest_admin type=password value=""><br>
               New Guest Password: <input id=password_guest_new type=password value=""><br>
               Confirm: <input id=password_guest_confirm type=password value=""><br>
-              <input type=submit value="Apply Now" onclick="CraftUI.config('password_guest', 0, 1)">
+              <input type=submit value="Apply Now" onclick="craftUI.config('password_guest', 0, 1)">
             <td>
               <span id=password_guest_result>...</span>
 
@@ -97,10 +118,10 @@
             <td align=right><span id="platform/active_craft_inet">...</span>
             <td align=right>
               <span id="platform/craft_ipaddr">...</span>
-              <input type=submit value="Apply Now" onclick="CraftUI.config('craft_ipaddr', 1)">
+              <input type=submit value="Apply Now" onclick="craftUI.config('craft_ipaddr', 1)">
             <td>
               <input id=craft_ipaddr type=text value="">
-              <input type=submit value=Configure onclick="CraftUI.config('craft_ipaddr')">
+              <input type=submit value=Configure onclick="craftUI.config('craft_ipaddr')">
             <td>
               <span id=craft_ipaddr_result>...</span>
 
@@ -109,10 +130,10 @@
             <td align=right><span id="platform/active_link_inet">...</span>
             <td align=right>
               <span id="platform/link_ipaddr">...</span>
-              <input type=submit value="Apply Now" onclick="CraftUI.config('link_ipaddr', 1)">
+              <input type=submit value="Apply Now" onclick="craftUI.config('link_ipaddr', 1)">
             <td>
               <input id=link_ipaddr type=text value="">
-              <input type=submit value=Configure onclick="CraftUI.config('link_ipaddr')">
+              <input type=submit value=Configure onclick="craftUI.config('link_ipaddr')">
             <td>
               <span id=link_ipaddr_result>...</span>
 
@@ -121,10 +142,10 @@
             <td align=right>See Peer
             <td align=right>
               <span id="platform/peer_ipaddr">...</span>
-              <input type=submit value="Apply Now" onclick="CraftUI.config('peer_ipaddr', 1)">
+              <input type=submit value="Apply Now" onclick="craftUI.config('peer_ipaddr', 1)">
             <td>
               <input id=peer_ipaddr type=text value="">
-              <input type=submit value=Configure onclick="CraftUI.config('peer_ipaddr')">
+              <input type=submit value=Configure onclick="craftUI.config('peer_ipaddr')">
             <td>
               <span id=peer_ipaddr_result>...</span>
 
@@ -133,10 +154,10 @@
             <td align=right><span id="platform/active_inband_vlan">...</span>
             <td align=right>
               <span id="platform/vlan_inband">...</span>
-              <input type=submit value="Apply Now" onclick="CraftUI.config('vlan_inband', 1)">
+              <input type=submit value="Apply Now" onclick="craftUI.config('vlan_inband', 1)">
             <td>
               <input id=vlan_inband type=text value="">
-              <input type=submit value=Configure onclick="CraftUI.config('vlan_inband')">
+              <input type=submit value=Configure onclick="craftUI.config('vlan_inband')">
             <td>
               <span id=vlan_inband_result>...</span>
 
@@ -145,10 +166,10 @@
             <td align=right><span id="platform/active_ooband_vlan">...</span>
             <td align=right>
               <span id="platform/vlan_ooband">...</span>
-              <input type=submit value="Apply Now" onclick="CraftUI.config('vlan_ooband', 1)">
+              <input type=submit value="Apply Now" onclick="craftUI.config('vlan_ooband', 1)">
             <td>
               <input id=vlan_ooband type=text value="">
-              <input type=submit value=Configure onclick="CraftUI.config('vlan_ooband')">
+              <input type=submit value=Configure onclick="craftUI.config('vlan_ooband')">
             <td>
               <span id=vlan_ooband_result>...</span>
 
@@ -157,10 +178,10 @@
             <td align=right><span id="platform/active_link_vlan">...</span>
             <td align=right>
               <span id="platform/vlan_link">...</span>
-              <input type=submit value="Apply Now" onclick="CraftUI.config('vlan_peer', 1)">
+              <input type=submit value="Apply Now" onclick="craftUI.config('vlan_peer', 1)">
             <td>
               <input id=vlan_peer type=text value="">
-              <input type=submit value=Configure onclick="CraftUI.config('vlan_peer')">
+              <input type=submit value=Configure onclick="craftUI.config('vlan_peer')">
             <td>
               <span id=vlan_peer_result>...</span>
 
@@ -185,7 +206,7 @@
             <td align=right><span id="radio/hiTransceiver/pll/frequency">...</span>
             <td>
               <input id=freq_hi type=text value="">
-              <input type=submit value=Apply onclick="CraftUI.config('freq_hi')">
+              <input type=submit value=Apply onclick="craftUI.config('freq_hi')">
             <td>
               <span id=freq_hi_result>...</span>
 
@@ -194,7 +215,7 @@
             <td align=right><span id="radio/loTransceiver/pll/frequency">...</span>
             <td>
               <input id=freq_lo type=text value="">
-              <input type=submit value=Apply onclick="CraftUI.config('freq_lo')">
+              <input type=submit value=Apply onclick="craftUI.config('freq_lo')">
             <td>
               <span id=freq_lo_result>...</span>
 
@@ -203,7 +224,7 @@
             <td align=right><span id="radio/hiTransceiver/mode">...</span>
             <td>
               <input id=mode_hi type=text value="">
-              <input type=submit value=Apply onclick="CraftUI.config('mode_hi')">
+              <input type=submit value=Apply onclick="craftUI.config('mode_hi')">
             <td>
               <span id=mode_hi_result>...</span>
 
@@ -212,7 +233,7 @@
             <td align=right><span id="modem/status/acmEngineRxSensorsEnabled">...</span>
             <td>
               <input id=acm_on type=text value="">
-              <input type=submit value=Apply onclick="CraftUI.config('acm_on')">
+              <input type=submit value=Apply onclick="craftUI.config('acm_on')">
             <td>
               <span id=acm_on_result>...</span>
 
@@ -221,7 +242,7 @@
             <td align=right><span id="radio/tx/paPowerSet">...</span>
             <td>
               <input id=tx_powerlevel type=text value="">
-              <input type=submit value=Apply onclick="CraftUI.config('tx_powerlevel')">
+              <input type=submit value=Apply onclick="craftUI.config('tx_powerlevel')">
             <td>
               <span id=tx_powerlevel_result>...</span>
 
@@ -230,7 +251,7 @@
             <td align=right><span id="radio/tx/vgaGain">...</span>
             <td>
               <input id=tx_gain type=text value="">
-              <input type=submit value=Apply onclick="CraftUI.config('tx_gain')">
+              <input type=submit value=Apply onclick="craftUI.config('tx_gain')">
             <td>
               <span id=tx_gain_result>...</span>
 
@@ -239,7 +260,7 @@
             <td align=right><span id="radio/rx/agcDigitalGainIndex">...</span>
             <td>
               <input id=rx_gainindex type=text value="">
-              <input type=submit value=Apply onclick="CraftUI.config('rx_gainindex')">
+              <input type=submit value=Apply onclick="craftUI.config('rx_gainindex')">
             <td>
               <span id=rx_gainindex_result>...</span>
 
@@ -248,11 +269,12 @@
             <td align=right><span id="radio/paLnaPowerEnabled">...</span>
             <td>
               <input id=palna_on type=text value="">
-              <input type=submit value=Apply onclick="CraftUI.config('palna_on')">
+              <input type=submit value=Apply onclick="craftUI.config('palna_on')">
             <td>
               <span id=palna_on_result>...</span>
 
         </table>
+        <div id="rsl-graph">...</div>
       </div>
     </div>
 
@@ -261,7 +283,7 @@
       <label for="tab-5">Reboot</label>
       <div class="content">
         <input hidden id=reboot type=text value="true">
-        <input type=submit value=Reboot onclick="CraftUI.config('reboot')">
+        <input type=submit value=Reboot onclick="craftUI.config('reboot')">
         <span class="values">
           <span id=reboot_result>...</span>
         </span>
@@ -270,7 +292,7 @@
         <br>
         <br>
         <input hidden id=factory_reset type=text value="true">
-        <input type=submit value="Factory Reset" onclick="CraftUI.config('factory_reset')">
+        <input type=submit value="Factory Reset" onclick="craftUI.config('factory_reset')">
         <span class="values">
           <span id=factory_reset_result>...</span>
         </span>
diff --git a/craftui/www/static/blue.gif b/craftui/www/static/blue.gif
new file mode 100644
index 0000000..3caecb9
--- /dev/null
+++ b/craftui/www/static/blue.gif
Binary files differ
diff --git a/craftui/www/static/craft.css b/craftui/www/static/craft.css
index 5c383ab..6887b60 100644
--- a/craftui/www/static/craft.css
+++ b/craftui/www/static/craft.css
@@ -1,6 +1,6 @@
 table, th, td {
   margin: 1px;
-  margin-bottom: 20px;
+  margin-bottom: 10px;
   border: 1px solid #ccc;
   font-size:12px;
 }
@@ -73,3 +73,8 @@
   z-index: 1;
   opacity: 1;
 }
+
+.leds {
+  background: #fff;
+  margin-bottom: 40px;
+}
diff --git a/craftui/www/static/craft.js b/craftui/www/static/craft.js
index 7b539ba..90a9bf9 100644
--- a/craftui/www/static/craft.js
+++ b/craftui/www/static/craft.js
@@ -7,25 +7,67 @@
     document.documentElement.classList.add('unsupported');
     return;
   }
-
-  // Initialize the info.
-  CraftUI.getInfo();
-
-  // Refresh data periodically.
-  window.setInterval(CraftUI.getInfo, 5000);
 };
 
-CraftUI.info = {checksum: 0};
-CraftUI.am_sending = false
+CraftUI.prototype.init = function() {
+  this.info = {checksum: 0};
+  this.am_sending = false
 
-CraftUI.updateField = function(key, val) {
+  // store history on some values
+  this.history = {
+    'radio/rx/rsl': { name: 'rsl-graph', count: 24, values: [] }
+  };
+
+  // Initialize the info.
+  this.getInfo();
+
+  // Refresh data periodically.
+  var f = function() {
+    this.ui.getInfo();
+  };
+  window.ui = this;
+  window.setInterval(f, 5000);
+};
+
+CraftUI.prototype.updateGraphs = function() {
+  for (var name in this.history) {
+    var h = this.history[name];
+    if (h.values.length == 0) {
+      continue;
+    }
+    if (!h.graph) {
+      h.graph = new Dygraph(document.getElementById(h.name), h.values, {
+        xlabel: 'Time',
+        ylabel: 'RSL',
+        labels: [ 'Date', 'RSL' ],
+      });
+    }
+    h.graph.updateOptions({ 'file': h.values });
+  }
+};
+
+CraftUI.prototype.updateField = function(key, val) {
+  // store history if requested
+  var h = this.history[key];
+  if (h) {
+    h.values.push([ui.date, val]);
+    if (h.values.length > h.count) {
+      h.values = h.values.slice(-h.count);
+    }
+  }
+  // find element, show on debug page if not used
   var el = document.getElementById(key);
   if (el == null) {
-    self.unhandled += key + '=' + val + '; ';
+    this.unhandled += key + '=' + val + '; ';
     return;
   }
-  el.innerHTML = ''; // Clear the field.
+  // For IMG objects, set image
+  if (el.src) {
+    el.src = '/static/' + val;
+    return;
+  }
   // For objects, create an unordered list and append the values as list items.
+  el.innerHTML = ''; // Clear the field.
   if (val && typeof val === 'object') {
     var ul = document.createElement('ul');
     for (key in val) {
@@ -48,44 +90,61 @@
   el.appendChild(document.createTextNode(val));
 };
 
-CraftUI.flattenAndUpdateFields = function(jsonmap, prefix) {
+CraftUI.prototype.flattenAndUpdateFields = function(jsonmap, prefix) {
   for (var key in jsonmap) {
     var val = jsonmap[key];
     if (typeof val !== 'object') {
-      CraftUI.updateField(prefix + key, jsonmap[key]);
+      this.updateField(prefix + key, jsonmap[key]);
     } else {
-      CraftUI.flattenAndUpdateFields(val, prefix + key + '/')
+      this.flattenAndUpdateFields(val, prefix + key + '/')
     }
   }
 };
 
-CraftUI.getInfo = function() {
+CraftUI.prototype.getInfo = function() {
   // Request info, set the connected status, and update the fields.
-  if (CraftUI.am_sending) {
+  if (this.am_sending) {
     return;
   }
   var peer_arg_on_peer = document.getElementById("peer_arg_on_peer").value;
   var xhr = new XMLHttpRequest();
+  xhr.timeout = 2000;
+  xhr.ui = this;
   xhr.onreadystatechange = function() {
-    self.unhandled = '';
-    if (xhr.readyState == 4 && xhr.status == 200) {
-      var list = JSON.parse(xhr.responseText);
-      CraftUI.flattenAndUpdateFields(list, '');
+    var ui = this.ui;
+    if (xhr.readyState != 4) {
+      return;
     }
-    CraftUI.updateField('unhandled', self.unhandled);
-    CraftUI.am_sending = false
+    ui.unhandled = '';
+    var led = 'red.gif';
+    if (xhr.status == 200) {
+      ui.date = new Date();
+      var list = JSON.parse(xhr.responseText);
+      ui.flattenAndUpdateFields(list, '');
+      led = 'green.gif';
+    } else {
+      var leds = ['ACS', 'Switch', 'Modem', 'Radio', 'RSSI', 'MSE', 'Peer'];
+      for (var i in leds) {
+        ui.updateField('leds/' + leds[i], 'grey.gif')
+      }
+    }
+    ui.updateField('unhandled', ui.unhandled);
+    ui.updateField('leds/Craft', led)
+    ui.updateGraphs();
+    ui.am_sending = false
   };
   xhr.open('get', '/content.json' + peer_arg_on_peer, true);
-  CraftUI.am_sending = true
+  this.am_sending = true
   xhr.send();
 };
 
-CraftUI.config = function(key, activate, is_password) {
+CraftUI.prototype.config = function(key, activate, is_password) {
   // POST as json
   var peer_arg_on_peer = document.getElementById("peer_arg_on_peer").value;
   var el = document.getElementById(key);
-  var xhr = new XMLHttpRequest();
   var action = "Configured";
+  var xhr = new XMLHttpRequest();
+  xhr.ui = this;
   xhr.open('post', '/content.json' + peer_arg_on_peer);
   xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
   var data;
@@ -108,13 +167,14 @@
   var resultid = key + "_result"
   var el = document.getElementById(resultid);
   xhr.onload = function(e) {
+    var ui = this.ui;
     var json = JSON.parse(xhr.responseText);
     if (json.error == 0) {
       el.innerHTML = action + " successfully.";
     } else {
       el.innerHTML = "Error: " + json.errorstring;
     }
-    CraftUI.getInfo();
+    ui.getInfo();
   }
   xhr.onerror = function(e) {
     el.innerHTML = xhr.statusText + xhr.responseText;
@@ -123,4 +183,5 @@
   xhr.send(txt);
 };
 
-new CraftUI();
+var craftUI = new CraftUI();
+craftUI.init();
diff --git a/craftui/www/static/green.gif b/craftui/www/static/green.gif
new file mode 100644
index 0000000..2281692
--- /dev/null
+++ b/craftui/www/static/green.gif
Binary files differ
diff --git a/craftui/www/static/grey.gif b/craftui/www/static/grey.gif
new file mode 100644
index 0000000..34fe83e
--- /dev/null
+++ b/craftui/www/static/grey.gif
Binary files differ
diff --git a/craftui/www/static/red.gif b/craftui/www/static/red.gif
new file mode 100644
index 0000000..539c5cd
--- /dev/null
+++ b/craftui/www/static/red.gif
Binary files differ
diff --git a/craftui/www/static/yellow.gif b/craftui/www/static/yellow.gif
new file mode 100644
index 0000000..9b20598
--- /dev/null
+++ b/craftui/www/static/yellow.gif
Binary files differ
diff --git a/craftui/www/status.thtml b/craftui/www/status.thtml
index 09e7d9b..a6e37be 100644
--- a/craftui/www/status.thtml
+++ b/craftui/www/status.thtml
@@ -3,6 +3,7 @@
   <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
   <meta content="utf-8" http-equiv="encoding">
   <script src="static/jquery-2.1.4.min.js"></script>
+  <script src="static/dygraph-combined.js"></script>
   <link rel="stylesheet" type="text/css" href="static/craft.css">
   <link rel=icon href=static/favicon.ico>
   <link rel=stylesheet href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700&amp;lang=en">
@@ -22,7 +23,6 @@
       </nav>
     </section>
   </header>
-  <br>
   <div hidden>
     <input id=hidden_on_https value="{{hidden_on_https}}">
     <input id=hidden_on_peer value="{{hidden_on_peer}}">
@@ -31,6 +31,27 @@
     <input id=peer_arg_on_peer value="{{peer_arg_on_peer}}">
   </div>
   <div {{shown_on_peer}}><font color="red"><b>This is the Peer</b></font></div>
+  <div>
+    <table class="leds">
+      <tr>
+        <td width=50 align=center>Craft<br>
+          <img id="leds/Craft" width=20 height=20 src=/static/grey.gif></td>
+        <td width=50 align=center>ACS<br>
+          <img id="leds/ACS" width=20 height=20 src=/static/grey.gif></td>
+        <td width=50 align=center>Switch<br>
+          <img id="leds/Switch" width=20 height=20 src=/static/grey.gif></td>
+        <td width=50 align=center>Modem<br>
+          <img id="leds/Modem" width=20 height=20 src=/static/grey.gif></td>
+        <td width=50 align=center>Radio<br>
+          <img id="leds/Radio" width=20 height=20 src=/static/grey.gif></td>
+        <td width=50 align=center>RSSI<br>
+          <img id="leds/RSSI" width=20 height=20 src=/static/grey.gif></td>
+        <td width=50 align=center>MSE<br>
+          <img id="leds/MSE" width=20 height=20 src=/static/grey.gif></td>
+        <td width=50 align=center>Peer<br>
+          <img id="leds/Peer" width=20 height=20 src=/static/grey.gif></td>
+    </table>
+  </div>
   <div class="tabs">
     <div class="tab">
       <input type="radio" id="tab-1" name="tab-group-1" checked>