modbus-serial
Advanced tools
Comparing version 3.4.6 to 3.5.0
@@ -5,12 +5,12 @@ 'use strict'; | ||
* | ||
* Permission to use, copy, modify, and/or distribute this software for any | ||
* purpose with or without fee is hereby granted, provided that the above | ||
* Permission to use, copy, modify, and/or distribute this software for any | ||
* purpose with or without fee is hereby granted, provided that the above | ||
* copyright notice and this permission notice appear in all copies. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
@@ -25,6 +25,6 @@ */ | ||
var addConnctionAPI = function(Modbus) { | ||
var cl = Modbus.prototype; | ||
/** | ||
/** | ||
* Connect to a communication port, using SerialPort. | ||
@@ -42,10 +42,10 @@ * | ||
} | ||
// create the SerialPort | ||
var SerialPort = require("serialport").SerialPort; | ||
var serialPort = new SerialPort(path, options); | ||
// re-set the serial port to use | ||
this._port = serialPort; | ||
// open and call next | ||
@@ -55,3 +55,3 @@ this.open(next); | ||
/** | ||
/** | ||
* Connect to a communication port, using TcpPort. | ||
@@ -65,3 +65,3 @@ * | ||
var port; | ||
// check if we have options | ||
@@ -72,15 +72,15 @@ if (typeof(next) == 'undefined' && typeof(options) == 'function') { | ||
} | ||
// create the TcpPort | ||
var TcpPort = require('../ports/tcpport'); | ||
var tcpPort = new TcpPort(ip, options); | ||
// re-set the port to use | ||
this._port = tcpPort; | ||
// open and call next | ||
this.open(next); | ||
} | ||
/** | ||
/** | ||
* Connect to a communication port, using TelnetPort. | ||
@@ -94,3 +94,3 @@ * | ||
var port; | ||
// check if we have options | ||
@@ -101,15 +101,15 @@ if (typeof(next) == 'undefined' && typeof(options) == 'function') { | ||
} | ||
// create the TcpPort | ||
var TelnetPort = require('../ports/telnetport'); | ||
var telnetPort = new TelnetPort(ip, options); | ||
// re-set the port to use | ||
this._port = telnetPort; | ||
// open and call next | ||
this.open(next); | ||
} | ||
/** | ||
/** | ||
* Connect to a communication port, using C701 UDP-to-Serial bridge. | ||
@@ -123,3 +123,3 @@ * | ||
var port; | ||
// check if we have options | ||
@@ -130,15 +130,15 @@ if (typeof(next) == 'undefined' && typeof(options) == 'function') { | ||
} | ||
// create the TcpPort | ||
var C701Port = require('../ports/c701port'); | ||
var c701Port = new C701Port(ip, options); | ||
// re-set the port to use | ||
this._port = c701Port; | ||
// open and call next | ||
this.open(next); | ||
} | ||
/** | ||
/** | ||
* Connect to a communication port, using Bufferd Serial port. | ||
@@ -156,15 +156,40 @@ * | ||
} | ||
// create the SerialPort | ||
var SerialPort = require('../ports/rtubufferedport'); | ||
var serialPort = new SerialPort(path, options); | ||
// re-set the serial port to use | ||
this._port = serialPort; | ||
// open and call next | ||
this.open(next); | ||
} | ||
/** | ||
* Connect to a communication port, using ASCII Serial port. | ||
* | ||
* @param {string} path the path to the Serial Port - required. | ||
* @param {object} options - the serial port options - optional. | ||
* @param {function} next the function to call next. | ||
*/ | ||
cl.connectAsciiSerial = function (path, options, next) { | ||
// check if we have options | ||
if (typeof(next) == 'undefined' && typeof(options) == 'function') { | ||
next = options; | ||
options = {}; | ||
} | ||
// create the ASCII SerialPort | ||
var SerialPortAscii = require('../ports/asciiport'); | ||
var serialPortAscii = new SerialPortAscii(path, options); | ||
// re-set the serial port to use | ||
this._port = serialPortAscii; | ||
// open and call next | ||
this.open(next); | ||
} | ||
} | ||
module.exports = addConnctionAPI; |
161
index.js
@@ -18,2 +18,6 @@ 'use strict'; | ||
/* Add bit operation functions to Buffer | ||
*/ | ||
require('./apis/buffer_bit')(); | ||
/** | ||
@@ -63,3 +67,3 @@ * @fileoverview ModbusRTU module, exports the ModbusRTU class. | ||
* Parse the data for a Modbus - | ||
* Read Coils (FC=02,01) | ||
* Read Coils (FC=02, 01) | ||
* | ||
@@ -88,3 +92,3 @@ * @param {buffer} data the data buffer to parse. | ||
* Parse the data for a Modbus - | ||
* Read Input Registers (FC=04,03) | ||
* Read Input Registers (FC=04, 03) | ||
* | ||
@@ -139,3 +143,3 @@ * @param {buffer} data the data buffer to parse. | ||
* Parse the data for a Modbus - | ||
* Preset Multiple Registers (FC=16) | ||
* Preset Multiple Registers (FC=15, 16) | ||
* | ||
@@ -206,2 +210,39 @@ * @param {buffer} data the data buffer to parse. | ||
/* check minimal length | ||
*/ | ||
if (data.length < 5) { | ||
error = "Data length error, expected " + | ||
length + " got " + data.length; | ||
if (next) | ||
next(error); | ||
return; | ||
} | ||
/* check message CRC | ||
* if CRC is bad raise an error | ||
*/ | ||
var crcIn = data.readUInt16LE(data.length - 2); | ||
var crc = _CRC16(data, data.length - 2); | ||
if (crcIn != crc) { | ||
error = "CRC error"; | ||
if (next) | ||
next(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 | modbus._nextCode)) { | ||
error = "Modbus exception " + data.readUInt8(2); | ||
if (next) | ||
next(error); | ||
return; | ||
} | ||
/* check message length | ||
@@ -211,3 +252,2 @@ * if we do not expect this data | ||
*/ | ||
if (data.length != length) { | ||
@@ -221,5 +261,2 @@ error = "Data length error, expected " + | ||
var address = data.readUInt8(0); | ||
var code = data.readUInt8(1); | ||
/* check message address and code | ||
@@ -242,48 +279,33 @@ * if we do not expect this message | ||
/* check message CRC | ||
* if CRC is bad raise an error | ||
*/ | ||
var crcIn = data.readUInt16LE(length - 2); | ||
var crc = _CRC16(data, length - 2); | ||
if (crcIn != crc) { | ||
error = "CRC error"; | ||
if (next) | ||
next(error); | ||
return; | ||
} | ||
/* parse incoming data | ||
*/ | ||
/* Read Coil Status (FC=01) | ||
* Read Input Status (FC=02) | ||
*/ | ||
if (code == 2 || code == 1) { | ||
_readFC2(data, next); | ||
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; | ||
} | ||
/* Read Input Registers (FC=04) | ||
* Read Holding Registers (FC=03) | ||
*/ | ||
if (code == 4 || code == 3) { | ||
_readFC4(data, next); | ||
} | ||
/* Force Single Coil (FC=05) | ||
*/ | ||
if (code == 5) { | ||
_readFC5(data, next); | ||
} | ||
/* Preset Single Register (FC=06) | ||
*/ | ||
if (code == 6) { | ||
_readFC6(data, next); | ||
} | ||
// Preset Multiple Registers (FC=16) | ||
if (code == 16) { | ||
_readFC16(data, next); | ||
} | ||
}); | ||
@@ -388,4 +410,4 @@ } | ||
* @param {number} address the slave unit address. | ||
* @param {number} dataAddress the Data Address of the first register. | ||
* @param {number} state the state to write to the coil (true / false). | ||
* @param {number} dataAddress the Data Address of the coil. | ||
* @param {number} state the boolean state to write to the coil (true / false). | ||
* @param {function} next the function to call next. | ||
@@ -422,3 +444,2 @@ */ | ||
/** | ||
@@ -456,4 +477,40 @@ * Write a Modbus "Preset Single Register " (FC=6) to serial port. | ||
/** | ||
* Write a Modbus "Force Multiple Coils" (FC=15) to serial port. | ||
* | ||
* @param {number} address the slave unit address. | ||
* @param {number} dataAddress the Data Address of the first coil. | ||
* @param {array} array the array of boolean states to write to coils. | ||
* @param {function} next the function to call next. | ||
*/ | ||
ModbusRTU.prototype.writeFC15 = function (address, dataAddress, array, next) { | ||
var code = 15; | ||
// set state variables | ||
this._nextAddress = address; | ||
this._nextCode = code; | ||
this._nextLength = 8; | ||
this._next = next; | ||
var dataBytes = Math.ceil(array.length / 8); | ||
var codeLength = 7 + dataBytes; | ||
var buf = new Buffer(codeLength + 2); // add 2 crc bytes | ||
buf.writeUInt8(address, 0); | ||
buf.writeUInt8(code, 1); | ||
buf.writeUInt16BE(dataAddress, 2); | ||
buf.writeUInt16BE(array.length, 4); | ||
buf.writeUInt8(dataBytes, 6); | ||
for (var i = 0; i < array.length; i++) { | ||
buf.writeBit(array[i], i, 7); | ||
} | ||
// add crc bytes to buffer | ||
_CRC16(buf, codeLength); | ||
// write buffer to serial port | ||
this._port.write(buf); | ||
} | ||
/** | ||
@@ -460,0 +517,0 @@ * Write a Modbus "Preset Multiple Registers" (FC=16) to serial port. |
{ | ||
"name": "modbus-serial", | ||
"version": "3.4.6", | ||
"version": "3.5.0", | ||
"description": "A pure JavaScript implemetation of MODBUS-RTU (and TCP) for NodeJS.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -22,3 +22,3 @@ 'use strict'; | ||
crc = crc ^ buf[i]; | ||
for (var j = 0; j < 8; j++) { | ||
@@ -38,2 +38,3 @@ tmp = crc & 0x0001; | ||
* check if a buffer chunk can be a modbus answer | ||
* or modbus exception | ||
* | ||
@@ -45,4 +46,4 @@ * @param {buffer} buf the buffer to check. | ||
// check buffer size | ||
if (buf.length != modbus._length) return false; | ||
if (buf.length != modbus._length && buf.length != 5) return false; | ||
// calculate crc16 | ||
@@ -52,4 +53,4 @@ var crcIn = buf.readUInt16LE(buf.length - 2); | ||
// check buffer unit-id, command and crc | ||
return (buf[0] == modbus._id && | ||
buf[1] == modbus._cmd && | ||
return (buf[0] == modbus._id && | ||
(0x7f & buf[1]) == modbus._cmd && | ||
crcIn == crc16(buf)); | ||
@@ -78,3 +79,3 @@ } | ||
// check message length | ||
if (data.length < (116 + modbus.length)) return; | ||
if (data.length < (116 + 5)) return; | ||
@@ -84,2 +85,3 @@ // check the C701 packet magic | ||
// check for modbus valid answer | ||
// get the serial data from the C701 packet | ||
@@ -91,3 +93,14 @@ var buffer = data.slice(data.length - modbus._length); | ||
modbus.emit('data', buffer); | ||
return; | ||
} | ||
// check for modbus exception | ||
// get the serial data from the C701 packet | ||
var buffer = data.slice(data.length - 5); | ||
//check the serial data | ||
if (checkData(modbus, buffer)) { | ||
modbus.emit('data', buffer); | ||
return; | ||
} | ||
}); | ||
@@ -144,2 +157,3 @@ | ||
case 6: | ||
case 15: | ||
case 16: | ||
@@ -146,0 +160,0 @@ this._length = 6 + 2; |
@@ -35,2 +35,3 @@ 'use strict'; | ||
* check if a buffer chunk can be a modbus answer | ||
* or modbus exception | ||
* | ||
@@ -42,3 +43,3 @@ * @param {buffer} buf the buffer to check. | ||
// check buffer size | ||
if (buf.length != modbus._length) return false; | ||
if (buf.length != modbus._length && buf.length != 5) return false; | ||
@@ -50,3 +51,3 @@ // calculate crc16 | ||
return (buf[0] == modbus._id && | ||
buf[1] == modbus._cmd && | ||
(0x7f & buf[1]) == modbus._cmd && | ||
crcIn == crc16(buf)); | ||
@@ -85,3 +86,3 @@ } | ||
// check data length | ||
if (bufferLength < 6 || length < 6) return; | ||
if (bufferLength < 5 || length < 6) return; | ||
@@ -155,2 +156,3 @@ // loop and check length-sized buffer chunks | ||
case 6: | ||
case 15: | ||
case 16: | ||
@@ -157,0 +159,0 @@ this._length = 6 + 2; |
@@ -37,2 +37,3 @@ 'use strict'; | ||
* check if a buffer chunk can be a modbus answer | ||
* or modbus exception | ||
* | ||
@@ -44,3 +45,3 @@ * @param {buffer} buf the buffer to check. | ||
// check buffer size | ||
if (buf.length != modbus._length) return false; | ||
if (buf.length != modbus._length && buf.length != 5) return false; | ||
@@ -52,3 +53,3 @@ // calculate crc16 | ||
return (buf[0] == modbus._id && | ||
buf[1] == modbus._cmd && | ||
(0x7f & buf[1]) == modbus._cmd && | ||
crcIn == crc16(buf)); | ||
@@ -89,3 +90,3 @@ } | ||
// check data length | ||
if (bufferLength < 6 || length < 6) return; | ||
if (bufferLength < 5 || length < 6) return; | ||
@@ -162,2 +163,3 @@ // loop and check length-sized buffer chunks | ||
case 6: | ||
case 15: | ||
case 16: | ||
@@ -164,0 +166,0 @@ this._length = 6 + 2; |
@@ -5,2 +5,6 @@ 'use strict'; | ||
/* Add bit operation functions to Buffer | ||
*/ | ||
require('../apis/buffer_bit')(); | ||
/** | ||
@@ -14,6 +18,6 @@ * Simulate a serial port with 4 modbus-rtu slaves connected | ||
var TestPort = function() { | ||
// simulate 14 input registers | ||
// simulate 11 input registers | ||
this._registers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; | ||
// simulate 14 holding registers | ||
// simulate 11 holding registers | ||
this._holding_registers = [0,0,0,0,0,0,0,0, 0xa12b, 0xffff, 0xb21a ]; | ||
@@ -167,3 +171,3 @@ | ||
if (state == 0xff00) { | ||
this._coils |= 1 << address; | ||
this._coils |= (1 << address); | ||
} else { | ||
@@ -191,4 +195,29 @@ this._coils &= ~(1 << address); | ||
// function code 15 | ||
if (functionCode == 15) { | ||
var address = buf.readUInt16BE(2); | ||
var length = buf.readUInt16BE(4); | ||
// if length is bad, ignore message | ||
if (buf.length != 7 + Math.ceil(length / 8) + 2) { | ||
return; | ||
} | ||
// build answer | ||
buffer = new Buffer(8); | ||
buffer.writeUInt16BE(address, 2); | ||
buffer.writeUInt16BE(length, 4); | ||
// write coils | ||
for (var i = 0; i < length; i++) { | ||
var state = buf.readBit(i, 7); | ||
if (state) { | ||
this._coils |= (1 << (address + i)); | ||
} else { | ||
this._coils &= ~(1 << (address + i)); | ||
} | ||
} | ||
} | ||
// function code 16 | ||
@@ -221,6 +250,2 @@ if (functionCode == 16) { | ||
// add crc | ||
crc = crc16(buffer); | ||
buffer.writeUInt16LE(crc, buffer.length - 2); | ||
// corrupt the answer | ||
@@ -235,6 +260,2 @@ switch (unitNumber) { | ||
break; | ||
case 3: | ||
// unit 3: answers with bad crc | ||
buffer.writeUInt16LE(crc + 1, buffer.length - 2); | ||
break; | ||
case 4: | ||
@@ -246,2 +267,11 @@ // unit 4: answers with bad unit number | ||
// add crc | ||
crc = crc16(buffer); | ||
buffer.writeUInt16LE(crc, buffer.length - 2); | ||
// unit 3: answers with bad crc | ||
if (unitNumber == 3) { | ||
buffer.writeUInt16LE(crc + 1, buffer.length - 2); | ||
} | ||
this.emit('data', buffer); | ||
@@ -248,0 +278,0 @@ } |
# modbus-serial | ||
A pure JavaScript implemetation of MODBUS-RTU (and TCP) for NodeJS | ||
[![NPM Version](https://img.shields.io/npm/v/gm.svg?style=flat)](https://www.npmjs.com/package/modbus-serial) | ||
[![npm](https://img.shields.io/npm/v/npm.svg)](https://www.npmjs.com/package/modbus-serial) | ||
@@ -65,2 +65,3 @@ This class makes ModbusRTU (and TCP) calls fun and easy. | ||
* FC6 "Preset Single Register" | ||
* FC15 "Force Multiple Coil" | ||
* FC16 "Preset Multiple Registers" | ||
@@ -70,3 +71,4 @@ | ||
* Modbus-RTU (modbus-rtu): Over serial line [require node serialport]. | ||
* modbus-RTU (modbus-rtu): Over serial line [require node serialport]. | ||
* modbus-ASCII (modbus-ascii): Over serial line [require node serialport]. | ||
* modbus-TCP (modbus-tcp): Over TCP/IP line. | ||
@@ -263,2 +265,12 @@ * modbus-RTU (telnet): Over Telnet server, TCP/IP serial bridge. | ||
---- | ||
##### .writeCoils(address, array) | ||
Writes "Force Multiple Coils" (FC=15) request to serial port. | ||
*address {number}:* | ||
The Data Address of the first register. | ||
*array {array}:* | ||
The array of states to force into the coils. | ||
---- | ||
##### .writeRegisters (address, array) | ||
@@ -420,3 +432,20 @@ Writes "Preset Multiple Registers" (FC=16) request to serial port. | ||
---- | ||
##### .writeFC6 (unit, address, array, callback) | ||
##### .writeFC15 (unit, address, array, callback) | ||
Writes "Force Multiple Coils" (FC=15) request to serial port. | ||
*unit {number}:* | ||
The slave unit address. | ||
*address {number}:* | ||
The Data Address of the first register. | ||
*array {array}:* | ||
The array of states to send to unit. | ||
*callback {function}:* (optional) | ||
Called once the unit returns an answer. The callback should be a function | ||
that looks like: function (error, data) { ... } | ||
---- | ||
##### .writeFC6 (unit, address, value, callback) | ||
Writes "Preset Single Register" (FC=6) request to serial port. | ||
@@ -551,1 +580,14 @@ | ||
Called once the client is connected. | ||
---- | ||
##### .connectAsciiSerial (path, options, callback) | ||
Connect using serial port with ASCII encoding. | ||
*path {string}:* | ||
The port path (e.g. "/dev/ttyS0") | ||
*options {object}:* (optional) | ||
The options for this connection. | ||
*callback {function}:* (optional) | ||
Called once the client is connected. |
@@ -27,2 +27,3 @@ 'use strict'; | ||
expect(data.data.toString()).to.equal([0xa12b, 0xffff, 0xb21a].toString()); | ||
done() | ||
@@ -37,2 +38,3 @@ }); | ||
expect(data.buffer.toString('hex')).to.equal("a12bffffb21a"); | ||
done() | ||
@@ -49,2 +51,3 @@ }); | ||
expect(data.data.toString()).to.equal([8, 9, 10].toString()); | ||
done() | ||
@@ -115,4 +118,50 @@ }); | ||
describe('#writeFC15() - force multiple coils.', function () { | ||
it('should write 3 coils [true, false, true] without errors', function (done) { | ||
modbusRTU.writeFC15(1, 8, [true, false, true], function(err, data) { | ||
expect(err).to.be.a('null'); | ||
done() | ||
}); | ||
}); | ||
it('should fail on short data answer', function (done) { | ||
modbusRTU.writeFC15(2, 8, [true, false, true], function(err, data) { | ||
expect(err).to.have.string('Data length error'); | ||
done() | ||
}); | ||
}); | ||
it('should fail on CRC error', function (done) { | ||
modbusRTU.writeFC15(3, 8, [true, false, true], function(err, data) { | ||
expect(err).to.have.string('CRC error'); | ||
done() | ||
}); | ||
}); | ||
it('should fail on unexpected replay', function (done) { | ||
modbusRTU.writeFC15(4, 8, [true, false, true], function(err, data) { | ||
expect(err).to.have.string('Unexpected data error'); | ||
done() | ||
}); | ||
}); | ||
}); | ||
describe('#writeFC1() - read coils after force multiple coils.', function () { | ||
it('should read coil 8, 9 ,10 to be true, false, true', function (done) { | ||
modbusRTU.writeFC1(1, 8, 4, function(err, data) { | ||
expect(err).to.be.a('null'); | ||
expect(data).to.have.property('data'); | ||
expect(data.data[0]).to.equal(true); | ||
expect(data.data[1]).to.equal(false); | ||
expect(data.data[2]).to.equal(true); | ||
done() | ||
}); | ||
}); | ||
}); | ||
describe('#writeFC16() - write holding registers.', function () { | ||
@@ -158,2 +207,3 @@ it('should write 3 registers [42, 128, 5] without errors', function (done) { | ||
expect(data.data.toString()).to.equal([42, 128, 5].toString()); | ||
done() | ||
@@ -160,0 +210,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
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
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
86978
0
2165
589