modbus-serial
Advanced tools
Comparing version 8.0.0 to 8.0.1-no-serial-port
319
index.js
@@ -24,2 +24,6 @@ "use strict"; | ||
var util = require("util"); | ||
var events = require("events"); | ||
var EventEmitter = events.EventEmitter || events; | ||
var PORT_NOT_OPEN_MESSAGE = "Port Not Open"; | ||
@@ -210,7 +214,14 @@ var PORT_NOT_OPEN_ERRNO = "ECONNREFUSED"; | ||
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); | ||
} | ||
@@ -233,3 +244,8 @@ | ||
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); | ||
} | ||
@@ -250,2 +266,153 @@ }, duration); | ||
/** | ||
* Handle incoming data from the Modbus port. | ||
* | ||
* @param {Buffer} data The data received | ||
* @private | ||
*/ | ||
function _onReceive(data) { | ||
var modbus = this; | ||
var error; | ||
// set locale helpers variables | ||
var transaction = modbus._transactions[modbus._port._transactionIdRead]; | ||
// the _transactionIdRead can be missing, ignore wrong transaction it's | ||
if (!transaction) { | ||
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; | ||
/* check if the timeout fired */ | ||
if (transaction._timeoutFired === true) { | ||
// we have already called back with an error, so don't generate a new callback | ||
return; | ||
} | ||
/* check incoming data | ||
*/ | ||
/* check minimal length | ||
*/ | ||
if (!transaction.lengthUnknown && data.length < 5) { | ||
error = "Data length error, expected " + | ||
transaction.nextLength + " got " + data.length; | ||
next(new Error(error)); | ||
return; | ||
} | ||
/* check message CRC | ||
* if CRC is bad raise an error | ||
*/ | ||
var crcIn = data.readUInt16LE(data.length - 2); | ||
if (crcIn !== crc16(data.slice(0, -2))) { | ||
error = "CRC error"; | ||
next(new Error(error)); | ||
return; | ||
} | ||
// if crc is OK, read address and function code | ||
var address = data.readUInt8(0); | ||
var code = data.readUInt8(1); | ||
/* check for modbus exception | ||
*/ | ||
if (data.length >= 5 && | ||
code === (0x80 | transaction.nextCode)) { | ||
var errorCode = data.readUInt8(2); | ||
if (transaction.next) { | ||
error = new Error("Modbus exception " + errorCode + ": " + (modbusErrorMessages[errorCode] || "Unknown error")); | ||
error.modbusCode = errorCode; | ||
next(error); | ||
} | ||
return; | ||
} | ||
/* check message length | ||
* if we do not expect this data | ||
* raise an error | ||
*/ | ||
if (!transaction.lengthUnknown && data.length !== transaction.nextLength) { | ||
error = "Data length error, expected " + | ||
transaction.nextLength + " got " + data.length; | ||
next(new Error(error)); | ||
return; | ||
} | ||
/* check message address and code | ||
* if we do not expect this message | ||
* raise an error | ||
*/ | ||
if (address !== transaction.nextAddress || code !== transaction.nextCode) { | ||
error = "Unexpected data error, expected " + | ||
transaction.nextAddress + " got " + address; | ||
next(new Error(error)); | ||
return; | ||
} | ||
/* parse incoming data | ||
*/ | ||
switch (code) { | ||
case 1: | ||
case 2: | ||
// Read Coil Status (FC=01) | ||
// Read Input Status (FC=02) | ||
_readFC2(data, next); | ||
break; | ||
case 3: | ||
case 4: | ||
// Read Input Registers (FC=04) | ||
// Read Holding Registers (FC=03) | ||
_readFC4(data, next); | ||
break; | ||
case 5: | ||
// Force Single Coil | ||
_readFC5(data, next); | ||
break; | ||
case 6: | ||
// Preset Single Register | ||
_readFC6(data, next); | ||
break; | ||
case 15: | ||
case 16: | ||
// Force Multiple Coils | ||
// Preset Multiple Registers | ||
_readFC16(data, next); | ||
break; | ||
case 43: | ||
// read device identification | ||
_readFC43(data, modbus, next); | ||
} | ||
} | ||
/** | ||
* Class making ModbusRTU calls fun and easy. | ||
@@ -263,3 +430,12 @@ * | ||
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); | ||
}; | ||
util.inherits(ModbusRTU, EventEmitter); | ||
@@ -288,124 +464,10 @@ /** | ||
/* On serial port success | ||
* register the modbus parser functions | ||
* (re-)register the modbus parser functions | ||
*/ | ||
modbus._port.on("data", function(data) { | ||
// set locale helpers variables | ||
var transaction = modbus._transactions[modbus._port._transactionIdRead]; | ||
modbus._port.removeListener("data", modbus._onReceive); | ||
modbus._port.on("data", modbus._onReceive); | ||
// the _transactionIdRead can be missing, ignore wrong transaction it's | ||
if (!transaction) { | ||
return; | ||
} | ||
/* Hook the close event so we can relay it to our callers. */ | ||
modbus._port.once("close", modbus.emit.bind(modbus, "close")); | ||
/* cancel the timeout */ | ||
_cancelTimeout(transaction._timeoutHandle); | ||
transaction._timeoutHandle = undefined; | ||
/* check if the timeout fired */ | ||
if (transaction._timeoutFired === true) { | ||
// we have already called back with an error, so don't generate a new callback | ||
return; | ||
} | ||
/* check incoming data | ||
*/ | ||
/* check minimal length | ||
*/ | ||
if (!transaction.lengthUnknown && data.length < 5) { | ||
error = "Data length error, expected " + | ||
transaction.nextLength + " got " + data.length; | ||
if (transaction.next) | ||
transaction.next(new Error(error)); | ||
return; | ||
} | ||
/* check message CRC | ||
* if CRC is bad raise an error | ||
*/ | ||
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)); | ||
return; | ||
} | ||
// if crc is OK, read address and function code | ||
var address = data.readUInt8(0); | ||
var code = data.readUInt8(1); | ||
/* check for modbus exception | ||
*/ | ||
if (data.length >= 5 && | ||
code === (0x80 | transaction.nextCode)) { | ||
var errorCode = data.readUInt8(2); | ||
if (transaction.next) { | ||
error = new Error("Modbus exception " + errorCode + ": " + (modbusErrorMessages[errorCode] || "Unknown error")); | ||
error.modbusCode = errorCode; | ||
transaction.next(error); | ||
} | ||
return; | ||
} | ||
/* check message length | ||
* if we do not expect this data | ||
* raise an error | ||
*/ | ||
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)); | ||
return; | ||
} | ||
/* check message address and code | ||
* if we do not expect this message | ||
* raise an error | ||
*/ | ||
if (address !== transaction.nextAddress || code !== transaction.nextCode) { | ||
error = "Unexpected data error, expected " + | ||
transaction.nextAddress + " got " + address; | ||
if (transaction.next) | ||
transaction.next(new Error(error)); | ||
return; | ||
} | ||
/* parse incoming data | ||
*/ | ||
switch (code) { | ||
case 1: | ||
case 2: | ||
// Read Coil Status (FC=01) | ||
// Read Input Status (FC=02) | ||
_readFC2(data, transaction.next); | ||
break; | ||
case 3: | ||
case 4: | ||
// Read Input Registers (FC=04) | ||
// Read Holding Registers (FC=03) | ||
_readFC4(data, transaction.next); | ||
break; | ||
case 5: | ||
// Force Single Coil | ||
_readFC5(data, transaction.next); | ||
break; | ||
case 6: | ||
// Preset Single Register | ||
_readFC6(data, transaction.next); | ||
break; | ||
case 15: | ||
case 16: | ||
// Force Multiple Coils | ||
// Preset Multiple Registers | ||
_readFC16(data, transaction.next); | ||
break; | ||
case 43: | ||
// read device identification | ||
_readFC43(data, modbus, transaction.next); | ||
} | ||
}); | ||
/* On serial port open OK call next function with no error */ | ||
@@ -420,2 +482,17 @@ if (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 | ||
@@ -422,0 +499,0 @@ */ |
{ | ||
"name": "modbus-serial", | ||
"version": "8.0.0", | ||
"version": "8.0.1-no-serial-port", | ||
"description": "A pure JavaScript implemetation of MODBUS-RTU (Serial and TCP) for NodeJS.", | ||
@@ -40,5 +40,4 @@ "main": "index.js", | ||
"dependencies": { | ||
"debug": "^4.1.1", | ||
"serialport": "^9.0.0" | ||
"debug": "^4.1.1" | ||
} | ||
} |
@@ -12,3 +12,40 @@ "use strict"; | ||
var MAX_BUFFER_LENGTH = 256; | ||
var CRC_LENGTH = 2; | ||
var READ_DEVICE_IDENTIFICATION_FUNCTION_CODE = 43; | ||
var LENGTH_UNKNOWN = "unknown"; | ||
var BITS_TO_NUM_OF_OBJECTS = 7; | ||
// Helper function -> Bool | ||
// BIT | TYPE | ||
// 8 | OBJECTID | ||
// 9 | length of OBJECTID | ||
// 10 -> n | the object | ||
// 10 + n + 1 | new object id | ||
var calculateFC43Length = function(buffer, numObjects, i, bufferLength) { | ||
var result = { hasAllData: true }; | ||
var currentByte = 8 + i; // current byte starts at object id. | ||
if (numObjects > 0) { | ||
for (var j = 0; j < numObjects; j++) { | ||
if (bufferLength < currentByte) { | ||
result.hasAllData = false; | ||
break; | ||
} | ||
var objLength = buffer[currentByte + 1]; | ||
if (!objLength) { | ||
result.hasAllData = false; | ||
break; | ||
} | ||
currentByte += 2 + objLength; | ||
} | ||
} | ||
if (currentByte + CRC_LENGTH > bufferLength) { | ||
// still waiting on the CRC! | ||
result.hasAllData = false; | ||
} | ||
if (result.hasAllData) { | ||
result.bufLength = currentByte + CRC_LENGTH; | ||
} | ||
return result; | ||
}; | ||
/** | ||
@@ -44,2 +81,4 @@ * Simulate a modbus-RTU port using buffered serial connection. | ||
modbusSerialDebug({ action: "receive serial rtu buffered port", data: data, buffer: self._buffer }); | ||
// check if buffer include a complete modbus answer | ||
@@ -49,6 +88,8 @@ var expectedLength = self._length; | ||
modbusSerialDebug({ action: "receive serial rtu buffered port", data: data, buffer: self._buffer }); | ||
// check data length | ||
if (expectedLength < MIN_DATA_LENGTH || bufferLength < EXCEPTION_LENGTH) return; | ||
if (expectedLength !== LENGTH_UNKNOWN && | ||
expectedLength < MIN_DATA_LENGTH || | ||
bufferLength < EXCEPTION_LENGTH | ||
) { return; } | ||
@@ -63,2 +104,3 @@ // check buffer size for MAX_BUFFER_SIZE | ||
var maxOffset = bufferLength - EXCEPTION_LENGTH; | ||
for (var i = 0; i <= maxOffset; i++) { | ||
@@ -70,10 +112,22 @@ var unitId = self._buffer[i]; | ||
if (functionCode === self._cmd && i + expectedLength <= bufferLength) { | ||
self._emitData(i, expectedLength); | ||
return; | ||
if (functionCode === self._cmd && functionCode === READ_DEVICE_IDENTIFICATION_FUNCTION_CODE) { | ||
if (bufferLength <= BITS_TO_NUM_OF_OBJECTS + i) { | ||
return; | ||
} | ||
var numObjects = self._buffer[7 + i]; | ||
var result = calculateFC43Length(self._buffer, numObjects, i, bufferLength); | ||
if (result.hasAllData) { | ||
self._emitData(i, result.bufLength); | ||
return; | ||
} | ||
} else { | ||
if (functionCode === self._cmd && i + expectedLength <= bufferLength) { | ||
self._emitData(i, expectedLength); | ||
return; | ||
} | ||
if (functionCode === (0x80 | self._cmd) && i + EXCEPTION_LENGTH <= bufferLength) { | ||
self._emitData(i, EXCEPTION_LENGTH); | ||
return; | ||
} | ||
} | ||
if (functionCode === (0x80 | self._cmd) && i + EXCEPTION_LENGTH <= bufferLength) { | ||
self._emitData(i, EXCEPTION_LENGTH); | ||
return; | ||
} | ||
@@ -110,5 +164,3 @@ // frame header matches, but still missing bytes pending | ||
var buffer = this._buffer.slice(start, start + length); | ||
modbusSerialDebug({ action: "emit data serial rtu buffered port", buffer: buffer }); | ||
this.emit("data", buffer); | ||
@@ -172,4 +224,7 @@ this._buffer = this._buffer.slice(start + length); | ||
case 43: | ||
modbusSerialDebug("RTUBuffered F43 not supported"); | ||
this._length = 0; | ||
// this function is super special | ||
// you know the format of the code response | ||
// and you need to continuously check that all of the data has arrived before emitting | ||
// see onData for more info. | ||
this._length = LENGTH_UNKNOWN; | ||
break; | ||
@@ -189,3 +244,4 @@ default: | ||
unitid: this._id, | ||
functionCode: this._cmd | ||
functionCode: this._cmd, | ||
length: this._length | ||
}); | ||
@@ -192,0 +248,0 @@ }; |
@@ -97,2 +97,3 @@ "use strict"; | ||
handleCallback(had_error); | ||
modbus.emit("close"); | ||
}); | ||
@@ -99,0 +100,0 @@ |
@@ -124,2 +124,3 @@ "use strict"; | ||
handleCallback(had_error); | ||
modbus.emit("close"); | ||
}); | ||
@@ -126,0 +127,0 @@ |
@@ -117,2 +117,3 @@ "use strict"; | ||
handleCallback(had_error); | ||
self.emit("close"); | ||
}); | ||
@@ -119,0 +120,0 @@ |
@@ -298,2 +298,43 @@ "use strict"; | ||
}); | ||
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(); | ||
}); | ||
}); | ||
}); | ||
@@ -300,0 +341,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
282878
1
7470
1
- Removedserialport@^9.0.0
- Removed@serialport/binding-abstract@9.2.3(transitive)
- Removed@serialport/binding-mock@9.2.4(transitive)
- Removed@serialport/bindings@9.2.8(transitive)
- Removed@serialport/parser-byte-length@9.2.4(transitive)
- Removed@serialport/parser-cctalk@9.2.4(transitive)
- Removed@serialport/parser-delimiter@9.2.4(transitive)
- Removed@serialport/parser-inter-byte-timeout@9.2.4(transitive)
- Removed@serialport/parser-readline@9.2.4(transitive)
- Removed@serialport/parser-ready@9.2.4(transitive)
- Removed@serialport/parser-regex@9.2.4(transitive)
- Removed@serialport/stream@9.2.4(transitive)
- Removedbase64-js@1.5.1(transitive)
- Removedbindings@1.5.0(transitive)
- Removedbl@4.1.0(transitive)
- Removedbuffer@5.7.1(transitive)
- Removedchownr@1.1.4(transitive)
- Removeddecompress-response@6.0.0(transitive)
- Removeddeep-extend@0.6.0(transitive)
- Removeddetect-libc@2.0.3(transitive)
- Removedend-of-stream@1.4.4(transitive)
- Removedexpand-template@2.0.3(transitive)
- Removedfile-uri-to-path@1.0.0(transitive)
- Removedfs-constants@1.0.0(transitive)
- Removedgithub-from-package@0.0.0(transitive)
- Removedieee754@1.2.1(transitive)
- Removedinherits@2.0.4(transitive)
- Removedini@1.3.8(transitive)
- Removedmimic-response@3.1.0(transitive)
- Removedminimist@1.2.8(transitive)
- Removedmkdirp-classic@0.5.3(transitive)
- Removednan@2.22.0(transitive)
- Removednapi-build-utils@1.0.2(transitive)
- Removednode-abi@3.71.0(transitive)
- Removedonce@1.4.0(transitive)
- Removedprebuild-install@7.1.2(transitive)
- Removedpump@3.0.2(transitive)
- Removedrc@1.2.8(transitive)
- Removedreadable-stream@3.6.2(transitive)
- Removedsafe-buffer@5.2.1(transitive)
- Removedsemver@7.6.3(transitive)
- Removedserialport@9.2.8(transitive)
- Removedsimple-concat@1.0.1(transitive)
- Removedsimple-get@4.0.1(transitive)
- Removedstring_decoder@1.3.0(transitive)
- Removedstrip-json-comments@2.0.1(transitive)
- Removedtar-fs@2.1.1(transitive)
- Removedtar-stream@2.2.0(transitive)
- Removedtunnel-agent@0.6.0(transitive)
- Removedutil-deprecate@1.0.2(transitive)
- Removedwrappy@1.0.2(transitive)