Repository navigation
Date: 2026-10-11
Previous release: v2.4.0
Summary
Patch release: adopts go-modbus v1.3.0, which hardens the client against lost, slow and out-of-step connections. No changes to commands, flags or JSON output.
Changes
- go-modbus: Requires
github.com/otfabric/go-modbusv1.3.0 (was v1.2.1). - No stale answers after a timeout (TCP/TLS): After a request times out or a response is malformed, the connection is replaced before the next request. A late response to the timed-out request can no longer be taken for the answer to the next one, which matters most for
client poll,client recordandclient scanagainst slow devices. - Reconnects by itself: A long-running
client pollorclient recordwhose connection drops fails that one sample and continues on a new connection with the next. - Ctrl-C and
--timeouttake effect immediately: A request in flight is interrupted instead of running to its timeout.
v2.4.0
Date: 2026-10-09
Previous release: v2.3.0
Summary
Closes the capability gap with classic poll-and-write tools. modbusctl now speaks Modbus RTU and ASCII on serial lines, Modbus UDP, and RTU/ASCII framing through serial-to-Ethernet bridges; it can write coils and holding registers (client write) and poll continuously at an interval without recording (client poll). Every existing client command works over the new transports. No breaking changes to flags or JSON output.
Highlights
- Transports (all client commands):
--urlnow acceptsrtu://<device>,ascii://<device>,udp://host:port,rtuovertcp://,rtuoverudp://andasciiovertcp://in addition totcp://. Serial devices are written asrtu:///dev/ttyUSB0(Linux/macOS) orrtu://COM3(Windows). - Serial settings: New
--baud,--data-bits,--parity(none/even/odd) and--stop-bitsflags on every client command, withMODBUSCTL_BAUD,MODBUSCTL_DATA_BITS,MODBUSCTL_PARITYandMODBUSCTL_STOP_BITS. Defaults: 19200 baud, 8 data bits for RTU and 7 for ASCII, no parity, 2 stop bits without parity or 1 with. They are ignored for network URLs. client write(new): Writes coils (FC05, FC15) and holding registers (FC06, FC16).--valuestakes one value or a comma-separated list; with--functionomitted, one register uses FC06 and several use FC16.--codecencodes typed values (floats, 32/64-bit integers, strings, IP addresses, timestamps) with the same codec registry asclient decode.--startis mandatory, everything is validated before anything is sent, and a write is sent exactly once (never retried).client poll(new): Reads the same block of coils, discrete inputs or registers every--intervalms and prints each sample as it is read — one line (text), one JSON object per line (json) or one row (table) — until--pollssamples or Ctrl-C. Optional--codecdecoding. Failed polls are reported in-stream and polling continues (reconnecting when needed); exit code 7 when any poll failed. Nothing is written to disk;client recordremains the MCAP capture command.- Serial buses: On
rtu://andascii://,identifyandreportserveridignore--paralleland query units one after the other, since a serial line carries one request at a time. - MCAP: Captures made over a serial line store the device path in the header
ipfield with port 0. The format is unchanged. - Completion: Tab completion for
--parityand forclient write --function(5, 6, 15, 16). - go-modbus: Requires
github.com/otfabric/go-modbusv1.2.0, which adds the Modbus ASCII transport and corrects function probing.
Fixes
- Silent empty result after a dropped connection:
client read,decodeandscancould report success with no data when the connection was reset during the request: the reconnect succeeded and its result overwrote the read error. The read error is now reported (and counted as a transport failure byscan). client scan --algo safe(the default) lost registers: After finding a block that followed unreadable addresses, the scan skipped one register (for example the last register of a short block), and could read one register below--start. Both are fixed.client scanwith--end 65535(the default):--algo smartand--algo deepnever finished building their work list and ran out of memory, and--algo safecould wrap around instead of finishing. Position arithmetic no longer overflows at the top of the address space.client scan --algo linear: No longer loops forever when the range reaches address 65535, and no longer issues zero-register reads for the full range 0–65535.client scan --algo boundary: Finds the left edge of blocks of any size (it used to stop after the first failed read), never reads more than 125 registers at once, and handles blocks and seeds that reach address 65535.client fingerprintandclient sunspec probe: A function is reported as supported only when the device implements it. Previously any exception counted, so a device answering Illegal Function — or a gateway answering for an absent unit — appeared to support every probed function; and a device that correctly echoed FC08 was reported as not supporting diagnostics. (Fixed in go-modbus v1.2.0.)- MCAP files cut short: A capture truncated exactly between two fields of a record was read as complete, silently dropping the damaged record. Every
mcapcommand now reports it as an unexpected end of file. A record too large for the format is rejected instead of corrupting the file. server static/server replaywith coils or discrete inputs: A request for more than one coil returned wrong values for every coil after the first. Multi-coil reads now return the recorded bits.server replay: Fixed a data race between the startup message and the replay loop.client sunspeccommands:--timeoutnow bounds every read. Previously a device that did not answer blocked for the whole operation budget (10 s or more) on the first base address.client identify/client reportserveridwith several units: Ctrl-C now stops at the next unit. Previously every remaining unit still waited for its full timeout.- Error classification: A connection closed by the device is reported as a transport error (exit code 4), not an internal error (10).
--format table: The rule line under the header no longer ends in trailing spaces.- Validation message: An
--outputpath whose parent is a file reported%!w(<nil>); it now says the output directory is not a directory. - Error text: Connection failures are reported as
connection error: …instead ofTCP connection error: …, since the transport may be a serial port. The JSONkind(transport) andcode(transport_connect_failed) are unchanged. - Documentation:
float32/layout:1234was described as big-endian in examples; it is little-endian. Examples now usefloat32/layout:4321(big-endian).client readreadsMODBUSCTL_ADDRESS(notMODBUSCTL_START) for--start; the help example and README now say so.
Quality
- Coverage: Test coverage raised from 51% to 96% of the whole module, with a floor of 85% enforced by
make coverage(COVERAGE_MIN) and a Codecov project target; CI now tests thecmdpackages too. A Code Coverage badge replaces the retired Go Report Card badge. - Hardware-free tests: New
internal/mbtestin-memory Modbus device served over TCP, UDP, RTU/ASCII over TCP, and RTU/ASCII on a pseudo-terminal, so serial transports are exercised end to end in CI. - Makefile: Sets
GOWORK=offso a parentgo.workcannot interfere.
Upgrading
- Scripts matching error text: Replace matches on
TCP connection errorwith the JSONcode(transport_connect_failed) or the exit code (4). - Completion users: Regenerate and re-source your shell completion script to pick up the new commands and flags.
- Float examples: If you copied
float32/layout:1234from earlier documentation expecting big-endian values, switch tofloat32/layout:4321. Codec behaviour itself has not changed.
Release workflow
- Unchanged from v2.3.0: reusable
go-binary-releaseworkflow and link-time version metadata.