---
title: "07 - Block reference"
space: "OpenPLC"
url: "https://docs.modbee.org/openplc/modbus-blocks/07-block-reference"
updated: "2026-10-25"
---

# 07 - Block reference

Every block, every pin. For what they are *for*, see
[04 - Being a device](/openplc/modbus-blocks/04-being-a-device) and
[05 - Reading and writing](/openplc/modbus-blocks/05-reading-and-writing).

Eleven function blocks: three you will always use, five you will use often, and three you place only when a default is not what you want.

Every block but `MODBUS_NODE` and `MODBUS_BRIDGE` takes the node on a `NODE`
in-out pin and returns `ERROR` and `STATUS`. **The pin must be assigned at
every call — leaving it out is a compile error.**

`MODBUS_BRIDGE` stands alone and takes no node, because a bridge is not a
client or a server: it is both ends of a wire.

---

## `MODBUS_NODE`

The link. Call it first, in a fast task of its own. **Eleven pins are the whole
of an ordinary link** — everything else lives on the three optional blocks
below, so it costs you nothing until you want it.

| input | type | |
| --- | --- | --- |
| `TRANSPORT` | `MODBUS_TRANSPORT` | where the bytes go |
| `ROLE` | `MODBUS_ROLE` | ask, answer, or both |
| `TCP_HOST` | `STRING(48)` | the device to connect to. **Empty means be a server** |
| `TCP_PORT` | `UINT` | 0 for 502 |
| `SERIAL_DEVICE` | `STRING(48)` | serial device, e.g. `/dev/ttyUSB0`. Runtime only |
| `SERIAL_BAUD` | `DINT` | 0 for 19200 |
| `SERIAL_PARITY` | `MODBUS_PARITY` | `EVEN` is the Modbus default |
| `SERIAL_UNIT_ID` | `BYTE` | the address this node answers to on a serial line, 1..247. **Serial server only** — a TCP server answers whatever unit id is asked for |
| `RESET_STATS` | `BOOL` | rising edge zeroes the counters `MODBUS_STATUS` reports |
| `CONNECT` | `BOOL` | `TRUE` by default. Drop it to release the port |

**The prefix says which transport a pin belongs to**, and a pin for a transport
you are not using is ignored. `SERIAL_UNIT_ID` is the one people expect to need
on TCP and do not.

| output | type | |
| --- | --- | --- |
| `READY` | `BOOL` | the link is open |
| `CONNECTED` | `BOOL` | something is at the other end; for a server, that it is listening |
| `CLIENTS` | `INT` | clients connected. TCP server only |
| `ERROR` | `BOOL` | it did not open, or has since failed |
| `STATUS` | `MODBUS_RESULT` | why |
| `LAST_ERROR` | `STRING(64)` | the operating system's own words |

**The node waits one scan before opening**, so a helper below may sit anywhere
in the same program — above or below. That is one millisecond of a fast task,
and it removes a placement rule nobody would remember.

---

## `MODBUS_CONFIG` — optional

Tuning. Place it only where a default is not what you want.

| input | type | |
| --- | --- | --- |
| `FRAMING` | `MODBUS_FRAMING` | how frames are found. `AUTO` reads RTU on a serial line and MBAP on a socket. **The pin for a converter** |
| `TIMEOUT` | `DINT` | **0 measures it per device**, which is almost always better than a figure you pick |
| `RETRIES` | `BYTE` | attempts after the first. 0 takes the library's |
| `FAILSAFE` | `DINT` | silence that counts as losing the client. 0 disables. Needed before `MODBUS_MAP`'s `ON_LOSS` does anything |
| `MAX_CLIENTS` | `BYTE` | clients a TCP server holds at once. 0 for eight on the Runtime, two on a board. No ceiling of its own; too many for the board's memory stops the node with *"out of memory for MAX_CLIENTS connections"* |
| `QUEUE` | `UINT` | requests that may wait at once. 0 for 16 on a board, 64 on the Runtime |
| `QUEUE_BYTES` | `UINT` | bytes the waiting requests' values share. 0 for 1024 on a board, 16192 on the Runtime. Too much for the board stops the node with *"out of memory for the request queue"* |
| `IDLE` | `DINT` | close an unused connection. 0 for 60000, -1 never |
| `RECONNECT` | `DINT` | how often to retry a dropped link. **Already on** — 0 takes the library's two seconds; -1 turns recovery off |

