Observe addresses and frames
This tutorial needs no network or serial port. You open one headless simulator, run PDUs against its data model (execute) and feed raw bytes to its frame decoders (fault). Every result reports source as local or synthetic: nothing goes on a wire.
- Convert a device-manual address such as 40108 to a PDU address, then write and read it.
- See how bit tables are packed and what an exception response looks like.
- Decode the same request as TCP, RTU and ASCII frames, and reproduce CRC, LRC and RTU character-gap errors.
What you need
Section titled “What you need”- The PDComm Modbus SDK, with
MODBUS_SDKpointing at it as in the quickstart - One terminal
1. Start the headless simulator
Section titled “1. Start the headless simulator”"$MODBUS_SDK/bin/pdcomm-modbus-headless"The headless simulator reads one JSON command per line from standard input and prints one reply line per command. It prints nothing until you send a command. A new session is a disconnected TCP server configuration with 1024 zeroed entries in each table.
Check what the build supports first:
{"action":"capabilities"}{"ok":true,"result":{"ascii":true,"canopen":false,"diagnostic_logging":true,"diagnostics":[0,1,2,3,4,10,11,12,13,14,15,16,17,18,20],"functions":[1,2,3,4,5,6,7,8,11,12,15,16,17,20,21,22,23,24,43],"logging_schema_version":1,"mei13":"opaque_extension","project_version":1,"protocol":"modbus","rtu":true,"security":true,"tcp":true}}If your SDK reports rtu or ascii as false, those transports fail with Transport is excluded in steps 6 and 7.
2. Convert a manual address
Section titled “2. Convert a manual address”Suppose the device manual lists “40108: temperature setpoint”. The prefix 4 means holding registers, and 108 minus 1 is PDU address 107 (0x006B). Write 1234 (0x04D2) and read it back:
{"action":"execute","pdu":"06006B04D2"}{"action":"execute","pdu":"03006B0001"}{"ok":true,"result":{"response":[6,0,107,4,210],"source":"local","status":"success"}}{"ok":true,"result":{"response":[3,2,4,210],"source":"local","status":"success"}}Responses are decimal bytes: [3,2,4,210] is function 3, a byte count of 2 and the value 0x04D2 (1234).
3. Check the bit tables
Section titled “3. Check the bit tables”Write 0xA5 to coils 0–7 in one request, then read the eight coils:
{"action":"execute","pdu":"0F0000000801A5"}{"action":"execute","pdu":"0100000008"}{"ok":true,"result":{"response":[15,0,0,0,8],"source":"local","status":"success"}}{"ok":true,"result":{"response":[1,1,165],"source":"local","status":"success"}}The least significant bit of 165 (0xA5) is coil 0, so coils 0, 2, 5 and 7 are on.
A client can’t write discrete inputs. In the simulator, data_set stands in for a changing device input:
{"action":"data_set","table":"discrete_inputs","address":0,"values":[1,0,1]}{"action":"execute","pdu":"0200000003"}{"ok":true,"result":{"written":3}}{"ok":true,"result":{"response":[2,1,5],"source":"local","status":"success"}}4. Get an exception response
Section titled “4. Get an exception response”Read address 65535, which isn’t in the model:
{"action":"execute","pdu":"03FFFF0001"}{"ok":true,"result":{"response":[131,2],"source":"local","status":"exception"}}131 is 0x83 (function 3 plus 0x80), and 2 is Illegal Data Address. The command itself succeeded, so ok is true.
5. Decode a TCP frame
Section titled “5. Decode a TCP frame”The fault command feeds raw bytes to a frame decoder and shows the result, without any I/O:
{"action":"fault","transport":"tcp","bytes":"000100000006010300000001"}{"ok":true,"result":{"errors":[],"frames":[{"pdu":[3,0,0,0,1],"transaction":1,"unit":1}],"source":"synthetic"}}The decoder removed the MBAP header (0001 0000 0006 01) and returned transaction 1, unit 1 and the PDU 03 0000 0001. Drop the last PDU byte and the frame never completes:
{"action":"fault","transport":"tcp","bytes":"0001000000060103000000"}{"ok":true,"result":{"errors":["Incomplete"],"frames":[],"source":"synthetic"}}6. Decode RTU and ASCII frames
Section titled “6. Decode RTU and ASCII frames”Send the PDU 03 0000 000A as RTU. With the correct CRC C5 CD you get a frame; change one bit and you get a Checksum error:
{"action":"fault","transport":"rtu","bytes":"01030000000AC5CD"}{"action":"fault","transport":"rtu","bytes":"01030000000AC5CC"}{"ok":true,"result":{"errors":[],"frames":[{"pdu":[3,0,0,0,10],"unit":1}],"source":"synthetic"}}{"ok":true,"result":{"errors":["Checksum"],"frames":[],"source":"synthetic"}}For ASCII, pass the byte values of the characters :01030000000AF2 plus CR LF in hex:
{"action":"fault","transport":"ascii","bytes":"3A30313033303030303030304146320D0A"}{"action":"fault","transport":"ascii","bytes":"3A30313033303030303030304146330D0A"}{"action":"fault","transport":"ascii","bytes":"3A30313033303030303030306166320D0A"}{"ok":true,"result":{"errors":[],"frames":[{"pdu":[3,0,0,0,10],"unit":1}],"source":"synthetic"}}{"ok":true,"result":{"errors":["Checksum"],"frames":[],"source":"synthetic"}}{"ok":true,"result":{"errors":["Encoding"],"frames":[],"source":"synthetic"}}- The second frame has its LRC changed from
F2toF3, so it fails withChecksum. - The third has
Areplaced by lowercasea(0x61), so it fails withEncoding: ASCII mode accepts uppercase hex digits only.
7. Reproduce RTU character-gap errors
Section titled “7. Reproduce RTU character-gap errors”gap_us sets a virtual gap between the bytes fed to the RTU decoder; the default is 100 µs. At the configuration’s default 19200 bit/s and 8E1, t1.5 is 860 µs and t3.5 is 2006 µs.
{"action":"fault","transport":"rtu","bytes":"01030000000AC5CD","gap_us":1000}{"action":"fault","transport":"rtu","bytes":"01030000000AC5CD","gap_us":3000}{"ok":true,"result":{"errors":["Timing"],"frames":[],"source":"synthetic"}}{"ok":true,"result":{"errors":["Length","Length","Length","Length","Length","Length","Length","Length"],"frames":[],"source":"synthetic"}}- 1000 µs exceeds t1.5, so the frame is dropped for a gap inside the frame (
Timing). - 3000 µs exceeds t3.5, so every byte ends a frame. One-byte frames are too short, which gives eight
Lengtherrors.
8. Check the record and clean up
Section titled “8. Check the record and clean up”{"action":"trace"}{"action":"trace_clear"}trace lists the execute commands as entries with source local; fault isn’t recorded. To finish, enter {"action":"shutdown"} or close the input (Ctrl+D on macOS and Linux, Ctrl+Z then Enter on Windows).
Common errors
Section titled “Common errors”| Output | Cause and fix |
|---|---|
{"error":"Invalid hex length","ok":false} |
The hex string has an odd number of characters. Write two characters per byte. |
{"error":"Invalid hex","ok":false} |
The string contains a character that isn’t hexadecimal. |
{"error":"key must be a string at line 1 column 2","ok":false} |
The line isn’t valid JSON. Put one complete JSON object on each line; a JSON object split over several lines is parsed line by line. |
{"error":"Unknown service action","ok":false} |
The action name is wrong. See the JSON command reference. |
If any command failed, the headless simulator exits with code 1 when it ends. It keeps processing the commands after an error.
Next steps
Section titled “Next steps”- In A closer look at the first TCP exchange, send the same PDUs over a real socket.
- In The 19 function codes and diagnostics, run the remaining function codes.