IotaWatt's status widget

Hi.

Has anyone written a little widget that could show on a site a similar real-time screen as the Status screen?

Something you could integrate on a web server?

Answering myself.
Unfortunately, as the iota only let you access it via http, and doesn’t define CORS headers, you can’t query the status via JS from another origin (nor mixing https and http in recent browsers)

So instead I went with a reverse proxy on my apache server.

First I extracted the source index.htm and stored it locally.
In it I modified some variable definition so it read:

var configFileURL = "/iotawatt/config.txt";
var configTablesURL = "/iotawatt/tables.txt";

in the method:
statusGet, I added to the URL status to read:
xmlHttp.open(“GET”,"/iotawatt/status?state=yes&inputs=yes&outputs=yes&stats=yes&datalogs=yes&influx=yes&emon=yes&pvoutput=yes", true);

I then added to the apache config:

ProxyPass "/iotawatt/status"  "http://iotawatl.local/status"
ProxyPass "/iotawatt/config.txt"  "http://iotawatt.local/config.txt"
ProxyPass "/iotawatt/tables.txt"  "http://iotawatt.local/tables.txt"
ProxyPassReverse "/iotawatt/"  "http://iotawatt.local/"

this was to limit the access to the iotawatt interface. Though only the Status screen works.

As I wanted to be able to display the live data on my site, I trimmed the code of everything that wasn’t necessary, and made it load the data and populate the table by default. Couldn’t simply call the statusBegin() method as it must be called only once the config files have been retrieved (it works on the normal site, because you’d be typically slower to click on the button that it would for the page to load.

This is the result.
js:

// Code from the IotaWatt project, See http://iotawatt.com/, awesome stuff

// configuration files
var configFileURL = "iotawatt/config.txt";
var configTablesURL = "iotawatt/tables.txt";
var noConfig = true;

var config;                         // configuration as Js Object
var tables;                         // tables as Js Object

/***************************************************************************************************
 *                       Shorthand functions
* ************************************************************************************************/
function EbyId(id) {return document.getElementById(id)};
function hide(id) {document.getElementById(id).style.display = "none";}
function show(id) {document.getElementById(id).style.display = "inline";}


/***********************************************************************************************
 *                        Setup and run status display
 * *******************************************************************************************/

function statusBegin(){
  EbyId("divStatus").style.display = "table";
  statusGet();
}

function statusGet(){
  var xmlHttp = new XMLHttpRequest();
  xmlHttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      statusDisplay(xmlHttp.responseText);
    }
  }
  xmlHttp.open("GET","iotawatt/status?inputs=yes&outputs=yes", true);
  xmlHttp.send(null);
}

function statusDisplay(statusMessage){
  var status = JSON.parse(statusMessage);


  statusTable = EbyId("inputStatusTable");
  statusTable.innerHTML = "";
  for(i in status.inputs){
    addRow();

    for(let j = 0; j < config.inputs.length; j++){
      if(config.inputs[j] !== null && config.inputs[j].channel == status.inputs[i].channel){
        if (status.inputs[i].reversed)  column1.innerHTML += "<span title=\"CT reversed\">&#8634 </span>";
        if(config.inputs[j].name !== undefined) {
          column1.innerHTML += "<strong>" + config.inputs[j].name + ":</strong>";
        }
        break;
      }
    }

    if(status.inputs[i].Watts !== undefined){
      var wattNode = document.createElement("font");
      wattNode.innerHTML = status.inputs[i].Watts + "&nbsp;" + "Watts";
      column3.appendChild(wattNode);

      if(status.inputs[i].reversed == "true"){
        //wattNode.setAttribute("color","DarkRed");
      }
      if(status.inputs[i].Watts < 0){
          wattNode.setAttribute("color","DarkGreen");
      }
      if(Math.abs(status.inputs[i].Watts) >= 60){
        var pf = Math.abs(status.inputs[i].Pf);
        var pfNode = document.createElement("font");
        pfNode.innerHTML = ", pf:" + "&nbsp;" + pf.toFixed(2);
        column3.appendChild(pfNode);
      }

    }
    else if(status.inputs[i].Vrms !== undefined){
      column3.appendChild(document.createTextNode(status.inputs[i].Vrms.toFixed(1) + " Volts"));
    }
  }

  statusTable = EbyId("outputStatusTable");
  statusTable.innerHTML = "";
    for(let i = 0; i < status.outputs.length; i++){
    addRow();
    column1.innerHTML += "<strong>" + status.outputs[i].name + ":</strong>";
    var wattNode = document.createElement("font");
    wattNode.innerHTML = status.outputs[i].value.toFixed(unitsPrecision(status.outputs[i].units)) + " " + status.outputs[i].units;
    column3.appendChild(wattNode);
  }

  setTimeout(function(){statusGet();},1000);

  function addRow(){
    newRow = document.createElement("tr");
    statusTable.appendChild(newRow);
    column1 = document.createElement("td");
    column1.setAttribute("align","right");
    newRow.appendChild(column1);
    column2 = document.createElement("td");
    newRow.appendChild(column2);
    column3 = document.createElement("td");
    newRow.appendChild(column3);
  }
}