| output | type | |
| --- | --- | --- |
| `APPLIED` / `ERROR` / `STATUS` | | whether the settings reached the node |

**Automatic recovery is already on and needs no block.** A link that drops is
retried every two seconds until it returns, and every watched value is made due
the moment it does. `RECONNECT` only paces that differently, or turns it off.

**Assign `TIMEOUT` even when you want the default.** It is the first pin, and
the editor reports a block whose first input is never assigned. `TIMEOUT := 0`
says "I have thought about the timeout and I want the measured one", which is
worth saying on a tuning block.

---

## `MODBUS_IDENTITY` — optional

What this node answers function code 43 with.

| input | type | |
| --- | --- | --- |
| `VENDOR` / `PRODUCT` / `REVISION` | `STRING(32)` | its nameplate |

| output | type | |
| --- | --- | --- |
| `APPLIED` / `ERROR` / `STATUS` | | whether it reached the node |

Worth setting on anything somebody else will commission: six identical-looking
nodes on one segment are six nodes you can only tell apart by asking.

---

## `MODBUS_ARDUINO` — optional, boards only

The pins only a board has. On the Runtime it reports
`MODBUS_INVALID_ARGUMENT` rather than doing nothing quietly.

| input | type | |
| --- | --- | --- |
| `PORT` | `BYTE` | hardware serial port number |
| `RX_PIN` / `TX_PIN` | `INT` | -1 for the board's own. ESP32 only |
| `DE_PIN` | `INT` | RS-485 driver enable, -1 if the board wires it |
| `DE_HOLD` | `DINT` | hold after the last byte. 0 for two character times |
| `SSID` | `STRING(48)` | WiFi network |
| `PASSWORD` | `STRING(63)` | WiFi passphrase, up to 63 characters (the WPA2 maximum) |
| `MAC` | `STRING(32)` | Ethernet. Empty takes a built-in default — a collision the moment there are two |
| `IP` | `STRING(32)` | **required** on WiFi and Ethernet. `'dhcp'` asks a server instead |
| `GATEWAY` | `STRING(32)` | empty derives it from `IP`, ending `.1` |
| `SUBNET` | `STRING(32)` | empty uses `255.255.255.0` |

| output | type | |
| --- | --- | --- |
| `APPLIED` / `ERROR` / `STATUS` | | whether the settings reached the node |

---

---

## `MODBUS_MAP`

Your variables at Modbus addresses. No request pin: once bound, the variable
**is** the register.

| input | type | |
| --- | --- | --- |
| `TABLE` | `MODBUS_TABLE` | which of the four lists |
| `ADDRESS` | `UINT` | the first address |
| `ON_LOSS` | `MODBUS_FAILSAFE` | what a writable address does when the client stops. Needs `FAILSAFE` on the node |
| `SAFE_VALUE` | `DINT` | what `MODBUS_RESET_TO_VALUE` goes to. Single values only |
| `VALUE` | `ANY` | the variable to bind. Single value or fixed-bounds array |

| output | type | |
| --- | --- | --- |
| `BOUND` | `BOOL` | the addresses are live |
| `COUNT` | `UINT` | how many addresses were taken |
| `WRITTEN` | `BOOL` | one scan, when a client wrote any of them |
| `SAFE_APPLIED` | `BOOL` | one scan, when the safe state was applied |
| `ERROR` | `BOOL` | why not |
| `STATUS` | `MODBUS_RESULT` | why |

---

## `MODBUS_READ`

Reads a device, on a rising edge of `REQ`.

