Skip to content

Command-line tool

modbusctl is a command-line tool for Modbus over TCP, UDP and serial lines (RTU and ASCII). It is built on go-modbus and covers the whole path from “what is this device?” to a hardware-free replay of it. Output is text, a table or JSON, with stable exit codes for scripts and CI.

Pre-built binaries for Linux (x86-64, ARM64, ARMv7), macOS (Apple Silicon, Intel) and Windows (x86-64, ARM64) are on GitHub Releases. Each release has a modbusctl-<version>-checksums.txt file with SHA-256 sums. No Go toolchain is needed.

Terminal window
VERSION=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/otfabric/modbusctl/releases/latest | sed 's|.*/v||')
case "$(uname -m)" in
x86_64) ARCH=amd64 ;;
aarch64|arm64) ARCH=arm64 ;;
armv7l) ARCH=armv7 ;;
esac
FILE="modbusctl-${VERSION}-linux-${ARCH}.tar.gz"
BASE="https://github.com/otfabric/modbusctl/releases/download/v${VERSION}"
curl -fsSLO "${BASE}/${FILE}"
curl -fsSLO "${BASE}/modbusctl-${VERSION}-checksums.txt"
grep "${FILE}" "modbusctl-${VERSION}-checksums.txt" | sha256sum -c -
mkdir -p modbusctl-dl && tar -xzf "${FILE}" -C modbusctl-dl
sudo install -m 0755 "$(find modbusctl-dl -type f -name 'modbusctl*' | head -n1)" /usr/local/bin/modbusctl

The README also has a PowerShell script for Windows that verifies the checksum and adds the binary to your PATH. Check the install with modbusctl version.

Every client command takes either --url or --ip with an optional --port (Modbus TCP). The two styles are mutually exclusive.

URL Transport
tcp://host:port Modbus TCP (same as --ip host --port port)
udp://host:port Modbus TCP framing over UDP
rtu://<device> Modbus RTU on a serial line
ascii://<device> Modbus ASCII on a serial line
rtuovertcp://host:port RTU framing through a serial-to-Ethernet bridge
rtuoverudp://host:port RTU framing over UDP
asciiovertcp://host:port ASCII framing through a serial-to-Ethernet bridge

The serial flags apply to rtu:// and ascii:// and are ignored for network URLs:

Flag Values Default
--baud any rate the adapter supports 19200
--data-bits 5, 6, 7, 8 8 for RTU, 7 for ASCII
--parity none, even, odd none
--stop-bits 1, 2 2 without parity, 1 with
Terminal window
# RTU at 9600 8E1, unit 3
modbusctl client read --url rtu:///dev/ttyUSB0 --baud 9600 --parity even --unit 3 --start 0 --count 4
# RTU device behind a serial-to-Ethernet bridge
modbusctl client scan --url rtuovertcp://192.168.1.50:4001 --unit 7 --start 0 --end 200 --output scan.mcap

--unit accepts a single ID, a range (1-10), a list (1,5,25), a mix (1-10,255) or all on the commands that probe several units. Each absent unit costs one --timeout, so use a short timeout (for example --timeout 300) when sweeping a bus. On serial lines --parallel is ignored.

modbusctl
├── client
│ ├── identify FC43 Read Device Identification (+ optional FC17)
│ ├── fingerprint Probe supported read functions per unit
│ ├── diagnostic FC08 Diagnostics
│ ├── reportserverid FC17 Report Server ID
│ ├── read Read registers (FC01/FC02/FC03/FC04)
│ ├── decode Read and decode registers via a codec (FC03/FC04)
│ ├── codecs List available register codecs
│ ├── poll Poll coils/registers at an interval
│ ├── write Write coils (FC05/FC15) or holding registers (FC06/FC16)
│ ├── scan Scan for readable register blocks
│ ├── record Record registers over time to MCAP
│ └── sunspec detect | models | map | probe
├── server
│ ├── static Serve scanned MCAP data
│ └── replay Replay recorded MCAP data
├── mcap convert | decode | extract | info | strings | frequencies
├── discover Discover Modbus devices on subnets
├── completion bash | zsh | fish | powershell
└── version
  1. Identify the device and check which read functions it answers:

    Terminal window
    modbusctl client identify --ip 192.168.1.10 --server-id
    modbusctl client fingerprint --ip 192.168.1.10 --unit 1-10
  2. Find the readable register blocks. Start gently:

    Terminal window
    modbusctl client scan --ip 192.168.1.10 --start 0 --end 1000 --delay 100 --output scan.mcap
  3. Decode a value you found:

    Terminal window
    modbusctl client decode --ip 192.168.1.10 --start 100 --codec uint32/layout:4321 --count 8 --format json
  4. Watch it change:

    Terminal window
    modbusctl client poll --ip 192.168.1.10 --function 4 --start 0 --codec float32/layout:4321 --interval 500 --polls 20

