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:
- Priority: if several conditions are true, the highest priority wins. A sensible order is offline, down, setup, running, idle.
- 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
statusturns 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=Truebecause 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:
- Availability: running time divided by planned production time, from the event history.
- Downtime log: each running-to-down transition opens a downtime record; operators add reasons on a tablet.
- Cycle time: the same cycle input, timestamped, gives cycle-by-cycle durations, covered in cycle time monitoring.
- Part counts: a counter on the same pulse feeds production counting with IoT sensors.
- 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.



