node-red-contrib-eskomsepush 0.0.9 → 0.0.11

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
@@ -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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-eskomsepush",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "Node-RED interface for the Eskomsepush API",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -36,7 +36,8 @@
36
36
  licensekey: {value:"", validate: RED.validators.regex(/^[0-9A-F\-]{35}$/)},
37
37
  area: {value:""},
38
38
  statusselect: {value:"", validate: RED.validators.regex(/^(eskom|capetown)$/)},
39
- test: { value: false }
39
+ test: { value: false },
40
+ verbose: {value: false}
40
41
  },
41
42
  inputs:0,
42
43
  outputs:2,
@@ -130,6 +131,10 @@
130
131
  <button id="searchAreaId">Search</button><br />
131
132
  </p>
132
133
  </div>
134
+ <div class="form-row" style="margin-bottom:0px;">
135
+ <input type="checkbox" checked id="node-input-verbose" style="display:inline-block; margin-left:8px; width:auto; vertical-align:top;">
136
+ <label style="min-width:390px" for="node-input-verbose"><i class="fa fa-power"></i> Verbose: add extra logging when running</label>
137
+ </div>
133
138
  </script>
134
139
 
135
140
  <script type="text/html" data-help-name="eskomsepush">
@@ -216,11 +221,24 @@ This output is mainly useful when debugging or writing your own functions and lo
216
221
  how many are left. This updates every 10 minutes.</p>
217
222
 
218
223
  <p>
219
- The red color status can be either filled (dot) or not (ring). In case of a dot,
224
+ The yellow color status can be either filled (dot) or not (ring). In case of a dot,
220
225
  the load schedding is because of an <em>event</em>. When it is a ring, it is caused by
221
226
  a matching <em>schedule</em>.
222
227
  </p>
223
228
 
229
+ <div id="legend">
230
+ <ul>
231
+ <li><svg class="bullet" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><rect x="5" y="5" width="9" height="9" rx="2" ry="2" stroke-width="3" fill="#fff" stroke="#5a8"></rect>
232
+ </svg> - grid available, working properly</li>
233
+ <li><svg class="bullet" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><rect x="5" y="5" width="9" height="9" rx="2" ry="2" stroke-width="3" fill="#F9DF31" stroke="#F9DF31"></rect>
234
+ </svg> - no grid, load shedding because of an <strong>event</strong></li>
235
+ <li><svg class="bullet" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><rect x="5" y="5" width="9" height="9" rx="2" ry="2" stroke-width="3" fill="#fff" stroke="#F9DF31"></rect>
236
+ </svg> - no grid, load shedding because of a <strong>schedule</strong></li>
237
+ <li><svg class="bullet" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><rect x="5" y="5" width="9" height="9" rx="2" ry="2" stroke-width="3" fill="#fff" stroke="#c00"></rect>
238
+ </svg> - ran out of API calls</li>
239
+ </ul>
240
+ </div>
241
+
224
242
  <h1 id="documentation">Documentation</h1>
225
243
 
226
244
  <p>The GitHub site for the node can be found <a href="https://github.com/dirkjanfaber/node-red-contrib-eskomsepush">here</a>, while the documentation for the API can be found <a href="https://documenter.getpostman.com/view/1296288/UzQuNk3E">here</a>.
@@ -238,4 +256,21 @@ select#areaDropdown {
238
256
  #list-areas li {
239
257
  cursor: pointer;
240
258
  }
259
+
260
+ #legend ul {
261
+ list-style: none;
262
+ padding: 0;
263
+ }
264
+ #legend li {
265
+ margin-bottom: 10px;
266
+ position: relative;
267
+ padding-left: 25px;
268
+ }
269
+ .bullet {
270
+ position: absolute;
271
+ top: 2px;
272
+ left: 0;
273
+ width: 35px;
274
+ height: 35px;
275
+ }
241
276
  </style>
