nodejs-poolcontroller 7.5.1 → 7.7.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 (64) hide show
  1. package/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -0
  2. package/.github/ISSUE_TEMPLATE/2-docs.md +12 -0
  3. package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -0
  4. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  5. package/Changelog +19 -0
  6. package/Dockerfile +3 -3
  7. package/README.md +13 -8
  8. package/app.ts +1 -1
  9. package/config/Config.ts +38 -2
  10. package/config/VersionCheck.ts +27 -12
  11. package/controller/Constants.ts +2 -1
  12. package/controller/Equipment.ts +193 -9
  13. package/controller/Errors.ts +10 -0
  14. package/controller/Lockouts.ts +503 -0
  15. package/controller/State.ts +269 -64
  16. package/controller/boards/AquaLinkBoard.ts +1000 -0
  17. package/controller/boards/BoardFactory.ts +4 -0
  18. package/controller/boards/EasyTouchBoard.ts +468 -144
  19. package/controller/boards/IntelliCenterBoard.ts +466 -307
  20. package/controller/boards/IntelliTouchBoard.ts +37 -5
  21. package/controller/boards/NixieBoard.ts +671 -141
  22. package/controller/boards/SystemBoard.ts +1397 -641
  23. package/controller/comms/Comms.ts +462 -362
  24. package/controller/comms/messages/Messages.ts +174 -30
  25. package/controller/comms/messages/config/ChlorinatorMessage.ts +6 -3
  26. package/controller/comms/messages/config/CircuitMessage.ts +1 -0
  27. package/controller/comms/messages/config/ExternalMessage.ts +10 -8
  28. package/controller/comms/messages/config/HeaterMessage.ts +141 -29
  29. package/controller/comms/messages/config/OptionsMessage.ts +9 -2
  30. package/controller/comms/messages/config/PumpMessage.ts +53 -35
  31. package/controller/comms/messages/config/ScheduleMessage.ts +33 -25
  32. package/controller/comms/messages/config/ValveMessage.ts +2 -2
  33. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +38 -86
  34. package/controller/comms/messages/status/EquipmentStateMessage.ts +59 -23
  35. package/controller/comms/messages/status/HeaterStateMessage.ts +57 -3
  36. package/controller/comms/messages/status/IntelliChemStateMessage.ts +56 -8
  37. package/controller/comms/messages/status/PumpStateMessage.ts +23 -1
  38. package/controller/nixie/Nixie.ts +1 -1
  39. package/controller/nixie/bodies/Body.ts +3 -0
  40. package/controller/nixie/chemistry/ChemController.ts +164 -51
  41. package/controller/nixie/chemistry/Chlorinator.ts +137 -88
  42. package/controller/nixie/circuits/Circuit.ts +51 -19
  43. package/controller/nixie/heaters/Heater.ts +241 -31
  44. package/controller/nixie/pumps/Pump.ts +488 -206
  45. package/controller/nixie/schedules/Schedule.ts +91 -35
  46. package/controller/nixie/valves/Valve.ts +1 -1
  47. package/defaultConfig.json +20 -0
  48. package/package.json +21 -21
  49. package/web/Server.ts +94 -49
  50. package/web/bindings/aqualinkD.json +505 -0
  51. package/web/bindings/influxDB.json +71 -1
  52. package/web/bindings/mqtt.json +98 -39
  53. package/web/bindings/mqttAlt.json +59 -1
  54. package/web/interfaces/baseInterface.ts +1 -0
  55. package/web/interfaces/httpInterface.ts +23 -2
  56. package/web/interfaces/influxInterface.ts +45 -10
  57. package/web/interfaces/mqttInterface.ts +114 -54
  58. package/web/services/config/Config.ts +55 -132
  59. package/web/services/state/State.ts +81 -4
  60. package/web/services/state/StateSocket.ts +4 -4
  61. package/web/services/utilities/Utilities.ts +8 -6
  62. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -52
  63. package/config copy.json +0 -300
  64. package/issue_template.md +0 -52
@@ -7,7 +7,7 @@ published by the Free Software Foundation, either version 3 of the
7
7
  License, or (at your option) any later version.
8
8
 
9
9
  This program is distributed in the hope that it will be useful,
10
- but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of1
11
11
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
12
  GNU Affero General Public License for more details.
13
13
 
@@ -26,7 +26,7 @@ import { sys, Chemical, ChemController } from './Equipment';
26
26
  import { versionCheck } from '../config/VersionCheck';
27
27
  import { EquipmentStateMessage } from './comms/messages/status/EquipmentStateMessage';
28
28
  import { DataLogger, DataLoggerEntry, IDataLoggerEntry } from '../logger/DataLogger';
29
-
29
+ import { delayMgr } from './Lockouts';
30
30
 
31
31
  export class State implements IState {
32
32
  statePath: string;
@@ -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.delays = delayMgr.serialize();
143
144
  return _state;
144
145
  }
145
146
  else {
@@ -153,7 +154,6 @@ export class State implements IState {
153
154
  return extend(true, [], this.data[section] || []);
154
155
  else
155
156
  return extend(true, {}, this.data[section] || {});
156
-
157
157
  }
158
158
  }
159
159
  public async stopAsync() {
@@ -212,7 +212,8 @@ export class State implements IState {
212
212
  sunrise: self.data.sunrise || '',
213
213
  sunset: self.data.sunset || '',
214
214
  alias: sys.general.alias,
215
- freeze: utils.makeBool(self.data.freeze)
215
+ freeze: utils.makeBool(self.data.freeze),
216
+ valveMode: self.data.valveMode || {},
216
217
  };
217
218
  }
218
219
  public emitAllEquipmentChanges() {
@@ -287,6 +288,14 @@ export class State implements IState {
287
288
  this.hasChanged = true;
288
289
  }
289
290
  }
291
+ public get valveMode(): number { return typeof this.data.valveMode !== 'undefined' ? this.data.valveMode.val : 0; }
292
+ public set valveMode(val: number) {
293
+ let m = sys.board.valueMaps.valveModes.transform(val);
294
+ if (m.val !== this.valveMode) {
295
+ this.data.valveMode = m;
296
+ this.hasChanged = true;
297
+ }
298
+ }
290
299
  public get freeze(): boolean { return this.data.freeze === true; }
