nodejs-poolcontroller 7.6.1 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/.eslintrc.json +36 -45
  2. package/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -0
  3. package/.github/ISSUE_TEMPLATE/2-docs.md +12 -0
  4. package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -0
  5. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  6. package/CONTRIBUTING.md +74 -74
  7. package/Changelog +242 -215
  8. package/Dockerfile +17 -17
  9. package/Gruntfile.js +40 -40
  10. package/LICENSE +661 -661
  11. package/README.md +195 -191
  12. package/anslq25/MessagesMock.ts +218 -0
  13. package/anslq25/boards/MockBoardFactory.ts +50 -0
  14. package/anslq25/boards/MockEasyTouchBoard.ts +696 -0
  15. package/anslq25/boards/MockSystemBoard.ts +217 -0
  16. package/anslq25/chemistry/MockChlorinator.ts +75 -0
  17. package/anslq25/pumps/MockPump.ts +84 -0
  18. package/app.ts +10 -14
  19. package/config/Config.ts +26 -8
  20. package/config/VersionCheck.ts +8 -4
  21. package/controller/Constants.ts +59 -25
  22. package/controller/Equipment.ts +2667 -2459
  23. package/controller/Errors.ts +181 -180
  24. package/controller/Lockouts.ts +534 -436
  25. package/controller/State.ts +596 -77
  26. package/controller/boards/AquaLinkBoard.ts +1003 -0
  27. package/controller/boards/BoardFactory.ts +53 -45
  28. package/controller/boards/EasyTouchBoard.ts +3079 -2653
  29. package/controller/boards/IntelliCenterBoard.ts +3821 -4230
  30. package/controller/boards/IntelliComBoard.ts +69 -63
  31. package/controller/boards/IntelliTouchBoard.ts +384 -241
  32. package/controller/boards/NixieBoard.ts +1871 -1675
  33. package/controller/boards/SunTouchBoard.ts +393 -0
  34. package/controller/boards/SystemBoard.ts +5244 -4697
  35. package/controller/comms/Comms.ts +905 -541
  36. package/controller/comms/ScreenLogic.ts +1663 -0
  37. package/controller/comms/messages/Messages.ts +382 -54
  38. package/controller/comms/messages/config/ChlorinatorMessage.ts +8 -4
  39. package/controller/comms/messages/config/CircuitGroupMessage.ts +5 -2
  40. package/controller/comms/messages/config/CircuitMessage.ts +82 -13
  41. package/controller/comms/messages/config/ConfigMessage.ts +3 -1
  42. package/controller/comms/messages/config/CoverMessage.ts +2 -1
  43. package/controller/comms/messages/config/CustomNameMessage.ts +31 -30
  44. package/controller/comms/messages/config/EquipmentMessage.ts +5 -1
  45. package/controller/comms/messages/config/ExternalMessage.ts +33 -3
  46. package/controller/comms/messages/config/FeatureMessage.ts +2 -1
  47. package/controller/comms/messages/config/GeneralMessage.ts +2 -1
  48. package/controller/comms/messages/config/HeaterMessage.ts +145 -11
  49. package/controller/comms/messages/config/IntellichemMessage.ts +2 -1
  50. package/controller/comms/messages/config/OptionsMessage.ts +16 -27
  51. package/controller/comms/messages/config/PumpMessage.ts +62 -47
  52. package/controller/comms/messages/config/RemoteMessage.ts +80 -13
  53. package/controller/comms/messages/config/ScheduleMessage.ts +390 -347
  54. package/controller/comms/messages/config/SecurityMessage.ts +2 -1
  55. package/controller/comms/messages/config/ValveMessage.ts +44 -27
  56. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +44 -91
  57. package/controller/comms/messages/status/EquipmentStateMessage.ts +139 -30
  58. package/controller/comms/messages/status/HeaterStateMessage.ts +135 -86
  59. package/controller/comms/messages/status/IntelliChemStateMessage.ts +448 -445
  60. package/controller/comms/messages/status/IntelliValveStateMessage.ts +36 -35
  61. package/controller/comms/messages/status/PumpStateMessage.ts +92 -2
  62. package/controller/comms/messages/status/VersionMessage.ts +2 -1
  63. package/controller/nixie/Nixie.ts +173 -162
  64. package/controller/nixie/NixieEquipment.ts +104 -103
  65. package/controller/nixie/bodies/Body.ts +120 -120
  66. package/controller/nixie/bodies/Filter.ts +135 -135
  67. package/controller/nixie/chemistry/ChemController.ts +2682 -2498
  68. package/controller/nixie/chemistry/ChemDoser.ts +806 -0
  69. package/controller/nixie/chemistry/Chlorinator.ts +367 -314
  70. package/controller/nixie/circuits/Circuit.ts +402 -248
  71. package/controller/nixie/heaters/Heater.ts +815 -649
  72. package/controller/nixie/pumps/Pump.ts +934 -661
  73. package/controller/nixie/schedules/Schedule.ts +319 -257
  74. package/controller/nixie/valves/Valve.ts +170 -170
  75. package/defaultConfig.json +346 -286
  76. package/logger/DataLogger.ts +448 -448
  77. package/logger/Logger.ts +38 -9
  78. package/package.json +60 -56
  79. package/tsconfig.json +25 -25
  80. package/web/Server.ts +275 -117
  81. package/web/bindings/aqualinkD.json +560 -0
  82. package/web/bindings/homeassistant.json +437 -0
  83. package/web/bindings/influxDB.json +1066 -1021
  84. package/web/bindings/mqtt.json +721 -654
  85. package/web/bindings/mqttAlt.json +746 -684
  86. package/web/bindings/rulesManager.json +54 -54
  87. package/web/bindings/smartThings-Hubitat.json +31 -31
  88. package/web/bindings/valveRelays.json +20 -20
  89. package/web/bindings/vera.json +25 -25
  90. package/web/interfaces/baseInterface.ts +188 -136
  91. package/web/interfaces/httpInterface.ts +148 -124
  92. package/web/interfaces/influxInterface.ts +283 -245
  93. package/web/interfaces/mqttInterface.ts +695 -475
  94. package/web/interfaces/ruleInterface.ts +87 -0
  95. package/web/services/config/Config.ts +177 -49
  96. package/web/services/config/ConfigSocket.ts +2 -1
  97. package/web/services/state/State.ts +154 -3
  98. package/web/services/state/StateSocket.ts +69 -18
  99. package/web/services/utilities/Utilities.ts +232 -42
  100. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -52
  101. package/config copy.json +0 -300
  102. package/issue_template.md +0 -52
