node-red-contrib-eskomsepush 0.0.10 → 0.0.12
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 +35 -35
- 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 +186 -164
- package/test/three-anchor-bay.js +26 -0
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
|
|
|
@@ -108,7 +108,7 @@ The status will show the situation regarding the API calls and when the next
|
|
|
108
108
|
shedding wil start or end. It also shows the count of API calls that have been
|
|
109
109
|
done and how many are left. This updates every 10 minutes.
|
|
110
110
|
|
|
111
|
-
The
|
|
111
|
+
The yellow color status can be either filled (dot) or not (ring). In case of a dot,
|
|
112
112
|
the load schedding is because of an _event_. When it is a ring, it is caused by
|
|
113
113
|
a matching _schedule_.
|
|
114
114
|
|
|
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
|
@@ -2,45 +2,32 @@ module.exports = function (RED) {
|
|
|
2
2
|
'use strict'
|
|
3
3
|
|
|
4
4
|
const axios = require('axios')
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
5
|
+
const EskomSePushInfo = {
|
|
6
|
+
api: {
|
|
7
|
+
lastUpdate: null,
|
|
8
|
+
info: {}
|
|
9
|
+
},
|
|
10
|
+
status: {
|
|
11
|
+
lastUpdate: null,
|
|
12
|
+
info: {}
|
|
13
|
+
},
|
|
14
|
+
area: {
|
|
15
|
+
lastUpdate: null,
|
|
16
|
+
info: {}
|
|
17
|
+
},
|
|
18
|
+
calc: {}
|
|
19
|
+
}
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
if (LoadShedding && LoadShedding.active && LoadShedding.next && LoadShedding.next.end) {
|
|
25
|
-
statusText += ' - ' + new Date(LoadShedding.next.end).toLocaleTimeString()
|
|
26
|
-
fill = 'yellow'
|
|
27
|
-
if (LoadShedding.type === 'event') {
|
|
28
|
-
shape = 'dot'
|
|
29
|
-
}
|
|
30
|
-
} else {
|
|
31
|
-
if (LoadShedding && LoadShedding.next && LoadShedding.next.start) {
|
|
32
|
-
statusText += ' - ' + new Date(LoadShedding.next.start).toLocaleTimeString()
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
if (EskomSePushAPI) {
|
|
36
|
-
statusText += ` (API: ${EskomSePushAPI.allowance.count}/${EskomSePushAPI.allowance.limit})`
|
|
37
|
-
if (EskomSePushAPI.allowance.count >= EskomSePushAPI.allowance.limit) {
|
|
38
|
-
fill = 'red'
|
|
39
|
-
}
|
|
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);
|
|
40
26
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
27
|
+
const timeDiff = targetTime - now;
|
|
28
|
+
const minutesLeft = Math.floor(timeDiff / (1000 * 60))
|
|
29
|
+
|
|
30
|
+
return minutesLeft
|
|
44
31
|
}
|
|
45
32
|
|
|
46
33
|
function checkAllowance (node) {
|
|
@@ -52,8 +39,12 @@ module.exports = function (RED) {
|
|
|
52
39
|
}
|
|
53
40
|
axios.get('https://developer.sepush.co.za/business/2.0/api_allowance',
|
|
54
41
|
{ params: options, headers }).then(function (response) {
|
|
55
|
-
|
|
56
|
-
|
|
42
|
+
EskomSePushInfo.api.info = response.data
|
|
43
|
+
if (EskomSePushInfo.api.lastUpdate === null) {
|
|
44
|
+
EskomSePushInfo.api.lastUpdate = new Date()
|
|
45
|
+
updateSheddingStatus(node)
|
|
46
|
+
}
|
|
47
|
+
EskomSePushInfo.api.lastUpdate = new Date()
|
|
57
48
|
})
|
|
58
49
|
.catch(error => {
|
|
59
50
|
node.warn({ error: error.message })
|
|
@@ -65,11 +56,19 @@ module.exports = function (RED) {
|
|
|
65
56
|
const headers = { token: node.config.licensekey }
|
|
66
57
|
|
|
67
58
|
if (node.config.verbose === true) {
|
|
68
|
-
|
|
59
|
+
let warnstring = 'Running function checkStage'
|
|
60
|
+
if (EskomSePushInfo.status.lastUpdate === null) {
|
|
61
|
+
warnstring += ' - initial run'
|
|
62
|
+
} else {
|
|
63
|
+
warnstring += ' after ' + ((new Date() - EskomSePushInfo.status.lastUpdate) / 60000).toFixed(0) + ' minutes'
|
|
64
|
+
}
|
|
65
|
+
node.warn(warnstring)
|
|
69
66
|
}
|
|
70
67
|
axios.get('https://developer.sepush.co.za/business/2.0/status',
|
|
71
68
|
{ params: options, headers }).then(function (response) {
|
|
72
|
-
|
|
69
|
+
EskomSePushInfo.status.info = response.data
|
|
70
|
+
EskomSePushInfo.status.lastUpdate = new Date()
|
|
71
|
+
// Call updateSheddingStatus again now we have new data
|
|
73
72
|
updateSheddingStatus(node)
|
|
74
73
|
})
|
|
75
74
|
.catch(error => {
|
|
@@ -77,21 +76,28 @@ module.exports = function (RED) {
|
|
|
77
76
|
})
|
|
78
77
|
}
|
|
79
78
|
|
|
80
|
-
function
|
|
79
|
+
function checkArea (node) {
|
|
81
80
|
const options = { id: node.config.area }
|
|
82
81
|
const headers = { token: node.config.licensekey }
|
|
83
82
|
const url = 'https://developer.sepush.co.za/business/2.0/area'
|
|
84
83
|
|
|
85
84
|
if (node.config.verbose === true) {
|
|
86
|
-
|
|
85
|
+
let warnstring = 'Running function checkArea'
|
|
86
|
+
if (EskomSePushInfo.area.lastUpdate === null) {
|
|
87
|
+
warnstring += ' - initial run'
|
|
88
|
+
} else {
|
|
89
|
+
warnstring += ' after ' + ((new Date() - EskomSePushInfo.area.lastUpdate) / 60000).toFixed(0) + ' minutes'
|
|
90
|
+
}
|
|
91
|
+
node.warn(warnstring)
|
|
87
92
|
}
|
|
88
93
|
if (node.config.test) {
|
|
89
94
|
options.test = 'current'
|
|
90
95
|
}
|
|
91
96
|
axios.get(url,
|
|
92
97
|
{ params: options, headers }).then(function (response) {
|
|
93
|
-
|
|
94
|
-
|
|
98
|
+
EskomSePushInfo.area.info = response.data
|
|
99
|
+
EskomSePushInfo.area.lastUpdate = new Date()
|
|
100
|
+
// Call updateSheddingStatus again now we have new data
|
|
95
101
|
updateSheddingStatus(node)
|
|
96
102
|
})
|
|
97
103
|
.catch(error => {
|
|
@@ -99,156 +105,168 @@ module.exports = function (RED) {
|
|
|
99
105
|
})
|
|
100
106
|
}
|
|
101
107
|
|
|
102
|
-
function updateSheddingStatus (node) {
|
|
108
|
+
function updateSheddingStatus (node, msg) {
|
|
103
109
|
const now = new Date()
|
|
104
110
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
if (EskomSePushAPI === null || (now.getTime() - lastStatusUpdate.getTime()) > 600000) {
|
|
111
|
+
// Check allowance every ten minutes
|
|
112
|
+
if ((msg && msg.payload === 'allowance' ) || EskomSePushInfo.api.lastUpdate === null || (now.getTime() - EskomSePushInfo.api.lastUpdate.getTime()) > 600000) {
|
|
109
113
|
checkAllowance(node)
|
|
110
|
-
lastStatusUpdate = now
|
|
111
114
|
}
|
|
112
115
|
|
|
113
|
-
|
|
114
|
-
|
|
116
|
+
// If we don't have API info, we just return
|
|
117
|
+
if (Object.entries(EskomSePushInfo.api.info).length === 0) {
|
|
118
|
+
node.warn('No API info (yet), refusing to continue')
|
|
119
|
+
return
|
|
115
120
|
}
|
|
116
121
|
|
|
117
|
-
if
|
|
118
|
-
|
|
122
|
+
// The same is true if we have no API calls left
|
|
123
|
+
if (EskomSePushInfo.api.info.allowance.count >= EskomSePushInfo.api.info.allowance.limit) {
|
|
124
|
+
node.warn('No API calls left, not checking status/schedule')
|
|
119
125
|
return
|
|
120
126
|
}
|
|
121
127
|
|
|
122
|
-
|
|
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
|
|
130
|
+
if ((EskomSePushInfo.api.info.allowance.limit - EskomSePushInfo.api.info.allowance.count) > 0) {
|
|
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 }
|
|
133
|
+
} else {
|
|
134
|
+
EskomSePushInfo.calc.sleeptime = 30
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (( msg && msg.payload === 'stage' ) || EskomSePushInfo.status.lastUpdate === null || (now.getTime() - EskomSePushInfo.status.lastUpdate) > (EskomSePushInfo.calc.sleeptime * 60000)) {
|
|
123
138
|
checkStage(node)
|
|
124
|
-
lastStageUpdate = now
|
|
125
139
|
}
|
|
126
140
|
|
|
127
|
-
if (
|
|
128
|
-
|
|
129
|
-
lastScheduleUpdate = now
|
|
141
|
+
if (( msg && msg.payload === 'area' ) || EskomSePushInfo.area.lastUpdate === null || (now.getTime() - EskomSePushInfo.area.lastUpdate) > (EskomSePushInfo.calc.sleeptime * 60000)) {
|
|
142
|
+
checkArea(node)
|
|
130
143
|
}
|
|
131
144
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
},
|
|
140
|
-
event: {
|
|
141
|
-
next: {},
|
|
142
|
-
active: false
|
|
143
|
-
},
|
|
144
|
-
checked: nowtime
|
|
145
|
-
}
|
|
145
|
+
// Now we have all info to continue. Just making sure that all update values are non null.
|
|
146
|
+
if (EskomSePushInfo.api.lastUpdate === null ||
|
|
147
|
+
EskomSePushInfo.status.lastUpdate === null ||
|
|
148
|
+
EskomSePushInfo.area.lastUpdate === null) {
|
|
149
|
+
(node.config.verbose === true) && node.warn('Not enough info to continue.')
|
|
150
|
+
return
|
|
151
|
+
}
|
|
146
152
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
153
|
+
// Determine the current stage
|
|
154
|
+
EskomSePushInfo.calc.stage = EskomSePushInfo.status.info.status[node.config.statusselect].stage
|
|
155
|
+
|
|
156
|
+
if (node.config.verbose === true) {
|
|
157
|
+
node.warn('API call status: ' + EskomSePushInfo.api.info.allowance.count + '/' + EskomSePushInfo.api.info.allowance.limit)
|
|
158
|
+
node.warn(EskomSePushInfo)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Default to false, overrule of loadshedding is active
|
|
162
|
+
EskomSePushInfo.calc.active = false
|
|
163
|
+
|
|
164
|
+
// Are there any events going on?
|
|
165
|
+
if (Object.entries(EskomSePushInfo.area.info.events).length > 0) {
|
|
166
|
+
EskomSePushInfo.calc.type = 'event'
|
|
167
|
+
const EventStart = Date.parse(EskomSePushInfo.area.info.events[0].start)
|
|
168
|
+
const EventEnd = Date.parse(EskomSePushInfo.area.info.events[0].end)
|
|
169
|
+
if (now >= EventStart && now < EventEnd) {
|
|
170
|
+
EskomSePushInfo.calc.active = true
|
|
171
|
+
if (EskomSePushInfo.area.info.events[0].note.match(/Stage (\d+)/i)) {
|
|
172
|
+
EskomSePushInfo.calc.stage = EskomSePushInfo.area.info.events[0].note.match(/Stage (\d+)/i)[1]
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
EskomSePushInfo.calc.next = {
|
|
176
|
+
type: 'event',
|
|
177
|
+
start: EventStart,
|
|
178
|
+
end: EventEnd,
|
|
179
|
+
stage: EskomSePushInfo.area.info.events[0].note.match(/Stage (\d+)/i)[1]
|
|
180
|
+
}
|
|
150
181
|
}
|
|
182
|
+
}
|
|
151
183
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
} else {
|
|
163
|
-
LoadShedding.schedule.next = {
|
|
164
|
-
start: Date.parse(Schedule.schedule.days[0].date + ' ' + schedule.split('-')[0]),
|
|
165
|
-
end: Date.parse(Schedule.schedule.days[1].date + ' ' + schedule.split('-')[1])
|
|
166
|
-
}
|
|
167
|
-
}
|
|
184
|
+
// Scheduled downtime has the thing that the time is in locatime
|
|
185
|
+
// So not just like events, where they are in UTC with an offset
|
|
186
|
+
let BreakLoop = false
|
|
187
|
+
for (const dates of EskomSePushInfo.area.info.schedule.days) {
|
|
188
|
+
for (const schedule of dates.stages[EskomSePushInfo.calc.stage]) {
|
|
189
|
+
const ScheduleStart = Date.parse(dates.date + ' ' + schedule.split('-')[0])
|
|
190
|
+
let ScheduleEnd = Date.parse(dates.date + ' ' + schedule.split('-')[1])
|
|
191
|
+
if (ScheduleEnd < ScheduleStart) {
|
|
192
|
+
ScheduleEnd += (24 * 60 * 60 * 1000)
|
|
168
193
|
}
|
|
169
|
-
if (
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
194
|
+
if (now < ScheduleEnd) {
|
|
195
|
+
BreakLoop = true
|
|
196
|
+
// This schedule is either active or will be next
|
|
197
|
+
if (now >= ScheduleStart) {
|
|
198
|
+
EskomSePushInfo.calc.active = true
|
|
199
|
+
EskomSePushInfo.calc.type = 'schedule'
|
|
200
|
+
EskomSePushInfo.calc.start = ScheduleStart
|
|
201
|
+
EskomSePushInfo.calc.end = ScheduleEnd
|
|
175
202
|
} else {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
203
|
+
EskomSePushInfo.calc.next = {
|
|
204
|
+
type: 'schedule',
|
|
205
|
+
start: ScheduleStart,
|
|
206
|
+
end: ScheduleEnd
|
|
179
207
|
}
|
|
180
208
|
}
|
|
181
209
|
}
|
|
210
|
+
if (BreakLoop) { break }
|
|
182
211
|
}
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
LoadShedding.schedule.next = {
|
|
186
|
-
start: Date.parse(Schedule.schedule.days[1].date + ' ' + s.split('-')[0]),
|
|
187
|
-
end: Date.parse(Schedule.schedule.days[1].date + ' ' + s.split('-')[1])
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
LoadShedding.event.next = {
|
|
191
|
-
start: Date.parse(Schedule.events[0].start),
|
|
192
|
-
end: Date.parse(Schedule.events[0].end)
|
|
193
|
-
}
|
|
194
|
-
if (now >= LoadShedding.event.next.start && now < LoadShedding.event.next.end) {
|
|
195
|
-
LoadShedding.event.active = true
|
|
196
|
-
if (Schedule.events[0].note.match(/Stage (\d+)/i)) {
|
|
197
|
-
stage = Schedule.events[0].note.match(/Stage (\d+)/i)[1]
|
|
198
|
-
Stage.active = stage
|
|
199
|
-
}
|
|
200
|
-
} else {
|
|
201
|
-
Stage.active = stage
|
|
202
|
-
}
|
|
212
|
+
if (BreakLoop) { break }
|
|
213
|
+
}
|
|
203
214
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
215
|
+
if (EskomSePushInfo.calc.next) {
|
|
216
|
+
EskomSePushInfo.calc.next.duration = (EskomSePushInfo.calc.next.end - EskomSePushInfo.calc.next.start) / 1000
|
|
217
|
+
EskomSePushInfo.calc.next.islong = EskomSePushInfo.calc.next.duration >= (4 * 3600)
|
|
218
|
+
EskomSePushInfo.calc.secondstostatechange = parseInt((EskomSePushInfo.calc.next.start - now) / 1000)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (EskomSePushInfo.calc.active) {
|
|
222
|
+
EskomSePushInfo.calc.duration = (EskomSePushInfo.calc.end - EskomSePushInfo.calc.start) / 1000
|
|
223
|
+
EskomSePushInfo.calc.islong = EskomSePushInfo.calc.duration >= (4 * 3600)
|
|
224
|
+
EskomSePushInfo.calc.secondstostatechange = parseInt((EskomSePushInfo.calc.end - now) / 1000)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (node.config.verbose === true) {
|
|
228
|
+
node.warn(EskomSePushInfo.calc)
|
|
229
|
+
}
|
|
218
230
|
|
|
219
|
-
|
|
220
|
-
|
|
231
|
+
// Send output
|
|
232
|
+
node.send([{
|
|
233
|
+
payload: EskomSePushInfo.calc.active,
|
|
234
|
+
stage: EskomSePushInfo.calc.stage,
|
|
235
|
+
statusselect: node.config.statusselect,
|
|
236
|
+
api: {
|
|
237
|
+
count: EskomSePushInfo.api.info.allowance.count,
|
|
238
|
+
limit: EskomSePushInfo.api.info.allowance.limit
|
|
239
|
+
},
|
|
240
|
+
calc: EskomSePushInfo.calc
|
|
241
|
+
}, {
|
|
242
|
+
stage: EskomSePushInfo.status,
|
|
243
|
+
schedule: EskomSePushInfo.area
|
|
244
|
+
}])
|
|
221
245
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
246
|
+
// And update the status
|
|
247
|
+
let fill = 'green'
|
|
248
|
+
let shape = 'ring'
|
|
249
|
+
let statusText = 'Stage ' + EskomSePushInfo.calc.stage + ': '
|
|
250
|
+
|
|
251
|
+
if (EskomSePushInfo.calc.active) {
|
|
252
|
+
fill = 'yellow'
|
|
253
|
+
if (EskomSePushInfo.calc.type === 'event') {
|
|
254
|
+
shape = 'dot'
|
|
227
255
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
256
|
+
statusText += new Date(EskomSePushInfo.calc.start).toLocaleTimeString([], {timeStyle: 'short'})
|
|
257
|
+
statusText += ' - ' + new Date(EskomSePushInfo.calc.end).toLocaleTimeString([], {timeStyle: 'short'})
|
|
258
|
+
} else {
|
|
259
|
+
if (new Date(EskomSePushInfo.calc.next.start).getUTCDay() !== now.getUTCDate) {
|
|
260
|
+
statusText += new Date(EskomSePushInfo.calc.next.start).toLocaleString([], {weekday: 'short'}) + ' '
|
|
232
261
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
payload: LoadShedding.active,
|
|
236
|
-
LoadShedding,
|
|
237
|
-
stage,
|
|
238
|
-
statusselect: node.config.statusselect,
|
|
239
|
-
api: {
|
|
240
|
-
count: EskomSePushAPI.allowance.count,
|
|
241
|
-
limit: EskomSePushAPI.allowance.limit,
|
|
242
|
-
lastStatusUpdate: lastStatusUpdate.toString(),
|
|
243
|
-
lastScheduleUpdate: lastScheduleUpdate.toString()
|
|
244
|
-
}
|
|
245
|
-
}, {
|
|
246
|
-
stage: Stage,
|
|
247
|
-
schedule: Schedule
|
|
248
|
-
}])
|
|
262
|
+
statusText += new Date(EskomSePushInfo.calc.next.start).toLocaleTimeString([], {timeStyle: 'short'})
|
|
263
|
+
statusText += ' - ' + new Date(EskomSePushInfo.calc.next.end).toLocaleTimeString([], {timeStyle: 'short'})
|
|
249
264
|
}
|
|
250
265
|
|
|
251
|
-
|
|
266
|
+
statusText += ' (API: ' + EskomSePushInfo.api.info.allowance.count + '/' + EskomSePushInfo.api.info.allowance.limit + ')'
|
|
267
|
+
node.status({
|
|
268
|
+
fill, shape, text: statusText
|
|
269
|
+
})
|
|
252
270
|
}
|
|
253
271
|
|
|
254
272
|
function EskomSePush (config) {
|
|
@@ -262,6 +280,10 @@ module.exports = function (RED) {
|
|
|
262
280
|
updateSheddingStatus(node)
|
|
263
281
|
}, 60000)
|
|
264
282
|
|
|
283
|
+
node.on('input', function(msg) {
|
|
284
|
+
updateSheddingStatus(node, msg)
|
|
285
|
+
})
|
|
286
|
+
|
|
265
287
|
node.on('close', function () {
|
|
266
288
|
clearInterval(intervalId)
|
|
267
289
|
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const EskomSePushInfo = { stage: { lastUpdate: '2023-06-16T07:18:37.847Z', info: { status: { capetown: { name: 'Cape Town', next_stages: [{ stage: '1', stage_start_timestamp: '2023-06-16T16:00:00+02:00' }, { stage: '0', stage_start_timestamp: '2023-06-17T00:00:00+02:00' }, { stage: '1', stage_start_timestamp: '2023-06-17T16:00:00+02:00' }, { stage: '0', stage_start_timestamp: '2023-06-18T00:00:00+02:00' }, { stage: '1', stage_start_timestamp: '2023-06-18T16:00:00+02:00' }], stage: '0', stage_updated: '2023-06-16T00:00:00.742510+02:00' }, eskom: { name: 'Eskom', next_stages: [{ stage: '3', stage_start_timestamp: '2023-06-16T16:00:00+02:00' }, { stage: '0', stage_start_timestamp: '2023-06-17T00:00:00+02:00' }, { stage: '3', stage_start_timestamp: '2023-06-17T16:00:00+02:00' }, { stage: '0', stage_start_timestamp: '2023-06-18T00:00:00+02:00' }, { stage: '3', stage_start_timestamp: '2023-06-18T16:00:00+02:00' }], stage: '0', stage_updated: '2023-06-16T00:00:00.742510+02:00' } } } }, schedule: { lastUpdate: '2023-06-16T07:18:38.957Z', info: { events: [{ end: '2023-06-17T00:30:00+02:00', note: 'Stage 1', start: '2023-06-16T22:00:00+02:00' }], info: { name: 'Helderberg Village (3)', region: 'City of Cape Town' }, schedule: { days: [{ date: '2023-06-16', name: 'Friday', stages: [['22:00-00:30'], ['06:00-08:30', '22:00-00:30'], ['06:00-08:30', '22:00-00:30'], ['06:00-08:30', '14:00-16:30', '22:00-00:30'], ['06:00-08:30', '14:00-16:30', '22:00-00:30'], ['06:00-08:30', '12:00-16:30', '22:00-00:30'], ['04:00-08:30', '12:00-16:30', '22:00-00:30'], ['04:00-08:30', '12:00-16:30', '20:00-00:30']] }, { date: '2023-06-17', name: 'Saturday', stages: [['04:00-06:30'], ['04:00-06:30', '20:00-22:30'], ['04:00-06:30', '12:00-14:30', '20:00-22:30'], ['04:00-06:30', '12:00-14:30', '20:00-22:30'], ['02:00-06:30', '12:00-14:30', '20:00-22:30'], ['02:00-06:30', '12:00-14:30', '18:00-22:30'], ['02:00-06:30', '10:00-14:30', '18:00-22:30'], ['02:00-06:30', '10:00-14:30', '18:00-22:30']] }, { date: '2023-06-18', name: 'Sunday', stages: [['12:00-14:30'], ['12:00-14:30'], ['12:00-14:30', '20:00-22:30'], ['04:00-06:30', '12:00-14:30', '20:00-22:30'], ['04:00-06:30', '10:00-14:30', '20:00-22:30'], ['04:00-06:30', '10:00-14:30', '20:00-22:30'], ['04:00-06:30', '10:00-14:30', '18:00-22:30'], ['02:00-06:30', '10:00-14:30', '18:00-22:30']] }, { date: '2023-06-19', name: 'Monday', stages: [['20:00-22:30'], ['04:00-06:30', '20:00-22:30'], ['04:00-06:30', '20:00-22:30'], ['04:00-06:30', '12:00-14:30', '20:00-22:30'], ['04:00-06:30', '12:00-14:30', '18:00-22:30'], ['02:00-06:30', '12:00-14:30', '18:00-22:30'], ['02:00-06:30', '12:00-14:30', '18:00-22:30'], ['02:00-06:30', '10:00-14:30', '18:00-22:30']] }, { date: '2023-06-20', name: 'Tuesday', stages: [[], ['12:00-14:30'], ['04:00-06:30', '12:00-14:30'], ['04:00-06:30', '12:00-14:30', '20:00-22:30'], ['04:00-06:30', '12:00-14:30', '20:00-22:30'], ['04:00-06:30', '10:00-14:30', '20:00-22:30'], ['02:00-06:30', '10:00-14:30', '20:00-22:30'], ['02:00-06:30', '10:00-14:30', '18:00-22:30']] }, { date: '2023-06-21', name: 'Wednesday', stages: [['02:00-04:30'], ['02:00-04:30', '18:00-20:30'], ['02:00-04:30', '10:00-12:30', '18:00-20:30'], ['02:00-04:30', '10:00-12:30', '18:00-20:30'], ['00:00-04:30', '10:00-12:30', '18:00-20:30'], ['00:00-04:30', '10:00-12:30', '16:00-20:30'], ['00:00-04:30', '08:00-12:30', '16:00-20:30'], ['00:00-04:30', '08:00-12:30', '16:00-20:30']] }, { date: '2023-06-22', name: 'Thursday', stages: [['10:00-12:30'], ['10:00-12:30'], ['10:00-12:30', '18:00-20:30'], ['02:00-04:30', '10:00-12:30', '18:00-20:30'], ['02:00-04:30', '08:00-12:30', '18:00-20:30'], ['02:00-04:30', '08:00-12:30', '18:00-20:30'], ['02:00-04:30', '08:00-12:30', '16:00-20:30'], ['00:00-04:30', '08:00-12:30', '16:00-20:30']] }], source: 'https://www.capetown.gov.za/loadshedding/' } } }, _msgid: 'e5eceed861704158' }
|
|
2
|
+
|
|
3
|
+
const now = new Date()
|
|
4
|
+
let BreakLoop = false
|
|
5
|
+
for (const dates of EskomSePushInfo.schedule.info.schedule.days) {
|
|
6
|
+
for (const schedule of dates.stages['0']) {
|
|
7
|
+
const ScheduleStart = Date.parse(dates.date + ' ' + schedule.split('-')[0])
|
|
8
|
+
let ScheduleEnd = Date.parse(dates.date + ' ' + schedule.split('-')[1])
|
|
9
|
+
if (ScheduleEnd < ScheduleStart) {
|
|
10
|
+
ScheduleEnd += (24 * 60 * 60 * 1000)
|
|
11
|
+
}
|
|
12
|
+
console.log('check: ' + new Date(ScheduleEnd))
|
|
13
|
+
if (now < new Date(ScheduleEnd)) {
|
|
14
|
+
BreakLoop = true
|
|
15
|
+
// This schedule is either active or will be next
|
|
16
|
+
if (now >= ScheduleStart) {
|
|
17
|
+
console.log('match')
|
|
18
|
+
} else {
|
|
19
|
+
console.log('next found')
|
|
20
|
+
console.log(new Date(ScheduleStart).toLocaleString())
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (BreakLoop) { break }
|
|
24
|
+
}
|
|
25
|
+
if (BreakLoop) { break }
|
|
26
|
+
}
|