CODESYS MQTT: IIoT Library Client, Not a Fieldbus Driver

Daniel Price7 min read
Industrial NetworkingOther ManufacturerTechnical Reference
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Follow the packet. A CODESYS runtime that publishes to MQTT is acting as an ordinary TCP client: the application task calls a function block, the block hands a CONNECT packet to the controller's socket layer, that packet leaves on the Ethernet port and terminates at a broker process listening on a port somewhere else on the network. Nothing in the fieldbus configuration tree participates. If the library is not installed, the runtime has no MQTT capability at all, and no amount of IP configuration will produce a publish.

That distinction drives the whole commissioning order below: prove the transport, prove the broker, then add the PLC last.

Which hops carry an MQTT publish, and where does it stop?

Five hops, and the failure is almost always at the first three:

Hop What runs there Fails when
1. Physical/link Controller Ethernet port, switch No link LED, wrong VLAN, controller port used for fieldbus only
2. IP Controller stack, gateway Broker on a different subnet with no route; PLC has no default gateway set
3. TCP Socket to broker port Broker listener bound to loopback; host firewall drops the port
4. MQTT session CONNECT / CONNACK, keepalive Auth rejected, duplicate client ID, keepalive expiry
5. Application Publish FB, topic, payload Topic typo, payload built but FB never re-triggered

Default ports, so you know what to open and what to sniff:

Port Transport Notes
1883 MQTT over plain TCP Use for bench work only
8883 MQTT over TLS Requires CA cert on the controller and a matching broker certificate
9001 (typical) MQTT over WebSockets Not a default; only present if the broker config declares that listener

Check: from an engineering PC on the same subnet as the controller, open a TCP connection to the broker port. A refused connection is a broker or firewall problem; a timeout is a routing or physical problem. Resolve that before touching CODESYS.

How do I prove the broker works without a PLC?

You do not need a controller to learn or to validate the protocol. Install Mosquitto on a laptop or a small Linux host and drive it with its own command-line clients. Two terminals give you a complete publish/subscribe path in under a minute.

# terminal 1 - subscriber, wildcard on everything
mosquitto_sub -h 127.0.0.1 -p 1883 -t '#' -v

# terminal 2 - publisher
mosquitto_pub -h 127.0.0.1 -p 1883 -t 'plant/line3/cell1/temp' -m '{"t":72.4}'

Two configuration facts trip up nearly every first install. Mosquitto 2.x binds its default listener to localhost only, and it rejects anonymous clients unless told otherwise. A PLC on the network will therefore get connection refused against an otherwise healthy broker. For bench commissioning, declare an explicit listener and re-test from a second machine:

listener 1883 0.0.0.0
allow_anonymous true

Move to a password file or TLS before the broker leaves the bench. Check: run mosquitto_sub from a different host than the broker and confirm messages arrive. Until a remote subscriber sees traffic, the PLC will not either.

What gives the CODESYS runtime an MQTT client?

Bare CODESYS has no MQTT stack. You add one of three things:

Option Source When it fits
IIoT Libraries SL (MQTT Client SL) CODESYS Store, via Package Manager Vendor-supported, works on any CODESYS 3.5 runtime; licensed
OEM library Controller vendor, e.g. WAGO's MQTT library for its PFC controllers Already licensed with the hardware, pre-tested against that runtime
Community library Open-source CODESYS MQTT projects on GitHub No license cost; you own the maintenance and the TLS behaviour

Install the package, then add the library in the Library Manager of the application, not the device. After a package install CODESYS must be restarted before the library appears in the repository.

Check: the library resolves in the Library Manager with no yellow warning triangle, and the project compiles clean with an instance of the client FB declared. An unresolved library compiles into a runtime that silently never connects.

How do I get the first CONNECT out of the controller?

Instantiate the client block, call it every cycle from a task, and watch its status outputs. Do not gate the call behind a rising edge — the client needs cyclic execution to service keepalive and to reconnect.


