Industrial IoT

Beckhoff TwinCAT MQTT: Publishing Structured PLC Data to a Broker

Use TwinCAT 3 IoT functions to publish structured PLC data as JSON, verify it on a public broker and then connect to a private broker with TLS, credentials and a Last Will.

Beckhoff industrial PC running TwinCAT 3 and publishing machine data to a cloud broker, illustrating Beckhoff TwinCAT MQTT
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

Beckhoff TwinCAT MQTT support lets a Beckhoff industrial PC publish machine data directly from the PLC runtime, with no separate gateway in the cabinet. In this tutorial you will use the TwinCAT 3 IoT functions to connect to an MQTT broker, serialize a structured data type to JSON, publish it on change and on a timer, and finally secure the connection with TLS and credentials. The same pattern scales from a single test bench to a fleet of machines.

TwinCAT MQTT Options Compared

Beckhoff offers more than one route to MQTT, and choosing the right one up front saves rework.

Option What it is Who configures it Best for
TF6701 IoT Communication PLC library (Tc3_IotBase) with an MQTT client function block PLC programmer in structured text Full control over topics, payloads and timing
TF6720 IoT Data Agent Configuration-based gateway that maps ADS symbols to MQTT Configurator GUI, no PLC code Publishing existing symbols without changing the PLC program
External ADS gateway Your own service reading symbols via ADS and publishing MQTT Software team Custom pipelines, many controllers, non-standard formats

This tutorial uses TF6701 because it is the most common choice for machine builders who want MQTT behavior to be part of the versioned PLC project. Both TF6701 and TF6720 are licensed TwinCAT functions; a 7-day trial license is enough to follow along.

Prerequisites

  • TwinCAT 3.1 XAE (Visual Studio or TwinCAT XAE Shell) and a target: a Beckhoff IPC, or your engineering PC in run mode for testing.
  • The TF6701 function installed on the target and a trial or full license activated.
  • Outbound network access from the target to the broker on TCP 1883 for testing and 8883 for TLS.
  • PLC libraries Tc3_IotBase and Tc3_JsonXml added to the project references.
  • mosquitto clients on a PC for verification.

Step 1: Define a Structured Data Type

Publishing a structure rather than loose variables keeps related values together and timestamps consistent. Add a DUT:

TYPE ST_PressData :
STRUCT
    rForce_kN       : REAL;
    rTemperature_C  : REAL;
    nCycleCount     : UDINT;
    bRunning        : BOOL;
    sState          : STRING(20);
END_STRUCT
END_TYPE

Step 2: Connect with FB_IotMqttClient

FB_IotMqttClient from Tc3_IotBase is configured through properties and driven by calling its Execute method every cycle. Call it from a task with a cycle of 10 ms or faster; the client handles its own communication in the background, but it only progresses when Execute is called.

PROGRAM MAIN
VAR
    fbMqttClient   : FB_IotMqttClient;
    bSetParameter  : BOOL := TRUE;
    bConnect       : BOOL := TRUE;

    fbJson         : FB_JsonSaxWriter;
    stPress        : ST_PressData;
    stLastPress    : ST_PressData;

    sTopicData     : STRING(255) := 'connectmq/demo/t3w9p1/plant1/stamping/cell4/press1/telemetry';
    sPayload       : STRING(1023);
    fbHeartbeat    : TON;
    bPublish       : BOOL;
END_VAR

// --- One-time client configuration ---
IF bSetParameter THEN
    bSetParameter := FALSE;
    fbMqttClient.sHostName  := 'broker.connectmq.com';
    fbMqttClient.nHostPort  := 1883;
    fbMqttClient.sClientId  := 'twincat-demo-t3w9p1';   // non-empty and unique
    fbMqttClient.nKeepAlive := 30;
END_IF

// --- Keep the connection alive every cycle ---
fbMqttClient.Execute(bConnect);

