nodejs-poolcontroller 7.5.1 → 7.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -0
  2. package/.github/ISSUE_TEMPLATE/2-docs.md +12 -0
  3. package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -0
  4. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  5. package/Changelog +19 -0
  6. package/Dockerfile +3 -3
  7. package/README.md +13 -8
  8. package/app.ts +1 -1
  9. package/config/Config.ts +38 -2
  10. package/config/VersionCheck.ts +27 -12
  11. package/controller/Constants.ts +2 -1
  12. package/controller/Equipment.ts +193 -9
  13. package/controller/Errors.ts +10 -0
  14. package/controller/Lockouts.ts +503 -0
  15. package/controller/State.ts +269 -64
  16. package/controller/boards/AquaLinkBoard.ts +1000 -0
  17. package/controller/boards/BoardFactory.ts +4 -0
  18. package/controller/boards/EasyTouchBoard.ts +468 -144
  19. package/controller/boards/IntelliCenterBoard.ts +466 -307
  20. package/controller/boards/IntelliTouchBoard.ts +37 -5
  21. package/controller/boards/NixieBoard.ts +671 -141
  22. package/controller/boards/SystemBoard.ts +1397 -641
  23. package/controller/comms/Comms.ts +462 -362
  24. package/controller/comms/messages/Messages.ts +174 -30
  25. package/controller/comms/messages/config/ChlorinatorMessage.ts +6 -3
  26. package/controller/comms/messages/config/CircuitMessage.ts +1 -0
  27. package/controller/comms/messages/config/ExternalMessage.ts +10 -8
  28. package/controller/comms/messages/config/HeaterMessage.ts +141 -29
  29. package/controller/comms/messages/config/OptionsMessage.ts +9 -2
  30. package/controller/comms/messages/config/PumpMessage.ts +53 -35
  31. package/controller/comms/messages/config/ScheduleMessage.ts +33 -25
  32. package/controller/comms/messages/config/ValveMessage.ts +2 -2
  33. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +38 -86
  34. package/controller/comms/messages/status/EquipmentStateMessage.ts +59 -23
  35. package/controller/comms/messages/status/HeaterStateMessage.ts +57 -3
  36. package/controller/comms/messages/status/IntelliChemStateMessage.ts +56 -8
  37. package/controller/comms/messages/status/PumpStateMessage.ts +23 -1
  38. package/controller/nixie/Nixie.ts +1 -1
  39. package/controller/nixie/bodies/Body.ts +3 -0
  40. package/controller/nixie/chemistry/ChemController.ts +164 -51
  41. package/controller/nixie/chemistry/Chlorinator.ts +137 -88
  42. package/controller/nixie/circuits/Circuit.ts +51 -19
  43. package/controller/nixie/heaters/Heater.ts +241 -31
  44. package/controller/nixie/pumps/Pump.ts +488 -206
  45. package/controller/nixie/schedules/Schedule.ts +91 -35
  46. package/controller/nixie/valves/Valve.ts +1 -1
  47. package/defaultConfig.json +20 -0
  48. package/package.json +21 -21
  49. package/web/Server.ts +94 -49
  50. package/web/bindings/aqualinkD.json +505 -0
  51. package/web/bindings/influxDB.json +71 -1
  52. package/web/bindings/mqtt.json +98 -39
  53. package/web/bindings/mqttAlt.json +59 -1
  54. package/web/interfaces/baseInterface.ts +1 -0
  55. package/web/interfaces/httpInterface.ts +23 -2
  56. package/web/interfaces/influxInterface.ts +45 -10
  57. package/web/interfaces/mqttInterface.ts +114 -54
  58. package/web/services/config/Config.ts +55 -132
  59. package/web/services/state/State.ts +81 -4
  60. package/web/services/state/StateSocket.ts +4 -4
  61. package/web/services/utilities/Utilities.ts +8 -6
  62. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -52
  63. package/config copy.json +0 -300
  64. package/issue_template.md +0 -52
@@ -77,7 +77,6 @@ export class NixieChemControllerCollection extends NixieEquipmentCollection<Nixi
77
77
  logger.info(`Initializing chemController ${cc.name}`);
78
78
  // First check to make sure it isnt already there.
79
79
  if (typeof this.find(elem => elem.id === cc.id) === 'undefined') {
80
-
81
80
  let ncc = NixieChemControllerBase.create(this.controlPanel, cc);
82
81
  this.push(ncc);
83
82
  }
@@ -241,24 +240,28 @@ export class NixieIntelliChemController extends NixieChemControllerBase {
241
240
  }
242
241
  if (isNaN(pHSetpoint) || pHSetpoint > type.ph.max || pHSetpoint < type.ph.min) return Promise.reject(new InvalidEquipmentDataError(`Invalid pH setpoint ${pHSetpoint}`, 'ph.setpoint', pHSetpoint));
243
242
  if (isNaN(orpSetpoint) || orpSetpoint > type.orp.max || orpSetpoint < type.orp.min) return Promise.reject(new InvalidEquipmentDataError(`Invalid orp setpoint`, 'orp.setpoint', orpSetpoint));
