Machine DataPythonWindows · Python 3.8+ · FANUC Fwlib DLLsMIT licenseAdvanced
FANUC FOCAS: read active alarms (Python)
"Is the machine down, and why" is the first question every monitoring dashboard has to answer, and it's the one FOCAS tutorials skip past fastest. This script checks cnc_alarm2 first — a cheap bitmask read that's zero the moment nothing is wrong — and only calls the heavier cnc_rdalmmsg2 to fetch actual alarm numbers and text when there's something to report.
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 active alarms from a FANUC control over FOCAS/Ethernet.
Usage: python focas_alarms.py 192.168.0.10
Requires Fwlib32.dll (+ companion DLLs) next to this script, and Python
whose bitness matches the DLL. The control needs the FOCAS/Ethernet
option; the standard port is 8193.
"""
import ctypes
import sys
PORT = 8193
TIMEOUT_S = 10
MAX_ALARMS = 10
ALM_MSG_LEN = 32 # 0i/30i default; widen if messages look truncated
class OdbAlmMsg(ctypes.Structure):
"""fwlib32.h: ODBALMMSG - one active alarm's number, type, and message."""
_fields_ = [
("alm_no", ctypes.c_short),
("type", ctypes.c_short),
("axis", ctypes.c_short),
("dummy", ctypes.c_short),
("alm_msg", ctypes.c_char * ALM_MSG_LEN),
]
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}.")
try:
status = ctypes.c_short()
ret = fwlib.cnc_alarm2(handle, ctypes.byref(status))
if ret != 0:
sys.exit(f"cnc_alarm2 failed, FOCAS code {ret}.")
if status.value == 0:
print("No active alarms.")
return
num = ctypes.c_short(MAX_ALARMS) # in: max to fetch, out: actual count
alarms = (OdbAlmMsg * MAX_ALARMS)()
ret = fwlib.cnc_rdalmmsg2(handle, ctypes.c_short(-1), ctypes.byref(num),
alarms)
if ret != 0:
sys.exit(f"cnc_rdalmmsg2 failed, FOCAS code {ret}.")
print(f"{num.value} active alarm(s):")
for i in range(num.value):
a = alarms[i]
msg = a.alm_msg.decode(errors="replace").rstrip("\x00")
print(f" #{a.alm_no:>4} type={a.type} {msg}")
finally:
fwlib.cnc_freelibhndl(handle)
if __name__ == "__main__":
main()What you get
$ python focas_alarms.py 192.168.0.10
2 active alarm(s):
# 300 type=0 SERVO ALARM: SV0300 SYSTEM ERROR
# 411 type=2 SPINDLE ALARM: EXCESS SPEED ERROR
How it works
cnc_alarm2is a cheap first check: it returns a nonzero status the moment anything is in alarm, so a healthy machine short-circuits before the more expensive message fetch runs.cnc_rdalmmsg2'snumargument is in/out: you pass the max alarms you're willing to fetch, and FOCAS overwrites it with how many it actually found — the same in/out convention FOCAS uses everywhere, once you've seen it once.(OdbAlmMsg * MAX_ALARMS)()allocates a fixed-size array of the struct via ctypes' array syntax, not a Python list, because FOCAS writes directly into this memory.- Alarm messages come back as fixed-width, null-padded byte strings —
.decode()then.rstrip("\x00")is the same cleanup every FOCAS text field needs, including the feed/spindle logger's.
Gotchas & honest limits
ODBALMMSG's message buffer length varies by control generation and language setting — this uses 32 bytes (0i/30i default); if messages look truncated, check your control's documented buffer size and widenALM_MSG_LEN.- This reads what's active right now — it's a monitor, not a historian. FOCAS has separate alarm-history functions this script doesn't cover.
- Bitness is still the #1 failure, same as the feed/spindle logger: 64-bit Python cannot load
Fwlib32.dll. - A non-zero return from either call is usually the control talking, not a bug — the same error codes as the feed/spindle logger (missing option, wrong port, handle limit).
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.