# Publishing to an HMI over Modbus

The program acts as a Modbus server and the HMI reads from it. It listens on port `1502`. To use the standard Modbus port `502`, run the program as root; any port above 1024 works without root.

```python
#!/usr/bin/env python3
"""Expose backplane readings over Modbus TCP."""

import signal
import time

import synergy_logic
from pyModbusTCP.server import DataBank, ModbusServer

running = True

def shutdown(signum, frame):
    global running
    running = False

signal.signal(signal.SIGTERM, shutdown)
signal.signal(signal.SIGINT, shutdown)

base = synergy_logic.Base()
level = base[1][1]
valve = base[2][1]

bank = DataBank()
server = ModbusServer(host="0.0.0.0", port=1502, no_block=True, data_bank=bank)
server.start()

try:
    while running:
        # Publish the reading into input register 1
        bank.set_input_registers(1, [level.value])

        # Take the setpoint from holding register 1
        setpoint = bank.get_holding_registers(1, 1)[0]
        valve.value = min(setpoint, 4095)

        time.sleep(0.1)
finally:
    valve.value = 0
    server.stop()
```

Install the dependency with:

```bash
pip3 install --user --break-system-packages pyModbusTCP
```

This installs the package for the `slm-rp4` user only. A program run as root needs it installed with `sudo`.

```{admonition} Register numbering
:class: note

`pyModbusTCP` addresses registers by offset within their block, without the leading digit of the traditional 4xxxx / 3xxxx notation. Holding register `400001` is offset `0`, `400002` is offset `1`, `423000` is offset `22999`.

Most HMI packages use the long form. Getting this wrong is the usual cause of "the tag exists but never updates".
```

To build a dashboard on the device that connects to this server, see [FUXA: A Local HMI](../../../integrations/fuxa-hmi.md).