@@ -2,50 +2,46 @@ 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 = ''
17
- if (Stage && Stage.status && Stage.status[node.config.statusselect].stage) {
18
- statusText += 'Stage ' + Stage.status[node.config.statusselect].stage
19
- }
20
- if (LoadShedding && LoadShedding.active && LoadShedding.next && LoadShedding.next.end) {
21
- statusText += ' - ' + new Date(LoadShedding.next.end).toLocaleTimeString()
22
- fill = 'red'
23
- if (LoadShedding.type === 'event') {
24
- shape = 'dot'
25
- }
26
- } else {
27
- if (LoadShedding && LoadShedding.next && LoadShedding.next.start) {
28
- statusText += ' - ' + new Date(LoadShedding.next.start).toLocaleTimeString()
29
- }
30
- }
31
- if (EskomSePushAPI) {
32
- statusText += ` (API: ${EskomSePushAPI.allowance.count}/${EskomSePushAPI.allowance.limit})`
33
- if (EskomSePushAPI.allowance.count >= EskomSePushAPI.allowance.limit) {
34
- fill = 'red'
35
- }
36
- }
37
- node.status({
38
- fill, shape, text: statusText
39
- })
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
+ }
20
+
21
+ function getMinutesLeftInDay () {
22
+ const currentDate = new Date()
23
+ const tomorrow = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate() + 1)
24
+ const timeDiff = tomorrow.getTime() - currentDate.getTime()
25
+ const minutesLeft = Math.floor(timeDiff / (1000 * 60))
26
+
27
+ return minutesLeft
40
28
  }
41
29
 
