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 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
- "LoadShedding":{
66
- "schedule":{
67
- "next":{
68
- "start":1683561600000,
69
- "end":1683570600000,
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
- "stage":"5",
90
- "statusselect":"capetown",
91
- "api":{
92
- "count":12,
93
- "limit":50,
94
- "lastStatusUpdate":"Mon May 08 2023 13:34:30 GMT+0200 (Central European Summer Time)",
95
- "lastScheduleUpdate":"Mon May 08 2023 13:34:30 GMT+0200 (Central European Summer Time)"
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
- "\_msgid":"6e77b593b7f9eda4"
97
+ "_msgid": "9fa5d503c47f083e"
98
98
  }
99
99
  ```
100
100
 
101
- The start and end objects contain the time of next load shedding period as unix timestamps in the Javascript format (milliseconds after the epoch).
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 red color status can be either filled (dot) or not (ring). In case of a dot,
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
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-eskomsepush",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "description": "Node-RED interface for the Eskomsepush API",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -39,7 +39,7 @@
39
39
  test: { value: false },
40
40
  verbose: {value: false}
41
41
  },
42
- inputs:0,
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 left: <span id="api_info">unknown/unknown</span><br />
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&#39;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
- "payload": false,
170
- "LoadShedding": {
171
- "schedule": {
172
- "next": {
173
- "start": 1684267200000,
174
- "end": 1684276200000,
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
- "active": false
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.LoadShedding.next.islong</code> value is boolean and will be <em>true</em> if the
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
 
@@ -2,45 +2,32 @@ module.exports = function (RED) {
2
2
  'use strict'
3
3
 
4
4
  const axios = require('axios')
5
- let EskomSePushAPI = null
6
- let Stage = null
7
- let Schedule = null
8
- let LoadShedding = null
9
- let lastStatusUpdate = new Date()
10
- let lastStageUpdate = new Date()
11
- let lastScheduleUpdate = new Date()
12
-
13
- function updateStatus (node) {
14
- let fill = 'green'
15
- let shape = 'ring'
16
- let statusText = ''
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
- if (node.config.verbose === true) {
19
- node.warn('Running function updateStatus')
20
- }
21
- if (Stage && Stage.status && Stage.status[node.config.statusselect].stage) {
22
- statusText += 'Stage ' + (Stage.active || Stage.status[node.config.statusselect].stage)
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
- node.status({
42
- fill, shape, text: statusText
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
- EskomSePushAPI = response.data
56
- updateStatus(node)
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
- node.warn('Running function checkStage')
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
- Stage = response.data
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 checkSchedule (node) {
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
- node.warn('Running function checkSchedule')
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
- Schedule = response.data
94
- Schedule.info.area = node.config.area
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
- if (node.config.verbose === true) {
106
- node.warn('Running function updateSheddingStatus')
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
- if (Schedule && Schedule.info.area !== node.config.area) {
114
- Schedule = null
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 (EskomSePushAPI && EskomSePushAPI.allowance.count >= EskomSePushAPI.allowance.limit) {
118
- updateStatus(node)
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
- if (Stage === null || (now.getTime() - lastStageUpdate.getTime()) > 3600000) {
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 (Schedule === null || (now.getTime() - lastScheduleUpdate.getTime()) > 3600000) {
128
- checkSchedule(node)
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
- if (Stage && Schedule && EskomSePushAPI) {
133
- let stage = Stage.status[node.config.statusselect].stage
134
- const nowtime = now.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit' })
135
- LoadShedding = {
136
- schedule: {
137
- next: {},
138
- active: false
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
- if (!Array.isArray(Schedule.schedule.days[0].stages[stage])) {
148
- node.warn(`No schedule defined for stage ${stage}`)
149
- return
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
- for (const schedule of Schedule.schedule.days[0].stages[stage]) {
153
- if (nowtime >= schedule.split('-')[0] && nowtime <= schedule.split('-')[1]) {
154
- LoadShedding.schedule = {
155
- active: true
156
- }
157
- if (schedule.split('-')[0] < schedule.split('-')[1]) {
158
- LoadShedding.schedule.next = {
159
- start: Date.parse(Schedule.schedule.days[0].date + ' ' + schedule.split('-')[0]),
160
- end: Date.parse(Schedule.schedule.days[0].date + ' ' + schedule.split('-')[1])
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 (nowtime < schedule.split('-')[0]) {
170
- if (schedule.split('-')[0] < schedule.split('-')[1]) {
171
- LoadShedding.schedule.next = {
172
- start: Date.parse(Schedule.schedule.days[0].date + ' ' + schedule.split('-')[0]),
173
- end: Date.parse(Schedule.schedule.days[0].date + ' ' + schedule.split('-')[1])
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
- LoadShedding.schedule.next = {
177
- start: Date.parse(Schedule.schedule.days[0].date + ' ' + schedule.split('-')[0]),
178
- end: Date.parse(Schedule.schedule.days[1].date + ' ' + schedule.split('-')[1])
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 (!LoadShedding.schedule.active && Object.keys(LoadShedding.schedule.next).length === 0) {
184
- const s = Schedule.schedule.days[1].stages[stage - 1][0]
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
- if (!LoadShedding.schedule | !LoadShedding.schedule.next ||
205
- !LoadShedding.event || !LoadShedding.event.next ||
206
- !LoadShedding.schedule.next.start || !LoadShedding.event.next.start) {
207
- node.warn('Unable to find next scheduled event and/or schedule')
208
- node.warn(LoadShedding)
209
- return
210
- }
211
- if (LoadShedding.schedule.next.start <= LoadShedding.event.next.start) {
212
- LoadShedding.next = LoadShedding.schedule.next
213
- LoadShedding.next.type = 'schedule'
214
- } else {
215
- LoadShedding.next = LoadShedding.event.next
216
- LoadShedding.next.type = 'event'
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
- LoadShedding.next.duration = (LoadShedding.next.end - LoadShedding.next.start) / 1000
220
- LoadShedding.next.islong = LoadShedding.next.duration >= (4 * 3600)
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
- LoadShedding.active = (LoadShedding.schedule.active || LoadShedding.event.active)
223
- if (LoadShedding.schedule.active) {
224
- LoadShedding.type = 'schedule'
225
- LoadShedding.start = LoadShedding.schedule.next.start
226
- LoadShedding.end = LoadShedding.schedule.next.end
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
- if (LoadShedding.event.active) {
229
- LoadShedding.type = 'event'
230
- LoadShedding.start = LoadShedding.event.next.start
231
- LoadShedding.end = LoadShedding.event.next.end
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
- node.send([{
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
- updateStatus(node)
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
+ }