Every machine-monitoring product on the market — the ones that charge you a monthly fee per machine — is, at its core, reading a handful of numbers off your control: is it running, what's the feedrate, what's the spindle load, which program is active, is there an alarm. On a FANUC control, they read those numbers through FOCAS. And FOCAS is not a secret. You can call the exact same API yourself.
FOCAS (FANUC Open CNC API Specifications) is FANUC's official library for talking to a control from a PC. It ships as a set of .dll files, and every function you'll ever need — read a macro variable, read the active program, read an alarm — is one call away once you're connected.
You need the library, and the machine needs the option
The Windows library (Fwlib32.dll) comes from FANUC, and the control usually needs the Data Server or FOCAS/Ethernet option enabled to accept a connection. That's the one gate: no option, no data. Check before you promise anyone a dashboard.
What you actually need
- A FANUC control on the network — 0i, 30i/31i/32i, and most modern models, with FOCAS/Ethernet enabled.
- The Fwlib library — FANUC's
Fwlib32.dll(Windows). The open-source fwlib project helps with headers and compiling. - A language binding — call the DLL from Python with
ctypes, or use a wrapper so you don't hand-roll every struct.
The three-line pattern every FOCAS program follows
Strip away the details and every FOCAS program is the same shape: open a handle to the control, read what you want, free the handle. Here's that skeleton in Python calling the DLL directly with ctypes:
import ctypes
fwlib = ctypes.windll.Fwlib32 # FANUC's licensed DLL
# 1. connect: IP, port 8193, 10s timeout -> a handle
handle = ctypes.c_ushort()
ret = fwlib.cnc_allclibhndl3(b'192.168.0.10', 8193, 10, ctypes.byref(handle))
if ret != 0:
raise RuntimeError(f'connect failed, code {ret}')
# 2. read: actual feedrate + spindle speed into an ODBSPEED struct
# (the struct layout comes from fwlib.h / your wrapper)
speed = SpeedData()
fwlib.cnc_rdspeed(handle, -1, ctypes.byref(speed))
print('feed:', speed.actf.data, ' rpm:', speed.acts.data)
# 3. always free the handle
fwlib.cnc_freelibhndl(handle)cnc_allclibhndl3 opens an Ethernet handle to the control. Every subsequent call takes that handle. The annoying part isn't the calls — it's the C structs each function writes into (ODBSPEED, ODBACT, ODBALMMSG…). That struct plumbing is exactly why wrappers exist.
The EW_ trap
When a call returns a non-zero code like EW_FUNC or EW_OPTION, it usually doesn't mean your code is wrong — it means the machine doesn't have that function's option enabled. Half of learning FOCAS is learning to read those return codes instead of assuming a bug.
The numbers most people actually want
- Feedrate & spindle speed —
cnc_rdspeed(orcnc_actf/cnc_acts). - Spindle & servo load —
cnc_rdspload/cnc_rdsvmeter, the basis of tool-wear and health signals. - Active program & block —
cnc_rdexecprog/cnc_rdprgnum, so you know what is running. - Run state —
cnc_statinfotells you running / hold / stop. - Alarms —
cnc_rdalmmsgreturns the alarm text, the single most useful thing to log. - Macro variables —
cnc_rdmacroreads probing results, part counts, and tool data (its own full post).
Python, or C#?
Three roads, and they're all valid. `ctypes` + Fwlib in Python is the quickest to prototype. [pyfanuc](https://github.com/diohpix/pyfanuc) reverse-engineers the protocol so you don't need the FANUC DLL at all — which means it runs on Linux, a real advantage for a Raspberry Pi collector. And the [Ladder99 fanuc-driver](https://github.com/Ladder99/fanuc-driver) (C#/.NET) is a configurable collector that post-processes straight to MQTT if you'd rather not write the loop yourself.
The gap between "we should monitor our machines" and "we are monitoring our machines" is about forty lines of code — most people just don't know the forty lines exist.
Once you can read these numbers on a timer, everything downstream opens up: a live dashboard, an OEE calculation, tool-wear trending, even an AI agent that answers questions about your machines. If you'd rather have someone build the collector than fight struct definitions, get in touch — this is exactly the kind of thing I do.