42
30
  function checkAllowance (node) {
43
31
  const options = {}
44
32
  const headers = { token: node.config.licensekey }
33
+
34
+ if (node.config.verbose === true) {
35
+ node.warn('Running function checkAllowance')
36
+ }
45
37
  axios.get('https://developer.sepush.co.za/business/2.0/api_allowance',
46
38
  { params: options, headers }).then(function (response) {
47
- EskomSePushAPI = response.data
48
- updateStatus(node)
39
+ EskomSePushInfo.api.info = response.data
40
+ if (EskomSePushInfo.api.lastUpdate === null) {
41
+ EskomSePushInfo.api.lastUpdate = new Date()
42
+ updateSheddingStatus(node)
43
+ }
44
+ EskomSePushInfo.api.lastUpdate = new Date()
49
45
  })
50
46
  .catch(error => {
51
47
  node.warn({ error: error.message })
@@ -55,9 +51,21 @@ module.exports = function (RED) {
55
51
  function checkStage (node) {
56
52
  const options = {}
57
53
  const headers = { token: node.config.licensekey }
54
+
55
+ if (node.config.verbose === true) {
56
+ let warnstring = 'Running function checkStage'
57
+ if (EskomSePushInfo.status.lastUpdate === null) {
58
+ warnstring += ' - initial run'
59
+ } else {
60
+ warnstring += ' after ' + ((new Date() - EskomSePushInfo.status.lastUpdate) / 60000).toFixed(0) + ' minutes'
61
+ }
62
+ node.warn(warnstring)
63
+ }
58
64
  axios.get('https://developer.sepush.co.za/business/2.0/status',
59
65
  { params: options, headers }).then(function (response) {
60
- Stage = response.data
66
+ EskomSePushInfo.status.info = response.data
67
+ EskomSePushInfo.status.lastUpdate = new Date()
68
+ // Call updateSheddingStatus again now we have new data
61
69
  updateSheddingStatus(node)
62
70
  })
63
71
  .catch(error => {
@@ -65,17 +73,28 @@ module.exports = function (RED) {
65
73
  })
66
74
  }
67
75
 
68
- function checkSchedule (node) {
76
+ function checkArea (node) {
69
77
  const options = { id: node.config.area }
70
78
  const headers = { token: node.config.licensekey }
71
79
  const url = 'https://developer.sepush.co.za/business/2.0/area'
80
+
81
+ if (node.config.verbose === true) {
82
+ let warnstring = 'Running function checkArea'
83
+ if (EskomSePushInfo.area.lastUpdate === null) {
84
+ warnstring += ' - initial run'
85
+ } else {
86
+ warnstring += ' after ' + ((new Date() - EskomSePushInfo.area.lastUpdate) / 60000).toFixed(0) + ' minutes'
87
+ }
88
+ node.warn(warnstring)
89
+ }
72
90
  if (node.config.test) {
73
91
  options.test = 'current'
74
92
  }
75
93
  axios.get(url,
76
94
  { params: options, headers }).then(function (response) {
77
- Schedule = response.data
78
- Schedule.info.area = node.config.area
95
+ EskomSePushInfo.area.info = response.data
96
+ EskomSePushInfo.area.lastUpdate = new Date()
97
+ // Call updateSheddingStatus again now we have new data
79
98
  updateSheddingStatus(node)
80
99
  })
81
100
  .catch(error => {
@@ -86,138 +105,158 @@ module.exports = function (RED) {
86
105
  function updateSheddingStatus (node) {
87
106
  const now = new Date()
88
107
 
89
- if (EskomSePushAPI === null || (now.getTime() - lastStatusUpdate.getTime()) > 600000) {
108
+ // Check allowance every ten minutes
109
+ if (EskomSePushInfo.api.lastUpdate === null || (now.getTime() - EskomSePushInfo.api.lastUpdate.getTime()) > 600000) {
90
110
  checkAllowance(node)
91
- lastStatusUpdate = now
92
111
  }
93
112
 
94
- if (Schedule && Schedule.info.area !== node.config.area) {
95
- Schedule = null
113
+ // If we don't have API info, we just return
114
+ if (Object.entries(EskomSePushInfo.api.info).length === 0) {
115
+ node.warn('No API info (yet), refusing to continue')
116
+ return
96
117
  }
97
118
 
98
- if (EskomSePushAPI && EskomSePushAPI.allowance.count >= EskomSePushAPI.allowance.limit) {
99
- updateStatus(node)
119
+ // The same is true if we have no API calls left
120
+ if (EskomSePushInfo.api.info.allowance.count >= EskomSePushInfo.api.info.allowance.limit) {
121
+ node.warn('No API calls left, not checking status/schedule')
100
122
  return
101
123
  }
102
124
 
103
- if (Stage === null || (now.getTime() - lastStageUpdate.getTime()) > 3600000) {
125
+ // Fetching actual information takes 2 calls, so calculate how long the day lasts and see how we
126
+ // can divide the available calls over the day
127
+ if ((EskomSePushInfo.api.info.allowance.limit - EskomSePushInfo.api.info.allowance.count) > 0) {
128
+ EskomSePushInfo.calc.sleeptime = (getMinutesLeftInDay() / ((EskomSePushInfo.api.info.allowance.limit - EskomSePushInfo.api.info.allowance.count) / 2)).toFixed(0)
129
+ } else {
130
+ EskomSePushInfo.calc.sleeptime = 30
131
+ }
132
+
133
+ if (EskomSePushInfo.status.lastUpdate === null || (now.getTime() - EskomSePushInfo.status.lastUpdate) > (EskomSePushInfo.calc.sleeptime * 60000)) {
104
134
  checkStage(node)
105
- lastStageUpdate = now
106
135
  }
107
136
 
108
- if (Schedule === null || (now.getTime() - lastScheduleUpdate.getTime()) > 3600000) {
109
- checkSchedule(node)
110
- lastScheduleUpdate = now
137
+ if (EskomSePushInfo.area.lastUpdate === null || (now.getTime() - EskomSePushInfo.area.lastUpdate) > (EskomSePushInfo.calc.sleeptime * 60000)) {
138
+ checkArea(node)
111
139
  }
112
140
 
113
- if (Stage && Schedule && EskomSePushAPI) {
114
- const stage = Stage.status[node.config.statusselect].stage
115
- const nowtime = now.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit' })
116
- LoadShedding = {
117
- schedule: {
118
- next: {},
119
- active: false
120
- },
121
- event: {
122
- next: {},
123
- active: false
124
- },
125
- checked: nowtime
126
- }
127
- for (const schedule of Schedule.schedule.days[0].stages[stage - 1]) {
128
- if (nowtime >= schedule.split('-')[0] && nowtime <= schedule.split('-')[1]) {
129
- LoadShedding.schedule = {
130
- active: true
131
- }
132
- if (schedule.split('-')[0] < schedule.split('-')[1]) {
133
- LoadShedding.schedule.next = {
134
- start: Date.parse(Schedule.schedule.days[0].date + ' ' + schedule.split('-')[0]),
135
- end: Date.parse(Schedule.schedule.days[0].date + ' ' + schedule.split('-')[1])
136
- }
137
- } else {
138
- LoadShedding.schedule.next = {
139
- start: Date.parse(Schedule.schedule.days[0].date + ' ' + schedule.split('-')[0]),
140
- end: Date.parse(Schedule.schedule.days[1].date + ' ' + schedule.split('-')[1])
141
- }
142
- }
141
+ // Now we have all info to continue. Just making sure that all update values are non null.
142
+ if (EskomSePushInfo.api.lastUpdate === null ||
143
+ EskomSePushInfo.status.lastUpdate === null ||
144
+ EskomSePushInfo.area.lastUpdate === null) {
145
+ (node.config.verbose === true) && node.warn('Not enough info to continue.')
146
+ return
147
+ }
148
+
149
+ // Determine the current stage
150
+ EskomSePushInfo.calc.stage = EskomSePushInfo.status.info.status[node.config.statusselect].stage
151
+
152
+ if (node.config.verbose === true) {
153
+ node.warn('API call status: ' + EskomSePushInfo.api.info.allowance.count + '/' + EskomSePushInfo.api.info.allowance.limit)
154
+ node.warn(EskomSePushInfo)
155
+ }
156
+
157
+ // Default to false, overrule of loadshedding is active
158
+ EskomSePushInfo.calc.active = false
159
+
160
+ // Are there any events going on?
161
+ if (Object.entries(EskomSePushInfo.area.info.events).length > 0) {
162
+ EskomSePushInfo.calc.type = 'event'
163
+ const EventStart = Date.parse(EskomSePushInfo.area.info.events[0].start)
164
+ const EventEnd = Date.parse(EskomSePushInfo.area.info.events[0].end)
165
+ if (now >= EventStart && now < EventEnd) {
166
+ EskomSePushInfo.calc.active = true
167
+ if (EskomSePushInfo.area.info.events[0].note.match(/Stage (\d+)/i)) {
168
+ EskomSePushInfo.calc.stage = EskomSePushInfo.area.info.events[0].note.match(/Stage (\d+)/i)[1]
143
169
  }
144
- if (nowtime < schedule.split('-')[0]) {
145
- if (schedule.split('-')[0] < schedule.split('-')[1]) {
146
- LoadShedding.schedule.next = {
147
- start: Date.parse(Schedule.schedule.days[0].date + ' ' + schedule.split('-')[0]),
148
- end: Date.parse(Schedule.schedule.days[0].date + ' ' + schedule.split('-')[1])
149
- }
170
+ } else {
171
+ EskomSePushInfo.calc.next = {
172
+ type: 'event',
173
+ start: EventStart,
174
+ end: EventEnd,
175
+ stage: EskomSePushInfo.area.info.events[0].note.match(/Stage (\d+)/i)[1]
176
+ }
177
+ }
178
+ }
179
+
180
+ // Scheduled downtime has the thing that the time is in locatime
181
+ // So not just like events, where they are in UTC with an offset
182
+ let BreakLoop = false
183
+ for (const dates of EskomSePushInfo.area.info.schedule.days) {
184
+ for (const schedule of dates.stages[EskomSePushInfo.calc.stage]) {
185
+ const ScheduleStart = Date.parse(dates.date + ' ' + schedule.split('-')[0])
186
+ const ScheduleEnd = Date.parse(dates.date + ' ' + schedule.split('-')[1])
187
+ if (now < ScheduleEnd) {
188
+ BreakLoop = true
189
+ // This schedule is either active or will be next
190
+ if (now >= ScheduleStart) {
191
+ EskomSePushInfo.calc.active = true
192
+ EskomSePushInfo.calc.type = 'schedule'
193
+ EskomSePushInfo.calc.start = ScheduleStart
194
+ EskomSePushInfo.calc.end = ScheduleEnd
150
195
  } else {
151
- LoadShedding.schedule.next = {
152
- start: Date.parse(Schedule.schedule.days[0].date + ' ' + schedule.split('-')[0]),
153
- end: Date.parse(Schedule.schedule.days[1].date + ' ' + schedule.split('-')[1])
196
+ EskomSePushInfo.calc.next = {
197
+ type: 'schedule',
198
+ start: ScheduleStart,
199
+ end: ScheduleEnd
154
200
  }
155
201
  }
156
202
  }
203
+ if (BreakLoop) { break }
157
204
  }
158
- if (!LoadShedding.schedule.active && Object.keys(LoadShedding.schedule.next).length === 0) {
159
- const s = Schedule.schedule.days[1].stages[stage - 1][0]
160
- LoadShedding.schedule.next = {
161
- start: Date.parse(Schedule.schedule.days[1].date + ' ' + s.split('-')[0]),
162
- end: Date.parse(Schedule.schedule.days[1].date + ' ' + s.split('-')[1])
163
- }
164
- }
165
- LoadShedding.event.next = {
166
- start: Date.parse(Schedule.events[0].start),
167
- end: Date.parse(Schedule.events[0].end)
168
- }
169
- if (now >= LoadShedding.event.next.start && now < LoadShedding.event.next.end) {
170
- LoadShedding.event.active = true
171
- }
205
+ if (BreakLoop) { break }
206
+ }
172
207
 
173
- if (!LoadShedding.schedule | !LoadShedding.schedule.next ||
174
- !LoadShedding.event || !LoadShedding.event.next ||
175
- !LoadShedding.schedule.next.start || !LoadShedding.event.next.start) {
176
- node.warn('Unable to find next scheduled event and/or schedule')
177
- node.warn(LoadShedding)
178
- return
179
- }
180
- if (LoadShedding.schedule.next.start <= LoadShedding.event.next.start) {
181
- LoadShedding.next = LoadShedding.schedule.next
182
- LoadShedding.next.type = 'schedule'
183
- } else {
184
- LoadShedding.next = LoadShedding.event.next
185
- LoadShedding.next.type = 'event'
186
- }
208
+ if (EskomSePushInfo.calc.next) {
209
+ EskomSePushInfo.calc.next.duration = (EskomSePushInfo.calc.next.end - EskomSePushInfo.calc.next.start) / 1000
210
+ EskomSePushInfo.calc.next.islong = EskomSePushInfo.calc.next.duration >= (4 * 3600)
211
+ EskomSePushInfo.calc.secondstostatechange = parseInt((EskomSePushInfo.calc.next.start - now) / 1000)
212
+ }
187
213
 
188
- LoadShedding.next.duration = (LoadShedding.next.end - LoadShedding.next.start) / 1000
189
- LoadShedding.next.islong = LoadShedding.next.duration >= (4 * 3600)
214
+ if (EskomSePushInfo.calc.active) {
215
+ EskomSePushInfo.calc.duration = (EskomSePushInfo.calc.end - EskomSePushInfo.calc.start) / 1000
216
+ EskomSePushInfo.calc.islong = EskomSePushInfo.calc.duration >= (4 * 3600)
217
+ EskomSePushInfo.calc.secondstostatechange = parseInt((EskomSePushInfo.calc.end - now) / 1000)
218
+ }
190
219
 
191
- LoadShedding.active = (LoadShedding.schedule.active || LoadShedding.event.active)
192
- if (LoadShedding.schedule.active) {
193
- LoadShedding.type = 'schedule'
194
- LoadShedding.start = LoadShedding.schedule.next.start
195
- LoadShedding.end = LoadShedding.schedule.next.end
196
- }
197
- if (LoadShedding.event.active) {
198
- LoadShedding.type = 'event'
199
- LoadShedding.start = LoadShedding.event.next.start
200
- LoadShedding.end = LoadShedding.event.next.end
201
- }
220
+ if (node.config.verbose === true) {
221
+ node.warn(EskomSePushInfo.calc)
222
+ }
202
223
 
203
- node.send([{
204
- payload: LoadShedding.active,
205
- LoadShedding,
206
- stage,
207
- statusselect: node.config.statusselect,
208
- api: {
209
- count: EskomSePushAPI.allowance.count,
210
- limit: EskomSePushAPI.allowance.limit,
211
- lastStatusUpdate: lastStatusUpdate.toString(),
212
- lastScheduleUpdate: lastScheduleUpdate.toString()
213
- }
214
- }, {
215
- stage: Stage,
216
- schedule: Schedule
217
- }])
224
+ // Send output
225
+ node.send([{
226
+ payload: EskomSePushInfo.calc.active,
227
+ stage: EskomSePushInfo.calc.stage,
228
+ statusselect: node.config.statusselect,
229
+ api: {
230
+ count: EskomSePushInfo.api.info.allowance.count,
231
+ limit: EskomSePushInfo.api.info.allowance.limit
232
+ },
233
+ calc: EskomSePushInfo.calc
234
+ }, {
235
+ stage: EskomSePushInfo.status,
236
+ schedule: EskomSePushInfo.area
237
+ }])
238
+
239
+ // And update the status
240
+ let fill = 'green'
241
+ let shape = 'ring'
242
+ let statusText = 'Stage ' + EskomSePushInfo.calc.stage
243
+
244
+ if (EskomSePushInfo.calc.active) {
245
+ fill = 'yellow'
246
+ if (EskomSePushInfo.calc.type === 'event') {
247
+ shape = 'dot'
248
+ }
249
+ statusText += ' - ' + new Date(EskomSePushInfo.calc.start).toLocaleTimeString()
250
+ statusText += ' - ' + new Date(EskomSePushInfo.calc.end).toLocaleTimeString()
251
+ } else {
252
+ statusText += ' - ' + new Date(EskomSePushInfo.calc.next.start).toLocaleTimeString()
253
+ statusText += ' - ' + new Date(EskomSePushInfo.calc.next.end).toLocaleTimeString()
218
254
  }
219
255
 
220
- updateStatus(node)
256
+ statusText += ' (API: ' + EskomSePushInfo.api.info.allowance.count + '/' + EskomSePushInfo.api.info.allowance.limit + ')'
257
+ node.status({
258
+ fill, shape, text: statusText
259
+ })
221
260
  }
222
261
 
223
262
  function EskomSePush (config) {