Smart Manufacturing

CNC Machine Monitoring with MQTT: Spindle Load, Program and Utilization

Learn how to collect execution state, active program, spindle load and part count from CNC machines, publish them over MQTT and turn them into an honest utilization number.

CNC machine monitoring illustration with a machining center streaming spindle load and program status over MQTT to a utilization chart
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

CNC machine monitoring answers the question every machine shop argues about: how much of the day are the spindles actually cutting? In this tutorial you will collect execution state, active program, spindle load and part count from a CNC controller, publish them over MQTT and calculate utilization you can trust. The examples use MTConnect, with notes on other controllers and a current-sensor fallback for older machines.

Why CNC Machine Monitoring Pays Off

Most shops overestimate utilization. A machine that looks busy may be waiting on a first-article check, a tool, a program edit or an operator who is running two other machines. Without data, these gaps hide inside the shift. Once execution state is logged second by second, the pattern of losses becomes visible: long setups on certain jobs, warm-up cycles that run too long, or a machine that idles every afternoon because material arrives late.

Good CNC monitoring also helps beyond utilization. Spindle load trends reveal tool wear, part counts feed quoting and scheduling, and program names connect machine time to specific jobs.

Which Signals to Collect

Signal Typical source Why it matters
Execution state (ACTIVE, READY, STOPPED, INTERRUPTED, FEED_HOLD) Controller Core of utilization and downtime
Controller mode (AUTOMATIC, MANUAL, MDI, EDIT) Controller Separates production from setup and proving
Active program name Controller Links time to jobs and parts
Spindle load (%) Controller Detects cutting vs air-cutting, tool wear
Spindle speed and feed override Controller Shows operators slowing programs down
Part count Controller or macro variable Production counting and cycle time
Alarm code and message Controller Downtime reasons without operator input
Spindle motor current External CT sensor Fallback for machines without a data port

Start with execution, mode, program and part count. Those four produce utilization by job. Add spindle load and alarms once the basics are stable.

Getting Data Out of the Controller

How you read the controller depends on the brand and age:

  • MTConnect. Many modern machines ship with an MTConnect adapter, or one can be added. An MTConnect agent exposes the machine's current state over HTTP as XML, which is easy to poll and vendor-neutral. The MTConnect to MQTT guide covers agent setup in detail.
  • OPC UA. Several controllers offer an OPC UA server option. An OPC UA client at the edge reads the same values.
  • Vendor libraries. Some controller families provide their own protocol or library for reading status. These work, but tie your gateway code to one brand.
  • Discrete I/O and current sensors. For legacy machines with no network port, wire the cycle-start lamp or clip a current transformer on the spindle motor lead. You lose program names but still get a reliable running signal.

Whatever the source, the pattern after the edge is the same: normalize the values into one JSON schema and publish to an MQTT broker. That keeps dashboards, OEE calculators and maintenance tools independent of controller brands.

Topic and Payload Design

Use one topic per machine for state and one for slower-changing job data:

connectmq/demo/<your-id>/plant1/machining/cnc01/state
connectmq/demo/<your-id>/plant1/machining/cnc01/job
{
  "execution": "ACTIVE",
  "mode": "AUTOMATIC",
  "spindle_load": 42.5,
  "spindle_speed": 8000,
  "part_count": 117,
  "alarm": null,
  "ts": "2026-10-16T09:14:05Z"
}

Publish the state message retained so any new subscriber immediately knows each machine's current condition. If you already run Sparkplug B on the shop floor, ConnectMQ can decode those payloads and republish them into ISA-95 topics such as connectmq/factory1/production/line1/cnc1/telemetry, so both conventions can coexist.

Build the MTConnect to MQTT Gateway

This Python gateway polls an MTConnect agent's /current endpoint every second, extracts the data items you configure and publishes a message only when something changes, plus a heartbeat every 30 seconds. Find your data item IDs by opening http://<agent>:5000/probe in a browser.

import json
import time
import urllib.request
import xml.etree.ElementTree as ET
from datetime import datetime, timezone

import paho.mqtt.client as mqtt

AGENT_URL = "http://192.168.10.50:5000/current"
TOPIC = "connectmq/demo/k2m8q4/plant1/machining/cnc01/state"

# Map JSON field names to the dataItemId values from /probe
DATA_ITEMS = {
    "execution": "exec",
    "mode": "mode",
    "program": "pgm",
    "spindle_load": "Sload",
    "spindle_speed": "Srpm",
    "part_count": "pc",
}
NUMERIC = {"spindle_load", "spindle_speed", "part_count"}


def read_agent():
    with urllib.request.urlopen(AGENT_URL, timeout=3) as resp:
        root = ET.fromstring(resp.read())
    by_id = {el.get("dataItemId"): el.text for el in root.iter() if el.get("dataItemId")}
    result = {}
    for field, item_id in DATA_ITEMS.items():
        value = by_id.get(item_id)
        if value in (None, "UNAVAILABLE"):
            result[field] = None
        elif field in NUMERIC:
            result[field] = float(value)
        else:
            result[field] = value
    return result


