nodejs-poolcontroller 7.3.0 → 7.6.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 (60) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/Changelog +23 -0
  3. package/README.md +5 -5
  4. package/app.ts +2 -0
  5. package/config/Config.ts +3 -0
  6. package/config/VersionCheck.ts +8 -4
  7. package/controller/Constants.ts +88 -0
  8. package/controller/Equipment.ts +246 -66
  9. package/controller/Errors.ts +24 -1
  10. package/controller/Lockouts.ts +423 -0
  11. package/controller/State.ts +314 -54
  12. package/controller/boards/EasyTouchBoard.ts +107 -59
  13. package/controller/boards/IntelliCenterBoard.ts +186 -125
  14. package/controller/boards/IntelliTouchBoard.ts +104 -30
  15. package/controller/boards/NixieBoard.ts +721 -159
  16. package/controller/boards/SystemBoard.ts +2370 -1108
  17. package/controller/comms/Comms.ts +85 -10
  18. package/controller/comms/messages/Messages.ts +10 -4
  19. package/controller/comms/messages/config/ChlorinatorMessage.ts +13 -4
  20. package/controller/comms/messages/config/CircuitGroupMessage.ts +6 -0
  21. package/controller/comms/messages/config/CoverMessage.ts +1 -0
  22. package/controller/comms/messages/config/EquipmentMessage.ts +4 -0
  23. package/controller/comms/messages/config/ExternalMessage.ts +44 -26
  24. package/controller/comms/messages/config/FeatureMessage.ts +8 -1
  25. package/controller/comms/messages/config/GeneralMessage.ts +8 -0
  26. package/controller/comms/messages/config/HeaterMessage.ts +15 -9
  27. package/controller/comms/messages/config/IntellichemMessage.ts +4 -1
  28. package/controller/comms/messages/config/OptionsMessage.ts +13 -1
  29. package/controller/comms/messages/config/PumpMessage.ts +4 -20
  30. package/controller/comms/messages/config/RemoteMessage.ts +4 -0
  31. package/controller/comms/messages/config/ScheduleMessage.ts +11 -0
  32. package/controller/comms/messages/config/SecurityMessage.ts +1 -0
  33. package/controller/comms/messages/config/ValveMessage.ts +13 -3
  34. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +2 -3
  35. package/controller/comms/messages/status/EquipmentStateMessage.ts +78 -24
  36. package/controller/comms/messages/status/HeaterStateMessage.ts +42 -9
  37. package/controller/comms/messages/status/IntelliChemStateMessage.ts +37 -26
  38. package/controller/nixie/Nixie.ts +18 -16
  39. package/controller/nixie/bodies/Body.ts +4 -1
  40. package/controller/nixie/chemistry/ChemController.ts +80 -77
  41. package/controller/nixie/chemistry/Chlorinator.ts +9 -8
  42. package/controller/nixie/circuits/Circuit.ts +55 -6
  43. package/controller/nixie/heaters/Heater.ts +192 -32
  44. package/controller/nixie/pumps/Pump.ts +146 -84
  45. package/controller/nixie/schedules/Schedule.ts +3 -2
  46. package/controller/nixie/valves/Valve.ts +1 -1
  47. package/defaultConfig.json +32 -1
  48. package/issue_template.md +1 -1
  49. package/logger/DataLogger.ts +37 -22
  50. package/package.json +20 -18
  51. package/web/Server.ts +520 -29
  52. package/web/bindings/influxDB.json +96 -8
  53. package/web/bindings/mqtt.json +151 -40
  54. package/web/bindings/mqttAlt.json +114 -4
  55. package/web/interfaces/httpInterface.ts +2 -0
  56. package/web/interfaces/influxInterface.ts +36 -19
  57. package/web/interfaces/mqttInterface.ts +14 -3
  58. package/web/services/config/Config.ts +171 -44
  59. package/web/services/state/State.ts +49 -5
  60. package/web/services/state/StateSocket.ts +18 -1
@@ -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,15 @@ 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;
472
501
  emitEquipmentChange();
473
502
  get(bCopy?: boolean);
474
503
  showInFeatures?: boolean;
504
+ isActive?: boolean;
505
+ startDelay?: boolean;
506
+ stopDelay?: boolean;
475
507
  }
476
508
 
477
509
  interface IEqStateCreator<T> { ctor(data: any, name: string, parent?): T; }
@@ -571,6 +603,20 @@ class EqStateCollection<T> {
571
603
  if (typeof (data[name]) === 'undefined') data[name] = [];
572
604
  this.data = data[name];
573
605
  }
