Code Library
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.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 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_alarm2 is 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's num argument 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 widen ALM_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.

Get in touch
Home
Blog
Tools
Code
Email
LinkedIn
Résumé