Code Library
Machine DataPythonPython 3.8+ · standard library onlyMIT licenseStarter

Poll an MTConnect agent's /current for execution state (Python)

MTConnect's whole pitch is that the data is just XML over HTTP — no vendor DLL, no bitness matching, no licensed library. This script proves it: it polls any agent's /current endpoint, strips the XML namespace every MTConnect response wraps its tags in (the part every first attempt trips on), and prints availability, execution state, controller mode, and part count for every device the agent knows about. It's tested here against the public demo agent, live, before ever touching a real machine.

Before you run it

  • Python 3.8+ (standard library only — no requests, no DLLs)
  • An MTConnect agent URL — test instantly against the public demo agent, https://demo.mtconnect.org

The code

GitHub
"""Poll an MTConnect agent's /current and print execution state per device.

Usage:  python mtc_poller.py https://demo.mtconnect.org
"""

import sys
import time
import urllib.request
import xml.etree.ElementTree as ET

POLL_SECONDS = 2


def local_name(tag):
    """Strip the namespace off an ElementTree tag - '{uri}Execution' -> 'Execution'."""
    return tag.rsplit("}", 1)[-1]


def read_current(base_url):
    with urllib.request.urlopen(f"{base_url}/current", timeout=10) as resp:
        root = ET.fromstring(resp.read())

    devices = {}
    for device_stream in root.iter():
        if local_name(device_stream.tag) != "DeviceStream":
            continue
        name = device_stream.get("name")
        state = {}
        for elem in device_stream.iter():
            tag = local_name(elem.tag)
            if tag in ("Execution", "ControllerMode", "Availability", "PartCount"):
                state[tag] = elem.text
        devices[name] = state
    return devices


def main():
    base_url = sys.argv[1] if len(sys.argv) > 1 else "https://demo.mtconnect.org"
    print(f"Polling {base_url}/current every {POLL_SECONDS}s - Ctrl+C to stop.")
    try:
        while True:
            for name, state in read_current(base_url).items():
                avail = state.get("Availability", "?")
                exe = state.get("Execution", "?")
                mode = state.get("ControllerMode", "?")
                parts = state.get("PartCount", "?")
                print(f"{name:<10} avail={avail:<12} exec={exe:<10} "
                      f"mode={mode:<10} parts={parts}")
            print("-" * 60)
            time.sleep(POLL_SECONDS)
    except KeyboardInterrupt:
        pass


if __name__ == "__main__":
    main()

What you get

$ python mtc_poller.py https://demo.mtconnect.org
Polling https://demo.mtconnect.org/current every 2s - Ctrl+C to stop.
OKUMA      avail=AVAILABLE   exec=ACTIVE    mode=AUTOMATIC parts=0
Agent      avail=AVAILABLE   exec=?         mode=?         parts=?
Mazak      avail=AVAILABLE   exec=ACTIVE    mode=AUTOMATIC parts=0
------------------------------------------------------------
^C

How it works

  • root.iter() walks every element regardless of nesting depth — MTConnect's XML nests DeviceStreamComponentStreamEvents/Samples several levels deep, and this script doesn't need to know the exact shape to find what it wants.
  • local_name() strips {urn:mtconnect.org:MTConnectStreams:2.7} off every tag. This is the line every first MTConnect script is missing — without it, elem.tag == "Execution" is never true, because ElementTree includes the namespace URI in the tag string whenever the document declares a default xmlns.
  • One urllib.request call and the standard library's own xml.etree.ElementTree — no dependencies, which is the entire point of MTConnect's HTTP/XML design over a vendor SDK.
  • The Agent device shows ? for execution and mode on purpose — the agent process itself isn't a machine tool, so those data items don't exist for it. Real code should expect gaps like this per device.

Gotchas & honest limits

  • The namespace is the whole trick. Different agent versions declare different MTConnect schema versions (2.7 here, 1.3/1.5 on older agents) — local_name() sidesteps that by ignoring the URI entirely rather than hardcoding a version.
  • /current is a snapshot, not a stream — for true event-driven updates, MTConnect's /sample with a sequence number (or SHDR over a socket) is the real mechanism; polling /current is the simplest correct starting point, not the final architecture.
  • Device and data-item names are agent-specificPartCount and Availability are common conventions, not guaranteed; browse /probe on your target agent to see what it actually exposes.
  • This script trusts the network call with a 10s timeout and otherwise does no retry logic — production pollers should handle a temporarily unreachable agent without dying.

Goes deeper

Want this adapted to your shop — or built into a real tool?

Samples are the free 80%. The last 20% is the part I do for a living.

Get in touch
Home
Blog
Tools
Code
Email
LinkedIn
Résumé