nodejs-poolcontroller 7.7.0 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/.eslintrc.json +26 -35
  2. package/Changelog +22 -0
  3. package/README.md +7 -3
  4. package/anslq25/MessagesMock.ts +218 -0
  5. package/anslq25/boards/MockBoardFactory.ts +50 -0
  6. package/anslq25/boards/MockEasyTouchBoard.ts +696 -0
  7. package/anslq25/boards/MockSystemBoard.ts +217 -0
  8. package/anslq25/chemistry/MockChlorinator.ts +75 -0
  9. package/anslq25/pumps/MockPump.ts +84 -0
  10. package/app.ts +10 -14
  11. package/config/Config.ts +13 -9
  12. package/config/VersionCheck.ts +6 -2
  13. package/controller/Constants.ts +58 -25
  14. package/controller/Equipment.ts +224 -41
  15. package/controller/Errors.ts +2 -1
  16. package/controller/Lockouts.ts +34 -2
  17. package/controller/State.ts +491 -48
  18. package/controller/boards/AquaLinkBoard.ts +6 -3
  19. package/controller/boards/BoardFactory.ts +5 -1
  20. package/controller/boards/EasyTouchBoard.ts +1971 -1751
  21. package/controller/boards/IntelliCenterBoard.ts +1311 -1688
  22. package/controller/boards/IntelliComBoard.ts +7 -1
  23. package/controller/boards/IntelliTouchBoard.ts +153 -42
  24. package/controller/boards/NixieBoard.ts +209 -66
  25. package/controller/boards/SunTouchBoard.ts +393 -0
  26. package/controller/boards/SystemBoard.ts +1862 -1543
  27. package/controller/comms/Comms.ts +539 -138
  28. package/controller/comms/ScreenLogic.ts +1663 -0
  29. package/controller/comms/messages/Messages.ts +242 -60
  30. package/controller/comms/messages/config/ChlorinatorMessage.ts +4 -3
  31. package/controller/comms/messages/config/CircuitGroupMessage.ts +5 -2
  32. package/controller/comms/messages/config/CircuitMessage.ts +81 -13
  33. package/controller/comms/messages/config/ConfigMessage.ts +3 -1
  34. package/controller/comms/messages/config/CoverMessage.ts +2 -1
  35. package/controller/comms/messages/config/CustomNameMessage.ts +2 -1
  36. package/controller/comms/messages/config/EquipmentMessage.ts +5 -1
  37. package/controller/comms/messages/config/ExternalMessage.ts +33 -3
  38. package/controller/comms/messages/config/FeatureMessage.ts +2 -1
  39. package/controller/comms/messages/config/GeneralMessage.ts +2 -1
  40. package/controller/comms/messages/config/HeaterMessage.ts +3 -1
  41. package/controller/comms/messages/config/IntellichemMessage.ts +2 -1
  42. package/controller/comms/messages/config/OptionsMessage.ts +12 -6
  43. package/controller/comms/messages/config/PumpMessage.ts +9 -12
  44. package/controller/comms/messages/config/RemoteMessage.ts +80 -13
  45. package/controller/comms/messages/config/ScheduleMessage.ts +43 -3
  46. package/controller/comms/messages/config/SecurityMessage.ts +2 -1
  47. package/controller/comms/messages/config/ValveMessage.ts +43 -26
  48. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +8 -7
  49. package/controller/comms/messages/status/EquipmentStateMessage.ts +93 -20
  50. package/controller/comms/messages/status/HeaterStateMessage.ts +24 -5
  51. package/controller/comms/messages/status/IntelliChemStateMessage.ts +7 -4
  52. package/controller/comms/messages/status/IntelliValveStateMessage.ts +2 -1
  53. package/controller/comms/messages/status/PumpStateMessage.ts +72 -4
  54. package/controller/comms/messages/status/VersionMessage.ts +2 -1
  55. package/controller/nixie/Nixie.ts +15 -4
  56. package/controller/nixie/NixieEquipment.ts +1 -0
  57. package/controller/nixie/chemistry/ChemController.ts +300 -129
  58. package/controller/nixie/chemistry/ChemDoser.ts +806 -0
  59. package/controller/nixie/chemistry/Chlorinator.ts +133 -129
  60. package/controller/nixie/circuits/Circuit.ts +171 -30
  61. package/controller/nixie/heaters/Heater.ts +337 -173
  62. package/controller/nixie/pumps/Pump.ts +264 -236
  63. package/controller/nixie/schedules/Schedule.ts +9 -3
  64. package/defaultConfig.json +45 -5
  65. package/logger/Logger.ts +38 -9
  66. package/package.json +13 -9
  67. package/web/Server.ts +235 -122
  68. package/web/bindings/aqualinkD.json +114 -59
  69. package/web/bindings/homeassistant.json +437 -0
  70. package/web/bindings/influxDB.json +15 -0
  71. package/web/bindings/mqtt.json +28 -9
  72. package/web/bindings/mqttAlt.json +15 -0
  73. package/web/interfaces/baseInterface.ts +58 -7
  74. package/web/interfaces/httpInterface.ts +5 -2
  75. package/web/interfaces/influxInterface.ts +9 -2
  76. package/web/interfaces/mqttInterface.ts +234 -74
  77. package/web/interfaces/ruleInterface.ts +87 -0
  78. package/web/services/config/Config.ts +140 -33
  79. package/web/services/config/ConfigSocket.ts +2 -1
  80. package/web/services/state/State.ts +144 -3
  81. package/web/services/state/StateSocket.ts +65 -14
  82. package/web/services/utilities/Utilities.ts +189 -1
@@ -1,5 +1,6 @@
1
1
  /* nodejs-poolController. An application to control pool equipment.
2
- Copyright (C) 2016, 2017, 2018, 2019, 2020. Russell Goldin, tagyoureit. russ.goldin@gmail.com
2
+ Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
3
+ Russell Goldin, tagyoureit. russ.goldin@gmail.com
3
4
 
4
5
  This program is free software: you can redistribute it and/or modify
5
6
  it under the terms of the GNU Affero General Public License as
@@ -22,10 +23,9 @@ import * as util from 'util';
22
23
  import { logger } from '../logger/Logger';
23
24
  import { webApp } from '../web/Server';
24
25
  import { ControllerType, Timestamp, utils, Heliotrope } from './Constants';
25
- import { sys, Chemical, ChemController } from './Equipment';
26
+ import { sys, Chemical, ChemController, ChemicalTank, ChemicalPump } from './Equipment';
26
27
  import { versionCheck } from '../config/VersionCheck';
27
- import { EquipmentStateMessage } from './comms/messages/status/EquipmentStateMessage';
28
- import { DataLogger, DataLoggerEntry, IDataLoggerEntry } from '../logger/DataLogger';
28
+ import { DataLogger, DataLoggerEntry } from '../logger/DataLogger';
29
29
  import { delayMgr } from './Lockouts';
30
30
 
31
31
  export class State implements IState {
@@ -99,7 +99,7 @@ export class State implements IState {
99
99
  lines = buff.toString().split('\n');
100
100
  }
101
101
  return lines;
102
- } catch (err) { logger.error(err); }
102
+ } catch (err) { logger.error(`Error reading log file ${logFile}: ${err.message}`); }
103
103
  }
104
104
  public async logData(logFile: string, data: any) {
105
105
  try {
@@ -116,7 +116,7 @@ export class State implements IState {
116
116
  else
117
117
  lines.unshift(data.toString());
118
118
  fs.writeFileSync(logPath, lines.join('\n'));
119
- } catch (err) { logger.error(err); }
119
+ } catch (err) { logger.error(`Error reading or writing logData ${logFile}: ${err.message}`); }
120
120
  }
121
121
  public getState(section?: string): any {
122
122
  // todo: getState('time') returns an array of chars. Needs no be fixed.
@@ -140,6 +140,7 @@ export class State implements IState {
140
140
  _state.filters = this.filters.getExtended();
141
141
  _state.schedules = this.schedules.getExtended();
142
142
  _state.chemControllers = this.chemControllers.getExtended();
143
+ _state.chemDosers = this.chemDosers.getExtended();
143
144
  _state.delays = delayMgr.serialize();
144
145
  return _state;
145
146
  }
@@ -160,7 +161,7 @@ export class State implements IState {
160
161
  try {
161
162
  if (this._timerDirty) clearTimeout(this._timerDirty);
162
163
  this.persist();
163
- if (sys.controllerType === ControllerType.Virtual) {
164
+ /* if (sys.controllerType === ControllerType.Virtual) {
164
165
  for (let i = 0; i < state.temps.bodies.length; i++) {
165
166
  state.temps.bodies.getItemByIndex(i).isOn = false;
166
167
  }
@@ -170,7 +171,7 @@ export class State implements IState {
170
171
  for (let i = 0; i < state.features.length; i++) {
171
172
  state.features.getItemByIndex(i).isOn = false;
172
173
  }
173
- }
174
+ } */
174
175
  logger.info('State process shut down');
