njs-modbus 1.5.0 → 2.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 (58) hide show
  1. package/README.md +16 -0
  2. package/dist/index.cjs +1938 -1148
  3. package/dist/index.d.ts +308 -89
  4. package/dist/index.mjs +1931 -1149
  5. package/dist/src/error-code.d.ts +27 -1
  6. package/dist/src/index.d.ts +2 -0
  7. package/dist/src/layers/application/abstract-application-layer.d.ts +11 -8
  8. package/dist/src/layers/application/ascii-application-layer.d.ts +14 -10
  9. package/dist/src/layers/application/index.d.ts +2 -0
  10. package/dist/src/layers/application/rtu-application-layer.d.ts +52 -18
  11. package/dist/src/layers/application/tcp-application-layer.d.ts +4 -8
  12. package/dist/src/layers/physical/abstract-physical-layer.d.ts +5 -1
  13. package/dist/src/layers/physical/index.d.ts +1 -0
  14. package/dist/src/layers/physical/serial-physical-layer.d.ts +2 -0
  15. package/dist/src/layers/physical/tcp-client-physical-layer.d.ts +3 -0
  16. package/dist/src/layers/physical/tcp-server-physical-layer.d.ts +3 -1
  17. package/dist/src/layers/physical/udp-physical-layer.d.ts +25 -10
  18. package/dist/src/master/index.d.ts +2 -0
  19. package/dist/src/master/master-session.d.ts +18 -0
  20. package/dist/src/master/master.d.ts +27 -5
  21. package/dist/src/slave/index.d.ts +1 -1
  22. package/dist/src/slave/slave.d.ts +18 -5
  23. package/dist/src/types.d.ts +33 -2
  24. package/dist/src/utils/bitsToMs.d.ts +13 -0
  25. package/dist/src/utils/crc.d.ts +1 -1
  26. package/dist/src/utils/genConnectionId.d.ts +2 -0
  27. package/dist/src/utils/index.d.ts +5 -1
  28. package/dist/src/utils/isUint8.d.ts +8 -0
  29. package/dist/src/utils/predictRtuFrameLength.d.ts +20 -0
  30. package/dist/src/vars.d.ts +47 -0
  31. package/dist/test/adu-buffer.test.d.ts +1 -0
  32. package/dist/test/ascii-hex-sentry.test.d.ts +1 -0
  33. package/dist/test/ascii-hex-validation.test.d.ts +1 -0
  34. package/dist/test/ascii-tcp-fragmentation.test.d.ts +1 -0
  35. package/dist/test/check-range.test.d.ts +1 -0
  36. package/dist/test/fallback-atomic.test.d.ts +1 -0
  37. package/dist/test/fallback-serial.test.d.ts +1 -0
  38. package/dist/test/fc17-serverid-validation.test.d.ts +1 -0
  39. package/dist/test/fc43-conformity.test.d.ts +1 -0
  40. package/dist/test/fc43-utf8-objects.test.d.ts +1 -0
  41. package/dist/test/gen-connection-id.test.d.ts +1 -0
  42. package/dist/test/helpers/raw-tcp.d.ts +38 -0
  43. package/dist/test/master-concurrent.test.d.ts +1 -0
  44. package/dist/test/modbus-error.test.d.ts +1 -0
  45. package/dist/test/physical-lifecycle.test.d.ts +1 -0
  46. package/dist/test/predict-rtu.test.d.ts +1 -0
  47. package/dist/test/rtu-custom-fc.test.d.ts +1 -0
  48. package/dist/test/rtu-pool-overflow.test.d.ts +1 -0
  49. package/dist/test/rtu-t15-timing.test.d.ts +1 -0
  50. package/dist/test/rtu-t35-default.test.d.ts +1 -0
  51. package/dist/test/rtu-t35-strict.test.d.ts +1 -0
  52. package/dist/test/rtu-tcp-fragmentation.test.d.ts +1 -0
  53. package/dist/test/serial-e2e.test.d.ts +1 -0
  54. package/dist/test/slave-multi-connection.test.d.ts +1 -0
  55. package/dist/test/tcp-fragmentation.test.d.ts +1 -0
  56. package/dist/test/udp-multi-client.test.d.ts +1 -0
  57. package/package.json +4 -2
  58. package/dist/src/utils/getThreePointFiveT.d.ts +0 -7
package/dist/index.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import EventEmitter from 'node:events';
2
2
  import { SerialPort } from 'serialport';
3
+ import { randomUUID } from 'node:crypto';
3
4
  import { Socket, createServer } from 'node:net';
4
5
  import { createSocket } from 'node:dgram';
5
6
 
@@ -15,20 +16,293 @@ var ErrorCode;
15
16
  ErrorCode[ErrorCode["GATEWAY_PATH_UNAVAILABLE"] = 10] = "GATEWAY_PATH_UNAVAILABLE";
16
17
  ErrorCode[ErrorCode["GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND"] = 11] = "GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND";
17
18
  })(ErrorCode || (ErrorCode = {}));
18
- const PREFIX = 'MODBUS_ERROR_CODE_';
19
+ /** Internal error codes for programmatic error handling. */
20
+ const ModbusErrorCode = {
21
+ TIMEOUT: 'ETIMEOUT',
22
+ INVALID_RESPONSE: 'EINVALID_RESPONSE',
23
+ INSUFFICIENT_DATA: 'EINSUFFICIENT_DATA',
24
+ MASTER_CLOSED: 'EMASTER_CLOSED',
25
+ MASTER_DESTROYED: 'EMASTER_DESTROYED',
26
+ CONCURRENT_NOT_TCP: 'ECONCURRENT_NOT_TCP',
27
+ PORT_DESTROYED: 'EPORT_DESTROYED',
28
+ PORT_ALREADY_OPEN: 'EPORT_ALREADY_OPEN',
29
+ PORT_NOT_OPEN: 'EPORT_NOT_OPEN',
30
+ NOT_SUPPORTED: 'ENOT_SUPPORTED',
31
+ INVALID_DATA: 'EINVALID_DATA',
32
+ INVALID_HEX: 'EINVALID_HEX',
33
+ CRC_MISMATCH: 'ECRC_MISMATCH',
34
+ LRC_MISMATCH: 'ELRC_MISMATCH',
35
+ INCOMPLETE_FRAME: 'EINCOMPLETE_FRAME',
36
+ T1_5_EXCEEDED: 'ET1_5_EXCEEDED',
37
+ UNKNOWN_FC: 'EUNKNOWN_FC',
38
+ INVALID_ROLE: 'EINVALID_ROLE',
39
+ RANGE: 'ERANGE',
40
+ };
41
+ const MODBUS_PREFIX = 'MODBUS_ERROR_CODE_';
42
+ class ModbusError extends Error {
43
+ constructor(code, message) {
44
+ super(message !== null && message !== void 0 ? message : code);
45
+ Object.defineProperty(this, "code", {
46
+ enumerable: true,
47
+ configurable: true,
48
+ writable: true,
49
+ value: code
50
+ });
51
+ this.name = 'ModbusError';
52
+ }
53
+ }
19
54
  function getErrorByCode(code) {
20
- return new Error(PREFIX + code);
55
+ return new ModbusError(String(code), `${MODBUS_PREFIX}${code}`);
21
56
  }
22
57
  function getCodeByError(err) {
23
- if (err.message.startsWith(PREFIX)) {
24
- return Number(err.message.slice(PREFIX.length));
58
+ if (err instanceof ModbusError) {
59
+ const num = Number(err.code);
60
+ if (!Number.isNaN(num) && num in ErrorCode) {
61
+ return num;
62
+ }
63
+ }
64
+ if (err.message.startsWith(MODBUS_PREFIX)) {
65
+ return Number(err.message.slice(MODBUS_PREFIX.length));
25
66
  }
26
67
  return ErrorCode.SERVER_DEVICE_FAILURE;
27
68
  }
28
69
 
70
+ /**
71
+ * Standard Modbus function codes (V1.1b3 §6).
72
+ */
73
+ var FunctionCode;
74
+ (function (FunctionCode) {
75
+ FunctionCode[FunctionCode["READ_COILS"] = 1] = "READ_COILS";
76
+ FunctionCode[FunctionCode["READ_DISCRETE_INPUTS"] = 2] = "READ_DISCRETE_INPUTS";
77
+ FunctionCode[FunctionCode["READ_HOLDING_REGISTERS"] = 3] = "READ_HOLDING_REGISTERS";
78
+ FunctionCode[FunctionCode["READ_INPUT_REGISTERS"] = 4] = "READ_INPUT_REGISTERS";
79
+ FunctionCode[FunctionCode["WRITE_SINGLE_COIL"] = 5] = "WRITE_SINGLE_COIL";
80
+ FunctionCode[FunctionCode["WRITE_SINGLE_REGISTER"] = 6] = "WRITE_SINGLE_REGISTER";
81
+ FunctionCode[FunctionCode["WRITE_MULTIPLE_COILS"] = 15] = "WRITE_MULTIPLE_COILS";
82
+ FunctionCode[FunctionCode["WRITE_MULTIPLE_REGISTERS"] = 16] = "WRITE_MULTIPLE_REGISTERS";
83
+ FunctionCode[FunctionCode["REPORT_SERVER_ID"] = 17] = "REPORT_SERVER_ID";
84
+ FunctionCode[FunctionCode["MASK_WRITE_REGISTER"] = 22] = "MASK_WRITE_REGISTER";
85
+ FunctionCode[FunctionCode["READ_WRITE_MULTIPLE_REGISTERS"] = 23] = "READ_WRITE_MULTIPLE_REGISTERS";
86
+ FunctionCode[FunctionCode["READ_DEVICE_IDENTIFICATION"] = 43] = "READ_DEVICE_IDENTIFICATION";
87
+ })(FunctionCode || (FunctionCode = {}));
88
+ /** Exception response FC = request FC | EXCEPTION_OFFSET (V1.1b3 §7). */
89
+ const EXCEPTION_OFFSET = 0x80;
90
+ /** Coil value encoding for FC 5 / FC 15 (V1.1b3 §6.5/§6.11). */
91
+ const COIL_ON = 0xff00;
92
+ const COIL_OFF = 0x0000;
93
+ /** FC 0x2B MEI sub-function selecting Read Device Identification (V1.1b3 §6.21). */
94
+ const MEI_READ_DEVICE_ID = 0x0e;
95
+ /** Read Device ID code values inside an FC 0x2B / MEI 0x0E request. */
96
+ var ReadDeviceIDCode;
97
+ (function (ReadDeviceIDCode) {
98
+ ReadDeviceIDCode[ReadDeviceIDCode["BASIC_STREAM"] = 1] = "BASIC_STREAM";
99
+ ReadDeviceIDCode[ReadDeviceIDCode["REGULAR_STREAM"] = 2] = "REGULAR_STREAM";
100
+ ReadDeviceIDCode[ReadDeviceIDCode["EXTENDED_STREAM"] = 3] = "EXTENDED_STREAM";
101
+ ReadDeviceIDCode[ReadDeviceIDCode["SPECIFIC_ACCESS"] = 4] = "SPECIFIC_ACCESS";
102
+ })(ReadDeviceIDCode || (ReadDeviceIDCode = {}));
103
+ /** Conformity level reported in an FC 0x2B / MEI 0x0E response. */
104
+ var ConformityLevel;
105
+ (function (ConformityLevel) {
106
+ ConformityLevel[ConformityLevel["BASIC"] = 129] = "BASIC";
107
+ ConformityLevel[ConformityLevel["REGULAR"] = 130] = "REGULAR";
108
+ ConformityLevel[ConformityLevel["EXTENDED"] = 131] = "EXTENDED";
109
+ })(ConformityLevel || (ConformityLevel = {}));
110
+ /** Modbus V1.1b3 PDU quantity limits. */
111
+ const LIMITS = {
112
+ READ_COILS_MIN: 0x0001,
113
+ READ_COILS_MAX: 0x07d0,
114
+ READ_REGISTERS_MIN: 0x0001,
115
+ READ_REGISTERS_MAX: 0x007d,
116
+ WRITE_COILS_MAX: 0x07b0,
117
+ WRITE_REGISTERS_MAX: 0x007b,
118
+ RW_REGISTERS_WRITE_MAX: 0x0079,
119
+ };
120
+
29
121
  class AbstractPhysicalLayer extends EventEmitter {
30
122
  }
31
123
 
124
+ function inRange(n, [min, max]) {
125
+ return n >= min && n <= max;
126
+ }
127
+ function checkRange(value, range) {
128
+ if (!range) {
129
+ return true;
130
+ }
131
+ const values = Array.isArray(value) ? value : [value];
132
+ if (typeof range[0] === 'number' && typeof range[1] === 'number') {
133
+ const [min, max] = range;
134
+ const [lo, hi] = min <= max ? [min, max] : [max, min];
135
+ return values.every((n) => inRange(n, [lo, hi]));
136
+ }
137
+ else if (range.length > 0) {
138
+ for (const r of range) {
139
+ const [min, max] = r;
140
+ const [lo, hi] = min <= max ? [min, max] : [max, min];
141
+ if (values.every((n) => inRange(n, [lo, hi]))) {
142
+ return true;
143
+ }
144
+ }
145
+ return false;
146
+ }
147
+ return true;
148
+ }
149
+
150
+ const TABLE = [
151
+ 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, 0xcc01,
152
+ 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, 0xd801, 0x18c0,
153
+ 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, 0x1400, 0xd4c1, 0xd581,
154
+ 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641, 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040, 0xf001, 0x30c0, 0x3180, 0xf141,
155
+ 0x3300, 0xf3c1, 0xf281, 0x3240, 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01,
156
+ 0x3fc0, 0x3e80, 0xfe41, 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0,
157
+ 0x2a80, 0xea41, 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40, 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681,
158
+ 0x2640, 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041, 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240,
159
+ 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441, 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, 0xaa01,
160
+ 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, 0xbe01, 0x7ec0,
161
+ 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640, 0x7200, 0xb2c1, 0xb381,
162
+ 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041, 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241, 0x9601, 0x56c0, 0x5780, 0x9741,
163
+ 0x5500, 0x95c1, 0x9481, 0x5440, 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901,
164
+ 0x59c0, 0x5880, 0x9841, 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0,
165
+ 0x4c80, 0x8c41, 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081,
166
+ 0x4040,
167
+ ];
168
+ function crc(data, seed = 0xffff) {
169
+ let crc = seed;
170
+ for (let index = 0; index < data.length; index++) {
171
+ crc = (TABLE[(crc ^ data[index]) & 0xff] ^ (crc >> 8)) & 0xffff;
172
+ }
173
+ return crc;
174
+ }
175
+
176
+ /** Cross-process unique id: `${prefix}-${uuid-v4}`. */
177
+ function genConnectionId(prefix) {
178
+ return `${prefix}-${randomUUID()}`;
179
+ }
180
+
181
+ /**
182
+ * Convert a number of bits to milliseconds at a given baud rate.
183
+ *
184
+ * Used to derive Modbus RTU timing intervals from bit counts — e.g. 38.5 bits
185
+ * = 3.5 character times at 11 bits/char (t3.5 inter-frame silence), or 16.5
186
+ * bits = 1.5 character times (t1.5 inter-character timeout), per Modbus V1.02
187
+ * §2.5.1.1.
188
+ *
189
+ * @param baudRate Serial port baud rate.
190
+ * @param bits Number of bits to convert.
191
+ * @returns Duration in milliseconds.
192
+ */
193
+ function bitsToMs(baudRate, bits) {
194
+ return (bits * 1000) / baudRate;
195
+ }
196
+
197
+ /**
198
+ * Returns true when `n` is an integer in the unsigned-byte range [0, 255].
199
+ *
200
+ * Used for byte-level Modbus payload validation (function-code values, raw
201
+ * byte arrays in FC17/FC43 responses) — rejects negative, fractional, NaN,
202
+ * Infinity, and out-of-range values uniformly.
203
+ */
204
+ function isUint8(n) {
205
+ return Number.isInteger(n) && n >= 0 && n <= 255;
206
+ }
207
+
208
+ function lrc(data) {
209
+ return (~data.reduce((sum, n) => sum + n, 0) + 1) & 0xff;
210
+ }
211
+
212
+ const REQUEST_FIXED_LENGTHS = {
213
+ [FunctionCode.READ_COILS]: 8,
214
+ [FunctionCode.READ_DISCRETE_INPUTS]: 8,
215
+ [FunctionCode.READ_HOLDING_REGISTERS]: 8,
216
+ [FunctionCode.READ_INPUT_REGISTERS]: 8,
217
+ [FunctionCode.WRITE_SINGLE_COIL]: 8,
218
+ [FunctionCode.WRITE_SINGLE_REGISTER]: 8,
219
+ [FunctionCode.REPORT_SERVER_ID]: 4,
220
+ [FunctionCode.MASK_WRITE_REGISTER]: 10,
221
+ [FunctionCode.READ_DEVICE_IDENTIFICATION]: 7,
222
+ };
223
+ const REQUEST_BYTE_COUNT = {
224
+ [FunctionCode.WRITE_MULTIPLE_COILS]: { offset: 6, extra: 9 },
225
+ [FunctionCode.WRITE_MULTIPLE_REGISTERS]: { offset: 6, extra: 9 },
226
+ [FunctionCode.READ_WRITE_MULTIPLE_REGISTERS]: { offset: 10, extra: 13 },
227
+ };
228
+ const RESPONSE_FIXED_LENGTHS = {
229
+ [FunctionCode.WRITE_SINGLE_COIL]: 8,
230
+ [FunctionCode.WRITE_SINGLE_REGISTER]: 8,
231
+ [FunctionCode.WRITE_MULTIPLE_COILS]: 8,
232
+ [FunctionCode.WRITE_MULTIPLE_REGISTERS]: 8,
233
+ [FunctionCode.MASK_WRITE_REGISTER]: 10,
234
+ };
235
+ const RESPONSE_BYTE_COUNT = {
236
+ [FunctionCode.READ_COILS]: { offset: 2, extra: 5 },
237
+ [FunctionCode.READ_DISCRETE_INPUTS]: { offset: 2, extra: 5 },
238
+ [FunctionCode.READ_HOLDING_REGISTERS]: { offset: 2, extra: 5 },
239
+ [FunctionCode.READ_INPUT_REGISTERS]: { offset: 2, extra: 5 },
240
+ [FunctionCode.REPORT_SERVER_ID]: { offset: 2, extra: 5 },
241
+ [FunctionCode.READ_WRITE_MULTIPLE_REGISTERS]: { offset: 2, extra: 5 },
242
+ };
243
+ /**
244
+ * Predict the total RTU frame length (PDU + 2-byte CRC) given the leading bytes.
245
+ *
246
+ * Returns a discriminated result so callers can distinguish:
247
+ * - `{ kind: 'length' }`: function code is known and total frame length is determined.
248
+ * - `{ kind: 'need-more' }`: function code is known, but more bytes are required
249
+ * (typically waiting on the byteCount byte).
250
+ * - `{ kind: 'unknown' }`: function code is not in the standard tables — the
251
+ * framing layer must defer to a registered `CustomFunctionCode` or treat this
252
+ * as a framing error.
253
+ */
254
+ function predictRtuFrameLength(buffer, isResponse) {
255
+ if (buffer.length < 2) {
256
+ return { kind: 'need-more' };
257
+ }
258
+ const fc = buffer[1];
259
+ if (isResponse && (fc & EXCEPTION_OFFSET) !== 0) {
260
+ return { kind: 'length', length: 5 };
261
+ }
262
+ const fixed = (isResponse ? RESPONSE_FIXED_LENGTHS : REQUEST_FIXED_LENGTHS)[fc];
263
+ if (fixed !== undefined) {
264
+ return { kind: 'length', length: fixed };
265
+ }
266
+ const bc = (isResponse ? RESPONSE_BYTE_COUNT : REQUEST_BYTE_COUNT)[fc];
267
+ if (bc !== undefined) {
268
+ if (buffer.length <= bc.offset) {
269
+ return { kind: 'need-more' };
270
+ }
271
+ return { kind: 'length', length: bc.extra + buffer[bc.offset] };
272
+ }
273
+ if (isResponse && fc === FunctionCode.READ_DEVICE_IDENTIFICATION) {
274
+ return predictFc43_14Response(buffer);
275
+ }
276
+ return { kind: 'unknown' };
277
+ }
278
+ /**
279
+ * Walk the variable-length FC 0x2B / MEI 0x0E (Read Device Identification)
280
+ * response structure per Modbus V1.1b3 §6.21.
281
+ *
282
+ * Layout (after unit and fc):
283
+ * mei(1) rdic(1) conformity(1) more(1) nextObjId(1) numObjs(1)
284
+ * [objId(1) objLen(1) objData(objLen)] × numObjs
285
+ * CRC(2)
286
+ */
287
+ function predictFc43_14Response(buffer) {
288
+ if (buffer.length < 8) {
289
+ return { kind: 'need-more' };
290
+ }
291
+ if (buffer[2] !== MEI_READ_DEVICE_ID) {
292
+ return { kind: 'unknown' };
293
+ }
294
+ const numObjs = buffer[7];
295
+ let offset = 8;
296
+ for (let i = 0; i < numObjs; i++) {
297
+ if (buffer.length < offset + 2) {
298
+ return { kind: 'need-more' };
299
+ }
300
+ const objLen = buffer[offset + 1];
301
+ offset += 2 + objLen;
302
+ }
303
+ return { kind: 'length', length: offset + 2 };
304
+ }
305
+
32
306
  class SerialPhysicalLayer extends AbstractPhysicalLayer {
33
307
  get isOpen() {
34
308
  return this._serialport.isOpen;
@@ -53,6 +327,18 @@ class SerialPhysicalLayer extends AbstractPhysicalLayer {
53
327
  writable: true,
54
328
  value: void 0
55
329
  });
330
+ Object.defineProperty(this, "_connection", {
331
+ enumerable: true,
332
+ configurable: true,
333
+ writable: true,
334
+ value: null
335
+ });
336
+ Object.defineProperty(this, "_isOpening", {
337
+ enumerable: true,
338
+ configurable: true,
339
+ writable: true,
340
+ value: false
341
+ });
56
342
  Object.defineProperty(this, "_destroyed", {
57
343
  enumerable: true,
58
344
  configurable: true,
@@ -69,27 +355,41 @@ class SerialPhysicalLayer extends AbstractPhysicalLayer {
69
355
  this._baudRate = options.baudRate;
70
356
  }
71
357
  open() {
72
- if (this.destroyed) {
73
- return Promise.reject(new Error('Port is destroyed'));
358
+ if (this._destroyed) {
359
+ return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Port is destroyed'));
360
+ }
361
+ if (this.isOpen || this._isOpening) {
362
+ return Promise.reject(new ModbusError(ModbusErrorCode.PORT_ALREADY_OPEN, 'Port is already open'));
74
363
  }
364
+ this._isOpening = true;
365
+ const connection = { id: genConnectionId('serial') };
366
+ this._connection = connection;
75
367
  return new Promise((resolve, reject) => {
76
368
  this._serialport.open((error) => {
77
369
  if (error) {
370
+ this._isOpening = false;
371
+ if (this._connection === connection) {
372
+ this._connection = null;
373
+ }
78
374
  reject(error);
375
+ return;
79
376
  }
80
- else {
81
- this._serialport.on('data', (data) => {
82
- this.emit('data', data, (data) => this.write(data));
83
- });
84
- this._serialport.on('error', (error) => {
85
- this.emit('error', error);
86
- });
87
- this._serialport.on('close', () => {
377
+ this._isOpening = false;
378
+ this._serialport.on('data', (data) => {
379
+ this.emit('data', data, (d) => this.write(d), connection);
380
+ });
381
+ this._serialport.on('error', (err) => {
382
+ this.emit('error', err);
383
+ });
384
+ this._serialport.once('close', () => {
385
+ if (this._connection === connection) {
88
386
  this._serialport.removeAllListeners();
387
+ this._connection = null;
388
+ this.emit('connection-close', connection);
89
389
  this.emit('close');
90
- });
91
- resolve();
92
- }
390
+ }
391
+ });
392
+ resolve();
93
393
  });
94
394
  });
95
395
  }
@@ -107,22 +407,24 @@ class SerialPhysicalLayer extends AbstractPhysicalLayer {
107
407
  });
108
408
  }
109
409
  else {
110
- reject(new Error('Port is not open'));
410
+ reject(new ModbusError(ModbusErrorCode.PORT_NOT_OPEN, 'Port is not open'));
111
411
  }
112
412
  });
113
413
  }
