Pmbridge
pmbridge (Programmable Modbus Bridge) is a cross-platform (Windows, Linux) Modbus gateway application (TCP, RTU, ASC).
Install / Use
/learn @serhmarch/PmbridgeREADME
pmbridge
Overview
pmbridge (Programmable Modbus Bridge) is a cross-platform (Windows, Linux) Modbus gateway application.
It supports different types of Modbus protocol: TCP, RTU, ASC.
Programmable means user can configure every Modbus function to read/write from remote device(s)
and where to put/get data within internal Modbus memory and
have some other simple commands described below.
In this case pmbridge acts like a client (master).
At the same time pmbridge can acts like a server (slave) and provide access to the internal memory.
Here are some common ways to use pmbridge:
-
Modbus data collector - collect data from remote Modbus devices and provide access to this data via inner Modbus server. It can collect only necessary data from remote devices and sort them in inner memory for easy access by PLC or SCADA system. E.g. this can be collecting data from RTU Modbus network and providing simplified access to this data via Modbus TCP server or collecting data via Modbus TCP from remote devices and providing access for PLC that already operates via Modbus RTU network.
-
Active bridge - read data from one Modbus device and write the data to another Modbus device from single network or between different networks. In this case PLC programmers need to reserve some memory for exchange data and all network operations will be done by
pmbridge.
It's free and open source software based on ModbusLib project:
https://github.com/serhmarch/ModbusLib
Clients can use next list of functions:
1(0x01) -READ_COILS2(0x02) -READ_DISCRETE_INPUTS3(0x03) -READ_HOLDING_REGISTERS4(0x04) -READ_INPUT_REGISTERS15(0x0F) -WRITE_MULTIPLE_COILS16(0x10) -WRITE_MULTIPLE_REGISTERS
Server implements next list of functions:
1(0x01) -READ_COILS2(0x02) -READ_DISCRETE_INPUTS3(0x03) -READ_HOLDING_REGISTERS4(0x04) -READ_INPUT_REGISTERS5(0x05) -WRITE_SINGLE_COIL6(0x06) -WRITE_SINGLE_REGISTER7(0x07) -READ_EXCEPTION_STATUS15(0x0F) -WRITE_MULTIPLE_COILS16(0x10) -WRITE_MULTIPLE_REGISTERS17(0x11) -REPORT_SERVER_ID22(0x16) -MASK_WRITE_REGISTER23(0x17) -READ_WRITE_MULTIPLE_REGISTERS
Using pmbridge
To use pmbridge user need to make configuration file: pmbridge.conf.
There is a program (configuration) defined in this file.
Configuration file
Configuration file contains list of commands (program). There can be declaration and execution commands.
Command can be singleline or multiline.
Singleline command can be defined without {}-brackets unlike multiline command:
<SINGLELINE_COMMAND>=<arg1>,<arg2>,...,<argN>
<MULTILINE_COMMAND>={<arg1>,
<arg2>,
...,
<argN>}
Declaration commands
-
MEMORY={<0x>,<1x>,<3x>,<4x>}Command for inner memory configuration.
0x- quantity of coils (0x)-memory1x- quantity of input discretes (1x)-memory3x- quantity of input registers (3x)-memory4x- quantity of holding registers (4x)-memory
-
SERVER={<type>,<name>,...} -
CLIENT={<type>,<name>,...}Command to create server and client port respectively.
type- type of Modbus protocol of the port. Can be {RTU,ASC,TCP}name- name/id of the port. It is used for QUERY commands (client) and log
Types of port and parameters:
-
SERVER={TCP,<name>,<tcpport>,<timeout>,<maxconn>,<ipaddr>,<units>,<broadcast>}tcpport- unnecessary parameter, server ModbusTCP port (502 by default)timeout- unnecessary parameter, timeout for read in milliseconds (3000 by default)maxconn- unnecessary parameter, maximum TCP connection for server (10 by default)ipaddr- unnecessary parameter, IP address of the server to bind ("0.0.0.0" by default)units- unnecessary parameter, filter, list of allowed unit/slave addresses separated by,or-(all units allowed by default)broadcast- unnecessary parameter, enableunit=0is broadcast (1 (enabled) by default)
-
CLIENT={TCP,<name>,<host>,<tcpport>,<timeout>}host- remote host to connecttcpport- unnecessary parameter, remote port to connect (502 by default)timeout- unnecessary parameter, timeout for read in milliseconds (3000 by default)
-
SERVER={RTU,<name>,<devname>,<baudrate>,<databits>,<parity>,<stopbits>,<flowcontrol>,<timeoutfb>,<timeoutib>,<units>,<broadcast>}SERVER={ASC,<name>,<devname>,<baudrate>,<databits>,<parity>,<stopbits>,<flowcontrol>,<timeoutfb>,<timeoutib>,<units>,<broadcast>}CLIENT={RTU,<name>,<devname>,<baudrate>,<databits>,<parity>,<stopbits>,<flowcontrol>,<timeoutfb>,<timeoutib>}CLIENT={ASC,<name>,<devname>,<baudrate>,<databits>,<parity>,<stopbits>,<flowcontrol>,<timeoutfb>,<timeoutib>}devname- device system name or port name. For example: COM13, /dev/ttyM0, /dev/ttyUSB0 etcbaudrate- unnecessary parameter, baud rate, use from serie: 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200 (9600 by default)databits- unnecessary parameter, number of databits, use from serie: 5, 6, 7, 8 (8 by default)parity- unnecessary parameter, parity: None,N - none; Even,E - even; Odd,O - odd (None by default)stopbits- unnecessary parameter, stop bits: may be 1, 1.5 or 2 (1 by default)flowcontrol- unnecessary parameter, flow control: No, Hard, Soft (No by default)timeoutfb- unnecessary parameter, timeout for read first byte of the input packet in milliseconds (1000 by default)timeoutib- unnecessary parameter, timeout for read next bytes of the input packet in milliseconds (50 by default)units- unnecessary parameter (server only), filter, list of allowed unit/slave addresses separated by,or-(all units allowed by default)broadcast- unnecessary parameter (server only), enableunit=0is broadcast (1 (enabled) by default)
Execution commands
-
QUERY={<client>,<unit>,<func>,<devadr>,<count>,<memadr>,<execpatt>,<succadr>,<errcadr>,<errvadr>}Command for remote request for previously configured client port.
client- name of client port previously defined inCLIENTcommandunit- modbus unit/address slavefunc- name of function. Can be {RD,WR}. What to read/write defined in the nextdevadrparameter.devadr- address of first item of the remote device to read/writecount- count of elements (discrete or register)memadr- address within inner memory to get/setexecpatt- execution pattern. Specifies the query will be executed once at execpatt-cyclesuccadr- address of success counter within inner memoryerrcadr- address of error counter within inner memoryerrvadr- address of last error within inner memory
-
COPY={<srcadr>,<count>,<destadr>}Copy data from one part of memory to another
srcadr- memory address to copy fromcount- count of elements to copy (discret or register)destadr- memory address to copy to
-
DELAY={<msec>}Delay execution of next command.
msec- time to delay in milliseconds
-
DUMP={<memadr>,<count>,<format>}Print memory dump into console.
memadr- memory address to printcount- count of elements to print (discret or register)format- format of element. Must be:Bin16Oct16Dec16UDec16Hex16Bin32Oct32Dec32UDec32Hex32Bin64Oct64Dec64UDec64Hex64FloatDouble
units-parameter for SERVER
This parameter allows to filter incoming requests by unit/slave address.
User can specify list of allowed unit/slave addresses separated by , or -.
For example:
units=1,3-5,10 means that only requests with unit/slave address
1, 3, 4, 5 and 10 will be processed by server.
Memory addressing
pmbridge support 3 types of memory addressing format:
| Memory type | Standard (1 based) | IEC 61131-3 (0 based)| IEC 61131-3 Hex (0 based)
|-------------------|--------------------|----------------------|---------------------------
| Coils | 000001 | %Q0 | %Q0000h
| Discrete inputs | 100016 | %I15 | %I000Fh
| Input registers | 300017 | %IW16 | %IW0010h
| Holding registers | 406658 | %MW6657 | %MW1A01h
Example of the program
# Declaration. Memory with 1000 coils, 1000 input discretes, 1000 input registers and 5000 holding registers
MEMORY={1000,1000,1000,5000}
# Declaration. Port as TCP server with port 502, timeout 5000ms and max 10 connections
SERVER={TCP,serv,502,5000,10}
# Declaration. Port as RTU client with portname "/dev/ttyM0", baudrate 19200, 8 databits, even parity, 1.5 stopbits
CLIENT={RTU,client1,"/dev/ttyM0",
19200,
8,
E,
1.5,
Hard,
1000,
50}
# Declaration. Port as TCP client for 'localhost'
CLIENT={TCP,client2,"127.0.0.1"}
# Execution. Write 10 holding register into remote device (from 400001 to 400010)
# with values from inner memory of 300001 to 300010.
# Success counter is 300901, error counter is 300902 and last error code is 300903 within inner memory.
QUERY={client1,1,WR,400001,10,300001,1,300901,300902,300903}
# Execution. Prin
