node-red-contrib-energymeterplus 0.2.8 → 0.3.1

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.5**
1
+ #### **EnergyMeterPlus Node Version 0.3.0**
2
2
 
3
3
 
4
4
 
@@ -172,7 +172,7 @@ Costs scale automatically with your configured unit cost.
172
172
 
173
173
 
174
174
 
175
- ###### **V2.3:**
175
+ ###### **v2.3:**
176
176
 
177
177
 
178
178
 
@@ -192,7 +192,7 @@ updated counters to continuous absorption, no transfer at rollover
192
192
 
193
193
 
194
194
 
195
- ###### v2.4
195
+ ###### **v2.4**
196
196
 
197
197
 
198
198
 
@@ -208,9 +208,17 @@ Fixed Baselines bug: Baselines captured once, stored in context, and survive aft
208
208
 
209
209
 
210
210
 
211
- v2.5
211
+ ###### **v2.5**
212
212
 
213
213
 
214
214
 
215
215
  Fixed internal counter bug
216
216
 
217
+
218
+
219
+ ###### **v3.0**
220
+
221
+
222
+
223
+ **Major overhaul of UI and internal logic programming to fix bugs in counters and baseline corrections**
224
+
@@ -1,7 +1,8 @@
1
+
1
2
  <script type="text/javascript">
2
3
  RED.nodes.registerType('energyMeterPlus', {
3
4
  category: 'energy',
4
- color: '#f3a108fe', <!-- Dutch Orange -->
5
+ color: '#f3a108fe', //-- Dutch Orange --
5
6
  defaults: {
6
7
  name: {value:""},
7
8
  baselineDaily: {value:0, required:false},
@@ -9,9 +10,9 @@
9
10
  baselineMonthly: {value:0, required:false},
10
11
  baselineYearly: {value:0, required:false},
11
12
  unitCost: {value:0.15, required:true},
12
- currency: {value:"USD", required:true}, // NEW field
13
+ currency: {value:"USD", required:true},
13
14
  filePath: {value:"/config/node_red/solargen_data.json", required:false},
14
- inputUnit: {value:"kW", required:true}
15
+ inputUnit: {value:"W", required:true}
15
16
  },
16
17
  inputs:1,
17
18
  outputs:1,
@@ -20,22 +21,36 @@
20
21
  return this.name || "energyMeterPlus";
21
22
  },
22
23
  oneditprepare: function() {
23
- // Nothing special here yet
24
+ // Nothing special here yet
24
25
  },
25
26
  oneditsave: function() {
26
- // When user clicks Done, send a one-time baseline message
27
+ // Collect baseline values
28
+ var daily = Number($("#node-input-baselineDaily").val()) || 0;
29
+ var weekly = Number($("#node-input-baselineWeekly").val()) || 0;
30
+ var monthly = Number($("#node-input-baselineMonthly").val()) || 0;
31
+ var yearly = Number($("#node-input-baselineYearly").val()) || 0;
32
+
27
33
  var baselineMsg = {
28
- topic: "applyBaseline",
29
- nodeId: this.id, // include the nodeId!
30
- payload: {
31
- daily: Number($("#node-input-baselineDaily").val()) || 0,
32
- weekly: Number($("#node-input-baselineWeekly").val()) || 0,
33
- monthly: Number($("#node-input-baselineMonthly").val()) || 0,
34
- yearly: Number($("#node-input-baselineYearly").val()) || 0
35
- }
34
+ nodeId: this.id,
35
+ payload: { daily, weekly, monthly, yearly }
36
36
  };
37
- // Push message into runtime instantly
38
- RED.comms.send("energyMeterPlus/applyBaseline", baselineMsg);
37
+
38
+ // Feedback logic
39
+ if (daily === 0 && weekly === 0 && monthly === 0 && yearly === 0) {
40
+ RED.notify("⚠️ Baseline values are all zero — nothing applied", {type:"warning", timeout:3000});
41
+ } else if (isNaN(daily) || isNaN(weekly) || isNaN(monthly) || isNaN(yearly)) {
42
+ RED.notify("❌ Baseline values invalid — please enter numbers", {type:"error", timeout:4000});
43
+ } else {
44
+ RED.comms.send("energyMeterPlus/applyBaseline", baselineMsg);
45
+ RED.notify("✅ Baseline offset applied successfully", {type:"success", timeout:2000});
46
+ console.log("Baseline offset applied:", baselineMsg.payload);
47
+ }
48
+
49
+ // Clear fields after save
50
+ $("#node-input-baselineDaily").val("");
51
+ $("#node-input-baselineWeekly").val("");
52
+ $("#node-input-baselineMonthly").val("");
53
+ $("#node-input-baselineYearly").val("");
39
54
  }
40
55
  });
