3cky / mbusd

Open-source Modbus TCP to Modbus RTU (RS-232/485) gateway.
BSD 3-Clause "New" or "Revised" License
565 stars 216 forks source link

feature-reply-on-broadcast #75

Closed dgoo2308 closed 3 years ago

dgoo2308 commented 3 years ago

Reply on Broadcast

Purpose

Some ModBus libraries don't support Broadcast unitId=0 over tcp/ip what creates timeout and/or error condition in the tcp client. While mbusd is already prepared not to wait for a reply on a Broadcast, mbusd does not send an answer to the client.

Change:

This merge request adds the option -b to mbusd startup options and replyonbroadcast as config file option to enable the reply on a Broadcast, defaulting to NO REPLY ON BROADCAST. Updates the man mbus and mbusd -h or usage:

Usage: mbusd [-h] [-d] [-L logfile] [-v level] [-c cfgfile]
             [-p device] [-s speed] [-m mode]
             [-t] [-y sysfsfile] [-Y sysfsfile]
             [-A address] [-P port] [-C maxconn] [-N retries]
             [-R pause] [-W wait] [-T timeout] [-b]

Options:
  -h         : this help
  -d         : don't daemonize
  -L logfile : set log file name (default /var/log/mbus.log,
               '-' for logging to STDOUT only)
  -v level   : set log level (0-9, default 2, 0 - errors only)
  -c cfgfile : read configuration from cfgfile
  -p device  : set serial port device name (default /dev/ttyS0)
  -s speed   : set serial port speed (default 19200)
  -m mode    : set serial port mode (default 8N1)
  -A address : set TCP server address to bind (default 0.0.0.0)
  -P port    : set TCP server port number (default 502)
  -t         : enable RTS RS-485 data direction control using RTS
  -y         : enable RTS RS-485 data direction control using sysfs file, active transmit
  -Y         : enable RTS RS-485 data direction control using sysfs file, active receive
  -C maxconn : set maximum number of simultaneous TCP connections
               (1-128, default 32)
  -N retries : set maximum number of request retries
               (0-15, default 3, 0 - without retries)
  -R pause   : set pause between requests in milliseconds
               (1-10000, default 100)
  -W wait    : set response wait time in milliseconds
               (1-10000, default 500)
  -T timeout : set connection timeout value in seconds
               (0-1000, default 60, 0 - no timeout)
  -b         : reply on broadcast (default 0)

Test w pymodbus

    client = ModbusClient('localhost', port=1501)
    client.connect()
    rq = client.write_register(1, 10, unit=0)

Remark: Without the pymodbus.client broadcast_enable=True, to simulate libraries that don't have the broadcast_enable on a tcp modbus connection.

original or new without -b

2021-06-19 13:06:17,583 MainThread      DEBUG    sync           :215      Connection to Modbus server established. Socket ('127.0.0.1', 52444)
2021-06-19 13:06:17,583 MainThread      DEBUG    transaction    :139      Current transaction state - IDLE
2021-06-19 13:06:17,583 MainThread      DEBUG    transaction    :144      Running transaction 1
2021-06-19 13:06:17,583 MainThread      DEBUG    transaction    :272      SEND: 0x0 0x1 0x0 0x0 0x0 0x6 0x0 0x6 0x0 0x1 0x0 0xa
2021-06-19 13:06:17,583 MainThread      DEBUG    sync           :76       New Transaction state 'SENDING'
2021-06-19 13:06:17,583 MainThread      DEBUG    transaction    :286      Changing transaction state from 'SENDING' to 'WAITING FOR REPLY'
2021-06-19 13:06:20,587 MainThread      DEBUG    transaction    :302      Transaction failed. (Modbus Error: [Invalid Message] No response received, expected at least 8 bytes (0 received))
2021-06-19 13:06:20,587 MainThread      DEBUG    socket_framer  :147      Processing:
2021-06-19 13:06:20,587 MainThread      DEBUG    transaction    :464      Getting transaction 1
2021-06-19 13:06:20,587 MainThread      DEBUG    transaction    :224      Changing transaction state from 'PROCESSING REPLY' to 'TRANSACTION_COMPLETE'
Traceback (most recent call last):
  File "/Users/dgoo2308/git/modbus_test/c.py", line 101, in <module>
    run_sync_client()
  File "/Users/dgoo2308/git/modbus_test/c.py", line 60, in run_sync_client
    assert(not rq.isError())     # test that we are not an error
