Smart Manufacturing

Machine Status Monitoring with MQTT: Running, Idle and Down in Real Time

Define a clean machine state model, derive running, idle and down from stack lights or PLC signals, and publish retained MQTT messages so every dashboard shows the current state the moment it connects.

Machine status monitoring board showing running, idle and down states for several machines updated live over MQTT retained messages
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

Machine status monitoring answers the most basic shop floor question, "is it running right now?", for every machine at once. In this tutorial you will define a simple state model, derive running, idle and down from stack light or PLC signals on a Raspberry Pi, and publish the state as retained MQTT messages so any dashboard shows the current status the instant it connects.

Why Start with Machine Status

Machine state is the foundation for almost every production KPI. Availability in OEE, downtime analysis, cycle time statistics and energy per idle hour all depend on knowing when a machine was running and when it was not. It is also the cheapest data to get: many machines already show their state on a stack light, and nearly every PLC has a "cycle active" and a "fault" bit.

The difficulty is not the signal, it is consistency. If one machine reports "auto", another "RUN" and a third only a green lamp, comparing them is painful. That is why the state model comes first.

A Practical State Model

Keep the number of states small and the definitions unambiguous. This five-state model covers most discrete manufacturing:

State Definition Typical signal
running Machine is producing; a cycle completed within the expected time Cycle pulse, green light, PLC "in cycle"
idle Machine is healthy and available but not producing (waiting for material, operator, upstream) No fault, no recent cycle
down Machine cannot produce because of a fault or alarm Red light, PLC fault bit
setup Planned changeover, tool change or maintenance Operator input, PLC mode, MES order state
offline No data from the edge device MQTT Last Will

Two rules make the model work:

  1. Priority: if several conditions are true, the highest priority wins. A sensible order is offline, down, setup, running, idle.
  2. Debounce with time: a machine that completes a cycle every 40 seconds should not flicker to idle between cycles. Treat it as running until no cycle has been seen for, say, twice the nominal cycle time.

Reasons for down and idle (no material, tool breakage, quality hold) are a separate dimension. Capture them in a reason field later instead of multiplying states.

Topic Design with Retained Messages

Each machine gets a state topic that holds its current state as a retained message. The broker stores the last retained message per topic and sends it immediately to every new subscriber. A dashboard opened at 10:15 does not have to wait for the next change; it sees "down since 09:52" right away. Read MQTT retained messages for the full mechanics.

connectmq/plant1/machining/line1/cnc-01/state     retained  current state
connectmq/plant1/machining/line1/cnc-01/events    not retained, one message per transition
connectmq/plant1/machining/line1/cnc-01/status    retained  online / offline (Last Will)

State payload:

{
  "state": "down",
  "since": 1760599920,
  "previous": "running",
  "reason": null,
  "source": "edge-cnc-01"
}

Including since lets a dashboard show the duration without any history database. The events topic is what a historian subscribes to for downtime and availability reports.

Wiring the Signals

For machines without network access to the PLC, tap the stack light or spare PLC outputs through 24 V optocoupler input modules into the Raspberry Pi GPIO. Never connect 24 V signals directly to the Pi; its GPIO pins are 3.3 V only. Electrical work in machine cabinets must be done by qualified staff.

Signal GPIO (BCM) Meaning when active
Cycle complete pulse or green light 17 A cycle finished / machine in cycle
Red light or fault output 27 Machine in fault
Setup key switch or yellow light 22 Planned setup or changeover

Where the PLC is reachable over the network, read the same bits over Modbus TCP, OPC UA or S7 instead; only the input function changes.

The Machine Status Monitoring Edge Script

Install the libraries in a virtual environment (pip install gpiozero lgpio paho-mqtt). The script below evaluates the inputs twice a second, applies priority and timing rules, and publishes only on transitions.

#!/usr/bin/env python3
"""Machine status monitoring edge script: GPIO signals -> retained MQTT state."""
import json
import time

import paho.mqtt.client as mqtt
from gpiozero import DigitalInputDevice

BROKER = "broker.connectmq.com"
MACHINE = "cnc-01"
BASE = f"connectmq/demo/msm-5t2r/line1/{MACHINE}"
NOMINAL_CYCLE_S = 40
RUN_TIMEOUT_S = 2 * NOMINAL_CYCLE_S

cycle_in = DigitalInputDevice(17, pull_up=True, bounce_time=0.02)
fault_in = DigitalInputDevice(27, pull_up=True, bounce_time=0.05)
setup_in = DigitalInputDevice(22, pull_up=True, bounce_time=0.05)

last_cycle = 0.0
state, since = None, 0


def on_cycle():
    global last_cycle
    last_cycle = time.time()


cycle_in.when_activated = on_cycle


def derive_state(now):
    if fault_in.is_active:
        return "down"
    if setup_in.is_active:
        return "setup"
    if cycle_in.is_active or now - last_cycle < RUN_TIMEOUT_S:
        return "running"
    return "idle"


def on_connect(client, userdata, flags, reason_code, properties):
    print("Connected:", reason_code)
    client.publish(f"{BASE}/status", "online", qos=1, retain=True)
    if state is not None:                           # restore after reconnect
        publish_state(client, state, state, since, event=False)


