node-red-contrib-energymeterplus 0.2.4 → 0.2.6
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/README.md +9 -1
- package/energyMeterPlus.js +23 -46
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#### **EnergyMeterPlus Node Version 0.2.
|
|
1
|
+
#### **EnergyMeterPlus Node Version 0.2.5**
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
@@ -206,3 +206,11 @@ Added payload validation
|
|
|
206
206
|
|
|
207
207
|
Fixed Baselines bug: Baselines captured once, stored in context, and survive after UI clears
|
|
208
208
|
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
v2.5
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
Fixed internal counter bug
|
|
216
|
+
|
package/energyMeterPlus.js
CHANGED
|
@@ -11,7 +11,7 @@ module.exports = function(RED) {
|
|
|
11
11
|
let inputUnit = config.inputUnit || "W";
|
|
12
12
|
let currencyCode = config.currency || "USD";
|
|
13
13
|
|
|
14
|
-
//
|
|
14
|
+
// Counters always start from context or zero
|
|
15
15
|
let baseline = node.context().get("baseline") || {
|
|
16
16
|
daily: 0,
|
|
17
17
|
weekly: 0,
|
|
@@ -19,7 +19,7 @@ module.exports = function(RED) {
|
|
|
19
19
|
yearly: 0
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
//
|
|
22
|
+
// Capture baselines once on deploy
|
|
23
23
|
let storedBaselines = node.context().get("storedBaselines") || {
|
|
24
24
|
daily: Number(config.baselineDaily) || 0,
|
|
25
25
|
weekly: Number(config.baselineWeekly) || 0,
|
|
@@ -27,21 +27,19 @@ module.exports = function(RED) {
|
|
|
27
27
|
yearly: Number(config.baselineYearly) || 0
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
// Apply
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
baseline.yearly += storedBaselines.yearly;
|
|
30
|
+
// Apply each baseline only to its own counter
|
|
31
|
+
if (storedBaselines.daily) baseline.daily += storedBaselines.daily;
|
|
32
|
+
if (storedBaselines.weekly) baseline.weekly += storedBaselines.weekly;
|
|
33
|
+
if (storedBaselines.monthly) baseline.monthly += storedBaselines.monthly;
|
|
34
|
+
if (storedBaselines.yearly) baseline.yearly += storedBaselines.yearly;
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
36
|
+
node.context().set("baseline", baseline);
|
|
37
|
+
node.context().set("storedBaselines", storedBaselines);
|
|
40
38
|
|
|
41
|
-
//
|
|
39
|
+
// Last timestamp
|
|
42
40
|
let lastCheck = node.context().get("lastCheck") || new Date();
|
|
43
41
|
|
|
44
|
-
//
|
|
42
|
+
// Helpers
|
|
45
43
|
function currencySymbol(code) {
|
|
46
44
|
switch (code) {
|
|
47
45
|
case "USD": return "$";
|
|
@@ -50,14 +48,11 @@ module.exports = function(RED) {
|
|
|
50
48
|
default: return code;
|
|
51
49
|
}
|
|
52
50
|
}
|
|
53
|
-
|
|
54
|
-
// Safe rounding helper
|
|
55
51
|
function round2(val) {
|
|
56
52
|
if (val === null || val === undefined || isNaN(val)) return 0;
|
|
57
53
|
return Number(val.toFixed(2));
|
|
58
54
|
}
|
|
59
55
|
|
|
60
|
-
// Archive yearly totals
|
|
61
56
|
function archiveYearly(total, year) {
|
|
62
57
|
try {
|
|
63
58
|
const archivePath = path.join(path.dirname(filePath), "yearly_archive.json");
|
|
@@ -65,11 +60,7 @@ module.exports = function(RED) {
|
|
|
65
60
|
if (fs.existsSync(archivePath)) {
|
|
66
61
|
archive = JSON.parse(fs.readFileSync(archivePath));
|
|
67
62
|
}
|
|
68
|
-
archive.push({
|
|
69
|
-
year: year,
|
|
70
|
-
total_kWh: total,
|
|
71
|
-
timestamp: new Date().toISOString()
|
|
72
|
-
});
|
|
63
|
+
archive.push({ year, total_kWh: total, timestamp: new Date().toISOString() });
|
|
73
64
|
fs.writeFileSync(archivePath, JSON.stringify(archive, null, 2));
|
|
74
65
|
} catch (err) {
|
|
75
66
|
node.error("Failed to archive yearly total: " + err);
|
|
@@ -79,19 +70,14 @@ module.exports = function(RED) {
|
|
|
79
70
|
// Rollover logic
|
|
80
71
|
function checkRollover() {
|
|
81
72
|
let now = new Date();
|
|
73
|
+
let stored = node.context().get("storedBaselines") || { daily:0, weekly:0, monthly:0, yearly:0 };
|
|
82
74
|
|
|
83
|
-
if (now.getDate() !== lastCheck.getDate())
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (now.getDay() === 1 && lastCheck.getDay() !== 1) {
|
|
87
|
-
baseline.weekly = storedBaselines.weekly;
|
|
88
|
-
}
|
|
89
|
-
if (now.getMonth() !== lastCheck.getMonth()) {
|
|
90
|
-
baseline.monthly = storedBaselines.monthly;
|
|
91
|
-
}
|
|
75
|
+
if (now.getDate() !== lastCheck.getDate()) baseline.daily = stored.daily;
|
|
76
|
+
if (now.getDay() === 1 && lastCheck.getDay() !== 1) baseline.weekly = stored.weekly;
|
|
77
|
+
if (now.getMonth() !== lastCheck.getMonth()) baseline.monthly = stored.monthly;
|
|
92
78
|
if (now.getFullYear() !== lastCheck.getFullYear()) {
|
|
93
79
|
archiveYearly(baseline.yearly, lastCheck.getFullYear());
|
|
94
|
-
baseline.yearly =
|
|
80
|
+
baseline.yearly = stored.yearly;
|
|
95
81
|
}
|
|
96
82
|
|
|
97
83
|
lastCheck = now;
|
|
@@ -100,27 +86,25 @@ module.exports = function(RED) {
|
|
|
100
86
|
}
|
|
101
87
|
setInterval(checkRollover, 60000);
|
|
102
88
|
|
|
89
|
+
// Input handler
|
|
103
90
|
node.on('input', function(msg) {
|
|
104
91
|
let now = new Date();
|
|
105
|
-
|
|
106
|
-
// Calculate duration BEFORE updating lastCheck
|
|
107
92
|
let durationHours = (now - lastCheck) / (1000 * 3600);
|
|
108
93
|
|
|
109
94
|
let power = Number(msg.payload);
|
|
110
|
-
if (!isNaN(power)) {
|
|
95
|
+
if (!isNaN(power) && durationHours > 0) {
|
|
111
96
|
let power_kW = (inputUnit === "W") ? power / 1000 : power;
|
|
112
97
|
let energyIncrement = power_kW * durationHours;
|
|
113
98
|
|
|
114
|
-
// Apply increment
|
|
115
99
|
baseline.daily += energyIncrement;
|
|
116
100
|
baseline.weekly += energyIncrement;
|
|
117
101
|
baseline.monthly += energyIncrement;
|
|
118
102
|
baseline.yearly += energyIncrement;
|
|
119
103
|
}
|
|
120
104
|
|
|
121
|
-
// Update lastCheck AFTER increment
|
|
122
105
|
lastCheck = now;
|
|
123
106
|
node.context().set("lastCheck", lastCheck);
|
|
107
|
+
node.context().set("baseline", baseline);
|
|
124
108
|
|
|
125
109
|
msg.payload = {
|
|
126
110
|
daily_kWh: round2(baseline.daily),
|
|
@@ -134,17 +118,10 @@ module.exports = function(RED) {
|
|
|
134
118
|
currency: currencySymbol(currencyCode)
|
|
135
119
|
};
|
|
136
120
|
|
|
137
|
-
// Ensure directory exists
|
|
138
121
|
const dir = path.dirname(filePath);
|
|
139
|
-
if (!fs.existsSync(dir)) {
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
try {
|
|
144
|
-
fs.writeFileSync(filePath, JSON.stringify(msg.payload, null, 2));
|
|
145
|
-
} catch (err) {
|
|
146
|
-
node.error("Failed to write to file: " + err);
|
|
147
|
-
}
|
|
122
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
123
|
+
try { fs.writeFileSync(filePath, JSON.stringify(msg.payload, null, 2)); }
|
|
124
|
+
catch (err) { node.error("Failed to write to file: " + err); }
|
|
148
125
|
|
|
149
126
|
node.send(msg);
|
|
150
127
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-energymeterplus",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
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",
|