114
414
  close() {
415
+ if (!this._serialport.isOpen) {
416
+ return Promise.resolve();
417
+ }
115
418
  return new Promise((resolve) => {
116
- this._serialport.removeAllListeners();
117
- this._serialport.close(() => {
118
- resolve();
119
- });
419
+ this._serialport.once('close', () => resolve());
420
+ this._serialport.close();
120
421
  });
121
422
  }
122
423
  destroy() {
123
424
  this._destroyed = true;
124
- this.removeAllListeners();
125
- return this.close();
425
+ return this.close().then(() => {
426
+ this.removeAllListeners();
427
+ });
126
428
  }
127
429
  }
128
430
 
@@ -145,7 +447,13 @@ class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
145
447
  enumerable: true,
146
448
  configurable: true,
147
449
  writable: true,
148
- value: void 0
450
+ value: null
451
+ });
452
+ Object.defineProperty(this, "_connection", {
453
+ enumerable: true,
454
+ configurable: true,
455
+ writable: true,
456
+ value: null
149
457
  });
150
458
  Object.defineProperty(this, "_isOpen", {
151
459
  enumerable: true,
@@ -153,46 +461,79 @@ class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
153
461
  writable: true,
154
462
  value: false
155
463
  });
464
+ Object.defineProperty(this, "_isOpening", {
465
+ enumerable: true,
466
+ configurable: true,
467
+ writable: true,
468
+ value: false
469
+ });
156
470
  Object.defineProperty(this, "_destroyed", {
157
471
  enumerable: true,
158
472
  configurable: true,
159
473
  writable: true,
160
474
  value: false
161
475
  });
162
- this._socket = new Socket(options);
476
+ Object.defineProperty(this, "_socketOptions", {
477
+ enumerable: true,
478
+ configurable: true,
479
+ writable: true,
480
+ value: void 0
481
+ });
482
+ this._socketOptions = options;
163
483
  }