// --- Decide when to publish: on change or every 10 s ---
fbHeartbeat(IN := NOT fbHeartbeat.Q, PT := T#10S);
bPublish := fbHeartbeat.Q
            OR ABS(stPress.rForce_kN - stLastPress.rForce_kN) > 1.0
            OR stPress.bRunning <> stLastPress.bRunning
            OR stPress.sState <> stLastPress.sState;

// --- Serialize and publish ---
IF bPublish AND fbMqttClient.bConnected THEN
    fbJson.ResetDocument();
    fbJson.StartObject();
    fbJson.AddKey('force_kN');       fbJson.AddReal(stPress.rForce_kN);
    fbJson.AddKey('temperature_C');  fbJson.AddReal(stPress.rTemperature_C);
    fbJson.AddKey('cycle_count');    fbJson.AddUdint(stPress.nCycleCount);
    fbJson.AddKey('running');        fbJson.AddBool(stPress.bRunning);
    fbJson.AddKey('state');          fbJson.AddString(stPress.sState);
    fbJson.EndObject();
    fbJson.CopyDocument(sPayload, SIZEOF(sPayload));

    fbMqttClient.Publish(
        sTopic       := sTopicData,
        pPayload     := ADR(sPayload),
        nPayloadSize := LEN(sPayload),
        eQoS         := TcIotMqttQos.AtLeastOnceDelivery,
        bRetain      := FALSE,
        bQueue       := FALSE);

    stLastPress := stPress;
END_IF

Some notes on the choices above:

  • CopyDocument instead of GetDocument. GetDocument returns a standard 255-character string, which silently truncates larger payloads. Copying into a sized buffer avoids that.
  • Report by exception. The force deadband and state comparisons publish real changes immediately, while the 10 second heartbeat proves the machine is alive even when nothing changes.
  • Error handling. Watch fbMqttClient.hrErrorCode and fbMqttClient.eConnectionState in online view during commissioning. Most failures show up there first.

For larger structures, Tc3_JsonXml also offers a helper that serializes a whole data type, including attributes such as units, in one call. Hand-written SAX output like the above gives you exact control over key names, which matters when other teams consume the data.

Step 3: Verify on the Public Broker

Activate the configuration, start the PLC and subscribe from a PC:

mosquitto_sub -h broker.connectmq.com -p 1883 -i twincat-watch-t3w9p1 \
  -t 'connectmq/demo/t3w9p1/#' -v

Expected output:

connectmq/demo/t3w9p1/plant1/stamping/cell4/press1/telemetry {"force_kN":412.5,"temperature_C":38.2,"cycle_count":10233,"running":true,"state":"AUTO"}

Force a value in online view and the change appears instantly. The anonymous test broker at broker.connectmq.com is perfect for this kind of bring-up, but it is public, so use a random topic prefix and nothing confidential.

Step 4: Secure the Beckhoff TwinCAT MQTT Connection

Production machines should never publish over plain TCP to the internet. Create a private broker in the ConnectMQ portal, note the generated username, password and client ID, then:

  1. Download the ISRG Root X1 certificate (the root of Let's Encrypt, which the ConnectMQ broker uses) and copy it to the target, for example into a Certificates folder under the TwinCAT boot or config directory.
  2. Extend the one-time configuration:
fbMqttClient.sHostName     := 'YOUR_BROKER_HOST';
fbMqttClient.nHostPort     := 8883;
fbMqttClient.sClientId     := 'YOUR_CLIENT_ID';
fbMqttClient.sUserName     := 'YOUR_USERNAME';
fbMqttClient.sUserPassword := 'YOUR_PASSWORD';
fbMqttClient.stTLS.sCA     := 'C:\TwinCAT\3.1\Config\Certificates\isrgrootx1.pem';
  1. Configure a Last Will through the client's will structure, for example a retained offline message on plant1/stamping/cell4/press1/status, and publish a retained online on the same topic after connecting. Consumers can then tell a stopped machine from a lost connection.
  2. Drop the demo prefix and use your plant's ISA-95 topic hierarchy.

Keep credentials out of source control: load them from a file on the target or from a persistent variable set during commissioning, rather than hard-coding them in a project that gets shared.

Designing Topics for a Machine Fleet

Machine builders often ship the same TwinCAT project to dozens of customers, so hard-coded topics quickly become a maintenance problem. A few habits help:

  • Make the topic prefix a parameter. Store enterprise, site, area, line and machine names in persistent variables set during commissioning, and build the topic string at startup.
  • Separate data, status and events. Use .../telemetry for periodic values, .../status for retained online and state information, and .../events for alarms or batch completions. Consumers subscribe only to what they need.
  • Version your payload. A "schema": 1 key costs a few bytes and lets downstream code handle a firmware update that adds or renames fields.
  • Keep payloads small. Publish the values people actually use. Diagnostic dumps can go on a separate, on-demand topic.

These conventions also make the data easy to browse: a broker that recognizes ISA-95 style paths can present every machine in a site and line tree without extra configuration.

Commissioning Checklist

Check Why it matters
Target clock synchronized via NTP TLS rejects certificates when the clock is far off
DNS configured on the IPC The broker hostname must resolve
Unique client ID per machine Duplicates disconnect each other repeatedly
Payload buffer sized for the largest JSON Prevents silent truncation
Heartbeat plus report by exception Low traffic with proof of life
Retained status topic with Last Will Reliable online and offline detection
License for TF6701 activated The trial expires after 7 days

Troubleshooting

  • hrErrorCode shows a connection refusal. Wrong credentials or client ID; on brokers that bind client IDs to credentials, both must match what was issued.
  • TLS errors after a reboot. The certificate path is wrong on the target, or the CA file was not copied with the boot project.
  • Nothing published despite bConnected. bPublish is never true, or Publish is called before the payload is built.
  • Messages arrive in bursts. bQueue := TRUE with a slow Execute call rate; call Execute every cycle from a fast task.

Where This Fits

Publishing from the IPC is one of several architectures for getting machine data out; compare it with gateway-based and SCADA-based designs in PLC to cloud connectivity. If you work with mixed controller brands, the CODESYS MQTT tutorial uses a very similar execute-and-publish pattern. And when many machines publish into one hierarchy, the industrial IoT with MQTT guide explains how to model it as a unified namespace that every application can consume through the MQTT broker.

Connect Your Beckhoff Machines to ConnectMQ

Running your own broker next to each machine means certificates, updates and monitoring for every site. A fully managed MQTT service removes that work. Create a free Serverless broker at app.connectmq.com, no credit card required, and watch your TwinCAT data arrive in the live topic explorer within minutes.

Frequently Asked Questions

Which TwinCAT license do I need for MQTT?

The PLC library approach requires TF6701 TwinCAT 3 IoT Communication. The configuration-based alternative is TF6720 IoT Data Agent. Both offer a 7-day trial license.

Does TwinCAT MQTT support TLS?

Yes. Set the CA certificate path in the client's TLS structure, connect to port 8883 and add credentials. Client certificates are also supported if your broker requires them.

Can TwinCAT subscribe to MQTT topics?

Yes. FB_IotMqttClient supports subscriptions with a message queue that you read in the PLC. Validate every received value before it touches control logic.

Why is my JSON payload cut off at 255 characters?

GetDocument returns a standard-length string. Use CopyDocument into a larger buffer, as shown above.

Related articles

IIoT Edge Gateway: What It Does and How to Choose One

An IIoT edge gateway connects machines that speak Modbus, OPC UA or proprietary protocols to MQTT and the cloud. Learn its core roles, common architectures and how to choose the right one.

7 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.