nodejs-poolcontroller 7.7.0 → 8.0.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 (82) hide show
  1. package/.eslintrc.json +26 -35
  2. package/Changelog +22 -0
  3. package/README.md +7 -3
  4. package/anslq25/MessagesMock.ts +218 -0
  5. package/anslq25/boards/MockBoardFactory.ts +50 -0
  6. package/anslq25/boards/MockEasyTouchBoard.ts +696 -0
  7. package/anslq25/boards/MockSystemBoard.ts +217 -0
  8. package/anslq25/chemistry/MockChlorinator.ts +75 -0
  9. package/anslq25/pumps/MockPump.ts +84 -0
  10. package/app.ts +10 -14
  11. package/config/Config.ts +13 -9
  12. package/config/VersionCheck.ts +6 -2
  13. package/controller/Constants.ts +58 -25
  14. package/controller/Equipment.ts +225 -41
  15. package/controller/Errors.ts +2 -1
  16. package/controller/Lockouts.ts +34 -2
  17. package/controller/State.ts +491 -48
  18. package/controller/boards/AquaLinkBoard.ts +6 -3
  19. package/controller/boards/BoardFactory.ts +5 -1
  20. package/controller/boards/EasyTouchBoard.ts +1971 -1751
  21. package/controller/boards/IntelliCenterBoard.ts +1311 -1688
  22. package/controller/boards/IntelliComBoard.ts +7 -1
  23. package/controller/boards/IntelliTouchBoard.ts +153 -42
  24. package/controller/boards/NixieBoard.ts +209 -66
  25. package/controller/boards/SunTouchBoard.ts +393 -0
  26. package/controller/boards/SystemBoard.ts +1862 -1543
  27. package/controller/comms/Comms.ts +539 -138
  28. package/controller/comms/ScreenLogic.ts +1663 -0
  29. package/controller/comms/messages/Messages.ts +242 -60
  30. package/controller/comms/messages/config/ChlorinatorMessage.ts +4 -3
  31. package/controller/comms/messages/config/CircuitGroupMessage.ts +5 -2
  32. package/controller/comms/messages/config/CircuitMessage.ts +81 -13
  33. package/controller/comms/messages/config/ConfigMessage.ts +3 -1
  34. package/controller/comms/messages/config/CoverMessage.ts +2 -1
  35. package/controller/comms/messages/config/CustomNameMessage.ts +2 -1
  36. package/controller/comms/messages/config/EquipmentMessage.ts +5 -1
  37. package/controller/comms/messages/config/ExternalMessage.ts +33 -3
  38. package/controller/comms/messages/config/FeatureMessage.ts +2 -1
  39. package/controller/comms/messages/config/GeneralMessage.ts +2 -1
  40. package/controller/comms/messages/config/HeaterMessage.ts +3 -1
  41. package/controller/comms/messages/config/IntellichemMessage.ts +2 -1
  42. package/controller/comms/messages/config/OptionsMessage.ts +12 -6
  43. package/controller/comms/messages/config/PumpMessage.ts +9 -12
  44. package/controller/comms/messages/config/RemoteMessage.ts +80 -13
  45. package/controller/comms/messages/config/ScheduleMessage.ts +43 -3
  46. package/controller/comms/messages/config/SecurityMessage.ts +2 -1
  47. package/controller/comms/messages/config/ValveMessage.ts +43 -26
  48. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +8 -7
  49. package/controller/comms/messages/status/EquipmentStateMessage.ts +93 -20
  50. package/controller/comms/messages/status/HeaterStateMessage.ts +24 -5
  51. package/controller/comms/messages/status/IntelliChemStateMessage.ts +7 -4
  52. package/controller/comms/messages/status/IntelliValveStateMessage.ts +2 -1
  53. package/controller/comms/messages/status/PumpStateMessage.ts +72 -4
  54. package/controller/comms/messages/status/VersionMessage.ts +2 -1
  55. package/controller/nixie/Nixie.ts +15 -4
  56. package/controller/nixie/NixieEquipment.ts +1 -0
  57. package/controller/nixie/chemistry/ChemController.ts +300 -129
  58. package/controller/nixie/chemistry/ChemDoser.ts +806 -0
  59. package/controller/nixie/chemistry/Chlorinator.ts +133 -129
  60. package/controller/nixie/circuits/Circuit.ts +171 -30
  61. package/controller/nixie/heaters/Heater.ts +337 -173
  62. package/controller/nixie/pumps/Pump.ts +264 -236
  63. package/controller/nixie/schedules/Schedule.ts +9 -3
  64. package/defaultConfig.json +46 -5
  65. package/logger/Logger.ts +38 -9
  66. package/package.json +13 -9
  67. package/web/Server.ts +235 -122
  68. package/web/bindings/aqualinkD.json +114 -59
  69. package/web/bindings/homeassistant.json +437 -0
  70. package/web/bindings/influxDB.json +15 -0
  71. package/web/bindings/mqtt.json +28 -9
  72. package/web/bindings/mqttAlt.json +15 -0
  73. package/web/interfaces/baseInterface.ts +58 -7
  74. package/web/interfaces/httpInterface.ts +5 -2
  75. package/web/interfaces/influxInterface.ts +9 -2
  76. package/web/interfaces/mqttInterface.ts +234 -74
  77. package/web/interfaces/ruleInterface.ts +87 -0
  78. package/web/services/config/Config.ts +140 -33
  79. package/web/services/config/ConfigSocket.ts +2 -1
  80. package/web/services/state/State.ts +144 -3
  81. package/web/services/state/StateSocket.ts +65 -14
  82. package/web/services/utilities/Utilities.ts +189 -1
