njs-modbus 1.5.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +16 -0
  2. package/dist/index.cjs +2033 -1136
  3. package/dist/index.d.ts +315 -89
  4. package/dist/index.mjs +2026 -1137
  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 +15 -10
  9. package/dist/src/layers/application/index.d.ts +2 -0
  10. package/dist/src/layers/application/rtu-application-layer.d.ts +53 -18
  11. package/dist/src/layers/application/tcp-application-layer.d.ts +5 -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 +29 -5
  21. package/dist/src/slave/index.d.ts +1 -1
  22. package/dist/src/slave/slave.d.ts +20 -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,27 @@ 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() {
424
+ if (this._destroyed) {
425
+ return Promise.resolve();
426
+ }
123
427
  this._destroyed = true;
124
- this.removeAllListeners();
125
- return this.close();
428
+ return this.close().then(() => {
429
+ this.removeAllListeners();
430
+ });
126
431
  }
127
432
  }
128
433
 
@@ -145,7 +450,13 @@ class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
145
450
  enumerable: true,
146
451
  configurable: true,
147
452
  writable: true,
148
- value: void 0
453
+ value: null
454
+ });
455
+ Object.defineProperty(this, "_connection", {
456
+ enumerable: true,
457
+ configurable: true,
458
+ writable: true,
459
+ value: null
149
460
  });
150
461
  Object.defineProperty(this, "_isOpen", {
151
462
  enumerable: true,
@@ -153,46 +464,79 @@ class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
153
464
  writable: true,
154
465
  value: false
155
466
  });
467
+ Object.defineProperty(this, "_isOpening", {
468
+ enumerable: true,
469
+ configurable: true,
470
+ writable: true,
471
+ value: false
472
+ });
156
473
  Object.defineProperty(this, "_destroyed", {
157
474
  enumerable: true,
158
475
  configurable: true,
159
476
  writable: true,
160
477
  value: false
161
478
  });
162
- this._socket = new Socket(options);
479
+ Object.defineProperty(this, "_socketOptions", {
480
+ enumerable: true,
481
+ configurable: true,
482
+ writable: true,
483
+ value: void 0
484
+ });
485
+ this._socketOptions = options;
163
486
  }
164
487
  open(options) {
165
- if (this.destroyed) {
166
- return Promise.reject(new Error('Port is destroyed'));
488
+ if (this._destroyed) {
489
+ return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Port is destroyed'));
167
490
  }
491
+ if (this._isOpen || this._isOpening) {
492
+ return Promise.reject(new ModbusError(ModbusErrorCode.PORT_ALREADY_OPEN, 'Port is already open'));
493
+ }
494
+ this._isOpening = true;
495
+ const socket = new Socket(this._socketOptions);
496
+ const connection = { id: genConnectionId('tcp-client') };
497
+ this._socket = socket;
498
+ this._connection = connection;
168
499
  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();
500
+ let connected = false;
501
+ socket.on('data', (data) => {
502
+ this.emit('data', data, (d) => this.write(d), connection);
182
503
  });
183
- this._socket.on('error', (error) => {
184
- if (called) {
185
- this.emit('error', error);
504
+ socket.on('error', (err) => {
505
+ if (connected) {
506
+ this.emit('error', err);
186
507
  }
187
508
  else {
188
- reject(error);
509
+ if (this._connection === connection) {
510
+ this._isOpening = false;
511
+ this._socket = null;
512
+ this._connection = null;
513
+ }
514
+ socket.removeAllListeners();
515
+ reject(err);
189
516
  }
190
517
  });
518
+ socket.once('close', () => {
519
+ if (this._connection === connection) {
520
+ this._isOpen = false;
521
+ this._isOpening = false;
522
+ this._socket = null;
523
+ this._connection = null;
524
+ socket.removeAllListeners();
525
+ this.emit('connection-close', connection);
526
+ this.emit('close');
527
+ }
528
+ });
529
+ socket.connect(options !== null && options !== void 0 ? options : { port: 502 }, () => {
530
+ connected = true;
531
+ this._isOpening = false;
532
+ this._isOpen = true;
533
+ resolve();
534
+ });
191
535
  });
192
536
  }
193
537
  write(data) {
194
538
  return new Promise((resolve, reject) => {
195
- if (this.isOpen) {
539
+ if (this._isOpen && this._socket) {
196
540
  this._socket.write(data, (error) => {
197
541
  if (error) {
198
542
  reject(error);
@@ -204,22 +548,28 @@ class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
204
548
  });
205
549
  }
206
550
  else {
207
- reject(new Error('Port is not open'));
551
+ reject(new ModbusError(ModbusErrorCode.PORT_NOT_OPEN, 'Port is not open'));
208
552
  }
209
553
  });
210
554
  }
211
555
  close() {
556
+ if (!this._socket) {
557
+ return Promise.resolve();
558
+ }
559
+ const socket = this._socket;
212
560
  return new Promise((resolve) => {
213
- this._isOpen = false;
214
- this._socket.removeAllListeners();
215
- this._socket.destroy();
216
- resolve();
561
+ socket.once('close', () => resolve());
562
+ socket.destroy();
217
563
  });
218
564
  }
219
565
  destroy() {
566
+ if (this._destroyed) {
567
+ return Promise.resolve();
568
+ }
220
569
  this._destroyed = true;
221
- this.removeAllListeners();
222
- return this.close();
570
+ return this.close().then(() => {
571
+ this.removeAllListeners();
572
+ });
223
573
  }
224
574
  }
225
575
 
@@ -242,7 +592,7 @@ class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
242
592
  enumerable: true,
243
593
  configurable: true,
244
594
  writable: true,
245
- value: void 0
595
+ value: null
246
596
  });
247
597
  Object.defineProperty(this, "_isOpen", {
248
598
  enumerable: true,
@@ -250,92 +600,137 @@ class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
250
600
  writable: true,
251
601
  value: false
252
602
  });
603
+ Object.defineProperty(this, "_isOpening", {
604
+ enumerable: true,
605
+ configurable: true,
606
+ writable: true,
607
+ value: false
608
+ });
253
609
  Object.defineProperty(this, "_destroyed", {
254
610
  enumerable: true,
255
611
  configurable: true,
256
612
  writable: true,
257
613
  value: false
258
614
  });
259
- Object.defineProperty(this, "_sockets", {
615
+ Object.defineProperty(this, "_connections", {
616
+ enumerable: true,
617
+ configurable: true,
618
+ writable: true,
619
+ value: new Map()
620
+ });
621
+ Object.defineProperty(this, "_serverOptions", {
260
622
  enumerable: true,
261
623
  configurable: true,
262
624
  writable: true,
263
- value: new Set()
625
+ value: void 0
264
626
  });
265
- this._server = createServer(options, (socket) => {
266
- this._sockets.add(socket);
627
+ this._serverOptions = options;
628
+ }
629
+ open(options) {
630
+ if (this._destroyed) {
631
+ return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Port is destroyed'));
632
+ }
633
+ if (this._isOpen || this._isOpening) {
634
+ return Promise.reject(new ModbusError(ModbusErrorCode.PORT_ALREADY_OPEN, 'Port is already open'));
635
+ }
636
+ this._isOpening = true;
637
+ this._connections.clear();
638
+ const server = createServer(this._serverOptions, (socket) => {
639
+ const conn = { id: genConnectionId('tcp-server') };
640
+ this._connections.set(socket, conn);
641
+ const response = (data) => new Promise((resolve, reject) => {
642
+ socket.write(data, (error) => {
643
+ if (error) {
644
+ reject(error);
645
+ }
646
+ else {
647
+ resolve();
648
+ }
649
+ });
650
+ });
267
651
  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
- }));
652
+ this.emit('data', data, response, conn);
653
+ });
654
+ socket.on('error', (err) => {
655
+ this.emit('error', err);
278
656
  });
279
657
  socket.once('close', () => {
658
+ if (this._connections.delete(socket)) {
659
+ this.emit('connection-close', conn);
660
+ }
280
661
  socket.removeAllListeners();
281
- this._sockets.delete(socket);
282
662
  });
283
663
  });
