nodejs-poolcontroller 7.6.1 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/.eslintrc.json +36 -45
  2. package/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -0
  3. package/.github/ISSUE_TEMPLATE/2-docs.md +12 -0
  4. package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -0
  5. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  6. package/CONTRIBUTING.md +74 -74
  7. package/Changelog +242 -215
  8. package/Dockerfile +17 -17
  9. package/Gruntfile.js +40 -40
  10. package/LICENSE +661 -661
  11. package/README.md +195 -191
  12. package/anslq25/MessagesMock.ts +218 -0
  13. package/anslq25/boards/MockBoardFactory.ts +50 -0
  14. package/anslq25/boards/MockEasyTouchBoard.ts +696 -0
  15. package/anslq25/boards/MockSystemBoard.ts +217 -0
  16. package/anslq25/chemistry/MockChlorinator.ts +75 -0
  17. package/anslq25/pumps/MockPump.ts +84 -0
  18. package/app.ts +10 -14
  19. package/config/Config.ts +26 -8
  20. package/config/VersionCheck.ts +8 -4
  21. package/controller/Constants.ts +59 -25
  22. package/controller/Equipment.ts +2667 -2459
  23. package/controller/Errors.ts +181 -180
  24. package/controller/Lockouts.ts +534 -436
  25. package/controller/State.ts +596 -77
  26. package/controller/boards/AquaLinkBoard.ts +1003 -0
  27. package/controller/boards/BoardFactory.ts +53 -45
  28. package/controller/boards/EasyTouchBoard.ts +3079 -2653
  29. package/controller/boards/IntelliCenterBoard.ts +3821 -4230
  30. package/controller/boards/IntelliComBoard.ts +69 -63
  31. package/controller/boards/IntelliTouchBoard.ts +384 -241
  32. package/controller/boards/NixieBoard.ts +1871 -1675
  33. package/controller/boards/SunTouchBoard.ts +393 -0
  34. package/controller/boards/SystemBoard.ts +5244 -4697
  35. package/controller/comms/Comms.ts +905 -541
  36. package/controller/comms/ScreenLogic.ts +1663 -0
  37. package/controller/comms/messages/Messages.ts +382 -54
  38. package/controller/comms/messages/config/ChlorinatorMessage.ts +8 -4
  39. package/controller/comms/messages/config/CircuitGroupMessage.ts +5 -2
  40. package/controller/comms/messages/config/CircuitMessage.ts +82 -13
  41. package/controller/comms/messages/config/ConfigMessage.ts +3 -1
  42. package/controller/comms/messages/config/CoverMessage.ts +2 -1
  43. package/controller/comms/messages/config/CustomNameMessage.ts +31 -30
  44. package/controller/comms/messages/config/EquipmentMessage.ts +5 -1
  45. package/controller/comms/messages/config/ExternalMessage.ts +33 -3
  46. package/controller/comms/messages/config/FeatureMessage.ts +2 -1
  47. package/controller/comms/messages/config/GeneralMessage.ts +2 -1
  48. package/controller/comms/messages/config/HeaterMessage.ts +145 -11
  49. package/controller/comms/messages/config/IntellichemMessage.ts +2 -1
  50. package/controller/comms/messages/config/OptionsMessage.ts +16 -27
  51. package/controller/comms/messages/config/PumpMessage.ts +62 -47
  52. package/controller/comms/messages/config/RemoteMessage.ts +80 -13
  53. package/controller/comms/messages/config/ScheduleMessage.ts +390 -347
  54. package/controller/comms/messages/config/SecurityMessage.ts +2 -1
  55. package/controller/comms/messages/config/ValveMessage.ts +44 -27
  56. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +44 -91
  57. package/controller/comms/messages/status/EquipmentStateMessage.ts +139 -30
  58. package/controller/comms/messages/status/HeaterStateMessage.ts +135 -86
  59. package/controller/comms/messages/status/IntelliChemStateMessage.ts +448 -445
  60. package/controller/comms/messages/status/IntelliValveStateMessage.ts +36 -35
  61. package/controller/comms/messages/status/PumpStateMessage.ts +92 -2
  62. package/controller/comms/messages/status/VersionMessage.ts +2 -1
  63. package/controller/nixie/Nixie.ts +173 -162
  64. package/controller/nixie/NixieEquipment.ts +104 -103
  65. package/controller/nixie/bodies/Body.ts +120 -120
  66. package/controller/nixie/bodies/Filter.ts +135 -135
  67. package/controller/nixie/chemistry/ChemController.ts +2682 -2498
  68. package/controller/nixie/chemistry/ChemDoser.ts +806 -0
  69. package/controller/nixie/chemistry/Chlorinator.ts +367 -314
  70. package/controller/nixie/circuits/Circuit.ts +402 -248
  71. package/controller/nixie/heaters/Heater.ts +815 -649
  72. package/controller/nixie/pumps/Pump.ts +934 -661
  73. package/controller/nixie/schedules/Schedule.ts +319 -257
  74. package/controller/nixie/valves/Valve.ts +170 -170
  75. package/defaultConfig.json +346 -286
  76. package/logger/DataLogger.ts +448 -448
  77. package/logger/Logger.ts +38 -9
  78. package/package.json +60 -56
  79. package/tsconfig.json +25 -25
  80. package/web/Server.ts +275 -117
  81. package/web/bindings/aqualinkD.json +560 -0
  82. package/web/bindings/homeassistant.json +437 -0
  83. package/web/bindings/influxDB.json +1066 -1021
  84. package/web/bindings/mqtt.json +721 -654
  85. package/web/bindings/mqttAlt.json +746 -684
  86. package/web/bindings/rulesManager.json +54 -54
  87. package/web/bindings/smartThings-Hubitat.json +31 -31
  88. package/web/bindings/valveRelays.json +20 -20
  89. package/web/bindings/vera.json +25 -25
  90. package/web/interfaces/baseInterface.ts +188 -136
  91. package/web/interfaces/httpInterface.ts +148 -124
  92. package/web/interfaces/influxInterface.ts +283 -245
  93. package/web/interfaces/mqttInterface.ts +695 -475
  94. package/web/interfaces/ruleInterface.ts +87 -0
  95. package/web/services/config/Config.ts +177 -49
  96. package/web/services/config/ConfigSocket.ts +2 -1
  97. package/web/services/state/State.ts +154 -3
  98. package/web/services/state/StateSocket.ts +69 -18
  99. package/web/services/utilities/Utilities.ts +232 -42
  100. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -52
  101. package/config copy.json +0 -300
  102. package/issue_template.md +0 -52
@@ -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
@@ -22,10 +23,9 @@ import * as util from 'util';
22
23
  import { logger } from '../logger/Logger';
23
24
  import { webApp } from '../web/Server';
24
25
  import { ControllerType, Timestamp, utils, Heliotrope } from './Constants';
25
- import { sys, Chemical, ChemController } from './Equipment';
26
+ import { sys, Chemical, ChemController, ChemicalTank, ChemicalPump } from './Equipment';
26
27
  import { versionCheck } from '../config/VersionCheck';
27
- import { EquipmentStateMessage } from './comms/messages/status/EquipmentStateMessage';
28
- import { DataLogger, DataLoggerEntry, IDataLoggerEntry } from '../logger/DataLogger';
28
+ import { DataLogger, DataLoggerEntry } from '../logger/DataLogger';
29
29
  import { delayMgr } from './Lockouts';
30
30
 
31
31
  export class State implements IState {
@@ -99,7 +99,7 @@ export class State implements IState {
99
99
  lines = buff.toString().split('\n');
100
100
  }
101
101
  return lines;
102
- } catch (err) { logger.error(err); }
102
+ } catch (err) { logger.error(`Error reading log file ${logFile}: ${err.message}`); }
103
103
  }
104
104
  public async logData(logFile: string, data: any) {
105
105
  try {
@@ -116,7 +116,7 @@ export class State implements IState {
116
116
  else
117
117
  lines.unshift(data.toString());
118
118
  fs.writeFileSync(logPath, lines.join('\n'));
119
- } catch (err) { logger.error(err); }
119
+ } catch (err) { logger.error(`Error reading or writing logData ${logFile}: ${err.message}`); }
120
120
  }