def on_connect(client, userdata, flags, reason_code, properties):
    print("Connected:", reason_code)


client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="cnc01-gw-k2m8q4")
client.on_connect = on_connect
client.will_set(TOPIC, json.dumps({"execution": "OFFLINE"}), qos=1, retain=True)
client.connect("broker.connectmq.com", 1883, keepalive=30)
client.loop_start()

def fingerprint(data):
    # Ignore fields that change constantly so we only publish real state changes
    return {k: v for k, v in data.items() if k not in ("spindle_load", "spindle_speed")}


last_fp, last_time = None, 0.0
while True:
    try:
        data = read_agent()
    except Exception as exc:  # agent down or network issue
        data = {"execution": "UNAVAILABLE", "error": str(exc)}
    fp = fingerprint(data)
    if fp != last_fp or time.time() - last_time > 30:
        data["ts"] = datetime.now(timezone.utc).isoformat()
        client.publish(TOPIC, json.dumps(data), qos=1, retain=True)
        last_fp, last_time = fp, time.time()
    time.sleep(1)

Two details matter. First, spindle load and speed are excluded from the change check because they fluctuate constantly; it still travels with every state change and heartbeat. If you need a load trend for tool wear, publish it on a separate load topic at a fixed rate. Second, the Last Will message marks the machine OFFLINE if the gateway dies, so a dead gateway is never mistaken for a running machine.

Watch the output from any terminal:

mosquitto_sub -h broker.connectmq.com -p 1883 -i cnc-viewer-k2m8q4 -t "connectmq/demo/k2m8q4/plant1/machining/+/state" -v

Turning State into Utilization

Utilization is time in a productive state divided by time available. Define the states explicitly, because "running" means different things to different people:

  1. Cutting: execution ACTIVE, mode AUTOMATIC. This is productive time.
  2. Proving or setup: execution ACTIVE in MANUAL or MDI, or a program name matching your setup naming convention.
  3. Ready but idle: execution READY or STOPPED with no alarm. Usually waiting on an operator, material or inspection.
  4. Feed hold or interrupted: operator paused the cycle. Often a quality check or chip problem.
  5. Alarm: an active alarm. Planned for maintenance follow-up.
  6. Offline: no data. Treat as unknown, not as idle.

A subscriber that stores each state transition with a timestamp can sum durations per state per shift. That same event stream feeds machine downtime tracking, and when you add part counts and ideal cycle times it becomes OEE. The broader pattern for any machine type is described in machine status monitoring.

Pitfalls in CNC Data Collection

  • Treating READY as running. A machine with a program loaded and the door closed is not producing. Only ACTIVE in AUTOMATIC counts as cutting.
  • Part count resets. Operators often reset the counter at job change. Log the program name with every count so resets are explainable, and compute production from increments, not absolute values.
  • Clock drift. Timestamp at the gateway with a synchronized clock (NTP), not at the dashboard, or state durations will be wrong across machines.
  • Polling too fast. One-second polling of /current is plenty for state. Faster polling adds load on the agent without improving utilization accuracy.
  • Network segmentation. Machine networks are often isolated. Run the gateway on a PC with one leg on the machine network and an outbound-only connection to the broker on port 8883, so nothing on the internet can reach the controllers.

From One Machine to the Whole Shop

Start with the two or three machines that cause the most scheduling arguments. When the numbers match what the operators see, roll out to the rest of the shop with the same payload schema. Because all machines publish into one topic tree, a single subscription with a wildcard such as plant1/machining/+/state feeds the shop dashboard; the production line monitoring dashboard tutorial shows how to build it with Grafana.

For production, move off the public broker to a private one. A hosted MQTT broker such as ConnectMQ gives each gateway its own credentials over TLS on port 8883, and the portal's devices view groups machines by ISA-95 level so you can see at a glance which gateways are connected. Threshold alert rules in the portal can flag, for example, spindle load above a limit. If you want to understand the moving parts first, read about what an MQTT broker is, and for the wider picture see the smart manufacturing guide.

Start Monitoring Your CNC Machines on ConnectMQ

Sign up at app.connectmq.com, create a broker on the free Serverless plan with no credit card, and replace the host, port and credentials in the gateway above. Your first machine can be streaming utilization data within the hour.

Frequently Asked Questions

Do I need MTConnect to monitor a CNC machine?

No. MTConnect is convenient and vendor-neutral, but OPC UA, vendor libraries or a simple current sensor on the spindle motor also work. The MQTT side stays the same.

How accurate is utilization from a current sensor?

It reliably tells you when the spindle is turning, which is a good proxy for cutting. It cannot tell you the program or mode, so setup and air-cutting can look like production.

How much bandwidth does CNC monitoring use?

Very little. State messages are a few hundred bytes and are sent on change plus a heartbeat, so even a large shop produces only a modest message rate.

Can I monitor older machines without a network port?

Yes. Wire the cycle-start or in-cycle lamp to a digital input on an ESP32 or small PLC, or use a current transformer, and publish the running state over MQTT.

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.