@@ -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,6 +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";
46
+ import { conn } from "../Comms"
47
+ import extend = require("extend");
48
+ import { MessagesMock } from "../../../anslq25/MessagesMock";
49
+
45
50
  export enum Direction {
46
51
  In = 'in',
47
52
  Out = 'out'
@@ -54,6 +59,8 @@ export enum Protocol {
54
59
  IntelliChem = 'intellichem',
55
60
  IntelliValve = 'intellivalve',
56
61
  Heater = 'heater',
62
+ AquaLink = 'aqualink',
63
+ Hayward = 'hayward',
57
64
  Unidentified = 'unidentified'
58
65
  }
59
66
  export class Message {
@@ -66,8 +73,11 @@ export class Message {
66
73
  private _id: number = -1;
67
74
  // Fields
68
75
  private static _messageId: number = 0;
69
- public static get nextMessageId(): number { return this._messageId < 80000 ? ++this._messageId : this._messageId = 0; }
70
-
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; }
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.
71
81
  public timestamp: Date = new Date();
72
82
  public direction: Direction = Direction.In;
73
83
  public protocol: Protocol = Protocol.Unknown;
@@ -81,36 +91,62 @@ export class Message {
81
91
  public set id(val: number) { this._id = val; }
82
92
  public isValid: boolean = true;
83
93
  public scope: string;
94
+ public isClone: boolean;
84
95
  // Properties
85
96
  public get isComplete(): boolean { return this._complete; }
86
97
  public get sub(): number { return this.header.length > 1 ? this.header[1] : -1; }
87
98
  public get dest(): number {
88
- if (this.protocol === Protocol.Chlorinator) {
89
- return this.header[2] >= 80 ? this.header[2] - 79 : 0;
99
+ if (this.header.length > 2) {
100
+ if (this.protocol === Protocol.Chlorinator || this.protocol === Protocol.AquaLink) {
101
+ return this.header.length > 2 ? (this.header[2] >= 80 ? this.header[2] : 0) : -1;
102
+ }
103
+ else if (this.protocol === Protocol.Hayward) {
104
+ // src act dest
105
+ //0x10, 0x02, 0x00, 0x0C, 0x00, 0x00, 0x2D, 0x02, 0x36, 0x00, 0x83, 0x10, 0x03 -- Response from pump
106
+ return this.header.length > 4 ? this.header[2] : -1;
107
+ }
108
+ else return this.header.length > 2 ? this.header[2] : -1;
90
109
  }
91
- if (this.header.length > 2) return this.header[2];
92
110
  else return -1;
93
111
  }
94
112
  public get source(): number {
95
113
  if (this.protocol === Protocol.Chlorinator) {
96
- return this.header[2] >= 80 ? 0 : 1;
114
+ return this.header.length > 2 ? (this.header[2] >= 80 ? 0 : this.header[2]) : -1;
97
115
  // have to assume incoming packets with header[2] >= 80 (sent to a chlorinator)
98
116
  // are from controller (0);
99
117
  // likewise, if the destination is 0 (controller) we
100
118
  // have to assume it was sent from the 1st chlorinator (1)
101
119
  // until we learn otherwise.
102
120
  }
121
+ else if (this.protocol === Protocol.AquaLink) {
122
+ // Once we decode the devices we will be able to tell where it came from based upon the commands.
123
+ return 0;
124
+ }
125
+ else if (this.protocol === Protocol.Hayward) {
126
+ // src act dest
127
+ //0x10, 0x02, 0x00, 0x0C, 0x00, 0x00, 0x2D, 0x02, 0x36, 0x00, 0x83, 0x10, 0x03 -- Response from pump
128
+ //0x10, 0x02, 0x0C, 0x01, 0x02, 0x2D, 0x00, 0x4E, 0x10, 0x03 -- Command to AUX2 Pump
129
+ return this.header.length > 4 ? this.header[4] : -1;
130
+ }
103
131
  if (this.header.length > 3) return this.header[3];
104
132
  else return -1;
105
133
  }
106
134
  public get action(): number {
107
- if (this.protocol === Protocol.Chlorinator) return this.header[3];
108
- if (this.header.length > 5) return this.header[4];
135
+ // The action byte is actually the 4th byte in the header the destination address is the 5th byte.
136
+ if (this.protocol === Protocol.Chlorinator ||
137
+ this.protocol === Protocol.AquaLink) return this.header.length > 3 ? this.header[3] : -1;
138
+ else if (this.protocol === Protocol.Hayward) {
139
+ // src act dest
140
+ //0x10, 0x02, 0x00, 0x0C, 0x00, 0x00, 0x2D, 0x02, 0x36, 0x00, 0x83, 0x10, 0x03 -- Response from pump
141
+ //0x10, 0x02, 0x0C, 0x01, 0x02, 0x2D, 0x00, 0x4E, 0x10, 0x03 -- Command to AUX2 Pump
142
+ return this.header.length > 3 ? this.header[3] || this.header[2] : -1;
143
+ }
144
+ if (this.header.length > 4) return this.header[4];
109
145
  else return -1;
110
146
  }
111
- public get datalen(): number { return this.protocol === Protocol.Chlorinator ? this.payload.length : this.header.length > 5 ? this.header[5] : -1; }
112
- public get chkHi(): number { return this.protocol === Protocol.Chlorinator ? 0 : this.term.length > 0 ? this.term[0] : -1; }
113
- public get chkLo(): number { return this.protocol === Protocol.Chlorinator ? this.term[0] : this.term[1]; }
147
+ public get datalen(): number { return this.protocol === Protocol.Chlorinator || this.protocol === Protocol.AquaLink || this.protocol === Protocol.Hayward ? this.payload.length : this.header.length > 5 ? this.header[5] : -1; }
148
+ public get chkHi(): number { return this.protocol === Protocol.Chlorinator || this.protocol === Protocol.AquaLink ? 0 : this.term.length > 0 ? this.term[0] : -1; }
149
+ public get chkLo(): number { return this.protocol === Protocol.Chlorinator || this.protocol === Protocol.AquaLink ? this.term[0] : this.term[1]; }
114
150
  public get checksum(): number {
115
151
  var sum = 0;
116
152
  for (let i = 0; i < this.header.length; i++) sum += this.header[i];
@@ -136,7 +172,59 @@ export class Message {
136
172
  return pkt;
137
173
  }
138
174
  public toLog(): string {
139
- 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)}"}`;
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;
140
228
  }
141
229
  }
142
230
  export class Inbound extends Message {
@@ -158,26 +246,93 @@ export class Inbound extends Message {
158
246
  public responseFor: number[] = [];
159
247
  public isProcessed: boolean = false;
160
248
  public collisions: number = 0;
249
+ public rewinds: number = 0;
161
250
  // Private methods
162
251
  private isValidChecksum(): boolean {
163
- if (this.protocol === Protocol.Chlorinator) return this.checksum % 256 === this.chkLo;
252
+ if (this.protocol === Protocol.Chlorinator || this.protocol === Protocol.AquaLink) return this.checksum % 256 === this.chkLo;
164
253
  return (this.chkHi * 256) + this.chkLo === this.checksum;
165
254
  }
166
255
  public toLog() {
167
256
  if (this.responseFor.length > 0)
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
- 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)}"}`;
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)}"}`;
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)}"}`;
170
259
  }
