nodejs-poolcontroller 7.3.1 → 7.6.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 (85) hide show
  1. package/.eslintrc.json +44 -44
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +52 -52
  3. package/CONTRIBUTING.md +74 -74
  4. package/Changelog +215 -195
  5. package/Dockerfile +17 -17
  6. package/Gruntfile.js +40 -40
  7. package/LICENSE +661 -661
  8. package/README.md +191 -186
  9. package/app.ts +2 -0
  10. package/config/Config.ts +27 -2
  11. package/config/VersionCheck.ts +33 -14
  12. package/config copy.json +299 -299
  13. package/controller/Constants.ts +88 -0
  14. package/controller/Equipment.ts +2459 -2225
  15. package/controller/Errors.ts +180 -157
  16. package/controller/Lockouts.ts +437 -0
  17. package/controller/State.ts +364 -79
  18. package/controller/boards/BoardFactory.ts +45 -45
  19. package/controller/boards/EasyTouchBoard.ts +2653 -2489
  20. package/controller/boards/IntelliCenterBoard.ts +4230 -3973
  21. package/controller/boards/IntelliComBoard.ts +63 -63
  22. package/controller/boards/IntelliTouchBoard.ts +241 -167
  23. package/controller/boards/NixieBoard.ts +1675 -1105
  24. package/controller/boards/SystemBoard.ts +4697 -3201
  25. package/controller/comms/Comms.ts +222 -10
  26. package/controller/comms/messages/Messages.ts +13 -9
  27. package/controller/comms/messages/config/ChlorinatorMessage.ts +13 -4
  28. package/controller/comms/messages/config/CircuitGroupMessage.ts +6 -0
  29. package/controller/comms/messages/config/CircuitMessage.ts +0 -0
  30. package/controller/comms/messages/config/ConfigMessage.ts +0 -0
  31. package/controller/comms/messages/config/CoverMessage.ts +1 -0
  32. package/controller/comms/messages/config/CustomNameMessage.ts +30 -30
  33. package/controller/comms/messages/config/EquipmentMessage.ts +4 -0
  34. package/controller/comms/messages/config/ExternalMessage.ts +53 -33
  35. package/controller/comms/messages/config/FeatureMessage.ts +8 -1
  36. package/controller/comms/messages/config/GeneralMessage.ts +8 -0
  37. package/controller/comms/messages/config/HeaterMessage.ts +14 -28
  38. package/controller/comms/messages/config/IntellichemMessage.ts +4 -1
  39. package/controller/comms/messages/config/OptionsMessage.ts +38 -2
  40. package/controller/comms/messages/config/PumpMessage.ts +4 -20
  41. package/controller/comms/messages/config/RemoteMessage.ts +4 -0
  42. package/controller/comms/messages/config/ScheduleMessage.ts +347 -331
  43. package/controller/comms/messages/config/SecurityMessage.ts +1 -0
  44. package/controller/comms/messages/config/ValveMessage.ts +13 -3
  45. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +2 -3
  46. package/controller/comms/messages/status/EquipmentStateMessage.ts +79 -25
  47. package/controller/comms/messages/status/HeaterStateMessage.ts +86 -53
  48. package/controller/comms/messages/status/IntelliChemStateMessage.ts +445 -386
  49. package/controller/comms/messages/status/IntelliValveStateMessage.ts +35 -35
  50. package/controller/comms/messages/status/PumpStateMessage.ts +0 -0
  51. package/controller/comms/messages/status/VersionMessage.ts +0 -0
  52. package/controller/nixie/Nixie.ts +162 -160
  53. package/controller/nixie/NixieEquipment.ts +103 -103
  54. package/controller/nixie/bodies/Body.ts +120 -117
  55. package/controller/nixie/bodies/Filter.ts +135 -135
  56. package/controller/nixie/chemistry/ChemController.ts +2498 -2395
  57. package/controller/nixie/chemistry/Chlorinator.ts +314 -313
  58. package/controller/nixie/circuits/Circuit.ts +248 -210
  59. package/controller/nixie/heaters/Heater.ts +649 -441
  60. package/controller/nixie/pumps/Pump.ts +661 -599
  61. package/controller/nixie/schedules/Schedule.ts +257 -256
  62. package/controller/nixie/valves/Valve.ts +170 -170
  63. package/defaultConfig.json +286 -271
  64. package/issue_template.md +51 -51
  65. package/logger/DataLogger.ts +448 -433
  66. package/logger/Logger.ts +0 -0
  67. package/package.json +56 -54
  68. package/tsconfig.json +25 -25
  69. package/web/Server.ts +522 -31
  70. package/web/bindings/influxDB.json +1022 -894
  71. package/web/bindings/mqtt.json +654 -543
  72. package/web/bindings/mqttAlt.json +684 -574
  73. package/web/bindings/rulesManager.json +54 -54
  74. package/web/bindings/smartThings-Hubitat.json +31 -31
  75. package/web/bindings/valveRelays.json +20 -20
  76. package/web/bindings/vera.json +25 -25
  77. package/web/interfaces/baseInterface.ts +136 -136
  78. package/web/interfaces/httpInterface.ts +124 -122
  79. package/web/interfaces/influxInterface.ts +245 -240
  80. package/web/interfaces/mqttInterface.ts +475 -464
  81. package/web/services/config/Config.ts +181 -152
  82. package/web/services/config/ConfigSocket.ts +0 -0
  83. package/web/services/state/State.ts +118 -7
  84. package/web/services/state/StateSocket.ts +18 -1
  85. package/web/services/utilities/Utilities.ts +42 -42
