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
@@ -6,9 +6,10 @@ import { logger } from '../../../logger/Logger';
6
6
  import { InterfaceServerResponse, webApp } from "../../../web/Server";
7
7
  import { Timestamp, utils } from '../../Constants';
8
8
  import { EquipmentNotFoundError, EquipmentTimeoutError, InvalidEquipmentDataError, InvalidEquipmentIdError, InvalidOperationError } from '../../Errors';
9
- import { ChemControllerState, ChemicalDoseState, ChemicalORPState, ChemicalPhState, ChemicalProbeORPState, ChemicalProbePHState, ChemicalProbeState, ChemicalPumpState, ChemicalState, ChemicalTankState, ChlorinatorState, state } from "../../State";
9
+ import { ChemControllerState, ChemicalChlorState, ChemicalDoseState, ChemicalORPState, ChemicalPhState, ChemicalProbeORPState, ChemicalProbePHState, ChemicalProbeState, ChemicalPumpState, ChemicalState, ChemicalTankState, ChlorinatorState, state } from "../../State";
10
10
  import { ncp } from '../Nixie';
11
11
  import { INixieControlPanel, NixieChildEquipment, NixieEquipment, NixieEquipmentCollection } from "../NixieEquipment";
12
+ import { NixieChlorinator } from './Chlorinator';
12
13
 
13
14
 
14
15
  export class NixieChemControllerCollection extends NixieEquipmentCollection<NixieChemControllerBase> {
@@ -50,7 +51,7 @@ export class NixieChemControllerCollection extends NixieEquipmentCollection<Nixi
50
51
  ncc = NixieChemControllerBase.create(this.controlPanel, chem);
51
52
  this.push(ncc);
52
53
  let ctype = sys.board.valueMaps.chemControllerTypes.transform(chem.type);
53
- logger.info(`A Chem controller was not found for id #${chem.id} starting ${ctype.desc}`);
54
+ logger.info(`Nixie Chem Controller was created at id #${chem.id} for type ${ctype.desc}`);
54
55
  await ncc.setControllerAsync(data);
55
56
  }
