Skip to main content
Version: 3.9

Building a Real-Time Custom UI with MQTT in R-COM Gateway

Example: A Live Production Line Counter

This tutorial shows you how to build a Custom UI that updates in real time using MQTT. Instead of refreshing the page or polling an API, the interface reacts the moment a new message is published to a topic.

We will build a Live Production Line Counter: a R-COM Gateway workflow publishes the running unit count for a production line using the MQTTPublisher action, and our Custom UI displays that count and updates instantly whenever a new message arrives.

In most real deployments the workflow is the main source of the data — it processes events, applies business logic, and pushes the result to a topic that the UI listens on.

By the end you will have configured:

  • A WSS-enabled MQTT client in the gateway.
  • A workflow that publishes live updates using the MQTTPublisher action.
  • A Custom UI (HTML + CSS + JavaScript).
  • An MQTT subscription inside the UI that reads live messages through the predefined message variable.

How Real-Time Updates Work​

A Custom UI runs entirely inside the user's browser. A browser cannot open a raw TCP MQTT connection (ports 1883 / 8883), so the UI subscribes through an MQTT client that connects to the broker over WSS (WebSocket Secure).

The end-to-end flow looks like this:

The MQTT client used by a Custom UI must be WSS-enabled

Because the UI runs in a browser served over HTTPS:

  • The connection must use WSS (wss://). Plain ws:// is blocked as insecure (mixed content).
  • The standard MQTT TCP ports (1883, 8883) will not work from the browser.
  • Your broker must expose a WebSocket (WSS) listener (commonly port 8084 or 443).

Prerequisites​

Before you start, make sure you have:

  • Access to the gateway with permission to create MQTT clients, workflows, Custom UIs, and roles.
  • An MQTT broker that exposes a WSS (WebSocket Secure) listener (for example, Mosquitto or HiveMQ configured with a WebSocket listener on port 8084).
  • A workflow that can publish the line count (using the MQTTPublisher action, shown in Step 6). Optionally, an MQTT tool such as MQTT.fx, MQTT Explorer, or mosquitto_pub for quick manual testing.

Our example uses the topic factory/line1/count with this JSON payload:

{
"line": "Line-1",
"count": 1287,
"status": "running",
"updatedAt": "2026-06-22T10:15:00Z"
}

Step 1: Configure a WSS-Enabled MQTT Client​

The Custom UI does not define its own broker connection. It references an existing MQTT client by name, so we create that client first.

  1. Go to Settings > Client Settings > MQTT.
  2. Click + New MQTT.
  3. Fill in the connection details, pointing the client at your broker's WSS endpoint:
FieldExample ValueNotes
MQTT Client IDcustom_ui_line1A unique identifier for the client.
NameLineCounterWSSThe name the Custom UI will reference. Remember this value.
Hostwss://broker.example.com:8084/mqttThe broker's WSS endpoint. Must be reachable from the browser.
Port8084The broker's WebSocket-Secure listener port.
Use TLSEnabledRequired for the encrypted wss:// transport.
Username / Password(optional)Only if your broker requires authentication.
Configure a WSS-enabled MQTT client

Configure a WSS-enabled MQTT client under Settings > Client Settings > MQTT

  1. Click Test Connection to verify connectivity. On success you will see a “MQTT Connection is valid” message.
  2. Click Submit to save.
info

The Name you set here (LineCounterWSS) is exactly what you will select inside the Custom UI later. For a full walkthrough of every field, see the MQTT Client Configuration guide.


Step 2: Create the Custom UI​

  1. Navigate to Custom UI → Custom UI Management.
  2. Click Create New.
  3. Configure the basic settings:
SettingExample ValuePurpose
Custom UI NameLine 1 Live CounterA friendly name to identify the interface.
Role AssignmentFloor_OperatorWhich roles may access the UI (skip if using public access).
Bootstrap OptionEnabledProvides ready-made styling for a quick, clean layout.
Allow Public AccessEnabled (optional)Lets the UI open without login, e.g. on a wall-mounted screen.
  1. Save to open the code editor.

Step 3: Add the HTML​

The HTML defines the structure of the counter. We use element IDs (count, status, updatedAt) that our JavaScript will update.

<div class="counter-card">
<h2>Production Line 1</h2>

<div class="count" id="count">--</div>
<div class="label">units produced</div>

<span class="status" id="status">waiting…</span>
<div class="updated">Last update: <span id="updatedAt">never</span></div>
</div>

Step 4: Add the CSS​

The CSS styles the card and the status badge.

.counter-card {
max-width: 360px;
margin: 40px auto;
padding: 24px;
border-radius: 12px;
background: #ffffff;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
text-align: center;
font-family: Arial, sans-serif;
}

.count {
font-size: 64px;
font-weight: 700;
color: #009688;
line-height: 1.1;
}

.label {
color: #666;
margin-bottom: 16px;
}

.status {
display: inline-block;
padding: 4px 12px;
border-radius: 999px;
background: #e0e0e0;
color: #333;
font-size: 14px;
}

.status.running { background: #c8f7dc; color: #0a7a3f; }
.status.stopped { background: #ffd6d6; color: #b00020; }

.updated {
margin-top: 12px;
font-size: 12px;
color: #999;
}

Step 5: Add the MQTT Subscription​

Now connect the UI to the broker so it can receive live messages.

  1. In the Custom UI editor, open the MQTT panel and click Manage Subscription → Add New.
  2. Configure the subscription:
FieldExample ValueDescription
NameLineCounterWSSThe WSS-enabled MQTT client you created in Step 1. This selects the broker connection.
Topicfactory/line1/countThe topic the UI listens to. Wildcards + and # are supported.
EditJS(see Step 6)The JavaScript that runs each time a message arrives on the topic.
Add an MQTT subscription in the Custom UI

Add an MQTT subscription: select the MQTT client, set the Topic, and add EditJS

note

There is no Host field here. The broker address, port, and WSS transport all come from the MQTT client selected in the Name field (configured under Settings > Client Settings > MQTT).


Step 6: Handle Incoming Messages with the message Variable​

Each MQTT subscription has its own EditJS editor that runs whenever a message arrives. Inside it, a predefined variable named message holds the incoming payload.

Key facts about message:

  • It is automatically populated by the gateway — you never declare or fetch it yourself.
  • It contains the raw payload as a string.
  • If the publisher sends JSON, parse it with JSON.parse(message).
  • The EditJS code re-runs for every new message, so message always holds the latest value.

Add the following to the subscription's EditJS editor:

// `message` is predefined and holds the incoming MQTT payload (a string).

function renderCount(message) {
let data;
try {
data = JSON.parse(message); // parse the JSON payload
} catch (e) {
console.error("Invalid MQTT payload:", message);
return; // ignore malformed messages
}

// Update the number
const count = data.count ?? 0;
document.getElementById("count").textContent = count.toLocaleString();

// Update the status badge
const statusEl = document.getElementById("status");
const status = (data.status ?? "unknown").toLowerCase();
statusEl.textContent = status;
statusEl.className = "status " + status; // applies .running / .stopped styles

// Update the timestamp
const ts = data.updatedAt ? new Date(data.updatedAt).toLocaleTimeString() : "now";
document.getElementById("updatedAt").textContent = ts;
}

// Invoke the handler with the predefined `message` variable
renderCount(message);

Whenever a new message is published to factory/line1/count, the gateway re-runs this code with the updated message, and the counter, status badge, and timestamp refresh instantly — no page reload required.

tip

Wrapping JSON.parse(message) in try/catch (as above) keeps a single malformed message from breaking the whole handler.


Step 7: Publish Live Updates from a Workflow (MQTTPublisher)​

In a real deployment, the data shown in the UI usually comes from a workflow. The workflow processes events (for example, each unit scanned on the line), builds the payload, and pushes it to the topic using the MQTTPublisher action. This is the main source of the live updates.

Inside your workflow, add an MQTTPublisher action configured to publish to the same topic the UI listens on:

ParameterExample ValueDescription
MQTT_CLIENTLineCounterWSSAn MQTT client connected to the same broker as the UI. The WSS client from Step 1 works.
TOPICfactory/line1/countMust match the Topic configured in the UI subscription (Step 5).
QOS_LEVEL00 (at most once) is fine for frequent dashboard updates; use 1 if no update may be lost.
PAYLOAD(JSON, see below)The message body the UI will receive in its message variable.
AWAIT_RESPONSEfalseDashboards are fire-and-forget; no broker reply is needed.
TIMEOUT_MILLIS0Only relevant when AWAIT_RESPONSE is true.
MQTTPublisher action in a workflow

The MQTTPublisher action publishing the payload to the topic the UI listens on

Build the PAYLOAD as a JSON string (for example with JsonSerializer, or by composing it directly) so it matches what the UI expects:

{
"line": "Line-1",
"count": 1288,
"status": "running",
"updatedAt": "2026-06-22T10:16:00Z"
}

Each time the workflow runs and the MQTTPublisher action executes, the broker forwards the message to every subscribed Custom UI, and the counter updates instantly.


Step 8: Save and Test​

  1. Save the Custom UI. Each save creates a new version, so you can roll back if needed.
  2. Open the UI's shareable link in a browser (or use Preview from Custom UI Management).
  3. Trigger the workflow from Step 7 (for example, by sending it an event or running it manually). Its MQTTPublisher action pushes a message to factory/line1/count.
  4. Watch the UI update immediately. Trigger the workflow again with different count and status values (for example "status":"stopped") and confirm the number, badge color, and timestamp all change in real time.
Custom UI updating in real time

The Custom UI reflecting the latest count, status, and timestamp after a message is published

Quick manual test

If you want to verify the UI before wiring up the workflow, you can publish a message by hand with any MQTT tool, such as mosquitto_pub:

mosquitto_pub -h broker.example.com -p 8883 \
-t "factory/line1/count" \
-m '{"line":"Line-1","count":1288,"status":"running","updatedAt":"2026-06-22T10:16:00Z"}'

Troubleshooting​

SymptomPossible CauseSolution
UI never updatesBrowser cannot reach the broker over WSSConfirm the MQTT client uses wss:// and the broker's WebSocket listener/port is open.
Console shows a mixed-content errorSubscription is using ws:// instead of wss://Use a WSS-enabled MQTT client; the UI is served over HTTPS and blocks insecure sockets.
Values show as undefinedPayload field names don't match the codeEnsure the JSON keys (count, status, updatedAt) match what the publisher sends.
Nothing happens for some messagesTopic mismatchThe subscription Topic must match the publisher's topic exactly (mind wildcards).
“Invalid MQTT payload” in the consolePublisher sent non-JSON or malformed dataPublish valid JSON, or adjust the handler to accept the payload format you use.

Summary​

You built a Custom UI that updates in real time using MQTT by:

  1. Creating a WSS-enabled MQTT client under Settings > Client Settings > MQTT.
  2. Building a Custom UI with HTML, CSS, and JavaScript.
  3. Adding an MQTT subscription that references the WSS client by Name and listens on a Topic.
  4. Reading each incoming payload through the predefined message variable in EditJS and updating the DOM.
  5. Publishing the live data from a workflow using the MQTTPublisher action — the main source of updates in production.

The same pattern works for live sensor dashboards, asset tracking, production monitoring, and alert screens — anywhere you need the interface to reflect events the instant they happen.