Date: 2026-10-13
Previous release: v1.1.0
Two things in one release.
Robustness. A code review and new chaos, stress and fuzz test suites exposed defects in how the simulator behaves with concurrent clients, with clients that misbehave, and with requests outside the configured range. All of them are fixed; several fixes change behaviour a user can observe and are listed under Behaviour changes.
Fewer limitations. TLS, FC22 and FC23, broadcast, a control API to reset and reload a running simulator, per-connection state and PDU bytes in the trace: see Added. Existing fixtures and expectation files keep working.
- TLS listener —
run --tls-cert server.pem --tls-key server.key --tls-client-ca ca.pemserves Modbus/TCP Security instead of plain TCP. All three are required: clients must present a certificate that chains to the client CA. The trace records"transport": "tcp+tls". - FC22 (mask write register) and FC23 (read/write multiple registers) on holding registers. FC23 makes the write first and then the read, as one step; when either is not allowed nothing is written. Rule matches accept
"fc": 22and23; for FC23 anaddressmatches when the read or the write covers it. The trace request hasandMask/orMaskfor FC22 andwriteAddress/valuesnext toaddress/quantityfor FC23. Two new example fixtures. - Broadcast — a fixture with
"broadcast": truetreats unit0as the broadcast address: a write (FC 5, 6, 15, 16, 22) is executed by every device that can take it and no response is sent; any other request to unit 0 is ignored. The trace has one record with"unitId": 0,"response": {"ok": true, "noResponse": true}and aunitIdon each state change. Off by default, where unit 0 stays an unknown unit. - Control API —
run --control 127.0.0.1:5021servesPOST /reset,POST /reset?unit=N,POST /reloadandGET /healthzover HTTP, so a test suite can start the simulator once and reset or reload it between cases. A fixture that is not valid is refused and the simulator carries on unchanged. No authentication: use a loopback address. These replace the deferredresetViaControlandreloadFixturestate modes. - Real
timeoutandconnectionClosefaults — see the behaviour changes. - PDU bytes in the trace —
run --trace-rawadds"raw": {"request": "<hex>", "response": "<hex>"}to each record. The PDUs are encoded from the decoded request and the returned response, not captured from the wire.
-
timeoutandconnectionClosefaults do what their names say. Atimeoutrule leaves the request unanswered and keeps the connection open, so the client runs into its own timeout; aconnectionCloserule closes the connection without a response. Both used to be answered with a Server Device Failure exception, which never exercised a client's timeout or reconnect handling. The trace is unchanged (ok: false,fault). -
stateMode: resetOnConnectiongives every connection a state of its own. Each connection works on its own copy of the fixture values, discarded when it disconnects. It used to reset one shared state whenever a client sent its first request, which wiped the work of a client that was in the middle of a session, and a reconnect from the same address and port was taken for the old connection. A client now never sees another client's writes on such a device. -
The trace is written in
seqorder, and that is the order in which requests took effect. With concurrent clients, lines could appear in the file out ofseqorder, and neither order was guaranteed to match the order of the writes on the state. A state operation, itsseqand its trace record are now one step, soorderedandexactassertions see the real history. -
Every trace record is flushed as it is written. The trace can be read while
runis still serving, and survives an abrupt stop. Records used to stay in a buffer until shutdown. -
A trace record that cannot be written is an error.
runexits with code4("write trace file") when it stops; write errors used to be dropped silently. -
--max-clients 0means no limit, as documented. It was passed to go-modbus as is, where0selects the default of 10 clients, so the 11th client was disconnected. -
An unknown
--formatis a usage error (exit2) for every command, reported before any work is done.validateandassert-traceused to do the work first and then exit10. -
The trace schema accepts
unitId0–255(was1–247). A request to a unit that no fixture can configure, such as 0 or 255, was answered correctly withillegal_function, but its trace record was rejected by the trace schema, soassert-tracerefused the whole file. Fixtures still accept1–247only. -
A read that runs past address 65535 is
illegal_data_addressunder everyunknownAddresspolicy. WithzeroForReadsthe store answered with zeroes. go-modbus already refuses such a request on the wire, so this was reachable only through the handler.
- Client addresses were remembered forever under
resetOnConnection, one entry per connection ever made. uint64codec values above 9223372036854775807 were rejected. The full range up to 18446744073709551615 is accepted.make cleandeleted every*.jsonfile in the repository root, including fixtures and expectation files created by following the README.- A missing
matchin an expectation evaluated through the Go API caused a nil dereference; it is now a failed assertion.
github.com/otfabric/go-modbusv1.1.0 → v1.4.0. v1.3.0 brings server fixes that apply here: adelayfault longer than the idle timeout no longer loses its response, and a handler is cancelled when its client disconnects. v1.4.0 addsErrNoResponse, which broadcast needs.github.com/santhosh-tekuri/jsonschema/v6v6.0.2 → v6.0.3,github.com/stretchr/testifyv1.11.1 → v1.12.1,github.com/otfabric/go-serialv0.1.3 → v0.1.6.golang.org/x/textv0.14.0 → v0.41.0, the newest release that builds with Go 1.25.
- Stress suite (
stress_test.go): parallel clients that check every answer against a model of their own registers while contending for a shared block, the handler called from many goroutines, connection churn, the client limit, and shutdown with requests in flight. - Chaos suite (
chaos_test.go): misbehaving raw TCP clients (garbage, truncated and oversized frames, random PDUs, illegal quantities, slow loris, pipelining, half-closed and abandoned connections) next to well-behaved ones, which must not notice. - Fuzz targets (
fuzz_test.go): fixtures, expectation files and arbitrary request sequences, including broadcasts. - Feature tests (
features_test.go): FC22 and FC23, broadcast in the handler and over TCP, isolation ofresetOnConnectionclients,timeoutandconnectionClosefaults on the wire for every function code, the control API, a TLS round trip with certificates generated at run time, and the PDU encoding. - After each run the trace must be valid against its schema, numbered
1, 2, 3, …in file order, and replay as a sequential history of the fixture.make soakloops the chaos and stress suites (SOAKTIME, 2m by default) andmake fuzzruns the fuzz targets (FUZZTIME, 30s each); a failing run logs its seed, andMODBUS_SIMULATOR_TEST_SEED=<seed>replays it. - The command line (
cmd/) is covered by tests for every command and exit code, and CI now runs them.
- README: badges corrected (the retired Go Report Card badge removed, Code Coverage and Go Reference added), install from release binaries, sections on state, reset and reload, broadcast, TLS and limitations, and the chaos, stress and fuzz tests.
- REQUIREMENTS.md: a table of the earlier decisions this release revises, the new features, the
codecs listcommand and--trace-decodedflag, theseqordering guarantee,matchedRuleabsent rather thannull, and whattimeoutandconnectionClosefaults do on the wire.
go install github.com/otfabric/modbus-simulator@v1.2.0Or download a pre-built binary from GitHub Releases.
Requires Go 1.25+ when building from source.
None for fixtures, expectation files and traces: every v1.1.0 fixture and expectation file is valid, and so is a trace written by v1.1.0. What can affect an existing setup:
- A test that expected a Server Device Failure exception from a
timeoutorconnectionCloserule: the client now gets no answer, or a closed connection. - A test that relied on one client's connection resetting the state another client sees on a
resetOnConnectiondevice: see the first behaviour change. - Scripts that relied on exit code
10for an unknown--format, or on--max-clients 0limiting clients to 10. - The trace schema types
rawas an object (it was a string that was never written).
- No RTU/serial server; the listener is TCP, plain or with TLS.
- Codec fields are register areas only (no coil/bit codecs). Trace
decoded[]entries are omitted when a request only partially overlaps a multi-register codec field. --trace-rawPDUs are re-encoded rather than captured; the response of FC43 is not included.- Control API actions are not written to the trace.
Date: 2026-07-06
Previous release: v1.0.0
Codec-aware fixtures: write typed values in JSON instead of hand-encoding Modbus registers. Still modbus-simulator.fixture.v1 — raw uint16/bool points remain fully supported.
- Codec register fields — optional
codec(go-modbus stable ID) and typedvalueon holding/input register points. Register span is derived from the codec; values are encoded to wire registers at load time. - Two-tier validation — JSON Schema (
oneOfraw vs codec point) plus semantic checks: codec registry lookup, value coercion, encode/decode round-trip, non-overlapping address spans. codecs list— discover available codec IDs and metadata (--family,--format json). Wrapsgithub.com/otfabric/go-modbus/codec.run --trace-decoded— optionaldecoded[]overlay on trace records when codec fields are fully contained in the request window.- Examples — nineteen fixtures in
examples/: ten function-code fixtures plus nine codec bundles (one per go-modbus codec family: integer, float, text, bcd, bytes, decimal_limb, time, network, hardware_address). Together they cover every stable ID fromcodecs list(195 codecs) with non-overlapping addresses and round-trip-validated sample values. Regenerate bundles withmake generate-codec-examples(tools/generate-codec-examples). TCP integration tests exercise each fixture;TestExamples_AllCodecsHaveExampleguards full codec coverage.
{
"schemaVersion": "modbus-simulator.fixture.v1",
"devices": [{
"unitId": 1,
"state": {
"holdingRegisters": [
{ "address": 10, "codec": "float32/layout:4321", "value": 230.5, "name": "voltage" }
]
}
}]
}Run modbus-simulator codecs list for all stable IDs. See go-modbus CODECS.md for layout and value-kind details.
| Fixture | Family | Codecs |
|---|---|---|
codec-integers.json |
integer | 29 |
codec-floats.json |
float | 8 |
codec-text.json |
text | 60 |
codec-bcd.json |
bcd | 48 |
codec-bytes.json |
bytes | 26 |
codec-decimal-limb.json |
decimal_limb | 12 |
codec-datetime.json |
time | 8 |
codec-network.json |
network | 2 |
codec-hardware.json |
hardware_address | 2 |
Function-code fixtures (fc01–fc43, all-function-codes.json) are unchanged from v1.0.0.
go install github.com/otfabric/modbus-simulator@v1.1.0Or download a pre-built binary from GitHub Releases.
Requires Go 1.25+ when building from source. Depends on github.com/otfabric/go-modbus v1.1.0+ for the codec registry.
None. Existing v1 fixtures with raw integer register values validate and run unchanged.
Unchanged from v1.0.0 unless noted:
- TCP only; RTU/serial server and TCP+TLS listen are deferred.
timeoutandconnectionClosefault rules are recorded in traces but approximated on the wire.resetViaControlandreloadFixturestate modes are deferred.- Codec fields are register areas only (no coil/bit codecs). FC16 writes need not align to codec field boundaries; traces still emit per-register
stateChanges. - Trace
decoded[]entries are omitted when a request only partially overlaps a multi-register codec field.
Date: 2026-07-05
Previous release: none
First release of the Modbus TCP device simulator for gateway integration and CI testing.
- Fixture-driven simulation —
modbus-simulator.fixture.v1JSON defines one or more devices (coils, discrete inputs, holding/input registers, FC43 identity, exception/fault rules). - Stateful runtime — writes persist in memory; optional
resetOnConnectionper device. - JSONL traces —
run --tracerecordsmodbus-simulator.trace.v1logs with deterministicseq, responses, andstateChanges. - CI assertions —
assert-traceevaluatesmodbus-simulator.expect.v1files (contains,notContains,count,ordered,exact). - CLI —
validate,run,assert-trace,version, and shellcompletion. - Schemas — canonical JSON Schemas in
schemas/(fixture-v1.json,trace-v1.json,expect-v1.json), embedded in the binary for offline validation. - Examples — ten ready-to-run fixtures in
examples/(one per supported FC plus an all-in-one fixture), each covered by TCP integration tests. - License — MIT (LICENSE).
Download the binary for your platform from GitHub Releases, or:
go install github.com/otfabric/modbus-simulator@v1.0.0Requires Go 1.25+ when building from source.
Pre-built binaries are published for linux/amd64, linux/arm64, linux/arm/v7, darwin/amd64, darwin/arm64, windows/amd64, and windows/arm64.
None (initial release).
- TCP only; RTU/serial server and TCP+TLS listen are deferred.
timeoutandconnectionClosefault rules are recorded in traces but approximated on the wire (go-modbus handler constraints).resetViaControlandreloadFixturestate modes are deferred.- Register values in fixtures were raw unsigned integers only (no typed codecs — added in v1.1.0).