node-red-contrib-energymeterplus 0.0.9 → 0.1.2

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 ADDED
@@ -0,0 +1,166 @@
1
+ #### **EnergyMeterPlus Node Version 0.1.2**
2
+
3
+
4
+
5
+ ##### **Overview:**
6
+
7
+
8
+
9
+ EnergyMeterPlus is a custom Node‑RED function node designed to convert instantaneous power readings (Watts) into accumulated energy values (kWh) over time. It integrates power based on elapsed time between samples, applies baseline corrections, calculates costs, and persists totals across restarts.
10
+
11
+
12
+
13
+
14
+
15
+ ##### **Features:**
16
+
17
+
18
+
19
+ * Converts power (W) into energy (kWh) using time integration.
20
+
21
+
22
+
23
+ * Tracks daily, weekly, monthly, and yearly energy totals.
24
+
25
+
26
+
27
+ * Calculates costs based on a configurable unit cost.
28
+
29
+
30
+
31
+ * Unit cost configurable in 3 different currencies
32
+
33
+
34
+
35
+ * Supports baseline corrections to start counters from existing values.
36
+
37
+
38
+
39
+ * Handles rollover at day, week, month, and year boundaries.
40
+
41
+
42
+
43
+ * Persists counters and timestamps in Node‑RED context and writes snapshots to file.
44
+
45
+
46
+
47
+ * Outputs clean values rounded to two decimal places for dashboards.
48
+
49
+
50
+
51
+
52
+
53
+ ###### **Configuration:**
54
+
55
+
56
+
57
+ Unit Cost: Cost per kWh (e.g., 0.15 for $0.15/kWh).
58
+
59
+
60
+
61
+ Currency: Choose USD, EUR, or NGN (fixed values, not convertible)
62
+
63
+
64
+
65
+ File Path: Path to store snapshots (default: /config/node\_red/solargen\_data.json).
66
+
67
+
68
+
69
+ Input Unit: "W" for Watts (default) or "kW" if your source already provides kilowatts.
70
+
71
+
72
+
73
+ Baselines: Initial values for daily, weekly, monthly, and yearly counters. Can also be used to correct baseline values or left blank if not needed.
74
+
75
+
76
+
77
+
78
+
79
+ ###### **Output:**
80
+
81
+
82
+
83
+ The node outputs a payload object:
84
+
85
+
86
+
87
+ json
88
+
89
+ {
90
+
91
+   "daily\_kWh": 4.53,
92
+
93
+   "weekly\_kWh": 32.18,
94
+
95
+   "monthly\_kWh": 128.74,
96
+
97
+   "yearly\_kWh": 1024.56,
98
+
99
+   "daily\_cost": 0.68,
100
+
101
+   "weekly\_cost": 4.83,
102
+
103
+   "monthly\_cost": 19.31,
104
+
105
+   "yearly\_cost": 153.68
106
+
107
+ }
108
+
109
+ All values are rounded to two decimal places.
110
+
111
+
112
+
113
+
114
+
115
+ ###### **Rollover Logic:**
116
+
117
+
118
+
119
+ * Daily resets at midnight.
120
+ *
121
+ * Weekly resets on Sunday.
122
+ *
123
+ * Monthly resets on the first of the month.
124
+ *
125
+ * Yearly resets on January 1st and archives the previous year’s totals.
126
+
127
+
128
+
129
+
130
+
131
+ ###### **Persistence:**
132
+
133
+
134
+
135
+ Counters and timestamps are stored in Node‑RED context.
136
+
137
+
138
+
139
+ Snapshots are written to the configured JSON file.
140
+
141
+
142
+
143
+ Directories are auto‑created if missing, ensuring no ENOENT errors.
144
+
145
+
146
+
147
+
148
+
149
+ ##### **Example:**
150
+
151
+
152
+
153
+ If your solar system outputs \~4526 W continuously:
154
+
155
+
156
+
157
+ After 1 hour → \~4.53 kWh added to daily total.
158
+
159
+
160
+
161
+ After 24 hours → \~108.6 kWh added to daily total.
162
+
163
+
164
+
165
+ Costs scale automatically with your configured unit cost.
166
+
@@ -1,7 +1,7 @@
1
1
  <script type="text/javascript">
