node-red-contrib-eskomsepush 0.0.11 → 0.0.13
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 +34 -34
- package/img/eskomsepush-configuration.png +0 -0
- package/img/eskomsepush-flow.png +0 -0
- package/package.json +1 -1
- package/src/nodes/eskomsepush.html +46 -41
- package/src/nodes/eskomsepush.js +36 -18
package/README.md
CHANGED
|
@@ -53,6 +53,18 @@ Then you need to fill out which status to follow. This can be either _National_
|
|
|
53
53
|
|
|
54
54
|
If the _test_ checkbox has been selected, test data for the specified area will be fetched instead of the actual schedule. This is useful when debugging.
|
|
55
55
|
|
|
56
|
+
### Inputs
|
|
57
|
+
|
|
58
|
+
The input side is not needed in most cases. The node will output its status every ten minutes and won't update the information it gets from the API more often. The input node however can be used to overrule this behaviour. When inserting a timestamp, the node will output the latest information it got and re-calculate all fields.
|
|
59
|
+
|
|
60
|
+
There are also special strings that can be injected as payload to force updates:
|
|
61
|
+
|
|
62
|
+
- `allowance` - for retrieving the latest API count values
|
|
63
|
+
- `stage` - for retrieving the latest active load shedding stage
|
|
64
|
+
- `area` - for retrieving the latest schedule information
|
|
65
|
+
|
|
66
|
+
So usually there is no reason to connect anything to the input. It is only needed if you want to have more control over the node.
|
|
67
|
+
|
|
56
68
|
### Outputs
|
|
57
69
|
|
|
58
70
|
The note has two outputs. In most cases, the first (upper) output will be used.
|
|
@@ -61,46 +73,34 @@ The first output of the node outputs a boolean value and some related data. When
|
|
|
61
73
|
|
|
62
74
|
```
|
|
63
75
|
{
|
|
64
|
-
"payload":false,
|
|
65
|
-
"
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
"type":"schedule"
|
|
71
|
-
},
|
|
72
|
-
"active":false
|
|
73
|
-
},
|
|
74
|
-
"event":{
|
|
75
|
-
"next":{
|
|
76
|
-
"start":1683561600000,
|
|
77
|
-
"end":1683570600000
|
|
78
|
-
},
|
|
79
|
-
"active":false
|
|
80
|
-
},
|
|
81
|
-
"checked":"13:35",
|
|
82
|
-
"next":{
|
|
83
|
-
"start":1683561600000,
|
|
84
|
-
"end":1683570600000,
|
|
85
|
-
"type":"schedule"
|
|
86
|
-
},
|
|
87
|
-
"active":false
|
|
76
|
+
"payload": false,
|
|
77
|
+
"stage": "0",
|
|
78
|
+
"statusselect": "capetown",
|
|
79
|
+
"api": {
|
|
80
|
+
"count": 30,
|
|
81
|
+
"limit": 50
|
|
88
82
|
},
|
|
89
|
-
"
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
"
|
|
93
|
-
"
|
|
94
|
-
"
|
|
95
|
-
|
|
83
|
+
"calc": {
|
|
84
|
+
"sleeptime": "58",
|
|
85
|
+
"stage": "0",
|
|
86
|
+
"active": false,
|
|
87
|
+
"type": "event",
|
|
88
|
+
"next": {
|
|
89
|
+
"type": "schedule",
|
|
90
|
+
"start": 1686830400000,
|
|
91
|
+
"end": 1686839400000,
|
|
92
|
+
"duration": 9000,
|
|
93
|
+
"islong": false
|
|
94
|
+
},
|
|
95
|
+
"secondstostatechange": 84912
|
|
96
96
|
},
|
|
97
|
-
"
|
|
97
|
+
"_msgid": "9fa5d503c47f083e"
|
|
98
98
|
}
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
The
|
|
101
|
+
The _start_ and _end_ objects contain the time as unix timestamp in the Javascript format (milliseconds after the epoch), while the duration is in seconds. The `msg.calc.next.islong` value is boolean and will be _true_ if the shedding lasts 4 hours or longer.
|
|
102
102
|
|
|
103
|
-
The second output shows `msg.stage` and `msg.schedule`, containing the latest information as retrieved from the API. This output is mainly useful when writing your own functions and logic.
|
|
103
|
+
The second output shows `msg.stage` and `msg.schedule`, containing the latest information as retrieved from the API, with the lastUpdate field added. This output is mainly useful when debugging or writing your own functions and logic.
|
|
104
104
|
|
|
105
105
|
### Status
|
|
106
106
|
|
|
Binary file
|
package/img/eskomsepush-flow.png
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
test: { value: false },
|
|
40
40
|
verbose: {value: false}
|
|
41
41
|
},
|
|
42
|
-
inputs:
|
|
42
|
+
inputs:1,
|
|
43
43
|
outputs:2,
|
|
44
44
|
icon: "eskomsepush.svg",
|
|
45
45
|
label: function() {
|
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
<ul id="list-areas"></ul>
|
|
127
127
|
|
|
128
128
|
<p>
|
|
129
|
-
Queries
|
|
129
|
+
Queries used: <span id="api_info">unknown/unknown</span><br />
|
|
130
130
|
<input type="text" id="searchAreaText" placeholder="Area" disabled />
|
|
131
131
|
<button id="searchAreaId">Search</button><br />
|
|
132
132
|
</p>
|
|
@@ -159,60 +159,65 @@ see how many queries you have left.</p>
|
|
|
159
159
|
<p>Next you need to insert the correct area. Once a valid license is filled out, the API can be used for searching the correct area id. Do note that this will cost some of the daily queries. If you don't want that and you already know the id of the area, fill out the area id manually.</p>
|
|
160
160
|
<p>Then you need to fill out which status to follow. This can be either <em>National</em> (eskom) or <em>Capetown</em>.</p>
|
|
161
161
|
<p>If the <em>test</em> checkbox has been selected, test data for the area will be fetched instead of the actual schedule. This is useful when debugging.</p>
|
|
162
|
+
<p>The <em>verbose</em> checkbox will give some additional <tt>node.warn()</tt> messages, appearing in the debug tab. This is also useful when debugging.</p>
|
|
163
|
+
|
|
164
|
+
<h3 id=""inputs">Inputs</h3>
|
|
165
|
+
|
|
166
|
+
<p>
|
|
167
|
+
The input side is not needed in most cases. The node will output its status every ten minutes and won't update the information it gets from the API more
|
|
168
|
+
often. The input node however can be used to overrule this behaviour. When inserting a timestamp, the node will output the latest information it got and
|
|
169
|
+
re-calculate all fields.
|
|
170
|
+
</p>
|
|
171
|
+
|
|
172
|
+
<p>There are also special strings that can be injected as payload to force updates:
|
|
173
|
+
<ul>
|
|
174
|
+
<li><code>allowance</code> - for retrieving the latest API count values</li>
|
|
175
|
+
<li><code>stage</code> - for retrieving the latest active load shedding stage</li>
|
|
176
|
+
<li><code>area</code> - for retrieving the latest schedule information</li>
|
|
177
|
+
</ul>
|
|
178
|
+
</p>
|
|
179
|
+
|
|
180
|
+
<p>
|
|
181
|
+
So usually there is no reason to connect anything to the input. It is only needed if you want to have more control over the node.
|
|
182
|
+
</p>
|
|
162
183
|
|
|
163
184
|
<h3 id="outputs">Outputs</h3>
|
|
164
185
|
|
|
165
186
|
<p>The first output of the node outputs a boolean value. When load shedding is active, the <code>msg.payload</code> will be <em>true</em>, otherwise it will be <em>false</em>. It also outputs some extra values:</p>
|
|
166
187
|
|
|
167
188
|
<pre>
|
|
168
|
-
{
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
"
|
|
172
|
-
"
|
|
173
|
-
|
|
174
|
-
|
|
189
|
+
{
|
|
190
|
+
"payload": false,
|
|
191
|
+
"stage": "0",
|
|
192
|
+
"statusselect": "capetown",
|
|
193
|
+
"api": {
|
|
194
|
+
"count": 30,
|
|
195
|
+
"limit": 50
|
|
196
|
+
},
|
|
197
|
+
"calc": {
|
|
198
|
+
"sleeptime": "58",
|
|
199
|
+
"stage": "0",
|
|
200
|
+
"active": false,
|
|
201
|
+
"type": "event",
|
|
202
|
+
"next": {
|
|
175
203
|
"type": "schedule",
|
|
204
|
+
"start": 1686830400000,
|
|
205
|
+
"end": 1686839400000,
|
|
176
206
|
"duration": 9000,
|
|
177
207
|
"islong": false
|
|
208
|
+
},
|
|
209
|
+
"secondstostatechange": 84912
|
|
178
210
|
},
|
|
179
|
-
"
|
|
180
|
-
|
|
181
|
-
"event": {
|
|
182
|
-
"next": {
|
|
183
|
-
"start": 1684267200000,
|
|
184
|
-
"end": 1684276200000
|
|
185
|
-
},
|
|
186
|
-
"active": false
|
|
187
|
-
},
|
|
188
|
-
"checked": "15:38",
|
|
189
|
-
"next": {
|
|
190
|
-
"start": 1684267200000,
|
|
191
|
-
"end": 1684276200000,
|
|
192
|
-
"type": "schedule",
|
|
193
|
-
"duration": 9000,
|
|
194
|
-
"islong": false
|
|
195
|
-
},
|
|
196
|
-
"active": false
|
|
197
|
-
},
|
|
198
|
-
"stage": "3",
|
|
199
|
-
"statusselect": "capetown",
|
|
200
|
-
"api": {
|
|
201
|
-
"count": 45,
|
|
202
|
-
"limit": 50,
|
|
203
|
-
"lastStatusUpdate": "Tue May 16 2023 15:38:15 GMT+0200 (Central European Summer Time)",
|
|
204
|
-
"lastScheduleUpdate": "Tue May 16 2023 15:38:15 GMT+0200 (Central European Summer Time)"
|
|
205
|
-
},
|
|
206
|
-
"_msgid": "dfa429a93a16472d"
|
|
207
|
-
}
|
|
211
|
+
"_msgid": "9fa5d503c47f083e"
|
|
212
|
+
}
|
|
208
213
|
</pre>
|
|
209
214
|
|
|
210
215
|
<p>The <tt>start</tt> and <tt>end</tt> objects contain the time as unix timestamp in the Javascript format (milliseconds after the epoch),
|
|
211
|
-
while the <tt>duration</tt> is in seconds. The <code>msg.
|
|
216
|
+
while the <tt>duration</tt> is in seconds. The <code>msg.calc.next.islong</code> value is boolean and will be <em>true</em> if the
|
|
212
217
|
shedding lasts 4 hours or longer.</p>
|
|
213
218
|
|
|
214
|
-
<p>The second output shows <code>msg.stage</code> and <code>msg.schedule</code>, containing the latest information as retrieved from the API
|
|
215
|
-
This output is mainly useful when debugging or writing your own functions and logic.</p>
|
|
219
|
+
<p>The second output shows <code>msg.stage</code> and <code>msg.schedule</code>, containing the latest information as retrieved from the API,
|
|
220
|
+
with the <tt>lastUpdate</tt> field added. This output is mainly useful when debugging or writing your own functions and logic.</p>
|
|
216
221
|
|
|
217
222
|
<h3 id="status">Status</h3>
|
|
218
223
|
|
package/src/nodes/eskomsepush.js
CHANGED
|
@@ -18,10 +18,13 @@ module.exports = function (RED) {
|
|
|
18
18
|
calc: {}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
function
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
21
|
+
function getMinutesToAPIReset () {
|
|
22
|
+
const now = new Date()
|
|
23
|
+
const targetTime = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 2, 0, 0);
|
|
24
|
+
if (now > targetTime) {
|
|
25
|
+
targetTime.setDate(targetTime.getDate() + 1);
|
|
26
|
+
}
|
|
27
|
+
const timeDiff = targetTime - now;
|
|
25
28
|
const minutesLeft = Math.floor(timeDiff / (1000 * 60))
|
|
26
29
|
|
|
27
30
|
return minutesLeft
|
|
@@ -102,11 +105,11 @@ module.exports = function (RED) {
|
|
|
102
105
|
})
|
|
103
106
|
}
|
|
104
107
|
|
|
105
|
-
function updateSheddingStatus (node) {
|
|
108
|
+
function updateSheddingStatus (node, msg) {
|
|
106
109
|
const now = new Date()
|
|
107
110
|
|
|
108
111
|
// Check allowance every ten minutes
|
|
109
|
-
if (EskomSePushInfo.api.lastUpdate === null || (now.getTime() - EskomSePushInfo.api.lastUpdate.getTime()) > 600000) {
|
|
112
|
+
if ((msg && msg.payload === 'allowance' ) || EskomSePushInfo.api.lastUpdate === null || (now.getTime() - EskomSePushInfo.api.lastUpdate.getTime()) > 600000) {
|
|
110
113
|
checkAllowance(node)
|
|
111
114
|
}
|
|
112
115
|
|
|
@@ -122,19 +125,20 @@ module.exports = function (RED) {
|
|
|
122
125
|
return
|
|
123
126
|
}
|
|
124
127
|
|
|
125
|
-
// Fetching actual information takes 2 calls, so calculate how long the
|
|
126
|
-
//
|
|
128
|
+
// Fetching actual information takes 2 calls, so calculate how long until the next API count
|
|
129
|
+
// reset and divide the calls over the day. Wait at least 10 minutes between calls
|
|
127
130
|
if ((EskomSePushInfo.api.info.allowance.limit - EskomSePushInfo.api.info.allowance.count) > 0) {
|
|
128
|
-
EskomSePushInfo.calc.sleeptime = (
|
|
131
|
+
EskomSePushInfo.calc.sleeptime = (getMinutesToAPIReset() / ((EskomSePushInfo.api.info.allowance.limit - EskomSePushInfo.api.info.allowance.count) / 2)).toFixed(0)
|
|
132
|
+
if (EskomSePushInfo.calc.sleeptime < 10) { EskomSePushInfo.calc.sleeptime = 10 }
|
|
129
133
|
} else {
|
|
130
134
|
EskomSePushInfo.calc.sleeptime = 30
|
|
131
135
|
}
|
|
132
136
|
|
|
133
|
-
if (EskomSePushInfo.status.lastUpdate === null || (now.getTime() - EskomSePushInfo.status.lastUpdate) > (EskomSePushInfo.calc.sleeptime * 60000)) {
|
|
137
|
+
if (( msg && msg.payload === 'stage' ) || EskomSePushInfo.status.lastUpdate === null || (now.getTime() - EskomSePushInfo.status.lastUpdate) > (EskomSePushInfo.calc.sleeptime * 60000)) {
|
|
134
138
|
checkStage(node)
|
|
135
139
|
}
|
|
136
140
|
|
|
137
|
-
if (EskomSePushInfo.area.lastUpdate === null || (now.getTime() - EskomSePushInfo.area.lastUpdate) > (EskomSePushInfo.calc.sleeptime * 60000)) {
|
|
141
|
+
if (( msg && msg.payload === 'area' ) || EskomSePushInfo.area.lastUpdate === null || (now.getTime() - EskomSePushInfo.area.lastUpdate) > (EskomSePushInfo.calc.sleeptime * 60000)) {
|
|
138
142
|
checkArea(node)
|
|
139
143
|
}
|
|
140
144
|
|
|
@@ -167,6 +171,8 @@ module.exports = function (RED) {
|
|
|
167
171
|
if (EskomSePushInfo.area.info.events[0].note.match(/Stage (\d+)/i)) {
|
|
168
172
|
EskomSePushInfo.calc.stage = EskomSePushInfo.area.info.events[0].note.match(/Stage (\d+)/i)[1]
|
|
169
173
|
}
|
|
174
|
+
EskomSePushInfo.calc.start = EventStart
|
|
175
|
+
EskomSePushInfo.calc.end = EventEnd
|
|
170
176
|
} else {
|
|
171
177
|
EskomSePushInfo.calc.next = {
|
|
172
178
|
type: 'event',
|
|
@@ -181,9 +187,12 @@ module.exports = function (RED) {
|
|
|
181
187
|
// So not just like events, where they are in UTC with an offset
|
|
182
188
|
let BreakLoop = false
|
|
183
189
|
for (const dates of EskomSePushInfo.area.info.schedule.days) {
|
|
184
|
-
for (const schedule of dates.stages[EskomSePushInfo.calc.stage]) {
|
|
190
|
+
for (const schedule of dates.stages[EskomSePushInfo.calc.stage-1]) {
|
|
185
191
|
const ScheduleStart = Date.parse(dates.date + ' ' + schedule.split('-')[0])
|
|
186
|
-
|
|
192
|
+
let ScheduleEnd = Date.parse(dates.date + ' ' + schedule.split('-')[1])
|
|
193
|
+
if (ScheduleEnd < ScheduleStart) {
|
|
194
|
+
ScheduleEnd += (24 * 60 * 60 * 1000)
|
|
195
|
+
}
|
|
187
196
|
if (now < ScheduleEnd) {
|
|
188
197
|
BreakLoop = true
|
|
189
198
|
// This schedule is either active or will be next
|
|
@@ -239,18 +248,23 @@ module.exports = function (RED) {
|
|
|
239
248
|
// And update the status
|
|
240
249
|
let fill = 'green'
|
|
241
250
|
let shape = 'ring'
|
|
242
|
-
let statusText = 'Stage ' + EskomSePushInfo.calc.stage
|
|
251
|
+
let statusText = 'Stage ' + EskomSePushInfo.calc.stage + ': '
|
|
243
252
|
|
|
244
253
|
if (EskomSePushInfo.calc.active) {
|
|
245
254
|
fill = 'yellow'
|
|
246
255
|
if (EskomSePushInfo.calc.type === 'event') {
|
|
247
256
|
shape = 'dot'
|
|
248
257
|
}
|
|
249
|
-
|
|
250
|
-
|
|
258
|
+
if ( EskomSePushInfo.calc.start ) {
|
|
259
|
+
statusText += new Date(EskomSePushInfo.calc.start).toLocaleTimeString([], {timeStyle: 'short'})
|
|
260
|
+
statusText += ' - ' + new Date(EskomSePushInfo.calc.end).toLocaleTimeString([], {timeStyle: 'short'})
|
|
261
|
+
}
|
|
251
262
|
} else {
|
|
252
|
-
|
|
253
|
-
|
|
263
|
+
if (new Date(EskomSePushInfo.calc.next.start).getUTCDay() !== now.getUTCDate) {
|
|
264
|
+
statusText += new Date(EskomSePushInfo.calc.next.start).toLocaleString([], {weekday: 'short'}) + ' '
|
|
265
|
+
}
|
|
266
|
+
statusText += new Date(EskomSePushInfo.calc.next.start).toLocaleTimeString([], {timeStyle: 'short'})
|
|
267
|
+
statusText += ' - ' + new Date(EskomSePushInfo.calc.next.end).toLocaleTimeString([], {timeStyle: 'short'})
|
|
254
268
|
}
|
|
255
269
|
|
|
256
270
|
statusText += ' (API: ' + EskomSePushInfo.api.info.allowance.count + '/' + EskomSePushInfo.api.info.allowance.limit + ')'
|
|
@@ -270,6 +284,10 @@ module.exports = function (RED) {
|
|
|
270
284
|
updateSheddingStatus(node)
|
|
271
285
|
}, 60000)
|
|
272
286
|
|
|
287
|
+
node.on('input', function(msg) {
|
|
288
|
+
updateSheddingStatus(node, msg)
|
|
289
|
+
})
|
|
290
|
+
|
|
273
291
|
node.on('close', function () {
|
|
274
292
|
clearInterval(intervalId)
|
|
275
293
|
})
|