nodejs-poolcontroller 7.3.1 → 7.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/.eslintrc.json +44 -44
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +52 -52
  3. package/CONTRIBUTING.md +74 -74
  4. package/Changelog +215 -195
  5. package/Dockerfile +17 -17
  6. package/Gruntfile.js +40 -40
  7. package/LICENSE +661 -661
  8. package/README.md +191 -186
  9. package/app.ts +2 -0
  10. package/config/Config.ts +27 -2
  11. package/config/VersionCheck.ts +33 -14
  12. package/config copy.json +299 -299
  13. package/controller/Constants.ts +88 -0
  14. package/controller/Equipment.ts +2459 -2225
  15. package/controller/Errors.ts +180 -157
  16. package/controller/Lockouts.ts +437 -0
  17. package/controller/State.ts +364 -79
  18. package/controller/boards/BoardFactory.ts +45 -45
  19. package/controller/boards/EasyTouchBoard.ts +2653 -2489
  20. package/controller/boards/IntelliCenterBoard.ts +4230 -3973
  21. package/controller/boards/IntelliComBoard.ts +63 -63
  22. package/controller/boards/IntelliTouchBoard.ts +241 -167
  23. package/controller/boards/NixieBoard.ts +1675 -1105
  24. package/controller/boards/SystemBoard.ts +4697 -3201
  25. package/controller/comms/Comms.ts +222 -10
  26. package/controller/comms/messages/Messages.ts +13 -9
  27. package/controller/comms/messages/config/ChlorinatorMessage.ts +13 -4
  28. package/controller/comms/messages/config/CircuitGroupMessage.ts +6 -0
  29. package/controller/comms/messages/config/CircuitMessage.ts +0 -0
  30. package/controller/comms/messages/config/ConfigMessage.ts +0 -0
  31. package/controller/comms/messages/config/CoverMessage.ts +1 -0
  32. package/controller/comms/messages/config/CustomNameMessage.ts +30 -30
  33. package/controller/comms/messages/config/EquipmentMessage.ts +4 -0
  34. package/controller/comms/messages/config/ExternalMessage.ts +53 -33
  35. package/controller/comms/messages/config/FeatureMessage.ts +8 -1
  36. package/controller/comms/messages/config/GeneralMessage.ts +8 -0
  37. package/controller/comms/messages/config/HeaterMessage.ts +14 -28
  38. package/controller/comms/messages/config/IntellichemMessage.ts +4 -1
  39. package/controller/comms/messages/config/OptionsMessage.ts +38 -2
  40. package/controller/comms/messages/config/PumpMessage.ts +4 -20
  41. package/controller/comms/messages/config/RemoteMessage.ts +4 -0
  42. package/controller/comms/messages/config/ScheduleMessage.ts +347 -331
  43. package/controller/comms/messages/config/SecurityMessage.ts +1 -0
  44. package/controller/comms/messages/config/ValveMessage.ts +13 -3
  45. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +2 -3
  46. package/controller/comms/messages/status/EquipmentStateMessage.ts +79 -25
  47. package/controller/comms/messages/status/HeaterStateMessage.ts +86 -53
  48. package/controller/comms/messages/status/IntelliChemStateMessage.ts +445 -386
  49. package/controller/comms/messages/status/IntelliValveStateMessage.ts +35 -35
  50. package/controller/comms/messages/status/PumpStateMessage.ts +0 -0
  51. package/controller/comms/messages/status/VersionMessage.ts +0 -0
  52. package/controller/nixie/Nixie.ts +162 -160
  53. package/controller/nixie/NixieEquipment.ts +103 -103
  54. package/controller/nixie/bodies/Body.ts +120 -117
  55. package/controller/nixie/bodies/Filter.ts +135 -135
  56. package/controller/nixie/chemistry/ChemController.ts +2498 -2395
  57. package/controller/nixie/chemistry/Chlorinator.ts +314 -313
  58. package/controller/nixie/circuits/Circuit.ts +248 -210
  59. package/controller/nixie/heaters/Heater.ts +649 -441
  60. package/controller/nixie/pumps/Pump.ts +661 -599
  61. package/controller/nixie/schedules/Schedule.ts +257 -256
  62. package/controller/nixie/valves/Valve.ts +170 -170
  63. package/defaultConfig.json +286 -271
  64. package/issue_template.md +51 -51
  65. package/logger/DataLogger.ts +448 -433
  66. package/logger/Logger.ts +0 -0
  67. package/package.json +56 -54
  68. package/tsconfig.json +25 -25
  69. package/web/Server.ts +522 -31
  70. package/web/bindings/influxDB.json +1022 -894
  71. package/web/bindings/mqtt.json +654 -543
  72. package/web/bindings/mqttAlt.json +684 -574
  73. package/web/bindings/rulesManager.json +54 -54
  74. package/web/bindings/smartThings-Hubitat.json +31 -31
  75. package/web/bindings/valveRelays.json +20 -20
  76. package/web/bindings/vera.json +25 -25
  77. package/web/interfaces/baseInterface.ts +136 -136
  78. package/web/interfaces/httpInterface.ts +124 -122
  79. package/web/interfaces/influxInterface.ts +245 -240
  80. package/web/interfaces/mqttInterface.ts +475 -464
  81. package/web/services/config/Config.ts +181 -152
  82. package/web/services/config/ConfigSocket.ts +0 -0
  83. package/web/services/state/State.ts +118 -7
  84. package/web/services/state/StateSocket.ts +18 -1
  85. package/web/services/utilities/Utilities.ts +42 -42
