nodejs-poolcontroller 7.2.0 → 7.5.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 (64) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/Changelog +13 -0
  3. package/Dockerfile +1 -0
  4. package/README.md +5 -5
  5. package/app.ts +11 -0
  6. package/config/Config.ts +3 -0
  7. package/config/VersionCheck.ts +8 -4
  8. package/controller/Constants.ts +165 -9
  9. package/controller/Equipment.ts +186 -65
  10. package/controller/Errors.ts +22 -1
  11. package/controller/State.ts +273 -57
  12. package/controller/boards/EasyTouchBoard.ts +194 -95
  13. package/controller/boards/IntelliCenterBoard.ts +115 -42
  14. package/controller/boards/IntelliTouchBoard.ts +104 -30
  15. package/controller/boards/NixieBoard.ts +155 -53
  16. package/controller/boards/SystemBoard.ts +1529 -514
  17. package/controller/comms/Comms.ts +219 -42
  18. package/controller/comms/messages/Messages.ts +16 -4
  19. package/controller/comms/messages/config/ChlorinatorMessage.ts +13 -3
  20. package/controller/comms/messages/config/CircuitGroupMessage.ts +6 -0
  21. package/controller/comms/messages/config/CircuitMessage.ts +1 -1
  22. package/controller/comms/messages/config/CoverMessage.ts +1 -0
  23. package/controller/comms/messages/config/EquipmentMessage.ts +4 -0
  24. package/controller/comms/messages/config/ExternalMessage.ts +43 -25
  25. package/controller/comms/messages/config/FeatureMessage.ts +8 -1
  26. package/controller/comms/messages/config/GeneralMessage.ts +8 -0
  27. package/controller/comms/messages/config/HeaterMessage.ts +15 -9
  28. package/controller/comms/messages/config/IntellichemMessage.ts +4 -1
  29. package/controller/comms/messages/config/OptionsMessage.ts +13 -1
  30. package/controller/comms/messages/config/PumpMessage.ts +4 -20
  31. package/controller/comms/messages/config/RemoteMessage.ts +4 -0
  32. package/controller/comms/messages/config/ScheduleMessage.ts +11 -0
  33. package/controller/comms/messages/config/SecurityMessage.ts +1 -0
  34. package/controller/comms/messages/config/ValveMessage.ts +12 -2
  35. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +14 -6
  36. package/controller/comms/messages/status/EquipmentStateMessage.ts +78 -24
  37. package/controller/comms/messages/status/HeaterStateMessage.ts +25 -5
  38. package/controller/comms/messages/status/IntelliChemStateMessage.ts +55 -26
  39. package/controller/nixie/Nixie.ts +18 -16
  40. package/controller/nixie/NixieEquipment.ts +6 -6
  41. package/controller/nixie/bodies/Body.ts +7 -4
  42. package/controller/nixie/bodies/Filter.ts +7 -4
  43. package/controller/nixie/chemistry/ChemController.ts +800 -283
  44. package/controller/nixie/chemistry/Chlorinator.ts +22 -14
  45. package/controller/nixie/circuits/Circuit.ts +42 -7
  46. package/controller/nixie/heaters/Heater.ts +303 -30
  47. package/controller/nixie/pumps/Pump.ts +57 -30
  48. package/controller/nixie/schedules/Schedule.ts +10 -7
  49. package/controller/nixie/valves/Valve.ts +7 -5
  50. package/defaultConfig.json +32 -1
  51. package/issue_template.md +1 -1
  52. package/logger/DataLogger.ts +37 -22
  53. package/package.json +20 -18
  54. package/web/Server.ts +529 -31
  55. package/web/bindings/influxDB.json +157 -5
  56. package/web/bindings/mqtt.json +112 -13
  57. package/web/bindings/mqttAlt.json +109 -11
  58. package/web/interfaces/baseInterface.ts +2 -1
  59. package/web/interfaces/httpInterface.ts +2 -0
  60. package/web/interfaces/influxInterface.ts +103 -54
  61. package/web/interfaces/mqttInterface.ts +16 -5
  62. package/web/services/config/Config.ts +179 -43
  63. package/web/services/state/State.ts +51 -5
  64. package/web/services/state/StateSocket.ts +19 -2
@@ -15,15 +15,15 @@ You should have received a copy of the GNU Affero General Public License
15
15
  along with this program. If not, see <http://www.gnu.org/licenses/>.
16
16
  */
17
17
  import * as extend from 'extend';
18
- import { SystemBoard, byteValueMap, ConfigQueue, ConfigRequest, BodyCommands, PumpCommands, HeaterCommands, SystemCommands, CircuitCommands, FeatureCommands, ChlorinatorCommands, EquipmentIdRange, ScheduleCommands, ChemControllerCommands } from './SystemBoard';
19
- import { PoolSystem, Body, Pump, sys, ConfigVersion, Heater, Schedule, EggTimer, ICircuit, CustomNameCollection, CustomName, LightGroup, LightGroupCircuit, Feature, ChemController, Circuit, ScheduleCollection } from '../Equipment';
20
- import { Protocol, Outbound, Message, Response } from '../comms/messages/Messages';
21
- import { state, ChlorinatorState, CommsState, State, ICircuitState, ICircuitGroupState, LightGroupState, BodyTempState, FilterState, ScheduleState } from '../State';
22
18
  import { logger } from '../../logger/Logger';
23
19
  import { conn } from '../comms/Comms';
24
- import { MessageError, InvalidEquipmentIdError, InvalidEquipmentDataError, InvalidOperationError } from '../Errors';
20
+ import { Message, Outbound, Protocol, Response } from '../comms/messages/Messages';
25
21
  import { utils } from '../Constants';