171
260
  private testChlorHeader(bytes: number[], ndx: number): boolean {
172
261
  // if packets have 16,2 (eg status=16,2,29) in them and they come as partial packets, they would have
173
262
  // prev been detected as chlor packets;
174
263
  // valid chlor packets should have 16,2,0 or 16,2,[80-96];
175
264
  // 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)))
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;
286
+ }
287
+ private testAquaLinkHeader(bytes: number[], ndx: number): boolean {
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;
294
+ }
295
+ private testHaywardHeader(bytes: number[], ndx: number): boolean {
296
+ //0x10, 0x02, 0x0C, 0x01, 0x00, 0x2D, 0x00, 0x4C, 0x10, 0x03 -- Command to pump
297
+ //[16,2,12,1,0]
298
+ //0x10, 0x02, 0x0C, 0x01, 0x00, 0x2D, 0x00, 0x4C, 0x10, 0x03 -- Command to Filter Pump
299
+ //[16,2,12,1,0]
300
+ //0x10, 0x02, 0x0C, 0x01, 0x02, 0x2D, 0x00, 0x4E, 0x10, 0x03 -- Command to AUX2 Pump
301
+ //[16,2,12,1,2]
302
+ // src act dest
303
+ //0x10, 0x02, 0x00, 0x0C, 0x00, 0x00, 0x2D, 0x02, 0x36, 0x00, 0x83, 0x10, 0x03 -- Response from pump
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;
177
315
  }
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; }
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; }
180
- private testChlorTerm(bytes: number[], ndx: number): boolean { return ndx < bytes.length - 2 && bytes[ndx + 1] === 16 && bytes[ndx + 2] === 3; }
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;
332
+ }
333
+ private testChlorTerm(bytes: number[], ndx: number): boolean { return ndx + 2 < bytes.length && bytes[ndx + 1] === 16 && bytes[ndx + 2] === 3; }
334
+ private testAquaLinkTerm(bytes: number[], ndx: number): boolean { return ndx + 2 < bytes.length && bytes[ndx + 1] === 16 && bytes[ndx + 2] === 3; }
335
+ private testHaywardTerm(bytes: number[], ndx: number): boolean { return ndx + 3 < bytes.length && bytes[ndx + 2] === 16 && bytes[ndx + 3] === 3; }
181
336
  private pushBytes(target: number[], bytes: number[], ndx: number, length: number): number {
182
337
  let end = ndx + length;
183
338
  while (ndx < bytes.length && ndx < end)
@@ -205,16 +360,18 @@ export class Inbound extends Message {
205
360
  this.isValid = true;
206
361
 
207
362
  this.collisions++;
363
+ this.rewinds++;
208
364
  logger.info(`rewinding message collision ${this.collisions} ${ndx} ${bytes.length} ${JSON.stringify(buff)}`);
209
365
  this.readPacket(buff);
210
366
  return ndx;
211
367
  //return this.padding.length + this.preamble.length;
212
368
  }
213
369
  public readPacket(bytes: number[]): number {
370
+ //logger.info(`BYTES: ${JSON.stringify(bytes)}`);
214
371
  var ndx = this.readHeader(bytes, 0);
215
372
  if (this.isValid && this.header.length > 0) ndx = this.readPayload(bytes, ndx);
216
373
  if (this.isValid && this.header.length > 0) ndx = this.readChecksum(bytes, ndx);
217
- //if (this.isComplete && !this.isValid) return this.rewind(bytes, ndx);
374
+ if (this.isComplete && !this.isValid) return this.rewind(bytes, ndx);
218
375
  return ndx;
219
376
  }
220
377
  public mergeBytes(bytes) {
@@ -227,22 +384,40 @@ export class Inbound extends Message {
227
384
  }
228
385
  public readHeader(bytes: number[], ndx: number): number {
229
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
+ //}
230
390
  let ndxStart = ndx;
231
- while (ndx < bytes.length) {
232
- if (this.testChlorHeader(bytes, ndx)) {
233
- this.protocol = Protocol.Chlorinator;
234
- break;
235
- }
236
- if (this.testBroadcastHeader(bytes, ndx)) {
237
- this.protocol = Protocol.Broadcast;
238
- break;
239
- }
240
- else if (this.testUnidentifiedHeader(bytes, ndx)) {
241
- this.protocol = Protocol.Unidentified;
242
- 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++]);
243
416
  }
244
- this.padding.push(bytes[ndx++]);
245
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.
246
421
  let ndxHeader = ndx;
247
422
  switch (this.protocol) {
248
423
  case Protocol.Pump:
@@ -257,11 +432,11 @@ export class Inbound extends Message {
257
432
  // We actually don't have a complete header yet so just return.
258
433
  // we will pick it up next go around.
259
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)}`);
260
436
  this.preamble = [];
261
437
  this.header = [];
262
438
  return ndxHeader;
263
439
  }
264
-
265
440
  if (this.source >= 96 && this.source <= 111) this.protocol = Protocol.Pump;
266
441
  else if (this.dest >= 96 && this.dest <= 111) this.protocol = Protocol.Pump;
267
442
  else if (this.source >= 112 && this.source <= 127) this.protocol = Protocol.Heater;
@@ -271,12 +446,13 @@ export class Inbound extends Message {
271
446
  else if (this.source == 12 || this.dest == 12) this.protocol = Protocol.IntelliValve;
272
447
  if (this.datalen > 75) {
273
448
  //this.isValid = false;
274
- 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}`);
275
450
  this.padding.push(...this.preamble);
276
451
  this.padding.push(...this.header.slice(0, 1));
277
452
  this.preamble = [];
278
453
  this.header = [];
279
454
  this.collisions++;
455
+ this.rewinds++;
280
456
  return ndxHeader + 1;
281
457
  }
