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:
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
Modbus address banks
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):
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.ceil(bytes / 2) (integer form: (bytes + 1) / 2). When omitted, defaults to 40 bytes (20 registers) and logs a Warning. Alias: STRING_SIZE. Must resolve to at most 123 registers (246 bytes), the FC16 write cap; larger values are rejected during route validation.{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).
STRING tag example
Reserve a writable 40-byte holding-register range for barcode values a master can write with FC16:WITH LENGTH 40 reserves holding registers 0–19 (ceil(40 / 2) = 20 registers). A master FC16 write to those 20 registers is decoded and published to scanner/barcode. STRING_SIZE is accepted as an alias for LENGTH.
Protocol Conformance
This server implements Modbus Application Protocol Conformance Class 0 and Class 1 over Modbus/TCP—the interoperability bar for standard masters: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: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.Confirm register → MQTT
DESTINATION_TOPIC, write a value from the master. Subscribe to the destination topic with MQTT Explorer or any MQTT client and confirm the publish arrives within one WITH EVERY interval.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.Accepted master writes: FC06 (Write Single Register) and FC16 (Write Multiple Registers) on WRITABLE HOLDING_REGISTER tags; FC05 (Write Single Coil) and FC15 (Write Multiple Coils) on WRITABLE COIL tags.Rejected master writes: any write to INPUT_REGISTER or DISCRETE_INPUT; writes to non-WRITABLE tags; writes to addresses outside the declared tag range (including partial FC16 ranges that extend beyond a STRING tag’s reserved registers).Master FC16 write fails on a STRING tag (Illegal Data Address)
Master FC16 write fails on a STRING tag (Illegal Data Address)
ceil(LENGTH / 2) registers. An omitted WITH LENGTH reserves the default 40 bytes (20 registers) and logs a Warning; older broker versions reserved only one register when the setting was omitted. STRING_SIZE remains accepted as an alias.Solution: Set WITH LENGTH to the complete byte width the master writes, up to 246 bytes (123 registers). Rejected undeclared holding-register ranges log Master write rejected: … Check WITH LENGTH on STRING tags.Master activity logging
Master activity logging
- Accepted writes — FC06/FC16 on writable
HOLDING_REGISTERtags; FC05/FC15 on writableCOILtags, with bank-aware function-code labels. - Rejected writes — read-only banks, non-writable tags, and undeclared address ranges (including partial FC16 spans beyond a STRING tag’s reserved registers).
- Rejected reads — undeclared or out-of-range read requests.
- Undeclared holding-register ranges — rejection messages include
Check WITH LENGTH on STRING tagswhen sizing is the likely cause. - Connections — open/close events with the active connection count from the existing watchdog.
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.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).
