Modbus TCP Server
MODBUS_SERVER)—the broker hosts a Modbus TCP server and exposes MQTT topics as register and coil addresses. External masters connect to the broker over TCP. To connect the broker to a remote Modbus device instead, see Modbus TCP Client.MODBUS_SERVER route makes your broker act as a Modbus TCP server (slave). External masters—SCADA, HMIs, historians, PLCs—connect to the broker and read or write a declared register/coil address space that bridges bidirectionally to MQTT topics.
Use MODBUS_TCP when you need to poll a device. Use MODBUS_SERVER when OT systems need to read and write values that live on your MQTT bus—no separate gateway required.
When to Use This Route
- SCADA / HMI integration — Let plant-floor tools poll broker data over standard Modbus without custom drivers.
- OT/IT bridge — Expose IoT sensor streams to masters that only speak Modbus.
- Bidirectional control — Writable tags let masters adjust setpoints; the broker publishes the new value to MQTT on a cadence you choose.
- Client-only field devices — Integrate master-only equipment into MQTT with no middleware between the broker and the bus.
The sections below cover data flow, configuration, persistence, and production patterns.
Quick Start
- Expose sensor values
- Writable setpoint
- Full plant server
- Crash-resilient setpoints
23.5 to plant/line1/temperature → registers 100–101 hold the FLOAT; a master reading them gets 23.5.How Data Flows
Sync is hybrid—WITH EVERY controls outbound latency:
| Direction | Behavior |
|---|---|
| MQTT → register (inbound) | A publish on a tag’s SOURCE_TOPIC updates the backing register or coil immediately. Works for every address type, including read-only INPUT_REGISTER and DISCRETE_INPUT. Retained MQTT values are seeded at startup. |
| register → MQTT (outbound) | When a master writes a WRITABLE HOLDING_REGISTER or COIL, the broker detects the change on the mapping’s WITH EVERY cadence and publishes the decoded value to the tag’s DESTINATION_TOPIC (rounded per DECIMAL_PLACES, retained). A master write surfaces on MQTT within one cadence interval. |
DESTINATION_TOPIC (direction guard).
On server routes, route status Connected means the endpoint is listening—not that a remote session is established.
Server Configuration
All transport settings live in theMODBUS_SERVER_CONFIG block. A valid route needs only WITH TYPE MODBUS_SERVER plus a mapping with one tag—it listens on 0.0.0.0:502 as unit 1 by default.
Connection parameters
0.0.0.0 = all interfaces). IP is accepted as an alias.SLAVE_ID is accepted as an alias.1 = PDU register 0). Modicon-style addresses (400001+, 300001+, 100001+) are always decoded regardless of this flag. See Address notation.GET ENV / GET SECRET for environment-derived values (for example WITH PORT GET ENV "MODBUS_PORT").Tag and Mapping Configuration
Each exposed register or coil is declared as anADD TAG. Only declared addresses are served—reads or writes to undeclared addresses return a Modbus Illegal Data Address exception.
Server vs. client semantics
| Setting | Client (MODBUS_TCP) | Server (MODBUS_SERVER) |
|---|---|---|
| Role | Master—dials out to a device | Slave—listens for masters |
HOST / IP | Device IP to dial | Bind interface to listen on |
SOURCE_TOPIC | Register value → MQTT (read) | MQTT → register (write into address space) |
DESTINATION_TOPIC | MQTT → device write | Register → MQTT when a master writes |
EVERY | Poll interval | Outbound sync cadence (master write → MQTT) |
| Transport | TCP / Serial (RTU) | TCP |
Modbus address banks
ADDRESS_TYPE | Bank | Element | Master access | Function codes |
|---|---|---|---|---|
HOLDING_REGISTER | Holding registers | 16-bit word | read / write | 3, 6, 16 |
INPUT_REGISTER | Input registers | 16-bit word | read-only | 4 |
COIL | Coils | 1 bit | read / write | 1, 5, 15 |
DISCRETE_INPUT | Discrete inputs | 1 bit | read-only | 2 |
Address notation
TagADDRESS values are converted to the PDU offset the broker exposes on the wire. External Modbus masters always use zero-based PDU addresses in their read/write requests—the ONE_BASED_ADDRESSES flag only affects how you label tags in LoT, not how masters address the server.
Three styles are supported (same rules as the Modbus TCP Client route):
| Style | Config | Tag address for PDU 0 | Tag address for PDU 99 |
|---|---|---|---|
| Zero-based plain (default) | ONE_BASED_ADDRESSES false | "0" | "99" |
| One-based plain | ONE_BASED_ADDRESSES true | "1" | "100" |
| Modicon holding register | either | "400001" | "400100" |
ONE_BASED_ADDRESSES. The flag only affects plain numeric addresses.Tag parameters
ONE_BASED_ADDRESSES true), or Modicon notation (400001+, 300001+, 100001+). Multi-word types occupy consecutive PDU addresses from the resolved offset. See Address notation.HOLDING_REGISTER, INPUT_REGISTER, COIL, or DISCRETE_INPUT.BOOL, INT16, UINT16, INT32, UINT32, INT64, UINT64, FLOAT, DOUBLE, or STRING. INT32/UINT32/FLOAT span 2 registers; INT64/UINT64/DOUBLE span 4.{mapping SOURCE_TOPIC}/{TagName}.HOLDING_REGISTER and COIL are master-writable.BIGENDIAN or LITTLEENDIAN.BIGENDIAN or LITTLEENDIAN.FLOAT/DOUBLE values published to DESTINATION_TOPIC.All four address types
One server exposing every bank type:plant/mixed/<TagName>; only PumpEnable accepts master writes (→ plant/mixed/pump/cmd).
Protocol Conformance
This server implements Modbus Application Protocol Conformance Class 0 and Class 1 over Modbus/TCP—the interoperability bar for standard masters:| Class | Function codes | Supported |
|---|---|---|
| Class 0 | FC 03 Read Holding Registers, FC 16 Write Multiple Registers | ✅ |
| Class 1 | FC 01 Read Coils, FC 02 Read Discrete Inputs, FC 04 Read Input Registers, FC 05 Write Single Coil, FC 06 Write Single Register | ✅ |
| Class 2 | FC 15 Write Multiple Coils | ✅ |
WRITABLE HOLDING_REGISTER/COIL addresses. Undeclared addresses and writes to read-only banks return exception 02 (Illegal Data Address).
Data Persistence & Recovery
By default a server route holds register and coil values in memory only—a broker restart resets every address to zero. SetWITH PERSISTENCE true on MODBUS_SERVER_CONFIG to snapshot served values and restore them on startup.
Ownership-aware policy
What is safe to persist depends on who owns the value:| Bank | Owner | Default | On restart |
|---|---|---|---|
HOLDING_REGISTER / COIL, WRITABLE true | Master-written setpoint lives only in the slave | Persisted | Restored authoritatively—wins over retained MQTT |
INPUT_REGISTER / DISCRETE_INPUT, and non-writable banks | Fed from live MQTT | Not persisted | Recovered from live MQTT (retained message / next publish) |
Per-tag overrides
WITH PERSISTENCE true|false on an individual ADD TAG overrides the route default:
Setpoint— restored authoritatively to the last master-written value.LastGoodTemp— served from snapshot untilplant/line1/LastGoodTempnext publishes.ScratchSetpoint— always comes back at zero.
Caching slow or infrequently-updated sources
Broker-fed tags are not persisted by default. When a source updates infrequently—an hourly energy total, a shift-latched value—force-persist withWITH PERSISTENCE "true" so the last received value is re-served on cold start until the next live publish overrides it.
WITH PERSISTENCE "true" once on the ADD MAPPING block instead of per tag.
Storage & durability
- Values are written through a non-blocking background worker on every change (rapid updates coalesce to the latest value).
- The snapshot lives in an encrypted-at-rest database under the broker data directory.
- A snapshot is keyed to the route’s tag definitions by a fingerprint. If addresses or types change between runs, the stale snapshot is discarded rather than replayed into reshaped addresses.
Verify Your Server
Deploy the route
Connect with a Modbus master
<HOST>:<PORT> and address the route’s UNIT_ID (default 1).Confirm MQTT → register
SOURCE_TOPIC. Read the corresponding address from the master and confirm the decoded value matches.Limitations
Troubleshooting
Publish-checkRouteConnection <RouteName> to $SYS/Coreflux/Command. For a server route this reports whether the hosted endpoint is listening, along with the endpoint, port, unit ID, and current connection count.
Route status never Connected
Route status never Connected
- Port in use — Another route or process owns
PORT. Choose a free port or stop the conflicting service. - Bind error — Check broker logs. Ensure
HOSTis a valid interface on the machine.
Master cannot connect
Master cannot connect
HOST/PORT, network path, and firewall rules. The master must target the route’s UNIT_ID (default 1).Master reads zero or stale values
Master reads zero or stale values
SOURCE_TOPIC has published; retained MQTT values are seeded at startup.Master write not appearing on MQTT
Master write not appearing on MQTT
- Confirm the tag is
WRITABLEon aHOLDING_REGISTERorCOILwith aDESTINATION_TOPIC. - The value surfaces within one
WITH EVERYinterval—verify the cadence.
Write rejected (Modbus exception)
Write rejected (Modbus exception)
INPUT_REGISTER, DISCRETE_INPUT) and non-writable tags reject master writes by design. Undeclared addresses return Illegal Data Address.Wrong numeric value
Wrong numeric value
BYTE_ORDER and WORD_ORDER with the master’s expectation. Default is big-endian, high word first.Address mismatch (Illegal Data Address)
Address mismatch (Illegal Data Address)
ADDRESS values match how the master addresses the server. Use WITH ONE_BASED_ADDRESSES true when your tag labels follow device-manual numbering (register 1 = address "1"). Modicon notation (400100) works regardless of the flag.Connection-limit behavior
Connection-limit behavior
MAX_CONNECTIONS is a best-effort soft cap—the underlying library exposes no per-connection control, so exceeding the cap is logged rather than enforced at accept time. Restrict concurrency at the network layer if a hard cap is required.Best Practices
Bind to a specific interface in production
Bind to a specific interface in production
WITH HOST "192.168.10.5" over 0.0.0.0 when only one network should reach the server.Keep environment values out of route text
Keep environment values out of route text
WITH PORT GET ENV "MODBUS_PORT" instead of hard-coding environment-specific ports.Declare each exposed address explicitly
Declare each exposed address explicitly
ADD TAG per register or coil a master needs.Make tags WRITABLE only when needed
Make tags WRITABLE only when needed
Match byte and word order to the master
Match byte and word order to the master
WORD_ORDER "LITTLEENDIAN" when the master expects low word first. Default is big-endian, high word first.Size WITH EVERY to outbound latency
Size WITH EVERY to outbound latency
Reserve ports per route
Reserve ports per route
Restrict access at the network layer
Restrict access at the network layer
Monitor route status
Monitor route status
$SYS/Coreflux/Routes/{RouteName}/status. Connected means the server is listening—investigate Error immediately (usually a port conflict).
