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.dlland its companion DLLs) in the script's folder - Python whose bitness matches the DLL (32-bit Python for
Fwlib32.dll, 64-bit forFwlib64.dll) - FOCAS/Ethernet or Data Server option on the control, port 8193 reachable
The code
"""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_rdprgnumandcnc_statinfoare two of the simplest FOCAS calls — small, flat structs, no axis arrays — which is exactly why they're the right second call to learn aftercnc_rdspeed.status.runis 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 (
0–4) follow the common 0i/30i convention; some control generations add extra states — cross-checkstatus.runagainst 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. alarmhere 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.