morkai / h5.modbus

Implementation of the MODBUS IP/ASCII/RTU master and slave over TCP/UDP/Serial for Node.js.
https://miracle.systems/p/h5.modbus
MIT License
28 stars 21 forks source link

Master Slave RTU over serial port #21

Closed ekawahyu closed 6 years ago

ekawahyu commented 6 years ago

I am trying to write the code to talk Modbus RTU over the serial port with no success. I posted an issue here since I could not find any documentation somehow? Here are my master.js and slave.js code and the errors I got:

master.js:

var SerialPort = require('serialport');
var modbus = require('h5.modbus');

var serialPort = new SerialPort('/dev/tty.virtual1', {
  baudRate: 9600
});

const master = new modbus.Master({
  transport: {
    type: 'rtu',
    eofTimeout: 10, // End of frame timeout
  },
  connection: {
    type: 'serial',
    serialPort: serialPort
  }
});

master.once('open', function()
{
  console.log('Connected!');

  const t = master.readHoldingRegisters(0x0000, 10);

  t.on('error', err => console.error(err.message));
  t.on('response', res => console.log(res.toString()));
});

master.on('error', (err) => console.log('master#error: %s', err.message));

slave.js:

var SerialPort = require('serialport');
var modbus = require('h5.modbus');

var serialPort = new SerialPort('/dev/tty.virtual0', {
  baudRate: 9600
});

const slave = new modbus.Slave({
  requestHandler: handleRequest,
  transport: {
    type: 'rtu',
    eofTimeout: 10, // End of frame timeout
  },
  listener: {
    type: 'serial',
    serialPort: serialPort
  }
});

function handleRequest(unit, request, respond)
{
 respond(modbus.ExceptionCode.IllegalFunctionCode);
}

slave.on('error', (err) => console.log('slave#error: %s', err.message));
slave.on('data', () => console.log('slave#gotdata'));

These are the error I got on master:

node master.js 
master#error: connect ECONNREFUSED 127.0.0.1:502
master#error: connect ECONNREFUSED 127.0.0.1:502
master#error: connect ECONNREFUSED 127.0.0.1:502
master#error: connect ECONNREFUSED 127.0.0.1:502
master#error: connect ECONNREFUSED 127.0.0.1:502

These are the error I got on slave:

node slave.js 
listener#error: listen EACCES 0.0.0.0:502

Why would it try to create a local server if all it needs to do is to get connected through RS485 devices. What did I do wrong? Thank you.

morkai commented 6 years ago

connection and listener must be top level properties. If not present, defaults (tcp) are used.

Correct:

const master = modbus.createMaster({
  transport: transport,
  connection: connection
});

Wrong:

const master = modbus.createMaster({
  transport: {
    connection: connection
  }
});
ekawahyu commented 6 years ago

Ok, I have updated the snippets above, but I got the same error still. Thanks for the prompt reply!

ekawahyu commented 6 years ago

Apparently, I need to execute slave.js with sudo. Now the master can get connected to the slave, but I still got these error on the slave:

sudo node slave.js 
listener#open
listener#client: 1
client#1#error: TypeError: Cannot read property 'fromBuffer' of undefined
    at ApplicationDataUnit.toRequest (/Users/nodino/Downloads/conectric-wi/node_modules/h5.modbus/lib/ApplicationDataUnit.js:66:49)
    at Slave.handleAdu (/Users/nodino/Downloads/conectric-wi/node_modules/h5.modbus/lib/Slave.js:213:21)
    at emitOne (events.js:115:13)
    at RemoteClient.emit (events.js:210:7)
    at IpTransportState.handleCompleteFrame (/Users/nodino/Downloads/conectric-wi/node_modules/h5.modbus/lib/transports/IpTransport.js:381:13)
    at IpTransportState.handleIncompleteFrame (/Users/nodino/Downloads/conectric-wi/node_modules/h5.modbus/lib/transports/IpTransport.js:353:12)
    at IpTransportState.onData (/Users/nodino/Downloads/conectric-wi/node_modules/h5.modbus/lib/transports/IpTransport.js:309:10)
    at emitOne (events.js:115:13)
    at RemoteClient.emit (events.js:210:7)
    at Socket.readable (/Users/nodino/Downloads/conectric-wi/node_modules/h5.modbus/lib/listeners/TcpListener.js:266:18)
