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
"""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 nestsDeviceStream→ComponentStream→Events/Samplesseveral 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 defaultxmlns.- One
urllib.requestcall and the standard library's ownxml.etree.ElementTree— no dependencies, which is the entire point of MTConnect's HTTP/XML design over a vendor SDK. - The
Agentdevice 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.7here,1.3/1.5on older agents) —local_name()sidesteps that by ignoring the URI entirely rather than hardcoding a version. /currentis a snapshot, not a stream — for true event-driven updates, MTConnect's/samplewith a sequence number (or SHDR over a socket) is the real mechanism; polling/currentis the simplest correct starting point, not the final architecture.- Device and data-item names are agent-specific —
PartCountandAvailabilityare common conventions, not guaranteed; browse/probeon 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.