284
- }
285
- open(options) {
286
- if (this.destroyed) {
287
- return Promise.reject(new Error('Port is destroyed'));
288
- }
664
+ this._server = server;
289
665
  return new Promise((resolve, reject) => {
290
666
  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', () => {
667
+ let listening = false;
668
+ server.once('error', (err) => {
669
+ if (listening) {
670
+ this.emit('error', err);
671
+ }
672
+ else {
673
+ if (this._server === server) {
674
+ this._isOpening = false;
675
+ this._server = null;
676
+ }
677
+ server.removeAllListeners();
678
+ reject(err);
679
+ }
680
+ });
681
+ server.once('close', () => {
682
+ if (this._server === server) {
297
683
  this._isOpen = false;
298
- this._server.removeAllListeners();
299
- for (const socket of this._sockets) {
300
- socket.removeAllListeners();
684
+ this._isOpening = false;
685
+ this._server = null;
686
+ const conns = Array.from(this._connections.values());
687
+ this._connections.clear();
688
+ for (const conn of conns) {
689
+ this.emit('connection-close', conn);
301
690
  }
302
691
  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
692
  }
313
693
  });
694
+ server.listen(Object.assign(Object.assign({}, options), { port: (_a = options === null || options === void 0 ? void 0 : options.port) !== null && _a !== void 0 ? _a : 502 }), () => {
695
+ listening = true;
696
+ this._isOpening = false;
697
+ this._isOpen = true;
698
+ resolve();
699
+ });
314
700
  });
315
701
  }
316
702
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
317
703
  write(data) {
318
704
  return new Promise((resolve, reject) => {
319
- reject(new Error('Not supported'));
705
+ reject(new ModbusError(ModbusErrorCode.NOT_SUPPORTED, 'Not supported'));
320
706
  });
321
707
  }
322
708
  close() {
709
+ if (!this._server) {
710
+ return Promise.resolve();
711
+ }
712
+ const server = this._server;
323
713
  return new Promise((resolve) => {
324
- this._isOpen = false;
325
- this._server.removeAllListeners();
326
- for (const socket of this._sockets) {
327
- socket.removeAllListeners();
714
+ server.once('close', () => resolve());
715
+ for (const [socket] of this._connections) {
328
716
  socket.destroy();
329
717
  }
330
- this._server.close(() => {
718
+ try {
719
+ server.close();
720
+ }
721
+ catch (_a) {
331
722
  resolve();
332
- });
723
+ }
333
724
  });
334
725
  }
335
726
  destroy() {
727
+ if (this._destroyed) {
728
+ return Promise.resolve();
729
+ }
336
730
  this._destroyed = true;
337
- this.removeAllListeners();
338
- return this.close();
731
+ return this.close().then(() => {
732
+ this.removeAllListeners();
733
+ });
339
734
  }
340
735
  }
341
736
 
@@ -346,14 +741,8 @@ class UdpPhysicalLayer extends AbstractPhysicalLayer {
346
741
  get destroyed() {
347
742
  return this._destroyed;
348
743
  }
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;
744
+ constructor(options) {
745
+ var _a, _b, _c, _d;
357
746
  super();
358
747
  Object.defineProperty(this, "TYPE", {
359
748
  enumerable: true,
@@ -365,7 +754,13 @@ class UdpPhysicalLayer extends AbstractPhysicalLayer {
365
754
  enumerable: true,
366
755
  configurable: true,
367
756
  writable: true,
368
- value: void 0
757
+ value: null
758
+ });
759
+ Object.defineProperty(this, "_connections", {
760
+ enumerable: true,
761
+ configurable: true,
762
+ writable: true,
763
+ value: new Map()
369
764
  });
370
765
  Object.defineProperty(this, "_isOpen", {
371
766
  enumerable: true,
@@ -373,12 +768,24 @@ class UdpPhysicalLayer extends AbstractPhysicalLayer {
373
768
  writable: true,
374
769
  value: false
375
770
  });
771
+ Object.defineProperty(this, "_isOpening", {
772
+ enumerable: true,
773
+ configurable: true,
774
+ writable: true,
775
+ value: false
776
+ });
376
777
  Object.defineProperty(this, "_destroyed", {
377
778
  enumerable: true,
378
779
  configurable: true,
379
780
  writable: true,
380
781
  value: false
381
782
  });
783
+ Object.defineProperty(this, "_socketOptions", {
784
+ enumerable: true,
785
+ configurable: true,
786
+ writable: true,
787
+ value: void 0
788
+ });
382
789
  Object.defineProperty(this, "_port", {
383
790
  enumerable: true,
384
791
  configurable: true,
@@ -391,67 +798,137 @@ class UdpPhysicalLayer extends AbstractPhysicalLayer {
391
798
  writable: true,
392
799
  value: void 0
393
800
  });
394
- Object.defineProperty(this, "isServer", {
801
+ Object.defineProperty(this, "_idleTimeout", {
395
802
  enumerable: true,
396
803
  configurable: true,
397
804
  writable: true,
398
805
  value: void 0
399
806
  });
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
- }));
807
+ Object.defineProperty(this, "isServer", {
808
+ enumerable: true,
809
+ configurable: true,
810
+ writable: true,
811
+ value: void 0
411
812
  });
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;
813
+ this._socketOptions = options === null || options === void 0 ? void 0 : options.socket;
814
+ this.isServer = !(options === null || options === void 0 ? void 0 : options.remote);
815
+ 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;
816
+ this._address = (_c = options === null || options === void 0 ? void 0 : options.remote) === null || _c === void 0 ? void 0 : _c.address;
817
+ this._idleTimeout = (_d = options === null || options === void 0 ? void 0 : options.idleTimeout) !== null && _d !== void 0 ? _d : 30000;
415
818
  }
416
819
  open(options) {
417
- if (this.destroyed) {
418
- return Promise.reject(new Error('Port is destroyed'));
820
+ var _a, _b;
821
+ if (this._destroyed) {
822
+ return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Port is destroyed'));
419
823
  }
824
+ if (this._isOpen || this._isOpening) {
825
+ return Promise.reject(new ModbusError(ModbusErrorCode.PORT_ALREADY_OPEN, 'Port is already open'));
826
+ }
827
+ this._isOpening = true;
828
+ this._connections.clear();
829
+ 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));
830
+ this._socket = socket;
420
831
  return new Promise((resolve, reject) => {
421
832
  var _a;
833
+ let started = false;
834
+ socket.on('error', (err) => {
835
+ if (started) {
836
+ this.emit('error', err);
837
+ }
838
+ else {
839
+ if (this._socket === socket) {
840
+ this._isOpening = false;
841
+ this._socket = null;
842
+ }
843
+ socket.removeAllListeners();
844
+ reject(err);
845
+ }
846
+ });
847
+ socket.once('close', () => {
848
+ if (this._socket !== socket) {
849
+ return;
850
+ }
851
+ this._isOpen = false;
852
+ this._isOpening = false;
853
+ this._socket = null;
854
+ socket.removeAllListeners();
855
+ const entries = Array.from(this._connections.values());
856
+ this._connections.clear();
857
+ for (const entry of entries) {
858
+ if (entry.idleTimer) {
859
+ clearTimeout(entry.idleTimer);
860
+ }
861
+ this.emit('connection-close', entry.conn);
862
+ }
863
+ this.emit('close');
864
+ });
422
865
  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;
866
+ socket.bind(Object.assign(Object.assign({}, options), { port: (_a = options === null || options === void 0 ? void 0 : options.port) !== null && _a !== void 0 ? _a : 502 }), () => {
867
+ started = true;
868
+ this._isOpening = false;
426
869
  this._isOpen = true;
427
- this._socket.on('close', () => {
428
- this._isOpen = false;
429
- this._socket.removeAllListeners();
430
- this.emit('close');
431
- });
432
870
  resolve();
433
871
  });
434
- this._socket.on('error', (error) => {
435
- if (called) {
436
- this.emit('error', error);
437
- }
438
- else {
439
- reject(error);
440
- }
441
- });
442
872
  }
443
873
  else {
874
+ started = true;
875
+ this._isOpening = false;
444
876
  this._isOpen = true;
445
877
  resolve();
446
878
  }
447
879
  });
448
880
  }
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);
881
+ _handleMessage(socket, msg, rinfo) {
882
+ if (this._socket !== socket) {
883
+ return;
884
+ }
885
+ if (!this.isServer) {
886
+ if (rinfo.port !== this._port) {
887
+ return;
888
+ }
889
+ if (this._address !== undefined && rinfo.address !== this._address) {
890
+ return;
891
+ }
892
+ }
893
+ const key = `${rinfo.address}:${rinfo.port}`;
894
+ let entry = this._connections.get(key);
895
+ if (!entry) {
896
+ entry = { conn: { id: genConnectionId('udp') } };
897
+ this._connections.set(key, entry);
898
+ }
899
+ if (this.isServer && this._idleTimeout > 0) {
900
+ if (entry.idleTimer) {
901
+ clearTimeout(entry.idleTimer);
902
+ }
903
+ const conn = entry.conn;
904
+ entry.idleTimer = setTimeout(() => {
905
+ const e = this._connections.get(key);
906
+ if (!e || e.conn !== conn) {
907
+ return;
908
+ }
909
+ this._connections.delete(key);
910
+ this.emit('connection-close', conn);
911
+ }, this._idleTimeout);
912
+ }
913
+ const conn = entry.conn;
914
+ const response = (data) => new Promise((resolve, reject) => {
915
+ socket.send(data, rinfo.port, rinfo.address, (error) => {
916
+ if (error) {
917
+ reject(error);
918
+ }
919
+ else {
920
+ resolve();
921
+ }
922
+ });
923
+ });
924
+ this.emit('data', msg, response, conn);
925
+ }
926
+ write(data) {
927
+ return new Promise((resolve, reject) => {
928
+ if (this._isOpen && this._socket) {
929
+ this._socket.send(data, this._port, this._address, (error) => {
930
+ if (error) {
931
+ reject(error);
455
932
  }
456
933
  else {
457
934
  this.emit('write', data);
@@ -460,117 +937,89 @@ class UdpPhysicalLayer extends AbstractPhysicalLayer {
460
937
  });
461
938
  }
462
939
  else {
463
- reject(new Error('Port is not open'));
940
+ reject(new ModbusError(ModbusErrorCode.PORT_NOT_OPEN, 'Port is not open'));
464
941
  }
465
942
  });
466
943
  }
467
944
  close() {
945
+ if (!this._socket) {
946
+ return Promise.resolve();
947
+ }
948
+ const socket = this._socket;
468
949
  return new Promise((resolve) => {
469
- this._isOpen = false;
470
- this._socket.removeAllListeners();
471
- this._socket.close(() => {
472
- resolve();
473
- });
950
+ socket.once('close', () => resolve());
951
+ try {
952
+ socket.close();
953
+ }
954
+ catch (_a) {
955
+ // Socket may already be closed; the registered 'close' listener still fires asynchronously.
956
+ }
474
957
  });
475
958
  }
476
959
  destroy() {
960
+ if (this._destroyed) {
961
+ return Promise.resolve();
962
+ }
477
963
  this._destroyed = true;
478
- this.removeAllListeners();
479
- return this.close();
964
+ return this.close().then(() => {
965
+ this.removeAllListeners();
966
+ });
480
967
  }
481
968
  }
482
969
 
483
970
  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
- }
971
+ constructor() {
972
+ super(...arguments);
973
+ Object.defineProperty(this, "_role", {
974
+ enumerable: true,
975
+ configurable: true,
976
+ writable: true,
977
+ value: void 0
978
+ });
979
+ }
980
+ get role() {
981
+ if (!this._role) {
982
+ throw new ModbusError(ModbusErrorCode.INVALID_ROLE, 'Application layer role not set');
492
983
  }
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;
984
+ return this._role;
985
+ }
986
+ set role(value) {
987
+ if (this._role && this._role !== value) {
988
+ throw new ModbusError(ModbusErrorCode.INVALID_ROLE, `Application layer role already set to ${this._role}`);
502
989
  }
990
+ this._role = value;
503
991
  }
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;
992
+ flush() {
993
+ // no-op — override in subclasses
994
+ }
995
+ addCustomFunctionCode(cfc) {
996
+ }
997
+ removeCustomFunctionCode(fc) {
529
998
  }
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
999
  }
546
1000
 
547
1001
  const MAX_FRAME_LENGTH = 256;
