jollyjinx/modbus-2-mqtt-bridge
modbus2mqtt exposes Modbus TCP and RTU devices through MQTT. It polls configured registers and coils, publishes typed JSON values, and translates validated MQTT request messages into Modbus writes.
Highlights
- Modbus TCP and serial RTU transports
- Read and write support through MQTT request/response topics
- Multi-device and multi-unit configurations
- Typed integers, floating-point values, strings, addresses, mappings, and bit fields
- Configurable scaling, floating-point resolution, polling, retention, and unchanged-value heartbeat
- Long-running retry behavior and optional HTTP device reset
- Linux container images for
amd64andarm64 - Bundled device definitions plus support for custom JSON definitions
Run the container
The stable image is published as ghcr.io/jollyjinx/modbus-2-mqtt-bridge:latest:
container run --name modbus2mqtt \
ghcr.io/jollyjinx/modbus-2-mqtt-bridge:latest \
modbus2mqtt \
--modbus-server lambda.local \
--mqtt-servername mqtt.local \
--topic lambda \
--device-description-file lambda.jsonThe default MQTT host is mqtt, the default port is 1883, and the default Modbus TCP port is 502. Use a pinned release tag instead of latest when reproducible deployment is important. The development image tag tracks the development branch.
To build locally:
container build . \
--file modbus2mqtt.product.dockerfile \
--tag modbus2mqtt
container run --name modbus2mqtt \
modbus2mqtt \
--modbus-server lambda.local \
--device-description-file lambda.json \
--topic lambdaBuild from source
Source builds require Swift 6.3 or newer. The package is developed for macOS and Linux.
swift build -c release --product modbus2mqtt
.build/release/modbus2mqtt --helpSingle-device configuration
Use --modbus-server, --modbus-port, and --modbus-address for Modbus TCP. For RTU, provide --modbus-device-path and optionally --modbus-serial-speed.
modbus2mqtt \
--modbus-server meter.local \
--modbus-address 1 \
--mqtt-servername mqtt.local \
--topic meters/main \
--device-description-file b+ge-tech.sd100-00b.jsonBroker credentials are available through --mqtt-username and --mqtt-password. Run modbus2mqtt --help for the complete, version-matched option list.
Multiple devices
Use --modbus-devices-file for a JSON configuration file or --modbus-devices-string for inline JSON. These options are mutually exclusive and cannot be mixed with the legacy single-device endpoint, topic, definition, or reset options.
The checked-in example configuration demonstrates two unit addresses behind one gateway and another device on a separate endpoint:
container run --name modbus2mqtt \
--volume "$PWD/Examples/config:/config:ro" \
ghcr.io/jollyjinx/modbus-2-mqtt-bridge:latest \
modbus2mqtt \
--mqtt-servername mqtt.local \
--modbus-devices-file /config/modbus-devices.jsonEach device entry provides networkAddress, optional port (default 502), optional modbusAddress (default 3), unique MQTT topic, deviceDescriptionFile, and optional deviceResetURL. Entries with the same host and port share a connection. They must use different host/unit combinations and MQTT topics; a shared endpoint must also use one consistent reset URL.
See the multi-device architecture for validation rules, topic routing, resource ownership, and recovery behavior.
Bundled device definitions
| Definition | Device | | --- | --- | | b+ge-tech.sd100-00b.json / .minimal.json | B+G E-Tech SD100-00B energy meter | | daheimladen.json | DaheimLader wallbox | | eastron.sdm72dm-v2.json / .minimal.json | Eastron SDM72DM-V2 energy meter | | goodwe-et15-30.json | GoodWe ET 15–30 solar inverter | | hanmatek.hm310t.json | Hanmatek HM310T laboratory power supply | | lambda.json | Lambda Eureka heat pumps | | lambda.solartherm.json | Lambda heat pump with solar thermal integration | | nibe.s2125.json | NIBE S2125 air/water heat pump | | phoenix.evcharger.json | Phoenix Contact EV charge controller | | sma.sunnyboy.json / .all.json | SMA Sunny Boy inverter | | sma.sunnystore.json / .all.json | SMA Sunny Boy Storage |
Definitions bundled into the executable may be selected by filename. A readable filesystem path can be used for a custom definition. The .minimal.json files reduce the published register set; the SMA .all.json files expand it. For SMA-specific integrations, sma2mqtt may be a better fit.
The format, supported fields, and copyable examples are documented in docs/device-definitions.md.
MQTT topics and writes
Values are published below the configured base topic. A writable definition can be changed by publishing a request to:
<base-topic>/request/<request-name>The bridge replies on the corresponding response path:
<base-topic>/response/<request-name>Example request:
{
"value": 14.04,
"date": "2026-07-22T08:43:22Z",
"topic": "set/voltage",
"id": "D2129DBF-9F94-46D7-86BC-4A07152FF1D8"
}Requests outside --mqtt-request-ttl are rejected. The response uses the same UUID so clients can correlate the result. See docs/mqtt-request-response.md for the contract.
Publication behavior
Changed values publish on their next configured poll. By default, unchanged non-retained values publish at most once every 15 seconds. Set --mqtt-unchanged-publish-interval 0 to publish them on every poll.
This heartbeat does not add Modbus reads; a slowly polled value publishes on its next scheduled poll. Explicitly retained definitions keep their retained behavior, while "publishalways": true bypasses unchanged-value suppression. --mqtt-auto-retain-time controls automatic retention separately.
Device reset
--device-reset-url can issue an HTTP GET after repeated communication failures, wait for the device to recover, and resume bridge attempts. Treat this URL as an operational control: restrict access to the bridge host, avoid embedding credentials in committed configuration, and verify that repeated calls are safe for the target device.
In multi-device mode the reset URL belongs to the physical host/port endpoint. A reset can affect every unit behind that endpoint.
Development and documentation
swift build
swift testThe test suite includes JSON decoding, device configuration, publication policy, request routing, and bridge-level behavior. Tests and builds should not require live Modbus devices or an external MQTT broker unless a test explicitly documents that dependency.
Start with DOCUMENTATION.md for the maintained documentation map. Agent-oriented routing is available in AI/README.md.
License
modbus2mqtt is available under the MIT License. Its dependencies retain their respective licenses.
Package Metadata
Repository: jollyjinx/modbus-2-mqtt-bridge
Default branch: main
README: README.md