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