244
- let phTolerance = typeof data.ph.tolerance !== 'undefined' ? data.ph.tolerance : chem.ph.tolerance;
245
- let orpTolerance = typeof data.orp.tolerance !== 'undefined' ? data.orp.tolerance : chem.orp.tolerance;
246
- if (typeof data.ph.tolerance !== 'undefined') {
247
- if (typeof data.ph.tolerance.enabled !== 'undefined') phTolerance.enabled = utils.makeBool(data.ph.tolerance.enabled);
248
- if (typeof data.ph.tolerance.low !== 'undefined') phTolerance.low = parseFloat(data.ph.tolerance.low);
249
- if (typeof data.ph.tolerance.high !== 'undefined') phTolerance.high = parseFloat(data.ph.tolerance.high);
250
- if (isNaN(phTolerance.low)) phTolerance.low = type.ph.min;
251
- if (isNaN(phTolerance.high)) phTolerance.high = type.ph.max;
252
- }
253
- if (typeof data.orp.tolerance !== 'undefined') {
254
- if (typeof data.orp.tolerance.enabled !== 'undefined') orpTolerance.enabled = utils.makeBool(data.orp.tolerance.enabled);
255
- if (typeof data.orp.tolerance.low !== 'undefined') orpTolerance.low = parseFloat(data.orp.tolerance.low);
256
- if (typeof data.orp.tolerance.high !== 'undefined') orpTolerance.high = parseFloat(data.orp.tolerance.high);
257
- if (isNaN(orpTolerance.low)) orpTolerance.low = type.orp.min;
258
- if (isNaN(orpTolerance.high)) orpTolerance.high = type.orp.max;
259
- }
260
- let phEnabled = typeof data.ph.enabled !== 'undefined' ? utils.makeBool(data.ph.enabled) : chem.ph.enabled;
261
- let orpEnabled = typeof data.orp.enabled !== 'undefined' ? utils.makeBool(data.orp.enabled) : chem.orp.enabled;
243
+ let phTolerance = typeof data.ph !== 'undefined' && typeof data.ph.tolerance !== 'undefined' ? data.ph.tolerance : chem.ph.tolerance;
244
+ if (typeof data.ph !== 'undefined') {
245
+ if (typeof data.ph.tolerance !== 'undefined') {
246
+ if (typeof data.ph.tolerance.enabled !== 'undefined') phTolerance.enabled = utils.makeBool(data.ph.tolerance.enabled);
247
+ if (typeof data.ph.tolerance.low !== 'undefined') phTolerance.low = parseFloat(data.ph.tolerance.low);
248
+ if (typeof data.ph.tolerance.high !== 'undefined') phTolerance.high = parseFloat(data.ph.tolerance.high);
249
+ if (isNaN(phTolerance.low)) phTolerance.low = type.ph.min;
250
+ if (isNaN(phTolerance.high)) phTolerance.high = type.ph.max;
251
+ }
252
+ }
253
+ let phEnabled = typeof data.ph !== 'undefined' && typeof data.ph.enabled !== 'undefined' ? utils.makeBool(data.ph.enabled) : chem.ph.enabled;
254
+ let orpTolerance = typeof data.orp !== 'undefined' && typeof data.orp.tolerance !== 'undefined' ? data.orp.tolerance : chem.orp.tolerance;
255
+ if (typeof data.orp !== 'undefined') {
256
+ if (typeof data.orp.tolerance !== 'undefined') {
257
+ if (typeof data.orp.tolerance.enabled !== 'undefined') orpTolerance.enabled = utils.makeBool(data.orp.tolerance.enabled);
258
+ if (typeof data.orp.tolerance.low !== 'undefined') orpTolerance.low = parseFloat(data.orp.tolerance.low);
259
+ if (typeof data.orp.tolerance.high !== 'undefined') orpTolerance.high = parseFloat(data.orp.tolerance.high);
260
+ if (isNaN(orpTolerance.low)) orpTolerance.low = type.orp.min;
261
+ if (isNaN(orpTolerance.high)) orpTolerance.high = type.orp.max;
262
+ }
263
+ }
264
+ let orpEnabled = typeof data.orp !== 'undefined' && typeof data.orp.enabled !== 'undefined' ? utils.makeBool(data.orp.enabled) : chem.orp.enabled;
262
265
  let siCalcType = typeof data.siCalcType !== 'undefined' ? sys.board.valueMaps.siCalcTypes.encode(data.siCalcType, 0) : chem.siCalcType;
263
266
  schem.siCalcType = chem.siCalcType = siCalcType;
264
267
  schem.ph.tank.capacity = chem.ph.tank.capacity = 6;
@@ -288,6 +291,8 @@ export class NixieIntelliChemController extends NixieChemControllerBase {
288
291
  chem.ph.tolerance.enabled = phTolerance.enabled;
289
292
  chem.ph.tolerance.low = phTolerance.low;
290
293
  chem.ph.tolerance.high = phTolerance.high;
294
+ schem.ph.tank.level = acidTankLevel;
295
+ schem.orp.tank.level = orpTankLevel;
291
296
  chem.orp.tolerance.enabled = orpTolerance.enabled;
292
297
  chem.orp.tolerance.low = orpTolerance.low;
293
298
  chem.orp.tolerance.high = orpTolerance.high;
@@ -340,6 +345,7 @@ export class NixieIntelliChemController extends NixieChemControllerBase {
340
345
  out.setPayloadByte(9, this.chem.cyanuricAcid);
341
346
  out.setPayloadByte(10, Math.floor(this.chem.alkalinity / 256) || 0);
342
347
  out.setPayloadByte(12, Math.round(this.chem.alkalinity % 256) || 0);
348
+ logger.verbose(`Nixie: ${this.chem.name} sending IntelliChem settings action 146`);
343
349
  conn.queueSendMessage(out);
344
350
  });