| input | type | |
| --- | --- | --- |
| `REQ` | `BOOL` | rising edge reads. Holding it high is safe |
| `UNIT` | `BYTE` | the device's address, 1..247 |
| `FC` | `MODBUS_READ_FC` | which read, named as the manual names it |
| `ADDRESS` | `UINT` | the first address on that device |
| `LENGTH` | `UINT` | **0 means "as many as are in the variable"**, which is almost always right |
| `ORDER` | `MODBUS_ORDER` | how a 32- or 64-bit value is laid out. Ignored for `BOOL` and `WORD` |
| `VALUE` | `ANY` | the variable to read into |

| output | type | |
| --- | --- | --- |
| `BUSY` | `BOOL` | a level: the request is outstanding |
| `DONE` | `BOOL` | one scan, when the answers arrived |
| `COUNT` | `UINT` | how many addresses were moved |
| `ERROR` | `BOOL` | one scan, when they did not |
| `STATUS` | `MODBUS_RESULT` | why |

---

## `MODBUS_WRITE`

Writes a device, on a rising edge of `REQ`.

| input | type | |
| --- | --- | --- |
| `REQ` | `BOOL` | rising edge writes |
| `UNIT` | `BYTE` | the device's address. **0 broadcasts**, and nobody answers |
| `FC` | `MODBUS_WRITE_FC` | `MODBUS_FC_AUTO` picks it from what you passed |
| `ADDRESS` | `UINT` | the first address |
| `LENGTH` | `UINT` | 0 means "as many as are in the variable" |
| `ORDER` | `MODBUS_ORDER` | word order for values wider than a register |
| `AND_MASK` / `OR_MASK` | `WORD` | for `MODBUS_FC22_MASK_WRITE_REGISTER` only |
| `VALUE` | `ANY` | the variable to write from |

| output | type | |
| --- | --- | --- |
| `BUSY` | `BOOL` | the request is outstanding |
| `DONE` | `BOOL` | one scan, when the device confirmed it |
| `COUNT` | `UINT` | how many addresses were written |
| `ERROR` | `BOOL` | one scan, when it did not |
| `STATUS` | `MODBUS_RESULT` | why |

---

## `MODBUS_WATCH`

A repeating read, with a quality. Never touches the wire itself.

| input | type | |
| --- | --- | --- |
| `UNIT` | `BYTE` | the device's address. May change while running |
| `FC` | `MODBUS_READ_FC` | which read. May change while running |
| `ADDRESS` | `UINT` | the first address. May change while running |
| `EVERY` | `TIME` | how often. `T#1s` if left at `T#0s`. **`QUALITY` is judged against this** |
| `LENGTH` | `UINT` | 0 means "as many as are in the variable". Fixed at the first call |
| `ORDER` | `MODBUS_ORDER` | word order. May change while running |
| `DEADBAND` | `UINT` | how far the value must move to count as a change |
| `ENABLE` | `BOOL` | `TRUE` by default. Stops and starts in place |
| `VALUE` | `ANY` | the variable to keep up to date. Fixed at the first call |

| output | type | |
| --- | --- | --- |
| `WATCHING` | `BOOL` | the watch is running |
| `COUNT` | `UINT` | how many addresses it covers |
| `QUALITY` | `MODBUS_QUALITY` | how much to believe `VALUE` right now |
| `SEEN` | `BOOL` | a value has arrived at least once |
| `AGE` | `TIME` | since it last arrived, `T#0s` if it never has (`SEEN` tells which) |
| `FAILURES` | `UINT` | reads that failed in a row |
| `ERROR` | `BOOL` | could not be set up, or its last read failed |
| `STATUS` | `MODBUS_RESULT` | why |

A new `UNIT`, `FC`, `ADDRESS` or `ORDER` re-aims the same watch: `QUALITY` goes
back to `UNKNOWN` and the next read goes to the new place.

## `MODBUS_POINT`

