lib60870库解析:实现IEC 60870-5-101/104主站与从站的关键技术
2026/9/16 1:27:19 网站建设 项目流程

简介:面向电力自动化、智能电网及远动通信开发者的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/IP,101 的传输层是串行链路(RS-232/RS-485),链路层实现完全不同,但应用层的 ASDU(Application Service Data Unit)定义是共用的。lib60870 把 ASDU 编解码放在一个共享层,而把 101 和 104 的传输链路分别封装成不同的连接对象。你在代码里会看到CS101_AppLayerCS104_Connection这样的命名,编译后生成不同的静态库,但 ASDU 结构体是同一套。

这种共享结构带来的直接好处是:先在 104 上调试好 ASDU 解析,再把同一套处理逻辑搬到 101 上,通常只需要改连接建立部分。下面是常见的工程选型对比表:

维度IEC 60870-5-101IEC 60870-5-104
物理层RS-232 / RS-485 / 光纤以太网(TCP/IP)
传输机制串行链路,不平衡或平衡传输客户端/服务端,TCP 连接
默认端口无固定端口,取决于链路TCP 2404
帧格式FT1.2 帧(起始符、控制域、地址域)APCI(0x68 + APDU 长度 + 控制域)
确认机制链路层直接确认I/S 帧带序号,k/w 窗口控制
适用场景距离远、无以太网布的 RTU 站局域网或可路由的网络

选型时先回答一个问题:现场已有的物理介质是什么?如果设备只有 RS-485 接口且没有以太网布线,只能选 101;如果现场有交换机或光纤收发器,104 更合适。lib60870 在 CMake 配置里用CS101_ENABLEDCS104_ENABLED开关控制编译,两个都打开会生成两个库,体积会大一些,但便于一套源码同时支持两套规约。

提示:如果一台从站同时提供 101 和 104 接口,建议把两个链路的公共地址分开配置。常见做法是 101 用公共地址 1,104 用公共地址 2,这样主站可以通过地址快速判断数据来自哪条链路。

2.2 IEC 60870-5-5 的基本应用功能在库里的体现

IEC 60870-5-5 规定了总召唤、时钟同步、参数下载和文件传输等高层交互流程。lib60870 并没有为这些功能单独建一个模块,而是把它们映射成具体的 ASDU 类型 ID。例如总召唤是类型 100(C_IC_NA_1),时钟同步是类型 103(C_CS_NA_1),查询参数是类型 110(P_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_ENABLED=ON -DCS101_ENABLED=OFF 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接收确认阈值必须小于 k
t010sTCP 建链超时跨公网时可加大到 15s
t115s发送后等待确认的超时从站负载高时可加大到 30s
t210s发送接收确认的最小周期需小于 t1
t320s发送测试帧的周期无线链路可加大到 30s

在代码里,通过CS104_Connection_setConnectTimeoutCS104_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 里有sendSelectsendExecute两个函数。很多初学者直接调用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 自动处理这些帧,但窗口参数仍会影响性能。默认k=12意味着最多连续发 12 个 I 帧,之后必须等一个 S 帧。如果从站处理速度慢,主站会阻塞。这时可以增大 k 到 20,但前提是从站窗口也支持。如果从站只支持 k=12,主站只设 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_stopThreaddestroy,然后重新createconnect,这样可以快速恢复。不要在一个连接实例上反复尝试connect,部分版本会因状态机阻塞导致资源泄漏。

处理多链路冗余时,可以把 101 和 104 的接收逻辑放在同一个线程池里,使用互斥锁保护共享点表。传输层不同,但数据源必须一致。我习惯把 ASDU 的创建和解析封装成两个独立组件,一份负责从串口来的 101 数据,一份负责网络来的 104 数据,最后都写入同一个环形缓冲区。这样即使一条链路断了,另一条链路的点表仍然是完整的。联调完成后,保存一份 pcap 或日志文件,既能用于日常巡检,也能在故障时快速定位是哪一侧的问题。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询