@@ -1,5 +1,6 @@
1
1
  /* nodejs-poolController. An application to control pool equipment.
2
- Copyright (C) 2016, 2017, 2018, 2019, 2020. Russell Goldin, tagyoureit. russ.goldin@gmail.com
2
+ Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
3
+ Russell Goldin, tagyoureit. russ.goldin@gmail.com
3
4
 
4
5
  This program is free software: you can redistribute it and/or modify
5
6
  it under the terms of the GNU Affero General Public License as
@@ -42,8 +43,10 @@ import { TouchScheduleCommands } from "controller/boards/EasyTouchBoard";
42
43
  import { IntelliValveStateMessage } from "./status/IntelliValveStateMessage";
43
44
  import { IntelliChemStateMessage } from "./status/IntelliChemStateMessage";
44
45
  import { OutboundMessageError } from "../../Errors";
45
- import { prototype } from "events";
46
+ import { conn } from "../Comms"
46
47
  import extend = require("extend");
48
+ import { MessagesMock } from "../../../anslq25/MessagesMock";
49
+
47
50
  export enum Direction {
48
51
  In = 'in',
49
52
  Out = 'out'
@@ -70,7 +73,10 @@ export class Message {
70
73
  private _id: number = -1;
71
74
  // Fields
72
75
  private static _messageId: number = 0;
73
- public static get nextMessageId(): number { return this._messageId < 80000 ? ++this._messageId : this._messageId = 0; }
76
+ public static get nextMessageId(): number {
77
+ let i = this._messageId < 80000 ? ++this._messageId : this._messageId = 0;
78
+ logger.debug(`Assigning message id ${i}`)
79
+ return i; }
74
80
  public portId = 0; // This will be the target or source port for the message. If this is from or to an Aux RS485 port the value will be > 0.
75
81
  public timestamp: Date = new Date();
76
82
  public direction: Direction = Direction.In;
@@ -85,18 +91,19 @@ export class Message {
85
91
  public set id(val: number) { this._id = val; }
86
92
  public isValid: boolean = true;
87
93
  public scope: string;
94
+ public isClone: boolean;
88
95
  // Properties
89
96
  public get isComplete(): boolean { return this._complete; }
90
97
  public get sub(): number { return this.header.length > 1 ? this.header[1] : -1; }
91
98
  public get dest(): number {
92
99
  if (this.header.length > 2) {
93
100
  if (this.protocol === Protocol.Chlorinator || this.protocol === Protocol.AquaLink) {
94
- return this.header.length > 2 ? (this.header[2] >= 80 ? this.header[2] - 79 : 0) : -1;
101
+ return this.header.length > 2 ? (this.header[2] >= 80 ? this.header[2] : 0) : -1;
95
102
  }
96
103
  else if (this.protocol === Protocol.Hayward) {
97
104
  // src act dest
98
105
  //0x10, 0x02, 0x00, 0x0C, 0x00, 0x00, 0x2D, 0x02, 0x36, 0x00, 0x83, 0x10, 0x03 -- Response from pump
99
- return this.header.length > 4 ? this.header[4] : -1;
106
+ return this.header.length > 4 ? this.header[2] : -1;
100
107
  }
101
108
  else return this.header.length > 2 ? this.header[2] : -1;
102
109
  }
@@ -104,7 +111,7 @@ export class Message {
104
111
  }
105
112
  public get source(): number {
106
113
  if (this.protocol === Protocol.Chlorinator) {
107
- return this.header.length > 2 ? (this.header[2] >= 80 ? 0 : 1) : -1;
114
+ return this.header.length > 2 ? (this.header[2] >= 80 ? 0 : this.header[2]) : -1;
108
115
  // have to assume incoming packets with header[2] >= 80 (sent to a chlorinator)
109
116
  // are from controller (0);
110
117
  // likewise, if the destination is 0 (controller) we
@@ -119,19 +126,20 @@ export class Message {
119
126
  // src act dest
120
127
  //0x10, 0x02, 0x00, 0x0C, 0x00, 0x00, 0x2D, 0x02, 0x36, 0x00, 0x83, 0x10, 0x03 -- Response from pump
121
128
  //0x10, 0x02, 0x0C, 0x01, 0x02, 0x2D, 0x00, 0x4E, 0x10, 0x03 -- Command to AUX2 Pump
122
- return this.header.length > 2 ? this.header[2] : -1;
129
+ return this.header.length > 4 ? this.header[4] : -1;
123
130
  }
124
131
  if (this.header.length > 3) return this.header[3];
125
132
  else return -1;
126
133
  }
127
134
  public get action(): number {
128
135
  // The action byte is actually the 4th byte in the header the destination address is the 5th byte.
129
- if (this.protocol === Protocol.Chlorinator || this.protocol === Protocol.AquaLink) return this.header.length > 3 ? this.header[3] : -1;
136
+ if (this.protocol === Protocol.Chlorinator ||
137
+ this.protocol === Protocol.AquaLink) return this.header.length > 3 ? this.header[3] : -1;
130
138
  else if (this.protocol === Protocol.Hayward) {
131
139
  // src act dest
132
140
  //0x10, 0x02, 0x00, 0x0C, 0x00, 0x00, 0x2D, 0x02, 0x36, 0x00, 0x83, 0x10, 0x03 -- Response from pump
133
141
  //0x10, 0x02, 0x0C, 0x01, 0x02, 0x2D, 0x00, 0x4E, 0x10, 0x03 -- Command to AUX2 Pump
134
- this.header.length > 3 ? this.header[3] : -1;
142
+ return this.header.length > 3 ? this.header[3] || this.header[2] : -1;
135
143
  }
136
144
  if (this.header.length > 4) return this.header[4];
137
145
  else return -1;
@@ -164,7 +172,59 @@ export class Message {
164
172
  return pkt;
165
173
  }
166
174
  public toLog(): string {
167
- return `{"port":${this.portId},"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)}"}`;
175
+ return `{"port":${this.portId},"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)}"}`;
176
+ }
177
+ public static convertOutboundToInbound(out: Outbound): Inbound {
178
+ let inbound = new Inbound();
179
+ inbound.portId = out.portId;
180
+ // inbound.id = Message.nextMessageId;
181
+ inbound.protocol = out.protocol;
182
+ inbound.scope = out.scope;
183
+ inbound.preamble = out.preamble;
184
+ inbound.padding = out.padding;
185
+ inbound.header = out.header;
186
+ inbound.payload = [...out.payload];
187
+ inbound.term = out.term;
188
+ inbound.portId = out.portId;
189
+ return inbound;
190
+ }
191
+ public static convertInboundToOutbound(inbound: Inbound): Outbound {
192
+ let out = new Outbound(
193
+ inbound.protocol,
194
+ inbound.source,
195
+ inbound.dest,
196
+ inbound.action,
197
+ inbound.payload,
198
+ );
199
+ out.scope = inbound.scope;
200
+ out.preamble = inbound.preamble;
201
+ out.padding = inbound.padding;
202
+ out.header = inbound.header;
203
+ out.term = inbound.term;
204
+ out.portId = inbound.portId;
205
+ return out;
206
+ }
207
+ public clone(): Inbound | Outbound {
208
+ let msg;
209
+ if (this instanceof Inbound) {
210
+ msg = new Inbound();
211
+ msg.id = Message.nextMessageId;
212
+ msg.scope = this.scope;
213
+ msg.preamble = this.preamble;
214
+ msg.padding = this.padding;
215
+ msg.payload = [...this.payload];
216
+ msg.header = this.header;
217
+ msg.term = this.term;
218
+ msg.portId = this.portId;
219
+ }
220
+ else {
221
+ msg = new Outbound(
222
+ this.protocol, this.source, this.dest, this.action, [...this.payload],
223
+ );
224
+ msg.portId = this.portId;
225
+ msg.scope = this.scope;
226
+ }
227
+ return msg;
168
228
  }
169
229
  }
170
230
  export class Inbound extends Message {
@@ -194,7 +254,7 @@ export class Inbound extends Message {
194
254
  }
195
255
  public toLog() {
196
256
  if (this.responseFor.length > 0)
197
- return `{"port":${this.portId || 0},"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) }"}`;
257
+ return `{"port":${this.portId || 0},"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)}"}`;
198
258
  return `{"port":${this.portId || 0},"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)}"}`;
199
259
  }
200
260
  private testChlorHeader(bytes: number[], ndx: number): boolean {
@@ -202,22 +262,74 @@ export class Inbound extends Message {
202
262
  // prev been detected as chlor packets;
203
263
  // valid chlor packets should have 16,2,0 or 16,2,[80-96];
204
264
  // this should reduce the number of false chlor packets
205
- return (ndx + 3 < bytes.length && bytes[ndx] === 16 && bytes[ndx + 1] === 2 && (bytes[ndx + 2] === 0 || (bytes[ndx + 2] >= 80 && bytes[ndx + 2] <= 96)))
265
+ // For any of these 16,2 type headers we need at least 5 bytes to determine the routing.
266
+ //63,15,16,2,29,9,36,0,0,0,0,0,16,0,32,0,0,2,0,75,75,32,241,80,85,24,241,16,16,48,245,69,45,100,186,16,2,80,17,0,115,16,3
267
+ if (bytes.length > ndx + 4) {
268
+ if (bytes[ndx] === 16 && bytes[ndx + 1] === 2) {
269
+ let dst = bytes[ndx + 2];
270
+ let act = bytes[ndx + 3];
271
+ // For now the dst byte will always be 0 or 80.
272
+ if (![0, 16, 80, 81, 82, 83].includes(dst)) {
273
+ //logger.info(`Sensed chlorinator header but the dst byte is ${dst}`);
274
+ return false;
275
+ }
276
+ else if (dst === 0 && [1, 18, 3].includes(act))
277
+ return true;
278
+ else if (![0, 17, 19, 20, 21, 22].includes(act)) {
279
+ //logger.info(`Sensed out chlorinator header but the dst byte is ${dst} ${act} ${JSON.stringify(bytes)}`);
280
+ return false;
281
+ }
282
+ return true;
283
+ }
284
+ }
285
+ return false;
206
286
  }
207
287
  private testAquaLinkHeader(bytes: number[], ndx: number): boolean {
208
- return (sys.controllerType === 'aqualink' && ndx + 3 < bytes.length && bytes[ndx] === 16 && bytes[ndx + 1] === 2);
288
+ if (bytes.length > ndx + 4 && sys.controllerType === 'aqualink') {
289
+ if (bytes[ndx] === 16 && bytes[ndx + 1] === 2) {
290
+ return true;
291
+ }
292
+ }
293
+ return false;
209
294
  }
210
295
  private testHaywardHeader(bytes: number[], ndx: number): boolean {
211
296
  //0x10, 0x02, 0x0C, 0x01, 0x00, 0x2D, 0x00, 0x4C, 0x10, 0x03 -- Command to pump
297
+ //[16,2,12,1,0]
212
298
  //0x10, 0x02, 0x0C, 0x01, 0x00, 0x2D, 0x00, 0x4C, 0x10, 0x03 -- Command to Filter Pump
299
+ //[16,2,12,1,0]
213
300
  //0x10, 0x02, 0x0C, 0x01, 0x02, 0x2D, 0x00, 0x4E, 0x10, 0x03 -- Command to AUX2 Pump
301
+ //[16,2,12,1,2]
214
302
  // src act dest
215
303
  //0x10, 0x02, 0x00, 0x0C, 0x00, 0x00, 0x2D, 0x02, 0x36, 0x00, 0x83, 0x10, 0x03 -- Response from pump
216
- return (sys.controllerType === 'nixie' && ndx + 4 < bytes.length && bytes[ndx] === 16 && bytes[ndx + 1] === 2 && (bytes[ndx + 2] === 12 && bytes[ndx + 3] === 12));
304
+ //[16,2,0,12,0] --> Response
305
+ //[16,2,0,12,0]
306
+ if (bytes.length > ndx + 4) {
307
+ if (sys.controllerType === 'aqualink') return false;
308
+ if (bytes[ndx] === 16 && bytes[ndx + 1] === 2) {
309
+ let dst = bytes[ndx + 3];
310
+ let src = bytes[ndx + 2];
311
+ if (dst === 12 || src === 12) return true;
312
+ }
313
+ }
314
+ return false;
315
+ }
316
+ private testBroadcastHeader(bytes: number[], ndx: number): boolean {
317
+ // We are looking for [255,0,255,165]
318
+ if (bytes.length > ndx + 3) {
319
+ if (bytes[ndx] === 255 && bytes[ndx + 1] === 0 && bytes[ndx + 2] === 255 && bytes[ndx + 3] === 165) return true;
320
+ return false;
321
+ }
322
+ //return ndx < bytes.length - 3 && bytes[ndx] === 255 && bytes[ndx + 1] === 0 && bytes[ndx + 2] === 255 && bytes[ndx + 3] === 165;
323
+ return false;
324
+ }
325
+ private testUnidentifiedHeader(bytes: number[], ndx: number): boolean {
326
+ if (bytes.length > ndx + 3) {
327
+ if (bytes[ndx] === 255 && bytes[ndx + 1] === 0 && bytes[ndx + 2] === 255 && bytes[ndx + 3] !== 165) return true;
328
+ return false;
329
+ }
330
+ //return ndx < bytes.length - 3 && bytes[ndx] === 255 && bytes[ndx + 1] === 0 && bytes[ndx + 2] === 255 && bytes[ndx + 3] !== 165;
331
+ return false;
217
332
  }
218
-
219
- 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; }
220
- 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; }
221
333
  private testChlorTerm(bytes: number[], ndx: number): boolean { return ndx + 2 < bytes.length && bytes[ndx + 1] === 16 && bytes[ndx + 2] === 3; }
222
334
  private testAquaLinkTerm(bytes: number[], ndx: number): boolean { return ndx + 2 < bytes.length && bytes[ndx + 1] === 16 && bytes[ndx + 2] === 3; }
223
335
  private testHaywardTerm(bytes: number[], ndx: number): boolean { return ndx + 3 < bytes.length && bytes[ndx + 2] === 16 && bytes[ndx + 3] === 3; }
@@ -255,10 +367,11 @@ export class Inbound extends Message {
255
367
  //return this.padding.length + this.preamble.length;
256
368
  }
257
369
  public readPacket(bytes: number[]): number {
370
+ //logger.info(`BYTES: ${JSON.stringify(bytes)}`);
258
371
  var ndx = this.readHeader(bytes, 0);
259
372
  if (this.isValid && this.header.length > 0) ndx = this.readPayload(bytes, ndx);
260
373
  if (this.isValid && this.header.length > 0) ndx = this.readChecksum(bytes, ndx);
261
- //if (this.isComplete && !this.isValid) return this.rewind(bytes, ndx);
374
+ if (this.isComplete && !this.isValid) return this.rewind(bytes, ndx);
262
375
  return ndx;
263
376
  }
264
377
  public mergeBytes(bytes) {
@@ -271,30 +384,40 @@ export class Inbound extends Message {
271
384
  }
272
385
  public readHeader(bytes: number[], ndx: number): number {
273
386
  // start over to include the padding bytes.
387
+ //if (this.protocol !== Protocol.Unknown) {
388
+ // logger.warn(`${this.protocol} resulted in an empty message header ${JSON.stringify(this.header)}`);
389
+ //}
274
390
  let ndxStart = ndx;
275
- while (ndx < bytes.length) {
276
- if (this.testChlorHeader(bytes, ndx)) {
277
- this.protocol = Protocol.Chlorinator;
278
- break;
279
- }
280
- if (this.testAquaLinkHeader(bytes, ndx)) {
281
- this.protocol = Protocol.AquaLink;
282
- break;
283
- }
284
- if (this.testHaywardHeader(bytes, ndx)) {
285
- this.protocol = Protocol.Hayward;
286
- break;
287
- }
288
- if (this.testBroadcastHeader(bytes, ndx)) {
289
- this.protocol = Protocol.Broadcast;
290
- break;
291
- }
292
- else if (this.testUnidentifiedHeader(bytes, ndx)) {
293
- this.protocol = Protocol.Unidentified;
294
- break;
391
+ // RKS: 05-30-22 -- OMG we have not been dealing with short headers. As a result it was restarting
392
+ // the header process even after it had identified it.
393
+ if (this.protocol === Protocol.Unknown) {
394
+ while (ndx < bytes.length) {
395
+ if (this.testBroadcastHeader(bytes, ndx)) {
396
+ this.protocol = Protocol.Broadcast;
397
+ break;
398
+ }
399
+ if (this.testUnidentifiedHeader(bytes, ndx)) {
400
+ this.protocol = Protocol.Unidentified;
401
+ break;
402
+ }
403
+ if (this.testChlorHeader(bytes, ndx)) {
404
+ this.protocol = Protocol.Chlorinator;
405
+ break;
406
+ }
407
+ if (this.testAquaLinkHeader(bytes, ndx)) {
408
+ this.protocol = Protocol.AquaLink;
409
+ break;
410
+ }
411
+ if (this.testHaywardHeader(bytes, ndx)) {
412
+ this.protocol = Protocol.Hayward;
413
+ break;
414
+ }
415
+ this.padding.push(bytes[ndx++]);
295
416
  }
296
- this.padding.push(bytes[ndx++]);
297
417
  }
418
+ // When the code above finds a protocol, ndx will be at the start of that
419
+ // header. If it is not identified then it will rewind to the initial
420
+ // start position until we get more bytes. This is the default case below.
298
421
  let ndxHeader = ndx;
299
422
  switch (this.protocol) {
300
423
  case Protocol.Pump:
@@ -309,11 +432,11 @@ export class Inbound extends Message {
309
432
  // We actually don't have a complete header yet so just return.
310
433
  // we will pick it up next go around.
311
434
  // logger.debug(`We have an incoming message but the serial port hasn't given a complete header. [${this.padding}][${this.preamble}][${this.header}]`);
435
+ //logger.info(`We don't have a complete header ${JSON.stringify(this.header)}`);
312
436
  this.preamble = [];
313
437
  this.header = [];
314
438
  return ndxHeader;
315
439
  }
316
-
317
440
  if (this.source >= 96 && this.source <= 111) this.protocol = Protocol.Pump;
318
441
  else if (this.dest >= 96 && this.dest <= 111) this.protocol = Protocol.Pump;
319
442
  else if (this.source >= 112 && this.source <= 127) this.protocol = Protocol.Heater;
@@ -323,7 +446,7 @@ export class Inbound extends Message {
323
446
  else if (this.source == 12 || this.dest == 12) this.protocol = Protocol.IntelliValve;
324
447
  if (this.datalen > 75) {
325
448
  //this.isValid = false;
326
- logger.debug(`Broadcast length ${this.datalen} exceeded 75bytes for ${this.protocol} message. Message rewound ${this.header}`);
449
+ logger.debug(`Broadcast length ${this.datalen} exceeded 75 bytes for ${this.protocol} message. Message rewound ${this.header}`);
327
450
  this.padding.push(...this.preamble);
328
451
  this.padding.push(...this.header.slice(0, 1));
329
452
  this.preamble = [];
@@ -352,11 +475,11 @@ export class Inbound extends Message {
352
475
  }
353
476
  break;
354
477
  case Protocol.Hayward:
355
- ndx = this.pushBytes(this.header, bytes, ndx, 4);
478
+ ndx = this.pushBytes(this.header, bytes, ndx, 5);
356
479
  if (this.header.length < 4) {
357
480
  // We actually don't have a complete header yet so just return.
358
481
  // we will pick it up next go around.
359
- logger.debug(`We have an incoming AquaLink message but the serial port hasn't given a complete header. [${this.padding}][${this.preamble}][${this.header}]`);
482
+ logger.debug(`We have an incoming Hayward message but the serial port hasn't given a complete header. [${this.padding}][${this.preamble}][${this.header}]`);
360
483
  this.preamble = [];
361
484
  this.header = [];
362
485
  return ndxHeader;
@@ -401,7 +524,11 @@ export class Inbound extends Message {
401
524
  case Protocol.IntelliValve:
402
525
  case Protocol.Heater:
403
526
  case Protocol.Unidentified:
404
- if (this.datalen - this.payload.length <= 0) return ndx; // We don't need any more payload.
527
+ if (this.datalen - this.payload.length <= 0) {
528
+ let buff = bytes.slice(ndx - 1);
529
+ //logger.info(`We don't need any more payload ${this.datalen - this.payload.length} ${ndx} ${JSON.stringify(buff)};`);
530
+ return ndx; // We don't need any more payload.
531
+ }
405
532
  ndx = this.pushBytes(this.payload, bytes, ndx, this.datalen - this.payload.length);
406
533
  break;
407
534
  case Protocol.Chlorinator:
@@ -586,7 +713,16 @@ export class Inbound extends Message {
586
713
  PumpMessage.process(this);
587
714
  break;
588
715
  case 30:
589
- if (sys.controllerType !== ControllerType.Unknown) OptionsMessage.process(this);
716
+ switch (sys.controllerType) {
717
+ case ControllerType.Unknown:
718
+ break;
719
+ case ControllerType.SunTouch:
720
+ ScheduleMessage.processSunTouch(this);
721
+ break;
722
+ default:
723
+ OptionsMessage.process(this);
724
+ break;
725
+ }
590
726
  break;
591
727
  case 22:
592
728
  case 32:
@@ -629,6 +765,9 @@ export class Inbound extends Message {
629
765
  case 147:
630
766
  IntellichemMessage.process(this);
631
767
  break;
768
+ case 136:
769
+ ExternalMessage.processTouchSetHeatMode(this);
770
+ break;
632
771
  default:
633
772
  if (this.action === 109 && this.payload[1] === 3) break;
634
773
  if (this.source === 17 && this.payload[0] === 109) break;
@@ -639,6 +778,13 @@ export class Inbound extends Message {
639
778
  }
640
779
  }
641
780
  public process() {
781
+ let port = conn.findPortById(this.portId);
782
+ if (this.portId === sys.anslq25.portId) {
783
+ return MessagesMock.process(this);
784
+ }
785
+ if (port.mock && port.hasAssignedEquipment()){
786
+ return MessagesMock.process(this);
787
+ }
642
788
  switch (this.protocol) {
643
789
  case Protocol.Broadcast:
644
790
  this.processBroadcast();
@@ -674,18 +820,38 @@ class OutboundCommon extends Message {
674
820
  public set sub(val: number) { if (this.protocol !== Protocol.Chlorinator && this.protocol !== Protocol.AquaLink) this.header[1] = val; }
675
821
  public get sub() { return super.sub; }
676
822
  public set dest(val: number) {
677
- if (this.protocol === Protocol.Chlorinator) this.header[2] = val + 79;
823
+ if (this.protocol === Protocol.Chlorinator) this.header[2] = val;
678
824
  else if (this.protocol === Protocol.Hayward) this.header[4] = val;
679
825
  else this.header[2] = val;
680
826
  }
681
827
  public get dest() { return super.dest; }
682
828
  public set source(val: number) {
683
- if (this.protocol === Protocol.Hayward) this.header[2] = val;
684
- else if (this.protocol !== Protocol.Chlorinator) this.header[3] = val;
829
+ switch (this.protocol) {
830
+ case Protocol.Chlorinator:
831
+ break;
832
+ case Protocol.Hayward:
833
+ this.header[3] = val;
834
+ break;
835
+ default:
836
+ this.header[3] = val;
837
+ break;
838
+ }
839
+ //if (this.protocol === Protocol.Hayward) this.header[2] = val;
840
+ //else if (this.protocol !== Protocol.Chlorinator) this.header[3] = val;
685
841
  }
686
842
  public get source() { return super.source; }
687
843
  public set action(val: number) {
688
- (this.protocol !== Protocol.Chlorinator && this.protocol !== Protocol.Hayward) ? this.header[4] = val : this.header[3] = val;
844
+ switch (this.protocol) {
845
+ case Protocol.Chlorinator:
846
+ this.header[3] = val;
847
+ break;
848
+ case Protocol.Hayward:
849
+ this.header[2] = val;
850
+ break;
851
+ default:
852
+ this.header[4] = val;
853
+ break;
854
+ }
689
855
  }
690
856
  public get action() { return super.action; }
691
857
  public set datalen(val: number) { if (this.protocol !== Protocol.Chlorinator && this.protocol !== Protocol.Hayward) this.header[5] = val; }
@@ -705,16 +871,13 @@ class OutboundCommon extends Message {
705
871
  case Protocol.Unidentified:
706
872
  case Protocol.IntelliChem:
707
873
  case Protocol.Heater:
874
+ case Protocol.Hayward:
708
875
  this.chkHi = Math.floor(sum / 256);
709
876
  this.chkLo = (sum - (super.chkHi * 256));
710
877
  break;
711
878
  case Protocol.AquaLink:
712
879
  case Protocol.Chlorinator:
713
- this.term[0] = sum;
714
- break;
715
- case Protocol.Hayward:
716
- this.chkHi = Math.floor(sum / 256);
717
- this.chkLo = (sum - (super.chkHi * 256));
880
+ this.term[0] = sum % 256;
718
881
  break;
719
882
  }
720
883
  }
@@ -790,7 +953,9 @@ export class Outbound extends OutboundCommon {
790
953
  public static createMessage(action: number, payload: number[], retries?: number, response?: Response | boolean): Outbound {
791
954
  return new Outbound(Protocol.Broadcast, sys.board.commandSourceAddress || Message.pluginAddress, sys.board.commandDestAddress || 16, action, payload, retries, response);
792
955
  }
793
-
956
+ public async sendAsync() {
957
+ return conn.queueSendMessageAsync(this);
958
+ }
794
959
  // Fields
795
960
  public retries: number = 0;
796
961
  public tries: number = 0;
@@ -883,11 +1048,11 @@ export class Outbound extends OutboundCommon {
883
1048
  if (typeof s === 'undefined') s = def;
884
1049
  let l = typeof len === 'undefined' ? s.length : len;
885
1050
  let buf = [];
886
- for (let i = 0; i < l; l++) {
887
- if (i < l) buf.push(s.charCodeAt(i));
888
- else buf.push(i);
1051
+ for (let i = 0; i < l; i++) {
1052
+ if (i < s.length) buf.push(s.charCodeAt(i));
1053
+ else buf.push(0);
889
1054
  }
890
- this.payload.splice(start, 0, ...buf);
1055
+ this.payload.splice(start, l, ...buf);
891
1056
  return this;
892
1057
  }
893
1058
  public toPacket(): number[] {
@@ -900,6 +1065,20 @@ export class Outbound extends OutboundCommon {
900
1065
  pkt.push.apply(pkt, this.term);
901
1066
  return pkt;
902
1067
  }
1068
+ public processMock(){
1069
+ // When the port is a mock port, we are no longer sending an
1070
+ // outbound message but converting it to an inbound and
1071
+ // skipping the actual send/receive part of the comms.
1072
+ let inbound = Message.convertOutboundToInbound(this);
1073
+ let port = conn.findPortById(this.portId);
1074
+ if (port.hasAssignedEquipment() || this.portId === sys.anslq25.portId){
1075
+ MessagesMock.process(inbound);
1076
+ }
1077
+ else {
1078
+ inbound.process();
1079
+ }
1080
+
1081
+ }
903
1082
  }
904
1083
  export class Ack extends Outbound {
905
1084
  constructor(byte: number) {
@@ -951,7 +1130,10 @@ export class Response extends OutboundCommon {
951
1130
  try {
952
1131
  if (typeof this.responseBool === 'boolean' && this.responseBool) bresp = this.evalResponse(msgIn, msgOut);
953
1132
  else return bresp;
954
- if (bresp === true && typeof msgOut !== 'undefined') msgIn.responseFor.push(msgOut.id);
1133
+ if (bresp === true && typeof msgOut !== 'undefined') {
1134
+ msgIn.responseFor.push(msgOut.id);
1135
+ logger.silly(`Message in ${msgIn.id} is a response for message out ${msgOut.id}`);
1136
+ }
955
1137
  return bresp;
956
1138
  }
957
1139
  catch (err) { }
@@ -1,5 +1,6 @@
1
1
  /* nodejs-poolController. An application to control pool equipment.
2
- Copyright (C) 2016, 2017, 2018, 2019, 2020. Russell Goldin, tagyoureit. russ.goldin@gmail.com
2
+ Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
3
+ Russell Goldin, tagyoureit. russ.goldin@gmail.com
3
4
 
4
5
  This program is free software: you can redistribute it and/or modify
5
6
  it under the terms of the GNU Affero General Public License as
@@ -28,7 +29,7 @@ export class ChlorinatorMessage {
28
29
  for (let i = 0; i < 4 && i + 30 < msg.payload.length; i++) {
29
30
  let isActive = msg.extractPayloadByte(i + 22) === 1;
30
31
  chlor = sys.chlorinators.getItemById(chlorId);
31
- //if (chlor.master !== 0) continue; // RSG: probably never need this. See Touch chlor below.
32
+ if (chlor.master !== 0) continue; // RSG: probably never need this. See Touch chlor below.
32
33
  if (isActive) {
33
34
  chlor = sys.chlorinators.getItemById(chlorId, true);
34
35
  let schlor = state.chlorinators.getItemById(chlor.id, true);
@@ -85,7 +86,7 @@ export class ChlorinatorMessage {
85
86
  // chlorinator. These should be 0 anyway.
86
87
  schlor.spaSetpoint = chlor.spaSetpoint = msg.extractPayloadByte(0) >> 1;
87
88
  schlor.poolSetpoint = chlor.poolSetpoint = msg.extractPayloadByte(1);
88
- chlor.address = chlor.id + 79;
89
+ chlor.address = msg.dest;
89
90
  schlor.body = chlor.body = sys.equipment.maxBodies >= 1 || sys.equipment.shared === true ? 32 : 0;
90
91
  }
91
92
  if (typeof chlor.name === 'undefined') schlor.name = chlor.name = msg.extractPayloadString(6, 16);
@@ -1,5 +1,6 @@
1
1
  /* nodejs-poolController. An application to control pool equipment.
2
- Copyright (C) 2016, 2017, 2018, 2019, 2020. Russell Goldin, tagyoureit. russ.goldin@gmail.com
2
+ Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
3
+ Russell Goldin, tagyoureit. russ.goldin@gmail.com
3
4
 
4
5
  This program is free software: you can redistribute it and/or modify
5
6
  it under the terms of the GNU Affero General Public License as
@@ -113,7 +114,8 @@ export class CircuitGroupMessage {
113
114
  group.nameId = sgroup.nameId = feature.nameId;
114
115
  group.type = sgroup.type = sys.board.valueMaps.circuitGroupTypes.getValue('circuit');
115
116
  group.isActive = _isActive;
116
- if (typeof group.showInFeatures === 'undefined') sgroup.showInFeatures = group.showInFeatures = true;
117
+ if (typeof group.showInFeatures === 'undefined') group.showInFeatures = true;
118
+ sgroup.showInFeatures = group.showInFeatures;
117
119
  let circuits: CircuitGroupCircuitCollection = group.circuits;
118
120
  for (let byte = 1; byte <= 7; byte++){
119
121
  let offByte = msg.extractPayloadByte(byte);
@@ -189,6 +191,7 @@ export class CircuitGroupMessage {
189
191
  group.isActive = sgroup.isActive = true;
190
192
  if (typeof group.showInFeatures === 'undefined') group.showInFeatures = true;
191
193
  sgroup.type = group.type;
194
+ sgroup.showInFeatures = group.showInFeatures;
192
195
  }
193
196
  state.emitEquipmentChanges();
194
197
  msg.isProcessed = true;