Code Library
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

GitHub
"""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 — parts looks like a number but needs an explicit int() before you do math with it, and last_cycle is a HH: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 with clean 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.

Get in touch
Home
Blog
Tools
Code
Contact
LinkedIn
Request Résumé