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 CHANGED
@@ -1,4 +1,4 @@
1
- #### **EnergyMeterPlus Node Version 0.2.3**
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 2.3:**
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
+
@@ -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
- // Baseline corrections (numeric values from config)
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
- // Store numeric baselines separately so resets don’t pull from blanked config
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
- // Apply baselines if new values entered
37
- let baselineApplied = node.context().get("baselineApplied") || false;
38
- if (!baselineApplied || baselineDaily || baselineWeekly || baselineMonthly || baselineYearly) {
39
- baseline.daily += baselineDaily;
40
- baseline.weekly += baselineWeekly;
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("baselineApplied", true);
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
- // Load last timestamp or initialize
40
+ // Last timestamp
62
41
  let lastCheck = node.context().get("lastCheck") || new Date();
63
42
 
64
- // Currency symbol helper
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
- // Daily rollover (midnight)
104
- if (now.getDate() !== lastCheck.getDate()) {
105
- baseline.daily = storedBaselines.daily;
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 = storedBaselines.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 energyIncrement = power_kW * durationHours;
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
- // Add increment to daily
142
- baseline.daily += energyIncrement;
100
+ baseline.daily += energyIncrement;
101
+ baseline.weekly += energyIncrement;
102
+ baseline.monthly += energyIncrement;
103
+ baseline.yearly += energyIncrement;
104
+ }
143
105
 
144
- // Continuous absorption
145
- baseline.weekly += energyIncrement;
146
- baseline.monthly += energyIncrement;
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
- fs.mkdirSync(dir, { recursive: true });
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",
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",