One value on a device, defined entirely by settings: read and re-read from its
pins every scan. See [11 - Points from settings](/openplc/modbus-blocks/11-points-from-settings).

| input | type | |
| --- | --- | --- |
| `UNIT` | `BYTE` | the device's address |
| `FC` | `MODBUS_PT_FC` | FC01..FC04 to read, FC05 / FC06 / FC16 to write |
| `ADDRESS` | `UINT` | the register or coil |
| `DTYPE` | `MODBUS_PT_TYPE` | `BIT`, `INT16`, `UINT16`, `INT32`, `UINT32`, `FLOAT32`, `REG_BIT` |
| `ORDER` | `MODBUS_ORDER` | word order of a 32-bit value |
| `BIT` | `USINT` | `REG_BIT` only: which bit, 0..15 |
| `SCALE` | `REAL` | value = raw × `SCALE` + `OFFSET`. 1.0 by default |
| `OFFSET` | `REAL` | |
| `EVERY` | `TIME` | how often to read, or to send a write again. `T#1s` if `T#0s` |
| `STALE` | `TIME` | no good answer this long = lost (`BAD`). `T#0s`: 3 × `EVERY` |
| `TIMEOUT` | `TIME` | this device's answer time limit (radio: a few seconds). `T#0s`: measured |
| `WRITE_VALUE` | `REAL` | write points: the value, in engineering units |
| `ENABLE` | `BOOL` | `TRUE` by default. `FALSE` = not fitted: never asked, never ok |

| output | type | |
| --- | --- | --- |
| `VALUE` | `REAL` | engineering units. The last good value is held while not ok |
| `Q` | `BOOL` | `VALUE <> 0`, for a bit |
| `OK` | `BOOL` | good or a little late. `FALSE` before the first answer and once lost |
| `QUALITY` | `MODBUS_QUALITY` | `UNKNOWN` never answered, `GOOD`, `UNCERTAIN` late, `BAD` lost |
| `AGE` | `TIME` | since the last good answer |
| `FAILURES` | `UINT` | failed attempts in a row |
| `ERROR` | `BOOL` | the last attempt failed, or the settings cannot work |
| `STATUS` | `MODBUS_RESULT` | why. `MODBUS_INVALID_ARGUMENT` for settings that cannot work |

## `MODBUS_POINTS`

The same, as a table: one block for every point on the bus.

| in-out | type | |
| --- | --- | --- |
| `NODE` | `MODBUS_NODE` | which node |
| `SETTINGS` | `ARRAY OF MODBUS_PT_CFG` | one row a point. Retain it |
| `POINTS` | `ARRAY OF MODBUS_PT_STS` | what each row is doing. Do not retain it |

| input | type | |
| --- | --- | --- |
| `RUN` | `BOOL` | `TRUE` by default. `FALSE` stops asking |

| output | type | |
| --- | --- | --- |
| `GOOD_COUNT` | `INT` | enabled rows that are ok |
| `LOST_COUNT` | `INT` | enabled rows that are not |
| `FIRST_LOST` | `INT` | the first such row, 0 when none |
| `ERROR` | `BOOL` | `LOST_COUNT > 0` |
| `STATUS` | `MODBUS_RESULT` | the first lost row's result |
| `STATUS_TEXT` | `STRING(30)` | `pt 3 unit 5 lost`, `pt 2 unit 9 no answer`, `pt 4 bad settings` |

`MODBUS_PT_CFG` (settings): `unit`, `fc`, `address`, `dtype`, `order`, `bit`,
`scale` (1.0), `offset`, `every` (`T#1s`), `stale`, `timeout`, `enable` (TRUE).

`MODBUS_PT_STS`: `w` (your logic: the value to write), and the status `v`, `q`,
`ok`, `quality`, `age`, `failures`, `result`.

---

## `MODBUS_STATUS`

How the link is doing. **No inputs**: it reports and changes nothing.

