node-red-contrib-energymeterplus 0.2.3 → 0.2.5
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 +30 -2
- package/energyMeterPlus.js +36 -83
- 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
|
|
|
@@ -168,7 +168,11 @@ Costs scale automatically with your configured unit cost.
|
|
|
168
168
|
|
|
169
169
|
|
|
170
170
|
|
|
171
|
-
##### **Updates
|
|
171
|
+
##### **Updates:**
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
###### **V2.3:**
|
|
172
176
|
|
|
173
177
|
|
|
174
178
|
|
|
@@ -186,3 +190,27 @@ Baselines now applied once and stored in context.
|
|
|
186
190
|
|
|
187
191
|
updated counters to continuous absorption, no transfer at rollover
|
|
188
192
|
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
###### v2.4
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
Fixed a bug where the internal counters were not updating incrementally.
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
Added payload validation
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
Fixed Baselines bug: Baselines captured once, stored in context, and survive after UI clears
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
v2.5
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
Fixed internal counter bug
|
|
216
|
+
|
package/energyMeterPlus.js
CHANGED
|
@@ -11,13 +11,7 @@ module.exports = function(RED) {
|
|
|
11
11
|
let inputUnit = config.inputUnit || "W";
|
|
12
12
|
let currencyCode = config.currency || "USD";
|
|
13
13
|
|
|
14
|
-
//
|
|
15
|
-
let baselineDaily = Number(config.baselineDaily) || 0;
|
|
16
|
-
let baselineWeekly = Number(config.baselineWeekly) || 0;
|
|
17
|
-
let baselineMonthly = Number(config.baselineMonthly) || 0;
|
|
18
|
-
let baselineYearly = Number(config.baselineYearly) || 0;
|
|
19
|
-
|
|
20
|
-
// Load persisted baseline or initialize
|
|
14
|
+
// Counters always start from context or zero
|
|
21
15
|
let baseline = node.context().get("baseline") || {
|
|
22
16
|
daily: 0,
|
|
23
17
|
weekly: 0,
|
|
@@ -25,43 +19,28 @@ module.exports = function(RED) {
|
|
|
25
19
|
yearly: 0
|
|
26
20
|
};
|
|
27
21
|
|
|
28
|
-
//
|
|
22
|
+
// Capture baselines once on deploy
|
|
29
23
|
let storedBaselines = node.context().get("storedBaselines") || {
|
|
30
|
-
daily: baselineDaily,
|
|
31
|
-
weekly: baselineWeekly,
|
|
32
|
-
monthly: baselineMonthly,
|
|
33
|
-
yearly: baselineYearly
|
|
24
|
+
daily: Number(config.baselineDaily) || 0,
|
|
25
|
+
weekly: Number(config.baselineWeekly) || 0,
|
|
26
|
+
monthly: Number(config.baselineMonthly) || 0,
|
|
27
|
+
yearly: Number(config.baselineYearly) || 0
|
|
34
28
|
};
|
|
35
29
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
baseline.
|
|
40
|
-
baseline.
|
|
41
|
-
baseline.monthly += baselineMonthly;
|
|
42
|
-
baseline.yearly += baselineYearly;
|
|
43
|
-
|
|
44
|
-
storedBaselines = {
|
|
45
|
-
daily: baselineDaily,
|
|
46
|
-
weekly: baselineWeekly,
|
|
47
|
-
monthly: baselineMonthly,
|
|
48
|
-
yearly: baselineYearly
|
|
49
|
-
};
|
|
30
|
+
if (storedBaselines.daily || storedBaselines.weekly || storedBaselines.monthly || storedBaselines.yearly) {
|
|
31
|
+
baseline.daily += storedBaselines.daily;
|
|
32
|
+
baseline.weekly += storedBaselines.weekly;
|
|
33
|
+
baseline.monthly += storedBaselines.monthly;
|
|
34
|
+
baseline.yearly += storedBaselines.yearly;
|
|
50
35
|
|
|
51
|
-
node.context().set("
|
|
36
|
+
node.context().set("baseline", baseline);
|
|
52
37
|
node.context().set("storedBaselines", storedBaselines);
|
|
53
|
-
|
|
54
|
-
// Cosmetic blanking: clear config fields so they show empty in UI
|
|
55
|
-
config.baselineDaily = "";
|
|
56
|
-
config.baselineWeekly = "";
|
|
57
|
-
config.baselineMonthly = "";
|
|
58
|
-
config.baselineYearly = "";
|
|
59
38
|
}
|
|
60
39
|
|
|
61
|
-
//
|
|
40
|
+
// Last timestamp
|
|
62
41
|
let lastCheck = node.context().get("lastCheck") || new Date();
|
|
63
42
|
|
|
64
|
-
//
|
|
43
|
+
// Helpers
|
|
65
44
|
function currencySymbol(code) {
|
|
66
45
|
switch (code) {
|
|
67
46
|
case "USD": return "$";
|
|
@@ -70,14 +49,11 @@ module.exports = function(RED) {
|
|
|
70
49
|
default: return code;
|
|
71
50
|
}
|
|
72
51
|
}
|
|
73
|
-
|
|
74
|
-
// Safe rounding helper
|
|
75
52
|
function round2(val) {
|
|
76
53
|
if (val === null || val === undefined || isNaN(val)) return 0;
|
|
77
54
|
return Number(val.toFixed(2));
|
|
78
55
|
}
|
|
79
56
|
|
|
80
|
-
// Archive yearly totals
|
|
81
57
|
function archiveYearly(total, year) {
|
|
82
58
|
try {
|
|
83
59
|
const archivePath = path.join(path.dirname(filePath), "yearly_archive.json");
|
|
@@ -85,11 +61,7 @@ module.exports = function(RED) {
|
|
|
85
61
|
if (fs.existsSync(archivePath)) {
|
|
86
62
|
archive = JSON.parse(fs.readFileSync(archivePath));
|
|
87
63
|
}
|
|
88
|
-
archive.push({
|
|
89
|
-
year: year,
|
|
90
|
-
total_kWh: total,
|
|
91
|
-
timestamp: new Date().toISOString()
|
|
92
|
-
});
|
|
64
|
+
archive.push({ year, total_kWh: total, timestamp: new Date().toISOString() });
|
|
93
65
|
fs.writeFileSync(archivePath, JSON.stringify(archive, null, 2));
|
|
94
66
|
} catch (err) {
|
|
95
67
|
node.error("Failed to archive yearly total: " + err);
|
|
@@ -99,26 +71,14 @@ module.exports = function(RED) {
|
|
|
99
71
|
// Rollover logic
|
|
100
72
|
function checkRollover() {
|
|
101
73
|
let now = new Date();
|
|
74
|
+
let stored = node.context().get("storedBaselines") || { daily:0, weekly:0, monthly:0, yearly:0 };
|
|
102
75
|
|
|
103
|
-
|
|
104
|
-
if (now.
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Weekly rollover (Sunday → Monday)
|
|
109
|
-
if (now.getDay() === 1 && lastCheck.getDay() !== 1) {
|
|
110
|
-
baseline.weekly = storedBaselines.weekly;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Monthly rollover (1st of month)
|
|
114
|
-
if (now.getMonth() !== lastCheck.getMonth()) {
|
|
115
|
-
baseline.monthly = storedBaselines.monthly;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Yearly rollover (Jan 1)
|
|
76
|
+
if (now.getDate() !== lastCheck.getDate()) baseline.daily = stored.daily;
|
|
77
|
+
if (now.getDay() === 1 && lastCheck.getDay() !== 1) baseline.weekly = stored.weekly;
|
|
78
|
+
if (now.getMonth() !== lastCheck.getMonth()) baseline.monthly = stored.monthly;
|
|
119
79
|
if (now.getFullYear() !== lastCheck.getFullYear()) {
|
|
120
80
|
archiveYearly(baseline.yearly, lastCheck.getFullYear());
|
|
121
|
-
baseline.yearly =
|
|
81
|
+
baseline.yearly = stored.yearly;
|
|
122
82
|
}
|
|
123
83
|
|
|
124
84
|
lastCheck = now;
|
|
@@ -127,24 +87,25 @@ module.exports = function(RED) {
|
|
|
127
87
|
}
|
|
128
88
|
setInterval(checkRollover, 60000);
|
|
129
89
|
|
|
90
|
+
// Input handler
|
|
130
91
|
node.on('input', function(msg) {
|
|
131
92
|
let now = new Date();
|
|
132
93
|
let durationHours = (now - lastCheck) / (1000 * 3600);
|
|
133
|
-
lastCheck = now;
|
|
134
|
-
node.context().set("lastCheck", lastCheck);
|
|
135
|
-
|
|
136
|
-
let power = Number(msg.payload) || 0;
|
|
137
|
-
let power_kW = (inputUnit === "W") ? power / 1000 : power;
|
|
138
94
|
|
|
139
|
-
let
|
|
95
|
+
let power = Number(msg.payload);
|
|
96
|
+
if (!isNaN(power) && durationHours > 0) {
|
|
97
|
+
let power_kW = (inputUnit === "W") ? power / 1000 : power;
|
|
98
|
+
let energyIncrement = power_kW * durationHours;
|
|
140
99
|
|
|
141
|
-
|
|
142
|
-
|
|
100
|
+
baseline.daily += energyIncrement;
|
|
101
|
+
baseline.weekly += energyIncrement;
|
|
102
|
+
baseline.monthly += energyIncrement;
|
|
103
|
+
baseline.yearly += energyIncrement;
|
|
104
|
+
}
|
|
143
105
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
baseline
|
|
147
|
-
baseline.yearly += energyIncrement;
|
|
106
|
+
lastCheck = now;
|
|
107
|
+
node.context().set("lastCheck", lastCheck);
|
|
108
|
+
node.context().set("baseline", baseline);
|
|
148
109
|
|
|
149
110
|
msg.payload = {
|
|
150
111
|
daily_kWh: round2(baseline.daily),
|
|
@@ -158,18 +119,10 @@ module.exports = function(RED) {
|
|
|
158
119
|
currency: currencySymbol(currencyCode)
|
|
159
120
|
};
|
|
160
121
|
|
|
161
|
-
// Ensure directory exists
|
|
162
122
|
const dir = path.dirname(filePath);
|
|
163
|
-
if (!fs.existsSync(dir)) {
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Write snapshot safely
|
|
168
|
-
try {
|
|
169
|
-
fs.writeFileSync(filePath, JSON.stringify(msg.payload, null, 2));
|
|
170
|
-
} catch (err) {
|
|
171
|
-
node.error("Failed to write to file: " + err);
|
|
172
|
-
}
|
|
123
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
124
|
+
try { fs.writeFileSync(filePath, JSON.stringify(msg.payload, null, 2)); }
|
|
125
|
+
catch (err) { node.error("Failed to write to file: " + err); }
|
|
173
126
|
|
|
174
127
|
node.send(msg);
|
|
175
128
|
});
|
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.5",
|
|
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",
|