Comparing version 0.4.0 to 0.5.0
{ | ||
"name": "jsmodbus", | ||
"version": "0.4.0", | ||
"version": "0.5.0", | ||
"description": "Implementation for the Serial/TCP Modbus protocol.", | ||
@@ -5,0 +5,0 @@ "author": "Stefan Poeter <stefan.poeter@cloud-automation.de>", |
127
README.md
@@ -6,3 +6,3 @@ A simple an easy to use Modbus TCP client/server implementation. | ||
Modbus is a simple Modbus TCP Client (Server implementation is coming, but feel free to start on your own) with a simple API. | ||
Modbus is a simple Modbus TCP Client with a simple API. | ||
@@ -12,3 +12,3 @@ Installation | ||
Just type `npm install modbus` and you are ready to go. | ||
Just type `npm install jsmodbus` and you are ready to go. | ||
@@ -26,64 +26,89 @@ Testing | ||
-------------- | ||
```javascript | ||
var modbus = require('jsmodbus'); | ||
var modbus = require('./modbus'); | ||
// create a modbus client | ||
var client = modbus.createTCPClient(502, '127.0.0.1', function (err) { | ||
if (err) { | ||
console.log(err); | ||
exit(0); | ||
} | ||
}); | ||
// make some calls | ||
client.readInputRegister(0, 10, function (resp, err) { | ||
// resp will look like { fc: 4, byteCount: 20, register: [ values 0 - 10 ] } | ||
}); | ||
client.readCoils(5, 3, function (resp, err) { | ||
// resp will look like { fc: 1, byteCount: 1, register: [ true, false, true ] } | ||
}); | ||
client.writeSingleCoil(5, true, function (resp, err) { | ||
// resp will look like { fc: 5, byteCount: 4, outputAddress: 5, outputValue: true } | ||
}); | ||
// create a modbus client | ||
var client = modbus.createTCPClient(8888, '127.0.0.1', function (err) { | ||
if (err) { | ||
console.log(err); | ||
process.exit(0); | ||
} | ||
}); | ||
client.writeSingleRegister(13, 42, function (resp, err) { | ||
// resp will look like { fc: 6, byteCount: 4, registerAddress: 13, registerValue: 42 } | ||
}); | ||
// make some calls | ||
client.readHoldingRegister(0, 10, function (reps, err) { | ||
// resp will look like { fc: 3, byteCount: 20, register: [ values 0 - 10 ] } | ||
console.log(err, resp); | ||
client.readCoils(0, 13, function (resp, err) { | ||
// resp will look like { fc: 1, byteCount: 20, coils: [ values 0 - 13 ] } | ||
console.log(err, resp); | ||
}); | ||
client.readDiscreteInput(0, 13, function (resp, err) { | ||
// resp will look like { fc: 2, byteCount: 20, coils: [ values 0 - 13 ] } | ||
console.log(err, resp); | ||
}); | ||
client.readInputRegister(0, 10, function (resp, err) { | ||
// resp will look like { fc: 4, byteCount: 20, register: [ values 0 - 10 ] } | ||
console.log(err, resp); | ||
}); | ||
client.readCoils(5, 3, function (resp, err) { | ||
// resp will look like { fc: 1, byteCount: 1, register: [ true, false, true ] } | ||
console.log(err, resp); | ||
}); | ||
client.writeSingleCoil(5, true, function (resp, err) { | ||
// resp will look like { fc: 5, byteCount: 4, outputAddress: 5, outputValue: true } | ||
console.log(err, resp); | ||
}); | ||
client.writeSingleRegister(13, 42, function (resp, err) { | ||
// resp will look like { fc: 6, byteCount: 4, registerAddress: 13, registerValue: 42 } | ||
console.log(err, resp); | ||
}); | ||
client.writeMultipleCoils(3, [1, 0, 1, 0, 1, 1], function (resp, err) { | ||
// resp will look like { fc: 15, startAddress: 3, quantity: 6 } | ||
console.log(err, resp); | ||
}); | ||
``` | ||
Server example | ||
-------------- | ||
```javascript | ||
var modbus = require('jsmodbus'); | ||
var modbus = require('./modbus'); | ||
// create readInputRegister handler | ||
var rirHandler = function (start, quantity) { | ||
var resp = []; | ||
for (var i = start; i < start + quantity; i += 1) { | ||
resp.push(i); | ||
} | ||
// create readInputRegister handler | ||
var rirHandler = function (start, quantity) { | ||
var resp[]; | ||
for (var i = start; i < start + quant; i += 1) { | ||
resp.push(i); | ||
} | ||
return [resp]; | ||
}; | ||
return [resp]; | ||
}; | ||
var coil = false; | ||
var writeCoilHandler = function (addr, value) { | ||
var coil = false; | ||
var writeCoilHandler = function (addr, value) { | ||
if (addr === 0) { | ||
coil = value; | ||
} | ||
if (addr === 0) { | ||
coil = value; | ||
} | ||
return [addr, value]; | ||
return [addr, value]; | ||
}; | ||
}; | ||
// create Modbus TCP Server | ||
modbus.createTCPServer(8888, '127.0.0.1', function (err, modbusServer) { | ||
// addHandler | ||
modbusServer.addHandler(4, rirHandler); | ||
modbusServer.addHandler(5, writeCoilHandler); | ||
}); | ||
``` | ||
// create Modbus TCP Server | ||
modbus.createTCPServer(8888, '127.0.0.1', function (err, modbusServer) { | ||
// addHandler | ||
server.addHandler(4, rirHandler); | ||
server.addHandler(5, writeCoilHandler); | ||
}); | ||
Development | ||
@@ -90,0 +115,0 @@ ----------- |
@@ -14,3 +14,3 @@ | ||
0x02 : 'ILLEGAL DATA ADDRESS', | ||
0x03 : 'ILLEGAL DATA VALE', | ||
0x03 : 'ILLEGAL DATA VALUE', | ||
0x04 : 'SLAVE DEVICE FAILURE', | ||
@@ -151,3 +151,3 @@ 0x05 : 'ACKNOWLEDGE', | ||
// ReadCoils | ||
1 : function (pdu, cb, defer) { | ||
1 : function (pdu, cb) { | ||
@@ -177,7 +177,55 @@ log("handeling read coils response."); | ||
cb(resp); | ||
defer.resolve(resp); | ||
}, | ||
// ReadDiscreteInput | ||
2 : function (pdu, cb) { | ||
log("handle read discrete input register response."); | ||
var fc = pdu.readUInt8(0), | ||
byteCount = pdu.readUInt8(1), | ||
cntr = 0, | ||
resp = { | ||
fc : fc, | ||
byteCount : byteCount, | ||
coils : [] | ||
}; | ||
for (var i = 0; i < byteCount; i+=1) { | ||
var h = 1, cur = pdu.readUInt8(2 + i); | ||
for (var j = 0; j < 8; j+=1) { | ||
resp.coils[cntr] = (cur & h) > 0 ; | ||
h = h << 1; | ||
cntr += 1; | ||
} | ||
} | ||
cb(resp); | ||
}, | ||
// ReadHoldingRegister | ||
3: function (pdu, cb) { | ||
log("handling read holding register response."); | ||
var fc = pdu.readUInt8(0), | ||
byteCount = pdu.readUInt8(1); | ||
var resp = { | ||
fc : fc, | ||
byteCount : byteCount, | ||
register : [ ] | ||
}; | ||
var registerCount = byteCount / 2; | ||
for (var i = 0; i < registerCount; i += 1) { | ||
resp.register.push(pdu.readUInt16BE(2 + (i * 2))); | ||
} | ||
cb(resp); | ||
}, | ||
// ReadInputRegister | ||
4 : function (pdu, cb, defer) { | ||
4 : function (pdu, cb) { | ||
@@ -202,6 +250,5 @@ log("handling read input register response."); | ||
cb(resp); | ||
defer.resolve(resp); | ||
}, | ||
5 : function (pdu, cb, defer) { | ||
5 : function (pdu, cb) { | ||
@@ -221,6 +268,5 @@ log("handling write single coil response."); | ||
cb(resp); | ||
defer.resolve(resp); | ||
}, | ||
6 : function (pdu, cb, defer) { | ||
6 : function (pdu, cb) { | ||
@@ -240,5 +286,21 @@ log("handling write single register response."); | ||
cb(resp); | ||
defer.resolve(resp); | ||
}, | ||
// WriteMultipleCoils | ||
15 : function (pdu, cb) { | ||
log("handling write multiple coils response"); | ||
} | ||
var fc = pdu.readUInt8(0), | ||
startAddress = pdu.readUInt16BE(1), | ||
quantity = pdu.readUInt16BE(3); | ||
var resp = { | ||
fc : fc, | ||
startAddress : startAddress, | ||
quantity : quantity | ||
}; | ||
cb(resp); | ||
} | ||
@@ -245,0 +307,0 @@ }; |
@@ -11,3 +11,3 @@ var net = require('net'), | ||
exports.createTCPClient = function (port, host, cb) { | ||
exports.createTCPClient = function (port, host, unit_id, cb) { | ||
@@ -20,6 +20,20 @@ var net = require('net'), | ||
serialClientModule.setLogger(log); | ||
// retrieve arguments as array | ||
var args = []; | ||
for (var i = 0; i < arguments.length; i++) { | ||
args.push(arguments[i]); | ||
} | ||
// first argument is the port, 2nd argument is the host | ||
port = args.shift(); | ||
host = args.shift(); | ||
// last argument is the callback function. | ||
cb = args.pop(); | ||
// if args still holds items, this is the unit_id | ||
if (args.length > 0) unit_id = args.shift(); else unit_id = 1; //default to 1 | ||
var socket = net.connect(port, host), | ||
tcpClient = tcpClientModule.create(socket); | ||
tcpClient = tcpClientModule.create(socket, unit_id); | ||
socket.on('error', function (e) { | ||
@@ -26,0 +40,0 @@ |
@@ -61,5 +61,62 @@ var util = require('util'), | ||
return that.makeRequest(fc, pdu, !cb?dummy:cb, defer); | ||
if (!cb) { | ||
that.makeRequest(fc, pdu, that.promiseCallback(defer)); | ||
return defer.promise; | ||
} | ||
return that.makeRequest(fc, pdu, cb); | ||
}, | ||
readHoldingRegister: function (start, quantity, cb) { | ||
var fc = 3, | ||
defer = Q.defer(), | ||
pdu = that.pduWithTwoParameter(fc, start, quantity); | ||
if (!cb) { | ||
that.makeRequest(fc, pdu, that.promiseCallback(defer)); | ||
return that.promise; | ||
} | ||
return that.makeRequest(fc, pdu, cb); | ||
}, | ||
readDiscreteInput: function (start, quantity, cb) { | ||
var fc = 2, | ||
defer = Q.defer(), | ||
pdu = that.pduWithTwoParameter(fc, start, quantity); | ||
if (quantity > 2000) { | ||
if (!cb) { | ||
defer.reject(); | ||
return defer.promise; | ||
} | ||
cb(null, {}); | ||
return; | ||
} | ||
if (!cb) { | ||
that.makeRequest(fc, pdu, that.promiseCallback(defer)); | ||
return defer.promise; | ||
} | ||
return that.makeRequest(fc, pdu, cb); | ||
}, | ||
readInputRegister: function (start, quantity, cb) { | ||
@@ -71,4 +128,12 @@ | ||
return that.makeRequest(fc, pdu, !cb?dummy:cb, defer); | ||
if (!cb) { | ||
that.makeRequest(fc, pdu, that.promiseCallback(defer)); | ||
return defer.promise; | ||
} | ||
return that.makeRequest(fc, pdu, cb); | ||
}, | ||
@@ -82,4 +147,11 @@ | ||
return that.makeRequest(fc, pdu, !cb?dummy:cb, defer); | ||
if (!cb) { | ||
that.makeRequest(fc, pdu, that.promiseCallback(defer)); | ||
return defer.promise; | ||
} | ||
return that.makeRequest(fc, pdu, cb); | ||
}, | ||
@@ -92,11 +164,69 @@ | ||
return that.makeRequest(fc, pdu, !cb?dummy:cb, defer); | ||
if (!cb) { | ||
that.makeRequest(fc, pdu, that.promiseCallback(defer)); | ||
return defer.promise; | ||
} | ||
return that.makeRequest(fc, pdu, cb); | ||
}, | ||
writeMultipleCoils: function (startAddress, coils, cb) { | ||
if (coils.length > 1968) { | ||
cb({}, true); | ||
return; | ||
} | ||
var fc = 15, | ||
byteCount = Math.ceil(coils.length / 8), | ||
curByte = 0, | ||
cntr = 0, | ||
defer = Q.defer(), | ||
pdu = Put() | ||
.word8(fc) | ||
.word16be(startAddress) | ||
.word16be(coils.length) | ||
.word8(byteCount); | ||
for (var i = 0; i < coils.length; i += 1) { | ||
curByte += coils[i]?Math.pow(2, cntr):0; | ||
cntr = (cntr + 1) % 8; | ||
if (cntr === 0) { | ||
pdu.word8(curByte); | ||
} | ||
} | ||
pdu = pdu.buffer(); | ||
if (!cb) { | ||
that.makeRequest(fc, pdu, that.promiseCallback(defer)); | ||
return defer.promise; | ||
} | ||
return that.makeRequest(fc, pdu, cb); | ||
}, | ||
isConnected: function () { | ||
return that.isConnected; | ||
}, | ||
on: function (name, cb) { | ||
socket.on(name, cb); | ||
}, | ||
@@ -109,4 +239,7 @@ | ||
close: function () { | ||
that.socket.end(); | ||
} | ||
}; | ||
@@ -120,2 +253,24 @@ | ||
proto.promiseCallback = function (defer) { | ||
var that = this; | ||
return function (resp, err) { | ||
console.log(arguments); | ||
if (err) { | ||
defer.reject(err); | ||
return; | ||
} | ||
defer.resolve(resp); | ||
}; | ||
}; | ||
/** | ||
@@ -125,5 +280,9 @@ * Pack up the pdu and the handler function | ||
*/ | ||
proto.makeRequest = function (fc, pdu, cb, defer) { | ||
proto.makeRequest = function (fc, pdu, cb) { | ||
var req = { fc: fc, cb: cb, pdu: pdu, defer: defer }; | ||
var req = { | ||
fc: fc, | ||
cb: cb, | ||
pdu: pdu | ||
}; | ||
@@ -133,7 +292,7 @@ this.pipe.push(req); | ||
if (this.state === 'ready') { | ||
this.flush(); | ||
} | ||
return defer.promise; | ||
}; | ||
@@ -148,3 +307,5 @@ | ||
if (!this.isConnected) { | ||
return; | ||
} | ||
@@ -203,3 +364,3 @@ | ||
} | ||
handler(pdu, that.current.cb, that.current.defer); | ||
handler(pdu, that.current.cb); | ||
@@ -253,6 +414,6 @@ that.current = null; | ||
return Put() | ||
.word8(fc) | ||
.word16be(start) | ||
.word16be(quantity) | ||
.buffer(); | ||
.word8(fc) | ||
.word16be(start) | ||
.word16be(quantity) | ||
.buffer(); | ||
@@ -274,3 +435,5 @@ }; | ||
return function () { | ||
that.isConnected = false; | ||
}; | ||
@@ -277,0 +440,0 @@ |
@@ -12,5 +12,4 @@ | ||
var PROTOCOL_VERSION = 0, | ||
UNIT_ID = 1; | ||
var PROTOCOL_VERSION = 0; | ||
/** | ||
@@ -22,6 +21,6 @@ * ModbusTCPClient handles the MBAP that is the | ||
*/ | ||
var ModbusTCPClient = function (socket) { | ||
var ModbusTCPClient = function (socket, unit_id) { | ||
if (!(this instanceof ModbusTCPClient)) { | ||
return new ModbusTCPClient(socket); | ||
return new ModbusTCPClient(socket, unit_id); | ||
} | ||
@@ -52,3 +51,3 @@ | ||
.word16be(pdu.length + 1) // pdu length | ||
.word8(UNIT_ID) // unit id | ||
.word8(typeof unit_id === 'number' ? unit_id:true) // unit id | ||
.put(pdu) // the actual pdu | ||
@@ -55,0 +54,0 @@ .buffer(); |
@@ -338,6 +338,133 @@ | ||
}); | ||
/** | ||
* Simply read holding registers with success | ||
*/ | ||
it("should read holding register just fine", function () { | ||
var cb = sinon.spy(); | ||
client.readHoldingRegister(0, 1, cb); | ||
var res = Put() | ||
.word8(3) // function code | ||
.word8(2) // byte count | ||
.word16be(42) // register 0 value | ||
.buffer(); | ||
socket.emit('data', res); | ||
assert.ok(cb.called); | ||
assert.deepEqual(cb.args[0][0], { fc: 3, byteCount: 2, register: [42]}); | ||
}); | ||
/** | ||
* Write Multiple Coils with success | ||
*/ | ||
it("should write multiple coils just fine", function () { | ||
var cb = sinon.spy(); | ||
client.writeMultipleCoils(123, [1, 0, 1, 0, 1, 0, 1, 1], cb); | ||
var res = Put() | ||
.word8(15) // function code | ||
.word16be(123) // start address | ||
.word16be(8) // quantity of outputs | ||
.buffer(); | ||
socket.emit('data', res); | ||
assert.ok(cb.called); | ||
assert.deepEqual(cb.args[0][0], { fc: 15, startAddress: 123, quantity: 8 }); | ||
}); | ||
/** | ||
* Write Multiple Coils with too many coils | ||
*/ | ||
it("should not write multiple coils due to too much coils", function () { | ||
var cb = sinon.spy(), | ||
coils = []; | ||
for (var i = 0; i < 1969; i += 1) { | ||
coils.push(1); | ||
} | ||
client.writeMultipleCoils(123, coils, cb); | ||
assert.ok(cb.called); | ||
assert.ok(cb.args[0][1]); | ||
}); | ||
// Read Discrete Input | ||
it('should handle a read discrete input request', function () { | ||
var cb = sinon.spy(); | ||
client.readDiscreteInput(0, 13, cb); | ||
var res = Put() | ||
.word8(2) // function code | ||
.word8(2) // register address | ||
.word8(3) // coils 1 - 8 | ||
.word8(1) // coils 9 - 13 | ||
.buffer(); | ||
socket.emit('data', res); | ||
assert.ok(cb.calledOnce); | ||
assert.deepEqual(cb.args[0][0], { | ||
fc: 2, | ||
byteCount: 2, | ||
coils: [ | ||
true, // 1 | ||
true, // 2 | ||
false, // 3 | ||
false, // 4 | ||
false, // 5 | ||
false, // 6 | ||
false, // 7 | ||
false, // 8 | ||
true, // 9 | ||
false, // 10 | ||
false, // 11 | ||
false, // 12 | ||
false, // 13 | ||
false, // filled in 14 | ||
false, // filled in 15 | ||
false, // filled in 16 | ||
] | ||
}); | ||
}); | ||
it('should handle a wrong read discrete input request', function () { | ||
var cb = sinon.spy(); | ||
// quantitiy is to big | ||
client.readDiscreteInput(0, 2001, cb); | ||
assert.ok(cb.calledOnce); | ||
assert.deepEqual(cb.args[0][1], { }); | ||
}); | ||
}); | ||
}); |
@@ -64,3 +64,3 @@ | ||
describe('Requests/Responses', function () { | ||
describe('Requests/Responses Without Unit Identifier', function () { | ||
@@ -80,5 +80,5 @@ var client; | ||
beforeEach(function (done) { | ||
socket = new SocketApi(); | ||
socket = new SocketApi(); | ||
client = modbusClient.create(socket); | ||
client = modbusClient.create(socket); | ||
@@ -147,2 +147,84 @@ done(); | ||
describe('Requests/Responses With Unit Identifier', function () { | ||
var client; | ||
var SocketApi = function () { | ||
eventEmitter.call(this); | ||
this.write = function () { }; | ||
}; | ||
util.inherits(SocketApi, eventEmitter); | ||
var socket; | ||
beforeEach(function (done) { | ||
socket = new SocketApi(); | ||
unit_id = 100; | ||
client = modbusClient.create(socket, unit_id); | ||
done(); | ||
}); | ||
it('should read a simple request', function () { | ||
var onDataSpy = sinon.spy(); | ||
client.on('data', onDataSpy); | ||
var res = Put() | ||
.word16be(0) | ||
.word16be(0) | ||
.word16be(5) | ||
.word8(unit_id) | ||
.word8(4) | ||
.word8(2) | ||
.word16be(42) | ||
.buffer(); | ||
var exRes = Put() | ||
.word8(4) | ||
.word8(2) | ||
.word16be(42) | ||
.buffer(); | ||
socket.emit('data', res); | ||
assert.ok(onDataSpy.called); | ||
assert.deepEqual(onDataSpy.args[0][0], exRes); | ||
}); | ||
it('should respond', function () { | ||
var writeSpy = sinon.spy(socket, 'write'); | ||
var req = Put() | ||
.word8(4) | ||
.word16be(2) | ||
.word16be(12) | ||
.buffer(); | ||
var exReq = Put() | ||
.word16be(0) | ||
.word16be(0) | ||
.word16be(6) | ||
.word8(unit_id) | ||
.word8(4) | ||
.word16be(2) | ||
.word16be(12) | ||
.buffer(); | ||
socket.emit('connect'); | ||
client.write(req); | ||
assert.ok(writeSpy.calledOnce); | ||
assert.deepEqual(writeSpy.args[0][0], exReq); | ||
}); | ||
}); | ||
}); |
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
78130
21
2212
130