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
@@ -22,28 +22,42 @@ export class HeaterStateMessage {
22
22
  public static process(msg: Inbound) {
23
23
  if (msg.protocol === Protocol.Heater) {
24
24
  switch (msg.action) {
25
- case 114: // This is a message from a master controlling the heater
25
+ case 112: // This is a message from a master controlling MasterTemp
26
+ case 114: // This is a message from a master controlling UltraTemp
27
+ msg.isProcessed = true;
28
+ break;
29
+ case 116:
30
+ HeaterStateMessage.processMasterTempStatus(msg);
26
31
  break;
27
32
  case 115:
28
- HeaterStateMessage.processHeaterStatus(msg);
33
+ HeaterStateMessage.processUltraTempStatus(msg);
29
34
  break;
30
35
  }
31
36
  }
32
37
  }
33
- public static processHeaterStatus(msg: Inbound) {
38
+ public static processUltraTempStatus(msg: Inbound) {
34
39
  // RKS: 07-03-21 - We only know byte 2 at this point for Ultratemp for the 115 message we are processing here. The
35
40
  // byte description
36
41
  // ------------------------------------------------
37
- // 0 Unknown
38
- // 1 Unknown
42
+ // 0 Unknown (always seems to be 160 for response)
43
+ // 1 Unknown (always 1)
39
44
  // 2 Current heater status 0=off, 1=heat, 2=cool
40
45
  // 3-9 Unknown
41
- let heater: Heater = sys.heaters.getItemByAddress(msg.source);
42
- let sheater = state.heaters.getItemById(heater.id);
43
- // We need to decode the message. For a 2 of
44
- //[165, 1, 15, 16, 2, 29][16, 42, 3, 0, 0, 0, 0, 0, 0, 32, 0, 0, 2, 0, 88, 88, 0, 241, 95, 100, 24, 246, 0, 0, 0, 0, 0, 40, 0][4, 221]
46
+
47
+ // 114 message - outbound response
45
48
  //[165, 0, 112, 16, 114, 10][144, 0, 0, 0, 0, 0, 0, 0, 0, 0][2, 49] // OCP to Heater
49
+ // byte description
50
+ // ------------------------------------------------
51
+ // 0 Unknown (always seems to be 144 for request)
52
+ // 1 Current heater status 0=off, 1=heat, 2=cool
53
+ // 3 Believed to be ofset temp
54
+ // 4-9 Unknown
55
+
56
+ // byto 0: always seems to be 144 for outbound
57
+ // byte 1: Sets heater mode to 0 = Off 1 = Heat 2 = Cool
46
58
  //[165, 0, 16, 112, 115, 10][160, 1, 0, 3, 0, 0, 0, 0, 0, 0][2, 70] // Heater Reply
59
+ let heater: Heater = sys.heaters.getItemByAddress(msg.source);
60
+ let sheater = state.heaters.getItemById(heater.id);
47
61
  let byte = msg.extractPayloadByte(2);
48
62
  sheater.isOn = byte >= 1;
49
63
  sheater.isCooling = byte === 2;
@@ -51,4 +65,23 @@ export class HeaterStateMessage {
51
65
  state.equipment.messages.removeItemByCode(`heater:${heater.id}:comms`);
52
66
  msg.isProcessed = true;
53
67
  }
68
+ public static processMasterTempStatus(msg: Inbound) {
69
+ //[255, 0, 255][165, 0, 16, 112, 116, 23][67, 0, 0, 0, 0, 0, 0, 0, 68, 0, 0, 0, 10, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0][2, 66]
70
+ // Byte 1 is the indicator to which setpoint it is heating to.
71
+ // Byte 8 increments over time when the heater is on.
72
+ // Byte 13 looks like the mode the heater is in for instance it is in cooldown mode.
73
+ // 0 = Normal
74
+ // 2 = ??????
75
+ // 6 = Cooldown
76
+ // Byte 14 looks like the cooldown delay in minutes.
77
+ let heater: Heater = sys.heaters.getItemByAddress(msg.source);
78
+ let sheater = state.heaters.getItemById(heater.id);
79
+ let byte = msg.extractPayloadByte(1);
80
+ sheater.isOn = byte >= 1;
81
+ sheater.isCooling = false;
82
+ sheater.commStatus = 0;
83
+ state.equipment.messages.removeItemByCode(`heater:${heater.id}:comms`);
84
+ msg.isProcessed = true;
85
+ }
86
+
54
87
  }
@@ -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
  }
@@ -34,7 +34,7 @@ export class NixieBodyCollection extends NixieEquipmentCollection<NixieBody> {
34
34
  let body = bodies.getItemByIndex(i);
35
35
  if (body.master === 1) {
36
36
  if (typeof this.find(elem => elem.id === body.id) === 'undefined') {
37
- logger.info(`Initializing body ${body.name}`);
37
+ logger.info(`Initializing Nixie body ${body.name}`);
38
38
  let nbody = new NixieBody(this.controlPanel, body);
39
39
  this.push(nbody);
40
40
  }
@@ -64,6 +64,9 @@ export class NixieBody extends NixieEquipment {
64
64
  super(ncp);
65
65
  this.body = body;
66
66
  this.pollEquipmentAsync();
67
+ let bs = state.temps.bodies.getItemById(body.id);
68
+ bs.heaterCooldownDelay = false;
69
+ bs.heatStatus = 0;
67
70
  }
68
71
  public get id(): number { return typeof this.body !== 'undefined' ? this.body.id : -1; }
69
72
  public async setBodyAsync(data: any) {