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.
Files changed (58) hide show
  1. package/README.md +16 -0
  2. package/dist/index.cjs +1938 -1148
  3. package/dist/index.d.ts +308 -89
  4. package/dist/index.mjs +1931 -1149
  5. package/dist/src/error-code.d.ts +27 -1
  6. package/dist/src/index.d.ts +2 -0
  7. package/dist/src/layers/application/abstract-application-layer.d.ts +11 -8
  8. package/dist/src/layers/application/ascii-application-layer.d.ts +14 -10
  9. package/dist/src/layers/application/index.d.ts +2 -0
  10. package/dist/src/layers/application/rtu-application-layer.d.ts +52 -18
  11. package/dist/src/layers/application/tcp-application-layer.d.ts +4 -8
  12. package/dist/src/layers/physical/abstract-physical-layer.d.ts +5 -1
  13. package/dist/src/layers/physical/index.d.ts +1 -0
  14. package/dist/src/layers/physical/serial-physical-layer.d.ts +2 -0
  15. package/dist/src/layers/physical/tcp-client-physical-layer.d.ts +3 -0
  16. package/dist/src/layers/physical/tcp-server-physical-layer.d.ts +3 -1
  17. package/dist/src/layers/physical/udp-physical-layer.d.ts +25 -10
  18. package/dist/src/master/index.d.ts +2 -0
  19. package/dist/src/master/master-session.d.ts +18 -0
  20. package/dist/src/master/master.d.ts +27 -5
  21. package/dist/src/slave/index.d.ts +1 -1
  22. package/dist/src/slave/slave.d.ts +18 -5
  23. package/dist/src/types.d.ts +33 -2
  24. package/dist/src/utils/bitsToMs.d.ts +13 -0
  25. package/dist/src/utils/crc.d.ts +1 -1
  26. package/dist/src/utils/genConnectionId.d.ts +2 -0
  27. package/dist/src/utils/index.d.ts +5 -1
  28. package/dist/src/utils/isUint8.d.ts +8 -0
  29. package/dist/src/utils/predictRtuFrameLength.d.ts +20 -0
  30. package/dist/src/vars.d.ts +47 -0
  31. package/dist/test/adu-buffer.test.d.ts +1 -0
  32. package/dist/test/ascii-hex-sentry.test.d.ts +1 -0
  33. package/dist/test/ascii-hex-validation.test.d.ts +1 -0
  34. package/dist/test/ascii-tcp-fragmentation.test.d.ts +1 -0
  35. package/dist/test/check-range.test.d.ts +1 -0
  36. package/dist/test/fallback-atomic.test.d.ts +1 -0
  37. package/dist/test/fallback-serial.test.d.ts +1 -0
  38. package/dist/test/fc17-serverid-validation.test.d.ts +1 -0
  39. package/dist/test/fc43-conformity.test.d.ts +1 -0
  40. package/dist/test/fc43-utf8-objects.test.d.ts +1 -0
  41. package/dist/test/gen-connection-id.test.d.ts +1 -0
  42. package/dist/test/helpers/raw-tcp.d.ts +38 -0
  43. package/dist/test/master-concurrent.test.d.ts +1 -0
  44. package/dist/test/modbus-error.test.d.ts +1 -0
  45. package/dist/test/physical-lifecycle.test.d.ts +1 -0
  46. package/dist/test/predict-rtu.test.d.ts +1 -0
  47. package/dist/test/rtu-custom-fc.test.d.ts +1 -0
  48. package/dist/test/rtu-pool-overflow.test.d.ts +1 -0
  49. package/dist/test/rtu-t15-timing.test.d.ts +1 -0
  50. package/dist/test/rtu-t35-default.test.d.ts +1 -0
  51. package/dist/test/rtu-t35-strict.test.d.ts +1 -0
  52. package/dist/test/rtu-tcp-fragmentation.test.d.ts +1 -0
  53. package/dist/test/serial-e2e.test.d.ts +1 -0
  54. package/dist/test/slave-multi-connection.test.d.ts +1 -0
  55. package/dist/test/tcp-fragmentation.test.d.ts +1 -0
  56. package/dist/test/udp-multi-client.test.d.ts +1 -0
  57. package/package.json +4 -2
  58. package/dist/src/utils/getThreePointFiveT.d.ts +0 -7
@@ -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
- export declare function getErrorByCode(code: ErrorCode): Error;
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;
@@ -1,4 +1,6 @@
1
+ export * from './types';
1
2
  export * from './error-code';
3
+ export * from './vars';
2
4
  export * from './layers/physical';
3
5
  export * from './layers/application';
4
6
  export * from './master';
@@ -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 startWaitingResponse(preCheck: ((frame: ApplicationDataUnit & {
10
- buffer: Buffer;
11
- }) => boolean | number | undefined)[], callback: (error: Error | null, frame?: ApplicationDataUnit & {
12
- buffer: Buffer;
13
- }) => void): void;
14
- abstract stopWaitingResponse(): void;
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
- private _waitingResponse?;
7
- private _status;
8
- private _frame;
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 declare class RtuApplicationLayer extends AbstractApplicationLayer {
6
- private _waitingResponse?;
7
- private _timerThreePointFive?;
8
- private _bufferRx;
9
- private _removeAllListeners;
10
- constructor(physicalLayer: SerialPhysicalLayer | TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer,
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
- * The time interval between two frames, support two formats:
13
- * - bit: `48bit` as default
14
- * - millisecond: `20ms`
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
- intervalBetweenFrames?: `${number}bit` | `${number}ms`);
17
- private framing;
18
- startWaitingResponse(preCheck: ((frame: ApplicationDataUnit & {
19
- buffer: Buffer;
20
- }) => boolean | number | undefined)[], callback: (error: Error | null, frame?: ApplicationDataUnit & {
21
- buffer: Buffer;
22
- }) => void): void;
23
- stopWaitingResponse(): void;
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
- private _waitingResponse?;
5
+ readonly PROTOCOL: "TCP";
6
6
  private _transactionId;
7
+ private _buffers;
7
8
  private _removeAllListeners;
8
9
  constructor(physicalLayer: TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer);
9
- private framing;
10
- startWaitingResponse(preCheck: ((frame: ApplicationDataUnit & {
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 _sockets;
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>;
@@ -1 +1,3 @@
1
+ export type { ModbusMasterOptions } from './master';
2
+ export { MasterSession } from './master-session';
1
3
  export { ModbusMaster } from './master';
@@ -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, timeout?: number);
23
- private waitResponse;
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: number[]) => number[] | undefined>;
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
- private _processing;
52
- private _queue;
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>;
@@ -4,10 +4,11 @@ export interface ApplicationDataUnit {
4
4
  transaction?: number;
5
5
  unit: number;
6
6
  fc: number;
7
- data: number[];
7
+ data: Buffer;
8
8
  }
9
9
  export interface ServerId {
10
- serverId?: number;
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;
@@ -1 +1 @@
1
- export declare function crc(data: Uint8Array): number;
1
+ export declare function crc(data: Uint8Array, seed?: number): number;
@@ -0,0 +1,2 @@
1
+ /** Cross-process unique id: `${prefix}-${uuid-v4}`. */
2
+ export declare function genConnectionId(prefix: string): string;
@@ -1,4 +1,8 @@
1
1
  export { checkRange } from './checkRange';
2
2
  export { crc } from './crc';
3
- export { getThreePointFiveT } from './getThreePointFiveT';
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 {};