nodejs-poolcontroller 7.4.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 (55) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/Changelog +3 -0
  3. package/README.md +2 -2
  4. package/app.ts +2 -0
  5. package/config/Config.ts +3 -0
  6. package/config/VersionCheck.ts +8 -4
  7. package/controller/Equipment.ts +89 -29
  8. package/controller/Errors.ts +14 -1
  9. package/controller/State.ts +75 -31
  10. package/controller/boards/EasyTouchBoard.ts +81 -36
  11. package/controller/boards/IntelliCenterBoard.ts +96 -32
  12. package/controller/boards/IntelliTouchBoard.ts +103 -29
  13. package/controller/boards/NixieBoard.ts +79 -27
  14. package/controller/boards/SystemBoard.ts +1552 -822
  15. package/controller/comms/Comms.ts +84 -9
  16. package/controller/comms/messages/Messages.ts +10 -4
  17. package/controller/comms/messages/config/ChlorinatorMessage.ts +13 -4
  18. package/controller/comms/messages/config/CircuitGroupMessage.ts +6 -0
  19. package/controller/comms/messages/config/CoverMessage.ts +1 -0
  20. package/controller/comms/messages/config/EquipmentMessage.ts +4 -0
  21. package/controller/comms/messages/config/ExternalMessage.ts +43 -25
  22. package/controller/comms/messages/config/FeatureMessage.ts +8 -1
  23. package/controller/comms/messages/config/GeneralMessage.ts +8 -0
  24. package/controller/comms/messages/config/HeaterMessage.ts +10 -9
  25. package/controller/comms/messages/config/IntellichemMessage.ts +4 -1
  26. package/controller/comms/messages/config/OptionsMessage.ts +13 -1
  27. package/controller/comms/messages/config/PumpMessage.ts +4 -20
  28. package/controller/comms/messages/config/RemoteMessage.ts +4 -0
  29. package/controller/comms/messages/config/ScheduleMessage.ts +11 -0
  30. package/controller/comms/messages/config/SecurityMessage.ts +1 -0
  31. package/controller/comms/messages/config/ValveMessage.ts +12 -2
  32. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +2 -3
  33. package/controller/comms/messages/status/EquipmentStateMessage.ts +74 -22
  34. package/controller/comms/messages/status/HeaterStateMessage.ts +15 -6
  35. package/controller/comms/messages/status/IntelliChemStateMessage.ts +37 -26
  36. package/controller/nixie/Nixie.ts +18 -16
  37. package/controller/nixie/chemistry/ChemController.ts +57 -37
  38. package/controller/nixie/chemistry/Chlorinator.ts +7 -8
  39. package/controller/nixie/circuits/Circuit.ts +17 -0
  40. package/controller/nixie/pumps/Pump.ts +49 -24
  41. package/controller/nixie/schedules/Schedule.ts +1 -1
  42. package/defaultConfig.json +15 -0
  43. package/issue_template.md +1 -1
  44. package/logger/DataLogger.ts +37 -22
  45. package/package.json +3 -1
  46. package/web/Server.ts +515 -27
  47. package/web/bindings/influxDB.json +35 -0
  48. package/web/bindings/mqtt.json +62 -3
  49. package/web/bindings/mqttAlt.json +57 -4
  50. package/web/interfaces/httpInterface.ts +2 -0
  51. package/web/interfaces/influxInterface.ts +3 -2
  52. package/web/interfaces/mqttInterface.ts +12 -1
  53. package/web/services/config/Config.ts +162 -37
  54. package/web/services/state/State.ts +47 -3
  55. package/web/services/state/StateSocket.ts +1 -1
