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 +12 -4
- package/energyMeterPlus.html +52 -26
- package/energyMeterPlus.js +35 -66
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#### **EnergyMeterPlus Node Version 0.
|
|
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
|
-
###### **
|
|
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
|
+
|
package/energyMeterPlus.html
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
|
|
1
2
|
<script type="text/javascript">
|
|
2
3
|
RED.nodes.registerType('energyMeterPlus', {
|
|
3
4
|
category: 'energy',
|
|
4
|
-
color: '#f3a108fe',
|
|
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},
|
|
13
|
+
currency: {value:"USD", required:true},
|
|
13
14
|
filePath: {value:"/config/node_red/solargen_data.json", required:false},
|
|
14
|
-
inputUnit: {value:"
|
|
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
|
-
|
|
24
|
+
// Nothing special here yet
|
|
24
25
|
},
|
|
25
26
|
oneditsave: function() {
|
|
26
|
-
//
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
38
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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>
|
package/energyMeterPlus.js
CHANGED
|
@@ -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
|
-
//
|
|
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
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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,
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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("
|
|
109
|
+
node.context().set("accumulated", accumulated);
|
|
141
110
|
|
|
142
111
|
msg.payload = {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
daily_cost:
|
|
148
|
-
weekly_cost:
|
|
149
|
-
monthly_cost: round2(baseline.monthly * unitCost),
|
|
150
|
-
yearly_cost:
|
|
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.
|
|
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",
|