ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

IEC 101/104 规约实战:从 iec-master 源码到报文调试与避坑

IEC 101/104 规约实战:从 iec-master 源码到报文调试与避坑 简介本资源围绕电力系统101规约DL/T634.5101-2002与104规约DL/T634.5104-2009展开基于Java语言实现面向从事配网自动化、变电站通信及电力规约开发的工程师与学习者。项目可完成规约报文的解析与组装适用于实际场景中发送报文的生成与交互处理帮助读者理解规约帧结构、信息体地址与传输流程。压缩包共43个文件以34个Java源码为核心辅以3个XML配置、2份规约实施细则文档、1份规约解析细则表格及README说明整体约1.79MB目录按解析与交互模块划分结构清晰。目前已有661人学习下载。通过源码与配套细则文档对照读者可掌握101/104规约的编解码逻辑快速搭建报文生成与解析环境并借助附件中的广东电网实施细则与解析细则表深入理解实际工程中的规约应用与调试要点。1. 从一份 iec-master 源码说起101/104 规约到底在解决什么问题如果你手上正好有一份叫iec-master的代码包打开目录大概率能看到iec101、iec104两个并列的模块外加一堆asdu、apci、cp56time2a之类的命名。很多人第一次接触电力104规约是从「为什么调度主站能实时看到我这条线路的遥测值」这个问题开始的。答案就藏在 IEC 60870-5 这套标准里101 规约跑在串口上104 规约跑在 TCP 上两者共享同一套 ASDU应用服务数据单元结构区别只在链路层和传输层。iec-master这类工程的价值是把这套又长又碎的报文格式变成能编译、能抓包、能对着字节调试的代码。它适合做变电站自动化、配网终端、规约网关的工程师也适合想搞懂「四遥」数据到底怎么在网线上跑的开发者。下面我按自己踩过的顺序把 101 和 104 从报文结构讲到能跑通的代码。2. 101 与 104 的报文骨架从 FT1.2 到 APCI 的差异在哪2.1 为什么同一套 ASDU 要配两种链路层IEC 60870-5-101 的定位是串行链路典型场景是变电站内的 RTU 通过 RS-485 或 RS-232 接到通信管理机。串口是字节流没有连接概念所以 101 用 FT1.2 帧格式来划界固定帧长、可变帧长、单字符三种帧型靠起始字节0x10、0x68和校验和来保证同步。104 则把链路层换成了 TCP/IP用 APCI应用规约控制信息的 6 字节头68 04 xx xx xx xx来标识报文长度和发送序号。这里有个容易翻车的点104 的 APCI 里那个68和 101 可变帧的68长得一样但含义完全不同抓包时如果按 101 的逻辑去解析 104 的 TCP 流序号字段会被当成链路地址整包数据全乱。我一般会先让读者记住一张对照表再去看代码维度IEC 101IEC 104传输介质串口RS-232/485TCP/IP链路帧格式FT1.2固定/可变/单字符APCI6 字节头链路地址1~2 字节无靠 IP 区分发送序号无2 字节模 32768典型端口无2404启动方式链路复位STARTDT 激活这张表不是背的是调试时用来定位问题的。比如你发现 104 连接建立后主站不发总召先看 STARTDT 有没有回而不是去查 ASDU 类型。2.2 ASDU 的公共地址与传送原因怎么读不管 101 还是 104真正承载遥测、遥信、遥控的是 ASDU。一个 ASDU 由数据单元标识和信息对象两部分组成。数据单元标识里类型标识TypeID决定后面跟的是什么数据可变结构限定词VSQ告诉你信息对象的个数和是否连续传送原因COT说明这是周期上送、响应总召还是突发。公共地址CA在 101 里常用来区分不同 RTU在 104 里通常填 1 或按主站约定。以最常见的单点遥信为例TypeID 是 1VSQ 的 bit7 为 0 表示不连续低 7 位是信息对象个数。COT 占两个字节低字节是原因高字节是源发站地址。信息对象地址IOA三个字节小端排列。这些字段在iec-master里通常对应一个struct但不同作者的字段顺序和字节序处理不一样直接拿来用之前一定要对着标准原文核一遍。提示101 和 104 的 ASDU 结构完全一致所以一份 ASDU 编解码代码可以同时服务两个规约前提是把链路层抽象干净。3. 用 iec-master 跑通 104 主站与从站的最小闭环3.1 环境准备与目录结构确认拿到iec-master后先别急着编译。我习惯先看三样东西有没有CMakeLists.txt或Makefile、iec104目录下是不是分了master和slave、有没有示例配置文件。常见做法是主站和从站各编一个可执行文件主站监听或主动连接从站绑定 2404 端口。如果代码里用了epoll或select说明是单线程事件循环调试时可以用strace跟一下系统调用。# 查看目录结构确认主从站入口 find . -maxdepth 3 -type f \( -name *.c -o -name *.cpp -o -name *.h \) | head -40 # 如果存在 CMakeLists按标准流程构建 mkdir -p build cd build cmake .. -DCMAKE_BUILD_TYPEDebug make -j4上面这段命令的作用是先摸清代码组织再决定编译方式。-DCMAKE_BUILD_TYPEDebug是为了后面能用 gdb 看报文组装过程Release 模式下有些变量会被优化掉单步调试时看不到中间值。如果项目只有 Makefile直接make即可但要留意有没有-lpthread之类的链接选项缺失。3.2 从站启动与 STARTDT 握手104 从站启动后第一件事是等主站发 STARTDT 激活。APCI 的 U 格式帧里0x07是 STARTDT 请求0x0B是 STARTDT 确认。很多新手写的从站一上来就主动上送遥测结果主站还没激活链路数据全被丢弃。正确顺序是TCP 连接建立 → 主站发 STARTDT → 从站回 STARTDT 确认 → 主站发总召 → 从站响应总召 → 进入周期上送。// 简化的 STARTDT 处理逻辑U 格式帧控制域为 1 字节 if (apci_len 6 (buf[2] 0x03) 0x03) { // U 格式帧buf[2] 低两位为 11 if (buf[2] 0x07) { // 收到 STARTDT 请求回复确认 uint8_t ack[6] {0x68, 0x04, 0x0B, 0x00, 0x00, 0x00}; send(fd, ack, 6, 0); startdt_active 1; } }这段代码的关键是判断 APCI 长度和控制域。0x68 0x04表示 APCI 总长 6 字节后面四个字节是控制域。U 格式帧的控制域第一个字节低两位为110x07和0x0B分别对应 STARTDT 的请求和确认。startdt_active这个标志位很重要后续所有 I 格式帧的上送都要先检查它否则主站会认为从站行为异常。3.3 总召响应与遥测上送总召是主站用 I 格式帧发来的TypeID 为 100总召唤命令COT 为 6激活。从站收到后要按信息对象地址顺序把遥信、遥测逐个上送最后再发一个 TypeID 100、COT 为 10激活终止的帧表示总召结束。遥测的 TypeID 常用 9归一化值或 11标度化值每个信息对象后面跟 2 字节数值和 1 字节品质位。// 组装一个归一化遥测 ASDUTypeID9COT3突发或 20响应总召 uint8_t asdu[32]; int idx 0; asdu[idx] 9; // 类型标识归一化遥测 asdu[idx] 0x01; // VSQ1 个信息对象不连续 asdu[idx] 20; // COT 低字节响应总召 asdu[idx] 0; // COT 高字节源发站地址 asdu[idx] 1; // 公共地址 asdu[idx] ioa 0xFF; // IOA 低字节 asdu[idx] (ioa 8) 0xFF; asdu[idx] (ioa 16) 0xFF; int16_t norm (int16_t)(value * 32767 / rated); // 归一化到 -1~1 asdu[idx] norm 0xFF; asdu[idx] (norm 8) 0xFF; asdu[idx] 0; // 品质位有效 // 再封装 APCI发送序号和接收序号按当前状态填这里value是实际工程值rated是额定值归一化公式按标准是value / rated * 32767注意溢出和符号。IOA 三字节小端品质位 0 表示有效如果遥测越限或无效要置对应的 bit。发送序号每发一帧加一模 32768接收序号是对端已确认的序号这两个序号维护错了主站会断开连接。4. 101 规约在串口上的调试要点与 FT1.2 帧解析4.1 可变帧长的校验和与结束符101 的 FT1.2 可变帧格式是68 L L 68 C A CS 16其中两个68之间是长度字段重复一次C是控制域A是链路地址CS是从控制域到信息域的校验和最后16是结束符。固定帧格式是10 C A CS 16没有信息域。调试串口时最常见的问题是校验和算错导致从站直接丢帧。校验和的算法是控制域、链路地址、信息域所有字节的算术和取低 8 位不取反。# 计算 101 可变帧校验和 def checksum(data): s 0 for b in data: s (s b) 0xFF return s # 假设控制域 0x53链路地址 0x01信息域为 asdu frame_body bytes([0x53, 0x01]) asdu cs checksum(frame_body) frame bytes([0x68, len(asdu)2, len(asdu)2, 0x68]) frame_body bytes([cs, 0x16])这段 Python 用来验证你手算的校验和对不对。len(asdu)2是因为长度字段包含控制域和链路地址。实际用 C 写的时候注意uint8_t溢出是自动取模的但如果你用了int累加最后要 0xFF。串口参数一般是 9600 或 19200 波特率8 数据位1 停止位偶校验具体看设备手册。4.2 链路复位与召唤的时序101 从站上电后主站会先发链路复位帧固定帧控制域0x40从站回确认控制域0x00。然后主站发总召从站响应。和 104 不同的是101 没有 STARTDT 这个概念链路复位就相当于激活。如果从站一直不回确认先查串口线是不是 A/B 接反再查波特率。我遇到过一批 RTU出厂默认波特率是 9600但现场通信管理机设成了 19200结果就是能收到帧但校验全错因为采样点对不上。注意101 的链路地址在有些设备上是 0有些是 1主站配置里必须和从站一致否则从站会认为不是发给自己的帧而丢弃。5. 避坑与排查报文对不上时先查这五处5.1 现象104 连接建立后主站不发总召原因从站没有正确回复 STARTDT 确认或者回复的 U 格式帧控制域写错。有些代码把0x0B写成了0x03主站认为链路没激活。解决抓包看从站回的第二个字节是不是0x0B同时确认 APCI 长度是 6。5.2 现象遥测值跳变或恒为 0原因归一化公式用错或者字节序搞反。104 的遥测值是小端低字节在前。如果从站用大端发送主站解析出来就是另一个数。解决用计算器把收到的两个字节按小端拼成int16_t再除以 32767 乘以额定值看是否和实际相符。5.3 现象总召响应到一半连接断开原因发送序号或接收序号没有按模 32768 递增或者收到主站的 S 格式帧后没有更新接收序号。104 的序号是强校验的序号错一次主站就可能断开。解决在代码里把每次发送和接收的序号打印出来对比抓包工具的序号字段。5.4 现象101 串口能收到数据但解析全是乱码原因串口参数不匹配最常见的是校验位设成了无校验而设备要求偶校验。偶校验下每个字节的最高位是校验位如果按无校验解析数据会整体偏移。解决用示波器或串口助手确认波特率和校验位再核对帧头的0x68或0x10是否出现在正确位置。5.5 现象遥控命令下发后从站不动作原因遥控的 TypeID 是 45单命令或 46双命令COT 是 6激活从站要回 COT 为 7激活确认的帧然后执行再回 COT 为 10激活终止。如果从站只回了确认没执行或者执行后没回终止主站会认为超时。解决检查遥控处理流程里有没有漏掉激活终止帧以及遥控输出是否被闭锁。6. 进阶用一份代码同时支持 101 和 104 的抽象技巧写到后面你会发现101 和 104 的差异其实只在链路层ASDU 层完全可以复用。我一般会把代码分成三层link层负责帧的收发和校验asdu层负责编解码app层负责业务逻辑。link层定义一个接口101 实现 FT1.2104 实现 APCI上层不关心底下是串口还是 TCP。// 链路层抽象接口 typedef struct { int (*init)(void *cfg); int (*send)(const uint8_t *asdu, int len); int (*recv)(uint8_t *buf, int maxlen); void (*close)(void); } link_ops_t; // 101 和 104 各自实现这套接口app 层只调 link_ops这样做的直接好处是调试 104 时可以用 101 的 ASDU 测试用例来验证编解码反过来也一样。另一个好处是现场如果从串口升级到网络业务代码一行不用改。我自己的习惯是每加一个 TypeID先在 101 的串口助手上发一遍确认 ASDU 字节对了再切到 104 走 TCP。这个顺序能省掉很多「到底是链路问题还是应用问题」的纠结。最后说一个验证方法用 Wireshark 抓 104 的包过滤tcp.port 2404然后对着iec-master的日志逐字段比对。如果 Wireshark 能正确解析出 TypeID 和 IOA说明你的 APCI 和 ASDU 都没问题如果 Wireshark 显示 malformed那一定是长度字段或序号错了。这个习惯我保持了几年比任何文档都快。希望帮到你。本文还有配套的精品资源点击获取
返回列表