The client ID must be unique. Two clients sharing an ID cause the broker to disconnect the older session on every new CONNECT, producing a connect/disconnect loop that looks like a network fault. Copying a project to a second controller without editing the ID is the most common way to create it.

Check: with mosquitto_sub -t '#' -v running, force the client online and watch for the topic. In parallel, the broker log records the CONNECT with the client ID — that line is the proof that the session reached layer 5, not just layer 4.

How do I build JSON payloads without burning scan time?

MQTT carries an opaque byte payload, and in practice that payload is JSON — text. String assembly is the work a PLC is worst at, and the packaging overhead per message is real. Three rules keep it bounded:

  1. Batch related tags into one payload with one timestamp. One topic per machine cell beats one topic per tag for both broker load and subscriber logic.
  2. Keep keys short. {"t":72.4,"st":1} costs a fraction of a verbose schema, and the difference is multiplied by every message on the bus.

Where the controller is already loaded, or the payload schema changes often, put the MQTT client outside the PLC: expose the data over a protocol the PLC does well and let Node-RED or a Python service do the JSON assembly, retry logic, and TLS. That gateway is also far easier to redeploy than a PLC application.

Pick QoS deliberately — it changes packet count on the wire:

QoS Handshake Use for
0 PUBLISH only High-rate telemetry where the next sample supersedes the last
1 PUBLISH + PUBACK Events, counters, alarms; duplicates possible, subscriber must tolerate them
2 Four-packet exchange Non-idempotent commands only; highest latency and broker cost

Check: subscribe and confirm the message rate matches your intended publish rate, not the task rate.

End-to-end verification

Work the path once more, in order, with the machine running:

  1. Subscribe with mosquitto_sub -h <broker> -t '#' -v from a third host — not the broker, not the PLC.
  2. Confirm every expected topic appears and the JSON parses. Pipe through a JSON parser; a truncated payload from an undersized string buffer parses as an error, not as a short value.
  3. Set a Last Will and Testament on the client, then pull the controller's Ethernet cable. The LWT topic must appear at the subscriber within the keepalive timeout — that is the only proof your consumers can detect a dead PLC.
  4. Reconnect the cable and confirm the client re-establishes on its own, with no PLC restart and no duplicate-ID churn in the broker log.
  5. Publish retained values for slow-changing setpoints, then start a fresh subscriber and confirm it receives current state immediately instead of waiting for the next change.

Step 3 is the one that gets skipped and the one that matters at 03:00, when a SCADA screen is showing a stale value from a controller that dropped off the network an hour earlier.

FAQ

How do I test MQTT without a PLC?

Install Mosquitto and use its own clients: mosquitto_sub -h 127.0.0.1 -t '#' -v in one terminal, mosquitto_pub -t 'test/topic' -m 'hello' in another. That exercises topics, wildcards, QoS, and retain end to end, and it is the correct place to learn the protocol before adding a controller.

How do I add MQTT to a CODESYS project?

Install the IIoT Libraries SL package through the CODESYS Package Manager (or your controller vendor's MQTT library, such as WAGO's for PFC hardware), restart CODESYS, then add the library in the application's Library Manager. Bare CODESYS ships no MQTT stack.

How do I fix a CODESYS client that will not connect to Mosquitto?

Check the broker first: Mosquitto 2.x binds only to localhost and blocks anonymous clients by default, so add listener 1883 0.0.0.0 and an authentication setting, then confirm a remote mosquitto_sub works. If that passes, verify the client ID is unique — a duplicate ID produces a repeating connect/disconnect cycle in the broker log.

How do I keep JSON string handling from loading the PLC?

Publish on change or on a heartbeat rather than every scan, batch several tags into one payload with a single timestamp, and use short keys. If the load is still high or the schema changes often, run the MQTT client in Node-RED or Python and let it pull data from the controller instead.

Back to blog