A pilot partner asked us last month whether Sancho could connect to their FANUC 0i-F CNC machine controllers, not just the robot arms. They had a cell where a FANUC M-710iC robot loaded parts into a FANUC 30i-B CNC machining center. The robot was already connected through our existing FANUC robot connector. The CNC was the gap.
FANUC publishes a library for CNC access called FOCAS2 (FANUC Open CNC API Specifications, version 2). It is a Windows DLL-based API that has been around since the early 2000s, and if you have done any serious FANUC CNC integration work, you have either used it or you have been handed someone else's code that uses it. We had the library, we had a test CNC in the lab, and we had two days before the pilot site visit. Here is exactly how it went.
The Connector Architecture We Were Building Against
Sancho connectors follow a defined interface: they implement a connect lifecycle (init, poll, disconnect), expose a typed data stream (machine state, program number, alarms, axis positions), and register with the connector registry so the orchestration engine can reference them in cell maps. A new connector needs to provide those outputs in a standard envelope. What happens inside the connector to get that data is the vendor-specific part.
For the FANUC CNC, the FOCAS2 library provides functions like cnc_sysinfo for controller identification, cnc_rdproginfo for active program data, cnc_rdopnlsgnl for operator panel signals (which includes the all-important AUTO/MDI/JOG mode indicator and the cycle-start signal), and a family of cnc_rd* functions for axis feedback. Our target output was: current program number, machine mode, cycle state (idle/running/paused), active alarms, and feed rate override percentage.
Gotcha 1: The Handle Is Not Thread-Safe
FOCAS2 uses a handle model. You call cnc_allclibhndl3 with the controller IP, port, and a timeout value, and it returns a handle you use for all subsequent calls. Straightforward enough. What the documentation does not emphasize clearly is that this handle is per-connection and not thread-safe. If you try to call two FOCAS2 functions simultaneously from different threads using the same handle, you get unpredictable results: sometimes an error return code, sometimes garbage data, occasionally a DLL access violation.
We discovered this when our connector tried to run the alarm polling and the axis position polling on separate threads to keep latency down. The alarm poller worked fine in isolation. So did the axis poller. Run both concurrently against the same handle and the data started corrupting. The fix was straightforward: serialize all FOCAS2 calls through a single-threaded poll loop with a mutex guard, and queue the results. For the polling intervals we needed (250ms for state, 1s for alarms), this was not a problem. But it would become a problem if someone tried to use this connector for high-frequency axis feedback.
Gotcha 2: The Return Codes Are Not Always What They Say
FOCAS2 functions return an integer error code, with EW_OK (0) meaning success. There is a published list of error codes in the documentation. In practice, we found that some CNC model/firmware combinations return EW_FUNC (-3, function not supported) for calls that the documentation lists as supported on that controller series, while still returning valid data in the output buffer. The right defensive practice turned out to be: check for EW_OK, but also check whether the output buffer contains a plausible value before discarding the result on a non-zero return code.
We hit this specifically with cnc_rdspload, which reads spindle load. The 0i-F returned EW_FUNC, but the spindle load value was valid and consistent. After confirming this against a second 0i-F controller in the lab, we added a whitelist of (function, return code, model series) triples where we accept the data despite a non-OK return code. Not an elegant solution, but it matches what the FOCAS2 veteran community recommends.
Gotcha 3: The Connection Timeout Is Global to the DLL
The third issue cost us an afternoon. When you call cnc_allclibhndl3, you pass a timeout value in seconds. If the controller is unreachable, the call blocks for that timeout before returning an error. What we did not realize initially is that this blocking is not thread-local. On Windows, the FOCAS2 DLL uses a single connection thread internally, and a blocked connection attempt can delay other cnc_allclibhndl3 calls from other handles in the same process.
In a cell map with multiple CNC machines, a single unreachable controller could stall the entire connector initialization sequence for the configured timeout duration, multiplied by however many retries the caller attempted. We were using a 10-second timeout and 3 retries, which could produce up to 30 seconds of stall on one machine while other machines sat idle. The fix: reduce the connection timeout to 3 seconds for the initial probe, run connection attempts sequentially with explicit backoff rather than in parallel, and promote any persistent connection failure to an alarm after two failed cycles rather than blocking indefinitely.
The Working State After 48 Hours
By the end of day two, the connector was stable on both the 0i-F and 30i-B test controllers. We were successfully surfacing: active program number and subroutine path, machine mode and cycle state, up to 32 active alarms with alarm text, feed rate and rapid traverse override percentages, and X/Y/Z axis absolute positions at 250ms resolution. The connector registered cleanly in the v0.3 registry and appeared in cell maps exactly like the existing robot connectors.
What we did not achieve in 48 hours: tool life data (the FOCAS2 tool management functions have enough model-specific variation that we want another day to stabilize them) and part program upload/download, which requires a different licensing discussion with FANUC for some controller families.
The time distribution was roughly: half a day on the basic connect/poll/disconnect lifecycle, half a day finding and fixing the three gotchas above, and one day on the alarm mapping and state machine logic that translates raw FOCAS2 signals into Sancho's normalized machine state enum. Nothing in that breakdown was surprising in retrospect, but the FOCAS2 documentation does not warn you about the thread-safety or connection blocking issues. That is what the afternoon cost.
What This Adds for Mixed-Vendor Cells
CNC machine tools and robot arms have always lived in different integration worlds, even when they come from the same vendor. FANUC's robot ecosystem (the R-30iB controller, the standard robot interface protocol) and FANUC's CNC ecosystem (FOCAS2, the 0i/30i/31i/32i series controllers) use completely different access paths. Most robot integration platforms only cover the robot side. Adding CNC machine tool connectivity means Sancho can now express, in a single cell map, the full production cycle: the robot loading the CNC, the CNC running the program, the robot unloading the finished part, and the PLC coordinating the safety zones between all of them. That is a closed loop that previously required custom glue code at every step.
Read next: Sancho 0.3 Vendor Connector Registry: What It Is and Why It Changes Deployment