nodejs-poolcontroller 8.1.1 → 8.3.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.
@@ -42,6 +42,7 @@ import { IntellichemMessage } from "./config/IntellichemMessage";
42
42
  import { TouchScheduleCommands } from "controller/boards/EasyTouchBoard";
43
43
  import { IntelliValveStateMessage } from "./status/IntelliValveStateMessage";
44
44
  import { IntelliChemStateMessage } from "./status/IntelliChemStateMessage";
45
+ import { RegalModbusStateMessage } from "./status/RegalModbusStateMessage";
45
46
  import { OutboundMessageError } from "../../Errors";
46
47
  import { conn } from "../Comms"
47
48
  import extend = require("extend");
@@ -61,7 +62,8 @@ export enum Protocol {
61
62
  Heater = 'heater',
62
63
  AquaLink = 'aqualink',
63
64
  Hayward = 'hayward',
64
- Unidentified = 'unidentified'
65
+ Unidentified = 'unidentified',
66
+ RegalModbus = 'regalmodbus'
65
67
  }
66
68
  export class Message {
67
69
  constructor() { }
@@ -105,6 +107,9 @@ export class Message {
105
107
  //0x10, 0x02, 0x00, 0x0C, 0x00, 0x00, 0x2D, 0x02, 0x36, 0x00, 0x83, 0x10, 0x03 -- Response from pump
106
108
  return this.header.length > 4 ? this.header[2] : -1;
107
109
  }
110
+ else if (this.protocol === Protocol.RegalModbus) {
111
+ return this.header.length > 0 ? this.header[0] : -1;
112
+ }
108
113
  else return this.header.length > 2 ? this.header[2] : -1;
109
114
  }
110
115
  else return -1;
@@ -128,6 +133,10 @@ export class Message {
128
133
  //0x10, 0x02, 0x0C, 0x01, 0x02, 0x2D, 0x00, 0x4E, 0x10, 0x03 -- Command to AUX2 Pump
129
134
  return this.header.length > 4 ? this.header[4] : -1;
130
135
  }
136
+ else if (this.protocol === Protocol.RegalModbus) {
137
+ // No source address in RegalModbus.
138
+ return -1;
139
+ }
131
140
  if (this.header.length > 3) return this.header[3];
132
141
  else return -1;
133
142
  }
@@ -141,10 +150,57 @@ export class Message {
141
150
  //0x10, 0x02, 0x0C, 0x01, 0x02, 0x2D, 0x00, 0x4E, 0x10, 0x03 -- Command to AUX2 Pump
142
151
  return this.header.length > 3 ? this.header[3] || this.header[2] : -1;
143
152
  }
153
+ else if (this.protocol === Protocol.RegalModbus) {
154
+ return this.header.length > 1 ? this.header[1]: -1;
155
+ }
156
+ else if (this.header.length > 4) return this.header[4];
157
+ else return -1;
144
158
  if (this.header.length > 4) return this.header[4];
145
159
  else return -1;
146
160
  }
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; }
161
+ public get datalen(): number {
162
+ if (
163
+ this.protocol === Protocol.Chlorinator ||
164
+ this.protocol === Protocol.AquaLink ||
165
+ this.protocol === Protocol.Hayward
166
+ ) {
167
+ return this.payload.length;
168
+ }
169
+ else if (this.protocol === Protocol.RegalModbus) {
170
+ let action = this.action;
171
+ let ack = this.header[2];
172
+ switch (action) {
173
+ case 0x41: // Go
174
+ case 0x42: // Stop
175
+ return 0;
176
+ case 0x43: // Status
177
+ switch (ack) {
178
+ case 0x10:
179
+ return 1;
180
+ case 0x20:
181
+ return 0
182
+ }
183
+ case 0x44: // Set demand
184
+ return 3;
185
+ case 0x45: // Read sensor
186
+ switch (ack) {
187
+ case 0x10:
188
+ return 4;
189
+ case 0x20:
190
+ return 2;
191
+ }
192
+ case 0x46: // Read identification
193
+ console.log("RegalModbus: Read identification not implemented yet.");
194
+ break;
195
+ case 0x64: // Configuration read/write
196
+ console.log("RegalModbus: Configuration read/write not implemented yet.");
197
+ break;
198
+ case 0x65: // Store configuration
199
+ return 0;
200
+ }
201
+ }
202
+ return this.header.length > 5 ? this.header[5] : -1;
203
+ }
148
204
  public get chkHi(): number { return this.protocol === Protocol.Chlorinator || this.protocol === Protocol.AquaLink ? 0 : this.term.length > 0 ? this.term[0] : -1; }
149
205
  public get chkLo(): number { return this.protocol === Protocol.Chlorinator || this.protocol === Protocol.AquaLink ? this.term[0] : this.term[1]; }