282
458
  break;
@@ -298,6 +474,28 @@ export class Inbound extends Message {
298
474
  return ndxHeader;
299
475
  }
300
476
  break;
477
+ case Protocol.Hayward:
478
+ ndx = this.pushBytes(this.header, bytes, ndx, 5);
479
+ if (this.header.length < 4) {
480
+ // We actually don't have a complete header yet so just return.
481
+ // we will pick it up next go around.
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}]`);
483
+ this.preamble = [];
484
+ this.header = [];
485
+ return ndxHeader;
486
+ }
487
+ break;
488
+ case Protocol.AquaLink:
489
+ ndx = this.pushBytes(this.header, bytes, ndx, 5);
490
+ if (this.header.length < 5) {
491
+ // We actually don't have a complete header yet so just return.
492
+ // we will pick it up next go around.
493
+ logger.debug(`We have an incoming AquaLink message but the serial port hasn't given a complete header. [${this.padding}][${this.preamble}][${this.header}]`);
494
+ this.preamble = [];
495
+ this.header = [];
496
+ return ndxHeader;
497
+ }
498
+ break;
301
499
  default:
302
500
  // We didn't get a message signature. don't do anything with it.
303
501
  ndx = ndxStart;
@@ -326,7 +524,11 @@ export class Inbound extends Message {
326
524
  case Protocol.IntelliValve:
327
525
  case Protocol.Heater:
328
526
  case Protocol.Unidentified:
329
- 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
+ }
330
532
  ndx = this.pushBytes(this.payload, bytes, ndx, this.datalen - this.payload.length);
