From 24b4a39fce61dd9bd8ab7757f3bbda0636adc8c9 Mon Sep 17 00:00:00 2001 From: James Barnett Date: Sat, 7 Apr 2018 18:49:48 +0100 Subject: Switch from http polling to data push via websockets --- routes/energy-usage.js | 166 ------------------------------------------------- routes/power-state.js | 23 ------- routes/ws.js | 25 ++++++++ 3 files changed, 25 insertions(+), 189 deletions(-) delete mode 100644 routes/energy-usage.js delete mode 100644 routes/power-state.js create mode 100644 routes/ws.js (limited to 'routes') diff --git a/routes/energy-usage.js b/routes/energy-usage.js deleted file mode 100644 index eb21eb9..0000000 --- a/routes/energy-usage.js +++ /dev/null @@ -1,166 +0,0 @@ -const express = require('express'); -const router = express.Router(); - -const deviceManager = require('../services/device-manager'); -const moment = require('moment'); - -router.get('/:deviceId/realtime', function(req, res, next) { - - let deviceId = req.params.deviceId; - - let realtimeUsage = {}; - // TODO - cache results with a short TTL so we don't hammer the plug if multiple clients are requesting data - deviceManager.getDevice(deviceId).emeter.getRealtime().then(response => { - - // Voltage seems to be reported as its peak to peak value, not RMS. - // Show the RMS value since thats what would you expect to see. - // i.e. 220v not 310v (in the U.K) - response.voltage = response.voltage / Math.sqrt(2); - - res.json(response); - }); - -}); - -router.get('/:deviceId/day-stats', function(req, res, next) { - - let deviceId = req.params.deviceId; - - // Get last x days - let totalDaysRequired = 30; // TODO currently only works for up to 2 months spans - let currentMoment = moment(); - let previousMoment = moment().subtract(totalDaysRequired, 'days'); - - // Month + 1 as the API months are index 1 based. - deviceManager.getDevice(deviceId).emeter.getDayStats(currentMoment.year(), currentMoment.month() +1).then(currentPeriodStats => { - - // Check if we also need the previous month to meet the required total number of samples - if(currentMoment.month() !== previousMoment.month()) { - - // Get previous month. This currently wont work if the previousMoment is more than 1 month before the currentMoment (see above) - deviceManager.getDevice(deviceId).emeter.getDayStats(previousMoment.year(), previousMoment.month() +1).then(previousPeriodStats => { - - let currentMonthStats = fillMissingDays(currentPeriodStats, currentMoment); - let previousMonthStats = fillMissingDays(previousPeriodStats, previousMoment); - let combinedStats = previousMonthStats.concat(currentMonthStats); - - res.json(trimStatResults(combinedStats, totalDaysRequired)); - - }); - } - else { - let dayStats = fillMissingDays(currentPeriodStats, currentMoment); - - res.json(trimStatResults(dayStats, totalDaysRequired)); - } - - }); - -}); - -router.get('/:deviceId/month-stats', function(req, res, next) { - let deviceId = req.params.deviceId; - - // Get last x months - let totalMonthsRequired = 12; // TODO currently only works for up to 14 month (2 year) spans - let currentMoment = moment(); - let previousMoment = moment().subtract(totalMonthsRequired, 'months'); - - deviceManager.getDevice(deviceId).emeter.getMonthStats(currentMoment.year()).then(currentPeriodStats => { - - // Check if we also need the previous year to meet the required total number of samples - if(currentMoment.month() + 1 < totalMonthsRequired) { - - // Get previous year (assuming the totalMonthsRequired limit described above). - deviceManager.getDevice(deviceId).emeter.getMonthStats(previousMoment.year()).then(previousPeriodStats => { - - let currentYearStats = fillMissingMonths(currentPeriodStats, currentMoment); - let previousYearStats = fillMissingMonths(previousPeriodStats, previousMoment); - let combinedStats = previousYearStats.concat(currentYearStats); - - res.json(trimStatResults(combinedStats, totalMonthsRequired)); - - }); - } - else { - let monthStats = fillMissingMonths(currentPeriodStats, currentMoment); - - res.json(trimStatResults(monthStats, totalMonthsRequired)); - } - - }); - -}); - -function fillMissingDays(sparseDayStats, statsMoment) { - let denseDayStats = []; - - let totalDays; - // If these stats are for the current month, fill up to the current day of the month - // Otherwise fill the whole month - if(moment().month() === statsMoment.month()) { - totalDays = statsMoment.date(); - } - else { - totalDays = statsMoment.daysInMonth(); - } - - Array.from({length: totalDays}, (x,i) => i + 1).forEach(d => { - - let stat = sparseDayStats.day_list.find(i => i.day === d); - - if(stat === undefined) { - denseDayStats.push({ - year: statsMoment.year(), - month: statsMoment.month() +1, - day: d, - energy: 0 - }) - } - else { - denseDayStats.push(stat); - } - - }); - - return denseDayStats; -} - -function fillMissingMonths(sparseMonthStats, statsMoment) { - let denseMonthStats = []; - - let maxMonths; - // Dont fill in months which exist in the future - if(statsMoment.year() === moment().year()) { - maxMonths = moment().month() + 1; // API months are 1 based - } - else { - maxMonths = 12; - } - - // Fill in any missing months up to the max amount - Array.from({length: maxMonths}, (x,i) => i + 1).forEach(m => { - - let stat = sparseMonthStats.month_list.find(i => i.month === m); - - if(stat === undefined) { - denseMonthStats.push({ - year: statsMoment.year(), - month: m, - energy: 0 - }) - } - else { - denseMonthStats.push(stat); - } - - }); - - return denseMonthStats; -} - -function trimStatResults(stats, maxSamples) { - return stats.splice(stats.length - maxSamples, stats.length); -} - -module.exports = router; \ No newline at end of file diff --git a/routes/power-state.js b/routes/power-state.js deleted file mode 100644 index 6333863..0000000 --- a/routes/power-state.js +++ /dev/null @@ -1,23 +0,0 @@ -const express = require('express'); -const router = express.Router(); - -const deviceManager = require('../services/device-manager'); -const moment = require('moment'); - -router.get('/:deviceId', function(req, res, next) { - - let deviceId = req.params.deviceId; - - deviceManager.getDevice(deviceId).getSysInfo().then(response => { - - let powerState = { - isOn: (response.relay_state === 1), - uptime: response.on_time - }; - - res.json(powerState); - }); - -}); - -module.exports = router; \ No newline at end of file diff --git a/routes/ws.js b/routes/ws.js new file mode 100644 index 0000000..dc22df2 --- /dev/null +++ b/routes/ws.js @@ -0,0 +1,25 @@ +const express = require('express'); +const router = express.Router(); + +const deviceManager = require('../services/device-manager'); +const dataFetcher = require('../services/data-fetcher'); +const dataBroadcaster = require('../services/data-broadcaster'); + +router.ws('/', function(ws, req) { + + ws.on('message', msg => { + + // Latest data is always pushed out to clients, but clients can also request cached data at any time. + if(msg === 'getCachedData') { + let cachedData = dataFetcher.getCachedData(); + + ws.send(dataBroadcaster.generatePayload('realtimeUsage', cachedData.realtimeUsage)); + ws.send(dataBroadcaster.generatePayload('dailyUsage', cachedData.dailyUsage)); + ws.send(dataBroadcaster.generatePayload('monthlyUsage', cachedData.monthlyUsage)); + ws.send(dataBroadcaster.generatePayload('powerState', cachedData.powerState)); + } + }); + +}); + +module.exports = router; -- cgit v1.2.3 From 1301d90e93af799a9054f133847ebf3cbda15f9d Mon Sep 17 00:00:00 2001 From: James Barnett Date: Sat, 7 Apr 2018 21:43:17 +0100 Subject: Add support for switching between multiple plugs --- routes/index.js | 20 ++++++++++++++++++-- routes/ws.js | 15 +++++++++------ 2 files changed, 27 insertions(+), 8 deletions(-) (limited to 'routes') diff --git a/routes/index.js b/routes/index.js index 4909ff8..12c62cd 100644 --- a/routes/index.js +++ b/routes/index.js @@ -5,11 +5,27 @@ const deviceManager = require('../services/device-manager'); router.get('/', function(req, res, next) { + let deviceId = sortDevices(deviceManager.getAllDevices())[0].deviceId; + + res.redirect('/' + deviceId); + +}); + +router.get('/:deviceId', function(req, res, next) { + + let deviceId = req.params.deviceId; + res.render('index', { - device: deviceManager.getDevice(), - devices: deviceManager.getAllDevices() + device: deviceManager.getDevice(deviceId), + devices: sortDevices(deviceManager.getAllDevices()) }); }); +function sortDevices(devices) { + return devices.slice().sort((a, b) => { + return a.alias.toLowerCase().localeCompare(b.alias.toLowerCase()) + }) +} + module.exports = router; diff --git a/routes/ws.js b/routes/ws.js index dc22df2..08b0a18 100644 --- a/routes/ws.js +++ b/routes/ws.js @@ -9,14 +9,17 @@ 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(msg === 'getCachedData') { - let cachedData = dataFetcher.getCachedData(); + if(message.requestType === 'getCachedData') { + let deviceId = message.deviceId; + let cachedData = dataFetcher.getCachedData(deviceId); - ws.send(dataBroadcaster.generatePayload('realtimeUsage', cachedData.realtimeUsage)); - ws.send(dataBroadcaster.generatePayload('dailyUsage', cachedData.dailyUsage)); - ws.send(dataBroadcaster.generatePayload('monthlyUsage', cachedData.monthlyUsage)); - ws.send(dataBroadcaster.generatePayload('powerState', cachedData.powerState)); + ws.send(dataBroadcaster.generatePayload('realtimeUsage', deviceId, cachedData.realtimeUsage)); + ws.send(dataBroadcaster.generatePayload('dailyUsage', deviceId, cachedData.dailyUsage)); + ws.send(dataBroadcaster.generatePayload('monthlyUsage', deviceId, cachedData.monthlyUsage)); + ws.send(dataBroadcaster.generatePayload('powerState', deviceId, cachedData.powerState)); } }); -- cgit v1.2.3