41
56
  </script>
@@ -45,24 +60,28 @@
45
60
  <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
46
61
  <input type="text" id="node-input-name">
47
62
  </div>
63
+
64
+ <!-- Baseline offsets -->
48
65
  <div class="form-row">
49
- <label for="node-input-baselineDaily">Baseline Daily (kWh)</label>
50
- <input type="number" id="node-input-baselineDaily">
66
+ <label for="node-input-baselineDaily"><i class="fa fa-calendar-day"></i> Baseline Daily Offset</label>
67
+ <input type="number" id="node-input-baselineDaily" placeholder="Daily offset">
51
68
  </div>
52
69
  <div class="form-row">
53
- <label for="node-input-baselineWeekly">Baseline Weekly (kWh)</label>
54
- <input type="number" id="node-input-baselineWeekly">
70
+ <label for="node-input-baselineWeekly"><i class="fa fa-calendar-week"></i> Baseline Weekly Offset</label>
71
+ <input type="number" id="node-input-baselineWeekly" placeholder="Weekly offset">
55
72
  </div>
56
73
  <div class="form-row">
57
- <label for="node-input-baselineMonthly">Baseline Monthly (kWh)</label>
58
- <input type="number" id="node-input-baselineMonthly">
74
+ <label for="node-input-baselineMonthly"><i class="fa fa-calendar-alt"></i> Baseline Monthly Offset</label>
75
+ <input type="number" id="node-input-baselineMonthly" placeholder="Monthly offset">
59
76
  </div>
60
77
  <div class="form-row">
61
- <label for="node-input-baselineYearly">Baseline Yearly (kWh)</label>
62
- <input type="number" id="node-input-baselineYearly">
78
+ <label for="node-input-baselineYearly"><i class="fa fa-calendar"></i> Baseline Yearly Offset</label>
79
+ <input type="number" id="node-input-baselineYearly" placeholder="Yearly offset">
63
80
  </div>
81
+
82
+ <!-- Other settings -->
64
83
  <div class="form-row">
65
- <label for="node-input-unitCost">Cost per Unit</label>
84
+ <label for="node-input-unitCost"><i class="fa fa-money"></i> Cost per Unit</label>
66
85
  <input type="number" id="node-input-unitCost" step="0.01">
67
86
  </div>
68
87
  <div class="form-row">
@@ -74,14 +93,21 @@
74
93
  </select>
75
94
  </div>
76
95
  <div class="form-row">
77
- <label for="node-input-filePath">Filepath</label>
96
+ <label for="node-input-filePath"><i class="fa fa-file"></i> Filepath</label>
78
97
  <input type="text" id="node-input-filePath">
79
98
  </div>
80
99
  <div class="form-row">
81
- <label for="node-input-inputUnit">Input Unit</label>
100
+ <label for="node-input-inputUnit"><i class="fa fa-bolt"></i> Input Unit</label>
82
101
  <select id="node-input-inputUnit">
83
102
  <option value="kW">kW</option>
84
103
  <option value="W">W</option>
85
104
  </select>
86
105
  </div>
87
106
  </script>
107
+
108
+ <script type="text/x-red" data-help-name="energyMeterPlus">
109
+ <p><b>Energy Meter Plus</b> tracks energy consumption and cost.</p>
110
+ <p>Outputs <code>energyDaily</code>, <code>energyWeekly</code>, <code>energyMonthly</code>, and <code>energyYearly</code> values, plus cost calculations.</p>
111
+ <p>You can set baseline offsets (daily, weekly, monthly, yearly) to adjust counters. These offsets are additive and applied on top of live increments.</p>
112
+ <p>Rollover logic resets counters at midnight (daily), Sunday midnight (weekly), first day of month (monthly), and Dec 31 midnight (yearly, with archive).</p>
113
+ </script>
@@ -2,25 +2,6 @@ const fs = require("fs");
2
2
  const path = require("path");
