njs-modbus 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,7 @@ A pure JavaScript implementation of MODBUS for NodeJS.
8
8
  [![npm download](http://img.shields.io/npm/dw/njs-modbus.svg?style=flat-square)](http://www.npm-stats.com/~packages/njs-modbus)
9
9
  [![npm latest package](http://img.shields.io/npm/v/njs-modbus/latest.svg?style=flat-square)](https://www.npmjs.com/package/njs-modbus)
10
10
  [![npm bundle size](https://img.shields.io/bundlephobia/minzip/njs-modbus?style=flat-square)](https://bundlephobia.com/package/njs-modbus)
11
+ [![CI](https://img.shields.io/github/actions/workflow/status/xiejay97/njs-modbus/ci.yml?branch=main&style=flat-square)](https://github.com/xiejay97/njs-modbus/actions)
11
12
  <!-- prettier-ignore-end -->
12
13
 
13
14
  </div>
@@ -188,7 +189,7 @@ If you broadcast over serial (RTU or ASCII), per Modbus over Serial Line V1.02
188
189
  const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
189
190
 
190
191
  await master.writeSingleRegister(0, 0x0000, 0x1234); // broadcast
191
- await sleep(100); // turnaround — tune per devices
192
+ await sleep(100); // turnaround — tune per devices
192
193
  await master.writeSingleRegister(1, 0x0000, 0x5678); // unicast to next slave
193
194
  ```
194
195
 
package/dist/index.cjs CHANGED
@@ -423,6 +423,9 @@ class SerialPhysicalLayer extends AbstractPhysicalLayer {
423
423
  });
424
424
  }
425
425
  destroy() {
426
+ if (this._destroyed) {
427
+ return Promise.resolve();
428
+ }
426
429
  this._destroyed = true;
427
430
  return this.close().then(() => {
428
431
  this.removeAllListeners();
@@ -562,6 +565,9 @@ class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
562
565
  });
563
566
  }
564
567
  destroy() {
568
+ if (this._destroyed) {
569
+ return Promise.resolve();
570
+ }
565
571
  this._destroyed = true;
566
572
  return this.close().then(() => {
567
573
  this.removeAllListeners();
@@ -711,10 +717,18 @@ class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
711
717
  for (const [socket] of this._connections) {
712
718
  socket.destroy();
713
719
  }
714
- server.close();
720
+ try {
721
+ server.close();
722
+ }
723
+ catch (_a) {
724
+ resolve();
725
+ }
715
726
  });
716
727
  }
717
728
  destroy() {
729
+ if (this._destroyed) {
730
+ return Promise.resolve();
731
+ }
718
732
  this._destroyed = true;
719
733
  return this.close().then(() => {
720
734
  this.removeAllListeners();
@@ -945,6 +959,9 @@ class UdpPhysicalLayer extends AbstractPhysicalLayer {
945
959
  });
946
960
  }
947
961
  destroy() {
962
+ if (this._destroyed) {
963
+ return Promise.resolve();
964
+ }
948
965
  this._destroyed = true;
949
966
  return this.close().then(() => {
950
967
  this.removeAllListeners();
@@ -1024,6 +1041,12 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
1024
1041
  writable: true,
1025
1042
  value: void 0
1026
1043
  });
1044
+ Object.defineProperty(this, "_destroyed", {
1045
+ enumerable: true,
1046
+ configurable: true,
1047
+ writable: true,
1048
+ value: false
1049
+ });
1027
1050
  const { intervalBetweenFrames, interCharTimeout } = options;
1028
1051
  let threePointFiveT = 0;
1029
1052
  let onePointFiveT = 0;
@@ -1274,14 +1297,20 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
1274
1297
  return buffer;
1275
1298
  }
1276
1299
  destroy() {
1300
+ if (this._destroyed) {
1301
+ return;
1302
+ }
1303
+ this._destroyed = true;
1277
1304
  this.removeAllListeners();
1278
1305
  for (const removeListener of this._removeAllListeners) {
1279
1306
  removeListener();
1280
1307
  }
1308
+ this._removeAllListeners = [];
1281
1309
  for (const state of this._states.values()) {
1282
1310
  this.clearStateTimers(state);
1283
1311
  }
1284
1312
  this._states.clear();
1313
+ this._customFunctionCodes.clear();
1285
1314
  }
1286
1315
  }
1287
1316
 
@@ -1334,6 +1363,12 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
1334
1363
  writable: true,
1335
1364
  value: []
1336
1365
  });
1366
+ Object.defineProperty(this, "_destroyed", {
1367
+ enumerable: true,
1368
+ configurable: true,
1369
+ writable: true,
1370
+ value: false
1371
+ });
1337
1372
  this.lenientHex = (_a = options.lenientHex) !== null && _a !== void 0 ? _a : false;
1338
1373
  const lenientHex = this.lenientHex;
1339
1374
  const isHexChar = (value) => {
@@ -1471,10 +1506,15 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
1471
1506
  return out;
1472
1507
  }
1473
1508
  destroy() {
1509
+ if (this._destroyed) {
1510
+ return;
1511
+ }
1512
+ this._destroyed = true;
1474
1513
  this.removeAllListeners();
1475
1514
  for (const removeListener of this._removeAllListeners) {
1476
1515
  removeListener();
1477
1516
  }
1517
+ this._removeAllListeners = [];
1478
1518
  this._states.clear();
1479
1519
  }
1480
1520
  }
@@ -1508,6 +1548,12 @@ class TcpApplicationLayer extends AbstractApplicationLayer {
1508
1548
  writable: true,
1509
1549
  value: []
1510
1550
  });
1551
+ Object.defineProperty(this, "_destroyed", {
1552
+ enumerable: true,
1553
+ configurable: true,
1554
+ writable: true,
1555
+ value: false
1556
+ });
1511
1557
  const handleData = (data, response, connection) => {
1512
1558
  var _a;
1513
1559
  let buffer = (_a = this._buffers.get(connection.id)) !== null && _a !== void 0 ? _a : EMPTY_BUFFER;
@@ -1590,10 +1636,15 @@ class TcpApplicationLayer extends AbstractApplicationLayer {
1590
1636
  return buffer;
1591
1637
  }
1592
1638
  destroy() {
1639
+ if (this._destroyed) {
1640
+ return;
1641
+ }
1642
+ this._destroyed = true;
1593
1643
  this.removeAllListeners();
1594
1644
  for (const removeListener of this._removeAllListeners) {
1595
1645
  removeListener();
1596
1646
  }
1647
+ this._removeAllListeners = [];
1597
1648
  this._buffers.clear();
1598
1649
  }
1599
1650
  }
@@ -1704,7 +1755,7 @@ class ModbusMaster extends EventEmitter {
1704
1755
  return this.physicalLayer.isOpen;
1705
1756
  }
1706
1757
  get destroyed() {
1707
- return this.physicalLayer.destroyed;
1758
+ return this._cleanLevel === 'destroy' || this.physicalLayer.destroyed;
1708
1759
  }
1709
1760
  constructor(applicationLayer, physicalLayer, options = {}) {
1710
1761
  var _a, _b;
@@ -1745,11 +1796,11 @@ class ModbusMaster extends EventEmitter {
1745
1796
  writable: true,
1746
1797
  value: 1
1747
1798
  });
1748
- Object.defineProperty(this, "_closed", {
1799
+ Object.defineProperty(this, "_cleanLevel", {
1749
1800
  enumerable: true,
1750
1801
  configurable: true,
1751
1802
  writable: true,
1752
- value: false
1803
+ value: 'none'
1753
1804
  });
1754
1805
  Object.defineProperty(this, "timeout", {
1755
1806
  enumerable: true,
@@ -1903,7 +1954,7 @@ class ModbusMaster extends EventEmitter {
1903
1954
  }
1904
1955
  _exchange(adu, preCheck, timeout, broadcast) {
1905
1956
  return new Promise((resolve, reject) => {
1906
- if (this._closed) {
1957
+ if (this._cleanLevel !== 'none') {
1907
1958
  reject(new ModbusError(ModbusErrorCode.MASTER_CLOSED, 'Master closed'));
1908
1959
  return;
1909
1960
  }
@@ -2056,7 +2107,7 @@ class ModbusMaster extends EventEmitter {
2056
2107
  if (frame) {
2057
2108
  const runStatusIndex = 1 + serverIdLength;
2058
2109
  return Object.assign(Object.assign({}, frame), { data: {
2059
- serverId: serverIdLength === 1 ? frame.data[1] : Array.from(frame.data.subarray(1, runStatusIndex)),
2110
+ serverId: Array.from(frame.data.subarray(1, runStatusIndex)),
2060
2111
  runIndicatorStatus: frame.data[runStatusIndex] === 0xff,
2061
2112
  additionalData: Array.from(frame.data.subarray(runStatusIndex + 1)),
2062
2113
  } });
@@ -2164,26 +2215,42 @@ class ModbusMaster extends EventEmitter {
2164
2215
  }
2165
2216
  });
2166
2217
  }
2218
+ _clean(level) {
2219
+ if (this._cleanLevel === 'destroy') {
2220
+ return;
2221
+ }
2222
+ if (this._cleanLevel === 'close' && level === 'close') {
2223
+ return;
2224
+ }
2225
+ const errorCode = level === 'destroy' ? ModbusErrorCode.MASTER_DESTROYED : ModbusErrorCode.MASTER_CLOSED;
2226
+ const message = level === 'destroy' ? 'Master destroyed' : 'Master closed';
2227
+ const queued = this._queue.splice(0);
2228
+ for (const item of queued) {
2229
+ item.cancel(new ModbusError(errorCode, message));
2230
+ }
2231
+ this._masterSession.stopAll(new ModbusError(errorCode, message));
2232
+ this._cleanLevel = level;
2233
+ }
2167
2234
  open(...args) {
2168
- this._closed = false;
2235
+ if (this._cleanLevel === 'destroy') {
2236
+ return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Master is destroyed'));
2237
+ }
2238
+ this._cleanLevel = 'none';
2239
+ this._nextTid = 1;
2169
2240
  return this.physicalLayer.open(...args);
2170
2241
  }
2171
2242
  close() {
2172
- this._closed = true;
2173
- const queued = this._queue.splice(0);
2174
- for (const item of queued) {
2175
- item.cancel(new ModbusError(ModbusErrorCode.MASTER_CLOSED, 'Master closed'));
2243
+ if (this._cleanLevel === 'destroy') {
2244
+ return Promise.resolve();
2176
2245
  }
2177
- this._masterSession.stopAll(new ModbusError(ModbusErrorCode.MASTER_CLOSED, 'Master closed'));
2246
+ this._clean('close');
2178
2247
  return this.physicalLayer.close();
2179
2248
  }
2180
2249
  destroy() {
2181
- this._closed = true;
2182
- const queued = this._queue.splice(0);
2183
- for (const item of queued) {
2184
- item.cancel(new ModbusError(ModbusErrorCode.MASTER_DESTROYED, 'Master destroyed'));
2250
+ if (this._cleanLevel === 'destroy') {
2251
+ return Promise.resolve();
2185
2252
  }
2186
- this._masterSession.stopAll(new ModbusError(ModbusErrorCode.MASTER_DESTROYED, 'Master destroyed'));
2253
+ this._clean('destroy');
2187
2254
  this.removeAllListeners();
2188
2255
  this.applicationLayer.destroy();
2189
2256
  return this.physicalLayer.destroy();
@@ -2195,7 +2262,7 @@ class ModbusSlave extends EventEmitter {
2195
2262
  return this.physicalLayer.isOpen;
2196
2263
  }
2197
2264
  get destroyed() {
2198
- return this.physicalLayer.destroyed;
2265
+ return this._cleanLevel === 'destroy' || this.physicalLayer.destroyed;
2199
2266
  }
2200
2267
  constructor(applicationLayer, physicalLayer, options = {}) {
2201
2268
  var _a;
@@ -2242,6 +2309,12 @@ class ModbusSlave extends EventEmitter {
2242
2309
  writable: true,
2243
2310
  value: new Map()
2244
2311
  });
2312
+ Object.defineProperty(this, "_cleanLevel", {
2313
+ enumerable: true,
2314
+ configurable: true,
2315
+ writable: true,
2316
+ value: 'none'
2317
+ });
2245
2318
  this.concurrent = (_a = options.concurrent) !== null && _a !== void 0 ? _a : false;
2246
2319
  if (this.concurrent && this.applicationLayer.PROTOCOL !== 'TCP') {
2247
2320
  throw new ModbusError(ModbusErrorCode.CONCURRENT_NOT_TCP, 'concurrent mode requires a Modbus TCP application layer');
@@ -2271,14 +2344,14 @@ class ModbusSlave extends EventEmitter {
2271
2344
  if (!q) {
2272
2345
  return;
2273
2346
  }
2274
- q.items.length = 0;
2347
+ q.items = [];
2275
2348
  if (!q.processing) {
2276
2349
  this._queues.delete(connection.id);
2277
2350
  }
2278
2351
  });
2279
2352
  physicalLayer.on('close', () => {
2280
2353
  for (const q of this._queues.values()) {
2281
- q.items.length = 0;
2354
+ q.items = [];
2282
2355
  }
2283
2356
  this._queues.clear();
2284
2357
  this.emit('close');
@@ -2567,8 +2640,8 @@ class ModbusSlave extends EventEmitter {
2567
2640
  return;
2568
2641
  }
2569
2642
  try {
2570
- const { serverId = (_a = model.unit) !== null && _a !== void 0 ? _a : 1, runIndicatorStatus = true, additionalData = [] } = yield model.reportServerId();
2571
- const serverIdBytes = Array.isArray(serverId) ? serverId : [serverId];
2643
+ const { serverId = [(_a = model.unit) !== null && _a !== void 0 ? _a : 1], runIndicatorStatus = true, additionalData = [] } = yield model.reportServerId();
2644
+ const serverIdBytes = serverId;
2572
2645
  const byteCount = serverIdBytes.length + 1 + additionalData.length;
2573
2646
  if (byteCount > 255) {
2574
2647
  yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
@@ -2974,14 +3047,40 @@ class ModbusSlave extends EventEmitter {
2974
3047
  this._customFunctionCodes.delete(fc);
2975
3048
  this.applicationLayer.removeCustomFunctionCode(fc);
2976
3049
  }
3050
+ _clean(level) {
3051
+ if (this._cleanLevel === 'destroy') {
3052
+ return;
3053
+ }
3054
+ if (this._cleanLevel === 'close' && level === 'close') {
3055
+ return;
3056
+ }
3057
+ for (const q of this._queues.values()) {
3058
+ q.items = [];
3059
+ }
3060
+ this._queues.clear();
3061
+ this._locks.clear();
3062
+ if (level === 'destroy') {
3063
+ this._customFunctionCodes.clear();
3064
+ this.models.clear();
3065
+ }
3066
+ this._cleanLevel = level;
3067
+ }
2977
3068
  open(...args) {
3069
+ this._cleanLevel = 'none';
2978
3070
  return this.physicalLayer.open(...args);
2979
3071
  }
2980
3072
  close() {
3073
+ if (this._cleanLevel === 'destroy') {
3074
+ return Promise.resolve();
3075
+ }
3076
+ this._clean('close');
2981
3077
  return this.physicalLayer.close();
2982
3078
  }
2983
3079
  destroy() {
2984
- this._locks.clear();
3080
+ if (this._cleanLevel === 'destroy') {
3081
+ return Promise.resolve();
3082
+ }
3083
+ this._clean('destroy');
2985
3084
  this.removeAllListeners();
2986
3085
  this.applicationLayer.destroy();
2987
3086
  return this.physicalLayer.destroy();
package/dist/index.d.ts CHANGED
@@ -12,7 +12,7 @@ interface ApplicationDataUnit {
12
12
  }
13
13
  interface ServerId {
14
14
  /** Server ID; may be 1 byte (number) or multi-byte (number[]) per device spec. */
15
- serverId?: number | number[];
15
+ serverId?: number[];
16
16
  runIndicatorStatus?: boolean;
17
17
  additionalData?: number[];
18
18
  }
@@ -313,6 +313,7 @@ declare class AsciiApplicationLayer extends AbstractApplicationLayer {
313
313
  readonly lenientHex: boolean;
314
314
  private _states;
315
315
  private _removeAllListeners;
316
+ private _destroyed;
316
317
  constructor(physicalLayer: SerialPhysicalLayer | TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer, options?: AsciiApplicationLayerOptions);
317
318
  private getState;
318
319
  flush(): void;
@@ -362,6 +363,7 @@ declare class RtuApplicationLayer extends AbstractApplicationLayer {
362
363
  private _removeAllListeners;
363
364
  private _threePointFiveT;
364
365
  private _onePointFiveT;
366
+ private _destroyed;
365
367
  constructor(physicalLayer: SerialPhysicalLayer | TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer, options?: RtuApplicationLayerOptions);
366
368
  private getState;
367
369
  private clearStateTimers;
@@ -383,6 +385,7 @@ declare class TcpApplicationLayer extends AbstractApplicationLayer {
383
385
  private _transactionId;
384
386
  private _buffers;
385
387
  private _removeAllListeners;
388
+ private _destroyed;
386
389
  constructor(physicalLayer: TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer);
387
390
  private tryExtract;
388
391
  private processFrame;
@@ -418,7 +421,7 @@ declare class ModbusMaster<A extends AbstractApplicationLayer, P extends Abstrac
418
421
  private _queue;
419
422
  private _draining;
420
423
  private _nextTid;
421
- private _closed;
424
+ private _cleanLevel;
422
425
  timeout: number;
423
426
  readonly concurrent: boolean;
424
427
  get isOpen(): boolean;
@@ -484,6 +487,7 @@ declare class ModbusMaster<A extends AbstractApplicationLayer, P extends Abstrac
484
487
  removeCustomFunctionCode(fc: number): void;
485
488
  sendCustomFC(unit: 0, fc: number, data: Buffer | number[], timeout?: number): Promise<void>;
486
489
  sendCustomFC(unit: number, fc: number, data: Buffer | number[], timeout?: number): Promise<Buffer>;
490
+ private _clean;
487
491
  open(...args: Parameters<P['open']>): Promise<void>;
488
492
  close(): Promise<void>;
489
493
  destroy(): Promise<void>;
@@ -563,6 +567,7 @@ declare class ModbusSlave<A extends AbstractApplicationLayer, P extends Abstract
563
567
  private _queues;
564
568
  private _customFunctionCodes;
565
569
  private _locks;
570
+ private _cleanLevel;
566
571
  get isOpen(): boolean;
567
572
  get destroyed(): boolean;
568
573
  constructor(applicationLayer: A, physicalLayer: P, options?: ModbusSlaveOptions);
@@ -588,6 +593,7 @@ declare class ModbusSlave<A extends AbstractApplicationLayer, P extends Abstract
588
593
  remove(unit: number): void;
589
594
  addCustomFunctionCode(cfc: CustomFunctionCode): void;
590
595
  removeCustomFunctionCode(fc: number): void;
596
+ private _clean;
591
597
  open(...args: Parameters<P['open']>): Promise<void>;
592
598
  close(): Promise<void>;
593
599
  destroy(): Promise<void>;
package/dist/index.mjs CHANGED
@@ -421,6 +421,9 @@ class SerialPhysicalLayer extends AbstractPhysicalLayer {
421
421
  });
422
422
  }
423
423
  destroy() {
424
+ if (this._destroyed) {
425
+ return Promise.resolve();
426
+ }
424
427
  this._destroyed = true;
425
428
  return this.close().then(() => {
426
429
  this.removeAllListeners();
@@ -560,6 +563,9 @@ class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
560
563
  });
561
564
  }
562
565
  destroy() {
566
+ if (this._destroyed) {
567
+ return Promise.resolve();
568
+ }
563
569
  this._destroyed = true;
564
570
  return this.close().then(() => {
565
571
  this.removeAllListeners();
@@ -709,10 +715,18 @@ class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
709
715
  for (const [socket] of this._connections) {
710
716
  socket.destroy();
711
717
  }
712
- server.close();
718
+ try {
719
+ server.close();
720
+ }
721
+ catch (_a) {
722
+ resolve();
723
+ }
713
724
  });
714
725
  }
715
726
  destroy() {
727
+ if (this._destroyed) {
728
+ return Promise.resolve();
729
+ }
716
730
  this._destroyed = true;
717
731
  return this.close().then(() => {
718
732
  this.removeAllListeners();
@@ -943,6 +957,9 @@ class UdpPhysicalLayer extends AbstractPhysicalLayer {
943
957
  });
944
958
  }
945
959
  destroy() {
960
+ if (this._destroyed) {
961
+ return Promise.resolve();
962
+ }
946
963
  this._destroyed = true;
947
964
  return this.close().then(() => {
948
965
  this.removeAllListeners();
@@ -1022,6 +1039,12 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
1022
1039
  writable: true,
1023
1040
  value: void 0
1024
1041
  });
1042
+ Object.defineProperty(this, "_destroyed", {
1043
+ enumerable: true,
1044
+ configurable: true,
1045
+ writable: true,
1046
+ value: false
1047
+ });
1025
1048
  const { intervalBetweenFrames, interCharTimeout } = options;
1026
1049
  let threePointFiveT = 0;
1027
1050
  let onePointFiveT = 0;
@@ -1272,14 +1295,20 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
1272
1295
  return buffer;
1273
1296
  }
1274
1297
  destroy() {
1298
+ if (this._destroyed) {
1299
+ return;
1300
+ }
1301
+ this._destroyed = true;
1275
1302
  this.removeAllListeners();
1276
1303
  for (const removeListener of this._removeAllListeners) {
1277
1304
  removeListener();
1278
1305
  }
1306
+ this._removeAllListeners = [];
1279
1307
  for (const state of this._states.values()) {
1280
1308
  this.clearStateTimers(state);
1281
1309
  }
1282
1310
  this._states.clear();
1311
+ this._customFunctionCodes.clear();
1283
1312
  }
1284
1313
  }
1285
1314
 
@@ -1332,6 +1361,12 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
1332
1361
  writable: true,
1333
1362
  value: []
1334
1363
  });
1364
+ Object.defineProperty(this, "_destroyed", {
1365
+ enumerable: true,
1366
+ configurable: true,
1367
+ writable: true,
1368
+ value: false
1369
+ });
1335
1370
  this.lenientHex = (_a = options.lenientHex) !== null && _a !== void 0 ? _a : false;
1336
1371
  const lenientHex = this.lenientHex;
1337
1372
  const isHexChar = (value) => {
@@ -1469,10 +1504,15 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
1469
1504
  return out;
1470
1505
  }
1471
1506
  destroy() {
1507
+ if (this._destroyed) {
1508
+ return;
1509
+ }
1510
+ this._destroyed = true;
1472
1511
  this.removeAllListeners();
1473
1512
  for (const removeListener of this._removeAllListeners) {
1474
1513
  removeListener();
1475
1514
  }
1515
+ this._removeAllListeners = [];
1476
1516
  this._states.clear();
1477
1517
  }
1478
1518
  }
@@ -1506,6 +1546,12 @@ class TcpApplicationLayer extends AbstractApplicationLayer {
1506
1546
  writable: true,
1507
1547
  value: []
1508
1548
  });
1549
+ Object.defineProperty(this, "_destroyed", {
1550
+ enumerable: true,
1551
+ configurable: true,
1552
+ writable: true,
1553
+ value: false
1554
+ });
1509
1555
  const handleData = (data, response, connection) => {
1510
1556
  var _a;
1511
1557
  let buffer = (_a = this._buffers.get(connection.id)) !== null && _a !== void 0 ? _a : EMPTY_BUFFER;
@@ -1588,10 +1634,15 @@ class TcpApplicationLayer extends AbstractApplicationLayer {
1588
1634
  return buffer;
1589
1635
  }
1590
1636
  destroy() {
1637
+ if (this._destroyed) {
1638
+ return;
1639
+ }
1640
+ this._destroyed = true;
1591
1641
  this.removeAllListeners();
1592
1642
  for (const removeListener of this._removeAllListeners) {
1593
1643
  removeListener();
1594
1644
  }
1645
+ this._removeAllListeners = [];
1595
1646
  this._buffers.clear();
1596
1647
  }
1597
1648
  }
@@ -1702,7 +1753,7 @@ class ModbusMaster extends EventEmitter {
1702
1753
  return this.physicalLayer.isOpen;
1703
1754
  }
1704
1755
  get destroyed() {
1705
- return this.physicalLayer.destroyed;
1756
+ return this._cleanLevel === 'destroy' || this.physicalLayer.destroyed;
1706
1757
  }
1707
1758
  constructor(applicationLayer, physicalLayer, options = {}) {
1708
1759
  var _a, _b;
@@ -1743,11 +1794,11 @@ class ModbusMaster extends EventEmitter {
1743
1794
  writable: true,
1744
1795
  value: 1
1745
1796
  });
1746
- Object.defineProperty(this, "_closed", {
1797
+ Object.defineProperty(this, "_cleanLevel", {
1747
1798
  enumerable: true,
1748
1799
  configurable: true,
1749
1800
  writable: true,
1750
- value: false
1801
+ value: 'none'
1751
1802
  });
1752
1803
  Object.defineProperty(this, "timeout", {
1753
1804
  enumerable: true,
@@ -1901,7 +1952,7 @@ class ModbusMaster extends EventEmitter {
1901
1952
  }
1902
1953
  _exchange(adu, preCheck, timeout, broadcast) {
1903
1954
  return new Promise((resolve, reject) => {
1904
- if (this._closed) {
1955
+ if (this._cleanLevel !== 'none') {
1905
1956
  reject(new ModbusError(ModbusErrorCode.MASTER_CLOSED, 'Master closed'));
1906
1957
  return;
1907
1958
  }
@@ -2054,7 +2105,7 @@ class ModbusMaster extends EventEmitter {
2054
2105
  if (frame) {
2055
2106
  const runStatusIndex = 1 + serverIdLength;
2056
2107
  return Object.assign(Object.assign({}, frame), { data: {
2057
- serverId: serverIdLength === 1 ? frame.data[1] : Array.from(frame.data.subarray(1, runStatusIndex)),
2108
+ serverId: Array.from(frame.data.subarray(1, runStatusIndex)),
2058
2109
  runIndicatorStatus: frame.data[runStatusIndex] === 0xff,
2059
2110
  additionalData: Array.from(frame.data.subarray(runStatusIndex + 1)),
2060
2111
  } });
@@ -2162,26 +2213,42 @@ class ModbusMaster extends EventEmitter {
2162
2213
  }
2163
2214
  });
2164
2215
  }
2216
+ _clean(level) {
2217
+ if (this._cleanLevel === 'destroy') {
2218
+ return;
2219
+ }
2220
+ if (this._cleanLevel === 'close' && level === 'close') {
2221
+ return;
2222
+ }
2223
+ const errorCode = level === 'destroy' ? ModbusErrorCode.MASTER_DESTROYED : ModbusErrorCode.MASTER_CLOSED;
2224
+ const message = level === 'destroy' ? 'Master destroyed' : 'Master closed';
2225
+ const queued = this._queue.splice(0);
2226
+ for (const item of queued) {
2227
+ item.cancel(new ModbusError(errorCode, message));
2228
+ }
2229
+ this._masterSession.stopAll(new ModbusError(errorCode, message));
2230
+ this._cleanLevel = level;
2231
+ }
2165
2232
  open(...args) {
2166
- this._closed = false;
2233
+ if (this._cleanLevel === 'destroy') {
2234
+ return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Master is destroyed'));
2235
+ }
2236
+ this._cleanLevel = 'none';
2237
+ this._nextTid = 1;
2167
2238
  return this.physicalLayer.open(...args);
2168
2239
  }
2169
2240
  close() {
2170
- this._closed = true;
2171
- const queued = this._queue.splice(0);
2172
- for (const item of queued) {
2173
- item.cancel(new ModbusError(ModbusErrorCode.MASTER_CLOSED, 'Master closed'));
2241
+ if (this._cleanLevel === 'destroy') {
2242
+ return Promise.resolve();
2174
2243
  }
2175
- this._masterSession.stopAll(new ModbusError(ModbusErrorCode.MASTER_CLOSED, 'Master closed'));
2244
+ this._clean('close');
2176
2245
  return this.physicalLayer.close();
2177
2246
  }
2178
2247
  destroy() {
2179
- this._closed = true;
2180
- const queued = this._queue.splice(0);
2181
- for (const item of queued) {
2182
- item.cancel(new ModbusError(ModbusErrorCode.MASTER_DESTROYED, 'Master destroyed'));
2248
+ if (this._cleanLevel === 'destroy') {
2249
+ return Promise.resolve();
2183
2250
  }
2184
- this._masterSession.stopAll(new ModbusError(ModbusErrorCode.MASTER_DESTROYED, 'Master destroyed'));
2251
+ this._clean('destroy');
2185
2252
  this.removeAllListeners();
2186
2253
  this.applicationLayer.destroy();
2187
2254
  return this.physicalLayer.destroy();
@@ -2193,7 +2260,7 @@ class ModbusSlave extends EventEmitter {
2193
2260
  return this.physicalLayer.isOpen;
2194
2261
  }
2195
2262
  get destroyed() {
2196
- return this.physicalLayer.destroyed;
2263
+ return this._cleanLevel === 'destroy' || this.physicalLayer.destroyed;
2197
2264
  }
2198
2265
  constructor(applicationLayer, physicalLayer, options = {}) {
2199
2266
  var _a;
@@ -2240,6 +2307,12 @@ class ModbusSlave extends EventEmitter {
2240
2307
  writable: true,
2241
2308
  value: new Map()
2242
2309
  });
2310
+ Object.defineProperty(this, "_cleanLevel", {
2311
+ enumerable: true,
2312
+ configurable: true,
2313
+ writable: true,
2314
+ value: 'none'
2315
+ });
2243
2316
  this.concurrent = (_a = options.concurrent) !== null && _a !== void 0 ? _a : false;
2244
2317
  if (this.concurrent && this.applicationLayer.PROTOCOL !== 'TCP') {
2245
2318
  throw new ModbusError(ModbusErrorCode.CONCURRENT_NOT_TCP, 'concurrent mode requires a Modbus TCP application layer');
@@ -2269,14 +2342,14 @@ class ModbusSlave extends EventEmitter {
2269
2342
  if (!q) {
2270
2343
  return;
2271
2344
  }
2272
- q.items.length = 0;
2345
+ q.items = [];
2273
2346
  if (!q.processing) {
2274
2347
  this._queues.delete(connection.id);
2275
2348
  }
2276
2349
  });
2277
2350
  physicalLayer.on('close', () => {
2278
2351
  for (const q of this._queues.values()) {
2279
- q.items.length = 0;
2352
+ q.items = [];
2280
2353
  }
2281
2354
  this._queues.clear();
2282
2355
  this.emit('close');
@@ -2565,8 +2638,8 @@ class ModbusSlave extends EventEmitter {
2565
2638
  return;
2566
2639
  }
2567
2640
  try {
2568
- const { serverId = (_a = model.unit) !== null && _a !== void 0 ? _a : 1, runIndicatorStatus = true, additionalData = [] } = yield model.reportServerId();
2569
- const serverIdBytes = Array.isArray(serverId) ? serverId : [serverId];
2641
+ const { serverId = [(_a = model.unit) !== null && _a !== void 0 ? _a : 1], runIndicatorStatus = true, additionalData = [] } = yield model.reportServerId();
2642
+ const serverIdBytes = serverId;
2570
2643
  const byteCount = serverIdBytes.length + 1 + additionalData.length;
2571
2644
  if (byteCount > 255) {
2572
2645
  yield this.responseError(frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
@@ -2972,14 +3045,40 @@ class ModbusSlave extends EventEmitter {
2972
3045
  this._customFunctionCodes.delete(fc);
2973
3046
  this.applicationLayer.removeCustomFunctionCode(fc);
2974
3047
  }
3048
+ _clean(level) {
3049
+ if (this._cleanLevel === 'destroy') {
3050
+ return;
3051
+ }
3052
+ if (this._cleanLevel === 'close' && level === 'close') {
3053
+ return;
3054
+ }
3055
+ for (const q of this._queues.values()) {
3056
+ q.items = [];
3057
+ }
3058
+ this._queues.clear();
3059
+ this._locks.clear();
3060
+ if (level === 'destroy') {
3061
+ this._customFunctionCodes.clear();
3062
+ this.models.clear();
3063
+ }
3064
+ this._cleanLevel = level;
3065
+ }
2975
3066
  open(...args) {
3067
+ this._cleanLevel = 'none';
2976
3068
  return this.physicalLayer.open(...args);
2977
3069
  }
2978
3070
  close() {
3071
+ if (this._cleanLevel === 'destroy') {
3072
+ return Promise.resolve();
3073
+ }
3074
+ this._clean('close');
2979
3075
  return this.physicalLayer.close();
2980
3076
  }
2981
3077
  destroy() {
2982
- this._locks.clear();
3078
+ if (this._cleanLevel === 'destroy') {
3079
+ return Promise.resolve();
3080
+ }
3081
+ this._clean('destroy');
2983
3082
  this.removeAllListeners();
2984
3083
  this.applicationLayer.destroy();
2985
3084
  return this.physicalLayer.destroy();
@@ -15,6 +15,7 @@ export declare class AsciiApplicationLayer extends AbstractApplicationLayer {
15
15
  readonly lenientHex: boolean;
16
16
  private _states;
17
17
  private _removeAllListeners;
18
+ private _destroyed;
18
19
  constructor(physicalLayer: SerialPhysicalLayer | TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer, options?: AsciiApplicationLayerOptions);
19
20
  private getState;
20
21
  flush(): void;
@@ -43,6 +43,7 @@ export declare class RtuApplicationLayer extends AbstractApplicationLayer {
43
43
  private _removeAllListeners;
44
44
  private _threePointFiveT;
45
45
  private _onePointFiveT;
46
+ private _destroyed;
46
47
  constructor(physicalLayer: SerialPhysicalLayer | TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer, options?: RtuApplicationLayerOptions);
47
48
  private getState;
48
49
  private clearStateTimers;
@@ -6,6 +6,7 @@ export declare class TcpApplicationLayer extends AbstractApplicationLayer {
6
6
  private _transactionId;
7
7
  private _buffers;
8
8
  private _removeAllListeners;
9
+ private _destroyed;
9
10
  constructor(physicalLayer: TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer);
10
11
  private tryExtract;
11
12
  private processFrame;
@@ -30,7 +30,7 @@ export declare class ModbusMaster<A extends AbstractApplicationLayer, P extends
30
30
  private _queue;
31
31
  private _draining;
32
32
  private _nextTid;
33
- private _closed;
33
+ private _cleanLevel;
34
34
  timeout: number;
35
35
  readonly concurrent: boolean;
36
36
  get isOpen(): boolean;
@@ -96,6 +96,7 @@ export declare class ModbusMaster<A extends AbstractApplicationLayer, P extends
96
96
  removeCustomFunctionCode(fc: number): void;
97
97
  sendCustomFC(unit: 0, fc: number, data: Buffer | number[], timeout?: number): Promise<void>;
98
98
  sendCustomFC(unit: number, fc: number, data: Buffer | number[], timeout?: number): Promise<Buffer>;
99
+ private _clean;
99
100
  open(...args: Parameters<P['open']>): Promise<void>;
100
101
  close(): Promise<void>;
101
102
  destroy(): Promise<void>;
@@ -60,6 +60,7 @@ export declare class ModbusSlave<A extends AbstractApplicationLayer, P extends A
60
60
  private _queues;
61
61
  private _customFunctionCodes;
62
62
  private _locks;
63
+ private _cleanLevel;
63
64
  get isOpen(): boolean;
64
65
  get destroyed(): boolean;
65
66
  constructor(applicationLayer: A, physicalLayer: P, options?: ModbusSlaveOptions);
@@ -85,6 +86,7 @@ export declare class ModbusSlave<A extends AbstractApplicationLayer, P extends A
85
86
  remove(unit: number): void;
86
87
  addCustomFunctionCode(cfc: CustomFunctionCode): void;
87
88
  removeCustomFunctionCode(fc: number): void;
89
+ private _clean;
88
90
  open(...args: Parameters<P['open']>): Promise<void>;
89
91
  close(): Promise<void>;
90
92
  destroy(): Promise<void>;
@@ -8,7 +8,7 @@ export interface ApplicationDataUnit {
8
8
  }
9
9
  export interface ServerId {
10
10
  /** Server ID; may be 1 byte (number) or multi-byte (number[]) per device spec. */
11
- serverId?: number | number[];
11
+ serverId?: number[];
12
12
  runIndicatorStatus?: boolean;
13
13
  additionalData?: number[];
14
14
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "njs-modbus",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "A pure JavaScript implemetation of Modbus for NodeJS.",
5
5
  "keywords": [
6
6
  "modbus",
@@ -20,6 +20,7 @@
20
20
  },
21
21
  "license": "MIT",
22
22
  "author": "Xie Jay <xiejay97@gmail.com>",
23
+ "packageManager": "pnpm@10.10.0",
23
24
  "sideEffects": false,
24
25
  "type": "module",
25
26
  "main": "dist/index.cjs",
@@ -32,7 +33,7 @@
32
33
  ],
33
34
  "scripts": {
34
35
  "build": "rollup -c",
35
- "test": "tsx --test \"test/*.test.ts\"",
36
+ "test": "tsx --test test/*.test.ts",
36
37
  "util:sort-package-json": "sort-package-json"
37
38
  },
38
39
  "devDependencies": {
@@ -59,6 +60,7 @@
59
60
  "serialport": ">=12.0.0",
60
61
  "sort-package-json": "^3.2.1",
61
62
  "tslib": "^2.8.1",
63
+ "tsx": "^4.21.0",
62
64
  "typescript": "~5.8.3",
63
65
  "typescript-eslint": "^8.34.0"
64
66
  },