@thingwala/node-red-contrib-geyserwala-connect 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/.envrc +0 -0
- package/LICENSE +21 -0
- package/Makefile +20 -0
- package/README.md +25 -0
- package/examples/Basic_All_Nodes.json +606 -0
- package/package.json +31 -0
- package/src/nodes/geyserwala-api-mqtt.js +125 -0
- package/src/nodes/geyserwala-api-rest.js +227 -0
- package/src/nodes/geyserwala-api.html +137 -0
- package/src/nodes/geyserwala-api.js +31 -0
- package/src/nodes/geyserwala-nodes.html +60 -0
- package/src/nodes/geyserwala-nodes.js +62 -0
package/.envrc
ADDED
|
File without changes
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Thingwala
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/Makefile
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
setup:
|
|
2
|
+
node -v
|
|
3
|
+
npm -v
|
|
4
|
+
mkdir -p .node-red && \
|
|
5
|
+
cd .node-red && \
|
|
6
|
+
npm init -y && \
|
|
7
|
+
npm install node-red && \
|
|
8
|
+
npm install mqtt && \
|
|
9
|
+
cd node_modules && \
|
|
10
|
+
ln -sf ../.. node-red-contrib-geyserwala-connect
|
|
11
|
+
|
|
12
|
+
dev:
|
|
13
|
+
cd .node-red && \
|
|
14
|
+
npx node-red
|
|
15
|
+
|
|
16
|
+
login:
|
|
17
|
+
npm login
|
|
18
|
+
|
|
19
|
+
publish:
|
|
20
|
+
npm publish
|
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Geyserwala Connect - Node-RED Integration <!-- omit in toc -->
|
|
2
|
+
===
|
|
3
|
+
|
|
4
|
+
***Automate your Geyserwala Connect from Node-RED***
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
Install `node-red-contrib-geyserwala-connect` using [npm](https://www.npmjs.com/):
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install --save node-red-contrib-geyserwala-connect
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
Use these nodes to integrate with your Geyserwala Connect to automate your Geyserwise system from Node-RED.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Contribution
|
|
20
|
+
|
|
21
|
+
Yes please! We want our Geyserwala integration with Node-RED to be the best it can be for everyone. If you have Node-RED development experience or have just noticed a niggly bug, feel free to fork this repo and submit a pull request. Here are the [Node-RED docs](https://nodered.org/docs/).
|
|
22
|
+
|
|
23
|
+
# License
|
|
24
|
+
|
|
25
|
+
In the spirit of the Hackers of the [Tech Model Railroad Club](https://en.wikipedia.org/wiki/Tech_Model_Railroad_Club) from the [Massachusetts Institute of Technology](https://en.wikipedia.org/wiki/Massachusetts_Institute_of_Technology), who gave us all so very much to play with. The license is [MIT](./LICENSE).
|
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "fc2acf2da9d1682c",
|
|
4
|
+
"type": "tab",
|
|
5
|
+
"label": "Geyserwala Connect",
|
|
6
|
+
"disabled": false,
|
|
7
|
+
"info": "",
|
|
8
|
+
"env": []
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": "85b616590e17a12c",
|
|
12
|
+
"type": "geyserwala-connect-tank-temp",
|
|
13
|
+
"z": "fc2acf2da9d1682c",
|
|
14
|
+
"name": "Tank Temp",
|
|
15
|
+
"geyserwalaApi": "db6794008ea04dea",
|
|
16
|
+
"x": 380,
|
|
17
|
+
"y": 60,
|
|
18
|
+
"wires": [
|
|
19
|
+
[
|
|
20
|
+
"691616a2e1b3365d"
|
|
21
|
+
]
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "c5e8c4ce98d9e562",
|
|
26
|
+
"type": "geyserwala-connect-element-demand",
|
|
27
|
+
"z": "fc2acf2da9d1682c",
|
|
28
|
+
"name": "Element Demand",
|
|
29
|
+
"geyserwalaApi": "db6794008ea04dea",
|
|
30
|
+
"x": 360,
|
|
31
|
+
"y": 100,
|
|
32
|
+
"wires": [
|
|
33
|
+
[
|
|
34
|
+
"691616a2e1b3365d"
|
|
35
|
+
]
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "5b6755e03df7cf61",
|
|
40
|
+
"type": "geyserwala-connect-collector-temp",
|
|
41
|
+
"z": "fc2acf2da9d1682c",
|
|
42
|
+
"name": "Collector Temp",
|
|
43
|
+
"geyserwalaApi": "db6794008ea04dea",
|
|
44
|
+
"x": 360,
|
|
45
|
+
"y": 140,
|
|
46
|
+
"wires": [
|
|
47
|
+
[
|
|
48
|
+
"691616a2e1b3365d"
|
|
49
|
+
]
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "dee0237bdf6d3fe6",
|
|
54
|
+
"type": "geyserwala-connect-pump-status",
|
|
55
|
+
"z": "fc2acf2da9d1682c",
|
|
56
|
+
"name": "Pump Status",
|
|
57
|
+
"geyserwalaApi": "db6794008ea04dea",
|
|
58
|
+
"x": 370,
|
|
59
|
+
"y": 180,
|
|
60
|
+
"wires": [
|
|
61
|
+
[
|
|
62
|
+
"691616a2e1b3365d"
|
|
63
|
+
]
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "691616a2e1b3365d",
|
|
68
|
+
"type": "debug",
|
|
69
|
+
"z": "fc2acf2da9d1682c",
|
|
70
|
+
"name": "info",
|
|
71
|
+
"active": true,
|
|
72
|
+
"tosidebar": true,
|
|
73
|
+
"console": false,
|
|
74
|
+
"tostatus": false,
|
|
75
|
+
"complete": "payload",
|
|
76
|
+
"targetType": "msg",
|
|
77
|
+
"statusVal": "",
|
|
78
|
+
"statusType": "auto",
|
|
79
|
+
"x": 590,
|
|
80
|
+
"y": 120,
|
|
81
|
+
"wires": []
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"id": "b159a980e9ca5816",
|
|
85
|
+
"type": "geyserwala-connect-external-demand",
|
|
86
|
+
"z": "fc2acf2da9d1682c",
|
|
87
|
+
"name": "External Demand",
|
|
88
|
+
"geyserwalaApi": "db6794008ea04dea",
|
|
89
|
+
"x": 350,
|
|
90
|
+
"y": 340,
|
|
91
|
+
"wires": [
|
|
92
|
+
[
|
|
93
|
+
"cf52f2f9a6713b72"
|
|
94
|
+
]
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"id": "4d7df3d6c919b539",
|
|
99
|
+
"type": "geyserwala-connect-external-setpoint",
|
|
100
|
+
"z": "fc2acf2da9d1682c",
|
|
101
|
+
"name": "External Setpoint",
|
|
102
|
+
"geyserwalaApi": "db6794008ea04dea",
|
|
103
|
+
"x": 350,
|
|
104
|
+
"y": 400,
|
|
105
|
+
"wires": [
|
|
106
|
+
[
|
|
107
|
+
"cf52f2f9a6713b72"
|
|
108
|
+
]
|
|
109
|
+
]
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"id": "b3c9c5848fbafd71",
|
|
113
|
+
"type": "geyserwala-connect-mode",
|
|
114
|
+
"z": "fc2acf2da9d1682c",
|
|
115
|
+
"name": "Mode",
|
|
116
|
+
"geyserwalaApi": "db6794008ea04dea",
|
|
117
|
+
"x": 390,
|
|
118
|
+
"y": 580,
|
|
119
|
+
"wires": [
|
|
120
|
+
[
|
|
121
|
+
"1d89d48c14b6c3b8"
|
|
122
|
+
]
|
|
123
|
+
]
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"id": "ca4f27161c7f81fa",
|
|
127
|
+
"type": "geyserwala-connect-setpoint",
|
|
128
|
+
"z": "fc2acf2da9d1682c",
|
|
129
|
+
"name": "Setpoint",
|
|
130
|
+
"geyserwalaApi": "db6794008ea04dea",
|
|
131
|
+
"x": 380,
|
|
132
|
+
"y": 640,
|
|
133
|
+
"wires": [
|
|
134
|
+
[
|
|
135
|
+
"1d89d48c14b6c3b8"
|
|
136
|
+
]
|
|
137
|
+
]
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"id": "94191a9675df61af",
|
|
141
|
+
"type": "geyserwala-connect-boost-demand",
|
|
142
|
+
"z": "fc2acf2da9d1682c",
|
|
143
|
+
"name": "Boost Demand",
|
|
144
|
+
"geyserwalaApi": "db6794008ea04dea",
|
|
145
|
+
"x": 360,
|
|
146
|
+
"y": 700,
|
|
147
|
+
"wires": [
|
|
148
|
+
[
|
|
149
|
+
"1d89d48c14b6c3b8"
|
|
150
|
+
]
|
|
151
|
+
]
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"id": "fcdc172bc333981b",
|
|
155
|
+
"type": "inject",
|
|
156
|
+
"z": "fc2acf2da9d1682c",
|
|
157
|
+
"name": "",
|
|
158
|
+
"props": [
|
|
159
|
+
{
|
|
160
|
+
"p": "payload"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"p": "topic",
|
|
164
|
+
"vt": "str"
|
|
165
|
+
}
|
|
166
|
+
],
|
|
167
|
+
"repeat": "",
|
|
168
|
+
"crontab": "",
|
|
169
|
+
"once": false,
|
|
170
|
+
"onceDelay": 0.1,
|
|
171
|
+
"topic": "",
|
|
172
|
+
"payload": "true",
|
|
173
|
+
"payloadType": "bool",
|
|
174
|
+
"x": 110,
|
|
175
|
+
"y": 220,
|
|
176
|
+
"wires": [
|
|
177
|
+
[
|
|
178
|
+
"fba9a6ea6ed3f0cb"
|
|
179
|
+
]
|
|
180
|
+
]
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"id": "7555b33894ed93a8",
|
|
184
|
+
"type": "inject",
|
|
185
|
+
"z": "fc2acf2da9d1682c",
|
|
186
|
+
"name": "",
|
|
187
|
+
"props": [
|
|
188
|
+
{
|
|
189
|
+
"p": "payload"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"p": "topic",
|
|
193
|
+
"vt": "str"
|
|
194
|
+
}
|
|
195
|
+
],
|
|
196
|
+
"repeat": "",
|
|
197
|
+
"crontab": "",
|
|
198
|
+
"once": false,
|
|
199
|
+
"onceDelay": 0.1,
|
|
200
|
+
"topic": "",
|
|
201
|
+
"payload": "false",
|
|
202
|
+
"payloadType": "bool",
|
|
203
|
+
"x": 110,
|
|
204
|
+
"y": 260,
|
|
205
|
+
"wires": [
|
|
206
|
+
[
|
|
207
|
+
"fba9a6ea6ed3f0cb"
|
|
208
|
+
]
|
|
209
|
+
]
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"id": "d25e9b6a0b51b3ff",
|
|
213
|
+
"type": "inject",
|
|
214
|
+
"z": "fc2acf2da9d1682c",
|
|
215
|
+
"name": "",
|
|
216
|
+
"props": [
|
|
217
|
+
{
|
|
218
|
+
"p": "payload"
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
"p": "topic",
|
|
222
|
+
"vt": "str"
|
|
223
|
+
}
|
|
224
|
+
],
|
|
225
|
+
"repeat": "",
|
|
226
|
+
"crontab": "",
|
|
227
|
+
"once": false,
|
|
228
|
+
"onceDelay": 0.1,
|
|
229
|
+
"topic": "",
|
|
230
|
+
"payload": "true",
|
|
231
|
+
"payloadType": "bool",
|
|
232
|
+
"x": 110,
|
|
233
|
+
"y": 320,
|
|
234
|
+
"wires": [
|
|
235
|
+
[
|
|
236
|
+
"b159a980e9ca5816"
|
|
237
|
+
]
|
|
238
|
+
]
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
"id": "1cec7d550f31e5e1",
|
|
242
|
+
"type": "inject",
|
|
243
|
+
"z": "fc2acf2da9d1682c",
|
|
244
|
+
"name": "",
|
|
245
|
+
"props": [
|
|
246
|
+
{
|
|
247
|
+
"p": "payload"
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"p": "topic",
|
|
251
|
+
"vt": "str"
|
|
252
|
+
}
|
|
253
|
+
],
|
|
254
|
+
"repeat": "",
|
|
255
|
+
"crontab": "",
|
|
256
|
+
"once": false,
|
|
257
|
+
"onceDelay": 0.1,
|
|
258
|
+
"topic": "",
|
|
259
|
+
"payload": "false",
|
|
260
|
+
"payloadType": "bool",
|
|
261
|
+
"x": 110,
|
|
262
|
+
"y": 360,
|
|
263
|
+
"wires": [
|
|
264
|
+
[
|
|
265
|
+
"b159a980e9ca5816"
|
|
266
|
+
]
|
|
267
|
+
]
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
"id": "e20fe185478f4007",
|
|
271
|
+
"type": "inject",
|
|
272
|
+
"z": "fc2acf2da9d1682c",
|
|
273
|
+
"name": "",
|
|
274
|
+
"props": [
|
|
275
|
+
{
|
|
276
|
+
"p": "payload"
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"p": "topic",
|
|
280
|
+
"vt": "str"
|
|
281
|
+
}
|
|
282
|
+
],
|
|
283
|
+
"repeat": "",
|
|
284
|
+
"crontab": "",
|
|
285
|
+
"once": false,
|
|
286
|
+
"onceDelay": 0.1,
|
|
287
|
+
"topic": "",
|
|
288
|
+
"payload": "35",
|
|
289
|
+
"payloadType": "num",
|
|
290
|
+
"x": 110,
|
|
291
|
+
"y": 420,
|
|
292
|
+
"wires": [
|
|
293
|
+
[
|
|
294
|
+
"4d7df3d6c919b539"
|
|
295
|
+
]
|
|
296
|
+
]
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
"id": "9b096ff0a428f394",
|
|
300
|
+
"type": "inject",
|
|
301
|
+
"z": "fc2acf2da9d1682c",
|
|
302
|
+
"name": "",
|
|
303
|
+
"props": [
|
|
304
|
+
{
|
|
305
|
+
"p": "payload"
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
"p": "topic",
|
|
309
|
+
"vt": "str"
|
|
310
|
+
}
|
|
311
|
+
],
|
|
312
|
+
"repeat": "",
|
|
313
|
+
"crontab": "",
|
|
314
|
+
"once": false,
|
|
315
|
+
"onceDelay": 0.1,
|
|
316
|
+
"topic": "",
|
|
317
|
+
"payload": "60",
|
|
318
|
+
"payloadType": "num",
|
|
319
|
+
"x": 110,
|
|
320
|
+
"y": 460,
|
|
321
|
+
"wires": [
|
|
322
|
+
[
|
|
323
|
+
"4d7df3d6c919b539"
|
|
324
|
+
]
|
|
325
|
+
]
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
"id": "b084411925aad05a",
|
|
329
|
+
"type": "inject",
|
|
330
|
+
"z": "fc2acf2da9d1682c",
|
|
331
|
+
"name": "",
|
|
332
|
+
"props": [
|
|
333
|
+
{
|
|
334
|
+
"p": "payload"
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
"p": "topic",
|
|
338
|
+
"vt": "str"
|
|
339
|
+
}
|
|
340
|
+
],
|
|
341
|
+
"repeat": "",
|
|
342
|
+
"crontab": "",
|
|
343
|
+
"once": false,
|
|
344
|
+
"onceDelay": 0.1,
|
|
345
|
+
"topic": "",
|
|
346
|
+
"payload": "SOLAR",
|
|
347
|
+
"payloadType": "str",
|
|
348
|
+
"x": 110,
|
|
349
|
+
"y": 520,
|
|
350
|
+
"wires": [
|
|
351
|
+
[
|
|
352
|
+
"b3c9c5848fbafd71"
|
|
353
|
+
]
|
|
354
|
+
]
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
"id": "f9c9c0f43fd44dae",
|
|
358
|
+
"type": "inject",
|
|
359
|
+
"z": "fc2acf2da9d1682c",
|
|
360
|
+
"name": "",
|
|
361
|
+
"props": [
|
|
362
|
+
{
|
|
363
|
+
"p": "payload"
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
"p": "topic",
|
|
367
|
+
"vt": "str"
|
|
368
|
+
}
|
|
369
|
+
],
|
|
370
|
+
"repeat": "",
|
|
371
|
+
"crontab": "",
|
|
372
|
+
"once": false,
|
|
373
|
+
"onceDelay": 0.1,
|
|
374
|
+
"topic": "",
|
|
375
|
+
"payload": "TIMER",
|
|
376
|
+
"payloadType": "str",
|
|
377
|
+
"x": 110,
|
|
378
|
+
"y": 560,
|
|
379
|
+
"wires": [
|
|
380
|
+
[
|
|
381
|
+
"b3c9c5848fbafd71"
|
|
382
|
+
]
|
|
383
|
+
]
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
"id": "cf52f2f9a6713b72",
|
|
387
|
+
"type": "debug",
|
|
388
|
+
"z": "fc2acf2da9d1682c",
|
|
389
|
+
"name": "automation",
|
|
390
|
+
"active": true,
|
|
391
|
+
"tosidebar": true,
|
|
392
|
+
"console": false,
|
|
393
|
+
"tostatus": false,
|
|
394
|
+
"complete": "payload",
|
|
395
|
+
"targetType": "msg",
|
|
396
|
+
"statusVal": "",
|
|
397
|
+
"statusType": "auto",
|
|
398
|
+
"x": 610,
|
|
399
|
+
"y": 280,
|
|
400
|
+
"wires": []
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
"id": "1d89d48c14b6c3b8",
|
|
404
|
+
"type": "debug",
|
|
405
|
+
"z": "fc2acf2da9d1682c",
|
|
406
|
+
"name": "manual",
|
|
407
|
+
"active": true,
|
|
408
|
+
"tosidebar": true,
|
|
409
|
+
"console": false,
|
|
410
|
+
"tostatus": false,
|
|
411
|
+
"complete": "payload",
|
|
412
|
+
"targetType": "msg",
|
|
413
|
+
"statusVal": "",
|
|
414
|
+
"statusType": "auto",
|
|
415
|
+
"x": 600,
|
|
416
|
+
"y": 640,
|
|
417
|
+
"wires": []
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
"id": "c19e8851fc0cbf79",
|
|
421
|
+
"type": "inject",
|
|
422
|
+
"z": "fc2acf2da9d1682c",
|
|
423
|
+
"name": "",
|
|
424
|
+
"props": [
|
|
425
|
+
{
|
|
426
|
+
"p": "payload"
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
"p": "topic",
|
|
430
|
+
"vt": "str"
|
|
431
|
+
}
|
|
432
|
+
],
|
|
433
|
+
"repeat": "",
|
|
434
|
+
"crontab": "",
|
|
435
|
+
"once": false,
|
|
436
|
+
"onceDelay": 0.1,
|
|
437
|
+
"topic": "",
|
|
438
|
+
"payload": "48",
|
|
439
|
+
"payloadType": "num",
|
|
440
|
+
"x": 110,
|
|
441
|
+
"y": 620,
|
|
442
|
+
"wires": [
|
|
443
|
+
[
|
|
444
|
+
"ca4f27161c7f81fa"
|
|
445
|
+
]
|
|
446
|
+
]
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
"id": "669942032c75c052",
|
|
450
|
+
"type": "inject",
|
|
451
|
+
"z": "fc2acf2da9d1682c",
|
|
452
|
+
"name": "",
|
|
453
|
+
"props": [
|
|
454
|
+
{
|
|
455
|
+
"p": "payload"
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
"p": "topic",
|
|
459
|
+
"vt": "str"
|
|
460
|
+
}
|
|
461
|
+
],
|
|
462
|
+
"repeat": "",
|
|
463
|
+
"crontab": "",
|
|
464
|
+
"once": false,
|
|
465
|
+
"onceDelay": 0.1,
|
|
466
|
+
"topic": "",
|
|
467
|
+
"payload": "55",
|
|
468
|
+
"payloadType": "num",
|
|
469
|
+
"x": 110,
|
|
470
|
+
"y": 660,
|
|
471
|
+
"wires": [
|
|
472
|
+
[
|
|
473
|
+
"ca4f27161c7f81fa"
|
|
474
|
+
]
|
|
475
|
+
]
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
"id": "2a1072fd3a5c4f1f",
|
|
479
|
+
"type": "inject",
|
|
480
|
+
"z": "fc2acf2da9d1682c",
|
|
481
|
+
"name": "",
|
|
482
|
+
"props": [
|
|
483
|
+
{
|
|
484
|
+
"p": "payload"
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
"p": "topic",
|
|
488
|
+
"vt": "str"
|
|
489
|
+
}
|
|
490
|
+
],
|
|
491
|
+
"repeat": "",
|
|
492
|
+
"crontab": "",
|
|
493
|
+
"once": false,
|
|
494
|
+
"onceDelay": 0.1,
|
|
495
|
+
"topic": "",
|
|
496
|
+
"payload": "true",
|
|
497
|
+
"payloadType": "bool",
|
|
498
|
+
"x": 110,
|
|
499
|
+
"y": 720,
|
|
500
|
+
"wires": [
|
|
501
|
+
[
|
|
502
|
+
"94191a9675df61af"
|
|
503
|
+
]
|
|
504
|
+
]
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
"id": "6ba37de220dc855b",
|
|
508
|
+
"type": "inject",
|
|
509
|
+
"z": "fc2acf2da9d1682c",
|
|
510
|
+
"name": "",
|
|
511
|
+
"props": [
|
|
512
|
+
{
|
|
513
|
+
"p": "payload"
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
"p": "topic",
|
|
517
|
+
"vt": "str"
|
|
518
|
+
}
|
|
519
|
+
],
|
|
520
|
+
"repeat": "",
|
|
521
|
+
"crontab": "",
|
|
522
|
+
"once": false,
|
|
523
|
+
"onceDelay": 0.1,
|
|
524
|
+
"topic": "",
|
|
525
|
+
"payload": "false",
|
|
526
|
+
"payloadType": "bool",
|
|
527
|
+
"x": 110,
|
|
528
|
+
"y": 760,
|
|
529
|
+
"wires": [
|
|
530
|
+
[
|
|
531
|
+
"94191a9675df61af"
|
|
532
|
+
]
|
|
533
|
+
]
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
"id": "70e5994a789dd73b",
|
|
537
|
+
"type": "geyserwala-connect-status",
|
|
538
|
+
"z": "fc2acf2da9d1682c",
|
|
539
|
+
"name": "Status",
|
|
540
|
+
"geyserwalaApi": "db6794008ea04dea",
|
|
541
|
+
"x": 390,
|
|
542
|
+
"y": 20,
|
|
543
|
+
"wires": [
|
|
544
|
+
[
|
|
545
|
+
"691616a2e1b3365d"
|
|
546
|
+
]
|
|
547
|
+
]
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
"id": "fba9a6ea6ed3f0cb",
|
|
551
|
+
"type": "geyserwala-connect-lowpower-enable",
|
|
552
|
+
"z": "fc2acf2da9d1682c",
|
|
553
|
+
"name": "Low Power Enable",
|
|
554
|
+
"geyserwalaApi": "db6794008ea04dea",
|
|
555
|
+
"x": 350,
|
|
556
|
+
"y": 260,
|
|
557
|
+
"wires": [
|
|
558
|
+
[
|
|
559
|
+
"cf52f2f9a6713b72"
|
|
560
|
+
]
|
|
561
|
+
]
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
"id": "db6794008ea04dea",
|
|
565
|
+
"type": "geyserwala-connect-api",
|
|
566
|
+
"name": "Geyserwala Connector",
|
|
567
|
+
"api": "MQTT",
|
|
568
|
+
"mqttBroker": "2f8be320790a3c93",
|
|
569
|
+
"mqttTopicTemplate": "geyserwala/%prefix%/%mac%",
|
|
570
|
+
"mqttTopicMac": "EFCDAB214365",
|
|
571
|
+
"mqttTopicIp": "",
|
|
572
|
+
"mqttTopicHostname": "",
|
|
573
|
+
"restHost": "192.168.1.55",
|
|
574
|
+
"restPort": "80",
|
|
575
|
+
"restUser": "admin",
|
|
576
|
+
"restPassword": "",
|
|
577
|
+
"restPollInterval": "3"
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
"id": "2f8be320790a3c93",
|
|
581
|
+
"type": "mqtt-broker",
|
|
582
|
+
"name": "MQTT Broker",
|
|
583
|
+
"broker": "192.168.1.5",
|
|
584
|
+
"port": "1883",
|
|
585
|
+
"clientid": "",
|
|
586
|
+
"autoConnect": true,
|
|
587
|
+
"usetls": false,
|
|
588
|
+
"protocolVersion": "4",
|
|
589
|
+
"keepalive": "60",
|
|
590
|
+
"cleansession": true,
|
|
591
|
+
"birthTopic": "",
|
|
592
|
+
"birthQos": "0",
|
|
593
|
+
"birthPayload": "",
|
|
594
|
+
"birthMsg": {},
|
|
595
|
+
"closeTopic": "",
|
|
596
|
+
"closeQos": "0",
|
|
597
|
+
"closePayload": "",
|
|
598
|
+
"closeMsg": {},
|
|
599
|
+
"willTopic": "",
|
|
600
|
+
"willQos": "0",
|
|
601
|
+
"willPayload": "",
|
|
602
|
+
"willMsg": {},
|
|
603
|
+
"userProps": "",
|
|
604
|
+
"sessionExpiry": ""
|
|
605
|
+
}
|
|
606
|
+
]
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@thingwala/node-red-contrib-geyserwala-connect",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"description": "Node-RED nodes for the Geyserwala Connect",
|
|
5
|
+
"author": "Thingwala",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://www.thingwala.com/geyserwala/connect",
|
|
8
|
+
"url" : "https://github.com/thingwala/node-red-contrib-geyserwala-connect/issues",
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"mqtt": "^4.3.7"
|
|
11
|
+
},
|
|
12
|
+
"node-red": {
|
|
13
|
+
"version": ">=3.0.2",
|
|
14
|
+
"nodes": {
|
|
15
|
+
"api": "src/nodes/geyserwala-api.js",
|
|
16
|
+
"nodes": "src/nodes/geyserwala-nodes.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"node-red",
|
|
21
|
+
"thingwala",
|
|
22
|
+
"geyserwala"
|
|
23
|
+
],
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/thingwala/node-red-contrib-geyserwala-connect"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=16.0.0"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
class GeyserwalaConnectorMqtt {
|
|
2
|
+
constructor(RED, broker, pubQos, retain, template, mac, ip, hostname) {
|
|
3
|
+
this.RED = RED
|
|
4
|
+
this.broker = broker
|
|
5
|
+
this.pubQos = pubQos
|
|
6
|
+
this.retain = retain
|
|
7
|
+
this.template = template.replace('%mac%', mac || 'MAC').replace('%ip%', ip || 'IP').replace('%hostname%', hostname || 'HOSTNAME')
|
|
8
|
+
this.subscriptions = {}
|
|
9
|
+
this.nodes = []
|
|
10
|
+
|
|
11
|
+
if (this.broker && !this.broker.client) {
|
|
12
|
+
this.broker.connect(() => {
|
|
13
|
+
this.setupBrokerEvents()
|
|
14
|
+
})
|
|
15
|
+
} else {
|
|
16
|
+
this.setupBrokerEvents();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
setupBrokerEvents() {
|
|
20
|
+
if (this.broker && this.broker.client) {
|
|
21
|
+
|
|
22
|
+
this.status({ fill: "green", shape: "dot", text: "connected" });
|
|
23
|
+
for (const topic in this.subscriptions) {
|
|
24
|
+
this.subscribeTopic(topic)
|
|
25
|
+
}
|
|
26
|
+
this.broker.client.on("message", (topic, payload) => {
|
|
27
|
+
try {
|
|
28
|
+
this.subscriptions[topic](payload)
|
|
29
|
+
} catch (error) {
|
|
30
|
+
this.RED.log.error(`{Geyserwala Connect} Handling subscription: ${error.message}`);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
this.broker.client.on("reconnect", () => {
|
|
35
|
+
this.status({ fill: "yellow", shape: "ring", text: "reconnecting" });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
this.broker.client.on("offline", () => {
|
|
39
|
+
this.status({ fill: "red", shape: "ring", text: "offline" });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
this.broker.client.on("close", () => {
|
|
43
|
+
this.status({ fill: "red", shape: "ring", text: "disconnected" });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
this.broker.client.on("error", (error) => {
|
|
47
|
+
this.status({ fill: "red", shape: "ring", text: "error" });
|
|
48
|
+
this.RED.log.error(`{Geyserwala Connect} MQTT error: ${error}`);
|
|
49
|
+
});
|
|
50
|
+
} else {
|
|
51
|
+
this.status({ fill: "red", shape: "ring", text: "no broker" });
|
|
52
|
+
this.RED.log.error(`{Geyserwala Connect} No MQTT broker configuration found!`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
subTopic(key) {
|
|
56
|
+
return this.template.replace('%prefix%', 'stat') + `/${key}`;
|
|
57
|
+
}
|
|
58
|
+
pubTopic(key) {
|
|
59
|
+
return this.template.replace('%prefix%', 'cmnd') + `/${key}`;
|
|
60
|
+
}
|
|
61
|
+
subscribeTopic(topic) {
|
|
62
|
+
this.broker.client.subscribe(topic, (err) => {
|
|
63
|
+
if (err) {
|
|
64
|
+
this.RED.log.error(`{Geyserwala Connect} Subscribing to topic: ${topic}: ${err}`);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
subscribe(key, type, onValue) {
|
|
69
|
+
let topic = this.subTopic(key)
|
|
70
|
+
this.subscriptions[topic] = (payload) => {
|
|
71
|
+
let msg = payload.toString()
|
|
72
|
+
let value
|
|
73
|
+
if (type === Number) {
|
|
74
|
+
value = parseInt(msg)
|
|
75
|
+
} else if (type === Boolean) {
|
|
76
|
+
value = msg == 'ON'
|
|
77
|
+
} else {
|
|
78
|
+
value = String(msg)
|
|
79
|
+
}
|
|
80
|
+
onValue(value)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (this.broker && this.broker.client) {
|
|
84
|
+
this.subscribeTopic(topic)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
unsubscribe(key) {
|
|
88
|
+
let topic = this.subTopic(key)
|
|
89
|
+
|
|
90
|
+
delete this.subscriptions[topic]
|
|
91
|
+
this.broker.client.unsubscribe(topic)
|
|
92
|
+
}
|
|
93
|
+
registerNode(node) {
|
|
94
|
+
this.nodes.push(node)
|
|
95
|
+
}
|
|
96
|
+
unregisterNode(node) {
|
|
97
|
+
this.nodes = this.nodes.filter((item) => {
|
|
98
|
+
return item !== node
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
status(blob) {
|
|
102
|
+
for (const node of this.nodes) {
|
|
103
|
+
node.status(blob)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
send(key, value) {
|
|
107
|
+
let payload
|
|
108
|
+
if (typeof value === 'boolean') {
|
|
109
|
+
payload = value ? "ON" : "OFF"
|
|
110
|
+
} else {
|
|
111
|
+
payload = String(value)
|
|
112
|
+
}
|
|
113
|
+
const options = {
|
|
114
|
+
qos: this.pubQos,
|
|
115
|
+
retain: this.retain,
|
|
116
|
+
};
|
|
117
|
+
this.broker.client.publish(this.pubTopic(key), payload, options, (err) => {
|
|
118
|
+
if (err) {
|
|
119
|
+
this.RED.log.error(`{Geyserwala Connect} Publishing to MQTT: ${err}`);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = GeyserwalaConnectorMqtt;
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
const http = require('http');
|
|
2
|
+
|
|
3
|
+
class GeyserwalaConnectorRest {
|
|
4
|
+
constructor(RED, host, port, user, password, pollInterval) {
|
|
5
|
+
this.RED = RED
|
|
6
|
+
this.host = host
|
|
7
|
+
this.port = port
|
|
8
|
+
this.user = user
|
|
9
|
+
this.password = password
|
|
10
|
+
this.req = null;
|
|
11
|
+
this.pollInterval = pollInterval
|
|
12
|
+
this.pollTimer = null;
|
|
13
|
+
|
|
14
|
+
this.subscriptions = {}
|
|
15
|
+
this.nodes = []
|
|
16
|
+
this.values = {}
|
|
17
|
+
this.backoff = 0
|
|
18
|
+
this.authToken = null;
|
|
19
|
+
|
|
20
|
+
this.startPollingWithBackoff();
|
|
21
|
+
}
|
|
22
|
+
subscribe(key, type, onValue) {
|
|
23
|
+
this.subscriptions[key] = onValue;
|
|
24
|
+
}
|
|
25
|
+
unsubscribe(key) {
|
|
26
|
+
delete this.subscriptions[key];
|
|
27
|
+
}
|
|
28
|
+
registerNode(node) {
|
|
29
|
+
this.nodes.push(node);
|
|
30
|
+
}
|
|
31
|
+
unregisterNode(node) {
|
|
32
|
+
this.nodes = this.nodes.filter((item) => {
|
|
33
|
+
return item !== node;
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
status(blob) {
|
|
37
|
+
for (const node of this.nodes) {
|
|
38
|
+
node.status(blob);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
statusOnline(msg) {
|
|
42
|
+
this.status({ fill: "green", shape: "dot", text: msg || "online" });
|
|
43
|
+
}
|
|
44
|
+
statusOffline(msg) {
|
|
45
|
+
this.status({ fill: "red", shape: "ring", text: msg || "offline" });
|
|
46
|
+
}
|
|
47
|
+
updateValues(blob) {
|
|
48
|
+
for (const key in blob) {
|
|
49
|
+
try {
|
|
50
|
+
if (this.values[key] != blob[key]) {
|
|
51
|
+
this.values[key] = blob[key];
|
|
52
|
+
this.subscriptions[key](blob[key]);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
console.debug('No subscription', key);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
startPolling(interval) {
|
|
61
|
+
if (this.pollTimer) {
|
|
62
|
+
clearTimeout(this.pollTimer);
|
|
63
|
+
}
|
|
64
|
+
this.pollTimer = setTimeout(
|
|
65
|
+
() => {
|
|
66
|
+
if (this.password && !this.authToken) {
|
|
67
|
+
this.auth();
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
this.poll();
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
interval * 1000
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
startPollingAfterSuccess() {
|
|
77
|
+
this.backoff = 0;
|
|
78
|
+
this.startPolling(this.pollInterval);
|
|
79
|
+
}
|
|
80
|
+
startPollingWithBackoff() {
|
|
81
|
+
this.backoff ++;
|
|
82
|
+
let interval = Math.min(this.pollInterval * this.backoff, 60);
|
|
83
|
+
this.startPolling(interval);
|
|
84
|
+
}
|
|
85
|
+
stopPolling() {
|
|
86
|
+
if (this.req) {
|
|
87
|
+
this.req.destroy();
|
|
88
|
+
this.req = null;
|
|
89
|
+
}
|
|
90
|
+
if (this.pollTimer) {
|
|
91
|
+
clearTimeout(this.pollTimer);
|
|
92
|
+
this.pollTimer = null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
auth() {
|
|
96
|
+
this.stopPolling()
|
|
97
|
+
let blob = {username: this.user, password: this.password}
|
|
98
|
+
this.request(
|
|
99
|
+
'POST',
|
|
100
|
+
'/api/session',
|
|
101
|
+
blob,
|
|
102
|
+
(status, blob) => {
|
|
103
|
+
if (status == 200 && blob.success) {
|
|
104
|
+
this.authToken = blob.token;
|
|
105
|
+
this.statusOnline('auth success');
|
|
106
|
+
this.startPollingAfterSuccess();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
this.statusOffline('auth failed');
|
|
110
|
+
this.startPollingWithBackoff();
|
|
111
|
+
},
|
|
112
|
+
(error) => {
|
|
113
|
+
this.RED.log.error(`{Geyserwala Connect} Authing: ${error.message}`);
|
|
114
|
+
this.statusOffline(error.message);
|
|
115
|
+
this.startPollingWithBackoff();
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
poll() {
|
|
120
|
+
this.stopPolling()
|
|
121
|
+
this.request(
|
|
122
|
+
'GET',
|
|
123
|
+
'/api/value?f=' + Object.keys(this.subscriptions).join(','),
|
|
124
|
+
undefined,
|
|
125
|
+
(status, blob) => {
|
|
126
|
+
if (status == 200) {
|
|
127
|
+
this.updateValues(blob);
|
|
128
|
+
this.statusOnline()
|
|
129
|
+
this.startPollingAfterSuccess();
|
|
130
|
+
return;
|
|
131
|
+
} else if (status == 401){
|
|
132
|
+
this.authToken = null;
|
|
133
|
+
this.statusOffline('access denied')
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
this.statusOffline()
|
|
137
|
+
}
|
|
138
|
+
this.startPollingWithBackoff();
|
|
139
|
+
},
|
|
140
|
+
(error) => {
|
|
141
|
+
// this.RED.log.error(`{Geyserwala Connect} Polling: ${error.message}`);
|
|
142
|
+
this.statusOffline(error.message);
|
|
143
|
+
this.startPollingWithBackoff();
|
|
144
|
+
}
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
send(key, value) {
|
|
148
|
+
this.stopPolling()
|
|
149
|
+
this.request('PATCH',
|
|
150
|
+
'/api/value',
|
|
151
|
+
{ [key]: value },
|
|
152
|
+
(status, blob) => {
|
|
153
|
+
if (status == 200) {
|
|
154
|
+
this.updateValues(blob)
|
|
155
|
+
this.statusOnline()
|
|
156
|
+
this.startPollingAfterSuccess();
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
this.statusOffline()
|
|
160
|
+
this.startPollingWithBackoff();
|
|
161
|
+
},
|
|
162
|
+
(error) => {
|
|
163
|
+
this.RED.log.error(`{Geyserwala Connect} Patching: ${error.message}`);
|
|
164
|
+
this.statusOffline(error.message);
|
|
165
|
+
this.startPollingWithBackoff();
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
request(method, path, blob, success, fail) {
|
|
170
|
+
const http = require('http');
|
|
171
|
+
|
|
172
|
+
let requestBody = ''
|
|
173
|
+
if (blob) {
|
|
174
|
+
requestBody = JSON.stringify(blob)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const options = {
|
|
178
|
+
hostname: this.host,
|
|
179
|
+
port: this.port,
|
|
180
|
+
path: path,
|
|
181
|
+
method: method,
|
|
182
|
+
headers: {
|
|
183
|
+
'Content-Type': 'application/json',
|
|
184
|
+
'Content-Length': requestBody.length
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
if (this.authToken) {
|
|
188
|
+
options.headers['Authorization'] = `Bearer ${this.authToken}`
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
this.req = http.request(options, (res) => {
|
|
192
|
+
let responseBody = [];
|
|
193
|
+
|
|
194
|
+
res.on('data', (chunk) => {
|
|
195
|
+
responseBody.push(chunk);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
res.on('end', () => {
|
|
199
|
+
try {
|
|
200
|
+
let blob = JSON.parse(Buffer.concat(responseBody).toString());
|
|
201
|
+
this.req = null
|
|
202
|
+
success(res.statusCode, blob)
|
|
203
|
+
}
|
|
204
|
+
catch(error) {
|
|
205
|
+
this.RED.log.error(`{Geyserwala Connect} Request failed: ${error.message}`)
|
|
206
|
+
fail(error);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
this.req.setTimeout(10000, () => {
|
|
211
|
+
this.req.abort();
|
|
212
|
+
fail({message: "Request timed out"});
|
|
213
|
+
});
|
|
214
|
+
this.req.on('abort', () => {
|
|
215
|
+
// console.debug('Request aborted.');
|
|
216
|
+
});
|
|
217
|
+
this.req.on('error', (error) => {
|
|
218
|
+
this.req = null;
|
|
219
|
+
fail(error);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
this.req.write(requestBody);
|
|
223
|
+
this.req.end();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
module.exports = GeyserwalaConnectorRest;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('geyserwala-connect-api', {
|
|
3
|
+
category: 'config',
|
|
4
|
+
paletteLabel: 'Geyserwala Connector',
|
|
5
|
+
defaults: {
|
|
6
|
+
name: { value: "Geyserwala", required: true },
|
|
7
|
+
|
|
8
|
+
api: { value: "MQTT", requred: true },
|
|
9
|
+
|
|
10
|
+
mqttBroker: { type: "mqtt-broker" },
|
|
11
|
+
mqttPubQos: { value: 0 },
|
|
12
|
+
mqttRetain: { value: false },
|
|
13
|
+
mqttTopicTemplate: { value: "geyserwala/%prefix%/%mac%" },
|
|
14
|
+
mqttTopicMac: { value: "" },
|
|
15
|
+
mqttTopicIp: { value: "" },
|
|
16
|
+
mqttTopicHostname: { value: "" },
|
|
17
|
+
|
|
18
|
+
restHost: { value: "" },
|
|
19
|
+
restPort: { value: 80 },
|
|
20
|
+
restUser: { value: "admin" },
|
|
21
|
+
restPassword: { value: "" },
|
|
22
|
+
restPollInterval: { value: 3 },
|
|
23
|
+
},
|
|
24
|
+
label: function () {
|
|
25
|
+
return this.name;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function showConfigFields(value) {
|
|
30
|
+
document.getElementById('config-api-mqtt').style.display = (value == 'MQTT' ? "" : "none");
|
|
31
|
+
document.getElementById('config-api-rest').style.display = (value == 'REST' ? "" : "none");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<script type="text/x-red" data-template-name="geyserwala-connect-api">
|
|
37
|
+
<div class="form-row">
|
|
38
|
+
<h4>Geyserwala Connect : API Connector</h4>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="form-row">
|
|
41
|
+
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
42
|
+
<input type="text" id="node-config-input-name" placeholder="Name">
|
|
43
|
+
</div>
|
|
44
|
+
<div class="form-row">
|
|
45
|
+
<label for="node-config-input-api"><i class="fa fa-link"></i> API</label>
|
|
46
|
+
<select id="node-config-input-api" style="width:20%;" onchange="showConfigFields(this.value)">
|
|
47
|
+
<option value="MQTT">MQTT</option>
|
|
48
|
+
<option value="REST">REST</option>
|
|
49
|
+
</select>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<fieldset id="config-api-mqtt">
|
|
53
|
+
<legend>MQTT Configuration</legend>
|
|
54
|
+
<div class="form-row">
|
|
55
|
+
<label for="node-config-input-mqttBroker"><i class="fa fa-server"></i> Broker</label>
|
|
56
|
+
<input type="text" id="node-config-input-mqttBroker">
|
|
57
|
+
</div>
|
|
58
|
+
<div class="form-row">
|
|
59
|
+
<!--
|
|
60
|
+
<div class="form-row-column">
|
|
61
|
+
<label for="node-config-input-mqttPubQos"><i class="fa fa-exclamation"></i> QoS</label>
|
|
62
|
+
<select id="node-config-input-mqttPubQos" style="width:30%;">
|
|
63
|
+
<option value="0">0</option>
|
|
64
|
+
<option value="1">1</option>
|
|
65
|
+
<option value="2">2</option>
|
|
66
|
+
</select>
|
|
67
|
+
</div>
|
|
68
|
+
<div class="form-row-column">
|
|
69
|
+
<label for="node-config-input-mqttRetain"><i class="fa fa-sticky-note-o"></i> Retain</label>
|
|
70
|
+
<select id="node-config-input-mqttRetain" style="width:30%;">
|
|
71
|
+
<option value="0">false</option>
|
|
72
|
+
<option value="1">true</option>
|
|
73
|
+
</select>
|
|
74
|
+
</div>
|
|
75
|
+
-->
|
|
76
|
+
</div>
|
|
77
|
+
<div style="border: 1px solid #ccc; border-radius: 5px; padding: 10px 5px 5px 10px">
|
|
78
|
+
<div class="form-row">
|
|
79
|
+
<label for="node-config-input-mqttTopicTemplate"><i class="fa fa-code"></i> Topic Template</label>
|
|
80
|
+
<input type="text" id="node-config-input-mqttTopicTemplate" placeholder="geyserwala/%prefix%/%mac%" pattern="^[a-zA-Z\d%\/]{1,80}?$">
|
|
81
|
+
</div>
|
|
82
|
+
<h4>Tokens</h4>
|
|
83
|
+
<p>These take the form of <b>%name%</b>, see <a href="https://github.com/thingwala/geyserwala-connect/blob/main/docs/MQTT.md" target="_blank">the docs</a>.
|
|
84
|
+
<div class="form-row">
|
|
85
|
+
<label for="node-config-input-mqttTopicMac"><i class="fa fa-ticket"></i> prefix</label>
|
|
86
|
+
<p>Required: will be either <b>stat</b> or <b>cmnd</b></p>.
|
|
87
|
+
</div>
|
|
88
|
+
<div class="form-row">
|
|
89
|
+
<label for="node-config-input-mqttTopicMac"><i class="fa fa-ticket"></i> mac</label>
|
|
90
|
+
<input type="text" id="node-config-input-mqttTopicMac" style="width:30%;" placeholder="EFCDAB214365" pattern="^[A-F\d]{12}?$">
|
|
91
|
+
</div>
|
|
92
|
+
<div class="form-row">
|
|
93
|
+
<label for="node-config-input-mqttTopicIp"><i class="fa fa-ticket"></i> ip</label>
|
|
94
|
+
<input type="text" id="node-config-input-mqttTopicIp" style="width:40%;" placeholder="192.168.1.5" pattern="^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$">
|
|
95
|
+
</div>
|
|
96
|
+
<div class="form-row">
|
|
97
|
+
<label for="node-config-input-mqttTopicHostname"><i class="fa fa-ticket"></i> hostname</label>
|
|
98
|
+
<input type="text" id="node-config-input-mqttTopicHostname" style="width:40%;" placeholder="geyserwala" pattern="^[A-F\d]{1,80}?$">
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
</fieldset>
|
|
103
|
+
|
|
104
|
+
<fieldset id="config-api-rest">
|
|
105
|
+
<legend>REST Configuration</legend>
|
|
106
|
+
|
|
107
|
+
<div class="form-row">
|
|
108
|
+
<label for="node-config-input-restHost"><i class="fa fa-server"></i> Host</label>
|
|
109
|
+
<input type="text" id="node-config-input-restHost" placeholder="192.168.1.5">
|
|
110
|
+
</div>
|
|
111
|
+
<div class="form-row">
|
|
112
|
+
<label for="node-config-input-restPort"><i class="fa fa-dot-circle-o"></i> Port</label>
|
|
113
|
+
<input type="number" id="node-config-input-restPort" style="width:15%;" placeholder="80" min=1 max=65535>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="form-row">
|
|
116
|
+
<label for="node-config-input-restUser"><i class="fa fa-user"></i> User</label>
|
|
117
|
+
<input type="text" id="node-config-input-restUser" style="width:40%;" placeholder="admin">
|
|
118
|
+
</div>
|
|
119
|
+
<div class="form-row">
|
|
120
|
+
<label for="node-config-input-restPassword"><i class="fa fa-key"></i> Password</label>
|
|
121
|
+
<input type="password" id="node-config-input-restPassword" style="width:40%;" placeholder="">
|
|
122
|
+
</div>
|
|
123
|
+
<div class="form-row">
|
|
124
|
+
<label for="node-config-input-restPollInterval"><i class="fa fa-hourglass-o"></i> Poll Interval</label>
|
|
125
|
+
<input type="number" id="node-config-input-restPollInterval" style="width:15%;" placeholder="3" min=1 max=300><span> seconds</span>
|
|
126
|
+
</div>
|
|
127
|
+
</fieldset>
|
|
128
|
+
<style>
|
|
129
|
+
.form-row {
|
|
130
|
+
display: flex;
|
|
131
|
+
}
|
|
132
|
+
.form-row-column {
|
|
133
|
+
flex: 1;
|
|
134
|
+
margin-right: 10px;
|
|
135
|
+
}
|
|
136
|
+
</style>
|
|
137
|
+
</script>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
|
|
2
|
+
module.exports = function (RED) {
|
|
3
|
+
function Node(config) {
|
|
4
|
+
RED.nodes.createNode(this, config);
|
|
5
|
+
|
|
6
|
+
if (config.api == 'MQTT') {
|
|
7
|
+
const GeyserwalaConnectorMqtt = require('./geyserwala-api-mqtt');
|
|
8
|
+
|
|
9
|
+
this.api = new GeyserwalaConnectorMqtt(RED,
|
|
10
|
+
RED.nodes.getNode(config.mqttBroker),
|
|
11
|
+
config.mqttPubQos,
|
|
12
|
+
config.mqttRetain,
|
|
13
|
+
config.mqttTopicTemplate,
|
|
14
|
+
config.mqttTopicMac,
|
|
15
|
+
config.mqttTopicIP,
|
|
16
|
+
config.mqttTopicHostname
|
|
17
|
+
);
|
|
18
|
+
} else if (config.api == "REST") {
|
|
19
|
+
const GeyserwalaConnectorRest = require('./geyserwala-api-rest');
|
|
20
|
+
|
|
21
|
+
this.api = new GeyserwalaConnectorRest(RED,
|
|
22
|
+
config.restHost,
|
|
23
|
+
config.restPort,
|
|
24
|
+
config.restUser,
|
|
25
|
+
config.restPassword,
|
|
26
|
+
config.restPollInterval,
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
RED.nodes.registerType("geyserwala-connect-api", Node);
|
|
31
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script type="text/template" id="node-input-template">
|
|
2
|
+
<div class="form-row">
|
|
3
|
+
<h4 id="title"></h4>
|
|
4
|
+
</div>
|
|
5
|
+
<div class="form-row">
|
|
6
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
7
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
|
8
|
+
</div>
|
|
9
|
+
<div class="form-row">
|
|
10
|
+
<label for="node-input-geyserwalaApi"><i class="fa fa-link"></i> Geyserwala</label>
|
|
11
|
+
<input type="text" id="node-input-geyserwalaApi">
|
|
12
|
+
</div>
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<script type="text/javascript">
|
|
16
|
+
function addInputDialog(valueKey, nodeLabel) {
|
|
17
|
+
var nodeInput = $("#node-input-template").clone().html();
|
|
18
|
+
|
|
19
|
+
var scriptTag = $("<script>").attr("type", "text/x-red").attr("data-template-name", `geyserwala-connect-${valueKey}`).append(nodeInput);
|
|
20
|
+
scriptTag.find('#title').text(`Geyserwala Connect : ${nodeLabel}`);
|
|
21
|
+
$('body').append(scriptTag.prop('outerHTML'));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function registerType(valueKey, nodeLabel, icon, input) {
|
|
25
|
+
addInputDialog(valueKey, nodeLabel)
|
|
26
|
+
|
|
27
|
+
RED.nodes.registerType(`geyserwala-connect-${valueKey}`, {
|
|
28
|
+
category: 'Geyserwala Connect',
|
|
29
|
+
paletteLabel: nodeLabel,
|
|
30
|
+
defaults: {
|
|
31
|
+
name: { value: nodeLabel },
|
|
32
|
+
geyserwalaApi: { type: "geyserwala-connect-api", required: true },
|
|
33
|
+
},
|
|
34
|
+
inputs: input ? 1 : 0,
|
|
35
|
+
outputs: 1,
|
|
36
|
+
color: "#2AAED7",
|
|
37
|
+
icon: icon,
|
|
38
|
+
label: function () {
|
|
39
|
+
return this.name;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Icons: https://fontawesome.com/v4/icons/
|
|
45
|
+
registerType('tank-temp', "Tank Temp", "font-awesome/fa-thermometer-half", false);
|
|
46
|
+
registerType('element-demand', "Element Demand", "font-awesome/fa-fire", false);
|
|
47
|
+
|
|
48
|
+
registerType('lowpower-enable', "Low Power Enable", "font-awesome/fa-stop-circle-o", true);
|
|
49
|
+
registerType('external-demand', "External Demand", "serial.png", true);
|
|
50
|
+
registerType('external-setpoint', "External Setpoint", "inject.png", true);
|
|
51
|
+
|
|
52
|
+
registerType('status', "Status", "font-awesome/fa-envelope-o", true);
|
|
53
|
+
registerType('mode', "Mode", "font-awesome/fa-info", true);
|
|
54
|
+
registerType('setpoint', "Setpoint", "inject.png", true);
|
|
55
|
+
registerType('boost-demand', "Boost Demand", "font-awesome/fa-power-off", true);
|
|
56
|
+
|
|
57
|
+
registerType('collector-temp', "Collector Temp", "font-awesome/fa-thermometer-half", false);
|
|
58
|
+
registerType('pump-status', "Pump Status", "font-awesome/fa-recycle", false);
|
|
59
|
+
|
|
60
|
+
</script>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module.exports = function (RED) {
|
|
2
|
+
function createNode (valueKey, type, input) {
|
|
3
|
+
function Node(config) {
|
|
4
|
+
RED.nodes.createNode(this, config);
|
|
5
|
+
|
|
6
|
+
var geyserwalaApi = RED.nodes.getNode(config.geyserwalaApi)
|
|
7
|
+
if (!geyserwalaApi) {
|
|
8
|
+
this.error('Connector not configured')
|
|
9
|
+
return
|
|
10
|
+
}
|
|
11
|
+
this.api = geyserwalaApi.api;
|
|
12
|
+
if (!geyserwalaApi.api) {
|
|
13
|
+
this.error('Invalid Connector')
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
this.api.registerNode(this)
|
|
18
|
+
|
|
19
|
+
this.api.subscribe(valueKey, type, (payload) => {
|
|
20
|
+
try {
|
|
21
|
+
this.send({ topic: valueKey, payload: payload })
|
|
22
|
+
} catch (error) {
|
|
23
|
+
RED.log.error(`{Geyserwala Connect} Outputing: ${error.message}`);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
this.on('close', () => {
|
|
27
|
+
this.api.unsubscribe(valueKey);
|
|
28
|
+
this.api.unregister(this);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (input) {
|
|
32
|
+
this.on('input', function (message) {
|
|
33
|
+
try {
|
|
34
|
+
this.api.send(valueKey, message.payload)
|
|
35
|
+
} catch (error) {
|
|
36
|
+
RED.log.error(`{Geyserwala Connect} Sending: ${error.message}`);
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
this.on('close', function () {
|
|
41
|
+
this.api.unregister(this);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
RED.nodes.registerType(`geyserwala-connect-${valueKey}`, Node);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
createNode('tank-temp', Number, false);
|
|
49
|
+
createNode('element-demand', Boolean, false);
|
|
50
|
+
|
|
51
|
+
createNode('lowpower-enable', Boolean, true);
|
|
52
|
+
createNode('external-demand', Boolean, true);
|
|
53
|
+
createNode('external-setpoint', Number, true);
|
|
54
|
+
|
|
55
|
+
createNode('status', String, false);
|
|
56
|
+
createNode('mode', String, true);
|
|
57
|
+
createNode('setpoint', Number, true);
|
|
58
|
+
createNode('boost-demand', Boolean, true);
|
|
59
|
+
|
|
60
|
+
createNode('collector-temp', Number, false);
|
|
61
|
+
createNode('pump-status', Boolean, false);
|
|
62
|
+
}
|