nodejs-poolcontroller 7.4.0 → 7.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/Changelog +3 -0
  3. package/README.md +2 -2
  4. package/app.ts +2 -0
  5. package/config/Config.ts +3 -0
  6. package/config/VersionCheck.ts +8 -4
  7. package/controller/Equipment.ts +89 -29
  8. package/controller/Errors.ts +14 -1
  9. package/controller/State.ts +75 -31
  10. package/controller/boards/EasyTouchBoard.ts +81 -36
  11. package/controller/boards/IntelliCenterBoard.ts +96 -32
  12. package/controller/boards/IntelliTouchBoard.ts +103 -29
  13. package/controller/boards/NixieBoard.ts +79 -27
  14. package/controller/boards/SystemBoard.ts +1552 -822
  15. package/controller/comms/Comms.ts +84 -9
  16. package/controller/comms/messages/Messages.ts +10 -4
  17. package/controller/comms/messages/config/ChlorinatorMessage.ts +13 -4
  18. package/controller/comms/messages/config/CircuitGroupMessage.ts +6 -0
  19. package/controller/comms/messages/config/CoverMessage.ts +1 -0
  20. package/controller/comms/messages/config/EquipmentMessage.ts +4 -0
  21. package/controller/comms/messages/config/ExternalMessage.ts +43 -25
  22. package/controller/comms/messages/config/FeatureMessage.ts +8 -1
  23. package/controller/comms/messages/config/GeneralMessage.ts +8 -0
  24. package/controller/comms/messages/config/HeaterMessage.ts +10 -9
  25. package/controller/comms/messages/config/IntellichemMessage.ts +4 -1
  26. package/controller/comms/messages/config/OptionsMessage.ts +13 -1
  27. package/controller/comms/messages/config/PumpMessage.ts +4 -20
  28. package/controller/comms/messages/config/RemoteMessage.ts +4 -0
  29. package/controller/comms/messages/config/ScheduleMessage.ts +11 -0
  30. package/controller/comms/messages/config/SecurityMessage.ts +1 -0
  31. package/controller/comms/messages/config/ValveMessage.ts +12 -2
  32. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +2 -3
  33. package/controller/comms/messages/status/EquipmentStateMessage.ts +74 -22
  34. package/controller/comms/messages/status/HeaterStateMessage.ts +15 -6
  35. package/controller/comms/messages/status/IntelliChemStateMessage.ts +37 -26
  36. package/controller/nixie/Nixie.ts +18 -16
  37. package/controller/nixie/chemistry/ChemController.ts +57 -37
  38. package/controller/nixie/chemistry/Chlorinator.ts +7 -8
  39. package/controller/nixie/circuits/Circuit.ts +17 -0
  40. package/controller/nixie/pumps/Pump.ts +49 -24
  41. package/controller/nixie/schedules/Schedule.ts +1 -1
  42. package/defaultConfig.json +15 -0
  43. package/issue_template.md +1 -1
  44. package/logger/DataLogger.ts +37 -22
  45. package/package.json +3 -1
  46. package/web/Server.ts +515 -27
  47. package/web/bindings/influxDB.json +35 -0
  48. package/web/bindings/mqtt.json +62 -3
  49. package/web/bindings/mqttAlt.json +57 -4
  50. package/web/interfaces/httpInterface.ts +2 -0
  51. package/web/interfaces/influxInterface.ts +3 -2
  52. package/web/interfaces/mqttInterface.ts +12 -1
  53. package/web/services/config/Config.ts +162 -37
  54. package/web/services/state/State.ts +47 -3
  55. package/web/services/state/StateSocket.ts +1 -1
