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.
- package/README.md +324 -147
- package/README.zh-CN.md +380 -0
- package/dist/index.cjs +2552 -2288
- package/dist/index.d.ts +400 -233
- package/dist/index.mjs +2548 -2287
- package/dist/src/error-code.d.ts +2 -24
- package/dist/src/layers/application/abstract-application-layer.d.ts +13 -8
- package/dist/src/layers/application/ascii-application-layer.d.ts +9 -11
- package/dist/src/layers/application/rtu-application-layer.d.ts +20 -47
- package/dist/src/layers/application/tcp-application-layer.d.ts +9 -8
- package/dist/src/layers/physical/abstract-physical-layer.d.ts +43 -14
- package/dist/src/layers/physical/index.d.ts +9 -3
- package/dist/src/layers/physical/serial-physical-layer.d.ts +43 -15
- package/dist/src/layers/physical/tcp-client-physical-layer.d.ts +13 -12
- package/dist/src/layers/physical/tcp-physical-connection.d.ts +16 -0
- package/dist/src/layers/physical/tcp-server-physical-layer.d.ts +24 -14
- package/dist/src/layers/physical/udp-client-physical-layer.d.ts +33 -0
- package/dist/src/layers/physical/udp-server-physical-layer.d.ts +50 -0
- package/dist/src/layers/physical/utils.d.ts +39 -0
- package/dist/src/layers/physical/vars.d.ts +11 -0
- package/dist/src/master/master-session.d.ts +3 -3
- package/dist/src/master/master.d.ts +55 -19
- package/dist/src/slave/slave.d.ts +58 -33
- package/dist/src/types.d.ts +2 -2
- package/dist/src/utils/callback.d.ts +8 -0
- package/dist/src/utils/crc.d.ts +1 -1
- package/dist/src/utils/index.d.ts +7 -4
- package/dist/src/utils/predictRtuFrameLength.d.ts +13 -16
- package/dist/src/utils/promisify-cb.d.ts +4 -0
- package/dist/src/utils/rtu-timing.d.ts +63 -0
- package/dist/src/utils/whitelist.d.ts +11 -0
- package/dist/src/vars.d.ts +2 -0
- package/package.json +15 -8
- package/dist/src/layers/physical/udp-physical-layer.d.ts +0 -42
- package/dist/src/utils/genConnectionId.d.ts +0 -2
- package/dist/test/adu-buffer.test.d.ts +0 -1
- package/dist/test/ascii-hex-sentry.test.d.ts +0 -1
- package/dist/test/ascii-hex-validation.test.d.ts +0 -1
- package/dist/test/ascii-tcp-fragmentation.test.d.ts +0 -1
- package/dist/test/check-range.test.d.ts +0 -1
- package/dist/test/fallback-atomic.test.d.ts +0 -1
- package/dist/test/fallback-serial.test.d.ts +0 -1
- package/dist/test/fc17-serverid-validation.test.d.ts +0 -1
- package/dist/test/fc43-conformity.test.d.ts +0 -1
- package/dist/test/fc43-utf8-objects.test.d.ts +0 -1
- package/dist/test/gen-connection-id.test.d.ts +0 -1
- package/dist/test/helpers/raw-tcp.d.ts +0 -38
- package/dist/test/master-concurrent.test.d.ts +0 -1
- package/dist/test/modbus-error.test.d.ts +0 -1
- package/dist/test/physical-lifecycle.test.d.ts +0 -1
- package/dist/test/predict-rtu.test.d.ts +0 -1
- package/dist/test/rtu-custom-fc.test.d.ts +0 -1
- package/dist/test/rtu-pool-overflow.test.d.ts +0 -1
- package/dist/test/rtu-t15-timing.test.d.ts +0 -1
- package/dist/test/rtu-t35-default.test.d.ts +0 -1
- package/dist/test/rtu-t35-strict.test.d.ts +0 -1
- package/dist/test/rtu-tcp-fragmentation.test.d.ts +0 -1
- package/dist/test/serial-e2e.test.d.ts +0 -1
- package/dist/test/slave-multi-connection.test.d.ts +0 -1
- package/dist/test/slave.test.d.ts +0 -1
- package/dist/test/tcp-fragmentation.test.d.ts +0 -1
- package/dist/test/udp-multi-client.test.d.ts +0 -1
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import type {
|
|
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
|
-
|
|
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<
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
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
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
get
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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 {
|
|
2
|
-
import type { AbstractPhysicalLayer } from '../layers/physical';
|
|
3
|
-
import type { CustomFunctionCode,
|
|
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?:
|
|
14
|
-
readDiscreteInputs?:
|
|
15
|
-
readCoils?:
|
|
16
|
-
writeSingleCoil?:
|
|
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?:
|
|
21
|
-
readInputRegisters?:
|
|
22
|
-
readHoldingRegisters?:
|
|
23
|
-
writeSingleRegister?:
|
|
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?:
|
|
29
|
+
writeMultipleRegisters?: MaybeAsyncFunction<(address: number, value: number[]) => void>;
|
|
28
30
|
/**
|
|
29
31
|
* If omitted, defaults to call `readHoldingRegisters` and `writeSingleRegister`.
|
|
30
32
|
*/
|
|
31
|
-
maskWriteRegister?:
|
|
32
|
-
reportServerId?:
|
|
33
|
-
readDeviceIdentification?:
|
|
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<
|
|
56
|
-
|
|
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
|
|
66
|
+
private _physicalLayer;
|
|
67
|
+
private _protocol;
|
|
68
|
+
private _appLayers;
|
|
61
69
|
private _customFunctionCodes;
|
|
62
70
|
private _locks;
|
|
63
|
-
private
|
|
64
|
-
|
|
65
|
-
get
|
|
66
|
-
|
|
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
|
|
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
|
-
|
|
90
|
-
|
|
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 {};
|
package/dist/src/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type Callback<T> = (error: Error | null, value: T) => void;
|
|
2
|
-
export type
|
|
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?:
|
|
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;
|
package/dist/src/utils/crc.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function crc(data: Uint8Array,
|
|
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
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
13
|
-
*
|
|
14
|
-
* -
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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):
|
|
17
|
+
export declare function predictRtuFrameLength(buffer: Buffer, isResponse: boolean): number;
|
|
@@ -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;
|
package/dist/src/vars.d.ts
CHANGED
|
@@ -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": "
|
|
4
|
-
"description": "A pure JavaScript
|
|
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
|
|
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
|
-
"
|
|
36
|
-
"
|
|
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 +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 {};
|