diff options
| author | James Barnett <noreply@jamesbarnett.xyz> | 2018-03-31 19:56:55 +0100 |
|---|---|---|
| committer | James Barnett <noreply@jamesbarnett.xyz> | 2018-03-31 19:56:55 +0100 |
| commit | f53f1d433b5d458ff0b8a5893e8127a8be3630f7 (patch) | |
| tree | a7145c7947a9ab6a77e49077c1aeb777fd81288e | |
| parent | f5e0f6bd1b12e347387ff4cefcfc54d9d7dcc348 (diff) | |
| download | tplink-energy-monitor-f53f1d433b5d458ff0b8a5893e8127a8be3630f7.tar.xz tplink-energy-monitor-f53f1d433b5d458ff0b8a5893e8127a8be3630f7.zip | |
Add daily kWH usage total for last 30 days
| -rw-r--r-- | package-lock.json | 5 | ||||
| -rw-r--r-- | package.json | 3 | ||||
| -rw-r--r-- | public/javascripts/dash.js | 66 | ||||
| -rw-r--r-- | routes/energy-usage.js | 71 | ||||
| -rw-r--r-- | views/index.hbs | 20 |
5 files changed, 159 insertions, 6 deletions
diff --git a/package-lock.json b/package-lock.json index 6eba9a5..36670a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -401,6 +401,11 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" }, + "moment": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.0.tgz", + "integrity": "sha512-1muXCh8jb1N/gHRbn9VDUBr0GYb8A/aVcHlII9QSB68a50spqEVLIGN6KVmCOnSvJrUhC0edGgKU5ofnGXdYdg==" + }, "morgan": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz", diff --git a/package.json b/package.json index 3b857f1..6eb6e9e 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "hbs": "~4.0.1", "http-errors": "~1.6.2", "morgan": "~1.9.0", - "tplink-smarthome-api": "0.22.0" + "tplink-smarthome-api": "0.22.0", + "moment": "2.22.0" } } diff --git a/public/javascripts/dash.js b/public/javascripts/dash.js index 510f804..3d02fd8 100644 --- a/public/javascripts/dash.js +++ b/public/javascripts/dash.js @@ -6,10 +6,15 @@ var dash = { realtimeTrendChart: null, realtimeTrendLastSample: 0, + dailyUsageChart: null, + init: function() { this.initRealtimeGauge(); this.initRealtimeTrendChart(); + this.initDailyUsageChart(); + this.startPolling(); + this.getDailyUsageData(); }, initRealtimeGauge: function() { @@ -77,6 +82,37 @@ var dash = { }); }, + initDailyUsageChart: function() { + var ctx = document.getElementById('du-chart').getContext('2d'); + this.dailyUsageChart = new Chart(ctx, { + type: 'bar', + data: { + datasets: [{ + label: "Energy (kWH)", + borderColor: 'rgb(255, 99, 132)', + backgroundColor: 'rgb(255, 99, 132)', + data: [] + }] + }, + options: { + legend: { + display: false + }, + scales: { + yAxes: [{ + ticks: { + beginAtZero:true + } + }] + }, + maintainAspectRatio: false, + tooltips: { + intersect: false + }, + } + }); + }, + realtimeTrendChartOnRefresh: function(chart) { chart.data.datasets.forEach(function(dataset) { dataset.data.push({ @@ -110,7 +146,7 @@ var dash = { this.realtimeGauge.set(power); // might switch to Vue.js if this gets tedious - $("#rtu-power").text(power + " kW") + $("#rtu-power").text(power + " W") $("#rtu-current").text(current + " A") $("#rtu-voltage").text(voltage + " V") @@ -134,7 +170,33 @@ var dash = { this.realtimeTrendChart.options.plugins.streaming = false; }, -} + getDailyUsageData: function() { + $.ajax({ + url: "/energy-usage/1/day-stats", + type: "GET", + success: function(data) { + dash.parseDailyUsageData(data); + }, + dataType: "json", + timeout: 4000 + }); + }, + + parseDailyUsageData: function(usageData) { + 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(entry.energy); + }); + }); + + dash.dailyUsageChart.update(); + } + +}; $(document).ready(function () { diff --git a/routes/energy-usage.js b/routes/energy-usage.js index 847248d..5706c75 100644 --- a/routes/energy-usage.js +++ b/routes/energy-usage.js @@ -2,16 +2,17 @@ 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 voltage, not RMS. + // 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); @@ -21,4 +22,70 @@ router.get('/:deviceId/realtime', function(req, res, next) { }); +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(trimDayStatResults(combinedStats, totalDaysRequired)); + + }); + } + else { + let dayStats = fillMissingDays(currentPeriodStats, currentMoment); + + res.json(trimDayStatResults(dayStats, totalDaysRequired)); + } + + }); + +}); + +function fillMissingDays(sparseDayStats, statsMoment) { + let denseDayStats = []; + + // Fill in any missing days + let totalDays = statsMoment.date(); + 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 trimDayStatResults(stats, maxSamples) { + return stats.splice(stats.length - maxSamples, stats.length); +} + module.exports = router;
\ No newline at end of file diff --git a/views/index.hbs b/views/index.hbs index 33d5549..ae7dac9 100644 --- a/views/index.hbs +++ b/views/index.hbs @@ -63,7 +63,7 @@ <div class="x_content"> <div class="row"> <h1 class="text-center"> - <strong id="rtu-power">- kW</strong> + <strong id="rtu-power">- W</strong> </h1> </div> <div class="row text-center"> @@ -103,6 +103,24 @@ </div> + <div class="row"> + <div class="col-md-6 col-sm-6 col-xs-12"> + <div class="x_panel tile"> + <div class="x_title"> + <h2> + <strong>Last 30 days (kWH)</strong> + </h2> + <div class="clearfix"></div> + </div> + <div class="x_content"> + <canvas id="du-chart" height="270"></canvas> + </div> + </div> + </div> + + + </div> + </div> </div> </div> |