Machine DataPythonWindows · Python 3.8+ · FANUC Fwlib DLLsMIT license
FANUC FOCAS: log feedrate & spindle speed to CSV (Python)
Every FOCAS tutorial shows the three-line pattern; almost none ship the part that actually stops people — the ctypes struct definitions. This is a complete, runnable logger: it connects to the control over Ethernet, reads actual feedrate and spindle speed once a second with cnc_rdspeed, and appends them to a timestamped CSV you can chart in Excel. Structs included, error codes explained, handle freed on Ctrl+C like a good citizen.
Before you run it
- FANUC's FOCAS library files (
Fwlib32.dlland its companion DLLs) in the script's folder — they come from FANUC / your machine tool builder - 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
"""Log feedrate + spindle speed from a FANUC control to CSV, once a second.
Usage: python focas_logger.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 csv
import ctypes
import datetime as dt
import sys
import time
PORT = 8193
TIMEOUT_S = 10
class SpeedElm(ctypes.Structure):
"""One element of ODBSPEED (fwlib32.h: SPEEDELM)."""
_fields_ = [
("data", ctypes.c_long), # value as an integer
("dec", ctypes.c_short), # decimal places in 'data'
("unit", ctypes.c_short),
("disp", ctypes.c_short),
("name", ctypes.c_char),
("suff", ctypes.c_char),
]
class OdbSpeed(ctypes.Structure):
"""fwlib32.h: ODBSPEED - actual feed (actf) + actual spindle (acts)."""
_fields_ = [("actf", SpeedElm), ("acts", SpeedElm)]
def scaled(elm):
"""FOCAS returns integers plus a decimal-place count - recombine them."""
return elm.data / (10 ** elm.dec)
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}. Common ones: "
"-16 socket error (IP/port), -8 handle limit, "
"7 EW_FUNC / option missing on the control."
)
out = f"machine_{ip.replace('.', '-')}.csv"
print(f"Connected to {ip}. Logging to {out} - Ctrl+C to stop.")
try:
with open(out, "a", newline="") as fh:
writer = csv.writer(fh)
if fh.tell() == 0:
writer.writerow(["timestamp", "feed", "spindle_rpm"])
while True:
speed = OdbSpeed()
ret = fwlib.cnc_rdspeed(handle, -1, ctypes.byref(speed))
if ret == 0:
writer.writerow([
dt.datetime.now().isoformat(timespec="seconds"),
scaled(speed.actf),
scaled(speed.acts),
])
fh.flush()
else:
print(f"read failed, FOCAS code {ret}")
time.sleep(1)
except KeyboardInterrupt:
pass
finally:
fwlib.cnc_freelibhndl(handle)
print("\nHandle freed. CSV is ready to chart.")
if __name__ == "__main__":
main()How it works
SpeedElm/OdbSpeedmirrorSPEEDELM/ODBSPEEDfromfwlib32.h— this is the plumbing every tutorial hand-waves.ctypes.c_longis 32-bit on Windows, matching FOCAS'slong.- FOCAS returns values as an integer plus a decimal-place count;
scaled()recombines them (data=1234, dec=1→ 123.4). Skipping this is why people see feeds ten times too high. cnc_allclibhndl3(ip, 8193, timeout, &handle)opens the Ethernet handle; every later call takes it, andcnc_freelibhndlin thefinallyblock releases it even on Ctrl+C — leaked handles eventually exhaust the control's connection slots.cnc_rdspeed(handle, -1, &speed)with-1asks for both feed and spindle in one call.- The CSV opens in append mode and only writes the header on an empty file, so restarting the logger extends the same day's data.
Gotchas & honest limits
- Bitness is the #1 failure: 64-bit Python cannot load
Fwlib32.dll(WinError 193). Either install 32-bit Python or getFwlib64.dllfrom FANUC. Fwlib32.dllneeds its companion DLLs (fwlibe1.dlletc.) in the same folder — copying just one file gives a load error that looks like a missing-DLL riddle.- A non-zero return is usually the machine talking, not a bug: no FOCAS option, wrong port, or a firewall. Code 7 (
EW_FUNC) almost always means the option isn't enabled. - Feed units follow the control's display (mm/min vs inch); the CSV header says
feed, notfeed_mm_min, on purpose. - One-second polling is fine for dashboards and utilization; it is not a vibration or load-spike instrument.
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.