njs-modbus 2.0.1 → 3.0.2

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 (56) hide show
  1. package/README.md +265 -154
  2. package/README.zh-CN.md +314 -0
  3. package/dist/index.cjs +1887 -1074
  4. package/dist/index.d.ts +369 -217
  5. package/dist/index.mjs +1884 -1073
  6. package/dist/src/error-code.d.ts +2 -24
  7. package/dist/src/layers/application/abstract-application-layer.d.ts +12 -7
  8. package/dist/src/layers/application/ascii-application-layer.d.ts +8 -9
  9. package/dist/src/layers/application/rtu-application-layer.d.ts +21 -44
  10. package/dist/src/layers/application/tcp-application-layer.d.ts +8 -6
  11. package/dist/src/layers/physical/abstract-physical-layer.d.ts +40 -12
  12. package/dist/src/layers/physical/index.d.ts +9 -3
  13. package/dist/src/layers/physical/serial-physical-layer.d.ts +43 -13
  14. package/dist/src/layers/physical/tcp-client-physical-layer.d.ts +12 -10
  15. package/dist/src/layers/physical/tcp-physical-connection.d.ts +17 -0
  16. package/dist/src/layers/physical/tcp-server-physical-layer.d.ts +23 -12
  17. package/dist/src/layers/physical/udp-client-physical-layer.d.ts +35 -0
  18. package/dist/src/layers/physical/udp-server-physical-layer.d.ts +54 -0
  19. package/dist/src/layers/physical/utils.d.ts +39 -0
  20. package/dist/src/layers/physical/vars.d.ts +11 -0
  21. package/dist/src/master/master.d.ts +45 -18
  22. package/dist/src/slave/slave.d.ts +57 -33
  23. package/dist/src/types.d.ts +3 -3
  24. package/dist/src/utils/index.d.ts +4 -2
  25. package/dist/src/utils/rtu-timing.d.ts +49 -0
  26. package/dist/src/utils/whitelist.d.ts +11 -0
  27. package/package.json +9 -7
  28. package/dist/src/layers/physical/udp-physical-layer.d.ts +0 -42
  29. package/dist/src/utils/genConnectionId.d.ts +0 -2
  30. package/dist/test/adu-buffer.test.d.ts +0 -1
  31. package/dist/test/ascii-hex-sentry.test.d.ts +0 -1
  32. package/dist/test/ascii-hex-validation.test.d.ts +0 -1
  33. package/dist/test/ascii-tcp-fragmentation.test.d.ts +0 -1
  34. package/dist/test/check-range.test.d.ts +0 -1
  35. package/dist/test/fallback-atomic.test.d.ts +0 -1
  36. package/dist/test/fallback-serial.test.d.ts +0 -1
  37. package/dist/test/fc17-serverid-validation.test.d.ts +0 -1
  38. package/dist/test/fc43-conformity.test.d.ts +0 -1
  39. package/dist/test/fc43-utf8-objects.test.d.ts +0 -1
  40. package/dist/test/gen-connection-id.test.d.ts +0 -1
  41. package/dist/test/helpers/raw-tcp.d.ts +0 -38
  42. package/dist/test/master-concurrent.test.d.ts +0 -1
  43. package/dist/test/modbus-error.test.d.ts +0 -1
  44. package/dist/test/physical-lifecycle.test.d.ts +0 -1
  45. package/dist/test/predict-rtu.test.d.ts +0 -1
  46. package/dist/test/rtu-custom-fc.test.d.ts +0 -1
  47. package/dist/test/rtu-pool-overflow.test.d.ts +0 -1
  48. package/dist/test/rtu-t15-timing.test.d.ts +0 -1
  49. package/dist/test/rtu-t35-default.test.d.ts +0 -1
  50. package/dist/test/rtu-t35-strict.test.d.ts +0 -1
  51. package/dist/test/rtu-tcp-fragmentation.test.d.ts +0 -1
  52. package/dist/test/serial-e2e.test.d.ts +0 -1
  53. package/dist/test/slave-multi-connection.test.d.ts +0 -1
  54. package/dist/test/slave.test.d.ts +0 -1
  55. package/dist/test/tcp-fragmentation.test.d.ts +0 -1
  56. package/dist/test/udp-multi-client.test.d.ts +0 -1
package/dist/index.mjs CHANGED
@@ -1,6 +1,5 @@
1
1
  import EventEmitter from 'node:events';
2
2
  import { SerialPort } from 'serialport';
3
- import { randomUUID } from 'node:crypto';
4
3
  import { Socket, createServer } from 'node:net';
5
4
  import { createSocket } from 'node:dgram';
6
5
 
@@ -16,32 +15,20 @@ var ErrorCode;
16
15
  ErrorCode[ErrorCode["GATEWAY_PATH_UNAVAILABLE"] = 10] = "GATEWAY_PATH_UNAVAILABLE";
17
16
  ErrorCode[ErrorCode["GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND"] = 11] = "GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND";
18
17
  })(ErrorCode || (ErrorCode = {}));
