node-red-contrib-eskomsepush 0.0.1 → 0.0.3

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  ![EskomsePush API](img/eskomsepush-flow.png)
4
4
 
5
- A node for retrieving info from the EskomSePush API.
5
+ A node for retrieving info from the [EskomSePush API](https://eskomsepush.gumroad.com/l/api).
6
6
 
7
7
  The EskomSePush-API node gives the tools to make working with the load shedding in South Africa as easy as possible.
8
8
 
@@ -12,10 +12,18 @@ Once deployed, the node will fetch the data from EskomSePush every hour. As ever
12
12
 
13
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.
14
14
 
15
+ ![EskomsePush Victron MinSOC](img/eskomsepush-victron-minsoc.png)
16
+
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 once 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.
20
+
15
21
  ### Configuration
16
22
 
17
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.
18
24
 
25
+ ![EskomsePush configuration](img/eskomsepush-configuration.png)
26
+
19
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.
20
28
 
21
29
  Then you need to fill out which status to follow. This can be either _National_ (eskom) or _Capetown_.
@@ -0,0 +1,362 @@
1
+ [
2
+ {
3
+ "id": "854cb9223eb1d95f",
4
+ "type": "eskomsepush",
5
+ "z": "614c1c00b0cf1810",
6
+ "name": "",
7
+ "licensekey": "",
8
+ "area": "capetown-3-helderbergvillage",
9
+ "statusselect": "capetown",
10
+ "test": false,
11
+ "x": 130,
12
+ "y": 660,
13
+ "wires": [
14
+ [
15
+ "685df7425884177e"
16
+ ],
17
+ []
18
+ ]
19
+ },
20
+ {
21
+ "id": "1304ce8952d56b0e",
22
+ "type": "debug",
23
+ "z": "614c1c00b0cf1810",
24
+ "name": "Scheduled charging",
25
+ "active": true,
26
+ "tosidebar": true,
27
+ "console": false,
28
+ "tostatus": false,
29
+ "complete": "true",
30
+ "targetType": "full",
31
+ "statusVal": "",
32
+ "statusType": "auto",
33
+ "x": 840,
34
+ "y": 540,
35
+ "wires": []
36
+ },
37
+ {
38
+ "id": "685df7425884177e",
39
+ "type": "switch",
40
+ "z": "614c1c00b0cf1810",
41
+ "name": "Stage",
42
+ "property": "stage",
43
+ "propertyType": "msg",
44
+ "rules": [
45
+ {
46
+ "t": "eq",
47
+ "v": "1",
48
+ "vt": "str"
49
+ },
50
+ {
51
+ "t": "eq",
52
+ "v": "2",
53
+ "vt": "str"
54
+ },
55
+ {
56
+ "t": "eq",
57
+ "v": "3",
58
+ "vt": "str"
59
+ },
60
+ {
61
+ "t": "eq",
62
+ "v": "4",
63
+ "vt": "str"
64
+ },
65
+ {
66
+ "t": "eq",
67
+ "v": "5",
68
+ "vt": "str"
69
+ },
70
+ {
71
+ "t": "eq",
72
+ "v": "6",
73
+ "vt": "str"
74
+ },
75
+ {
76
+ "t": "eq",
77
+ "v": "7",
78
+ "vt": "str"
79
+ },
80
+ {
81
+ "t": "eq",
82
+ "v": "8",
83
+ "vt": "str"
84
+ },
85
+ {
86
+ "t": "else"
87
+ }
88
+ ],
89
+ "checkall": "true",
90
+ "repair": false,
91
+ "outputs": 9,
92
+ "x": 310,
93
+ "y": 660,
94
+ "wires": [
95
+ [
96
+ "d52bbef6b535646e"
97
+ ],
98
+ [
99
+ "d52bbef6b535646e"
100
+ ],
101
+ [
102
+ "9a6d3dab537b880f"
103
+ ],
104
+ [
105
+ "f1c3df4e680f41a6"
106
+ ],
107
+ [
108
+ "02a4b5afeb44828f"
109
+ ],
110
+ [
111
+ "a4c5cec636c88ea4"
112
+ ],
113
+ [
114
+ "a5f3427bd40f7aa1"
115
+ ],
116
+ [
117
+ "a5f3427bd40f7aa1"
118
+ ],
119
+ [
120
+ "c80133f055cc326a"
121
+ ]
122
+ ],
123
+ "inputLabels": [
124
+ "EskomSePush API"
125
+ ],
126
+ "outputLabels": [
127
+ "stage 1",
128
+ "stage 2",
129
+ "stage 3",
130
+ "stage 4",
131
+ "stage 5",
132
+ "stage 6",
133
+ "stage 7",
134
+ "stage 8",
135
+ "otherwise"
136
+ ]
137
+ },
138
+ {
139
+ "id": "9a6d3dab537b880f",
140
+ "type": "change",
141
+ "z": "614c1c00b0cf1810",
142
+ "name": "60%",
143
+ "rules": [
144
+ {
145
+ "t": "set",
146
+ "p": "payload",
147
+ "pt": "msg",
148
+ "to": "60",
149
+ "tot": "num"
150
+ }
151
+ ],
152
+ "action": "",
153
+ "property": "",
154
+ "from": "",
155
+ "to": "",
156
+ "reg": false,
157
+ "x": 490,
158
+ "y": 580,
159
+ "wires": [
160
+ [
161
+ "c7e0a8b8221196b2"
162
+ ]
163
+ ]
164
+ },
165
+ {
166
+ "id": "02a4b5afeb44828f",
167
+ "type": "change",
168
+ "z": "614c1c00b0cf1810",
169
+ "name": "80%",
170
+ "rules": [
171
+ {
172
+ "t": "set",
173
+ "p": "payload",
174
+ "pt": "msg",
175
+ "to": "80",
176
+ "tot": "num"
177
+ }
178
+ ],
179
+ "action": "",
180
+ "property": "",
181
+ "from": "",
182
+ "to": "",
183
+ "reg": false,
184
+ "x": 490,
185
+ "y": 660,
186
+ "wires": [
187
+ [
188
+ "c7e0a8b8221196b2"
189
+ ]
190
+ ]
191
+ },
192
+ {
193
+ "id": "a5f3427bd40f7aa1",
194
+ "type": "change",
195
+ "z": "614c1c00b0cf1810",
196
+ "name": "100%",
197
+ "rules": [
198
+ {
199
+ "t": "set",
200
+ "p": "payload",
201
+ "pt": "msg",
202
+ "to": "100",
203
+ "tot": "num"
204
+ }
205
+ ],
206
+ "action": "",
207
+ "property": "",
208
+ "from": "",
209
+ "to": "",
210
+ "reg": false,
211
+ "x": 490,
212
+ "y": 740,
213
+ "wires": [
214
+ [
215
+ "c7e0a8b8221196b2"
216
+ ]
217
+ ]
218
+ },
219
+ {
220
+ "id": "d811aee571fa6d6b",
221
+ "type": "victron-output-settings",
222
+ "z": "614c1c00b0cf1810",
223
+ "service": "com.victronenergy.settings",
224
+ "path": "/Settings/CGwacs/BatteryLife/MinimumSocLimit",
225
+ "serviceObj": {
226
+ "service": "com.victronenergy.settings",
227
+ "name": "Venus settings"
228
+ },
229
+ "pathObj": {
230
+ "path": "/Settings/CGwacs/BatteryLife/MinimumSocLimit",
231
+ "type": "float",
232
+ "name": "ESS Minimum SoC (unless grid fails) (%)",
233
+ "writable": true
234
+ },
235
+ "name": "",
236
+ "onlyChanges": false,
237
+ "x": 910,
238
+ "y": 640,
239
+ "wires": []
240
+ },
241
+ {
242
+ "id": "a4c5cec636c88ea4",
243
+ "type": "change",
244
+ "z": "614c1c00b0cf1810",
245
+ "name": "90%",
246
+ "rules": [
247
+ {
248
+ "t": "set",
249
+ "p": "payload",
250
+ "pt": "msg",
251
+ "to": "90",
252
+ "tot": "num"
253
+ }
254
+ ],
255
+ "action": "",
256
+ "property": "",
257
+ "from": "",
258
+ "to": "",
259
+ "reg": false,
260
+ "x": 490,
261
+ "y": 700,
262
+ "wires": [
263
+ [
264
+ "c7e0a8b8221196b2"
265
+ ]
266
+ ]
267
+ },
268
+ {
269
+ "id": "f1c3df4e680f41a6",
270
+ "type": "change",
271
+ "z": "614c1c00b0cf1810",
272
+ "name": "70%",
273
+ "rules": [
274
+ {
275
+ "t": "set",
276
+ "p": "payload",
277
+ "pt": "msg",
278
+ "to": "70",
279
+ "tot": "num"
280
+ }
281
+ ],
282
+ "action": "",
283
+ "property": "",
284
+ "from": "",
285
+ "to": "",
286
+ "reg": false,
287
+ "x": 490,
288
+ "y": 620,
289
+ "wires": [
290
+ [
291
+ "c7e0a8b8221196b2"
292
+ ]
293
+ ]
294
+ },
295
+ {
296
+ "id": "d52bbef6b535646e",
297
+ "type": "change",
298
+ "z": "614c1c00b0cf1810",
299
+ "name": "50%",
300
+ "rules": [
301
+ {
302
+ "t": "set",
303
+ "p": "payload",
304
+ "pt": "msg",
305
+ "to": "50",
306
+ "tot": "num"
307
+ }
308
+ ],
309
+ "action": "",
310
+ "property": "",
311
+ "from": "",
312
+ "to": "",
313
+ "reg": false,
314
+ "x": 490,
315
+ "y": 540,
316
+ "wires": [
317
+ [
318
+ "c7e0a8b8221196b2"
319
+ ]
320
+ ]
321
+ },
322
+ {
323
+ "id": "c80133f055cc326a",
324
+ "type": "change",
325
+ "z": "614c1c00b0cf1810",
326
+ "name": "30%",
327
+ "rules": [
328
+ {
329
+ "t": "set",
330
+ "p": "payload",
331
+ "pt": "msg",
332
+ "to": "30",
333
+ "tot": "num"
334
+ }
335
+ ],
336
+ "action": "",
337
+ "property": "",
338
+ "from": "",
339
+ "to": "",
340
+ "reg": false,
341
+ "x": 490,
342
+ "y": 780,
343
+ "wires": [
344
+ [
345
+ "c7e0a8b8221196b2"
346
+ ]
347
+ ]
348
+ },
349
+ {
350
+ "id": "c7e0a8b8221196b2",
351
+ "type": "junction",
352
+ "z": "614c1c00b0cf1810",
353
+ "x": 640,
354
+ "y": 640,
355
+ "wires": [
356
+ [
357
+ "1304ce8952d56b0e",
358
+ "d811aee571fa6d6b"
359
+ ]
360
+ ]
361
+ }
362
+ ]
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-eskomsepush",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Node-RED interface for the Eskomsepush API",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -19,13 +19,13 @@
19
19
  "license": "MIT",
20
20
  "node-red": {
21
21
  "nodes": {
22
- "eskomepush": "./src/nodes/eskomsepush.js"
22
+ "eskomsepush": "./src/nodes/eskomsepush.js"
23
23
  },
24
24
  "version": ">=3.0.2"
25
25
  },
26
26
  "repository": {
27
27
  "type": "git",
28
- "url": "https://github.com/dirkjanfaber/node-red-contrib-eskomepush"
28
+ "url": "https://github.com/dirkjanfaber/node-red-contrib-eskomsepush"
29
29
  },
30
30
  "dependencies": {
31
31
  "axios": "^1.3.5"
@@ -166,7 +166,8 @@ This output is mainly useful when writing your own functions and logic.</p>
166
166
 
167
167
  <h3 id="status">Status</h3>
168
168
 
169
- <p>The status will show the situation regarding the API calls. It will show the count of API calls that have been done and
169
+ <p>The status will show the situation regarding the API calls and when the next shedding wil start or end.
170
+ It also shows the count of API calls that have been done and
170
171
  how many are left. This updates every 10 minutes.</p>
171
172
 
172
173
  <h1 id="documentation">Documentation</h1>
@@ -8,16 +8,15 @@ module.exports = function (RED) {
8
8
  let lastStatusUpdate = new Date()
9
9
  let lastStageUpdate = new Date()
10
10
  let lastScheduleUpdate = new Date()
11
- let LoadShedding = false
12
11
  let fill = 'green'
13
12
  let shape = 'ring'
14
-
13
+
15
14
  function checkAllowance (node) {
16
15
  const options = {}
17
16
  const headers = { token: node.config.licensekey }
18
17
  axios.get('https://developer.sepush.co.za/business/2.0/api_allowance',
19
18
  { params: options, headers }).then(function (response) {
20
- EskomSePushAPI = response.data
19
+ EskomSePushAPI = response.data
21
20
  })
22
21
  .catch(error => {
23
22
  node.warn({ error: error.message })
@@ -29,7 +28,7 @@ module.exports = function (RED) {
29
28
  const headers = { token: node.config.licensekey }
30
29
  axios.get('https://developer.sepush.co.za/business/2.0/status',
31
30
  { params: options, headers }).then(function (response) {
32
- Stage = response.data
31
+ Stage = response.data
33
32
  })
34
33
  .catch(error => {
35
34
  node.warn({ error: error.message })
@@ -39,22 +38,22 @@ module.exports = function (RED) {
39
38
  function checkSchedule (node) {
40
39
  const options = { id: node.config.area }
41
40
  const headers = { token: node.config.licensekey }
42
- let url = 'https://developer.sepush.co.za/business/2.0/area'
41
+ const url = 'https://developer.sepush.co.za/business/2.0/area'
43
42
  if (node.config.test) {
44
43
  options.test = 'current'
45
44
  }
46
45
  axios.get(url,
47
46
  { params: options, headers }).then(function (response) {
48
- Schedule = response.data
49
- Schedule.info.area = node.config.area
47
+ Schedule = response.data
48
+ Schedule.info.area = node.config.area
50
49
  })
51
50
  .catch(error => {
52
51
  node.warn({ error: error.message })
53
52
  })
54
53
  }
55
54
 
56
- function updateStatus(node) {
57
- const now = new Date();
55
+ function updateStatus (node) {
56
+ const now = new Date()
58
57
  let statusText = ''
59
58
 
60
59
  if (EskomSePushAPI === null || (now.getTime() - lastStatusUpdate.getTime()) > 600000) {
@@ -70,25 +69,24 @@ module.exports = function (RED) {
70
69
  statusText += 'API quota reached'
71
70
  fill = 'red'
72
71
  shape = 'dot'
73
-
74
72
  } else {
75
- if (Stage === null || (now.getTime() - lastStatusUpdate.getTime()) > 3600000) {
76
- node.status({fill: 'yellow', shape, text: 'Fetching status'})
73
+ if (Stage === null || (now.getTime() - lastStageUpdate.getTime()) > 3600000) {
74
+ node.status({ fill: 'yellow', shape, text: 'Fetching stage' })
77
75
  checkStage(node)
78
76
  lastStageUpdate = now
79
77
  }
80
78
 
81
79
  if (Schedule === null || (now.getTime() - lastScheduleUpdate.getTime()) > 3600000) {
82
- node.status({fill: 'yellow', shape, text: 'Fetching schedule'})
80
+ node.status({ fill: 'yellow', shape, text: 'Fetching schedule' })
83
81
  checkSchedule(node)
84
82
  lastScheduleUpdate = now
85
83
  }
86
- }
84
+ }
87
85
 
88
86
  if (Stage && Schedule && EskomSePushAPI) {
89
- let stage = Stage.status[node.config.statusselect].stage
90
- let nowtime = now.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit'})
91
- let LoadShedding = {
87
+ const stage = Stage.status[node.config.statusselect].stage
88
+ const nowtime = now.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit' })
89
+ const LoadShedding = {
92
90
  schedule: {
93
91
  next: {},
94
92
  active: false
@@ -100,15 +98,15 @@ module.exports = function (RED) {
100
98
  checked: nowtime
101
99
  }
102
100
  fill = 'green'
103
- for (let schedule of Schedule.schedule.days[0].stages[stage-1]) {
104
- if ( nowtime >= schedule.split('-')[0] && nowtime <= schedule.split('-')[1] ) {
101
+ for (const schedule of Schedule.schedule.days[0].stages[stage - 1]) {
102
+ if (nowtime >= schedule.split('-')[0] && nowtime <= schedule.split('-')[1]) {
105
103
  LoadShedding.schedule = {
106
104
  active: true,
107
105
  start: Date.parse(Schedule.schedule.days[0].date + ' ' + schedule.split('-')[0]),
108
106
  end: Date.parse(Schedule.schedule.days[0].date + ' ' + schedule.split('-')[1])
109
107
  }
110
108
  }
111
- if ( Object.keys(LoadShedding.schedule.next).length === 0 && nowtime < schedule.split('-')[0] ) {
109
+ if (Object.keys(LoadShedding.schedule.next).length === 0 && nowtime < schedule.split('-')[0]) {
112
110
  LoadShedding.schedule.next = {
113
111
  start: Date.parse(Schedule.schedule.days[0].date + ' ' + schedule.split('-')[0]),
114
112
  end: Date.parse(Schedule.schedule.days[0].date + ' ' + schedule.split('-')[1])
@@ -116,7 +114,7 @@ module.exports = function (RED) {
116
114
  }
117
115
  }
118
116
  if (Object.keys(LoadShedding.schedule.next).length === 0) {
119
- let s = Schedule.schedule.days[1].stages[stage-1][0]
117
+ const s = Schedule.schedule.days[1].stages[stage - 1][0]
120
118
  LoadShedding.schedule.next = {
121
119
  start: Date.parse(Schedule.sschedule.days[1].date + s.split('-')[0]),
122
120
  end: Date.parse(Schedule.sschedule.days[1].date + s.split('-')[1])
@@ -126,14 +124,14 @@ module.exports = function (RED) {
126
124
  start: Date.parse(Schedule.events[0].start),
127
125
  end: Date.parse(Schedule.events[0].end)
128
126
  }
129
- if ( nowtime >= LoadShedding.event.start && nowtime < LoadShedding.event.end ) {
127
+ if (nowtime >= LoadShedding.event.start && nowtime < LoadShedding.event.end) {
130
128
  LoadShedding.event.active = true
131
129
  LoadShedding.start = LoadShedding.event.start
132
130
  LoadShedding.end = LoadShedding.event.end
133
131
  }
134
132
 
135
133
  if (!LoadShedding.schedule.next.start || !LoadShedding.event.next.start) {
136
- node.warn("Unable to find next scheduled event and/or schedule")
134
+ node.warn('Unable to find next scheduled event and/or schedule')
137
135
  return
138
136
  }
139
137
  if (LoadShedding.schedule.next.start <= LoadShedding.event.next.start) {
@@ -144,14 +142,15 @@ module.exports = function (RED) {
144
142
  LoadShedding.next.type = 'event'
145
143
  }
146
144
 
145
+ statusText += 'Stage '+stage
147
146
  LoadShedding.active = (LoadShedding.schedule.active || LoadShedding.event.active)
148
147
  if (LoadShedding.active) {
149
- statusText += 'Until: '+ new Date(LoadShedding.end).toLocaleTimeString()
148
+ statusText += ' - ' + new Date(LoadShedding.end).toLocaleTimeString()
150
149
  fill = 'red'
151
150
  } else {
152
- statusText += 'Next: ' + new Date(LoadShedding.next.start).toLocaleTimeString()
151
+ statusText += ' - ' + new Date(LoadShedding.next.start).toLocaleTimeString()
153
152
  }
154
- node.send([{
153
+ node.send([{
155
154
  payload: LoadShedding.active,
156
155
  LoadShedding,
157
156
  stage,
@@ -164,7 +163,8 @@ module.exports = function (RED) {
164
163
  }
165
164
  }, {
166
165
  stage: Stage,
167
- schedule: Schedule }])
166
+ schedule: Schedule
167
+ }])
168
168
  }
169
169
 
170
170
  if (EskomSePushAPI) {
@@ -172,7 +172,7 @@ module.exports = function (RED) {
172
172
  }
173
173
 
174
174
  node.status({
175
- fill, shape, text: ( statusText || 'Ok' )
175
+ fill, shape, text: (statusText || 'Ok')
176
176
  })
177
177
  }
178
178