(function($, window, document) {
var options = {
timeout: 5000, // ajax timeout
heartBeat: {
interval: 10000 // heartbeat interval
},
sensors: {
interval: 30000 // sensor interval
},
messages: {
timeout: 10000 // message fade interval
},
api: "", // api url
test: {
// stage: "wifi" // setup state for testing
// stage: "middleware" // setup state for testing
}
};
var sparkdata = [],
sparkline;
function getUrlParams() {
var vars = {};
decodeURIComponent(window.location.search).replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) {
vars[key] = value;
});
return vars;
}
function hashCode(str) {
var hash = 0, i, chr, len;
if (str.length === 0) return hash;
for (i = 0, len = str.length; i < len; i++) {
chr = str.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
}
function ajax(url, settings) {
var log = (settings.data) ? url + "
" + JSON.stringify(settings.data) : url;
var hash = notify("info", "API Log", log, true);
return $.ajax(url, settings).done(function(json) {
$("." + hash + " .message").html($("." + hash + " .message").html() + "
" + JSON.stringify(json));
});
}
function template(tpl, target) {
return $(".template" + tpl).clone().removeClass("template").appendTo(target);
}
function initializePlugins() {
return $.getJSON(getApi("/api/plugins")).done(function(json) {
// add plugins
$.each(json, function(i, plugin) {
var unit,
el = template(".plugin", ".state-plugins:first()").addClass("plugin-" + plugin.name);
$(".state-plugins .not-configured").remove();
if (plugin.name == "1wire") {
unit = "°C";
plugin.title = "1-Wire";
el.find(".description").html("1-Wire is a device communications bus system designed by Dallas Semiconductor Corp. that provides low-speed data, signaling, and power over a single signal. (Source: Wikipedia)");
}
else if (plugin.name == "analog") {
unit = "V";
plugin.title = "Analog";
el.find(".description").html("Analog plugin uses the built-in analog to digital (ADC) converter to measure analog voltages.");
}
else if (plugin.name == "gpio") {
unit = "Imp";
plugin.title = "GPIO";
el.find(".description").html("GPIO plugin is used to register and count digital pulses.");
}
else if (plugin.name == "wifi") {
unit = "dbm";
plugin.title = "WiFi";
el.find(".description").html("WiFi plugin measures the received signal strength indicator (RSSI) if the WiFi signal.");
}
else if (plugin.name == "dht") {
unit = "°C";
plugin.title = "DHT";
el.find(".description").html("DHT sensors are basic, ultra low-cost digital temperature and humidity sensors.");
}
else {
unit = "";
plugin.title = plugin.name;
}
el.find(".name, .title").text(plugin.title);
if (json && json.length) {
$(".state-plugins .not-configured").remove();
}
// add sensors
$.each(plugin.sensors, function(j, sensor) {
$(".state-sensors .not-configured").remove();
// update sensor, check unit again
sensor.plugin = plugin.name;
sensor.unit = unit;
getSensorType(sensor);
// home screen
template(".sensor-home", ".state-home").addClass("sensor-" + sensor.plugin + "-" + sensor.addr);
// plugins screen
var el = template(".sensor", ".state-sensors").addClass("sensor-" + sensor.plugin + "-" + sensor.addr);
updateSensorUI(sensor);
});
});
});
}
function updateSensorUI(sensor) {
var el = $(".sensor-" + sensor.plugin + "-" + sensor.addr);
el.data(sensor);
el.find(".name").text(sensor.addr);
el.find(".value").text(sensor.value);
el.find(".unit").text(sensor.unit);
el.find(".sensor-monitor").unbind().click(function() {
// "Monitor");
window.open(getFrontend() + "?uuid[]=" + sensor.uuid, "frontend");
});
if (sensor.uuid) {
el.find(".sensor-connect").addClass("hide");
el.find(".sensor-monitor, .sensor-disconnect, .sensor-delete").removeClass("hide");
el.find(".sensor-disconnect").unbind().click(function() {
disconnectSensor(sensor);
});
el.find(".sensor-delete").unbind().click(function() {
disconnectSensor(sensor, true);
});
}
else {
el.find(".sensor-monitor, .sensor-disconnect, .sensor-delete").addClass("hide");
el.find(".sensor-connect").removeClass("hide").unbind().click(function() {
connectSensor(sensor);
});
}
// update monitor all button
if ($(".state-sensors .sensor:not(.template)").length) {
$(".state-sensors .sensor-frontend").removeClass("secondary");
}
else {
$(".state-sensors .sensor-frontend").addClass("secondary");
}
$(".state-sensors .sensor:not(.template)").each(function(i, el) {
console.log($(el).data());
});
}
function connectSensor(sensor) {
// 1) check if sensor already exists at middleware
var deferredUuid = $.ajax(getMiddleware() + "/iot/" + sensor.hash + ".json").then(function(json) {
if (json.entities && json.entities.length === 1) {
// exactly one match - resolve uuid
return $.Deferred().resolveWith(this, [json.entities[0].uuid]);
}
if (json.entities === undefined) {
// wrong mw version
console.warn("FOOOOO"); // @TODO
notify("warning", "Middleware failed", "Could not perform middleware operation. Check middleware version (needs d63104b).");
}
// 2) if not create channel
var data = {
operation: "add",
type: getSensorType(sensor),
title: sensor.addr,
owner: sensor.hash, // remember sensor hash
style: "lines",
resolution: 1
};
return $.ajax(getMiddleware() + "/channel.json?" + $.param(data)).then(function(json) {
if (json.entity !== undefined && json.entity.uuid !== undefined) {
return $.Deferred().resolveWith(this, [json.entity.uuid]);
}
notify("error", "Sensor not connected", "Could not connect sensor " + sensor.addr + " to the middleware. Middleware says: '" + json.exception.message + "'");
return $.Deferred().reject();
}, function() {
notify("error", "Sensor not connected", "Could not connect sensor " + sensor.addr + " to the middleware.");
return $.Deferred().reject();
});
}, function() {
notify("error", "Middleware error", "Could not connect to middleware.");
});
// 3) update sensor config with uuid
deferredUuid.done(function(uuid) {
sensor.uuid = uuid;
$.ajax(getSensorApi(sensor) + "?" + $.param({
uuid: sensor.uuid
}))
.done(function(json) {
notify("success", "Sensor associated", "The sensor " + sensor.addr + " is now successfully connected. Sensor data will be directly logged to the middleware.");
updateSensorUI(sensor);
})
.fail(function() {
notify("error", "Sensor not connected", "Failed to update sensor " + sensor.addr + " with middleware identifier.");
});
});
}
function disconnectSensor(sensor, fullDelete) {
var deferred = (fullDelete) ?
$.ajax(getMiddleware() + "/channel/" + sensor.uuid + ".json?" + $.param({
operation: "delete"
})) :
$.Deferred().resolveWith(this, [{}]);
deferred.done(function(json) {
if (json.exception !== undefined) {
var msg = "Could not delete sensor " + sensor.addr + " from middleware. Sensor will be disconnected instead. ";
msg += "Middleware says: '" + json.exception.message + "'";
notify("error", "Sensor not deleted", msg);
}
// clear uuid from vzero
$.ajax(getSensorApi(sensor) + "?" + $.param({
uuid: ""
}))
.done(function(json) {
delete sensor.uuid;
notify("success", "Sensor disconnected", "The sensor " + sensor.addr + " has been successfully disconnected. Sensor data will no longer be logged to the middleware.");
updateSensorUI(sensor);
})
.fail(function() {
notify("error", "Sensor not disconnected", "Failed to delete middleware identifier from sensor " + sensor.addr + ".");
});
})
.fail(function() {
notify("error", "Sensor not disconnected", "Could not disconnect sensor " + sensor.addr + " from the middleware.");
});
}
function updateSensors() {
$.ajax(getApi("/api/plugins"), {
timeout: options.timeout
})
.done(function(json) {
// plugins
$.each(json, function(i, plugin) {
// sensors
$.each(plugin.sensors, function(j, sensor) {
$(".sensor-" + plugin.name + "-" + sensor.addr + " .value").text(sensor.value);
});
});
})
.fail(function() {
notify("warning", "No connection", "Could not update sensors from VZero.");
});
}
function getSensorType(sensor) {
switch (sensor.plugin) {
case "1wire":
return "temperature";
case "dht":
if (sensor.addr == "temp") {
sensor.unit = "°C";
return "temperature";
}
sensor.unit = "%";
return "humidity";
case "analog":
return "voltage";
case "wifi":
return "rssi";
default:
return "powersensor";
}
}
function getApi(api) {
return options.api + api;
}
function getSensorApi(data) {
return getApi("/api/" + data.plugin + "/" + data.addr);
}
function getMiddleware() {
var mw = $(".middleware").val();
if (!mw) {
mw = "http://localhost:8888/vz/htdocs/middleware.php";
$(".middleware").val(mw);
}
if (mw.length && mw[mw.length-1] == '/') {
mw = mw.substring(0, mw.length-1);
}
return mw;
}
function getFrontend() {
var mw = getMiddleware();
var i = mw.indexOf("middleware");
if (i >= 0) {
mw = mw.substring(0, i-1);
}
if (mw.length && mw[mw.length-1] == '/') {
mw = mw.substring(0, mw.length-1);
}
mw += "/frontend";
return mw;
}
function heartBeat(initial) {
return $.ajax(getApi('/api/status' + (initial ? "?initial=1" : "")), {
timeout: options.timeout
})
.done(function(json) {
// [0: "DEFAULT", 1: "WDT", 2: "EXCEPTION", 3: "SOFT_WDT", 4: "SOFT_RESTART", 5: "DEEP_SLEEP_AWAKE", 6: "EXT_SYS_RST"]
if (json.resetcode == 1 || json.resetcode == 3) {
notify("error", "Unexpected restart", "The VZero has experienced an unexpected restart, triggered by the built-in watch dog timer.");
}
else if (json.resetcode == 2) {
notify("error", "Unexpected restart", "The VZero has experienced an unexpected restart, caused by an exception.");
}
else if (json.resetcode == 4 && json.uptime < 30000) {
notify("warning", "Restart", "The VZero was restarted.");
}
var heap = json.heap;
if (heap < 8192) {
notify("warning", "Low memory", "Available memory has reached a critical limit. VZero might become unstable.");
}
if (heap > 1024) {
heap = Math.round(heap / 1024) + 'kB';
}
if (json.minheap) {
heap += " (min " + Math.round(json.minheap / 1024) + 'kB' + ")";
}
$(".heap").text(heap);
var flash = json.flash;
if (flash > 1024) {
flash = Math.round(flash / 1024) + 'kB';
}
$(".flash").text(flash);
var date = new Date(json.uptime);
var hours = parseInt(date / 3.6e6) % 24;
$(".uptime").text(
(hours < 10 ? "0" + hours : hours) +":"+ ("0"+date.getMinutes()).slice(-2) +":"+ ("0"+date.getSeconds()).slice(-2)
);
if (sparkline) {
sparkdata.push(json.heap);
if (sparkdata.length > 50) {
sparkdata.shift();
}
sparkline.draw(sparkdata);
}
return $.Deferred().resolveWith(this, [json]);
})
.fail(function() {
notify("warning", "No connection", "Could not update status from VZero.");
});
}
function notify(type, title, message, force) {
var hash = "hash" + hashCode(message);
if (force) hash += Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
var el = $("." + hash);
if (el.length > 0) {
el.data({created: Date.now()});
return hash;
}
// unhide message area
$(".footer-container").removeClass("hide");
// add message
el = template("." + type, ".messages").addClass(hash).data({created: Date.now()});
el.find(".title").text(title);
el.find(".message").text(message);
return hash;
}
function menu(sel) {
$(".menu a").removeClass("em");
$(".menu a[href=#" + sel + "]").addClass("em");
$("body > .column.row:not(.state-always)").addClass("hide");
$(".column.row.state-" + sel + ", .column.row.state-menu").removeClass("hide");
}
function connectDevice() {
heartBeat(true).done(function(json) {
$('.loader').remove();
$('.content > *').unwrap();
var addr = "http://vzero-" + json.serial + ".local";
$("a.address").text(addr);
$("a.address").attr("href", addr);
$('title').text($('title').text() + " (" + json.serial + ")");
$('.ip').text(json.ip);
$('.wifimode').text(json.wifimode);
$('.serial').text(json.serial);
$('.build').text(json.build);
$('.ssid').val(json.ssid);
$('.pass').val(json.pass);
$('.middleware').val(json.middleware);
// initial setup - wifi
if ($(".wifimode").text() == "Access Point" || options.test.stage == "wifi") {
$(".column.row:not(.state-always), .menu-container").addClass("hide");
$(".state-initial-wifi").removeClass("hide");
}
// initial setup - middleware
else if ($(".middleware").val().trim() === "" || options.test.stage == "middleware") {
$(".middleware").val("http://demo.volkszaehler.org/middleware.php");
$(".column.row:not(.state-always), .menu-container").addClass("hide");
$(".state-initial-middleware").removeClass("hide");
}
else {
menu(window.location.hash.substr(1) || "home");
$(".menu a").click(function() {
menu($(this).attr("href").slice(1));
});
sparkline = new Sparkline($(".spark")[0], {
height: 30,
width: $(".spark").width(),
lineColor: "#666",
endColor: "#0292C0",
min: 0
});
// read plugins and setup sensor update
initializePlugins().done(function() {
window.setInterval(function() {
updateSensors();
}, options.sensors.interval);
});
// setup heartbeat update
window.setInterval(function() {
heartBeat();
}, options.heartBeat.interval);
window.setInterval(function() {
var now = Date.now();
$(".messages .row").each(function(i, el) {
if (now - $(el).data().created > options.messages.timeout) {
$(el).fadeOut("slow", function() {
$(el).remove();
if ($(".messages .row").length === 0) {
$(".messages").addClass("hide");
}
});
}
});
}, 2000);
}
}).fail(function() {
window.setTimeout(connectDevice, 200);
});
}
// dom ready
$(function() {
// url parameters
var params = getUrlParams();
$.extend(options, params);
console.warn(options);
// js loaded - update UI
$('.loader .subheader').text("connecting...");
connectDevice();
});
})(window.jQuery, window, document);