1002
+ const MIN_FRAME_LENGTH = 4;
548
1003
  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) {
1004
+ constructor(physicalLayer, options = {}) {
556
1005
  super();
557
- Object.defineProperty(this, "_waitingResponse", {
1006
+ Object.defineProperty(this, "PROTOCOL", {
558
1007
  enumerable: true,
559
1008
  configurable: true,
560
1009
  writable: true,
561
- value: void 0
1010
+ value: 'RTU'
562
1011
  });
563
- Object.defineProperty(this, "_timerThreePointFive", {
1012
+ Object.defineProperty(this, "_states", {
564
1013
  enumerable: true,
565
1014
  configurable: true,
566
1015
  writable: true,
567
- value: void 0
1016
+ value: new Map()
568
1017
  });
569
- Object.defineProperty(this, "_bufferRx", {
1018
+ Object.defineProperty(this, "_customFunctionCodes", {
570
1019
  enumerable: true,
571
1020
  configurable: true,
572
1021
  writable: true,
573
- value: Buffer.alloc(0)
1022
+ value: new Map()
574
1023
  });
575
1024
  Object.defineProperty(this, "_removeAllListeners", {
576
1025
  enumerable: true,
@@ -578,129 +1027,288 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
578
1027
  writable: true,
579
1028
  value: []
580
1029
  });
1030
+ Object.defineProperty(this, "_threePointFiveT", {
1031
+ enumerable: true,
1032
+ configurable: true,
1033
+ writable: true,
1034
+ value: void 0
1035
+ });
1036
+ Object.defineProperty(this, "_onePointFiveT", {
1037
+ enumerable: true,
1038
+ configurable: true,
1039
+ writable: true,
1040
+ value: void 0
1041
+ });
1042
+ Object.defineProperty(this, "_destroyed", {
1043
+ enumerable: true,
1044
+ configurable: true,
1045
+ writable: true,
1046
+ value: false
1047
+ });
1048
+ const { intervalBetweenFrames, interCharTimeout } = options;
581
1049
  let threePointFiveT = 0;
1050
+ let onePointFiveT = 0;
582
1051
  if (physicalLayer.TYPE === 'SERIAL') {
583
- if (intervalBetweenFrames && intervalBetweenFrames.endsWith('ms')) {
584
- threePointFiveT = Number(intervalBetweenFrames.slice(0, -2));
1052
+ const baudRate = physicalLayer.baudRate;
1053
+ if (intervalBetweenFrames && intervalBetweenFrames.unit === 'ms') {
1054
+ threePointFiveT = intervalBetweenFrames.value;
585
1055
  }
586
1056
  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();
1057
+ threePointFiveT =
1058
+ baudRate > 19200 ? 1.75 : Math.ceil(bitsToMs(baudRate, intervalBetweenFrames ? intervalBetweenFrames.value : 38.5));
614
1059
  }
615
- else {
616
- if (threePointFiveT) {
617
- this._timerThreePointFive = setTimeout(handleData, threePointFiveT);
1060
+ if (interCharTimeout) {
1061
+ if (interCharTimeout.unit === 'ms') {
1062
+ onePointFiveT = interCharTimeout.value;
618
1063
  }
619
1064
  else {
620
- handleData();
1065
+ onePointFiveT = baudRate > 19200 ? 0.75 : Math.ceil(bitsToMs(baudRate, interCharTimeout.value));
621
1066
  }
622
1067
  }
1068
+ }
1069
+ this._threePointFiveT = threePointFiveT;
1070
+ this._onePointFiveT = onePointFiveT;
1071
+ const handleData = (data, response, connection) => {
1072
+ let state = this.getState(connection.id);
1073
+ if (state.t15Expired && state.end > state.start) {
1074
+ this.emit('framing-error', new ModbusError(ModbusErrorCode.T1_5_EXCEEDED, 'Inter-character timeout (t1.5) exceeded'));
1075
+ state.start = 0;
1076
+ state.end = 0;
1077
+ }
1078
+ state.t15Expired = false;
1079
+ let dataOffset = 0;
1080
+ while (dataOffset < data.length) {
1081
+ const space = state.pool.length - state.end;
1082
+ if (space === 0) {
1083
+ this.clearStateTimers(state);
1084
+ this.flushBuffer(connection.id, response, connection, this._threePointFiveT > 0);
1085
+ state = this.getState(connection.id);
1086
+ if (state.pool.length - state.end === 0) {
1087
+ this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_DATA, 'Frame buffer exhausted before complete frame received'));
1088
+ state.start = 0;
1089
+ state.end = 0;
1090
+ state.t15Expired = false;
1091
+ }
1092
+ continue;
1093
+ }
1094
+ const toCopy = Math.min(space, data.length - dataOffset);
1095
+ data.copy(state.pool, state.end, dataOffset, dataOffset + toCopy);
1096
+ state.end += toCopy;
1097
+ dataOffset += toCopy;
1098
+ }
1099
+ this.clearStateTimers(state);
1100
+ const commitFrame = () => {
1101
+ this.clearStateTimers(state);
1102
+ this.flushBuffer(connection.id, response, connection, this._threePointFiveT > 0);
1103
+ };
1104
+ if (state.end - state.start >= MAX_FRAME_LENGTH) {
1105
+ commitFrame();
1106
+ }
1107
+ else if (this._threePointFiveT) {
1108
+ if (this._onePointFiveT > 0) {
1109
+ state.interCharTimer = setTimeout(() => {
1110
+ state.interCharTimer = undefined;
1111
+ state.t15Expired = true;
1112
+ }, this._onePointFiveT);
1113
+ }
1114
+ state.timer = setTimeout(commitFrame, this._threePointFiveT);
1115
+ }
1116
+ else {
1117
+ commitFrame();
1118
+ }
623
1119
  };
624
1120
  physicalLayer.on('data', handleData);
625
- this._removeAllListeners.push(() => {
626
- physicalLayer.removeListener('data', handleData);
627
- });
1121
+ this._removeAllListeners.push(() => physicalLayer.removeListener('data', handleData));
1122
+ const handleConnectionClose = (connection) => {
1123
+ this.clearState(connection.id);
1124
+ };
1125
+ physicalLayer.on('connection-close', handleConnectionClose);
1126
+ this._removeAllListeners.push(() => physicalLayer.removeListener('connection-close', handleConnectionClose));
628
1127
  const handleClose = () => {
629
- clearTimeout(this._timerThreePointFive);
630
- this._bufferRx = Buffer.alloc(0);
1128
+ for (const key of [...this._states.keys()]) {
1129
+ this.clearState(key);
1130
+ }
631
1131
  };
632
1132
  physicalLayer.on('close', handleClose);
633
- this._removeAllListeners.push(() => {
634
- physicalLayer.removeListener('close', handleClose);
635
- });
1133
+ this._removeAllListeners.push(() => physicalLayer.removeListener('close', handleClose));
636
1134
  }
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
- }
1135
+ getState(key) {
1136
+ let state = this._states.get(key);
1137
+ if (!state) {
1138
+ state = { pool: Buffer.alloc(MAX_FRAME_LENGTH * 2), start: 0, end: 0 };
1139
+ this._states.set(key, state);
1140
+ }
1141
+ return state;
1142
+ }
1143
+ clearStateTimers(state) {
1144
+ if (state.timer) {
1145
+ clearTimeout(state.timer);
1146
+ state.timer = undefined;
1147
+ }
1148
+ if (state.interCharTimer) {
1149
+ clearTimeout(state.interCharTimer);
1150
+ state.interCharTimer = undefined;
1151
+ }
1152
+ }
1153
+ clearState(key) {
1154
+ const state = this._states.get(key);
1155
+ if (state) {
1156
+ this.clearStateTimers(state);
1157
+ }
1158
+ this._states.delete(key);
1159
+ }
1160
+ flush() {
1161
+ for (const state of this._states.values()) {
1162
+ this.clearStateTimers(state);
1163
+ state.start = 0;
1164
+ state.end = 0;
1165
+ }
1166
+ this._states.clear();
1167
+ }
1168
+ addCustomFunctionCode(cfc) {
1169
+ if (!isUint8(cfc.fc)) {
1170
+ throw new ModbusError(ModbusErrorCode.RANGE, `fc must be an integer in 0..255, got ${cfc.fc}`);
1171
+ }
1172
+ this._customFunctionCodes.set(cfc.fc, cfc);
1173
+ }
1174
+ removeCustomFunctionCode(fc) {
1175
+ this._customFunctionCodes.delete(fc);
1176
+ }
1177
+ flushBuffer(key, response, connection, strict) {
1178
+ const state = this._states.get(key);
1179
+ if (!state) {
1180
+ return;
1181
+ }
1182
+ while (state.end - state.start > 0) {
1183
+ const buffer = state.pool.subarray(state.start, state.end);
1184
+ const result = this.tryExtract(buffer);
1185
+ if (result.kind === 'frame') {
1186
+ state.start += result.frame.length;
1187
+ this.deliverFrame(result.frame, response, connection);
1188
+ }
1189
+ else if (result.kind === 'skip') {
1190
+ if (strict) {
1191
+ this.emit('framing-error', new ModbusError(ModbusErrorCode.CRC_MISMATCH, 'CRC mismatch'));
1192
+ state.start = 0;
1193
+ state.end = 0;
1194
+ state.t15Expired = false;
1195
+ return;
666
1196
  }
1197
+ state.start += 1;
667
1198
  }
668
- const crcPassed = buffer.readUInt16LE(buffer.length - 2) === crc(buffer.subarray(0, buffer.length - 2));
669
- if (crcPassed) {
670
- callback(null, frame);
1199
+ else if (result.kind === 'insufficient') {
1200
+ if (state.end - state.start >= MAX_FRAME_LENGTH) {
1201
+ state.start += 1;
1202
+ continue;
1203
+ }
1204
+ if (strict) {
1205
+ 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'));
1206
+ state.start = 0;
1207
+ state.end = 0;
1208
+ state.t15Expired = false;
1209
+ return;
1210
+ }
1211
+ if (state.t15Expired) {
1212
+ this.emit('framing-error', new ModbusError(ModbusErrorCode.T1_5_EXCEEDED, 'Inter-character timeout (t1.5) exceeded'));
1213
+ state.start = 0;
1214
+ state.end = 0;
1215
+ state.t15Expired = false;
1216
+ }
1217
+ break;
671
1218
  }
672
1219
  else {
673
- callback(new Error('CRC check failed'));
1220
+ this.emit('framing-error', result.error);
1221
+ state.start = 0;
1222
+ state.end = 0;
1223
+ return;
1224
+ }
1225
+ }
1226
+ if (state.start > 0) {
1227
+ if (state.start < state.end) {
1228
+ state.pool.copy(state.pool, 0, state.start, state.end);
674
1229
  }
1230
+ state.end -= state.start;
1231
+ state.start = 0;
675
1232
  }
676
- else {
677
- callback(new Error('Insufficient data length'));
1233
+ if (state.end === 0 && !state.timer) {
1234
+ this._states.delete(key);
678
1235
  }
679
1236
  }
680
- startWaitingResponse(preCheck, callback) {
681
- this._waitingResponse = { preCheck, callback };
682
- clearTimeout(this._timerThreePointFive);
683
- this._bufferRx = Buffer.alloc(0);
1237
+ tryExtract(buffer) {
1238
+ if (buffer.length < MIN_FRAME_LENGTH) {
1239
+ return { kind: 'insufficient' };
1240
+ }
1241
+ const isResponse = this.role === 'MASTER';
1242
+ const fc = buffer[1];
1243
+ const cfc = this._customFunctionCodes.get(fc);
1244
+ if (cfc) {
1245
+ const predictor = isResponse ? cfc.predictResponseLength : cfc.predictRequestLength;
1246
+ const expected = predictor(buffer);
1247
+ if (expected === null) {
1248
+ return { kind: 'insufficient' };
1249
+ }
1250
+ return this.checkExpected(buffer, expected);
1251
+ }
1252
+ const result = predictRtuFrameLength(buffer, isResponse);
1253
+ if (result.kind === 'length') {
1254
+ return this.checkExpected(buffer, result.length);
1255
+ }
1256
+ if (result.kind === 'need-more') {
1257
+ return { kind: 'insufficient' };
1258
+ }
1259
+ return {
1260
+ kind: 'error',
1261
+ error: new ModbusError(ModbusErrorCode.UNKNOWN_FC, `Unknown function code 0x${fc.toString(16).padStart(2, '0')} — register a CustomFunctionCode to frame this FC`),
1262
+ };
684
1263
  }
685
- stopWaitingResponse() {
686
- this._waitingResponse = undefined;
1264
+ checkExpected(buffer, expected) {
1265
+ if (expected > MAX_FRAME_LENGTH || expected < MIN_FRAME_LENGTH) {
1266
+ return { kind: 'error', error: new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data') };
1267
+ }
1268
+ if (buffer.length < expected) {
1269
+ return { kind: 'insufficient' };
1270
+ }
1271
+ if (this.crcMatches(buffer, expected)) {
1272
+ return { kind: 'frame', frame: buffer.subarray(0, expected), rest: buffer.subarray(expected) };
1273
+ }
1274
+ return { kind: 'skip', rest: buffer.subarray(1) };
1275
+ }
1276
+ crcMatches(buffer, length) {
1277
+ return buffer.readUInt16LE(length - 2) === crc(buffer.subarray(0, length - 2));
1278
+ }
1279
+ deliverFrame(buffer, response, connection) {
1280
+ const frameBuf = Buffer.from(buffer);
1281
+ const frame = {
1282
+ unit: frameBuf[0],
1283
+ fc: frameBuf[1],
1284
+ data: frameBuf.subarray(2, frameBuf.length - 2),
1285
+ buffer: frameBuf,
1286
+ };
1287
+ this.emit('framing', frame, response, connection);
687
1288
  }
688
1289
  encode(data) {
689
1290
  const buffer = Buffer.alloc(data.data.length + 4);
690
1291
  buffer.writeUInt8(data.unit, 0);
691
1292
  buffer.writeUInt8(data.fc, 1);
692
- data.data.forEach((num, index) => {
693
- buffer.writeUInt8(num, 2 + index);
694
- });
1293
+ buffer.set(data.data, 2);
695
1294
  buffer.writeUInt16LE(crc(buffer.subarray(0, -2)), buffer.length - 2);
696
1295
  return buffer;
697
1296
  }
698
1297
  destroy() {
1298
+ if (this._destroyed) {
1299
+ return;
1300
+ }
1301
+ this._destroyed = true;
699
1302
  this.removeAllListeners();
700
- for (const removeAllListener of this._removeAllListeners) {
701
- removeAllListener();
1303
+ for (const removeListener of this._removeAllListeners) {
1304
+ removeListener();
702
1305
  }
703
- clearTimeout(this._timerThreePointFive);
1306
+ this._removeAllListeners.length = 0;
1307
+ for (const state of this._states.values()) {
1308
+ this.clearStateTimers(state);
1309
+ }
1310
+ this._states.clear();
1311
+ this._customFunctionCodes.clear();
704
1312
  }
705
1313
  }
706
1314
 
@@ -709,26 +1317,43 @@ const CHAR_CODE = {
709
1317
  CR: '\r'.charCodeAt(0),
710
1318
  LF: '\n'.charCodeAt(0),
711
1319
  };
1320
+ // Modbus ASCII frame encodes at most 256 PDU bytes as 512 hex chars between
1321
+ // `:` and `\r`. Cap per-connection buffering so a peer that never sends `\r`
1322
+ // cannot grow `state.frame` without bound.
1323
+ const MAX_ASCII_PAYLOAD = 512;
1324
+ const HEX_DECODE = new Uint8Array(256);
1325
+ HEX_DECODE.fill(0xff);
1326
+ for (let i = 0x30; i <= 0x39; i++) {
1327
+ HEX_DECODE[i] = i - 0x30;
1328
+ }
1329
+ for (let i = 0x41; i <= 0x46; i++) {
1330
+ HEX_DECODE[i] = i - 0x41 + 10;
1331
+ }
1332
+ for (let i = 0x61; i <= 0x66; i++) {
1333
+ HEX_DECODE[i] = i - 0x61 + 10;
1334
+ }
1335
+ const HEX_ENCODE = new Uint8Array('0123456789ABCDEF'.split('').map((c) => c.charCodeAt(0)));
712
1336
  class AsciiApplicationLayer extends AbstractApplicationLayer {
713
- constructor(physicalLayer) {
1337
+ constructor(physicalLayer, options = {}) {
1338
+ var _a;
714
1339
  super();
715
- Object.defineProperty(this, "_waitingResponse", {
1340
+ Object.defineProperty(this, "PROTOCOL", {
716
1341
  enumerable: true,
717
1342
  configurable: true,
718
1343
  writable: true,
719
- value: void 0
1344
+ value: 'ASCII'
720
1345
  });
721
- Object.defineProperty(this, "_status", {
1346
+ Object.defineProperty(this, "lenientHex", {
722
1347
  enumerable: true,
723
1348
  configurable: true,
724
1349
  writable: true,
725
- value: 'idle'
1350
+ value: void 0
726
1351
  });
727
- Object.defineProperty(this, "_frame", {
1352
+ Object.defineProperty(this, "_states", {
728
1353
  enumerable: true,
729
1354
  configurable: true,
730
1355
  writable: true,
731
- value: []
1356
+ value: new Map()
732
1357
  });
733
1358
  Object.defineProperty(this, "_removeAllListeners", {
734
1359
  enumerable: true,
@@ -736,44 +1361,68 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
736
1361
  writable: true,
737
1362
  value: []
738
1363
  });
739
- const handleData = (data, response) => {
1364
+ Object.defineProperty(this, "_destroyed", {
1365
+ enumerable: true,
1366
+ configurable: true,
1367
+ writable: true,
1368
+ value: false
1369
+ });
1370
+ this.lenientHex = (_a = options.lenientHex) !== null && _a !== void 0 ? _a : false;
1371
+ const lenientHex = this.lenientHex;
1372
+ const isHexChar = (value) => {
1373
+ if (value >= 0x30 && value <= 0x39) {
1374
+ return true;
1375
+ }
1376
+ if (value >= 0x41 && value <= 0x46) {
1377
+ return true;
1378
+ }
1379
+ if (lenientHex && value >= 0x61 && value <= 0x66) {
1380
+ return true;
1381
+ }
1382
+ return false;
1383
+ };
1384
+ const handleData = (data, response, connection) => {
1385
+ const state = this.getState(connection.id);
740
1386
  data.forEach((value) => {
741
- switch (this._status) {
1387
+ switch (state.status) {
742
1388
  case 'idle': {
743
1389
  if (value === CHAR_CODE.COLON) {
744
- this._status = 'reception';
745
- this._frame = [];
1390
+ state.status = 'reception';
1391
+ state.frame = [];
746
1392
  }
747
1393
  break;
748
1394
  }
749
1395
  case 'reception': {
750
1396
  if (value === CHAR_CODE.COLON) {
751
- this._frame = [];
1397
+ state.frame = [];
752
1398
  }
753
1399
  else if (value === CHAR_CODE.CR) {
754
- this._status = 'waiting end';
1400
+ state.status = 'waiting end';
1401
+ }
1402
+ else if (state.frame.length >= MAX_ASCII_PAYLOAD) {
1403
+ state.status = 'idle';
1404
+ state.frame = [];
1405
+ this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data'));
1406
+ }
1407
+ else if (!isHexChar(value)) {
1408
+ state.status = 'idle';
1409
+ state.frame = [];
1410
+ this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_HEX, 'Invalid hex character'));
755
1411
  }
756
1412
  else {
757
- this._frame.push(value);
1413
+ state.frame.push(value);
758
1414
  }
759
1415
  break;
760
1416
  }
761
1417
  case 'waiting end': {
762
1418
  if (value === CHAR_CODE.COLON) {
763
- this._status = 'reception';
764
- this._frame = [];
1419
+ state.status = 'reception';
1420
+ state.frame = [];
765
1421
  }
766
1422
  else {
767
- this._status = 'idle';
1423
+ state.status = 'idle';
768
1424
  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
- });
1425
+ this.framing(Buffer.from(state.frame), response, connection);
777
1426
  }
778
1427
  }
779
1428
  break;
@@ -782,115 +1431,102 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
782
1431
  });
783
1432
  };
784
1433
  physicalLayer.on('data', handleData);
785
- this._removeAllListeners.push(() => {
786
- physicalLayer.removeListener('data', handleData);
787
- });
1434
+ this._removeAllListeners.push(() => physicalLayer.removeListener('data', handleData));
1435
+ const handleConnectionClose = (connection) => {
1436
+ this._states.delete(connection.id);
1437
+ };
1438
+ physicalLayer.on('connection-close', handleConnectionClose);
1439
+ this._removeAllListeners.push(() => physicalLayer.removeListener('connection-close', handleConnectionClose));
788
1440
  const handleClose = () => {
789
- this._status = 'idle';
790
- this._frame = [];
1441
+ this._states.clear();
791
1442
  };
792
1443
  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'));
1444
+ this._removeAllListeners.push(() => physicalLayer.removeListener('close', handleClose));
1445
+ }
1446
+ getState(key) {
1447
+ let state = this._states.get(key);
1448
+ if (!state) {
1449
+ state = { status: 'idle', frame: [] };
1450
+ this._states.set(key, state);
853
1451
  }
1452
+ return state;
854
1453
  }
855
- startWaitingResponse(preCheck, callback) {
856
- this._waitingResponse = { preCheck, callback };
857
- this._status = 'idle';
858
- this._frame = [];
1454
+ flush() {
1455
+ this._states.clear();
859
1456
  }
860
- stopWaitingResponse() {
861
- this._waitingResponse = undefined;
1457
+ framing(_buffer, response, connection) {
1458
+ if (_buffer.length < 6) {
1459
+ this.emit('framing-error', new ModbusError(ModbusErrorCode.INSUFFICIENT_DATA, 'Insufficient data length'));
1460
+ return;
1461
+ }
1462
+ if (_buffer.length % 2 !== 0) {
1463
+ this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data'));
1464
+ return;
1465
+ }
1466
+ const buffer = Buffer.allocUnsafe(_buffer.length / 2);
1467
+ for (let i = 0; i < _buffer.length; i += 2) {
1468
+ const hi = HEX_DECODE[_buffer[i]];
1469
+ const lo = HEX_DECODE[_buffer[i + 1]];
1470
+ if (hi === 0xff || lo === 0xff) {
1471
+ this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_HEX, 'Invalid hex character'));
1472
+ return;
1473
+ }
1474
+ buffer[i / 2] = (hi << 4) | lo;
1475
+ }
1476
+ const frame = {
1477
+ unit: buffer[0],
1478
+ fc: buffer[1],
1479
+ data: buffer.subarray(2, buffer.length - 1),
1480
+ buffer: _buffer,
1481
+ };
1482
+ const lrcPassed = buffer[buffer.length - 1] === lrc(buffer.subarray(0, buffer.length - 1));
1483
+ if (!lrcPassed) {
1484
+ this.emit('framing-error', new ModbusError(ModbusErrorCode.LRC_MISMATCH, 'LRC check failed'));
1485
+ return;
1486
+ }
1487
+ this.emit('framing', frame, response, connection);
862
1488
  }
863
1489
  encode(data) {
864
1490
  const buffer = Buffer.alloc(data.data.length + 3);
865
1491
  buffer.writeUInt8(data.unit, 0);
866
1492
  buffer.writeUInt8(data.fc, 1);
867
- data.data.forEach((num, index) => {
868
- buffer.writeUInt8(num, 2 + index);
869
- });
1493
+ buffer.set(data.data, 2);
870
1494
  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');
1495
+ const out = Buffer.allocUnsafe(1 + buffer.length * 2 + 2);
1496
+ out[0] = CHAR_CODE.COLON;
1497
+ for (let i = 0; i < buffer.length; i++) {
1498
+ const byte = buffer[i];
1499
+ out[1 + i * 2] = HEX_ENCODE[byte >> 4];
1500
+ out[2 + i * 2] = HEX_ENCODE[byte & 0x0f];
874
1501
  }
875
- frame += '\r\n';
876
- return Buffer.from(frame);
1502
+ out[out.length - 2] = CHAR_CODE.CR;
1503
+ out[out.length - 1] = CHAR_CODE.LF;
1504
+ return out;
877
1505
  }
878
1506
  destroy() {
1507
+ if (this._destroyed) {
1508
+ return;
1509
+ }
1510
+ this._destroyed = true;
879
1511
  this.removeAllListeners();
880
- for (const removeAllListener of this._removeAllListeners) {
881
- removeAllListener();
1512
+ for (const removeListener of this._removeAllListeners) {
1513
+ removeListener();
882
1514
  }
1515
+ this._removeAllListeners.length = 0;
1516
+ this._states.clear();
883
1517
  }
884
1518
  }
885
1519
 
1520
+ const MAX_TCP_FRAME = 260;
1521
+ const EMPTY_BUFFER = Buffer.alloc(0);
886
1522
  class TcpApplicationLayer extends AbstractApplicationLayer {
887
1523
  constructor(physicalLayer) {
888
1524
  super();
889
- Object.defineProperty(this, "_waitingResponse", {
1525
+ Object.defineProperty(this, "PROTOCOL", {
890
1526
  enumerable: true,
891
1527
  configurable: true,
892
1528
  writable: true,
893
- value: void 0
1529
+ value: 'TCP'
894
1530
  });
895
1531
  Object.defineProperty(this, "_transactionId", {
896
1532
  enumerable: true,
@@ -898,106 +1534,229 @@ class TcpApplicationLayer extends AbstractApplicationLayer {
898
1534
  writable: true,
899
1535
  value: 1
900
1536
  });
1537
+ Object.defineProperty(this, "_buffers", {
1538
+ enumerable: true,
1539
+ configurable: true,
1540
+ writable: true,
1541
+ value: new Map()
1542
+ });
901
1543
  Object.defineProperty(this, "_removeAllListeners", {
902
1544
  enumerable: true,
903
1545
  configurable: true,
904
1546
  writable: true,
905
1547
  value: []
906
1548
  });
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);
1549
+ Object.defineProperty(this, "_destroyed", {
1550
+ enumerable: true,
1551
+ configurable: true,
1552
+ writable: true,
1553
+ value: false
1554
+ });
1555
+ const handleData = (data, response, connection) => {
1556
+ var _a;
1557
+ let buffer = (_a = this._buffers.get(connection.id)) !== null && _a !== void 0 ? _a : EMPTY_BUFFER;
1558
+ buffer = buffer.length === 0 ? data : Buffer.concat([buffer, data]);
1559
+ while (buffer.length > 0) {
1560
+ const result = this.tryExtract(buffer);
1561
+ if (result.kind === 'frame') {
1562
+ this.processFrame(result.frame, response, connection);
1563
+ buffer = result.rest;
1564
+ }
1565
+ else if (result.kind === 'insufficient') {
1566
+ break;
914
1567
  }
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
- }
1568
+ else {
1569
+ this.emit('framing-error', result.error);
1570
+ buffer = EMPTY_BUFFER;
1571
+ break;
954
1572
  }
955
- callback(null, frame);
1573
+ }
1574
+ if (buffer.length === 0) {
1575
+ this._buffers.delete(connection.id);
956
1576
  }
957
1577
  else {
958
- callback(new Error('Invalid data'));
1578
+ this._buffers.set(connection.id, buffer);
959
1579
  }
1580
+ };
1581
+ physicalLayer.on('data', handleData);
1582
+ this._removeAllListeners.push(() => physicalLayer.removeListener('data', handleData));
1583
+ const handleConnectionClose = (connection) => {
1584
+ this._buffers.delete(connection.id);
1585
+ };
1586
+ physicalLayer.on('connection-close', handleConnectionClose);
1587
+ this._removeAllListeners.push(() => physicalLayer.removeListener('connection-close', handleConnectionClose));
1588
+ const handleClose = () => {
1589
+ this._buffers.clear();
1590
+ };
1591
+ physicalLayer.on('close', handleClose);
1592
+ this._removeAllListeners.push(() => physicalLayer.removeListener('close', handleClose));
1593
+ }
1594
+ tryExtract(buffer) {
1595
+ if (buffer.length < 8) {
1596
+ return { kind: 'insufficient' };
960
1597
  }
961
- else {
962
- callback(new Error('Insufficient data length'));
1598
+ if (buffer[2] !== 0 || buffer[3] !== 0) {
1599
+ return { kind: 'error', error: new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data') };
963
1600
  }
964
- }
965
- startWaitingResponse(preCheck, callback) {
966
- this._waitingResponse = { preCheck, callback };
967
- }
968
- stopWaitingResponse() {
969
- this._waitingResponse = undefined;
1601
+ const length = buffer.readUInt16BE(4);
1602
+ const total = 6 + length;
1603
+ if (total > MAX_TCP_FRAME || length < 2) {
1604
+ return { kind: 'error', error: new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data') };
1605
+ }
1606
+ if (buffer.length < total) {
1607
+ return { kind: 'insufficient' };
1608
+ }
1609
+ return { kind: 'frame', frame: buffer.subarray(0, total), rest: buffer.subarray(total) };
1610
+ }
1611
+ processFrame(buffer, response, connection) {
1612
+ const frame = {
1613
+ transaction: buffer.readUInt16BE(0),
1614
+ unit: buffer[6],
1615
+ fc: buffer[7],
1616
+ data: buffer.subarray(8),
1617
+ buffer,
1618
+ };
1619
+ this.emit('framing', frame, response, connection);
970
1620
  }
971
1621
  encode(data) {
972
1622
  var _a;
1623
+ const transaction = (_a = data.transaction) !== null && _a !== void 0 ? _a : this._transactionId;
973
1624
  const buffer = Buffer.alloc(data.data.length + 8);
974
- buffer.writeUInt16BE((_a = data.transaction) !== null && _a !== void 0 ? _a : this._transactionId, 0);
1625
+ buffer.writeUInt16BE(transaction, 0);
975
1626
  buffer.writeUInt16BE(0, 2);
976
1627
  buffer.writeUInt16BE(data.data.length + 2, 4);
977
1628
  buffer.writeUInt8(data.unit, 6);
978
1629
  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;
1630
+ buffer.set(data.data, 8);
1631
+ if (data.transaction === undefined) {
1632
+ this._transactionId = (this._transactionId + 1) % 65536 || 1;
1633
+ }
983
1634
  return buffer;
984
1635
  }
985
1636
  destroy() {
1637
+ if (this._destroyed) {
1638
+ return;
1639
+ }
1640
+ this._destroyed = true;
986
1641
  this.removeAllListeners();
987
- for (const removeAllListener of this._removeAllListeners) {
988
- removeAllListener();
1642
+ for (const removeListener of this._removeAllListeners) {
1643
+ removeListener();
989
1644
  }
1645
+ this._removeAllListeners.length = 0;
1646
+ this._buffers.clear();
990
1647
  }
991
1648
  }
992
1649
 
1650
+ const FIFO_KEY = 'fifo';
1651
+ class MasterSession {
1652
+ constructor() {
1653
+ Object.defineProperty(this, "_waiters", {
1654
+ enumerable: true,
1655
+ configurable: true,
1656
+ writable: true,
1657
+ value: new Map()
1658
+ });
1659
+ }
1660
+ start(key, preCheck, callback) {
1661
+ this._waiters.set(key, { preCheck, callback });
1662
+ }
1663
+ stop(key) {
1664
+ this._waiters.delete(key);
1665
+ }
1666
+ stopAll(error) {
1667
+ const waiters = [...this._waiters.values()];
1668
+ this._waiters.clear();
1669
+ for (const w of waiters) {
1670
+ w.callback(error);
1671
+ }
1672
+ }
1673
+ has(key) {
1674
+ return this._waiters.has(key);
1675
+ }
1676
+ handleFrame(frame) {
1677
+ var _a;
1678
+ const key = (_a = frame.transaction) !== null && _a !== void 0 ? _a : FIFO_KEY;
1679
+ const waiter = this._waiters.get(key);
1680
+ if (!waiter) {
1681
+ return;
1682
+ }
1683
+ if (!this.runPreChecks(waiter, frame)) {
1684
+ return;
1685
+ }
1686
+ waiter.callback(null, frame);
1687
+ }
1688
+ handleError(error) {
1689
+ // Framing errors lose transaction context; reject every in-flight waiter.
1690
+ this.stopAll(error);
1691
+ }
1692
+ runPreChecks(waiter, frame) {
1693
+ for (const check of waiter.preCheck) {
1694
+ const res = check(frame);
1695
+ if (typeof res === 'undefined') {
1696
+ waiter.callback(new ModbusError(ModbusErrorCode.INSUFFICIENT_DATA, 'Insufficient data length'));
1697
+ return false;
1698
+ }
1699
+ if (typeof res === 'number') {
1700
+ if (frame.data.length < res) {
1701
+ waiter.callback(new ModbusError(ModbusErrorCode.INSUFFICIENT_DATA, 'Insufficient data length'));
1702
+ return false;
1703
+ }
1704
+ if (frame.data.length !== res) {
1705
+ waiter.callback(new ModbusError(ModbusErrorCode.INVALID_RESPONSE, 'Invalid response'));
1706
+ return false;
1707
+ }
1708
+ continue;
1709
+ }
1710
+ if (!res) {
1711
+ waiter.callback(new ModbusError(ModbusErrorCode.INVALID_RESPONSE, 'Invalid response'));
1712
+ return false;
1713
+ }
1714
+ }
1715
+ return true;
1716
+ }
1717
+ }
1718
+
1719
+ /******************************************************************************
1720
+ Copyright (c) Microsoft Corporation.
1721
+
1722
+ Permission to use, copy, modify, and/or distribute this software for any
1723
+ purpose with or without fee is hereby granted.
1724
+
1725
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
1726
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
1727
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
1728
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
1729
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
1730
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
1731
+ PERFORMANCE OF THIS SOFTWARE.
1732
+ ***************************************************************************** */
1733
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
1734
+
1735
+
1736
+ function __awaiter(thisArg, _arguments, P, generator) {
1737
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
1738
+ return new (P || (P = Promise))(function (resolve, reject) {
1739
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
1740
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
1741
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
1742
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
1743
+ });
1744
+ }
1745
+
1746
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
1747
+ var e = new Error(message);
1748
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
1749
+ };
1750
+
993
1751
  class ModbusMaster extends EventEmitter {
994
1752
  get isOpen() {
995
1753
  return this.physicalLayer.isOpen;
996
1754
  }
997
1755
  get destroyed() {
998
- return this.physicalLayer.destroyed;
1756
+ return this._cleanLevel === 'destroy' || this.physicalLayer.destroyed;
999
1757
  }
1000
- constructor(applicationLayer, physicalLayer, timeout = 1000) {
1758
+ constructor(applicationLayer, physicalLayer, options = {}) {
1759
+ var _a, _b;
1001
1760
  super();
1002
1761
  Object.defineProperty(this, "applicationLayer", {
1003
1762
  enumerable: true,
@@ -1011,11 +1770,53 @@ class ModbusMaster extends EventEmitter {
1011
1770
  writable: true,
1012
1771
  value: physicalLayer
1013
1772
  });
1773
+ Object.defineProperty(this, "_masterSession", {
1774
+ enumerable: true,
1775
+ configurable: true,
1776
+ writable: true,
1777
+ value: new MasterSession()
1778
+ });
1779
+ Object.defineProperty(this, "_queue", {
1780
+ enumerable: true,
1781
+ configurable: true,
1782
+ writable: true,
1783
+ value: []
1784
+ });
1785
+ Object.defineProperty(this, "_draining", {
1786
+ enumerable: true,
1787
+ configurable: true,
1788
+ writable: true,
1789
+ value: false
1790
+ });
1791
+ Object.defineProperty(this, "_nextTid", {
1792
+ enumerable: true,
1793
+ configurable: true,
1794
+ writable: true,
1795
+ value: 1
1796
+ });
1797
+ Object.defineProperty(this, "_closed", {
1798
+ enumerable: true,
1799
+ configurable: true,
1800
+ writable: true,
1801
+ value: false
1802
+ });
1803
+ Object.defineProperty(this, "_cleanLevel", {
1804
+ enumerable: true,
1805
+ configurable: true,
1806
+ writable: true,
1807
+ value: 'none'
1808
+ });
1014
1809
  Object.defineProperty(this, "timeout", {
1015
1810
  enumerable: true,
1016
1811
  configurable: true,
1017
1812
  writable: true,
1018
- value: timeout
1813
+ value: void 0
1814
+ });
1815
+ Object.defineProperty(this, "concurrent", {
1816
+ enumerable: true,
1817
+ configurable: true,
1818
+ writable: true,
1819
+ value: void 0
1019
1820
  });
1020
1821
  Object.defineProperty(this, "writeFC1", {
1021
1822
  enumerable: true,
@@ -1089,6 +1890,12 @@ class ModbusMaster extends EventEmitter {
1089
1890
  writable: true,
1090
1891
  value: void 0
1091
1892
  });
1893
+ this.timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : 1000;
1894
+ this.concurrent = (_b = options.concurrent) !== null && _b !== void 0 ? _b : false;
1895
+ if (this.concurrent && this.applicationLayer.PROTOCOL !== 'TCP') {
1896
+ throw new ModbusError(ModbusErrorCode.CONCURRENT_NOT_TCP, 'concurrent mode requires a Modbus TCP application layer');
1897
+ }
1898
+ this.applicationLayer.role = 'MASTER';
1092
1899
  this.writeFC1 = this.readCoils;
1093
1900
  this.writeFC2 = this.readDiscreteInputs;
1094
1901
  this.writeFC3 = this.readHoldingRegisters;
@@ -1101,6 +1908,12 @@ class ModbusMaster extends EventEmitter {
1101
1908
  this.handleFC22 = this.maskWriteRegister;
1102
1909
  this.handleFC23 = this.readAndWriteMultipleRegisters;
1103
1910
  this.handleFC43_14 = this.readDeviceIdentification;
1911
+ applicationLayer.on('framing', (frame) => {
1912
+ this._masterSession.handleFrame(frame);
1913
+ });
1914
+ applicationLayer.on('framing-error', (error) => {
1915
+ this._masterSession.handleError(error);
1916
+ });
1104
1917
  physicalLayer.on('error', (error) => {
1105
1918
  this.emit('error', error);
1106
1919
  });
@@ -1108,50 +1921,90 @@ class ModbusMaster extends EventEmitter {
1108
1921
  this.emit('close');
1109
1922
  });
1110
1923
  }
1111
- waitResponse(request, response, timeout) {
1924
+ send(adu, preCheck, timeout, broadcast) {
1925
+ const task = () => this._exchange(adu, preCheck, timeout, broadcast);
1926
+ if (this.concurrent) {
1927
+ return task();
1928
+ }
1929
+ return new Promise((resolve, reject) => {
1930
+ const run = () => __awaiter(this, void 0, void 0, function* () {
1931
+ try {
1932
+ resolve(yield task());
1933
+ }
1934
+ catch (error) {
1935
+ reject(error);
1936
+ }
1937
+ });
1938
+ this._queue.push({ run, cancel: reject });
1939
+ this._drain();
1940
+ });
1941
+ }
1942
+ _drain() {
1943
+ return __awaiter(this, void 0, void 0, function* () {
1944
+ if (this._draining) {
1945
+ return;
1946
+ }
1947
+ this._draining = true;
1948
+ try {
1949
+ while (this._queue.length > 0) {
1950
+ const item = this._queue.shift();
1951
+ yield item.run();
1952
+ }
1953
+ }
1954
+ finally {
1955
+ this._draining = false;
1956
+ }
1957
+ });
1958
+ }
1959
+ _exchange(adu, preCheck, timeout, broadcast) {
1112
1960
  return new Promise((resolve, reject) => {
1961
+ if (this._closed) {
1962
+ reject(new ModbusError(ModbusErrorCode.MASTER_CLOSED, 'Master closed'));
1963
+ return;
1964
+ }
1965
+ const usesTid = this.applicationLayer.PROTOCOL === 'TCP' && !broadcast;
1966
+ let tid;
1967
+ if (usesTid) {
1968
+ tid = this._nextTid;
1969
+ this._nextTid = (this._nextTid + 1) % 65536 || 1;
1970
+ }
1971
+ const key = tid !== null && tid !== void 0 ? tid : FIFO_KEY;
1972
+ const payload = this.applicationLayer.encode(Object.assign(Object.assign({}, adu), { transaction: tid }));
1113
1973
  let settled = false;
1114
- const tid = setTimeout(() => {
1115
- if (!settled) {
1116
- settled = true;
1117
- this.applicationLayer.stopWaitingResponse();
1118
- reject(new Error('Timeout'));
1974
+ const settle = (error, frame) => {
1975
+ if (settled) {
1976
+ return;
1977
+ }
1978
+ settled = true;
1979
+ clearTimeout(timer);
1980
+ this._masterSession.stop(key);
1981
+ if (error) {
1982
+ reject(error);
1983
+ }
1984
+ else {
1985
+ resolve(frame);
1119
1986
  }
1120
- }, timeout);
1987
+ };
1988
+ const timer = setTimeout(() => settle(new ModbusError(ModbusErrorCode.TIMEOUT, 'Timeout')), timeout);
1989
+ // FIFO mode: clear stale buffer state before this request.
1990
+ // Concurrent mode: must not flush — other in-flight requests share the buffer.
1991
+ if (!this.concurrent) {
1992
+ this.applicationLayer.flush();
1993
+ }
1121
1994
  this.physicalLayer
1122
- .write(request.data)
1995
+ .write(payload)
1123
1996
  .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
- }
1997
+ if (settled) {
1998
+ return;
1145
1999
  }
1146
- })
1147
- .catch((error) => {
1148
- if (!settled) {
1149
- settled = true;
1150
- clearTimeout(tid);
1151
- this.applicationLayer.stopWaitingResponse();
1152
- reject(error);
2000
+ if (broadcast) {
2001
+ settle(null);
2002
+ return;
1153
2003
  }
1154
- });
2004
+ const finalChecks = usesTid ? [(frame) => frame.transaction === tid, ...preCheck] : preCheck;
2005
+ this._masterSession.start(key, finalChecks, (error, frame) => settle(error, frame));
2006
+ })
2007
+ .catch((error) => settle(error));
1155
2008
  });
1156
2009
  }
1157
2010
  writeFC1Or2(unit, fc, address, length, timeout) {
@@ -1159,104 +2012,60 @@ class ModbusMaster extends EventEmitter {
1159
2012
  const bufferTx = Buffer.alloc(4);
1160
2013
  bufferTx.writeUInt16BE(address, 0);
1161
2014
  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) => {
2015
+ 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
2016
  if (frame) {
1173
- return Object.assign(Object.assign({}, frame), { data: Array.from({ length }).map((_, index) => (frame.data[1 + ~~(index / 8)] & (1 << index % 8)) > 0) });
2017
+ return Object.assign(Object.assign({}, frame), { data: Array.from({ length }, (_, index) => (frame.data[1 + ~~(index / 8)] & (1 << index % 8)) > 0) });
1174
2018
  }
1175
2019
  });
1176
2020
  }
1177
2021
  readCoils(unit, address, length, timeout = this.timeout) {
1178
- return this.writeFC1Or2(unit, 0x01, address, length, timeout);
2022
+ return this.writeFC1Or2(unit, FunctionCode.READ_COILS, address, length, timeout);
1179
2023
  }
1180
2024
  readDiscreteInputs(unit, address, length, timeout = this.timeout) {
1181
- return this.writeFC1Or2(unit, 0x02, address, length, timeout);
2025
+ return this.writeFC1Or2(unit, FunctionCode.READ_DISCRETE_INPUTS, address, length, timeout);
1182
2026
  }
1183
2027
  writeFC3Or4(unit, fc, address, length, timeout) {
1184
2028
  const byteCount = length * 2;
1185
2029
  const bufferTx = Buffer.alloc(4);
1186
2030
  bufferTx.writeUInt16BE(address, 0);
1187
2031
  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) => {
2032
+ 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
2033
  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)) });
2034
+ const bufferRx = frame.data.subarray(1);
2035
+ return Object.assign(Object.assign({}, frame), { data: Array.from({ length }, (_, index) => bufferRx.readUInt16BE(index * 2)) });
1201
2036
  }
1202
2037
  });
1203
2038
  }
1204
2039
  readHoldingRegisters(unit, address, length, timeout = this.timeout) {
1205
- return this.writeFC3Or4(unit, 0x03, address, length, timeout);
2040
+ return this.writeFC3Or4(unit, FunctionCode.READ_HOLDING_REGISTERS, address, length, timeout);
1206
2041
  }
1207
2042
  readInputRegisters(unit, address, length, timeout = this.timeout) {
1208
- return this.writeFC3Or4(unit, 0x04, address, length, timeout);
2043
+ return this.writeFC3Or4(unit, FunctionCode.READ_INPUT_REGISTERS, address, length, timeout);
1209
2044
  }
1210
2045
  writeSingleCoil(unit, address, value, timeout = this.timeout) {
1211
- const fc = 0x05;
2046
+ const fc = FunctionCode.WRITE_SINGLE_COIL;
1212
2047
  const bufferTx = Buffer.alloc(4);
1213
2048
  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) => {
2049
+ bufferTx.writeUInt16BE(value ? COIL_ON : COIL_OFF, 2);
2050
+ 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
2051
  if (frame) {
1230
2052
  return Object.assign(Object.assign({}, frame), { data: value });
1231
2053
  }
1232
2054
  });
1233
2055
  }
1234
2056
  writeSingleRegister(unit, address, value, timeout = this.timeout) {
1235
- const fc = 0x06;
2057
+ const fc = FunctionCode.WRITE_SINGLE_REGISTER;
1236
2058
  const bufferTx = Buffer.alloc(4);
1237
2059
  bufferTx.writeUInt16BE(address, 0);
1238
2060
  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) => {
2061
+ 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
2062
  if (frame) {
1254
2063
  return Object.assign(Object.assign({}, frame), { data: value });
1255
2064
  }
1256
2065
  });
1257
2066
  }
1258
2067
  writeMultipleCoils(unit, address, value, timeout = this.timeout) {
1259
- const fc = 0x0f;
2068
+ const fc = FunctionCode.WRITE_MULTIPLE_COILS;
1260
2069
  const byteCount = Math.ceil(value.length / 8);
1261
2070
  const bufferTx = Buffer.alloc(5 + byteCount);
1262
2071
  bufferTx.writeUInt16BE(address, 0);
@@ -1267,23 +2076,14 @@ class ModbusMaster extends EventEmitter {
1267
2076
  bufferTx[5 + ~~(i / 8)] |= 1 << i % 8;
1268
2077
  }
1269
2078
  });
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) => {
2079
+ 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
2080
  if (frame) {
1281
2081
  return Object.assign(Object.assign({}, frame), { data: value });
1282
2082
  }
1283
2083
  });
1284
2084
  }
1285
2085
  writeMultipleRegisters(unit, address, value, timeout = this.timeout) {
1286
- const fc = 0x10;
2086
+ const fc = FunctionCode.WRITE_MULTIPLE_REGISTERS;
1287
2087
  const byteCount = value.length * 2;
1288
2088
  const bufferTx = Buffer.alloc(5 + byteCount);
1289
2089
  bufferTx.writeUInt16BE(address, 0);
@@ -1292,73 +2092,48 @@ class ModbusMaster extends EventEmitter {
1292
2092
  value.forEach((v, i) => {
1293
2093
  bufferTx.writeUInt16BE(v, 5 + i * 2);
1294
2094
  });
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) => {
2095
+ 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
2096
  if (frame) {
1306
2097
  return Object.assign(Object.assign({}, frame), { data: value });
1307
2098
  }
1308
2099
  });
1309
2100
  }
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) => {
2101
+ reportServerId(unit, serverIdLength = 1, timeout = this.timeout) {
2102
+ const fc = FunctionCode.REPORT_SERVER_ID;
2103
+ return this.send({ unit, fc, data: Buffer.alloc(0) }, [
2104
+ (frame) => frame.unit === unit && frame.fc === fc,
2105
+ (frame) => {
2106
+ if (frame.data.length >= 2 + serverIdLength) {
2107
+ return 1 + frame.data[0];
2108
+ }
2109
+ },
2110
+ ], timeout, unit === 0).then((frame) => {
1329
2111
  if (frame) {
2112
+ const runStatusIndex = 1 + serverIdLength;
1330
2113
  return Object.assign(Object.assign({}, frame), { data: {
1331
- serverId: frame.data[1],
1332
- runIndicatorStatus: frame.data[2] === 0xff,
1333
- additionalData: frame.data.slice(3),
2114
+ serverId: serverIdLength === 1 ? frame.data[1] : Array.from(frame.data.subarray(1, runStatusIndex)),
2115
+ runIndicatorStatus: frame.data[runStatusIndex] === 0xff,
2116
+ additionalData: Array.from(frame.data.subarray(runStatusIndex + 1)),
1334
2117
  } });
1335
2118
  }
1336
2119
  });
1337
2120
  }
1338
2121
  maskWriteRegister(unit, address, andMask, orMask, timeout = this.timeout) {
1339
- const fc = 0x16;
2122
+ const fc = FunctionCode.MASK_WRITE_REGISTER;
1340
2123
  const bufferTx = Buffer.alloc(6);
1341
2124
  bufferTx.writeUInt16BE(address, 0);
1342
2125
  bufferTx.writeUInt16BE(andMask, 2);
1343
2126
  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) => {
2127
+ 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
2128
  if (frame) {
1355
2129
  return Object.assign(Object.assign({}, frame), { data: { andMask, orMask } });
1356
2130
  }
1357
2131
  });
1358
2132
  }
1359
2133
  readAndWriteMultipleRegisters(unit, read, write, timeout = this.timeout) {
1360
- const fc = 0x17;
2134
+ const fc = FunctionCode.READ_WRITE_MULTIPLE_REGISTERS;
1361
2135
  const byteCount = write.value.length * 2;
2136
+ const readByteCount = read.length * 2;
1362
2137
  const bufferTx = Buffer.alloc(9 + byteCount);
1363
2138
  bufferTx.writeUInt16BE(read.address, 0);
1364
2139
  bufferTx.writeUInt16BE(read.length, 2);
@@ -1368,158 +2143,135 @@ class ModbusMaster extends EventEmitter {
1368
2143
  write.value.forEach((v, i) => {
1369
2144
  bufferTx.writeUInt16BE(v, 9 + i * 2);
1370
2145
  });
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) => {
2146
+ 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
2147
  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)) });
2148
+ const bufferRx = frame.data.subarray(1);
2149
+ return Object.assign(Object.assign({}, frame), { data: Array.from({ length: read.length }, (_, index) => bufferRx.readUInt16BE(index * 2)) });
1384
2150
  }
1385
2151
  });
