ARTICLE DETAIL

资讯详情

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

IEC-104报文结构详解:APCI与ASDU解析及调试实战

IEC-104报文结构详解:APCI与ASDU解析及调试实战 简介这份资料面向电力系统自动化、配电终端及工控通信方向的开发者与调试人员聚焦IEC-104规约常用报文的逐字节解析。内容围绕启动字符、APDU长度、控制域、类型标识、传送原因、公共地址与信息对象地址等关键字段展开帮助读者快速理解遥测、遥信、遥控、总召唤及对时等典型报文的组成规则解决开发与现场调试中报文看不懂、定位难的问题。资源包共11个文件以PDF文档与HTML网页为主辅以JavaScript脚本、字体图标及说明文本压缩后约838KB可直接打开PDF阅读也可通过index.html在浏览器中浏览便于随时查阅。目前已有8257人学习下载适合需要系统掌握104报文结构、提升协议解析与排错效率的初、中级技术人员参考。1. 从一条 104 报文说起为什么它值得单独拆开看很多做电力自动化的工程师第一次抓 104 报文时都会愣一下明明叫“规约”抓出来的却是一串68 0E 00 00 00 00 64 01 06 00 01 00 00 00 00 14这样的十六进制。它既不像 Modbus 那样一眼能看出功能码也不像普通 TCP 那样有可读的 HTTP 头。原因在于 IEC-104 是“应用层规约 TCP 承载”的组合体报文本身被拆成了 APCI 和 ASDU 两层前者管链路后者管业务。这套规约在国内变电站、配电自动化、新能源场站里用得极广主站和 RTU、测控装置、保护装置之间的遥测、遥信、遥控、遥调基本都跑在它上面。标题里说的“常用报文详解”落到实际工作就是三件事能看懂抓包里的每一段字节、能判断链路为什么起不来、能定位遥测值为什么对不上。适合刚接触 104 调试的新人也适合已经会调但没系统梳理过报文结构的老手。2. IEC-104 报文结构拆解APCI 与 ASDU 到底怎么分2.1 APCI 六个字节固定头启动字符、长度与四种控制域IEC-104 的每一帧都以0x68开头这是启动字符抓包时看到它就说明这是一条 104 报文。紧接着一个字节是 APDU 长度注意它统计的是“控制域 4 字节 ASDU 长度”不包含启动字符和长度字节本身。所以一条最短的链路报文长度是 4一条带单点遥信的报文长度通常是 14 左右。控制域 4 个字节决定了这帧报文是干什么的常见有四种格式控制域首字节报文类型用途0x01 / 0x03I 格式带 ASDU 的信息传输遥测遥信遥控都走它0x07S 格式确认收到对方的 I 帧只回计数0x0BU 格式 STARTDT启动数据传输0x43U 格式 STOPDT停止数据传输0x83U 格式 TESTFR链路测试判断方法很简单看控制域第一个字节的最低位。最低位为 0 是 I 格式为 1 且次低位为 0 是 S 格式两个低位都为 1 是 U 格式。这个规则在写解析脚本时是第一个要落地的判断。2.2 ASDU 结构类型标识、可变结构限定词与传送原因ASDU 才是业务本体它的头部固定 6 个字节顺序是类型标识 TI、可变结构限定词 VSQ、传送原因 COT2 字节、公共地址 CA2 字节之后才是信息对象地址和具体数据。类型标识决定这条报文是遥测还是遥信。常用的几个值0x01单点遥信、0x03双点遥信、0x09归一化遥测、0x0B标度化遥测、0x0D短浮点遥测、0x2D单点遥信带时标、0x64总召唤、0x67时钟同步、0x2E双点遥信带 CP56Time2a 时标。可变结构限定词这个字节容易被忽略它高位的 SQ 位表示“信息对象是否连续寻址”低 7 位表示“本帧包含多少个信息对象”。SQ0 时每个对象都带自己的信息对象地址SQ1 时只写第一个地址后续地址依次加一。总召唤响应里 SQ 经常是 1因为遥测点号是连续的。传送原因 2 字节里低字节是原因值高字节是源发站地址。常见原因值0x03突发、0x05被请求、0x06激活、0x07激活确认、0x0A激活终止、0x14响应站召唤。调试时如果看到原因值对不上比如总召唤回的是突发而不是被请求基本可以判断装置侧配置有问题。2.3 用 Python 把一条 104 报文拆成字段光看表记不住写个最小解析脚本最快。下面这段代码只处理 I 格式帧把 APCI 和 ASDU 头部拆出来打印# iec104_parse.py # 解析单条 IEC-104 I 格式报文输入为十六进制字符串 def parse_104(hex_str): data bytes.fromhex(hex_str.replace( , )) if data[0] ! 0x68: return 非 104 报文启动字符不是 0x68 apdu_len data[1] ctrl data[2:6] # 控制域最低位为 0 表示 I 格式 if ctrl[0] 0x01 ! 0: return 非 I 格式帧控制域首字节0x%02X % ctrl[0] # 发送序号 15 位接收序号 15 位 tx ((ctrl[0] | (ctrl[1] 8)) 1) 0x7FFF rx ((ctrl[2] | (ctrl[3] 8)) 1) 0x7FFF asdu data[6:6 apdu_len - 4] ti asdu[0] # 类型标识 vsq asdu[1] # 可变结构限定词 sq (vsq 7) 0x01 # 是否连续寻址 num vsq 0x7F # 信息对象个数 cot asdu[2] | (asdu[3] 8) # 传送原因 ca asdu[4] | (asdu[5] 8) # 公共地址 return { APDU长度: apdu_len, 发送序号: tx, 接收序号: rx, 类型标识: hex(ti), SQ: sq, 对象个数: num, 传送原因: hex(cot 0xFF), 源发站: cot 8, 公共地址: ca, ASDU原始: asdu.hex( ) } if __name__ __main__: # 一条典型的单点遥信 I 帧 print(parse_104(68 0E 00 00 00 00 01 01 14 00 01 00 01 00 00 00))逻辑上先校验启动字符再判断控制域格式然后按 15 位右移取序号。ASDU 部分按固定偏移取头部字段apdu_len - 4是因为长度里含了 4 字节控制域。参数上要注意cot 0xFF取原因值cot 8取源发站地址这两个别搞反否则总召唤的源发站会显示成原因值。实际调试时把抓到的十六进制串丢进去几秒钟就能确认类型标识和传送原因是否符合预期。3. 常用报文类型实战总召唤、遥测、遥信、遥控怎么读3.1 总召唤交互全过程与报文时序总召唤是主站和装置建立数据同步的标准动作交互顺序固定主站发总召唤激活TI0x64COT0x06装置回激活确认COT0x07然后装置用若干帧 I 格式把遥测遥信送上来COT0x14 响应站召唤最后装置发激活终止COT0x0A。如果中间任何一步缺失主站侧就会显示“总召超时”。抓包时判断总召是否正常看三个点激活确认有没有在几百毫秒内回来、数据帧的公共地址是否和主站配置一致、激活终止有没有出现。常见故障是装置回了激活确认但数据帧一直不来多半是装置侧点表没配或者信息对象地址越界。3.2 遥测报文归一化、标度化、短浮点三种值怎么换算遥测是 104 里最容易算错的部分因为同一个物理量可能用三种编码传。归一化值TI0x09是 2 字节有符号整数范围 -32768 到 32767对应 -1 到 1 之间的比例实际值 原始值 / 32768 × 量程。标度化值TI0x0B是 2 字节有符号整数直接就是工程量不用再乘比例。短浮点TI0x0D是 4 字节 IEEE 754直接读就行。下面这段代码演示三种遥测的取值import struct def decode_measured(ti, payload): # payload 为信息对象地址之后的数值字节 if ti 0x09: # 归一化 raw struct.unpack(h, payload[:2])[0] return raw / 32768.0 # 返回 -1~1 的比例 elif ti 0x0B: # 标度化 return struct.unpack(h, payload[:2])[0] elif ti 0x0D: # 短浮点 return struct.unpack(f, payload[:4])[0] return None # 归一化原始值 0x4000 16384比例约 0.5 print(decode_measured(0x09, bytes.fromhex(4000))) # 短浮点 0x41480000 约等于 12.5 print(decode_measured(0x0D, bytes.fromhex(41480000)))参数说明h是大端有符号短整型104 全部用大端f是大端单精度浮点。归一化值一定要确认量程系数很多现场遥测偏差一倍就是量程配错。标度化值虽然直观但要注意装置侧是否做了系数转换否则主站显示的是原始码值。3.3 遥信与遥控单点双点区别、选择执行与返校遥信单点TI0x01用 1 字节表示分合双点TI0x03用 2 位表示分、合、中间态、无效四种状态。双点遥信在断路器位置判断上更可靠因为能识别中间态。带时标的遥信TI0x2D、0x2E会多 7 字节 CP56Time2a 时间格式是毫秒低字节、毫秒高字节、分、时、日、月、年解析时注意年是两位。遥控走的是“选择-执行”两阶段主站先发选择COT0x06类型 0x2D 单点遥控或 0x2E 双点遥控装置回选择确认COT0x07主站再发执行COT0x06装置回执行确认COT0x07并动作。如果选择阶段就失败说明遥控点号或公共地址不对如果执行阶段失败多半是装置侧五防闭锁或遥控压板没投。调试遥控时一定要先确认选择确认里的信息对象地址和主站下发的一致。4. 104 链路调试与排错从抓包到定位的完整路径4.1 用 tcpdump 和 Wireshark 抓 104 报文104 默认端口是 2404抓包命令很直接# 抓取 2404 端口报文并保存供 Wireshark 分析 tcpdump -i eth0 -nn port 2404 -w iec104.pcap # 实时看十六进制确认链路是否在交互 tcpdump -i eth0 -nn -X port 2404参数说明-i指定网卡-nn不做端口和地址解析-X打印十六进制和 ASCII。Wireshark 自带 IEC 60870-5-104 解析器但默认可能把 2404 当普通 TCP需要在 Decode As 里手动指定为 IEC104。解析出来后能直接看到类型标识、传送原因、信息对象地址比手工拆字节快得多。4.2 链路起不来的四类典型原因第一类是 TCP 连不上表现为三次握手失败检查 IP、端口、防火墙。第二类是 TCP 通了但 STARTDT 没交互抓包能看到主站发68 04 07 00 00 00但装置不回68 04 0B 00 00 00通常是装置侧 104 服务没启动或链路地址配错。第三类是 STARTDT 成功但总召无响应看公共地址和 ASDU 地址是否匹配。第四类是数据能上来但序号乱I 帧的发送和接收序号不连续说明有丢包或装置侧序号管理异常。提示序号不连续时先看是不是网络抖动导致丢包再排查装置侧是否有多主站连接抢占。4.3 常见异常报文与序号回绕处理104 的发送序号和接收序号都是 15 位范围 0 到 32767超过后回绕到 0。解析脚本里做序号连续性判断时不能简单用后一帧减前一帧等于 1要处理回绕如果当前序号小于前一帧且差值接近 32767说明发生了回绕。另外 S 帧只确认接收序号不携带数据如果长时间只看到 S 帧没有 I 帧说明对端没有数据要发链路是空闲而非断开。异常报文里还有一类是 TESTFR主站或装置在链路空闲时会发测试帧保活收到后要回确认。如果测试帧一直没被确认链路会被判定为断开。调试长连接场景时这个保活机制要确认双方都开启。5. 进阶技巧把 104 解析脚本做成可复用的诊断工具前面几段代码都是单条解析实际排错时更需要的是一边抓包一边自动统计。一个实用做法是把解析逻辑挂到 pcap 文件上逐帧输出类型标识、传送原因、信息对象地址和数值最后汇总各类型报文数量。这样一眼就能看出总召有没有走完、遥测帧有多少、有没有异常原因值。具体可以这样组织先用 scapy 或 dpkt 读 pcap过滤出 2404 端口的 TCP 载荷按0x68切分 APDU再调用前面的解析函数。统计维度建议至少包含I/S/U 帧数量、各类型标识计数、各传送原因计数、公共地址分布。如果发现某个公共地址的报文突然消失基本就是那台装置掉线了。另一个技巧是处理粘包。TCP 是流式的一次 recv 可能拿到半条报文或多条报文解析时必须先读长度字节再切分不能假设一次读到的就是完整 APDU。判断方法是缓冲区里如果剩余字节数小于长度字节声明的 APDU 长度加 2就继续等下一批数据。这个细节在写实时解析工具时最容易踩坑抓包文件里看不出来但接实时流就会暴露。最后给一个校验思路把解析出来的遥测值和主站画面显示值对一遍如果偏差固定倍数查量程如果偏差随机查时标和刷新周期如果某些点一直不变查信息对象地址是否和点表一致。104 的排错说到底就是字节、地址、原因值三样东西对齐对齐了链路就通数据就准。本文还有配套的精品资源点击获取
返回列表