diff --git a/package-lock.json b/package-lock.json index 3beabd3..6d8f388 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "jsmodbus", - "version": "5.0.0", + "version": "5.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "jsmodbus", - "version": "5.0.0", + "version": "5.0.1", "license": "MIT", "dependencies": { "commander": "^11.1.0", diff --git a/package.json b/package.json index a992265..5e93f4f 100644 --- a/package.json +++ b/package.json @@ -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 ", "main": "./dist/modbus.js", diff --git a/src/client-response-handler.ts b/src/client-response-handler.ts index 1225cb4..5488aa3 100644 --- a/src/client-response-handler.ts +++ b/src/client-response-handler.ts @@ -1,4 +1,6 @@ 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 @@ -6,6 +8,7 @@ import MBAbstractResponse from './abstract-response' export default abstract class ModbusClientResponseHandler { protected _buffer: Buffer protected abstract _messages: ResType[] + protected readonly _customHandlers = new Map() /** Create new Modbus Client Response Handler */ constructor () { @@ -23,4 +26,38 @@ export default abstract class ModbusClientResponseHandler { + const handler = this._customHandlers.get(buffer[0]) + + if (!handler) { + return ResponseFactory.fromBuffer(buffer) + } + + try { + return handler.fromBuffer(buffer) + } catch (e) { + return null + } + } } diff --git a/src/modbus-client.ts b/src/modbus-client.ts index 9208e78..aec2c4a 100644 --- a/src/modbus-client.ts +++ b/src/modbus-client.ts @@ -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 @@ -342,6 +343,32 @@ export default abstract class MBClient { + /** @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 +} diff --git a/src/rtu-client-response-handler.ts b/src/rtu-client-response-handler.ts index 71fade0..3c21c28 100644 --- a/src/rtu-client-response-handler.ts +++ b/src/rtu-client-response-handler.ts @@ -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') diff --git a/src/rtu-response.ts b/src/rtu-response.ts index c903738..060f22e 100644 --- a/src/rtu-response.ts +++ b/src/rtu-response.ts @@ -49,7 +49,10 @@ export default class ModbusRTUResponse ModbusResponseBody | null = ResponseFactory.fromBuffer + ) { if (buffer.length < 1) { return null } @@ -58,7 +61,7 @@ export default class ModbusRTUResponse ModbusResponseBody | null = ResponseFactory.fromBuffer + ) { try { const id = buffer.readUInt16BE(0) const protocol = buffer.readUInt16BE(2) @@ -80,7 +83,7 @@ export default class ModbusTCPResponse { throw new Error('boom') } }) + + handler.handleData(Buffer.from([0x01, 0x03, 0x02, 0x12, 0x34, 0xcd, 0xab])) + + assert.strictEqual(undefined, handler.shift()) + }) + }) + + describe('Modbus/TCP Client Response Handler.', function () { + it('should pass exactly the PDU to the custom handler', function () { + const handler = new ModbusTCPClientResponseHandler() + let received + + handler.registerResponseHandler(0x03, { + fromBuffer (buffer) { + received = buffer + return singleRegisterHandler.fromBuffer(buffer) + } + }) + + handler.handleData(Buffer.from([ + 0x00, 0x01, // transaction id + 0x00, 0x00, // protocol + 0x00, 0x05, // length + 0x01, // unit id + 0x03, 0xff, 0x12, 0x34 // pdu with broken byte count + ])) + + assert.deepEqual(Buffer.from([0x03, 0xff, 0x12, 0x34]), received) + assert.deepEqual([0x1234], handler.shift().body.valuesAsArray) + }) + }) + + describe('Modbus Client.', function () { + let socket + + beforeEach(function () { + socket = new EventEmitter() + socket.write = function () {} + }) + + it('should resolve the request with the body of the custom handler', function (done) { + const client = new Modbus.client.TCP(socket) + client.registerResponseHandler(0x03, singleRegisterHandler) + + socket.emit('connect') + + client.readHoldingRegisters(0, 1) + .then(function (resp) { + assert.equal(1, singleRegisterHandler.calls) + assert.deepEqual([0x1234], resp.response.body.valuesAsArray) + done() + }).catch(done) + + socket.emit('data', Buffer.from([ + 0x00, 0x01, // transaction id + 0x00, 0x00, // protocol + 0x00, 0x05, // length + 0x01, // unit id + 0x03, 0xff, 0x12, 0x34 + ])) + }) + + it('should parse the next response after discarding a truncated one', function (done) { + const client = new Modbus.client.TCP(socket) + + socket.emit('connect') + + /* the rest of a frame whose request was given up on */ + socket.emit('data', Buffer.from([0x00, 0x07, 0x00, 0x00, 0x00, 0x05])) + assert.equal(6, client.discardResponseBuffer()) + assert.equal(0, client.discardResponseBuffer()) + + client.readHoldingRegisters(0, 1) + .then(function (resp) { + assert.deepEqual([0x1234], resp.response.body.valuesAsArray) + done() + }).catch(done) + + socket.emit('data', Buffer.from([ + 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x01, + 0x03, 0x02, 0x12, 0x34 + ])) + }) + + it('should unregister a custom handler on the client', function () { + const client = new Modbus.client.RTU(new EventEmitter(), 1) + client.registerResponseHandler(0x03, singleRegisterHandler) + + assert.equal(true, client.unregisterResponseHandler(0x03)) + assert.equal(false, client.unregisterResponseHandler(0x03)) + }) + + it('should keep custom handlers per client', function (done) { + const otherClient = new Modbus.client.TCP(new EventEmitter()) + const client = new Modbus.client.TCP(socket) + otherClient.registerResponseHandler(0x03, singleRegisterHandler) + + socket.emit('connect') + + client.readHoldingRegisters(0, 1) + .then(function (resp) { + assert.equal(0, singleRegisterHandler.calls) + assert.deepEqual([0x1234], resp.response.body.valuesAsArray) + done() + }).catch(done) + + socket.emit('data', Buffer.from([ + 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x01, + 0x03, 0x02, 0x12, 0x34 + ])) + }) + }) +})