TCP-to-serial gateway
A gateway receives Modbus TCP requests, forwards them to devices on a serial bus and returns the responses. In this tutorial a PDComm headless simulator acts as the gateway, and an RTU server across a pair of virtual serial ports stands in for the serial device.
terminal B terminal G terminal V terminal ATCP client ──TCP──> gateway 127.0.0.1:1504 ── modbus-b ══ pair ══ modbus-a ── RTU server (address 7) unit 1 ─────────> routes {"1":7, "2":9}- Understand a configuration with
rolegatewayand itsroutestable. - Use
traceto see TCP unit identifiers turn into serial addresses. - Tell a missing route (exception 0A) from a silent serial device (exception 0B).
What you need
Section titled “What you need”- The prerequisites of RTU and ASCII communication: the SDK, the C++ console and
pdcomm-vserial - Four terminals, all started in the same working folder
1. Create a virtual port pair
Section titled “1. Create a virtual port pair”Run this in terminal V and keep it open:
pdcomm-vserial pair --link-a "$PWD/modbus-a" --link-b "$PWD/modbus-b"2. Open an RTU server as the serial device
Section titled “2. Open an RTU server as the serial device”In terminal A, open an RTU server with address 7 on modbus-a:
"$MODBUS_SDK/bin/pdcomm-modbus-headless"{"action":"configure","config":{"transport":"rtu","role":"server","address":"127.0.0.1:0","port":"/absolute/path/modbus-a","unit":7,"baud":19200,"parity":"even","stop_bits":1,"timeout_ms":1000}}{"action":"connect"}{"action":"data_set","table":"holding_registers","address":0,"values":[1200,1185]}3. Open the gateway
Section titled “3. Open the gateway”In terminal G, configure a second headless simulator as the gateway:
"$MODBUS_SDK/bin/pdcomm-modbus-headless"{"action":"configure","config":{"transport":"rtu","role":"gateway","address":"127.0.0.1:1504","port":"/absolute/path/modbus-b","unit":1,"baud":19200,"parity":"even","stop_bits":1,"timeout_ms":1000,"routes":{"1":7,"2":9}}}{"action":"connect"}In a gateway configuration:
| Field | Meaning |
|---|---|
transport |
The serial bus side, rtu or ascii. The TCP side is always Modbus TCP; tcp fails with Gateway requires a serial bus transport. |
address |
Where TCP clients connect |
port, baud, parity, stop_bits |
The serial bus settings, matching the devices on the bus |
routes |
TCP unit identifier → serial address. Here unit 1 goes to address 7 and unit 2 to address 9. |
timeout_ms |
How long to wait for the serial device; afterwards the gateway returns exception 0B. |
A gateway doesn’t use unit, but the format needs a value from 1 to 247. Route keys (TCP units) must be 1–255 and values (serial addresses) 1–247; 0 or out-of-range values fail with Invalid gateway route.
4. Send requests from a TCP client
Section titled “4. Send requests from a TCP client”In terminal B, start the C++ console and connect to the gateway. Give the client a longer timeout_ms than the gateway’s serial wait, so you receive exception 0B instead of a timeout.
./build/modbus-tutorial/pdcomm-modbus-tutorial{"action":"configure","config":{"transport":"tcp","role":"client","address":"127.0.0.1:1504","port":"","unit":1,"baud":19200,"parity":"even","stop_bits":1,"timeout_ms":3000}}{"action":"connect"}{"action":"request","pdu":"0300000002"}{"action":"request","pdu":"0600010064"}{"action":"request","unit":2,"pdu":"0300000001"}{"action":"request","unit":3,"pdu":"0300000001"}{"action":"request","pdu":"0300000002"}{"ok":true,"result":{"response":[3,4,4,176,4,161],"source":"wire","status":"success"}}{"ok":true,"result":{"response":[6,0,1,0,100],"source":"wire","status":"success"}}{"ok":true,"result":{"response":[131,11],"source":"wire","status":"exception"}}{"ok":true,"result":{"response":[131,10],"source":"wire","status":"exception"}}{"ok":true,"result":{"response":[3,4,4,176,0,100],"source":"wire","status":"success"}}| Request | Result | Explanation |
|---|---|---|
| Unit 1 read and write | Normal | Route 1→7 forwarded them to the serial server. |
| Unit 2 | Exception 0B ([131,11]) after about 1 s |
Route 2→9 exists, but no device with address 9 answered. |
| Unit 3 | Exception 0A ([131,10]) at once |
The route table has no unit 3, so nothing goes on the serial bus. |
5. Look at the serial side
Section titled “5. Look at the serial side”In terminal G, read the gateway’s record:
{"action":"trace"}The first byte of each tx entry is the serial address:
bytes |
Meaning |
|---|---|
[7,3,0,0,0,2,196,109] |
Unit 1’s read went to address 7. |
[7,3,4,4,176,4,161,95,156] (rx) |
Address 7’s response |
[9,3,0,0,0,1,133,66] |
Unit 2’s read went to address 9, with no rx after it. |
Unit 3’s request never reached the serial bus, so it has no entry.
6. Clean up
Section titled “6. Clean up”- Terminal B:
quit - Terminals G and A:
{"action":"shutdown"} - Terminal V:
{"command":"stop"}
How the gateway behaves
Section titled “How the gateway behaves”- The serial bus carries one transaction at a time. Requests from several TCP clients queue up to 64 deep; beyond that a client gets exception 06 (Server Device Busy).
- Queued requests of a client whose TCP connection closed are dropped, and a late serial reply is never delivered as the reply to another request.
- Serial-line-only functions (07, 08, 11, 12, 17) get exception 01.
- Serial requests are never resent automatically; a failure returns exception 0B.
- Up to 32 TCP clients can connect.
- Like a server, a gateway works only while it’s polled. The headless simulator and the GUI poll for you.
To reach real RS-485 devices, set port to the name of a USB-RS485 adapter and match the bus settings. See Connect a real device for wiring and settings.