njs-modbus 2.1.0 → 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 +264 -154
  2. package/README.zh-CN.md +314 -0
  3. package/dist/index.cjs +1887 -1066
  4. package/dist/index.d.ts +368 -215
  5. package/dist/index.mjs +1884 -1065
  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 -17
  22. package/dist/src/slave/slave.d.ts +57 -33
  23. package/dist/src/types.d.ts +2 -2
  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 +8 -8
  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
- }
718
- try {
719
- server.close();
720
1416
  }
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));
1134
- }
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;
1790
+ connection.on('close', onClose);
1791
+ this._cleanupFns.add(() => connection.off('close', onClose));
1142
1792
  }
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 = [];
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;
2071
+ connection.on('close', onClose);
2072
+ this._cleanupFns.add(() => connection.off('close', onClose));
1453
2073
  }
1454
- flush() {
1455
- this._states.clear();
1456
- }
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 = [];
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 = [];
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,6 +2398,30 @@ class ModbusMaster extends EventEmitter {
1776
2398
  writable: true,
1777
2399
  value: new MasterSession()
1778
2400
  });
2401
+ Object.defineProperty(this, "_physicalLayer", {
2402
+ enumerable: true,
2403
+ configurable: true,
2404
+ writable: true,
2405
+ value: void 0
2406
+ });
2407
+ Object.defineProperty(this, "_protocol", {
2408
+ enumerable: true,
2409
+ configurable: true,
2410
+ writable: true,
2411
+ value: void 0
2412
+ });
2413
+ Object.defineProperty(this, "_appLayer", {
2414
+ enumerable: true,
2415
+ configurable: true,
2416
+ writable: true,
2417
+ value: void 0
2418
+ });
2419
+ Object.defineProperty(this, "_customFunctionCodes", {
2420
+ enumerable: true,
2421
+ configurable: true,
2422
+ writable: true,
2423
+ value: new Map()
2424
+ });
1779
2425
  Object.defineProperty(this, "_queue", {
1780
2426
  enumerable: true,
1781
2427
  configurable: true,
@@ -1794,23 +2440,17 @@ class ModbusMaster extends EventEmitter {
1794
2440
  writable: true,
1795
2441
  value: 1
1796
2442
  });
1797
- Object.defineProperty(this, "_cleanLevel", {
1798
- enumerable: true,
1799
- configurable: true,
1800
- writable: true,
1801
- value: 'none'
1802
- });
1803
- Object.defineProperty(this, "timeout", {
2443
+ Object.defineProperty(this, "_cleanupFns", {
1804
2444
  enumerable: true,
1805
2445
  configurable: true,
1806
2446
  writable: true,
1807
- value: void 0
2447
+ value: new Set()
1808
2448
  });
1809
- Object.defineProperty(this, "concurrent", {
2449
+ Object.defineProperty(this, "_closePromise", {
1810
2450
  enumerable: true,
1811
2451
  configurable: true,
1812
2452
  writable: true,
1813
- value: void 0
2453
+ value: null
1814
2454
  });
1815
2455
  Object.defineProperty(this, "writeFC1", {
1816
2456
  enumerable: true,
@@ -1886,10 +2526,60 @@ class ModbusMaster extends EventEmitter {
1886
2526
  });
1887
2527
  this.timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : 1000;
1888
2528
  this.concurrent = (_b = options.concurrent) !== null && _b !== void 0 ? _b : false;
1889
- if (this.concurrent && this.applicationLayer.PROTOCOL !== 'TCP') {
1890
- 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');
1891
2533
  }
1892
- 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));
1893
2583
  this.writeFC1 = this.readCoils;
1894
2584
  this.writeFC2 = this.readDiscreteInputs;
1895
2585
  this.writeFC3 = this.readHoldingRegisters;
@@ -1902,20 +2592,27 @@ class ModbusMaster extends EventEmitter {
1902
2592
  this.handleFC22 = this.maskWriteRegister;
1903
2593
  this.handleFC23 = this.readAndWriteMultipleRegisters;
1904
2594
  this.handleFC43_14 = this.readDeviceIdentification;
1905
- applicationLayer.on('framing', (frame) => {
1906
- this._masterSession.handleFrame(frame);
1907
- });
1908
- applicationLayer.on('framing-error', (error) => {
1909
- this._masterSession.handleError(error);
1910
- });
1911
- physicalLayer.on('error', (error) => {
1912
- this.emit('error', error);
1913
- });
1914
- physicalLayer.on('close', () => {
1915
- this.emit('close');
1916
- });
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);
1917
2611
  }
1918
2612
  send(adu, preCheck, timeout, broadcast) {
2613
+ if (this._physicalLayer.state !== PhysicalState.OPEN) {
2614
+ return Promise.reject(new Error('Master is not open'));
2615
+ }
1919
2616
  const task = () => this._exchange(adu, preCheck, timeout, broadcast);
1920
2617
  if (this.concurrent) {
1921
2618
  return task();
@@ -1935,14 +2632,20 @@ class ModbusMaster extends EventEmitter {
1935
2632
  }
1936
2633
  _drain() {
1937
2634
  return __awaiter(this, void 0, void 0, function* () {
1938
- if (this._draining) {
2635
+ if (this._draining || this._physicalLayer.state !== PhysicalState.OPEN) {
1939
2636
  return;
1940
2637
  }
1941
2638
  this._draining = true;
1942
2639
  try {
1943
2640
  while (this._queue.length > 0) {
2641
+ if (this._physicalLayer.state !== PhysicalState.OPEN) {
2642
+ return;
2643
+ }
1944
2644
  const item = this._queue.shift();
1945
- yield item.run();
2645
+ try {
2646
+ yield item.run();
2647
+ }
2648
+ catch (_a) { }
1946
2649
  }
1947
2650
  }
1948
2651
  finally {
@@ -1952,18 +2655,26 @@ class ModbusMaster extends EventEmitter {
1952
2655
  }
1953
2656
  _exchange(adu, preCheck, timeout, broadcast) {
1954
2657
  return new Promise((resolve, reject) => {
1955
- if (this._cleanLevel !== 'none') {
1956
- 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'));
1957
2665
  return;
1958
2666
  }
1959
- const usesTid = this.applicationLayer.PROTOCOL === 'TCP' && !broadcast;
2667
+ const connection = appLayer.connection;
2668
+ const usesTid = appLayer.PROTOCOL === 'TCP' && !broadcast;
1960
2669
  let tid;
1961
2670
  if (usesTid) {
1962
- tid = this._nextTid;
1963
- 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));
1964
2675
  }
1965
2676
  const key = tid !== null && tid !== void 0 ? tid : FIFO_KEY;
1966
- const payload = this.applicationLayer.encode(Object.assign(Object.assign({}, adu), { transaction: tid }));
2677
+ const payload = appLayer.encode(Object.assign(Object.assign({}, adu), { transaction: tid }));
1967
2678
  let settled = false;
1968
2679
  const settle = (error, frame) => {
1969
2680
  if (settled) {
@@ -1979,18 +2690,22 @@ class ModbusMaster extends EventEmitter {
1979
2690
  resolve(frame);
1980
2691
  }
1981
2692
  };
1982
- const timer = setTimeout(() => settle(new ModbusError(ModbusErrorCode.TIMEOUT, 'Timeout')), timeout);
2693
+ const timer = setTimeout(() => settle(new Error('Timeout')), timeout);
1983
2694
  // FIFO mode: clear stale buffer state before this request.
1984
2695
  // Concurrent mode: must not flush — other in-flight requests share the buffer.
1985
2696
  if (!this.concurrent) {
1986
- this.applicationLayer.flush();
2697
+ appLayer.flush();
1987
2698
  }
1988
- this.physicalLayer
2699
+ connection
1989
2700
  .write(payload)
1990
2701
  .then(() => {
1991
2702
  if (settled) {
1992
2703
  return;
1993
2704
  }
2705
+ if (this._physicalLayer.state !== PhysicalState.OPEN) {
2706
+ settle(new Error('Master is not open'));
2707
+ return;
2708
+ }
1994
2709
  if (broadcast) {
1995
2710
  settle(null);
1996
2711
  return;
@@ -2003,7 +2718,7 @@ class ModbusMaster extends EventEmitter {
2003
2718
  }
2004
2719
  writeFC1Or2(unit, fc, address, length, timeout) {
2005
2720
  const byteCount = Math.ceil(length / 8);
2006
- const bufferTx = Buffer.alloc(4);
2721
+ const bufferTx = Buffer.allocUnsafe(4);
2007
2722
  bufferTx.writeUInt16BE(address, 0);
2008
2723
  bufferTx.writeUInt16BE(length, 2);
2009
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) => {
@@ -2020,7 +2735,7 @@ class ModbusMaster extends EventEmitter {
2020
2735
  }
2021
2736
  writeFC3Or4(unit, fc, address, length, timeout) {
2022
2737
  const byteCount = length * 2;
2023
- const bufferTx = Buffer.alloc(4);
2738
+ const bufferTx = Buffer.allocUnsafe(4);
2024
2739
  bufferTx.writeUInt16BE(address, 0);
2025
2740
  bufferTx.writeUInt16BE(length, 2);
2026
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) => {
@@ -2038,7 +2753,7 @@ class ModbusMaster extends EventEmitter {
2038
2753
  }
2039
2754
  writeSingleCoil(unit, address, value, timeout = this.timeout) {
2040
2755
  const fc = FunctionCode.WRITE_SINGLE_COIL;
2041
- const bufferTx = Buffer.alloc(4);
2756
+ const bufferTx = Buffer.allocUnsafe(4);
2042
2757
  bufferTx.writeUInt16BE(address, 0);
2043
2758
  bufferTx.writeUInt16BE(value ? COIL_ON : COIL_OFF, 2);
2044
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) => {
@@ -2049,7 +2764,7 @@ class ModbusMaster extends EventEmitter {
2049
2764
  }
2050
2765
  writeSingleRegister(unit, address, value, timeout = this.timeout) {
2051
2766
  const fc = FunctionCode.WRITE_SINGLE_REGISTER;
2052
- const bufferTx = Buffer.alloc(4);
2767
+ const bufferTx = Buffer.allocUnsafe(4);
2053
2768
  bufferTx.writeUInt16BE(address, 0);
2054
2769
  bufferTx.writeUInt16BE(value, 2);
2055
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) => {
@@ -2079,7 +2794,7 @@ class ModbusMaster extends EventEmitter {
2079
2794
  writeMultipleRegisters(unit, address, value, timeout = this.timeout) {
2080
2795
  const fc = FunctionCode.WRITE_MULTIPLE_REGISTERS;
2081
2796
  const byteCount = value.length * 2;
2082
- const bufferTx = Buffer.alloc(5 + byteCount);
2797
+ const bufferTx = Buffer.allocUnsafe(5 + byteCount);
2083
2798
  bufferTx.writeUInt16BE(address, 0);
2084
2799
  bufferTx.writeUInt16BE(value.length, 2);
2085
2800
  bufferTx.writeUInt8(byteCount, 4);
@@ -2114,7 +2829,7 @@ class ModbusMaster extends EventEmitter {
2114
2829
  }
2115
2830
  maskWriteRegister(unit, address, andMask, orMask, timeout = this.timeout) {
2116
2831
  const fc = FunctionCode.MASK_WRITE_REGISTER;
2117
- const bufferTx = Buffer.alloc(6);
2832
+ const bufferTx = Buffer.allocUnsafe(6);
2118
2833
  bufferTx.writeUInt16BE(address, 0);
2119
2834
  bufferTx.writeUInt16BE(andMask, 2);
2120
2835
  bufferTx.writeUInt16BE(orMask, 4);
@@ -2128,7 +2843,7 @@ class ModbusMaster extends EventEmitter {
2128
2843
  const fc = FunctionCode.READ_WRITE_MULTIPLE_REGISTERS;
2129
2844
  const byteCount = write.value.length * 2;
2130
2845
  const readByteCount = read.length * 2;
2131
- const bufferTx = Buffer.alloc(9 + byteCount);
2846
+ const bufferTx = Buffer.allocUnsafe(9 + byteCount);
2132
2847
  bufferTx.writeUInt16BE(read.address, 0);
2133
2848
  bufferTx.writeUInt16BE(read.length, 2);
2134
2849
  bufferTx.writeUInt16BE(write.address, 4);
@@ -2200,10 +2915,14 @@ class ModbusMaster extends EventEmitter {
2200
2915
  });
2201
2916
  }
2202
2917
  addCustomFunctionCode(cfc) {
2203
- 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);
2204
2921
  }
2205
2922
  removeCustomFunctionCode(fc) {
2206
- 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);
2207
2926
  }
2208
2927
  sendCustomFC(unit, fc, data, timeout = this.timeout) {
2209
2928
  const payload = Buffer.isBuffer(data) ? data : Buffer.from(data);
@@ -2213,83 +2932,107 @@ class ModbusMaster extends EventEmitter {
2213
2932
  }
2214
2933
  });
2215
2934
  }
2216
- _clean(level) {
2217
- if (this._cleanLevel === 'destroy') {
2218
- return;
2219
- }
2220
- if (this._cleanLevel === 'close' && level === 'close') {
2221
- return;
2222
- }
2223
- const errorCode = level === 'destroy' ? ModbusErrorCode.MASTER_DESTROYED : ModbusErrorCode.MASTER_CLOSED;
2224
- const message = level === 'destroy' ? 'Master destroyed' : 'Master closed';
2935
+ _clean(message) {
2225
2936
  const queued = this._queue.splice(0);
2226
2937
  for (const item of queued) {
2227
- item.cancel(new ModbusError(errorCode, message));
2228
- }
2229
- this._masterSession.stopAll(new ModbusError(errorCode, message));
2230
- this._cleanLevel = level;
2231
- }
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
+ */
2232
2950
  open(...args) {
2233
- if (this._cleanLevel === 'destroy') {
2234
- 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'));
2235
2953
  }
2236
- this._cleanLevel = 'none';
2237
- this._nextTid = 1;
2238
- return this.physicalLayer.open(...args);
2954
+ return this._physicalLayer.open(...args);
2239
2955
  }
2240
- close() {
2241
- if (this._cleanLevel === 'destroy') {
2242
- return Promise.resolve();
2243
- }
2244
- this._clean('close');
2245
- 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
+ });
2246
2975
  }
2247
- destroy() {
2248
- if (this._cleanLevel === 'destroy') {
2249
- return Promise.resolve();
2250
- }
2251
- this._clean('destroy');
2252
- this.removeAllListeners();
2253
- this.applicationLayer.destroy();
2254
- 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
+ });
2255
2992
  }
2256
2993
  }
2257
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
+ }
2258
3001
  class ModbusSlave extends EventEmitter {
2259
- get isOpen() {
2260
- return this.physicalLayer.isOpen;
3002
+ get state() {
3003
+ return this._physicalLayer.state;
2261
3004
  }
2262
- get destroyed() {
2263
- return this._cleanLevel === 'destroy' || this.physicalLayer.destroyed;
3005
+ get physicalLayer() {
3006
+ return this._physicalLayer;
2264
3007
  }
2265
- constructor(applicationLayer, physicalLayer, options = {}) {
3008
+ constructor(options) {
2266
3009
  var _a;
2267
3010
  super();
2268
- Object.defineProperty(this, "applicationLayer", {
3011
+ Object.defineProperty(this, "models", {
2269
3012
  enumerable: true,
2270
3013
  configurable: true,
2271
3014
  writable: true,
2272
- value: applicationLayer
3015
+ value: new Map()
2273
3016
  });
2274
- Object.defineProperty(this, "physicalLayer", {
3017
+ Object.defineProperty(this, "concurrent", {
2275
3018
  enumerable: true,
2276
3019
  configurable: true,
2277
3020
  writable: true,
2278
- value: physicalLayer
3021
+ value: void 0
2279
3022
  });
2280
- Object.defineProperty(this, "models", {
3023
+ Object.defineProperty(this, "_physicalLayer", {
2281
3024
  enumerable: true,
2282
3025
  configurable: true,
2283
3026
  writable: true,
2284
- value: new Map()
3027
+ value: void 0
2285
3028
  });
2286
- Object.defineProperty(this, "concurrent", {
3029
+ Object.defineProperty(this, "_protocol", {
2287
3030
  enumerable: true,
2288
3031
  configurable: true,
2289
3032
  writable: true,
2290
3033
  value: void 0
2291
3034
  });
2292
- Object.defineProperty(this, "_queues", {
3035
+ Object.defineProperty(this, "_appLayers", {
2293
3036
  enumerable: true,
2294
3037
  configurable: true,
2295
3038
  writable: true,
@@ -2307,72 +3050,112 @@ class ModbusSlave extends EventEmitter {
2307
3050
  writable: true,
2308
3051
  value: new Map()
2309
3052
  });
2310
- Object.defineProperty(this, "_cleanLevel", {
3053
+ Object.defineProperty(this, "_cleanupFns", {
2311
3054
  enumerable: true,
2312
3055
  configurable: true,
2313
3056
  writable: true,
2314
- value: 'none'
2315
- });
2316
- this.concurrent = (_a = options.concurrent) !== null && _a !== void 0 ? _a : false;
2317
- if (this.concurrent && this.applicationLayer.PROTOCOL !== 'TCP') {
2318
- throw new ModbusError(ModbusErrorCode.CONCURRENT_NOT_TCP, 'concurrent mode requires a Modbus TCP application layer');
2319
- }
2320
- this.applicationLayer.role = 'SLAVE';
2321
- applicationLayer.on('framing', (frame, response, connection) => {
2322
- if (!(frame.unit === 0x00 || this.models.has(frame.unit))) {
2323
- return;
2324
- }
2325
- if (this.concurrent) {
2326
- this._processFrame(frame, response).catch((error) => this.emit('error', error));
2327
- return;
2328
- }
2329
- let q = this._queues.get(connection.id);
2330
- if (!q) {
2331
- q = { items: [], processing: false };
2332
- this._queues.set(connection.id, q);
2333
- }
2334
- q.items.push({ frame, response });
2335
- this._drain(connection.id, q);
2336
- });
2337
- physicalLayer.on('error', (error) => {
2338
- this.emit('error', error);
3057
+ value: new Set()
2339
3058
  });
2340
- physicalLayer.on('connection-close', (connection) => {
2341
- const q = this._queues.get(connection.id);
2342
- if (!q) {
2343
- return;
2344
- }
2345
- q.items = [];
2346
- if (!q.processing) {
2347
- this._queues.delete(connection.id);
2348
- }
3059
+ Object.defineProperty(this, "_closePromise", {
3060
+ enumerable: true,
3061
+ configurable: true,
3062
+ writable: true,
3063
+ value: null
2349
3064
  });
2350
- physicalLayer.on('close', () => {
2351
- for (const q of this._queues.values()) {
2352
- q.items = [];
2353
- }
2354
- 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 = () => {
2355
3113
  this.emit('close');
2356
- });
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);
2357
3139
  }
2358
- handleFC1(model, frame, response) {
3140
+ handleFC1(appLayer, model, frame, response) {
2359
3141
  return __awaiter(this, void 0, void 0, function* () {
2360
3142
  var _a;
2361
3143
  if (frame.data.length !== 4) {
3144
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2362
3145
  return;
2363
3146
  }
2364
3147
  if (!model.readCoils) {
2365
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3148
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2366
3149
  return;
2367
3150
  }
2368
3151
  const address = (frame.data[0] << 8) | frame.data[1];
2369
3152
  const length = (frame.data[2] << 8) | frame.data[3];
2370
3153
  if (length < LIMITS.READ_COILS_MIN || length > LIMITS.READ_COILS_MAX) {
2371
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
3154
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2372
3155
  return;
2373
3156
  }
2374
3157
  if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
2375
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
3158
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2376
3159
  return;
2377
3160
  }
2378
3161
  try {
@@ -2383,31 +3166,32 @@ class ModbusSlave extends EventEmitter {
2383
3166
  bufferTx[Math.floor(index / 8)] |= 1 << index % 8;
2384
3167
  }
2385
3168
  });
2386
- 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) })));
2387
3170
  }
2388
3171
  catch (error) {
2389
- yield this.responseError(frame, response, error);
3172
+ yield this.responseError(appLayer, frame, response, error);
2390
3173
  }
2391
3174
  });
2392
3175
  }
2393
- handleFC2(model, frame, response) {
3176
+ handleFC2(appLayer, model, frame, response) {
2394
3177
  return __awaiter(this, void 0, void 0, function* () {
2395
3178
  var _a;
2396
3179
  if (frame.data.length !== 4) {
3180
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2397
3181
  return;
2398
3182
  }
2399
3183
  if (!model.readDiscreteInputs) {
2400
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3184
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2401
3185
  return;
2402
3186
  }
2403
3187
  const address = (frame.data[0] << 8) | frame.data[1];
2404
3188
  const length = (frame.data[2] << 8) | frame.data[3];
2405
3189
  if (length < LIMITS.READ_COILS_MIN || length > LIMITS.READ_COILS_MAX) {
2406
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
3190
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2407
3191
  return;
2408
3192
  }
2409
3193
  if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).discreteInputs)) {
2410
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
3194
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2411
3195
  return;
2412
3196
  }
2413
3197
  try {
@@ -2418,152 +3202,157 @@ class ModbusSlave extends EventEmitter {
2418
3202
  bufferTx[Math.floor(index / 8)] |= 1 << index % 8;
2419
3203
  }
2420
3204
  });
2421
- 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) })));
2422
3206
  }
2423
3207
  catch (error) {
2424
- yield this.responseError(frame, response, error);
3208
+ yield this.responseError(appLayer, frame, response, error);
2425
3209
  }
2426
3210
  });
2427
3211
  }
2428
- handleFC3(model, frame, response) {
3212
+ handleFC3(appLayer, model, frame, response) {
2429
3213
  return __awaiter(this, void 0, void 0, function* () {
2430
3214
  var _a;
2431
3215
  if (frame.data.length !== 4) {
3216
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2432
3217
  return;
2433
3218
  }
2434
3219
  if (!model.readHoldingRegisters) {
2435
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3220
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2436
3221
  return;
2437
3222
  }
2438
3223
  const address = (frame.data[0] << 8) | frame.data[1];
2439
3224
  const length = (frame.data[2] << 8) | frame.data[3];
2440
3225
  if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.READ_REGISTERS_MAX) {
2441
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
3226
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2442
3227
  return;
2443
3228
  }
2444
3229
  if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
2445
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
3230
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2446
3231
  return;
2447
3232
  }
2448
3233
  try {
2449
3234
  const registers = yield model.readHoldingRegisters(address, length);
2450
- const bufferTx = Buffer.alloc(length * 2);
3235
+ const bufferTx = Buffer.allocUnsafe(length * 2);
2451
3236
  registers.forEach((register, index) => {
2452
3237
  bufferTx.writeUInt16BE(register, index * 2);
2453
3238
  });
2454
- 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) })));
2455
3240
  }
2456
3241
  catch (error) {
2457
- yield this.responseError(frame, response, error);
3242
+ yield this.responseError(appLayer, frame, response, error);
2458
3243
  }
2459
3244
  });
2460
3245
  }
2461
- handleFC4(model, frame, response) {
3246
+ handleFC4(appLayer, model, frame, response) {
2462
3247
  return __awaiter(this, void 0, void 0, function* () {
2463
3248
  var _a;
2464
3249
  if (frame.data.length !== 4) {
3250
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2465
3251
  return;
2466
3252
  }
2467
3253
  if (!model.readInputRegisters) {
2468
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3254
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2469
3255
  return;
2470
3256
  }
2471
3257
  const address = (frame.data[0] << 8) | frame.data[1];
2472
3258
  const length = (frame.data[2] << 8) | frame.data[3];
2473
3259
  if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.READ_REGISTERS_MAX) {
2474
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
3260
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2475
3261
  return;
2476
3262
  }
2477
3263
  if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).inputRegisters)) {
2478
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
3264
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2479
3265
  return;
2480
3266
  }
2481
3267
  try {
2482
3268
  const registers = yield model.readInputRegisters(address, length);
2483
- const bufferTx = Buffer.alloc(length * 2);
3269
+ const bufferTx = Buffer.allocUnsafe(length * 2);
2484
3270
  registers.forEach((register, index) => {
2485
3271
  bufferTx.writeUInt16BE(register, index * 2);
2486
3272
  });
2487
- 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) })));
2488
3274
  }
2489
3275
  catch (error) {
2490
- yield this.responseError(frame, response, error);
3276
+ yield this.responseError(appLayer, frame, response, error);
2491
3277
  }
2492
3278
  });
2493
3279
  }
2494
- handleFC5(model, frame, response) {
3280
+ handleFC5(appLayer, model, frame, response) {
2495
3281
  return __awaiter(this, void 0, void 0, function* () {
2496
3282
  var _a;
2497
3283
  if (frame.data.length !== 4) {
3284
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2498
3285
  return;
2499
3286
  }
2500
3287
  if (!model.writeSingleCoil) {
2501
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3288
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2502
3289
  return;
2503
3290
  }
2504
3291
  const address = (frame.data[0] << 8) | frame.data[1];
2505
3292
  const value = (frame.data[2] << 8) | frame.data[3];
2506
3293
  if (value !== COIL_OFF && value !== COIL_ON) {
2507
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
3294
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2508
3295
  return;
2509
3296
  }
2510
3297
  if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
2511
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
3298
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2512
3299
  return;
2513
3300
  }
2514
3301
  try {
2515
3302
  yield model.writeSingleCoil(address, value === COIL_ON);
2516
- yield response(this.applicationLayer.encode(frame));
3303
+ yield response(appLayer.encode(frame));
2517
3304
  }
2518
3305
  catch (error) {
2519
- yield this.responseError(frame, response, error);
3306
+ yield this.responseError(appLayer, frame, response, error);
2520
3307
  }
2521
3308
  });
2522
3309
  }
2523
- handleFC6(model, frame, response) {
3310
+ handleFC6(appLayer, model, frame, response) {
2524
3311
  return __awaiter(this, void 0, void 0, function* () {
2525
3312
  var _a;
2526
3313
  if (frame.data.length !== 4) {
3314
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2527
3315
  return;
2528
3316
  }
2529
3317
  if (!model.writeSingleRegister) {
2530
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3318
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2531
3319
  return;
2532
3320
  }
2533
3321
  const address = (frame.data[0] << 8) | frame.data[1];
2534
3322
  const value = (frame.data[2] << 8) | frame.data[3];
2535
3323
  if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
2536
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
3324
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2537
3325
  return;
2538
3326
  }
2539
3327
  try {
2540
3328
  yield model.writeSingleRegister(address, value);
2541
- yield response(this.applicationLayer.encode(frame));
3329
+ yield response(appLayer.encode(frame));
2542
3330
  }
2543
3331
  catch (error) {
2544
- yield this.responseError(frame, response, error);
3332
+ yield this.responseError(appLayer, frame, response, error);
2545
3333
  }
2546
3334
  });
2547
3335
  }
2548
- handleFC15(model, frame, response) {
3336
+ handleFC15(appLayer, model, frame, response) {
2549
3337
  return __awaiter(this, void 0, void 0, function* () {
2550
3338
  var _a;
2551
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));
2552
3341
  return;
2553
3342
  }
2554
3343
  if (!model.writeMultipleCoils && !model.writeSingleCoil) {
2555
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3344
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2556
3345
  return;
2557
3346
  }
2558
3347
  const address = (frame.data[0] << 8) | frame.data[1];
2559
3348
  const length = (frame.data[2] << 8) | frame.data[3];
2560
3349
  const byteCount = frame.data[4];
2561
3350
  if (length < LIMITS.READ_COILS_MIN || length > LIMITS.WRITE_COILS_MAX || byteCount !== Math.ceil(length / 8)) {
2562
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
3351
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2563
3352
  return;
2564
3353
  }
2565
3354
  if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
2566
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
3355
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2567
3356
  return;
2568
3357
  }
2569
3358
  const value = new Array(length);
@@ -2579,32 +3368,33 @@ class ModbusSlave extends EventEmitter {
2579
3368
  yield model.writeSingleCoil(address + i, value[i]);
2580
3369
  }
2581
3370
  }
