From 54056f43d601dce89501a993cbe5130cfb9f5619 Mon Sep 17 00:00:00 2001 From: Taylor Veith Date: Sat, 8 Jun 2019 16:57:04 -0500 Subject: [PATCH 1/4] Support toggling power state by clicking ON/OFF label --- public/javascripts/dash.js | 22 ++++++++++++++++++---- routes/ws.js | 21 ++++++++++++++++----- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/public/javascripts/dash.js b/public/javascripts/dash.js index 527c433..f87bbb8 100644 --- a/public/javascripts/dash.js +++ b/public/javascripts/dash.js @@ -1,4 +1,6 @@ var dash = { + ws: null, + deviceId: null, realtimeGauge: null, @@ -23,11 +25,12 @@ var dash = { this.initUsageLog(); this.initWsConnection(); + this.initTogglePowerState(); }, initWsConnection: function() { var wsUri = 'ws://' + window.location.host + '/ws' - var ws = new WebSocket(wsUri); + ws = new WebSocket(wsUri); ws.onopen = function () { console.log('Websocket connection established'); $('#connection-error').hide(200); @@ -242,6 +245,19 @@ var dash = { }); }, + initTogglePowerState: () => { + $('#power-state').on('click', () => { + if (ws) { + ws.send(JSON.stringify( + { + requestType: 'togglePowerState', + deviceId: dash.deviceId + } + )); + } + }); + }, + addLogEntry: function (logEntry, updateChart) { dash.usageLogChart.data.labels.push(moment(logEntry.ts, 'x').format("MMM Do HH:mm")); @@ -377,7 +393,5 @@ var dash = { else { $("#uptime").text(moment.duration(powerState.uptime, "seconds").format("d[d] h[h] m[m]", {largest: 2})); } - - }, - + } }; diff --git a/routes/ws.js b/routes/ws.js index 1599721..40b3f79 100644 --- a/routes/ws.js +++ b/routes/ws.js @@ -6,17 +6,17 @@ const dataFetcher = require('../services/data-fetcher'); const dataBroadcaster = require('../services/data-broadcaster'); const dataLogger = require('../services/data-logger.js'); -router.ws('/', function(ws, req) { +router.ws('/', function (ws, req) { ws.on('message', msg => { let message = JSON.parse(msg); // Latest data is always pushed out to clients, but clients can also request cached data at any time. - if(message.requestType === 'getCachedData') { + if (message.requestType === 'getCachedData') { let deviceId = message.deviceId; let cachedData = dataFetcher.getCachedData(deviceId); - + ws.send(dataBroadcaster.generatePayload('realtimeUsage', deviceId, cachedData.realtimeUsage)); ws.send(dataBroadcaster.generatePayload('dailyUsage', deviceId, cachedData.dailyUsage)); ws.send(dataBroadcaster.generatePayload('monthlyUsage', deviceId, cachedData.monthlyUsage)); @@ -24,10 +24,21 @@ router.ws('/', function(ws, req) { dataLogger.getLogEntriesForDevice(deviceId, (loggedData) => { ws.send(dataBroadcaster.generatePayload('loggedData', deviceId, loggedData)); }); - + + } else if (message.requestType === 'togglePowerState') { + let deviceId = message.deviceId; + let device = deviceManager.getDevice(deviceId); + if (device !== undefined) { + device.togglePowerState().then(result => { + ws.send(dataBroadcaster.generatePayload('powerState', deviceId, { + isOn: result, + uptime: 0 + })); + }); + } + } }); - }); module.exports = router; From def3cc26a898eef016b55ab6ef7500f6c6426b92 Mon Sep 17 00:00:00 2001 From: Taylor Veith Date: Sat, 8 Jun 2019 16:57:47 -0500 Subject: [PATCH 2/4] Prevent empty text when uptime is less than 60 seconds --- public/javascripts/dash.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/public/javascripts/dash.js b/public/javascripts/dash.js index f87bbb8..f0a84b8 100644 --- a/public/javascripts/dash.js +++ b/public/javascripts/dash.js @@ -389,9 +389,10 @@ var dash = { if(powerState.uptime === 0) { $("#uptime").text("-"); - } - else { + } else if (powerState.uptime > 60) { $("#uptime").text(moment.duration(powerState.uptime, "seconds").format("d[d] h[h] m[m]", {largest: 2})); + } else { + $("#uptime").text(moment.duration(powerState.uptime, "seconds").format("m[m] s[s]", {largest: 2})); } } }; From ed2497533ac7d065591f992c103f24dde126e434 Mon Sep 17 00:00:00 2001 From: Taylor Veith Date: Mon, 22 Jul 2019 20:47:18 -0500 Subject: [PATCH 3/4] Apply formatting --- public/javascripts/dash.js | 121 ++++++++++++++++++------------------- 1 file changed, 60 insertions(+), 61 deletions(-) diff --git a/public/javascripts/dash.js b/public/javascripts/dash.js index f0a84b8..6212409 100644 --- a/public/javascripts/dash.js +++ b/public/javascripts/dash.js @@ -11,7 +11,7 @@ var dash = { monthlyUsageChart: null, usageLogChart: null, - init: function(deviceId) { + init: function (deviceId) { this.deviceId = deviceId; if (this.deviceId) { @@ -23,12 +23,12 @@ var dash = { this.initDailyUsageChart(); this.initMonthlyUsageChart(); this.initUsageLog(); - + this.initWsConnection(); this.initTogglePowerState(); }, - initWsConnection: function() { + initWsConnection: function () { var wsUri = 'ws://' + window.location.host + '/ws' ws = new WebSocket(wsUri); ws.onopen = function () { @@ -43,7 +43,7 @@ var dash = { } ws.onmessage = dash.wsMessageHandler; - ws.onclose = function() { + ws.onclose = function () { // Usually caused by mobile devices going to sleep or the user minimising the browser app. // The setTimeout will begin once the device wakes from sleep or the browser regains focus. $('#connection-error').show(); @@ -51,32 +51,32 @@ var dash = { } }, - wsMessageHandler: function(messageEvent) { + wsMessageHandler: function (messageEvent) { let message = JSON.parse(messageEvent.data); - if(message.deviceId === dash.deviceId) { - if(message.dataType === 'realtimeUsage') { + if (message.deviceId === dash.deviceId) { + if (message.dataType === 'realtimeUsage') { dash.refreshRealtimeDisplay(message.data); } - else if(message.dataType === 'dailyUsage') { + else if (message.dataType === 'dailyUsage') { dash.parseDailyUsageData(message.data); } - else if(message.dataType === 'monthlyUsage') { + else if (message.dataType === 'monthlyUsage') { dash.parseMonthlyUsageData(message.data); } - else if(message.dataType === 'powerState') { + else if (message.dataType === 'powerState') { dash.refreshPowerState(message.data); } - else if(message.dataType === 'newLogEntry') { + else if (message.dataType === 'newLogEntry') { dash.addLogEntry(message.data, true); } - else if(message.dataType === 'loggedData') { + else if (message.dataType === 'loggedData') { dash.loadLogEntries(message.data); } } }, - initRealtimeGauge: function() { + initRealtimeGauge: function () { var opts = { angle: 0, lineWidth: 0.2, @@ -88,7 +88,7 @@ var dash = { limitMax: true, limitMin: true, generateGradient: true, - highDpiSupport: true, + highDpiSupport: true, staticLabels: { font: "12px sans-serif", labels: [500, 1500, 3000] @@ -100,14 +100,14 @@ var dash = { ] }; var target = document.getElementById('rtu-gauge'); - + dash.realtimeGauge = new Gauge(target).setOptions(opts); dash.realtimeGauge.maxValue = 3000; dash.realtimeGauge.setMinValue(0); dash.realtimeGauge.animationSpeed = 32; }, - initRealtimeTrendChart: function() { + initRealtimeTrendChart: function () { var ctx = document.getElementById('rtt-chart').getContext('2d'); this.realtimeTrendChart = new Chart(ctx, { type: 'line', @@ -129,7 +129,7 @@ var dash = { }], yAxes: [{ ticks: { - beginAtZero:true + beginAtZero: true } }] }, @@ -150,7 +150,7 @@ var dash = { }); }, - initDailyUsageChart: function() { + initDailyUsageChart: function () { var ctx = document.getElementById('du-chart').getContext('2d'); this.dailyUsageChart = new Chart(ctx, { type: 'bar', @@ -169,7 +169,7 @@ var dash = { scales: { yAxes: [{ ticks: { - beginAtZero:true + beginAtZero: true } }] }, @@ -181,7 +181,7 @@ var dash = { }); }, - initMonthlyUsageChart: function() { + initMonthlyUsageChart: function () { var ctx = document.getElementById('mu-chart').getContext('2d'); this.monthlyUsageChart = new Chart(ctx, { type: 'bar', @@ -200,7 +200,7 @@ var dash = { scales: { yAxes: [{ ticks: { - beginAtZero:true + beginAtZero: true } }] }, @@ -212,7 +212,7 @@ var dash = { }); }, - initUsageLog: function() { + initUsageLog: function () { var ctx = document.getElementById('logged-usage-chart').getContext('2d'); this.usageLogChart = new Chart(ctx, { type: 'line', @@ -259,7 +259,6 @@ var dash = { }, addLogEntry: function (logEntry, updateChart) { - dash.usageLogChart.data.labels.push(moment(logEntry.ts, 'x').format("MMM Do HH:mm")); dash.usageLogChart.data.datasets.forEach(function (dataset) { dataset.data.push({ @@ -272,16 +271,16 @@ var dash = { } }, - loadLogEntries: function(logEntries) { - logEntries.forEach(function(entry) { + loadLogEntries: function (logEntries) { + logEntries.forEach(function (entry) { dash.addLogEntry(entry, false); }) dash.usageLogChart.update(); }, - realtimeTrendChartOnRefresh: function(chart) { - chart.data.datasets.forEach(function(dataset) { + realtimeTrendChartOnRefresh: function (chart) { + chart.data.datasets.forEach(function (dataset) { dataset.data.push({ x: Date.now(), y: dash.realtimeTrendLastSample @@ -289,11 +288,11 @@ var dash = { }); }, - refreshRealtimeDisplay: function(realtime) { + refreshRealtimeDisplay: function (realtime) { - var power = Math.round(('power_mw' in realtime) ? (realtime.power_mw/1000) : realtime.power); - var current = (('current_ma' in realtime) ? (realtime.current_ma/1000) : realtime.current).toFixed(2); - var voltage = Math.round(('voltage_mv' in realtime) ? (realtime.voltage_mv/1000) : realtime.voltage); + var power = Math.round(('power_mw' in realtime) ? (realtime.power_mw / 1000) : realtime.power); + var current = (('current_ma' in realtime) ? (realtime.current_ma / 1000) : realtime.current).toFixed(2); + var voltage = Math.round(('voltage_mv' in realtime) ? (realtime.voltage_mv / 1000) : realtime.voltage); this.realtimeGauge.set(power); // might switch to Vue.js if this gets tedious @@ -305,21 +304,21 @@ var dash = { }, - parseDailyUsageData: function(usageData) { + parseDailyUsageData: function (usageData) { // Clear previous data dash.dailyUsageChart.data.labels = []; - dash.dailyUsageChart.data.datasets.forEach(function(dataset) { + dash.dailyUsageChart.data.datasets.forEach(function (dataset) { dataset.data = []; }); - usageData.forEach(function(entry) { + usageData.forEach(function (entry) { // Months from API response are 1 based var day = moment([entry.year, entry.month - 1, entry.day]); dash.dailyUsageChart.data.labels.push(day.format('MMM D')); - dash.dailyUsageChart.data.datasets.forEach(function(dataset) { - dataset.data.push(('energy_wh' in entry) ? (entry.energy_wh/1000) : entry.energy); + dash.dailyUsageChart.data.datasets.forEach(function (dataset) { + dataset.data.push(('energy_wh' in entry) ? (entry.energy_wh / 1000) : entry.energy); }); }); @@ -327,37 +326,37 @@ var dash = { dash.setDailyUsageStats(usageData); }, - setDailyUsageStats: function(usageData) { + setDailyUsageStats: function (usageData) { - var dailyTotal = usageData.find(function(d) { - return d.day === moment().date() && d.month === (moment().month()+1) && d.year === moment().year() + var dailyTotal = usageData.find(function (d) { + return d.day === moment().date() && d.month === (moment().month() + 1) && d.year === moment().year() }); - var energy = ('energy_wh' in dailyTotal) ? (dailyTotal.energy_wh/1000) : dailyTotal.energy + var energy = ('energy_wh' in dailyTotal) ? (dailyTotal.energy_wh / 1000) : dailyTotal.energy $("#total-day").text(energy.toFixed(2)); - var total = usageData.reduce(function(t, d) {return t + (('energy_wh' in d) ? (d.energy_wh/1000) : d.energy)}, 0); - var avg = total/usageData.length; + var total = usageData.reduce(function (t, d) { return t + (('energy_wh' in d) ? (d.energy_wh / 1000) : d.energy) }, 0); + var avg = total / usageData.length; $("#avg-day").text(avg.toFixed(2)); }, - parseMonthlyUsageData: function(usageData) { - + parseMonthlyUsageData: function (usageData) { + // Clear previous data dash.monthlyUsageChart.data.labels = []; - dash.monthlyUsageChart.data.datasets.forEach(function(dataset) { + dash.monthlyUsageChart.data.datasets.forEach(function (dataset) { dataset.data = []; }); - usageData.forEach(function(entry) { + usageData.forEach(function (entry) { // Months from API response are 1 based - var month = moment().month(entry.month -1); + var month = moment().month(entry.month - 1); dash.monthlyUsageChart.data.labels.push(month.format('MMM')); - dash.monthlyUsageChart.data.datasets.forEach(function(dataset) { - dataset.data.push(('energy_wh' in entry) ? (entry.energy_wh/1000) : entry.energy); + dash.monthlyUsageChart.data.datasets.forEach(function (dataset) { + dataset.data.push(('energy_wh' in entry) ? (entry.energy_wh / 1000) : entry.energy); }); }); @@ -365,34 +364,34 @@ var dash = { dash.setMonthlyUsageStats(usageData); }, - setMonthlyUsageStats: function(usageData) { + setMonthlyUsageStats: function (usageData) { - var monthlyTotal = usageData.find(function(m) { - return m.month === (moment().month()+1) && m.year === moment().year() + var monthlyTotal = usageData.find(function (m) { + return m.month === (moment().month() + 1) && m.year === moment().year() }); - var energy = ('energy_wh' in monthlyTotal) ? (monthlyTotal.energy_wh/1000) : monthlyTotal.energy + var energy = ('energy_wh' in monthlyTotal) ? (monthlyTotal.energy_wh / 1000) : monthlyTotal.energy $("#total-month").text(energy.toFixed(2)); - var total = usageData.reduce(function(t, m) {return t + (('energy_wh' in m) ? (m.energy_wh/1000) : m.energy)}, 0); - var avg = total/usageData.length; + var total = usageData.reduce(function (t, m) { return t + (('energy_wh' in m) ? (m.energy_wh / 1000) : m.energy) }, 0); + var avg = total / usageData.length; $("#avg-month").text(avg.toFixed(2)); }, - refreshPowerState: function(powerState) { - if(powerState.isOn) { + refreshPowerState: function (powerState) { + if (powerState.isOn) { $("#power-state").text("ON").attr("class", "label label-success"); } else { $("#power-state").text("OFF").attr("class", "label label-danger"); } - if(powerState.uptime === 0) { + if (powerState.uptime === 0) { $("#uptime").text("-"); } else if (powerState.uptime > 60) { - $("#uptime").text(moment.duration(powerState.uptime, "seconds").format("d[d] h[h] m[m]", {largest: 2})); + $("#uptime").text(moment.duration(powerState.uptime, "seconds").format("d[d] h[h] m[m]", { largest: 2 })); } else { - $("#uptime").text(moment.duration(powerState.uptime, "seconds").format("m[m] s[s]", {largest: 2})); + $("#uptime").text(moment.duration(powerState.uptime, "seconds").format("m[m] s[s]", { largest: 2 })); } } -}; +}; \ No newline at end of file From 4cd7be2eb5082afd79dee5c59240da70b486181b Mon Sep 17 00:00:00 2001 From: Taylor Veith Date: Mon, 22 Jul 2019 20:47:56 -0500 Subject: [PATCH 4/4] Reinitialize usage log before appending existing entries --- public/javascripts/dash.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/javascripts/dash.js b/public/javascripts/dash.js index 6212409..a5fd1c9 100644 --- a/public/javascripts/dash.js +++ b/public/javascripts/dash.js @@ -272,6 +272,8 @@ var dash = { }, loadLogEntries: function (logEntries) { + dash.initUsageLog(); + logEntries.forEach(function (entry) { dash.addLogEntry(entry, false); })