# Modbus

Read and write Modbus TCP and RTU devices

- Category: Industrial
- Capabilities: Streams, Assets, Commands, Operations, Entities
- Runs on: Linux x64, Linux ARM64, macOS Apple silicon, macOS Intel, Windows x64

Read and write PLCs, power meters, sensors and process controllers over
**Modbus** **TCP** or serial (**RTU**). One connector covers any Modbus device:
you describe yours with a **register map**. No code needed.

## What you get

- **Live readings** — each mapped value is its own channel, scaled into
  engineering units.
- **Setpoints** — values marked writable can be set from **Trellis** via
  `ch.write(path, value)`. Each write is read back, so you see what the device
  took.
- **One device per input**, named by its **Device name**. Channels read
  `/modbus/plc1/…`.

## Setup

1. Note the device's address and unit ID (usually `1`). The standard Modbus
   TCP port is **502**. For an RTU device, note its serial port
   (`/dev/ttyUSB0`, `COM3`) and line settings.
2. In **Inputs**, choose **Add input**, pick **modbus**, and fill in:
   - **Device name** — a short label, e.g. `plc1`
   - **Connection** — `192.168.1.60:502`, or the serial port for RTU
   - **Transport** — `tcp` (default) or `rtu`. RTU uses **Serial baud rate /
     parity / stop bits / data bits** (default 9600 8N1).
   - **Model** — pick a built-in map, **or** paste your device's map into
     **Register map**. Each entry names a value, its address, type and scaling.
3. Press **Test connection**. It reads the first mapped register, so a pass
   means the device answered.
4. Channels appear on **Live**. Writable entries are available in **Trellis**.
5. In the **Channels** table, give a channel a name and unit, such as
   `temp` → `bearing temperature`, `degC`.

### Register map example

```json
[
  {"alias": "temp", "address": 0, "kind": "input", "dtype": "i16",
   "scale": {"gain": 0.1, "offset": 0}, "unit": "degC"},
  {"alias": "pump_speed_sp", "address": 10, "kind": "holding",
   "dtype": "u16", "unit": "rpm", "writable": true}
]
```

- Addresses are zero-based: a manual's `40001` is address `0`.
- Types: `u16`, `i16`, `u32`, `i32`, `f32`, and `bool` for coils and discrete
  inputs.
- For a 32-bit value sent low word first, set `word_swap`.

## Dead-man (heartbeat)

If this computer hangs, loses power or loses the network, a Modbus output
keeps its last value. A **dead-man** lets your PLC take over. While a test
setup is deployed, Phloem keeps changing a **heartbeat** register. Your PLC
drives its outputs safe when the heartbeat stops changing.

Your PLC needs a few lines of logic, or a drive's communication-loss setting:

- The heartbeat is one **holding register** (`u16`).
- **`0` means "not supervised"**: do nothing while it reads `0`.
- Any other value must **change** at least once per timeout. If it does not,
  drive the outputs to their safe states.

Then fill in the input's **Dead-man** section:

- **Heartbeat register** — the alias of that register in your map. Map it
  **without** `writable`, so a test setup can never write it.
- **PLC timeout (s)** — the timeout you set in the PLC. Phloem uses it to
  decide how often to write.
- **Outputs the PLC drives safe** — the writable aliases your PLC makes safe.
  Outputs you leave out show as **not credited** to the dead-man.

```json
{"alias": "heartbeat", "address": 100, "kind": "holding", "dtype": "u16"}
```

When a setup is undeployed, Phloem writes `0`. The PLC stops supervising and
leaves the outputs where they are. Pick a timeout longer than your slowest
write; a few seconds suits most networks.

## Notes

- A device we have never seen works as soon as you paste its map.
- Writes are checked against the map before they are sent. Every write is
  recorded.

## Supports

- Any Modbus-TCP device via a register map (paste your device's map)
- Any Modbus-RTU (serial) device via the same register map

## Connection fields

| Field | Type | Required | Notes |
|---|---|---|---|
| Device name | Text | Yes |  |
| Connection (host:port or serial device) | Text | Yes |  |
| Transport | Text | No |  |
| Serial baud rate (RTU) | Number | No |  |
| Serial parity (RTU) | Text | No |  |
| Serial stop bits (RTU) | Number | No |  |
| Serial data bits (RTU) | Number | No |  |
| Unit ID | Number | No |  |
| Model (built-in map) | Text | No |  |
| Register map (JSON) | Text | No |  |
| Poll rate (Hz) | Number | No |  |
| Heartbeat register (alias) | Text | No | The alias of a one-word holding register your PLC watches. Map it read-only: the connector writes it, a document never can. Empty = no dead-man. |
| PLC timeout (s) | Number | No | How long your PLC waits on an unchanged heartbeat before it drives the outputs safe — the value configured in the PLC. |
| Outputs the PLC drives safe | Text | No | The writable aliases your PLC drives to a safe state when the heartbeat stops. |