2582
- 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) })));
2583
3372
  }
2584
3373
  catch (error) {
2585
- yield this.responseError(frame, response, error);
3374
+ yield this.responseError(appLayer, frame, response, error);
2586
3375
  }
2587
3376
  });
2588
3377
  }
2589
- handleFC16(model, frame, response) {
3378
+ handleFC16(appLayer, model, frame, response) {
2590
3379
  return __awaiter(this, void 0, void 0, function* () {
2591
3380
  var _a;
2592
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));
2593
3383
  return;
2594
3384
  }
2595
3385
  if (!model.writeMultipleRegisters && !model.writeSingleRegister) {
2596
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3386
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2597
3387
  return;
2598
3388
  }
2599
3389
  const address = (frame.data[0] << 8) | frame.data[1];
2600
3390
  const length = (frame.data[2] << 8) | frame.data[3];
2601
3391
  const byteCount = frame.data[4];
2602
3392
  if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.WRITE_REGISTERS_MAX || byteCount !== length * 2) {
2603
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
3393
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2604
3394
  return;
2605
3395
  }
2606
3396
  if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
2607
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
3397
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2608
3398
  return;
2609
3399
  }
2610
3400
  const value = new Array(length);
@@ -2620,21 +3410,22 @@ class ModbusSlave extends EventEmitter {
2620
3410
  yield model.writeSingleRegister(address + i, value[i]);
2621
3411
  }