1386
2152
  }
1387
2153
  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;
2154
+ const fc = FunctionCode.READ_DEVICE_IDENTIFICATION;
2155
+ let parsed;
2156
+ return this.send({ unit, fc, data: Buffer.from([MEI_READ_DEVICE_ID, readDeviceIDCode, objectId]) }, [
2157
+ (frame) => frame.unit === unit && frame.fc === fc,
2158
+ (frame) => {
2159
+ if (frame.data.length >= 6) {
2160
+ if (frame.data[0] === MEI_READ_DEVICE_ID && frame.data[1] === readDeviceIDCode) {
2161
+ const objects = [];
2162
+ let object = [];
2163
+ let totalBytes = 0;
2164
+ for (const v of frame.data.subarray(6)) {
2165
+ switch (object.length) {
2166
+ case 0:
2167
+ case 1: {
2168
+ object.push(v);
2169
+ break;
2170
+ }
2171
+ case 2: {
2172
+ object.push([v]);
2173
+ break;
2174
+ }
2175
+ case 3: {
2176
+ object[2].push(v);
2177
+ if (object[1] === object[2].length) {
2178
+ objects.push({ id: object[0], value: Buffer.from(object[2]).toString() });
2179
+ totalBytes += 2 + object[1];
2180
+ object = [];
1422
2181
  }
2182
+ break;
1423
2183
  }
1424
2184
  }
1425
- if (objects.length === frame.data[5]) {
1426
- return 6 + objects.reduce((previous, current) => previous + current, 0);
1427
- }
1428
2185
  }
1429
- else {
1430
- return false;
2186
+ if (objects.length === frame.data[5]) {
2187
+ parsed = objects;
2188
+ return 6 + totalBytes;
1431
2189
  }
1432
2190
  }
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
- }
2191
+ else {
2192
+ return false;
1458
2193
  }
1459
2194
  }
2195
+ },
2196
+ ], timeout, unit === 0).then((frame) => {
2197
+ if (frame && parsed) {
1460
2198
  return Object.assign(Object.assign({}, frame), { data: {
1461
2199
  readDeviceIDCode,
1462
2200
  conformityLevel: frame.data[2],
1463
2201
  moreFollows: frame.data[3] === 0xff,
1464
2202
  nextObjectId: frame.data[4],
1465
- objects,
2203
+ objects: parsed,
1466
2204
  } });
1467
2205
  }
1468
2206
  });
