node-red-contrib-energymeterplus 0.3.2 → 0.3.4

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.
Files changed (2) hide show
  1. package/energyMeterPlus.js +96 -69
  2. package/package.json +1 -1
@@ -11,88 +11,57 @@ module.exports = function(RED) {
11
11
  let inputUnit = config.inputUnit || "W";
12
12
  let currencyCode = config.currency || "USD";
13
13
 
14
- // Baseline offsets
15
- let baseline = node.context().get("baseline") || {
16
- daily: Number(config.baselineDaily) || 0,
17
- weekly: Number(config.baselineWeekly) || 0,
18
- monthly: Number(config.baselineMonthly) || 0,
19
- yearly: Number(config.baselineYearly) || 0
14
+ // --- Editor-config delta apply (run on every deploy)
15
+ function toNum(v) { const n = Number(v); return isNaN(n) ? 0 : n; }
16
+
17
+ // read stored "last applied editor values" (so we can compute deltas)
18
+ let lastApplied = node.context().get("lastAppliedEditor") || { daily:0, weekly:0, monthly:0, yearly:0 };
19
+
20
+ // current editor values (always read from config)
21
+ let editor = {
22
+ daily: toNum(config.baselineDaily),
23
+ weekly: toNum(config.baselineWeekly),
24
+ monthly: toNum(config.baselineMonthly),
25
+ yearly: toNum(config.baselineYearly)
20
26
  };
21
27
 
22
- // Live increments
23
- let accumulated = node.context().get("accumulated") || {
24
- daily:0, weekly:0, monthly:0, yearly:0
28
+ // compute delta = editor - lastApplied
29
+ let delta = {
30
+ daily: editor.daily - (toNum(lastApplied.daily) || 0),
31
+ weekly: editor.weekly - (toNum(lastApplied.weekly) || 0),
32
+ monthly: editor.monthly - (toNum(lastApplied.monthly) || 0),
33
+ yearly: editor.yearly - (toNum(lastApplied.yearly) || 0)
25
34
  };
26
35
 
27
- // Rehydrate lastCheck from context
28
- let lastCheck = node.context().get("lastCheck");
29
- if (!(lastCheck instanceof Date)) {
30
- lastCheck = new Date(lastCheck || Date.now());
31
- }
32
-
33
- function currencySymbol(code) {
34
- switch (code) {
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
- }
36
+ // ensure baseline exists
37
+ let baseline = node.context().get("baseline") || { daily:0, weekly:0, monthly:0, yearly:0 };
59
38
 
60
- // Rollover logic
61
- function checkRollover() {
62
- let now = new Date();
39
+ // apply delta (this adds positive or negative changes)
40
+ baseline.daily += delta.daily;
41
+ baseline.weekly += delta.weekly;
42
+ baseline.monthly += delta.monthly;
43
+ baseline.yearly += delta.yearly;
63
44
 
64
- // Ensure lastCheck is a Date
65
- if (!(lastCheck instanceof Date)) {
66
- lastCheck = new Date(lastCheck || Date.now());
67
- }
45
+ // persist baseline and remember the editor values we just applied
46
+ node.context().set("baseline", baseline);
47
+ node.context().set("lastAppliedEditor", editor);
48
+ // -------------------------------------------------------------------------------
68
49
 
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
- }
50
+ // Accumulated values
51
+ let accumulated = node.context().get("accumulated") || { daily:0, weekly:0, monthly:0, yearly:0 };
82
52
 
83
- lastCheck = now;
84
- node.context().set("lastCheck", lastCheck);
85
- node.context().set("accumulated", accumulated);
86
- node.context().set("baseline", baseline);
53
+ // Last check timestamp
54
+ let lastCheck = node.context().get("lastCheck");
55
+ if (!(lastCheck instanceof Date)) {
56
+ lastCheck = new Date(lastCheck || Date.now());
87
57
  }
88
- setInterval(checkRollover, 60000);
89
58
 
90
- // Input handler
59
+ // 1. Input handler: baseline updates + power increments
91
60
  node.on('input', function(msg) {
92
61
  let now = new Date();
93
62
  let durationHours = (now - lastCheck) / (1000 * 3600);
94
63
 
95
- // Handle baseline updates
64
+ // Handle baseline updates via message (runtime applyBaseline)
96
65
  if (msg.topic === "applyBaseline" && msg.payload) {
97
66
  baseline.daily += Number(msg.payload.daily) || 0;
98
67
  baseline.weekly += Number(msg.payload.weekly) || 0;
@@ -117,6 +86,7 @@ module.exports = function(RED) {
117
86
  node.context().set("lastCheck", lastCheck);
118
87
  node.context().set("accumulated", accumulated);
119
88
 
89
+ // 3. Output payload
120
90
  msg.payload = {
121
91
  energyDaily: round2(baseline.daily + accumulated.daily),
122
92
  energyWeekly: round2(baseline.weekly + accumulated.weekly),
@@ -129,6 +99,7 @@ module.exports = function(RED) {
129
99
  currency: currencySymbol(currencyCode)
130
100
  };
131
101
 
102
+ // 4. Persist to file
132
103
  const dir = path.dirname(filePath);
133
104
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
134
105
  try { fs.writeFileSync(filePath, JSON.stringify(msg.payload, null, 2)); }
@@ -136,9 +107,65 @@ module.exports = function(RED) {
136
107
 
137
108
  node.send(msg);
138
109
  });
110
+
111
+ // 5. Rollover logic
112
+ function checkRollover() {
113
+ let now = new Date();
114
+
115
+ if (!(lastCheck instanceof Date)) {
116
+ lastCheck = new Date(lastCheck || Date.now());
117
+ }
118
+
119
+ if (now.getDate() !== lastCheck.getDate()) {
120
+ accumulated.daily = 0; baseline.daily = 0;
121
+ }
122
+ if (now.getDay() === 1 && lastCheck.getDay() !== 1) {
123
+ accumulated.weekly = 0; baseline.weekly = 0;
124
+ }
125
+ if (now.getMonth() !== lastCheck.getMonth()) {
126
+ accumulated.monthly = 0; baseline.monthly = 0;
127
+ }
128
+ if (now.getFullYear() !== lastCheck.getFullYear()) {
129
+ archiveYearly(baseline.yearly + accumulated.yearly, lastCheck.getFullYear());
130
+ accumulated.yearly = 0; baseline.yearly = 0;
131
+ }
132
+
133
+ lastCheck = now;
134
+ node.context().set("lastCheck", lastCheck);
135
+ node.context().set("accumulated", accumulated);
136
+ node.context().set("baseline", baseline);
137
+ }
138
+ setInterval(checkRollover, 60000);
139
+
140
+ // 6. Helpers
141
+ function currencySymbol(code) {
142
+ switch (code) {
143
+ case "USD": return "$";
144
+ case "EUR": return "€";
145
+ case "NGN": return "₦";
146
+ default: return code;
147
+ }
148
+ }
149
+ function round2(val) {
150
+ if (val === null || val === undefined || isNaN(val)) return 0;
151
+ return Number(val.toFixed(2));
152
+ }
153
+ function archiveYearly(total, year) {
154
+ try {
155
+ const archivePath = path.join(path.dirname(filePath), "yearly_archive.json");
156
+ let archive = [];
157
+ if (fs.existsSync(archivePath)) {
158
+ archive = JSON.parse(fs.readFileSync(archivePath));
159
+ }
160
+ archive.push({ year, total, timestamp: new Date().toISOString() });
161
+ fs.writeFileSync(archivePath, JSON.stringify(archive, null, 2));
162
+ } catch (err) {
163
+ node.error("Failed to archive yearly total: " + err);
164
+ }
165
+ }
139
166
  }
140
167
 
141
168
  RED.nodes.registerType("energyMeterPlus", EnergyMeterPlus, {
142
169
  color: "#f3a108fe"
143
170
  });
144
- }
171
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-energymeterplus",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
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",