njs-modbus 2.1.0 → 3.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.
Files changed (62) hide show
  1. package/README.md +324 -147
  2. package/README.zh-CN.md +380 -0
  3. package/dist/index.cjs +2552 -2288
  4. package/dist/index.d.ts +400 -233
  5. package/dist/index.mjs +2548 -2287
  6. package/dist/src/error-code.d.ts +2 -24
  7. package/dist/src/layers/application/abstract-application-layer.d.ts +13 -8
  8. package/dist/src/layers/application/ascii-application-layer.d.ts +9 -11
  9. package/dist/src/layers/application/rtu-application-layer.d.ts +20 -47
  10. package/dist/src/layers/application/tcp-application-layer.d.ts +9 -8
  11. package/dist/src/layers/physical/abstract-physical-layer.d.ts +43 -14
  12. package/dist/src/layers/physical/index.d.ts +9 -3
  13. package/dist/src/layers/physical/serial-physical-layer.d.ts +43 -15
  14. package/dist/src/layers/physical/tcp-client-physical-layer.d.ts +13 -12
  15. package/dist/src/layers/physical/tcp-physical-connection.d.ts +16 -0
  16. package/dist/src/layers/physical/tcp-server-physical-layer.d.ts +24 -14
  17. package/dist/src/layers/physical/udp-client-physical-layer.d.ts +33 -0
  18. package/dist/src/layers/physical/udp-server-physical-layer.d.ts +50 -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-session.d.ts +3 -3
  22. package/dist/src/master/master.d.ts +55 -19
  23. package/dist/src/slave/slave.d.ts +58 -33
  24. package/dist/src/types.d.ts +2 -2
  25. package/dist/src/utils/callback.d.ts +8 -0
  26. package/dist/src/utils/crc.d.ts +1 -1
  27. package/dist/src/utils/index.d.ts +7 -4
  28. package/dist/src/utils/predictRtuFrameLength.d.ts +13 -16
  29. package/dist/src/utils/promisify-cb.d.ts +4 -0
  30. package/dist/src/utils/rtu-timing.d.ts +63 -0
  31. package/dist/src/utils/whitelist.d.ts +11 -0
  32. package/dist/src/vars.d.ts +2 -0
  33. package/package.json +15 -8
  34. package/dist/src/layers/physical/udp-physical-layer.d.ts +0 -42
  35. package/dist/src/utils/genConnectionId.d.ts +0 -2
  36. package/dist/test/adu-buffer.test.d.ts +0 -1
  37. package/dist/test/ascii-hex-sentry.test.d.ts +0 -1
  38. package/dist/test/ascii-hex-validation.test.d.ts +0 -1
  39. package/dist/test/ascii-tcp-fragmentation.test.d.ts +0 -1
  40. package/dist/test/check-range.test.d.ts +0 -1
  41. package/dist/test/fallback-atomic.test.d.ts +0 -1
  42. package/dist/test/fallback-serial.test.d.ts +0 -1
  43. package/dist/test/fc17-serverid-validation.test.d.ts +0 -1
  44. package/dist/test/fc43-conformity.test.d.ts +0 -1
  45. package/dist/test/fc43-utf8-objects.test.d.ts +0 -1
  46. package/dist/test/gen-connection-id.test.d.ts +0 -1
  47. package/dist/test/helpers/raw-tcp.d.ts +0 -38
  48. package/dist/test/master-concurrent.test.d.ts +0 -1
  49. package/dist/test/modbus-error.test.d.ts +0 -1
  50. package/dist/test/physical-lifecycle.test.d.ts +0 -1
  51. package/dist/test/predict-rtu.test.d.ts +0 -1
  52. package/dist/test/rtu-custom-fc.test.d.ts +0 -1
  53. package/dist/test/rtu-pool-overflow.test.d.ts +0 -1
  54. package/dist/test/rtu-t15-timing.test.d.ts +0 -1
  55. package/dist/test/rtu-t35-default.test.d.ts +0 -1
  56. package/dist/test/rtu-t35-strict.test.d.ts +0 -1
  57. package/dist/test/rtu-tcp-fragmentation.test.d.ts +0 -1
  58. package/dist/test/serial-e2e.test.d.ts +0 -1
  59. package/dist/test/slave-multi-connection.test.d.ts +0 -1
  60. package/dist/test/slave.test.d.ts +0 -1
  61. package/dist/test/tcp-fragmentation.test.d.ts +0 -1
  62. package/dist/test/udp-multi-client.test.d.ts +0 -1