331
533
  break;
332
534
  case Protocol.Chlorinator:
@@ -341,6 +543,31 @@ export class Inbound extends Message {
341
543
  }
342
544
  }
343
545
  break;
546
+ case Protocol.AquaLink:
547
+ // We need to deal with AquaLink packets where the terminator is actually split meaning only the first byte or
548
+ // two of the total payload is provided for the term. We need at least 3 bytes to make this determination.
549
+ while (ndx + 3 <= bytes.length && !this.testAquaLinkTerm(bytes, ndx)) {
550
+ this.payload.push(bytes[ndx++]);
551
+ if (this.payload.length > 25) {
552
+ this.isValid = false; // We have a runaway packet. Some collision occurred so lets preserve future packets.
553
+ logger.debug(`AquaLink message marked as invalid after not finding 16,3 in payload after ${this.payload.length} bytes`);
554
+ break;
555
+ }
556
+ }
557
+ break;
558
+ case Protocol.Hayward:
559
+ // We need to deal with AquaLink packets where the terminator is actually split meaning only the first byte or
560
+ // two of the total payload is provided for the term. We need at least 3 bytes to make this determination.
561
+ while (ndx + 4 <= bytes.length && !this.testHaywardTerm(bytes, ndx)) {
562
+ this.payload.push(bytes[ndx++]);
563
+ if (this.payload.length > 25) {
564
+ this.isValid = false; // We have a runaway packet. Some collision occurred so lets preserve future packets.
565
+ logger.debug(`Hayward message marked as invalid after not finding 16,3 in payload after ${this.payload.length} bytes`);
566
+ break;
567
+ }
568
+ }
569
+ break;
570
+
344
571
  }