2622
3412
  }
2623
- 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) })));
2624
3414
  }
2625
3415
  catch (error) {
2626
- yield this.responseError(frame, response, error);
3416
+ yield this.responseError(appLayer, frame, response, error);
2627
3417
  }
2628
3418
  });
2629
3419
  }
2630
- handleFC17(model, frame, response) {
3420
+ handleFC17(appLayer, model, frame, response) {
2631
3421
  return __awaiter(this, void 0, void 0, function* () {
2632
3422
  var _a;
2633
3423
  if (frame.data.length !== 0) {
3424
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2634
3425
  return;
2635
3426
  }
2636
3427
  if (!model.reportServerId) {
2637
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3428
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2638
3429
  return;
2639
3430
  }
2640
3431
  try {
@@ -2642,40 +3433,44 @@ class ModbusSlave extends EventEmitter {
2642
3433
  const serverIdBytes = serverId;
2643
3434
  const byteCount = serverIdBytes.length + 1 + additionalData.length;
2644
3435
  if (byteCount > 255) {
2645
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
3436
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
2646
3437
  return;
2647
3438
  }
2648
3439
  const allBytes = [...serverIdBytes, runIndicatorStatus ? 0xff : 0x00, ...additionalData];
2649
3440
  if (allBytes.some((b) => !isUint8(b))) {
2650
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
3441
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
2651
3442
  return;
2652
3443
  }
2653
- 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 })));
2654
3448
  }
2655
3449
  catch (error) {
2656
- yield this.responseError(frame, response, error);
3450
+ yield this.responseError(appLayer, frame, response, error);
2657
3451
  }
2658
3452
  });
