node-red-contrib-energymeterplus 0.3.2 → 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 +75 -76
- package/package.json +1 -1
package/energyMeterPlus.js
CHANGED
|
@@ -11,88 +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
|
-
// Rehydrate lastCheck from context
|
|
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
|
|
28
26
|
let lastCheck = node.context().get("lastCheck");
|
|
29
27
|
if (!(lastCheck instanceof Date)) {
|
|
30
28
|
lastCheck = new Date(lastCheck || Date.now());
|
|
31
29
|
}
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
case "USD": return "$";
|
|
36
|
-
case "EUR": return "€";
|
|
37
|
-
case "NGN": return "₦";
|
|
38
|
-
default: return code;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function round2(val) {
|
|
42
|
-
if (val === null || val === undefined || isNaN(val)) return 0;
|
|
43
|
-
return Number(val.toFixed(2));
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function archiveYearly(total, year) {
|
|
47
|
-
try {
|
|
48
|
-
const archivePath = path.join(path.dirname(filePath), "yearly_archive.json");
|
|
49
|
-
let archive = [];
|
|
50
|
-
if (fs.existsSync(archivePath)) {
|
|
51
|
-
archive = JSON.parse(fs.readFileSync(archivePath));
|
|
52
|
-
}
|
|
53
|
-
archive.push({ year, total, timestamp: new Date().toISOString() });
|
|
54
|
-
fs.writeFileSync(archivePath, JSON.stringify(archive, null, 2));
|
|
55
|
-
} catch (err) {
|
|
56
|
-
node.error("Failed to archive yearly total: " + err);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Rollover logic
|
|
61
|
-
function checkRollover() {
|
|
62
|
-
let now = new Date();
|
|
63
|
-
|
|
64
|
-
// Ensure lastCheck is a Date
|
|
65
|
-
if (!(lastCheck instanceof Date)) {
|
|
66
|
-
lastCheck = new Date(lastCheck || Date.now());
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (now.getDate() !== lastCheck.getDate()) {
|
|
70
|
-
accumulated.daily = 0; baseline.daily = 0;
|
|
71
|
-
}
|
|
72
|
-
if (now.getDay() === 1 && lastCheck.getDay() !== 1) {
|
|
73
|
-
accumulated.weekly = 0; baseline.weekly = 0;
|
|
74
|
-
}
|
|
75
|
-
if (now.getMonth() !== lastCheck.getMonth()) {
|
|
76
|
-
accumulated.monthly = 0; baseline.monthly = 0;
|
|
77
|
-
}
|
|
78
|
-
if (now.getFullYear() !== lastCheck.getFullYear()) {
|
|
79
|
-
archiveYearly(baseline.yearly + accumulated.yearly, lastCheck.getFullYear());
|
|
80
|
-
accumulated.yearly = 0; baseline.yearly = 0;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
lastCheck = now;
|
|
84
|
-
node.context().set("lastCheck", lastCheck);
|
|
85
|
-
node.context().set("accumulated", accumulated);
|
|
86
|
-
node.context().set("baseline", baseline);
|
|
87
|
-
}
|
|
88
|
-
setInterval(checkRollover, 60000);
|
|
89
|
-
|
|
90
|
-
// Input handler
|
|
91
|
-
node.on('input', function(msg) {
|
|
31
|
+
// 1. Input handler: baseline updates + power increments
|
|
32
|
+
node.on('input', function(msg) {
|
|
92
33
|
let now = new Date();
|
|
93
34
|
let durationHours = (now - lastCheck) / (1000 * 3600);
|
|
94
35
|
|
|
95
|
-
// Handle baseline updates
|
|
36
|
+
// Handle baseline updates via message
|
|
96
37
|
if (msg.topic === "applyBaseline" && msg.payload) {
|
|
97
38
|
baseline.daily += Number(msg.payload.daily) || 0;
|
|
98
39
|
baseline.weekly += Number(msg.payload.weekly) || 0;
|
|
@@ -101,7 +42,7 @@ module.exports = function(RED) {
|
|
|
101
42
|
node.context().set("baseline", baseline);
|
|
102
43
|
}
|
|
103
44
|
|
|
104
|
-
|
|
45
|
+
// 2. Handle power input
|
|
105
46
|
let power = Number(msg.payload);
|
|
106
47
|
if (!isNaN(power) && durationHours > 0) {
|
|
107
48
|
let power_kW = (inputUnit === "W") ? power / 1000 : power;
|
|
@@ -117,6 +58,7 @@ module.exports = function(RED) {
|
|
|
117
58
|
node.context().set("lastCheck", lastCheck);
|
|
118
59
|
node.context().set("accumulated", accumulated);
|
|
119
60
|
|
|
61
|
+
// 3. Output payload
|
|
120
62
|
msg.payload = {
|
|
121
63
|
energyDaily: round2(baseline.daily + accumulated.daily),
|
|
122
64
|
energyWeekly: round2(baseline.weekly + accumulated.weekly),
|
|
@@ -129,6 +71,7 @@ module.exports = function(RED) {
|
|
|
129
71
|
currency: currencySymbol(currencyCode)
|
|
130
72
|
};
|
|
131
73
|
|
|
74
|
+
// 4. Persist to file
|
|
132
75
|
const dir = path.dirname(filePath);
|
|
133
76
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
134
77
|
try { fs.writeFileSync(filePath, JSON.stringify(msg.payload, null, 2)); }
|
|
@@ -136,9 +79,65 @@ module.exports = function(RED) {
|
|
|
136
79
|
|
|
137
80
|
node.send(msg);
|
|
138
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
|
+
}
|
|
139
138
|
}
|
|
140
139
|
|
|
141
140
|
RED.nodes.registerType("energyMeterPlus", EnergyMeterPlus, {
|
|
142
141
|
color: "#f3a108fe"
|
|
143
142
|
});
|
|
144
|
-
}
|
|
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",
|