Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jsmodbus",
"version": "5.0.0",
"version": "5.0.1",
"description": "Implementation for the Serial/TCP Modbus protocol.",
"author": "Stefan Poeter <stefan.poeter@cloud-automation.de>",
"main": "./dist/modbus.js",
Expand Down
37 changes: 37 additions & 0 deletions src/client-response-handler.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import MBAbstractResponse from './abstract-response'
import IResponseBodyHandler from './response/response-body-handler.js'
import ResponseFactory from './response/response-factory.js'

/** Modbus Client Repsonse Handler
* @abstract
*/
export default abstract class ModbusClientResponseHandler<ResType extends MBAbstractResponse = MBAbstractResponse> {
protected _buffer: Buffer
protected abstract _messages: ResType[]
protected readonly _customHandlers = new Map<number, IResponseBodyHandler>()

/** Create new Modbus Client Response Handler */
constructor () {
Expand All @@ -23,4 +26,38 @@ export default abstract class ModbusClientResponseHandler<ResType extends MBAbst
public shift () {
return this._messages.shift()
}

/** Drop whatever is left in the receive buffer.
* @returns {number} The number of discarded bytes.
*/
public discardBuffer (): number {
const discarded = this._buffer.length
this._buffer = Buffer.alloc(0)
return discarded
}

/** Parse responses of a function code with a custom handler instead of the built-in one. */
public registerResponseHandler (fc: number, handler: IResponseBodyHandler) {
this._customHandlers.set(fc, handler)
}

/** Go back to the built-in parser for a function code. Returns true if a custom handler was removed. */
public unregisterResponseHandler (fc: number) {
return this._customHandlers.delete(fc)
}

/** Parse a response body with the custom handler of its function code, if there is one. */
protected _parseBody = (buffer: Buffer) => {
const handler = this._customHandlers.get(buffer[0])

if (!handler) {
return ResponseFactory.fromBuffer(buffer)
}

try {
return handler.fromBuffer(buffer)
} catch (e) {
return null
}
}
}
27 changes: 27 additions & 0 deletions src/modbus-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import MBClientResponseHandler from './client-response-handler.js'
import { UserRequestError } from './errors'
import { CastRequestBody } from './request-response-map'
import { WriteMultipleCoilsResponseBody } from './response'
import IResponseBodyHandler from './response/response-body-handler.js'
import { PromiseUserRequest } from './user-request.js'

/** Common Modbus Client
Expand Down Expand Up @@ -342,6 +343,32 @@ export default abstract class MBClient<S extends Stream.Duplex, Req extends Modb
return this._requestHandler.customErrorRequest(err)
}

/** Drop whatever is left in the receive buffer, e.g. the rest of a response whose request
* timed out.
* @returns {number} The number of discarded bytes.
*/
public discardResponseBuffer (): number {
return this._responseHandler.discardBuffer()
}

/** Parse responses of a function code with a custom handler instead of the built-in one.
* The returned body is matched against the pending request as usual, so it has to carry
* the function code of the request (or be an exception response).
* @param {number} fc Function code
* @param {IResponseBodyHandler} handler Object with a fromBuffer (buffer) method.
*/
public registerResponseHandler (fc: number, handler: IResponseBodyHandler) {
this._responseHandler.registerResponseHandler(fc, handler)
}

/** Remove a custom response handler and go back to the built-in parser.
* @param {number} fc Function code
* @returns {boolean} true if a custom handler was removed.
*/
public unregisterResponseHandler (fc: number) {
return this._responseHandler.unregisterResponseHandler(fc)
}

private _onData (data: Buffer) {
debug('received data')

Expand Down
1 change: 1 addition & 0 deletions src/modbus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export { default as ModbusAbstractRequest } from './abstract-request'
export { default as ModbusAbstractResponse } from './abstract-response'
export { default as MBClientRequestHandler } from './client-request-handler'
export { default as ModbusClientResponseHandler } from './client-response-handler'
export { default as ResponseBodyHandler } from './response/response-body-handler'
export { default as ModbusClient } from './modbus-client'
export * from './request-response-map'
export { default as ModbusTCPRequest } from './tcp-request'
Expand Down
12 changes: 12 additions & 0 deletions src/response/response-body-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import ModbusResponseBody from './response-body.js'

/** Parses the response body of a single function code. The built-in response body
* classes (e.g. ReadHoldingRegistersResponseBody) satisfy it through their static fromBuffer.
*/
export default interface IResponseBodyHandler<Body extends ModbusResponseBody = ModbusResponseBody> {
/** @param {Buffer} buffer Starts at the function code. With Modbus/RTU the returned body's
* byteCount decides how many bytes are consumed before the CRC.
* @returns {ModbusResponseBody | null} null if not enough data has arrived yet.
*/
fromBuffer (buffer: Buffer): Body | null
}
2 changes: 1 addition & 1 deletion src/rtu-client-response-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default class ModbusRTUClientResponseHandler extends ModbusClientResponse
debug('buffer', this._buffer)

do {
const response = ModbusRTUResponse.fromBuffer(this._buffer)
const response = ModbusRTUResponse.fromBuffer(this._buffer, this._parseBody)

if (!response) {
debug('not enough data available to parse')
Expand Down
7 changes: 5 additions & 2 deletions src/rtu-response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ export default class ModbusRTUResponse<ResBody extends ModbusResponseBody = Modb
modbusBody)
}

public static fromBuffer (buffer: Buffer) {
public static fromBuffer (
buffer: Buffer,
parseBody: (body: Buffer) => ModbusResponseBody | null = ResponseFactory.fromBuffer
) {
if (buffer.length < 1) {
return null
}
Expand All @@ -58,7 +61,7 @@ export default class ModbusRTUResponse<ResBody extends ModbusResponseBody = Modb

debug('address', address, 'buffer', buffer)

const body = ResponseFactory.fromBuffer(buffer.slice(1))
const body = parseBody(buffer.slice(1))

if (!body) {
return null
Expand Down
2 changes: 1 addition & 1 deletion src/tcp-client-response-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default class ModbusTCPClientResponseHandler extends ModbusClientResponse
debug('buffer', this._buffer)

do {
const response = ModbusTCPResponse.fromBuffer(this._buffer)
const response = ModbusTCPResponse.fromBuffer(this._buffer, this._parseBody)

if (!response) {
debug('not enough data available to parse')
Expand Down
7 changes: 5 additions & 2 deletions src/tcp-response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ export default class ModbusTCPResponse<ResBody extends ModbusResponseBody = Modb
* @param {Buffer} buffer
* @returns {ModbusTCPResponse} Returns null if not enough data located in the buffer.
*/
public static fromBuffer (buffer: Buffer) {
public static fromBuffer (
buffer: Buffer,
parseBody: (body: Buffer) => ModbusResponseBody | null = ResponseFactory.fromBuffer
) {
try {
const id = buffer.readUInt16BE(0)
const protocol = buffer.readUInt16BE(2)
Expand All @@ -80,7 +83,7 @@ export default class ModbusTCPResponse<ResBody extends ModbusResponseBody = Modb
debug('tcp header complete, id', id, 'protocol', protocol, 'length', length, 'unitId', unitId)
debug('buffer', buffer)

const body = ResponseFactory.fromBuffer(buffer.slice(7, 7 + length - 1))
const body = parseBody(buffer.slice(7, 7 + length - 1))

if (!body) {
debug('not enough data for a response body')
Expand Down
Loading
Loading