345
351
  }
@@ -622,19 +628,14 @@ export class NixieChemController extends NixieChemControllerBase {
622
628
  // Calculate all the alarms. These are only informational at this point.
623
629
  let setupValid = true;
624
630
  if (this.flowSensor.sensor.type === 0) {
625
- if (!schem.isBodyOn) schem.alarms.flow = 0;
631
+ // When there is no flow sensor we always use the body to determine flow. This means that the
632
+ // flow alarm can never be triggered.
633
+ schem.alarms.flow = 0;
626
634
  }
627
635
  else {
628
- if (this.flowSensor.sensor.type === 1) {
629
- schem.alarms.flow = schem.isBodyOn === schem.flowDetected ? 0 : 1;
630
- }
631
- else {
632
- // both flow and pressure sensors (type 2 & 4)
633
- if (schem.isBodyOn && !schem.flowDetected || !schem.isBodyOn && schem.flowDetected) {
634
- schem.alarms.flow = 1;
635
- }
636
- else schem.alarms.flow = 0;
637
- }
636
+ // If the body is on and there is no flow detected then we need
637
+ // to indicate this to the user.
638
+ schem.alarms.flow = schem.isBodyOn && !schem.flowDetected ? 1 : 0;
638
639
  }
639
640
  schem.ph.dailyVolumeDosed = schem.ph.calcDoseHistory();
640
641
  schem.orp.dailyVolumeDosed = schem.orp.calcDoseHistory();
@@ -666,7 +667,7 @@ export class NixieChemController extends NixieChemControllerBase {
666
667
  if (probeType !== 0 && chem.orp.tolerance.enabled)
667
668
  schem.alarms.orp = schem.orp.level < chem.orp.tolerance.low ? 16 : schem.orp.level > chem.orp.tolerance.high ? 8 : 0;
668
669
  else schem.alarms.orp = 0;
669
- schem.warnings.chlorinatorCommError = useChlorinator && state.chlorinators.getItemById(1).status & 0xF0 ? 16 : 0;
670
+ schem.warnings.chlorinatorCommError = useChlorinator && schem.isBodyOn && state.chlorinators.getItemById(1).status & 0xF0 ? 16 : 0;
670
671
  schem.warnings.pHLockout = useChlorinator === false && probeType !== 0 && pumpType !== 0 && schem.ph.level >= chem.orp.phLockout ? 1 : 0;
671
672
  }
672
673
  else {
@@ -674,6 +675,7 @@ export class NixieChemController extends NixieChemControllerBase {
674
675
  schem.warnings.chlorinatorCommError = 0;
675
676
  schem.warnings.pHLockout = 0;
676
677
  }
678
+ schem.orp.freezeProtect = (state.freeze && chem.orp.disableOnFreeze && schem.isBodyOn);
677
679
  }
678
680
  else {
679
681
  schem.warnings.chlorinatorCommError = 0;
@@ -681,6 +683,7 @@ export class NixieChemController extends NixieChemControllerBase {
681
683
  schem.warnings.orpDailyLimitReached = 0;
682
684
  schem.alarms.orp = 0;
683
685
  schem.warnings.pHLockout = 0;
686
+ schem.orp.freezeProtect = false;
684
687
  }
685
688
  if (this.chem.ph.enabled) {
686
689
  let pumpType = chem.ph.pump.type;
@@ -709,11 +712,21 @@ export class NixieChemController extends NixieChemControllerBase {
709
712
  else schem.alarms.pH = 0;
710
713
  }
711
714
  else schem.alarms.pH = 0;
715
+ schem.ph.freezeProtect = (state.freeze && chem.ph.disableOnFreeze && schem.isBodyOn);
712
716
  }
717
+ else {
718
+ schem.alarms.pHTank = 0;
719
+ schem.warnings.pHDailyLimitReached = 0;
720
+ schem.alarms.pH = 0;
721
+ schem.ph.freezeProtect = false;
722
+ }
723
+
713
724
  if (chem.lsiRange.enabled) {
714
725
  schem.warnings.waterChemistry = schem.saturationIndex < chem.lsiRange.low ? 1 : schem.saturationIndex > chem.lsiRange.high ? 2 : 0;
715
726
  }
716
727
  else schem.warnings.waterChemistry = 0;
728
+
729
+ schem.alarms.freezeProtect = (schem.ph.freezeProtect || schem.orp.freezeProtect) ? sys.board.valueMaps.chemControllerAlarms.getValue('freezeprotect') : 0;
717
730
  } catch (err) { logger.error(`Error processing chem controller ${this.chem.name} alarms: ${err.message}`); }
718
731
  }