56
57
  else {
@@ -72,9 +73,11 @@ export class NixieChemControllerCollection extends NixieEquipmentCollection<Nixi
72
73
  for (let i = 0; i < controllers.length; i++) {
73
74
  let cc = controllers.getItemByIndex(i);
74
75
  if (cc.master === 1) {
76
+ let type = sys.board.valueMaps.chemControllerTypes.transform(cc.type);
75
77
  logger.info(`Initializing chemController ${cc.name}`);
76
78
  // First check to make sure it isnt already there.
77
79
  if (typeof this.find(elem => elem.id === cc.id) === 'undefined') {
80
+
78
81
  let ncc = NixieChemControllerBase.create(this.controlPanel, cc);
79
82
  this.push(ncc);
80
83
  }
@@ -98,6 +101,19 @@ export class NixieChemControllerCollection extends NixieEquipmentCollection<Nixi
98
101
 
99
102
  } catch (err) { } // Don't bail if we have an error
100
103
  }
104
+ public async deleteChlorAsync(chlor: NixieChlorinator) {
105
+ // if we delete the chlor, make sure it is removed from all REM Chem Controllers
106
+ try {
107
+ for (let i = this.length - 1; i >= 0; i--) {
108
+ try {
109
+ let ncc = this[i] as NixieChemControllerBase;;
110
+ ncc.orp.deleteChlorAsync(chlor);
111
+ } catch (err) { logger.error(`Error deleting chlor from Nixie Chem Controller ${err}`); return Promise.reject(err); }
112
+ }
113
+
114
+ }
115
+ catch (err) { logger.error(`ncp.deleteChlorAsync: ${err.message}`); return Promise.reject(err); }
116
+ }
101
117
  // This is currently not used for anything.
102
118
  /* public async searchIntelliChem(): Promise<number[]> {
103
119
  let arr = [];
@@ -145,12 +161,10 @@ export class NixieChemControllerBase extends NixieEquipment {
145
161
  public chem: ChemController;
146
162
  public syncRemoteREMFeeds(servers) { }
147
163
  public static create(ncp: INixieControlPanel, chem: ChemController): NixieChemControllerBase {
148
- // RKS: 06-25-21 - Keeping the homegrown around for now but I don't really know why we care.
149
164
  let type = sys.board.valueMaps.chemControllerTypes.transform(chem.type);
150
165
  switch (type.name) {
151
166
  case 'intellichem':
152
167
  return new NixieIntelliChemController(ncp, chem);
153
- case 'homegrown':
154
168
  case 'rem':
155
169
  return new NixieChemController(ncp, chem);
156
170
  default:
@@ -194,7 +208,7 @@ export class NixieIntelliChemController extends NixieChemControllerBase {
194
208
  }
195
209
  }
196
210
  catch (err) { logger.error(`Error polling IntelliChem Controller - ${err}`); return Promise.reject(err); }
197
- finally { this.suspendPolling = false; if (!this.closing) this._pollTimer = setTimeout(async () => { try { await self.pollEquipmentAsync() } catch (err) { return Promise.reject(err); } }, this.pollingInterval || 10000); }
211
+ finally { this.suspendPolling = false; if (!this.closing) this._pollTimer = setTimeout(() => { self.pollEquipmentAsync(); }, this.pollingInterval || 10000); }
198
212
  }
199
213
  public async setControllerAsync(data: any) {
200
214
  try {
@@ -203,7 +217,7 @@ export class NixieIntelliChemController extends NixieChemControllerBase {
203
217
  let address = typeof data.address !== 'undefined' ? parseInt(data.address) : chem.address;
204
218
  let name = typeof data.name !== 'undefined' ? data.name : chem.name || `IntelliChem - ${address - 143}`;
205
219
  let type = sys.board.valueMaps.chemControllerTypes.transformByName('intellichem');
206
- // So now we are down to the nitty gritty setting the data for the REM or Homegrown Chem controller.
220
+ // So now we are down to the nitty gritty setting the data for the REM Chem controller.
207
221
  let calciumHardness = typeof data.calciumHardness !== 'undefined' ? parseInt(data.calciumHardness, 10) : chem.calciumHardness;
208
222
  let cyanuricAcid = typeof data.cyanuricAcid !== 'undefined' ? parseInt(data.cyanuricAcid, 10) : chem.cyanuricAcid;
209
223
  let alkalinity = typeof data.alkalinity !== 'undefined' ? parseInt(data.alkalinity, 10) : chem.alkalinity;
@@ -543,6 +557,7 @@ export class NixieChemController extends NixieChemControllerBase {
543
557
  else if (typeof ret.obj.state === 'boolean') v = ret.obj.state;
544
558
  else if (typeof ret.obj.state === 'number') v = utils.makeBool(ret.obj.state);
545
559
  else if (typeof ret.obj.state.val === 'number') v = utils.makeBool(ret.obj.state.val);
560
+ else if (typeof ret.obj.state.value === 'number') v = utils.makeBool(ret.obj.state.value);
546
561
  else v = false;
547
562
  this.flowDetected = schem.flowDetected = v;
548
563
  }
@@ -584,7 +599,6 @@ export class NixieChemController extends NixieChemControllerBase {
584
599
  await this.checkFlowAsync(schem);
585
600
  await this.validateSetupAsync(this.chem, schem);
586
601
  if (this.chem.ph.enabled) await this.ph.probe.setTempCompensationAsync(schem.ph.probe);
587
- // We are not processing Homegrown at this point.
588
602
  // Check each piece of equipment to make sure it is doing its thing.
589
603
  schem.calculateSaturationIndex();
590
604
  this.processAlarms(schem);
@@ -596,15 +610,10 @@ export class NixieChemController extends NixieChemControllerBase {
596
610
  }
597
611
  this._ispolling = false;
598
612
  }
599
- catch (err) { this._ispolling = false; logger.error(`Error polling Chem Controller - ${err}`); return Promise.reject(err); }
613
+ catch (err) { this._ispolling = false; logger.error(`Error polling Chem Controller - ${err}`); }
600
614
  finally {
601
615
  if (!this.closing && !this._ispolling)
602
- this._pollTimer = setTimeout(async () => {
603
- try { await self.pollEquipmentAsync() } catch (err) {
604
- //return Promise.reject(err);
605
- logger.error(err);
606
- }
607
- }, this.pollingInterval || 10000);
616
+ this._pollTimer = setTimeout(() => { self.pollEquipmentAsync(); }, this.pollingInterval || 10000);
608
617
  logger.verbose(`End polling Chem Controller ${this.id}`);
609
618
  }
610
619
  }
@@ -963,10 +972,9 @@ class NixieChemical extends NixieChildEquipment {
963
972
  if (this._stoppingMix) return;
964
973
  schem.chlor.isDosing = schem.pump.isDosing = false;
965
974
  if (!this.chemical.flowOnlyMixing || (schem.chemController.isBodyOn && this.chemController.flowDetected)) {
966
- if (this.chemType === 'orp' && typeof this.chemController.orp.orp.useChlorinator !== 'undefined' && this.chemController.orp.orp.useChlorinator && this.chemController.orp.orp.dosingMethod > 0){
967
- await this.chlor.stopDosing(schem, 'mixing');
975
+ if (this.chemType === 'orp' && typeof this.chemController.orp.orp.useChlorinator !== 'undefined' && this.chemController.orp.orp.useChlorinator && this.chemController.orp.orp.dosingMethod > 0) {
968
976
  if (state.chlorinators.getItemById(1).currentOutput !== 0) {
969
- logger.debug(`Chem mixing ORP (chlorinator) paused waiting for chlor current output to be 0%. Mix time remaining: ${utils.formatDuration(schem.mixTimeRemaining)} `);
977
+ logger.debug(`Chem mixing ORP (chlorinator) paused waiting for chlor current output to be 0%. Mix time remaining: ${utils.formatDuration(schem.mixTimeRemaining)} `);
970
978
  return;
971
979
  }
972
980
  }
@@ -1283,20 +1291,17 @@ export class NixieChemChlor extends NixieChildEquipment {
1283
1291
  public chlorInterval = 15;
1284
1292
  constructor(chemical: NixieChemical, chlor: ChemicalChlor) { super(chemical); this.chlor = chlor; }
1285
1293
  public get chemical(): NixieChemical { return this.getParent() as NixieChemical; }
1286
- /* public async setChlorAsync(schlor: ChemicalChlorState, data: any) {
1287
- try {
1288
- if (typeof data !== 'undefined') {
1289
- // this.chlor.enabled = typeof data.useChlorinator !== 'undefined' ? data.useChlorinator : this.chlor.enabled;
1290
- // let's grab the chlor type from the one installed
1291
- let chlor = sys.chlorinators.getItemById(1);
1292
- let chlorModel = sys.board.valueMaps.chlorinatorModel.get(chlor.type);
1293
- // this.chlor.type = typeof chlor.type !== 'undefined' ? chlor.type : this.chlor.type;
1294
- // this.chlor.ratedLbs = typeof chlorModel.chlorModelinePerSec !== 'undefined' ? chlorModel.chlorinePerSec : this.chlor.ratedLbs;
1295
- // this.chlor.connectionId = typeof data.connectionId !== 'undefined' ? data.connectionId : this.chlor.connectionId;
1296
- // this.chlor.deviceBinding = typeof data.deviceBinding !== 'undefined' ? data.deviceBinding : this.chlor.deviceBinding;
1297
- }
1298
- } catch (err) { logger.error(`setChlorAsync: ${err.message}`); return Promise.reject(err); }
1299
- } */
1294
+ public async setChlorAsync(schlor: ChemicalChlorState, data: any) {
1295
+ try {
1296
+ if (typeof data.chlorDosingMethod !== 'undefined' && data.chlorDosingMethod === 0) {
1297
+ if (schlor.chemical.dosingStatus === 0) { await this.chemical.cancelDosing(schlor.chemController.orp, 'dosing method changed'); }
1298
+ if (schlor.chemical.dosingStatus === 1) { await this.chemical.cancelMixing(schlor.chemController.orp); }
1299
+ let chlor = sys.chlorinators.getItemById(1);
1300
+ chlor.disabled = false;
1301
+ chlor.isDosing = false;
1302
+ }
1303
+ } catch (err) { logger.error(`setChlorAsync: ${err.message}`); return Promise.reject(err); }
1304
+ }
1300
1305
  public async stopDosing(schem: ChemicalState, reason: string): Promise<void> {
1301
1306
  try {
1302
1307
  if (this._dosingTimer) {
@@ -1368,7 +1373,7 @@ export class NixieChemChlor extends NixieChildEquipment {
1368
1373
  try {
1369
1374
  await this.turnOn(schem);
1370
1375
  if (schlor.currentOutput !== 100) {
1371
- logger.error(`Chlor dose not added because current output is not 100%`);
1376
+ logger.warn(`Chlor dose not added because current output is not 100%`);
1372
1377
  }
1373
1378
  else {
1374
1379
  if (typeof dose._lastLatch !== 'undefined') {
@@ -1419,18 +1424,15 @@ export class NixieChemChlor extends NixieChildEquipment {
1419
1424
  }
1420
1425
  public async turnOff(schem: ChemicalState): Promise<ChlorinatorState> {
1421
1426
  try {
1427
+ logger.info(`Turning off the chlorinator`);
1422
1428
  let chlor = sys.chlorinators.getItemById(1);
1423
1429
  let schlor = state.chlorinators.getItemById(1);
1424
- if (schlor.currentOutput === 0 && schlor.targetOutput === 0 && !schlor.superChlor && chlor.disabled && schlor.poolSetpoint === 0 && schlor.spaSetpoint === 0 && !chlor.isDosing) {
1430
+ if (schlor.currentOutput === 0 && schlor.targetOutput === 0 && !schlor.superChlor && chlor.disabled && !chlor.isDosing) {
1425
1431
  this.isOn = schem.chlor.isDosing = false;
1426
1432
  return schlor;
1427
1433
  }
1428
1434
  let cstate = await sys.board.chlorinator.setChlorAsync({
1429
1435
  id: 1,
1430
- poolSetpoint: 0,
1431
- spaSetpoint: 0,
1432
- superChlor: false,
1433
- superChlorHours: 0,
1434
1436
  disabled: true,
1435
1437
  isDosing: false
1436
1438
  })
@@ -1443,20 +1445,12 @@ export class NixieChemChlor extends NixieChildEquipment {
1443
1445
  try {
1444
1446
  let chlor = sys.chlorinators.getItemById(1);
1445
1447
  let schlor = state.chlorinators.getItemById(1);
1446
- let chem = schem.chemController;
1447
- let poolSetpoint: number = 0, spaSetpoint: number = 0;
1448
- if (chem.body === 0 || chem.body === 32) poolSetpoint = 100;
1449
- if (chem.body === 1 || chem.body === 32) spaSetpoint = 100;
1450
- if (schlor.currentOutput === 100 && schlor.targetOutput === 100 && !schlor.superChlor && !chlor.disabled && schlor.poolSetpoint === poolSetpoint && schlor.spaSetpoint === spaSetpoint && chlor.isDosing) {
1448
+ if (schlor.currentOutput === 100 && schlor.targetOutput === 100 && !schlor.superChlor && !chlor.disabled && chlor.isDosing) {
1451
1449
  this.isOn = schem.chlor.isDosing = true;
1452
1450
  return schlor;
1453
1451
  }
1454
1452
  let cstate = await sys.board.chlorinator.setChlorAsync({
1455
1453
  id: 1,
1456
- poolSetpoint,
1457
- spaSetpoint,
1458
- superChlor: false,
1459
- superChlorHours: 0,
1460
1454
  disabled: false,
1461
1455
  isDosing: true
1462
1456
  })
@@ -1512,7 +1506,7 @@ export class NixieChemicalPh extends NixieChemical {
1512
1506
  let sorp = sph.chemController.orp;
1513
1507
  for (let i = 0; i < chlors.length; i++) {
1514
1508
  let chlor = chlors.getItemByIndex(i);
1515
- if (!chlor.disabled) await sys.board.chlorinator.setChlorAsync({ id: chlor.id, disabled: true });
1509
+ if (!chlor.disabled) await sys.board.chlorinator.setChlorAsync({ id: chlor.id, disabled: true, isDosing: false });
1516
1510
  }
1517
1511
  // If we are currently dosing ORP then we need to stop that because pH is currently dosing.
1518
1512
  if (sorp.pump.isDosing || sorp.chlor.isDosing) await this.chemController.orp.cancelDosing(sorp, 'pH priority');
@@ -1756,24 +1750,13 @@ export class NixieChemicalORP extends NixieChemical {
1756
1750
  sorp.level = typeof data.level !== 'undefined' && !isNaN(parseFloat(data.level)) ? parseFloat(data.level) : sorp.level;
1757
1751
  this.orp.phLockout = typeof data.phLockout !== 'undefined' && !isNaN(parseFloat(data.phLockout)) ? parseFloat(data.phLockout) : this.orp.phLockout;
1758
1752
  this.orp.flowReadingsOnly = typeof data.flowReadingsOnly !== 'undefined' ? utils.makeBool(data.flowReadingsOnly) : this.orp.flowReadingsOnly;
1759
- this.orp.chlorDosingMethod = typeof data.chlorDosingMethod !== 'undefined' ? data.chlorDosingMethod : this.orp.chlorDosingMethod;
1760
- // if dynamically calculating the chlor dose, set the chlor body assoc
1761
- // to the same as REM chem (dP will lock this out)
1762
- let schlor = state.chlorinators.getItemById(1);
1763
- /* if (this.orp.chlorDosingMethod > 0) {
1764
- let chlor = sys.chlorinators.getItemById(1);
1765
- chlor.body = this.chemController.chem.body;
1766
- schlor.lockSetpoints = true;
1767
- }
1768
- else {
1769
- schlor.lockSetpoints = false;
1770
- } */
1753
+ if (typeof data.chlorDosingMethod !== 'undefined') { this.orp.chlorDosingMethod = data.chlorDosingMethod; }
1771
1754
  await this.setDosing(this.orp, data);
1772
1755
  await this.setMixing(this.orp, data);
1773
1756
  await this.probe.setProbeORPAsync(sorp.probe, data.probe);
1774
1757
  await this.tank.setTankAsync(sorp.tank, data.tank);
1775
1758
  await this.pump.setPumpAsync(sorp.pump, data.pump);
1776
- // await this.chlor.setChlorAsync(sorp.chlor, data);
1759
+ await this.chlor.setChlorAsync(sorp.chlor, data);
1777
1760
  this.orp.setpoint = sorp.setpoint = typeof data.setpoint !== 'undefined' ? parseInt(data.setpoint, 10) : this.orp.setpoint;
1778
1761
  if (typeof data.tolerance !== 'undefined') {
1779
1762
  if (typeof data.tolerance.enabled !== 'undefined') this.orp.tolerance.enabled = utils.makeBool(data.tolerance.enabled);
@@ -1812,7 +1795,7 @@ export class NixieChemicalORP extends NixieChemical {
1812
1795
  if (sorp.tank.level > 0) {
1813
1796
  logger.verbose(`Chem orp dose activate pump ${this.pump.pump.ratedFlow}mL/min`);
1814
1797
  await this.pump.dose(sorp);
1815
- }
1798
+ }
1816
1799
  }
1817
1800
  catch (err) { logger.error(`manualDoseAsync ORP: ${err.message}`); logger.error(err); return Promise.reject(err); }
1818
1801
  }
@@ -1824,12 +1807,17 @@ export class NixieChemicalORP extends NixieChemical {
1824
1807
  if (sorp.doseHistory.length) {
1825
1808
  // if last dose was within 15 minutes, set mix time to 15 mins-lastdose
1826
1809
  // if no dose in last 15, then we should be monitoring
1827
- let lastDoseTime = sorp.doseHistory[0].timeDosed;
1828
- let mixTime = Math.min(Math.max(this.chlor.chlorInterval * 60 - lastDoseTime, 0), this.chlor.chlorInterval * 60);
1829
- if (sorp.dosingStatus === 0) await this.mixChemicals(sorp, mixTime);
1810
+ if (new Date().getTime() - sorp.doseHistory[0].end.getTime() < this.chlor.chlorInterval * 60 * 1000){
1811
+ let lastDoseTime = sorp.doseHistory[0].timeDosed;
1812
+ let mixTime = Math.min(Math.max(this.chlor.chlorInterval * 60 - lastDoseTime, 0), this.chlor.chlorInterval * 60);
1813
+ // if (mixTime === 0) return; // due to delays with setting chlor, let the checkDosing pick up the cycle again with the chlor already on.
1814
+ if (sorp.dosingStatus === 0) await this.mixChemicals(sorp, mixTime);
1815
+ }
1830
1816
  }
1831
- else
1817
+ else{
1832
1818
  if (sorp.dosingStatus === 0) await this.mixChemicals(sorp);
1819
+ }
1820
+ return;
1833
1821
  }
1834
1822
  else {
1835
1823
  // Just stop the pump for now but we will do some logging later.
@@ -1871,6 +1859,7 @@ export class NixieChemicalORP extends NixieChemical {
1871
1859
  // if no dose in last 15, then we should be monitoring
1872
1860
  let lastDoseTime = schem.doseHistory[0].timeDosed;
1873
1861
  let mixTime = Math.min(Math.max(this.chlor.chlorInterval * 60 - lastDoseTime, 0), this.chlor.chlorInterval * 60);
1862
+ // if (mixTime === 0) return; // due to delays in the setting the chlor, if we had a full dose last time let the chlor continue
1874
1863
  this.currentMix.set({ time: this.chlor.chlorInterval, timeMixed: Math.max(0, mixTime - schem.mixTimeRemaining) });
1875
1864
  }
1876
1865
  else
@@ -1899,14 +1888,15 @@ export class NixieChemicalORP extends NixieChemical {
1899
1888
  }
1900
1889
  public async checkDosing(chem: ChemController, sorp: ChemicalORPState): Promise<void> {
1901
1890
  try {
1902
- let status = sys.board.valueMaps.chemControllerDosingStatus.getName(sorp.dosingStatus);
1903
- if (!chem.orp.flowReadingsOnly || (chem.orp.flowReadingsOnly && sorp.chemController.flowDetected)){
1891
+ let status = sys.board.valueMaps.chemControllerDosingStatus.getName(sorp.dosingStatus);
1892
+ if (!chem.orp.flowReadingsOnly || (chem.orp.flowReadingsOnly && sorp.chemController.flowDetected)) {
1904
1893
  // demand in raw mV
1905
1894
  sorp.demand = this.orp.setpoint - sorp.level;
1906
1895
  // log the demand. We'll store the last 100 data points.
1907
1896
  // With 1s intervals, this will only be 1m 40s. Likely should consider more... and def time to move this to an external file.
1908
1897
  sorp.appendDemand(new Date().valueOf(), sorp.demand);
1909
1898
  }
1899
+ 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
1910
1900
  if (sorp.suspendDosing) {
1911
1901
  // Kill off the dosing and make sure the pump isn't running. Let's force the issue here.
1912
1902
  await this.cancelDosing(sorp, 'suspended');
@@ -1991,7 +1981,7 @@ export class NixieChemicalORP extends NixieChemical {
1991
1981
  return;
1992
1982
  }
1993
1983
  // Old fashion method; let the setpoints on chlor be the master
1994
- if (chem.orp.chlorDosingMethod === 0) return;
1984
+ // if (chem.orp.chlorDosingMethod === 0) return;
1995
1985
  // if there is a current pending dose, finish it out
1996
1986
  if (typeof sorp.currentDose === 'undefined') {
1997
1987
  if (sorp.dosingStatus === 0) { // 0 is dosing
@@ -2049,7 +2039,6 @@ export class NixieChemicalORP extends NixieChemical {
2049
2039
  percentOfTime = 1;
2050
2040
  }
2051
2041
  else if (sorp.demand < -20) {
2052
- logger.info(`Chlor % of time should be 0%`)
2053
2042
  await this.cancelDosing(sorp, 'demand < -20');
2054
2043
  }
2055
2044
  else {
@@ -2083,16 +2072,16 @@ export class NixieChemicalORP extends NixieChemical {
2083
2072
  // convert the % of time back to an amount of chlorine over 15 minutes;
2084
2073
  let time = this.chlor.chlorInterval * 60 * percentOfTime;
2085
2074
  let dose = model.chlorinePerSec * time;
2086
- logger.info(`Chem chlor calculated dosing at ${Math.round(percentOfTime * 10000) / 100}% and will dose ${Math.round(dose * 1000000) / 1000000}Lbs of chlorine over the next ${utils.formatDuration(time)}.`)
2087
-
2075
+
2088
2076
  if (dose > 0) {
2077
+ logger.info(`Chem chlor calculated dosing at ${Math.round(percentOfTime * 10000) / 100}% and will dose ${Math.round(dose * 1000000) / 1000000}Lbs of chlorine over the next ${utils.formatDuration(time)}.`)
2089
2078
  sorp.startDose(new Date(), 'auto', dose, 0, time, 0);
2090
2079
  await this.chlor.dose(sorp);
2091
2080
  return;
2092
2081
  }
2093
2082
 
2094
2083
  // if none of the other conditions are true, mix
2095
- await this.mixChemicals(sorp, this.chlor.chlorInterval * 60);
2084
+ // await this.mixChemicals(sorp, this.chlor.chlorInterval * 60);
2096
2085
 
2097
2086
  }
2098
2087
  else if (this.orp.setpoint > sorp.level) {
@@ -2156,6 +2145,14 @@ export class NixieChemicalORP extends NixieChemical {
2156
2145
  }
2157
2146
  catch (err) { logger.error(`checkDosing ORP: ${err.message}`); return Promise.reject(err); }
2158
2147
  }
2148
+ public async deleteChlorAsync(chlor: NixieChlorinator) {
2149
+ logger.info(`Removing chlor ${chlor.id} from Chem Controller ${this.getParent().id}`);
2150
+ let schem = state.chemControllers.getItemById(this.getParent().id);
2151
+ this.orp.useChlorinator = false;
2152
+ schem.orp.useChlorinator = false;
2153
+ if (schem.orp.dosingStatus === 0) { await this.cancelDosing(schem.orp, 'deleting chlorinator'); }
2154
+ if (schem.orp.dosingStatus === 1) { await this.cancelMixing(schem.orp); }
2155
+ }
2159
2156
  }
2160
2157
  class NixieChemProbe extends NixieChildEquipment {
2161
2158
  constructor(parent: NixieChemical) { super(parent); }
@@ -2209,7 +2206,7 @@ export class NixieChemProbePh extends NixieChemProbe {
2209
2206
  // Set the current body so that it references the temperature of the current running body.
2210
2207
  let body = sys.board.bodies.getBodyState(this.chemical.chemController.chem.body);
2211
2208
  if (typeof body !== 'undefined' && body.isOn) {
2212
- let units = sys.board.valueMaps.tempUnits.transform(sys.general.options.units);
2209
+ let units = sys.board.valueMaps.tempUnits.transform(state.temps.units);
2213
2210
  let obj = {};
2214
2211
  obj[`temp${units.name.toUpperCase()}`] = body.temp;
2215
2212
  sprobe.tempUnits = units.val;
@@ -2235,7 +2232,7 @@ export class NixieChemProbePh extends NixieChemProbe {
2235
2232
  deviceBinding: this.probe.deviceBinding,
2236
2233
  eventName: "chemController",
2237
2234
  property: "pHLevel",
2238
- sendValue: 'pH',
2235
+ sendValue: 'all',
2239
2236
  isActive: data.remFeedEnabled,
2240
2237
  sampling: 1,
2241
2238
  changesOnly: false,
@@ -2243,7 +2240,7 @@ export class NixieChemProbePh extends NixieChemProbe {
2243
2240
  }
2244
2241
  let res = await NixieChemController.putDeviceService(this.probe.connectionId, '/config/feed', d);
2245
2242
  if (res.status.code === 200) { this.probe.remFeedEnabled = data.remFeedEnabled; }
2246
- else { logger.warn(`setRemoteREMFeed: Cannot set remote feed. Message:${JSON.stringify(res.status)} for feed: ${JSON.stringify(d)}.`); return Promise.reject(`Cannot set REM feed for pH probe: ${JSON.stringify(res)}.`); }
2243
+ else { logger.warn(`setRemoteREMFeed: Cannot set remote feed. Message:${JSON.stringify(res.status)} for feed: ${JSON.stringify(d)}.`); }
2247
2244
  }
2248
2245
  catch (err) { logger.error(`setRemoteREMFeed: ${err.message}`); return Promise.reject(err); }
2249
2246
  }
@@ -2326,9 +2323,15 @@ export class NixieChemProbeORP extends NixieChemProbe {
2326
2323
  }
2327
2324
  let res = await NixieChemController.putDeviceService(this.probe.connectionId, '/config/feed', d);
2328
2325
  if (res.status.code === 200) { this.probe.remFeedEnabled = data.remFeedEnabled; }
2329
- else { logger.warn(`setRemoteREMFeed: Cannot set remote feed. Message:${JSON.stringify(res.status)} for feed: ${JSON.stringify(d)}.`); return Promise.reject(new InvalidOperationError(`Nixie could not set remote REM feed for the ORP probe.`, this.probe.dataName)); }
2326
+ else {
2327
+ logger.warn(`setRemoteREMFeed: Cannot set remote feed. Message:${JSON.stringify(res.status)} for feed: ${JSON.stringify(d)}.`);
2328
+ // return Promise.reject(new InvalidOperationError(`Nixie could not set remote REM feed for the ORP probe.`, this.probe.dataName));
2329
+ }
2330
+ }
2331
+ catch (err) {
2332
+ logger.error(`setRemoteREMFeed: ${err.message}`);
2333
+ //return Promise.reject(err); // don't muck up chem controller if we can't set the feeds.
2330
2334
  }
2331
- catch (err) { logger.error(`setRemoteREMFeed: ${err.message}`); return Promise.reject(err); }
2332
2335
  }
2333
2336
  public syncRemoteREMFeeds(chem: ChemController, servers) {
2334
2337
  // match any feeds and store the id/statusf
@@ -9,6 +9,7 @@ import { setTimeout, clearTimeout } from 'timers';
9
9
  import { webApp, InterfaceServerResponse } from "../../../web/Server";
10
10
  import { Outbound, Protocol, Response } from '../../comms/messages/Messages';
11
11
  import { conn } from '../../comms/Comms';
12
+ import { ncp } from '../Nixie';
12
13
 
13
14
  export class NixieChlorinatorCollection extends NixieEquipmentCollection<NixieChlorinator> {
14
15
  public async deleteChlorinatorAsync(id: number) {
@@ -18,6 +19,7 @@ export class NixieChlorinatorCollection extends NixieEquipmentCollection<NixieCh
18
19
  for (let i = this.length - 1; i >= 0; i--) {
19
20
  let c = this[i];
20
21
  if (c.id === id) {
22
+ await ncp.chemControllers.deleteChlorAsync(c as NixieChlorinator);
21
23
  await c.closeAsync();
22
24
  this.splice(i, 1);
23
25
  }
@@ -75,7 +77,6 @@ export class NixieChlorinator extends NixieEquipment {
75
77
  private _pollTimer: NodeJS.Timeout = null;
76
78
  private superChlorinating: boolean = false;
77
79
  private superChlorStart: number = 0;
78
- private chlorinating: boolean = false;
79
80
  public chlor: Chlorinator;
80
81
  public bodyOnTime: number;
81
82
  protected _suspendPolling: number = 0;
@@ -101,7 +102,7 @@ export class NixieChlorinator extends NixieEquipment {
101
102
  let superChlorHours = typeof data.superChlorHours !== 'undefined' ? parseInt(data.superChlorHours, 10) : chlor.superChlorHours;
102
103
  let disabled = typeof data.disabled !== 'undefined' ? utils.makeBool(data.disabled) : chlor.disabled;
103
104
  let isDosing = typeof data.isDosing !== 'undefined' ? utils.makeBool(data.isDosing) : chlor.isDosing;
104
- let model = typeof data.model !== 'undefined' ? data.model : chlor.model;
105
+ let model = typeof data.model !== 'undefined' ? data.model : chlor.model || 0;
105
106
  if (typeof body === 'undefined') return Promise.reject(new InvalidEquipmentDataError(`Invalid body assignment`, 'chlorinator', data.body || chlor.body));
106
107
  if (isNaN(poolSetpoint)) poolSetpoint = 0;
107
108
  if (isNaN(spaSetpoint)) spaSetpoint = 0;
@@ -113,9 +114,8 @@ export class NixieChlorinator extends NixieEquipment {
113
114
  schlor.superChlor = chlor.superChlor = superChlor;
114
115
  schlor.superChlorHours = chlor.superChlorHours = superChlorHours;
115
116
  schlor.type = chlor.type = chlorType;
116
- chlor.body = body;
117
117
  chlor.model = model;
118
- schlor.body = chlor.body;
118
+ schlor.body = chlor.body = body.val;
119
119
  chlor.disabled = disabled;
120
120
  chlor.isDosing = isDosing;
121
121
  schlor.name = chlor.name = data.name || chlor.name || `Chlorinator ${chlor.id}`;
@@ -178,7 +178,7 @@ export class NixieChlorinator extends NixieEquipment {
178
178
  // Comms failure will be handeled by the message processor.
179
179
  logger.error(`Chlorinator ${this.chlor.name} comms failure: ${err.message}`);
180
180
  }
181
- finally { if(!this.closing) this._pollTimer = setTimeout(async () => { await self.pollEquipment(); }, this.pollingInterval); }
181
+ finally { if(!this.closing) this._pollTimer = setTimeout(() => {self.pollEquipment();}, this.pollingInterval); }
182
182
  }
183
183
  public async takeControl(): Promise<boolean> {
184
184
  try {
@@ -230,7 +230,7 @@ export class NixieChlorinator extends NixieEquipment {
230
230
  let setpoint = 0;
231
231
  if (typeof body !== 'undefined') {
232
232
  setpoint = (body.id === 1) ? this.chlor.poolSetpoint : this.chlor.spaSetpoint;
233
- if (this.chlor.superChlor === true) setpoint = 100;
233
+ if (this.chlor.superChlor === true || this.chlor.isDosing) setpoint = 100;
234
234
  if (this.chlor.disabled === true) setpoint = 0; // Our target should be 0 because we have other things going on. For instance,
235
235
  // we may be dosing acid which will cause the disabled flag to be true.
236
236
  }
@@ -251,7 +251,6 @@ export class NixieChlorinator extends NixieEquipment {
251
251
  onAbort: () => {},
252
252
  onComplete: (err) => {
253
253
  if (err) {
254
- this.chlorinating = false;
255
254
  cstate.currentOutput = 0;
256
255
  cstate.status = 128;
257
256
  resolve(false);
@@ -260,7 +259,6 @@ export class NixieChlorinator extends NixieEquipment {
260
259
  // The action:17 message originated from us so we will not see it in the
261
260
  // ChlorinatorStateMessage module.
262
261
  cstate.currentOutput = setpoint;
263
- this.chlorinating = true;
264
262
  if (!this.superChlorinating && cstate.superChlor) {
265
263
  cstate.superChlorRemaining = cstate.superChlorHours * 3600;
266
264
  this.superChlorStart = Math.floor(new Date().getTime() / 1000) * 1000;
@@ -274,6 +272,8 @@ export class NixieChlorinator extends NixieEquipment {
274
272
  }
275
273
  }
276
274
  });
275
+ // #338
276
+ if (setpoint === 16) { out.appendPayloadByte(0); }
277
277
  conn.queueSendMessage(out);
278
278
  });
279
279
 
@@ -306,6 +306,7 @@ export class NixieChlorinator extends NixieEquipment {
306
306
  conn.queueSendMessage(out);
307
307
  });
308
308
  }
309
+ else return Promise.resolve(false);
309
310
  } catch (err) { logger.error(`Communication error with Chlorinator ${this.chlor.name} : ${err.message}`); return Promise.reject(err);}
310
311
 
311
312
  }
@@ -56,6 +56,12 @@ export class NixieCircuitCollection extends NixieEquipmentCollection<NixieCircui
56
56
  }
57
57
  catch (err) { logger.error(`setCircuitAsync: ${err.message}`); return Promise.reject(err); }
58
58
  }
59
+ public async checkCircuitEggTimerExpirationAsync(cstate: ICircuitState) {
60
+ try {
61
+ let c: NixieCircuit = this.find(elem => elem.id === cstate.id) as NixieCircuit;
62
+ await c.checkCircuitEggTimerExpirationAsync(cstate);
63
+ } catch (err) { logger.error(`NCP: Error synching circuit states: ${err}`); }
64
+ }
59
65
  public async initAsync(circuits: CircuitCollection) {
60
66
  try {
61
67
  for (let i = 0; i < circuits.length; i++) {
@@ -112,6 +118,10 @@ export class NixieCircuit extends NixieEquipment {
112
118
  constructor(ncp: INixieControlPanel, circuit: Circuit) {
113
119
  super(ncp);
114
120
  this.circuit = circuit;
121
+ // Clear out the delays.
122
+ let cstate = state.circuits.getItemById(circuit.id);
123
+ cstate.startDelay = false;
124
+ cstate.stopDelay = false;
115
125
  }
116
126
  public get id(): number { return typeof this.circuit !== 'undefined' ? this.circuit.id : -1; }
117
127
  public get eggTimerOff(): Timestamp { return typeof this.timeOn !== 'undefined' && !this.circuit.dontStop ? this.timeOn.clone().addMinutes(this.circuit.eggTimer) : undefined; }
@@ -126,18 +136,40 @@ export class NixieCircuit extends NixieEquipment {
126
136
  this._sequencing = true;
127
137
  let arr = [];
128
138
  let t = typeof timeout === 'undefined' ? 100 : timeout;
129
- arr.push({ isOn: true, timeout: t }); // This may not be needed but we always need to start from on.
139
+ arr.push({ isOn: false, timeout: t }); // This may not be needed but we always need to start from off.
130
140
  //[{ isOn: true, timeout: 1000 }, { isOn: false, timeout: 1000 }]
131
141
  for (let i = 0; i < count; i++) {
132
- arr.push({ isOn: false, timeout: t });
133
142
  arr.push({ isOn: true, timeout: t });
143
+ if(i < count - 1) arr.push({ isOn: false, timeout: t });
134
144
  }
145
+ // The documentation for IntelliBrite is incorrect. The sequence below will give us Party mode.
146
+ // Party mode:2
147
+ // Start: Off
148
+ // On
149
+ // Off
150
+ // On
151
+ // According to the docs this is the sequence they lay out.
152
+ // Party mode:2
153
+ // Start: On
154
+ // Off
155
+ // On
156
+ // Off
157
+ // On
158
+
135
159
  let res = await NixieEquipment.putDeviceService(this.circuit.connectionId, `/state/device/${this.circuit.deviceBinding}`, arr, 60000);
136
160
  return res;
137
161
  } catch (err) { logger.error(`Nixie: Error sending circuit sequence ${this.id}: ${count}`); }
138
162
  finally { this._sequencing = false; }
139
163
  }
140
- public async setCircuitStateAsync(cstate: ICircuitState, val: boolean, scheduled: boolean = false) : Promise<InterfaceServerResponse> {
164
+ public async setThemeAsync(cstate: ICircuitState, theme: number): Promise<InterfaceServerResponse> {
165
+ try {
166
+
167
+
168
+
169
+ return new InterfaceServerResponse(200, 'Sucess');
170
+ } catch (err) { logger.error(`Nixie: Error setting light theme ${cstate.id}-${cstate.name} to ${theme}`); }
171
+ }
172
+ public async setCircuitStateAsync(cstate: ICircuitState, val: boolean, scheduled: boolean = false): Promise<InterfaceServerResponse> {
141
173
  try {
142
174
  if (val !== cstate.isOn) {
143
175
  logger.info(`NCP: Setting Circuit ${cstate.name} to ${val}`);
@@ -149,20 +181,35 @@ export class NixieCircuit extends NixieEquipment {
149
181
  // Check to see if we should be on by poking the schedules.
150
182
  }
151
183
  if (utils.isNullOrEmpty(this.circuit.connectionId) || utils.isNullOrEmpty(this.circuit.deviceBinding)) {
184
+ sys.board.circuits.setEndTime(sys.circuits.getInterfaceById(cstate.id), cstate, val);
152
185
  cstate.isOn = val;
153
186
  return new InterfaceServerResponse(200, 'Success');
154
187
  }
155
188
  if (this._sequencing) return new InterfaceServerResponse(200, 'Success');
156
189
  let res = await NixieEquipment.putDeviceService(this.circuit.connectionId, `/state/device/${this.circuit.deviceBinding}`, { isOn: val, latch: val ? 10000 : undefined });
157
190
  if (res.status.code === 200) {
158
- cstate.isOn = val;
191
+ sys.board.circuits.setEndTime(sys.circuits.getInterfaceById(cstate.id), cstate, val);
159
192
  // Set this up so we can process our egg timer.
160
- if (!cstate.isOn && val) { this.timeOn = new Timestamp(); }
161
- else if (!val) this.timeOn = undefined;
193
+ //if (!cstate.isOn && val) { cstate.startTime = this.timeOn = new Timestamp(); }
194
+ //else if (!val) cstate.startTime = this.timeOn = undefined;
195
+ cstate.isOn = val;
162
196
  }
163
197
  return res;
164
198
  } catch (err) { logger.error(`Nixie: Error setting circuit state ${cstate.id}-${cstate.name} to ${val}`); }
165
199
  }
200
+ public async checkCircuitEggTimerExpirationAsync(cstate: ICircuitState) {
201
+ // if circuit end time is past current time, either the schedule is finished
202
+ // (this should already be turned off) or the egg timer has expired
203
+ try {
204
+ if (!cstate.isActive || !cstate.isOn) return;
205
+ if (typeof cstate.endTime !== 'undefined') {
206
+ if (cstate.endTime.toDate() < new Timestamp().toDate()) {
207
+ await sys.board.circuits.setCircuitStateAsync(cstate.id, false);
208
+ cstate.emitEquipmentChange();
209
+ }
210
+ }
211
+ } catch (err) { logger.error(`Error syncing circuit: ${err}`); }
212
+ }
166
213
  private async checkHardwareStatusAsync(connectionId: string, deviceBinding: string) {
167
214
  try {
168
215
  let dev = await NixieEquipment.getDeviceService(connectionId, `/status/device/${deviceBinding}`);
@@ -187,6 +234,8 @@ export class NixieCircuit extends NixieEquipment {
187
234
  public async closeAsync() {
188
235
  try {
189
236
  let cstate = state.circuits.getItemById(this.circuit.id);
237
+ cstate.stopDelay = false;
238
+ cstate.startDelay = false;
190
239
  await this.setCircuitStateAsync(cstate, false);
191
240
  cstate.emitEquipmentChange();
192
241
  }