291
300
  public set freeze(val: boolean) {
292
301
  if (this.data.freeze !== val) {
@@ -486,12 +495,18 @@ export interface ICircuitState {
486
495
  name: string;
487
496
  nameId?: number;
488
497
  isOn: boolean;
498
+ startTime?: Timestamp;
489
499
  endTime: Timestamp;
490
500
  lightingTheme?: number;
501
+ action?: number;
491
502
  emitEquipmentChange();
492
503
  get(bCopy?: boolean);
493
504
  showInFeatures?: boolean;
494
505
  isActive?: boolean;
506
+ startDelay?: boolean;
507
+ stopDelay?: boolean;
508
+ manualPriorityActive?: boolean;
509
+ dataName?: string;
495
510
  }
496
511
 
497
512
  interface IEqStateCreator<T> { ctor(data: any, name: string, parent?): T; }
@@ -883,7 +898,9 @@ export class PumpState extends EqState {
883
898
  if (typeof this.data.status === 'undefined') {
884
899
  this.data.status = { name: 'ok', desc: 'Ok', val: 0 };
885
900
  }
901
+ if (typeof this.data.pumpOnDelay === 'undefined') this.data.pumpOnDelay = false;
886
902
  }
903
+ private _pumpOnDelayTimer: NodeJS.Timeout;
887
904
  private _threshold = 0.05;
888
905
  private exceedsThreshold(origVal: number, newVal: number) {
889
906
  return Math.abs((newVal - origVal) / origVal) > this._threshold;
@@ -898,6 +915,8 @@ export class PumpState extends EqState {
898
915
  public set rpm(val: number) { this.setDataVal('rpm', val, this.exceedsThreshold(this.data.rpm, val)); }
899
916
  public get relay(): number { return this.data.relay; }
900
917
  public set relay(val: number) { this.setDataVal('relay', val); }
918
+ public get program(): number { return this.data.program; }
919
+ public set program(val: number) { this.setDataVal('program', val); }
901
920
  public get watts(): number { return this.data.watts; }
902
921
  public set watts(val: number) { this.setDataVal('watts', val, this.exceedsThreshold(this.data.watts, val)); }
903
922
  public get flow(): number { return this.data.flow; }
@@ -943,6 +962,23 @@ export class PumpState extends EqState {
943
962
  }
944
963
  public get time(): number { return this.data.time; }
945
964
  public set time(val: number) { this.setDataVal('time', val, false); }
965
+ public get pumpOnDelay() { return this.data.pumpOnDelay; }
966
+ public set pumpOnDelay(val: boolean) {
967
+ if (val === false) {
968
+ if (typeof this._pumpOnDelayTimer !== 'undefined') clearTimeout(this._pumpOnDelayTimer);
969
+ this._pumpOnDelayTimer = undefined;
970
+ }
971
+ this.setDataVal('pumpOnDelay', val);
972
+ }
973
+ public setPumpOnDelayTimeout(delay: number) {
974
+ this.pumpOnDelay = true;
975
+ logger.info(`Pump ON Delay ${this.name} for ${delay / 1000} seconds`);
976
+ this._pumpOnDelayTimer = setTimeout(() => {
977
+ logger.info(`Pump ON Delay ${this.name} expired`);
978
+ this.pumpOnDelay = false;
979
+ }, delay);
980
+ }
981
+
946
982
  public getExtended() {
947
983
  let pump = this.get(true);
948
984
  let cpump = sys.pumps.getItemById(pump.id);
@@ -953,23 +989,43 @@ export class PumpState extends EqState {
953
989
  pump.speedStepSize = cpump.speedStepSize;
954
990
  pump.flowStepSize = cpump.flowStepSize;
955
991
  pump.circuits = [];
956
- for (let i = 0; i < 8; i++) {
992
+ for (let i = 0; i < cpump.circuits.length; i++) {
957
993
  let c = cpump.circuits.getItemByIndex(i).get(true);
958
994
  c.circuit = state.circuits.getInterfaceById(c.circuit).get(true);
959
- if (typeof c.circuit.id === 'undefined' || typeof c.circuit.name === 'undefined') {
960
- // return "blank" circuit if none defined
961
- c.circuit.id = 0;
962
- c.circuit.name = 'Not Used';
963
- if (sys.board.valueMaps.pumpTypes.getName(cpump.type) === 'vf') {
964
- c.units = sys.board.valueMaps.pumpUnits.getValue('gpm');
965
- c.circuit.flow = 0;
966
- }
967
- else {
968
- c.units = sys.board.valueMaps.pumpUnits.getValue('rpm');
969
- c.circuit.speed = 0;
970
- }
995
+ switch (pump.type.name) {
996
+ case 'vf':
997
+ c.units = sys.board.valueMaps.pumpUnits.transformByName('gpm');
998
+ break;
999
+ case 'hwvs':
1000
+ case 'vssvrs':
1001
+ case 'vs':
1002
+ c.units = sys.board.valueMaps.pumpUnits.transformByName('rpm');
1003
+ break;
1004
+ case 'ss':
1005
+ case 'ds':
1006
+ case 'sf':
1007
+ case 'hwrly':
1008
+ c.units = 'undefined';
1009
+ break;
1010
+ default:
1011
+ c.units = sys.board.valueMaps.pumpUnits.transform(c.units || 0);
1012
+ break;
971
1013
  }
972
- c.units = sys.board.valueMaps.pumpUnits.transform(c.units);
1014
+ // 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.
1015
+ //if (typeof c.circuit.id === 'undefined' || typeof c.circuit.name === 'undefined') {
1016
+ // // return "blank" circuit if none defined
1017
+ // c.circuit.id = 0;
1018
+ // c.circuit.name = 'Not Used';
1019
+ // if (sys.board.valueMaps.pumpTypes.getName(cpump.type) === 'vf') {
1020
+ // c.units = sys.board.valueMaps.pumpUnits.getValue('gpm');
1021
+ // c.circuit.flow = 0;
1022
+ // }
1023
+ // else {
1024
+ // c.units = sys.board.valueMaps.pumpUnits.getValue('rpm');
1025
+ // c.circuit.speed = 0;
1026
+ // }
1027
+ //}
1028
+ //c.units = sys.board.valueMaps.pumpUnits.transform(c.units);
973
1029
  pump.circuits.push(c);
974
1030
  }
975
1031
  pump.circuits.sort((a, b) => { return a.id > b.id ? 1 : -1; });
@@ -991,9 +1047,9 @@ export class ScheduleState extends EqState {
991
1047
  if (typeof this.data.startDate === 'undefined') this._startDate = new Date();
992
1048
  else this._startDate = new Date(this.data.startDate);
993
1049
  if (isNaN(this._startDate.getTime())) this._startDate = new Date();
994
- if (typeof this.data.startTimeType === 'undefined') this.data.startTimeType = 0;
995
- if (typeof this.data.endTimeType === 'undefined') this.data.endTimeType = 0;
996
- if (typeof this.data.display === 'undefined') this.display = 0;
1050
+ if (typeof this.data.startTimeType === 'undefined') this.data.startTimeType = sys.board.valueMaps.scheduleTimeTypes.transform(0);
1051
+ if (typeof this.data.endTimeType === 'undefined') this.data.endTimeType = sys.board.valueMaps.scheduleTimeTypes.transform(0);
1052
+ if (typeof this.data.display === 'undefined') this.data.display = sys.board.valueMaps.scheduleDisplayTypes.transform(0);
997
1053
  }
998
1054
  private _startDate: Date = new Date();
999
1055
  public get startDate(): Date { return this._startDate; }
@@ -1014,6 +1070,8 @@ export class ScheduleState extends EqState {
1014
1070
  public set endTime(val: number) { this.setDataVal('endTime', val); }
1015
1071
  public get circuit(): number { return this.data.circuit; }
1016
1072
  public set circuit(val: number) { this.setDataVal('circuit', val); }
1073
+ public get disabled(): boolean { return this.data.disabled; }
1074
+ public set disabled(val: boolean) { this.setDataVal('disabled', val); }
1017
1075
  public get scheduleType(): number { return typeof (this.data.scheduleType) !== 'undefined' ? this.data.scheduleType.val : undefined; }
1018
1076
  public set scheduleType(val: number) {
1019
1077
  if (this.scheduleType !== val) {
@@ -1065,6 +1123,8 @@ export class ScheduleState extends EqState {
1065
1123
  public set coolSetpoint(val: number) { this.setDataVal('coolSetpoint', val); }
1066
1124
  public get isOn(): boolean { return this.data.isOn; }
1067
1125
  public set isOn(val: boolean) { this.setDataVal('isOn', val); }
1126
+ public get manualPriorityActive(): boolean { return this.data.manualPriorityActive; }
1127
+ public set manualPriorityActive(val: boolean) { this.setDataVal('manualPriorityActive', val); }
1068
1128
  public getExtended() {
1069
1129
  let sched = this.get(true); // Always operate on a copy.
1070
1130
  //if (typeof this.circuit !== 'undefined')
@@ -1092,6 +1152,7 @@ export interface ICircuitGroupState {
1092
1152
  dataName: string;
1093
1153
  lightingTheme?: number;
1094
1154
  showInFeatures?: boolean;
1155
+ manualPriorityActive?: boolean;
1095
1156
  get(bCopy?: boolean);
1096
1157
  emitEquipmentChange();
1097
1158
  }
@@ -1148,6 +1209,8 @@ export class CircuitGroupState extends EqState implements ICircuitGroupState, IC
1148
1209
  public set isActive(val: boolean) { this.setDataVal('isActive', val); }
1149
1210
  public get showInFeatures(): boolean { return typeof this.data.showInFeatures === 'undefined' ? true : this.data.showInFeatures; }
1150
1211
  public set showInFeatures(val: boolean) { this.setDataVal('showInFeatures', val); }
1212
+ public get manualPriorityActive(): boolean { return this.data.manualPriorityActive; }
1213
+ public set manualPriorityActive(val: boolean) { this.setDataVal('manualPriorityActive', val); }
1151
1214
  public getExtended() {
1152
1215
  let sgrp = this.get(true); // Always operate on a copy.
1153
1216
  if (typeof sgrp.showInFeatures === 'undefined') sgrp.showInFeatures = true;
@@ -1205,7 +1268,7 @@ export class LightGroupState extends EqState implements ICircuitGroupState, ICir
1205
1268
  public get action(): number { return typeof this.data.action !== 'undefined' ? this.data.action.val : 0; }
1206
1269
  public set action(val: number) {
1207
1270
  if (this.action !== val || typeof this.data.action === 'undefined') {
1208
- this.data.action = sys.board.valueMaps.intellibriteActions.transform(val);
1271
+ this.data.action = sys.board.valueMaps.circuitActions.transform(val);
1209
1272
  this.hasChanged = true;
1210
1273
  }
1211
1274
  }
@@ -1232,12 +1295,14 @@ export class LightGroupState extends EqState implements ICircuitGroupState, ICir
1232
1295
  public set isOn(val: boolean) { this.setDataVal('isOn', val); }
1233
1296
  public get isActive(): boolean { return this.data.isActive; }
1234
1297
  public set isActive(val: boolean) { this.setDataVal('isActive', val); }
1298
+ public get manualPriorityActive(): boolean { return this.data.manualPriorityActive; }
1299
+ public set manualPriorityActive(val: boolean) { this.setDataVal('manualPriorityActive', val); }
1235
1300
  public async setThemeAsync(val: number) { return sys.board.circuits.setLightThemeAsync; }
1236
1301
  public getExtended() {
1237
1302
  let sgrp = this.get(true); // Always operate on a copy.
1238
1303
  sgrp.circuits = [];
1239
1304
  if (typeof sgrp.lightingTheme === 'undefined') sgrp.lightingTheme = sys.board.valueMaps.lightThemes.transformByName('white');
1240
- if (typeof sgrp.action === 'undefined') sgrp.action = sys.board.valueMaps.intellibriteActions.transform(0);
1305
+ if (typeof sgrp.action === 'undefined') sgrp.action = sys.board.valueMaps.circuitActions.transform(0);
1241
1306
  let cgrp = sys.circuitGroups.getItemById(this.id);
1242
1307
  for (let i = 0; i < cgrp.circuits.length; i++) {
1243
1308
  let lgc = cgrp.circuits.getItemByIndex(i).get(true);
@@ -1262,6 +1327,16 @@ export class BodyTempStateCollection extends EqStateCollection<BodyTempState> {
1262
1327
  }
1263
1328
  return undefined;
1264
1329
  }
1330
+ public getBodyByCircuitId(circuitId: number) {
1331
+ let b = this.data.find(x => x.circuit === circuitId);
1332
+ if (typeof b === 'undefined') {
1333
+ let circ = sys.circuits.getItemById(circuitId);
1334
+ // Find our body by circuit function.
1335
+ let cfn = sys.board.valueMaps.circuitFunctions.get(circ.type);
1336
+ if (typeof cfn.body !== 'undefined') b = this.data.find(x => x.id === cfn.body);
1337
+ }
1338
+ return typeof b !== 'undefined' ? this.createItem(b) : undefined;
1339
+ }
1265
1340
  public cleanupState() {
1266
1341
  for (let i = this.data.length - 1; i >= 0; i--) {
1267
1342
  if (isNaN(this.data[i].id)) this.data.splice(i, 1);
@@ -1289,6 +1364,10 @@ export class BodyTempState extends EqState {
1289
1364
  public initData() {
1290
1365
  if (typeof this.data.heaterOptions === 'undefined') this.data.heaterOptions = { total: 0 };
1291
1366
  if (typeof this.data.isCovered === 'undefined') this.data.isCovered = false;
1367
+ if (typeof this.heaterCooldownDelay === 'undefined') this.data.heaterCooldownDelay = false;
1368
+ if (typeof this.data.startDelay === 'undefined') this.data.startDelay = false;
1369
+ if (typeof this.data.stopDelay === 'undefined') this.data.stopDelay = false;
1370
+ if (typeof this.data.showInDashboard === 'undefined') this.data.showInDashboard = true;
1292
1371
  }
1293
1372
  public get id(): number { return this.data.id; }
1294
1373
  public set id(val: number) { this.setDataVal('id', val); }
@@ -1327,8 +1406,21 @@ export class BodyTempState extends EqState {
1327
1406
  public set coolSetpoint(val: number) { this.setDataVal('coolSetpoint', val); }
1328
1407
  public get isOn(): boolean { return this.data.isOn; }
1329
1408
  public set isOn(val: boolean) { this.setDataVal('isOn', val); }
1409
+ public get startDelay(): boolean { return this.data.startDelay; }
1410
+ public set startDelay(val: boolean) { this.setDataVal('startDelay', val); }
1411
+ public get stopDelay(): boolean { return this.data.stopDelay; }
1412
+ public set stopDelay(val: boolean) { this.setDataVal('stopDelay', val); }
1413
+ public get showInDashboard(): boolean { return this.data.showInDashboard; }
1414
+ public set showInDashboard(val: boolean) { this.setDataVal('showInDashboard', val); }
1415
+
1330
1416
  public get isCovered(): boolean { return this.data.isCovered; }
1331
1417
  public set isCovered(val: boolean) { this.setDataVal('isCovered', val); }
1418
+ // RKS: Heater cooldown delays force the current valve and body configuration until the
1419
+ // heater cooldown expires. This occurs at the pool level but it is triggered by the heater attached
1420
+ // to the body. Unfortunately, I think we can only detect this condition in Nixie as there really isn't an
1421
+ // indicator with Pentair OCPs. This is triggered in NixieBoard and managed by the delayMgr.
1422
+ public get heaterCooldownDelay(): boolean { return this.data.heaterCooldownDelay; }
1423
+ public set heaterCooldownDelay(val: boolean) { this.setDataVal('heaterCooldownDelay', val); }
1332
1424
  public emitData(name: string, data: any) { webApp.emitToClients('body', this.data); }
1333
1425
  // RKS: This is a very interesting object because we have a varied object. Type safety rules should not apply
1334
1426
  // 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
@@ -1355,7 +1447,7 @@ export class BodyTempState extends EqState {
1355
1447
  }
1356
1448
  export class TemperatureState extends EqState {
1357
1449
  public initData() {
1358
- if (typeof this.data.units === 'undefined') this.units = 0;
1450
+ if (typeof this.data.units === 'undefined') this.data.units = sys.board.valueMaps.tempUnits.transform(0);
1359
1451
  }
1360
1452
  public get waterSensor1(): number { return this.data.waterSensor1; }
1361
1453
  public set waterSensor1(val: number) { this.setDataVal('waterSensor1', val); }
@@ -1414,16 +1506,36 @@ export class HeaterStateCollection extends EqStateCollection<HeaterState> {
1414
1506
  }
1415
1507
  export class HeaterState extends EqState {
1416
1508
  public dataName: string = 'heater';
1509
+ public initData() {
1510
+ if (typeof this.data.startupDelay === 'undefined') this.data.startupDelay = false;
1511
+ if (typeof this.data.shutdownDelay === 'undefined') this.data.shutdownDelay = false;
1512
+ }
1417
1513
  public get id(): number { return this.data.id; }
1418
1514
  public set id(val: number) { this.data.id = val; }
1419
1515
  public get name(): string { return this.data.name; }
1420
1516
  public set name(val: string) { this.setDataVal('name', val); }
1421
1517
  public get isOn(): boolean { return this.data.isOn; }
1422
- public set isOn(val: boolean) { this.setDataVal('isOn', val); }
1518
+ public set isOn(val: boolean) {
1519
+ if (val !== this.data.isOn) {
1520
+ if (val) this.startTime = new Timestamp();
1521
+ else this.endTime = new Timestamp();
1522
+ }
1523
+ this.setDataVal('isOn', val);
1524
+ }
1525
+ public get startTime(): Timestamp {
1526
+ if (typeof this.data.startTime === 'undefined') return undefined;
1527
+ return new Timestamp(this.data.startTime);
1528
+ }
1529
+ public set startTime(val: Timestamp) { typeof val !== 'undefined' ? this.setDataVal('startTime', Timestamp.toISOLocal(val.toDate())) : this.setDataVal('startTime', undefined); }
1530
+
1531
+ public get endTime(): Timestamp {
1532
+ if (typeof this.data.endTime === 'undefined') return undefined;
1533
+ return new Timestamp(this.data.endTime);
1534
+ }
1535
+ public set endTime(val: Timestamp) { typeof val !== 'undefined' ? this.setDataVal('endTime', Timestamp.toISOLocal(val.toDate())) : this.setDataVal('endTime', undefined); }
1536
+
1423
1537
  public get isCooling(): boolean { return this.data.isCooling; }
1424
1538
  public set isCooling(val: boolean) { this.setDataVal('isCooling', val); }
1425
- //public get isVirtual(): boolean { return this.data.isVirtual; }
1426
- //public set isVirtual(val: boolean) { this.setDataVal('isVirtual', val); }
1427
1539
  public get type(): number | any { return typeof this.data.type !== 'undefined' ? this.data.type.val : 0; }
1428
1540
  public set type(val: number | any) {
1429
1541
  if (this.type !== val) {
@@ -1438,6 +1550,13 @@ export class HeaterState extends EqState {
1438
1550
  this.hasChanged = true;
1439
1551
  }
1440
1552
  }
1553
+ public get startupDelay(): boolean { return this.data.startupDelay; }
1554
+ public set startupDelay(val: boolean) { this.setDataVal('startupDelay', val); }
1555
+ public get shutdownDelay(): boolean { return this.data.shutdownDelay; }
1556
+ public set shutdownDelay(val: boolean) { this.setDataVal('shutdownDelay', val); }
1557
+ public get bodyId(): number { return this.data.bodyId || 0 }
1558
+ public set bodyId(val: number) { this.setDataVal('bodyId', val); }
1559
+
1441
1560
  }
1442
1561
  export class FeatureStateCollection extends EqStateCollection<FeatureState> {
1443
1562
  public createItem(data: any): FeatureState { return new FeatureState(data); }
@@ -1495,6 +1614,8 @@ export class FeatureState extends EqState implements ICircuitState {
1495
1614
  public set freezeProtect(val: boolean) { this.setDataVal('freezeProtect', val); }
1496
1615
  public get isActive(): boolean { return this.data.isActive; }
1497
1616
  public set isActive(val: boolean) { this.setDataVal('isActive', val); }
1617
+ public get manualPriorityActive(): boolean { return this.data.manualPriorityActive; }
1618
+ public set manualPriorityActive(val: boolean) { this.setDataVal('manualPriorityActive', val); }
1498
1619
  }
1499
1620
  export class VirtualCircuitState extends EqState implements ICircuitState {
1500
1621
  public dataName: string = 'virtualCircuit';
@@ -1518,6 +1639,8 @@ export class VirtualCircuitState extends EqState implements ICircuitState {
1518
1639
  return new Timestamp(this.data.endTime);
1519
1640
  }
1520
1641
  public set endTime(val: Timestamp) { typeof val !== 'undefined' ? this.setDataVal('endTime', Timestamp.toISOLocal(val.toDate())) : this.setDataVal('endTime', undefined); }
1642
+ public get isActive(): boolean { return this.data.isActive; }
1643
+ public set isActive(val: boolean) { this.setDataVal('isActive', val); }
1521
1644
  }
1522
1645
  export class VirtualCircuitStateCollection extends EqStateCollection<VirtualCircuitState> {
1523
1646
  public createItem(data: any): VirtualCircuitState { return new VirtualCircuitState(data); }
@@ -1563,6 +1686,8 @@ export class CircuitState extends EqState implements ICircuitState {
1563
1686
  public dataName = 'circuit';
1564
1687
  public initData() {
1565
1688
  if (typeof this.data.freezeProtect === 'undefined') this.data.freezeProtect = false;
1689
+ if (typeof this.data.action === 'undefined') this.data.action = sys.board.valueMaps.circuitActions.transform(0);
1690
+ if (typeof this.data.type === 'undefined') this.data.type = sys.board.valueMaps.circuitFunctions.transform(0);
1566
1691
  }
1567
1692
  public get id(): number { return this.data.id; }
1568
1693
  public set id(val: number) { this.data.id = val; }
@@ -1570,10 +1695,21 @@ export class CircuitState extends EqState implements ICircuitState {
1570
1695
  public set name(val: string) { this.setDataVal('name', val); }
1571
1696
  public get nameId(): number { return this.data.nameId; }
1572
1697
  public set nameId(val: number) { this.setDataVal('nameId', val); }
1698
+ public get action(): number { return typeof this.data.action !== 'undefined' ? this.data.action.val : 0; }
1699
+ public set action(val: number) {
1700
+ if (this.action !== val || typeof this.data.action === 'undefined') {
1701
+ this.data.action = sys.board.valueMaps.circuitActions.transform(val);
1702
+ this.hasChanged = true;
1703
+ }
1704
+ }
1573
1705
  public get showInFeatures(): boolean { return this.data.showInFeatures; }
1574
1706
  public set showInFeatures(val: boolean) { this.setDataVal('showInFeatures', val); }
1575
1707
  public get isOn(): boolean { return this.data.isOn; }
1576
- public set isOn(val: boolean) { this.setDataVal('isOn', val); }
1708
+ public set isOn(val: boolean) {
1709
+ if (val && !this.data.isOn) this.startTime = new Timestamp();
1710
+ else if (!val) this.startTime = undefined;
1711
+ this.setDataVal('isOn', val);
1712
+ }
1577
1713
  public get type() { return typeof (this.data.type) !== 'undefined' ? this.data.type.val : -1; }
1578
1714
  public set type(val: number) {
1579
1715
  if (this.type !== val) {
@@ -1599,6 +1735,12 @@ export class CircuitState extends EqState implements ICircuitState {
1599
1735
  this.hasChanged = true;
1600
1736
  }
1601
1737
  }
1738
+ public get startTime(): Timestamp {
1739
+ if (typeof this.data.startTime === 'undefined') return undefined;
1740
+ return new Timestamp(this.data.startTime);
1741
+ }
1742
+ public set startTime(val: Timestamp) { typeof val !== 'undefined' ? this.setDataVal('startTime', Timestamp.toISOLocal(val.toDate())) : this.setDataVal('startTime', undefined); }
1743
+
1602
1744
  public get endTime(): Timestamp {
1603
1745
  if (typeof this.data.endTime === 'undefined') return undefined;
1604
1746
  return new Timestamp(this.data.endTime);
@@ -1610,6 +1752,18 @@ export class CircuitState extends EqState implements ICircuitState {
1610
1752
  public set freezeProtect(val: boolean) { this.setDataVal('freezeProtect', val); }
1611
1753
  public get isActive(): boolean { return this.data.isActive; }
1612
1754
  public set isActive(val: boolean) { this.setDataVal('isActive', val); }
1755
+ // The properties below are for delays and lockouts. Manual or scheduled
1756
+ // actions cannot be performed when the flags below are set.
1757
+ public get startDelay(): boolean { return this.data.startDelay; }
1758
+ public set startDelay(val: boolean) { this.setDataVal('startDelay', val); }
1759
+ public get stopDelay(): boolean { return this.data.stopDelay; }
1760
+ public set stopDelay(val: boolean) { this.setDataVal('stopDelay', val); }
1761
+ public get lockoutOn(): boolean { return this.data.lockoutOn; }
1762
+ public set lockoutOn(val: boolean) { this.setDataVal('lockoutOn', val); }
1763
+ public get lockoutOff(): boolean { return this.data.lockoutOff; }
1764
+ public set lockoutOff(val: boolean) { this.setDataVal('lockoutOff', val); }
1765
+ public get manualPriorityActive(): boolean { return this.data.manualPriorityActive; }
1766
+ public set manualPriorityActive(val: boolean) { this.setDataVal('manualPriorityActive', val); }
1613
1767
  }
1614
1768
  export class ValveStateCollection extends EqStateCollection<ValveState> {
1615
1769
  public createItem(data: any): ValveState { return new ValveState(data); }
@@ -1686,9 +1840,16 @@ export class CoverState extends EqState {
1686
1840
  public set isClosed(val: boolean) { this.setDataVal('isClosed', val); }
1687
1841
  }
1688
1842
  export class ChlorinatorStateCollection extends EqStateCollection<ChlorinatorState> {
1843
+ public superChlor: { id:number, lastDispatch: number, reference: number }[] = [];
1844
+ public getSuperChlor(id: number): { id: number, lastDispatch: number, reference: number } {
1845
+ let sc = this.superChlor.find(elem => id === elem.id);
1846
+ if (typeof sc === 'undefined') {
1847
+ sc = { id: id, lastDispatch: 0, reference: 0 };
1848
+ this.superChlor.push(sc);
1849
+ }
1850
+ return sc;
1851
+ }
1689
1852
  public createItem(data: any): ChlorinatorState { return new ChlorinatorState(data); }
1690
- public superChlorReference: number = 0;
1691
- public lastDispatchSuperChlor: number = 0;
1692
1853
  public cleanupState() {
1693
1854
  for (let i = this.data.length - 1; i >= 0; i--) {
1694
1855
  if (isNaN(this.data[i].id)) this.data.splice(i, 1);
@@ -1701,10 +1862,10 @@ export class ChlorinatorStateCollection extends EqStateCollection<ChlorinatorSta
1701
1862
  let c = cfg[i];
1702
1863
  let s = this.getItemById(cfg[i].id, true);
1703
1864
  s.type = c.type;
1865
+ s.model = c.model;
1704
1866
  s.name = c.name;
1705
1867
  s.isActive = c.isActive;
1706
1868
  }
1707
-
1708
1869
  }
1709
1870
  }
1710
1871
  export class ChlorinatorState extends EqState {
@@ -1754,6 +1915,13 @@ export class ChlorinatorState extends EqState {
1754
1915
  this.hasChanged = true;
1755
1916
  }
1756
1917
  }
1918
+ public get model(): number { return typeof (this.data.model) !== 'undefined' ? this.data.model.val : 0; }
1919
+ public set model(val: number) {
1920
+ if (this.model !== val) {
1921
+ this.data.model = sys.board.valueMaps.chlorinatorModel.transform(val);
1922
+ this.hasChanged = true;
1923
+ }
1924
+ }
1757
1925
  public get body(): number { return typeof (this.data.body) !== 'undefined' ? this.data.body.val : -1; }
1758
1926
  public set body(val: number) {
1759
1927
  if (this.body !== val) {
@@ -1801,37 +1969,59 @@ export class ChlorinatorState extends EqState {
1801
1969
  }
1802
1970
  public get superChlorRemaining(): number { return this.data.superChlorRemaining || 0; }
1803
1971
  public set superChlorRemaining(val: number) {
1972
+ if (val === this.data.superChlorRemaining) return;
1804
1973
  let remaining: number;
1805
- if (sys.controllerType === 'nixie') {
1806
- remaining = Math.max(0, val);
1974
+ let sc = state.chlorinators.getSuperChlor(this.id);
1975
+ let chlor = sys.chlorinators.getItemById(this.id);
1976
+ if (chlor.master === 1) {
1977
+ // If we are 10 seconds different then we need to send it off and save the data.
1978
+ if (Math.floor(val / 10) !== Math.floor(this.superChlorRemaining / 10)) {
1979
+ this.hasChanged = true;
1980
+ remaining = val;
1981
+ sc.reference = Math.floor(new Date().getTime() / 1000);
1982
+ this.setDataVal('superChlorRemaining', remaining);
1983
+ }
1984
+ else if (val <= 0)
1985
+ remaining = 0;
1986
+ else
1987
+ remaining = this.superChlorRemaining;
1988
+ }
1989
+ else if (chlor.master === 2) {
1990
+ // If we are 10 seconds different then we need to send it off and save the data.
1991
+ if (Math.floor(val / 10) !== Math.floor(this.superChlorRemaining / 10)) {
1992
+ this.hasChanged = true;
1993
+ remaining = val;
1994
+ sc.reference = Math.floor(new Date().getTime() / 1000);
1995
+ this.setDataVal('superChlorRemaining', remaining);
1996
+ }
1807
1997
  }
1808
1998
  else if (sys.controllerType === 'intellicenter') {
1809
1999
  // Trim the seconds off both of these as we will be keeping the seconds separately since this
1810
2000
  // only reports in minutes. That way our seconds become self healing.
1811
2001
  if (Math.ceil(this.superChlorRemaining / 60) * 60 !== val) {
1812
- state.chlorinators.superChlorReference = Math.floor(new Date().getTime() / 1000); // Get the epoc and strip the milliseconds.
2002
+ sc.reference = Math.floor(new Date().getTime() / 1000); // Get the epoc and strip the milliseconds.
1813
2003
  this.hasChanged = true;
1814
2004
  }
1815
- let secs = Math.floor(new Date().getTime() / 1000) - state.chlorinators.superChlorReference;
2005
+ let secs = Math.floor(new Date().getTime() / 1000) - sc.reference;
1816
2006
  remaining = Math.max(0, val - Math.min(secs, 60));
2007
+ if (sc.lastDispatch - 5 > remaining) this.hasChanged = true;
2008
+ this.data.superChlorRemaining = remaining;
1817
2009
  }
1818
2010
  else {
1819
2011
  // *Touch only reports superchlor hours remaining.
1820
2012
  // If we have the same hours as existing, retain the mins + secs
1821
2013
  if (Math.ceil(this.superChlorRemaining / 3600) * 60 !== val / 60) {
1822
- state.chlorinators.superChlorReference = Math.floor(new Date().getTime() / 1000); // Get the epoc and strip the milliseconds.
2014
+ sc.reference = Math.floor(new Date().getTime() / 1000); // Get the epoc and strip the milliseconds.
1823
2015
  this.hasChanged = true;
1824
2016
  }
1825
- let secs = Math.floor(new Date().getTime() / 1000) - state.chlorinators.superChlorReference;
2017
+ let secs = Math.floor(new Date().getTime() / 1000) - sc.reference;
1826
2018
  remaining = Math.max(0, val - Math.min(secs, 3600));
2019
+ if (sc.lastDispatch - 5 > remaining) this.hasChanged = true;
2020
+ this.data.superChlorRemaining = remaining;
1827
2021
  }
1828
- if (state.chlorinators.lastDispatchSuperChlor - 5 > remaining) this.hasChanged = true;
1829
- if (this.hasChanged) state.chlorinators.lastDispatchSuperChlor = remaining;
1830
- this.data.superChlorRemaining = remaining;
1831
- if (remaining > 0)
1832
- this.setDataVal('superChlor', true);
1833
- else
1834
- this.setDataVal('superChlor', false);
2022
+ if (this.hasChanged) sc.lastDispatch = remaining;
2023
+ this.setDataVal('superChlor', remaining > 0);
2024
+ chlor.superChlor = remaining > 0;
1835
2025
  }
1836
2026
  public getExtended(): any {
1837
2027
  let schlor = this.get(true);
@@ -1870,7 +2060,9 @@ export class ChemControllerState extends EqState {
1870
2060
  if (typeof this.data.orp === 'undefined') this.data.orp = {};
1871
2061
  if (typeof this.data.ph === 'undefined') this.data.ph = {};
1872
2062
  if (typeof this.data.flowSensor === 'undefined') this.data.flowSensor = {};
1873
- if (typeof this.data.type === 'undefined') { this.type = 1; }
2063
+ if (typeof this.data.type === 'undefined') {
2064
+ this.data.type = sys.board.valueMaps.chemControllerTypes.transform(1);
2065
+ }
1874
2066
  else if (typeof this.data.type.ph === 'undefined') {
1875
2067
  this.data.type = sys.board.valueMaps.chemControllerTypes.transform(this.type);
1876
2068
  }
@@ -2086,7 +2278,7 @@ export class ChemControllerState extends EqState {
2086
2278
  public get flowSensor(): ChemicalFlowSensorState { return new ChemicalFlowSensorState(this.data, 'flowSensor', this); }
2087
2279
  public get warnings(): ChemControllerStateWarnings { return new ChemControllerStateWarnings(this.data, 'warnings', this); }
2088
2280
  public get alarms(): ChemControllerStateAlarms { return new ChemControllerStateAlarms(this.data, 'alarms', this); }
2089
- public get siCalcType(): number { return this.data.siCalcType; }
2281
+ public get siCalcType(): number { return typeof this.data.siCalcType === 'undefined' ? 0 : this.data.siCalcType.val; }
2090
2282
  public set siCalcType(val: number) {
2091
2283
  if (this.siCalcType !== val) {
2092
2284
  this.data.siCalcType = sys.board.valueMaps.siCalcTypes.transform(val);
@@ -2126,6 +2318,7 @@ export class ChemicalState extends ChildEqState {
2126
2318
  if (typeof this.data.flowDelay === 'undefined') this.data.flowDelay = false;
2127
2319
  if (typeof this.data.dosingStatus === 'undefined') this.dosingStatus = 2;
2128
2320
  if (typeof this.data.enabled === 'undefined') this.data.enabled = true;
2321
+ if (typeof this.data.freezeProtect === 'undefined') this.data.freezeProtect = false;
2129
2322
  }
2130
2323
  public getConfig(): Chemical { return; }
2131
2324
  public calcDoseHistory(): number {
@@ -2232,6 +2425,8 @@ export class ChemicalState extends ChildEqState {
2232
2425
  public get demandHistory() { return new ChemicalDemandState(this.data, 'demandHistory', this) };
2233
2426
  public get enabled(): boolean { return this.data.enabled; }
2234
2427
  public set enabled(val: boolean) { this.data.enabled = val; }
2428
+ public get freezeProtect(): boolean { return this.data.freezeProtect; }
2429
+ public set freezeProtect(val: boolean) { this.data.freezeProtect = val; }
2235
2430
  public get level(): number { return this.data.level; }
2236
2431
  public set level(val: number) { this.setDataVal('level', val); }
2237
2432
  public get setpoint(): number { return this.data.setpoint; }
@@ -2289,8 +2484,8 @@ export class ChemicalState extends ChildEqState {
2289
2484
  }
2290
2485
  export class ChemicalPhState extends ChemicalState {
2291
2486
  public initData() {
2292
- // if (typeof this.data.chemType === 'undefined') this.data.chemType === 'acid'; // RSG 10-23-21 - Only a getter; don't need to set this.
2293
2487
  super.initData();
2488
+ if (typeof this.data.chemType === 'undefined') this.data.chemType = 'none';
2294
2489
  }
2295
2490
  public getConfig() {
2296
2491
  let schem = this.chemController;
@@ -2299,7 +2494,8 @@ export class ChemicalPhState extends ChemicalState {
2299
2494
  return typeof chem !== 'undefined' ? chem.ph : undefined;
2300
2495
  }
2301
2496
  }
2302
- public get chemType() { return 'acid'; }
2497
+ public get chemType() { return this.data.chemType; }
2498
+ public set chemType(val: string) { this.setDataVal('chemType', val); }
2303
2499
  public get probe(): ChemicalProbePHState { return new ChemicalProbePHState(this.data, 'probe', this); }
2304
2500
  public getExtended() {
2305
2501
  let chem = super.getExtended();
@@ -2348,7 +2544,7 @@ export class ChemicalPhState extends ChemicalState {
2348
2544
  export class ChemicalORPState extends ChemicalState {
2349
2545
  public initData() {
2350
2546
  if (typeof this.data.probe === 'undefined') this.data.probe = {};
2351
- if (typeof this.data.chemType === 'undefined') this.data.chemType === 'orp';
2547
+ if (typeof this.data.chemType === 'undefined') this.data.chemType = 'none';
2352
2548
  if (typeof this.data.useChlorinator === 'undefined') this.data.useChlorinator = false;
2353
2549
  super.initData();
2354
2550
  // Load up the 24 hours doseHistory.
@@ -2361,6 +2557,7 @@ export class ChemicalORPState extends ChemicalState {
2361
2557
  //});
2362
2558
  }
2363
2559
  public get chemType() { return 'orp'; }
2560
+ public set chemType(val) { this.setDataVal('chemType', val); }
2364
2561
  public get probe() { return new ChemicalProbeORPState(this.data, 'probe', this); }
2365
2562
  public get useChlorinator(): boolean { return utils.makeBool(this.data.useChlorinator); }
2366
2563
  public set useChlorinator(val: boolean) { this.setDataVal('useChlorinator', val); }
@@ -2639,20 +2836,21 @@ export class ChemControllerStateAlarms extends ChildEqState {
2639
2836
  //ctor(data): ChemControllerStateWarnings { return new ChemControllerStateWarnings(data, name || 'alarms'); }
2640
2837
  public dataName = 'chemControllerAlarms';
2641
2838
  public initData() {
2642
- if (typeof this.data.flow === 'undefined') this.flow = 0;
2643
- if (typeof this.data.pH === 'undefined') this.pH = 0;
2644
- if (typeof this.data.orp === 'undefined') this.orp = 0;
2645
- if (typeof this.data.pHTank === 'undefined') this.pHTank = 0;
2646
- if (typeof this.data.orpTank === 'undefined') this.orpTank = 0;
2647
- if (typeof this.data.probeFault === 'undefined') this.probeFault = 0;
2648
- if (typeof this.data.pHProbeFault === 'undefined') this.pHProbeFault = 0;
2649
- if (typeof this.data.orpProbeFault === 'undefined') this.orpProbeFault = 0;
2650
- if (typeof this.data.pHPumpFault === 'undefined') this.pHPumpFault = 0;
2651
- if (typeof this.data.orpPumpFault === 'undefined') this.orpPumpFault = 0;
2652
- if (typeof this.data.chlorFault === 'undefined') this.chlorFault = 0;
2653
- if (typeof this.data.bodyFault === 'undefined') this.bodyFault = 0;
2654
- if (typeof this.data.flowSensorFault === 'undefined') this.flowSensorFault = 0;
2655
- if (typeof this.data.comms === 'undefined') this.comms = 0;
2839
+ if (typeof this.data.flow === 'undefined') this.data.flow = sys.board.valueMaps.chemControllerAlarms.transform(0);
2840
+ if (typeof this.data.pH === 'undefined') this.data.pH = sys.board.valueMaps.chemControllerAlarms.transform(0);
2841
+ if (typeof this.data.orp === 'undefined') this.data.orp = sys.board.valueMaps.chemControllerAlarms.transform(0);
2842
+ if (typeof this.data.pHTank === 'undefined') this.data.pHTank = sys.board.valueMaps.chemControllerAlarms.transform(0);
2843
+ if (typeof this.data.orpTank === 'undefined') this.data.orpTank = sys.board.valueMaps.chemControllerAlarms.transform(0);
2844
+ if (typeof this.data.probeFault === 'undefined') this.data.probeFault = sys.board.valueMaps.chemControllerAlarms.transform(0);
2845
+ if (typeof this.data.pHProbeFault === 'undefined') this.data.pHProbeFault = sys.board.valueMaps.chemControllerAlarms.transform(0);
2846
+ if (typeof this.data.orpProbeFault === 'undefined') this.data.orpProbeFault = sys.board.valueMaps.chemControllerAlarms.transform(0);
2847
+ if (typeof this.data.pHPumpFault === 'undefined') this.data.pHPumpFault = sys.board.valueMaps.chemControllerHardwareFaults.transform(0);
2848
+ if (typeof this.data.orpPumpFault === 'undefined') this.data.orpPumpFault = sys.board.valueMaps.chemControllerHardwareFaults.transform(0);
2849
+ if (typeof this.data.chlorFault === 'undefined') this.data.chlorFault = sys.board.valueMaps.chemControllerHardwareFaults.transform(0);
2850
+ if (typeof this.data.bodyFault === 'undefined') this.data.bodyFault = sys.board.valueMaps.chemControllerHardwareFaults.transform(0);
2851
+ if (typeof this.data.flowSensorFault === 'undefined') this.data.flowSensorFault = sys.board.valueMaps.chemControllerHardwareFaults.transform(0);
2852
+ if (typeof this.data.comms === 'undefined') this.data.comms = sys.board.valueMaps.chemControllerStatus.transform(0);
2853
+ if (typeof this.data.freezeProtect === 'undefined') this.data.freezeProtect = sys.board.valueMaps.chemControllerAlarms.transform(0);
2656
2854
  }
2657
2855
  public get flow(): number { return typeof this.data.flow === 'undefined' ? undefined : this.data.flow.val; }
2658
2856
  public set flow(val: number) {
@@ -2745,7 +2943,6 @@ export class ChemControllerStateAlarms extends ChildEqState {
2745
2943
  this.hasChanged = true;
2746
2944
  }
2747
2945
  }
2748
-
2749
2946
  public get comms(): number { return typeof this.data.comms === 'undefined' ? undefined : this.data.comms.val; }
2750
2947
  public set comms(val: number) {
2751
2948
  if (this.comms !== val) {
@@ -2753,6 +2950,14 @@ export class ChemControllerStateAlarms extends ChildEqState {
2753
2950
  this.hasChanged = true;
2754
2951
  }
2755
2952
  }
2953
+ public get freezeProtect(): number { return typeof this.data.freezeProtect === 'undefined' ? undefined : this.data.freezeProtect.val; }
2954
+ public set freezeProtect(val: number) {
2955
+ if (this.freezeProtect !== val) {
2956
+ this.data.freezeProtect = sys.board.valueMaps.chemControllerAlarms.transform(val);
2957
+ this.hasChanged = true;
2958
+ }
2959
+ }
2960
+
2756
2961
  }
2757
2962
  export class AppVersionState extends EqState {
2758
2963
  public get nextCheckTime(): string { return this.data.nextCheckTime; }