AssertionError

mbusd -b

2021-06-19 13:01:55,616 MainThread      DEBUG    sync           :215      Connection to Modbus server established. Socket ('127.0.0.1', 52156)
2021-06-19 13:01:55,616 MainThread      DEBUG    transaction    :139      Current transaction state - IDLE
2021-06-19 13:01:55,616 MainThread      DEBUG    transaction    :144      Running transaction 1
2021-06-19 13:01:55,616 MainThread      DEBUG    transaction    :272      SEND: 0x0 0x1 0x0 0x0 0x0 0x6 0x0 0x6 0x0 0x1 0x0 0xa
2021-06-19 13:01:55,616 MainThread      DEBUG    sync           :76       New Transaction state 'SENDING'
2021-06-19 13:01:55,616 MainThread      DEBUG    transaction    :286      Changing transaction state from 'SENDING' to 'WAITING FOR REPLY'
2021-06-19 13:01:55,618 MainThread      DEBUG    transaction    :374      Changing transaction state from 'WAITING FOR REPLY' to 'PROCESSING REPLY'
2021-06-19 13:01:55,618 MainThread      DEBUG    transaction    :296      RECV: 0x0 0x1 0x0 0x0 0x0 0x6 0x0 0x6 0x0 0x1 0x0 0xa
2021-06-19 13:01:55,618 MainThread      DEBUG    socket_framer  :147      Processing: 0x0 0x1 0x0 0x0 0x0 0x6 0x0 0x6 0x0 0x1 0x0 0xa
2021-06-19 13:01:55,618 MainThread      DEBUG    factory        :266      Factory Response[WriteSingleRegisterResponse: 6]
2021-06-19 13:01:55,618 MainThread      DEBUG    transaction    :453      Adding transaction 1
2021-06-19 13:01:55,618 MainThread      DEBUG    transaction    :464      Getting transaction 1
2021-06-19 13:01:55,618 MainThread      DEBUG    transaction    :224      Changing transaction state from 'PROCESSING REPLY' to 'TRANSACTION_COMPLETE'
2021-06-19 13:01:55,618 MainThread      DEBUG    transaction    :139      Current transaction state - TRANSACTION_COMPLETE

test libmodbus

ModBus version: 3.1.6

#include <modbus/modbus.h>
int main(int argc, const char * argv[]){
   modbus_t * ctx=modbus_new_tcp_pi("mbus-5", "502");
   modbus_set_slave(ctx,0);
   modbus_set_debug(ctx, TRUE);
   modbus_write_bits(ctx, 229, 1, &data);
   modbus_close(ctx);
   modbus_free(ctx);
  return 0;
}

original or new without -b

[00][01][00][00][00][08][00][0F][00][E5][00][01][01][01]
Waiting for a confirmation...
ERROR Connection timed out: select

mbusd -b

[00][01][00][00][00][08][00][0F][00][E5][00][01][01][01]
Waiting for a confirmation...
<00><01><00><00><00><08><00><0F><00><E5><00><01>
OK
3cky commented 3 years ago

Thanks for the PR! Overall I like the idea, but still not sure that a simple request echo will work as expected in all situations. No doubt it will be correct for force single coil/register functions, but force multiple coil/register functions responses are different from simple request echo, not saying read functions. Do you have some examples of Modbus TCP client software that is expecting such responses for broadcast requests?

dgoo2308 commented 3 years ago

@3cky

are the ones I run into and I'm aware of.

I get your point, I'll setup some testing with other writes beside force single coil/register functions and study the responses, I didnt think about that, but yes it falls under 'a write' and thus can have a broadcast.

Thanks

3cky commented 3 years ago

Hello @dgoo2308,

I merged your PR with some small fixes. Please note I decided to change the semantics of replyonbroadcast config file option. Now it should have a value y or yes to enable reply on a broadcast.

Thank you again!

dgoo2308 commented 3 years ago

@3cky thanks, looking good.