719
732
  private async checkHardwareStatusAsync(connectionId: string, deviceBinding: string) {
@@ -752,6 +765,7 @@ export class NixieChemController extends NixieChemControllerBase {
752
765
  }
753
766
  else
754
767
  schem.alarms.orpPumpFault = schem.alarms.chlorFault = 0;
768
+
755
769
  }
756
770
  else schem.alarms.orpPumpFault = schem.alarms.chlorFault = schem.alarms.orpProbeFault = 0;
757
771
  if (chem.ph.enabled) {
@@ -869,7 +883,7 @@ class NixieChemical extends NixieChildEquipment {
869
883
  chemical.maxDosingTime = typeof data.maxDosingTime !== 'undefined' ? parseInt(data.maxDosingTime, 10) : chemical.maxDosingTime;
870
884
  chemical.maxDosingVolume = typeof data.maxDosingVolume !== 'undefined' ? parseInt(data.maxDosingVolume, 10) : chemical.maxDosingVolume;
871
885
  chemical.startDelay = typeof data.startDelay !== 'undefined' ? parseFloat(data.startDelay) : chemical.startDelay;
872
- chemical.maxDailyVolume = typeof data.maxDailyVolume !== 'undefined' ? parseInt(data.maxDailyVolume, 10) : chemical.maxDailyVolume;
886
+ chemical.maxDailyVolume = typeof data.maxDailyVolume !== 'undefined' ? typeof data.maxDailyVolume === 'number' ? data.maxDailyVolume : parseInt(data.maxDailyVolume, 10) : chemical.maxDailyVolume;
873
887
  }
874
888
  } catch (err) { logger.error(`setDosing: ${err.message}`); return Promise.reject(err); }
875
889
  }