2659
3453
  }
2660
- handleFC22(model, frame, response) {
3454
+ handleFC22(appLayer, model, frame, response) {
2661
3455
  return __awaiter(this, void 0, void 0, function* () {
2662
3456
  var _a;
2663
3457
  if (frame.data.length !== 6) {
3458
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2664
3459
  return;
2665
3460
  }
2666
3461
  if (!model.maskWriteRegister && !(model.readHoldingRegisters && model.writeSingleRegister)) {
2667
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3462
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2668
3463
  return;
2669
3464
  }
2670
3465
  const address = (frame.data[0] << 8) | frame.data[1];
2671
3466
  const andMask = (frame.data[2] << 8) | frame.data[3];
2672
3467
  const orMask = (frame.data[4] << 8) | frame.data[5];
2673
3468
  if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
2674
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
3469
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2675
3470
  return;
2676
3471
  }
2677
3472
  try {
2678
- yield this.withAddressLock([address], () => __awaiter(this, void 0, void 0, function* () {
3473
+ yield this._withAddressLock([address], () => __awaiter(this, void 0, void 0, function* () {
2679
3474
  if (model.maskWriteRegister) {
2680
3475
  yield model.maskWriteRegister(address, andMask, orMask);
2681
3476
  }
@@ -2684,21 +3479,22 @@ class ModbusSlave extends EventEmitter {
2684
3479
  yield model.writeSingleRegister(address, (value & andMask) | (orMask & (~andMask & 0xffff)));
2685
3480
  }
2686
3481
  }));
2687
- yield response(this.applicationLayer.encode(frame));
3482
+ yield response(appLayer.encode(frame));
2688
3483
  }
2689
3484
  catch (error) {
2690
- yield this.responseError(frame, response, error);
3485
+ yield this.responseError(appLayer, frame, response, error);
2691
3486
  }
2692
3487
  });
2693
3488
  }
2694
- handleFC23(model, frame, response) {
3489
+ handleFC23(appLayer, model, frame, response) {
2695
3490
  return __awaiter(this, void 0, void 0, function* () {
2696
3491
  var _a;
2697
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));
2698
3494
  return;
2699
3495
  }
2700
3496
  if (!model.readHoldingRegisters || (!model.writeMultipleRegisters && !model.writeSingleRegister)) {
2701
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3497
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2702
3498
  return;
2703
3499
  }
2704
3500
  const address = {
@@ -2715,11 +3511,11 @@ class ModbusSlave extends EventEmitter {
2715
3511
  length.write < LIMITS.READ_REGISTERS_MIN ||
2716
3512
  length.write > LIMITS.RW_REGISTERS_WRITE_MAX ||
2717
3513
  byteCount !== length.write * 2) {
2718
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
3514
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2719
3515
  return;
2720
3516
  }
2721
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)) {
2722
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
3518
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2723
3519
  return;
2724
3520
  }
2725
3521
  const value = new Array(length.write);
@@ -2728,7 +3524,7 @@ class ModbusSlave extends EventEmitter {
2728
3524
  }
2729
3525
  try {
2730
3526
  const writeAddresses = Array.from({ length: length.write }, (_, i) => address.write + i);
2731
- yield this.withAddressLock(writeAddresses, () => __awaiter(this, void 0, void 0, function* () {
3527
+ yield this._withAddressLock(writeAddresses, () => __awaiter(this, void 0, void 0, function* () {
2732
3528
  if (model.writeMultipleRegisters) {
2733
3529
  yield model.writeMultipleRegisters(address.write, value);
2734
3530
  }
@@ -2739,18 +3535,18 @@ class ModbusSlave extends EventEmitter {
2739
3535
  }
2740
3536
  }));
2741
3537
  const registers = yield model.readHoldingRegisters(address.read, length.read);
2742
- const bufferTx = Buffer.alloc(length.read * 2);
3538
+ const bufferTx = Buffer.allocUnsafe(length.read * 2);
2743
3539
  registers.forEach((register, index) => {
2744
3540
  bufferTx.writeUInt16BE(register, index * 2);
2745
3541
  });
2746
- 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) })));
2747
3543
  }
2748
3544
  catch (error) {
2749
- yield this.responseError(frame, response, error);
3545
+ yield this.responseError(appLayer, frame, response, error);
2750
3546
  }
2751
3547
  });