| output | type | |
| --- | --- | --- |
| `CONNECTED` | `BOOL` | there is something at the other end |
| `CLIENTS` | `INT` | clients connected, for a server |
| `PENDING` | `INT` | requests queued and not yet concluded |
| `SENT` / `RECEIVED` | `DINT` | frames, both directions |
| `CHECKSUM_ERRORS` | `DINT` | the bytes arrived and the maths did not agree |
| `FRAMING_ERRORS` | `DINT` | structurally impossible frames |
| `NOISE_BYTES` | `DINT` | discarded while hunting for a frame start |
| `FRAMING_REASON` | `STRING(160)` | how this link finds frames, and why, in a sentence |
| `ERROR` | `BOOL` | no such node |
| `STATUS` | `MODBUS_RESULT` | why |

---

## `MODBUS_DEVICE`

How one device is doing. Free while `POLL` is 0 — it reads the traffic your
program already makes and sends nothing. **Which means a device nobody asks
anything reads as not answering**: give `POLL` to a device this block is the
only thing pointed at.

| input | type | |
| --- | --- | --- |
| `UNIT` | `BYTE` | the device's address |
| `IDENTIFY` | `BOOL` | rising edge asks for its nameplate. Once is enough |
| `POLL` | `DINT` | ask this device whether it is alive, this often. **0 watches only the traffic you already make** — which answers nothing about a device nobody talks to |
| `GONE` | `DINT` | silence before `ANSWERING` drops. 0 uses 5000 |

| output | type | |
| --- | --- | --- |
| `ANSWERING` | `BOOL` | it has answered within `GONE` |
| `SILENT` | `DINT` | since it last answered, or **-1 if it never has** |
| `ROUND_TRIP` | `DINT` | how long it takes to reply, measured |
| `VENDOR` / `PRODUCT` / `REVISION` | `STRING(32)` | its nameplate |
| `IDENTIFIED` | `BOOL` | one scan, when the nameplate arrived |
| `ERROR` | `BOOL` | no such node, or the nameplate could not be read |
| `STATUS` | `MODBUS_RESULT` | why |

---

## `MODBUS_BRIDGE`

TCP clients in front, a serial segment behind. Takes no `NODE`; put it in the
fast task a node would go in.

| input | type | |
| --- | --- | --- |
| `SERIAL_DEVICE` | `STRING(48)` | the serial device behind. Runtime only |
| `SERIAL_BAUD` | `DINT` | 0 for 19200 |
| `SERIAL_PARITY` | `MODBUS_PARITY` | `EVEN` is the Modbus default |
| `PORT` / `RX_PIN` / `TX_PIN` | `BYTE` / `INT` | board only |
| `DE_PIN` / `DE_HOLD` | `INT` / `DINT` | RS-485 driver enable |
| `TCP_PORT` | `UINT` | the port clients connect to. 0 for 502 |
| `FIRST_UNIT` / `LAST_UNIT` | `BYTE` | the unit ids out on the segment |
| `MAX_CLIENTS` | `BYTE` | clients at once. 0 for eight on the Runtime, two on a board. No ceiling of its own |
| `MAX_PENDING` | `BYTE` | transactions outstanding on the serial bus. 0 for 8 |
| `PASS_OTHERS` | `BOOL` | forward anything not routed, instead of refusing it |
| `CONNECT` | `BOOL` | `TRUE` by default |

| output | type | |
| --- | --- | --- |
| `READY` | `BOOL` | both ends are open |
| `CLIENTS` | `INT` | clients connected |
| `FORWARDED` | `DINT` | requests sent to the serial segment |
| `NO_PATH` | `DINT` | refused: we have no route. **Our** problem |
| `NO_DEVICE` | `DINT` | refused: the device did not answer. **Its** problem |
| `FRAMING_REASON` | `STRING(160)` | how the serial segment finds frames |
| `ERROR` | `BOOL` | it did not open |
| `STATUS` | `MODBUS_RESULT` | why |
| `LAST_ERROR` | `STRING(64)` | in words |

---

