# Modbus TCP

> Modbus registers and coils over TCP/IP, with typed addressing, byte-order control and a bounded network scan.

The Modbus TCP driver reads and writes Modbus registers and coils over TCP/IP:
PLCs, meters, gateways and any server speaking Modbus/TCP. Addressing is fully
typed: you pick a register class and a 0-based register number, and the app
assembles the wire request; there is no free-text address to get wrong.

## Device {#device}

| Field | Meaning | Default |
| --- | --- | --- |
| **Host** | DNS name, IPv4 or IPv6 address of the Modbus server or gateway. | empty: the device is not connected until one is entered |
| **Port** | TCP port, 1 to 65535. An empty box shows 502 in grey and the device is dialed on it, which is also how a device saved with 0 reads. | 502 |
| **Unit ID** | The Modbus unit (slave) id carried in every request: what a gateway uses to route to the device behind it. 0 to 255. | 1 |
| **Framing** | How each request travels on the connection: **MBAP**, the Modbus TCP header in front of every request, or **RTU**, the RTU frame with its CRC sent as it is, for a serial-to-Ethernet converter in transparent mode (see [RTU over TCP](#rtu-over-tcp)). Host, port, unit ID and tags are the same either way. | MBAP |
| **Minimum transaction interval (ms)** | Compatibility pacing: the minimum silence after one transaction completes before the next starts, 0 to 1000. Zero adds no artificial delay. | 0 |
| **Poll interval (ms)** | Default polling rate for tags that do not override it. | 1000 |

The Base URI shown in the panel header is
`modbus+tcp://host:port/unitID`, plus `?minimumTransactionIntervalMs=n` when the
interval is set.

Devices that share a host and port also share one transaction gate: requests to
the same endpoint never overlap, and the **largest** configured minimum
transaction interval among the enabled devices on that endpoint paces all of
them. The panel says when another device raises the effective interval above the
one configured here. The interval governs only traffic this application emits;
it is a compatibility aid for a slow server or gateway, not a repair for network
faults.

Connecting waits at most 5 seconds; a response is waited on for at most 10
seconds; there are no protocol-level retries. The background retry loop owns
reconnection, so a device that is off costs nothing but a bad-quality reading.

## RTU over TCP {#rtu-over-tcp}

A serial-to-Ethernet converter in transparent mode has no Modbus TCP of its own:
what arrives on its socket goes out on its serial bus as it is, and what the bus
answers comes back. A device behind one is a Modbus TCP device with **Framing** set
to **RTU**: the station sends the RTU frame (the unit ID, the request and its CRC)
with no MBAP header, and believes an answer only when its CRC holds. The Base URI
then carries `framing=rtu` in its query, for example
`modbus+tcp://192.168.0.20:502/1?framing=rtu`; a device on **MBAP** keeps the
address it always had. Everything else on this page holds unchanged: the register
classes, the function codes, the write queue, the timeouts and the transaction
interval.

- The **Unit ID** is the address of the device on the serial bus behind the
  converter, 1 to 247.
- The devices behind one converter share its host and port, and with them its
  transaction gate: one request at a time, which is also all a serial bus carries.
  If the converter needs a pause between frames, raise the **Minimum transaction
  interval (ms)**.
- The converter's own serial settings (baud rate, format) are set on the
  converter; the station never sees them.
- An RTU frame carries no transaction number, so before every request the station
  drops whatever the converter has already sent: an answer that arrives after its
  request timed out is never taken for the next one's.

## Tag addressing {#tag-addressing}

| Field | Meaning | Default |
| --- | --- | --- |
| **Register class** | Which Modbus entity the tag addresses: Coil (0xxxx), Discrete input (1xxxx), Input register (3xxxx) or Holding register (4xxxx). Each is named with the first digit a manual's address carries for it, so a manual that prints only 30001 points to the input registers. | Derived from type and access: Boolean tags get a discrete input (read-only) or a coil (writable); every other type gets an input register (read-only) or a holding register (writable). |
| **Register number (0-based)** | The raw protocol address. This is the 0-based address on the wire, not the manual's notation: holding register 40001 is register number 0. Typing 30001 here does not pick the class, since some devices really have registers above 30000: the class is chosen above. The register travels the wire as a 16-bit number, so the field takes 0 to 65535 and refuses anything wider where it is typed. | 0 |
| **Quantity (registers, blank = auto)** | How many 16-bit registers the value occupies. The box is drawn on String tags only: every other type has a fixed width, so there is nothing to override. | By type: Boolean, Int16 and UInt16 1, Int32 and Float 2, String 4 |
| **Most significant byte first (MSB first, big-endian)** | Which byte of each 16-bit register comes first on the wire; the words in brackets are the ones manuals print for it. Numeric tags, and a Boolean kept in a holding or an input register; a Boolean in a coil or a discrete input is a single bit with no byte to order, so the box is not drawn there. | on (what the Modbus specification asks for) |
| **Most significant word first (high word first)** | Which register of a two-register value carries the high half. Int32 and Float tags only: a one-register value (Boolean, Int16, UInt16) has no second register, so the box is not drawn there. Under the two boxes, **Byte order of the value** names the layout they make in a manual's letters ([Byte and word order](#byte-and-word-order)). | on |

The Source card ends with **Address in the manual**, where the tag sits in the
notation a device manual prints: the class's first digit, then the register
counted from 1. Input register 0 reads 30001, a Float at holding register 100
reads 40101 to 40102, and a register past 9998 takes six digits (holding
register 9999 reads 410000), so the panel can be read against the manual line
by line.

### Value types and quantities {#value-types-and-quantities}

Six value types are offered, and the protocol width of each is fixed:

| Data type | Registers | Wire form |
| --- | --- | --- |
| Boolean | 1 | On a register class: any non-zero register reads true, and a write sends 1 or 0. On `Coil`/`DiscreteInput`: the bit itself. |
| Int16 | 1 | 16-bit signed integer in a single register, -32768 to 32767. |
| UInt16 | 1 | 16-bit unsigned integer in a single register, 0 to 65535. The same register reads 65535 as UInt16 and -1 as Int16: the sign comes from the data type chosen, not from the register. |
| Int32 | 2 | 32-bit signed integer across a register pair. |
| Float | 2 | IEEE 754 single precision across a register pair. |
| String | 1 or more (default 4) | UTF-8 bytes packed two per register. A read stops at the first NUL byte; a write must fit in the declared quantity (2 bytes per register) and is zero-padded. |

A String tag is read and written in the order the device holds the bytes, and a
write always sends UTF-8. Bytes that are not valid UTF-8, which is what a range
the device never initialised or text written in another encoding looks like,
read as the Unicode replacement character rather than failing: the tag shows
something visibly wrong, and every other tag on that device keeps reading.

**Quantity (registers, blank = auto)** is asked on String tags only. Boolean, Int16,
UInt16, Int32 and Float widths are fixed, so the box is not drawn on those points,
neither in the tag panel nor in the tag grid; the override exists to size String tags.

### Byte and word order {#byte-and-word-order}

The byte-order box is drawn on numeric tags and on a Boolean kept in a register, the word-order box on the two-register ones (Int32, Float) only. They are independent questions:
which byte leads inside each register, and which register leads across a pair.
Modbus defines neither for text, so a String tag is not offered them: its bytes
travel in the order the device holds them. For the value `0x01020304`,
with `A` the most significant byte, the four layouts are:

| Most significant byte first | Most significant word first | Wire bytes |
| --- | --- | --- |
| on | on | `AB CD` (the specification default) |
| on | off | `CD AB` (the classic word swap) |
| off | on | `BA DC` |
| off | off | `DC BA` |

Reads and writes apply the same two answers in the same order, so a write lays
down exactly the bytes a read would have taken back. **Byte order of the value**,
under the boxes, says the layout in one word (ABCD, CDAB, BADC or DCBA), the way
a manual names it.

An Int16 or UInt16 value is a single register, so only the byte order applies to
it: for `0x1234`, most significant byte first sends `12 34` and off sends `34 12`.
There is no second register to place, so the word-order box is not drawn for it.

### Function codes {#function-codes}

| Register class | Read | Write |
| --- | --- | --- |
| Holding register (4xxxx) | 3 (Read Holding Registers) | 6 (single register) or 16 (multiple registers) |
| Input register (3xxxx) | 4 (Read Input Registers) | read-only; a write is refused |
| Coil (0xxxx) | 1 (Read Coils) | 5 (Force Single Coil) |
| Discrete input (1xxxx) | 2 (Read Discrete Inputs) | read-only; a write is refused |

A write returns success only when the server's protocol response confirms
acceptance; the cached value advances only then.

## Discovery {#discovery}

The scan is explicit and bounded: the app never infers or sweeps your local
subnet. The scope form takes:

| Field | Meaning | Default |
| --- | --- | --- |
| **IPv4 host or CIDR** | One address (`192.168.1.10`) or a subnet no broader than /24 (`192.168.1.0/24`). Network and broadcast addresses are skipped. | required |
| **Port** | The port to probe. | 502 |
| **Unit IDs** | Values, comma-separated lists or ascending ranges: `1`, `1,5,9`, `1, 3, 8-16` (0 to 255). | 1 |
| **Framing** | The frame every probe is sent in. A converter in transparent mode answers only RTU frames, so scan it in RTU. | MBAP |
| **Minimum transaction interval (ms)** | Pacing between completed probes on the same host, 0 to 1000. | 0 |

The scan sends every probe in the **Framing** chosen. On **RTU** each probe is the
bare RTU frame, and an answer counts only when its unit ID matches and its CRC
holds, so the units behind a converter in transparent mode are found like any
other. A unit found keeps the framing of the scan when it is added.

A single scan may send at most **256 probes** (hosts × unit ids); a larger plan is
refused before any socket opens. The only request sent is **Read Basic Device
Identification** (function 43/14); discovery never reads registers or guesses a
register map. A fully valid Modbus exception response proves a unit is present
without claiming its identity; TCP reachability, timeouts and malformed frames do
not. Up to four hosts are scanned in parallel, sequentially within each host, with
750 ms connect and probe timeouts.

Results carry the complete endpoint and a suggested name built from the vendor
and product code when the device reports them. Candidates have zero tags;
registers are configured after **Add**. A unit that is already configured is
marked as such and offers **Open** instead of creating a duplicate.

## Limits worth knowing {#limits-worth-knowing}

- Other integer widths (SByte, Byte, UInt32, Int64, UInt64) and Double are not
  offered: the wire codec round-trips exactly the six types above.
- One request is outstanding per endpoint at a time; many devices behind one
  gateway share that serialization by design.
- Writes address one coil or one register run at a time; there is no multi-coil
  write.