164
484
  open(options) {
165
- if (this.destroyed) {
166
- return Promise.reject(new Error('Port is destroyed'));
485
+ if (this._destroyed) {
486
+ return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Port is destroyed'));
487
+ }
488
+ if (this._isOpen || this._isOpening) {
489
+ return Promise.reject(new ModbusError(ModbusErrorCode.PORT_ALREADY_OPEN, 'Port is already open'));
167
490
  }
491
+ this._isOpening = true;
492
+ const socket = new Socket(this._socketOptions);
493
+ const connection = { id: genConnectionId('tcp-client') };
494
+ this._socket = socket;
495
+ this._connection = connection;
168
496
  return new Promise((resolve, reject) => {
169
- let called = false;
170
- this._socket.connect(options !== null && options !== void 0 ? options : { port: 502 }, () => {
171
- called = true;
172
- this._isOpen = true;
173
- this._socket.on('data', (data) => {
174
- this.emit('data', data, (data) => this.write(data));
175
- });
176
- this._socket.on('close', () => {
177
- this._isOpen = false;
178
- this._socket.removeAllListeners();
179
- this.emit('close');
180
- });
181
- resolve();
497
+ let connected = false;
498
+ socket.on('data', (data) => {
499
+ this.emit('data', data, (d) => this.write(d), connection);
182
500
  });
183
- this._socket.on('error', (error) => {
184
- if (called) {
185
- this.emit('error', error);
501
+ socket.on('error', (err) => {
502
+ if (connected) {
503
+ this.emit('error', err);
186
504
  }
187
505
  else {
188
- reject(error);
506
+ if (this._connection === connection) {
507
+ this._isOpening = false;
508
+ this._socket = null;
509
+ this._connection = null;
510
+ }
511
+ socket.removeAllListeners();
512
+ reject(err);
513
+ }
514
+ });
515
+ socket.once('close', () => {
516
+ if (this._connection === connection) {
517
+ this._isOpen = false;
518
+ this._isOpening = false;
519
+ this._socket = null;
520
+ this._connection = null;
521
+ socket.removeAllListeners();
522
+ this.emit('connection-close', connection);
523
+ this.emit('close');
189
524
  }
190
525
  });
526
+ socket.connect(options !== null && options !== void 0 ? options : { port: 502 }, () => {
527
+ connected = true;
528
+ this._isOpening = false;
529
+ this._isOpen = true;
530
+ resolve();
531
+ });
191
532
  });
192
533
  }
193
534
  write(data) {
194
535
  return new Promise((resolve, reject) => {
195
- if (this.isOpen) {
536
+ if (this._isOpen && this._socket) {
196
537
  this._socket.write(data, (error) => {
197
538
  if (error) {
198
539
  reject(error);
@@ -204,22 +545,25 @@ class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
204
545
  });
205
546
  }
206
547
  else {
207
- reject(new Error('Port is not open'));
548
+ reject(new ModbusError(ModbusErrorCode.PORT_NOT_OPEN, 'Port is not open'));
208
549
  }
209
550
  });
210
551
  }
211
552
  close() {
553
+ if (!this._socket) {
554
+ return Promise.resolve();
555
+ }
556
+ const socket = this._socket;
212
557
  return new Promise((resolve) => {
213
- this._isOpen = false;
214
- this._socket.removeAllListeners();
215
- this._socket.destroy();
216
- resolve();
558
+ socket.once('close', () => resolve());
559
+ socket.destroy();
217
560
  });
218
561
  }
219
562
  destroy() {
220
563
  this._destroyed = true;
221
- this.removeAllListeners();
222
- return this.close();
564
+ return this.close().then(() => {
565
+ this.removeAllListeners();
566
+ });
223
567
  }
224
568
  }
225
569
 
@@ -242,7 +586,7 @@ class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
242
586
  enumerable: true,
243
587
  configurable: true,
244
588
  writable: true,
245
- value: void 0
589
+ value: null
246
590
  });
247
591
  Object.defineProperty(this, "_isOpen", {
248
592
  enumerable: true,
@@ -250,92 +594,129 @@ class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
250
594
  writable: true,
251
595
  value: false
252
596
  });
597
+ Object.defineProperty(this, "_isOpening", {
598
+ enumerable: true,
599
+ configurable: true,
600
+ writable: true,
601
+ value: false
602
+ });
253
603
  Object.defineProperty(this, "_destroyed", {
254
604
  enumerable: true,
255
605
  configurable: true,
256
606
  writable: true,
257
607
  value: false
258
608
  });
259
- Object.defineProperty(this, "_sockets", {
609
+ Object.defineProperty(this, "_connections", {
610
+ enumerable: true,
611
+ configurable: true,
612
+ writable: true,
613
+ value: new Map()
614
+ });
615
+ Object.defineProperty(this, "_serverOptions", {
260
616
  enumerable: true,
261
617
  configurable: true,
262
618
  writable: true,
263
- value: new Set()
619
+ value: void 0
264
620
  });
265
- this._server = createServer(options, (socket) => {
266
- this._sockets.add(socket);
621
+ this._serverOptions = options;
622
+ }
623
+ open(options) {
624
+ if (this._destroyed) {
625
+ return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Port is destroyed'));
626
+ }
627
+ if (this._isOpen || this._isOpening) {
628
+ return Promise.reject(new ModbusError(ModbusErrorCode.PORT_ALREADY_OPEN, 'Port is already open'));
629
+ }
630
+ this._isOpening = true;
631
+ this._connections.clear();
632
+ const server = createServer(this._serverOptions, (socket) => {
633
+ const conn = { id: genConnectionId('tcp-server') };
634
+ this._connections.set(socket, conn);
635
+ const response = (data) => new Promise((resolve, reject) => {
636
+ socket.write(data, (error) => {
637
+ if (error) {
638
+ reject(error);
639
+ }
640
+ else {
641
+ resolve();
642
+ }
643
+ });
644
+ });
267
645
  socket.on('data', (data) => {
268
- this.emit('data', data, (data) => new Promise((resolve, reject) => {
269
- socket.write(data, (error) => {
270
- if (error) {
271
- reject(error);
272
- }
273
- else {
274
- resolve();
275
- }
276
- });
277
- }));
646
+ this.emit('data', data, response, conn);
647
+ });
648
+ socket.on('error', (err) => {
649
+ this.emit('error', err);
278
650
  });
279
651
  socket.once('close', () => {
652
+ if (this._connections.delete(socket)) {
653
+ this.emit('connection-close', conn);
654
+ }
280
655
  socket.removeAllListeners();
281
- this._sockets.delete(socket);
282
656
  });
283
657
  });
284
- }
285
- open(options) {
286
- if (this.destroyed) {
287
- return Promise.reject(new Error('Port is destroyed'));
288
- }
658
+ this._server = server;
289
659
  return new Promise((resolve, reject) => {
290
660
  var _a;
291
- let called = false;
292
- this._server.listen(Object.assign(Object.assign({}, options), { port: (_a = options === null || options === void 0 ? void 0 : options.port) !== null && _a !== void 0 ? _a : 502 }), () => {
293
- called = true;
294
- this._isOpen = true;
295
- this._sockets.clear();
296
- this._server.on('close', () => {
661
+ let listening = false;
662
+ server.once('error', (err) => {
663
+ if (listening) {
664
+ this.emit('error', err);
665
+ }
666
+ else {
667
+ if (this._server === server) {
668
+ this._isOpening = false;
669
+ this._server = null;
670
+ }
671
+ server.removeAllListeners();
672
+ reject(err);
673
+ }
674
+ });
675
+ server.once('close', () => {
676
+ if (this._server === server) {
297
677
  this._isOpen = false;
298
- this._server.removeAllListeners();
299
- for (const socket of this._sockets) {
300
- socket.removeAllListeners();
678
+ this._isOpening = false;
679
+ this._server = null;
680
+ const conns = Array.from(this._connections.values());
681
+ this._connections.clear();
682
+ for (const conn of conns) {
683
+ this.emit('connection-close', conn);
301
684
  }
302
685
  this.emit('close');
303
- });
304
- resolve();
305
- });
306
- this._server.on('error', (error) => {
307
- if (called) {
308
- this.emit('error', error);
309
- }
310
- else {
311
- reject(error);
312
686
  }
313
687
  });
688
+ server.listen(Object.assign(Object.assign({}, options), { port: (_a = options === null || options === void 0 ? void 0 : options.port) !== null && _a !== void 0 ? _a : 502 }), () => {
689
+ listening = true;
690
+ this._isOpening = false;
691
+ this._isOpen = true;
692
+ resolve();
693
+ });
314
694
  });
315
695
  }
316
696
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
317
697
  write(data) {
318
698
  return new Promise((resolve, reject) => {
319
- reject(new Error('Not supported'));
699
+ reject(new ModbusError(ModbusErrorCode.NOT_SUPPORTED, 'Not supported'));
320
700
  });
321
701
  }
322
702
  close() {
703
+ if (!this._server) {
704
+ return Promise.resolve();
705
+ }
706
+ const server = this._server;
323
707
  return new Promise((resolve) => {
324
- this._isOpen = false;
325
- this._server.removeAllListeners();
326
- for (const socket of this._sockets) {
327
- socket.removeAllListeners();
708
+ server.once('close', () => resolve());
709
+ for (const [socket] of this._connections) {
328
710
  socket.destroy();
329
711
  }
330
- this._server.close(() => {
331
- resolve();
332
- });
712
+ server.close();
333
713
  });
334
714
  }
335
715
  destroy() {
336
716
  this._destroyed = true;
337
- this.removeAllListeners();
338
- return this.close();
717
+ return this.close().then(() => {
718
+ this.removeAllListeners();
719
+ });
339
720
  }
340
721
  }
341
722
 
@@ -346,14 +727,8 @@ class UdpPhysicalLayer extends AbstractPhysicalLayer {
346
727
  get destroyed() {
347
728
  return this._destroyed;
348
729
  }
349
- /**
350
- *
351
- * @param options
352
- * @param remote If omitted, as server.
353
- * Otherwise as client.
354
- */
355
- constructor(options, remote) {
356
- var _a, _b;
730
+ constructor(options) {
731
+ var _a, _b, _c, _d;
357
732
  super();
358
733
  Object.defineProperty(this, "TYPE", {
359
734
  enumerable: true,
@@ -365,7 +740,13 @@ class UdpPhysicalLayer extends AbstractPhysicalLayer {
365
740
  enumerable: true,
366
741
  configurable: true,
367
742
  writable: true,
368
- value: void 0
743
+ value: null
744
+ });
745
+ Object.defineProperty(this, "_connections", {
746
+ enumerable: true,
747
+ configurable: true,
748
+ writable: true,
749
+ value: new Map()
369
750
  });
370
751
  Object.defineProperty(this, "_isOpen", {
371
752
  enumerable: true,
@@ -373,12 +754,24 @@ class UdpPhysicalLayer extends AbstractPhysicalLayer {
373
754
  writable: true,
374
755
  value: false
375
756
  });
757
+ Object.defineProperty(this, "_isOpening", {
758
+ enumerable: true,
759
+ configurable: true,
760
+ writable: true,
761
+ value: false
762
+ });
376
763
  Object.defineProperty(this, "_destroyed", {
377
764
  enumerable: true,
378
765
  configurable: true,
379
766
  writable: true,
380
767
  value: false
381
768
  });
769
+ Object.defineProperty(this, "_socketOptions", {
770
+ enumerable: true,
771
+ configurable: true,
772
+ writable: true,
773
+ value: void 0
774
+ });
382
775
  Object.defineProperty(this, "_port", {
383
776
  enumerable: true,
384
777
  configurable: true,
@@ -391,186 +784,225 @@ class UdpPhysicalLayer extends AbstractPhysicalLayer {
391
784
  writable: true,
392
785
  value: void 0
393
786
  });
394
- Object.defineProperty(this, "isServer", {
787
+ Object.defineProperty(this, "_idleTimeout", {
395
788
  enumerable: true,
396
789
  configurable: true,
397
790
  writable: true,
398
791
  value: void 0
399
792
  });
400
- this._socket = createSocket(Object.assign(Object.assign({}, options), { type: (_a = options === null || options === void 0 ? void 0 : options.type) !== null && _a !== void 0 ? _a : 'udp4' }), (msg, rinfo) => {
401
- this.emit('data', msg, (data) => new Promise((resolve, reject) => {
402
- this._socket.send(data, rinfo.port, rinfo.address, (error) => {
403
- if (error) {
404
- reject(error);
405
- }
406
- else {
407
- resolve();
408
- }
409
- });
410
- }));
793
+ Object.defineProperty(this, "isServer", {
794
+ enumerable: true,
795
+ configurable: true,
796
+ writable: true,
797
+ value: void 0
411
798
  });
412
- this.isServer = !remote;
413
- this._port = (_b = remote === null || remote === void 0 ? void 0 : remote.port) !== null && _b !== void 0 ? _b : 502;
414
- this._address = remote === null || remote === void 0 ? void 0 : remote.address;
799
+ this._socketOptions = options === null || options === void 0 ? void 0 : options.socket;
800
+ this.isServer = !(options === null || options === void 0 ? void 0 : options.remote);
801
+ this._port = (_b = (_a = options === null || options === void 0 ? void 0 : options.remote) === null || _a === void 0 ? void 0 : _a.port) !== null && _b !== void 0 ? _b : 502;
802
+ this._address = (_c = options === null || options === void 0 ? void 0 : options.remote) === null || _c === void 0 ? void 0 : _c.address;
803
+ this._idleTimeout = (_d = options === null || options === void 0 ? void 0 : options.idleTimeout) !== null && _d !== void 0 ? _d : 30000;
415
804
  }
416
805
  open(options) {
417
- if (this.destroyed) {
418
- return Promise.reject(new Error('Port is destroyed'));
806
+ var _a, _b;
807
+ if (this._destroyed) {
808
+ return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Port is destroyed'));
809
+ }
810
+ if (this._isOpen || this._isOpening) {
811
+ return Promise.reject(new ModbusError(ModbusErrorCode.PORT_ALREADY_OPEN, 'Port is already open'));
419
812
  }
813
+ this._isOpening = true;
814
+ this._connections.clear();
815
+ const socket = createSocket(Object.assign(Object.assign({}, this._socketOptions), { type: (_b = (_a = this._socketOptions) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : 'udp4' }), (msg, rinfo) => this._handleMessage(socket, msg, rinfo));
816
+ this._socket = socket;
420
817
  return new Promise((resolve, reject) => {
421
818
  var _a;
819
+ let started = false;
820
+ socket.on('error', (err) => {
821
+ if (started) {
822
+ this.emit('error', err);
823
+ }
824
+ else {
825
+ if (this._socket === socket) {
826
+ this._isOpening = false;
827
+ this._socket = null;
828
+ }
829
+ socket.removeAllListeners();
830
+ reject(err);
831
+ }
832
+ });
833
+ socket.once('close', () => {
834
+ if (this._socket !== socket) {
835
+ return;
836
+ }
837
+ this._isOpen = false;
838
+ this._isOpening = false;
839
+ this._socket = null;
840
+ socket.removeAllListeners();
841
+ const entries = Array.from(this._connections.values());
842
+ this._connections.clear();
843
+ for (const entry of entries) {
844
+ if (entry.idleTimer) {
845
+ clearTimeout(entry.idleTimer);
846
+ }
847
+ this.emit('connection-close', entry.conn);
848
+ }
849
+ this.emit('close');
850
+ });
422
851
  if (this.isServer) {
423
- let called = false;
424
- this._socket.bind(Object.assign(Object.assign({}, options), { port: (_a = options === null || options === void 0 ? void 0 : options.port) !== null && _a !== void 0 ? _a : 502 }), () => {
425
- called = true;
852
+ socket.bind(Object.assign(Object.assign({}, options), { port: (_a = options === null || options === void 0 ? void 0 : options.port) !== null && _a !== void 0 ? _a : 502 }), () => {
853
+ started = true;
854
+ this._isOpening = false;
426
855
  this._isOpen = true;
427
- this._socket.on('close', () => {
428
- this._isOpen = false;
429
- this._socket.removeAllListeners();
430
- this.emit('close');
431
- });
432
856
  resolve();
433
857
  });
434
- this._socket.on('error', (error) => {
435
- if (called) {
436
- this.emit('error', error);
437
- }
438
- else {
439
- reject(error);
440
- }
441
- });
442
858
  }
443
859
  else {
860
+ started = true;
861
+ this._isOpening = false;
444
862
  this._isOpen = true;
445
863
  resolve();
446
864
  }
447
865
  });
448
866
  }
449
- write(data) {
450
- return new Promise((resolve, reject) => {
451
- if (this.isOpen) {
452
- this._socket.send(data, this._port, this._address, (error) => {
453
- if (error) {
454
- reject(error);
455
- }
456
- else {
457
- this.emit('write', data);
458
- resolve();
459
- }
867
+ _handleMessage(socket, msg, rinfo) {
868
+ if (this._socket !== socket) {
869
+ return;
870
+ }
871
+ if (!this.isServer) {
872
+ if (rinfo.port !== this._port) {
873
+ return;
874
+ }
875
+ if (this._address !== undefined && rinfo.address !== this._address) {
876
+ return;
877
+ }
878
+ }
879
+ const key = `${rinfo.address}:${rinfo.port}`;
880
+ let entry = this._connections.get(key);
881
+ if (!entry) {
882
+ entry = { conn: { id: genConnectionId('udp') } };
883
+ this._connections.set(key, entry);
884
+ }
885
+ if (this.isServer && this._idleTimeout > 0) {
886
+ if (entry.idleTimer) {
887
+ clearTimeout(entry.idleTimer);
888
+ }
889
+ const conn = entry.conn;
890
+ entry.idleTimer = setTimeout(() => {
891
+ const e = this._connections.get(key);
892
+ if (!e || e.conn !== conn) {
893
+ return;
894
+ }
895
+ this._connections.delete(key);
896
+ this.emit('connection-close', conn);
897
+ }, this._idleTimeout);
898
+ }
899
+ const conn = entry.conn;
900
+ const response = (data) => new Promise((resolve, reject) => {
901
+ socket.send(data, rinfo.port, rinfo.address, (error) => {
902
+ if (error) {
903
+ reject(error);
904
+ }
905
+ else {
906
+ resolve();
907
+ }
908
+ });
909
+ });
910
+ this.emit('data', msg, response, conn);
911
+ }
912
+ write(data) {
913
+ return new Promise((resolve, reject) => {
914
+ if (this._isOpen && this._socket) {
915
+ this._socket.send(data, this._port, this._address, (error) => {
916
+ if (error) {
917
+ reject(error);
918
+ }
919
+ else {
920
+ this.emit('write', data);
921
+ resolve();
922
+ }
460
923
  });
461
924
  }
462
925
  else {
463
- reject(new Error('Port is not open'));
926
+ reject(new ModbusError(ModbusErrorCode.PORT_NOT_OPEN, 'Port is not open'));
464
927
  }
465
928
  });
466
929
  }
467
930
  close() {
931
+ if (!this._socket) {
932
+ return Promise.resolve();
933
+ }
934
+ const socket = this._socket;
468
935
  return new Promise((resolve) => {
469
- this._isOpen = false;
470
- this._socket.removeAllListeners();
471
- this._socket.close(() => {
472
- resolve();
473
- });
936
+ socket.once('close', () => resolve());
937
+ try {
938
+ socket.close();
939
+ }
940
+ catch (_a) {
941
+ // Socket may already be closed; the registered 'close' listener still fires asynchronously.
942
+ }
474
943
  });
475
944
  }
476
945
  destroy() {
477
946
  this._destroyed = true;
478
- this.removeAllListeners();
479
- return this.close();
947
+ return this.close().then(() => {
948
+ this.removeAllListeners();
949
+ });
480
950
  }
481
951
  }
482
952
 
483
953
  class AbstractApplicationLayer extends EventEmitter {
484
- }
485
-
486
- function checkRange(value, range) {
487
- if (range) {
488
- if (typeof range[0] === 'number' && typeof range[1] === 'number') {
489
- if (range[0] < range[1]) {
490
- return (Array.isArray(value) ? value : [value]).every((n) => n >= range[0] && n <= range[1]);
491
- }
954
+ constructor() {
955
+ super(...arguments);
956
+ Object.defineProperty(this, "_role", {
957
+ enumerable: true,
958
+ configurable: true,
959
+ writable: true,
960
+ value: void 0
961
+ });
962
+ }
963
+ get role() {
964
+ if (!this._role) {
965
+ throw new ModbusError(ModbusErrorCode.INVALID_ROLE, 'Application layer role not set');
492
966
  }
493
- else if (range.length > 0) {
494
- for (const r of range) {
495
- if (r[0] < r[1]) {
496
- if ((Array.isArray(value) ? value : [value]).every((n) => n >= r[0] && n <= r[1])) {
497
- return true;
498
- }
499
- }
500
- }
501
- return false;
967
+ return this._role;
968
+ }
969
+ set role(value) {
970
+ if (this._role && this._role !== value) {
971
+ throw new ModbusError(ModbusErrorCode.INVALID_ROLE, `Application layer role already set to ${this._role}`);
502
972
  }
973
+ this._role = value;
503
974
  }
504
- return true;
505
- }
506
-
507
- const TABLE = [
508
- 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, 0xcc01,
509
- 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, 0xd801, 0x18c0,
510
- 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, 0x1400, 0xd4c1, 0xd581,
511
- 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641, 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040, 0xf001, 0x30c0, 0x3180, 0xf141,
512
- 0x3300, 0xf3c1, 0xf281, 0x3240, 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01,
513
- 0x3fc0, 0x3e80, 0xfe41, 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0,
514
- 0x2a80, 0xea41, 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40, 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681,
515
- 0x2640, 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041, 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240,
516
- 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441, 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, 0xaa01,
517
- 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, 0xbe01, 0x7ec0,
518
- 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640, 0x7200, 0xb2c1, 0xb381,
519
- 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041, 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241, 0x9601, 0x56c0, 0x5780, 0x9741,
520
- 0x5500, 0x95c1, 0x9481, 0x5440, 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901,
521
- 0x59c0, 0x5880, 0x9841, 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0,
522
- 0x4c80, 0x8c41, 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081,
523
- 0x4040,
524
- ];
525
- function crc(data) {
526
- let crc = 0xffff;
527
- for (let index = 0; index < data.length; index++) {
528
- crc = (TABLE[(crc ^ data[index]) & 0xff] ^ (crc >> 8)) & 0xffff;
975
+ flush() {
976
+ // no-op — override in subclasses
977
+ }
978
+ addCustomFunctionCode(cfc) {
979
+ }
980
+ removeCustomFunctionCode(fc) {
529
981
  }
530
- return crc;
531
- }
532
-
533
- /**
534
- * Get time interval between message frames witch well-known as 3.5T.
535
- * @param baudRate Serial port baud rate.
536
- * @param {number} [approximation=48] Approximate number of bits corresponding to 3.5T.
537
- * @returns `ms`.
538
- */
539
- function getThreePointFiveT(baudRate, approximation = 48) {
540
- return (approximation * 1000) / baudRate;
541
- }
542
-
543
- function lrc(data) {
544
- return (~data.reduce((sum, n) => sum + n, 0) + 1) & 0xff;
545
982
  }
546
983
 
547
984
  const MAX_FRAME_LENGTH = 256;
985
+ const MIN_FRAME_LENGTH = 4;
548
986
  class RtuApplicationLayer extends AbstractApplicationLayer {
549
- constructor(physicalLayer,
550
- /**
551
- * The time interval between two frames, support two formats:
552
- * - bit: `48bit` as default
553
- * - millisecond: `20ms`
554
- */
555
- intervalBetweenFrames) {
987
+ constructor(physicalLayer, options = {}) {
556
988
  super();
557
- Object.defineProperty(this, "_waitingResponse", {
989
+ Object.defineProperty(this, "PROTOCOL", {
558
990
  enumerable: true,
559
991
  configurable: true,
560
992
  writable: true,
561
- value: void 0
993
+ value: 'RTU'
562
994
  });
563
- Object.defineProperty(this, "_timerThreePointFive", {
995
+ Object.defineProperty(this, "_states", {
564
996
  enumerable: true,
565
997
  configurable: true,
566
998
  writable: true,
567
- value: void 0
999
+ value: new Map()
568
1000
  });
569
- Object.defineProperty(this, "_bufferRx", {
1001
+ Object.defineProperty(this, "_customFunctionCodes", {
570
1002
  enumerable: true,
571
1003
  configurable: true,
572
1004
  writable: true,
573
- value: Buffer.alloc(0)
1005
+ value: new Map()
574
1006
  });
575
1007
  Object.defineProperty(this, "_removeAllListeners", {
576
1008
  enumerable: true,
@@ -578,129 +1010,276 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
578
1010
  writable: true,
579
1011
  value: []
580
1012
  });
1013
+ Object.defineProperty(this, "_threePointFiveT", {
1014
+ enumerable: true,
1015
+ configurable: true,
1016
+ writable: true,
1017
+ value: void 0
1018
+ });
1019
+ Object.defineProperty(this, "_onePointFiveT", {
1020
+ enumerable: true,
1021
+ configurable: true,
1022
+ writable: true,
1023
+ value: void 0
1024
+ });
1025
+ const { intervalBetweenFrames, interCharTimeout } = options;
581
1026
  let threePointFiveT = 0;
1027
+ let onePointFiveT = 0;
582
1028
  if (physicalLayer.TYPE === 'SERIAL') {
583
- if (intervalBetweenFrames && intervalBetweenFrames.endsWith('ms')) {
584
- threePointFiveT = Number(intervalBetweenFrames.slice(0, -2));
1029
+ const baudRate = physicalLayer.baudRate;
1030
+ if (intervalBetweenFrames && intervalBetweenFrames.unit === 'ms') {
1031
+ threePointFiveT = intervalBetweenFrames.value;
585
1032
  }
586
1033
  else {
587
- threePointFiveT = Math.ceil(physicalLayer.baudRate > 19200
588
- ? 1.8
589
- : getThreePointFiveT(physicalLayer.baudRate, intervalBetweenFrames ? Number(intervalBetweenFrames.slice(0, -3)) : 48));
590
- }
591
- }
592
- const handleData = (data, response) => {
593
- this._bufferRx = Buffer.concat([this._bufferRx, data]);
594
- clearTimeout(this._timerThreePointFive);
595
- const handleData = () => {
596
- this.framing(this._bufferRx, (error, frame) => {
597
- if (this._waitingResponse) {
598
- if (error && error.message === 'Insufficient data length') {
599
- return;
600
- }
601
- this._waitingResponse.callback(error, frame);
602
- this._bufferRx = Buffer.alloc(0);
603
- }
604
- else {
605
- if (!error) {
606
- this.emit('framing', frame, response);
607
- }
608
- this._bufferRx = Buffer.alloc(0);
609
- }
610
- });
611
- };
612
- if (this._bufferRx.length >= MAX_FRAME_LENGTH) {
613
- handleData();
1034
+ threePointFiveT =
1035
+ baudRate > 19200 ? 1.75 : Math.ceil(bitsToMs(baudRate, intervalBetweenFrames ? intervalBetweenFrames.value : 38.5));
614
1036
  }
615
- else {
616
- if (threePointFiveT) {
617
- this._timerThreePointFive = setTimeout(handleData, threePointFiveT);
1037
+ if (interCharTimeout) {
1038
+ if (interCharTimeout.unit === 'ms') {
1039
+ onePointFiveT = interCharTimeout.value;
618
1040
  }
619
1041
  else {
620
- handleData();
1042
+ onePointFiveT = baudRate > 19200 ? 0.75 : Math.ceil(bitsToMs(baudRate, interCharTimeout.value));
621
1043
  }
622
1044
  }
1045
+ }
1046
+ this._threePointFiveT = threePointFiveT;
1047
+ this._onePointFiveT = onePointFiveT;
1048
+ const handleData = (data, response, connection) => {
1049
+ let state = this.getState(connection.id);
1050
+ if (state.t15Expired && state.end > state.start) {
1051
+ this.emit('framing-error', new ModbusError(ModbusErrorCode.T1_5_EXCEEDED, 'Inter-character timeout (t1.5) exceeded'));
1052
+ state.start = 0;
1053
+ state.end = 0;
1054
+ }
1055
+ state.t15Expired = false;
1056
+ let dataOffset = 0;
1057
+ while (dataOffset < data.length) {
1058
+ const space = state.pool.length - state.end;
1059
+ if (space === 0) {
1060
+ this.clearStateTimers(state);
1061
+ this.flushBuffer(connection.id, response, connection, this._threePointFiveT > 0);
1062
+ state = this.getState(connection.id);
1063
+ if (state.pool.length - state.end === 0) {
1064
+ this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_DATA, 'Frame buffer exhausted before complete frame received'));
1065
+ state.start = 0;
1066
+ state.end = 0;
1067
+ state.t15Expired = false;
1068
+ }
1069
+ continue;
1070
+ }
1071
+ const toCopy = Math.min(space, data.length - dataOffset);
1072
+ data.copy(state.pool, state.end, dataOffset, dataOffset + toCopy);
1073
+ state.end += toCopy;
1074
+ dataOffset += toCopy;
1075
+ }
1076
+ this.clearStateTimers(state);
1077
+ const commitFrame = () => {
1078
+ this.clearStateTimers(state);
1079
+ this.flushBuffer(connection.id, response, connection, this._threePointFiveT > 0);
1080
+ };
1081
+ if (state.end - state.start >= MAX_FRAME_LENGTH) {
1082
+ commitFrame();
1083
+ }
1084
+ else if (this._threePointFiveT) {
1085
+ if (this._onePointFiveT > 0) {
1086
+ state.interCharTimer = setTimeout(() => {
1087
+ state.interCharTimer = undefined;
1088
+ state.t15Expired = true;
1089
+ }, this._onePointFiveT);
1090
+ }
1091
+ state.timer = setTimeout(commitFrame, this._threePointFiveT);
1092
+ }
1093
+ else {
1094
+ commitFrame();
1095
+ }
623
1096
  };
624
1097
  physicalLayer.on('data', handleData);
625
- this._removeAllListeners.push(() => {
626
- physicalLayer.removeListener('data', handleData);
627
- });
1098
+ this._removeAllListeners.push(() => physicalLayer.removeListener('data', handleData));
1099
+ const handleConnectionClose = (connection) => {
1100
+ this.clearState(connection.id);
1101
+ };
1102
+ physicalLayer.on('connection-close', handleConnectionClose);
1103
+ this._removeAllListeners.push(() => physicalLayer.removeListener('connection-close', handleConnectionClose));
628
1104
  const handleClose = () => {
629
- clearTimeout(this._timerThreePointFive);
630
- this._bufferRx = Buffer.alloc(0);
1105
+ for (const key of [...this._states.keys()]) {
1106
+ this.clearState(key);
1107
+ }
631
1108
  };
632
1109
  physicalLayer.on('close', handleClose);
633
- this._removeAllListeners.push(() => {
634
- physicalLayer.removeListener('close', handleClose);
635
- });
1110
+ this._removeAllListeners.push(() => physicalLayer.removeListener('close', handleClose));
636
1111
  }
637
- framing(buffer, callback) {
638
- if (buffer.length >= 4) {
639
- const frame = {
640
- unit: buffer[0],
641
- fc: buffer[1],
642
- data: Array.from(buffer.subarray(2, buffer.length - 2)),
643
- buffer,
644
- };
645
- if (this._waitingResponse) {
646
- for (const check of this._waitingResponse.preCheck) {
647
- const res = check(frame);
648
- if (typeof res === 'undefined') {
649
- callback(new Error('Insufficient data length'));
650
- return;
651
- }
652
- if (typeof res === 'number') {
653
- if (frame.data.length < res) {
654
- callback(new Error('Insufficient data length'));
655
- return;
656
- }
657
- if (frame.data.length !== res) {
658
- callback(new Error('Invalid response'));
659
- return;
660
- }
661
- }
662
- if (!res) {
663
- callback(new Error('Invalid response'));
664
- return;
665
- }
1112
+ getState(key) {
1113
+ let state = this._states.get(key);
1114
+ if (!state) {
1115
+ state = { pool: Buffer.alloc(MAX_FRAME_LENGTH * 2), start: 0, end: 0 };
1116
+ this._states.set(key, state);
1117
+ }
1118
+ return state;
1119
+ }
1120
+ clearStateTimers(state) {
1121
+ if (state.timer) {
1122
+ clearTimeout(state.timer);
1123
+ state.timer = undefined;
1124
+ }
1125
+ if (state.interCharTimer) {
1126
+ clearTimeout(state.interCharTimer);
1127
+ state.interCharTimer = undefined;
1128
+ }
1129
+ }
1130
+ clearState(key) {
1131
+ const state = this._states.get(key);
1132
+ if (state) {
1133
+ this.clearStateTimers(state);
1134
+ }
1135
+ this._states.delete(key);
1136
+ }
1137
+ flush() {
1138
+ for (const state of this._states.values()) {
1139
+ this.clearStateTimers(state);
1140
+ state.start = 0;
1141
+ state.end = 0;
1142
+ }
1143
+ this._states.clear();
1144
+ }
1145
+ addCustomFunctionCode(cfc) {
1146
+ if (!isUint8(cfc.fc)) {
1147
+ throw new ModbusError(ModbusErrorCode.RANGE, `fc must be an integer in 0..255, got ${cfc.fc}`);
1148
+ }
1149
+ this._customFunctionCodes.set(cfc.fc, cfc);
1150
+ }
1151
+ removeCustomFunctionCode(fc) {
1152
+ this._customFunctionCodes.delete(fc);
1153
+ }
1154
+ flushBuffer(key, response, connection, strict) {
1155
+ const state = this._states.get(key);
1156
+ if (!state) {
1157
+ return;
1158
+ }
1159
+ while (state.end - state.start > 0) {
1160
+ const buffer = state.pool.subarray(state.start, state.end);
1161
+ const result = this.tryExtract(buffer);
1162
+ if (result.kind === 'frame') {
1163
+ state.start += result.frame.length;
1164
+ this.deliverFrame(result.frame, response, connection);
1165
+ }
1166
+ else if (result.kind === 'skip') {
1167
+ if (strict) {
1168
+ this.emit('framing-error', new ModbusError(ModbusErrorCode.CRC_MISMATCH, 'CRC mismatch'));
1169
+ state.start = 0;
1170
+ state.end = 0;
1171
+ state.t15Expired = false;
1172
+ return;
666
1173
  }
1174
+ state.start += 1;
667
1175
  }
668
- const crcPassed = buffer.readUInt16LE(buffer.length - 2) === crc(buffer.subarray(0, buffer.length - 2));
669
- if (crcPassed) {
670
- callback(null, frame);
1176
+ else if (result.kind === 'insufficient') {
1177
+ if (state.end - state.start >= MAX_FRAME_LENGTH) {
1178
+ state.start += 1;
1179
+ continue;
1180
+ }
1181
+ if (strict) {
1182
+ this.emit('framing-error', new ModbusError(state.t15Expired ? ModbusErrorCode.T1_5_EXCEEDED : ModbusErrorCode.INCOMPLETE_FRAME, state.t15Expired ? 'Inter-character timeout (t1.5) exceeded' : 'Incomplete frame at t3.5'));
1183
+ state.start = 0;
1184
+ state.end = 0;
1185
+ state.t15Expired = false;
1186
+ return;
1187
+ }
1188
+ if (state.t15Expired) {
1189
+ this.emit('framing-error', new ModbusError(ModbusErrorCode.T1_5_EXCEEDED, 'Inter-character timeout (t1.5) exceeded'));
1190
+ state.start = 0;
1191
+ state.end = 0;
1192
+ state.t15Expired = false;
1193
+ }
1194
+ break;
671
1195
  }
672
1196
  else {
673
- callback(new Error('CRC check failed'));
1197
+ this.emit('framing-error', result.error);
1198
+ state.start = 0;
1199
+ state.end = 0;
1200
+ return;
674
1201
  }
675
1202
  }
676
- else {
677
- callback(new Error('Insufficient data length'));
1203
+ if (state.start > 0) {
1204
+ if (state.start < state.end) {
1205
+ state.pool.copy(state.pool, 0, state.start, state.end);
1206
+ }
1207
+ state.end -= state.start;
1208
+ state.start = 0;
1209
+ }
1210
+ if (state.end === 0 && !state.timer) {
1211
+ this._states.delete(key);
678
1212
  }
679
1213
  }
680
- startWaitingResponse(preCheck, callback) {
681
- this._waitingResponse = { preCheck, callback };
682
- clearTimeout(this._timerThreePointFive);
683
- this._bufferRx = Buffer.alloc(0);
1214
+ tryExtract(buffer) {
1215
+ if (buffer.length < MIN_FRAME_LENGTH) {
1216
+ return { kind: 'insufficient' };
1217
+ }
1218
+ const isResponse = this.role === 'MASTER';
1219
+ const fc = buffer[1];
1220
+ const cfc = this._customFunctionCodes.get(fc);
1221
+ if (cfc) {
1222
+ const predictor = isResponse ? cfc.predictResponseLength : cfc.predictRequestLength;
1223
+ const expected = predictor(buffer);
1224
+ if (expected === null) {
1225
+ return { kind: 'insufficient' };
1226
+ }
1227
+ return this.checkExpected(buffer, expected);
1228
+ }
1229
+ const result = predictRtuFrameLength(buffer, isResponse);
1230
+ if (result.kind === 'length') {
1231
+ return this.checkExpected(buffer, result.length);
1232
+ }
1233
+ if (result.kind === 'need-more') {
1234
+ return { kind: 'insufficient' };
1235
+ }
1236
+ return {
1237
+ kind: 'error',
1238
+ error: new ModbusError(ModbusErrorCode.UNKNOWN_FC, `Unknown function code 0x${fc.toString(16).padStart(2, '0')} — register a CustomFunctionCode to frame this FC`),
1239
+ };
684
1240
  }
685
- stopWaitingResponse() {
686
- this._waitingResponse = undefined;
1241
+ checkExpected(buffer, expected) {
1242
+ if (expected > MAX_FRAME_LENGTH || expected < MIN_FRAME_LENGTH) {
1243
+ return { kind: 'error', error: new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data') };
1244
+ }
1245
+ if (buffer.length < expected) {
1246
+ return { kind: 'insufficient' };
1247
+ }
1248
+ if (this.crcMatches(buffer, expected)) {
1249
+ return { kind: 'frame', frame: buffer.subarray(0, expected), rest: buffer.subarray(expected) };
1250
+ }
1251
+ return { kind: 'skip', rest: buffer.subarray(1) };
1252
+ }
1253
+ crcMatches(buffer, length) {
1254
+ return buffer.readUInt16LE(length - 2) === crc(buffer.subarray(0, length - 2));
1255
+ }
1256
+ deliverFrame(buffer, response, connection) {
1257
+ const frameBuf = Buffer.from(buffer);
1258
+ const frame = {
1259
+ unit: frameBuf[0],
1260
+ fc: frameBuf[1],
1261
+ data: frameBuf.subarray(2, frameBuf.length - 2),
1262
+ buffer: frameBuf,
1263
+ };
1264
+ this.emit('framing', frame, response, connection);
687
1265
  }
688
1266
  encode(data) {
689
1267
  const buffer = Buffer.alloc(data.data.length + 4);
690
1268
  buffer.writeUInt8(data.unit, 0);
691
1269
  buffer.writeUInt8(data.fc, 1);
692
- data.data.forEach((num, index) => {
693
- buffer.writeUInt8(num, 2 + index);
694
- });
1270
+ buffer.set(data.data, 2);
695
1271
  buffer.writeUInt16LE(crc(buffer.subarray(0, -2)), buffer.length - 2);
696
1272
  return buffer;
697
1273
  }
698
1274
  destroy() {
699
1275
  this.removeAllListeners();
700
- for (const removeAllListener of this._removeAllListeners) {
701
- removeAllListener();
1276
+ for (const removeListener of this._removeAllListeners) {
1277
+ removeListener();
702
1278
  }
703
- clearTimeout(this._timerThreePointFive);
1279
+ for (const state of this._states.values()) {
1280
+ this.clearStateTimers(state);
1281
+ }
1282
+ this._states.clear();
704
1283
  }
705
1284
  }
706
1285
 
@@ -709,26 +1288,43 @@ const CHAR_CODE = {
709
1288
  CR: '\r'.charCodeAt(0),
710
1289
  LF: '\n'.charCodeAt(0),
711
1290
  };
1291
+ // Modbus ASCII frame encodes at most 256 PDU bytes as 512 hex chars between
1292
+ // `:` and `\r`. Cap per-connection buffering so a peer that never sends `\r`
1293
+ // cannot grow `state.frame` without bound.
1294
+ const MAX_ASCII_PAYLOAD = 512;
1295
+ const HEX_DECODE = new Uint8Array(256);
1296
+ HEX_DECODE.fill(0xff);
1297
+ for (let i = 0x30; i <= 0x39; i++) {
1298
+ HEX_DECODE[i] = i - 0x30;
1299
+ }
1300
+ for (let i = 0x41; i <= 0x46; i++) {
1301
+ HEX_DECODE[i] = i - 0x41 + 10;
1302
+ }
1303
+ for (let i = 0x61; i <= 0x66; i++) {
1304
+ HEX_DECODE[i] = i - 0x61 + 10;
1305
+ }
1306
+ const HEX_ENCODE = new Uint8Array('0123456789ABCDEF'.split('').map((c) => c.charCodeAt(0)));
712
1307
  class AsciiApplicationLayer extends AbstractApplicationLayer {
713
- constructor(physicalLayer) {
1308
+ constructor(physicalLayer, options = {}) {
1309
+ var _a;
714
1310
  super();
715
- Object.defineProperty(this, "_waitingResponse", {
1311
+ Object.defineProperty(this, "PROTOCOL", {
716
1312
  enumerable: true,
717
1313
  configurable: true,
718
1314
  writable: true,
719
- value: void 0
1315
+ value: 'ASCII'
720
1316
  });
721
- Object.defineProperty(this, "_status", {
1317
+ Object.defineProperty(this, "lenientHex", {
722
1318
  enumerable: true,
723
1319
  configurable: true,
724
1320
  writable: true,
725
- value: 'idle'
1321
+ value: void 0
726
1322
  });
727
- Object.defineProperty(this, "_frame", {
1323
+ Object.defineProperty(this, "_states", {
728
1324
  enumerable: true,
729
1325
  configurable: true,
730
1326
  writable: true,
731
- value: []
1327
+ value: new Map()
732
1328
  });
733
1329
  Object.defineProperty(this, "_removeAllListeners", {
734
1330
  enumerable: true,
@@ -736,44 +1332,62 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
736
1332
  writable: true,
737
1333
  value: []
738
1334
  });
739
- const handleData = (data, response) => {
1335
+ this.lenientHex = (_a = options.lenientHex) !== null && _a !== void 0 ? _a : false;
1336
+ const lenientHex = this.lenientHex;
1337
+ const isHexChar = (value) => {
1338
+ if (value >= 0x30 && value <= 0x39) {
1339
+ return true;
1340
+ }
1341
+ if (value >= 0x41 && value <= 0x46) {
1342
+ return true;
1343
+ }
1344
+ if (lenientHex && value >= 0x61 && value <= 0x66) {
1345
+ return true;
1346
+ }
1347
+ return false;
1348
+ };
1349
+ const handleData = (data, response, connection) => {
1350
+ const state = this.getState(connection.id);
740
1351
  data.forEach((value) => {
741
- switch (this._status) {
1352
+ switch (state.status) {
742
1353
  case 'idle': {
743
1354
  if (value === CHAR_CODE.COLON) {
744
- this._status = 'reception';
745
- this._frame = [];
1355
+ state.status = 'reception';
1356
+ state.frame = [];
746
1357
  }
747
1358
  break;
748
1359
  }
749
1360
  case 'reception': {
750
1361
  if (value === CHAR_CODE.COLON) {
751
- this._frame = [];
1362
+ state.frame = [];
752
1363
  }
753
1364
  else if (value === CHAR_CODE.CR) {
754
- this._status = 'waiting end';
1365
+ state.status = 'waiting end';
1366
+ }
1367
+ else if (state.frame.length >= MAX_ASCII_PAYLOAD) {
1368
+ state.status = 'idle';
1369
+ state.frame = [];
1370
+ this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data'));
1371
+ }
1372
+ else if (!isHexChar(value)) {
1373
+ state.status = 'idle';
1374
+ state.frame = [];
1375
+ this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_HEX, 'Invalid hex character'));
755
1376
  }
756
1377
  else {
757
- this._frame.push(value);
1378
+ state.frame.push(value);
758
1379
  }
759
1380
  break;
760
1381
  }
761
1382
  case 'waiting end': {
762
1383
  if (value === CHAR_CODE.COLON) {
763
- this._status = 'reception';
764
- this._frame = [];
1384
+ state.status = 'reception';
1385
+ state.frame = [];
765
1386
  }
766
1387
  else {
767
- this._status = 'idle';
1388
+ state.status = 'idle';
768
1389
  if (value === CHAR_CODE.LF) {
769
- this.framing(Buffer.from(this._frame), (error, frame) => {
770
- if (this._waitingResponse) {
771
- this._waitingResponse.callback(error, frame);
772
- }
773
- else if (!error) {
774
- this.emit('framing', frame, response);
775
- }
776
- });
1390
+ this.framing(Buffer.from(state.frame), response, connection);
777
1391
  }
778
1392
  }
779
1393
  break;
@@ -782,115 +1396,97 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
782
1396
  });
783
1397
  };
784
1398
  physicalLayer.on('data', handleData);
785
- this._removeAllListeners.push(() => {
786
- physicalLayer.removeListener('data', handleData);
787
- });
1399
+ this._removeAllListeners.push(() => physicalLayer.removeListener('data', handleData));
1400
+ const handleConnectionClose = (connection) => {
1401
+ this._states.delete(connection.id);
1402
+ };
1403
+ physicalLayer.on('connection-close', handleConnectionClose);
1404
+ this._removeAllListeners.push(() => physicalLayer.removeListener('connection-close', handleConnectionClose));
788
1405
  const handleClose = () => {
789
- this._status = 'idle';
790
- this._frame = [];
1406
+ this._states.clear();
791
1407
  };
792
1408
  physicalLayer.on('close', handleClose);
793
- this._removeAllListeners.push(() => {
794
- physicalLayer.removeListener('close', handleClose);
795
- });
796
- }
797
- framing(_buffer, callback) {
798
- if (_buffer.length >= 6) {
799
- if (_buffer.length % 2 === 0) {
800
- const ascii = [];
801
- let num = '';
802
- for (const value of _buffer) {
803
- num += String.fromCharCode(value);
804
- if (num.length === 2) {
805
- ascii.push(Number('0x' + num));
806
- num = '';
807
- }
808
- }
809
- const buffer = Buffer.from(ascii);
810
- const frame = {
811
- unit: buffer[0],
812
- fc: buffer[1],
813
- data: Array.from(buffer.subarray(2, buffer.length - 1)),
814
- buffer: _buffer,
815
- };
816
- if (this._waitingResponse) {
817
- for (const check of this._waitingResponse.preCheck) {
818
- const res = check(frame);
819
- if (typeof res === 'undefined') {
820
- callback(new Error('Insufficient data length'));
821
- return;
822
- }
823
- if (typeof res === 'number') {
824
- if (frame.data.length < res) {
825
- callback(new Error('Insufficient data length'));
826
- return;
827
- }
828
- if (frame.data.length !== res) {
829
- callback(new Error('Invalid response'));
830
- return;
831
- }
832
- }
833
- if (!res) {
834
- callback(new Error('Invalid response'));
835
- return;
836
- }
837
- }
838
- }
839
- const lrcPassed = buffer[buffer.length - 1] === lrc(buffer.subarray(0, buffer.length - 1));
840
- if (lrcPassed) {
841
- callback(null, frame);
842
- }
843
- else {
844
- callback(new Error('LRC check failed'));
845
- }
846
- }
847
- else {
848
- callback(new Error('Invalid data'));
849
- }
850
- }
851
- else {
852
- callback(new Error('Insufficient data length'));
1409
+ this._removeAllListeners.push(() => physicalLayer.removeListener('close', handleClose));
1410
+ }
1411
+ getState(key) {
1412
+ let state = this._states.get(key);
1413
+ if (!state) {
1414
+ state = { status: 'idle', frame: [] };
1415
+ this._states.set(key, state);
853
1416
  }
1417
+ return state;
854
1418
  }
855
- startWaitingResponse(preCheck, callback) {
856
- this._waitingResponse = { preCheck, callback };
857
- this._status = 'idle';
858
- this._frame = [];
1419
+ flush() {
1420
+ this._states.clear();
859
1421
  }
860
- stopWaitingResponse() {
861
- this._waitingResponse = undefined;
1422
+ framing(_buffer, response, connection) {
1423
+ if (_buffer.length < 6) {
1424
+ this.emit('framing-error', new ModbusError(ModbusErrorCode.INSUFFICIENT_DATA, 'Insufficient data length'));
1425
+ return;
1426
+ }
1427
+ if (_buffer.length % 2 !== 0) {
1428
+ this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data'));
1429
+ return;
1430
+ }
1431
+ const buffer = Buffer.allocUnsafe(_buffer.length / 2);
1432
+ for (let i = 0; i < _buffer.length; i += 2) {
1433
+ const hi = HEX_DECODE[_buffer[i]];
1434
+ const lo = HEX_DECODE[_buffer[i + 1]];
1435
+ if (hi === 0xff || lo === 0xff) {
1436
+ this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_HEX, 'Invalid hex character'));
1437
+ return;
1438
+ }
1439
+ buffer[i / 2] = (hi << 4) | lo;
1440
+ }
1441
+ const frame = {
1442
+ unit: buffer[0],
1443
+ fc: buffer[1],
1444
+ data: buffer.subarray(2, buffer.length - 1),
1445
+ buffer: _buffer,
1446
+ };
1447
+ const lrcPassed = buffer[buffer.length - 1] === lrc(buffer.subarray(0, buffer.length - 1));
1448
+ if (!lrcPassed) {
1449
+ this.emit('framing-error', new ModbusError(ModbusErrorCode.LRC_MISMATCH, 'LRC check failed'));
1450
+ return;
1451
+ }
1452
+ this.emit('framing', frame, response, connection);
862
1453
  }
863
1454
  encode(data) {
864
1455
  const buffer = Buffer.alloc(data.data.length + 3);
865
1456
  buffer.writeUInt8(data.unit, 0);
866
1457
  buffer.writeUInt8(data.fc, 1);
867
- data.data.forEach((num, index) => {
868
- buffer.writeUInt8(num, 2 + index);
869
- });
1458
+ buffer.set(data.data, 2);
870
1459
  buffer.writeUInt8(lrc(buffer.subarray(0, -1)), buffer.length - 1);
871
- let frame = ':';
872
- for (const value of buffer) {
873
- frame += value.toString(16).toUpperCase().padStart(2, '0');
1460
+ const out = Buffer.allocUnsafe(1 + buffer.length * 2 + 2);
1461
+ out[0] = CHAR_CODE.COLON;
1462
+ for (let i = 0; i < buffer.length; i++) {
1463
+ const byte = buffer[i];
1464
+ out[1 + i * 2] = HEX_ENCODE[byte >> 4];
1465
+ out[2 + i * 2] = HEX_ENCODE[byte & 0x0f];
874
1466
  }
875
- frame += '\r\n';
876
- return Buffer.from(frame);
1467
+ out[out.length - 2] = CHAR_CODE.CR;
1468
+ out[out.length - 1] = CHAR_CODE.LF;
1469
+ return out;
877
1470
  }
878
1471
  destroy() {
879
1472
  this.removeAllListeners();
880
- for (const removeAllListener of this._removeAllListeners) {
881
- removeAllListener();
1473
+ for (const removeListener of this._removeAllListeners) {
1474
+ removeListener();
882
1475
  }
1476
+ this._states.clear();
883
1477
  }
884
1478
  }
885
1479
 
1480
+ const MAX_TCP_FRAME = 260;
1481
+ const EMPTY_BUFFER = Buffer.alloc(0);
886
1482
  class TcpApplicationLayer extends AbstractApplicationLayer {
887
1483
  constructor(physicalLayer) {
888
1484
  super();
889
- Object.defineProperty(this, "_waitingResponse", {
1485
+ Object.defineProperty(this, "PROTOCOL", {
890
1486
  enumerable: true,
891
1487
  configurable: true,
892
1488
  writable: true,
893
- value: void 0
1489
+ value: 'TCP'
894
1490
  });
895
1491
  Object.defineProperty(this, "_transactionId", {
896
1492
  enumerable: true,
@@ -898,98 +1494,209 @@ class TcpApplicationLayer extends AbstractApplicationLayer {
898
1494
  writable: true,
899
1495
  value: 1
900
1496
  });
1497
+ Object.defineProperty(this, "_buffers", {
1498
+ enumerable: true,
1499
+ configurable: true,
1500
+ writable: true,
1501
+ value: new Map()
1502
+ });
901
1503
  Object.defineProperty(this, "_removeAllListeners", {
902
1504
  enumerable: true,
903
1505
  configurable: true,
904
1506
  writable: true,
905
1507
  value: []
906
1508
  });
907
- const handleData = (data, response) => {
908
- this.framing(data, (error, frame) => {
909
- if (this._waitingResponse) {
910
- this._waitingResponse.callback(error, frame);
911
- }
912
- else if (!error) {
913
- this.emit('framing', frame, response);
1509
+ const handleData = (data, response, connection) => {
1510
+ var _a;
1511
+ let buffer = (_a = this._buffers.get(connection.id)) !== null && _a !== void 0 ? _a : EMPTY_BUFFER;
1512
+ buffer = buffer.length === 0 ? data : Buffer.concat([buffer, data]);
1513
+ while (buffer.length > 0) {
1514
+ const result = this.tryExtract(buffer);
1515
+ if (result.kind === 'frame') {
1516
+ this.processFrame(result.frame, response, connection);
1517
+ buffer = result.rest;
1518
+ }
1519
+ else if (result.kind === 'insufficient') {
1520
+ break;
914
1521
  }
915
- });
916
- };
917
- physicalLayer.on('data', handleData);
918
- this._removeAllListeners.push(() => {
919
- physicalLayer.removeListener('data', handleData);
920
- });
921
- }
922
- framing(buffer, callback) {
923
- if (buffer.length >= 8) {
924
- if (buffer[2] === 0 && buffer[3] === 0 && buffer.readUInt16BE(4) === buffer.length - 6) {
925
- const frame = {
926
- transaction: buffer.readUInt16BE(0),
927
- unit: buffer[6],
928
- fc: buffer[7],
929
- data: Array.from(buffer.subarray(8)),
930
- buffer,
931
- };
932
- if (this._waitingResponse) {
933
- for (const check of this._waitingResponse.preCheck) {
934
- const res = check(frame);
935
- if (typeof res === 'undefined') {
936
- callback(new Error('Insufficient data length'));
937
- return;
938
- }
939
- if (typeof res === 'number') {
940
- if (frame.data.length < res) {
941
- callback(new Error('Insufficient data length'));
942
- return;
943
- }
944
- if (frame.data.length !== res) {
945
- callback(new Error('Invalid response'));
946
- return;
947
- }
948
- }
949
- if (!res) {
950
- callback(new Error('Invalid response'));
951
- return;
952
- }
953
- }
1522
+ else {
1523
+ this.emit('framing-error', result.error);
1524
+ buffer = EMPTY_BUFFER;
1525
+ break;
954
1526
  }
955
- callback(null, frame);
1527
+ }
1528
+ if (buffer.length === 0) {
1529
+ this._buffers.delete(connection.id);
956
1530
  }
957
1531
  else {
958
- callback(new Error('Invalid data'));
1532
+ this._buffers.set(connection.id, buffer);
959
1533
  }
1534
+ };
1535
+ physicalLayer.on('data', handleData);
1536
+ this._removeAllListeners.push(() => physicalLayer.removeListener('data', handleData));
1537
+ const handleConnectionClose = (connection) => {
1538
+ this._buffers.delete(connection.id);
1539
+ };
1540
+ physicalLayer.on('connection-close', handleConnectionClose);
1541
+ this._removeAllListeners.push(() => physicalLayer.removeListener('connection-close', handleConnectionClose));
1542
+ const handleClose = () => {
1543
+ this._buffers.clear();
1544
+ };
1545
+ physicalLayer.on('close', handleClose);
1546
+ this._removeAllListeners.push(() => physicalLayer.removeListener('close', handleClose));
1547
+ }
1548
+ tryExtract(buffer) {
1549
+ if (buffer.length < 8) {
1550
+ return { kind: 'insufficient' };
1551
+ }
1552
+ if (buffer[2] !== 0 || buffer[3] !== 0) {
1553
+ return { kind: 'error', error: new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data') };
1554
+ }
1555
+ const length = buffer.readUInt16BE(4);
1556
+ const total = 6 + length;
1557
+ if (total > MAX_TCP_FRAME || length < 2) {
1558
+ return { kind: 'error', error: new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data') };
1559
+ }
1560
+ if (buffer.length < total) {
1561
+ return { kind: 'insufficient' };
1562
+ }
1563
+ return { kind: 'frame', frame: buffer.subarray(0, total), rest: buffer.subarray(total) };
1564
+ }
1565
+ processFrame(buffer, response, connection) {
1566
+ const frame = {
1567
+ transaction: buffer.readUInt16BE(0),
1568
+ unit: buffer[6],
1569
+ fc: buffer[7],
1570
+ data: buffer.subarray(8),
1571
+ buffer,
1572
+ };
1573
+ this.emit('framing', frame, response, connection);
1574
+ }
1575
+ encode(data) {
1576
+ var _a;
1577
+ const transaction = (_a = data.transaction) !== null && _a !== void 0 ? _a : this._transactionId;
1578
+ const buffer = Buffer.alloc(data.data.length + 8);
1579
+ buffer.writeUInt16BE(transaction, 0);
1580
+ buffer.writeUInt16BE(0, 2);
1581
+ buffer.writeUInt16BE(data.data.length + 2, 4);
1582
+ buffer.writeUInt8(data.unit, 6);
1583
+ buffer.writeUInt8(data.fc, 7);
1584
+ buffer.set(data.data, 8);
1585
+ if (data.transaction === undefined) {
1586
+ this._transactionId = (this._transactionId + 1) % 65536 || 1;
960
1587
  }
961
- else {
962
- callback(new Error('Insufficient data length'));
1588
+ return buffer;
1589
+ }
1590
+ destroy() {
1591
+ this.removeAllListeners();
1592
+ for (const removeListener of this._removeAllListeners) {
1593
+ removeListener();
963
1594
  }
1595
+ this._buffers.clear();
964
1596
  }
965
- startWaitingResponse(preCheck, callback) {
966
- this._waitingResponse = { preCheck, callback };
1597
+ }
1598
+
1599
+ const FIFO_KEY = 'fifo';
1600
+ class MasterSession {
1601
+ constructor() {
1602
+ Object.defineProperty(this, "_waiters", {
1603
+ enumerable: true,
1604
+ configurable: true,
1605
+ writable: true,
1606
+ value: new Map()
1607
+ });
1608
+ }
1609
+ start(key, preCheck, callback) {
1610
+ this._waiters.set(key, { preCheck, callback });
1611
+ }
1612
+ stop(key) {
1613
+ this._waiters.delete(key);
1614
+ }
1615
+ stopAll(error) {
1616
+ const waiters = [...this._waiters.values()];
1617
+ this._waiters.clear();
1618
+ for (const w of waiters) {
1619
+ w.callback(error);
1620
+ }
967
1621
  }
968
- stopWaitingResponse() {
969
- this._waitingResponse = undefined;
1622
+ has(key) {
1623
+ return this._waiters.has(key);
970
1624
  }
971
- encode(data) {
1625
+ handleFrame(frame) {
972
1626
  var _a;
973
- const buffer = Buffer.alloc(data.data.length + 8);
974
- buffer.writeUInt16BE((_a = data.transaction) !== null && _a !== void 0 ? _a : this._transactionId, 0);
975
- buffer.writeUInt16BE(0, 2);
976
- buffer.writeUInt16BE(data.data.length + 2, 4);
977
- buffer.writeUInt8(data.unit, 6);
978
- buffer.writeUInt8(data.fc, 7);
979
- data.data.forEach((num, index) => {
980
- buffer.writeUInt8(num, 8 + index);
981
- });
982
- this._transactionId = (this._transactionId + 1) % 256 || 1;
983
- return buffer;
984
- }
985
- destroy() {
986
- this.removeAllListeners();
987
- for (const removeAllListener of this._removeAllListeners) {
988
- removeAllListener();
1627
+ const key = (_a = frame.transaction) !== null && _a !== void 0 ? _a : FIFO_KEY;
1628
+ const waiter = this._waiters.get(key);
1629
+ if (!waiter) {
1630
+ return;
1631
+ }
1632
+ if (!this.runPreChecks(waiter, frame)) {
1633
+ return;
1634
+ }
1635
+ waiter.callback(null, frame);
1636
+ }
1637
+ handleError(error) {
1638
+ // Framing errors lose transaction context; reject every in-flight waiter.
1639
+ this.stopAll(error);
1640
+ }
1641
+ runPreChecks(waiter, frame) {
1642
+ for (const check of waiter.preCheck) {
1643
+ const res = check(frame);
1644
+ if (typeof res === 'undefined') {
1645
+ waiter.callback(new ModbusError(ModbusErrorCode.INSUFFICIENT_DATA, 'Insufficient data length'));
1646
+ return false;
1647
+ }
1648
+ if (typeof res === 'number') {
1649
+ if (frame.data.length < res) {
1650
+ waiter.callback(new ModbusError(ModbusErrorCode.INSUFFICIENT_DATA, 'Insufficient data length'));
1651
+ return false;
1652
+ }
1653
+ if (frame.data.length !== res) {
1654
+ waiter.callback(new ModbusError(ModbusErrorCode.INVALID_RESPONSE, 'Invalid response'));
1655
+ return false;
1656
+ }
1657
+ continue;
1658
+ }
1659
+ if (!res) {
1660
+ waiter.callback(new ModbusError(ModbusErrorCode.INVALID_RESPONSE, 'Invalid response'));
1661
+ return false;
1662
+ }
989
1663
  }
1664
+ return true;
990
1665
  }
991
1666
  }
992
1667
 
1668
+ /******************************************************************************
1669
+ Copyright (c) Microsoft Corporation.
1670
+
1671
+ Permission to use, copy, modify, and/or distribute this software for any
1672
+ purpose with or without fee is hereby granted.
1673
+
1674
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
1675
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
1676
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
1677
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
1678
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
1679
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
1680
+ PERFORMANCE OF THIS SOFTWARE.
1681
+ ***************************************************************************** */
1682
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
1683
+
1684
+
1685
+ function __awaiter(thisArg, _arguments, P, generator) {
1686
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
1687
+ return new (P || (P = Promise))(function (resolve, reject) {
1688
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
1689
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
1690
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
1691
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
1692
+ });
1693
+ }
1694
+
1695
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
1696
+ var e = new Error(message);
1697
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
1698
+ };
1699
+
993
1700
  class ModbusMaster extends EventEmitter {
994
1701
  get isOpen() {
995
1702
  return this.physicalLayer.isOpen;
@@ -997,7 +1704,8 @@ class ModbusMaster extends EventEmitter {
997
1704
  get destroyed() {
998
1705
  return this.physicalLayer.destroyed;
999
1706
  }
1000
- constructor(applicationLayer, physicalLayer, timeout = 1000) {
1707
+ constructor(applicationLayer, physicalLayer, options = {}) {
1708
+ var _a, _b;
1001
1709
  super();
1002
1710
  Object.defineProperty(this, "applicationLayer", {
1003
1711
  enumerable: true,
@@ -1011,11 +1719,47 @@ class ModbusMaster extends EventEmitter {
1011
1719
  writable: true,
1012
1720
  value: physicalLayer
1013
1721
  });
1722
+ Object.defineProperty(this, "_masterSession", {
1723
+ enumerable: true,
1724
+ configurable: true,
1725
+ writable: true,
1726
+ value: new MasterSession()
1727
+ });
1728
+ Object.defineProperty(this, "_queue", {
1729
+ enumerable: true,
1730
+ configurable: true,
1731
+ writable: true,
1732
+ value: []
1733
+ });
1734
+ Object.defineProperty(this, "_draining", {
1735
+ enumerable: true,
1736
+ configurable: true,
1737
+ writable: true,
1738
+ value: false
1739
+ });
1740
+ Object.defineProperty(this, "_nextTid", {
1741
+ enumerable: true,
1742
+ configurable: true,
1743
+ writable: true,
1744
+ value: 1
1745
+ });
1746
+ Object.defineProperty(this, "_closed", {
1747
+ enumerable: true,
1748
+ configurable: true,
1749
+ writable: true,
1750
+ value: false
1751
+ });
1014
1752
  Object.defineProperty(this, "timeout", {
1015
1753
  enumerable: true,
1016
1754
  configurable: true,
1017
1755
  writable: true,
1018
- value: timeout
1756
+ value: void 0
1757
+ });
1758
+ Object.defineProperty(this, "concurrent", {
1759
+ enumerable: true,
1760
+ configurable: true,
1761
+ writable: true,
1762
+ value: void 0
1019
1763
  });
1020
1764
  Object.defineProperty(this, "writeFC1", {
1021
1765
  enumerable: true,
@@ -1089,6 +1833,12 @@ class ModbusMaster extends EventEmitter {
1089
1833
  writable: true,
1090
1834
  value: void 0
1091
1835
  });
1836
+ this.timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : 1000;
1837
+ this.concurrent = (_b = options.concurrent) !== null && _b !== void 0 ? _b : false;
1838
+ if (this.concurrent && this.applicationLayer.PROTOCOL !== 'TCP') {
1839
+ throw new ModbusError(ModbusErrorCode.CONCURRENT_NOT_TCP, 'concurrent mode requires a Modbus TCP application layer');
1840
+ }
1841
+ this.applicationLayer.role = 'MASTER';
1092
1842
  this.writeFC1 = this.readCoils;
1093
1843
  this.writeFC2 = this.readDiscreteInputs;
1094
1844
  this.writeFC3 = this.readHoldingRegisters;
@@ -1101,6 +1851,12 @@ class ModbusMaster extends EventEmitter {
1101
1851
  this.handleFC22 = this.maskWriteRegister;
1102
1852
  this.handleFC23 = this.readAndWriteMultipleRegisters;
1103
1853
  this.handleFC43_14 = this.readDeviceIdentification;
1854
+ applicationLayer.on('framing', (frame) => {
1855
+ this._masterSession.handleFrame(frame);
1856
+ });
1857
+ applicationLayer.on('framing-error', (error) => {
1858
+ this._masterSession.handleError(error);
1859
+ });
1104
1860
  physicalLayer.on('error', (error) => {
1105
1861
  this.emit('error', error);
1106
1862
  });
@@ -1108,50 +1864,90 @@ class ModbusMaster extends EventEmitter {
1108
1864
  this.emit('close');
1109
1865
  });
1110
1866
  }
1111
- waitResponse(request, response, timeout) {
1867
+ send(adu, preCheck, timeout, broadcast) {
1868
+ const task = () => this._exchange(adu, preCheck, timeout, broadcast);
1869
+ if (this.concurrent) {
1870
+ return task();
1871
+ }
1872
+ return new Promise((resolve, reject) => {
1873
+ const run = () => __awaiter(this, void 0, void 0, function* () {
1874
+ try {
1875
+ resolve(yield task());
1876
+ }
1877
+ catch (error) {
1878
+ reject(error);
1879
+ }
1880
+ });
1881
+ this._queue.push({ run, cancel: reject });
1882
+ this._drain();
1883
+ });
1884
+ }
1885
+ _drain() {
1886
+ return __awaiter(this, void 0, void 0, function* () {
1887
+ if (this._draining) {
1888
+ return;
1889
+ }
1890
+ this._draining = true;
1891
+ try {
1892
+ while (this._queue.length > 0) {
1893
+ const item = this._queue.shift();
1894
+ yield item.run();
1895
+ }
1896
+ }
1897
+ finally {
1898
+ this._draining = false;
1899
+ }
1900
+ });
1901
+ }
1902
+ _exchange(adu, preCheck, timeout, broadcast) {
1112
1903
  return new Promise((resolve, reject) => {
1904
+ if (this._closed) {
1905
+ reject(new ModbusError(ModbusErrorCode.MASTER_CLOSED, 'Master closed'));
1906
+ return;
1907
+ }
1908
+ const usesTid = this.applicationLayer.PROTOCOL === 'TCP' && !broadcast;
1909
+ let tid;
1910
+ if (usesTid) {
1911
+ tid = this._nextTid;
1912
+ this._nextTid = (this._nextTid + 1) % 65536 || 1;
1913
+ }
1914
+ const key = tid !== null && tid !== void 0 ? tid : FIFO_KEY;
1915
+ const payload = this.applicationLayer.encode(Object.assign(Object.assign({}, adu), { transaction: tid }));
1113
1916
  let settled = false;
1114
- const tid = setTimeout(() => {
1115
- if (!settled) {
1116
- settled = true;
1117
- this.applicationLayer.stopWaitingResponse();
1118
- reject(new Error('Timeout'));
1917
+ const settle = (error, frame) => {
1918
+ if (settled) {
1919
+ return;
1920
+ }
1921
+ settled = true;
1922
+ clearTimeout(timer);
1923
+ this._masterSession.stop(key);
1924
+ if (error) {
1925
+ reject(error);
1926
+ }
1927
+ else {
1928
+ resolve(frame);
1119
1929
  }
1120
- }, timeout);
1930
+ };
1931
+ const timer = setTimeout(() => settle(new ModbusError(ModbusErrorCode.TIMEOUT, 'Timeout')), timeout);
1932
+ // FIFO mode: clear stale buffer state before this request.
1933
+ // Concurrent mode: must not flush — other in-flight requests share the buffer.
1934
+ if (!this.concurrent) {
1935
+ this.applicationLayer.flush();
1936
+ }
1121
1937
  this.physicalLayer
1122
- .write(request.data)
1938
+ .write(payload)
1123
1939
  .then(() => {
1124
- if (!settled) {
1125
- if (request.broadcast) {
1126
- settled = true;
1127
- clearTimeout(tid);
1128
- resolve();
1129
- }
1130
- else {
1131
- this.applicationLayer.startWaitingResponse(response.preCheck, (error, frame) => {
1132
- if (!settled) {
1133
- settled = true;
1134
- clearTimeout(tid);
1135
- this.applicationLayer.stopWaitingResponse();
1136
- if (error) {
1137
- reject(error);
1138
- }
1139
- else {
1140
- resolve(frame);
1141
- }
1142
- }
1143
- });
1144
- }
1940
+ if (settled) {
1941
+ return;
1145
1942
  }
1146
- })
1147
- .catch((error) => {
1148
- if (!settled) {
1149
- settled = true;
1150
- clearTimeout(tid);
1151
- this.applicationLayer.stopWaitingResponse();
1152
- reject(error);
1943
+ if (broadcast) {
1944
+ settle(null);
1945
+ return;
1153
1946
  }
1154
- });
1947
+ const finalChecks = usesTid ? [(frame) => frame.transaction === tid, ...preCheck] : preCheck;
1948
+ this._masterSession.start(key, finalChecks, (error, frame) => settle(error, frame));
1949
+ })
1950
+ .catch((error) => settle(error));
1155
1951
  });
1156
1952
  }
1157
1953
  writeFC1Or2(unit, fc, address, length, timeout) {
@@ -1159,104 +1955,60 @@ class ModbusMaster extends EventEmitter {
1159
1955
  const bufferTx = Buffer.alloc(4);
1160
1956
  bufferTx.writeUInt16BE(address, 0);
1161
1957
  bufferTx.writeUInt16BE(length, 2);
1162
- return this.waitResponse({
1163
- data: this.applicationLayer.encode({
1164
- unit,
1165
- fc,
1166
- data: Array.from(bufferTx),
1167
- }),
1168
- broadcast: unit === 0,
1169
- }, {
1170
- preCheck: [(frame) => frame.unit === unit && frame.fc === fc, () => 1 + byteCount, (frame) => frame.data[0] === byteCount],
1171
- }, timeout).then((frame) => {
1958
+ return this.send({ unit, fc, data: bufferTx }, [(frame) => frame.unit === unit && frame.fc === fc, () => 1 + byteCount, (frame) => frame.data[0] === byteCount], timeout, unit === 0).then((frame) => {
1172
1959
  if (frame) {
1173
- return Object.assign(Object.assign({}, frame), { data: Array.from({ length }).map((_, index) => (frame.data[1 + ~~(index / 8)] & (1 << index % 8)) > 0) });
1960
+ return Object.assign(Object.assign({}, frame), { data: Array.from({ length }, (_, index) => (frame.data[1 + ~~(index / 8)] & (1 << index % 8)) > 0) });
1174
1961
  }
1175
1962
  });
1176
1963
  }
1177
1964
  readCoils(unit, address, length, timeout = this.timeout) {
1178
- return this.writeFC1Or2(unit, 0x01, address, length, timeout);
1965
+ return this.writeFC1Or2(unit, FunctionCode.READ_COILS, address, length, timeout);
1179
1966
  }
1180
1967
  readDiscreteInputs(unit, address, length, timeout = this.timeout) {
1181
- return this.writeFC1Or2(unit, 0x02, address, length, timeout);
1968
+ return this.writeFC1Or2(unit, FunctionCode.READ_DISCRETE_INPUTS, address, length, timeout);
1182
1969
  }
1183
1970
  writeFC3Or4(unit, fc, address, length, timeout) {
1184
1971
  const byteCount = length * 2;
1185
1972
  const bufferTx = Buffer.alloc(4);
1186
1973
  bufferTx.writeUInt16BE(address, 0);
1187
1974
  bufferTx.writeUInt16BE(length, 2);
1188
- return this.waitResponse({
1189
- data: this.applicationLayer.encode({
1190
- unit,
1191
- fc,
1192
- data: Array.from(bufferTx),
1193
- }),
1194
- broadcast: unit === 0,
1195
- }, {
1196
- preCheck: [(frame) => frame.unit === unit && frame.fc === fc, () => 1 + byteCount, (frame) => frame.data[0] === byteCount],
1197
- }, timeout).then((frame) => {
1975
+ return this.send({ unit, fc, data: bufferTx }, [(frame) => frame.unit === unit && frame.fc === fc, () => 1 + byteCount, (frame) => frame.data[0] === byteCount], timeout, unit === 0).then((frame) => {
1198
1976
  if (frame) {
1199
- const bufferRx = Buffer.from(frame.data.slice(1));
1200
- return Object.assign(Object.assign({}, frame), { data: Array.from({ length }).map((_, index) => bufferRx.readUInt16BE(index * 2)) });
1977
+ const bufferRx = frame.data.subarray(1);
1978
+ return Object.assign(Object.assign({}, frame), { data: Array.from({ length }, (_, index) => bufferRx.readUInt16BE(index * 2)) });
1201
1979
  }
1202
1980
  });
1203
1981
  }
1204
1982
  readHoldingRegisters(unit, address, length, timeout = this.timeout) {
1205
- return this.writeFC3Or4(unit, 0x03, address, length, timeout);
1983
+ return this.writeFC3Or4(unit, FunctionCode.READ_HOLDING_REGISTERS, address, length, timeout);
1206
1984
  }
1207
1985
  readInputRegisters(unit, address, length, timeout = this.timeout) {
1208
- return this.writeFC3Or4(unit, 0x04, address, length, timeout);
1986
+ return this.writeFC3Or4(unit, FunctionCode.READ_INPUT_REGISTERS, address, length, timeout);
1209
1987
  }
1210
1988
  writeSingleCoil(unit, address, value, timeout = this.timeout) {
1211
- const fc = 0x05;
1989
+ const fc = FunctionCode.WRITE_SINGLE_COIL;
1212
1990
  const bufferTx = Buffer.alloc(4);
1213
1991
  bufferTx.writeUInt16BE(address, 0);
1214
- bufferTx.writeUInt16BE(value ? 0xff00 : 0x0000, 2);
1215
- return this.waitResponse({
1216
- data: this.applicationLayer.encode({
1217
- unit,
1218
- fc,
1219
- data: Array.from(bufferTx),
1220
- }),
1221
- broadcast: unit === 0,
1222
- }, {
1223
- preCheck: [
1224
- (frame) => frame.unit === unit && frame.fc === fc,
1225
- () => bufferTx.length,
1226
- (frame) => frame.data.every((v, i) => v === bufferTx[i]),
1227
- ],
1228
- }, timeout).then((frame) => {
1992
+ bufferTx.writeUInt16BE(value ? COIL_ON : COIL_OFF, 2);
1993
+ return this.send({ unit, fc, data: bufferTx }, [(frame) => frame.unit === unit && frame.fc === fc, () => bufferTx.length, (frame) => frame.data.equals(bufferTx)], timeout, unit === 0).then((frame) => {
1229
1994
  if (frame) {
1230
1995
  return Object.assign(Object.assign({}, frame), { data: value });
1231
1996
  }
1232
1997
  });
1233
1998
  }
1234
1999
  writeSingleRegister(unit, address, value, timeout = this.timeout) {
1235
- const fc = 0x06;
2000
+ const fc = FunctionCode.WRITE_SINGLE_REGISTER;
1236
2001
  const bufferTx = Buffer.alloc(4);
1237
2002
  bufferTx.writeUInt16BE(address, 0);
1238
2003
  bufferTx.writeUInt16BE(value, 2);
1239
- return this.waitResponse({
1240
- data: this.applicationLayer.encode({
1241
- unit,
1242
- fc,
1243
- data: Array.from(bufferTx),
1244
- }),
1245
- broadcast: unit === 0,
1246
- }, {
1247
- preCheck: [
1248
- (frame) => frame.unit === unit && frame.fc === fc,
1249
- () => bufferTx.length,
1250
- (frame) => frame.data.every((v, i) => v === bufferTx[i]),
1251
- ],
1252
- }, timeout).then((frame) => {
2004
+ return this.send({ unit, fc, data: bufferTx }, [(frame) => frame.unit === unit && frame.fc === fc, () => bufferTx.length, (frame) => frame.data.equals(bufferTx)], timeout, unit === 0).then((frame) => {
1253
2005
  if (frame) {
1254
2006
  return Object.assign(Object.assign({}, frame), { data: value });
1255
2007
  }
1256
2008
  });
1257
2009
  }
1258
2010
  writeMultipleCoils(unit, address, value, timeout = this.timeout) {
1259
- const fc = 0x0f;
2011
+ const fc = FunctionCode.WRITE_MULTIPLE_COILS;
1260
2012
  const byteCount = Math.ceil(value.length / 8);
1261
2013
  const bufferTx = Buffer.alloc(5 + byteCount);
1262
2014
  bufferTx.writeUInt16BE(address, 0);
@@ -1267,23 +2019,14 @@ class ModbusMaster extends EventEmitter {
1267
2019
  bufferTx[5 + ~~(i / 8)] |= 1 << i % 8;
1268
2020
  }
1269
2021
  });
1270
- return this.waitResponse({
1271
- data: this.applicationLayer.encode({
1272
- unit,
1273
- fc,
1274
- data: Array.from(bufferTx),
1275
- }),
1276
- broadcast: unit === 0,
1277
- }, {
1278
- preCheck: [(frame) => frame.unit === unit && frame.fc === fc, () => 4, (frame) => frame.data.every((v, i) => v === bufferTx[i])],
1279
- }, timeout).then((frame) => {
2022
+ return this.send({ unit, fc, data: bufferTx }, [(frame) => frame.unit === unit && frame.fc === fc, () => 4, (frame) => frame.data.equals(bufferTx.subarray(0, 4))], timeout, unit === 0).then((frame) => {
1280
2023
  if (frame) {
1281
2024
  return Object.assign(Object.assign({}, frame), { data: value });
1282
2025
  }
1283
2026
  });
1284
2027
  }
1285
2028
  writeMultipleRegisters(unit, address, value, timeout = this.timeout) {
1286
- const fc = 0x10;
2029
+ const fc = FunctionCode.WRITE_MULTIPLE_REGISTERS;
1287
2030
  const byteCount = value.length * 2;
1288
2031
  const bufferTx = Buffer.alloc(5 + byteCount);
1289
2032
  bufferTx.writeUInt16BE(address, 0);
@@ -1292,73 +2035,48 @@ class ModbusMaster extends EventEmitter {
1292
2035
  value.forEach((v, i) => {
1293
2036
  bufferTx.writeUInt16BE(v, 5 + i * 2);
1294
2037
  });
1295
- return this.waitResponse({
1296
- data: this.applicationLayer.encode({
1297
- unit,
1298
- fc,
1299
- data: Array.from(bufferTx),
1300
- }),
1301
- broadcast: unit === 0,
1302
- }, {
1303
- preCheck: [(frame) => frame.unit === unit && frame.fc === fc, () => 4, (frame) => frame.data.every((v, i) => v === bufferTx[i])],
1304
- }, timeout).then((frame) => {
2038
+ return this.send({ unit, fc, data: bufferTx }, [(frame) => frame.unit === unit && frame.fc === fc, () => 4, (frame) => frame.data.equals(bufferTx.subarray(0, 4))], timeout, unit === 0).then((frame) => {
1305
2039
  if (frame) {
1306
2040
  return Object.assign(Object.assign({}, frame), { data: value });
1307
2041
  }
1308
2042
  });
1309
2043
  }
1310
- reportServerId(unit, timeout = this.timeout) {
1311
- const fc = 0x11;
1312
- return this.waitResponse({
1313
- data: this.applicationLayer.encode({
1314
- unit,
1315
- fc,
1316
- data: [],
1317
- }),
1318
- broadcast: unit === 0,
1319
- }, {
1320
- preCheck: [
1321
- (frame) => frame.unit === unit && frame.fc === fc,
1322
- (frame) => {
1323
- if (frame.data.length >= 3) {
1324
- return 1 + frame.data[0];
1325
- }
1326
- },
1327
- ],
1328
- }, timeout).then((frame) => {
2044
+ reportServerId(unit, serverIdLength = 1, timeout = this.timeout) {
2045
+ const fc = FunctionCode.REPORT_SERVER_ID;
2046
+ return this.send({ unit, fc, data: Buffer.alloc(0) }, [
2047
+ (frame) => frame.unit === unit && frame.fc === fc,
2048
+ (frame) => {
2049
+ if (frame.data.length >= 2 + serverIdLength) {
2050
+ return 1 + frame.data[0];
2051
+ }
2052
+ },
2053
+ ], timeout, unit === 0).then((frame) => {
1329
2054
  if (frame) {
2055
+ const runStatusIndex = 1 + serverIdLength;
1330
2056
  return Object.assign(Object.assign({}, frame), { data: {
1331
- serverId: frame.data[1],
1332
- runIndicatorStatus: frame.data[2] === 0xff,
1333
- additionalData: frame.data.slice(3),
2057
+ serverId: serverIdLength === 1 ? frame.data[1] : Array.from(frame.data.subarray(1, runStatusIndex)),
2058
+ runIndicatorStatus: frame.data[runStatusIndex] === 0xff,
2059
+ additionalData: Array.from(frame.data.subarray(runStatusIndex + 1)),
1334
2060
  } });
1335
2061
  }
1336
2062
  });
1337
2063
  }
1338
2064
  maskWriteRegister(unit, address, andMask, orMask, timeout = this.timeout) {
1339
- const fc = 0x16;
2065
+ const fc = FunctionCode.MASK_WRITE_REGISTER;
1340
2066
  const bufferTx = Buffer.alloc(6);
1341
2067
  bufferTx.writeUInt16BE(address, 0);
1342
2068
  bufferTx.writeUInt16BE(andMask, 2);
1343
2069
  bufferTx.writeUInt16BE(orMask, 4);
1344
- return this.waitResponse({
1345
- data: this.applicationLayer.encode({
1346
- unit,
1347
- fc,
1348
- data: Array.from(bufferTx),
1349
- }),
1350
- broadcast: unit === 0,
1351
- }, {
1352
- preCheck: [(frame) => frame.unit === unit && frame.fc === fc, () => 6, (frame) => frame.data.every((v, i) => v === bufferTx[i])],
1353
- }, timeout).then((frame) => {
2070
+ return this.send({ unit, fc, data: bufferTx }, [(frame) => frame.unit === unit && frame.fc === fc, () => 6, (frame) => frame.data.equals(bufferTx)], timeout, unit === 0).then((frame) => {
1354
2071
  if (frame) {
1355
2072
  return Object.assign(Object.assign({}, frame), { data: { andMask, orMask } });
1356
2073
  }
1357
2074
  });
1358
2075
  }
1359
2076
  readAndWriteMultipleRegisters(unit, read, write, timeout = this.timeout) {
1360
- const fc = 0x17;
2077
+ const fc = FunctionCode.READ_WRITE_MULTIPLE_REGISTERS;
1361
2078
  const byteCount = write.value.length * 2;
2079
+ const readByteCount = read.length * 2;
1362
2080
  const bufferTx = Buffer.alloc(9 + byteCount);
1363
2081
  bufferTx.writeUInt16BE(read.address, 0);
1364
2082
  bufferTx.writeUInt16BE(read.length, 2);
@@ -1368,150 +2086,108 @@ class ModbusMaster extends EventEmitter {
1368
2086
  write.value.forEach((v, i) => {
1369
2087
  bufferTx.writeUInt16BE(v, 9 + i * 2);
1370
2088
  });
1371
- return this.waitResponse({
1372
- data: this.applicationLayer.encode({
1373
- unit,
1374
- fc,
1375
- data: Array.from(bufferTx),
1376
- }),
1377
- broadcast: unit === 0,
1378
- }, {
1379
- preCheck: [(frame) => frame.unit === unit && frame.fc === fc, () => 1 + byteCount, (frame) => frame.data[0] === byteCount],
1380
- }, timeout).then((frame) => {
2089
+ return this.send({ unit, fc, data: bufferTx }, [(frame) => frame.unit === unit && frame.fc === fc, () => 1 + readByteCount, (frame) => frame.data[0] === readByteCount], timeout, unit === 0).then((frame) => {
1381
2090
  if (frame) {
1382
- const bufferRx = Buffer.from(frame.data.slice(1));
1383
- return Object.assign(Object.assign({}, frame), { data: Array.from({ length: read.length }).map((_, index) => bufferRx.readUInt16BE(index * 2)) });
2091
+ const bufferRx = frame.data.subarray(1);
2092
+ return Object.assign(Object.assign({}, frame), { data: Array.from({ length: read.length }, (_, index) => bufferRx.readUInt16BE(index * 2)) });
1384
2093
  }
1385
2094
  });
1386
2095
  }
1387
2096
  readDeviceIdentification(unit, readDeviceIDCode, objectId, timeout = this.timeout) {
1388
- const fc = 0x2b;
1389
- return this.waitResponse({
1390
- data: this.applicationLayer.encode({
1391
- unit,
1392
- fc,
1393
- data: [0x0e, readDeviceIDCode, objectId],
1394
- }),
1395
- broadcast: unit === 0,
1396
- }, {
1397
- preCheck: [
1398
- (frame) => frame.unit === unit && frame.fc === fc,
1399
- (frame) => {
1400
- if (frame.data.length >= 6) {
1401
- if (frame.data[0] === 0x0e && frame.data[1] === readDeviceIDCode) {
1402
- const objects = [];
1403
- let object = [];
1404
- for (const v of frame.data.slice(6)) {
1405
- switch (object.length) {
1406
- case 0:
1407
- case 1: {
1408
- object.push(v);
1409
- break;
1410
- }
1411
- case 2: {
1412
- object.push([v]);
1413
- break;
1414
- }
1415
- case 3: {
1416
- object[2].push(v);
1417
- if (object[1] === object[2].length) {
1418
- objects.push(2 + object[1]);
1419
- object = [];
1420
- }
1421
- break;
2097
+ const fc = FunctionCode.READ_DEVICE_IDENTIFICATION;
2098
+ let parsed;
2099
+ return this.send({ unit, fc, data: Buffer.from([MEI_READ_DEVICE_ID, readDeviceIDCode, objectId]) }, [
2100
+ (frame) => frame.unit === unit && frame.fc === fc,
2101
+ (frame) => {
2102
+ if (frame.data.length >= 6) {
2103
+ if (frame.data[0] === MEI_READ_DEVICE_ID && frame.data[1] === readDeviceIDCode) {
2104
+ const objects = [];
2105
+ let object = [];
2106
+ let totalBytes = 0;
2107
+ for (const v of frame.data.subarray(6)) {
2108
+ switch (object.length) {
2109
+ case 0:
2110
+ case 1: {
2111
+ object.push(v);
2112
+ break;
2113
+ }
2114
+ case 2: {
2115
+ object.push([v]);
2116
+ break;
2117
+ }
2118
+ case 3: {
2119
+ object[2].push(v);
2120
+ if (object[1] === object[2].length) {
2121
+ objects.push({ id: object[0], value: Buffer.from(object[2]).toString() });
2122
+ totalBytes += 2 + object[1];
2123
+ object = [];
1422
2124
  }
2125
+ break;
1423
2126
  }
1424
2127
  }
1425
- if (objects.length === frame.data[5]) {
1426
- return 6 + objects.reduce((previous, current) => previous + current, 0);
1427
- }
1428
2128
  }
1429
- else {
1430
- return false;
2129
+ if (objects.length === frame.data[5]) {
2130
+ parsed = objects;
2131
+ return 6 + totalBytes;
1431
2132
  }
1432
2133
  }
1433
- },
1434
- ],
1435
- }, timeout).then((frame) => {
1436
- if (frame) {
1437
- const objects = [];
1438
- let object = [];
1439
- for (const v of frame.data.slice(6)) {
1440
- switch (object.length) {
1441
- case 0:
1442
- case 1: {
1443
- object.push(v);
1444
- break;
1445
- }
1446
- case 2: {
1447
- object.push([v]);
1448
- break;
1449
- }
1450
- case 3: {
1451
- object[2].push(v);
1452
- if (object[1] === object[2].length) {
1453
- objects.push({ id: object[0], value: Buffer.from(object[2]).toString() });
1454
- object = [];
1455
- }
1456
- break;
1457
- }
2134
+ else {
2135
+ return false;
1458
2136
  }
1459
2137
  }
2138
+ },
2139
+ ], timeout, unit === 0).then((frame) => {
2140
+ if (frame && parsed) {
1460
2141
  return Object.assign(Object.assign({}, frame), { data: {
1461
2142
  readDeviceIDCode,
1462
2143
  conformityLevel: frame.data[2],
1463
2144
  moreFollows: frame.data[3] === 0xff,
1464
2145
  nextObjectId: frame.data[4],
1465
- objects,
2146
+ objects: parsed,
1466
2147
  } });
1467
2148
  }
1468
2149
  });
1469
2150
  }
2151
+ addCustomFunctionCode(cfc) {
2152
+ this.applicationLayer.addCustomFunctionCode(cfc);
2153
+ }
2154
+ removeCustomFunctionCode(fc) {
2155
+ this.applicationLayer.removeCustomFunctionCode(fc);
2156
+ }
2157
+ sendCustomFC(unit, fc, data, timeout = this.timeout) {
2158
+ const payload = Buffer.isBuffer(data) ? data : Buffer.from(data);
2159
+ return this.send({ unit, fc, data: payload }, [(frame) => frame.unit === unit && frame.fc === fc], timeout, unit === 0).then((frame) => {
2160
+ if (frame) {
2161
+ return frame.data;
2162
+ }
2163
+ });
2164
+ }
1470
2165
  open(...args) {
2166
+ this._closed = false;
1471
2167
  return this.physicalLayer.open(...args);
1472
2168
  }
1473
2169
  close() {
2170
+ this._closed = true;
2171
+ const queued = this._queue.splice(0);
2172
+ for (const item of queued) {
2173
+ item.cancel(new ModbusError(ModbusErrorCode.MASTER_CLOSED, 'Master closed'));
2174
+ }
2175
+ this._masterSession.stopAll(new ModbusError(ModbusErrorCode.MASTER_CLOSED, 'Master closed'));
1474
2176
  return this.physicalLayer.close();
1475
2177
  }
1476
2178
  destroy() {
2179
+ this._closed = true;
2180
+ const queued = this._queue.splice(0);
2181
+ for (const item of queued) {
2182
+ item.cancel(new ModbusError(ModbusErrorCode.MASTER_DESTROYED, 'Master destroyed'));
2183
+ }
2184
+ this._masterSession.stopAll(new ModbusError(ModbusErrorCode.MASTER_DESTROYED, 'Master destroyed'));
1477
2185
  this.removeAllListeners();
1478
2186
  this.applicationLayer.destroy();
1479
2187
  return this.physicalLayer.destroy();
1480
2188
  }
1481
2189
  }
1482
2190
 
1483
- /******************************************************************************
1484
- Copyright (c) Microsoft Corporation.
1485
-
1486
- Permission to use, copy, modify, and/or distribute this software for any
1487
- purpose with or without fee is hereby granted.
1488
-
1489
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
1490
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
1491
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
1492
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
1493
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
1494
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
1495
- PERFORMANCE OF THIS SOFTWARE.
1496
- ***************************************************************************** */
1497
- /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
1498
-
1499
-
1500
- function __awaiter(thisArg, _arguments, P, generator) {
1501
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
1502
- return new (P || (P = Promise))(function (resolve, reject) {
1503
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
1504
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
1505
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
1506
- step((generator = generator.apply(thisArg, _arguments || [])).next());
1507
- });
1508
- }
1509
-
1510
- typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
1511
- var e = new Error(message);
1512
- return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
1513
- };
1514
-
1515
2191
  class ModbusSlave extends EventEmitter {
1516
2192
  get isOpen() {
1517
2193
  return this.physicalLayer.isOpen;
@@ -1519,7 +2195,8 @@ class ModbusSlave extends EventEmitter {
1519
2195
  get destroyed() {
1520
2196
  return this.physicalLayer.destroyed;
1521
2197
  }
1522
- constructor(applicationLayer, physicalLayer) {
2198
+ constructor(applicationLayer, physicalLayer, options = {}) {
2199
+ var _a;
1523
2200
  super();
1524
2201
  Object.defineProperty(this, "applicationLayer", {
1525
2202
  enumerable: true,
@@ -1539,444 +2216,493 @@ class ModbusSlave extends EventEmitter {
1539
2216
  writable: true,
1540
2217
  value: new Map()
1541
2218
  });
1542
- Object.defineProperty(this, "_processing", {
2219
+ Object.defineProperty(this, "concurrent", {
1543
2220
  enumerable: true,
1544
2221
  configurable: true,
1545
2222
  writable: true,
1546
- value: false
2223
+ value: void 0
1547
2224
  });
1548
- Object.defineProperty(this, "_queue", {
2225
+ Object.defineProperty(this, "_queues", {
1549
2226
  enumerable: true,
1550
2227
  configurable: true,
1551
2228
  writable: true,
1552
- value: []
2229
+ value: new Map()
2230
+ });
2231
+ Object.defineProperty(this, "_customFunctionCodes", {
2232
+ enumerable: true,
2233
+ configurable: true,
2234
+ writable: true,
2235
+ value: new Map()
2236
+ });
2237
+ Object.defineProperty(this, "_locks", {
2238
+ enumerable: true,
2239
+ configurable: true,
2240
+ writable: true,
2241
+ value: new Map()
1553
2242
  });
1554
- applicationLayer.on('framing', (frame, _response) => {
2243
+ this.concurrent = (_a = options.concurrent) !== null && _a !== void 0 ? _a : false;
2244
+ if (this.concurrent && this.applicationLayer.PROTOCOL !== 'TCP') {
2245
+ throw new ModbusError(ModbusErrorCode.CONCURRENT_NOT_TCP, 'concurrent mode requires a Modbus TCP application layer');
2246
+ }
2247
+ this.applicationLayer.role = 'SLAVE';
2248
+ applicationLayer.on('framing', (frame, response, connection) => {
1555
2249
  if (!(frame.unit === 0x00 || this.models.has(frame.unit))) {
1556
2250
  return;
1557
2251
  }
1558
- this._queue.push({ frame, response: _response });
1559
- this._drain();
2252
+ if (this.concurrent) {
2253
+ this._processFrame(frame, response).catch((error) => this.emit('error', error));
2254
+ return;
2255
+ }
2256
+ let q = this._queues.get(connection.id);
2257
+ if (!q) {
2258
+ q = { items: [], processing: false };
2259
+ this._queues.set(connection.id, q);
2260
+ }
2261
+ q.items.push({ frame, response });
2262
+ this._drain(connection.id, q);
1560
2263
  });
1561
2264
  physicalLayer.on('error', (error) => {
1562
2265
  this.emit('error', error);
1563
2266
  });
2267
+ physicalLayer.on('connection-close', (connection) => {
2268
+ const q = this._queues.get(connection.id);
2269
+ if (!q) {
2270
+ return;
2271
+ }
2272
+ q.items.length = 0;
2273
+ if (!q.processing) {
2274
+ this._queues.delete(connection.id);
2275
+ }
2276
+ });
1564
2277
  physicalLayer.on('close', () => {
2278
+ for (const q of this._queues.values()) {
2279
+ q.items.length = 0;
2280
+ }
2281
+ this._queues.clear();
1565
2282
  this.emit('close');
1566
2283
  });
1567
2284
  }
1568
2285
  handleFC1(model, frame, response) {
1569
2286
  return __awaiter(this, void 0, void 0, function* () {
1570
2287
  var _a;
1571
- if (frame.data.length === 4) {
1572
- if (model.readCoils) {
1573
- const bufferRx = Buffer.from(frame.data);
1574
- const address = bufferRx.readUInt16BE(0);
1575
- const length = bufferRx.readUInt16BE(2);
1576
- if (length >= 0x0001 && length <= 0x07d0) {
1577
- if (checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
1578
- try {
1579
- const coils = yield Promise.resolve(model.readCoils(address, length));
1580
- const bufferTx = Buffer.alloc(Math.ceil(length / 8));
1581
- coils.forEach((coil, index) => {
1582
- if (coil) {
1583
- bufferTx[~~(index / 8)] |= 1 << index % 8;
1584
- }
1585
- });
1586
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: [bufferTx.length].concat(Array.from(bufferTx)) })));
1587
- }
1588
- catch (error) {
1589
- yield this.responseError(frame, response, error);
1590
- }
1591
- }
1592
- else {
1593
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
1594
- }
1595
- }
1596
- else {
1597
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2288
+ if (frame.data.length !== 4) {
2289
+ return;
2290
+ }
2291
+ if (!model.readCoils) {
2292
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2293
+ return;
2294
+ }
2295
+ const address = (frame.data[0] << 8) | frame.data[1];
2296
+ const length = (frame.data[2] << 8) | frame.data[3];
2297
+ if (length < LIMITS.READ_COILS_MIN || length > LIMITS.READ_COILS_MAX) {
2298
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2299
+ return;
2300
+ }
2301
+ if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
2302
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2303
+ return;
2304
+ }
2305
+ try {
2306
+ const coils = yield model.readCoils(address, length);
2307
+ const bufferTx = Buffer.alloc(Math.ceil(length / 8));
2308
+ coils.forEach((coil, index) => {
2309
+ if (coil) {
2310
+ bufferTx[Math.floor(index / 8)] |= 1 << index % 8;
1598
2311
  }
1599
- }
1600
- else {
1601
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
1602
- }
2312
+ });
2313
+ yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
2314
+ }
2315
+ catch (error) {
2316
+ yield this.responseError(frame, response, error);
1603
2317
  }
1604
2318
  });
1605
2319
  }
1606
2320
  handleFC2(model, frame, response) {
1607
2321
  return __awaiter(this, void 0, void 0, function* () {
1608
2322
  var _a;
1609
- if (frame.data.length === 4) {
1610
- if (model.readDiscreteInputs) {
1611
- const bufferRx = Buffer.from(frame.data);
1612
- const address = bufferRx.readUInt16BE(0);
1613
- const length = bufferRx.readUInt16BE(2);
1614
- if (length >= 0x0001 && length <= 0x07d0) {
1615
- if (checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).discreteInputs)) {
1616
- try {
1617
- const discreteInputs = yield Promise.resolve(model.readDiscreteInputs(address, length));
1618
- const bufferTx = Buffer.alloc(Math.ceil(length / 8));
1619
- discreteInputs.forEach((discreteInput, index) => {
1620
- if (discreteInput) {
1621
- bufferTx[~~(index / 8)] |= 1 << index % 8;
1622
- }
1623
- });
1624
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: [bufferTx.length].concat(Array.from(bufferTx)) })));
1625
- }
1626
- catch (error) {
1627
- yield this.responseError(frame, response, error);
1628
- }
1629
- }
1630
- else {
1631
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
1632
- }
1633
- }
1634
- else {
1635
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2323
+ if (frame.data.length !== 4) {
2324
+ return;
2325
+ }
2326
+ if (!model.readDiscreteInputs) {
2327
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2328
+ return;
2329
+ }
2330
+ const address = (frame.data[0] << 8) | frame.data[1];
2331
+ const length = (frame.data[2] << 8) | frame.data[3];
2332
+ if (length < LIMITS.READ_COILS_MIN || length > LIMITS.READ_COILS_MAX) {
2333
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2334
+ return;
2335
+ }
2336
+ if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).discreteInputs)) {
2337
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2338
+ return;
2339
+ }
2340
+ try {
2341
+ const discreteInputs = yield model.readDiscreteInputs(address, length);
2342
+ const bufferTx = Buffer.alloc(Math.ceil(length / 8));
2343
+ discreteInputs.forEach((discreteInput, index) => {
2344
+ if (discreteInput) {
2345
+ bufferTx[Math.floor(index / 8)] |= 1 << index % 8;
1636
2346
  }
1637
- }
1638
- else {
1639
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
1640
- }
2347
+ });
2348
+ yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
2349
+ }
2350
+ catch (error) {
2351
+ yield this.responseError(frame, response, error);
1641
2352
  }
1642
2353
  });
1643
2354
  }
1644
2355
  handleFC3(model, frame, response) {
1645
2356
  return __awaiter(this, void 0, void 0, function* () {
1646
2357
  var _a;
1647
- if (frame.data.length === 4) {
1648
- if (model.readHoldingRegisters) {
1649
- const bufferRx = Buffer.from(frame.data);
1650
- const address = bufferRx.readUInt16BE(0);
1651
- const length = bufferRx.readUInt16BE(2);
1652
- if (length >= 0x0001 && length <= 0x007d) {
1653
- if (checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
1654
- try {
1655
- const registers = yield Promise.resolve(model.readHoldingRegisters(address, length));
1656
- const bufferTx = Buffer.alloc(length * 2);
1657
- registers.forEach((register, index) => {
1658
- bufferTx.writeUInt16BE(register, index * 2);
1659
- });
1660
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: [bufferTx.length].concat(Array.from(bufferTx)) })));
1661
- }
1662
- catch (error) {
1663
- yield this.responseError(frame, response, error);
1664
- }
1665
- }
1666
- else {
1667
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
1668
- }
1669
- }
1670
- else {
1671
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
1672
- }
1673
- }
1674
- else {
1675
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
1676
- }
2358
+ if (frame.data.length !== 4) {
2359
+ return;
2360
+ }
2361
+ if (!model.readHoldingRegisters) {
2362
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2363
+ return;
2364
+ }
2365
+ const address = (frame.data[0] << 8) | frame.data[1];
2366
+ const length = (frame.data[2] << 8) | frame.data[3];
2367
+ if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.READ_REGISTERS_MAX) {
2368
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2369
+ return;
2370
+ }
2371
+ if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
2372
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2373
+ return;
2374
+ }
2375
+ try {
2376
+ const registers = yield model.readHoldingRegisters(address, length);
2377
+ const bufferTx = Buffer.alloc(length * 2);
2378
+ registers.forEach((register, index) => {
2379
+ bufferTx.writeUInt16BE(register, index * 2);
2380
+ });
2381
+ yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
2382
+ }
2383
+ catch (error) {
2384
+ yield this.responseError(frame, response, error);
1677
2385
  }
1678
2386
  });
1679
2387
  }
1680
2388
  handleFC4(model, frame, response) {
1681
2389
  return __awaiter(this, void 0, void 0, function* () {
1682
2390
  var _a;
1683
- if (frame.data.length === 4) {
1684
- if (model.readInputRegisters) {
1685
- const bufferRx = Buffer.from(frame.data);
1686
- const address = bufferRx.readUInt16BE(0);
1687
- const length = bufferRx.readUInt16BE(2);
1688
- if (length >= 0x0001 && length <= 0x007d) {
1689
- if (checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).inputRegisters)) {
1690
- try {
1691
- const registers = yield Promise.resolve(model.readInputRegisters(address, length));
1692
- const bufferTx = Buffer.alloc(length * 2);
1693
- registers.forEach((register, index) => {
1694
- bufferTx.writeUInt16BE(register, index * 2);
1695
- });
1696
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: [bufferTx.length].concat(Array.from(bufferTx)) })));
1697
- }
1698
- catch (error) {
1699
- yield this.responseError(frame, response, error);
1700
- }
1701
- }
1702
- else {
1703
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
1704
- }
1705
- }
1706
- else {
1707
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
1708
- }
1709
- }
1710
- else {
1711
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
1712
- }
2391
+ if (frame.data.length !== 4) {
2392
+ return;
2393
+ }
2394
+ if (!model.readInputRegisters) {
2395
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2396
+ return;
2397
+ }
2398
+ const address = (frame.data[0] << 8) | frame.data[1];
2399
+ const length = (frame.data[2] << 8) | frame.data[3];
2400
+ if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.READ_REGISTERS_MAX) {
2401
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2402
+ return;
2403
+ }
2404
+ if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).inputRegisters)) {
2405
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2406
+ return;
2407
+ }
2408
+ try {
2409
+ const registers = yield model.readInputRegisters(address, length);
2410
+ const bufferTx = Buffer.alloc(length * 2);
2411
+ registers.forEach((register, index) => {
2412
+ bufferTx.writeUInt16BE(register, index * 2);
2413
+ });
2414
+ yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
2415
+ }
2416
+ catch (error) {
2417
+ yield this.responseError(frame, response, error);
1713
2418
  }
1714
2419
  });
1715
2420
  }
1716
2421
  handleFC5(model, frame, response) {
1717
2422
  return __awaiter(this, void 0, void 0, function* () {
1718
2423
  var _a;
1719
- if (frame.data.length === 4) {
1720
- if (model.writeSingleCoil) {
1721
- const bufferRx = Buffer.from(frame.data);
1722
- const address = bufferRx.readUInt16BE(0);
1723
- const value = bufferRx.readUInt16BE(2);
1724
- if (value === 0x0000 || value === 0xff00) {
1725
- if (checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
1726
- try {
1727
- yield Promise.resolve(model.writeSingleCoil(address, value === 0xff00));
1728
- yield response(this.applicationLayer.encode(frame));
1729
- }
1730
- catch (error) {
1731
- yield this.responseError(frame, response, error);
1732
- }
1733
- }
1734
- else {
1735
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
1736
- }
1737
- }
1738
- else {
1739
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
1740
- }
1741
- }
1742
- else {
1743
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
1744
- }
2424
+ if (frame.data.length !== 4) {
2425
+ return;
2426
+ }
2427
+ if (!model.writeSingleCoil) {
2428
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2429
+ return;
2430
+ }
2431
+ const address = (frame.data[0] << 8) | frame.data[1];
2432
+ const value = (frame.data[2] << 8) | frame.data[3];
2433
+ if (value !== COIL_OFF && value !== COIL_ON) {
2434
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2435
+ return;
2436
+ }
2437
+ if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
2438
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2439
+ return;
2440
+ }
2441
+ try {
2442
+ yield model.writeSingleCoil(address, value === COIL_ON);
2443
+ yield response(this.applicationLayer.encode(frame));
2444
+ }
2445
+ catch (error) {
2446
+ yield this.responseError(frame, response, error);
1745
2447
  }
1746
2448
  });
1747
2449
  }
1748
2450
  handleFC6(model, frame, response) {
1749
2451
  return __awaiter(this, void 0, void 0, function* () {
1750
2452
  var _a;
1751
- if (frame.data.length === 4) {
1752
- if (model.writeSingleRegister) {
1753
- const bufferRx = Buffer.from(frame.data);
1754
- const address = bufferRx.readUInt16BE(0);
1755
- const value = bufferRx.readUInt16BE(2);
1756
- if (value >= 0x0000 && value <= 0xffff) {
1757
- if (checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
1758
- try {
1759
- yield Promise.resolve(model.writeSingleRegister(address, value));
1760
- yield response(this.applicationLayer.encode(frame));
1761
- }
1762
- catch (error) {
1763
- yield this.responseError(frame, response, error);
1764
- }
1765
- }
1766
- else {
1767
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
1768
- }
1769
- }
1770
- else {
1771
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
1772
- }
1773
- }
1774
- else {
1775
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
1776
- }
2453
+ if (frame.data.length !== 4) {
2454
+ return;
2455
+ }
2456
+ if (!model.writeSingleRegister) {
2457
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2458
+ return;
2459
+ }
2460
+ const address = (frame.data[0] << 8) | frame.data[1];
2461
+ const value = (frame.data[2] << 8) | frame.data[3];
2462
+ if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
2463
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2464
+ return;
2465
+ }
2466
+ try {
2467
+ yield model.writeSingleRegister(address, value);
2468
+ yield response(this.applicationLayer.encode(frame));
2469
+ }
2470
+ catch (error) {
2471
+ yield this.responseError(frame, response, error);
1777
2472
  }
1778
2473
  });
1779
2474
  }
1780
2475
  handleFC15(model, frame, response) {
1781
2476
  return __awaiter(this, void 0, void 0, function* () {
1782
2477
  var _a;
1783
- if (frame.data.length > 5 && frame.data.length === 5 + frame.data[4]) {
1784
- if (model.writeMultipleCoils || model.writeSingleCoil) {
1785
- const bufferRx = Buffer.from(frame.data);
1786
- const address = bufferRx.readUInt16BE(0);
1787
- const length = bufferRx.readUInt16BE(2);
1788
- const byteCount = bufferRx[4];
1789
- if (length >= 0x0001 && length <= 0x07b0 && byteCount === Math.ceil(length / 8)) {
1790
- if (checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
1791
- const value = Array.from({ length }).map((_, index) => (bufferRx[5 + ~~(index / 8)] & (1 << index % 8)) > 0);
1792
- try {
1793
- yield Promise.resolve(model.writeMultipleCoils
1794
- ? model.writeMultipleCoils(address, value)
1795
- : Promise.all(value.map((v, i) => model.writeSingleCoil(address + i, v))));
1796
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Array.from(bufferRx).slice(0, 4) })));
1797
- }
1798
- catch (error) {
1799
- yield this.responseError(frame, response, error);
1800
- }
1801
- }
1802
- else {
1803
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
1804
- }
1805
- }
1806
- else {
1807
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
1808
- }
2478
+ if (frame.data.length <= 5 || frame.data.length !== 5 + frame.data[4]) {
2479
+ return;
2480
+ }
2481
+ if (!model.writeMultipleCoils && !model.writeSingleCoil) {
2482
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2483
+ return;
2484
+ }
2485
+ const address = (frame.data[0] << 8) | frame.data[1];
2486
+ const length = (frame.data[2] << 8) | frame.data[3];
2487
+ const byteCount = frame.data[4];
2488
+ if (length < LIMITS.READ_COILS_MIN || length > LIMITS.WRITE_COILS_MAX || byteCount !== Math.ceil(length / 8)) {
2489
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2490
+ return;
2491
+ }
2492
+ if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
2493
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2494
+ return;
2495
+ }
2496
+ const value = new Array(length);
2497
+ for (let i = 0; i < length; i++) {
2498
+ value[i] = (frame.data[5 + Math.floor(i / 8)] & (1 << i % 8)) > 0;
2499
+ }
2500
+ try {
2501
+ if (model.writeMultipleCoils) {
2502
+ yield model.writeMultipleCoils(address, value);
1809
2503
  }
1810
2504
  else {
1811
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2505
+ for (let i = 0; i < value.length; i++) {
2506
+ yield model.writeSingleCoil(address + i, value[i]);
2507
+ }
1812
2508
  }
2509
+ yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: frame.data.subarray(0, 4) })));
2510
+ }
2511
+ catch (error) {
2512
+ yield this.responseError(frame, response, error);
1813
2513
  }
1814
2514
  });
1815
2515
  }
1816
2516
  handleFC16(model, frame, response) {
1817
2517
  return __awaiter(this, void 0, void 0, function* () {
1818
2518
  var _a;
1819
- if (frame.data.length > 5 && frame.data.length === 5 + frame.data[4]) {
1820
- if (model.writeMultipleRegisters || model.writeSingleRegister) {
1821
- const bufferRx = Buffer.from(frame.data);
1822
- const address = bufferRx.readUInt16BE(0);
1823
- const length = bufferRx.readUInt16BE(2);
1824
- const byteCount = bufferRx[4];
1825
- if (length >= 0x0001 && length <= 0x007b && byteCount === length * 2) {
1826
- if (checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
1827
- const value = Array.from({ length }).map((_, index) => bufferRx.readUInt16BE(5 + index * 2));
1828
- try {
1829
- yield Promise.resolve(model.writeMultipleRegisters
1830
- ? model.writeMultipleRegisters(address, value)
1831
- : Promise.all(value.map((v, i) => model.writeSingleRegister(address + i, v))));
1832
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Array.from(bufferRx).slice(0, 4) })));
1833
- }
1834
- catch (error) {
1835
- yield this.responseError(frame, response, error);
1836
- }
1837
- }
1838
- else {
1839
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
1840
- }
1841
- }
1842
- else {
1843
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
1844
- }
2519
+ if (frame.data.length <= 5 || frame.data.length !== 5 + frame.data[4]) {
2520
+ return;
2521
+ }
2522
+ if (!model.writeMultipleRegisters && !model.writeSingleRegister) {
2523
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2524
+ return;
2525
+ }
2526
+ const address = (frame.data[0] << 8) | frame.data[1];
2527
+ const length = (frame.data[2] << 8) | frame.data[3];
2528
+ const byteCount = frame.data[4];
2529
+ if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.WRITE_REGISTERS_MAX || byteCount !== length * 2) {
2530
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2531
+ return;
2532
+ }
2533
+ if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
2534
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2535
+ return;
2536
+ }
2537
+ const value = new Array(length);
2538
+ for (let i = 0; i < length; i++) {
2539
+ value[i] = (frame.data[5 + i * 2] << 8) | frame.data[6 + i * 2];
2540
+ }
2541
+ try {
2542
+ if (model.writeMultipleRegisters) {
2543
+ yield model.writeMultipleRegisters(address, value);
1845
2544
  }
1846
2545
  else {
1847
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2546
+ for (let i = 0; i < value.length; i++) {
2547
+ yield model.writeSingleRegister(address + i, value[i]);
2548
+ }
1848
2549
  }
2550
+ yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: frame.data.subarray(0, 4) })));
2551
+ }
2552
+ catch (error) {
2553
+ yield this.responseError(frame, response, error);
1849
2554
  }
1850
2555
  });
1851
2556
  }
1852
2557
  handleFC17(model, frame, response) {
1853
2558
  return __awaiter(this, void 0, void 0, function* () {
1854
2559
  var _a;
1855
- if (frame.data.length === 0) {
1856
- if (model.reportServerId) {
1857
- try {
1858
- const { serverId = (_a = model.unit) !== null && _a !== void 0 ? _a : 1, runIndicatorStatus = true, additionalData = [] } = yield Promise.resolve(model.reportServerId());
1859
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: [2 + additionalData.length, serverId, runIndicatorStatus ? 0xff : 0x00].concat(additionalData) })));
1860
- }
1861
- catch (error) {
1862
- yield this.responseError(frame, response, error);
1863
- }
2560
+ if (frame.data.length !== 0) {
2561
+ return;
2562
+ }
2563
+ if (!model.reportServerId) {
2564
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2565
+ return;
2566
+ }
2567
+ try {
2568
+ const { serverId = (_a = model.unit) !== null && _a !== void 0 ? _a : 1, runIndicatorStatus = true, additionalData = [] } = yield model.reportServerId();
2569
+ const serverIdBytes = Array.isArray(serverId) ? serverId : [serverId];
2570
+ const byteCount = serverIdBytes.length + 1 + additionalData.length;
2571
+ if (byteCount > 255) {
2572
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
2573
+ return;
1864
2574
  }
1865
- else {
1866
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2575
+ const allBytes = [...serverIdBytes, runIndicatorStatus ? 0xff : 0x00, ...additionalData];
2576
+ if (allBytes.some((b) => !isUint8(b))) {
2577
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
2578
+ return;
1867
2579
  }
2580
+ yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([byteCount]), Buffer.from(allBytes)]) })));
2581
+ }
2582
+ catch (error) {
2583
+ yield this.responseError(frame, response, error);
1868
2584
  }
1869
2585
  });
1870
2586
  }
1871
2587
  handleFC22(model, frame, response) {
1872
2588
  return __awaiter(this, void 0, void 0, function* () {
1873
2589
  var _a;
1874
- if (frame.data.length === 6) {
1875
- if (model.maskWriteRegister || (model.readHoldingRegisters && model.writeSingleRegister)) {
1876
- const bufferRx = Buffer.from(frame.data);
1877
- const address = bufferRx.readUInt16BE(0);
1878
- const andMask = bufferRx.readUInt16BE(2);
1879
- const orMask = bufferRx.readUInt16BE(4);
1880
- if (checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
1881
- try {
1882
- yield Promise.resolve(model.maskWriteRegister
1883
- ? model.maskWriteRegister(address, andMask, orMask)
1884
- : Promise.resolve(model.readHoldingRegisters(address, 1)).then(([value]) => {
1885
- return Promise.resolve(model.writeSingleRegister(address, (value & andMask) | (orMask & (~andMask & 0xff))));
1886
- }));
1887
- yield response(this.applicationLayer.encode(frame));
1888
- }
1889
- catch (error) {
1890
- yield this.responseError(frame, response, error);
1891
- }
2590
+ if (frame.data.length !== 6) {
2591
+ return;
2592
+ }
2593
+ if (!model.maskWriteRegister && !(model.readHoldingRegisters && model.writeSingleRegister)) {
2594
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2595
+ return;
2596
+ }
2597
+ const address = (frame.data[0] << 8) | frame.data[1];
2598
+ const andMask = (frame.data[2] << 8) | frame.data[3];
2599
+ const orMask = (frame.data[4] << 8) | frame.data[5];
2600
+ if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
2601
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2602
+ return;
2603
+ }
2604
+ try {
2605
+ yield this.withAddressLock([address], () => __awaiter(this, void 0, void 0, function* () {
2606
+ if (model.maskWriteRegister) {
2607
+ yield model.maskWriteRegister(address, andMask, orMask);
1892
2608
  }
1893
2609
  else {
1894
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2610
+ const [value] = yield model.readHoldingRegisters(address, 1);
2611
+ yield model.writeSingleRegister(address, (value & andMask) | (orMask & (~andMask & 0xffff)));
1895
2612
  }
1896
- }
1897
- else {
1898
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
1899
- }
2613
+ }));
2614
+ yield response(this.applicationLayer.encode(frame));
2615
+ }
2616
+ catch (error) {
2617
+ yield this.responseError(frame, response, error);
1900
2618
  }
1901
2619
  });
1902
2620
  }
1903
2621
  handleFC23(model, frame, response) {
1904
2622
  return __awaiter(this, void 0, void 0, function* () {
1905
2623
  var _a;
1906
- if (frame.data.length > 9 && frame.data.length === 9 + frame.data[8]) {
1907
- if (model.readHoldingRegisters && (model.writeMultipleRegisters || model.writeSingleRegister)) {
1908
- const bufferRx = Buffer.from(frame.data);
1909
- const address = {
1910
- read: bufferRx.readUInt16BE(0),
1911
- write: bufferRx.readUInt16BE(4),
1912
- };
1913
- const length = {
1914
- read: bufferRx.readUInt16BE(2),
1915
- write: bufferRx.readUInt16BE(6),
1916
- };
1917
- const byteCount = bufferRx[8];
1918
- if (length.read >= 0x0001 &&
1919
- length.read <= 0x007d &&
1920
- length.write >= 0x0001 &&
1921
- length.write <= 0x0079 &&
1922
- byteCount === length.write * 2) {
1923
- if (checkRange([address.read, address.read + length.read, address.write, address.write + length.write], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
1924
- const value = Array.from({ length: length.write }).map((_, index) => bufferRx.readUInt16BE(9 + index * 2));
1925
- try {
1926
- yield Promise.resolve(model.writeMultipleRegisters
1927
- ? model.writeMultipleRegisters(address.write, value)
1928
- : Promise.all(value.map((v, i) => model.writeSingleRegister(address.write + i, v))));
1929
- const registers = yield Promise.resolve(model.readHoldingRegisters(address.read, length.read));
1930
- const bufferTx = Buffer.alloc(length.read * 2);
1931
- registers.forEach((register, index) => {
1932
- bufferTx.writeUInt16BE(register, index * 2);
1933
- });
1934
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: [bufferTx.length].concat(Array.from(bufferTx)) })));
1935
- }
1936
- catch (error) {
1937
- yield this.responseError(frame, response, error);
1938
- }
1939
- }
1940
- else {
1941
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
1942
- }
2624
+ if (frame.data.length <= 9 || frame.data.length !== 9 + frame.data[8]) {
2625
+ return;
2626
+ }
2627
+ if (!model.readHoldingRegisters || (!model.writeMultipleRegisters && !model.writeSingleRegister)) {
2628
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2629
+ return;
2630
+ }
2631
+ const address = {
2632
+ read: (frame.data[0] << 8) | frame.data[1],
2633
+ write: (frame.data[4] << 8) | frame.data[5],
2634
+ };
2635
+ const length = {
2636
+ read: (frame.data[2] << 8) | frame.data[3],
2637
+ write: (frame.data[6] << 8) | frame.data[7],
2638
+ };
2639
+ const byteCount = frame.data[8];
2640
+ if (length.read < LIMITS.READ_REGISTERS_MIN ||
2641
+ length.read > LIMITS.READ_REGISTERS_MAX ||
2642
+ length.write < LIMITS.READ_REGISTERS_MIN ||
2643
+ length.write > LIMITS.RW_REGISTERS_WRITE_MAX ||
2644
+ byteCount !== length.write * 2) {
2645
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2646
+ return;
2647
+ }
2648
+ if (!checkRange([address.read, address.read + length.read, address.write, address.write + length.write], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
2649
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2650
+ return;
2651
+ }
2652
+ const value = new Array(length.write);
2653
+ for (let i = 0; i < length.write; i++) {
2654
+ value[i] = (frame.data[9 + i * 2] << 8) | frame.data[10 + i * 2];
2655
+ }
2656
+ try {
2657
+ const writeAddresses = Array.from({ length: length.write }, (_, i) => address.write + i);
2658
+ yield this.withAddressLock(writeAddresses, () => __awaiter(this, void 0, void 0, function* () {
2659
+ if (model.writeMultipleRegisters) {
2660
+ yield model.writeMultipleRegisters(address.write, value);
1943
2661
  }
1944
2662
  else {
1945
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2663
+ for (let i = 0; i < value.length; i++) {
2664
+ yield model.writeSingleRegister(address.write + i, value[i]);
2665
+ }
1946
2666
  }
1947
- }
1948
- else {
1949
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
1950
- }
2667
+ }));
2668
+ const registers = yield model.readHoldingRegisters(address.read, length.read);
2669
+ const bufferTx = Buffer.alloc(length.read * 2);
2670
+ registers.forEach((register, index) => {
2671
+ bufferTx.writeUInt16BE(register, index * 2);
2672
+ });
2673
+ yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
2674
+ }
2675
+ catch (error) {
2676
+ yield this.responseError(frame, response, error);
1951
2677
  }
1952
2678
  });
1953
2679
  }
1954
2680
  handleFC43_14(model, frame, response) {
1955
2681
  return __awaiter(this, void 0, void 0, function* () {
1956
2682
  if (frame.data.length === 3) {
1957
- if (frame.data[0] === 0x0e && model.readDeviceIdentification) {
2683
+ if (frame.data[0] === MEI_READ_DEVICE_ID && model.readDeviceIdentification) {
1958
2684
  const readDeviceIDCode = frame.data[1];
1959
2685
  let objectID = frame.data[2];
1960
2686
  switch (readDeviceIDCode) {
1961
- case 0x01: {
2687
+ case ReadDeviceIDCode.BASIC_STREAM: {
1962
2688
  if (objectID > 0x02 || (objectID > 0x06 && objectID < 0x80)) {
1963
2689
  objectID = 0x00;
1964
2690
  }
1965
2691
  break;
1966
2692
  }
1967
- case 0x02: {
2693
+ case ReadDeviceIDCode.REGULAR_STREAM: {
1968
2694
  if (objectID >= 0x80 || (objectID > 0x06 && objectID < 0x80)) {
1969
2695
  objectID = 0x00;
1970
2696
  }
1971
2697
  break;
1972
2698
  }
1973
- case 0x03: {
2699
+ case ReadDeviceIDCode.EXTENDED_STREAM: {
1974
2700
  if (objectID > 0x06 && objectID < 0x80) {
1975
2701
  objectID = 0x00;
1976
2702
  }
1977
2703
  break;
1978
2704
  }
1979
- case 0x04: {
2705
+ case ReadDeviceIDCode.SPECIFIC_ACCESS: {
1980
2706
  if (objectID > 0x06 && objectID < 0x80) {
1981
2707
  yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
1982
2708
  return;
@@ -1989,70 +2715,74 @@ class ModbusSlave extends EventEmitter {
1989
2715
  }
1990
2716
  }
1991
2717
  try {
1992
- const identification = yield Promise.resolve(model.readDeviceIdentification());
1993
- const objects = new Map([
1994
- [0x00, 'null'],
1995
- [0x01, 'null'],
1996
- [0x02, 'null'],
1997
- ]);
1998
- for (const [key, value] of Object.entries(identification)) {
1999
- const id = parseInt(key);
2000
- if (!isNaN(id) && id >= 0 && id <= 255) {
2001
- objects.set(id, value);
2718
+ const identification = yield model.readDeviceIdentification();
2719
+ const objects = new Map();
2720
+ for (const key of Object.keys(identification)) {
2721
+ const id = Number(key);
2722
+ if (id >= 0 && id <= 255) {
2723
+ objects.set(id, identification[id]);
2724
+ }
2725
+ }
2726
+ for (let id = 0x00; id <= 0x02; id++) {
2727
+ if (!objects.has(id)) {
2728
+ objects.set(id, 'null');
2002
2729
  }
2003
2730
  }
2004
2731
  if (!objects.has(objectID)) {
2005
- if (readDeviceIDCode === 0x04) {
2732
+ if (readDeviceIDCode === ReadDeviceIDCode.SPECIFIC_ACCESS) {
2006
2733
  yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2007
2734
  return;
2008
2735
  }
2009
2736
  objectID = 0x00;
2010
2737
  }
2011
- const ids = [];
2012
- let totalLength = 10;
2013
- let lastID = 0;
2014
- let conformityLevel = 0x81;
2015
- for (const [id, value] of objects.entries()) {
2016
- if (id < 0x00 || (id >= 0x07 && id <= 0x7f) || id > 0xff) {
2738
+ let maxId = 0;
2739
+ for (const id of objects.keys()) {
2740
+ if ((id >= 0x07 && id <= 0x7f) || id > 0xff) {
2017
2741
  yield this.responseError(frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
2018
2742
  return;
2019
2743
  }
2020
- if (id > 0x02) {
2021
- conformityLevel = 0x82;
2022
- }
2023
- if (id > 0x80) {
2024
- conformityLevel = 0x83;
2744
+ if (id > maxId) {
2745
+ maxId = id;
2025
2746
  }
2747
+ }
2748
+ const conformityLevel = maxId >= 0x80 ? ConformityLevel.EXTENDED : maxId > 0x02 ? ConformityLevel.REGULAR : ConformityLevel.BASIC;
2749
+ const ids = [];
2750
+ let totalLength = 10;
2751
+ let lastID = 0;
2752
+ for (const [id, value] of objects.entries()) {
2026
2753
  if (objectID > id) {
2027
2754
  continue;
2028
2755
  }
2029
- if (value.length > 245) {
2756
+ const byteLength = Buffer.byteLength(value);
2757
+ if (byteLength > 245) {
2030
2758
  yield this.responseError(frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
2031
2759
  return;
2032
2760
  }
2033
2761
  if (lastID !== 0) {
2034
2762
  continue;
2035
2763
  }
2036
- if (value.length + 2 > 253 - totalLength) {
2764
+ if (byteLength + 2 > 253 - totalLength) {
2037
2765
  if (lastID === 0) {
2038
2766
  lastID = id;
2039
2767
  }
2040
2768
  }
2041
2769
  else {
2042
- totalLength += value.length + 2;
2770
+ totalLength += byteLength + 2;
2043
2771
  ids.push(id);
2044
- if (readDeviceIDCode === 0x04) {
2772
+ if (readDeviceIDCode === ReadDeviceIDCode.SPECIFIC_ACCESS) {
2045
2773
  break;
2046
2774
  }
2047
2775
  }
2048
2776
  }
2049
2777
  ids.sort((a, b) => a - b);
2050
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: [0x0e, readDeviceIDCode, conformityLevel, lastID === 0 ? 0x00 : 0xff, lastID, ids.length].concat(ids
2051
- .map((id) => {
2052
- const value = objects.get(id);
2053
- return [id, value.length].concat(Array.from(Buffer.from(value)));
2054
- })
2055
- .flat()) })));
2778
+ const parts = [];
2779
+ parts.push(Buffer.from([MEI_READ_DEVICE_ID, readDeviceIDCode, conformityLevel, lastID === 0 ? 0x00 : 0xff, lastID, ids.length]));
2780
+ for (const id of ids) {
2781
+ const value = objects.get(id);
2782
+ parts.push(Buffer.from([id, Buffer.byteLength(value)]));
2783
+ parts.push(Buffer.from(value));
2784
+ }
2785
+ yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat(parts) })));
2056
2786
  }
2057
2787
  catch (error) {
2058
2788
  yield this.responseError(frame, response, error);
@@ -2066,26 +2796,32 @@ class ModbusSlave extends EventEmitter {
2066
2796
  }
2067
2797
  responseError(frame, response, error) {
2068
2798
  return __awaiter(this, void 0, void 0, function* () {
2069
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { fc: frame.fc | 0x80, data: [getCodeByError(error)] })));
2799
+ yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { fc: frame.fc | EXCEPTION_OFFSET, data: Buffer.from([getCodeByError(error)]) })));
2070
2800
  });
2071
2801
  }
2072
- _drain() {
2802
+ _drain(key, q) {
2073
2803
  return __awaiter(this, void 0, void 0, function* () {
2074
- if (this._processing || this._queue.length === 0) {
2804
+ if (q.processing) {
2075
2805
  return;
2076
2806
  }
2077
- this._processing = true;
2078
- const { frame, response: _response } = this._queue.shift();
2807
+ q.processing = true;
2079
2808
  try {
2080
- yield this._processFrame(frame, _response);
2081
- }
2082
- catch (error) {
2083
- this.emit('error', error);
2809
+ while (q.items.length > 0) {
2810
+ const { frame, response } = q.items.shift();
2811
+ try {
2812
+ yield this._processFrame(frame, response);
2813
+ }
2814
+ catch (error) {
2815
+ this.emit('error', error);
2816
+ }
2817
+ }
2084
2818
  }
2085
2819
  finally {
2086
- this._processing = false;
2087
- if (this._queue.length > 0) {
2088
- setImmediate(() => this._drain());
2820
+ q.processing = false;
2821
+ // Cleanup empty queue entries so the map doesn't grow unbounded across
2822
+ // ephemeral connections (UDP rinfo, brief TCP clients).
2823
+ if (q.items.length === 0 && this._queues.get(key) === q) {
2824
+ this._queues.delete(key);
2089
2825
  }
2090
2826
  }
2091
2827
  });
@@ -2102,8 +2838,8 @@ class ModbusSlave extends EventEmitter {
2102
2838
  catch (error) { }
2103
2839
  });
2104
2840
  for (const model of frame.unit === 0x00 ? this.models.values() : [this.models.get(frame.unit)]) {
2105
- const res = yield this._intercept(model, frame, response);
2106
- if (res !== 'break') {
2841
+ const intercepted = yield this._intercept(model, frame, response);
2842
+ if (!intercepted) {
2107
2843
  yield this._handleFC(model, frame, response);
2108
2844
  }
2109
2845
  }
@@ -2111,17 +2847,42 @@ class ModbusSlave extends EventEmitter {
2111
2847
  }
2112
2848
  _intercept(model, frame, response) {
2113
2849
  return __awaiter(this, void 0, void 0, function* () {
2114
- if (model.interceptor) {
2115
- try {
2116
- const data = yield model.interceptor(frame.fc, frame.data);
2117
- if (data) {
2118
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data })));
2119
- return 'break';
2120
- }
2850
+ if (!model.interceptor) {
2851
+ return false;
2852
+ }
2853
+ try {
2854
+ const data = yield model.interceptor(frame.fc, frame.data);
2855
+ if (data !== undefined) {
2856
+ yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data })));
2857
+ return true;
2121
2858
  }
2122
- catch (error) {
2123
- yield this.responseError(frame, response, error);
2124
- return 'break';
2859
+ return false;
2860
+ }
2861
+ catch (error) {
2862
+ yield this.responseError(frame, response, error);
2863
+ return true;
2864
+ }
2865
+ });
2866
+ }
2867
+ withAddressLock(addresses, fn) {
2868
+ return __awaiter(this, void 0, void 0, function* () {
2869
+ const sorted = [...new Set(addresses)].sort((a, b) => a - b);
2870
+ const previous = sorted.map((addr) => { var _a; return (_a = this._locks.get(addr)) !== null && _a !== void 0 ? _a : Promise.resolve(); });
2871
+ const work = Promise.all(previous).then(() => fn());
2872
+ const cleanup = work.catch(() => {
2873
+ /* ignore */
2874
+ });
2875
+ for (const addr of sorted) {
2876
+ this._locks.set(addr, cleanup);
2877
+ }
2878
+ try {
2879
+ return yield work;
2880
+ }
2881
+ finally {
2882
+ for (const addr of sorted) {
2883
+ if (this._locks.get(addr) === cleanup) {
2884
+ this._locks.delete(addr);
2885
+ }
2125
2886
  }
2126
2887
  }
2127
2888
  });
@@ -2129,56 +2890,68 @@ class ModbusSlave extends EventEmitter {
2129
2890
  _handleFC(model, frame, response) {
2130
2891
  return __awaiter(this, void 0, void 0, function* () {
2131
2892
  switch (frame.fc) {
2132
- case 0x01: {
2893
+ case FunctionCode.READ_COILS: {
2133
2894
  yield this.handleFC1(model, frame, response);
2134
2895
  break;
2135
2896
  }
2136
- case 0x02: {
2897
+ case FunctionCode.READ_DISCRETE_INPUTS: {
2137
2898
  yield this.handleFC2(model, frame, response);
2138
2899
  break;
2139
2900
  }
2140
- case 0x03: {
2901
+ case FunctionCode.READ_HOLDING_REGISTERS: {
2141
2902
  yield this.handleFC3(model, frame, response);
2142
2903
  break;
2143
2904
  }
2144
- case 0x04: {
2905
+ case FunctionCode.READ_INPUT_REGISTERS: {
2145
2906
  yield this.handleFC4(model, frame, response);
2146
2907
  break;
2147
2908
  }
2148
- case 0x05: {
2909
+ case FunctionCode.WRITE_SINGLE_COIL: {
2149
2910
  yield this.handleFC5(model, frame, response);
2150
2911
  break;
2151
2912
  }
2152
- case 0x06: {
2913
+ case FunctionCode.WRITE_SINGLE_REGISTER: {
2153
2914
  yield this.handleFC6(model, frame, response);
2154
2915
  break;
2155
2916
  }
2156
- case 0x0f: {
2917
+ case FunctionCode.WRITE_MULTIPLE_COILS: {
2157
2918
  yield this.handleFC15(model, frame, response);
2158
2919
  break;
2159
2920
  }
2160
- case 0x10: {
2921
+ case FunctionCode.WRITE_MULTIPLE_REGISTERS: {
2161
2922
  yield this.handleFC16(model, frame, response);
2162
2923
  break;
2163
2924
  }
2164
- case 0x11: {
2925
+ case FunctionCode.REPORT_SERVER_ID: {
2165
2926
  yield this.handleFC17(model, frame, response);
2166
2927
  break;
2167
2928
  }
2168
- case 0x16: {
2929
+ case FunctionCode.MASK_WRITE_REGISTER: {
2169
2930
  yield this.handleFC22(model, frame, response);
2170
2931
  break;
2171
2932
  }
2172
- case 0x17: {
2933
+ case FunctionCode.READ_WRITE_MULTIPLE_REGISTERS: {
2173
2934
  yield this.handleFC23(model, frame, response);
2174
2935
  break;
2175
2936
  }
2176
- case 0x2b: {
2937
+ case FunctionCode.READ_DEVICE_IDENTIFICATION: {
2177
2938
  yield this.handleFC43_14(model, frame, response);
2178
2939
  break;
2179
2940
  }
2180
2941
  default: {
2181
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2942
+ const cfc = this._customFunctionCodes.get(frame.fc);
2943
+ if (cfc === null || cfc === void 0 ? void 0 : cfc.handle) {
2944
+ try {
2945
+ const responseData = yield cfc.handle(frame.data, frame.unit);
2946
+ yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: responseData })));
2947
+ }
2948
+ catch (error) {
2949
+ yield this.responseError(frame, response, error);
2950
+ }
2951
+ }
2952
+ else {
2953
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2954
+ }
2182
2955
  break;
2183
2956
  }
2184
2957
  }
@@ -2191,6 +2964,14 @@ class ModbusSlave extends EventEmitter {
2191
2964
  remove(unit) {
2192
2965
  this.models.delete(unit);
2193
2966
  }
2967
+ addCustomFunctionCode(cfc) {
2968
+ this._customFunctionCodes.set(cfc.fc, cfc);
2969
+ this.applicationLayer.addCustomFunctionCode(cfc);
2970
+ }
2971
+ removeCustomFunctionCode(fc) {
2972
+ this._customFunctionCodes.delete(fc);
2973
+ this.applicationLayer.removeCustomFunctionCode(fc);
2974
+ }
2194
2975
  open(...args) {
2195
2976
  return this.physicalLayer.open(...args);
2196
2977
  }
@@ -2198,10 +2979,11 @@ class ModbusSlave extends EventEmitter {
2198
2979
  return this.physicalLayer.close();
2199
2980
  }
2200
2981
  destroy() {
2982
+ this._locks.clear();
2201
2983
  this.removeAllListeners();
2202
2984
  this.applicationLayer.destroy();
2203
2985
  return this.physicalLayer.destroy();
2204
2986
  }
2205
2987
  }
2206
2988
 
2207
- export { AbstractApplicationLayer, AbstractPhysicalLayer, AsciiApplicationLayer, ErrorCode, ModbusMaster, ModbusSlave, RtuApplicationLayer, SerialPhysicalLayer, TcpApplicationLayer, TcpClientPhysicalLayer, TcpServerPhysicalLayer, UdpPhysicalLayer, getCodeByError, getErrorByCode };
2989
+ export { AbstractApplicationLayer, AbstractPhysicalLayer, AsciiApplicationLayer, COIL_OFF, COIL_ON, ConformityLevel, EXCEPTION_OFFSET, ErrorCode, FunctionCode, LIMITS, MEI_READ_DEVICE_ID, MasterSession, ModbusError, ModbusErrorCode, ModbusMaster, ModbusSlave, ReadDeviceIDCode, RtuApplicationLayer, SerialPhysicalLayer, TcpApplicationLayer, TcpClientPhysicalLayer, TcpServerPhysicalLayer, UdpPhysicalLayer, getCodeByError, getErrorByCode };