njs-modbus 3.0.2 → 3.1.1

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.
@@ -21,6 +21,6 @@ export declare abstract class AbstractApplicationLayer extends EventEmitter<Abst
21
21
  flush(): void;
22
22
  addCustomFunctionCode(cfc: CustomFunctionCode): void;
23
23
  removeCustomFunctionCode(fc: number): void;
24
- abstract encode(data: ApplicationDataUnit): Buffer;
24
+ abstract encode(unit: number, fc: number, data: Buffer, transaction?: number): Buffer;
25
25
  }
26
26
  export {};
@@ -1,4 +1,3 @@
1
- import type { ApplicationDataUnit } from '../../types';
2
1
  import type { AbstractPhysicalConnection } from '../physical';
3
2
  import { AbstractApplicationLayer } from './abstract-application-layer';
4
3
  export interface AsciiApplicationLayerOptions {
@@ -20,5 +19,5 @@ export declare class AsciiApplicationLayer extends AbstractApplicationLayer {
20
19
  constructor(role: 'MASTER' | 'SLAVE', connection: AbstractPhysicalConnection, options?: AsciiApplicationLayerOptions);
21
20
  private framing;
22
21
  flush(): void;
23
- encode(data: ApplicationDataUnit): Buffer;
22
+ encode(unit: number, fc: number, data: Buffer, transaction?: number): Buffer;
24
23
  }
@@ -1,4 +1,4 @@
1
- import type { ApplicationDataUnit, CustomFunctionCode } from '../../types';
1
+ import type { CustomFunctionCode } from '../../types';
2
2
  import type { AbstractPhysicalConnection } from '../physical';
3
3
  import { AbstractApplicationLayer } from './abstract-application-layer';
4
4
  export interface RtuApplicationLayerOptions {
@@ -27,12 +27,8 @@ export declare class RtuApplicationLayer extends AbstractApplicationLayer {
27
27
  constructor(role: 'MASTER' | 'SLAVE', connection: AbstractPhysicalConnection, options?: RtuApplicationLayerOptions);
28
28
  private clearStateTimers;
29
29
  private flushBuffer;
30
- private deliverFrame;
31
- private tryExtract;
32
- private checkExpected;
33
- private crcMatches;
34
30
  flush(): void;
35
31
  addCustomFunctionCode(cfc: CustomFunctionCode): void;
36
32
  removeCustomFunctionCode(fc: number): void;
37
- encode(data: ApplicationDataUnit): Buffer;
33
+ encode(unit: number, fc: number, data: Buffer, transaction?: number): Buffer;
38
34
  }
@@ -1,4 +1,3 @@
1
- import type { ApplicationDataUnit } from '../../types';
2
1
  import type { AbstractPhysicalConnection } from '../physical';
3
2
  import { AbstractApplicationLayer } from './abstract-application-layer';
4
3
  export declare class TcpApplicationLayer extends AbstractApplicationLayer {
@@ -13,5 +12,5 @@ export declare class TcpApplicationLayer extends AbstractApplicationLayer {
13
12
  private tryExtract;
14
13
  private processFrame;
15
14
  flush(): void;
16
- encode(data: ApplicationDataUnit): Buffer;
15
+ encode(unit: number, fc: number, data: Buffer, transaction?: number): Buffer;
17
16
  }
@@ -18,8 +18,8 @@ interface AbstractPhysicalConnectionEvents {
18
18
  export declare abstract class AbstractPhysicalConnection extends EventEmitter<AbstractPhysicalConnectionEvents> {
19
19
  abstract readonly state: PhysicalConnectionState;
20
20
  abstract readonly physicalLayer: AbstractPhysicalLayer;
21
- abstract write(data: Buffer): Promise<void>;
22
- abstract destroy(): Promise<void>;
21
+ abstract write(data: Buffer, cb?: (err?: Error | null) => void): void;
22
+ abstract destroy(cb?: (err?: Error | null) => void): void;
23
23
  }
24
24
  export interface AbstractPhysicalLayerEvents {
25
25
  open: [];
@@ -43,7 +43,8 @@ export declare abstract class AbstractPhysicalLayer extends EventEmitter<Abstrac
43
43
  is(type: 'UDP_CLIENT'): this is UdpClientPhysicalLayer;
44
44
  is(type: 'UDP_SERVER'): this is UdpServerPhysicalLayer;
45
45
  abstract readonly state: PhysicalState;
46
- abstract open(...args: any[]): Promise<void>;
47
- abstract close(): Promise<void>;
46
+ /** Last argument is the callback: `(err?: Error | null) => void`. Callback is optional. */
47
+ abstract open(...args: any[]): void;
48
+ abstract close(cb?: (err?: Error | null) => void): void;
48
49
  }
49
50
  export {};
@@ -41,14 +41,13 @@ export declare class SerialPhysicalConnection extends AbstractPhysicalConnection
41
41
  private _state;
42
42
  private _physicalLayer;
43
43
  private _serialport;
44
- private _destroyPromise;
45
- private _resolveDestroy;
44
+ private _pendingDestroyCbs;
46
45
  private _cleanupFns;
47
46
  get state(): PhysicalConnectionState;
48
47
  get physicalLayer(): AbstractPhysicalLayer;
49
48
  constructor(physicalLayer: SerialPhysicalLayer, serialport: SerialPort);
50
- write(data: Buffer): Promise<void>;
51
- destroy(): Promise<void>;
49
+ write(data: Buffer, cb?: (err?: Error | null) => void): void;
50
+ destroy(cb?: (err?: Error | null) => void): void;
52
51
  }
53
52
  export declare class SerialPhysicalLayer extends AbstractPhysicalLayer {
54
53
  readonly TYPE: "SERIAL";
@@ -58,15 +57,14 @@ export declare class SerialPhysicalLayer extends AbstractPhysicalLayer {
58
57
  private _serialportOpts;
59
58
  private _path;
60
59
  private _baudRate;
61
- private _openPromise;
62
- private _closePromise;
63
- private _resolveClose;
60
+ private _pendingOpenCbs;
61
+ private _pendingCloseCbs;
64
62
  private _cleanupFns;
65
63
  get state(): PhysicalState;
66
64
  get serialport(): SerialPort | null;
67
65
  get path(): string;
68
66
  get baudRate(): number;
69
67
  constructor(options: SerialPhysicalLayerOptions);
70
- open(): Promise<void>;
71
- close(): Promise<void>;
68
+ open(cb?: (err?: Error | null) => void): void;
69
+ close(cb?: (err?: Error | null) => void): void;
72
70
  }
@@ -8,13 +8,13 @@ export declare class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
8
8
  private _connections;
9
9
  private _socket;
10
10
  private _socketOpts?;
11
- private _openPromise;
12
- private _closePromise;
13
- private _resolveClose;
11
+ private _pendingOpenCbs;
12
+ private _pendingCloseCbs;
14
13
  private _cleanupFns;
15
14
  get state(): PhysicalState;
16
15
  get socket(): Socket | null;
17
16
  constructor(options?: SocketConstructorOpts);
18
- open(options?: SocketConnectOpts): Promise<void>;
19
- close(): Promise<void>;
17
+ open(cb?: (err?: Error | null) => void): void;
18
+ open(options: SocketConnectOpts, cb?: (err?: Error | null) => void): void;
19
+ close(cb?: (err?: Error | null) => void): void;
20
20
  }
@@ -6,12 +6,11 @@ export declare class TcpPhysicalConnection extends AbstractPhysicalConnection {
6
6
  private _state;
7
7
  private _physicalLayer;
8
8
  private _socket;
9
- private _destroyPromise;
10
- private _resolveDestroy;
9
+ private _pendingDestroyCbs;
11
10
  private _cleanupFns;
12
11
  get state(): PhysicalConnectionState;
13
12
  get physicalLayer(): AbstractPhysicalLayer;
14
13
  constructor(physicalLayer: AbstractPhysicalLayer, socket: Socket);
15
- write(data: Buffer): Promise<void>;
16
- destroy(): Promise<void>;
14
+ write(data: Buffer, cb?: (err?: Error | null) => void): void;
15
+ destroy(cb?: (err?: Error | null) => void): void;
17
16
  }
@@ -17,13 +17,13 @@ export declare class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
17
17
  private _connections;
18
18
  private _server;
19
19
  private _opts;
20
- private _openPromise;
21
- private _closePromise;
22
- private _resolveClose;
20
+ private _pendingOpenCbs;
21
+ private _pendingCloseCbs;
23
22
  private _cleanupFns;
24
23
  get state(): PhysicalState;
25
24
  get server(): Server | null;
26
25
  constructor(options?: TcpServerPhysicalLayerOptions);
27
- open(options?: ListenOptions): Promise<void>;
28
- close(): Promise<void>;
26
+ open(cb?: (err?: Error | null) => void): void;
27
+ open(options: ListenOptions, cb?: (err?: Error | null) => void): void;
28
+ close(cb?: (err?: Error | null) => void): void;
29
29
  }
@@ -5,14 +5,13 @@ export declare class UdpClientPhysicalConnection extends AbstractPhysicalConnect
5
5
  private _state;
6
6
  private _physicalLayer;
7
7
  private _socket;
8
- private _destroyPromise;
9
- private _resolveDestroy;
8
+ private _pendingDestroyCbs;
10
9
  private _cleanupFns;
11
10
  get state(): PhysicalConnectionState;
12
11
  get physicalLayer(): AbstractPhysicalLayer;
13
12
  constructor(physicalLayer: UdpClientPhysicalLayer, socket: Socket);
14
- write(data: Buffer): Promise<void>;
15
- destroy(): Promise<void>;
13
+ write(data: Buffer, cb?: (err?: Error | null) => void): void;
14
+ destroy(cb?: (err?: Error | null) => void): void;
16
15
  }
17
16
  export declare class UdpClientPhysicalLayer extends AbstractPhysicalLayer {
18
17
  readonly TYPE: "UDP_CLIENT";
@@ -20,16 +19,16 @@ export declare class UdpClientPhysicalLayer extends AbstractPhysicalLayer {
20
19
  private _connections;
21
20
  private _socket;
22
21
  private _socketOpts;
23
- private _openPromise;
24
- private _closePromise;
25
- private _resolveClose;
22
+ private _pendingOpenCbs;
23
+ private _pendingCloseCbs;
26
24
  private _cleanupFns;
27
25
  get state(): PhysicalState;
28
26
  get socket(): Socket | null;
29
27
  constructor(options?: Partial<SocketOptions>);
30
- open(remote?: {
28
+ open(cb?: (err?: Error | null) => void): void;
29
+ open(remote: {
31
30
  port?: number;
32
31
  address?: string;
33
- }): Promise<void>;
34
- close(): Promise<void>;
32
+ }, cb?: (err?: Error | null) => void): void;
33
+ close(cb?: (err?: Error | null) => void): void;
35
34
  }
@@ -24,8 +24,8 @@ export declare class UdpServerPhysicalConnection extends AbstractPhysicalConnect
24
24
  add: (listener: (msg: Buffer, rinfo: RemoteInfo) => void) => void;
25
25
  remove: (listener: (...args: any[]) => void) => void;
26
26
  });
