njs-modbus 1.4.0 → 2.0.0

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