1469
2207
  }
2208
+ addCustomFunctionCode(cfc) {
2209
+ this.applicationLayer.addCustomFunctionCode(cfc);
2210
+ }
2211
+ removeCustomFunctionCode(fc) {
2212
+ this.applicationLayer.removeCustomFunctionCode(fc);
2213
+ }
2214
+ sendCustomFC(unit, fc, data, timeout = this.timeout) {
2215
+ const payload = Buffer.isBuffer(data) ? data : Buffer.from(data);
2216
+ return this.send({ unit, fc, data: payload }, [(frame) => frame.unit === unit && frame.fc === fc], timeout, unit === 0).then((frame) => {
2217
+ if (frame) {
2218
+ return frame.data;
2219
+ }
2220
+ });
2221
+ }
2222
+ _clean(level) {
2223
+ if (this._cleanLevel === 'destroy') {
2224
+ return;
2225
+ }
2226
+ if (this._cleanLevel === 'close' && level === 'close') {
2227
+ return;
2228
+ }
2229
+ const errorCode = level === 'destroy' ? ModbusErrorCode.MASTER_DESTROYED : ModbusErrorCode.MASTER_CLOSED;
2230
+ const message = level === 'destroy' ? 'Master destroyed' : 'Master closed';
2231
+ this._closed = true;
2232
+ const queued = this._queue.splice(0);
2233
+ for (const item of queued) {
2234
+ item.cancel(new ModbusError(errorCode, message));
2235
+ }
2236
+ this._masterSession.stopAll(new ModbusError(errorCode, message));
2237
+ this._cleanLevel = level;
2238
+ }
1470
2239
  open(...args) {
2240
+ if (this._cleanLevel === 'destroy') {
2241
+ return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Master is destroyed'));
2242
+ }
2243
+ this._cleanLevel = 'none';
2244
+ this._closed = false;
2245
+ this._nextTid = 1;
1471
2246
  return this.physicalLayer.open(...args);
1472
2247
  }