@@ -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;
@@ -36,6 +36,7 @@ export class State implements IState {
36
36
  private _isDirty: boolean;
37
37
  private _timerDirty: NodeJS.Timeout;
38
38
  protected _dt: Timestamp;
39
+ protected _startTime: Timestamp;
39
40
  protected _controllerType: ControllerType;
40
41
  protected onchange = (obj, fn) => {
41
42
  const handler = {
@@ -139,6 +140,7 @@ export class State implements IState {
139
140
  _state.filters = this.filters.getExtended();
140
141
  _state.schedules = this.schedules.getExtended();
141
142
  _state.chemControllers = this.chemControllers.getExtended();
143
+ _state.delays = delayMgr.serialize();
142
144
  return _state;
143
145
  }
144
146
  else {
@@ -152,7 +154,6 @@ export class State implements IState {
152
154
  return extend(true, [], this.data[section] || []);
153
155
  else
154
156
  return extend(true, {}, this.data[section] || {});
155
-
156
157
  }
157
158
  }
158
159
  public async stopAsync() {
@@ -191,6 +192,8 @@ export class State implements IState {
191
192
  public get controllerState() {
192
193
  var self = this;
193
194
  return {
195
+ systemUnits: sys.board.valueMaps.systemUnits.transform(sys.general.options.units),
196
+ startTime: self.data.startTime || '',
194
197
  time: self.data.time || '',
195
198
  // body: self.data.body || {},
196
199
  valve: self.data.valve || 0,
@@ -200,7 +203,6 @@ export class State implements IState {
200
203
  batteryVoltage: self.data.batteryVoltage || 0,
201
204
  status: self.data.status || {},
202
205
  mode: self.data.mode || {},
203
- // freeze: self.data.freeze || false,
204
206
  appVersion: sys.appVersion || '',
205
207
  appVersionState: self.appVersion.get(true) || {},
206
208
  clockMode: sys.board.valueMaps.clockModes.transform(sys.general.options.clockMode) || {},
@@ -209,7 +211,9 @@ export class State implements IState {
209
211
  model: sys.equipment.model,
210
212
  sunrise: self.data.sunrise || '',
211
213
  sunset: self.data.sunset || '',
212
- alias: sys.general.alias
214
+ alias: sys.general.alias,
215
+ freeze: utils.makeBool(self.data.freeze),
216
+ valveMode: self.data.valveMode || {},
213
217
  };
214
218
  }
215
219
  public emitAllEquipmentChanges() {
@@ -284,6 +288,14 @@ export class State implements IState {
284
288
  this.hasChanged = true;
285
289
  }
286
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
+ }
287
299
  public get freeze(): boolean { return this.data.freeze === true; }
288
300
  public set freeze(val: boolean) {
289
301
  if (this.data.freeze !== val) {
@@ -305,7 +317,6 @@ export class State implements IState {
305
317
  this.hasChanged = true;
306
318
  }
307
319
  }
308
-
309
320
  }
310
321
  public get valve(): number { return this.data.valve; }
311
322
  public set valve(val: number) {
@@ -330,14 +341,30 @@ export class State implements IState {
330
341
  public get isInitialized(): boolean { return typeof (this.data.status) !== 'undefined' && this.data.status.val !== 0; }
331
342
  public init() {
332
343
  console.log(`Init state for Pool Controller`);
333
- var state = this.loadFile(this.statePath, {});
334
- state = extend(true, { mode: { val: -1 }, temps: { units: { val: 0, name: 'F', desc: 'Fahrenheit' } } }, state);
344
+ var sdata = this.loadFile(this.statePath, {});
345
+ sdata = extend(true, { mode: { val: -1 }, temps: { units: { val: 0, name: 'F', desc: 'Fahrenheit' } } }, sdata);
346
+ if (typeof sdata.temps !== 'undefined' && typeof sdata.temps.bodies !== 'undefined') {
347
+ EqStateCollection.removeNullIds(sdata.temps.bodies);
348
+ }
349
+ EqStateCollection.removeNullIds(sdata.schedules);
350
+ EqStateCollection.removeNullIds(sdata.features);
351
+ EqStateCollection.removeNullIds(sdata.circuits);
352
+ EqStateCollection.removeNullIds(sdata.pumps);
353
+ EqStateCollection.removeNullIds(sdata.chlorinators);
354
+ EqStateCollection.removeNullIds(sdata.valves);
355
+ EqStateCollection.removeNullIds(sdata.heaters);
356
+ EqStateCollection.removeNullIds(sdata.covers);
357
+ EqStateCollection.removeNullIds(sdata.circuitGroups);
358
+ EqStateCollection.removeNullIds(sdata.lightGroups);
359
+ EqStateCollection.removeNullIds(sdata.remotes);
360
+ EqStateCollection.removeNullIds(sdata.chemControllers);
361
+ EqStateCollection.removeNullIds(sdata.filters);
335
362
  var self = this;
336
- let pnlTime = typeof state.time !== 'undefined' ? new Date(state.time) : new Date();
363
+ let pnlTime = typeof sdata.time !== 'undefined' ? new Date(sdata.time) : new Date();
337
364
  if (isNaN(pnlTime.getTime())) pnlTime = new Date();
338
365
  this._dt = new Timestamp(pnlTime);
339
366
  this._dt.milliseconds = 0;
340
- this.data = state;
367
+ this.data = sdata;
341
368
  //this.onchange(state, function () { self.dirty = true; });
342
369
  this._dt.emitter.on('change', function () {
343
370
  self.data.time = self._dt.format();
@@ -370,6 +397,7 @@ export class State implements IState {
370
397
  this.comms = new CommsState();
371
398
  this.heliotrope = new Heliotrope();
372
399
  this.appVersion = new AppVersionState(this.data, 'appVersion');
400
+ this.data.startTime = Timestamp.toISOLocal(new Date());
373
401
  versionCheck.checkGitLocal();
374
402
  }
375
403
  public resetData() {
@@ -467,11 +495,16 @@ export interface ICircuitState {
467
495
  name: string;
468
496
  nameId?: number;
469
497
  isOn: boolean;
498
+ startTime?: Timestamp;
470
499
  endTime: Timestamp;
471
500
  lightingTheme?: number;
501
+ action?: number;
472
502
  emitEquipmentChange();
473
503
  get(bCopy?: boolean);
474
504
  showInFeatures?: boolean;
505
+ isActive?: boolean;
506
+ startDelay?: boolean;
507
+ stopDelay?: boolean;
475
508
  }
476
509
 
477
510
  interface IEqStateCreator<T> { ctor(data: any, name: string, parent?): T; }
@@ -571,6 +604,20 @@ class EqStateCollection<T> {
571
604
  if (typeof (data[name]) === 'undefined') data[name] = [];
572
605
  this.data = data[name];
573
606
  }
607
+ public static removeNullIds(data: any) {
608
+ if (typeof data !== 'undefined' && Array.isArray(data) && typeof data.length === 'number') {
609
+ for (let i = data.length - 1; i >= 0; i--) {
610
+ if (typeof data[i].id !== 'number') {
611
+ console.log(`Removing ${data[i].id}-${data[i].name}`);
612
+ data.splice(i, 1);
613
+ }
614
+ else if (typeof data[i].id === 'undefined' || isNaN(data[i].id)) {
615
+ console.log(`Removing isNaN ${data[i].id}-${data[i].name}`);
616
+ data.splice(i, 1);
617
+ }
618
+ }
619
+ }
620
+ }
574
621
  public getItemById(id: number, add?: boolean, data?: any): T {
575
622
  for (let i = 0; i < this.data.length; i++)
576
623
  if (typeof this.data[i].id !== 'undefined' && this.data[i].id === id) {
@@ -827,7 +874,10 @@ export class PumpStateCollection extends EqStateCollection<PumpState> {
827
874
  }
828
875
  public cleanupState() {
829
876
  for (let i = this.data.length - 1; i >= 0; i--) {
830
- if (typeof sys.pumps.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
877
+ if (isNaN(this.data[i].id)) this.data.splice(i, 1);
878
+ else {
879
+ if (typeof sys.pumps.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
880
+ }
831
881
  }
832
882
  let cfg = sys.pumps.toArray();
833
883
  for (let i = 0; i < cfg.length; i++) {
@@ -842,6 +892,13 @@ export class PumpStateCollection extends EqStateCollection<PumpState> {
842
892
  }
843
893
  export class PumpState extends EqState {
844
894
  public dataName: string = 'pump';
895
+ public initData() {
896
+ if (typeof this.data.status === 'undefined') {
897
+ this.data.status = { name: 'ok', desc: 'Ok', val: 0 };
898
+ }
899
+ if (typeof this.data.pumpOnDelay === 'undefined') this.data.pumpOnDelay = false;
900
+ }
901
+ private _pumpOnDelayTimer: NodeJS.Timeout;
845
902
  private _threshold = 0.05;
846
903
  private exceedsThreshold(origVal: number, newVal: number) {
847
904
  return Math.abs((newVal - origVal) / origVal) > this._threshold;
@@ -875,7 +932,7 @@ export class PumpState extends EqState {
875
932
  // quick fix for #172
876
933
  if (this.status !== val) {
877
934
  if (sys.board.valueMaps.pumpTypes.getName(this.type) === 'vsf' && val === 0) {
878
- this.data.status = { name: 'ok', desc: 'Ok', val };
935
+ this.data.status = { name: 'ok', desc: 'Ok', val: 0 };
879
936
  }
880
937
  else this.data.status = sys.board.valueMaps.pumpStatus.transform(val);
881
938
  this.hasChanged = true;
@@ -901,6 +958,23 @@ export class PumpState extends EqState {
901
958
  }
902
959
  public get time(): number { return this.data.time; }
903
960
  public set time(val: number) { this.setDataVal('time', val, false); }
961
+ public get pumpOnDelay() { return this.data.pumpOnDelay; }
962
+ public set pumpOnDelay(val: boolean) {
963
+ if (val === false) {
964
+ if (typeof this._pumpOnDelayTimer !== 'undefined') clearTimeout(this._pumpOnDelayTimer);
965
+ this._pumpOnDelayTimer = undefined;
966
+ }
967
+ this.setDataVal('pumpOnDelay', val);
968
+ }
969
+ public setPumpOnDelayTimeout(delay: number) {
970
+ this.pumpOnDelay = true;
971
+ logger.info(`Pump ON Delay ${this.name} for ${delay / 1000} seconds`);
972
+ this._pumpOnDelayTimer = setTimeout(() => {
973
+ logger.info(`Pump ON Delay ${this.name} expired`);
974
+ this.pumpOnDelay = false;
975
+ }, delay);
976
+ }
977
+
904
978
  public getExtended() {
905
979
  let pump = this.get(true);
906
980
  let cpump = sys.pumps.getItemById(pump.id);
@@ -949,14 +1023,15 @@ export class ScheduleState extends EqState {
949
1023
  if (typeof this.data.startDate === 'undefined') this._startDate = new Date();
950
1024
  else this._startDate = new Date(this.data.startDate);
951
1025
  if (isNaN(this._startDate.getTime())) this._startDate = new Date();
952
- if (typeof this.data.startTimeType === 'undefined') this.data.startTimeType = 0;
953
- if (typeof this.data.endTimeType === 'undefined') this.data.endTimeType = 0;
954
- if (typeof this.data.display === 'undefined') this.display = 0;
1026
+ if (typeof this.data.startTimeType === 'undefined') this.data.startTimeType = sys.board.valueMaps.scheduleTimeTypes.transform(0);
1027
+ if (typeof this.data.endTimeType === 'undefined') this.data.endTimeType = sys.board.valueMaps.scheduleTimeTypes.transform(0);
1028
+ if (typeof this.data.display === 'undefined') this.data.display = sys.board.valueMaps.scheduleDisplayTypes.transform(0);
955
1029
  }
956
1030
  private _startDate: Date = new Date();
957
1031
  public get startDate(): Date { return this._startDate; }
958
1032
  public set startDate(val: Date) { this._startDate = val; this._saveStartDate(); }
959
1033
  private _saveStartDate() {
1034
+ if (typeof this._startDate === 'undefined') this._startDate = new Date();
960
1035
  this.startDate.setHours(0, 0, 0, 0);
961
1036
  this.setDataVal('startDate', Timestamp.toISOLocal(this.startDate));
962
1037
  }
@@ -1061,7 +1136,10 @@ export class CircuitGroupStateCollection extends EqStateCollection<CircuitGroupS
1061
1136
  }
1062
1137
  public cleanupState() {
1063
1138
  for (let i = this.data.length - 1; i >= 0; i--) {
1064
- if (typeof sys.circuitGroups.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1139
+ if (isNaN(this.data[i].id)) this.data.splice(i, 1);
1140
+ else {
1141
+ if (typeof sys.circuitGroups.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1142
+ }
1065
1143
  }
1066
1144
  let cfg = sys.circuitGroups.toArray();
1067
1145
  for (let i = 0; i < cfg.length; i++) {
@@ -1121,12 +1199,23 @@ export class CircuitGroupState extends EqState implements ICircuitGroupState, IC
1121
1199
  state._dirtyList.removeEqState(this);
1122
1200
  }
1123
1201
  }
1202
+ public get(bcopy?: boolean): any {
1203
+ let d = super.get(bcopy);
1204
+ let cg = sys.circuitGroups.getItemById(this.id);
1205
+ if (!cg.isActive) d.isActive = false;
1206
+ else d.isActive = undefined;
1207
+ return d;
1208
+ }
1209
+
1124
1210
  }
1125
1211
  export class LightGroupStateCollection extends EqStateCollection<LightGroupState> {
1126
1212
  public createItem(data: any): LightGroupState { return new LightGroupState(data); }
1127
1213
  public cleanupState() {
1128
1214
  for (let i = this.data.length - 1; i >= 0; i--) {
1129
- if (typeof sys.lightGroups.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1215
+ if (isNaN(this.data[i].id)) this.data.splice(i, 1);
1216
+ else {
1217
+ if (typeof sys.lightGroups.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1218
+ }
1130
1219
  }
1131
1220
  let cfg = sys.lightGroups.toArray();
1132
1221
  for (let i = 0; i < cfg.length; i++) {
@@ -1148,7 +1237,7 @@ export class LightGroupState extends EqState implements ICircuitGroupState, ICir
1148
1237
  public get action(): number { return typeof this.data.action !== 'undefined' ? this.data.action.val : 0; }
1149
1238
  public set action(val: number) {
1150
1239
  if (this.action !== val || typeof this.data.action === 'undefined') {
1151
- this.data.action = sys.board.valueMaps.intellibriteActions.transform(val);
1240
+ this.data.action = sys.board.valueMaps.circuitActions.transform(val);
1152
1241
  this.hasChanged = true;
1153
1242
  }
1154
1243
  }
@@ -1180,7 +1269,7 @@ export class LightGroupState extends EqState implements ICircuitGroupState, ICir
1180
1269
  let sgrp = this.get(true); // Always operate on a copy.
1181
1270
  sgrp.circuits = [];
1182
1271
  if (typeof sgrp.lightingTheme === 'undefined') sgrp.lightingTheme = sys.board.valueMaps.lightThemes.transformByName('white');
1183
- if (typeof sgrp.action === 'undefined') sgrp.action = sys.board.valueMaps.intellibriteActions.transform(0);
1272
+ if (typeof sgrp.action === 'undefined') sgrp.action = sys.board.valueMaps.circuitActions.transform(0);
1184
1273
  let cgrp = sys.circuitGroups.getItemById(this.id);
1185
1274
  for (let i = 0; i < cgrp.circuits.length; i++) {
1186
1275
  let lgc = cgrp.circuits.getItemByIndex(i).get(true);
@@ -1205,10 +1294,24 @@ export class BodyTempStateCollection extends EqStateCollection<BodyTempState> {
1205
1294
  }
1206
1295
  return undefined;
1207
1296
  }
1297
+ public getBodyByCircuitId(circuitId: number) {
1298
+ let b = this.data.find(x => x.circuit === circuitId);
1299
+ if (typeof b === 'undefined') {
1300
+ let circ = sys.circuits.getItemById(circuitId);
1301
+ // Find our body by circuit function.
1302
+ let cfn = sys.board.valueMaps.circuitFunctions.get(circ.type);
1303
+ if (typeof cfn.body !== 'undefined') b = this.data.find(x => x.id === cfn.body);
1304
+ }
1305
+ return typeof b !== 'undefined' ? this.createItem(b) : undefined;
1306
+ }
1208
1307
  public cleanupState() {
1209
1308
  for (let i = this.data.length - 1; i >= 0; i--) {
1210
- if (typeof sys.bodies.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1309
+ if (isNaN(this.data[i].id)) this.data.splice(i, 1);
1310
+ else {
1311
+ if (typeof sys.bodies.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1312
+ }
1211
1313
  }
1314
+ this.sortById();
1212
1315
  }
1213
1316
 
1214
1317
  }
@@ -1227,6 +1330,10 @@ export class BodyTempState extends EqState {
1227
1330
  public dataName = 'bodyTempState';
1228
1331
  public initData() {
1229
1332
  if (typeof this.data.heaterOptions === 'undefined') this.data.heaterOptions = { total: 0 };
1333
+ if (typeof this.data.isCovered === 'undefined') this.data.isCovered = false;
1334
+ if (typeof this.heaterCooldownDelay === 'undefined') this.data.heaterCooldownDelay = false;
1335
+ if (typeof this.data.startDelay === 'undefined') this.data.startDelay = false;
1336
+ if (typeof this.data.stopDelay === 'undefined') this.data.stopDelay = false;
1230
1337
  }
1231
1338
  public get id(): number { return this.data.id; }
1232
1339
  public set id(val: number) { this.setDataVal('id', val); }
@@ -1265,6 +1372,19 @@ export class BodyTempState extends EqState {
1265
1372
  public set coolSetpoint(val: number) { this.setDataVal('coolSetpoint', val); }
1266
1373
  public get isOn(): boolean { return this.data.isOn; }
1267
1374
  public set isOn(val: boolean) { this.setDataVal('isOn', val); }
1375
+ public get startDelay(): boolean { return this.data.startDelay; }
1376
+ public set startDelay(val: boolean) { this.setDataVal('startDelay', val); }
1377
+ public get stopDelay(): boolean { return this.data.stopDelay; }
1378
+ public set stopDelay(val: boolean) { this.setDataVal('stopDelay', val); }
1379
+
1380
+ public get isCovered(): boolean { return this.data.isCovered; }
1381
+ public set isCovered(val: boolean) { this.setDataVal('isCovered', val); }
1382
+ // RKS: Heater cooldown delays force the current valve and body configuration until the
1383
+ // heater cooldown expires. This occurs at the pool level but it is triggered by the heater attached
1384
+ // to the body. Unfortunately, I think we can only detect this condition in Nixie as there really isn't an
1385
+ // indicator with Pentair OCPs. This is triggered in NixieBoard and managed by the delayMgr.
1386
+ public get heaterCooldownDelay(): boolean { return this.data.heaterCooldownDelay; }
1387
+ public set heaterCooldownDelay(val: boolean) { this.setDataVal('heaterCooldownDelay', val); }
1268
1388
  public emitData(name: string, data: any) { webApp.emitToClients('body', this.data); }
1269
1389
  // RKS: This is a very interesting object because we have a varied object. Type safety rules should not apply
1270
1390
  // 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
@@ -1291,7 +1411,7 @@ export class BodyTempState extends EqState {
1291
1411
  }
1292
1412
  export class TemperatureState extends EqState {
1293
1413
  public initData() {
1294
- if (typeof this.data.units === 'undefined') this.units = 0;
1414
+ if (typeof this.data.units === 'undefined') this.data.units = sys.board.valueMaps.tempUnits.transform(0);
1295
1415
  }
1296
1416
  public get waterSensor1(): number { return this.data.waterSensor1; }
1297
1417
  public set waterSensor1(val: number) { this.setDataVal('waterSensor1', val); }
@@ -1331,7 +1451,13 @@ export class HeaterStateCollection extends EqStateCollection<HeaterState> {
1331
1451
  public createItem(data: any): HeaterState { return new HeaterState(data); }
1332
1452
  public cleanupState() {
1333
1453
  for (let i = this.data.length - 1; i >= 0; i--) {
1334
- if (typeof sys.heaters.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1454
+ if (isNaN(this.data[i].id)) {
1455
+ logger.info(`Removed Invalid Heater ${this.data[i].id}-${this.data[i].name}`);
1456
+ this.data.splice(i, 1);
1457
+ }
1458
+ else {
1459
+ if (typeof sys.heaters.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1460
+ }
1335
1461
  }
1336
1462
  let cfg = sys.heaters.toArray();
1337
1463
  for (let i = 0; i < cfg.length; i++) {
@@ -1344,16 +1470,36 @@ export class HeaterStateCollection extends EqStateCollection<HeaterState> {
1344
1470
  }
1345
1471
  export class HeaterState extends EqState {
1346
1472
  public dataName: string = 'heater';
1473
+ public initData() {
1474
+ if (typeof this.data.startupDelay === 'undefined') this.data.startupDelay = false;
1475
+ if (typeof this.data.shutdownDelay === 'undefined') this.data.shutdownDelay = false;
1476
+ }
1347
1477
  public get id(): number { return this.data.id; }
1348
1478
  public set id(val: number) { this.data.id = val; }
1349
1479
  public get name(): string { return this.data.name; }
1350
1480
  public set name(val: string) { this.setDataVal('name', val); }
1351
1481
  public get isOn(): boolean { return this.data.isOn; }
1352
- public set isOn(val: boolean) { this.setDataVal('isOn', val); }
1482
+ public set isOn(val: boolean) {
1483
+ if (val !== this.data.isOn) {
1484
+ if (val) this.startTime = new Timestamp();
1485
+ else this.endTime = new Timestamp();
1486
+ }
1487
+ this.setDataVal('isOn', val);
1488
+ }
1489
+ public get startTime(): Timestamp {
1490
+ if (typeof this.data.startTime === 'undefined') return undefined;
1491
+ return new Timestamp(this.data.startTime);
1492
+ }
1493
+ public set startTime(val: Timestamp) { typeof val !== 'undefined' ? this.setDataVal('startTime', Timestamp.toISOLocal(val.toDate())) : this.setDataVal('startTime', undefined); }
1494
+
1495
+ public get endTime(): Timestamp {
1496
+ if (typeof this.data.endTime === 'undefined') return undefined;
1497
+ return new Timestamp(this.data.endTime);
1498
+ }
1499
+ public set endTime(val: Timestamp) { typeof val !== 'undefined' ? this.setDataVal('endTime', Timestamp.toISOLocal(val.toDate())) : this.setDataVal('endTime', undefined); }
1500
+
1353
1501
  public get isCooling(): boolean { return this.data.isCooling; }
1354
1502
  public set isCooling(val: boolean) { this.setDataVal('isCooling', val); }
1355
- //public get isVirtual(): boolean { return this.data.isVirtual; }
1356
- //public set isVirtual(val: boolean) { this.setDataVal('isVirtual', val); }
1357
1503
  public get type(): number | any { return typeof this.data.type !== 'undefined' ? this.data.type.val : 0; }
1358
1504
  public set type(val: number | any) {
1359
1505
  if (this.type !== val) {
@@ -1368,6 +1514,13 @@ export class HeaterState extends EqState {
1368
1514
  this.hasChanged = true;
1369
1515
  }
1370
1516
  }
1517
+ public get startupDelay(): boolean { return this.data.startupDelay; }
1518
+ public set startupDelay(val: boolean) { this.setDataVal('startupDelay', val); }
1519
+ public get shutdownDelay(): boolean { return this.data.shutdownDelay; }
1520
+ public set shutdownDelay(val: boolean) { this.setDataVal('shutdownDelay', val); }
1521
+ public get bodyId(): number { return this.data.bodyId || 0 }
1522
+ public set bodyId(val: number) { this.setDataVal('bodyId', val); }
1523
+
1371
1524
  }
1372
1525
  export class FeatureStateCollection extends EqStateCollection<FeatureState> {
1373
1526
  public createItem(data: any): FeatureState { return new FeatureState(data); }
@@ -1375,7 +1528,10 @@ export class FeatureStateCollection extends EqStateCollection<FeatureState> {
1375
1528
  public async toggleFeatureStateAsync(id: number) { return sys.board.features.toggleFeatureStateAsync(id); }
1376
1529
  public cleanupState() {
1377
1530
  for (let i = this.data.length - 1; i >= 0; i--) {
1378
- if (typeof sys.features.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1531
+ if (isNaN(this.data[i].id)) this.data.splice(i, 1);
1532
+ else {
1533
+ if (typeof sys.features.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1534
+ }
1379
1535
  }
1380
1536
  let cfg = sys.features.toArray();
1381
1537
  for (let i = 0; i < cfg.length; i++) {
@@ -1391,6 +1547,9 @@ export class FeatureStateCollection extends EqStateCollection<FeatureState> {
1391
1547
 
1392
1548
  export class FeatureState extends EqState implements ICircuitState {
1393
1549
  public dataName: string = 'feature';
1550
+ public initData() {
1551
+ if (typeof this.data.freezeProtect === 'undefined') this.data.freezeProtect = false;
1552
+ }
1394
1553
  public get id(): number { return this.data.id; }
1395
1554
  public set id(val: number) { this.data.id = val; }
1396
1555
  public get name(): string { return this.data.name; }
@@ -1413,6 +1572,12 @@ export class FeatureState extends EqState implements ICircuitState {
1413
1572
  return new Timestamp(this.data.endTime);
1414
1573
  }
1415
1574
  public set endTime(val: Timestamp) { typeof val !== 'undefined' ? this.setDataVal('endTime', Timestamp.toISOLocal(val.toDate())) : this.setDataVal('endTime', undefined); }
1575
+ // This property will be set if the system has turn this feature on for freeze protection reasons. We have no way of knowing when Pentair does this but
1576
+ // need to know (so we can shut it off) if we have done this.
1577
+ public get freezeProtect(): boolean { return this.data.freezeProtect; }
1578
+ public set freezeProtect(val: boolean) { this.setDataVal('freezeProtect', val); }
1579
+ public get isActive(): boolean { return this.data.isActive; }
1580
+ public set isActive(val: boolean) { this.setDataVal('isActive', val); }
1416
1581
  }
1417
1582
  export class VirtualCircuitState extends EqState implements ICircuitState {
1418
1583
  public dataName: string = 'virtualCircuit';
@@ -1436,6 +1601,8 @@ export class VirtualCircuitState extends EqState implements ICircuitState {
1436
1601
  return new Timestamp(this.data.endTime);
1437
1602
  }
1438
1603
  public set endTime(val: Timestamp) { typeof val !== 'undefined' ? this.setDataVal('endTime', Timestamp.toISOLocal(val.toDate())) : this.setDataVal('endTime', undefined); }
1604
+ public get isActive(): boolean { return this.data.isActive; }
1605
+ public set isActive(val: boolean) { this.setDataVal('isActive', val); }
1439
1606
  }
1440
1607
  export class VirtualCircuitStateCollection extends EqStateCollection<VirtualCircuitState> {
1441
1608
  public createItem(data: any): VirtualCircuitState { return new VirtualCircuitState(data); }
@@ -1460,7 +1627,10 @@ export class CircuitStateCollection extends EqStateCollection<CircuitState> {
1460
1627
  }
1461
1628
  public cleanupState() {
1462
1629
  for (let i = this.data.length - 1; i >= 0; i--) {
1463
- if (typeof sys.circuits.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1630
+ if (isNaN(this.data[i].id)) this.data.splice(i, 1);
1631
+ else {
1632
+ if (typeof sys.circuits.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1633
+ }
1464
1634
  }
1465
1635
  let cfg = sys.circuits.toArray();
1466
1636
  for (let i = 0; i < cfg.length; i++) {
@@ -1476,16 +1646,32 @@ export class CircuitStateCollection extends EqStateCollection<CircuitState> {
1476
1646
  }
1477
1647
  export class CircuitState extends EqState implements ICircuitState {
1478
1648
  public dataName = 'circuit';
1649
+ public initData() {
1650
+ if (typeof this.data.freezeProtect === 'undefined') this.data.freezeProtect = false;
1651
+ if (typeof this.data.action === 'undefined') this.data.action = sys.board.valueMaps.circuitActions.transform(0);
1652
+ if (typeof this.data.type === 'undefined') this.data.type = sys.board.valueMaps.circuitFunctions.transform(0);
1653
+ }
1479
1654
  public get id(): number { return this.data.id; }
1480
1655
  public set id(val: number) { this.data.id = val; }
1481
1656
  public get name(): string { return this.data.name; }
1482
1657
  public set name(val: string) { this.setDataVal('name', val); }
1483
1658
  public get nameId(): number { return this.data.nameId; }
1484
1659
  public set nameId(val: number) { this.setDataVal('nameId', val); }
1660
+ public get action(): number { return typeof this.data.action !== 'undefined' ? this.data.action.val : 0; }
1661
+ public set action(val: number) {
1662
+ if (this.action !== val || typeof this.data.action === 'undefined') {
1663
+ this.data.action = sys.board.valueMaps.circuitActions.transform(val);
1664
+ this.hasChanged = true;
1665
+ }
1666
+ }
1485
1667
  public get showInFeatures(): boolean { return this.data.showInFeatures; }
1486
1668
  public set showInFeatures(val: boolean) { this.setDataVal('showInFeatures', val); }
1487
1669
  public get isOn(): boolean { return this.data.isOn; }
1488
- public set isOn(val: boolean) { this.setDataVal('isOn', val); }
1670
+ public set isOn(val: boolean) {
1671
+ if (val && !this.data.isOn) this.startTime = new Timestamp();
1672
+ else if (!val) this.startTime = undefined;
1673
+ this.setDataVal('isOn', val);
1674
+ }
1489
1675
  public get type() { return typeof (this.data.type) !== 'undefined' ? this.data.type.val : -1; }
1490
1676
  public set type(val: number) {
1491
1677
  if (this.type !== val) {
@@ -1493,10 +1679,12 @@ export class CircuitState extends EqState implements ICircuitState {
1493
1679
  this.hasChanged = true;
1494
1680
  }
1495
1681
  }
1496
- public get lightingTheme(): number { return typeof (this.data.lightingTheme) !== 'undefined' ? this.data.lightingTheme.val : 255; }
1682
+ public get lightingTheme(): number { return typeof this.data.lightingTheme !== 'undefined' ? this.data.lightingTheme.val : 255; }
1497
1683
  public set lightingTheme(val: number) {
1498
1684
  if (this.lightingTheme !== val) {
1499
- this.data.lightingTheme = sys.board.valueMaps.lightThemes.transform(val);
1685
+ // Force this to undefined when we are a circuit without a theme.
1686
+ if (typeof val === 'undefined') this.data.lightingTheme = undefined;
1687
+ else this.data.lightingTheme = sys.board.valueMaps.lightThemes.transform(val);
1500
1688
  this.hasChanged = true;
1501
1689
  }
1502
1690
  }
@@ -1509,17 +1697,42 @@ export class CircuitState extends EqState implements ICircuitState {
1509
1697
  this.hasChanged = true;
1510
1698
  }
1511
1699
  }
1700
+ public get startTime(): Timestamp {
1701
+ if (typeof this.data.startTime === 'undefined') return undefined;
1702
+ return new Timestamp(this.data.startTime);
1703
+ }
1704
+ public set startTime(val: Timestamp) { typeof val !== 'undefined' ? this.setDataVal('startTime', Timestamp.toISOLocal(val.toDate())) : this.setDataVal('startTime', undefined); }
1705
+
1512
1706
  public get endTime(): Timestamp {
1513
1707
  if (typeof this.data.endTime === 'undefined') return undefined;
1514
1708
  return new Timestamp(this.data.endTime);
1515
1709
  }
1516
1710
  public set endTime(val: Timestamp) { typeof val !== 'undefined' ? this.setDataVal('endTime', Timestamp.toISOLocal(val.toDate())) : this.setDataVal('endTime', undefined); }
1711
+ // This property will be set if the system has turn this circuit on for freeze protection reasons. We have no way of knowing when Pentair does this but
1712
+ // need to know (so we can shut it off) if we have done this.
1713
+ public get freezeProtect(): boolean { return this.data.freezeProtect; }
1714
+ public set freezeProtect(val: boolean) { this.setDataVal('freezeProtect', val); }
1715
+ public get isActive(): boolean { return this.data.isActive; }
1716
+ public set isActive(val: boolean) { this.setDataVal('isActive', val); }
1717
+ // The properties below are for delays and lockouts. Manual or scheduled
1718
+ // actions cannot be performed when the flags below are set.
1719
+ public get startDelay(): boolean { return this.data.startDelay; }
1720
+ public set startDelay(val: boolean) { this.setDataVal('startDelay', val); }
1721
+ public get stopDelay(): boolean { return this.data.stopDelay; }
1722
+ public set stopDelay(val: boolean) { this.setDataVal('stopDelay', val); }
1723
+ public get lockoutOn(): boolean { return this.data.lockoutOn; }
1724
+ public set lockoutOn(val: boolean) { this.setDataVal('lockoutOn', val); }
1725
+ public get lockoutOff(): boolean { return this.data.lockoutOff; }
1726
+ public set lockoutOff(val: boolean) { this.setDataVal('lockoutOff', val); }
1517
1727
  }
1518
1728
  export class ValveStateCollection extends EqStateCollection<ValveState> {
1519
1729
  public createItem(data: any): ValveState { return new ValveState(data); }
1520
1730
  public cleanupState() {
1521
1731
  for (let i = this.data.length - 1; i >= 0; i--) {
1522
- if (typeof sys.valves.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1732
+ if (isNaN(this.data[i].id)) this.data.splice(i, 1);
1733
+ else {
1734
+ if (typeof sys.valves.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1735
+ }
1523
1736
  }
1524
1737
  let cfg = sys.valves.toArray();
1525
1738
  for (let i = 0; i < cfg.length; i++) {
@@ -1558,7 +1771,7 @@ export class ValveState extends EqState {
1558
1771
  if (valve.circuit !== 256) vstate.circuit = state.circuits.getInterfaceById(valve.circuit).get(true);
1559
1772
  vstate.isIntake = utils.makeBool(valve.isIntake);
1560
1773
  vstate.isReturn = utils.makeBool(valve.isReturn);
1561
- vstate.isVirtual = utils.makeBool(valve.isVirtual);
1774
+ // vstate.isVirtual = utils.makeBool(valve.isVirtual);
1562
1775
  vstate.isActive = utils.makeBool(valve.isActive);
1563
1776
  vstate.pinId = valve.pinId;
1564
1777
  return vstate;
@@ -1576,12 +1789,15 @@ export class CoverStateCollection extends EqStateCollection<CoverState> {
1576
1789
  }
1577
1790
  export class CoverState extends EqState {
1578
1791
  public dataName: string = 'cover';
1792
+ public initData() {
1793
+ if (typeof this.data.isClosed === 'undefined') this.data.isClosed = false;
1794
+ }
1579
1795
  public get id(): number { return this.data.id; }
1580
1796
  public set id(val: number) { this.data.id = val; }
1581
1797
  public get name(): string { return this.data.name; }
1582
1798
  public set name(val: string) { this.setDataVal('name', val); }
1583
- public get isOpen(): boolean { return this.data.isOpen; }
1584
- public set isOpen(val: boolean) { this.setDataVal('isOpen', val); }
1799
+ public get isClosed(): boolean { return this.data.isClosed; }
1800
+ public set isClosed(val: boolean) { this.setDataVal('isClosed', val); }
1585
1801
  }
1586
1802
  export class ChlorinatorStateCollection extends EqStateCollection<ChlorinatorState> {
1587
1803
  public createItem(data: any): ChlorinatorState { return new ChlorinatorState(data); }
@@ -1589,7 +1805,10 @@ export class ChlorinatorStateCollection extends EqStateCollection<ChlorinatorSta
1589
1805
  public lastDispatchSuperChlor: number = 0;
1590
1806
  public cleanupState() {
1591
1807
  for (let i = this.data.length - 1; i >= 0; i--) {
1592
- if (typeof sys.chlorinators.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1808
+ if (isNaN(this.data[i].id)) this.data.splice(i, 1);
1809
+ else {
1810
+ if (typeof sys.chlorinators.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1811
+ }
1593
1812
  }
1594
1813
  let cfg = sys.chlorinators.toArray();
1595
1814
  for (let i = 0; i < cfg.length; i++) {
@@ -1642,16 +1861,6 @@ export class ChlorinatorState extends EqState {
1642
1861
  this.hasChanged = true;
1643
1862
  }
1644
1863
  }
1645
- //public get virtualControllerStatus(): number {
1646
- // return typeof (this.data.virtualControllerStatus) !== 'undefined' ? this.data.virtualControllerStatus.val : -1;
1647
- //}
1648
- //public set virtualControllerStatus(val: number) {
1649
- // if (this.virtualControllerStatus !== val) {
1650
- // this.data.virtualControllerStatus = sys.board.valueMaps.virtualControllerStatus.transform(val);
1651
- // this.hasChanged = true;
1652
-
1653
- // }
1654
- //}
1655
1864
  public get type(): number { return typeof (this.data.type) !== 'undefined' ? this.data.type.val : -1; }
1656
1865
  public set type(val: number) {
1657
1866
  if (this.type !== val) {
@@ -1738,12 +1947,21 @@ export class ChlorinatorState extends EqState {
1738
1947
  else
1739
1948
  this.setDataVal('superChlor', false);
1740
1949
  }
1950
+ public getExtended(): any {
1951
+ let schlor = this.get(true);
1952
+ let chlor = sys.chlorinators.getItemById(this.id, false);
1953
+ schlor.lockSetpoints = chlor.disabled || chlor.isDosing;
1954
+ return schlor;
1955
+ }
1741
1956
  }
1742
1957
  export class ChemControllerStateCollection extends EqStateCollection<ChemControllerState> {
1743
1958
  public createItem(data: any): ChemControllerState { return new ChemControllerState(data); }
1744
1959
  public cleanupState() {
1745
1960
  for (let i = this.data.length - 1; i >= 0; i--) {
1746
- if (typeof sys.chemControllers.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1961
+ if (isNaN(this.data[i].id)) this.data.splice(i, 1);
1962
+ else {
1963
+ if (typeof sys.chemControllers.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1964
+ }
1747
1965
  }
1748
1966
  // Make sure we have at least the items that exist in the config.
1749
1967
  let cfg = sys.chemControllers.toArray();
@@ -1766,7 +1984,9 @@ export class ChemControllerState extends EqState {
1766
1984
  if (typeof this.data.orp === 'undefined') this.data.orp = {};
1767
1985
  if (typeof this.data.ph === 'undefined') this.data.ph = {};
1768
1986
  if (typeof this.data.flowSensor === 'undefined') this.data.flowSensor = {};
1769
- if (typeof this.data.type === 'undefined') { this.type = 1; }
1987
+ if (typeof this.data.type === 'undefined') {
1988
+ this.data.type = sys.board.valueMaps.chemControllerTypes.transform(1);
1989
+ }
1770
1990
  else if (typeof this.data.type.ph === 'undefined') {
1771
1991
  this.data.type = sys.board.valueMaps.chemControllerTypes.transform(this.type);
1772
1992
  }
@@ -1783,7 +2003,7 @@ export class ChemControllerState extends EqState {
1783
2003
  //var chemControllerState = {
1784
2004
  // lastComm: 'number', // The unix time the chem controller sent its status.
1785
2005
  // id: 'number', // Id of the chemController.
1786
- // type: 'valueMap', // intellichem, homegrown, rem.
2006
+ // type: 'valueMap', // intellichem, rem.
1787
2007
  // address: 'number', // Assigned address if IntelliChem.
1788
2008
  // name: 'string', // Name assigned to the controller.
1789
2009
  // status: 'valueMap', // ok, nocomms, setupError
@@ -1982,7 +2202,7 @@ export class ChemControllerState extends EqState {
1982
2202
  public get flowSensor(): ChemicalFlowSensorState { return new ChemicalFlowSensorState(this.data, 'flowSensor', this); }
1983
2203
  public get warnings(): ChemControllerStateWarnings { return new ChemControllerStateWarnings(this.data, 'warnings', this); }
1984
2204
  public get alarms(): ChemControllerStateAlarms { return new ChemControllerStateAlarms(this.data, 'alarms', this); }
1985
- public get siCalcType(): number { return this.data.siCalcType; }
2205
+ public get siCalcType(): number { return typeof this.data.siCalcType === 'undefined' ? 0 : this.data.siCalcType.val; }
1986
2206
  public set siCalcType(val: number) {
1987
2207
  if (this.siCalcType !== val) {
1988
2208
  this.data.siCalcType = sys.board.valueMaps.siCalcTypes.transform(val);
@@ -2006,14 +2226,15 @@ export class ChemControllerState extends EqState {
2006
2226
  export class ChemicalState extends ChildEqState {
2007
2227
  public initData() {
2008
2228
  if (typeof this.data.probe === 'undefined') this.data.probe = {};
2009
- if (typeof this.data.tank == 'undefined') this.data.tank = { capacity: 0, level: 0, units: 0 };
2229
+ if (typeof this.data.tank === 'undefined') this.data.tank = { capacity: 0, level: 0, units: 0 };
2230
+ if (typeof this.data.pump === 'undefined') this.data.pump = { isDosing: false };
2010
2231
  if (typeof this.data.dosingTimeRemaining === 'undefined') this.data.dosingTimeRemaining = 0;
2011
2232
  if (typeof this.data.delayTimeRemaining === 'undefined') this.data.delayTimeRemaining = 0;
2012
2233
  if (typeof this.data.dosingVolumeRemaining === 'undefined') this.data.dosingVolumeRemaining = 0;
2013
2234
  if (typeof this.data.doseVolume === 'undefined') this.data.doseVolume = 0;
2014
2235
  if (typeof this.data.doseTime === 'undefined') this.data.doseTime = 0;
2015
2236
  if (typeof this.data.lockout === 'undefined') this.data.lockout = false;
2016
- if (typeof this.data.level == 'undefined') this.data.level = 0;
2237
+ if (typeof this.data.level === 'undefined') this.data.level = 0;
2017
2238
  if (typeof this.data.mixTimeRemaining === 'undefined') this.data.mixTimeRemaining = 0;
2018
2239
  if (typeof this.data.dailyLimitReached === 'undefined') this.data.dailyLimitReached = false;
2019
2240
  if (typeof this.data.manualDosing === 'undefined') this.data.manualDosing = false;
@@ -2021,7 +2242,7 @@ export class ChemicalState extends ChildEqState {
2021
2242
  if (typeof this.data.flowDelay === 'undefined') this.data.flowDelay = false;
2022
2243
  if (typeof this.data.dosingStatus === 'undefined') this.dosingStatus = 2;
2023
2244
  if (typeof this.data.enabled === 'undefined') this.data.enabled = true;
2024
- if (typeof this.data.level === 'undefined') this.data.level = 0;
2245
+ if (typeof this.data.freezeProtect === 'undefined') this.data.freezeProtect = false;
2025
2246
  }
2026
2247
  public getConfig(): Chemical { return; }
2027
2248
  public calcDoseHistory(): number {
@@ -2128,6 +2349,8 @@ export class ChemicalState extends ChildEqState {
2128
2349
  public get demandHistory() { return new ChemicalDemandState(this.data, 'demandHistory', this) };
2129
2350
  public get enabled(): boolean { return this.data.enabled; }
2130
2351
  public set enabled(val: boolean) { this.data.enabled = val; }
2352
+ public get freezeProtect(): boolean { return this.data.freezeProtect; }
2353
+ public set freezeProtect(val: boolean) { this.data.freezeProtect = val; }
2131
2354
  public get level(): number { return this.data.level; }
2132
2355
  public set level(val: number) { this.setDataVal('level', val); }
2133
2356
  public get setpoint(): number { return this.data.setpoint; }
@@ -2185,8 +2408,8 @@ export class ChemicalState extends ChildEqState {
2185
2408
  }
2186
2409
  export class ChemicalPhState extends ChemicalState {
2187
2410
  public initData() {
2188
- if (typeof this.data.chemType === 'undefined') this.data.chemType === 'acid';
2189
2411
  super.initData();
2412
+ if (typeof this.data.chemType === 'undefined') this.data.chemType = 'none';
2190
2413
  }
2191
2414
  public getConfig() {
2192
2415
  let schem = this.chemController;
@@ -2195,7 +2418,8 @@ export class ChemicalPhState extends ChemicalState {
2195
2418
  return typeof chem !== 'undefined' ? chem.ph : undefined;
2196
2419
  }
2197
2420
  }
2198
- public get chemType() { return 'acid'; }
2421
+ public get chemType() { return this.data.chemType; }
2422
+ public set chemType(val: string) { this.setDataVal('chemType', val); }
2199
2423
  public get probe(): ChemicalProbePHState { return new ChemicalProbePHState(this.data, 'probe', this); }
2200
2424
  public getExtended() {
2201
2425
  let chem = super.getExtended();
@@ -2244,7 +2468,7 @@ export class ChemicalPhState extends ChemicalState {
2244
2468
  export class ChemicalORPState extends ChemicalState {
2245
2469
  public initData() {
2246
2470
  if (typeof this.data.probe === 'undefined') this.data.probe = {};
2247
- if (typeof this.data.chemType === 'undefined') this.data.chemType === 'orp';
2471
+ if (typeof this.data.chemType === 'undefined') this.data.chemType = 'none';
2248
2472
  if (typeof this.data.useChlorinator === 'undefined') this.data.useChlorinator = false;
2249
2473
  super.initData();
2250
2474
  // Load up the 24 hours doseHistory.
@@ -2257,6 +2481,7 @@ export class ChemicalORPState extends ChemicalState {
2257
2481
  //});
2258
2482
  }
2259
2483
  public get chemType() { return 'orp'; }
2484
+ public set chemType(val) { this.setDataVal('chemType', val); }
2260
2485
  public get probe() { return new ChemicalProbeORPState(this.data, 'probe', this); }
2261
2486
  public get useChlorinator(): boolean { return utils.makeBool(this.data.useChlorinator); }
2262
2487
  public set useChlorinator(val: boolean) { this.setDataVal('useChlorinator', val); }
@@ -2390,14 +2615,16 @@ export class ChemicalDoseState extends DataLoggerEntry {
2390
2615
  public _isManual: boolean;
2391
2616
 
2392
2617
  constructor(entry?: string | object) {
2393
- super(entry);
2618
+ super();
2619
+ if (typeof entry === 'object') entry = JSON.stringify(entry);
2620
+ if (typeof entry === 'string') this.parse(entry);
2394
2621
  // Javascript is idiotic in that the initialization of variables
2395
2622
  // do not happen before the assignment so some of the values can be undefined.
2396
2623
  if (typeof this.volumeDosed === 'undefined' || !this.volumeDosed) this.volumeDosed = 0;
2397
2624
  if (typeof this.volume === 'undefined' || !this.volume) this.volume = 0;
2398
2625
  if (typeof this._isManual === 'undefined') this._isManual = this.method === 'manual';
2399
2626
  if (typeof this.timeDosed === 'undefined' || !this.timeDosed) this.timeDosed = 0;
2400
- if (typeof this._timeDosed === 'undefined') this.timeDosed * 1000;
2627
+ if (typeof this._timeDosed === 'undefined') this._timeDosed = this.timeDosed * 1000;
2401
2628
  if (typeof this.time === 'undefined' || !this.time) this.time = 0;
2402
2629
  }
2403
2630
  public id: number;
@@ -2414,10 +2641,40 @@ export class ChemicalDoseState extends DataLoggerEntry {
2414
2641
  public time: number;
2415
2642
  public timeDosed: number;
2416
2643
 
2417
- public createInstance(entry?: string): ChemicalDoseState { return new ChemicalDoseState(entry); }
2644
+ public static createInstance(entry?: string): ChemicalDoseState { return new ChemicalDoseState(entry); }
2418
2645
  public save() { DataLogger.writeEnd(`chemDosage_${this.chem}.log`, this); }
2419
2646
  public get timeRemaining(): number { return Math.floor(Math.max(0, this.time - (this._timeDosed / 1000))); }
2420
2647
  public get volumeRemaining(): number { return Math.max(0, this.volume - this.volumeDosed); }
2648
+ public parse(entry: string) {
2649
+ // let obj = typeof entry !== 'undefined' ? JSON.parse(entry, this.dateParser) : {};
2650
+ let obj = typeof entry !== 'undefined' ? JSON.parse(entry) : {};
2651
+ for (const prop in obj) {obj[prop] = this.dateParser(prop, obj[prop])}
2652
+ if (typeof obj.setpoint !== 'undefined') this.setpoint = obj.setpoint;
2653
+ if (typeof obj.method !== 'undefined') this.method = obj.method;
2654
+ if (typeof obj.start !== 'undefined') this.start = obj.start;
2655
+ if (typeof obj.end !== 'undefined') this.end = obj.end;
2656
+ if (typeof obj.chem !== 'undefined') this.chem = obj.chem;
2657
+ if (typeof obj.demand !== 'undefined') this.demand = obj.demand;
2658
+ if (typeof obj.id !== 'undefined') this.id = obj.id;
2659
+ if (typeof obj.level !== 'undefined') this.level = obj.level;
2660
+ if (typeof obj.volume !== 'undefined') this.volume = obj.volume;
2661
+ if (typeof obj.status !== 'undefined') this.status = obj.status;
2662
+ if (typeof obj.volumeDosed !== 'undefined') this.volumeDosed = obj.volumeDosed;
2663
+ if (typeof obj.time !== 'undefined') this.time = obj.time;
2664
+ if (typeof obj.timeDosed !== 'undefined') this.timeDosed = obj.timeDosed;
2665
+ // this.setProperties(obj);
2666
+ }
2667
+ protected setProperties(data: any) {
2668
+ let op = Object.getOwnPropertyNames(Object.getPrototypeOf(this));
2669
+ for (let i in op) {
2670
+ let prop = op[i];
2671
+ if (typeof this[prop] === 'function') continue;
2672
+ if (typeof data[prop] !== 'undefined') {
2673
+ if (typeof this[prop] === null || typeof data[prop] === null) continue;
2674
+ this[prop] = data[prop];
2675
+ }
2676
+ }
2677
+ }
2421
2678
  }
2422
2679
 
2423
2680
  export class ChemicalDemandState extends ChildEqState {
@@ -2503,20 +2760,21 @@ export class ChemControllerStateAlarms extends ChildEqState {
2503
2760
  //ctor(data): ChemControllerStateWarnings { return new ChemControllerStateWarnings(data, name || 'alarms'); }
2504
2761
  public dataName = 'chemControllerAlarms';
2505
2762
  public initData() {
2506
- if (typeof this.data.flow === 'undefined') this.flow = 0;
2507
- if (typeof this.data.pH === 'undefined') this.pH = 0;
2508
- if (typeof this.data.orp === 'undefined') this.orp = 0;
2509
- if (typeof this.data.pHTank === 'undefined') this.pHTank = 0;
2510
- if (typeof this.data.orpTank === 'undefined') this.orpTank = 0;
2511
- if (typeof this.data.probeFault === 'undefined') this.probeFault = 0;
2512
- if (typeof this.data.pHProbeFault === 'undefined') this.pHProbeFault = 0;
2513
- if (typeof this.data.orpProbeFault === 'undefined') this.orpProbeFault = 0;
2514
- if (typeof this.data.pHPumpFault === 'undefined') this.pHPumpFault = 0;
2515
- if (typeof this.data.orpPumpFault === 'undefined') this.orpPumpFault = 0;
2516
- if (typeof this.data.chlorFault === 'undefined') this.chlorFault = 0;
2517
- if (typeof this.data.bodyFault === 'undefined') this.bodyFault = 0;
2518
- if (typeof this.data.flowSensorFault === 'undefined') this.flowSensorFault = 0;
2519
- if (typeof this.data.comms === 'undefined') this.comms = 0;
2763
+ if (typeof this.data.flow === 'undefined') this.data.flow = sys.board.valueMaps.chemControllerAlarms.transform(0);
2764
+ if (typeof this.data.pH === 'undefined') this.data.pH = sys.board.valueMaps.chemControllerAlarms.transform(0);
2765
+ if (typeof this.data.orp === 'undefined') this.data.orp = sys.board.valueMaps.chemControllerAlarms.transform(0);
2766
+ if (typeof this.data.pHTank === 'undefined') this.data.pHTank = sys.board.valueMaps.chemControllerAlarms.transform(0);
2767
+ if (typeof this.data.orpTank === 'undefined') this.data.orpTank = sys.board.valueMaps.chemControllerAlarms.transform(0);
2768
+ if (typeof this.data.probeFault === 'undefined') this.data.probeFault = sys.board.valueMaps.chemControllerAlarms.transform(0);
2769
+ if (typeof this.data.pHProbeFault === 'undefined') this.data.pHProbeFault = sys.board.valueMaps.chemControllerAlarms.transform(0);
2770
+ if (typeof this.data.orpProbeFault === 'undefined') this.data.orpProbeFault = sys.board.valueMaps.chemControllerAlarms.transform(0);
2771
+ if (typeof this.data.pHPumpFault === 'undefined') this.data.pHPumpFault = sys.board.valueMaps.chemControllerHardwareFaults.transform(0);
2772
+ if (typeof this.data.orpPumpFault === 'undefined') this.data.orpPumpFault = sys.board.valueMaps.chemControllerHardwareFaults.transform(0);
2773
+ if (typeof this.data.chlorFault === 'undefined') this.data.chlorFault = sys.board.valueMaps.chemControllerHardwareFaults.transform(0);
2774
+ if (typeof this.data.bodyFault === 'undefined') this.data.bodyFault = sys.board.valueMaps.chemControllerHardwareFaults.transform(0);
2775
+ if (typeof this.data.flowSensorFault === 'undefined') this.data.flowSensorFault = sys.board.valueMaps.chemControllerHardwareFaults.transform(0);
2776
+ if (typeof this.data.comms === 'undefined') this.data.comms = sys.board.valueMaps.chemControllerStatus.transform(0);
2777
+ if (typeof this.data.freezeProtect === 'undefined') this.data.freezeProtect = sys.board.valueMaps.chemControllerAlarms.transform(0);
2520
2778
  }
2521
2779
  public get flow(): number { return typeof this.data.flow === 'undefined' ? undefined : this.data.flow.val; }
2522
2780
  public set flow(val: number) {
@@ -2609,7 +2867,6 @@ export class ChemControllerStateAlarms extends ChildEqState {
2609
2867
  this.hasChanged = true;
2610
2868
  }
2611
2869
  }
2612
-
2613
2870
  public get comms(): number { return typeof this.data.comms === 'undefined' ? undefined : this.data.comms.val; }
2614
2871
  public set comms(val: number) {
2615
2872
  if (this.comms !== val) {
@@ -2617,6 +2874,14 @@ export class ChemControllerStateAlarms extends ChildEqState {
2617
2874
  this.hasChanged = true;
2618
2875
  }
2619
2876
  }
2877
+ public get freezeProtect(): number { return typeof this.data.freezeProtect === 'undefined' ? undefined : this.data.freezeProtect.val; }
2878
+ public set freezeProtect(val: number) {
2879
+ if (this.freezeProtect !== val) {
2880
+ this.data.freezeProtect = sys.board.valueMaps.chemControllerAlarms.transform(val);
2881
+ this.hasChanged = true;
2882
+ }
2883
+ }
2884
+
2620
2885
  }
2621
2886
  export class AppVersionState extends EqState {
2622
2887
  public get nextCheckTime(): string { return this.data.nextCheckTime; }
@@ -2665,15 +2930,35 @@ export class FilterState extends EqState {
2665
2930
  this.hasChanged = true;
2666
2931
  }
2667
2932
  }
2668
- public get psi(): number { return this.data.psi; }
2669
- public set psi(val: number) { this.setDataVal('psi', val); }
2670
- public get filterPsi(): number { return this.data.filterPsi; } // do not exceed value.
2671
- public set filterPsi(val: number) { this.setDataVal('filterPsi', val); }
2933
+ public get pressureUnits(): number { return this.data.pressureUnits; }
2934
+ public set pressureUnits(val: number) {
2935
+ if (this.pressureUnits !== val) {
2936
+ this.setDataVal('pressureUnits', sys.board.valueMaps.pressureUnits.transform(val));
2937
+ }
2938
+ }
2939
+ public get pressure(): number { return this.data.pressure; }
2940
+ public set pressure(val: number) { this.setDataVal('pressure', val); }
2941
+ public get refPressure(): number { return this.data.refPressure; }
2942
+ public set refPressure(val: number) {
2943
+ if (val !== this.refPressure) {
2944
+ this.setDataVal('refPressure', val);
2945
+ this.calcCleanPercentage();
2946
+ }
2947
+ else { this.setDataVal('refPressure', val); }
2948
+ }
2949
+ public get cleanPercentage(): number { return this.data.cleanPercentage; }
2950
+ public set cleanPercentage(val: number) { this.setDataVal('cleanPercentage', val); }
2672
2951
  public get lastCleanDate(): Timestamp { return this.data.lastCleanDate; }
2673
2952
  public set lastCleanDate(val: Timestamp) { this.setDataVal('lastCleanDate', val); }
2674
- public get needsCleaning(): number { return this.data.needsCleaning; }
2675
- public set needsCleaning(val: number) { this.setDataVal('needsCleaning', val); }
2676
2953
  public get isOn(): boolean { return utils.makeBool(this.data.isOn); }
2677
2954
  public set isOn(val: boolean) { this.setDataVal('isOn', val); }
2955
+ public calcCleanPercentage() {
2956
+ if (typeof this.refPressure === 'undefined') return;
2957
+ let filter = sys.filters.find(elem => elem.id == this.id);
2958
+ // 8 to 10
2959
+ let cp = filter.cleanPressure || 0;
2960
+ let dp = filter.dirtyPressure || 1;
2961
+ this.cleanPercentage = (cp - dp != 0) ? Math.round(Math.max(0, (1 - (this.refPressure - cp) / (dp - cp)) * 100) * 100)/100 : 0;
2962
+ }
2678
2963
  }
2679
2964
  export var state = new State();