Code Library
Machine DataPythonWindows · Python 3.8+ · FANUC Fwlib DLLsMIT licenseAdvanced

FANUC FOCAS: read program number and run status (Python)

Before a piece count or a cycle-time number means anything, a dashboard needs the simplest fact of all: what program is loaded, and is the machine actually cutting. This script polls cnc_rdprgnum and cnc_statinfo once a second — two of the flattest, simplest structs in FOCAS, and the right second call to learn after the feed/spindle logger.

Before you run it

  • FANUC's FOCAS library files (Fwlib32.dll and its companion DLLs) in the script's folder
  • Python whose bitness matches the DLL (32-bit Python for Fwlib32.dll, 64-bit for Fwlib64.dll)
  • FOCAS/Ethernet or Data Server option on the control, port 8193 reachable

The code

GitHub
"""Read the running program number and machine run status from a FANUC
control over FOCAS/Ethernet, once a second.

Usage:  python focas_status.py 192.168.0.10
"""

import ctypes
import sys
import time

PORT = 8193
TIMEOUT_S = 10

RUN_STATUS = {0: "***", 1: "STOP", 2: "HOLD", 3: "STRT", 4: "MSTR"}


class OdbPro(ctypes.Structure):
    """fwlib32.h: ODBPRO - current main program number + subprogram/data number."""
    _fields_ = [("o_num", ctypes.c_long), ("mdata", ctypes.c_long)]


class OdbSt(ctypes.Structure):
    """fwlib32.h: ODBST - machine run status (cnc_statinfo)."""
    _fields_ = [
        ("dummy", ctypes.c_short),
        ("tmmode", ctypes.c_short),
        ("run", ctypes.c_short),     # 0=***, 1=STOP, 2=HOLD, 3=STRT, 4=MSTR
        ("motion", ctypes.c_short),
        ("mstb", ctypes.c_short),
        ("estb", ctypes.c_short),
        ("alarm", ctypes.c_short),
        ("edit", ctypes.c_short),
    ]


def main():
    ip = sys.argv[1] if len(sys.argv) > 1 else "192.168.0.10"
    fwlib = ctypes.WinDLL("Fwlib32.dll")

    handle = ctypes.c_ushort()
    ret = fwlib.cnc_allclibhndl3(ip.encode(), PORT, TIMEOUT_S, ctypes.byref(handle))
    if ret != 0:
        sys.exit(f"Connect failed, FOCAS code {ret}.")

    print(f"Connected to {ip}. Ctrl+C to stop.")
    try:
        while True:
            prog = OdbPro()
            fwlib.cnc_rdprgnum(handle, ctypes.byref(prog))

            status = OdbSt()
            fwlib.cnc_statinfo(handle, ctypes.byref(status))

            run_state = RUN_STATUS.get(status.run, f"?({status.run})")
            print(f"O{prog.o_num}  {run_state}  alarm={bool(status.alarm)}")
            time.sleep(1)
    except KeyboardInterrupt:
        pass
    finally:
        fwlib.cnc_freelibhndl(handle)
        print("\nHandle freed.")


if __name__ == "__main__":
    main()

What you get

$ python focas_status.py 192.168.0.10
Connected to 192.168.0.10. Ctrl+C to stop.
O1234  STRT  alarm=False
O1234  STRT  alarm=False
O1234  HOLD  alarm=False
^C
Handle freed.

How it works

  • cnc_rdprgnum and cnc_statinfo are two of the simplest FOCAS calls — small, flat structs, no axis arrays — which is exactly why they're the right second call to learn after cnc_rdspeed.
  • status.run is the number that answers "is the spindle actually cutting" better than anything else on the control: 3 (STRT) means running, 2 (HOLD) means feed-held, 1 (STOP) means idle — that's the raw signal behind every OEE availability calculation.
  • Polling both calls once a second, in the same loop, is enough for a shop-floor dashboard; it is not fast enough to catch a feed-hold that lasts half a second.
  • A true parts-counter reads a piece-count parameter via cnc_rdparam, which uses a more involved union-typed struct that varies by control generation — worth the jump once this simpler pair is working. Check your FANUC parameter manual (often in the 6700–6714 range) for your control's exact parameter number.

Gotchas & honest limits

  • Run-state codes (04) follow the common 0i/30i convention; some control generations add extra states — cross-check status.run against your control's FOCAS reference before wiring it into anything that pages someone.
  • Bitness and companion DLLs: same failure mode as the feed/spindle logger — 64-bit Python can't load Fwlib32.dll.
  • alarm here is a coarse flag, not the alarm text — pair this with the dedicated alarm-message reader when you need to know what's wrong, not just that something is.
  • One-second polling is a dashboard cadence, not a control-loop cadence — don't build safety logic on top of it.

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é