Machine DataPythonPython 3.8+ · standard library onlyMIT licenseStarter
Poll a Haas control for status over Ethernet (Q-commands)
Haas doesn't ship an SDK, and for basic status it doesn't need one: Setting 143 turns on a plain, documented TCP protocol called Q-commands, and querying it is nothing more than sending ?Q100\r\n and reading the reply. This script polls five of the most useful codes — serial number, mode, active tool, last cycle time, and parts count — and prints one status line per cycle. No pip install, no vendor account, just socket. Tested here against a small mock TCP server that speaks the real response format before ever touching a control.
Before you run it
- Python 3.8+ (standard library only — no installs)
- Setting 143 (Machine Data Collect port) enabled on the control — the default is
5051 - The control reachable on the network from wherever this script runs, with that port open through any firewall in between
The code
"""Poll a Haas control for status over Ethernet (Q-commands).
Usage: python haas_q_poller.py 192.168.1.50
Requires Setting 143 (Machine Data Collect port) enabled - default 5051.
"""
import socket
import sys
import time
PORT = 5051
POLL_SECONDS = 2
QUERIES = {
"Q100": "serial",
"Q104": "mode",
"Q201": "tool",
"Q303": "last_cycle",
"Q402": "parts",
}
def query(sock, code):
sock.sendall(f"?{code}\r\n".encode("ascii"))
data = sock.recv(256).decode("ascii", errors="replace")
# Responses are prefixed with '>' and end CR/LF - strip both.
return data.strip().lstrip(">").strip()
def poll_once(host):
with socket.create_connection((host, PORT), timeout=5) as sock:
return {label: query(sock, code) for code, label in QUERIES.items()}
def main():
if len(sys.argv) < 2:
sys.exit("Usage: python haas_q_poller.py <machine-ip>")
host = sys.argv[1]
print(f"Polling {host}:{PORT} every {POLL_SECONDS}s - Ctrl+C to stop.")
try:
while True:
status = poll_once(host)
print(
f"serial={status['serial']:<10} mode={status['mode']:<8} "
f"tool={status['tool']:<4} last_cycle={status['last_cycle']:<12} "
f"parts={status['parts']}"
)
time.sleep(POLL_SECONDS)
except KeyboardInterrupt:
pass
except OSError as e:
sys.exit(f"Could not reach {host}:{PORT} - {e}")
if __name__ == "__main__":
main()What you get
$ python haas_q_poller.py 192.168.1.50
Polling 192.168.1.50:5051 every 2s - Ctrl+C to stop.
serial=1234567 mode=MEM tool=4 last_cycle=00000:00:13 parts=506
serial=1234567 mode=MEM tool=4 last_cycle=00000:00:13 parts=506
serial=1234567 mode=MEM tool=4 last_cycle=00000:00:13 parts=506
^C
How it works
- Q-commands are request/response, not a stream: send
?Q100\r\n, get one line back starting with>, ready for the next query on the same connection —poll_once()opens one socket and asks all five questions before closing it. - Setting 143 is the whole install.** No DLL, no bitness matching, no licensed library — the control just listens on a TCP port and answers text queries, which is why this script is standard-library-only.
- The five codes here are a starting set, not the full list — Q200/Q201 (tool changes/current tool), Q300/Q301 (power-on/motion time), and Q402/Q403 (M30 counters #1/#2) are the other commonly useful ones; add them the same way.
- The response parsing (
lstrip(">")) is deliberately forgiving — different firmware revisions are inconsistent about trailing whitespace and line endings, so the code strips first and parses the cleaned string rather than assuming an exact byte layout.
Gotchas & honest limits
- Ports 8082 and 9090–9999 are reserved by the control for other services — don't repoint Setting 143 at them.
- Q-command values come back as strings, always —
partslooks like a number but needs an explicitint()before you do math with it, andlast_cycleis aHH:HH:SS-style duration string, not seconds. - This script opens a fresh connection per poll cycle, which is simple and robust but not the fastest option — a long-lived connection that sends queries back-to-back works too, at the cost of handling a dropped connection yourself instead of letting
withclean it up each time. - Classic (pre-NGC) controls and NGC controls don't support an identical Q-command set — check your control's operator manual for the exact list your firmware exposes before assuming a code from this list is present.
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.