nodejs-poolcontroller 8.1.2 → 8.4.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/.eslintrc.json +36 -36
- package/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -84
- package/.github/ISSUE_TEMPLATE/2-docs.md +12 -12
- package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -28
- package/.github/ISSUE_TEMPLATE/config.yml +8 -8
- package/.github/copilot-instructions.md +63 -0
- package/.github/workflows/ghcr-publish.yml +67 -0
- package/AGENTS.md +597 -0
- package/CONTRIBUTING.md +74 -74
- package/Changelog +292 -257
- package/Dockerfile +62 -19
- package/Gruntfile.js +40 -40
- package/LICENSE +661 -661
- package/README.md +318 -191
- package/anslq25/MessagesMock.ts +221 -221
- package/anslq25/boards/MockBoardFactory.ts +49 -49
- package/anslq25/boards/MockEasyTouchBoard.ts +696 -696
- package/anslq25/boards/MockSystemBoard.ts +216 -216
- package/anslq25/chemistry/MockChlorinator.ts +98 -98
- package/anslq25/pumps/MockPump.ts +83 -83
- package/app.ts +115 -115
- package/config/Config.ts +57 -7
- package/config/VersionCheck.ts +63 -35
- package/controller/Constants.ts +809 -805
- package/controller/Equipment.ts +2688 -2664
- package/controller/Errors.ts +181 -181
- package/controller/Lockouts.ts +549 -549
- package/controller/State.ts +3738 -3690
- package/controller/boards/AquaLinkBoard.ts +1003 -1003
- package/controller/boards/BoardFactory.ts +53 -53
- package/controller/boards/EasyTouchBoard.ts +3202 -3202
- package/controller/boards/IntelliCenterBoard.ts +4393 -3899
- package/controller/boards/IntelliComBoard.ts +69 -69
- package/controller/boards/IntelliTouchBoard.ts +382 -382
- package/controller/boards/NixieBoard.ts +1944 -1929
- package/controller/boards/SunTouchBoard.ts +400 -400
- package/controller/boards/SystemBoard.ts +5268 -5268
- package/controller/comms/Comms.ts +1272 -1214
- package/controller/comms/ScreenLogic.ts +1665 -1665
- package/controller/comms/messages/Messages.ts +1433 -1243
- package/controller/comms/messages/config/ChlorinatorMessage.ts +5 -0
- package/controller/comms/messages/config/CircuitGroupMessage.ts +0 -0
- package/controller/comms/messages/config/CircuitMessage.ts +0 -0
- package/controller/comms/messages/config/ConfigMessage.ts +6 -0
- package/controller/comms/messages/config/CoverMessage.ts +0 -0
- package/controller/comms/messages/config/CustomNameMessage.ts +31 -31
- package/controller/comms/messages/config/EquipmentMessage.ts +216 -210
- package/controller/comms/messages/config/ExternalMessage.ts +96 -10
- package/controller/comms/messages/config/FeatureMessage.ts +0 -0
- package/controller/comms/messages/config/GeneralMessage.ts +0 -0
- package/controller/comms/messages/config/HeaterMessage.ts +0 -0
- package/controller/comms/messages/config/IntellichemMessage.ts +0 -0
- package/controller/comms/messages/config/OptionsMessage.ts +194 -174
- package/controller/comms/messages/config/PumpMessage.ts +0 -0
- package/controller/comms/messages/config/RemoteMessage.ts +0 -0
- package/controller/comms/messages/config/ScheduleMessage.ts +401 -390
- package/controller/comms/messages/config/SecurityMessage.ts +0 -0
- package/controller/comms/messages/config/ValveMessage.ts +0 -0
- package/controller/comms/messages/status/ChlorinatorStateMessage.ts +0 -0
- package/controller/comms/messages/status/EquipmentStateMessage.ts +1158 -822
- package/controller/comms/messages/status/HeaterStateMessage.ts +135 -135
- package/controller/comms/messages/status/IntelliChemStateMessage.ts +448 -448
- package/controller/comms/messages/status/IntelliValveStateMessage.ts +36 -36
- package/controller/comms/messages/status/PumpStateMessage.ts +0 -0
- package/controller/comms/messages/status/RegalModbusStateMessage.ts +411 -0
- package/controller/comms/messages/status/VersionMessage.ts +103 -41
- package/controller/nixie/Nixie.ts +173 -173
- package/controller/nixie/NixieEquipment.ts +104 -104
- package/controller/nixie/bodies/Body.ts +120 -120
- package/controller/nixie/bodies/Filter.ts +135 -135
- package/controller/nixie/chemistry/ChemController.ts +2724 -2724
- package/controller/nixie/chemistry/ChemDoser.ts +806 -806
- package/controller/nixie/chemistry/Chlorinator.ts +367 -367
- package/controller/nixie/circuits/Circuit.ts +478 -478
- package/controller/nixie/heaters/Heater.ts +834 -834
- package/controller/nixie/pumps/Pump.ts +1194 -996
- package/controller/nixie/schedules/Schedule.ts +401 -401
- package/controller/nixie/valves/Valve.ts +170 -170
- package/defaultConfig.json +352 -347
- package/docker-compose.yml +32 -0
- package/logger/DataLogger.ts +448 -448
- package/logger/Logger.ts +448 -436
- package/package.json +58 -60
- package/sendSocket.js +32 -32
- package/tsconfig.json +25 -25
- package/types/express-multer.d.ts +32 -0
- package/web/Server.ts +1937 -1927
- package/web/bindings/aqualinkD.json +559 -559
- package/web/bindings/influxDB.json +1066 -1066
- package/web/bindings/mqtt.json +721 -721
- package/web/bindings/mqttAlt.json +746 -746
- package/web/bindings/rulesManager.json +54 -54
- package/web/bindings/smartThings-Hubitat.json +31 -31
- package/web/bindings/valveRelays.json +20 -20
- package/web/bindings/vera.json +25 -25
- package/web/interfaces/baseInterface.ts +188 -188
- package/web/interfaces/httpInterface.ts +148 -148
- package/web/interfaces/influxInterface.ts +283 -283
- package/web/interfaces/mqttInterface.ts +695 -695
- package/web/interfaces/ruleInterface.ts +101 -87
- package/web/services/config/Config.ts +1063 -1053
- package/web/services/config/ConfigSocket.ts +0 -0
- package/web/services/state/State.ts +0 -0
- package/web/services/state/StateSocket.ts +0 -0
- package/web/services/utilities/Utilities.ts +233 -233
- package/.github/workflows/docker-publish-njsPC-linux.yml +0 -50
|
@@ -1,1243 +1,1433 @@
|
|
|
1
|
-
/* nodejs-poolController. An application to control pool equipment.
|
|
2
|
-
Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
|
|
3
|
-
Russell Goldin, tagyoureit. russ.goldin@gmail.com
|
|
4
|
-
|
|
5
|
-
This program is free software: you can redistribute it and/or modify
|
|
6
|
-
it under the terms of the GNU Affero General Public License as
|
|
7
|
-
published by the Free Software Foundation, either version 3 of the
|
|
8
|
-
License, or (at your option) any later version.
|
|
9
|
-
|
|
10
|
-
This program is distributed in the hope that it will be useful,
|
|
11
|
-
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
-
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
-
GNU Affero General Public License for more details.
|
|
14
|
-
|
|
15
|
-
You should have received a copy of the GNU Affero General Public License
|
|
16
|
-
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
-
*/
|
|
18
|
-
import { ConfigMessage } from "./config/ConfigMessage";
|
|
19
|
-
import { PumpMessage } from "./config/PumpMessage";
|
|
20
|
-
import { VersionMessage } from "./status/VersionMessage";
|
|
21
|
-
import { PumpStateMessage } from "./status/PumpStateMessage";
|
|
22
|
-
import { EquipmentStateMessage } from "./status/EquipmentStateMessage";
|
|
23
|
-
import { HeaterStateMessage } from "./status/HeaterStateMessage";
|
|
24
|
-
import { ChlorinatorStateMessage } from "./status/ChlorinatorStateMessage";
|
|
25
|
-
import { ChlorinatorMessage } from "./config/ChlorinatorMessage";
|
|
26
|
-
import { ExternalMessage } from "./config/ExternalMessage";
|
|
27
|
-
import { Timestamp, ControllerType } from "../../Constants";
|
|
28
|
-
import { CircuitMessage } from "./config/CircuitMessage";
|
|
29
|
-
import { config } from '../../../config/Config';
|
|
30
|
-
import { sys } from '../../Equipment';
|
|
31
|
-
import { logger } from "../../../logger/Logger";
|
|
32
|
-
import { CustomNameMessage } from "./config/CustomNameMessage";
|
|
33
|
-
import { ScheduleMessage } from "./config/ScheduleMessage";
|
|
34
|
-
import { RemoteMessage } from "./config/RemoteMessage";
|
|
35
|
-
import { OptionsMessage } from "./config/OptionsMessage";
|
|
36
|
-
import { EquipmentMessage } from "./config/EquipmentMessage";
|
|
37
|
-
import { ValveMessage } from "./config/ValveMessage";
|
|
38
|
-
import { state } from "../../State";
|
|
39
|
-
import { HeaterMessage } from "./config/HeaterMessage";
|
|
40
|
-
import { CircuitGroupMessage } from "./config/CircuitGroupMessage";
|
|
41
|
-
import { IntellichemMessage } from "./config/IntellichemMessage";
|
|
42
|
-
import { TouchScheduleCommands } from "controller/boards/EasyTouchBoard";
|
|
43
|
-
import { IntelliValveStateMessage } from "./status/IntelliValveStateMessage";
|
|
44
|
-
import { IntelliChemStateMessage } from "./status/IntelliChemStateMessage";
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
47
|
-
import
|
|
48
|
-
import
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
private
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
public
|
|
83
|
-
public
|
|
84
|
-
public
|
|
85
|
-
public
|
|
86
|
-
public
|
|
87
|
-
public
|
|
88
|
-
public
|
|
89
|
-
public
|
|
90
|
-
public
|
|
91
|
-
public
|
|
92
|
-
public
|
|
93
|
-
public
|
|
94
|
-
public
|
|
95
|
-
|
|
96
|
-
public
|
|
97
|
-
|
|
98
|
-
public get
|
|
99
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (this.protocol === Protocol.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
let
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
inbound.
|
|
242
|
-
inbound.
|
|
243
|
-
inbound.
|
|
244
|
-
inbound.
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
public
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
//
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
//
|
|
387
|
-
//
|
|
388
|
-
//
|
|
389
|
-
//
|
|
390
|
-
|
|
391
|
-
//
|
|
392
|
-
//
|
|
393
|
-
if (
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
this.
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
if (this.
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
this.
|
|
496
|
-
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
if (bytes
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
return
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
case Protocol.
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
this.
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
if (
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
case
|
|
769
|
-
|
|
770
|
-
break;
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
public get
|
|
969
|
-
|
|
970
|
-
if (this.
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
}
|
|
1010
|
-
public
|
|
1011
|
-
if (
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
}
|
|
1018
|
-
public
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
this.payload.
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
this.
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
this.
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
}
|
|
1077
|
-
else {
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
public
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
public
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
if (
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1
|
+
/* nodejs-poolController. An application to control pool equipment.
|
|
2
|
+
Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
|
|
3
|
+
Russell Goldin, tagyoureit. russ.goldin@gmail.com
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Affero General Public License as
|
|
7
|
+
published by the Free Software Foundation, either version 3 of the
|
|
8
|
+
License, or (at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Affero General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import { ConfigMessage } from "./config/ConfigMessage";
|
|
19
|
+
import { PumpMessage } from "./config/PumpMessage";
|
|
20
|
+
import { VersionMessage } from "./status/VersionMessage";
|
|
21
|
+
import { PumpStateMessage } from "./status/PumpStateMessage";
|
|
22
|
+
import { EquipmentStateMessage } from "./status/EquipmentStateMessage";
|
|
23
|
+
import { HeaterStateMessage } from "./status/HeaterStateMessage";
|
|
24
|
+
import { ChlorinatorStateMessage } from "./status/ChlorinatorStateMessage";
|
|
25
|
+
import { ChlorinatorMessage } from "./config/ChlorinatorMessage";
|
|
26
|
+
import { ExternalMessage } from "./config/ExternalMessage";
|
|
27
|
+
import { Timestamp, ControllerType } from "../../Constants";
|
|
28
|
+
import { CircuitMessage } from "./config/CircuitMessage";
|
|
29
|
+
import { config } from '../../../config/Config';
|
|
30
|
+
import { sys } from '../../Equipment';
|
|
31
|
+
import { logger } from "../../../logger/Logger";
|
|
32
|
+
import { CustomNameMessage } from "./config/CustomNameMessage";
|
|
33
|
+
import { ScheduleMessage } from "./config/ScheduleMessage";
|
|
34
|
+
import { RemoteMessage } from "./config/RemoteMessage";
|
|
35
|
+
import { OptionsMessage } from "./config/OptionsMessage";
|
|
36
|
+
import { EquipmentMessage } from "./config/EquipmentMessage";
|
|
37
|
+
import { ValveMessage } from "./config/ValveMessage";
|
|
38
|
+
import { state } from "../../State";
|
|
39
|
+
import { HeaterMessage } from "./config/HeaterMessage";
|
|
40
|
+
import { CircuitGroupMessage } from "./config/CircuitGroupMessage";
|
|
41
|
+
import { IntellichemMessage } from "./config/IntellichemMessage";
|
|
42
|
+
import { TouchScheduleCommands } from "controller/boards/EasyTouchBoard";
|
|
43
|
+
import { IntelliValveStateMessage } from "./status/IntelliValveStateMessage";
|
|
44
|
+
import { IntelliChemStateMessage } from "./status/IntelliChemStateMessage";
|
|
45
|
+
import { RegalModbusStateMessage } from "./status/RegalModbusStateMessage";
|
|
46
|
+
import { OutboundMessageError } from "../../Errors";
|
|
47
|
+
import { conn } from "../Comms"
|
|
48
|
+
import extend = require("extend");
|
|
49
|
+
import { MessagesMock } from "../../../anslq25/MessagesMock";
|
|
50
|
+
|
|
51
|
+
export enum Direction {
|
|
52
|
+
In = 'in',
|
|
53
|
+
Out = 'out'
|
|
54
|
+
}
|
|
55
|
+
export enum Protocol {
|
|
56
|
+
Unknown = 'unknown',
|
|
57
|
+
Broadcast = 'broadcast',
|
|
58
|
+
Pump = 'pump',
|
|
59
|
+
Chlorinator = 'chlorinator',
|
|
60
|
+
IntelliChem = 'intellichem',
|
|
61
|
+
IntelliValve = 'intellivalve',
|
|
62
|
+
Heater = 'heater',
|
|
63
|
+
AquaLink = 'aqualink',
|
|
64
|
+
Hayward = 'hayward',
|
|
65
|
+
Unidentified = 'unidentified',
|
|
66
|
+
RegalModbus = 'regalmodbus'
|
|
67
|
+
}
|
|
68
|
+
export class Message {
|
|
69
|
+
constructor() { }
|
|
70
|
+
|
|
71
|
+
// Internal Storage
|
|
72
|
+
protected _complete: boolean = false;
|
|
73
|
+
public static headerSubByte: number = 33;
|
|
74
|
+
public static pluginAddress: number = config.getSection('controller', { address: 33 }).address;
|
|
75
|
+
private _id: number = -1;
|
|
76
|
+
// Fields
|
|
77
|
+
private static _messageId: number = 0;
|
|
78
|
+
public static get nextMessageId(): number {
|
|
79
|
+
let i = this._messageId < 80000 ? ++this._messageId : this._messageId = 0;
|
|
80
|
+
//logger.debug(`Assigning message id ${i}`)
|
|
81
|
+
return i; }
|
|
82
|
+
public portId = 0; // This will be the target or source port for the message. If this is from or to an Aux RS485 port the value will be > 0.
|
|
83
|
+
public timestamp: Date = new Date();
|
|
84
|
+
public direction: Direction = Direction.In;
|
|
85
|
+
public protocol: Protocol = Protocol.Unknown;
|
|
86
|
+
public padding: number[] = [];
|
|
87
|
+
public preamble: number[] = [];
|
|
88
|
+
public header: number[] = [];
|
|
89
|
+
public payload: number[] = [];
|
|
90
|
+
public term: number[] = [];
|
|
91
|
+
public packetCount: number = 0;
|
|
92
|
+
public get id(): number { return this._id; }
|
|
93
|
+
public set id(val: number) { this._id = val; }
|
|
94
|
+
public isValid: boolean = true;
|
|
95
|
+
public scope: string;
|
|
96
|
+
public isClone: boolean;
|
|
97
|
+
// Properties
|
|
98
|
+
public get isComplete(): boolean { return this._complete; }
|
|
99
|
+
public get sub(): number { return this.header.length > 1 ? this.header[1] : -1; }
|
|
100
|
+
public get dest(): number {
|
|
101
|
+
if (this.header.length > 2) {
|
|
102
|
+
if (this.protocol === Protocol.Chlorinator || this.protocol === Protocol.AquaLink) {
|
|
103
|
+
return this.header.length > 2 ? (this.header[2] >= 80 ? this.header[2] : 0) : -1;
|
|
104
|
+
}
|
|
105
|
+
else if (this.protocol === Protocol.Hayward) {
|
|
106
|
+
// src act dest
|
|
107
|
+
//0x10, 0x02, 0x00, 0x0C, 0x00, 0x00, 0x2D, 0x02, 0x36, 0x00, 0x83, 0x10, 0x03 -- Response from pump
|
|
108
|
+
return this.header.length > 4 ? this.header[2] : -1;
|
|
109
|
+
}
|
|
110
|
+
else if (this.protocol === Protocol.RegalModbus) {
|
|
111
|
+
return this.header.length > 0 ? this.header[0] : -1;
|
|
112
|
+
}
|
|
113
|
+
else return this.header.length > 2 ? this.header[2] : -1;
|
|
114
|
+
}
|
|
115
|
+
else return -1;
|
|
116
|
+
}
|
|
117
|
+
public get source(): number {
|
|
118
|
+
if (this.protocol === Protocol.Chlorinator) {
|
|
119
|
+
return this.header.length > 2 ? (this.header[2] >= 80 ? 0 : this.header[2]) : -1;
|
|
120
|
+
// have to assume incoming packets with header[2] >= 80 (sent to a chlorinator)
|
|
121
|
+
// are from controller (0);
|
|
122
|
+
// likewise, if the destination is 0 (controller) we
|
|
123
|
+
// have to assume it was sent from the 1st chlorinator (1)
|
|
124
|
+
// until we learn otherwise.
|
|
125
|
+
}
|
|
126
|
+
else if (this.protocol === Protocol.AquaLink) {
|
|
127
|
+
// Once we decode the devices we will be able to tell where it came from based upon the commands.
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
else if (this.protocol === Protocol.Hayward) {
|
|
131
|
+
// src act dest
|
|
132
|
+
//0x10, 0x02, 0x00, 0x0C, 0x00, 0x00, 0x2D, 0x02, 0x36, 0x00, 0x83, 0x10, 0x03 -- Response from pump
|
|
133
|
+
//0x10, 0x02, 0x0C, 0x01, 0x02, 0x2D, 0x00, 0x4E, 0x10, 0x03 -- Command to AUX2 Pump
|
|
134
|
+
return this.header.length > 4 ? this.header[4] : -1;
|
|
135
|
+
}
|
|
136
|
+
else if (this.protocol === Protocol.RegalModbus) {
|
|
137
|
+
// No source address in RegalModbus.
|
|
138
|
+
return -1;
|
|
139
|
+
}
|
|
140
|
+
if (this.header.length > 3) return this.header[3];
|
|
141
|
+
else return -1;
|
|
142
|
+
}
|
|
143
|
+
public get action(): number {
|
|
144
|
+
// The action byte is actually the 4th byte in the header the destination address is the 5th byte.
|
|
145
|
+
if (this.protocol === Protocol.Chlorinator ||
|
|
146
|
+
this.protocol === Protocol.AquaLink) return this.header.length > 3 ? this.header[3] : -1;
|
|
147
|
+
else if (this.protocol === Protocol.Hayward) {
|
|
148
|
+
// src act dest
|
|
149
|
+
//0x10, 0x02, 0x00, 0x0C, 0x00, 0x00, 0x2D, 0x02, 0x36, 0x00, 0x83, 0x10, 0x03 -- Response from pump
|
|
150
|
+
//0x10, 0x02, 0x0C, 0x01, 0x02, 0x2D, 0x00, 0x4E, 0x10, 0x03 -- Command to AUX2 Pump
|
|
151
|
+
return this.header.length > 3 ? this.header[3] || this.header[2] : -1;
|
|
152
|
+
}
|
|
153
|
+
else if (this.protocol === Protocol.RegalModbus) {
|
|
154
|
+
return this.header.length > 1 ? this.header[1]: -1;
|
|
155
|
+
}
|
|
156
|
+
else if (this.header.length > 4) return this.header[4];
|
|
157
|
+
else return -1;
|
|
158
|
+
if (this.header.length > 4) return this.header[4];
|
|
159
|
+
else return -1;
|
|
160
|
+
}
|
|
161
|
+
public get datalen(): number {
|
|
162
|
+
if (
|
|
163
|
+
this.protocol === Protocol.Chlorinator ||
|
|
164
|
+
this.protocol === Protocol.AquaLink ||
|
|
165
|
+
this.protocol === Protocol.Hayward
|
|
166
|
+
) {
|
|
167
|
+
return this.payload.length;
|
|
168
|
+
}
|
|
169
|
+
else if (this.protocol === Protocol.RegalModbus) {
|
|
170
|
+
let action = this.action;
|
|
171
|
+
let ack = this.header[2];
|
|
172
|
+
switch (action) {
|
|
173
|
+
case 0x41: // Go
|
|
174
|
+
case 0x42: // Stop
|
|
175
|
+
return 0;
|
|
176
|
+
case 0x43: // Status
|
|
177
|
+
switch (ack) {
|
|
178
|
+
case 0x10:
|
|
179
|
+
return 1;
|
|
180
|
+
case 0x20:
|
|
181
|
+
return 0
|
|
182
|
+
}
|
|
183
|
+
case 0x44: // Set demand
|
|
184
|
+
return 3;
|
|
185
|
+
case 0x45: // Read sensor
|
|
186
|
+
switch (ack) {
|
|
187
|
+
case 0x10:
|
|
188
|
+
return 4;
|
|
189
|
+
case 0x20:
|
|
190
|
+
return 2;
|
|
191
|
+
}
|
|
192
|
+
case 0x46: // Read identification
|
|
193
|
+
console.log("RegalModbus: Read identification not implemented yet.");
|
|
194
|
+
break;
|
|
195
|
+
case 0x64: // Configuration read/write
|
|
196
|
+
console.log("RegalModbus: Configuration read/write not implemented yet.");
|
|
197
|
+
break;
|
|
198
|
+
case 0x65: // Store configuration
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return this.header.length > 5 ? this.header[5] : -1;
|
|
203
|
+
}
|
|
204
|
+
public get chkHi(): number { return this.protocol === Protocol.Chlorinator || this.protocol === Protocol.AquaLink ? 0 : this.term.length > 0 ? this.term[0] : -1; }
|
|
205
|
+
public get chkLo(): number { return this.protocol === Protocol.Chlorinator || this.protocol === Protocol.AquaLink ? this.term[0] : this.term[1]; }
|
|
206
|
+
public get checksum(): number {
|
|
207
|
+
var sum = 0;
|
|
208
|
+
for (let i = 0; i < this.header.length; i++) sum += this.header[i];
|
|
209
|
+
for (let i = 0; i < this.payload.length; i++) sum += this.payload[i];
|
|
210
|
+
return sum;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Methods
|
|
214
|
+
public toPacket(): number[] {
|
|
215
|
+
const pkt = [];
|
|
216
|
+
pkt.push(...this.padding);
|
|
217
|
+
pkt.push(...this.preamble);
|
|
218
|
+
pkt.push(...this.header);
|
|
219
|
+
pkt.push(...this.payload);
|
|
220
|
+
pkt.push(...this.term);
|
|
221
|
+
return pkt;
|
|
222
|
+
}
|
|
223
|
+
public toShortPacket(): number[] {
|
|
224
|
+
const pkt = [];
|
|
225
|
+
pkt.push(...this.header);
|
|
226
|
+
pkt.push(...this.payload);
|
|
227
|
+
pkt.push(...this.term);
|
|
228
|
+
return pkt;
|
|
229
|
+
}
|
|
230
|
+
public toLog(): string {
|
|
231
|
+
return `{"port":${this.portId},"id":${this.id},"valid":${this.isValid},"dir":"${this.direction}","proto":"${this.protocol}","pkt":[${JSON.stringify(this.padding)},${JSON.stringify(this.preamble)}, ${JSON.stringify(this.header)}, ${JSON.stringify(this.payload)},${JSON.stringify(this.term)}],"ts":"${Timestamp.toISOLocal(this.timestamp)}"}`;
|
|
232
|
+
}
|
|
233
|
+
public static convertOutboundToInbound(out: Outbound): Inbound {
|
|
234
|
+
let inbound = new Inbound();
|
|
235
|
+
inbound.portId = out.portId;
|
|
236
|
+
// inbound.id = Message.nextMessageId;
|
|
237
|
+
inbound.protocol = out.protocol;
|
|
238
|
+
inbound.scope = out.scope;
|
|
239
|
+
inbound.preamble = out.preamble;
|
|
240
|
+
inbound.padding = out.padding;
|
|
241
|
+
inbound.header = out.header;
|
|
242
|
+
inbound.payload = [...out.payload];
|
|
243
|
+
inbound.term = out.term;
|
|
244
|
+
inbound.portId = out.portId;
|
|
245
|
+
return inbound;
|
|
246
|
+
}
|
|
247
|
+
public static convertInboundToOutbound(inbound: Inbound): Outbound {
|
|
248
|
+
let out = new Outbound(
|
|
249
|
+
inbound.protocol,
|
|
250
|
+
inbound.source,
|
|
251
|
+
inbound.dest,
|
|
252
|
+
inbound.action,
|
|
253
|
+
inbound.payload,
|
|
254
|
+
);
|
|
255
|
+
out.scope = inbound.scope;
|
|
256
|
+
out.preamble = inbound.preamble;
|
|
257
|
+
out.padding = inbound.padding;
|
|
258
|
+
out.header = inbound.header;
|
|
259
|
+
out.term = inbound.term;
|
|
260
|
+
out.portId = inbound.portId;
|
|
261
|
+
return out;
|
|
262
|
+
}
|
|
263
|
+
public clone(): Inbound | Outbound {
|
|
264
|
+
let msg;
|
|
265
|
+
if (this instanceof Inbound) {
|
|
266
|
+
msg = new Inbound();
|
|
267
|
+
msg.id = Message.nextMessageId;
|
|
268
|
+
msg.scope = this.scope;
|
|
269
|
+
msg.preamble = this.preamble;
|
|
270
|
+
msg.padding = this.padding;
|
|
271
|
+
msg.payload = [...this.payload];
|
|
272
|
+
msg.header = this.header;
|
|
273
|
+
msg.term = this.term;
|
|
274
|
+
msg.portId = this.portId;
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
msg = new Outbound(
|
|
278
|
+
this.protocol, this.source, this.dest, this.action, [...this.payload],
|
|
279
|
+
);
|
|
280
|
+
msg.portId = this.portId;
|
|
281
|
+
msg.scope = this.scope;
|
|
282
|
+
}
|
|
283
|
+
return msg;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
export class Inbound extends Message {
|
|
287
|
+
// /usr/bin/socat TCP-LISTEN:9801,fork,reuseaddr FILE:/dev/ttyUSB0,b9600,raw
|
|
288
|
+
// /usr/bin/socat TCP-LISTEN:9801,fork,reuseaddr FILE:/dev/ttyUSB0,b9600,cs8,cstopb=1,parenb=0,raw
|
|
289
|
+
// /usr/bin / socat TCP - LISTEN: 9801,fork,reuseaddr FILE:/dev/ttyUSB0, b9600, cs8, cstopb = 1, parenb = 0, raw
|
|
290
|
+
constructor() {
|
|
291
|
+
super();
|
|
292
|
+
this.direction = Direction.In;
|
|
293
|
+
}
|
|
294
|
+
// Factory
|
|
295
|
+
public static replay(obj?: any) {
|
|
296
|
+
let inbound = new Inbound();
|
|
297
|
+
inbound.readHeader(obj.header, 0);
|
|
298
|
+
inbound.readPayload(obj.payload, 0);
|
|
299
|
+
inbound.readChecksum(obj.term, 0);
|
|
300
|
+
inbound.process();
|
|
301
|
+
}
|
|
302
|
+
public responseFor: number[] = [];
|
|
303
|
+
public isProcessed: boolean = false;
|
|
304
|
+
public collisions: number = 0;
|
|
305
|
+
public rewinds: number = 0;
|
|
306
|
+
// Private methods
|
|
307
|
+
private isValidChecksum(): boolean {
|
|
308
|
+
switch (this.protocol) {
|
|
309
|
+
case Protocol.Chlorinator:
|
|
310
|
+
case Protocol.AquaLink:
|
|
311
|
+
return this.checksum % 256 === this.chkLo;
|
|
312
|
+
case Protocol.RegalModbus: {
|
|
313
|
+
const data = this.header.concat(this.payload);
|
|
314
|
+
const crcComputed = computeCRC16(data);
|
|
315
|
+
const crcReceived = (this.chkLo << 8) | this.chkHi;
|
|
316
|
+
return crcComputed === crcReceived;
|
|
317
|
+
}
|
|
318
|
+
default:
|
|
319
|
+
return (this.chkHi * 256) + this.chkLo === this.checksum;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
public toLog() {
|
|
323
|
+
if (this.responseFor.length > 0)
|
|
324
|
+
return `{"port":${this.portId || 0},"id":${this.id},"valid":${this.isValid},"dir":"${this.direction}","proto":"${this.protocol}","for":${JSON.stringify(this.responseFor)},"pkt":[${JSON.stringify(this.padding)},${JSON.stringify(this.preamble)},${JSON.stringify(this.header)},${JSON.stringify(this.payload)},${JSON.stringify(this.term)}],"ts": "${Timestamp.toISOLocal(this.timestamp)}"}`;
|
|
325
|
+
return `{"port":${this.portId || 0},"id":${this.id},"valid":${this.isValid},"dir":"${this.direction}","proto":"${this.protocol}","pkt":[${JSON.stringify(this.padding)},${JSON.stringify(this.preamble)},${JSON.stringify(this.header)},${JSON.stringify(this.payload)},${JSON.stringify(this.term)}],"ts": "${Timestamp.toISOLocal(this.timestamp)}"}`;
|
|
326
|
+
}
|
|
327
|
+
private testChlorHeader(bytes: number[], ndx: number): boolean {
|
|
328
|
+
// if packets have 16,2 (eg status=16,2,29) in them and they come as partial packets, they would have
|
|
329
|
+
// prev been detected as chlor packets;
|
|
330
|
+
// valid chlor packets should have 16,2,0 or 16,2,[80-96];
|
|
331
|
+
// this should reduce the number of false chlor packets
|
|
332
|
+
// For any of these 16,2 type headers we need at least 5 bytes to determine the routing.
|
|
333
|
+
//63,15,16,2,29,9,36,0,0,0,0,0,16,0,32,0,0,2,0,75,75,32,241,80,85,24,241,16,16,48,245,69,45,100,186,16,2,80,17,0,115,16,3
|
|
334
|
+
if (bytes.length > ndx + 4) {
|
|
335
|
+
if (bytes[ndx] === 16 && bytes[ndx + 1] === 2) {
|
|
336
|
+
let dst = bytes[ndx + 2];
|
|
337
|
+
let act = bytes[ndx + 3];
|
|
338
|
+
// For now the dst byte will always be 0 or 80.
|
|
339
|
+
if (![0, 16, 80, 81, 82, 83].includes(dst)) {
|
|
340
|
+
//logger.info(`Sensed chlorinator header but the dst byte is ${dst}`);
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
else if (dst === 0 && [1, 18, 3].includes(act))
|
|
344
|
+
return true;
|
|
345
|
+
else if (![0, 17, 19, 20, 21, 22].includes(act)) {
|
|
346
|
+
//logger.info(`Sensed out chlorinator header but the dst byte is ${dst} ${act} ${JSON.stringify(bytes)}`);
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
private testRegalModbusHeader(bytes: number[], ndx: number): boolean {
|
|
355
|
+
// RegalModbus protocol: header, function, ack, payload, crcLo, crcHi
|
|
356
|
+
if (bytes.length > ndx + 3 && sys.controllerType === 'nixie') {
|
|
357
|
+
// address must be in the range 0x15 to 0xF7
|
|
358
|
+
// function code must be in the range 0x00 to 0x7F
|
|
359
|
+
// ack must be in 0x10, 0x20, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x09, 0x0A
|
|
360
|
+
let addr = bytes[ndx];
|
|
361
|
+
let func = bytes[ndx + 1];
|
|
362
|
+
let ack = bytes[ndx + 2];
|
|
363
|
+
let acceptableAcks = [0x10, 0x20, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x09, 0x0A];
|
|
364
|
+
|
|
365
|
+
// logger.debug('Testing RegalModbus header', bytes, addr, func, ack, acceptableAcks.includes(ack));
|
|
366
|
+
// logger.debug(`Current bytes: ${JSON.stringify(bytes)}`);
|
|
367
|
+
|
|
368
|
+
if (addr >= 0x15 && addr <= 0xF7 && func >= 0x00 && func <= 0x7F && acceptableAcks.includes(ack)) {
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
private testAquaLinkHeader(bytes: number[], ndx: number): boolean {
|
|
375
|
+
if (bytes.length > ndx + 4 && sys.controllerType === 'aqualink') {
|
|
376
|
+
if (bytes[ndx] === 16 && bytes[ndx + 1] === 2) {
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
private testHaywardHeader(bytes: number[], ndx: number): boolean {
|
|
383
|
+
//0x10, 0x02, 0x0C, 0x01, 0x00, 0x2D, 0x00, 0x4C, 0x10, 0x03 -- Command to pump
|
|
384
|
+
//[16,2,12,1,0]
|
|
385
|
+
//0x10, 0x02, 0x0C, 0x01, 0x00, 0x2D, 0x00, 0x4C, 0x10, 0x03 -- Command to Filter Pump
|
|
386
|
+
//[16,2,12,1,0]
|
|
387
|
+
//0x10, 0x02, 0x0C, 0x01, 0x02, 0x2D, 0x00, 0x4E, 0x10, 0x03 -- Command to AUX2 Pump
|
|
388
|
+
//[16,2,12,1,2]
|
|
389
|
+
// src act dest
|
|
390
|
+
//0x10, 0x02, 0x00, 0x0C, 0x00, 0x00, 0x2D, 0x02, 0x36, 0x00, 0x83, 0x10, 0x03 -- Response from pump
|
|
391
|
+
//[16,2,0,12,0] --> Response
|
|
392
|
+
//[16,2,0,12,0]
|
|
393
|
+
if (bytes.length > ndx + 4) {
|
|
394
|
+
if (sys.controllerType === 'aqualink') return false;
|
|
395
|
+
if (bytes[ndx] === 16 && bytes[ndx + 1] === 2) {
|
|
396
|
+
let dst = bytes[ndx + 3];
|
|
397
|
+
let src = bytes[ndx + 2];
|
|
398
|
+
if (dst === 12 || src === 12) return true;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
private testBroadcastHeader(bytes: number[], ndx: number): boolean {
|
|
404
|
+
// We are looking for [255,0,255,165]
|
|
405
|
+
if (bytes.length > ndx + 3) {
|
|
406
|
+
if (bytes[ndx] === 255 && bytes[ndx + 1] === 0 && bytes[ndx + 2] === 255 && bytes[ndx + 3] === 165) return true;
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
//return ndx < bytes.length - 3 && bytes[ndx] === 255 && bytes[ndx + 1] === 0 && bytes[ndx + 2] === 255 && bytes[ndx + 3] === 165;
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
private testUnidentifiedHeader(bytes: number[], ndx: number): boolean {
|
|
413
|
+
if (bytes.length > ndx + 3) {
|
|
414
|
+
if (bytes[ndx] === 255 && bytes[ndx + 1] === 0 && bytes[ndx + 2] === 255 && bytes[ndx + 3] !== 165) return true;
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
//return ndx < bytes.length - 3 && bytes[ndx] === 255 && bytes[ndx + 1] === 0 && bytes[ndx + 2] === 255 && bytes[ndx + 3] !== 165;
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
private testChlorTerm(bytes: number[], ndx: number): boolean { return ndx + 2 < bytes.length && bytes[ndx + 1] === 16 && bytes[ndx + 2] === 3; }
|
|
421
|
+
private testAquaLinkTerm(bytes: number[], ndx: number): boolean { return ndx + 2 < bytes.length && bytes[ndx + 1] === 16 && bytes[ndx + 2] === 3; }
|
|
422
|
+
private testHaywardTerm(bytes: number[], ndx: number): boolean { return ndx + 3 < bytes.length && bytes[ndx + 2] === 16 && bytes[ndx + 3] === 3; }
|
|
423
|
+
private pushBytes(target: number[], bytes: number[], ndx: number, length: number): number {
|
|
424
|
+
let end = ndx + length;
|
|
425
|
+
while (ndx < bytes.length && ndx < end)
|
|
426
|
+
target.push(bytes[ndx++]);
|
|
427
|
+
return ndx;
|
|
428
|
+
}
|
|
429
|
+
// Methods
|
|
430
|
+
public rewind(bytes: number[], ndx: number): number {
|
|
431
|
+
let buff = [];
|
|
432
|
+
//buff.push(...this.padding);
|
|
433
|
+
//buff.push(...this.preamble);
|
|
434
|
+
buff.push(...this.header);
|
|
435
|
+
buff.push(...this.payload);
|
|
436
|
+
buff.push(...this.term);
|
|
437
|
+
// Add in the remaining bytes.
|
|
438
|
+
if (ndx < bytes.length - 1) buff.push(...bytes.slice(ndx, bytes.length - 1));
|
|
439
|
+
this.padding.push(...this.preamble);
|
|
440
|
+
this.preamble.length = 0;
|
|
441
|
+
this.header.length = 0;
|
|
442
|
+
this.payload.length = 0;
|
|
443
|
+
this.term.length = 0;
|
|
444
|
+
buff.shift();
|
|
445
|
+
this.protocol = Protocol.Unknown;
|
|
446
|
+
this._complete = false;
|
|
447
|
+
this.isValid = true;
|
|
448
|
+
|
|
449
|
+
this.collisions++;
|
|
450
|
+
this.rewinds++;
|
|
451
|
+
logger.info(`rewinding message collision ${this.collisions} ${ndx} ${bytes.length} ${JSON.stringify(buff)}`);
|
|
452
|
+
this.readPacket(buff);
|
|
453
|
+
return ndx;
|
|
454
|
+
//return this.padding.length + this.preamble.length;
|
|
455
|
+
}
|
|
456
|
+
public readPacket(bytes: number[]): number {
|
|
457
|
+
//logger.info(`BYTES: ${JSON.stringify(bytes)}`);
|
|
458
|
+
var ndx = this.readHeader(bytes, 0);
|
|
459
|
+
if (this.isValid && this.header.length > 0) ndx = this.readPayload(bytes, ndx);
|
|
460
|
+
if (this.isValid && this.header.length > 0) ndx = this.readChecksum(bytes, ndx);
|
|
461
|
+
if (this.isComplete && !this.isValid) return this.rewind(bytes, ndx);
|
|
462
|
+
return ndx;
|
|
463
|
+
}
|
|
464
|
+
public mergeBytes(bytes) {
|
|
465
|
+
var ndx = 0;
|
|
466
|
+
if (this.header.length === 0) ndx = this.readHeader(bytes, ndx);
|
|
467
|
+
if (this.isValid && this.header.length > 0) ndx = this.readPayload(bytes, ndx);
|
|
468
|
+
if (this.isValid && this.header.length > 0) ndx = this.readChecksum(bytes, ndx);
|
|
469
|
+
//if (this.isComplete && !this.isValid) return this.rewind(bytes, ndx);
|
|
470
|
+
return ndx;
|
|
471
|
+
}
|
|
472
|
+
public readHeader(bytes: number[], ndx: number): number {
|
|
473
|
+
// start over to include the padding bytes.
|
|
474
|
+
//if (this.protocol !== Protocol.Unknown) {
|
|
475
|
+
// logger.warn(`${this.protocol} resulted in an empty message header ${JSON.stringify(this.header)}`);
|
|
476
|
+
//}
|
|
477
|
+
let ndxStart = ndx;
|
|
478
|
+
// RKS: 05-30-22 -- OMG we have not been dealing with short headers. As a result it was restarting
|
|
479
|
+
// the header process even after it had identified it.
|
|
480
|
+
if (this.protocol === Protocol.Unknown) {
|
|
481
|
+
while (ndx < bytes.length) {
|
|
482
|
+
if (this.testBroadcastHeader(bytes, ndx)) {
|
|
483
|
+
this.protocol = Protocol.Broadcast;
|
|
484
|
+
break;
|
|
485
|
+
}
|
|
486
|
+
if (this.testUnidentifiedHeader(bytes, ndx)) {
|
|
487
|
+
this.protocol = Protocol.Unidentified;
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
if (this.testChlorHeader(bytes, ndx)) {
|
|
491
|
+
this.protocol = Protocol.Chlorinator;
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
if (this.testAquaLinkHeader(bytes, ndx)) {
|
|
495
|
+
this.protocol = Protocol.AquaLink;
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
if (this.testHaywardHeader(bytes, ndx)) {
|
|
499
|
+
this.protocol = Protocol.Hayward;
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
502
|
+
if (this.testRegalModbusHeader(bytes, ndx)) {
|
|
503
|
+
this.protocol = Protocol.RegalModbus;
|
|
504
|
+
logger.debug(`RegalModbus header detected. ${JSON.stringify(bytes)}`);
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
this.padding.push(bytes[ndx++]);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
// When the code above finds a protocol, ndx will be at the start of that
|
|
511
|
+
// header. If it is not identified then it will rewind to the initial
|
|
512
|
+
// start position until we get more bytes. This is the default case below.
|
|
513
|
+
let ndxHeader = ndx;
|
|
514
|
+
switch (this.protocol) {
|
|
515
|
+
case Protocol.Pump:
|
|
516
|
+
case Protocol.IntelliChem:
|
|
517
|
+
case Protocol.IntelliValve:
|
|
518
|
+
case Protocol.Broadcast:
|
|
519
|
+
case Protocol.Heater:
|
|
520
|
+
case Protocol.Unidentified:
|
|
521
|
+
ndx = this.pushBytes(this.preamble, bytes, ndx, 3);
|
|
522
|
+
ndx = this.pushBytes(this.header, bytes, ndx, 6);
|
|
523
|
+
if (this.header.length < 6) {
|
|
524
|
+
// We actually don't have a complete header yet so just return.
|
|
525
|
+
// we will pick it up next go around.
|
|
526
|
+
// logger.debug(`We have an incoming message but the serial port hasn't given a complete header. [${this.padding}][${this.preamble}][${this.header}]`);
|
|
527
|
+
//logger.info(`We don't have a complete header ${JSON.stringify(this.header)}`);
|
|
528
|
+
this.preamble = [];
|
|
529
|
+
this.header = [];
|
|
530
|
+
return ndxHeader;
|
|
531
|
+
}
|
|
532
|
+
if (this.source >= 96 && this.source <= 111) this.protocol = Protocol.Pump;
|
|
533
|
+
else if (this.dest >= 96 && this.dest <= 111) this.protocol = Protocol.Pump;
|
|
534
|
+
else if (this.source >= 112 && this.source <= 127) this.protocol = Protocol.Heater;
|
|
535
|
+
else if (this.dest >= 112 && this.dest <= 127) this.protocol = Protocol.Heater;
|
|
536
|
+
else if (this.dest >= 144 && this.dest <= 158) this.protocol = Protocol.IntelliChem;
|
|
537
|
+
else if (this.source >= 144 && this.source <= 158) this.protocol = Protocol.IntelliChem;
|
|
538
|
+
else if (this.source == 12 || this.dest == 12) this.protocol = Protocol.IntelliValve;
|
|
539
|
+
if (this.datalen > 75) {
|
|
540
|
+
//this.isValid = false;
|
|
541
|
+
logger.debug(`Broadcast length ${this.datalen} exceeded 75 bytes for ${this.protocol} message. Message rewound ${this.header}`);
|
|
542
|
+
this.padding.push(...this.preamble);
|
|
543
|
+
this.padding.push(...this.header.slice(0, 1));
|
|
544
|
+
this.preamble = [];
|
|
545
|
+
this.header = [];
|
|
546
|
+
this.collisions++;
|
|
547
|
+
this.rewinds++;
|
|
548
|
+
return ndxHeader + 1;
|
|
549
|
+
}
|
|
550
|
+
break;
|
|
551
|
+
case Protocol.Chlorinator:
|
|
552
|
+
// RKS: 06-06-20 We occasionally get messages where the 16, 2 is interrupted. The message below
|
|
553
|
+
// has an IntelliValve broadcast embedded within as well as a chlorinator status request. So
|
|
554
|
+
// in the instance below we have two messages being tossed because something on the bus interrupted
|
|
555
|
+
// the chlorinator. The first 240 byte does not belong to the chlorinator nor does it belong to
|
|
556
|
+
// the IntelliValve
|
|
557
|
+
//[][16, 2, 240][255, 0, 255, 165, 1, 16, 12, 82, 8, 0, 128, 216, 128, 57, 64, 25, 166, 4, 44, 16, 2, 80, 17, 0][115, 16, 3]
|
|
558
|
+
//[][16, 2, 80, 17][0][115, 16, 3]
|
|
559
|
+
ndx = this.pushBytes(this.header, bytes, ndx, 4);
|
|
560
|
+
if (this.header.length < 4) {
|
|
561
|
+
// We actually don't have a complete header yet so just return.
|
|
562
|
+
// we will pick it up next go around.
|
|
563
|
+
logger.debug(`We have an incoming chlorinator message but the serial port hasn't given a complete header. [${this.padding}][${this.preamble}][${this.header}]`);
|
|
564
|
+
this.preamble = [];
|
|
565
|
+
this.header = [];
|
|
566
|
+
return ndxHeader;
|
|
567
|
+
}
|
|
568
|
+
break;
|
|
569
|
+
case Protocol.Hayward:
|
|
570
|
+
ndx = this.pushBytes(this.header, bytes, ndx, 5);
|
|
571
|
+
if (this.header.length < 4) {
|
|
572
|
+
// We actually don't have a complete header yet so just return.
|
|
573
|
+
// we will pick it up next go around.
|
|
574
|
+
logger.debug(`We have an incoming Hayward message but the serial port hasn't given a complete header. [${this.padding}][${this.preamble}][${this.header}]`);
|
|
575
|
+
this.preamble = [];
|
|
576
|
+
this.header = [];
|
|
577
|
+
return ndxHeader;
|
|
578
|
+
}
|
|
579
|
+
break;
|
|
580
|
+
case Protocol.AquaLink:
|
|
581
|
+
ndx = this.pushBytes(this.header, bytes, ndx, 5);
|
|
582
|
+
if (this.header.length < 5) {
|
|
583
|
+
// We actually don't have a complete header yet so just return.
|
|
584
|
+
// we will pick it up next go around.
|
|
585
|
+
logger.debug(`We have an incoming AquaLink message but the serial port hasn't given a complete header. [${this.padding}][${this.preamble}][${this.header}]`);
|
|
586
|
+
this.preamble = [];
|
|
587
|
+
this.header = [];
|
|
588
|
+
return ndxHeader;
|
|
589
|
+
}
|
|
590
|
+
break;
|
|
591
|
+
case Protocol.RegalModbus:
|
|
592
|
+
ndx = this.pushBytes(this.header, bytes, ndx, 3);
|
|
593
|
+
if (this.header.length < 3) {
|
|
594
|
+
// We actually don't have a complete header yet so just return.
|
|
595
|
+
// we will pick it up next go around.
|
|
596
|
+
logger.debug(`We have an incoming RegalModbus message but the serial port hasn't given a complete header. [${this.padding}][${this.preamble}][${this.header}]`);
|
|
597
|
+
this.preamble = [];
|
|
598
|
+
this.header = [];
|
|
599
|
+
return ndxHeader;
|
|
600
|
+
}
|
|
601
|
+
break;
|
|
602
|
+
default:
|
|
603
|
+
// We didn't get a message signature. don't do anything with it.
|
|
604
|
+
ndx = ndxStart;
|
|
605
|
+
if (bytes.length > 24) {
|
|
606
|
+
// The length of the incoming bytes have exceeded 24 bytes. This is very likely
|
|
607
|
+
// flat out garbage on the serial port. Strip off all but the last 5 preamble + signature bytes and move on. Heck we aren't even
|
|
608
|
+
// going to keep them.
|
|
609
|
+
// 255, 255, 255, 0, 255
|
|
610
|
+
ndx = bytes.length - 5;
|
|
611
|
+
let arr = bytes.slice(0, ndx);
|
|
612
|
+
// Remove all but the last 4 bytes. This will result in nothing anyway.
|
|
613
|
+
logger.verbose(`[Port ${this.portId}] Tossed Inbound Bytes ${arr} due to an unrecoverable collision.`);
|
|
614
|
+
}
|
|
615
|
+
this.padding = [];
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
return ndx;
|
|
619
|
+
}
|
|
620
|
+
public readPayload(bytes: number[], ndx: number): number {
|
|
621
|
+
//if (!this.isValid) return bytes.length;
|
|
622
|
+
if (!this.isValid) return ndx;
|
|
623
|
+
switch (this.protocol) {
|
|
624
|
+
case Protocol.Broadcast:
|
|
625
|
+
case Protocol.Pump:
|
|
626
|
+
case Protocol.IntelliChem:
|
|
627
|
+
case Protocol.IntelliValve:
|
|
628
|
+
case Protocol.Heater:
|
|
629
|
+
case Protocol.Unidentified:
|
|
630
|
+
if (this.datalen - this.payload.length <= 0) {
|
|
631
|
+
let buff = bytes.slice(ndx - 1);
|
|
632
|
+
//logger.info(`We don't need any more payload ${this.datalen - this.payload.length} ${ndx} ${JSON.stringify(buff)};`);
|
|
633
|
+
return ndx; // We don't need any more payload.
|
|
634
|
+
}
|
|
635
|
+
ndx = this.pushBytes(this.payload, bytes, ndx, this.datalen - this.payload.length);
|
|
636
|
+
break;
|
|
637
|
+
case Protocol.Chlorinator:
|
|
638
|
+
// We need to deal with chlorinator packets where the terminator is actually split meaning only the first byte or
|
|
639
|
+
// two of the total payload is provided for the term. We need at least 3 bytes to make this determination.
|
|
640
|
+
while (ndx + 3 <= bytes.length && !this.testChlorTerm(bytes, ndx)) {
|
|
641
|
+
this.payload.push(bytes[ndx++]);
|
|
642
|
+
if (this.payload.length > 25) {
|
|
643
|
+
this.isValid = false; // We have a runaway packet. Some collision occurred so lets preserve future packets.
|
|
644
|
+
logger.debug(`Chlorinator message marked as invalid after not finding 16,3 in payload after ${this.payload.length} bytes`);
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
break;
|
|
649
|
+
case Protocol.AquaLink:
|
|
650
|
+
// We need to deal with AquaLink packets where the terminator is actually split meaning only the first byte or
|
|
651
|
+
// two of the total payload is provided for the term. We need at least 3 bytes to make this determination.
|
|
652
|
+
while (ndx + 3 <= bytes.length && !this.testAquaLinkTerm(bytes, ndx)) {
|
|
653
|
+
this.payload.push(bytes[ndx++]);
|
|
654
|
+
if (this.payload.length > 25) {
|
|
655
|
+
this.isValid = false; // We have a runaway packet. Some collision occurred so lets preserve future packets.
|
|
656
|
+
logger.debug(`AquaLink message marked as invalid after not finding 16,3 in payload after ${this.payload.length} bytes`);
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
break;
|
|
661
|
+
case Protocol.Hayward:
|
|
662
|
+
// We need to deal with AquaLink packets where the terminator is actually split meaning only the first byte or
|
|
663
|
+
// two of the total payload is provided for the term. We need at least 3 bytes to make this determination.
|
|
664
|
+
while (ndx + 4 <= bytes.length && !this.testHaywardTerm(bytes, ndx)) {
|
|
665
|
+
this.payload.push(bytes[ndx++]);
|
|
666
|
+
if (this.payload.length > 25) {
|
|
667
|
+
this.isValid = false; // We have a runaway packet. Some collision occurred so lets preserve future packets.
|
|
668
|
+
logger.debug(`Hayward message marked as invalid after not finding 16,3 in payload after ${this.payload.length} bytes`);
|
|
669
|
+
break;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
break;
|
|
673
|
+
case Protocol.RegalModbus:
|
|
674
|
+
// RegalModbus protocol: header, function, ack, payload, crcLo, crcHi
|
|
675
|
+
while (ndx + 3 <= bytes.length) {
|
|
676
|
+
this.payload.push(bytes[ndx++]);
|
|
677
|
+
if (this.payload.length > 11) {
|
|
678
|
+
this.isValid = false; // We have a runaway packet. Some collision occurred so lets preserve future packets.
|
|
679
|
+
logger.debug(`RegalModbus message marked as invalid due to payload more than 11 bytes`);
|
|
680
|
+
break;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
break;
|
|
684
|
+
|
|
685
|
+
}
|
|
686
|
+
return ndx;
|
|
687
|
+
}
|
|
688
|
+
public readChecksum(bytes: number[], ndx: number): number {
|
|
689
|
+
if (!this.isValid) return bytes.length;
|
|
690
|
+
if (ndx >= bytes.length) return ndx;
|
|
691
|
+
switch (this.protocol) {
|
|
692
|
+
case Protocol.Broadcast:
|
|
693
|
+
case Protocol.Pump:
|
|
694
|
+
case Protocol.IntelliValve:
|
|
695
|
+
case Protocol.IntelliChem:
|
|
696
|
+
case Protocol.Heater:
|
|
697
|
+
case Protocol.RegalModbus:
|
|
698
|
+
case Protocol.Unidentified:
|
|
699
|
+
// If we don't have enough bytes to make the terminator then continue on and
|
|
700
|
+
// hope we get them on the next go around.
|
|
701
|
+
if (this.payload.length >= this.datalen && ndx + 2 <= bytes.length) {
|
|
702
|
+
this._complete = true;
|
|
703
|
+
ndx = this.pushBytes(this.term, bytes, ndx, 2);
|
|
704
|
+
this.isValid = this.isValidChecksum();
|
|
705
|
+
}
|
|
706
|
+
break;
|
|
707
|
+
case Protocol.Chlorinator:
|
|
708
|
+
if (ndx + 3 <= bytes.length && this.testChlorTerm(bytes, ndx)) {
|
|
709
|
+
this._complete = true;
|
|
710
|
+
ndx = this.pushBytes(this.term, bytes, ndx, 3);
|
|
711
|
+
this.isValid = this.isValidChecksum();
|
|
712
|
+
}
|
|
713
|
+
break;
|
|
714
|
+
case Protocol.AquaLink:
|
|
715
|
+
if (ndx + 3 <= bytes.length && this.testAquaLinkTerm(bytes, ndx)) {
|
|
716
|
+
this._complete = true;
|
|
717
|
+
ndx = this.pushBytes(this.term, bytes, ndx, 3);
|
|
718
|
+
this.isValid = this.isValidChecksum();
|
|
719
|
+
}
|
|
720
|
+
break;
|
|
721
|
+
case Protocol.Hayward:
|
|
722
|
+
if (ndx + 4 <= bytes.length && this.testHaywardTerm(bytes, ndx)) {
|
|
723
|
+
this._complete = true;
|
|
724
|
+
ndx = this.pushBytes(this.term, bytes, ndx, 4);
|
|
725
|
+
this.isValid = this.isValidChecksum();
|
|
726
|
+
}
|
|
727
|
+
break;
|
|
728
|
+
|
|
729
|
+
}
|
|
730
|
+
return ndx;
|
|
731
|
+
}
|
|
732
|
+
public extractPayloadString(start: number, length: number) {
|
|
733
|
+
var s = '';
|
|
734
|
+
for (var i = start; i < this.payload.length && i < start + length; i++) {
|
|
735
|
+
if (this.payload[i] <= 0) break;
|
|
736
|
+
s += String.fromCharCode(this.payload[i]);
|
|
737
|
+
}
|
|
738
|
+
return s;
|
|
739
|
+
}
|
|
740
|
+
// return Little Endian Int
|
|
741
|
+
public extractPayloadInt(ndx: number, def?: number) {
|
|
742
|
+
return ndx + 1 < this.payload.length ? (this.payload[ndx + 1] * 256) + this.payload[ndx] : def;
|
|
743
|
+
|
|
744
|
+
}
|
|
745
|
+
// return Big Endian Int
|
|
746
|
+
public extractPayloadIntBE(ndx: number, endian = 'le', def?: number) {
|
|
747
|
+
return ndx + 1 < this.payload.length ? (this.payload[ndx] * 256) + this.payload[ndx + 1] : def;
|
|
748
|
+
}
|
|
749
|
+
public extractPayloadByte(ndx: number, def?: number) {
|
|
750
|
+
return ndx < this.payload.length ? this.payload[ndx] : def;
|
|
751
|
+
}
|
|
752
|
+
private processBroadcast(): void {
|
|
753
|
+
if (this.action !== 2 && !state.isInitialized) {
|
|
754
|
+
// RKS: This is a placeholder for now so that messages aren't processed until we
|
|
755
|
+
// are certain who is on the other end of the wire. Once the system config is normalized
|
|
756
|
+
// we won't need this check here anymore.
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
switch (sys.controllerType) {
|
|
760
|
+
// RKS: 10-10-20 - We have a message somewhere that is ending up in a process for one of the other controllers. This
|
|
761
|
+
// makes sure we are processing every message and alerting when a message is not being processed.
|
|
762
|
+
case ControllerType.IntelliCenter:
|
|
763
|
+
switch (this.action) {
|
|
764
|
+
case 1: // ACK
|
|
765
|
+
VersionMessage.processAction168Ack(this);
|
|
766
|
+
break;
|
|
767
|
+
case 2:
|
|
768
|
+
case 204:
|
|
769
|
+
EquipmentStateMessage.process(this);
|
|
770
|
+
break;
|
|
771
|
+
case 30:
|
|
772
|
+
ConfigMessage.process(this);
|
|
773
|
+
break;
|
|
774
|
+
case 147: // Not sure whether this is only for *Touch. If it is not then it probably should have been caught by the protocol.
|
|
775
|
+
IntelliChemStateMessage.process(this);
|
|
776
|
+
break;
|
|
777
|
+
case 164:
|
|
778
|
+
VersionMessage.process(this);
|
|
779
|
+
break;
|
|
780
|
+
case 168:
|
|
781
|
+
ExternalMessage.processIntelliCenter(this);
|
|
782
|
+
break;
|
|
783
|
+
case 179: // v3.004+ Heartbeat request - handled by EquipmentStateMessage
|
|
784
|
+
EquipmentStateMessage.process(this);
|
|
785
|
+
break;
|
|
786
|
+
case 180: // v3.004+ Heartbeat response/status (may be sent by other devices)
|
|
787
|
+
// No processing required; mark as handled to avoid noisy "not processed" logs.
|
|
788
|
+
this.isProcessed = true;
|
|
789
|
+
break;
|
|
790
|
+
case 184: // v3.004+ Circuit control from wireless remote (replaces Action 134)
|
|
791
|
+
// Wireless remote sends this to control circuits
|
|
792
|
+
// Currently handled by EquipmentStateMessage for logging
|
|
793
|
+
EquipmentStateMessage.process(this);
|
|
794
|
+
break;
|
|
795
|
+
case 217: // v3.004+ Device list broadcast
|
|
796
|
+
EquipmentStateMessage.process(this);
|
|
797
|
+
break;
|
|
798
|
+
case 222: // A panel is asking for action 30s
|
|
799
|
+
this.isProcessed = true;
|
|
800
|
+
break;
|
|
801
|
+
case 228: // A panel is asking for the current version
|
|
802
|
+
VersionMessage.processVersionRequest(this);
|
|
803
|
+
break;
|
|
804
|
+
case 251: // v3.004+ Device announcement/registration request
|
|
805
|
+
// Devices send this to announce presence to OCP
|
|
806
|
+
// Payload byte 0: device address
|
|
807
|
+
// Response: Action 253
|
|
808
|
+
this.isProcessed = true;
|
|
809
|
+
break;
|
|
810
|
+
case 253: // v3.004+ Device registration confirmation
|
|
811
|
+
// OCP sends this in response to Action 251
|
|
812
|
+
// Payload byte 2: 1 = registered, 0 = not registered
|
|
813
|
+
logger.info(`Device registration confirmed: ${this.toPacket()}`);
|
|
814
|
+
this.isProcessed = true;
|
|
815
|
+
break;
|
|
816
|
+
default:
|
|
817
|
+
logger.info(`An unprocessed message was received ${this.toPacket()}`)
|
|
818
|
+
break;
|
|
819
|
+
|
|
820
|
+
}
|
|
821
|
+
if (!this.isProcessed) logger.info(`The message was not processed ${this.action} - ${this.toPacket()}`);
|
|
822
|
+
break;
|
|
823
|
+
default:
|
|
824
|
+
switch (this.action) {
|
|
825
|
+
case 1: // Ack
|
|
826
|
+
break;
|
|
827
|
+
case 2: // Shared IntelliCenter/IntelliTouch
|
|
828
|
+
case 5:
|
|
829
|
+
case 8:
|
|
830
|
+
case 96: // intellibrite lights
|
|
831
|
+
EquipmentStateMessage.process(this);
|
|
832
|
+
break;
|
|
833
|
+
// IntelliTouch
|
|
834
|
+
case 10:
|
|
835
|
+
CustomNameMessage.process(this);
|
|
836
|
+
break;
|
|
837
|
+
case 11:
|
|
838
|
+
CircuitMessage.processTouch(this);
|
|
839
|
+
break;
|
|
840
|
+
case 25:
|
|
841
|
+
ChlorinatorMessage.processTouch(this);
|
|
842
|
+
break;
|
|
843
|
+
case 153:
|
|
844
|
+
ExternalMessage.processTouchChlorinator(this);
|
|
845
|
+
break;
|
|
846
|
+
case 17:
|
|
847
|
+
case 145:
|
|
848
|
+
ScheduleMessage.process(this);
|
|
849
|
+
break;
|
|
850
|
+
case 18:
|
|
851
|
+
IntellichemMessage.process(this);
|
|
852
|
+
break;
|
|
853
|
+
case 24:
|
|
854
|
+
case 27:
|
|
855
|
+
case 152:
|
|
856
|
+
case 155:
|
|
857
|
+
PumpMessage.process(this);
|
|
858
|
+
break;
|
|
859
|
+
case 30:
|
|
860
|
+
switch (sys.controllerType) {
|
|
861
|
+
case ControllerType.Unknown:
|
|
862
|
+
break;
|
|
863
|
+
case ControllerType.SunTouch:
|
|
864
|
+
ScheduleMessage.processSunTouch(this);
|
|
865
|
+
break;
|
|
866
|
+
default:
|
|
867
|
+
OptionsMessage.process(this);
|
|
868
|
+
break;
|
|
869
|
+
}
|
|
870
|
+
break;
|
|
871
|
+
case 22:
|
|
872
|
+
case 32:
|
|
873
|
+
case 33:
|
|
874
|
+
RemoteMessage.process(this);
|
|
875
|
+
break;
|
|
876
|
+
case 29:
|
|
877
|
+
case 35:
|
|
878
|
+
ValveMessage.process(this);
|
|
879
|
+
break;
|
|
880
|
+
case 39:
|
|
881
|
+
case 167:
|
|
882
|
+
CircuitMessage.processTouch(this);
|
|
883
|
+
break;
|
|
884
|
+
case 40:
|
|
885
|
+
case 168:
|
|
886
|
+
OptionsMessage.process(this);
|
|
887
|
+
break;
|
|
888
|
+
case 41:
|
|
889
|
+
CircuitGroupMessage.process(this);
|
|
890
|
+
break;
|
|
891
|
+
case 197:
|
|
892
|
+
EquipmentStateMessage.process(this); // Date/Time request
|
|
893
|
+
break;
|
|
894
|
+
case 252:
|
|
895
|
+
EquipmentMessage.process(this);
|
|
896
|
+
break;
|
|
897
|
+
case 9:
|
|
898
|
+
case 16:
|
|
899
|
+
case 34:
|
|
900
|
+
case 137:
|
|
901
|
+
case 144:
|
|
902
|
+
case 162:
|
|
903
|
+
HeaterMessage.process(this);
|
|
904
|
+
break;
|
|
905
|
+
case 114:
|
|
906
|
+
case 115:
|
|
907
|
+
HeaterStateMessage.process(this);
|
|
908
|
+
break
|
|
909
|
+
case 147:
|
|
910
|
+
IntellichemMessage.process(this);
|
|
911
|
+
break;
|
|
912
|
+
case 136:
|
|
913
|
+
ExternalMessage.processTouchSetHeatMode(this);
|
|
914
|
+
break;
|
|
915
|
+
default:
|
|
916
|
+
if (this.action === 109 && this.payload[1] === 3) break;
|
|
917
|
+
if (this.source === 17 && this.payload[0] === 109) break;
|
|
918
|
+
logger.debug(`Packet not processed: ${this.toPacket()}`);
|
|
919
|
+
break;
|
|
920
|
+
}
|
|
921
|
+
break;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
public process() {
|
|
925
|
+
let port = conn.findPortById(this.portId);
|
|
926
|
+
if (this.portId === sys.anslq25.portId) {
|
|
927
|
+
return MessagesMock.process(this);
|
|
928
|
+
}
|
|
929
|
+
if (port.mock && port.hasAssignedEquipment()){
|
|
930
|
+
return MessagesMock.process(this);
|
|
931
|
+
}
|
|
932
|
+
switch (this.protocol) {
|
|
933
|
+
case Protocol.Broadcast:
|
|
934
|
+
this.processBroadcast();
|
|
935
|
+
break;
|
|
936
|
+
case Protocol.IntelliValve:
|
|
937
|
+
IntelliValveStateMessage.process(this);
|
|
938
|
+
break;
|
|
939
|
+
case Protocol.IntelliChem:
|
|
940
|
+
IntelliChemStateMessage.process(this);
|
|
941
|
+
break;
|
|
942
|
+
case Protocol.Pump:
|
|
943
|
+
if ((this.source >= 96 && this.source <= 111) || (this.dest >= 96 && this.dest <= 111))
|
|
944
|
+
PumpStateMessage.process(this);
|
|
945
|
+
else
|
|
946
|
+
this.processBroadcast();
|
|
947
|
+
break;
|
|
948
|
+
case Protocol.Heater:
|
|
949
|
+
HeaterStateMessage.process(this);
|
|
950
|
+
break;
|
|
951
|
+
case Protocol.Chlorinator:
|
|
952
|
+
ChlorinatorStateMessage.process(this);
|
|
953
|
+
break;
|
|
954
|
+
case Protocol.Hayward:
|
|
955
|
+
PumpStateMessage.processHayward(this);
|
|
956
|
+
break;
|
|
957
|
+
case Protocol.RegalModbus:
|
|
958
|
+
RegalModbusStateMessage.process(this);
|
|
959
|
+
break;
|
|
960
|
+
default:
|
|
961
|
+
logger.debug(`Unprocessed Message ${this.toPacket()}`)
|
|
962
|
+
break;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
class OutboundCommon extends Message {
|
|
967
|
+
public set sub(val: number) { if (this.protocol !== Protocol.Chlorinator && this.protocol !== Protocol.AquaLink) this.header[1] = val; }
|
|
968
|
+
public get sub() { return super.sub; }
|
|
969
|
+
public set dest(val: number) {
|
|
970
|
+
if (this.protocol === Protocol.Chlorinator) this.header[2] = val;
|
|
971
|
+
else if (this.protocol === Protocol.Hayward) this.header[4] = val;
|
|
972
|
+
else if (this.protocol === Protocol.RegalModbus) this.header[0] = val;
|
|
973
|
+
else this.header[2] = val;
|
|
974
|
+
}
|
|
975
|
+
public get dest() { return super.dest; }
|
|
976
|
+
public set source(val: number) {
|
|
977
|
+
switch (this.protocol) {
|
|
978
|
+
case Protocol.Chlorinator:
|
|
979
|
+
break;
|
|
980
|
+
case Protocol.Hayward:
|
|
981
|
+
this.header[3] = val;
|
|
982
|
+
break;
|
|
983
|
+
case Protocol.RegalModbus:
|
|
984
|
+
break;
|
|
985
|
+
default:
|
|
986
|
+
this.header[3] = val;
|
|
987
|
+
break;
|
|
988
|
+
}
|
|
989
|
+
//if (this.protocol === Protocol.Hayward) this.header[2] = val;
|
|
990
|
+
//else if (this.protocol !== Protocol.Chlorinator) this.header[3] = val;
|
|
991
|
+
}
|
|
992
|
+
public get source() { return super.source; }
|
|
993
|
+
public set action(val: number) {
|
|
994
|
+
switch (this.protocol) {
|
|
995
|
+
case Protocol.Chlorinator:
|
|
996
|
+
this.header[3] = val;
|
|
997
|
+
break;
|
|
998
|
+
case Protocol.Hayward:
|
|
999
|
+
this.header[2] = val;
|
|
1000
|
+
break;
|
|
1001
|
+
case Protocol.RegalModbus:
|
|
1002
|
+
this.header[1] = val;
|
|
1003
|
+
break;
|
|
1004
|
+
default:
|
|
1005
|
+
this.header[4] = val;
|
|
1006
|
+
break;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
public get action() { return super.action; }
|
|
1010
|
+
public set datalen(val: number) {
|
|
1011
|
+
if (this.protocol !== Protocol.Chlorinator && this.protocol !== Protocol.Hayward && this.protocol !== Protocol.RegalModbus) {
|
|
1012
|
+
this.header[5] = val;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
public get datalen() { return super.datalen; }
|
|
1016
|
+
public set chkHi(val: number) { if (this.protocol !== Protocol.Chlorinator) this.term[0] = val; }
|
|
1017
|
+
public get chkHi() { return super.chkHi; }
|
|
1018
|
+
public set chkLo(val: number) { if (this.protocol !== Protocol.Chlorinator) this.term[1] = val; else this.term[0] = val; }
|
|
1019
|
+
public get chkLo() { return super.chkLo; }
|
|
1020
|
+
// Methods
|
|
1021
|
+
public calcChecksum() {
|
|
1022
|
+
this.datalen = this.payload.length;
|
|
1023
|
+
let sum: number = this.checksum;
|
|
1024
|
+
switch (this.protocol) {
|
|
1025
|
+
case Protocol.Pump:
|
|
1026
|
+
case Protocol.Broadcast:
|
|
1027
|
+
case Protocol.IntelliValve:
|
|
1028
|
+
case Protocol.Unidentified:
|
|
1029
|
+
case Protocol.IntelliChem:
|
|
1030
|
+
case Protocol.Heater:
|
|
1031
|
+
case Protocol.Hayward:
|
|
1032
|
+
this.chkHi = Math.floor(sum / 256);
|
|
1033
|
+
this.chkLo = (sum - (this.chkHi * 256));
|
|
1034
|
+
break;
|
|
1035
|
+
case Protocol.AquaLink:
|
|
1036
|
+
case Protocol.Chlorinator:
|
|
1037
|
+
this.term[0] = sum % 256;
|
|
1038
|
+
break;
|
|
1039
|
+
case Protocol.RegalModbus:
|
|
1040
|
+
// Calculate checksum using the CRC16 algorithm and set chkHi and chkLo.
|
|
1041
|
+
// This.payload is expected to be an array of numbers (byte values 0–255)
|
|
1042
|
+
// combine header and payload for CRC calculation
|
|
1043
|
+
let data: number[] = this.header.concat(this.payload);
|
|
1044
|
+
const crc: number = computeCRC16(data);
|
|
1045
|
+
// Extract the high and low bytes from the 16-bit CRC:
|
|
1046
|
+
this.chkLo = (crc >> 8) & 0xFF;
|
|
1047
|
+
this.chkHi = crc & 0xFF;
|
|
1048
|
+
break;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
export class Outbound extends OutboundCommon {
|
|
1053
|
+
constructor(proto: Protocol, source: number, dest: number, action: number, payload: number[], retries?: number, response?: Response | boolean, scope?: string) {
|
|
1054
|
+
super();
|
|
1055
|
+
this.id = Message.nextMessageId;
|
|
1056
|
+
this.protocol = proto;
|
|
1057
|
+
this.direction = Direction.Out;
|
|
1058
|
+
this.retries = retries || 0;
|
|
1059
|
+
this.preamble.length = 0;
|
|
1060
|
+
this.header.length = 0;
|
|
1061
|
+
this.term.length = 0;
|
|
1062
|
+
this.payload.length = 0;
|
|
1063
|
+
if (proto === Protocol.Chlorinator || proto === Protocol.AquaLink) {
|
|
1064
|
+
this.header.push.apply(this.header, [16, 2, 0, 0]);
|
|
1065
|
+
this.term.push.apply(this.term, [0, 16, 3]);
|
|
1066
|
+
}
|
|
1067
|
+
else if (proto === Protocol.Broadcast) {
|
|
1068
|
+
this.preamble.push.apply(this.preamble, [255, 0, 255]);
|
|
1069
|
+
this.header.push.apply(this.header, [165, Message.headerSubByte, 15, Message.pluginAddress, 0, 0]);
|
|
1070
|
+
this.term.push.apply(this.term, [0, 0]);
|
|
1071
|
+
}
|
|
1072
|
+
else if (proto === Protocol.Pump || proto === Protocol.IntelliValve || proto === Protocol.IntelliChem || proto === Protocol.Heater) {
|
|
1073
|
+
this.preamble.push.apply(this.preamble, [255, 0, 255]);
|
|
1074
|
+
this.header.push.apply(this.header, [165, 0, 15, Message.pluginAddress, 0, 0]);
|
|
1075
|
+
this.term.push.apply(this.term, [0, 0]);
|
|
1076
|
+
}
|
|
1077
|
+
else if (proto === Protocol.Hayward) {
|
|
1078
|
+
this.header.push.apply(this.header, [16, 2, 0, 0, 0]);
|
|
1079
|
+
this.term.push.apply(this.term, [0, 0, 16, 3]);
|
|
1080
|
+
}
|
|
1081
|
+
else if (proto === Protocol.RegalModbus) {
|
|
1082
|
+
this.header.push.apply(this.header, [this.dest, this.action, 0x20]);
|
|
1083
|
+
}
|
|
1084
|
+
this.scope = scope;
|
|
1085
|
+
this.source = source;
|
|
1086
|
+
this.dest = dest;
|
|
1087
|
+
this.action = action;
|
|
1088
|
+
this.payload.push.apply(this.payload, payload);
|
|
1089
|
+
this.calcChecksum();
|
|
1090
|
+
if (typeof response === "boolean" && response)
|
|
1091
|
+
this.response = Response.create({ protocol: this.protocol, response: true });
|
|
1092
|
+
else
|
|
1093
|
+
this.response = response as Response;
|
|
1094
|
+
}
|
|
1095
|
+
// Factory
|
|
1096
|
+
public static create(obj?: any) {
|
|
1097
|
+
let o = extend({
|
|
1098
|
+
protocol: Protocol.Broadcast,
|
|
1099
|
+
source: sys.board.commandSourceAddress || Message.pluginAddress,
|
|
1100
|
+
dest: sys.board.commandDestAddress || 16,
|
|
1101
|
+
action: 0,
|
|
1102
|
+
payload: [],
|
|
1103
|
+
retries: 0,
|
|
1104
|
+
response: false,
|
|
1105
|
+
}, obj, true);
|
|
1106
|
+
let out = new Outbound(o.protocol, o.source, o.dest, o.action, o.payload, o.retries, o.response, o.scope);
|
|
1107
|
+
//let out = new Outbound(obj.protocol || Protocol.Broadcast,
|
|
1108
|
+
// obj.source || sys.board.commandSourceAddress || Message.pluginAddress, obj.dest || sys.board.commandDestAddress || 16, obj.action || 0, obj.payload || [], obj.retries || 0, obj.response || false, obj.scope || undefined);
|
|
1109
|
+
out.portId = obj.portId || 0;
|
|
1110
|
+
out.onComplete = obj.onComplete;
|
|
1111
|
+
out.onAbort = obj.onAbort;
|
|
1112
|
+
out.timeout = obj.timeout;
|
|
1113
|
+
for (let i = 0; i < out.header.length; i++) {
|
|
1114
|
+
if (out.header[i] >= 0 && out.header[i] <= 255 && out.header[i] !== null && typeof out.header[i] !== 'undefined') continue;
|
|
1115
|
+
throw new OutboundMessageError(out, `Invalid header detected: ${out.toShortPacket()}`);
|
|
1116
|
+
}
|
|
1117
|
+
for (let i = 0; i < out.payload.length; i++) {
|
|
1118
|
+
if (out.payload[i] >= 0 && out.payload[i] <= 255 && out.payload[i] !== null && typeof out.payload[i] !== 'undefined') continue;
|
|
1119
|
+
throw new OutboundMessageError(out, `Invalid payload detected: ${out.toShortPacket()}`);
|
|
1120
|
+
}
|
|
1121
|
+
return out;
|
|
1122
|
+
}
|
|
1123
|
+
public static createMessage(action: number, payload: number[], retries?: number, response?: Response | boolean): Outbound {
|
|
1124
|
+
return new Outbound(Protocol.Broadcast, sys.board.commandSourceAddress || Message.pluginAddress, sys.board.commandDestAddress || 16, action, payload, retries, response);
|
|
1125
|
+
}
|
|
1126
|
+
public async sendAsync() {
|
|
1127
|
+
return conn.queueSendMessageAsync(this);
|
|
1128
|
+
}
|
|
1129
|
+
// Fields
|
|
1130
|
+
public retries: number = 0;
|
|
1131
|
+
public tries: number = 0;
|
|
1132
|
+
public timeout: number = 1000;
|
|
1133
|
+
public response: Response;
|
|
1134
|
+
public failed: boolean = false;
|
|
1135
|
+
public onComplete: (error: Error, msg: Inbound) => void;
|
|
1136
|
+
public onAbort: () => void;
|
|
1137
|
+
// Properties
|
|
1138
|
+
public get requiresResponse(): boolean {
|
|
1139
|
+
if (typeof this.response === 'undefined' || (typeof this.response === 'boolean' && !this.response)) return false;
|
|
1140
|
+
if (this.response instanceof Response || typeof this.response === 'function') { return true; }
|
|
1141
|
+
return false;
|
|
1142
|
+
}
|
|
1143
|
+
public get remainingTries(): number { return this.retries - this.tries + 1; } // Always allow 1 try.
|
|
1144
|
+
public setPayloadByte(ndx: number, value: number, def?: number) {
|
|
1145
|
+
if (typeof value === 'undefined' || isNaN(value)) value = def;
|
|
1146
|
+
if (ndx < this.payload.length) this.payload[ndx] = value;
|
|
1147
|
+
return this;
|
|
1148
|
+
}
|
|
1149
|
+
public appendPayloadByte(value: number, def?: number) {
|
|
1150
|
+
if (typeof value === 'undefined' || isNaN(value)) value = def;
|
|
1151
|
+
this.payload.push(value);
|
|
1152
|
+
return this;
|
|
1153
|
+
}
|
|
1154
|
+
public appendPayloadBytes(value: number, len: number) {
|
|
1155
|
+
for (let i = 0; i < len; i++) this.payload.push(value);
|
|
1156
|
+
return this;
|
|
1157
|
+
}
|
|
1158
|
+
public setPayloadBytes(value: number, len: number) {
|
|
1159
|
+
for (let i = 0; i < len; i++) {
|
|
1160
|
+
if (i < this.payload.length) this.payload[i] = value;
|
|
1161
|
+
}
|
|
1162
|
+
return this;
|
|
1163
|
+
}
|
|
1164
|
+
public insertPayloadBytes(ndx: number, value: number, len: number) {
|
|
1165
|
+
let buf = [];
|
|
1166
|
+
for (let i = 0; i < len; i++) {
|
|
1167
|
+
buf.push(value);
|
|
1168
|
+
}
|
|
1169
|
+
this.payload.splice(ndx, 0, ...buf);
|
|
1170
|
+
return this;
|
|
1171
|
+
}
|
|
1172
|
+
public setPayloadInt(ndx: number, value: number, def?: number) {
|
|
1173
|
+
if (typeof value === 'undefined' || isNaN(value)) value = def;
|
|
1174
|
+
let b1 = Math.floor(value / 256);
|
|
1175
|
+
let b0 = value - (b1 * 256);
|
|
1176
|
+
if (ndx < this.payload.length) this.payload[ndx] = b0;
|
|
1177
|
+
if (ndx + 1 < this.payload.length) this.payload[ndx + 1] = b1;
|
|
1178
|
+
return this;
|
|
1179
|
+
}
|
|
1180
|
+
public appendPayloadInt(value: number, def?: number) {
|
|
1181
|
+
if (typeof value === 'undefined' || isNaN(value)) value = def;
|
|
1182
|
+
let b1 = Math.floor(value / 256);
|
|
1183
|
+
let b0 = value - (b1 * 256);
|
|
1184
|
+
this.payload.push(b0);
|
|
1185
|
+
this.payload.push(b1);
|
|
1186
|
+
return this;
|
|
1187
|
+
}
|
|
1188
|
+
public insertPayloadInt(ndx: number, value: number, def?: number) {
|
|
1189
|
+
if (typeof value === 'undefined' || isNaN(value)) value = def;
|
|
1190
|
+
let b1 = Math.floor(value / 256);
|
|
1191
|
+
let b0 = (value - b1) * 256;
|
|
1192
|
+
this.payload.splice(ndx, 0, b0, b1);
|
|
1193
|
+
return this;
|
|
1194
|
+
}
|
|
1195
|
+
public setPayloadString(s: string, len?: number, def?: string) {
|
|
1196
|
+
if (typeof s === 'undefined') s = def;
|
|
1197
|
+
for (var i = 0; i < s.length; i++) {
|
|
1198
|
+
if (i < this.payload.length) this.payload[i] = s.charCodeAt(i);
|
|
1199
|
+
}
|
|
1200
|
+
if (typeof (len) !== 'undefined') {
|
|
1201
|
+
for (var j = i; j < len; j++)
|
|
1202
|
+
if (i < this.payload.length) this.payload[i] = 0;
|
|
1203
|
+
}
|
|
1204
|
+
return this;
|
|
1205
|
+
}
|
|
1206
|
+
public appendPayloadString(s: string, len?: number, def?: string) {
|
|
1207
|
+
if (typeof s === 'undefined') s = def;
|
|
1208
|
+
for (var i = 0; i < s.length; i++) {
|
|
1209
|
+
if (typeof (len) !== 'undefined' && i >= len) break;
|
|
1210
|
+
this.payload.push(s.charCodeAt(i));
|
|
1211
|
+
}
|
|
1212
|
+
if (typeof (len) !== 'undefined') {
|
|
1213
|
+
for (var j = i; j < len; j++) this.payload.push(0);
|
|
1214
|
+
}
|
|
1215
|
+
return this;
|
|
1216
|
+
}
|
|
1217
|
+
public insertPayloadString(start: number, s: string, len?: number, def?: string) {
|
|
1218
|
+
if (typeof s === 'undefined') s = def;
|
|
1219
|
+
let l = typeof len === 'undefined' ? s.length : len;
|
|
1220
|
+
let buf = [];
|
|
1221
|
+
for (let i = 0; i < l; i++) {
|
|
1222
|
+
if (i < s.length) buf.push(s.charCodeAt(i));
|
|
1223
|
+
else buf.push(0);
|
|
1224
|
+
}
|
|
1225
|
+
this.payload.splice(start, l, ...buf);
|
|
1226
|
+
return this;
|
|
1227
|
+
}
|
|
1228
|
+
public toPacket(): number[] {
|
|
1229
|
+
var pkt = [];
|
|
1230
|
+
this.calcChecksum();
|
|
1231
|
+
pkt.push.apply(pkt, this.padding);
|
|
1232
|
+
pkt.push.apply(pkt, this.preamble);
|
|
1233
|
+
pkt.push.apply(pkt, this.header);
|
|
1234
|
+
pkt.push.apply(pkt, this.payload);
|
|
1235
|
+
pkt.push.apply(pkt, this.term);
|
|
1236
|
+
return pkt;
|
|
1237
|
+
}
|
|
1238
|
+
public processMock(){
|
|
1239
|
+
// When the port is a mock port, we are no longer sending an
|
|
1240
|
+
// outbound message but converting it to an inbound and
|
|
1241
|
+
// skipping the actual send/receive part of the comms.
|
|
1242
|
+
let inbound = Message.convertOutboundToInbound(this);
|
|
1243
|
+
let port = conn.findPortById(this.portId);
|
|
1244
|
+
if (port.hasAssignedEquipment() || this.portId === sys.anslq25.portId){
|
|
1245
|
+
MessagesMock.process(inbound);
|
|
1246
|
+
}
|
|
1247
|
+
else {
|
|
1248
|
+
inbound.process();
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
export class Ack extends Outbound {
|
|
1254
|
+
constructor(byte: number) {
|
|
1255
|
+
super(Protocol.Broadcast, Message.pluginAddress, 15, 1, [byte]);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
export class Response extends OutboundCommon {
|
|
1259
|
+
/*
|
|
1260
|
+
RG 6-2021: This class is now purely for identifying inbound messages and it is a property of the Outbound message.
|
|
1261
|
+
This can be created by passing response: Response.create({}) or response: boolean to the Outbound message.
|
|
1262
|
+
Response used to accept a function but that is deprecated.
|
|
1263
|
+
Response also no longer needs to be passed msgOut because that is the parent object/message and can be
|
|
1264
|
+
accessed via the internal symbol parent.
|
|
1265
|
+
*/
|
|
1266
|
+
public message: Inbound;
|
|
1267
|
+
// rsg moved accessors here because we won't have a full header; just set/check the individual byte.
|
|
1268
|
+
public set action(val: number) { (this.protocol !== Protocol.Chlorinator) ? this.header[4] = val : this.header[3] = val; }
|
|
1269
|
+
public get action(): number {
|
|
1270
|
+
if (this.protocol === Protocol.Chlorinator) return this.header[3];
|
|
1271
|
+
else if (typeof this.header[4] !== 'undefined') return this.header[4]
|
|
1272
|
+
else return -1;
|
|
1273
|
+
}
|
|
1274
|
+
constructor(proto: Protocol, source: number, dest: number, action?: number, payload?: number[], ack?: number, callback?: (err, msg?: Outbound) => void) {
|
|
1275
|
+
super();
|
|
1276
|
+
this.protocol = proto;
|
|
1277
|
+
this.direction = Direction.In;
|
|
1278
|
+
this.source = source;
|
|
1279
|
+
this.dest = dest;
|
|
1280
|
+
this.action = action;
|
|
1281
|
+
if (typeof payload !== 'undefined' && payload.length > 0) this.payload.push(...payload);
|
|
1282
|
+
if (typeof ack !== 'undefined' && ack !== null) this.ack = new Ack(ack);
|
|
1283
|
+
this.callback = callback;
|
|
1284
|
+
}
|
|
1285
|
+
public static create(obj?: any) {
|
|
1286
|
+
let res = new Response(obj.protocol || Protocol.Broadcast,
|
|
1287
|
+
obj.source || Message.pluginAddress, obj.dest || 16, obj.action || 0, obj.payload || [], obj.ack, obj.callback);
|
|
1288
|
+
res.responseBool = obj.response;
|
|
1289
|
+
if (typeof obj.action !== 'undefined') res.responseBool = true;
|
|
1290
|
+
return res;
|
|
1291
|
+
}
|
|
1292
|
+
// Fields
|
|
1293
|
+
public ack: Ack;
|
|
1294
|
+
public callback: (err, msg?: Outbound) => void;
|
|
1295
|
+
public responseBool: boolean; // if `response: true|false` is passed to the Outbound message we will store that input here
|
|
1296
|
+
|
|
1297
|
+
// Methods
|
|
1298
|
+
public isResponse(msgIn: Inbound, msgOut?: Outbound): boolean {
|
|
1299
|
+
let bresp = false;;
|
|
1300
|
+
try {
|
|
1301
|
+
if (typeof this.responseBool === 'boolean' && this.responseBool) bresp = this.evalResponse(msgIn, msgOut);
|
|
1302
|
+
else return bresp;
|
|
1303
|
+
if (bresp === true && typeof msgOut !== 'undefined') {
|
|
1304
|
+
msgIn.responseFor.push(msgOut.id);
|
|
1305
|
+
logger.silly(`Message in ${msgIn.id} is a response for message out ${msgOut.id}`);
|
|
1306
|
+
}
|
|
1307
|
+
return bresp;
|
|
1308
|
+
}
|
|
1309
|
+
catch (err) { }
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
public evalResponse(msgIn: Inbound, msgOut?: Outbound): boolean {
|
|
1313
|
+
// this holds the logic to determine if an inbound message is a response.
|
|
1314
|
+
// Aka is this Response object
|
|
1315
|
+
// a response to the parent message of Outbound class.
|
|
1316
|
+
if (typeof msgOut === 'undefined') return false;
|
|
1317
|
+
if (msgIn.protocol !== msgOut.protocol) { return false; }
|
|
1318
|
+
if (typeof msgIn === 'undefined') { return false; } // getting here on msg send failure
|
|
1319
|
+
|
|
1320
|
+
// if these properties were set on the Response (this) object via creation,
|
|
1321
|
+
// then use the passed in values. Otherwise, use the msgIn/msgOut matching rules
|
|
1322
|
+
// IntelliCenter config queue uses (action,payload-prefix) matching for Action 30 responses.
|
|
1323
|
+
// Keep this stricter prefix matching scoped to IntelliCenter to avoid unintended effects on other controllers.
|
|
1324
|
+
if (this.action > 0 && sys.controllerType === ControllerType.IntelliCenter) {
|
|
1325
|
+
if (this.action !== msgIn.action) return false;
|
|
1326
|
+
// If no payload prefix is provided, action match is sufficient (e.g. v3 Action 30 with empty payload).
|
|
1327
|
+
if (this.payload.length === 0) return true;
|
|
1328
|
+
if (msgIn.payload.length < this.payload.length) return false;
|
|
1329
|
+
for (let i = 0; i < this.payload.length; i++) {
|
|
1330
|
+
if (this.payload[i] !== msgIn.payload[i]) return false;
|
|
1331
|
+
}
|
|
1332
|
+
return true;
|
|
1333
|
+
}
|
|
1334
|
+
else if (msgOut.protocol === Protocol.Pump) {
|
|
1335
|
+
switch (msgIn.action) {
|
|
1336
|
+
case 7:
|
|
1337
|
+
// Scenario 1. Request for pump status.
|
|
1338
|
+
// Msg In: [165,0,16, 96, 7,15], [4,0,0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0,17,31], [1,95]
|
|
1339
|
+
// Msg Out: [165,0,96, 16, 7, 0],[1,28]
|
|
1340
|
+
if (msgIn.source !== msgOut.dest || (msgIn.dest !== msgOut.source && msgIn.dest != 16)) { return false; }
|
|
1341
|
+
if (msgIn.action === 7 && msgOut.action === 7) { return true; }
|
|
1342
|
+
return false;
|
|
1343
|
+
default:
|
|
1344
|
+
//Scenario 2, pump messages are mimics of each other but the dest/src are swapped
|
|
1345
|
+
if (msgIn.source !== msgOut.dest || (msgIn.dest !== msgOut.source && msgIn.dest != 16)) { return false; }
|
|
1346
|
+
// sub-case
|
|
1347
|
+
// Msg In: [165,0,16, 96, 1, 2], [3,32],[1,59]
|
|
1348
|
+
// Msg Out: [165,0,96,16, 1,4],[3,39, 3,32], [1,103]
|
|
1349
|
+
if (msgIn.payload[0] === msgOut.payload[2] && msgIn.payload[1] === msgOut.payload[3]) { return true; }
|
|
1350
|
+
// else mimics
|
|
1351
|
+
if (JSON.stringify(msgIn.payload) === JSON.stringify(msgOut.payload)) { return true; }
|
|
1352
|
+
return false;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
else if (msgIn.protocol === Protocol.RegalModbus) {
|
|
1356
|
+
// RegalModbus is a little different. The action is the function code and the payload is the data.
|
|
1357
|
+
// We are looking for a match on the action an ack of 0x10.
|
|
1358
|
+
if (msgIn.action === msgOut.action && msgIn.header[2] === 0x10) return true;
|
|
1359
|
+
return false;
|
|
1360
|
+
}
|
|
1361
|
+
else if (msgIn.protocol === Protocol.Chlorinator) {
|
|
1362
|
+
switch (msgIn.action) {
|
|
1363
|
+
case 1:
|
|
1364
|
+
return msgOut.action === 0 ? true : false;
|
|
1365
|
+
case 3:
|
|
1366
|
+
return msgOut.action === 20 ? true : false;
|
|
1367
|
+
case 18:
|
|
1368
|
+
case 21:
|
|
1369
|
+
case 22:
|
|
1370
|
+
return msgOut.action === 17 ? true : false;
|
|
1371
|
+
default:
|
|
1372
|
+
return false;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
else if (msgIn.protocol === Protocol.IntelliChem) {
|
|
1376
|
+
switch (msgIn.action) {
|
|
1377
|
+
case 1: // ack
|
|
1378
|
+
if (msgIn.source === msgOut.dest && msgIn.payload[0] === msgOut.action) return true;
|
|
1379
|
+
break;
|
|
1380
|
+
default:
|
|
1381
|
+
// in: 18; out 210 fits parent & 0x63 pattern
|
|
1382
|
+
if (msgIn.action === (msgOut.action & 63) && msgIn.source === msgOut.dest) return true;
|
|
1383
|
+
return false;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
else if (sys.controllerType !== ControllerType.IntelliCenter) {
|
|
1387
|
+
switch (msgIn.action) {
|
|
1388
|
+
// these responses have multiple items so match the 1st payload byte
|
|
1389
|
+
case 1: // ack
|
|
1390
|
+
if (msgIn.payload[0] === msgOut.action) return true;
|
|
1391
|
+
break;
|
|
1392
|
+
case 10:
|
|
1393
|
+
case 11:
|
|
1394
|
+
case 17:
|
|
1395
|
+
if (msgIn.action === (msgOut.action & 63) && msgIn.payload[0] === msgOut.payload[0]) return true;
|
|
1396
|
+
break;
|
|
1397
|
+
case 252:
|
|
1398
|
+
if (msgOut.action === 253) return true;
|
|
1399
|
+
break;
|
|
1400
|
+
default:
|
|
1401
|
+
if (msgIn.action === (msgOut.action & 63)) return true;
|
|
1402
|
+
}
|
|
1403
|
+
return false;
|
|
1404
|
+
}
|
|
1405
|
+
else if (sys.controllerType === ControllerType.IntelliCenter) {
|
|
1406
|
+
// intellicenter packets
|
|
1407
|
+
if (this.dest >= 0 && msgIn.dest !== this.dest) return false;
|
|
1408
|
+
for (let i = 0; i < this.payload.length; i++) {
|
|
1409
|
+
if (i > msgIn.payload.length - 1)
|
|
1410
|
+
return false;
|
|
1411
|
+
//console.log({ msg: 'Checking response', p1: msgIn.payload[i], pd: this.payload[i] });
|
|
1412
|
+
if (msgIn.payload[i] !== this.payload[i]) return false;
|
|
1413
|
+
}
|
|
1414
|
+
return true;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
/**
|
|
1420
|
+
* Computes the CRC16 checksum over an array of bytes using the RegalModbus algorithm.
|
|
1421
|
+
* @param data - The array of byte values (numbers between 0 and 255).
|
|
1422
|
+
* @returns The computed 16-bit checksum.
|
|
1423
|
+
*/
|
|
1424
|
+
export function computeCRC16(data: number[]): number {
|
|
1425
|
+
let crc = 0xFFFF;
|
|
1426
|
+
for (const byte of data) {
|
|
1427
|
+
crc ^= byte;
|
|
1428
|
+
for (let j = 0; j < 8; j++) {
|
|
1429
|
+
crc = (crc & 0x0001) ? (crc >> 1) ^ 0xA001 : crc >> 1;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
return crc;
|
|
1433
|
+
}
|