27
- write(data: Buffer): Promise<void>;
28
- destroy(): Promise<void>;
27
+ write(data: Buffer, cb?: (err?: Error | null) => void): void;
28
+ destroy(cb?: (err?: Error | null) => void): void;
29
29
  }
30
30
  export declare class UdpServerPhysicalLayer extends AbstractPhysicalLayer {
31
31
  readonly TYPE: "UDP_SERVER";
@@ -33,9 +33,8 @@ export declare class UdpServerPhysicalLayer extends AbstractPhysicalLayer {
33
33
  private _connections;
34
34
  private _socket;
35
35
  private _opts;
36
- private _openPromise;
37
- private _closePromise;
38
- private _resolveClose;
36
+ private _pendingOpenCbs;
37
+ private _pendingCloseCbs;
39
38
  private _cleanupFns;
40
39
  get state(): PhysicalState;
41
40
  get socket(): Socket | null;
@@ -44,11 +43,9 @@ export declare class UdpServerPhysicalLayer extends AbstractPhysicalLayer {
44
43
  * Bind the UDP socket and start accepting datagrams.
45
44
  *
46
45
  * @param options Bind options (port, address, etc.). Defaults to port 502.
47
- * @param [idleTimeout=30000] Maximum idle time in milliseconds before an
48
- * inactive client connection is evicted. Pass `0` to disable eviction
49
- * (connections never time out). Disabling eviction may cause unbounded
50
- * memory growth if the server sees many unique clients.
46
+ * @param cb Callback invoked when binding completes or fails.
51
47
  */
52
- open(options?: BindOptions): Promise<void>;
53
- close(): Promise<void>;
48
+ open(cb?: (err?: Error | null) => void): void;
49
+ open(options: BindOptions, cb?: (err?: Error | null) => void): void;
50
+ close(cb?: (err?: Error | null) => void): void;
54
51
  }
@@ -2,17 +2,17 @@ import type { ApplicationDataUnit } from '../types';
2
2
  type Frame = ApplicationDataUnit & {
3
3
  buffer: Buffer;
4
4
  };
5
- type PreCheck = (frame: Frame) => boolean | number | undefined;
6
5
  type Callback = (error: Error | null, frame?: Frame) => void;
7
6
  export declare const FIFO_KEY: "fifo";
8
7
  export declare class MasterSession {
9
8
  private _waiters;
10
- start(key: string | number, preCheck: PreCheck[], callback: Callback): void;
9
+ /** Register a callback for `key`. No timer timeout is managed by the caller. */
10
+ start(key: string | number, callback: Callback): void;
11
+ /** Cancel a pending waiter without firing its callback. */
11
12
  stop(key: string | number): void;
12
13
  stopAll(error: Error): void;
13
14
  has(key: string | number): boolean;
14
15
  handleFrame(frame: Frame): void;
15
16
  handleError(error: Error): void;
16
- private runPreChecks;
17
17
  }
18
18
  export {};
@@ -39,7 +39,15 @@ export declare class ModbusMaster<T extends ModbusMasterOptions = ModbusMasterOp
39
39
  private _protocol;
40
40
  private _appLayer?;
41
41
  private _customFunctionCodes;
42
- private _queue;
42
+ private _queueUnits;
43
+ private _queueFcs;
44
+ private _queueDatas;
45
+ private _queueTimeouts;
46
+ private _queueBroadcasts;
47
+ private _queueResolves;
48
+ private _queueRejects;
49
+ private _queueHead;
50
+ private _queueLen;
43
51
  private _draining;
44
52
  private _nextTid;
45
53
  private _cleanupFns;
@@ -50,6 +58,7 @@ export declare class ModbusMaster<T extends ModbusMasterOptions = ModbusMasterOp
50
58
  private _createAppLayer;
51
59
  private send;
52
60
  private _drain;
61
+ private _processNext;
53
62
  private _exchange;
54
63
  private writeFC1Or2;
55
64
  writeFC1: this['readCoils'];
@@ -108,7 +117,6 @@ export declare class ModbusMaster<T extends ModbusMasterOptions = ModbusMasterOp
108
117
  removeCustomFunctionCode(fc: number): void;
109
118
  sendCustomFC(unit: 0, fc: number, data: Buffer | number[], timeout?: number): Promise<void>;
110
119
  sendCustomFC(unit: number, fc: number, data: Buffer | number[], timeout?: number): Promise<Buffer>;
111
- private _clean;
112
120
  /**
113
121
  * Open the underlying physical layer and begin accepting connections.
114
122
  *
@@ -92,6 +92,7 @@ export declare class ModbusSlave<T extends ModbusSlaveOptions = ModbusSlaveOptio
92
92
  private _intercept;
93
93
  private _withAddressLock;
94
94
  private _handleFC;
95
+ private _handleCustomFC;
95
96
  add(model: ModbusSlaveModel): void;
96
97
  remove(unit: number): void;
97
98
  addCustomFunctionCode(cfc: CustomFunctionCode): void;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Drain a pending-callback array: invoke each callback with the given error (or null).
3
+ *
4
+ * Handles `null`/`undefined` entries (from optional `cb?` parameters) gracefully.
5
+ * Used by physical layers and connections to resolve queued
6
+ * open / close / destroy callbacks.
7
+ */
8
+ export declare function drainCbs(cbs: (((err?: Error | null) => void) | undefined)[] | null, err?: Error | null): void;
@@ -1 +1 @@
1
- export declare function crc(data: Uint8Array, seed?: number): number;
1
+ export declare function crc(data: Uint8Array, start?: number, end?: number): number;
@@ -1,10 +1,11 @@
1
- export type { PredictResult } from './predictRtuFrameLength';
2
1
  export type { RtuProtocolOptions, ResolvedRtuTiming } from './rtu-timing';
2
+ export { bitsToMs } from './bitsToMs';
3
+ export { drainCbs } from './callback';
3
4
  export { checkRange } from './checkRange';
4
5
  export { crc } from './crc';
5
- export { bitsToMs } from './bitsToMs';
6
6
  export { isUint8 } from './isUint8';
7
7
  export { lrc } from './lrc';
8
- export { predictRtuFrameLength } from './predictRtuFrameLength';
8
+ export { PREDICT_NEED_MORE, PREDICT_UNKNOWN, predictRtuFrameLength } from './predictRtuFrameLength';
9
+ export { promisifyCb } from './promisify-cb';
9
10
  export { resolveRtuTiming } from './rtu-timing';
10
11
  export { isWhitelisted } from './whitelist';
@@ -1,20 +1,17 @@
1
- export type PredictResult = {
2
- kind: 'length';
3
- length: number;
4
- } | {
5
- kind: 'need-more';
6
- } | {
7
- kind: 'unknown';
8
- };
1
+ /** Sentinel: caller needs to feed more bytes before length can be determined. */
2
+ export declare const PREDICT_NEED_MORE = 0;
3
+ /** Sentinel: function code is not in the standard tables. */
4
+ export declare const PREDICT_UNKNOWN = -1;
9
5
  /**
10
6
  * Predict the total RTU frame length (PDU + 2-byte CRC) given the leading bytes.
11
7
  *
12
- * Returns a discriminated result so callers can distinguish:
13
- * - `{ kind: 'length' }`: function code is known and total frame length is determined.
14
- * - `{ kind: 'need-more' }`: function code is known, but more bytes are required
15
- * (typically waiting on the byteCount byte).
16
- * - `{ kind: 'unknown' }`: function code is not in the standard tables — the
17
- * framing layer must defer to a registered `CustomFunctionCode` or treat this
18
- * as a framing error.
8
+ * Returns a sentinel-encoded number to avoid per-call object allocation on the
9
+ * RTU decode hot path:
10
+ * - Positive integer (>= 4): total frame length, function code is known.
11
+ * - {@link PREDICT_NEED_MORE} (0): function code is known but more bytes are
12
+ * required (typically waiting on the byteCount byte).
13
+ * - {@link PREDICT_UNKNOWN} (-1): function code is not in the standard tables —
14
+ * the framing layer must defer to a registered `CustomFunctionCode` or treat
15
+ * this as a framing error.
19
16
  */
20
- export declare function predictRtuFrameLength(buffer: Buffer, isResponse: boolean): PredictResult;
17
+ export declare function predictRtuFrameLength(buffer: Buffer, isResponse: boolean): number;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Convert a callback-style `(cb: (err?) => void) => void` call into a Promise.
3
+ */
4
+ export declare function promisifyCb(fn: (cb: (err?: Error | null) => void) => void): Promise<void>;
@@ -1,29 +1,43 @@
1
+ /**
2
+ * RTU timing parameter — accepts either:
3
+ * - a bare `number` in milliseconds (`0` to disable the timer entirely)
4
+ * - `{ unit: 'ms', value: N }` — explicit milliseconds (equivalent to bare `N`)
5
+ * - `{ unit: 'bit', value: N }` — bit-time approximation, derived from `baudRate`
6
+ *
7
+ * The bare-number form is the recommended default; the object form exists for
8
+ * specs that quote bit-time. Pass `0` (or `{ unit: 'ms', value: 0 }`) to disable
9
+ * the timer; either form short-circuits the baudRate-derived fallback.
10
+ */
11
+ export type RtuTimingValue = number | {
12
+ unit: 'bit' | 'ms';
13
+ value: number;
14
+ };
1
15
  /** User-facing RTU protocol options (supports both bit and ms units). */
2
16
  export interface RtuProtocolOptions {
3
17
  /**
4
18
  * Inter-frame silence (Modbus RTU t3.5).
19
+ *
20
+ * - `20` or `{ unit: 'ms', value: 20 }` — 20 ms
5
21
  * - `{ unit: 'bit', value: 38.5 }` — spec bit-time approximation (default when `baudRate` is provided)
6
- * - `{ unit: 'ms', value: 20 }` explicit milliseconds
22
+ * - `0` disable t3.5 timing (immediate parse on every chunk; useful for
23
+ * lossless transports such as RTU-over-TCP or PTY-based tests where the
24
+ * wire's silence semantics do not apply)
7
25
  *
8
26
  * Per Modbus V1.02 §2.5.1.1, at baud rates > 19200 a fixed 1.75 ms is used
9
27
  * regardless of the bit value.
10
28
  */
11
- intervalBetweenFrames?: {
12
- unit: 'bit' | 'ms';
13
- value: number;
14
- };
29
+ intervalBetweenFrames?: RtuTimingValue;
15
30
  /**
16
31
  * Inter-character timeout (Modbus RTU t1.5). Opt-in; **disabled** by default.
32
+ *
33
+ * - `1` or `{ unit: 'ms', value: 1 }` — 1 ms
17
34
  * - `{ unit: 'bit', value: 21 }` — bit-time approximation (~1.5 char times)
18
- * - `{ unit: 'ms', value: 1 }` — explicit milliseconds
35
+ * - `0` — disable explicitly
19
36
  *
20
37
  * Per Modbus V1.02 §2.5.1.1, at baud rates > 19200 a fixed 0.75 ms is used
21
38
  * regardless of the bit value.
22
39
  */
23
- interCharTimeout?: {
24
- unit: 'bit' | 'ms';
25
- value: number;
26
- };
40
+ interCharTimeout?: RtuTimingValue;
27
41
  /**
28
42
  * Buffer pool size per connection (bytes). Defaults to `MAX_FRAME_LENGTH * 2`
29
43
  * (512 bytes). Increase this if you expect frames larger than 256 bytes or
@@ -40,7 +54,7 @@ export interface ResolvedRtuTiming {
40
54
  * Resolve Modbus RTU timing parameters from user options into milliseconds.
41
55
  *
42
56
  * - `intervalBetweenFrames` (t3.5): if omitted and `baudRate` is present,
43
- * defaults to 38.5 bits per Modbus V1.02 §2.5.1.1.
57
+ * defaults to 38.5 bits per Modbus V1.02 §2.5.1.1. Pass `0` to disable.
44
58
  * - `interCharTimeout` (t1.5): opt-in; only resolved when explicitly provided.
45
59
  *
46
60
  * Per the spec, at baud rates > 19200 fixed values are used
@@ -35,6 +35,8 @@ export declare enum ConformityLevel {
35
35
  REGULAR = 130,
36
36
  EXTENDED = 131
37
37
  }
38
+ /** Shared empty Buffer to avoid repeated allocations. */
39
+ export declare const EMPTY_BUFFER: Buffer<ArrayBuffer>;
38
40
  /** Modbus V1.1b3 PDU quantity limits. */
39
41
  export declare const LIMITS: {
40
42
  readonly READ_COILS_MIN: 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "njs-modbus",
3
- "version": "3.0.2",
3
+ "version": "3.1.1",
4
4
  "description": "A pure JavaScript implementation of Modbus for Node.js.",
5
5
  "keywords": [
6
6
  "modbus",
@@ -33,6 +33,8 @@
33
33
  "scripts": {
34
34
  "build": "node -e \"fs.rmSync('dist', {recursive: true, force: true})\" && rollup -c",
35
35
  "test": "tsx --test test/**/*.test.ts",
36
+ "benchmark": "tsx benchmark/encode-decode.ts && echo && tsx benchmark/tcp-throughput.ts && echo && tsx benchmark/concurrent.ts",
37
+ "benchmark:report": "tsx benchmark/generate-report.ts",
36
38
  "util:sort-package-json": "sort-package-json"
37
39
  },
38
40
  "devDependencies": {
@@ -50,7 +52,9 @@
50
52
  "eslint-config-prettier": "^10.1.5",
51
53
  "eslint-plugin-import": "2.31.0",
52
54
  "eslint-plugin-prettier": "^5.4.1",
55
+ "jsmodbus": "^4.0.10",
53
56
  "microbundle": "^0.15.1",
57
+ "modbus-serial": "^8.0.25",
54
58
  "nx": "21.1.3",
55
59
  "prettier": "^3.5.3",
56
60
  "rollup": "^4.43.0",
@@ -66,5 +70,8 @@
66
70
  },
67
71
  "peerDependencies": {
68
72
  "serialport": ">=12.0.0"
73
+ },
74
+ "engines": {
75
+ "node": ">=18.19"
69
76
  }
70
77
  }