node-red-contrib-energymeterplus 0.2.4 → 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 +9 -1
- package/energyMeterPlus.js +17 -39
- 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,8 +27,7 @@ module.exports = function(RED) {
|
|
|
27
27
|
yearly: Number(config.baselineYearly) || 0
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
if (config.baselineDaily || config.baselineWeekly || config.baselineMonthly || config.baselineYearly) {
|
|
30
|
+
if (storedBaselines.daily || storedBaselines.weekly || storedBaselines.monthly || storedBaselines.yearly) {
|
|
32
31
|
baseline.daily += storedBaselines.daily;
|
|
33
32
|
baseline.weekly += storedBaselines.weekly;
|
|
34
33
|
baseline.monthly += storedBaselines.monthly;
|
|
@@ -38,10 +37,10 @@ module.exports = function(RED) {
|
|
|
38
37
|
node.context().set("storedBaselines", storedBaselines);
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
//
|
|
40
|
+
// Last timestamp
|
|
42
41
|
let lastCheck = node.context().get("lastCheck") || new Date();
|
|
43
42
|
|
|
44
|
-
//
|
|
43
|
+
// Helpers
|
|
45
44
|
function currencySymbol(code) {
|
|
46
45
|
switch (code) {
|
|
47
46
|
case "USD": return "$";
|
|
@@ -50,14 +49,11 @@ module.exports = function(RED) {
|
|
|
50
49
|
default: return code;
|
|
51
50
|
}
|
|
52
51
|
}
|
|
53
|
-
|
|
54
|
-
// Safe rounding helper
|
|
55
52
|
function round2(val) {
|
|
56
53
|
if (val === null || val === undefined || isNaN(val)) return 0;
|
|
57
54
|
return Number(val.toFixed(2));
|
|
58
55
|
}
|
|
59
56
|
|
|
60
|
-
// Archive yearly totals
|
|
61
57
|
function archiveYearly(total, year) {
|
|
62
58
|
try {
|
|
63
59
|
const archivePath = path.join(path.dirname(filePath), "yearly_archive.json");
|
|
@@ -65,11 +61,7 @@ module.exports = function(RED) {
|
|
|
65
61
|
if (fs.existsSync(archivePath)) {
|
|
66
62
|
archive = JSON.parse(fs.readFileSync(archivePath));
|
|
67
63
|
}
|
|
68
|
-
archive.push({
|
|
69
|
-
year: year,
|
|
70
|
-
total_kWh: total,
|
|
71
|
-
timestamp: new Date().toISOString()
|
|
72
|
-
});
|
|
64
|
+
archive.push({ year, total_kWh: total, timestamp: new Date().toISOString() });
|
|
73
65
|
fs.writeFileSync(archivePath, JSON.stringify(archive, null, 2));
|
|
74
66
|
} catch (err) {
|
|
75
67
|
node.error("Failed to archive yearly total: " + err);
|
|
@@ -79,19 +71,14 @@ module.exports = function(RED) {
|
|
|
79
71
|
// Rollover logic
|
|
80
72
|
function checkRollover() {
|
|
81
73
|
let now = new Date();
|
|
74
|
+
let stored = node.context().get("storedBaselines") || { daily:0, weekly:0, monthly:0, yearly:0 };
|
|
82
75
|
|
|
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
|
-
}
|
|
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;
|
|
92
79
|
if (now.getFullYear() !== lastCheck.getFullYear()) {
|
|
93
80
|
archiveYearly(baseline.yearly, lastCheck.getFullYear());
|
|
94
|
-
baseline.yearly =
|
|
81
|
+
baseline.yearly = stored.yearly;
|
|
95
82
|
}
|
|
96
83
|
|
|
97
84
|
lastCheck = now;
|
|
@@ -100,27 +87,25 @@ module.exports = function(RED) {
|
|
|
100
87
|
}
|
|
101
88
|
setInterval(checkRollover, 60000);
|
|
102
89
|
|
|
90
|
+
// Input handler
|
|
103
91
|
node.on('input', function(msg) {
|
|
104
92
|
let now = new Date();
|
|
105
|
-
|
|
106
|
-
// Calculate duration BEFORE updating lastCheck
|
|
107
93
|
let durationHours = (now - lastCheck) / (1000 * 3600);
|
|
108
94
|
|
|
109
95
|
let power = Number(msg.payload);
|
|
110
|
-
if (!isNaN(power)) {
|
|
96
|
+
if (!isNaN(power) && durationHours > 0) {
|
|
111
97
|
let power_kW = (inputUnit === "W") ? power / 1000 : power;
|
|
112
98
|
let energyIncrement = power_kW * durationHours;
|
|
113
99
|
|
|
114
|
-
// Apply increment
|
|
115
100
|
baseline.daily += energyIncrement;
|
|
116
101
|
baseline.weekly += energyIncrement;
|
|
117
102
|
baseline.monthly += energyIncrement;
|
|
118
103
|
baseline.yearly += energyIncrement;
|
|
119
104
|
}
|
|
120
105
|
|
|
121
|
-
// Update lastCheck AFTER increment
|
|
122
106
|
lastCheck = now;
|
|
123
107
|
node.context().set("lastCheck", lastCheck);
|
|
108
|
+
node.context().set("baseline", baseline);
|
|
124
109
|
|
|
125
110
|
msg.payload = {
|
|
126
111
|
daily_kWh: round2(baseline.daily),
|
|
@@ -134,17 +119,10 @@ module.exports = function(RED) {
|
|
|
134
119
|
currency: currencySymbol(currencyCode)
|
|
135
120
|
};
|
|
136
121
|
|
|
137
|
-
// Ensure directory exists
|
|
138
122
|
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
|
-
}
|
|
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); }
|
|
148
126
|
|
|
149
127
|
node.send(msg);
|
|
150
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",
|