rps=0 (min=0 max=0 cur=0) req=0 res=0 err=0 rss=27 (min=27 max=27)
rps=0 (min=0 max=0 cur=0) req=0 res=0 err=0 rss=27 (min=27 max=27)
client#1#close

And these are the error on the master:

node master.js 
Connected!
No response was received from the slave in the specified time.
No response was received from the slave in the specified time.
morkai commented 6 years ago

Error suggests usage of an unsupported function code (or a broken frame).

Add some more logging:

slave.listener.on('client', client =>
{
  console.log('listener#client');

  client.on('error', err => console.log('client#error: %s', err.stack));
  client.on('close', () => console.log('client#close'));
  client.on('data', data => console.log('client#data:', data));
  client.on('write', data => console.log('client#write:', data));
});
ekawahyu commented 6 years ago

The part that I don't understand... is that why would the slave need sudo permission? I granted access to peripherals like serial port, so somewhere in the code still requires access to the networking stuff? How to disable it and let it run RTU transport only over the serial port.

BTW, I added the logging per your suggestion, the error messages look the same.

morkai commented 6 years ago

If the serial slave is properly configured and used then the default TCP shouldn't start. Check netstat and/or try to write a simple client/server with only node-serialport (using the same config options and without sudo).

BTW, I added the logging per your suggestion, the error messages look the same.

