nodejs-poolcontroller 7.7.0 → 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 +26 -35
- package/Changelog +22 -0
- package/README.md +7 -3
- 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 +13 -9
- package/config/VersionCheck.ts +6 -2
- package/controller/Constants.ts +58 -25
- package/controller/Equipment.ts +224 -41
- package/controller/Errors.ts +2 -1
- package/controller/Lockouts.ts +34 -2
- package/controller/State.ts +491 -48
- package/controller/boards/AquaLinkBoard.ts +6 -3
- package/controller/boards/BoardFactory.ts +5 -1
- package/controller/boards/EasyTouchBoard.ts +1971 -1751
- package/controller/boards/IntelliCenterBoard.ts +1311 -1688
- package/controller/boards/IntelliComBoard.ts +7 -1
- package/controller/boards/IntelliTouchBoard.ts +153 -42
- package/controller/boards/NixieBoard.ts +209 -66
- package/controller/boards/SunTouchBoard.ts +393 -0
- package/controller/boards/SystemBoard.ts +1862 -1543
- package/controller/comms/Comms.ts +539 -138
- package/controller/comms/ScreenLogic.ts +1663 -0
- package/controller/comms/messages/Messages.ts +242 -60
- package/controller/comms/messages/config/ChlorinatorMessage.ts +4 -3
- package/controller/comms/messages/config/CircuitGroupMessage.ts +5 -2
- package/controller/comms/messages/config/CircuitMessage.ts +81 -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 +2 -1
- 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 +3 -1
- package/controller/comms/messages/config/IntellichemMessage.ts +2 -1
- package/controller/comms/messages/config/OptionsMessage.ts +12 -6
- package/controller/comms/messages/config/PumpMessage.ts +9 -12
- package/controller/comms/messages/config/RemoteMessage.ts +80 -13
- package/controller/comms/messages/config/ScheduleMessage.ts +43 -3
- package/controller/comms/messages/config/SecurityMessage.ts +2 -1
- package/controller/comms/messages/config/ValveMessage.ts +43 -26
- package/controller/comms/messages/status/ChlorinatorStateMessage.ts +8 -7
- package/controller/comms/messages/status/EquipmentStateMessage.ts +93 -20
- package/controller/comms/messages/status/HeaterStateMessage.ts +24 -5
- package/controller/comms/messages/status/IntelliChemStateMessage.ts +7 -4
- package/controller/comms/messages/status/IntelliValveStateMessage.ts +2 -1
- package/controller/comms/messages/status/PumpStateMessage.ts +72 -4
- package/controller/comms/messages/status/VersionMessage.ts +2 -1
- package/controller/nixie/Nixie.ts +15 -4
- package/controller/nixie/NixieEquipment.ts +1 -0
- package/controller/nixie/chemistry/ChemController.ts +300 -129
- package/controller/nixie/chemistry/ChemDoser.ts +806 -0
- package/controller/nixie/chemistry/Chlorinator.ts +133 -129
- package/controller/nixie/circuits/Circuit.ts +171 -30
- package/controller/nixie/heaters/Heater.ts +337 -173
- package/controller/nixie/pumps/Pump.ts +264 -236
- package/controller/nixie/schedules/Schedule.ts +9 -3
- package/defaultConfig.json +45 -5
- package/logger/Logger.ts +38 -9
- package/package.json +13 -9
- package/web/Server.ts +235 -122
- package/web/bindings/aqualinkD.json +114 -59
- package/web/bindings/homeassistant.json +437 -0
- package/web/bindings/influxDB.json +15 -0
- package/web/bindings/mqtt.json +28 -9
- package/web/bindings/mqttAlt.json +15 -0
- package/web/interfaces/baseInterface.ts +58 -7
- package/web/interfaces/httpInterface.ts +5 -2
- package/web/interfaces/influxInterface.ts +9 -2
- package/web/interfaces/mqttInterface.ts +234 -74
- package/web/interfaces/ruleInterface.ts +87 -0
- package/web/services/config/Config.ts +140 -33
- package/web/services/config/ConfigSocket.ts +2 -1
- package/web/services/state/State.ts +144 -3
- package/web/services/state/StateSocket.ts +65 -14
- package/web/services/utilities/Utilities.ts +189 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* nodejs-poolController. An application to control pool equipment.
|
|
2
|
-
Copyright (C) 2016, 2017, 2018, 2019, 2020
|
|
2
|
+
Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
|
|
3
|
+
Russell Goldin, tagyoureit. russ.goldin@gmail.com
|
|
3
4
|
|
|
4
5
|
This program is free software: you can redistribute it and/or modify
|
|
5
6
|
it under the terms of the GNU Affero General Public License as
|
|
@@ -14,16 +15,19 @@ GNU Affero General Public License for more details.
|
|
|
14
15
|
You should have received a copy of the GNU Affero General Public License
|
|
15
16
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
17
|
*/
|
|
18
|
+
|
|
19
|
+
import { sl } from '../../controller/comms/ScreenLogic';
|
|
17
20
|
import * as extend from 'extend';
|
|
18
21
|
import { logger } from '../../logger/Logger';
|
|
19
22
|
import { conn } from '../comms/Comms';
|
|
20
23
|
import { Message, Outbound, Protocol, Response } from '../comms/messages/Messages';
|
|
21
|
-
import { utils } from '../Constants';
|
|
22
|
-
import { Body, ChemController, ConfigVersion, CustomName, EggTimer, Feature, Heater, ICircuit, LightGroup, LightGroupCircuit, Options, PoolSystem, Pump, Schedule, sys } from '../Equipment';
|
|
23
|
-
import {
|
|
24
|
+
import { Timestamp, utils } from '../Constants';
|
|
25
|
+
import { Body, ChemController, ConfigVersion, CustomName, EggTimer, Feature, Heater, ICircuit, LightGroup, LightGroupCircuit, Options, PoolSystem, Pump, Schedule, sys, Valve } from '../Equipment';
|
|
26
|
+
import { InvalidEquipmentDataError, InvalidEquipmentIdError, InvalidOperationError } from '../Errors';
|
|
24
27
|
import { ncp } from "../nixie/Nixie";
|
|
25
28
|
import { BodyTempState, ChlorinatorState, ICircuitGroupState, ICircuitState, LightGroupState, state } from '../State';
|
|
26
|
-
import { BodyCommands, byteValueMap, ChemControllerCommands, ChlorinatorCommands, CircuitCommands, ConfigQueue, ConfigRequest, EquipmentIdRange, FeatureCommands, HeaterCommands, PumpCommands, ScheduleCommands, SystemBoard, SystemCommands } from './SystemBoard';
|
|
29
|
+
import { BodyCommands, byteValueMap, ChemControllerCommands, ChlorinatorCommands, CircuitCommands, ConfigQueue, ConfigRequest, EquipmentIdRange, FeatureCommands, HeaterCommands, PumpCommands, ScheduleCommands, SystemBoard, SystemCommands, ValveCommands } from './SystemBoard';
|
|
30
|
+
|
|
27
31
|
|
|
28
32
|
export class EasyTouchBoard extends SystemBoard {
|
|
29
33
|
public needsConfigChanges: boolean = false;
|
|
@@ -32,8 +36,11 @@ export class EasyTouchBoard extends SystemBoard {
|
|
|
32
36
|
this._statusInterval = -1;
|
|
33
37
|
this.equipmentIds.circuits = new EquipmentIdRange(function () { return this.start; }, function () { return this.start + sys.equipment.maxCircuits - 1; });
|
|
34
38
|
this.equipmentIds.features = new EquipmentIdRange(() => { return 11; }, () => { return this.equipmentIds.features.start + sys.equipment.maxFeatures + 1; });
|
|
35
|
-
this.equipmentIds.
|
|
36
|
-
this.equipmentIds.
|
|
39
|
+
this.equipmentIds.features.start = 11;
|
|
40
|
+
this.equipmentIds.virtualCircuits = new EquipmentIdRange(function () { return 128; }, function () { return 136; });
|
|
41
|
+
this.equipmentIds.virtualCircuits.start = 128;
|
|
42
|
+
this.equipmentIds.circuitGroups = new EquipmentIdRange(function () { return 192; }, function () { return this.start + sys.equipment.maxCircuitGroups - 1; });
|
|
43
|
+
this.equipmentIds.circuitGroups.start = 192;
|
|
37
44
|
this.equipmentIds.circuits.start = sys.equipment.shared || sys.equipment.dual ? 1 : 2;
|
|
38
45
|
if (typeof sys.configVersion.equipment === 'undefined') { sys.configVersion.equipment = 0; }
|
|
39
46
|
this.valueMaps.heatSources = new byteValueMap([
|
|
@@ -46,7 +53,7 @@ export class EasyTouchBoard extends SystemBoard {
|
|
|
46
53
|
[2, { name: 'cooling', desc: 'Cooling' }],
|
|
47
54
|
[3, { name: 'solar', desc: 'Solar' }],
|
|
48
55
|
[4, { name: 'hpheat', desc: 'Heatpump' }],
|
|
49
|
-
[5, { name: 'dual', desc: 'Dual'}]
|
|
56
|
+
[5, { name: 'dual', desc: 'Dual' }]
|
|
50
57
|
]);
|
|
51
58
|
this.valueMaps.customNames = new byteValueMap(
|
|
52
59
|
sys.customNames.get().map((el, idx) => {
|
|
@@ -91,7 +98,7 @@ export class EasyTouchBoard extends SystemBoard {
|
|
|
91
98
|
[30, { name: 'fiberoptic', desc: 'Fiber Optic' }],
|
|
92
99
|
[31, { name: 'fiberworks', desc: 'Fiber Works' }],
|
|
93
100
|
[32, { name: 'fillline', desc: 'Fill Line' }],
|
|
94
|
-
[33, { name: 'floorclnr', desc: 'Floor
|
|
101
|
+
[33, { name: 'floorclnr', desc: 'Floor Cleaner' }],
|
|
95
102
|
[34, { name: 'fogger', desc: 'Fogger' }],
|
|
96
103
|
[35, { name: 'fountain', desc: 'Fountain' }],
|
|
97
104
|
[36, { name: 'fountain1', desc: 'Fountain 1' }],
|
|
@@ -329,16 +336,16 @@ export class EasyTouchBoard extends SystemBoard {
|
|
|
329
336
|
]);
|
|
330
337
|
this.valueMaps.expansionBoards = new byteValueMap([
|
|
331
338
|
[0, { name: 'ET28', part: 'ET2-8', desc: 'EasyTouch2 8', circuits: 8, shared: true }],
|
|
332
|
-
[1, { name: 'ET28P', part: 'ET2-8P', desc: 'EasyTouch2 8P', circuits: 8, shared: false }],
|
|
339
|
+
[1, { name: 'ET28P', part: 'ET2-8P', desc: 'EasyTouch2 8P', circuits: 8, single: true, shared: false }],
|
|
333
340
|
[2, { name: 'ET24', part: 'ET2-4', desc: 'EasyTouch2 4', circuits: 4, shared: true }],
|
|
334
|
-
[3, { name: 'ET24P', part: 'ET2-4P', desc: 'EasyTouch2 4P', circuits: 4, shared: false }],
|
|
341
|
+
[3, { name: 'ET24P', part: 'ET2-4P', desc: 'EasyTouch2 4P', circuits: 4, single: true, shared: false }],
|
|
335
342
|
[6, { name: 'ETPSL4', part: 'ET-PSL4', desc: 'EasyTouch PSL4', circuits: 4, features: 2, schedules: 4, pumps: 1, shared: true }],
|
|
336
|
-
[7, { name: 'ETPL4', part: 'ET-PL4', desc: 'EasyTouch PL4', circuits: 4, features: 2, schedules: 4, pumps: 1, shared: false }],
|
|
343
|
+
[7, { name: 'ETPL4', part: 'ET-PL4', desc: 'EasyTouch PL4', circuits: 4, features: 2, schedules: 4, pumps: 1, single: true, shared: false }],
|
|
337
344
|
// EasyTouch 1 models all start at 128.
|
|
338
345
|
[128, { name: 'ET8', part: 'ET-8', desc: 'EasyTouch 8', circuits: 8, shared: true }],
|
|
339
|
-
[129, { name: 'ET8P', part: 'ET-8P', desc: 'EasyTouch
|
|
346
|
+
[129, { name: 'ET8P', part: 'ET-8P', desc: 'EasyTouch 8P', circuits: 8, single: true, shared: false }],
|
|
340
347
|
[130, { name: 'ET4', part: 'ET-4', desc: 'EasyTouch 4', circuits: 4, shared: true }],
|
|
341
|
-
[
|
|
348
|
+
[131, { name: 'ET4P', part: 'ET-4P', desc: 'EasyTouch 4P', circuits: 4, single: true, shared: false }]
|
|
342
349
|
]);
|
|
343
350
|
}
|
|
344
351
|
public initHeaterDefaults() {
|
|
@@ -374,6 +381,13 @@ export class EasyTouchBoard extends SystemBoard {
|
|
|
374
381
|
let bt = sys.board.valueMaps.bodyTypes.transform(cbody.type);
|
|
375
382
|
tbody.name = cbody.name = bt.desc;
|
|
376
383
|
}
|
|
384
|
+
if (tbody.id === 1) {
|
|
385
|
+
tbody.circuit = cbody.circuit = 6;
|
|
386
|
+
}
|
|
387
|
+
else if (tbody.id === 2) {
|
|
388
|
+
tbody.circuit = cbody.circuit = 1;
|
|
389
|
+
}
|
|
390
|
+
|
|
377
391
|
}
|
|
378
392
|
if (!sys.equipment.shared && !sys.equipment.dual && state.equipment.controllerType !== 'intellitouch') {
|
|
379
393
|
sys.bodies.removeItemById(2);
|
|
@@ -407,7 +421,9 @@ export class EasyTouchBoard extends SystemBoard {
|
|
|
407
421
|
eq.maxFeatures = md.features = typeof mt.features !== 'undefined' ? mt.features : 8;
|
|
408
422
|
eq.maxValves = md.valves = typeof mt.valves !== 'undefined' ? mt.valves : mt.shared ? 4 : 2;
|
|
409
423
|
eq.maxPumps = md.maxPumps = typeof mt.pumps !== 'undefined' ? mt.pumps : 2;
|
|
424
|
+
eq.maxHeaters = md.maxHeaters = typeof mt.heaters !== 'undefined' ? mt.heaters : 2;
|
|
410
425
|
eq.shared = mt.shared;
|
|
426
|
+
eq.single = typeof mt.single !== 'undefined' ? mt.single : false;
|
|
411
427
|
eq.dual = false;
|
|
412
428
|
eq.maxChlorinators = md.chlorinators = 1;
|
|
413
429
|
eq.maxChemControllers = md.chemControllers = 1;
|
|
@@ -450,8 +466,10 @@ export class EasyTouchBoard extends SystemBoard {
|
|
|
450
466
|
state.equipment.maxPumps = sys.equipment.maxPumps;
|
|
451
467
|
state.equipment.maxSchedules = sys.equipment.maxSchedules;
|
|
452
468
|
state.equipment.maxValves = sys.equipment.maxValves;
|
|
469
|
+
state.equipment.single = sys.equipment.single;
|
|
453
470
|
state.equipment.shared = sys.equipment.shared;
|
|
454
471
|
state.equipment.dual = sys.equipment.dual;
|
|
472
|
+
eq.setEquipmentIds();
|
|
455
473
|
state.emitControllerChange();
|
|
456
474
|
}
|
|
457
475
|
public bodies: TouchBodyCommands = new TouchBodyCommands(this);
|
|
@@ -463,8 +481,16 @@ export class EasyTouchBoard extends SystemBoard {
|
|
|
463
481
|
public schedules: TouchScheduleCommands = new TouchScheduleCommands(this);
|
|
464
482
|
public heaters: TouchHeaterCommands = new TouchHeaterCommands(this);
|
|
465
483
|
public chemControllers: TouchChemControllerCommands = new TouchChemControllerCommands(this);
|
|
484
|
+
public valves: TouchValveCommands = new TouchValveCommands(this);
|
|
466
485
|
protected _configQueue: TouchConfigQueue = new TouchConfigQueue();
|
|
467
|
-
|
|
486
|
+
public reloadConfig() {
|
|
487
|
+
//sys.resetSystem();
|
|
488
|
+
sys.configVersion.clear();
|
|
489
|
+
state.status = 0;
|
|
490
|
+
this.needsConfigChanges = true;
|
|
491
|
+
console.log('RESETTING THE CONFIGURATION');
|
|
492
|
+
this.modulesAcquired = false;
|
|
493
|
+
}
|
|
468
494
|
public checkConfiguration() {
|
|
469
495
|
if ((this.needsConfigChanges || (Date.now().valueOf() - new Date(sys.configVersion.lastUpdated).valueOf()) / 1000 / 60 > 20)) {
|
|
470
496
|
//this._configQueue.clearTimer();
|
|
@@ -510,28 +536,26 @@ export class TouchConfigQueue extends ConfigQueue {
|
|
|
510
536
|
protected queueItems(cat: number, items: number[] = [0]) { this.push(new TouchConfigRequest(cat, items)); }
|
|
511
537
|
public queueChanges() {
|
|
512
538
|
this.reset();
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
// todo: add chlor or other commands not asked for by screenlogic if there is no remote/indoor panel present
|
|
534
|
-
}
|
|
539
|
+
logger.info(`Requesting ${sys.controllerType} configuration`);
|
|
540
|
+
this.queueItems(GetTouchConfigCategories.dateTime);
|
|
541
|
+
this.queueItems(GetTouchConfigCategories.version);
|
|
542
|
+
this.queueRange(GetTouchConfigCategories.customNames, 0, sys.equipment.maxCustomNames - 1);
|
|
543
|
+
this.queueRange(GetTouchConfigCategories.circuits, 1, sys.board.equipmentIds.features.end);
|
|
544
|
+
this.queueRange(GetTouchConfigCategories.schedules, 1, sys.equipment.maxSchedules);
|
|
545
|
+
// moved heat/solar request items after circuits to allow bodies to be discovered
|
|
546
|
+
this.queueItems(GetTouchConfigCategories.heatTemperature);
|
|
547
|
+
this.queueItems(GetTouchConfigCategories.solarHeatPump);
|
|
548
|
+
this.queueItems(GetTouchConfigCategories.delays);
|
|
549
|
+
this.queueItems(GetTouchConfigCategories.settings);
|
|
550
|
+
this.queueItems(GetTouchConfigCategories.intellifloSpaSideRemotes);
|
|
551
|
+
this.queueItems(GetTouchConfigCategories.is4is10);
|
|
552
|
+
this.queueItems(GetTouchConfigCategories.quickTouchRemote);
|
|
553
|
+
this.queueItems(GetTouchConfigCategories.valves);
|
|
554
|
+
this.queueItems(GetTouchConfigCategories.lightGroupPositions);
|
|
555
|
+
this.queueItems(GetTouchConfigCategories.highSpeedCircuits);
|
|
556
|
+
this.queueRange(GetTouchConfigCategories.pumpConfig, 1, sys.equipment.maxPumps);
|
|
557
|
+
this.queueItems(GetTouchConfigCategories.intellichlor);
|
|
558
|
+
// todo: add chlor or other commands not asked for by screenlogic if there is no remote/indoor panel present
|
|
535
559
|
if (this.remainingItems > 0) {
|
|
536
560
|
var self = this;
|
|
537
561
|
setTimeout(() => { self.processNext(); }, 50);
|
|
@@ -578,6 +602,7 @@ export class TouchConfigQueue extends ConfigQueue {
|
|
|
578
602
|
let itm = 0;
|
|
579
603
|
const self = this;
|
|
580
604
|
if (this.curr && !this.curr.isComplete) {
|
|
605
|
+
|
|
581
606
|
itm = this.curr.items.shift();
|
|
582
607
|
const out: Outbound = Outbound.create({
|
|
583
608
|
source: Message.pluginAddress,
|
|
@@ -585,11 +610,33 @@ export class TouchConfigQueue extends ConfigQueue {
|
|
|
585
610
|
action: this.curr.setcategory,
|
|
586
611
|
payload: [itm],
|
|
587
612
|
retries: 3,
|
|
588
|
-
response: Response.create({
|
|
613
|
+
response: Response.create({
|
|
614
|
+
response: true
|
|
615
|
+
, callback: () => {
|
|
616
|
+
console.log(`CALLBACKED`);
|
|
617
|
+
}
|
|
618
|
+
// , callback: () => {
|
|
619
|
+
// self.processNext(out);
|
|
620
|
+
// }
|
|
621
|
+
})
|
|
589
622
|
// response: true,
|
|
590
623
|
// onResponseProcessed: function () { self.processNext(out); }
|
|
591
624
|
});
|
|
592
|
-
|
|
625
|
+
// RKS: 12-1-22 the unfortunate part of the response: true setting is that there is mapping in the isResponse that should translate the exceptions
|
|
626
|
+
// Unfortunately somewhere along the line we quit asking for the firmware version. RG 12-4-22 Not sure where this is lost, but it is added back.
|
|
627
|
+
//out.timeout = 5000;
|
|
628
|
+
// setTimeout(() => conn.queueSendMessage(out), 50);
|
|
629
|
+
out.sendAsync()
|
|
630
|
+
.then(() => {
|
|
631
|
+
logger.debug(`msg ${out.toShortPacket()} sent successfully`);
|
|
632
|
+
})
|
|
633
|
+
.catch((err) => {
|
|
634
|
+
logger.error(`Error sending configuration request message on port ${out.portId}: ${err.message};`);
|
|
635
|
+
})
|
|
636
|
+
.finally(() => {
|
|
637
|
+
setTimeout(() => { self.processNext(out); }, 50);
|
|
638
|
+
})
|
|
639
|
+
|
|
593
640
|
} else {
|
|
594
641
|
// Now that we are done check the configuration a final time. If we have anything outstanding
|
|
595
642
|
// it will get picked up.
|
|
@@ -607,165 +654,135 @@ export class TouchConfigQueue extends ConfigQueue {
|
|
|
607
654
|
}
|
|
608
655
|
}
|
|
609
656
|
export class TouchScheduleCommands extends ScheduleCommands {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
657
|
+
public async setScheduleAsync(data: any, send: boolean = true): Promise<Schedule> {
|
|
658
|
+
try {
|
|
659
|
+
let id = typeof data.id === 'undefined' ? -1 : parseInt(data.id, 10);
|
|
660
|
+
let slSchedId = id;
|
|
661
|
+
if (id <= 0) id = sys.schedules.getNextEquipmentId(new EquipmentIdRange(1, sys.equipment.maxSchedules));
|
|
662
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid schedule id: ${data.id}`, data.id, 'Schedule'));
|
|
663
|
+
let sched = sys.schedules.getItemById(id, id > 0);
|
|
664
|
+
let ssched = state.schedules.getItemById(id, id > 0);
|
|
665
|
+
let schedType = typeof data.scheduleType !== 'undefined' ? data.scheduleType : sched.scheduleType;
|
|
666
|
+
if (typeof schedType === 'undefined') schedType = sys.board.valueMaps.scheduleTypes.getValue('repeat'); // Repeats
|
|
617
667
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
668
|
+
let startTimeType = typeof data.startTimeType !== 'undefined' ? data.startTimeType : sched.startTimeType;
|
|
669
|
+
let endTimeType = typeof data.endTimeType !== 'undefined' ? data.endTimeType : sched.endTimeType;
|
|
670
|
+
// let startDate = typeof data.startDate !== 'undefined' ? data.startDate : sched.startDate;
|
|
671
|
+
// if (typeof startDate.getMonth !== 'function') startDate = new Date(startDate);
|
|
672
|
+
let heatSource = typeof data.heatSource !== 'undefined' && data.heatSource !== null ? data.heatSource : sched.heatSource || 32;
|
|
673
|
+
let heatSetpoint = typeof data.heatSetpoint !== 'undefined' ? data.heatSetpoint : sched.heatSetpoint;
|
|
674
|
+
let circuit = typeof data.circuit !== 'undefined' ? data.circuit : sched.circuit;
|
|
675
|
+
let startTime = typeof data.startTime !== 'undefined' ? data.startTime : sched.startTime;
|
|
676
|
+
let endTime = typeof data.endTime !== 'undefined' ? data.endTime : sched.endTime;
|
|
677
|
+
let schedDays = sys.board.schedules.transformDays(typeof data.scheduleDays !== 'undefined' ? data.scheduleDays : sched.scheduleDays || 255); // default to all days
|
|
678
|
+
let changeHeatSetpoint = typeof (data.changeHeatSetpoint !== 'undefined') ? utils.makeBool(data.changeHeatSetpoint) : sched.changeHeatSetpoint;
|
|
679
|
+
let display = typeof data.display !== 'undefined' ? data.display : sched.display || 0;
|
|
621
680
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
if (sched instanceof EggTimer) {
|
|
632
|
-
setSchedConfig.payload[1] = sched.circuit;
|
|
633
|
-
setSchedConfig.payload[2] = 25;
|
|
634
|
-
setSchedConfig.payload[4] = Math.floor(sched.runTime);
|
|
635
|
-
setSchedConfig.payload[5] = sched.runTime - (setSchedConfig.payload[4] * 60);
|
|
636
|
-
}
|
|
637
|
-
else if (sched instanceof Schedule) {
|
|
638
|
-
setSchedConfig.payload[1] = sched.circuit;
|
|
639
|
-
setSchedConfig.payload[2] = Math.floor(sched.startTime / 60);
|
|
640
|
-
setSchedConfig.payload[3] = sched.startTime - (setSchedConfig.payload[2] * 60);
|
|
641
|
-
setSchedConfig.payload[4] = Math.floor(sched.endTime / 60);
|
|
642
|
-
setSchedConfig.payload[5] = sched.endTime - (setSchedConfig.payload[4] * 60);
|
|
643
|
-
setSchedConfig.payload[6] = sched.scheduleDays;
|
|
644
|
-
if (sched.scheduleType === sys.board.valueMaps.scheduleTypes.getValue('runonce')) setSchedConfig.payload[6] = setSchedConfig.payload[6] | 0x80;
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
const schedConfigRequest = Outbound.create({
|
|
648
|
-
action: 209,
|
|
649
|
-
payload: [sched.id],
|
|
650
|
-
retries: 2
|
|
651
|
-
});
|
|
681
|
+
// Ensure all the defaults.
|
|
682
|
+
// if (isNaN(startDate.getTime())) startDate = new Date();
|
|
683
|
+
if (typeof startTime === 'undefined') startTime = 480; // 8am
|
|
684
|
+
if (typeof endTime === 'undefined') endTime = 1020; // 5pm
|
|
685
|
+
if (typeof startTimeType === 'undefined') startTimeType = 0; // Manual
|
|
686
|
+
if (typeof endTimeType === 'undefined') endTimeType = 0; // Manual
|
|
687
|
+
if (typeof circuit === 'undefined') circuit = 6; // pool
|
|
688
|
+
if (typeof heatSource !== 'undefined' && typeof heatSetpoint === 'undefined') heatSetpoint = state.temps.units === sys.board.valueMaps.tempUnits.getValue('C') ? 26 : 80;
|
|
689
|
+
if (typeof changeHeatSetpoint === 'undefined') changeHeatSetpoint = false;
|
|
652
690
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
691
|
+
// At this point we should have all the data. Validate it.
|
|
692
|
+
if (!sys.board.valueMaps.scheduleTypes.valExists(schedType)) { sys.schedules.removeItemById(id); state.schedules.removeItemById(id); return Promise.reject(new InvalidEquipmentDataError(`Invalid schedule type; ${schedType}`, 'Schedule', schedType)); }
|
|
693
|
+
if (!sys.board.valueMaps.scheduleTimeTypes.valExists(startTimeType)) { sys.schedules.removeItemById(id); state.schedules.removeItemById(id); return Promise.reject(new InvalidEquipmentDataError(`Invalid start time type; ${startTimeType}`, 'Schedule', startTimeType)); }
|
|
694
|
+
if (!sys.board.valueMaps.scheduleTimeTypes.valExists(endTimeType)) { sys.schedules.removeItemById(id); state.schedules.removeItemById(id); return Promise.reject(new InvalidEquipmentDataError(`Invalid end time type; ${endTimeType}`, 'Schedule', endTimeType)); }
|
|
695
|
+
if (!sys.board.valueMaps.heatSources.valExists(heatSource)) { sys.schedules.removeItemById(id); state.schedules.removeItemById(id); return Promise.reject(new InvalidEquipmentDataError(`Invalid heat source: ${heatSource}`, 'Schedule', heatSource)); }
|
|
696
|
+
if (heatSetpoint < 0 || heatSetpoint > 104) { sys.schedules.removeItemById(id); state.schedules.removeItemById(id); return Promise.reject(new InvalidEquipmentDataError(`Invalid heat setpoint: ${heatSetpoint}`, 'Schedule', heatSetpoint)); }
|
|
697
|
+
if (sys.board.circuits.getCircuitReferences(true, true, false, true).find(elem => elem.id === circuit) === undefined) { sys.schedules.removeItemById(id); state.schedules.removeItemById(id); return Promise.reject(new InvalidEquipmentDataError(`Invalid circuit reference: ${circuit}`, 'Schedule', circuit)); }
|
|
698
|
+
// if (schedDays === 0) return Promise.reject(new InvalidEquipmentDataError(`Invalid schedule days: ${schedDays}. You must supply days that the schedule is to run.`, 'Schedule', schedDays));
|
|
699
|
+
if (typeof heatSource !== 'undefined' && !sys.circuits.getItemById(circuit).hasHeatSource) heatSource = undefined;
|
|
656
700
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
let heatSetpoint = typeof data.heatSetpoint !== 'undefined' ? data.heatSetpoint : sched.heatSetpoint;
|
|
671
|
-
let circuit = typeof data.circuit !== 'undefined' ? data.circuit : sched.circuit;
|
|
672
|
-
let startTime = typeof data.startTime !== 'undefined' ? data.startTime : sched.startTime;
|
|
673
|
-
let endTime = typeof data.endTime !== 'undefined' ? data.endTime : sched.endTime;
|
|
674
|
-
let schedDays = sys.board.schedules.transformDays(typeof data.scheduleDays !== 'undefined' ? data.scheduleDays : sched.scheduleDays || 255); // default to all days
|
|
675
|
-
let changeHeatSetpoint = typeof (data.changeHeatSetpoint !== 'undefined') ? utils.makeBool(data.changeHeatSetpoint) : sched.changeHeatSetpoint;
|
|
676
|
-
let display = typeof data.display !== 'undefined' ? data.display : sched.display || 0;
|
|
677
|
-
|
|
678
|
-
// Ensure all the defaults.
|
|
679
|
-
// if (isNaN(startDate.getTime())) startDate = new Date();
|
|
680
|
-
if (typeof startTime === 'undefined') startTime = 480; // 8am
|
|
681
|
-
if (typeof endTime === 'undefined') endTime = 1020; // 5pm
|
|
682
|
-
if (typeof startTimeType === 'undefined') startTimeType = 0; // Manual
|
|
683
|
-
if (typeof endTimeType === 'undefined') endTimeType = 0; // Manual
|
|
684
|
-
if (typeof circuit === 'undefined') circuit = 6; // pool
|
|
685
|
-
if (typeof heatSource !== 'undefined' && typeof heatSetpoint === 'undefined') heatSetpoint = state.temps.units === sys.board.valueMaps.tempUnits.getValue('C') ? 26 : 80;
|
|
686
|
-
if (typeof changeHeatSetpoint === 'undefined') changeHeatSetpoint = false;
|
|
687
|
-
|
|
688
|
-
// At this point we should have all the data. Validate it.
|
|
689
|
-
if (!sys.board.valueMaps.scheduleTypes.valExists(schedType)) { sys.schedules.removeItemById(id); state.schedules.removeItemById(id); return Promise.reject(new InvalidEquipmentDataError(`Invalid schedule type; ${schedType}`, 'Schedule', schedType)); }
|
|
690
|
-
if (!sys.board.valueMaps.scheduleTimeTypes.valExists(startTimeType)) { sys.schedules.removeItemById(id); state.schedules.removeItemById(id); return Promise.reject(new InvalidEquipmentDataError(`Invalid start time type; ${startTimeType}`, 'Schedule', startTimeType)); }
|
|
691
|
-
if (!sys.board.valueMaps.scheduleTimeTypes.valExists(endTimeType)) { sys.schedules.removeItemById(id); state.schedules.removeItemById(id); return Promise.reject(new InvalidEquipmentDataError(`Invalid end time type; ${endTimeType}`, 'Schedule', endTimeType)); }
|
|
692
|
-
if (!sys.board.valueMaps.heatSources.valExists(heatSource)) { sys.schedules.removeItemById(id); state.schedules.removeItemById(id); return Promise.reject(new InvalidEquipmentDataError(`Invalid heat source: ${heatSource}`, 'Schedule', heatSource)); }
|
|
693
|
-
if (heatSetpoint < 0 || heatSetpoint > 104) { sys.schedules.removeItemById(id); state.schedules.removeItemById(id); return Promise.reject(new InvalidEquipmentDataError(`Invalid heat setpoint: ${heatSetpoint}`, 'Schedule', heatSetpoint)); }
|
|
694
|
-
if (sys.board.circuits.getCircuitReferences(true, true, false, true).find(elem => elem.id === circuit) === undefined) { sys.schedules.removeItemById(id); state.schedules.removeItemById(id); return Promise.reject(new InvalidEquipmentDataError(`Invalid circuit reference: ${circuit}`, 'Schedule', circuit)); }
|
|
695
|
-
// if (schedDays === 0) return Promise.reject(new InvalidEquipmentDataError(`Invalid schedule days: ${schedDays}. You must supply days that the schedule is to run.`, 'Schedule', schedDays));
|
|
696
|
-
if (typeof heatSource !== 'undefined' && !sys.circuits.getItemById(circuit).hasHeatSource) heatSource = undefined;
|
|
697
|
-
|
|
698
|
-
// If we make it here we can make it anywhere.
|
|
699
|
-
// let runOnce = (schedDays || (schedType !== 0 ? 0 : 0x80));
|
|
700
|
-
if (schedType === sys.board.valueMaps.scheduleTypes.getValue('runonce')) {
|
|
701
|
-
// make sure only 1 day is selected
|
|
702
|
-
let scheduleDays = sys.board.valueMaps.scheduleDays.transform(schedDays);
|
|
703
|
-
let s2 = sys.board.valueMaps.scheduleDays.toArray();
|
|
704
|
-
if (scheduleDays.days.length > 1) {
|
|
705
|
-
schedDays = scheduleDays.days[scheduleDays.days.length - 1].val; // get the earliest day in the week
|
|
706
|
-
}
|
|
707
|
-
else if (scheduleDays.days.length === 0) {
|
|
708
|
-
for (let i = 0; i < s2.length; i++) {
|
|
709
|
-
if (s2[i].days[0].name === 'sun') schedDays = s2[i].val;
|
|
701
|
+
// If we make it here we can make it anywhere.
|
|
702
|
+
// let runOnce = (schedDays || (schedType !== 0 ? 0 : 0x80));
|
|
703
|
+
if (schedType === sys.board.valueMaps.scheduleTypes.getValue('runonce')) {
|
|
704
|
+
// make sure only 1 day is selected
|
|
705
|
+
let scheduleDays = sys.board.valueMaps.scheduleDays.transform(schedDays);
|
|
706
|
+
let s2 = sys.board.valueMaps.scheduleDays.toArray();
|
|
707
|
+
if (scheduleDays.days.length > 1) {
|
|
708
|
+
schedDays = scheduleDays.days[scheduleDays.days.length - 1].val; // get the earliest day in the week
|
|
709
|
+
}
|
|
710
|
+
else if (scheduleDays.days.length === 0) {
|
|
711
|
+
for (let i = 0; i < s2.length; i++) {
|
|
712
|
+
if (s2[i].days[0].name === 'sun') schedDays = s2[i].val;
|
|
713
|
+
}
|
|
710
714
|
}
|
|
715
|
+
// update end time incase egg timer changed
|
|
716
|
+
const eggTimer = sys.circuits.getInterfaceById(circuit).eggTimer || 720;
|
|
717
|
+
endTime = (startTime + eggTimer) % 1440; // remove days if we go past midnight
|
|
711
718
|
}
|
|
712
|
-
// update end time incase egg timer changed
|
|
713
|
-
const eggTimer = sys.circuits.getInterfaceById(circuit).eggTimer || 720;
|
|
714
|
-
endTime = (startTime + eggTimer) % 1440; // remove days if we go past midnight
|
|
715
|
-
}
|
|
716
719
|
|
|
717
720
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
721
|
+
// If we have sunrise/sunset then adjust for the values; if heliotrope isn't set just ignore
|
|
722
|
+
if (state.heliotrope.isCalculated) {
|
|
723
|
+
const sunrise = state.heliotrope.sunrise.getHours() * 60 + state.heliotrope.sunrise.getMinutes();
|
|
724
|
+
const sunset = state.heliotrope.sunset.getHours() * 60 + state.heliotrope.sunset.getMinutes();
|
|
725
|
+
if (startTimeType === sys.board.valueMaps.scheduleTimeTypes.getValue('sunrise')) startTime = sunrise;
|
|
726
|
+
else if (startTimeType === sys.board.valueMaps.scheduleTimeTypes.getValue('sunset')) startTime = sunset;
|
|
727
|
+
if (endTimeType === sys.board.valueMaps.scheduleTimeTypes.getValue('sunrise')) endTime = sunrise;
|
|
728
|
+
else if (endTimeType === sys.board.valueMaps.scheduleTimeTypes.getValue('sunset')) endTime = sunset;
|
|
729
|
+
}
|
|
727
730
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
// ,response: Response.create({ action: 1, payload: [145] })
|
|
740
|
-
});
|
|
741
|
-
return new Promise<Schedule>((resolve, reject) => {
|
|
742
|
-
out.onComplete = (err, msg) => {
|
|
743
|
-
if (!err) {
|
|
744
|
-
sched.circuit = ssched.circuit = circuit;
|
|
745
|
-
sched.scheduleDays = ssched.scheduleDays = schedDays;
|
|
746
|
-
sched.scheduleType = ssched.scheduleType = schedType;
|
|
747
|
-
sched.changeHeatSetpoint = ssched.changeHeatSetpoint = changeHeatSetpoint;
|
|
748
|
-
sched.heatSetpoint = ssched.heatSetpoint = heatSetpoint;
|
|
749
|
-
sched.heatSource = ssched.heatSource = heatSource;
|
|
750
|
-
sched.startTime = ssched.startTime = startTime;
|
|
751
|
-
sched.endTime = ssched.endTime = endTime;
|
|
752
|
-
sched.startTimeType = ssched.startTimeType = startTimeType;
|
|
753
|
-
sched.endTimeType = ssched.endTimeType = endTimeType;
|
|
754
|
-
sched.isActive = ssched.isActive = true;
|
|
755
|
-
ssched.display = sched.display = display;
|
|
756
|
-
ssched.emitEquipmentChange();
|
|
757
|
-
// For good measure russ is sending out a config request for
|
|
758
|
-
// the schedule in question. If there was a failure on the
|
|
759
|
-
// OCP side this will resolve it.
|
|
760
|
-
let req = Outbound.create({ action: 209, payload: [sched.id], retries: 2 });
|
|
761
|
-
conn.queueSendMessage(req);
|
|
762
|
-
state.schedules.sortById();
|
|
763
|
-
resolve(sched);
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
if (sl.enabled && send) {
|
|
734
|
+
let slId = await sl.schedules.setScheduleAsync(slSchedId, circuit, startTime, endTime, schedDays, schedType, changeHeatSetpoint, heatSource, heatSetpoint);
|
|
735
|
+
// if SL adds this as a new schedule id different from what we expect.
|
|
736
|
+
if (slId !== id) {
|
|
737
|
+
sys.schedules.removeItemById(id);
|
|
738
|
+
state.schedules.removeItemById(id);
|
|
739
|
+
id = slId;
|
|
740
|
+
sched = sys.schedules.getItemById(id, id > 0);
|
|
741
|
+
ssched = state.schedules.getItemById(id, id > 0);
|
|
764
742
|
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
743
|
+
}
|
|
744
|
+
else if (send) {
|
|
745
|
+
let out = Outbound.create({
|
|
746
|
+
action: 145,
|
|
747
|
+
payload: [
|
|
748
|
+
id,
|
|
749
|
+
circuit,
|
|
750
|
+
Math.floor(startTime / 60),
|
|
751
|
+
startTime - (Math.floor(startTime / 60) * 60),
|
|
752
|
+
schedType === sys.board.valueMaps.scheduleTypes.getValue('runonce') ? sys.board.valueMaps.scheduleTypes.getValue('runonce') : Math.floor(endTime / 60),
|
|
753
|
+
endTime - (Math.floor(endTime / 60) * 60),
|
|
754
|
+
schedDays],
|
|
755
|
+
retries: 2
|
|
756
|
+
// ,response: Response.create({ action: 1, payload: [145] })
|
|
757
|
+
});
|
|
758
|
+
await out.sendAsync();
|
|
759
|
+
}
|
|
760
|
+
sched.circuit = ssched.circuit = circuit;
|
|
761
|
+
sched.scheduleDays = ssched.scheduleDays = schedDays;
|
|
762
|
+
sched.scheduleType = ssched.scheduleType = schedType;
|
|
763
|
+
sched.changeHeatSetpoint = ssched.changeHeatSetpoint = changeHeatSetpoint;
|
|
764
|
+
sched.heatSetpoint = ssched.heatSetpoint = heatSetpoint;
|
|
765
|
+
sched.heatSource = ssched.heatSource = heatSource;
|
|
766
|
+
sched.startTime = ssched.startTime = startTime;
|
|
767
|
+
sched.endTime = ssched.endTime = endTime;
|
|
768
|
+
sched.startTimeType = ssched.startTimeType = startTimeType;
|
|
769
|
+
sched.endTimeType = ssched.endTimeType = endTimeType;
|
|
770
|
+
sched.isActive = ssched.isActive = true;
|
|
771
|
+
ssched.display = sched.display = display;
|
|
772
|
+
ssched.emitEquipmentChange();
|
|
773
|
+
// For good measure russ is sending out a config request for
|
|
774
|
+
// the schedule in question. If there was a failure on the
|
|
775
|
+
// OCP side this will resolve it.
|
|
776
|
+
if (send && !sl.enabled) {
|
|
777
|
+
let req = Outbound.create({ action: 209, payload: [sched.id], retries: 2 });
|
|
778
|
+
await req.sendAsync();
|
|
779
|
+
}
|
|
780
|
+
state.schedules.sortById();
|
|
781
|
+
return sched;
|
|
782
|
+
}
|
|
783
|
+
catch (err) {
|
|
784
|
+
return Promise.reject(err);
|
|
785
|
+
}
|
|
769
786
|
}
|
|
770
787
|
public async deleteScheduleAsync(data: any): Promise<Schedule> {
|
|
771
788
|
let id = typeof data.id === 'undefined' ? -1 : parseInt(data.id, 10);
|
|
@@ -773,106 +790,162 @@ export class TouchScheduleCommands extends ScheduleCommands {
|
|
|
773
790
|
let sched = sys.schedules.getItemById(id);
|
|
774
791
|
let ssched = state.schedules.getItemById(id);
|
|
775
792
|
// RKS: Assuming you just send 0s for the schedule and it will delete it.
|
|
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
|
-
|
|
793
|
+
try {
|
|
794
|
+
if (sl.enabled) {
|
|
795
|
+
await sl.schedules.deleteScheduleAsync(id);
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
|
|
799
|
+
let out = Outbound.create({
|
|
800
|
+
action: 145,
|
|
801
|
+
payload: [
|
|
802
|
+
id,
|
|
803
|
+
0,
|
|
804
|
+
0,
|
|
805
|
+
0,
|
|
806
|
+
0,
|
|
807
|
+
0,
|
|
808
|
+
0],
|
|
809
|
+
retries: 3
|
|
810
|
+
});
|
|
811
|
+
await out.sendAsync();
|
|
812
|
+
}
|
|
813
|
+
sys.schedules.removeItemById(id);
|
|
814
|
+
state.schedules.removeItemById(id);
|
|
815
|
+
ssched.emitEquipmentChange();
|
|
816
|
+
sched.isActive = false;
|
|
817
|
+
if (!sl.enabled) {
|
|
818
|
+
let req = Outbound.create({ action: 209, payload: [sched.id], retries: 2 });
|
|
819
|
+
await req.sendAsync();
|
|
820
|
+
}
|
|
821
|
+
return sched;
|
|
822
|
+
}
|
|
823
|
+
catch (err) {
|
|
824
|
+
return Promise.reject(err);
|
|
825
|
+
}
|
|
803
826
|
}
|
|
804
|
-
public async setEggTimerAsync(data?: any): Promise<EggTimer> {
|
|
827
|
+
public async setEggTimerAsync(data?: any, send: boolean = true): Promise<EggTimer> {
|
|
805
828
|
let id = typeof data.id === 'undefined' ? -1 : parseInt(data.id, 10);
|
|
806
829
|
if (id <= 0) id = sys.schedules.getNextEquipmentId(new EquipmentIdRange(1, sys.equipment.maxSchedules));
|
|
807
830
|
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid schedule/eggTimer id: ${data.id} or all schedule/eggTimer ids filled (${sys.eggTimers.length + sys.schedules.length} used out of ${sys.equipment.maxSchedules})`, data.id, 'Schedule'));
|
|
808
831
|
let circuit = sys.circuits.getInterfaceById(data.circuit);
|
|
809
832
|
if (typeof circuit === 'undefined') return Promise.reject(new InvalidEquipmentIdError(`Invalid circuit id: ${data.circuit} for schedule id ${data.id}`, data.id, 'Schedule'));
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
id,
|
|
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
|
-
|
|
833
|
+
|
|
834
|
+
try {
|
|
835
|
+
if (send) {
|
|
836
|
+
if (sl.enabled) {
|
|
837
|
+
await sl.schedules.setEggTimerAsync(circuit.id, parseInt(data.runTime, 10));
|
|
838
|
+
}
|
|
839
|
+
else {
|
|
840
|
+
|
|
841
|
+
let out = Outbound.create({
|
|
842
|
+
action: 145,
|
|
843
|
+
payload: [
|
|
844
|
+
id,
|
|
845
|
+
circuit.id,
|
|
846
|
+
25,
|
|
847
|
+
0,
|
|
848
|
+
utils.makeBool(data.dontStop) ? 27 : Math.floor(parseInt(data.runTime, 10) / 60),
|
|
849
|
+
utils.makeBool(data.dontStop) ? 0 : data.runTime - (Math.floor(parseInt(data.runTime, 10) / 60) * 60),
|
|
850
|
+
0],
|
|
851
|
+
retries: 2
|
|
852
|
+
});
|
|
853
|
+
await out.sendAsync();
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
let eggTimer = sys.eggTimers.getItemById(id, true);
|
|
857
|
+
eggTimer.circuit = circuit.id;
|
|
858
|
+
eggTimer.runTime = circuit.eggTimer = typeof data.runTime !== 'undefined' ? data.runTime : circuit.eggTimer || 720;
|
|
859
|
+
circuit.dontStop = typeof data.dontStop !== 'undefined' ? utils.makeBool(data.dontStop) : eggTimer.runTime === 1620;
|
|
860
|
+
eggTimer.isActive = true;
|
|
861
|
+
// For good measure russ is sending out a config request for
|
|
862
|
+
// the schedule in question. If there was a failure on the
|
|
863
|
+
// OCP side this will resolve it.
|
|
864
|
+
if (send) {
|
|
865
|
+
let req = Outbound.create({ action: 209, payload: [eggTimer.id], retries: 2 });
|
|
866
|
+
await req.sendAsync();
|
|
867
|
+
}
|
|
868
|
+
return eggTimer;
|
|
869
|
+
}
|
|
870
|
+
catch (err) {
|
|
871
|
+
return Promise.reject(err);
|
|
872
|
+
}
|
|
873
|
+
|
|
841
874
|
}
|
|
842
875
|
public async deleteEggTimerAsync(data: any): Promise<EggTimer> {
|
|
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
|
-
|
|
876
|
+
let id = typeof data.id === 'undefined' ? -1 : parseInt(data.id, 10);
|
|
877
|
+
if (isNaN(id) || id < 0) throw new InvalidEquipmentIdError(`Invalid eggTimer id: ${data.id}`, data.id, 'Schedule');
|
|
878
|
+
let eggTimer = sys.eggTimers.getItemById(id);
|
|
879
|
+
// RKS: Assuming you just send 0s for the schedule and it will delete it.
|
|
880
|
+
try {
|
|
881
|
+
if (sl.enabled) {
|
|
882
|
+
await sl.schedules.setEggTimerAsync(data.circuit, 720);
|
|
883
|
+
}
|
|
884
|
+
else {
|
|
885
|
+
let out = Outbound.create({
|
|
886
|
+
action: 145,
|
|
887
|
+
payload: [
|
|
888
|
+
id,
|
|
889
|
+
0,
|
|
890
|
+
0,
|
|
891
|
+
0,
|
|
892
|
+
0,
|
|
893
|
+
0,
|
|
894
|
+
0],
|
|
895
|
+
retries: 3
|
|
896
|
+
});
|
|
897
|
+
await out.sendAsync();
|
|
898
|
+
}
|
|
899
|
+
const circuit = sys.circuits.getInterfaceById(data.circuit);
|
|
900
|
+
circuit.eggTimer = 720;
|
|
901
|
+
circuit.dontStop = circuit.eggTimer === 1620;
|
|
902
|
+
sys.eggTimers.removeItemById(id);
|
|
903
|
+
eggTimer.isActive = false;
|
|
904
|
+
if (!sl.enabled) {
|
|
905
|
+
let req = Outbound.create({ action: 209, payload: [eggTimer.id], retries: 2 });
|
|
906
|
+
await req.sendAsync();
|
|
907
|
+
}
|
|
908
|
+
return eggTimer;
|
|
909
|
+
}
|
|
910
|
+
catch (err) {
|
|
911
|
+
return Promise.reject(err);
|
|
912
|
+
}
|
|
875
913
|
}
|
|
914
|
+
public async updateSunriseSunsetAsync(): Promise<boolean> {
|
|
915
|
+
// *Touch doesn't have a notion of sunrise/sunset on the schedules;
|
|
916
|
+
// This will check the schedule and if the existing sunrise/sunset times
|
|
917
|
+
// are not matching the desired time it will update the time on the OCP.
|
|
918
|
+
// https://github.com/tagyoureit/nodejs-poolController/discussions/560#discussioncomment-3362149
|
|
919
|
+
if (!state.heliotrope.isCalculated) { return false; }
|
|
920
|
+
const sunrise = state.heliotrope.sunrise.getHours() * 60 + state.heliotrope.sunrise.getMinutes();
|
|
921
|
+
const sunset = state.heliotrope.sunset.getHours() * 60 + state.heliotrope.sunset.getMinutes();
|
|
922
|
+
|
|
923
|
+
let anyUpdated = false;
|
|
924
|
+
for (let i = 0; i <= sys.schedules.length; i++) {
|
|
925
|
+
let sUpdated = false;
|
|
926
|
+
let sched = sys.schedules.getItemByIndex(i);
|
|
927
|
+
if (sched.startTimeType === sys.board.valueMaps.scheduleTimeTypes.getValue('sunrise') && sched.startTime !== sunrise) {
|
|
928
|
+
sched.startTime = sunrise;
|
|
929
|
+
anyUpdated = sUpdated = true;
|
|
930
|
+
}
|
|
931
|
+
else if (sched.startTimeType === sys.board.valueMaps.scheduleTimeTypes.getValue('sunset') && sched.startTime !== sunset) {
|
|
932
|
+
sched.startTime = sunset;
|
|
933
|
+
anyUpdated = sUpdated = true;
|
|
934
|
+
}
|
|
935
|
+
if (sched.endTimeType === sys.board.valueMaps.scheduleTimeTypes.getValue('sunrise') && sched.endTime !== sunrise) {
|
|
936
|
+
sched.endTime = sunrise;
|
|
937
|
+
anyUpdated = sUpdated = true;
|
|
938
|
+
}
|
|
939
|
+
else if (sched.endTimeType === sys.board.valueMaps.scheduleTimeTypes.getValue('sunset') && sched.endTime !== sunset) {
|
|
940
|
+
sched.endTime = sunset;
|
|
941
|
+
anyUpdated = sUpdated = true;
|
|
942
|
+
}
|
|
943
|
+
if (sUpdated) {
|
|
944
|
+
await sys.board.schedules.setScheduleAsync({ id: sched.id });
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
return Promise.resolve(anyUpdated);
|
|
948
|
+
};
|
|
876
949
|
}
|
|
877
950
|
|
|
878
951
|
// todo: this can be implemented as a bytevaluemap
|
|
@@ -882,7 +955,7 @@ export enum TouchConfigCategories {
|
|
|
882
955
|
customNames = 10,
|
|
883
956
|
circuits = 11,
|
|
884
957
|
schedules = 17,
|
|
885
|
-
|
|
958
|
+
quickTouchRemote = 22,
|
|
886
959
|
pumpStatus = 23,
|
|
887
960
|
pumpConfig = 24,
|
|
888
961
|
intellichlor = 25,
|
|
@@ -902,7 +975,7 @@ export enum GetTouchConfigCategories {
|
|
|
902
975
|
customNames = 202,
|
|
903
976
|
circuits = 203,
|
|
904
977
|
schedules = 209,
|
|
905
|
-
|
|
978
|
+
quickTouchRemote = 214,
|
|
906
979
|
pumpStatus = 215,
|
|
907
980
|
pumpConfig = 216,
|
|
908
981
|
intellichlor = 217,
|
|
@@ -919,172 +992,172 @@ export enum GetTouchConfigCategories {
|
|
|
919
992
|
}
|
|
920
993
|
class TouchSystemCommands extends SystemCommands {
|
|
921
994
|
public async cancelDelay() {
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
995
|
+
try {
|
|
996
|
+
if (sl.enabled) {
|
|
997
|
+
await sl.bodies.cancelDalayAsync();
|
|
998
|
+
}
|
|
999
|
+
else {
|
|
1000
|
+
let out = Outbound.create({
|
|
1001
|
+
action: 131,
|
|
1002
|
+
payload: [0],
|
|
1003
|
+
retries: 0,
|
|
1004
|
+
response: true
|
|
1005
|
+
});
|
|
1006
|
+
await out.sendAsync();
|
|
1007
|
+
}
|
|
1008
|
+
state.delay = sys.board.valueMaps.delay.getValue('nodelay');
|
|
1009
|
+
return state.data.delay;
|
|
1010
|
+
}
|
|
1011
|
+
catch (err) {
|
|
1012
|
+
return Promise.reject(err);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
public async setDateTimeAsync(obj: any, send: boolean = true): Promise<any> {
|
|
1016
|
+
|
|
1017
|
+
let dst = sys.general.options.adjustDST ? 1 : 0;
|
|
1018
|
+
if (typeof obj.dst !== 'undefined') utils.makeBool(obj.dst) ? dst = 1 : dst = 0;
|
|
1019
|
+
let { hour = state.time.hours,
|
|
1020
|
+
min = state.time.minutes,
|
|
1021
|
+
date = state.time.date,
|
|
1022
|
+
month = state.time.month,
|
|
1023
|
+
year = state.time.year >= 100 ? state.time.year - 2000 : state.time.year,
|
|
1024
|
+
dow = Timestamp.dayOfWeek(state.time) } = obj;
|
|
1025
|
+
if (obj.dt instanceof Date) {
|
|
1026
|
+
let _dt: Date = obj.dt;
|
|
1027
|
+
hour = _dt.getHours();
|
|
1028
|
+
min = _dt.getMinutes();
|
|
1029
|
+
date = _dt.getDate();
|
|
1030
|
+
month = _dt.getMonth() + 1;
|
|
1031
|
+
year = _dt.getFullYear() - 2000;
|
|
1032
|
+
let dates = sys.board.valueMaps.scheduleDays.toArray();
|
|
1033
|
+
dates.forEach(d => {
|
|
1034
|
+
if (d.dow === _dt.getDay()) dow = d.val;
|
|
1035
|
+
})
|
|
1036
|
+
}
|
|
1037
|
+
if (obj.clockSource === 'manual' || obj.clockSource === 'server') sys.general.options.clockSource = obj.clockSource;
|
|
1038
|
+
// dow= day of week as expressed as [0=Sunday, 1=Monday, 2=Tuesday, 4=Wednesday, 8=Thursday, 16=Friday, 32=Saturday]
|
|
1039
|
+
// and DST = 0(manually adjst for DST) or 1(automatically adjust DST)
|
|
1040
|
+
// [165,33,16,34,133,8],[13,10,16,29,8,19,0,0],[1,228]
|
|
1041
|
+
// [165,33,34,16,1,1],[133],[1,127]
|
|
1042
|
+
const out = Outbound.create({
|
|
1043
|
+
source: Message.pluginAddress,
|
|
1044
|
+
dest: 16,
|
|
1045
|
+
action: 133,
|
|
1046
|
+
payload: [hour, min, dow, date, month, year, 0, dst],
|
|
1047
|
+
retries: 3,
|
|
1048
|
+
response: true
|
|
940
1049
|
});
|
|
1050
|
+
try {
|
|
1051
|
+
if (sl.enabled && send) {
|
|
1052
|
+
await sl.controller.setSystemTime();
|
|
1053
|
+
}
|
|
1054
|
+
else if (send) await out.sendAsync();
|
|
1055
|
+
state.time.hours = hour;
|
|
1056
|
+
state.time.minutes = min;
|
|
1057
|
+
state.time.date = date;
|
|
1058
|
+
state.time.month = month;
|
|
1059
|
+
state.time.year = year;
|
|
1060
|
+
if (sys.general.options.clockSource !== 'server' || typeof sys.general.options.adjustDST === 'undefined') sys.general.options.adjustDST = dst === 1 ? true : false;
|
|
1061
|
+
sys.board.system.setTZ();
|
|
1062
|
+
return {
|
|
1063
|
+
time: state.time.format(),
|
|
1064
|
+
adjustDST: sys.general.options.adjustDST,
|
|
1065
|
+
clockSource: sys.general.options.clockSource
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
catch (err) {
|
|
1069
|
+
return Promise.reject(err);
|
|
1070
|
+
}
|
|
941
1071
|
}
|
|
942
|
-
public async
|
|
943
|
-
let
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
if (typeof obj.dst !== 'undefined') utils.makeBool(obj.dst) ? dst = 1 : dst = 0;
|
|
953
|
-
let { hour = state.time.hours,
|
|
954
|
-
min = state.time.minutes,
|
|
955
|
-
date = state.time.date,
|
|
956
|
-
month = state.time.month,
|
|
957
|
-
year = state.time.year >= 100 ? state.time.year - 2000 : state.time.year,
|
|
958
|
-
dow = dayOfWeek() } = obj;
|
|
959
|
-
if (obj.dt instanceof Date) {
|
|
960
|
-
let _dt: Date = obj.dt;
|
|
961
|
-
hour = _dt.getHours();
|
|
962
|
-
min = _dt.getMinutes();
|
|
963
|
-
date = _dt.getDate();
|
|
964
|
-
month = _dt.getMonth() + 1;
|
|
965
|
-
year = _dt.getFullYear() - 2000;
|
|
966
|
-
let dates = sys.board.valueMaps.scheduleDays.toArray();
|
|
967
|
-
dates.forEach(d => {
|
|
968
|
-
if (d.dow === _dt.getDay()) dow = d.val;
|
|
969
|
-
})
|
|
1072
|
+
public async setCustomNameAsync(data: any, send: boolean = true): Promise<CustomName> {
|
|
1073
|
+
let id = parseInt(data.id, 10);
|
|
1074
|
+
if (isNaN(id)) throw new InvalidEquipmentIdError('Invalid Custom Name Id', data.id, 'customName');
|
|
1075
|
+
if (id > sys.equipment.maxCustomNames) throw new InvalidEquipmentIdError('Custom Name Id out of range', data.id, 'customName');
|
|
1076
|
+
let cname = sys.customNames.getItemById(id);
|
|
1077
|
+
// No need to make any changes. Just return.
|
|
1078
|
+
if (cname.name === data.name) return cname;
|
|
1079
|
+
try {
|
|
1080
|
+
if (sl.enabled && send) {
|
|
1081
|
+
await sl.controller.setCustomName(id, data.name);
|
|
970
1082
|
}
|
|
971
|
-
if (
|
|
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
|
-
}
|
|
1083
|
+
else if (send) {
|
|
1084
|
+
let out = Outbound.create({
|
|
1085
|
+
action: 138,
|
|
1086
|
+
payload: [data.id],
|
|
1087
|
+
response: true,
|
|
1088
|
+
retries: 3
|
|
1089
|
+
});
|
|
1090
|
+
out.appendPayloadString(data.name, 11);
|
|
1091
|
+
await out.sendAsync();
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
let c = sys.customNames.getItemById(id, true);
|
|
1095
|
+
c.name = data.name;
|
|
1096
|
+
|
|
1097
|
+
sys.board.system.syncCustomNamesValueMap();
|
|
1098
|
+
sys.emitEquipmentChange();
|
|
1099
|
+
for (let i = 0; i < sys.circuits.length; i++) {
|
|
1100
|
+
let circ = sys.circuits.getItemByIndex(i);
|
|
1101
|
+
if (circ.nameId === data.id + 200) {
|
|
1102
|
+
let cstate = state.circuits.getItemById(circ.id);
|
|
1103
|
+
cstate.name = circ.name = data.name;
|
|
1104
|
+
for (let j = 0; j < state.schedules.length; j++) {
|
|
1105
|
+
let ssched = state.schedules.getItemByIndex(j);
|
|
1106
|
+
if (ssched.circuit === cstate.id) {
|
|
1107
|
+
ssched.hasChanged = true;
|
|
1108
|
+
ssched.emitEquipmentChange();
|
|
1109
|
+
}
|
|
998
1110
|
}
|
|
999
1111
|
}
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
if (cname.name === data.name) return resolve(cname);
|
|
1012
|
-
let out = Outbound.create({
|
|
1013
|
-
action: 138,
|
|
1014
|
-
payload: [data.id],
|
|
1015
|
-
response: true,
|
|
1016
|
-
retries: 3,
|
|
1017
|
-
onComplete: (err) => {
|
|
1018
|
-
if (err) reject(err);
|
|
1019
|
-
else {
|
|
1020
|
-
let c = sys.customNames.getItemById(id, true);
|
|
1021
|
-
c.name = data.name;
|
|
1022
|
-
resolve(c);
|
|
1023
|
-
sys.board.system.syncCustomNamesValueMap();
|
|
1024
|
-
sys.emitEquipmentChange();
|
|
1025
|
-
for (let i = 0; i < sys.circuits.length; i++) {
|
|
1026
|
-
let circ = sys.circuits.getItemByIndex(i);
|
|
1027
|
-
if (circ.nameId === data.id + 200) {
|
|
1028
|
-
let cstate = state.circuits.getItemById(circ.id);
|
|
1029
|
-
cstate.name = circ.name = data.name;
|
|
1030
|
-
for (let j = 0; j < state.schedules.length; j++) {
|
|
1031
|
-
let ssched = state.schedules.getItemByIndex(j);
|
|
1032
|
-
if (ssched.circuit === cstate.id) {
|
|
1033
|
-
ssched.hasChanged = true;
|
|
1034
|
-
ssched.emitEquipmentChange();
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
for (let i = 0; i < sys.circuitGroups.length; i++) {
|
|
1040
|
-
let cg = sys.circuitGroups.getItemByIndex(i);
|
|
1041
|
-
if (cg.nameId === data.id + 200) {
|
|
1042
|
-
let cgstate = state.circuitGroups.getItemById(cg.id);
|
|
1043
|
-
cgstate.name = cg.name = data.name;
|
|
1044
|
-
for (let j = 0; j < state.schedules.length; j++) {
|
|
1045
|
-
let ssched = state.schedules.getItemByIndex(j);
|
|
1046
|
-
if (ssched.circuit === cgstate.id) {
|
|
1047
|
-
ssched.hasChanged = true;
|
|
1048
|
-
ssched.emitEquipmentChange();
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1112
|
+
}
|
|
1113
|
+
for (let i = 0; i < sys.circuitGroups.length; i++) {
|
|
1114
|
+
let cg = sys.circuitGroups.getItemByIndex(i);
|
|
1115
|
+
if (cg.nameId === data.id + 200) {
|
|
1116
|
+
let cgstate = state.circuitGroups.getItemById(cg.id);
|
|
1117
|
+
cgstate.name = cg.name = data.name;
|
|
1118
|
+
for (let j = 0; j < state.schedules.length; j++) {
|
|
1119
|
+
let ssched = state.schedules.getItemByIndex(j);
|
|
1120
|
+
if (ssched.circuit === cgstate.id) {
|
|
1121
|
+
ssched.hasChanged = true;
|
|
1122
|
+
ssched.emitEquipmentChange();
|
|
1052
1123
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
for (let i = 0; i < sys.lightGroups.length; i++) {
|
|
1128
|
+
let lg = sys.lightGroups.getItemByIndex(i);
|
|
1129
|
+
if (lg.nameId === data.id + 200) {
|
|
1130
|
+
let lgstate = state.lightGroups.getItemById(lg.id);
|
|
1131
|
+
lgstate.name = lg.name = data.name;
|
|
1132
|
+
for (let j = 0; j < state.schedules.length; j++) {
|
|
1133
|
+
let ssched = state.schedules.getItemByIndex(j);
|
|
1134
|
+
if (ssched.circuit === lgstate.id) {
|
|
1135
|
+
ssched.hasChanged = true;
|
|
1136
|
+
ssched.emitEquipmentChange();
|
|
1066
1137
|
}
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
for (let i = 0; i < sys.features.length; i++) {
|
|
1142
|
+
let f = sys.features.getItemByIndex(i);
|
|
1143
|
+
if (f.nameId === data.id + 200) {
|
|
1144
|
+
let fstate = state.features.getItemById(f.id);
|
|
1145
|
+
fstate.name = f.name = data.name;
|
|
1146
|
+
for (let j = 0; j < state.schedules.length; j++) {
|
|
1147
|
+
let ssched = state.schedules.getItemByIndex(j);
|
|
1148
|
+
if (ssched.circuit === fstate.id) {
|
|
1149
|
+
ssched.hasChanged = true;
|
|
1150
|
+
ssched.emitEquipmentChange();
|
|
1080
1151
|
}
|
|
1081
|
-
state.emitEquipmentChanges();
|
|
1082
1152
|
}
|
|
1083
1153
|
}
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
})
|
|
1154
|
+
}
|
|
1155
|
+
state.emitEquipmentChanges();
|
|
1156
|
+
return c;
|
|
1157
|
+
} catch (err) {
|
|
1158
|
+
return Promise.reject(err);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1088
1161
|
}
|
|
1089
1162
|
public async setOptionsAsync(obj: any): Promise<Options> {
|
|
1090
1163
|
// Proxy for setBodyAsync. See below for explanation.
|
|
@@ -1110,266 +1183,259 @@ class TouchBodyCommands extends BodyCommands {
|
|
|
1110
1183
|
// We also need to return the proper body setting manual heat, but it is irrelevant
|
|
1111
1184
|
// for when we are returning to chemController
|
|
1112
1185
|
try {
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1186
|
+
let manualHeat = sys.general.options.manualHeat;
|
|
1187
|
+
let manualPriority = sys.general.options.manualPriority;
|
|
1188
|
+
if (typeof obj.manualHeat !== 'undefined') manualHeat = utils.makeBool(obj.manualHeat);
|
|
1189
|
+
if (typeof obj.manualPriority !== 'undefined') manualPriority = utils.makeBool(obj.manualPriority);
|
|
1190
|
+
let body = sys.bodies.getItemById(obj.id, false);
|
|
1191
|
+
let intellichemInstalled = sys.chemControllers.getItemByAddress(144, false).isActive;
|
|
1192
|
+
if (sl.enabled) {
|
|
1193
|
+
await sl.controller.setEquipmentAsync({manualHeat, intellichem: intellichemInstalled}, 'misc');
|
|
1194
|
+
}
|
|
1195
|
+
else {
|
|
1120
1196
|
let out = Outbound.create({
|
|
1121
1197
|
dest: 16,
|
|
1122
1198
|
action: 168,
|
|
1123
1199
|
retries: 3,
|
|
1124
1200
|
response: true,
|
|
1125
|
-
onComplete: (err, msg) => {
|
|
1126
|
-
if (err) reject(err);
|
|
1127
|
-
else {
|
|
1128
|
-
sys.general.options.manualHeat = manualHeat;
|
|
1129
|
-
sys.general.options.manualPriority = manualPriority;
|
|
1130
|
-
let sbody = state.temps.bodies.getItemById(body.id, true);
|
|
1131
|
-
if (body.type === 1){ // spa
|
|
1132
|
-
body.manualHeat = manualHeat;
|
|
1133
|
-
};
|
|
1134
|
-
if (typeof obj.name !== 'undefined') body.name = sbody.name = obj.name;
|
|
1135
|
-
if (typeof obj.capacity !== 'undefined') body.capacity = parseInt(obj.capacity, 10);
|
|
1136
|
-
if (typeof obj.showInDashboard !== 'undefined') body.showInDashboard = sbody.showInDashboard = utils.makeBool(obj.showInDashboard);
|
|
1137
|
-
state.emitEquipmentChanges();
|
|
1138
|
-
resolve(body);
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
1201
|
});
|
|
1142
1202
|
out.insertPayloadBytes(0, 0, 9);
|
|
1143
1203
|
out.setPayloadByte(3, intellichemInstalled ? 255 : 254);
|
|
1144
1204
|
out.setPayloadByte(4, manualHeat ? 1 : 0);
|
|
1145
1205
|
out.setPayloadByte(5, manualPriority ? 1 : 0);
|
|
1146
|
-
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1206
|
+
await out.sendAsync();
|
|
1207
|
+
}
|
|
1208
|
+
sys.general.options.manualHeat = manualHeat;
|
|
1209
|
+
sys.general.options.manualPriority = manualPriority;
|
|
1210
|
+
let sbody = state.temps.bodies.getItemById(body.id, true);
|
|
1211
|
+
if (body.type === 1) { // spa
|
|
1212
|
+
body.manualHeat = manualHeat;
|
|
1213
|
+
};
|
|
1214
|
+
if (typeof obj.name !== 'undefined') body.name = sbody.name = obj.name;
|
|
1215
|
+
if (typeof obj.capacity !== 'undefined') body.capacity = parseInt(obj.capacity, 10);
|
|
1216
|
+
if (typeof obj.showInDashboard !== 'undefined') body.showInDashboard = sbody.showInDashboard = utils.makeBool(obj.showInDashboard);
|
|
1217
|
+
state.emitEquipmentChanges();
|
|
1218
|
+
return body;
|
|
1149
1219
|
}
|
|
1150
1220
|
catch (err) { return Promise.reject(err); }
|
|
1151
1221
|
}
|
|
1152
1222
|
public async setHeatModeAsync(body: Body, mode: number): Promise<BodyTempState> {
|
|
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
|
-
response: true,
|
|
1184
|
-
onComplete: (err, msg) => {
|
|
1185
|
-
if (err) reject(err);
|
|
1186
|
-
body.heatMode = mode;
|
|
1187
|
-
let bstate = state.temps.bodies.getItemById(body.id);
|
|
1188
|
-
bstate.heatMode = mode;
|
|
1189
|
-
state.emitEquipmentChanges();
|
|
1190
|
-
resolve(bstate);
|
|
1191
|
-
}
|
|
1192
|
-
});
|
|
1193
|
-
conn.queueSendMessage(out);
|
|
1223
|
+
// [16,34,136,4],[POOL HEAT Temp,SPA HEAT Temp,Heat Mode,0,2,56]
|
|
1224
|
+
// [85, 97, 7, 0]
|
|
1225
|
+
// byte | val |
|
|
1226
|
+
// 0 | 85 | Pool Setpoint
|
|
1227
|
+
// 1 | 97 | Spa setpoint
|
|
1228
|
+
// 2 | 7 | Pool/spa heat modes (01 = Heater spa 11 = Solar Only pool)
|
|
1229
|
+
// 3 | 0 | Cool set point for ultratemp
|
|
1230
|
+
|
|
1231
|
+
|
|
1232
|
+
// Heat modes
|
|
1233
|
+
// 0 = Off
|
|
1234
|
+
// 1 = Heater
|
|
1235
|
+
// 2 = Solar/Heatpump Pref
|
|
1236
|
+
// 3 = Solar
|
|
1237
|
+
//
|
|
1238
|
+
|
|
1239
|
+
const body1 = sys.bodies.getItemById(1);
|
|
1240
|
+
const body2 = sys.bodies.getItemById(2);
|
|
1241
|
+
const temp1 = body1.setPoint || 100;
|
|
1242
|
+
const temp2 = body2.setPoint || 100;
|
|
1243
|
+
let cool = body1.coolSetpoint || 0;
|
|
1244
|
+
let mode1 = body1.heatMode;
|
|
1245
|
+
let mode2 = body2.heatMode;
|
|
1246
|
+
body.id === 1 ? mode1 = mode : mode2 = mode;
|
|
1247
|
+
let out = Outbound.create({
|
|
1248
|
+
dest: 16,
|
|
1249
|
+
action: 136,
|
|
1250
|
+
payload: [temp1, temp2, mode2 << 2 | mode1, cool],
|
|
1251
|
+
retries: 3,
|
|
1252
|
+
response: true
|
|
1194
1253
|
});
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
let setPoint = typeof obj.setPoint !== 'undefined' ? parseInt(obj.setPoint, 10) : parseInt(obj.heatSetpoint, 10);
|
|
1199
|
-
let coolSetPoint = typeof obj.coolSetPoint !== 'undefined' ? parseInt(obj.coolSetPoint, 10) : 0;
|
|
1200
|
-
if (isNaN(setPoint)) return Promise.reject(new InvalidEquipmentDataError(`Invalid ${body.name} setpoint ${obj.setPoint || obj.heatSetpoint}`, 'body', obj));
|
|
1201
|
-
// [16,34,136,4],[POOL HEAT Temp,SPA HEAT Temp,Heat Mode,0,2,56]
|
|
1202
|
-
// 165,33,16,34,136,4,89,99,7,0,2,71 Request
|
|
1203
|
-
// 165,33,34,16,1,1,136,1,130 Controller Response
|
|
1204
|
-
const tempUnits = state.temps.units;
|
|
1205
|
-
switch (tempUnits) {
|
|
1206
|
-
case 0: // fahrenheit
|
|
1207
|
-
{
|
|
1208
|
-
if (setPoint < 40 || setPoint > 104) {
|
|
1209
|
-
logger.warn(`Setpoint of ${setPoint} is outside acceptable range.`);
|
|
1210
|
-
}
|
|
1211
|
-
if (coolSetPoint < 40 || coolSetPoint > 104) {
|
|
1212
|
-
logger.warn(`Cool Setpoint of ${setPoint} is outside acceptable range.`);
|
|
1213
|
-
return;
|
|
1214
|
-
}
|
|
1215
|
-
break;
|
|
1216
|
-
}
|
|
1217
|
-
case 1: // celsius
|
|
1218
|
-
{
|
|
1219
|
-
if (setPoint < 4 || setPoint > 40) {
|
|
1220
|
-
logger.warn(
|
|
1221
|
-
`Setpoint of ${setPoint} is outside of acceptable range.`
|
|
1222
|
-
);
|
|
1223
|
-
return;
|
|
1224
|
-
}
|
|
1225
|
-
if (coolSetPoint < 4 || coolSetPoint > 40) {
|
|
1226
|
-
logger.warn(`Cool SetPoint of ${coolSetPoint} is outside of acceptable range.`
|
|
1227
|
-
);
|
|
1228
|
-
return;
|
|
1229
|
-
}
|
|
1230
|
-
break;
|
|
1231
|
-
}
|
|
1254
|
+
try {
|
|
1255
|
+
if (sl.enabled) {
|
|
1256
|
+
await sl.bodies.setHeatModeAsync(body, mode);
|
|
1232
1257
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
let
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
payload: [temp1, temp2, mode2 << 2 | mode1, cool],
|
|
1245
|
-
retries: 3,
|
|
1246
|
-
response: true,
|
|
1247
|
-
onComplete: (err, msg) => {
|
|
1248
|
-
if (err) reject(err);
|
|
1249
|
-
body.setPoint = setPoint;
|
|
1250
|
-
let bstate = state.temps.bodies.getItemById(body.id);
|
|
1251
|
-
bstate.setPoint = setPoint;
|
|
1252
|
-
if (body.id === 1) body.coolSetpoint = bstate.coolSetpoint = cool;
|
|
1253
|
-
state.temps.emitEquipmentChange();
|
|
1254
|
-
resolve(bstate);
|
|
1255
|
-
}
|
|
1258
|
+
else {
|
|
1259
|
+
await out.sendAsync();
|
|
1260
|
+
}
|
|
1261
|
+
body.heatMode = mode;
|
|
1262
|
+
let bstate = state.temps.bodies.getItemById(body.id);
|
|
1263
|
+
bstate.heatMode = mode;
|
|
1264
|
+
state.emitEquipmentChanges();
|
|
1265
|
+
return bstate;
|
|
1266
|
+
} catch (err) {
|
|
1267
|
+
return Promise.reject(err);
|
|
1268
|
+
}
|
|
1256
1269
|
|
|
1257
|
-
});
|
|
1258
|
-
conn.queueSendMessage(out);
|
|
1259
|
-
});
|
|
1260
1270
|
}
|
|
1261
|
-
public async
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1271
|
+
public async setSetpoints(body: Body, obj: any, send: boolean = true): Promise<BodyTempState> {
|
|
1272
|
+
let setPoint = typeof obj.setPoint !== 'undefined' ? parseInt(obj.setPoint, 10) : parseInt(obj.heatSetpoint, 10);
|
|
1273
|
+
let coolSetPoint = typeof obj.coolSetPoint !== 'undefined' ? parseInt(obj.coolSetPoint, 10) : 0;
|
|
1274
|
+
if (isNaN(setPoint)) return Promise.reject(new InvalidEquipmentDataError(`Invalid ${body.name} setpoint ${obj.setPoint || obj.heatSetpoint}`, 'body', obj));
|
|
1275
|
+
// [16,34,136,4],[POOL HEAT Temp,SPA HEAT Temp,Heat Mode,0,2,56]
|
|
1276
|
+
// 165,33,16,34,136,4,89,99,7,0,2,71 Request
|
|
1277
|
+
// 165,33,34,16,1,1,136,1,130 Controller Response
|
|
1278
|
+
const tempUnits = state.temps.units;
|
|
1279
|
+
switch (tempUnits) {
|
|
1280
|
+
case 0: // fahrenheit
|
|
1281
|
+
{
|
|
1269
1282
|
if (setPoint < 40 || setPoint > 104) {
|
|
1270
1283
|
logger.warn(`Setpoint of ${setPoint} is outside acceptable range.`);
|
|
1284
|
+
}
|
|
1285
|
+
if (coolSetPoint < 40 || coolSetPoint > 104) {
|
|
1286
|
+
logger.warn(`Cool Setpoint of ${setPoint} is outside acceptable range.`);
|
|
1271
1287
|
return;
|
|
1272
1288
|
}
|
|
1273
1289
|
break;
|
|
1274
|
-
|
|
1290
|
+
}
|
|
1291
|
+
case 1: // celsius
|
|
1292
|
+
{
|
|
1275
1293
|
if (setPoint < 4 || setPoint > 40) {
|
|
1276
1294
|
logger.warn(
|
|
1277
1295
|
`Setpoint of ${setPoint} is outside of acceptable range.`
|
|
1278
1296
|
);
|
|
1279
1297
|
return;
|
|
1280
1298
|
}
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
const body1 = sys.bodies.getItemById(1);
|
|
1284
|
-
const body2 = sys.bodies.getItemById(2);
|
|
1285
|
-
let temp1 = body1.setPoint || 100;
|
|
1286
|
-
let temp2 = body2.setPoint || 100;
|
|
1287
|
-
body.id === 1 ? temp1 = setPoint : temp2 = setPoint;
|
|
1288
|
-
const mode1 = body1.heatMode || 0;
|
|
1289
|
-
const mode2 = body2.heatMode || 0;
|
|
1290
|
-
let cool = body1.coolSetpoint || (body1.setPoint + 1);
|
|
1291
|
-
const out = Outbound.create({
|
|
1292
|
-
dest: 16,
|
|
1293
|
-
action: 136,
|
|
1294
|
-
payload: [temp1, temp2, mode2 << 2 | mode1, cool],
|
|
1295
|
-
retries: 3,
|
|
1296
|
-
response: true,
|
|
1297
|
-
onComplete: (err, msg) => {
|
|
1298
|
-
if (err) reject(err);
|
|
1299
|
-
body.setPoint = setPoint;
|
|
1300
|
-
let bstate = state.temps.bodies.getItemById(body.id);
|
|
1301
|
-
bstate.setPoint = setPoint;
|
|
1302
|
-
state.temps.emitEquipmentChange();
|
|
1303
|
-
resolve(bstate);
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
});
|
|
1307
|
-
conn.queueSendMessage(out);
|
|
1308
|
-
});
|
|
1309
|
-
}
|
|
1310
|
-
public async setCoolSetpointAsync(body: Body, setPoint: number): Promise<BodyTempState> {
|
|
1311
|
-
return new Promise<BodyTempState>((resolve, reject) => {
|
|
1312
|
-
// [16,34,136,4],[POOL HEAT Temp,SPA HEAT Temp,Heat Mode,Cool,2,56]
|
|
1313
|
-
// 165,33,16,34,136,4,89,99,7,0,2,71 Request
|
|
1314
|
-
// 165,33,34,16,1,1,136,1,130 Controller Response
|
|
1315
|
-
const tempUnits = state.temps.units;
|
|
1316
|
-
switch (tempUnits) {
|
|
1317
|
-
case 0: // fahrenheit
|
|
1318
|
-
if (setPoint < 40 || setPoint > 104) {
|
|
1319
|
-
logger.warn(`Setpoint of ${setPoint} is outside acceptable range.`);
|
|
1320
|
-
return;
|
|
1321
|
-
}
|
|
1322
|
-
break;
|
|
1323
|
-
case 1: // celsius
|
|
1324
|
-
if (setPoint < 4 || setPoint > 40) {
|
|
1325
|
-
logger.warn(
|
|
1326
|
-
`Setpoint of ${setPoint} is outside of acceptable range.`
|
|
1299
|
+
if (coolSetPoint < 4 || coolSetPoint > 40) {
|
|
1300
|
+
logger.warn(`Cool SetPoint of ${coolSetPoint} is outside of acceptable range.`
|
|
1327
1301
|
);
|
|
1328
1302
|
return;
|
|
1329
1303
|
}
|
|
1330
1304
|
break;
|
|
1331
|
-
}
|
|
1332
|
-
const body1 = sys.bodies.getItemById(1);
|
|
1333
|
-
const body2 = sys.bodies.getItemById(2);
|
|
1334
|
-
let temp1 = body1.setPoint || 100;
|
|
1335
|
-
let temp2 = body2.setPoint || 100;
|
|
1336
|
-
const mode1 = body1.heatMode || 0;
|
|
1337
|
-
const mode2 = body2.heatMode || 0;
|
|
1338
|
-
const out = Outbound.create({
|
|
1339
|
-
dest: 16,
|
|
1340
|
-
action: 136,
|
|
1341
|
-
payload: [temp1, temp2, mode2 << 2 | mode1, setPoint],
|
|
1342
|
-
retries: 3,
|
|
1343
|
-
response: true,
|
|
1344
|
-
onComplete: (err, msg) => {
|
|
1345
|
-
if (err) reject(err);
|
|
1346
|
-
let bstate = state.temps.bodies.getItemById(body.id);
|
|
1347
|
-
body.coolSetpoint = bstate.coolSetpoint = setPoint;
|
|
1348
|
-
state.temps.emitEquipmentChange();
|
|
1349
|
-
resolve(bstate);
|
|
1350
1305
|
}
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1306
|
+
}
|
|
1307
|
+
const body1 = sys.bodies.getItemById(1);
|
|
1308
|
+
const body2 = sys.bodies.getItemById(2);
|
|
1309
|
+
let temp1 = body1.setPoint || tempUnits === 0 ? 40 : 4;
|
|
1310
|
+
let temp2 = body2.setPoint || tempUnits === 0 ? 40 : 4;
|
|
1311
|
+
let cool = coolSetPoint || body1.setPoint + 1;
|
|
1312
|
+
body.id === 1 ? temp1 = setPoint : temp2 = setPoint;
|
|
1313
|
+
const mode1 = body1.heatMode;
|
|
1314
|
+
const mode2 = body2.heatMode;
|
|
1315
|
+
const out = Outbound.create({
|
|
1316
|
+
dest: 16,
|
|
1317
|
+
action: 136,
|
|
1318
|
+
payload: [temp1, temp2, mode2 << 2 | mode1, cool],
|
|
1319
|
+
retries: 3,
|
|
1320
|
+
response: true
|
|
1354
1321
|
});
|
|
1355
|
-
|
|
1356
|
-
|
|
1322
|
+
try {
|
|
1323
|
+
if (sl.enabled && send) {
|
|
1324
|
+
await sl.bodies.setHeatSetpointAsync(body, setPoint);
|
|
1325
|
+
}
|
|
1326
|
+
else if (send) {
|
|
1327
|
+
await out.sendAsync();
|
|
1328
|
+
}
|
|
1329
|
+
body.setPoint = setPoint;
|
|
1330
|
+
let bstate = state.temps.bodies.getItemById(body.id);
|
|
1331
|
+
bstate.setPoint = setPoint;
|
|
1332
|
+
if (body.id === 1) body.coolSetpoint = bstate.coolSetpoint = cool;
|
|
1333
|
+
state.temps.emitEquipmentChange();
|
|
1334
|
+
return bstate;
|
|
1335
|
+
} catch (err) {
|
|
1336
|
+
return Promise.reject(err);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
public async setHeatSetpointAsync(body: Body, setPoint: number): Promise<BodyTempState> {
|
|
1340
|
+
// [16,34,136,4],[POOL HEAT Temp,SPA HEAT Temp,Heat Mode,0,2,56]
|
|
1341
|
+
// 165,33,16,34,136,4,89,99,7,0,2,71 Request
|
|
1342
|
+
// 165,33,34,16,1,1,136,1,130 Controller Response
|
|
1343
|
+
const tempUnits = state.temps.units;
|
|
1344
|
+
switch (tempUnits) {
|
|
1345
|
+
case 0: // fahrenheit
|
|
1346
|
+
if (setPoint < 40 || setPoint > 104) {
|
|
1347
|
+
logger.warn(`Setpoint of ${setPoint} is outside acceptable range.`);
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
break;
|
|
1351
|
+
case 1: // celsius
|
|
1352
|
+
if (setPoint < 4 || setPoint > 40) {
|
|
1353
|
+
logger.warn(
|
|
1354
|
+
`Setpoint of ${setPoint} is outside of acceptable range.`
|
|
1355
|
+
);
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
break;
|
|
1359
|
+
}
|
|
1360
|
+
const body1 = sys.bodies.getItemById(1);
|
|
1361
|
+
const body2 = sys.bodies.getItemById(2);
|
|
1362
|
+
let temp1 = body1.setPoint || 100;
|
|
1363
|
+
let temp2 = body2.setPoint || 100;
|
|
1364
|
+
body.id === 1 ? temp1 = setPoint : temp2 = setPoint;
|
|
1365
|
+
const mode1 = body1.heatMode || 0;
|
|
1366
|
+
const mode2 = body2.heatMode || 0;
|
|
1367
|
+
let cool = body1.coolSetpoint || (body1.setPoint + 1);
|
|
1368
|
+
const out = Outbound.create({
|
|
1369
|
+
dest: 16,
|
|
1370
|
+
action: 136,
|
|
1371
|
+
payload: [temp1, temp2, mode2 << 2 | mode1, cool],
|
|
1372
|
+
retries: 3,
|
|
1373
|
+
response: true
|
|
1374
|
+
});
|
|
1375
|
+
try {
|
|
1376
|
+
if (sl.enabled) {
|
|
1377
|
+
await sl.bodies.setHeatSetpointAsync(body, setPoint);
|
|
1378
|
+
}
|
|
1379
|
+
else {
|
|
1380
|
+
await out.sendAsync();
|
|
1381
|
+
}
|
|
1382
|
+
body.setPoint = setPoint;
|
|
1383
|
+
let bstate = state.temps.bodies.getItemById(body.id);
|
|
1384
|
+
bstate.setPoint = setPoint;
|
|
1385
|
+
state.temps.emitEquipmentChange();
|
|
1386
|
+
return bstate;
|
|
1387
|
+
} catch (err) {
|
|
1388
|
+
return Promise.reject(err);
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
public async setCoolSetpointAsync(body: Body, setPoint: number): Promise<BodyTempState> {
|
|
1392
|
+
// [16,34,136,4],[POOL HEAT Temp,SPA HEAT Temp,Heat Mode,Cool,2,56]
|
|
1393
|
+
// 165,33,16,34,136,4,89,99,7,0,2,71 Request
|
|
1394
|
+
// 165,33,34,16,1,1,136,1,130 Controller Response
|
|
1395
|
+
const tempUnits = state.temps.units;
|
|
1396
|
+
switch (tempUnits) {
|
|
1397
|
+
case 0: // fahrenheit
|
|
1398
|
+
if (setPoint < 40 || setPoint > 104) {
|
|
1399
|
+
logger.warn(`Setpoint of ${setPoint} is outside acceptable range.`);
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
break;
|
|
1403
|
+
case 1: // celsius
|
|
1404
|
+
if (setPoint < 4 || setPoint > 40) {
|
|
1405
|
+
logger.warn(
|
|
1406
|
+
`Setpoint of ${setPoint} is outside of acceptable range.`
|
|
1407
|
+
);
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
break;
|
|
1411
|
+
}
|
|
1412
|
+
const body1 = sys.bodies.getItemById(1);
|
|
1413
|
+
const body2 = sys.bodies.getItemById(2);
|
|
1414
|
+
let temp1 = body1.setPoint || 100;
|
|
1415
|
+
let temp2 = body2.setPoint || 100;
|
|
1416
|
+
const mode1 = body1.heatMode || 0;
|
|
1417
|
+
const mode2 = body2.heatMode || 0;
|
|
1418
|
+
const out = Outbound.create({
|
|
1419
|
+
dest: 16,
|
|
1420
|
+
action: 136,
|
|
1421
|
+
payload: [temp1, temp2, mode2 << 2 | mode1, setPoint],
|
|
1422
|
+
retries: 3,
|
|
1423
|
+
response: true
|
|
1424
|
+
});
|
|
1425
|
+
if (sl.enabled) {
|
|
1426
|
+
await sl.bodies.setCoolSetpointAsync(body, setPoint);
|
|
1427
|
+
}
|
|
1428
|
+
else {
|
|
1429
|
+
await out.sendAsync();
|
|
1430
|
+
}
|
|
1431
|
+
let bstate = state.temps.bodies.getItemById(body.id);
|
|
1432
|
+
body.coolSetpoint = bstate.coolSetpoint = setPoint;
|
|
1433
|
+
state.temps.emitEquipmentChange();
|
|
1434
|
+
return bstate;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1357
1437
|
export class TouchCircuitCommands extends CircuitCommands {
|
|
1358
|
-
|
|
1359
|
-
// for multiple types of standardized on/off sequences with nixie controllers.
|
|
1360
|
-
//public getLightThemes(type?: number): any[] {
|
|
1361
|
-
// let themes = sys.board.valueMaps.lightThemes.toArray();
|
|
1362
|
-
// if (typeof type === 'undefined') return themes;
|
|
1363
|
-
// switch (type) {
|
|
1364
|
-
// case 8: // Magicstream
|
|
1365
|
-
// return themes.filter(theme => theme.types.includes('magicstream'));
|
|
1366
|
-
// case 16: // Intellibrite
|
|
1367
|
-
// return themes.filter(theme => theme.types.includes('intellibrite'));
|
|
1368
|
-
// default:
|
|
1369
|
-
// return [];
|
|
1370
|
-
// }
|
|
1371
|
-
//}
|
|
1372
|
-
public async setCircuitAsync(data: any): Promise<ICircuit> {
|
|
1438
|
+
public async setCircuitAsync(data: any, send: boolean = true): Promise<ICircuit> {
|
|
1373
1439
|
try {
|
|
1374
1440
|
// example [255,0,255][165,33,16,34,139,5][17,14,209,0,0][2,120]
|
|
1375
1441
|
// set circuit 17 to function 14 and name 209
|
|
@@ -1383,53 +1449,55 @@ export class TouchCircuitCommands extends CircuitCommands {
|
|
|
1383
1449
|
let circ = await super.setCircuitAsync(data);
|
|
1384
1450
|
return circ;
|
|
1385
1451
|
}
|
|
1386
|
-
|
|
1387
|
-
let
|
|
1452
|
+
let cstate = state.circuits.getInterfaceById(data.id, true);
|
|
1453
|
+
let showInFeatures = cstate.showInFeatures = typeof data.showInFeatures !== 'undefined' ? utils.makeBool(data.showInFeatures) : circuit.showInFeatures;
|
|
1454
|
+
let typeByte = parseInt(data.type, 10) === 0 ? 0 : parseInt(data.type, 10) || circuit.type || sys.board.valueMaps.circuitFunctions.getValue('generic');
|
|
1455
|
+
let freeze = typeof data.freeze !== 'undefined' ? utils.makeBool(data.freeze) : circuit.freeze;
|
|
1388
1456
|
let nameByte = 3; // set default `Aux 1`
|
|
1389
1457
|
if (typeof data.nameId !== 'undefined') nameByte = data.nameId;
|
|
1390
1458
|
else if (typeof circuit.name !== 'undefined') nameByte = circuit.nameId;
|
|
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
|
-
|
|
1459
|
+
if (send) {
|
|
1460
|
+
if (sl.enabled) {
|
|
1461
|
+
// SL show in features = 0 = pool; 1 = spa; 2 = features; 4 = lights; 5 = hide
|
|
1462
|
+
await sl.circuits.setCircuitAsync(parseInt(data.id, 10), nameByte, typeByte, showInFeatures ? 2 : 0, freeze)
|
|
1463
|
+
}
|
|
1464
|
+
else {
|
|
1465
|
+
let out = Outbound.create({
|
|
1466
|
+
action: 139,
|
|
1467
|
+
payload: [parseInt(data.id, 10), typeByte | (utils.makeBool(data.freeze) ? 64 : 0), nameByte, 0, 0],
|
|
1468
|
+
retries: 3,
|
|
1469
|
+
response: true
|
|
1470
|
+
});
|
|
1471
|
+
await out.sendAsync();
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
circuit = sys.circuits.getInterfaceById(data.id, true);
|
|
1475
|
+
circuit.nameId = cstate.nameId = nameByte;
|
|
1476
|
+
circuit.name = cstate.name = sys.board.valueMaps.circuitNames.transform(nameByte).desc;
|
|
1477
|
+
circuit.showInFeatures = showInFeatures;
|
|
1478
|
+
circuit.freeze = freeze;
|
|
1479
|
+
circuit.type = cstate.type = typeByte;
|
|
1480
|
+
circuit.eggTimer = typeof data.eggTimer !== 'undefined' ? parseInt(data.eggTimer, 10) : circuit.eggTimer || 720;
|
|
1481
|
+
circuit.dontStop = (typeof data.dontStop !== 'undefined') ? utils.makeBool(data.dontStop) : circuit.eggTimer === 1620;
|
|
1482
|
+
cstate.isActive = circuit.isActive = true;
|
|
1483
|
+
circuit.master = 0;
|
|
1484
|
+
let eggTimer = sys.eggTimers.find(elem => elem.circuit === parseInt(data.id, 10));
|
|
1485
|
+
try {
|
|
1486
|
+
if (circuit.eggTimer === 720) {
|
|
1487
|
+
if (typeof eggTimer !== 'undefined') await sys.board.schedules.deleteEggTimerAsync({ id: eggTimer.id });
|
|
1488
|
+
}
|
|
1489
|
+
else {
|
|
1490
|
+
await sys.board.schedules.setEggTimerAsync({ id: typeof eggTimer !== 'undefined' ? eggTimer.id : -1, runTime: circuit.eggTimer, dontStop: circuit.dontStop, circuit: circuit.id });
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
catch (err) {
|
|
1494
|
+
// fail silently if there are no slots to fill in the schedules
|
|
1495
|
+
logger.info(`Cannot set/delete eggtimer on circuit ${circuit.id}. Error: ${err.message}`);
|
|
1496
|
+
circuit.eggTimer = 720;
|
|
1497
|
+
circuit.dontStop = false;
|
|
1498
|
+
}
|
|
1499
|
+
state.emitEquipmentChanges();
|
|
1500
|
+
return circuit;
|
|
1433
1501
|
}
|
|
1434
1502
|
catch (err) { logger.error(`setCircuitAsync error setting circuit ${JSON.stringify(data)}: ${err}`); return Promise.reject(err); }
|
|
1435
1503
|
}
|
|
@@ -1455,27 +1523,24 @@ export class TouchCircuitCommands extends CircuitCommands {
|
|
|
1455
1523
|
id = 1;
|
|
1456
1524
|
val = false;
|
|
1457
1525
|
}
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
scope: `circuitState${id}`,
|
|
1466
|
-
onComplete: (err, msg) => {
|
|
1467
|
-
if (err) reject(err);
|
|
1468
|
-
else {
|
|
1469
|
-
sys.board.circuits.setEndTime(c, cstate, val);
|
|
1470
|
-
cstate.isOn = val;
|
|
1471
|
-
state.emitEquipmentChanges();
|
|
1472
|
-
resolve(cstate);
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
});
|
|
1476
|
-
conn.queueSendMessage(out);
|
|
1526
|
+
let cstate = state.circuits.getInterfaceById(id);
|
|
1527
|
+
let out = Outbound.create({
|
|
1528
|
+
action: 134,
|
|
1529
|
+
payload: [id, val ? 1 : 0],
|
|
1530
|
+
retries: 3,
|
|
1531
|
+
response: true,
|
|
1532
|
+
scope: `circuitState${id}`
|
|
1477
1533
|
});
|
|
1478
|
-
|
|
1534
|
+
if (sl.enabled) {
|
|
1535
|
+
await sl.circuits.setCircuitStateAsync(id, val);
|
|
1536
|
+
}
|
|
1537
|
+
else {
|
|
1538
|
+
await out.sendAsync();
|
|
1539
|
+
}
|
|
1540
|
+
sys.board.circuits.setEndTime(c, cstate, val);
|
|
1541
|
+
cstate.isOn = val;
|
|
1542
|
+
state.emitEquipmentChanges();
|
|
1543
|
+
return cstate;
|
|
1479
1544
|
}
|
|
1480
1545
|
public async setLightGroupStateAsync(id: number, val: boolean): Promise<ICircuitGroupState> { return this.setCircuitGroupStateAsync(id, val); }
|
|
1481
1546
|
public async toggleCircuitStateAsync(id: number) {
|
|
@@ -1485,147 +1550,117 @@ export class TouchCircuitCommands extends CircuitCommands {
|
|
|
1485
1550
|
}
|
|
1486
1551
|
return await this.setCircuitStateAsync(id, !cstate.isOn);
|
|
1487
1552
|
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1553
|
+
|
|
1554
|
+
public async setLightGroupAsync(obj: any, send: boolean = true): Promise<LightGroup> {
|
|
1555
|
+
try {
|
|
1556
|
+
let group: LightGroup = null;
|
|
1557
|
+
let id = typeof obj.id !== 'undefined' ? parseInt(obj.id, 10) : -1;
|
|
1558
|
+
if (id <= 0) {
|
|
1559
|
+
// We are adding a circuit group.
|
|
1560
|
+
id = sys.circuitGroups.getNextEquipmentId(sys.board.equipmentIds.circuitGroups);
|
|
1561
|
+
}
|
|
1562
|
+
if (typeof id === 'undefined') return Promise.reject(new InvalidEquipmentIdError(`Max circuit light group id exceeded`, id, 'LightGroup'));
|
|
1563
|
+
if (isNaN(id) || !sys.board.equipmentIds.circuitGroups.isInRange(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid circuit group id: ${obj.id}`, obj.id, 'LightGroup'));
|
|
1564
|
+
group = sys.lightGroups.getItemById(id, true);
|
|
1565
|
+
|
|
1566
|
+
if (typeof obj.name !== 'undefined') group.name = obj.name;
|
|
1567
|
+
if (typeof obj.eggTimer !== 'undefined') group.eggTimer = Math.min(Math.max(parseInt(obj.eggTimer, 10), 0), 1440); // this isn't an *Touch thing, so need to figure out if we can handle it some other way
|
|
1568
|
+
group.dontStop = (group.eggTimer === 1440);
|
|
1569
|
+
group.isActive = true;
|
|
1570
|
+
group.type = 3; // intellibrite
|
|
1571
|
+
if (typeof obj.circuits !== 'undefined') {
|
|
1572
|
+
for (let i = 0; i < obj.circuits.length; i++) {
|
|
1573
|
+
let cobj = obj.circuits[i];
|
|
1574
|
+
let c: LightGroupCircuit;
|
|
1575
|
+
if (typeof cobj.id !== 'undefined') c = group.circuits.getItemById(parseInt(cobj.id, 10), true);
|
|
1576
|
+
else if (typeof cobj.circuit !== 'undefined') c = group.circuits.getItemByCircuitId(parseInt(cobj.circuit, 10), true);
|
|
1577
|
+
else c = group.circuits.getItemByIndex(i, true, { id: i + 1 });
|
|
1578
|
+
if (typeof cobj.circuit !== 'undefined') c.circuit = cobj.circuit;
|
|
1579
|
+
//if (typeof cobj.lightingTheme !== 'undefined') c.lightingTheme = parseInt(cobj.lightingTheme, 10); // does this belong here?
|
|
1580
|
+
if (typeof cobj.color !== 'undefined') c.color = parseInt(cobj.color, 10);
|
|
1581
|
+
if (typeof cobj.swimDelay !== 'undefined') c.swimDelay = parseInt(cobj.swimDelay, 10);
|
|
1582
|
+
if (typeof cobj.position !== 'undefined') c.position = parseInt(cobj.position, 10);
|
|
1583
|
+
}
|
|
1584
|
+
// group.circuits.length = obj.circuits.length;
|
|
1585
|
+
}
|
|
1586
|
+
if (sl.enabled && send) {
|
|
1587
|
+
await sl.controller.setEquipmentAsync(obj,'lightGroup');
|
|
1588
|
+
}
|
|
1589
|
+
else if (send) {
|
|
1590
|
+
if (sys.equipment.maxIntelliBrites === 8) {
|
|
1591
|
+
// Easytouch
|
|
1592
|
+
|
|
1593
|
+
let out = Outbound.create({
|
|
1594
|
+
action: 167,
|
|
1595
|
+
retries: 3,
|
|
1596
|
+
response: true
|
|
1597
|
+
});
|
|
1598
|
+
const lgcircuits = group.circuits.get();
|
|
1599
|
+
for (let circ = 0; circ < 8; circ++) {
|
|
1600
|
+
const lgcirc = lgcircuits[circ];
|
|
1601
|
+
if (typeof lgcirc === 'undefined') out.payload.push(0, 0, 0, 0);
|
|
1500
1602
|
else {
|
|
1501
|
-
|
|
1603
|
+
out.payload.push(lgcirc.circuit);
|
|
1604
|
+
out.payload.push(((lgcirc.position - 1) << 4) + lgcirc.color);
|
|
1605
|
+
out.payload.push(lgcirc.swimDelay << 1);
|
|
1606
|
+
out.payload.push(0);
|
|
1502
1607
|
}
|
|
1503
1608
|
}
|
|
1504
|
-
|
|
1505
|
-
const lgcircuits = group.circuits.get();
|
|
1506
|
-
for (let circ = 0; circ < 8; circ++) {
|
|
1507
|
-
const lgcirc = lgcircuits[circ];
|
|
1508
|
-
if (typeof lgcirc === 'undefined') out.payload.push(0, 0, 0, 0);
|
|
1509
|
-
else {
|
|
1510
|
-
out.payload.push(lgcirc.circuit);
|
|
1511
|
-
out.payload.push(((lgcirc.position - 1) << 4) + lgcirc.color);
|
|
1512
|
-
out.payload.push(lgcirc.swimDelay << 1);
|
|
1513
|
-
out.payload.push(0);
|
|
1514
|
-
}
|
|
1609
|
+
await out.sendAsync();
|
|
1515
1610
|
}
|
|
1516
|
-
|
|
1517
|
-
|
|
1611
|
+
else {
|
|
1612
|
+
// Intellitouch
|
|
1613
|
+
const lgcircuits = group.circuits.get();
|
|
1614
|
+
if (send) {
|
|
1518
1615
|
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1616
|
+
let out = Outbound.create({
|
|
1617
|
+
action: 167,
|
|
1618
|
+
retries: 3,
|
|
1619
|
+
payload: [1],
|
|
1620
|
+
response: true
|
|
1621
|
+
});
|
|
1622
|
+
for (let circ = 0; circ < 5; circ++) {
|
|
1623
|
+
const lgcirc = lgcircuits[circ];
|
|
1624
|
+
if (typeof lgcirc === 'undefined') out.payload.push.apply([0, 0, 0, 0]);
|
|
1625
|
+
else {
|
|
1626
|
+
out.payload.push(lgcirc.id);
|
|
1627
|
+
out.payload.push(((lgcirc.position - 1) << 4) + lgcirc.color);
|
|
1628
|
+
out.payload.push(lgcirc.swimDelay << 1);
|
|
1629
|
+
out.payload.push(0);
|
|
1630
|
+
}
|
|
1533
1631
|
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1632
|
+
await out.sendAsync();
|
|
1633
|
+
|
|
1634
|
+
out = Outbound.create({
|
|
1635
|
+
action: 167,
|
|
1636
|
+
retries: 3,
|
|
1637
|
+
payload: [2],
|
|
1638
|
+
response: true
|
|
1639
|
+
});
|
|
1640
|
+
for (let circ = 5; circ < 10; circ++) {
|
|
1641
|
+
const lgcirc = lgcircuits[circ];
|
|
1642
|
+
if (typeof lgcirc === 'undefined') out.payload.push.apply([0, 0, 0, 0]);
|
|
1643
|
+
else {
|
|
1644
|
+
out.payload.push(lgcirc.id);
|
|
1645
|
+
out.payload.push(((lgcirc.position - 1) << 4) + lgcirc.color);
|
|
1646
|
+
out.payload.push(lgcirc.swimDelay << 1);
|
|
1647
|
+
out.payload.push(0);
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
out.sendAsync();
|
|
1651
|
+
|
|
1544
1652
|
}
|
|
1545
1653
|
}
|
|
1546
|
-
conn.queueSendMessage(out);
|
|
1547
|
-
}));
|
|
1548
|
-
packets.push(new Promise(function (resolve, reject) {
|
|
1549
1654
|
let out = Outbound.create({
|
|
1550
|
-
action:
|
|
1551
|
-
|
|
1552
|
-
payload: [2],
|
|
1553
|
-
response: true,
|
|
1554
|
-
onComplete: (err, msg) => {
|
|
1555
|
-
if (err) return Promise.reject(err);
|
|
1556
|
-
else {
|
|
1557
|
-
return Promise.resolve();
|
|
1558
|
-
}
|
|
1559
|
-
}
|
|
1655
|
+
action: 231,
|
|
1656
|
+
payload: [0]
|
|
1560
1657
|
});
|
|
1561
|
-
|
|
1562
|
-
const lgcirc = lgcircuits[circ];
|
|
1563
|
-
if (typeof lgcirc === 'undefined') out.payload.push.apply([0, 0, 0, 0]);
|
|
1564
|
-
else {
|
|
1565
|
-
out.payload.push(lgcirc.id);
|
|
1566
|
-
out.payload.push(((lgcirc.position - 1) << 4) + lgcirc.color);
|
|
1567
|
-
out.payload.push(lgcirc.swimDelay << 1);
|
|
1568
|
-
out.payload.push(0);
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
|
-
conn.queueSendMessage(out);
|
|
1572
|
-
}));
|
|
1573
|
-
}
|
|
1574
|
-
return packets;
|
|
1575
|
-
}
|
|
1576
|
-
public async setLightGroupAsync(obj: any): Promise<LightGroup> {
|
|
1577
|
-
let group: LightGroup = null;
|
|
1578
|
-
let id = typeof obj.id !== 'undefined' ? parseInt(obj.id, 10) : -1;
|
|
1579
|
-
if (id <= 0) {
|
|
1580
|
-
// We are adding a circuit group.
|
|
1581
|
-
id = sys.circuitGroups.getNextEquipmentId(sys.board.equipmentIds.circuitGroups);
|
|
1582
|
-
}
|
|
1583
|
-
if (typeof id === 'undefined') return Promise.reject(new InvalidEquipmentIdError(`Max circuit light group id exceeded`, id, 'LightGroup'));
|
|
1584
|
-
if (isNaN(id) || !sys.board.equipmentIds.circuitGroups.isInRange(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid circuit group id: ${obj.id}`, obj.id, 'LightGroup'));
|
|
1585
|
-
group = sys.lightGroups.getItemById(id, true);
|
|
1586
|
-
|
|
1587
|
-
if (typeof obj.name !== 'undefined') group.name = obj.name;
|
|
1588
|
-
if (typeof obj.eggTimer !== 'undefined') group.eggTimer = Math.min(Math.max(parseInt(obj.eggTimer, 10), 0), 1440); // this isn't an *Touch thing, so need to figure out if we can handle it some other way
|
|
1589
|
-
group.dontStop = (group.eggTimer === 1440);
|
|
1590
|
-
group.isActive = true;
|
|
1591
|
-
if (typeof obj.circuits !== 'undefined') {
|
|
1592
|
-
for (let i = 0; i < obj.circuits.length; i++) {
|
|
1593
|
-
let cobj = obj.circuits[i];
|
|
1594
|
-
let c: LightGroupCircuit;
|
|
1595
|
-
if (typeof cobj.id !== 'undefined') c = group.circuits.getItemById(parseInt(cobj.id, 10), true);
|
|
1596
|
-
else if (typeof cobj.circuit !== 'undefined') c = group.circuits.getItemByCircuitId(parseInt(cobj.circuit, 10), true);
|
|
1597
|
-
else c = group.circuits.getItemByIndex(i, true, { id: i + 1 });
|
|
1598
|
-
if (typeof cobj.circuit !== 'undefined') c.circuit = cobj.circuit;
|
|
1599
|
-
//if (typeof cobj.lightingTheme !== 'undefined') c.lightingTheme = parseInt(cobj.lightingTheme, 10); // does this belong here?
|
|
1600
|
-
if (typeof cobj.color !== 'undefined') c.color = parseInt(cobj.color, 10);
|
|
1601
|
-
if (typeof cobj.swimDelay !== 'undefined') c.swimDelay = parseInt(cobj.swimDelay, 10);
|
|
1602
|
-
if (typeof cobj.position !== 'undefined') c.position = parseInt(cobj.position, 10);
|
|
1603
|
-
}
|
|
1604
|
-
// group.circuits.length = obj.circuits.length;
|
|
1605
|
-
}
|
|
1606
|
-
let messages = this.createLightGroupMessages(group);
|
|
1607
|
-
messages.push(new Promise(function (resolve, reject) {
|
|
1608
|
-
let out = Outbound.create({
|
|
1609
|
-
action: 231,
|
|
1610
|
-
payload: [0],
|
|
1611
|
-
onComplete: (err, msg) => {
|
|
1612
|
-
if (err) reject(err);
|
|
1613
|
-
else resolve();
|
|
1614
|
-
}
|
|
1615
|
-
|
|
1616
|
-
});
|
|
1617
|
-
conn.queueSendMessage(out);
|
|
1618
|
-
}));
|
|
1619
|
-
|
|
1620
|
-
return new Promise<LightGroup>(async (resolve, reject) => {
|
|
1621
|
-
try {
|
|
1622
|
-
await Promise.all(messages).catch(err => reject(err));
|
|
1623
|
-
sys.emitData('lightGroupConfig', group.get(true));
|
|
1624
|
-
resolve(group);
|
|
1658
|
+
await out.sendAsync();
|
|
1625
1659
|
}
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1660
|
+
sys.emitData('lightGroupConfig', group.get(true));
|
|
1661
|
+
return group;
|
|
1662
|
+
}
|
|
1663
|
+
catch (err) { return Promise.reject(err); }
|
|
1629
1664
|
}
|
|
1630
1665
|
public async setLightThemeAsync(id: number, theme: number): Promise<ICircuitState> {
|
|
1631
1666
|
// Re-route this as we cannot set individual circuit themes in *Touch.
|
|
@@ -1689,71 +1724,69 @@ export class TouchCircuitCommands extends CircuitCommands {
|
|
|
1689
1724
|
catch (err) { return Promise.reject(`Error runLightCommandAsync ${err.message}`); }
|
|
1690
1725
|
}
|
|
1691
1726
|
public async setLightGroupThemeAsync(id = sys.board.equipmentIds.circuitGroups.start, theme: number): Promise<ICircuitState> {
|
|
1692
|
-
|
|
1727
|
+
try {
|
|
1693
1728
|
const grp = sys.lightGroups.getItemById(id);
|
|
1694
1729
|
const sgrp = state.lightGroups.getItemById(id);
|
|
1695
1730
|
grp.lightingTheme = sgrp.lightingTheme = theme;
|
|
1696
1731
|
sgrp.action = sys.board.valueMaps.circuitActions.getValue('lighttheme');
|
|
1697
1732
|
sgrp.emitEquipmentChange();
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
conn.queueSendMessage(out);
|
|
1754
|
-
});
|
|
1755
|
-
}
|
|
1733
|
+
if (sl.enabled) {
|
|
1734
|
+
await sl.circuits.setLightGroupThemeAsync(theme);
|
|
1735
|
+
}
|
|
1736
|
+
else {
|
|
1737
|
+
let out = Outbound.create({
|
|
1738
|
+
action: 96,
|
|
1739
|
+
payload: [theme, 0],
|
|
1740
|
+
retries: 3,
|
|
1741
|
+
response: true,
|
|
1742
|
+
scope: `lightGroupTheme${id}`
|
|
1743
|
+
});
|
|
1744
|
+
await out.sendAsync();
|
|
1745
|
+
}
|
|
1746
|
+
// Let everyone know we turned these on. The theme messages will come later.
|
|
1747
|
+
for (let i = 0; i < grp.circuits.length; i++) {
|
|
1748
|
+
let c = grp.circuits.getItemByIndex(i);
|
|
1749
|
+
let cstate = state.circuits.getItemById(c.circuit);
|
|
1750
|
+
// if theme is 'off' light groups should not turn on
|
|
1751
|
+
if (cstate.isOn && sys.board.valueMaps.lightThemes.getName(theme) === 'off')
|
|
1752
|
+
await sys.board.circuits.setCircuitStateAsync(c.circuit, false);
|
|
1753
|
+
else if (!cstate.isOn && sys.board.valueMaps.lightThemes.getName(theme) !== 'off') await sys.board.circuits.setCircuitStateAsync(c.circuit, true);
|
|
1754
|
+
}
|
|
1755
|
+
let isOn = sys.board.valueMaps.lightThemes.getName(theme) === 'off' ? false : true;
|
|
1756
|
+
sys.board.circuits.setEndTime(grp, sgrp, isOn);
|
|
1757
|
+
sgrp.isOn = isOn;
|
|
1758
|
+
switch (theme) {
|
|
1759
|
+
case 0: // off
|
|
1760
|
+
case 1: // on
|
|
1761
|
+
break;
|
|
1762
|
+
case 128: // sync
|
|
1763
|
+
setImmediate(function () { sys.board.circuits.sequenceLightGroupAsync(grp.id, 'sync'); });
|
|
1764
|
+
break;
|
|
1765
|
+
case 144: // swim
|
|
1766
|
+
setImmediate(function () { sys.board.circuits.sequenceLightGroupAsync(grp.id, 'swim'); });
|
|
1767
|
+
break;
|
|
1768
|
+
case 160: // swim
|
|
1769
|
+
setImmediate(function () { sys.board.circuits.sequenceLightGroupAsync(grp.id, 'set'); });
|
|
1770
|
+
break;
|
|
1771
|
+
case 190: // save
|
|
1772
|
+
case 191: // recall
|
|
1773
|
+
setImmediate(function () { sys.board.circuits.sequenceLightGroupAsync(grp.id, 'other'); });
|
|
1774
|
+
break;
|
|
1775
|
+
default:
|
|
1776
|
+
setImmediate(function () { sys.board.circuits.sequenceLightGroupAsync(grp.id, 'lighttheme'); });
|
|
1777
|
+
// other themes for magicstream?
|
|
1778
|
+
}
|
|
1779
|
+
sgrp.action = 0;
|
|
1780
|
+
sgrp.hasChanged = true; // Say we are dirty but we really are pure as the driven snow.
|
|
1781
|
+
state.emitEquipmentChanges();
|
|
1782
|
+
return sgrp;
|
|
1783
|
+
}
|
|
1784
|
+
catch (err) {
|
|
1785
|
+
logger.error(`error setting intellibrite theme: ${err.message}`);
|
|
1786
|
+
return Promise.reject(err);
|
|
1787
|
+
}
|
|
1756
1788
|
|
|
1789
|
+
}
|
|
1757
1790
|
}
|
|
1758
1791
|
|
|
1759
1792
|
class TouchFeatureCommands extends FeatureCommands {
|
|
@@ -1769,155 +1802,155 @@ class TouchFeatureCommands extends FeatureCommands {
|
|
|
1769
1802
|
return this.board.circuits.toggleCircuitStateAsync(id);
|
|
1770
1803
|
}
|
|
1771
1804
|
public async setFeatureAsync(data: any): Promise<Feature> {
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1805
|
+
let id = parseInt(data.id, 10);
|
|
1806
|
+
let feature: Feature;
|
|
1807
|
+
if (id <= 0) {
|
|
1808
|
+
id = sys.features.getNextEquipmentId(sys.board.equipmentIds.features);
|
|
1809
|
+
feature = sys.features.getItemById(id, false, { isActive: true, freeze: false });
|
|
1810
|
+
}
|
|
1811
|
+
else
|
|
1812
|
+
feature = sys.features.getItemById(id, false);
|
|
1813
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('feature Id has not been defined', data.id, 'Feature'));
|
|
1814
|
+
if (!sys.board.equipmentIds.features.isInRange(id)) return Promise.reject(new InvalidEquipmentIdError(`feature Id ${id}: is out of range.`, id, 'Feature'));
|
|
1815
|
+
let typeByte = data.type === 0 ? data.type : data.type || feature.type || sys.board.valueMaps.circuitFunctions.getValue('generic');
|
|
1816
|
+
let nameByte = 3; // set default `Aux 1`
|
|
1817
|
+
if (typeof data.nameId !== 'undefined') nameByte = data.nameId;
|
|
1818
|
+
else if (typeof feature.name !== 'undefined') nameByte = feature.nameId;
|
|
1819
|
+
let freeze = (typeof data.freeze !== 'undefined' ? utils.makeBool(data.freeze) : feature.freeze);
|
|
1820
|
+
let showInFeatures = feature.showInFeatures = (typeof data.showInFeatures !== 'undefined' ? utils.makeBool(data.showInFeatures) : feature.showInFeatures);
|
|
1821
|
+
// [165,23,16,34,139,5],[17,0,1,0,0],[1,144]
|
|
1822
|
+
if (sl.enabled) {
|
|
1823
|
+
// SL show in features = 0 = pool; 1 = spa; 2 = features; 4 = lights; 5 = hide
|
|
1824
|
+
await sl.circuits.setCircuitAsync(id, nameByte, typeByte, showInFeatures ? 2 : 0, freeze);
|
|
1825
|
+
}
|
|
1826
|
+
else {
|
|
1788
1827
|
let out = Outbound.create({
|
|
1789
1828
|
action: 139,
|
|
1790
1829
|
payload: [id, typeByte | (utils.makeBool(data.freeze) ? 64 : 0), nameByte, 0, 0],
|
|
1791
1830
|
retries: 3,
|
|
1792
|
-
response: true
|
|
1793
|
-
onComplete: async (err, msg) => {
|
|
1794
|
-
if (err) reject(err);
|
|
1795
|
-
else {
|
|
1796
|
-
let feature = sys.features.getItemById(id);
|
|
1797
|
-
let fstate = state.features.getItemById(data.id);
|
|
1798
|
-
feature.nameId = fstate.nameId = nameByte;
|
|
1799
|
-
// circuit.name = cstate.name = sys.board.valueMaps.circuitNames.get(nameByte).desc;
|
|
1800
|
-
feature.name = fstate.name = sys.board.valueMaps.circuitNames.transform(nameByte).desc;
|
|
1801
|
-
feature.type = fstate.type = typeByte;
|
|
1802
|
-
|
|
1803
|
-
feature.freeze = (typeof data.freeze !== 'undefined' ? utils.makeBool(data.freeze) : feature.freeze);
|
|
1804
|
-
fstate.showInFeatures = feature.showInFeatures = (typeof data.showInFeatures !== 'undefined' ? utils.makeBool(data.showInFeatures) : feature.showInFeatures);
|
|
1805
|
-
feature.eggTimer = typeof data.eggTimer !== 'undefined' ? parseInt(data.eggTimer, 10) : feature.eggTimer || 720;
|
|
1806
|
-
feature.dontStop = (typeof data.dontStop !== 'undefined') ? utils.makeBool(data.dontStop) : feature.eggTimer === 1620;
|
|
1807
|
-
let eggTimer = sys.eggTimers.find(elem => elem.circuit === id);
|
|
1808
|
-
try {
|
|
1809
|
-
if (feature.eggTimer === 720) {
|
|
1810
|
-
if (typeof eggTimer !== 'undefined') await sys.board.schedules.deleteEggTimerAsync({ id: eggTimer.id });
|
|
1811
|
-
}
|
|
1812
|
-
else {
|
|
1813
|
-
await sys.board.schedules.setEggTimerAsync({ id: typeof eggTimer !== 'undefined' ? eggTimer.id : -1, runTime: feature.eggTimer, dontStop: feature.dontStop, circuit: feature.id });
|
|
1814
|
-
}
|
|
1815
|
-
}
|
|
1816
|
-
catch (err) {
|
|
1817
|
-
// fail silently if there are no slots to fill in the schedules
|
|
1818
|
-
logger.info(`Cannot set/delete eggtimer on feature ${feature.id}. Error: ${err.message}`);
|
|
1819
|
-
feature.eggTimer = 720;
|
|
1820
|
-
feature.dontStop = false;
|
|
1821
|
-
}
|
|
1822
|
-
state.emitEquipmentChanges();
|
|
1823
|
-
resolve(feature);
|
|
1824
|
-
}
|
|
1825
|
-
}
|
|
1831
|
+
response: true
|
|
1826
1832
|
});
|
|
1827
|
-
|
|
1828
|
-
}
|
|
1833
|
+
await out.sendAsync();
|
|
1834
|
+
}
|
|
1835
|
+
feature = sys.features.getItemById(id);
|
|
1836
|
+
let fstate = state.features.getItemById(data.id);
|
|
1837
|
+
feature.nameId = fstate.nameId = nameByte;
|
|
1838
|
+
// circuit.name = cstate.name = sys.board.valueMaps.circuitNames.get(nameByte).desc;
|
|
1839
|
+
feature.name = fstate.name = sys.board.valueMaps.circuitNames.transform(nameByte).desc;
|
|
1840
|
+
feature.type = fstate.type = typeByte;
|
|
1841
|
+
|
|
1842
|
+
feature.freeze = freeze;
|
|
1843
|
+
fstate.showInFeatures = showInFeatures;
|
|
1844
|
+
feature.eggTimer = typeof data.eggTimer !== 'undefined' ? parseInt(data.eggTimer, 10) : feature.eggTimer || 720;
|
|
1845
|
+
feature.dontStop = (typeof data.dontStop !== 'undefined') ? utils.makeBool(data.dontStop) : feature.eggTimer === 1620;
|
|
1846
|
+
let eggTimer = sys.eggTimers.find(elem => elem.circuit === id);
|
|
1847
|
+
try {
|
|
1848
|
+
if (feature.eggTimer === 720) {
|
|
1849
|
+
if (typeof eggTimer !== 'undefined') await sys.board.schedules.deleteEggTimerAsync({ id: eggTimer.id });
|
|
1850
|
+
}
|
|
1851
|
+
else {
|
|
1852
|
+
if (sl.enabled) {
|
|
1853
|
+
await sl.schedules.setEggTimerAsync(feature.id, feature.eggTimer);
|
|
1854
|
+
}
|
|
1855
|
+
else {
|
|
1856
|
+
await sys.board.schedules.setEggTimerAsync({ id: typeof eggTimer !== 'undefined' ? eggTimer.id : -1, runTime: feature.eggTimer, dontStop: feature.dontStop, circuit: feature.id });
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
catch (err) {
|
|
1861
|
+
// fail silently if there are no slots to fill in the schedules
|
|
1862
|
+
logger.info(`Cannot set/delete eggtimer on feature ${feature.id}. Error: ${err.message}`);
|
|
1863
|
+
feature.eggTimer = 720;
|
|
1864
|
+
feature.dontStop = false;
|
|
1865
|
+
}
|
|
1866
|
+
state.emitEquipmentChanges();
|
|
1867
|
+
return feature;
|
|
1868
|
+
}
|
|
1869
|
+
public async deleteFeatureAsync(data: any): Promise<Feature> {
|
|
1870
|
+
let circuit = sys.circuits.getItemById(data.id);
|
|
1871
|
+
if (circuit.master === 1) return await super.deleteFeatureAsync(data);
|
|
1872
|
+
data.nameId = 0;
|
|
1873
|
+
data.functionId = sys.board.valueMaps.circuitFunctions.getValue('notused');
|
|
1874
|
+
return this.setFeatureAsync(data);
|
|
1829
1875
|
}
|
|
1830
1876
|
|
|
1831
1877
|
}
|
|
1832
1878
|
class TouchChlorinatorCommands extends ChlorinatorCommands {
|
|
1833
|
-
public async setChlorAsync(obj: any): Promise<ChlorinatorState> {
|
|
1834
|
-
let id = parseInt(obj.id, 10);
|
|
1835
|
-
// Bail out right away if this is not controlled by the OCP.
|
|
1836
|
-
if (typeof obj.master !== 'undefined' && parseInt(obj.master, 10) !== 0) return super.setChlorAsync(obj);
|
|
1837
|
-
let isAdd = false;
|
|
1838
|
-
if (isNaN(id) || id <= 0) {
|
|
1839
|
-
// We are adding so we need to see if there is another chlorinator that is not external.
|
|
1840
|
-
if (sys.chlorinators.count(elem => elem.master !== 2) > sys.equipment.maxChlorinators) return Promise.reject(new InvalidEquipmentDataError(`The max number of chlorinators has been exceeded you may only add ${sys.equipment.maxChlorinators}`, 'chlorinator', sys.equipment.maxChlorinators));
|
|
1841
|
-
id = 1;
|
|
1842
|
-
isAdd = true;
|
|
1843
|
-
}
|
|
1844
|
-
let chlor = sys.chlorinators.getItemById(id);
|
|
1845
|
-
if (chlor.master !== 0 && !isAdd) return super.setChlorAsync(obj);
|
|
1846
|
-
|
|
1847
|
-
// RKS: I am not even sure this can be done with Touch as the master on the RS485 bus.
|
|
1848
|
-
if (typeof chlor.master === 'undefined') chlor.master = 0;
|
|
1849
|
-
let name = obj.name || chlor.name || 'IntelliChlor' + id;
|
|
1850
|
-
let superChlorHours = parseInt(obj.superChlorHours, 10);
|
|
1851
|
-
if (typeof obj.superChlorinate !== 'undefined') obj.superChlor = utils.makeBool(obj.superChlorinate);
|
|
1852
|
-
let superChlorinate = typeof obj.superChlor === 'undefined' ? undefined : utils.makeBool(obj.superChlor);
|
|
1853
|
-
let isDosing = typeof obj.isDosing !== 'undefined' ? utils.makeBool(obj.isDosing) : chlor.isDosing;
|
|
1854
|
-
let disabled = typeof obj.disabled !== 'undefined' ? utils.makeBool(obj.disabled) : chlor.disabled;
|
|
1855
|
-
let poolSetpoint = typeof obj.poolSetpoint !== 'undefined' ? parseInt(obj.poolSetpoint, 10) : chlor.poolSetpoint;
|
|
1856
|
-
let spaSetpoint = typeof obj.spaSetpoint !== 'undefined' ? parseInt(obj.spaSetpoint, 10) : chlor.spaSetpoint;
|
|
1857
|
-
let model = typeof obj.model !== 'undefined' ? sys.board.valueMaps.chlorinatorModel.encode(obj.model) : chlor.model || 0;
|
|
1858
|
-
let chlorType = typeof obj.type !== 'undefined' ? sys.board.valueMaps.chlorinatorType.encode(obj.type) : chlor.type || 0;
|
|
1859
|
-
let portId = typeof obj.portId !== 'undefined' ? parseInt(obj.portId, 10) : chlor.portId;
|
|
1860
|
-
if (portId !== chlor.portId && sys.chlorinators.count(elem => elem.id !== chlor.id && elem.portId === portId && elem.master !== 2) > 0) return Promise.reject(new InvalidEquipmentDataError(`Another chlorinator is installed on port #${portId}. Only one chlorinator can be installed per port.`, 'Chlorinator', portId));
|
|
1861
|
-
if (isAdd) {
|
|
1862
|
-
if (isNaN(poolSetpoint)) poolSetpoint = 50;
|
|
1863
|
-
if (isNaN(spaSetpoint)) spaSetpoint = 10;
|
|
1864
|
-
if (isNaN(superChlorHours)) superChlorHours = 8;
|
|
1865
|
-
if (typeof superChlorinate === 'undefined') superChlorinate = false;
|
|
1866
|
-
}
|
|
1867
|
-
else {
|
|
1868
|
-
if (isNaN(poolSetpoint)) poolSetpoint = chlor.poolSetpoint || 0;
|
|
1869
|
-
if (isNaN(spaSetpoint)) spaSetpoint = chlor.spaSetpoint || 0;
|
|
1870
|
-
if (isNaN(superChlorHours)) superChlorHours = chlor.superChlorHours;
|
|
1871
|
-
if (typeof superChlorinate === 'undefined') superChlorinate = utils.makeBool(chlor.superChlor);
|
|
1872
|
-
}
|
|
1873
|
-
if (typeof obj.disabled !== 'undefined') chlor.disabled = utils.makeBool(obj.disabled);
|
|
1874
|
-
if (typeof chlor.body === 'undefined') chlor.body = parseInt(obj.body, 10) || 32;
|
|
1875
|
-
// Verify the data.
|
|
1876
|
-
let body = sys.board.bodies.mapBodyAssociation(chlor.body);
|
|
1877
|
-
if (typeof body === 'undefined') {
|
|
1878
|
-
if (sys.equipment.shared) body = 32;
|
|
1879
|
-
else if (!sys.equipment.dual) body = 0;
|
|
1880
|
-
else return Promise.reject(new InvalidEquipmentDataError(`Chlorinator body association is not valid: ${body}`, 'chlorinator', body));
|
|
1881
|
-
}
|
|
1882
|
-
if (poolSetpoint > 100 || poolSetpoint < 0) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator poolSetpoint is out of range: ${chlor.poolSetpoint}`, 'chlorinator', chlor.poolSetpoint));
|
|
1883
|
-
if (spaSetpoint > 100 || spaSetpoint < 0) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator spaSetpoint is out of range: ${chlor.poolSetpoint}`, 'chlorinator', chlor.spaSetpoint));
|
|
1884
|
-
if (typeof obj.ignoreSaltReading !== 'undefined') chlor.ignoreSaltReading = utils.makeBool(obj.ignoreSaltReading);
|
|
1885
|
-
|
|
1886
|
-
let _timeout: NodeJS.Timeout;
|
|
1879
|
+
public async setChlorAsync(obj: any, send: boolean = true): Promise<ChlorinatorState> {
|
|
1887
1880
|
try {
|
|
1888
|
-
let
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
});
|
|
1912
|
-
conn.queueSendMessage(out);
|
|
1913
|
-
_timeout = setTimeout(()=>{
|
|
1914
|
-
if (typeof reject === 'undefined' || typeof resolve === 'undefined') return;
|
|
1915
|
-
reject(new EquipmentTimeoutError(`no chlor response in 7 seconds`, `chlorTimeOut`));
|
|
1916
|
-
reject = undefined;
|
|
1881
|
+
let id = parseInt(obj.id, 10);
|
|
1882
|
+
// Bail out right away if this is not controlled by the OCP.
|
|
1883
|
+
if (typeof obj.master !== 'undefined' && parseInt(obj.master, 10) !== 0) return super.setChlorAsync(obj);
|
|
1884
|
+
let isAdd = false;
|
|
1885
|
+
if (isNaN(id) || id <= 0) {
|
|
1886
|
+
// We are adding so we need to see if there is another chlorinator that is not external.
|
|
1887
|
+
if (sys.chlorinators.count(elem => elem.master !== 2) > sys.equipment.maxChlorinators) return Promise.reject(new InvalidEquipmentDataError(`The max number of chlorinators has been exceeded you may only add ${sys.equipment.maxChlorinators}`, 'chlorinator', sys.equipment.maxChlorinators));
|
|
1888
|
+
id = 1;
|
|
1889
|
+
isAdd = true;
|
|
1890
|
+
}
|
|
1891
|
+
let chlor = sys.chlorinators.getItemById(id, isAdd);
|
|
1892
|
+
if (chlor.master !== 0 && !isAdd) return super.setChlorAsync(obj);
|
|
1893
|
+
// RKS: I am not even sure this can be done with Touch as the master on the RS485 bus.
|
|
1894
|
+
if (typeof chlor.master === 'undefined') chlor.master = 0;
|
|
1895
|
+
let name = obj.name || chlor.name || 'IntelliChlor' + id;
|
|
1896
|
+
let superChlorHours = parseInt(obj.superChlorHours, 10);
|
|
1897
|
+
if (typeof obj.superChlorinate !== 'undefined') obj.superChlor = utils.makeBool(obj.superChlorinate);
|
|
1898
|
+
let superChlorinate = typeof obj.superChlor === 'undefined' ? undefined : utils.makeBool(obj.superChlor);
|
|
1899
|
+
let isDosing = typeof obj.isDosing !== 'undefined' ? utils.makeBool(obj.isDosing) : chlor.isDosing;
|
|
1900
|
+
let disabled = typeof obj.disabled !== 'undefined' ? utils.makeBool(obj.disabled) : chlor.disabled;
|
|
1901
|
+
let poolSetpoint = typeof obj.poolSetpoint !== 'undefined' ? parseInt(obj.poolSetpoint, 10) : chlor.poolSetpoint;
|
|
1902
|
+
let spaSetpoint = typeof obj.spaSetpoint !== 'undefined' ? parseInt(obj.spaSetpoint, 10) : chlor.spaSetpoint;
|
|
1903
|
+
let saltTarget = typeof obj.saltTarget === 'number' ? parseInt(obj.saltTarget, 10) : chlor.saltTarget;
|
|
1917
1904
|
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1905
|
+
let model = typeof obj.model !== 'undefined' ? sys.board.valueMaps.chlorinatorModel.encode(obj.model) : chlor.model || 0;
|
|
1906
|
+
let chlorType = typeof obj.type !== 'undefined' ? sys.board.valueMaps.chlorinatorType.encode(obj.type) : chlor.type || 0;
|
|
1907
|
+
let portId = typeof obj.portId !== 'undefined' ? parseInt(obj.portId, 10) : chlor.portId;
|
|
1908
|
+
if (portId !== chlor.portId && sys.chlorinators.count(elem => elem.id !== chlor.id && elem.portId === portId && elem.master !== 2) > 0) return Promise.reject(new InvalidEquipmentDataError(`Another chlorinator is installed on port #${portId}. Only one chlorinator can be installed per port.`, 'Chlorinator', portId));
|
|
1909
|
+
if (isAdd) {
|
|
1910
|
+
if (isNaN(poolSetpoint)) poolSetpoint = 50;
|
|
1911
|
+
if (isNaN(spaSetpoint)) spaSetpoint = 10;
|
|
1912
|
+
if (isNaN(superChlorHours)) superChlorHours = 8;
|
|
1913
|
+
if (typeof superChlorinate === 'undefined') superChlorinate = false;
|
|
1914
|
+
}
|
|
1915
|
+
else {
|
|
1916
|
+
if (isNaN(poolSetpoint)) poolSetpoint = chlor.poolSetpoint || 0;
|
|
1917
|
+
if (isNaN(spaSetpoint)) spaSetpoint = chlor.spaSetpoint || 0;
|
|
1918
|
+
if (isNaN(superChlorHours)) superChlorHours = chlor.superChlorHours;
|
|
1919
|
+
if (typeof superChlorinate === 'undefined') superChlorinate = utils.makeBool(chlor.superChlor);
|
|
1920
|
+
}
|
|
1921
|
+
if (typeof obj.disabled !== 'undefined') chlor.disabled = utils.makeBool(obj.disabled);
|
|
1922
|
+
if (typeof chlor.body === 'undefined') chlor.body = parseInt(obj.body, 10) || 32;
|
|
1923
|
+
// Verify the data.
|
|
1924
|
+
let body = sys.board.bodies.mapBodyAssociation(chlor.body).val;
|
|
1925
|
+
if (typeof body === 'undefined') {
|
|
1926
|
+
if (sys.equipment.shared) body = 32;
|
|
1927
|
+
else if (!sys.equipment.dual) body = 0;
|
|
1928
|
+
else return Promise.reject(new InvalidEquipmentDataError(`Chlorinator body association is not valid: ${body}`, 'chlorinator', body));
|
|
1929
|
+
}
|
|
1930
|
+
if (poolSetpoint > 100 || poolSetpoint < 0) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator poolSetpoint is out of range: ${chlor.poolSetpoint}`, 'chlorinator', chlor.poolSetpoint));
|
|
1931
|
+
if (spaSetpoint > 100 || spaSetpoint < 0) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator spaSetpoint is out of range: ${chlor.poolSetpoint}`, 'chlorinator', chlor.spaSetpoint));
|
|
1932
|
+
if (typeof obj.ignoreSaltReading !== 'undefined') chlor.ignoreSaltReading = utils.makeBool(obj.ignoreSaltReading);
|
|
1933
|
+
|
|
1934
|
+
|
|
1935
|
+
if (send) {
|
|
1936
|
+
if (sl.enabled) {
|
|
1937
|
+
if (!chlor.isActive) {await sl.chlor.setChlorEnabledAsync(true);}
|
|
1938
|
+
await sl.chlor.setChlorOutputAsync(poolSetpoint, spaSetpoint);
|
|
1939
|
+
}
|
|
1940
|
+
else {
|
|
1941
|
+
let out = Outbound.create({
|
|
1942
|
+
dest: 16,
|
|
1943
|
+
action: 153,
|
|
1944
|
+
// removed disable ? 0 : (spaSetpoint << 1) + 1 because only deleteChlorAsync should remove it from the OCP
|
|
1945
|
+
payload: [(disabled ? 0 : isDosing ? 100 << 1 : spaSetpoint << 1) + 1, disabled ? 0 : isDosing ? 100 : poolSetpoint,
|
|
1946
|
+
utils.makeBool(superChlorinate) && superChlorHours > 0 ? superChlorHours + 128 : 0, // We only want to set the superChlor when the user sends superChlor = true
|
|
1947
|
+
0, 0, 0, 0, 0, 0, 0],
|
|
1948
|
+
retries: 3,
|
|
1949
|
+
response: true,
|
|
1950
|
+
});
|
|
1951
|
+
await out.sendAsync();
|
|
1952
|
+
}
|
|
1953
|
+
};
|
|
1921
1954
|
let schlor = state.chlorinators.getItemById(id, true);
|
|
1922
1955
|
chlor.disabled = disabled;
|
|
1923
1956
|
schlor.isActive = chlor.isActive = true;
|
|
@@ -1926,40 +1959,22 @@ class TouchChlorinatorCommands extends ChlorinatorCommands {
|
|
|
1926
1959
|
schlor.spaSetpoint = chlor.spaSetpoint = spaSetpoint;
|
|
1927
1960
|
schlor.superChlorHours = chlor.superChlorHours = superChlorHours;
|
|
1928
1961
|
schlor.body = chlor.body = body;
|
|
1929
|
-
chlor.address = 79 + id;
|
|
1962
|
+
if (typeof chlor.address === 'undefined') chlor.address = 80; // 79 + id;
|
|
1930
1963
|
chlor.name = schlor.name = name;
|
|
1931
1964
|
schlor.model = chlor.model = model;
|
|
1932
1965
|
schlor.type = chlor.type = chlorType;
|
|
1933
1966
|
chlor.isDosing = isDosing;
|
|
1934
1967
|
chlor.portId = portId;
|
|
1935
|
-
|
|
1936
|
-
|
|
1968
|
+
chlor.saltTarget = saltTarget;
|
|
1969
|
+
if (send && !sl.enabled) {
|
|
1937
1970
|
let out = Outbound.create({
|
|
1938
1971
|
dest: 16,
|
|
1939
1972
|
action: 217,
|
|
1940
1973
|
payload: [0],
|
|
1941
1974
|
retries: 3,
|
|
1942
|
-
// scope: Math.random(),
|
|
1943
1975
|
response: true,
|
|
1944
|
-
onComplete: (err) => {
|
|
1945
|
-
// if (typeof reject === 'undefined') {
|
|
1946
|
-
// logger.error(`reject chlor already called.`)
|
|
1947
|
-
// }
|
|
1948
|
-
if (err) {
|
|
1949
|
-
logger.error(`Error requesting chlor status: ${err.message}`);
|
|
1950
|
-
reject(err);
|
|
1951
|
-
}
|
|
1952
|
-
else{
|
|
1953
|
-
resolve();
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
1976
|
})
|
|
1957
|
-
|
|
1958
|
-
});
|
|
1959
|
-
await request217Packet;
|
|
1960
|
-
if (typeof _timeout !== 'undefined'){
|
|
1961
|
-
clearTimeout(_timeout);
|
|
1962
|
-
_timeout = undefined;
|
|
1977
|
+
await out.sendAsync();
|
|
1963
1978
|
}
|
|
1964
1979
|
state.emitEquipmentChanges();
|
|
1965
1980
|
return state.chlorinators.getItemById(id);
|
|
@@ -1969,117 +1984,78 @@ class TouchChlorinatorCommands extends ChlorinatorCommands {
|
|
|
1969
1984
|
}
|
|
1970
1985
|
}
|
|
1971
1986
|
public async deleteChlorAsync(obj: any): Promise<ChlorinatorState> {
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
/*
|
|
2004
|
-
public setChlorAsync(obj: any): Promise<ChlorinatorState> {
|
|
2005
|
-
let id = parseInt(obj.id, 10);
|
|
2006
|
-
if (isNaN(id)) obj.id = 1;
|
|
2007
|
-
// Merge all the information.
|
|
2008
|
-
let chlor = extend(true, {}, sys.chlorinators.getItemById(id).get(), obj);
|
|
2009
|
-
if (typeof obj.superChlorinate !== 'undefined') {
|
|
2010
|
-
chlor.superChlor = obj.superChlorinate;
|
|
2011
|
-
}
|
|
2012
|
-
if (typeof obj.superChlorHours !== 'undefined') chlor.superChlorHours = obj.superChlorHours;
|
|
2013
|
-
|
|
2014
|
-
if (chlor.isActive && chlor.isVirtual) return super.setChlorAsync(obj);
|
|
2015
|
-
if (typeof chlor.body === 'undefined') chlor.body = obj.body || 32;
|
|
2016
|
-
// Verify the data.
|
|
2017
|
-
let body = sys.board.bodies.mapBodyAssociation(chlor.body);
|
|
2018
|
-
if (typeof body === 'undefined') return Promise.reject(new InvalidEquipmentDataError(`Chlorinator body association is not valid: ${chlor.body}`, 'chlorinator', chlor.body));
|
|
2019
|
-
else chlor.body = body.val;
|
|
2020
|
-
if (chlor.poolSetpoint > 100 || chlor.poolSetpoint < 0) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator poolSetpoint is out of range: ${chlor.poolSetpoint}`, 'chlorinator', chlor.poolSetpoint));
|
|
2021
|
-
if (chlor.spaSetpoint > 100 || chlor.spaSetpoint < 0) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator spaSetpoint is out of range: ${chlor.spaSetpoint}`, 'chlorinator', chlor.spaSetpoint));
|
|
2022
|
-
|
|
2023
|
-
let disabled = utils.makeBool(chlor.disabled);
|
|
2024
|
-
return new Promise<ChlorinatorState>((resolve, reject) => {
|
|
2025
|
-
let out = Outbound.create({
|
|
2026
|
-
dest: 16,
|
|
2027
|
-
action: 153,
|
|
2028
|
-
payload: [disabled ? 0 : (chlor.spaSetpoint << 1) + 1, disabled ? 0 : chlor.poolSetpoint,
|
|
2029
|
-
utils.makeBool(chlor.superChlor) && chlor.superChlorHours > 0 ? chlor.superChlorHours + 128 : 0, // We only want to set the superChlor when the user sends superChlor = true
|
|
2030
|
-
0, 0, 0, 0, 0, 0, 0],
|
|
2031
|
-
retries: 3,
|
|
2032
|
-
response: true,
|
|
2033
|
-
onComplete: (err) => {
|
|
2034
|
-
if (err) {
|
|
2035
|
-
logger.error(`Error setting Chlorinator values: ${err.message}`);
|
|
2036
|
-
reject(err);
|
|
2037
|
-
}
|
|
2038
|
-
let schlor = state.chlorinators.getItemById(id, true);
|
|
2039
|
-
let cchlor = sys.chlorinators.getItemById(id, true);
|
|
2040
|
-
for (let prop in chlor) {
|
|
2041
|
-
if (prop in schlor) schlor[prop] = chlor[prop];
|
|
2042
|
-
if (prop in cchlor) cchlor[prop] = chlor[prop];
|
|
2043
|
-
}
|
|
2044
|
-
schlor.isActive = cchlor.isActive = true;
|
|
2045
|
-
schlor.superChlor = cchlor.superChlor = utils.makeBool(chlor.superChlor);
|
|
2046
|
-
|
|
2047
|
-
let hours = typeof chlor.superChlorHours === 'undefined' ? parseInt(chlor.superChlorHours, 10) : 24;
|
|
2048
|
-
if (isNaN(hours)) hours = 24;
|
|
2049
|
-
schlor.superChlorHours = cchlor.superChlorHours = hours;
|
|
2050
|
-
|
|
2051
|
-
let request25Packet = Outbound.create({
|
|
2052
|
-
dest: 16,
|
|
2053
|
-
action: 217,
|
|
2054
|
-
payload: [0],
|
|
2055
|
-
retries: 3,
|
|
2056
|
-
response: true,
|
|
2057
|
-
onComplete: (err) => {
|
|
2058
|
-
if (err) {
|
|
2059
|
-
logger.error(`Error requesting chlor status: ${err.message}`);
|
|
2060
|
-
reject(err);
|
|
2061
|
-
}
|
|
2062
|
-
}
|
|
2063
|
-
});
|
|
2064
|
-
conn.queueSendMessage(request25Packet);
|
|
2065
|
-
state.emitEquipmentChanges();
|
|
2066
|
-
resolve(schlor);
|
|
2067
|
-
}
|
|
2068
|
-
});
|
|
2069
|
-
conn.queueSendMessage(out);
|
|
2070
|
-
});
|
|
1987
|
+
try {
|
|
1988
|
+
let id = parseInt(obj.id, 10);
|
|
1989
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator id is not valid: ${obj.id}`, 'chlorinator', obj.id));
|
|
1990
|
+
let chlor = sys.chlorinators.getItemById(id);
|
|
1991
|
+
if (chlor.master === 1) return await super.deleteChlorAsync(obj);
|
|
1992
|
+
if (sl.enabled) {
|
|
1993
|
+
await sl.chlor.setChlorEnabledAsync(false);
|
|
1994
|
+
}
|
|
1995
|
+
else {
|
|
1996
|
+
let out = Outbound.create({
|
|
1997
|
+
dest: 16,
|
|
1998
|
+
action: 153,
|
|
1999
|
+
payload: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
2000
|
+
retries: 3,
|
|
2001
|
+
response: true
|
|
2002
|
+
});
|
|
2003
|
+
await out.sendAsync();
|
|
2004
|
+
}
|
|
2005
|
+
ncp.chlorinators.deleteChlorinatorAsync(id).then(() => { });
|
|
2006
|
+
let cstate = state.chlorinators.getItemById(id, true);
|
|
2007
|
+
chlor = sys.chlorinators.getItemById(id, true);
|
|
2008
|
+
chlor.isActive = cstate.isActive = false;
|
|
2009
|
+
sys.chlorinators.removeItemById(id);
|
|
2010
|
+
state.chlorinators.removeItemById(id);
|
|
2011
|
+
return cstate;
|
|
2012
|
+
}
|
|
2013
|
+
catch (err) {
|
|
2014
|
+
logger.error(`Error deleting chlorinator: ${err.message}`);
|
|
2015
|
+
return Promise.reject(err);
|
|
2016
|
+
}
|
|
2071
2017
|
}
|
|
2072
|
-
*/
|
|
2073
2018
|
}
|
|
2074
2019
|
class TouchPumpCommands extends PumpCommands {
|
|
2075
|
-
public setPump(pump: Pump, obj?: any) {
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2020
|
+
//public setPump(pump: Pump, obj?: any) {
|
|
2021
|
+
// pump.set(obj);
|
|
2022
|
+
// let msgs: Outbound[] = this.createPumpConfigMessages(pump);
|
|
2023
|
+
// for (let i = 0; i <= msgs.length; i++) {
|
|
2024
|
+
// conn.queueSendMessage(msgs[i]);
|
|
2025
|
+
// }
|
|
2026
|
+
//}
|
|
2027
|
+
// RKS: 05-20-22 This was moved out of systemBoard it does not belong there and probably should not
|
|
2028
|
+
// be called in any current form since it was not being called as part of a message result.
|
|
2029
|
+
private setType(pump: Pump, pumpType: number) {
|
|
2030
|
+
// if we are changing pump types, need to clear out circuits
|
|
2031
|
+
// and props that aren't for this pump type
|
|
2032
|
+
let _id = pump.id;
|
|
2033
|
+
if (pump.type !== pumpType || pumpType === 0) {
|
|
2034
|
+
let _p = pump.get(true);
|
|
2035
|
+
sys.pumps.removeItemById(_id);
|
|
2036
|
+
pump = sys.pumps.getItemById(_id, true);
|
|
2037
|
+
state.pumps.removeItemById(pump.id);
|
|
2038
|
+
pump.type = pumpType;
|
|
2039
|
+
let type = sys.board.valueMaps.pumpTypes.transform(pumpType);
|
|
2040
|
+
if (type.name === 'vs' || type.name === 'vsf') {
|
|
2041
|
+
pump.speedStepSize = 10;
|
|
2042
|
+
pump.minSpeed = type.minSpeed;
|
|
2043
|
+
pump.maxSpeed = type.maxSpeed;
|
|
2044
|
+
}
|
|
2045
|
+
if (type.name === 'vf' || type.name === 'vsf') {
|
|
2046
|
+
pump.flowStepSize = 1;
|
|
2047
|
+
pump.minFlow = type.minFlow;
|
|
2048
|
+
pump.maxFlow = type.maxFlow;
|
|
2049
|
+
}
|
|
2050
|
+
let spump = state.pumps.getItemById(pump.id, true);
|
|
2051
|
+
spump.type = pump.type;
|
|
2052
|
+
spump.isActive = pump.isActive;
|
|
2053
|
+
spump.status = 0;
|
|
2054
|
+
spump.emitData('pumpExt', spump.getExtended());
|
|
2080
2055
|
}
|
|
2081
2056
|
}
|
|
2082
|
-
|
|
2057
|
+
|
|
2058
|
+
public async setPumpAsync(data: any, send: boolean = true): Promise<Pump> {
|
|
2083
2059
|
// Rules regarding Pumps in *Touch
|
|
2084
2060
|
// In *Touch there are basically three classifications of pumps. These include those under control of RS485, Dual Speed, and Single Speed.
|
|
2085
2061
|
// 485 Controlled pumps - Any of the IntelliFlo pumps. These are managed by the control panel.
|
|
@@ -2093,271 +2069,481 @@ class TouchPumpCommands extends PumpCommands {
|
|
|
2093
2069
|
// 3. There can only be 1 single speed pump it will be id 10
|
|
2094
2070
|
// a. single speed pumps allow the identification of an ss pump model. This determines the continuous wattage for when it is on.
|
|
2095
2071
|
// 4. Background Circuits can be assigned for (vf, vsf, vs, ss, and ds pumps).
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
if (
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
id = 10;
|
|
2114
|
-
if (sys.pumps.find(elem => elem.type === ntype)) return Promise.reject(new InvalidEquipmentDataError(`You may add only one ${type.desc} pump`, 'Pump', data));
|
|
2115
|
-
}
|
|
2116
|
-
else if (type.name === 'none') return Promise.reject(new InvalidEquipmentDataError('You must supply a valid id when removing a pump.', 'Pump', data));
|
|
2117
|
-
else {
|
|
2118
|
-
// Under most circumstances the id will = the address minus 95.
|
|
2119
|
-
if (typeof data.address !== 'undefined') {
|
|
2120
|
-
data.address = parseInt(data.address, 10);
|
|
2121
|
-
if (isNaN(data.address)) return Promise.reject(new InvalidEquipmentDataError(`You must supply a valid pump address to add a ${type.desc} pump.`, 'Pump', data));
|
|
2122
|
-
id = data.address - 95;
|
|
2123
|
-
// Make sure it doesn't already exist.
|
|
2124
|
-
if (sys.pumps.find(elem => elem.address === data.address)) return Promise.reject(new InvalidEquipmentDataError(`A pump already exists at address ${data.address - 95}`, 'Pump', data));
|
|
2072
|
+
try {
|
|
2073
|
+
let pump: Pump;
|
|
2074
|
+
let ntype;
|
|
2075
|
+
let type;
|
|
2076
|
+
let isAdd = false;
|
|
2077
|
+
let id = (typeof data.id === 'undefined') ? -1 : parseInt(data.id, 10);
|
|
2078
|
+
if (typeof data.id === 'undefined' || isNaN(id) || id <= 0) {
|
|
2079
|
+
// We are adding a new pump
|
|
2080
|
+
ntype = sys.board.valueMaps.pumpTypes.encode(data.type);
|
|
2081
|
+
type = sys.board.valueMaps.pumpTypes.transform(ntype);
|
|
2082
|
+
// If this is one of the pumps that are not supported by touch send it to system board.
|
|
2083
|
+
if (type.equipmentMaster > 0 || data.master > 0) return await super.setPumpAsync(data);
|
|
2084
|
+
data.master = 0;
|
|
2085
|
+
if (typeof data.type === 'undefined' || isNaN(ntype) || typeof type.name === 'undefined') return Promise.reject(new InvalidEquipmentDataError('You must supply a pump type when creating a new pump', 'Pump', data));
|
|
2086
|
+
if (type.name === 'ds') {
|
|
2087
|
+
id = 9;
|
|
2088
|
+
if (sys.pumps.find(elem => elem.type === ntype)) return Promise.reject(new InvalidEquipmentDataError(`You may add only one ${type.desc} pump`, 'Pump', data));
|
|
2125
2089
|
}
|
|
2090
|
+
else if (type.name === 'ss') {
|
|
2091
|
+
id = 10;
|
|
2092
|
+
if (sys.pumps.find(elem => elem.type === ntype)) return Promise.reject(new InvalidEquipmentDataError(`You may add only one ${type.desc} pump`, 'Pump', data));
|
|
2093
|
+
}
|
|
2094
|
+
else if (type.name === 'none') return Promise.reject(new InvalidEquipmentDataError('You must supply a valid id when removing a pump.', 'Pump', data));
|
|
2126
2095
|
else {
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2096
|
+
// Under most circumstances the id will = the address minus 95.
|
|
2097
|
+
if (typeof data.address !== 'undefined') {
|
|
2098
|
+
data.address = parseInt(data.address, 10);
|
|
2099
|
+
if (isNaN(data.address)) return Promise.reject(new InvalidEquipmentDataError(`You must supply a valid pump address to add a ${type.desc} pump.`, 'Pump', data));
|
|
2100
|
+
id = data.address - 95;
|
|
2101
|
+
// Make sure it doesn't already exist.
|
|
2102
|
+
if (sys.pumps.find(elem => elem.address === data.address)) return Promise.reject(new InvalidEquipmentDataError(`A pump already exists at address ${data.address - 95}`, 'Pump', data));
|
|
2103
|
+
}
|
|
2104
|
+
else {
|
|
2105
|
+
if (typeof id === 'undefined') return Promise.reject(new InvalidEquipmentDataError(`You may not add another ${type.desc} pump. Max number of pumps exceeded.`, 'Pump', data));
|
|
2106
|
+
id = sys.pumps.getNextEquipmentId(sys.board.equipmentIds.pumps);
|
|
2107
|
+
data.address = id + 95;
|
|
2108
|
+
}
|
|
2130
2109
|
}
|
|
2131
|
-
}
|
|
2132
|
-
isAdd = true;
|
|
2133
|
-
pump = sys.pumps.getItemById(id, true);
|
|
2134
|
-
}
|
|
2135
|
-
else {
|
|
2136
|
-
pump = sys.pumps.getItemById(id, false);
|
|
2137
|
-
if (data.master > 0 || pump.master > 0) return await super.setPumpAsync(data);
|
|
2138
|
-
ntype = typeof data.type === 'undefined' ? pump.type : parseInt(data.type, 10);
|
|
2139
|
-
if (isNaN(ntype)) return Promise.reject(new InvalidEquipmentDataError(`Pump type ${data.type} is not valid`, 'Pump', data));
|
|
2140
|
-
type = sys.board.valueMaps.pumpTypes.transform(ntype);
|
|
2141
|
-
// changing type? clear out all props and add as new
|
|
2142
|
-
if (ntype !== pump.type) {
|
|
2143
2110
|
isAdd = true;
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
// The OCP doesn't deal with single speed pumps. Simply add it to the config.
|
|
2167
|
-
data.circuits = [];
|
|
2168
|
-
pump.set(pump);
|
|
2169
|
-
let spump = state.pumps.getItemById(id, true);
|
|
2170
|
-
for (let prop in spump) {
|
|
2171
|
-
if (typeof data[prop] !== 'undefined') spump[prop] = data[prop];
|
|
2172
|
-
}
|
|
2173
|
-
spump.emitEquipmentChange();
|
|
2174
|
-
return Promise.resolve(pump);
|
|
2175
|
-
}
|
|
2176
|
-
else if (type.name === 'ds') {
|
|
2177
|
-
// We are going to set all the high speed circuits.
|
|
2178
|
-
// RSG: TODO I don't know what the message is to set the high speed circuits. The following should
|
|
2179
|
-
// be moved into the onComplete for the outbound message to set high speed circuits.
|
|
2180
|
-
for (let prop in pump) {
|
|
2181
|
-
if (typeof data[prop] !== 'undefined') pump[prop] = data[prop];
|
|
2182
|
-
}
|
|
2183
|
-
let spump = state.pumps.getItemById(id, true);
|
|
2184
|
-
for (let prop in spump) {
|
|
2185
|
-
if (typeof data[prop] !== 'undefined') spump[prop] = data[prop];
|
|
2186
|
-
}
|
|
2187
|
-
spump.emitEquipmentChange();
|
|
2188
|
-
return Promise.resolve(pump);
|
|
2189
|
-
}
|
|
2190
|
-
else {
|
|
2191
|
-
let arr = [];
|
|
2111
|
+
pump = sys.pumps.getItemById(id, true);
|
|
2112
|
+
}
|
|
2113
|
+
else {
|
|
2114
|
+
pump = sys.pumps.getItemById(id, false);
|
|
2115
|
+
if (data.master > 0 || pump.master > 0) return await super.setPumpAsync(data);
|
|
2116
|
+
data.master = 0;
|
|
2117
|
+
ntype = typeof data.type === 'undefined' ? pump.type : parseInt(data.type, 10);
|
|
2118
|
+
if (isNaN(ntype)) return Promise.reject(new InvalidEquipmentDataError(`Pump type ${data.type} is not valid`, 'Pump', data));
|
|
2119
|
+
type = sys.board.valueMaps.pumpTypes.transform(ntype);
|
|
2120
|
+
// changing type? clear out all props and add as new
|
|
2121
|
+
if (ntype !== pump.type) {
|
|
2122
|
+
isAdd = true;
|
|
2123
|
+
this.setType(pump, ntype);
|
|
2124
|
+
pump = sys.pumps.getItemById(id, false); // refetch pump with new value
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
// Validate all the ids since in *Touch the address is determined from the id.
|
|
2128
|
+
if (!isAdd) isAdd = sys.pumps.find(elem => elem.id === id) === undefined;
|
|
2129
|
+
// Now lets validate the ids related to the type.
|
|
2130
|
+
if (id === 9 && type.name !== 'ds') return Promise.reject(new InvalidEquipmentDataError(`The id for a ${type.desc} pump must be 9`, 'Pump', data));
|
|
2131
|
+
else if (id === 10 && type.name !== 'ss') return Promise.reject(new InvalidEquipmentDataError(`The id for a ${type.desc} pump must be 10`, 'Pump', data));
|
|
2132
|
+
else if (id > sys.equipment.maxPumps) return Promise.reject(new InvalidEquipmentDataError(`The id for a ${type.desc} must be less than ${sys.equipment.maxPumps}`, 'Pump', data));
|
|
2192
2133
|
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2134
|
+
// Need to do a check here if we are clearing out the circuits; id data.circuits === []
|
|
2135
|
+
// extend will keep the original array
|
|
2136
|
+
let bClearPumpCircuits = typeof data.circuits !== 'undefined' && data.circuits.length === 0;
|
|
2137
|
+
// RKS: 09-14-22 - This is fundamentally wrong. This ensures that no circuit can be deleted
|
|
2138
|
+
// from the pump.
|
|
2139
|
+
if (!isAdd) {
|
|
2140
|
+
data.address = typeof data.address !== 'undefined' ? data.address : pump.address;
|
|
2141
|
+
data.backgroundCircuit = typeof data.backgroundCircuit !== 'undefined' ? data.backgroundCircuit : pump.backgroundCircuit;
|
|
2142
|
+
data.backwashFlow = typeof data.backwashFlow !== 'undefined' ? data.backwashFlow : pump.backwashFlow;
|
|
2143
|
+
data.backwashTime = typeof data.backwashTime !== 'undefined' ? data.backwashTime : pump.backwashTime;
|
|
2144
|
+
data.body = typeof data.body !== 'undefined' ? data.body : pump.body;
|
|
2145
|
+
data.filterSize = typeof data.filterSize !== 'undefined' ? data.filterSize : pump.filterSize;
|
|
2146
|
+
data.flowStepSize = typeof data.flowStepSize !== 'undefined' ? data.flowStepSize : pump.flowStepSize
|
|
2147
|
+
data.manualFilterGPM = typeof data.manualFilterGPM !== 'undefined' ? data.manualFilterGPM : pump.manualFilterGPM;
|
|
2148
|
+
data.master = 0;
|
|
2149
|
+
data.maxFlow = typeof data.maxFlow !== 'undefined' ? data.maxFlow : pump.maxFlow;
|
|
2150
|
+
data.maxPressureIncrease = typeof data.maxPressureIncrease ? data.maxPressureIncrease : pump.maxPressureIncrease;
|
|
2151
|
+
data.maxSpeed = typeof data.maxSpeed !== 'undefined' ? data.maxSpeed : pump.maxSpeed;
|
|
2152
|
+
data.maxSystemTime = typeof data.maxSystemTime !== 'undefined' ? data.maxSystemTime : pump.maxSystemTime;
|
|
2153
|
+
data.minFlow = typeof data.minFlow !== 'undefined' ? data.minFlow : pump.minFlow;
|
|
2154
|
+
data.minSpeed = typeof data.minSpeed !== 'undefined' ? data.minSpeed : pump.minSpeed;
|
|
2155
|
+
data.model = typeof data.model !== 'undefined' ? data.model : pump.model;
|
|
2156
|
+
data.name = typeof data.name !== 'undefined' ? data.name : pump.name;
|
|
2157
|
+
data.portId = typeof data.portId !== 'undefined' ? data.portId : pump.portId || 0;
|
|
2158
|
+
data.primingSpeed = typeof data.primingSpeed !== 'undefined' ? data.primingSpeed : pump.primingSpeed;
|
|
2159
|
+
data.primingTime = typeof data.primingTime !== 'undefined' ? data.primingTime : pump.primingTime;
|
|
2160
|
+
data.rinseTime = typeof data.rinseTime !== 'undefined' ? data.rinseTime : pump.rinseTime;
|
|
2161
|
+
data.speedStepSize = typeof data.speedStepSize !== 'undefined' ? data.speedStepSize : pump.speedStepSize;
|
|
2162
|
+
data.turnovers = typeof data.turnovers !== 'undefined' ? data.turnovers : pump.turnovers;
|
|
2163
|
+
data.vacuumFlow = typeof data.vacuumFlow !== 'undefined' ? data.vacuumFlow : pump.vacuumFlow;
|
|
2164
|
+
data.vacuumTime = typeof data.vacuumTime !== 'undefined' ? data.vacuumTime : pump.vacuumTime;
|
|
2165
|
+
if (typeof data.circuits !== 'undefined') {
|
|
2166
|
+
let circs = extend(true, [], data.circuits);
|
|
2167
|
+
data = extend(true, {}, pump.get(true), data, { id: id, type: ntype });
|
|
2168
|
+
data.circuits = circs;
|
|
2169
|
+
}
|
|
2170
|
+
else
|
|
2171
|
+
data = extend(true, {}, pump.get(true), data, { id: id, type: ntype });
|
|
2172
|
+
}
|
|
2173
|
+
else data = extend(false, {}, data, { id: id, type: ntype });
|
|
2174
|
+
if (!isAdd && bClearPumpCircuits) data.circuits = [];
|
|
2175
|
+
data.name = data.name || pump.name || type.desc;
|
|
2176
|
+
data.portId = 0;
|
|
2177
|
+
// RKS: 12-02-22 -- The EasyTouch 1 OCPs only support 1 pump and this pump must be an IntelliFlo VS. Do some checks here to make sure we are
|
|
2178
|
+
// sending the right messages to the right controller. At this point I only know of 1 EasyTouch v1 panel and it is a 4.
|
|
2179
|
+
let isVersion1 = sys.equipment.modules.getItemByIndex(0, false).type >= 128;
|
|
2180
|
+
// We will not be sending message for ss type pumps.
|
|
2181
|
+
if (type.name === 'ss') {
|
|
2182
|
+
// The OCP doesn't deal with single speed pumps. Simply add it to the config.
|
|
2183
|
+
data.circuits = [];
|
|
2184
|
+
pump.set(pump);
|
|
2185
|
+
let spump = state.pumps.getItemById(id, true);
|
|
2186
|
+
for (let prop in spump) {
|
|
2187
|
+
if (typeof data[prop] !== 'undefined') spump[prop] = data[prop];
|
|
2188
|
+
}
|
|
2189
|
+
data.model = typeof data.model === 'undefined' ? sys.board.valueMaps.pumpSSModels.encode(data.model) : pump.model || 0;
|
|
2190
|
+
spump.emitEquipmentChange();
|
|
2191
|
+
return Promise.resolve(pump);
|
|
2192
|
+
}
|
|
2193
|
+
else if (type.name === 'ds') {
|
|
2194
|
+
// We are going to set all the high speed circuits.
|
|
2195
|
+
// RSG: TODO I don't know what the message is to set the high speed circuits. The following should
|
|
2196
|
+
// be moved into the onComplete for the outbound message to set high speed circuits.
|
|
2197
|
+
data.model = typeof data.model === 'undefined' ? sys.board.valueMaps.pumpDSModels.encode(data.model) : pump.model || 0;
|
|
2198
|
+
for (let prop in pump) {
|
|
2199
|
+
if (typeof data[prop] !== 'undefined') pump[prop] = data[prop];
|
|
2200
|
+
}
|
|
2201
|
+
let spump = state.pumps.getItemById(id, true);
|
|
2202
|
+
for (let prop in spump) {
|
|
2203
|
+
if (typeof data[prop] !== 'undefined') spump[prop] = data[prop];
|
|
2204
|
+
}
|
|
2205
|
+
spump.emitEquipmentChange();
|
|
2206
|
+
return Promise.resolve(pump);
|
|
2207
|
+
}
|
|
2208
|
+
else {
|
|
2209
|
+
let arrCircuits = [];
|
|
2210
|
+
data.address = id + 95;
|
|
2211
|
+
if (isVersion1) {
|
|
2212
|
+
if (data.address !== 96) return Promise.reject(new InvalidEquipmentDataError(`EasyTouch Version 1 controllers only support VS pumps at the first address`, 'Pump', data));
|
|
2213
|
+
if (type.name !== 'vs') return Promise.reject(new InvalidEquipmentDataError(`EasyTouch Version 1 controllers only support VS pump types. ${type.desc} pumps are not supported`, 'Pump', data));
|
|
2214
|
+
let outc = Outbound.create({
|
|
2215
|
+
action: 150,
|
|
2216
|
+
retries: 2,
|
|
2217
|
+
response: Response.create({ action: 1, payload: [150] })
|
|
2218
|
+
});
|
|
2219
|
+
outc.appendPayloadBytes(0, 13);
|
|
2220
|
+
data.primingTime = typeof data.primingTime !== 'undefined' ? isNaN(parseInt(data.primingTime, 10)) ? pump.primingTime || 0 : 0 : pump.primingTime;
|
|
2221
|
+
data.primingSpeed = typeof data.primingSpeed !== 'undefined' ? parseInt(data.primingSpeed, 10) : pump.primingSpeed || type.minSpeed;
|
|
2222
|
+
// If we do not have any circuits to define we should use the circuits from the existing pump.
|
|
2223
|
+
if (typeof data.circuits === 'undefined') data.circuits = pump.circuits.toArray();
|
|
2224
|
+
for (let i = 0; i < 4; i++) {
|
|
2225
|
+
let c = i < data.circuits.length ? data.circuits[i] : { id: i + 1, master: 0, speed: 0, circuit: 0, units: 0 };
|
|
2226
|
+
let byte = (i * 3) + 1;
|
|
2227
|
+
c.units = 0;
|
|
2228
|
+
c.master = 0;
|
|
2229
|
+
c.id = arrCircuits.length + 1;
|
|
2230
|
+
outc.setPayloadByte(byte, c.circuit);
|
|
2231
|
+
outc.setPayloadByte(byte + 1, Math.floor(c.speed / 256));
|
|
2232
|
+
outc.setPayloadByte(byte + 2, c.speed % 256);
|
|
2233
|
+
if (c.circuit > 0) {
|
|
2234
|
+
// Check to see if the circuit was already included.
|
|
2235
|
+
if (typeof arrCircuits.find(x => x.circuit === c.cuircuit) !== 'undefined') return Promise.reject(new InvalidEquipmentDataError(`Configuration for pump ${pump.name} is not correct circuit #${c.circuit} as included more than once. ${JSON.stringify(c)}`, 'Pump', data));
|
|
2236
|
+
arrCircuits.push(c);
|
|
2237
|
+
}
|
|
2238
|
+
if (sl.enabled && send) {
|
|
2239
|
+
// this is a shortcut for only updating a single pump
|
|
2240
|
+
// speed at a time; for full config we need a different API
|
|
2241
|
+
for (let i = 0; i < pump.circuits.length; i++) {
|
|
2242
|
+
let pc = pump.circuits.getItemByIndex(i);
|
|
2243
|
+
if (pc.circuit === c.id && (pc.speed !== c.speed || pc.flow !== c.flow)) {
|
|
2244
|
+
|
|
2245
|
+
await sl.pumps.setPumpSpeedAsync(pump, c);
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
2249
|
}
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2250
|
+
data.circuits = arrCircuits;
|
|
2251
|
+
|
|
2252
|
+
if (send) {
|
|
2253
|
+
return new Promise<Pump>(async (resolve, reject) => {
|
|
2254
|
+
outc.onComplete = (err, msg) => {
|
|
2255
|
+
if (err) reject(err);
|
|
2256
|
+
else {
|
|
2257
|
+
pump = sys.pumps.getItemById(id, true);
|
|
2258
|
+
pump.set(data);
|
|
2259
|
+
let spump = state.pumps.getItemById(id, true);
|
|
2260
|
+
spump.isActive = pump.isActive = true;
|
|
2261
|
+
spump.name = pump.name;
|
|
2262
|
+
spump.type = pump.type;
|
|
2263
|
+
spump.emitEquipmentChange();
|
|
2264
|
+
resolve(pump);
|
|
2265
|
+
const pumpConfigRequest = Outbound.create({
|
|
2266
|
+
action: 214,
|
|
2267
|
+
payload: [0],
|
|
2268
|
+
retries: 2,
|
|
2269
|
+
response: true
|
|
2270
|
+
});
|
|
2271
|
+
conn.queueSendMessage(pumpConfigRequest);
|
|
2272
|
+
}
|
|
2273
|
+
};
|
|
2274
|
+
await outc.sendAsync();
|
|
2275
|
+
});
|
|
2253
2276
|
}
|
|
2254
2277
|
}
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2278
|
+
else {
|
|
2279
|
+
let outc = Outbound.create({
|
|
2280
|
+
action: 155,
|
|
2281
|
+
payload: [id, ntype],
|
|
2282
|
+
retries: 2,
|
|
2283
|
+
response: Response.create({ action: 1, payload: [155] })
|
|
2284
|
+
});
|
|
2285
|
+
outc.appendPayloadBytes(0, 44);
|
|
2286
|
+
if (type.val === 128) {
|
|
2287
|
+
outc.setPayloadByte(3, 2);
|
|
2288
|
+
data.model = 0;
|
|
2289
|
+
}
|
|
2290
|
+
if (typeof type.maxPrimingTime !== 'undefined' && type.maxPrimingTime > 0 && type.val >= 64) {
|
|
2291
|
+
// We need to set all of this back to data since later pump.set is called to set the data after success.
|
|
2292
|
+
data.primingTime = typeof data.primingTime !== 'undefined' ? isNaN(parseInt(data.primingTime, 10)) ? pump.primingTime || 0 : 0 : pump.primingTime;
|
|
2293
|
+
data.primingSpeed = typeof data.primingSpeed !== 'undefined' ? parseInt(data.primingSpeed, 10) : pump.primingSpeed || type.minSpeed;
|
|
2294
|
+
outc.setPayloadByte(2, data.primingTime);
|
|
2295
|
+
outc.setPayloadByte(21, Math.floor(data.primingSpeed / 256));
|
|
2296
|
+
outc.setPayloadByte(30, data.primingSpeed % 256);
|
|
2297
|
+
}
|
|
2298
|
+
if (type.val === 1) { // Any VF pump.
|
|
2299
|
+
// We need to set all of this back to data since later pump.set is called to set the data after success.
|
|
2300
|
+
data.backgroundCircuit = typeof data.backgroundCircuit !== 'undefined' ? parseInt(data.backgroundCircuit, 10) : pump.backgroundCircuit || 6;
|
|
2301
|
+
data.filterSize = typeof data.filterSize !== 'undefined' ? parseInt(data.filterSize, 10) : pump.filterSize || 15000;
|
|
2302
|
+
data.turnovers = typeof data.turnovers !== 'undefined' ? parseInt(data.turnovers, 10) : pump.turnovers || 2;
|
|
2303
|
+
data.manualFilterGPM = typeof data.manualFilterGPM !== 'undefined' ? parseInt(data.manualFilterGPM, 10) : pump.manualFilterGPM || 30;
|
|
2304
|
+
data.primingSpeed = typeof data.primingSpeed !== 'undefined' ? parseInt(data.primingSpeed, 10) : pump.primingSpeed || 55;
|
|
2305
|
+
data.primingTime = typeof data.primingTime !== 'undefined' ? parseInt(data.primingTime, 10) : pump.primingTime || 0;
|
|
2306
|
+
data.maxSystemTime = typeof data.maxSystemTime !== 'undefined' ? parseInt(data.maxSystemTime, 10) : pump.maxSystemTime || 0;
|
|
2307
|
+
data.maxPressureIncrease = typeof data.maxPressureIncrease != 'undefined' ? parseInt(data.maxPressureIncrease, 10) : pump.maxPressureIncrease || 0;
|
|
2308
|
+
data.backwashFlow = typeof data.backwashFlow !== 'undefined' ? parseInt(data.backwashFlow, 10) : pump.backwashFlow || 60;
|
|
2309
|
+
data.backwashTime = typeof data.backwashTime !== 'undefined' ? parseInt(data.bacwashTime, 10) : pump.backwashTime || 5;
|
|
2310
|
+
data.rinseTime = typeof data.rinseTime !== 'undefined' ? parseInt(data.rinseTime, 10) : pump.rinseTime || 1;
|
|
2311
|
+
data.vacuumFlow = typeof data.vacuumFlow !== 'undefined' ? parseInt(data.vacuumFlow, 10) : pump.vacuumFlow || 50;
|
|
2312
|
+
data.vacuumTime = typeof data.vacuumTime !== 'undefined' ? parseInt(data.vacuumTime, 10) : pump.vacuumTime || 10;
|
|
2313
|
+
data.model = 0;
|
|
2314
|
+
outc.setPayloadByte(1, data.backgroundCircuit);
|
|
2315
|
+
outc.setPayloadByte(2, data.filterSize);
|
|
2316
|
+
outc.setPayloadByte(3, data.turnovers);
|
|
2317
|
+
outc.setPayloadByte(21, data.manualFilterGPM);
|
|
2318
|
+
outc.setPayloadByte(22, data.primingSpeed);
|
|
2319
|
+
outc.setPayloadByte(23, data.primingTime | data.maxSystemTime << 4, 5);
|
|
2320
|
+
outc.setPayloadByte(24, data.maxPressureIncrease);
|
|
2321
|
+
outc.setPayloadByte(25, data.backwashFlow);
|
|
2322
|
+
outc.setPayloadByte(26, data.backwashTime);
|
|
2323
|
+
outc.setPayloadByte(27, data.rinseTime);
|
|
2324
|
+
outc.setPayloadByte(28, data.vacuumFlow);
|
|
2325
|
+
outc.setPayloadByte(30, data.vacuumTime);
|
|
2326
|
+
}
|
|
2327
|
+
if (typeof type.maxCircuits !== 'undefined' && type.maxCircuits > 0 && typeof data.circuits !== 'undefined') { // This pump type supports circuits
|
|
2328
|
+
// Do some validation to make sure we don't have a condition where a circuit is declared twice.
|
|
2329
|
+
let arrCircuits = [];
|
|
2330
|
+
// Below is a very strange mess that goofs up the circuit settings.
|
|
2331
|
+
//{id:1, circuits:[{speed:1750, units:{val:0}, id:1, circuit:6}, {speed:2100, units:{val:0}, id:2, circuit:6}]}
|
|
2332
|
+
let ubyte = 0;
|
|
2333
|
+
for (let i = 1; i <= data.circuits.length && i <= type.maxCircuits; i++) {
|
|
2334
|
+
// RKS: This notion of always returning the max number of circuits was misguided. It leaves gaps in the circuit definitions and makes the pump
|
|
2335
|
+
// layouts difficult when there are a variety of supported circuits. For instance with SF pumps you only get 4.
|
|
2336
|
+
let c = i > data.circuits.length ? { speed: type.minSpeed || 0, flow: type.minFlow || 0, circuit: 0 } : data.circuits[i - 1];
|
|
2337
|
+
//{speed:1750, units:{val:0}, id:1, circuit:6}
|
|
2338
|
+
let speed = parseInt(c.speed, 10);
|
|
2339
|
+
let flow = parseInt(c.flow, 10);
|
|
2340
|
+
let circuit = parseInt(c.circuit, 10);
|
|
2341
|
+
if (isNaN(circuit)) return Promise.reject(new InvalidEquipmentDataError(`An invalid pump circuit was supplied for pump ${pump.name}. ${JSON.stringify(c)}`, 'Pump', data))
|
|
2342
|
+
if (isNaN(speed)) speed = type.minSpeed;
|
|
2343
|
+
if (isNaN(flow)) flow = type.minFlow;
|
|
2344
|
+
outc.setPayloadByte((i * 2) + 3, circuit, 0);
|
|
2345
|
+
let units;
|
|
2346
|
+
if (type.name === 'vf') units = sys.board.valueMaps.pumpUnits.getValue('gpm');
|
|
2347
|
+
else if (type.name === 'vs') units = sys.board.valueMaps.pumpUnits.getValue('rpm');
|
|
2348
|
+
else units = sys.board.valueMaps.pumpUnits.encode(c.units);
|
|
2349
|
+
c.units = units;
|
|
2350
|
+
if (isNaN(units)) units = sys.board.valueMaps.pumpUnits.getValue('rpm');
|
|
2351
|
+
if (typeof type.minSpeed !== 'undefined' && c.units === sys.board.valueMaps.pumpUnits.getValue('rpm')) {
|
|
2352
|
+
outc.setPayloadByte((i * 2) + 4, Math.floor(speed / 256)); // Set to rpm
|
|
2353
|
+
outc.setPayloadByte(i + 21, speed % 256);
|
|
2354
|
+
c.speed = speed;
|
|
2355
|
+
ubyte |= (1 << (i - 1));
|
|
2356
|
+
}
|
|
2357
|
+
else if (typeof type.minFlow !== 'undefined' && c.units === sys.board.valueMaps.pumpUnits.getValue('gpm')) {
|
|
2358
|
+
outc.setPayloadByte(i * 2 + 4, flow); // Set to gpm
|
|
2359
|
+
c.flow = flow;
|
|
2360
|
+
}
|
|
2361
|
+
c.id = i;
|
|
2362
|
+
c.circuit = circuit;
|
|
2363
|
+
if (arrCircuits.includes(c.circuit)) return Promise.reject(new InvalidEquipmentDataError(`Configuration for pump ${pump.name} is not correct circuit #${c.circuit} as included more than once. ${JSON.stringify(c)}`, 'Pump', data))
|
|
2364
|
+
arrCircuits.push(c.circuit);
|
|
2365
|
+
if (sl.enabled && send) {
|
|
2366
|
+
// this is a shortcut for only updating a single pump
|
|
2367
|
+
// speed at a time; for full config we need a different API
|
|
2368
|
+
for (let i = 0; i < pump.circuits.length; i++) {
|
|
2369
|
+
let pc = pump.circuits.getItemByIndex(i);
|
|
2370
|
+
if (pc.circuit === c.circuit && (pc.speed !== c.speed || pc.flow !== c.flow)) {
|
|
2371
|
+
// todo: now that equipconfig is functional for pumps, this is really
|
|
2372
|
+
// only needed when changing speeds on the home page
|
|
2373
|
+
// (not config).
|
|
2374
|
+
await sl.pumps.setPumpSpeedAsync(pump, c);
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
// data.circuits = arrCircuits;
|
|
2380
|
+
if (type.name === 'vsf') outc.setPayloadByte(4, ubyte);
|
|
2381
|
+
}
|
|
2382
|
+
else if (typeof type.maxCircuits !== 'undefined' && type.maxCircuits > 0 && typeof data.circuits === 'undefined') { // This pump type supports circuits and the payload did not contain them.
|
|
2383
|
+
// Copy the data from the circuits array. That way when we call pump.set to set the data back it will be persisted correctly.
|
|
2384
|
+
data.circuits = extend(true, {}, pump.circuits.get());
|
|
2385
|
+
let ubyte = 0;
|
|
2386
|
+
for (let i = 1; i <= data.circuits.length; i++) data.circuits[i].id = i;
|
|
2387
|
+
for (let i = 1; i <= pump.circuits.length && i <= type.maxCircuits; i++) {
|
|
2388
|
+
let c = pump.circuits.getItemByIndex(i - 1);
|
|
2389
|
+
let speed = c.speed;
|
|
2390
|
+
let flow = c.flow;
|
|
2391
|
+
let circuit = c.circuit;
|
|
2392
|
+
if (isNaN(speed)) speed = type.minSpeed;
|
|
2393
|
+
if (isNaN(flow)) flow = type.minFlow;
|
|
2394
|
+
outc.setPayloadByte((i * 2) + 3, circuit, 0);
|
|
2395
|
+
let units;
|
|
2396
|
+
if (type.name === 'vf') units = sys.board.valueMaps.pumpUnits.getValue('gpm');
|
|
2397
|
+
else if (type.name === 'vs') units = sys.board.valueMaps.pumpUnits.getValue('rpm');
|
|
2398
|
+
else units = c.units;
|
|
2399
|
+
if (isNaN(units)) units = sys.board.valueMaps.pumpUnits.getValue('rpm');
|
|
2400
|
+
c.units = units;
|
|
2401
|
+
if (typeof type.minSpeed !== 'undefined' && c.units === sys.board.valueMaps.pumpUnits.getValue('rpm')) {
|
|
2402
|
+
outc.setPayloadByte((i * 2) + 4, Math.floor(speed / 256)); // Set to rpm
|
|
2403
|
+
outc.setPayloadByte(i + 21, speed % 256);
|
|
2404
|
+
ubyte |= (1 << (i - 1));
|
|
2405
|
+
}
|
|
2406
|
+
else if (typeof type.minFlow !== 'undefined' && c.units === sys.board.valueMaps.pumpUnits.getValue('gpm')) {
|
|
2407
|
+
outc.setPayloadByte((i * 2) + 4, flow); // Set to gpm
|
|
2408
|
+
}
|
|
2409
|
+
if (sl.enabled && send) {
|
|
2410
|
+
await sl.pumps.setPumpSpeedAsync(pump, c);
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
if (type.name === 'vsf') outc.setPayloadByte(4, ubyte);
|
|
2414
|
+
|
|
2415
|
+
}
|
|
2416
|
+
if (sl.enabled) {
|
|
2417
|
+
if (send) {
|
|
2418
|
+
// set Equip Config with pumps
|
|
2419
|
+
await sl.controller.setEquipmentAsync(data, 'pump');
|
|
2420
|
+
}
|
|
2260
2421
|
pump = sys.pumps.getItemById(id, true);
|
|
2261
2422
|
pump.set(data); // Sets all the data back to the pump.
|
|
2262
2423
|
let spump = state.pumps.getItemById(id, true);
|
|
2424
|
+
spump.isActive = pump.isActive = true;
|
|
2263
2425
|
spump.name = pump.name;
|
|
2264
2426
|
spump.type = pump.type;
|
|
2265
2427
|
spump.emitEquipmentChange();
|
|
2266
|
-
resolve(pump);
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2428
|
+
return Promise.resolve(pump);
|
|
2429
|
+
}
|
|
2430
|
+
else if (send) {
|
|
2431
|
+
return new Promise<Pump>(async (resolve, reject) => {
|
|
2432
|
+
outc.onComplete = (err, msg) => {
|
|
2433
|
+
if (err) reject(err);
|
|
2434
|
+
else {
|
|
2435
|
+
pump = sys.pumps.getItemById(id, true);
|
|
2436
|
+
// RKS: 05-20-22 Boooh to this if the payload does not include its
|
|
2437
|
+
// circuits we have just destroyed the pump definition. So I added code to
|
|
2438
|
+
// make sure that the data is complete.
|
|
2439
|
+
pump.set(data); // Sets all the data back to the pump.
|
|
2440
|
+
let spump = state.pumps.getItemById(id, true);
|
|
2441
|
+
spump.isActive = pump.isActive = true;
|
|
2442
|
+
spump.name = pump.name;
|
|
2443
|
+
spump.type = pump.type;
|
|
2444
|
+
spump.emitEquipmentChange();
|
|
2445
|
+
resolve(pump);
|
|
2446
|
+
const pumpConfigRequest = Outbound.create({
|
|
2447
|
+
action: 216,
|
|
2448
|
+
payload: [pump.id],
|
|
2449
|
+
retries: 2,
|
|
2450
|
+
response: true
|
|
2451
|
+
});
|
|
2452
|
+
conn.queueSendMessage(pumpConfigRequest);
|
|
2453
|
+
}
|
|
2454
|
+
};
|
|
2455
|
+
await outc.sendAsync();
|
|
2272
2456
|
});
|
|
2273
|
-
conn.queueSendMessage(pumpConfigRequest);
|
|
2274
2457
|
}
|
|
2275
|
-
};
|
|
2276
|
-
conn.queueSendMessage(outc);
|
|
2277
|
-
});
|
|
2278
|
-
}
|
|
2279
|
-
}
|
|
2280
|
-
private createPumpConfigMessages(pump: Pump): Outbound[] {
|
|
2281
|
-
// [165,33,16,34,155,46],[1,128,0,2,0,16,12,6,7,1,9,4,11,11,3,128,8,0,2,18,2,3,128,8,196,184,232,152,188,238,232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[9,75]
|
|
2282
|
-
const setPumpConfig = Outbound.create({
|
|
2283
|
-
action: 155,
|
|
2284
|
-
payload: [pump.id, pump.type, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
2285
|
-
retries: 2,
|
|
2286
|
-
response: true
|
|
2287
|
-
});
|
|
2288
|
-
if (pump.type === 128) {
|
|
2289
|
-
// vs
|
|
2290
|
-
//[165, 1, 16, 33, 155, 47]
|
|
2291
|
-
//[1, 128, 0, 0, 0, 6, 10, 1, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 190, 134, 0, 0, 0, 0, 0, 0, 232, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
2292
|
-
//[4, 109]
|
|
2293
|
-
setPumpConfig.payload[2] = pump.primingTime || 0;
|
|
2294
|
-
setPumpConfig.payload[21] = Math.floor(pump.primingSpeed / 256) || 3;
|
|
2295
|
-
setPumpConfig.payload[30] =
|
|
2296
|
-
pump.primingSpeed - Math.floor(pump.primingSpeed / 256) * 256 || 232;
|
|
2297
|
-
for (let i = 1; i <= 8; i++) {
|
|
2298
|
-
let circ = pump.circuits.getItemById(i);
|
|
2299
|
-
setPumpConfig.payload[i * 2 + 3] = circ.circuit || 0;
|
|
2300
|
-
setPumpConfig.payload[i * 2 + 4] = Math.floor(circ.speed / 256) || 3;
|
|
2301
|
-
setPumpConfig.payload[i + 21] =
|
|
2302
|
-
(circ.speed - (setPumpConfig.payload[i * 2 + 4] * 256)) || 232;
|
|
2303
|
-
}
|
|
2304
|
-
}
|
|
2305
|
-
else if (pump.type === 64)
|
|
2306
|
-
// vsf
|
|
2307
|
-
for (let i = 1; i <= 8; i++) {
|
|
2308
|
-
let circ = pump.circuits.getItemById(i);
|
|
2309
|
-
setPumpConfig.payload[i * 2 + 3] = circ.circuit || 0;
|
|
2310
|
-
if (circ.units === 0)
|
|
2311
|
-
// gpm
|
|
2312
|
-
setPumpConfig.payload[i * 2 + 4] = circ.flow || 30;
|
|
2313
|
-
else {
|
|
2314
|
-
// rpm
|
|
2315
|
-
setPumpConfig.payload[4] =
|
|
2316
|
-
setPumpConfig.payload[4] << i - 1; // set rpm/gpm flag
|
|
2317
|
-
setPumpConfig.payload[i * 2 + 4] = Math.floor(circ.speed / 256) || 3;
|
|
2318
|
-
setPumpConfig.payload[i + 21] =
|
|
2319
|
-
circ.speed - ((setPumpConfig.payload[i * 2 + 4] * 256)) || 232;
|
|
2320
2458
|
}
|
|
2321
2459
|
}
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
setPumpConfig.payload[2] = body.capacity / 1000 || 15;
|
|
2328
|
-
setPumpConfig.payload[21] = pump.manualFilterGPM || 30;
|
|
2329
|
-
setPumpConfig.payload[22] = pump.primingSpeed || 55;
|
|
2330
|
-
setPumpConfig.payload[23] =
|
|
2331
|
-
pump.primingTime | pump.maxSystemTime << 4 || 5;
|
|
2332
|
-
setPumpConfig.payload[24] = pump.maxPressureIncrease || 10;
|
|
2333
|
-
setPumpConfig.payload[25] = pump.backwashFlow || 60;
|
|
2334
|
-
setPumpConfig.payload[26] = pump.backwashTime || 5;
|
|
2335
|
-
setPumpConfig.payload[27] = pump.rinseTime || 1;
|
|
2336
|
-
setPumpConfig.payload[28] = pump.vacuumFlow || 50;
|
|
2337
|
-
setPumpConfig.payload[30] = pump.vacuumTime || 10;
|
|
2338
|
-
for (let i = 1; i <= 8; i++) {
|
|
2339
|
-
let circ = pump.circuits.getItemById(i);
|
|
2340
|
-
setPumpConfig.payload[i * 2 + 3] = circ.circuit || 0;
|
|
2341
|
-
setPumpConfig.payload[i * 2 + 4] = circ.flow || 15;
|
|
2342
|
-
}
|
|
2343
|
-
}
|
|
2344
|
-
const pumpConfigRequest = Outbound.create({
|
|
2345
|
-
action: 216,
|
|
2346
|
-
payload: [pump.id],
|
|
2347
|
-
retries: 2,
|
|
2348
|
-
response: true
|
|
2349
|
-
});
|
|
2350
|
-
return [setPumpConfig, pumpConfigRequest];
|
|
2351
|
-
}
|
|
2352
|
-
public setType(pump: Pump, pumpType: number) {
|
|
2353
|
-
pump.type = pumpType;
|
|
2354
|
-
// pump.circuits.clear(); // reset circuits
|
|
2355
|
-
this.setPump(pump);
|
|
2356
|
-
let spump = state.pumps.getItemById(pump.id, true);
|
|
2357
|
-
spump.type = pump.type;
|
|
2358
|
-
spump.status = 0;
|
|
2460
|
+
}
|
|
2461
|
+
catch (err) {
|
|
2462
|
+
logger.error(`Error setting pump: ${err.message}`);
|
|
2463
|
+
return Promise.reject(err);
|
|
2464
|
+
}
|
|
2359
2465
|
}
|
|
2360
|
-
|
|
2466
|
+
//private createPumpConfigMessages(pump: Pump): Outbound[] {
|
|
2467
|
+
// // [165,33,16,34,155,46],[1,128,0,2,0,16,12,6,7,1,9,4,11,11,3,128,8,0,2,18,2,3,128,8,196,184,232,152,188,238,232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[9,75]
|
|
2468
|
+
// const setPumpConfig = Outbound.create({
|
|
2469
|
+
// action: 155,
|
|
2470
|
+
// payload: [pump.id, pump.type, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
2471
|
+
// retries: 2,
|
|
2472
|
+
// response: true
|
|
2473
|
+
// });
|
|
2474
|
+
// if (pump.type === 128) {
|
|
2475
|
+
// // vs
|
|
2476
|
+
// //[165, 1, 16, 33, 155, 47]
|
|
2477
|
+
// //[1, 128, 0, 0, 0, 6, 10, 1, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 190, 134, 0, 0, 0, 0, 0, 0, 232, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
2478
|
+
// //[4, 109]
|
|
2479
|
+
// setPumpConfig.payload[2] = pump.primingTime || 0;
|
|
2480
|
+
// setPumpConfig.payload[21] = Math.floor(pump.primingSpeed / 256) || 3;
|
|
2481
|
+
// setPumpConfig.payload[30] =
|
|
2482
|
+
// pump.primingSpeed - Math.floor(pump.primingSpeed / 256) * 256 || 232;
|
|
2483
|
+
// for (let i = 1; i <= 8; i++) {
|
|
2484
|
+
// let circ = pump.circuits.getItemById(i);
|
|
2485
|
+
// setPumpConfig.payload[i * 2 + 3] = circ.circuit || 0;
|
|
2486
|
+
// setPumpConfig.payload[i * 2 + 4] = Math.floor(circ.speed / 256) || 3;
|
|
2487
|
+
// setPumpConfig.payload[i + 21] =
|
|
2488
|
+
// (circ.speed - (setPumpConfig.payload[i * 2 + 4] * 256)) || 232;
|
|
2489
|
+
// }
|
|
2490
|
+
// }
|
|
2491
|
+
// else if (pump.type === 64)
|
|
2492
|
+
// // vsf
|
|
2493
|
+
// for (let i = 1; i <= 8; i++) {
|
|
2494
|
+
// let circ = pump.circuits.getItemById(i);
|
|
2495
|
+
// setPumpConfig.payload[i * 2 + 3] = circ.circuit || 0;
|
|
2496
|
+
// if (circ.units === 0)
|
|
2497
|
+
// // gpm
|
|
2498
|
+
// setPumpConfig.payload[i * 2 + 4] = circ.flow || 30;
|
|
2499
|
+
// else {
|
|
2500
|
+
// // rpm
|
|
2501
|
+
// setPumpConfig.payload[4] =
|
|
2502
|
+
// setPumpConfig.payload[4] << i - 1; // set rpm/gpm flag
|
|
2503
|
+
// setPumpConfig.payload[i * 2 + 4] = Math.floor(circ.speed / 256) || 3;
|
|
2504
|
+
// setPumpConfig.payload[i + 21] =
|
|
2505
|
+
// circ.speed - ((setPumpConfig.payload[i * 2 + 4] * 256)) || 232;
|
|
2506
|
+
// }
|
|
2507
|
+
// }
|
|
2508
|
+
// else if (pump.type >= 1 && pump.type < 64) {
|
|
2509
|
+
// // vf
|
|
2510
|
+
// setPumpConfig.payload[1] = pump.backgroundCircuit || 6;
|
|
2511
|
+
// setPumpConfig.payload[3] = pump.turnovers || 2;
|
|
2512
|
+
// const body = sys.bodies.getItemById(1, sys.equipment.maxBodies >= 1);
|
|
2513
|
+
// setPumpConfig.payload[2] = body.capacity / 1000 || 15;
|
|
2514
|
+
// setPumpConfig.payload[21] = pump.manualFilterGPM || 30;
|
|
2515
|
+
// setPumpConfig.payload[22] = pump.primingSpeed || 55;
|
|
2516
|
+
// setPumpConfig.payload[23] =
|
|
2517
|
+
// pump.primingTime | pump.maxSystemTime << 4 || 5;
|
|
2518
|
+
// setPumpConfig.payload[24] = pump.maxPressureIncrease || 10;
|
|
2519
|
+
// setPumpConfig.payload[25] = pump.backwashFlow || 60;
|
|
2520
|
+
// setPumpConfig.payload[26] = pump.backwashTime || 5;
|
|
2521
|
+
// setPumpConfig.payload[27] = pump.rinseTime || 1;
|
|
2522
|
+
// setPumpConfig.payload[28] = pump.vacuumFlow || 50;
|
|
2523
|
+
// setPumpConfig.payload[30] = pump.vacuumTime || 10;
|
|
2524
|
+
// for (let i = 1; i <= 8; i++) {
|
|
2525
|
+
// let circ = pump.circuits.getItemById(i);
|
|
2526
|
+
// setPumpConfig.payload[i * 2 + 3] = circ.circuit || 0;
|
|
2527
|
+
// setPumpConfig.payload[i * 2 + 4] = circ.flow || 15;
|
|
2528
|
+
// }
|
|
2529
|
+
// }
|
|
2530
|
+
// const pumpConfigRequest = Outbound.create({
|
|
2531
|
+
// action: 216,
|
|
2532
|
+
// payload: [pump.id],
|
|
2533
|
+
// retries: 2,
|
|
2534
|
+
// response: true
|
|
2535
|
+
// });
|
|
2536
|
+
// return [setPumpConfig, pumpConfigRequest];
|
|
2537
|
+
//}
|
|
2538
|
+
//public setType(pump: Pump, pumpType: number) {
|
|
2539
|
+
// pump.type = pumpType;
|
|
2540
|
+
// // pump.circuits.clear(); // reset circuits
|
|
2541
|
+
// this.setPump(pump);
|
|
2542
|
+
// let spump = state.pumps.getItemById(pump.id, true);
|
|
2543
|
+
// spump.type = pump.type;
|
|
2544
|
+
// spump.status = 0;
|
|
2545
|
+
//}
|
|
2546
|
+
public async deletePumpAsync(data: any): Promise<Pump> {
|
|
2361
2547
|
let id = parseInt(data.id, 10);
|
|
2362
2548
|
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`deletePumpAsync: Pump ${id} is not valid.`, 0, `pump`));
|
|
2363
2549
|
let pump = sys.pumps.getItemById(id, false);
|
|
@@ -2368,13 +2554,13 @@ class TouchPumpCommands extends PumpCommands {
|
|
|
2368
2554
|
retries: 2,
|
|
2369
2555
|
response: true
|
|
2370
2556
|
});
|
|
2371
|
-
return new Promise<Pump>((resolve, reject) => {
|
|
2557
|
+
return new Promise<Pump>(async (resolve, reject) => {
|
|
2372
2558
|
outc.onComplete = (err, msg) => {
|
|
2373
2559
|
if (err) reject(err);
|
|
2374
2560
|
else {
|
|
2375
2561
|
sys.pumps.removeItemById(id);
|
|
2376
2562
|
state.pumps.removeItemById(id);
|
|
2377
|
-
resolve(sys.pumps.getItemById(id,false));
|
|
2563
|
+
resolve(sys.pumps.getItemById(id, false));
|
|
2378
2564
|
const pumpConfigRequest = Outbound.create({
|
|
2379
2565
|
action: 216,
|
|
2380
2566
|
payload: [id],
|
|
@@ -2384,7 +2570,7 @@ class TouchPumpCommands extends PumpCommands {
|
|
|
2384
2570
|
conn.queueSendMessage(pumpConfigRequest);
|
|
2385
2571
|
}
|
|
2386
2572
|
};
|
|
2387
|
-
|
|
2573
|
+
await outc.sendAsync();
|
|
2388
2574
|
});
|
|
2389
2575
|
}
|
|
2390
2576
|
}
|
|
@@ -2446,167 +2632,165 @@ class TouchHeaterCommands extends HeaterCommands {
|
|
|
2446
2632
|
}
|
|
2447
2633
|
}
|
|
2448
2634
|
// RKS: Not sure what to do with this as the heater data for Touch isn't actually processed anywhere.
|
|
2449
|
-
public async setHeaterAsync(obj: any): Promise<Heater> {
|
|
2635
|
+
public async setHeaterAsync(obj: any, send: boolean = true): Promise<Heater> {
|
|
2450
2636
|
if (obj.master === 1 || parseInt(obj.id, 10) > 255) return super.setHeaterAsync(obj);
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
break;
|
|
2512
|
-
}
|
|
2637
|
+
let id = typeof obj.id === 'undefined' ? -1 : parseInt(obj.id, 10);
|
|
2638
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Heater Id is not valid.', obj.id, 'Heater'));
|
|
2639
|
+
let heater: Heater;
|
|
2640
|
+
let address: number;
|
|
2641
|
+
let htype;
|
|
2642
|
+
let out = Outbound.create({
|
|
2643
|
+
action: 162,
|
|
2644
|
+
payload: [5, 0, 0],
|
|
2645
|
+
retries: 2,
|
|
2646
|
+
// I am assuming that there should be an action 34 when the 162 is sent but I do not have this
|
|
2647
|
+
// data.
|
|
2648
|
+
response: Response.create({ dest: -1, action: 34 })
|
|
2649
|
+
});
|
|
2650
|
+
if (id <= 0) {
|
|
2651
|
+
// Touch only supports two installed heaters. So the type determines the id.
|
|
2652
|
+
if (sys.heaters.length > sys.equipment.maxHeaters) return Promise.reject(new InvalidEquipmentDataError('The maximum number of heaters are already installed.', 'Heater', sys.heaters.length));
|
|
2653
|
+
htype = sys.board.valueMaps.heaterTypes.findItem(obj.type);
|
|
2654
|
+
if (typeof htype === 'undefined') return Promise.reject(new InvalidEquipmentDataError('Heater type is not valid.', 'Heater', obj.heaterType));
|
|
2655
|
+
// Check to see if we can find any heaters of this type already installed.
|
|
2656
|
+
if (sys.heaters.count(h => h.type === htype.val) > 0) return Promise.reject(new InvalidEquipmentDataError(`Only one ${htype.desc} heater can be installed`, 'Heater', htype));
|
|
2657
|
+
// Next we need to see if this heater is compatible with all the other heaters. For Touch you may only have the following combos.
|
|
2658
|
+
// 1 Gas + 1 Solar
|
|
2659
|
+
// 1 Gas + 1 Heatpump
|
|
2660
|
+
// 1 Hybrid
|
|
2661
|
+
|
|
2662
|
+
// Heater ids are as follows.
|
|
2663
|
+
// 1 = Gas Heater
|
|
2664
|
+
// 2 = Solar
|
|
2665
|
+
// 3 = UltraTemp (HEATPUMPCOM)
|
|
2666
|
+
// 4 = UltraTemp ETi (Hybrid)
|
|
2667
|
+
switch (htype.name) {
|
|
2668
|
+
case 'gas':
|
|
2669
|
+
id = 1;
|
|
2670
|
+
break;
|
|
2671
|
+
case 'solar':
|
|
2672
|
+
out.setPayloadByte(0, out.payload[0] | 0x02);
|
|
2673
|
+
// Set the start and stop temp delta.
|
|
2674
|
+
out.setPayloadByte(1, (obj.freeze ? 0x80 : 0x00) | (obj.coolingEnabled ? 0x20 : 0x00));
|
|
2675
|
+
out.setPayloadByte(2, ((obj.startTempDelta || 6) - 3 << 6) | ((obj.stopTempDelta || 3) - 2 << 1));
|
|
2676
|
+
id = 2;
|
|
2677
|
+
break;
|
|
2678
|
+
case 'ultratemp':
|
|
2679
|
+
case 'heatpump':
|
|
2680
|
+
address = 112;
|
|
2681
|
+
out.setPayloadByte(0, out.payload[0] | 0x02);
|
|
2682
|
+
out.setPayloadByte(1, out.payload[1] | 0x10 | (obj.coolingEnabled ? 0x20 : 0x00));
|
|
2683
|
+
id = 3;
|
|
2684
|
+
break;
|
|
2685
|
+
case 'hybrid':
|
|
2686
|
+
// If we are adding a hybrid heater this means that the gas heater is to be replaced. This means that only
|
|
2687
|
+
// a gas heater can be installed.
|
|
2688
|
+
if (sys.heaters.length > 1) return Promise.reject(new InvalidEquipmentDataError(`Hybrid heaters can only be installed by themselves`, 'Heater', htype));
|
|
2689
|
+
if (sys.heaters.getItemByIndex(0).type > 1) return Promise.reject(new InvalidEquipmentDataError(`Hybrid heaters can only replace the gas heater`, 'Heater', htype));
|
|
2690
|
+
out.setPayloadByte(0, 5);
|
|
2691
|
+
out.setPayloadByte(1, 16);
|
|
2692
|
+
// NOTE: byte 2 makes absolutely no sense. Perhaps this is because we have no idea what message action 16 is. This probably contains the rest of the info
|
|
2693
|
+
// for heaters on Touch panels.
|
|
2694
|
+
out.setPayloadByte(2, 118);
|
|
2695
|
+
id = 4;
|
|
2696
|
+
break;
|
|
2513
2697
|
}
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2698
|
+
}
|
|
2699
|
+
else {
|
|
2700
|
+
// This all works because there are 0 items that can be set on a Touch heater with the exception of a few items on solar. This means that the
|
|
2701
|
+
// first two bytes are calculated based upon the existing heaters.
|
|
2702
|
+
heater = sys.heaters.find(x => id === x.id);
|
|
2703
|
+
if (typeof heater === 'undefined') return Promise.reject(new InvalidEquipmentIdError(`Heater #${id} is not installed and cannot be updated.`, id, 'Heater'));
|
|
2704
|
+
// So here we go with the settings.
|
|
2705
|
+
htype = sys.board.valueMaps.heaterTypes.findItem(heater.type);
|
|
2706
|
+
switch (htype.name) {
|
|
2707
|
+
case 'gas':
|
|
2708
|
+
break;
|
|
2709
|
+
case 'solar':
|
|
2710
|
+
out.setPayloadByte(0, out.payload[0] | 0x02);
|
|
2711
|
+
// Set the start and stop temp delta.
|
|
2712
|
+
out.setPayloadByte(1, (obj.freeze ? 0x80 : 0x00) | (obj.coolingEnabled ? 0x20 : 0x00));
|
|
2713
|
+
out.setPayloadByte(2, ((obj.startTempDelta || 6) - 3 << 6) | ((obj.stopTempDelta || 3) - 2 << 1));
|
|
2714
|
+
break;
|
|
2715
|
+
case 'ultratemp':
|
|
2716
|
+
case 'heatpump':
|
|
2717
|
+
address = 112;
|
|
2718
|
+
out.setPayloadByte(0, out.payload[0] | 0x02);
|
|
2719
|
+
out.setPayloadByte(1, out.payload[1] | 0x10 | (obj.coolingEnabled ? 0x20 : 0x00));
|
|
2720
|
+
break;
|
|
2721
|
+
case 'hybrid':
|
|
2722
|
+
address = 112;
|
|
2723
|
+
out.setPayloadByte(0, 5);
|
|
2724
|
+
out.setPayloadByte(1, 16);
|
|
2725
|
+
// NOTE: byte 2 makes absolutely no sense. Perhaps this is because we have no idea what message action 144/16 is. This probably contains the rest of the info
|
|
2726
|
+
// for heaters on Touch panels.
|
|
2727
|
+
out.setPayloadByte(2, 118);
|
|
2728
|
+
break;
|
|
2545
2729
|
}
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2730
|
+
}
|
|
2731
|
+
// Set the bytes from the existing installed heaters.
|
|
2732
|
+
for (let i = 0; i < sys.heaters.length; i++) {
|
|
2733
|
+
let h = sys.heaters.getItemByIndex(i);
|
|
2734
|
+
if (h.id === id) continue;
|
|
2735
|
+
let ht = sys.board.valueMaps.heaterTypes.transform(h.type);
|
|
2736
|
+
switch (ht.name) {
|
|
2737
|
+
case 'gas':
|
|
2738
|
+
break;
|
|
2739
|
+
case 'solar':
|
|
2740
|
+
out.setPayloadByte(0, out.payload[0] | 0x02);
|
|
2741
|
+
out.setPayloadByte(1, (h.freeze ? 0x80 : 0x00) | (h.coolingEnabled ? 0x20 : 0x00));
|
|
2742
|
+
out.setPayloadByte(2, ((h.startTempDelta || 6) - 3 << 6) | ((h.stopTempDelta || 3) - 2 << 1));
|
|
2743
|
+
break;
|
|
2744
|
+
case 'ultratemp':
|
|
2745
|
+
case 'heatpump':
|
|
2746
|
+
out.setPayloadByte(0, out.payload[0] | 0x02);
|
|
2747
|
+
out.setPayloadByte(1, out.payload[1] | 0x10 | (h.coolingEnabled ? 0x20 : 0x00));
|
|
2748
|
+
break;
|
|
2749
|
+
case 'hybrid':
|
|
2750
|
+
break;
|
|
2567
2751
|
}
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
heater.address = address;
|
|
2588
|
-
heater.master = 0;
|
|
2589
|
-
heater.body = sys.equipment.shared ? 32 : 0;
|
|
2590
|
-
sys.board.heaters.updateHeaterServices();
|
|
2591
|
-
sys.board.heaters.syncHeaterStates();
|
|
2592
|
-
resolve(heater);
|
|
2593
|
-
}
|
|
2752
|
+
}
|
|
2753
|
+
if (sl.enabled && send) {
|
|
2754
|
+
await sl.controller.setEquipmentAsync(obj, 'heater');
|
|
2755
|
+
}
|
|
2756
|
+
else if (send) {
|
|
2757
|
+
await out.sendAsync();
|
|
2758
|
+
}
|
|
2759
|
+
heater = sys.heaters.getItemById(id, true);
|
|
2760
|
+
let sheater = state.heaters.getItemById(id, true);
|
|
2761
|
+
for (var s in obj) {
|
|
2762
|
+
switch (s) {
|
|
2763
|
+
case 'id':
|
|
2764
|
+
case 'name':
|
|
2765
|
+
case 'type':
|
|
2766
|
+
case 'address':
|
|
2767
|
+
break;
|
|
2768
|
+
default:
|
|
2769
|
+
heater[s] = obj[s];
|
|
2770
|
+
break;
|
|
2594
2771
|
}
|
|
2595
|
-
}
|
|
2772
|
+
}
|
|
2773
|
+
sheater.name = heater.name = typeof obj.name !== 'undefined' ? obj.name : heater.name;
|
|
2774
|
+
sheater.type = heater.type = htype.val;
|
|
2775
|
+
heater.address = address;
|
|
2776
|
+
heater.master = 0;
|
|
2777
|
+
heater.body = sys.equipment.shared ? 32 : 0;
|
|
2778
|
+
sys.board.heaters.updateHeaterServices();
|
|
2779
|
+
sys.board.heaters.syncHeaterStates();
|
|
2780
|
+
return heater;
|
|
2596
2781
|
}
|
|
2597
2782
|
public async deleteHeaterAsync(obj: any): Promise<Heater> {
|
|
2598
2783
|
if (utils.makeBool(obj.master === 1 || parseInt(obj.id, 10) > 255)) return super.deleteHeaterAsync(obj);
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
});
|
|
2784
|
+
let id = parseInt(obj.id, 10);
|
|
2785
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Cannot delete. Heater Id is not valid.', obj.id, 'Heater'));
|
|
2786
|
+
let heater = sys.heaters.getItemById(id);
|
|
2787
|
+
heater.isActive = false;
|
|
2788
|
+
sys.heaters.removeItemById(id);
|
|
2789
|
+
state.heaters.removeItemById(id);
|
|
2790
|
+
sys.board.heaters.updateHeaterServices();
|
|
2791
|
+
sys.board.heaters.syncHeaterStates();
|
|
2792
|
+
return heater;
|
|
2793
|
+
|
|
2610
2794
|
}
|
|
2611
2795
|
public updateHeaterServices() {
|
|
2612
2796
|
let htypes = sys.board.heaters.getInstalledHeaterTypes();
|
|
@@ -2674,186 +2858,222 @@ class TouchHeaterCommands extends HeaterCommands {
|
|
|
2674
2858
|
}
|
|
2675
2859
|
}
|
|
2676
2860
|
sys.board.valueMaps.heatSources.set(32, { name: 'nochange', desc: 'No Change' });
|
|
2861
|
+
// Now set the body data.
|
|
2862
|
+
for (let i = 0; i < sys.bodies.length; i++) {
|
|
2863
|
+
let body = sys.bodies.getItemByIndex(i);
|
|
2864
|
+
let btemp = state.temps.bodies.getItemById(body.id, body.isActive !== false);
|
|
2865
|
+
let opts = sys.board.heaters.getInstalledHeaterTypes(body.id);
|
|
2866
|
+
btemp.heaterOptions = opts;
|
|
2867
|
+
}
|
|
2677
2868
|
this.setActiveTempSensors();
|
|
2678
2869
|
}
|
|
2679
2870
|
}
|
|
2680
2871
|
class TouchChemControllerCommands extends ChemControllerCommands {
|
|
2681
2872
|
// This method is not meant to be called directly. The setChemControllerAsync method does some routing to set IntelliChem correctly
|
|
2682
2873
|
// if an OCP is involved. This is the reason that the method is protected.
|
|
2683
|
-
protected async setIntelliChemAsync(data: any): Promise<ChemController> {
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
let
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
if (typeof data.lsiRange
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
if (typeof data.ph.tolerance
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
if (typeof data.orp.tolerance
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2874
|
+
protected async setIntelliChemAsync(data: any, send: boolean = true): Promise<ChemController> {
|
|
2875
|
+
try {
|
|
2876
|
+
|
|
2877
|
+
let chem = sys.board.chemControllers.findChemController(data);
|
|
2878
|
+
let ichemType = sys.board.valueMaps.chemControllerTypes.encode('intellichem');
|
|
2879
|
+
if (typeof chem === 'undefined') {
|
|
2880
|
+
// We are adding an IntelliChem. Check to see how many intellichems we have.
|
|
2881
|
+
let arr = sys.chemControllers.toArray();
|
|
2882
|
+
let count = 0;
|
|
2883
|
+
for (let i = 0; i < arr.length; i++) {
|
|
2884
|
+
let cc: ChemController = arr[i];
|
|
2885
|
+
if (cc.type === ichemType) count++;
|
|
2886
|
+
}
|
|
2887
|
+
if (count >= sys.equipment.maxChemControllers) return Promise.reject(new InvalidEquipmentDataError(`The max number of IntelliChem controllers has been reached: ${sys.equipment.maxChemControllers}`, 'chemController', sys.equipment.maxChemControllers));
|
|
2888
|
+
chem = sys.chemControllers.getItemById(data.id);
|
|
2889
|
+
}
|
|
2890
|
+
let address = typeof data.address !== 'undefined' ? parseInt(data.address, 10) : chem.address;
|
|
2891
|
+
if (typeof address === 'undefined' || isNaN(address) || (address < 144 || address > 158)) return Promise.reject(new InvalidEquipmentDataError(`Invalid IntelliChem address`, 'chemController', address));
|
|
2892
|
+
if (typeof sys.chemControllers.find(elem => elem.id !== data.id && elem.type === ichemType && elem.address === address) !== 'undefined') return Promise.reject(new InvalidEquipmentDataError(`Invalid IntelliChem address: Address is used on another IntelliChem`, 'chemController', address));
|
|
2893
|
+
// Now lets do all our validation to the incoming chem controller data.
|
|
2894
|
+
let name = typeof data.name !== 'undefined' ? data.name : chem.name || `IntelliChem - ${address - 143}`;
|
|
2895
|
+
let type = sys.board.valueMaps.chemControllerTypes.transformByName('intellichem');
|
|
2896
|
+
// So now we are down to the nitty gritty setting the data for the REM Chem controller.
|
|
2897
|
+
let calciumHardness = typeof data.calciumHardness !== 'undefined' ? parseInt(data.calciumHardness, 10) : chem.calciumHardness;
|
|
2898
|
+
let cyanuricAcid = typeof data.cyanuricAcid !== 'undefined' ? parseInt(data.cyanuricAcid, 10) : chem.cyanuricAcid;
|
|
2899
|
+
let alkalinity = typeof data.alkalinity !== 'undefined' ? parseInt(data.alkalinity, 10) : chem.alkalinity;
|
|
2900
|
+
let borates = typeof data.borates !== 'undefined' ? parseInt(data.borates, 10) : chem.borates || 0;
|
|
2901
|
+
let body = sys.board.bodies.mapBodyAssociation(typeof data.body === 'undefined' ? chem.body : data.body);
|
|
2902
|
+
if (typeof body === 'undefined') return Promise.reject(new InvalidEquipmentDataError(`Invalid body assignment`, 'chemController', data.body || chem.body));
|
|
2903
|
+
// Do a final validation pass so we dont send this off in a mess.
|
|
2904
|
+
if (isNaN(calciumHardness)) return Promise.reject(new InvalidEquipmentDataError(`Invalid calcium hardness`, 'chemController', calciumHardness));
|
|
2905
|
+
if (isNaN(cyanuricAcid)) return Promise.reject(new InvalidEquipmentDataError(`Invalid cyanuric acid`, 'chemController', cyanuricAcid));
|
|
2906
|
+
if (isNaN(alkalinity)) return Promise.reject(new InvalidEquipmentDataError(`Invalid alkalinity`, 'chemController', alkalinity));
|
|
2907
|
+
if (isNaN(borates)) return Promise.reject(new InvalidEquipmentDataError(`Invalid borates`, 'chemController', borates));
|
|
2908
|
+
let schem = state.chemControllers.getItemById(chem.id, true);
|
|
2909
|
+
let pHSetpoint = typeof data.ph !== 'undefined' && typeof data.ph.setpoint !== 'undefined' ? parseFloat(data.ph.setpoint) : chem.ph.setpoint;
|
|
2910
|
+
let orpSetpoint = typeof data.orp !== 'undefined' && typeof data.orp.setpoint !== 'undefined' ? parseInt(data.orp.setpoint, 10) : chem.orp.setpoint;
|
|
2911
|
+
let lsiRange = typeof data.lsiRange !== 'undefined' ? data.lsiRange : chem.lsiRange || {};
|
|
2912
|
+
if (typeof data.lsiRange !== 'undefined') {
|
|
2913
|
+
if (typeof data.lsiRange.enabled !== 'undefined') lsiRange.enabled = utils.makeBool(data.lsiRange.enabled);
|
|
2914
|
+
if (typeof data.lsiRange.low === 'number') lsiRange.low = parseFloat(data.lsiRange.low);
|
|
2915
|
+
if (typeof data.lsiRange.high === 'number') lsiRange.high = parseFloat(data.lsiRange.high);
|
|
2916
|
+
}
|
|
2917
|
+
if (isNaN(pHSetpoint) || pHSetpoint > type.ph.max || pHSetpoint < type.ph.min) return Promise.reject(new InvalidEquipmentDataError(`Invalid pH setpoint`, 'ph.setpoint', pHSetpoint));
|
|
2918
|
+
if (isNaN(orpSetpoint) || orpSetpoint > type.orp.max || orpSetpoint < type.orp.min) return Promise.reject(new InvalidEquipmentDataError(`Invalid orp setpoint`, 'orp.setpoint', orpSetpoint));
|
|
2919
|
+
let phTolerance = typeof data.ph.tolerance !== 'undefined' ? data.ph.tolerance : chem.ph.tolerance;
|
|
2920
|
+
let orpTolerance = typeof data.orp.tolerance !== 'undefined' ? data.orp.tolerance : chem.orp.tolerance;
|
|
2921
|
+
if (typeof data.ph.tolerance !== 'undefined') {
|
|
2922
|
+
if (typeof data.ph.tolerance.enabled !== 'undefined') phTolerance.enabled = utils.makeBool(data.ph.tolerance.enabled);
|
|
2923
|
+
if (typeof data.ph.tolerance.low !== 'undefined') phTolerance.low = parseFloat(data.ph.tolerance.low);
|
|
2924
|
+
if (typeof data.ph.tolerance.high !== 'undefined') phTolerance.high = parseFloat(data.ph.tolerance.high);
|
|
2925
|
+
if (isNaN(phTolerance.low)) phTolerance.low = type.ph.min;
|
|
2926
|
+
if (isNaN(phTolerance.high)) phTolerance.high = type.ph.max;
|
|
2927
|
+
}
|
|
2928
|
+
if (typeof data.orp.tolerance !== 'undefined') {
|
|
2929
|
+
if (typeof data.orp.tolerance.enabled !== 'undefined') orpTolerance.enabled = utils.makeBool(data.orp.tolerance.enabled);
|
|
2930
|
+
if (typeof data.orp.tolerance.low !== 'undefined') orpTolerance.low = parseFloat(data.orp.tolerance.low);
|
|
2931
|
+
if (typeof data.orp.tolerance.high !== 'undefined') orpTolerance.high = parseFloat(data.orp.tolerance.high);
|
|
2932
|
+
if (isNaN(orpTolerance.low)) orpTolerance.low = type.orp.min;
|
|
2933
|
+
if (isNaN(orpTolerance.high)) orpTolerance.high = type.orp.max;
|
|
2934
|
+
}
|
|
2935
|
+
let phEnabled = typeof data.ph.enabled !== 'undefined' ? utils.makeBool(data.ph.enabled) : chem.ph.enabled;
|
|
2936
|
+
let orpEnabled = typeof data.orp.enabled !== 'undefined' ? utils.makeBool(data.orp.enabled) : chem.orp.enabled;
|
|
2937
|
+
let siCalcType = typeof data.siCalcType !== 'undefined' ? sys.board.valueMaps.siCalcTypes.encode(data.siCalcType, 0) : chem.siCalcType;
|
|
2938
|
+
|
|
2939
|
+
let saltLevel = (state.chlorinators.length > 0) ? state.chlorinators.getItemById(1).saltLevel || 1000 : 1000
|
|
2940
|
+
chem.ph.tank.capacity = 6;
|
|
2941
|
+
chem.orp.tank.capacity = 6;
|
|
2942
|
+
let acidTankLevel = typeof data.ph !== 'undefined' && typeof data.ph.tank !== 'undefined' && typeof data.ph.tank.level !== 'undefined' ? parseInt(data.ph.tank.level, 10) : schem.ph.tank.level;
|
|
2943
|
+
let orpTankLevel = typeof data.orp !== 'undefined' && typeof data.orp.tank !== 'undefined' && typeof data.orp.tank.level !== 'undefined' ? parseInt(data.orp.tank.level, 10) : schem.orp.tank.level;
|
|
2944
|
+
// OCP needs to set the IntelliChem as active so it knows that it exists
|
|
2945
|
+
if (sl.enabled && send) {
|
|
2946
|
+
if (!schem.isActive) {
|
|
2947
|
+
await sl.controller.setEquipmentAsync({intellichem: true}, 'misc');
|
|
2948
|
+
}
|
|
2949
|
+
// todo: need to add chem controller logic for setting ph/orp/etc
|
|
2950
|
+
}
|
|
2951
|
+
else if (send) {
|
|
2952
|
+
let out = Outbound.create({
|
|
2953
|
+
action: 211,
|
|
2954
|
+
payload: [],
|
|
2955
|
+
retries: 3, // We are going to try 4 times.
|
|
2956
|
+
response: Response.create({ protocol: Protocol.IntelliChem, action: 1, payload: [211] }),
|
|
2957
|
+
onAbort: () => { }
|
|
2958
|
+
});
|
|
2959
|
+
out.insertPayloadBytes(0, 0, 22);
|
|
2960
|
+
out.setPayloadByte(0, address - 144);
|
|
2961
|
+
out.setPayloadByte(1, Math.floor((pHSetpoint * 100) / 256) || 0);
|
|
2962
|
+
out.setPayloadByte(2, Math.round((pHSetpoint * 100) % 256) || 0);
|
|
2963
|
+
out.setPayloadByte(3, Math.floor(orpSetpoint / 256) || 0);
|
|
2964
|
+
out.setPayloadByte(4, Math.round(orpSetpoint % 256) || 0);
|
|
2965
|
+
out.setPayloadByte(5, phEnabled ? acidTankLevel + 1 : 0);
|
|
2966
|
+
out.setPayloadByte(6, orpEnabled ? orpTankLevel + 1 : 0);
|
|
2967
|
+
out.setPayloadByte(7, Math.floor(calciumHardness / 256) || 0);
|
|
2968
|
+
out.setPayloadByte(8, Math.round(calciumHardness % 256) || 0);
|
|
2969
|
+
out.setPayloadByte(9, parseInt(data.cyanuricAcid, 10), chem.cyanuricAcid || 0);
|
|
2970
|
+
out.setPayloadByte(11, Math.floor(alkalinity / 256) || 0);
|
|
2971
|
+
out.setPayloadByte(12, Math.round(alkalinity % 256) || 0);
|
|
2972
|
+
out.setPayloadByte(13, Math.round(saltLevel / 50) || 20);
|
|
2973
|
+
await out.sendAsync();
|
|
2974
|
+
}
|
|
2975
|
+
chem = sys.chemControllers.getItemById(data.id, true);
|
|
2976
|
+
schem = state.chemControllers.getItemById(data.id, true);
|
|
2977
|
+
chem.master = 0;
|
|
2978
|
+
// Copy the data back to the chem object.
|
|
2979
|
+
schem.name = chem.name = name;
|
|
2980
|
+
schem.type = chem.type = sys.board.valueMaps.chemControllerTypes.encode('intellichem');
|
|
2981
|
+
chem.calciumHardness = calciumHardness;
|
|
2982
|
+
chem.cyanuricAcid = cyanuricAcid;
|
|
2983
|
+
chem.alkalinity = alkalinity;
|
|
2984
|
+
chem.borates = borates;
|
|
2985
|
+
chem.body = schem.body = body.val;
|
|
2986
|
+
schem.isActive = chem.isActive = true;
|
|
2987
|
+
chem.lsiRange.enabled = lsiRange.enabled;
|
|
2988
|
+
chem.lsiRange.low = lsiRange.low;
|
|
2989
|
+
chem.lsiRange.high = lsiRange.high;
|
|
2990
|
+
chem.ph.tolerance.enabled = phTolerance.enabled;
|
|
2991
|
+
chem.ph.tolerance.low = phTolerance.low;
|
|
2992
|
+
chem.ph.tolerance.high = phTolerance.high;
|
|
2993
|
+
chem.orp.tolerance.enabled = orpTolerance.enabled;
|
|
2994
|
+
chem.orp.tolerance.low = orpTolerance.low;
|
|
2995
|
+
chem.orp.tolerance.high = orpTolerance.high;
|
|
2996
|
+
chem.ph.setpoint = pHSetpoint;
|
|
2997
|
+
chem.orp.setpoint = orpSetpoint;
|
|
2998
|
+
schem.siCalcType = chem.siCalcType = siCalcType;
|
|
2999
|
+
chem.address = schem.address = address;
|
|
3000
|
+
chem.name = schem.name = name;
|
|
3001
|
+
chem.flowSensor.enabled = false;
|
|
3002
|
+
await sys.board.bodies.setBodyAsync(sys.bodies.getItemById(1, false));
|
|
3003
|
+
return chem;
|
|
3004
|
+
|
|
3005
|
+
} catch (err) {
|
|
3006
|
+
logger.error(`Error setting chem: ${err.message}`);
|
|
3007
|
+
return Promise.reject(err);
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
public async deleteChemControllerAsync(data: any): Promise<ChemController> {
|
|
3011
|
+
try {
|
|
3012
|
+
|
|
3013
|
+
let id = typeof data.id !== 'undefined' ? parseInt(data.id, 10) : -1;
|
|
3014
|
+
if (typeof id === 'undefined' || isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid Chem Controller Id`, id, 'chemController'));
|
|
3015
|
+
let chem = sys.board.chemControllers.findChemController(data);
|
|
3016
|
+
if (chem.master === 1) return super.deleteChemControllerAsync(data);
|
|
2754
3017
|
let out = Outbound.create({
|
|
2755
3018
|
action: 211,
|
|
2756
|
-
payload: [],
|
|
2757
|
-
retries: 3, // We are going to try 4 times.
|
|
2758
3019
|
response: Response.create({ protocol: Protocol.IntelliChem, action: 1, payload: [211] }),
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
if (err) reject(err);
|
|
2762
|
-
else {
|
|
2763
|
-
chem = sys.chemControllers.getItemById(data.id, true);
|
|
2764
|
-
schem = state.chemControllers.getItemById(data.id, true);
|
|
2765
|
-
chem.master = 0;
|
|
2766
|
-
// Copy the data back to the chem object.
|
|
2767
|
-
schem.name = chem.name = name;
|
|
2768
|
-
schem.type = chem.type = sys.board.valueMaps.chemControllerTypes.encode('intellichem');
|
|
2769
|
-
chem.calciumHardness = calciumHardness;
|
|
2770
|
-
chem.cyanuricAcid = cyanuricAcid;
|
|
2771
|
-
chem.alkalinity = alkalinity;
|
|
2772
|
-
chem.borates = borates;
|
|
2773
|
-
chem.body = schem.body = body.val;
|
|
2774
|
-
schem.isActive = chem.isActive = true;
|
|
2775
|
-
chem.lsiRange.enabled = lsiRange.enabled;
|
|
2776
|
-
chem.lsiRange.low = lsiRange.low;
|
|
2777
|
-
chem.lsiRange.high = lsiRange.high;
|
|
2778
|
-
chem.ph.tolerance.enabled = phTolerance.enabled;
|
|
2779
|
-
chem.ph.tolerance.low = phTolerance.low;
|
|
2780
|
-
chem.ph.tolerance.high = phTolerance.high;
|
|
2781
|
-
chem.orp.tolerance.enabled = orpTolerance.enabled;
|
|
2782
|
-
chem.orp.tolerance.low = orpTolerance.low;
|
|
2783
|
-
chem.orp.tolerance.high = orpTolerance.high;
|
|
2784
|
-
chem.ph.setpoint = pHSetpoint;
|
|
2785
|
-
chem.orp.setpoint = orpSetpoint;
|
|
2786
|
-
schem.siCalcType = chem.siCalcType = siCalcType;
|
|
2787
|
-
chem.address = schem.address = address;
|
|
2788
|
-
chem.name = schem.name = name;
|
|
2789
|
-
chem.flowSensor.enabled = false;
|
|
2790
|
-
sys.board.bodies.setBodyAsync(sys.bodies.getItemById(1, false))
|
|
2791
|
-
.then(()=>{resolve(chem)});
|
|
2792
|
-
}
|
|
2793
|
-
}
|
|
3020
|
+
retries: 3,
|
|
3021
|
+
payload: [],
|
|
2794
3022
|
});
|
|
3023
|
+
// I think this payload should delete the controller on Touch.
|
|
2795
3024
|
out.insertPayloadBytes(0, 0, 22);
|
|
2796
|
-
out.setPayloadByte(0, address - 144);
|
|
2797
|
-
out.setPayloadByte(1, Math.floor((
|
|
2798
|
-
out.setPayloadByte(2, Math.round((
|
|
2799
|
-
out.setPayloadByte(3, Math.floor(
|
|
2800
|
-
out.setPayloadByte(4, Math.round(
|
|
2801
|
-
out.setPayloadByte(5,
|
|
2802
|
-
out.setPayloadByte(6,
|
|
2803
|
-
out.setPayloadByte(7, Math.floor(calciumHardness / 256) || 0);
|
|
2804
|
-
out.setPayloadByte(8, Math.round(calciumHardness % 256) || 0);
|
|
2805
|
-
out.setPayloadByte(9,
|
|
2806
|
-
out.setPayloadByte(11, Math.floor(alkalinity / 256) || 0);
|
|
2807
|
-
out.setPayloadByte(12, Math.round(alkalinity % 256) || 0);
|
|
2808
|
-
out.setPayloadByte(13,
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
}
|
|
2812
|
-
public async deleteChemControllerAsync(data: any): Promise<ChemController> {
|
|
2813
|
-
let id = typeof data.id !== 'undefined' ? parseInt(data.id, 10) : -1;
|
|
2814
|
-
if (typeof id === 'undefined' || isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid Chem Controller Id`, id, 'chemController'));
|
|
2815
|
-
let chem = sys.board.chemControllers.findChemController(data);
|
|
2816
|
-
if (chem.master === 1) return super.deleteChemControllerAsync(data);
|
|
2817
|
-
return new Promise<ChemController>((resolve, reject) => {
|
|
2818
|
-
let out = Outbound.create({
|
|
2819
|
-
action: 211,
|
|
2820
|
-
response: Response.create({ protocol: Protocol.IntelliChem, action: 1, payload: [211] }),
|
|
2821
|
-
retries: 3,
|
|
2822
|
-
payload: [],
|
|
2823
|
-
onComplete: (err) => {
|
|
2824
|
-
if (err) { reject(err); }
|
|
2825
|
-
else {
|
|
2826
|
-
let schem = state.chemControllers.getItemById(id);
|
|
2827
|
-
chem.isActive = false;
|
|
2828
|
-
chem.ph.tank.capacity = chem.orp.tank.capacity = 6;
|
|
2829
|
-
chem.ph.tank.units = chem.orp.tank.units = '';
|
|
2830
|
-
schem.isActive = false;
|
|
2831
|
-
sys.board.bodies.setBodyAsync(sys.bodies.getItemById(1, false))
|
|
2832
|
-
.then(()=>{
|
|
2833
|
-
sys.chemControllers.removeItemById(id);
|
|
2834
|
-
state.chemControllers.removeItemById(id);
|
|
2835
|
-
resolve(chem);
|
|
2836
|
-
})
|
|
2837
|
-
.catch(()=>{reject(err);});
|
|
2838
|
-
}
|
|
3025
|
+
out.setPayloadByte(0, chem.address - 144 || 0);
|
|
3026
|
+
out.setPayloadByte(1, Math.floor((chem.ph.setpoint * 100) / 256) || 0);
|
|
3027
|
+
out.setPayloadByte(2, Math.round((chem.ph.setpoint * 100) % 256) || 0);
|
|
3028
|
+
out.setPayloadByte(3, Math.floor(chem.orp.setpoint / 256) || 0);
|
|
3029
|
+
out.setPayloadByte(4, Math.round(chem.orp.setpoint % 256) || 0);
|
|
3030
|
+
out.setPayloadByte(5, 0);
|
|
3031
|
+
out.setPayloadByte(6, 0);
|
|
3032
|
+
out.setPayloadByte(7, Math.floor(chem.calciumHardness / 256) || 0);
|
|
3033
|
+
out.setPayloadByte(8, Math.round(chem.calciumHardness % 256) || 0);
|
|
3034
|
+
out.setPayloadByte(9, chem.cyanuricAcid || 0);
|
|
3035
|
+
out.setPayloadByte(11, Math.floor(chem.alkalinity / 256) || 0);
|
|
3036
|
+
out.setPayloadByte(12, Math.round(chem.alkalinity % 256) || 0);
|
|
3037
|
+
out.setPayloadByte(13, 20);
|
|
3038
|
+
if (sl.enabled) {
|
|
3039
|
+
await sl.controller.setEquipmentAsync({intellichem: false}, 'misc');
|
|
2839
3040
|
}
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
});
|
|
3041
|
+
else {
|
|
3042
|
+
await out.sendAsync();
|
|
3043
|
+
}
|
|
3044
|
+
let schem = state.chemControllers.getItemById(id);
|
|
3045
|
+
chem.isActive = false;
|
|
3046
|
+
chem.ph.tank.capacity = chem.orp.tank.capacity = 6;
|
|
3047
|
+
chem.ph.tank.units = chem.orp.tank.units = '';
|
|
3048
|
+
schem.isActive = false;
|
|
3049
|
+
await sys.board.bodies.setBodyAsync(sys.bodies.getItemById(1, false));
|
|
3050
|
+
sys.chemControllers.removeItemById(id);
|
|
3051
|
+
state.chemControllers.removeItemById(id);
|
|
3052
|
+
return chem;
|
|
3053
|
+
|
|
3054
|
+
} catch (err) {
|
|
3055
|
+
logger.error(`Error deleting chem controller: ${err.message}`);
|
|
3056
|
+
return Promise.reject(err);
|
|
3057
|
+
}
|
|
2858
3058
|
}
|
|
2859
3059
|
}
|
|
3060
|
+
class TouchValveCommands extends ValveCommands {
|
|
3061
|
+
public async setValveAsync(obj: any, send: boolean = true): Promise<Valve> {
|
|
3062
|
+
try {
|
|
3063
|
+
let id = typeof obj.id !== 'undefined' ? parseInt(obj.id, 10) : -1;
|
|
3064
|
+
obj.master = 0;
|
|
3065
|
+
if (isNaN(id) || id <= 0) id = Math.max(sys.valves.getMaxId(false, 49) + 1, 50);
|
|
3066
|
+
|
|
3067
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`EasyTouch: Valve Id has not been defined ${id}`, obj.id, 'Valve'));
|
|
3068
|
+
let valve = sys.valves.getItemById(id, true);
|
|
3069
|
+
// Set all the valve properies.
|
|
3070
|
+
let vstate = state.valves.getItemById(valve.id, true);
|
|
3071
|
+
valve.isActive = true;
|
|
3072
|
+
valve.circuit = typeof obj.circuit !== 'undefined' ? obj.circuit : valve.circuit;
|
|
3073
|
+
valve.name = typeof obj.name !== 'undefined' ? obj.name : valve.name;
|
|
3074
|
+
valve.connectionId = typeof obj.connectionId ? obj.connectionId : valve.connectionId;
|
|
3075
|
+
|
|
3076
|
+
return valve;
|
|
3077
|
+
} catch (err) { logger.error(`Nixie: Error setting valve definition. ${err.message}`); return Promise.reject(err); }
|
|
3078
|
+
}
|
|
3079
|
+
}
|