@@ -43,9 +43,9 @@ export class Connection {
43
43
  if (conn.connTimer !== null) clearTimeout(conn.connTimer);
44
44
  if (!conn._cfg.mockPort && conn._cfg.inactivityRetry > 0 && !conn._closing) conn.connTimer = setTimeout(async () => {
45
45
  try {
46
- await conn.openAsync()
46
+ await conn.openAsync();
47
47
  }
48
- catch (err) {};
48
+ catch (err) { logger.error(`Error resetting RS485 port on inactivity: ${err.message}`); };
49
49
  }, conn._cfg.inactivityRetry * 1000);
50
50
  }
51
51
  public isRTS: boolean = true;
@@ -82,8 +82,132 @@ export class Connection {
82
82
  return this._cfg;
83
83
  } catch (err) { return Promise.reject(err); }
84
84
  }
85
+ // RKS: 12-18-21 This is a bullpen method to work through the inconsistencies in the socket implementation for node. There
86
+ // are issues related to the event listeners and the construction of a socket. We want an implementation that awaits until the socket
87
+ // is completely open before continuing.
88
+ // We also need to be able to destroy the port without it restarting on its own when tearing the port down or deliberately closing it. This means
89
+ // that the listeners need to be removed before closing via method but re-open the port when the close is hit prematurely.
90
+ protected async openNetSerialPort(): Promise<boolean> {
91
+ try {
92
+ let opts: net.NetConnectOpts = {
93
+ host: this._cfg.netHost,
94
+ port: this._cfg.netPort
95
+ };
96
+ if (typeof this.connTimer !== 'undefined' && this.connTimer) clearTimeout(this.connTimer);
97
+ this.connTimer = null;
98
+ let ret = await new Promise<boolean>((resolve, _) => {
99
+ let nc = net.createConnection(opts, () => {
100
+ nc.on('connect', () => { logger.info(`Net connect (socat) connected to: ${this._cfg.netHost}:${this._cfg.netPort}`); }); // Socket is opened but not yet ready.
101
+ nc.on('ready', () => {
102
+ this.isOpen = true;
103
+ this.isRTS = true;
104
+ logger.info(`Net connect (socat) ready and communicating: ${this._cfg.netHost}:${this._cfg.netPort}`);
105
+ nc.on('data', (data) => {
106
+ if (data.length > 0 && !this.isPaused) this.emitter.emit('packetread', data);
107
+ });
108
+ this._port = nc;
109
+ // After the port is fully opened, set an inactivity timeout to restart it when it stops communicating.
110
+ nc.setTimeout(Math.max(this._cfg.inactivityRetry, 10) * 1000, async () => {
111
+ logger.warn(`Net connect (socat) connection idle: ${this._cfg.netHost}:${this._cfg.netPort} retrying connection.`);
112
+ try {
113
+ await conn.endAsync();
114
+ await conn.openAsync();
115
+ } catch (err) { logger.error(`Net connect (socat) error retrying connection ${err.message}`); }
116
+ });
117
+ resolve(true);
118
+ });
119
+ nc.on('close', (hadError: boolean) => {
120
+ this.isOpen = false;
121
+ if (typeof this._port !== 'undefined') {
122
+ this._port.destroy();
123
+ this._port.removeAllListeners();
124
+ }
125
+ this._port = undefined;
126
+ this.buffer.clearOutbound();
127
+ logger.info(`Net connect (socat) closed ${hadError === true ? 'due to error' : ''}: ${this._cfg.netHost}:${this._cfg.netPort}`);
128
+ if (!this._closing) {
129
+ // If we are closing manually this event should have been cleared already and should never be called. If this is fired out
130
+ // of sequence then we will check the closing flag to ensure we are not forcibly closing the socket.
131
+ if (typeof this.connTimer !== 'undefined' && this.connTimer) {
132
+ clearTimeout(this.connTimer);
133
+ this.connTimer = null;
134
+ }
135
+ this.connTimer = setTimeout(async () => {
136
+ try {
137
+ // We are already closed so give some inactivity retry and try again.
138
+ await conn.openAsync();
139
+ } catch (err) { }
140
+ }, this._cfg.inactivityRetry * 1000);
141
+ }
142
+ });
143
+ nc.on('end', () => { // Happens when the other end of the socket closes.
144
+ this.isOpen = false;
145
+ logger.info(`Net connect (socat) end event was fired`);
146
+ });
147
+ });
148
+ nc.once('error', (err) => {
149
+ // if the promise has already been fulfilled, but the error happens later, we don't want to call the promise again.
150
+ if (this._cfg.inactivityRetry > 0) {
151
+ logger.error(`Net connect (socat) connection error: ${err}. Retry in ${this._cfg.inactivityRetry} seconds`);
152
+ this.connTimer = setTimeout(async () => {
153
+ try {
154
+ await conn.closeAsync();
155
+ await conn.openAsync();
156
+ } catch (err) { }
157
+ }, this._cfg.inactivityRetry * 1000);
158
+ }
159
+ else logger.error(`Net connect (socat) connection error: ${err}. Never retrying -- No retry time set`);
160
+ resolve(false);
161
+ });
162
+ });
163
+ return ret;
164
+ } catch (err) { logger.error(`Error opening net serial port. ${this._cfg.netHost}:${this._cfg.netPort}`); }
165
+ }
166
+ protected async closeNetSerialPort(): Promise<boolean> {
167
+ try {
168
+ this._closing = true;
169
+ if (this.connTimer) clearTimeout(this.connTimer);
170
+ this.connTimer = null;
171
+ if (typeof this._port !== 'undefined' && this.isOpen) {
172
+ let success = await new Promise<boolean>((resolve, reject) => {
173
+ if (this._cfg.netConnect) {
174
+ this._port.removeAllListeners();
175
+ this._port.once('error', (err) => {
176
+ if (err) {
177
+ logger.error(`Net connect (socat) error closing ${this._cfg.netHost}:${this._cfg.netPort}: ${err}`);
178
+ resolve(false);
179
+ }
180
+ else {
181
+ conn._port = undefined;
182
+ this.isOpen = false;
183
+ logger.info(`Successfully closed (socat) port ${this._cfg.netHost}:${this._cfg.netPort}`);
184
+ resolve(true);
185
+ }
186
+ });
187
+ this._port.once('close', (p) => {
188
+ this.isOpen = false;
189
+ this._port = undefined;
190
+ logger.info(`Net connect (socat) successfully closed: ${this._cfg.netHost}:${this._cfg.netPort}`);
191
+ resolve(true);
192
+ });
193
+ this._port.destroy();
194
+ }
195
+ else {
196
+ resolve(true);
197
+ conn._port = undefined;
198
+ }
199
+ });
200
+ if (success) {
201
+ if (typeof conn.buffer !== 'undefined') conn.buffer.close();
202
+ }
203
+ return success;
204
+ }
205
+ return true;
206
+ } catch (err) { logger.error(`Error closing comms connection: ${err.message}`); return Promise.resolve(false); }
207
+
208
+ }
85
209
  public async openAsync(): Promise<boolean> {
86
- if (typeof (this.buffer) === 'undefined') {
210
+ if (typeof this.buffer === 'undefined') {
87
211
  this.buffer = new SendRecieveBuffer();
88
212
  this.emitter.on('packetread', (pkt) => { this.buffer.pushIn(pkt); });
89
213
  this.emitter.on('messagewrite', (msg) => { this.buffer.pushOut(msg); });
@@ -97,18 +221,38 @@ export class Connection {
97
221
  let nc: net.Socket = new net.Socket();
98
222
  nc.on('connect', () => { logger.info(`Net connect (socat) connected to: ${this._cfg.netHost}:${this._cfg.netPort}`); }); // Socket is opened but not yet ready.
99
223
  nc.on('ready', () => {
224
+ this.isOpen = true;
225
+ this.isRTS = true;
100
226
  logger.info(`Net connect (socat) ready and communicating: ${this._cfg.netHost}:${this._cfg.netPort}`);
101
- nc.on('data', (data) => { if (data.length > 0 && !this.isPaused) this.emitter.emit('packetread', data); });
227
+ nc.on('data', (data) => {
228
+ //this.resetConnTimer();
229
+ if (data.length > 0 && !this.isPaused) this.emitter.emit('packetread', data);
230
+ });
102
231
  });
103
232
  nc.on('close', (p) => {
104
233
  this.isOpen = false;
105
234
  if (typeof this._port !== 'undefined') this._port.destroy();
106
235
  this._port = undefined;
236
+ this.buffer.clearOutbound();
237
+ if (!this._closing) {
238
+ // If we are closing manually this event should have been cleared already and should never be called. If this is fired out
239
+ // of sequence then we will check the closing flag to ensure we are not forcibly closing the socket.
240
+ if (typeof this.connTimer !== 'undefined' && this.connTimer) {
241
+ clearTimeout(this.connTimer);
242
+ this.connTimer = null;
243
+ }
244
+ this.connTimer = setTimeout(async () => {
245
+ try {
246
+ // We are already closed so give some inactivity retry and try again.
247
+ await conn.openAsync();
248
+ } catch (err) { }
249
+ }, this._cfg.inactivityRetry * 1000);
250
+ }
107
251
  logger.info(`Net connect (socat) closed ${p === true ? 'due to error' : ''}: ${this._cfg.netHost}:${this._cfg.netPort}`);
108
252
  });
109
253
  nc.on('end', () => { // Happens when the other end of the socket closes.
110
254
  this.isOpen = false;
111
- this.resetConnTimer();
255
+ //this.resetConnTimer();
112
256
  logger.info(`Net connect (socat) end event was fired`);
113
257
  });
114
258
  //nc.on('drain', () => { logger.info(`The drain event was fired.`); });
@@ -116,15 +260,28 @@ export class Connection {
116
260
  // Occurs when there is no activity. This should not reset the connection, the previous implementation did so and
117
261
  // left the connection in a weird state where the previous connection was processing events and the new connection was
118
262
  // doing so as well. This isn't an error it is a warning as the RS485 bus will most likely be communicating at all times.
119
- nc.on('timeout', () => { logger.warn(`Net connect (socat) Connection Idle: ${this._cfg.netHost}:${this._cfg.netPort}`); });
263
+ //nc.on('timeout', () => { logger.warn(`Net connect (socat) Connection Idle: ${this._cfg.netHost}:${this._cfg.netPort}`); });
264
+ nc.setTimeout(Math.max(this._cfg.inactivityRetry, 10) * 1000, async () => {
265
+ logger.warn(`Net connect (socat) connection idle: ${this._cfg.netHost}:${this._cfg.netPort} retrying connection.`);
266
+ try {
267
+ await conn.endAsync();
268
+ await conn.openAsync();
269
+ } catch (err) { logger.error(`Net connect (socat) error retrying connection ${err.message}`); }
270
+ });
271
+
120
272
  return await new Promise<boolean>((resolve, _) => {
121
273
  // We only connect an error once as we will destroy this connection on error then recreate a new socket on failure.
122
274
  nc.once('error', (err) => {
123
- logger.error(`Net connect (socat) Connection: ${err}. ${this._cfg.inactivityRetry > 0 ? `Retry in ${this._cfg.inactivityRetry} seconds` : `Never retrying; inactivityRetry set to ${this._cfg.inactivityRetry}`}`);
124
- this.resetConnTimer();
275
+ //logger.error(`Net connect (socat) Connection: ${err}. ${this._cfg.inactivityRetry > 0 ? `Retry in ${this._cfg.inactivityRetry} seconds` : `Never retrying; inactivityRetry set to ${this._cfg.inactivityRetry}`}`);
276
+ //this.resetConnTimer();
125
277
  this.isOpen = false;
126
278
  // if the promise has already been fulfilled, but the error happens later, we don't want to call the promise again.
127
279
  if (typeof resolve !== 'undefined') { resolve(false); }
280
+ if (this._cfg.inactivityRetry > 0) {
281
+ logger.error(`Net connect (socat) connection error: ${err}. Retry in ${this._cfg.inactivityRetry} seconds`);
282
+ setTimeout(async () => { try { await conn.openAsync(); } catch (err) { } }, this._cfg.inactivityRetry * 1000);
283
+ }
284
+ else logger.error(`Net connect (socat) connection error: ${err}. Never retrying -- No retry time set`);
128
285
  });
129
286
  nc.connect(conn._cfg.netPort, conn._cfg.netHost, () => {
130
287
  if (typeof this._port !== 'undefined') logger.warn('Net connect (socat) recovered from lost connection.');
@@ -243,12 +400,67 @@ export class Connection {
243
400
  if (success) {
244
401
  if (typeof conn.buffer !== 'undefined') conn.buffer.close();
245
402
  }
246
-
247
403
  return success;
248
404
  }
249
405
  return true;
250
406
  } catch (err) { logger.error(`Error closing comms connection: ${err.message}`); return Promise.resolve(false); }
251
407
  }
408
+ public async endAsync(): Promise<boolean> {
409
+ try {
410
+ this._closing = true;
411
+ if (this.connTimer) clearTimeout(this.connTimer);
412
+ if (typeof this._port !== 'undefined' && this.isOpen) {
413
+ let success = await new Promise<boolean>((resolve, reject) => {
414
+ if (this._cfg.netConnect) {
415
+ this._port.removeAllListeners();
416
+ this._port.once('error', (err) => {
417
+ if (err) {
418
+ logger.error(`Error closing ${this._cfg.netHost}:${this._cfg.netPort}/${this._cfg.rs485Port}: ${err}`);
419
+ resolve(false);
420
+ }
421
+ else {
422
+ conn._port = undefined;
423
+ this.isOpen = false;
424
+ logger.info(`Successfully closed (socat) port ${this._cfg.netHost}:${this._cfg.netPort}/${this._cfg.rs485Port}`);
425
+ resolve(true);
426
+ }
427
+ });
428
+ this._port.once('close', (p) => {
429
+ this.isOpen = false;
430
+ this._port = undefined;
431
+ logger.info(`Net connect (socat) successfully closed: ${this._cfg.netHost}:${this._cfg.netPort}`);
432
+ resolve(true);
433
+ });
434
+ this._port.destroy();
435
+ }
436
+ else if (typeof conn._port.close === 'function') {
437
+ conn._port.close((err) => {
438
+ if (err) {
439
+ logger.error(`Error closing ${this._cfg.rs485Port}: ${err}`);
440
+ resolve(false);
441
+ }
442
+ else {
443
+ conn._port = undefined;
444
+ logger.info(`Successfully closed seral port ${this._cfg.rs485Port}`);
445
+ resolve(true);
446
+ this.isOpen = false;
447
+ }
448
+ });
449
+ }
450
+ else {
451
+ resolve(true);
452
+ conn._port = undefined;
453
+ }
454
+ });
455
+ if (success) {
456
+ if (typeof conn.buffer !== 'undefined') conn.buffer.close();
457
+ }
458
+ return success;
459
+ }
460
+ return true;
461
+ } catch (err) { logger.error(`Error closing comms connection: ${err.message}`); return Promise.resolve(false); }
462
+ finally { this._closing = false; }
463
+ }
252
464
  public drain(cb: Function) {
253
465
  if (typeof (conn._port.drain) === 'function')
254
466
  conn._port.drain(cb);
@@ -347,7 +559,7 @@ export class SendRecieveBuffer {
347
559
  public pushOut(msg) { conn.buffer._outBuffer.push(msg); setTimeout(() => { this.processPackets(); }, 0); }
348
560
  public clear() { conn.buffer._inBuffer.length = 0; conn.buffer._outBuffer.length = 0; }
349
561
  public close() { clearTimeout(conn.buffer.procTimer); conn.buffer.clear(); this._msg = undefined; }
350
-
562
+ public clearOutbound() { conn.buffer._outBuffer.length = 0; conn.buffer._waitingPacket = undefined; }
351
563
  /********************************************************************
352
564
  * RKS: 06-06-20
353
565
  * This used to process every 175ms. While the processing was light
@@ -168,12 +168,13 @@ export class Inbound extends Message {
168
168
  return `{"id":${this.id},"valid":${this.isValid},"dir":"${this.direction}","proto":"${this.protocol}","for":${JSON.stringify(this.responseFor)},"pkt":[${JSON.stringify(this.padding)},${JSON.stringify(this.preamble)},${JSON.stringify(this.header)},${JSON.stringify(this.payload)},${JSON.stringify(this.term)}],"ts": "${Timestamp.toISOLocal(this.timestamp)}"}`;
169
169
  return `{"id":${this.id},"valid":${this.isValid},"dir":"${this.direction}","proto":"${this.protocol}","pkt":[${JSON.stringify(this.padding)},${JSON.stringify(this.preamble)},${JSON.stringify(this.header)},${JSON.stringify(this.payload)},${JSON.stringify(this.term)}],"ts": "${Timestamp.toISOLocal(this.timestamp)}"}`;
170
170
  }
171
- private testChlorHeader(bytes: number[], ndx: number): boolean {
171
+ private testChlorHeader(bytes: number[], ndx: number): boolean {
172
172
  // if packets have 16,2 (eg status=16,2,29) in them and they come as partial packets, they would have
173
173
  // prev been detected as chlor packets;
174
174
  // valid chlor packets should have 16,2,0 or 16,2,[80-96];
175
175
  // this should reduce the number of false chlor packets
176
- return (ndx + 2 < bytes.length && bytes[ndx] === 16 && bytes[ndx + 1] === 2 && (bytes[ndx + 2] === 0 || (bytes[ndx + 2] >= 80 && bytes[ndx + 2] <= 96))); }
176
+ return (ndx + 2 < bytes.length && bytes[ndx] === 16 && bytes[ndx + 1] === 2 && (bytes[ndx + 2] === 0 || (bytes[ndx + 2] >= 80 && bytes[ndx + 2] <= 96)))
177
+ }
177
178
  private testBroadcastHeader(bytes: number[], ndx: number): boolean { return ndx < bytes.length - 3 && bytes[ndx] === 255 && bytes[ndx + 1] === 0 && bytes[ndx + 2] === 255 && bytes[ndx + 3] === 165; }
178
179
  private testUnidentifiedHeader(bytes: number[], ndx: number): boolean { return ndx < bytes.length - 3 && bytes[ndx] === 255 && bytes[ndx + 1] === 0 && bytes[ndx + 2] === 255 && bytes[ndx + 3] !== 165; }
179
180
  private testChlorTerm(bytes: number[], ndx: number): boolean { return ndx < bytes.length - 2 && bytes[ndx + 1] === 16 && bytes[ndx + 2] === 3; }
@@ -404,6 +405,7 @@ export class Inbound extends Message {
404
405
  case ControllerType.IntelliCenter:
405
406
  switch (this.action) {
406
407
  case 1: // ACK
408
+ this.isProcessed = true;
407
409
  break;
408
410
  case 2:
409
411
  case 204:
@@ -423,12 +425,14 @@ export class Inbound extends Message {
423
425
  break;
424
426
  case 222: // A panel is asking for action 30s
425
427
  case 228: // A panel is asking for the current version
428
+ this.isProcessed = true;
426
429
  break;
427
430
  default:
428
431
  logger.info(`An unprocessed message was received ${this.toPacket()}`)
429
432
  break;
430
433
 
431
434
  }
435
+ if (!this.isProcessed) logger.info(`The message was not processed ${this.action} - ${this.toPacket()}`);
432
436
  break;
433
437
  default:
434
438
  switch (this.action) {
@@ -483,14 +487,12 @@ export class Inbound extends Message {
483
487
  CircuitMessage.processTouch(this);
484
488
  break;
485
489
  case 40:
490
+ case 168:
486
491
  OptionsMessage.process(this);
487
492
  break;
488
493
  case 41:
489
494
  CircuitGroupMessage.process(this);
490
495
  break;
491
- case 168:
492
- if (sys.controllerType !== ControllerType.Unknown) HeaterMessage.process(this);
493
- break;
494
496
  case 197:
495
497
  EquipmentStateMessage.process(this); // Date/Time request
496
498
  break;
@@ -500,17 +502,19 @@ export class Inbound extends Message {
500
502
  case 9:
501
503
  case 16:
502
504
  case 34:
503
- case 114:
504
505
  case 137:
505
506
  case 144:
506
507
  case 162:
507
508
  HeaterMessage.process(this);
508
509
  break;
510
+ case 114:
511
+ case 115:
512
+ HeaterStateMessage.process(this);
513
+ break
509
514
  case 147:
510
515
  IntellichemMessage.process(this);
511
516
  break;
512
517
  default:
513
- // take these out...
514
518
  if (this.action === 109 && this.payload[1] === 3) break;
515
519
  if (this.source === 17 && this.payload[0] === 109) break;
516
520
  logger.debug(`Packet not processed: ${this.toPacket()}`);
@@ -626,11 +630,11 @@ export class Outbound extends OutboundCommon {
626
630
  out.onComplete = obj.onComplete;
627
631
  out.onAbort = obj.onAbort;
628
632
  out.timeout = obj.timeout;
629
- for (let i = 0; i < out.header.length; i++){
633
+ for (let i = 0; i < out.header.length; i++) {
630
634
  if (out.header[i] >= 0 && out.header[i] <= 255 && out.header[i] !== null && typeof out.header[i] !== 'undefined') continue;
631
635
  throw new OutboundMessageError(out, `Invalid header detected: ${out.toShortPacket()}`);
632
636
  }
633
- for (let i = 0; i < out.payload.length; i++){
637
+ for (let i = 0; i < out.payload.length; i++) {
634
638
  if (out.payload[i] >= 0 && out.payload[i] <= 255 && out.payload[i] !== null && typeof out.payload[i] !== 'undefined') continue;
635
639
  throw new OutboundMessageError(out, `Invalid payload detected: ${out.toShortPacket()}`);
636
640
  }
@@ -27,15 +27,19 @@ export class ChlorinatorMessage {
27
27
  chlorId = 1;
28
28
  for (let i = 0; i < 4 && i + 30 < msg.payload.length; i++) {
29
29
  let isActive = msg.extractPayloadByte(i + 22) === 1;
30
+ chlor = sys.chlorinators.getItemById(chlorId);
31
+ if (chlor.master === 1) continue; // RSG: probably never need this. See Touch chlor below.
30
32
  if (isActive) {
31
- let chlor = sys.chlorinators.getItemById(chlorId, true);
33
+ chlor = sys.chlorinators.getItemById(chlorId, true);
32
34
  let schlor = state.chlorinators.getItemById(chlor.id, true);
33
35
  chlor.isActive = schlor.isActive = true;
34
36
  chlor.body = msg.extractPayloadByte(i + 2);
35
37
  chlor.type = msg.extractPayloadByte(i + 6);
36
- if (!chlor.disabled) {
38
+ if (!chlor.disabled && !chlor.isDosing) {
37
39
  // RKS: We don't want to change the setpoints if our chem controller disabled
38
40
  // the chlorinator. These should be 0.
41
+ if (msg.extractPayloadByte(i + 10) === 0 && chlor.poolSetpoint > 0) logger.info(`Changing pool setpoint to 0 ${msg.extractPayloadByte(i + 10)}`);
42
+
39
43
  chlor.poolSetpoint = msg.extractPayloadByte(i + 10);
40
44
  chlor.spaSetpoint = msg.extractPayloadByte(i + 14);
41
45
  }
@@ -57,6 +61,7 @@ export class ChlorinatorMessage {
57
61
  }
58
62
  chlorId++;
59
63
  }
64
+ msg.isProcessed = true;
60
65
  break;
61
66
  default:
62
67
  logger.debug(`Unprocessed Config Message ${msg.toPacket()}`)
@@ -65,6 +70,8 @@ export class ChlorinatorMessage {
65
70
  }
66
71
  public static processTouch(msg: Inbound) {
67
72
  // This is for the 25 message that is broadcast from the OCP.
73
+ let chlor = sys.chlorinators.getItemById(1);
74
+ if (chlor.master === 1) return; // Some Aquarite chlors need more frequent control (via Nixie) but will be disabled via Touch. https://github.com/tagyoureit/nodejs-poolController/issues/349
68
75
  let isActive = (msg.extractPayloadByte(0) & 0x01) === 1;
69
76
  if (isActive) {
70
77
  let chlor = sys.chlorinators.getItemById(1, true);
@@ -78,8 +85,9 @@ export class ChlorinatorMessage {
78
85
  chlor.address = chlor.id + 79;
79
86
  schlor.body = chlor.body = sys.equipment.maxBodies >= 1 || sys.equipment.shared === true ? 32 : 0;
80
87
  }
81
- schlor.name = chlor.name = msg.extractPayloadString(6, 16);
82
- if (typeof chlor.model === 'undefined') chlor.model = schlor.name;
88
+ if (typeof chlor.name === 'undefined') schlor.name = chlor.name = msg.extractPayloadString(6, 16);
89
+ if (typeof chlor.model === 'undefined') chlor.model = sys.board.valueMaps.chlorinatorModel.getValue(schlor.name.toLowerCase());
90
+ if (typeof chlor.type === 'undefined') chlor.type = schlor.type = 0;
83
91
  schlor.saltLevel = msg.extractPayloadByte(3) * 50 || schlor.saltLevel;
84
92
  schlor.status = msg.extractPayloadByte(4) & 0x007F; // Strip off the high bit. The chlorinator does not actually report this.;
85
93
  // Pull the hours from the 25 message.
@@ -110,5 +118,6 @@ export class ChlorinatorMessage {
110
118
  sys.chlorinators.removeItemById(1);
111
119
  state.chlorinators.removeItemById(1);
112
120
  }
121
+ msg.isProcessed = true;
113
122
  }
114
123
  }
@@ -80,6 +80,7 @@ export class CircuitGroupMessage {
80
80
  group.circuits.removeItemByIndex(i);
81
81
  }
82
82
  }
83
+ msg.isProcessed = true;
83
84
  }
84
85
  else if (msgId >= 16 && msgId <= 31) {
85
86
  groupId = msgId - 16 + sys.board.equipmentIds.circuitGroups.start;
@@ -90,6 +91,7 @@ export class CircuitGroupMessage {
90
91
  group.name = msg.extractPayloadString(2, 16);
91
92
  sgroup.name = group.name;
92
93
  }
94
+ msg.isProcessed = true;
93
95
  }
94
96
  }
95
97
  }
@@ -143,6 +145,7 @@ export class CircuitGroupMessage {
143
145
  state.circuitGroups.removeItemById(groupId);
144
146
  }
145
147
  state.emitEquipmentChanges();
148
+ msg.isProcessed = true;
146
149
  }
147
150
  private static processGroupType(msg: Inbound) {
148
151
  var groupId = ((msg.extractPayloadByte(1) - 32) * 16) + sys.board.equipmentIds.circuitGroups.start;
@@ -188,6 +191,7 @@ export class CircuitGroupMessage {
188
191
  sgroup.type = group.type;
189
192
  }
190
193
  state.emitEquipmentChanges();
194
+ msg.isProcessed = true;
191
195
  }
192
196
  private static processColor(msg: Inbound) {
193
197
  var groupId = ((msg.extractPayloadByte(1) - 35)) + sys.board.equipmentIds.circuitGroups.start;
@@ -208,6 +212,7 @@ export class CircuitGroupMessage {
208
212
  }
209
213
 
210
214
  }
215
+ msg.isProcessed = true;
211
216
  }
212
217
  private static processEggTimer(msg: Inbound) {
213
218
  var groupId = ((msg.extractPayloadByte(1) - 34) * 16) + sys.board.equipmentIds.circuitGroups.start;
@@ -220,5 +225,6 @@ export class CircuitGroupMessage {
220
225
  // sgroup.eggTimer = group.eggTimer;
221
226
  }
222
227
  }
228
+ msg.isProcessed = true;
223
229
  }
224
230
  }
File without changes
@@ -32,6 +32,7 @@ export class CoverMessage {
32
32
  for (let i = 1; i < 10; i++) {
33
33
  if (msg.extractPayloadByte(i + 18) !== 255) cover.circuits.push(msg.extractPayloadByte(i + 18));
34
34
  }
35
+ msg.isProcessed = true;
35
36
  break;
36
37
  default:
37
38
  logger.debug(`Unprocessed Config Message ${msg.toPacket()}`)
@@ -1,31 +1,31 @@
1
- /* nodejs-poolController. An application to control pool equipment.
2
- Copyright (C) 2016, 2017, 2018, 2019, 2020. Russell Goldin, tagyoureit. russ.goldin@gmail.com
3
-
4
- This program is free software: you can redistribute it and/or modify
5
- it under the terms of the GNU Affero General Public License as
6
- published by the Free Software Foundation, either version 3 of the
7
- License, or (at your option) any later version.
8
-
9
- This program is distributed in the hope that it will be useful,
10
- but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
- GNU Affero General Public License for more details.
13
-
14
- You should have received a copy of the GNU Affero General Public License
15
- along with this program. If not, see <http://www.gnu.org/licenses/>.
16
- */
17
- import { Inbound } from "../Messages";
18
- import { sys } from "../../../Equipment";
19
- export class CustomNameMessage
20
- {
21
- public static process ( msg: Inbound ): void
22
- {
23
- let customNameId = msg.extractPayloadByte( 0 );
24
- let customName = sys.customNames.getItemById( customNameId, customNameId <= sys.equipment.maxCustomNames );
25
- customName.name = msg.extractPayloadString( 1, 11 );
26
- // customName.isActive = customNameId <= sys.equipment.maxCustomNames && !customName.name.includes('USERNAME-')
27
- if (customNameId >= sys.equipment.maxCustomNames) sys.equipment.maxCustomNames = customNameId + 1;
28
- sys.board.system.syncCustomNamesValueMap();
29
- msg.isProcessed = true;
30
- }
1
+ /* nodejs-poolController. An application to control pool equipment.
2
+ Copyright (C) 2016, 2017, 2018, 2019, 2020. Russell Goldin, tagyoureit. russ.goldin@gmail.com
3
+
4
+ This program is free software: you can redistribute it and/or modify
5
+ it under the terms of the GNU Affero General Public License as
6
+ published by the Free Software Foundation, either version 3 of the
7
+ License, or (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU Affero General Public License for more details.
13
+
14
+ You should have received a copy of the GNU Affero General Public License
15
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ */
17
+ import { Inbound } from "../Messages";
18
+ import { sys } from "../../../Equipment";
19
+ export class CustomNameMessage
20
+ {
21
+ public static process ( msg: Inbound ): void
22
+ {
23
+ let customNameId = msg.extractPayloadByte( 0 );
24
+ let customName = sys.customNames.getItemById( customNameId, customNameId <= sys.equipment.maxCustomNames );
25
+ customName.name = msg.extractPayloadString( 1, 11 );
26
+ // customName.isActive = customNameId <= sys.equipment.maxCustomNames && !customName.name.includes('USERNAME-')
27
+ if (customNameId >= sys.equipment.maxCustomNames) sys.equipment.maxCustomNames = customNameId + 1;
28
+ sys.board.system.syncCustomNamesValueMap();
29
+ msg.isProcessed = true;
30
+ }
31
31
  }
@@ -48,6 +48,7 @@ export class EquipmentMessage {
48
48
  body.capacity = msg.extractPayloadByte(34) * 1000;
49
49
  if (body.isActive && sys.equipment.maxBodies === 0) sys.bodies.removeItemById(1);
50
50
  body.isActive = sys.equipment.maxBodies > 0;
51
+ msg.isProcessed = true;
51
52
  break;
52
53
  case 1:
53
54
  pnl = sys.equipment.expansions.getItemById(2);
@@ -66,6 +67,7 @@ export class EquipmentMessage {
66
67
  }
67
68
  pnl = sys.equipment.expansions.getItemById(3);
68
69
  pnl.name = msg.extractPayloadString(18, 16);
70
+ msg.isProcessed = true;
69
71
  break;
70
72
  case 2:
71
73
  // The first name is the first body in this packet and the second is the third. Go figure.
@@ -87,6 +89,7 @@ export class EquipmentMessage {
87
89
  sys.bodies.removeItemById(bodyId);
88
90
  state.temps.bodies.removeItemById(bodyId);
89
91
  }
92
+ msg.isProcessed = true;
90
93
  break;
91
94
  case 3:
92
95
  // The first name is the second body and the 2nd is the 4th. This packet also contains
@@ -136,6 +139,7 @@ export class EquipmentMessage {
136
139
  state.equipment.maxValves = sys.equipment.maxValves;
137
140
  state.equipment.maxSchedules = sys.equipment.maxSchedules;
138
141
  state.equipment.maxPumps = sys.equipment.maxPumps;
142
+ msg.isProcessed = true;
139
143
  break;
140
144
  default:
141
145
  logger.debug(`Unprocessed Config Message ${msg.toPacket()}`)