3
3
 
4
4
  module.exports = function(RED) {
5
-
6
- // Listener for baseline messages from the editor
7
- RED.comms.subscribe("energyMeterPlus/applyBaseline", function(baselineMsg) {
8
- // baselineMsg should include nodeId so we know which instance to update
9
- const nodeInstance = RED.nodes.getNode(baselineMsg.nodeId);
10
- if (nodeInstance) {
11
- let baseline = nodeInstance.context().get("baseline") || {
12
- daily:0, weekly:0, monthly:0, yearly:0
13
- };
14
-
15
- baseline.daily = baselineMsg.payload.daily;
16
- baseline.weekly = baselineMsg.payload.weekly;
17
- baseline.monthly = baselineMsg.payload.monthly;
18
- baseline.yearly = baselineMsg.payload.yearly;
19
-
20
- nodeInstance.context().set("baseline", baseline);
21
- }
22
- });
23
-
24
5
  function EnergyMeterPlus(config) {
25
6
  RED.nodes.createNode(this, config);
26
7
  var node = this;
@@ -30,36 +11,21 @@ module.exports = function(RED) {
30
11
  let inputUnit = config.inputUnit || "W";
31
12
  let currencyCode = config.currency || "USD";
32
13
 
33
- // Counters start from context or zero
14
+ // Baseline offsets
34
15
  let baseline = node.context().get("baseline") || {
35
- daily: 0,
36
- weekly: 0,
37
- monthly: 0,
38
- yearly: 0
39
- };
40
-
41
- // Capture baselines once on deploy
42
- let storedBaselines = {
43
16
  daily: Number(config.baselineDaily) || 0,
44
17
  weekly: Number(config.baselineWeekly) || 0,
45
18
  monthly: Number(config.baselineMonthly) || 0,
46
19
  yearly: Number(config.baselineYearly) || 0
47
20
  };
48
21
 
49
- // Apply baselines once
50
- baseline.daily += storedBaselines.daily;
51
- baseline.weekly += storedBaselines.weekly;
52
- baseline.monthly += storedBaselines.monthly;
53
- baseline.yearly += storedBaselines.yearly;
54
-
55
- // Discard baselines from UI after applying
56
- node.context().set("baseline", baseline);
57
- node.context().set("storedBaselines", { daily:0, weekly:0, monthly:0, yearly:0 });
22
+ // Live increments
23
+ let accumulated = node.context().get("accumulated") || {
24
+ daily:0, weekly:0, monthly:0, yearly:0
25
+ };
58
26
 
59
- // Last timestamp
60
27
  let lastCheck = node.context().get("lastCheck") || new Date();
61
28
 
62
- // Helpers
63
29
  function currencySymbol(code) {
64
30
  switch (code) {
65
31
  case "USD": return "$";
@@ -80,7 +46,7 @@ module.exports = function(RED) {
80
46
  if (fs.existsSync(archivePath)) {
81
47
  archive = JSON.parse(fs.readFileSync(archivePath));
82
48
  }
83
- archive.push({ year, total_kWh: total, timestamp: new Date().toISOString() });
49
+ archive.push({ year, total, timestamp: new Date().toISOString() });
84
50
  fs.writeFileSync(archivePath, JSON.stringify(archive, null, 2));
85
51
  } catch (err) {
86
52
  node.error("Failed to archive yearly total: " + err);
@@ -91,28 +57,22 @@ module.exports = function(RED) {
91
57
  function checkRollover() {
92
58
  let now = new Date();
93
59
 
94
- // Daily rollover
95
60
  if (now.getDate() !== lastCheck.getDate()) {
96
- baseline.daily = 0;
61
+ accumulated.daily = 0; baseline.daily = 0;
97
62
  }
98
-
99
- // Weekly rollover (Sunday midnight → Monday start)
100
63
  if (now.getDay() === 1 && lastCheck.getDay() !== 1) {
101
- baseline.weekly = 0;
64
+ accumulated.weekly = 0; baseline.weekly = 0;
102
65
  }
103
-
104
- // Monthly rollover
105
66
  if (now.getMonth() !== lastCheck.getMonth()) {
106
- baseline.monthly = 0;
67
+ accumulated.monthly = 0; baseline.monthly = 0;
107
68
  }
108
-
109
- // Yearly rollover
110
69
  if (now.getFullYear() !== lastCheck.getFullYear()) {
111
- archiveYearly(baseline.yearly, lastCheck.getFullYear());
112
- baseline.yearly = 0;
70
+ archiveYearly(baseline.yearly + accumulated.yearly, lastCheck.getFullYear());
71
+ accumulated.yearly = 0; baseline.yearly = 0;
113
72
  }
114
73
 
115
74
  lastCheck = now;
75
+ node.context().set("accumulated", accumulated);
116
76
  node.context().set("baseline", baseline);
117
77
  node.context().set("lastCheck", lastCheck);
118
78
  }
@@ -123,31 +83,40 @@ module.exports = function(RED) {
123
83
  let now = new Date();
124
84
  let durationHours = (now - lastCheck) / (1000 * 3600);
125
85
 
86
+ // Handle baseline updates
87
+ if (msg.topic === "applyBaseline" && msg.payload) {
88
+ baseline.daily += Number(msg.payload.daily) || 0;
89
+ baseline.weekly += Number(msg.payload.weekly) || 0;
90
+ baseline.monthly += Number(msg.payload.monthly) || 0;
91
+ baseline.yearly += Number(msg.payload.yearly) || 0;
92
+ node.context().set("baseline", baseline);
93
+ }
94
+
95
+ // Handle power input
126
96
  let power = Number(msg.payload);
127
97
  if (!isNaN(power) && durationHours > 0) {
128
98
  let power_kW = (inputUnit === "W") ? power / 1000 : power;
129
99
  let energyIncrement = power_kW * durationHours;
130
100
 
131
- // Apply increment separately to each counter
132
- baseline.daily += energyIncrement;
133
- baseline.weekly += energyIncrement;
134
- baseline.monthly += energyIncrement;
135
- baseline.yearly += energyIncrement;
101
+ accumulated.daily += energyIncrement;
102
+ accumulated.weekly += energyIncrement;
103
+ accumulated.monthly += energyIncrement;
104
+ accumulated.yearly += energyIncrement;
136
105
  }
137
106
 
138
107
  lastCheck = now;
139
108
  node.context().set("lastCheck", lastCheck);
140
- node.context().set("baseline", baseline);
109
+ node.context().set("accumulated", accumulated);
141
110
 
142
111
  msg.payload = {
143
- daily_kWh: round2(baseline.daily),
144
- weekly_kWh: round2(baseline.weekly),
145
- monthly_kWh: round2(baseline.monthly),
146
- yearly_kWh: round2(baseline.yearly),
147
- daily_cost: round2(baseline.daily * unitCost),
148
- weekly_cost: round2(baseline.weekly * unitCost),
149
- monthly_cost: round2(baseline.monthly * unitCost),
150
- yearly_cost: round2(baseline.yearly * unitCost),
112
+ energyDaily: round2(baseline.daily + accumulated.daily),
113
+ energyWeekly: round2(baseline.weekly + accumulated.weekly),
114
+ energyMonthly: round2(baseline.monthly + accumulated.monthly),
115
+ energyYearly: round2(baseline.yearly + accumulated.yearly),
116
+ daily_cost: round2((baseline.daily + accumulated.daily) * unitCost),
117
+ weekly_cost: round2((baseline.weekly + accumulated.weekly) * unitCost),
118
+ monthly_cost: round2((baseline.monthly + accumulated.monthly) * unitCost),
119
+ yearly_cost: round2((baseline.yearly + accumulated.yearly) * unitCost),
151
120
  currency: currencySymbol(currencyCode)
152
121
  };
153
122
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-energymeterplus",
3
- "version": "0.2.8",
3
+ "version": "0.3.1",
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",