1473
2248
  close() {
2249
+ if (this._cleanLevel === 'destroy') {
2250
+ return Promise.resolve();
2251
+ }
2252
+ this._clean('close');
1474
2253
  return this.physicalLayer.close();
1475
2254
  }
1476
2255
  destroy() {
2256
+ if (this._cleanLevel === 'destroy') {
2257
+ return Promise.resolve();
2258
+ }
2259
+ this._clean('destroy');
1477
2260
  this.removeAllListeners();
1478
2261
  this.applicationLayer.destroy();
1479
2262
  return this.physicalLayer.destroy();
1480
2263
  }
1481
2264
  }
1482
2265
 
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
2266
  class ModbusSlave extends EventEmitter {
1516
2267
  get isOpen() {
1517
2268
  return this.physicalLayer.isOpen;
1518
2269
  }
1519
2270
  get destroyed() {
1520
- return this.physicalLayer.destroyed;
2271
+ return this._cleanLevel === 'destroy' || this.physicalLayer.destroyed;
1521
2272
  }
1522
- constructor(applicationLayer, physicalLayer) {
2273
+ constructor(applicationLayer, physicalLayer, options = {}) {
2274
+ var _a;
1523
2275
  super();
1524
2276
  Object.defineProperty(this, "applicationLayer", {
1525
2277
  enumerable: true,
@@ -1539,444 +2291,499 @@ class ModbusSlave extends EventEmitter {
1539
2291
  writable: true,
1540
2292
  value: new Map()
1541
2293
  });
1542
- Object.defineProperty(this, "_processing", {
2294
+ Object.defineProperty(this, "concurrent", {
1543
2295
  enumerable: true,
1544
2296
  configurable: true,
1545
2297
  writable: true,
1546
- value: false
2298
+ value: void 0
1547
2299
  });
1548
- Object.defineProperty(this, "_queue", {
2300
+ Object.defineProperty(this, "_queues", {
1549
2301
  enumerable: true,
1550
2302
  configurable: true,
1551
2303
  writable: true,
1552
- value: []
2304
+ value: new Map()
2305
+ });
2306
+ Object.defineProperty(this, "_customFunctionCodes", {
2307
+ enumerable: true,
2308
+ configurable: true,
2309
+ writable: true,
2310
+ value: new Map()
2311
+ });
2312
+ Object.defineProperty(this, "_locks", {
2313
+ enumerable: true,
2314
+ configurable: true,
2315
+ writable: true,
2316
+ value: new Map()
2317
+ });
2318
+ Object.defineProperty(this, "_cleanLevel", {
2319
+ enumerable: true,
2320
+ configurable: true,
2321
+ writable: true,
2322
+ value: 'none'
1553
2323
  });
1554
- applicationLayer.on('framing', (frame, _response) => {
2324
+ this.concurrent = (_a = options.concurrent) !== null && _a !== void 0 ? _a : false;
2325
+ if (this.concurrent && this.applicationLayer.PROTOCOL !== 'TCP') {
2326
+ throw new ModbusError(ModbusErrorCode.CONCURRENT_NOT_TCP, 'concurrent mode requires a Modbus TCP application layer');
2327
+ }
2328
+ this.applicationLayer.role = 'SLAVE';
2329
+ applicationLayer.on('framing', (frame, response, connection) => {
1555
2330
  if (!(frame.unit === 0x00 || this.models.has(frame.unit))) {
1556
2331
  return;
1557
2332
  }
1558
- this._queue.push({ frame, response: _response });
1559
- this._drain();
2333
+ if (this.concurrent) {
2334
+ this._processFrame(frame, response).catch((error) => this.emit('error', error));
2335
+ return;
2336
+ }
2337
+ let q = this._queues.get(connection.id);
2338
+ if (!q) {
2339
+ q = { items: [], processing: false };
2340
+ this._queues.set(connection.id, q);
2341
+ }
2342
+ q.items.push({ frame, response });
2343
+ this._drain(connection.id, q);
1560
2344
  });
1561
2345
  physicalLayer.on('error', (error) => {
1562
2346
  this.emit('error', error);
1563
2347
  });
2348
+ physicalLayer.on('connection-close', (connection) => {
2349
+ const q = this._queues.get(connection.id);
2350
+ if (!q) {
2351
+ return;
2352
+ }
2353
+ q.items.length = 0;
2354
+ if (!q.processing) {
2355
+ this._queues.delete(connection.id);
2356
+ }
2357
+ });
1564
2358
  physicalLayer.on('close', () => {
2359
+ for (const q of this._queues.values()) {
2360
+ q.items.length = 0;
2361
+ }
2362
+ this._queues.clear();
1565
2363
  this.emit('close');
1566
2364
  });
1567
2365
  }
1568
2366
  handleFC1(model, frame, response) {
1569
2367
  return __awaiter(this, void 0, void 0, function* () {
1570
2368
  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));
2369
+ if (frame.data.length !== 4) {
2370
+ return;
2371
+ }
2372
+ if (!model.readCoils) {
2373
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2374
+ return;
2375
+ }
2376
+ const address = (frame.data[0] << 8) | frame.data[1];
2377
+ const length = (frame.data[2] << 8) | frame.data[3];
2378
+ if (length < LIMITS.READ_COILS_MIN || length > LIMITS.READ_COILS_MAX) {
2379
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2380
+ return;
2381
+ }
2382
+ if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
2383
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2384
+ return;
2385
+ }
2386
+ try {
2387
+ const coils = yield model.readCoils(address, length);
2388
+ const bufferTx = Buffer.alloc(Math.ceil(length / 8));
2389
+ coils.forEach((coil, index) => {
2390
+ if (coil) {
2391
+ bufferTx[Math.floor(index / 8)] |= 1 << index % 8;
1598
2392
  }
1599
- }
1600
- else {
1601
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
1602
- }
2393
+ });
2394
+ yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
2395
+ }
2396
+ catch (error) {
2397
+ yield this.responseError(frame, response, error);
1603
2398
  }
1604
2399
  });
1605
2400
  }
1606
2401
  handleFC2(model, frame, response) {
1607
2402
  return __awaiter(this, void 0, void 0, function* () {
1608
2403
  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));
2404
+ if (frame.data.length !== 4) {
2405
+ return;
2406
+ }
2407
+ if (!model.readDiscreteInputs) {
2408
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2409
+ return;
2410
+ }
2411
+ const address = (frame.data[0] << 8) | frame.data[1];
2412
+ const length = (frame.data[2] << 8) | frame.data[3];
2413
+ if (length < LIMITS.READ_COILS_MIN || length > LIMITS.READ_COILS_MAX) {
2414
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2415
+ return;
2416
+ }
2417
+ if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).discreteInputs)) {
2418
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2419
+ return;
2420
+ }
2421
+ try {
2422
+ const discreteInputs = yield model.readDiscreteInputs(address, length);
2423
+ const bufferTx = Buffer.alloc(Math.ceil(length / 8));
2424
+ discreteInputs.forEach((discreteInput, index) => {
2425
+ if (discreteInput) {
2426
+ bufferTx[Math.floor(index / 8)] |= 1 << index % 8;
1636
2427
  }
1637
- }
1638
- else {
1639
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
1640
- }
2428
+ });
2429
+ yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
2430
+ }
2431
+ catch (error) {
2432
+ yield this.responseError(frame, response, error);
1641
2433
  }
1642
2434
  });
1643
2435
  }