## The enumerations

| type | values |
| --- | --- |
| `MODBUS_TRANSPORT` | `MODBUS_RUNTIME_SERIAL`, `MODBUS_RUNTIME_TCP`, `MODBUS_ARDUINO_SERIAL`, `MODBUS_ARDUINO_WIFI`, `MODBUS_ARDUINO_ETHERNET` |
| `MODBUS_FRAMING` | `MODBUS_FRAMING_AUTO`, `MODBUS_FRAMING_RTU`, `MODBUS_FRAMING_ASCII`, `MODBUS_FRAMING_TCP` |
| `MODBUS_ROLE` | `MODBUS_ROLE_CLIENT`, `MODBUS_ROLE_SERVER`, `MODBUS_ROLE_BOTH` |
| `MODBUS_PARITY` | `MODBUS_PARITY_EVEN`, `MODBUS_PARITY_ODD`, `MODBUS_PARITY_NONE` (8N2), `MODBUS_PARITY_NONE_8N1` |
| `MODBUS_TABLE` | `MODBUS_COILS`, `MODBUS_DISCRETE_INPUTS`, `MODBUS_HOLDING_REGISTERS`, `MODBUS_INPUT_REGISTERS` |
| `MODBUS_READ_FC` | `MODBUS_FC01_READ_COILS`, `MODBUS_FC02_READ_DISCRETE_INPUTS`, `MODBUS_FC03_READ_HOLDING_REGISTERS`, `MODBUS_FC04_READ_INPUT_REGISTERS` |
| `MODBUS_WRITE_FC` | `MODBUS_FC_AUTO`, `MODBUS_FC05_WRITE_SINGLE_COIL`, `MODBUS_FC06_WRITE_SINGLE_REGISTER`, `MODBUS_FC15_WRITE_MULTIPLE_COILS`, `MODBUS_FC16_WRITE_MULTIPLE_REGISTERS`, `MODBUS_FC22_MASK_WRITE_REGISTER` |
| `MODBUS_ORDER` | `MODBUS_ORDER_ABCD`, `MODBUS_ORDER_CDAB`, `MODBUS_ORDER_BADC`, `MODBUS_ORDER_DCBA` |
| `MODBUS_QUALITY` | `MODBUS_QUALITY_UNKNOWN`, `MODBUS_QUALITY_GOOD`, `MODBUS_QUALITY_UNCERTAIN`, `MODBUS_QUALITY_BAD` |
| `MODBUS_FAILSAFE` | `MODBUS_HOLD_LAST_VALUE`, `MODBUS_RESET_TO_VALUE` |
| `MODBUS_PT_FC` | `MODBUS_PT_FC01_READ_COILS`, `MODBUS_PT_FC02_READ_DISCRETE_INPUTS`, `MODBUS_PT_FC03_READ_HOLDING_REGISTERS`, `MODBUS_PT_FC04_READ_INPUT_REGISTERS`, `MODBUS_PT_FC05_WRITE_COIL`, `MODBUS_PT_FC06_WRITE_REGISTER`, `MODBUS_PT_FC16_WRITE_REGISTERS` |
| `MODBUS_PT_TYPE` | `MODBUS_PT_BIT`, `MODBUS_PT_INT16`, `MODBUS_PT_UINT16`, `MODBUS_PT_INT32`, `MODBUS_PT_UINT32`, `MODBUS_PT_FLOAT32`, `MODBUS_PT_REG_BIT` |
| `MODBUS_RESULT` | see [06 - Diagnostics](/openplc/modbus-blocks/06-diagnostics) |

## Not in this version

**FC 23, read/write multiple registers.** It needs a second buffer and a second
address, which would leave four pins dead on every ordinary write. Use a write
followed by a read.

**FC 43 as a general call.** It appears as the identity outputs on
`MODBUS_DEVICE` instead, which is what anybody actually wants from it.

**Next:** [08 - Patterns](/openplc/modbus-blocks/08-patterns)
