node-red-contrib-energymeterplus 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/energyMeterPlus.js +78 -70
- package/package.json +1 -1
package/energyMeterPlus.js
CHANGED
|
@@ -11,79 +11,29 @@ module.exports = function(RED) {
|
|
|
11
11
|
let inputUnit = config.inputUnit || "W";
|
|
12
12
|
let currencyCode = config.currency || "USD";
|
|
13
13
|
|
|
14
|
-
// Baseline
|
|
15
|
-
let baseline = node.context().get("baseline") || {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
let accumulated = node.context().get("accumulated") || {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
function currencySymbol(code) {
|
|
30
|
-
switch (code) {
|
|
31
|
-
case "USD": return "$";
|
|
32
|
-
case "EUR": return "€";
|
|
33
|
-
case "NGN": return "₦";
|
|
34
|
-
default: return code;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
function round2(val) {
|
|
38
|
-
if (val === null || val === undefined || isNaN(val)) return 0;
|
|
39
|
-
return Number(val.toFixed(2));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function archiveYearly(total, year) {
|
|
43
|
-
try {
|
|
44
|
-
const archivePath = path.join(path.dirname(filePath), "yearly_archive.json");
|
|
45
|
-
let archive = [];
|
|
46
|
-
if (fs.existsSync(archivePath)) {
|
|
47
|
-
archive = JSON.parse(fs.readFileSync(archivePath));
|
|
48
|
-
}
|
|
49
|
-
archive.push({ year, total, timestamp: new Date().toISOString() });
|
|
50
|
-
fs.writeFileSync(archivePath, JSON.stringify(archive, null, 2));
|
|
51
|
-
} catch (err) {
|
|
52
|
-
node.error("Failed to archive yearly total: " + err);
|
|
53
|
-
}
|
|
14
|
+
// Baseline initialization + merge editor config
|
|
15
|
+
let baseline = node.context().get("baseline") || { daily:0, weekly:0, monthly:0, yearly:0 };
|
|
16
|
+
baseline.daily += Number(config.baselineDaily) || 0;
|
|
17
|
+
baseline.weekly += Number(config.baselineWeekly) || 0;
|
|
18
|
+
baseline.monthly += Number(config.baselineMonthly) || 0;
|
|
19
|
+
baseline.yearly += Number(config.baselineYearly) || 0;
|
|
20
|
+
node.context().set("baseline", baseline);
|
|
21
|
+
|
|
22
|
+
// Accumulated values
|
|
23
|
+
let accumulated = node.context().get("accumulated") || { daily:0, weekly:0, monthly:0, yearly:0 };
|
|
24
|
+
|
|
25
|
+
// Last check timestamp
|
|
26
|
+
let lastCheck = node.context().get("lastCheck");
|
|
27
|
+
if (!(lastCheck instanceof Date)) {
|
|
28
|
+
lastCheck = new Date(lastCheck || Date.now());
|
|
54
29
|
}
|
|
55
30
|
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
let now = new Date();
|
|
59
|
-
|
|
60
|
-
if (now.getDate() !== lastCheck.getDate()) {
|
|
61
|
-
accumulated.daily = 0; baseline.daily = 0;
|
|
62
|
-
}
|
|
63
|
-
if (now.getDay() === 1 && lastCheck.getDay() !== 1) {
|
|
64
|
-
accumulated.weekly = 0; baseline.weekly = 0;
|
|
65
|
-
}
|
|
66
|
-
if (now.getMonth() !== lastCheck.getMonth()) {
|
|
67
|
-
accumulated.monthly = 0; baseline.monthly = 0;
|
|
68
|
-
}
|
|
69
|
-
if (now.getFullYear() !== lastCheck.getFullYear()) {
|
|
70
|
-
archiveYearly(baseline.yearly + accumulated.yearly, lastCheck.getFullYear());
|
|
71
|
-
accumulated.yearly = 0; baseline.yearly = 0;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
lastCheck = now;
|
|
75
|
-
node.context().set("accumulated", accumulated);
|
|
76
|
-
node.context().set("baseline", baseline);
|
|
77
|
-
node.context().set("lastCheck", lastCheck);
|
|
78
|
-
}
|
|
79
|
-
setInterval(checkRollover, 60000);
|
|
80
|
-
|
|
81
|
-
// Input handler
|
|
82
|
-
node.on('input', function(msg) {
|
|
31
|
+
// 1. Input handler: baseline updates + power increments
|
|
32
|
+
node.on('input', function(msg) {
|
|
83
33
|
let now = new Date();
|
|
84
34
|
let durationHours = (now - lastCheck) / (1000 * 3600);
|
|
85
35
|
|
|
86
|
-
// Handle baseline updates
|
|
36
|
+
// Handle baseline updates via message
|
|
87
37
|
if (msg.topic === "applyBaseline" && msg.payload) {
|
|
88
38
|
baseline.daily += Number(msg.payload.daily) || 0;
|
|
89
39
|
baseline.weekly += Number(msg.payload.weekly) || 0;
|
|
@@ -92,7 +42,7 @@ module.exports = function(RED) {
|
|
|
92
42
|
node.context().set("baseline", baseline);
|
|
93
43
|
}
|
|
94
44
|
|
|
95
|
-
|
|
45
|
+
// 2. Handle power input
|
|
96
46
|
let power = Number(msg.payload);
|
|
97
47
|
if (!isNaN(power) && durationHours > 0) {
|
|
98
48
|
let power_kW = (inputUnit === "W") ? power / 1000 : power;
|
|
@@ -108,6 +58,7 @@ module.exports = function(RED) {
|
|
|
108
58
|
node.context().set("lastCheck", lastCheck);
|
|
109
59
|
node.context().set("accumulated", accumulated);
|
|
110
60
|
|
|
61
|
+
// 3. Output payload
|
|
111
62
|
msg.payload = {
|
|
112
63
|
energyDaily: round2(baseline.daily + accumulated.daily),
|
|
113
64
|
energyWeekly: round2(baseline.weekly + accumulated.weekly),
|
|
@@ -120,6 +71,7 @@ module.exports = function(RED) {
|
|
|
120
71
|
currency: currencySymbol(currencyCode)
|
|
121
72
|
};
|
|
122
73
|
|
|
74
|
+
// 4. Persist to file
|
|
123
75
|
const dir = path.dirname(filePath);
|
|
124
76
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
125
77
|
try { fs.writeFileSync(filePath, JSON.stringify(msg.payload, null, 2)); }
|
|
@@ -127,9 +79,65 @@ module.exports = function(RED) {
|
|
|
127
79
|
|
|
128
80
|
node.send(msg);
|
|
129
81
|
});
|
|
82
|
+
|
|
83
|
+
// 5. Rollover logic
|
|
84
|
+
function checkRollover() {
|
|
85
|
+
let now = new Date();
|
|
86
|
+
|
|
87
|
+
if (!(lastCheck instanceof Date)) {
|
|
88
|
+
lastCheck = new Date(lastCheck || Date.now());
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (now.getDate() !== lastCheck.getDate()) {
|
|
92
|
+
accumulated.daily = 0; baseline.daily = 0;
|
|
93
|
+
}
|
|
94
|
+
if (now.getDay() === 1 && lastCheck.getDay() !== 1) {
|
|
95
|
+
accumulated.weekly = 0; baseline.weekly = 0;
|
|
96
|
+
}
|
|
97
|
+
if (now.getMonth() !== lastCheck.getMonth()) {
|
|
98
|
+
accumulated.monthly = 0; baseline.monthly = 0;
|
|
99
|
+
}
|
|
100
|
+
if (now.getFullYear() !== lastCheck.getFullYear()) {
|
|
101
|
+
archiveYearly(baseline.yearly + accumulated.yearly, lastCheck.getFullYear());
|
|
102
|
+
accumulated.yearly = 0; baseline.yearly = 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
lastCheck = now;
|
|
106
|
+
node.context().set("lastCheck", lastCheck);
|
|
107
|
+
node.context().set("accumulated", accumulated);
|
|
108
|
+
node.context().set("baseline", baseline);
|
|
109
|
+
}
|
|
110
|
+
setInterval(checkRollover, 60000);
|
|
111
|
+
|
|
112
|
+
// 6. Helpers
|
|
113
|
+
function currencySymbol(code) {
|
|
114
|
+
switch (code) {
|
|
115
|
+
case "USD": return "$";
|
|
116
|
+
case "EUR": return "€";
|
|
117
|
+
case "NGN": return "₦";
|
|
118
|
+
default: return code;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function round2(val) {
|
|
122
|
+
if (val === null || val === undefined || isNaN(val)) return 0;
|
|
123
|
+
return Number(val.toFixed(2));
|
|
124
|
+
}
|
|
125
|
+
function archiveYearly(total, year) {
|
|
126
|
+
try {
|
|
127
|
+
const archivePath = path.join(path.dirname(filePath), "yearly_archive.json");
|
|
128
|
+
let archive = [];
|
|
129
|
+
if (fs.existsSync(archivePath)) {
|
|
130
|
+
archive = JSON.parse(fs.readFileSync(archivePath));
|
|
131
|
+
}
|
|
132
|
+
archive.push({ year, total, timestamp: new Date().toISOString() });
|
|
133
|
+
fs.writeFileSync(archivePath, JSON.stringify(archive, null, 2));
|
|
134
|
+
} catch (err) {
|
|
135
|
+
node.error("Failed to archive yearly total: " + err);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
130
138
|
}
|
|
131
139
|
|
|
132
140
|
RED.nodes.registerType("energyMeterPlus", EnergyMeterPlus, {
|
|
133
141
|
color: "#f3a108fe"
|
|
134
142
|
});
|
|
135
|
-
}
|
|
143
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-energymeterplus",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "A custom Node-RED node that integrates power readings into energy totals with persistence and cost calculation.",
|
|
5
5
|
"author": "Arcfrankye",
|
|
6
6
|
"license": "MIT",
|