Description
HydroBridge is a Python library that reads registers from an already existing PLC/HMI device over Modbus TCP, decodes them into named values using a register map you supply, and publishes them as telemetry (Azure IoT Hub out of the box).
Pretty useful for scenarios, when you need to send some data from a PLC to a remote server (Cloud/on-premises) for some analytical/statistical purposes through a separate device.
Example:
HydroBridge
|
|
V
PLC <---------> Device with Internet connection <---------> Data Server
If the data server is unreachable, the most recent readings (20 by default) are kept and sent once it's back. If the PLC drops the connection, the Modbus adapter reconnects on its own.
Installation
The core (hydrobridge) has no dependencies; the adapters need extras:
pip install "hydrobridge[modbus,azure]" # Modbus source + Azure IoT Hub publisher
pip install "hydrobridge[modbus]" # only the Modbus source
pip install hydrobridge # only the core, with your own source/publisher
Requires Python 3.11+.
Usage
import asyncio
from hydrobridge import RegisterField, RegisterMap, RegistersProcessingService
from hydrobridge.adapters.modbus import ModbusPLCAdapter
from hydrobridge.adapters.azure_iot import AzureIoTHubPublisher
register_map = RegisterMap([
RegisterField("tank_level", address=0, data_type="float32", word_order="low_high", decimals=2),
RegisterField("pump_state", address=2, data_type="uint16"),
])
async def main():
async with ModbusPLCAdapter(
host="192.168.1.10", port=502, device_id="gateway-1",
start_address=register_map.start_address, count=register_map.register_count,
) as plc, AzureIoTHubPublisher(connection_string="HostName=...") as azure:
service = RegistersProcessingService(plc, azure, register_map)
while True:
await service.run_once() # read one block, publish it (and anything still pending)
await asyncio.sleep(5)
asyncio.run(main())
Each reading is published as JSON:
{"deviceId": "gateway-1", "ts": "2026-10-19T12:00:00", "registers": {"tank_level": 883.8, "pump_state": 1}}
Register map
Each RegisterField names one value in the PLC's registers:
| Argument | Meaning |
|---|---|
name |
key in the published registers |
address |
Modbus address of the field's first register |
data_type |
float32 (default), int32, uint32 (2 registers each), int16, uint16 (1 register) |
word_order |
for 32-bit types: low_high (default, low word first) or high_low |
decimals |
round the value to this many decimals; None (default) keeps it as decoded |
RegisterMap rejects duplicate names and overlapping fields, and gives the block to read: start_address and register_count (a Modbus read is limited to 125 registers).
The map can also live in a file, .toml or .json, with the same keys:
[[register]]
name = "tank_level"
address = 0
data_type = "float32"
word_order = "low_high"
decimals = 2
[[register]]
name = "pump_state"
address = 2
data_type = "uint16"
from hydrobridge import load_register_map
register_map = load_register_map("plc_map.toml")
Your own source or publisher
The service works with anything implementing the ports in hydrobridge.ports:
RegisterSourcePort.read_block()returns aRawRegisterBlockand raisesSourceConnectionErrorwhen the server can't be reached;TelemetryPublisherPort.publish(reading)raisesPublishFailedErrorwhen a reading wasn't delivered (the service keeps it and retries).
The adapters also accept a ready-made client, e.g. for tests or a custom-configured connection: ModbusPLCAdapter(..., client_factory=lambda: my_client) (a factory, because a dropped easymodbus client can't reconnect and is replaced), AzureIoTHubPublisher(client=my_iot_hub_client).
Logging
HydroBridge logs through the standard logging module under the hydrobridge logger and doesn't configure logging itself. Without any setup only warnings are shown. To see more:
import logging
logging.basicConfig(format="%(asctime)s %(levelname)s %(name)s: %(message)s")
logging.getLogger("hydrobridge").setLevel(logging.DEBUG) # or logging.INFO
- WARNING: failed connection attempts, a dropped PLC connection, failed publishes, readings dropped because the pending queue is full
- INFO: connected to the PLC
- DEBUG: every block read and every reading published
Set the level on "hydrobridge" rather than with basicConfig(level=...): easymodbus logs every request and response on the root logger at INFO and can change the root logger's level.
Example application
The example application in app/ (not part of the package) wires the Modbus source to Azure IoT Hub, with the device and its register map in app/config/devices_config.json. Run it from the project root:
poetry install --all-extras --all-groups
python -m app.main --instance server # local Modbus test server fed from app/input.csv
python -m app.main --instance client # poll the PLC and publish to Azure
python -m app.main --instance client --log-level DEBUG
The client reads the Azure connection string from project_settings.py (not in the repository).
Development
poetry install --all-extras --all-groups
poetry run pytest # everything
poetry run pytest -m "not packaging" # skip the slower wheel/venv test
GitHub Actions runs the tests on Python 3.11–3.14 on every push and pull request.
Releasing
-
Set the new version in
pyproject.toml(poetry version patch,minorormajor) and commit it. -
Tag the commit with the same version and push the tag:
git tag v0.1.1 git push origin v0.1.1
GitHub Actions (release.yml) then runs the tests, builds the package, publishes it to TestPyPI, checks that it installs from there, and publishes it to PyPI. A version number can only be uploaded once.
License
MIT, see LICENSE.
TODO
- Transform main.py into a polling client: to poll everything from server.py, and to generate a csv file once in 2 mins.
Metadata
Release files for hydrobridge 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| hydrobridge-0.1.0.tar.gz | 12.1 kB | With provenance | Details for hydrobridge-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| hydrobridge-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for hydrobridge-0.1.0-py3-none-any.whl |
Total release size: 25.0 kB
Release files / hydrobridge-0.1.0.tar.gz
| Download URL | hydrobridge-0.1.0.tar.gz |
|---|---|
| Size | 12.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
211b0a9e86de2cb77d7222f8501296aa9c7ca610b6d9a6e94cd2f738d7339870
|
|
BLAKE2b-256 checksum How to use checksums |
d25695643d1a572c1cc5eec80795f286ff9cb89e7fec269236c1e0b5d37d272e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logRelease files / hydrobridge-0.1.0-py3-none-any.whl
| Download URL | hydrobridge-0.1.0-py3-none-any.whl |
|---|---|
| Size | 12.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5f91fa169d1b5dce1b6a566b3ecc03b97da390326c177ebd4672085ef81ff5aa
|
|
BLAKE2b-256 checksum How to use checksums |
4b5de6968c1c7e039a6c426a7c5fce7c7de02c76a36b0525f3322e278ec8ac6b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logSecurity for hydrobridge 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for hydrobridge-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.