121
121
  public getState(section?: string): any {
122
122
  // todo: getState('time') returns an array of chars. Needs no be fixed.
@@ -140,6 +140,7 @@ export class State implements IState {
140
140
  _state.filters = this.filters.getExtended();
141
141
  _state.schedules = this.schedules.getExtended();
142
142
  _state.chemControllers = this.chemControllers.getExtended();
143
+ _state.chemDosers = this.chemDosers.getExtended();
143
144
  _state.delays = delayMgr.serialize();
144
145
  return _state;
145
146
  }
@@ -160,7 +161,7 @@ export class State implements IState {
160
161
  try {
161
162
  if (this._timerDirty) clearTimeout(this._timerDirty);
162
163
  this.persist();
163
- if (sys.controllerType === ControllerType.Virtual) {
164
+ /* if (sys.controllerType === ControllerType.Virtual) {
164
165
  for (let i = 0; i < state.temps.bodies.length; i++) {
165
166
  state.temps.bodies.getItemByIndex(i).isOn = false;
166
167
  }
@@ -170,7 +171,7 @@ export class State implements IState {
170
171
  for (let i = 0; i < state.features.length; i++) {
171
172
  state.features.getItemByIndex(i).isOn = false;
172
173
  }
173
- }
174
+ } */
174
175
  logger.info('State process shut down');
175
176
  } catch (err) { logger.error(`Error shutting down state process: ${err.message}`); }
176
177
  }
@@ -265,6 +266,9 @@ export class State implements IState {
265
266
  for (let i = 0; i < state.chemControllers.length; i++) {
266
267
  state.chemControllers.getItemByIndex(i).hasChanged = true;
267
268
  }
269
+ for (let i = 0; i < state.chemDosers.length; i++) {
270
+ state.chemDosers.getItemByIndex(i).hasChanged = true;
271
+ }
268
272
  state.emitEquipmentChanges();
269
273
  }
270
274
  public emitEquipmentChanges() {
@@ -341,6 +345,7 @@ export class State implements IState {
341
345
  public get isInitialized(): boolean { return typeof (this.data.status) !== 'undefined' && this.data.status.val !== 0; }
342
346
  public init() {
343
347
  console.log(`Init state for Pool Controller`);
348
+
344
349
  var sdata = this.loadFile(this.statePath, {});
345
350
  sdata = extend(true, { mode: { val: -1 }, temps: { units: { val: 0, name: 'F', desc: 'Fahrenheit' } } }, sdata);
346
351
  if (typeof sdata.temps !== 'undefined' && typeof sdata.temps.bodies !== 'undefined') {
@@ -358,6 +363,7 @@ export class State implements IState {
358
363
  EqStateCollection.removeNullIds(sdata.lightGroups);
359
364
  EqStateCollection.removeNullIds(sdata.remotes);
360
365
  EqStateCollection.removeNullIds(sdata.chemControllers);
366
+ EqStateCollection.removeNullIds(sdata.chemDosers);
361
367
  EqStateCollection.removeNullIds(sdata.filters);
362
368
  var self = this;
363
369
  let pnlTime = typeof sdata.time !== 'undefined' ? new Date(sdata.time) : new Date();
@@ -365,6 +371,9 @@ export class State implements IState {
365
371
  this._dt = new Timestamp(pnlTime);
366
372
  this._dt.milliseconds = 0;
367
373
  this.data = sdata;
374
+ this.equipment = new EquipmentState(this.data, 'equipment');
375
+ this.equipment.messages.clear();
376
+
368
377
  //this.onchange(state, function () { self.dirty = true; });
369
378
  this._dt.emitter.on('change', function () {
370
379
  self.data.time = self._dt.format();
@@ -378,7 +387,6 @@ export class State implements IState {
378
387
  versionCheck.checkGitRemote();
379
388
  });
380
389
  this.status = 0; // Initializing
381
- this.equipment = new EquipmentState(this.data, 'equipment');
382
390
  this.equipment.controllerType = this._controllerType;
383
391
  this.temps = new TemperatureState(this.data, 'temps');
384
392
  this.pumps = new PumpStateCollection(this.data, 'pumps');
@@ -392,6 +400,7 @@ export class State implements IState {
392
400
  this.lightGroups = new LightGroupStateCollection(this.data, 'lightGroups');
393
401
  this.virtualCircuits = new VirtualCircuitStateCollection(this.data, 'virtualCircuits');
394
402
  this.chemControllers = new ChemControllerStateCollection(this.data, 'chemControllers');
403
+ this.chemDosers = new ChemDoserStateCollection(this.data, 'chemDosers');
395
404
  this.covers = new CoverStateCollection(this.data, 'covers');
396
405
  this.filters = new FilterStateCollection(this.data, 'filters');
397
406
  this.comms = new CommsState();
@@ -433,6 +442,7 @@ export class State implements IState {
433
442
  public covers: CoverStateCollection;
434
443
  public filters: FilterStateCollection;
435
444
  public chemControllers: ChemControllerStateCollection;
445
+ public chemDosers: ChemDoserStateCollection;
436
446
  public comms: CommsState;
437
447
  public appVersion: AppVersionState;
438
448
 
@@ -505,6 +515,8 @@ export interface ICircuitState {
505
515
  isActive?: boolean;
506
516
  startDelay?: boolean;
507
517
  stopDelay?: boolean;
518
+ manualPriorityActive?: boolean;
519
+ dataName?: string;
508
520
  }
509
521
 
510
522
  interface IEqStateCreator<T> { ctor(data: any, name: string, parent?): T; }
@@ -639,6 +651,10 @@ class EqStateCollection<T> {
639
651
  }
640
652
  return rem;
641
653
  }
654
+ public removeItemByIndex(ndx: number) {
655
+ return this.data.splice(ndx, 1);
656
+ }
657
+
642
658
  public createItem(data: any): T { return new EqState(data) as unknown as T; }
643
659
  public clear() { this.data.length = 0; }
644
660
  public get length(): number { return typeof (this.data) !== 'undefined' ? this.data.length : 0; }
@@ -730,6 +746,8 @@ export class EquipmentState extends EqState {
730
746
  public set name(val: string) { this.setDataVal('name', val); }
731
747
  public get model(): string { return this.data.model; }
732
748
  public set model(val: string) { this.setDataVal('model', val); }
749
+ public get single(): boolean { return this.data.single; }
750
+ public set single(val: boolean) { this.setDataVal('single', val); }
733
751
  public get shared(): boolean { return this.data.shared; }
734
752
  public set shared(val: boolean) { this.setDataVal('shared', val); }
735
753
  public get dual(): boolean { return this.data.dual; }
@@ -805,12 +823,14 @@ export class EquipmentMessages extends EqStateCollection<EquipmentMessage> {
805
823
  rem = this.data.splice(i, 1);
806
824
  }
807
825
  }
826
+ if (typeof rem !== 'undefined') webApp.emitToClients('sysmessages', this.get(true));
808
827
  return typeof rem !== 'undefined' ? new EquipmentMessage(rem, undefined, undefined) : undefined;
809
828
  }
810
829
  // For lack of a better term category includes the equipment identifier if supplied.
811
830
  public removeItemByCategory(category: string) {
812
831
  let rem: EquipmentMessage[] = [];
813
832
  let cmr = EquipmentMessage.parseMessageCode(category);
833
+ let hasChanges = false;
814
834
  for (let i = this.data.length - 1; i >= 0; i--) {
815
835
  if (typeof (this.data[i].code) !== 'undefined') {
816
836
  let cm = EquipmentMessage.parseMessageCode(this.data.code);
@@ -819,17 +839,20 @@ export class EquipmentMessages extends EqStateCollection<EquipmentMessage> {
819
839
  if (typeof cmr.messageId === 'undefined' || cm.messageId === cmr.messageId) {
820
840
  let data = this.data.splice(i, 1);
821
841
  rem.push(new EquipmentMessage(data, undefined, undefined));
842
+ hasChanges = true;
822
843
  }
823
844
  }
824
845
  }
825
846
  }
826
847
  }
848
+ if (hasChanges) webApp.emitToClients('sysmessages', this.get(true));
827
849
  return rem;
828
850
  }
829
851
  public setMessageByCode(code: string, severity: string | number, message: string): EquipmentMessage {
830
852
  let msg = this.getItemByCode(code, true);
831
853
  msg.severity = sys.board.valueMaps.eqMessageSeverities.encode(severity, 0);
832
854
  msg.message = message;
855
+ webApp.emitToClients('sysmessages', this.get(true));
833
856
  return msg;
834
857
  }
835
858
  }
@@ -910,13 +933,18 @@ export class PumpState extends EqState {
910
933
  public get name(): string { return this.data.name; }
911
934
  public set name(val: string) { this.setDataVal('name', val); }
912
935
  public get rpm(): number { return this.data.rpm; }
913
- public set rpm(val: number) { this.setDataVal('rpm', val, this.exceedsThreshold(this.data.rpm, val)); }
936
+ public set rpm(val: number) { this.setDataVal('rpm', val); }
937
+ //public set rpm(val: number) { this.setDataVal('rpm', val, this.exceedsThreshold(this.data.rpm, val)); }
914
938
  public get relay(): number { return this.data.relay; }
915
939
  public set relay(val: number) { this.setDataVal('relay', val); }
940
+ public get program(): number { return this.data.program; }
941
+ public set program(val: number) { this.setDataVal('program', val); }
916
942
  public get watts(): number { return this.data.watts; }
917
- public set watts(val: number) { this.setDataVal('watts', val, this.exceedsThreshold(this.data.watts, val)); }
943
+ public set watts(val: number) { this.setDataVal('watts', val); }
944
+ //public set watts(val: number) { this.setDataVal('watts', val, this.exceedsThreshold(this.data.watts, val)); }
918
945
  public get flow(): number { return this.data.flow; }
919
- public set flow(val: number) { this.setDataVal('flow', val, this.exceedsThreshold(this.data.flow, val)); }
946
+ public set flow(val: number) { this.setDataVal('flow', val); }
947
+ //public set flow(val: number) { this.setDataVal('flow', val, this.exceedsThreshold(this.data.flow, val)); }
920
948
  public get mode(): number { return this.data.mode; }
921
949
  public set mode(val: number) { this.setDataVal('mode', val); }
922
950
  public get driveState(): number { return this.data.driveState; }
@@ -938,7 +966,7 @@ export class PumpState extends EqState {
938
966
  this.hasChanged = true;
939
967
  }
940
968
  }
941
- public get virtualControllerStatus(): number {
969
+ /* public get virtualControllerStatus(): number {
942
970
  return typeof (this.data.virtualControllerStatus) !== 'undefined' ? this.data.virtualControllerStatus.val : -1;
943
971
  }
944
972
  public set virtualControllerStatus(val: number) {
@@ -946,7 +974,7 @@ export class PumpState extends EqState {
946
974
  this.data.virtualControllerStatus = sys.board.valueMaps.virtualControllerStatus.transform(val);
947
975
  this.hasChanged = true;
948
976
  }
949
- }
977
+ } */
950
978
  public get targetSpeed(): number { return this.data.targetSpeed; } // used for virtual controller
951
979
  public set targetSpeed(val: number) { this.setDataVal('targetSpeed', val); }
952
980
  public get type() { return typeof (this.data.type) !== 'undefined' ? this.data.type.val : -1; }
@@ -985,23 +1013,43 @@ export class PumpState extends EqState {
985
1013
  pump.speedStepSize = cpump.speedStepSize;
986
1014
  pump.flowStepSize = cpump.flowStepSize;
987
1015
  pump.circuits = [];
988
- for (let i = 0; i < 8; i++) {
1016
+ for (let i = 0; i < cpump.circuits.length; i++) {
989
1017
  let c = cpump.circuits.getItemByIndex(i).get(true);
990
1018
  c.circuit = state.circuits.getInterfaceById(c.circuit).get(true);
991
- if (typeof c.circuit.id === 'undefined' || typeof c.circuit.name === 'undefined') {
992
- // return "blank" circuit if none defined
993
- c.circuit.id = 0;
994
- c.circuit.name = 'Not Used';
995
- if (sys.board.valueMaps.pumpTypes.getName(cpump.type) === 'vf') {
996
- c.units = sys.board.valueMaps.pumpUnits.getValue('gpm');
997
- c.circuit.flow = 0;
998
- }
999
- else {
1000
- c.units = sys.board.valueMaps.pumpUnits.getValue('rpm');
1001
- c.circuit.speed = 0;
1002
- }
1019
+ switch (pump.type.name) {
1020
+ case 'vf':
1021
+ c.units = sys.board.valueMaps.pumpUnits.transformByName('gpm');
1022
+ break;
1023
+ case 'hwvs':
1024
+ case 'vssvrs':
1025
+ case 'vs':
1026
+ c.units = sys.board.valueMaps.pumpUnits.transformByName('rpm');
1027
+ break;
1028
+ case 'ss':
1029
+ case 'ds':
1030
+ case 'sf':
1031
+ case 'hwrly':
1032
+ c.units = 'undefined';
1033
+ break;
1034
+ default:
1035
+ c.units = sys.board.valueMaps.pumpUnits.transform(c.units || 0);
1036
+ break;
1003
1037
  }
1004
- c.units = sys.board.valueMaps.pumpUnits.transform(c.units);
1038
+ // RKS: 04-08-22 - This is just wrong. If the user did not define circuits then they should not be sent down and it creates a whole host of issues.
1039
+ //if (typeof c.circuit.id === 'undefined' || typeof c.circuit.name === 'undefined') {
1040
+ // // return "blank" circuit if none defined
1041
+ // c.circuit.id = 0;
1042
+ // c.circuit.name = 'Not Used';
1043
+ // if (sys.board.valueMaps.pumpTypes.getName(cpump.type) === 'vf') {
1044
+ // c.units = sys.board.valueMaps.pumpUnits.getValue('gpm');
1045
+ // c.circuit.flow = 0;
1046
+ // }
1047
+ // else {
1048
+ // c.units = sys.board.valueMaps.pumpUnits.getValue('rpm');
1049
+ // c.circuit.speed = 0;
1050
+ // }
1051
+ //}
1052
+ //c.units = sys.board.valueMaps.pumpUnits.transform(c.units);
1005
1053
  pump.circuits.push(c);
1006
1054
  }
1007
1055
  pump.circuits.sort((a, b) => { return a.id > b.id ? 1 : -1; });
@@ -1046,6 +1094,8 @@ export class ScheduleState extends EqState {
1046
1094
  public set endTime(val: number) { this.setDataVal('endTime', val); }
1047
1095
  public get circuit(): number { return this.data.circuit; }
1048
1096
  public set circuit(val: number) { this.setDataVal('circuit', val); }
1097
+ public get disabled(): boolean { return this.data.disabled; }
1098
+ public set disabled(val: boolean) { this.setDataVal('disabled', val); }
1049
1099
  public get scheduleType(): number { return typeof (this.data.scheduleType) !== 'undefined' ? this.data.scheduleType.val : undefined; }
1050
1100
  public set scheduleType(val: number) {
1051
1101
  if (this.scheduleType !== val) {
@@ -1097,6 +1147,8 @@ export class ScheduleState extends EqState {
1097
1147
  public set coolSetpoint(val: number) { this.setDataVal('coolSetpoint', val); }
1098
1148
  public get isOn(): boolean { return this.data.isOn; }
1099
1149
  public set isOn(val: boolean) { this.setDataVal('isOn', val); }
1150
+ public get manualPriorityActive(): boolean { return this.data.manualPriorityActive; }
1151
+ public set manualPriorityActive(val: boolean) { this.setDataVal('manualPriorityActive', val); }
1100
1152
  public getExtended() {
1101
1153
  let sched = this.get(true); // Always operate on a copy.
1102
1154
  //if (typeof this.circuit !== 'undefined')
@@ -1124,6 +1176,7 @@ export interface ICircuitGroupState {
1124
1176
  dataName: string;
1125
1177
  lightingTheme?: number;
1126
1178
  showInFeatures?: boolean;
1179
+ manualPriorityActive?: boolean;
1127
1180
  get(bCopy?: boolean);
1128
1181
  emitEquipmentChange();
1129
1182
  }
@@ -1180,10 +1233,15 @@ export class CircuitGroupState extends EqState implements ICircuitGroupState, IC
1180
1233
  public set isActive(val: boolean) { this.setDataVal('isActive', val); }
1181
1234
  public get showInFeatures(): boolean { return typeof this.data.showInFeatures === 'undefined' ? true : this.data.showInFeatures; }
1182
1235
  public set showInFeatures(val: boolean) { this.setDataVal('showInFeatures', val); }
1236
+ public get manualPriorityActive(): boolean { return this.data.manualPriorityActive; }
1237
+ public set manualPriorityActive(val: boolean) { this.setDataVal('manualPriorityActive', val); }
1183
1238
  public getExtended() {
1184
1239
  let sgrp = this.get(true); // Always operate on a copy.
1185
1240
  if (typeof sgrp.showInFeatures === 'undefined') sgrp.showInFeatures = true;
1241
+
1186
1242
  let cgrp = sys.circuitGroups.getItemById(this.id);
1243
+ sgrp.showInFeatures = this.showInFeatures = cgrp.showInFeatures;
1244
+ sgrp.isActive = this.isActive = cgrp.isActive;
1187
1245
  sgrp.circuits = [];
1188
1246
  for (let i = 0; i < cgrp.circuits.length; i++) {
1189
1247
  let cgc = cgrp.circuits.getItemByIndex(i).get(true);
@@ -1264,6 +1322,8 @@ export class LightGroupState extends EqState implements ICircuitGroupState, ICir
1264
1322
  public set isOn(val: boolean) { this.setDataVal('isOn', val); }
1265
1323
  public get isActive(): boolean { return this.data.isActive; }
1266
1324
  public set isActive(val: boolean) { this.setDataVal('isActive', val); }
1325
+ public get manualPriorityActive(): boolean { return this.data.manualPriorityActive; }
1326
+ public set manualPriorityActive(val: boolean) { this.setDataVal('manualPriorityActive', val); }
1267
1327
  public async setThemeAsync(val: number) { return sys.board.circuits.setLightThemeAsync; }
1268
1328
  public getExtended() {
1269
1329
  let sgrp = this.get(true); // Always operate on a copy.
@@ -1334,6 +1394,8 @@ export class BodyTempState extends EqState {
1334
1394
  if (typeof this.heaterCooldownDelay === 'undefined') this.data.heaterCooldownDelay = false;
1335
1395
  if (typeof this.data.startDelay === 'undefined') this.data.startDelay = false;
1336
1396
  if (typeof this.data.stopDelay === 'undefined') this.data.stopDelay = false;
1397
+ if (typeof this.data.showInDashboard === 'undefined') this.data.showInDashboard = true;
1398
+ if (typeof this.data.heatMode === 'undefined') this.data.heatMode = sys.board.valueMaps.heatModes.transform(0);
1337
1399
  }
1338
1400
  public get id(): number { return this.data.id; }
1339
1401
  public set id(val: number) { this.setDataVal('id', val); }
@@ -1376,6 +1438,8 @@ export class BodyTempState extends EqState {
1376
1438
  public set startDelay(val: boolean) { this.setDataVal('startDelay', val); }
1377
1439
  public get stopDelay(): boolean { return this.data.stopDelay; }
1378
1440
  public set stopDelay(val: boolean) { this.setDataVal('stopDelay', val); }
1441
+ public get showInDashboard(): boolean { return this.data.showInDashboard; }
1442
+ public set showInDashboard(val: boolean) { this.setDataVal('showInDashboard', val); }
1379
1443
 
1380
1444
  public get isCovered(): boolean { return this.data.isCovered; }
1381
1445
  public set isCovered(val: boolean) { this.setDataVal('isCovered', val); }
@@ -1544,7 +1608,6 @@ export class FeatureStateCollection extends EqStateCollection<FeatureState> {
1544
1608
  }
1545
1609
  }
1546
1610
  }
1547
-
1548
1611
  export class FeatureState extends EqState implements ICircuitState {
1549
1612
  public dataName: string = 'feature';
1550
1613
  public initData() {
@@ -1578,6 +1641,8 @@ export class FeatureState extends EqState implements ICircuitState {
1578
1641
  public set freezeProtect(val: boolean) { this.setDataVal('freezeProtect', val); }
1579
1642
  public get isActive(): boolean { return this.data.isActive; }
1580
1643
  public set isActive(val: boolean) { this.setDataVal('isActive', val); }
1644
+ public get manualPriorityActive(): boolean { return this.data.manualPriorityActive; }
1645
+ public set manualPriorityActive(val: boolean) { this.setDataVal('manualPriorityActive', val); }
1581
1646
  }
1582
1647
  export class VirtualCircuitState extends EqState implements ICircuitState {
1583
1648
  public dataName: string = 'virtualCircuit';
@@ -1724,6 +1789,8 @@ export class CircuitState extends EqState implements ICircuitState {
1724
1789
  public set lockoutOn(val: boolean) { this.setDataVal('lockoutOn', val); }
1725
1790
  public get lockoutOff(): boolean { return this.data.lockoutOff; }
1726
1791
  public set lockoutOff(val: boolean) { this.setDataVal('lockoutOff', val); }
1792
+ public get manualPriorityActive(): boolean { return this.data.manualPriorityActive; }
1793
+ public set manualPriorityActive(val: boolean) { this.setDataVal('manualPriorityActive', val); }
1727
1794
  }
1728
1795
  export class ValveStateCollection extends EqStateCollection<ValveState> {
1729
1796
  public createItem(data: any): ValveState { return new ValveState(data); }
@@ -1800,9 +1867,16 @@ export class CoverState extends EqState {
1800
1867
  public set isClosed(val: boolean) { this.setDataVal('isClosed', val); }
1801
1868
  }
1802
1869
  export class ChlorinatorStateCollection extends EqStateCollection<ChlorinatorState> {
1870
+ public superChlor: { id: number, lastDispatch: number, reference: number }[] = [];
1871
+ public getSuperChlor(id: number): { id: number, lastDispatch: number, reference: number } {
1872
+ let sc = this.superChlor.find(elem => id === elem.id);
1873
+ if (typeof sc === 'undefined') {
1874
+ sc = { id: id, lastDispatch: 0, reference: 0 };
1875
+ this.superChlor.push(sc);
1876
+ }
1877
+ return sc;
1878
+ }
1803
1879
  public createItem(data: any): ChlorinatorState { return new ChlorinatorState(data); }
1804
- public superChlorReference: number = 0;
1805
- public lastDispatchSuperChlor: number = 0;
1806
1880
  public cleanupState() {
1807
1881
  for (let i = this.data.length - 1; i >= 0; i--) {
1808
1882
  if (isNaN(this.data[i].id)) this.data.splice(i, 1);
@@ -1815,10 +1889,10 @@ export class ChlorinatorStateCollection extends EqStateCollection<ChlorinatorSta
1815
1889
  let c = cfg[i];
1816
1890
  let s = this.getItemById(cfg[i].id, true);
1817
1891
  s.type = c.type;
1892
+ s.model = c.model;
1818
1893
  s.name = c.name;
1819
1894
  s.isActive = c.isActive;
1820
1895
  }
1821
-
1822
1896
  }
1823
1897
  }
1824
1898
  export class ChlorinatorState extends EqState {
@@ -1868,6 +1942,13 @@ export class ChlorinatorState extends EqState {
1868
1942
  this.hasChanged = true;
1869
1943
  }
1870
1944
  }
1945
+ public get model(): number { return typeof (this.data.model) !== 'undefined' ? this.data.model.val : 0; }
1946
+ public set model(val: number) {
1947
+ if (this.model !== val) {
1948
+ this.data.model = sys.board.valueMaps.chlorinatorModel.transform(val);
1949
+ this.hasChanged = true;
1950
+ }
1951
+ }
1871
1952
  public get body(): number { return typeof (this.data.body) !== 'undefined' ? this.data.body.val : -1; }
1872
1953
  public set body(val: number) {
1873
1954
  if (this.body !== val) {
@@ -1881,32 +1962,15 @@ export class ChlorinatorState extends EqState {
1881
1962
  public set spaSetpoint(val: number) { this.setDataVal('spaSetpoint', val); }
1882
1963
  public get superChlorHours(): number { return this.data.superChlorHours; }
1883
1964
  public set superChlorHours(val: number) { this.setDataVal('superChlorHours', val); }
1965
+ public get saltTarget(): number { return this.data.saltTarget; }
1966
+ public set saltTarget(val: number) { this.setDataVal('saltTarget', val); }
1884
1967
  public get saltRequired(): number { return this.data.saltRequired; }
1885
1968
  public get saltLevel(): number { return this.data.saltLevel; }
1886
1969
  public set saltLevel(val: number) {
1887
- this.setDataVal('saltLevel', val);
1888
- //this.data.saltLevel = val;
1889
- // Calculate the salt required.
1890
- let capacity = 0;
1891
- for (let i = 0; i < sys.bodies.length; i++) {
1892
- let body = sys.bodies.getItemById(i + 1);
1893
- if (this.body === 32)
1894
- capacity = Math.max(body.capacity, capacity);
1895
- else if (this.body === 0 && body.id === 1)
1896
- capacity = Math.max(body.capacity, capacity);
1897
- else if (this.body === 1 && body.id === 2)
1898
- capacity = Math.max(body.capacity, capacity);
1899
- }
1900
- if (capacity > 0 && this.saltLevel < 3100) {
1901
- // Salt requirements calculation.
1902
- // Target - SaltLevel = NeededSalt = 3400 - 2900 = 500ppm
1903
- // So to raise 120ppm you need to add 1lb per 1000 gal.
1904
- // (NeededSalt/120ppm) * (MaxBody/1000) = (500/120) * (33000/1000) = 137.5lbs of salt required to hit target.
1905
- let dec = Math.pow(10, 2);
1906
- this.data.saltRequired = Math.round((((3400 - this.saltLevel) / 120) * (capacity / 1000)) * dec) / dec;
1970
+ if (this.saltLevel !== val) {
1971
+ this.setDataVal('saltLevel', val);
1972
+ this.calcSaltRequired();
1907
1973
  }
1908
- else
1909
- this.data.saltRequired = 0;
1910
1974
  }
1911
1975
  public get superChlor(): boolean { return this.data.superChlor; }
1912
1976
  public set superChlor(val: boolean) {
@@ -1915,41 +1979,92 @@ export class ChlorinatorState extends EqState {
1915
1979
  }
1916
1980
  public get superChlorRemaining(): number { return this.data.superChlorRemaining || 0; }
1917
1981
  public set superChlorRemaining(val: number) {
1982
+ if (val === this.data.superChlorRemaining) return;
1918
1983
  let remaining: number;
1919
- if (sys.controllerType === 'nixie') {
1920
- remaining = Math.max(0, val);
1984
+ let sc = state.chlorinators.getSuperChlor(this.id);
1985
+ let chlor = sys.chlorinators.getItemById(this.id);
1986
+ if (chlor.master === 1) {
1987
+ // If we are 10 seconds different then we need to send it off and save the data.
1988
+ if (Math.floor(val / 10) !== Math.floor(this.superChlorRemaining / 10)) {
1989
+ this.hasChanged = true;
1990
+ remaining = val;
1991
+ sc.reference = Math.floor(new Date().getTime() / 1000);
1992
+ this.setDataVal('superChlorRemaining', remaining);
1993
+ }
1994
+ else if (val <= 0)
1995
+ remaining = 0;
1996
+ else
1997
+ remaining = this.superChlorRemaining;
1998
+ }
1999
+ else if (chlor.master === 2) {
2000
+ // If we are 10 seconds different then we need to send it off and save the data.
2001
+ if (Math.floor(val / 10) !== Math.floor(this.superChlorRemaining / 10)) {
2002
+ this.hasChanged = true;
2003
+ remaining = val;
2004
+ sc.reference = Math.floor(new Date().getTime() / 1000);
2005
+ this.setDataVal('superChlorRemaining', remaining);
2006
+ }
1921
2007
  }
1922
2008
  else if (sys.controllerType === 'intellicenter') {
1923
2009
  // Trim the seconds off both of these as we will be keeping the seconds separately since this
1924
2010
  // only reports in minutes. That way our seconds become self healing.
1925
2011
  if (Math.ceil(this.superChlorRemaining / 60) * 60 !== val) {
1926
- state.chlorinators.superChlorReference = Math.floor(new Date().getTime() / 1000); // Get the epoc and strip the milliseconds.
2012
+ sc.reference = Math.floor(new Date().getTime() / 1000); // Get the epoc and strip the milliseconds.
1927
2013
  this.hasChanged = true;
1928
2014
  }
1929
- let secs = Math.floor(new Date().getTime() / 1000) - state.chlorinators.superChlorReference;
2015
+ let secs = Math.floor(new Date().getTime() / 1000) - sc.reference;
1930
2016
  remaining = Math.max(0, val - Math.min(secs, 60));
2017
+ if (sc.lastDispatch - 5 > remaining) this.hasChanged = true;
2018
+ this.data.superChlorRemaining = remaining;
1931
2019
  }
1932
2020
  else {
1933
2021
  // *Touch only reports superchlor hours remaining.
1934
2022
  // If we have the same hours as existing, retain the mins + secs
1935
2023
  if (Math.ceil(this.superChlorRemaining / 3600) * 60 !== val / 60) {
1936
- state.chlorinators.superChlorReference = Math.floor(new Date().getTime() / 1000); // Get the epoc and strip the milliseconds.
2024
+ sc.reference = Math.floor(new Date().getTime() / 1000); // Get the epoc and strip the milliseconds.
1937
2025
  this.hasChanged = true;
1938
2026
  }
1939
- let secs = Math.floor(new Date().getTime() / 1000) - state.chlorinators.superChlorReference;
2027
+ let secs = Math.floor(new Date().getTime() / 1000) - sc.reference;
1940
2028
  remaining = Math.max(0, val - Math.min(secs, 3600));
2029
+ if (sc.lastDispatch - 5 > remaining) this.hasChanged = true;
2030
+ this.data.superChlorRemaining = remaining;
1941
2031
  }
1942
- if (state.chlorinators.lastDispatchSuperChlor - 5 > remaining) this.hasChanged = true;
1943
- if (this.hasChanged) state.chlorinators.lastDispatchSuperChlor = remaining;
1944
- this.data.superChlorRemaining = remaining;
1945
- if (remaining > 0)
1946
- this.setDataVal('superChlor', true);
1947
- else
1948
- this.setDataVal('superChlor', false);
2032
+ if (this.hasChanged) sc.lastDispatch = remaining;
2033
+ this.setDataVal('superChlor', remaining > 0);
2034
+ chlor.superChlor = remaining > 0;
1949
2035
  }
2036
+ public calcSaltRequired(saltTarget?: number) : number {
2037
+ if (typeof saltTarget === 'undefined') saltTarget = sys.chlorinators.getItemById(this.id, false).saltTarget || 0;
2038
+ let saltRequired = 0;
2039
+ //this.data.saltLevel = val;
2040
+ // Calculate the salt required.
2041
+ let capacity = 0;
2042
+ for (let i = 0; i < sys.bodies.length; i++) {
2043
+ let body = sys.bodies.getItemById(i + 1);
2044
+ if (this.body === 32)
2045
+ capacity = Math.max(body.capacity, capacity);
2046
+ else if (this.body === 0 && body.id === 1)
2047
+ capacity = Math.max(body.capacity, capacity);
2048
+ else if (this.body === 1 && body.id === 2)
2049
+ capacity = Math.max(body.capacity, capacity);
2050
+ }
2051
+ if (capacity > 0 && this.saltLevel < saltTarget) {
2052
+ // Salt requirements calculation.
2053
+ // Target - SaltLevel = NeededSalt = 3400 - 2900 = 500ppm
2054
+ // So to raise 120ppm you need to add 1lb per 1000 gal.
2055
+ // (NeededSalt/120ppm) * (MaxBody/1000) = (500/120) * (33000/1000) = 137.5lbs of salt required to hit target.
2056
+ let dec = Math.pow(10, 2);
2057
+ saltRequired = Math.round((((saltTarget - this.saltLevel) / 120) * (capacity / 1000)) * dec) / dec;
2058
+ if (this.saltRequired < 0) saltRequired = 0;
2059
+ }
2060
+ this.setDataVal('saltRequired', saltRequired);
2061
+ return saltRequired;
2062
+ }
2063
+ public getEmitData() { return this.getExtended(); }
1950
2064
  public getExtended(): any {
1951
2065
  let schlor = this.get(true);
1952
2066
  let chlor = sys.chlorinators.getItemById(this.id, false);
2067
+ schlor.saltTarget = chlor.saltTarget;
1953
2068
  schlor.lockSetpoints = chlor.disabled || chlor.isDosing;
1954
2069
  return schlor;
1955
2070
  }
@@ -1976,8 +2091,278 @@ export class ChemControllerStateCollection extends EqStateCollection<ChemControl
1976
2091
  }
1977
2092
  }
1978
2093
  }
2094
+ export class ChemDoserStateCollection extends EqStateCollection<ChemDoserState> {
2095
+ public createItem(data: any): ChemDoserState { return new ChemDoserState(data); }
2096
+ public cleanupState() {
2097
+ for (let i = this.data.length - 1; i >= 0; i--) {
2098
+ if (isNaN(this.data[i].id)) this.data.splice(i, 1);
2099
+ else {
2100
+ if (typeof sys.chemControllers.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
2101
+ }
2102
+ }
2103
+ // Make sure we have at least the items that exist in the config.
2104
+ let cfg = sys.chemControllers.toArray();
2105
+ for (let i = 0; i < cfg.length; i++) {
2106
+ let c = cfg[i];
2107
+ let s = this.getItemById(cfg[i].id, true);
2108
+ s.body = c.body;
2109
+ s.name = c.name;
2110
+ s.type = c.type;
2111
+ s.isActive = c.isActive;
2112
+ }
2113
+ }
2114
+ }
2115
+ export interface IChemControllerState {
2116
+ flowDetected: boolean;
2117
+ isBodyOn: boolean;
2118
+ emitEquipmentChange: () => void;
2119
+ }
2120
+ export interface IChemicalState {
2121
+ enabled: boolean;
2122
+ currentDose: ChemicalDoseState;
2123
+ manualDosing: boolean;
2124
+ manualMixing: boolean;
2125
+ dosingTimeRemaining: number;
2126
+ dosingVolumeRemaining: number;
2127
+ volumeDosed: number;
2128
+ timeDosed: number;
2129
+ endDose: (dtEnd?: Date, status?: string, volumeDosed?: number, timeDosed?: number) => ChemicalDoseState;
2130
+ appendDose: (volumeDosed: number, timeDosed: number) => ChemicalDoseState;
2131
+ tank: ChemicalTankState;
2132
+ pump: ChemicalPumpState;
2133
+ doseHistory: ChemicalDoseState[];
2134
+ freezeProtect: boolean;
2135
+ mixTimeRemaining: number;
2136
+ delayTimeRemaining: number;
2137
+ flowDelay: boolean;
2138
+ dailyVolumeDosed: number;
2139
+ chemType: string;
2140
+ dosingStatus: number;
2141
+ chemController: IChemControllerState;
2142
+ chlor?: ChemicalChlorState;
2143
+
2144
+ }
2145
+ export class ChemDoserState extends EqState implements IChemicalState, IChemControllerState {
2146
+ public initData() {
2147
+ if (typeof this.data.flowDetected === 'undefined') this.data.flowDetected = false;
2148
+ if (typeof this.data.flowSensor === 'undefined') this.data.flowSensor = {};
2149
+ if (typeof this.data.alarms === 'undefined') { let a = this.alarms; }
2150
+ if (typeof this.data.warnings === 'undefined') { let w = this.warnings; }
2151
+ if (typeof this.data.tank === 'undefined') this.data.tank = { capacity: 0, level: 0, units: 0 };
2152
+ if (typeof this.data.pump === 'undefined') this.data.pump = { isDosing: false };
2153
+ if (typeof this.data.dosingTimeRemaining === 'undefined') this.data.dosingTimeRemaining = 0;
2154
+ if (typeof this.data.delayTimeRemaining === 'undefined') this.data.delayTimeRemaining = 0;
2155
+ if (typeof this.data.dosingVolumeRemaining === 'undefined') this.data.dosingVolumeRemaining = 0;
2156
+ if (typeof this.data.doseVolume === 'undefined') this.data.doseVolume = 0;
2157
+ if (typeof this.data.doseTime === 'undefined') this.data.doseTime = 0;
2158
+ if (typeof this.data.lockout === 'undefined') this.data.lockout = false;
2159
+ if (typeof this.data.level === 'undefined') this.data.level = 0;
2160
+ if (typeof this.data.mixTimeRemaining === 'undefined') this.data.mixTimeRemaining = 0;
2161
+ if (typeof this.data.dailyLimitReached === 'undefined') this.data.dailyLimitReached = false;
2162
+ if (typeof this.data.manualDosing === 'undefined') this.data.manualDosing = false;
2163
+ if (typeof this.data.manualMixing === 'undefined') this.data.manualMixing = false;
2164
+ if (typeof this.data.flowDelay === 'undefined') this.data.flowDelay = false;
2165
+ if (typeof this.data.dosingStatus === 'undefined') this.dosingStatus = 2;
2166
+ if (typeof this.data.enabled === 'undefined') this.data.enabled = true;
2167
+ if (typeof this.data.freezeProtect === 'undefined') this.data.freezeProtect = false;
2168
+ if (typeof this.data.setpoint === 'undefined') this.data.setpoint = 100;
2169
+ if (typeof this.data.suspendDosing === 'undefined') this.data.suspendDosing = false;
2170
+ }
2171
+ public dataName: string = 'chemDoser';
2172
+ public get id(): number { return this.data.id; }
2173
+ public set id(val: number) { this.setDataVal('id', val); }
2174
+ public get isActive(): boolean { return this.data.isActive; }
2175
+ public set isActive(val: boolean) { this.setDataVal('isActive', val); }
2176
+ public get name(): string { return this.data.name; }
2177
+ public set name(val: string) { this.setDataVal('name', val); }
2178
+ public get setpoint(): number { return this.data.setpoint; }
2179
+ public set setpoint(val: number) { this.setDataVal('setpoint', val); }
1979
2180
 
1980
- export class ChemControllerState extends EqState {
2181
+ public get lastComm(): number { return this.data.lastComm || 0; }
2182
+ public set lastComm(val: number) { this.setDataVal('lastComm', val, false); }
2183
+ public get isBodyOn(): boolean { return this.data.isBodyOn; }
2184
+ public set isBodyOn(val: boolean) { this.data.isBodyOn = val; }
2185
+ public get flowDetected(): boolean { return this.data.flowDetected; }
2186
+ public set flowDetected(val: boolean) { this.data.flowDetected = val; }
2187
+ public get status(): number { return typeof (this.data.status) !== 'undefined' ? this.data.status.val : -1; }
2188
+ public set status(val: number) {
2189
+ if (this.status !== val) {
2190
+ this.data.status = sys.board.valueMaps.chemDoserStatus.transform(val);
2191
+ this.hasChanged = true;
2192
+ }
2193
+ }
2194
+ public get body(): number { return typeof (this.data.body) !== 'undefined' ? this.data.body.val : -1; }
2195
+ public set body(val: number) {
2196
+ if (this.body !== val) {
2197
+ this.data.body = sys.board.valueMaps.bodies.transform(val);
2198
+ this.hasChanged = true;
2199
+ }
2200
+ }
2201
+ public get type(): number { return typeof (this.data.type) !== 'undefined' ? this.data.type.val : 0; }
2202
+ public set type(val: number) {
2203
+ if (this.type !== val) {
2204
+ this.data.type = sys.board.valueMaps.chemDoserTypes.transform(val);
2205
+ this.data.chemType = this.data.type.desc;
2206
+ this.hasChanged = true;
2207
+ }
2208
+ }
2209
+ public get enabled(): boolean { return this.data.enabled; }
2210
+ public set enabled(val: boolean) { this.data.enabled = val; }
2211
+ public get freezeProtect(): boolean { return this.data.freezeProtect; }
2212
+ public set freezeProtect(val: boolean) { this.data.freezeProtect = val; }
2213
+ public get suspendDosing(): boolean { return this.data.suspendDosing; }
2214
+ public set suspendDosing(val: boolean) { this.data.suspendDosing = val; }
2215
+ public get chemType(): string { return this.data.chemType; }
2216
+ public get delayTimeRemaining(): number { return this.data.delayTimeRemaining; }
2217
+ public set delayTimeRemaining(val: number) { this.setDataVal('delayTimeRemaining', val); }
2218
+ public get doseTime(): number { return this.data.doseTime; }
2219
+ public set doseTime(val: number) { this.setDataVal('doseTime', val); }
2220
+ public get doseVolume(): number { return this.data.doseVolume; }
2221
+ public set doseVolume(val: number) { this.setDataVal('doseVolume', val); }
2222
+ public get dosingTimeRemaining(): number { return this.data.dosingTimeRemaining; }
2223
+ public set dosingTimeRemaining(val: number) { this.setDataVal('dosingTimeRemaining', val); }
2224
+ public get dosingVolumeRemaining(): number { return this.data.dosingVolumeRemaining; }
2225
+ public set dosingVolumeRemaining(val: number) { this.setDataVal('dosingVolumeRemaining', val); }
2226
+ public get volumeDosed(): number { return this.data.volumeDosed; }
2227
+ public set volumeDosed(val: number) { this.setDataVal('volumeDosed', val); }
2228
+ public get timeDosed(): number { return this.data.timeDosed; }
2229
+ public set timeDosed(val: number) { this.setDataVal('timeDosed', val); }
2230
+ public get dailyVolumeDosed(): number { return this.data.dailyVolumeDosed; }
2231
+ public set dailyVolumeDosed(val: number) { this.setDataVal('dailyVolumeDosed', val); }
2232
+ public get mixTimeRemaining(): number { return this.data.mixTimeRemaining; }
2233
+ public set mixTimeRemaining(val: number) { this.setDataVal('mixTimeRemaining', val); }
2234
+ public get dosingStatus(): number { return typeof (this.data.dosingStatus) !== 'undefined' ? this.data.dosingStatus.val : undefined; }
2235
+ public set dosingStatus(val: number) {
2236
+ if (this.dosingStatus !== val) {
2237
+ logger.debug(`${this.chemType} dosing status changed from ${sys.board.valueMaps.chemControllerDosingStatus.getName(this.dosingStatus)} (${this.dosingStatus}) to ${sys.board.valueMaps.chemControllerDosingStatus.getName(val)}(${val})`);
2238
+ this.data.dosingStatus = sys.board.valueMaps.chemControllerDosingStatus.transform(val);
2239
+ this.hasChanged = true;
2240
+ }
2241
+ }
2242
+ public get lockout(): boolean { return utils.makeBool(this.data.lockout); }
2243
+ public set lockout(val: boolean) { this.setDataVal('lockout', val); }
2244
+ public get flowDelay(): boolean { return utils.makeBool(this.data.flowDelay); }
2245
+ public set flowDelay(val: boolean) { this.data.flowDelay = val; }
2246
+ public get manualDosing(): boolean { return utils.makeBool(this.data.manualDosing); }
2247
+ public set manualDosing(val: boolean) { this.setDataVal('manualDosing', val); }
2248
+ public get manualMixing(): boolean { return utils.makeBool(this.data.manualMixing); }
2249
+ public set manualMixing(val: boolean) { this.setDataVal('manualMixing', val); }
2250
+ public get dailyLimitReached(): boolean { return utils.makeBool(this.data.dailyLimitReached); }
2251
+ public set dailyLimitReached(val: boolean) { this.data.dailyLimitReached = val; }
2252
+ public get tank(): ChemicalTankState { return new ChemicalTankState(this.data, 'tank', this); }
2253
+ public get pump(): ChemicalPumpState { return new ChemicalPumpState(this.data, 'pump', this); }
2254
+ public get flowSensor(): ChemicalFlowSensorState { return new ChemicalFlowSensorState(this.data, 'flowSensor', this); }
2255
+ public get warnings(): ChemDoserStateWarnings { return new ChemDoserStateWarnings(this.data, 'warnings', this); }
2256
+ public get alarms(): ChemDoserStateAlarms { return new ChemDoserStateAlarms(this.data, 'alarms', this); }
2257
+ public calcDoseHistory(): number {
2258
+ // The dose history records will already exist when the state is loaded. There are enough records to cover 24 hours in this
2259
+ // instance. We need to prune off any records that are > 24 hours when we calculate.
2260
+ let dailyVolumeDosed = 0;
2261
+ let dt = new Date();
2262
+ let dtMax = dt.setTime(dt.getTime() - (24 * 60 * 60 * 1000));
2263
+ for (let i = this.doseHistory.length - 1; i >= 0; i--) {
2264
+ let dh = this.doseHistory[i];
2265
+ if (typeof dh.end !== 'undefined'
2266
+ && typeof dh.end.getTime == 'function'
2267
+ && dh.end.getTime() > dtMax
2268
+ && dh.volumeDosed > 0) dailyVolumeDosed += dh.volumeDosed;
2269
+ else {
2270
+ logger.info(`Removing dose history ${dh.chem} ${dh.end}`);
2271
+ this.doseHistory.splice(i, 1);
2272
+ }
2273
+ }
2274
+ return dailyVolumeDosed + (typeof this.currentDose !== 'undefined' ? this.currentDose.volumeDosed : 0);
2275
+ }
2276
+ public startDose(dtStart: Date = new Date(), method: string = 'auto', volume: number = 0, volumeDosed: number = 0, time: number = 0, timeDosed: number = 0): ChemicalDoseState {
2277
+ this.currentDose = new ChemicalDoseState();
2278
+ this.currentDose.type = this.chemType;
2279
+ this.currentDose.id = this.id;
2280
+ this.currentDose.start = dtStart;
2281
+ this.currentDose.method = method;
2282
+ this.currentDose.volumeDosed = volumeDosed;
2283
+ this.doseVolume = this.currentDose.volume = volume;
2284
+ this.currentDose.chem = this.chemType;
2285
+ this.currentDose.time = time;
2286
+ this.currentDose._timeDosed = timeDosed;
2287
+ this.volumeDosed = this.currentDose.volumeDosed;
2288
+ this.timeDosed = Math.round(timeDosed / 1000);
2289
+ this.dosingTimeRemaining = this.currentDose.timeRemaining;
2290
+ this.dosingVolumeRemaining = this.currentDose.volumeRemaining;
2291
+ this.doseTime = Math.round(this.currentDose.time / 1000);
2292
+ this.currentDose._isManual = method === 'manual';
2293
+ this.currentDose.status = 'current';
2294
+ //webApp.emitToClients(`chemicalDose`, this.currentDose);
2295
+ return this.currentDose;
2296
+ }
2297
+ public endDose(dtEnd: Date = new Date(), status: string = 'completed', volumeDosed: number = 0, timeDosed: number = 0): ChemicalDoseState {
2298
+ let dose = this.currentDose;
2299
+ if (typeof dose !== 'undefined') {
2300
+ dose.type = 'Peristaltic';
2301
+ dose._timeDosed += timeDosed;
2302
+ dose.volumeDosed += volumeDosed;
2303
+ dose.end = dtEnd;
2304
+ dose.timeDosed = dose._timeDosed / 1000;
2305
+ dose.status = status;
2306
+ this.volumeDosed = dose.volumeDosed;
2307
+ this.timeDosed = Math.round(dose._timeDosed / 1000);
2308
+ this.dosingTimeRemaining = 0;
2309
+ this.dosingVolumeRemaining = 0;
2310
+ if (dose.volumeDosed > 0 || dose.timeDosed > 0) {
2311
+ this.dailyVolumeDosed = this.calcDoseHistory();
2312
+ this.doseHistory.unshift(dose);
2313
+ DataLogger.writeEnd(`chemDosage_${this.chemType}.log`, dose);
2314
+ setImmediate(() => { webApp.emitToClients(`chemicalDose`, dose); });
2315
+ }
2316
+ this.currentDose = undefined;
2317
+ }
2318
+ return dose;
2319
+ }
2320
+ // Appends dose information to the current dose. The time here is in ms and our target will be in sec.
2321
+ public appendDose(volumeDosed: number, timeDosed: number): ChemicalDoseState {
2322
+ let dose = typeof this.currentDose !== 'undefined' ? this.currentDose : this.currentDose = this.startDose();
2323
+ dose._timeDosed += timeDosed;
2324
+ dose.volumeDosed += volumeDosed;
2325
+ dose.timeDosed = dose._timeDosed / 1000;
2326
+ dose.type = 'Peristaltic';
2327
+ this.volumeDosed = dose.volumeDosed;
2328
+ this.timeDosed = Math.round(dose._timeDosed / 1000);
2329
+ this.dosingTimeRemaining = dose.timeRemaining;
2330
+ this.dosingVolumeRemaining = dose.volumeRemaining;
2331
+
2332
+ if (dose.volumeDosed > 0 || timeDosed > 0) setImmediate(() => { webApp.emitToClients(`chemicalDose`, dose); });
2333
+ return dose;
2334
+ }
2335
+ public get currentDose(): ChemicalDoseState {
2336
+ if (typeof this.data.currentDose === 'undefined') return this.data.currentDose;
2337
+ if (typeof this.data.currentDose.save !== 'function') this.data.currentDose = new ChemicalDoseState(this.data.currentDose);
2338
+ return this.data.currentDose;
2339
+ }
2340
+ public set currentDose(val: ChemicalDoseState) {
2341
+ this.setDataVal('currentDose', val);
2342
+ }
2343
+ public get doseHistory(): ChemicalDoseState[] {
2344
+ if (typeof this.data.doseHistory === 'undefined') this.data.doseHistory = [];
2345
+ if (this.data.doseHistory.length === 0) return this.data.doseHistory;
2346
+ if (typeof this.data.doseHistory[0].save !== 'function') {
2347
+ let arr: ChemicalDoseState[] = [];
2348
+ for (let i = 0; i < this.data.doseHistory.length; i++) {
2349
+ arr.push(new ChemicalDoseState(this.data.doseHistory[i]));
2350
+ }
2351
+ this.data.doseHistory = arr;
2352
+ }
2353
+ return this.data.doseHistory;
2354
+ }
2355
+ public set doseHistory(val: ChemicalDoseState[]) { this.setDataVal('doseHistory', val); }
2356
+ public get chemController() { return this; }
2357
+ public getExtended(): any {
2358
+ let chem = sys.chemDosers.getItemById(this.id);
2359
+ let obj = this.get(true);
2360
+ obj = extend(true, obj, chem.getExtended());
2361
+ return obj;
2362
+ }
2363
+ }
2364
+
2365
+ export class ChemControllerState extends EqState implements IChemControllerState {
1981
2366
  public initData() {
1982
2367
  if (typeof this.data.saturationIndex === 'undefined') this.data.saturationIndex = 0;
1983
2368
  if (typeof this.data.flowDetected === 'undefined') this.data.flowDetected = false;
@@ -2209,10 +2594,22 @@ export class ChemControllerState extends EqState {
2209
2594
  this.hasChanged = true;
2210
2595
  }
2211
2596
  }
2597
+ public getEmitData(): any {
2598
+ let chem = sys.chemControllers.getItemById(this.id);
2599
+ let obj = this.get(true);
2600
+ obj.address = chem.address;
2601
+ obj.borates = chem.borates;
2602
+ obj.saturationIndex = this.saturationIndex || 0;
2603
+ obj.alkalinity = chem.alkalinity;
2604
+ obj.calciumHardness = chem.calciumHardness;
2605
+ obj.cyanuricAcid = chem.cyanuricAcid;
2606
+ return obj;
2607
+ }
2212
2608
  public getExtended(): any {
2213
2609
  let chem = sys.chemControllers.getItemById(this.id);
2214
2610
  let obj = this.get(true);
2215
2611
  obj.address = chem.address;
2612
+ obj.borates = chem.borates;
2216
2613
  obj.saturationIndex = this.saturationIndex || 0;
2217
2614
  obj.alkalinity = chem.alkalinity;
2218
2615
  obj.calciumHardness = chem.calciumHardness;
@@ -2223,7 +2620,7 @@ export class ChemControllerState extends EqState {
2223
2620
  return obj;
2224
2621
  }
2225
2622
  }
2226
- export class ChemicalState extends ChildEqState {
2623
+ export class ChemicalState extends ChildEqState implements IChemicalState {
2227
2624
  public initData() {
2228
2625
  if (typeof this.data.probe === 'undefined') this.data.probe = {};
2229
2626
  if (typeof this.data.tank === 'undefined') this.data.tank = { capacity: 0, level: 0, units: 0 };
@@ -2266,6 +2663,7 @@ export class ChemicalState extends ChildEqState {
2266
2663
  }
2267
2664
  public startDose(dtStart: Date = new Date(), method: string = 'auto', volume: number = 0, volumeDosed: number = 0, time: number = 0, timeDosed: number = 0): ChemicalDoseState {
2268
2665
  this.currentDose = new ChemicalDoseState();
2666
+ this.currentDose.type = this.type;
2269
2667
  this.currentDose.id = this.chemController.id;
2270
2668
  this.currentDose.start = dtStart;
2271
2669
  this.currentDose.method = method;
@@ -2299,9 +2697,9 @@ export class ChemicalState extends ChildEqState {
2299
2697
  this.timeDosed = Math.round(dose._timeDosed / 1000);
2300
2698
  this.dosingTimeRemaining = 0;
2301
2699
  this.dosingVolumeRemaining = 0;
2302
- if (dose.volumeDosed > 0) {
2303
- this.doseHistory.unshift(dose);
2700
+ if (dose.volumeDosed > 0 || dose.timeDosed > 0) {
2304
2701
  this.dailyVolumeDosed = this.calcDoseHistory();
2702
+ this.doseHistory.unshift(dose);
2305
2703
  DataLogger.writeEnd(`chemDosage_${this.chemType}.log`, dose);
2306
2704
  setImmediate(() => { webApp.emitToClients(`chemicalDose`, dose); });
2307
2705
  }
@@ -2314,11 +2712,12 @@ export class ChemicalState extends ChildEqState {
2314
2712
  let dose = typeof this.currentDose !== 'undefined' ? this.currentDose : this.currentDose = this.startDose();
2315
2713
  dose._timeDosed += timeDosed;
2316
2714
  dose.volumeDosed += volumeDosed;
2715
+ dose.timeDosed = dose._timeDosed/1000;
2317
2716
  this.volumeDosed = dose.volumeDosed;
2318
2717
  this.timeDosed = Math.round(dose._timeDosed / 1000);
2319
2718
  this.dosingTimeRemaining = dose.timeRemaining;
2320
2719
  this.dosingVolumeRemaining = dose.volumeRemaining;
2321
- if (dose.volumeDosed > 0) setImmediate(() => { webApp.emitToClients(`chemicalDose`, dose); });
2720
+ if (dose.volumeDosed > 0 || timeDosed > 0) setImmediate(() => { webApp.emitToClients(`chemicalDose`, dose); });
2322
2721
  return dose;
2323
2722
  }
2324
2723
  public get currentDose(): ChemicalDoseState {
@@ -2346,6 +2745,7 @@ export class ChemicalState extends ChildEqState {
2346
2745
  let dH = this.demandHistory;
2347
2746
  dH.appendDemand(time, val);
2348
2747
  }
2748
+ public get type() { return this.data.type; };
2349
2749
  public get demandHistory() { return new ChemicalDemandState(this.data, 'demandHistory', this) };
2350
2750
  public get enabled(): boolean { return this.data.enabled; }
2351
2751
  public set enabled(val: boolean) { this.data.enabled = val; }
@@ -2410,6 +2810,7 @@ export class ChemicalPhState extends ChemicalState {
2410
2810
  public initData() {
2411
2811
  super.initData();
2412
2812
  if (typeof this.data.chemType === 'undefined') this.data.chemType = 'none';
2813
+ if (typeof this.data.type === 'undefined') this.data.type = 'ph';
2413
2814
  }
2414
2815
  public getConfig() {
2415
2816
  let schem = this.chemController;
@@ -2436,13 +2837,27 @@ export class ChemicalPhState extends ChemicalState {
2436
2837
  // Calculate how many mL are required to raise to our pH level.
2437
2838
  // 1. Get the total gallons of water that the chem controller is in
2438
2839
  // control of.
2840
+ // 2. RSG 5-22-22 - If the spa is on, calc demand only based on the spa volume. Otherwise, long periods of spa usage
2841
+ // will result in an overdose if pH is high.
2439
2842
  let totalGallons = 0;
2843
+ // The bodyIsOn code was throwing an exception whenver no bodies were on.
2844
+ if (chem.body === 32 && sys.equipment.shared) {
2845
+ // We are shared and when body 2 (spa) is on body 1 (pool) is off.
2846
+ if (state.temps.bodies.getItemById(2).isOn === true) totalGallons = sys.bodies.getItemById(2).capacity;
2847
+ else totalGallons = sys.bodies.getItemById(1).capacity + sys.bodies.getItemById(2).capacity;
2848
+ }
2849
+ else {
2850
+ // These are all single body implementations so we simply match to the body.
2851
+ totalGallons = sys.bodies.getItemById(chem.body + 1).capacity;
2852
+ }
2440
2853
 
2441
- if (chem.body === 0 || chem.body === 32 || sys.equipment.shared) totalGallons += sys.bodies.getItemById(1).capacity;
2442
- if (chem.body === 1 || chem.body === 32 || sys.equipment.shared) totalGallons += sys.bodies.getItemById(2).capacity;
2443
- if (chem.body === 2) totalGallons += sys.bodies.getItemById(3).capacity;
2444
- if (chem.body === 3) totalGallons += sys.bodies.getItemById(4).capacity;
2445
- logger.verbose(`Chem begin calculating ${this.chemType} demand: ${this.level} setpoint: ${this.setpoint} body: ${totalGallons}`);
2854
+ //if (chem.body === 0 || chem.body === 32 || sys.equipment.shared) totalGallons += sys.bodies.getItemById(1).capacity;
2855
+ //let bodyIsOn = state.temps.bodies.getBodyIsOn();
2856
+ //if (bodyIsOn.circuit === 1 && sys.circuits.getInterfaceById(bodyIsOn.circuit).type === sys.board.valueMaps.circuitFunctions.getValue('spa') && (chem.body === 1 || chem.body === 32 || sys.equipment.shared)) totalGallons = sys.bodies.getItemById(2).capacity;
2857
+ //else if (chem.body === 1 || chem.body === 32 || sys.equipment.shared) totalGallons += sys.bodies.getItemById(2).capacity;
2858
+ //if (chem.body === 2) totalGallons += sys.bodies.getItemById(3).capacity;
2859
+ //if (chem.body === 3) totalGallons += sys.bodies.getItemById(4).capacity;
2860
+ logger.verbose(`Chem begin calculating ${this.chemType} demand: ${this.level} setpoint: ${this.setpoint} total gallons: ${totalGallons}`);
2446
2861
  let chg = this.setpoint - this.level;
2447
2862
  let delta = chg * totalGallons;
2448
2863
  let temp = (this.level + this.setpoint) / 2;
@@ -2470,6 +2885,8 @@ export class ChemicalORPState extends ChemicalState {
2470
2885
  if (typeof this.data.probe === 'undefined') this.data.probe = {};
2471
2886
  if (typeof this.data.chemType === 'undefined') this.data.chemType = 'none';
2472
2887
  if (typeof this.data.useChlorinator === 'undefined') this.data.useChlorinator = false;
2888
+ if (typeof this.data.type === 'undefined') this.data.type = 'orp';
2889
+
2473
2890
  super.initData();
2474
2891
  // Load up the 24 hours doseHistory.
2475
2892
  //this.doseHistory = DataLogger.readFromEnd(`chemDosage_${this.chemType}.log`, ChemicalDoseState, (lineNumber: number, entry: ChemicalDoseState): boolean => {
@@ -2640,6 +3057,7 @@ export class ChemicalDoseState extends DataLoggerEntry {
2640
3057
  public volumeDosed: number;
2641
3058
  public time: number;
2642
3059
  public timeDosed: number;
3060
+ public type: string;
2643
3061
 
2644
3062
  public static createInstance(entry?: string): ChemicalDoseState { return new ChemicalDoseState(entry); }
2645
3063
  public save() { DataLogger.writeEnd(`chemDosage_${this.chem}.log`, this); }
@@ -2755,6 +3173,107 @@ export class ChemControllerStateWarnings extends ChildEqState {
2755
3173
  }
2756
3174
  }
2757
3175
 
3176
+ }
3177
+ export class ChemDoserStateWarnings extends ChildEqState {
3178
+ ///ctor(data): ChemControllerStateWarnings { return new ChemControllerStateWarnings(data, name || 'warnings'); }
3179
+ public dataName = 'chemDoserWarnings';
3180
+ public initData() {
3181
+ if (typeof this.data.lockout === 'undefined') this.lockout = 0;
3182
+ if (typeof this.data.pHDailyLimitReached === 'undefined') this.dailyLimitReached = 0;
3183
+ if (typeof this.data.invalidSetup === 'undefined') this.invalidSetup = 0;
3184
+ if (typeof this.data.chlorinatorCommError === 'undefined') this.chlorinatorCommError = 0;
3185
+ }
3186
+ public get lockout(): number { return this.data.lockout; }
3187
+ public set lockout(val: number) {
3188
+ if (this.lockout !== val) {
3189
+ this.data.lockout = sys.board.valueMaps.chemDoserLimits.transform(val);
3190
+ this.hasChanged = true;
3191
+ }
3192
+ }
3193
+ public get dailyLimitReached(): number { return this.data.dailyLimitReached; }
3194
+ public set dailyLimitReached(val: number) {
3195
+ if (this.dailyLimitReached !== val) {
3196
+ this.data.dailyLimitReached = sys.board.valueMaps.chemDoserLimits.transform(val);
3197
+ this.hasChanged = true;
3198
+ }
3199
+ }
3200
+ public get invalidSetup(): number { return this.data.invalidSetup; }
3201
+ public set invalidSetup(val: number) {
3202
+ if (this.invalidSetup !== val) {
3203
+ this.data.invalidSetup = sys.board.valueMaps.chemDoserLimits.transform(val);
3204
+ this.hasChanged = true;
3205
+ }
3206
+ }
3207
+ public get chlorinatorCommError(): number { return this.data.chlorinatorCommError; }
3208
+ public set chlorinatorCommError(val: number) {
3209
+ if (this.chlorinatorCommError !== val) {
3210
+ this.data.chlorinatorCommError = sys.board.valueMaps.chemDoserWarnings.transform(val);
3211
+ this.hasChanged = true;
3212
+ }
3213
+ }
3214
+ }
3215
+
3216
+ export class ChemDoserStateAlarms extends ChildEqState {
3217
+ public dataName = 'chemControllerAlarms';
3218
+ public initData() {
3219
+ if (typeof this.data.flow === 'undefined') this.data.flow = sys.board.valueMaps.chemDoserAlarms.transform(0);
3220
+ if (typeof this.data.tank === 'undefined') this.data.tank = sys.board.valueMaps.chemDoserAlarms.transform(0);
3221
+ if (typeof this.data.pumpFault === 'undefined') this.data.pumpFault = sys.board.valueMaps.chemDoserHardwareFaults.transform(0);
3222
+ if (typeof this.data.bodyFault === 'undefined') this.data.bodyFault = sys.board.valueMaps.chemDoserHardwareFaults.transform(0);
3223
+ if (typeof this.data.flowSensorFault === 'undefined') this.data.flowSensorFault = sys.board.valueMaps.chemDoserHardwareFaults.transform(0);
3224
+ if (typeof this.data.comms === 'undefined') this.data.comms = sys.board.valueMaps.chemDoserStatus.transform(0);
3225
+ if (typeof this.data.freezeProtect === 'undefined') this.data.freezeProtect = sys.board.valueMaps.chemDoserAlarms.transform(0);
3226
+ }
3227
+ public get flow(): number { return typeof this.data.flow === 'undefined' ? undefined : this.data.flow.val; }
3228
+ public set flow(val: number) {
3229
+ if (this.flow !== val) {
3230
+ this.data.flow = sys.board.valueMaps.chemDoserAlarms.transform(val);
3231
+ this.hasChanged = true;
3232
+ }
3233
+ }
3234
+ public get tank(): number { return typeof this.data.pHTank === 'undefined' ? undefined : this.data.tank.val; }
3235
+ public set tank(val: number) {
3236
+ if (this.tank !== val) {
3237
+ this.data.tank = sys.board.valueMaps.chemDoserAlarms.transform(val);
3238
+ this.hasChanged = true;
3239
+ }
3240
+ }
3241
+ public get pumpFault(): number { return typeof this.data.pumpFault === 'undefined' ? undefined : this.data.pumpFault.val; }
3242
+ public set pumpFault(val: number) {
3243
+ if (this.pumpFault !== val) {
3244
+ this.data.pumpFault = sys.board.valueMaps.chemDoserHardwareFaults.transform(val);
3245
+ this.hasChanged = true;
3246
+ }
3247
+ }
3248
+ public get bodyFault(): number { return typeof this.data.bodyFault === 'undefined' ? undefined : this.data.bodyFault.val; }
3249
+ public set bodyFault(val: number) {
3250
+ if (this.bodyFault !== val) {
3251
+ this.data.bodyFault = sys.board.valueMaps.chemDoserHardwareFaults.transform(val);
3252
+ this.hasChanged = true;
3253
+ }
3254
+ }
3255
+ public get flowSensorFault(): number { return typeof this.data.flowSensorFault === 'undefined' ? undefined : this.data.flowSensorFault.val; }
3256
+ public set flowSensorFault(val: number) {
3257
+ if (this.flowSensorFault !== val) {
3258
+ this.data.flowSensorFault = sys.board.valueMaps.chemDoserHardwareFaults.transform(val);
3259
+ this.hasChanged = true;
3260
+ }
3261
+ }
3262
+ public get comms(): number { return typeof this.data.comms === 'undefined' ? undefined : this.data.comms.val; }
3263
+ public set comms(val: number) {
3264
+ if (this.comms !== val) {
3265
+ this.data.comms = sys.board.valueMaps.chemDoserStatus.transform(val);
3266
+ this.hasChanged = true;
3267
+ }
3268
+ }
3269
+ public get freezeProtect(): number { return typeof this.data.freezeProtect === 'undefined' ? undefined : this.data.freezeProtect.val; }
3270
+ public set freezeProtect(val: number) {
3271
+ if (this.freezeProtect !== val) {
3272
+ this.data.freezeProtect = sys.board.valueMaps.chemDoserAlarms.transform(val);
3273
+ this.hasChanged = true;
3274
+ }
3275
+ }
3276
+
2758
3277
  }
2759
3278
  export class ChemControllerStateAlarms extends ChildEqState {
2760
3279
  //ctor(data): ChemControllerStateWarnings { return new ChemControllerStateWarnings(data, name || 'alarms'); }