1644
2436
  handleFC3(model, frame, response) {
1645
2437
  return __awaiter(this, void 0, void 0, function* () {
1646
2438
  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
- }
2439
+ if (frame.data.length !== 4) {
2440
+ return;
2441
+ }
2442
+ if (!model.readHoldingRegisters) {
2443
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2444
+ return;
2445
+ }
2446
+ const address = (frame.data[0] << 8) | frame.data[1];
2447
+ const length = (frame.data[2] << 8) | frame.data[3];
2448
+ if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.READ_REGISTERS_MAX) {
2449
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2450
+ return;
2451
+ }
2452
+ if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
2453
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2454
+ return;
2455
+ }
2456
+ try {
2457
+ const registers = yield model.readHoldingRegisters(address, length);
2458
+ const bufferTx = Buffer.alloc(length * 2);
2459
+ registers.forEach((register, index) => {
2460
+ bufferTx.writeUInt16BE(register, index * 2);
2461
+ });
2462
+ yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
2463
+ }
2464
+ catch (error) {
2465
+ yield this.responseError(frame, response, error);
1677
2466
  }
1678
2467
  });
1679
2468
  }
1680
2469
  handleFC4(model, frame, response) {
1681
2470
  return __awaiter(this, void 0, void 0, function* () {
1682
2471
  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
- }
2472
+ if (frame.data.length !== 4) {
2473
+ return;
2474
+ }
2475
+ if (!model.readInputRegisters) {
2476
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2477
+ return;
2478
+ }
2479
+ const address = (frame.data[0] << 8) | frame.data[1];
2480
+ const length = (frame.data[2] << 8) | frame.data[3];
2481
+ if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.READ_REGISTERS_MAX) {
2482
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2483
+ return;
2484
+ }
2485
+ if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).inputRegisters)) {
2486
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2487
+ return;
2488
+ }
2489
+ try {
2490
+ const registers = yield model.readInputRegisters(address, length);
2491
+ const bufferTx = Buffer.alloc(length * 2);
2492
+ registers.forEach((register, index) => {
2493
+ bufferTx.writeUInt16BE(register, index * 2);
2494
+ });
2495
+ yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
2496
+ }
2497
+ catch (error) {
2498
+ yield this.responseError(frame, response, error);
1713
2499
  }
1714
2500
  });
1715
2501
  }
1716
2502
  handleFC5(model, frame, response) {
1717
2503
  return __awaiter(this, void 0, void 0, function* () {
1718
2504
  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
- }
2505
+ if (frame.data.length !== 4) {
2506
+ return;
2507
+ }
2508
+ if (!model.writeSingleCoil) {
2509
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2510
+ return;
2511
+ }
2512
+ const address = (frame.data[0] << 8) | frame.data[1];
2513
+ const value = (frame.data[2] << 8) | frame.data[3];
2514
+ if (value !== COIL_OFF && value !== COIL_ON) {
2515
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2516
+ return;
2517
+ }
2518
+ if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
2519
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2520
+ return;
2521
+ }
2522
+ try {
2523
+ yield model.writeSingleCoil(address, value === COIL_ON);
2524
+ yield response(this.applicationLayer.encode(frame));
2525
+ }
2526
+ catch (error) {
2527
+ yield this.responseError(frame, response, error);
1745
2528
  }
1746
2529
  });
1747
2530
  }
1748
2531
  handleFC6(model, frame, response) {
1749
2532
  return __awaiter(this, void 0, void 0, function* () {
1750
2533
  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
- }
2534
+ if (frame.data.length !== 4) {
2535
+ return;
2536
+ }
2537
+ if (!model.writeSingleRegister) {
2538
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2539
+ return;
2540
+ }
2541
+ const address = (frame.data[0] << 8) | frame.data[1];
2542
+ const value = (frame.data[2] << 8) | frame.data[3];
2543
+ if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
2544
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2545
+ return;
2546
+ }
2547
+ try {
2548
+ yield model.writeSingleRegister(address, value);
2549
+ yield response(this.applicationLayer.encode(frame));
2550
+ }
2551
+ catch (error) {
2552
+ yield this.responseError(frame, response, error);
1777
2553
  }
1778
2554
  });
1779
2555
  }
1780
2556
  handleFC15(model, frame, response) {
1781
2557
  return __awaiter(this, void 0, void 0, function* () {
1782
2558
  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
- }
2559
+ if (frame.data.length <= 5 || frame.data.length !== 5 + frame.data[4]) {
2560
+ return;
2561
+ }
2562
+ if (!model.writeMultipleCoils && !model.writeSingleCoil) {
2563
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2564
+ return;
2565
+ }
2566
+ const address = (frame.data[0] << 8) | frame.data[1];
2567
+ const length = (frame.data[2] << 8) | frame.data[3];
2568
+ const byteCount = frame.data[4];
2569
+ if (length < LIMITS.READ_COILS_MIN || length > LIMITS.WRITE_COILS_MAX || byteCount !== Math.ceil(length / 8)) {
2570
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2571
+ return;
2572
+ }
2573
+ if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
2574
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2575
+ return;
2576
+ }
2577
+ const value = new Array(length);
2578
+ for (let i = 0; i < length; i++) {
2579
+ value[i] = (frame.data[5 + Math.floor(i / 8)] & (1 << i % 8)) > 0;
2580
+ }
2581
+ try {
2582
+ if (model.writeMultipleCoils) {
2583
+ yield model.writeMultipleCoils(address, value);
1809
2584
  }
1810
2585
  else {
1811
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2586
+ for (let i = 0; i < value.length; i++) {
2587
+ yield model.writeSingleCoil(address + i, value[i]);
2588
+ }
1812
2589
  }
2590
+ yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: frame.data.subarray(0, 4) })));
2591
+ }
2592
+ catch (error) {
2593
+ yield this.responseError(frame, response, error);
1813
2594
  }
1814
2595
  });
1815
2596
  }
1816
2597
  handleFC16(model, frame, response) {
1817
2598
  return __awaiter(this, void 0, void 0, function* () {
1818
2599
  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
- }
2600
+ if (frame.data.length <= 5 || frame.data.length !== 5 + frame.data[4]) {
2601
+ return;
2602
+ }
2603
+ if (!model.writeMultipleRegisters && !model.writeSingleRegister) {
2604
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2605
+ return;
2606
+ }
2607
+ const address = (frame.data[0] << 8) | frame.data[1];
2608
+ const length = (frame.data[2] << 8) | frame.data[3];
2609
+ const byteCount = frame.data[4];
2610
+ if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.WRITE_REGISTERS_MAX || byteCount !== length * 2) {
2611
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2612
+ return;
2613
+ }
2614
+ if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
2615
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2616
+ return;
2617
+ }
2618
+ const value = new Array(length);
2619
+ for (let i = 0; i < length; i++) {
2620
+ value[i] = (frame.data[5 + i * 2] << 8) | frame.data[6 + i * 2];
2621
+ }
2622
+ try {
2623
+ if (model.writeMultipleRegisters) {
2624
+ yield model.writeMultipleRegisters(address, value);
1845
2625
  }
1846
2626
  else {
1847
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2627
+ for (let i = 0; i < value.length; i++) {
2628
+ yield model.writeSingleRegister(address + i, value[i]);
2629
+ }
1848
2630
  }
2631
+ yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: frame.data.subarray(0, 4) })));
2632
+ }
2633
+ catch (error) {
2634
+ yield this.responseError(frame, response, error);
1849
2635
  }
1850
2636
  });
1851
2637
  }
1852
2638
  handleFC17(model, frame, response) {
1853
2639
  return __awaiter(this, void 0, void 0, function* () {
1854
2640
  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
- }
2641
+ if (frame.data.length !== 0) {
2642
+ return;
2643
+ }
2644
+ if (!model.reportServerId) {
2645
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2646
+ return;
2647
+ }
2648
+ try {
2649
+ const { serverId = (_a = model.unit) !== null && _a !== void 0 ? _a : 1, runIndicatorStatus = true, additionalData = [] } = yield model.reportServerId();
2650
+ const serverIdBytes = Array.isArray(serverId) ? serverId : [serverId];
2651
+ const byteCount = serverIdBytes.length + 1 + additionalData.length;
2652
+ if (byteCount > 255) {
2653
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
2654
+ return;
1864
2655
  }
1865
- else {
1866
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2656
+ const allBytes = [...serverIdBytes, runIndicatorStatus ? 0xff : 0x00, ...additionalData];
2657
+ if (allBytes.some((b) => !isUint8(b))) {
2658
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
2659
+ return;
1867
2660
  }
2661
+ yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([byteCount]), Buffer.from(allBytes)]) })));
2662
+ }
2663
+ catch (error) {
2664
+ yield this.responseError(frame, response, error);
1868
2665
  }
1869
2666
  });
1870
2667
  }
1871
2668
  handleFC22(model, frame, response) {
1872
2669
  return __awaiter(this, void 0, void 0, function* () {
1873
2670
  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
- }
2671
+ if (frame.data.length !== 6) {
2672
+ return;
2673
+ }
2674
+ if (!model.maskWriteRegister && !(model.readHoldingRegisters && model.writeSingleRegister)) {
2675
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2676
+ return;
2677
+ }
2678
+ const address = (frame.data[0] << 8) | frame.data[1];
2679
+ const andMask = (frame.data[2] << 8) | frame.data[3];
2680
+ const orMask = (frame.data[4] << 8) | frame.data[5];
2681
+ if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
2682
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2683
+ return;
2684
+ }
2685
+ try {
2686
+ yield this.withAddressLock([address], () => __awaiter(this, void 0, void 0, function* () {
2687
+ if (model.maskWriteRegister) {
2688
+ yield model.maskWriteRegister(address, andMask, orMask);
1892
2689
  }
1893
2690
  else {
1894
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2691
+ const [value] = yield model.readHoldingRegisters(address, 1);
2692
+ yield model.writeSingleRegister(address, (value & andMask) | (orMask & (~andMask & 0xffff)));
1895
2693
  }
1896
- }
1897
- else {
1898
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
1899
- }
2694
+ }));
2695
+ yield response(this.applicationLayer.encode(frame));
2696
+ }
2697
+ catch (error) {
2698
+ yield this.responseError(frame, response, error);
1900
2699
  }
1901
2700
  });
1902
2701
  }
1903
2702
  handleFC23(model, frame, response) {
1904
2703
  return __awaiter(this, void 0, void 0, function* () {
1905
2704
  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
- }
2705
+ if (frame.data.length <= 9 || frame.data.length !== 9 + frame.data[8]) {
2706
+ return;
2707
+ }
2708
+ if (!model.readHoldingRegisters || (!model.writeMultipleRegisters && !model.writeSingleRegister)) {
2709
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2710
+ return;
2711
+ }
2712
+ const address = {
2713
+ read: (frame.data[0] << 8) | frame.data[1],
2714
+ write: (frame.data[4] << 8) | frame.data[5],
2715
+ };
2716
+ const length = {
2717
+ read: (frame.data[2] << 8) | frame.data[3],
2718
+ write: (frame.data[6] << 8) | frame.data[7],
2719
+ };
2720
+ const byteCount = frame.data[8];
2721
+ if (length.read < LIMITS.READ_REGISTERS_MIN ||
2722
+ length.read > LIMITS.READ_REGISTERS_MAX ||
2723
+ length.write < LIMITS.READ_REGISTERS_MIN ||
2724
+ length.write > LIMITS.RW_REGISTERS_WRITE_MAX ||
2725
+ byteCount !== length.write * 2) {
2726
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2727
+ return;
2728
+ }
2729
+ 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)) {
2730
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2731
+ return;
2732
+ }
2733
+ const value = new Array(length.write);
2734
+ for (let i = 0; i < length.write; i++) {
2735
+ value[i] = (frame.data[9 + i * 2] << 8) | frame.data[10 + i * 2];
2736
+ }
2737
+ try {
2738
+ const writeAddresses = Array.from({ length: length.write }, (_, i) => address.write + i);
2739
+ yield this.withAddressLock(writeAddresses, () => __awaiter(this, void 0, void 0, function* () {
2740
+ if (model.writeMultipleRegisters) {
2741
+ yield model.writeMultipleRegisters(address.write, value);
1943
2742
  }
1944
2743
  else {
1945
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2744
+ for (let i = 0; i < value.length; i++) {
2745
+ yield model.writeSingleRegister(address.write + i, value[i]);
2746
+ }
1946
2747
  }
1947
- }
1948
- else {
1949
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
1950
- }
2748
+ }));
2749
+ const registers = yield model.readHoldingRegisters(address.read, length.read);
2750
+ const bufferTx = Buffer.alloc(length.read * 2);
2751
+ registers.forEach((register, index) => {
2752
+ bufferTx.writeUInt16BE(register, index * 2);
2753
+ });
2754
+ yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
2755
+ }
2756
+ catch (error) {
2757
+ yield this.responseError(frame, response, error);
1951
2758
  }
1952
2759
  });
1953
2760
  }