175
176
  } catch (err) { logger.error(`Error shutting down state process: ${err.message}`); }
176
177
  }
@@ -265,6 +266,9 @@ export class State implements IState {
265
266
  for (let i = 0; i < state.chemControllers.length; i++) {
266
267
  state.chemControllers.getItemByIndex(i).hasChanged = true;
267
268
  }
269
+ for (let i = 0; i < state.chemDosers.length; i++) {
270
+ state.chemDosers.getItemByIndex(i).hasChanged = true;
271
+ }
268
272
  state.emitEquipmentChanges();
269
273
  }
270
274
  public emitEquipmentChanges() {
@@ -341,6 +345,7 @@ export class State implements IState {
341
345
  public get isInitialized(): boolean { return typeof (this.data.status) !== 'undefined' && this.data.status.val !== 0; }
342
346
  public init() {
343
347
  console.log(`Init state for Pool Controller`);
348
+
344
349
  var sdata = this.loadFile(this.statePath, {});
345
350
  sdata = extend(true, { mode: { val: -1 }, temps: { units: { val: 0, name: 'F', desc: 'Fahrenheit' } } }, sdata);
346
351
  if (typeof sdata.temps !== 'undefined' && typeof sdata.temps.bodies !== 'undefined') {
@@ -358,6 +363,7 @@ export class State implements IState {
358
363
  EqStateCollection.removeNullIds(sdata.lightGroups);
359
364
  EqStateCollection.removeNullIds(sdata.remotes);
360
365
  EqStateCollection.removeNullIds(sdata.chemControllers);
366
+ EqStateCollection.removeNullIds(sdata.chemDosers);
361
367
  EqStateCollection.removeNullIds(sdata.filters);
362
368
  var self = this;
363
369
  let pnlTime = typeof sdata.time !== 'undefined' ? new Date(sdata.time) : new Date();
@@ -365,6 +371,9 @@ export class State implements IState {
365
371
  this._dt = new Timestamp(pnlTime);
366
372
  this._dt.milliseconds = 0;
367
373
  this.data = sdata;
374
+ this.equipment = new EquipmentState(this.data, 'equipment');
375
+ this.equipment.messages.clear();
376
+
368
377
  //this.onchange(state, function () { self.dirty = true; });
369
378
  this._dt.emitter.on('change', function () {
370
379
  self.data.time = self._dt.format();
@@ -378,7 +387,6 @@ export class State implements IState {
378
387
  versionCheck.checkGitRemote();
379
388
  });
380
389
  this.status = 0; // Initializing
381
- this.equipment = new EquipmentState(this.data, 'equipment');
382
390
  this.equipment.controllerType = this._controllerType;
383
391
  this.temps = new TemperatureState(this.data, 'temps');
384
392
  this.pumps = new PumpStateCollection(this.data, 'pumps');
@@ -392,6 +400,7 @@ export class State implements IState {
392
400
  this.lightGroups = new LightGroupStateCollection(this.data, 'lightGroups');
393
401
  this.virtualCircuits = new VirtualCircuitStateCollection(this.data, 'virtualCircuits');
394
402
  this.chemControllers = new ChemControllerStateCollection(this.data, 'chemControllers');
403
+ this.chemDosers = new ChemDoserStateCollection(this.data, 'chemDosers');
395
404
  this.covers = new CoverStateCollection(this.data, 'covers');
396
405
  this.filters = new FilterStateCollection(this.data, 'filters');
397
406
  this.comms = new CommsState();
@@ -433,6 +442,7 @@ export class State implements IState {
433
442
  public covers: CoverStateCollection;
434
443
  public filters: FilterStateCollection;
435
444
  public chemControllers: ChemControllerStateCollection;
445
+ public chemDosers: ChemDoserStateCollection;
436
446
  public comms: CommsState;
437
447
  public appVersion: AppVersionState;
438
448
 
@@ -641,6 +651,10 @@ class EqStateCollection<T> {
641
651
  }
642
652
  return rem;
643
653
  }
654
+ public removeItemByIndex(ndx: number) {
655
+ return this.data.splice(ndx, 1);
656
+ }
657
+
644
658
  public createItem(data: any): T { return new EqState(data) as unknown as T; }
645
659
  public clear() { this.data.length = 0; }
646
660
  public get length(): number { return typeof (this.data) !== 'undefined' ? this.data.length : 0; }
@@ -732,6 +746,8 @@ export class EquipmentState extends EqState {
732
746
  public set name(val: string) { this.setDataVal('name', val); }
733
747
  public get model(): string { return this.data.model; }
734
748
  public set model(val: string) { this.setDataVal('model', val); }
749
+ public get single(): boolean { return this.data.single; }
750
+ public set single(val: boolean) { this.setDataVal('single', val); }
735
751
  public get shared(): boolean { return this.data.shared; }
736
752
  public set shared(val: boolean) { this.setDataVal('shared', val); }
737
753
  public get dual(): boolean { return this.data.dual; }
@@ -807,12 +823,14 @@ export class EquipmentMessages extends EqStateCollection<EquipmentMessage> {
807
823
  rem = this.data.splice(i, 1);
808
824
  }
809
825
  }
826
+ if (typeof rem !== 'undefined') webApp.emitToClients('sysmessages', this.get(true));
810
827
  return typeof rem !== 'undefined' ? new EquipmentMessage(rem, undefined, undefined) : undefined;
811
828
  }
812
829
  // For lack of a better term category includes the equipment identifier if supplied.
813
830
  public removeItemByCategory(category: string) {
814
831
  let rem: EquipmentMessage[] = [];
815
832
  let cmr = EquipmentMessage.parseMessageCode(category);
833
+ let hasChanges = false;
816
834
  for (let i = this.data.length - 1; i >= 0; i--) {
817
835
  if (typeof (this.data[i].code) !== 'undefined') {
818
836
  let cm = EquipmentMessage.parseMessageCode(this.data.code);
@@ -821,17 +839,20 @@ export class EquipmentMessages extends EqStateCollection<EquipmentMessage> {
821
839
  if (typeof cmr.messageId === 'undefined' || cm.messageId === cmr.messageId) {
822
840
  let data = this.data.splice(i, 1);
823
841
  rem.push(new EquipmentMessage(data, undefined, undefined));
842
+ hasChanges = true;
824
843
  }
825
844
  }
826
845
  }
827
846
  }
828
847
  }
848
+ if (hasChanges) webApp.emitToClients('sysmessages', this.get(true));
829
849
  return rem;
830
850
  }
831
851
  public setMessageByCode(code: string, severity: string | number, message: string): EquipmentMessage {
832
852
  let msg = this.getItemByCode(code, true);
833
853
  msg.severity = sys.board.valueMaps.eqMessageSeverities.encode(severity, 0);
834
854
  msg.message = message;
855
+ webApp.emitToClients('sysmessages', this.get(true));
835
856
  return msg;
836
857
  }
837
858
  }
@@ -912,15 +933,18 @@ export class PumpState extends EqState {
912
933
  public get name(): string { return this.data.name; }
913
934
  public set name(val: string) { this.setDataVal('name', val); }
914
935
  public get rpm(): number { return this.data.rpm; }
915
- public set rpm(val: number) { this.setDataVal('rpm', val, this.exceedsThreshold(this.data.rpm, val)); }
936
+ public set rpm(val: number) { this.setDataVal('rpm', val); }
937
+ //public set rpm(val: number) { this.setDataVal('rpm', val, this.exceedsThreshold(this.data.rpm, val)); }
916
938
  public get relay(): number { return this.data.relay; }
917
939
  public set relay(val: number) { this.setDataVal('relay', val); }
918
940
  public get program(): number { return this.data.program; }
919
941
  public set program(val: number) { this.setDataVal('program', val); }
920
942
  public get watts(): number { return this.data.watts; }
921
- public set watts(val: number) { this.setDataVal('watts', val, this.exceedsThreshold(this.data.watts, val)); }
943
+ public set watts(val: number) { this.setDataVal('watts', val); }
944
+ //public set watts(val: number) { this.setDataVal('watts', val, this.exceedsThreshold(this.data.watts, val)); }
922
945
  public get flow(): number { return this.data.flow; }
923
- public set flow(val: number) { this.setDataVal('flow', val, this.exceedsThreshold(this.data.flow, val)); }
946
+ public set flow(val: number) { this.setDataVal('flow', val); }
947
+ //public set flow(val: number) { this.setDataVal('flow', val, this.exceedsThreshold(this.data.flow, val)); }
924
948
  public get mode(): number { return this.data.mode; }
925
949
  public set mode(val: number) { this.setDataVal('mode', val); }
926
950
  public get driveState(): number { return this.data.driveState; }
@@ -942,7 +966,7 @@ export class PumpState extends EqState {
942
966
  this.hasChanged = true;
943
967
  }
944
968
  }
945
- public get virtualControllerStatus(): number {
969
+ /* public get virtualControllerStatus(): number {
946
970
  return typeof (this.data.virtualControllerStatus) !== 'undefined' ? this.data.virtualControllerStatus.val : -1;
947
971
  }
948
972
  public set virtualControllerStatus(val: number) {
@@ -950,7 +974,7 @@ export class PumpState extends EqState {
950
974
  this.data.virtualControllerStatus = sys.board.valueMaps.virtualControllerStatus.transform(val);
951
975
  this.hasChanged = true;
952
976
  }
953
- }
977
+ } */
954
978
  public get targetSpeed(): number { return this.data.targetSpeed; } // used for virtual controller
955
979
  public set targetSpeed(val: number) { this.setDataVal('targetSpeed', val); }
956
980
  public get type() { return typeof (this.data.type) !== 'undefined' ? this.data.type.val : -1; }
@@ -1214,7 +1238,10 @@ export class CircuitGroupState extends EqState implements ICircuitGroupState, IC
1214
1238
  public getExtended() {
1215
1239
  let sgrp = this.get(true); // Always operate on a copy.
1216
1240
  if (typeof sgrp.showInFeatures === 'undefined') sgrp.showInFeatures = true;
1241
+
1217
1242
  let cgrp = sys.circuitGroups.getItemById(this.id);
1243
+ sgrp.showInFeatures = this.showInFeatures = cgrp.showInFeatures;
1244
+ sgrp.isActive = this.isActive = cgrp.isActive;
1218
1245
  sgrp.circuits = [];
1219
1246
  for (let i = 0; i < cgrp.circuits.length; i++) {
1220
1247
  let cgc = cgrp.circuits.getItemByIndex(i).get(true);
@@ -1368,6 +1395,7 @@ export class BodyTempState extends EqState {
1368
1395
  if (typeof this.data.startDelay === 'undefined') this.data.startDelay = false;
1369
1396
  if (typeof this.data.stopDelay === 'undefined') this.data.stopDelay = false;
1370
1397
  if (typeof this.data.showInDashboard === 'undefined') this.data.showInDashboard = true;
1398
+ if (typeof this.data.heatMode === 'undefined') this.data.heatMode = sys.board.valueMaps.heatModes.transform(0);
1371
1399
  }
1372
1400
  public get id(): number { return this.data.id; }
1373
1401
  public set id(val: number) { this.setDataVal('id', val); }
@@ -1580,7 +1608,6 @@ export class FeatureStateCollection extends EqStateCollection<FeatureState> {
1580
1608
  }
1581
1609
  }
1582
1610
  }
1583
-
1584
1611
  export class FeatureState extends EqState implements ICircuitState {
1585
1612
  public dataName: string = 'feature';
1586
1613
  public initData() {
@@ -1840,7 +1867,7 @@ export class CoverState extends EqState {
1840
1867
  public set isClosed(val: boolean) { this.setDataVal('isClosed', val); }
1841
1868
  }
1842
1869
  export class ChlorinatorStateCollection extends EqStateCollection<ChlorinatorState> {
1843
- public superChlor: { id:number, lastDispatch: number, reference: number }[] = [];
1870
+ public superChlor: { id: number, lastDispatch: number, reference: number }[] = [];
1844
1871
  public getSuperChlor(id: number): { id: number, lastDispatch: number, reference: number } {
1845
1872
  let sc = this.superChlor.find(elem => id === elem.id);
1846
1873
  if (typeof sc === 'undefined') {
@@ -1935,32 +1962,15 @@ export class ChlorinatorState extends EqState {
1935
1962
  public set spaSetpoint(val: number) { this.setDataVal('spaSetpoint', val); }
1936
1963
  public get superChlorHours(): number { return this.data.superChlorHours; }
1937
1964
  public set superChlorHours(val: number) { this.setDataVal('superChlorHours', val); }
1965
+ public get saltTarget(): number { return this.data.saltTarget; }
1966
+ public set saltTarget(val: number) { this.setDataVal('saltTarget', val); }
1938
1967
  public get saltRequired(): number { return this.data.saltRequired; }
1939
1968
  public get saltLevel(): number { return this.data.saltLevel; }
1940
1969
  public set saltLevel(val: number) {
1941
- this.setDataVal('saltLevel', val);
1942
- //this.data.saltLevel = val;
1943
- // Calculate the salt required.
1944
- let capacity = 0;
1945
- for (let i = 0; i < sys.bodies.length; i++) {
1946
- let body = sys.bodies.getItemById(i + 1);
1947
- if (this.body === 32)
1948
- capacity = Math.max(body.capacity, capacity);
1949
- else if (this.body === 0 && body.id === 1)
1950
- capacity = Math.max(body.capacity, capacity);
1951
- else if (this.body === 1 && body.id === 2)
1952
- capacity = Math.max(body.capacity, capacity);
1953
- }
1954
- if (capacity > 0 && this.saltLevel < 3100) {
1955
- // Salt requirements calculation.
1956
- // Target - SaltLevel = NeededSalt = 3400 - 2900 = 500ppm
1957
- // So to raise 120ppm you need to add 1lb per 1000 gal.
1958
- // (NeededSalt/120ppm) * (MaxBody/1000) = (500/120) * (33000/1000) = 137.5lbs of salt required to hit target.
1959
- let dec = Math.pow(10, 2);
1960
- this.data.saltRequired = Math.round((((3400 - this.saltLevel) / 120) * (capacity / 1000)) * dec) / dec;
1970
+ if (this.saltLevel !== val) {
1971
+ this.setDataVal('saltLevel', val);
1972
+ this.calcSaltRequired();
1961
1973
  }
1962
- else
1963
- this.data.saltRequired = 0;
1964
1974
  }
1965
1975
  public get superChlor(): boolean { return this.data.superChlor; }
1966
1976
  public set superChlor(val: boolean) {
@@ -2023,9 +2033,38 @@ export class ChlorinatorState extends EqState {
2023
2033
  this.setDataVal('superChlor', remaining > 0);
2024
2034
  chlor.superChlor = remaining > 0;
2025
2035
  }
2036
+ public calcSaltRequired(saltTarget?: number) : number {
2037
+ if (typeof saltTarget === 'undefined') saltTarget = sys.chlorinators.getItemById(this.id, false).saltTarget || 0;
2038
+ let saltRequired = 0;
2039
+ //this.data.saltLevel = val;
2040
+ // Calculate the salt required.
2041
+ let capacity = 0;
2042
+ for (let i = 0; i < sys.bodies.length; i++) {
2043
+ let body = sys.bodies.getItemById(i + 1);
2044
+ if (this.body === 32)
2045
+ capacity = Math.max(body.capacity, capacity);
2046
+ else if (this.body === 0 && body.id === 1)
2047
+ capacity = Math.max(body.capacity, capacity);
2048
+ else if (this.body === 1 && body.id === 2)
2049
+ capacity = Math.max(body.capacity, capacity);
2050
+ }
2051
+ if (capacity > 0 && this.saltLevel < saltTarget) {
2052
+ // Salt requirements calculation.
2053
+ // Target - SaltLevel = NeededSalt = 3400 - 2900 = 500ppm
2054
+ // So to raise 120ppm you need to add 1lb per 1000 gal.
2055
+ // (NeededSalt/120ppm) * (MaxBody/1000) = (500/120) * (33000/1000) = 137.5lbs of salt required to hit target.
2056
+ let dec = Math.pow(10, 2);
2057
+ saltRequired = Math.round((((saltTarget - this.saltLevel) / 120) * (capacity / 1000)) * dec) / dec;
2058
+ if (this.saltRequired < 0) saltRequired = 0;
2059
+ }
2060
+ this.setDataVal('saltRequired', saltRequired);
2061
+ return saltRequired;
2062
+ }
2063
+ public getEmitData() { return this.getExtended(); }
2026
2064
  public getExtended(): any {
2027
2065
  let schlor = this.get(true);
2028
2066
  let chlor = sys.chlorinators.getItemById(this.id, false);
2067
+ schlor.saltTarget = chlor.saltTarget;
2029
2068
  schlor.lockSetpoints = chlor.disabled || chlor.isDosing;
2030
2069
  return schlor;
2031
2070
  }
@@ -2052,8 +2091,278 @@ export class ChemControllerStateCollection extends EqStateCollection<ChemControl
2052
2091
  }
2053
2092
  }
2054
2093
  }
2094
+ export class ChemDoserStateCollection extends EqStateCollection<ChemDoserState> {
2095
+ public createItem(data: any): ChemDoserState { return new ChemDoserState(data); }
2096
+ public cleanupState() {
2097
+ for (let i = this.data.length - 1; i >= 0; i--) {
2098
+ if (isNaN(this.data[i].id)) this.data.splice(i, 1);
2099
+ else {
2100
+ if (typeof sys.chemControllers.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
2101
+ }
2102
+ }
2103
+ // Make sure we have at least the items that exist in the config.
2104
+ let cfg = sys.chemControllers.toArray();
2105
+ for (let i = 0; i < cfg.length; i++) {
2106
+ let c = cfg[i];
2107
+ let s = this.getItemById(cfg[i].id, true);
2108
+ s.body = c.body;
2109
+ s.name = c.name;
2110
+ s.type = c.type;
2111
+ s.isActive = c.isActive;
2112
+ }
2113
+ }
2114
+ }
2115
+ export interface IChemControllerState {
2116
+ flowDetected: boolean;
2117
+ isBodyOn: boolean;
2118
+ emitEquipmentChange: () => void;
2119
+ }
2120
+ export interface IChemicalState {
2121
+ enabled: boolean;
2122
+ currentDose: ChemicalDoseState;
2123
+ manualDosing: boolean;
2124
+ manualMixing: boolean;
2125
+ dosingTimeRemaining: number;
2126
+ dosingVolumeRemaining: number;
2127
+ volumeDosed: number;
2128
+ timeDosed: number;
2129
+ endDose: (dtEnd?: Date, status?: string, volumeDosed?: number, timeDosed?: number) => ChemicalDoseState;
2130
+ appendDose: (volumeDosed: number, timeDosed: number) => ChemicalDoseState;
2131
+ tank: ChemicalTankState;
2132
+ pump: ChemicalPumpState;
2133
+ doseHistory: ChemicalDoseState[];
2134
+ freezeProtect: boolean;
2135
+ mixTimeRemaining: number;
2136
+ delayTimeRemaining: number;
2137
+ flowDelay: boolean;
2138
+ dailyVolumeDosed: number;
2139
+ chemType: string;
2140
+ dosingStatus: number;
2141
+ chemController: IChemControllerState;
2142
+ chlor?: ChemicalChlorState;
2143
+
2144
+ }
2145
+ export class ChemDoserState extends EqState implements IChemicalState, IChemControllerState {
2146
+ public initData() {
2147
+ if (typeof this.data.flowDetected === 'undefined') this.data.flowDetected = false;
2148
+ if (typeof this.data.flowSensor === 'undefined') this.data.flowSensor = {};
2149
+ if (typeof this.data.alarms === 'undefined') { let a = this.alarms; }
2150
+ if (typeof this.data.warnings === 'undefined') { let w = this.warnings; }
2151
+ if (typeof this.data.tank === 'undefined') this.data.tank = { capacity: 0, level: 0, units: 0 };
2152
+ if (typeof this.data.pump === 'undefined') this.data.pump = { isDosing: false };
2153
+ if (typeof this.data.dosingTimeRemaining === 'undefined') this.data.dosingTimeRemaining = 0;
2154
+ if (typeof this.data.delayTimeRemaining === 'undefined') this.data.delayTimeRemaining = 0;
2155
+ if (typeof this.data.dosingVolumeRemaining === 'undefined') this.data.dosingVolumeRemaining = 0;
2156
+ if (typeof this.data.doseVolume === 'undefined') this.data.doseVolume = 0;
2157
+ if (typeof this.data.doseTime === 'undefined') this.data.doseTime = 0;
2158
+ if (typeof this.data.lockout === 'undefined') this.data.lockout = false;
2159
+ if (typeof this.data.level === 'undefined') this.data.level = 0;
2160
+ if (typeof this.data.mixTimeRemaining === 'undefined') this.data.mixTimeRemaining = 0;
2161
+ if (typeof this.data.dailyLimitReached === 'undefined') this.data.dailyLimitReached = false;
2162
+ if (typeof this.data.manualDosing === 'undefined') this.data.manualDosing = false;
2163
+ if (typeof this.data.manualMixing === 'undefined') this.data.manualMixing = false;
2164
+ if (typeof this.data.flowDelay === 'undefined') this.data.flowDelay = false;
2165
+ if (typeof this.data.dosingStatus === 'undefined') this.dosingStatus = 2;
2166
+ if (typeof this.data.enabled === 'undefined') this.data.enabled = true;
2167
+ if (typeof this.data.freezeProtect === 'undefined') this.data.freezeProtect = false;
2168
+ if (typeof this.data.setpoint === 'undefined') this.data.setpoint = 100;
2169
+ if (typeof this.data.suspendDosing === 'undefined') this.data.suspendDosing = false;
2170
+ }
2171
+ public dataName: string = 'chemDoser';
2172
+ public get id(): number { return this.data.id; }
2173
+ public set id(val: number) { this.setDataVal('id', val); }
2174
+ public get isActive(): boolean { return this.data.isActive; }
2175
+ public set isActive(val: boolean) { this.setDataVal('isActive', val); }
2176
+ public get name(): string { return this.data.name; }
2177
+ public set name(val: string) { this.setDataVal('name', val); }
2178
+ public get setpoint(): number { return this.data.setpoint; }
2179
+ public set setpoint(val: number) { this.setDataVal('setpoint', val); }
2180
+
2181
+ public get lastComm(): number { return this.data.lastComm || 0; }
2182
+ public set lastComm(val: number) { this.setDataVal('lastComm', val, false); }
2183
+ public get isBodyOn(): boolean { return this.data.isBodyOn; }
2184
+ public set isBodyOn(val: boolean) { this.data.isBodyOn = val; }
2185
+ public get flowDetected(): boolean { return this.data.flowDetected; }
2186
+ public set flowDetected(val: boolean) { this.data.flowDetected = val; }
2187
+ public get status(): number { return typeof (this.data.status) !== 'undefined' ? this.data.status.val : -1; }
2188
+ public set status(val: number) {
2189
+ if (this.status !== val) {
2190
+ this.data.status = sys.board.valueMaps.chemDoserStatus.transform(val);
2191
+ this.hasChanged = true;
2192
+ }
2193
+ }
2194
+ public get body(): number { return typeof (this.data.body) !== 'undefined' ? this.data.body.val : -1; }
2195
+ public set body(val: number) {
2196
+ if (this.body !== val) {
2197
+ this.data.body = sys.board.valueMaps.bodies.transform(val);
2198
+ this.hasChanged = true;
2199
+ }
2200
+ }
2201
+ public get type(): number { return typeof (this.data.type) !== 'undefined' ? this.data.type.val : 0; }
2202
+ public set type(val: number) {
2203
+ if (this.type !== val) {
2204
+ this.data.type = sys.board.valueMaps.chemDoserTypes.transform(val);
2205
+ this.data.chemType = this.data.type.desc;
2206
+ this.hasChanged = true;
2207
+ }
2208
+ }
2209
+ public get enabled(): boolean { return this.data.enabled; }
2210
+ public set enabled(val: boolean) { this.data.enabled = val; }
2211
+ public get freezeProtect(): boolean { return this.data.freezeProtect; }
2212
+ public set freezeProtect(val: boolean) { this.data.freezeProtect = val; }
2213
+ public get suspendDosing(): boolean { return this.data.suspendDosing; }
2214
+ public set suspendDosing(val: boolean) { this.data.suspendDosing = val; }
2215
+ public get chemType(): string { return this.data.chemType; }
2216
+ public get delayTimeRemaining(): number { return this.data.delayTimeRemaining; }
2217
+ public set delayTimeRemaining(val: number) { this.setDataVal('delayTimeRemaining', val); }
2218
+ public get doseTime(): number { return this.data.doseTime; }
2219
+ public set doseTime(val: number) { this.setDataVal('doseTime', val); }
2220
+ public get doseVolume(): number { return this.data.doseVolume; }
2221
+ public set doseVolume(val: number) { this.setDataVal('doseVolume', val); }
2222
+ public get dosingTimeRemaining(): number { return this.data.dosingTimeRemaining; }
2223
+ public set dosingTimeRemaining(val: number) { this.setDataVal('dosingTimeRemaining', val); }
2224
+ public get dosingVolumeRemaining(): number { return this.data.dosingVolumeRemaining; }
2225
+ public set dosingVolumeRemaining(val: number) { this.setDataVal('dosingVolumeRemaining', val); }
2226
+ public get volumeDosed(): number { return this.data.volumeDosed; }
2227
+ public set volumeDosed(val: number) { this.setDataVal('volumeDosed', val); }
2228
+ public get timeDosed(): number { return this.data.timeDosed; }
2229
+ public set timeDosed(val: number) { this.setDataVal('timeDosed', val); }
2230
+ public get dailyVolumeDosed(): number { return this.data.dailyVolumeDosed; }
2231
+ public set dailyVolumeDosed(val: number) { this.setDataVal('dailyVolumeDosed', val); }
2232
+ public get mixTimeRemaining(): number { return this.data.mixTimeRemaining; }
2233
+ public set mixTimeRemaining(val: number) { this.setDataVal('mixTimeRemaining', val); }
2234
+ public get dosingStatus(): number { return typeof (this.data.dosingStatus) !== 'undefined' ? this.data.dosingStatus.val : undefined; }
2235
+ public set dosingStatus(val: number) {
2236
+ if (this.dosingStatus !== val) {
2237
+ logger.debug(`${this.chemType} dosing status changed from ${sys.board.valueMaps.chemControllerDosingStatus.getName(this.dosingStatus)} (${this.dosingStatus}) to ${sys.board.valueMaps.chemControllerDosingStatus.getName(val)}(${val})`);
2238
+ this.data.dosingStatus = sys.board.valueMaps.chemControllerDosingStatus.transform(val);
2239
+ this.hasChanged = true;
2240
+ }
2241
+ }
2242
+ public get lockout(): boolean { return utils.makeBool(this.data.lockout); }
2243
+ public set lockout(val: boolean) { this.setDataVal('lockout', val); }
2244
+ public get flowDelay(): boolean { return utils.makeBool(this.data.flowDelay); }
2245
+ public set flowDelay(val: boolean) { this.data.flowDelay = val; }
2246
+ public get manualDosing(): boolean { return utils.makeBool(this.data.manualDosing); }
2247
+ public set manualDosing(val: boolean) { this.setDataVal('manualDosing', val); }
2248
+ public get manualMixing(): boolean { return utils.makeBool(this.data.manualMixing); }
2249
+ public set manualMixing(val: boolean) { this.setDataVal('manualMixing', val); }
2250
+ public get dailyLimitReached(): boolean { return utils.makeBool(this.data.dailyLimitReached); }
2251
+ public set dailyLimitReached(val: boolean) { this.data.dailyLimitReached = val; }
2252
+ public get tank(): ChemicalTankState { return new ChemicalTankState(this.data, 'tank', this); }
2253
+ public get pump(): ChemicalPumpState { return new ChemicalPumpState(this.data, 'pump', this); }
2254
+ public get flowSensor(): ChemicalFlowSensorState { return new ChemicalFlowSensorState(this.data, 'flowSensor', this); }
2255
+ public get warnings(): ChemDoserStateWarnings { return new ChemDoserStateWarnings(this.data, 'warnings', this); }
2256
+ public get alarms(): ChemDoserStateAlarms { return new ChemDoserStateAlarms(this.data, 'alarms', this); }
2257
+ public calcDoseHistory(): number {
2258
+ // The dose history records will already exist when the state is loaded. There are enough records to cover 24 hours in this
2259
+ // instance. We need to prune off any records that are > 24 hours when we calculate.
2260
+ let dailyVolumeDosed = 0;
2261
+ let dt = new Date();
2262
+ let dtMax = dt.setTime(dt.getTime() - (24 * 60 * 60 * 1000));
2263
+ for (let i = this.doseHistory.length - 1; i >= 0; i--) {
2264
+ let dh = this.doseHistory[i];
2265
+ if (typeof dh.end !== 'undefined'
2266
+ && typeof dh.end.getTime == 'function'
2267
+ && dh.end.getTime() > dtMax
2268
+ && dh.volumeDosed > 0) dailyVolumeDosed += dh.volumeDosed;
2269
+ else {
2270
+ logger.info(`Removing dose history ${dh.chem} ${dh.end}`);
2271
+ this.doseHistory.splice(i, 1);
2272
+ }
2273
+ }
2274
+ return dailyVolumeDosed + (typeof this.currentDose !== 'undefined' ? this.currentDose.volumeDosed : 0);
2275
+ }
2276
+ public startDose(dtStart: Date = new Date(), method: string = 'auto', volume: number = 0, volumeDosed: number = 0, time: number = 0, timeDosed: number = 0): ChemicalDoseState {
2277
+ this.currentDose = new ChemicalDoseState();
2278
+ this.currentDose.type = this.chemType;
2279
+ this.currentDose.id = this.id;
2280
+ this.currentDose.start = dtStart;
2281
+ this.currentDose.method = method;
2282
+ this.currentDose.volumeDosed = volumeDosed;
2283
+ this.doseVolume = this.currentDose.volume = volume;
2284
+ this.currentDose.chem = this.chemType;
2285
+ this.currentDose.time = time;
2286
+ this.currentDose._timeDosed = timeDosed;
2287
+ this.volumeDosed = this.currentDose.volumeDosed;
2288
+ this.timeDosed = Math.round(timeDosed / 1000);
2289
+ this.dosingTimeRemaining = this.currentDose.timeRemaining;
2290
+ this.dosingVolumeRemaining = this.currentDose.volumeRemaining;
2291
+ this.doseTime = Math.round(this.currentDose.time / 1000);
2292
+ this.currentDose._isManual = method === 'manual';
2293
+ this.currentDose.status = 'current';
2294
+ //webApp.emitToClients(`chemicalDose`, this.currentDose);
2295
+ return this.currentDose;
2296
+ }
2297
+ public endDose(dtEnd: Date = new Date(), status: string = 'completed', volumeDosed: number = 0, timeDosed: number = 0): ChemicalDoseState {
2298
+ let dose = this.currentDose;
2299
+ if (typeof dose !== 'undefined') {
2300
+ dose.type = 'Peristaltic';
2301
+ dose._timeDosed += timeDosed;
2302
+ dose.volumeDosed += volumeDosed;
2303
+ dose.end = dtEnd;
2304
+ dose.timeDosed = dose._timeDosed / 1000;
2305
+ dose.status = status;
2306
+ this.volumeDosed = dose.volumeDosed;
2307
+ this.timeDosed = Math.round(dose._timeDosed / 1000);
2308
+ this.dosingTimeRemaining = 0;
2309
+ this.dosingVolumeRemaining = 0;
2310
+ if (dose.volumeDosed > 0 || dose.timeDosed > 0) {
2311
+ this.dailyVolumeDosed = this.calcDoseHistory();
2312
+ this.doseHistory.unshift(dose);
2313
+ DataLogger.writeEnd(`chemDosage_${this.chemType}.log`, dose);
2314
+ setImmediate(() => { webApp.emitToClients(`chemicalDose`, dose); });
2315
+ }
2316
+ this.currentDose = undefined;
2317
+ }
2318
+ return dose;
2319
+ }
2320
+ // Appends dose information to the current dose. The time here is in ms and our target will be in sec.
2321
+ public appendDose(volumeDosed: number, timeDosed: number): ChemicalDoseState {
2322
+ let dose = typeof this.currentDose !== 'undefined' ? this.currentDose : this.currentDose = this.startDose();
2323
+ dose._timeDosed += timeDosed;
2324
+ dose.volumeDosed += volumeDosed;
2325
+ dose.timeDosed = dose._timeDosed / 1000;
2326
+ dose.type = 'Peristaltic';
2327
+ this.volumeDosed = dose.volumeDosed;
2328
+ this.timeDosed = Math.round(dose._timeDosed / 1000);
2329
+ this.dosingTimeRemaining = dose.timeRemaining;
2330
+ this.dosingVolumeRemaining = dose.volumeRemaining;
2331
+
2332
+ if (dose.volumeDosed > 0 || timeDosed > 0) setImmediate(() => { webApp.emitToClients(`chemicalDose`, dose); });
2333
+ return dose;
2334
+ }
2335
+ public get currentDose(): ChemicalDoseState {
2336
+ if (typeof this.data.currentDose === 'undefined') return this.data.currentDose;
2337
+ if (typeof this.data.currentDose.save !== 'function') this.data.currentDose = new ChemicalDoseState(this.data.currentDose);
2338
+ return this.data.currentDose;
2339
+ }
2340
+ public set currentDose(val: ChemicalDoseState) {
2341
+ this.setDataVal('currentDose', val);
2342
+ }
2343
+ public get doseHistory(): ChemicalDoseState[] {
2344
+ if (typeof this.data.doseHistory === 'undefined') this.data.doseHistory = [];
2345
+ if (this.data.doseHistory.length === 0) return this.data.doseHistory;
2346
+ if (typeof this.data.doseHistory[0].save !== 'function') {
2347
+ let arr: ChemicalDoseState[] = [];
2348
+ for (let i = 0; i < this.data.doseHistory.length; i++) {
2349
+ arr.push(new ChemicalDoseState(this.data.doseHistory[i]));
2350
+ }
2351
+ this.data.doseHistory = arr;
2352
+ }
2353
+ return this.data.doseHistory;
2354
+ }
2355
+ public set doseHistory(val: ChemicalDoseState[]) { this.setDataVal('doseHistory', val); }
2356
+ public get chemController() { return this; }
2357
+ public getExtended(): any {
2358
+ let chem = sys.chemDosers.getItemById(this.id);
2359
+ let obj = this.get(true);
2360
+ obj = extend(true, obj, chem.getExtended());
2361
+ return obj;
2362
+ }
2363
+ }
2055
2364
 
2056
- export class ChemControllerState extends EqState {
2365
+ export class ChemControllerState extends EqState implements IChemControllerState {
2057
2366
  public initData() {
2058
2367
  if (typeof this.data.saturationIndex === 'undefined') this.data.saturationIndex = 0;
2059
2368
  if (typeof this.data.flowDetected === 'undefined') this.data.flowDetected = false;
@@ -2285,10 +2594,22 @@ export class ChemControllerState extends EqState {
2285
2594
  this.hasChanged = true;
2286
2595
  }
2287
2596
  }
2597
+ public getEmitData(): any {
2598
+ let chem = sys.chemControllers.getItemById(this.id);
2599
+ let obj = this.get(true);
2600
+ obj.address = chem.address;
2601
+ obj.borates = chem.borates;
2602
+ obj.saturationIndex = this.saturationIndex || 0;
2603
+ obj.alkalinity = chem.alkalinity;
2604
+ obj.calciumHardness = chem.calciumHardness;
2605
+ obj.cyanuricAcid = chem.cyanuricAcid;
2606
+ return obj;
2607
+ }
2288
2608
  public getExtended(): any {
2289
2609
  let chem = sys.chemControllers.getItemById(this.id);
2290
2610
  let obj = this.get(true);
2291
2611
  obj.address = chem.address;
2612
+ obj.borates = chem.borates;
2292
2613
  obj.saturationIndex = this.saturationIndex || 0;
2293
2614
  obj.alkalinity = chem.alkalinity;
2294
2615
  obj.calciumHardness = chem.calciumHardness;
@@ -2299,7 +2620,7 @@ export class ChemControllerState extends EqState {
2299
2620
  return obj;
2300
2621
  }
2301
2622
  }
2302
- export class ChemicalState extends ChildEqState {
2623
+ export class ChemicalState extends ChildEqState implements IChemicalState {
2303
2624
  public initData() {
2304
2625
  if (typeof this.data.probe === 'undefined') this.data.probe = {};
2305
2626
  if (typeof this.data.tank === 'undefined') this.data.tank = { capacity: 0, level: 0, units: 0 };
@@ -2342,6 +2663,7 @@ export class ChemicalState extends ChildEqState {
2342
2663
  }
2343
2664
  public startDose(dtStart: Date = new Date(), method: string = 'auto', volume: number = 0, volumeDosed: number = 0, time: number = 0, timeDosed: number = 0): ChemicalDoseState {
2344
2665
  this.currentDose = new ChemicalDoseState();
2666
+ this.currentDose.type = this.type;
2345
2667
  this.currentDose.id = this.chemController.id;
2346
2668
  this.currentDose.start = dtStart;
2347
2669
  this.currentDose.method = method;
@@ -2375,9 +2697,9 @@ export class ChemicalState extends ChildEqState {
2375
2697
  this.timeDosed = Math.round(dose._timeDosed / 1000);
2376
2698
  this.dosingTimeRemaining = 0;
2377
2699
  this.dosingVolumeRemaining = 0;
2378
- if (dose.volumeDosed > 0) {
2379
- this.doseHistory.unshift(dose);
2700
+ if (dose.volumeDosed > 0 || dose.timeDosed > 0) {
2380
2701
  this.dailyVolumeDosed = this.calcDoseHistory();
2702
+ this.doseHistory.unshift(dose);
2381
2703
  DataLogger.writeEnd(`chemDosage_${this.chemType}.log`, dose);
2382
2704
  setImmediate(() => { webApp.emitToClients(`chemicalDose`, dose); });
2383
2705
  }
@@ -2390,11 +2712,12 @@ export class ChemicalState extends ChildEqState {
2390
2712
  let dose = typeof this.currentDose !== 'undefined' ? this.currentDose : this.currentDose = this.startDose();
2391
2713
  dose._timeDosed += timeDosed;
2392
2714
  dose.volumeDosed += volumeDosed;
2715
+ dose.timeDosed = dose._timeDosed/1000;
2393
2716
  this.volumeDosed = dose.volumeDosed;
2394
2717
  this.timeDosed = Math.round(dose._timeDosed / 1000);
2395
2718
  this.dosingTimeRemaining = dose.timeRemaining;
2396
2719
  this.dosingVolumeRemaining = dose.volumeRemaining;
2397
- if (dose.volumeDosed > 0) setImmediate(() => { webApp.emitToClients(`chemicalDose`, dose); });
2720
+ if (dose.volumeDosed > 0 || timeDosed > 0) setImmediate(() => { webApp.emitToClients(`chemicalDose`, dose); });
2398
2721
  return dose;
2399
2722
  }
2400
2723
  public get currentDose(): ChemicalDoseState {
@@ -2422,6 +2745,7 @@ export class ChemicalState extends ChildEqState {
2422
2745
  let dH = this.demandHistory;
2423
2746
  dH.appendDemand(time, val);
2424
2747
  }
2748
+ public get type() { return this.data.type; };
2425
2749
  public get demandHistory() { return new ChemicalDemandState(this.data, 'demandHistory', this) };
2426
2750
  public get enabled(): boolean { return this.data.enabled; }
2427
2751
  public set enabled(val: boolean) { this.data.enabled = val; }
@@ -2486,6 +2810,7 @@ export class ChemicalPhState extends ChemicalState {
2486
2810
  public initData() {
2487
2811
  super.initData();
2488
2812
  if (typeof this.data.chemType === 'undefined') this.data.chemType = 'none';
2813
+ if (typeof this.data.type === 'undefined') this.data.type = 'ph';
2489
2814
  }
2490
2815
  public getConfig() {
2491
2816
  let schem = this.chemController;
@@ -2512,13 +2837,27 @@ export class ChemicalPhState extends ChemicalState {
2512
2837
  // Calculate how many mL are required to raise to our pH level.
2513
2838
  // 1. Get the total gallons of water that the chem controller is in
2514
2839
  // control of.
2840
+ // 2. RSG 5-22-22 - If the spa is on, calc demand only based on the spa volume. Otherwise, long periods of spa usage
2841
+ // will result in an overdose if pH is high.
2515
2842
  let totalGallons = 0;
2843
+ // The bodyIsOn code was throwing an exception whenver no bodies were on.
2844
+ if (chem.body === 32 && sys.equipment.shared) {
2845
+ // We are shared and when body 2 (spa) is on body 1 (pool) is off.
2846
+ if (state.temps.bodies.getItemById(2).isOn === true) totalGallons = sys.bodies.getItemById(2).capacity;
2847
+ else totalGallons = sys.bodies.getItemById(1).capacity + sys.bodies.getItemById(2).capacity;
2848
+ }
2849
+ else {
2850
+ // These are all single body implementations so we simply match to the body.
2851
+ totalGallons = sys.bodies.getItemById(chem.body + 1).capacity;
2852
+ }
2516
2853
 
2517
- if (chem.body === 0 || chem.body === 32 || sys.equipment.shared) totalGallons += sys.bodies.getItemById(1).capacity;
2518
- if (chem.body === 1 || chem.body === 32 || sys.equipment.shared) totalGallons += sys.bodies.getItemById(2).capacity;
2519
- if (chem.body === 2) totalGallons += sys.bodies.getItemById(3).capacity;
2520
- if (chem.body === 3) totalGallons += sys.bodies.getItemById(4).capacity;
2521
- logger.verbose(`Chem begin calculating ${this.chemType} demand: ${this.level} setpoint: ${this.setpoint} body: ${totalGallons}`);
2854
+ //if (chem.body === 0 || chem.body === 32 || sys.equipment.shared) totalGallons += sys.bodies.getItemById(1).capacity;
2855
+ //let bodyIsOn = state.temps.bodies.getBodyIsOn();
2856
+ //if (bodyIsOn.circuit === 1 && sys.circuits.getInterfaceById(bodyIsOn.circuit).type === sys.board.valueMaps.circuitFunctions.getValue('spa') && (chem.body === 1 || chem.body === 32 || sys.equipment.shared)) totalGallons = sys.bodies.getItemById(2).capacity;
2857
+ //else if (chem.body === 1 || chem.body === 32 || sys.equipment.shared) totalGallons += sys.bodies.getItemById(2).capacity;
2858
+ //if (chem.body === 2) totalGallons += sys.bodies.getItemById(3).capacity;
2859
+ //if (chem.body === 3) totalGallons += sys.bodies.getItemById(4).capacity;
2860
+ logger.verbose(`Chem begin calculating ${this.chemType} demand: ${this.level} setpoint: ${this.setpoint} total gallons: ${totalGallons}`);
2522
2861
  let chg = this.setpoint - this.level;
2523
2862
  let delta = chg * totalGallons;
2524
2863
  let temp = (this.level + this.setpoint) / 2;
@@ -2546,6 +2885,8 @@ export class ChemicalORPState extends ChemicalState {
2546
2885
  if (typeof this.data.probe === 'undefined') this.data.probe = {};
2547
2886
  if (typeof this.data.chemType === 'undefined') this.data.chemType = 'none';
2548
2887
  if (typeof this.data.useChlorinator === 'undefined') this.data.useChlorinator = false;
2888
+ if (typeof this.data.type === 'undefined') this.data.type = 'orp';
2889
+
2549
2890
  super.initData();
2550
2891
  // Load up the 24 hours doseHistory.
2551
2892
  //this.doseHistory = DataLogger.readFromEnd(`chemDosage_${this.chemType}.log`, ChemicalDoseState, (lineNumber: number, entry: ChemicalDoseState): boolean => {
@@ -2716,6 +3057,7 @@ export class ChemicalDoseState extends DataLoggerEntry {
2716
3057
  public volumeDosed: number;
2717
3058
  public time: number;
2718
3059
  public timeDosed: number;
3060
+ public type: string;
2719
3061
 
2720
3062
  public static createInstance(entry?: string): ChemicalDoseState { return new ChemicalDoseState(entry); }
2721
3063
  public save() { DataLogger.writeEnd(`chemDosage_${this.chem}.log`, this); }
@@ -2831,6 +3173,107 @@ export class ChemControllerStateWarnings extends ChildEqState {
2831
3173
  }
2832
3174
  }
2833
3175
 
3176
+ }
3177
+ export class ChemDoserStateWarnings extends ChildEqState {
3178
+ ///ctor(data): ChemControllerStateWarnings { return new ChemControllerStateWarnings(data, name || 'warnings'); }
3179
+ public dataName = 'chemDoserWarnings';
3180
+ public initData() {
3181
+ if (typeof this.data.lockout === 'undefined') this.lockout = 0;
3182
+ if (typeof this.data.pHDailyLimitReached === 'undefined') this.dailyLimitReached = 0;
3183
+ if (typeof this.data.invalidSetup === 'undefined') this.invalidSetup = 0;
3184
+ if (typeof this.data.chlorinatorCommError === 'undefined') this.chlorinatorCommError = 0;
3185
+ }
3186
+ public get lockout(): number { return this.data.lockout; }
3187
+ public set lockout(val: number) {
3188
+ if (this.lockout !== val) {
3189
+ this.data.lockout = sys.board.valueMaps.chemDoserLimits.transform(val);
3190
+ this.hasChanged = true;
3191
+ }
3192
+ }
3193
+ public get dailyLimitReached(): number { return this.data.dailyLimitReached; }
3194
+ public set dailyLimitReached(val: number) {
3195
+ if (this.dailyLimitReached !== val) {
3196
+ this.data.dailyLimitReached = sys.board.valueMaps.chemDoserLimits.transform(val);
3197
+ this.hasChanged = true;
3198
+ }
3199
+ }
3200
+ public get invalidSetup(): number { return this.data.invalidSetup; }
3201
+ public set invalidSetup(val: number) {
3202
+ if (this.invalidSetup !== val) {
3203
+ this.data.invalidSetup = sys.board.valueMaps.chemDoserLimits.transform(val);
3204
+ this.hasChanged = true;
3205
+ }
3206
+ }
3207
+ public get chlorinatorCommError(): number { return this.data.chlorinatorCommError; }
3208
+ public set chlorinatorCommError(val: number) {
3209
+ if (this.chlorinatorCommError !== val) {
3210
+ this.data.chlorinatorCommError = sys.board.valueMaps.chemDoserWarnings.transform(val);
3211
+ this.hasChanged = true;
3212
+ }
3213
+ }
3214
+ }
3215
+
3216
+ export class ChemDoserStateAlarms extends ChildEqState {
3217
+ public dataName = 'chemControllerAlarms';
3218
+ public initData() {
3219
+ if (typeof this.data.flow === 'undefined') this.data.flow = sys.board.valueMaps.chemDoserAlarms.transform(0);
3220
+ if (typeof this.data.tank === 'undefined') this.data.tank = sys.board.valueMaps.chemDoserAlarms.transform(0);
3221
+ if (typeof this.data.pumpFault === 'undefined') this.data.pumpFault = sys.board.valueMaps.chemDoserHardwareFaults.transform(0);
3222
+ if (typeof this.data.bodyFault === 'undefined') this.data.bodyFault = sys.board.valueMaps.chemDoserHardwareFaults.transform(0);
3223
+ if (typeof this.data.flowSensorFault === 'undefined') this.data.flowSensorFault = sys.board.valueMaps.chemDoserHardwareFaults.transform(0);
3224
+ if (typeof this.data.comms === 'undefined') this.data.comms = sys.board.valueMaps.chemDoserStatus.transform(0);
3225
+ if (typeof this.data.freezeProtect === 'undefined') this.data.freezeProtect = sys.board.valueMaps.chemDoserAlarms.transform(0);
3226
+ }
3227
+ public get flow(): number { return typeof this.data.flow === 'undefined' ? undefined : this.data.flow.val; }
3228
+ public set flow(val: number) {
3229
+ if (this.flow !== val) {
3230
+ this.data.flow = sys.board.valueMaps.chemDoserAlarms.transform(val);
3231
+ this.hasChanged = true;
3232
+ }
3233
+ }
3234
+ public get tank(): number { return typeof this.data.pHTank === 'undefined' ? undefined : this.data.tank.val; }
3235
+ public set tank(val: number) {
3236
+ if (this.tank !== val) {
3237
+ this.data.tank = sys.board.valueMaps.chemDoserAlarms.transform(val);
3238
+ this.hasChanged = true;
3239
+ }
3240
+ }
3241
+ public get pumpFault(): number { return typeof this.data.pumpFault === 'undefined' ? undefined : this.data.pumpFault.val; }
3242
+ public set pumpFault(val: number) {
3243
+ if (this.pumpFault !== val) {
3244
+ this.data.pumpFault = sys.board.valueMaps.chemDoserHardwareFaults.transform(val);
3245
+ this.hasChanged = true;
3246
+ }
3247
+ }
3248
+ public get bodyFault(): number { return typeof this.data.bodyFault === 'undefined' ? undefined : this.data.bodyFault.val; }
3249
+ public set bodyFault(val: number) {
3250
+ if (this.bodyFault !== val) {
3251
+ this.data.bodyFault = sys.board.valueMaps.chemDoserHardwareFaults.transform(val);
3252
+ this.hasChanged = true;
3253
+ }
3254
+ }
3255
+ public get flowSensorFault(): number { return typeof this.data.flowSensorFault === 'undefined' ? undefined : this.data.flowSensorFault.val; }
3256
+ public set flowSensorFault(val: number) {
3257
+ if (this.flowSensorFault !== val) {
3258
+ this.data.flowSensorFault = sys.board.valueMaps.chemDoserHardwareFaults.transform(val);
3259
+ this.hasChanged = true;
3260
+ }
3261
+ }
3262
+ public get comms(): number { return typeof this.data.comms === 'undefined' ? undefined : this.data.comms.val; }
3263
+ public set comms(val: number) {
3264
+ if (this.comms !== val) {
3265
+ this.data.comms = sys.board.valueMaps.chemDoserStatus.transform(val);
3266
+ this.hasChanged = true;
3267
+ }
3268
+ }
3269
+ public get freezeProtect(): number { return typeof this.data.freezeProtect === 'undefined' ? undefined : this.data.freezeProtect.val; }
3270
+ public set freezeProtect(val: number) {
3271
+ if (this.freezeProtect !== val) {
3272
+ this.data.freezeProtect = sys.board.valueMaps.chemDoserAlarms.transform(val);
3273
+ this.hasChanged = true;
3274
+ }
3275
+ }
3276
+
2834
3277
  }
2835
3278
  export class ChemControllerStateAlarms extends ChildEqState {
2836
3279
  //ctor(data): ChemControllerStateWarnings { return new ChemControllerStateWarnings(data, name || 'alarms'); }