22
+ import { Body, ChemController, ConfigVersion, CustomName, EggTimer, Feature, Heater, ICircuit, LightGroup, LightGroupCircuit, PoolSystem, Pump, Schedule, sys } from '../Equipment';
23
+ import { EquipmentTimeoutError, InvalidEquipmentDataError, InvalidEquipmentIdError } from '../Errors';
26
24
  import { ncp } from "../nixie/Nixie";
25
+ 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';
27
27
 
28
28
  export class EasyTouchBoard extends SystemBoard {
29
29
  public needsConfigChanges: boolean = false;
@@ -162,10 +162,10 @@ export class EasyTouchBoard extends SystemBoard {
162
162
  // We need this because there is a no-pump thing in *Touch.
163
163
  // RKS: 05-04-21 The no-pump item was removed as this was only required for -webClient. deletePumpAsync should remove the pump from operation.
164
164
  this.valueMaps.pumpTypes = new byteValueMap([
165
- [1, { name: 'vf', desc: 'Intelliflo VF', minFlow: 15, maxFlow: 130, flowStepSize: 1, maxCircuits: 8, hasAddress: true }],
165
+ [1, { name: 'vf', desc: 'Intelliflo VF', maxPrimingTime: 6, minFlow: 15, maxFlow: 130, flowStepSize: 1, maxCircuits: 8, hasAddress: true }],
166
166
  [64, { name: 'vsf', desc: 'Intelliflo VSF', minSpeed: 450, maxSpeed: 3450, speedStepSize: 10, minFlow: 15, maxFlow: 130, flowStepSize: 1, maxCircuits: 8, hasAddress: true }],
167
167
  [65, { name: 'ds', desc: 'Two-Speed', maxCircuits: 40, hasAddress: false, hasBody: true }],
168
- [128, { name: 'vs', desc: 'Intelliflo VS', maxPrimingTime: 6, minSpeed: 450, maxSpeed: 3450, speedStepSize: 10, maxCircuits: 8, hasAddress: true }],
168
+ [128, { name: 'vs', desc: 'Intelliflo VS', maxPrimingTime: 10, minSpeed: 450, maxSpeed: 3450, speedStepSize: 10, maxCircuits: 8, hasAddress: true }],
169
169
  [169, { name: 'vssvrs', desc: 'IntelliFlo VS+SVRS', maxPrimingTime: 6, minSpeed: 450, maxSpeed: 3450, speedStepSize: 10, maxCircuits: 8, hasAddress: true }],
170
170
  [257, { name: 'ss', desc: 'Single Speed', maxCircuits: 0, hasAddress: false, hasBody: true, equipmentMaster: 1 }],
171
171
  [256, { name: 'sf', desc: 'SuperFlo VS', hasAddress: false, maxCircuits: 8, maxRelays: 4, equipmentMaster: 1 }]
@@ -306,6 +306,14 @@ export class EasyTouchBoard extends SystemBoard {
306
306
  return extend(true, {}, { val: byte, desc: customName.name, name: customName.name });
307
307
  }
308
308
  };
309
+ this.valueMaps.panelModes = new byteValueMap([
310
+ [0, { val: 0, name: 'auto', desc: 'Auto' }],
311
+ [1, { val: 1, name: 'service', desc: 'Service' }],
312
+ [8, { val: 8, name: 'freeze', desc: 'Freeze' }],
313
+ [128, { val: 128, name: 'timeout', desc: 'Timeout' }],
314
+ [129, { val: 129, name: 'service-timeout', desc: 'Service/Timeout' }],
315
+ [255, { name: 'error', desc: 'System Error' }]
316
+ ]);
309
317
  this.valueMaps.expansionBoards = new byteValueMap([
310
318
  [0, { name: 'ET28', part: 'ET2-8', desc: 'EasyTouch2 8', circuits: 8, shared: true }],
311
319
  [1, { name: 'ET28P', part: 'ET2-8P', desc: 'EasyTouch2 8P', circuits: 8, shared: false }],
@@ -328,7 +336,7 @@ export class EasyTouchBoard extends SystemBoard {
328
336
  let sheater = state.heaters.getItemById(1, true);
329
337
  sheater.type = heater.type;
330
338
  sheater.name = heater.name;
331
- sheater.isVirtual = heater.isVirtual = false;
339
+ //sheater.isVirtual = heater.isVirtual = false;
332
340
  sys.equipment.shared ? heater.body = 32 : heater.body = 0;
333
341
  }
334
342
  public initBodyDefaults() {
@@ -337,15 +345,16 @@ export class EasyTouchBoard extends SystemBoard {
337
345
  // Add in the bodies for the configuration. These need to be set.
338
346
  let cbody = sys.bodies.getItemById(i, true);
339
347
  let tbody = state.temps.bodies.getItemById(i, true);
340
- // If the body doesn't represent a spa then we set the type.
341
- tbody.type = cbody.type = i > 1 && !sys.equipment.shared ? 1 : 0;
342
348
  cbody.isActive = true;
349
+ // If the body doesn't represent a spa then we set the type.
350
+ // RSG - 10-5-21: If a single body IT (i5+3s/i9+3s) the bodies are the same; set to pool
351
+ tbody.type = cbody.type = i > 1 && !sys.equipment.shared && sys.equipment.intakeReturnValves ? 1 : 0;
343
352
  if (typeof cbody.name === 'undefined') {
344
353
  let bt = sys.board.valueMaps.bodyTypes.transform(cbody.type);
345
354
  tbody.name = cbody.name = bt.name;
346
355
  }
347
356
  }
348
- if (!sys.equipment.shared && !sys.equipment.dual) {
357
+ if (!sys.equipment.shared && !sys.equipment.dual && state.equipment.controllerType !== 'intellitouch') {
349
358
  sys.bodies.removeItemById(2);
350
359
  state.temps.bodies.removeItemById(2);
351
360
  }
@@ -374,7 +383,7 @@ export class EasyTouchBoard extends SystemBoard {
374
383
  let md = mod.get();
375
384
  eq.maxBodies = md.bodies = typeof mt.bodies !== 'undefined' ? mt.bodies : mt.shared ? 2 : 1;
376
385
  eq.maxCircuits = md.circuits = typeof mt.circuits !== 'undefined' ? mt.circuits : 8;
377
- eq.maxFeatures = md.features = typeof mt.features !== 'undefined' ? mt.features : 10
386
+ eq.maxFeatures = md.features = typeof mt.features !== 'undefined' ? mt.features : 8;
378
387
  eq.maxValves = md.valves = typeof mt.valves !== 'undefined' ? mt.valves : mt.shared ? 4 : 2;
379
388
  eq.maxPumps = md.maxPumps = typeof mt.pumps !== 'undefined' ? mt.pumps : 2;
380
389
  eq.shared = mt.shared;
@@ -382,6 +391,7 @@ export class EasyTouchBoard extends SystemBoard {
382
391
  eq.maxChlorinators = md.chlorinators = 1;
383
392
  eq.maxChemControllers = md.chemControllers = 1;
384
393
  eq.maxCustomNames = 10;
394
+ eq.intakeReturnValves = md.intakeReturnValves = typeof mt.intakeReturnValves !== 'undefined' ? mt.intakeReturnValves : false;
385
395
  // Calculate out the invalid ids.
386
396
  sys.board.equipmentIds.invalidIds.set([]);
387
397
  if (!eq.shared) sys.board.equipmentIds.invalidIds.merge([1]);
@@ -454,8 +464,8 @@ export class TouchConfigRequest extends ConfigRequest {
454
464
  if (typeof items !== 'undefined') this.items.push(...items);
455
465
  this.oncomplete = oncomplete;
456
466
  }
457
- public category: TouchConfigCategories;
458
- public setcategory: GetTouchConfigCategories;
467
+ declare category: TouchConfigCategories;
468
+ declare setcategory: GetTouchConfigCategories;
459
469
  }
460
470
  export class TouchConfigQueue extends ConfigQueue {
461
471
  //protected _configQueueTimer: NodeJS.Timeout;
@@ -543,7 +553,7 @@ export class TouchConfigQueue extends ConfigQueue {
543
553
  action: this.curr.setcategory,
544
554
  payload: [itm],
545
555
  retries: 3,
546
- response: Response.create({response: true, callback: () => {self.processNext(out);}})
556
+ response: Response.create({ response: true, callback: () => { self.processNext(out); } })
547
557
  // response: true,
548
558
  // onResponseProcessed: function () { self.processNext(out); }
549
559
  });
@@ -985,7 +995,7 @@ class TouchSystemCommands extends SystemCommands {
985
995
  if (circ.nameId === data.id + 200) {
986
996
  let cstate = state.circuits.getItemById(circ.id);
987
997
  cstate.name = circ.name = data.name;
988
- for (let j = 0; j < state.schedules.length; j++){
998
+ for (let j = 0; j < state.schedules.length; j++) {
989
999
  let ssched = state.schedules.getItemByIndex(j);
990
1000
  if (ssched.circuit === cstate.id) {
991
1001
  ssched.hasChanged = true;
@@ -999,7 +1009,7 @@ class TouchSystemCommands extends SystemCommands {
999
1009
  if (cg.nameId === data.id + 200) {
1000
1010
  let cgstate = state.circuitGroups.getItemById(cg.id);
1001
1011
  cgstate.name = cg.name = data.name;
1002
- for (let j = 0; j < state.schedules.length; j++){
1012
+ for (let j = 0; j < state.schedules.length; j++) {
1003
1013
  let ssched = state.schedules.getItemByIndex(j);
1004
1014
  if (ssched.circuit === cgstate.id) {
1005
1015
  ssched.hasChanged = true;
@@ -1013,7 +1023,7 @@ class TouchSystemCommands extends SystemCommands {
1013
1023
  if (lg.nameId === data.id + 200) {
1014
1024
  let lgstate = state.lightGroups.getItemById(lg.id);
1015
1025
  lgstate.name = lg.name = data.name;
1016
- for (let j = 0; j < state.schedules.length; j++){
1026
+ for (let j = 0; j < state.schedules.length; j++) {
1017
1027
  let ssched = state.schedules.getItemByIndex(j);
1018
1028
  if (ssched.circuit === lgstate.id) {
1019
1029
  ssched.hasChanged = true;
@@ -1027,7 +1037,7 @@ class TouchSystemCommands extends SystemCommands {
1027
1037
  if (f.nameId === data.id + 200) {
1028
1038
  let fstate = state.features.getItemById(f.id);
1029
1039
  fstate.name = f.name = data.name;
1030
- for (let j = 0; j < state.schedules.length; j++){
1040
+ for (let j = 0; j < state.schedules.length; j++) {
1031
1041
  let ssched = state.schedules.getItemByIndex(j);
1032
1042
  if (ssched.circuit === fstate.id) {
1033
1043
  ssched.hasChanged = true;
@@ -1262,6 +1272,7 @@ export class TouchCircuitCommands extends CircuitCommands {
1262
1272
  // response: [255,0,255][165,33,34,16,1,1][139][1,133]
1263
1273
  let id = parseInt(data.id, 10);
1264
1274
  if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Circuit Id is invalid', data.id, 'Feature'));
1275
+ if (id >= 255 || data.master === 1) return super.setCircuitAsync(data);
1265
1276
  let circuit = sys.circuits.getInterfaceById(id);
1266
1277
  // Alright check to see if we are adding a nixie circuit.
1267
1278
  if (id === -1 || circuit.master !== 0) {
@@ -1291,6 +1302,8 @@ export class TouchCircuitCommands extends CircuitCommands {
1291
1302
  circuit.type = cstate.type = typeByte;
1292
1303
  circuit.eggTimer = typeof data.eggTimer !== 'undefined' ? parseInt(data.eggTimer, 10) : circuit.eggTimer || 720;
1293
1304
  circuit.dontStop = (typeof data.dontStop !== 'undefined') ? utils.makeBool(data.dontStop) : circuit.eggTimer === 1620;
1305
+ cstate.isActive = circuit.isActive = true;
1306
+ circuit.master = 0;
1294
1307
  let eggTimer = sys.eggTimers.find(elem => elem.circuit === parseInt(data.id, 10));
1295
1308
  try {
1296
1309
  if (circuit.eggTimer === 720) {
@@ -1329,6 +1342,15 @@ export class TouchCircuitCommands extends CircuitCommands {
1329
1342
  if (c.master !== 0) return await super.setCircuitStateAsync(id, val);
1330
1343
  if (id === 192 || c.type === 3) return await sys.board.circuits.setLightGroupThemeAsync(id - 191, val ? 1 : 0);
1331
1344
  if (id >= 192) return await sys.board.circuits.setCircuitGroupStateAsync(id, val);
1345
+
1346
+ // for some dumb reason, if the spa is on and the pool circuit is desired to be on,
1347
+ // it will ignore the packet.
1348
+ // We can override that by emulating a click to turn off the spa instead of turning
1349
+ // on the pool
1350
+ if (sys.equipment.maxBodies > 1 && id === 6 && val && state.circuits.getItemById(1).isOn) {
1351
+ id = 1;
1352
+ val = false;
1353
+ }
1332
1354
  return new Promise<ICircuitState>((resolve, reject) => {
1333
1355
  let cstate = state.circuits.getInterfaceById(id);
1334
1356
  let out = Outbound.create({
@@ -1349,6 +1371,7 @@ export class TouchCircuitCommands extends CircuitCommands {
1349
1371
  });
1350
1372
  conn.queueSendMessage(out);
1351
1373
  });
1374
+
1352
1375
  }
1353
1376
  public async setLightGroupStateAsync(id: number, val: boolean): Promise<ICircuitGroupState> { return this.setCircuitGroupStateAsync(id, val); }
1354
1377
  public async toggleCircuitStateAsync(id: number) {
@@ -1529,7 +1552,7 @@ export class TouchCircuitCommands extends CircuitCommands {
1529
1552
  else if (!cstate.isOn && sys.board.valueMaps.lightThemes.getName(theme) !== 'off') await sys.board.circuits.setCircuitStateAsync(c.circuit, true);
1530
1553
  }
1531
1554
  let isOn = sys.board.valueMaps.lightThemes.getName(theme) === 'off' ? false : true;
1532
- sys.board.circuits.setEndTime(grp, sgrp, isOn);
1555
+ sys.board.circuits.setEndTime(grp, sgrp, isOn);
1533
1556
  sgrp.isOn = isOn;
1534
1557
  switch (theme) {
1535
1558
  case 0: // off
@@ -1643,30 +1666,31 @@ class TouchFeatureCommands extends FeatureCommands {
1643
1666
 
1644
1667
  }
1645
1668
  class TouchChlorinatorCommands extends ChlorinatorCommands {
1646
- public setChlorAsync(obj: any): Promise<ChlorinatorState> {
1669
+ public async setChlorAsync(obj: any): Promise<ChlorinatorState> {
1647
1670
  let id = parseInt(obj.id, 10);
1648
1671
  let isAdd = false;
1649
- let isVirtual = false;
1650
- if (id <= 0 || isNaN(id)) id = 1;
1651
1672
  let chlor = sys.chlorinators.getItemById(id);
1652
- if (id < 0 || isNaN(id)) {
1673
+ if (id <= 0 || isNaN(id)) {
1653
1674
  isAdd = true;
1654
- chlor.master = utils.makeBool(obj.isVirtual) ? 0 : 1;
1675
+ chlor.master = utils.makeBool(obj.master) ? 1 : 0;
1655
1676
  // Calculate an id for the chlorinator. The messed up part is that if a chlorinator is not attached to the OCP, its address
1656
1677
  // cannot be set by the MUX. This will have to wait.
1657
1678
  id = 1;
1658
- }
1659
- //let chlor = extend(true, {}, sys.chlorinators.getItemById(id).get(), obj);
1660
- // If this is a virtual chlorinator then go to the base class and handle it from there.
1679
+ }
1680
+ // If this is a Nixie chlorinator then go to the base class and handle it from there.
1681
+ if (chlor.master === 1) return super.setChlorAsync(obj);
1661
1682
  // RKS: I am not even sure this can be done with Touch as the master on the RS485 bus.
1662
- if (chlor.master === 1 || isVirtual) return super.setChlorAsync(obj);
1663
- let name = obj.name || 'IntelliChlor' + id;
1664
- let poolSetpoint = parseInt(obj.poolSetpoint, 10);
1665
- let spaSetpoint = parseInt(obj.spaSetpoint, 10);
1683
+ if (typeof chlor.master === 'undefined') chlor.master = 0;
1684
+ let name = obj.name || chlor.name || 'IntelliChlor' + id;
1666
1685
  let superChlorHours = parseInt(obj.superChlorHours, 10);
1667
1686
  if (typeof obj.superChlorinate !== 'undefined') obj.superChlor = utils.makeBool(obj.superChlorinate);
1668
1687
  let superChlorinate = typeof obj.superChlor === 'undefined' ? undefined : utils.makeBool(obj.superChlor);
1688
+ let isDosing = typeof obj.isDosing !== 'undefined' ? utils.makeBool(obj.isDosing) : chlor.isDosing;
1669
1689
  let disabled = typeof obj.disabled !== 'undefined' ? utils.makeBool(obj.disabled) : chlor.disabled;
1690
+ let poolSetpoint = typeof obj.poolSetpoint !== 'undefined' ? parseInt(obj.poolSetpoint, 10) : chlor.poolSetpoint;
1691
+ let spaSetpoint = typeof obj.spaSetpoint !== 'undefined' ? parseInt(obj.spaSetpoint, 10) : chlor.spaSetpoint;
1692
+ let model = typeof obj.model !== 'undefined' ? obj.model : chlor.model;
1693
+ let chlorType = typeof obj.type !== 'undefined' ? sys.board.valueMaps.chlorinatorType.encode(obj.type) : chlor.type || 0;
1670
1694
  if (isAdd) {
1671
1695
  if (isNaN(poolSetpoint)) poolSetpoint = 50;
1672
1696
  if (isNaN(spaSetpoint)) spaSetpoint = 10;
@@ -1674,8 +1698,8 @@ class TouchChlorinatorCommands extends ChlorinatorCommands {
1674
1698
  if (typeof superChlorinate === 'undefined') superChlorinate = false;
1675
1699
  }
1676
1700
  else {
1677
- if (isNaN(poolSetpoint)) poolSetpoint = chlor.poolSetpoint;
1678
- if (isNaN(spaSetpoint)) spaSetpoint = chlor.spaSetpoint;
1701
+ if (isNaN(poolSetpoint)) poolSetpoint = chlor.poolSetpoint || 0;
1702
+ if (isNaN(spaSetpoint)) spaSetpoint = chlor.spaSetpoint || 0;
1679
1703
  if (isNaN(superChlorHours)) superChlorHours = chlor.superChlorHours;
1680
1704
  if (typeof superChlorinate === 'undefined') superChlorinate = utils.makeBool(chlor.superChlor);
1681
1705
  }
@@ -1691,55 +1715,96 @@ class TouchChlorinatorCommands extends ChlorinatorCommands {
1691
1715
  if (poolSetpoint > 100 || poolSetpoint < 0) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator poolSetpoint is out of range: ${chlor.poolSetpoint}`, 'chlorinator', chlor.poolSetpoint));
1692
1716
  if (spaSetpoint > 100 || spaSetpoint < 0) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator spaSetpoint is out of range: ${chlor.poolSetpoint}`, 'chlorinator', chlor.spaSetpoint));
1693
1717
  if (typeof obj.ignoreSaltReading !== 'undefined') chlor.ignoreSaltReading = utils.makeBool(obj.ignoreSaltReading);
1694
- return new Promise<ChlorinatorState>((resolve, reject) => {
1695
- let out = Outbound.create({
1696
- dest: 16,
1697
- action: 153,
1698
- payload: [disabled ? 0 : (spaSetpoint << 1) + 1, disabled ? 0 : poolSetpoint,
1699
- utils.makeBool(superChlorinate) && superChlorHours > 0 ? superChlorHours + 128 : 0, // We only want to set the superChlor when the user sends superChlor = true
1700
- 0, 0, 0, 0, 0, 0, 0],
1701
- retries: 3,
1702
- response: true,
1703
- onComplete: (err) => {
1704
- if (err) {
1705
- logger.error(`Error setting Chlorinator values: ${err.message}`);
1706
- reject(err);
1718
+
1719
+ let _timeout: NodeJS.Timeout;
1720
+ try {
1721
+ let request153packet = new Promise<void>((resolve, reject) => {
1722
+ let out = Outbound.create({
1723
+ dest: 16,
1724
+ action: 153,
1725
+ // removed disable ? 0 : (spaSetpoint << 1) + 1 because only deleteChlorAsync should remove it from the OCP
1726
+ payload: [(disabled ? 0 : isDosing ? 100 << 1: spaSetpoint << 1) + 1, disabled ? 0 : isDosing ? 100 : poolSetpoint,
1727
+ utils.makeBool(superChlorinate) && superChlorHours > 0 ? superChlorHours + 128 : 0, // We only want to set the superChlor when the user sends superChlor = true
1728
+ 0, 0, 0, 0, 0, 0, 0],
1729
+ retries: 3,
1730
+ response: true,
1731
+ // scope: Math.random(),
1732
+ onComplete: (err)=>{
1733
+ if (err) {
1734
+ logger.error(`Error setting Chlorinator values: ${err.message}`);
1735
+ // in case of race condition
1736
+ if (typeof reject !== 'undefined') reject(err);
1737
+ reject = undefined;
1738
+ }
1739
+ else {
1740
+ resolve();
1741
+ resolve = undefined;
1742
+ }
1707
1743
  }
1708
- let schlor = state.chlorinators.getItemById(id, true);
1709
- let cchlor = sys.chlorinators.getItemById(id, true);
1710
- schlor.isActive = cchlor.isActive = true;
1711
- schlor.superChlor = cchlor.superChlor = superChlorinate;
1712
- schlor.poolSetpoint = cchlor.poolSetpoint = poolSetpoint;
1713
- schlor.spaSetpoint = cchlor.spaSetpoint = spaSetpoint;
1714
- schlor.superChlorHours = cchlor.superChlorHours = superChlorHours;
1715
- schlor.body = cchlor.body = body;
1716
- cchlor.address = 79 + id;
1744
+ });
1745
+ conn.queueSendMessage(out);
1746
+ _timeout = setTimeout(()=>{
1747
+ if (typeof reject === 'undefined' || typeof resolve === 'undefined') return;
1748
+ reject(new EquipmentTimeoutError(`no chlor response in 7 seconds`, `chlorTimeOut`));
1749
+ reject = undefined;
1750
+
1751
+ }, 3000);
1752
+ });
1753
+ await request153packet;
1754
+ let schlor = state.chlorinators.getItemById(id, true);
1755
+ chlor.disabled = disabled;
1756
+ schlor.isActive = chlor.isActive = true;
1757
+ schlor.superChlor = chlor.superChlor = superChlorinate;
1758
+ schlor.poolSetpoint = chlor.poolSetpoint = poolSetpoint;
1759
+ schlor.spaSetpoint = chlor.spaSetpoint = spaSetpoint;
1760
+ schlor.superChlorHours = chlor.superChlorHours = superChlorHours;
1761
+ schlor.body = chlor.body = body;
1762
+ chlor.address = 79 + id;
1763
+ chlor.name = schlor.name = name;
1764
+ chlor.model = model;
1765
+ schlor.type = chlor.type = chlorType;
1766
+ chlor.isDosing = isDosing;
1717
1767
 
1718
- let request25Packet = Outbound.create({
1719
- dest: 16,
1720
- action: 217,
1721
- payload: [0],
1722
- retries: 3,
1723
- response: true,
1724
- onComplete: (err) => {
1725
- if (err) {
1726
- logger.error(`Error requesting chlor status: ${err.message}`);
1727
- reject(err);
1728
- }
1729
- else
1730
- resolve(state.chlorinators.getItemById(id));
1731
- state.emitEquipmentChanges();
1768
+ let request217Packet = new Promise<void>((resolve, reject) => {
1769
+ let out = Outbound.create({
1770
+ dest: 16,
1771
+ action: 217,
1772
+ payload: [0],
1773
+ retries: 3,
1774
+ // scope: Math.random(),
1775
+ response: true,
1776
+ onComplete: (err) => {
1777
+ // if (typeof reject === 'undefined') {
1778
+ // logger.error(`reject chlor already called.`)
1779
+ // }
1780
+ if (err) {
1781
+ logger.error(`Error requesting chlor status: ${err.message}`);
1782
+ reject(err);
1732
1783
  }
1733
- });
1734
- conn.queueSendMessage(request25Packet);
1735
- }
1784
+ else{
1785
+ resolve();
1786
+ }
1787
+ }
1788
+ })
1789
+ conn.queueSendMessage(out);
1736
1790
  });
1737
- conn.queueSendMessage(out);
1738
- });
1791
+ await request217Packet;
1792
+ if (typeof _timeout !== 'undefined'){
1793
+ clearTimeout(_timeout);
1794
+ _timeout = undefined;
1795
+ }
1796
+ state.emitEquipmentChanges();
1797
+ return state.chlorinators.getItemById(id);
1798
+ } catch (err) {
1799
+ logger.error(`*Touch setChlorAsync Error: ${err.message}`);
1800
+ return Promise.reject(err);
1801
+ }
1739
1802
  }
1740
1803
  public async deleteChlorAsync(obj: any): Promise<ChlorinatorState> {
1741
1804
  let id = parseInt(obj.id, 10);
1742
- if (isNaN(id)) obj.id = 1;
1805
+ if (isNaN(id)) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator id is not valid: ${obj.id}`, 'chlorinator', obj.id));
1806
+ let chlor = sys.chlorinators.getItemById(id);
1807
+ if (chlor.master === 1) return await super.deleteChlorAsync(obj);
1743
1808
  return new Promise<ChlorinatorState>((resolve, reject) => {
1744
1809
  let out = Outbound.create({
1745
1810
  dest: 16,
@@ -1753,8 +1818,9 @@ class TouchChlorinatorCommands extends ChlorinatorCommands {
1753
1818
  reject(err);
1754
1819
  }
1755
1820
  else {
1756
- let cstate = state.chlorinators.getItemById(id);
1757
- let chlor = sys.chlorinators.getItemById(id);
1821
+ ncp.chlorinators.deleteChlorinatorAsync(id).then(()=>{});
1822
+ let cstate = state.chlorinators.getItemById(id, true);
1823
+ chlor = sys.chlorinators.getItemById(id, true);
1758
1824
  chlor.isActive = cstate.isActive = false;
1759
1825
  sys.chlorinators.removeItemById(id);
1760
1826
  state.chlorinators.removeItemById(id);
@@ -1896,10 +1962,11 @@ class TouchPumpCommands extends PumpCommands {
1896
1962
  }
1897
1963
  }
1898
1964
  isAdd = true;
1965
+ pump = sys.pumps.getItemById(id, true);
1899
1966
  }
1900
1967
  else {
1901
1968
  pump = sys.pumps.getItemById(id, false);
1902
- if (data.master > 0 || pump.master > 0 || pump.isVirtual) return await super.setPumpAsync(data);
1969
+ if (data.master > 0 || pump.master > 0) return await super.setPumpAsync(data);
1903
1970
  ntype = typeof data.type === 'undefined' ? pump.type : parseInt(data.type, 10);
1904
1971
  if (isNaN(ntype)) return Promise.reject(new InvalidEquipmentDataError(`Pump type ${data.type} is not valid`, 'Pump', data));
1905
1972
  type = sys.board.valueMaps.pumpTypes.transform(ntype);
@@ -1930,7 +1997,6 @@ class TouchPumpCommands extends PumpCommands {
1930
1997
  if (type.name === 'ss') {
1931
1998
  // The OCP doesn't deal with single speed pumps. Simply add it to the config.
1932
1999
  data.circuits = [];
1933
- pump = sys.pumps.getItemById(id, true);
1934
2000
  pump.set(pump);
1935
2001
  let spump = state.pumps.getItemById(id, true);
1936
2002
  for (let prop in spump) {
@@ -1962,21 +2028,25 @@ class TouchPumpCommands extends PumpCommands {
1962
2028
  retries: 2,
1963
2029
  response: Response.create({ action: 1, payload: [155] })
1964
2030
  });
1965
- outc.appendPayloadByte(typeof type.maxPrimingTime !== 'undefined' ? data.primingTime : 0, pump.primingTime | 0);
1966
2031
  outc.appendPayloadBytes(0, 44);
1967
- if (typeof type.maxPrimingTime !== 'undefined' && type.maxPrimingTime > 0) {
2032
+ if (type.val === 128){
2033
+ outc.setPayloadByte(3, 2);
2034
+ }
2035
+ if (typeof type.maxPrimingTime !== 'undefined' && type.maxPrimingTime > 0 && type.val >=64) {
2036
+ outc.setPayloadByte(2, parseInt(data.primingTime, 10), pump.primingTime || 1);
1968
2037
  let primingSpeed = typeof data.primingSpeed !== 'undefined' ? parseInt(data.primingSpeed, 10) : pump.primingSpeed || type.minSpeed;
1969
2038
  outc.setPayloadByte(21, Math.floor(primingSpeed / 256));
1970
- outc.setPayloadByte(30, primingSpeed - (Math.floor(primingSpeed / 256) * 256));
2039
+ outc.setPayloadByte(30, primingSpeed % 256);
1971
2040
  }
1972
- if (type.val > 1 && type.val < 64) { // Any VF pump. It probably only goes up to Circuit 40 because that's how many circuits *Touch can support.
2041
+ if (type.val === 1) { // Any VF pump.
1973
2042
  outc.setPayloadByte(1, parseInt(data.backgroundCircuit, 10), pump.backgroundCircuit || 6);
2043
+ outc.setPayloadByte(2, parseInt(data.filterSize, 10) / 1000, pump.filterSize / 1000 || 15);
2044
+ // outc.setPayloadByte(2, body.capacity / 1000, 15); RSG - This is filter size, which may or may not equal the body size.
1974
2045
  outc.setPayloadByte(3, parseInt(data.turnovers, 10), pump.turnovers || 2);
1975
2046
  let body = sys.bodies.getItemById(1, sys.equipment.maxBodies >= 1);
1976
- outc.setPayloadByte(2, body.capacity / 1000, 15);
1977
2047
  outc.setPayloadByte(21, parseInt(data.manualFilterGPM, 10), pump.manualFilterGPM || 30);
1978
2048
  outc.setPayloadByte(22, parseInt(data.primingSpeed, 10), pump.primingSpeed || 55);
1979
- let primingTime = typeof data.primingTime !== 'undefined' ? parseInt(data.primingTime, 10) : pump.primingTime;
2049
+ let primingTime = typeof data.primingTime !== 'undefined' ? parseInt(data.primingTime, 10) : pump.primingTime || 0;
1980
2050
  let maxSystemTime = typeof data.maxSystemTime !== 'undefined' ? parseInt(data.maxSystemTime, 10) : pump.maxSystemTime;
1981
2051
  outc.setPayloadByte(23, primingTime | maxSystemTime << 4, 5);
1982
2052
  outc.setPayloadByte(24, parseInt(data.maxPressureIncrease, 10), pump.maxPressureIncrease || 10);
@@ -1984,7 +2054,7 @@ class TouchPumpCommands extends PumpCommands {
1984
2054
  outc.setPayloadByte(26, parseInt(data.backwashTime, 10), pump.backwashTime || 5);
1985
2055
  outc.setPayloadByte(27, parseInt(data.rinseTime, 10), pump.rinseTime || 1);
1986
2056
  outc.setPayloadByte(28, parseInt(data.vacuumFlow, 10), pump.vacuumFlow || 50);
1987
- outc.setPayloadByte(28, parseInt(data.vacuumTime, 10), pump.vacuumTime || 10);
2057
+ outc.setPayloadByte(30, parseInt(data.vacuumTime, 10), pump.vacuumTime || 10);
1988
2058
  }
1989
2059
  if (typeof type.maxCircuits !== 'undefined' && type.maxCircuits > 0 && typeof data.circuits !== 'undefined') { // This pump type supports circuits
1990
2060
  for (let i = 1; i <= data.circuits.length && i <= type.maxCircuits; i++) {
@@ -1997,7 +2067,7 @@ class TouchPumpCommands extends PumpCommands {
1997
2067
  c.units = parseInt(c.units, 10) || type.name === 'vf' ? sys.board.valueMaps.pumpUnits.getValue('gpm') : sys.board.valueMaps.pumpUnits.getValue('rpm');
1998
2068
  if (typeof type.minSpeed !== 'undefined' && c.units === sys.board.valueMaps.pumpUnits.getValue('rpm')) {
1999
2069
  outc.setPayloadByte(i * 2 + 4, Math.floor(speed / 256)); // Set to rpm
2000
- outc.setPayloadByte(i + 21, speed - (Math.floor(speed / 256) * 256));
2070
+ outc.setPayloadByte(i + 21, speed % 256);
2001
2071
  c.speed = speed;
2002
2072
  }
2003
2073
  else if (typeof type.minFlow !== 'undefined' && c.units === sys.board.valueMaps.pumpUnits.getValue('gpm')) {
@@ -2034,7 +2104,7 @@ class TouchPumpCommands extends PumpCommands {
2034
2104
  // [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]
2035
2105
  const setPumpConfig = Outbound.create({
2036
2106
  action: 155,
2037
- payload: [pump.id, pump.type, 0, 2, 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],
2107
+ 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],
2038
2108
  retries: 2,
2039
2109
  response: true
2040
2110
  });
@@ -2110,6 +2180,34 @@ class TouchPumpCommands extends PumpCommands {
2110
2180
  spump.type = pump.type;
2111
2181
  spump.status = 0;
2112
2182
  }
2183
+ public async deletePumpAsync(pump: Pump):Promise<Pump>{
2184
+ let id = pump.id;
2185
+ if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`DeletePumpAsync: Pump ${id} is not valid.`, 0, `pump`))
2186
+ const outc = Outbound.create({
2187
+ action: 155,
2188
+ payload: [id, 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, 0],
2189
+ retries: 2,
2190
+ response: true
2191
+ });
2192
+ return new Promise<Pump>((resolve, reject) => {
2193
+ outc.onComplete = (err, msg) => {
2194
+ if (err) reject(err);
2195
+ else {
2196
+ sys.pumps.removeItemById(id);
2197
+ state.pumps.removeItemById(id);
2198
+ resolve(sys.pumps.getItemById(id,false));
2199
+ const pumpConfigRequest = Outbound.create({
2200
+ action: 216,
2201
+ payload: [id],
2202
+ retries: 2,
2203
+ response: true
2204
+ });
2205
+ conn.queueSendMessage(pumpConfigRequest);
2206
+ }
2207
+ };
2208
+ conn.queueSendMessage(outc);
2209
+ });
2210
+ }
2113
2211
  }
2114
2212
  class TouchHeaterCommands extends HeaterCommands {
2115
2213
  public getInstalledHeaterTypes(body?: number): any {
@@ -2170,13 +2268,14 @@ class TouchHeaterCommands extends HeaterCommands {
2170
2268
  }
2171
2269
  // RKS: Not sure what to do with this as the heater data for Touch isn't actually processed anywhere.
2172
2270
  public async setHeaterAsync(obj: any): Promise<Heater> {
2271
+ if (obj.master === 1 || parseInt(obj.id, 10) > 255) return super.setHeaterAsync(obj);
2173
2272
  return new Promise<Heater>((resolve, reject) => {
2174
2273
  let id = typeof obj.id === 'undefined' ? -1 : parseInt(obj.id, 10);
2175
2274
  if (isNaN(id)) return reject(new InvalidEquipmentIdError('Heater Id is not valid.', obj.id, 'Heater'));
2176
2275
  let heater: Heater;
2177
2276
  if (id <= 0) {
2178
2277
  // We are adding a heater. In this case all heaters are virtual.
2179
- let heaters = sys.heaters.filter(h => h.isVirtual === false);
2278
+ let heaters = sys.heaters.filter(h => h.master === 1);
2180
2279
  id = heaters.getMaxId() + 1;
2181
2280
  }
2182
2281
  heater = sys.heaters.getItemById(id, true);
@@ -2187,7 +2286,7 @@ class TouchHeaterCommands extends HeaterCommands {
2187
2286
  }
2188
2287
  }
2189
2288
  let hstate = state.heaters.getItemById(id, true);
2190
- hstate.isVirtual = heater.isVirtual = true;
2289
+
2191
2290
  hstate.name = heater.name;
2192
2291
  hstate.type = heater.type;
2193
2292
  heater.master = 1;
@@ -2197,7 +2296,7 @@ class TouchHeaterCommands extends HeaterCommands {
2197
2296
  });
2198
2297
  }
2199
2298
  public async deleteHeaterAsync(obj: any): Promise<Heater> {
2200
- if (utils.makeBool(obj.isVirtual) || obj.master === 1 || parseInt(obj.id, 10) > 255) return await super.deleteHeaterAsync(obj);
2299
+ if (utils.makeBool(obj.master === 1 || parseInt(obj.id, 10) > 255)) return super.deleteHeaterAsync(obj);
2201
2300
  return new Promise<Heater>((resolve, reject) => {
2202
2301
  let id = parseInt(obj.id, 10);
2203
2302
  if (isNaN(id)) return reject(new InvalidEquipmentIdError('Cannot delete. Heater Id is not valid.', obj.id, 'Heater'));
@@ -2283,7 +2382,7 @@ class TouchChemControllerCommands extends ChemControllerCommands {
2283
2382
  // Now lets do all our validation to the incoming chem controller data.
2284
2383
  let name = typeof data.name !== 'undefined' ? data.name : chem.name || `IntelliChem - ${address - 143}`;
2285
2384
  let type = sys.board.valueMaps.chemControllerTypes.transformByName('intellichem');
2286
- // So now we are down to the nitty gritty setting the data for the REM or Homegrown Chem controller.
2385
+ // So now we are down to the nitty gritty setting the data for the REM Chem controller.
2287
2386
  let calciumHardness = typeof data.calciumHardness !== 'undefined' ? parseInt(data.calciumHardness, 10) : chem.calciumHardness;
2288
2387
  let cyanuricAcid = typeof data.cyanuricAcid !== 'undefined' ? parseInt(data.cyanuricAcid, 10) : chem.cyanuricAcid;
2289
2388
  let alkalinity = typeof data.alkalinity !== 'undefined' ? parseInt(data.alkalinity, 10) : chem.alkalinity;
@@ -2336,7 +2435,7 @@ class TouchChemControllerCommands extends ChemControllerCommands {
2336
2435
  action: 211,
2337
2436
  payload: [],
2338
2437
  retries: 3, // We are going to try 4 times.
2339
- response: Response.create({ protocol: Protocol.IntelliChem, action: 1, payload:[211] }),
2438
+ response: Response.create({ protocol: Protocol.IntelliChem, action: 1, payload: [211] }),
2340
2439
  onAbort: () => { },
2341
2440
  onComplete: (err) => {
2342
2441
  if (err) reject(err);
@@ -2364,7 +2463,7 @@ class TouchChemControllerCommands extends ChemControllerCommands {
2364
2463
  chem.orp.tolerance.high = orpTolerance.high;
2365
2464
  chem.ph.setpoint = pHSetpoint;
2366
2465
  chem.orp.setpoint = orpSetpoint;
2367
- chem.siCalcType = siCalcType;
2466
+ schem.siCalcType = chem.siCalcType = siCalcType;
2368
2467
  chem.address = schem.address = address;
2369
2468
  chem.name = schem.name = name;
2370
2469
  chem.flowSensor.enabled = false;