2752
3548
  }
2753
- handleFC43_14(model, frame, response) {
3549
+ handleFC43_14(appLayer, model, frame, response) {
2754
3550
  return __awaiter(this, void 0, void 0, function* () {
2755
3551
  if (frame.data.length === 3) {
2756
3552
  if (frame.data[0] === MEI_READ_DEVICE_ID && model.readDeviceIdentification) {
@@ -2777,13 +3573,13 @@ class ModbusSlave extends EventEmitter {
2777
3573
  }
2778
3574
  case ReadDeviceIDCode.SPECIFIC_ACCESS: {
2779
3575
  if (objectID > 0x06 && objectID < 0x80) {
2780
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
3576
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2781
3577
  return;
2782
3578
  }
2783
3579
  break;
2784
3580
  }
2785
3581
  default: {
2786
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
3582
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
2787
3583
  return;
2788
3584
  }
2789
3585
  }
@@ -2803,7 +3599,7 @@ class ModbusSlave extends EventEmitter {
2803
3599
  }
2804
3600
  if (!objects.has(objectID)) {
2805
3601
  if (readDeviceIDCode === ReadDeviceIDCode.SPECIFIC_ACCESS) {
2806
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
3602
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
2807
3603
  return;
2808
3604
  }
2809
3605
  objectID = 0x00;
@@ -2811,7 +3607,7 @@ class ModbusSlave extends EventEmitter {
2811
3607
  let maxId = 0;
2812
3608
  for (const id of objects.keys()) {
2813
3609
  if ((id >= 0x07 && id <= 0x7f) || id > 0xff) {
2814
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
3610
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
2815
3611
  return;
2816
3612
  }
2817
3613
  if (id > maxId) {
@@ -2828,97 +3624,104 @@ class ModbusSlave extends EventEmitter {
2828
3624
  }
2829
3625
  const byteLength = Buffer.byteLength(value);
2830
3626
  if (byteLength > 245) {
2831
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
3627
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
2832
3628
  return;
2833
3629
  }
2834
3630
  if (lastID !== 0) {
2835
3631
  continue;
2836
3632
  }
2837
3633
  if (byteLength + 2 > 253 - totalLength) {
2838
- if (lastID === 0) {
2839
- lastID = id;
2840
- }
3634
+ lastID = id;
2841
3635
  }
2842
3636
  else {
2843
3637
  totalLength += byteLength + 2;
2844
- ids.push(id);
3638
+ ids.push({ id, byteLength });
2845
3639
  if (readDeviceIDCode === ReadDeviceIDCode.SPECIFIC_ACCESS) {
2846
3640
  break;
2847
3641
  }
2848
3642
  }
2849
3643
  }
2850
- ids.sort((a, b) => a - b);
2851
- const parts = [];
2852
- parts.push(Buffer.from([MEI_READ_DEVICE_ID, readDeviceIDCode, conformityLevel, lastID === 0 ? 0x00 : 0xff, lastID, ids.length]));
2853
- 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) {
2854
3658
  const value = objects.get(id);
2855
- parts.push(Buffer.from([id, Buffer.byteLength(value)]));
2856
- parts.push(Buffer.from(value));
3659
+ data[offset++] = id;
3660
+ data[offset++] = byteLength;
3661
+ offset += data.write(value, offset);
2857
3662
  }
2858
- 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 })));
2859
3664
  }
2860
3665
  catch (error) {
2861
- yield this.responseError(frame, response, error);
3666
+ yield this.responseError(appLayer, frame, response, error);
2862
3667
  }
2863
3668
  }
2864
3669
  else {
2865
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3670
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
2866
3671
  }
2867
3672
  }