@@ -1,11 +1,9 @@
1
- import type { AbstractApplicationLayer } from '../layers/application';
2
- import type { AbstractPhysicalLayer } from '../layers/physical';
1
+ import type { AsciiApplicationLayerOptions } from '../layers/application';
2
+ import type { AbstractPhysicalLayer, AbstractPhysicalLayerEvents, OpenArgs, PhysicalConfig } from '../layers/physical';
3
3
  import type { CustomFunctionCode, DeviceIdentification, ServerId } from '../types';
4
+ import type { RtuProtocolOptions } from '../utils';
4
5
  import EventEmitter from 'node:events';
5
- interface ModbusMasterEvents {
6
- error: [error: Error];
7
- close: [];
8
- }
6
+ import { PhysicalState } from '../layers/physical';
9
7
  interface ReturnValue<T> {
10
8
  transaction?: number;
11
9
  unit: number;
@@ -22,22 +20,45 @@ export interface ModbusMasterOptions {
22
20
  * Default false (FIFO queue, requests are serialized).
23
21
  */
24
22
  concurrent?: boolean;
23
+ physical: PhysicalConfig;
24
+ protocol: {
25
+ type: 'RTU';
26
+ opts?: RtuProtocolOptions;
27
+ } | {
28
+ type: 'TCP';
29
+ } | {
30
+ type: 'ASCII';
31
+ opts?: AsciiApplicationLayerOptions;
32
+ };
25
33
  }
26
- export declare class ModbusMaster<A extends AbstractApplicationLayer, P extends AbstractPhysicalLayer> extends EventEmitter<ModbusMasterEvents> {
27
- private applicationLayer;
28
- private physicalLayer;
34
+ export declare class ModbusMaster<T extends ModbusMasterOptions = ModbusMasterOptions> extends EventEmitter<AbstractPhysicalLayerEvents> {
35
+ readonly timeout: number;
36
+ readonly concurrent: boolean;
29
37
  private _masterSession;
30
- private _queue;
38
+ private _physicalLayer;
39
+ private _protocol;
40
+ private _appLayer?;
41
+ private _customFunctionCodes;
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;
31
51
  private _draining;
32
52
  private _nextTid;
33
- private _cleanLevel;
34
- timeout: number;
35
- readonly concurrent: boolean;
36
- get isOpen(): boolean;
37
- get destroyed(): boolean;
38
- constructor(applicationLayer: A, physicalLayer: P, options?: ModbusMasterOptions);
53
+ private _cleanupFns;
54
+ private _closePromise;
55
+ get state(): PhysicalState;
56
+ get physicalLayer(): AbstractPhysicalLayer;
57
+ constructor(options: T);
58
+ private _createAppLayer;
39
59
  private send;
40
60
  private _drain;
61
+ private _processNext;
41
62
  private _exchange;
42
63
  private writeFC1Or2;
43
64
  writeFC1: this['readCoils'];
@@ -96,9 +117,24 @@ export declare class ModbusMaster<A extends AbstractApplicationLayer, P extends
96
117
  removeCustomFunctionCode(fc: number): void;
97
118
  sendCustomFC(unit: 0, fc: number, data: Buffer | number[], timeout?: number): Promise<void>;
98
119
  sendCustomFC(unit: number, fc: number, data: Buffer | number[], timeout?: number): Promise<Buffer>;
99
- private _clean;
100
- open(...args: Parameters<P['open']>): Promise<void>;
120
+ /**
121
+ * Open the underlying physical layer and begin accepting connections.
122
+ *
123
+ * A `ModbusMaster` instance can only be opened once. Once {@link close}
124
+ * is called — explicitly or because the physical layer disconnected —
125
+ * the instance is permanently closed and cannot be reopened.
126
+ * Create a new `ModbusMaster` if a new connection is required.
127
+ */
128
+ open(...args: OpenArgs<T['physical']>): Promise<void>;
129
+ private _close;
130
+ /**
131
+ * Permanently close the master and release all resources.
132
+ *
133
+ * After calling this method the instance is considered dead:
134
+ * - No further requests can be sent.
135
+ * - The instance cannot be reopened via {@link open}.
136
+ * - All event listeners registered on this master are removed.
137
+ */
101
138
  close(): Promise<void>;
102
- destroy(): Promise<void>;
103
139
  }
104
140
  export {};
@@ -1,7 +1,9 @@
1
- import type { AbstractApplicationLayer } from '../layers/application';
2
- import type { AbstractPhysicalLayer } from '../layers/physical';
3
- import type { CustomFunctionCode, FConvertPromise, ServerId } from '../types';
1
+ import type { AsciiApplicationLayerOptions } from '../layers/application';
2
+ import type { AbstractPhysicalLayer, AbstractPhysicalLayerEvents, OpenArgs, PhysicalConfig } from '../layers/physical';
3
+ import type { CustomFunctionCode, MaybeAsyncFunction, ServerId } from '../types';
4
+ import type { RtuProtocolOptions } from '../utils';
4
5
  import EventEmitter from 'node:events';
6
+ import { PhysicalState } from '../layers/physical';
5
7
  export interface ModbusSlaveModel {
6
8
  unit?: number;
7
9
  /**
@@ -10,27 +12,27 @@ export interface ModbusSlaveModel {
10
12
  * If provide the return value, use this value as data of `PDU` to respond.
11
13
  * Otherwise keep the default read and write behavior.
12
14
  */
13
- interceptor?: FConvertPromise<(fc: number, data: Buffer) => Buffer | undefined>;
14
- readDiscreteInputs?: FConvertPromise<(address: number, length: number) => boolean[]>;
15
- readCoils?: FConvertPromise<(address: number, length: number) => boolean[]>;
16
- writeSingleCoil?: FConvertPromise<(address: number, value: boolean) => void>;
15
+ interceptor?: MaybeAsyncFunction<(fc: number, data: Buffer) => Buffer | undefined>;
16
+ readDiscreteInputs?: MaybeAsyncFunction<(address: number, length: number) => boolean[]>;
17
+ readCoils?: MaybeAsyncFunction<(address: number, length: number) => boolean[]>;
18
+ writeSingleCoil?: MaybeAsyncFunction<(address: number, value: boolean) => void>;
17
19
  /**
18
20
  * If omitted, defaults to loop and call `writeSingleCoil`.
19
21
  */
20
- writeMultipleCoils?: FConvertPromise<(address: number, value: boolean[]) => void>;
21
- readInputRegisters?: FConvertPromise<(address: number, length: number) => number[]>;
22
- readHoldingRegisters?: FConvertPromise<(address: number, length: number) => number[]>;
23
- writeSingleRegister?: FConvertPromise<(address: number, value: number) => void>;
22
+ writeMultipleCoils?: MaybeAsyncFunction<(address: number, value: boolean[]) => void>;
23
+ readInputRegisters?: MaybeAsyncFunction<(address: number, length: number) => number[]>;
24
+ readHoldingRegisters?: MaybeAsyncFunction<(address: number, length: number) => number[]>;
25
+ writeSingleRegister?: MaybeAsyncFunction<(address: number, value: number) => void>;
24
26
  /**
25
27
  * If omitted, defaults to loop and call `writeSingleRegister`.
26
28
  */
27
- writeMultipleRegisters?: FConvertPromise<(address: number, value: number[]) => void>;
29
+ writeMultipleRegisters?: MaybeAsyncFunction<(address: number, value: number[]) => void>;
28
30
  /**
29
31
  * If omitted, defaults to call `readHoldingRegisters` and `writeSingleRegister`.
30
32
  */
31
- maskWriteRegister?: FConvertPromise<(address: number, andMask: number, orMask: number) => void>;
32
- reportServerId?: FConvertPromise<() => ServerId>;
33
- readDeviceIdentification?: FConvertPromise<() => {
33
+ maskWriteRegister?: MaybeAsyncFunction<(address: number, andMask: number, orMask: number) => void>;
34
+ reportServerId?: MaybeAsyncFunction<() => ServerId>;
35
+ readDeviceIdentification?: MaybeAsyncFunction<() => {
34
36
  [index: number]: string;
35
37
  }>;
36
38
  getAddressRange?: () => {
@@ -40,10 +42,6 @@ export interface ModbusSlaveModel {
40
42
  holdingRegisters?: [number, number] | [number, number][];
41
43
  };
42
44
  }
43
- interface ModbusSlaveEvents {
44
- error: [error: Error];
45
- close: [];
46
- }
47
45
  export interface ModbusSlaveOptions {
48
46
  /**
49
47
  * Pipelined concurrent processing of requests within one connection.
@@ -51,19 +49,31 @@ export interface ModbusSlaveOptions {
51
49
  * Default false — per-connection FIFO. RTU/ASCII + concurrent=true throws.
52
50
  */
53
51
  concurrent?: boolean;
52
+ physical: PhysicalConfig;
53
+ protocol: {
54
+ type: 'RTU';
55
+ opts?: RtuProtocolOptions;
56
+ } | {
57
+ type: 'TCP';
58
+ } | {
59
+ type: 'ASCII';
60
+ opts?: AsciiApplicationLayerOptions;
61
+ };
54
62
  }
55
- export declare class ModbusSlave<A extends AbstractApplicationLayer, P extends AbstractPhysicalLayer> extends EventEmitter<ModbusSlaveEvents> {
56
- private applicationLayer;
57
- private physicalLayer;
58
- models: Map<number, ModbusSlaveModel>;
63
+ export declare class ModbusSlave<T extends ModbusSlaveOptions = ModbusSlaveOptions> extends EventEmitter<AbstractPhysicalLayerEvents> {
64
+ readonly models: Map<number, ModbusSlaveModel>;
59
65
  readonly concurrent: boolean;
60
- private _queues;
66
+ private _physicalLayer;
67
+ private _protocol;
68
+ private _appLayers;
61
69
  private _customFunctionCodes;
62
70
  private _locks;
63
- private _cleanLevel;
64
- get isOpen(): boolean;
65
- get destroyed(): boolean;
66
- constructor(applicationLayer: A, physicalLayer: P, options?: ModbusSlaveOptions);
71
+ private _cleanupFns;
72
+ private _closePromise;
73
+ get state(): PhysicalState;
74
+ get physicalLayer(): AbstractPhysicalLayer;
75
+ constructor(options: T);
76
+ private _createAppLayer;
67
77
  private handleFC1;
68
78
  private handleFC2;
69
79
  private handleFC3;
@@ -80,15 +90,30 @@ export declare class ModbusSlave<A extends AbstractApplicationLayer, P extends A
80
90
  private _drain;
81
91
  private _processFrame;
82
92
  private _intercept;
83
- private withAddressLock;
93
+ private _withAddressLock;
84
94
  private _handleFC;
95
+ private _handleCustomFC;
85
96
  add(model: ModbusSlaveModel): void;
86
97
  remove(unit: number): void;
87
98
  addCustomFunctionCode(cfc: CustomFunctionCode): void;
88
99
  removeCustomFunctionCode(fc: number): void;
89
- private _clean;
90
- open(...args: Parameters<P['open']>): Promise<void>;
100
+ /**
101
+ * Open the underlying physical layer and begin accepting connections.
102
+ *
103
+ * A `ModbusSlave` instance can only be opened once. Once {@link close}
104
+ * is called — explicitly or because the physical layer disconnected —
105
+ * the instance is permanently closed and cannot be reopened.
106
+ * Create a new `ModbusSlave` if a new server is required.
107
+ */
108
+ open(...args: OpenArgs<T['physical']>): Promise<void>;
109
+ private _close;
110
+ /**
111
+ * Permanently close the slave and release all resources.
112
+ *
113
+ * After calling this method the instance is considered dead:
114
+ * - No further requests can be processed.
115
+ * - The instance cannot be reopened via {@link open}.
116
+ * - All event listeners registered on this slave are removed.
117
+ */
91
118
  close(): Promise<void>;
92
- destroy(): Promise<void>;
93
119
  }
94
- export {};
@@ -1,5 +1,5 @@
1
1
  export type Callback<T> = (error: Error | null, value: T) => void;
2
- export type FConvertPromise<F extends (...args: any) => any> = F extends (...args: infer A) => infer R ? ((...args: A) => Promise<R>) | ((...args: A) => R) : never;
2
+ export type MaybeAsyncFunction<F extends (...args: any) => any> = F extends (...args: infer A) => infer R ? ((...args: A) => Promise<R>) | ((...args: A) => R) : never;
3
3
  export interface ApplicationDataUnit {
4
4
  transaction?: number;
5
5
  unit: number;
@@ -50,5 +50,5 @@ export interface CustomFunctionCode {
50
50
  * Throwing inside `handle` is turned into a Modbus exception response by the slave.
51
51
  * If `handle` is omitted, the slave returns an ILLEGAL_FUNCTION exception for this FC.
52
52
  */
53
- handle?: FConvertPromise<(data: Buffer, unit: number) => Buffer>;
53
+ handle?: MaybeAsyncFunction<(data: Buffer, unit: number) => Buffer>;
54
54
  }
@@ -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,8 +1,11 @@
1
+ export type { RtuProtocolOptions, ResolvedRtuTiming } from './rtu-timing';
2
+ export { bitsToMs } from './bitsToMs';
3
+ export { drainCbs } from './callback';
1
4
  export { checkRange } from './checkRange';
2
5
  export { crc } from './crc';
3
- export { genConnectionId } from './genConnectionId';
4
- export { bitsToMs } from './bitsToMs';
5
6
  export { isUint8 } from './isUint8';
6
7
  export { lrc } from './lrc';
7
- export { predictRtuFrameLength } from './predictRtuFrameLength';
8
- export type { PredictResult } from './predictRtuFrameLength';
8
+ export { PREDICT_NEED_MORE, PREDICT_UNKNOWN, predictRtuFrameLength } from './predictRtuFrameLength';
9
+ export { promisifyCb } from './promisify-cb';
10
+ export { resolveRtuTiming } from './rtu-timing';
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>;
@@ -0,0 +1,63 @@
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
+ };
15
+ /** User-facing RTU protocol options (supports both bit and ms units). */
16
+ export interface RtuProtocolOptions {
17
+ /**
18
+ * Inter-frame silence (Modbus RTU t3.5).
19
+ *
20
+ * - `20` or `{ unit: 'ms', value: 20 }` — 20 ms
21
+ * - `{ unit: 'bit', value: 38.5 }` — spec bit-time approximation (default when `baudRate` is provided)
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)
25
+ *
26
+ * Per Modbus V1.02 §2.5.1.1, at baud rates > 19200 a fixed 1.75 ms is used
27
+ * regardless of the bit value.
28
+ */
29
+ intervalBetweenFrames?: RtuTimingValue;
30
+ /**
31
+ * Inter-character timeout (Modbus RTU t1.5). Opt-in; **disabled** by default.
32
+ *
33
+ * - `1` or `{ unit: 'ms', value: 1 }` — 1 ms
34
+ * - `{ unit: 'bit', value: 21 }` — bit-time approximation (~1.5 char times)
35
+ * - `0` — disable explicitly
36
+ *
37
+ * Per Modbus V1.02 §2.5.1.1, at baud rates > 19200 a fixed 0.75 ms is used
38
+ * regardless of the bit value.
39
+ */
40
+ interCharTimeout?: RtuTimingValue;
41
+ /**
42
+ * Buffer pool size per connection (bytes). Defaults to `MAX_FRAME_LENGTH * 2`
43
+ * (512 bytes). Increase this if you expect frames larger than 256 bytes or
44
+ * heavy pipelining on a single connection.
45
+ */
46
+ poolSize?: number;
47
+ }
48
+ /** Resolved RTU timing values in milliseconds. */
49
+ export interface ResolvedRtuTiming {
50
+ intervalBetweenFrames: number;
51
+ interCharTimeout: number;
52
+ }
53
+ /**
54
+ * Resolve Modbus RTU timing parameters from user options into milliseconds.
55
+ *
56
+ * - `intervalBetweenFrames` (t3.5): if omitted and `baudRate` is present,
57
+ * defaults to 38.5 bits per Modbus V1.02 §2.5.1.1. Pass `0` to disable.
58
+ * - `interCharTimeout` (t1.5): opt-in; only resolved when explicitly provided.
59
+ *
60
+ * Per the spec, at baud rates > 19200 fixed values are used
61
+ * (1.75 ms for t3.5, 0.75 ms for t1.5) regardless of the bit value.
62
+ */
63
+ export declare function resolveRtuTiming(opts?: RtuProtocolOptions, baudRate?: number): ResolvedRtuTiming;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Normalize an IP address by stripping the IPv4-mapped IPv6 prefix.
3
+ * This ensures consistent comparison of addresses like `::ffff:192.168.1.1`.
4
+ */
5
+ export declare function normalizeAddress(address?: string): string;
6
+ /**
7
+ * Check whether a remote address is allowed by the given whitelist.
8
+ * IPv4-mapped IPv6 addresses are normalized before comparison.
9
+ * Returns `true` when whitelist is absent or empty.
10
+ */
11
+ export declare function isWhitelisted(address: string | undefined, whitelist: string[] | undefined): boolean;
@@ -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,7 +1,7 @@
1
1
  {
2
2
  "name": "njs-modbus",
3
- "version": "2.1.0",
4
- "description": "A pure JavaScript implemetation of Modbus for NodeJS.",
3
+ "version": "3.1.0",
4
+ "description": "A pure JavaScript implementation of Modbus for Node.js.",
5
5
  "keywords": [
6
6
  "modbus",
7
7
  "rtu",
@@ -10,17 +10,16 @@
10
10
  "tcp",
11
11
  "udp"
12
12
  ],
13
- "homepage": "https://github.com/xiejay97/njs-modbus/tree/main/packages/ui#readme",
13
+ "homepage": "https://github.com/xiejay97/njs-modbus/tree/main#readme",
14
14
  "bugs": {
15
15
  "url": "https://github.com/xiejay97/njs-modbus/issues"
16
16
  },
17
17
  "repository": {
18
18
  "type": "git",
19
- "url": "https://github.com/xiejay97/njs-modbus.git"
19
+ "url": "git+https://github.com/xiejay97/njs-modbus.git"
20
20
  },
21
21
  "license": "MIT",
22
22
  "author": "Xie Jay <xiejay97@gmail.com>",
23
- "packageManager": "pnpm@10.10.0",
24
23
  "sideEffects": false,
25
24
  "type": "module",
26
25
  "main": "dist/index.cjs",
@@ -32,8 +31,10 @@
32
31
  "*.d.ts"
33
32
  ],
34
33
  "scripts": {
35
- "build": "rollup -c",
36
- "test": "tsx --test test/*.test.ts",
34
+ "benchmark": "tsx benchmark/encode-decode.ts && echo && tsx benchmark/tcp-throughput.ts && echo && tsx benchmark/concurrent.ts",
35
+ "benchmark:report": "tsx benchmark/generate-report.ts",
36
+ "build": "node -e \"fs.rmSync('dist', {recursive: true, force: true})\" && rollup -c",
37
+ "test": "tsx --test test/**/*.test.ts",
37
38
  "util:sort-package-json": "sort-package-json"
38
39
  },
39
40
  "devDependencies": {
@@ -51,7 +52,9 @@
51
52
  "eslint-config-prettier": "^10.1.5",
52
53
  "eslint-plugin-import": "2.31.0",
53
54
  "eslint-plugin-prettier": "^5.4.1",
55
+ "jsmodbus": "^4.0.10",
54
56
  "microbundle": "^0.15.1",
57
+ "modbus-serial": "^8.0.25",
55
58
  "nx": "21.1.3",
56
59
  "prettier": "^3.5.3",
57
60
  "rollup": "^4.43.0",
@@ -62,9 +65,13 @@
62
65
  "tslib": "^2.8.1",
63
66
  "tsx": "^4.21.0",
64
67
  "typescript": "~5.8.3",
65
- "typescript-eslint": "^8.34.0"
68
+ "typescript-eslint": "^8.34.0",
69
+ "why-is-node-running": "^3.2.2"
66
70
  },
67
71
  "peerDependencies": {
68
72
  "serialport": ">=12.0.0"
73
+ },
74
+ "engines": {
75
+ "node": ">=18.19"
69
76
  }
70
77
  }
@@ -1,42 +0,0 @@
1
- import type { BindOptions, SocketOptions } from 'node:dgram';
2
- import { AbstractPhysicalLayer } from './abstract-physical-layer';
3
- export interface UdpPhysicalLayerOptions {
4
- /** dgram Socket creation options. */
5
- socket?: Partial<SocketOptions>;
6
- /**
7
- * Provided → client mode (send to this single remote, filter inbound).
8
- * Omitted → server mode (bind locally, accept from any rinfo).
9
- */
10
- remote?: {
11
- port?: number;
12
- address?: string;
13
- };
14
- /**
15
- * Server mode only. Each unique rinfo (`address:port`) gets its own
16
- * `PhysicalConnection`; if no datagram arrives within this many ms, the
17
- * connection is evicted (`connection-close` emitted, upper-layer framing
18
- * state released). Default 30000. Set 0 to disable eviction.
19
- */
20
- idleTimeout?: number;
21
- }
22
- export declare class UdpPhysicalLayer extends AbstractPhysicalLayer {
23
- TYPE: 'SERIAL' | 'NET';
24
- private _socket;
25
- private _connections;
26
- private _isOpen;
27
- private _isOpening;
28
- private _destroyed;
29
- private _socketOptions?;
30
- private _port;
31
- private _address?;
32
- private _idleTimeout;
33
- isServer: boolean;
34
- get isOpen(): boolean;
35
- get destroyed(): boolean;
36
- constructor(options?: UdpPhysicalLayerOptions);
37
- open(options?: BindOptions): Promise<void>;
38
- private _handleMessage;
39
- write(data: Buffer): Promise<void>;
40
- close(): Promise<void>;
41
- destroy(): Promise<void>;
42
- }
@@ -1,2 +0,0 @@
1
- /** Cross-process unique id: `${prefix}-${uuid-v4}`. */
2
- export declare function genConnectionId(prefix: string): string;
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1,38 +0,0 @@
1
- import type { Server } from 'node:net';
2
- import { Socket } from 'node:net';
3
- export interface RawClient {
4
- socket: Socket;
5
- write: (data: Buffer) => Promise<void>;
6
- end: () => Promise<void>;
7
- onData: (callback: (chunk: Buffer) => void) => void;
8
- collected: () => Buffer;
9
- }
10
- export declare function connectRawClient(port: number, host?: string): Promise<RawClient>;
11
- export interface RawServer {
12
- server: Server;
13
- port: number;
14
- /** Resolves with the first accepted socket. */
15
- firstSocket: Promise<Socket>;
16
- close: () => Promise<void>;
17
- }
18
- export declare function startRawServer(port?: number, host?: string): Promise<RawServer>;
19
- export declare function sleep(ms: number): Promise<void>;
20
- /** Build a Modbus TCP MBAP request frame. */
21
- export declare function buildMbapRequest(opts: {
22
- transaction: number;
23
- unit: number;
24
- fc: number;
25
- data: Buffer | number[];
26
- }): Buffer;
27
- /** Build a Modbus RTU frame (PDU + 2-byte CRC, little-endian). */
28
- export declare function buildRtuFrame(opts: {
29
- unit: number;
30
- fc: number;
31
- data: Buffer | number[];
32
- }): Buffer;
33
- /** Build a Modbus ASCII frame: `:` + hex(unit + fc + data + LRC) + CRLF. */
34
- export declare function buildAsciiFrame(opts: {
35
- unit: number;
36
- fc: number;
37
- data: Buffer | number[];
38
- }): Buffer;
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};