150
206
  public get checksum(): number {
@@ -249,8 +305,19 @@ export class Inbound extends Message {
249
305
  public rewinds: number = 0;
250
306
  // Private methods
251
307
  private isValidChecksum(): boolean {
252
- if (this.protocol === Protocol.Chlorinator || this.protocol === Protocol.AquaLink) return this.checksum % 256 === this.chkLo;
253
- return (this.chkHi * 256) + this.chkLo === this.checksum;
308
+ switch (this.protocol) {
309
+ case Protocol.Chlorinator:
310
+ case Protocol.AquaLink:
311
+ return this.checksum % 256 === this.chkLo;
312
+ case Protocol.RegalModbus: {
313
+ const data = this.header.concat(this.payload);
314
+ const crcComputed = computeCRC16(data);
315
+ const crcReceived = (this.chkLo << 8) | this.chkHi;
316
+ return crcComputed === crcReceived;
317
+ }
318
+ default:
319
+ return (this.chkHi * 256) + this.chkLo === this.checksum;
320
+ }
254
321
  }
255
322
  public toLog() {
256
323
  if (this.responseFor.length > 0)
@@ -284,6 +351,26 @@ export class Inbound extends Message {
284
351
  }
285
352
  return false;
286
353
  }
354
+ private testRegalModbusHeader(bytes: number[], ndx: number): boolean {
355
+ // RegalModbus protocol: header, function, ack, payload, crcLo, crcHi
356
+ if (bytes.length > ndx + 3 && sys.controllerType === 'nixie') {
357
+ // address must be in the range 0x15 to 0xF7
358
+ // function code must be in the range 0x00 to 0x7F
359
+ // ack must be in 0x10, 0x20, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x09, 0x0A
360
+ let addr = bytes[ndx];
361
+ let func = bytes[ndx + 1];
362
+ let ack = bytes[ndx + 2];
363
+ let acceptableAcks = [0x10, 0x20, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x09, 0x0A];
364
+
365
+ // logger.debug('Testing RegalModbus header', bytes, addr, func, ack, acceptableAcks.includes(ack));
366
+ // logger.debug(`Current bytes: ${JSON.stringify(bytes)}`);
367
+
368
+ if (addr >= 0x15 && addr <= 0xF7 && func >= 0x00 && func <= 0x7F && acceptableAcks.includes(ack)) {
369
+ return true;
370
+ }
371
+ }
372
+ return false;
373
+ }
287
374
  private testAquaLinkHeader(bytes: number[], ndx: number): boolean {
288
375
  if (bytes.length > ndx + 4 && sys.controllerType === 'aqualink') {
289
376
  if (bytes[ndx] === 16 && bytes[ndx + 1] === 2) {
@@ -412,6 +499,11 @@ export class Inbound extends Message {
412
499
  this.protocol = Protocol.Hayward;
413
500
  break;
414
501
  }
502
+ if (this.testRegalModbusHeader(bytes, ndx)) {
503
+ this.protocol = Protocol.RegalModbus;
504
+ logger.debug(`RegalModbus header detected. ${JSON.stringify(bytes)}`);
505
+ break;
506
+ }
415
507
  this.padding.push(bytes[ndx++]);
416
508
  }
417
509
  }
@@ -496,6 +588,17 @@ export class Inbound extends Message {
496
588
  return ndxHeader;
497
589
  }
498
590
  break;
591
+ case Protocol.RegalModbus:
592
+ ndx = this.pushBytes(this.header, bytes, ndx, 3);
593
+ if (this.header.length < 3) {
594
+ // We actually don't have a complete header yet so just return.
595
+ // we will pick it up next go around.
596
+ logger.debug(`We have an incoming RegalModbus message but the serial port hasn't given a complete header. [${this.padding}][${this.preamble}][${this.header}]`);
597
+ this.preamble = [];
598
+ this.header = [];
599
+ return ndxHeader;
600
+ }
601
+ break;
499
602
  default:
500
603
  // We didn't get a message signature. don't do anything with it.
501
604
  ndx = ndxStart;
@@ -567,6 +670,17 @@ export class Inbound extends Message {
567
670
  }
568
671
  }
569
672
  break;
673
+ case Protocol.RegalModbus:
674
+ // RegalModbus protocol: header, function, ack, payload, crcLo, crcHi
675
+ while (ndx + 3 <= bytes.length) {
676
+ this.payload.push(bytes[ndx++]);
677
+ if (this.payload.length > 11) {
678
+ this.isValid = false; // We have a runaway packet. Some collision occurred so lets preserve future packets.
679
+ logger.debug(`RegalModbus message marked as invalid due to payload more than 11 bytes`);
680
+ break;
681
+ }
682
+ }
683
+ break;
570
684
 
571
685
  }
572
686
  return ndx;
@@ -580,6 +694,7 @@ export class Inbound extends Message {
580
694
  case Protocol.IntelliValve:
581
695
  case Protocol.IntelliChem:
582
696
  case Protocol.Heater:
697
+ case Protocol.RegalModbus:
583
698
  case Protocol.Unidentified:
584
699
  // If we don't have enough bytes to make the terminator then continue on and
585
700
  // hope we get them on the next go around.
@@ -810,6 +925,9 @@ export class Inbound extends Message {
810
925
  case Protocol.Hayward:
811
926
  PumpStateMessage.processHayward(this);
812
927
  break;
928
+ case Protocol.RegalModbus:
929
+ RegalModbusStateMessage.process(this);
930
+ break;
813
931
  default:
814
932
  logger.debug(`Unprocessed Message ${this.toPacket()}`)
815
933
  break;
@@ -822,6 +940,7 @@ class OutboundCommon extends Message {
822
940
  public set dest(val: number) {
823
941
  if (this.protocol === Protocol.Chlorinator) this.header[2] = val;
824
942
  else if (this.protocol === Protocol.Hayward) this.header[4] = val;
943
+ else if (this.protocol === Protocol.RegalModbus) this.header[0] = val;
825
944
  else this.header[2] = val;
826
945
  }
827
946
  public get dest() { return super.dest; }
@@ -832,6 +951,8 @@ class OutboundCommon extends Message {
832
951
  case Protocol.Hayward:
833
952
  this.header[3] = val;
834
953
  break;
954
+ case Protocol.RegalModbus:
955
+ break;
835
956
  default:
836
957
  this.header[3] = val;
837
958
  break;
@@ -848,13 +969,20 @@ class OutboundCommon extends Message {
848
969
  case Protocol.Hayward:
849
970
  this.header[2] = val;
850
971
  break;
972
+ case Protocol.RegalModbus:
973
+ this.header[1] = val;
974
+ break;
851
975
  default:
852
976
  this.header[4] = val;
853
977
  break;
854
978
  }
855
979
  }
856
980
  public get action() { return super.action; }
857
- public set datalen(val: number) { if (this.protocol !== Protocol.Chlorinator && this.protocol !== Protocol.Hayward) this.header[5] = val; }
981
+ public set datalen(val: number) {
982
+ if (this.protocol !== Protocol.Chlorinator && this.protocol !== Protocol.Hayward && this.protocol !== Protocol.RegalModbus) {
983
+ this.header[5] = val;
984
+ }
985
+ }
858
986
  public get datalen() { return super.datalen; }
859
987
  public set chkHi(val: number) { if (this.protocol !== Protocol.Chlorinator) this.term[0] = val; }
860
988
  public get chkHi() { return super.chkHi; }
@@ -879,6 +1007,16 @@ class OutboundCommon extends Message {
879
1007
  case Protocol.Chlorinator:
880
1008
  this.term[0] = sum % 256;
881
1009
  break;
1010
+ case Protocol.RegalModbus:
1011
+ // Calculate checksum using the CRC16 algorithm and set chkHi and chkLo.
1012
+ // This.payload is expected to be an array of numbers (byte values 0–255)
1013
+ // combine header and payload for CRC calculation
1014
+ let data: number[] = this.header.concat(this.payload);
1015
+ const crc: number = computeCRC16(data);
1016
+ // Extract the high and low bytes from the 16-bit CRC:
1017
+ this.chkLo = (crc >> 8) & 0xFF;
1018
+ this.chkHi = crc & 0xFF;
1019
+ break;
882
1020
  }
883
1021
  }
884
1022
  }
@@ -911,6 +1049,9 @@ export class Outbound extends OutboundCommon {
911
1049
  this.header.push.apply(this.header, [16, 2, 0, 0, 0]);
912
1050
  this.term.push.apply(this.term, [0, 0, 16, 3]);
913
1051
  }
1052
+ else if (proto === Protocol.RegalModbus) {
1053
+ this.header.push.apply(this.header, [this.dest, this.action, 0x20]);
1054
+ }
914
1055
  this.scope = scope;
915
1056
  this.source = source;
916
1057
  this.dest = dest;
@@ -1184,6 +1325,12 @@ export class Response extends OutboundCommon {
1184
1325
  return false;
1185
1326
  }
1186
1327
  }
1328
+ else if (msgIn.protocol === Protocol.RegalModbus) {
1329
+ // RegalModbus is a little different. The action is the function code and the payload is the data.
1330
+ // We are looking for a match on the action an ack of 0x10.
1331
+ if (msgIn.action === msgOut.action && msgIn.header[2] === 0x10) return true;
1332
+ return false;
1333
+ }
1187
1334
  else if (msgIn.protocol === Protocol.Chlorinator) {
1188
1335
  switch (msgIn.action) {
1189
1336
  case 1:
@@ -1240,4 +1387,20 @@ export class Response extends OutboundCommon {
1240
1387
  return true;
1241
1388
  }
1242
1389
  }
1243
- }
1390
+ }
1391
+
1392
+ /**
1393
+ * Computes the CRC16 checksum over an array of bytes using the RegalModbus algorithm.
1394
+ * @param data - The array of byte values (numbers between 0 and 255).
1395
+ * @returns The computed 16-bit checksum.
1396
+ */
1397
+ export function computeCRC16(data: number[]): number {
1398
+ let crc = 0xFFFF;
1399
+ for (const byte of data) {
1400
+ crc ^= byte;
1401
+ for (let j = 0; j < 8; j++) {
1402
+ crc = (crc & 0x0001) ? (crc >> 1) ^ 0xA001 : crc >> 1;
1403
+ }
1404
+ }
1405
+ return crc;
1406
+ }