2868
3673
  });
2869
3674
  }
2870
- responseError(frame, response, error) {
3675
+ responseError(appLayer, frame, response, error) {
2871
3676
  return __awaiter(this, void 0, void 0, function* () {
2872
- 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)]) })));
2873
3678
  });
2874
3679
  }
2875
- _drain(key, q) {
3680
+ _drain(appLayer, q) {
2876
3681
  return __awaiter(this, void 0, void 0, function* () {
2877
- if (q.processing) {
3682
+ if (q.processing || this._physicalLayer.state !== PhysicalState.OPEN) {
2878
3683
  return;
2879
3684
  }
2880
3685
  q.processing = true;
2881
3686
  try {
2882
- while (q.items.length > 0) {
2883
- const { frame, response } = q.items.shift();
2884
- try {
2885
- yield this._processFrame(frame, response);
2886
- }
2887
- catch (error) {
2888
- this.emit('error', error);
3687
+ while (q.frames.length > 0) {
3688
+ if (this._physicalLayer.state !== PhysicalState.OPEN) {
3689
+ return;
2889
3690
  }
3691
+ const frame = q.frames.shift();
3692
+ yield this._processFrame(appLayer, frame);
2890
3693
  }
2891
3694
  }
2892
3695
  finally {
2893
3696
  q.processing = false;
2894
- // Cleanup empty queue entries so the map doesn't grow unbounded across
2895
- // ephemeral connections (UDP rinfo, brief TCP clients).
2896
- if (q.items.length === 0 && this._queues.get(key) === q) {
2897
- this._queues.delete(key);
2898
- }
2899
3697
  }
2900
3698
  });
2901
3699
  }
2902
- _processFrame(frame, _response) {
3700
+ _processFrame(appLayer, frame) {
2903
3701
  return __awaiter(this, void 0, void 0, function* () {
2904
3702
  const response = (data) => __awaiter(this, void 0, void 0, function* () {
2905
- if (frame.unit === 0x00) {
3703
+ if (frame.unit === 0x00 || this._physicalLayer.state !== PhysicalState.OPEN) {
2906
3704
  return;
2907
3705
  }
2908
3706
  try {
2909
- 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. */
2910
3711
  }
2911
- catch (error) { }
2912
3712
  });
2913
3713
  for (const model of frame.unit === 0x00 ? this.models.values() : [this.models.get(frame.unit)]) {
2914
- 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);
2915
3718
  if (!intercepted) {
2916
- yield this._handleFC(model, frame, response);
3719
+ yield this._handleFC(appLayer, model, frame, response);
2917
3720
  }
2918
3721
  }
2919
3722
  });
2920
3723
  }
2921
- _intercept(model, frame, response) {
3724
+ _intercept(appLayer, model, frame, response) {
2922
3725
  return __awaiter(this, void 0, void 0, function* () {
2923
3726
  if (!model.interceptor) {
2924
3727
  return false;
@@ -2926,18 +3729,18 @@ class ModbusSlave extends EventEmitter {
2926
3729
  try {
2927
3730
  const data = yield model.interceptor(frame.fc, frame.data);
2928
3731
  if (data !== undefined) {
2929
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data })));
3732
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data })));
2930
3733
  return true;
2931
3734
  }
2932
3735
  return false;
2933
3736
  }
2934
3737
  catch (error) {
2935
- yield this.responseError(frame, response, error);
3738
+ yield this.responseError(appLayer, frame, response, error);
2936
3739
  return true;
2937
3740
  }
2938
3741
  });
2939
3742
  }
