Skip to content

Commit

Permalink
Add debugging payload information to response data. (#349)
Browse files Browse the repository at this point in the history
* WC-2981: index.js: Expose isDebugEnabled property.

This will be used to turn on/off the gathering of raw request/response
data from the Modbus interface.  By default, this is turned off.

* WC-2981: index.js: Add Modbus/RTU payload request/responses

If debugging is enabled, add the request and response data to any and
all response data or errors.

Responses:
- `request`: A `Buffer` containing the request PDU (Modbus/RTU format)
- `responses` an `Array` of `Buffer` objects containing the responses
   seen.

Exception properties:
- `modbusRequest`: A `Buffer` containing the request PDU (Modbus/RTU format)
- `modbusResponses` an `Array` of `Buffer` objects containing the responses
   seen.

* WC-2981: index.js: Do write _after_ processing transaction

In the test suites, we're using a dummy port that operates
synchronously.

This fails when we're testing debug mode because this routine is
responsible for creating the `request` and `response` properties on the
transaction.

So instead, move the write call.  This still passes tests, and I'd
expect it to make bugger all difference in production.  (If
`_startTimeout` is taking long periods of time, then you have other
problems.)

* WC-2981: unit tests: Test debug payload pass-through.

Test that, if enabled, the debug data gets passed through in the happy
and unhappy paths.

Since the code is common to all function code routines, we don't bother
testing for every single one.
  • Loading branch information
sjlongland authored Jun 25, 2020
1 parent 1d593c0 commit 781ea7e
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 17 deletions.
87 changes: 70 additions & 17 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,11 +210,18 @@ function _readFC43(data, modbus, next) {
function _writeBufferToPort(buffer, transactionId) {
var transaction = this._transactions[transactionId];

this._port.write(buffer);
if (transaction) {
transaction._timeoutFired = false;
transaction._timeoutHandle = _startTimeout(this._timeout, transaction);

// If in debug mode, stash a copy of the request payload
if (this._debugEnabled) {
transaction.request = Uint8Array.prototype.slice.call(buffer);
transaction.responses = [];
}
}

this._port.write(buffer);
}

/**
Expand All @@ -232,7 +239,12 @@ function _startTimeout(duration, transaction) {
return setTimeout(function() {
transaction._timeoutFired = true;
if (transaction.next) {
transaction.next(new TransactionTimedOutError());
var err = new TransactionTimedOutError();
if (transaction.request && transaction.responses) {
err.modbusRequest = transaction.request;
err.modbusResponses = transaction.responses;
}
transaction.next(err);
}
}, duration);
}
Expand Down Expand Up @@ -265,6 +277,32 @@ function _onReceive(data) {
return;
}

if (transaction.responses) {
/* Stash what we received */
transaction.responses.push(Uint8Array.prototype.slice.call(data));
}

/* What do we do next? */
var next = function(err, res) {
if (transaction.next) {
/* Include request/response data if enabled */
if (transaction.request && transaction.responses) {
if (err) {
err.modbusRequest = transaction.request;
err.modbusResponses = transaction.responses;
}

if (res) {
res.request = transaction.request;
res.responses = transaction.responses;
}
}

/* Pass the data on */
return transaction.next(err, res);
}
};

/* cancel the timeout */
_cancelTimeout(transaction._timeoutHandle);
transaction._timeoutHandle = undefined;
Expand All @@ -283,8 +321,7 @@ function _onReceive(data) {
if (!transaction.lengthUnknown && data.length < 5) {
error = "Data length error, expected " +
transaction.nextLength + " got " + data.length;
if (transaction.next)
transaction.next(new Error(error));
next(new Error(error));
return;
}

Expand All @@ -294,8 +331,7 @@ function _onReceive(data) {
var crcIn = data.readUInt16LE(data.length - 2);
if (crcIn !== crc16(data.slice(0, -2))) {
error = "CRC error";
if (transaction.next)
transaction.next(new Error(error));
next(new Error(error));
return;
}

Expand All @@ -311,7 +347,7 @@ function _onReceive(data) {
if (transaction.next) {
error = new Error("Modbus exception " + errorCode + ": " + (modbusErrorMessages[errorCode] || "Unknown error"));
error.modbusCode = errorCode;
transaction.next(error);
next(error);
}
return;
}
Expand All @@ -323,8 +359,7 @@ function _onReceive(data) {
if (!transaction.lengthUnknown && data.length !== transaction.nextLength) {
error = "Data length error, expected " +
transaction.nextLength + " got " + data.length;
if (transaction.next)
transaction.next(new Error(error));
next(new Error(error));
return;
}

Expand All @@ -335,8 +370,7 @@ function _onReceive(data) {
if (address !== transaction.nextAddress || code !== transaction.nextCode) {
error = "Unexpected data error, expected " +
transaction.nextAddress + " got " + address;
if (transaction.next)
transaction.next(new Error(error));
next(new Error(error));
return;
}

Expand All @@ -348,31 +382,31 @@ function _onReceive(data) {
case 2:
// Read Coil Status (FC=01)
// Read Input Status (FC=02)
_readFC2(data, transaction.next);
_readFC2(data, next);
break;
case 3:
case 4:
// Read Input Registers (FC=04)
// Read Holding Registers (FC=03)
_readFC4(data, transaction.next);
_readFC4(data, next);
break;
case 5:
// Force Single Coil
_readFC5(data, transaction.next);
_readFC5(data, next);
break;
case 6:
// Preset Single Register
_readFC6(data, transaction.next);
_readFC6(data, next);
break;
case 15:
case 16:
// Force Multiple Coils
// Preset Multiple Registers
_readFC16(data, transaction.next);
_readFC16(data, next);
break;
case 43:
// read device identification
_readFC43(data, modbus, transaction.next);
_readFC43(data, modbus, next);
}
}

Expand All @@ -390,6 +424,10 @@ var ModbusRTU = function(port) {
this._timeout = null; // timeout in msec before unanswered request throws timeout error
this._unitID = 1;

// Flag to indicate whether debug mode (pass-through of raw
// request/response) is enabled.
this._debugEnabled = false;

this._onReceive = _onReceive.bind(this);

EventEmitter.call(this);
Expand Down Expand Up @@ -434,6 +472,21 @@ ModbusRTU.prototype.open = function(callback) {
};


/**
* Check if port debug mode is enabled
*/
Object.defineProperty(ModbusRTU.prototype, "isDebugEnabled", {
enumerable: true,
get: function() {
return this._debugEnabled;
},
set: function(enable) {
enable = Boolean(enable);
this._debugEnabled = enable;
}
});


/**
* Check if port is open
*/
Expand Down
41 changes: 41 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,47 @@ describe("ModbusRTU", function() {
done();
});
});

it("should include raw payload data in response if debug enabled", function(done) {
/* This feature is common to _all_ function handlers. */
modbusRTU.isDebugEnabled = true;

modbusRTU.writeFC3(1, 8, 3, function(err, data) {
modbusRTU.isDebugEnabled = false;

expect(err).to.be.a("null");
expect(data).to.have.property("request").with.length(8);
expect(data.request.toString("hex")).to.equal("0103000800038409");

expect(data).to.have.property("responses").with.length(1);
expect(data.responses[0]).to.be.instanceof(Buffer).with.length(11);
expect(data.responses[0].toString("hex")).to.equal("010306002a00800005f958");

done();
});
});

it("should include raw payload data in exception if debug enabled", function(done) {
/* This feature is common to _all_ function handlers. */
modbusRTU.isDebugEnabled = true;

// Pretend Unit 3 sends buggy CRCs apparently.
modbusRTU.writeFC3(3, 8, 3, function(err, data) {
modbusRTU.isDebugEnabled = false;

expect(err).to.not.be.a("null");
expect(err).to.have.property("modbusRequest").with.length(8);
expect(err.modbusRequest.toString("hex")).to.equal("03030008000385eb");

expect(err).to.have.property("modbusResponses").with.length(1);
expect(err.modbusResponses[0]).to.be.instanceof(Buffer).with.length(11);
expect(err.modbusResponses[0].toString("hex")).to.equal("030306002a00800005e138");

expect(data).to.be.undefined;

done();
});
});
});

describe("#writeFC5() - force one coil.", function() {
Expand Down

0 comments on commit 781ea7e

Please sign in to comment.