1954
2761
  handleFC43_14(model, frame, response) {
1955
2762
  return __awaiter(this, void 0, void 0, function* () {
1956
2763
  if (frame.data.length === 3) {
1957
- if (frame.data[0] === 0x0e && model.readDeviceIdentification) {
2764
+ if (frame.data[0] === MEI_READ_DEVICE_ID && model.readDeviceIdentification) {
1958
2765
  const readDeviceIDCode = frame.data[1];
1959
2766
  let objectID = frame.data[2];
1960
2767
  switch (readDeviceIDCode) {
1961
- case 0x01: {
2768
+ case ReadDeviceIDCode.BASIC_STREAM: {
1962
2769
  if (objectID > 0x02 || (objectID > 0x06 && objectID < 0x80)) {
1963
2770
  objectID = 0x00;
1964
2771
  }
1965
2772
  break;
1966
2773
  }
1967
- case 0x02: {
2774
+ case ReadDeviceIDCode.REGULAR_STREAM: {
1968
2775
  if (objectID >= 0x80 || (objectID > 0x06 && objectID < 0x80)) {
1969
2776
  objectID = 0x00;
1970
2777
  }
1971
2778
  break;
1972
2779
  }
1973
- case 0x03: {
2780
+ case ReadDeviceIDCode.EXTENDED_STREAM: {
1974
2781
  if (objectID > 0x06 && objectID < 0x80) {
1975
2782
  objectID = 0x00;
1976
2783
  }
1977
2784
  break;
1978
2785
  }
1979
- case 0x04: {
2786
+ case ReadDeviceIDCode.SPECIFIC_ACCESS: {
1980
2787
  if (objectID > 0x06 && objectID < 0x80) {
1981
2788
  yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
1982
2789
  return;
@@ -1989,70 +2796,74 @@ class ModbusSlave extends EventEmitter {
1989
2796
  }
1990
2797
  }
1991
2798
  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);
2799
+ const identification = yield model.readDeviceIdentification();
2800
+ const objects = new Map();
2801
+ for (const key of Object.keys(identification)) {
2802
+ const id = Number(key);
2803
+ if (id >= 0 && id <= 255) {
2804
+ objects.set(id, identification[id]);
2805
+ }
2806
+ }
2807
+ for (let id = 0x00; id <= 0x02; id++) {
2808
+ if (!objects.has(id)) {
2809
+ objects.set(id, 'null');
2002
2810
  }
2003
2811
  }
2004
2812
  if (!objects.has(objectID)) {
2005
- if (readDeviceIDCode === 0x04) {
2813
+ if (readDeviceIDCode === ReadDeviceIDCode.SPECIFIC_ACCESS) {
2006
2814
  yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2007
2815
  return;
2008
2816
  }
2009
2817
  objectID = 0x00;
2010
2818
  }
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) {
2819
+ let maxId = 0;
2820
+ for (const id of objects.keys()) {
2821
+ if ((id >= 0x07 && id <= 0x7f) || id > 0xff) {
2017
2822
  yield this.responseError(frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
2018
2823
  return;
2019
2824
  }
2020
- if (id > 0x02) {
2021
- conformityLevel = 0x82;
2022
- }
2023
- if (id > 0x80) {
2024
- conformityLevel = 0x83;
2825
+ if (id > maxId) {
2826
+ maxId = id;
2025
2827
  }
2828
+ }
2829
+ const conformityLevel = maxId >= 0x80 ? ConformityLevel.EXTENDED : maxId > 0x02 ? ConformityLevel.REGULAR : ConformityLevel.BASIC;
2830
+ const ids = [];
2831
+ let totalLength = 10;
2832
+ let lastID = 0;
2833
+ for (const [id, value] of objects.entries()) {
2026
2834
  if (objectID > id) {
2027
2835
  continue;
2028
2836
  }
2029
- if (value.length > 245) {
2837
+ const byteLength = Buffer.byteLength(value);
2838
+ if (byteLength > 245) {
2030
2839
  yield this.responseError(frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
2031
2840
  return;
2032
2841
  }
2033
2842
  if (lastID !== 0) {
2034
2843
  continue;
2035
2844
  }
2036
- if (value.length + 2 > 253 - totalLength) {
2845
+ if (byteLength + 2 > 253 - totalLength) {
2037
2846
  if (lastID === 0) {
2038
2847
  lastID = id;
2039
2848
  }
2040
2849
  }
2041
2850
  else {
2042
- totalLength += value.length + 2;
2851
+ totalLength += byteLength + 2;
2043
2852
  ids.push(id);
2044
- if (readDeviceIDCode === 0x04) {
2853
+ if (readDeviceIDCode === ReadDeviceIDCode.SPECIFIC_ACCESS) {
2045
2854
  break;
2046
2855
  }
2047
2856
  }
2048
2857
  }
2049
2858
  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()) })));
2859
+ const parts = [];
2860
+ parts.push(Buffer.from([MEI_READ_DEVICE_ID, readDeviceIDCode, conformityLevel, lastID === 0 ? 0x00 : 0xff, lastID, ids.length]));
2861
+ for (const id of ids) {
2862
+ const value = objects.get(id);
2863
+ parts.push(Buffer.from([id, Buffer.byteLength(value)]));
2864
+ parts.push(Buffer.from(value));
2865
+ }
2866
+ yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat(parts) })));
2056
2867
  }
2057
2868
  catch (error) {
2058
2869
  yield this.responseError(frame, response, error);
@@ -2066,26 +2877,32 @@ class ModbusSlave extends EventEmitter {
2066
2877
  }
2067
2878
  responseError(frame, response, error) {
2068
2879
  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)] })));
2880
+ yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { fc: frame.fc | EXCEPTION_OFFSET, data: Buffer.from([getCodeByError(error)]) })));
2070
2881
  });
2071
2882
  }
2072
- _drain() {
2883
+ _drain(key, q) {
2073
2884
  return __awaiter(this, void 0, void 0, function* () {
2074
- if (this._processing || this._queue.length === 0) {
2885
+ if (q.processing) {
2075
2886
  return;
2076
2887
  }
2077
- this._processing = true;
2078
- const { frame, response: _response } = this._queue.shift();
2888
+ q.processing = true;
2079
2889
  try {
2080
- yield this._processFrame(frame, _response);
2081
- }
2082
- catch (error) {
2083
- this.emit('error', error);
2890
+ while (q.items.length > 0) {
2891
+ const { frame, response } = q.items.shift();
2892
+ try {
2893
+ yield this._processFrame(frame, response);
2894
+ }
2895
+ catch (error) {
2896
+ this.emit('error', error);
2897
+ }
2898
+ }
2084
2899
  }
2085
2900
  finally {
2086
- this._processing = false;
2087
- if (this._queue.length > 0) {
2088
- setImmediate(() => this._drain());
2901
+ q.processing = false;
2902
+ // Cleanup empty queue entries so the map doesn't grow unbounded across
2903
+ // ephemeral connections (UDP rinfo, brief TCP clients).
2904
+ if (q.items.length === 0 && this._queues.get(key) === q) {
2905
+ this._queues.delete(key);
2089
2906
  }
2090
2907
  }
2091
2908
  });
@@ -2102,8 +2919,8 @@ class ModbusSlave extends EventEmitter {
2102
2919
  catch (error) { }
2103
2920
  });
2104
2921
  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') {
2922
+ const intercepted = yield this._intercept(model, frame, response);
2923
+ if (!intercepted) {
2107
2924
  yield this._handleFC(model, frame, response);
2108
2925
  }
2109
2926
  }
@@ -2111,17 +2928,42 @@ class ModbusSlave extends EventEmitter {
2111
2928
  }
2112
2929
  _intercept(model, frame, response) {
2113
2930
  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
- }
2931
+ if (!model.interceptor) {
2932
+ return false;
2933
+ }
2934
+ try {
2935
+ const data = yield model.interceptor(frame.fc, frame.data);
2936
+ if (data !== undefined) {
2937
+ yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data })));
2938
+ return true;
2121
2939
  }
2122
- catch (error) {
2123
- yield this.responseError(frame, response, error);
2124
- return 'break';
2940
+ return false;
2941
+ }
2942
+ catch (error) {
2943
+ yield this.responseError(frame, response, error);
2944
+ return true;
2945
+ }
2946
+ });
2947
+ }
2948
+ withAddressLock(addresses, fn) {
2949
+ return __awaiter(this, void 0, void 0, function* () {
2950
+ const sorted = [...new Set(addresses)].sort((a, b) => a - b);
2951
+ const previous = sorted.map((addr) => { var _a; return (_a = this._locks.get(addr)) !== null && _a !== void 0 ? _a : Promise.resolve(); });
2952
+ const work = Promise.all(previous).then(() => fn());
2953
+ const cleanup = work.catch(() => {
2954
+ /* ignore */
2955
+ });
2956
+ for (const addr of sorted) {
2957
+ this._locks.set(addr, cleanup);
2958
+ }
2959
+ try {
2960
+ return yield work;
2961
+ }
2962
+ finally {
2963
+ for (const addr of sorted) {
2964
+ if (this._locks.get(addr) === cleanup) {
2965
+ this._locks.delete(addr);
2966
+ }
2125
2967
  }
2126
2968
  }
2127
2969
  });
@@ -2129,56 +2971,68 @@ class ModbusSlave extends EventEmitter {
2129
2971
  _handleFC(model, frame, response) {
2130
2972
  return __awaiter(this, void 0, void 0, function* () {
2131
2973
  switch (frame.fc) {
2132
- case 0x01: {
2974
+ case FunctionCode.READ_COILS: {
2133
2975
  yield this.handleFC1(model, frame, response);
2134
2976
  break;
2135
2977
  }
2136
- case 0x02: {
2978
+ case FunctionCode.READ_DISCRETE_INPUTS: {
2137
2979
  yield this.handleFC2(model, frame, response);
2138
2980
  break;
2139
2981
  }
2140
- case 0x03: {
2982
+ case FunctionCode.READ_HOLDING_REGISTERS: {
2141
2983
  yield this.handleFC3(model, frame, response);
2142
2984
  break;
2143
2985
  }
2144
- case 0x04: {
2986
+ case FunctionCode.READ_INPUT_REGISTERS: {
2145
2987
  yield this.handleFC4(model, frame, response);
2146
2988
  break;
2147
2989
  }
2148
- case 0x05: {
2990
+ case FunctionCode.WRITE_SINGLE_COIL: {
2149
2991
  yield this.handleFC5(model, frame, response);
2150
2992
  break;
2151
2993
  }
2152
- case 0x06: {
2994
+ case FunctionCode.WRITE_SINGLE_REGISTER: {
2153
2995
  yield this.handleFC6(model, frame, response);
2154
2996
  break;
2155
2997
  }
2156
- case 0x0f: {
2998
+ case FunctionCode.WRITE_MULTIPLE_COILS: {
2157
2999
  yield this.handleFC15(model, frame, response);
2158
3000
  break;
2159
3001
  }
2160
- case 0x10: {
3002
+ case FunctionCode.WRITE_MULTIPLE_REGISTERS: {
2161
3003
  yield this.handleFC16(model, frame, response);
2162
3004
  break;
2163
3005
  }
2164
- case 0x11: {
3006
+ case FunctionCode.REPORT_SERVER_ID: {
2165
3007
  yield this.handleFC17(model, frame, response);
2166
3008
  break;
2167
3009
  }
2168
- case 0x16: {
3010
+ case FunctionCode.MASK_WRITE_REGISTER: {
2169
3011
  yield this.handleFC22(model, frame, response);
2170
3012
  break;
2171
3013
  }
2172
- case 0x17: {
3014
+ case FunctionCode.READ_WRITE_MULTIPLE_REGISTERS: {
2173
3015
  yield this.handleFC23(model, frame, response);
2174
3016
  break;
2175
3017
  }
2176
- case 0x2b: {
3018
+ case FunctionCode.READ_DEVICE_IDENTIFICATION: {
2177
3019
  yield this.handleFC43_14(model, frame, response);
2178
3020
  break;
2179
3021
  }
2180
3022
  default: {
2181
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3023
+ const cfc = this._customFunctionCodes.get(frame.fc);
3024
+ if (cfc === null || cfc === void 0 ? void 0 : cfc.handle) {
3025
+ try {
3026
+ const responseData = yield cfc.handle(frame.data, frame.unit);
3027
+ yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: responseData })));
3028
+ }
3029
+ catch (error) {
3030
+ yield this.responseError(frame, response, error);
3031
+ }
3032
+ }
3033
+ else {
3034
+ yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3035
+ }
2182
3036
  break;
2183
3037
  }
2184
3038
  }
@@ -2191,17 +3045,52 @@ class ModbusSlave extends EventEmitter {
2191
3045
  remove(unit) {
2192
3046
  this.models.delete(unit);
2193
3047
  }
3048
+ addCustomFunctionCode(cfc) {
3049
+ this._customFunctionCodes.set(cfc.fc, cfc);
3050
+ this.applicationLayer.addCustomFunctionCode(cfc);
3051
+ }
3052
+ removeCustomFunctionCode(fc) {
3053
+ this._customFunctionCodes.delete(fc);
3054
+ this.applicationLayer.removeCustomFunctionCode(fc);
3055
+ }
3056
+ _clean(level) {
3057
+ if (this._cleanLevel === 'destroy') {
3058
+ return;
3059
+ }
3060
+ if (this._cleanLevel === 'close' && level === 'close') {
3061
+ return;
3062
+ }
3063
+ for (const q of this._queues.values()) {
3064
+ q.items.length = 0;
3065
+ }
3066
+ this._queues.clear();
3067
+ this._locks.clear();
3068
+ if (level === 'destroy') {
3069
+ this._customFunctionCodes.clear();
3070
+ this.models.clear();
3071
+ }
3072
+ this._cleanLevel = level;
3073
+ }
2194
3074
  open(...args) {
3075
+ this._cleanLevel = 'none';
2195
3076
  return this.physicalLayer.open(...args);
2196
3077
  }
2197
3078
  close() {
3079
+ if (this._cleanLevel === 'destroy') {
3080
+ return Promise.resolve();
3081
+ }
3082
+ this._clean('close');
2198
3083
  return this.physicalLayer.close();
2199
3084
  }
2200
3085
  destroy() {
3086
+ if (this._cleanLevel === 'destroy') {
3087
+ return Promise.resolve();
3088
+ }
3089
+ this._clean('destroy');
2201
3090
  this.removeAllListeners();
2202
3091
  this.applicationLayer.destroy();
2203
3092
  return this.physicalLayer.destroy();
2204
3093
  }
2205
3094
  }
2206
3095
 
2207
- export { AbstractApplicationLayer, AbstractPhysicalLayer, AsciiApplicationLayer, ErrorCode, ModbusMaster, ModbusSlave, RtuApplicationLayer, SerialPhysicalLayer, TcpApplicationLayer, TcpClientPhysicalLayer, TcpServerPhysicalLayer, UdpPhysicalLayer, getCodeByError, getErrorByCode };
3096
+ 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 };