ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从IEC101到IEC104:用iec-master实战电力104规约主站调试

从IEC101到IEC104:用iec-master实战电力104规约主站调试 简介面向电力自动化与配网通信领域的 Java 开发者这份资源基于 DL/T634.5101-2002 和 DL/T634.5104-2009 标准实现了 IEC101/104 规约报文的解析与组装可应用于实际工程中的发送报文生成、报文校验与调试等场景也能为协议栈二次开发提供可复用的骨架。包体共 43 个文件以 34 个 Java 源码文件为主辅以 3 个 XML 配置、2 个规约实施细则 DOCX 文档、1 个 XLSX 解析细则表及说明与许可文件压缩包仅 1.79MB整体轻量且结构清晰。代码按解析、交互等模块划分并配有 docs 目录下的广东电网配网自动化规约实施细则与解析细则表格方便对照标准核查字段定义与组装流程。已有 658 人学习下载适合正在从事电网规约开发、需要快速落地 101/104 通信逻辑或排查报文问题的初中级工程师参考可以显著缩短从报文分析到编码实现的时间。1. 用iec-master视角看电力104规约它和IEC101是什么关系在电力远动这一行待久了你会发现IEC60870-5-101 和 IEC60870-5-104 的关系像同一套词汇表配了两本语法书。101 走串口链路层要自己处理地址、确认和重发104 走 TCP把链路层的活交给 TCP 的序号和确认机制。但到了信息体这一层遥信、遥测、遥控的 ASDU 结构是同一套所以做 104 主站收到的数据帧用 101 的报文知识照样能读懂大半。标题里这几个关键词拼在一起说的就是这件事用 iec-master 做 104 主站拉数据同时保留对 101 老站装置的兼容能力。做储能、光伏、配网终端接入或者在 EMS 里写规约适配层的人大概率都会撞上这个题。下面这六章按我实际调试的习惯展开先把两种规约的边界讲清再把 104 的帧格式、参数、解析和排错逐一落到可执行的命令上。2. 从IEC101到IEC104串口到TCP的映射带出报文结构差异2.1 101和104差在哪一层链路层职责转移IEC60870-5-101 定位在串口通信链路层是它自己的规约。101 的一个链路规约数据单元LPDU大致是启动字符 0x68、长度 L、链路控制字、链路地址后面再挂 ASDU。链路控制字里带着 FCB、FCV、ACD、DFC 这些位主站和从站要靠它做收发确认、忙闲通知一套流程走完才能保证一帧数据可靠送达。IEC60870-5-104 把 101 的链路层整个换掉了。104 直接跑在 TCP/IP 上链路地址不再单独出现公共地址收编进 ASDU 里链路控制字的职责交给 TCP 的滑动窗口和 6 字节 APCI 控制域。这个替换的结果是101 的 ASDU 几乎原样搬进 104但打包方式和确认机制完全不同。实际工作中经常遇到“同一个从站既出 101 又出 104”的情况从站程序里 ASDU 编解码是共用的只有链路层分了两套实现。这对主站开发者的启示很直接如果你已经写过 101 主站104 主站要新写的是 APCI 封装、序号管理、TCP 连接维护这三块ASDU 那层解析逻辑可以直接复用。反过来只懂 104 的人去看 101最容易懵的就是链路控制字那几位。2.2 104报文的APCI四字节控制域I帧、S帧、U帧的一个表格104 的 APDU应用规约数据单元由两部分组成4 字节 APCI 控制域 可选的 ASDU。完整报文的第一个字节固定是 0x68第二个字节是 APDU 长度表示后面的 4 字节控制域加 ASDU 一共多长。第三个字节到第六个字节就是控制域本体它同时承担了帧类型识别、序号传递和链路控制三个功能。68 LEN C1 C2 C3 C4 ASDU...控制域第一字节的低两位决定了帧类型帧类型低两位作用典型第一字节I帧00承载数据带发送序号和接收序号0x00~0x02 等偶数S帧01纯确认只带接收序号0x01U帧11链路控制启动、停止、测试0x07、0x0B、0x43 等I帧的 4 字节控制域里第 1、2 字节是发送序号左移一位第 3、4 字节是接收序号左移一位。为什么左移一位因为最低位要让给帧类型标记。接收方拿到序号后右移一位就是真实的序号 N。U帧的三个命令在调试里最常用0x07 是启动传输 STARTDT act0x0B 是启动确认 STARTDT con0x43 是测试帧 TESTFR act。记住这六个字节的小表看抓包就能一眼分帧型。2.2.1 三种帧的识别与序号规则I帧序号是 15 位取值范围 0 到 32767循环使用。主站和从站各自维护自己的发送序号不收对方影响。S帧只携带接收序号用来确认自己已经收到对方多少帧。U帧不需要序号也不能携带数据它的作用是建立传输关系。这里有一条规则值得单独强调U帧的 STARTDT act 必须在 TCP 连接建立之后、任何 I 帧发送之前完成。有的主站实现省掉这一步直接发总召唤部分从站会直接丢弃表现就是“TCP 能连上但一条遥测都收不到”。我一般会在连接成功后的第一件事就发 0x07等收到 0x0B 再进数据流程。2.3 一个起始报文STARTDT act的逐字节含义拿最常见的启动报文做例子主站发给从站的 STARTDT act 全长只有 6 个字节68 04 07 00 00 00逐个字节说0x68 是启动字符0x04 表示 APCI 长度因为后面没有 ASDU只有 4 字节控制域第三字节 0x07 是 STARTDT act 的标记后面三个字节固定填 0。从站如果同意会回一个 68 04 0B 00 00 00启动确认。之后主站才能发 I 帧。这个 6 字节的最小报文值得背下来调试时用它验证 TCP 通道是否通、从站是否在线比直接发总召唤更安全。如果连 0x0B 都收不到问题基本在 TCP 层或对端服务没起来后面查什么都是白搭。3. 用iec-master做主站K/W参数与T0~T3定时器怎么设置3.1 主站状态机连接、启动、总召唤、正常运行的四步iec-master 这类主站实现表面上是一堆报文收发本质上是一个有限状态机。状态依次是TCP 已连接、传输已启动、总召唤完成、正常遥测运行。每一步都有对应的报文驱动跳步就会出问题。第一步 TCP 连接连上从站的 2404 端口这一步有 T0 超时限制。第二步发 STARTDT act 等 STARTDT con这一步有 T1 超时限制。第三步发总召唤 C_IC_NA_1类型标识 100传送原因 6等从站回传送原因 7 的激活确认再收传送原因 20 的数据。第四步进入稳态周期收遥测、按需发遥控空闲时用 TESTFR 保活。实际项目里最常见的“连上 2404 端口但收不到数据”八成都卡在第二步或第三步。第二步的启动确认没完成从站不认你第三步总召唤没发成功从站不会主动推数据。很多从站一条常态遥测都不主动上送只有总召唤响应才给全量快照所以总召唤这步跳不得。3.2 K和W接收窗口与发送确认为什么默认是12和8K 和 W 是 104 规约流控的两个关键参数。K 表示发送方在未收到确认的情况下最多能连续发送多少个 I 帧W 表示接收方在收到多少个 I 帧之后必须回一个 S 帧确认。IEC 60870-5-104 标准建议 K 默认 12W 默认 8二者必须满足 W 小于等于 K。可以这样理解主站这边每发一帧发送计数加一每收到一个 S 帧或 I 帧用对方的接收序号把自己的未确认计数清零。如果连续发了 12 帧还没收到任何确认发送方就得停下来等防止序号溢出和接收方缓冲溢出。从站侧同理收到 8 帧 I 帧后必须发 S 帧否则主站会认为通道阻塞。调参的实际场景一般是这两种一是大容量对时场景主站一次要突袭上千个遥信点可以把 W 调大减少回确认的频率二是通道质量差、频繁出现序号重叠可以适当调小 K 值降低重传压力。但要注意K 和 W 是连接双方协商好的能力修改时要确认对端也支持同样的窗口否则会出现对端按自己窗口收帧、本端按自己的窗口计数两边序号错位。3.3 T0~T3定时器四个超时的默认值与调参场景104 规约里有四个定时器T0 到 T3分别管连接建立、确认等待、接收确认延迟和空闲保活。它们的默认值在不同实现里略有差异但通用的推荐值是 T0 为 30 秒T1 为 15 秒T2 为 10 秒T3 为 20 秒。T2 必须小于 T1这是规约明确要求的因为 T2 是接收方发确认的延迟上限如果 T2 大于 T1发送方会在等到确认之前就认为超时了。定时器作用触发时机默认值常见调整方向T0TCP 连接建立超时connect 到 accept30s跨网段连接调大到 60sT1发送后等待确认超时发 I/S/U 帧后15s通道抖动大调到 30sT2收到 I 帧后延迟确认收帧后起算10s收多发少时调小到 5sT3空闲时发送测试帧无收发超过 T320s保活要求高调到 10s我一般建议在调任何定时器之前先抓包看一轮完整的交互。如果主站反复在 T1 超时后断链先别急着调大 T1看是不是从站根本没回 S 帧如果从站回了但确认晚于 15 秒那才说明通道延迟真的大这时调 T1 才有意义。3.3.1 T2必须小于T1这一条最容易配错配错 T2 和 T1 的关系是新手主站调试里最常见的低级错误之一。有的实现里 T2、T1 是分开配置的两个文本框默认值就配反了结果主站发一堆 I 帧从站还没来得及回 S 帧主站这边 T1 先炸了反复断链。排查时看日志全是 “T1 timeout”很容易怀疑是网络问题其实只是参数配错。调参的原则是遵循 T2 T1 T3 这条链T3 大于 T1 是为了避免测试帧和确认超时互相打架在这个前提下按通道质量调整。内网直连可以把 T1 压到 5 秒让断链更快暴露跨运营商专线建议 T1 放到 20 秒以上给公网抖动留余量。3.4 Python最小主站骨架连接2404端口并完成启动和总召唤下面这段代码是我做原型验证时常用的最小骨架纯 socket 实现不依赖第三方规约库方便在隔离环境里复现抓包里的每一条报文。它完成的是状态机的前三步建立 TCP 连接、发送 STARTDT act、发送总召唤。import socket import struct import time def build_u_frame(cmd): # U帧长度固定4控制域4字节cmd对应STARTDT/TESTFR等 return bytes([0x68, 0x04]) bytes([cmd, 0x00, 0x00, 0x00]) def build_i_frame(send_seq, recv_seq, asdu): # I帧序号左移1位保留bit00表示I帧低字节在前 s (send_seq 1) 0x7FFF r (recv_seq 1) 0x7FFF ctrl struct.pack(BBBB, s 0xFF, (s 8) 0xFF, r 0xFF, (r 8) 0xFF) return bytes([0x68, len(asdu) 4]) ctrl asdu def build_interrogation_asdu(common_addr1): # 类型100总召唤VSQ1表示1个对象COT6激活IOA0QOI0站召唤 return bytes([0x64, 0x01, 0x06, 0x00, common_addr 0xFF, (common_addr 8) 0xFF, 0x00, 0x00, 0x00, 0x00]) sock socket.create_connection((192.168.1.20, 2404), timeout10) sock.sendall(build_u_frame(0x07)) # STARTDT act resp sock.recv(1024) assert resp bytes([0x68, 0x04, 0x0B, 0x00, 0x00, 0x00]), 启动确认异常 send_n 1 recv_n 0 asdu build_interrogation_asdu(common_addr1) sock.sendall(build_i_frame(send_n, recv_n, asdu)) # 总召唤代码里几个细节说清楚。STARTDT act 的 0x07 和确认的 0x0B 必须严格匹配我遇到有的从站实现会回 0x0B 但不是预期字节序这里断言能快速暴露。I 帧的序号左移一位后发送计数每次发 I 帧才加 1S 帧和 U 帧不占序号总召唤 ASDU 里公共地址按小端写入IOA 固定 3 字节且低字节在前这几个字节序错了从站直接丢弃。这个最小主站跑通后再往上面加收帧解析、心跳保活就是一个可用的调试工具。4. 104规约报文详解类型标识、传送原因与一个通用解析器4.1 类型标识与传送原因读懂报文的两个坐标104 报文里的 ASDU第一字节类型标识决定了这条报文在说什么第三第四字节的传送原因决定了这条报文是干嘛的。两个坐标一起看基本就能定位报文的语义。类型标识对应具体的数据类型比如单点遥信、双点遥信、短浮点遥测、遥控命令、总召唤传送原因则说明是主动上送、响应召唤、激活还是激活确认。实际调试最常用的一张对照表我列在下面都是 IEC 60870-5-101/104 标准里的固定编号不同厂家实现也遵循这套编号类型标识助记符方向说明1M_SP_NA_1从站到主站单点遥信3M_DP_NA_1从站到主站双点遥信9M_ME_NA_1从站到主站归一化遥测13M_ME_NC_1从站到主站短浮点遥测4字节IEEE75415M_IT_NA_1从站到主站累计量常见于电度30M_SP_TB_1从站到主站带时标单点遥信SOE45C_SC_NA_1主站到从站单点遥控50C_SE_NC_1主站到从站设点控制短浮点100C_IC_NA_1主站到从站总召唤103C_CS_NA_1主站到从站时钟同步传送原因也是固定编号最常用的是6 激活命令/召唤已发出、7 激活确认从站已受理、8 停止激活、9 停止激活确认、20 响应站召唤从站开始批量上送数据。看到 100 类型加传送原因 6是总召唤请求看到 1 类型加传送原因 20是对总召唤的遥信响应。4.2 逐字节拆一条总召唤确认应答从站收到主站总召唤后先回一条传送原因 7 的激活确认随后批量上送数据。下面是一条典型的从站数据帧三条单点遥信在同一个 ASDU 里连续编排68 10 02 00 02 00 01 83 14 00 01 00 0A 00 00 01 00 01帧头先拆0x68 是启动字符0x10 表示 APCI 加 ASDU 共 16 字节控制域 02 00 02 00前两个字节是发送序号 1后两个字节是接收序号 1这是一条 I 帧。再看 ASDU 这 12 字节。0x01 说明类型是单点遥信0x83 是可变结构限定词最高位是 1 表示连续编排低 7 位是 3 表示后面有 3 个信息体14 00 传送原因是 20响应总召唤01 00 公共地址是 10A 00 00 是起始信息体地址 10后面 01 00 01 分别是三个信息体的值1 表示合0 表示分。因为连续编排三个信息体的地址是 10、11、12不需要逐个写地址这是 104 压缩报文长度的主要手段。4.3 解析函数落地从裸字节到对象写主站解析不能用“看到 0x01 就知道是遥信”这种思路要把字节流变成结构化数据。下面这个解析函数覆盖了 I 帧、S 帧、U 帧的识别以及 ASDU 公共部分的提取def parse_apdu(buf): if buf[0] ! 0x68: raise ValueError(不是104报文) length buf[1] if len(buf) 2 length: raise ValueError(报文不完整) apdu buf[:2 length] ctrl apdu[2:6] if (ctrl[0] 0x01) 0: # I帧序号为15位右移1位还原 send_seq ((ctrl[1] 8) | ctrl[0]) 1 recv_seq ((ctrl[3] 8) | ctrl[2]) 1 return {frame: I, send: send_seq, recv: recv_seq, asdu: parse_asdu(apdu[6:])} if (ctrl[0] 0x03) 0x01: # S帧只有接收序号 recv_seq ((ctrl[3] 8) | ctrl[2]) 1 return {frame: S, recv: recv_seq} # U帧cmd值映射到语义 u_cmds {0x07: STARTDT act, 0x0B: STARTDT con, 0x43: TESTFR act, 0x83: TESTFR con} return {frame: U, cmd: u_cmds.get(ctrl[0], hex(ctrl[0]))} def parse_asdu(asdu): type_id asdu[0] vsq asdu[1] cot int.from_bytes(asdu[2:4], little) ca int.from_bytes(asdu[4:6], little) info_elem_count vsq 0x7F ioa_start int.from_bytes(asdu[6:9], little) return {type_id: type_id, vsq: vsq, cot: cot, common_addr: ca, count: info_elem_count, ioa_start: ioa_start, body: asdu[9:]}代码逻辑基于 3 字节信息体地址、2 字节公共地址的标准 104 配置。I 帧识别后把序号和 ASDU 分开S 帧只做确认计数U 帧映射成可读命令。要注意 VSQ 的最高位是连续的编排标志位低 7 位才是信息体数量连续编排时只需要读起始 IOA逐条递增即可非连续编排时每个信息体都带自己的 3 字节 IOA。这套解析逻辑写清楚后遥控下发和时钟同步只是在 type_id 和 body 上做文章框架不动。5. 电力104规约主站排错T3超时、序号错位与Wireshark定位5.1 三个最常见的交互故障与判断依据主站调试绕不开三类故障T3 超时断链、序号错位导致对端丢帧、总召唤发了没响应。这三类故障的表现接近都是“连接不正常、数据不来”但定位路径完全不同抓包后看几个关键特征就能区分。症状抓包特征根因方向周期性断链间隔稳定每过约 T3 秒发 TESTFR act 无确认对端忙或保活机制未实现发送后长时间无回应TESTFR 有回但 I 帧序号跳跃本端序号计算错误总召唤无响应发 100/COT6 后 100/COT7 一直不来公共地址或装置状态问题T3 超时的典型现场是连接稳定跑几分钟然后主站发 TESTFR act从站不回 TESTFR con主站等 T1 超时后断链重连。碰到这个先别调 T3去查从站是不是被另一个主站占用了连接。104 从站在 IEC 规范里允许双连接但很多实际装置只支持单连接第二个主站连上来时从站直接不响应测试帧。序号错位的典型表现是主站发了第 13 个 I 帧后对端失联因为 K 值默认 12超过未确认窗口对端直接丢弃。这类问题要先检查自己的发送序号是不是在重发的时候加了两次或者把 S 帧也计入了发送序号。对端回一个带接收序号的帧时用那个值校准本端的已确认计数比盲目调大 K 更稳。5.2 Wireshark看104过滤表达式与四层检查Wireshark 对 IEC 60870-5-104 有完整的解析器抓包后不需要手动数字节。过滤表达式用 tcp.port2404 或直接按协议名过滤 iec60870_104旧版本里叫 iec60870_5_104看本机版本选一个。打开解析结果后I 帧、S 帧、U 帧会被自动分开ASDU 里的类型标识、传送原因、公共地址、IOA 都会被拆成独立的字段这比盯着十六进制效率高一个数量级。我检查 104 交互时习惯按四层顺序看TCP 三次握手是否完成连接是否稳定在 ESTABLISHED。STARTDT act 和 con 是否成对出现有没有发送后没有确认。有没有总召唤请求和对应的激活确认以及随后的 COT20 批量数据。数据帧的序号是否连续S 帧确认间隔是否在 K 值窗口内。四层里任何一层断了问题就在那一层不要跨层去查。曾经有个现场前三层全通就是没有 COT20 的数据帧最后发现是主站总召唤的公共地址写成 0从站公共地址是 1地址不匹配从站直接丢弃总召唤。这种问题光看 TCP 连接是看不出来的。5.2.1 四层检查顺序按顺序看的好处是能快速缩小范围。TCP 层有问题后面三层不用看STARTDT 没确认不用查总召唤总召唤正常但没有数据再看是装置没配置还是地址不匹配。实际调试里我踩过的坑是跳过第二层直接查总召唤花半小时排查地址问题最后发现 STARTDT 确认包被防火墙丢了白白浪费时间。5.3 调参验证改T1和T2之前先确认这个前提调 T1、T2 参数有一个前提必须先确认当前生效的连接参数是本次运行配置的不是上一次遗留的。104 连接参数在连接建立时由双方协商主站改完配置要重连才生效。有的从站实现会把参数固化在启动文件里主站改了 T1从站没改两边对不上。验证方法是抓两次包对比。第一次抓包看调参前的超时时间点记录从发包到断链的间隔第二次抓包改参后重连看同样场景下的间隔是否按预期变化。如果两次一样先检查是不是连接没重来、配置没加载确认都在生效后再谈参数值合不合理。这一步能挡住一大批“改了半天没效果”的无效排查。6. 抓包逆向校验ASDU字节序新接入装置唯一要防的坑6.1 现象解析结果出现4.6e29这类数值新接一台从站调试遥测值解析出来全是 4.6e29 或者 -3.14e-41 这种明显不是电力量的数值十有八九是字节序的问题。104 报文里数值型数据默认低位在前但有些老装置、或者转发的中间网关会把浮点数的字节序搞成高位在前。更隐蔽的是浮点正常但信息体地址、公共地址的大小端不一致导致你解析出来点位全对不上。6.2 校验流程与代码用一个已知量测做锚点是拆字节序问题最可靠的办法。选一个你确定数值的量测点比如某线路电流理论上是 100.0A从抓包里把它的 4 字节测量值抠出来分别按大小端解一遍import struct def probe_float(raw4): for fmt, tag in [(f, 小端), (f, 大端)]: val struct.unpack(fmt, raw4)[0] print(tag, val) # 顺带校验3字节IOA和2字节公共地址 ioa_bytes raw4[:3] print(IOA小端:, int.from_bytes(ioa_bytes, little)) print(IOA大端:, int.from_bytes(ioa_bytes, big)) probe_float(bytes([0x9A, 0x99, 0xC8, 0x42]))跑完这段看哪个方向的解析结果接近实际值就能确定装置的字节序。这里有个技巧IOA 通常是小端所以 3 字节的 IOA 解析结果如果是类似 65536 这种规整的大数也值得警惕。我用这套方法判断过一台 101 转 104 的规约转换器104 侧浮点按小端发但内嵌的 IOA 按大端编导致点位全部错位用锚点验证一次就暴露了。确定字节序后把这份校验固化成一个启动自检函数每次接入新装置时先跑一遍比对已知量测点的解析值通过后再进正常轮询。这个习惯能把新装置接入的调试时间压缩到原来的三分之一也是区分老手和新手最明显的分水岭。本文还有配套的精品资源点击获取
返回列表