function unitsPrecision(units){
  if(units == "Watts") return 1;
  if(units == "Volts") return 1;
  if(units == "Amps") return 2;
  if(units == "Hz") return 1;
  if(units == "PF") return 2;
  if(units == "VA") return 1;
  return 1;
}

function checkConfig(){
  if(config.device.channels === undefined){
    config.device.channels = 15;
  }
  if(config.inputs === undefined){
    config.inputs = [{channel:0, type:"VT", model:"generic", cal:10, phase:2}];
  }
  for(var i=0; i<config.inputs.length; i++){
    if(config.inputs[i] === undefined ||
    (config.inputs[i] !== null && config.inputs[i].channel > i)){
      config.inputs.splice(i,0,null);
    }
  }
  for(var i=config.inputs.length; i<config.device.channels; i++){
    config.inputs.push(null);
  }
  config.inputs.splice(config.device.channels,config.inputs.length-config.device.channels);

  for(i in config.inputs){
    if(config.inputs[i] != null && config.inputs[i].model != "generic"){
      var table = tables.CT;
      if(config.inputs[i].type == "VT") table = tables.VT;
      for(j in table){
        if(config.inputs[i].model == table[j].model){
          config.inputs[i].phase = table[j].phase;
        }
      }
    }
  }

  if(config.server === undefined || config.server.type === undefined){
    config.server = {type: "none"};
  }
}

/**********************************************************************************************
 *              File I/O and management
 * *******************************************************************************************/

function getConfig(callback){
  EbyId("panicMessageDiv").style.display = "none";
  readFile(configFileURL, function(response){
                            if(response == undefined){
                              panic("configuration not found.");
                            }
                            else {
                              try {
                                config = JSON.parse(response);
                              }
                              catch (e) {
                                panic("configuration parse failed: " + e.message);
                                return;
                              }
                              try {
                                  checkConfig();
                              }
                              catch (e) {
                                panic("configuration can't be processed: " + e.message);
                                return;
                              }
                              noConfig = false;
                              if(callback !== undefined) callback();
                              statusBegin();  
                            }
                          })
}

function panic(message){
  EbyId("panicList").innerHTML = "<p>" + message + "</p>";
  EbyId("panicMessageDiv").style.display = "block";
  noConfig = true;
}

function getTables(){
    readFile(configTablesURL, function(response){
        tables = JSON.parse(response);
        getConfig();
    });
}

function readFile(path, responseHandler){
  var xmlHttp = new XMLHttpRequest();
  xmlHttp.onreadystatechange = function() {
    if (this.readyState == 4) {
      if(this.status == 200){
        responseHandler(this.responseText);
      }
      else {
        responseHandler(undefined);
      }
    }
  };
  xmlHttp.open("GET", path, true);
  xmlHttp.send();
}

function iotawattsetup(){
  getTables();
}

And the html:

<!DOCTYPE html>
<!-- version 02_03_02 -->
<html lang="en-US">
<head>
<script type="text/javascript" src="iotawattdata.js"></script>
<meta charset="utf-8">
</head>
<body onload=" iotawattsetup()">
  
  <div id="panicMessageDiv" class="body color_body" style="display:none">
    <div id="panicList">
    </div>
  </div>

    <div id="divStatus" class="body color_body" style="display:none">
          <table id="statusIOBody" width="100%">
            <tr><th width="60%">Inputs</th><th width="40%">Outputs</th></tr>
            <tr valign="top">
              <td><table id="inputStatusTable"></table></td>
              <td><table id="outputStatusTable"></table></td>
            </tr>
          </table>
    </div> <!-- divStatus -->

</body>
</html>

This is the result:
https://mediaserver.avenard.org/power/live

6 Likes