node-red-contrib-virtualmeter
Version:
Virtual Meter Node to be used within Corrently Ecosystem (German Electricity Grid)
231 lines (196 loc) • 9.06 kB
JavaScript
const _ = require("lodash");
module.exports = function(RED) {
let Influx = require('influx');
function LoadPredictionNode(config) {
RED.nodes.createNode(this,config);
this.influxdbConfig = RED.nodes.getNode(config.influxdb);
this.client = this.influxdbConfig.client;
var node = this;
node.on('input', async function(msg) {
let energy_reading = 0;
let energy_request = await node.client.query('select last("energy") from '+config.name,{});
if(energy_request.length > 0) {
energy_reading = energy_request[0].last;
}
let query = 'SELECT mean("power"),stddev("power"),min("power"),max("power") FROM "'+config.name+'" WHERE time>now()-7d GROUP BY time(1h) fill(previous)';
let results = await node.client.query(query, {});
let cleaned = [];
for(let i=0;i<results.length;i++) {
if(results[i].mean !== null) {
let date = new Date(results[i].time);
results[i].hour = date.getHours();
results[i].day = date.getDay();
if((results[i].day >0) && (results[i].day <6)) {
results[i].weekend = 0;
} else {
results[i].weekend = 1;
}
cleaned.push(results[i]);
}
}
// Initialize Classification
let class_hour = {};
let class_weekend = {};
let class_day = {};
const mean = function(aobj) {
let sum = 0;
let cnt = 0;
for(let i=0;i<aobj.length;i++) {
sum += aobj[i].mean;
cnt++;
}
return sum/cnt;
}
const classStats = function(classification) {
let sum = 0;
let min = 999999999999;
let max = -99999999999;
let sum_stddev = 0;
let cnt = 0;
for (let c in classification) {
if(c !== "stats") {
for(let i=0;i<classification[c].length;i++) {
sum += classification[c][i].mean;
sum_stddev += classification[c][i].stddev;
cnt ++;
if(classification[c][i].min < min) min = classification[c][i].min;
if(classification[c][i].max > max) max = classification[c][i].max;
}
}
}
classification.stats = {
min:min,
max:max,
mean:sum/cnt,
stddev: sum_stddev/cnt,
cnt:cnt,
sum:sum
}
}
const classify = function(classobject,candidate,fieldname) {
if(typeof classobject["c"+candidate[fieldname]] == "undefined") {
classobject["c"+candidate[fieldname]] = [];
}
classobject["c"+candidate[fieldname]].push(candidate);
}
// build Classifications
for(let i=0; i<cleaned.length;i++) {
classify(class_hour,cleaned[i],'hour');
classify(class_weekend,cleaned[i],'weekend');
classify(class_day,cleaned[i],'day');
}
classStats(class_hour);
classStats(class_weekend);
classStats(class_day);
for(let c in class_weekend) {
if(c !== "stats") {
let class_weekend_hour = {};
let class_weekend_day = {};
for(let i=0;i<class_weekend[c].length;i++) {
classify(class_weekend_hour,class_weekend[c][i],'hour');
classify(class_weekend_day,class_weekend[c][i],'day');
}
classStats(class_weekend_hour);
for(let d in class_weekend_hour) {
if(d !== "stats") {
classStats(class_weekend_hour[d]);
}
}
classStats(class_weekend_day);
for(let d in class_weekend_day) {
if(d !== "stats") {
classStats(class_weekend_day[d]);
}
}
if(typeof class_weekend.subclasses == "undefined") class_weekend.subclasses = {};
if(typeof class_weekend.subclasses[c] == "undefined") class_weekend.subclasses[c] = {};
class_weekend.subclasses[c].hour = class_weekend_hour;
class_weekend.subclasses[c].day = class_weekend_day;
}
}
// run prediction of upcomming 48 hours
let date = new Date();
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);
let ts = date.getTime();
let predictions = [];
for(let i=0;i<72;i++) {
// classes
let hour = new Date(ts).getHours();
let day = new Date(ts).getDay();
let weekend = 1;
if((day >0) && (day <6)) {
weekend = 0;
}
let power =0;
let cnt = 0;
let srcs = [];
// best Class is weekend_hour fallback to weekend day
if(typeof class_weekend.subclasses["c"+weekend] !== "undefined") {
if(typeof class_weekend.subclasses["c"+weekend].hour["c"+hour] !== "undefined") {
power += mean(class_weekend.subclasses["c"+weekend].hour["c"+hour]);
cnt++;
srcs.push("weekend_hour");
} else if(typeof class_weekend.subclasses["c"+weekend].day["c"+day] !== "undefined") {
power += mean(class_weekend.subclasses["c"+weekend].day["c"+day]);
cnt++;
srcs.push("weekend_day");
}
if(cnt === 0) {
power += class_weekend.stats.mean;
cnt++;
srcs.push("weekend_stats");
}
} else {
if(typeof class_hour["c"+hour] !== "undefined") {
power += mean(class_hour["c"+hour]);
cnt ++;
srcs.push("hour");
} else if(typeof class_day["c"+day] !== "undefined") {
power += mean(class_day["c"+day]);
cnt ++;
srcs.push("day");
} else if(typeof class_weekend["c"+weekend] !== "undefined") {
power += mean(class_hour["c"+weekend]);
cnt ++;
srcs.push("weekend");
} else {
power += class_hour.stats.mean;
cnt ++;
srcs.push("hour_stats");
}
}
energy_reading += power;
let prediction = {
time: new Date(ts),
mean: power,
energy: energy_reading,
srcs: srcs
};
predictions.push(prediction);
ts+=3600000;
}
msg.payload = predictions;
let batch = [];
let measurement = ''
if((typeof config.name !== 'undefined') && (config.name !== null)) {
measurement = config.name+"_prediction";
} else {
measurement = node.id+"_prediction";
}
for(let i=0;i<predictions.length;i++) {
batch.push({
measurement: measurement,
fields: {
power: predictions[i].mean,
energy: predictions[i].energy
},
timestamp: new Date(predictions[i].time).getTime() * 1000000
});
}
node.send([msg,{payload:batch}]);
});
}
RED.nodes.registerType("loadprediction",LoadPredictionNode);
}