345
572
  return ndx;
346
573
  }
@@ -369,6 +596,21 @@ export class Inbound extends Message {
369
596
  this.isValid = this.isValidChecksum();
370
597
  }
371
598
  break;
599
+ case Protocol.AquaLink:
600
+ if (ndx + 3 <= bytes.length && this.testAquaLinkTerm(bytes, ndx)) {
601
+ this._complete = true;
602
+ ndx = this.pushBytes(this.term, bytes, ndx, 3);
603
+ this.isValid = this.isValidChecksum();
604
+ }
605
+ break;
606
+ case Protocol.Hayward:
607
+ if (ndx + 4 <= bytes.length && this.testHaywardTerm(bytes, ndx)) {
608
+ this._complete = true;
609
+ ndx = this.pushBytes(this.term, bytes, ndx, 4);
610
+ this.isValid = this.isValidChecksum();
611
+ }
612
+ break;
613
+
372
614
  }
373
615
  return ndx;
374
616
  }
@@ -471,7 +713,16 @@ export class Inbound extends Message {
471
713
  PumpMessage.process(this);
472
714
  break;
473
715
  case 30:
474
- 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
+ }
475
726
  break;
476
727
  case 22:
477
728
  case 32:
@@ -514,6 +765,9 @@ export class Inbound extends Message {
514
765
  case 147:
515
766
  IntellichemMessage.process(this);
516
767
  break;
768
+ case 136:
769
+ ExternalMessage.processTouchSetHeatMode(this);
770
+ break;
517
771
  default:
518
772
  if (this.action === 109 && this.payload[1] === 3) break;
519
773
  if (this.source === 17 && this.payload[0] === 109) break;
@@ -524,6 +778,13 @@ export class Inbound extends Message {
524
778
  }
525
779
  }
