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.
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_COILS
2
(0x02
) -READ_DISCRETE_INPUTS
3
(0x03
) -READ_HOLDING_REGISTERS
4
(0x04
) -READ_INPUT_REGISTERS
15
(0x0F
) -WRITE_MULTIPLE_COILS
16
(0x10
) -WRITE_MULTIPLE_REGISTERS
Server implements next list of functions:
1
(0x01
) -READ_COILS
2
(0x02
) -READ_DISCRETE_INPUTS
3
(0x03
) -READ_HOLDING_REGISTERS
4
(0x04
) -READ_INPUT_REGISTERS
5
(0x05
) -WRITE_SINGLE_COIL
6
(0x06
) -WRITE_SINGLE_REGISTER
7
(0x07
) -READ_EXCEPTION_STATUS
15
(0x0F
) -WRITE_MULTIPLE_COILS
16
(0x10
) -WRITE_MULTIPLE_REGISTERS
17
(0x11
) -REPORT_SERVER_ID
22
(0x16
) -MASK_WRITE_REGISTER
23
(0x17
) -READ_WRITE_MULTIPLE_REGISTERS
To use pmbridge
user need to make configuration file: pmbridge.conf
.
There is a program (configuration) defined in this 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>}
-
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>}
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)
-
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>}
SERVER={ASC,<name>,<devname>,<baudrate>,<databits>,<parity>,<stopbits>,<flowcontrol>,<timeoutfb>,<timeoutib>}
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)
-
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 inCLIENT
commandunit
- modbus unit/address slavefunc
- name of function. Can be {RD,WR}. What to read/write defined in the nextdevadr
parameter.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:Bin16
Oct16
Dec16
UDec16
Hex16
Bin32
Oct32
Dec32
UDec32
Hex32
Bin64
Oct64
Dec64
UDec64
Hex64
Float
Double
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 |
# 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. Print inner memory dump into console: 3 registers (from 300901 to 300903) in hexadecimal format
DUMP={300901,3,Hex16}
# Execution. Wait 200 milliseconds
DELAY={200}
# Execution. Read 10 holding register from remote device (from 400001 to 400010)
# and put values into inner memory 400101 to 400110.
# Success counter is 300904, error counter is 300905 and last error code is 300906 within inner memory.
QUERY={client2,1,RD,%MW0,3,%MW100,1,300904,300905,300906}
# Execution. Print inner memory dump into console: 3 registers
# (from 300904 to 300906) in decimal format
DUMP={300904,3,Dec16}
# Execution. Copy 6 registers (previously defined counters)
# from inner memory 300901 to 300906 into inner memory 400001 to 400006
COPY={300901,6,%MW0000h}
# Execution. Wait 2 seconds (2000 milliseconds)
DELAY={2000}
Table of error codes:
Hex code | Dec code | Name | Description |
---|---|---|---|
0x0001 |
1 |
Status_BadIllegalFunction |
Standard error. The feature is not supported |
0x0002 |
2 |
Status_BadIllegalDataAddress |
Standard error. Invalid data address |
0x0003 |
3 |
Status_BadIllegalDataValue |
Standard error. Invalid data value |
0x0004 |
4 |
Status_BadServerDeviceFailure |
Standard error. Failure during a specified operation |
0x0005 |
5 |
Status_BadAcknowledge |
Standard error. The server has accepted the request and is processing it, but it will take a long time |
0x0006 |
6 |
Status_BadServerDeviceBusy |
Standard error. The server is busy processing a long command. The request must be repeated later |
0x0007 |
7 |
Status_BadNegativeAcknowledge |
Standard error. The programming function cannot be performed |
0x0008 |
8 |
Status_BadMemoryParityError |
Standard error. The server attempted to read a record file but detected a parity error in memory |
0x000A |
10 |
Status_BadGatewayPathUnavailable |
Standard error. Indicates that the gateway was unable to allocate an internal communication path from the input port o the output port for processing the request. Usually means that the gateway is misconfigured or overloaded |
0x000B |
11 |
Status_BadGatewayTargetDeviceFailedToRespond |
Standard error. Indicates that no response was obtained from the target device. Usually means that the device is not present on the network |
0x0101 |
257 |
Status_BadEmptyResponse |
Error. Empty request/response body |
0x0102 |
258 |
Status_BadNotCorrectRequest |
Error. Invalid request |
0x0103 |
259 |
Status_BadNotCorrectResponse |
Error. Invalid response |
0x0104 |
260 |
Status_BadWriteBufferOverflow |
Error. Write buffer overflow |
0x0105 |
261 |
Status_BadReadBufferOverflow |
Error. Request receive buffer overflow |
0x0201 |
513 |
Status_BadSerialOpen |
Error. Serial port cannot be opened |
0x0202 |
514 |
Status_BadSerialWrite |
Error. Cannot send a parcel to the serial port |
0x0203 |
515 |
Status_BadSerialRead |
Error. Reading the serial port |
0x0204 |
516 |
Status_BadSerialReadTimeout |
Error. Reading the serial port (timeout) |
0x0205 |
517 |
Status_BadSerialWriteTimeout |
Error. Writing the serial port (timeout) |
0x0301 |
769 |
Status_BadAscMissColon |
Error (ASC). Missing packet start character ':' |
0x0302 |
770 |
Status_BadAscMissCrLf |
Error (ASC). '\r\n' end of packet character missing |
0x0303 |
771 |
Status_BadAscChar |
Error (ASC). Invalid ASCII character |
0x0304 |
772 |
Status_BadLrc |
Error (ASC). Invalid checksum |
0x0401 |
1025 |
Status_BadCrc |
Error (RTU). Wrong checksum |
0x0501 |
1281 |
Status_BadTcpCreate |
Error. Unable to create a TCP socket |
0x0502 |
1282 |
Status_BadTcpConnect |
Error. Unable to create a TCP connection |
0x0503 |
1283 |
Status_BadTcpWrite |
Error. Unable to send a TCP packet |
0x0504 |
1284 |
Status_BadTcpRead |
Error. Unable to receive a TCP packet |
0x0505 |
1285 |
Status_BadTcpBind |
Error. Unable to bind a TCP socket (server side) |
0x0506 |
1286 |
Status_BadTcpListen |
Error. Unable to listen a TCP socket (server side) |
0x0507 |
1287 |
Status_BadTcpAccept |
Error. Unable accept bind a TCP socket (server side) |
0x0508 |
1288 |
Status_BadTcpDisconnect |
Error. Bad disconnection result |
To show list of available parameters print:
$ pmbridge --help
Usage: pmbridge [options]
Options:
--version (-v) - show program version.
--help (-?) - show this help.
--file (-f) - path to *.conf file (pmbridge.conf by default)
--log-flags (-lc) - list of log flags (categories); `,`, `;` or `|` separated
--log-format (-lfmt) - format of each message to output
--log-time (-lt) - format of time of each message to output
Format can contain following special symbols:
%time
- timestamp of the message, for which the format is also specified (--log-time
)%cat
- text representation of the message category%text
- text of the message
Time format of the message (%time
) has the following special symbols:
%Y
- year (4 characters)%M
- month (2 characters01
-12
)%D
- day (2 characters01
-31
)%h
- hour (2 characters00
-23
)%m
- minute (2 characters00
-59
)%s
- second (2 characters00
-59
)%f
- millisecond (3 characters000
-999
)
Category of the message. Can be:
ERR
- error messagesWARN
- warning messagesINFO
- information messagesDUMP
- dump command messagesCONN
- connect/disconnect messagesTX
- transmited Modbus messagesRX
- received Modbus messagesALL
- all categories included
-
Build Tools
Previously you need to install c++ compiler kit, git and cmake itself. Then set PATH env variable to find compliler, cmake, git etc.
-
Create project directory, move to it and clone repository:
$ cd ~ $ mkdir src $ cd src $ git clone --recursive https://github.com/serhmarch/pmbridge.git
-
Create and/or move to directory for build output, e.g.
~/bin/pmbridge
:$ cd ~ $ mkdir -p bin/pmbridge $ cd bin/pmbridge
-
Run cmake to generate project (make) files.
$ cmake -S ~/src/pmbridge -B .
-
Make binaries (+ debug|release config):
$ cmake --build . $ cmake --build . --config Debug $ cmake --build . --config Release
-
Resulting bin files is located in
./bin
directory.