I started working on a little Modbus CLI utility: GitHub - kevinherron/modbus-cli: Native CLI utility for reading, writing, and scanning Modbus TCP
Ripped from the README, some quick examples:
Quick Examples
Start a test server:
$ modbus server
Modbus server started on 0.0.0.0:502
The server initializes with 65536 holding registers, each pre-populated with its address as a value.
Leave this running in one terminal while trying the client examples below in another.
Read holding registers:
$ modbus client localhost rhr 0 10
Hostname: localhost:502, Unit ID: 1
→ ReadHoldingRegistersRequest[address=0, quantity=10]
← ReadHoldingRegistersResponse[registers=0000000100020003000400050006000700080009]
Offset (hex) Bytes (hex)
-------------------------
00000000 00 00 00 01 00 02 00 03 00 04 00 05 00 06 00 07
00000010 00 08 00 09 .. .. .. .. .. .. .. .. .. .. .. ..
Get JSON output for automation:
$ modbus --format=json client localhost rhr 0 10
{"timestamp":"2025-11-02T23:07:57.618695Z","type":"info","message":"Hostname: localhost:502, Unit ID: 1"}
{"timestamp":"2025-11-02T23:07:57.627904Z","type":"register_table","start_address":0,"quantity":10,"data":[0,0,0,1,0,2,0,3,0,4,0,5,0,6,0,7,0,8,0,9]}
Write then read back a register:
$ modbus --format=json client localhost wsr 100 42
{"timestamp":"2025-11-02T23:08:09.316542Z","type":"protocol","direction":"received","function_code":6,"pdu":"060064002a"}
$ modbus --format=json client localhost rhr 100 1
{"timestamp":"2025-11-02T23:08:09.332309Z","type":"register_table","start_address":100,"quantity":1,"data":[0,42]}
Scan a range of registers:
$ modbus client localhost scan 0 50 --size=10
Hostname: localhost:502, Unit ID: 1
Address Values (hex, 2 bytes each)
-------------------------------------------------------
0000 0000 0001 0002 0003 0004 0005 0006 0007
0008 0008 0009 000A 000B 000C 000D 000E 000F
0010 0010 0011 0012 0013 0014 0015 0016 0017
0018 0018 0019 001A 001B 001C 001D 001E 001F
0020 0020 0021 0022 0023 0024 0025 0026 0027
0028 0028 0029 002A 002B 002C 002D 002E 002F
0030 0030 0031
Poll and filter JSON output with jq:
$ modbus --format=json client localhost rhr 0 10 -c 5 | jq -c 'select(.type == "register_table") | {timestamp, data}'
{"timestamp":"2025-11-02T23:12:37.072923Z","data":[0,0,0,1,0,2,0,3,0,4,0,5,0,6,0,7,0,8,0,9]}
{"timestamp":"2025-11-02T23:12:38.075708Z","data":[0,0,0,1,0,2,0,3,0,4,0,5,0,6,0,7,0,8,0,9]}
{"timestamp":"2025-11-02T23:12:39.081328Z","data":[0,0,0,1,0,2,0,3,0,4,0,5,0,6,0,7,0,8,0,9]}
{"timestamp":"2025-11-02T23:12:40.086801Z","data":[0,0,0,1,0,2,0,3,0,4,0,5,0,6,0,7,0,8,0,9]}
{"timestamp":"2025-11-02T23:12:41.088714Z","data":[0,0,0,1,0,2,0,3,0,4,0,5,0,6,0,7,0,8,0,9]}
It's still at version 0.1-SNAPSHOT. There are binaries you might be able to download here, not sure about the permissions though.
If anybody wants to play with it and give me some feedback about the output format or missing features that would be helpful.
Already I'm pretty sure I want the data output in the examples above to be an array of 16-bit register values instead of bytes, and then to include a "raw_data" field that has the raw hex-encoded bytes similar to the "pdu" field in the protocol messages ("pdu":"060064002a").
The default server behavior is kind of a placeholder as well. Haven't thought too much about that side of it yet.