client scan --algo selects the strategy. The default is safe.

Algorithm Use it for
safe Fragile devices and first passes. Linear, descending block sizes, fixed delay, no retry storms.
smart Unknown devices. Adaptive interval splitting. The README calls it the best default.
deep smart plus refinement around block edges. Lab work and reverse engineering.
stepped Cheap triage of a huge range at --step positions.
linear Long contiguous maps, such as PLCs.
boundary Grow a known-good read (--seed-start, --seed-count) to its full extent.
sunspec Find the SunSpec base and capture every model to MCAP.

ALGOS.md specifies each algorithm, including worst-case request counts.

client decode, client poll and client write share the go-modbus codec registry. modbusctl client codecs lists the IDs, with filters such as --family float or --registers 2. Examples: float32/layout:4321 (big-endian float), uint32/layout:2143 (word-swapped), ascii/registers:8, ip_addr.

Terminal window
# One holding register (FC06)
modbusctl client write --ip 192.168.1.10 --start 100 --values 1234
# Switch coil 8 on (FC05)
modbusctl client write --ip 192.168.1.10 --function 5 --start 8 --values on
# A big-endian float32 setpoint: two registers, FC16
modbusctl client write --ip 192.168.1.10 --start 200 --codec float32/layout:4321 --values 21.5

The safety model is explicit:

  • --start is required. There is no default address for a write.
  • Values, codec, quantity limits and the address range are all validated before anything is sent. A rejected command exits with code 3 and has sent nothing.
  • A write is sent exactly once and never retried. If it times out, read the value back before sending it again.
  • The documentation describes no dry-run mode and no confirmation prompt. Rehearse against a simulator instead.

--format text|json|table (or MODBUSCTL_OUTPUT_FORMAT) controls the final result on stdout for the client commands. Progress for scan and record goes to stderr, so stdout stays a single parseable result. client poll --format json writes one JSON object per poll (JSON Lines). With --format json, a fatal error is one JSON object with kind, code and message.

mcap convert --format is a different flag: it chooses csv or json export (environment variable MODBUSCTL_FORMAT).

Code Meaning
0 Success
2 Usage error (reserved)
3 Invalid input, flags or configuration
4 Transport: TCP or UDP dial failure, or a serial port that cannot be opened
5 Timeout (documented as reserved for typed timeouts; a timed-out client write exits 5)
6 Protocol error or Modbus exception
7 Partial success: valid output, but some units, scan requests or polls failed
10 Output, internal or unknown error

Match on the exit code or the JSON code, not on message text.

Most flags have a MODBUSCTL_* equivalent, for example MODBUSCTL_URL, MODBUSCTL_IP, MODBUSCTL_UNIT, MODBUSCTL_TIMEOUT (milliseconds; 0 means 10000), MODBUSCTL_BAUD, MODBUSCTL_PARITY, MODBUSCTL_CODEC and MODBUSCTL_ALGO. Note that client read, decode, poll and write take their start address from MODBUSCTL_ADDRESS, while client scan uses MODBUSCTL_START.

Terminal window
MODBUSCTL_URL=rtu:///dev/ttyUSB0 MODBUSCTL_BAUD=9600 MODBUSCTL_PARITY=even MODBUSCTL_UNIT=3 \
modbusctl client poll --start 0 --count 4

MCAP (Modbus Capture) is modbusctl’s own file format, inspired by PCAP: a JSON header with the target and function code, followed by binary records with timestamps, addresses and raw register data. client scan and client record write it; the mcap commands analyse it.

Terminal window
# Turn a scan into a block list, then record those blocks every second for a minute
modbusctl mcap extract --input scan.mcap --output register-ranges.json
modbusctl client record --ip 192.168.1.10 --blocks-file register-ranges.json --interval 1000 --duration 60000 --output session.mcap
# Serve the data as a Modbus TCP device: fixed values, or replayed over time
modbusctl server static --input session.mcap --port 1502
modbusctl server replay --input session.mcap --port 1502 --loops 0
# Look for device names and ~50 Hz frequency readings
modbusctl mcap strings --input session.mcap
modbusctl mcap frequencies --input session.mcap

server and discover are Modbus TCP only. The servers listen on all interfaces and accept any client, so run them on trusted networks. discover caps large subnets unless you pass --force-large-scan, and --resolve-mac uses raw sockets that may need CAP_NET_RAW.

client sunspec does transport-level SunSpec discovery: the SunS marker, the base address and the model headers. It does not decode points; use sunspecctl for that.

Terminal window
modbusctl client sunspec detect --url tcp://192.168.1.10:502 --unit 1
modbusctl client sunspec models --ip 192.168.1.10 --unit 1 --base 40000 --format json
modbusctl client sunspec probe --url tcp://192.168.1.10:502 --unit 1