njs-modbus 1.5.0 → 2.0.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 +16 -0
- package/dist/index.cjs +1938 -1148
- package/dist/index.d.ts +308 -89
- package/dist/index.mjs +1931 -1149
- package/dist/src/error-code.d.ts +27 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/layers/application/abstract-application-layer.d.ts +11 -8
- package/dist/src/layers/application/ascii-application-layer.d.ts +14 -10
- package/dist/src/layers/application/index.d.ts +2 -0
- package/dist/src/layers/application/rtu-application-layer.d.ts +52 -18
- package/dist/src/layers/application/tcp-application-layer.d.ts +4 -8
- package/dist/src/layers/physical/abstract-physical-layer.d.ts +5 -1
- package/dist/src/layers/physical/index.d.ts +1 -0
- package/dist/src/layers/physical/serial-physical-layer.d.ts +2 -0
- package/dist/src/layers/physical/tcp-client-physical-layer.d.ts +3 -0
- package/dist/src/layers/physical/tcp-server-physical-layer.d.ts +3 -1
- package/dist/src/layers/physical/udp-physical-layer.d.ts +25 -10
- package/dist/src/master/index.d.ts +2 -0
- package/dist/src/master/master-session.d.ts +18 -0
- package/dist/src/master/master.d.ts +27 -5
- package/dist/src/slave/index.d.ts +1 -1
- package/dist/src/slave/slave.d.ts +18 -5
- package/dist/src/types.d.ts +33 -2
- package/dist/src/utils/bitsToMs.d.ts +13 -0
- package/dist/src/utils/crc.d.ts +1 -1
- package/dist/src/utils/genConnectionId.d.ts +2 -0
- package/dist/src/utils/index.d.ts +5 -1
- package/dist/src/utils/isUint8.d.ts +8 -0
- package/dist/src/utils/predictRtuFrameLength.d.ts +20 -0
- package/dist/src/vars.d.ts +47 -0
- package/dist/test/adu-buffer.test.d.ts +1 -0
- package/dist/test/ascii-hex-sentry.test.d.ts +1 -0
- package/dist/test/ascii-hex-validation.test.d.ts +1 -0
- package/dist/test/ascii-tcp-fragmentation.test.d.ts +1 -0
- package/dist/test/check-range.test.d.ts +1 -0
- package/dist/test/fallback-atomic.test.d.ts +1 -0
- package/dist/test/fallback-serial.test.d.ts +1 -0
- package/dist/test/fc17-serverid-validation.test.d.ts +1 -0
- package/dist/test/fc43-conformity.test.d.ts +1 -0
- package/dist/test/fc43-utf8-objects.test.d.ts +1 -0
- package/dist/test/gen-connection-id.test.d.ts +1 -0
- package/dist/test/helpers/raw-tcp.d.ts +38 -0
- package/dist/test/master-concurrent.test.d.ts +1 -0
- package/dist/test/modbus-error.test.d.ts +1 -0
- package/dist/test/physical-lifecycle.test.d.ts +1 -0
- package/dist/test/predict-rtu.test.d.ts +1 -0
- package/dist/test/rtu-custom-fc.test.d.ts +1 -0
- package/dist/test/rtu-pool-overflow.test.d.ts +1 -0
- package/dist/test/rtu-t15-timing.test.d.ts +1 -0
- package/dist/test/rtu-t35-default.test.d.ts +1 -0
- package/dist/test/rtu-t35-strict.test.d.ts +1 -0
- package/dist/test/rtu-tcp-fragmentation.test.d.ts +1 -0
- package/dist/test/serial-e2e.test.d.ts +1 -0
- package/dist/test/slave-multi-connection.test.d.ts +1 -0
- package/dist/test/tcp-fragmentation.test.d.ts +1 -0
- package/dist/test/udp-multi-client.test.d.ts +1 -0
- package/package.json +4 -2
- package/dist/src/utils/getThreePointFiveT.d.ts +0 -7
package/dist/src/error-code.d.ts
CHANGED
|
@@ -9,5 +9,31 @@ export declare enum ErrorCode {
|
|
|
9
9
|
GATEWAY_PATH_UNAVAILABLE = 10,
|
|
10
10
|
GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND = 11
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
/** Internal error codes for programmatic error handling. */
|
|
13
|
+
export declare const ModbusErrorCode: {
|
|
14
|
+
readonly TIMEOUT: "ETIMEOUT";
|
|
15
|
+
readonly INVALID_RESPONSE: "EINVALID_RESPONSE";
|
|
16
|
+
readonly INSUFFICIENT_DATA: "EINSUFFICIENT_DATA";
|
|
17
|
+
readonly MASTER_CLOSED: "EMASTER_CLOSED";
|
|
18
|
+
readonly MASTER_DESTROYED: "EMASTER_DESTROYED";
|
|
19
|
+
readonly CONCURRENT_NOT_TCP: "ECONCURRENT_NOT_TCP";
|
|
20
|
+
readonly PORT_DESTROYED: "EPORT_DESTROYED";
|
|
21
|
+
readonly PORT_ALREADY_OPEN: "EPORT_ALREADY_OPEN";
|
|
22
|
+
readonly PORT_NOT_OPEN: "EPORT_NOT_OPEN";
|
|
23
|
+
readonly NOT_SUPPORTED: "ENOT_SUPPORTED";
|
|
24
|
+
readonly INVALID_DATA: "EINVALID_DATA";
|
|
25
|
+
readonly INVALID_HEX: "EINVALID_HEX";
|
|
26
|
+
readonly CRC_MISMATCH: "ECRC_MISMATCH";
|
|
27
|
+
readonly LRC_MISMATCH: "ELRC_MISMATCH";
|
|
28
|
+
readonly INCOMPLETE_FRAME: "EINCOMPLETE_FRAME";
|
|
29
|
+
readonly T1_5_EXCEEDED: "ET1_5_EXCEEDED";
|
|
30
|
+
readonly UNKNOWN_FC: "EUNKNOWN_FC";
|
|
31
|
+
readonly INVALID_ROLE: "EINVALID_ROLE";
|
|
32
|
+
readonly RANGE: "ERANGE";
|
|
33
|
+
};
|
|
34
|
+
export declare class ModbusError extends Error {
|
|
35
|
+
readonly code: string;
|
|
36
|
+
constructor(code: string, message?: string);
|
|
37
|
+
}
|
|
38
|
+
export declare function getErrorByCode(code: ErrorCode): ModbusError;
|
|
13
39
|
export declare function getCodeByError(err: Error): ErrorCode;
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
import type { ApplicationDataUnit } from '../../types';
|
|
1
|
+
import type { ApplicationDataUnit, CustomFunctionCode } from '../../types';
|
|
2
|
+
import type { PhysicalConnection } from '../physical';
|
|
2
3
|
import EventEmitter from 'node:events';
|
|
3
4
|
interface AbstractApplicationLayerEvents {
|
|
4
5
|
framing: [frame: ApplicationDataUnit & {
|
|
5
6
|
buffer: Buffer;
|
|
6
|
-
}, response: (data: Buffer) => Promise<void
|
|
7
|
+
}, response: (data: Buffer) => Promise<void>, connection: PhysicalConnection];
|
|
8
|
+
'framing-error': [error: Error];
|
|
7
9
|
}
|
|
8
10
|
export declare abstract class AbstractApplicationLayer extends EventEmitter<AbstractApplicationLayerEvents> {
|
|
9
|
-
abstract
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
abstract readonly PROTOCOL: 'TCP' | 'RTU' | 'ASCII';
|
|
12
|
+
private _role?;
|
|
13
|
+
get role(): 'MASTER' | 'SLAVE';
|
|
14
|
+
set role(value: 'MASTER' | 'SLAVE');
|
|
15
|
+
flush(): void;
|
|
16
|
+
addCustomFunctionCode(cfc: CustomFunctionCode): void;
|
|
17
|
+
removeCustomFunctionCode(fc: number): void;
|
|
15
18
|
abstract encode(data: ApplicationDataUnit): Buffer;
|
|
16
19
|
abstract destroy(): void;
|
|
17
20
|
}
|
|
@@ -2,19 +2,23 @@ import type { ApplicationDataUnit } from '../../types';
|
|
|
2
2
|
import type { TcpClientPhysicalLayer, TcpServerPhysicalLayer, UdpPhysicalLayer } from '../physical';
|
|
3
3
|
import type { SerialPhysicalLayer } from '../physical';
|
|
4
4
|
import { AbstractApplicationLayer } from './abstract-application-layer';
|
|
5
|
+
export interface AsciiApplicationLayerOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Accept lowercase hex digits (`a-f`) in addition to uppercase (`A-F`).
|
|
8
|
+
* Default false (strict per Modbus V1.1b3 §2.2 — uppercase only).
|
|
9
|
+
* Non-hex characters are always rejected with a `framing-error`.
|
|
10
|
+
*/
|
|
11
|
+
lenientHex?: boolean;
|
|
12
|
+
}
|
|
5
13
|
export declare class AsciiApplicationLayer extends AbstractApplicationLayer {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
private
|
|
14
|
+
readonly PROTOCOL: "ASCII";
|
|
15
|
+
readonly lenientHex: boolean;
|
|
16
|
+
private _states;
|
|
9
17
|
private _removeAllListeners;
|
|
10
|
-
constructor(physicalLayer: SerialPhysicalLayer | TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer);
|
|
18
|
+
constructor(physicalLayer: SerialPhysicalLayer | TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer, options?: AsciiApplicationLayerOptions);
|
|
19
|
+
private getState;
|
|
20
|
+
flush(): void;
|
|
11
21
|
private framing;
|
|
12
|
-
startWaitingResponse(preCheck: ((frame: ApplicationDataUnit & {
|
|
13
|
-
buffer: Buffer;
|
|
14
|
-
}) => boolean | number | undefined)[], callback: (error: Error | null, frame?: ApplicationDataUnit & {
|
|
15
|
-
buffer: Buffer;
|
|
16
|
-
}) => void): void;
|
|
17
|
-
stopWaitingResponse(): void;
|
|
18
22
|
encode(data: ApplicationDataUnit): Buffer;
|
|
19
23
|
destroy(): void;
|
|
20
24
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export type { AsciiApplicationLayerOptions } from './ascii-application-layer';
|
|
2
|
+
export type { RtuApplicationLayerOptions } from './rtu-application-layer';
|
|
1
3
|
export { AbstractApplicationLayer } from './abstract-application-layer';
|
|
2
4
|
export { RtuApplicationLayer } from './rtu-application-layer';
|
|
3
5
|
export { AsciiApplicationLayer } from './ascii-application-layer';
|
|
@@ -1,26 +1,60 @@
|
|
|
1
|
-
import type { ApplicationDataUnit } from '../../types';
|
|
1
|
+
import type { ApplicationDataUnit, CustomFunctionCode } from '../../types';
|
|
2
2
|
import type { TcpClientPhysicalLayer, TcpServerPhysicalLayer, UdpPhysicalLayer } from '../physical';
|
|
3
3
|
import type { SerialPhysicalLayer } from '../physical';
|
|
4
4
|
import { AbstractApplicationLayer } from './abstract-application-layer';
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
export interface RtuApplicationLayerOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Inter-frame silence (Modbus RTU t3.5). Defaults to `{ unit: 'bit', value: 38.5 }`
|
|
8
|
+
* for serial (3.5 character times × 11 bits/char per Modbus V1.02 §2.5.1.1);
|
|
9
|
+
* ignored for TCP/UDP transports.
|
|
10
|
+
* - `{ unit: 'bit', value: 38.5 }` — spec bit-time approximation
|
|
11
|
+
* - `{ unit: 'ms', value: 20 }` — explicit milliseconds
|
|
12
|
+
*
|
|
13
|
+
* Per Modbus V1.02 §2.5.1.1, at baud rates > 19200 the spec uses a fixed
|
|
14
|
+
* 1.75 ms regardless of the bit value supplied.
|
|
15
|
+
*/
|
|
16
|
+
intervalBetweenFrames?: {
|
|
17
|
+
unit: 'bit' | 'ms';
|
|
18
|
+
value: number;
|
|
19
|
+
};
|
|
11
20
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
21
|
+
* Inter-character timeout (Modbus RTU t1.5). Opt-in; **disabled** by default
|
|
22
|
+
* because Node.js `setTimeout` precision (~1 ms minimum, ~15.6 ms on Windows)
|
|
23
|
+
* is too coarse to reliably honor t1.5 at common baud rates without
|
|
24
|
+
* false-positive frame discards. When set, a mid-frame gap exceeding this
|
|
25
|
+
* duration discards the in-progress buffer and emits `framing-error`.
|
|
26
|
+
* Only takes effect on serial transports.
|
|
27
|
+
*
|
|
28
|
+
* - `{ unit: 'bit', value: 21 }` — bit-time approximation (~1.5 char times)
|
|
29
|
+
* - `{ unit: 'ms', value: 1 }` — explicit milliseconds
|
|
30
|
+
*
|
|
31
|
+
* Per Modbus V1.02 §2.5.1.1, at baud rates > 19200 the spec uses a fixed
|
|
32
|
+
* 0.75 ms regardless of the bit value supplied.
|
|
15
33
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
interCharTimeout?: {
|
|
35
|
+
unit: 'bit' | 'ms';
|
|
36
|
+
value: number;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export declare class RtuApplicationLayer extends AbstractApplicationLayer {
|
|
40
|
+
readonly PROTOCOL: "RTU";
|
|
41
|
+
private _states;
|
|
42
|
+
private _customFunctionCodes;
|
|
43
|
+
private _removeAllListeners;
|
|
44
|
+
private _threePointFiveT;
|
|
45
|
+
private _onePointFiveT;
|
|
46
|
+
constructor(physicalLayer: SerialPhysicalLayer | TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer, options?: RtuApplicationLayerOptions);
|
|
47
|
+
private getState;
|
|
48
|
+
private clearStateTimers;
|
|
49
|
+
private clearState;
|
|
50
|
+
flush(): void;
|
|
51
|
+
addCustomFunctionCode(cfc: CustomFunctionCode): void;
|
|
52
|
+
removeCustomFunctionCode(fc: number): void;
|
|
53
|
+
private flushBuffer;
|
|
54
|
+
private tryExtract;
|
|
55
|
+
private checkExpected;
|
|
56
|
+
private crcMatches;
|
|
57
|
+
private deliverFrame;
|
|
24
58
|
encode(data: ApplicationDataUnit): Buffer;
|
|
25
59
|
destroy(): void;
|
|
26
60
|
}
|
|
@@ -2,17 +2,13 @@ import type { ApplicationDataUnit } from '../../types';
|
|
|
2
2
|
import type { TcpClientPhysicalLayer, TcpServerPhysicalLayer, UdpPhysicalLayer } from '../physical';
|
|
3
3
|
import { AbstractApplicationLayer } from './abstract-application-layer';
|
|
4
4
|
export declare class TcpApplicationLayer extends AbstractApplicationLayer {
|
|
5
|
-
|
|
5
|
+
readonly PROTOCOL: "TCP";
|
|
6
6
|
private _transactionId;
|
|
7
|
+
private _buffers;
|
|
7
8
|
private _removeAllListeners;
|
|
8
9
|
constructor(physicalLayer: TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer);
|
|
9
|
-
private
|
|
10
|
-
|
|
11
|
-
buffer: Buffer;
|
|
12
|
-
}) => boolean | number | undefined)[], callback: (error: Error | null, frame?: ApplicationDataUnit & {
|
|
13
|
-
buffer: Buffer;
|
|
14
|
-
}) => void): void;
|
|
15
|
-
stopWaitingResponse(): void;
|
|
10
|
+
private tryExtract;
|
|
11
|
+
private processFrame;
|
|
16
12
|
encode(data: ApplicationDataUnit): Buffer;
|
|
17
13
|
destroy(): void;
|
|
18
14
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import EventEmitter from 'node:events';
|
|
2
|
+
export interface PhysicalConnection {
|
|
3
|
+
readonly id: string | number;
|
|
4
|
+
}
|
|
2
5
|
interface AbstractPhysicalLayerEvents {
|
|
3
|
-
data: [data: Buffer, response: (data: Buffer) => Promise<void
|
|
6
|
+
data: [data: Buffer, response: (data: Buffer) => Promise<void>, connection: PhysicalConnection];
|
|
4
7
|
write: [data: Buffer];
|
|
5
8
|
error: [error: Error];
|
|
9
|
+
'connection-close': [connection: PhysicalConnection];
|
|
6
10
|
close: [];
|
|
7
11
|
}
|
|
8
12
|
export declare abstract class AbstractPhysicalLayer extends EventEmitter<AbstractPhysicalLayerEvents> {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export type { PhysicalConnection } from './abstract-physical-layer';
|
|
1
2
|
export { AbstractPhysicalLayer } from './abstract-physical-layer';
|
|
2
3
|
export { SerialPhysicalLayer } from './serial-physical-layer';
|
|
3
4
|
export { TcpClientPhysicalLayer } from './tcp-client-physical-layer';
|
|
@@ -27,6 +27,8 @@ export interface SerialPhysicalLayerOptions {
|
|
|
27
27
|
export declare class SerialPhysicalLayer extends AbstractPhysicalLayer {
|
|
28
28
|
TYPE: 'SERIAL' | 'NET';
|
|
29
29
|
private _serialport;
|
|
30
|
+
private _connection;
|
|
31
|
+
private _isOpening;
|
|
30
32
|
private _destroyed;
|
|
31
33
|
private _baudRate;
|
|
32
34
|
get isOpen(): boolean;
|
|
@@ -3,8 +3,11 @@ import { AbstractPhysicalLayer } from './abstract-physical-layer';
|
|
|
3
3
|
export declare class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
|
|
4
4
|
TYPE: 'SERIAL' | 'NET';
|
|
5
5
|
private _socket;
|
|
6
|
+
private _connection;
|
|
6
7
|
private _isOpen;
|
|
8
|
+
private _isOpening;
|
|
7
9
|
private _destroyed;
|
|
10
|
+
private _socketOptions?;
|
|
8
11
|
get isOpen(): boolean;
|
|
9
12
|
get destroyed(): boolean;
|
|
10
13
|
constructor(options?: SocketConstructorOpts);
|
|
@@ -4,8 +4,10 @@ export declare class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
|
|
|
4
4
|
TYPE: 'SERIAL' | 'NET';
|
|
5
5
|
private _server;
|
|
6
6
|
private _isOpen;
|
|
7
|
+
private _isOpening;
|
|
7
8
|
private _destroyed;
|
|
8
|
-
private
|
|
9
|
+
private _connections;
|
|
10
|
+
private _serverOptions?;
|
|
9
11
|
get isOpen(): boolean;
|
|
10
12
|
get destroyed(): boolean;
|
|
11
13
|
constructor(options?: NetConnectOpts);
|
|
@@ -1,26 +1,41 @@
|
|
|
1
1
|
import type { BindOptions, SocketOptions } from 'node:dgram';
|
|
2
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
|
+
}
|
|
3
22
|
export declare class UdpPhysicalLayer extends AbstractPhysicalLayer {
|
|
4
23
|
TYPE: 'SERIAL' | 'NET';
|
|
5
24
|
private _socket;
|
|
25
|
+
private _connections;
|
|
6
26
|
private _isOpen;
|
|
27
|
+
private _isOpening;
|
|
7
28
|
private _destroyed;
|
|
29
|
+
private _socketOptions?;
|
|
8
30
|
private _port;
|
|
9
31
|
private _address?;
|
|
32
|
+
private _idleTimeout;
|
|
10
33
|
isServer: boolean;
|
|
11
34
|
get isOpen(): boolean;
|
|
12
35
|
get destroyed(): boolean;
|
|
13
|
-
|
|
14
|
-
*
|
|
15
|
-
* @param options
|
|
16
|
-
* @param remote If omitted, as server.
|
|
17
|
-
* Otherwise as client.
|
|
18
|
-
*/
|
|
19
|
-
constructor(options?: Partial<SocketOptions>, remote?: {
|
|
20
|
-
port?: number;
|
|
21
|
-
address?: string;
|
|
22
|
-
});
|
|
36
|
+
constructor(options?: UdpPhysicalLayerOptions);
|
|
23
37
|
open(options?: BindOptions): Promise<void>;
|
|
38
|
+
private _handleMessage;
|
|
24
39
|
write(data: Buffer): Promise<void>;
|
|
25
40
|
close(): Promise<void>;
|
|
26
41
|
destroy(): Promise<void>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ApplicationDataUnit } from '../types';
|
|
2
|
+
type Frame = ApplicationDataUnit & {
|
|
3
|
+
buffer: Buffer;
|
|
4
|
+
};
|
|
5
|
+
type PreCheck = (frame: Frame) => boolean | number | undefined;
|
|
6
|
+
type Callback = (error: Error | null, frame?: Frame) => void;
|
|
7
|
+
export declare const FIFO_KEY: "fifo";
|
|
8
|
+
export declare class MasterSession {
|
|
9
|
+
private _waiters;
|
|
10
|
+
start(key: string | number, preCheck: PreCheck[], callback: Callback): void;
|
|
11
|
+
stop(key: string | number): void;
|
|
12
|
+
stopAll(error: Error): void;
|
|
13
|
+
has(key: string | number): boolean;
|
|
14
|
+
handleFrame(frame: Frame): void;
|
|
15
|
+
handleError(error: Error): void;
|
|
16
|
+
private runPreChecks;
|
|
17
|
+
}
|
|
18
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AbstractApplicationLayer } from '../layers/application';
|
|
2
2
|
import type { AbstractPhysicalLayer } from '../layers/physical';
|
|
3
|
-
import type { DeviceIdentification, ServerId } from '../types';
|
|
3
|
+
import type { CustomFunctionCode, DeviceIdentification, ServerId } from '../types';
|
|
4
4
|
import EventEmitter from 'node:events';
|
|
5
5
|
interface ModbusMasterEvents {
|
|
6
6
|
error: [error: Error];
|
|
@@ -13,14 +13,32 @@ interface ReturnValue<T> {
|
|
|
13
13
|
data: T;
|
|
14
14
|
buffer: Buffer;
|
|
15
15
|
}
|
|
16
|
+
export interface ModbusMasterOptions {
|
|
17
|
+
/** Per-request timeout in ms. Default 1000. */
|
|
18
|
+
timeout?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Enable pipelined concurrent requests on a single connection.
|
|
21
|
+
* Only valid for Modbus TCP application layer.
|
|
22
|
+
* Default false (FIFO queue, requests are serialized).
|
|
23
|
+
*/
|
|
24
|
+
concurrent?: boolean;
|
|
25
|
+
}
|
|
16
26
|
export declare class ModbusMaster<A extends AbstractApplicationLayer, P extends AbstractPhysicalLayer> extends EventEmitter<ModbusMasterEvents> {
|
|
17
27
|
private applicationLayer;
|
|
18
28
|
private physicalLayer;
|
|
29
|
+
private _masterSession;
|
|
30
|
+
private _queue;
|
|
31
|
+
private _draining;
|
|
32
|
+
private _nextTid;
|
|
33
|
+
private _closed;
|
|
19
34
|
timeout: number;
|
|
35
|
+
readonly concurrent: boolean;
|
|
20
36
|
get isOpen(): boolean;
|
|
21
37
|
get destroyed(): boolean;
|
|
22
|
-
constructor(applicationLayer: A, physicalLayer: P,
|
|
23
|
-
private
|
|
38
|
+
constructor(applicationLayer: A, physicalLayer: P, options?: ModbusMasterOptions);
|
|
39
|
+
private send;
|
|
40
|
+
private _drain;
|
|
41
|
+
private _exchange;
|
|
24
42
|
private writeFC1Or2;
|
|
25
43
|
writeFC1: this['readCoils'];
|
|
26
44
|
readCoils(unit: 0, address: number, length: number, timeout?: number): Promise<void>;
|
|
@@ -48,8 +66,8 @@ export declare class ModbusMaster<A extends AbstractApplicationLayer, P extends
|
|
|
48
66
|
writeMultipleRegisters(unit: 0, address: number, value: number[], timeout?: number): Promise<void>;
|
|
49
67
|
writeMultipleRegisters(unit: number, address: number, value: number[], timeout?: number): Promise<ReturnValue<number[]>>;
|
|
50
68
|
handleFC17: this['reportServerId'];
|
|
51
|
-
reportServerId(unit: 0, timeout?: number): Promise<void>;
|
|
52
|
-
reportServerId(unit: number, timeout?: number): Promise<ReturnValue<ServerId>>;
|
|
69
|
+
reportServerId(unit: 0, serverIdLength?: number, timeout?: number): Promise<void>;
|
|
70
|
+
reportServerId(unit: number, serverIdLength?: number, timeout?: number): Promise<ReturnValue<ServerId>>;
|
|
53
71
|
handleFC22: this['maskWriteRegister'];
|
|
54
72
|
maskWriteRegister(unit: 0, address: number, andMask: number, orMask: number, timeout?: number): Promise<void>;
|
|
55
73
|
maskWriteRegister(unit: number, address: number, andMask: number, orMask: number, timeout?: number): Promise<ReturnValue<{
|
|
@@ -74,6 +92,10 @@ export declare class ModbusMaster<A extends AbstractApplicationLayer, P extends
|
|
|
74
92
|
handleFC43_14: this['readDeviceIdentification'];
|
|
75
93
|
readDeviceIdentification(unit: 0, readDeviceIDCode: number, objectId: number, timeout?: number): Promise<void>;
|
|
76
94
|
readDeviceIdentification(unit: number, readDeviceIDCode: number, objectId: number, timeout?: number): Promise<ReturnValue<DeviceIdentification>>;
|
|
95
|
+
addCustomFunctionCode(cfc: CustomFunctionCode): void;
|
|
96
|
+
removeCustomFunctionCode(fc: number): void;
|
|
97
|
+
sendCustomFC(unit: 0, fc: number, data: Buffer | number[], timeout?: number): Promise<void>;
|
|
98
|
+
sendCustomFC(unit: number, fc: number, data: Buffer | number[], timeout?: number): Promise<Buffer>;
|
|
77
99
|
open(...args: Parameters<P['open']>): Promise<void>;
|
|
78
100
|
close(): Promise<void>;
|
|
79
101
|
destroy(): Promise<void>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type { ModbusSlaveModel } from './slave';
|
|
1
|
+
export type { ModbusSlaveModel, ModbusSlaveOptions } from './slave';
|
|
2
2
|
export { ModbusSlave } from './slave';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AbstractApplicationLayer } from '../layers/application';
|
|
2
2
|
import type { AbstractPhysicalLayer } from '../layers/physical';
|
|
3
|
-
import type { FConvertPromise, ServerId } from '../types';
|
|
3
|
+
import type { CustomFunctionCode, FConvertPromise, ServerId } from '../types';
|
|
4
4
|
import EventEmitter from 'node:events';
|
|
5
5
|
export interface ModbusSlaveModel {
|
|
6
6
|
unit?: number;
|
|
@@ -10,7 +10,7 @@ export interface ModbusSlaveModel {
|
|
|
10
10
|
* If provide the return value, use this value as data of `PDU` to respond.
|
|
11
11
|
* Otherwise keep the default read and write behavior.
|
|
12
12
|
*/
|
|
13
|
-
interceptor?: FConvertPromise<(fc: number, data:
|
|
13
|
+
interceptor?: FConvertPromise<(fc: number, data: Buffer) => Buffer | undefined>;
|
|
14
14
|
readDiscreteInputs?: FConvertPromise<(address: number, length: number) => boolean[]>;
|
|
15
15
|
readCoils?: FConvertPromise<(address: number, length: number) => boolean[]>;
|
|
16
16
|
writeSingleCoil?: FConvertPromise<(address: number, value: boolean) => void>;
|
|
@@ -44,15 +44,25 @@ interface ModbusSlaveEvents {
|
|
|
44
44
|
error: [error: Error];
|
|
45
45
|
close: [];
|
|
46
46
|
}
|
|
47
|
+
export interface ModbusSlaveOptions {
|
|
48
|
+
/**
|
|
49
|
+
* Pipelined concurrent processing of requests within one connection.
|
|
50
|
+
* Only valid for Modbus TCP application layer (TID disambiguates responses).
|
|
51
|
+
* Default false — per-connection FIFO. RTU/ASCII + concurrent=true throws.
|
|
52
|
+
*/
|
|
53
|
+
concurrent?: boolean;
|
|
54
|
+
}
|
|
47
55
|
export declare class ModbusSlave<A extends AbstractApplicationLayer, P extends AbstractPhysicalLayer> extends EventEmitter<ModbusSlaveEvents> {
|
|
48
56
|
private applicationLayer;
|
|
49
57
|
private physicalLayer;
|
|
50
58
|
models: Map<number, ModbusSlaveModel>;
|
|
51
|
-
|
|
52
|
-
private
|
|
59
|
+
readonly concurrent: boolean;
|
|
60
|
+
private _queues;
|
|
61
|
+
private _customFunctionCodes;
|
|
62
|
+
private _locks;
|
|
53
63
|
get isOpen(): boolean;
|
|
54
64
|
get destroyed(): boolean;
|
|
55
|
-
constructor(applicationLayer: A, physicalLayer: P);
|
|
65
|
+
constructor(applicationLayer: A, physicalLayer: P, options?: ModbusSlaveOptions);
|
|
56
66
|
private handleFC1;
|
|
57
67
|
private handleFC2;
|
|
58
68
|
private handleFC3;
|
|
@@ -69,9 +79,12 @@ export declare class ModbusSlave<A extends AbstractApplicationLayer, P extends A
|
|
|
69
79
|
private _drain;
|
|
70
80
|
private _processFrame;
|
|
71
81
|
private _intercept;
|
|
82
|
+
private withAddressLock;
|
|
72
83
|
private _handleFC;
|
|
73
84
|
add(model: ModbusSlaveModel): void;
|
|
74
85
|
remove(unit: number): void;
|
|
86
|
+
addCustomFunctionCode(cfc: CustomFunctionCode): void;
|
|
87
|
+
removeCustomFunctionCode(fc: number): void;
|
|
75
88
|
open(...args: Parameters<P['open']>): Promise<void>;
|
|
76
89
|
close(): Promise<void>;
|
|
77
90
|
destroy(): Promise<void>;
|
package/dist/src/types.d.ts
CHANGED
|
@@ -4,10 +4,11 @@ export interface ApplicationDataUnit {
|
|
|
4
4
|
transaction?: number;
|
|
5
5
|
unit: number;
|
|
6
6
|
fc: number;
|
|
7
|
-
data:
|
|
7
|
+
data: Buffer;
|
|
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
12
|
runIndicatorStatus?: boolean;
|
|
12
13
|
additionalData?: number[];
|
|
13
14
|
}
|
|
@@ -21,3 +22,33 @@ export interface DeviceIdentification {
|
|
|
21
22
|
value: string;
|
|
22
23
|
}[];
|
|
23
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Defines a non-standard / user-defined Modbus function code.
|
|
27
|
+
*
|
|
28
|
+
* Registration paths:
|
|
29
|
+
* - `RtuApplicationLayer.addCustomFunctionCode(cfc)` — framing only.
|
|
30
|
+
* - `ModbusSlave.addCustomFunctionCode(cfc)` — framing + slave-side dispatch via `handle`.
|
|
31
|
+
* - `ModbusMaster.addCustomFunctionCode(cfc)` + `ModbusMaster.sendCustomFC(...)` — framing + request issuance.
|
|
32
|
+
*
|
|
33
|
+
* The two `predict*` callbacks declare how to derive the total RTU frame length
|
|
34
|
+
* (PDU + 2-byte CRC) from leading bytes; they are required so the framing FSM
|
|
35
|
+
* can advance without the deleted sliding-window CRC fallback.
|
|
36
|
+
*
|
|
37
|
+
* Return `null` from a predictor to signal "need more bytes before I can decide".
|
|
38
|
+
* Return a positive integer (>= 4, <= 256) for the total frame length.
|
|
39
|
+
*/
|
|
40
|
+
export interface CustomFunctionCode {
|
|
41
|
+
fc: number;
|
|
42
|
+
/** Predict total RTU frame length for an incoming request (slave-side framing). */
|
|
43
|
+
predictRequestLength: (buffer: Buffer) => number | null;
|
|
44
|
+
/** Predict total RTU frame length for an incoming response (master-side framing). */
|
|
45
|
+
predictResponseLength: (buffer: Buffer) => number | null;
|
|
46
|
+
/**
|
|
47
|
+
* Slave-side handler. Receives PDU payload (bytes after `fc`, before CRC) and
|
|
48
|
+
* the unit ID being addressed; must return the PDU payload of the response.
|
|
49
|
+
*
|
|
50
|
+
* Throwing inside `handle` is turned into a Modbus exception response by the slave.
|
|
51
|
+
* If `handle` is omitted, the slave returns an ILLEGAL_FUNCTION exception for this FC.
|
|
52
|
+
*/
|
|
53
|
+
handle?: FConvertPromise<(data: Buffer, unit: number) => Buffer>;
|
|
54
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a number of bits to milliseconds at a given baud rate.
|
|
3
|
+
*
|
|
4
|
+
* Used to derive Modbus RTU timing intervals from bit counts — e.g. 38.5 bits
|
|
5
|
+
* = 3.5 character times at 11 bits/char (t3.5 inter-frame silence), or 16.5
|
|
6
|
+
* bits = 1.5 character times (t1.5 inter-character timeout), per Modbus V1.02
|
|
7
|
+
* §2.5.1.1.
|
|
8
|
+
*
|
|
9
|
+
* @param baudRate Serial port baud rate.
|
|
10
|
+
* @param bits Number of bits to convert.
|
|
11
|
+
* @returns Duration in milliseconds.
|
|
12
|
+
*/
|
|
13
|
+
export declare function bitsToMs(baudRate: number, bits: number): number;
|
package/dist/src/utils/crc.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function crc(data: Uint8Array): number;
|
|
1
|
+
export declare function crc(data: Uint8Array, seed?: number): number;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export { checkRange } from './checkRange';
|
|
2
2
|
export { crc } from './crc';
|
|
3
|
-
export {
|
|
3
|
+
export { genConnectionId } from './genConnectionId';
|
|
4
|
+
export { bitsToMs } from './bitsToMs';
|
|
5
|
+
export { isUint8 } from './isUint8';
|
|
4
6
|
export { lrc } from './lrc';
|
|
7
|
+
export { predictRtuFrameLength } from './predictRtuFrameLength';
|
|
8
|
+
export type { PredictResult } from './predictRtuFrameLength';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns true when `n` is an integer in the unsigned-byte range [0, 255].
|
|
3
|
+
*
|
|
4
|
+
* Used for byte-level Modbus payload validation (function-code values, raw
|
|
5
|
+
* byte arrays in FC17/FC43 responses) — rejects negative, fractional, NaN,
|
|
6
|
+
* Infinity, and out-of-range values uniformly.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isUint8(n: number): boolean;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type PredictResult = {
|
|
2
|
+
kind: 'length';
|
|
3
|
+
length: number;
|
|
4
|
+
} | {
|
|
5
|
+
kind: 'need-more';
|
|
6
|
+
} | {
|
|
7
|
+
kind: 'unknown';
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Predict the total RTU frame length (PDU + 2-byte CRC) given the leading bytes.
|
|
11
|
+
*
|
|
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.
|
|
19
|
+
*/
|
|
20
|
+
export declare function predictRtuFrameLength(buffer: Buffer, isResponse: boolean): PredictResult;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard Modbus function codes (V1.1b3 §6).
|
|
3
|
+
*/
|
|
4
|
+
export declare enum FunctionCode {
|
|
5
|
+
READ_COILS = 1,
|
|
6
|
+
READ_DISCRETE_INPUTS = 2,
|
|
7
|
+
READ_HOLDING_REGISTERS = 3,
|
|
8
|
+
READ_INPUT_REGISTERS = 4,
|
|
9
|
+
WRITE_SINGLE_COIL = 5,
|
|
10
|
+
WRITE_SINGLE_REGISTER = 6,
|
|
11
|
+
WRITE_MULTIPLE_COILS = 15,
|
|
12
|
+
WRITE_MULTIPLE_REGISTERS = 16,
|
|
13
|
+
REPORT_SERVER_ID = 17,
|
|
14
|
+
MASK_WRITE_REGISTER = 22,
|
|
15
|
+
READ_WRITE_MULTIPLE_REGISTERS = 23,
|
|
16
|
+
READ_DEVICE_IDENTIFICATION = 43
|
|
17
|
+
}
|
|
18
|
+
/** Exception response FC = request FC | EXCEPTION_OFFSET (V1.1b3 §7). */
|
|
19
|
+
export declare const EXCEPTION_OFFSET = 128;
|
|
20
|
+
/** Coil value encoding for FC 5 / FC 15 (V1.1b3 §6.5/§6.11). */
|
|
21
|
+
export declare const COIL_ON = 65280;
|
|
22
|
+
export declare const COIL_OFF = 0;
|
|
23
|
+
/** FC 0x2B MEI sub-function selecting Read Device Identification (V1.1b3 §6.21). */
|
|
24
|
+
export declare const MEI_READ_DEVICE_ID = 14;
|
|
25
|
+
/** Read Device ID code values inside an FC 0x2B / MEI 0x0E request. */
|
|
26
|
+
export declare enum ReadDeviceIDCode {
|
|
27
|
+
BASIC_STREAM = 1,
|
|
28
|
+
REGULAR_STREAM = 2,
|
|
29
|
+
EXTENDED_STREAM = 3,
|
|
30
|
+
SPECIFIC_ACCESS = 4
|
|
31
|
+
}
|
|
32
|
+
/** Conformity level reported in an FC 0x2B / MEI 0x0E response. */
|
|
33
|
+
export declare enum ConformityLevel {
|
|
34
|
+
BASIC = 129,
|
|
35
|
+
REGULAR = 130,
|
|
36
|
+
EXTENDED = 131
|
|
37
|
+
}
|
|
38
|
+
/** Modbus V1.1b3 PDU quantity limits. */
|
|
39
|
+
export declare const LIMITS: {
|
|
40
|
+
readonly READ_COILS_MIN: 1;
|
|
41
|
+
readonly READ_COILS_MAX: 2000;
|
|
42
|
+
readonly READ_REGISTERS_MIN: 1;
|
|
43
|
+
readonly READ_REGISTERS_MAX: 125;
|
|
44
|
+
readonly WRITE_COILS_MAX: 1968;
|
|
45
|
+
readonly WRITE_REGISTERS_MAX: 123;
|
|
46
|
+
readonly RW_REGISTERS_WRITE_MAX: 121;
|
|
47
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|