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
@@ -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;
@@ -83,7 +83,7 @@ export class Connection {
83
83
  } catch (err) { return Promise.reject(err); }
84
84
  }
85
85
  public async openAsync(): Promise<boolean> {
86
- if (typeof (this.buffer) === 'undefined') {
86
+ if (typeof this.buffer === 'undefined') {
87
87
  this.buffer = new SendRecieveBuffer();
88
88
  this.emitter.on('packetread', (pkt) => { this.buffer.pushIn(pkt); });
89
89
  this.emitter.on('messagewrite', (msg) => { this.buffer.pushOut(msg); });
@@ -97,18 +97,24 @@ export class Connection {
97
97
  let nc: net.Socket = new net.Socket();
98
98
  nc.on('connect', () => { logger.info(`Net connect (socat) connected to: ${this._cfg.netHost}:${this._cfg.netPort}`); }); // Socket is opened but not yet ready.
99
99
  nc.on('ready', () => {
100
+ this.isOpen = true;
101
+ this.isRTS = true;
100
102
  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); });
103
+ nc.on('data', (data) => {
104
+ //this.resetConnTimer();
105
+ if (data.length > 0 && !this.isPaused) this.emitter.emit('packetread', data);
106
+ });
102
107
  });
103
108
  nc.on('close', (p) => {
104
109
  this.isOpen = false;
105
110
  if (typeof this._port !== 'undefined') this._port.destroy();
106
111
  this._port = undefined;
112
+ this.buffer.clearOutbound();
107
113
  logger.info(`Net connect (socat) closed ${p === true ? 'due to error' : ''}: ${this._cfg.netHost}:${this._cfg.netPort}`);
108
114
  });
109
115
  nc.on('end', () => { // Happens when the other end of the socket closes.
110
116
  this.isOpen = false;
111
- this.resetConnTimer();
117
+ //this.resetConnTimer();
112
118
  logger.info(`Net connect (socat) end event was fired`);
113
119
  });
114
120
  //nc.on('drain', () => { logger.info(`The drain event was fired.`); });
@@ -116,15 +122,28 @@ export class Connection {
116
122
  // Occurs when there is no activity. This should not reset the connection, the previous implementation did so and
117
123
  // left the connection in a weird state where the previous connection was processing events and the new connection was
118
124
  // 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}`); });
125
+ //nc.on('timeout', () => { logger.warn(`Net connect (socat) Connection Idle: ${this._cfg.netHost}:${this._cfg.netPort}`); });
126
+ nc.setTimeout(Math.max(this._cfg.inactivityRetry, 10) * 1000, async () => {
127
+ logger.warn(`Net connect (socat) connection idle: ${this._cfg.netHost}:${this._cfg.netPort} retrying connection.`);
128
+ try {
129
+ await conn.endAsync();
130
+ await conn.openAsync();
131
+ } catch (err) { logger.error(`Net connect (socat) error retrying connection ${err.message}`); }
132
+ });
133
+
120
134
  return await new Promise<boolean>((resolve, _) => {
121
135
  // We only connect an error once as we will destroy this connection on error then recreate a new socket on failure.
122
136
  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();
137
+ //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}`}`);
138
+ //this.resetConnTimer();
125
139
  this.isOpen = false;
126
140
  // if the promise has already been fulfilled, but the error happens later, we don't want to call the promise again.
127
141
  if (typeof resolve !== 'undefined') { resolve(false); }
142
+ if (this._cfg.inactivityRetry > 0) {
143
+ logger.error(`Net connect (socat) connection error: ${err}. Retry in ${this._cfg.inactivityRetry} seconds`);
144
+ setTimeout(async () => { try { await conn.openAsync(); } catch (err) { } }, this._cfg.inactivityRetry * 1000);
145
+ }
146
+ else logger.error(`Net connect (socat) connection error: ${err}. Never retrying -- No retry time set`);
128
147
  });
129
148
  nc.connect(conn._cfg.netPort, conn._cfg.netHost, () => {
130
149
  if (typeof this._port !== 'undefined') logger.warn('Net connect (socat) recovered from lost connection.');
@@ -249,6 +268,62 @@ export class Connection {
249
268
  return true;
250
269
  } catch (err) { logger.error(`Error closing comms connection: ${err.message}`); return Promise.resolve(false); }
251
270
  }
271
+ public async endAsync(): Promise<boolean> {
272
+ try {
273
+ this._closing = true;
274
+ if (this.connTimer) clearTimeout(this.connTimer);
275
+ if (typeof this._port !== 'undefined' && this.isOpen) {
276
+ let success = await new Promise<boolean>((resolve, reject) => {
277
+ if (this._cfg.netConnect) {
278
+ this._port.removeAllListeners();
279
+ this._port.once('error', (err) => {
280
+ if (err) {
281
+ logger.error(`Error closing ${this._cfg.netHost}:${this._cfg.netPort}/${this._cfg.rs485Port}: ${err}`);
282
+ resolve(false);
283
+ }
284
+ else {
285
+ conn._port = undefined;
286
+ this.isOpen = false;
287
+ logger.info(`Successfully closed (socat) port ${this._cfg.netHost}:${this._cfg.netPort}/${this._cfg.rs485Port}`);
288
+ resolve(true);
289
+ }
290
+ });
291
+ this._port.once('close', (p) => {
292
+ this.isOpen = false;
293
+ this._port = undefined;
294
+ logger.info(`Net connect (socat) successfully closed: ${this._cfg.netHost}:${this._cfg.netPort}`);
295
+ resolve(true);
296
+ });
297
+ this._port.destroy();
298
+ }
299
+ else if (typeof conn._port.close === 'function') {
300
+ conn._port.close((err) => {
301
+ if (err) {
302
+ logger.error(`Error closing ${this._cfg.rs485Port}: ${err}`);
303
+ resolve(false);
304
+ }
305
+ else {
306
+ conn._port = undefined;
307
+ logger.info(`Successfully closed seral port ${this._cfg.rs485Port}`);
308
+ resolve(true);
309
+ this.isOpen = false;
310
+ }
311
+ });
312
+ }
313
+ else {
314
+ resolve(true);
315
+ conn._port = undefined;
316
+ }
317
+ });
318
+ if (success) {
319
+ if (typeof conn.buffer !== 'undefined') conn.buffer.close();
320
+ }
321
+ return success;
322
+ }
323
+ return true;
324
+ } catch (err) { logger.error(`Error closing comms connection: ${err.message}`); return Promise.resolve(false); }
325
+ finally { this._closing = false; }
326
+ }
252
327
  public drain(cb: Function) {
253
328
  if (typeof (conn._port.drain) === 'function')
254
329
  conn._port.drain(cb);
@@ -347,7 +422,7 @@ export class SendRecieveBuffer {
347
422
  public pushOut(msg) { conn.buffer._outBuffer.push(msg); setTimeout(() => { this.processPackets(); }, 0); }
348
423
  public clear() { conn.buffer._inBuffer.length = 0; conn.buffer._outBuffer.length = 0; }
349
424
  public close() { clearTimeout(conn.buffer.procTimer); conn.buffer.clear(); this._msg = undefined; }
350
-
425
+ public clearOutbound() { conn.buffer._outBuffer.length = 0; conn.buffer._waitingPacket = undefined; }
351
426
  /********************************************************************
352
427
  * RKS: 06-06-20
353
428
  * 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) {
@@ -500,17 +504,19 @@ export class Inbound extends Message {
500
504
  case 9:
501
505
  case 16:
502
506
  case 34:
503
- case 114:
504
507
  case 137:
505
508
  case 144:
506
509
  case 162:
507
510
  HeaterMessage.process(this);
508
511
  break;
512
+ case 114:
513
+ case 115:
514
+ HeaterStateMessage.process(this);
515
+ break
509
516
  case 147:
510
517
  IntellichemMessage.process(this);
511
518
  break;
512
519
  default:
513
- // take these out...
514
520
  if (this.action === 109 && this.payload[1] === 3) break;
515
521
  if (this.source === 17 && this.payload[0] === 109) break;
516
522
  logger.debug(`Packet not processed: ${this.toPacket()}`);
@@ -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) 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
  }
@@ -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()}`)
@@ -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()}`)
@@ -17,7 +17,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
17
17
  import { Inbound } from "../Messages";
18
18
  import { sys, Body, ICircuitGroup, LightGroup, CircuitGroup } from "../../../Equipment";
19
19
  import { state, ICircuitGroupState, LightGroupState } from "../../../State";
20
- import { utils } from "../../../Constants";
20
+ import { Timestamp, utils } from "../../../Constants";
21
21
  import { logger } from "../../../../logger/Logger";
22
22
  export class ExternalMessage {
23
23
  public static processIntelliCenter(msg: Inbound): void {
@@ -83,7 +83,8 @@ export class ExternalMessage {
83
83
  if (isActive) {
84
84
  let chem = sys.chemControllers.getItemById(id, true);
85
85
  let schem = state.chemControllers.getItemById(id, true);
86
- chem.isVirtual = false;
86
+ // chem.isVirtual = false;
87
+ chem.master = 0;
87
88
  chem.ph.tank.capacity = chem.orp.tank.capacity = 6;
88
89
  chem.ph.tank.units = chem.orp.tank.units = '';
89
90
  schem.type = chem.type = 2;
@@ -106,7 +107,8 @@ export class ExternalMessage {
106
107
  let valve = sys.valves.getItemById(msg.extractPayloadByte(2) + 1);
107
108
  valve.circuit = msg.extractPayloadByte(3) + 1;
108
109
  valve.name = msg.extractPayloadString(4, 16);
109
- valve.isVirtual = false;
110
+ valve.master = 0;
111
+ // valve.isVirtual = false;
110
112
  msg.isProcessed = true;
111
113
  }
112
114
  public static processPool(msg: Inbound) {
@@ -272,31 +274,30 @@ export class ExternalMessage {
272
274
  private static processHeater(msg: Inbound) {
273
275
  // So a user is changing the heater info. Lets
274
276
  // hijack it and get it ourselves.
275
- let heater = sys.heaters.getItemById(msg.extractPayloadByte(2) + 1);
276
- heater.type = msg.extractPayloadByte(3);
277
- heater.body = msg.extractPayloadByte(4);
278
- heater.cooldownDelay = msg.extractPayloadByte(5);
279
- heater.startTempDelta = msg.extractPayloadByte(6);
280
- heater.stopTempDelta = msg.extractPayloadByte(7);
281
- heater.coolingEnabled = msg.extractPayloadByte(8) > 0;
282
- heater.differentialTemp = msg.extractPayloadByte(9);
283
- heater.address = msg.extractPayloadByte(10);
284
- heater.name = msg.extractPayloadString(11, 16);
285
- heater.efficiencyMode = msg.extractPayloadByte(27);
286
- heater.maxBoostTemp = msg.extractPayloadByte(28);
287
- heater.economyTime = msg.extractPayloadByte(29);
288
- if (heater.type === 0) {
289
- sys.heaters.removeItemById(heater.id);
290
- state.heaters.removeItemById(heater.id);
277
+ let isActive = msg.extractPayloadByte(3) !== 0;
278
+ let heaterId = msg.extractPayloadByte(2) + 1;
279
+ if (isActive) {
280
+ let heater = sys.heaters.getItemById(heaterId, true);
281
+ let hstate = state.heaters.getItemById(heater.id, true);
282
+
283
+ hstate.type = heater.type = msg.extractPayloadByte(3);
284
+ heater.body = msg.extractPayloadByte(4);
285
+ heater.cooldownDelay = msg.extractPayloadByte(5);
286
+ heater.startTempDelta = msg.extractPayloadByte(6);
287
+ heater.stopTempDelta = msg.extractPayloadByte(7);
288
+ heater.coolingEnabled = msg.extractPayloadByte(8) > 0;
289
+ heater.differentialTemp = msg.extractPayloadByte(9);
290
+ heater.address = msg.extractPayloadByte(10);
291
+ hstate.name = heater.name = msg.extractPayloadString(11, 16);
292
+ heater.efficiencyMode = msg.extractPayloadByte(27);
293
+ heater.maxBoostTemp = msg.extractPayloadByte(28);
294
+ heater.economyTime = msg.extractPayloadByte(29);
295
+ heater.master = 0;
291
296
  }
292
297
  else {
293
- let hstate = state.heaters.getItemById(heater.id, true);
294
- hstate.name = heater.name;
295
- //heater.isVirtual = hstate.isVirtual = false;
296
- hstate.name = heater.name;
297
- hstate.type = heater.type;
298
+ sys.heaters.removeItemById(heaterId);
299
+ state.heaters.removeItemById(heaterId);
298
300
  }
299
-
300
301
  sys.board.heaters.updateHeaterServices();
301
302
  // Check anyway to make sure we got it all.
302
303
  //setTimeout(() => sys.checkConfiguration(), 500);
@@ -738,6 +739,8 @@ export class ExternalMessage {
738
739
  sys.general.options.clockMode = (msg.extractPayloadByte(14) & 0x0001) == 1 ? 24 : 12;
739
740
  msg.isProcessed = true;
740
741
  break;
742
+ case 12: // This is byte 15 but we don't know what it is. Numbers witnessed include 51, 52, 89, 235.
743
+ break;
741
744
  case 14: // Clock source
742
745
  if ((msg.extractPayloadByte(17) & 0x0040) === 1)
743
746
  sys.general.options.clockSource = 'internet';
@@ -745,6 +748,8 @@ export class ExternalMessage {
745
748
  sys.general.options.clockSource = 'manual';
746
749
  msg.isProcessed = true;
747
750
  break;
751
+ case 15: // This is byte 18 but we don't know what it is. Numbers witnessed include 1, 2, 3, 5, 100.
752
+ break;
748
753
  case 18: // Body 1 Heat Setpoint
749
754
  body = sys.bodies.getItemById(1, false);
750
755
  body.heatSetpoint = msg.extractPayloadByte(21);
@@ -817,6 +822,19 @@ export class ExternalMessage {
817
822
  sys.general.options.manualHeat = msg.extractPayloadByte(40) !== 0;
818
823
  msg.isProcessed = true;
819
824
  break;
825
+ case 64: // Vacation mode
826
+ let yy = msg.extractPayloadByte(5) + 2000;
827
+ let mm = msg.extractPayloadByte(6);
828
+ let dd = msg.extractPayloadByte(7);
829
+ sys.general.options.vacation.startDate = new Date(yy, mm - 1, dd);
830
+ yy = msg.extractPayloadByte(8) + 2000;
831
+ mm = msg.extractPayloadByte(9);
832
+ dd = msg.extractPayloadByte(10);
833
+ sys.general.options.vacation.endDate = new Date(yy, mm - 1, dd);
834
+ sys.general.options.vacation.enabled = msg.extractPayloadByte(3) > 0;
835
+ sys.general.options.vacation.useTimeframe = msg.extractPayloadByte(4) > 0;
836
+ msg.isProcessed = true;
837
+ break;
820
838
  }
821
839
  }
822
840
  public static processTouchChlorinator(msg: Inbound) {
@@ -58,6 +58,7 @@ export class FeatureMessage {
58
58
  FeatureMessage.processFeatureNames(msg);
59
59
  break;
60
60
  case 22: // Not sure what this is.
61
+ msg.isProcessed = true;
61
62
  break;
62
63
  default:
63
64
  logger.debug(`Unprocessed Config Message ${msg.toPacket()}`)
@@ -70,6 +71,7 @@ export class FeatureMessage {
70
71
  var feature: Feature = sys.features.getItemById(featureId, false);
71
72
  feature.dontStop = msg.extractPayloadByte(i + 1) == 1;
72
73
  }
74
+ msg.isProcessed = true;
73
75
  }
74
76
  private static processFeatureType(msg: Inbound) {
75
77
  for (let i = 1; i < msg.payload.length - 1 && i <= sys.equipment.maxFeatures; i++) {
@@ -88,6 +90,7 @@ export class FeatureMessage {
88
90
  state.features.removeItemById(featureId);
89
91
  }
90
92
  }
93
+ msg.isProcessed = true;
91
94
  }
92
95
  private static processFreezeProtect(msg: Inbound) {
93
96
  for (let i = 1; i < msg.payload.length - 1 && i <= sys.equipment.maxFeatures; i++) {
@@ -95,6 +98,7 @@ export class FeatureMessage {
95
98
  var feature: Feature = sys.features.getItemById(featureId);
96
99
  feature.freeze = msg.extractPayloadByte(i + 1) > 0;
97
100
  }
101
+ msg.isProcessed = true;
98
102
  }
99
103
  private static processFeatureNames(msg: Inbound) {
100
104
  var featureId = ((msg.extractPayloadByte(1) - 6) * 2) + sys.board.equipmentIds.features.start;
@@ -109,6 +113,7 @@ export class FeatureMessage {
109
113
  if (feature.isActive) state.features.getItemById(feature.id).name = feature.name;
110
114
  }
111
115
  state.emitEquipmentChanges();
116
+ msg.isProcessed = true;
112
117
  }
113
118
  private static processEggTimerHours(msg: Inbound) {
114
119
  for (let i = 1; i < msg.payload.length - 1 && i <= sys.equipment.maxFeatures; i++) {
@@ -116,6 +121,7 @@ export class FeatureMessage {
116
121
  let feature: Feature = sys.features.getItemById(featureId);
117
122
  feature.eggTimer = (msg.extractPayloadByte(i + 1) * 60) + ((feature.eggTimer || 0) % 60);
118
123
  }
124
+ msg.isProcessed = true;
119
125
  }
120
126
  private static processEggTimerMinutes(msg: Inbound) {
121
127
  for (let i = 1; i < msg.payload.length - 1 && i <= sys.equipment.maxFeatures; i++) {
@@ -123,6 +129,7 @@ export class FeatureMessage {
123
129
  var feature: Feature = sys.features.getItemById(featureId);
124
130
  feature.eggTimer = (Math.floor(feature.eggTimer / 60) * 60) + msg.extractPayloadByte(i + 1);
125
131
  }
132
+ msg.isProcessed = true;
126
133
  }
127
134
  private static processShowInFeatures(msg: Inbound) {
128
135
  for (let i = 1; i < msg.payload.length - 1 && i <= sys.equipment.maxFeatures; i++) {
@@ -132,6 +139,6 @@ export class FeatureMessage {
132
139
  if (feature.isActive) state.features.getItemById(featureId, feature.isActive).showInFeatures = feature.showInFeatures;
133
140
  }
134
141
  state.emitEquipmentChanges();
142
+ msg.isProcessed = true;
135
143
  }
136
-
137
144
  }
@@ -24,31 +24,39 @@ export class GeneralMessage {
24
24
  sys.general.alias = msg.extractPayloadString(2, 16);
25
25
  sys.general.owner.name = msg.extractPayloadString(18, 16);
26
26
  sys.general.location.zip = msg.extractPayloadString(34, 6);
27
+ msg.isProcessed = true;
27
28
  break;
28
29
  case 1:
29
30
  sys.general.owner.phone = msg.extractPayloadString(2, 20);
30
31
  sys.general.owner.phone2 = msg.extractPayloadString(21, 15);
31
32
  sys.general.location.latitude = ((msg.extractPayloadByte(35) * 256) + msg.extractPayloadByte(34)) / 100;
33
+ msg.isProcessed = true;
32
34
  break;
33
35
  case 2:
34
36
  sys.general.location.address = msg.extractPayloadString(2, 32);
35
37
  sys.general.location.longitude = -(((msg.extractPayloadByte(35) * 256) + msg.extractPayloadByte(34)) / 100);
38
+ msg.isProcessed = true;
36
39
  break;
37
40
  case 3:
38
41
  sys.general.owner.email = msg.extractPayloadString(2, 32);
39
42
  sys.general.location.timeZone = msg.extractPayloadByte(34);
43
+ msg.isProcessed = true;
40
44
  break;
41
45
  case 4:
42
46
  sys.general.owner.email2 = msg.extractPayloadString(2, 32);
47
+ msg.isProcessed = true;
43
48
  break;
44
49
  case 5:
45
50
  sys.general.location.country = msg.extractPayloadString(2, 32);
51
+ msg.isProcessed = true;
46
52
  break;
47
53
  case 6:
48
54
  sys.general.location.city = msg.extractPayloadString(2, 32);
55
+ msg.isProcessed = true;
49
56
  break;
50
57
  case 7:
51
58
  sys.general.location.state = msg.extractPayloadString(2, 32);
59
+ msg.isProcessed = true;
52
60
  break;
53
61
  default:
54
62
  logger.debug(`Unprocessed Config Message ${msg.toPacket()}`)