2
2
  RED.nodes.registerType('energyMeterPlus', {
3
3
  category: 'energy',
4
- color: '#d9910cfe', <!-- Dutch Orange -->
4
+ color: '#f3a108fe', <!-- Dutch Orange -->
5
5
  defaults: {
6
6
  name: {value:""},
7
7
  baselineDaily: {value:0},
@@ -9,7 +9,8 @@
9
9
  baselineMonthly: {value:0},
10
10
  baselineYearly: {value:0},
11
11
  unitCost: {value:0.15},
12
- filePath: {value:"/addon_configs/a0d7b954_nodered/node_red/solarGen_data.json"},
12
+ currency: {value:"USD"}, // NEW field
13
+ filePath: {value:"/config/node_red/solargen_data.json"},
13
14
  inputUnit: {value:"kW"}
14
15
  },
15
16
  inputs:1,
@@ -46,6 +47,14 @@
46
47
  <label for="node-input-unitCost">Cost per Unit</label>
47
48
  <input type="number" id="node-input-unitCost" step="0.01">
48
49
  </div>
50
+ <div class="form-row">
51
+ <label for="node-input-currency"><i class="fa fa-money"></i> Currency</label>
52
+ <select id="node-input-currency">
53
+ <option value="USD">USD ($)</option>
54
+ <option value="EUR">Euro (€)</option>
55
+ <option value="NGN">Naira (₦)</option>
56
+ </select>
57
+ </div>
49
58
  <div class="form-row">
50
59
  <label for="node-input-filePath">Filepath</label>
51
60
  <input type="text" id="node-input-filePath">
@@ -1,4 +1,5 @@
1
1
  const fs = require("fs");
2
+ const path = require("path");
2
3
 
3
4
  module.exports = function(RED) {
4
5
  function EnergyMeterPlus(config) {
@@ -6,18 +7,36 @@ module.exports = function(RED) {
6
7
  var node = this;
7
8
 
8
9
  let unitCost = Number(config.unitCost) || 0;
9
- let filePath = config.filePath || "/addon_configs/a0d7b954_nodered/node_red/solarGen_data.json";
10
- let inputUnit = config.inputUnit || "W"; // default to Watts since your stream is in W
10
+ let filePath = config.filePath || "/config/node_red/solargen_data.json";
11
+ let inputUnit = config.inputUnit || "W";
12
+ let currencyCode = config.currency || "USD";
11
13
 
12
- // Numeric baseline corrections
14
+ // Baseline corrections
13
15
  let baselineDaily = Number(config.baselineDaily) || 0;
14
16
  let baselineWeekly = Number(config.baselineWeekly) || 0;
15
17
  let baselineMonthly = Number(config.baselineMonthly) || 0;
16
18
  let baselineYearly = Number(config.baselineYearly) || 0;
17
19
 
18
- // Baseline counters
19
- let baseline = { daily: baselineDaily, weekly: baselineWeekly, monthly: baselineMonthly, yearly: baselineYearly };
20
- let lastCheck = new Date();
20
+ // Load persisted baseline or initialize
21
+ let baseline = node.context().get("baseline") || {
22
+ daily: baselineDaily,
23
+ weekly: baselineWeekly,
24
+ monthly: baselineMonthly,
25
+ yearly: baselineYearly
26
+ };
27
+
28
+ // Load last timestamp or initialize
29
+ let lastCheck = node.context().get("lastCheck") || new Date();
30
+
31
+ // Currency symbol helper
32
+ function currencySymbol(code) {
33
+ switch (code) {
34
+ case "USD": return "$";
35
+ case "EUR": return "€";
36
+ case "NGN": return "₦";
37
+ default: return code;
38
+ }
39
+ }
21
40
 
22
41
  // Rollover logic
23
42
  function checkRollover() {
@@ -25,60 +44,63 @@ module.exports = function(RED) {
25
44
  if (now.getDate() !== lastCheck.getDate()) baseline.daily = baselineDaily;
26
45
  if (now.getDay() === 0 && lastCheck.getDay() !== 0) baseline.weekly = baselineWeekly;
27
46
  if (now.getMonth() !== lastCheck.getMonth()) baseline.monthly = baselineMonthly;
28
- if (now.getFullYear() !== lastCheck.getFullYear()) {
29
- let archiveName = filePath.replace(".json", `_${lastCheck.getFullYear()}.json`);
30
- try { fs.writeFileSync(archiveName, JSON.stringify(baseline, null, 2)); }
31
- catch (err) { node.error("Failed to archive yearly file: " + err); }
32
- baseline.yearly = baselineYearly;
33
- }
47
+ if (now.getFullYear() !== lastCheck.getFullYear()) baseline.yearly = baselineYearly;
34
48
  lastCheck = now;
49
+ node.context().set("baseline", baseline);
50
+ node.context().set("lastCheck", lastCheck);
35
51
  }
36
52
  setInterval(checkRollover, 60000);
37
53
 
38
54
  node.on('input', function(msg) {
39
55
  let now = new Date();
40
- let durationHours = (now - lastCheck) / (1000 * 3600); // elapsed time in hours
56
+ let durationHours = (now - lastCheck) / (1000 * 3600);
41
57
  lastCheck = now;
58
+ node.context().set("lastCheck", lastCheck);
42
59
 
43
60
  let power = Number(msg.payload) || 0;
44
61
  let power_kW = (inputUnit === "W") ? power / 1000 : power;
45
62
 
46
- // Energy increment = Power × Time
47
63
  let energyIncrement = power_kW * durationHours;
48
64
 
49
- // Add to counters
50
65
  baseline.daily += energyIncrement;
51
66
  baseline.weekly += energyIncrement;
52
67
  baseline.monthly += energyIncrement;
53
68
  baseline.yearly += energyIncrement;
54
69
 
55
- // Calculate costs
56
- let daily_cost = baseline.daily * unitCost;
57
- let weekly_cost = baseline.weekly * unitCost;
58
- let monthly_cost = baseline.monthly * unitCost;
59
- let yearly_cost = baseline.yearly * unitCost;
70
+ node.context().set("baseline", baseline);
71
+
72
+ function round2(val) { return Number(val.toFixed(2)); }
60
73
 
61
- // Build Output
62
74
  msg.payload = {
63
- daily_kWh: baseline.daily,
64
- weekly_kWh: baseline.weekly,
65
- monthly_kWh: baseline.monthly,
66
- yearly_kWh: baseline.yearly,
67
- daily_cost,
68
- weekly_cost,
69
- monthly_cost,
70
- yearly_cost
75
+ daily_kWh: round2(baseline.daily),
76
+ weekly_kWh: round2(baseline.weekly),
77
+ monthly_kWh: round2(baseline.monthly),
78
+ yearly_kWh: round2(baseline.yearly),
79
+ daily_cost: round2(baseline.daily * unitCost),
80
+ weekly_cost: round2(baseline.weekly * unitCost),
81
+ monthly_cost: round2(baseline.monthly * unitCost),
82
+ yearly_cost: round2(baseline.yearly * unitCost),
83
+ currency: currencySymbol(currencyCode)
71
84
  };
72
85
 
73
- // Save snapshot
74
- try { fs.writeFileSync(filePath, JSON.stringify(msg.payload, null, 2)); }
75
- catch (err) { node.error("Failed to write to file: " + err); }
86
+ // Ensure directory exists
87
+ const dir = path.dirname(filePath);
88
+ if (!fs.existsSync(dir)) {
89
+ fs.mkdirSync(dir, { recursive: true });
90
+ }
91
+
92
+ // Write snapshot safely
93
+ try {
94
+ fs.writeFileSync(filePath, JSON.stringify(msg.payload, null, 2));
95
+ } catch (err) {
96
+ node.error("Failed to write to file: " + err);
97
+ }
76
98
 
77
99
  node.send(msg);
78
100
  });
79
101
  }
80
102
 
81
103
  RED.nodes.registerType("energyMeterPlus", EnergyMeterPlus, {
82
- color: "#228B22" // Forest Green
104
+ color: "#f3a108fe"
83
105
  });
84
106
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "node-red-contrib-energymeterplus",
3
- "version": "0.0.9",
4
- "description": "A node-red energy meter node with editable baseline data, milestone summary, and cost calculation",
3
+ "version": "0.1.2",
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",
7
7
  "keywords": [
@@ -13,3 +13,4 @@
13
13
  }
14
14
  }
15
15
  }
16
+