nodejs-poolcontroller 7.5.1 → 7.7.0
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/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -0
- package/.github/ISSUE_TEMPLATE/2-docs.md +12 -0
- package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/Changelog +19 -0
- package/Dockerfile +3 -3
- package/README.md +13 -8
- package/app.ts +1 -1
- package/config/Config.ts +38 -2
- package/config/VersionCheck.ts +27 -12
- package/controller/Constants.ts +2 -1
- package/controller/Equipment.ts +193 -9
- package/controller/Errors.ts +10 -0
- package/controller/Lockouts.ts +503 -0
- package/controller/State.ts +269 -64
- package/controller/boards/AquaLinkBoard.ts +1000 -0
- package/controller/boards/BoardFactory.ts +4 -0
- package/controller/boards/EasyTouchBoard.ts +468 -144
- package/controller/boards/IntelliCenterBoard.ts +466 -307
- package/controller/boards/IntelliTouchBoard.ts +37 -5
- package/controller/boards/NixieBoard.ts +671 -141
- package/controller/boards/SystemBoard.ts +1397 -641
- package/controller/comms/Comms.ts +462 -362
- package/controller/comms/messages/Messages.ts +174 -30
- package/controller/comms/messages/config/ChlorinatorMessage.ts +6 -3
- package/controller/comms/messages/config/CircuitMessage.ts +1 -0
- package/controller/comms/messages/config/ExternalMessage.ts +10 -8
- package/controller/comms/messages/config/HeaterMessage.ts +141 -29
- package/controller/comms/messages/config/OptionsMessage.ts +9 -2
- package/controller/comms/messages/config/PumpMessage.ts +53 -35
- package/controller/comms/messages/config/ScheduleMessage.ts +33 -25
- package/controller/comms/messages/config/ValveMessage.ts +2 -2
- package/controller/comms/messages/status/ChlorinatorStateMessage.ts +38 -86
- package/controller/comms/messages/status/EquipmentStateMessage.ts +59 -23
- package/controller/comms/messages/status/HeaterStateMessage.ts +57 -3
- package/controller/comms/messages/status/IntelliChemStateMessage.ts +56 -8
- package/controller/comms/messages/status/PumpStateMessage.ts +23 -1
- package/controller/nixie/Nixie.ts +1 -1
- package/controller/nixie/bodies/Body.ts +3 -0
- package/controller/nixie/chemistry/ChemController.ts +164 -51
- package/controller/nixie/chemistry/Chlorinator.ts +137 -88
- package/controller/nixie/circuits/Circuit.ts +51 -19
- package/controller/nixie/heaters/Heater.ts +241 -31
- package/controller/nixie/pumps/Pump.ts +488 -206
- package/controller/nixie/schedules/Schedule.ts +91 -35
- package/controller/nixie/valves/Valve.ts +1 -1
- package/defaultConfig.json +20 -0
- package/package.json +21 -21
- package/web/Server.ts +94 -49
- package/web/bindings/aqualinkD.json +505 -0
- package/web/bindings/influxDB.json +71 -1
- package/web/bindings/mqtt.json +98 -39
- package/web/bindings/mqttAlt.json +59 -1
- package/web/interfaces/baseInterface.ts +1 -0
- package/web/interfaces/httpInterface.ts +23 -2
- package/web/interfaces/influxInterface.ts +45 -10
- package/web/interfaces/mqttInterface.ts +114 -54
- package/web/services/config/Config.ts +55 -132
- package/web/services/state/State.ts +81 -4
- package/web/services/state/StateSocket.ts +4 -4
- package/web/services/utilities/Utilities.ts +8 -6
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -52
- package/config copy.json +0 -300
- package/issue_template.md +0 -52
package/web/bindings/mqtt.json
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
],
|
|
35
|
-
"rootTopic-DIRECTIONS": "You can override the root topic by renaming _rootTopic to rootTopic",
|
|
36
|
-
"_rootTopic": "@bind=(state.equipment.alias).replace(' ','-').replace('/','').toLowerCase();",
|
|
37
|
-
"clientId": "@bind='mqttjs_njsPC_'+Math.random().toString(16).substr(2, 8);"
|
|
2
|
+
"context": {
|
|
3
|
+
"name": "MQTT",
|
|
4
|
+
"options": {
|
|
5
|
+
"formatter": [
|
|
6
|
+
{
|
|
7
|
+
"transform": ".toLowerCase()"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"regexkey": "\\s",
|
|
11
|
+
"replace": "",
|
|
12
|
+
"description": "Remove whitespace"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"regexkey": "\\/",
|
|
16
|
+
"replace": "",
|
|
17
|
+
"description": "Remove /"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"regexkey": "\\+",
|
|
21
|
+
"replace": "",
|
|
22
|
+
"description": "Remove +"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"regexkey": "\\$",
|
|
26
|
+
"replace": "",
|
|
27
|
+
"description": "Remove $"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"regexkey": "\\#",
|
|
31
|
+
"replace": "",
|
|
32
|
+
"description": "Remove #"
|
|
38
33
|
}
|
|
39
|
-
|
|
34
|
+
],
|
|
35
|
+
"rootTopic-DIRECTIONS": "You can override the root topic by renaming _rootTopic to rootTopic",
|
|
36
|
+
"_rootTopic": "@bind=(state.equipment.alias).replace(' ','-').replace('/','').toLowerCase();",
|
|
37
|
+
"clientId": "@bind='mqttjs_njsPC_'+Math.random().toString(16).substr(2, 8);"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
40
|
"events": [
|
|
41
41
|
{
|
|
42
42
|
"name": "config",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
},
|
|
68
68
|
{
|
|
69
69
|
"topic": "state/startTime",
|
|
70
|
-
"message":
|
|
70
|
+
"message": "@bind=data.startTime;"
|
|
71
71
|
}
|
|
72
72
|
]
|
|
73
73
|
},
|
|
@@ -184,7 +184,50 @@
|
|
|
184
184
|
"topic": "state/temps/solar",
|
|
185
185
|
"message": "{\"temp\":@bind=data.solar;}",
|
|
186
186
|
"description": "Send solar temp.",
|
|
187
|
-
"filter": "@bind=typeof data.solar
|
|
187
|
+
"filter": "@bind=typeof data.solar !== 'undefined';"
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"topic": "state/temps/solarSensor2",
|
|
191
|
+
"message": "{\"temp\":@bind=data.solarSensor2;}",
|
|
192
|
+
"description": "Solar temp",
|
|
193
|
+
"filter": "@bind=typeof data.solarSensor2 !== 'undefined';"
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"topic": "state/temps/solarSensor3",
|
|
197
|
+
"message": "{\"temp\":@bind=data.solarSensor3;}",
|
|
198
|
+
"description": "Solar temp",
|
|
199
|
+
"filter": "@bind=typeof data.solarSensor3 !== 'undefined';"
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"topic": "state/temps/solarSensor4",
|
|
203
|
+
"message": "{\"temp\":@bind=data.solarSensor4;}",
|
|
204
|
+
"description": "Solar temp",
|
|
205
|
+
"filter": "@bind=typeof data.solarSensor4 !== 'undefined';"
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
{
|
|
209
|
+
"topic": "state/temps/waterSensor1",
|
|
210
|
+
"message": "{\"temp\":@bind=data.waterSensor1;}",
|
|
211
|
+
"description": "Water temp sensor 1",
|
|
212
|
+
"filter": "@bind=typeof data.waterSensor1 !== 'undefined';"
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"topic": "state/temps/waterSensor2",
|
|
216
|
+
"message": "{\"temp\":@bind=data.waterSensor2;}",
|
|
217
|
+
"description": "Water temp sensor 2",
|
|
218
|
+
"filter": "@bind=typeof data.waterSensor2 !== 'undefined';"
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
"topic": "state/temps/waterSensor3",
|
|
222
|
+
"message": "{\"temp\":@bind=data.waterSensor3;}",
|
|
223
|
+
"description": "Water temp sensor 3",
|
|
224
|
+
"filter": "@bind=typeof data.waterSensor3 !== 'undefined';"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"topic": "state/temps/waterSensor4",
|
|
228
|
+
"message": "{\"temp\":@bind=data.waterSensor4;}",
|
|
229
|
+
"description": "Water temp sensor 4",
|
|
230
|
+
"filter": "@bind=typeof data.waterSensor4 !== 'undefined';"
|
|
188
231
|
},
|
|
189
232
|
{
|
|
190
233
|
"topic": "state/temps/units",
|
|
@@ -303,6 +346,11 @@
|
|
|
303
346
|
"message": "{\"type\":@bind=data.type;}",
|
|
304
347
|
"description": "Send type."
|
|
305
348
|
},
|
|
349
|
+
{
|
|
350
|
+
"topic": "state/chlorinators/@bind=data.id;/@bind=data.name;/model",
|
|
351
|
+
"message": "{\"type\":@bind=data.model;}",
|
|
352
|
+
"description": "Send Model."
|
|
353
|
+
},
|
|
306
354
|
{
|
|
307
355
|
"topic": "state/chlorinators/@bind=data.id;/@bind=data.name;/targetOutput",
|
|
308
356
|
"message": "{\"targetOutput\":@bind=data.targetOutput;}",
|
|
@@ -582,6 +630,17 @@
|
|
|
582
630
|
}
|
|
583
631
|
]
|
|
584
632
|
},
|
|
633
|
+
{
|
|
634
|
+
"name": "chemicalDose",
|
|
635
|
+
"description": "Event when a chemical is being dosed",
|
|
636
|
+
"topics": [
|
|
637
|
+
{
|
|
638
|
+
"topic": "state/chemControllers/@bind=data.id;/@bind=data.chem;",
|
|
639
|
+
"message": "@bind=data;",
|
|
640
|
+
"enabled": true
|
|
641
|
+
}
|
|
642
|
+
]
|
|
643
|
+
},
|
|
585
644
|
{
|
|
586
645
|
"name": "filter",
|
|
587
646
|
"description": "Populate the filter topic",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
{
|
|
93
93
|
"topic": "state/circuits/@bind=data.id;/endTime",
|
|
94
94
|
"message": "@bind=data.endTime;",
|
|
95
|
-
"description":
|
|
95
|
+
"description": "The end time for the circuit."
|
|
96
96
|
},
|
|
97
97
|
{
|
|
98
98
|
"topic": "state/circuits/@bind=data.id;/lightingTheme",
|
|
@@ -295,6 +295,48 @@
|
|
|
295
295
|
"description": "Solar temp",
|
|
296
296
|
"filter": "@bind=typeof data.solar !== 'undefined';"
|
|
297
297
|
},
|
|
298
|
+
{
|
|
299
|
+
"topic": "state/temps/solarSensor2",
|
|
300
|
+
"message": "@bind=data.solarSensor2;",
|
|
301
|
+
"description": "Solar temp",
|
|
302
|
+
"filter": "@bind=typeof data.solarSensor2 !== 'undefined';"
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
"topic": "state/temps/solarSensor3",
|
|
306
|
+
"message": "@bind=data.solarSensor3;",
|
|
307
|
+
"description": "Solar temp",
|
|
308
|
+
"filter": "@bind=typeof data.solarSensor3 !== 'undefined';"
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
"topic": "state/temps/solarSensor4",
|
|
312
|
+
"message": "@bind=data.solarSensor4;",
|
|
313
|
+
"description": "Solar temp",
|
|
314
|
+
"filter": "@bind=typeof data.solarSensor4 !== 'undefined';"
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
"topic": "state/temps/waterSensor1",
|
|
318
|
+
"message": "@bind=data.waterSensor1;",
|
|
319
|
+
"description": "Water temp sensor 1",
|
|
320
|
+
"filter": "@bind=typeof data.waterSensor1 !== 'undefined';"
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"topic": "state/temps/waterSensor2",
|
|
324
|
+
"message": "@bind=data.waterSensor2;",
|
|
325
|
+
"description": "Water temp sensor 2",
|
|
326
|
+
"filter": "@bind=typeof data.waterSensor2 !== 'undefined';"
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
"topic": "state/temps/waterSensor3",
|
|
330
|
+
"message": "@bind=data.waterSensor3;",
|
|
331
|
+
"description": "Water temp sensor 3",
|
|
332
|
+
"filter": "@bind=typeof data.waterSensor3 !== 'undefined';"
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
"topic": "state/temps/waterSensor4",
|
|
336
|
+
"message": "@bind=data.waterSensor4;",
|
|
337
|
+
"description": "Water temp sensor 4",
|
|
338
|
+
"filter": "@bind=typeof data.waterSensor4 !== 'undefined';"
|
|
339
|
+
},
|
|
298
340
|
{
|
|
299
341
|
"topic": "state/temps/units",
|
|
300
342
|
"message": "@bind=data.units;"
|
|
@@ -407,6 +449,11 @@
|
|
|
407
449
|
"message": "@bind=data.type;",
|
|
408
450
|
"description": "Send type."
|
|
409
451
|
},
|
|
452
|
+
{
|
|
453
|
+
"topic": "state/chlorinators/@bind=data.id;/model",
|
|
454
|
+
"message": "@bind=data.model;",
|
|
455
|
+
"description": "Send Model"
|
|
456
|
+
},
|
|
410
457
|
{
|
|
411
458
|
"topic": "state/chlorinators/@bind=data.id;/targetOutput",
|
|
412
459
|
"message": "@bind=data.targetOutput;",
|
|
@@ -602,6 +649,17 @@
|
|
|
602
649
|
}
|
|
603
650
|
]
|
|
604
651
|
},
|
|
652
|
+
{
|
|
653
|
+
"name": "chemicalDose",
|
|
654
|
+
"description": "Event when a chemical is being dosed",
|
|
655
|
+
"topics": [
|
|
656
|
+
{
|
|
657
|
+
"topic": "state/chemControllers/@bind=data.id;/@bind=data.chem;",
|
|
658
|
+
"message": "@bind=data;",
|
|
659
|
+
"enabled": true
|
|
660
|
+
}
|
|
661
|
+
]
|
|
662
|
+
},
|
|
605
663
|
{
|
|
606
664
|
"name": "filter",
|
|
607
665
|
"description": "Populate the filter topic",
|
|
@@ -20,14 +20,15 @@ import * as http from "http";
|
|
|
20
20
|
import * as https from "https";
|
|
21
21
|
import extend=require("extend");
|
|
22
22
|
import { logger } from "../../logger/Logger";
|
|
23
|
-
import { sys } from "../../controller/Equipment";
|
|
24
|
-
import { state } from "../../controller/State";
|
|
23
|
+
import { PoolSystem, sys } from "../../controller/Equipment";
|
|
24
|
+
import { State, state } from "../../controller/State";
|
|
25
25
|
import { InterfaceContext, InterfaceEvent, BaseInterfaceBindings } from "./baseInterface";
|
|
26
26
|
|
|
27
27
|
export class HttpInterfaceBindings extends BaseInterfaceBindings {
|
|
28
28
|
constructor(cfg) {
|
|
29
29
|
super(cfg);
|
|
30
30
|
}
|
|
31
|
+
declare sockets: HttpInterfaceSocketEvent[];
|
|
31
32
|
public bindEvent(evt: string, ...data: any) {
|
|
32
33
|
// Find the binding by first looking for the specific event name.
|
|
33
34
|
// If that doesn't exist then look for the "*" (all events).
|
|
@@ -121,4 +122,24 @@ export class HttpInterfaceBindings extends BaseInterfaceBindings {
|
|
|
121
122
|
}
|
|
122
123
|
}
|
|
123
124
|
}
|
|
125
|
+
class HttpInterfaceSocketEvent {
|
|
126
|
+
event: string;
|
|
127
|
+
description: string;
|
|
128
|
+
processor: (sock: HttpInterfaceSocketEvent, sys: PoolSystem, state: State, value: any) => void;
|
|
129
|
+
constructor(sock: any) {
|
|
130
|
+
this.event = sock.event;
|
|
131
|
+
if (typeof sock.processor !== 'undefined') {
|
|
132
|
+
let fnBody = Array.isArray(sock.processor) ? sock.processor.join('\n') : sock.processor;
|
|
133
|
+
try {
|
|
134
|
+
this.processor = new Function('sock', 'sys', 'state', 'value', fnBody) as (sock: HttpInterfaceSocketEvent, sys: PoolSystem, state: State, value: any) => void;
|
|
135
|
+
} catch (err) { logger.error(`Error compiling socket event processor: ${err} -- ${fnBody}`); }
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
export interface IHTTPInterfaceSocketEvent {
|
|
140
|
+
event: string,
|
|
141
|
+
description: string,
|
|
142
|
+
processor?: string
|
|
143
|
+
}
|
|
144
|
+
|
|
124
145
|
|
|
@@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import extend = require("extend");
|
|
19
|
-
import { ClientOptions, InfluxDB, Point, WriteApi, WritePrecisionType } from '@influxdata/influxdb-client';
|
|
19
|
+
import { ClientOptions, DEFAULT_WriteOptions, InfluxDB, Point, WriteApi, WriteOptions, WritePrecisionType } from '@influxdata/influxdb-client';
|
|
20
20
|
import { utils, Timestamp } from '../../controller/Constants';
|
|
21
21
|
import { logger } from "../../logger/Logger";
|
|
22
22
|
import { BaseInterfaceBindings, InterfaceContext, InterfaceEvent } from "./baseInterface";
|
|
@@ -33,7 +33,8 @@ export class InfluxInterfaceBindings extends BaseInterfaceBindings {
|
|
|
33
33
|
let url = 'http://';
|
|
34
34
|
if (typeof baseOpts.protocol !== 'undefined' && baseOpts.protocol) url = baseOpts.protocol;
|
|
35
35
|
if (!url.endsWith('://')) url += '://';
|
|
36
|
-
url = `${url}${baseOpts.host}
|
|
36
|
+
url = `${url}${baseOpts.host}`;
|
|
37
|
+
if(typeof baseOpts.port !== 'undefined' && baseOpts.port !== null && !isNaN(baseOpts.port)) url = `${url}:${baseOpts.port}`;
|
|
37
38
|
let influxDB: InfluxDB;
|
|
38
39
|
let bucket;
|
|
39
40
|
let org;
|
|
@@ -62,9 +63,6 @@ export class InfluxInterfaceBindings extends BaseInterfaceBindings {
|
|
|
62
63
|
}
|
|
63
64
|
influxDB = new InfluxDB(clientOptions);
|
|
64
65
|
}
|
|
65
|
-
this.writeApi = influxDB.getWriteApi(org, bucket, 'ms');
|
|
66
|
-
|
|
67
|
-
|
|
68
66
|
// set global tags from context
|
|
69
67
|
let baseTags = {}
|
|
70
68
|
baseOpts.tags.forEach(tag => {
|
|
@@ -74,7 +72,40 @@ export class InfluxInterfaceBindings extends BaseInterfaceBindings {
|
|
|
74
72
|
if (typeof sname !== 'undefined' && typeof svalue !== 'undefined' && !sname.includes('@bind') && !svalue.includes('@bind'))
|
|
75
73
|
baseTags[sname] = svalue;
|
|
76
74
|
})
|
|
77
|
-
this.writeApi.useDefaultTags(baseTags);
|
|
75
|
+
//this.writeApi.useDefaultTags(baseTags);
|
|
76
|
+
const writeOptions:WriteOptions = {
|
|
77
|
+
/* the maximum points/line to send in a single batch to InfluxDB server */
|
|
78
|
+
batchSize: baseOpts.batchSize || 100,
|
|
79
|
+
/* default tags to add to every point */
|
|
80
|
+
defaultTags: baseTags,
|
|
81
|
+
/* maximum time in millis to keep points in an unflushed batch, 0 means don't periodically flush */
|
|
82
|
+
flushInterval: DEFAULT_WriteOptions.flushInterval,
|
|
83
|
+
/* maximum size of the retry buffer - it contains items that could not be sent for the first time */
|
|
84
|
+
maxBufferLines: DEFAULT_WriteOptions.maxBufferLines,
|
|
85
|
+
/* the count of retries, the delays between retries follow an exponential backoff strategy if there is no Retry-After HTTP header */
|
|
86
|
+
maxRetries: DEFAULT_WriteOptions.maxRetries,
|
|
87
|
+
/* maximum delay between retries in milliseconds */
|
|
88
|
+
maxRetryDelay: DEFAULT_WriteOptions.maxRetryDelay,
|
|
89
|
+
/* minimum delay between retries in milliseconds */
|
|
90
|
+
minRetryDelay: DEFAULT_WriteOptions.minRetryDelay, // minimum delay between retries
|
|
91
|
+
/* a random value of up to retryJitter is added when scheduling next retry */
|
|
92
|
+
retryJitter: DEFAULT_WriteOptions.retryJitter,
|
|
93
|
+
// ... or you can customize what to do on write failures when using a writeFailed fn, see the API docs for details
|
|
94
|
+
writeFailed: function(error, lines, failedAttempts){
|
|
95
|
+
/** return promise or void */
|
|
96
|
+
logger.error(`InfluxDB batch write failed writing ${lines.length} lines with ${failedAttempts} failed attempts. ${error.message}`);
|
|
97
|
+
},
|
|
98
|
+
writeSuccess: function(lines){
|
|
99
|
+
logger.silly(`InfluxDB successfully wrote ${lines.length} lines.`)
|
|
100
|
+
},
|
|
101
|
+
maxRetryTime: DEFAULT_WriteOptions.maxRetryTime,
|
|
102
|
+
exponentialBase: DEFAULT_WriteOptions.exponentialBase,
|
|
103
|
+
randomRetry: DEFAULT_WriteOptions.randomRetry
|
|
104
|
+
}
|
|
105
|
+
this.writeApi = influxDB.getWriteApi(org, bucket, 'ms', writeOptions);
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
78
109
|
}
|
|
79
110
|
public bindEvent(evt: string, ...data: any) {
|
|
80
111
|
|
|
@@ -115,6 +146,10 @@ export class InfluxInterfaceBindings extends BaseInterfaceBindings {
|
|
|
115
146
|
}
|
|
116
147
|
else {
|
|
117
148
|
logger.error(`InfluxDB tag binding failure on ${evt}:${_tag.name}/${_tag.value} --> ${svalue || 'undefined'} ${JSON.stringify(data[0])}`);
|
|
149
|
+
if (typeof sname === 'undefined') logger.error(`InfluxDB tag name is undefined`);
|
|
150
|
+
if (typeof svalue === 'undefined') logger.error(`InfluxDB value is undefined`);
|
|
151
|
+
if (svalue.includes('@bind')) logger.error(`InfluxDB value not bound`);
|
|
152
|
+
if (svalue === null) logger.error(`InfluxDB value is null`);
|
|
118
153
|
}
|
|
119
154
|
})
|
|
120
155
|
_point.fields.forEach(_field => {
|
|
@@ -188,14 +223,14 @@ export class InfluxInterfaceBindings extends BaseInterfaceBindings {
|
|
|
188
223
|
let sec = ts.getSeconds() - 1;
|
|
189
224
|
ts.setSeconds(sec);
|
|
190
225
|
point2.timestamp(ts);
|
|
191
|
-
logger.silly(`
|
|
226
|
+
logger.silly(`Batching influx ${e.name} inverse data point ${point2.toString()})`)
|
|
192
227
|
this.writeApi.writePoint(point2);
|
|
193
228
|
}
|
|
194
229
|
if (typeof point.toLineProtocol() !== 'undefined') {
|
|
195
|
-
logger.silly(`
|
|
230
|
+
logger.silly(`Batching influx ${e.name} data point ${point.toString()}`)
|
|
196
231
|
this.writeApi.writePoint(point);
|
|
197
|
-
this.writeApi.flush()
|
|
198
|
-
|
|
232
|
+
// this.writeApi.flush()
|
|
233
|
+
// .catch(error => { logger.error(`Error flushing Influx data point ${point.toString()} ${error}`); });
|
|
199
234
|
//logger.info(`INFLUX: ${point.toLineProtocol()}`)
|
|
200
235
|
}
|
|
201
236
|
else {
|
|
@@ -20,8 +20,8 @@ import * as http from "http";
|
|
|
20
20
|
import * as https from "https";
|
|
21
21
|
import extend = require("extend");
|
|
22
22
|
import { logger } from "../../logger/Logger";
|
|
23
|
-
import { sys } from "../../controller/Equipment";
|
|
24
|
-
import { state } from "../../controller/State";
|
|
23
|
+
import { PoolSystem, sys } from "../../controller/Equipment";
|
|
24
|
+
import { State, state } from "../../controller/State";
|
|
25
25
|
import { InterfaceEvent, BaseInterfaceBindings } from "./baseInterface";
|
|
26
26
|
import { sys as sysAlias } from "../../controller/Equipment";
|
|
27
27
|
import { state as stateAlias } from "../../controller/State";
|
|
@@ -35,8 +35,9 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings {
|
|
|
35
35
|
this.subscribed = false;
|
|
36
36
|
}
|
|
37
37
|
private client: MqttClient;
|
|
38
|
-
private topics:
|
|
38
|
+
private topics: MqttTopicSubscription[] = [];
|
|
39
39
|
declare events: MqttInterfaceEvent[];
|
|
40
|
+
declare subscriptions: MqttTopicSubscription[];
|
|
40
41
|
private subscribed: boolean; // subscribed to events or not
|
|
41
42
|
private sentInitialMessages = false;
|
|
42
43
|
private init = () => {
|
|
@@ -55,7 +56,6 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings {
|
|
|
55
56
|
url
|
|
56
57
|
}
|
|
57
58
|
this.client = connect(url, opts);
|
|
58
|
-
|
|
59
59
|
this.client.on('connect', () => {
|
|
60
60
|
try {
|
|
61
61
|
logger.info(`MQTT connected to ${url}`);
|
|
@@ -82,7 +82,7 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings {
|
|
|
82
82
|
while (this.topics.length > 0) {
|
|
83
83
|
let topic = this.topics.pop();
|
|
84
84
|
if (typeof topic !== 'undefined') {
|
|
85
|
-
this.client.unsubscribe(topic, (err, packet) => {
|
|
85
|
+
this.client.unsubscribe(topic.topicPath, (err, packet) => {
|
|
86
86
|
if (err) logger.error(`Error unsubscribing from MQTT topic ${topic}`);
|
|
87
87
|
else {
|
|
88
88
|
logger.debug(`Unsubscribed from MQTT topic ${topic}`);
|
|
@@ -92,47 +92,47 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings {
|
|
|
92
92
|
}
|
|
93
93
|
} catch (err) { logger.error(`Error unsubcribing to MQTT topic: ${err.message}`); }
|
|
94
94
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
this.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
logger.error(`MQTT Subscribe: ${err}`);
|
|
125
|
-
// reject(err);
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
// });
|
|
129
|
-
|
|
95
|
+
protected subscribe() {
|
|
96
|
+
if (this.topics.length > 0) this.unsubscribe();
|
|
97
|
+
let root = this.rootTopic();
|
|
98
|
+
if (typeof this.subscriptions !== 'undefined') {
|
|
99
|
+
for (let i = 0; i < this.subscriptions.length; i++) {
|
|
100
|
+
let sub = this.subscriptions[i];
|
|
101
|
+
if(sub.enabled !== false) this.topics.push(new MqttTopicSubscription(root, sub));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
let arrTopics = [
|
|
106
|
+
`state/+/setState`,
|
|
107
|
+
`state/+/setstate`,
|
|
108
|
+
`state/+/toggleState`,
|
|
109
|
+
`state/+/togglestate`,
|
|
110
|
+
`state/body/setPoint`,
|
|
111
|
+
`state/body/setpoint`,
|
|
112
|
+
`state/body/heatMode`,
|
|
113
|
+
`state/body/heatmode`,
|
|
114
|
+
`state/+/setTheme`,
|
|
115
|
+
`state/+/settheme`,
|
|
116
|
+
`state/temps`,
|
|
117
|
+
`config/tempSensors`,
|
|
118
|
+
`config/chemController`,
|
|
119
|
+
`state/chemController`,
|
|
120
|
+
`config/chlorinator`,
|
|
121
|
+
`state/chlorinator`];
|
|
122
|
+
for (let i = 0; i < arrTopics.length; i++) {
|
|
123
|
+
this.topics.push(new MqttTopicSubscription(root, { topic: arrTopics[i] }));
|
|
130
124
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
125
|
+
}
|
|
126
|
+
for (let i = 0; i < this.topics.length; i++) {
|
|
127
|
+
let topic = this.topics[i];
|
|
128
|
+
this.client.subscribe(topic.topicPath, (err, granted) => {
|
|
129
|
+
if (!err) logger.debug(`MQTT subscribed to ${JSON.stringify(granted)}`);
|
|
130
|
+
else logger.error(`MQTT Subscribe: ${err}`);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
this.client.on('message', async (topic, msg) => { try { await this.messageHandler(topic, msg) } catch (err) { logger.error(`Error processing MQTT request ${err}.`) }; })
|
|
134
|
+
this.subscribed = true;
|
|
134
135
|
}
|
|
135
|
-
|
|
136
136
|
// this will take in the MQTT Formatter options and format each token that is bound
|
|
137
137
|
// otherwise, it's the same as the base buildTokens fn.
|
|
138
138
|
// This could be combined into one fn but for now it's specific to MQTT formatting of topics
|
|
@@ -178,7 +178,6 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings {
|
|
|
178
178
|
}
|
|
179
179
|
return toks;
|
|
180
180
|
}
|
|
181
|
-
|
|
182
181
|
private rootTopic = () => {
|
|
183
182
|
let toks = {};
|
|
184
183
|
let baseOpts = extend(true, { headers: {} }, this.cfg.options, this.context.options);
|
|
@@ -187,7 +186,6 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings {
|
|
|
187
186
|
topic = this.replaceTokens(baseOpts.rootTopic, toks);
|
|
188
187
|
return topic;
|
|
189
188
|
}
|
|
190
|
-
|
|
191
189
|
public bindEvent(evt: string, ...data: any) {
|
|
192
190
|
try {
|
|
193
191
|
if (!this.sentInitialMessages && evt === 'controller' && data[0].status.val === 1) {
|
|
@@ -239,10 +237,28 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings {
|
|
|
239
237
|
topic = `${rootTopic}/${this.replaceTokens(t.topic, topicToks)}`;
|
|
240
238
|
// Filter out any topics where there may be undefined in it. We don't want any of this if that is the case.
|
|
241
239
|
if (topic.endsWith('/undefined') || topic.indexOf('/undefined/') !== -1 || topic.startsWith('null/') || topic.indexOf('/null') !== -1) return;
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
240
|
+
if (typeof t.processor !== 'undefined') {
|
|
241
|
+
if (t.ignoreProcessor) message = "err";
|
|
242
|
+
else {
|
|
243
|
+
if (typeof t._fnProcessor !== 'function') {
|
|
244
|
+
let fnBody = Array.isArray(t.processor) ? t.processor.join('\n') : t.processor;
|
|
245
|
+
try {
|
|
246
|
+
// Try to compile it.
|
|
247
|
+
t._fnProcessor = new Function('ctx', 'pub', 'sys', 'state', 'data', fnBody) as (ctx: any, pub: MQTTPublishTopic, sys: PoolSystem, state: State, data: any) => any;
|
|
248
|
+
} catch (err) { logger.error(`Error compiling subscription processor: ${err} -- ${fnBody}`); t.ignoreProcessor = true; }
|
|
249
|
+
}
|
|
250
|
+
if (typeof t._fnProcessor === 'function') {
|
|
251
|
+
let ctx = { util: utils }
|
|
252
|
+
try {
|
|
253
|
+
message = t._fnProcessor(ctx, t, sys, state, data[0]).toString();
|
|
254
|
+
} catch (err) { logger.error(`Error publishing MQTT data for topic ${t.topic}: ${err.message}`); message = "err"; }
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
this.buildTokens(t.message, evt, topicToks, e, data[0]);
|
|
260
|
+
message = this.tokensReplacer(t.message, evt, topicToks, e, data[0]);
|
|
261
|
+
}
|
|
246
262
|
|
|
247
263
|
let publishOptions: IClientPublishOptions = { retain: typeof baseOpts.retain !== 'undefined' ? baseOpts.retain : true, qos: typeof baseOpts.qos !== 'undefined' ? baseOpts.qos : 2 };
|
|
248
264
|
let changesOnly = typeof baseOpts.changesOnly !== 'undefined' ? baseOpts.changesOnly : true;
|
|
@@ -286,6 +302,16 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings {
|
|
|
286
302
|
try {
|
|
287
303
|
let msg = message.toString();
|
|
288
304
|
if (msg[0] === '{') msg = JSON.parse(msg);
|
|
305
|
+
|
|
306
|
+
let sub: MqttTopicSubscription = this.topics.find(elem => topic === elem.topicPath);
|
|
307
|
+
if (typeof sub !== 'undefined') {
|
|
308
|
+
logger.debug(`Topic not found ${topic}`)
|
|
309
|
+
// Alright so now lets process our results.
|
|
310
|
+
if (typeof sub.processor === 'function') {
|
|
311
|
+
sub.executeProcessor(msg);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
289
315
|
const topics = topic.split('/');
|
|
290
316
|
logger.debug(`MQTT: Inbound ${topic}: ${message.toString()}`);
|
|
291
317
|
if (topic.startsWith(this.rootTopic() + '/') && typeof msg === 'object') {
|
|
@@ -452,12 +478,10 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings {
|
|
|
452
478
|
}
|
|
453
479
|
}
|
|
454
480
|
}
|
|
455
|
-
|
|
456
481
|
class MqttInterfaceEvent extends InterfaceEvent {
|
|
457
|
-
public topics:
|
|
482
|
+
public topics: MQTTPublishTopic[]
|
|
458
483
|
}
|
|
459
|
-
|
|
460
|
-
export interface IMQTT {
|
|
484
|
+
export class MQTTPublishTopic {
|
|
461
485
|
topic: string;
|
|
462
486
|
message: string;
|
|
463
487
|
description: string;
|
|
@@ -468,8 +492,44 @@ export interface IMQTT {
|
|
|
468
492
|
filter?: string;
|
|
469
493
|
lastSent: MQTTMessage[];
|
|
470
494
|
options: any;
|
|
495
|
+
processor?: string[];
|
|
496
|
+
ignoreProcessor: boolean = false;
|
|
497
|
+
_fnProcessor: (ctx: any, pub: MQTTPublishTopic, sys: PoolSystem, state: State, data: any) => any
|
|
471
498
|
}
|
|
472
499
|
class MQTTMessage {
|
|
473
500
|
topic: string;
|
|
474
501
|
message: string;
|
|
475
502
|
}
|
|
503
|
+
|
|
504
|
+
class MqttSubscriptions {
|
|
505
|
+
public subscriptions: IMQTTSubscription[]
|
|
506
|
+
}
|
|
507
|
+
class MqttTopicSubscription {
|
|
508
|
+
root: string;
|
|
509
|
+
topic: string;
|
|
510
|
+
enabled: boolean;
|
|
511
|
+
processor: (ctx: any, sub: MqttTopicSubscription, sys: PoolSystem, state: State, value: any) => void;
|
|
512
|
+
constructor(root: string, sub: any) {
|
|
513
|
+
this.root = sub.root || root;
|
|
514
|
+
this.topic = sub.topic;
|
|
515
|
+
if (typeof sub.processor !== 'undefined') {
|
|
516
|
+
let fnBody = Array.isArray(sub.processor) ? sub.processor.join('\n') : sub.processor;
|
|
517
|
+
try {
|
|
518
|
+
this.processor = new Function('ctx', 'sub', 'sys', 'state', 'value', fnBody) as (ctx: any, sub: MqttTopicSubscription, sys: PoolSystem, state: State, value: any) => void;
|
|
519
|
+
} catch (err) { logger.error(`Error compiling subscription processor: ${err} -- ${fnBody}`); }
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
public get topicPath(): string { return `${this.root}/${this.topic}` };
|
|
523
|
+
public executeProcessor(value: any) {
|
|
524
|
+
let ctx = { util:utils }
|
|
525
|
+
this.processor(ctx, this, sys, state, value);
|
|
526
|
+
state.emitEquipmentChanges();
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
}
|
|
530
|
+
export interface IMQTTSubscription {
|
|
531
|
+
topic: string,
|
|
532
|
+
description: string,
|
|
533
|
+
processor?: string,
|
|
534
|
+
enabled?: boolean
|
|
535
|
+
}
|