526
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
+ }
527
788
  switch (this.protocol) {
528
789
  case Protocol.Broadcast:
529
790
  this.processBroadcast();
@@ -546,6 +807,9 @@ export class Inbound extends Message {
546
807
  case Protocol.Chlorinator:
547
808
  ChlorinatorStateMessage.process(this);
548
809
  break;
810
+ case Protocol.Hayward:
811
+ PumpStateMessage.processHayward(this);
812
+ break;
549
813
  default:
550
814
  logger.debug(`Unprocessed Message ${this.toPacket()}`)
551
815
  break;
@@ -553,15 +817,44 @@ export class Inbound extends Message {
553
817
  }
554
818
  }
555
819
  class OutboundCommon extends Message {
556
- public set sub(val: number) { if (this.protocol !== Protocol.Chlorinator) this.header[1] = val; }
820
+ public set sub(val: number) { if (this.protocol !== Protocol.Chlorinator && this.protocol !== Protocol.AquaLink) this.header[1] = val; }
557
821
  public get sub() { return super.sub; }
558
- public set dest(val: number) { this.protocol !== Protocol.Chlorinator ? this.header[2] = val : this.header[2] = val + 79; }
822
+ public set dest(val: number) {
823
+ if (this.protocol === Protocol.Chlorinator) this.header[2] = val;
824
+ else if (this.protocol === Protocol.Hayward) this.header[4] = val;
825
+ else this.header[2] = val;
826
+ }
559
827
  public get dest() { return super.dest; }
560
- public set source(val: number) { if (this.protocol !== Protocol.Chlorinator) this.header[3] = val; }
828
+ public set source(val: number) {
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;
841
+ }
561
842
  public get source() { return super.source; }
562
- public set action(val: number) { (this.protocol !== Protocol.Chlorinator) ? this.header[4] = val : this.header[3] = val; }
843
+ public set action(val: number) {
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
+ }
855
+ }
563
856
  public get action() { return super.action; }
564
- public set datalen(val: number) { if (this.protocol !== Protocol.Chlorinator) this.header[5] = val; }
857
+ public set datalen(val: number) { if (this.protocol !== Protocol.Chlorinator && this.protocol !== Protocol.Hayward) this.header[5] = val; }
565
858
  public get datalen() { return super.datalen; }
566
859
  public set chkHi(val: number) { if (this.protocol !== Protocol.Chlorinator) this.term[0] = val; }
567
860
  public get chkHi() { return super.chkHi; }
@@ -578,11 +871,13 @@ class OutboundCommon extends Message {
578
871
  case Protocol.Unidentified:
579
872
  case Protocol.IntelliChem:
580
873
  case Protocol.Heater:
874
+ case Protocol.Hayward:
581
875
  this.chkHi = Math.floor(sum / 256);
582
876
  this.chkLo = (sum - (super.chkHi * 256));
583
877
  break;
878
+ case Protocol.AquaLink:
584
879
  case Protocol.Chlorinator:
585
- this.term[0] = sum;
880
+ this.term[0] = sum % 256;
586
881
  break;
587
882
  }
588
883
  }