606
+ public static removeNullIds(data: any) {
607
+ if (typeof data !== 'undefined' && Array.isArray(data) && typeof data.length === 'number') {
608
+ for (let i = data.length - 1; i >= 0; i--) {
609
+ if (typeof data[i].id !== 'number') {
610
+ console.log(`Removing ${data[i].id}-${data[i].name}`);
611
+ data.splice(i, 1);
612
+ }
613
+ else if (typeof data[i].id === 'undefined' || isNaN(data[i].id)) {
614
+ console.log(`Removing isNaN ${data[i].id}-${data[i].name}`);
615
+ data.splice(i, 1);
616
+ }
617
+ }
618
+ }
619
+ }
574
620
  public getItemById(id: number, add?: boolean, data?: any): T {
575
621
  for (let i = 0; i < this.data.length; i++)
576
622
  if (typeof this.data[i].id !== 'undefined' && this.data[i].id === id) {
@@ -827,7 +873,10 @@ export class PumpStateCollection extends EqStateCollection<PumpState> {
827
873
  }
828
874
  public cleanupState() {
829
875
  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);
876
+ if (isNaN(this.data[i].id)) this.data.splice(i, 1);
877
+ else {
878
+ if (typeof sys.pumps.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
879
+ }
831
880
  }
832
881
  let cfg = sys.pumps.toArray();
833
882
  for (let i = 0; i < cfg.length; i++) {
@@ -842,6 +891,13 @@ export class PumpStateCollection extends EqStateCollection<PumpState> {
842
891
  }
843
892
  export class PumpState extends EqState {
844
893
  public dataName: string = 'pump';
894
+ public initData() {
895
+ if (typeof this.data.status === 'undefined') {
896
+ this.data.status = { name: 'ok', desc: 'Ok', val: 0 };
897
+ }
898
+ if (typeof this.data.pumpOnDelay === 'undefined') this.data.pumpOnDelay = false;
899
+ }
900
+ private _pumpOnDelayTimer: NodeJS.Timeout;
845
901
  private _threshold = 0.05;
846
902
  private exceedsThreshold(origVal: number, newVal: number) {
847
903
  return Math.abs((newVal - origVal) / origVal) > this._threshold;
@@ -875,7 +931,7 @@ export class PumpState extends EqState {
875
931
  // quick fix for #172
876
932
  if (this.status !== val) {
877
933
  if (sys.board.valueMaps.pumpTypes.getName(this.type) === 'vsf' && val === 0) {
878
- this.data.status = { name: 'ok', desc: 'Ok', val };
934
+ this.data.status = { name: 'ok', desc: 'Ok', val: 0 };
879
935
  }
880
936
  else this.data.status = sys.board.valueMaps.pumpStatus.transform(val);
881
937
  this.hasChanged = true;
@@ -901,6 +957,23 @@ export class PumpState extends EqState {
901
957
  }
902
958
  public get time(): number { return this.data.time; }
903
959
  public set time(val: number) { this.setDataVal('time', val, false); }
960
+ public get pumpOnDelay() { return this.data.pumpOnDelay; }
961
+ public set pumpOnDelay(val: boolean) {
962
+ if (val === false) {
963
+ if (typeof this._pumpOnDelayTimer !== 'undefined') clearTimeout(this._pumpOnDelayTimer);
964
+ this._pumpOnDelayTimer = undefined;
965
+ }
966
+ this.setDataVal('pumpOnDelay', val);
967
+ }
968
+ public setPumpOnDelayTimeout(delay: number) {
969
+ this.pumpOnDelay = true;
970
+ logger.info(`Pump ON Delay ${this.name} for ${delay / 1000} seconds`);
971
+ this._pumpOnDelayTimer = setTimeout(() => {
972
+ logger.info(`Pump ON Delay ${this.name} expired`);
973
+ this.pumpOnDelay = false;
974
+ }, delay);
975
+ }
976
+
904
977
  public getExtended() {
905
978
  let pump = this.get(true);
906
979
  let cpump = sys.pumps.getItemById(pump.id);
@@ -957,6 +1030,7 @@ export class ScheduleState extends EqState {
957
1030
  public get startDate(): Date { return this._startDate; }
958
1031
  public set startDate(val: Date) { this._startDate = val; this._saveStartDate(); }
959
1032
  private _saveStartDate() {
1033
+ if (typeof this._startDate === 'undefined') this._startDate = new Date();
960
1034
  this.startDate.setHours(0, 0, 0, 0);
961
1035
  this.setDataVal('startDate', Timestamp.toISOLocal(this.startDate));
962
1036
  }
@@ -1061,7 +1135,10 @@ export class CircuitGroupStateCollection extends EqStateCollection<CircuitGroupS
1061
1135
  }
1062
1136
  public cleanupState() {
1063
1137
  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);
1138
+ if (isNaN(this.data[i].id)) this.data.splice(i, 1);
1139
+ else {
1140
+ if (typeof sys.circuitGroups.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1141
+ }
1065
1142
  }
1066
1143
  let cfg = sys.circuitGroups.toArray();
1067
1144
  for (let i = 0; i < cfg.length; i++) {
@@ -1121,12 +1198,23 @@ export class CircuitGroupState extends EqState implements ICircuitGroupState, IC
1121
1198
  state._dirtyList.removeEqState(this);
1122
1199
  }
1123
1200
  }
1201
+ public get(bcopy?: boolean): any {
1202
+ let d = super.get(bcopy);
1203
+ let cg = sys.circuitGroups.getItemById(this.id);
1204
+ if (!cg.isActive) d.isActive = false;
1205
+ else d.isActive = undefined;
1206
+ return d;
1207
+ }
1208
+
1124
1209
  }
1125
1210
  export class LightGroupStateCollection extends EqStateCollection<LightGroupState> {
1126
1211
  public createItem(data: any): LightGroupState { return new LightGroupState(data); }
1127
1212
  public cleanupState() {
1128
1213
  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);
1214
+ if (isNaN(this.data[i].id)) this.data.splice(i, 1);
1215
+ else {
1216
+ if (typeof sys.lightGroups.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1217
+ }
1130
1218
  }
1131
1219
  let cfg = sys.lightGroups.toArray();
1132
1220
  for (let i = 0; i < cfg.length; i++) {
@@ -1205,10 +1293,24 @@ export class BodyTempStateCollection extends EqStateCollection<BodyTempState> {
1205
1293
  }
1206
1294
  return undefined;
1207
1295
  }
1296
+ public getBodyByCircuitId(circuitId: number) {
1297
+ let b = this.data.find(x => x.circuit === circuitId);
1298
+ if (typeof b === 'undefined') {
1299
+ let circ = sys.circuits.getItemById(circuitId);
1300
+ // Find our body by circuit function.
1301
+ let cfn = sys.board.valueMaps.circuitFunctions.get(circ.type);
1302
+ if (typeof cfn.body !== 'undefined') b = this.data.find(x => x.id === cfn.body);
1303
+ }
1304
+ return typeof b !== 'undefined' ? this.createItem(b) : undefined;
1305
+ }
1208
1306
  public cleanupState() {
1209
1307
  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);
1308
+ if (isNaN(this.data[i].id)) this.data.splice(i, 1);
1309
+ else {
1310
+ if (typeof sys.bodies.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1311
+ }
1211
1312
  }
1313
+ this.sortById();
1212
1314
  }
1213
1315
 
1214
1316
  }
@@ -1227,6 +1329,10 @@ export class BodyTempState extends EqState {
1227
1329
  public dataName = 'bodyTempState';
1228
1330
  public initData() {
1229
1331
  if (typeof this.data.heaterOptions === 'undefined') this.data.heaterOptions = { total: 0 };
1332
+ if (typeof this.data.isCovered === 'undefined') this.data.isCovered = false;
1333
+ if (typeof this.heaterCooldownDelay === 'undefined') this.data.heaterCooldownDelay = false;
1334
+ if (typeof this.data.startDelay === 'undefined') this.data.startDelay = false;
1335
+ if (typeof this.data.stopDelay === 'undefined') this.data.stopDelay = false;
1230
1336
  }
1231
1337
  public get id(): number { return this.data.id; }
1232
1338
  public set id(val: number) { this.setDataVal('id', val); }
@@ -1265,6 +1371,19 @@ export class BodyTempState extends EqState {
1265
1371
  public set coolSetpoint(val: number) { this.setDataVal('coolSetpoint', val); }
1266
1372
  public get isOn(): boolean { return this.data.isOn; }
1267
1373
  public set isOn(val: boolean) { this.setDataVal('isOn', val); }
1374
+ public get startDelay(): boolean { return this.data.startDelay; }
1375
+ public set startDelay(val: boolean) { this.setDataVal('startDelay', val); }
1376
+ public get stopDelay(): boolean { return this.data.stopDelay; }
1377
+ public set stopDelay(val: boolean) { this.setDataVal('stopDelay', val); }
1378
+
1379
+ public get isCovered(): boolean { return this.data.isCovered; }
1380
+ public set isCovered(val: boolean) { this.setDataVal('isCovered', val); }
1381
+ // RKS: Heater cooldown delays force the current valve and body configuration until the
1382
+ // heater cooldown expires. This occurs at the pool level but it is triggered by the heater attached
1383
+ // to the body. Unfortunately, I think we can only detect this condition in Nixie as there really isn't an
1384
+ // indicator with Pentair OCPs. This is triggered in NixieBoard and managed by the delayMgr.
1385
+ public get heaterCooldownDelay(): boolean { return this.data.heaterCooldownDelay; }
1386
+ public set heaterCooldownDelay(val: boolean) { this.setDataVal('heaterCooldownDelay', val); }
1268
1387
  public emitData(name: string, data: any) { webApp.emitToClients('body', this.data); }
1269
1388
  // RKS: This is a very interesting object because we have a varied object. Type safety rules should not apply
1270
1389
  // 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
@@ -1331,7 +1450,13 @@ export class HeaterStateCollection extends EqStateCollection<HeaterState> {
1331
1450
  public createItem(data: any): HeaterState { return new HeaterState(data); }
1332
1451
  public cleanupState() {
1333
1452
  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);
1453
+ if (isNaN(this.data[i].id)) {
1454
+ logger.info(`Removed Invalid Heater ${this.data[i].id}-${this.data[i].name}`);
1455
+ this.data.splice(i, 1);
1456
+ }
1457
+ else {
1458
+ if (typeof sys.heaters.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1459
+ }
1335
1460
  }
1336
1461
  let cfg = sys.heaters.toArray();
1337
1462
  for (let i = 0; i < cfg.length; i++) {
@@ -1344,16 +1469,36 @@ export class HeaterStateCollection extends EqStateCollection<HeaterState> {
1344
1469
  }
1345
1470
  export class HeaterState extends EqState {
1346
1471
  public dataName: string = 'heater';
1472
+ public initData() {
1473
+ if (typeof this.data.startupDelay === 'undefined') this.data.startupDelay = false;
1474
+ if (typeof this.data.shutdownDelay === 'undefined') this.data.shutdownDelay = false;
1475
+ }
1347
1476
  public get id(): number { return this.data.id; }
1348
1477
  public set id(val: number) { this.data.id = val; }
1349
1478
  public get name(): string { return this.data.name; }
1350
1479
  public set name(val: string) { this.setDataVal('name', val); }
1351
1480
  public get isOn(): boolean { return this.data.isOn; }
1352
- public set isOn(val: boolean) { this.setDataVal('isOn', val); }
1481
+ public set isOn(val: boolean) {
1482
+ if (val !== this.data.isOn) {
1483
+ if (val) this.startTime = new Timestamp();
1484
+ else this.endTime = new Timestamp();
1485
+ }
1486
+ this.setDataVal('isOn', val);
1487
+ }
1488
+ public get startTime(): Timestamp {
1489
+ if (typeof this.data.startTime === 'undefined') return undefined;
1490
+ return new Timestamp(this.data.startTime);
1491
+ }
1492
+ public set startTime(val: Timestamp) { typeof val !== 'undefined' ? this.setDataVal('startTime', Timestamp.toISOLocal(val.toDate())) : this.setDataVal('startTime', undefined); }
1493
+
1494
+ public get endTime(): Timestamp {
1495
+ if (typeof this.data.endTime === 'undefined') return undefined;
1496
+ return new Timestamp(this.data.endTime);
1497
+ }
1498
+ public set endTime(val: Timestamp) { typeof val !== 'undefined' ? this.setDataVal('endTime', Timestamp.toISOLocal(val.toDate())) : this.setDataVal('endTime', undefined); }
1499
+
1353
1500
  public get isCooling(): boolean { return this.data.isCooling; }
1354
1501
  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
1502
  public get type(): number | any { return typeof this.data.type !== 'undefined' ? this.data.type.val : 0; }
1358
1503
  public set type(val: number | any) {
1359
1504
  if (this.type !== val) {
@@ -1368,6 +1513,13 @@ export class HeaterState extends EqState {
1368
1513
  this.hasChanged = true;
1369
1514
  }
1370
1515
  }
1516
+ public get startupDelay(): boolean { return this.data.startupDelay; }
1517
+ public set startupDelay(val: boolean) { this.setDataVal('startupDelay', val); }
1518
+ public get shutdownDelay(): boolean { return this.data.shutdownDelay; }
1519
+ public set shutdownDelay(val: boolean) { this.setDataVal('shutdownDelay', val); }
1520
+ public get bodyId(): number { return this.data.bodyId || 0 }
1521
+ public set bodyId(val: number) { this.setDataVal('bodyId', val); }
1522
+
1371
1523
  }
1372
1524
  export class FeatureStateCollection extends EqStateCollection<FeatureState> {
1373
1525
  public createItem(data: any): FeatureState { return new FeatureState(data); }
@@ -1375,7 +1527,10 @@ export class FeatureStateCollection extends EqStateCollection<FeatureState> {
1375
1527
  public async toggleFeatureStateAsync(id: number) { return sys.board.features.toggleFeatureStateAsync(id); }
1376
1528
  public cleanupState() {
1377
1529
  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);
1530
+ if (isNaN(this.data[i].id)) this.data.splice(i, 1);
1531
+ else {
1532
+ if (typeof sys.features.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1533
+ }
1379
1534
  }
1380
1535
  let cfg = sys.features.toArray();
1381
1536
  for (let i = 0; i < cfg.length; i++) {
@@ -1391,6 +1546,9 @@ export class FeatureStateCollection extends EqStateCollection<FeatureState> {
1391
1546
 
1392
1547
  export class FeatureState extends EqState implements ICircuitState {
1393
1548
  public dataName: string = 'feature';
1549
+ public initData() {
1550
+ if (typeof this.data.freezeProtect === 'undefined') this.data.freezeProtect = false;
1551
+ }
1394
1552
  public get id(): number { return this.data.id; }
1395
1553
  public set id(val: number) { this.data.id = val; }
1396
1554
  public get name(): string { return this.data.name; }
@@ -1413,6 +1571,12 @@ export class FeatureState extends EqState implements ICircuitState {
1413
1571
  return new Timestamp(this.data.endTime);
1414
1572
  }
1415
1573
  public set endTime(val: Timestamp) { typeof val !== 'undefined' ? this.setDataVal('endTime', Timestamp.toISOLocal(val.toDate())) : this.setDataVal('endTime', undefined); }
1574
+ // 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
1575
+ // need to know (so we can shut it off) if we have done this.
1576
+ public get freezeProtect(): boolean { return this.data.freezeProtect; }
1577
+ public set freezeProtect(val: boolean) { this.setDataVal('freezeProtect', val); }
1578
+ public get isActive(): boolean { return this.data.isActive; }
1579
+ public set isActive(val: boolean) { this.setDataVal('isActive', val); }
1416
1580
  }
1417
1581
  export class VirtualCircuitState extends EqState implements ICircuitState {
1418
1582
  public dataName: string = 'virtualCircuit';
@@ -1436,6 +1600,8 @@ export class VirtualCircuitState extends EqState implements ICircuitState {
1436
1600
  return new Timestamp(this.data.endTime);
1437
1601
  }
1438
1602
  public set endTime(val: Timestamp) { typeof val !== 'undefined' ? this.setDataVal('endTime', Timestamp.toISOLocal(val.toDate())) : this.setDataVal('endTime', undefined); }
1603
+ public get isActive(): boolean { return this.data.isActive; }
1604
+ public set isActive(val: boolean) { this.setDataVal('isActive', val); }
1439
1605
  }
1440
1606
  export class VirtualCircuitStateCollection extends EqStateCollection<VirtualCircuitState> {
1441
1607
  public createItem(data: any): VirtualCircuitState { return new VirtualCircuitState(data); }
@@ -1460,7 +1626,10 @@ export class CircuitStateCollection extends EqStateCollection<CircuitState> {
1460
1626
  }
1461
1627
  public cleanupState() {
1462
1628
  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);
1629
+ if (isNaN(this.data[i].id)) this.data.splice(i, 1);
1630
+ else {
1631
+ if (typeof sys.circuits.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1632
+ }
1464
1633
  }
1465
1634
  let cfg = sys.circuits.toArray();
1466
1635
  for (let i = 0; i < cfg.length; i++) {
@@ -1476,6 +1645,9 @@ export class CircuitStateCollection extends EqStateCollection<CircuitState> {
1476
1645
  }
1477
1646
  export class CircuitState extends EqState implements ICircuitState {
1478
1647
  public dataName = 'circuit';
1648
+ public initData() {
1649
+ if (typeof this.data.freezeProtect === 'undefined') this.data.freezeProtect = false;
1650
+ }
1479
1651
  public get id(): number { return this.data.id; }
1480
1652
  public set id(val: number) { this.data.id = val; }
1481
1653
  public get name(): string { return this.data.name; }
@@ -1485,7 +1657,11 @@ export class CircuitState extends EqState implements ICircuitState {
1485
1657
  public get showInFeatures(): boolean { return this.data.showInFeatures; }
1486
1658
  public set showInFeatures(val: boolean) { this.setDataVal('showInFeatures', val); }
1487
1659
  public get isOn(): boolean { return this.data.isOn; }
1488
- public set isOn(val: boolean) { this.setDataVal('isOn', val); }
1660
+ public set isOn(val: boolean) {
1661
+ if (val && !this.data.isOn) this.startTime = new Timestamp();
1662
+ else if (!val) this.startTime = undefined;
1663
+ this.setDataVal('isOn', val);
1664
+ }
1489
1665
  public get type() { return typeof (this.data.type) !== 'undefined' ? this.data.type.val : -1; }
1490
1666
  public set type(val: number) {
1491
1667
  if (this.type !== val) {
@@ -1493,10 +1669,12 @@ export class CircuitState extends EqState implements ICircuitState {
1493
1669
  this.hasChanged = true;
1494
1670
  }
1495
1671
  }
1496
- public get lightingTheme(): number { return typeof (this.data.lightingTheme) !== 'undefined' ? this.data.lightingTheme.val : 255; }
1672
+ public get lightingTheme(): number { return typeof this.data.lightingTheme !== 'undefined' ? this.data.lightingTheme.val : 255; }
1497
1673
  public set lightingTheme(val: number) {
1498
1674
  if (this.lightingTheme !== val) {
1499
- this.data.lightingTheme = sys.board.valueMaps.lightThemes.transform(val);
1675
+ // Force this to undefined when we are a circuit without a theme.
1676
+ if (typeof val === 'undefined') this.data.lightingTheme = undefined;
1677
+ else this.data.lightingTheme = sys.board.valueMaps.lightThemes.transform(val);
1500
1678
  this.hasChanged = true;
1501
1679
  }
1502
1680
  }
@@ -1509,17 +1687,42 @@ export class CircuitState extends EqState implements ICircuitState {
1509
1687
  this.hasChanged = true;
1510
1688
  }
1511
1689
  }
1690
+ public get startTime(): Timestamp {
1691
+ if (typeof this.data.startTime === 'undefined') return undefined;
1692
+ return new Timestamp(this.data.startTime);
1693
+ }
1694
+ public set startTime(val: Timestamp) { typeof val !== 'undefined' ? this.setDataVal('startTime', Timestamp.toISOLocal(val.toDate())) : this.setDataVal('startTime', undefined); }
1695
+
1512
1696
  public get endTime(): Timestamp {
1513
1697
  if (typeof this.data.endTime === 'undefined') return undefined;
1514
1698
  return new Timestamp(this.data.endTime);
1515
1699
  }
1516
1700
  public set endTime(val: Timestamp) { typeof val !== 'undefined' ? this.setDataVal('endTime', Timestamp.toISOLocal(val.toDate())) : this.setDataVal('endTime', undefined); }
1701
+ // 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
1702
+ // need to know (so we can shut it off) if we have done this.
1703
+ public get freezeProtect(): boolean { return this.data.freezeProtect; }
1704
+ public set freezeProtect(val: boolean) { this.setDataVal('freezeProtect', val); }
1705
+ public get isActive(): boolean { return this.data.isActive; }
1706
+ public set isActive(val: boolean) { this.setDataVal('isActive', val); }
1707
+ // The properties below are for delays and lockouts. Manual or scheduled
1708
+ // actions cannot be performed when the flags below are set.
1709
+ public get startDelay(): boolean { return this.data.startDelay; }
1710
+ public set startDelay(val: boolean) { this.setDataVal('startDelay', val); }
1711
+ public get stopDelay(): boolean { return this.data.stopDelay; }
1712
+ public set stopDelay(val: boolean) { this.setDataVal('stopDelay', val); }
1713
+ public get lockoutOn(): boolean { return this.data.lockoutOn; }
1714
+ public set lockoutOn(val: boolean) { this.setDataVal('lockoutOn', val); }
1715
+ public get lockoutOff(): boolean { return this.data.lockoutOff; }
1716
+ public set lockoutOff(val: boolean) { this.setDataVal('lockoutOff', val); }
1517
1717
  }
1518
1718
  export class ValveStateCollection extends EqStateCollection<ValveState> {
1519
1719
  public createItem(data: any): ValveState { return new ValveState(data); }
1520
1720
  public cleanupState() {
1521
1721
  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);
1722
+ if (isNaN(this.data[i].id)) this.data.splice(i, 1);
1723
+ else {
1724
+ if (typeof sys.valves.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1725
+ }
1523
1726
  }
1524
1727
  let cfg = sys.valves.toArray();
1525
1728
  for (let i = 0; i < cfg.length; i++) {
@@ -1558,7 +1761,7 @@ export class ValveState extends EqState {
1558
1761
  if (valve.circuit !== 256) vstate.circuit = state.circuits.getInterfaceById(valve.circuit).get(true);
1559
1762
  vstate.isIntake = utils.makeBool(valve.isIntake);
1560
1763
  vstate.isReturn = utils.makeBool(valve.isReturn);
1561
- vstate.isVirtual = utils.makeBool(valve.isVirtual);
1764
+ // vstate.isVirtual = utils.makeBool(valve.isVirtual);
1562
1765
  vstate.isActive = utils.makeBool(valve.isActive);
1563
1766
  vstate.pinId = valve.pinId;
1564
1767
  return vstate;
@@ -1576,12 +1779,15 @@ export class CoverStateCollection extends EqStateCollection<CoverState> {
1576
1779
  }
1577
1780
  export class CoverState extends EqState {
1578
1781
  public dataName: string = 'cover';
1782
+ public initData() {
1783
+ if (typeof this.data.isClosed === 'undefined') this.data.isClosed = false;
1784
+ }
1579
1785
  public get id(): number { return this.data.id; }
1580
1786
  public set id(val: number) { this.data.id = val; }
1581
1787
  public get name(): string { return this.data.name; }
1582
1788
  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); }
1789
+ public get isClosed(): boolean { return this.data.isClosed; }
1790
+ public set isClosed(val: boolean) { this.setDataVal('isClosed', val); }
1585
1791
  }
1586
1792
  export class ChlorinatorStateCollection extends EqStateCollection<ChlorinatorState> {
1587
1793
  public createItem(data: any): ChlorinatorState { return new ChlorinatorState(data); }
@@ -1589,7 +1795,10 @@ export class ChlorinatorStateCollection extends EqStateCollection<ChlorinatorSta
1589
1795
  public lastDispatchSuperChlor: number = 0;
1590
1796
  public cleanupState() {
1591
1797
  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);
1798
+ if (isNaN(this.data[i].id)) this.data.splice(i, 1);
1799
+ else {
1800
+ if (typeof sys.chlorinators.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1801
+ }
1593
1802
  }
1594
1803
  let cfg = sys.chlorinators.toArray();
1595
1804
  for (let i = 0; i < cfg.length; i++) {
@@ -1642,16 +1851,6 @@ export class ChlorinatorState extends EqState {
1642
1851
  this.hasChanged = true;
1643
1852
  }
1644
1853
  }
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
1854
  public get type(): number { return typeof (this.data.type) !== 'undefined' ? this.data.type.val : -1; }
1656
1855
  public set type(val: number) {
1657
1856
  if (this.type !== val) {
@@ -1738,12 +1937,21 @@ export class ChlorinatorState extends EqState {
1738
1937
  else
1739
1938
  this.setDataVal('superChlor', false);
1740
1939
  }
1940
+ public getExtended(): any {
1941
+ let schlor = this.get(true);
1942
+ let chlor = sys.chlorinators.getItemById(this.id, false);
1943
+ schlor.lockSetpoints = chlor.disabled || chlor.isDosing;
1944
+ return schlor;
1945
+ }
1741
1946
  }
1742
1947
  export class ChemControllerStateCollection extends EqStateCollection<ChemControllerState> {
1743
1948
  public createItem(data: any): ChemControllerState { return new ChemControllerState(data); }
1744
1949
  public cleanupState() {
1745
1950
  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);
1951
+ if (isNaN(this.data[i].id)) this.data.splice(i, 1);
1952
+ else {
1953
+ if (typeof sys.chemControllers.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
1954
+ }
1747
1955
  }
1748
1956
  // Make sure we have at least the items that exist in the config.
1749
1957
  let cfg = sys.chemControllers.toArray();
@@ -1783,7 +1991,7 @@ export class ChemControllerState extends EqState {
1783
1991
  //var chemControllerState = {
1784
1992
  // lastComm: 'number', // The unix time the chem controller sent its status.
1785
1993
  // id: 'number', // Id of the chemController.
1786
- // type: 'valueMap', // intellichem, homegrown, rem.
1994
+ // type: 'valueMap', // intellichem, rem.
1787
1995
  // address: 'number', // Assigned address if IntelliChem.
1788
1996
  // name: 'string', // Name assigned to the controller.
1789
1997
  // status: 'valueMap', // ok, nocomms, setupError
@@ -2006,14 +2214,15 @@ export class ChemControllerState extends EqState {
2006
2214
  export class ChemicalState extends ChildEqState {
2007
2215
  public initData() {
2008
2216
  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 };
2217
+ if (typeof this.data.tank === 'undefined') this.data.tank = { capacity: 0, level: 0, units: 0 };
2218
+ if (typeof this.data.pump === 'undefined') this.data.pump = { isDosing: false };
2010
2219
  if (typeof this.data.dosingTimeRemaining === 'undefined') this.data.dosingTimeRemaining = 0;
2011
2220
  if (typeof this.data.delayTimeRemaining === 'undefined') this.data.delayTimeRemaining = 0;
2012
2221
  if (typeof this.data.dosingVolumeRemaining === 'undefined') this.data.dosingVolumeRemaining = 0;
2013
2222
  if (typeof this.data.doseVolume === 'undefined') this.data.doseVolume = 0;
2014
2223
  if (typeof this.data.doseTime === 'undefined') this.data.doseTime = 0;
2015
2224
  if (typeof this.data.lockout === 'undefined') this.data.lockout = false;
2016
- if (typeof this.data.level == 'undefined') this.data.level = 0;
2225
+ if (typeof this.data.level === 'undefined') this.data.level = 0;
2017
2226
  if (typeof this.data.mixTimeRemaining === 'undefined') this.data.mixTimeRemaining = 0;
2018
2227
  if (typeof this.data.dailyLimitReached === 'undefined') this.data.dailyLimitReached = false;
2019
2228
  if (typeof this.data.manualDosing === 'undefined') this.data.manualDosing = false;
@@ -2021,7 +2230,6 @@ export class ChemicalState extends ChildEqState {
2021
2230
  if (typeof this.data.flowDelay === 'undefined') this.data.flowDelay = false;
2022
2231
  if (typeof this.data.dosingStatus === 'undefined') this.dosingStatus = 2;
2023
2232
  if (typeof this.data.enabled === 'undefined') this.data.enabled = true;
2024
- if (typeof this.data.level === 'undefined') this.data.level = 0;
2025
2233
  }
2026
2234
  public getConfig(): Chemical { return; }
2027
2235
  public calcDoseHistory(): number {
@@ -2185,7 +2393,7 @@ export class ChemicalState extends ChildEqState {
2185
2393
  }
2186
2394
  export class ChemicalPhState extends ChemicalState {
2187
2395
  public initData() {
2188
- if (typeof this.data.chemType === 'undefined') this.data.chemType === 'acid';
2396
+ // if (typeof this.data.chemType === 'undefined') this.data.chemType === 'acid'; // RSG 10-23-21 - Only a getter; don't need to set this.
2189
2397
  super.initData();
2190
2398
  }
2191
2399
  public getConfig() {
@@ -2390,14 +2598,16 @@ export class ChemicalDoseState extends DataLoggerEntry {
2390
2598
  public _isManual: boolean;
2391
2599
 
2392
2600
  constructor(entry?: string | object) {
2393
- super(entry);
2601
+ super();
2602
+ if (typeof entry === 'object') entry = JSON.stringify(entry);
2603
+ if (typeof entry === 'string') this.parse(entry);
2394
2604
  // Javascript is idiotic in that the initialization of variables
2395
2605
  // do not happen before the assignment so some of the values can be undefined.
2396
2606
  if (typeof this.volumeDosed === 'undefined' || !this.volumeDosed) this.volumeDosed = 0;
2397
2607
  if (typeof this.volume === 'undefined' || !this.volume) this.volume = 0;
2398
2608
  if (typeof this._isManual === 'undefined') this._isManual = this.method === 'manual';
2399
2609
  if (typeof this.timeDosed === 'undefined' || !this.timeDosed) this.timeDosed = 0;
2400
- if (typeof this._timeDosed === 'undefined') this.timeDosed * 1000;
2610
+ if (typeof this._timeDosed === 'undefined') this._timeDosed = this.timeDosed * 1000;
2401
2611
  if (typeof this.time === 'undefined' || !this.time) this.time = 0;
2402
2612
  }
2403
2613
  public id: number;
@@ -2414,10 +2624,40 @@ export class ChemicalDoseState extends DataLoggerEntry {
2414
2624
  public time: number;
2415
2625
  public timeDosed: number;
2416
2626
 
2417
- public createInstance(entry?: string): ChemicalDoseState { return new ChemicalDoseState(entry); }
2627
+ public static createInstance(entry?: string): ChemicalDoseState { return new ChemicalDoseState(entry); }
2418
2628
  public save() { DataLogger.writeEnd(`chemDosage_${this.chem}.log`, this); }
2419
2629
  public get timeRemaining(): number { return Math.floor(Math.max(0, this.time - (this._timeDosed / 1000))); }
2420
2630
  public get volumeRemaining(): number { return Math.max(0, this.volume - this.volumeDosed); }
2631
+ public parse(entry: string) {
2632
+ // let obj = typeof entry !== 'undefined' ? JSON.parse(entry, this.dateParser) : {};
2633
+ let obj = typeof entry !== 'undefined' ? JSON.parse(entry) : {};
2634
+ for (const prop in obj) {obj[prop] = this.dateParser(prop, obj[prop])}
2635
+ if (typeof obj.setpoint !== 'undefined') this.setpoint = obj.setpoint;
2636
+ if (typeof obj.method !== 'undefined') this.method = obj.method;
2637
+ if (typeof obj.start !== 'undefined') this.start = obj.start;
2638
+ if (typeof obj.end !== 'undefined') this.end = obj.end;
2639
+ if (typeof obj.chem !== 'undefined') this.chem = obj.chem;
2640
+ if (typeof obj.demand !== 'undefined') this.demand = obj.demand;
2641
+ if (typeof obj.id !== 'undefined') this.id = obj.id;
2642
+ if (typeof obj.level !== 'undefined') this.level = obj.level;
2643
+ if (typeof obj.volume !== 'undefined') this.volume = obj.volume;
2644
+ if (typeof obj.status !== 'undefined') this.status = obj.status;
2645
+ if (typeof obj.volumeDosed !== 'undefined') this.volumeDosed = obj.volumeDosed;
2646
+ if (typeof obj.time !== 'undefined') this.time = obj.time;
2647
+ if (typeof obj.timeDosed !== 'undefined') this.timeDosed = obj.timeDosed;
2648
+ // this.setProperties(obj);
2649
+ }
2650
+ protected setProperties(data: any) {
2651
+ let op = Object.getOwnPropertyNames(Object.getPrototypeOf(this));
2652
+ for (let i in op) {
2653
+ let prop = op[i];
2654
+ if (typeof this[prop] === 'function') continue;
2655
+ if (typeof data[prop] !== 'undefined') {
2656
+ if (typeof this[prop] === null || typeof data[prop] === null) continue;
2657
+ this[prop] = data[prop];
2658
+ }
2659
+ }
2660
+ }
2421
2661
  }
2422
2662
 
2423
2663
  export class ChemicalDemandState extends ChildEqState {
@@ -2665,15 +2905,35 @@ export class FilterState extends EqState {
2665
2905
  this.hasChanged = true;
2666
2906
  }
2667
2907
  }
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); }
2908
+ public get pressureUnits(): number { return this.data.pressureUnits; }
2909
+ public set pressureUnits(val: number) {
2910
+ if (this.pressureUnits !== val) {
2911
+ this.setDataVal('pressureUnits', sys.board.valueMaps.pressureUnits.transform(val));
2912
+ }
2913
+ }
2914
+ public get pressure(): number { return this.data.pressure; }
2915
+ public set pressure(val: number) { this.setDataVal('pressure', val); }
2916
+ public get refPressure(): number { return this.data.refPressure; }
2917
+ public set refPressure(val: number) {
2918
+ if (val !== this.refPressure) {
2919
+ this.setDataVal('refPressure', val);
2920
+ this.calcCleanPercentage();
2921
+ }
2922
+ else { this.setDataVal('refPressure', val); }
2923
+ }
2924
+ public get cleanPercentage(): number { return this.data.cleanPercentage; }
2925
+ public set cleanPercentage(val: number) { this.setDataVal('cleanPercentage', val); }
2672
2926
  public get lastCleanDate(): Timestamp { return this.data.lastCleanDate; }
2673
2927
  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
2928
  public get isOn(): boolean { return utils.makeBool(this.data.isOn); }
2677
2929
  public set isOn(val: boolean) { this.setDataVal('isOn', val); }
2930
+ public calcCleanPercentage() {
2931
+ if (typeof this.refPressure === 'undefined') return;
2932
+ let filter = sys.filters.find(elem => elem.id == this.id);
2933
+ // 8 to 10
2934
+ let cp = filter.cleanPressure || 0;
2935
+ let dp = filter.dirtyPressure || 1;
2936
+ this.cleanPercentage = (cp - dp != 0) ? Math.round(Math.max(0, (1 - (this.refPressure - cp) / (dp - cp)) * 100) * 100)/100 : 0;
2937
+ }
2678
2938
  }
2679
2939
  export var state = new State();