19
- /** Internal error codes for programmatic error handling. */
20
- const ModbusErrorCode = {
21
- TIMEOUT: 'ETIMEOUT',
22
- INVALID_RESPONSE: 'EINVALID_RESPONSE',
23
- INSUFFICIENT_DATA: 'EINSUFFICIENT_DATA',
24
- MASTER_CLOSED: 'EMASTER_CLOSED',
25
- MASTER_DESTROYED: 'EMASTER_DESTROYED',
26
- CONCURRENT_NOT_TCP: 'ECONCURRENT_NOT_TCP',
27
- PORT_DESTROYED: 'EPORT_DESTROYED',
28
- PORT_ALREADY_OPEN: 'EPORT_ALREADY_OPEN',
29
- PORT_NOT_OPEN: 'EPORT_NOT_OPEN',
30
- NOT_SUPPORTED: 'ENOT_SUPPORTED',
31
- INVALID_DATA: 'EINVALID_DATA',
32
- INVALID_HEX: 'EINVALID_HEX',
33
- CRC_MISMATCH: 'ECRC_MISMATCH',
34
- LRC_MISMATCH: 'ELRC_MISMATCH',
35
- INCOMPLETE_FRAME: 'EINCOMPLETE_FRAME',
36
- T1_5_EXCEEDED: 'ET1_5_EXCEEDED',
37
- UNKNOWN_FC: 'EUNKNOWN_FC',
38
- INVALID_ROLE: 'EINVALID_ROLE',
39
- RANGE: 'ERANGE',
18
+ const ERROR_MESSAGES = {
19
+ [ErrorCode.ILLEGAL_FUNCTION]: 'Illegal function',
20
+ [ErrorCode.ILLEGAL_DATA_ADDRESS]: 'Illegal data address',
21
+ [ErrorCode.ILLEGAL_DATA_VALUE]: 'Illegal data value',
22
+ [ErrorCode.SERVER_DEVICE_FAILURE]: 'Server device failure',
23
+ [ErrorCode.ACKNOWLEDGE]: 'Acknowledge',
24
+ [ErrorCode.SERVER_DEVICE_BUSY]: 'Server device busy',
25
+ [ErrorCode.MEMORY_PARITY_ERROR]: 'Memory parity error',
26
+ [ErrorCode.GATEWAY_PATH_UNAVAILABLE]: 'Gateway path unavailable',
27
+ [ErrorCode.GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND]: 'Gateway target device failed to respond',
40
28
  };
41
- const MODBUS_PREFIX = 'MODBUS_ERROR_CODE_';
42
29
  class ModbusError extends Error {
43
- constructor(code, message) {
44
- super(message !== null && message !== void 0 ? message : code);
30
+ constructor(code, message = ERROR_MESSAGES[code]) {
31
+ super(message);
45
32
  Object.defineProperty(this, "code", {
46
33
  enumerable: true,
47
34
  configurable: true,
@@ -52,18 +39,15 @@ class ModbusError extends Error {
52
39
  }
53
40
  }
54
41
  function getErrorByCode(code) {
55
- return new ModbusError(String(code), `${MODBUS_PREFIX}${code}`);
42
+ return new ModbusError(code);
56
43
  }
57
44
  function getCodeByError(err) {
58
- if (err instanceof ModbusError) {
59
- const num = Number(err.code);
60
- if (!Number.isNaN(num) && num in ErrorCode) {
61
- return num;
45
+ if (err.name === 'ModbusError' && 'code' in err) {
46
+ const code = err.code;
47
+ if (code in ErrorCode) {
48
+ return code;
62
49
  }
63
50
  }
64
- if (err.message.startsWith(MODBUS_PREFIX)) {
65
- return Number(err.message.slice(MODBUS_PREFIX.length));
66
- }
67
51
  return ErrorCode.SERVER_DEVICE_FAILURE;
68
52
  }
69
53
 
@@ -118,23 +102,584 @@ const LIMITS = {
118
102
  RW_REGISTERS_WRITE_MAX: 0x0079,
119
103
  };
120
104
 
105
+ /**
106
+ * A one-way data transmission channel.
107
+ *
108
+ * State transitions are unidirectional: CONNECTED → DESTROYING → DESTROYED.
109
+ * Once closed, the instance cannot be reused; create a new connection instead.
110
+ */
111
+ class AbstractPhysicalConnection extends EventEmitter {
112
+ }
113
+ /**
114
+ * An abstraction over local hardware or network resources.
115
+ *
116
+ * `open()` acquires the resource (serial port, TCP socket, UDP socket, etc.).
117
+ * Once ready, it emits `connect` with an {@link AbstractPhysicalConnection},
118
+ * unifying serial, TCP client, TCP server, and UDP under a single
119
+ * "connection-oriented" model similar to a TCP server accepting sockets.
120
+ */
121
121
  class AbstractPhysicalLayer extends EventEmitter {
122
+ is(type) {
123
+ return this.TYPE === type;
124
+ }
125
+ }
126
+
127
+ var PhysicalState;
128
+ (function (PhysicalState) {
129
+ PhysicalState["OPENING"] = "opening";
130
+ PhysicalState["OPEN"] = "open";
131
+ PhysicalState["CLOSING"] = "closing";
132
+ PhysicalState["CLOSED"] = "closed";
133
+ })(PhysicalState || (PhysicalState = {}));
134
+ var PhysicalConnectionState;
135
+ (function (PhysicalConnectionState) {
136
+ PhysicalConnectionState["CONNECTED"] = "connected";
137
+ PhysicalConnectionState["DESTROYING"] = "destroying";
138
+ PhysicalConnectionState["DESTROYED"] = "destroyed";
139
+ })(PhysicalConnectionState || (PhysicalConnectionState = {}));
140
+
141
+ class SerialPhysicalConnection extends AbstractPhysicalConnection {
142
+ get state() {
143
+ return this._state;
144
+ }
145
+ get physicalLayer() {
146
+ return this._physicalLayer;
147
+ }
148
+ constructor(physicalLayer, serialport) {
149
+ super();
150
+ Object.defineProperty(this, "_state", {
151
+ enumerable: true,
152
+ configurable: true,
153
+ writable: true,
154
+ value: PhysicalConnectionState.CONNECTED
155
+ });
156
+ Object.defineProperty(this, "_physicalLayer", {
157
+ enumerable: true,
158
+ configurable: true,
159
+ writable: true,
160
+ value: void 0
161
+ });
162
+ Object.defineProperty(this, "_serialport", {
163
+ enumerable: true,
164
+ configurable: true,
165
+ writable: true,
166
+ value: void 0
167
+ });
168
+ Object.defineProperty(this, "_destroyPromise", {
169
+ enumerable: true,
170
+ configurable: true,
171
+ writable: true,
172
+ value: null
173
+ });
174
+ Object.defineProperty(this, "_resolveDestroy", {
175
+ enumerable: true,
176
+ configurable: true,
177
+ writable: true,
178
+ value: null
179
+ });
180
+ Object.defineProperty(this, "_cleanupFns", {
181
+ enumerable: true,
182
+ configurable: true,
183
+ writable: true,
184
+ value: new Set()
185
+ });
186
+ this._physicalLayer = physicalLayer;
187
+ this._serialport = serialport;
188
+ const onData = (chunk) => {
189
+ if (this.state !== PhysicalConnectionState.CONNECTED) {
190
+ return;
191
+ }
192
+ this.emit('data', chunk);
193
+ };
194
+ serialport.on('data', onData);
195
+ this._cleanupFns.add(() => serialport.off('data', onData));
196
+ const onSerialError = (err) => {
197
+ this.physicalLayer.emit('error', err);
198
+ };
199
+ serialport.on('error', onSerialError);
200
+ this._cleanupFns.add(() => serialport.off('error', onSerialError));
201
+ const cleanupClose = () => serialport.off('close', onClose);
202
+ const onClose = () => {
203
+ var _a;
204
+ this._cleanupFns.delete(cleanupClose);
205
+ this._state = PhysicalConnectionState.DESTROYED;
206
+ this._destroyPromise = null;
207
+ for (const fn of this._cleanupFns) {
208
+ fn();
209
+ }
210
+ this._cleanupFns.clear();
211
+ this.emit('close');
212
+ (_a = this._resolveDestroy) === null || _a === void 0 ? void 0 : _a.call(this);
213
+ this._resolveDestroy = null;
214
+ };
215
+ serialport.once('close', onClose);
216
+ this._cleanupFns.add(cleanupClose);
217
+ }
218
+ write(data) {
219
+ return new Promise((resolve, reject) => {
220
+ if (this.state === PhysicalConnectionState.CONNECTED) {
221
+ this._serialport.write(data, (error) => {
222
+ if (error) {
223
+ reject(error);
224
+ }
225
+ else {
226
+ resolve();
227
+ }
228
+ });
229
+ }
230
+ else {
231
+ reject(new Error('Connection is not connected'));
232
+ }
233
+ });
234
+ }
235
+ destroy() {
236
+ if (this.state === PhysicalConnectionState.DESTROYED) {
237
+ return Promise.resolve();
238
+ }
239
+ if (this.state === PhysicalConnectionState.DESTROYING) {
240
+ return this._destroyPromise;
241
+ }
242
+ this._state = PhysicalConnectionState.DESTROYING;
243
+ this._destroyPromise = new Promise((resolve, reject) => {
244
+ this._resolveDestroy = resolve;
245
+ this._serialport.close((err) => {
246
+ if (err) {
247
+ // Design choice: serialport.close() only rejects in extreme hardware
248
+ // deadlock scenarios (e.g., stuck USB-to-serial adapter). At that
249
+ // point the device is unrecoverable from software; propagating the
250
+ // error is the best we can do.
251
+ this._destroyPromise = null;
252
+ this._resolveDestroy = null;
253
+ reject(err);
254
+ }
255
+ });
256
+ });
257
+ return this._destroyPromise;
258
+ }
259
+ }
260
+ class SerialPhysicalLayer extends AbstractPhysicalLayer {
261
+ get state() {
262
+ return this._state;
263
+ }
264
+ get serialport() {
265
+ return this._serialport;
266
+ }
267
+ get path() {
268
+ return this._path;
269
+ }
270
+ get baudRate() {
271
+ return this._baudRate;
272
+ }
273
+ constructor(options) {
274
+ super();
275
+ Object.defineProperty(this, "TYPE", {
276
+ enumerable: true,
277
+ configurable: true,
278
+ writable: true,
279
+ value: 'SERIAL'
280
+ });
281
+ Object.defineProperty(this, "_state", {
282
+ enumerable: true,
283
+ configurable: true,
284
+ writable: true,
285
+ value: PhysicalState.CLOSED
286
+ });
287
+ Object.defineProperty(this, "_connections", {
288
+ enumerable: true,
289
+ configurable: true,
290
+ writable: true,
291
+ value: new Set()
292
+ });
293
+ Object.defineProperty(this, "_serialport", {
294
+ enumerable: true,
295
+ configurable: true,
296
+ writable: true,
297
+ value: null
298
+ });
299
+ Object.defineProperty(this, "_serialportOpts", {
300
+ enumerable: true,
301
+ configurable: true,
302
+ writable: true,
303
+ value: void 0
304
+ });
305
+ Object.defineProperty(this, "_path", {
306
+ enumerable: true,
307
+ configurable: true,
308
+ writable: true,
309
+ value: void 0
310
+ });
311
+ Object.defineProperty(this, "_baudRate", {
312
+ enumerable: true,
313
+ configurable: true,
314
+ writable: true,
315
+ value: void 0
316
+ });
317
+ Object.defineProperty(this, "_openPromise", {
318
+ enumerable: true,
319
+ configurable: true,
320
+ writable: true,
321
+ value: null
322
+ });
323
+ Object.defineProperty(this, "_closePromise", {
324
+ enumerable: true,
325
+ configurable: true,
326
+ writable: true,
327
+ value: null
328
+ });
329
+ Object.defineProperty(this, "_resolveClose", {
330
+ enumerable: true,
331
+ configurable: true,
332
+ writable: true,
333
+ value: null
334
+ });
335
+ Object.defineProperty(this, "_cleanupFns", {
336
+ enumerable: true,
337
+ configurable: true,
338
+ writable: true,
339
+ value: new Set()
340
+ });
341
+ this._serialportOpts = options;
342
+ this._path = options.path;
343
+ this._baudRate = options.baudRate;
344
+ }
345
+ open() {
346
+ if (this.state === PhysicalState.OPEN) {
347
+ return Promise.resolve();
348
+ }
349
+ if (this.state === PhysicalState.OPENING) {
350
+ return this._openPromise;
351
+ }
352
+ if (this.state === PhysicalState.CLOSING) {
353
+ return Promise.reject(new Error('Port is closing'));
354
+ }
355
+ this._state = PhysicalState.OPENING;
356
+ this._openPromise = new Promise((resolve, reject) => {
357
+ const serialport = new SerialPort(Object.assign(Object.assign({}, this._serialportOpts), { autoOpen: false }));
358
+ this._serialport = serialport;
359
+ serialport.open((err) => {
360
+ if (err) {
361
+ this._state = PhysicalState.CLOSED;
362
+ this._openPromise = null;
363
+ this._serialport = null;
364
+ reject(err);
365
+ }
366
+ else {
367
+ this._state = PhysicalState.OPEN;
368
+ this._openPromise = null;
369
+ this.emit('open');
370
+ resolve();
371
+ const connection = new SerialPhysicalConnection(this, serialport);
372
+ this._connections.add(connection);
373
+ const cleanupConnectionClose = () => connection.off('close', onConnectionClose);
374
+ const onConnectionClose = () => {
375
+ var _a;
376
+ this._cleanupFns.delete(cleanupConnectionClose);
377
+ this._connections.delete(connection);
378
+ this._state = PhysicalState.CLOSED;
379
+ this._closePromise = null;
380
+ for (const fn of this._cleanupFns) {
381
+ fn();
382
+ }
383
+ this._cleanupFns.clear();
384
+ this._serialport = null;
385
+ this.emit('close');
386
+ (_a = this._resolveClose) === null || _a === void 0 ? void 0 : _a.call(this);
387
+ this._resolveClose = null;
388
+ };
389
+ connection.once('close', onConnectionClose);
390
+ this._cleanupFns.add(cleanupConnectionClose);
391
+ this.emit('connect', connection);
392
+ }
393
+ });
394
+ });
395
+ return this._openPromise;
396
+ }
397
+ close() {
398
+ if (this.state === PhysicalState.CLOSED) {
399
+ return Promise.resolve();
400
+ }
401
+ if (this.state === PhysicalState.CLOSING) {
402
+ return this._closePromise;
403
+ }
404
+ if (this.state === PhysicalState.OPENING) {
405
+ return Promise.reject(new Error('Port is opening'));
406
+ }
407
+ this._state = PhysicalState.CLOSING;
408
+ this._closePromise = new Promise((resolve, reject) => {
409
+ this._resolveClose = resolve;
410
+ Promise.all(Array.from(this._connections).map((connection) => connection.destroy())).catch((err) => {
411
+ this._resolveClose = null;
412
+ reject(err);
413
+ });
414
+ });
415
+ return this._closePromise;
416
+ }
417
+ }
418
+
419
+ class TcpPhysicalConnection extends AbstractPhysicalConnection {
420
+ get state() {
421
+ return this._state;
422
+ }
423
+ get physicalLayer() {
424
+ return this._physicalLayer;
425
+ }
426
+ constructor(physicalLayer, socket) {
427
+ super();
428
+ Object.defineProperty(this, "_state", {
429
+ enumerable: true,
430
+ configurable: true,
431
+ writable: true,
432
+ value: PhysicalConnectionState.CONNECTED
433
+ });
434
+ Object.defineProperty(this, "_physicalLayer", {
435
+ enumerable: true,
436
+ configurable: true,
437
+ writable: true,
438
+ value: void 0
439
+ });
440
+ Object.defineProperty(this, "_socket", {
441
+ enumerable: true,
442
+ configurable: true,
443
+ writable: true,
444
+ value: void 0
445
+ });
446
+ Object.defineProperty(this, "_destroyPromise", {
447
+ enumerable: true,
448
+ configurable: true,
449
+ writable: true,
450
+ value: null
451
+ });
452
+ Object.defineProperty(this, "_resolveDestroy", {
453
+ enumerable: true,
454
+ configurable: true,
455
+ writable: true,
456
+ value: null
457
+ });
458
+ Object.defineProperty(this, "_cleanupFns", {
459
+ enumerable: true,
460
+ configurable: true,
461
+ writable: true,
462
+ value: new Set()
463
+ });
464
+ this._physicalLayer = physicalLayer;
465
+ this._socket = socket;
466
+ const onData = (chunk) => {
467
+ if (this.state !== PhysicalConnectionState.CONNECTED) {
468
+ return;
469
+ }
470
+ this.emit('data', chunk);
471
+ };
472
+ socket.on('data', onData);
473
+ this._cleanupFns.add(() => socket.off('data', onData));
474
+ const onSocketError = (err) => {
475
+ this.physicalLayer.emit('error', err);
476
+ };
477
+ socket.on('error', onSocketError);
478
+ this._cleanupFns.add(() => socket.off('error', onSocketError));
479
+ const cleanupClose = () => socket.off('close', onClose);
480
+ const onClose = () => {
481
+ var _a;
482
+ this._cleanupFns.delete(cleanupClose);
483
+ this._state = PhysicalConnectionState.DESTROYED;
484
+ this._destroyPromise = null;
485
+ for (const fn of this._cleanupFns) {
486
+ fn();
487
+ }
488
+ this._cleanupFns.clear();
489
+ this.emit('close');
490
+ (_a = this._resolveDestroy) === null || _a === void 0 ? void 0 : _a.call(this);
491
+ this._resolveDestroy = null;
492
+ };
493
+ socket.once('close', onClose);
494
+ this._cleanupFns.add(cleanupClose);
495
+ }
496
+ write(data) {
497
+ return new Promise((resolve, reject) => {
498
+ if (this.state === PhysicalConnectionState.CONNECTED) {
499
+ this._socket.write(data, (error) => {
500
+ if (error) {
501
+ reject(error);
502
+ }
503
+ else {
504
+ resolve();
505
+ }
506
+ });
507
+ }
508
+ else {
509
+ reject(new Error('Connection is not connected'));
510
+ }
511
+ });
512
+ }
513
+ destroy() {
514
+ if (this.state === PhysicalConnectionState.DESTROYED) {
515
+ return Promise.resolve();
516
+ }
517
+ if (this.state === PhysicalConnectionState.DESTROYING) {
518
+ return this._destroyPromise;
519
+ }
520
+ this._state = PhysicalConnectionState.DESTROYING;
521
+ this._destroyPromise = new Promise((resolve) => {
522
+ this._resolveDestroy = resolve;
523
+ this._socket.destroy();
524
+ });
525
+ return this._destroyPromise;
526
+ }
527
+ }
528
+
529
+ class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
530
+ get state() {
531
+ return this._state;
532
+ }
533
+ get socket() {
534
+ return this._socket;
535
+ }
536
+ constructor(options) {
537
+ super();
538
+ Object.defineProperty(this, "TYPE", {
539
+ enumerable: true,
540
+ configurable: true,
541
+ writable: true,
542
+ value: 'TCP_CLIENT'
543
+ });
544
+ Object.defineProperty(this, "_state", {
545
+ enumerable: true,
546
+ configurable: true,
547
+ writable: true,
548
+ value: PhysicalState.CLOSED
549
+ });
550
+ Object.defineProperty(this, "_connections", {
551
+ enumerable: true,
552
+ configurable: true,
553
+ writable: true,
554
+ value: new Set()
555
+ });
556
+ Object.defineProperty(this, "_socket", {
557
+ enumerable: true,
558
+ configurable: true,
559
+ writable: true,
560
+ value: null
561
+ });
562
+ Object.defineProperty(this, "_socketOpts", {
563
+ enumerable: true,
564
+ configurable: true,
565
+ writable: true,
566
+ value: void 0
567
+ });
568
+ Object.defineProperty(this, "_openPromise", {
569
+ enumerable: true,
570
+ configurable: true,
571
+ writable: true,
572
+ value: null
573
+ });
574
+ Object.defineProperty(this, "_closePromise", {
575
+ enumerable: true,
576
+ configurable: true,
577
+ writable: true,
578
+ value: null
579
+ });
580
+ Object.defineProperty(this, "_resolveClose", {
581
+ enumerable: true,
582
+ configurable: true,
583
+ writable: true,
584
+ value: null
585
+ });
586
+ Object.defineProperty(this, "_cleanupFns", {
587
+ enumerable: true,
588
+ configurable: true,
589
+ writable: true,
590
+ value: new Set()
591
+ });
592
+ this._socketOpts = options;
593
+ }
594
+ open(options) {
595
+ if (this.state === PhysicalState.OPEN) {
596
+ return Promise.resolve();
597
+ }
598
+ if (this.state === PhysicalState.OPENING) {
599
+ return this._openPromise;
600
+ }
601
+ if (this.state === PhysicalState.CLOSING) {
602
+ return Promise.reject(new Error('Port is closing'));
603
+ }
604
+ this._state = PhysicalState.OPENING;
605
+ this._openPromise = new Promise((resolve, reject) => {
606
+ const socket = new Socket(this._socketOpts);
607
+ this._socket = socket;
608
+ const onConnect = () => {
609
+ socket.off('error', onError);
610
+ this._state = PhysicalState.OPEN;
611
+ this._openPromise = null;
612
+ this.emit('open');
613
+ resolve();
614
+ const connection = new TcpPhysicalConnection(this, socket);
615
+ this._connections.add(connection);
616
+ const cleanupConnectionClose = () => connection.off('close', onConnectionClose);
617
+ const onConnectionClose = () => {
618
+ var _a;
619
+ this._cleanupFns.delete(cleanupConnectionClose);
620
+ this._connections.delete(connection);
621
+ this._state = PhysicalState.CLOSED;
622
+ this._closePromise = null;
623
+ for (const fn of this._cleanupFns) {
624
+ fn();
625
+ }
626
+ this._cleanupFns.clear();
627
+ this._socket = null;
628
+ this.emit('close');
629
+ (_a = this._resolveClose) === null || _a === void 0 ? void 0 : _a.call(this);
630
+ this._resolveClose = null;
631
+ };
632
+ connection.once('close', onConnectionClose);
633
+ this._cleanupFns.add(cleanupConnectionClose);
634
+ this.emit('connect', connection);
635
+ };
636
+ const onError = (err) => {
637
+ socket.off('connect', onConnect);
638
+ this._state = PhysicalState.CLOSED;
639
+ this._openPromise = null;
640
+ this._socket = null;
641
+ reject(err);
642
+ };
643
+ socket.once('connect', onConnect);
644
+ socket.once('error', onError);
645
+ socket.connect(options !== null && options !== void 0 ? options : { port: 502 });
646
+ });
647
+ return this._openPromise;
648
+ }
649
+ close() {
650
+ if (this.state === PhysicalState.CLOSED) {
651
+ return Promise.resolve();
652
+ }
653
+ if (this.state === PhysicalState.CLOSING) {
654
+ return this._closePromise;
655
+ }
656
+ if (this.state === PhysicalState.OPENING) {
657
+ return Promise.reject(new Error('Port is opening'));
658
+ }
659
+ this._state = PhysicalState.CLOSING;
660
+ this._closePromise = new Promise((resolve, reject) => {
661
+ this._resolveClose = resolve;
662
+ Promise.all(Array.from(this._connections).map((connection) => connection.destroy())).catch((err) => {
663
+ this._resolveClose = null;
664
+ reject(err);
665
+ });
666
+ });
667
+ return this._closePromise;
668
+ }
122
669
  }
123
670
 
124
671
  function inRange(n, [min, max]) {
125
672
  return n >= min && n <= max;
126
673
  }
674
+ function isRangeArray(range) {
675
+ return Array.isArray(range[0]);
676
+ }
127
677
  function checkRange(value, range) {
128
- if (!range) {
678
+ if (!range || range.length === 0) {
129
679
  return true;
130
680
  }
131
681
  const values = Array.isArray(value) ? value : [value];
132
- if (typeof range[0] === 'number' && typeof range[1] === 'number') {
133
- const [min, max] = range;
134
- const [lo, hi] = min <= max ? [min, max] : [max, min];
135
- return values.every((n) => inRange(n, [lo, hi]));
136
- }
137
- else if (range.length > 0) {
682
+ if (isRangeArray(range)) {
138
683
  for (const r of range) {
139
684
  const [min, max] = r;
140
685
  const [lo, hi] = min <= max ? [min, max] : [max, min];
@@ -144,7 +689,9 @@ function checkRange(value, range) {
144
689
  }
145
690
  return false;
146
691
  }
147
- return true;
692
+ const [min, max] = range;
693
+ const [lo, hi] = min <= max ? [min, max] : [max, min];
694
+ return values.every((n) => inRange(n, [lo, hi]));
148
695
  }
149
696
 
150
697
  const TABLE = [
@@ -173,11 +720,6 @@ function crc(data, seed = 0xffff) {
173
720
  return crc;
174
721
  }
175
722
 
176
- /** Cross-process unique id: `${prefix}-${uuid-v4}`. */
177
- function genConnectionId(prefix) {
178
- return `${prefix}-${randomUUID()}`;
179
- }
180
-
181
723
  /**
182
724
  * Convert a number of bits to milliseconds at a given baud rate.
183
725
  *
@@ -303,15 +845,64 @@ function predictFc43_14Response(buffer) {
303
845
  return { kind: 'length', length: offset + 2 };
304
846
  }
305
847
 
306
- class SerialPhysicalLayer extends AbstractPhysicalLayer {
307
- get isOpen() {
308
- return this._serialport.isOpen;
848
+ /**
849
+ * Resolve Modbus RTU timing parameters from user options into milliseconds.
850
+ *
851
+ * - `intervalBetweenFrames` (t3.5): if omitted and `baudRate` is present,
852
+ * defaults to 38.5 bits per Modbus V1.02 §2.5.1.1.
853
+ * - `interCharTimeout` (t1.5): opt-in; only resolved when explicitly provided.
854
+ *
855
+ * Per the spec, at baud rates > 19200 fixed values are used
856
+ * (1.75 ms for t3.5, 0.75 ms for t1.5) regardless of the bit value.
857
+ */
858
+ function resolveRtuTiming(opts = {}, baudRate) {
859
+ var _a, _b, _c;
860
+ let intervalBetweenFrames = 0;
861
+ let interCharTimeout = 0;
862
+ if (((_a = opts.intervalBetweenFrames) === null || _a === void 0 ? void 0 : _a.unit) === 'ms') {
863
+ intervalBetweenFrames = opts.intervalBetweenFrames.value;
309
864
  }
310
- get destroyed() {
311
- return this._destroyed;
865
+ else if (baudRate !== undefined) {
866
+ const bitValue = (_c = (_b = opts.intervalBetweenFrames) === null || _b === void 0 ? void 0 : _b.value) !== null && _c !== void 0 ? _c : 38.5;
867
+ intervalBetweenFrames = baudRate > 19200 ? 1.75 : Math.ceil(bitsToMs(baudRate, bitValue));
312
868
  }
313
- get baudRate() {
314
- return this._baudRate;
869
+ if (opts.interCharTimeout) {
870
+ if (opts.interCharTimeout.unit === 'ms') {
871
+ interCharTimeout = opts.interCharTimeout.value;
872
+ }
873
+ else if (baudRate !== undefined) {
874
+ interCharTimeout = baudRate > 19200 ? 0.75 : Math.ceil(bitsToMs(baudRate, opts.interCharTimeout.value));
875
+ }
876
+ }
877
+ return { intervalBetweenFrames, interCharTimeout };
878
+ }
879
+
880
+ /**
881
+ * Normalize an IP address by stripping the IPv4-mapped IPv6 prefix.
882
+ * This ensures consistent comparison of addresses like `::ffff:192.168.1.1`.
883
+ */
884
+ function normalizeAddress(address = '') {
885
+ return address.replace(/^::ffff:/, '');
886
+ }
887
+ /**
888
+ * Check whether a remote address is allowed by the given whitelist.
889
+ * IPv4-mapped IPv6 addresses are normalized before comparison.
890
+ * Returns `true` when whitelist is absent or empty.
891
+ */
892
+ function isWhitelisted(address, whitelist) {
893
+ if (!whitelist || whitelist.length === 0) {
894
+ return true;
895
+ }
896
+ const normalized = normalizeAddress(address);
897
+ return whitelist.includes(normalized);
898
+ }
899
+
900
+ class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
901
+ get state() {
902
+ return this._state;
903
+ }
904
+ get server() {
905
+ return this._server;
315
906
  }
316
907
  constructor(options) {
317
908
  super();
@@ -319,327 +910,502 @@ class SerialPhysicalLayer extends AbstractPhysicalLayer {
319
910
  enumerable: true,
320
911
  configurable: true,
321
912
  writable: true,
322
- value: 'SERIAL'
913
+ value: 'TCP_SERVER'
323
914
  });
324
- Object.defineProperty(this, "_serialport", {
915
+ Object.defineProperty(this, "_state", {
916
+ enumerable: true,
917
+ configurable: true,
918
+ writable: true,
919
+ value: PhysicalState.CLOSED
920
+ });
921
+ Object.defineProperty(this, "_connections", {
922
+ enumerable: true,
923
+ configurable: true,
924
+ writable: true,
925
+ value: new Set()
926
+ });
927
+ Object.defineProperty(this, "_server", {
928
+ enumerable: true,
929
+ configurable: true,
930
+ writable: true,
931
+ value: null
932
+ });
933
+ Object.defineProperty(this, "_opts", {
325
934
  enumerable: true,
326
935
  configurable: true,
327
936
  writable: true,
328
937
  value: void 0
329
938
  });
330
- Object.defineProperty(this, "_connection", {
939
+ Object.defineProperty(this, "_openPromise", {
331
940
  enumerable: true,
332
941
  configurable: true,
333
942
  writable: true,
334
943
  value: null
335
944
  });
336
- Object.defineProperty(this, "_isOpening", {
945
+ Object.defineProperty(this, "_closePromise", {
337
946
  enumerable: true,
338
947
  configurable: true,
339
948
  writable: true,
340
- value: false
949
+ value: null
341
950
  });
342
- Object.defineProperty(this, "_destroyed", {
951
+ Object.defineProperty(this, "_resolveClose", {
343
952
  enumerable: true,
344
953
  configurable: true,
345
954
  writable: true,
346
- value: false
955
+ value: null
347
956
  });
348
- Object.defineProperty(this, "_baudRate", {
957
+ Object.defineProperty(this, "_cleanupFns", {
349
958
  enumerable: true,
350
959
  configurable: true,
351
960
  writable: true,
352
- value: void 0
961
+ value: new Set()
353
962
  });
354
- this._serialport = new SerialPort(Object.assign(Object.assign({}, options), { autoOpen: false }));
355
- this._baudRate = options.baudRate;
963
+ this._opts = options !== null && options !== void 0 ? options : {};
356
964
  }
357
- open() {
358
- if (this._destroyed) {
359
- return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Port is destroyed'));
965
+ open(options) {
966
+ if (this.state === PhysicalState.OPEN) {
967
+ return Promise.resolve();
360
968
  }
361
- if (this.isOpen || this._isOpening) {
362
- return Promise.reject(new ModbusError(ModbusErrorCode.PORT_ALREADY_OPEN, 'Port is already open'));
969
+ if (this.state === PhysicalState.OPENING) {
970
+ return this._openPromise;
363
971
  }
364
- this._isOpening = true;
365
- const connection = { id: genConnectionId('serial') };
366
- this._connection = connection;
367
- return new Promise((resolve, reject) => {
368
- this._serialport.open((error) => {
369
- if (error) {
370
- this._isOpening = false;
371
- if (this._connection === connection) {
372
- this._connection = null;
373
- }
374
- reject(error);
972
+ if (this.state === PhysicalState.CLOSING) {
973
+ return Promise.reject(new Error('Port is closing'));
974
+ }
975
+ this._state = PhysicalState.OPENING;
976
+ this._openPromise = new Promise((resolve, reject) => {
977
+ var _a;
978
+ const server = createServer(this._opts.serverOpts, (socket) => {
979
+ if (!isWhitelisted(socket.remoteAddress, this._opts.whitelist)) {
980
+ socket.destroy();
375
981
  return;
376
982
  }
377
- this._isOpening = false;
378
- this._serialport.on('data', (data) => {
379
- this.emit('data', data, (d) => this.write(d), connection);
380
- });
381
- this._serialport.on('error', (err) => {
382
- this.emit('error', err);
383
- });
384
- this._serialport.once('close', () => {
385
- if (this._connection === connection) {
386
- this._serialport.removeAllListeners();
387
- this._connection = null;
388
- this.emit('connection-close', connection);
389
- this.emit('close');
983
+ let cleanupTimeout = null;
984
+ if (this._opts.idleTimeout) {
985
+ socket.setTimeout(this._opts.idleTimeout);
986
+ cleanupTimeout = () => socket.off('timeout', onTimeout);
987
+ const onTimeout = () => {
988
+ this._cleanupFns.delete(cleanupTimeout);
989
+ socket.destroy();
990
+ };
991
+ socket.once('timeout', onTimeout);
992
+ this._cleanupFns.add(cleanupTimeout);
993
+ }
994
+ const connection = new TcpPhysicalConnection(this, socket);
995
+ this._connections.add(connection);
996
+ const cleanupConnectionClose = () => connection.off('close', onConnectionClose);
997
+ const onConnectionClose = () => {
998
+ if (cleanupTimeout) {
999
+ cleanupTimeout();
1000
+ this._cleanupFns.delete(cleanupTimeout);
390
1001
  }
391
- });
1002
+ this._cleanupFns.delete(cleanupConnectionClose);
1003
+ this._connections.delete(connection);
1004
+ };
1005
+ connection.once('close', onConnectionClose);
1006
+ this._cleanupFns.add(cleanupConnectionClose);
1007
+ this.emit('connect', connection);
1008
+ });
1009
+ this._server = server;
1010
+ if (this._opts.maxConnections !== undefined) {
1011
+ server.maxConnections = this._opts.maxConnections;
1012
+ }
1013
+ const onConnect = () => {
1014
+ server.off('error', onError);
1015
+ this._state = PhysicalState.OPEN;
1016
+ this._openPromise = null;
1017
+ this.emit('open');
392
1018
  resolve();
1019
+ {
1020
+ const onError = (err) => {
1021
+ this.emit('error', err);
1022
+ };
1023
+ server.on('error', onError);
1024
+ this._cleanupFns.add(() => server.off('error', onError));
1025
+ const cleanupClose = () => server.off('close', onClose);
1026
+ const onClose = () => {
1027
+ var _a;
1028
+ this._cleanupFns.delete(cleanupClose);
1029
+ this._state = PhysicalState.CLOSED;
1030
+ this._closePromise = null;
1031
+ for (const fn of this._cleanupFns) {
1032
+ fn();
1033
+ }
1034
+ this._cleanupFns.clear();
1035
+ this._server = null;
1036
+ this.emit('close');
1037
+ (_a = this._resolveClose) === null || _a === void 0 ? void 0 : _a.call(this);
1038
+ this._resolveClose = null;
1039
+ };
1040
+ server.once('close', onClose);
1041
+ this._cleanupFns.add(cleanupClose);
1042
+ }
1043
+ };
1044
+ const onError = (err) => {
1045
+ server.off('listening', onConnect);
1046
+ this._state = PhysicalState.CLOSED;
1047
+ this._openPromise = null;
1048
+ this._server = null;
1049
+ reject(err);
1050
+ };
1051
+ server.once('listening', onConnect);
1052
+ server.once('error', onError);
1053
+ server.listen(Object.assign(Object.assign({}, options), { port: (_a = options === null || options === void 0 ? void 0 : options.port) !== null && _a !== void 0 ? _a : 502 }));
1054
+ });
1055
+ return this._openPromise;
1056
+ }
1057
+ close() {
1058
+ if (this.state === PhysicalState.CLOSED) {
1059
+ return Promise.resolve();
1060
+ }
1061
+ if (this.state === PhysicalState.CLOSING) {
1062
+ return this._closePromise;
1063
+ }
1064
+ if (this.state === PhysicalState.OPENING) {
1065
+ return Promise.reject(new Error('Port is opening'));
1066
+ }
1067
+ this._state = PhysicalState.CLOSING;
1068
+ this._closePromise = new Promise((resolve, reject) => {
1069
+ this._resolveClose = resolve;
1070
+ this.server.close();
1071
+ Promise.all(Array.from(this._connections).map((connection) => connection.destroy())).catch((err) => {
1072
+ this._resolveClose = null;
1073
+ reject(err);
393
1074
  });
394
1075
  });
1076
+ return this._closePromise;
1077
+ }
1078
+ }
1079
+
1080
+ class UdpClientPhysicalConnection extends AbstractPhysicalConnection {
1081
+ get state() {
1082
+ return this._state;
1083
+ }
1084
+ get physicalLayer() {
1085
+ return this._physicalLayer;
1086
+ }
1087
+ constructor(physicalLayer, socket) {
1088
+ super();
1089
+ Object.defineProperty(this, "_state", {
1090
+ enumerable: true,
1091
+ configurable: true,
1092
+ writable: true,
1093
+ value: PhysicalConnectionState.CONNECTED
1094
+ });
1095
+ Object.defineProperty(this, "_physicalLayer", {
1096
+ enumerable: true,
1097
+ configurable: true,
1098
+ writable: true,
1099
+ value: void 0
1100
+ });
1101
+ Object.defineProperty(this, "_socket", {
1102
+ enumerable: true,
1103
+ configurable: true,
1104
+ writable: true,
1105
+ value: void 0
1106
+ });
1107
+ Object.defineProperty(this, "_destroyPromise", {
1108
+ enumerable: true,
1109
+ configurable: true,
1110
+ writable: true,
1111
+ value: null
1112
+ });
1113
+ Object.defineProperty(this, "_resolveDestroy", {
1114
+ enumerable: true,
1115
+ configurable: true,
1116
+ writable: true,
1117
+ value: null
1118
+ });
1119
+ Object.defineProperty(this, "_cleanupFns", {
1120
+ enumerable: true,
1121
+ configurable: true,
1122
+ writable: true,
1123
+ value: new Set()
1124
+ });
1125
+ this._physicalLayer = physicalLayer;
1126
+ this._socket = socket;
1127
+ const onMessage = (msg) => {
1128
+ if (this.state !== PhysicalConnectionState.CONNECTED) {
1129
+ return;
1130
+ }
1131
+ this.emit('data', msg);
1132
+ };
1133
+ socket.on('message', onMessage);
1134
+ this._cleanupFns.add(() => socket.off('message', onMessage));
1135
+ const onSocketError = (err) => {
1136
+ this.physicalLayer.emit('error', err);
1137
+ };
1138
+ socket.on('error', onSocketError);
1139
+ this._cleanupFns.add(() => socket.off('error', onSocketError));
1140
+ const cleanupClose = () => socket.off('close', onClose);
1141
+ const onClose = () => {
1142
+ var _a;
1143
+ this._cleanupFns.delete(cleanupClose);
1144
+ this._state = PhysicalConnectionState.DESTROYED;
1145
+ this._destroyPromise = null;
1146
+ for (const fn of this._cleanupFns) {
1147
+ fn();
1148
+ }
1149
+ this._cleanupFns.clear();
1150
+ this.emit('close');
1151
+ (_a = this._resolveDestroy) === null || _a === void 0 ? void 0 : _a.call(this);
1152
+ this._resolveDestroy = null;
1153
+ };
1154
+ socket.once('close', onClose);
1155
+ this._cleanupFns.add(cleanupClose);
395
1156
  }
396
1157
  write(data) {
397
1158
  return new Promise((resolve, reject) => {
398
- if (this.isOpen) {
399
- this._serialport.write(data, (error) => {
1159
+ if (this.state === PhysicalConnectionState.CONNECTED) {
1160
+ this._socket.send(data, (error) => {
400
1161
  if (error) {
401
1162
  reject(error);
402
1163
  }
403
1164
  else {
404
- this.emit('write', data);
405
1165
  resolve();
406
1166
  }
407
1167
  });
408
1168
  }
409
1169
  else {
410
- reject(new ModbusError(ModbusErrorCode.PORT_NOT_OPEN, 'Port is not open'));
1170
+ reject(new Error('Connection is not connected'));
411
1171
  }
412
1172
  });
413
1173
  }
414
- close() {
415
- if (!this._serialport.isOpen) {
416
- return Promise.resolve();
417
- }
418
- return new Promise((resolve) => {
419
- this._serialport.once('close', () => resolve());
420
- this._serialport.close();
421
- });
422
- }
423
1174
  destroy() {
424
- if (this._destroyed) {
1175
+ if (this.state === PhysicalConnectionState.DESTROYED) {
425
1176
  return Promise.resolve();
426
1177
  }
427
- this._destroyed = true;
428
- return this.close().then(() => {
429
- this.removeAllListeners();
1178
+ if (this.state === PhysicalConnectionState.DESTROYING) {
1179
+ return this._destroyPromise;
1180
+ }
1181
+ this._state = PhysicalConnectionState.DESTROYING;
1182
+ this._destroyPromise = new Promise((resolve) => {
1183
+ this._resolveDestroy = resolve;
1184
+ this._socket.close();
430
1185
  });
1186
+ return this._destroyPromise;
431
1187
  }
432
1188
  }
433
-
434
- class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
435
- get isOpen() {
436
- return this._isOpen;
1189
+ class UdpClientPhysicalLayer extends AbstractPhysicalLayer {
1190
+ get state() {
1191
+ return this._state;
437
1192
  }
438
- get destroyed() {
439
- return this._destroyed;
1193
+ get socket() {
1194
+ return this._socket;
440
1195
  }
441
1196
  constructor(options) {
1197
+ var _a;
442
1198
  super();
443
1199
  Object.defineProperty(this, "TYPE", {
444
1200
  enumerable: true,
445
1201
  configurable: true,
446
1202
  writable: true,
447
- value: 'NET'
1203
+ value: 'UDP_CLIENT'
448
1204
  });
449
- Object.defineProperty(this, "_socket", {
1205
+ Object.defineProperty(this, "_state", {
450
1206
  enumerable: true,
451
1207
  configurable: true,
452
1208
  writable: true,
453
- value: null
1209
+ value: PhysicalState.CLOSED
454
1210
  });
455
- Object.defineProperty(this, "_connection", {
1211
+ Object.defineProperty(this, "_connections", {
1212
+ enumerable: true,
1213
+ configurable: true,
1214
+ writable: true,
1215
+ value: new Set()
1216
+ });
1217
+ Object.defineProperty(this, "_socket", {
456
1218
  enumerable: true,
457
1219
  configurable: true,
458
1220
  writable: true,
459
1221
  value: null
460
1222
  });
461
- Object.defineProperty(this, "_isOpen", {
1223
+ Object.defineProperty(this, "_socketOpts", {
462
1224
  enumerable: true,
463
1225
  configurable: true,
464
1226
  writable: true,
465
- value: false
1227
+ value: void 0
466
1228
  });
467
- Object.defineProperty(this, "_isOpening", {
1229
+ Object.defineProperty(this, "_openPromise", {
468
1230
  enumerable: true,
469
1231
  configurable: true,
470
1232
  writable: true,
471
- value: false
1233
+ value: null
472
1234
  });
473
- Object.defineProperty(this, "_destroyed", {
1235
+ Object.defineProperty(this, "_closePromise", {
474
1236
  enumerable: true,
475
1237
  configurable: true,
476
1238
  writable: true,
477
- value: false
1239
+ value: null
478
1240
  });
479
- Object.defineProperty(this, "_socketOptions", {
1241
+ Object.defineProperty(this, "_resolveClose", {
480
1242
  enumerable: true,
481
1243
  configurable: true,
482
1244
  writable: true,
483
- value: void 0
1245
+ value: null
484
1246
  });
485
- this._socketOptions = options;
486
- }
487
- open(options) {
488
- if (this._destroyed) {
489
- return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Port is destroyed'));
490
- }
491
- if (this._isOpen || this._isOpening) {
492
- return Promise.reject(new ModbusError(ModbusErrorCode.PORT_ALREADY_OPEN, 'Port is already open'));
493
- }
494
- this._isOpening = true;
495
- const socket = new Socket(this._socketOptions);
496
- const connection = { id: genConnectionId('tcp-client') };
497
- this._socket = socket;
498
- this._connection = connection;
499
- return new Promise((resolve, reject) => {
500
- let connected = false;
501
- socket.on('data', (data) => {
502
- this.emit('data', data, (d) => this.write(d), connection);
503
- });
504
- socket.on('error', (err) => {
505
- if (connected) {
506
- this.emit('error', err);
507
- }
508
- else {
509
- if (this._connection === connection) {
510
- this._isOpening = false;
511
- this._socket = null;
512
- this._connection = null;
513
- }
514
- socket.removeAllListeners();
515
- reject(err);
516
- }
517
- });
518
- socket.once('close', () => {
519
- if (this._connection === connection) {
520
- this._isOpen = false;
521
- this._isOpening = false;
522
- this._socket = null;
523
- this._connection = null;
524
- socket.removeAllListeners();
525
- this.emit('connection-close', connection);
526
- this.emit('close');
527
- }
528
- });
529
- socket.connect(options !== null && options !== void 0 ? options : { port: 502 }, () => {
530
- connected = true;
531
- this._isOpening = false;
532
- this._isOpen = true;
533
- resolve();
534
- });
1247
+ Object.defineProperty(this, "_cleanupFns", {
1248
+ enumerable: true,
1249
+ configurable: true,
1250
+ writable: true,
1251
+ value: new Set()
535
1252
  });
1253
+ this._socketOpts = Object.assign(Object.assign({}, options), { type: (_a = options === null || options === void 0 ? void 0 : options.type) !== null && _a !== void 0 ? _a : 'udp4' });
536
1254
  }
537
- write(data) {
538
- return new Promise((resolve, reject) => {
539
- if (this._isOpen && this._socket) {
540
- this._socket.write(data, (error) => {
541
- if (error) {
542
- reject(error);
543
- }
544
- else {
545
- this.emit('write', data);
546
- resolve();
1255
+ open(remote) {
1256
+ if (this.state === PhysicalState.OPEN) {
1257
+ return Promise.resolve();
1258
+ }
1259
+ if (this.state === PhysicalState.OPENING) {
1260
+ return this._openPromise;
1261
+ }
1262
+ if (this.state === PhysicalState.CLOSING) {
1263
+ return Promise.reject(new Error('Port is closing'));
1264
+ }
1265
+ this._state = PhysicalState.OPENING;
1266
+ this._openPromise = new Promise((resolve, reject) => {
1267
+ const socket = createSocket(this._socketOpts);
1268
+ this._socket = socket;
1269
+ const onListening = () => {
1270
+ var _a;
1271
+ socket.off('error', onError);
1272
+ socket.connect((_a = remote === null || remote === void 0 ? void 0 : remote.port) !== null && _a !== void 0 ? _a : 502, remote === null || remote === void 0 ? void 0 : remote.address);
1273
+ this._state = PhysicalState.OPEN;
1274
+ this._openPromise = null;
1275
+ this.emit('open');
1276
+ resolve();
1277
+ const connection = new UdpClientPhysicalConnection(this, socket);
1278
+ this._connections.add(connection);
1279
+ const cleanupConnectionClose = () => connection.off('close', onConnectionClose);
1280
+ const onConnectionClose = () => {
1281
+ var _a;
1282
+ this._cleanupFns.delete(cleanupConnectionClose);
1283
+ this._connections.delete(connection);
1284
+ this._state = PhysicalState.CLOSED;
1285
+ this._closePromise = null;
1286
+ for (const fn of this._cleanupFns) {
1287
+ fn();
547
1288
  }
548
- });
549
- }
550
- else {
551
- reject(new ModbusError(ModbusErrorCode.PORT_NOT_OPEN, 'Port is not open'));
552
- }
1289
+ this._cleanupFns.clear();
1290
+ this._socket = null;
1291
+ this.emit('close');
1292
+ (_a = this._resolveClose) === null || _a === void 0 ? void 0 : _a.call(this);
1293
+ this._resolveClose = null;
1294
+ };
1295
+ connection.once('close', onConnectionClose);
1296
+ this._cleanupFns.add(cleanupConnectionClose);
1297
+ this.emit('connect', connection);
1298
+ };
1299
+ const onError = (err) => {
1300
+ socket.off('listening', onListening);
1301
+ this._state = PhysicalState.CLOSED;
1302
+ this._openPromise = null;
1303
+ this._socket = null;
1304
+ reject(err);
1305
+ };
1306
+ socket.once('listening', onListening);
1307
+ socket.once('error', onError);
1308
+ socket.bind();
553
1309
  });
1310
+ return this._openPromise;
554
1311
  }
555
1312
  close() {
556
- if (!this._socket) {
1313
+ if (this.state === PhysicalState.CLOSED) {
557
1314
  return Promise.resolve();
558
1315
  }
559
- const socket = this._socket;
560
- return new Promise((resolve) => {
561
- socket.once('close', () => resolve());
562
- socket.destroy();
563
- });
564
- }
565
- destroy() {
566
- if (this._destroyed) {
567
- return Promise.resolve();
1316
+ if (this.state === PhysicalState.CLOSING) {
1317
+ return this._closePromise;
568
1318
  }
569
- this._destroyed = true;
570
- return this.close().then(() => {
571
- this.removeAllListeners();
1319
+ if (this.state === PhysicalState.OPENING) {
1320
+ return Promise.reject(new Error('Port is opening'));
1321
+ }
1322
+ this._state = PhysicalState.CLOSING;
1323
+ this._closePromise = new Promise((resolve, reject) => {
1324
+ this._resolveClose = resolve;
1325
+ Promise.all(Array.from(this._connections).map((connection) => connection.destroy())).catch((err) => {
1326
+ this._resolveClose = null;
1327
+ reject(err);
1328
+ });
572
1329
  });
1330
+ return this._closePromise;
573
1331
  }
574
1332
  }
575
1333
 
576
- class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
577
- get isOpen() {
578
- return this._isOpen;
1334
+ class UdpServerPhysicalConnection extends AbstractPhysicalConnection {
1335
+ get state() {
1336
+ return this._state;
579
1337
  }
580
- get destroyed() {
581
- return this._destroyed;
1338
+ get physicalLayer() {
1339
+ return this._physicalLayer;
582
1340
  }
583
- constructor(options) {
1341
+ constructor(physicalLayer, socket, remote, idleTimeout, messageEventDelegation) {
584
1342
  super();
585
- Object.defineProperty(this, "TYPE", {
586
- enumerable: true,
587
- configurable: true,
588
- writable: true,
589
- value: 'NET'
590
- });
591
- Object.defineProperty(this, "_server", {
1343
+ Object.defineProperty(this, "_state", {
592
1344
  enumerable: true,
593
1345
  configurable: true,
594
1346
  writable: true,
595
- value: null
1347
+ value: PhysicalConnectionState.CONNECTED
596
1348
  });
597
- Object.defineProperty(this, "_isOpen", {
1349
+ Object.defineProperty(this, "_physicalLayer", {
598
1350
  enumerable: true,
599
1351
  configurable: true,
600
1352
  writable: true,
601
- value: false
1353
+ value: void 0
602
1354
  });
603
- Object.defineProperty(this, "_isOpening", {
1355
+ Object.defineProperty(this, "_socket", {
604
1356
  enumerable: true,
605
1357
  configurable: true,
606
1358
  writable: true,
607
- value: false
1359
+ value: void 0
608
1360
  });
609
- Object.defineProperty(this, "_destroyed", {
1361
+ Object.defineProperty(this, "_remote", {
610
1362
  enumerable: true,
611
1363
  configurable: true,
612
1364
  writable: true,
613
- value: false
1365
+ value: void 0
614
1366
  });
615
- Object.defineProperty(this, "_connections", {
1367
+ Object.defineProperty(this, "_idleTid", {
616
1368
  enumerable: true,
617
1369
  configurable: true,
618
1370
  writable: true,
619
- value: new Map()
1371
+ value: null
620
1372
  });
621
- Object.defineProperty(this, "_serverOptions", {
1373
+ Object.defineProperty(this, "_cleanupFns", {
622
1374
  enumerable: true,
623
1375
  configurable: true,
624
1376
  writable: true,
625
- value: void 0
1377
+ value: new Set()
626
1378
  });
627
- this._serverOptions = options;
1379
+ this._physicalLayer = physicalLayer;
1380
+ this._socket = socket;
1381
+ this._remote = remote;
1382
+ const onMessage = (msg) => {
1383
+ if (this.state !== PhysicalConnectionState.CONNECTED) {
1384
+ return;
1385
+ }
1386
+ if (this._idleTid !== null) {
1387
+ clearTimeout(this._idleTid);
1388
+ this._idleTid = null;
1389
+ if (idleTimeout > 0) {
1390
+ this._idleTid = setTimeout(() => {
1391
+ this.destroy();
1392
+ }, idleTimeout);
1393
+ }
1394
+ }
1395
+ this.emit('data', msg);
1396
+ };
1397
+ messageEventDelegation.add(onMessage);
1398
+ this._cleanupFns.add(() => messageEventDelegation.remove(onMessage));
1399
+ if (idleTimeout > 0) {
1400
+ this._idleTid = setTimeout(() => {
1401
+ this.destroy();
1402
+ }, idleTimeout);
1403
+ }
628
1404
  }
629
- open(options) {
630
- if (this._destroyed) {
631
- return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Port is destroyed'));
632
- }
633
- if (this._isOpen || this._isOpening) {
634
- return Promise.reject(new ModbusError(ModbusErrorCode.PORT_ALREADY_OPEN, 'Port is already open'));
635
- }
636
- this._isOpening = true;
637
- this._connections.clear();
638
- const server = createServer(this._serverOptions, (socket) => {
639
- const conn = { id: genConnectionId('tcp-server') };
640
- this._connections.set(socket, conn);
641
- const response = (data) => new Promise((resolve, reject) => {
642
- socket.write(data, (error) => {
1405
+ write(data) {
1406
+ return new Promise((resolve, reject) => {
1407
+ if (this.state === PhysicalConnectionState.CONNECTED) {
1408
+ this._socket.send(data, this._remote.port, this._remote.address, (error) => {
643
1409
  if (error) {
644
1410
  reject(error);
645
1411
  }
@@ -647,114 +1413,46 @@ class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
647
1413
  resolve();
648
1414
  }
649
1415
  });
650
- });
651
- socket.on('data', (data) => {
652
- this.emit('data', data, response, conn);
653
- });
654
- socket.on('error', (err) => {
655
- this.emit('error', err);
656
- });
657
- socket.once('close', () => {
658
- if (this._connections.delete(socket)) {
659
- this.emit('connection-close', conn);
660
- }
661
- socket.removeAllListeners();
662
- });
663
- });
664
- this._server = server;
665
- return new Promise((resolve, reject) => {
666
- var _a;
667
- let listening = false;
668
- server.once('error', (err) => {
669
- if (listening) {
670
- this.emit('error', err);
671
- }
672
- else {
673
- if (this._server === server) {
674
- this._isOpening = false;
675
- this._server = null;
676
- }
677
- server.removeAllListeners();
678
- reject(err);
679
- }
680
- });
681
- server.once('close', () => {
682
- if (this._server === server) {
683
- this._isOpen = false;
684
- this._isOpening = false;
685
- this._server = null;
686
- const conns = Array.from(this._connections.values());
687
- this._connections.clear();
688
- for (const conn of conns) {
689
- this.emit('connection-close', conn);
690
- }
691
- this.emit('close');
692
- }
693
- });
694
- server.listen(Object.assign(Object.assign({}, options), { port: (_a = options === null || options === void 0 ? void 0 : options.port) !== null && _a !== void 0 ? _a : 502 }), () => {
695
- listening = true;
696
- this._isOpening = false;
697
- this._isOpen = true;
698
- resolve();
699
- });
700
- });
701
- }
702
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
703
- write(data) {
704
- return new Promise((resolve, reject) => {
705
- reject(new ModbusError(ModbusErrorCode.NOT_SUPPORTED, 'Not supported'));
706
- });
707
- }
708
- close() {
709
- if (!this._server) {
710
- return Promise.resolve();
711
- }
712
- const server = this._server;
713
- return new Promise((resolve) => {
714
- server.once('close', () => resolve());
715
- for (const [socket] of this._connections) {
716
- socket.destroy();
717
1416
  }
718
- try {
719
- server.close();
720
- }
721
- catch (_a) {
722
- resolve();
1417
+ else {
1418
+ reject(new Error('Connection is not connected'));
723
1419
  }
724
1420
  });
725
1421
  }
726
1422
  destroy() {
727
- if (this._destroyed) {
728
- return Promise.resolve();
1423
+ this._state = PhysicalConnectionState.DESTROYED;
1424
+ for (const fn of this._cleanupFns) {
1425
+ fn();
729
1426
  }
730
- this._destroyed = true;
731
- return this.close().then(() => {
732
- this.removeAllListeners();
733
- });
1427
+ this._cleanupFns.clear();
1428
+ if (this._idleTid !== null) {
1429
+ clearTimeout(this._idleTid);
1430
+ this._idleTid = null;
1431
+ }
1432
+ this.emit('close');
1433
+ return Promise.resolve();
734
1434
  }
735
1435
  }
736
-
737
- class UdpPhysicalLayer extends AbstractPhysicalLayer {
738
- get isOpen() {
739
- return this._isOpen;
1436
+ class UdpServerPhysicalLayer extends AbstractPhysicalLayer {
1437
+ get state() {
1438
+ return this._state;
740
1439
  }
741
- get destroyed() {
742
- return this._destroyed;
1440
+ get socket() {
1441
+ return this._socket;
743
1442
  }
744
1443
  constructor(options) {
745
- var _a, _b, _c, _d;
746
1444
  super();
747
1445
  Object.defineProperty(this, "TYPE", {
748
1446
  enumerable: true,
749
1447
  configurable: true,
750
1448
  writable: true,
751
- value: 'NET'
1449
+ value: 'UDP_SERVER'
752
1450
  });
753
- Object.defineProperty(this, "_socket", {
1451
+ Object.defineProperty(this, "_state", {
754
1452
  enumerable: true,
755
1453
  configurable: true,
756
1454
  writable: true,
757
- value: null
1455
+ value: PhysicalState.CLOSED
758
1456
  });
759
1457
  Object.defineProperty(this, "_connections", {
760
1458
  enumerable: true,
@@ -762,233 +1460,197 @@ class UdpPhysicalLayer extends AbstractPhysicalLayer {
762
1460
  writable: true,
763
1461
  value: new Map()
764
1462
  });
765
- Object.defineProperty(this, "_isOpen", {
766
- enumerable: true,
767
- configurable: true,
768
- writable: true,
769
- value: false
770
- });
771
- Object.defineProperty(this, "_isOpening", {
772
- enumerable: true,
773
- configurable: true,
774
- writable: true,
775
- value: false
776
- });
777
- Object.defineProperty(this, "_destroyed", {
1463
+ Object.defineProperty(this, "_socket", {
778
1464
  enumerable: true,
779
1465
  configurable: true,
780
1466
  writable: true,
781
- value: false
1467
+ value: null
782
1468
  });
783
- Object.defineProperty(this, "_socketOptions", {
1469
+ Object.defineProperty(this, "_opts", {
784
1470
  enumerable: true,
785
1471
  configurable: true,
786
1472
  writable: true,
787
1473
  value: void 0
788
1474
  });
789
- Object.defineProperty(this, "_port", {
1475
+ Object.defineProperty(this, "_openPromise", {
790
1476
  enumerable: true,
791
1477
  configurable: true,
792
1478
  writable: true,
793
- value: void 0
1479
+ value: null
794
1480
  });
795
- Object.defineProperty(this, "_address", {
1481
+ Object.defineProperty(this, "_closePromise", {
796
1482
  enumerable: true,
797
1483
  configurable: true,
798
1484
  writable: true,
799
- value: void 0
1485
+ value: null
800
1486
  });
801
- Object.defineProperty(this, "_idleTimeout", {
1487
+ Object.defineProperty(this, "_resolveClose", {
802
1488
  enumerable: true,
803
1489
  configurable: true,
804
1490
  writable: true,
805
- value: void 0
1491
+ value: null
806
1492
  });
807
- Object.defineProperty(this, "isServer", {
1493
+ Object.defineProperty(this, "_cleanupFns", {
808
1494
  enumerable: true,
809
1495
  configurable: true,
810
1496
  writable: true,
811
- value: void 0
1497
+ value: new Set()
812
1498
  });
813
- this._socketOptions = options === null || options === void 0 ? void 0 : options.socket;
814
- this.isServer = !(options === null || options === void 0 ? void 0 : options.remote);
815
- this._port = (_b = (_a = options === null || options === void 0 ? void 0 : options.remote) === null || _a === void 0 ? void 0 : _a.port) !== null && _b !== void 0 ? _b : 502;
816
- this._address = (_c = options === null || options === void 0 ? void 0 : options.remote) === null || _c === void 0 ? void 0 : _c.address;
817
- this._idleTimeout = (_d = options === null || options === void 0 ? void 0 : options.idleTimeout) !== null && _d !== void 0 ? _d : 30000;
1499
+ this._opts = options !== null && options !== void 0 ? options : {};
818
1500
  }
1501
+ /**
1502
+ * Bind the UDP socket and start accepting datagrams.
1503
+ *
1504
+ * @param options Bind options (port, address, etc.). Defaults to port 502.
1505
+ * @param [idleTimeout=30000] Maximum idle time in milliseconds before an
1506
+ * inactive client connection is evicted. Pass `0` to disable eviction
1507
+ * (connections never time out). Disabling eviction may cause unbounded
1508
+ * memory growth if the server sees many unique clients.
1509
+ */
819
1510
  open(options) {
820
- var _a, _b;
821
- if (this._destroyed) {
822
- return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Port is destroyed'));
1511
+ if (this.state === PhysicalState.OPEN) {
1512
+ return Promise.resolve();
823
1513
  }
824
- if (this._isOpen || this._isOpening) {
825
- return Promise.reject(new ModbusError(ModbusErrorCode.PORT_ALREADY_OPEN, 'Port is already open'));
1514
+ if (this.state === PhysicalState.OPENING) {
1515
+ return this._openPromise;
826
1516
  }
827
- this._isOpening = true;
828
- this._connections.clear();
829
- const socket = createSocket(Object.assign(Object.assign({}, this._socketOptions), { type: (_b = (_a = this._socketOptions) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : 'udp4' }), (msg, rinfo) => this._handleMessage(socket, msg, rinfo));
830
- this._socket = socket;
831
- return new Promise((resolve, reject) => {
832
- var _a;
833
- let started = false;
834
- socket.on('error', (err) => {
835
- if (started) {
836
- this.emit('error', err);
837
- }
838
- else {
839
- if (this._socket === socket) {
840
- this._isOpening = false;
841
- this._socket = null;
842
- }
843
- socket.removeAllListeners();
844
- reject(err);
845
- }
846
- });
847
- socket.once('close', () => {
848
- if (this._socket !== socket) {
1517
+ if (this.state === PhysicalState.CLOSING) {
1518
+ return Promise.reject(new Error('Port is closing'));
1519
+ }
1520
+ this._state = PhysicalState.OPENING;
1521
+ this._openPromise = new Promise((resolve, reject) => {
1522
+ var _a, _b, _c;
1523
+ const socket = createSocket(Object.assign(Object.assign({}, this._opts.socketOpts), { type: (_b = (_a = this._opts.socketOpts) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : 'udp4' }), (msg, rinfo) => {
1524
+ var _a;
1525
+ if (!isWhitelisted(rinfo.address, this._opts.whitelist)) {
849
1526
  return;
850
1527
  }
851
- this._isOpen = false;
852
- this._isOpening = false;
853
- this._socket = null;
854
- socket.removeAllListeners();
855
- const entries = Array.from(this._connections.values());
856
- this._connections.clear();
857
- for (const entry of entries) {
858
- if (entry.idleTimer) {
859
- clearTimeout(entry.idleTimer);
1528
+ const id = `${rinfo.address}:${rinfo.port}`;
1529
+ let connection = this._connections.get(id);
1530
+ if (!connection) {
1531
+ if (this._opts.maxConnections !== undefined && this._connections.size >= this._opts.maxConnections) {
1532
+ return;
860
1533
  }
861
- this.emit('connection-close', entry.conn);
1534
+ const listeners = new Set();
1535
+ const conn = new UdpServerPhysicalConnection(this, socket, rinfo, (_a = this._opts.idleTimeout) !== null && _a !== void 0 ? _a : 30000, {
1536
+ add: (listener) => {
1537
+ listeners.add(listener);
1538
+ },
1539
+ remove: (listener) => {
1540
+ listeners.delete(listener);
1541
+ },
1542
+ });
1543
+ connection = { connection: conn, listeners };
1544
+ this._connections.set(id, connection);
1545
+ const cleanupConnectionClose = () => conn.off('close', onConnectionClose);
1546
+ const onConnectionClose = () => {
1547
+ this._cleanupFns.delete(cleanupConnectionClose);
1548
+ this._connections.delete(id);
1549
+ };
1550
+ conn.once('close', onConnectionClose);
1551
+ this._cleanupFns.add(cleanupConnectionClose);
1552
+ this.emit('connect', conn);
1553
+ }
1554
+ for (const fn of connection.listeners) {
1555
+ fn(msg, rinfo);
862
1556
  }
863
- this.emit('close');
864
1557
  });
865
- if (this.isServer) {
866
- socket.bind(Object.assign(Object.assign({}, options), { port: (_a = options === null || options === void 0 ? void 0 : options.port) !== null && _a !== void 0 ? _a : 502 }), () => {
867
- started = true;
868
- this._isOpening = false;
869
- this._isOpen = true;
870
- resolve();
871
- });
872
- }
873
- else {
874
- started = true;
875
- this._isOpening = false;
876
- this._isOpen = true;
1558
+ this._socket = socket;
1559
+ const onListening = () => {
1560
+ socket.off('error', onError);
1561
+ this._state = PhysicalState.OPEN;
1562
+ this._openPromise = null;
1563
+ this.emit('open');
877
1564
  resolve();
878
- }
879
- });
880
- }
881
- _handleMessage(socket, msg, rinfo) {
882
- if (this._socket !== socket) {
883
- return;
884
- }
885
- if (!this.isServer) {
886
- if (rinfo.port !== this._port) {
887
- return;
888
- }
889
- if (this._address !== undefined && rinfo.address !== this._address) {
890
- return;
891
- }
892
- }
893
- const key = `${rinfo.address}:${rinfo.port}`;
894
- let entry = this._connections.get(key);
895
- if (!entry) {
896
- entry = { conn: { id: genConnectionId('udp') } };
897
- this._connections.set(key, entry);
898
- }
899
- if (this.isServer && this._idleTimeout > 0) {
900
- if (entry.idleTimer) {
901
- clearTimeout(entry.idleTimer);
902
- }
903
- const conn = entry.conn;
904
- entry.idleTimer = setTimeout(() => {
905
- const e = this._connections.get(key);
906
- if (!e || e.conn !== conn) {
907
- return;
908
- }
909
- this._connections.delete(key);
910
- this.emit('connection-close', conn);
911
- }, this._idleTimeout);
912
- }
913
- const conn = entry.conn;
914
- const response = (data) => new Promise((resolve, reject) => {
915
- socket.send(data, rinfo.port, rinfo.address, (error) => {
916
- if (error) {
917
- reject(error);
918
- }
919
- else {
920
- resolve();
1565
+ {
1566
+ const onError = (err) => {
1567
+ this.emit('error', err);
1568
+ };
1569
+ socket.on('error', onError);
1570
+ this._cleanupFns.add(() => socket.off('error', onError));
1571
+ const cleanupClose = () => socket.off('close', onClose);
1572
+ const onClose = () => {
1573
+ var _a;
1574
+ this._socket = null;
1575
+ this._cleanupFns.delete(cleanupClose);
1576
+ this._state = PhysicalState.CLOSED;
1577
+ this._closePromise = null;
1578
+ for (const fn of this._cleanupFns) {
1579
+ fn();
1580
+ }
1581
+ this._cleanupFns.clear();
1582
+ this._socket = null;
1583
+ this.emit('close');
1584
+ (_a = this._resolveClose) === null || _a === void 0 ? void 0 : _a.call(this);
1585
+ this._resolveClose = null;
1586
+ };
1587
+ socket.once('close', onClose);
1588
+ this._cleanupFns.add(cleanupClose);
921
1589
  }
922
- });
923
- });
924
- this.emit('data', msg, response, conn);
925
- }
926
- write(data) {
927
- return new Promise((resolve, reject) => {
928
- if (this._isOpen && this._socket) {
929
- this._socket.send(data, this._port, this._address, (error) => {
930
- if (error) {
931
- reject(error);
932
- }
933
- else {
934
- this.emit('write', data);
935
- resolve();
936
- }
937
- });
938
- }
939
- else {
940
- reject(new ModbusError(ModbusErrorCode.PORT_NOT_OPEN, 'Port is not open'));
941
- }
1590
+ };
1591
+ const onError = (err) => {
1592
+ socket.off('listening', onListening);
1593
+ this._state = PhysicalState.CLOSED;
1594
+ this._openPromise = null;
1595
+ this._socket = null;
1596
+ reject(err);
1597
+ };
1598
+ socket.once('listening', onListening);
1599
+ socket.once('error', onError);
1600
+ socket.bind(Object.assign(Object.assign({}, options), { port: (_c = options === null || options === void 0 ? void 0 : options.port) !== null && _c !== void 0 ? _c : 502 }));
942
1601
  });
1602
+ return this._openPromise;
943
1603
  }
944
1604
  close() {
945
- if (!this._socket) {
1605
+ if (this.state === PhysicalState.CLOSED) {
946
1606
  return Promise.resolve();
947
1607
  }
948
- const socket = this._socket;
949
- return new Promise((resolve) => {
950
- socket.once('close', () => resolve());
951
- try {
952
- socket.close();
953
- }
954
- catch (_a) {
955
- // Socket may already be closed; the registered 'close' listener still fires asynchronously.
1608
+ if (this.state === PhysicalState.CLOSING) {
1609
+ return this._closePromise;
1610
+ }
1611
+ if (this.state === PhysicalState.OPENING) {
1612
+ return Promise.reject(new Error('Port is opening'));
1613
+ }
1614
+ this._state = PhysicalState.CLOSING;
1615
+ this._closePromise = new Promise((resolve) => {
1616
+ this._resolveClose = resolve;
1617
+ this.socket.close();
1618
+ // Snapshot connections so destruction (which removes from the Map)
1619
+ // does not interfere with iteration.
1620
+ const connections = Array.from(this._connections.values());
1621
+ for (const { connection } of connections) {
1622
+ connection.destroy();
956
1623
  }
957
1624
  });
1625
+ return this._closePromise;
958
1626
  }
959
- destroy() {
960
- if (this._destroyed) {
961
- return Promise.resolve();
962
- }
963
- this._destroyed = true;
964
- return this.close().then(() => {
965
- this.removeAllListeners();
966
- });
1627
+ }
1628
+
1629
+ function createPhysicalLayer(config) {
1630
+ switch (config.type) {
1631
+ case 'SERIAL':
1632
+ return new SerialPhysicalLayer(config.opts);
1633
+ case 'TCP_CLIENT':
1634
+ return new TcpClientPhysicalLayer(config.socketOpts);
1635
+ case 'TCP_SERVER':
1636
+ return new TcpServerPhysicalLayer(config.opts);
1637
+ case 'UDP_CLIENT':
1638
+ return new UdpClientPhysicalLayer(config.socketOpts);
1639
+ case 'UDP_SERVER':
1640
+ return new UdpServerPhysicalLayer(config.opts);
1641
+ case 'CUSTOM':
1642
+ return config.layer;
967
1643
  }
968
1644
  }
969
1645
 
1646
+ /**
1647
+ * Application-layer protocol handler bound to a single physical connection.
1648
+ *
1649
+ * Its lifetime follows the channel: created when the underlying connection is
1650
+ * established and discarded when the connection closes. Subclasses implement
1651
+ * ASCII, RTU, or TCP framing rules.
1652
+ */
970
1653
  class AbstractApplicationLayer extends EventEmitter {
971
- constructor() {
972
- super(...arguments);
973
- Object.defineProperty(this, "_role", {
974
- enumerable: true,
975
- configurable: true,
976
- writable: true,
977
- value: void 0
978
- });
979
- }
980
- get role() {
981
- if (!this._role) {
982
- throw new ModbusError(ModbusErrorCode.INVALID_ROLE, 'Application layer role not set');
983
- }
984
- return this._role;
985
- }
986
- set role(value) {
987
- if (this._role && this._role !== value) {
988
- throw new ModbusError(ModbusErrorCode.INVALID_ROLE, `Application layer role already set to ${this._role}`);
989
- }
990
- this._role = value;
991
- }
992
1654
  flush() {
993
1655
  // no-op — override in subclasses
994
1656
  }
@@ -1001,7 +1663,10 @@ class AbstractApplicationLayer extends EventEmitter {
1001
1663
  const MAX_FRAME_LENGTH = 256;
1002
1664
  const MIN_FRAME_LENGTH = 4;
1003
1665
  class RtuApplicationLayer extends AbstractApplicationLayer {
1004
- constructor(physicalLayer, options = {}) {
1666
+ get connection() {
1667
+ return this._connection;
1668
+ }
1669
+ constructor(role, connection, options = {}) {
1005
1670
  super();
1006
1671
  Object.defineProperty(this, "PROTOCOL", {
1007
1672
  enumerable: true,
@@ -1009,23 +1674,29 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
1009
1674
  writable: true,
1010
1675
  value: 'RTU'
1011
1676
  });
1012
- Object.defineProperty(this, "_states", {
1677
+ Object.defineProperty(this, "ROLE", {
1013
1678
  enumerable: true,
1014
1679
  configurable: true,
1015
1680
  writable: true,
1016
- value: new Map()
1681
+ value: void 0
1017
1682
  });
1018
- Object.defineProperty(this, "_customFunctionCodes", {
1683
+ Object.defineProperty(this, "_connection", {
1019
1684
  enumerable: true,
1020
1685
  configurable: true,
1021
1686
  writable: true,
1022
- value: new Map()
1687
+ value: void 0
1023
1688
  });
1024
- Object.defineProperty(this, "_removeAllListeners", {
1689
+ Object.defineProperty(this, "_state", {
1025
1690
  enumerable: true,
1026
1691
  configurable: true,
1027
1692
  writable: true,
1028
- value: []
1693
+ value: void 0
1694
+ });
1695
+ Object.defineProperty(this, "_poolSize", {
1696
+ enumerable: true,
1697
+ configurable: true,
1698
+ writable: true,
1699
+ value: void 0
1029
1700
  });
1030
1701
  Object.defineProperty(this, "_threePointFiveT", {
1031
1702
  enumerable: true,
@@ -1039,39 +1710,29 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
1039
1710
  writable: true,
1040
1711
  value: void 0
1041
1712
  });
1042
- Object.defineProperty(this, "_destroyed", {
1713
+ Object.defineProperty(this, "_customFunctionCodes", {
1043
1714
  enumerable: true,
1044
1715
  configurable: true,
1045
1716
  writable: true,
1046
- value: false
1717
+ value: new Map()
1047
1718
  });
1048
- const { intervalBetweenFrames, interCharTimeout } = options;
1049
- let threePointFiveT = 0;
1050
- let onePointFiveT = 0;
1051
- if (physicalLayer.TYPE === 'SERIAL') {
1052
- const baudRate = physicalLayer.baudRate;
1053
- if (intervalBetweenFrames && intervalBetweenFrames.unit === 'ms') {
1054
- threePointFiveT = intervalBetweenFrames.value;
1055
- }
1056
- else {
1057
- threePointFiveT =
1058
- baudRate > 19200 ? 1.75 : Math.ceil(bitsToMs(baudRate, intervalBetweenFrames ? intervalBetweenFrames.value : 38.5));
1059
- }
1060
- if (interCharTimeout) {
1061
- if (interCharTimeout.unit === 'ms') {
1062
- onePointFiveT = interCharTimeout.value;
1063
- }
1064
- else {
1065
- onePointFiveT = baudRate > 19200 ? 0.75 : Math.ceil(bitsToMs(baudRate, interCharTimeout.value));
1066
- }
1067
- }
1068
- }
1069
- this._threePointFiveT = threePointFiveT;
1070
- this._onePointFiveT = onePointFiveT;
1071
- const handleData = (data, response, connection) => {
1072
- let state = this.getState(connection.id);
1719
+ Object.defineProperty(this, "_cleanupFns", {
1720
+ enumerable: true,
1721
+ configurable: true,
1722
+ writable: true,
1723
+ value: new Set()
1724
+ });
1725
+ this.ROLE = role;
1726
+ this._connection = connection;
1727
+ const { intervalBetweenFrames, interCharTimeout, poolSize } = options;
1728
+ this._poolSize = poolSize !== null && poolSize !== void 0 ? poolSize : MAX_FRAME_LENGTH * 2;
1729
+ this._state = { pool: Buffer.alloc(this._poolSize), start: 0, end: 0 };
1730
+ this._threePointFiveT = intervalBetweenFrames !== null && intervalBetweenFrames !== void 0 ? intervalBetweenFrames : 0;
1731
+ this._onePointFiveT = interCharTimeout !== null && interCharTimeout !== void 0 ? interCharTimeout : 0;
1732
+ const onData = (data) => {
1733
+ let state = this._state;
1073
1734
  if (state.t15Expired && state.end > state.start) {
1074
- this.emit('framing-error', new ModbusError(ModbusErrorCode.T1_5_EXCEEDED, 'Inter-character timeout (t1.5) exceeded'));
1735
+ this.emit('framing-error', new Error('Inter-character timeout (t1.5) exceeded'));
1075
1736
  state.start = 0;
1076
1737
  state.end = 0;
1077
1738
  }
@@ -1080,11 +1741,11 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
1080
1741
  while (dataOffset < data.length) {
1081
1742
  const space = state.pool.length - state.end;
1082
1743
  if (space === 0) {
1083
- this.clearStateTimers(state);
1084
- this.flushBuffer(connection.id, response, connection, this._threePointFiveT > 0);
1085
- state = this.getState(connection.id);
1744
+ this.clearStateTimers();
1745
+ this.flushBuffer(this._threePointFiveT > 0);
1746
+ state = this._state;
1086
1747
  if (state.pool.length - state.end === 0) {
1087
- this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_DATA, 'Frame buffer exhausted before complete frame received'));
1748
+ this.emit('framing-error', new Error('Frame buffer exhausted before complete frame received'));
1088
1749
  state.start = 0;
1089
1750
  state.end = 0;
1090
1751
  state.t15Expired = false;
@@ -1096,10 +1757,10 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
1096
1757
  state.end += toCopy;
1097
1758
  dataOffset += toCopy;
1098
1759
  }
1099
- this.clearStateTimers(state);
1760
+ this.clearStateTimers();
1100
1761
  const commitFrame = () => {
1101
- this.clearStateTimers(state);
1102
- this.flushBuffer(connection.id, response, connection, this._threePointFiveT > 0);
1762
+ this.clearStateTimers();
1763
+ this.flushBuffer(this._threePointFiveT > 0);
1103
1764
  };
1104
1765
  if (state.end - state.start >= MAX_FRAME_LENGTH) {
1105
1766
  commitFrame();
@@ -1117,30 +1778,20 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
1117
1778
  commitFrame();
1118
1779
  }
1119
1780
  };
1120
- physicalLayer.on('data', handleData);
1121
- this._removeAllListeners.push(() => physicalLayer.removeListener('data', handleData));
1122
- const handleConnectionClose = (connection) => {
1123
- this.clearState(connection.id);
1124
- };
1125
- physicalLayer.on('connection-close', handleConnectionClose);
1126
- this._removeAllListeners.push(() => physicalLayer.removeListener('connection-close', handleConnectionClose));
1127
- const handleClose = () => {
1128
- for (const key of [...this._states.keys()]) {
1129
- this.clearState(key);
1130
- }
1781
+ connection.on('data', onData);
1782
+ this._cleanupFns.add(() => connection.off('data', onData));
1783
+ const onClose = () => {
1784
+ for (const fn of this._cleanupFns) {
1785
+ fn();
1786
+ }
1787
+ this._cleanupFns.clear();
1788
+ this.clearStateTimers();
1131
1789
  };
1132
- physicalLayer.on('close', handleClose);
1133
- this._removeAllListeners.push(() => physicalLayer.removeListener('close', handleClose));
1790
+ connection.on('close', onClose);
1791
+ this._cleanupFns.add(() => connection.off('close', onClose));
1134
1792
  }
1135
- getState(key) {
1136
- let state = this._states.get(key);
1137
- if (!state) {
1138
- state = { pool: Buffer.alloc(MAX_FRAME_LENGTH * 2), start: 0, end: 0 };
1139
- this._states.set(key, state);
1140
- }
1141
- return state;
1142
- }
1143
- clearStateTimers(state) {
1793
+ clearStateTimers() {
1794
+ const state = this._state;
1144
1795
  if (state.timer) {
1145
1796
  clearTimeout(state.timer);
1146
1797
  state.timer = undefined;
@@ -1150,45 +1801,18 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
1150
1801
  state.interCharTimer = undefined;
1151
1802
  }
1152
1803
  }
1153
- clearState(key) {
1154
- const state = this._states.get(key);
1155
- if (state) {
1156
- this.clearStateTimers(state);
1157
- }
1158
- this._states.delete(key);
1159
- }
1160
- flush() {
1161
- for (const state of this._states.values()) {
1162
- this.clearStateTimers(state);
1163
- state.start = 0;
1164
- state.end = 0;
1165
- }
1166
- this._states.clear();
1167
- }
1168
- addCustomFunctionCode(cfc) {
1169
- if (!isUint8(cfc.fc)) {
1170
- throw new ModbusError(ModbusErrorCode.RANGE, `fc must be an integer in 0..255, got ${cfc.fc}`);
1171
- }
1172
- this._customFunctionCodes.set(cfc.fc, cfc);
1173
- }
1174
- removeCustomFunctionCode(fc) {
1175
- this._customFunctionCodes.delete(fc);
1176
- }
1177
- flushBuffer(key, response, connection, strict) {
1178
- const state = this._states.get(key);
1179
- if (!state) {
1180
- return;
1181
- }
1804
+ flushBuffer(strict) {
1805
+ const state = this._state;
1182
1806
  while (state.end - state.start > 0) {
1183
1807
  const buffer = state.pool.subarray(state.start, state.end);
1184
1808
  const result = this.tryExtract(buffer);
1185
1809
  if (result.kind === 'frame') {
1186
1810
  state.start += result.frame.length;
1187
- this.deliverFrame(result.frame, response, connection);
1811
+ this.deliverFrame(result.frame);
1188
1812
  }
1189
1813
  else if (result.kind === 'skip') {
1190
1814
  if (strict) {
1191
- this.emit('framing-error', new ModbusError(ModbusErrorCode.CRC_MISMATCH, 'CRC mismatch'));
1815
+ this.emit('framing-error', new Error('CRC mismatch'));
1192
1816
  state.start = 0;
1193
1817
  state.end = 0;
1194
1818
  state.t15Expired = false;
@@ -1202,14 +1826,14 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
1202
1826
  continue;
1203
1827
  }
1204
1828
  if (strict) {
1205
- this.emit('framing-error', new ModbusError(state.t15Expired ? ModbusErrorCode.T1_5_EXCEEDED : ModbusErrorCode.INCOMPLETE_FRAME, state.t15Expired ? 'Inter-character timeout (t1.5) exceeded' : 'Incomplete frame at t3.5'));
1829
+ this.emit('framing-error', new Error(state.t15Expired ? 'Inter-character timeout (t1.5) exceeded' : 'Incomplete frame at t3.5'));
1206
1830
  state.start = 0;
1207
1831
  state.end = 0;
1208
1832
  state.t15Expired = false;
1209
1833
  return;
1210
1834
  }
1211
1835
  if (state.t15Expired) {
1212
- this.emit('framing-error', new ModbusError(ModbusErrorCode.T1_5_EXCEEDED, 'Inter-character timeout (t1.5) exceeded'));
1836
+ this.emit('framing-error', new Error('Inter-character timeout (t1.5) exceeded'));
1213
1837
  state.start = 0;
1214
1838
  state.end = 0;
1215
1839
  state.t15Expired = false;
@@ -1230,15 +1854,22 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
1230
1854
  state.end -= state.start;
1231
1855
  state.start = 0;
1232
1856
  }
1233
- if (state.end === 0 && !state.timer) {
1234
- this._states.delete(key);
1235
- }
1857
+ }
1858
+ deliverFrame(buffer) {
1859
+ const frameBuf = Buffer.from(buffer);
1860
+ const frame = {
1861
+ unit: frameBuf[0],
1862
+ fc: frameBuf[1],
1863
+ data: frameBuf.subarray(2, frameBuf.length - 2),
1864
+ buffer: frameBuf,
1865
+ };
1866
+ this.emit('framing', frame);
1236
1867
  }
1237
1868
  tryExtract(buffer) {
1238
1869
  if (buffer.length < MIN_FRAME_LENGTH) {
1239
1870
  return { kind: 'insufficient' };
1240
1871
  }
1241
- const isResponse = this.role === 'MASTER';
1872
+ const isResponse = this.ROLE === 'MASTER';
1242
1873
  const fc = buffer[1];
1243
1874
  const cfc = this._customFunctionCodes.get(fc);
1244
1875
  if (cfc) {
@@ -1258,12 +1889,12 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
1258
1889
  }
1259
1890
  return {
1260
1891
  kind: 'error',
1261
- error: new ModbusError(ModbusErrorCode.UNKNOWN_FC, `Unknown function code 0x${fc.toString(16).padStart(2, '0')} — register a CustomFunctionCode to frame this FC`),
1892
+ error: new Error(`Unknown function code 0x${fc.toString(16).padStart(2, '0')} — register a CustomFunctionCode to frame this FC`),
1262
1893
  };
1263
1894
  }
1264
1895
  checkExpected(buffer, expected) {
1265
1896
  if (expected > MAX_FRAME_LENGTH || expected < MIN_FRAME_LENGTH) {
1266
- return { kind: 'error', error: new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data') };
1897
+ return { kind: 'error', error: new Error('Invalid data') };
1267
1898
  }
1268
1899
  if (buffer.length < expected) {
1269
1900
  return { kind: 'insufficient' };
@@ -1276,40 +1907,28 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
1276
1907
  crcMatches(buffer, length) {
1277
1908
  return buffer.readUInt16LE(length - 2) === crc(buffer.subarray(0, length - 2));
1278
1909
  }
1279
- deliverFrame(buffer, response, connection) {
1280
- const frameBuf = Buffer.from(buffer);
1281
- const frame = {
1282
- unit: frameBuf[0],
1283
- fc: frameBuf[1],
1284
- data: frameBuf.subarray(2, frameBuf.length - 2),
1285
- buffer: frameBuf,
1286
- };
1287
- this.emit('framing', frame, response, connection);
1910
+ flush() {
1911
+ this.clearStateTimers();
1912
+ this._state.start = 0;
1913
+ this._state.end = 0;
1914
+ }
1915
+ addCustomFunctionCode(cfc) {
1916
+ if (!isUint8(cfc.fc)) {
1917
+ throw new Error(`fc must be an integer in 0..255, got ${cfc.fc}`);
1918
+ }
1919
+ this._customFunctionCodes.set(cfc.fc, cfc);
1920
+ }
1921
+ removeCustomFunctionCode(fc) {
1922
+ this._customFunctionCodes.delete(fc);
1288
1923
  }
1289
1924
  encode(data) {
1290
- const buffer = Buffer.alloc(data.data.length + 4);
1925
+ const buffer = Buffer.allocUnsafe(data.data.length + 4);
1291
1926
  buffer.writeUInt8(data.unit, 0);
1292
1927
  buffer.writeUInt8(data.fc, 1);
1293
1928
  buffer.set(data.data, 2);
1294
1929
  buffer.writeUInt16LE(crc(buffer.subarray(0, -2)), buffer.length - 2);
1295
1930
  return buffer;
1296
1931
  }
1297
- destroy() {
1298
- if (this._destroyed) {
1299
- return;
1300
- }
1301
- this._destroyed = true;
1302
- this.removeAllListeners();
1303
- for (const removeListener of this._removeAllListeners) {
1304
- removeListener();
1305
- }
1306
- this._removeAllListeners.length = 0;
1307
- for (const state of this._states.values()) {
1308
- this.clearStateTimers(state);
1309
- }
1310
- this._states.clear();
1311
- this._customFunctionCodes.clear();
1312
- }
1313
1932
  }
1314
1933
 
1315
1934
  const CHAR_CODE = {
@@ -1334,7 +1953,10 @@ for (let i = 0x61; i <= 0x66; i++) {
1334
1953
  }
1335
1954
  const HEX_ENCODE = new Uint8Array('0123456789ABCDEF'.split('').map((c) => c.charCodeAt(0)));
1336
1955
  class AsciiApplicationLayer extends AbstractApplicationLayer {
1337
- constructor(physicalLayer, options = {}) {
1956
+ get connection() {
1957
+ return this._connection;
1958
+ }
1959
+ constructor(role, connection, options = {}) {
1338
1960
  var _a;
1339
1961
  super();
1340
1962
  Object.defineProperty(this, "PROTOCOL", {
@@ -1343,30 +1965,38 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
1343
1965
  writable: true,
1344
1966
  value: 'ASCII'
1345
1967
  });
1968
+ Object.defineProperty(this, "ROLE", {
1969
+ enumerable: true,
1970
+ configurable: true,
1971
+ writable: true,
1972
+ value: void 0
1973
+ });
1346
1974
  Object.defineProperty(this, "lenientHex", {
1347
1975
  enumerable: true,
1348
1976
  configurable: true,
1349
1977
  writable: true,
1350
1978
  value: void 0
1351
1979
  });
1352
- Object.defineProperty(this, "_states", {
1980
+ Object.defineProperty(this, "_connection", {
1353
1981
  enumerable: true,
1354
1982
  configurable: true,
1355
1983
  writable: true,
1356
- value: new Map()
1984
+ value: void 0
1357
1985
  });
1358
- Object.defineProperty(this, "_removeAllListeners", {
1986
+ Object.defineProperty(this, "_state", {
1359
1987
  enumerable: true,
1360
1988
  configurable: true,
1361
1989
  writable: true,
1362
- value: []
1990
+ value: { status: 'idle', frame: [] }
1363
1991
  });
1364
- Object.defineProperty(this, "_destroyed", {
1992
+ Object.defineProperty(this, "_cleanupFns", {
1365
1993
  enumerable: true,
1366
1994
  configurable: true,
1367
1995
  writable: true,
1368
- value: false
1996
+ value: new Set()
1369
1997
  });
1998
+ this.ROLE = role;
1999
+ this._connection = connection;
1370
2000
  this.lenientHex = (_a = options.lenientHex) !== null && _a !== void 0 ? _a : false;
1371
2001
  const lenientHex = this.lenientHex;
1372
2002
  const isHexChar = (value) => {
@@ -1381,8 +2011,8 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
1381
2011
  }
1382
2012
  return false;
1383
2013
  };
1384
- const handleData = (data, response, connection) => {
1385
- const state = this.getState(connection.id);
2014
+ const onData = (data) => {
2015
+ const state = this._state;
1386
2016
  data.forEach((value) => {
1387
2017
  switch (state.status) {
1388
2018
  case 'idle': {
@@ -1402,12 +2032,12 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
1402
2032
  else if (state.frame.length >= MAX_ASCII_PAYLOAD) {
1403
2033
  state.status = 'idle';
1404
2034
  state.frame = [];
1405
- this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data'));
2035
+ this.emit('framing-error', new Error('Invalid data'));
1406
2036
  }
1407
2037
  else if (!isHexChar(value)) {
1408
2038
  state.status = 'idle';
1409
2039
  state.frame = [];
1410
- this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_HEX, 'Invalid hex character'));
2040
+ this.emit('framing-error', new Error('Invalid hex character'));
1411
2041
  }
1412
2042
  else {
1413
2043
  state.frame.push(value);
@@ -1422,7 +2052,7 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
1422
2052
  else {
1423
2053
  state.status = 'idle';
1424
2054
  if (value === CHAR_CODE.LF) {
1425
- this.framing(Buffer.from(state.frame), response, connection);
2055
+ this.framing(Buffer.from(state.frame));
1426
2056
  }
1427
2057
  }
1428
2058
  break;
@@ -1430,64 +2060,56 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
1430
2060
  }
1431
2061
  });
1432
2062
  };
1433
- physicalLayer.on('data', handleData);
1434
- this._removeAllListeners.push(() => physicalLayer.removeListener('data', handleData));
1435
- const handleConnectionClose = (connection) => {
1436
- this._states.delete(connection.id);
1437
- };
1438
- physicalLayer.on('connection-close', handleConnectionClose);
1439
- this._removeAllListeners.push(() => physicalLayer.removeListener('connection-close', handleConnectionClose));
1440
- const handleClose = () => {
1441
- this._states.clear();
2063
+ connection.on('data', onData);
2064
+ this._cleanupFns.add(() => connection.off('data', onData));
2065
+ const onClose = () => {
2066
+ for (const fn of this._cleanupFns) {
2067
+ fn();
2068
+ }
2069
+ this._cleanupFns.clear();
1442
2070
  };
1443
- physicalLayer.on('close', handleClose);
1444
- this._removeAllListeners.push(() => physicalLayer.removeListener('close', handleClose));
1445
- }
1446
- getState(key) {
1447
- let state = this._states.get(key);
1448
- if (!state) {
1449
- state = { status: 'idle', frame: [] };
1450
- this._states.set(key, state);
1451
- }
1452
- return state;
1453
- }
1454
- flush() {
1455
- this._states.clear();
2071
+ connection.on('close', onClose);
2072
+ this._cleanupFns.add(() => connection.off('close', onClose));
1456
2073
  }
1457
- framing(_buffer, response, connection) {
1458
- if (_buffer.length < 6) {
1459
- this.emit('framing-error', new ModbusError(ModbusErrorCode.INSUFFICIENT_DATA, 'Insufficient data length'));
2074
+ framing(hexChars) {
2075
+ if (hexChars.length < 6) {
2076
+ this.emit('framing-error', new Error('Insufficient data length'));
1460
2077
  return;
1461
2078
  }
1462
- if (_buffer.length % 2 !== 0) {
1463
- this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data'));
2079
+ if (hexChars.length % 2 !== 0) {
2080
+ this.emit('framing-error', new Error('Invalid data'));
1464
2081
  return;
1465
2082
  }
1466
- const buffer = Buffer.allocUnsafe(_buffer.length / 2);
1467
- for (let i = 0; i < _buffer.length; i += 2) {
1468
- const hi = HEX_DECODE[_buffer[i]];
1469
- const lo = HEX_DECODE[_buffer[i + 1]];
2083
+ const decoded = Buffer.allocUnsafe(hexChars.length / 2);
2084
+ for (let i = 0; i < hexChars.length; i += 2) {
2085
+ const hi = HEX_DECODE[hexChars[i]];
2086
+ const lo = HEX_DECODE[hexChars[i + 1]];
2087
+ // Defensive: the FSM should already have filtered non-hex characters,
2088
+ // but guard here in case framing is ever called directly.
1470
2089
  if (hi === 0xff || lo === 0xff) {
1471
- this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_HEX, 'Invalid hex character'));
2090
+ this.emit('framing-error', new Error('Invalid hex character'));
1472
2091
  return;
1473
2092
  }
1474
- buffer[i / 2] = (hi << 4) | lo;
2093
+ decoded[i / 2] = (hi << 4) | lo;
1475
2094
  }
1476
2095
  const frame = {
1477
- unit: buffer[0],
1478
- fc: buffer[1],
1479
- data: buffer.subarray(2, buffer.length - 1),
1480
- buffer: _buffer,
2096
+ unit: decoded[0],
2097
+ fc: decoded[1],
2098
+ data: decoded.subarray(2, decoded.length - 1),
2099
+ buffer: hexChars,
1481
2100
  };
1482
- const lrcPassed = buffer[buffer.length - 1] === lrc(buffer.subarray(0, buffer.length - 1));
2101
+ const lrcPassed = decoded[decoded.length - 1] === lrc(decoded.subarray(0, decoded.length - 1));
1483
2102
  if (!lrcPassed) {
1484
- this.emit('framing-error', new ModbusError(ModbusErrorCode.LRC_MISMATCH, 'LRC check failed'));
2103
+ this.emit('framing-error', new Error('LRC check failed'));
1485
2104
  return;
1486
2105
  }
1487
- this.emit('framing', frame, response, connection);
2106
+ this.emit('framing', frame);
2107
+ }
2108
+ flush() {
2109
+ this._state = { status: 'idle', frame: [] };
1488
2110
  }
1489
2111
  encode(data) {
1490
- const buffer = Buffer.alloc(data.data.length + 3);
2112
+ const buffer = Buffer.allocUnsafe(data.data.length + 3);
1491
2113
  buffer.writeUInt8(data.unit, 0);
1492
2114
  buffer.writeUInt8(data.fc, 1);
1493
2115
  buffer.set(data.data, 2);
@@ -1503,24 +2125,14 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
1503
2125
  out[out.length - 1] = CHAR_CODE.LF;
1504
2126
  return out;
1505
2127
  }
1506
- destroy() {
1507
- if (this._destroyed) {
1508
- return;
1509
- }
1510
- this._destroyed = true;
1511
- this.removeAllListeners();
1512
- for (const removeListener of this._removeAllListeners) {
1513
- removeListener();
1514
- }
1515
- this._removeAllListeners.length = 0;
1516
- this._states.clear();
1517
- }
1518
2128
  }
1519
2129
 
1520
2130
  const MAX_TCP_FRAME = 260;
1521
- const EMPTY_BUFFER = Buffer.alloc(0);
1522
2131
  class TcpApplicationLayer extends AbstractApplicationLayer {
1523
- constructor(physicalLayer) {
2132
+ get connection() {
2133
+ return this._connection;
2134
+ }
2135
+ constructor(role, connection) {
1524
2136
  super();
1525
2137
  Object.defineProperty(this, "PROTOCOL", {
1526
2138
  enumerable: true,
@@ -1528,38 +2140,50 @@ class TcpApplicationLayer extends AbstractApplicationLayer {
1528
2140
  writable: true,
1529
2141
  value: 'TCP'
1530
2142
  });
1531
- Object.defineProperty(this, "_transactionId", {
2143
+ Object.defineProperty(this, "ROLE", {
1532
2144
  enumerable: true,
1533
2145
  configurable: true,
1534
2146
  writable: true,
1535
- value: 1
2147
+ value: void 0
1536
2148
  });
1537
- Object.defineProperty(this, "_buffers", {
2149
+ Object.defineProperty(this, "_connection", {
1538
2150
  enumerable: true,
1539
2151
  configurable: true,
1540
2152
  writable: true,
1541
- value: new Map()
2153
+ value: void 0
1542
2154
  });
1543
- Object.defineProperty(this, "_removeAllListeners", {
2155
+ Object.defineProperty(this, "_transactionId", {
1544
2156
  enumerable: true,
1545
2157
  configurable: true,
1546
2158
  writable: true,
1547
- value: []
2159
+ value: 1
1548
2160
  });
1549
- Object.defineProperty(this, "_destroyed", {
2161
+ Object.defineProperty(this, "_buffer", {
1550
2162
  enumerable: true,
1551
2163
  configurable: true,
1552
2164
  writable: true,
1553
- value: false
2165
+ value: Buffer.alloc(0)
1554
2166
  });
1555
- const handleData = (data, response, connection) => {
1556
- var _a;
1557
- let buffer = (_a = this._buffers.get(connection.id)) !== null && _a !== void 0 ? _a : EMPTY_BUFFER;
1558
- buffer = buffer.length === 0 ? data : Buffer.concat([buffer, data]);
2167
+ Object.defineProperty(this, "_cleanupFns", {
2168
+ enumerable: true,
2169
+ configurable: true,
2170
+ writable: true,
2171
+ value: new Set()
2172
+ });
2173
+ this.ROLE = role;
2174
+ this._connection = connection;
2175
+ const onData = (data) => {
2176
+ let buffer = this._buffer;
2177
+ if (buffer.length === 0) {
2178
+ buffer = data;
2179
+ }
2180
+ else if (data.length > 0) {
2181
+ buffer = Buffer.concat([buffer, data]);
2182
+ }
1559
2183
  while (buffer.length > 0) {
1560
2184
  const result = this.tryExtract(buffer);
1561
2185
  if (result.kind === 'frame') {
1562
- this.processFrame(result.frame, response, connection);
2186
+ this.processFrame(result.frame);
1563
2187
  buffer = result.rest;
1564
2188
  }
1565
2189
  else if (result.kind === 'insufficient') {
@@ -1567,48 +2191,53 @@ class TcpApplicationLayer extends AbstractApplicationLayer {
1567
2191
  }
1568
2192
  else {
1569
2193
  this.emit('framing-error', result.error);
1570
- buffer = EMPTY_BUFFER;
2194
+ buffer = Buffer.alloc(0);
1571
2195
  break;
1572
2196
  }
1573
2197
  }
1574
2198
  if (buffer.length === 0) {
1575
- this._buffers.delete(connection.id);
2199
+ this._buffer = Buffer.alloc(0);
2200
+ }
2201
+ else if (buffer === data) {
2202
+ // Copy into a right-sized buffer so we do not retain the potentially
2203
+ // large backing buffer of the original incoming data (Node.js pool).
2204
+ const copy = Buffer.allocUnsafe(buffer.length);
2205
+ buffer.copy(copy);
2206
+ this._buffer = copy;
1576
2207
  }
1577
2208
  else {
1578
- this._buffers.set(connection.id, buffer);
2209
+ this._buffer = buffer;
1579
2210
  }
1580
2211
  };
1581
- physicalLayer.on('data', handleData);
1582
- this._removeAllListeners.push(() => physicalLayer.removeListener('data', handleData));
1583
- const handleConnectionClose = (connection) => {
1584
- this._buffers.delete(connection.id);
1585
- };
1586
- physicalLayer.on('connection-close', handleConnectionClose);
1587
- this._removeAllListeners.push(() => physicalLayer.removeListener('connection-close', handleConnectionClose));
1588
- const handleClose = () => {
1589
- this._buffers.clear();
2212
+ connection.on('data', onData);
2213
+ this._cleanupFns.add(() => connection.off('data', onData));
2214
+ const onClose = () => {
2215
+ for (const fn of this._cleanupFns) {
2216
+ fn();
2217
+ }
2218
+ this._cleanupFns.clear();
1590
2219
  };
1591
- physicalLayer.on('close', handleClose);
1592
- this._removeAllListeners.push(() => physicalLayer.removeListener('close', handleClose));
2220
+ connection.on('close', onClose);
2221
+ this._cleanupFns.add(() => connection.off('close', onClose));
1593
2222
  }
1594
2223
  tryExtract(buffer) {
1595
2224
  if (buffer.length < 8) {
1596
2225
  return { kind: 'insufficient' };
1597
2226
  }
1598
2227
  if (buffer[2] !== 0 || buffer[3] !== 0) {
1599
- return { kind: 'error', error: new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data') };
2228
+ return { kind: 'error', error: new Error('Invalid data') };
1600
2229
  }
1601
2230
  const length = buffer.readUInt16BE(4);
1602
2231
  const total = 6 + length;
1603
2232
  if (total > MAX_TCP_FRAME || length < 2) {
1604
- return { kind: 'error', error: new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data') };
2233
+ return { kind: 'error', error: new Error('Invalid data') };
1605
2234
  }
1606
2235
  if (buffer.length < total) {
1607
2236
  return { kind: 'insufficient' };
1608
2237
  }
1609
2238
  return { kind: 'frame', frame: buffer.subarray(0, total), rest: buffer.subarray(total) };
1610
2239
  }
1611
- processFrame(buffer, response, connection) {
2240
+ processFrame(buffer) {
1612
2241
  const frame = {
1613
2242
  transaction: buffer.readUInt16BE(0),
1614
2243
  unit: buffer[6],
@@ -1616,12 +2245,15 @@ class TcpApplicationLayer extends AbstractApplicationLayer {
1616
2245
  data: buffer.subarray(8),
1617
2246
  buffer,
1618
2247
  };
1619
- this.emit('framing', frame, response, connection);
2248
+ this.emit('framing', frame);
2249
+ }
2250
+ flush() {
2251
+ this._buffer = Buffer.alloc(0);
1620
2252
  }
1621
2253
  encode(data) {
1622
2254
  var _a;
1623
2255
  const transaction = (_a = data.transaction) !== null && _a !== void 0 ? _a : this._transactionId;
1624
- const buffer = Buffer.alloc(data.data.length + 8);
2256
+ const buffer = Buffer.allocUnsafe(data.data.length + 8);
1625
2257
  buffer.writeUInt16BE(transaction, 0);
1626
2258
  buffer.writeUInt16BE(0, 2);
1627
2259
  buffer.writeUInt16BE(data.data.length + 2, 4);
@@ -1633,18 +2265,6 @@ class TcpApplicationLayer extends AbstractApplicationLayer {
1633
2265
  }
1634
2266
  return buffer;
1635
2267
  }
1636
- destroy() {
1637
- if (this._destroyed) {
1638
- return;
1639
- }
1640
- this._destroyed = true;
1641
- this.removeAllListeners();
1642
- for (const removeListener of this._removeAllListeners) {
1643
- removeListener();
1644
- }
1645
- this._removeAllListeners.length = 0;
1646
- this._buffers.clear();
1647
- }
1648
2268
  }
1649
2269
 
1650
2270
  const FIFO_KEY = 'fifo';
@@ -1681,8 +2301,10 @@ class MasterSession {
1681
2301
  return;
1682
2302
  }
1683
2303
  if (!this.runPreChecks(waiter, frame)) {
2304
+ this._waiters.delete(key);
1684
2305
  return;
1685
2306
  }
2307
+ this._waiters.delete(key);
1686
2308
  waiter.callback(null, frame);
1687
2309
  }
1688
2310
  handleError(error) {
@@ -1693,22 +2315,22 @@ class MasterSession {
1693
2315
  for (const check of waiter.preCheck) {
1694
2316
  const res = check(frame);
1695
2317
  if (typeof res === 'undefined') {
1696
- waiter.callback(new ModbusError(ModbusErrorCode.INSUFFICIENT_DATA, 'Insufficient data length'));
2318
+ waiter.callback(new Error('Insufficient data length'));
1697
2319
  return false;
1698
2320
  }
1699
2321
  if (typeof res === 'number') {
1700
2322
  if (frame.data.length < res) {
1701
- waiter.callback(new ModbusError(ModbusErrorCode.INSUFFICIENT_DATA, 'Insufficient data length'));
2323
+ waiter.callback(new Error('Insufficient data length'));
1702
2324
  return false;
1703
2325
  }
1704
2326
  if (frame.data.length !== res) {
1705
- waiter.callback(new ModbusError(ModbusErrorCode.INVALID_RESPONSE, 'Invalid response'));
2327
+ waiter.callback(new Error('Invalid response'));
1706
2328
  return false;
1707
2329
  }
1708
2330
  continue;
1709
2331
  }
1710
2332
  if (!res) {
1711
- waiter.callback(new ModbusError(ModbusErrorCode.INVALID_RESPONSE, 'Invalid response'));
2333
+ waiter.callback(new Error('Invalid response'));
1712
2334
  return false;
1713
2335
  }
1714
2336
  }
@@ -1749,26 +2371,26 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
1749
2371
  };
1750
2372
 
1751
2373
  class ModbusMaster extends EventEmitter {
1752
- get isOpen() {
1753
- return this.physicalLayer.isOpen;
2374
+ get state() {
2375
+ return this._physicalLayer.state;
1754
2376
  }
1755
- get destroyed() {
1756
- return this._cleanLevel === 'destroy' || this.physicalLayer.destroyed;
2377
+ get physicalLayer() {
2378
+ return this._physicalLayer;
1757
2379
  }
1758
- constructor(applicationLayer, physicalLayer, options = {}) {
2380
+ constructor(options) {
1759
2381
  var _a, _b;
1760
2382
  super();
1761
- Object.defineProperty(this, "applicationLayer", {
2383
+ Object.defineProperty(this, "timeout", {
1762
2384
  enumerable: true,
1763
2385
  configurable: true,
1764
2386
  writable: true,
1765
- value: applicationLayer
2387
+ value: void 0
1766
2388
  });
1767
- Object.defineProperty(this, "physicalLayer", {
2389
+ Object.defineProperty(this, "concurrent", {
1768
2390
  enumerable: true,
1769
2391
  configurable: true,
1770
2392
  writable: true,
1771
- value: physicalLayer
2393
+ value: void 0
1772
2394
  });
1773
2395
  Object.defineProperty(this, "_masterSession", {
1774
2396
  enumerable: true,
@@ -1776,47 +2398,59 @@ class ModbusMaster extends EventEmitter {
1776
2398
  writable: true,
1777
2399
  value: new MasterSession()
1778
2400
  });
1779
- Object.defineProperty(this, "_queue", {
2401
+ Object.defineProperty(this, "_physicalLayer", {
1780
2402
  enumerable: true,
1781
2403
  configurable: true,
1782
2404
  writable: true,
1783
- value: []
2405
+ value: void 0
1784
2406
  });
1785
- Object.defineProperty(this, "_draining", {
2407
+ Object.defineProperty(this, "_protocol", {
1786
2408
  enumerable: true,
1787
2409
  configurable: true,
1788
2410
  writable: true,
1789
- value: false
2411
+ value: void 0
1790
2412
  });
1791
- Object.defineProperty(this, "_nextTid", {
2413
+ Object.defineProperty(this, "_appLayer", {
1792
2414
  enumerable: true,
1793
2415
  configurable: true,
1794
2416
  writable: true,
1795
- value: 1
2417
+ value: void 0
2418
+ });
2419
+ Object.defineProperty(this, "_customFunctionCodes", {
2420
+ enumerable: true,
2421
+ configurable: true,
2422
+ writable: true,
2423
+ value: new Map()
2424
+ });
2425
+ Object.defineProperty(this, "_queue", {
2426
+ enumerable: true,
2427
+ configurable: true,
2428
+ writable: true,
2429
+ value: []
1796
2430
  });
1797
- Object.defineProperty(this, "_closed", {
2431
+ Object.defineProperty(this, "_draining", {
1798
2432
  enumerable: true,
1799
2433
  configurable: true,
1800
2434
  writable: true,
1801
2435
  value: false
1802
2436
  });
1803
- Object.defineProperty(this, "_cleanLevel", {
2437
+ Object.defineProperty(this, "_nextTid", {
1804
2438
  enumerable: true,
1805
2439
  configurable: true,
1806
2440
  writable: true,
1807
- value: 'none'
2441
+ value: 1
1808
2442
  });
1809
- Object.defineProperty(this, "timeout", {
2443
+ Object.defineProperty(this, "_cleanupFns", {
1810
2444
  enumerable: true,
1811
2445
  configurable: true,
1812
2446
  writable: true,
1813
- value: void 0
2447
+ value: new Set()
1814
2448
  });
1815
- Object.defineProperty(this, "concurrent", {
2449
+ Object.defineProperty(this, "_closePromise", {
1816
2450
  enumerable: true,
1817
2451
  configurable: true,
1818
2452
  writable: true,
1819
- value: void 0
2453
+ value: null
1820
2454
  });
1821
2455
  Object.defineProperty(this, "writeFC1", {
1822
2456
  enumerable: true,
@@ -1892,10 +2526,60 @@ class ModbusMaster extends EventEmitter {
1892
2526
  });
1893
2527
  this.timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : 1000;
1894
2528
  this.concurrent = (_b = options.concurrent) !== null && _b !== void 0 ? _b : false;
1895
- if (this.concurrent && this.applicationLayer.PROTOCOL !== 'TCP') {
1896
- throw new ModbusError(ModbusErrorCode.CONCURRENT_NOT_TCP, 'concurrent mode requires a Modbus TCP application layer');
2529
+ this._physicalLayer = createPhysicalLayer(options.physical);
2530
+ this._protocol = options.protocol;
2531
+ if (this.concurrent && options.protocol.type !== 'TCP') {
2532
+ throw new Error('concurrent mode requires a Modbus TCP application layer');
1897
2533
  }
1898
- this.applicationLayer.role = 'MASTER';
2534
+ const onOpen = () => {
2535
+ this.emit('open');
2536
+ };
2537
+ this._physicalLayer.on('open', onOpen);
2538
+ this._cleanupFns.add(() => this._physicalLayer.off('open', onOpen));
2539
+ const onConnect = (connection) => {
2540
+ const appLayer = this._createAppLayer(connection);
2541
+ this._appLayer = appLayer;
2542
+ for (const cfc of this._customFunctionCodes.values()) {
2543
+ appLayer.addCustomFunctionCode(cfc);
2544
+ }
2545
+ const cleanupFraming = () => appLayer.off('framing', onFraming);
2546
+ const onFraming = (frame) => {
2547
+ this._masterSession.handleFrame(frame);
2548
+ };
2549
+ appLayer.on('framing', onFraming);
2550
+ this._cleanupFns.add(cleanupFraming);
2551
+ const cleanupFramingError = () => appLayer.off('framing-error', onFramingError);
2552
+ const onFramingError = (error) => {
2553
+ this._masterSession.handleError(error);
2554
+ };
2555
+ appLayer.on('framing-error', onFramingError);
2556
+ this._cleanupFns.add(cleanupFramingError);
2557
+ const cleanupClose = () => connection.off('close', onClose);
2558
+ const onClose = () => {
2559
+ cleanupFraming();
2560
+ cleanupFramingError();
2561
+ cleanupClose();
2562
+ this._cleanupFns.delete(cleanupFraming);
2563
+ this._cleanupFns.delete(cleanupFramingError);
2564
+ this._cleanupFns.delete(cleanupClose);
2565
+ };
2566
+ connection.on('close', onClose);
2567
+ this._cleanupFns.add(cleanupClose);
2568
+ this.emit('connect', connection);
2569
+ };
2570
+ this._physicalLayer.on('connect', onConnect);
2571
+ this._cleanupFns.add(() => this._physicalLayer.off('connect', onConnect));
2572
+ const onClose = () => {
2573
+ this.emit('close');
2574
+ this.close();
2575
+ };
2576
+ this._physicalLayer.on('close', onClose);
2577
+ this._cleanupFns.add(() => this._physicalLayer.off('close', onClose));
2578
+ const onError = (err) => {
2579
+ this.emit('error', err);
2580
+ };
2581
+ this._physicalLayer.on('error', onError);
2582
+ this._cleanupFns.add(() => this._physicalLayer.off('error', onError));
1899
2583
  this.writeFC1 = this.readCoils;
1900
2584
  this.writeFC2 = this.readDiscreteInputs;
1901
2585
  this.writeFC3 = this.readHoldingRegisters;
@@ -1908,20 +2592,27 @@ class ModbusMaster extends EventEmitter {
1908
2592
  this.handleFC22 = this.maskWriteRegister;
1909
2593
  this.handleFC23 = this.readAndWriteMultipleRegisters;
1910
2594
  this.handleFC43_14 = this.readDeviceIdentification;
1911
- applicationLayer.on('framing', (frame) => {
1912
- this._masterSession.handleFrame(frame);
1913
- });
1914
- applicationLayer.on('framing-error', (error) => {
1915
- this._masterSession.handleError(error);
1916
- });
1917
- physicalLayer.on('error', (error) => {
1918
- this.emit('error', error);
1919
- });
1920
- physicalLayer.on('close', () => {
1921
- this.emit('close');
1922
- });
2595
+ }
2596
+ _createAppLayer(connection) {
2597
+ var _a;
2598
+ if (this._protocol.type === 'RTU') {
2599
+ const baudRate = this._physicalLayer.is('SERIAL') ? this._physicalLayer.baudRate : undefined;
2600
+ const { intervalBetweenFrames, interCharTimeout } = resolveRtuTiming(this._protocol.opts, baudRate);
2601
+ return new RtuApplicationLayer('MASTER', connection, {
2602
+ intervalBetweenFrames,
2603
+ interCharTimeout,
2604
+ poolSize: (_a = this._protocol.opts) === null || _a === void 0 ? void 0 : _a.poolSize,
2605
+ });
2606
+ }
2607
+ if (this._protocol.type === 'TCP') {
2608
+ return new TcpApplicationLayer('MASTER', connection);
2609
+ }
2610
+ return new AsciiApplicationLayer('MASTER', connection, this._protocol.opts);
1923
2611
  }
1924
2612
  send(adu, preCheck, timeout, broadcast) {
2613
+ if (this._physicalLayer.state !== PhysicalState.OPEN) {
2614
+ return Promise.reject(new Error('Master is not open'));
2615
+ }
1925
2616
  const task = () => this._exchange(adu, preCheck, timeout, broadcast);
1926
2617
  if (this.concurrent) {
1927
2618
  return task();
@@ -1941,14 +2632,20 @@ class ModbusMaster extends EventEmitter {
1941
2632
  }
1942
2633
  _drain() {
1943
2634
  return __awaiter(this, void 0, void 0, function* () {
1944
- if (this._draining) {
2635
+ if (this._draining || this._physicalLayer.state !== PhysicalState.OPEN) {
1945
2636
  return;
1946
2637
  }
1947
2638
  this._draining = true;
1948
2639
  try {
1949
2640
  while (this._queue.length > 0) {
2641
+ if (this._physicalLayer.state !== PhysicalState.OPEN) {
2642
+ return;
2643
+ }
1950
2644
  const item = this._queue.shift();
1951
- yield item.run();
2645
+ try {
2646
+ yield item.run();
2647
+ }
2648
+ catch (_a) { }
1952
2649
  }
1953
2650
  }
1954
2651
  finally {
@@ -1958,18 +2655,26 @@ class ModbusMaster extends EventEmitter {
1958
2655
  }
1959
2656
  _exchange(adu, preCheck, timeout, broadcast) {
1960
2657
  return new Promise((resolve, reject) => {
1961
- if (this._closed) {
1962
- reject(new ModbusError(ModbusErrorCode.MASTER_CLOSED, 'Master closed'));
2658
+ if (this._physicalLayer.state !== PhysicalState.OPEN) {
2659
+ reject(new Error('Master is not open'));
2660
+ return;
2661
+ }
2662
+ const appLayer = this._appLayer;
2663
+ if (!appLayer) {
2664
+ reject(new Error('Connection not established'));
1963
2665
  return;
1964
2666
  }
1965
- const usesTid = this.applicationLayer.PROTOCOL === 'TCP' && !broadcast;
2667
+ const connection = appLayer.connection;
2668
+ const usesTid = appLayer.PROTOCOL === 'TCP' && !broadcast;
1966
2669
  let tid;
1967
2670
  if (usesTid) {
1968
- tid = this._nextTid;
1969
- this._nextTid = (this._nextTid + 1) % 65536 || 1;
2671
+ do {
2672
+ tid = this._nextTid;
2673
+ this._nextTid = (this._nextTid + 1) % 65536 || 1;
2674
+ } while (this._masterSession.has(tid));
1970
2675
  }
1971
2676
  const key = tid !== null && tid !== void 0 ? tid : FIFO_KEY;
1972
- const payload = this.applicationLayer.encode(Object.assign(Object.assign({}, adu), { transaction: tid }));
2677
+ const payload = appLayer.encode(Object.assign(Object.assign({}, adu), { transaction: tid }));
1973
2678
  let settled = false;
1974
2679
  const settle = (error, frame) => {
1975
2680
  if (settled) {
@@ -1985,18 +2690,22 @@ class ModbusMaster extends EventEmitter {
1985
2690
  resolve(frame);
1986
2691
  }
1987
2692
  };
1988
- const timer = setTimeout(() => settle(new ModbusError(ModbusErrorCode.TIMEOUT, 'Timeout')), timeout);
2693
+ const timer = setTimeout(() => settle(new Error('Timeout')), timeout);
1989
2694
  // FIFO mode: clear stale buffer state before this request.
1990
2695
  // Concurrent mode: must not flush — other in-flight requests share the buffer.
1991
2696
  if (!this.concurrent) {
1992
- this.applicationLayer.flush();
2697
+ appLayer.flush();
1993
2698
  }
1994
- this.physicalLayer
2699
+ connection
1995
2700
  .write(payload)
1996
2701
  .then(() => {
1997
2702
  if (settled) {
1998
2703
  return;
1999
2704
  }
2705
+ if (this._physicalLayer.state !== PhysicalState.OPEN) {
2706
+ settle(new Error('Master is not open'));
2707
+ return;
2708
+ }
2000
2709
  if (broadcast) {
2001
2710
  settle(null);
2002
2711
  return;
@@ -2009,7 +2718,7 @@ class ModbusMaster extends EventEmitter {
2009
2718
  }
2010
2719
  writeFC1Or2(unit, fc, address, length, timeout) {
2011
2720
  const byteCount = Math.ceil(length / 8);
2012
- const bufferTx = Buffer.alloc(4);
2721
+ const bufferTx = Buffer.allocUnsafe(4);
2013
2722
  bufferTx.writeUInt16BE(address, 0);
2014
2723
  bufferTx.writeUInt16BE(length, 2);
2015
2724
  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) => {
@@ -2026,7 +2735,7 @@ class ModbusMaster extends EventEmitter {
2026
2735
  }
2027
2736
  writeFC3Or4(unit, fc, address, length, timeout) {
2028
2737
  const byteCount = length * 2;
2029
- const bufferTx = Buffer.alloc(4);
2738
+ const bufferTx = Buffer.allocUnsafe(4);
2030
2739
  bufferTx.writeUInt16BE(address, 0);
2031
2740
  bufferTx.writeUInt16BE(length, 2);
2032
2741
  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) => {
@@ -2044,7 +2753,7 @@ class ModbusMaster extends EventEmitter {
2044
2753
  }
2045
2754
  writeSingleCoil(unit, address, value, timeout = this.timeout) {
2046
2755
  const fc = FunctionCode.WRITE_SINGLE_COIL;
2047
- const bufferTx = Buffer.alloc(4);
2756
+ const bufferTx = Buffer.allocUnsafe(4);
2048
2757
  bufferTx.writeUInt16BE(address, 0);
2049
2758
  bufferTx.writeUInt16BE(value ? COIL_ON : COIL_OFF, 2);
2050
2759
  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) => {
@@ -2055,7 +2764,7 @@ class ModbusMaster extends EventEmitter {
2055
2764
  }
2056
2765
  writeSingleRegister(unit, address, value, timeout = this.timeout) {
2057
2766
  const fc = FunctionCode.WRITE_SINGLE_REGISTER;
2058
- const bufferTx = Buffer.alloc(4);
2767
+ const bufferTx = Buffer.allocUnsafe(4);
2059
2768
  bufferTx.writeUInt16BE(address, 0);
2060
2769
  bufferTx.writeUInt16BE(value, 2);
2061
2770
  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) => {
@@ -2085,7 +2794,7 @@ class ModbusMaster extends EventEmitter {
2085
2794
  writeMultipleRegisters(unit, address, value, timeout = this.timeout) {
2086
2795
  const fc = FunctionCode.WRITE_MULTIPLE_REGISTERS;
2087
2796
  const byteCount = value.length * 2;
2088
- const bufferTx = Buffer.alloc(5 + byteCount);
2797
+ const bufferTx = Buffer.allocUnsafe(5 + byteCount);
2089
2798
  bufferTx.writeUInt16BE(address, 0);
2090
2799
  bufferTx.writeUInt16BE(value.length, 2);
2091
2800
  bufferTx.writeUInt8(byteCount, 4);
@@ -2111,7 +2820,7 @@ class ModbusMaster extends EventEmitter {
2111
2820
  if (frame) {
2112
2821
  const runStatusIndex = 1 + serverIdLength;
2113
2822
  return Object.assign(Object.assign({}, frame), { data: {
2114
- serverId: serverIdLength === 1 ? frame.data[1] : Array.from(frame.data.subarray(1, runStatusIndex)),
2823
+ serverId: Array.from(frame.data.subarray(1, runStatusIndex)),
2115
2824
  runIndicatorStatus: frame.data[runStatusIndex] === 0xff,
2116
2825
  additionalData: Array.from(frame.data.subarray(runStatusIndex + 1)),
2117
2826
  } });
@@ -2120,7 +2829,7 @@ class ModbusMaster extends EventEmitter {
2120
2829
  }
2121
2830
  maskWriteRegister(unit, address, andMask, orMask, timeout = this.timeout) {
2122
2831
  const fc = FunctionCode.MASK_WRITE_REGISTER;
2123
- const bufferTx = Buffer.alloc(6);
2832
+ const bufferTx = Buffer.allocUnsafe(6);
2124
2833
  bufferTx.writeUInt16BE(address, 0);
2125
2834
  bufferTx.writeUInt16BE(andMask, 2);
2126
2835
  bufferTx.writeUInt16BE(orMask, 4);
@@ -2134,7 +2843,7 @@ class ModbusMaster extends EventEmitter {
2134
2843
  const fc = FunctionCode.READ_WRITE_MULTIPLE_REGISTERS;
2135
2844
  const byteCount = write.value.length * 2;
2136
2845
  const readByteCount = read.length * 2;
2137
- const bufferTx = Buffer.alloc(9 + byteCount);
2846
+ const bufferTx = Buffer.allocUnsafe(9 + byteCount);
2138
2847
  bufferTx.writeUInt16BE(read.address, 0);
2139
2848
  bufferTx.writeUInt16BE(read.length, 2);
2140
2849
  bufferTx.writeUInt16BE(write.address, 4);
@@ -2206,10 +2915,14 @@ class ModbusMaster extends EventEmitter {
2206
2915
  });
2207
2916
  }
2208
2917
  addCustomFunctionCode(cfc) {
2209
- this.applicationLayer.addCustomFunctionCode(cfc);
2918
+ var _a;
2919
+ this._customFunctionCodes.set(cfc.fc, cfc);
2920
+ (_a = this._appLayer) === null || _a === void 0 ? void 0 : _a.addCustomFunctionCode(cfc);
2210
2921
  }
2211
2922
  removeCustomFunctionCode(fc) {
2212
- this.applicationLayer.removeCustomFunctionCode(fc);
2923
+ var _a;
2924
+ this._customFunctionCodes.delete(fc);
2925
+ (_a = this._appLayer) === null || _a === void 0 ? void 0 : _a.removeCustomFunctionCode(fc);
2213
2926
  }
2214
2927
  sendCustomFC(unit, fc, data, timeout = this.timeout) {
2215
2928
  const payload = Buffer.isBuffer(data) ? data : Buffer.from(data);
@@ -2219,85 +2932,107 @@ class ModbusMaster extends EventEmitter {
2219
2932
  }
2220
2933
  });
2221
2934
  }
2222
- _clean(level) {
2223
- if (this._cleanLevel === 'destroy') {
2224
- return;
2225
- }
2226
- if (this._cleanLevel === 'close' && level === 'close') {
2227
- return;
2228
- }
2229
- const errorCode = level === 'destroy' ? ModbusErrorCode.MASTER_DESTROYED : ModbusErrorCode.MASTER_CLOSED;
2230
- const message = level === 'destroy' ? 'Master destroyed' : 'Master closed';
2231
- this._closed = true;
2935
+ _clean(message) {
2232
2936
  const queued = this._queue.splice(0);
2233
2937
  for (const item of queued) {
2234
- item.cancel(new ModbusError(errorCode, message));
2235
- }
2236
- this._masterSession.stopAll(new ModbusError(errorCode, message));
2237
- this._cleanLevel = level;
2238
- }
2938
+ item.cancel(new Error(message));
2939
+ }
2940
+ this._masterSession.stopAll(new Error(message));
2941
+ }
2942
+ /**
2943
+ * Open the underlying physical layer and begin accepting connections.
2944
+ *
2945
+ * A `ModbusMaster` instance can only be opened once. Once {@link close}
2946
+ * is called — explicitly or because the physical layer disconnected —
2947
+ * the instance is permanently closed and cannot be reopened.
2948
+ * Create a new `ModbusMaster` if a new connection is required.
2949
+ */
2239
2950
  open(...args) {
2240
- if (this._cleanLevel === 'destroy') {
2241
- return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Master is destroyed'));
2951
+ if (this._closePromise) {
2952
+ return Promise.reject(new Error('The port has been permanently closed and CANNOT be reopened'));
2242
2953
  }
2243
- this._cleanLevel = 'none';
2244
- this._closed = false;
2245
- this._nextTid = 1;
2246
- return this.physicalLayer.open(...args);
2954
+ return this._physicalLayer.open(...args);
2247
2955
  }
2248
- close() {
2249
- if (this._cleanLevel === 'destroy') {
2250
- return Promise.resolve();
2251
- }
2252
- this._clean('close');
2253
- return this.physicalLayer.close();
2956
+ _close() {
2957
+ return __awaiter(this, void 0, void 0, function* () {
2958
+ for (const fn of this._cleanupFns) {
2959
+ fn();
2960
+ }
2961
+ this._cleanupFns.clear();
2962
+ this._clean('Master is closing');
2963
+ let err = null;
2964
+ try {
2965
+ yield this._physicalLayer.close();
2966
+ }
2967
+ catch (error) {
2968
+ err = error;
2969
+ }
2970
+ this.removeAllListeners();
2971
+ if (err) {
2972
+ return Promise.reject(err);
2973
+ }
2974
+ });
2254
2975
  }
2255
- destroy() {
2256
- if (this._cleanLevel === 'destroy') {
2257
- return Promise.resolve();
2258
- }
2259
- this._clean('destroy');
2260
- this.removeAllListeners();
2261
- this.applicationLayer.destroy();
2262
- return this.physicalLayer.destroy();
2976
+ /**
2977
+ * Permanently close the master and release all resources.
2978
+ *
2979
+ * After calling this method the instance is considered dead:
2980
+ * - No further requests can be sent.
2981
+ * - The instance cannot be reopened via {@link open}.
2982
+ * - All event listeners registered on this master are removed.
2983
+ */
2984
+ close() {
2985
+ return __awaiter(this, void 0, void 0, function* () {
2986
+ if (this._closePromise) {
2987
+ return this._closePromise;
2988
+ }
2989
+ this._closePromise = this._close();
2990
+ return this._closePromise;
2991
+ });
2263
2992
  }
2264
2993
  }
2265
2994
 
2995
+ function prependByte(byte, buffer) {
2996
+ const result = Buffer.allocUnsafe(buffer.length + 1);
2997
+ result[0] = byte;
2998
+ buffer.copy(result, 1);
2999
+ return result;
3000
+ }
2266
3001
  class ModbusSlave extends EventEmitter {
2267
- get isOpen() {
2268
- return this.physicalLayer.isOpen;
3002
+ get state() {
3003
+ return this._physicalLayer.state;
2269
3004
  }
2270
- get destroyed() {
2271
- return this._cleanLevel === 'destroy' || this.physicalLayer.destroyed;
3005
+ get physicalLayer() {
3006
+ return this._physicalLayer;
2272
3007
  }
2273
- constructor(applicationLayer, physicalLayer, options = {}) {
3008
+ constructor(options) {
2274
3009
  var _a;
2275
3010
  super();
2276
- Object.defineProperty(this, "applicationLayer", {
3011
+ Object.defineProperty(this, "models", {
2277
3012
  enumerable: true,
2278
3013
  configurable: true,
2279
3014
  writable: true,
2280
- value: applicationLayer
3015
+ value: new Map()
2281
3016
  });
2282
- Object.defineProperty(this, "physicalLayer", {
3017
+ Object.defineProperty(this, "concurrent", {
2283
3018
  enumerable: true,
2284
3019
  configurable: true,
2285
3020
  writable: true,
2286
- value: physicalLayer
3021
+ value: void 0
2287
3022
  });
2288
- Object.defineProperty(this, "models", {
3023
+ Object.defineProperty(this, "_physicalLayer", {
2289
3024
  enumerable: true,
2290
3025
  configurable: true,
2291
3026
  writable: true,
2292
- value: new Map()
3027
+ value: void 0
2293
3028
  });
2294
- Object.defineProperty(this, "concurrent", {
3029
+ Object.defineProperty(this, "_protocol", {
2295
3030
  enumerable: true,
2296
3031
  configurable: true,
2297
3032
  writable: true,
2298
3033
  value: void 0
2299
3034
  });
2300
- Object.defineProperty(this, "_queues", {
3035
+ Object.defineProperty(this, "_appLayers", {
2301
3036
  enumerable: true,
2302
3037
  configurable: true,
2303
3038
  writable: true,
@@ -2315,72 +3050,112 @@ class ModbusSlave extends EventEmitter {
2315
3050
  writable: true,
2316
3051
  value: new Map()
2317
3052
  });
2318
- Object.defineProperty(this, "_cleanLevel", {
3053
+ Object.defineProperty(this, "_cleanupFns", {
2319
3054
  enumerable: true,
2320
3055
  configurable: true,
2321
3056
  writable: true,
2322
- value: 'none'
2323
- });
2324
- this.concurrent = (_a = options.concurrent) !== null && _a !== void 0 ? _a : false;
2325
- if (this.concurrent && this.applicationLayer.PROTOCOL !== 'TCP') {
2326
- throw new ModbusError(ModbusErrorCode.CONCURRENT_NOT_TCP, 'concurrent mode requires a Modbus TCP application layer');
2327
- }
2328
- this.applicationLayer.role = 'SLAVE';
2329
- applicationLayer.on('framing', (frame, response, connection) => {
2330
- if (!(frame.unit === 0x00 || this.models.has(frame.unit))) {
2331
- return;
2332
- }
2333
- if (this.concurrent) {
2334
- this._processFrame(frame, response).catch((error) => this.emit('error', error));
2335
- return;
2336
- }
2337
- let q = this._queues.get(connection.id);
2338
- if (!q) {
2339
- q = { items: [], processing: false };
2340
- this._queues.set(connection.id, q);
2341
- }
2342
- q.items.push({ frame, response });
2343
- this._drain(connection.id, q);
2344
- });
2345
- physicalLayer.on('error', (error) => {
2346
- this.emit('error', error);
3057
+ value: new Set()
2347
3058
  });
2348
- physicalLayer.on('connection-close', (connection) => {
2349
- const q = this._queues.get(connection.id);
2350
- if (!q) {
2351
- return;
2352
- }
2353
- q.items.length = 0;
2354
- if (!q.processing) {
2355
- this._queues.delete(connection.id);
2356
- }
3059
+ Object.defineProperty(this, "_closePromise", {
3060
+ enumerable: true,
3061
+ configurable: true,
3062
+ writable: true,
3063
+ value: null
2357
3064
  });
2358
- physicalLayer.on('close', () => {
2359
- for (const q of this._queues.values()) {
2360
- q.items.length = 0;
2361
- }
2362
- this._queues.clear();
3065
+ this.concurrent = (_a = options.concurrent) !== null && _a !== void 0 ? _a : false;
3066
+ this._physicalLayer = createPhysicalLayer(options.physical);
3067
+ this._protocol = options.protocol;
3068
+ if (this.concurrent && options.protocol.type !== 'TCP') {
3069
+ throw new Error('concurrent mode requires a Modbus TCP application layer');
3070
+ }
3071
+ const onOpen = () => this.emit('open');
3072
+ this._physicalLayer.on('open', onOpen);
3073
+ this._cleanupFns.add(() => this._physicalLayer.off('open', onOpen));
3074
+ const onConnect = (connection) => {
3075
+ const appLayer = this._createAppLayer(connection);
3076
+ const appLayerData = { queues: { frames: [], processing: false } };
3077
+ this._appLayers.set(appLayer, appLayerData);
3078
+ for (const cfc of this._customFunctionCodes.values()) {
3079
+ appLayer.addCustomFunctionCode(cfc);
3080
+ }
3081
+ const cleanupFraming = () => appLayer.off('framing', onFraming);
3082
+ const onFraming = (frame) => {
3083
+ if (this._physicalLayer.state !== PhysicalState.OPEN) {
3084
+ return;
3085
+ }
3086
+ if (!(frame.unit === 0x00 || this.models.has(frame.unit))) {
3087
+ return;
3088
+ }
3089
+ if (this.concurrent) {
3090
+ this._processFrame(appLayer, frame);
3091
+ return;
3092
+ }
3093
+ appLayerData.queues.frames.push(frame);
3094
+ this._drain(appLayer, appLayerData.queues);
3095
+ };
3096
+ appLayer.on('framing', onFraming);
3097
+ this._cleanupFns.add(cleanupFraming);
3098
+ const cleanupClose = () => connection.off('close', onClose);
3099
+ const onClose = () => {
3100
+ cleanupFraming();
3101
+ cleanupClose();
3102
+ this._cleanupFns.delete(cleanupFraming);
3103
+ this._cleanupFns.delete(cleanupClose);
3104
+ this._appLayers.delete(appLayer);
3105
+ };
3106
+ connection.on('close', onClose);
3107
+ this._cleanupFns.add(cleanupClose);
3108
+ this.emit('connect', connection);
3109
+ };
3110
+ this._physicalLayer.on('connect', onConnect);
3111
+ this._cleanupFns.add(() => this._physicalLayer.off('connect', onConnect));
3112
+ const onClose = () => {
2363
3113
  this.emit('close');
2364
- });
3114
+ this.close();
3115
+ };
3116
+ this._physicalLayer.on('close', onClose);
3117
+ this._cleanupFns.add(() => this._physicalLayer.off('close', onClose));
3118
+ const onError = (err) => {
3119
+ this.emit('error', err);
3120
+ };
3121
+ this._physicalLayer.on('error', onError);
3122
+ this._cleanupFns.add(() => this._physicalLayer.off('error', onError));
3123
+ }
3124
+ _createAppLayer(connection) {
3125
+ var _a;
3126
+ if (this._protocol.type === 'RTU') {
3127
+ const baudRate = this._physicalLayer.is('SERIAL') ? this._physicalLayer.baudRate : undefined;
3128
+ const { intervalBetweenFrames, interCharTimeout } = resolveRtuTiming(this._protocol.opts, baudRate);
3129
+ return new RtuApplicationLayer('SLAVE', connection, {
3130
+ intervalBetweenFrames,
3131
+ interCharTimeout,
3132
+ poolSize: (_a = this._protocol.opts) === null || _a === void 0 ? void 0 : _a.poolSize,
3133
+ });
3134
+ }
3135
+ if (this._protocol.type === 'TCP') {
3136
+ return new TcpApplicationLayer('SLAVE', connection);
3137
+ }
3138
+ return new AsciiApplicationLayer('SLAVE', connection, this._protocol.opts);
2365
3139
  }
2366
- handleFC1(model, frame, response) {
3140
+ handleFC1(appLayer, model, frame, response) {
2367
3141
  return __awaiter(this, void 0, void 0, function* () {
2368
3142
  var _a;
2369
3143
  if (frame.data.length !== 4) {
3144
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2370
3145
  return;
2371
3146
  }
2372
3147
  if (!model.readCoils) {
2373
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3148
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2374
3149
  return;
2375
3150
  }
2376
3151
  const address = (frame.data[0] << 8) | frame.data[1];
2377
3152
  const length = (frame.data[2] << 8) | frame.data[3];
2378
3153
  if (length < LIMITS.READ_COILS_MIN || length > LIMITS.READ_COILS_MAX) {
2379
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
3154
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2380
3155
  return;
2381
3156
  }
2382
3157
  if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
2383
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
3158
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2384
3159
  return;
2385
3160
  }
2386
3161
  try {
@@ -2391,31 +3166,32 @@ class ModbusSlave extends EventEmitter {
2391
3166
  bufferTx[Math.floor(index / 8)] |= 1 << index % 8;
2392
3167
  }
2393
3168
  });
2394
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
3169
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
2395
3170
  }
2396
3171
  catch (error) {
2397
- yield this.responseError(frame, response, error);
3172
+ yield this.responseError(appLayer, frame, response, error);
2398
3173
  }
2399
3174
  });
2400
3175
  }
2401
- handleFC2(model, frame, response) {
3176
+ handleFC2(appLayer, model, frame, response) {
2402
3177
  return __awaiter(this, void 0, void 0, function* () {
2403
3178
  var _a;
2404
3179
  if (frame.data.length !== 4) {
3180
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2405
3181
  return;
2406
3182
  }
2407
3183
  if (!model.readDiscreteInputs) {
2408
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3184
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2409
3185
  return;
2410
3186
  }
2411
3187
  const address = (frame.data[0] << 8) | frame.data[1];
2412
3188
  const length = (frame.data[2] << 8) | frame.data[3];
2413
3189
  if (length < LIMITS.READ_COILS_MIN || length > LIMITS.READ_COILS_MAX) {
2414
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
3190
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2415
3191
  return;
2416
3192
  }
2417
3193
  if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).discreteInputs)) {
2418
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
3194
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2419
3195
  return;
2420
3196
  }
2421
3197
  try {
@@ -2426,152 +3202,157 @@ class ModbusSlave extends EventEmitter {
2426
3202
  bufferTx[Math.floor(index / 8)] |= 1 << index % 8;
2427
3203
  }
2428
3204
  });
2429
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
3205
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
2430
3206
  }
2431
3207
  catch (error) {
2432
- yield this.responseError(frame, response, error);
3208
+ yield this.responseError(appLayer, frame, response, error);
2433
3209
  }
2434
3210
  });
2435
3211
  }
2436
- handleFC3(model, frame, response) {
3212
+ handleFC3(appLayer, model, frame, response) {
2437
3213
  return __awaiter(this, void 0, void 0, function* () {
2438
3214
  var _a;
2439
3215
  if (frame.data.length !== 4) {
3216
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2440
3217
  return;
2441
3218
  }
2442
3219
  if (!model.readHoldingRegisters) {
2443
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3220
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2444
3221
  return;
2445
3222
  }
2446
3223
  const address = (frame.data[0] << 8) | frame.data[1];
2447
3224
  const length = (frame.data[2] << 8) | frame.data[3];
2448
3225
  if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.READ_REGISTERS_MAX) {
2449
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
3226
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2450
3227
  return;
2451
3228
  }
2452
3229
  if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
2453
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
3230
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2454
3231
  return;
2455
3232
  }
2456
3233
  try {
2457
3234
  const registers = yield model.readHoldingRegisters(address, length);
2458
- const bufferTx = Buffer.alloc(length * 2);
3235
+ const bufferTx = Buffer.allocUnsafe(length * 2);
2459
3236
  registers.forEach((register, index) => {
2460
3237
  bufferTx.writeUInt16BE(register, index * 2);
2461
3238
  });
2462
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
3239
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
2463
3240
  }
2464
3241
  catch (error) {
2465
- yield this.responseError(frame, response, error);
3242
+ yield this.responseError(appLayer, frame, response, error);
2466
3243
  }
2467
3244
  });
2468
3245
  }
2469
- handleFC4(model, frame, response) {
3246
+ handleFC4(appLayer, model, frame, response) {
2470
3247
  return __awaiter(this, void 0, void 0, function* () {
2471
3248
  var _a;
2472
3249
  if (frame.data.length !== 4) {
3250
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2473
3251
  return;
2474
3252
  }
2475
3253
  if (!model.readInputRegisters) {
2476
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3254
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2477
3255
  return;
2478
3256
  }
2479
3257
  const address = (frame.data[0] << 8) | frame.data[1];
2480
3258
  const length = (frame.data[2] << 8) | frame.data[3];
2481
3259
  if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.READ_REGISTERS_MAX) {
2482
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
3260
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2483
3261
  return;
2484
3262
  }
2485
3263
  if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).inputRegisters)) {
2486
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
3264
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2487
3265
  return;
2488
3266
  }
2489
3267
  try {
2490
3268
  const registers = yield model.readInputRegisters(address, length);
2491
- const bufferTx = Buffer.alloc(length * 2);
3269
+ const bufferTx = Buffer.allocUnsafe(length * 2);
2492
3270
  registers.forEach((register, index) => {
2493
3271
  bufferTx.writeUInt16BE(register, index * 2);
2494
3272
  });
2495
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
3273
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
2496
3274
  }
2497
3275
  catch (error) {
2498
- yield this.responseError(frame, response, error);
3276
+ yield this.responseError(appLayer, frame, response, error);
2499
3277
  }
2500
3278
  });
2501
3279
  }
2502
- handleFC5(model, frame, response) {
3280
+ handleFC5(appLayer, model, frame, response) {
2503
3281
  return __awaiter(this, void 0, void 0, function* () {
2504
3282
  var _a;
2505
3283
  if (frame.data.length !== 4) {
3284
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2506
3285
  return;
2507
3286
  }
2508
3287
  if (!model.writeSingleCoil) {
2509
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3288
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2510
3289
  return;
2511
3290
  }
2512
3291
  const address = (frame.data[0] << 8) | frame.data[1];
2513
3292
  const value = (frame.data[2] << 8) | frame.data[3];
2514
3293
  if (value !== COIL_OFF && value !== COIL_ON) {
2515
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
3294
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2516
3295
  return;
2517
3296
  }
2518
3297
  if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
2519
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
3298
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2520
3299
  return;
2521
3300
  }
2522
3301
  try {
2523
3302
  yield model.writeSingleCoil(address, value === COIL_ON);
2524
- yield response(this.applicationLayer.encode(frame));
3303
+ yield response(appLayer.encode(frame));
2525
3304
  }
2526
3305
  catch (error) {
2527
- yield this.responseError(frame, response, error);
3306
+ yield this.responseError(appLayer, frame, response, error);
2528
3307
  }
2529
3308
  });
2530
3309
  }
2531
- handleFC6(model, frame, response) {
3310
+ handleFC6(appLayer, model, frame, response) {
2532
3311
  return __awaiter(this, void 0, void 0, function* () {
2533
3312
  var _a;
2534
3313
  if (frame.data.length !== 4) {
3314
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2535
3315
  return;
2536
3316
  }
2537
3317
  if (!model.writeSingleRegister) {
2538
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3318
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2539
3319
  return;
2540
3320
  }
2541
3321
  const address = (frame.data[0] << 8) | frame.data[1];
2542
3322
  const value = (frame.data[2] << 8) | frame.data[3];
2543
3323
  if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
2544
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
3324
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2545
3325
  return;
2546
3326
  }
2547
3327
  try {
2548
3328
  yield model.writeSingleRegister(address, value);
2549
- yield response(this.applicationLayer.encode(frame));
3329
+ yield response(appLayer.encode(frame));
2550
3330
  }
2551
3331
  catch (error) {
2552
- yield this.responseError(frame, response, error);
3332
+ yield this.responseError(appLayer, frame, response, error);
2553
3333
  }
2554
3334
  });
2555
3335
  }
2556
- handleFC15(model, frame, response) {
3336
+ handleFC15(appLayer, model, frame, response) {
2557
3337
  return __awaiter(this, void 0, void 0, function* () {
2558
3338
  var _a;
2559
3339
  if (frame.data.length <= 5 || frame.data.length !== 5 + frame.data[4]) {
3340
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2560
3341
  return;
2561
3342
  }
2562
3343
  if (!model.writeMultipleCoils && !model.writeSingleCoil) {
2563
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3344
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2564
3345
  return;
2565
3346
  }
2566
3347
  const address = (frame.data[0] << 8) | frame.data[1];
2567
3348
  const length = (frame.data[2] << 8) | frame.data[3];
2568
3349
  const byteCount = frame.data[4];
2569
3350
  if (length < LIMITS.READ_COILS_MIN || length > LIMITS.WRITE_COILS_MAX || byteCount !== Math.ceil(length / 8)) {
2570
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
3351
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2571
3352
  return;
2572
3353
  }
2573
3354
  if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
2574
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
3355
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2575
3356
  return;
2576
3357
  }
2577
3358
  const value = new Array(length);
@@ -2587,32 +3368,33 @@ class ModbusSlave extends EventEmitter {
2587
3368
  yield model.writeSingleCoil(address + i, value[i]);
2588
3369
  }
2589
3370
  }
2590
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: frame.data.subarray(0, 4) })));
3371
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: frame.data.subarray(0, 4) })));
2591
3372
  }
2592
3373
  catch (error) {
2593
- yield this.responseError(frame, response, error);
3374
+ yield this.responseError(appLayer, frame, response, error);
2594
3375
  }
2595
3376
  });
2596
3377
  }
2597
- handleFC16(model, frame, response) {
3378
+ handleFC16(appLayer, model, frame, response) {
2598
3379
  return __awaiter(this, void 0, void 0, function* () {
2599
3380
  var _a;
2600
3381
  if (frame.data.length <= 5 || frame.data.length !== 5 + frame.data[4]) {
3382
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2601
3383
  return;
2602
3384
  }
2603
3385
  if (!model.writeMultipleRegisters && !model.writeSingleRegister) {
2604
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3386
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2605
3387
  return;
2606
3388
  }
2607
3389
  const address = (frame.data[0] << 8) | frame.data[1];
2608
3390
  const length = (frame.data[2] << 8) | frame.data[3];
2609
3391
  const byteCount = frame.data[4];
2610
3392
  if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.WRITE_REGISTERS_MAX || byteCount !== length * 2) {
2611
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
3393
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2612
3394
  return;
2613
3395
  }
2614
3396
  if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
2615
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
3397
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2616
3398
  return;
2617
3399
  }
2618
3400
  const value = new Array(length);
@@ -2628,62 +3410,67 @@ class ModbusSlave extends EventEmitter {
2628
3410
  yield model.writeSingleRegister(address + i, value[i]);
2629
3411
  }
2630
3412
  }
2631
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: frame.data.subarray(0, 4) })));
3413
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: frame.data.subarray(0, 4) })));
2632
3414
  }
2633
3415
  catch (error) {
2634
- yield this.responseError(frame, response, error);
3416
+ yield this.responseError(appLayer, frame, response, error);
2635
3417
  }
2636
3418
  });
2637
3419
  }
2638
- handleFC17(model, frame, response) {
3420
+ handleFC17(appLayer, model, frame, response) {
2639
3421
  return __awaiter(this, void 0, void 0, function* () {
2640
3422
  var _a;
2641
3423
  if (frame.data.length !== 0) {
3424
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2642
3425
  return;
2643
3426
  }
2644
3427
  if (!model.reportServerId) {
2645
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3428
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2646
3429
  return;
2647
3430
  }
2648
3431
  try {
2649
- const { serverId = (_a = model.unit) !== null && _a !== void 0 ? _a : 1, runIndicatorStatus = true, additionalData = [] } = yield model.reportServerId();
2650
- const serverIdBytes = Array.isArray(serverId) ? serverId : [serverId];
3432
+ const { serverId = [(_a = model.unit) !== null && _a !== void 0 ? _a : 1], runIndicatorStatus = true, additionalData = [] } = yield model.reportServerId();
3433
+ const serverIdBytes = serverId;
2651
3434
  const byteCount = serverIdBytes.length + 1 + additionalData.length;
2652
3435
  if (byteCount > 255) {
2653
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
3436
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
2654
3437
  return;
2655
3438
  }
2656
3439
  const allBytes = [...serverIdBytes, runIndicatorStatus ? 0xff : 0x00, ...additionalData];
2657
3440
  if (allBytes.some((b) => !isUint8(b))) {
2658
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
3441
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
2659
3442
  return;
2660
3443
  }
2661
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([byteCount]), Buffer.from(allBytes)]) })));
3444
+ const data = Buffer.allocUnsafe(byteCount + 1);
3445
+ data[0] = byteCount;
3446
+ data.set(allBytes, 1);
3447
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data })));
2662
3448
  }
2663
3449
  catch (error) {
2664
- yield this.responseError(frame, response, error);
3450
+ yield this.responseError(appLayer, frame, response, error);
2665
3451
  }
2666
3452
  });
2667
3453
  }
2668
- handleFC22(model, frame, response) {
3454
+ handleFC22(appLayer, model, frame, response) {
2669
3455
  return __awaiter(this, void 0, void 0, function* () {
2670
3456
  var _a;
2671
3457
  if (frame.data.length !== 6) {
3458
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2672
3459
  return;
2673
3460
  }
2674
3461
  if (!model.maskWriteRegister && !(model.readHoldingRegisters && model.writeSingleRegister)) {
2675
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3462
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2676
3463
  return;
2677
3464
  }
2678
3465
  const address = (frame.data[0] << 8) | frame.data[1];
2679
3466
  const andMask = (frame.data[2] << 8) | frame.data[3];
2680
3467
  const orMask = (frame.data[4] << 8) | frame.data[5];
2681
3468
  if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
2682
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
3469
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2683
3470
  return;
2684
3471
  }
2685
3472
  try {
2686
- yield this.withAddressLock([address], () => __awaiter(this, void 0, void 0, function* () {
3473
+ yield this._withAddressLock([address], () => __awaiter(this, void 0, void 0, function* () {
2687
3474
  if (model.maskWriteRegister) {
2688
3475
  yield model.maskWriteRegister(address, andMask, orMask);
2689
3476
  }
@@ -2692,21 +3479,22 @@ class ModbusSlave extends EventEmitter {
2692
3479
  yield model.writeSingleRegister(address, (value & andMask) | (orMask & (~andMask & 0xffff)));
2693
3480
  }
2694
3481
  }));
2695
- yield response(this.applicationLayer.encode(frame));
3482
+ yield response(appLayer.encode(frame));
2696
3483
  }
2697
3484
  catch (error) {
2698
- yield this.responseError(frame, response, error);
3485
+ yield this.responseError(appLayer, frame, response, error);
2699
3486
  }
2700
3487
  });
2701
3488
  }
2702
- handleFC23(model, frame, response) {
3489
+ handleFC23(appLayer, model, frame, response) {
2703
3490
  return __awaiter(this, void 0, void 0, function* () {
2704
3491
  var _a;
2705
3492
  if (frame.data.length <= 9 || frame.data.length !== 9 + frame.data[8]) {
3493
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2706
3494
  return;
2707
3495
  }
2708
3496
  if (!model.readHoldingRegisters || (!model.writeMultipleRegisters && !model.writeSingleRegister)) {
2709
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3497
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2710
3498
  return;
2711
3499
  }
2712
3500
  const address = {
@@ -2723,11 +3511,11 @@ class ModbusSlave extends EventEmitter {
2723
3511
  length.write < LIMITS.READ_REGISTERS_MIN ||
2724
3512
  length.write > LIMITS.RW_REGISTERS_WRITE_MAX ||
2725
3513
  byteCount !== length.write * 2) {
2726
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
3514
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2727
3515
  return;
2728
3516
  }
2729
3517
  if (!checkRange([address.read, address.read + length.read, address.write, address.write + length.write], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
2730
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
3518
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2731
3519
  return;
2732
3520
  }
2733
3521
  const value = new Array(length.write);
@@ -2736,7 +3524,7 @@ class ModbusSlave extends EventEmitter {
2736
3524
  }
2737
3525
  try {
2738
3526
  const writeAddresses = Array.from({ length: length.write }, (_, i) => address.write + i);
2739
- yield this.withAddressLock(writeAddresses, () => __awaiter(this, void 0, void 0, function* () {
3527
+ yield this._withAddressLock(writeAddresses, () => __awaiter(this, void 0, void 0, function* () {
2740
3528
  if (model.writeMultipleRegisters) {
2741
3529
  yield model.writeMultipleRegisters(address.write, value);
2742
3530
  }
@@ -2747,18 +3535,18 @@ class ModbusSlave extends EventEmitter {
2747
3535
  }
2748
3536
  }));
2749
3537
  const registers = yield model.readHoldingRegisters(address.read, length.read);
2750
- const bufferTx = Buffer.alloc(length.read * 2);
3538
+ const bufferTx = Buffer.allocUnsafe(length.read * 2);
2751
3539
  registers.forEach((register, index) => {
2752
3540
  bufferTx.writeUInt16BE(register, index * 2);
2753
3541
  });
2754
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
3542
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
2755
3543
  }
2756
3544
  catch (error) {
2757
- yield this.responseError(frame, response, error);
3545
+ yield this.responseError(appLayer, frame, response, error);
2758
3546
  }
2759
3547
  });
2760
3548
  }
2761
- handleFC43_14(model, frame, response) {
3549
+ handleFC43_14(appLayer, model, frame, response) {
2762
3550
  return __awaiter(this, void 0, void 0, function* () {
2763
3551
  if (frame.data.length === 3) {
2764
3552
  if (frame.data[0] === MEI_READ_DEVICE_ID && model.readDeviceIdentification) {
@@ -2785,13 +3573,13 @@ class ModbusSlave extends EventEmitter {
2785
3573
  }
2786
3574
  case ReadDeviceIDCode.SPECIFIC_ACCESS: {
2787
3575
  if (objectID > 0x06 && objectID < 0x80) {
2788
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
3576
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2789
3577
  return;
2790
3578
  }
2791
3579
  break;
2792
3580
  }
2793
3581
  default: {
2794
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
3582
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2795
3583
  return;
2796
3584
  }
2797
3585
  }
@@ -2811,7 +3599,7 @@ class ModbusSlave extends EventEmitter {
2811
3599
  }
2812
3600
  if (!objects.has(objectID)) {
2813
3601
  if (readDeviceIDCode === ReadDeviceIDCode.SPECIFIC_ACCESS) {
2814
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
3602
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2815
3603
  return;
2816
3604
  }
2817
3605
  objectID = 0x00;
@@ -2819,7 +3607,7 @@ class ModbusSlave extends EventEmitter {
2819
3607
  let maxId = 0;
2820
3608
  for (const id of objects.keys()) {
2821
3609
  if ((id >= 0x07 && id <= 0x7f) || id > 0xff) {
2822
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
3610
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
2823
3611
  return;
2824
3612
  }
2825
3613
  if (id > maxId) {
@@ -2836,97 +3624,104 @@ class ModbusSlave extends EventEmitter {
2836
3624
  }
2837
3625
  const byteLength = Buffer.byteLength(value);
2838
3626
  if (byteLength > 245) {
2839
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
3627
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
2840
3628
  return;
2841
3629
  }
2842
3630
  if (lastID !== 0) {
2843
3631
  continue;
2844
3632
  }
2845
3633
  if (byteLength + 2 > 253 - totalLength) {
2846
- if (lastID === 0) {
2847
- lastID = id;
2848
- }
3634
+ lastID = id;
2849
3635
  }
2850
3636
  else {
2851
3637
  totalLength += byteLength + 2;
2852
- ids.push(id);
3638
+ ids.push({ id, byteLength });
2853
3639
  if (readDeviceIDCode === ReadDeviceIDCode.SPECIFIC_ACCESS) {
2854
3640
  break;
2855
3641
  }
2856
3642
  }
2857
3643
  }
2858
- ids.sort((a, b) => a - b);
2859
- const parts = [];
2860
- parts.push(Buffer.from([MEI_READ_DEVICE_ID, readDeviceIDCode, conformityLevel, lastID === 0 ? 0x00 : 0xff, lastID, ids.length]));
2861
- for (const id of ids) {
3644
+ ids.sort((a, b) => a.id - b.id);
3645
+ let dataLength = 6;
3646
+ for (const { byteLength } of ids) {
3647
+ dataLength += 2 + byteLength;
3648
+ }
3649
+ const data = Buffer.allocUnsafe(dataLength);
3650
+ let offset = 0;
3651
+ data[offset++] = MEI_READ_DEVICE_ID;
3652
+ data[offset++] = readDeviceIDCode;
3653
+ data[offset++] = conformityLevel;
3654
+ data[offset++] = lastID === 0 ? 0x00 : 0xff;
3655
+ data[offset++] = lastID;
3656
+ data[offset++] = ids.length;
3657
+ for (const { id, byteLength } of ids) {
2862
3658
  const value = objects.get(id);
2863
- parts.push(Buffer.from([id, Buffer.byteLength(value)]));
2864
- parts.push(Buffer.from(value));
3659
+ data[offset++] = id;
3660
+ data[offset++] = byteLength;
3661
+ offset += data.write(value, offset);
2865
3662
  }
2866
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat(parts) })));
3663
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data })));
2867
3664
  }
2868
3665
  catch (error) {
2869
- yield this.responseError(frame, response, error);
3666
+ yield this.responseError(appLayer, frame, response, error);
2870
3667
  }
2871
3668
  }
2872
3669
  else {
2873
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3670
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2874
3671
  }
2875
3672
  }
2876
3673
  });
2877
3674
  }
2878
- responseError(frame, response, error) {
3675
+ responseError(appLayer, frame, response, error) {
2879
3676
  return __awaiter(this, void 0, void 0, function* () {
2880
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { fc: frame.fc | EXCEPTION_OFFSET, data: Buffer.from([getCodeByError(error)]) })));
3677
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { fc: frame.fc | EXCEPTION_OFFSET, data: Buffer.from([getCodeByError(error)]) })));
2881
3678
  });
2882
3679
  }
2883
- _drain(key, q) {
3680
+ _drain(appLayer, q) {
2884
3681
  return __awaiter(this, void 0, void 0, function* () {
2885
- if (q.processing) {
3682
+ if (q.processing || this._physicalLayer.state !== PhysicalState.OPEN) {
2886
3683
  return;
2887
3684
  }
2888
3685
  q.processing = true;
2889
3686
  try {
2890
- while (q.items.length > 0) {
2891
- const { frame, response } = q.items.shift();
2892
- try {
2893
- yield this._processFrame(frame, response);
2894
- }
2895
- catch (error) {
2896
- this.emit('error', error);
3687
+ while (q.frames.length > 0) {
3688
+ if (this._physicalLayer.state !== PhysicalState.OPEN) {
3689
+ return;
2897
3690
  }
3691
+ const frame = q.frames.shift();
3692
+ yield this._processFrame(appLayer, frame);
2898
3693
  }
2899
3694
  }
2900
3695
  finally {
2901
3696
  q.processing = false;
2902
- // Cleanup empty queue entries so the map doesn't grow unbounded across
2903
- // ephemeral connections (UDP rinfo, brief TCP clients).
2904
- if (q.items.length === 0 && this._queues.get(key) === q) {
2905
- this._queues.delete(key);
2906
- }
2907
3697
  }
2908
3698
  });
2909
3699
  }
2910
- _processFrame(frame, _response) {
3700
+ _processFrame(appLayer, frame) {
2911
3701
  return __awaiter(this, void 0, void 0, function* () {
2912
3702
  const response = (data) => __awaiter(this, void 0, void 0, function* () {
2913
- if (frame.unit === 0x00) {
3703
+ if (frame.unit === 0x00 || this._physicalLayer.state !== PhysicalState.OPEN) {
2914
3704
  return;
2915
3705
  }
2916
3706
  try {
2917
- yield _response(data);
3707
+ yield appLayer.connection.write(data);
3708
+ }
3709
+ catch (_a) {
3710
+ /* A write failure usually means the connection is already broken; let the connection's close event handle cleanup. */
2918
3711
  }
2919
- catch (error) { }
2920
3712
  });
2921
3713
  for (const model of frame.unit === 0x00 ? this.models.values() : [this.models.get(frame.unit)]) {
2922
- const intercepted = yield this._intercept(model, frame, response);
3714
+ if (this._physicalLayer.state !== PhysicalState.OPEN) {
3715
+ return;
3716
+ }
3717
+ const intercepted = yield this._intercept(appLayer, model, frame, response);
2923
3718
  if (!intercepted) {
2924
- yield this._handleFC(model, frame, response);
3719
+ yield this._handleFC(appLayer, model, frame, response);
2925
3720
  }
2926
3721
  }
2927
3722
  });
2928
3723
  }
2929
- _intercept(model, frame, response) {
3724
+ _intercept(appLayer, model, frame, response) {
2930
3725
  return __awaiter(this, void 0, void 0, function* () {
2931
3726
  if (!model.interceptor) {
2932
3727
  return false;
@@ -2934,18 +3729,18 @@ class ModbusSlave extends EventEmitter {
2934
3729
  try {
2935
3730
  const data = yield model.interceptor(frame.fc, frame.data);
2936
3731
  if (data !== undefined) {
2937
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data })));
3732
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data })));
2938
3733
  return true;
2939
3734
  }
2940
3735
  return false;
2941
3736
  }
2942
3737
  catch (error) {
2943
- yield this.responseError(frame, response, error);
3738
+ yield this.responseError(appLayer, frame, response, error);
2944
3739
  return true;
2945
3740
  }
2946
3741
  });
2947
3742
  }
2948
- withAddressLock(addresses, fn) {
3743
+ _withAddressLock(addresses, fn) {
2949
3744
  return __awaiter(this, void 0, void 0, function* () {
2950
3745
  const sorted = [...new Set(addresses)].sort((a, b) => a - b);
2951
3746
  const previous = sorted.map((addr) => { var _a; return (_a = this._locks.get(addr)) !== null && _a !== void 0 ? _a : Promise.resolve(); });
@@ -2968,55 +3763,55 @@ class ModbusSlave extends EventEmitter {
2968
3763
  }
2969
3764
  });
2970
3765
  }
2971
- _handleFC(model, frame, response) {
3766
+ _handleFC(appLayer, model, frame, response) {
2972
3767
  return __awaiter(this, void 0, void 0, function* () {
2973
3768
  switch (frame.fc) {
2974
3769
  case FunctionCode.READ_COILS: {
2975
- yield this.handleFC1(model, frame, response);
3770
+ yield this.handleFC1(appLayer, model, frame, response);
2976
3771
  break;
2977
3772
  }
2978
3773
  case FunctionCode.READ_DISCRETE_INPUTS: {
2979
- yield this.handleFC2(model, frame, response);
3774
+ yield this.handleFC2(appLayer, model, frame, response);
2980
3775
  break;
2981
3776
  }
2982
3777
  case FunctionCode.READ_HOLDING_REGISTERS: {
2983
- yield this.handleFC3(model, frame, response);
3778
+ yield this.handleFC3(appLayer, model, frame, response);
2984
3779
  break;
2985
3780
  }
2986
3781
  case FunctionCode.READ_INPUT_REGISTERS: {
2987
- yield this.handleFC4(model, frame, response);
3782
+ yield this.handleFC4(appLayer, model, frame, response);
2988
3783
  break;
2989
3784
  }
2990
3785
  case FunctionCode.WRITE_SINGLE_COIL: {
2991
- yield this.handleFC5(model, frame, response);
3786
+ yield this.handleFC5(appLayer, model, frame, response);
2992
3787
  break;
2993
3788
  }
2994
3789
  case FunctionCode.WRITE_SINGLE_REGISTER: {
2995
- yield this.handleFC6(model, frame, response);
3790
+ yield this.handleFC6(appLayer, model, frame, response);
2996
3791
  break;
2997
3792
  }
2998
3793
  case FunctionCode.WRITE_MULTIPLE_COILS: {
2999
- yield this.handleFC15(model, frame, response);
3794
+ yield this.handleFC15(appLayer, model, frame, response);
3000
3795
  break;
3001
3796
  }
3002
3797
  case FunctionCode.WRITE_MULTIPLE_REGISTERS: {
3003
- yield this.handleFC16(model, frame, response);
3798
+ yield this.handleFC16(appLayer, model, frame, response);
3004
3799
  break;
3005
3800
  }
3006
3801
  case FunctionCode.REPORT_SERVER_ID: {
3007
- yield this.handleFC17(model, frame, response);
3802
+ yield this.handleFC17(appLayer, model, frame, response);
3008
3803
  break;
3009
3804
  }
3010
3805
  case FunctionCode.MASK_WRITE_REGISTER: {
3011
- yield this.handleFC22(model, frame, response);
3806
+ yield this.handleFC22(appLayer, model, frame, response);
3012
3807
  break;
3013
3808
  }
3014
3809
  case FunctionCode.READ_WRITE_MULTIPLE_REGISTERS: {
3015
- yield this.handleFC23(model, frame, response);
3810
+ yield this.handleFC23(appLayer, model, frame, response);
3016
3811
  break;
3017
3812
  }
3018
3813
  case FunctionCode.READ_DEVICE_IDENTIFICATION: {
3019
- yield this.handleFC43_14(model, frame, response);
3814
+ yield this.handleFC43_14(appLayer, model, frame, response);
3020
3815
  break;
3021
3816
  }
3022
3817
  default: {
@@ -3024,14 +3819,14 @@ class ModbusSlave extends EventEmitter {
3024
3819
  if (cfc === null || cfc === void 0 ? void 0 : cfc.handle) {
3025
3820
  try {
3026
3821
  const responseData = yield cfc.handle(frame.data, frame.unit);
3027
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: responseData })));
3822
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: responseData })));
3028
3823
  }
3029
3824
  catch (error) {
3030
- yield this.responseError(frame, response, error);
3825
+ yield this.responseError(appLayer, frame, response, error);
3031
3826
  }
3032
3827
  }
3033
3828
  else {
3034
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3829
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3035
3830
  }
3036
3831
  break;
3037
3832
  }
@@ -3047,50 +3842,66 @@ class ModbusSlave extends EventEmitter {
3047
3842
  }
3048
3843
  addCustomFunctionCode(cfc) {
3049
3844
  this._customFunctionCodes.set(cfc.fc, cfc);
3050
- this.applicationLayer.addCustomFunctionCode(cfc);
3845
+ for (const appLayer of this._appLayers.keys()) {
3846
+ appLayer.addCustomFunctionCode(cfc);
3847
+ }
3051
3848
  }
3052
3849
  removeCustomFunctionCode(fc) {
3053
3850
  this._customFunctionCodes.delete(fc);
3054
- this.applicationLayer.removeCustomFunctionCode(fc);
3055
- }
3056
- _clean(level) {
3057
- if (this._cleanLevel === 'destroy') {
3058
- return;
3059
- }
3060
- if (this._cleanLevel === 'close' && level === 'close') {
3061
- return;
3062
- }
3063
- for (const q of this._queues.values()) {
3064
- q.items.length = 0;
3065
- }
3066
- this._queues.clear();
3067
- this._locks.clear();
3068
- if (level === 'destroy') {
3069
- this._customFunctionCodes.clear();
3070
- this.models.clear();
3851
+ for (const appLayer of this._appLayers.keys()) {
3852
+ appLayer.removeCustomFunctionCode(fc);
3853
+ }
3854
+ }
3855
+ /**
3856
+ * Open the underlying physical layer and begin accepting connections.
3857
+ *
3858
+ * A `ModbusSlave` instance can only be opened once. Once {@link close}
3859
+ * is called — explicitly or because the physical layer disconnected —
3860
+ * the instance is permanently closed and cannot be reopened.
3861
+ * Create a new `ModbusSlave` if a new server is required.
3862
+ */
3863
+ open(...args) {
3864
+ if (this._closePromise) {
3865
+ return Promise.reject(new Error('The port has been permanently closed and CANNOT be reopened'));
3071
3866
  }
3072
- this._cleanLevel = level;
3867
+ return this._physicalLayer.open(...args);
3073
3868
  }
3074
- open(...args) {
3075
- this._cleanLevel = 'none';
3076
- return this.physicalLayer.open(...args);
3869
+ _close() {
3870
+ return __awaiter(this, void 0, void 0, function* () {
3871
+ for (const fn of this._cleanupFns) {
3872
+ fn();
3873
+ }
3874
+ this._cleanupFns.clear();
3875
+ let err = null;
3876
+ try {
3877
+ yield this._physicalLayer.close();
3878
+ }
3879
+ catch (error) {
3880
+ err = error;
3881
+ }
3882
+ this.removeAllListeners();
3883
+ if (err) {
3884
+ return Promise.reject(err);
3885
+ }
3886
+ });
3077
3887
  }
3888
+ /**
3889
+ * Permanently close the slave and release all resources.
3890
+ *
3891
+ * After calling this method the instance is considered dead:
3892
+ * - No further requests can be processed.
3893
+ * - The instance cannot be reopened via {@link open}.
3894
+ * - All event listeners registered on this slave are removed.
3895
+ */
3078
3896
  close() {
3079
- if (this._cleanLevel === 'destroy') {
3080
- return Promise.resolve();
3081
- }
3082
- this._clean('close');
3083
- return this.physicalLayer.close();
3084
- }
3085
- destroy() {
3086
- if (this._cleanLevel === 'destroy') {
3087
- return Promise.resolve();
3088
- }
3089
- this._clean('destroy');
3090
- this.removeAllListeners();
3091
- this.applicationLayer.destroy();
3092
- return this.physicalLayer.destroy();
3897
+ return __awaiter(this, void 0, void 0, function* () {
3898
+ if (this._closePromise) {
3899
+ return this._closePromise;
3900
+ }
3901
+ this._closePromise = this._close();
3902
+ return this._closePromise;
3903
+ });
3093
3904
  }
3094
3905
  }
3095
3906
 
3096
- export { AbstractApplicationLayer, AbstractPhysicalLayer, AsciiApplicationLayer, COIL_OFF, COIL_ON, ConformityLevel, EXCEPTION_OFFSET, ErrorCode, FunctionCode, LIMITS, MEI_READ_DEVICE_ID, MasterSession, ModbusError, ModbusErrorCode, ModbusMaster, ModbusSlave, ReadDeviceIDCode, RtuApplicationLayer, SerialPhysicalLayer, TcpApplicationLayer, TcpClientPhysicalLayer, TcpServerPhysicalLayer, UdpPhysicalLayer, getCodeByError, getErrorByCode };
3907
+ export { AbstractApplicationLayer, AbstractPhysicalConnection, AbstractPhysicalLayer, AsciiApplicationLayer, COIL_OFF, COIL_ON, ConformityLevel, EXCEPTION_OFFSET, ErrorCode, FunctionCode, LIMITS, MEI_READ_DEVICE_ID, MasterSession, ModbusError, ModbusMaster, ModbusSlave, PhysicalConnectionState, PhysicalState, ReadDeviceIDCode, RtuApplicationLayer, SerialPhysicalLayer, TcpApplicationLayer, TcpClientPhysicalLayer, TcpServerPhysicalLayer, UdpClientPhysicalLayer, UdpServerPhysicalLayer, createPhysicalLayer, getCodeByError, getErrorByCode };