├── .gitignore ├── .travis.yml ├── .vimrc ├── .vscode ├── settings.json └── tasks.json ├── README.md ├── bin └── jsmodbus.js ├── examples ├── javascript │ ├── serial │ │ ├── ListSerialInterfaces.js │ │ ├── ReadCoils.js │ │ ├── ReadDiscreteInput.js │ │ ├── ReadHoldingRegisters.js │ │ ├── ReadInputRegister.js │ │ ├── WriteSingleCoil.js │ │ └── WriteSingleRegister.js │ └── tcp │ │ ├── ReadCoils.js │ │ ├── ReadDiscreteInputs.js │ │ ├── ReadHoldingRegisters.js │ │ ├── ReadInputRegisters.js │ │ ├── Reconnect.js │ │ ├── SimpleServer.js │ │ ├── WriteMultipleCoils.js │ │ ├── WriteMultipleRegisters.js │ │ ├── WriteSingleCoil.js │ │ ├── WriteSingleRegister.js │ │ ├── lr-allfcs-a.js │ │ ├── lr-allfcs-b.js │ │ ├── lr-fc03.js │ │ └── lr-server.js └── typescript │ ├── serial │ ├── ListSerialInterfaces.ts │ ├── ReadCoils.ts │ ├── ReadDiscreteInput.ts │ ├── ReadHoldingRegisters.ts │ ├── ReadInputRegister.ts │ ├── WriteSingleCoil.ts │ └── WriteSingleRegister.ts │ └── tcp │ ├── ReadCoils.ts │ ├── ReadDiscreteInputs.ts │ ├── ReadHoldingRegisters.ts │ ├── ReadInputRegisters.ts │ ├── Reconnect.ts │ ├── SimpleServer.ts │ ├── WriteMultipleCoils.ts │ ├── WriteMultipleRegisters.ts │ ├── WriteSingleCoil.ts │ ├── WriteSingleRegister.ts │ ├── lr-allfcs-a.ts │ ├── lr-allfcs-b.ts │ ├── lr-fc03.ts │ └── lr-server.ts ├── package-lock.json ├── package.json ├── src ├── abstract-request.ts ├── abstract-response.ts ├── buffer-utils.ts ├── client-request-handler.ts ├── client-response-handler.ts ├── codes │ ├── errors.ts │ ├── function-codes.ts │ └── index.ts ├── constants │ ├── index.ts │ ├── limits.ts │ └── primatives.ts ├── errors │ ├── index.ts │ ├── isInternalException.ts │ └── isUserRequestError.ts ├── modbus-client.ts ├── modbus-rtu-client.ts ├── modbus-rtu-server.ts ├── modbus-server-client.ts ├── modbus-server-request-handler.ts ├── modbus-server-response-handler.ts ├── modbus-server.ts ├── modbus-tcp-client.ts ├── modbus-tcp-server.ts ├── modbus.ts ├── request-response-map.ts ├── request │ ├── exception.ts │ ├── index.ts │ ├── read-coils.ts │ ├── read-discrete-inputs.ts │ ├── read-holding-registers.ts │ ├── read-input-registers.ts │ ├── request-body.ts │ ├── request-factory.ts │ ├── write-multiple-coils.ts │ ├── write-multiple-registers.ts │ ├── write-single-coil.ts │ └── write-single-register.ts ├── response │ ├── exception.ts │ ├── index.ts │ ├── read-coils.ts │ ├── read-discrete-inputs.ts │ ├── read-holding-registers.ts │ ├── read-input-registers.ts │ ├── read-response-body.ts │ ├── response-body.ts │ ├── response-factory.ts │ ├── write-multiple-coils.ts │ ├── write-multiple-registers.ts │ ├── write-response.body.ts │ ├── write-single-coil.ts │ └── write-single-register.ts ├── rtu-client-request-handler.ts ├── rtu-client-response-handler.ts ├── rtu-request.ts ├── rtu-response.ts ├── tcp-client-request-handler.ts ├── tcp-client-response-handler.ts ├── tcp-request.ts ├── tcp-response.ts ├── user-request-error.ts ├── user-request-metrics.ts └── user-request.ts ├── test ├── buffer-utils.test.js ├── heap-test-coils.js ├── read-coils.test.js ├── read-discrete-inputs.test.js ├── read-holding-registers.test.js ├── read-input-registers.test.js ├── response-body.test.js ├── rtu-client-request-handler.test.js ├── rtu-client-response-handler.test.js ├── rtu-server.test.js ├── tcp-client.test.js ├── tcp-request-handler.test.js ├── tcp-request.test.js ├── tcp-response-handler.test.js ├── tcp-server-response-handler.test.js ├── tcp-server.test.js ├── write-multiple-coils.test.js ├── write-multiple-registers.test.js ├── write-single-coil.test.js └── write-single-register.test.js ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.swp 3 | out 4 | old-tests 5 | .nyc-output 6 | dist 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 10 4 | - 8 5 | - 7 6 | - 6 7 | - 6.0 8 | -------------------------------------------------------------------------------- /.vimrc: -------------------------------------------------------------------------------- 1 | set nocompatible 2 | filetype plugin indent on 3 | set tabstop=2 4 | set softtabstop=2 5 | set shiftwidth=2 6 | set expandtab 7 | 8 | set t_Co=256 9 | syntax on 10 | set background=light 11 | colorscheme distinguished 12 | 13 | let g:formatters_html = ['htmlbeautify'] 14 | 15 | let g:syntastic_html_checkers = ['jshint'] 16 | let g:syntastic_javascript_checkers = ['standard'] 17 | let g:syntastic_check_on_open=1 18 | 19 | autocmd BufWritePost *.js silent !standard --fix % 20 | autocmd BufWrite * :Autoformat 21 | 22 | set autoread 23 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [{ 6 | "label": "tsc watch", 7 | "type": "shell", 8 | "command": "./node_modules/.bin/tsc", 9 | "isBackground": true, 10 | "args": ["--watch", "--noEmit", "--project", "."], 11 | "group": { 12 | "kind": "build", 13 | "isDefault": true 14 | }, 15 | "presentation": { 16 | "reveal": "never", 17 | "echo": false, 18 | "focus": false, 19 | "panel": "dedicated" 20 | }, 21 | "problemMatcher": "$tsc-watch" 22 | }] 23 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A simple an easy to use Modbus TCP client/server implementation. 2 | 3 | [![JavaScript Style Guide](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard) 4 | 5 | Modbus [![Build Status](https://travis-ci.org/Cloud-Automation/node-modbus.png)](https://travis-ci.org/Cloud-Automation/node-modbus) 6 | ======== 7 | 8 | Modbus is a simple Modbus TCP/RTU Client/Server with a simple API. It supports modbus function codes 1 - 6 and 15 and 16. 9 | 10 | Status 11 | ------ 12 | 13 | Version 4.0.0 is a early beta release. Please use and test it and help make it better. We keep you posted on the status of this module. 14 | 15 | Installation 16 | ------------ 17 | 18 | Just type `npm install jsmodbus` and you are ready to go. You can also install this module globally and use the Command Line Interface. Simply type `npm install -g jsmodbus`. 19 | 20 | CLI 21 | --- 22 | 23 | Version 4 offers a Command Line Interface. Just install the module globally and type `jsmodbus --help` to get some more Information. 24 | 25 | Testing 26 | ------- 27 | 28 | The test files are implemented using [mocha](https://github.com/visionmedia/mocha) and sinon. 29 | 30 | Simply `npm install -g mocha` and `npm install -g sinon`. To run the tests type from the projects root folder `mocha test/*`. 31 | 32 | Please feel free to fork and add your own tests. 33 | 34 | Debugging 35 | --------- 36 | If you want to see some debugging information, since Version 3 we use the debug module. You can filter the debug output by defining the DEBUG environment variable like so `export DEBUG=*` 37 | 38 | TCP Client Example 39 | -------------- 40 | ```javascript 41 | // create a tcp modbus client 42 | const Modbus = require('jsmodbus') 43 | const net = require('net') 44 | const socket = new net.Socket() 45 | const client = new Modbus.client.TCP(socket, unitId) 46 | const options = { 47 | 'host' : host, 48 | 'port' : port 49 | } 50 | 51 | ``` 52 | 53 | RTU Client Example 54 | --------------------- 55 | ```javascript 56 | 57 | // create a tcp modbus client 58 | const Modbus = require('jsmodbus') 59 | const SerialPort = require('serialport') 60 | const options = { 61 | baudRate: 57600 62 | } 63 | const socket = new SerialPort("/dev/tty-usbserial1", options) 64 | const client = new Modbus.client.RTU(socket, address) 65 | ``` 66 | 67 | Client API Example 68 | ------------------ 69 | ```javascript 70 | // for reconnecting see node-net-reconnect npm module 71 | 72 | // use socket.on('open', ...) when using serialport 73 | socket.on('connect', function () { 74 | 75 | // make some calls 76 | 77 | client.readCoils(0, 13).then(function (resp) { 78 | 79 | // resp will look like { response : [TCP|RTU]Response, request: [TCP|RTU]Request } 80 | // the data will be located in resp.response.body.coils: , resp.response.body.payload: 81 | 82 | console.log(resp); 83 | 84 | }, console.error); 85 | 86 | }); 87 | 88 | socket.connect(options) 89 | 90 | ``` 91 | 92 | Keeping TCP Connections alive 93 | ---------------------------- 94 | I've written a module to keep net.Socket connections alive by emitting TCP Keep-Alive messages. Have a look at the node-net-reconnect module. 95 | 96 | Server example 97 | -------------- 98 | ```javascript 99 | 100 | const modbus = require('jsmodbus') 101 | const net = require('net') 102 | const netServer = new net.Server() 103 | const server = new modbus.server.TCP(netServer) 104 | 105 | netServer.listen(502) 106 | 107 | ```` 108 | 109 | 110 | ## License (MIT) 111 | 112 | Copyright (C) 2017 Stefan Poeter (Stefan.Poeter[at]cloud-automation.de) 113 | 114 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 115 | 116 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 117 | 118 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 119 | -------------------------------------------------------------------------------- /examples/javascript/serial/ListSerialInterfaces.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const SerialPort = require('serialport') 4 | 5 | SerialPort.list(function (err, ports) { 6 | if (err) { 7 | console.error(err) 8 | return 9 | } 10 | 11 | ports.forEach(function (port) { 12 | console.log(port.comName) 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /examples/javascript/serial/ReadCoils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const modbus = require('../../') 4 | const SerialPort = require('serialport') 5 | const socket = new SerialPort('/dev/ttyUSB1', { 6 | baudRate: 115200, 7 | parity: 'even', 8 | stopBits: 1, 9 | dataBits: 8 10 | }) 11 | const client = new modbus.client.RTU(socket, 0x01) 12 | 13 | socket.on('open', function () { 14 | client.readCoils(process.argv[4], process.argv[5]) 15 | .then(function (resp) { 16 | console.log(resp) 17 | socket.end() 18 | }).catch(function () { 19 | console.error(arguments) 20 | socket.end() 21 | }) 22 | }) 23 | 24 | socket.on('error', console.error) 25 | -------------------------------------------------------------------------------- /examples/javascript/serial/ReadDiscreteInput.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 'use strict' 3 | const modbus = require('../..') 4 | const Serialport = require('serialport') 5 | const socket = new Serialport('COM6', { 6 | baudRate: 19200, 7 | Parity: 'none', 8 | stopBits: 1, 9 | dataBits: 8 10 | }) 11 | 12 | // set Slave PLC ID 13 | const client = new modbus.client.RTU(socket, 1) 14 | 15 | socket.on('connect', function () { 16 | client.readDiscreteInputs(0, 12).then(function (resp) { 17 | console.log(resp) 18 | socket.close() 19 | }, function (err) { 20 | console.log(err) 21 | socket.close() 22 | }) 23 | }) 24 | 25 | socket.on('error', function (err) { 26 | console.log(err) 27 | }) 28 | -------------------------------------------------------------------------------- /examples/javascript/serial/ReadHoldingRegisters.js: -------------------------------------------------------------------------------- 1 | const modbus = require('../../') 2 | const SerialPort = require('serialport') 3 | const socket = new SerialPort('/dev/ttyUSB0', { 4 | baudrate: 115200, 5 | parity: 'even', 6 | stopbits: 1 7 | }) 8 | 9 | const client = new modbus.client.RTU(socket, 1) 10 | 11 | socket.on('close', function () { 12 | console.log(arguments) 13 | }) 14 | 15 | socket.on('open', function () { 16 | client.readInputRegisters(1000, 1) 17 | .then(function (resp) { 18 | console.log(resp) 19 | socket.close() 20 | }).catch(function () { 21 | console.error(arguments) 22 | socket.close() 23 | }) 24 | }) 25 | 26 | socket.on('data', function () { 27 | console.log(arguments) 28 | }) 29 | 30 | socket.on('error', console.error) 31 | -------------------------------------------------------------------------------- /examples/javascript/serial/ReadInputRegister.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const modbus = require('../..') 4 | const Serialport = require('serialport') 5 | const socket = new Serialport('COM6', { 6 | baudRate: 19200, 7 | Parity: 'none', 8 | stopBits: 1, 9 | dataBits: 8 10 | }) 11 | 12 | // set Slave PLC ID 13 | const client = new modbus.client.RTU(socket, 1) 14 | 15 | socket.on('connect', function () { 16 | client.readInputRegister(0, 12).then(function (resp) { 17 | console.log(resp) 18 | socket.close() 19 | }, function (err) { 20 | console.log(err) 21 | socket.close() 22 | }) 23 | }) 24 | 25 | socket.on('error', function (err) { 26 | console.log(err) 27 | }) 28 | -------------------------------------------------------------------------------- /examples/javascript/serial/WriteSingleCoil.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const modbus = require('../..') 4 | const Serialport = require('serialport') 5 | const socket = new Serialport('COM6', { 6 | baudRate: 19200, 7 | Parity: 'none', 8 | stopBits: 1, 9 | dataBits: 8 10 | }) 11 | 12 | // set Slave PLC ID 13 | const client = new modbus.client.RTU(socket, 1) 14 | 15 | socket.on('connect', function () { 16 | client.writeSingleCoil(4, true).then(function (resp) { 17 | console.log(resp) 18 | socket.close() 19 | }, function (err) { 20 | console.log(err) 21 | socket.close() 22 | }) 23 | }) 24 | 25 | socket.on('error', function (err) { 26 | console.log(err) 27 | }) 28 | -------------------------------------------------------------------------------- /examples/javascript/serial/WriteSingleRegister.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const modbus = require('../..') 4 | const Serialport = require('serialport') 5 | const socket = new Serialport('COM6', { 6 | baudRate: 19200, 7 | Parity: 'none', 8 | stopBits: 1, 9 | dataBits: 8 10 | }) 11 | 12 | // set Slave PLC ID 13 | const client = new modbus.client.RTU(socket, 1) 14 | 15 | socket.on('connect', function () { 16 | client.writeSingleRegister(5, 123).then(function (resp) { 17 | console.log(resp) 18 | socket.close() 19 | }).fail(function (err) { 20 | console.log(err) 21 | socket.close() 22 | }) 23 | }) 24 | 25 | socket.on('error', function (err) { 26 | console.log(err) 27 | }) 28 | -------------------------------------------------------------------------------- /examples/javascript/tcp/ReadCoils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const modbus = require('../../') 4 | const net = require('net') 5 | const socket = new net.Socket() 6 | const options = { 7 | 'host': '127.0.0.1', 8 | 'port': '8502' 9 | } 10 | const client = new modbus.client.TCP(socket) 11 | 12 | socket.on('connect', function () { 13 | client.readCoils(0, 8) 14 | .then(function (resp) { 15 | console.log(resp) 16 | socket.end() 17 | }).catch(function () { 18 | console.error(arguments) 19 | socket.end() 20 | }) 21 | }) 22 | 23 | socket.on('error', console.error) 24 | socket.connect(options) 25 | -------------------------------------------------------------------------------- /examples/javascript/tcp/ReadDiscreteInputs.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const modbus = require('../../') 4 | const net = require('net') 5 | const socket = new net.Socket() 6 | const options = { 7 | 'host': '127.0.0.1', 8 | 'port': '8502' 9 | } 10 | const client = new modbus.client.TCP(socket) 11 | socket.on('connect', function () { 12 | client.readDiscreteInputs(0, 12) 13 | .then(function (resp) { 14 | console.log(resp) 15 | socket.end() 16 | }).catch(function () { 17 | console.error(arguments) 18 | socket.end() 19 | }) 20 | }) 21 | 22 | socket.on('error', console.error) 23 | socket.connect(options) 24 | -------------------------------------------------------------------------------- /examples/javascript/tcp/ReadHoldingRegisters.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const modbus = require('../..') 4 | const net = require('net') 5 | const socket = new net.Socket() 6 | const options = { 7 | 'host': '127.0.0.1', 8 | 'port': '502' 9 | } 10 | const client = new modbus.client.TCP(socket) 11 | 12 | socket.on('connect', function () { 13 | client.readHoldingRegisters(0, 10) 14 | .then(function (resp) { 15 | console.log(resp.response._body.valuesAsArray) 16 | socket.end() 17 | }).catch(function () { 18 | console.error(require('util').inspect(arguments, { 19 | depth: null 20 | })) 21 | socket.end() 22 | }) 23 | }) 24 | 25 | socket.on('error', console.error) 26 | socket.connect(options) 27 | -------------------------------------------------------------------------------- /examples/javascript/tcp/ReadInputRegisters.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const modbus = require('../..') 4 | const net = require('net') 5 | const socket = new net.Socket() 6 | const options = { 7 | 'host': '127.0.0.1', 8 | 'port': '8502' 9 | } 10 | const client = new modbus.client.TCP(socket) 11 | 12 | socket.on('connect', function () { 13 | client.readInputRegisters(0, 2) 14 | .then(function (resp) { 15 | console.log(resp) 16 | socket.end() 17 | }).catch(function () { 18 | console.error(arguments) 19 | socket.end() 20 | }) 21 | }) 22 | 23 | socket.on('error', console.error) 24 | socket.connect(options) 25 | -------------------------------------------------------------------------------- /examples/javascript/tcp/Reconnect.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const modbus = require('../..') 4 | const client = modbus.client.tcp.complete({ 5 | 'host': process.argv[2], 6 | 'port': process.argv[3], 7 | 'autoReconnect': false, 8 | 'logEnabled': true 9 | }).connect() 10 | let successCount = 0 11 | let errorCount = 0 12 | let reconnectCount = 0 13 | let closedOnPurpose = false 14 | let firstTime = true 15 | 16 | const start = function () { 17 | console.log('Starting request...') 18 | 19 | client.readHoldingRegisters(process.argv[4], process.argv[5]).then(function (resp) { 20 | successCount += 1 21 | 22 | console.log('Success', successCount, 'Errors', errorCount, 'Reconnect', reconnectCount) 23 | 24 | console.log('Request finished successfull.') 25 | 26 | setTimeout(function () { 27 | start() 28 | }, 2000) 29 | }, function (err) { 30 | console.error(err) 31 | errorCount += 1 32 | 33 | console.log('Success', successCount, 'Errors', errorCount, 'Reconnect', reconnectCount) 34 | 35 | console.log('Request finished UNsuccessfull.') 36 | }) 37 | } 38 | 39 | client.on('connect', function () { 40 | console.log('client connected.') 41 | 42 | if (firstTime) { 43 | firstTime = false 44 | } else { 45 | reconnectCount += 1 46 | } 47 | 48 | start() 49 | }) 50 | 51 | const shutdown = function () { 52 | closedOnPurpose = true 53 | 54 | client.close() 55 | } 56 | 57 | process.on('SIGTERM', shutdown) 58 | process.on('SIGINT', shutdown) 59 | 60 | client.on('close', function () { 61 | console.log('Client closed, stopping interval.') 62 | 63 | if (!closedOnPurpose) { 64 | client.reconnect() 65 | } 66 | }) 67 | 68 | client.on('error', function (err) { 69 | console.log('Client Error', err) 70 | }) 71 | -------------------------------------------------------------------------------- /examples/javascript/tcp/SimpleServer.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const net = require('net') 4 | const modbus = require('../..') 5 | const netServer = new net.Server() 6 | const holding = Buffer.alloc(10000) 7 | const server = new modbus.server.TCP(netServer, { 8 | holding: holding 9 | /* Set the buffer options to undefined to use the events */ 10 | /* coils: undefined */ 11 | /* discrete: undefined */ 12 | /* holding: undefined */ 13 | /* input: undefined */ 14 | }) 15 | 16 | server.on('connection', function (client) { 17 | console.log('New Connection') 18 | }) 19 | 20 | server.on('readCoils', function (request, response, send) { 21 | /* Implement your own, set coil to undefined in server options to use coil events */ 22 | 23 | response.body.coils[0] = true 24 | response.body.coils[1] = false 25 | 26 | send(response) 27 | }) 28 | 29 | server.on('readHoldingRegisters', function (request, response, send) { 30 | 31 | /* Implement your own, set holding to undefined in server options to use holding events */ 32 | 33 | }) 34 | 35 | server.on('preWriteSingleRegister', function (value, address) { 36 | console.log('Write Single Register') 37 | console.log('Original {register, value}: {', address, ',', server.holding.readUInt16BE(address), '}') 38 | }) 39 | 40 | server.on('WriteSingleRegister', function (value, address) { 41 | console.log('New {register, value}: {', address, ',', server.holding.readUInt16BE(address), '}') 42 | }) 43 | 44 | server.on('writeMultipleCoils', function (value) { 45 | console.log('Write multiple coils - Existing: ', value) 46 | }) 47 | 48 | server.on('postWriteMultipleCoils', function (value) { 49 | console.log('Write multiple coils - Complete: ', value) 50 | }) 51 | 52 | /* server.on('writeMultipleRegisters', function (value) { 53 | console.log('Write multiple registers - Existing: ', value) 54 | }) */ 55 | 56 | server.on('postWriteMultipleRegisters', function (value) { 57 | console.log('Write multiple registers - Complete: ', holding.readUInt16BE(0)) 58 | }) 59 | 60 | server.on('connection', function (client) { 61 | 62 | /* work with the modbus tcp client */ 63 | 64 | }) 65 | 66 | server.coils.writeUInt16BE(0x0000, 0) 67 | server.coils.writeUInt16BE(0x0000, 2) 68 | server.coils.writeUInt16BE(0x0000, 4) 69 | server.coils.writeUInt16BE(0x0000, 6) 70 | 71 | server.discrete.writeUInt16BE(0x5678, 0) 72 | 73 | server.holding.writeUInt16BE(0x0000, 0) 74 | server.holding.writeUInt16BE(0x0000, 2) 75 | 76 | server.input.writeUInt16BE(0xff00, 0) 77 | server.input.writeUInt16BE(0xff00, 2) 78 | 79 | console.log(process.argv[2]) 80 | netServer.listen(process.argv[2] || 8502) 81 | -------------------------------------------------------------------------------- /examples/javascript/tcp/WriteMultipleCoils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const modbus = require('../..') 4 | const net = require('net') 5 | const socket = new net.Socket() 6 | const options = { 7 | 'host': '127.0.0.1', 8 | 'port': '8502' 9 | } 10 | const client = new modbus.client.TCP(socket) 11 | 12 | socket.on('connect', function () { 13 | const values = Buffer.from([0xff]) 14 | 15 | client.writeMultipleCoils(13, values, 8) 16 | .then(function (resp) { 17 | console.log(resp) 18 | socket.end() 19 | }).catch(function () { 20 | console.error(arguments) 21 | socket.end() 22 | }) 23 | }) 24 | 25 | socket.on('error', console.error) 26 | socket.connect(options) 27 | -------------------------------------------------------------------------------- /examples/javascript/tcp/WriteMultipleRegisters.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const modbus = require('../..') 4 | const net = require('net') 5 | const socket = new net.Socket() 6 | const options = { 7 | 'host': '127.0.0.1', 8 | 'port': '8502' 9 | } 10 | const client = new modbus.client.TCP(socket) 11 | 12 | socket.on('connect', function () { 13 | client.writeMultipleRegisters(1, [0x000a, 0x0102]) 14 | .then(function (resp) { 15 | console.log(resp) 16 | socket.end() 17 | }).catch(function () { 18 | console.error(arguments) 19 | socket.end() 20 | }) 21 | }) 22 | 23 | socket.on('error', console.error) 24 | socket.connect(options) 25 | -------------------------------------------------------------------------------- /examples/javascript/tcp/WriteSingleCoil.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const modbus = require('../..') 4 | const net = require('net') 5 | const socket = new net.Socket() 6 | const options = { 7 | 'host': '127.0.0.1', 8 | 'port': '8502' 9 | } 10 | const client = new modbus.client.TCP(socket) 11 | 12 | socket.on('connect', function () { 13 | client.writeSingleCoil(17, false) 14 | .then(function (resp) { 15 | console.log(resp) 16 | socket.end() 17 | }).catch(function () { 18 | console.error(arguments) 19 | socket.end() 20 | }) 21 | }) 22 | 23 | socket.on('error', console.error) 24 | socket.connect(options) 25 | -------------------------------------------------------------------------------- /examples/javascript/tcp/WriteSingleRegister.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const modbus = require('../..') 4 | const net = require('net') 5 | const socket = new net.Socket() 6 | const options = { 7 | 'host': '192.168.56.101', 8 | 'port': '502' 9 | } 10 | const client = new modbus.client.TCP(socket) 11 | 12 | socket.on('connect', function () { 13 | client.writeSingleRegister(1002, 333) 14 | .then(function (resp) { 15 | console.log(resp) 16 | socket.end() 17 | }).catch(function () { 18 | console.error(arguments) 19 | socket.end() 20 | }) 21 | }) 22 | 23 | socket.on('error', console.error) 24 | socket.connect(options) 25 | -------------------------------------------------------------------------------- /examples/javascript/tcp/lr-allfcs-a.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const modbus = require('../..') 4 | const net = require('net') 5 | const socket = new net.Socket() 6 | const options = { 7 | 'host': '127.0.0.1', 8 | 'port': '8502' 9 | } 10 | const client = new modbus.client.TCP(socket) 11 | let cycleDone = true 12 | 13 | socket.on('connect', function () { 14 | setInterval(function () { 15 | if (!cycleDone) { 16 | return 17 | } 18 | 19 | cycleDone = false 20 | 21 | const fc01 = client.readCoils(0, 40) 22 | const fc02 = client.readDiscreteInputs(0, 40) 23 | const fc03 = client.readHoldingRegisters(0, 100) 24 | const fc04 = client.readInputRegisters(0, 100) 25 | 26 | const fc05StartAddress = Math.trunc(Math.random() * 1000) 27 | const fc05 = client.writeSingleCoil(fc05StartAddress, true) 28 | 29 | const fc06StartAddress = Math.trunc(Math.random() * 100) 30 | const fc06Value = Math.trunc(Math.random() * 0xFFFF) 31 | const fc06 = client.writeSingleRegister(fc06StartAddress, fc06Value) 32 | 33 | const fc0FStartAddress = Math.trunc(Math.random() * 100) 34 | const fc0FBufferSize = Math.trunc(Math.random() * 100) 35 | const fc0F = client.writeMultipleCoils(fc0FStartAddress, Buffer.alloc(fc0FBufferSize * 2)) 36 | 37 | const fc10StartAddress = Math.trunc(Math.random() * 100) 38 | const fc10BufferSize = Math.trunc(Math.random() * 100) 39 | const fc10 = client.writeMultipleCoils(fc10StartAddress, Buffer.alloc(fc10BufferSize * 2)) 40 | 41 | const allFcs = Promise.all([fc01, fc02, fc03, fc04, fc05, fc06, fc0F, fc10]) 42 | 43 | allFcs.then(function () { 44 | cycleDone = true 45 | }, socket.close) 46 | }, 100) 47 | }) 48 | 49 | socket.on('error', console.error) 50 | socket.connect(options) 51 | -------------------------------------------------------------------------------- /examples/javascript/tcp/lr-allfcs-b.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const modbus = require('../..') 4 | const net = require('net') 5 | const options = { 6 | 'host': '127.0.0.1', 7 | 'port': '8502' 8 | } 9 | let cycleDone = true 10 | 11 | setInterval(function () { 12 | if (!cycleDone) { 13 | return 14 | } 15 | 16 | cycleDone = false 17 | 18 | const socket = new net.Socket() 19 | const client = new modbus.client.TCP(socket) 20 | 21 | socket.on('end', function () { 22 | cycleDone = true 23 | }) 24 | 25 | socket.on('connect', function () { 26 | const fc01 = client.readCoils(0, 40) 27 | const fc02 = client.readDiscreteInputs(0, 40) 28 | const fc03 = client.readHoldingRegisters(0, 100) 29 | const fc04 = client.readInputRegisters(0, 100) 30 | 31 | const fc05StartAddress = Math.trunc(Math.random() * 1000) 32 | const fc05 = client.writeSingleCoil(fc05StartAddress, true) 33 | 34 | const fc06StartAddress = Math.trunc(Math.random() * 100) 35 | const fc06Value = Math.trunc(Math.random() * 0xFFFF) 36 | const fc06 = client.writeSingleRegister(fc06StartAddress, fc06Value) 37 | 38 | const fc0FStartAddress = Math.trunc(Math.random() * 100) 39 | const fc0FBufferSize = Math.trunc(Math.random() * 100) 40 | const fc0F = client.writeMultipleCoils(fc0FStartAddress, Buffer.alloc(fc0FBufferSize * 2)) 41 | 42 | const fc10StartAddress = Math.trunc(Math.random() * 100) 43 | const fc10BufferSize = Math.trunc(Math.random() * 100) 44 | const fc10 = client.writeMultipleCoils(fc10StartAddress, Buffer.alloc(fc10BufferSize * 2)) 45 | 46 | const allFcs = Promise.all([fc01, fc02, fc03, fc04, fc05, fc06, fc0F, fc10]) 47 | 48 | allFcs.then(function () { 49 | socket.end() 50 | }, process.exit) 51 | }, 200) 52 | 53 | socket.on('error', console.error) 54 | socket.connect(options) 55 | }) 56 | -------------------------------------------------------------------------------- /examples/javascript/tcp/lr-fc03.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const modbus = require('../..') 4 | const net = require('net') 5 | const socket = new net.Socket() 6 | const options = { 7 | 'host': '127.0.0.1', 8 | 'port': '8502' 9 | } 10 | const client = new modbus.client.TCP(socket) 11 | 12 | socket.on('connect', function () { 13 | setInterval(function () { 14 | client.readHoldingRegisters(0, 2) 15 | .then(function (resp) {}).catch(function () { 16 | console.error(arguments) 17 | socket.end() 18 | }) 19 | }, 200) 20 | }) 21 | 22 | socket.on('error', console.error) 23 | socket.connect(options) 24 | -------------------------------------------------------------------------------- /examples/javascript/tcp/lr-server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict' 4 | 5 | const net = require('net') 6 | const modbus = require('../..') 7 | const netServer = new net.Server() 8 | const server = new modbus.server.TCP(netServer) 9 | 10 | server.on('connection', function () { 11 | 12 | }) 13 | 14 | netServer.listen(8502) 15 | -------------------------------------------------------------------------------- /examples/typescript/serial/ListSerialInterfaces.ts: -------------------------------------------------------------------------------- 1 | import SerialPort from 'serialport' 2 | 3 | SerialPort.list(function (err, ports) { 4 | if (err) { 5 | console.error(err) 6 | return 7 | } 8 | 9 | ports.forEach(function (port) { 10 | console.log(port.comName) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /examples/typescript/serial/ReadCoils.ts: -------------------------------------------------------------------------------- 1 | import Modbus from '../../../'; 2 | import SerialPort, { OpenOptions } from 'serialport'; 3 | 4 | const options: OpenOptions = { 5 | baudRate: 115200, 6 | parity: 'even', 7 | stopBits: 1, 8 | dataBits: 8 9 | } 10 | 11 | const socket = new SerialPort('/dev/ttyUSB1', options); 12 | 13 | const address = 0x01; 14 | const client = new Modbus.client.RTU(socket, address); 15 | 16 | const readStart = 0; 17 | const readCount = 5; 18 | 19 | socket.on('open', function () { 20 | 21 | client.readCoils(readStart, readCount) 22 | .then(({ metrics, request, response }) => { 23 | console.log('Transfer Time: ' + metrics.transferTime) 24 | console.log('Response Body Payload: ' + response.body.valuesAsArray) 25 | console.log('Response Body Payload As Buffer: ' + response.body.valuesAsBuffer) 26 | }) 27 | .catch(handleErrors) 28 | .finally(() => socket.end()) 29 | 30 | }) 31 | 32 | socket.on('error', console.error) 33 | 34 | function handleErrors(err: any) { 35 | if (Modbus.errors.isUserRequestError(err)) { 36 | switch (err.err) { 37 | case 'OutOfSync': 38 | case 'Protocol': 39 | case 'Timeout': 40 | case 'ManuallyCleared': 41 | case 'ModbusException': 42 | case 'Offline': 43 | case 'crcMismatch': 44 | console.log('Error Message: ' + err.message, 'Error' + 'Modbus Error Type: ' + err.err) 45 | break; 46 | } 47 | 48 | } else if (Modbus.errors.isInternalException(err)) { 49 | console.log('Error Message: ' + err.message, 'Error' + 'Error Name: ' + err.name, err.stack) 50 | } else { 51 | console.log('Unknown Error', err); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/typescript/serial/ReadDiscreteInput.ts: -------------------------------------------------------------------------------- 1 | import Modbus from '../../../dist/modbus'; 2 | import SerialPort, { OpenOptions } from 'serialport'; 3 | 4 | const options: OpenOptions = { 5 | baudRate: 19200, 6 | parity: 'none', 7 | stopBits: 1, 8 | dataBits: 8 9 | } 10 | 11 | const socket = new SerialPort('COM6', options); 12 | 13 | const address = 0x01; 14 | const client = new Modbus.client.RTU(socket, address) 15 | 16 | const readStart = 0; 17 | const readCount = 12; 18 | 19 | socket.on('connect', function () { 20 | 21 | client.readDiscreteInputs(readStart, readCount) 22 | .then(({ metrics, request, response }) => { 23 | console.log('Transfer Time: ' + metrics.transferTime) 24 | console.log('Response Body Payload: ' + response.body.valuesAsArray) 25 | console.log('Response Body Payload As Buffer: ' + response.body.valuesAsBuffer) 26 | }) 27 | .catch(handleErrors) 28 | .finally(() => socket.close()) 29 | 30 | }) 31 | 32 | socket.on('error', function (err) { 33 | console.log(err) 34 | }) 35 | 36 | function handleErrors(err: any) { 37 | if (Modbus.errors.isUserRequestError(err)) { 38 | switch (err.err) { 39 | case 'OutOfSync': 40 | case 'Protocol': 41 | case 'Timeout': 42 | case 'ManuallyCleared': 43 | case 'ModbusException': 44 | case 'Offline': 45 | case 'crcMismatch': 46 | console.log('Error Message: ' + err.message, 'Error' + 'Modbus Error Type: ' + err.err) 47 | break; 48 | } 49 | 50 | } else if (Modbus.errors.isInternalException(err)) { 51 | console.log('Error Message: ' + err.message, 'Error' + 'Error Name: ' + err.name, err.stack) 52 | } else { 53 | console.log('Unknown Error', err); 54 | } 55 | } -------------------------------------------------------------------------------- /examples/typescript/serial/ReadHoldingRegisters.ts: -------------------------------------------------------------------------------- 1 | import Modbus from '../../../dist/modbus'; 2 | import SerialPort, { OpenOptions } from 'serialport'; 3 | 4 | const options: OpenOptions = { 5 | baudRate: 115200, 6 | parity: 'even', 7 | stopBits: 1 8 | } 9 | 10 | const socket = new SerialPort('/dev/ttyUSB0', options); 11 | 12 | const address = 0x01; 13 | const client = new Modbus.client.RTU(socket, address) 14 | 15 | socket.on('close', function () { 16 | console.log(arguments) 17 | }) 18 | 19 | const readStart = 1000; 20 | const readCount = 1; 21 | 22 | socket.on('open', function () { 23 | 24 | client.readInputRegisters(readStart, readCount) 25 | .then(({ metrics, request, response }) => { 26 | console.log('Transfer Time: ' + metrics.transferTime) 27 | console.log('Response Body Payload: ' + response.body.valuesAsArray) 28 | console.log('Response Body Payload As Buffer: ' + response.body.valuesAsBuffer) 29 | }) 30 | .catch(handleErrors) 31 | .finally(() => socket.close()) 32 | 33 | }) 34 | 35 | socket.on('data', function () { 36 | console.log(arguments) 37 | }) 38 | 39 | socket.on('error', console.error) 40 | 41 | 42 | function handleErrors(err: any) { 43 | if (Modbus.errors.isUserRequestError(err)) { 44 | switch (err.err) { 45 | case 'OutOfSync': 46 | case 'Protocol': 47 | case 'Timeout': 48 | case 'ManuallyCleared': 49 | case 'ModbusException': 50 | case 'Offline': 51 | case 'crcMismatch': 52 | console.log('Error Message: ' + err.message, 'Error' + 'Modbus Error Type: ' + err.err) 53 | break; 54 | } 55 | 56 | } else if (Modbus.errors.isInternalException(err)) { 57 | console.log('Error Message: ' + err.message, 'Error' + 'Error Name: ' + err.name, err.stack) 58 | } else { 59 | console.log('Unknown Error', err); 60 | } 61 | } -------------------------------------------------------------------------------- /examples/typescript/serial/ReadInputRegister.ts: -------------------------------------------------------------------------------- 1 | import Modbus from '../../../dist/modbus'; 2 | import SerialPort, { OpenOptions } from 'serialport'; 3 | 4 | const options: OpenOptions = { 5 | baudRate: 19200, 6 | parity: 'none', 7 | stopBits: 1, 8 | dataBits: 8 9 | } 10 | 11 | const socket = new SerialPort('COM6', options) 12 | 13 | const address = 0x01; 14 | const client = new Modbus.client.RTU(socket, address) 15 | 16 | const readStart = 0; 17 | const readCount = 12; 18 | 19 | socket.on('connect', function () { 20 | 21 | client.readInputRegisters(readStart, readCount) 22 | .then(({ metrics, request, response }) => { 23 | console.log('Transfer Time: ' + metrics.transferTime) 24 | console.log('Response Body Payload: ' + response.body.valuesAsArray) 25 | console.log('Response Body Payload As Buffer: ' + response.body.valuesAsBuffer) 26 | }) 27 | .catch(handleErrors) 28 | .finally(() => socket.close()) 29 | 30 | }) 31 | 32 | socket.on('error', function (err) { 33 | console.log(err) 34 | }) 35 | 36 | function handleErrors(err: any) { 37 | if (Modbus.errors.isUserRequestError(err)) { 38 | switch (err.err) { 39 | case 'OutOfSync': 40 | case 'Protocol': 41 | case 'Timeout': 42 | case 'ManuallyCleared': 43 | case 'ModbusException': 44 | case 'Offline': 45 | case 'crcMismatch': 46 | console.log('Error Message: ' + err.message, 'Error' + 'Modbus Error Type: ' + err.err) 47 | break; 48 | } 49 | 50 | } else if (Modbus.errors.isInternalException(err)) { 51 | console.log('Error Message: ' + err.message, 'Error' + 'Error Name: ' + err.name, err.stack) 52 | } else { 53 | console.log('Unknown Error', err); 54 | } 55 | } -------------------------------------------------------------------------------- /examples/typescript/serial/WriteSingleCoil.ts: -------------------------------------------------------------------------------- 1 | import Modbus from '../../../dist/modbus'; 2 | import SerialPort, { OpenOptions } from 'serialport'; 3 | 4 | const options: OpenOptions = { 5 | baudRate: 19200, 6 | parity: 'none', 7 | stopBits: 1, 8 | dataBits: 8 9 | } 10 | 11 | const socket = new SerialPort('COM6', options) 12 | 13 | const address = 0x01; 14 | const client = new Modbus.client.RTU(socket, address) 15 | 16 | const writeAddress = 0; 17 | const writeValue = true; // either 0, 1, or boolean 18 | 19 | socket.on('connect', function () { 20 | 21 | client.writeSingleCoil(writeAddress, writeValue) 22 | .then(({ metrics, request, response }) => { 23 | console.log('Transfer Time: ' + metrics.transferTime) 24 | console.log('Response Function Code: ' + response.body.fc) 25 | }) 26 | .catch(handleErrors) 27 | .finally(() => socket.close()) 28 | 29 | }) 30 | 31 | socket.on('error', function (err) { 32 | console.log(err) 33 | }) 34 | 35 | function handleErrors(err: any) { 36 | if (Modbus.errors.isUserRequestError(err)) { 37 | switch (err.err) { 38 | case 'OutOfSync': 39 | case 'Protocol': 40 | case 'Timeout': 41 | case 'ManuallyCleared': 42 | case 'ModbusException': 43 | case 'Offline': 44 | case 'crcMismatch': 45 | console.log('Error Message: ' + err.message, 'Error' + 'Modbus Error Type: ' + err.err) 46 | break; 47 | } 48 | 49 | } else if (Modbus.errors.isInternalException(err)) { 50 | console.log('Error Message: ' + err.message, 'Error' + 'Error Name: ' + err.name, err.stack) 51 | } else { 52 | console.log('Unknown Error', err); 53 | } 54 | } -------------------------------------------------------------------------------- /examples/typescript/serial/WriteSingleRegister.ts: -------------------------------------------------------------------------------- 1 | import Modbus from '../../../dist/modbus'; 2 | import SerialPort, { OpenOptions } from 'serialport'; 3 | 4 | const options: OpenOptions = { 5 | baudRate: 19200, 6 | parity: 'none', 7 | stopBits: 1, 8 | dataBits: 8 9 | } 10 | 11 | const socket = new SerialPort('COM6', options) 12 | 13 | const address = 0x01; 14 | const client = new Modbus.client.RTU(socket, address) 15 | 16 | const writeAddress = 5; 17 | const writeValue = 123; // either 0, 1, or boolean 18 | 19 | socket.on('connect', function () { 20 | 21 | client.writeSingleRegister(writeAddress, writeValue) 22 | .then(({ metrics, request, response }) => { 23 | console.log('Transfer Time: ' + metrics.transferTime) 24 | console.log('Response Function Code: ' + response.body.fc) 25 | }) 26 | .catch(handleErrors) 27 | .finally(() => socket.close()) 28 | 29 | }) 30 | 31 | socket.on('error', function (err) { 32 | console.log(err) 33 | }) 34 | 35 | function handleErrors(err: any) { 36 | if (Modbus.errors.isUserRequestError(err)) { 37 | switch (err.err) { 38 | case 'OutOfSync': 39 | case 'Protocol': 40 | case 'Timeout': 41 | case 'ManuallyCleared': 42 | case 'ModbusException': 43 | case 'Offline': 44 | case 'crcMismatch': 45 | console.log('Error Message: ' + err.message, 'Error' + 'Modbus Error Type: ' + err.err) 46 | break; 47 | } 48 | 49 | } else if (Modbus.errors.isInternalException(err)) { 50 | console.log('Error Message: ' + err.message, 'Error' + 'Error Name: ' + err.name, err.stack) 51 | } else { 52 | console.log('Unknown Error', err); 53 | } 54 | } -------------------------------------------------------------------------------- /examples/typescript/tcp/ReadCoils.ts: -------------------------------------------------------------------------------- 1 | import Modbus from '../../..' 2 | import { Socket, SocketConnectOpts } from 'net' 3 | 4 | const socket = new Socket() 5 | 6 | const options: SocketConnectOpts = { 7 | host: '127.0.0.1', 8 | port: 8502 9 | } 10 | const client = new Modbus.client.TCP(socket) 11 | 12 | const readStart = 0; 13 | const readCount = 5; 14 | 15 | socket.on('connect', function () { 16 | 17 | client.readCoils(readStart, readCount) 18 | .then(({ metrics, request, response }) => { 19 | console.log('Transfer Time: ' + metrics.transferTime) 20 | console.log('Response Body Payload: ' + response.body.valuesAsArray) 21 | console.log('Response Body Payload As Buffer: ' + response.body.valuesAsBuffer) 22 | }) 23 | .catch(handleErrors) 24 | .finally(() => socket.end()) 25 | 26 | }) 27 | 28 | socket.on('error', console.error) 29 | socket.connect(options) 30 | 31 | 32 | function handleErrors(err: any) { 33 | if (Modbus.errors.isUserRequestError(err)) { 34 | switch (err.err) { 35 | case 'OutOfSync': 36 | case 'Protocol': 37 | case 'Timeout': 38 | case 'ManuallyCleared': 39 | case 'ModbusException': 40 | case 'Offline': 41 | case 'crcMismatch': 42 | console.log('Error Message: ' + err.message, 'Error' + 'Modbus Error Type: ' + err.err) 43 | break; 44 | } 45 | 46 | } else if (Modbus.errors.isInternalException(err)) { 47 | console.log('Error Message: ' + err.message, 'Error' + 'Error Name: ' + err.name, err.stack) 48 | } else { 49 | console.log('Unknown Error', err); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/typescript/tcp/ReadDiscreteInputs.ts: -------------------------------------------------------------------------------- 1 | import Modbus from '../../..' 2 | import { Socket, SocketConnectOpts } from 'net' 3 | 4 | const socket = new Socket() 5 | 6 | const options: SocketConnectOpts = { 7 | host: '127.0.0.1', 8 | port: 8502 9 | } 10 | const client = new Modbus.client.TCP(socket) 11 | 12 | const readStart = 0; 13 | const readCount = 12; 14 | 15 | 16 | socket.on('connect', function () { 17 | client.readDiscreteInputs(readStart, readCount) 18 | .then(({ metrics, request, response }) => { 19 | console.log('Transfer Time: ' + metrics.transferTime) 20 | console.log('Response Body Payload: ' + response.body.valuesAsArray) 21 | console.log('Response Body Payload As Buffer: ' + response.body.valuesAsBuffer) 22 | }) 23 | .catch(handleErrors) 24 | .finally(() => socket.end()) 25 | }) 26 | 27 | socket.on('error', console.error) 28 | socket.connect(options) 29 | 30 | function handleErrors(err: any) { 31 | if (Modbus.errors.isUserRequestError(err)) { 32 | switch (err.err) { 33 | case 'OutOfSync': 34 | case 'Protocol': 35 | case 'Timeout': 36 | case 'ManuallyCleared': 37 | case 'ModbusException': 38 | case 'Offline': 39 | case 'crcMismatch': 40 | console.log('Error Message: ' + err.message, 'Error' + 'Modbus Error Type: ' + err.err) 41 | break; 42 | } 43 | 44 | } else if (Modbus.errors.isInternalException(err)) { 45 | console.log('Error Message: ' + err.message, 'Error' + 'Error Name: ' + err.name, err.stack) 46 | } else { 47 | console.log('Unknown Error', err); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/typescript/tcp/ReadHoldingRegisters.ts: -------------------------------------------------------------------------------- 1 | import Modbus from '../../..' 2 | import { Socket, SocketConnectOpts } from 'net' 3 | 4 | const socket = new Socket() 5 | 6 | const options: SocketConnectOpts = { 7 | host: '127.0.0.1', 8 | port: 8502 9 | } 10 | const client = new Modbus.client.TCP(socket) 11 | 12 | const readStart = 0; 13 | const readCount = 10; 14 | 15 | socket.on('connect', function () { 16 | client.readHoldingRegisters(readStart, readCount) 17 | .then(({ metrics, request, response }) => { 18 | console.log('Transfer Time: ' + metrics.transferTime) 19 | console.log('Response Body Payload: ' + response.body.valuesAsArray) 20 | console.log('Response Body Payload As Buffer: ' + response.body.valuesAsBuffer) 21 | }) 22 | .catch(handleErrors) 23 | .finally(() => socket.end()) 24 | }) 25 | 26 | socket.on('error', console.error) 27 | socket.connect(options) 28 | 29 | function handleErrors(err: any) { 30 | if (Modbus.errors.isUserRequestError(err)) { 31 | switch (err.err) { 32 | case 'OutOfSync': 33 | case 'Protocol': 34 | case 'Timeout': 35 | case 'ManuallyCleared': 36 | case 'ModbusException': 37 | case 'Offline': 38 | case 'crcMismatch': 39 | console.log('Error Message: ' + err.message, 'Error' + 'Modbus Error Type: ' + err.err) 40 | break; 41 | } 42 | 43 | } else if (Modbus.errors.isInternalException(err)) { 44 | console.log('Error Message: ' + err.message, 'Error' + 'Error Name: ' + err.name, err.stack) 45 | } else { 46 | console.log('Unknown Error', err); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/typescript/tcp/ReadInputRegisters.ts: -------------------------------------------------------------------------------- 1 | import Modbus from '../../..' 2 | import { Socket, SocketConnectOpts } from 'net' 3 | 4 | const socket = new Socket() 5 | 6 | const options: SocketConnectOpts = { 7 | host: '127.0.0.1', 8 | port: 8502 9 | } 10 | const client = new Modbus.client.TCP(socket) 11 | 12 | const readStart = 0; 13 | const readCount = 2; 14 | 15 | socket.on('connect', function () { 16 | client.readInputRegisters(readStart, readCount) 17 | .then(({ metrics, request, response }) => { 18 | console.log('Transfer Time: ' + metrics.transferTime) 19 | console.log('Response Body Payload: ' + response.body.valuesAsArray) 20 | console.log('Response Body Payload As Buffer: ' + response.body.valuesAsBuffer) 21 | }) 22 | .catch(handleErrors) 23 | .finally(() => socket.end()) 24 | }) 25 | 26 | socket.on('error', console.error) 27 | socket.connect(options) 28 | 29 | function handleErrors(err: any) { 30 | if (Modbus.errors.isUserRequestError(err)) { 31 | switch (err.err) { 32 | case 'OutOfSync': 33 | case 'Protocol': 34 | case 'Timeout': 35 | case 'ManuallyCleared': 36 | case 'ModbusException': 37 | case 'Offline': 38 | case 'crcMismatch': 39 | console.log('Error Message: ' + err.message, 'Error' + 'Modbus Error Type: ' + err.err) 40 | break; 41 | } 42 | 43 | } else if (Modbus.errors.isInternalException(err)) { 44 | console.log('Error Message: ' + err.message, 'Error' + 'Error Name: ' + err.name, err.stack) 45 | } else { 46 | console.log('Unknown Error', err); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/typescript/tcp/Reconnect.ts: -------------------------------------------------------------------------------- 1 | import Modbus from '../../..' 2 | import { Socket, SocketConnectOpts } from 'net' 3 | 4 | const socket = new Socket() 5 | 6 | const options: SocketConnectOpts = { 7 | host: '127.0.0.1', 8 | port: 8502 9 | } 10 | const client = new Modbus.client.TCP(socket) 11 | 12 | let successCount = 0 13 | let errorCount = 0 14 | let reconnectCount = 0 15 | let closedOnPurpose = false 16 | let firstTime = true 17 | 18 | const readStart = 0; 19 | const readCount = 10; 20 | 21 | const start = function () { 22 | console.log('Starting request...') 23 | 24 | client.readHoldingRegisters(readStart, readCount) 25 | .then(({ metrics, request, response }) => { 26 | successCount += 1 27 | 28 | console.log('Transfer Time: ' + metrics.transferTime) 29 | console.log('Response Body Payload: ' + response.body.valuesAsArray) 30 | console.log('Response Body Payload As Buffer: ' + response.body.valuesAsBuffer) 31 | 32 | console.log('Success', successCount, 'Errors', errorCount, 'Reconnect', reconnectCount) 33 | console.log('Request finished successfull.') 34 | 35 | setTimeout(start, 2000) 36 | }) 37 | .catch(err => { 38 | console.error(err) 39 | errorCount += 1 40 | 41 | console.log('Success', successCount, 'Errors', errorCount, 'Reconnect', reconnectCount) 42 | 43 | console.log('Request finished Unsuccessfully.') 44 | }) 45 | } 46 | 47 | socket.on('connect', function () { 48 | console.log('client connected.') 49 | 50 | if (firstTime) { 51 | firstTime = false 52 | } else { 53 | reconnectCount += 1 54 | } 55 | 56 | start() 57 | }) 58 | 59 | const shutdown = () => { 60 | closedOnPurpose = true 61 | socket.end() 62 | } 63 | 64 | const reconnect = () => { 65 | if (!closedOnPurpose) { 66 | socket.connect(options) 67 | } 68 | } 69 | 70 | process.on('SIGTERM', shutdown) 71 | process.on('SIGINT', shutdown) 72 | 73 | socket.on('close', function () { 74 | console.log('Socket closed, stopping interval.') 75 | reconnect() 76 | }) 77 | 78 | socket.on('error', function (err) { 79 | console.log('Socket Error', err) 80 | }) 81 | -------------------------------------------------------------------------------- /examples/typescript/tcp/SimpleServer.ts: -------------------------------------------------------------------------------- 1 | import Modbus from '../../../dist/modbus' 2 | import { Server } from 'net' 3 | 4 | const netServer = new Server() 5 | const initialHoldingRegisters = Buffer.alloc(10000) 6 | const server = new Modbus.server.TCP(netServer, { 7 | holding: initialHoldingRegisters 8 | }) 9 | 10 | server.on('connection', function (client) { 11 | console.log('New Connection') 12 | }) 13 | 14 | server.on('readCoils', function (request, response, send) { 15 | /* Implement your own */ 16 | 17 | response.body.coils[0] = true 18 | response.body.coils[1] = false 19 | 20 | send(response) 21 | }) 22 | 23 | server.on('readHoldingRegisters', function (request, response, send) { 24 | 25 | /* Implement your own */ 26 | 27 | }) 28 | 29 | server.on('preWriteSingleRegister', function (value, address) { 30 | console.log('Write Single Register') 31 | console.log('Original {register, value}: {', address, ',', server.holding.readUInt16BE(address), '}') 32 | }) 33 | 34 | server.on('WriteSingleRegister', function (value, address) { 35 | console.log('New {register, value}: {', address, ',', server.holding.readUInt16BE(address), '}') 36 | }) 37 | 38 | server.on('writeMultipleCoils', function (value) { 39 | console.log('Write multiple coils - Existing: ', value) 40 | }) 41 | 42 | server.on('postWriteMultipleCoils', function (value) { 43 | console.log('Write multiple coils - Complete: ', value) 44 | }) 45 | 46 | /* server.on('writeMultipleRegisters', function (value) { 47 | console.log('Write multiple registers - Existing: ', value) 48 | }) */ 49 | 50 | server.on('postWriteMultipleRegisters', function (value) { 51 | console.log('Write multiple registers - Complete: ', holding.readUInt16BE(0)) 52 | }) 53 | 54 | server.on('connection', function (client) { 55 | 56 | /* work with the modbus tcp client */ 57 | 58 | }) 59 | 60 | server.coils.writeUInt16BE(0x0000, 0) 61 | server.coils.writeUInt16BE(0x0000, 2) 62 | server.coils.writeUInt16BE(0x0000, 4) 63 | server.coils.writeUInt16BE(0x0000, 6) 64 | 65 | server.discrete.writeUInt16BE(0x5678, 0) 66 | 67 | server.holding.writeUInt16BE(0x0000, 0) 68 | server.holding.writeUInt16BE(0x0000, 2) 69 | 70 | server.input.writeUInt16BE(0xff00, 0) 71 | server.input.writeUInt16BE(0xff00, 2) 72 | 73 | console.log(process.argv[2]) 74 | netServer.listen(process.argv[2] || 8502) 75 | -------------------------------------------------------------------------------- /examples/typescript/tcp/WriteMultipleCoils.ts: -------------------------------------------------------------------------------- 1 | import Modbus from '../../../dist/modbus'; 2 | import { Socket, SocketConnectOpts } from 'net' 3 | 4 | const socket = new Socket() 5 | 6 | const options: SocketConnectOpts = { 7 | host: '127.0.0.1', 8 | port: 8502 9 | } 10 | 11 | const client = new Modbus.client.TCP(socket) 12 | 13 | socket.on('connect', function () { 14 | const values = Buffer.from([0xff]) 15 | 16 | client.writeMultipleCoils(13, values, 8) 17 | .then(({ metrics, request, response }) => { 18 | console.log('Transfer Time: ' + metrics.transferTime) 19 | console.log('Response Function Code: ' + response.body.fc) 20 | }) 21 | .catch(handleErrors) 22 | .finally(() => socket.end()) 23 | }) 24 | 25 | socket.on('error', console.error) 26 | socket.connect(options) 27 | 28 | function handleErrors(err: any) { 29 | if (Modbus.errors.isUserRequestError(err)) { 30 | switch (err.err) { 31 | case 'OutOfSync': 32 | case 'Protocol': 33 | case 'Timeout': 34 | case 'ManuallyCleared': 35 | case 'ModbusException': 36 | case 'Offline': 37 | case 'crcMismatch': 38 | console.log('Error Message: ' + err.message, 'Error' + 'Modbus Error Type: ' + err.err) 39 | break; 40 | } 41 | 42 | } else if (Modbus.errors.isInternalException(err)) { 43 | console.log('Error Message: ' + err.message, 'Error' + 'Error Name: ' + err.name, err.stack) 44 | } else { 45 | console.log('Unknown Error', err); 46 | } 47 | } -------------------------------------------------------------------------------- /examples/typescript/tcp/WriteMultipleRegisters.ts: -------------------------------------------------------------------------------- 1 | import Modbus from '../../../dist/modbus'; 2 | import { Socket, SocketConnectOpts } from 'net' 3 | 4 | const socket = new Socket() 5 | 6 | const options: SocketConnectOpts = { 7 | host: '127.0.0.1', 8 | port: 8502 9 | } 10 | 11 | const client = new Modbus.client.TCP(socket) 12 | 13 | socket.on('connect', function () { 14 | 15 | client.writeMultipleRegisters(1, [0x000a, 0x0102]) 16 | .then(({ metrics, request, response }) => { 17 | console.log('Transfer Time: ' + metrics.transferTime) 18 | console.log('Response Function Code: ' + response.body.fc) 19 | }) 20 | .catch(handleErrors) 21 | .finally(() => socket.end()) 22 | 23 | }) 24 | 25 | socket.on('error', console.error) 26 | socket.connect(options) 27 | 28 | function handleErrors(err: any) { 29 | if (Modbus.errors.isUserRequestError(err)) { 30 | switch (err.err) { 31 | case 'OutOfSync': 32 | case 'Protocol': 33 | case 'Timeout': 34 | case 'ManuallyCleared': 35 | case 'ModbusException': 36 | case 'Offline': 37 | case 'crcMismatch': 38 | console.log('Error Message: ' + err.message, 'Error' + 'Modbus Error Type: ' + err.err) 39 | break; 40 | } 41 | 42 | } else if (Modbus.errors.isInternalException(err)) { 43 | console.log('Error Message: ' + err.message, 'Error' + 'Error Name: ' + err.name, err.stack) 44 | } else { 45 | console.log('Unknown Error', err); 46 | } 47 | } -------------------------------------------------------------------------------- /examples/typescript/tcp/WriteSingleCoil.ts: -------------------------------------------------------------------------------- 1 | import Modbus from '../../../dist/modbus'; 2 | import { Socket, SocketConnectOpts } from 'net' 3 | 4 | const socket = new Socket() 5 | 6 | const options: SocketConnectOpts = { 7 | host: '127.0.0.1', 8 | port: 8502 9 | } 10 | 11 | const client = new Modbus.client.TCP(socket) 12 | 13 | socket.on('connect', function () { 14 | 15 | client.writeSingleCoil(17, false) 16 | .then(({ metrics, request, response }) => { 17 | console.log('Transfer Time: ' + metrics.transferTime) 18 | console.log('Response Function Code: ' + response.body.fc) 19 | }) 20 | .catch(handleErrors) 21 | .finally(() => socket.end()) 22 | 23 | }) 24 | 25 | socket.on('error', console.error) 26 | socket.connect(options) 27 | 28 | function handleErrors(err: any) { 29 | if (Modbus.errors.isUserRequestError(err)) { 30 | switch (err.err) { 31 | case 'OutOfSync': 32 | case 'Protocol': 33 | case 'Timeout': 34 | case 'ManuallyCleared': 35 | case 'ModbusException': 36 | case 'Offline': 37 | case 'crcMismatch': 38 | console.log('Error Message: ' + err.message, 'Error' + 'Modbus Error Type: ' + err.err) 39 | break; 40 | } 41 | 42 | } else if (Modbus.errors.isInternalException(err)) { 43 | console.log('Error Message: ' + err.message, 'Error' + 'Error Name: ' + err.name, err.stack) 44 | } else { 45 | console.log('Unknown Error', err); 46 | } 47 | } -------------------------------------------------------------------------------- /examples/typescript/tcp/WriteSingleRegister.ts: -------------------------------------------------------------------------------- 1 | import Modbus from '../../../dist/modbus'; 2 | import { Socket, SocketConnectOpts } from 'net' 3 | 4 | const socket = new Socket() 5 | 6 | const options: SocketConnectOpts = { 7 | host: '127.0.0.1', 8 | port: 8502 9 | } 10 | 11 | const client = new Modbus.client.TCP(socket) 12 | 13 | socket.on('connect', function () { 14 | 15 | client.writeSingleRegister(1002, 333) 16 | .then(({ metrics, request, response }) => { 17 | console.log('Transfer Time: ' + metrics.transferTime) 18 | console.log('Response Function Code: ' + response.body.fc) 19 | }) 20 | .catch(handleErrors) 21 | .finally(() => socket.end()) 22 | 23 | }) 24 | 25 | socket.on('error', console.error) 26 | socket.connect(options) 27 | 28 | function handleErrors(err: any) { 29 | if (Modbus.errors.isUserRequestError(err)) { 30 | switch (err.err) { 31 | case 'OutOfSync': 32 | case 'Protocol': 33 | case 'Timeout': 34 | case 'ManuallyCleared': 35 | case 'ModbusException': 36 | case 'Offline': 37 | case 'crcMismatch': 38 | console.log('Error Message: ' + err.message, 'Error' + 'Modbus Error Type: ' + err.err) 39 | break; 40 | } 41 | 42 | } else if (Modbus.errors.isInternalException(err)) { 43 | console.log('Error Message: ' + err.message, 'Error' + 'Error Name: ' + err.name, err.stack) 44 | } else { 45 | console.log('Unknown Error', err); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/typescript/tcp/lr-allfcs-a.ts: -------------------------------------------------------------------------------- 1 | import Modbus from '../../..' 2 | import { Socket, SocketConnectOpts } from 'net' 3 | 4 | const socket = new Socket() 5 | 6 | const options: SocketConnectOpts = { 7 | host: '127.0.0.1', 8 | port: 8502 9 | } 10 | 11 | const client = new Modbus.client.TCP(socket) 12 | let cycleDone = true 13 | 14 | socket.on('connect', function () { 15 | setInterval(function () { 16 | if (!cycleDone) { 17 | return 18 | } 19 | 20 | cycleDone = false 21 | 22 | const fc01 = client.readCoils(0, 40) 23 | const fc02 = client.readDiscreteInputs(0, 40) 24 | const fc03 = client.readHoldingRegisters(0, 100) 25 | const fc04 = client.readInputRegisters(0, 100) 26 | 27 | const fc05StartAddress = Math.trunc(Math.random() * 1000) 28 | const fc05 = client.writeSingleCoil(fc05StartAddress, true) 29 | 30 | const fc06StartAddress = Math.trunc(Math.random() * 100) 31 | const fc06Value = Math.trunc(Math.random() * 0xFFFF) 32 | const fc06 = client.writeSingleRegister(fc06StartAddress, fc06Value) 33 | 34 | const fc0FStartAddress = Math.trunc(Math.random() * 100) 35 | const fc0FBufferSize = Math.trunc(Math.random() * 100) 36 | const fc0F = client.writeMultipleCoils(fc0FStartAddress, Buffer.alloc(fc0FBufferSize * 2), fc0FBufferSize) 37 | 38 | const fc10StartAddress = Math.trunc(Math.random() * 100) 39 | const fc10BufferSize = Math.trunc(Math.random() * 100) 40 | const fc10 = client.writeMultipleCoils(fc10StartAddress, Buffer.alloc(fc10BufferSize * 2), fc0FBufferSize) 41 | 42 | const allFcs = Promise.all([fc01, fc02, fc03, fc04, fc05, fc06, fc0F, fc10]) 43 | 44 | allFcs 45 | .then(function () { 46 | cycleDone = true 47 | }) 48 | .finally(() => socket.end()) 49 | }, 100) 50 | }) 51 | 52 | socket.on('error', console.error) 53 | 54 | socket.connect(options) 55 | 56 | -------------------------------------------------------------------------------- /examples/typescript/tcp/lr-allfcs-b.ts: -------------------------------------------------------------------------------- 1 | import Modbus from '../../..' 2 | import { Socket, SocketConnectOpts } from 'net' 3 | 4 | const socket = new Socket() 5 | 6 | const options: SocketConnectOpts = { 7 | host: '127.0.0.1', 8 | port: 8502 9 | } 10 | 11 | let cycleDone = true 12 | 13 | setInterval(function () { 14 | if (!cycleDone) { 15 | return 16 | } 17 | 18 | cycleDone = false 19 | 20 | const socket = new Socket() 21 | const client = new Modbus.client.TCP(socket) 22 | 23 | socket.on('end', function () { 24 | cycleDone = true 25 | }) 26 | 27 | socket.on('connect', function () { 28 | const fc01 = client.readCoils(0, 40) 29 | const fc02 = client.readDiscreteInputs(0, 40) 30 | const fc03 = client.readHoldingRegisters(0, 100) 31 | const fc04 = client.readInputRegisters(0, 100) 32 | 33 | const fc05StartAddress = Math.trunc(Math.random() * 1000) 34 | const fc05 = client.writeSingleCoil(fc05StartAddress, true) 35 | 36 | const fc06StartAddress = Math.trunc(Math.random() * 100) 37 | const fc06Value = Math.trunc(Math.random() * 0xFFFF) 38 | const fc06 = client.writeSingleRegister(fc06StartAddress, fc06Value) 39 | 40 | const fc0FStartAddress = Math.trunc(Math.random() * 100) 41 | const fc0FBufferSize = Math.trunc(Math.random() * 100) 42 | const fc0F = client.writeMultipleCoils(fc0FStartAddress, Buffer.alloc(fc0FBufferSize * 2), fc0FBufferSize) 43 | 44 | const fc10StartAddress = Math.trunc(Math.random() * 100) 45 | const fc10BufferSize = Math.trunc(Math.random() * 100) 46 | const fc10 = client.writeMultipleCoils(fc10StartAddress, Buffer.alloc(fc10BufferSize * 2), fc0FBufferSize) 47 | 48 | const allFcs = Promise.all([fc01, fc02, fc03, fc04, fc05, fc06, fc0F, fc10]) 49 | 50 | allFcs 51 | .then(function () { 52 | cycleDone = true 53 | }) 54 | .finally(() => { 55 | socket.end(); 56 | process.exit(); 57 | }) 58 | }) 59 | }, 200) 60 | 61 | socket.on('error', console.error) 62 | socket.connect(options) 63 | -------------------------------------------------------------------------------- /examples/typescript/tcp/lr-fc03.ts: -------------------------------------------------------------------------------- 1 | import Modbus from '../../..' 2 | import { Socket, SocketConnectOpts } from 'net' 3 | 4 | const socket = new Socket() 5 | 6 | const options: SocketConnectOpts = { 7 | host: '127.0.0.1', 8 | port: 8502 9 | } 10 | 11 | const client = new Modbus.client.TCP(socket) 12 | 13 | const readStart = 0; 14 | const readCount = 2; 15 | 16 | socket.on('connect', function () { 17 | setInterval(function () { 18 | client.readHoldingRegisters(readStart, readCount) 19 | .then(({ metrics, request, response }) => { 20 | console.log('Transfer Time: ' + metrics.transferTime) 21 | console.log('Response Body Payload: ' + response.body.valuesAsArray) 22 | console.log('Response Body Payload As Buffer: ' + response.body.valuesAsBuffer) 23 | }) 24 | .catch(handleErrors) 25 | .finally(() => socket.end()) 26 | }, 200) 27 | }) 28 | 29 | socket.on('error', console.error) 30 | socket.connect(options) 31 | 32 | 33 | function handleErrors(err: any) { 34 | if (Modbus.errors.isUserRequestError(err)) { 35 | switch (err.err) { 36 | case 'OutOfSync': 37 | case 'Protocol': 38 | case 'Timeout': 39 | case 'ManuallyCleared': 40 | case 'ModbusException': 41 | case 'Offline': 42 | case 'crcMismatch': 43 | console.log('Error Message: ' + err.message, 'Error' + 'Modbus Error Type: ' + err.err) 44 | break; 45 | } 46 | 47 | } else if (Modbus.errors.isInternalException(err)) { 48 | console.log('Error Message: ' + err.message, 'Error' + 'Error Name: ' + err.name, err.stack) 49 | } else { 50 | console.log('Unknown Error', err); 51 | } 52 | } -------------------------------------------------------------------------------- /examples/typescript/tcp/lr-server.ts: -------------------------------------------------------------------------------- 1 | import Modbus from '../../../dist/modbus' 2 | import { Server } from 'net' 3 | 4 | const netServer = new Server() 5 | 6 | const server = new Modbus.server.TCP(netServer) 7 | 8 | server.on('connection', function () { 9 | 10 | }) 11 | 12 | netServer.listen(8502) 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsmodbus", 3 | "version": "4.0.12", 4 | "description": "Implementation for the Serial/TCP Modbus protocol.", 5 | "author": "Stefan Poeter ", 6 | "main": "./dist/modbus.js", 7 | "types": "./dist/modbus.d.ts", 8 | "bin": { 9 | "jsmodbus": "./bin/jsmodbus.js" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/Cloud-Automation/node-modbus" 14 | }, 15 | "engines": { 16 | "node": ">=6.0.0" 17 | }, 18 | "dependencies": { 19 | "crc": "3.4.0", 20 | "debug": "^3.1.0" 21 | }, 22 | "devDependencies": { 23 | "@types/crc": "^3.4.0", 24 | "@types/debug": "^4.1.4", 25 | "@types/mocha": "^5.2.7", 26 | "@types/node": "^12.0.10", 27 | "@types/serialport": "^7.0.4", 28 | "commander": "^3.0.2", 29 | "mocha": "^3.3.0", 30 | "nyc": "^13.1.0", 31 | "serialport": "^7.1.5", 32 | "sinon": "2.2.0", 33 | "tslint": "^5.20.0", 34 | "tslint-config-standard": "^8.0.1", 35 | "typescript": "^3.5.2" 36 | }, 37 | "scripts": { 38 | "test": "npm run lint && npm run build && npx mocha test/*.test.js", 39 | "build": "npx tsc", 40 | "lint": "npx tslint -c tslint.json 'src/**/*.ts'", 41 | "lint:fix": "npx tslint -c tslint.json 'src/**/*.ts' --fix", 42 | "watch": "npm run lint && npm run build && npx mocha --watch test/*.test.js", 43 | "prepublishOnly": "npm run test", 44 | "prepare": "npm run build", 45 | "cov": "nyc mocha test/*.test.js" 46 | }, 47 | "license": "MIT", 48 | "keywords": [ 49 | "client", 50 | "server", 51 | "serial", 52 | "port", 53 | "modbus", 54 | "tcp" 55 | ], 56 | "readmeFilename": "README.md", 57 | "directories": { 58 | "test": "test", 59 | "example": "examples" 60 | }, 61 | "files": [ 62 | "bin/**/*", 63 | "dist/**/*" 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /src/abstract-request.ts: -------------------------------------------------------------------------------- 1 | import { ModbusRequestBody } from './request' 2 | 3 | /** 4 | * 5 | * 6 | * @export 7 | * @abstract 8 | * @class ModbusAbstractRequest 9 | */ 10 | export default abstract class ModbusAbstractRequest { 11 | 12 | /** The actual modbus function code and parameters */ 13 | public abstract get body (): ReqBody; 14 | 15 | /** 16 | * Unit ID 17 | * 18 | * @readonly 19 | * @abstract 20 | * @type {number} 21 | * @alias slaveId 22 | * @alias address 23 | * @memberof ModbusAbstractRequest 24 | */ 25 | public abstract get unitId (): number; 26 | 27 | /** 28 | * Slave Id 29 | * 30 | * @readonly 31 | * @abstract 32 | * @alias unitId 33 | * @alias address 34 | * @type {number} 35 | * @memberof ModbusAbstractRequest 36 | */ 37 | public abstract get slaveId (): number; 38 | 39 | /** 40 | * RTU Address 41 | * 42 | * @readonly 43 | * @abstract 44 | * @alias unitId 45 | * @alias slaveId 46 | * @type {number} 47 | * @memberof ModbusAbstractRequest 48 | */ 49 | public abstract get address (): number; 50 | 51 | /** 52 | * The calculated byte count of the byte representation 53 | * 54 | * @readonly 55 | * @abstract 56 | * @type {number} 57 | * @memberof ModbusAbstractRequest 58 | */ 59 | public abstract get byteCount (): number; 60 | 61 | public static fromBuffer: ModbusAbstractRequestFromBuffer = (buffer) => { 62 | throw new TypeError('Cannot call from buffer from base abstract class') 63 | } 64 | protected abstract _body: ReqBody 65 | 66 | /** 67 | * Creates a buffer object representing the modbus request 68 | * 69 | * @abstract 70 | * @returns {Buffer} 71 | * @memberof ModbusAbstractRequest 72 | */ 73 | public abstract createPayload (): Buffer 74 | } 75 | 76 | export type ModbusAbstractRequestFromBuffer = 77 | (buffer: Buffer) => ReqBody | null 78 | 79 | export function isModbusRequest (x: any): x is ModbusAbstractRequest { 80 | if (x.body !== undefined) { 81 | return true 82 | } else { 83 | return false 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/abstract-response.ts: -------------------------------------------------------------------------------- 1 | import ModbusAbstractRequest from './abstract-request' 2 | import { ModbusRequestBody } from './request' 3 | import { ModbusResponseBody } from './response' 4 | 5 | /** 6 | * 7 | * 8 | * @export 9 | * @abstract 10 | * @class ModbusAbstractResponse 11 | */ 12 | export default abstract class ModbusAbstractResponse { 13 | 14 | /** 15 | * Unit ID 16 | * 17 | * @readonly 18 | * @abstract 19 | * @type {number} 20 | * @alias slaveId 21 | * @alias address 22 | * @memberof ModbusAbstractResponse 23 | */ 24 | public abstract get unitId (): number; 25 | 26 | /** 27 | * Slave Id 28 | * 29 | * @readonly 30 | * @abstract 31 | * @alias unitId 32 | * @alias address 33 | * @type {number} 34 | * @memberof ModbusAbstractResponse 35 | */ 36 | public abstract get slaveId (): number; 37 | 38 | /** 39 | * RTU Address 40 | * 41 | * @readonly 42 | * @abstract 43 | * @alias unitId 44 | * @alias slaveId 45 | * @type {number} 46 | * @memberof ModbusAbstractResponse 47 | */ 48 | public abstract get address (): number; 49 | 50 | /** Modbus response body */ 51 | public get body () { 52 | return this._body 53 | } 54 | 55 | /** 56 | * Creates Modbus TCP or RTU Response from a Modbus TCP or RTU Request including 57 | * the modbus function body. 58 | * 59 | * @static 60 | * @param {ModbusAbstractResponse} request 61 | * @param {ModbusResponseBody} body 62 | * @returns {ModbusAbstractResponse} 63 | * @memberof ModbusAbstractResponse 64 | */ 65 | public static fromRequest ( 66 | request: ModbusAbstractRequest, 67 | body: ResBody 68 | ): ModbusAbstractResponse { 69 | throw new TypeError('Cannot call fromRequest directly from abstract class') 70 | } 71 | protected abstract _body: ResBody 72 | 73 | public abstract createPayload (): Buffer 74 | 75 | } 76 | 77 | export type ModbusAbstractResponseFromRequest = 78 | (request: ModbusAbstractRequest, body: ModbusResponseBody) => ModbusAbstractResponse 79 | -------------------------------------------------------------------------------- /src/buffer-utils.ts: -------------------------------------------------------------------------------- 1 | 2 | import { BooleanArray, Byte } from './constants' 3 | 4 | import Debug = require('debug'); const debug = Debug('buffer-utils') 5 | 6 | // Buffer utilities to make simplify writing multiple coils 7 | /* 8 | * Outputs to set might be a long buffer starting mid way through a byte. 9 | * For example, outputs [0b11111111, 0b11111111, 0b11111111] starting at coil 5 10 | * Original Coils: [0b00000010, 0b00000000, 0b00000000, 0b11111111] 11 | * Outputs shifted: [0b11110000, 0b11111111, 0b11111111, 0b00001111] 12 | * Resultant Coils: [0b11110010, 0b11111111, 0b11111111, 0b11111111] 13 | * The resultant coils are set to shifted outputs, but special attention needs to 14 | * be paid to the first and last bytes. 15 | * 16 | * This code is broken up into 3 funtions: 17 | * bufferShift 18 | * firstByte 19 | * lastByte 20 | */ 21 | 22 | /** bufferShift shift a buffer of ouputs so they can be used to overwrite existing coils 23 | * @param {start_address} first coil to write 24 | * @param {end_address} last coil to write 25 | * @param {outputs} buffer of outputs to write 26 | * @returns shifted output buffer 27 | */ 28 | 29 | class BufferUtils { 30 | public static bufferShift (startAddress: number, endAddress: number, outputs: Buffer) { 31 | startAddress = startAddress - 1 32 | const startShift = startAddress % 8 33 | const startByte = Math.floor(startAddress / 8) 34 | const endByte = Math.floor(endAddress / 8) 35 | 36 | const size = endByte - startByte + 1 37 | 38 | // Define a new buffer 39 | const buffer = Buffer.allocUnsafe(size) 40 | 41 | buffer[0] = outputs[0] << startShift 42 | debug('buffer[0] = %s ( %s << %d )', buffer[0].toString(2), outputs[0].toString(2), startShift) 43 | 44 | const paddedBuffer = Buffer.concat([outputs, Buffer.alloc(1)], outputs.length + 1) 45 | 46 | for (let i = 1; i < size; i++) { 47 | buffer[i] = (paddedBuffer[i] << startShift) + (paddedBuffer[i - 1] >> (8 - startShift)) 48 | debug('buffer[%d] = %s ( %s << %d + %s >> %d)', 49 | i, 50 | buffer[i].toString(2), 51 | paddedBuffer[i].toString(2), 52 | startShift, 53 | paddedBuffer[i - 1].toString(2), 54 | 8 - startAddress 55 | ) 56 | } 57 | 58 | return buffer 59 | } 60 | 61 | /** firstByte ensure first byte is set correctly 62 | * @param {Byte} startAddress coil to write 63 | * @param {Byte} originalByte from the original coils buffer 64 | * @param {Byte} outputByte byte from the shifted outputs buffer 65 | * @returns correct first byte to be written to coils buffer 66 | */ 67 | public static firstByte (startAddress: Byte, originalByte: Byte, outputByte: Byte): number { 68 | startAddress = startAddress - 1 69 | const startShift = startAddress % 8 70 | const mask = 0xff >> (8 - startShift) 71 | const maskedOriginalByte = originalByte & mask 72 | 73 | return outputByte + maskedOriginalByte 74 | } 75 | 76 | /** lastByte ensure last byte is set correctly 77 | * @param {number} last coil to write 78 | * @param {Byte} byte from the original coils buffer 79 | * @param {Byte} last byte from the shifted outputs buffer 80 | * @returns {number} correct last byte to be written to coils buffer 81 | */ 82 | public static lastByte (endAddress: number, originalByte: Byte, outputByte: Byte): number { 83 | const endShift = endAddress % 8 84 | const mask = 0xff << endShift 85 | const maskedOriginalByte = originalByte & mask 86 | 87 | return outputByte + maskedOriginalByte 88 | } 89 | 90 | public static bufferToArrayStatus (buffer: Buffer): BooleanArray { 91 | const statusArray: BooleanArray = [] 92 | let pos: number 93 | let curByteIdx: number 94 | let curByte: Byte 95 | if (!(buffer instanceof Buffer)) { 96 | return statusArray 97 | } 98 | 99 | for (let i = 0; i < buffer.length * 8; i += 1) { 100 | pos = i % 8 101 | curByteIdx = Math.floor(i / 8) 102 | curByte = buffer.readUInt8(curByteIdx) 103 | const value = ((curByte & Math.pow(2, pos)) > 0) 104 | statusArray.push(value ? 1 : 0) 105 | } 106 | 107 | return statusArray 108 | } 109 | 110 | public static arrayStatusToBuffer (array: BooleanArray) { 111 | const byteCount = array instanceof Array ? Math.ceil(array.length / 8) : 0 112 | const buffer = Buffer.alloc(byteCount) 113 | 114 | if (!(array instanceof Array)) { 115 | return buffer 116 | } 117 | 118 | let byteOffset: number 119 | let bitOffset: number 120 | let byte: Byte 121 | for (let i = 0; i < array.length; i += 1) { 122 | byteOffset = Math.floor(i / 8) 123 | bitOffset = i % 8 124 | byte = buffer.readUInt8(byteOffset) 125 | byte += array[i] ? Math.pow(2, bitOffset) : 0 126 | buffer.writeUInt8(byte, byteOffset) 127 | } 128 | 129 | return buffer 130 | } 131 | } 132 | 133 | export = BufferUtils 134 | -------------------------------------------------------------------------------- /src/client-response-handler.ts: -------------------------------------------------------------------------------- 1 | import MBAbstractResponse from './abstract-response' 2 | 3 | /** Modbus Client Repsonse Handler 4 | * @abstract 5 | */ 6 | export default abstract class ModbusClientResponseHandler { 7 | protected _buffer: Buffer 8 | protected abstract _messages: ResType[] 9 | 10 | /** Create new Modbus Client Response Hanlder */ 11 | constructor () { 12 | this._buffer = Buffer.alloc(0) 13 | } 14 | 15 | /** Process new incoming data and enqueue new modbus responses. 16 | * @param {Buffer} data New incoming data from the socket. 17 | */ 18 | public abstract handleData (data: Buffer): void 19 | 20 | /** Extract latest Modbus Response. 21 | * @returns {ModbusResponse} 22 | */ 23 | public shift () { 24 | return this._messages.shift() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/codes/errors.ts: -------------------------------------------------------------------------------- 1 | export type ErrorCode = 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 11 2 | 3 | export const ErrorMessages = { 4 | 0x01: 'ILLEGAL FUNCTION', 5 | 0x02: 'ILLEGAL DATA ADDRESS', 6 | 0x03: 'ILLEGAL DATA VALUE', 7 | 0x04: 'SLAVE DEVICE FAILURE', 8 | 0x05: 'ACKNOWLEDGE', 9 | 0x06: 'SLAVE DEVICE BUSY', 10 | 0x08: 'MEMORY PARITY ERROR', 11 | 0x0A: 'GATEWAY PATH UNAVAILABLE', 12 | 0x0B: 'GATEWAY TARGET DEVICE FAILED TO RESPOND' 13 | } as const 14 | 15 | type IErrorMessage = typeof ErrorMessages 16 | 17 | type ErrorMessage = IErrorMessage[ErrorCode] 18 | 19 | export function errorCodeToMessage (x: number): ErrorMessage 20 | export function errorCodeToMessage (x: ErrorCode): ErrorMessage 21 | export function errorCodeToMessage (x: any) { 22 | if (isErrorCode(x)) { 23 | return ErrorMessages[x] 24 | } else { 25 | throw new Error('') 26 | } 27 | } 28 | 29 | export function isErrorCode (x: any): x is ErrorCode { 30 | switch (x) { 31 | case 0x01: 32 | case 0x02: 33 | case 0x03: 34 | case 0x04: 35 | case 0x05: 36 | case 0x06: 37 | case 0x08: 38 | case 0x0A: 39 | case 0x0B: 40 | return true 41 | default: 42 | return false 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/codes/function-codes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Function Code Enumerable 3 | * 4 | * @export 5 | * @enum {number} 6 | */ 7 | export enum FC { 8 | READ_COIL = 1, 9 | READ_DISCRETE_INPUT = 2, 10 | READ_HOLDING_REGISTERS = 3, 11 | READ_INPUT_REGISTERS = 4, 12 | WRITE_SINGLE_COIL = 5, 13 | WRITE_SINGLE_HOLDING_REGISTER = 6, 14 | WRITE_MULTIPLE_COILS = 15, 15 | WRITE_MULTIPLE_HOLDING_REGISTERS = 16 16 | } 17 | 18 | export function isFunctionCode (x: number): x is FunctionCode { 19 | if (FC[x] === undefined) { 20 | return false 21 | } else { 22 | return true 23 | } 24 | } 25 | 26 | export type FunctionCode = 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 27 | -------------------------------------------------------------------------------- /src/codes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './errors' 2 | export * from './function-codes' 3 | -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export * from './limits' 2 | export * from './primatives' 3 | -------------------------------------------------------------------------------- /src/constants/limits.ts: -------------------------------------------------------------------------------- 1 | const UINT16_MIN = 0x0000 2 | const UINT16_MAX = 0xFFFF 3 | const REGISTER_MAX = UINT16_MAX 4 | const REGISTER_MIN = UINT16_MIN 5 | 6 | const COIL_MIN = 0x00 7 | const COIL_MAX = 0x01 8 | 9 | const ERROR_CODE_THRESHOLD = 0x80 10 | 11 | export const LIMITS = { 12 | COIL_MAX, 13 | COIL_MIN, 14 | ERROR_CODE_THRESHOLD, 15 | REGISTER_MAX, 16 | REGISTER_MIN, 17 | UINT16_MAX, 18 | UINT16_MIN 19 | } as const 20 | -------------------------------------------------------------------------------- /src/constants/primatives.ts: -------------------------------------------------------------------------------- 1 | export type Byte = number 2 | export type BooleanArray = Array 3 | -------------------------------------------------------------------------------- /src/errors/index.ts: -------------------------------------------------------------------------------- 1 | export * from './isInternalException' 2 | export * from './isUserRequestError' 3 | export { isExceptionResponseBody } from '../response/exception' 4 | export { isExceptionRequestBody } from '../request/exception' 5 | -------------------------------------------------------------------------------- /src/errors/isInternalException.ts: -------------------------------------------------------------------------------- 1 | export type InternalErrorMessages = 2 | | 'InvalidStartAddress' 3 | | 'InvalidQuantity' 4 | | 'InvalidArraySize' 5 | | 'InvalidBufferSize' 6 | | 'InvalidCoilsInput' 7 | | 'InvalidType_MustBeBufferOrArray' 8 | | 'InvalidValue' 9 | 10 | export interface IInternalException extends Error { 11 | readonly message: InternalErrorMessages 12 | } 13 | 14 | const InternalErrorMessagesArray: ReadonlyArray = [ 15 | 'InvalidStartAddress', 16 | 'InvalidQuantity', 17 | 'InvalidArraySize', 18 | 'InvalidBufferSize', 19 | 'InvalidCoilsInput', 20 | 'InvalidType_MustBeBufferOrArray', 21 | 'InvalidValue' 22 | ] 23 | 24 | export function isInternalException (x: any): x is IInternalException { 25 | if (typeof x !== 'object') { 26 | return false 27 | } 28 | 29 | if (InternalErrorMessagesArray.includes(x.message)) { 30 | return true 31 | } 32 | 33 | return false 34 | } 35 | -------------------------------------------------------------------------------- /src/errors/isUserRequestError.ts: -------------------------------------------------------------------------------- 1 | export * from '../user-request-error' 2 | -------------------------------------------------------------------------------- /src/modbus-rtu-client.ts: -------------------------------------------------------------------------------- 1 | 2 | import MBClient from './modbus-client.js' 3 | import ModbusRTUClientRequestHandler from './rtu-client-request-handler.js' 4 | import ModbusRTUClientResponseHandler from './rtu-client-response-handler.js' 5 | 6 | import * as SerialPort from 'serialport' 7 | import ModbusRTURequest from './rtu-request.js' 8 | import ModbusRTUResponse from './rtu-response.js' 9 | 10 | /** This Client musst be initiated with a socket object that implements the event emitter 11 | * interface and fires a 'data' event with a buffer as a parameter. It also needs to 12 | * implement the 'write' method to send data to the socket. 13 | * 14 | * @example Create new Modbus/RTU Client 15 | * const Modbus = require('jsmodbus') 16 | * const SerialPort = require('serialport') 17 | * const socket = new SerialPort("/dev/tty/ttyUSB0", { "baudRate: 57600" }) 18 | * const client = new Modbus.client.RTU(socket, address) 19 | * 20 | * @extends MBClient 21 | * @class 22 | */ 23 | export default class ModbusRTUClient extends MBClient { 24 | protected _requestHandler: ModbusRTUClientRequestHandler 25 | protected _responseHandler: ModbusRTUClientResponseHandler 26 | 27 | /** Creates a new Modbus/RTU Client. 28 | * @param {SerialPort} socket The serial Socket. 29 | * @param {number} address The address of the serial client. 30 | * @param {number} [timeout=5000] 31 | */ 32 | constructor (socket: SerialPort, address: number, timeout = 5000) { 33 | super(socket) 34 | 35 | this._requestHandler = new ModbusRTUClientRequestHandler(socket, address, timeout) 36 | this._responseHandler = new ModbusRTUClientResponseHandler() 37 | } 38 | 39 | public get slaveId () { 40 | return this._requestHandler.address 41 | } 42 | 43 | public get unitId () { 44 | return this._requestHandler.address 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/modbus-rtu-server.ts: -------------------------------------------------------------------------------- 1 | 2 | import ModbusServerClient from './modbus-server-client.js' 3 | import ModbusServer, { IModbusServerOptions } from './modbus-server.js' 4 | import ModbusRTURequest from './rtu-request.js' 5 | import ModbusRTUResponse from './rtu-response.js' 6 | 7 | import * as SerialPort from 'serialport' 8 | 9 | export default class ModbusRTUServer extends ModbusServer { 10 | public _socket: any 11 | public emit: any 12 | 13 | constructor (socket: SerialPort, options?: Partial) { 14 | super(options) 15 | this._socket = socket 16 | 17 | const fromBuffer = ModbusRTURequest.fromBuffer 18 | const fromRequest = ModbusRTUResponse.fromRequest as any 19 | const client = new ModbusServerClient(this, socket, fromBuffer, fromRequest) 20 | this.emit('connection', client) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/modbus-server-client.ts: -------------------------------------------------------------------------------- 1 | 2 | import Debug = require('debug'); const debug = Debug('modbus tcp client socket') 3 | import * as Stream from 'stream' 4 | import { ModbusAbstractRequestFromBuffer } from './abstract-request.js' 5 | import { ModbusAbstractResponseFromRequest } from './abstract-response.js' 6 | import ModbusServerRequestHandler from './modbus-server-request-handler.js' 7 | import ModbusServerResponseHandler from './modbus-server-response-handler.js' 8 | import ModbusServer from './modbus-server.js' 9 | 10 | export default class ModbusServerClient< 11 | S extends Stream.Duplex, 12 | ReqFromBufferMethod extends ModbusAbstractRequestFromBuffer, 13 | ResFromRequestMethod extends ModbusAbstractResponseFromRequest> { 14 | public _server: ModbusServer 15 | public _socket: S 16 | public _requestHandler: ModbusServerRequestHandler 17 | public _responseHandler: ModbusServerResponseHandler 18 | 19 | constructor ( 20 | server: ModbusServer, 21 | socket: S, 22 | fromBufferMethod: ReqFromBufferMethod, 23 | fromRequestMethod: ResFromRequestMethod 24 | ) { 25 | this._server = server 26 | this._socket = socket 27 | 28 | this._requestHandler = new ModbusServerRequestHandler(fromBufferMethod) 29 | this._responseHandler = new ModbusServerResponseHandler(this._server, fromRequestMethod) 30 | 31 | this._socket.on('data', this._onData.bind(this)) 32 | } 33 | 34 | get socket () { 35 | return this._socket 36 | } 37 | 38 | get server () { 39 | return this._server 40 | } 41 | 42 | public _onData (data: Buffer) { 43 | debug('new data coming in') 44 | this._requestHandler.handle(data) 45 | 46 | do { 47 | const request = this._requestHandler.shift() 48 | 49 | if (!request) { 50 | debug('no request to process') 51 | /* TODO: close client connection */ 52 | break 53 | } 54 | 55 | this._responseHandler.handle(request, (response) => { 56 | this._socket.write(response, () => { 57 | debug('response flushed', response) 58 | }) 59 | }) 60 | } while (1) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/modbus-server-request-handler.ts: -------------------------------------------------------------------------------- 1 | import ModbusAbstractRequest, { ModbusAbstractRequestFromBuffer } from './abstract-request' 2 | import ModbusRTURequest from './rtu-request' 3 | 4 | import Debug = require('debug'); const debug = Debug('modbus-server-request-handler') 5 | 6 | export default class ModbusServerRequestHandler> { 7 | public _fromBuffer: FB 8 | public _requests: ModbusAbstractRequest[] 9 | public _buffer: Buffer 10 | 11 | constructor (fromBufferMethod: FB) { 12 | this._fromBuffer = fromBufferMethod 13 | this._requests = [] 14 | this._buffer = Buffer.alloc(0) 15 | } 16 | 17 | public shift () { 18 | return this._requests.shift() 19 | } 20 | 21 | public handle (data: Buffer) { 22 | this._buffer = Buffer.concat([this._buffer, data]) 23 | debug('this._buffer', this._buffer) 24 | 25 | do { 26 | const request = this._fromBuffer(this._buffer) 27 | debug('request', request) 28 | 29 | if (!request) { 30 | return 31 | } 32 | 33 | if (request instanceof ModbusRTURequest && request.corrupted) { 34 | const corruptDataDump = this._buffer.slice(0, request.byteCount).toString('hex') 35 | debug(`request message was corrupt: ${corruptDataDump}`) 36 | } else { 37 | this._requests.unshift(request) 38 | } 39 | 40 | this._buffer = this._buffer.slice(request.byteCount) 41 | } while (1) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/modbus-tcp-client.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Socket } from 'net' 3 | import MBClient from './modbus-client.js' 4 | import MBTCPClientRequestHandler from './tcp-client-request-handler.js' 5 | import ModbusTCPClientResponseHandler from './tcp-client-response-handler.js' 6 | import ModbusTCPRequest from './tcp-request.js' 7 | import ModbusTCPResponse from './tcp-response.js' 8 | 9 | /** This client must be initiated with a net.Socket object. The module does not handle reconnections 10 | * or anything related to keep the connection up in case of an unplugged cable or a closed server. See 11 | * the node-net-reconnect module for these issues. 12 | * @extends MBClient 13 | * @class 14 | * @example Create new Modbus/TCP Client 15 | * const net = require('net') 16 | * const socket = new net.Socket() 17 | * const client = new Modbus.tcp.Client(socket) 18 | * 19 | * socket.connect({'host' : hostname, 'port' : 502 }) 20 | * 21 | * socket.on('connect', function () { 22 | * 23 | * client.readCoils(...) 24 | * 25 | * }) 26 | * 27 | */ 28 | export default class ModbusTCPClient extends MBClient { 29 | protected _requestHandler: MBTCPClientRequestHandler 30 | protected _responseHandler: ModbusTCPClientResponseHandler 31 | protected readonly _unitId: number 32 | protected readonly _timeout: number 33 | 34 | /** 35 | * Creates a new Modbus/TCP Client. 36 | * @param {Socket} socket The TCP Socket. 37 | * @param {number} [unitId=1] Unit ID 38 | * @param {number} [timeout=5000] Timeout for requests in ms. 39 | * @memberof ModbusTCPClient 40 | */ 41 | constructor (socket: Socket, unitId: number = 1, timeout: number = 5000) { 42 | super(socket) 43 | 44 | this._requestHandler = new MBTCPClientRequestHandler(socket, unitId, timeout) 45 | this._responseHandler = new ModbusTCPClientResponseHandler() 46 | 47 | this._unitId = unitId 48 | this._timeout = timeout 49 | } 50 | 51 | get slaveId () { 52 | return this._unitId 53 | } 54 | 55 | get unitId () { 56 | return this._unitId 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/modbus-tcp-server.ts: -------------------------------------------------------------------------------- 1 | import Debug = require('debug'); const debug = Debug('modbus tcp server') 2 | import { Server, Socket } from 'net' 3 | import ModbusServer, { IModbusServerOptions } from './modbus-server' 4 | import ModbusServerClient from './modbus-server-client.js' 5 | import ModbusTCPRequest from './tcp-request.js' 6 | import ModbusTCPResponse from './tcp-response.js' 7 | 8 | export default class ModbusTCPServer extends ModbusServer { 9 | public _server: Server | ModbusServer 10 | 11 | constructor (server: Server | ModbusServer, options?: Partial) { 12 | super(options) 13 | this._server = server 14 | 15 | server.on('connection', this._onConnection.bind(this)) 16 | } 17 | 18 | public _onConnection (socket: Socket) { 19 | debug('new connection coming in') 20 | 21 | const Request = ModbusTCPRequest.fromBuffer 22 | const Response = ModbusTCPResponse.fromRequest as any 23 | 24 | const client = new ModbusServerClient(this, socket, Request, Response) 25 | 26 | this.emit('connection', client) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/modbus.ts: -------------------------------------------------------------------------------- 1 | 2 | /** jsModbus is a node.js module that enables the developer to interact with modbus/tcp and modbus/rtu server (slaves) 3 | * or to create a modbus/tcp server (master). 4 | * @module jsmodbus 5 | * 6 | */ 7 | 8 | /** module:jsmodbus.client.TCP 9 | * @example Create new Modbus/TCP Client. 10 | * const Modbus = require('jsmodbus') 11 | * const net = require('net') 12 | * const socket = new new.Socket() 13 | * const client = new Modbus.client.TCP(socket, unitId) 14 | * const options = { 15 | * 'host' : host 16 | * 'port' : port 17 | * } 18 | * 19 | * socket.connect(options) 20 | */ 21 | import ModbusTCPClient from './modbus-tcp-client.js' 22 | 23 | /** module:jsmodbus.client.RTU 24 | * @example Create new Modbus/RTU Client. 25 | * const Modbus = require('jsmodbus') 26 | * const SerialPort = require('serialport') 27 | * const socket = new SerialPort('/dev/tty/ttyUSB0', { baudRate: 57600 }) 28 | * const client = new Modbus.client.TCP(socket, address) 29 | */ 30 | 31 | import ModbusRTUClient from './modbus-rtu-client.js' 32 | 33 | /** module:jsmodbus.server.TCP */ 34 | import ModbusTCPServer from './modbus-tcp-server.js' 35 | 36 | /** module:jsmodbus.server.RTU */ 37 | import ModbusRTUServer from './modbus-rtu-server.js' 38 | 39 | import * as Codes from './codes' 40 | import * as Errors from './errors' 41 | import * as Requests from './request' 42 | import * as Responses from './response' 43 | import UserRequest from './user-request.js' 44 | 45 | import { LIMITS } from './constants' 46 | 47 | export const client = { 48 | RTU: ModbusRTUClient, 49 | TCP: ModbusTCPClient 50 | } 51 | 52 | export const server = { 53 | RTU: ModbusRTUServer, 54 | TCP: ModbusTCPServer 55 | } 56 | 57 | export const requests = { 58 | ...Requests, 59 | UserRequest 60 | } 61 | 62 | export const responses = Responses 63 | export const codes = Codes 64 | export const errors = Errors 65 | export const limits = LIMITS 66 | 67 | export { default as ModbusAbstractRequest } from './abstract-request' 68 | export { default as ModbusAbstractResponse } from './abstract-response' 69 | export { default as MBClientRequestHandler } from './client-request-handler' 70 | export { default as ModbusClientResponseHandler } from './client-response-handler' 71 | export { default as ModbusClient } from './modbus-client' 72 | export * from './request-response-map' 73 | export { default as ModbusTCPRequest } from './tcp-request' 74 | export { default as ModbusTCPResponse } from './tcp-response' 75 | export { default as ModbusRTURequest } from './rtu-request' 76 | export { default as ModbusRTUResponse } from './rtu-response' 77 | export { UserRequestError } from './user-request-error' 78 | export { 79 | default as UserRequest, 80 | ModbusRequest, 81 | IUserRequestResolve as UserRequestResolve, 82 | PromiseUserRequest 83 | } from './user-request' 84 | export { 85 | UserRequestMetrics 86 | } from './user-request-metrics' 87 | 88 | export { 89 | ModbusTCPClient, 90 | ModbusRTUClient, 91 | ModbusTCPServer, 92 | ModbusRTUServer 93 | } 94 | -------------------------------------------------------------------------------- /src/request-response-map.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-shadowed-variable 2 | import ModbusAbstractRequest from './abstract-request' 3 | import ModbusAbstractResponse from './abstract-response' 4 | import { 5 | ModbusRequestBody, 6 | ReadCoilsRequestBody, 7 | ReadDiscreteInputsRequestBody, 8 | ReadHoldingRegistersRequestBody, 9 | ReadInputRegistersRequestBody, 10 | WriteMultipleCoilsRequestBody, 11 | WriteMultipleRegistersRequestBody, 12 | WriteSingleCoilRequestBody, 13 | WriteSingleRegisterRequestBody 14 | } from './request' 15 | import { 16 | ModbusResponseBody, 17 | ReadCoilsResponseBody, 18 | ReadDiscreteInputsResponseBody, 19 | ReadHoldingRegistersResponseBody, 20 | ReadInputRegistersResponseBody, 21 | WriteMultipleCoilsResponseBody, 22 | WriteMultipleRegistersResponseBody, 23 | WriteSingleCoilResponseBody, 24 | WriteSingleRegisterResponseBody 25 | } from './response' 26 | import ModbusRTURequest from './rtu-request' 27 | import ModbusRTUResponse from './rtu-response' 28 | import ModbusTCPRequest from './tcp-request' 29 | import ModbusTCPResponse from './tcp-response' 30 | 31 | export type BodyRequestToResponse = 32 | T extends ReadCoilsRequestBody ? ReadCoilsResponseBody : 33 | T extends ReadDiscreteInputsRequestBody ? ReadDiscreteInputsResponseBody : 34 | T extends ReadHoldingRegistersRequestBody ? ReadHoldingRegistersResponseBody : 35 | T extends ReadInputRegistersRequestBody ? ReadInputRegistersResponseBody : 36 | T extends WriteMultipleCoilsRequestBody ? WriteMultipleCoilsResponseBody : 37 | T extends WriteMultipleRegistersRequestBody ? WriteMultipleRegistersResponseBody : 38 | T extends WriteSingleCoilRequestBody ? WriteSingleCoilResponseBody : 39 | T extends WriteSingleRegisterRequestBody ? WriteSingleRegisterResponseBody : 40 | T extends ModbusRequestBody ? ModbusResponseBody : unknown 41 | 42 | export type BodyResponseToRequest = 43 | T extends ReadCoilsResponseBody ? ReadCoilsRequestBody : 44 | T extends ReadDiscreteInputsResponseBody ? ReadDiscreteInputsRequestBody : 45 | T extends ReadHoldingRegistersResponseBody ? ReadHoldingRegistersRequestBody : 46 | T extends ReadInputRegistersResponseBody ? ReadInputRegistersRequestBody : 47 | T extends WriteMultipleCoilsResponseBody ? WriteMultipleCoilsRequestBody : 48 | T extends WriteMultipleRegistersResponseBody ? WriteMultipleRegistersRequestBody : 49 | T extends WriteSingleCoilResponseBody ? WriteSingleCoilRequestBody : 50 | T extends WriteSingleRegisterResponseBody ? WriteSingleRegisterRequestBody : 51 | T extends ModbusResponseBody ? ModbusRequestBody : unknown 52 | 53 | export type RequestToResponse = 54 | T extends ModbusTCPRequest ? ModbusTCPResponse> : 55 | T extends ModbusRTURequest ? ModbusRTUResponse> : 56 | T extends ModbusAbstractRequest ? ModbusAbstractResponse> : 57 | unknown 58 | 59 | export type GetBody = 60 | T extends ModbusAbstractRequest ? B : 61 | T extends ModbusAbstractResponse ? B : 62 | unknown 63 | 64 | export type CastRequestBody = 65 | T extends ModbusTCPRequest ? ModbusTCPRequest : 66 | T extends ModbusRTURequest ? ModbusRTURequest : 67 | T extends ModbusAbstractRequest ? ModbusAbstractRequest : 68 | unknown 69 | -------------------------------------------------------------------------------- /src/request/exception.ts: -------------------------------------------------------------------------------- 1 | import { ErrorCode, FunctionCode, isFunctionCode } from '../codes' 2 | 3 | import ModbusRequestBody from './request-body.js' 4 | 5 | /** Write Single Coil Request Body 6 | * @extends ModbusRequestBody 7 | */ 8 | export default class ExceptionRequestBody extends ModbusRequestBody { 9 | 10 | /** Address to be written */ 11 | get code () { 12 | return this._code 13 | } 14 | 15 | get name () { 16 | return 'ExceptionRequest' as const 17 | } 18 | 19 | get count () { 20 | return 0 21 | } 22 | 23 | /** Returns the byte count of this request for the byte representation. 24 | * @returns {Number} 25 | */ 26 | get byteCount () { 27 | return 2 28 | } 29 | 30 | get isException () { 31 | return true 32 | } 33 | 34 | public static fromBuffer (buffer: Buffer) { 35 | try { 36 | const fc = buffer.readUInt8(0) 37 | 38 | if (fc > 0x2B) { 39 | return null 40 | } 41 | 42 | return new ExceptionRequestBody(fc, 0x01) 43 | } catch (e) { 44 | return null 45 | } 46 | } 47 | protected _code: ErrorCode 48 | 49 | /** Create a new Exception Request Body. 50 | * @param {FunctionCode} related function code. 51 | * @param {ErrorCode} exception code. 52 | * @throws {InvalidFunctionCodeError} - when the function code is invalid 53 | */ 54 | constructor (fc: FunctionCode, code: ErrorCode) 55 | constructor (fc: number, code: ErrorCode) 56 | constructor (fc: number, code: ErrorCode) { 57 | if (!isFunctionCode(fc)) { 58 | throw Error('InvalidFunctionCode') 59 | } 60 | super(fc) 61 | this._code = code 62 | } 63 | 64 | public createPayload () { 65 | const payload = Buffer.alloc(2) 66 | 67 | payload.writeUInt8(this._fc, 0) // function code 68 | payload.writeUInt8(this._code, 1) // code address 69 | 70 | return payload 71 | } 72 | } 73 | 74 | export function isExceptionRequestBody (x: any): x is ExceptionRequestBody { 75 | if (x instanceof ExceptionRequestBody) { 76 | return true 77 | } else { 78 | return false 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/request/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ExceptionRequestBody, isExceptionRequestBody } from './exception' 2 | export { default as ReadCoilsRequestBody, isReadCoilsRequestBody } from './read-coils' 3 | export { default as ReadDiscreteInputsRequestBody, isReadDiscreteInputsRequestBody } from './read-discrete-inputs' 4 | export { default as ReadHoldingRegistersRequestBody, isReadHoldingRegistersRequestBody } from './read-holding-registers' 5 | export { default as ReadInputRegistersRequestBody, isReadInputRegistersRequestBody } from './read-input-registers' 6 | export { default as ModbusRequestBody, isModbusRequestBody, ModbusRequestTypeName } from './request-body' 7 | export { default as RequestFactory } from './request-factory' 8 | export { default as WriteMultipleCoilsRequestBody, isWriteMultipleCoilsRequestBody } from './write-multiple-coils' 9 | export { default as WriteMultipleRegistersRequestBody, isWriteMultipleRegistersRequestBody } from './write-multiple-registers' 10 | export { default as WriteSingleCoilRequestBody, isWriteSingleCoilRequestBody } from './write-single-coil' 11 | export { default as WriteSingleRegisterRequestBody, isWriteSingleRegisterRequestBody } from './write-single-register' 12 | -------------------------------------------------------------------------------- /src/request/read-coils.ts: -------------------------------------------------------------------------------- 1 | import { FC } from '../codes/index.js' 2 | import ModbusRequestBody from './request-body.js' 3 | 4 | /** Read Coils Request Body 5 | * @extends ModbusRequestBody 6 | */ 7 | export default class ReadCoilsRequestBody extends ModbusRequestBody { 8 | 9 | /** Start Address. */ 10 | get start () { 11 | return this._start 12 | } 13 | 14 | /** Coil Quantity. */ 15 | get count () { 16 | return this._count 17 | } 18 | 19 | get name () { 20 | return 'ReadCoils' as const 21 | } 22 | 23 | /** Returns the byte count of this request for the byte representation. 24 | * @returns {Number} 25 | */ 26 | get byteCount () { 27 | return 5 28 | } 29 | 30 | public static fromBuffer (buffer: Buffer) { 31 | try { 32 | const fc = buffer.readUInt8(0) 33 | 34 | if (fc !== FC.READ_COIL) { 35 | return null 36 | } 37 | 38 | const start = buffer.readUInt16BE(1) 39 | const quantity = buffer.readUInt16BE(3) 40 | 41 | return new ReadCoilsRequestBody(start, quantity) 42 | } catch (e) { 43 | return null 44 | } 45 | } 46 | private _start: number 47 | private _count: number 48 | 49 | /** Create a new Read Coils Request Body. 50 | * @param {number} start Start Address. 51 | * @param {number} count Quantity of coils to be read. 52 | * @throws {InvalidStartAddressException} When Start address is larger than 0xFFFF. 53 | * @throws {InvalidQuantityException} When count is larger than 0x7D0. 54 | */ 55 | constructor (start: number, count: number) { 56 | super(FC.READ_COIL) 57 | this._start = start 58 | this._count = count 59 | 60 | if (this._start > 0xFFFF) { 61 | throw new Error('InvalidStartAddress') 62 | } 63 | 64 | if (this._count > 0x7D0) { 65 | throw new Error('InvalidQuantity') 66 | } 67 | } 68 | 69 | public createPayload () { 70 | const payload = Buffer.alloc(5) 71 | 72 | payload.writeUInt8(this._fc, 0) // function code 73 | payload.writeUInt16BE(this._start, 1) // start address 74 | payload.writeUInt16BE(this._count, 3) // Quantitiy of coils 75 | 76 | return payload 77 | } 78 | } 79 | 80 | export function isReadCoilsRequestBody (x: any): x is ReadCoilsRequestBody { 81 | if (x instanceof ReadCoilsRequestBody) { 82 | return true 83 | } else { 84 | return false 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/request/read-discrete-inputs.ts: -------------------------------------------------------------------------------- 1 | import { FC } from '../codes' 2 | import ModbusRequestBody from './request-body.js' 3 | 4 | /** Read Discrete Inputs Request Body 5 | * @extends ModbusRequestBody 6 | */ 7 | export default class ReadDiscreteInputsRequestBody extends ModbusRequestBody { 8 | 9 | /** Start Address. */ 10 | get start () { 11 | return this._start 12 | } 13 | 14 | /** Coil Quantity. */ 15 | get count () { 16 | return this._count 17 | } 18 | 19 | get name () { 20 | return 'ReadDiscreteInput' as const 21 | } 22 | 23 | get byteCount () { 24 | return 5 25 | } 26 | 27 | public static fromBuffer (buffer: Buffer) { 28 | try { 29 | const fc = buffer.readUInt8(0) 30 | 31 | if (fc !== FC.READ_DISCRETE_INPUT) { 32 | return null 33 | } 34 | 35 | const start = buffer.readUInt16BE(1) 36 | const quantity = buffer.readUInt16BE(3) 37 | 38 | return new ReadDiscreteInputsRequestBody(start, quantity) 39 | } catch (e) { 40 | return null 41 | } 42 | } 43 | private _start: number 44 | private _count: number 45 | 46 | /** Create a new Read Discrete Inputs Request Body. 47 | * @param {number} start Start Address. 48 | * @param {number} count Quantity of coils to be read. 49 | * @throws {InvalidStartAddressException} When Start address is larger than 0xFFFF. 50 | * @throws {InvalidQuantityException} When count is larger than 0x7D0. 51 | */ 52 | constructor (start: number, count: number) { 53 | super(FC.READ_DISCRETE_INPUT) 54 | 55 | if (start > 0xFFFF) { 56 | throw new Error('InvalidStartAddress') 57 | } 58 | 59 | if (count > 0x7D0) { 60 | throw new Error('InvalidQuantity') 61 | } 62 | 63 | this._start = start 64 | this._count = count 65 | } 66 | 67 | public createPayload () { 68 | const payload = Buffer.alloc(5) 69 | 70 | payload.writeUInt8(this._fc, 0) // function code 71 | payload.writeUInt16BE(this._start, 1) // start address 72 | payload.writeUInt16BE(this._count, 3) // quantitiy of coils 73 | 74 | return payload 75 | } 76 | } 77 | 78 | export function isReadDiscreteInputsRequestBody (x: any): x is ReadDiscreteInputsRequestBody { 79 | if (x instanceof ReadDiscreteInputsRequestBody) { 80 | return true 81 | } else { 82 | return false 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/request/read-holding-registers.ts: -------------------------------------------------------------------------------- 1 | import { FC } from '../codes' 2 | import ModbusRequestBody from './request-body.js' 3 | 4 | /** Read Holding Registers Request Body 5 | * @extends ModbusRequestBody 6 | */ 7 | export default class ReadHoldingRegistersRequestBody extends ModbusRequestBody { 8 | 9 | /** Start Address. */ 10 | get start () { 11 | return this._start 12 | } 13 | 14 | /** Quantity of registers. */ 15 | get count () { 16 | return this._count 17 | } 18 | 19 | get byteCount () { 20 | return 5 21 | } 22 | 23 | get name () { 24 | return 'ReadHoldingRegisters' as const 25 | } 26 | 27 | public static fromBuffer (buffer: Buffer) { 28 | try { 29 | const fc = buffer.readUInt8(0) 30 | const start = buffer.readUInt16BE(1) 31 | const count = buffer.readUInt16BE(3) 32 | 33 | if (fc !== FC.READ_HOLDING_REGISTERS) { 34 | return null 35 | } 36 | 37 | return new ReadHoldingRegistersRequestBody(start, count) 38 | } catch (e) { 39 | return null 40 | } 41 | } 42 | private _start: number 43 | private _count: number 44 | 45 | /** Create a new Read Holding Registers Request Body. 46 | * @param {Number} start Start Address. 47 | * @param {Numer} count Quantity of registers to be read. 48 | * @throws {InvalidStartAddressException} When start address is larger than 0xFFFF. 49 | * @throws {InvalidQuantityException} When count is larger than 0x7D0. 50 | */ 51 | constructor (start: number, count: number) { 52 | super(FC.READ_HOLDING_REGISTERS) 53 | if (start > 0xFFFF) { 54 | throw new Error('InvalidStartAddress') 55 | } 56 | if (count > 0x7D0) { 57 | throw new Error('InvalidQuantity') 58 | } 59 | this._start = start 60 | this._count = count 61 | } 62 | 63 | public createPayload () { 64 | const payload = Buffer.alloc(5) 65 | payload.writeUInt8(this._fc, 0) // function code 66 | payload.writeUInt16BE(this._start, 1) // start address 67 | payload.writeUInt16BE(this._count, 3) // quantitiy of coils 68 | return payload 69 | } 70 | } 71 | 72 | export function isReadHoldingRegistersRequestBody (x: any): x is ReadHoldingRegistersRequestBody { 73 | if (x instanceof ReadHoldingRegistersRequestBody) { 74 | return true 75 | } else { 76 | return false 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/request/read-input-registers.ts: -------------------------------------------------------------------------------- 1 | import { FC } from '../codes' 2 | 3 | import ModbusRequestBody from './request-body.js' 4 | 5 | /** Read Input Registers Request Body 6 | * @extends ModbusRequestBody 7 | */ 8 | export default class ReadInputRegistersRequestBody extends ModbusRequestBody { 9 | 10 | /** Start Address. */ 11 | get start () { 12 | return this._start 13 | } 14 | 15 | /** Quantity of registers */ 16 | get count () { 17 | return this._count 18 | } 19 | 20 | get name () { 21 | return 'ReadInputRegisters' as const 22 | } 23 | 24 | get byteCount () { 25 | return 5 26 | } 27 | 28 | public static fromBuffer (buffer: Buffer) { 29 | try { 30 | const fc = buffer.readUInt8(0) 31 | const start = buffer.readUInt16BE(1) 32 | const count = buffer.readUInt16BE(3) 33 | 34 | if (fc !== FC.READ_INPUT_REGISTERS) { 35 | return null 36 | } 37 | 38 | return new ReadInputRegistersRequestBody(start, count) 39 | } catch (e) { 40 | return null 41 | } 42 | } 43 | private _start: number 44 | private _count: number 45 | 46 | /** Create a new Read Input Registers Request Body. 47 | * @param {number} start Start Address. 48 | * @param {number} count Quantity of coils to be read. 49 | * @throws {InvalidStartAddressException} When Start address is larger than 0xFFFF. 50 | * @throws {InvalidQuantityException} When count is larger than 0x7D0. 51 | */ 52 | constructor (start: number, count: number) { 53 | super(FC.READ_INPUT_REGISTERS) 54 | if (start > 0xFFFF) { 55 | throw new Error('InvalidStartAddress') 56 | } 57 | if (count > 0x7D0) { 58 | throw new Error('InvalidQuantity') 59 | } 60 | this._start = start 61 | this._count = count 62 | } 63 | 64 | public createPayload () { 65 | const payload = Buffer.alloc(5) 66 | 67 | payload.writeUInt8(this._fc, 0) // function code 68 | payload.writeUInt16BE(this._start, 1) // start address 69 | payload.writeUInt16BE(this._count, 3) // quantitiy of coils 70 | 71 | return payload 72 | } 73 | } 74 | 75 | export function isReadInputRegistersRequestBody (x: any): x is ReadInputRegistersRequestBody { 76 | if (x instanceof ReadInputRegistersRequestBody) { 77 | return true 78 | } else { 79 | return false 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/request/request-body.ts: -------------------------------------------------------------------------------- 1 | 2 | import { FunctionCode } from '../codes' 3 | 4 | import Debug from 'debug' 5 | const debug = Debug('request-body') 6 | 7 | export type ModbusRequestTypeName = 8 | | 'ReadCoils' 9 | | 'ReadDiscreteInput' 10 | | 'ReadHoldingRegisters' 11 | | 'ReadInputRegisters' 12 | | 'WriteMultipleCoils' 13 | | 'WriteMultipleRegisters' 14 | | 'WriteSingleCoil' 15 | | 'WriteSingleRegister' 16 | | 'ExceptionRequest' 17 | /** Common Modbus Request Body 18 | * 19 | * 20 | * @abstract 21 | * @class ModbusRequestBody 22 | */ 23 | export default abstract class ModbusRequestBody { 24 | protected _fc: FunctionCode 25 | 26 | /** Creates a new Common Modbus Request Body. Do not use this, 27 | * use the actual request body 28 | * @param {FunctionCode} fc Function Code 29 | */ 30 | constructor (fc: FunctionCode) { 31 | if (new.target === ModbusRequestBody) { 32 | throw new TypeError('Cannot construct ModbusRequestBody directly.') 33 | } 34 | 35 | this._fc = fc 36 | } 37 | 38 | /** Function Code */ 39 | get fc () { 40 | return this._fc 41 | } 42 | 43 | /** Create byte representation. 44 | * @returns {Buffer} 45 | */ 46 | public abstract createPayload (): Buffer 47 | 48 | /** Returns the byte count of the `request` for the byte representation. 49 | * @returns {Number} 50 | */ 51 | abstract get byteCount (): number 52 | 53 | /** 54 | * Name of the request body 55 | * 56 | * @memberof ModbusRequestBody 57 | */ 58 | abstract get name (): ModbusRequestTypeName; 59 | 60 | /** 61 | * Returns the count of the quantity 62 | * of registers, coils, etc. 63 | * 64 | * @readonly 65 | * @abstract 66 | * @type {number} 67 | * @memberof ModbusRequestBody 68 | */ 69 | abstract get count (): number; 70 | 71 | get isException (): boolean { 72 | return false 73 | } 74 | 75 | public get isModbusRequestBody () { 76 | return true 77 | } 78 | } 79 | 80 | export function isModbusRequestBody (x: any): x is ModbusRequestBody { 81 | if (x.isModbusRequestBody) { 82 | return true 83 | } else { 84 | return false 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/request/request-factory.ts: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | FC, 4 | isFunctionCode 5 | } from '../codes' 6 | import ExceptionRequest from './exception.js' 7 | import ReadCoilsRequest from './read-coils.js' 8 | import ReadDiscreteInputsRequest from './read-discrete-inputs.js' 9 | import ReadHoldingRegistersRequest from './read-holding-registers.js' 10 | import ReadInputRegistersRequest from './read-input-registers.js' 11 | import WriteMultipleCoilsResponse from './write-multiple-coils.js' 12 | import WriteMultipleRegistersResponse from './write-multiple-registers.js' 13 | import WriteSingleCoilRequest from './write-single-coil.js' 14 | import WriteSingleRegisterRequest from './write-single-register.js' 15 | 16 | import Debug from 'debug'; const debug = Debug('request-factory') 17 | 18 | export default class RequestFactory { 19 | 20 | /** Create a Modbus Request Body from a buffer object. Depending on the function code 21 | * in the buffer the request body could by any function codes request body. 22 | * @param {Buffer} buffer The buffer to be parsed. 23 | * @returns {ModbusRequestBody} The actual request body or null if there is not enough data in the buffer. 24 | */ 25 | public static fromBuffer (buffer: Buffer) { 26 | /* TODO: detect non modbus requests and return a InvalidProtocolRequest. Requests 27 | * of this kind should lead to disconnecting the client. This way we can make sure that 28 | * unintendet messages do not harm the server */ 29 | try { 30 | const fc = buffer.readUInt8(0) 31 | 32 | debug('fc', fc, 'payload', buffer) 33 | 34 | if (isFunctionCode(fc)) { 35 | switch (fc) { 36 | 37 | case FC.READ_COIL: 38 | return ReadCoilsRequest.fromBuffer(buffer) 39 | 40 | case FC.READ_DISCRETE_INPUT: 41 | return ReadDiscreteInputsRequest.fromBuffer(buffer) 42 | 43 | case FC.READ_HOLDING_REGISTERS: 44 | return ReadHoldingRegistersRequest.fromBuffer(buffer) 45 | 46 | case FC.READ_INPUT_REGISTERS: 47 | return ReadInputRegistersRequest.fromBuffer(buffer) 48 | 49 | case FC.WRITE_SINGLE_COIL: 50 | return WriteSingleCoilRequest.fromBuffer(buffer) 51 | 52 | case FC.WRITE_SINGLE_HOLDING_REGISTER: 53 | return WriteSingleRegisterRequest.fromBuffer(buffer) 54 | 55 | case FC.WRITE_MULTIPLE_COILS: 56 | return WriteMultipleCoilsResponse.fromBuffer(buffer) 57 | 58 | case FC.WRITE_MULTIPLE_HOLDING_REGISTERS: 59 | return WriteMultipleRegistersResponse.fromBuffer(buffer) 60 | 61 | } 62 | } 63 | 64 | if (fc <= 0x2B) { 65 | debug('Illegal Function (fc %d)', fc) 66 | return new ExceptionRequest(fc, 0x01) 67 | } 68 | } catch (e) { 69 | debug('Exception while reading function code', e) 70 | return null 71 | } 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/request/write-multiple-registers.ts: -------------------------------------------------------------------------------- 1 | import { FC } from '../codes' 2 | 3 | import ModbusRequestBody from './request-body.js' 4 | 5 | /** Write Multiple Registers Request Body 6 | * @extends ModbusRequestBody 7 | */ 8 | export default class WriteMultipleRegistersRequestBody extends ModbusRequestBody { 9 | 10 | /** Start Address to begin writing data */ 11 | get address () { 12 | return this._address 13 | } 14 | 15 | /** Quantity of registers beein written */ 16 | get quantity () { 17 | return this._quantity 18 | } 19 | 20 | get count () { 21 | return this.quantity 22 | } 23 | 24 | /** Values to be written */ 25 | get values () { 26 | return this._values 27 | } 28 | 29 | get valuesAsArray () { 30 | return this._valuesAsArray 31 | } 32 | 33 | get valuesAsBuffer () { 34 | return this._valuesAsBuffer 35 | } 36 | 37 | get byteCount () { 38 | return this._byteCount 39 | } 40 | 41 | get numberOfBytes () { 42 | return this._numberOfBytes 43 | } 44 | 45 | get name () { 46 | return 'WriteMultipleRegisters' as const 47 | } 48 | 49 | public static fromBuffer (buffer: Buffer) { 50 | try { 51 | const fc = buffer.readUInt8(0) 52 | const address = buffer.readUInt16BE(1) 53 | const numberOfBytes = buffer.readUInt8(5) 54 | const values = buffer.slice(6, 6 + numberOfBytes) 55 | 56 | if (fc !== FC.WRITE_MULTIPLE_HOLDING_REGISTERS) { 57 | return null 58 | } 59 | 60 | return new WriteMultipleRegistersRequestBody(address, values) 61 | } catch (e) { 62 | return null 63 | } 64 | } 65 | private _address: number 66 | private _values: number[] | Buffer 67 | private _byteCount: number 68 | private _numberOfBytes: number 69 | private _quantity: number 70 | private _valuesAsBuffer: Buffer 71 | private _valuesAsArray: number[] 72 | 73 | /** Create a new Write Multiple Registers Request Body. 74 | * @param {number} address Write address. 75 | * @param {number[] | Buffer} values Values to be written. Either a Array of UInt16 values or a Buffer. 76 | * @param {number} quantity In case of values being a Buffer, specify the number of coils that needs to be written. 77 | * @throws {InvalidStartAddressException} When address is larger than 0xFFFF. 78 | * @throws {InvalidArraySizeException} 79 | * @throws {InvalidBufferSizeException} 80 | */ 81 | constructor (address: number, values: number[] | Buffer) { 82 | super(FC.WRITE_MULTIPLE_HOLDING_REGISTERS) 83 | if (address > 0xFFFF) { 84 | throw new Error('InvalidStartAddress') 85 | } 86 | if (Array.isArray(values) && values.length > 0x7b) { 87 | throw new Error('InvalidArraySize') 88 | } 89 | if (values instanceof Buffer && values.length > 0x7b * 2) { 90 | throw new Error('InvalidBufferSize') 91 | } 92 | this._address = address 93 | this._values = values 94 | 95 | if (this._values instanceof Buffer) { 96 | this._byteCount = Math.min(this._values.length + 6, 0xF6) 97 | this._numberOfBytes = this._values.length 98 | this._quantity = Math.floor(this._values.length / 2) 99 | this._valuesAsBuffer = this._values 100 | this._valuesAsArray = [] 101 | for (let i = 0; i < this._values.length; i += 2) { 102 | this._valuesAsArray.push(this._values.readUInt16BE(i)) 103 | } 104 | } else if (this._values instanceof Array) { 105 | this._valuesAsArray = this._values 106 | this._byteCount = Math.min(this._values.length * 2 + 6, 0xF6) 107 | this._numberOfBytes = Math.floor(this._values.length * 2) 108 | this._quantity = this._values.length 109 | this._valuesAsBuffer = Buffer.alloc(this._numberOfBytes) 110 | this._values.forEach((v, i) => { 111 | this._valuesAsBuffer.writeUInt16BE(v, i * 2) 112 | }) 113 | } else { 114 | throw new Error('InvalidType_MustBeBufferOrArray') 115 | } 116 | } 117 | 118 | public createPayload () { 119 | const payload = Buffer.alloc(6 + this._numberOfBytes) 120 | payload.writeUInt8(this._fc, 0) // function code 121 | payload.writeUInt16BE(this._address, 1) // start address 122 | payload.writeUInt16BE(this._quantity, 3) 123 | payload.writeUInt8(this._numberOfBytes, 5) 124 | this._valuesAsBuffer.copy(payload, 6) 125 | return payload 126 | } 127 | } 128 | 129 | export function isWriteMultipleRegistersRequestBody (x: any): x is WriteMultipleRegistersRequestBody { 130 | if (x instanceof WriteMultipleRegistersRequestBody) { 131 | return true 132 | } else { 133 | return false 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/request/write-single-coil.ts: -------------------------------------------------------------------------------- 1 | import { FC } from '../codes/index.js' 2 | import ModbusRequestBody from './request-body.js' 3 | 4 | /** Write Single Coil Request Body 5 | * @extends ModbusRequestBody 6 | */ 7 | export default class WriteSingleCoilRequestBody extends ModbusRequestBody { 8 | 9 | /** Address to be written */ 10 | get address () { 11 | return this._address 12 | } 13 | 14 | /** Value to be written */ 15 | get value () { 16 | return this._value ? 0xFF00 : 0x0000 17 | } 18 | 19 | get byteCount () { 20 | return 5 21 | } 22 | 23 | get count () { 24 | return 1 25 | } 26 | 27 | get name () { 28 | return 'WriteSingleCoil' as const 29 | } 30 | 31 | public static fromBuffer (buffer: Buffer) { 32 | try { 33 | const fc = buffer.readUInt8(0) 34 | const address = buffer.readUInt16BE(1) 35 | const value = buffer.readUInt16BE(3) === 0xff00 36 | 37 | if (fc !== FC.WRITE_SINGLE_COIL) { 38 | return null 39 | } 40 | 41 | return new WriteSingleCoilRequestBody(address, value) 42 | } catch (e) { 43 | return null 44 | } 45 | } 46 | private _address: number 47 | private _value: boolean | 0 | 1 48 | 49 | /** Create a new Write Single Coil Request Body. 50 | * @param {number} address Write address. 51 | * @param {boolean | 0 | 1} value Value to be written. 52 | * @throws {InvalidStartAddressException} When address is larger than 0xFFFF. 53 | */ 54 | constructor (address: number, value: boolean | 0 | 1) { 55 | super(FC.WRITE_SINGLE_COIL) 56 | if (address > 0xFFFF) { 57 | throw new Error('InvalidStartAddress') 58 | } 59 | this._address = address 60 | this._value = value 61 | } 62 | 63 | public createPayload () { 64 | const payload = Buffer.alloc(5) 65 | 66 | payload.writeUInt8(this._fc, 0) // function code 67 | payload.writeUInt16BE(this._address, 1) // output address 68 | payload.writeUInt16BE(this._value ? 0xFF00 : 0x0000, 3) // output value 69 | 70 | return payload 71 | } 72 | } 73 | 74 | export function isWriteSingleCoilRequestBody (x: any): x is WriteSingleCoilRequestBody { 75 | if (x instanceof WriteSingleCoilRequestBody) { 76 | return true 77 | } else { 78 | return false 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/request/write-single-register.ts: -------------------------------------------------------------------------------- 1 | import { FC } from '../codes' 2 | import ModbusRequestBody from './request-body.js' 3 | 4 | /** Write Single Register Request Body 5 | * @extends ModbusRequestBody 6 | */ 7 | export default class WriteSingleRegisterRequestBody extends ModbusRequestBody { 8 | 9 | /** Address to be written. */ 10 | get address () { 11 | return this._address 12 | } 13 | 14 | /** Value to be written. */ 15 | get value () { 16 | return this._value 17 | } 18 | 19 | get name () { 20 | return 'WriteSingleRegister' as const 21 | } 22 | 23 | get quantity () { 24 | return 1 25 | } 26 | 27 | get count () { 28 | return 1 29 | } 30 | 31 | get byteCount () { 32 | return 5 33 | } 34 | 35 | public static fromBuffer (buffer: Buffer) { 36 | try { 37 | const fc = buffer.readUInt8(0) 38 | const address = buffer.readUInt16BE(1) 39 | const value = buffer.readUInt16BE(3) 40 | 41 | if (fc !== FC.WRITE_SINGLE_HOLDING_REGISTER) { 42 | return null 43 | } 44 | 45 | return new WriteSingleRegisterRequestBody(address, value) 46 | } catch (e) { 47 | return null 48 | } 49 | } 50 | private _address: number 51 | private _value: number 52 | 53 | /** Create a new Write Single Register Request Body. 54 | * @param {number} address Write address. 55 | * @param {number} value Value to be written. 56 | * @throws {InvalidStartAddressException} When address is larger than 0xFFFF. 57 | */ 58 | constructor (address: number, value: number) { 59 | super(FC.WRITE_SINGLE_HOLDING_REGISTER) 60 | if (address > 0xFFFF) { 61 | throw new Error('InvalidStartAddress') 62 | } 63 | if (!Number.isInteger(value) || value < 0 || value > 0xFFFF) { 64 | throw new Error('InvalidValue') 65 | } 66 | this._address = address 67 | this._value = value 68 | } 69 | 70 | public createPayload () { 71 | const payload = Buffer.alloc(5) 72 | payload.writeUInt8(this._fc, 0) // function code 73 | payload.writeUInt16BE(this._address, 1) // output address 74 | payload.writeUInt16BE(this._value, 3) // output value 75 | return payload 76 | } 77 | } 78 | 79 | export function isWriteSingleRegisterRequestBody (x: any): x is WriteSingleRegisterRequestBody { 80 | if (x instanceof WriteSingleRegisterRequestBody) { 81 | return true 82 | } else { 83 | return false 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/response/exception.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ErrorCode, 3 | errorCodeToMessage, 4 | FunctionCode, 5 | isFunctionCode 6 | } from '../codes' 7 | import ExceptionRequestBody from '../request/exception.js' 8 | import ModbusRequestBody from '../request/request-body.js' 9 | import ModbusResponseBody from './response-body.js' 10 | 11 | /** Modbus Excepiton Response Body 12 | * @extends ModbusResponseBody 13 | * @class 14 | */ 15 | export default class ExceptionResponseBody extends ModbusResponseBody { 16 | 17 | /** Exception Code */ 18 | get code () { 19 | return this._code 20 | } 21 | 22 | /** Exception message */ 23 | get message () { 24 | return errorCodeToMessage(this._code) 25 | } 26 | 27 | get byteCount () { 28 | return 2 29 | } 30 | 31 | get isException (): boolean { 32 | return true 33 | } 34 | 35 | /** Create Exception Response from buffer. 36 | * @param {Buffer} buffer Buffer 37 | * @returns {ExceptionResponseBody} 38 | */ 39 | public static fromBuffer (buffer: Buffer) { 40 | const fc = buffer.readUInt8(0) - 0x80 41 | const code = buffer.readUInt8(1) as ErrorCode 42 | 43 | if (!isFunctionCode(fc)) { 44 | throw Error('InvalidFunctionCode') 45 | } 46 | return new ExceptionResponseBody(fc, code) 47 | } 48 | 49 | // TODO: Figure out what type the requestBody is 50 | public static fromRequest (requestBody: ExceptionRequestBody) { 51 | return new ExceptionResponseBody(requestBody.fc, requestBody.code) 52 | } 53 | private _code: ErrorCode 54 | 55 | /** Create ExceptionResponseBody 56 | * @param {FunctionCode} fc Function Code 57 | * @param {ErrorCode} code Exception Code 58 | */ 59 | constructor (fc: FunctionCode, code: ErrorCode) { 60 | const ignoreInvalidFunctionCode = true 61 | super(fc, ignoreInvalidFunctionCode) 62 | this._code = code 63 | } 64 | 65 | public createPayload () { 66 | const payload = Buffer.alloc(2) 67 | // This is a exception Response 68 | // Add 0x80 for compatibility (crc check) 69 | payload.writeUInt8(this._fc + 0x80, 0) 70 | payload.writeUInt8(this._code, 1) 71 | return payload 72 | } 73 | } 74 | 75 | export function isExceptionResponseBody (x: any): x is ExceptionResponseBody { 76 | if (x instanceof ExceptionResponseBody) { 77 | return true 78 | } else { 79 | return false 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/response/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ExceptionResponseBody, isExceptionResponseBody } from './exception' 2 | export { default as ReadCoilsResponseBody } from './read-coils' 3 | export { default as ReadDiscreteInputsResponseBody } from './read-discrete-inputs' 4 | export { default as ReadHoldingRegistersResponseBody } from './read-holding-registers' 5 | export { default as ReadInputRegistersResponseBody } from './read-input-registers' 6 | export { default as ModbusResponseBody } from './response-body' 7 | export { default as ResponseFactory } from './response-factory' 8 | export { default as WriteMultipleCoilsResponseBody } from './write-multiple-coils' 9 | export { default as WriteMultipleRegistersResponseBody } from './write-multiple-registers' 10 | export { default as WriteSingleCoilResponseBody } from './write-single-coil' 11 | export { default as WriteSingleRegisterResponseBody } from './write-single-register' 12 | -------------------------------------------------------------------------------- /src/response/read-coils.ts: -------------------------------------------------------------------------------- 1 | import Debug = require('debug'); const debug = Debug('read-coils-response') 2 | import BufferUtils from '../buffer-utils.js' 3 | import { FC } from '../codes' 4 | import { BooleanArray } from '../constants' 5 | import ReadCoilsRequestBody from '../request/read-coils.js' 6 | import ModbusReadResponseBody from './read-response-body.js' 7 | 8 | const { 9 | bufferToArrayStatus, 10 | arrayStatusToBuffer 11 | } = BufferUtils 12 | 13 | /** Read Coils Response Body 14 | * @extends ModbusResponseBody 15 | * @class 16 | */ 17 | export default class ReadCoilsResponseBody extends ModbusReadResponseBody { 18 | 19 | /** Coils */ 20 | get values () { 21 | return this._coils 22 | } 23 | 24 | get valuesAsArray () { 25 | return this._valuesAsArray 26 | } 27 | 28 | get valuesAsBuffer () { 29 | return this._valuesAsBuffer 30 | } 31 | 32 | /** Length */ 33 | get numberOfBytes () { 34 | return this._numberOfBytes 35 | } 36 | 37 | get byteCount () { 38 | return this._numberOfBytes + 2 39 | } 40 | 41 | /** Creates a response body from a request body and 42 | * the coils buffer 43 | * @param {ReadCoilsRequestBody} request 44 | * @param {Buffer} coils 45 | * @returns {ReadCoilsResponseBody} 46 | */ 47 | public static fromRequest (requestBody: ReadCoilsRequestBody, coils: Buffer) { 48 | const coilsStatus = bufferToArrayStatus(coils) 49 | 50 | const start = requestBody.start 51 | const end = start + requestBody.count 52 | 53 | // Extract the segment of coils status 54 | const coilsSegment = coilsStatus.slice(start, end) 55 | 56 | return new ReadCoilsResponseBody(coilsSegment, Math.ceil(coilsSegment.length / 8)) 57 | } 58 | 59 | /** Create ReadCoilsResponseBody from buffer. 60 | * @param {Buffer} buffer 61 | * @returns {ReadCoilsResponseBody} Returns Null of not enough data located in the buffer. 62 | */ 63 | public static fromBuffer (buffer: Buffer) { 64 | try { 65 | const fc = buffer.readUInt8(0) 66 | const byteCount = buffer.readUInt8(1) 67 | const coilStatus = buffer.slice(2, 2 + byteCount) 68 | 69 | if (coilStatus.length !== byteCount) { 70 | return null 71 | } 72 | 73 | if (fc !== FC.READ_COIL) { 74 | return null 75 | } 76 | 77 | return new ReadCoilsResponseBody(coilStatus, byteCount) 78 | } catch (e) { 79 | debug('no valid read coils response body in the buffer yet') 80 | return null 81 | } 82 | } 83 | protected _valuesAsArray: BooleanArray 84 | protected _valuesAsBuffer: Buffer 85 | private _coils: BooleanArray | Buffer 86 | private _numberOfBytes: number 87 | 88 | /** 89 | * Create new ReadCoilsResponseBody 90 | * @param {(BooleanArray | Buffer)} coils 91 | * @param {number} numberOfBytes 92 | * @memberof ReadCoilsResponseBody 93 | */ 94 | constructor (coils: BooleanArray | Buffer, numberOfBytes: number) { 95 | super(FC.READ_COIL) 96 | this._coils = coils 97 | this._numberOfBytes = numberOfBytes 98 | 99 | if (coils instanceof Array) { 100 | this._valuesAsArray = coils 101 | this._valuesAsBuffer = arrayStatusToBuffer(coils) 102 | } else if (coils instanceof Buffer) { 103 | this._valuesAsBuffer = coils 104 | this._valuesAsArray = bufferToArrayStatus(coils) 105 | } else { 106 | throw new Error('InvalidCoilsInput') 107 | } 108 | } 109 | 110 | public createPayload () { 111 | const payload = Buffer.alloc(this.byteCount) 112 | 113 | payload.writeUInt8(this._fc, 0) 114 | payload.writeUInt8(this._numberOfBytes, 1) 115 | 116 | this._valuesAsBuffer.copy(payload, 2) 117 | 118 | return payload 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/response/read-discrete-inputs.ts: -------------------------------------------------------------------------------- 1 | import BufferUtils from '../buffer-utils.js' 2 | import { FC } from '../codes/index.js' 3 | import { BooleanArray } from '../constants/index.js' 4 | import ReadDiscreteInputsRequestBody from '../request/read-discrete-inputs.js' 5 | import UserRequest from '../user-request.js' 6 | import ModbusReadResponseBody from './read-response-body.js' 7 | 8 | const { 9 | bufferToArrayStatus, 10 | arrayStatusToBuffer 11 | } = BufferUtils 12 | 13 | /** Read Discrete Inputs Response Body (Function Code 0x02) 14 | * @extends ModbusResponseBody 15 | * @class 16 | */ 17 | export default class ReadDiscreteInputsResponseBody extends ModbusReadResponseBody { 18 | 19 | /** Coils */ 20 | get discrete () { 21 | return this._discrete 22 | } 23 | 24 | get valuesAsArray () { 25 | return this._valuesAsArray 26 | } 27 | 28 | get valuesAsBuffer () { 29 | return this._valuesAsBuffer 30 | } 31 | 32 | /** Length */ 33 | get numberOfBytes () { 34 | return this._numberOfBytes 35 | } 36 | 37 | get byteCount () { 38 | return this._numberOfBytes + 2 39 | } 40 | 41 | /** Create ReadDiscreteInputsResponseBody from Request 42 | * @param {ReadDiscreteInputsRequestBody} request 43 | * @param {Buffer} discreteInputs 44 | * @returns ReadDiscreteInputsResponseBody 45 | */ 46 | public static fromRequest (requestBody: ReadDiscreteInputsRequestBody, discreteInputs: Buffer) { 47 | const discreteStatus = bufferToArrayStatus(discreteInputs) 48 | 49 | const start = requestBody.start 50 | const end = start + requestBody.count 51 | 52 | // Extract the segment of coils status 53 | const segmentStatus = discreteStatus.slice(start, end) 54 | 55 | return new ReadDiscreteInputsResponseBody(segmentStatus, Math.ceil(segmentStatus.length / 8)) 56 | } 57 | 58 | /** Create ReadDiscreteInputsResponseBody from Buffer 59 | * @param {Buffer} buffer 60 | * @returns ReadDiscreteInputsResponseBody 61 | */ 62 | public static fromBuffer (buffer: Buffer) { 63 | try { 64 | const fc = buffer.readUInt8(0) 65 | const byteCount = buffer.readUInt8(1) 66 | const coilStatus = buffer.slice(2, 2 + byteCount) 67 | 68 | if (coilStatus.length !== byteCount) { 69 | return null 70 | } 71 | 72 | if (fc !== FC.READ_DISCRETE_INPUT) { 73 | return null 74 | } 75 | 76 | return new ReadDiscreteInputsResponseBody(coilStatus, byteCount) 77 | } catch (e) { 78 | return null 79 | } 80 | } 81 | protected _valuesAsArray: BooleanArray 82 | protected _valuesAsBuffer: Buffer 83 | private _discrete: BooleanArray | Buffer 84 | private _numberOfBytes: number 85 | 86 | /** Creates a ReadDiscreteInputsResponseBody 87 | * @param {Array} discrete Array with Boolean values 88 | * @param {Number} length Quantity of Coils 89 | */ 90 | constructor (discrete: BooleanArray | Buffer, numberOfBytes: number) { 91 | super(FC.READ_DISCRETE_INPUT) 92 | this._discrete = discrete 93 | this._numberOfBytes = numberOfBytes 94 | 95 | if (discrete instanceof Array) { 96 | this._valuesAsArray = discrete 97 | this._valuesAsBuffer = arrayStatusToBuffer(discrete) 98 | } else if (discrete instanceof Buffer) { 99 | this._valuesAsBuffer = discrete 100 | this._valuesAsArray = bufferToArrayStatus(discrete) 101 | } else { 102 | throw new Error('InvalidType_MustBeBufferOrArray') 103 | } 104 | } 105 | 106 | public createPayload () { 107 | const payload = Buffer.alloc(this.byteCount) 108 | 109 | payload.writeUInt8(this._fc, 0) 110 | payload.writeUInt8(this._numberOfBytes, 1) 111 | 112 | this._valuesAsBuffer.copy(payload, 2) 113 | 114 | return payload 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/response/read-holding-registers.ts: -------------------------------------------------------------------------------- 1 | import Debug = require('debug'); const debug = Debug('ReadHoldingRegistersResponseBody') 2 | import BufferUtils from '../buffer-utils.js' 3 | import { FC } from '../codes' 4 | import ReadHoldingRegistersRequestBody from '../request/read-holding-registers' 5 | import ModbusReadResponseBody from './read-response-body.js' 6 | 7 | /** Read Holding Registers ResponseBody (Function Code 0x03) 8 | * @extends ModbusResponseBody 9 | * @class 10 | */ 11 | export default class ReadHoldingRegistersResponseBody extends ModbusReadResponseBody { 12 | 13 | get byteCount () { 14 | return this._bufferLength 15 | } 16 | 17 | get values () { 18 | return this._values 19 | } 20 | 21 | get valuesAsArray () { 22 | return this._valuesAsArray 23 | } 24 | 25 | get valuesAsBuffer () { 26 | return this._valuesAsBuffer 27 | } 28 | 29 | get length () { 30 | return this._values.length 31 | } 32 | 33 | /** Create ReadHoldingRegistersResponseBody from Request 34 | * @param {ReadHoldingRegistersRequestBody} request 35 | * @param {Buffer} holdingRegisters 36 | * @returns ReadHoldingRegistersResponseBody 37 | */ 38 | public static fromRequest (requestBody: ReadHoldingRegistersRequestBody, holdingRegisters: Buffer) { 39 | const startByte = requestBody.start * 2 40 | const endByte = (requestBody.start * 2) + (requestBody.count * 2) 41 | 42 | const bufferSegment = holdingRegisters.slice(startByte, endByte) 43 | 44 | /* TODO: check wheather holdingRegisters is big enough for this request */ 45 | 46 | return new ReadHoldingRegistersResponseBody(bufferSegment.length, bufferSegment) 47 | } 48 | 49 | /** Create ReadHoldingRegistersResponseBody from Buffer 50 | * @param {Buffer} buffer 51 | * @returns ReadHoldingRegistersResponseBody 52 | */ 53 | public static fromBuffer (buffer: Buffer) { 54 | const fc = buffer.readUInt8(0) 55 | const byteCount = buffer.readUInt8(1) 56 | const payload = buffer.slice(2, 2 + byteCount) 57 | 58 | if (fc !== FC.READ_HOLDING_REGISTERS) { 59 | return null 60 | } 61 | 62 | const values = [] 63 | for (let i = 0; i < byteCount; i += 2) { 64 | values.push(payload.readUInt16BE(i)) 65 | } 66 | 67 | return new ReadHoldingRegistersResponseBody(byteCount, values, payload) 68 | } 69 | protected _valuesAsArray: number[] | Uint16Array 70 | protected _valuesAsBuffer: Buffer 71 | private _byteCount: any 72 | private _values: number[] | Buffer 73 | private _bufferLength: any 74 | 75 | constructor (byteCount: number, values: number[] | Buffer, payload?: Buffer) { 76 | super(FC.READ_HOLDING_REGISTERS) 77 | this._byteCount = byteCount 78 | this._values = values 79 | this._bufferLength = 2 80 | 81 | debug('ReadHoldingRegistersResponseBody values', values) 82 | 83 | if (values instanceof Array) { 84 | this._valuesAsArray = values 85 | this._valuesAsBuffer = Buffer.from(values) 86 | this._bufferLength += values.length * 2 87 | } else if (values instanceof Buffer) { 88 | this._valuesAsArray = Uint16Array.from(values) 89 | this._valuesAsBuffer = values 90 | this._bufferLength += values.length 91 | } else { 92 | throw new Error('InvalidType_MustBeBufferOrArray') 93 | } 94 | 95 | if (payload instanceof Buffer) { 96 | this._valuesAsBuffer = payload 97 | } 98 | } 99 | 100 | public createPayload () { 101 | if (this._values instanceof Buffer) { 102 | let payload = Buffer.alloc(2) 103 | payload.writeUInt8(this._fc, 0) 104 | payload.writeUInt8(this._byteCount, 1) 105 | payload = Buffer.concat([payload, this._values]) 106 | return payload 107 | } 108 | 109 | if (this._values instanceof Array) { 110 | const payload = Buffer.alloc(this._byteCount + 2) 111 | payload.writeUInt8(this._fc, 0) 112 | payload.writeUInt8(this._byteCount, 1) 113 | this._values.forEach((value, i) => { 114 | payload.writeUInt16BE(Math.max(0, Math.min(0xFFFF, value)), 2 * i + 2) 115 | }) 116 | 117 | return payload 118 | } 119 | 120 | throw new Error('InvalidType_MustBeBufferOrArray') 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/response/read-input-registers.ts: -------------------------------------------------------------------------------- 1 | import { FC } from '../codes/index.js' 2 | import ReadInputRegistersRequestBody from '../request/read-input-registers.js' 3 | import ModbusReadResponseBody from './read-response-body.js' 4 | 5 | /** Read Input Registers Response Body (Function Code 0x04) 6 | * @extends ModbusResponseBody 7 | * @class 8 | */ 9 | export default class ReadInputRegistersResponseBody extends ModbusReadResponseBody { 10 | 11 | get byteCount () { 12 | return this._bufferLength 13 | } 14 | 15 | get values () { 16 | return this._values 17 | } 18 | 19 | get valuesAsArray () { 20 | return this._valuesAsArray 21 | } 22 | 23 | get valuesAsBuffer () { 24 | return this._valuesAsBuffer 25 | } 26 | 27 | get length () { 28 | return this._values.length 29 | } 30 | 31 | /** Create ReadInputRegistersResponseBody from Request 32 | * @param {ReadInputRegistersRequestBody} request 33 | * @param {Buffer} inputRegisters 34 | * @returns ReadInputRegistersResponseBody 35 | */ 36 | public static fromRequest (requestBody: ReadInputRegistersRequestBody, inputRegisters: Buffer) { 37 | const startByte = requestBody.start * 2 38 | const endByte = startByte + (requestBody.count * 2) 39 | 40 | const buf = inputRegisters.slice(startByte, endByte) 41 | 42 | return new ReadInputRegistersResponseBody(buf.length, buf) 43 | } 44 | 45 | /** Create ReadInputRegistersResponseBody from Buffer 46 | * @param {Buffer} buffer 47 | * @returns ReadInputRegistersResponseBody 48 | */ 49 | public static fromBuffer (buffer: Buffer) { 50 | const fc = buffer.readUInt8(0) 51 | const byteCount = buffer.readUInt8(1) 52 | const payload = buffer.slice(2, 2 + byteCount) 53 | 54 | if (fc !== FC.READ_INPUT_REGISTERS) { 55 | return null 56 | } 57 | 58 | const values = [] 59 | for (let i = 0; i < byteCount; i += 2) { 60 | values.push(payload.readUInt16BE(i)) 61 | } 62 | 63 | return new ReadInputRegistersResponseBody(byteCount, values, payload) 64 | } 65 | protected _valuesAsArray: number[] | Uint16Array 66 | protected _valuesAsBuffer: Buffer 67 | private _byteCount: number 68 | private _values: number[] | Uint16Array | Buffer 69 | private _bufferLength: number 70 | 71 | constructor (byteCount: number, values: number[] | Uint16Array | Buffer, payload?: Buffer) { 72 | super(FC.READ_INPUT_REGISTERS) 73 | this._byteCount = byteCount 74 | this._values = values 75 | this._bufferLength = 2 76 | 77 | if (values instanceof Array) { 78 | this._valuesAsArray = values 79 | this._valuesAsBuffer = Buffer.from(values) 80 | this._bufferLength += values.length * 2 81 | } else if (values instanceof Buffer) { 82 | this._valuesAsArray = Uint16Array.from(values) 83 | this._valuesAsBuffer = values 84 | this._bufferLength += values.length 85 | } else { 86 | throw new Error('InvalidType_MustBeBufferOrArray') 87 | } 88 | 89 | if (payload instanceof Buffer) { 90 | this._valuesAsBuffer = payload 91 | } 92 | } 93 | 94 | public createPayload () { 95 | if (this._values instanceof Buffer) { 96 | let payload = Buffer.alloc(2) 97 | payload.writeUInt8(this._fc, 0) 98 | payload.writeUInt8(this._byteCount, 1) 99 | payload = Buffer.concat([payload, this._values]) 100 | return payload 101 | } 102 | 103 | if (this._values instanceof Array) { 104 | const payload = Buffer.alloc(this._byteCount + 2) 105 | payload.writeUInt8(this._fc, 0) 106 | payload.writeUInt8(this._byteCount, 1) 107 | this._values.forEach((value, i) => { 108 | payload.writeUInt16BE(Math.max(0, Math.min(0xFFFF, value)), 2 + 2 * i) 109 | }) 110 | 111 | return payload 112 | } 113 | 114 | throw new Error('this._values is not an instance of a Buffer or an Array') 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/response/read-response-body.ts: -------------------------------------------------------------------------------- 1 | import { FunctionCode } from '../codes' 2 | import { BooleanArray } from '../constants' 3 | import ModbusBaseResponseBody from './response-body' 4 | 5 | /** Modbus Response Body 6 | * @abstract 7 | */ 8 | export default abstract class ModbusReadResponseBody extends ModbusBaseResponseBody { 9 | protected abstract _valuesAsArray?: number[] | BooleanArray | Uint16Array 10 | protected abstract _valuesAsBuffer?: Buffer 11 | 12 | /** Create new ModbusResponseBody 13 | * @param {FunctionCode} fc Function Code 14 | * @throws {InvalidFunctionCode} 15 | */ 16 | constructor (fc: FunctionCode) { 17 | super(fc) 18 | } 19 | 20 | /** Function Code */ 21 | get fc () { 22 | return this._fc 23 | } 24 | 25 | /** Number of bytes for the payload. */ 26 | abstract get byteCount (): number; 27 | 28 | /** Create payload to be send over a socket. 29 | * @returns {Buffer} 30 | */ 31 | public abstract createPayload (): Buffer 32 | 33 | // /** 34 | // * Start Address / Coil 35 | // * 36 | // * @abstract 37 | // * @type {number} 38 | // * @memberof ModbusResponseBody 39 | // */ 40 | // public abstract readonly start: number; 41 | 42 | // /** 43 | // * Coil / Address Quantity 44 | // * 45 | // * @abstract 46 | // * @type {number} 47 | // * @memberof ModbusResponseBody 48 | // */ 49 | // public abstract readonly count: number; 50 | 51 | public abstract get valuesAsArray (): number[] | BooleanArray | Uint16Array 52 | public abstract get valuesAsBuffer (): Buffer 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/response/response-body.ts: -------------------------------------------------------------------------------- 1 | 2 | import { FunctionCode, isFunctionCode } from '../codes' 3 | import ModbusRequestBody from '../request/request-body' 4 | 5 | /** Modbus Response Body 6 | * @abstract 7 | */ 8 | export default abstract class ModbusBaseResponseBody { 9 | 10 | /** Function Code */ 11 | get fc () { 12 | return this._fc 13 | } 14 | 15 | /** Number of bytes for the payload. */ 16 | abstract get byteCount (): number; 17 | 18 | get isException (): boolean { 19 | return false 20 | } 21 | 22 | public static fromRequest (requestBody: ModbusRequestBody, buf: Buffer): any { 23 | throw new TypeError('Cannot call from request from abstract class') 24 | } 25 | protected _fc: FunctionCode 26 | 27 | /** Create new ModbusResponseBody 28 | * @param {FunctionCode} fc Function Code 29 | * @throws {InvalidFunctionCode} 30 | */ 31 | constructor (fc: FunctionCode, ignoreInvalidFunctionCode = false) { 32 | if (ignoreInvalidFunctionCode === false) { 33 | if (!isFunctionCode(fc)) { 34 | throw Error('InvalidFunctionCode') 35 | } 36 | } 37 | 38 | this._fc = fc 39 | } 40 | 41 | /** Create payload to be send over a socket. 42 | * @returns {Buffer} 43 | */ 44 | public abstract createPayload (): Buffer 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/response/response-factory.ts: -------------------------------------------------------------------------------- 1 | import Debug = require('debug'); const debug = Debug('response-factory') 2 | 3 | import { FC } from '../codes/index.js' 4 | import ExceptionResponseBody from './exception.js' 5 | import ReadCoilsResponseBody from './read-coils.js' 6 | import ReadDiscreteInputsBody from './read-discrete-inputs.js' 7 | import ReadHoldingRegistersBody from './read-holding-registers.js' 8 | import ReadInputRegistersBody from './read-input-registers.js' 9 | import WriteMultipleCoilsBody from './write-multiple-coils.js' 10 | import WriteMultipleRegistersBody from './write-multiple-registers.js' 11 | import WriteSingleCoilBody from './write-single-coil.js' 12 | import WriteSingleRegisterBody from './write-single-register.js' 13 | 14 | /** Response Factory 15 | * @factory 16 | */ 17 | export default class ResponseFactory { 18 | public static fromBuffer (buffer: Buffer) { 19 | try { 20 | const fc = buffer.readUInt8(0) 21 | 22 | debug('fc', fc, 'payload', buffer) 23 | 24 | /* Exception Response */ 25 | if (fc > 0x80) { 26 | return ExceptionResponseBody.fromBuffer(buffer) 27 | } 28 | 29 | /* Read Coils Response */ 30 | if (fc === FC.READ_COIL) { 31 | return ReadCoilsResponseBody.fromBuffer(buffer) 32 | } 33 | 34 | /* Read Discrete Inputs Response */ 35 | if (fc === FC.READ_DISCRETE_INPUT) { 36 | return ReadDiscreteInputsBody.fromBuffer(buffer) 37 | } 38 | 39 | /* Read Holding Registers Response */ 40 | if (fc === FC.READ_HOLDING_REGISTERS) { 41 | return ReadHoldingRegistersBody.fromBuffer(buffer) 42 | } 43 | 44 | /* Read Input Registers Response */ 45 | if (fc === FC.READ_INPUT_REGISTERS) { 46 | return ReadInputRegistersBody.fromBuffer(buffer) 47 | } 48 | 49 | /* Write Single Coil Response */ 50 | if (fc === FC.WRITE_SINGLE_COIL) { 51 | return WriteSingleCoilBody.fromBuffer(buffer) 52 | } 53 | 54 | /* Write Single Register Response */ 55 | if (fc === FC.WRITE_SINGLE_HOLDING_REGISTER) { 56 | return WriteSingleRegisterBody.fromBuffer(buffer) 57 | } 58 | 59 | /* Write Multiple Coils Response */ 60 | if (fc === FC.WRITE_MULTIPLE_COILS) { 61 | return WriteMultipleCoilsBody.fromBuffer(buffer) 62 | } 63 | 64 | /* Write Multiple Registers Response */ 65 | if (fc === FC.WRITE_MULTIPLE_HOLDING_REGISTERS) { 66 | return WriteMultipleRegistersBody.fromBuffer(buffer) 67 | } 68 | 69 | return null 70 | } catch (e) { 71 | debug('when NoSuchIndex Exception, the buffer does not contain a complete message') 72 | debug(e) 73 | return null 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/response/write-multiple-coils.ts: -------------------------------------------------------------------------------- 1 | import { FC } from '../codes/index.js' 2 | import WriteMultipleCoilsRequestBody from '../request/write-multiple-coils.js' 3 | import ModbusWriteResponseBody from './write-response.body.js' 4 | 5 | /** WriteMultipleCoils Response Body (Function Code 0x0f) 6 | * @extends ModbusResponseBody 7 | * @class 8 | */ 9 | export default class WriteMultipleCoilsResponseBody extends ModbusWriteResponseBody { 10 | 11 | get start () { 12 | return this._start 13 | } 14 | 15 | get quantity () { 16 | return this._quantity 17 | } 18 | 19 | get count () { 20 | return this.quantity 21 | } 22 | 23 | get byteCount () { 24 | return 5 25 | } 26 | 27 | /** Create WriteMultipleCoilsResponseBody from Request 28 | * @param {WriteMultipleCoilsRequestBody} request 29 | * @param {Buffer} coil 30 | * @returns WriteMultipleCoilsResponseBody 31 | */ 32 | public static fromRequest (requestBody: WriteMultipleCoilsRequestBody) { 33 | const start = requestBody.address 34 | const quantity = requestBody.quantity 35 | 36 | return new WriteMultipleCoilsResponseBody(start, quantity) 37 | } 38 | 39 | public static fromBuffer (buffer: Buffer) { 40 | const fc = buffer.readUInt8(0) 41 | const start = buffer.readUInt16BE(1) 42 | const quantity = buffer.readUInt16BE(3) 43 | 44 | if (fc !== FC.WRITE_MULTIPLE_COILS) { 45 | return null 46 | } 47 | 48 | return new WriteMultipleCoilsResponseBody(start, quantity) 49 | } 50 | public _start: number 51 | public _quantity: number 52 | 53 | constructor (start: number, quantity: number) { 54 | super(FC.WRITE_MULTIPLE_COILS) 55 | this._start = start 56 | this._quantity = quantity 57 | } 58 | 59 | public createPayload () { 60 | const payload = Buffer.alloc(this.byteCount) 61 | 62 | payload.writeUInt8(this._fc, 0) 63 | payload.writeUInt16BE(this._start, 1) 64 | payload.writeUInt16BE(this._quantity, 3) 65 | 66 | return payload 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/response/write-multiple-registers.ts: -------------------------------------------------------------------------------- 1 | import { FC } from '../codes' 2 | import WriteMultipleRegistersRequestBody from '../request/write-multiple-registers' 3 | 4 | import ModbusWriteResponseBody from './write-response.body' 5 | 6 | /** WriteMultipleRegisters Respone Body (Function code 0x10) 7 | * @extends ModbusResponseBody 8 | * @class 9 | */ 10 | export default class WriteMultipleRegistersResponseBody extends ModbusWriteResponseBody { 11 | 12 | get start () { 13 | return this._start 14 | } 15 | 16 | get quantity () { 17 | return this._quantity 18 | } 19 | 20 | get count () { 21 | return this.quantity 22 | } 23 | 24 | get byteCount () { 25 | return 5 26 | } 27 | 28 | /** Create WriteMultipleRegisterResponseBody from Request 29 | * @param {WriteMultipleRegistersRequestBody} request 30 | * @param {Buffer} coil 31 | * @returns WriteMultipleRegisterResponseBody 32 | */ 33 | public static fromRequest (requestBody: WriteMultipleRegistersRequestBody) { 34 | const start = requestBody.address 35 | const quantity = requestBody.quantity 36 | 37 | return new WriteMultipleRegistersResponseBody(start, quantity) 38 | } 39 | 40 | public static fromBuffer (buffer: Buffer) { 41 | const fc = buffer.readUInt8(0) 42 | const start = buffer.readUInt16BE(1) 43 | const quantity = buffer.readUInt16BE(3) 44 | 45 | if (fc !== FC.WRITE_MULTIPLE_HOLDING_REGISTERS) { 46 | return null 47 | } 48 | 49 | return new WriteMultipleRegistersResponseBody(start, quantity) 50 | } 51 | protected _start: number 52 | protected _quantity: number 53 | 54 | constructor (start: number, quantity: number) { 55 | super(FC.WRITE_MULTIPLE_HOLDING_REGISTERS) 56 | this._start = start 57 | this._quantity = quantity 58 | } 59 | 60 | public createPayload () { 61 | const payload = Buffer.alloc(this.byteCount) 62 | 63 | payload.writeUInt8(this._fc, 0) 64 | payload.writeUInt16BE(this._start, 1) 65 | payload.writeUInt16BE(this._quantity, 3) 66 | 67 | return payload 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/response/write-response.body.ts: -------------------------------------------------------------------------------- 1 | import ModbusBaseResponseBody from './response-body' 2 | 3 | export default abstract class ModbusWriteResponseBody extends ModbusBaseResponseBody { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/response/write-single-coil.ts: -------------------------------------------------------------------------------- 1 | import { FC } from '../codes/index.js' 2 | import WriteSingleCoilRequestBody from '../request/write-single-coil.js' 3 | import ModbusWriteResponseBody from './write-response.body.js' 4 | 5 | /** Write Single Coil Response Body 6 | * @extends ModbusResponseBody 7 | * @class 8 | */ 9 | export default class WriteSingleCoilResponseBody extends ModbusWriteResponseBody { 10 | 11 | get address () { 12 | return this._address 13 | } 14 | 15 | get value () { 16 | return this._value === 0xff00 17 | } 18 | 19 | get byteCount () { 20 | return 5 21 | } 22 | 23 | /** Create WriteSingleCoilResponseBody from Request 24 | * @param {WriteSingleCoilRequestBody} request 25 | * @param {Buffer} coil 26 | * @returns WriteSingleCoilResponseBody 27 | */ 28 | public static fromRequest (requestBody: WriteSingleCoilRequestBody) { 29 | const address = requestBody.address 30 | const value = requestBody.value 31 | 32 | return new WriteSingleCoilResponseBody(address, value) 33 | } 34 | 35 | /** Creates a WriteSingleResponseBody from a Buffer 36 | * @param {Buffer} buffer 37 | * @returns New WriteSingleResponseBody Object 38 | */ 39 | public static fromBuffer (buffer: Buffer) { 40 | const fc = buffer.readUInt8(0) 41 | const address = buffer.readUInt16BE(1) 42 | const value = buffer.readUInt16BE(3) === 0xFF00 43 | 44 | if (fc !== FC.WRITE_SINGLE_COIL) { 45 | return null 46 | } 47 | 48 | return new WriteSingleCoilResponseBody(address, value) 49 | } 50 | public _address: number 51 | public _value: 0 | 0xff00 52 | 53 | constructor (address: number, value: 0 | 0xff00 | boolean) { 54 | super(FC.WRITE_SINGLE_COIL) 55 | this._address = address 56 | 57 | this._value = (value === 0xFF00 || value === true) ? 0xFF00 : 0x0000 58 | } 59 | 60 | public createPayload () { 61 | const payload = Buffer.alloc(this.byteCount) 62 | 63 | payload.writeUInt8(this._fc, 0) 64 | payload.writeUInt16BE(this._address, 1) 65 | payload.writeUInt16BE(this._value, 3) 66 | 67 | return payload 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/response/write-single-register.ts: -------------------------------------------------------------------------------- 1 | import { FC } from '../codes/index.js' 2 | import WriteSingleRegisterRequestBody from '../request/write-single-register.js' 3 | import ModbusWriteResponseBody from './write-response.body.js' 4 | 5 | /** WriteSingleRegister Resonse Body (Function code 0x05) 6 | * @extends ModbusResponseBody 7 | * @class 8 | */ 9 | export default class WriteSingleRegisterResponseBody extends ModbusWriteResponseBody { 10 | 11 | get address () { 12 | return this._address 13 | } 14 | 15 | get value () { 16 | return this._value 17 | } 18 | 19 | get byteCount () { 20 | return 5 21 | } 22 | 23 | /** Create WriteSingleRegisterResponseBody from Request 24 | * @param {WriteSingleRegisterRequestBody} request 25 | * @param {Buffer} coil 26 | * @returns WriteSingleRegisterResponseBody 27 | */ 28 | public static fromRequest (requestBody: WriteSingleRegisterRequestBody) { 29 | const address = requestBody.address 30 | const value = requestBody.value 31 | 32 | return new WriteSingleRegisterResponseBody(address, value) 33 | } 34 | 35 | public static fromBuffer (buffer: Buffer) { 36 | const fc = buffer.readUInt8(0) 37 | const address = buffer.readUInt16BE(1) 38 | const value = buffer.readUInt16BE(3) 39 | 40 | if (fc !== FC.WRITE_SINGLE_HOLDING_REGISTER) { 41 | return null 42 | } 43 | 44 | return new WriteSingleRegisterResponseBody(address, value) 45 | } 46 | private _address: number 47 | private _value: number 48 | 49 | constructor (address: number, value: number) { 50 | super(FC.WRITE_SINGLE_HOLDING_REGISTER) 51 | this._address = address 52 | this._value = value 53 | } 54 | 55 | public createPayload () { 56 | const payload = Buffer.alloc(5) 57 | 58 | payload.writeUInt8(this._fc, 0) 59 | payload.writeUInt16BE(this._address, 1) 60 | payload.writeUInt16BE(this._value, 3) 61 | 62 | return payload 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/rtu-client-request-handler.ts: -------------------------------------------------------------------------------- 1 | 2 | import Debug = require('debug'); const debug = Debug('rtu-client-request-handler') 3 | import CRC from 'crc' 4 | import * as SerialSocket from 'serialport' 5 | import MBClientRequestHandler from './client-request-handler.js' 6 | import ModbusRequestBody from './request/request-body.js' 7 | import ModbusRTURequest from './rtu-request.js' 8 | import ModbusRTUResponse from './rtu-response.js' 9 | import { UserRequestError } from './user-request-error' 10 | import UserRequest from './user-request.js' 11 | 12 | /** Modbus/RTU Client Request Handler 13 | * Implements behaviour for Client Requests for Modbus/RTU 14 | * @extends MBClientRequestHandler 15 | * @class 16 | */ 17 | export default class ModbusRTUClientRequestHandler extends MBClientRequestHandler { 18 | protected _requests: Array> 19 | protected _currentRequest: UserRequest | null | undefined 20 | protected readonly _address: number 21 | 22 | /** 23 | * Creates an instance of ModbusRTUClientRequestHandler. 24 | * @param {SerialSocket} socket Any serial Socket that implements the serialport interface 25 | * @param {number} address The serial address of the modbus slave 26 | * @param {number} [timeout=5000] 27 | * @memberof ModbusRTUClientRequestHandler 28 | */ 29 | constructor (socket: SerialSocket, address: number, timeout: number = 5000) { 30 | super(socket, timeout) 31 | this._address = address 32 | this._requests = [] 33 | this._currentRequest = null 34 | 35 | this._socket.on('open', this._onConnect.bind(this)) 36 | 37 | // Check if the passed in socket connection is already connected 38 | if (this._socket.isOpen) { 39 | this._onConnect() 40 | } 41 | } 42 | 43 | // TODO: Find a better way then putting in the any overide 44 | public register (requestBody: T): any { 45 | debug('registrating new request') 46 | 47 | const request = new ModbusRTURequest(this._address, requestBody) 48 | 49 | return super.registerRequest(request) 50 | } 51 | 52 | public handle (response: T) { 53 | debug('new response coming in') 54 | if (!response) { 55 | return 56 | } 57 | 58 | const userRequest = this._currentRequest 59 | 60 | if (!userRequest) { 61 | debug('something is strange, received a respone without a request') 62 | return 63 | } 64 | 65 | const buf = Buffer.concat([Buffer.from([response.address]), response.body.createPayload()]) 66 | debug('create crc from response', buf) 67 | 68 | const crc = CRC.crc16modbus(buf) 69 | 70 | if (response.crc !== crc) { 71 | debug('CRC does not match', response.crc, '!==', crc) 72 | userRequest.reject(new UserRequestError({ 73 | err: 'crcMismatch', 74 | message: 'the response payload does not match the crc', 75 | request: userRequest.request, 76 | response 77 | })) 78 | this._clearAllRequests() 79 | return 80 | } 81 | 82 | super.handle(response) 83 | } 84 | 85 | public get address () { 86 | return this._address 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/rtu-client-response-handler.ts: -------------------------------------------------------------------------------- 1 | import Debug = require('debug'); const debug = Debug('rtu-response-handler') 2 | import ModbusClientResponseHandler from './client-response-handler.js' 3 | import ModbusRTUResponse from './rtu-response.js' 4 | 5 | /** Modbus/RTU Client Response Handler 6 | * @extends ModbusClientResponseHandler 7 | * @class 8 | */ 9 | export default class ModbusRTUClientResponseHandler extends ModbusClientResponseHandler { 10 | protected _messages: ModbusRTUResponse[] 11 | 12 | constructor () { 13 | super() 14 | this._messages = [] 15 | } 16 | 17 | public handleData (data: Buffer) { 18 | debug('receiving new data') 19 | this._buffer = Buffer.concat([this._buffer, data]) 20 | 21 | debug('buffer', this._buffer) 22 | 23 | do { 24 | const response = ModbusRTUResponse.fromBuffer(this._buffer) 25 | 26 | if (!response) { 27 | debug('not enough data available to parse') 28 | return 29 | } 30 | 31 | debug('crc', response.crc) 32 | 33 | debug('reset buffer from', this._buffer.length, 'to', (this._buffer.length - response.byteCount)) 34 | 35 | /* reduce buffer */ 36 | this._buffer = this._buffer.slice(response.byteCount) 37 | 38 | this._messages.push(response) 39 | } while (1) 40 | } 41 | 42 | public shift () { 43 | return this._messages.shift() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/rtu-request.ts: -------------------------------------------------------------------------------- 1 | import Debug = require('debug'); const debug = Debug('rtu-request') 2 | import CRC = require('crc') 3 | import ModbusAbstractRequest from './abstract-request.js' 4 | import ModbusRequestBody from './request/request-body.js' 5 | import RequestFactory from './request/request-factory.js' 6 | 7 | /** 8 | * 9 | * 10 | * @export 11 | * @class ModbusRTURequest 12 | * @extends {ModbusAbstractRequest} 13 | */ 14 | export default class ModbusRTURequest 15 | extends ModbusAbstractRequest { 16 | 17 | get address () { 18 | return this._address 19 | } 20 | 21 | get slaveId () { 22 | return this.address 23 | } 24 | 25 | get unitId () { 26 | return this.address 27 | } 28 | 29 | get crc () { 30 | return this._crc 31 | } 32 | 33 | get name () { 34 | return this._body.name 35 | } 36 | 37 | get corrupted () { 38 | return (this._corrupted === true) 39 | } 40 | 41 | public get body () { 42 | return this._body 43 | } 44 | 45 | get byteCount () { 46 | return this.body.byteCount + 3 47 | } 48 | 49 | /** Convert a buffer into a new Modbus RTU Request. Returns null if the buffer 50 | * does not contain enough data. 51 | * @param {Buffer} buffer 52 | * @return A new Modbus RTU Request or null. 53 | */ 54 | public static fromBuffer (buffer: Buffer) { 55 | try { 56 | if (buffer.length < 1 /* address */ + 2 /* CRC */) { 57 | debug('not enough data in the buffer yet') 58 | return null 59 | } 60 | 61 | const address = buffer.readUInt8(0) 62 | 63 | debug(`rtu header complete, address, ${address}`) 64 | debug('buffer', buffer) 65 | 66 | // NOTE: This is potentially more than the body; the body length isn't know at this point... 67 | const body = RequestFactory.fromBuffer(buffer.slice(1)) 68 | 69 | if (!body) { 70 | return null 71 | } 72 | 73 | const payloadLength = 1 /* address */ + body.byteCount 74 | const expectedCrc = CRC.crc16modbus(buffer.slice(0, payloadLength)) 75 | const actualCrc = buffer.readUInt16LE(payloadLength) 76 | const corrupted = (expectedCrc !== actualCrc) 77 | 78 | return new ModbusRTURequest(address, body, corrupted) 79 | } catch (e) { 80 | debug('not enough data to create a rtu request', e) 81 | return null 82 | } 83 | } 84 | protected _address: number 85 | protected _body: ReqBody 86 | protected _corrupted: boolean 87 | protected _crc!: number 88 | /** 89 | * Creates an instance of ModbusRTURequest. 90 | * @param {number} address 91 | * @param {ReqBody} body 92 | * @param {boolean} [corrupted=false] 93 | * @memberof ModbusRTURequest 94 | */ 95 | constructor (address: number, body: ReqBody, corrupted: boolean = false) { 96 | super() 97 | this._address = address 98 | this._body = body 99 | this._corrupted = corrupted 100 | } 101 | 102 | public createPayload () { 103 | const bodyPayload = this._body.createPayload() 104 | 105 | this._crc = CRC.crc16modbus(Buffer.concat([Buffer.from([this._address]), bodyPayload])) 106 | const crBu = Buffer.alloc(2) 107 | crBu.writeUInt16LE(this._crc, 0) 108 | const idBuf = Buffer.from([this._address]) 109 | const payload = Buffer.concat([idBuf, bodyPayload, crBu]) 110 | 111 | return payload 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/rtu-response.ts: -------------------------------------------------------------------------------- 1 | import Debug = require('debug'); const debug = Debug('rtu-response') 2 | import CRC = require('crc') 3 | import ModbusAbstractResponse from './abstract-response.js' 4 | import { ModbusRequestBody } from './request/index.js' 5 | import ModbusResponseBody from './response/response-body.js' 6 | import ResponseFactory from './response/response-factory.js' 7 | import ModbusRTURequest from './rtu-request.js' 8 | 9 | export default class ModbusRTUResponse 10 | extends ModbusAbstractResponse { 11 | 12 | get address () { 13 | return this._address 14 | } 15 | 16 | get crc () { 17 | return this._crc 18 | } 19 | 20 | get body () { 21 | return this._body 22 | } 23 | 24 | get byteCount () { 25 | return this._body.byteCount + 3 26 | } 27 | 28 | get slaveId () { 29 | return this._address 30 | } 31 | 32 | get unitId () { 33 | return this._address 34 | } 35 | 36 | /** Create Modbus/RTU Response from a Modbus/RTU Request including 37 | * the modbus function body. 38 | * @param {ModbusRTURequest} request 39 | * @param {ModbusResponseBody} body 40 | * @returns {ModbusRTUResponse} 41 | */ 42 | public static fromRequest ( 43 | rtuRequest: ModbusRTURequest, 44 | modbusBody: ResBody 45 | ): ModbusRTUResponse { 46 | return new ModbusRTUResponse( 47 | rtuRequest.address, 48 | undefined, // CRC is calculated when createPayload () is called 49 | modbusBody) 50 | } 51 | 52 | public static fromBuffer (buffer: Buffer) { 53 | if (buffer.length < 1) { 54 | return null 55 | } 56 | 57 | const address = buffer.readUInt8(0) 58 | 59 | debug('address', address, 'buffer', buffer) 60 | 61 | const body = ResponseFactory.fromBuffer(buffer.slice(1)) 62 | 63 | if (!body) { 64 | return null 65 | } 66 | 67 | let crc 68 | try { 69 | crc = buffer.readUInt16LE(1 + body.byteCount) 70 | } catch (e) { 71 | debug('If NoSuchIndexException, it is probably serial and not all data has arrived') 72 | return null 73 | } 74 | 75 | return new ModbusRTUResponse(address, crc, body) 76 | } 77 | public _address: number 78 | public _crc: number | undefined 79 | protected _body: ResBody 80 | 81 | constructor (address: number, crc: number | undefined, body: ResBody) { 82 | super() 83 | this._address = address 84 | this._crc = crc 85 | this._body = body 86 | } 87 | 88 | public createPayload () { 89 | /* Payload is a buffer with: 90 | * Address/Unit ID = 1 Byte 91 | * Body = N Bytes 92 | * CRC = 2 Bytes 93 | */ 94 | const payload = Buffer.alloc(this.byteCount) 95 | payload.writeUInt8(this._address, 0) 96 | const bodyPayload = this._body.createPayload() 97 | bodyPayload.copy(payload, 1) 98 | this._crc = CRC.crc16modbus(payload.slice(0, this.byteCount - 2 /* CRC bytes */)) 99 | payload.writeUInt16LE(this._crc, this.byteCount - 2) 100 | return payload 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/tcp-client-request-handler.ts: -------------------------------------------------------------------------------- 1 | 2 | import ModbusRequestBody from './request/request-body' 3 | import ModbusTCPResponse from './tcp-response' 4 | 5 | import Debug = require('debug'); const debug = Debug('tcp-client-request-handler') 6 | import { Socket } from 'net' 7 | import MBClientRequestHandler from './client-request-handler.js' 8 | import ModbusTCPRequest from './tcp-request.js' 9 | import UserRequest from './user-request' 10 | import { UserRequestError } from './user-request-error' 11 | 12 | const OUT_OF_SYNC = 'OutOfSync' 13 | const PROTOCOL = 'Protocol' 14 | 15 | /** TCP Client Request Handler 16 | * Implements the behaviour for Client Requests for Modus/TCP. 17 | * @extends MBClientRequestHandler 18 | * @class 19 | */ 20 | export default class ModbusTCPClientRequestHandler extends MBClientRequestHandler { 21 | protected _requests: Array> 22 | protected _currentRequest: UserRequest | null | undefined 23 | private _requestId: number 24 | private _unitId: number 25 | 26 | /** 27 | * Creates an instance of ModbusTCPClientRequestHandler. 28 | * @param {Socket} socket net.Socket 29 | * @param {number} unitId Unit ID 30 | * @param {number} [timeout=5000] Timeout in ms for requests 31 | * @memberof ModbusTCPClientRequestHandler 32 | */ 33 | constructor (socket: Socket, unitId: number, timeout: number = 5000) { 34 | super(socket, timeout) 35 | this._requestId = 0 36 | this._unitId = unitId 37 | this._requests = [] 38 | this._currentRequest = null 39 | 40 | this._socket.on('connect', this._onConnect.bind(this)) 41 | this._socket.on('close', this._onClose.bind(this)) 42 | 43 | } 44 | 45 | // TODO: Find a better way then putting in the any overide 46 | public register (requestBody: T): any { 47 | this._requestId = (this._requestId + 1) % 0xFFFF 48 | debug( 49 | 'registrating new request', 50 | 'transaction id', this._requestId, 51 | 'unit id', this._unitId, 52 | 'length', requestBody.byteCount 53 | ) 54 | 55 | const tcpRequest = new ModbusTCPRequest(this._requestId, 0x00, requestBody.byteCount + 1, this._unitId, requestBody) 56 | 57 | return super.registerRequest(tcpRequest) 58 | } 59 | 60 | public handle (response: T) { 61 | if (!response) { 62 | return 63 | } 64 | 65 | const userRequest = this._currentRequest 66 | 67 | if (!userRequest) { 68 | debug('something is strange, received a respone without a request') 69 | return 70 | } 71 | 72 | const request = userRequest.request 73 | 74 | /* check if response id equals request id */ 75 | if (response.id !== request.id) { 76 | debug( 77 | 'something weird is going on, response transition id does not equal request transition id', 78 | response.id, 79 | request.id 80 | ) 81 | /* clear all request, client must be reset */ 82 | userRequest.reject(new UserRequestError({ 83 | err: OUT_OF_SYNC, 84 | message: 'request fc and response fc does not match.', 85 | request 86 | })) 87 | this._clearAllRequests() 88 | return 89 | } 90 | 91 | /* check if protocol version of response is 0x00 */ 92 | if (response.protocol !== 0x00) { 93 | debug('server responds with wrong protocol version') 94 | userRequest.reject(new UserRequestError({ 95 | err: PROTOCOL, 96 | message: 'Unknown protocol version ' + response.protocol, 97 | request 98 | })) 99 | this._clearAllRequests() 100 | return 101 | } 102 | 103 | super.handle(response) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/tcp-client-response-handler.ts: -------------------------------------------------------------------------------- 1 | import Debug = require('debug'); const debug = Debug('tcp-response-handler') 2 | import ModbusClientResponseHandler from './client-response-handler.js' 3 | import ModbusTCPResponse from './tcp-response.js' 4 | 5 | /** Modbus/TCP Client Response Handler. 6 | * @extends ModbusClientResponseHandler 7 | * @class 8 | */ 9 | export default class ModbusTCPClientResponseHandler extends ModbusClientResponseHandler { 10 | protected _messages: ModbusTCPResponse[] 11 | 12 | /** Create new Modbus/TCP Client Response Handler */ 13 | constructor () { 14 | super() 15 | this._buffer = Buffer.alloc(0) 16 | this._messages = [] 17 | } 18 | 19 | public handleData (data: Buffer) { 20 | debug('receiving new data', data) 21 | this._buffer = Buffer.concat([this._buffer, data]) 22 | 23 | debug('buffer', this._buffer) 24 | 25 | do { 26 | const response = ModbusTCPResponse.fromBuffer(this._buffer) 27 | 28 | if (!response) { 29 | debug('not enough data available to parse') 30 | return 31 | } 32 | 33 | debug( 34 | 'response id', response.id, 35 | 'protocol', response.protocol, 36 | 'length', response.bodyLength, 37 | 'unit', response.unitId 38 | ) 39 | 40 | debug('reset buffer from', this._buffer.length, 'to', (this._buffer.length - response.byteCount)) 41 | 42 | this._messages.push(response) 43 | 44 | /* reduce buffer */ 45 | this._buffer = this._buffer.slice(response.byteCount) 46 | } while (1) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/tcp-request.ts: -------------------------------------------------------------------------------- 1 | import Debug = require('debug'); const debug = Debug('tcp-request') 2 | import ModbusAbstractRequest from './abstract-request.js' 3 | import ModbusRequestBody from './request/request-body.js' 4 | import RequestFactory from './request/request-factory.js' 5 | 6 | /** Class representing a Modbus TCP Request */ 7 | export default class ModbusTCPRequest 8 | extends ModbusAbstractRequest { 9 | 10 | /** The Transaction ID */ 11 | get id () { 12 | return this._id 13 | } 14 | 15 | /** The protocol version */ 16 | get protocol () { 17 | return this._protocol 18 | } 19 | 20 | /** The body length in bytes including the unit ID */ 21 | get length () { 22 | return this._length 23 | } 24 | 25 | /** The unit ID */ 26 | get unitId () { 27 | return this._unitId 28 | } 29 | 30 | get address () { 31 | return this.unitId 32 | } 33 | 34 | get slaveId () { 35 | return this.unitId 36 | } 37 | 38 | /** get function name */ 39 | get name () { 40 | return this._body.name 41 | } 42 | 43 | public get body () { 44 | return this._body 45 | } 46 | 47 | /** For interface compatibility with ModbusRTURequest where data 48 | * integrity checking happens as part of the Modbus protocol 49 | */ 50 | get corrupted () { 51 | return false 52 | } 53 | 54 | get byteCount () { 55 | return this._length + 6 56 | } 57 | 58 | /** Convert a buffer into a new Modbus TCP Request. Returns null if the buffer 59 | * does not contain enough data. 60 | * @param {Buffer} buffer 61 | * @return A new Modbus TCP Request or Null. 62 | */ 63 | public static fromBuffer (buffer: Buffer) { 64 | try { 65 | if (buffer.length < 7) { 66 | debug('no enough data in the buffer yet') 67 | return null 68 | } 69 | 70 | const id = buffer.readUInt16BE(0) 71 | const protocol = buffer.readUInt16BE(2) 72 | const length = buffer.readUInt16BE(4) 73 | const unitId = buffer.readUInt8(6) 74 | 75 | debug('tcp header complete, id', id, 'protocol', protocol, 'length', length, 'unitId', unitId) 76 | debug('buffer', buffer) 77 | 78 | const body = RequestFactory.fromBuffer(buffer.slice(7, 6 + length)) 79 | 80 | if (!body) { 81 | return null 82 | } 83 | 84 | return new ModbusTCPRequest(id, protocol, length, unitId, body) 85 | } catch (e) { 86 | debug('not enough data to create a tcp request', e) 87 | return null 88 | } 89 | } 90 | protected _id: number 91 | protected _protocol: number 92 | protected _length: number 93 | protected _unitId: number 94 | protected _body: ReqBody 95 | 96 | /** Creates a new Modbus TCP Request 97 | * @param {number} Transaction ID 98 | * @param {number} Protocol Type 99 | * @param {number} Byte count of the following data (inc. unitId) 100 | * @param {number} Unit ID 101 | * @param {ReqBody} Actual modbus request containing function code and parameters. 102 | */ 103 | constructor (id: number, protocol: number, length: number, unitId: number, body: ReqBody) { 104 | super() 105 | this._id = id 106 | this._protocol = protocol 107 | this._length = length 108 | this._unitId = unitId 109 | this._body = body 110 | } 111 | 112 | /** Creates a buffer object representing the modbus tcp request. 113 | * @returns {Buffer} 114 | */ 115 | public createPayload () { 116 | const body = this._body.createPayload() 117 | const payload = Buffer.alloc(7 + this._body.byteCount) 118 | 119 | payload.writeUInt16BE(this._id, 0) // transaction id 120 | payload.writeUInt16BE(0x0000, 2) // protocol version 121 | payload.writeUInt16BE(this._body.byteCount + 1, 4) // length 122 | payload.writeUInt8(this._unitId, 6) // unit id 123 | 124 | body.copy(payload, 7) 125 | 126 | return payload 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/tcp-response.ts: -------------------------------------------------------------------------------- 1 | import Debug = require('debug'); const debug = Debug('tcp-response') 2 | import ModbusAbstractResponse from './abstract-response.js' 3 | import { ModbusRequestBody } from './request' 4 | import ModbusResponseBody from './response/response-body.js' 5 | import ResponseFactory from './response/response-factory.js' 6 | import ModbusTCPRequest from './tcp-request.js' 7 | 8 | /** Modbus/TCP Response 9 | * @class 10 | */ 11 | export default class ModbusTCPResponse 12 | extends ModbusAbstractResponse { 13 | 14 | /** Transaction ID */ 15 | get id () { 16 | return this._id 17 | } 18 | 19 | /** Protocol version */ 20 | get protocol () { 21 | return this._protocol 22 | } 23 | 24 | /** Body length */ 25 | get bodyLength () { 26 | return this._bodyLength 27 | } 28 | 29 | /** Payload byte count */ 30 | get byteCount () { 31 | return this._bodyLength + 6 32 | } 33 | 34 | get unitId () { 35 | return this._unitId 36 | } 37 | 38 | get slaveId () { 39 | return this._unitId 40 | } 41 | 42 | get address () { 43 | return this._unitId 44 | } 45 | 46 | /** Modbus response body */ 47 | get body () { 48 | return this._body 49 | } 50 | 51 | /** Create Modbus/TCP Response from a Modbus/TCP Request including 52 | * the modbus function body. 53 | * @param {ModbusTCPRequest} request 54 | * @param {ModbusResponseBody} body 55 | * @returns {ModbusTCPResponse} 56 | */ 57 | public static fromRequest ( 58 | tcpRequest: ModbusTCPRequest, 59 | modbusBody: ResBody 60 | ) { 61 | return new ModbusTCPResponse( 62 | tcpRequest.id, 63 | tcpRequest.protocol, 64 | modbusBody.byteCount + 1, 65 | tcpRequest.unitId, 66 | modbusBody) 67 | } 68 | 69 | /** Create Modbus/TCP Response from a buffer 70 | * @param {Buffer} buffer 71 | * @returns {ModbusTCPResponse} Returns null if not enough data located in the buffer. 72 | */ 73 | public static fromBuffer (buffer: Buffer) { 74 | try { 75 | const id = buffer.readUInt16BE(0) 76 | const protocol = buffer.readUInt16BE(2) 77 | const length = buffer.readUInt16BE(4) 78 | const unitId = buffer.readUInt8(6) 79 | 80 | debug('tcp header complete, id', id, 'protocol', protocol, 'length', length, 'unitId', unitId) 81 | debug('buffer', buffer) 82 | 83 | const body = ResponseFactory.fromBuffer(buffer.slice(7, 7 + length - 1)) 84 | 85 | if (!body) { 86 | debug('not enough data for a response body') 87 | return null 88 | } 89 | 90 | debug('buffer contains a valid response body') 91 | 92 | return new ModbusTCPResponse(id, protocol, length, unitId, body) 93 | } catch (e) { 94 | debug('not enough data available') 95 | return null 96 | } 97 | } 98 | protected _id: number 99 | protected _protocol: number 100 | protected _bodyLength: number 101 | protected _unitId: number 102 | protected _body: ResBody 103 | 104 | /** Create new Modbus/TCP Response Object. 105 | * @param {number} id Transaction ID 106 | * @param {number} protocol Protcol version (Usually 0) 107 | * @param {number} bodyLength Body length + 1 108 | * @param {number} unitId Unit ID 109 | * @param {ModbusResponseBody} body Modbus response body object 110 | */ 111 | constructor (id: number, protocol: number, bodyLength: number, unitId: number, body: ResBody) { 112 | super() 113 | this._id = id 114 | this._protocol = protocol 115 | this._bodyLength = bodyLength 116 | this._unitId = unitId 117 | this._body = body 118 | } 119 | 120 | public createPayload () { 121 | /* Payload is a buffer with: 122 | * Transaction ID = 2 Bytes 123 | * Protocol ID = 2 Bytes 124 | * Length = 2 Bytes 125 | * Unit ID = 1 Byte 126 | * Function code = 1 Byte 127 | * Byte count = 1 Byte 128 | * Coil status = n Bytes 129 | */ 130 | const payload = Buffer.alloc(this.byteCount) 131 | payload.writeUInt16BE(this._id, 0) 132 | payload.writeUInt16BE(this._protocol, 2) 133 | payload.writeUInt16BE(this._bodyLength, 4) 134 | payload.writeUInt8(this._unitId, 6) 135 | this._body.createPayload().copy(payload, 7) 136 | 137 | return payload 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/user-request-error.ts: -------------------------------------------------------------------------------- 1 | import ModbusAbstractRequest from './abstract-request' 2 | import ModbusAbstractResponse from './abstract-response' 3 | 4 | export type UserRequestErrorCodes = 'OutOfSync' | 'Protocol' | 'Timeout' | 'ManuallyCleared' | 'ModbusException' | 'Offline' | 'crcMismatch' 5 | 6 | export interface IUserRequestError { 7 | err: UserRequestErrorCodes 8 | message: string 9 | response?: Res, 10 | request?: Req 11 | } 12 | 13 | export class UserRequestError 14 | implements IUserRequestError { 15 | public err: UserRequestErrorCodes 16 | public message: string 17 | public request?: Req 18 | public response?: Res 19 | constructor ({ err, message, response, request }: IUserRequestError) { 20 | this.err = err 21 | this.message = message 22 | this.request = request 23 | this.response = response 24 | } 25 | } 26 | 27 | export function isUserRequestError (x: any): x is UserRequestError { 28 | if (x instanceof isUserRequestError) { 29 | return true 30 | } 31 | 32 | if (typeof x !== 'object') { 33 | return false 34 | } 35 | 36 | if (x.err === undefined || typeof x.err !== 'string') { 37 | return false 38 | } 39 | 40 | if (x.message === undefined || typeof x.message !== 'string') { 41 | return false 42 | } 43 | 44 | return true 45 | } 46 | -------------------------------------------------------------------------------- /src/user-request-metrics.ts: -------------------------------------------------------------------------------- 1 | // export type ModbusResponse = Either; 2 | export class UserRequestMetrics { 3 | /** 4 | * Timestamp when the request was sent 5 | */ 6 | public createdAt: Date = new Date() 7 | /** 8 | * Timestamp when the request was sent 9 | */ 10 | public startedAt: Date = new Date() 11 | /** 12 | * Timestamp when the response was received 13 | */ 14 | public receivedAt: Date = new Date() 15 | /** 16 | * Difference in the start and end date in milliseconds 17 | */ 18 | public get transferTime (): number { 19 | return this.receivedAt.getTime() - this.startedAt.getTime() 20 | } 21 | /** 22 | * Amount of time in milliseconds the request was waiting in 23 | * the cue. 24 | */ 25 | public get waitTime (): number { 26 | return this.startedAt.getTime() - this.createdAt.getTime() 27 | } 28 | public toJSON () { 29 | return { 30 | ...this, 31 | transferTime: this.transferTime 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/user-request.ts: -------------------------------------------------------------------------------- 1 | 2 | import ModbusAbstractRequest from './abstract-request' 3 | import { RequestToResponse } from './request-response-map' 4 | import ModbusRTURequest from './rtu-request' 5 | import ModbusTCPRequest from './tcp-request' 6 | import { UserRequestError } from './user-request-error' 7 | import { UserRequestMetrics } from './user-request-metrics' 8 | 9 | import Debug = require('debug'); const debug = Debug('user-request') 10 | 11 | export type ModbusRequest = ModbusTCPRequest | ModbusRTURequest 12 | export interface IUserRequestResolve { 13 | metrics: UserRequestMetrics 14 | request: Req 15 | response: RequestToResponse 16 | } 17 | 18 | export type PromiseUserRequest = Promise> 19 | 20 | /** Request created for the user. It contains the actual modbus request, 21 | * the timeout handler and the promise delivered from the readCoils method 22 | * in the client. 23 | * @export 24 | * @class UserRequest 25 | * @template ReqBody 26 | * @template ResBody 27 | */ 28 | export default class UserRequest { 29 | protected readonly _request: Req 30 | protected readonly _timeout: number 31 | protected readonly _promise: PromiseUserRequest 32 | protected _resolve!: (value: IUserRequestResolve) => void 33 | protected _reject!: (err: UserRequestError, Req>) => void 34 | protected _timer!: NodeJS.Timeout 35 | 36 | protected _metrics: UserRequestMetrics 37 | 38 | /** 39 | * Creates an instance of UserRequest. 40 | * @param {Req} request 41 | * @param {number} [timeout=5000] 42 | * @memberof UserRequest 43 | */ 44 | constructor (request: Req, timeout: number = 5000) { 45 | debug('creating new user request with timeout', timeout) 46 | this._request = request 47 | this._timeout = timeout 48 | 49 | this._metrics = new UserRequestMetrics() 50 | 51 | this._promise = new Promise>((resolve, reject) => { 52 | this._resolve = resolve 53 | this._reject = reject 54 | }) 55 | } 56 | 57 | public createPayload () { 58 | return this._request.createPayload() 59 | } 60 | 61 | public start (cb: () => void) { 62 | this._metrics.startedAt = new Date() 63 | 64 | this._timer = setTimeout(() => { 65 | this._reject(new UserRequestError({ 66 | err: 'Timeout', 67 | message: 'Req timed out', 68 | request: this._request 69 | })) 70 | cb() 71 | }, this._timeout) 72 | } 73 | 74 | public get metrics () { 75 | return this._metrics 76 | } 77 | 78 | public done () { 79 | clearTimeout(this._timer) 80 | } 81 | 82 | get request () { 83 | return this._request 84 | } 85 | 86 | get timeout () { 87 | return this._timeout 88 | } 89 | 90 | get promise () { 91 | return this._promise 92 | } 93 | 94 | public resolve (response: RequestToResponse) { 95 | this._metrics.receivedAt = new Date() 96 | debug('request completed in %d ms (sat in cue %d ms)', this.metrics.transferTime, this.metrics.waitTime) 97 | return this._resolve({ 98 | metrics: this.metrics, 99 | request: this._request, 100 | response 101 | }) 102 | } 103 | 104 | get reject () { 105 | return this._reject 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /test/heap-test-coils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const modbus = require('../') 4 | const v8 = require('v8') 5 | const net = require('net') 6 | const socket = new net.Socket() 7 | const options = { 8 | 'host': 'localhost', 9 | 'port': 8888 10 | } 11 | const client = new modbus.client.TCP(socket) 12 | 13 | const request = function () { 14 | return client.readCoils(0, 13) 15 | } 16 | 17 | socket.connect(options) 18 | 19 | socket.on('connect', function () { 20 | let p = Promise.resolve() 21 | 22 | for (let i = 1; i < 1e5; i++) { 23 | p = p.then(request) 24 | } 25 | 26 | p.then(function () { 27 | const usedHeapSize = Math.floor(v8.getHeapStatistics().used_heap_size / 1e6) 28 | 29 | console.log('Heap:', usedHeapSize, 'MB') 30 | 31 | socket.end() 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /test/read-discrete-inputs.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* global describe, it */ 4 | 5 | const assert = require('assert') 6 | const ReadDiscreteInputsRequest = require('../dist/request/read-discrete-inputs.js').default 7 | const ReadDiscreteInputsResponse = require('../dist/response/read-discrete-inputs.js').default 8 | 9 | describe('ReadDiscreteInputs Tests.', function () { 10 | describe('ReadDiscreteInputs Response', function () { 11 | it('should create a buffer from a read discrete inputs message', function () { 12 | const response = new ReadDiscreteInputsResponse([1, 0, 1, 0, 1, 0, 1, 0, 1, 0], 2) 13 | const buffer = response.createPayload() 14 | const expected = Buffer.from([0x02, 0x02, 0x55, 0x01]) 15 | 16 | assert.deepEqual(expected, buffer) 17 | }) 18 | it('should create a message object from a buffer', function () { 19 | const buffer = Buffer.from([0x02, 0x02, 0x55, 0x01]) 20 | const message = ReadDiscreteInputsResponse.fromBuffer(buffer) 21 | 22 | assert.equal(0x02, message.fc) 23 | assert.equal(0x02, message.numberOfBytes) 24 | assert.deepEqual([1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0], message.valuesAsArray) 25 | assert.deepEqual(Buffer.from([0x55, 0x01]), message.valuesAsBuffer) 26 | }) 27 | it('should return null on not enough buffer data', function () { 28 | const buffer = Buffer.from([0x02]) 29 | const message = ReadDiscreteInputsResponse.fromBuffer(buffer) 30 | 31 | assert.ok(message === null) 32 | }) 33 | it('should return null on wrong function code', function () { 34 | const buffer = Buffer.from([0x03, 0x03, 0x0a, 0x00, 0x0c]) 35 | const message = ReadDiscreteInputsResponse.fromBuffer(buffer) 36 | 37 | assert.ok(message === null) 38 | }) 39 | }) 40 | 41 | describe('ReadDiscreteInputs Requests', function () { 42 | it('should create a buffer from a discrete inputs message', function () { 43 | const readDiscreteInputsRequest = new ReadDiscreteInputsRequest(22, 33) 44 | const buffer = readDiscreteInputsRequest.createPayload() 45 | const expected = Buffer.from([0x02, 0x00, 0x16, 0x00, 0x21]) 46 | 47 | assert.deepEqual(expected, buffer) 48 | }) 49 | it('should create a message from a buffer', function () { 50 | const buffer = Buffer.from([0x02, 0x00, 0x16, 0x00, 0x21]) 51 | const message = ReadDiscreteInputsRequest.fromBuffer(buffer) 52 | 53 | assert.ok(message !== null) 54 | assert.equal(0x02, message.fc) 55 | assert.equal(22, message.start) 56 | assert.equal(33, message.count) 57 | }) 58 | it('should return null on not enough buffer data', function () { 59 | const buffer = Buffer.from([0x02, 0x00]) 60 | const message = ReadDiscreteInputsRequest.fromBuffer(buffer) 61 | 62 | assert.ok(message === null) 63 | }) 64 | it('should return null on wrong function code', function () { 65 | const buffer = Buffer.from([0x03, 0x00, 0x0a, 0x00, 0x0c]) 66 | const message = ReadDiscreteInputsRequest.fromBuffer(buffer) 67 | 68 | assert.ok(message === null) 69 | }) 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /test/read-holding-registers.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* global describe, it */ 4 | 5 | const assert = require('assert') 6 | const ReadHoldingRegistersRequest = require('../dist/request/read-holding-registers.js').default 7 | const ReadHoldingRegistersResponse = require('../dist/response/read-holding-registers.js').default 8 | 9 | describe('ReadHoldingRegisters Tests.', function () { 10 | describe('ReadHoldingRegisters Response', function () { 11 | it('should create a response from a buffer', function () { 12 | const request = new ReadHoldingRegistersRequest(0, 1) 13 | const holdingRegisters = Buffer.from([0x01, 0x00, 0x02, 0x00, 0xFF, 0xFF]) 14 | const response = ReadHoldingRegistersResponse.fromRequest(request, holdingRegisters) 15 | const respPayload = response.createPayload() 16 | const expected = Buffer.from([0x03, 0x02, 0x01, 0x00]) 17 | 18 | assert.deepEqual(respPayload, expected) 19 | }) 20 | it('should create a response with constructor from array', function () { 21 | const response = new ReadHoldingRegistersResponse(6, [0x01, 0x02, 0xFFFE]) 22 | const respPayload = response.createPayload() 23 | const expected = Buffer.from([0x03, 0x06, 0x00, 0x01, 0x00, 0x02, 0xFF, 0xFE]) 24 | 25 | assert.deepEqual(respPayload, expected) 26 | }) 27 | }) 28 | describe('ReadHoldingRegisters Request', function () { 29 | it('should create a buffer from a read holding registers message', function () { 30 | const request = new ReadHoldingRegistersRequest(22, 33) 31 | const buffer = request.createPayload() 32 | const expected = Buffer.from([0x03, 0x00, 0x16, 0x00, 0x21]) 33 | 34 | assert.deepEqual(expected, buffer) 35 | }) 36 | it('should create a message from a buffer', function () { 37 | const buffer = Buffer.from([0x03, 0x00, 0x16, 0x00, 0x21]) 38 | const message = ReadHoldingRegistersRequest.fromBuffer(buffer) 39 | 40 | assert.ok(message !== null) 41 | assert.equal(0x03, message.fc) 42 | assert.equal(22, message.start) 43 | assert.equal(33, message.count) 44 | }) 45 | it('should return null on not enough buffer data', function () { 46 | const buffer = Buffer.from([0x03, 0x00]) 47 | const message = ReadHoldingRegistersRequest.fromBuffer(buffer) 48 | 49 | assert.ok(message === null) 50 | }) 51 | it('should return null on wrong function code', function () { 52 | const buffer = Buffer.from([0x04, 0x00, 0x0a, 0x00, 0x0c]) 53 | const message = ReadHoldingRegistersRequest.fromBuffer(buffer) 54 | 55 | assert.ok(message === null) 56 | }) 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /test/read-input-registers.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* global describe, it */ 4 | 5 | const assert = require('assert') 6 | const ReadInputRegistersRequest = require('../dist/request/read-input-registers.js').default 7 | const ReadInputRegistersResponse = require('../dist/response/read-input-registers.js').default 8 | 9 | describe('ReadInputRegisters Tests.', function () { 10 | describe('ReadInputRegisters Response', function () { 11 | it('should create a response from a buffer', function () { 12 | const request = new ReadInputRegistersRequest(0, 1) 13 | const inputRegisters = Buffer.from([0x01, 0x00, 0x02, 0x00, 0xFF, 0xFF]) 14 | const response = ReadInputRegistersResponse.fromRequest(request, inputRegisters) 15 | const respPayload = response.createPayload() 16 | const expected = Buffer.from([0x04, 0x02, 0x01, 0x00]) 17 | 18 | assert.deepEqual(respPayload, expected) 19 | }) 20 | it('should create a response with constructor from array', function () { 21 | const response = new ReadInputRegistersResponse(6, [0x01, 0x02, 0xFFFE]) 22 | const respPayload = response.createPayload() 23 | const expected = Buffer.from([0x04, 0x06, 0x00, 0x01, 0x00, 0x02, 0xFF, 0xFE]) 24 | 25 | assert.deepEqual(respPayload, expected) 26 | }) 27 | }) 28 | describe('ReadInputRegisters Request', function () { 29 | it('should create a buffer from a read input registers message', function () { 30 | const request = new ReadInputRegistersRequest(22, 33) 31 | const buffer = request.createPayload() 32 | const expected = Buffer.from([0x04, 0x00, 0x16, 0x00, 0x21]) 33 | 34 | assert.deepEqual(expected, buffer) 35 | }) 36 | it('should create a message from a buffer', function () { 37 | const buffer = Buffer.from([0x04, 0x00, 0x16, 0x00, 0x21]) 38 | const message = ReadInputRegistersRequest.fromBuffer(buffer) 39 | 40 | assert.ok(message !== null) 41 | assert.equal(0x04, message.fc) 42 | assert.equal(22, message.start) 43 | assert.equal(33, message.count) 44 | }) 45 | it('should return null on not enough buffer data', function () { 46 | const buffer = Buffer.from([0x04, 0x00]) 47 | const message = ReadInputRegistersRequest.fromBuffer(buffer) 48 | 49 | assert.ok(message === null) 50 | }) 51 | it('should return null on wrong function code', function () { 52 | const buffer = Buffer.from([0x05, 0x00, 0x0a, 0x00, 0x0c]) 53 | const message = ReadInputRegistersRequest.fromBuffer(buffer) 54 | 55 | assert.ok(message === null) 56 | }) 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /test/response-body.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* global describe, it */ 4 | 5 | const assert = require('assert') 6 | const ResponseFactory = require('../dist/response/response-factory.js').default 7 | const ReadCoilsResponseBody = require('../dist/response/read-coils.js').default 8 | 9 | describe('Modbus Response Tests.', function () { 10 | /* with the read coils tests we test most of the common errors 11 | * like modbus exceptions, outOfSync errors, timeouts and so on */ 12 | describe('Read Coils Tests.', function () { 13 | it('should create request from buffer', function () { 14 | const buffer = Buffer.from([ 15 | 0x01, // fc 16 | 0x02, // byte count 17 | 0xdd, // coils 18 | 0x00 19 | ]) 20 | 21 | const response = ResponseFactory.fromBuffer(buffer) 22 | 23 | assert.ok(response !== null) 24 | assert.ok(response instanceof ReadCoilsResponseBody) 25 | assert.equal(0x01, response.fc) 26 | assert.equal(0x02, response.numberOfBytes) 27 | assert.equal(0x04, response.byteCount) 28 | assert.deepEqual( 29 | [true, 30 | false, 31 | true, 32 | true, 33 | true, 34 | false, 35 | true, 36 | true, 37 | false, 38 | false, 39 | false, 40 | false, 41 | false, 42 | false, 43 | false, 44 | false], response.valuesAsArray) 45 | }) 46 | it('should handle invalid buffer content', function () { 47 | const buffer = Buffer.from([ 48 | 0x01, // fc 49 | 0x02, // byte count 50 | 0xdd // coils 51 | ]) 52 | 53 | const response = ReadCoilsResponseBody.fromBuffer(buffer) 54 | 55 | assert.ok(response === null) 56 | }) 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /test/rtu-client-request-handler.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* global describe, it, beforeEach */ 4 | const assert = require('assert') 5 | const sinon = require('sinon') 6 | const EventEmitter = require('events') 7 | const ReadCoilsRequest = require('../dist/request/read-holding-registers.js').default 8 | const ReadHoldingRegistersResponseBody = require('../dist/response/read-holding-registers.js').default 9 | const ReadHoldingRegistersRequestBody = require('../dist/request/read-holding-registers.js').default 10 | const ModbusRTUResponse = require('../dist/rtu-response.js').default 11 | const ExceptionResponse = require('../dist/response/exception.js').default 12 | const ModbusRTUClientRequestHandler = require('../dist/rtu-client-request-handler.js').default 13 | const Modbus = require('../dist/modbus.js') 14 | 15 | 16 | describe('Modbus/RTU Client Request Tests', function () { 17 | let socket 18 | let socketMock 19 | 20 | beforeEach(function () { 21 | socket = new EventEmitter() 22 | socket.write = function () {} 23 | 24 | socketMock = sinon.mock(socket) 25 | }) 26 | 27 | /* we are using the read coils function to test the rtu request specifics */ 28 | describe('Register Test.', function () { 29 | it('should register an rtu request', function () { 30 | const handler = new ModbusRTUClientRequestHandler(socket, 4) 31 | const readCoilsRequest = new ReadCoilsRequest(0x4321, 0x0120) 32 | 33 | socket.emit('open') 34 | 35 | socketMock.expects('write').once() 36 | 37 | const promise = handler.register(readCoilsRequest) 38 | 39 | assert.ok(promise instanceof Promise) 40 | 41 | socketMock.verify() 42 | }) 43 | }) 44 | 45 | describe('Handle Data Tests.', function () { 46 | it('should register an rtu request and handle a response', function (done) { 47 | const handler = new ModbusRTUClientRequestHandler(socket, 1) 48 | const request = new ReadHoldingRegistersRequestBody(0, 1) 49 | const response = new ReadHoldingRegistersResponseBody(1, Buffer.from([0x00, 0x32])) 50 | const rtuResponse = new ModbusRTUResponse(1, 0x91C9, response) 51 | 52 | socket.emit('open') 53 | 54 | socketMock.expects('write').once() 55 | 56 | handler.register(request) 57 | .then(function (resp) { 58 | assert.ok(true) 59 | socketMock.verify() 60 | 61 | done() 62 | }).catch(function () { 63 | assert.ok(false) 64 | done() 65 | }) 66 | 67 | handler.handle(rtuResponse) 68 | }) 69 | it('should register an rtu request and handle a exception response', function (done) { 70 | const handler = new ModbusRTUClientRequestHandler(socket, 4) 71 | const request = new ReadCoilsRequest(0x0000, 0x0008) 72 | const response = new ExceptionResponse(0x01, 0x01) 73 | const rtuResponse = new ModbusRTUResponse(4, 37265, response) 74 | 75 | socket.emit('open') 76 | 77 | socketMock.expects('write').once() 78 | 79 | handler.register(request) 80 | .then(function (resp) { 81 | assert.ok(false) 82 | 83 | done() 84 | }).catch(function (err) { 85 | // Exception type should be ModbusException not crcMismatch or any other 86 | assert.equal(err.err, 'ModbusException') 87 | assert.equal(err.request instanceof Modbus.ModbusRTURequest, true) 88 | assert.equal(err.request.body, request) 89 | socketMock.verify() 90 | 91 | done() 92 | }) 93 | 94 | handler.handle(rtuResponse) 95 | }) 96 | 97 | it('should calculate exception response crc correctly', function (done) { 98 | const handler = new ModbusRTUClientRequestHandler(socket, 1) 99 | const request = new ReadHoldingRegistersRequestBody(0x4000, 0x0002) 100 | const responseBuffer = Buffer.from([ 101 | 0x01, // address 102 | 0x83, // fc 103 | 0x02, // error code 104 | 0xc0, 0xf1 // crc 105 | ]) 106 | const rtuResponse = ModbusRTUResponse.fromBuffer(responseBuffer) 107 | 108 | socket.emit('open') 109 | 110 | socketMock.expects('write').once() 111 | 112 | handler.register(request) 113 | .then(function (resp) { 114 | assert.ok(false) 115 | 116 | done() 117 | }).catch(function (err) { 118 | // Exception type should be ModbusException not crcMismatch or any other 119 | assert.equal(err.err, 'ModbusException') 120 | assert.equal(err.request instanceof Modbus.ModbusRTURequest, true) 121 | assert.equal(err.request.body, request) 122 | socketMock.verify() 123 | 124 | done() 125 | }) 126 | 127 | handler.handle(rtuResponse) 128 | // rtuResponse.crc 129 | 130 | }) 131 | }) 132 | }) 133 | -------------------------------------------------------------------------------- /test/rtu-client-response-handler.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* global describe, it, beforeEach */ 4 | 5 | const assert = require('assert') 6 | const ModbusRTUClientResponseHandler = require('../dist/rtu-client-response-handler.js').default 7 | 8 | describe('Modbus/RTU Client Response Tests', function () { 9 | let handler 10 | 11 | beforeEach(function () { 12 | handler = new ModbusRTUClientResponseHandler() 13 | }) 14 | 15 | /* we are using the read coils function to test rtu specifics */ 16 | it('should handle a valid read coils response', function () { 17 | const responseBuffer = Buffer.from([ 18 | 0x01, // address 19 | 0x01, // function code 20 | 0x02, // byte count 21 | 0xdd, // coils 22 | 0x00, 23 | 0xCD, 0xAB // crc 24 | ]) 25 | 26 | handler.handleData(responseBuffer) 27 | 28 | const response = handler.shift() 29 | 30 | assert.ok(response !== null) 31 | assert.equal(1, response.address) 32 | assert.equal(1, response.body.fc) 33 | assert.equal(0xABCD, response.crc) 34 | assert.equal(7, response.byteCount) 35 | 36 | assert.equal(1, response.body.fc) 37 | assert.deepEqual([1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], response.body.valuesAsArray) 38 | }) 39 | it('should handle a exception', function () { 40 | const responseBuffer = Buffer.from([ 41 | 0x01, // address 42 | 0x81, // exception code for fc 0x01 43 | 0x01, // exception code ILLEGAL FUNCTION 44 | 0x00, 0x00 // crc 45 | ]) 46 | 47 | handler.handleData(responseBuffer) 48 | 49 | const response = handler.shift() 50 | 51 | assert.ok(response !== undefined) 52 | assert.equal(0x01, response.address) 53 | assert.equal(0x01, response.body.fc) 54 | assert.equal(0x01, response.body.code) 55 | assert.equal('ILLEGAL FUNCTION', response.body.message) 56 | }) 57 | it('should handle a chopped response', function () { 58 | const responseBufferA = Buffer.from([ 59 | 0x01 // address 60 | ]) 61 | const responseBufferB = Buffer.from([ 62 | 0x01, // function code 63 | 0x02, // byte count 64 | 0xdd, // coils 65 | 0x00, 66 | 0x00, 0x00 // crc 67 | ]) 68 | 69 | handler.handleData(responseBufferA) 70 | 71 | let response = handler.shift() 72 | 73 | assert.ok(response === undefined) 74 | 75 | handler.handleData(responseBufferB) 76 | 77 | response = handler.shift() 78 | 79 | assert.ok(response !== undefined) 80 | assert.equal(1, response.address) 81 | assert.equal(1, response.body.fc) 82 | assert.deepEqual([1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], response.body.valuesAsArray) 83 | }) 84 | }) 85 | -------------------------------------------------------------------------------- /test/tcp-request-handler.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* global describe, it, beforeEach */ 4 | const assert = require('assert') 5 | const sinon = require('sinon') 6 | const EventEmitter = require('events') 7 | const TCPRequestHandler = require('../dist/tcp-client-request-handler.js').default 8 | const ReadCoilsRequest = require('../dist/request/read-coils.js').default 9 | 10 | describe('TCP Modbus Request Tests', function () { 11 | let socket 12 | let socketMock 13 | 14 | beforeEach(function () { 15 | socket = new EventEmitter() 16 | socket.write = function () {} 17 | 18 | socketMock = sinon.mock(socket) 19 | }) 20 | 21 | /* we are using the read coils request function to test tcp-requests. */ 22 | it('should write a tcp request.', function () { 23 | const handler = new TCPRequestHandler(socket, 3) 24 | const readCoilsRequest = new ReadCoilsRequest(0xa0fa, 0x0120) 25 | const requestBuffer = Buffer.from([0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x03, 0x01, 0xa0, 0xfa, 0x01, 0x20]) 26 | 27 | socket.emit('connect') 28 | 29 | socketMock.expects('write').once().withArgs(requestBuffer).yields() 30 | 31 | /* should flush the request right away */ 32 | const promise = handler.register(readCoilsRequest) 33 | 34 | assert.ok(promise instanceof Promise) 35 | 36 | socketMock.verify() 37 | }) 38 | }) 39 | 40 | process.on('unhandledRejection', function (err) { 41 | console.error(err) 42 | }) 43 | -------------------------------------------------------------------------------- /test/tcp-request.test.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | 'use strict' 3 | 4 | const assert = require('assert') 5 | const TCPRequest = require('../dist/tcp-request.js').default 6 | 7 | describe('TCP Request Tests', function () { 8 | it('should return a valid TCPRequest object for function 15', function () { 9 | const requestBuffer = Buffer.from([ 10 | 0x00, 0x01, // transaction id 11 | 0x00, 0x00, // protocol 12 | 0x00, 0x09, // byte count 13 | 0x02, // unit id 14 | 0x0F, // function code 15 | 0x00, 0x00, // address 16 | 0x00, 0x08, // quantity 17 | 0x02, // byte count 18 | 0x55, 0x55 // values 19 | ]) 20 | 21 | const request = TCPRequest.fromBuffer(requestBuffer) 22 | assert.ok(request) 23 | assert.equal(request.id, 0x0001) 24 | assert.equal(request.protocol, 0x0000) 25 | assert.equal(request.length, 0x0009) 26 | assert.equal(request.unitId, 0x02) 27 | assert.equal(request.body.fc, 0x0F) 28 | assert.equal(request.body.address, 0x0000) 29 | assert.deepEqual(request.body.valuesAsArray, [1, 0, 1, 0, 1, 0, 1, 0]) 30 | assert.deepEqual(request.body.valuesAsBuffer, Buffer.from([0x55, 0x55])) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /test/tcp-response-handler.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* global describe, it, beforeEach */ 4 | 5 | const assert = require('assert') 6 | const TCPResponseHandler = require('../dist/tcp-client-response-handler.js').default 7 | 8 | describe('Modbus/TCP Client Response Handler Tests', function () { 9 | let handler 10 | 11 | beforeEach(function () { 12 | handler = new TCPResponseHandler() 13 | }) 14 | 15 | /* we are using the read coils function to test the modbus/tcp specifics */ 16 | 17 | it('should handle a valid read coils response', function () { 18 | const responseBuffer = Buffer.from([ 19 | 0x00, 0x01, // transaction id 20 | 0x00, 0x00, // protocol 21 | 0x00, 0x05, // byte count 22 | 0x03, // unit id 23 | 0x01, // function code 24 | 0x02, // byte count 25 | 0xdd, // coils 26 | 0x00 27 | ]) 28 | 29 | handler.handleData(responseBuffer) 30 | 31 | const response = handler.shift() 32 | 33 | assert.ok(response !== null) 34 | assert.equal(1, response.id) 35 | assert.equal(0, response.protocol) 36 | assert.equal(5, response.bodyLength) 37 | assert.equal(11, response.byteCount) 38 | assert.equal(3, response.unitId) 39 | assert.equal(1, response.body.fc) 40 | assert.deepEqual([1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], response.body.valuesAsArray) 41 | }) 42 | it('should handle a exception', function () { 43 | const responseBuffer = Buffer.from([ 44 | 0x00, 0x01, // transaction id 45 | 0x00, 0x00, // protocol 46 | 0x00, 0x03, // byte count 47 | 0x03, // unit id 48 | 0x81, // exception code for fc 0x01 49 | 0x01 // exception code ILLEGAL FUNCTION 50 | ]) 51 | 52 | handler.handleData(responseBuffer) 53 | 54 | const response = handler.shift() 55 | 56 | assert.ok(response !== undefined) 57 | assert.equal(0x01, response.id) 58 | assert.equal(0x00, response.protocol) 59 | assert.equal(0x03, response.bodyLength) 60 | assert.equal(0x09, response.byteCount) 61 | assert.equal(0x03, response.unitId) 62 | assert.equal(0x01, response.body.fc) 63 | assert.equal(0x01, response.body.code) 64 | assert.equal('ILLEGAL FUNCTION', response.body.message) 65 | }) 66 | it('should handle a chopped response', function () { 67 | const responseBufferA = Buffer.from([ 68 | 0x00, 0x01, // transaction id 69 | 0x00, 0x00, // protocol 70 | 0x00, 0x05 // byte count 71 | ]) 72 | const responseBufferB = Buffer.from([ 73 | 0x03, // unit id 74 | 0x01, // function code 75 | 0x02, // byte count 76 | 0xdd, // coils 77 | 0x00 78 | ]) 79 | 80 | /* deliver first part */ 81 | handler.handleData(responseBufferA) 82 | 83 | let response = handler.shift() 84 | 85 | assert.ok(response === undefined) 86 | 87 | /* deliver second part */ 88 | handler.handleData(responseBufferB) 89 | 90 | response = handler.shift() 91 | 92 | assert.ok(response !== undefined) 93 | assert.equal(1, response.id) 94 | assert.equal(0, response.protocol) 95 | assert.equal(5, response.bodyLength) 96 | assert.equal(11, response.byteCount) 97 | assert.equal(3, response.unitId) 98 | assert.equal(1, response.body.fc) 99 | assert.deepEqual([1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], response.body.valuesAsArray) 100 | }) 101 | }) 102 | -------------------------------------------------------------------------------- /test/write-multiple-coils.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* global describe, it */ 4 | 5 | const assert = require('assert') 6 | const WriteMultipleCoilsRequest = require('../dist/request/write-multiple-coils.js').default 7 | 8 | describe('WriteMultipleCoils Tests.', function () { 9 | describe('WriteMultipleCoils Response', function () { 10 | 11 | }) 12 | 13 | describe('WriteMultipleCoils Request', function () { 14 | it('should create a buffer from a write multiple coils message', function () { 15 | const request = new WriteMultipleCoilsRequest(10, [1, 0, 1, 0, 0, 1, 0, 1, 1]) 16 | const buffer = request.createPayload() 17 | const expected = Buffer.from([0x0F, 0x00, 0x0a, 0x00, 0x09, 0x02, 0xa5, 0x01]) 18 | 19 | assert.ok(request !== null) 20 | assert.ok(request.numberOfBytes, 2) 21 | 22 | assert.deepEqual(expected, buffer) 23 | }) 24 | it('should create a message from a buffer', function () { 25 | const buffer = Buffer.from([0x0f, 0x00, 0x0a, 0x00, 0x09, 0x02, 0xa5, 0x01]) 26 | const message = WriteMultipleCoilsRequest.fromBuffer(buffer) 27 | 28 | assert.ok(message !== null) 29 | assert.equal(0x0f, message.fc) 30 | assert.equal(10, message.address) 31 | assert.equal(0x02, message.numberOfBytes) 32 | assert.deepEqual([1, 0, 1, 0, 0, 1, 0, 1, 1], message.valuesAsArray) 33 | assert.deepEqual(Buffer.from([0xa5, 0x01]), message.valuesAsBuffer) 34 | }) 35 | it('should return null on not enough buffer data', function () { 36 | const buffer = Buffer.from([0x0f, 0x00]) 37 | const message = WriteMultipleCoilsRequest.fromBuffer(buffer) 38 | 39 | assert.ok(message === null) 40 | }) 41 | it('should return null on wrong function code', function () { 42 | const buffer = Buffer.from([0x10, 0x00, 0x0a, 0xff, 0x00]) 43 | const message = WriteMultipleCoilsRequest.fromBuffer(buffer) 44 | 45 | assert.ok(message === null) 46 | }) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /test/write-multiple-registers.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* global describe, it */ 4 | 5 | const assert = require('assert') 6 | const WriteMultipleRegistersRequest = require('../dist/request/write-multiple-registers.js').default 7 | 8 | describe('WriteMultipleRegisters Tests.', function () { 9 | describe('WriteMultipleRegisters Response', function () { 10 | 11 | }) 12 | 13 | describe('WriteMultipleRegisters Request', function () { 14 | it('should create a buffer from a write multiple registers message', function () { 15 | const request = new WriteMultipleRegistersRequest(10, [0x0001, 0x0002, 0x1234, 0x4321]) 16 | const buffer = request.createPayload() 17 | const expected = Buffer.from([0x10, 0x00, 0x0a, 0x00, 0x04, 0x08, 0x00, 0x01, 0x00, 0x02, 0x12, 0x34, 0x43, 0x21]) 18 | 19 | assert.ok(request !== null) 20 | assert.ok(request.numberOfBytes, 8) 21 | 22 | assert.deepEqual(expected, buffer) 23 | }) 24 | it('should create a message from a buffer', function () { 25 | const buffer = Buffer.from([0x10, 0x00, 0x0a, 0x00, 0x04, 0x08, 0x00, 0x01, 0x00, 0x02, 0x12, 0x34, 0x43, 0x21]) 26 | const message = WriteMultipleRegistersRequest.fromBuffer(buffer) 27 | 28 | assert.ok(message !== null) 29 | assert.equal(0x10, message.fc) 30 | assert.equal(10, message.address) 31 | assert.equal(0x08, message.numberOfBytes) 32 | assert.deepEqual([0x0001, 0x0002, 0x1234, 0x4321], message.valuesAsArray) 33 | assert.deepEqual(Buffer.from([0x00, 0x01, 0x00, 0x02, 0x12, 0x34, 0x43, 0x21]), message.valuesAsBuffer) 34 | }) 35 | it('should return null on not enough buffer data', function () { 36 | const buffer = Buffer.from([0x0f, 0x00]) 37 | const message = WriteMultipleRegistersRequest.fromBuffer(buffer) 38 | 39 | assert.ok(message === null) 40 | }) 41 | it('should return null on wrong function code', function () { 42 | const buffer = Buffer.from([0x11, 0x00, 0x0a, 0xff, 0x00]) 43 | const message = WriteMultipleRegistersRequest.fromBuffer(buffer) 44 | 45 | assert.ok(message === null) 46 | }) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /test/write-single-coil.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* global describe, it */ 4 | 5 | const assert = require('assert') 6 | const WriteSingleCoilRequest = require('../dist/request/write-single-coil.js').default 7 | const WriteSingleCoilResponseBody = require("../dist/response/write-single-coil.js").default; 8 | 9 | describe('WriteSingleCoil Tests.', function () { 10 | describe('WriteSingleCoil Response', function () { 11 | it('should create a buffer from a write single coil message, true test', function () { 12 | const request = new WriteSingleCoilRequest(10, true) 13 | const buffer = request.createPayload() 14 | const expected = Buffer.from([0x05, 0x00, 0x0a, 0xff, 0x00]) 15 | 16 | assert.deepEqual(expected, buffer) 17 | }) 18 | it('should create a buffer from a write single coil message, false test', function () { 19 | const request = new WriteSingleCoilRequest(10, false) 20 | const buffer = request.createPayload() 21 | const expected = Buffer.from([0x05, 0x00, 0x0a, 0x00, 0x00]) 22 | 23 | assert.deepEqual(expected, buffer) 24 | }) 25 | it('should provide a response that equals the write request, true test', function () { 26 | const expected = Buffer.from([0x05, 0x00, 0x0a, 0xff, 0x00]) 27 | 28 | const request = new WriteSingleCoilRequest(10, true) 29 | const requestBuffer = request.createPayload() 30 | const response = WriteSingleCoilResponseBody.fromBuffer(requestBuffer) 31 | const responseBuffer = response.createPayload() 32 | 33 | assert.strictEqual(true, response.value) 34 | assert.deepEqual(expected, responseBuffer) 35 | }) 36 | it('should provide a response that equals the write request, false test', function () { 37 | const expected = Buffer.from([0x05, 0x00, 0x0a, 0x00, 0x00]) 38 | 39 | const request = new WriteSingleCoilRequest(10, false) 40 | const requestBuffer = request.createPayload() 41 | const response = WriteSingleCoilResponseBody.fromBuffer(requestBuffer) 42 | const responseBuffer = response.createPayload() 43 | 44 | assert.strictEqual(false, response.value) 45 | assert.deepEqual(expected, responseBuffer) 46 | }) 47 | it('should create a message from a buffer', function () { 48 | const buffer = Buffer.from([0x05, 0x00, 0x0a, 0xff, 0x00]) 49 | const message = WriteSingleCoilRequest.fromBuffer(buffer) 50 | 51 | assert.ok(message !== null) 52 | assert.equal(0x05, message.fc) 53 | assert.equal(10, message.address) 54 | assert.equal(0xff00, message.value) 55 | }) 56 | it('should return null on not enough buffer data', function () { 57 | const buffer = Buffer.from([0x05, 0x00]) 58 | const message = WriteSingleCoilRequest.fromBuffer(buffer) 59 | 60 | assert.ok(message === null) 61 | }) 62 | it('should return null on wrong function code', function () { 63 | const buffer = Buffer.from([0x06, 0x00, 0x0a, 0xff, 0x00]) 64 | const message = WriteSingleCoilRequest.fromBuffer(buffer) 65 | 66 | assert.ok(message === null) 67 | }) 68 | }) 69 | 70 | describe('WriteSingleCoil Request', function () { 71 | 72 | }) 73 | }) 74 | -------------------------------------------------------------------------------- /test/write-single-register.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* global describe, it */ 4 | 5 | const assert = require('assert') 6 | const WriteSingleRegisterRequest = require('../dist/request/write-single-register.js').default 7 | 8 | describe('WriteSingleRegister Tests.', function () { 9 | describe('WriteSingleRegister Response', function () { 10 | 11 | }) 12 | 13 | describe('WriteSingleRegister Request', function () { 14 | it('should create a buffer from a write single register message', function () { 15 | const request = new WriteSingleRegisterRequest(10, 0x1234) 16 | const buffer = request.createPayload() 17 | const expected = Buffer.from([0x06, 0x00, 0x0a, 0x12, 0x34]) 18 | 19 | assert.deepEqual(expected, buffer) 20 | }) 21 | it('should create a message from a buffer', function () { 22 | const buffer = Buffer.from([0x06, 0x00, 0x0a, 0x12, 0x34]) 23 | const message = WriteSingleRegisterRequest.fromBuffer(buffer) 24 | 25 | assert.ok(message !== null) 26 | assert.equal(0x06, message.fc) 27 | assert.equal(10, message.address) 28 | assert.equal(0x1234, message.value) 29 | }) 30 | it('should return null on not enough buffer data', function () { 31 | const buffer = Buffer.from([0x05, 0x00]) 32 | const message = WriteSingleRegisterRequest.fromBuffer(buffer) 33 | 34 | assert.ok(message === null) 35 | }) 36 | it('should return null on wrong function code', function () { 37 | const buffer = Buffer.from([0x07, 0x00, 0x0a, 0xff, 0x00]) 38 | const message = WriteSingleRegisterRequest.fromBuffer(buffer) 39 | 40 | assert.ok(message === null) 41 | }) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es6", 5 | /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 6 | "module": "commonjs", 7 | /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 8 | // "lib": [], /* Specify library files to be included in the compilation. */ 9 | "allowJs": false, 10 | /* Allow javascript files to be compiled. */ 11 | "checkJs": false, 12 | /* Report errors in .js files. */ 13 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 14 | "declaration": true, 15 | /* Generates corresponding '.d.ts' file. */ 16 | "declarationMap": true, 17 | /* Generates a sourcemap for each corresponding '.d.ts' file. */ 18 | // "sourceMap": true, 19 | /* Generates corresponding '.map' file. */ 20 | // "outFile": "./", /* Concatenate and emit output to single file. */ 21 | "outDir": "dist", 22 | /* Redirect output structure to the directory. */ 23 | // "composite": true, /* Enable project compilation */ 24 | "removeComments": true, 25 | /* Do not emit comments to output. */ 26 | // "noEmit": true, /* Do not emit outputs. */ 27 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 28 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 29 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 30 | 31 | /* Strict Type-Checking Options */ 32 | "strict": true, 33 | /* Enable all strict type-checking options. */ 34 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 35 | // "strictNullChecks": true, /* Enable strict null checks. */ 36 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 37 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 38 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 39 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 40 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 41 | 42 | /* Additional Checks */ 43 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 44 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 45 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 46 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 47 | 48 | /* Module Resolution Options */ 49 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 50 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 51 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 52 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 53 | // "typeRoots": [], /* List of folders to include type definitions from. */ 54 | "types": [ 55 | "node", 56 | "crc", 57 | "mocha" 58 | ], 59 | /* Type declaration files to be included in compilation. */ 60 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 61 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 62 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 63 | 64 | /* Source Map Options */ 65 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 66 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 67 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 68 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 69 | 70 | /* Experimental Options */ 71 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 72 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 73 | }, 74 | "include": [ 75 | "src/**/*", 76 | "types/**/*.ts" 77 | ], 78 | "exclude": [ 79 | "node_modules", 80 | "!node_modules/@types" 81 | ] 82 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:recommended", 4 | "tslint-config-standard" 5 | ], 6 | "rules":{ 7 | "unified-signatures":false, 8 | "no-bitwise":false 9 | } 10 | } --------------------------------------------------------------------------------