FOCAS CNC数据采集开发包:从机床联网到MES实战 简介面向FANUC CNC数据采集开发的接口程序与示例包适合数控系统二次开发、机床联网监控及工业数据采集方向的工程师与进阶学习者使用。内容围绕FOCAS开放式通讯架构展开涉及HSSB高速串行总线等通信方式可用于建立CNC控制器与上位机之间的数据通道采集轴位置、速度、切削参数和报警信息并服务于实时监控、故障诊断与预测性维护等场景。压缩包为zip格式大小约26.5MB文件总数显示为0且类型明细暂未提供具体构成以实际包内文件为准。已有5202人学习或下载说明其在FANUC数据采集主题下具有一定参考热度。资源价值主要体现在硬件接口说明、C语言或.NET环境下的调用示例以及FOCAS函数用途、参数、返回值和错误代码解析便于读者理解接口调用流程、减少常见编程陷阱并在此基础上扩展定制化采集方案。1. 从机床面板到数据库中间隔着一个 FOCAS产线上有台加工中心面板上坐标、主轴转速、报警号跳得清清楚楚可你想把这些数据搬进 MES却发现网络上什么都看不见。机床本体就是个数据黑匣子网线插上去ping 得通但读不出一个数字。FANUC CNC 数据采集开发包FOCAS解决的就是这一段厂商把底层通讯协议封成一组 C 接口库你先调cnc_allclibhndl3建个句柄再调cnc_rddynamic2把坐标取出来中间的通讯帧、心跳、超时重连全由库自己处理。这份资源是一套接口程序加示例工程覆盖以太网与 HSSB 两条接入路线适合做设备联网、产线看板、刀具寿命管理和加工节拍统计的开发者。哪怕你只写 C#把几个结构体对齐好一天之内也能读到第一帧真实数据。2. 开发包定位与技术选型FOCAS 到底替你封装了什么压缩包解开第一眼容易看懵一堆.lib、.dll、.h加几个示例工程没有那种从第一页读到最后一页的说明书。先建立个正确认知FOCAS 是跑在 PC 侧的通讯中间件它把 CNC 内部已经组织好的数据区运行状态、坐标、参数、刀补、程序目录、报警历史映射成固定结构体你负责填句柄、给缓冲区、读结构体剩下的协议细节它管。这个定位决定了三件事。第一采样频率的上限不在你的代码而在通讯链路和 CNC 的处理能力第二能拿到什么数据取决于机床侧开了哪些选件功能而不是你写了多少行代码第三它不负责数据清洗和存储读回来的是原始值单位换算、异常过滤、断点续传都得你自己在上层做。所以工程上的选型本质上是在回答三个问题走哪条物理链路、用哪个位数的库、按什么节奏采。2.1 三条接入路线以太网、HSSB、串口怎么选以太网是最常见的一条。机床自带 RJ45 口在参数里把 IP 配好PC 侧直接用cnc_allclibhndl3指定地址和端口连过去默认端口 8193。PC 侧零硬件成本一台工控机可以连几十台机床代价是采样周期通常只能做到百毫秒级想拿伺服波形基本没戏。HSSB 走的是另一条路PC 侧要插专用通讯卡并装驱动用专用电缆连到 CNC 的 HSSB 接口。它的优势是延时低、可以高频轮询老机型没有以太网选件时往往也只有这条路能走。代价很直接卡要钱驱动挑操作系统而且很多驱动是 32 位的你的上位机进程可能被迫编成 x86。串口现在是兜底方案速率低一般只在极老设备上做最简单的状态位采集。接入方式PC 侧硬件典型采样周期适用场景主要限制以太网无普通网卡100ms 起新机型、多机联网高频采样受限HSSB专用通讯卡可到 10ms 级老机型、波形采集成本高、驱动挑系统串口无秒级极简状态位数据量极小选型的实操建议先查机床有没有以太网选件。有就走网口别为了快那几十毫秒去插卡后期维护成本差好几倍。只有当你要做伺服负载分析、主轴振动这类需要连续高频采样的活才值得上 HSSB并且要把 32 位进程这件事提前跟做上位机的人说清楚别等界面都写完了才发现驱动装不上。提示接入方式和机型强相关签约前先让现场把 CNC 的选件配置清单导出来对一遍比事后返工便宜得多。2.2 包里的目录与文件库、头文件、示例工程怎么用开发包的目录结构大同小异一般长这样focas_sdk/ ├── inc/ │ └── fwlib32.h # 全部函数原型与结构体定义 ├── lib/ │ ├── fwlib32.lib # 32 位静态导入库 │ └── fwlib64.lib # 64 位静态导入库 ├── bin/ │ ├── fwlib32.dll # 32 位运行库 │ └── fwlib64.dll # 64 位运行库 └── sample/ ├── cnc/ # 状态、坐标、报警等示例 └── ...四个地方容易出错。头文件只有一个fwlib32.h不管 32 位还是 64 位都包含它别去找什么fwlib64.h。导入库和运行库的位数必须跟你的进程一致一个 64 位程序链了fwlib32.lib编译期可能不报错运行期一调函数就崩。DLL 要么放在 exe 同目录要么进系统 PATH很多“明明编译通过却跑不起来”的问题都出在这。示例工程一般是最老旧的 C 控制台工程用新版本 IDE 打开会提示升级工具集升就行别被它吓住。我一般的做法是先在开发机上单独建一个空的 C 控制台工程只把fwlib32.h和对应的.lib挂进去写一个“连上就释放”的最小程序。这一步跑通后面所有问题都能定位到是逻辑问题还是环境问题。2.3 函数族地图句柄、结构体、返回码三件套FOCAS 的函数看着多套路是统一的第一个参数永远是unsigned short类型的句柄最后一个参数永远是指向某个结构体的指针返回值永远是一个short类型的返回码。记住这个形状随便翻一个函数都能猜出用法。按数据类别分常用的就这么几族数据类别典型函数输出结构备注运行状态cnc_statinfoODBST自动/手动、运行、急停、报警坐标与进给cnc_rddynamic2ODBDY2绝对/相对/机械坐标一次取回报警cnc_alarm2ODBALM报警号列表参数cnc_rdparamIODBPSD按参数号读可分轴刀补cnc_rdtofsODBTOFS按刀补号读程序号cnc_exeprgnameODBEXEPRG当前执行程序名机床标识cnc_rdcncid数组做设备指纹很实用返回码要先处理掉。EW_OK是 0代表成功其余值分两类一类是通讯层面的连不上、超时、句柄无效一类是业务层面的参数号不存在、该功能未开通。这两类混在一起处理是新手常犯的错——通讯失败要重连业务失败重连一万次也没用。/* 统一返回码处理避免到处写 if (ret ! 0) */ static int check(short ret, const char *what) { if (ret EW_OK) return 0; switch (ret) { case EW_SOCKET: /* 链路层问题值得重连 */ case EW_HANDLE: /* 句柄失效必须重建 */ fprintf(stderr, [%s] comm error %d, need reconnect\n, what, ret); return -1; case EW_NODLL: /* 库没找到环境问题 */ fprintf(stderr, [%s] dll not found\n, what); return -2; default: /* 其余多为功能/参数不支持重连无用 */ fprintf(stderr, [%s] func error %d\n, what, ret); return -3; } }what是调用点的名字日志里能直接看出是哪个数据项挂了返回 -1 交给上层的重连逻辑-3 只记日志不重连。这套分流看着朴素但在几十台机床同时跑的现场能省掉大量无效重连带来的抖动。2.4 能力边界FOCAS 拿不到的东西提前说清楚这条能避免项目中期扯皮。FOCAS 读的是 CNC 已经组织好的数据不是万能探针。PLC 梯形图里的内部继电器、伺服波形细节、加工条件的深层数据往往需要额外的选件功能或者专门的接口函数有些还要在机床侧开通授权。同一个函数在不同代次的 CNC 上返回值也可能不一样老系统上直接返回“功能不支持”是常态。落地时的做法是把“要采的字段”列成一张表逐条标注来自哪个函数、需要什么选件、在目标机型上是否验证过。这张表在项目启动会上过一遍比写完代码再逐台调试要省事太多。3. 跑通第一个采集程序从建句柄到读到真实坐标示例工程里代码不少但真正需要先跑通的只有四段环境准备、建立连接、读一个有用数据、按节奏轮询。这四段拼起来就是一个最小可用的采集器剩下的函数都是在这个骨架上加肉。3.1 环境准备位数、库路径与第一个空工程先在开发机上确认两件事你的进程是 32 位还是 64 位目标机床能不能 ping 通。位数决定用哪套库ping 通只是必要条件不是充分条件——端口 8193 被防火墙拦了ping 照样通但句柄建不起来。Windows 上最省事的做法是把 DLL 拷到 exe 同目录Linux 侧则是把.so放到LD_LIBRARY_PATH能找到的位置或者干脆在编译时写死 rpath。命令行编译一个最小测试程序大概是这样# Linux 下编译最小测试程序注意 -m32 与库的位数要一致 gcc -m32 -I./inc connect_test.c -L./lib -lfwlib32 -o connect_test # 运行时把库路径带上避免 cannot open shared object file LD_LIBRARY_PATH./lib ./connect_test-m32表示编 32 位程序必须和libfwlib32匹配-I指定头文件目录-L加-l指定库。Windows 上对应的是在工程属性里加附加包含目录和附加库目录再把 DLL 拷到输出目录。这一步没配好后面所有代码都是白写。3.2 建立连接句柄、超时与断线判定连接是整个采集器的命门。所有后续函数都依赖这个句柄句柄一失效后面全盘皆输。#include stdio.h #include fwlib32.h int main(void) { unsigned short h 0; /* 句柄所有后续调用都要带上它 */ short ret; /* 参数依次为机床 IP、端口(默认 8193)、超时秒数、句柄输出 */ ret cnc_allclibhndl3(192.168.1.10, 8193, 10, h); if (ret ! EW_OK) { printf(connect failed, ret%d\n, ret); return -1; } printf(connected, handle%u\n, h); /* 读一次状态验证链路真的通了不只是建了句柄 */ ODBST st; ret cnc_statinfo(h, st); printf(statinfo ret%d run%d alarm%d\n, ret, st.run, st.alarm); /* 用完必须释放否则服务端句柄槽位会被占满 */ cnc_freelibhndl(h); return 0; }cnc_allclibhndl3的四个参数分别是目标地址、端口、超时秒数和句柄指针。超时别设太小老机床响应慢3 秒以内很容易误判成断线也别设太大链路真断了会白白卡住采集线程。我给的经验值是 10 秒配一个独立的心跳线程做存活检测。建完句柄一定要立刻读一次数据验证。有些现场会出现“句柄建成功但读不了”的情况典型原因是 CNC 侧允许的连接数满了或者选件没开。这一行验证代码能把这两种情况提前暴露出来。cnc_freelibhndl是必须调的。CNC 侧可同时连接的句柄数量有限程序异常退出没释放下一次连接就会失败现场表现为“昨天还好好的今天怎么都连不上”重启机床才好——这就是没释放句柄留下的问题。3.3 读坐标与运行状态示例里最值钱的两段代码坐标和状态是采集频率最高的两个数据项也是示例工程里最值得抄的两段。ODBDY2 dyn; ODBST st; short ret; /* 状态自动/手动、运行中、急停、报警都是短整型 */ ret cnc_statinfo(h, st); if (ret EW_OK) { printf(mode(aut%d manual%d) run%d motion%d emergency%d alarm%d\n, st.aut, st.manual, st.run, st.motion, st.emergency, st.alarm); } /* 动态数据一次取回坐标、进给速度、主轴转速、当前刀具号 */ memset(dyn, 0, sizeof(dyn)); ret cnc_rddynamic2(h, ALL_AXES, sizeof(dyn), dyn); if (ret EW_OK) { /* axis 告诉你有几个轴有效多轴机床别写死 3 */ for (int i 0; i dyn.axis i 32; i) { printf(axis%d abs%.3f rel%.3f mach%.3f\n, i 1, dyn.abs[i], dyn.rel[i], dyn.mach[i]); } }cnc_rddynamic2的第二个参数传ALL_AXES表示一次取回全部轴也可以传具体轴号只取一根轴第三个参数是缓冲区长度务必传sizeof(dyn)传小了库会按你给的长度截断读回来的结构体后半段全是零排查起来极其费劲。返回后先看dyn.axis它才是本次真正有效的轴数硬编码 3 轴在四轴、五轴机床上会漏数据。坐标单位是毫米已经带小数不需要再除什么系数。有些老机床会返回机床坐标系下的值跟面板显示的对不上这时候要和面板对一次确认你要的是abs还是mach。这个对账动作看着笨却是最快排除“读到的数不对”这类玄学问题的办法。3.4 轮询节奏与线程模型采集器不能一个死循环狂读。CNC 侧处理每个请求都要占资源读太快会把机床拖慢严重的会影响加工节拍这在现场是不能接受的。节奏上我的习惯是分档状态和坐标 200500ms 一次报警 1s 一次参数和刀补这类变化慢的 30s 甚至 5 分钟一次。加工中心做看板200ms 完全够用做节拍分析得根据最短工序时间倒推一般不小于 100ms。线程模型上有一条铁律一个句柄只在一个线程里用。FOCAS 的句柄不是线程安全的两个线程拿同一个句柄并发调用轻则返回错乱的数据重则把整个库的上下文搞坏。正确做法是每台机床一个采集线程线程内部自己建句柄、自己释放线程之间不共享任何 FOCAS 资源。/* 每台机床一个采集线程句柄在线程内创建与销毁 */ void *collector(void *arg) { machine_t *m (machine_t *)arg; unsigned short h 0; while (!m-stop) { if (h 0) { if (cnc_allclibhndl3(m-ip, 8193, 10, h) ! EW_OK) { sleep_ms(3000); /* 连不上就退避别死循环重试 */ continue; } } if (poll_once(h, m) ! 0) { /* 通讯类错误丢弃句柄重连 */ cnc_freelibhndl(h); h 0; } sleep_ms(m-interval_ms); } if (h) cnc_freelibhndl(h); return NULL; }poll_once返回非 0 表示链路已经不可用这时候主动释放句柄再重建比抱着一个坏句柄反复调用要干净。退避时间别设太短机床断电或者网络闪断时3 秒重试一次就够10 台机床一起疯狂重连反而会把交换机打满。4. 核心函数族拆解报警、参数、程序号与加工实绩坐标和状态只是看板上的几个数字真正让甲方愿意掏钱的是报警追溯、刀补管理和加工实绩。这几块的数据结构比坐标复杂坑也更多。4.1 报警采集cnc_alarm2 与报警号的落地映射报警是采集里最容易被低估的一块。很多人以为读个报警号就完了实际落地时甲方要的是“这个报警出现过几次、持续多久、当时在跑哪支程序”。ODBALM alm; short ret; memset(alm, 0, sizeof(alm)); ret cnc_alarm2(h, alm); if (ret EW_OK) { /* num 是本次实际返回的报警条数别当成固定 32 条 */ for (int i 0; i alm.num i 32; i) { printf(alarm no%d\n, alm.data[i]); } if (alm.num 0) { printf(no alarm\n); } }alm.num是关键字段为 0 表示当前无报警。这里有个细节报警清掉之后num会变 0但data数组里的旧值不一定被清零所以循环必须严格按num走不能拿“数组里还有值”当有报警的判断依据。我见过有人写成遍历 32 个元素找非零值结果报警消了还在一直上报被现场投诉。报警号拿到之后还需要一张映射表把它翻译成中文描述。这张表一般来源于机床厂的报警说明书自己没有的话只能人工整理前期先落库后面遇到一个补一个。报警的持续性也需要自己维护同一报警号连续出现在多次轮询里算作一次事件消失后再出现算新事件。这个状态机不难写但不写就没法算停机时长。4.2 参数与刀补cnc_rdparam 与 cnc_rdtofs 的分批读取参数是排查问题的关键证据。机床跑出来的活不对第一个动作往往是导出相关参数做对比。IODBPSD psd; short ret; /* 读 1 号参数第 0 轴部分参数不区分轴传 0 即可 */ memset(psd, 0, sizeof(psd)); ret cnc_rdparam(h, 1, 0, sizeof(psd), psd); if (ret EW_OK) { /* 参数按类型存在不同成员里long 型用 ldata位型用 cdata */ printf(param1 %ld\n, psd.ldata); }cnc_rdparam的第二个参数是参数号第三个是轴号第四个是缓冲区长度。这里最容易出错的是取值成员IODBPSD是个联合体按参数类型分别用cdata字节、idata短整、ldata长整、rdata实数。用错成员读出来就是一堆看着像随机数的值。判断类型的办法是查参数手册或者先按手册标定的类型写死别指望库能自动识别。刀补的读法类似cnc_rdtofs按刀补号和类型读返回结构里data就是补偿量。批量读的时候注意别在一个轮询周期里把几百个刀补号全读一遍那会明显拖慢响应。我一般的做法是分页读一页 20 个一个周期读一页转完一圈再从头来。参数写入要格外克制。示例里有写参数的函数但在生产环境里让程序去改机床参数是高风险动作除非有明确的业务需求和完善的回滚方案否则一律只读。4.3 程序号与加工实绩cnc_exeprgname 与目录遍历要统计“哪支程序加工了多久、做了多少件”就得知道当前在跑什么程序。ODBEXEPRG prg; short ret; memset(prg, 0, sizeof(prg)); ret cnc_exeprgname(h, prg); if (ret EW_OK) { /* name 是当前执行的主程序名o 是程序号 */ printf(running program: %s (O%ld)\n, prg.name, prg.o); }prg.name已经是以\0结尾的字符串可以直接打印。程序切换的判定很简单记住上一次的名字变了就是换程序配合时间戳就能算出单支程序的加工时长。但要注意子程序调用期间这个名字不会变如果你的业务关心子程序得靠cnc_rdprogdir遍历目录或者从 PLC 侧取这块要提前和甲方确认口径。加工计数是另一个高频需求。有些机床的计数器可以通过参数读到有些只能靠外部信号或者程序末尾的宏变量。宏变量用cnc_rdmacro读写法跟读参数类似值是字符串形式存的读回来还得自己转成数字——这个转换别用atof一把梭得先判断是不是数值宏变量里存日期、存程序名的情况太常见了。4.4 结构体对齐与字节序读出来是天文数字的根源这一段是纯经验。跨语言调用 FOCAS 时最常见的问题是结构体对不上表现是读回来的浮点数变成1e300这种天文数字或者整数变成一个巨大的负值。根因有两个。一是结构体对齐方式不一致C 侧默认按成员自然对齐C# 侧默认按 8 字节对齐两边 sizeof 就差了几个字节后面所有字段全部错位。二是字段顺序或者类型理解错比如把short当成int读一次错位后面全乱。解决办法很朴素在 C# 里给每个结构体显式标[StructLayout(LayoutKind.Sequential, Pack 1)]或者按头文件里的实际对齐来然后用Marshal.SizeOf打印出来和 C 侧printf(%zu, sizeof(ODBDY2))的结果比一比。两个数字一样结构体才算对上了。这一步花十分钟能省掉后面几天的排查。注意不同代次的 CNC同一个结构体的成员可能略有差异。编译前对着包里fwlib32.h核一遍字段名和顺序别照着网上抄。5. 常见问题排查连不上、读乱、掉线的五条现场记录这一章是纯踩坑记录。每一条都是“现象—原因—解决”的结构遇到对得上的直接照着排。5.1 现象句柄一直是 0 或负数日志里全是连接失败现象很直接cnc_allclibhndl3返回非EW_OK句柄输出是 0后面所有调用全部失败。原因通常有三个。最常见的是网络层没通——不是 ping 不通那种而是端口 8193 被防火墙或者安全软件拦了。其次是机床侧没有启用相关服务或者 IP 配错了网段。第三种较少见但很坑CNC 侧可用的连接数已经被占满之前的程序异常退出没释放句柄。排查顺序我固定这么走先在 PC 上用 telnet 直接敲端口通了再往下看然后换一台机器用官方示例程序连同一台机床示例能连上说明是你的代码或环境问题示例也连不上就是网络或者机床配置问题最后检查是不是有残留进程占着连接把 PC 侧所有相关进程杀掉再试一次。5.2 现象坐标能读到但数值大得离谱或者全是 0现象是结构体读回来了返回码也是成功但abs数组里全是天文数字或者干脆一片 0。原因基本锁定在结构体定义上。要么是 C# 侧的Pack没设对要么是字段类型照抄了别的版本的头文件。还有一种情况是缓冲区长度传错了——cnc_rddynamic2的第三个参数如果传的是一个硬编码的小数字库会按你给的长度只填前面一小段后面的成员保持调用前的值看着就像“读到了但不对”。解决办法是按 4.4 节的办法核对sizeof然后在调用前memset清零。清零这一步很重要能把“没被填充”和“填充了零值”区分开。5.3 现象跑几个小时就掉线重启程序又能跑这是长跑采集器最典型的症状。程序刚起来一切正常几小时后开始零星报错再过一阵子彻底不采了。原因多半是句柄泄漏或者没有错误恢复。每条采集路径都在建句柄但没在异常分支里释放句柄槽位一点点被吃光另一种是某个调用返回了通讯错误代码里只打了日志没重建连接后面所有调用都在用一个死句柄。解决的关键是把“释放—重建”做成一个明确的函数所有通讯类错误统一走它。我一般会在采集线程里维护一个失败计数连续失败超过阈值就强制释放重建同时把这次重建记进日志。日志里能看到重建频率频率突然升高通常意味着网络或者机床本体出了问题是一个很好的预警信号。5.4 现象HSSB 卡驱动装好了但枚举不到任何机床现象是设备管理器里能看到卡驱动也没报错但用示例程序就是找不到机床。原因有三类。一是驱动位数和进程位数不匹配卡是 32 位驱动程序编成了 64 位两边根本对不上话。二是专用电缆没插到位或者接错口HSSB 的接头有方向性插反了外观上看不出来。三是机床侧的 HSSB 参数没配对不同机型这部分配置项的位置不一样得照着机床手册逐项对。排查上先确认进程位数这一步能解决大半问题然后用厂商配的工具做链路自检工具能识别到设备再回来调代码最后才是查机床侧参数。5.5 现象并发采集十几台之后程序越来越慢直到卡死现象是单台测试很流畅加到十几台之后响应变慢最后整个程序卡住不动。原因是线程和句柄的关系没设计好。常见写法是所有机床共用一个线程池池里的线程随机去拿任务同一个句柄被不同线程交叉调用库内部的上下文被打乱表现就是越来越慢。另一种是每台机床起一个线程但没设上限二十台机床二十个线程同时高频轮询CNC 侧和网络都扛不住。解决办法是回到 3.4 节的模型一台机床一个固定线程句柄在线程内闭环线程之间零共享。并发数超过三十台时考虑按车间分组每组一个采集进程进程之间用消息队列汇总避免单进程资源耗尽拖垮全部。提示排查并发问题最有效的手段是给每个线程打带机床编号的日志前缀出问题时一眼能看出是哪台机床、哪个线程在拖后腿。6. 二次开发改造把示例包封装成一条产线的采集服务示例代码是“能跑”离“能上线”还差一层封装。我的改造思路是分三层每层职责单一出问题能快速定位。最底下是通讯层只做一件事把 FOCAS 的句柄管理和返回码处理包起来对外暴露connect、read_status、read_position、read_alarm这几个语义化方法。上层拿到的要么是结构体要么是一个明确的错误类型不接触任何原始返回码。这一层是唯一引用fwlib32.h的地方将来换接入方式比如从以太网换成 HSSB改动只在这里。中间是映射层负责把 FOCAS 的原始结构翻译成业务字段。比如st.run是 1 还是 3对应到“运行/暂停/停止”哪个状态这层用一张配置表做映射别写死在代码里。机床型号多的时候映射表会变成项目里最值钱的东西一定要外置成配置文件。最上面是上报层负责攒批、去重、断网缓存。CNC 采集最怕的就是网络闪断丢数据本地留一个环形缓冲网恢复了补发比什么都实在。配置化是第二个改造点。机床清单、采集周期、字段映射全部外置一份配置大概长这样{ machines: [ { id: MC-01, ip: 192.168.1.10, port: 8193, timeout: 10, poll_ms: 300, fields: { position: true, alarm: true, spindle_load: true, param_list: [1, 2, 100, 101] } } ] }poll_ms按机床性能单独调老机床给 500ms 甚至 1000ms新机床给 200mstimeout保持一致即可param_list按机床型号列不同型号的参数号并不通用硬编码一套必然出错。这份配置改完不需要重新编译现场调试效率差别很大。验证方法上我有一个用了很多年的笨办法先用官方示例程序读一遍把值截图或者记下来再用自己写的服务读同一台机床同一个时刻两边对。对不上就逐字段比从结构体大小、字段偏移一路查到单位换算。这套对拍流程看着原始却能在十分钟内判断问题出在库还是出在自己代码。还有个小技巧值得单说给每台机床的采集线程加一个“数据年龄”指标记录最近一次成功读取距今多少毫秒。看板不显示这个指标但内部监控一定要有。采集停摆的时候这个数字会先动起来比等到甲方打电话过来早好几个小时发现。最后说个教训。早期做一个多机床项目我图省事把句柄存在全局变量里几个线程共用在开发机上测了三天都没事上线第二天全车间采集全挂。从那以后我每次写采集线程都强制走一遍“句柄在线程内创建、线程内释放、跨线程零共享”这个检查清单再急也不跳。希望帮到你。本文还有配套的精品资源点击获取