node-red-contrib-eskomsepush 0.0.6 → 0.0.8

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
@@ -4,35 +4,59 @@
4
4
 
5
5
  A node for retrieving info from the [EskomSePush API](https://eskomsepush.gumroad.com/l/api).
6
6
 
7
- The EskomSePush-API node gives the tools to make working with the load shedding in South Africa as easy as possible.
7
+ The EskomSePush-API node makes it easier for South African users to incorporate the load shedding schedules into their flows.
8
8
 
9
- First you need to configure the node by entering the license key and entering the correct area.
9
+ ### Summary
10
+
11
+ The node must be configured by entering the license key and the correct area id (which needs to fetched from the API).
10
12
 
11
13
  Once deployed, the node will fetch the data from EskomSePush every hour. As every fetch from the API takes 2 calls, the 50 free queries per day on a free account should suffice. Every ten minutes the API status is checked to see how many queries you have left.
12
14
 
13
- Internally the node checks every minute if a schedule is currently active or not. It will also output a message on the first deployment.
15
+ Internally, the node checks every minute if a schedule is currently active or not. It will also output a message on the first deployment.
14
16
 
15
17
  ![EskomsePush Victron MinSOC](img/eskomsepush-victron-minsoc.png)
16
18
 
17
- The node has been made to work well together with the [@victronenergy/node-red-contrib-victron](https://flows.nodered.org/node/@victronenergy/node-red-contrib-victron) nodes. One of the main ones being the
18
- ESS control node for setting the MinSoc, based on the currently active stage. You can find the
19
- example flow for this via importing the [victron-minsoc-stage-based.json](examples/victron-minsoc-stage-based.json) example.
19
+ The node has been made to work well together with the [@victronenergy/node-red-contrib-victron](https://flows.nodered.org/node/@victronenergy/node-red-contrib-victron) nodes. The ESS control node for setting the Minimum State of Charge is an obvious combination to enable a user to change the MinSoC value based on the currently active loadshedding stage.
20
+
21
+ You can find the example flow for this via importing the [victron-minsoc-stage-based.json](examples/victron-minsoc-stage-based.json) file.
20
22
 
21
23
  ### Configuration
22
24
 
23
- First you will need a _licence key_. You can get one from [here](https://eskomsepush.gumroad.com/l/api), by subsribing to the Free model. Note that this is for personal use only.
25
+ First you will need a _licence key_. You can get one from [here](https://eskomsepush.gumroad.com/l/api), by subscribing to the Free model. Note that this is for personal use only.
24
26
 
25
27
  ![EskomsePush configuration](img/eskomsepush-configuration.png)
26
28
 
27
- Next you need to insert the correct area. Once you entered 5 characters (and a valid license is used), the API will be used for searching the correct area. 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 first and then the license key.
29
+ Next you need to insert the correct area id. This can be done by either:
30
+
31
+ * making a manual API call to search for the area and paste the id from the API response into the area field in the node; or
32
+ * by first entering a valid API license key into the node, and then entering at least 5 characters, the node will use the API to search for the area. Note that this will consume some of the daily API quota.
33
+
34
+ If you don't want to use API quota by searching, and you already know the id of the area, fill out the area first and then the license key.
28
35
 
29
- Then you need to fill out which status to follow. This can be either _National_ (eskom) or _Capetown_.
36
+ To fetch the area id manually, make an `areas_search` API call using your API license key `token`, a word of search `text`. In the response returned by the API, copy the `id` value of the matching area.
30
37
 
31
- If the _test_ checkbox has been selected, test data for the area will be fetched instead of the actual schedule. This is useful when debugging.
38
+ In the example below (on MacOS), `curl` is used to query the API and the search text value is 'ballito' (the license key token is invalid and must be replaced with a valid key). The area id value that will be used from this example is `eskmo-15-ballitokwadukuzakwazulunatal`:
39
+
40
+ ```
41
+ % curl --location --request GET 'https://developer.sepush.co.za/business/2.0/areas_search?text=ballito' --header 'token: 2DFB82AC-46254F6E-A68B26A4-8DF1303E'
42
+ {
43
+ "areas":[
44
+ {"id":"eskmo-15-ballitokwadukuzakwazulunatal","name":"Ballito (15)","region":"Eskom Municipal, Kwadukuza, Kwazulu-Natal"},
45
+ {"id":"eskdo-15-ballitokwadukuzakwazulunatal","name":"Ballito (15)","region":"Eskom Direct, KwaDukuza, KwaZulu-Natal"}
46
+ ]
47
+ }
48
+ ```
49
+
50
+ Then you need to fill out which status to follow. This can be either _National_ (Eskom) or _Cape Town_.
51
+
52
+ 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.
32
53
 
33
54
  ### Outputs
34
55
 
35
- The first output of the node outputs a boolean value. When load shedding is active, the `msg.payload` will be _true_, otherwise it will be _false_. It also outputs some extra values:
56
+ The note has two outputs. In most cases, the first (upper) output will be used.
57
+
58
+ The first output of the node outputs a boolean value and some related data. When load shedding is active, the `msg.payload` will be _true_, otherwise it will be _false_. It also outputs some extra values:
59
+
36
60
  ```
37
61
  {
38
62
  "payload":false,
@@ -72,7 +96,7 @@ The first output of the node outputs a boolean value. When load shedding is acti
72
96
  }
73
97
  ```
74
98
 
75
- The start and end objects contain the time as unix timestamp in the Javascript format (milliseconds after the epoch).
99
+ The start and end objects contain the time of next load shedding period as unix timestamps in the Javascript format (milliseconds after the epoch).
76
100
 
77
101
  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.
78
102
 
@@ -82,12 +106,12 @@ The status will show the situation regarding the API calls and when the next
82
106
  shedding wil start or end. It also shows the count of API calls that have been
83
107
  done and how many are left. This updates every 10 minutes.
84
108
 
85
-
86
109
  ### Documentation
87
110
 
88
111
  Documentation for the API can be found [here](https://documenter.getpostman.com/view/1296288/UzQuNk3E)
89
112
 
90
113
  When quota has been exceeded:
114
+
91
115
  ```
92
116
  {"error":"Quota Exceeded - Reminder: you can use the 'test' query param for development. Check the docs! \ud83d\ude05"}
93
117
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-eskomsepush",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "Node-RED interface for the Eskomsepush API",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -5,11 +5,40 @@ module.exports = function (RED) {
5
5
  let EskomSePushAPI = null
6
6
  let Stage = null
7
7
  let Schedule = null
8
+ let LoadShedding = null
8
9
  let lastStatusUpdate = new Date()
9
10
  let lastStageUpdate = new Date()
10
11
  let lastScheduleUpdate = new Date()
11
- let fill = 'green'
12
- let shape = 'ring'
12
+
13
+
14
+ function updateStatus(node) {
15
+ let fill = 'green'
16
+ let shape = 'ring'
17
+ let statusText = ''
18
+ if (Stage && Stage.status && Stage.status[node.config.statusselect].stage) {
19
+ statusText += 'Stage '+Stage.status[node.config.statusselect].stage
20
+ }
21
+ if (LoadShedding && LoadShedding.active && LoadShedding.next && LoadShedding.next.end) {
22
+ statusText += ' - ' + new Date(LoadShedding.next.end).toLocaleTimeString()
23
+ fill = 'red'
24
+ if (LoadShedding.type === 'event') {
25
+ shape = 'dot'
26
+ }
27
+ } else {
28
+ if (LoadShedding && LoadShedding.next && LoadShedding.next.start) {
29
+ statusText += ' - ' + new Date(LoadShedding.next.start).toLocaleTimeString()
30
+ }
31
+ }
32
+ if (EskomSePushAPI) {
33
+ statusText += ` (API: ${EskomSePushAPI.allowance.count}/${EskomSePushAPI.allowance.limit})`
34
+ if (EskomSePushAPI.allowance.count >= EskomSePushAPI.allowance.limit) {
35
+ fill = 'red'
36
+ }
37
+ }
38
+ node.status({
39
+ fill, shape, text: statusText
40
+ })
41
+ }
13
42
 
14
43
  function checkAllowance (node) {
15
44
  const options = {}
@@ -17,6 +46,7 @@ module.exports = function (RED) {
17
46
  axios.get('https://developer.sepush.co.za/business/2.0/api_allowance',
18
47
  { params: options, headers }).then(function (response) {
19
48
  EskomSePushAPI = response.data
49
+ updateStatus(node)
20
50
  })
21
51
  .catch(error => {
22
52
  node.warn({ error: error.message })
@@ -29,6 +59,7 @@ module.exports = function (RED) {
29
59
  axios.get('https://developer.sepush.co.za/business/2.0/status',
30
60
  { params: options, headers }).then(function (response) {
31
61
  Stage = response.data
62
+ updateSheddingStatus(node)
32
63
  })
33
64
  .catch(error => {
34
65
  node.warn({ error: error.message })
@@ -46,15 +77,15 @@ module.exports = function (RED) {
46
77
  { params: options, headers }).then(function (response) {
47
78
  Schedule = response.data
48
79
  Schedule.info.area = node.config.area
80
+ updateSheddingStatus(node)
49
81
  })
50
82
  .catch(error => {
51
83
  node.warn({ error: error.message })
52
84
  })
53
85
  }
54
86
 
55
- function updateStatus (node) {
87
+ function updateSheddingStatus (node) {
56
88
  const now = new Date()
57
- let statusText = ''
58
89
 
59
90
  if (EskomSePushAPI === null || (now.getTime() - lastStatusUpdate.getTime()) > 600000) {
60
91
  checkAllowance(node)
@@ -66,27 +97,24 @@ module.exports = function (RED) {
66
97
  }
67
98
 
68
99
  if (EskomSePushAPI && EskomSePushAPI.allowance.count >= EskomSePushAPI.allowance.limit) {
69
- statusText += 'API quota reached'
70
- fill = 'red'
71
- shape = 'dot'
72
- } else {
73
- if (Stage === null || (now.getTime() - lastStageUpdate.getTime()) > 3600000) {
74
- node.status({ fill: 'yellow', shape, text: 'Fetching stage' })
75
- checkStage(node)
76
- lastStageUpdate = now
77
- }
100
+ updateStatus(node)
101
+ return
102
+ }
78
103
 
79
- if (Schedule === null || (now.getTime() - lastScheduleUpdate.getTime()) > 3600000) {
80
- node.status({ fill: 'yellow', shape, text: 'Fetching schedule' })
81
- checkSchedule(node)
82
- lastScheduleUpdate = now
83
- }
104
+ if (Stage === null || (now.getTime() - lastStageUpdate.getTime()) > 3600000) {
105
+ checkStage(node)
106
+ lastStageUpdate = now
107
+ }
108
+
109
+ if (Schedule === null || (now.getTime() - lastScheduleUpdate.getTime()) > 3600000) {
110
+ checkSchedule(node)
111
+ lastScheduleUpdate = now
84
112
  }
85
113
 
86
114
  if (Stage && Schedule && EskomSePushAPI) {
87
115
  const stage = Stage.status[node.config.statusselect].stage
88
116
  const nowtime = now.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit' })
89
- const LoadShedding = {
117
+ LoadShedding = {
90
118
  schedule: {
91
119
  next: {},
92
120
  active: false
@@ -97,11 +125,12 @@ module.exports = function (RED) {
97
125
  },
98
126
  checked: nowtime
99
127
  }
100
- fill = 'green'
101
128
  for (const schedule of Schedule.schedule.days[0].stages[stage - 1]) {
102
129
  if (nowtime >= schedule.split('-')[0] && nowtime <= schedule.split('-')[1]) {
103
130
  LoadShedding.schedule = {
104
- active: true,
131
+ active: true
132
+ }
133
+ LoadShedding.schedule.next = {
105
134
  start: Date.parse(Schedule.schedule.days[0].date + ' ' + schedule.split('-')[0]),
106
135
  end: Date.parse(Schedule.schedule.days[0].date + ' ' + schedule.split('-')[1])
107
136
  }
@@ -113,7 +142,7 @@ module.exports = function (RED) {
113
142
  }
114
143
  }
115
144
  }
116
- if (Object.keys(LoadShedding.schedule.next).length === 0) {
145
+ if (!LoadShedding.schedule.active && Object.keys(LoadShedding.schedule.next).length === 0) {
117
146
  const s = Schedule.schedule.days[1].stages[stage - 1][0]
118
147
  LoadShedding.schedule.next = {
119
148
  start: Date.parse(Schedule.schedule.days[1].date + ' ' + s.split('-')[0]),
@@ -124,13 +153,13 @@ module.exports = function (RED) {
124
153
  start: Date.parse(Schedule.events[0].start),
125
154
  end: Date.parse(Schedule.events[0].end)
126
155
  }
127
- if (nowtime >= LoadShedding.event.start && nowtime < LoadShedding.event.end) {
156
+ if (now >= LoadShedding.event.next.start && now < LoadShedding.event.next.end) {
128
157
  LoadShedding.event.active = true
129
- LoadShedding.start = LoadShedding.event.start
130
- LoadShedding.end = LoadShedding.event.end
131
158
  }
132
159
 
133
- if (!LoadShedding.schedule.next.start || !LoadShedding.event.next.start) {
160
+ if (!LoadShedding.schedule | !LoadShedding.schedule.next ||
161
+ !LoadShedding.event || !LoadShedding.event.next ||
162
+ !LoadShedding.schedule.next.start || !LoadShedding.event.next.start) {
134
163
  node.warn('Unable to find next scheduled event and/or schedule')
135
164
  node.warn(LoadShedding)
136
165
  return
@@ -143,14 +172,18 @@ module.exports = function (RED) {
143
172
  LoadShedding.next.type = 'event'
144
173
  }
145
174
 
146
- statusText += 'Stage '+stage
147
175
  LoadShedding.active = (LoadShedding.schedule.active || LoadShedding.event.active)
148
- if (LoadShedding.active) {
149
- statusText += ' - ' + new Date(LoadShedding.next.end).toLocaleTimeString()
150
- fill = 'red'
151
- } else {
152
- statusText += ' - ' + new Date(LoadShedding.next.start).toLocaleTimeString()
176
+ if (LoadShedding.schedule.active) {
177
+ LoadShedding.type = 'schedule'
178
+ LoadShedding.start = LoadShedding.schedule.next.start
179
+ LoadShedding.end = LoadShedding.schedule.next.end
180
+ }
181
+ if (LoadShedding.event.active) {
182
+ LoadShedding.type = 'event'
183
+ LoadShedding.start = LoadShedding.event.next.start
184
+ LoadShedding.end = LoadShedding.event.next.end
153
185
  }
186
+
154
187
  node.send([{
155
188
  payload: LoadShedding.active,
156
189
  LoadShedding,
@@ -168,13 +201,7 @@ module.exports = function (RED) {
168
201
  }])
169
202
  }
170
203
 
171
- if (EskomSePushAPI) {
172
- statusText += ` (API: ${EskomSePushAPI.allowance.count}/${EskomSePushAPI.allowance.limit})`
173
- }
174
-
175
- node.status({
176
- fill, shape, text: (statusText || 'Ok')
177
- })
204
+ updateStatus(node)
178
205
  }
179
206
 
180
207
  function EskomSePush (config) {
@@ -183,9 +210,9 @@ module.exports = function (RED) {
183
210
  const node = this
184
211
  node.config = config
185
212
 
186
- updateStatus(node)
213
+ updateSheddingStatus(node)
187
214
  const intervalId = setInterval(function () {
188
- updateStatus(node)
215
+ updateSheddingStatus(node)
189
216
  }, 60000)
190
217
 
191
218
  node.on('close', function () {