node-red-contrib-energymeterplus 0.3.1 → 0.3.3

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 +78 -70
  2. package/package.json +1 -1
@@ -11,79 +11,29 @@ 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
20
- };
21
-
22
- // Live increments
23
- let accumulated = node.context().get("accumulated") || {
24
- daily:0, weekly:0, monthly:0, yearly:0
25
- };
26
-
27
- let lastCheck = node.context().get("lastCheck") || new Date();
28
-
29
- function currencySymbol(code) {
30
- switch (code) {
31
- case "USD": return "$";
32
- case "EUR": return "€";
33
- case "NGN": return "₦";
34
- default: return code;
35
- }
36
- }
37
- function round2(val) {
38
- if (val === null || val === undefined || isNaN(val)) return 0;
39
- return Number(val.toFixed(2));
40
- }
41
-
42
- function archiveYearly(total, year) {
43
- try {
44
- const archivePath = path.join(path.dirname(filePath), "yearly_archive.json");
45
- let archive = [];
46
- if (fs.existsSync(archivePath)) {
47
- archive = JSON.parse(fs.readFileSync(archivePath));
48
- }
49
- archive.push({ year, total, timestamp: new Date().toISOString() });
50
- fs.writeFileSync(archivePath, JSON.stringify(archive, null, 2));
51
- } catch (err) {
52
- node.error("Failed to archive yearly total: " + err);
53
- }
14
+ // Baseline initialization + merge editor config
15
+ let baseline = node.context().get("baseline") || { daily:0, weekly:0, monthly:0, yearly:0 };
16
+ baseline.daily += Number(config.baselineDaily) || 0;
17
+ baseline.weekly += Number(config.baselineWeekly) || 0;
18
+ baseline.monthly += Number(config.baselineMonthly) || 0;
19
+ baseline.yearly += Number(config.baselineYearly) || 0;
20
+ node.context().set("baseline", baseline);
21
+
22
+ // Accumulated values
23
+ let accumulated = node.context().get("accumulated") || { daily:0, weekly:0, monthly:0, yearly:0 };
24
+
25
+ // Last check timestamp
26
+ let lastCheck = node.context().get("lastCheck");
27
+ if (!(lastCheck instanceof Date)) {
28
+ lastCheck = new Date(lastCheck || Date.now());
54
29
  }
55
30
 
56
- // Rollover logic
57
- function checkRollover() {
58
- let now = new Date();
59
-
60
- if (now.getDate() !== lastCheck.getDate()) {
61
- accumulated.daily = 0; baseline.daily = 0;
62
- }
63
- if (now.getDay() === 1 && lastCheck.getDay() !== 1) {
64
- accumulated.weekly = 0; baseline.weekly = 0;
65
- }
66
- if (now.getMonth() !== lastCheck.getMonth()) {
67
- accumulated.monthly = 0; baseline.monthly = 0;
68
- }
69
- if (now.getFullYear() !== lastCheck.getFullYear()) {
70
- archiveYearly(baseline.yearly + accumulated.yearly, lastCheck.getFullYear());
71
- accumulated.yearly = 0; baseline.yearly = 0;
72
- }
73
-
74
- lastCheck = now;
75
- node.context().set("accumulated", accumulated);
76
- node.context().set("baseline", baseline);
77
- node.context().set("lastCheck", lastCheck);
78
- }
79
- setInterval(checkRollover, 60000);
80
-
81
- // Input handler
82
- node.on('input', function(msg) {
31
+ // 1. Input handler: baseline updates + power increments
32
+ node.on('input', function(msg) {
83
33
  let now = new Date();
84
34
  let durationHours = (now - lastCheck) / (1000 * 3600);
85
35
 
86
- // Handle baseline updates
36
+ // Handle baseline updates via message
87
37
  if (msg.topic === "applyBaseline" && msg.payload) {
88
38
  baseline.daily += Number(msg.payload.daily) || 0;
89
39
  baseline.weekly += Number(msg.payload.weekly) || 0;
@@ -92,7 +42,7 @@ module.exports = function(RED) {
92
42
  node.context().set("baseline", baseline);
93
43
  }
94
44
 
95
- // Handle power input
45
+ // 2. Handle power input
96
46
  let power = Number(msg.payload);
97
47
  if (!isNaN(power) && durationHours > 0) {
98
48
  let power_kW = (inputUnit === "W") ? power / 1000 : power;
@@ -108,6 +58,7 @@ module.exports = function(RED) {
108
58
  node.context().set("lastCheck", lastCheck);
109
59
  node.context().set("accumulated", accumulated);
110
60
 
61
+ // 3. Output payload
111
62
  msg.payload = {
112
63
  energyDaily: round2(baseline.daily + accumulated.daily),
113
64
  energyWeekly: round2(baseline.weekly + accumulated.weekly),
@@ -120,6 +71,7 @@ module.exports = function(RED) {
120
71
  currency: currencySymbol(currencyCode)
121
72
  };
122
73
 
74
+ // 4. Persist to file
123
75
  const dir = path.dirname(filePath);
124
76
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
125
77
  try { fs.writeFileSync(filePath, JSON.stringify(msg.payload, null, 2)); }
@@ -127,9 +79,65 @@ module.exports = function(RED) {
127
79
 
128
80
  node.send(msg);
129
81
  });
82
+
83
+ // 5. Rollover logic
84
+ function checkRollover() {
85
+ let now = new Date();
86
+
87
+ if (!(lastCheck instanceof Date)) {
88
+ lastCheck = new Date(lastCheck || Date.now());
89
+ }
90
+
91
+ if (now.getDate() !== lastCheck.getDate()) {
92
+ accumulated.daily = 0; baseline.daily = 0;
93
+ }
94
+ if (now.getDay() === 1 && lastCheck.getDay() !== 1) {
95
+ accumulated.weekly = 0; baseline.weekly = 0;
96
+ }
97
+ if (now.getMonth() !== lastCheck.getMonth()) {
98
+ accumulated.monthly = 0; baseline.monthly = 0;
99
+ }
100
+ if (now.getFullYear() !== lastCheck.getFullYear()) {
101
+ archiveYearly(baseline.yearly + accumulated.yearly, lastCheck.getFullYear());
102
+ accumulated.yearly = 0; baseline.yearly = 0;
103
+ }
104
+
105
+ lastCheck = now;
106
+ node.context().set("lastCheck", lastCheck);
107
+ node.context().set("accumulated", accumulated);
108
+ node.context().set("baseline", baseline);
109
+ }
110
+ setInterval(checkRollover, 60000);
111
+
112
+ // 6. Helpers
113
+ function currencySymbol(code) {
114
+ switch (code) {
115
+ case "USD": return "$";
116
+ case "EUR": return "€";
117
+ case "NGN": return "₦";
118
+ default: return code;
119
+ }
120
+ }
121
+ function round2(val) {
122
+ if (val === null || val === undefined || isNaN(val)) return 0;
123
+ return Number(val.toFixed(2));
124
+ }
125
+ function archiveYearly(total, year) {
126
+ try {
127
+ const archivePath = path.join(path.dirname(filePath), "yearly_archive.json");
128
+ let archive = [];
129
+ if (fs.existsSync(archivePath)) {
130
+ archive = JSON.parse(fs.readFileSync(archivePath));
131
+ }
132
+ archive.push({ year, total, timestamp: new Date().toISOString() });
133
+ fs.writeFileSync(archivePath, JSON.stringify(archive, null, 2));
134
+ } catch (err) {
135
+ node.error("Failed to archive yearly total: " + err);
136
+ }
137
+ }
130
138
  }
131
139
 
132
140
  RED.nodes.registerType("energyMeterPlus", EnergyMeterPlus, {
133
141
  color: "#f3a108fe"
134
142
  });
135
- }
143
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-energymeterplus",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
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",