@@ -36,6 +36,7 @@ export class State implements IState {
36
36
  private _isDirty: boolean;
37
37
  private _timerDirty: NodeJS.Timeout;
38
38
  protected _dt: Timestamp;
39
+ protected _startTime: Timestamp;
39
40
  protected _controllerType: ControllerType;
40
41
  protected onchange = (obj, fn) => {
41
42
  const handler = {
@@ -191,6 +192,8 @@ export class State implements IState {
191
192
  public get controllerState() {
192
193
  var self = this;
193
194
  return {
195
+ systemUnits: sys.board.valueMaps.systemUnits.transform(sys.general.options.units),
196
+ startTime: self.data.startTime || '',
194
197
  time: self.data.time || '',
195
198
  // body: self.data.body || {},
196
199
  valve: self.data.valve || 0,
@@ -305,7 +308,6 @@ export class State implements IState {
305
308
  this.hasChanged = true;
306
309
  }
307
310
  }
308
-
309
311
  }
310
312
  public get valve(): number { return this.data.valve; }
311
313
  public set valve(val: number) {
@@ -386,6 +388,7 @@ export class State implements IState {
386
388
  this.comms = new CommsState();
387
389
  this.heliotrope = new Heliotrope();
388
390
  this.appVersion = new AppVersionState(this.data, 'appVersion');
391
+ this.data.startTime = Timestamp.toISOLocal(new Date());
389
392
  versionCheck.checkGitLocal();
390
393
  }
391
394
  public resetData() {
@@ -876,6 +879,11 @@ export class PumpStateCollection extends EqStateCollection<PumpState> {
876
879
  }
877
880
  export class PumpState extends EqState {
878
881
  public dataName: string = 'pump';
882
+ public initData() {
883
+ if (typeof this.data.status === 'undefined') {
884
+ this.data.status = { name: 'ok', desc: 'Ok', val: 0 };
885
+ }
886
+ }
879
887
  private _threshold = 0.05;
880
888
  private exceedsThreshold(origVal: number, newVal: number) {
881
889
  return Math.abs((newVal - origVal) / origVal) > this._threshold;
@@ -909,7 +917,7 @@ export class PumpState extends EqState {
909
917
  // quick fix for #172
910
918
  if (this.status !== val) {
911
919
  if (sys.board.valueMaps.pumpTypes.getName(this.type) === 'vsf' && val === 0) {
912
- this.data.status = { name: 'ok', desc: 'Ok', val };
920
+ this.data.status = { name: 'ok', desc: 'Ok', val: 0 };
913
921
  }
914
922
  else this.data.status = sys.board.valueMaps.pumpStatus.transform(val);
915
923
  this.hasChanged = true;
@@ -991,6 +999,7 @@ export class ScheduleState extends EqState {
991
999
  public get startDate(): Date { return this._startDate; }
992
1000
  public set startDate(val: Date) { this._startDate = val; this._saveStartDate(); }
993
1001
  private _saveStartDate() {
1002
+ if (typeof this._startDate === 'undefined') this._startDate = new Date();
994
1003
  this.startDate.setHours(0, 0, 0, 0);
995
1004
  this.setDataVal('startDate', Timestamp.toISOLocal(this.startDate));
996
1005
  }
@@ -1097,7 +1106,7 @@ export class CircuitGroupStateCollection extends EqStateCollection<CircuitGroupS
1097
1106
  for (let i = this.data.length - 1; i >= 0; i--) {
1098
1107
  if (isNaN(this.data[i].id)) this.data.splice(i, 1);
1099
1108
  else {
1100
- if (typeof sys.pumps.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1109
+ if (typeof sys.circuitGroups.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1101
1110
  }
1102
1111
  }
1103
1112
  let cfg = sys.circuitGroups.toArray();
@@ -1173,7 +1182,7 @@ export class LightGroupStateCollection extends EqStateCollection<LightGroupState
1173
1182
  for (let i = this.data.length - 1; i >= 0; i--) {
1174
1183
  if (isNaN(this.data[i].id)) this.data.splice(i, 1);
1175
1184
  else {
1176
- if (typeof sys.pumps.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1185
+ if (typeof sys.lightGroups.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1177
1186
  }
1178
1187
  }
1179
1188
  let cfg = sys.lightGroups.toArray();
@@ -1279,6 +1288,7 @@ export class BodyTempState extends EqState {
1279
1288
  public dataName = 'bodyTempState';
1280
1289
  public initData() {
1281
1290
  if (typeof this.data.heaterOptions === 'undefined') this.data.heaterOptions = { total: 0 };
1291
+ if (typeof this.data.isCovered === 'undefined') this.data.isCovered = false;
1282
1292
  }
1283
1293
  public get id(): number { return this.data.id; }
1284
1294
  public set id(val: number) { this.setDataVal('id', val); }
@@ -1317,6 +1327,8 @@ export class BodyTempState extends EqState {
1317
1327
  public set coolSetpoint(val: number) { this.setDataVal('coolSetpoint', val); }
1318
1328
  public get isOn(): boolean { return this.data.isOn; }
1319
1329
  public set isOn(val: boolean) { this.setDataVal('isOn', val); }
1330
+ public get isCovered(): boolean { return this.data.isCovered; }
1331
+ public set isCovered(val: boolean) { this.setDataVal('isCovered', val); }
1320
1332
  public emitData(name: string, data: any) { webApp.emitToClients('body', this.data); }
1321
1333
  // RKS: This is a very interesting object because we have a varied object. Type safety rules should not apply
1322
1334
  // here as the heater types are specific to the installed equipment. The reason is because it has no meaning without the body and the calculation of it should
@@ -1388,7 +1400,7 @@ export class HeaterStateCollection extends EqStateCollection<HeaterState> {
1388
1400
  this.data.splice(i, 1);
1389
1401
  }
1390
1402
  else {
1391
- if (typeof sys.pumps.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1403
+ if (typeof sys.heaters.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1392
1404
  }
1393
1405
  }
1394
1406
  let cfg = sys.heaters.toArray();
@@ -1435,7 +1447,7 @@ export class FeatureStateCollection extends EqStateCollection<FeatureState> {
1435
1447
  for (let i = this.data.length - 1; i >= 0; i--) {
1436
1448
  if (isNaN(this.data[i].id)) this.data.splice(i, 1);
1437
1449
  else {
1438
- if (typeof sys.pumps.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1450
+ if (typeof sys.features.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1439
1451
  }
1440
1452
  }
1441
1453
  let cfg = sys.features.toArray();
@@ -1532,7 +1544,7 @@ export class CircuitStateCollection extends EqStateCollection<CircuitState> {
1532
1544
  for (let i = this.data.length - 1; i >= 0; i--) {
1533
1545
  if (isNaN(this.data[i].id)) this.data.splice(i, 1);
1534
1546
  else {
1535
- if (typeof sys.pumps.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1547
+ if (typeof sys.circuits.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1536
1548
  }
1537
1549
  }
1538
1550
  let cfg = sys.circuits.toArray();
@@ -1569,10 +1581,12 @@ export class CircuitState extends EqState implements ICircuitState {
1569
1581
  this.hasChanged = true;
1570
1582
  }
1571
1583
  }
1572
- public get lightingTheme(): number { return typeof (this.data.lightingTheme) !== 'undefined' ? this.data.lightingTheme.val : 255; }
1584
+ public get lightingTheme(): number { return typeof this.data.lightingTheme !== 'undefined' ? this.data.lightingTheme.val : 255; }
1573
1585
  public set lightingTheme(val: number) {
1574
1586
  if (this.lightingTheme !== val) {
1575
- this.data.lightingTheme = sys.board.valueMaps.lightThemes.transform(val);
1587
+ // Force this to undefined when we are a circuit without a theme.
1588
+ if (typeof val === 'undefined') this.data.lightingTheme = undefined;
1589
+ else this.data.lightingTheme = sys.board.valueMaps.lightThemes.transform(val);
1576
1590
  this.hasChanged = true;
1577
1591
  }
1578
1592
  }
@@ -1603,7 +1617,7 @@ export class ValveStateCollection extends EqStateCollection<ValveState> {
1603
1617
  for (let i = this.data.length - 1; i >= 0; i--) {
1604
1618
  if (isNaN(this.data[i].id)) this.data.splice(i, 1);
1605
1619
  else {
1606
- if (typeof sys.pumps.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1620
+ if (typeof sys.valves.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1607
1621
  }
1608
1622
  }
1609
1623
  let cfg = sys.valves.toArray();
@@ -1643,7 +1657,7 @@ export class ValveState extends EqState {
1643
1657
  if (valve.circuit !== 256) vstate.circuit = state.circuits.getInterfaceById(valve.circuit).get(true);
1644
1658
  vstate.isIntake = utils.makeBool(valve.isIntake);
1645
1659
  vstate.isReturn = utils.makeBool(valve.isReturn);
1646
- vstate.isVirtual = utils.makeBool(valve.isVirtual);
1660
+ // vstate.isVirtual = utils.makeBool(valve.isVirtual);
1647
1661
  vstate.isActive = utils.makeBool(valve.isActive);
1648
1662
  vstate.pinId = valve.pinId;
1649
1663
  return vstate;
@@ -1661,12 +1675,15 @@ export class CoverStateCollection extends EqStateCollection<CoverState> {
1661
1675
  }
1662
1676
  export class CoverState extends EqState {
1663
1677
  public dataName: string = 'cover';
1678
+ public initData() {
1679
+ if (typeof this.data.isClosed === 'undefined') this.data.isClosed = false;
1680
+ }
1664
1681
  public get id(): number { return this.data.id; }
1665
1682
  public set id(val: number) { this.data.id = val; }
1666
1683
  public get name(): string { return this.data.name; }
1667
1684
  public set name(val: string) { this.setDataVal('name', val); }
1668
- public get isOpen(): boolean { return this.data.isOpen; }
1669
- public set isOpen(val: boolean) { this.setDataVal('isOpen', val); }
1685
+ public get isClosed(): boolean { return this.data.isClosed; }
1686
+ public set isClosed(val: boolean) { this.setDataVal('isClosed', val); }
1670
1687
  }
1671
1688
  export class ChlorinatorStateCollection extends EqStateCollection<ChlorinatorState> {
1672
1689
  public createItem(data: any): ChlorinatorState { return new ChlorinatorState(data); }
@@ -1676,7 +1693,7 @@ export class ChlorinatorStateCollection extends EqStateCollection<ChlorinatorSta
1676
1693
  for (let i = this.data.length - 1; i >= 0; i--) {
1677
1694
  if (isNaN(this.data[i].id)) this.data.splice(i, 1);
1678
1695
  else {
1679
- if (typeof sys.pumps.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1696
+ if (typeof sys.chlorinators.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1680
1697
  }
1681
1698
  }
1682
1699
  let cfg = sys.chlorinators.toArray();
@@ -1730,16 +1747,6 @@ export class ChlorinatorState extends EqState {
1730
1747
  this.hasChanged = true;
1731
1748
  }
1732
1749
  }
1733
- //public get virtualControllerStatus(): number {
1734
- // return typeof (this.data.virtualControllerStatus) !== 'undefined' ? this.data.virtualControllerStatus.val : -1;
1735
- //}
1736
- //public set virtualControllerStatus(val: number) {
1737
- // if (this.virtualControllerStatus !== val) {
1738
- // this.data.virtualControllerStatus = sys.board.valueMaps.virtualControllerStatus.transform(val);
1739
- // this.hasChanged = true;
1740
-
1741
- // }
1742
- //}
1743
1750
  public get type(): number { return typeof (this.data.type) !== 'undefined' ? this.data.type.val : -1; }
1744
1751
  public set type(val: number) {
1745
1752
  if (this.type !== val) {
@@ -1826,6 +1833,12 @@ export class ChlorinatorState extends EqState {
1826
1833
  else
1827
1834
  this.setDataVal('superChlor', false);
1828
1835
  }
1836
+ public getExtended(): any {
1837
+ let schlor = this.get(true);
1838
+ let chlor = sys.chlorinators.getItemById(this.id, false);
1839
+ schlor.lockSetpoints = chlor.disabled || chlor.isDosing;
1840
+ return schlor;
1841
+ }
1829
1842
  }
1830
1843
  export class ChemControllerStateCollection extends EqStateCollection<ChemControllerState> {
1831
1844
  public createItem(data: any): ChemControllerState { return new ChemControllerState(data); }
@@ -1833,7 +1846,7 @@ export class ChemControllerStateCollection extends EqStateCollection<ChemControl
1833
1846
  for (let i = this.data.length - 1; i >= 0; i--) {
1834
1847
  if (isNaN(this.data[i].id)) this.data.splice(i, 1);
1835
1848
  else {
1836
- if (typeof sys.pumps.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1849
+ if (typeof sys.chemControllers.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1837
1850
  }
1838
1851
  }
1839
1852
  // Make sure we have at least the items that exist in the config.
@@ -1874,7 +1887,7 @@ export class ChemControllerState extends EqState {
1874
1887
  //var chemControllerState = {
1875
1888
  // lastComm: 'number', // The unix time the chem controller sent its status.
1876
1889
  // id: 'number', // Id of the chemController.
1877
- // type: 'valueMap', // intellichem, homegrown, rem.
1890
+ // type: 'valueMap', // intellichem, rem.
1878
1891
  // address: 'number', // Assigned address if IntelliChem.
1879
1892
  // name: 'string', // Name assigned to the controller.
1880
1893
  // status: 'valueMap', // ok, nocomms, setupError
@@ -2113,7 +2126,6 @@ export class ChemicalState extends ChildEqState {
2113
2126
  if (typeof this.data.flowDelay === 'undefined') this.data.flowDelay = false;
2114
2127
  if (typeof this.data.dosingStatus === 'undefined') this.dosingStatus = 2;
2115
2128
  if (typeof this.data.enabled === 'undefined') this.data.enabled = true;
2116
- if (typeof this.data.level === 'undefined') this.data.level = 0;
2117
2129
  }
2118
2130
  public getConfig(): Chemical { return; }
2119
2131
  public calcDoseHistory(): number {
@@ -2277,7 +2289,7 @@ export class ChemicalState extends ChildEqState {
2277
2289
  }
2278
2290
  export class ChemicalPhState extends ChemicalState {
2279
2291
  public initData() {
2280
- if (typeof this.data.chemType === 'undefined') this.data.chemType === 'acid';
2292
+ // if (typeof this.data.chemType === 'undefined') this.data.chemType === 'acid'; // RSG 10-23-21 - Only a getter; don't need to set this.
2281
2293
  super.initData();
2282
2294
  }
2283
2295
  public getConfig() {
@@ -2482,14 +2494,16 @@ export class ChemicalDoseState extends DataLoggerEntry {
2482
2494
  public _isManual: boolean;
2483
2495
 
2484
2496
  constructor(entry?: string | object) {
2485
- super(entry);
2497
+ super();
2498
+ if (typeof entry === 'object') entry = JSON.stringify(entry);
2499
+ if (typeof entry === 'string') this.parse(entry);
2486
2500
  // Javascript is idiotic in that the initialization of variables
2487
2501
  // do not happen before the assignment so some of the values can be undefined.
2488
2502
  if (typeof this.volumeDosed === 'undefined' || !this.volumeDosed) this.volumeDosed = 0;
2489
2503
  if (typeof this.volume === 'undefined' || !this.volume) this.volume = 0;
2490
2504
  if (typeof this._isManual === 'undefined') this._isManual = this.method === 'manual';
2491
2505
  if (typeof this.timeDosed === 'undefined' || !this.timeDosed) this.timeDosed = 0;
2492
- if (typeof this._timeDosed === 'undefined') this.timeDosed * 1000;
2506
+ if (typeof this._timeDosed === 'undefined') this._timeDosed = this.timeDosed * 1000;
2493
2507
  if (typeof this.time === 'undefined' || !this.time) this.time = 0;
2494
2508
  }
2495
2509
  public id: number;
@@ -2506,10 +2520,40 @@ export class ChemicalDoseState extends DataLoggerEntry {
2506
2520
  public time: number;
2507
2521
  public timeDosed: number;
2508
2522
 
2509
- public createInstance(entry?: string): ChemicalDoseState { return new ChemicalDoseState(entry); }
2523
+ public static createInstance(entry?: string): ChemicalDoseState { return new ChemicalDoseState(entry); }
2510
2524
  public save() { DataLogger.writeEnd(`chemDosage_${this.chem}.log`, this); }
2511
2525
  public get timeRemaining(): number { return Math.floor(Math.max(0, this.time - (this._timeDosed / 1000))); }
2512
2526
  public get volumeRemaining(): number { return Math.max(0, this.volume - this.volumeDosed); }
2527
+ public parse(entry: string) {
2528
+ // let obj = typeof entry !== 'undefined' ? JSON.parse(entry, this.dateParser) : {};
2529
+ let obj = typeof entry !== 'undefined' ? JSON.parse(entry) : {};
2530
+ for (const prop in obj) {obj[prop] = this.dateParser(prop, obj[prop])}
2531
+ if (typeof obj.setpoint !== 'undefined') this.setpoint = obj.setpoint;
2532
+ if (typeof obj.method !== 'undefined') this.method = obj.method;
2533
+ if (typeof obj.start !== 'undefined') this.start = obj.start;
2534
+ if (typeof obj.end !== 'undefined') this.end = obj.end;
2535
+ if (typeof obj.chem !== 'undefined') this.chem = obj.chem;
2536
+ if (typeof obj.demand !== 'undefined') this.demand = obj.demand;
2537
+ if (typeof obj.id !== 'undefined') this.id = obj.id;
2538
+ if (typeof obj.level !== 'undefined') this.level = obj.level;
2539
+ if (typeof obj.volume !== 'undefined') this.volume = obj.volume;
2540
+ if (typeof obj.status !== 'undefined') this.status = obj.status;
2541
+ if (typeof obj.volumeDosed !== 'undefined') this.volumeDosed = obj.volumeDosed;
2542
+ if (typeof obj.time !== 'undefined') this.time = obj.time;
2543
+ if (typeof obj.timeDosed !== 'undefined') this.timeDosed = obj.timeDosed;
2544
+ // this.setProperties(obj);
2545
+ }
2546
+ protected setProperties(data: any) {
2547
+ let op = Object.getOwnPropertyNames(Object.getPrototypeOf(this));
2548
+ for (let i in op) {
2549
+ let prop = op[i];
2550
+ if (typeof this[prop] === 'function') continue;
2551
+ if (typeof data[prop] !== 'undefined') {
2552
+ if (typeof this[prop] === null || typeof data[prop] === null) continue;
2553
+ this[prop] = data[prop];
2554
+ }
2555
+ }
2556
+ }
2513
2557
  }
2514
2558
 
2515
2559
  export class ChemicalDemandState extends ChildEqState {
@@ -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 }],
@@ -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
  }
@@ -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]);
@@ -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) {
@@ -1292,6 +1303,7 @@ export class TouchCircuitCommands extends CircuitCommands {
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;
1294
1305
  cstate.isActive = circuit.isActive = true;
1306
+ circuit.master = 0;
1295
1307
  let eggTimer = sys.eggTimers.find(elem => elem.circuit === parseInt(data.id, 10));
1296
1308
  try {
1297
1309
  if (circuit.eggTimer === 720) {
@@ -1657,28 +1669,26 @@ class TouchChlorinatorCommands extends ChlorinatorCommands {
1657
1669
  public async setChlorAsync(obj: any): Promise<ChlorinatorState> {
1658
1670
  let id = parseInt(obj.id, 10);
1659
1671
  let isAdd = false;
1660
- let isVirtual = false;
1661
- if (id <= 0 || isNaN(id)) id = 1;
1662
1672
  let chlor = sys.chlorinators.getItemById(id);
1663
- if (id < 0 || isNaN(id)) {
1673
+ if (id <= 0 || isNaN(id)) {
1664
1674
  isAdd = true;
1665
- chlor.master = utils.makeBool(obj.isVirtual) ? 0 : 1;
1675
+ chlor.master = utils.makeBool(obj.master) ? 1 : 0;
1666
1676
  // Calculate an id for the chlorinator. The messed up part is that if a chlorinator is not attached to the OCP, its address
1667
1677
  // cannot be set by the MUX. This will have to wait.
1668
1678
  id = 1;
1669
- }
1670
- //let chlor = extend(true, {}, sys.chlorinators.getItemById(id).get(), obj);
1671
- // 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);
1672
1682
  // RKS: I am not even sure this can be done with Touch as the master on the RS485 bus.
1673
- if (chlor.master === 1 || isVirtual) return super.setChlorAsync(obj);
1683
+ if (typeof chlor.master === 'undefined') chlor.master = 0;
1674
1684
  let name = obj.name || chlor.name || 'IntelliChlor' + id;
1675
- let poolSetpoint = parseInt(obj.poolSetpoint, 10);
1676
- let spaSetpoint = parseInt(obj.spaSetpoint, 10);
1677
1685
  let superChlorHours = parseInt(obj.superChlorHours, 10);
1678
1686
  if (typeof obj.superChlorinate !== 'undefined') obj.superChlor = utils.makeBool(obj.superChlorinate);
1679
1687
  let superChlorinate = typeof obj.superChlor === 'undefined' ? undefined : utils.makeBool(obj.superChlor);
1680
- let disabled = typeof obj.disabled !== 'undefined' ? utils.makeBool(obj.disabled) : chlor.disabled;
1681
1688
  let isDosing = typeof obj.isDosing !== 'undefined' ? utils.makeBool(obj.isDosing) : chlor.isDosing;
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;
1682
1692
  let model = typeof obj.model !== 'undefined' ? obj.model : chlor.model;
1683
1693
  let chlorType = typeof obj.type !== 'undefined' ? sys.board.valueMaps.chlorinatorType.encode(obj.type) : chlor.type || 0;
1684
1694
  if (isAdd) {
@@ -1705,6 +1715,7 @@ class TouchChlorinatorCommands extends ChlorinatorCommands {
1705
1715
  if (poolSetpoint > 100 || poolSetpoint < 0) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator poolSetpoint is out of range: ${chlor.poolSetpoint}`, 'chlorinator', chlor.poolSetpoint));
1706
1716
  if (spaSetpoint > 100 || spaSetpoint < 0) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator spaSetpoint is out of range: ${chlor.poolSetpoint}`, 'chlorinator', chlor.spaSetpoint));
1707
1717
  if (typeof obj.ignoreSaltReading !== 'undefined') chlor.ignoreSaltReading = utils.makeBool(obj.ignoreSaltReading);
1718
+
1708
1719
  let _timeout: NodeJS.Timeout;
1709
1720
  try {
1710
1721
  let request153packet = new Promise<void>((resolve, reject) => {
@@ -1726,7 +1737,6 @@ class TouchChlorinatorCommands extends ChlorinatorCommands {
1726
1737
  reject = undefined;
1727
1738
  }
1728
1739
  else {
1729
- logger.debug(`chlor response 153`);
1730
1740
  resolve();
1731
1741
  resolve = undefined;
1732
1742
  }
@@ -1772,7 +1782,6 @@ class TouchChlorinatorCommands extends ChlorinatorCommands {
1772
1782
  reject(err);
1773
1783
  }
1774
1784
  else{
1775
- logger.debug(`chlor resolve 217`);
1776
1785
  resolve();
1777
1786
  }
1778
1787
  }
@@ -1793,7 +1802,9 @@ class TouchChlorinatorCommands extends ChlorinatorCommands {
1793
1802
  }
1794
1803
  public async deleteChlorAsync(obj: any): Promise<ChlorinatorState> {
1795
1804
  let id = parseInt(obj.id, 10);
1796
- 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);
1797
1808
  return new Promise<ChlorinatorState>((resolve, reject) => {
1798
1809
  let out = Outbound.create({
1799
1810
  dest: 16,
@@ -1807,8 +1818,9 @@ class TouchChlorinatorCommands extends ChlorinatorCommands {
1807
1818
  reject(err);
1808
1819
  }
1809
1820
  else {
1810
- let cstate = state.chlorinators.getItemById(id);
1811
- 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);
1812
1824
  chlor.isActive = cstate.isActive = false;
1813
1825
  sys.chlorinators.removeItemById(id);
1814
1826
  state.chlorinators.removeItemById(id);
@@ -1950,10 +1962,11 @@ class TouchPumpCommands extends PumpCommands {
1950
1962
  }
1951
1963
  }
1952
1964
  isAdd = true;
1965
+ pump = sys.pumps.getItemById(id, true);
1953
1966
  }
1954
1967
  else {
1955
1968
  pump = sys.pumps.getItemById(id, false);
1956
- 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);
1957
1970
  ntype = typeof data.type === 'undefined' ? pump.type : parseInt(data.type, 10);
1958
1971
  if (isNaN(ntype)) return Promise.reject(new InvalidEquipmentDataError(`Pump type ${data.type} is not valid`, 'Pump', data));
1959
1972
  type = sys.board.valueMaps.pumpTypes.transform(ntype);
@@ -1984,7 +1997,6 @@ class TouchPumpCommands extends PumpCommands {
1984
1997
  if (type.name === 'ss') {
1985
1998
  // The OCP doesn't deal with single speed pumps. Simply add it to the config.
1986
1999
  data.circuits = [];
1987
- pump = sys.pumps.getItemById(id, true);
1988
2000
  pump.set(pump);
1989
2001
  let spump = state.pumps.getItemById(id, true);
1990
2002
  for (let prop in spump) {
@@ -2016,21 +2028,25 @@ class TouchPumpCommands extends PumpCommands {
2016
2028
  retries: 2,
2017
2029
  response: Response.create({ action: 1, payload: [155] })
2018
2030
  });
2019
- outc.appendPayloadByte(typeof type.maxPrimingTime !== 'undefined' ? data.primingTime : 0, pump.primingTime | 0);
2020
2031
  outc.appendPayloadBytes(0, 44);
2021
- 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);
2022
2037
  let primingSpeed = typeof data.primingSpeed !== 'undefined' ? parseInt(data.primingSpeed, 10) : pump.primingSpeed || type.minSpeed;
2023
2038
  outc.setPayloadByte(21, Math.floor(primingSpeed / 256));
2024
- outc.setPayloadByte(30, primingSpeed - (Math.floor(primingSpeed / 256) * 256));
2039
+ outc.setPayloadByte(30, primingSpeed % 256);
2025
2040
  }
2026
- 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.
2027
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.
2028
2045
  outc.setPayloadByte(3, parseInt(data.turnovers, 10), pump.turnovers || 2);
2029
2046
  let body = sys.bodies.getItemById(1, sys.equipment.maxBodies >= 1);
2030
- outc.setPayloadByte(2, body.capacity / 1000, 15);
2031
2047
  outc.setPayloadByte(21, parseInt(data.manualFilterGPM, 10), pump.manualFilterGPM || 30);
2032
2048
  outc.setPayloadByte(22, parseInt(data.primingSpeed, 10), pump.primingSpeed || 55);
2033
- 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;
2034
2050
  let maxSystemTime = typeof data.maxSystemTime !== 'undefined' ? parseInt(data.maxSystemTime, 10) : pump.maxSystemTime;
2035
2051
  outc.setPayloadByte(23, primingTime | maxSystemTime << 4, 5);
2036
2052
  outc.setPayloadByte(24, parseInt(data.maxPressureIncrease, 10), pump.maxPressureIncrease || 10);
@@ -2038,7 +2054,7 @@ class TouchPumpCommands extends PumpCommands {
2038
2054
  outc.setPayloadByte(26, parseInt(data.backwashTime, 10), pump.backwashTime || 5);
2039
2055
  outc.setPayloadByte(27, parseInt(data.rinseTime, 10), pump.rinseTime || 1);
2040
2056
  outc.setPayloadByte(28, parseInt(data.vacuumFlow, 10), pump.vacuumFlow || 50);
2041
- outc.setPayloadByte(28, parseInt(data.vacuumTime, 10), pump.vacuumTime || 10);
2057
+ outc.setPayloadByte(30, parseInt(data.vacuumTime, 10), pump.vacuumTime || 10);
2042
2058
  }
2043
2059
  if (typeof type.maxCircuits !== 'undefined' && type.maxCircuits > 0 && typeof data.circuits !== 'undefined') { // This pump type supports circuits
2044
2060
  for (let i = 1; i <= data.circuits.length && i <= type.maxCircuits; i++) {
@@ -2051,7 +2067,7 @@ class TouchPumpCommands extends PumpCommands {
2051
2067
  c.units = parseInt(c.units, 10) || type.name === 'vf' ? sys.board.valueMaps.pumpUnits.getValue('gpm') : sys.board.valueMaps.pumpUnits.getValue('rpm');
2052
2068
  if (typeof type.minSpeed !== 'undefined' && c.units === sys.board.valueMaps.pumpUnits.getValue('rpm')) {
2053
2069
  outc.setPayloadByte(i * 2 + 4, Math.floor(speed / 256)); // Set to rpm
2054
- outc.setPayloadByte(i + 21, speed - (Math.floor(speed / 256) * 256));
2070
+ outc.setPayloadByte(i + 21, speed % 256);
2055
2071
  c.speed = speed;
2056
2072
  }
2057
2073
  else if (typeof type.minFlow !== 'undefined' && c.units === sys.board.valueMaps.pumpUnits.getValue('gpm')) {
@@ -2088,7 +2104,7 @@ class TouchPumpCommands extends PumpCommands {
2088
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]
2089
2105
  const setPumpConfig = Outbound.create({
2090
2106
  action: 155,
2091
- 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],
2092
2108
  retries: 2,
2093
2109
  response: true
2094
2110
  });
@@ -2164,6 +2180,34 @@ class TouchPumpCommands extends PumpCommands {
2164
2180
  spump.type = pump.type;
2165
2181
  spump.status = 0;
2166
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
+ }
2167
2211
  }
2168
2212
  class TouchHeaterCommands extends HeaterCommands {
2169
2213
  public getInstalledHeaterTypes(body?: number): any {
@@ -2224,13 +2268,14 @@ class TouchHeaterCommands extends HeaterCommands {
2224
2268
  }
2225
2269
  // RKS: Not sure what to do with this as the heater data for Touch isn't actually processed anywhere.
2226
2270
  public async setHeaterAsync(obj: any): Promise<Heater> {
2271
+ if (obj.master === 1 || parseInt(obj.id, 10) > 255) return super.setHeaterAsync(obj);
2227
2272
  return new Promise<Heater>((resolve, reject) => {
2228
2273
  let id = typeof obj.id === 'undefined' ? -1 : parseInt(obj.id, 10);
2229
2274
  if (isNaN(id)) return reject(new InvalidEquipmentIdError('Heater Id is not valid.', obj.id, 'Heater'));
2230
2275
  let heater: Heater;
2231
2276
  if (id <= 0) {
2232
2277
  // We are adding a heater. In this case all heaters are virtual.
2233
- let heaters = sys.heaters.filter(h => h.isVirtual === false);
2278
+ let heaters = sys.heaters.filter(h => h.master === 1);
2234
2279
  id = heaters.getMaxId() + 1;
2235
2280
  }
2236
2281
  heater = sys.heaters.getItemById(id, true);
@@ -2241,7 +2286,7 @@ class TouchHeaterCommands extends HeaterCommands {
2241
2286
  }
2242
2287
  }
2243
2288
  let hstate = state.heaters.getItemById(id, true);
2244
- //hstate.isVirtual = heater.isVirtual = true;
2289
+
2245
2290
  hstate.name = heater.name;
2246
2291
  hstate.type = heater.type;
2247
2292
  heater.master = 1;
@@ -2251,7 +2296,7 @@ class TouchHeaterCommands extends HeaterCommands {
2251
2296
  });
2252
2297
  }
2253
2298
  public async deleteHeaterAsync(obj: any): Promise<Heater> {
2254
- 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);
2255
2300
  return new Promise<Heater>((resolve, reject) => {
2256
2301
  let id = parseInt(obj.id, 10);
2257
2302
  if (isNaN(id)) return reject(new InvalidEquipmentIdError('Cannot delete. Heater Id is not valid.', obj.id, 'Heater'));
@@ -2337,7 +2382,7 @@ class TouchChemControllerCommands extends ChemControllerCommands {
2337
2382
  // Now lets do all our validation to the incoming chem controller data.
2338
2383
  let name = typeof data.name !== 'undefined' ? data.name : chem.name || `IntelliChem - ${address - 143}`;
2339
2384
  let type = sys.board.valueMaps.chemControllerTypes.transformByName('intellichem');
2340
- // 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.
2341
2386
  let calciumHardness = typeof data.calciumHardness !== 'undefined' ? parseInt(data.calciumHardness, 10) : chem.calciumHardness;
2342
2387
  let cyanuricAcid = typeof data.cyanuricAcid !== 'undefined' ? parseInt(data.cyanuricAcid, 10) : chem.cyanuricAcid;
2343
2388
  let alkalinity = typeof data.alkalinity !== 'undefined' ? parseInt(data.alkalinity, 10) : chem.alkalinity;