nodejs-poolcontroller 7.7.0 → 8.0.1

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.
Files changed (82) hide show
  1. package/.eslintrc.json +26 -35
  2. package/Changelog +22 -0
  3. package/README.md +7 -3
  4. package/anslq25/MessagesMock.ts +218 -0
  5. package/anslq25/boards/MockBoardFactory.ts +50 -0
  6. package/anslq25/boards/MockEasyTouchBoard.ts +696 -0
  7. package/anslq25/boards/MockSystemBoard.ts +217 -0
  8. package/anslq25/chemistry/MockChlorinator.ts +75 -0
  9. package/anslq25/pumps/MockPump.ts +84 -0
  10. package/app.ts +10 -14
  11. package/config/Config.ts +13 -9
  12. package/config/VersionCheck.ts +6 -2
  13. package/controller/Constants.ts +58 -25
  14. package/controller/Equipment.ts +225 -41
  15. package/controller/Errors.ts +2 -1
  16. package/controller/Lockouts.ts +34 -2
  17. package/controller/State.ts +491 -48
  18. package/controller/boards/AquaLinkBoard.ts +6 -3
  19. package/controller/boards/BoardFactory.ts +5 -1
  20. package/controller/boards/EasyTouchBoard.ts +1971 -1751
  21. package/controller/boards/IntelliCenterBoard.ts +1311 -1688
  22. package/controller/boards/IntelliComBoard.ts +7 -1
  23. package/controller/boards/IntelliTouchBoard.ts +153 -42
  24. package/controller/boards/NixieBoard.ts +209 -66
  25. package/controller/boards/SunTouchBoard.ts +393 -0
  26. package/controller/boards/SystemBoard.ts +1862 -1543
  27. package/controller/comms/Comms.ts +539 -138
  28. package/controller/comms/ScreenLogic.ts +1663 -0
  29. package/controller/comms/messages/Messages.ts +242 -60
  30. package/controller/comms/messages/config/ChlorinatorMessage.ts +4 -3
  31. package/controller/comms/messages/config/CircuitGroupMessage.ts +5 -2
  32. package/controller/comms/messages/config/CircuitMessage.ts +81 -13
  33. package/controller/comms/messages/config/ConfigMessage.ts +3 -1
  34. package/controller/comms/messages/config/CoverMessage.ts +2 -1
  35. package/controller/comms/messages/config/CustomNameMessage.ts +2 -1
  36. package/controller/comms/messages/config/EquipmentMessage.ts +5 -1
  37. package/controller/comms/messages/config/ExternalMessage.ts +33 -3
  38. package/controller/comms/messages/config/FeatureMessage.ts +2 -1
  39. package/controller/comms/messages/config/GeneralMessage.ts +2 -1
  40. package/controller/comms/messages/config/HeaterMessage.ts +3 -1
  41. package/controller/comms/messages/config/IntellichemMessage.ts +2 -1
  42. package/controller/comms/messages/config/OptionsMessage.ts +12 -6
  43. package/controller/comms/messages/config/PumpMessage.ts +9 -12
  44. package/controller/comms/messages/config/RemoteMessage.ts +80 -13
  45. package/controller/comms/messages/config/ScheduleMessage.ts +43 -3
  46. package/controller/comms/messages/config/SecurityMessage.ts +2 -1
  47. package/controller/comms/messages/config/ValveMessage.ts +43 -26
  48. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +8 -7
  49. package/controller/comms/messages/status/EquipmentStateMessage.ts +93 -20
  50. package/controller/comms/messages/status/HeaterStateMessage.ts +24 -5
  51. package/controller/comms/messages/status/IntelliChemStateMessage.ts +7 -4
  52. package/controller/comms/messages/status/IntelliValveStateMessage.ts +2 -1
  53. package/controller/comms/messages/status/PumpStateMessage.ts +72 -4
  54. package/controller/comms/messages/status/VersionMessage.ts +2 -1
  55. package/controller/nixie/Nixie.ts +15 -4
  56. package/controller/nixie/NixieEquipment.ts +1 -0
  57. package/controller/nixie/chemistry/ChemController.ts +300 -129
  58. package/controller/nixie/chemistry/ChemDoser.ts +806 -0
  59. package/controller/nixie/chemistry/Chlorinator.ts +133 -129
  60. package/controller/nixie/circuits/Circuit.ts +171 -30
  61. package/controller/nixie/heaters/Heater.ts +337 -173
  62. package/controller/nixie/pumps/Pump.ts +264 -236
  63. package/controller/nixie/schedules/Schedule.ts +9 -3
  64. package/defaultConfig.json +46 -5
  65. package/logger/Logger.ts +38 -9
  66. package/package.json +13 -9
  67. package/web/Server.ts +235 -122
  68. package/web/bindings/aqualinkD.json +114 -59
  69. package/web/bindings/homeassistant.json +437 -0
  70. package/web/bindings/influxDB.json +15 -0
  71. package/web/bindings/mqtt.json +28 -9
  72. package/web/bindings/mqttAlt.json +15 -0
  73. package/web/interfaces/baseInterface.ts +58 -7
  74. package/web/interfaces/httpInterface.ts +5 -2
  75. package/web/interfaces/influxInterface.ts +9 -2
  76. package/web/interfaces/mqttInterface.ts +234 -74
  77. package/web/interfaces/ruleInterface.ts +87 -0
  78. package/web/services/config/Config.ts +140 -33
  79. package/web/services/config/ConfigSocket.ts +2 -1
  80. package/web/services/state/State.ts +144 -3
  81. package/web/services/state/StateSocket.ts +65 -14
  82. 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. Russell Goldin, tagyoureit. russ.goldin@gmail.com
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 { EquipmentTimeoutError, InvalidEquipmentDataError, InvalidEquipmentIdError, InvalidOperationError } from '../Errors';
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.virtualCircuits = new EquipmentIdRange(128, 136);
36
- this.equipmentIds.circuitGroups = new EquipmentIdRange(192, function () { return this.start + sys.equipment.maxCircuitGroups - 1; });
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 CLeaner' }],
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 8', circuits: 8, shared: false }],
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
- [129, { name: 'ET4P', part: 'ET-4P', desc: 'EasyTouch 4P', circuits: 4, shared: false }]
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
- if (conn.mockPort) {
514
- logger.info(`Skipping configuration request from OCP because MockPort enabled.`);
515
- } else {
516
- logger.info(`Requesting ${sys.controllerType} configuration`);
517
- this.queueItems(GetTouchConfigCategories.dateTime);
518
- this.queueRange(GetTouchConfigCategories.customNames, 0, sys.equipment.maxCustomNames - 1);
519
- this.queueRange(GetTouchConfigCategories.circuits, 1, sys.board.equipmentIds.features.end);
520
- this.queueRange(GetTouchConfigCategories.schedules, 1, sys.equipment.maxSchedules);
521
- // moved heat/solar request items after circuits to allow bodies to be discovered
522
- this.queueItems(GetTouchConfigCategories.heatTemperature);
523
- this.queueItems(GetTouchConfigCategories.solarHeatPump);
524
- this.queueItems(GetTouchConfigCategories.delays);
525
- this.queueItems(GetTouchConfigCategories.settings);
526
- this.queueItems(GetTouchConfigCategories.intellifloSpaSideRemotes);
527
- this.queueItems(GetTouchConfigCategories.is4is10);
528
- this.queueItems(GetTouchConfigCategories.spaSideRemote);
529
- this.queueItems(GetTouchConfigCategories.valves);
530
- this.queueItems(GetTouchConfigCategories.lightGroupPositions);
531
- this.queueItems(GetTouchConfigCategories.highSpeedCircuits);
532
- this.queueRange(GetTouchConfigCategories.pumpConfig, 1, sys.equipment.maxPumps);
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({ response: true, callback: () => { self.processNext(out); } })
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
- setTimeout(() => conn.queueSendMessage(out), 50);
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
- /* public setSchedule(sched: Schedule | EggTimer, obj?: any) {
611
- super.setSchedule(sched, obj);
612
- let msgs: Outbound[] = this.createSchedConfigMessages(sched);
613
- for (let i = 0; i <= msgs.length; i++) {
614
- conn.queueSendMessage(msgs[i]);
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
- public createSchedConfigMessages(sched: Schedule | EggTimer): Outbound[] {
619
- // delete sched 1
620
- // [ 255, 0, 255], [165, 33, 16, 33, 145, 7], [1, 0, 0, 0, 0, 0, 0], [1, 144]
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
- const setSchedConfig = Outbound.create({
623
- action: 145,
624
- payload: [sched.id, 0, 0, 0, 0, 0, 0],
625
- retries: 2
626
- });
627
- if (sched.circuit === 0) {
628
- // delete - take defaults
629
- }
630
- else {
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
- return [setSchedConfig, schedConfigRequest];
654
- } */
655
- public async setScheduleAsync(data: any): Promise<Schedule> {
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
- let id = typeof data.id === 'undefined' ? -1 : parseInt(data.id, 10);
658
- if (id <= 0) id = sys.schedules.getNextEquipmentId(new EquipmentIdRange(1, sys.equipment.maxSchedules));
659
- if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid schedule id: ${data.id}`, data.id, 'Schedule'));
660
- let sched = sys.schedules.getItemById(id, id > 0);
661
- let ssched = state.schedules.getItemById(id, id > 0);
662
- let schedType = typeof data.scheduleType !== 'undefined' ? data.scheduleType : sched.scheduleType;
663
- if (typeof schedType === 'undefined') schedType = sys.board.valueMaps.scheduleTypes.getValue('repeat'); // Repeats
664
-
665
- let startTimeType = typeof data.startTimeType !== 'undefined' ? data.startTimeType : sched.startTimeType;
666
- let endTimeType = typeof data.endTimeType !== 'undefined' ? data.endTimeType : sched.endTimeType;
667
- // let startDate = typeof data.startDate !== 'undefined' ? data.startDate : sched.startDate;
668
- // if (typeof startDate.getMonth !== 'function') startDate = new Date(startDate);
669
- let heatSource = typeof data.heatSource !== 'undefined' && data.heatSource !== null ? data.heatSource : sched.heatSource || 32;
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
- // If we have sunrise/sunset then adjust for the values; if heliotrope isn't set just ignore
719
- if (state.heliotrope.isCalculated) {
720
- const sunrise = state.heliotrope.sunrise.getHours() * 60 + state.heliotrope.sunrise.getMinutes();
721
- const sunset = state.heliotrope.sunset.getHours() * 60 + state.heliotrope.sunset.getMinutes();
722
- if (startTimeType === sys.board.valueMaps.scheduleTimeTypes.getValue('sunrise')) startTime = sunrise;
723
- else if (startTimeType === sys.board.valueMaps.scheduleTimeTypes.getValue('sunset')) startTime = sunset;
724
- if (endTimeType === sys.board.valueMaps.scheduleTimeTypes.getValue('sunrise')) endTime = sunrise;
725
- else if (endTimeType === sys.board.valueMaps.scheduleTimeTypes.getValue('sunset')) endTime = sunset;
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
- let out = Outbound.create({
729
- action: 145,
730
- payload: [
731
- id,
732
- circuit,
733
- Math.floor(startTime / 60),
734
- startTime - (Math.floor(startTime / 60) * 60),
735
- schedType === sys.board.valueMaps.scheduleTypes.getValue('runonce') ? sys.board.valueMaps.scheduleTypes.getValue('runonce') : Math.floor(endTime / 60),
736
- endTime - (Math.floor(endTime / 60) * 60),
737
- schedDays],
738
- retries: 2
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
- else reject(err);
766
- };
767
- conn.queueSendMessage(out); // Send it off in a letter to yourself.
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
- let out = Outbound.create({
777
- action: 145,
778
- payload: [
779
- id,
780
- 0,
781
- 0,
782
- 0,
783
- 0,
784
- 0,
785
- 0],
786
- retries: 3
787
- });
788
- return new Promise<Schedule>((resolve, reject) => {
789
- out.onComplete = (err, msg) => {
790
- if (!err) {
791
- sys.schedules.removeItemById(id);
792
- state.schedules.removeItemById(id);
793
- ssched.emitEquipmentChange();
794
- sched.isActive = false;
795
- let req = Outbound.create({ action: 209, payload: [sched.id], retries: 2 });
796
- conn.queueSendMessage(req);
797
- resolve(sched);
798
- }
799
- else reject(err);
800
- };
801
- conn.queueSendMessage(out);
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
- return new Promise<EggTimer>((resolve, reject) => {
811
- let out = Outbound.create({
812
- action: 145,
813
- payload: [
814
- id,
815
- circuit.id,
816
- 25,
817
- 0,
818
- utils.makeBool(data.dontStop) ? 27 : Math.floor(parseInt(data.runTime, 10) / 60),
819
- utils.makeBool(data.dontStop) ? 0 : data.runTime - (Math.floor(parseInt(data.runTime, 10) / 60) * 60),
820
- 0],
821
- onComplete: (err, msg) => {
822
- if (!err) {
823
- let eggTimer = sys.eggTimers.getItemById(id, true);
824
- eggTimer.circuit = circuit.id;
825
- eggTimer.runTime = circuit.eggTimer = typeof data.runTime !== 'undefined' ? data.runTime : circuit.eggTimer || 720;
826
- circuit.dontStop = typeof data.dontStop !== 'undefined' ? utils.makeBool(data.dontStop) : eggTimer.runTime === 1620;
827
- eggTimer.isActive = true;
828
- // For good measure russ is sending out a config request for
829
- // the schedule in question. If there was a failure on the
830
- // OCP side this will resolve it.
831
- let req = Outbound.create({ action: 209, payload: [eggTimer.id], retries: 2 });
832
- conn.queueSendMessage(req);
833
- resolve(eggTimer);
834
- }
835
- else reject(err);
836
- },
837
- retries: 2
838
- });
839
- conn.queueSendMessage(out); // Send it off in a letter to yourself.
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
- return new Promise<EggTimer>((resolve, reject) => {
844
- let id = typeof data.id === 'undefined' ? -1 : parseInt(data.id, 10);
845
- if (isNaN(id) || id < 0) reject(new InvalidEquipmentIdError(`Invalid eggTimer id: ${data.id}`, data.id, 'Schedule'));
846
- let eggTimer = sys.eggTimers.getItemById(id);
847
- // RKS: Assuming you just send 0s for the schedule and it will delete it.
848
- let out = Outbound.create({
849
- action: 145,
850
- payload: [
851
- id,
852
- 0,
853
- 0,
854
- 0,
855
- 0,
856
- 0,
857
- 0],
858
- onComplete: (err, msg) => {
859
- if (!err) {
860
- const circuit = sys.circuits.getInterfaceById(data.circuit);
861
- circuit.eggTimer = 720;
862
- circuit.dontStop = circuit.eggTimer === 1620;
863
- sys.eggTimers.removeItemById(id);
864
- eggTimer.isActive = false;
865
- let req = Outbound.create({ action: 209, payload: [eggTimer.id], retries: 2 });
866
- conn.queueSendMessage(req);
867
- resolve(eggTimer);
868
- }
869
- else reject(err);
870
- },
871
- retries: 3
872
- });
873
- conn.queueSendMessage(out);
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
- spaSideRemote = 22,
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
- spaSideRemote = 214,
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
- return new Promise<void>((resolve, reject) => {
923
- let out = Outbound.create({
924
- action: 131,
925
- payload: [0],
926
- retries: 0,
927
- response: true,
928
- onComplete: (err, msg) => {
929
- if (err) {
930
- reject(err);
931
- }
932
- else {
933
- // todo: track delay status?
934
- state.delay = sys.board.valueMaps.delay.getValue('nodelay');
935
- resolve(state.data.delay);
936
- }
937
- }
938
- });
939
- conn.queueSendMessage(out);
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 setDateTimeAsync(obj: any): Promise<any> {
943
- let dayOfWeek = function (): number {
944
- // for IntelliTouch set date/time
945
- if (state.time.toDate().getUTCDay() === 0)
946
- return 0;
947
- else
948
- return Math.pow(2, state.time.toDate().getUTCDay() - 1);
949
- }
950
- return new Promise<any>((resolve, reject) => {
951
- let dst = sys.general.options.adjustDST ? 1 : 0;
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 (obj.clockSource === 'manual' || obj.clockSource === 'server') sys.general.options.clockSource = obj.clockSource;
972
- // dow= day of week as expressed as [0=Sunday, 1=Monday, 2=Tuesday, 4=Wednesday, 8=Thursday, 16=Friday, 32=Saturday]
973
- // and DST = 0(manually adjst for DST) or 1(automatically adjust DST)
974
- // [165,33,16,34,133,8],[13,10,16,29,8,19,0,0],[1,228]
975
- // [165,33,34,16,1,1],[133],[1,127]
976
- const out = Outbound.create({
977
- source: Message.pluginAddress,
978
- dest: 16,
979
- action: 133,
980
- payload: [hour, min, dow, date, month, year, 0, dst],
981
- retries: 3,
982
- response: true,
983
- onComplete: (err, msg) => {
984
- if (err) reject(err)
985
- else {
986
- state.time.hours = hour;
987
- state.time.minutes = min;
988
- state.time.date = date;
989
- state.time.month = month;
990
- state.time.year = year;
991
- if (sys.general.options.clockSource !== 'server' || typeof sys.general.options.adjustDST === 'undefined') sys.general.options.adjustDST = dst === 1 ? true : false;
992
- sys.board.system.setTZ();
993
- resolve({
994
- time: state.time.format(),
995
- adjustDST: sys.general.options.adjustDST,
996
- clockSource: sys.general.options.clockSource
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
- conn.queueSendMessage(out);
1002
- });
1003
- }
1004
- public async setCustomNameAsync(data: any): Promise<CustomName> {
1005
- return new Promise<CustomName>((resolve, reject) => {
1006
- let id = parseInt(data.id, 10);
1007
- if (isNaN(id)) return reject(new InvalidEquipmentIdError('Invalid Custom Name Id', data.id, 'customName'));
1008
- if (id > sys.equipment.maxCustomNames) return reject(new InvalidEquipmentIdError('Custom Name Id out of range', data.id, 'customName'));
1009
- let cname = sys.customNames.getItemById(id);
1010
- // No need to make any changes. Just return.
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
- for (let i = 0; i < sys.lightGroups.length; i++) {
1054
- let lg = sys.lightGroups.getItemByIndex(i);
1055
- if (lg.nameId === data.id + 200) {
1056
- let lgstate = state.lightGroups.getItemById(lg.id);
1057
- lgstate.name = lg.name = data.name;
1058
- for (let j = 0; j < state.schedules.length; j++) {
1059
- let ssched = state.schedules.getItemByIndex(j);
1060
- if (ssched.circuit === lgstate.id) {
1061
- ssched.hasChanged = true;
1062
- ssched.emitEquipmentChange();
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
- for (let i = 0; i < sys.features.length; i++) {
1068
- let f = sys.features.getItemByIndex(i);
1069
- if (f.nameId === data.id + 200) {
1070
- let fstate = state.features.getItemById(f.id);
1071
- fstate.name = f.name = data.name;
1072
- for (let j = 0; j < state.schedules.length; j++) {
1073
- let ssched = state.schedules.getItemByIndex(j);
1074
- if (ssched.circuit === fstate.id) {
1075
- ssched.hasChanged = true;
1076
- ssched.emitEquipmentChange();
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
- out.appendPayloadString(data.name, 11);
1086
- conn.queueSendMessage(out);
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
- return new Promise<Body>((resolve, reject) => {
1114
- let manualHeat = sys.general.options.manualHeat;
1115
- let manualPriority = sys.general.options.manualPriority;
1116
- if (typeof obj.manualHeat !== 'undefined') manualHeat = utils.makeBool(obj.manualHeat);
1117
- if (typeof obj.manualPriority !== 'undefined') manualPriority = utils.makeBool(obj.manualPriority);
1118
- let body = sys.bodies.getItemById(obj.id, false);
1119
- let intellichemInstalled = sys.chemControllers.getItemByAddress(144, false).isActive;
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
- conn.queueSendMessage(out);
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
- return new Promise<BodyTempState>((resolve, reject) => {
1154
- // [16,34,136,4],[POOL HEAT Temp,SPA HEAT Temp,Heat Mode,0,2,56]
1155
- // [85, 97, 7, 0]
1156
- // byte | val |
1157
- // 0 | 85 | Pool Setpoint
1158
- // 1 | 97 | Spa setpoint
1159
- // 2 | 7 | Pool/spa heat modes (01 = Heater spa 11 = Solar Only pool)
1160
- // 3 | 0 | Cool set point for ultratemp
1161
-
1162
-
1163
- // Heat modes
1164
- // 0 = Off
1165
- // 1 = Heater
1166
- // 2 = Solar/Heatpump Pref
1167
- // 3 = Solar
1168
- //
1169
-
1170
- const body1 = sys.bodies.getItemById(1);
1171
- const body2 = sys.bodies.getItemById(2);
1172
- const temp1 = body1.setPoint || 100;
1173
- const temp2 = body2.setPoint || 100;
1174
- let cool = body1.coolSetpoint || 0;
1175
- let mode1 = body1.heatMode;
1176
- let mode2 = body2.heatMode;
1177
- body.id === 1 ? mode1 = mode : mode2 = mode;
1178
- let out = Outbound.create({
1179
- dest: 16,
1180
- action: 136,
1181
- payload: [temp1, temp2, mode2 << 2 | mode1, cool],
1182
- retries: 3,
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
- public async setSetpoints(body: Body, obj: any): Promise<BodyTempState> {
1197
- return new Promise<BodyTempState>((resolve, reject) => {
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
- const body1 = sys.bodies.getItemById(1);
1234
- const body2 = sys.bodies.getItemById(2);
1235
- let temp1 = body1.setPoint || tempUnits === 0 ? 40 : 4;
1236
- let temp2 = body2.setPoint || tempUnits === 0 ? 40 : 4;
1237
- let cool = coolSetPoint || body1.setPoint + 1;
1238
- body.id === 1 ? temp1 = setPoint : temp2 = setPoint;
1239
- const mode1 = body1.heatMode;
1240
- const mode2 = body2.heatMode;
1241
- const out = Outbound.create({
1242
- dest: 16,
1243
- action: 136,
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 setHeatSetpointAsync(body: Body, setPoint: number): Promise<BodyTempState> {
1262
- return new Promise<BodyTempState>((resolve, reject) => {
1263
- // [16,34,136,4],[POOL HEAT Temp,SPA HEAT Temp,Heat Mode,0,2,56]
1264
- // 165,33,16,34,136,4,89,99,7,0,2,71 Request
1265
- // 165,33,34,16,1,1,136,1,130 Controller Response
1266
- const tempUnits = state.temps.units;
1267
- switch (tempUnits) {
1268
- case 0: // fahrenheit
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
- case 1: // celsius
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
- break;
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
- conn.queueSendMessage(out);
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
- // RKS: 12-01-2021 This has been deprecated we are now driving this through metadata on the valuemaps. This allows
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 typeByte = parseInt(data.type, 10) || circuit.type || sys.board.valueMaps.circuitFunctions.getValue('generic');
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
- return new Promise<ICircuit>(async (resolve, reject) => {
1392
- let out = Outbound.create({
1393
- action: 139,
1394
- payload: [parseInt(data.id, 10), typeByte | (utils.makeBool(data.freeze) ? 64 : 0), nameByte, 0, 0],
1395
- retries: 3,
1396
- response: true,
1397
- onComplete: async (err, msg) => {
1398
- if (err) reject(err);
1399
- else {
1400
- let circuit = sys.circuits.getInterfaceById(data.id);
1401
- let cstate = state.circuits.getInterfaceById(data.id);
1402
- circuit.nameId = cstate.nameId = nameByte;
1403
- circuit.name = cstate.name = sys.board.valueMaps.circuitNames.transform(nameByte).desc;
1404
- circuit.showInFeatures = cstate.showInFeatures = typeof data.showInFeatures !== 'undefined' ? data.showInFeatures : circuit.showInFeatures || true;
1405
- circuit.freeze = typeof data.freeze !== 'undefined' ? utils.makeBool(data.freeze) : circuit.freeze;
1406
- circuit.type = cstate.type = typeByte;
1407
- circuit.eggTimer = typeof data.eggTimer !== 'undefined' ? parseInt(data.eggTimer, 10) : circuit.eggTimer || 720;
1408
- circuit.dontStop = (typeof data.dontStop !== 'undefined') ? utils.makeBool(data.dontStop) : circuit.eggTimer === 1620;
1409
- cstate.isActive = circuit.isActive = true;
1410
- circuit.master = 0;
1411
- let eggTimer = sys.eggTimers.find(elem => elem.circuit === parseInt(data.id, 10));
1412
- try {
1413
- if (circuit.eggTimer === 720) {
1414
- if (typeof eggTimer !== 'undefined') await sys.board.schedules.deleteEggTimerAsync({ id: eggTimer.id });
1415
- }
1416
- else {
1417
- await sys.board.schedules.setEggTimerAsync({ id: typeof eggTimer !== 'undefined' ? eggTimer.id : -1, runTime: circuit.eggTimer, dontStop: circuit.dontStop, circuit: circuit.id });
1418
- }
1419
- }
1420
- catch (err) {
1421
- // fail silently if there are no slots to fill in the schedules
1422
- logger.info(`Cannot set/delete eggtimer on circuit ${circuit.id}. Error: ${err.message}`);
1423
- circuit.eggTimer = 720;
1424
- circuit.dontStop = false;
1425
- }
1426
- state.emitEquipmentChanges();
1427
- resolve(circuit);
1428
- }
1429
- }
1430
- });
1431
- conn.queueSendMessage(out);
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
- return new Promise<ICircuitState>((resolve, reject) => {
1459
- let cstate = state.circuits.getInterfaceById(id);
1460
- let out = Outbound.create({
1461
- action: 134,
1462
- payload: [id, val ? 1 : 0],
1463
- retries: 3,
1464
- response: true,
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
- public createLightGroupMessages(group: LightGroup) {
1489
- let packets: Promise<void>[] = [];
1490
- // intellibrites can come with 8 settings (1 packet) or 10 settings (2 packets)
1491
- if (sys.equipment.maxIntelliBrites === 8) {
1492
- // Easytouch
1493
- packets.push(new Promise(function (resolve, reject) {
1494
- let out = Outbound.create({
1495
- action: 167,
1496
- retries: 3,
1497
- response: true,
1498
- onComplete: (err, msg) => {
1499
- if (err) return reject(err);
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
- return resolve();
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
- conn.queueSendMessage(out);
1517
- }));
1611
+ else {
1612
+ // Intellitouch
1613
+ const lgcircuits = group.circuits.get();
1614
+ if (send) {
1518
1615
 
1519
- }
1520
- else {
1521
- // Intellitouch
1522
- const lgcircuits = group.circuits.get();
1523
- packets.push(new Promise(function (resolve, reject) {
1524
- let out = Outbound.create({
1525
- action: 167,
1526
- retries: 3,
1527
- payload: [1],
1528
- response: true,
1529
- onComplete: (err, msg) => {
1530
- if (err) return reject(err);
1531
- else {
1532
- return resolve();
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
- for (let circ = 0; circ < 5; circ++) {
1537
- const lgcirc = lgcircuits[circ];
1538
- if (typeof lgcirc === 'undefined') out.payload.push.apply([0, 0, 0, 0]);
1539
- else {
1540
- out.payload.push(lgcirc.id);
1541
- out.payload.push(((lgcirc.position - 1) << 4) + lgcirc.color);
1542
- out.payload.push(lgcirc.swimDelay << 1);
1543
- out.payload.push(0);
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: 167,
1551
- retries: 3,
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
- for (let circ = 5; circ < 10; circ++) {
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
- catch (err) { reject(err); }
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
- return new Promise<ICircuitState>((resolve, reject) => {
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
- let out = Outbound.create({
1699
- action: 96,
1700
- payload: [theme, 0],
1701
- retries: 3,
1702
- response: true,
1703
- scope: `lightGroupTheme${id}`,
1704
- onComplete: async (err, msg) => {
1705
- if (err) reject(err);
1706
- else {
1707
- try {
1708
- // Let everyone know we turned these on. The theme messages will come later.
1709
- for (let i = 0; i < grp.circuits.length; i++) {
1710
- let c = grp.circuits.getItemByIndex(i);
1711
- let cstate = state.circuits.getItemById(c.circuit);
1712
- // if theme is 'off' light groups should not turn on
1713
- if (cstate.isOn && sys.board.valueMaps.lightThemes.getName(theme) === 'off')
1714
- await sys.board.circuits.setCircuitStateAsync(c.circuit, false);
1715
- else if (!cstate.isOn && sys.board.valueMaps.lightThemes.getName(theme) !== 'off') await sys.board.circuits.setCircuitStateAsync(c.circuit, true);
1716
- }
1717
- let isOn = sys.board.valueMaps.lightThemes.getName(theme) === 'off' ? false : true;
1718
- sys.board.circuits.setEndTime(grp, sgrp, isOn);
1719
- sgrp.isOn = isOn;
1720
- switch (theme) {
1721
- case 0: // off
1722
- case 1: // on
1723
- break;
1724
- case 128: // sync
1725
- setImmediate(function () { sys.board.circuits.sequenceLightGroupAsync(grp.id, 'sync'); });
1726
- break;
1727
- case 144: // swim
1728
- setImmediate(function () { sys.board.circuits.sequenceLightGroupAsync(grp.id, 'swim'); });
1729
- break;
1730
- case 160: // swim
1731
- setImmediate(function () { sys.board.circuits.sequenceLightGroupAsync(grp.id, 'set'); });
1732
- break;
1733
- case 190: // save
1734
- case 191: // recall
1735
- setImmediate(function () { sys.board.circuits.sequenceLightGroupAsync(grp.id, 'other'); });
1736
- break;
1737
- default:
1738
- setImmediate(function () { sys.board.circuits.sequenceLightGroupAsync(grp.id, 'color'); });
1739
- // other themes for magicstream?
1740
- }
1741
- sgrp.action = 0;
1742
- sgrp.hasChanged = true; // Say we are dirty but we really are pure as the driven snow.
1743
- state.emitEquipmentChanges();
1744
- resolve(sgrp);
1745
- }
1746
- catch (err) {
1747
- logger.error(`error setting intellibrite theme: ${err.message}`);
1748
- reject(err);
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
- return new Promise<Feature>((resolve, reject) => {
1773
- let id = parseInt(data.id, 10);
1774
- let feature: Feature;
1775
- if (id <= 0) {
1776
- id = sys.features.getNextEquipmentId(sys.board.equipmentIds.features);
1777
- feature = sys.features.getItemById(id, false, { isActive: true, freeze: false });
1778
- }
1779
- else
1780
- feature = sys.features.getItemById(id, false);
1781
- if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('feature Id has not been defined', data.id, 'Feature'));
1782
- if (!sys.board.equipmentIds.features.isInRange(id)) return Promise.reject(new InvalidEquipmentIdError(`feature Id ${id}: is out of range.`, id, 'Feature'));
1783
- let typeByte = data.type || feature.type || sys.board.valueMaps.circuitFunctions.getValue('generic');
1784
- let nameByte = 3; // set default `Aux 1`
1785
- if (typeof data.nameId !== 'undefined') nameByte = data.nameId;
1786
- else if (typeof feature.name !== 'undefined') nameByte = feature.nameId;
1787
- // [165,23,16,34,139,5],[17,0,1,0,0],[1,144]
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
- conn.queueSendMessage(out);
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 request153packet = new Promise<void>((resolve, reject) => {
1889
- let out = Outbound.create({
1890
- dest: 16,
1891
- action: 153,
1892
- // removed disable ? 0 : (spaSetpoint << 1) + 1 because only deleteChlorAsync should remove it from the OCP
1893
- payload: [(disabled ? 0 : isDosing ? 100 << 1: spaSetpoint << 1) + 1, disabled ? 0 : isDosing ? 100 : poolSetpoint,
1894
- utils.makeBool(superChlorinate) && superChlorHours > 0 ? superChlorHours + 128 : 0, // We only want to set the superChlor when the user sends superChlor = true
1895
- 0, 0, 0, 0, 0, 0, 0],
1896
- retries: 3,
1897
- response: true,
1898
- // scope: Math.random(),
1899
- onComplete: (err)=>{
1900
- if (err) {
1901
- logger.error(`Error setting Chlorinator values: ${err.message}`);
1902
- // in case of race condition
1903
- if (typeof reject !== 'undefined') reject(err);
1904
- reject = undefined;
1905
- }
1906
- else {
1907
- resolve();
1908
- resolve = undefined;
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
- }, 3000);
1919
- });
1920
- await request153packet;
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
- let request217Packet = new Promise<void>((resolve, reject) => {
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
- conn.queueSendMessage(out);
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
- let id = parseInt(obj.id, 10);
1973
- if (isNaN(id)) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator id is not valid: ${obj.id}`, 'chlorinator', obj.id));
1974
- let chlor = sys.chlorinators.getItemById(id);
1975
- if (chlor.master === 1) return await super.deleteChlorAsync(obj);
1976
- return new Promise<ChlorinatorState>((resolve, reject) => {
1977
- let out = Outbound.create({
1978
- dest: 16,
1979
- action: 153,
1980
- payload: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
1981
- retries: 3,
1982
- response: true,
1983
- onComplete: (err) => {
1984
- if (err) {
1985
- logger.error(`Error deleting chlorinator: ${err.message}`);
1986
- reject(err);
1987
- }
1988
- else {
1989
- ncp.chlorinators.deleteChlorinatorAsync(id).then(()=>{});
1990
- let cstate = state.chlorinators.getItemById(id, true);
1991
- chlor = sys.chlorinators.getItemById(id, true);
1992
- chlor.isActive = cstate.isActive = false;
1993
- sys.chlorinators.removeItemById(id);
1994
- state.chlorinators.removeItemById(id);
1995
- resolve(cstate);
1996
- }
1997
- }
1998
- });
1999
- conn.queueSendMessage(out);
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
- pump.set(obj);
2077
- let msgs: Outbound[] = this.createPumpConfigMessages(pump);
2078
- for (let i = 0; i <= msgs.length; i++) {
2079
- conn.queueSendMessage(msgs[i]);
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
- public async setPumpAsync(data: any): Promise<Pump> {
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
- let pump: Pump;
2097
- let ntype;
2098
- let type;
2099
- let isAdd = false;
2100
- let id = (typeof data.id === 'undefined') ? -1 : parseInt(data.id, 10);
2101
- if (typeof data.id === 'undefined' || isNaN(id) || id <= 0) {
2102
- // We are adding a new pump
2103
- ntype = parseInt(data.type, 10);
2104
- type = sys.board.valueMaps.pumpTypes.transform(ntype);
2105
- // If this is one of the pumps that are not supported by touch send it to system board.
2106
- if (type.equipmentMaster === 1) return super.setPumpAsync(data);
2107
- 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));
2108
- if (type.name === 'ds') {
2109
- id = 9;
2110
- if (sys.pumps.find(elem => elem.type === ntype)) return Promise.reject(new InvalidEquipmentDataError(`You may add only one ${type.desc} pump`, 'Pump', data));
2111
- }
2112
- else if (type.name === 'ss') {
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
- if (typeof id === 'undefined') return Promise.reject(new InvalidEquipmentDataError(`You may not add another ${type.desc} pump. Max number of pumps exceeded.`, 'Pump', data));
2128
- id = sys.pumps.getNextEquipmentId(sys.board.equipmentIds.pumps);
2129
- data.address = id + 95;
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
- super.setType(pump, ntype);
2145
- pump = sys.pumps.getItemById(id, false); // refetch pump with new value
2146
- }
2147
- }
2148
- // Validate all the ids since in *Touch the address is determined from the id.
2149
- if (!isAdd) isAdd = sys.pumps.find(elem => elem.id === id) === undefined;
2150
- // Now lets validate the ids related to the type.
2151
- if (id === 9 && type.name !== 'ds') return Promise.reject(new InvalidEquipmentDataError(`The id for a ${type.desc} pump must be 9`, 'Pump', data));
2152
- else if (id === 10 && type.name !== 'ss') return Promise.reject(new InvalidEquipmentDataError(`The id for a ${type.desc} pump must be 10`, 'Pump', data));
2153
- 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));
2154
-
2155
-
2156
- // Need to do a check here if we are clearing out the circuits; id data.circuits === []
2157
- // extend will keep the original array
2158
- let bClearPumpCircuits = typeof data.circuits !== 'undefined' && data.circuits.length === 0;
2159
-
2160
- if (!isAdd) data = extend(true, {}, pump.get(true), data, { id: id, type: ntype });
2161
- else data = extend(false, {}, data, { id: id, type: ntype });
2162
- if (!isAdd && bClearPumpCircuits) data.circuits = [];
2163
- data.name = data.name || pump.name || type.desc;
2164
- // We will not be sending message for ss type pumps.
2165
- if (type.name === 'ss') {
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
- let outc = Outbound.create({
2194
- action: 155,
2195
- payload: [id, ntype],
2196
- retries: 2,
2197
- response: Response.create({ action: 1, payload: [155] })
2198
- });
2199
- outc.appendPayloadBytes(0, 44);
2200
- if (type.val === 128){
2201
- outc.setPayloadByte(3, 2);
2202
- }
2203
- if (typeof type.maxPrimingTime !== 'undefined' && type.maxPrimingTime > 0 && type.val >=64) {
2204
- outc.setPayloadByte(2, parseInt(data.primingTime, 10), pump.primingTime || 1);
2205
- let primingSpeed = typeof data.primingSpeed !== 'undefined' ? parseInt(data.primingSpeed, 10) : pump.primingSpeed || type.minSpeed;
2206
- outc.setPayloadByte(21, Math.floor(primingSpeed / 256));
2207
- outc.setPayloadByte(30, primingSpeed % 256);
2208
- }
2209
- if (type.val === 1) { // Any VF pump.
2210
- outc.setPayloadByte(1, parseInt(data.backgroundCircuit, 10), pump.backgroundCircuit || 6);
2211
- outc.setPayloadByte(2, parseInt(data.filterSize, 10) / 1000, pump.filterSize / 1000 || 15);
2212
- // outc.setPayloadByte(2, body.capacity / 1000, 15); RSG - This is filter size, which may or may not equal the body size.
2213
- outc.setPayloadByte(3, parseInt(data.turnovers, 10), pump.turnovers || 2);
2214
- let body = sys.bodies.getItemById(1, sys.equipment.maxBodies >= 1);
2215
- outc.setPayloadByte(21, parseInt(data.manualFilterGPM, 10), pump.manualFilterGPM || 30);
2216
- outc.setPayloadByte(22, parseInt(data.primingSpeed, 10), pump.primingSpeed || 55);
2217
- let primingTime = typeof data.primingTime !== 'undefined' ? parseInt(data.primingTime, 10) : pump.primingTime || 0;
2218
- let maxSystemTime = typeof data.maxSystemTime !== 'undefined' ? parseInt(data.maxSystemTime, 10) : pump.maxSystemTime;
2219
- outc.setPayloadByte(23, primingTime | maxSystemTime << 4, 5);
2220
- outc.setPayloadByte(24, parseInt(data.maxPressureIncrease, 10), pump.maxPressureIncrease || 10);
2221
- outc.setPayloadByte(25, parseInt(data.backwashFlow, 10), pump.backwashFlow || 60);
2222
- outc.setPayloadByte(26, parseInt(data.backwashTime, 10), pump.backwashTime || 5);
2223
- outc.setPayloadByte(27, parseInt(data.rinseTime, 10), pump.rinseTime || 1);
2224
- outc.setPayloadByte(28, parseInt(data.vacuumFlow, 10), pump.vacuumFlow || 50);
2225
- outc.setPayloadByte(30, parseInt(data.vacuumTime, 10), pump.vacuumTime || 10);
2226
- }
2227
- if (typeof type.maxCircuits !== 'undefined' && type.maxCircuits > 0 && typeof data.circuits !== 'undefined') { // This pump type supports circuits
2228
- for (let i = 1; i <= data.circuits.length && i <= type.maxCircuits; i++) {
2229
- // RKS: This notion of always returning the max number of circuits was misguided. It leaves gaps in the circuit definitions and makes the pump
2230
- // layouts difficult when there are a variety of supported circuits. For instance with SF pumps you only get 4.
2231
- let c = i >= data.circuits.length - 1 ? { speed: type.minSpeed || 0, flow: type.minFlow || 0, circuit: 0 } : data.circuits[i - 1];
2232
- //let c = data.circuits[i - 1];
2233
- let speed = parseInt(c.speed, 10);
2234
- let flow = parseInt(c.flow, 10);
2235
- if (isNaN(speed)) speed = type.minSpeed;
2236
- if (isNaN(flow)) flow = type.minFlow;
2237
- outc.setPayloadByte(i * 2 + 3, parseInt(c.circuit, 10), 0);
2238
- let units;
2239
- if (type.name === 'vf') units = sys.board.valueMaps.pumpUnits.getValue('gpm');
2240
- else if (type.name === 'vs') units = sys.board.valueMaps.pumpUnits.getValue('rpm');
2241
- else units = sys.board.valueMaps.pumpUnits.encode(c.units);
2242
- if (isNaN(units)) units = sys.board.valueMaps.pumpUnits.getValue('rpm');
2243
- c.units = units;
2244
- //c.units = parseInt(c.units, 10) || type.name === 'vf' ? sys.board.valueMaps.pumpUnits.getValue('gpm') : sys.board.valueMaps.pumpUnits.getValue('rpm');
2245
- if (typeof type.minSpeed !== 'undefined' && c.units === sys.board.valueMaps.pumpUnits.getValue('rpm')) {
2246
- outc.setPayloadByte(i * 2 + 4, Math.floor(speed / 256)); // Set to rpm
2247
- outc.setPayloadByte(i + 21, speed % 256);
2248
- c.speed = speed;
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
- else if (typeof type.minFlow !== 'undefined' && c.units === sys.board.valueMaps.pumpUnits.getValue('gpm')) {
2251
- outc.setPayloadByte(i * 2 + 4, flow); // Set to gpm
2252
- c.flow = flow;
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
- return new Promise<Pump>((resolve, reject) => {
2257
- outc.onComplete = (err, msg) => {
2258
- if (err) reject(err);
2259
- else {
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
- const pumpConfigRequest = Outbound.create({
2268
- action: 216,
2269
- payload: [pump.id],
2270
- retries: 2,
2271
- response: true
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
- else if (pump.type >= 1 && pump.type < 64) {
2323
- // vf
2324
- setPumpConfig.payload[1] = pump.backgroundCircuit || 6;
2325
- setPumpConfig.payload[3] = pump.turnovers || 2;
2326
- const body = sys.bodies.getItemById(1, sys.equipment.maxBodies >= 1);
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
- public async deletePumpAsync(data: any):Promise<Pump>{
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
- conn.queueSendMessage(outc);
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
- return new Promise<Heater>((resolve, reject) => {
2452
- let id = typeof obj.id === 'undefined' ? -1 : parseInt(obj.id, 10);
2453
- if (isNaN(id)) return reject(new InvalidEquipmentIdError('Heater Id is not valid.', obj.id, 'Heater'));
2454
- let heater: Heater;
2455
- let address: number;
2456
- let out = Outbound.create({
2457
- action: 162,
2458
- payload: [5, 0, 0],
2459
- retries: 2,
2460
- // I am assuming that there should be an action 34 when the 162 is sent but I do not have this
2461
- // data.
2462
- response: Response.create({ dest: -1, action: 34 })
2463
- });
2464
- let htype;
2465
- if (id <= 0) {
2466
- // Touch only supports two installed heaters. So the type determines the id.
2467
- if (sys.heaters.length > sys.equipment.maxHeaters) return reject(new InvalidEquipmentDataError('The maximum number of heaters are already installed.', 'Heater', sys.heaters.length));
2468
- htype = sys.board.valueMaps.heaterTypes.findItem(obj.type);
2469
- if (typeof htype === 'undefined') return reject(new InvalidEquipmentDataError('Heater type is not valid.', 'Heater', obj.heaterType));
2470
- // Check to see if we can find any heaters of this type already installed.
2471
- if (sys.heaters.count(h => h.type === htype.val) > 0) return reject(new InvalidEquipmentDataError(`Only one ${htype.desc} heater can be installed`, 'Heater', htype));
2472
- // Next we need to see if this heater is compatible with all the other heaters. For Touch you may only have the following combos.
2473
- // 1 Gas + 1 Solar
2474
- // 1 Gas + 1 Heatpump
2475
- // 1 Hybrid
2476
-
2477
- // Heater ids are as follows.
2478
- // 1 = Gas Heater
2479
- // 2 = Solar
2480
- // 3 = UltraTemp (HEATPUMPCOM)
2481
- // 4 = UltraTemp ETi (Hybrid)
2482
- switch (htype.name) {
2483
- case 'gas':
2484
- id = 1;
2485
- break;
2486
- case 'solar':
2487
- out.setPayloadByte(0, out.payload[0] | 0x02);
2488
- // Set the start and stop temp delta.
2489
- out.setPayloadByte(1, (obj.freeze ? 0x80 : 0x00) | (obj.coolingEnabled ? 0x20 : 0x00));
2490
- out.setPayloadByte(2, ((obj.startTempDelta || 6) - 3 << 6) | ((obj.stopTempDelta || 3) - 2 << 1));
2491
- id = 2;
2492
- break;
2493
- case 'ultratemp':
2494
- case 'heatpump':
2495
- address = 112;
2496
- out.setPayloadByte(0, out.payload[0] | 0x02);
2497
- out.setPayloadByte(1, out.payload[1] | 0x10 | (obj.coolingEnabled ? 0x20 : 0x00));
2498
- id = 3;
2499
- break;
2500
- case 'hybrid':
2501
- // If we are adding a hybrid heater this means that the gas heater is to be replaced. This means that only
2502
- // a gas heater can be installed.
2503
- if (sys.heaters.length > 1) return reject(new InvalidEquipmentDataError(`Hybrid heaters can only be installed by themselves`, 'Heater', htype));
2504
- if (sys.heaters.getItemByIndex(0).type > 1) return reject(new InvalidEquipmentDataError(`Hybrid heaters can only replace the gas heater`, 'Heater', htype));
2505
- out.setPayloadByte(0, 5);
2506
- out.setPayloadByte(1, 16);
2507
- // 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
2508
- // for heaters on Touch panels.
2509
- out.setPayloadByte(2, 118);
2510
- id = 4;
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
- else {
2515
- // 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
2516
- // first two bytes are calculated based upon the existing heaters.
2517
- heater = sys.heaters.find(x => id === x.id);
2518
- if (typeof heater === 'undefined') return reject(new InvalidEquipmentIdError(`Heater #${id} is not installed and cannot be updated.`, id, 'Heater'));
2519
- // So here we go with the settings.
2520
- htype = sys.board.valueMaps.heaterTypes.findItem(heater.type);
2521
- switch (htype.name) {
2522
- case 'gas':
2523
- break;
2524
- case 'solar':
2525
- out.setPayloadByte(0, out.payload[0] | 0x02);
2526
- // Set the start and stop temp delta.
2527
- out.setPayloadByte(1, (obj.freeze ? 0x80 : 0x00) | (obj.coolingEnabled ? 0x20 : 0x00));
2528
- out.setPayloadByte(2, ((obj.startTempDelta || 6) - 3 << 6) | ((obj.stopTempDelta || 3) - 2 << 1));
2529
- break;
2530
- case 'ultratemp':
2531
- case 'heatpump':
2532
- address = 112;
2533
- out.setPayloadByte(0, out.payload[0] | 0x02);
2534
- out.setPayloadByte(1, out.payload[1] | 0x10 | (obj.coolingEnabled ? 0x20 : 0x00));
2535
- break;
2536
- case 'hybrid':
2537
- address = 112;
2538
- out.setPayloadByte(0, 5);
2539
- out.setPayloadByte(1, 16);
2540
- // 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
2541
- // for heaters on Touch panels.
2542
- out.setPayloadByte(2, 118);
2543
- break;
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
- // Set the bytes from the existing installed heaters.
2547
- for (let i = 0; i < sys.heaters.length; i++) {
2548
- let h = sys.heaters.getItemByIndex(i);
2549
- if (h.id === id) continue;
2550
- let ht = sys.board.valueMaps.heaterTypes.transform(h.type);
2551
- switch (ht.name) {
2552
- case 'gas':
2553
- break;
2554
- case 'solar':
2555
- out.setPayloadByte(0, out.payload[0] | 0x02);
2556
- out.setPayloadByte(1, (h.freeze ? 0x80 : 0x00) | (h.coolingEnabled ? 0x20 : 0x00));
2557
- out.setPayloadByte(2, ((h.startTempDelta || 6) - 3 << 6) | ((h.stopTempDelta || 3) - 2 << 1));
2558
- break;
2559
- case 'ultratemp':
2560
- case 'heatpump':
2561
- out.setPayloadByte(0, out.payload[0] | 0x02);
2562
- out.setPayloadByte(1, out.payload[1] | 0x10 | (h.coolingEnabled ? 0x20 : 0x00));
2563
- break;
2564
- case 'hybrid':
2565
- break;
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
- out.onComplete = (err, msg) => {
2569
- if (err) reject(err);
2570
- else {
2571
- heater = sys.heaters.getItemById(id, true);
2572
- let sheater = state.heaters.getItemById(id, true);
2573
- for (var s in obj) {
2574
- switch (s) {
2575
- case 'id':
2576
- case 'name':
2577
- case 'type':
2578
- case 'address':
2579
- break;
2580
- default:
2581
- heater[s] = obj[s];
2582
- break;
2583
- }
2584
- }
2585
- sheater.name = heater.name = typeof obj.name !== 'undefined' ? obj.name : heater.name;
2586
- sheater.type = heater.type = htype.val;
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
- return new Promise<Heater>((resolve, reject) => {
2600
- let id = parseInt(obj.id, 10);
2601
- if (isNaN(id)) return reject(new InvalidEquipmentIdError('Cannot delete. Heater Id is not valid.', obj.id, 'Heater'));
2602
- let heater = sys.heaters.getItemById(id);
2603
- heater.isActive = false;
2604
- sys.heaters.removeItemById(id);
2605
- state.heaters.removeItemById(id);
2606
- sys.board.heaters.updateHeaterServices();
2607
- sys.board.heaters.syncHeaterStates();
2608
- resolve(heater);
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
- let chem = sys.board.chemControllers.findChemController(data);
2685
- let ichemType = sys.board.valueMaps.chemControllerTypes.encode('intellichem');
2686
- if (typeof chem === 'undefined') {
2687
- // We are adding an IntelliChem. Check to see how many intellichems we have.
2688
- let arr = sys.chemControllers.toArray();
2689
- let count = 0;
2690
- for (let i = 0; i < arr.length; i++) {
2691
- let cc: ChemController = arr[i];
2692
- if (cc.type === ichemType) count++;
2693
- }
2694
- 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));
2695
- chem = sys.chemControllers.getItemById(data.id);
2696
- }
2697
- let address = typeof data.address !== 'undefined' ? parseInt(data.address, 10) : chem.address;
2698
- if (typeof address === 'undefined' || isNaN(address) || (address < 144 || address > 158)) return Promise.reject(new InvalidEquipmentDataError(`Invalid IntelliChem address`, 'chemController', address));
2699
- 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));
2700
- // Now lets do all our validation to the incoming chem controller data.
2701
- let name = typeof data.name !== 'undefined' ? data.name : chem.name || `IntelliChem - ${address - 143}`;
2702
- let type = sys.board.valueMaps.chemControllerTypes.transformByName('intellichem');
2703
- // So now we are down to the nitty gritty setting the data for the REM Chem controller.
2704
- let calciumHardness = typeof data.calciumHardness !== 'undefined' ? parseInt(data.calciumHardness, 10) : chem.calciumHardness;
2705
- let cyanuricAcid = typeof data.cyanuricAcid !== 'undefined' ? parseInt(data.cyanuricAcid, 10) : chem.cyanuricAcid;
2706
- let alkalinity = typeof data.alkalinity !== 'undefined' ? parseInt(data.alkalinity, 10) : chem.alkalinity;
2707
- let borates = typeof data.borates !== 'undefined' ? parseInt(data.borates, 10) : chem.borates || 0;
2708
- let body = sys.board.bodies.mapBodyAssociation(typeof data.body === 'undefined' ? chem.body : data.body);
2709
- if (typeof body === 'undefined') return Promise.reject(new InvalidEquipmentDataError(`Invalid body assignment`, 'chemController', data.body || chem.body));
2710
- // Do a final validation pass so we dont send this off in a mess.
2711
- if (isNaN(calciumHardness)) return Promise.reject(new InvalidEquipmentDataError(`Invalid calcium hardness`, 'chemController', calciumHardness));
2712
- if (isNaN(cyanuricAcid)) return Promise.reject(new InvalidEquipmentDataError(`Invalid cyanuric acid`, 'chemController', cyanuricAcid));
2713
- if (isNaN(alkalinity)) return Promise.reject(new InvalidEquipmentDataError(`Invalid alkalinity`, 'chemController', alkalinity));
2714
- if (isNaN(borates)) return Promise.reject(new InvalidEquipmentDataError(`Invalid borates`, 'chemController', borates));
2715
- let schem = state.chemControllers.getItemById(chem.id, true);
2716
- let pHSetpoint = typeof data.ph !== 'undefined' && typeof data.ph.setpoint !== 'undefined' ? parseFloat(data.ph.setpoint) : chem.ph.setpoint;
2717
- let orpSetpoint = typeof data.orp !== 'undefined' && typeof data.orp.setpoint !== 'undefined' ? parseInt(data.orp.setpoint, 10) : chem.orp.setpoint;
2718
- let lsiRange = typeof data.lsiRange !== 'undefined' ? data.lsiRange : chem.lsiRange || {};
2719
- if (typeof data.lsiRange !== 'undefined') {
2720
- if (typeof data.lsiRange.enabled !== 'undefined') lsiRange.enabled = utils.makeBool(data.lsiRange.enabled);
2721
- if (typeof data.lsiRange.low === 'number') lsiRange.low = parseFloat(data.lsiRange.low);
2722
- if (typeof data.lsiRange.high === 'number') lsiRange.high = parseFloat(data.lsiRange.high);
2723
- }
2724
- if (isNaN(pHSetpoint) || pHSetpoint > type.ph.max || pHSetpoint < type.ph.min) Promise.reject(new InvalidEquipmentDataError(`Invalid pH setpoint`, 'ph.setpoint', pHSetpoint));
2725
- if (isNaN(orpSetpoint) || orpSetpoint > type.orp.max || orpSetpoint < type.orp.min) Promise.reject(new InvalidEquipmentDataError(`Invalid orp setpoint`, 'orp.setpoint', orpSetpoint));
2726
- let phTolerance = typeof data.ph.tolerance !== 'undefined' ? data.ph.tolerance : chem.ph.tolerance;
2727
- let orpTolerance = typeof data.orp.tolerance !== 'undefined' ? data.orp.tolerance : chem.orp.tolerance;
2728
- if (typeof data.ph.tolerance !== 'undefined') {
2729
- if (typeof data.ph.tolerance.enabled !== 'undefined') phTolerance.enabled = utils.makeBool(data.ph.tolerance.enabled);
2730
- if (typeof data.ph.tolerance.low !== 'undefined') phTolerance.low = parseFloat(data.ph.tolerance.low);
2731
- if (typeof data.ph.tolerance.high !== 'undefined') phTolerance.high = parseFloat(data.ph.tolerance.high);
2732
- if (isNaN(phTolerance.low)) phTolerance.low = type.ph.min;
2733
- if (isNaN(phTolerance.high)) phTolerance.high = type.ph.max;
2734
- }
2735
- if (typeof data.orp.tolerance !== 'undefined') {
2736
- if (typeof data.orp.tolerance.enabled !== 'undefined') orpTolerance.enabled = utils.makeBool(data.orp.tolerance.enabled);
2737
- if (typeof data.orp.tolerance.low !== 'undefined') orpTolerance.low = parseFloat(data.orp.tolerance.low);
2738
- if (typeof data.orp.tolerance.high !== 'undefined') orpTolerance.high = parseFloat(data.orp.tolerance.high);
2739
- if (isNaN(orpTolerance.low)) orpTolerance.low = type.orp.min;
2740
- if (isNaN(orpTolerance.high)) orpTolerance.high = type.orp.max;
2741
- }
2742
- let phEnabled = typeof data.ph.enabled !== 'undefined' ? utils.makeBool(data.ph.enabled) : chem.ph.enabled;
2743
- let orpEnabled = typeof data.orp.enabled !== 'undefined' ? utils.makeBool(data.orp.enabled) : chem.orp.enabled;
2744
- let siCalcType = typeof data.siCalcType !== 'undefined' ? sys.board.valueMaps.siCalcTypes.encode(data.siCalcType, 0) : chem.siCalcType;
2745
-
2746
- let saltLevel = (state.chlorinators.length > 0) ? state.chlorinators.getItemById(1).saltLevel || 1000 : 1000
2747
- chem.ph.tank.capacity = 6;
2748
- chem.orp.tank.capacity = 6;
2749
- 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;
2750
- 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;
2751
- // OCP needs to set the IntelliChem as active so it knows that it exists
2752
-
2753
- return new Promise<ChemController>((resolve, reject) => {
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
- onAbort: () => { },
2760
- onComplete: (err) => {
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((pHSetpoint * 100) / 256) || 0);
2798
- out.setPayloadByte(2, Math.round((pHSetpoint * 100) % 256) || 0);
2799
- out.setPayloadByte(3, Math.floor(orpSetpoint / 256) || 0);
2800
- out.setPayloadByte(4, Math.round(orpSetpoint % 256) || 0);
2801
- out.setPayloadByte(5, phEnabled ? acidTankLevel + 1 : 0);
2802
- out.setPayloadByte(6, orpEnabled ? orpTankLevel + 1 : 0);
2803
- out.setPayloadByte(7, Math.floor(calciumHardness / 256) || 0);
2804
- out.setPayloadByte(8, Math.round(calciumHardness % 256) || 0);
2805
- out.setPayloadByte(9, parseInt(data.cyanuricAcid, 10), chem.cyanuricAcid || 0);
2806
- out.setPayloadByte(11, Math.floor(alkalinity / 256) || 0);
2807
- out.setPayloadByte(12, Math.round(alkalinity % 256) || 0);
2808
- out.setPayloadByte(13, Math.round(saltLevel / 50) || 20);
2809
- conn.queueSendMessage(out);
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
- // I think this payload should delete the controller on Touch.
2842
- out.insertPayloadBytes(0, 0, 22);
2843
- out.setPayloadByte(0, chem.address - 144 || 0);
2844
- out.setPayloadByte(1, Math.floor((chem.ph.setpoint * 100) / 256) || 0);
2845
- out.setPayloadByte(2, Math.round((chem.ph.setpoint * 100) % 256) || 0);
2846
- out.setPayloadByte(3, Math.floor(chem.orp.setpoint / 256) || 0);
2847
- out.setPayloadByte(4, Math.round(chem.orp.setpoint % 256) || 0);
2848
- out.setPayloadByte(5, 0);
2849
- out.setPayloadByte(6, 0);
2850
- out.setPayloadByte(7, Math.floor(chem.calciumHardness / 256) || 0);
2851
- out.setPayloadByte(8, Math.round(chem.calciumHardness % 256) || 0);
2852
- out.setPayloadByte(9, chem.cyanuricAcid || 0);
2853
- out.setPayloadByte(11, Math.floor(chem.alkalinity / 256) || 0);
2854
- out.setPayloadByte(12, Math.round(chem.alkalinity % 256) || 0);
2855
- out.setPayloadByte(13, 20);
2856
- conn.queueSendMessage(out);
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
+ }