lib60870库解析:实现IEC 60870-5-101/104主站与从站的关键技术

发布时间:2026/9/16 1:27:27
lib60870库解析:实现IEC 60870-5-101/104主站与从站的关键技术
简介面向电力自动化、智能电网及远动通信开发者的IEC60870-5系列标准实现库聚焦101/104协议及5-5补充规范提供完整的C语言协议栈源码、测试示例与使用文档支持串口与TCP/IP两类通信方式。压缩包共157个文件包括42个C源文件、38个头文件构成CS101/CS104从站、链路层、ASDU信息对象等核心模块另有makefile工程文件、sample运行示例、txt/adoc/readme等说明文档以及cer/pem安全证书文件便于支持TLS加密通信。整体约1.66MB结构紧凑适合嵌入式或Linux环境快速集成便于直接嵌入到现有监控系统。目前已有722人学习下载适合需要掌握IEC60870-5通信规约的中高级开发者。通过研读源码与结合示例可深入理解ASDU编解码、TCP连接管理、错误恢复等实现细节也可直接基于库API构建自定义主站/从站程序提升电力通信系统的标准符合性与互操作性对排查通信故障也有直接帮助。1. 从标题堆叠里拆出 lib60870 的真实语义标题 lib60870_iec60870-5-101_13lib_IEC60870-5-5_IEC60870-5-104_lib608 看起来像关键词堆砌但在电力自动化行业它指向一个具体的东西lib60870一个用 C 语言实现 IEC 60870-5-101 和 IEC 60870-5-104 协议的开源库。这两个协议是变电站、配电自动化与调度主站之间最常用的规约。101 走串口或光纤104 走以太网但它们的应用层数据结构ASDU几乎一样区别集中在传输层、帧格式和确认机制。标题里的 IEC60870-5-5 是标准中关于基本应用功能的部分比如时钟同步、总召唤和档位调节lib60870 把这部分也实现了。适合三类人接主站的设备厂商工程师、做协议转换网关的老手以及需要快速跑通功能验证样机的开发者。下面按搭建主站和从站的思路把库的边界、参数和坑讲透。2. lib60870 的协议栈结构与工程选型2.1 从 101 到 104传输层不同应用层同源IEC 60870-5-101 和 IEC 60870-5-104 的关系经常被误解。很多人以为 104 就是 101 的以太网版本改改端口就能用。实际上104 的传输层是 TCP/IP101 的传输层是串行链路RS-232/RS-485链路层实现完全不同但应用层的 ASDUApplication Service Data Unit定义是共用的。lib60870 把 ASDU 编解码放在一个共享层而把 101 和 104 的传输链路分别封装成不同的连接对象。你在代码里会看到CS101_AppLayer和CS104_Connection这样的命名编译后生成不同的静态库但 ASDU 结构体是同一套。这种共享结构带来的直接好处是先在 104 上调试好 ASDU 解析再把同一套处理逻辑搬到 101 上通常只需要改连接建立部分。下面是常见的工程选型对比表维度IEC 60870-5-101IEC 60870-5-104物理层RS-232 / RS-485 / 光纤以太网TCP/IP传输机制串行链路不平衡或平衡传输客户端/服务端TCP 连接默认端口无固定端口取决于链路TCP 2404帧格式FT1.2 帧起始符、控制域、地址域APCI0x68 APDU 长度 控制域确认机制链路层直接确认I/S 帧带序号k/w 窗口控制适用场景距离远、无以太网布的 RTU 站局域网或可路由的网络选型时先回答一个问题现场已有的物理介质是什么如果设备只有 RS-485 接口且没有以太网布线只能选 101如果现场有交换机或光纤收发器104 更合适。lib60870 在 CMake 配置里用CS101_ENABLED和CS104_ENABLED开关控制编译两个都打开会生成两个库体积会大一些但便于一套源码同时支持两套规约。提示如果一台从站同时提供 101 和 104 接口建议把两个链路的公共地址分开配置。常见做法是 101 用公共地址 1104 用公共地址 2这样主站可以通过地址快速判断数据来自哪条链路。2.2 IEC 60870-5-5 的基本应用功能在库里的体现IEC 60870-5-5 规定了总召唤、时钟同步、参数下载和文件传输等高层交互流程。lib60870 并没有为这些功能单独建一个模块而是把它们映射成具体的 ASDU 类型 ID。例如总召唤是类型 100C_IC_NA_1时钟同步是类型 103C_CS_NA_1查询参数是类型 110P_ME_NA_1。在处理函数里只需要 switch 这些类型 ID 即可。所有 ASDU 都有共同的属性公共地址、传送原因、信息对象地址和元素个数。理解这一点很重要因为你在看从站代码时费劲的地方不在于协议本身而在于从一堆业务逻辑里找出哪个函数在处理哪个类型。建议在代码里用宏定义把类型 ID 命名清楚不要直接用数字。比如#define TYPE_MEASURED_VALUE_SHORT 36 // M_ME_NF_1 #define TYPE_SINGLE_POINT_INFO 1 // M_SP_NA_1 #define TYPE_COMMAND_SINGLE 45 // C_SC_NA_1这样在asduHandler里就不容易混淆。2.3 Connection 与 ASDU 的 API 分层lib60870 的 API 分三层最上层是连接对象负责建链、断线重连、心跳保活中间层是 ASDU 处理负责创建、解析、序列化最底层是缓冲区管理避免频繁动态分配内存。面向嵌入式设备时底层缓冲区尤其重要。可以从 lib60870 的BufferFrame相关函数看到设计思路它允许你在栈上申请固定大小的缓冲然后把 ASDU 填充进去。创建 ASDU 最常用的方式是CS101_ASDU_createFromType。这个函数会根据类型 ID 自动分配合适大小的空间。但要注意某些类型比如带时标的测量值有多个构造函数比如CS101_ASDU_createFromTypeWithTime。用错构造函数会导致时标字段未初始化。下面是一个创建 104 从站长帧的例子CS101_ASDU asdu CS101_ASDU_createFromTypeWithTime(C_IC_NA_1); CS101_ASDU_setCommonAddress(asdu, 1); CS101_ASDU_setCauseOfTransmission(asdu, CS101_COT_INTERROGATED_BY_STATION); CS104_Connection_sendASDU(connection, asdu); CS101_ASDU_destroy(asdu);第三行的传送原因CS101_COT_INTERROGATED_BY_STATION表示由总召唤触发。如果你发的是周期数据应该改成CS101_COT_PERIODIC。传送原因不对主站侧可能将数据判为无效或直接丢弃。CS101_ASDU_destroy用来释放由库分配的内存一定不要忘记调用。3. 用 lib60870 搭建一个 IEC 60870-5-104 主站的最小实现3.1 编译与链接的最小命令从 MZ Automation 的官方仓库克隆 lib60870 后使用 CMake 构建git clone https://github.com/mz-automation/lib60870.git cd lib60870 mkdir build cd build cmake .. -DCS104_ENABLEDON -DCS101_ENABLEDOFF make -j$(nproc)如果只做 104 主站关掉 101 可以减小库体积。生成的静态库和头文件在build/src下。你的主站代码需要包含iec60870-104.h链接时加上-l60870或直接指定.a文件。下面是主站连接的最小代码#include iec60870-104.h int main() { CS104_Connection con CS104_Connection_create(192.168.1.100, 2404); CS104_Connection_connect(con); CS104_Connection_sendStartDT(con); Thread_sleep(1000); CS104_Connection_destroy(con); return 0; }这里sendStartDT必须调用。104 协议规定TCP 建链后主站要发送 STARTDT act从站确认 STARTDT con 之后主站才能收发 ASDU。跳过这一步从站会忽略所有后续报文。很多刚接触 104 的人以为连接上就能发数据结果数据发出去从站没有响应抓包才发现少了 STARTDT 握手。3.2 主站连接参数窗口与超时104 的主站参数并不多真正需要调的就下面几个参数默认值含义调整建议k12发送未确认 I 帧的最大数量从站处理慢时适当减小w8接收确认阈值必须小于 kt010sTCP 建链超时跨公网时可加大到 15st115s发送后等待确认的超时从站负载高时可加大到 30st210s发送接收确认的最小周期需小于 t1t320s发送测试帧的周期无线链路可加大到 30s在代码里通过CS104_Connection_setConnectTimeout和CS104_Connection_setTimeoutParameters设置。注意这些设置必须在connect之前调用。我见过有人把t3设成 5 秒导致无线公网环境下频繁发送测试帧占用带宽且被运营商误判为异常流量。通常无线环境建议t3不低于 15 秒。3.3 解析从站上报的遥信和遥测从站上报的 ASDU 在回调函数里处理。一个典型的遥测回调如下static bool asduHandler(void* parameter, CS104_Connection connection, CS101_ASDU asdu) { switch (CS101_ASDU_getTypeID(asdu)) { case M_ME_NC_1: { int count CS101_ASDU_getNumberOfElements(asdu); for (int i 0; i count; i) { float value CS101_ASDU_getNCValue(asdu, i); int addr CS101_ASDU_getInfoObjectAddress(asdu, i); printf(addr%d value%.2f\n, addr, value); } break; } default: break; } return true; }getNCValue返回的是归一化值-1.0 ~ 1.0实际工程值需要乘以刻度scale factor。你需要在设备点表里查到这个刻度否则会出现数值差 100 倍或 1000 倍的错误。对于短浮点测量值M_ME_NF_1用getNFValue直接获取浮点即可。注意count是信息对象里的元素个数循环变量不要越界否则会读到未初始化的内存。3.4 发送控制命令的注意事项控制命令遥控分选择和执行两步。选择是为了防止误操作从站收到选择命令后会先校验对象地址是否合法然后进入待执行状态。lib60870 里有sendSelect和sendExecute两个函数。很多初学者直接调用sendExecute从站会丢弃该命令。正确的单点遥控示例如下CS101_ASDU asdu CS101_ASDU_create(CS101_ASDU_createFromType(C_SC_NA_1)); CS101_ASDU_setCommonAddress(asdu, 1); CS101_ASDU_addSingleCommand(asdu, 5001, 1, true, 0); CS104_Connection_sendASDU(con, asdu); CS101_ASDU_destroy(asdu);这里true表示需要从站返回一个执行确认。最后一个参数是指定输出命令时的重试次数通常填 0。如果从站不支持选择执行可以只发 execute但这属于例外必须在点表里注明。不建议在生产环境关闭选择步骤。4. 101 串口链路与 104 网络的差异处理4.1 101 的链路层状态机101 的串口链路有平衡和不平衡两种模式。不平衡模式下主站轮询从站不能主动上报所有数据上传都要等到主站发请求。平衡模式允许从站主动上报但需要双方支持同一套冲突检测。在 lib60870 中通过CS101_LinkLayerParameters配置链路地址和帧长。下面是一段从站串口初始化的代码CS101_LinkLayerParameters linkParams; linkParams.linkLayerAddress 10; linkParams.unbalancedTransmission false; CS101_SerialLink serialLink CS101_SerialLink_create(linkParams, asduHandler, NULL);linkLayerAddress是从站地址。如果现场 RTU 的拨码地址是 10这里就写 10。unbalancedTransmission设为true表示不平衡模式主站轮询时用 1 字节帧长设为false表示平衡模式从站可以主动发送。调试 101 时最常见的问题是从站不响应用串口抓包看主站发的帧是否带正确的地址和请求功能码。101 请求帧链路地址放在帧中间和 104 的公共地址不是一回事必须严格对应设备参数。4.2 104 的窗口参数与流量控制104 网络传输中I 帧携带发送序号和接收序号S 帧只带接收序号U 帧用于 STARTDT/STOPDT/TESTFR。lib60870 自动处理这些帧但窗口参数仍会影响性能。默认k12意味着最多连续发 12 个 I 帧之后必须等一个 S 帧。如果从站处理速度慢主站会阻塞。这时可以增大 k 到 20但前提是从站窗口也支持。如果从站只支持 k12主站只设 20 是没用的因为从站会因序号超出窗口而丢弃帧。窗口参数的调优需要结合业务数据量。比如秒级刷新 1000 个遥测每个遥测是短浮点每个 ASDU 可以打包多个元素。打包数量越大帧数越少窗口压力越小。lib60870 的CS101_ASDU_addInformationObject支持一次添加多个信息对象建议在实现中把同一类型的点合并到同一 ASDU而不是每点一帧。4.3 地址字段宽度匹配101 和 104 的公共地址和信息对象地址都有 1、2、3 字节的选项。104 默认公共地址 2 字节信息对象地址 3 字节101 很多设备用的是 1 字节公共地址。如果两侧宽度不匹配报文会被误解析。例如主站把公共地址设成 2 字节从站按 1 字节解析读到的高字节会被当成链路地址或干扰字节导致整个帧被丢弃。解决办法是在创建连接时显式指定CS104_Connection con CS104_Connection_create(192.168.1.100, 2404); CS104_Connection_setASDUParameters(con, 2, 3, 10);第三个参数是公共地址的值 10。这里的位数必须和从站配置一致。遇到地址不匹配时用 Wireshark 看报文里的地址字段长度然后和从站手册比对。101 的地址字段设置在CS101_LinkLayerParameters中同样要注意。4.4 抓包与日志定位问题调试 104 时Wireshark 有现成的解析器可以看到 APDU 类型、传送原因和地址。调试 101 则需要逻辑分析仪或串口监视器。lib60870 内置了调试输出可以打印收发帧。启用方法CS104_Connection_debugSetVerbosity(con, CS104_DEBUG_INFO); CS104_Connection_setDebugOutput(con, debugOutput, NULL);debugOutput是一个回调函数可以把十六进制报文写到日志文件。建议在现场联调时把双向报文都打出来先看主站发出的 STARTDT 是否被从站确认再看遥测命令的地址是否符合点表。大多数问题集中在传送原因和地址不匹配上而日志能瞬间暴露。5. 进阶用 lib60870 做多链路冗余与数据质量5.1 双主站热备的连接仲裁大型调度系统中双主站热备是常见要求。104 从站允许两个 TCP 客户端同时建立连接但只有一个是活动主站。第二个主站发起 STARTDT 时从站需要决定是否切换。在 lib60870 中可以通过连接事件回调来实现。比如在CS104_Server_setConnectionHandler里根据客户端 IP 判断是否允许激活同时维护一个主备标志。切换时广播一个总召唤让新主站快速同步状态。5.2 数据质量位的过滤遥信遥测的品质描述位不能忽略。无效位CS101_ASDU_QUALITY_INVALID、溢出位、非当前值位都会影响主站决策。例如某个开关状态因为通信瞬时中断被标记为无效如果主站直接按断开的逻辑跳闸后果严重。正确的做法是在解析到无效位时要么丢弃数据要么打上可疑标记并等待下一次刷新。lib60870 提供CS101_ASDU_getQualityDescriptor获取质量字节。建议在回调里做统一判断uint8_t q CS101_ASDU_getQualityDescriptor(asdu, i); if (q CS101_ASDU_QUALITY_INVALID) { return false; }返回false通知库不确认该 ASDU从站会重发或标记错误。对于不需要重发的场景可以返回true但把状态置为无效由应用层决定如何处理。5.3 链路活性测试技巧104 的测试帧 TESTFR 默认周期是 t3。如果网络抖动可能误判链路中断并重连。可以在应用层加一个附加心跳定期发送带时标的总召唤或时钟同步命令如果在规定时间内收到回应就认为链路活跃。注意不要和库的自动重连机制冲突。当检测到链路长时间无数据时主动调用CS104_Connection_stopThread或destroy然后重新create和connect这样可以快速恢复。不要在一个连接实例上反复尝试connect部分版本会因状态机阻塞导致资源泄漏。处理多链路冗余时可以把 101 和 104 的接收逻辑放在同一个线程池里使用互斥锁保护共享点表。传输层不同但数据源必须一致。我习惯把 ASDU 的创建和解析封装成两个独立组件一份负责从串口来的 101 数据一份负责网络来的 104 数据最后都写入同一个环形缓冲区。这样即使一条链路断了另一条链路的点表仍然是完整的。联调完成后保存一份 pcap 或日志文件既能用于日常巡检也能在故障时快速定位是哪一侧的问题。本文还有配套的精品资源点击获取