def publish_state(client, new, previous, ts, event=True):
    payload = {"state": new, "since": ts, "previous": previous,
               "reason": None, "source": f"edge-{MACHINE}"}
    client.publish(f"{BASE}/state", json.dumps(payload), qos=1, retain=True)
    if event:
        client.publish(f"{BASE}/events", json.dumps(payload), qos=1)
    print(payload)


client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"msm-5t2r-{MACHINE}")
client.will_set(f"{BASE}/status", "offline", qos=1, retain=True)
client.on_connect = on_connect
client.connect(BROKER, 1883, keepalive=30)
client.loop_start()

while True:
    now = time.time()
    new = derive_state(now)
    if new != state:
        previous, state, since = state, new, int(now)
        publish_state(client, state, previous, since)
    time.sleep(0.5)

A few design choices worth noting:

  • The Last Will on status turns a crashed script, a lost network or a power cut into a visible "offline" state rather than a frozen "running".
  • On reconnect the script republishes the retained state, so the broker always holds the latest truth even if a transition happened while the connection was down.
  • Inputs use pull_up=True because typical optocoupler modules pull the line low when active. Invert if your module behaves differently.

Run it as a systemd service so it starts on boot. For a production broker, add client.username_pw_set("YOUR_USERNAME", "YOUR_PASSWORD"), client.tls_set(), use your issued client ID and connect to port 8883.

Viewing the Status Board

Any MQTT client can now show live status. From a terminal:

mosquitto_sub -h broker.connectmq.com -p 1883 -i msm-board-$RANDOM \
  -t "connectmq/demo/msm-5t2r/line1/+/state" -v

The retained states appear immediately, one line per machine. In a browser dashboard, MQTT.js connects over secure WebSocket and does the same:

import mqtt from "mqtt";

const client = mqtt.connect("wss://broker.connectmq.com:8084/mqtt", {
  clientId: "msm-board-" + Math.random().toString(16).slice(2, 8),
  protocolVersion: 5,
});
const board = {};

client.on("connect", () => client.subscribe("connectmq/demo/msm-5t2r/line1/+/state"));
client.on("message", (topic, payload) => {
  const machine = topic.split("/").at(-2);
  board[machine] = JSON.parse(payload.toString());
  const lines = Object.entries(board).map(([m, s]) => {
    const minutes = Math.round((Date.now() / 1000 - s.since) / 60);
    return `${m}: ${s.state} for ${minutes} min`;
  });
  console.clear();
  console.log(lines.join("\n"));
});

Remember that topics on the public MQTT test broker are visible to everyone; machine names and states from a real plant belong on a private broker.

From Status to Metrics

Once the events stream exists, the rest of line monitoring builds on it:

  1. Availability: running time divided by planned production time, from the event history.
  2. Downtime log: each running-to-down transition opens a downtime record; operators add reasons on a tablet.
  3. Cycle time: the same cycle input, timestamped, gives cycle-by-cycle durations, covered in cycle time monitoring.
  4. Part counts: a counter on the same pulse feeds production counting with IoT sensors.
  5. Escalation: a machine down longer than a threshold triggers a call on a digital andon system.

Troubleshooting

Symptom Cause Fix
State flickers between running and idle Run timeout shorter than real cycle variation Increase RUN_TIMEOUT_S
Dashboard shows stale "running" after a power cut No Last Will, or dashboard ignores status Use Last Will and show offline with priority
Old machines still appear on the board Retained messages remain after decommissioning Publish an empty retained message to clear the topic
Fault input always active Inverted logic of the optocoupler module Change pull_up or use active_state

Running It at Scale

Dozens of machines, each with a retained state and an edge client that must reconnect reliably, is exactly the workload a managed MQTT broker is built for. ConnectMQ also exposes connection state per client under $SYS/broker/connection/{clientId}/state, which helps spot edge devices that keep dropping. The smart manufacturing guide shows how machine state fits into a full monitoring roadmap.

Get Started with ConnectMQ

ConnectMQ gives you a TLS-secured MQTT 5 broker with retained messages, a live topic explorer and an ISA-95 devices view that groups machines by area and line. Create a free Serverless broker with no credit card and move your machine status monitoring from the test broker to production.

Frequently Asked Questions

Why use retained messages for machine status?

A retained message is delivered to every new subscriber immediately, so dashboards, tablets and services always start with the current state of each machine instead of waiting for the next change.

How do I detect that a machine's edge device is offline?

Set a Last Will message on a retained status topic when the client connects. If the device disconnects unexpectedly, the broker publishes the Will, and dashboards can display the machine as offline.

Can I get machine state directly from the PLC instead of stack lights?

Yes. Read the PLC's cycle and fault bits over Modbus TCP, OPC UA or a vendor protocol and feed them into the same state logic. PLC signals are usually more precise than stack lights.

How many states should a machine status model have?

As few as possible while still answering your questions. Running, idle, down, setup and offline cover most needs; use reason codes for details rather than adding states.

Related articles

OEE vs TEEP vs OOE: Which Metric Should You Use?

OEE, OOE and TEEP share the same performance and quality factors but measure against different time bases. This comparison works through one example and lists the MQTT signals each metric needs.

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.