@@ -893,7 +907,7 @@ class NixieChemical extends NixieChildEquipment {
893
907
  this._stoppingMix = true;
894
908
  this.suspendPolling = true;
895
909
  if (typeof this.currentMix !== 'undefined') logger.debug(`Stopping ${schem.chemType} mix and clearing the current mix object.`);
896
- if (typeof this.chemController.orp.orp.useChlorinator !== 'undefined' && this.chemController.orp.orp.useChlorinator && this.chemController.orp.orp.dosingMethod > 0)
910
+ if (typeof this.chemController.orp.orp.useChlorinator !== 'undefined' && this.chemController.orp.orp.useChlorinator && this.chemController.orp.orp.chlorDosingMethod > 0)
897
911
  schem.chlor.isDosing = false;
898
912
  else
899
913
  schem.pump.isDosing = false;
@@ -967,12 +981,20 @@ class NixieChemical extends NixieChildEquipment {
967
981
  return;
968
982
  }
969
983
  this._processingMix = true;
984
+ if (!this.chemical.enabled) {
985
+ // The chemical is not enabled so we need to ditch the mixing if it is currently underway.
986
+ await this.stopMixing(schem);
987
+ return;
988
+
989
+
990
+ }
991
+
970
992
  let dt = new Date().getTime();
971
993
  await this.initMixChemicals(schem, mixingTime);
972
994
  if (this._stoppingMix) return;
973
995
  schem.chlor.isDosing = schem.pump.isDosing = false;
974
- if (!this.chemical.flowOnlyMixing || (schem.chemController.isBodyOn && this.chemController.flowDetected)) {
975
- if (this.chemType === 'orp' && typeof this.chemController.orp.orp.useChlorinator !== 'undefined' && this.chemController.orp.orp.useChlorinator && this.chemController.orp.orp.dosingMethod > 0) {
996
+ if (!this.chemical.flowOnlyMixing || (schem.chemController.isBodyOn && this.chemController.flowDetected && !schem.freezeProtect)) {
997
+ if (this.chemType === 'orp' && typeof this.chemController.orp.orp.useChlorinator !== 'undefined' && this.chemController.orp.orp.useChlorinator && this.chemController.orp.orp.chlorDosingMethod > 0) {
976
998
  if (state.chlorinators.getItemById(1).currentOutput !== 0) {
977
999
  logger.debug(`Chem mixing ORP (chlorinator) paused waiting for chlor current output to be 0%. Mix time remaining: ${utils.formatDuration(schem.mixTimeRemaining)} `);
978
1000
  return;
@@ -1015,7 +1037,7 @@ class NixieChemical extends NixieChildEquipment {
1015
1037
  }
1016
1038
  public async cancelDosing(schem: ChemicalState, reason: string): Promise<void> {
1017
1039
  try {
1018
- if (typeof this.chemController.orp.orp.useChlorinator !== 'undefined' && this.chemController.orp.orp.useChlorinator && this.chemController.orp.orp.dosingMethod > 0) {
1040
+ if (typeof this.chemController.orp.orp.useChlorinator !== 'undefined' && this.chemController.orp.orp.useChlorinator && this.chemController.orp.orp.chlorDosingMethod > 0) {
1019
1041
  if (!this.chlor.chlor.superChlor) await this.chlor.stopDosing(schem, reason);
1020
1042
  // for chlor, we want 15 minute intervals
1021
1043
  if (schem.doseHistory.length) {
@@ -1080,8 +1102,9 @@ export class NixieChemPump extends NixieChildEquipment {
1080
1102
  public async setPumpAsync(spump: ChemicalPumpState, data: any): Promise<void> {
1081
1103
  try {
1082
1104
  if (typeof data !== 'undefined') {
1083
- this.pump.enabled = typeof data.enabled !== 'undefined' ? data.enabled : this.pump.enabled;
1105
+ //this.pump.enabled = typeof data.enabled !== 'undefined' ? data.enabled : this.pump.enabled;
1084
1106
  this.pump.type = typeof data.type !== 'undefined' ? data.type : this.pump.type;
1107
+ this.pump.enabled = this.pump.type !== 0;
1085
1108
  this.pump.ratedFlow = typeof data.ratedFlow !== 'undefined' ? data.ratedFlow : this.pump.ratedFlow;
1086
1109
  this.pump.connectionId = typeof data.connectionId !== 'undefined' ? data.connectionId : this.pump.connectionId;
1087
1110
  this.pump.deviceBinding = typeof data.deviceBinding !== 'undefined' ? data.deviceBinding : this.pump.deviceBinding;
@@ -1141,7 +1164,7 @@ export class NixieChemPump extends NixieChildEquipment {
1141
1164
  await this.chemical.initDose(schem);
1142
1165
  let delay = 0;
1143
1166
  // Check to see if we are in delay. The start delay for the configuration is in minutes.
1144
- if (isBodyOn) {
1167
+ if (isBodyOn && !schem.freezeProtect) {
1145
1168
  // The remaining delay = delay time - (current time - on time).
1146
1169
  let timeElapsed = new Date().getTime() - this.chemical.chemController.bodyOnTime;
1147
1170
  delay = Math.max(0, ((this.chemical.chemical.startDelay * 60) * 1000) - timeElapsed);
@@ -1165,6 +1188,12 @@ export class NixieChemPump extends NixieChildEquipment {
1165
1188
  // the chlorinator to work more smoothly.
1166
1189
  await this.chemical.cancelDosing(schem, 'no flow');
1167
1190
  }
1191
+ else if (schem.freezeProtect) {
1192
+ logger.info(`Chem pump freeze protection`);
1193
+ // We originally thought that we could wait to turn the dosing on but instead we will cancel the dose. This will allow
1194
+ // the chlorinator to work more smoothly.
1195
+ await this.chemical.cancelDosing(schem, 'freeze');
1196
+ }
1168
1197
  else if (schem.tank.level <= 0) {
1169
1198
  logger.info(`Chem tank ran dry with ${schem.currentDose.volumeRemaining}mL remaining`);
1170
1199
  await this.chemical.cancelDosing(schem, 'empty tank');
@@ -1373,7 +1402,7 @@ export class NixieChemChlor extends NixieChildEquipment {
1373
1402
  try {
1374
1403
  await this.turnOn(schem);
1375
1404
  if (schlor.currentOutput !== 100) {
1376
- logger.warn(`Chlor dose not added because current output is not 100%`);
1405
+ logger.silly(`Chlor dose not added because current output is not 100%`);
1377
1406
  }
1378
1407
  else {
1379
1408
  if (typeof dose._lastLatch !== 'undefined') {
@@ -1424,7 +1453,7 @@ export class NixieChemChlor extends NixieChildEquipment {
1424
1453
  }
1425
1454
  public async turnOff(schem: ChemicalState): Promise<ChlorinatorState> {
1426
1455
  try {
1427
- logger.info(`Turning off the chlorinator`);
1456
+ //logger.info(`Turning off the chlorinator`);
1428
1457
  let chlor = sys.chlorinators.getItemById(1);
1429
1458
  let schlor = state.chlorinators.getItemById(1);
1430
1459
  if (schlor.currentOutput === 0 && schlor.targetOutput === 0 && !schlor.superChlor && chlor.disabled && !chlor.isDosing) {
@@ -1470,6 +1499,15 @@ export class NixieChemicalPh extends NixieChemical {
1470
1499
  super(controller, chemical);
1471
1500
  this.chemType = 'acid';
1472
1501
  this.probe = new NixieChemProbePh(this, chemical.probe);
1502
+ let sph = state.chemControllers.getItemById(controller.id).ph;
1503
+ if (!this.ph.enabled || !this.ph.pump.enabled) {
1504
+ this.ph.doserType = 0;
1505
+ sph.chemType = 'none';
1506
+ }
1507
+ else {
1508
+ this.ph.doserType = 1; // External Relay
1509
+ sph.chemType = (this.ph.phSupply === 0) ? 'base' : 'acid';
1510
+ }
1473
1511
  }
1474
1512
  public async setPhAsync(sph: ChemicalPhState, data: any) {
1475
1513
  try {
@@ -1485,6 +1523,17 @@ export class NixieChemicalPh extends NixieChemical {
1485
1523
  this.ph.acidType = typeof data.acidType !== 'undefined' ? data.acidType : this.ph.acidType;
1486
1524
  this.ph.flowReadingsOnly = typeof data.flowReadingsOnly !== 'undefined' ? utils.makeBool(data.flowReadingsOnly) : this.ph.flowReadingsOnly;
1487
1525
  sph.level = typeof data.level !== 'undefined' && !isNaN(parseFloat(data.level)) ? parseFloat(data.level) : sph.level;
1526
+ this.ph.disableOnFreeze = typeof data.disableOnFreeze !== 'undefined' ? utils.makeBool(data.disableOnFreeze) : this.ph.disableOnFreeze;
1527
+ if (!this.ph.disableOnFreeze) sph.freezeProtect = false;
1528
+ if (!this.ph.enabled || !this.ph.pump.enabled) {
1529
+ this.ph.doserType = 0;
1530
+ sph.chemType = 'none';
1531
+ }
1532
+ else {
1533
+ this.ph.doserType = 1; // External Relay
1534
+ sph.chemType = (this.ph.phSupply === 0) ? 'base' : 'acid';
1535
+ }
1536
+
1488
1537
  if (typeof data.tolerance !== 'undefined') {
1489
1538
  if (typeof data.tolerance.enabled !== 'undefined') this.ph.tolerance.enabled = utils.makeBool(data.tolerance.enabled);
1490
1539
  if (typeof data.tolerance.low === 'number') this.ph.tolerance.low = data.tolerance.low;
@@ -1525,6 +1574,10 @@ export class NixieChemicalPh extends NixieChemical {
1525
1574
  logger.debug(`Begin check ${sph.chemType} dosing status = ${status}`);
1526
1575
  let demand = sph.calcDemand(chem);
1527
1576
  sph.demand = Math.max(demand, 0);
1577
+ if (!chem.ph.enabled) {
1578
+ await this.cancelDosing(sph, 'disabled');
1579
+ return;
1580
+ }
1528
1581
  if (sph.suspendDosing) {
1529
1582
  // Kill off the dosing and make sure the pump isn't running. Let's force the issue here.
1530
1583
  await this.cancelDosing(sph, 'suspended');
@@ -1592,6 +1645,8 @@ export class NixieChemicalPh extends NixieChemical {
1592
1645
  // Check the setpoint and the current level to see if we need to dose.
1593
1646
  if (!sph.chemController.isBodyOn)
1594
1647
  await this.cancelDosing(sph, 'body off');
1648
+ else if (sph.freezeProtect)
1649
+ await this.cancelDosing(sph, 'freeze');
1595
1650
  else if (!sph.chemController.flowDetected)
1596
1651
  await this.cancelDosing(sph, 'no flow');
1597
1652
  else if (demand <= 0)
@@ -1740,6 +1795,23 @@ export class NixieChemicalORP extends NixieChemical {
1740
1795
  this.orp = chemical;
1741
1796
  this.probe = new NixieChemProbeORP(this, chemical.probe);
1742
1797
  this.chlor = new NixieChemChlor(this, chemical.chlor);
1798
+ let sorp = state.chemControllers.getItemById(controller.id).orp;
1799
+ if (!this.orp.enabled) {
1800
+ this.orp.doserType = 0;
1801
+ sorp.chemType = 'none';
1802
+ }
1803
+ else if (sorp.useChlorinator) {
1804
+ this.orp.doserType = 2;
1805
+ sorp.chemType = 'chlorine';
1806
+ }
1807
+ else if (this.orp.pump.enabled) {
1808
+ this.orp.doserType = 1;
1809
+ sorp.chemType = 'chlorine';
1810
+ }
1811
+ else {
1812
+ this.orp.doserType = 0;
1813
+ sorp.chemType = 'none';
1814
+ }
1743
1815
  }
1744
1816
  public get logFilename() { return `chemDosage_orp.log`; }
1745
1817
  public async setORPAsync(sorp: ChemicalORPState, data: any) {
@@ -1750,6 +1822,8 @@ export class NixieChemicalORP extends NixieChemical {
1750
1822
  sorp.level = typeof data.level !== 'undefined' && !isNaN(parseFloat(data.level)) ? parseFloat(data.level) : sorp.level;
1751
1823
  this.orp.phLockout = typeof data.phLockout !== 'undefined' && !isNaN(parseFloat(data.phLockout)) ? parseFloat(data.phLockout) : this.orp.phLockout;
1752
1824
  this.orp.flowReadingsOnly = typeof data.flowReadingsOnly !== 'undefined' ? utils.makeBool(data.flowReadingsOnly) : this.orp.flowReadingsOnly;
1825
+ this.orp.disableOnFreeze = typeof data.disableOnFreeze !== 'undefined' ? utils.makeBool(data.disableOnFreeze) : this.orp.disableOnFreeze;
1826
+ if (!this.orp.disableOnFreeze) sorp.freezeProtect = false;
1753
1827
  if (typeof data.chlorDosingMethod !== 'undefined') { this.orp.chlorDosingMethod = data.chlorDosingMethod; }
1754
1828
  await this.setDosing(this.orp, data);
1755
1829
  await this.setMixing(this.orp, data);
@@ -1757,6 +1831,23 @@ export class NixieChemicalORP extends NixieChemical {
1757
1831
  await this.tank.setTankAsync(sorp.tank, data.tank);
1758
1832
  await this.pump.setPumpAsync(sorp.pump, data.pump);
1759
1833
  await this.chlor.setChlorAsync(sorp.chlor, data);
1834
+ if (!this.orp.enabled) {
1835
+ this.orp.doserType = 0;
1836
+ sorp.chemType = 'none';
1837
+ }
1838
+ else if (sorp.useChlorinator) {
1839
+ this.orp.doserType = 2;
1840
+ sorp.chemType = 'chlorine';
1841
+ }
1842
+ else if (this.orp.pump.enabled) {
1843
+ this.orp.doserType = 1;
1844
+ sorp.chemType = 'chlorine';
1845
+ }
1846
+ else {
1847
+ this.orp.doserType = 0;
1848
+ sorp.chemType = 'none';
1849
+ }
1850
+
1760
1851
  this.orp.setpoint = sorp.setpoint = typeof data.setpoint !== 'undefined' ? parseInt(data.setpoint, 10) : this.orp.setpoint;
1761
1852
  if (typeof data.tolerance !== 'undefined') {
1762
1853
  if (typeof data.tolerance.enabled !== 'undefined') this.orp.tolerance.enabled = utils.makeBool(data.tolerance.enabled);
@@ -1801,7 +1892,7 @@ export class NixieChemicalORP extends NixieChemical {
1801
1892
  }
1802
1893
  public async cancelDosing(sorp: ChemicalORPState, reason: string): Promise<void> {
1803
1894
  try {
1804
- if (typeof sorp.useChlorinator !== 'undefined' && sorp.useChlorinator && this.chemController.orp.orp.dosingMethod > 0) {
1895
+ if (typeof sorp.useChlorinator !== 'undefined' && sorp.useChlorinator && this.chemController.orp.orp.chlorDosingMethod > 0) {
1805
1896
  await this.chlor.stopDosing(sorp, reason);
1806
1897
  // for chlor, we want 15 minute intervals
1807
1898
  if (sorp.doseHistory.length) {
@@ -1850,7 +1941,7 @@ export class NixieChemicalORP extends NixieChemical {
1850
1941
  this.currentMix.set({ time: schem.mixTimeRemaining, timeMixed: 0, isManual: true });
1851
1942
  }
1852
1943
  else
1853
- if (typeof this.chemController.orp.orp.useChlorinator !== 'undefined' && this.chemController.orp.orp.useChlorinator && this.chemController.orp.orp.dosingMethod > 0) {
1944
+ if (typeof this.chemController.orp.orp.useChlorinator !== 'undefined' && this.chemController.orp.orp.useChlorinator && this.chemController.orp.orp.chlorDosingMethod > 0) {
1854
1945
  // if last dose was within 15 minutes, set mix time to 15 mins-(now-lastdose)
1855
1946
  // if no dose in last 15, then we should be monitoring
1856
1947
  await this.chlor.stopDosing(schem, 'mix override'); // ensure chlor has stopped
@@ -1872,7 +1963,7 @@ export class NixieChemicalORP extends NixieChemical {
1872
1963
  }
1873
1964
  }
1874
1965
  else
1875
- if (typeof this.chemController.orp.orp.useChlorinator !== 'undefined' && this.chemController.orp.orp.useChlorinator && this.chemController.orp.orp.dosingMethod > 0)
1966
+ if (typeof this.chemController.orp.orp.useChlorinator !== 'undefined' && this.chemController.orp.orp.useChlorinator && this.chemController.orp.orp.chlorDosingMethod > 0)
1876
1967
  this.currentMix.set({ time: this.chlor.chlorInterval * 60, timeMixed: 0 });
1877
1968
  else
1878
1969
  this.currentMix.set({ time: this.chemical.mixingTime, timeMixed: 0 });
@@ -1888,6 +1979,11 @@ export class NixieChemicalORP extends NixieChemical {
1888
1979
  }
1889
1980
  public async checkDosing(chem: ChemController, sorp: ChemicalORPState): Promise<void> {
1890
1981
  try {
1982
+ if (!chem.orp.enabled) {
1983
+ await this.cancelDosing(sorp, 'disabled');
1984
+ return;
1985
+ }
1986
+
1891
1987
  let status = sys.board.valueMaps.chemControllerDosingStatus.getName(sorp.dosingStatus);
1892
1988
  if (!chem.orp.flowReadingsOnly || (chem.orp.flowReadingsOnly && sorp.chemController.flowDetected)) {
1893
1989
  // demand in raw mV
@@ -1897,6 +1993,7 @@ export class NixieChemicalORP extends NixieChemical {
1897
1993
  sorp.appendDemand(new Date().valueOf(), sorp.demand);
1898
1994
  }
1899
1995
  if (chem.orp.useChlorinator && chem.orp.chlorDosingMethod === 0) return; // if chlor is managing itself, don't even cancel/stop as it will set the flags on the chlor
1996
+
1900
1997
  if (sorp.suspendDosing) {
1901
1998
  // Kill off the dosing and make sure the pump isn't running. Let's force the issue here.
1902
1999
  await this.cancelDosing(sorp, 'suspended');
@@ -1934,6 +2031,9 @@ export class NixieChemicalORP extends NixieChemical {
1934
2031
  }
1935
2032
  else await this.cancelDosing(sorp, 'empty tank');
1936
2033
  }
2034
+ else if (sorp.freezeProtect) {
2035
+ await this.cancelDosing(sorp, 'freeze');
2036
+ }
1937
2037
  else if (sorp.dailyLimitReached && !chem.orp.useChlorinator) {
1938
2038
  await this.cancelDosing(sorp, 'daily limit');
1939
2039
  }
@@ -2185,7 +2285,13 @@ export class NixieChemProbePh extends NixieChemProbe {
2185
2285
  if (this.probe.type !== 1 || this.probe.deviceBinding !== data.deviceBinding) {
2186
2286
  let disabledFeed = this.probe;
2187
2287
  disabledFeed.remFeedEnabled = false;
2188
- await this.setRemoteREMFeed(disabledFeed);
2288
+ try {
2289
+ // if remote REM server was not found this would error out
2290
+ await this.setRemoteREMFeed(disabledFeed);
2291
+ }
2292
+ catch (err){
2293
+ logger.silly(`Disabling remote REM connection for PH Probe returned error ${err.message}. Continuing.`)
2294
+ }
2189
2295
  this.probe.remFeedId = undefined;
2190
2296
  }
2191
2297
  await this.setProbeAsync(this.probe, sprobe, data);
@@ -2194,6 +2300,7 @@ export class NixieChemProbePh extends NixieChemProbe {
2194
2300
  sprobe.temperature = typeof data.temperature !== 'undefined' ? parseFloat(data.temperature) : sprobe.temperature;
2195
2301
  sprobe.tempUnits = typeof data.tempUnits !== 'undefined' ? data.tempUnits : sprobe.tempUnits;
2196
2302
  this.probe.feedBodyTemp = typeof data.feedBodyTemp !== 'undefined' ? utils.makeBool(data.feedBodyTemp) : utils.makeBool(this.probe.feedBodyTemp);
2303
+ //this.probe.connectionId = typeof data.connectionId !== 'undefined' ? data.connectionId : this.probe.connectionId;
2197
2304
  await this.setRemoteREMFeed(data);
2198
2305
  }
2199
2306
  } catch (err) { logger.error(`setProbeAsync pH: ${err.message}`); return Promise.reject(err); }
@@ -2234,11 +2341,11 @@ export class NixieChemProbePh extends NixieChemProbe {
2234
2341
  property: "pHLevel",
2235
2342
  sendValue: 'all',
2236
2343
  isActive: data.remFeedEnabled,
2237
- sampling: 1,
2238
- changesOnly: false,
2344
+ //sampling: 1,
2345
+ //changesOnly: false,
2239
2346
  propertyDesc: '[chemController].pHLevel'
2240
2347
  }
2241
- let res = await NixieChemController.putDeviceService(this.probe.connectionId, '/config/feed', d);
2348
+ let res = await NixieChemController.putDeviceService(this.probe.connectionId, '/config/verifyFeed', d);
2242
2349
  if (res.status.code === 200) { this.probe.remFeedEnabled = data.remFeedEnabled; }
2243
2350
  else { logger.warn(`setRemoteREMFeed: Cannot set remote feed. Message:${JSON.stringify(res.status)} for feed: ${JSON.stringify(d)}.`); }
2244
2351
  }
@@ -2291,7 +2398,13 @@ export class NixieChemProbeORP extends NixieChemProbe {
2291
2398
  if (this.probe.type !== 1 || this.probe.deviceBinding !== data.deviceBinding) {
2292
2399
  let disabledFeed = this.probe;
2293
2400
  disabledFeed.remFeedEnabled = false;
2294
- await this.setRemoteREMFeed(disabledFeed);
2401
+ try {
2402
+ // if remote REM server was not found this would error out
2403
+ await this.setRemoteREMFeed(disabledFeed);
2404
+ }
2405
+ catch (err){
2406
+ logger.silly(`Disabling remote REM connection for ORP Probe returned error ${err.message}. Continuing.`)
2407
+ }
2295
2408
  this.probe.remFeedId = undefined;
2296
2409
  }
2297
2410
  await this.setProbeAsync(this.probe, sprobe, data);
@@ -2317,11 +2430,11 @@ export class NixieChemProbeORP extends NixieChemProbe {
2317
2430
  property: 'orpLevel',
2318
2431
  sendValue: 'orp',
2319
2432
  isActive: data.remFeedEnabled,
2320
- sampling: 1,
2321
- changesOnly: false,
2433
+ //sampling: 1,
2434
+ //changesOnly: false,
2322
2435
  propertyDesc: '[chemController].orpLevel'
2323
2436
  }
2324
- let res = await NixieChemController.putDeviceService(this.probe.connectionId, '/config/feed', d);
2437
+ let res = await NixieChemController.putDeviceService(this.probe.connectionId, '/config/verifyFeed', d);
2325
2438
  if (res.status.code === 200) { this.probe.remFeedEnabled = data.remFeedEnabled; }
2326
2439
  else {
2327
2440
  logger.warn(`setRemoteREMFeed: Cannot set remote feed. Message:${JSON.stringify(res.status)} for feed: ${JSON.stringify(d)}.`);