@@ -23,15 +23,25 @@ import { Timestamp, utils } from "../../../Constants"
23
23
  export class IntelliChemStateMessage {
24
24
  public static process(msg: Inbound) {
25
25
  if (sys.controllerType === ControllerType.Unknown) return;
26
- if (msg.source < 144 || msg.source > 158) return;
26
+ let address = (msg.dest >= 144 && msg.dest <= 158) ? msg.dest : msg.source;
27
+ if (address < 144 || address > 158) return;
28
+ let controller = sys.chemControllers.getItemByAddress(address);
29
+ if (!controller.isActive) {
30
+ msg.isProcessed = true;
31
+ return;
32
+ }
27
33
  switch (msg.action) {
28
-
29
34
  // ---------- IntelliChem Control panel is spitting out its status ----------- //
30
35
  case 18: // IntelliChem is sending us it's status.
31
36
  IntelliChemStateMessage.processState(msg);
32
37
  break;
33
38
  case 210: // OCP is asking IntelliChem controller for it's current status info.
34
39
  // [165,0,144,16,210,1],[210],[2,234]
40
+ let schem = state.chemControllers.getItemById(controller.id);
41
+ if (schem.lastComm + (30 * 1000) < new Date().getTime()) {
42
+ // We have not talked to the chem controller in 30 seconds so we have lost communication.
43
+ schem.status = schem.alarms.comms = 1;
44
+ }
35
45
  msg.isProcessed = true;
36
46
  break;
37
47
  // ---------- End IntelliChem set get ----------- //
@@ -43,7 +53,7 @@ export class IntelliChemStateMessage {
43
53
 
44
54
  /* RKS: This is processed in the IntellichemMessage.processTouch() and is the results of asking for the IntelliChem configuration.
45
55
  case 147: // OCP is broadcasting it's known ic values... Need to change our settings if virtual.
46
- // 147 is a proto:broadcast message;
56
+ // 147 is a proto:broadcast message;
47
57
  // it has exactly the same format as 18 but there is payload[0] which is inserted at the beginning. Likely the chem controller id.
48
58
  if (msg.dest < 144 || msg.dest > 158) return;
49
59
  IntelliChemStateMessage.processControllerChange(msg);
@@ -57,14 +67,11 @@ export class IntelliChemStateMessage {
57
67
  msg.isProcessed = true;
58
68
  break;
59
69
  case 146: // OCP is telling IntelliChem that it needs to change its settings to...
60
- let address = msg.dest;
61
- // The address is king here. The id is not.
62
- let controller = sys.chemControllers.getItemByAddress(address, true);
63
- let scontroller = state.chemControllers.getItemById(controller.id, true);
64
- if (scontroller.lastComm + (30 * 1000) < new Date().getTime()) {
65
- // We have not talked to the chem controller in 30 seconds so we have lost communication.
66
- scontroller.status = scontroller.alarms.comms = 1;
67
- }
70
+ //let scontroller = state.chemControllers.getItemById(controller.id, true);
71
+ //if (scontroller.lastComm + (30 * 1000) < new Date().getTime()) {
72
+ // // We have not talked to the chem controller in 30 seconds so we have lost communication.
73
+ // scontroller.status = scontroller.alarms.comms = 1;
74
+ //}
68
75
  controller.ph.tank.capacity = controller.orp.tank.capacity = 6;
69
76
  controller.ph.tank.units = controller.orp.tank.units = '';
70
77
  msg.isProcessed = true;
@@ -89,8 +96,8 @@ export class IntelliChemStateMessage {
89
96
  // 6-7 : ORP Setpoint : byte(6) x 256 + byte(7)
90
97
  // 8 : Unknown = 0
91
98
  // 9 : Unknown = 0
92
- // 10 : Unknown = 0
93
- // 11-12 : pH Dose time seconds. The number of seconds since the dose started. byte(11) x 256 + byte(12)
99
+ // 10-11 : pH Dose time seconds. The number of seconds since the dose started. byte(10) x 256 + byte(11)
100
+ // 12: Unknown
94
101
  // 13 : Unknown
95
102
  // 14-15 : ORP Dose time seconds. The number of seconds since the dose started. byte(14) x 256 + byte(15)
96
103
  // 16-17 : pH Dose volume (unknown units) - These appear to be mL.
@@ -122,10 +129,13 @@ export class IntelliChemStateMessage {
122
129
  schem.type = chem.type = sys.board.valueMaps.chemControllerTypes.getValue('intellichem');
123
130
  chem.name = chem.name || `IntelliChem ${chem.address - 143}`; // default to true id if no name is set
124
131
  schem.lastComm = new Date().getTime();
125
- schem.status = schem.alarms.comms = 0;
132
+ schem.status = schem.alarms.comms = 0;
126
133
  chem.ph.tank.capacity = chem.orp.tank.capacity = 6;
127
134
  chem.ph.tank.units = chem.orp.tank.units = '';
128
-
135
+ chem.ph.tank.alarmEmptyEnabled = false;
136
+ chem.ph.tank.alarmEmptyLevel = 1;
137
+ chem.orp.tank.alarmEmptyEnabled = false;
138
+ chem.orp.tank.alarmEmptyLevel = 1;
129
139
  schem.address = chem.address;
130
140
  schem.ph.level = schem.ph.probe.level = msg.extractPayloadIntBE(0) / 100;
131
141
  schem.orp.level = schem.orp.probe.level = msg.extractPayloadIntBE(2);
@@ -133,11 +143,11 @@ export class IntelliChemStateMessage {
133
143
  chem.orp.setpoint = msg.extractPayloadIntBE(6);
134
144
  // Missing information on the related bytes.
135
145
  // Bytes 8-14 (Probably Total Dissolved Solids in here if no IntelliChlor)
136
- let phPrev = { status: schem.orp.dosingStatus, time: schem.ph.timeDosed || 0, vol: schem.ph.volumeDosed };
146
+ let phPrev = { status: schem.ph.dosingStatus, time: schem.ph.timeDosed || 0, vol: schem.ph.volumeDosed };
137
147
  let orpPrev = { status: schem.orp.dosingStatus, time: schem.orp.timeDosed || 0, vol: schem.orp.volumeDosed };
138
148
  // IntelliChem never tells us what the dose time or volume is so we will let that dog lie.
139
- // 11-12 : pH Dose time
140
- schem.ph.timeDosed = (msg.extractPayloadByte(11) * 256) + msg.extractPayloadByte(12);
149
+ // 10-11 : pH Dose time
150
+ schem.ph.timeDosed = (msg.extractPayloadByte(10) * 256) + msg.extractPayloadByte(11);
141
151
  // 14-15 : ORP Dose time seconds. The number of seconds since the dose started.
142
152
  schem.orp.timeDosed = (msg.extractPayloadByte(14) * 256) + msg.extractPayloadByte(15);
143
153
  // 16-17 : pH Dose volume (unknown units) = 35
@@ -157,7 +167,7 @@ export class IntelliChemStateMessage {
157
167
  chem.cyanuricAcid = msg.extractPayloadByte(26);
158
168
  // 27-28 : Alkalinity
159
169
  chem.alkalinity = (msg.extractPayloadByte(27) * 256) + msg.extractPayloadByte(28);
160
- // 29 : Salt level = 20
170
+ // 29 : Salt level = 20
161
171
  if (sys.chlorinators.length > 0) {
162
172
  let chlor = state.chlorinators.find(elem => elem.id === 1);
163
173
  schem.orp.probe.saltLevel = (typeof chlor !== 'undefined') ? chlor.saltLevel : msg.extractPayloadByte(29) * 50;
@@ -169,6 +179,7 @@ export class IntelliChemStateMessage {
169
179
  // 32 : Alarms = 8 = (no alarm)
170
180
  const alarms = schem.alarms;
171
181
  alarms.flow = msg.extractPayloadByte(32) & 0x01;
182
+ if (alarms.flow === 0) schem.flowDetected = true;
172
183
  alarms.pH = msg.extractPayloadByte(32) & 0x06;
173
184
  alarms.orp = msg.extractPayloadByte(32) & 0x18;
174
185
  alarms.pHTank = msg.extractPayloadByte(32) & 0x20;
@@ -187,11 +198,11 @@ export class IntelliChemStateMessage {
187
198
  schem.ph.dosingStatus = (msg.extractPayloadByte(34) & 0x30) >> 4; // mask 00xx0000 and shift bit 5 & 6
188
199
  schem.orp.dosingStatus = (msg.extractPayloadByte(34) & 0xC0) >> 6; // mask xx000000 and shift bit 7 & 8
189
200
  // 35 : Delays = 0
190
- schem.status = msg.extractPayloadByte(35) & 0x80 >> 7; // to be verified as comms lost
201
+ schem.status = (msg.extractPayloadByte(35) & 0x80) >> 7; // to be verified as comms lost
191
202
  schem.ph.manualDosing = (msg.extractPayloadByte(35) & 0x08) === 1 ? true : false;
192
203
  chem.orp.useChlorinator = (msg.extractPayloadByte(35) & 0x10) === 1 ? true : false;
193
204
  chem.HMIAdvancedDisplay = (msg.extractPayloadByte(35) & 0x20) === 1 ? true : false;
194
- chem.ph.phSupply = (msg.extractPayloadByte(35) & 0x40) === 1 ? true : false; // acid pH dosing = 1; base pH dosing = 0;
205
+ chem.ph.phSupply = (msg.extractPayloadByte(35) & 0x40) === 1 ? 'acid' : 'base'; // acid pH dosing = 1; base pH dosing = 0;
195
206
  // 36-37 : Firmware = 80,1 = 1.080
196
207
  chem.firmware = `${msg.extractPayloadByte(37)}.${msg.extractPayloadByte(36).toString().padStart(3, '0')}`
197
208
  // 38 : Water Chemistry Warning
@@ -230,12 +241,12 @@ export class IntelliChemStateMessage {
230
241
  if (typeof schem.ph.currentDose !== 'undefined') {
231
242
  // We just ended a dose so write it out to the chem logs.
232
243
  schem.ph.endDose(Timestamp.now.addSeconds(-(schem.ph.doseTime - phPrev.time)).toDate(), 'completed',
233
- schem.ph.volumeDosed - phPrev.vol, (schem.ph.doseTime - phPrev.time) * 1000);
244
+ schem.ph.volumeDosed - phPrev.vol, (schem.ph.timeDosed - phPrev.time) * 1000);
234
245
  }
235
246
  }
236
247
  else if (schem.ph.dosingStatus === 0) {
237
248
  // We are still dosing so add the time and volume to the dose.
238
- schem.ph.appendDose(schem.ph.doseVolume - phPrev.vol, (schem.ph.doseTime - phPrev.time) * 1000);
249
+ schem.ph.appendDose(schem.ph.doseVolume - phPrev.vol, (schem.ph.timeDosed - phPrev.time) * 1000);
239
250
  }
240
251
  else {
241
252
  console.log(`DOSING STATUS === ${schem.ph.dosingStatus}`);
@@ -246,19 +257,19 @@ export class IntelliChemStateMessage {
246
257
  if (schem.orp.dosingStatus === 0 && orpPrev.status !== 0) {
247
258
  if (schem.orp.dosingStatus === 0) {
248
259
  // We are starting a dose so we need to set the current dose.
249
- schem.orp.startDose(Timestamp.now.addSeconds(-schem.orp.doseTime).toDate(), schem.orp.manualDosing ? 'manual' : 'auto', 0, schem.orp.volumeDosed, schem.orp.timeDosed * 1000);
260
+ schem.orp.startDose(Timestamp.now.addSeconds(-schem.orp.doseTime).toDate(), schem.orp.manualDosing ? 'manual' : 'auto', 0, schem.orp.volumeDosed, 0, schem.orp.timeDosed * 1000);
250
261
  }
251
262
  }
252
263
  else if (schem.orp.dosingStatus !== 0 && orpPrev.status === 0) {
253
264
  if (typeof schem.orp.currentDose !== 'undefined') {
254
265
  // We just ended a dose so write it out to the chem logs.
255
266
  schem.orp.endDose(Timestamp.now.addSeconds(-(schem.orp.doseTime - orpPrev.time)).toDate(), 'completed',
256
- schem.orp.volumeDosed - orpPrev.vol, schem.orp.doseTime - orpPrev.time);
267
+ schem.orp.volumeDosed - orpPrev.vol, (schem.orp.timeDosed - orpPrev.time) * 1000);
257
268
  }
258
269
  }
259
270
  else if (schem.orp.dosingStatus === 0) {
260
271
  // We are still dosing so add the time and volume to the dose.
261
- schem.orp.appendDose(schem.orp.doseTime - orpPrev.time, schem.orp.doseVolume - orpPrev.vol);
272
+ schem.orp.appendDose(schem.orp.doseVolume - orpPrev.vol, (schem.orp.timeDosed - orpPrev.time) * 1000);
262
273
  }
263
274
  else {
264
275
  // Make sure we don't have a current dose going.
@@ -136,23 +136,25 @@ export class NixieControlPanel implements INixieControlPanel {
136
136
  try {
137
137
  let srv = [];
138
138
  let servers = webApp.findServersByType('rem');
139
- for (let i = 0; i < servers.length; i++) {
140
- let server = servers[i];
141
- // Sometimes I hate type safety.
142
- let devices = typeof server['getDevices'] === 'function' ? await server['getDevices']() : [];
143
- let int = config.getInterfaceByUuid(servers[i].uuid);
144
- srv.push({
145
- uuid: servers[i].uuid,
146
- name: servers[i].name,
147
- type: servers[i].type,
148
- isRunning: servers[i].isRunning,
149
- isConnected: servers[i].isConnected,
150
- devices: devices,
151
- remoteConnectionId: servers[i].remoteConnectionId,
152
- interface: int
153
- });
139
+ if (typeof servers !== 'undefined') {
140
+ for (let i = 0; i < servers.length; i++) {
141
+ let server = servers[i];
142
+ // Sometimes I hate type safety.
143
+ let devices = typeof server['getDevices'] === 'function' ? await server['getDevices']() : [];
144
+ let int = config.getInterfaceByUuid(servers[i].uuid);
145
+ srv.push({
146
+ uuid: servers[i].uuid,
147
+ name: servers[i].name,
148
+ type: servers[i].type,
149
+ isRunning: servers[i].isRunning,
150
+ isConnected: servers[i].isConnected,
151
+ devices: devices,
152
+ remoteConnectionId: servers[i].remoteConnectionId,
153
+ interface: int
154
+ });
155
+ }
156
+ await ncp.chemControllers.syncRemoteREMFeeds(srv);
154
157
  }
155
- await ncp.chemControllers.syncRemoteREMFeeds(srv);
156
158
  return srv;
157
159
  } catch (err) { logger.error(err); }
158
160
  }
@@ -9,6 +9,7 @@ import { EquipmentNotFoundError, EquipmentTimeoutError, InvalidEquipmentDataErro
9
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:
@@ -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
  }
@@ -964,9 +973,8 @@ class NixieChemical extends NixieChildEquipment {
964
973
  schem.chlor.isDosing = schem.pump.isDosing = false;
965
974
  if (!this.chemical.flowOnlyMixing || (schem.chemController.isBodyOn && this.chemController.flowDetected)) {
966
975
  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');
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
  }
@@ -1365,7 +1373,7 @@ export class NixieChemChlor extends NixieChildEquipment {
1365
1373
  try {
1366
1374
  await this.turnOn(schem);
1367
1375
  if (schlor.currentOutput !== 100) {
1368
- 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%`);
1369
1377
  }
1370
1378
  else {
1371
1379
  if (typeof dose._lastLatch !== 'undefined') {
@@ -1416,9 +1424,10 @@ export class NixieChemChlor extends NixieChildEquipment {
1416
1424
  }
1417
1425
  public async turnOff(schem: ChemicalState): Promise<ChlorinatorState> {
1418
1426
  try {
1427
+ logger.info(`Turning off the chlorinator`);
1419
1428
  let chlor = sys.chlorinators.getItemById(1);
1420
1429
  let schlor = state.chlorinators.getItemById(1);
1421
- 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) {
1422
1431
  this.isOn = schem.chlor.isDosing = false;
1423
1432
  return schlor;
1424
1433
  }
@@ -1436,11 +1445,7 @@ export class NixieChemChlor extends NixieChildEquipment {
1436
1445
  try {
1437
1446
  let chlor = sys.chlorinators.getItemById(1);
1438
1447
  let schlor = state.chlorinators.getItemById(1);
1439
- let chem = schem.chemController;
1440
- let poolSetpoint: number = 0, spaSetpoint: number = 0;
1441
- if (chem.body === 0 || chem.body === 32) poolSetpoint = 100;
1442
- if (chem.body === 1 || chem.body === 32) spaSetpoint = 100;
1443
- 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) {
1444
1449
  this.isOn = schem.chlor.isDosing = true;
1445
1450
  return schlor;
1446
1451
  }
@@ -1802,13 +1807,17 @@ export class NixieChemicalORP extends NixieChemical {
1802
1807
  if (sorp.doseHistory.length) {
1803
1808
  // if last dose was within 15 minutes, set mix time to 15 mins-lastdose
1804
1809
  // if no dose in last 15, then we should be monitoring
1805
- let lastDoseTime = sorp.doseHistory[0].timeDosed;
1806
- let mixTime = Math.min(Math.max(this.chlor.chlorInterval * 60 - lastDoseTime, 0), this.chlor.chlorInterval * 60);
1807
- // if (mixTime === 0) return; // due to delays with setting chlor, let the checkDosing pick up the cycle again with the chlor already on.
1808
- 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
+ }
1809
1816
  }
1810
- else
1817
+ else{
1811
1818
  if (sorp.dosingStatus === 0) await this.mixChemicals(sorp);
1819
+ }
1820
+ return;
1812
1821
  }
1813
1822
  else {
1814
1823
  // Just stop the pump for now but we will do some logging later.
@@ -2030,7 +2039,6 @@ export class NixieChemicalORP extends NixieChemical {
2030
2039
  percentOfTime = 1;
2031
2040
  }
2032
2041
  else if (sorp.demand < -20) {
2033
- logger.info(`Chlor % of time should be 0%`)
2034
2042
  await this.cancelDosing(sorp, 'demand < -20');
2035
2043
  }
2036
2044
  else {
@@ -2064,16 +2072,16 @@ export class NixieChemicalORP extends NixieChemical {
2064
2072
  // convert the % of time back to an amount of chlorine over 15 minutes;
2065
2073
  let time = this.chlor.chlorInterval * 60 * percentOfTime;
2066
2074
  let dose = model.chlorinePerSec * time;
2067
- 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)}.`)
2068
-
2075
+
2069
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)}.`)
2070
2078
  sorp.startDose(new Date(), 'auto', dose, 0, time, 0);
2071
2079
  await this.chlor.dose(sorp);
2072
2080
  return;
2073
2081
  }
2074
2082
 
2075
2083
  // if none of the other conditions are true, mix
2076
- await this.mixChemicals(sorp, this.chlor.chlorInterval * 60);
2084
+ // await this.mixChemicals(sorp, this.chlor.chlorInterval * 60);
2077
2085
 
2078
2086
  }
2079
2087
  else if (this.orp.setpoint > sorp.level) {
@@ -2137,6 +2145,14 @@ export class NixieChemicalORP extends NixieChemical {
2137
2145
  }
2138
2146
  catch (err) { logger.error(`checkDosing ORP: ${err.message}`); return Promise.reject(err); }
2139
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
+ }
2140
2156
  }
2141
2157
  class NixieChemProbe extends NixieChildEquipment {
2142
2158
  constructor(parent: NixieChemical) { super(parent); }
@@ -2190,7 +2206,7 @@ export class NixieChemProbePh extends NixieChemProbe {
2190
2206
  // Set the current body so that it references the temperature of the current running body.
2191
2207
  let body = sys.board.bodies.getBodyState(this.chemical.chemController.chem.body);
2192
2208
  if (typeof body !== 'undefined' && body.isOn) {
2193
- let units = sys.board.valueMaps.tempUnits.transform(sys.general.options.units);
2209
+ let units = sys.board.valueMaps.tempUnits.transform(state.temps.units);
2194
2210
  let obj = {};
2195
2211
  obj[`temp${units.name.toUpperCase()}`] = body.temp;
2196
2212
  sprobe.tempUnits = units.val;
@@ -2216,7 +2232,7 @@ export class NixieChemProbePh extends NixieChemProbe {
2216
2232
  deviceBinding: this.probe.deviceBinding,
2217
2233
  eventName: "chemController",
2218
2234
  property: "pHLevel",
2219
- sendValue: 'pH',
2235
+ sendValue: 'all',
2220
2236
  isActive: data.remFeedEnabled,
2221
2237
  sampling: 1,
2222
2238
  changesOnly: false,
@@ -2224,7 +2240,7 @@ export class NixieChemProbePh extends NixieChemProbe {
2224
2240
  }
2225
2241
  let res = await NixieChemController.putDeviceService(this.probe.connectionId, '/config/feed', d);
2226
2242
  if (res.status.code === 200) { this.probe.remFeedEnabled = data.remFeedEnabled; }
2227
- 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)}.`); }
2228
2244
  }
2229
2245
  catch (err) { logger.error(`setRemoteREMFeed: ${err.message}`); return Promise.reject(err); }
2230
2246
  }
@@ -2307,11 +2323,15 @@ export class NixieChemProbeORP extends NixieChemProbe {
2307
2323
  }
2308
2324
  let res = await NixieChemController.putDeviceService(this.probe.connectionId, '/config/feed', d);
2309
2325
  if (res.status.code === 200) { this.probe.remFeedEnabled = data.remFeedEnabled; }
2310
- 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.
2311
2334
  }
2312
- catch (err) { logger.error(`setRemoteREMFeed: ${err.message}`);
2313
- //return Promise.reject(err); // don't muck up chem controller if we can't set the feeds.
2314
- }
2315
2335
  }
2316
2336
  public syncRemoteREMFeeds(chem: ChemController, servers) {
2317
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;
@@ -308,6 +306,7 @@ export class NixieChlorinator extends NixieEquipment {
308
306
  conn.queueSendMessage(out);
309
307
  });
310
308
  }
309
+ else return Promise.resolve(false);
311
310
  } catch (err) { logger.error(`Communication error with Chlorinator ${this.chlor.name} : ${err.message}`); return Promise.reject(err);}
312
311
 
313
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 syncing circuit states: ${err}`); }
64
+ }
59
65
  public async initAsync(circuits: CircuitCollection) {
60
66
  try {
61
67
  for (let i = 0; i < circuits.length; i++) {
@@ -179,6 +185,17 @@ export class NixieCircuit extends NixieEquipment {
179
185
  return res;
180
186
  } catch (err) { logger.error(`Nixie: Error setting circuit state ${cstate.id}-${cstate.name} to ${val}`); }
181
187
  }
188
+ public async checkCircuitEggTimerExpirationAsync(cstate: ICircuitState) {
189
+ // if circuit end time is past current time, either the schedule is finished
190
+ // (this should already be turned off) or the egg timer has expired
191
+ try {
192
+ if (!cstate.isActive || !cstate.isOn) return;
193
+ if (cstate.endTime.toDate() < new Timestamp().toDate()) {
194
+ await sys.board.circuits.setCircuitStateAsync(cstate.id, false);
195
+ cstate.emitEquipmentChange();
196
+ }
197
+ } catch (err) { logger.error(`Error syncing circuit: ${err}`); }
198
+ }
182
199
  private async checkHardwareStatusAsync(connectionId: string, deviceBinding: string) {
183
200
  try {
184
201
  let dev = await NixieEquipment.getDeviceService(connectionId, `/status/device/${deviceBinding}`);