nodejs-poolcontroller 7.6.1 → 8.0.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 -45
- package/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -0
- package/.github/ISSUE_TEMPLATE/2-docs.md +12 -0
- package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/CONTRIBUTING.md +74 -74
- package/Changelog +242 -215
- package/Dockerfile +17 -17
- package/Gruntfile.js +40 -40
- package/LICENSE +661 -661
- package/README.md +195 -191
- package/anslq25/MessagesMock.ts +218 -0
- package/anslq25/boards/MockBoardFactory.ts +50 -0
- package/anslq25/boards/MockEasyTouchBoard.ts +696 -0
- package/anslq25/boards/MockSystemBoard.ts +217 -0
- package/anslq25/chemistry/MockChlorinator.ts +75 -0
- package/anslq25/pumps/MockPump.ts +84 -0
- package/app.ts +10 -14
- package/config/Config.ts +26 -8
- package/config/VersionCheck.ts +8 -4
- package/controller/Constants.ts +59 -25
- package/controller/Equipment.ts +2667 -2459
- package/controller/Errors.ts +181 -180
- package/controller/Lockouts.ts +534 -436
- package/controller/State.ts +596 -77
- package/controller/boards/AquaLinkBoard.ts +1003 -0
- package/controller/boards/BoardFactory.ts +53 -45
- package/controller/boards/EasyTouchBoard.ts +3079 -2653
- package/controller/boards/IntelliCenterBoard.ts +3821 -4230
- package/controller/boards/IntelliComBoard.ts +69 -63
- package/controller/boards/IntelliTouchBoard.ts +384 -241
- package/controller/boards/NixieBoard.ts +1871 -1675
- package/controller/boards/SunTouchBoard.ts +393 -0
- package/controller/boards/SystemBoard.ts +5244 -4697
- package/controller/comms/Comms.ts +905 -541
- package/controller/comms/ScreenLogic.ts +1663 -0
- package/controller/comms/messages/Messages.ts +382 -54
- package/controller/comms/messages/config/ChlorinatorMessage.ts +8 -4
- package/controller/comms/messages/config/CircuitGroupMessage.ts +5 -2
- package/controller/comms/messages/config/CircuitMessage.ts +82 -13
- package/controller/comms/messages/config/ConfigMessage.ts +3 -1
- package/controller/comms/messages/config/CoverMessage.ts +2 -1
- package/controller/comms/messages/config/CustomNameMessage.ts +31 -30
- package/controller/comms/messages/config/EquipmentMessage.ts +5 -1
- package/controller/comms/messages/config/ExternalMessage.ts +33 -3
- package/controller/comms/messages/config/FeatureMessage.ts +2 -1
- package/controller/comms/messages/config/GeneralMessage.ts +2 -1
- package/controller/comms/messages/config/HeaterMessage.ts +145 -11
- package/controller/comms/messages/config/IntellichemMessage.ts +2 -1
- package/controller/comms/messages/config/OptionsMessage.ts +16 -27
- package/controller/comms/messages/config/PumpMessage.ts +62 -47
- package/controller/comms/messages/config/RemoteMessage.ts +80 -13
- package/controller/comms/messages/config/ScheduleMessage.ts +390 -347
- package/controller/comms/messages/config/SecurityMessage.ts +2 -1
- package/controller/comms/messages/config/ValveMessage.ts +44 -27
- package/controller/comms/messages/status/ChlorinatorStateMessage.ts +44 -91
- package/controller/comms/messages/status/EquipmentStateMessage.ts +139 -30
- package/controller/comms/messages/status/HeaterStateMessage.ts +135 -86
- package/controller/comms/messages/status/IntelliChemStateMessage.ts +448 -445
- package/controller/comms/messages/status/IntelliValveStateMessage.ts +36 -35
- package/controller/comms/messages/status/PumpStateMessage.ts +92 -2
- package/controller/comms/messages/status/VersionMessage.ts +2 -1
- package/controller/nixie/Nixie.ts +173 -162
- package/controller/nixie/NixieEquipment.ts +104 -103
- package/controller/nixie/bodies/Body.ts +120 -120
- package/controller/nixie/bodies/Filter.ts +135 -135
- package/controller/nixie/chemistry/ChemController.ts +2682 -2498
- package/controller/nixie/chemistry/ChemDoser.ts +806 -0
- package/controller/nixie/chemistry/Chlorinator.ts +367 -314
- package/controller/nixie/circuits/Circuit.ts +402 -248
- package/controller/nixie/heaters/Heater.ts +815 -649
- package/controller/nixie/pumps/Pump.ts +934 -661
- package/controller/nixie/schedules/Schedule.ts +319 -257
- package/controller/nixie/valves/Valve.ts +170 -170
- package/defaultConfig.json +346 -286
- package/logger/DataLogger.ts +448 -448
- package/logger/Logger.ts +38 -9
- package/package.json +60 -56
- package/tsconfig.json +25 -25
- package/web/Server.ts +275 -117
- package/web/bindings/aqualinkD.json +560 -0
- package/web/bindings/homeassistant.json +437 -0
- package/web/bindings/influxDB.json +1066 -1021
- package/web/bindings/mqtt.json +721 -654
- package/web/bindings/mqttAlt.json +746 -684
- 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 -136
- package/web/interfaces/httpInterface.ts +148 -124
- package/web/interfaces/influxInterface.ts +283 -245
- package/web/interfaces/mqttInterface.ts +695 -475
- package/web/interfaces/ruleInterface.ts +87 -0
- package/web/services/config/Config.ts +177 -49
- package/web/services/config/ConfigSocket.ts +2 -1
- package/web/services/state/State.ts +154 -3
- package/web/services/state/StateSocket.ts +69 -18
- package/web/services/utilities/Utilities.ts +232 -42
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -52
- package/config copy.json +0 -300
- package/issue_template.md +0 -52
|
@@ -1,1675 +1,1871 @@
|
|
|
1
|
-
/* nodejs-poolController. An application to control pool equipment.
|
|
2
|
-
Copyright (C) 2016, 2017, 2018, 2019, 2020
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
import
|
|
19
|
-
import { ncp } from "../nixie/Nixie";
|
|
20
|
-
import { NixieHeaterBase } from "../nixie/heaters/Heater";
|
|
21
|
-
import { utils
|
|
22
|
-
import {SystemBoard, byteValueMap,
|
|
23
|
-
import { logger } from '../../logger/Logger';
|
|
24
|
-
import { state,
|
|
25
|
-
import { sys, Equipment,
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this.
|
|
36
|
-
this.equipmentIds.
|
|
37
|
-
this.equipmentIds.
|
|
38
|
-
this.equipmentIds.
|
|
39
|
-
this.equipmentIds.
|
|
40
|
-
this.equipmentIds.
|
|
41
|
-
this.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
[
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
[
|
|
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
|
-
[
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
[
|
|
81
|
-
[
|
|
82
|
-
[
|
|
83
|
-
[
|
|
84
|
-
[
|
|
85
|
-
[
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
this.valueMaps.
|
|
89
|
-
[
|
|
90
|
-
[
|
|
91
|
-
[
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
[1, { name: '
|
|
96
|
-
[2, { name: '
|
|
97
|
-
[3, { name: '
|
|
98
|
-
[4, { name: '
|
|
99
|
-
[5, { name: '
|
|
100
|
-
[6, { name: '
|
|
101
|
-
[7, { name: '
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
[
|
|
147
|
-
[
|
|
148
|
-
[
|
|
149
|
-
[
|
|
150
|
-
]);
|
|
151
|
-
this.valueMaps.
|
|
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
|
-
[
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
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
|
-
|
|
484
|
-
|
|
485
|
-
public
|
|
486
|
-
public
|
|
487
|
-
public
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
public
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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
|
-
|
|
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
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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
|
-
let
|
|
643
|
-
|
|
644
|
-
let
|
|
645
|
-
if (
|
|
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
|
-
|
|
769
|
-
|
|
770
|
-
|
|
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
|
-
if (
|
|
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
|
-
let
|
|
944
|
-
if (
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
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
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
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
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
let
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
if (
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
let
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
for (let i =
|
|
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
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
if (
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
switch (
|
|
1329
|
-
case '
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
public async
|
|
1462
|
-
try {
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
let
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
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 * as extend from 'extend';
|
|
19
|
+
import { ncp } from "../nixie/Nixie";
|
|
20
|
+
import { NixieHeaterBase } from "../nixie/heaters/Heater";
|
|
21
|
+
import { utils } from '../Constants';
|
|
22
|
+
import {SystemBoard, byteValueMap, BodyCommands, FilterCommands, PumpCommands, SystemCommands, CircuitCommands, FeatureCommands, ValveCommands, HeaterCommands, ChlorinatorCommands, ChemControllerCommands, EquipmentIdRange} from './SystemBoard';
|
|
23
|
+
import { logger } from '../../logger/Logger';
|
|
24
|
+
import { state, CircuitState, ICircuitState, ICircuitGroupState, LightGroupState, ValveState, FilterState, BodyTempState, FeatureState } from '../State';
|
|
25
|
+
import { sys, Equipment, General, PoolSystem, CircuitGroupCircuit, CircuitGroup, ChemController, Circuit, Feature, Valve, ICircuit, Heater, LightGroup, LightGroupCircuit, ControllerType, Filter } from '../Equipment';
|
|
26
|
+
import { BoardProcessError, EquipmentNotFoundError, InvalidEquipmentDataError, InvalidEquipmentIdError, ServiceParameterError } from '../Errors';
|
|
27
|
+
import { delayMgr } from '../Lockouts';
|
|
28
|
+
import { webApp } from "../../web/Server";
|
|
29
|
+
import { setTimeout } from 'timers/promises';
|
|
30
|
+
import { setTimeout as setTimeoutSync } from 'timers';
|
|
31
|
+
|
|
32
|
+
export class NixieBoard extends SystemBoard {
|
|
33
|
+
constructor (system: PoolSystem){
|
|
34
|
+
super(system);
|
|
35
|
+
this._statusInterval = 3000;
|
|
36
|
+
this.equipmentIds.circuits = new EquipmentIdRange(1, function () { return this.start + sys.equipment.maxCircuits - 1; });
|
|
37
|
+
this.equipmentIds.features = new EquipmentIdRange(function () { return 129; }, function () { return this.start + sys.equipment.maxFeatures - 1; });
|
|
38
|
+
this.equipmentIds.circuitGroups = new EquipmentIdRange(function () { return this.start; }, function () { return this.start + sys.equipment.maxCircuitGroups - 1; });
|
|
39
|
+
this.equipmentIds.virtualCircuits = new EquipmentIdRange(function () { return this.start; }, function () { return 277; });
|
|
40
|
+
this.equipmentIds.features.start = 129;
|
|
41
|
+
this.equipmentIds.circuitGroups.start = 193;
|
|
42
|
+
this.equipmentIds.virtualCircuits.start = 237;
|
|
43
|
+
this.valueMaps.equipmentMaster = new byteValueMap([
|
|
44
|
+
[1, { val: 1, name: 'ncp', desc: 'Nixie Control Panel' }],
|
|
45
|
+
[2, { val: 2, name: 'ext', desc: 'External Control Panel'}]
|
|
46
|
+
]);
|
|
47
|
+
this.valueMaps.panelModes = new byteValueMap([
|
|
48
|
+
[0, { name: 'auto', desc: 'Auto' }],
|
|
49
|
+
[1, { name: 'service', desc: 'Service' }],
|
|
50
|
+
[128, { name: 'timeout', desc: 'Timeout' }],
|
|
51
|
+
[255, { name: 'error', desc: 'System Error' }]
|
|
52
|
+
]);
|
|
53
|
+
this.valueMaps.featureFunctions = new byteValueMap([
|
|
54
|
+
[0, { name: 'generic', desc: 'Generic' }],
|
|
55
|
+
[1, { name: 'spillway', desc: 'Spillway' }],
|
|
56
|
+
[2, { name: 'spadrain', desc: 'Spa Drain' }]
|
|
57
|
+
]);
|
|
58
|
+
this.valueMaps.circuitFunctions = new byteValueMap([
|
|
59
|
+
[0, { name: 'generic', desc: 'Generic' }],
|
|
60
|
+
[1, { name: 'spillway', desc: 'Spillway' }],
|
|
61
|
+
[2, { name: 'mastercleaner', desc: 'Master Cleaner', body: 1 }],
|
|
62
|
+
[3, { name: 'chemrelay', desc: 'Chem Relay' }],
|
|
63
|
+
[4, { name: 'light', desc: 'Light', isLight: true }],
|
|
64
|
+
[5, { name: 'intellibrite', desc: 'Intellibrite', isLight: true, theme: 'intellibrite' }],
|
|
65
|
+
[6, { name: 'globrite', desc: 'GloBrite', isLight: true, theme: 'intellibrite' }],
|
|
66
|
+
[7, { name: 'globritewhite', desc: 'GloBrite White', isLight: true }],
|
|
67
|
+
[8, { name: 'magicstream', desc: 'Magicstream', isLight: true, theme: 'magicstream' }],
|
|
68
|
+
[9, { name: 'dimmer', desc: 'Dimmer', isLight: true }],
|
|
69
|
+
[10, { name: 'colorcascade', desc: 'ColorCascade', isLight: true, theme: 'intellibrite' }],
|
|
70
|
+
[11, { name: 'mastercleaner2', desc: 'Master Cleaner 2', body: 2 }],
|
|
71
|
+
[12, { name: 'pool', desc: 'Pool', hasHeatSource: true, body: 1 }],
|
|
72
|
+
[13, { name: 'spa', desc: 'Spa', hasHeatSource: true, body: 2 }],
|
|
73
|
+
[14, { name: 'colorlogic', desc: 'ColorLogic', isLight: true, theme: 'colorlogic' }],
|
|
74
|
+
[15, { name: 'spadrain', desc: 'Spa Drain' }],
|
|
75
|
+
[16, { name: 'pooltone', desc: 'Pool Tone', isLight: true, theme: 'pooltone' }],
|
|
76
|
+
]);
|
|
77
|
+
this.valueMaps.pumpTypes = new byteValueMap([
|
|
78
|
+
[1, { name: 'ss', desc: 'Single Speed', maxCircuits: 8, hasAddress: false, hasBody: false, maxRelays: 1, relays: [{ id: 1, name: 'Pump On/Off' }]}],
|
|
79
|
+
[2, { name: 'ds', desc: 'Two Speed', maxCircuits: 8, hasAddress: false, hasBody: false, maxRelays: 2, relays: [{ id: 1, name: 'Low Speed' }, { id: 2, name: 'High Speed' }]}],
|
|
80
|
+
[3, { name: 'vs', desc: 'Intelliflo VS', maxPrimingTime: 6, minSpeed: 450, maxSpeed: 3450, maxCircuits: 8, hasAddress: true }],
|
|
81
|
+
[4, { name: 'vsf', desc: 'Intelliflo VSF', minSpeed: 450, maxSpeed: 3450, minFlow: 15, maxFlow: 130, maxCircuits: 8, hasAddress: true }],
|
|
82
|
+
[5, { name: 'vf', desc: 'Intelliflo VF', minFlow: 15, maxFlow: 130, maxCircuits: 8, hasAddress: true }],
|
|
83
|
+
[6, { name: 'hwvs', desc: 'Hayward Eco/TriStar VS', minSpeed: 450, maxSpeed: 3450, maxCircuits: 8, hasAddress: true }],
|
|
84
|
+
[7, { name: 'hwrly', desc: 'Hayward Relay VS', hasAddress: false, maxCircuits: 8, maxRelays: 4, maxSpeeds: 8, relays: [{ id: 1, name: 'Step #1' }, { id: 2, name: 'Step #2'}, { id: 3, name: 'Step #3' }, { id: 4, name: 'Pump On' }] }],
|
|
85
|
+
[100, { name: 'sf', desc: 'SuperFlo VS', hasAddress: false, maxCircuits: 8, maxRelays: 4, equipmentMaster: 1, maxSpeeds: 4, relays: [{ id: 1, name: 'Program #1' }, { id: 2, name: 'Program #2' }, { id: 3, name: 'Program #3' }, { id: 4, name: 'Program #4' }]}]
|
|
86
|
+
]);
|
|
87
|
+
// RSG - same as systemBoard definition; can delete.
|
|
88
|
+
this.valueMaps.heatModes = new byteValueMap([
|
|
89
|
+
[0, { name: 'off', desc: 'Off' }],
|
|
90
|
+
[3, { name: 'heater', desc: 'Heater' }],
|
|
91
|
+
[5, { name: 'solar', desc: 'Solar Only' }],
|
|
92
|
+
[12, { name: 'solarpref', desc: 'Solar Preferred' }]
|
|
93
|
+
]);
|
|
94
|
+
this.valueMaps.scheduleDays = new byteValueMap([
|
|
95
|
+
[1, { name: 'mon', desc: 'Monday', dow: 1, bitval: 1 }],
|
|
96
|
+
[2, { name: 'tue', desc: 'Tuesday', dow: 2, bitval: 2 }],
|
|
97
|
+
[3, { name: 'wed', desc: 'Wednesday', dow: 3, bitval: 4 }],
|
|
98
|
+
[4, { name: 'thu', desc: 'Thursday', dow: 4, bitval: 8 }],
|
|
99
|
+
[5, { name: 'fri', desc: 'Friday', dow: 5, bitval: 16 }],
|
|
100
|
+
[6, { name: 'sat', desc: 'Saturday', dow: 6, bitval: 32 }],
|
|
101
|
+
[7, { name: 'sun', desc: 'Sunday', dow: 0, bitval: 64 }]
|
|
102
|
+
]);
|
|
103
|
+
this.valueMaps.groupCircuitStates = new byteValueMap([
|
|
104
|
+
[1, { name: 'on', desc: 'On/Off' }],
|
|
105
|
+
[2, { name: 'off', desc: 'Off/On' }],
|
|
106
|
+
[3, { name: 'ignore', desc: 'Ignore' }],
|
|
107
|
+
[4, { name: 'on+ignore', desc: 'On/Ignore' }],
|
|
108
|
+
[5, { name: 'off+ignore', desc: 'Off/Ignore' }]
|
|
109
|
+
]);
|
|
110
|
+
this.valueMaps.chlorinatorModel = new byteValueMap([
|
|
111
|
+
[0, { name: 'unknown', desc: 'unknown', capacity: 0, chlorinePerDay: 0, chlorinePerSec: 0 }],
|
|
112
|
+
[1, { name: 'intellichlor--15', desc: 'IntelliChlor IC15', capacity: 15000, chlorinePerDay: 0.60, chlorinePerSec: 0.60 / 86400 }],
|
|
113
|
+
[2, { name: 'intellichlor--20', desc: 'IntelliChlor IC20', capacity: 20000, chlorinePerDay: 0.70, chlorinePerSec: 0.70 / 86400 }],
|
|
114
|
+
[3, { name: 'intellichlor--40', desc: 'IntelliChlor IC40', capacity: 40000, chlorinePerDay: 1.40, chlorinePerSec: 1.4 / 86400 }],
|
|
115
|
+
[4, { name: 'intellichlor--60', desc: 'IntelliChlor IC60', capacity: 60000, chlorinePerDay: 2, chlorinePerSec: 2 / 86400 }],
|
|
116
|
+
[5, { name: 'aquarite-t15', desc: 'AquaRite T15', capacity: 40000, chlorinePerDay: 1.47, chlorinePerSec: 1.47 / 86400 }],
|
|
117
|
+
[6, { name: 'aquarite-t9', desc: 'AquaRite T9', capacity: 30000, chlorinePerDay: 0.98, chlorinePerSec: 0.98 / 86400 }],
|
|
118
|
+
[7, { name: 'aquarite-t5', desc: 'AquaRite T5', capacity: 20000, chlorinePerDay: 0.735, chlorinePerSec: 0.735 / 86400 }],
|
|
119
|
+
[8, { name: 'aquarite-t3', desc: 'AquaRite T3', capacity: 15000, chlorinePerDay: 0.53, chlorinePerSec: 0.53 / 86400 }],
|
|
120
|
+
[9, { name: 'aquarite-925', desc: 'AquaRite 925', capacity: 25000, chlorinePerDay: 0.98, chlorinePerSec: 0.98 / 86400 }],
|
|
121
|
+
[10, { name: 'aquarite-940', desc: 'AquaRite 940', capacity: 40000, chlorinePerDay: 1.47, chlorinePerSec: 1.47 / 86400 }]
|
|
122
|
+
]);
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
// Keep this around for now so I can fart with the custom names array.
|
|
126
|
+
//this.valueMaps.customNames = new byteValueMap(
|
|
127
|
+
// sys.customNames.get().map((el, idx) => {
|
|
128
|
+
// return [idx + 200, { name: el.name, desc: el.name }];
|
|
129
|
+
// })
|
|
130
|
+
//);
|
|
131
|
+
this.valueMaps.scheduleDays.toArray = function () {
|
|
132
|
+
let arrKeys = Array.from(this.keys());
|
|
133
|
+
let arr = [];
|
|
134
|
+
for (let i = 0; i < arrKeys.length; i++) arr.push(extend(true, { val: arrKeys[i] }, this.get(arrKeys[i])));
|
|
135
|
+
return arr;
|
|
136
|
+
}
|
|
137
|
+
this.valueMaps.scheduleDays.transform = function (byte) {
|
|
138
|
+
let days = [];
|
|
139
|
+
let b = byte & 0x007F;
|
|
140
|
+
for (let bit = 6; bit >= 0; bit--) {
|
|
141
|
+
if ((byte & (1 << bit)) > 0) days.push(extend(true, {}, this.get(bit + 1)));
|
|
142
|
+
}
|
|
143
|
+
return { val: b, days: days };
|
|
144
|
+
};
|
|
145
|
+
this.valueMaps.expansionBoards = new byteValueMap([
|
|
146
|
+
[0, { name: 'nxp', part: 'NXP', desc: 'Nixie Single Body', bodies: 1, valves: 0, single: true, shared: false, dual: false }],
|
|
147
|
+
[1, { name: 'nxps', part: 'NXPS', desc: 'Nixie Shared Body', bodies: 2, valves: 2, shared: true, dual: false, chlorinators: 1, chemControllers: 1 }],
|
|
148
|
+
[2, { name: 'nxpd', part: 'NXPD', desc: 'Nixie Dual Body', bodies: 2, valves: 0, shared: false, dual: true, chlorinators: 2, chemControllers: 2 }],
|
|
149
|
+
[255, { name: 'nxnb', part: 'NXNB', desc: 'Nixie No Body', bodies: 0, valves: 0, shared: false, dual: false, chlorinators: 0, chemControllers: 0 }]
|
|
150
|
+
]);
|
|
151
|
+
this.valueMaps.virtualCircuits = new byteValueMap([
|
|
152
|
+
[237, { name: 'heatBoost', desc: 'Heat Boost' }],
|
|
153
|
+
[238, { name: 'heatEnable', desc: 'Heat Enable' }],
|
|
154
|
+
[239, { name: 'pumpSpeedUp', desc: 'Pump Speed +' }],
|
|
155
|
+
[240, { name: 'pumpSpeedDown', desc: 'Pump Speed -' }],
|
|
156
|
+
[244, { name: 'poolHeater', desc: 'Pool Heater' }],
|
|
157
|
+
[245, { name: 'spaHeater', desc: 'Spa Heater' }],
|
|
158
|
+
[246, { name: 'freeze', desc: 'Freeze' }],
|
|
159
|
+
[247, { name: 'poolSpa', desc: 'Pool/Spa' }],
|
|
160
|
+
[251, { name: 'heater', desc: 'Heater' }],
|
|
161
|
+
[252, { name: 'solar', desc: 'Solar' }],
|
|
162
|
+
[253, { name: 'solar1', desc: 'Solar Body 1' }],
|
|
163
|
+
[254, { name: 'solar2', desc: 'Solar Body 2' }],
|
|
164
|
+
[255, { name: 'solar3', desc: 'Solar Body 3' }],
|
|
165
|
+
[256, { name: 'solar4', desc: 'Solar Body 4' }],
|
|
166
|
+
[257, { name: 'poolHeatEnable', desc: 'Pool Heat Enable' }],
|
|
167
|
+
[258, { name: 'anyHeater', desc: 'Any Heater' }],
|
|
168
|
+
[259, { name: 'heatpump', desc: 'Heat Pump'}]
|
|
169
|
+
]);
|
|
170
|
+
this.valueMaps.scheduleTimeTypes.merge([
|
|
171
|
+
[1, { name: 'sunrise', desc: 'Sunrise' }],
|
|
172
|
+
[2, { name: 'sunset', desc: 'Sunset' }]
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
this.valueMaps.lightThemes = new byteValueMap([
|
|
176
|
+
// IntelliBrite Themes
|
|
177
|
+
[0, { name: 'white', desc: 'White', types: ['intellibrite', 'magicstream'], sequence: 11 }],
|
|
178
|
+
[1, { name: 'green', desc: 'Green', types: ['intellibrite', 'magicstream'], sequence: 9 }],
|
|
179
|
+
[2, { name: 'blue', desc: 'Blue', types: ['intellibrite', 'magicstream'], sequence: 8 }],
|
|
180
|
+
[3, { name: 'magenta', desc: 'Magenta', types: ['intellibrite', 'magicstream'], sequence: 12 }],
|
|
181
|
+
[4, { name: 'red', desc: 'Red', types: ['intellibrite', 'magicstream'], sequence: 10 }],
|
|
182
|
+
[5, { name: 'sam', desc: 'SAm Mode', types: ['intellibrite', 'magicstream'], sequence: 1 }],
|
|
183
|
+
[6, { name: 'party', desc: 'Party', types: ['intellibrite', 'magicstream'], sequence: 2 }],
|
|
184
|
+
[7, { name: 'romance', desc: 'Romance', types: ['intellibrite', 'magicstream'], sequence: 3 }],
|
|
185
|
+
[8, { name: 'caribbean', desc: 'Caribbean', types: ['intellibrite', 'magicstream'], sequence: 4 }],
|
|
186
|
+
[9, { name: 'american', desc: 'American', types: ['intellibrite', 'magicstream'], sequence: 5 }],
|
|
187
|
+
[10, { name: 'sunset', desc: 'Sunset', types: ['intellibrite', 'magicstream'], sequence: 6 }],
|
|
188
|
+
[11, { name: 'royal', desc: 'Royal', types: ['intellibrite', 'magicstream'], sequence: 7 }],
|
|
189
|
+
// ColorLogic Themes
|
|
190
|
+
[20, { name: 'cloudwhite', desc: 'Cloud White', types: ['colorlogic'], sequence: 7 }],
|
|
191
|
+
[21, { name: 'deepsea', desc: 'Deep Sea', types: ['colorlogic'], sequence: 2 }],
|
|
192
|
+
[22, { name: 'royalblue', desc: 'Royal Blue', types: ['colorlogic'], sequence: 3 }],
|
|
193
|
+
[23, { name: 'afternoonskies', desc: 'Afternoon Skies', types: ['colorlogic'], sequence: 4 }],
|
|
194
|
+
[24, { name: 'aquagreen', desc: 'Aqua Green', types: ['colorlogic'], sequence: 5 }],
|
|
195
|
+
[25, { name: 'emerald', desc: 'Emerald', types: ['colorlogic'], sequence: 6 }],
|
|
196
|
+
[26, { name: 'warmred', desc: 'Warm Red', types: ['colorlogic'], sequence: 8 }],
|
|
197
|
+
[27, { name: 'flamingo', desc: 'Flamingo', types: ['colorlogic'], sequence: 9 }],
|
|
198
|
+
[28, { name: 'vividviolet', desc: 'Vivid Violet', types: ['colorlogic'], sequence: 10 }],
|
|
199
|
+
[29, { name: 'sangria', desc: 'Sangria', types: ['colorlogic'], sequence: 11 }],
|
|
200
|
+
[30, { name: 'voodoolounge', desc: 'Voodoo Lounge', types: ['colorlogic'], sequence: 1 }],
|
|
201
|
+
[31, { name: 'twilight', desc: 'Twilight', types: ['colorlogic'], sequence: 12 }],
|
|
202
|
+
[32, { name: 'tranquility', desc: 'Tranquility', types: ['colorlogic'], sequence: 13 }],
|
|
203
|
+
[33, { name: 'gemstone', desc: 'Gemstone', types: ['colorlogic'], sequence: 14 }],
|
|
204
|
+
[34, { name: 'usa', desc: 'USA', types: ['colorlogic'], sequence: 15 }],
|
|
205
|
+
[35, { name: 'mardigras', desc: 'Mardi Gras', types: ['colorlogic'], sequence: 16 }],
|
|
206
|
+
[36, { name: 'coolcabaret', desc: 'Cabaret', types: ['colorlogic'], sequence: 17 }],
|
|
207
|
+
// Sunseeker PoolTone Themes
|
|
208
|
+
[40, { name: 'eveningsea', desc: 'Evening Sea', types: ['pooltone'], sequence: 1 }],
|
|
209
|
+
[41, { name: 'eveningrivers', desc: 'Evening Rivers', types: ['pooltone'], sequence: 2 }],
|
|
210
|
+
[42, { name: 'riviera', desc: 'Riviera', types: ['pooltone'], sequence: 3 }],
|
|
211
|
+
[43, { name: 'neutralwhite', desc: 'Neutral White', types: ['pooltone'], sequence: 4 }],
|
|
212
|
+
[44, { name: 'rainbow', desc: 'Rainbow', types: ['pooltone'], sequence: 5 }],
|
|
213
|
+
[45, { name: 'colorriver', desc: 'Color River', types: ['pooltone'], sequence: 6 }],
|
|
214
|
+
[46, { name: 'disco', desc: 'Disco', types: ['pooltone'], sequence: 7 }],
|
|
215
|
+
[47, { name: 'fourseasons', desc: 'Four Seasons', types: ['pooltone'], sequence: 8 }],
|
|
216
|
+
[48, { name: 'Party', desc: 'Party', types: ['pooltone'], sequence: 9 }],
|
|
217
|
+
[49, { name: 'sunwhite', desc: 'Sun White', types: ['pooltone'], sequence: 10 }],
|
|
218
|
+
[50, { name: 'red', desc: 'Red', types: ['pooltone'], sequence: 11 }],
|
|
219
|
+
[51, { name: 'green', desc: 'Green', types: ['pooltone'], sequence: 12 }],
|
|
220
|
+
[52, { name: 'blue', desc: 'Blue', types: ['pooltone'], sequence: 13 }],
|
|
221
|
+
[53, { name: 'greenblue', desc: 'Green-Blue', types: ['pooltone'], sequence: 14 }],
|
|
222
|
+
[54, { name: 'redgreen', desc: 'Red-Green', types: ['pooltone'], sequence: 15 }],
|
|
223
|
+
[55, { name: 'bluered', desc: 'Blue-red', types: ['pooltone'], sequence: 16 }],
|
|
224
|
+
[255, { name: 'none', desc: 'None' }]
|
|
225
|
+
]);
|
|
226
|
+
this.valueMaps.lightColors = new byteValueMap([
|
|
227
|
+
[0, { name: 'white', desc: 'White' }],
|
|
228
|
+
[16, { name: 'lightgreen', desc: 'Light Green' }],
|
|
229
|
+
[32, { name: 'green', desc: 'Green' }],
|
|
230
|
+
[48, { name: 'cyan', desc: 'Cyan' }],
|
|
231
|
+
[64, { name: 'blue', desc: 'Blue' }],
|
|
232
|
+
[80, { name: 'lavender', desc: 'Lavender' }],
|
|
233
|
+
[96, { name: 'magenta', desc: 'Magenta' }],
|
|
234
|
+
[112, { name: 'lightmagenta', desc: 'Light Magenta' }]
|
|
235
|
+
]);
|
|
236
|
+
this.valueMaps.heatSources = new byteValueMap([
|
|
237
|
+
[1, { name: 'off', desc: 'Off' }],
|
|
238
|
+
[2, { name: 'heater', desc: 'Heater' }],
|
|
239
|
+
[3, { name: 'solar', desc: 'Solar Only' }],
|
|
240
|
+
[4, { name: 'solarpref', desc: 'Solar Preferred' }],
|
|
241
|
+
[5, { name: 'ultratemp', desc: 'Ultratemp Only' }],
|
|
242
|
+
[6, { name: 'ultratemppref', desc: 'Ultratemp Pref' }],
|
|
243
|
+
[9, { name: 'heatpump', desc: 'Heatpump Only' }],
|
|
244
|
+
[25, { name: 'heatpumppref', desc: 'Heatpump Pref' }],
|
|
245
|
+
[32, { name: 'nochange', desc: 'No Change' }]
|
|
246
|
+
]);
|
|
247
|
+
this.valueMaps.heatStatus = new byteValueMap([
|
|
248
|
+
[0, { name: 'off', desc: 'Off' }],
|
|
249
|
+
[1, { name: 'heater', desc: 'Heater' }],
|
|
250
|
+
[2, { name: 'solar', desc: 'Solar' }],
|
|
251
|
+
[3, { name: 'cooling', desc: 'Cooling' }],
|
|
252
|
+
[6, { name: 'mtheat', desc: 'Heater' }],
|
|
253
|
+
[4, { name: 'hpheat', desc: 'Heating' }],
|
|
254
|
+
[8, { name: 'hpcool', desc: 'Cooling' }],
|
|
255
|
+
[128, {name: 'cooldown', desc: 'Cooldown'}]
|
|
256
|
+
]);
|
|
257
|
+
this.valueMaps.scheduleTypes = new byteValueMap([
|
|
258
|
+
[0, { name: 'runonce', desc: 'Run Once', startDate: true, startTime: true, endTime: true, days: false, heatSource: true, heatSetpoint: true }],
|
|
259
|
+
[128, { name: 'repeat', desc: 'Repeats', startDate: false, startTime: true, endTime: true, days: 'multi', heatSource: true, heatSetpoint: true }]
|
|
260
|
+
]);
|
|
261
|
+
this.valueMaps.remoteTypes = new byteValueMap([
|
|
262
|
+
[0, { name: 'none', desc: 'Not Installed', maxButtons: 0 }],
|
|
263
|
+
[1, { name: 'is4', desc: 'iS4 Spa-Side Remote', maxButtons: 4 }],
|
|
264
|
+
[2, { name: 'is10', desc: 'iS10 Spa-Side Remote', maxButtons: 10 }],
|
|
265
|
+
[3, { name: 'quickTouch', desc: 'Quick Touch Remote', maxButtons: 4 }],
|
|
266
|
+
[4, { name: 'spaCommand', desc: 'Spa Command', maxButtons: 10 }]
|
|
267
|
+
]);
|
|
268
|
+
}
|
|
269
|
+
public async checkConfiguration() {
|
|
270
|
+
state.status = sys.board.valueMaps.controllerStatus.transform(0, 0);
|
|
271
|
+
state.emitControllerChange();
|
|
272
|
+
state.status = sys.board.valueMaps.controllerStatus.transform(1, 100);
|
|
273
|
+
state.emitControllerChange();
|
|
274
|
+
}
|
|
275
|
+
public async initNixieBoard() {
|
|
276
|
+
try {
|
|
277
|
+
this.killStatusCheck();
|
|
278
|
+
let self = this;
|
|
279
|
+
sys.general.options.clockSource = 'server';
|
|
280
|
+
state.status = sys.board.valueMaps.controllerStatus.transform(0, 0);
|
|
281
|
+
// First lets clear out all the messages.
|
|
282
|
+
state.equipment.messages.removeItemByCode('EQ')
|
|
283
|
+
// Set up all the default information for the controller. This should be done
|
|
284
|
+
// for the startup of the system. The equipment installed at module 0 is the main
|
|
285
|
+
// system descriptor.
|
|
286
|
+
let mod = sys.equipment.modules.getItemById(0, true);
|
|
287
|
+
mod.master = 1;
|
|
288
|
+
//[0, { name: 'nxp', part: 'NXP', desc: 'Nixie Single Body', bodies: 1, valves: 2, shared: false, dual: false }],
|
|
289
|
+
//[1, { name: 'nxps', part: 'NXPS', desc: 'Nixie Shared Body', bodies: 2, valves: 4, shared: true, dual: false, chlorinators: 1, chemControllers: 1 }],
|
|
290
|
+
//[2, { name: 'nxpd', part: 'NXPD', desc: 'Nixe Dual Body', bodies: 2, valves: 2, shared: false, dual: true, chlorinators: 2, chemControllers: 2 }],
|
|
291
|
+
//[255, { name: 'nxu', part: 'Unspecified', desc: 'Nixie No Body', bodies: 0, valves: 0, shared: false, dual: false, chlorinators: 0, chemControllers: 0 }]
|
|
292
|
+
let type = typeof mod.type !== 'undefined' ? this.valueMaps.expansionBoards.transform(mod.type) : this.valueMaps.expansionBoards.transform(0);
|
|
293
|
+
logger.info(`Initializing Nixie Control Panel for ${type.desc}`);
|
|
294
|
+
|
|
295
|
+
state.equipment.shared = sys.equipment.shared = type.shared;
|
|
296
|
+
state.equipment.dual = sys.equipment.dual = type.dual;
|
|
297
|
+
state.equipment.single = sys.equipment.single = sys.equipment.shared === false && sys.equipment.dual === false;
|
|
298
|
+
sys.equipment.controllerFirmware = '1.0.0';
|
|
299
|
+
mod.type = type.val;
|
|
300
|
+
mod.part = type.part;
|
|
301
|
+
let md = mod.get();
|
|
302
|
+
md['bodies'] = type.bodies;
|
|
303
|
+
md['part'] = type.part;
|
|
304
|
+
md['valves'] = type.valves;
|
|
305
|
+
mod.name = type.name;
|
|
306
|
+
sys.equipment.model = mod.desc = type.desc;
|
|
307
|
+
state.equipment.maxValves = sys.equipment.maxValves = 32;
|
|
308
|
+
state.equipment.maxCircuits = sys.equipment.maxCircuits = 40;
|
|
309
|
+
state.equipment.maxFeatures = sys.equipment.maxFeatures = 32;
|
|
310
|
+
state.equipment.maxHeaters = sys.equipment.maxHeaters = 16;
|
|
311
|
+
state.equipment.maxLightGroups = sys.equipment.maxLightGroups = 16;
|
|
312
|
+
state.equipment.maxCircuitGroups = sys.equipment.maxCircuitGroups = 16;
|
|
313
|
+
state.equipment.maxSchedules = sys.equipment.maxSchedules = 100;
|
|
314
|
+
state.equipment.maxPumps = sys.equipment.maxPumps = 16;
|
|
315
|
+
state.equipment.controllerType = sys.controllerType;
|
|
316
|
+
sys.equipment.maxCustomNames = 0;
|
|
317
|
+
state.equipment.model = type.desc;
|
|
318
|
+
state.equipment.maxBodies = sys.equipment.maxBodies = type.bodies;
|
|
319
|
+
let bodyUnits = sys.general.options.units === 0 ? 1 : 2;
|
|
320
|
+
sys.equipment.single = typeof type.single !== 'undefined' ? type.single : false;
|
|
321
|
+
|
|
322
|
+
if (typeof state.temps.units === 'undefined' || state.temps.units < 0) state.temps.units = sys.general.options.units;
|
|
323
|
+
if (type.bodies > 0) {
|
|
324
|
+
let pool = sys.bodies.getItemById(1, true);
|
|
325
|
+
let sbody = state.temps.bodies.getItemById(1, true);
|
|
326
|
+
if (typeof pool.type === 'undefined') pool.type = 0;
|
|
327
|
+
if (typeof pool.name === 'undefined') pool.name = type.dual ? 'Body 1' : 'Pool';
|
|
328
|
+
if (typeof pool.capacity === 'undefined') pool.capacity = 0;
|
|
329
|
+
if (typeof pool.setPoint === 'undefined') pool.setPoint = 0;
|
|
330
|
+
pool.circuit = 6;
|
|
331
|
+
pool.isActive = true;
|
|
332
|
+
pool.master = 1;
|
|
333
|
+
pool.capacityUnits = bodyUnits;
|
|
334
|
+
sbody.name = pool.name;
|
|
335
|
+
sbody.setPoint = pool.setPoint;
|
|
336
|
+
sbody.circuit = pool.circuit;
|
|
337
|
+
sbody.type = pool.type;
|
|
338
|
+
// We need to add in a circuit for 6.
|
|
339
|
+
let circ = sys.circuits.getItemById(6, true, { name: pool.name, showInFeatures: false });
|
|
340
|
+
let scirc = state.circuits.getItemById(6, true);
|
|
341
|
+
//[12, { name: 'pool', desc: 'Pool', hasHeatSource: true }],
|
|
342
|
+
//[13, { name: 'spa', desc: 'Spa', hasHeatSource: true }]
|
|
343
|
+
circ.type = 12;
|
|
344
|
+
if (typeof circ.showInFeatures === 'undefined') circ.showInFeatures = false;
|
|
345
|
+
circ.isActive = true;
|
|
346
|
+
circ.master = 1;
|
|
347
|
+
scirc.showInFeatures = circ.showInFeatures;
|
|
348
|
+
scirc.type = circ.type;
|
|
349
|
+
scirc.name = circ.name;
|
|
350
|
+
if (type.shared || type.dual) {
|
|
351
|
+
// We are going to add two bodies and prune off the othergood ls.
|
|
352
|
+
let spa = sys.bodies.getItemById(2, true);
|
|
353
|
+
if (typeof spa.type === 'undefined') spa.type = type.dual ? 0 : 1;
|
|
354
|
+
if (typeof spa.name === 'undefined') spa.name = type.dual ? 'Body 2' : 'Spa';
|
|
355
|
+
if (typeof spa.capacity === 'undefined') spa.capacity = 0;
|
|
356
|
+
if (typeof spa.setPoint === 'undefined') spa.setPoint = 0;
|
|
357
|
+
circ = sys.circuits.getItemById(1, true, {name: spa.name, showInFeatures: false });
|
|
358
|
+
circ.type = type.dual ? 12 : 13;
|
|
359
|
+
circ.isActive = true;
|
|
360
|
+
circ.master = 1;
|
|
361
|
+
spa.circuit = 1;
|
|
362
|
+
spa.isActive = true;
|
|
363
|
+
spa.master = 1;
|
|
364
|
+
sbody = state.temps.bodies.getItemById(2, true);
|
|
365
|
+
sbody.name = spa.name;
|
|
366
|
+
sbody.setPoint = spa.setPoint;
|
|
367
|
+
sbody.circuit = spa.circuit;
|
|
368
|
+
sbody.type = spa.type;
|
|
369
|
+
spa.capacityUnits = bodyUnits;
|
|
370
|
+
scirc = state.circuits.getItemById(1, true);
|
|
371
|
+
scirc.showInFeatures = circ.showInFeatures;
|
|
372
|
+
scirc.type = circ.type;
|
|
373
|
+
scirc.name = circ.name;
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
// Remove the items that are not part of our board.
|
|
377
|
+
sys.bodies.removeItemById(2);
|
|
378
|
+
state.temps.bodies.removeItemById(2);
|
|
379
|
+
sys.circuits.removeItemById(1);
|
|
380
|
+
state.circuits.removeItemById(1);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
sys.bodies.removeItemById(1);
|
|
385
|
+
sys.bodies.removeItemById(2);
|
|
386
|
+
state.temps.bodies.removeItemById(1);
|
|
387
|
+
state.temps.bodies.removeItemById(2);
|
|
388
|
+
sys.circuits.removeItemById(1);
|
|
389
|
+
state.circuits.removeItemById(1);
|
|
390
|
+
sys.circuits.removeItemById(6);
|
|
391
|
+
state.circuits.removeItemById(6);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
sys.equipment.setEquipmentIds();
|
|
395
|
+
sys.board.bodies.initFilters();
|
|
396
|
+
state.status = sys.board.valueMaps.controllerStatus.transform(2, 0);
|
|
397
|
+
// Add up all the stuff we need to initialize.
|
|
398
|
+
let total = sys.bodies.length;
|
|
399
|
+
total += sys.circuits.length;
|
|
400
|
+
total += sys.heaters.length;
|
|
401
|
+
total += sys.chlorinators.length;
|
|
402
|
+
total += sys.chemControllers.length;
|
|
403
|
+
total += sys.filters.length;
|
|
404
|
+
total += sys.pumps.length;
|
|
405
|
+
total += sys.valves.length;
|
|
406
|
+
total += sys.schedules.length;
|
|
407
|
+
this.initValves();
|
|
408
|
+
sys.board.heaters.initTempSensors();
|
|
409
|
+
await this.verifySetup();
|
|
410
|
+
await ncp.initAsync(sys);
|
|
411
|
+
sys.board.heaters.updateHeaterServices();
|
|
412
|
+
state.cleanupState();
|
|
413
|
+
logger.info(`${sys.equipment.model} control board initialized`);
|
|
414
|
+
state.status = sys.board.valueMaps.controllerStatus.transform(1, 100);
|
|
415
|
+
state.mode = sys.board.valueMaps.panelModes.encode('auto');
|
|
416
|
+
// At this point we should have the start of a board so lets check to see if we are ready or if we are stuck initializing.
|
|
417
|
+
await setTimeout(5000);
|
|
418
|
+
await self.processStatusAsync();
|
|
419
|
+
} catch (err) { state.status = 255; logger.error(`Error Initializing Nixie Control Panel ${err.message}`); }
|
|
420
|
+
}
|
|
421
|
+
public initValves() {
|
|
422
|
+
logger.info(`Initializing Intake/Return valves`);
|
|
423
|
+
let iv = sys.valves.find(elem => elem.isIntake === true);
|
|
424
|
+
let rv = sys.valves.find(elem => elem.isReturn === true);
|
|
425
|
+
if (sys.equipment.shared) {
|
|
426
|
+
if (typeof iv === 'undefined') iv = sys.valves.getItemById(sys.valves.getMaxId(false, 0) + 1, true);
|
|
427
|
+
iv.isIntake = true;
|
|
428
|
+
iv.isReturn = false;
|
|
429
|
+
iv.type = 0;
|
|
430
|
+
iv.name = 'Intake';
|
|
431
|
+
iv.circuit = 247;
|
|
432
|
+
iv.isActive = true;
|
|
433
|
+
iv.master = 1;
|
|
434
|
+
if (typeof rv === 'undefined') rv = sys.valves.getItemById(sys.valves.getMaxId(false, 0) + 1, true);
|
|
435
|
+
rv.isIntake = false;
|
|
436
|
+
rv.isReturn = true;
|
|
437
|
+
rv.name = 'Return';
|
|
438
|
+
rv.type = 0;
|
|
439
|
+
rv.circuit = 247;
|
|
440
|
+
rv.isActive = true;
|
|
441
|
+
rv.master = 1;
|
|
442
|
+
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
if (typeof iv !== 'undefined') {
|
|
446
|
+
sys.valves.removeItemById(iv.id);
|
|
447
|
+
state.valves.removeItemById(iv.id);
|
|
448
|
+
}
|
|
449
|
+
if (typeof rv !== 'undefined') {
|
|
450
|
+
sys.valves.removeItemById(rv.id);
|
|
451
|
+
state.valves.removeItemById(rv.id);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
public async verifySetup() {
|
|
456
|
+
try {
|
|
457
|
+
// In here we are going to attempt to check all the nixie relays. We will not check the other equipment just the items
|
|
458
|
+
// that make up a raw pool like the circuits. The other stuff is the stuff of the equipment control.
|
|
459
|
+
let circs = sys.circuits.toArray().filter((val) => { return val.controller === 1; });
|
|
460
|
+
for (let i = 0; i < circs.length; i++) {
|
|
461
|
+
let circ = circs[i];
|
|
462
|
+
// Make sure we have a circuit identified in the ncp if it is controlled by Nixie.
|
|
463
|
+
let c = await ncp.circuits.initCircuitAsync(circ);
|
|
464
|
+
// Now we should have the circuit from nixie so check the status to see if it can be
|
|
465
|
+
// controlled. i.e. The comms are up.
|
|
466
|
+
await c.validateSetupAsync(circ, state.circuits.getItemById(circ.id))
|
|
467
|
+
}
|
|
468
|
+
// Now we need to validate the heaters. Some heaters will be connected via a relay. If they have comms we will check it.
|
|
469
|
+
let heaters = sys.heaters.toArray().filter((val) => { return val.controller === 1 });
|
|
470
|
+
for (let i = 0; i < heaters.length; i++) {
|
|
471
|
+
let heater = heaters[i];
|
|
472
|
+
let h = await ncp.heaters.initHeaterAsync(heater);
|
|
473
|
+
}
|
|
474
|
+
// If we have relay based pumps, init them here... ss, ds, superflo
|
|
475
|
+
let pumps = sys.heaters.toArray().filter((val) => { return val.controller === 1 });
|
|
476
|
+
for (let i = 0; i < pumps.length; i++) {
|
|
477
|
+
let pump = pumps[i];
|
|
478
|
+
if (pump.type === 65){ // how are we defining ss and superflo?
|
|
479
|
+
await ncp.pumps.initPumpAsync(pump);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
} catch (err) { logger.error(`Error verifying setup`); }
|
|
483
|
+
}
|
|
484
|
+
public equipmentMaster = 1;
|
|
485
|
+
public system: NixieSystemCommands = new NixieSystemCommands(this);
|
|
486
|
+
public circuits: NixieCircuitCommands = new NixieCircuitCommands(this);
|
|
487
|
+
public features: NixieFeatureCommands = new NixieFeatureCommands(this);
|
|
488
|
+
//public chlorinator: NixieChlorinatorCommands = new NixieChlorinatorCommands(this);
|
|
489
|
+
public bodies: NixieBodyCommands = new NixieBodyCommands(this);
|
|
490
|
+
public filters: NixieFilterCommands = new NixieFilterCommands(this);
|
|
491
|
+
public pumps: NixiePumpCommands = new NixiePumpCommands(this);
|
|
492
|
+
//public schedules: NixieScheduleCommands = new NixieScheduleCommands(this);
|
|
493
|
+
public heaters: NixieHeaterCommands = new NixieHeaterCommands(this);
|
|
494
|
+
public valves: NixieValveCommands = new NixieValveCommands(this);
|
|
495
|
+
public chemControllers: NixieChemControllerCommands = new NixieChemControllerCommands(this);
|
|
496
|
+
public async setControllerType(obj): Promise<Equipment> {
|
|
497
|
+
try {
|
|
498
|
+
if (typeof obj.model === 'undefined') return Promise.reject(new InvalidEquipmentDataError(`Nixie: Controller model not supplied`, 'model', obj.model));
|
|
499
|
+
let mt = this.valueMaps.expansionBoards.findItem(obj.model);
|
|
500
|
+
if (typeof mt === 'undefined') return Promise.reject(new InvalidEquipmentDataError(`Nixie: A valid Controller model not supplied ${obj.model}`, 'model', obj.model));
|
|
501
|
+
this.killStatusCheck();
|
|
502
|
+
let mod = sys.equipment.modules.getItemById(0, true);
|
|
503
|
+
mod.type = mt.val;
|
|
504
|
+
await this.initNixieBoard();
|
|
505
|
+
state.emitControllerChange();
|
|
506
|
+
return sys.equipment;
|
|
507
|
+
} catch (err) { logger.error(`Error setting Nixie controller type.`); }
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
export class NixieBodyCommands extends BodyCommands {
|
|
511
|
+
|
|
512
|
+
}
|
|
513
|
+
export class NixieFilterCommands extends FilterCommands {
|
|
514
|
+
public async setFilterStateAsync(filter: Filter, fstate: FilterState, isOn: boolean) {
|
|
515
|
+
try {
|
|
516
|
+
await ncp.filters.setFilterStateAsync(fstate, isOn);
|
|
517
|
+
}
|
|
518
|
+
catch (err) { return Promise.reject(new BoardProcessError(`Nixie: Error setFiterStateAsync ${err.message}`, 'setFilterStateAsync')); }
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
export class NixieSystemCommands extends SystemCommands {
|
|
522
|
+
protected _modeTimer: NodeJS.Timeout;
|
|
523
|
+
public cancelDelay(): Promise<any> {
|
|
524
|
+
delayMgr.cancelPumpValveDelays();
|
|
525
|
+
delayMgr.cancelHeaterCooldownDelays();
|
|
526
|
+
delayMgr.cancelHeaterStartupDelays();
|
|
527
|
+
delayMgr.cancelCleanerStartDelays();
|
|
528
|
+
delayMgr.cancelManualPriorityDelays();
|
|
529
|
+
state.delay = sys.board.valueMaps.delay.getValue('nodelay');
|
|
530
|
+
return Promise.resolve(state.data.delay);
|
|
531
|
+
}
|
|
532
|
+
public setManualOperationPriority(id: number): Promise<any> {
|
|
533
|
+
let cstate = state.circuits.getInterfaceById(id);
|
|
534
|
+
delayMgr.setManualPriorityDelay(cstate);
|
|
535
|
+
return Promise.resolve(cstate);
|
|
536
|
+
}
|
|
537
|
+
public setDateTimeAsync(obj: any): Promise<any> { return Promise.resolve(); }
|
|
538
|
+
public getDOW() { return this.board.valueMaps.scheduleDays.toArray(); }
|
|
539
|
+
public async setGeneralAsync(obj: any): Promise<General> {
|
|
540
|
+
let general = sys.general.get();
|
|
541
|
+
if (typeof obj.alias === 'string') sys.general.alias = obj.alias;
|
|
542
|
+
if (typeof obj.options !== 'undefined') await sys.board.system.setOptionsAsync(obj.options);
|
|
543
|
+
if (typeof obj.location !== 'undefined') await sys.board.system.setLocationAsync(obj.location);
|
|
544
|
+
if (typeof obj.owner !== 'undefined') await sys.board.system.setOwnerAsync(obj.owner);
|
|
545
|
+
return new Promise<General>(function (resolve, reject) { resolve(sys.general); });
|
|
546
|
+
}
|
|
547
|
+
public async setModelAsync(obj: any) {
|
|
548
|
+
try {
|
|
549
|
+
// First things first.
|
|
550
|
+
|
|
551
|
+
} catch (err) { return logger.error(`Error setting Nixie Model: ${err.message}`); }
|
|
552
|
+
}
|
|
553
|
+
public async setPanelModeAsync(data: any): Promise<any> {
|
|
554
|
+
let mode = sys.board.valueMaps.panelModes.findItem(data.mode);
|
|
555
|
+
let timeout = parseInt(data.timeout, 10);
|
|
556
|
+
if (typeof mode === 'undefined') return Promise.reject(new ServiceParameterError(`Invalid mode value cannot set mode`, 'setPanelModeAsync', 'mode', data.mode));
|
|
557
|
+
switch (mode.name) {
|
|
558
|
+
case 'timeout':
|
|
559
|
+
if (isNaN(timeout) || timeout <= 0) return Promise.reject(new ServiceParameterError(`Invalid timeout value cannot set mode`, 'setPanelModeAsync', 'timeout', data.timeout));
|
|
560
|
+
await this.initServiceMode(mode, timeout);
|
|
561
|
+
break;
|
|
562
|
+
case 'service':
|
|
563
|
+
await this.initServiceMode(mode);
|
|
564
|
+
break;
|
|
565
|
+
case 'auto':
|
|
566
|
+
// Ok we are switching back to auto.
|
|
567
|
+
// 1. Kill the timeout timer if it exists.
|
|
568
|
+
// 2. Set the mode to auto.
|
|
569
|
+
if (this._modeTimer) clearTimeout(this._modeTimer);
|
|
570
|
+
this._modeTimer = null;
|
|
571
|
+
state.mode = 0;
|
|
572
|
+
webApp.emitToClients('panelMode', { mode: mode, remaining: 0 });
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
private checkServiceTimeout(mode: any, start: number, timeout: number, interval?: number) {
|
|
577
|
+
if (this._modeTimer) clearTimeout(this._modeTimer);
|
|
578
|
+
this._modeTimer = null;
|
|
579
|
+
// The timeout is in seconds so we will need to deal with that.
|
|
580
|
+
let elapsed = (new Date().getTime() - start) / 1000;
|
|
581
|
+
let remaining = timeout - elapsed;
|
|
582
|
+
logger.info(`Timeout: ${timeout} Elapsed: ${elapsed}`);
|
|
583
|
+
if (remaining > 0) {
|
|
584
|
+
webApp.emitToClients('panelMode', { mode: mode, remaining: remaining, elapsed: elapsed, timeout: timeout });
|
|
585
|
+
this._modeTimer = setTimeoutSync(() => { this.checkServiceTimeout(mode, start, timeout, interval || 1000); }, interval || 1000);
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
webApp.emitToClients('panelMode', { mode: sys.board.valueMaps.panelModes.transform(0), remaining: 0 });
|
|
589
|
+
state.mode = 0;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
public async initServiceMode(mode, timeout?: number) {
|
|
593
|
+
if (this._modeTimer) clearTimeout(this._modeTimer);
|
|
594
|
+
for (let i = 0; i < sys.circuits.length; i++) {
|
|
595
|
+
let circ = sys.circuits.getItemByIndex(i);
|
|
596
|
+
if (circ.master === 1) {
|
|
597
|
+
let cstate = state.circuits.getItemById(circ.id);
|
|
598
|
+
if (cstate.isOn) await sys.board.circuits.setCircuitStateAsync(circ.id, false, true);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
delayMgr.clearAllDelays();
|
|
602
|
+
state.mode = mode.val;
|
|
603
|
+
// Shut everything down.
|
|
604
|
+
await ncp.setServiceModeAsync();
|
|
605
|
+
if (timeout > 0) {
|
|
606
|
+
let start = new Date().getTime();
|
|
607
|
+
this.checkServiceTimeout(mode, start, timeout, 1000);
|
|
608
|
+
webApp.emitToClients('panelMode', { mode: mode, remaining: timeout, elapsed: 0, timeout: timeout });
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
webApp.emitToClients('panelMode', { mode: mode });
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
export class NixieCircuitCommands extends CircuitCommands {
|
|
617
|
+
// This is our poll loop for circuit relay states.
|
|
618
|
+
public async syncCircuitRelayStates() {
|
|
619
|
+
try {
|
|
620
|
+
if (state.mode !== 0) return;
|
|
621
|
+
for (let i = 0; i < sys.circuits.length; i++) {
|
|
622
|
+
// Run through all the controlled circuits to see whether they should be triggered or not.
|
|
623
|
+
let circ = sys.circuits.getItemByIndex(i);
|
|
624
|
+
if (circ.master === 1 && circ.isActive) {
|
|
625
|
+
let cstate = state.circuits.getItemById(circ.id);
|
|
626
|
+
if (cstate.isOn) await ncp.circuits.setCircuitStateAsync(cstate, cstate.isOn);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
} catch (err) { logger.error(`syncCircuitRelayStates: Error synchronizing circuit relays ${err.message}`); }
|
|
630
|
+
}
|
|
631
|
+
public async setCircuitStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<ICircuitState> {
|
|
632
|
+
sys.board.suspendStatus(true);
|
|
633
|
+
try {
|
|
634
|
+
// We need to do some routing here as it is now critical that circuits, groups, and features
|
|
635
|
+
// have their own processing. The virtual controller used to only deal with one circuit.
|
|
636
|
+
if (sys.board.equipmentIds.circuitGroups.isInRange(id))
|
|
637
|
+
return await sys.board.circuits.setCircuitGroupStateAsync(id, val);
|
|
638
|
+
else if (sys.board.equipmentIds.features.isInRange(id))
|
|
639
|
+
return await sys.board.features.setFeatureStateAsync(id, val);
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
let circuit: ICircuit = sys.circuits.getInterfaceById(id, false, { isActive: false });
|
|
643
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Circuit or Feature id ${id} not valid`, id, 'Circuit'));
|
|
644
|
+
let circ = state.circuits.getInterfaceById(id, circuit.isActive !== false);
|
|
645
|
+
if (state.mode !== 0) return circ;
|
|
646
|
+
if (circ.stopDelay) {
|
|
647
|
+
// Send this off so that the relays are properly set. In the end we cannot change right now. If this
|
|
648
|
+
// happens to be a body circuit then the relay state will be skipped anyway.
|
|
649
|
+
await ncp.circuits.setCircuitStateAsync(circ, circ.isOn);
|
|
650
|
+
return circ;
|
|
651
|
+
}
|
|
652
|
+
let newState = utils.makeBool(val);
|
|
653
|
+
let ctype = sys.board.valueMaps.circuitFunctions.getName(circ.type);
|
|
654
|
+
// Filter out any special circuit types.
|
|
655
|
+
switch (ctype) {
|
|
656
|
+
case 'pool':
|
|
657
|
+
case 'spa':
|
|
658
|
+
await this.setBodyCircuitStateAsync(id, newState, ignoreDelays);
|
|
659
|
+
break;
|
|
660
|
+
case 'mastercleaner':
|
|
661
|
+
case 'mastercleaner2':
|
|
662
|
+
await this.setCleanerCircuitStateAsync(id, newState, ignoreDelays);
|
|
663
|
+
break;
|
|
664
|
+
case 'spillway':
|
|
665
|
+
await this.setSpillwayCircuitStateAsync(id, newState, ignoreDelays);
|
|
666
|
+
break;
|
|
667
|
+
case 'spadrain':
|
|
668
|
+
await this.setDrainCircuitStateAsync(id, newState, ignoreDelays);
|
|
669
|
+
break;
|
|
670
|
+
default:
|
|
671
|
+
await ncp.circuits.setCircuitStateAsync(circ, newState);
|
|
672
|
+
await sys.board.processStatusAsync();
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
// Let the main nixie controller set the circuit state and affect the relays if it needs to.
|
|
676
|
+
return state.circuits.getInterfaceById(circ.id);
|
|
677
|
+
}
|
|
678
|
+
catch (err) { logger.error(`Nixie: setCircuitState ${err.message}`); return Promise.reject(new BoardProcessError(`Nixie: Error setCircuitStateAsync ${err.message}`, 'setCircuitState')); }
|
|
679
|
+
finally {
|
|
680
|
+
state.emitEquipmentChanges();
|
|
681
|
+
ncp.pumps.syncPumpStates();
|
|
682
|
+
sys.board.suspendStatus(false);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
protected async setCleanerCircuitStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<ICircuitState> {
|
|
686
|
+
try {
|
|
687
|
+
let cstate = state.circuits.getItemById(id);
|
|
688
|
+
let circuit = sys.circuits.getItemById(id);
|
|
689
|
+
// We know which body the cleaner belongs to by an attribute on the circuit function.
|
|
690
|
+
let ctype = sys.board.valueMaps.circuitFunctions.get(circuit.type);
|
|
691
|
+
let bstate = state.temps.bodies.getItemById(ctype.body || 1);
|
|
692
|
+
// Cleaner lockout should occur when
|
|
693
|
+
// 1. The body circuit is off.
|
|
694
|
+
// 2. The spillway mode is running.
|
|
695
|
+
|
|
696
|
+
// Optional modes include
|
|
697
|
+
// 1. The current body is heating with solar.
|
|
698
|
+
|
|
699
|
+
// Lockouts are cleared when
|
|
700
|
+
// 1. The above conditions are no longer true.
|
|
701
|
+
// 2. The user requests the circuit to be off.
|
|
702
|
+
if (!val) {
|
|
703
|
+
// We can always turn a cleaner circuit off. Even if a delay is underway.
|
|
704
|
+
delayMgr.clearCleanerStartDelays(bstate.id);
|
|
705
|
+
await ncp.circuits.setCircuitStateAsync(cstate, false);
|
|
706
|
+
}
|
|
707
|
+
else if (val) {
|
|
708
|
+
logger.info(`Setting cleaner circuit ${cstate.name} to ${val}`);
|
|
709
|
+
// Alright we are turning the cleaner on.
|
|
710
|
+
// To turn on the cleaner circuit we must first ensure the body is on. If it is not then we abort.
|
|
711
|
+
if (!bstate.isOn) {
|
|
712
|
+
logger.info(`Cannot turn on cleaner circuit ${cstate.name}. ${bstate.name} is not running`);
|
|
713
|
+
await ncp.circuits.setCircuitStateAsync(cstate, false);
|
|
714
|
+
return cstate;
|
|
715
|
+
}
|
|
716
|
+
// If there is a drain circuit going shut that thing off.
|
|
717
|
+
await this.turnOffDrainCircuits(ignoreDelays);
|
|
718
|
+
// If solar is currently on and the cleaner solar delay is set then we need to calculate a delay
|
|
719
|
+
// to turn on the cleaner.
|
|
720
|
+
let delayTime = 0;
|
|
721
|
+
let dtNow = new Date().getTime();
|
|
722
|
+
if (typeof ignoreDelays === 'undefined' || !ignoreDelays) {
|
|
723
|
+
if (sys.general.options.cleanerSolarDelay && sys.general.options.cleanerSolarDelayTime > 0) {
|
|
724
|
+
let circBody = state.circuits.getItemById(bstate.circuit);
|
|
725
|
+
// If the body has not been on or the solar heater has not been on long enough then we need to delay the startup.
|
|
726
|
+
if (sys.board.valueMaps.heatStatus.getName(bstate.heatStatus) === 'solar') {
|
|
727
|
+
// Check for the solar delay. We need to know when the heater first kicked in. A cleaner and solar
|
|
728
|
+
// heater can run at the same time but the heater must be on long enough for the timer to expire.
|
|
729
|
+
|
|
730
|
+
// The reasoning behind this is so that the booster pump can be assured that there is sufficient pressure
|
|
731
|
+
// for it to start and any air from the solar has had time to purge through the system.
|
|
732
|
+
let heaters = sys.heaters.getSolarHeaters(bstate.id);
|
|
733
|
+
let startTime = 0;
|
|
734
|
+
for (let i = 0; i < heaters.length; i++) {
|
|
735
|
+
let heater = heaters.getItemByIndex(i);
|
|
736
|
+
let hstate = state.heaters.getItemById(heater.id);
|
|
737
|
+
startTime = Math.max(startTime, hstate.startTime.getTime());
|
|
738
|
+
}
|
|
739
|
+
// Lets see if we have a solar start delay.
|
|
740
|
+
delayTime = Math.max(Math.round(((sys.general.options.cleanerSolarDelayTime * 1000) - (dtNow - startTime))) / 1000, delayTime);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
if (sys.general.options.cleanerStartDelay && sys.general.options.cleanerStartDelayTime) {
|
|
744
|
+
let bcstate = state.circuits.getItemById(bstate.circuit);
|
|
745
|
+
// So we should be started. Lets determine whethere there should be any delay.
|
|
746
|
+
delayTime = Math.max(Math.round(((sys.general.options.cleanerStartDelayTime * 1000) - (dtNow - bcstate.startTime.getTime())) / 1000), delayTime);
|
|
747
|
+
logger.info(`Cleaner delay time calculated to ${delayTime}`);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
if (delayTime > 5) delayMgr.setCleanerStartDelay(cstate, bstate.id, delayTime);
|
|
751
|
+
else await ncp.circuits.setCircuitStateAsync(cstate, true);
|
|
752
|
+
}
|
|
753
|
+
return cstate;
|
|
754
|
+
} catch (err) { return Promise.reject(new BoardProcessError(`Nixie: Error setting cleaner circuit state: ${err.message}`, 'setCleanerCircuitStateAsync')); }
|
|
755
|
+
}
|
|
756
|
+
protected async setBodyCircuitStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<CircuitState> {
|
|
757
|
+
try {
|
|
758
|
+
let cstate = state.circuits.getItemById(id);
|
|
759
|
+
let circuit = sys.circuits.getItemById(id);
|
|
760
|
+
let bstate = state.temps.bodies.getBodyByCircuitId(id);
|
|
761
|
+
if (cstate.isOn === val) return; // If body is already in desired state, don't do anything.
|
|
762
|
+
// https://github.com/tagyoureit/nodejs-poolController/issues/361#issuecomment-1186087763
|
|
763
|
+
if (val) {
|
|
764
|
+
// We are turning on a body circuit.
|
|
765
|
+
logger.verbose(`Turning on a body circuit ${bstate.name}`);
|
|
766
|
+
if (sys.equipment.shared === true) {
|
|
767
|
+
// If we are turning on and this is a shared system it means that we need to turn off
|
|
768
|
+
// the other circuit.
|
|
769
|
+
let delayPumps = false;
|
|
770
|
+
await this.turnOffDrainCircuits(ignoreDelays);
|
|
771
|
+
if (bstate.id === 2) await this.turnOffSpillwayCircuits();
|
|
772
|
+
if (sys.general.options.pumpDelay === true && ignoreDelays !== true) {
|
|
773
|
+
// Now that this is off check the valve positions. If they are not currently in the correct position we need to delay any attached pump
|
|
774
|
+
// so that it does not come on while the valve is rotating. Default 30 seconds.
|
|
775
|
+
let iValves = sys.valves.getIntake();
|
|
776
|
+
for (let i = 0; i < iValves.length && !delayPumps; i++) {
|
|
777
|
+
let vstate = state.valves.getItemById(iValves[i].id);
|
|
778
|
+
if (vstate.isDiverted === true && circuit.type === 12) delayPumps = true;
|
|
779
|
+
else if (vstate.isDiverted === false && circuit.type === 13) delayPumps = true;
|
|
780
|
+
}
|
|
781
|
+
if (!delayPumps) {
|
|
782
|
+
let rValves = sys.valves.getReturn();
|
|
783
|
+
for (let i = 0; i < rValves.length && !delayPumps; i++) {
|
|
784
|
+
let vstate = state.valves.getItemById(rValves[i].id);
|
|
785
|
+
if (vstate.isDiverted === true && circuit.type === 12) delayPumps = true;
|
|
786
|
+
else if (vstate.isDiverted === false && circuit.type === 13) delayPumps = true;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
// If we are shared we need to turn off the other circuit.
|
|
791
|
+
let offType = circuit.type === 12 ? 13 : 12;
|
|
792
|
+
let off = sys.circuits.get().filter(elem => elem.type === offType);
|
|
793
|
+
let delayCooldown = false;
|
|
794
|
+
// Turn the circuits off that are part of the shared system. We are going back to the board
|
|
795
|
+
// just in case we got here for a circuit that isn't on the current defined panel.
|
|
796
|
+
for (let i = 0; i < off.length; i++) {
|
|
797
|
+
let coff = off[i];
|
|
798
|
+
let bsoff = state.temps.bodies.getBodyByCircuitId(coff.id);
|
|
799
|
+
let csoff = state.circuits.getItemById(coff.id);
|
|
800
|
+
// Ensure the cleaner circuits for this body are off.
|
|
801
|
+
await this.turnOffCleanerCircuits(bsoff);
|
|
802
|
+
if (csoff.isOn) {
|
|
803
|
+
logger.verbose(`Turning off shared body ${coff.name} circuit`);
|
|
804
|
+
delayMgr.clearBodyStartupDelay(bsoff);
|
|
805
|
+
if (bsoff.heaterCooldownDelay && ignoreDelays !== true) {
|
|
806
|
+
// In this condition we are requesting that the shared body start when the cooldown delay
|
|
807
|
+
// has finished. This will add this request to the cooldown delay code. The setHeaterCooldownDelay
|
|
808
|
+
// code is expected to be re-entrant and checks the id so that it does not clear
|
|
809
|
+
// the original request if it is asked for again.
|
|
810
|
+
|
|
811
|
+
// NOTE: There is room for improvement here. For instance, if the result
|
|
812
|
+
// of turning on the circuit is that the heater(s) requiring cooldown will result in being on
|
|
813
|
+
// then why not cancel the current cooldown cycle and let the user get on with it.
|
|
814
|
+
// Consider:
|
|
815
|
+
// 1. Check each heater attached to the off body to see if it is also attached to the on body.
|
|
816
|
+
// 2. If the heater is attached check to see if there is any cooldown time left on it.
|
|
817
|
+
// 3. If the above conditions are true cancel the cooldown cycle.
|
|
818
|
+
logger.verbose(`${bsoff.name} is already in Cooldown mode`);
|
|
819
|
+
delayMgr.setHeaterCooldownDelay(bsoff, bstate);
|
|
820
|
+
delayCooldown = true;
|
|
821
|
+
}
|
|
822
|
+
else {
|
|
823
|
+
// We need to deal with heater cooldown delays here since you cannot turn off the body while the heater is
|
|
824
|
+
// cooling down. This means we need to check to see if the heater requires cooldown then set a delay for it
|
|
825
|
+
// if it does. The delay manager will shut the body off and start the new body when it is done.
|
|
826
|
+
let heaters = sys.board.heaters.getHeatersByCircuitId(circuit.id);
|
|
827
|
+
let cooldownTime = 0;
|
|
828
|
+
if (ignoreDelays !== true) {
|
|
829
|
+
for (let j = 0; j < heaters.length; j++) {
|
|
830
|
+
let nheater = ncp.heaters.find(x => x.id === heaters[j].id) as NixieHeaterBase;
|
|
831
|
+
cooldownTime = Math.max(nheater.getCooldownTime(), cooldownTime);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
if (cooldownTime > 0) {
|
|
835
|
+
// We need do start a cooldown cycle for the body. If there is already
|
|
836
|
+
// a cooldown underway this will append the on to it.
|
|
837
|
+
delayMgr.setHeaterCooldownDelay(bsoff, bstate, cooldownTime * 1000);
|
|
838
|
+
delayCooldown = true;
|
|
839
|
+
}
|
|
840
|
+
else {
|
|
841
|
+
await ncp.circuits.setCircuitStateAsync(csoff, false);
|
|
842
|
+
bsoff.isOn = false;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
if (delayCooldown) return cstate;
|
|
848
|
+
if (delayPumps === true) sys.board.pumps.setPumpValveDelays([id, bstate.circuit]);
|
|
849
|
+
}
|
|
850
|
+
// Now we need to set the startup delay for all the heaters. This is true whether
|
|
851
|
+
// the system is shared or not so lets get a list of all the associated heaters for the body in question.
|
|
852
|
+
if (sys.general.options.heaterStartDelay && sys.general.options.heaterStartDelayTime > 0) {
|
|
853
|
+
let heaters = sys.board.heaters.getHeatersByCircuitId(circuit.id);
|
|
854
|
+
for (let j = 0; j < heaters.length; j++) {
|
|
855
|
+
let hstate = state.heaters.getItemById(heaters[j].id);
|
|
856
|
+
delayMgr.setHeaterStartupDelay(hstate);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
await ncp.circuits.setCircuitStateAsync(cstate, val);
|
|
860
|
+
bstate.isOn = val;
|
|
861
|
+
}
|
|
862
|
+
else if (!val) {
|
|
863
|
+
// Alright we are turning off a circuit that will result in a body shutting off. If this
|
|
864
|
+
// circuit is already under delay it should have been processed out earlier.
|
|
865
|
+
delayMgr.cancelPumpValveDelays();
|
|
866
|
+
delayMgr.cancelHeaterStartupDelays();
|
|
867
|
+
if (cstate.startDelay) delayMgr.clearBodyStartupDelay(bstate);
|
|
868
|
+
await this.turnOffCleanerCircuits(bstate);
|
|
869
|
+
if (sys.equipment.shared && bstate.id === 2) await this.turnOffDrainCircuits(ignoreDelays);
|
|
870
|
+
logger.verbose(`Turning off a body circuit ${circuit.name}`);
|
|
871
|
+
if (cstate.isOn) {
|
|
872
|
+
// Check to see if we have any heater cooldown delays that need to take place.
|
|
873
|
+
let heaters = sys.board.heaters.getHeatersByCircuitId(circuit.id);
|
|
874
|
+
let cooldownTime = 0;
|
|
875
|
+
for (let j = 0; j < heaters.length; j++) {
|
|
876
|
+
let nheater = ncp.heaters.find(x => x.id === heaters[j].id) as NixieHeaterBase;
|
|
877
|
+
cooldownTime = Math.max(nheater.getCooldownTime(), cooldownTime);
|
|
878
|
+
}
|
|
879
|
+
if (cooldownTime > 0) {
|
|
880
|
+
logger.info(`Starting a Cooldown Delay ${cooldownTime}sec`);
|
|
881
|
+
// We need do start a cooldown cycle for the body.
|
|
882
|
+
delayMgr.setHeaterCooldownDelay(bstate, undefined, cooldownTime * 1000);
|
|
883
|
+
}
|
|
884
|
+
else {
|
|
885
|
+
await ncp.circuits.setCircuitStateAsync(cstate, val);
|
|
886
|
+
bstate.isOn = val;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
else {
|
|
890
|
+
bstate.isOn = val;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
return cstate;
|
|
894
|
+
} catch (err) { logger.error(`Nixie: Error setBodyCircuitStateAsync ${err.message}`); return Promise.reject(new BoardProcessError(`Nixie: Error setBodyCircuitStateAsync ${err.message}`, 'setBodyCircuitStateAsync')); }
|
|
895
|
+
}
|
|
896
|
+
protected async setSpillwayCircuitStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<CircuitState> {
|
|
897
|
+
try {
|
|
898
|
+
let cstate = state.circuits.getItemById(id);
|
|
899
|
+
let delayPumps = false;
|
|
900
|
+
if (cstate.isOn !== val) {
|
|
901
|
+
if (sys.equipment.shared === true) {
|
|
902
|
+
// First we need to check to see if the pool is on.
|
|
903
|
+
if (val) {
|
|
904
|
+
let spastate = state.circuits.getItemById(1);
|
|
905
|
+
if (spastate.isOn) {
|
|
906
|
+
logger.warn(`Cannot turn ${cstate.name} on because ${spastate.name} is on`);
|
|
907
|
+
return cstate;
|
|
908
|
+
}
|
|
909
|
+
// If there are any drain circuits or features that are currently engaged we need to turn them off.
|
|
910
|
+
await this.turnOffDrainCircuits(ignoreDelays);
|
|
911
|
+
if (sys.general.options.pumpDelay && sys.general.options.valveDelayTime > 0) sys.board.pumps.setPumpValveDelays([6, id]);
|
|
912
|
+
}
|
|
913
|
+
else if (!val && !ignoreDelays) {
|
|
914
|
+
// If we are turning off and there is another circuit that ties to the same pumps then we need set a valve delay. This means
|
|
915
|
+
// that if the pool circuit is on then we need to delay the pumps. However, if there is no other circuit that needs
|
|
916
|
+
// the pump to be on, then no harm no foul a delay in the pump won't mean anything.
|
|
917
|
+
|
|
918
|
+
// Conditions where this should not delay.
|
|
919
|
+
// 1. Another spillway circuit or feature is on.
|
|
920
|
+
// 2. There is no other running circuit that will affect the intake or return.
|
|
921
|
+
let arrIds = sys.board.valves.getBodyValveCircuitIds(true);
|
|
922
|
+
if (arrIds.length > 1) {
|
|
923
|
+
if (sys.general.options.pumpDelay && sys.general.options.valveDelayTime > 0) {
|
|
924
|
+
sys.board.pumps.setPumpValveDelays([6, id]);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
logger.verbose(`Turning ${val ? 'on' : 'off'} a spillway circuit ${cstate.name}`);
|
|
931
|
+
await ncp.circuits.setCircuitStateAsync(cstate, val);
|
|
932
|
+
return cstate;
|
|
933
|
+
} catch (err) { logger.error(`Nixie: Error setSpillwayCircuitStateAsync ${err.message}`); return Promise.reject(new BoardProcessError(`Nixie: Error setSpillwayCircuitStateAsync ${err.message}`, 'setBodyCircuitStateAsync')); }
|
|
934
|
+
}
|
|
935
|
+
protected async setDrainCircuitStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<CircuitState> {
|
|
936
|
+
try {
|
|
937
|
+
// Drain circuits can be very bad. This is because they can be turned on then never turned off
|
|
938
|
+
// we may want to create some limits are to how long they can be on or even force them off
|
|
939
|
+
// if for instance the spa is not on.
|
|
940
|
+
// RULES FOR DRAIN CIRCUITS:
|
|
941
|
+
// 1. All spillway circuits must be off.
|
|
942
|
+
let cstate = state.circuits.getItemById(id);
|
|
943
|
+
let delayPumps = false;
|
|
944
|
+
if (cstate.isOn !== val) {
|
|
945
|
+
if (sys.equipment.shared === true) {
|
|
946
|
+
let spastate = state.temps.bodies.getItemById(2);
|
|
947
|
+
let poolstate = state.temps.bodies.getItemById(1);
|
|
948
|
+
// First we need to check to see if the pool is on.
|
|
949
|
+
if (val) {
|
|
950
|
+
if (spastate.isOn || spastate.startDelay || poolstate.isOn || poolstate.startDelay) {
|
|
951
|
+
logger.warn(`Cannot turn ${cstate.name} on because a body is on`);
|
|
952
|
+
return cstate;
|
|
953
|
+
}
|
|
954
|
+
// If there are any spillway circuits or features that are currently engaged we need to turn them off.
|
|
955
|
+
await this.turnOffSpillwayCircuits(true);
|
|
956
|
+
// If there are any cleaner circuits on for the main body turn them off.
|
|
957
|
+
await this.turnOffCleanerCircuits(state.temps.bodies.getItemById(1), true);
|
|
958
|
+
if (!ignoreDelays && sys.general.options.pumpDelay && sys.general.options.valveDelayTime > 0) sys.board.pumps.setPumpValveDelays([id, 1, 6]);
|
|
959
|
+
}
|
|
960
|
+
else if (!val && !ignoreDelays) {
|
|
961
|
+
if (!ignoreDelays && sys.general.options.pumpDelay && sys.general.options.valveDelayTime > 0) sys.board.pumps.setPumpValveDelays([id, 1, 6]);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
logger.verbose(`Turning ${val ? 'on' : 'off'} a drain circuit ${cstate.name}`);
|
|
966
|
+
await ncp.circuits.setCircuitStateAsync(cstate, val);
|
|
967
|
+
return cstate;
|
|
968
|
+
} catch (err) { logger.error(`Nixie: Error setDrainCircuitStateAsync ${err.message}`); return Promise.reject(new BoardProcessError(`Nixie: Error setDrainCircuitStateAsync ${err.message}`, 'setDrainCircuitStateAsync')); }
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
public toggleCircuitStateAsync(id: number): Promise<ICircuitState> {
|
|
972
|
+
let circ = state.circuits.getInterfaceById(id);
|
|
973
|
+
return this.setCircuitStateAsync(id, !(circ.isOn || false));
|
|
974
|
+
}
|
|
975
|
+
public async setLightThemeAsync(id: number, theme: number) {
|
|
976
|
+
if (sys.board.equipmentIds.circuitGroups.isInRange(id)) {
|
|
977
|
+
await this.setLightGroupThemeAsync(id, theme);
|
|
978
|
+
return Promise.resolve(state.lightGroups.getItemById(id));
|
|
979
|
+
}
|
|
980
|
+
let cstate = state.circuits.getItemById(id);
|
|
981
|
+
if (state.mode !== 0) return cstate;
|
|
982
|
+
let circ = sys.circuits.getItemById(id);
|
|
983
|
+
let thm = sys.board.valueMaps.lightThemes.findItem(theme);
|
|
984
|
+
if (typeof thm !== 'undefined' && typeof thm.sequence !== 'undefined' && circ.master === 1) {
|
|
985
|
+
logger.info(`Setting light theme for ${circ.name} to ${thm.name} [${thm.sequence}]`);
|
|
986
|
+
await ncp.circuits.setLightThemeAsync(id, thm);
|
|
987
|
+
}
|
|
988
|
+
cstate.lightingTheme = theme;
|
|
989
|
+
return Promise.resolve(cstate as ICircuitState);
|
|
990
|
+
}
|
|
991
|
+
public setDimmerLevelAsync(id: number, level: number): Promise<ICircuitState> {
|
|
992
|
+
let circ = state.circuits.getItemById(id);
|
|
993
|
+
circ.level = level;
|
|
994
|
+
return Promise.resolve(circ as ICircuitState);
|
|
995
|
+
}
|
|
996
|
+
public getCircuitReferences(includeCircuits?: boolean, includeFeatures?: boolean, includeVirtual?: boolean, includeGroups?: boolean) {
|
|
997
|
+
let arrRefs = [];
|
|
998
|
+
if (includeCircuits) {
|
|
999
|
+
// RSG: converted this to getItemByIndex because hasHeatSource isn't actually stored as part of the data
|
|
1000
|
+
for (let i = 0; i < sys.circuits.length; i++) {
|
|
1001
|
+
let c = sys.circuits.getItemByIndex(i);
|
|
1002
|
+
arrRefs.push({ id: c.id, name: c.name, type: c.type, equipmentType: 'circuit', nameId: c.nameId, hasHeatSource: c.hasHeatSource });
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
if (includeFeatures) {
|
|
1006
|
+
let features = sys.features.get();
|
|
1007
|
+
for (let i = 0; i < sys.features.length; i++) {
|
|
1008
|
+
let c = features[i];
|
|
1009
|
+
arrRefs.push({ id: c.id, name: c.name, type: c.type, equipmentType: 'feature', nameId: c.nameId });
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
if (includeVirtual) {
|
|
1013
|
+
let vcs = sys.board.valueMaps.virtualCircuits.toArray();
|
|
1014
|
+
for (let i = 0; i < vcs.length; i++) {
|
|
1015
|
+
let c = vcs[i];
|
|
1016
|
+
arrRefs.push({ id: c.val, name: c.desc, equipmentType: 'virtual', assignableToPumpCircuit: c.assignableToPumpCircuit });
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
if (includeGroups) {
|
|
1020
|
+
let groups = sys.circuitGroups.get();
|
|
1021
|
+
for (let i = 0; i < groups.length; i++) {
|
|
1022
|
+
let c = groups[i];
|
|
1023
|
+
arrRefs.push({ id: c.id, name: c.name, equipmentType: 'circuitGroup', nameId: c.nameId });
|
|
1024
|
+
}
|
|
1025
|
+
groups = sys.lightGroups.get();
|
|
1026
|
+
for (let i = 0; i < groups.length; i++) {
|
|
1027
|
+
let c = groups[i];
|
|
1028
|
+
arrRefs.push({ id: c.id, name: c.name, equipmentType: 'lightGroup', nameId: c.nameId });
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
arrRefs.sort((a, b) => { return a.id > b.id ? 1 : a.id === b.id ? 0 : -1; });
|
|
1032
|
+
return arrRefs;
|
|
1033
|
+
}
|
|
1034
|
+
public getLightReferences() {
|
|
1035
|
+
let circuits = sys.circuits.get();
|
|
1036
|
+
let arrRefs = [];
|
|
1037
|
+
for (let i = 0; i < circuits.length; i++) {
|
|
1038
|
+
let c = circuits[i];
|
|
1039
|
+
let type = sys.board.valueMaps.circuitFunctions.transform(c.type);
|
|
1040
|
+
if (type.isLight) arrRefs.push({ id: c.id, name: c.name, type: c.type, equipmentType: 'circuit', nameId: c.nameId });
|
|
1041
|
+
}
|
|
1042
|
+
return arrRefs;
|
|
1043
|
+
}
|
|
1044
|
+
public getLightThemes(type?: number) {
|
|
1045
|
+
let tobj = (typeof type === 'undefined') ? sys.board.valueMaps.circuitFunctions.transformByName('intellibrite') : sys.board.valueMaps.circuitFunctions.transform(type);
|
|
1046
|
+
let arrThemes = sys.board.valueMaps.lightThemes.toArray();
|
|
1047
|
+
let arr = [];
|
|
1048
|
+
for (let i = 0; i < arrThemes.length; i++) {
|
|
1049
|
+
if (tobj.name === arrThemes[i].type) arr.push(arrThemes[i]);
|
|
1050
|
+
}
|
|
1051
|
+
return arr;
|
|
1052
|
+
}
|
|
1053
|
+
public getCircuitFunctions() {
|
|
1054
|
+
let cf = sys.board.valueMaps.circuitFunctions.toArray();
|
|
1055
|
+
if (!sys.equipment.shared) cf = cf.filter(x => { return x.name !== 'spillway' && x.name !== 'spadrain' });
|
|
1056
|
+
return cf;
|
|
1057
|
+
}
|
|
1058
|
+
public getCircuitNames() {
|
|
1059
|
+
return [...sys.board.valueMaps.circuitNames.toArray(), ...sys.board.valueMaps.customNames.toArray()];
|
|
1060
|
+
}
|
|
1061
|
+
public async setCircuitAsync(data: any): Promise<ICircuit> {
|
|
1062
|
+
try {
|
|
1063
|
+
let id = parseInt(data.id, 10);
|
|
1064
|
+
if (id <= 0 || isNaN(id)) {
|
|
1065
|
+
// You can add any circuit so long as it isn't 1 or 6.
|
|
1066
|
+
id = sys.circuits.getNextEquipmentId(sys.board.equipmentIds.circuits, [1, 6]);
|
|
1067
|
+
}
|
|
1068
|
+
if (isNaN(id) || !sys.board.equipmentIds.circuits.isInRange(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid circuit id: ${data.id}`, data.id, 'Circuit'));
|
|
1069
|
+
let circuit = sys.circuits.getItemById(id, true);
|
|
1070
|
+
let scircuit = state.circuits.getItemById(id, true);
|
|
1071
|
+
scircuit.isActive = circuit.isActive = true;
|
|
1072
|
+
circuit.master = 1;
|
|
1073
|
+
if (data.name) circuit.name = scircuit.name = data.name;
|
|
1074
|
+
else if (!circuit.name && !data.name) circuit.name = scircuit.name = Circuit.getIdName(id);
|
|
1075
|
+
if (typeof data.type !== 'undefined' || typeof circuit.type === 'undefined') circuit.type = scircuit.type = parseInt(data.type, 10) || 0;
|
|
1076
|
+
if (typeof data.freeze !== 'undefined' || typeof circuit.freeze === 'undefined') circuit.freeze = utils.makeBool(data.freeze) || false;
|
|
1077
|
+
if (typeof data.showInFeatures !== 'undefined' || typeof data.showInFeatures === 'undefined') circuit.showInFeatures = scircuit.showInFeatures = utils.makeBool(data.showInFeatures);
|
|
1078
|
+
if (typeof data.dontStop !== 'undefined' && utils.makeBool(data.dontStop) === true) data.eggTimer = 1440;
|
|
1079
|
+
if (typeof data.eggTimer !== 'undefined' || typeof circuit.eggTimer === 'undefined') circuit.eggTimer = parseInt(data.eggTimer, 10) || 0;
|
|
1080
|
+
if (typeof data.connectionId !== 'undefined') circuit.connectionId = data.connectionId;
|
|
1081
|
+
if (typeof data.deviceBinding !== 'undefined') circuit.deviceBinding = data.deviceBinding;
|
|
1082
|
+
circuit.dontStop = circuit.eggTimer === 1440;
|
|
1083
|
+
// update end time in case egg timer is changed while circuit is on
|
|
1084
|
+
sys.board.circuits.setEndTime(circuit, scircuit, scircuit.isOn, true);
|
|
1085
|
+
sys.emitEquipmentChange();
|
|
1086
|
+
state.emitEquipmentChanges();
|
|
1087
|
+
ncp.circuits.setCircuitAsync(circuit, data);
|
|
1088
|
+
return circuit;
|
|
1089
|
+
} catch (err) { logger.error(`Error setting circuit data ${err.message}`); }
|
|
1090
|
+
}
|
|
1091
|
+
public async setCircuitGroupAsync(obj: any): Promise<CircuitGroup> {
|
|
1092
|
+
let group: CircuitGroup = null;
|
|
1093
|
+
let id = typeof obj.id !== 'undefined' ? parseInt(obj.id, 10) : -1;
|
|
1094
|
+
if (id <= 0) {
|
|
1095
|
+
// We are adding a circuit group so we need to get the next equipment id. For circuit groups and light groups, they share ids.
|
|
1096
|
+
let range = sys.board.equipmentIds.circuitGroups;
|
|
1097
|
+
for (let i = range.start; i <= range.end; i++) {
|
|
1098
|
+
if (!sys.lightGroups.find(elem => elem.id === i) && !sys.circuitGroups.find(elem => elem.id === i)) {
|
|
1099
|
+
id = i;
|
|
1100
|
+
break;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
if (typeof id === 'undefined') return Promise.reject(new InvalidEquipmentIdError(`Max circuit group id exceeded`, id, 'CircuitGroup'));
|
|
1105
|
+
if (isNaN(id) || !sys.board.equipmentIds.circuitGroups.isInRange(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid circuit group id: ${obj.id}`, obj.id, 'CircuitGroup'));
|
|
1106
|
+
group = sys.circuitGroups.getItemById(id, true);
|
|
1107
|
+
let sgroup = state.circuitGroups.getItemById(id, true);
|
|
1108
|
+
return new Promise<CircuitGroup>((resolve, reject) => {
|
|
1109
|
+
if (typeof obj.name !== 'undefined') group.name = sgroup.name = obj.name;
|
|
1110
|
+
if (typeof obj.nameId !== 'undefined') sgroup.nameId = group.nameId =obj.nameId;
|
|
1111
|
+
if (typeof obj.dontStop !== 'undefined' && utils.makeBool(obj.dontStop) === true) obj.eggTimer = 1440;
|
|
1112
|
+
if (typeof obj.eggTimer !== 'undefined') group.eggTimer = Math.min(Math.max(parseInt(obj.eggTimer, 10), 0), 1440);
|
|
1113
|
+
if (typeof obj.showInFeatures !== 'undefined') sgroup.showInFeatures = group.showInFeatures = utils.makeBool(obj.showInFeatures);
|
|
1114
|
+
sgroup.type = group.type;
|
|
1115
|
+
|
|
1116
|
+
group.dontStop = group.eggTimer === 1440;
|
|
1117
|
+
group.isActive = sgroup.isActive = true;
|
|
1118
|
+
|
|
1119
|
+
if (typeof obj.circuits !== 'undefined') {
|
|
1120
|
+
for (let i = 0; i < obj.circuits.length; i++) {
|
|
1121
|
+
let c = group.circuits.getItemByIndex(i, true, { id: i + 1 });
|
|
1122
|
+
let cobj = obj.circuits[i];
|
|
1123
|
+
if (typeof cobj.circuit !== 'undefined') c.circuit = cobj.circuit;
|
|
1124
|
+
if (typeof cobj.desiredState !== 'undefined')
|
|
1125
|
+
c.desiredState = parseInt(cobj.desiredState, 10);
|
|
1126
|
+
else if (typeof cobj.desiredStateOn !== 'undefined') {
|
|
1127
|
+
// Shim for prior interfaces that send desiredStateOn.
|
|
1128
|
+
c.desiredState = utils.makeBool(cobj.desiredStateOn) ? 0 : 1;
|
|
1129
|
+
//c.desiredStateOn = utils.makeBool(cobj.desiredStateOn);
|
|
1130
|
+
}
|
|
1131
|
+
//RKS: 09-26-20 There is no such thing as a lighting theme on a circuit group circuit. That is what lighGroups are for.
|
|
1132
|
+
//if (typeof cobj.lightingTheme !== 'undefined') c.lightingTheme = parseInt(cobj.lightingTheme, 10);
|
|
1133
|
+
}
|
|
1134
|
+
group.circuits.length = obj.circuits.length; // RSG - removed as this will delete circuits that were not changed
|
|
1135
|
+
}
|
|
1136
|
+
// update end time in case group is changed while circuit is on
|
|
1137
|
+
sys.board.circuits.setEndTime(group, sgroup, sgroup.isOn, true);
|
|
1138
|
+
resolve(group);
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
}
|
|
1142
|
+
public async setLightGroupAsync(obj: any): Promise<LightGroup> {
|
|
1143
|
+
let group: LightGroup = null;
|
|
1144
|
+
let id = typeof obj.id !== 'undefined' ? parseInt(obj.id, 10) : -1;
|
|
1145
|
+
if (id <= 0) {
|
|
1146
|
+
// We are adding a circuit group so we need to get the next equipment id. For circuit groups and light groups, they share ids.
|
|
1147
|
+
let range = sys.board.equipmentIds.circuitGroups;
|
|
1148
|
+
for (let i = range.start; i <= range.end; i++) {
|
|
1149
|
+
if (!sys.lightGroups.find(elem => elem.id === i) && !sys.circuitGroups.find(elem => elem.id === i)) {
|
|
1150
|
+
id = i;
|
|
1151
|
+
break;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
if (typeof id === 'undefined') return Promise.reject(new InvalidEquipmentIdError(`Max circuit light group id exceeded`, id, 'LightGroup'));
|
|
1156
|
+
if (isNaN(id) || !sys.board.equipmentIds.circuitGroups.isInRange(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid circuit group id: ${obj.id}`, obj.id, 'LightGroup'));
|
|
1157
|
+
group = sys.lightGroups.getItemById(id, true);
|
|
1158
|
+
let sgroup = state.lightGroups.getItemById(id, true);
|
|
1159
|
+
return new Promise<LightGroup>((resolve, reject) => {
|
|
1160
|
+
if (typeof obj.name !== 'undefined') sgroup.name = group.name = obj.name;
|
|
1161
|
+
if (typeof obj.dontStop !== 'undefined' && utils.makeBool(obj.dontStop) === true) obj.eggTimer = 1440;
|
|
1162
|
+
if (typeof obj.eggTimer !== 'undefined') group.eggTimer = Math.min(Math.max(parseInt(obj.eggTimer, 10), 0), 1440);
|
|
1163
|
+
group.dontStop = group.eggTimer === 1440;
|
|
1164
|
+
group.isActive = true;
|
|
1165
|
+
if (typeof obj.circuits !== 'undefined') {
|
|
1166
|
+
for (let i = 0; i < obj.circuits.length; i++) {
|
|
1167
|
+
let cobj = obj.circuits[i];
|
|
1168
|
+
let c: LightGroupCircuit;
|
|
1169
|
+
if (typeof cobj.id !== 'undefined') c = group.circuits.getItemById(parseInt(cobj.id, 10), true);
|
|
1170
|
+
else if (typeof cobj.circuit !== 'undefined') c = group.circuits.getItemByCircuitId(parseInt(cobj.circuit, 10), true);
|
|
1171
|
+
else c = group.circuits.getItemByIndex(i, true, { id: i + 1 });
|
|
1172
|
+
if (typeof cobj.circuit !== 'undefined') c.circuit = cobj.circuit;
|
|
1173
|
+
if (typeof cobj.lightingTheme !== 'undefined') c.lightingTheme = parseInt(cobj.lightingTheme, 10);
|
|
1174
|
+
if (typeof cobj.color !== 'undefined') c.color = parseInt(cobj.color, 10);
|
|
1175
|
+
if (typeof cobj.swimDelay !== 'undefined') c.swimDelay = parseInt(cobj.swimDelay, 10);
|
|
1176
|
+
if (typeof cobj.position !== 'undefined') c.position = parseInt(cobj.position, 10);
|
|
1177
|
+
}
|
|
1178
|
+
// RKS: 09-25-21 - This has to be here. Not sure the goal of not setting the entire circuit array when saving the group.
|
|
1179
|
+
// group.circuits.length = obj.circuits.length; // RSG - removed as this will delete circuits that were not changed
|
|
1180
|
+
group.circuits.length = obj.circuits.length;
|
|
1181
|
+
sgroup.emitEquipmentChange();
|
|
1182
|
+
|
|
1183
|
+
}
|
|
1184
|
+
resolve(group);
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
public async deleteCircuitGroupAsync(obj: any): Promise<CircuitGroup> {
|
|
1188
|
+
let id = parseInt(obj.id, 10);
|
|
1189
|
+
if (isNaN(id)) return Promise.reject(new EquipmentNotFoundError(`Invalid group id: ${obj.id}`, 'CircuitGroup'));
|
|
1190
|
+
if (!sys.board.equipmentIds.circuitGroups.isInRange(id)) return;
|
|
1191
|
+
if (typeof obj.id !== 'undefined') {
|
|
1192
|
+
let group = sys.circuitGroups.getItemById(id, false);
|
|
1193
|
+
let sgroup = state.circuitGroups.getItemById(id, false);
|
|
1194
|
+
sys.circuitGroups.removeItemById(id);
|
|
1195
|
+
state.circuitGroups.removeItemById(id);
|
|
1196
|
+
group.isActive = false;
|
|
1197
|
+
sgroup.isOn = false;
|
|
1198
|
+
sgroup.isActive = false;
|
|
1199
|
+
sgroup.emitEquipmentChange();
|
|
1200
|
+
return new Promise<CircuitGroup>((resolve, reject) => { resolve(group); });
|
|
1201
|
+
}
|
|
1202
|
+
else
|
|
1203
|
+
return Promise.reject(new InvalidEquipmentIdError('Group id has not been defined', id, 'CircuitGroup'));
|
|
1204
|
+
}
|
|
1205
|
+
public async deleteLightGroupAsync(obj: any): Promise<LightGroup> {
|
|
1206
|
+
let id = parseInt(obj.id, 10);
|
|
1207
|
+
if (isNaN(id)) return Promise.reject(new EquipmentNotFoundError(`Invalid group id: ${obj.id}`, 'LightGroup'));
|
|
1208
|
+
if (!sys.board.equipmentIds.circuitGroups.isInRange(id)) return;
|
|
1209
|
+
if (typeof obj.id !== 'undefined') {
|
|
1210
|
+
let group = sys.lightGroups.getItemById(id, false);
|
|
1211
|
+
let sgroup = state.lightGroups.getItemById(id, false);
|
|
1212
|
+
sys.lightGroups.removeItemById(id);
|
|
1213
|
+
state.lightGroups.removeItemById(id);
|
|
1214
|
+
group.isActive = false;
|
|
1215
|
+
sgroup.isOn = false;
|
|
1216
|
+
sgroup.isActive = false;
|
|
1217
|
+
sgroup.emitEquipmentChange();
|
|
1218
|
+
return new Promise<LightGroup>((resolve, reject) => { resolve(group); });
|
|
1219
|
+
}
|
|
1220
|
+
else
|
|
1221
|
+
return Promise.reject(new InvalidEquipmentIdError('Group id has not been defined', id, 'LightGroup'));
|
|
1222
|
+
}
|
|
1223
|
+
public async deleteCircuitAsync(data: any): Promise<ICircuit> {
|
|
1224
|
+
let id = parseInt(data.id, 10);
|
|
1225
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid circuit id: ${data.id}`, data.id, 'Circuit'));
|
|
1226
|
+
if (!sys.board.equipmentIds.circuits.isInRange(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid circuit id: ${data.id}`, data.id, 'Circuit'));
|
|
1227
|
+
let circuit = sys.circuits.getInterfaceById(id);
|
|
1228
|
+
let cstate = state.circuits.getInterfaceById(id);
|
|
1229
|
+
if (circuit instanceof Circuit) {
|
|
1230
|
+
sys.circuits.removeItemById(circuit.id);
|
|
1231
|
+
state.circuits.removeItemById(circuit.id);
|
|
1232
|
+
cstate.isActive = circuit.isActive = false;
|
|
1233
|
+
}
|
|
1234
|
+
if (circuit instanceof Feature) {
|
|
1235
|
+
sys.features.removeItemById(circuit.id);
|
|
1236
|
+
state.features.removeItemById(circuit.id);
|
|
1237
|
+
cstate.isActive = circuit.isActive = false;
|
|
1238
|
+
}
|
|
1239
|
+
cstate.emitEquipmentChange();
|
|
1240
|
+
return new Promise<ICircuit>((resolve, reject) => { resolve(circuit); });
|
|
1241
|
+
}
|
|
1242
|
+
public deleteCircuit(data: any) {
|
|
1243
|
+
if (typeof data.id !== 'undefined') {
|
|
1244
|
+
let circuit = sys.circuits.getInterfaceById(data.id);
|
|
1245
|
+
if (circuit instanceof Circuit) {
|
|
1246
|
+
sys.circuits.removeItemById(circuit.id);
|
|
1247
|
+
state.circuits.removeItemById(circuit.id);
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
if (circuit instanceof Feature) {
|
|
1251
|
+
sys.features.removeItemById(data.id);
|
|
1252
|
+
state.features.removeItemById(data.id);
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
public getNameById(id: number) {
|
|
1258
|
+
if (id < 200)
|
|
1259
|
+
return sys.board.valueMaps.circuitNames.transform(id).desc;
|
|
1260
|
+
else
|
|
1261
|
+
return sys.customNames.getItemById(id - 200).name;
|
|
1262
|
+
}
|
|
1263
|
+
public async setLightGroupThemeAsync(id: number, theme: number): Promise<ICircuitState> {
|
|
1264
|
+
const grp = sys.lightGroups.getItemById(id);
|
|
1265
|
+
const sgrp = state.lightGroups.getItemById(id);
|
|
1266
|
+
//grp.lightingTheme = sgrp.lightingTheme = theme;
|
|
1267
|
+
let thm = sys.board.valueMaps.lightThemes.transform(theme);
|
|
1268
|
+
sgrp.action = sys.board.valueMaps.circuitActions.getValue('lighttheme');
|
|
1269
|
+
|
|
1270
|
+
try {
|
|
1271
|
+
// Go through and set the theme for all lights in the group.
|
|
1272
|
+
for (let i = 0; i < grp.circuits.length; i++) {
|
|
1273
|
+
let c = grp.circuits.getItemByIndex(i);
|
|
1274
|
+
//let cstate = state.circuits.getItemById(c.circuit);
|
|
1275
|
+
await sys.board.circuits.setLightThemeAsync(c.circuit, theme);
|
|
1276
|
+
await sys.board.circuits.setCircuitStateAsync(c.circuit, false);
|
|
1277
|
+
}
|
|
1278
|
+
await setTimeout(5000);
|
|
1279
|
+
// Turn the circuits all back on again.
|
|
1280
|
+
for (let i = 0; i < grp.circuits.length; i++) {
|
|
1281
|
+
let c = grp.circuits.getItemByIndex(i);
|
|
1282
|
+
//let cstate = state.circuits.getItemById(c.circuit);
|
|
1283
|
+
await sys.board.circuits.setCircuitStateAsync(c.circuit, true);
|
|
1284
|
+
}
|
|
1285
|
+
sgrp.lightingTheme = theme;
|
|
1286
|
+
return sgrp;
|
|
1287
|
+
}
|
|
1288
|
+
catch (err) { return Promise.reject(err); }
|
|
1289
|
+
finally {
|
|
1290
|
+
sgrp.action = 0;
|
|
1291
|
+
sgrp.emitEquipmentChange();
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
public async setLightGroupAttribsAsync(group: LightGroup): Promise<LightGroup> {
|
|
1295
|
+
let grp = sys.lightGroups.getItemById(group.id);
|
|
1296
|
+
try {
|
|
1297
|
+
grp.circuits.clear();
|
|
1298
|
+
for (let i = 0; i < group.circuits.length; i++) {
|
|
1299
|
+
let circuit = grp.circuits.getItemByIndex(i);
|
|
1300
|
+
grp.circuits.add({ id: i, circuit: circuit.circuit, color: circuit.color, position: i, swimDelay: circuit.swimDelay });
|
|
1301
|
+
}
|
|
1302
|
+
let sgrp = state.lightGroups.getItemById(group.id);
|
|
1303
|
+
sgrp.hasChanged = true; // Say we are dirty but we really are pure as the driven snow.
|
|
1304
|
+
return Promise.resolve(grp);
|
|
1305
|
+
}
|
|
1306
|
+
catch (err) { return Promise.reject(err); }
|
|
1307
|
+
}
|
|
1308
|
+
//public async runLightCommandAsync(id: number, command: string): Promise<ICircuitState> {
|
|
1309
|
+
// let circ = sys.circuits.getItemById(id);
|
|
1310
|
+
// try {
|
|
1311
|
+
// let type = sys.board.valueMaps.circuitFunctions.transform(circ.type);
|
|
1312
|
+
// let cmd = sys.board.valueMaps.lightCommands.findItem(command);
|
|
1313
|
+
// if (typeof cmd === 'undefined') return Promise.reject(new InvalidOperationError(`Light command ${command} does not exist`, 'runLightCommandAsync'));
|
|
1314
|
+
// if (typeof cmd.sequence !== 'undefined' && circ.master === 1) {
|
|
1315
|
+
// await sys.board.circuits.setCircuitStateAsync(id, true);
|
|
1316
|
+
// await ncp.circuits.sendOnOffSequenceAsync(id, cmd.sequence);
|
|
1317
|
+
// }
|
|
1318
|
+
// return state.circuits.getItemById(id);
|
|
1319
|
+
// }
|
|
1320
|
+
// catch (err) { return Promise.reject(`Error runLightCommandAsync ${err.message}`); }
|
|
1321
|
+
//}
|
|
1322
|
+
public async sequenceLightGroupAsync(id: number, operation: string): Promise<LightGroupState> {
|
|
1323
|
+
let sgroup = state.lightGroups.getItemById(id);
|
|
1324
|
+
if (state.mode !== 0) return sgroup;
|
|
1325
|
+
let grp = sys.lightGroups.getItemById(id);
|
|
1326
|
+
let nop = sys.board.valueMaps.circuitActions.getValue(operation);
|
|
1327
|
+
try {
|
|
1328
|
+
switch (operation) {
|
|
1329
|
+
case 'colorsync':
|
|
1330
|
+
sgroup.action = nop;
|
|
1331
|
+
sgroup.emitEquipmentChange();
|
|
1332
|
+
for (let i = 0; i < grp.circuits.length; i++) {
|
|
1333
|
+
let c = grp.circuits.getItemByIndex(i);
|
|
1334
|
+
await sys.board.circuits.setCircuitStateAsync(c.circuit, false);
|
|
1335
|
+
}
|
|
1336
|
+
await setTimeout(10000);
|
|
1337
|
+
// Turn the circuits all back on again.
|
|
1338
|
+
for (let i = 0; i < grp.circuits.length; i++) {
|
|
1339
|
+
let c = grp.circuits.getItemByIndex(i);
|
|
1340
|
+
await sys.board.circuits.setCircuitStateAsync(c.circuit, true);
|
|
1341
|
+
}
|
|
1342
|
+
break;
|
|
1343
|
+
case 'colorset':
|
|
1344
|
+
sgroup.action = nop;
|
|
1345
|
+
sgroup.emitEquipmentChange();
|
|
1346
|
+
await setTimeout(5000);
|
|
1347
|
+
break;
|
|
1348
|
+
case 'colorswim':
|
|
1349
|
+
sgroup.action = nop;
|
|
1350
|
+
sgroup.emitEquipmentChange();
|
|
1351
|
+
await setTimeout(5000);
|
|
1352
|
+
break;
|
|
1353
|
+
}
|
|
1354
|
+
return sgroup;
|
|
1355
|
+
} catch (err) { return Promise.reject(err); }
|
|
1356
|
+
finally { sgroup.action = 0; sgroup.emitEquipmentChange(); }
|
|
1357
|
+
}
|
|
1358
|
+
public async setCircuitGroupStateAsync(id: number, val: boolean): Promise<ICircuitGroupState> {
|
|
1359
|
+
let grp = sys.circuitGroups.getItemById(id, false, { isActive: false });
|
|
1360
|
+
if (grp.dataName !== 'circuitGroupConfig') return await sys.board.circuits.setLightGroupStateAsync(id, val);
|
|
1361
|
+
let gstate = state.circuitGroups.getItemById(grp.id, grp.isActive !== false);
|
|
1362
|
+
if (state.mode !== 0) return gstate;
|
|
1363
|
+
let circuits = grp.circuits.toArray();
|
|
1364
|
+
sys.board.circuits.setEndTime(sys.circuits.getInterfaceById(gstate.id), gstate, val);
|
|
1365
|
+
gstate.isOn = val;
|
|
1366
|
+
let arr = [];
|
|
1367
|
+
for (let i = 0; i < circuits.length; i++) {
|
|
1368
|
+
let circuit:CircuitGroupCircuit = circuits[i];
|
|
1369
|
+
// The desiredState will be as follows.
|
|
1370
|
+
// 1 = on, 2 = off, 3 = ignore.
|
|
1371
|
+
let cval = true;
|
|
1372
|
+
if (circuit.desiredState === 1) cval = val ? true : false;
|
|
1373
|
+
else if (circuit.desiredState === 2) cval = val ? false : true;
|
|
1374
|
+
else if (circuit.desiredState === 3) continue;
|
|
1375
|
+
else if (circuit.desiredState === 4){
|
|
1376
|
+
// on/ignore
|
|
1377
|
+
if (val) cval = true;
|
|
1378
|
+
else continue;
|
|
1379
|
+
}
|
|
1380
|
+
else if (circuit.desiredState === 5){
|
|
1381
|
+
// off/ignore
|
|
1382
|
+
if (val) cval = false;
|
|
1383
|
+
else continue;
|
|
1384
|
+
}
|
|
1385
|
+
await sys.board.circuits.setCircuitStateAsync(circuit.circuit, cval);
|
|
1386
|
+
//arr.push(sys.board.circuits.setCircuitStateAsync(circuit.circuit, cval));
|
|
1387
|
+
}
|
|
1388
|
+
return state.circuitGroups.getItemById(grp.id, grp.isActive !== false);
|
|
1389
|
+
//return new Promise<ICircuitGroupState>(async (resolve, reject) => {
|
|
1390
|
+
// await Promise.all(arr).catch((err) => { reject(err) });
|
|
1391
|
+
// resolve(gstate);
|
|
1392
|
+
//});
|
|
1393
|
+
}
|
|
1394
|
+
public async setLightGroupStateAsync(id: number, val: boolean): Promise<ICircuitGroupState> {
|
|
1395
|
+
let grp = sys.circuitGroups.getItemById(id, false, { isActive: false });
|
|
1396
|
+
if (grp.dataName === 'circuitGroupConfig') return await sys.board.circuits.setCircuitGroupStateAsync(id, val);
|
|
1397
|
+
let gstate = state.lightGroups.getItemById(grp.id, grp.isActive !== false);
|
|
1398
|
+
if (state.mode !== 0) return gstate;
|
|
1399
|
+
let circuits = grp.circuits.toArray();
|
|
1400
|
+
sys.board.circuits.setEndTime(grp, gstate, val);
|
|
1401
|
+
gstate.isOn = val;
|
|
1402
|
+
let arr = [];
|
|
1403
|
+
for (let i = 0; i < circuits.length; i++) {
|
|
1404
|
+
let circuit = circuits[i];
|
|
1405
|
+
arr.push(sys.board.circuits.setCircuitStateAsync(circuit.circuit, val));
|
|
1406
|
+
}
|
|
1407
|
+
return new Promise<ICircuitGroupState>(async (resolve, reject) => {
|
|
1408
|
+
await Promise.all(arr).catch((err) => { reject(err) });
|
|
1409
|
+
resolve(gstate);
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
export class NixieFeatureCommands extends FeatureCommands {
|
|
1414
|
+
public async setFeatureAsync(obj: any): Promise<Feature> {
|
|
1415
|
+
let id = parseInt(obj.id, 10);
|
|
1416
|
+
if (id <= 0 || isNaN(id)) {
|
|
1417
|
+
id = sys.features.getNextEquipmentId(sys.board.equipmentIds.features);
|
|
1418
|
+
}
|
|
1419
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid feature id: ${obj.id}`, obj.id, 'Feature'));
|
|
1420
|
+
if (!sys.board.equipmentIds.features.isInRange(id)) return Promise.reject(new InvalidEquipmentIdError(`Feature id out of range: ${id}: ${sys.board.equipmentIds.features.start} to ${sys.board.equipmentIds.features.end}`, obj.id, 'Feature'));
|
|
1421
|
+
let feature = sys.features.getItemById(id, true);
|
|
1422
|
+
let sfeature = state.features.getItemById(id, true);
|
|
1423
|
+
feature.isActive = true;
|
|
1424
|
+
sfeature.isOn = false;
|
|
1425
|
+
if (obj.nameId) {
|
|
1426
|
+
feature.nameId = sfeature.nameId = obj.nameId;
|
|
1427
|
+
feature.name = sfeature.name = sys.board.valueMaps.circuitNames.get(obj.nameId);
|
|
1428
|
+
}
|
|
1429
|
+
else if (obj.name) feature.name = sfeature.name = obj.name;
|
|
1430
|
+
else if (!feature.name && !obj.name) feature.name = sfeature.name = `feature${obj.id}`;
|
|
1431
|
+
if (typeof obj.type !== 'undefined') feature.type = sfeature.type = parseInt(obj.type, 10);
|
|
1432
|
+
else if (!feature.type && typeof obj.type !== 'undefined') feature.type = sfeature.type = 0;
|
|
1433
|
+
if (typeof obj.freeze !== 'undefined') feature.freeze = utils.makeBool(obj.freeze);
|
|
1434
|
+
if (typeof obj.showInFeatures !== 'undefined') feature.showInFeatures = sfeature.showInFeatures = utils.makeBool(obj.showInFeatures);
|
|
1435
|
+
if (typeof obj.dontStop !== 'undefined' && utils.makeBool(obj.dontStop) === true) obj.eggTimer = 1440;
|
|
1436
|
+
if (typeof obj.eggTimer !== 'undefined') feature.eggTimer = parseInt(obj.eggTimer, 10);
|
|
1437
|
+
feature.dontStop = feature.eggTimer === 1440;
|
|
1438
|
+
// update end time in case feature is changed while circuit is on
|
|
1439
|
+
sys.board.circuits.setEndTime(feature, sfeature, sfeature.isOn, true);
|
|
1440
|
+
return new Promise<Feature>((resolve, reject) => { resolve(feature); });
|
|
1441
|
+
}
|
|
1442
|
+
public async deleteFeatureAsync(obj: any): Promise<Feature> {
|
|
1443
|
+
let id = parseInt(obj.id, 10);
|
|
1444
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid feature id: ${obj.id}`, obj.id, 'Feature'));
|
|
1445
|
+
if (!sys.board.equipmentIds.features.isInRange(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid feature id: ${obj.id}`, obj.id, 'Feature'));
|
|
1446
|
+
if (typeof obj.id !== 'undefined') {
|
|
1447
|
+
let feature = sys.features.getItemById(id, false);
|
|
1448
|
+
let sfeature = state.features.getItemById(id, false);
|
|
1449
|
+
sys.features.removeItemById(id);
|
|
1450
|
+
state.features.removeItemById(id);
|
|
1451
|
+
feature.isActive = false;
|
|
1452
|
+
sfeature.isOn = false;
|
|
1453
|
+
sfeature.showInFeatures = false;
|
|
1454
|
+
sfeature.isActive = false;
|
|
1455
|
+
sfeature.emitEquipmentChange();
|
|
1456
|
+
return new Promise<Feature>((resolve, reject) => { resolve(feature); });
|
|
1457
|
+
}
|
|
1458
|
+
else
|
|
1459
|
+
Promise.reject(new InvalidEquipmentIdError('Feature id has not been defined', undefined, 'Feature'));
|
|
1460
|
+
}
|
|
1461
|
+
public async setFeatureStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<ICircuitState> {
|
|
1462
|
+
try {
|
|
1463
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid feature id: ${id}`, id, 'Feature'));
|
|
1464
|
+
if (!sys.board.equipmentIds.features.isInRange(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid feature id: ${id}`, id, 'Feature'));
|
|
1465
|
+
let feature = sys.features.getItemById(id);
|
|
1466
|
+
let fstate = state.features.getItemById(feature.id, feature.isActive !== false);
|
|
1467
|
+
feature.master = 1;
|
|
1468
|
+
if (state.mode !== 0) return fstate;
|
|
1469
|
+
let ftype = sys.board.valueMaps.featureFunctions.getName(feature.type);
|
|
1470
|
+
if(val && !fstate.isOn) sys.board.circuits.setEndTime(feature, fstate, val);
|
|
1471
|
+
switch (ftype) {
|
|
1472
|
+
case 'spadrain':
|
|
1473
|
+
this.setDrainFeatureStateAsync(id, val, ignoreDelays);
|
|
1474
|
+
break;
|
|
1475
|
+
case 'spillway':
|
|
1476
|
+
this.setSpillwayFeatureStateAsync(id, val, ignoreDelays);
|
|
1477
|
+
break;
|
|
1478
|
+
default:
|
|
1479
|
+
fstate.isOn = val;
|
|
1480
|
+
break;
|
|
1481
|
+
}
|
|
1482
|
+
sys.board.valves.syncValveStates();
|
|
1483
|
+
ncp.pumps.syncPumpStates();
|
|
1484
|
+
if (!val){
|
|
1485
|
+
if (fstate.manualPriorityActive) delayMgr.cancelManualPriorityDelay(fstate.id);
|
|
1486
|
+
fstate.manualPriorityActive = false; // if the delay was previously cancelled, still need to turn this off
|
|
1487
|
+
}
|
|
1488
|
+
state.emitEquipmentChanges();
|
|
1489
|
+
return fstate;
|
|
1490
|
+
} catch (err) { return Promise.reject(new Error(`Error setting feature state ${err.message}`)); }
|
|
1491
|
+
}
|
|
1492
|
+
protected async setSpillwayFeatureStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<FeatureState> {
|
|
1493
|
+
try {
|
|
1494
|
+
let cstate = state.features.getItemById(id);
|
|
1495
|
+
if (cstate.isOn !== val) {
|
|
1496
|
+
if (sys.equipment.shared === true) {
|
|
1497
|
+
let spastate = state.temps.bodies.getItemById(2);
|
|
1498
|
+
if (val) {
|
|
1499
|
+
if (spastate.isOn || spastate.startDelay) {
|
|
1500
|
+
logger.warn(`Cannot turn ${cstate.name} on because ${spastate.name} is on`);
|
|
1501
|
+
return cstate;
|
|
1502
|
+
}
|
|
1503
|
+
// If there are any drain circuits or features that are currently engaged we need to turn them off.
|
|
1504
|
+
await sys.board.circuits.turnOffDrainCircuits(ignoreDelays);
|
|
1505
|
+
if (!ignoreDelays && sys.general.options.pumpDelay && sys.general.options.valveDelayTime > 0) sys.board.pumps.setPumpValveDelays([id, 6]);
|
|
1506
|
+
}
|
|
1507
|
+
else if (!val) {
|
|
1508
|
+
let arrIds = sys.board.valves.getBodyValveCircuitIds(true);
|
|
1509
|
+
if (arrIds.length > 1) {
|
|
1510
|
+
if (!ignoreDelays && sys.general.options.pumpDelay && sys.general.options.valveDelayTime > 0) sys.board.pumps.setPumpValveDelays([id, 6]);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
logger.verbose(`Turning ${val ? 'on' : 'off'} a spillway feature ${cstate.name}`);
|
|
1515
|
+
cstate.isOn = val;
|
|
1516
|
+
}
|
|
1517
|
+
return cstate;
|
|
1518
|
+
} catch (err) { logger.error(`Nixie: Error setSpillwayFeatureStateAsync ${err.message}`); return Promise.reject(new BoardProcessError(`Nixie: Error setSpillwayFeatureStateAsync ${err.message}`, 'setSpillwayFeatureStateAsync')); }
|
|
1519
|
+
}
|
|
1520
|
+
protected async setDrainFeatureStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<FeatureState> {
|
|
1521
|
+
try {
|
|
1522
|
+
// Drain circuits can be very bad. This is because they can be turned on then never turned off
|
|
1523
|
+
// we may want to create some limits are to how long they can be on or even force them off
|
|
1524
|
+
// if for instance the spa is not on.
|
|
1525
|
+
// RULES FOR DRAIN CIRCUITS:
|
|
1526
|
+
// 1. All spillway circuits must be off.
|
|
1527
|
+
let cstate = state.features.getItemById(id);
|
|
1528
|
+
if (cstate.isOn !== val) {
|
|
1529
|
+
if (sys.equipment.shared === true) {
|
|
1530
|
+
if (val) {
|
|
1531
|
+
// First we need to check to see if the pool is on.
|
|
1532
|
+
let poolstate = state.temps.bodies.getItemById(1);
|
|
1533
|
+
let spastate = state.temps.bodies.getItemById(2);
|
|
1534
|
+
if ((spastate.isOn || spastate.startDelay || poolstate.isOn || poolstate.startDelay) && val) {
|
|
1535
|
+
logger.warn(`Cannot turn ${cstate.name} on because a body circuit is on`);
|
|
1536
|
+
return cstate;
|
|
1537
|
+
}
|
|
1538
|
+
// If there are any spillway circuits or features that are currently engaged we need to turn them off.
|
|
1539
|
+
await sys.board.circuits.turnOffSpillwayCircuits(true);
|
|
1540
|
+
// If there are any cleaner circuits on for the main body turn them off.
|
|
1541
|
+
await sys.board.circuits.turnOffCleanerCircuits(state.temps.bodies.getItemById(1), true);
|
|
1542
|
+
if (!ignoreDelays && sys.general.options.pumpDelay && sys.general.options.valveDelayTime > 0) sys.board.pumps.setPumpValveDelays([id, 1, 6]);
|
|
1543
|
+
}
|
|
1544
|
+
else if (!val) {
|
|
1545
|
+
if (!ignoreDelays && sys.general.options.pumpDelay && sys.general.options.valveDelayTime > 0) sys.board.pumps.setPumpValveDelays([id, 1, 6]);
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
logger.verbose(`Turning ${val ? 'on' : 'off'} a spa drain feature ${cstate.name}`);
|
|
1549
|
+
cstate.isOn = val;
|
|
1550
|
+
}
|
|
1551
|
+
return cstate;
|
|
1552
|
+
} catch (err) { logger.error(`Nixie: Error setDrainFeatureStateAsync ${err.message}`); return Promise.reject(new BoardProcessError(`Nixie: Error setDrainFeatureStateAsync ${err.message}`, 'setDrainFeatureStateAsync')); }
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
public async toggleFeatureStateAsync(id: number): Promise<ICircuitState> {
|
|
1556
|
+
let feat = state.features.getItemById(id);
|
|
1557
|
+
return this.setFeatureStateAsync(id, !(feat.isOn || false));
|
|
1558
|
+
}
|
|
1559
|
+
public syncGroupStates() {
|
|
1560
|
+
// The way this should work is that when all of the states are met
|
|
1561
|
+
// the group should be on. Otherwise it should be off. That means that if
|
|
1562
|
+
// you turned on all the group circuits that should be on individually then
|
|
1563
|
+
// the group should be on.
|
|
1564
|
+
for (let i = 0; i < sys.circuitGroups.length; i++) {
|
|
1565
|
+
let grp: CircuitGroup = sys.circuitGroups.getItemByIndex(i);
|
|
1566
|
+
let circuits = grp.circuits.toArray();
|
|
1567
|
+
if (grp.isActive) {
|
|
1568
|
+
let bIsOn = true;
|
|
1569
|
+
// Iterate the circuits and break out should we find a condition
|
|
1570
|
+
// where the group should be off.
|
|
1571
|
+
for (let j = 0; j < circuits.length && bIsOn === true; j++) {
|
|
1572
|
+
let circuit: CircuitGroupCircuit = grp.circuits.getItemByIndex(j);
|
|
1573
|
+
let cstate = state.circuits.getInterfaceById(circuit.circuit);
|
|
1574
|
+
// RSG: desiredState for Nixie is 1=on, 2=off, 3=ignore
|
|
1575
|
+
if (circuit.desiredState === 1 || circuit.desiredState === 4) {
|
|
1576
|
+
// The circuit should be on if the value is 1.
|
|
1577
|
+
// If we are on 'ignore' we should still only treat the circuit as
|
|
1578
|
+
// desiredstate = 1.
|
|
1579
|
+
if (!utils.makeBool(cstate.isOn)) bIsOn = false;
|
|
1580
|
+
}
|
|
1581
|
+
else if (circuit.desiredState === 2 || circuit.desiredState === 5) { // The circuit should be off.
|
|
1582
|
+
if (utils.makeBool(cstate.isOn)) bIsOn = false;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
let sgrp = state.circuitGroups.getItemById(grp.id);
|
|
1586
|
+
sgrp.isOn = bIsOn;
|
|
1587
|
+
if (sgrp.isOn && typeof sgrp.endTime === 'undefined') sys.board.circuits.setEndTime(grp, sgrp, sgrp.isOn, true);
|
|
1588
|
+
if (!sgrp.isOn && sgrp.manualPriorityActive){
|
|
1589
|
+
delayMgr.cancelManualPriorityDelays();
|
|
1590
|
+
sgrp.manualPriorityActive = false; // if the delay was previously cancelled, still need to turn this off
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
sys.board.valves.syncValveStates();
|
|
1594
|
+
}
|
|
1595
|
+
// I am guessing that there will only be one here but iterate
|
|
1596
|
+
// just in case we expand.
|
|
1597
|
+
for (let i = 0; i < sys.lightGroups.length; i++) {
|
|
1598
|
+
let grp: LightGroup = sys.lightGroups.getItemByIndex(i);
|
|
1599
|
+
let circuits = grp.circuits.toArray();
|
|
1600
|
+
if (grp.isActive) {
|
|
1601
|
+
let bIsOn = true;
|
|
1602
|
+
for (let j = 0; j < circuits.length && bIsOn === true; j++) {
|
|
1603
|
+
let circuit: LightGroupCircuit = grp.circuits.getItemByIndex(j);
|
|
1604
|
+
let cstate = state.circuits.getInterfaceById(circuit.circuit);
|
|
1605
|
+
if (!utils.makeBool(cstate.isOn)) bIsOn = false;
|
|
1606
|
+
}
|
|
1607
|
+
let sgrp = state.lightGroups.getItemById(grp.id);
|
|
1608
|
+
sgrp.isOn = bIsOn;
|
|
1609
|
+
if (sgrp.isOn && typeof sgrp.endTime === 'undefined') sys.board.circuits.setEndTime(grp, sgrp, sgrp.isOn, true);
|
|
1610
|
+
if (!sgrp.isOn && sgrp.manualPriorityActive){
|
|
1611
|
+
delayMgr.cancelManualPriorityDelay(grp.id);
|
|
1612
|
+
sgrp.manualPriorityActive = false; // if the delay was previously cancelled, still need to turn this off
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
sys.board.valves.syncValveStates();
|
|
1617
|
+
}
|
|
1618
|
+
state.emitEquipmentChanges();
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
export class NixiePumpCommands extends PumpCommands {
|
|
1622
|
+
public async setPumpValveDelays(circuitIds: number[], delay?: number) {
|
|
1623
|
+
try {
|
|
1624
|
+
logger.info(`Setting pump valve delays: ${JSON.stringify(circuitIds)}`);
|
|
1625
|
+
// Alright now we have to delay the pumps associated with the circuit. So lets iterate all our
|
|
1626
|
+
// pump states and see where we land.
|
|
1627
|
+
for (let i = 0; i < sys.pumps.length; i++) {
|
|
1628
|
+
let pump = sys.pumps.getItemByIndex(i);
|
|
1629
|
+
let pstate = state.pumps.getItemById(pump.id);
|
|
1630
|
+
let pt = sys.board.valueMaps.pumpTypes.get(pump.type);
|
|
1631
|
+
|
|
1632
|
+
// Old - [1, { name: 'ss', desc: 'Single Speed', maxCircuits: 0, hasAddress: false, hasBody: true, maxRelays: 1 }],
|
|
1633
|
+
// New 07/22 - [1, { name: 'ss', desc: 'Single Speed', maxCircuits: 8, hasAddress: false, hasBody: false, maxRelays: 1, relays: [{ id: 1, name: 'Pump On/Off' }]}],
|
|
1634
|
+
// [2, { name: 'ds', desc: 'Two Speed', maxCircuits: 8, hasAddress: false, hasBody: false, maxRelays: 2 }],
|
|
1635
|
+
// [3, { name: 'vs', desc: 'Intelliflo VS', maxPrimingTime: 6, minSpeed: 450, maxSpeed: 3450, maxCircuits: 8, hasAddress: true }],
|
|
1636
|
+
// [4, { name: 'vsf', desc: 'Intelliflo VSF', minSpeed: 450, maxSpeed: 3450, minFlow: 15, maxFlow: 130, maxCircuits: 8, hasAddress: true }],
|
|
1637
|
+
// [5, { name: 'vf', desc: 'Intelliflo VF', minFlow: 15, maxFlow: 130, maxCircuits: 8, hasAddress: true }],
|
|
1638
|
+
// [100, { name: 'sf', desc: 'SuperFlo VS', hasAddress: false, maxCircuits: 8, maxRelays: 4, equipmentMaster: 1 }]
|
|
1639
|
+
switch (pt.name) {
|
|
1640
|
+
case 'ss':{
|
|
1641
|
+
// rsg - ss now has circuit assignments. will check but still leave existing code
|
|
1642
|
+
if (pt.maxCircuits === 0 || typeof pump.body !== 'undefined'){
|
|
1643
|
+
// If a single speed pump is designated it will be the filter pump but we need to map any settings
|
|
1644
|
+
// to bodies.
|
|
1645
|
+
console.log(`Body: ${pump.body} Pump: ${pump.name} Pool: ${circuitIds.includes(6)} `);
|
|
1646
|
+
if ((pump.body === 255 && (circuitIds.includes(6) || circuitIds.includes(1))) ||
|
|
1647
|
+
(pump.body === 0 && circuitIds.includes(6)) ||
|
|
1648
|
+
(pump.body === 101 && circuitIds.includes(1))) {
|
|
1649
|
+
delayMgr.setPumpValveDelay(pstate);
|
|
1650
|
+
}
|
|
1651
|
+
break;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
default:
|
|
1655
|
+
if (pt.maxCircuits > 0) {
|
|
1656
|
+
for (let j = 0; j < pump.circuits.length; j++) {
|
|
1657
|
+
let circ = pump.circuits.getItemByIndex(j);
|
|
1658
|
+
if (circuitIds.includes(circ.circuit)) {
|
|
1659
|
+
delayMgr.setPumpValveDelay(pstate);
|
|
1660
|
+
break;
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
break;
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
} catch (err) { }
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
export class NixieValveCommands extends ValveCommands {
|
|
1671
|
+
public async setValveAsync(obj: any): Promise<Valve> {
|
|
1672
|
+
try {
|
|
1673
|
+
let id = typeof obj.id !== 'undefined' ? parseInt(obj.id, 10) : -1;
|
|
1674
|
+
obj.master = 1;
|
|
1675
|
+
if (isNaN(id) || id <= 0) id = Math.max(sys.valves.getMaxId(false, 49) + 1, 50);
|
|
1676
|
+
|
|
1677
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Nixie: Valve Id has not been defined ${id}`, obj.id, 'Valve'));
|
|
1678
|
+
// Check the Nixie Control Panel to make sure the valve exist there. If it needs to be added then we should add it.
|
|
1679
|
+
let valve = sys.valves.getItemById(id, true);
|
|
1680
|
+
// Set all the valve properies.
|
|
1681
|
+
let vstate = state.valves.getItemById(valve.id, true);
|
|
1682
|
+
valve.isActive = true;
|
|
1683
|
+
valve.circuit = typeof obj.circuit !== 'undefined' ? obj.circuit : valve.circuit;
|
|
1684
|
+
valve.name = typeof obj.name !== 'undefined' ? obj.name : valve.name;
|
|
1685
|
+
valve.connectionId = typeof obj.connectionId ? obj.connectionId : valve.connectionId;
|
|
1686
|
+
valve.deviceBinding = typeof obj.deviceBinding !== 'undefined' ? obj.deviceBinding : valve.deviceBinding;
|
|
1687
|
+
valve.pinId = typeof obj.pinId !== 'undefined' ? obj.pinId : valve.pinId;
|
|
1688
|
+
await ncp.valves.setValveAsync(valve, obj);
|
|
1689
|
+
await sys.board.syncEquipmentItems();
|
|
1690
|
+
return valve;
|
|
1691
|
+
} catch (err) { logger.error(`Nixie: Error setting valve definition. ${err.message}`); return Promise.reject(err); }
|
|
1692
|
+
}
|
|
1693
|
+
public async deleteValveAsync(obj: any): Promise<Valve> {
|
|
1694
|
+
try {
|
|
1695
|
+
let id = parseInt(obj.id, 10);
|
|
1696
|
+
// The following code will make sure we do not encroach on any valves defined by the OCP.
|
|
1697
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Valve Id has not been defined', obj.id, 'Valve'));
|
|
1698
|
+
let valve = sys.valves.getItemById(id, false);
|
|
1699
|
+
let vstate = state.valves.getItemById(id);
|
|
1700
|
+
valve.isActive = false;
|
|
1701
|
+
vstate.hasChanged = true;
|
|
1702
|
+
vstate.emitEquipmentChange();
|
|
1703
|
+
sys.valves.removeItemById(id);
|
|
1704
|
+
state.valves.removeItemById(id);
|
|
1705
|
+
ncp.valves.removeById(id);
|
|
1706
|
+
return valve;
|
|
1707
|
+
} catch (err) { logger.error(`Nixie: Error removing valve from system ${obj.id}: ${err.message}`); return Promise.reject(new Error(`Nixie: Error removing valve from system ${ obj.id }: ${ err.message }`)); }
|
|
1708
|
+
}
|
|
1709
|
+
public async setValveStateAsync(valve: Valve, vstate: ValveState, isDiverted: boolean) {
|
|
1710
|
+
try {
|
|
1711
|
+
vstate.name = valve.name;
|
|
1712
|
+
await ncp.valves.setValveStateAsync(vstate, isDiverted);
|
|
1713
|
+
} catch (err) { logger.error(`Nixie: Error setting valve ${vstate.id}-${vstate.name} state to ${isDiverted}: ${err}`); return Promise.reject(err); }
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
export class NixieHeaterCommands extends HeaterCommands {
|
|
1717
|
+
public async setHeaterAsync(obj: any): Promise<Heater> {
|
|
1718
|
+
try {
|
|
1719
|
+
let id = typeof obj.id === 'undefined' || !obj.id ? -1 : parseInt(obj.id, 10);
|
|
1720
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Heater Id is not valid.', obj.id, 'Heater'));
|
|
1721
|
+
else if (id < 256 && id > 0) return Promise.reject(new InvalidEquipmentIdError('Nixie Heaters controlled by njspc must have an Id > 256.', obj.id, 'Heater'));
|
|
1722
|
+
let heater: Heater;
|
|
1723
|
+
if (id <= 0) {
|
|
1724
|
+
// We are adding a heater. In this case all heaters are virtual.
|
|
1725
|
+
let vheaters = sys.heaters.filter(h => h.master === 1);
|
|
1726
|
+
id = Math.max(vheaters.getMaxId() + 1 || 0, vheaters.length + 256);
|
|
1727
|
+
logger.info(`Adding a new heater with id ${id}`);
|
|
1728
|
+
}
|
|
1729
|
+
heater = sys.heaters.getItemById(id, true);
|
|
1730
|
+
if (typeof obj !== undefined) {
|
|
1731
|
+
for (var s in obj) {
|
|
1732
|
+
if (s === 'id') continue;
|
|
1733
|
+
heater[s] = obj[s];
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
let hstate = state.heaters.getItemById(id, true);
|
|
1737
|
+
//hstate.isVirtual = heater.isVirtual = true;
|
|
1738
|
+
hstate.name = heater.name;
|
|
1739
|
+
hstate.type = heater.type;
|
|
1740
|
+
heater.master = 1;
|
|
1741
|
+
await ncp.heaters.setHeaterAsync(heater, obj);
|
|
1742
|
+
await sys.board.heaters.updateHeaterServices();
|
|
1743
|
+
return heater;
|
|
1744
|
+
} catch (err) { return Promise.reject(new Error(`Error setting heater configuration: ${err}`)); }
|
|
1745
|
+
}
|
|
1746
|
+
public async deleteHeaterAsync(obj: any): Promise<Heater> {
|
|
1747
|
+
try {
|
|
1748
|
+
let id = parseInt(obj.id, 10);
|
|
1749
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Cannot delete. Heater Id is not valid.', obj.id, 'Heater'));
|
|
1750
|
+
let heater = sys.heaters.getItemById(id);
|
|
1751
|
+
heater.isActive = false;
|
|
1752
|
+
await ncp.heaters.deleteHeaterAsync(id);
|
|
1753
|
+
sys.heaters.removeItemById(id);
|
|
1754
|
+
state.heaters.removeItemById(id);
|
|
1755
|
+
sys.board.heaters.updateHeaterServices();
|
|
1756
|
+
return heater;
|
|
1757
|
+
} catch (err) { return Promise.reject(new BoardProcessError(err.message, 'deleteHeaterAsync')); }
|
|
1758
|
+
}
|
|
1759
|
+
public updateHeaterServices() {
|
|
1760
|
+
let htypes = sys.board.heaters.getInstalledHeaterTypes();
|
|
1761
|
+
let solarInstalled = htypes.solar > 0;
|
|
1762
|
+
let heatPumpInstalled = htypes.heatpump > 0;
|
|
1763
|
+
let gasHeaterInstalled = htypes.gas > 0;
|
|
1764
|
+
let ultratempInstalled = htypes.ultratemp > 0;
|
|
1765
|
+
let mastertempInstalled = htypes.mastertemp > 0;
|
|
1766
|
+
let hybridInstalled = htypes.hybrid > 0;
|
|
1767
|
+
// The heat mode options are
|
|
1768
|
+
// 1 = Off
|
|
1769
|
+
// 2 = Gas Heater
|
|
1770
|
+
// 3 = Solar Heater
|
|
1771
|
+
// 4 = Solar Preferred
|
|
1772
|
+
// 5 = UltraTemp Only
|
|
1773
|
+
// 6 = UltraTemp Preferred???? This might be 22
|
|
1774
|
+
// 9 = Heat Pump
|
|
1775
|
+
// 25 = Heat Pump Preferred
|
|
1776
|
+
// ?? = Hybrid
|
|
1777
|
+
|
|
1778
|
+
|
|
1779
|
+
// The heat source options are
|
|
1780
|
+
// 0 = No Change
|
|
1781
|
+
// 1 = Off
|
|
1782
|
+
// 2 = Gas Heater
|
|
1783
|
+
// 3 = Solar Heater
|
|
1784
|
+
// 4 = Solar Preferred
|
|
1785
|
+
// 5 = Heat Pump
|
|
1786
|
+
if (sys.heaters.length > 0) sys.board.valueMaps.heatSources = new byteValueMap([[1, { name: 'off', desc: 'Off' }]]);
|
|
1787
|
+
sys.board.valueMaps.heatModes = new byteValueMap([[1, { name: 'off', desc: 'Off' }]]);
|
|
1788
|
+
if (hybridInstalled) {
|
|
1789
|
+
sys.board.valueMaps.heatModes.merge([
|
|
1790
|
+
[7, { name: 'hybheat', desc: 'Gas Only' }],
|
|
1791
|
+
[8, { name: 'hybheatpump', desc: 'Heat Pump Only' }],
|
|
1792
|
+
[9, { name: 'hybhybrid', desc: 'Hybrid' }],
|
|
1793
|
+
[10, { name: 'hybdual', desc: 'Dual Heat' }]
|
|
1794
|
+
]);
|
|
1795
|
+
sys.board.valueMaps.heatSources.merge([
|
|
1796
|
+
[7, { name: 'hybheat', desc: 'Gas Only' }],
|
|
1797
|
+
[8, { name: 'hybheatpump', desc: 'Heat Pump Only' }],
|
|
1798
|
+
[9, { name: 'hybhybrid', desc: 'Hybrid' }],
|
|
1799
|
+
[10, { name: 'hybdual', desc: 'Dual Heat' }]
|
|
1800
|
+
]);
|
|
1801
|
+
// RKS: 08-24-22 The heat modes and sources for the hybrid heater are unique. Turns out that
|
|
1802
|
+
// these should be available if there is a gas heater ganged to the body as well.
|
|
1803
|
+
// types cannot be ignored since they are specific to the heater.
|
|
1804
|
+
//sys.board.valueMaps.heatModes.merge([
|
|
1805
|
+
// [9, { name: 'heatpump', desc: 'Heat Pump' }],
|
|
1806
|
+
// [2, { name: 'heater', desc: 'Heater' }],
|
|
1807
|
+
// [25, { name: 'heatpumppref', desc: 'Hybrid' }],
|
|
1808
|
+
// [26, { name: 'dual', desc: 'Dual Heat' }]
|
|
1809
|
+
//]);
|
|
1810
|
+
//sys.board.valueMaps.heatSources.merge([
|
|
1811
|
+
// [2, { name: 'heater', desc: 'Gas Heat' }],
|
|
1812
|
+
// [9, { name: 'heatpump', desc: 'Heat Pump' }],
|
|
1813
|
+
// [20, { name: 'heatpumppref', desc: 'Hybrid' }],
|
|
1814
|
+
// [21, { name: 'dual', desc: 'Dual Heat' }]
|
|
1815
|
+
//]);
|
|
1816
|
+
}
|
|
1817
|
+
if (gasHeaterInstalled) sys.board.valueMaps.heatSources.merge([[2, { name: 'heater', desc: 'Heater' }]]);
|
|
1818
|
+
if (mastertempInstalled) sys.board.valueMaps.heatSources.merge([[11, { name: 'mtheater', desc: 'MasterTemp' }]]);
|
|
1819
|
+
if (solarInstalled && (gasHeaterInstalled || heatPumpInstalled)) sys.board.valueMaps.heatSources.merge([[3, { name: 'solar', desc: 'Solar Only', hasCoolSetpoint: htypes.hasCoolSetpoint }], [4, { name: 'solarpref', desc: 'Solar Preferred', hasCoolSetpoint: htypes.hasCoolSetpoint }]]);
|
|
1820
|
+
else if (solarInstalled) sys.board.valueMaps.heatSources.merge([[3, { name: 'solar', desc: 'Solar', hasCoolsetpoint: htypes.hasCoolSetpoint }]]);
|
|
1821
|
+
if (heatPumpInstalled && (gasHeaterInstalled || solarInstalled)) sys.board.valueMaps.heatSources.merge([[9, { name: 'heatpump', desc: 'Heatpump Only' }], [25, { name: 'heatpumppref', desc: 'Heat Pump Pref' }]]);
|
|
1822
|
+
else if (heatPumpInstalled) sys.board.valueMaps.heatSources.merge([[9, { name: 'heatpump', desc: 'Heat Pump' }]]);
|
|
1823
|
+
if (ultratempInstalled && (gasHeaterInstalled || heatPumpInstalled)) sys.board.valueMaps.heatSources.merge([[5, { name: 'ultratemp', desc: 'UltraTemp Only', hasCoolSetpoint: htypes.hasCoolSetpoint }], [6, { name: 'ultratemppref', desc: 'UltraTemp Pref', hasCoolSetpoint: htypes.hasCoolSetpoint }]]);
|
|
1824
|
+
else if (ultratempInstalled) sys.board.valueMaps.heatSources.merge([[5, { name: 'ultratemp', desc: 'UltraTemp', hasCoolSetpoint: htypes.hasCoolSetpoint }]]);
|
|
1825
|
+
sys.board.valueMaps.heatSources.merge([[0, { name: 'nochange', desc: 'No Change' }]]);
|
|
1826
|
+
|
|
1827
|
+
|
|
1828
|
+
if (gasHeaterInstalled) sys.board.valueMaps.heatModes.merge([[2, { name: 'heater', desc: 'Heater' }]]);
|
|
1829
|
+
if (mastertempInstalled) sys.board.valueMaps.heatModes.merge([[11, { name: 'mtheater', desc: 'MasterTemp' }]]);
|
|
1830
|
+
if (solarInstalled && (gasHeaterInstalled || heatPumpInstalled || mastertempInstalled)) sys.board.valueMaps.heatModes.merge([[3, { name: 'solar', desc: 'Solar Only' }], [4, { name: 'solarpref', desc: 'Solar Preferred' }]]);
|
|
1831
|
+
else if (solarInstalled) sys.board.valueMaps.heatModes.merge([[3, { name: 'solar', desc: 'Solar' }]]);
|
|
1832
|
+
if (ultratempInstalled && (gasHeaterInstalled || heatPumpInstalled || mastertempInstalled)) sys.board.valueMaps.heatModes.merge([[5, { name: 'ultratemp', desc: 'UltraTemp Only' }], [6, { name: 'ultratemppref', desc: 'UltraTemp Pref' }]]);
|
|
1833
|
+
else if (ultratempInstalled) sys.board.valueMaps.heatModes.merge([[5, { name: 'ultratemp', desc: 'UltraTemp' }]]);
|
|
1834
|
+
if (heatPumpInstalled && (gasHeaterInstalled || solarInstalled || mastertempInstalled)) sys.board.valueMaps.heatModes.merge([[9, { name: 'heatpump', desc: 'Heatpump Only' }], [25, { name: 'heatpumppref', desc: 'Heat Pump Preferred' }]]);
|
|
1835
|
+
else if (heatPumpInstalled) sys.board.valueMaps.heatModes.merge([[9, { name: 'heatpump', desc: 'Heat Pump' }]]);
|
|
1836
|
+
// Now set the body data.
|
|
1837
|
+
for (let i = 0; i < sys.bodies.length; i++) {
|
|
1838
|
+
let body = sys.bodies.getItemByIndex(i);
|
|
1839
|
+
let btemp = state.temps.bodies.getItemById(body.id, body.isActive !== false);
|
|
1840
|
+
let opts = sys.board.heaters.getInstalledHeaterTypes(body.id);
|
|
1841
|
+
btemp.heaterOptions = opts;
|
|
1842
|
+
}
|
|
1843
|
+
this.setActiveTempSensors();
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
export class NixieChemControllerCommands extends ChemControllerCommands {
|
|
1847
|
+
protected async setIntelliChemAsync(data: any): Promise<ChemController> {
|
|
1848
|
+
try {
|
|
1849
|
+
// Nixie is always in control so let her do her thing.
|
|
1850
|
+
let chem = sys.chemControllers.getItemById(data.id, true);
|
|
1851
|
+
await ncp.chemControllers.setControllerAsync(chem, data);
|
|
1852
|
+
return chem;
|
|
1853
|
+
}
|
|
1854
|
+
catch (err) { return Promise.reject(err); }
|
|
1855
|
+
}
|
|
1856
|
+
public async deleteChemControllerAsync(data: any): Promise<ChemController> {
|
|
1857
|
+
try {
|
|
1858
|
+
let id = typeof data.id !== 'undefined' ? parseInt(data.id, 10) : -1;
|
|
1859
|
+
if (typeof id === 'undefined' || isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid Chem Controller Id`, id, 'chemController'));
|
|
1860
|
+
let chem = sys.chemControllers.getItemById(id);
|
|
1861
|
+
let schem = state.chemControllers.getItemById(chem.id);
|
|
1862
|
+
await ncp.chemControllers.removeById(chem.id);
|
|
1863
|
+
chem.isActive = schem.isActive = false;
|
|
1864
|
+
sys.chemControllers.removeItemById(chem.id);
|
|
1865
|
+
state.chemControllers.removeItemById(chem.id);
|
|
1866
|
+
schem.emitEquipmentChange();
|
|
1867
|
+
return chem;
|
|
1868
|
+
}
|
|
1869
|
+
catch (err) { return Promise.reject(err); }
|
|
1870
|
+
}
|
|
1871
|
+
}
|