简介:面向Fanuc数控系统二次开发工程师与自动化集成人员,这份V4.5版Focas2通讯接口文件专门解决Linux环境下PC与CNC设备双向交互的难题,适用于数据采集、程序管理和远程监控场景。包内共2000个文件,以XML配置与HTM说明文档为主,配合DLL动态库、H头文件、EXE可执行程序及TXT使用说明,涵盖了从API调用、驱动注册到示例运行所需的完整文件链,整体压缩包约19.94MB。Focas2协议通过标准接口实现机床状态上传、加工程序下发、报警信息读取与远程维护,可帮助团队搭建定制化生产管理系统。资源已吸引191人学习,适合需要直接调用Focas2 API或参考Linux通信方案的技术人员,能大幅缩短环境配置与联调周期,并为后续功能扩展打下基础。
1. FOCAS2 不是裸 Socket:Linux 接入 Fanuc CNC 先过接口文件这一关
早年在车间调试一台 Fanuc 0i 系列,上位机是 Ubuntu,想着既然是以太网通讯,直接建个 TCP 连接读数据就行。结果对端返回的二进制流既不是协议头也不像 ASCII,完全没法解析。后来才明白,Fanuc 把 CNC 与 PC 之间的数据交换封装成了 FOCAS2,不是裸 Socket 协议。这个 2015 年 V4.5 时代的通讯接口文件包,正是打开问题的钥匙:里面有 FOCAS2 的头文件和库文件,Windows 和 Linux 共用一套 API 定义,但 Linux 下能不能直接编译运行,还要看动态库和编译参数。对做 MES 数据采集、设备远程诊断、刀具寿命管理的工程师来说,接下来我会直接拆文件、讲编译链路和运行排错,而不是捡到一个 DLL 就试着加载。
2. 拆开 Focas2 通讯接口包:库文件、头文件与 Linux 编译映射
这个 zip 解压后第一眼看上去很“脏”:FWLIB32.CSS、Ncboot32.doc、Fwlib32.h、Fwlib64.h,没有一个标准的 Linux.so文件。这不代表包无用,反而说明该资源是“API 定义 + 库文件”的组合体。在真正编译程序之前,需要先处理编码、确认真实文件类型、判断 32/64 位边界。
2.1 解压、辨类型:FWLIB32.CSS 不等于 .so
压缩包文件名里带中文逗号,在 Linux 下解压经常出现乱码。常见做法是用unzip -O GBK指定字符集,尤其是 2015 年之前的 zip 归档,压缩时大多按 GBK 存文件名:
unzip -O GBK "Fanuc 2015 V4,5 Focas2__CNC同PC通讯接口文件__支持Linux.zip"这个命令里的-O是 unzip 的字符集选项,和 Linux 系统当前 locale 无关,专门用于解包时把 GBK 文件名转成 UTF-8。如果发行版自带 unzip 不支持-O,改用python3 -m zipfile -e虽然能解出来,但文件名仍然可能是乱码,配合convmv转一次更省事。
解压后第一个要怀疑的是FWLIB32.CSS。正常情况下 FOCAS2 的库文件后缀应该是.dll或.so,.CSS更像是下载站防审查时改掉的扩展名,也可能被杀毒软件重命名。不要靠后缀猜,直接让file命令识别真实格式:
file FWLIB32.CSS Fwlib32.h Fwlib64.hfile会读取文件头,不依赖扩展名。如果FWLIB32.CSS显示为PE32 executable (DLL),它其实仍是 Windows 下的 FOCAS2 库;如果显示ELF 32-bit LSB shared object,才是 Linux 可以直接加载的共享库。第二步再看文件名里的32或64,这决定后续编译目标是-m32还是默认 64 位。
结合包内文件可以得到一张选型表:
| 文件 | file 命令可能输出的类型 | 实际用途 |
|---|---|---|
| FWLIB32.CSS | PE32 DLL 或 ELF32 shared object | 32 位 FOCAS2 库,后缀不重要 |
| Fwlib32.h | C source, ASCII text | 32 位 API 函数声明 |
| Fwlib64.h | C source, ASCII text | 64 位 API 函数声明 |
| Ncboot32.doc | Composite Document 或 HTML | FOCAS 引导初始化说明文档 |
| Ncboot32j.doc | Composite Document 或 HTML | 日文版引导文档 |
这里要特别提醒:不能因为Fwlib64.h存在就默认有libFWLIB64.so。很多资源包只是把两套头文件都放进来,真正的 64 位库往往要单独向 Fanuc 授权获取。拿到一个资源先按当前 CNC 型号和系统位数,只选一套头文件去对库,不要混用。
2.2 Fwlib32.h 与 Fwlib64.h:API 兼容性的关键
FOCAS2 的核心不是协议报文,而是一组以句柄为中心的 C 函数。Linux 下与 Windows 下看到的是同一份Fwlib32.h,所以上层调用逻辑几乎一样,区别只在于底层连接由libFWLIB32.so实现,Windows 下则对应FWLIB32.dll。
头文件里定义了所有导出函数和返回码宏。我用过的大部分 Fanuc 系统,连接入口都是cnc_allclibhndl3,通过 IP、端口、超时时间拿到一个unsigned short句柄,后续读写坐标、宏变量、报警都靠这个句柄。另一个高频函数是cnc_rdmacro,用于读取刀具补偿、工件坐标等工艺参数。这些 API 的名字在多年版本里保持稳定,但参数顺序和结构体成员有变化,所以头文件一定要和现场库配套,不能跨版本混用。
常见做法是先把头文件放进系统目录,再链接-lFWLIB32:
sudo mkdir -p /usr/local/include/fanuc sudo cp Fwlib32.h Fwlib64.h /usr/local/include/fanuc/ sudo cp libFWLIB32.so /usr/local/lib/ sudo ldconfigldconfig是 Linux 动态链接器维护缓存的关键命令,不执行它,很多程序运行时即使库在/usr/local/lib也找不到。如果系统是 64 位但库是 32 位 ELF,还要额外处理 32 位库搜索路径,这部分放到第 3 章展开。
2.3 第一个探针程序:先拿到 handle,再谈采集数据
很多开发者第一个程序就想读坐标,我一般会先写一个“只连接、不读数据”的探针。下面的代码在 Linux 和 Windows 下编译逻辑差异很小,区别只在链接库名:
#include <stdio.h> #include <stdlib.h> #include "Fwlib32.h" int main(int argc, char *argv[]) { unsigned short handle = 0; short ret; if (argc < 2) { fprintf(stderr, "usage: %s <cnc_ip>\n", argv[0]); return 2; } /* 第三个参数是超时毫秒,10000 表示 10 秒 */ ret = cnc_allclibhndl3(argv[1], 8193, 10000, &handle); if (ret != EW_OK) { fprintf(stderr, "cnc_allclibhndl3 failed, ret=%d\n", ret); return 1; } fprintf(stdout, "connected, handle=%u\n", handle); /* 断开连接,释放句柄 */ cnc_freelibhndl(handle); return 0; }cnc_allclibhndl3的第一个参数是 CNC 的 IP 地址,第二个参数是端口,Fanuc 的 FOCAS2 默认端口一般固定为 8193。第三个参数单位是毫秒,不是秒,写 10000 表示 10 秒。第四个参数返回句柄,后续所有 FOCAS2 请求都要带着它。EW_OK是头文件里定义的“成功”返回宏,实际值通常为 0,但不要直接和 0 比较,避免不同版本调整定义。
编译命令要注意库名大小写:
gcc -o focas_probe focas_probe.c -I/usr/local/include/fanuc -L/usr/local/lib -lFWLIB32-I指定头文件路径,-L指定库文件路径,-lFWLIB32让链接器去找libFWLIB32.so或libFWLIB32.a。如果资源里的库实际叫libfwlib32.so,把命令里的FWLIB32改成小写即可,具体以ls /usr/local/lib看到的结果为准。
3. Linux 下编译与运行:依赖路径、动态库加载和首测
代码写对只是第一步,Linux 下 FOCAS2 项目一半的坑在链接和运行环境。编译通过不代表能跑起来,因为gcc -L只对链接阶段有效,程序启动后由动态链接器负责找库,这一章就是解决从error while loading shared libraries到connected的完整链路。
3.1 动态链接器找不到库?用 ldd 和 ldconfig 收口
在 Linux 上跑 FOCAS2 程序,最经典的问题是编译成功、运行时报错:
error while loading shared libraries: libFWLIB32.so: cannot open shared object file: No such file or directory这时不要改代码,先用ldd看程序依赖了哪些库以及它们的解析路径:
ldd ./focas_probe如果输出里有libFWLIB32.so => not found,说明动态链接器不知道这个库。临时测试可以用环境变量强制指定:
LD_LIBRARY_PATH=/usr/local/lib ./focas_probe 192.168.1.10但生产采集服务不建议依赖LD_LIBRARY_PATH,因为 systemd 服务、重启后的环境容易丢。最稳的写法是把库路径写入系统配置:
echo "/usr/local/lib" | sudo tee /etc/ld.so.conf.d/fanuc-focas.conf sudo ldconfig ldconfig -p | grep FWLIB这里的ldconfig会扫描/etc/ld.so.conf.d/下的所有.conf文件,把目录加入动态库缓存;ldconfig -p | grep只是验证缓存里是否已经出现 FWLIB32。注意同一个目录下不要同时放 32 位和 64 位同名库,否则ldconfig后索引混乱,程序可能加载到错误位宽的库。
现场常见错误可以整理成一张排查表:
| 错误现象 | 可能原因 | 处理方式 |
|---|---|---|
| libFWLIB32.so not found | 库路径不在动态链接器缓存中 | 写ld.so.conf.d后ldconfig |
| skipping incompatible ... | 库是 32 位,程序编译成 64 位 | 对程序加-m32或换 64 位库 |
| undefined symbol: cnc_allclibhndl3 | 头文件版本高于库,或链接库选错 | 确认-l对应的库和头文件来自同一版本 |
| connection timeout | CNC 侧未开启 FOCAS 服务或 IP 不通 | 检查 Fanuc 以太网参数与端口 |
最终的定位方式是把strace和connect连起来看,这在 3.3 节展开。
3.2 IP、端口、防火墙:连接前的排错顺序
FOCAS2 不是“网线插上就能读”。Fanuc CNC 上需要把以太网端口开启,并配置 IP 和端口号。上位机侧先把本机 IP 固定到同一网段,再测链路:
ping -c 3 192.168.1.10 nc -vz 192.168.1.10 8193nc -vz只做 TCP 三次握手探测,不发送业务数据,适合把问题切在“网络通不通”这一层。如果 ping 通但nc -vz失败,基本可以认为 FOCAS2 服务没有运行,或者被机床侧路由拦截。工业现场很少在 CNC 前置防火墙上做限制,但虚拟机里跑采集服务时要特别注意网络模式:VMware NAT 模式下虚拟机不能直接用宿主机 IP 访问机床,改成桥接模式才能让虚拟机和机床在同一广播域。
有些机床开启了端口过滤,上位机请求会被静默丢弃。此时先用ip addr确认本机 IP,再让现场电工检查机床面板上的“内嵌以太网”设置页面,重点确认端口不是 8193 而是操作员自定义的其他值。探针程序的端口参数要支持命令行传入,避免每次改参数都重编译。
3.3 返回码打印与 strace 结合定位问题
FOCAS2 的函数返回码信息量很大,但现场调试时很多人只打印“连接失败”,没有任何编码。我习惯把返回值按无符号短整型打印成十六进制:
if (ret != EW_OK) { fprintf(stderr, "ret=0x%04X\n", (unsigned short)ret); return 1; }这样看到的是0x0000到0xFFFF之间的原始返回码,再到Fwlib32.h里搜索对应的#define,往往能直接定位到“超时”“句柄无效”“CPU 版本不支持”等具体原因。比ret=-1这种输出有用得多。
如果返回码也不能解释问题,就用strace看库加载和网络调用:
sudo apt install strace strace -f -e trace=openat,connect ./focas_probe 192.168.1.10-e trace=openat,connect只跟踪文件打开和 TCP 连接两类系统调用。日志里会显示进程是否真的打开了/usr/local/lib/libFWLIB32.so,以及connect的目标 IP 和端口。如果看到ECONNREFUSED,说明 CNC 上 8193 端口没监听;如果一直卡在connect后的in_progress,说明网络路径上有丢包,需要往交换机链路排查。
4. 数据采集实战:长连接轮询、ctypes 验证与对齐陷阱
连接建立之后,采集程序的架构决定了现场稳定性。对 MES 或刀具管理系统来说,既要保证数据刷新率,又不能让 FOCAS2 连接数把 CNC 侧服务打满。这一章从采集模型到实际编码,再到 32/64 位陷阱,一次说透。
4.1 采集模型选择:长连接轮询比事件推送更可控
FOCAS2 本质是请求-响应模型,CNC 侧不会主动把坐标推给上位机,除非你去读。所以生产环境里最常见的是采集线程长连接轮询,用一条连接周期性地批量读取数据。不建议每次采集都cnc_allclibhndl3/cnc_freelibhndl,频繁建连会让 2015 年这批 CNC 的网卡和 FOCAS 服务变得不稳定。
轮询周期要按数据类型分开。坐标和宏变量实时性要求高,报警和程序状态可以慢一点。我一般用这样一组参数:
| 采集对象 | 推荐周期 ms | 常见接口 | 备注 |
|---|---|---|---|
| 坐标/轴位置 | 200 | coords / position 相关接口 | 不要低于 50ms,会加重 CNC 负载 |
| 宏变量 | 100 | cnc_rdmacro | 适合采集刀具寿命、计件数 |
| 程序状态 | 200 | program info 相关接口 | 与当前程序名配合使用 |
| 报警信息 | 500 | alarm 相关接口 | 文本短,但编码处理要小心 |
这套节奏在多数 0i/30i 系统上运行稳定。如果现场有多台上位机同时采集同一台 CNC,尽量让一台机器做汇聚节点,再通过 OPC UA 推给上层,避免同一台 CNC 的 FOCAS2 服务被多个客户端并发打爆。
4.2 用 Python ctypes 快速验证线路和宏变量读取
在 Linux 下做原型验证时,我一般不会直接从.c文件开始,而是用 Python 的ctypes直接加载 FOCAS2 共享库,先确认 IP、端口、协议栈是否正常。下面是一段读取宏变量 #501 的骨架:
import ctypes fwlib = ctypes.CDLL("libFWLIB32.so") handle = ctypes.c_ushort(0) ret = fwlib.cnc_allclibhndl3( b"192.168.1.10", # IP 必须为 bytes,不能传 str 8193, # FOCAS2 默认端口 10000, # 超时 10 秒 ctypes.byref(handle) ) print(f"connect ret={ret}, handle={handle.value}") # 读取宏变量的函数签名请以 Fwlib32.h 为准 value = ctypes.c_short(0) ret = fwlib.cnc_rdmacro(handle, 501, ctypes.byref(value)) print(f"macro#501 ret={ret}, value={value.value}") fwlib.cnc_freelibhndl(handle)ctypes.CDLL加载的是系统动态库缓存里的libFWLIB32.so,所以第 3 章的ldconfig必须提前做对。cnc_allclibhndl3的第一个参数在 Python 里必须是bytes,写成"192.168.1.10"会在 ctypes 传参时被当成指针,轻则连接失败,重则段错误。第三参数超时单位是毫秒,很多人写10以为 10 秒,实际只有 10 毫秒。
cnc_rdmacro在不同 FOCAS2 版本里参数顺序可能有差异,有的版本需要传宏变量长度。遇到TypeError或返回码异常,不要猜,直接打开Fwlib32.h搜索cnc_rdmacro的原型,再按原型调整ctypes的参数类型。这里也体现出头文件比库更重要的原因:结构体、函数签名全在里面。
4.3 32/64 位结构体对齐与 -m32 的取舍
FOCAS2 返回的数据,有的是一段紧凑结构体。Fwlib32.h和Fwlib64.h里的差异不只是long的宽度:在 Windows 64 位下long是 4 字节,在 Linux 64 位下是 8 字节,直接把结构体按 Windows 习惯去解析 Linux 返回的缓冲区,必然错位。跨平台采集程序不要按字节偏移量手工解析,要使用与库位数匹配的头文件结构体。
但很多从 2015 年流传下来的资源包,只给了 32 位库和Fwlib32.h。在 64 位 Linux 上强行编译 64 位程序会链接失败,最直接的策略是装多库环境,用-m32编译整个采集器:
sudo apt install gcc-multilib g++-multilib gcc -m32 -o focas_probe32 focas_probe.c -I/usr/local/include/fanuc -L/usr/local/lib -lFWLIB32-m32让gcc生成 32 位代码,但 32 位 ELF 需要 32 位 C 运行时库,所以必须安装gcc-multilib。如果libFWLIB32.so本身是 32 位,而你想在 64 位程序里调用,链接器会直接报skipping incompatible,这时不要绕,直接按库的位数决定编译目标。
结构体对齐是另一个暗坑。Fanuc 头文件里有些结构体没有#pragma pack,默认对齐在不同架构下会变。我习惯在读取坐标前,先确认头文件里对应的ODB结构体定义是否自带 pack,如果源文件里用了#pragma pack(push, 8),程序侧也要保持一致,否则同一个sizeof在不同编译器下差出 8 字节,读出来的坐标错得离谱。
4.4 报警文本的 SJIS 编码处理与截断坑
读报警文本时,FOCAS2 返回的字符串经常是 Shift_JIS 编码,尤其是在日文系统参数下。直接打印会得到一片乱码,但这不代表采集失败,而是编码没转。Linux 下最直接的工具是iconv:
iconv -f SHIFT_JIS -t UTF-8 alarm_raw.txt > alarm_utf8.txt实际程序里,我更建议用系统库的iconv()函数做流式转换。报警文本返回时通常带固定长度,末尾是空格和\0混合,不能用strlen判断有效长度,否则遇到中间的 0 字节会提前截断。正确做法是拿到返回的数据长度,先memcpy到独立缓冲区,再按长度进行编码转换。
另一个容易忽略的点是 SJIS 中不是所有字节都能映射到 UTF-8,遇到特殊符号时iconv可能报EILSEQ错误中断整个采集线程。转换前把错误处理设为errors="ignore",或者在 C 里把iconv最后一个参数处理成跳过非法字节。报警文本只是用来展示,丢一两个字符不会影响设备安全,但让线程崩溃就得不偿失了。
5. strace、tcpdump 与返回码回归:FOCAS2 模块的现场验证技巧
当采集程序能在开发机上跑通,只代表接入完成了一半。现场机床参数可能被改过,库文件可能被运维覆盖,甚至 Linux 系统是裁剪版本,这类问题只有在部署现场才会暴露。最后分享三个不依赖 IDE 的验证技巧,可以直接写进部署文档,也方便在车间里应急。
5.1 用 LD_DEBUG 观察库加载顺序
ldd能显示库是否存在,但看不到动态链接器的搜索顺序。遇到多个路径下存在同名 FOCAS2 库时,设置LD_DEBUG=libs能让链接器输出整个搜索过程:
LD_DEBUG=libs ./focas_probe 192.168.1.10命令输出里会有大量find library=libFWLIB32.so的日志,以及最终“搜索路径变化”的提示。只要在日志中看到trying file=/usr/local/lib/libFWLIB32.so且后面跟着cached,就说明它走的是系统缓存,而不是某个工作目录里的临时库。这个技巧尤其适合排查:开发机正常、服务器上却加载了同一个旧版本的.so的问题。
5.2 抓包确认请求是否发出
抓包工具很多,但工业现场最给力的还是tcpdump,因为它不依赖浏览器图形界面:
sudo tcpdump -i eth0 host 192.168.1.10 and port 8193 -XX-XX同时输出链路层和 IP 数据头,方便看 TCP 标志位。正常流程是三次握手完成后,上位机发出一个几十字节的小包,CNC 快速回复同样长度的小包。如果看到大量 TCP 重传,说明网络有丢包;如果只有握手包后没有业务数据,通常程序挂在句柄初始化之后、真正读数据之前,问题不在网络而在 API 调用顺序。看到RST标志则说明主动方端口或服务不存在,优先改机床侧设置。
5.3 返回码基线回归
FOCAS2 库文件升级或者更换电路板后,返回码行为可能变化。我一般会把探针程序的所有返回码保存成基线文件,换库之后跑一次 diff:
./focas_probe 192.168.1.10 > /tmp/ret_base.txt 2>&1 # 替换 libFWLIB32.so 之后 cp libFWLIB32.so /usr/local/lib/ ldconfig ./focas_probe 192.168.1.10 > /tmp/ret_after.txt 2>&1 diff /tmp/ret_base.txt /tmp/ret_after.txtdiff 没有输出,说明新旧库对外表现一致。有差异就逐个字段看,是把EW_OK变了,还是超时时间被新库吞掉了。这个回归成本不到五分钟,却能把“开发机正常、上到车间就掉线”的常见问题挡在发布之前。把这套验证流程放进 CI,每次替换库文件后自动跑一遍,FOCAS2 接入模块基本不会在车间里当场翻车。
本文还有配套的精品资源,点击获取