njs-modbus 2.0.1 → 3.0.2

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