njs-modbus
Version:
A pure JavaScript implemetation of Modbus for NodeJS.
373 lines (358 loc) • 15.1 kB
TypeScript
import EventEmitter from 'node:events';
import { SocketConstructorOpts, SocketConnectOpts, NetConnectOpts, ListenOptions } from 'node:net';
import { SocketOptions, BindOptions } from 'node:dgram';
declare enum ErrorCode {
ILLEGAL_FUNCTION = 1,
ILLEGAL_DATA_ADDRESS = 2,
ILLEGAL_DATA_VALUE = 3,
SERVER_DEVICE_FAILURE = 4,
ACKNOWLEDGE = 5,
SERVER_DEVICE_BUSY = 6,
MEMORY_PARITY_ERROR = 8,
GATEWAY_PATH_UNAVAILABLE = 10,
GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND = 11
}
declare function getErrorByCode(code: ErrorCode): Error;
declare function getCodeByError(err: Error): ErrorCode;
interface AbstractPhysicalLayerEvents {
data: [data: Buffer, response: (data: Buffer) => Promise<void>];
write: [data: Buffer];
error: [error: Error];
close: [];
}
declare abstract class AbstractPhysicalLayer extends EventEmitter<AbstractPhysicalLayerEvents> {
abstract readonly TYPE: 'SERIAL' | 'NET';
abstract readonly isOpen: boolean;
abstract readonly destroyed: boolean;
abstract open(...args: any[]): Promise<void>;
abstract write(data: Buffer): Promise<void>;
abstract close(): Promise<void>;
abstract destroy(): Promise<void>;
}
interface SerialPhysicalLayerOptions {
/** The system path of the serial port you want to open. For example, `/dev/tty.XXX` on Mac/Linux, or `COM1` on Windows */
path: string;
/**
* The baud rate of the port to be opened. This should match one of the commonly available baud rates, such as 110, 300, 1200, 2400, 4800, 9600, 14400, 19200, 38400, 57600, or 115200. Custom rates are supported best effort per platform. The device connected to the serial port is not guaranteed to support the requested baud rate, even if the port itself supports that baud rate.
*/
baudRate: number;
/** Must be one of these: 5, 6, 7, or 8 defaults to 8 */
dataBits?: 5 | 6 | 7 | 8;
/** Prevent other processes from opening the port. Windows does not currently support `false`. Defaults to true */
lock?: boolean;
/** Must be 1, 1.5 or 2 defaults to 1 */
stopBits?: 1 | 1.5 | 2;
parity?: string;
/** Flow control Setting. Defaults to false */
rtscts?: boolean;
/** Flow control Setting. Defaults to false */
xon?: boolean;
/** Flow control Setting. Defaults to false */
xoff?: boolean;
/** Flow control Setting defaults to false*/
xany?: boolean;
/** drop DTR on close. Defaults to true */
hupcl?: boolean;
}
declare class SerialPhysicalLayer extends AbstractPhysicalLayer {
TYPE: 'SERIAL' | 'NET';
private _serialport;
private _destroyed;
private _baudRate;
get isOpen(): boolean;
get destroyed(): boolean;
get baudRate(): number;
constructor(options: SerialPhysicalLayerOptions);
open(): Promise<void>;
write(data: Buffer): Promise<void>;
close(): Promise<void>;
destroy(): Promise<void>;
}
declare class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
TYPE: 'SERIAL' | 'NET';
private _socket;
private _isOpen;
private _destroyed;
get isOpen(): boolean;
get destroyed(): boolean;
constructor(options?: SocketConstructorOpts);
open(options?: SocketConnectOpts): Promise<void>;
write(data: Buffer): Promise<void>;
close(): Promise<void>;
destroy(): Promise<void>;
}
declare class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
TYPE: 'SERIAL' | 'NET';
private _server;
private _isOpen;
private _destroyed;
private _sockets;
get isOpen(): boolean;
get destroyed(): boolean;
constructor(options?: NetConnectOpts);
open(options?: ListenOptions): Promise<void>;
write(data: Buffer): Promise<void>;
close(): Promise<void>;
destroy(): Promise<void>;
}
declare class UdpPhysicalLayer extends AbstractPhysicalLayer {
TYPE: 'SERIAL' | 'NET';
private _socket;
private _isOpen;
private _destroyed;
private _port;
private _address?;
isServer: boolean;
get isOpen(): boolean;
get destroyed(): boolean;
/**
*
* @param options
* @param remote If omitted, as server.
* Otherwise as client.
*/
constructor(options?: Partial<SocketOptions>, remote?: {
port?: number;
address?: string;
});
open(options?: BindOptions): Promise<void>;
write(data: Buffer): Promise<void>;
close(): Promise<void>;
destroy(): Promise<void>;
}
type FConvertPromise<F extends (...args: any) => any> = F extends (...args: infer A) => infer R ? ((...args: A) => Promise<R>) | ((...args: A) => R) : never;
interface ApplicationDataUnit {
transaction?: number;
unit: number;
fc: number;
data: number[];
}
interface ServerId {
serverId?: number;
runIndicatorStatus?: boolean;
additionalData?: number[];
}
interface DeviceIdentification {
readDeviceIDCode: number;
conformityLevel: number;
moreFollows: boolean;
nextObjectId: number;
objects: {
id: number;
value: string;
}[];
}
interface AbstractApplicationLayerEvents {
framing: [frame: ApplicationDataUnit & {
buffer: Buffer;
}, response: (data: Buffer) => Promise<void>];
}
declare abstract class AbstractApplicationLayer extends EventEmitter<AbstractApplicationLayerEvents> {
abstract startWaitingResponse(preCheck: ((frame: ApplicationDataUnit & {
buffer: Buffer;
}) => boolean | number | undefined)[], callback: (error: Error | null, frame?: ApplicationDataUnit & {
buffer: Buffer;
}) => void): void;
abstract stopWaitingResponse(): void;
abstract encode(data: ApplicationDataUnit): Buffer;
abstract destroy(): void;
}
declare class RtuApplicationLayer extends AbstractApplicationLayer {
private _waitingResponse?;
private _timerThreePointFive?;
private _bufferRx;
private _removeAllListeners;
constructor(physicalLayer: SerialPhysicalLayer | TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer,
/**
* The time interval between two frames, support two formats:
* - bit: `48bit` as default
* - millisecond: `20ms`
*/
intervalBetweenFrames?: `${number}bit` | `${number}ms`);
private framing;
startWaitingResponse(preCheck: ((frame: ApplicationDataUnit & {
buffer: Buffer;
}) => boolean | number | undefined)[], callback: (error: Error | null, frame?: ApplicationDataUnit & {
buffer: Buffer;
}) => void): void;
stopWaitingResponse(): void;
encode(data: ApplicationDataUnit): Buffer;
destroy(): void;
}
declare class AsciiApplicationLayer extends AbstractApplicationLayer {
private _waitingResponse?;
private _status;
private _frame;
private _removeAllListeners;
constructor(physicalLayer: SerialPhysicalLayer | TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer);
private framing;
startWaitingResponse(preCheck: ((frame: ApplicationDataUnit & {
buffer: Buffer;
}) => boolean | number | undefined)[], callback: (error: Error | null, frame?: ApplicationDataUnit & {
buffer: Buffer;
}) => void): void;
stopWaitingResponse(): void;
encode(data: ApplicationDataUnit): Buffer;
destroy(): void;
}
declare class TcpApplicationLayer extends AbstractApplicationLayer {
private _waitingResponse?;
private _transactionId;
private _removeAllListeners;
constructor(physicalLayer: TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer);
private framing;
startWaitingResponse(preCheck: ((frame: ApplicationDataUnit & {
buffer: Buffer;
}) => boolean | number | undefined)[], callback: (error: Error | null, frame?: ApplicationDataUnit & {
buffer: Buffer;
}) => void): void;
stopWaitingResponse(): void;
encode(data: ApplicationDataUnit): Buffer;
destroy(): void;
}
interface ModbusMasterEvents {
error: [error: Error];
close: [];
}
interface ReturnValue<T> {
transaction?: number;
unit: number;
fc: number;
data: T;
buffer: Buffer;
}
declare class ModbusMaster<A extends AbstractApplicationLayer, P extends AbstractPhysicalLayer> extends EventEmitter<ModbusMasterEvents> {
private applicationLayer;
private physicalLayer;
timeout: number;
get isOpen(): boolean;
get destroyed(): boolean;
constructor(applicationLayer: A, physicalLayer: P, timeout?: number);
private waitResponse;
private writeFC1Or2;
writeFC1: this['readCoils'];
readCoils(unit: 0, address: number, length: number, timeout?: number): Promise<void>;
readCoils(unit: number, address: number, length: number, timeout?: number): Promise<ReturnValue<boolean[]>>;
writeFC2: this['readDiscreteInputs'];
readDiscreteInputs(unit: 0, address: number, length: number, timeout?: number): Promise<void>;
readDiscreteInputs(unit: number, address: number, length: number, timeout?: number): Promise<ReturnValue<boolean[]>>;
private writeFC3Or4;
writeFC3: this['readHoldingRegisters'];
readHoldingRegisters(unit: 0, address: number, length: number, timeout?: number): Promise<void>;
readHoldingRegisters(unit: number, address: number, length: number, timeout?: number): Promise<ReturnValue<number[]>>;
writeFC4: this['readInputRegisters'];
readInputRegisters(unit: 0, address: number, length: number, timeout?: number): Promise<void>;
readInputRegisters(unit: number, address: number, length: number, timeout?: number): Promise<ReturnValue<number[]>>;
writeFC5: this['writeSingleCoil'];
writeSingleCoil(unit: 0, address: number, value: boolean, timeout?: number): Promise<void>;
writeSingleCoil(unit: number, address: number, value: boolean, timeout?: number): Promise<ReturnValue<boolean>>;
writeFC6: this['writeSingleRegister'];
writeSingleRegister(unit: 0, address: number, value: number, timeout?: number): Promise<void>;
writeSingleRegister(unit: number, address: number, value: number, timeout?: number): Promise<ReturnValue<number>>;
writeFC15: this['writeMultipleCoils'];
writeMultipleCoils(unit: 0, address: number, value: boolean[], timeout?: number): Promise<void>;
writeMultipleCoils(unit: number, address: number, value: boolean[], timeout?: number): Promise<ReturnValue<boolean[]>>;
writeFC16: this['writeMultipleRegisters'];
writeMultipleRegisters(unit: 0, address: number, value: number[], timeout?: number): Promise<void>;
writeMultipleRegisters(unit: number, address: number, value: number[], timeout?: number): Promise<ReturnValue<number[]>>;
handleFC17: this['reportServerId'];
reportServerId(unit: 0, timeout?: number): Promise<void>;
reportServerId(unit: number, timeout?: number): Promise<ReturnValue<ServerId>>;
handleFC22: this['maskWriteRegister'];
maskWriteRegister(unit: 0, address: number, andMask: number, orMask: number, timeout?: number): Promise<void>;
maskWriteRegister(unit: number, address: number, andMask: number, orMask: number, timeout?: number): Promise<ReturnValue<{
andMask: number;
orMask: number;
}>>;
handleFC23: this['readAndWriteMultipleRegisters'];
readAndWriteMultipleRegisters(unit: 0, read: {
address: number;
length: number;
}, write: {
address: number;
value: number[];
}, timeout?: number): Promise<void>;
readAndWriteMultipleRegisters(unit: number, read: {
address: number;
length: number;
}, write: {
address: number;
value: number[];
}, timeout?: number): Promise<ReturnValue<number[]>>;
handleFC43_14: this['readDeviceIdentification'];
readDeviceIdentification(unit: 0, readDeviceIDCode: number, objectId: number, timeout?: number): Promise<void>;
readDeviceIdentification(unit: number, readDeviceIDCode: number, objectId: number, timeout?: number): Promise<ReturnValue<DeviceIdentification>>;
open(...args: Parameters<P['open']>): Promise<void>;
close(): Promise<void>;
destroy(): Promise<void>;
}
interface ModbusSlaveModel {
unit?: number;
/**
* Intercept read and write behavior.
*
* If provide the return value, use this value as data of `PDU` to respond.
* Otherwise keep the default read and write behavior.
*/
interceptor?: FConvertPromise<(fc: number, data: number[]) => number[] | undefined>;
readDiscreteInputs?: FConvertPromise<(address: number, length: number) => boolean[]>;
readCoils?: FConvertPromise<(address: number, length: number) => boolean[]>;
writeSingleCoil?: FConvertPromise<(address: number, value: boolean) => void>;
/**
* If omitted, defaults to loop and call `writeSingleCoil`.
*/
writeMultipleCoils?: FConvertPromise<(address: number, value: boolean[]) => void>;
readInputRegisters?: FConvertPromise<(address: number, length: number) => number[]>;
readHoldingRegisters?: FConvertPromise<(address: number, length: number) => number[]>;
writeSingleRegister?: FConvertPromise<(address: number, value: number) => void>;
/**
* If omitted, defaults to loop and call `writeSingleRegister`.
*/
writeMultipleRegisters?: FConvertPromise<(address: number, value: number[]) => void>;
/**
* If omitted, defaults to call `readHoldingRegisters` and `writeSingleRegister`.
*/
maskWriteRegister?: FConvertPromise<(address: number, andMask: number, orMask: number) => void>;
reportServerId?: FConvertPromise<() => ServerId>;
readDeviceIdentification?: FConvertPromise<() => {
[index: number]: string;
}>;
getAddressRange?: () => {
discreteInputs?: [number, number] | [number, number][];
coils?: [number, number] | [number, number][];
inputRegisters?: [number, number] | [number, number][];
holdingRegisters?: [number, number] | [number, number][];
};
}
interface ModbusSlaveEvents {
error: [error: Error];
close: [];
}
declare class ModbusSlave<A extends AbstractApplicationLayer, P extends AbstractPhysicalLayer> extends EventEmitter<ModbusSlaveEvents> {
private applicationLayer;
private physicalLayer;
models: Map<number, ModbusSlaveModel>;
get isOpen(): boolean;
get destroyed(): boolean;
constructor(applicationLayer: A, physicalLayer: P);
private handleFC1;
private handleFC2;
private handleFC3;
private handleFC4;
private handleFC5;
private handleFC6;
private handleFC15;
private handleFC16;
private handleFC17;
private handleFC22;
private handleFC23;
private handleFC43_14;
private responseError;
add(model: ModbusSlaveModel): void;
remove(unit: number): void;
open(...args: Parameters<P['open']>): Promise<void>;
close(): Promise<void>;
destroy(): Promise<void>;
}
export { AbstractApplicationLayer, AbstractPhysicalLayer, AsciiApplicationLayer, ErrorCode, ModbusMaster, ModbusSlave, RtuApplicationLayer, SerialPhysicalLayer, TcpApplicationLayer, TcpClientPhysicalLayer, TcpServerPhysicalLayer, UdpPhysicalLayer, getCodeByError, getErrorByCode };
export type { ModbusSlaveModel };