node-red-contrib-eskomsepush 0.0.10 → 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.10",
3
+ "version": "0.0.11",
4
4
  "description": "Node-RED interface for the Eskomsepush API",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -2,45 +2,29 @@ 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
- }
40
- }
41
- node.status({
42
- fill, shape, text: statusText
43
- })
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
44
28
  }
45
29
 
46
30
  function checkAllowance (node) {
@@ -52,8 +36,12 @@ module.exports = function (RED) {
52
36
  }
53
37
  axios.get('https://developer.sepush.co.za/business/2.0/api_allowance',
54
38
  { params: options, headers }).then(function (response) {
55
- EskomSePushAPI = response.data
56
- 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()
57
45
  })
58
46
  .catch(error => {
59
47
  node.warn({ error: error.message })
@@ -65,11 +53,19 @@ module.exports = function (RED) {
65
53
  const headers = { token: node.config.licensekey }
66
54
 
67
55
  if (node.config.verbose === true) {
68
- node.warn('Running function checkStage')
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)
69
63
  }
70
64
  axios.get('https://developer.sepush.co.za/business/2.0/status',
71
65
  { params: options, headers }).then(function (response) {
72
- Stage = response.data
66
+ EskomSePushInfo.status.info = response.data
67
+ EskomSePushInfo.status.lastUpdate = new Date()
68
+ // Call updateSheddingStatus again now we have new data
73
69
  updateSheddingStatus(node)
74
70
  })
75
71
  .catch(error => {
@@ -77,21 +73,28 @@ module.exports = function (RED) {
77
73
  })
78
74
  }
79
75
 
80
- function checkSchedule (node) {
76
+ function checkArea (node) {
81
77
  const options = { id: node.config.area }
82
78
  const headers = { token: node.config.licensekey }
83
79
  const url = 'https://developer.sepush.co.za/business/2.0/area'
84
80
 
85
81
  if (node.config.verbose === true) {
86
- node.warn('Running function checkSchedule')
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)
87
89
  }
88
90
  if (node.config.test) {
89
91
  options.test = 'current'
90
92
  }
91
93
  axios.get(url,
92
94
  { params: options, headers }).then(function (response) {
93
- Schedule = response.data
94
- 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
95
98
  updateSheddingStatus(node)
96
99
  })
97
100
  .catch(error => {
@@ -102,153 +105,158 @@ module.exports = function (RED) {
102
105
  function updateSheddingStatus (node) {
103
106
  const now = new Date()
104
107
 
105
- if (node.config.verbose === true) {
106
- node.warn('Running function updateSheddingStatus')
107
- }
108
- 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) {
109
110
  checkAllowance(node)
110
- lastStatusUpdate = now
111
111
  }
112
112
 
113
- if (Schedule && Schedule.info.area !== node.config.area) {
114
- 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
115
117
  }
116
118
 
117
- if (EskomSePushAPI && EskomSePushAPI.allowance.count >= EskomSePushAPI.allowance.limit) {
118
- 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')
119
122
  return
120
123
  }
121
124
 
122
- 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)) {
123
134
  checkStage(node)
124
- lastStageUpdate = now
125
135
  }
126
136
 
127
- if (Schedule === null || (now.getTime() - lastScheduleUpdate.getTime()) > 3600000) {
128
- checkSchedule(node)
129
- lastScheduleUpdate = now
137
+ if (EskomSePushInfo.area.lastUpdate === null || (now.getTime() - EskomSePushInfo.area.lastUpdate) > (EskomSePushInfo.calc.sleeptime * 60000)) {
138
+ checkArea(node)
130
139
  }
131
140
 
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
- }
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
+ }
146
148
 
147
- if (!Array.isArray(Schedule.schedule.days[0].stages[stage])) {
148
- node.warn(`No schedule defined for stage ${stage}`)
149
- return
150
- }
149
+ // Determine the current stage
150
+ EskomSePushInfo.calc.stage = EskomSePushInfo.status.info.status[node.config.statusselect].stage
151
151
 
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
- }
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]
168
169
  }
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
- }
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
175
195
  } 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])
196
+ EskomSePushInfo.calc.next = {
197
+ type: 'schedule',
198
+ start: ScheduleStart,
199
+ end: ScheduleEnd
179
200
  }
180
201
  }
181
202
  }
203
+ if (BreakLoop) { break }
182
204
  }
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
- }
205
+ if (BreakLoop) { break }
206
+ }
203
207
 
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
- }
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
+ }
218
213
 
219
- LoadShedding.next.duration = (LoadShedding.next.end - LoadShedding.next.start) / 1000
220
- 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
+ }
221
219
 
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
227
- }
228
- if (LoadShedding.event.active) {
229
- LoadShedding.type = 'event'
230
- LoadShedding.start = LoadShedding.event.next.start
231
- LoadShedding.end = LoadShedding.event.next.end
232
- }
220
+ if (node.config.verbose === true) {
221
+ node.warn(EskomSePushInfo.calc)
222
+ }
233
223
 
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
- }])
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()
249
254
  }
250
255
 
251
- updateStatus(node)
256
+ statusText += ' (API: ' + EskomSePushInfo.api.info.allowance.count + '/' + EskomSePushInfo.api.info.allowance.limit + ')'
257
+ node.status({
258
+ fill, shape, text: statusText
259
+ })
252
260
  }
253
261
 
254
262
  function EskomSePush (config) {