2940
- withAddressLock(addresses, fn) {
3743
+ _withAddressLock(addresses, fn) {
2941
3744
  return __awaiter(this, void 0, void 0, function* () {
2942
3745
  const sorted = [...new Set(addresses)].sort((a, b) => a - b);
2943
3746
  const previous = sorted.map((addr) => { var _a; return (_a = this._locks.get(addr)) !== null && _a !== void 0 ? _a : Promise.resolve(); });
@@ -2960,55 +3763,55 @@ class ModbusSlave extends EventEmitter {
2960
3763
  }
2961
3764
  });
2962
3765
  }
2963
- _handleFC(model, frame, response) {
3766
+ _handleFC(appLayer, model, frame, response) {
2964
3767
  return __awaiter(this, void 0, void 0, function* () {
2965
3768
  switch (frame.fc) {
2966
3769
  case FunctionCode.READ_COILS: {
2967
- yield this.handleFC1(model, frame, response);
3770
+ yield this.handleFC1(appLayer, model, frame, response);
2968
3771
  break;
2969
3772
  }
2970
3773
  case FunctionCode.READ_DISCRETE_INPUTS: {
2971
- yield this.handleFC2(model, frame, response);
3774
+ yield this.handleFC2(appLayer, model, frame, response);
2972
3775
  break;
2973
3776
  }
2974
3777
  case FunctionCode.READ_HOLDING_REGISTERS: {
2975
- yield this.handleFC3(model, frame, response);
3778
+ yield this.handleFC3(appLayer, model, frame, response);
2976
3779
  break;
2977
3780
  }
2978
3781
  case FunctionCode.READ_INPUT_REGISTERS: {
2979
- yield this.handleFC4(model, frame, response);
3782
+ yield this.handleFC4(appLayer, model, frame, response);
2980
3783
  break;
2981
3784
  }
2982
3785
  case FunctionCode.WRITE_SINGLE_COIL: {
2983
- yield this.handleFC5(model, frame, response);
3786
+ yield this.handleFC5(appLayer, model, frame, response);
2984
3787
  break;
2985
3788
  }
2986
3789
  case FunctionCode.WRITE_SINGLE_REGISTER: {
2987
- yield this.handleFC6(model, frame, response);
3790
+ yield this.handleFC6(appLayer, model, frame, response);
2988
3791
  break;
2989
3792
  }
2990
3793
  case FunctionCode.WRITE_MULTIPLE_COILS: {
2991
- yield this.handleFC15(model, frame, response);
3794
+ yield this.handleFC15(appLayer, model, frame, response);
2992
3795
  break;
2993
3796
  }
2994
3797
  case FunctionCode.WRITE_MULTIPLE_REGISTERS: {
2995
- yield this.handleFC16(model, frame, response);
3798
+ yield this.handleFC16(appLayer, model, frame, response);
2996
3799
  break;
2997
3800
  }
2998
3801
  case FunctionCode.REPORT_SERVER_ID: {
2999
- yield this.handleFC17(model, frame, response);
3802
+ yield this.handleFC17(appLayer, model, frame, response);
3000
3803
  break;
3001
3804
  }
3002
3805
  case FunctionCode.MASK_WRITE_REGISTER: {
3003
- yield this.handleFC22(model, frame, response);
3806
+ yield this.handleFC22(appLayer, model, frame, response);
3004
3807
  break;
3005
3808
  }
3006
3809
  case FunctionCode.READ_WRITE_MULTIPLE_REGISTERS: {
3007
- yield this.handleFC23(model, frame, response);
3810
+ yield this.handleFC23(appLayer, model, frame, response);
3008
3811
  break;
3009
3812
  }
3010
3813
  case FunctionCode.READ_DEVICE_IDENTIFICATION: {
3011
- yield this.handleFC43_14(model, frame, response);
3814
+ yield this.handleFC43_14(appLayer, model, frame, response);
3012
3815
  break;
3013
3816
  }
3014
3817
  default: {
@@ -3016,14 +3819,14 @@ class ModbusSlave extends EventEmitter {
3016
3819
  if (cfc === null || cfc === void 0 ? void 0 : cfc.handle) {
3017
3820
  try {
3018
3821
  const responseData = yield cfc.handle(frame.data, frame.unit);
3019
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: responseData })));
3822
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: responseData })));
3020
3823
  }
3021
3824
  catch (error) {
3022
- yield this.responseError(frame, response, error);
3825
+ yield this.responseError(appLayer, frame, response, error);
3023
3826
  }
3024
3827
  }