I need the full client/server code & output (especially client#data: ...).

ekawahyu commented 6 years ago

I have tested node-serialport only and it works without requiring sudo. I have physically wired two computers with USB to Serial/RS485 adapter and node-serialport perfectly works, no sudo required. When I look at the h5.modbus code, setting up its transport layer to RTU and connection type as serial are the only thing to configure. But there is something with the listener that invokes something requiring sudo. What else I can configure in the listener?

morkai commented 6 years ago

SerialListener has the following options:

If you look at the source there is nothing special that would require sudo (include node-serialport, instantiate it and call open()).

ekawahyu commented 6 years ago

I think the more I look into the source code, the more I found the deprecated code for the serial port. For example, SerialPort.SerialPort is deprecated, isOpen() has now become a property instead of function. I got more error after fixing the source here and there. I believe the node-serialport code are not up-to-date:

https://github.com/node-serialport/node-serialport/blob/master/UPGRADE_GUIDE.md

morkai commented 6 years ago

Yup, when the SerialListener was written, node-serialport was at version 2 (now 6).

ekawahyu commented 6 years ago

Ok, I updated some lines to match the latest node-serialport API. But now I am stuck on this:

node master.js 
/Users/nodino/Downloads/conectric-wi/node_modules/h5.modbus/lib/Master.js:489
    connection.on('open', this.onConnectionOpen);
               ^

TypeError: connection.on is not a function
    at Master.setUpConnection (/Users/nodino/Downloads/conectric-wi/node_modules/h5.modbus/lib/Master.js:489:16)
    at new Master (/Users/nodino/Downloads/conectric-wi/node_modules/h5.modbus/lib/Master.js:51:28)
    at Object.<anonymous> (/Users/nodino/Downloads/conectric-wi/js/master.js:8:16)
    at Module._compile (module.js:624:30)
    at Object.Module._extensions..js (module.js:635:10)
    at Module.load (module.js:545:32)
    at tryModuleLoad (module.js:508:12)
    at Function.Module._load (module.js:500:3)
    at Function.Module.runMain (module.js:665:10)
    at startup (bootstrap_node.js:187:16)

What am I missing here? the Master is an extends of EventEmitter, yet it says that .on is not one of its methods?

morkai commented 6 years ago

It says the connection doesn't have the .on(), but Connection is also EventEmitter so the connection passed to the master is not an instance of Connection.

Show me how you are creating the master+connection+transport.

ekawahyu commented 6 years ago
var SerialPort = require('serialport');
var modbus = require('h5.modbus');

var serial = new SerialPort('/dev/tty.virtual1', {
  baudRate: 9600
});

var master = new modbus.Master({
  transport: {
    type: 'rtu',
    eofTimeout: 10, // End of frame timeout
  },
  connection: {
    type: 'serial',
    serialPort: serial
  }
});
morkai commented 6 years ago

When instantiating Master directly you also have to directly pass instances of Transport and Connection.

Options objects with type property are only accepted in the createMaster() factory function (which in turn uses createTransport() and createConnection()).

So either:

var master = modbus.createMaster({
  transport: {
    type: 'rtu',
    eofTimeout: 10, // End of frame timeout
  },
  connection: {
    type: 'serial',
    serialPort: serial
  }
});

or

var connection = new modbus.SerialConnection({serialPort});
var transport = new modbus.RtuTransport({eofTimeout: 10});
var master = new modbus.Master({connection, transport});
ekawahyu commented 6 years ago

Ok, that fixed the issue. Now, moving on to the slave.js:

var SerialPort = require('serialport');
var modbus = require('h5.modbus');

var serial = new SerialPort('/dev/tty.virtual0', {
  baudRate: 9600
});

var slave = modbus.createSlave({
  requestHandler: handleRequest,
  transport: {
    type: 'rtu',
    eofTimeout: 10, // End of frame timeout
  },
  listener: {
    type: 'serial',
    serialPort: serial
  }
});

function handleRequest(unit, request, respond)
{
 respond(modbus.ExceptionCode.IllegalFunctionCode);
}

slave.listener.on('client', client =>
{
  console.log('listener#client');

  client.on('error', err => console.log('client#error: %s', err.stack));
  client.on('close', () => console.log('client#close'));
  client.on('data', data => console.log('client#data:', data));
  client.on('write', data => console.log('client#write:', data));
});

This is the error it throws:

node slave.js
events.js:182
      throw er; // Unhandled 'error' event
      ^

Error: Port is opening
    at SerialPort.open (/Users/nodino/Downloads/conectric-wi/node_modules/serialport/lib/serialport.js:231:29)
    at SerialListener.open (/Users/nodino/Downloads/conectric-wi/node_modules/h5.modbus/lib/listeners/SerialListener.js:144:21)
    at new SerialListener (/Users/nodino/Downloads/conectric-wi/node_modules/h5.modbus/lib/listeners/SerialListener.js:94:12)
    at createByType (/Users/nodino/Downloads/conectric-wi/node_modules/h5.modbus/lib/index.js:312:7)
    at Object.exports.createListener (/Users/nodino/Downloads/conectric-wi/node_modules/h5.modbus/lib/index.js:246:10)
    at Object.exports.createSlave (/Users/nodino/Downloads/conectric-wi/node_modules/h5.modbus/lib/index.js:204:30)
    at Object.<anonymous> (/Users/nodino/Downloads/conectric-wi/js/slave.js:8:20)
    at Module._compile (module.js:624:30)
    at Object.Module._extensions..js (module.js:635:10)
    at Module.load (module.js:545:32)

I don't have any process holding up the serial port, nor other slave process running. How to solve this issue?

morkai commented 6 years ago
var serial = new SerialPort('/dev/tty.virtual0', {
  baudRate: 9600,
  autoOpen: false
});
ekawahyu commented 6 years ago

Ok, that works! At least I don't need to run sudo for the slave and the master is able to send something over the serial port and received by the slave.

ekawahyu commented 6 years ago

The fixed I did on the serialport is as follow: SerialListerner.js and SerialConnection.js:

  isOpen()
  {
    //return this.serialPort !== null && this.serialPort.isOpen();
    return this.serialPort !== null && this.serialPort.isOpen;
  }
  setUpSerialPort(serialPort)
  {
    if (!serialPort)
    {
      if (SerialPort === null)
      {
        //SerialPort = require('serialport').SerialPort;
        SerialPort = require('serialport');
      }
    //
    ...
    //
  }

AFK, will continue working tomorrow, thanks a lot! I think you can close this issue.

morkai commented 6 years ago

Already commited: https://github.com/morkai/h5.modbus/commit/7c8842b3abf036698ce77b3d17e2b9e02bda0f29