@@ -598,7 +893,7 @@ export class Outbound extends OutboundCommon {
598
893
  this.header.length = 0;
599
894
  this.term.length = 0;
600
895
  this.payload.length = 0;
601
- if (proto === Protocol.Chlorinator) {
896
+ if (proto === Protocol.Chlorinator || proto === Protocol.AquaLink) {
602
897
  this.header.push.apply(this.header, [16, 2, 0, 0]);
603
898
  this.term.push.apply(this.term, [0, 16, 3]);
604
899
  }
@@ -612,6 +907,10 @@ export class Outbound extends OutboundCommon {
612
907
  this.header.push.apply(this.header, [165, 0, 15, Message.pluginAddress, 0, 0]);
613
908
  this.term.push.apply(this.term, [0, 0]);
614
909
  }
910
+ else if (proto === Protocol.Hayward) {
911
+ this.header.push.apply(this.header, [16, 2, 0, 0, 0]);
912
+ this.term.push.apply(this.term, [0, 0, 16, 3]);
913
+ }
615
914
  this.scope = scope;
616
915
  this.source = source;
617
916
  this.dest = dest;
@@ -625,8 +924,19 @@ export class Outbound extends OutboundCommon {
625
924
  }
626
925
  // Factory
627
926
  public static create(obj?: any) {
628
- let out = new Outbound(obj.protocol || Protocol.Broadcast,
629
- obj.source || sys.board.commandSourceAddress || Message.pluginAddress, obj.dest || sys.board.commandDestAddress || 16, obj.action || 0, obj.payload || [], obj.retries || 0, obj.response || false, obj.scope || undefined);
927
+ let o = extend({
928
+ protocol: Protocol.Broadcast,
929
+ source: sys.board.commandSourceAddress || Message.pluginAddress,
930
+ dest: sys.board.commandDestAddress || 16,
931
+ action: 0,
932
+ payload: [],
933
+ retries: 0,
934
+ response: false,
935
+ }, obj, true);
936
+ let out = new Outbound(o.protocol, o.source, o.dest, o.action, o.payload, o.retries, o.response, o.scope);
937
+ //let out = new Outbound(obj.protocol || Protocol.Broadcast,
938
+ // obj.source || sys.board.commandSourceAddress || Message.pluginAddress, obj.dest || sys.board.commandDestAddress || 16, obj.action || 0, obj.payload || [], obj.retries || 0, obj.response || false, obj.scope || undefined);
939
+ out.portId = obj.portId || 0;
630
940
  out.onComplete = obj.onComplete;
631
941
  out.onAbort = obj.onAbort;
632
942
  out.timeout = obj.timeout;
@@ -643,7 +953,9 @@ export class Outbound extends OutboundCommon {
643
953
  public static createMessage(action: number, payload: number[], retries?: number, response?: Response | boolean): Outbound {
644
954
  return new Outbound(Protocol.Broadcast, sys.board.commandSourceAddress || Message.pluginAddress, sys.board.commandDestAddress || 16, action, payload, retries, response);
645
955
  }
646
-
956
+ public async sendAsync() {
957
+ return conn.queueSendMessageAsync(this);
958
+ }
647
959
  // Fields
648
960
  public retries: number = 0;
649
961
  public tries: number = 0;
@@ -659,7 +971,6 @@ export class Outbound extends OutboundCommon {
659
971
  return false;
660
972
  }
661
973
  public get remainingTries(): number { return this.retries - this.tries + 1; } // Always allow 1 try.
662
-
663
974
  public setPayloadByte(ndx: number, value: number, def?: number) {
664
975
  if (typeof value === 'undefined' || isNaN(value)) value = def;
665
976
  if (ndx < this.payload.length) this.payload[ndx] = value;
@@ -737,11 +1048,11 @@ export class Outbound extends OutboundCommon {
737
1048
  if (typeof s === 'undefined') s = def;
738
1049
  let l = typeof len === 'undefined' ? s.length : len;
739
1050
  let buf = [];
740
- for (let i = 0; i < l; l++) {
741
- if (i < l) buf.push(s.charCodeAt(i));
742
- 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);
743
1054
  }
744
- this.payload.splice(start, 0, ...buf);
1055
+ this.payload.splice(start, l, ...buf);
745
1056
  return this;
746
1057
  }
747
1058
  public toPacket(): number[] {
@@ -754,6 +1065,20 @@ export class Outbound extends OutboundCommon {
754
1065
  pkt.push.apply(pkt, this.term);
755
1066
  return pkt;
756
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
+ }
757
1082
  }
758
1083
  export class Ack extends Outbound {
759
1084
  constructor(byte: number) {
@@ -805,7 +1130,10 @@ export class Response extends OutboundCommon {
805
1130
  try {
806
1131
  if (typeof this.responseBool === 'boolean' && this.responseBool) bresp = this.evalResponse(msgIn, msgOut);
807
1132
  else return bresp;
808
- 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
+ }
809
1137
  return bresp;
810
1138
  }
811
1139
  catch (err) { }