3025
3828
  else {
3026
- yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3829
+ yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
3027
3830
  }
3028
3831
  break;
3029
3832
  }
@@ -3039,50 +3842,66 @@ class ModbusSlave extends EventEmitter {
3039
3842
  }
3040
3843
  addCustomFunctionCode(cfc) {
3041
3844
  this._customFunctionCodes.set(cfc.fc, cfc);
3042
- this.applicationLayer.addCustomFunctionCode(cfc);
3845
+ for (const appLayer of this._appLayers.keys()) {
3846
+ appLayer.addCustomFunctionCode(cfc);
3847
+ }
3043
3848
  }
3044
3849
  removeCustomFunctionCode(fc) {
3045
3850
  this._customFunctionCodes.delete(fc);
3046
- this.applicationLayer.removeCustomFunctionCode(fc);
3047
- }
3048
- _clean(level) {
3049
- if (this._cleanLevel === 'destroy') {
3050
- return;
3051
- }
3052
- if (this._cleanLevel === 'close' && level === 'close') {
3053
- return;
3054
- }
3055
- for (const q of this._queues.values()) {
3056
- q.items = [];
3057
- }
3058
- this._queues.clear();
3059
- this._locks.clear();
3060
- if (level === 'destroy') {
3061
- this._customFunctionCodes.clear();
3062
- 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'));
3063
3866
  }
3064
- this._cleanLevel = level;
3867
+ return this._physicalLayer.open(...args);
3065
3868
  }
3066
- open(...args) {
3067
- this._cleanLevel = 'none';
3068
- 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
+ });
3069
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
+ */
3070
3896
  close() {
3071
- if (this._cleanLevel === 'destroy') {
3072
- return Promise.resolve();
3073
- }
3074
- this._clean('close');
3075
- return this.physicalLayer.close();
3076
- }
3077
- destroy() {
3078
- if (this._cleanLevel === 'destroy') {
3079
- return Promise.resolve();
3080
- }
3081
- this._clean('destroy');
3082
- this.removeAllListeners();
3083
- this.applicationLayer.destroy();
3084
- 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
+ });
3085
3904
  }
3086
3905
  }
3087
3906
 
3088
- 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 };