IEC104报文解析实战:从十六进制到业务语义的速查指南
发布时间:2026/10/22 13:17:37 锦皓数字建站

简介本资源是一份面向电力自动化系统开发工程师、SCADA/RTU调试人员及工业通信协议学习者的IEC 60870-5-104IEC104报文解析实战指南聚焦现场最常 encountered 的六类核心报文结构与字段语义。PDF文档虽仅22KB但内容高度凝练完整覆盖变化遥测、总召唤遥测、链路测试帧、主站确认帧、遥信变位/告警、SOE及遥控全流程报文逐帧拆解报文头如68 04 07 00 00 00、ASDU类型、可变结构限定词如0x09/0x03的最高位含义、信息体地址编排逻辑连续递增 vs 独立inf、传送原因码3变化上送20总召唤、公共地址与数据字节序低位在前等关键细节并附带真实十六进制报文实例与逐字段注释。资源为单文件PDF结构清晰、即开即用已有1324人学习下载是快速掌握IEC104协议解析逻辑、定位通信异常、验证子站响应行为的高价值参考材料。1. IEC104报文解析不是“看懂十六进制”而是让调试器真正吐出“哪个点变了、值是多少、什么时候变的”你手头刚拿到一份现场抓包的.pcap文件Wireshark 里全是68 xx xx xx xx xx开头的 TCP 流或者你在写子站模拟器发出去的遥信变位总被主站丢弃又或者某次总召唤后主站画面里遥测值全为 0但子站本地数据库明明有数据——这时候翻遍协议文档最痛的不是“找不到定义”而是“明明每个字节都对得上可逻辑就是不通”。这份《IEC104报文解析.pdf》不是教你怎么背协议号它是一份按真实调试场景反向拆解的报文字典把每一段十六进制字符串直接映射到“起始地址 0x4070 的第 3 个遥测”“公共地址 0x0B01 对应主站 A”“SOE 时间戳 0x0E354978 解析为 2000 年 1 月 1 日 14:53:18.808”这种可验证、可复现、可打断点的具体结论。它面向的是正在联调的嵌入式工程师、SCADA 系统集成商、电力自动化测试人员——你不需要从 OSI 七层模型开始复习只需要知道“当看到09 08开头的帧下一步该查信息体地址还是传送原因”。文中所有报文示例均来自真实设备交互非标准文档虚构且明确标注了字节序低位在前、地址递增规则、ASDU 类型与业务语义的绑定关系。如果你正卡在“为什么这个遥信变位没上送”或“总召响应里遥测值全错”这份资料就是你打开串口调试器前必须先扫一眼的“报文解码速查表”。2. 报文结构解剖从启动字符68H到 ASDU逐层剥开 IEC104 的“洋葱皮”IEC104 报文不是一串扁平字节而是一个严格分层的嵌套结构。理解它必须放弃“通读整帧”的想法转为“按层定位、逐段验证”的工程思维。核心四层启动字符 APDU 长度 控制域 ASDU。其中 ASDU应用服务数据单元是业务逻辑的载体也是解析难度最高的部分。下面以总召唤遥测报文68 40 18 00 04 00 09 91 14 00 01 0B 70 40 00 ...为例逐层拆解。2.1 启动字符与 APDU 长度确认这不是乱码而是合法 IEC104 帧所有 IEC104 报文以0x68十进制 104开头这是协议的“身份证”。紧随其后的是APDU 长度字段Length占 1 字节表示 APDU应用协议数据单元中除启动字符和长度字段外的字节数。例如68 40 ...中的0x40十进制 64意味着后续 64 字节构成完整 APDU。提示若 Wireshark 抓包显示68 00或68 FF基本可判定链路异常或设备未按规约组帧——0x00表示长度为 0非法0xFF超出常见帧长上限IEC104 规定最大 APDU 长度为 249 字节即0xF9。# 在 Linux 下用 tcpdump 抓取 IEC104 流量过滤启动字符 tcpdump -i eth0 tcp port 2404 and (tcp[20:1] 0x68) -w iec104.pcap代码说明tcp[20:1]表示 TCP 数据部分的第 20 字节跳过 20 字节 IPTCP 头 0x68精准匹配启动字符。此命令避免抓取大量无关流量直击有效报文。2.2 控制域解析识别主站/子站角色、帧类型与链路状态控制域Control Field位于启动字符和长度之后固定 4 字节是判断报文方向与功能的关键。其结构为[DIR, PRM, FCB/FCV, FUN, RES, ACD, DFC]各 1 位[发送序号][接收序号]。实际解析时我们更关注字节 1DIR/PRM/FCB/FCV和字节 2FUN。字节 10x07二进制00000111→ DIR0主站发、PRM1主站、FCB1帧计数位、FCV1帧计数有效字节 20x00→ FUN0I-格式帧含 ASDU字节 3 4发送序号0x0000接收序号0x0000对比链路测试帧68 04 43 00 00 00字节 10x4301000011→ DIR1子站发、PRM0子站、FCB/FCV0无序号字节 20x00→ FUN0I-帧字节 30x00、字节 40x00→ 发送/接收序号为 0符合测试帧特征参数说明DIRDirection位决定主从关系PRMPrimary位标识发起方。误设 DIR/PRM 是现场最常见握手失败原因——主站发帧却设DIR1子站直接丢弃。2.3 ASDU 结构ASDU 类型、可变结构限定词与传送原因的三位一体绑定ASDU 是业务数据的容器其结构为[类型标识] [可变结构限定词] [传送原因] [公共地址] [信息体地址] [信息体元素]。三者类型标识、可变结构限定词、传送原因必须协同解读单独看任一字段都会误判。以变化遥测报文68 3A 00 00 00 00 09 08 03 00 01 0B 01 40 00 45 00 00 ...为例0x09类型标识 遥测数据单点信息体3 字节值0x08可变结构限定词 00001000二进制最高位0→非连续地址模式后续每个遥测前带独立 3 字节地址0x0003传送原因 0x03低位在前→ 变化上送非总召、非周期而总召唤遥测68 40 ... 09 91 14 00 ...中0x09同为遥测数据0x9110010001最高位1→连续地址模式仅提供起始地址0x4070后续地址自动10x00140x14 20 → 召唤上送关键逻辑可变结构限定词的最高位是地址模式的开关。0表示“每个数据前带地址”适合变化遥信/遥控1表示“只给起始地址自动递增”适合总召、周期上送。这是解析时第一个必须检查的位否则地址解析必然错乱。2.4 公共地址与信息体地址区分“谁在说话”和“说哪个点”公共地址Common Address2 字节标识主站或子站的唯一 ID。如01 0B低位在前0x0B01 2817。该地址由系统配置确定主站与子站必须严格一致否则报文被当作非法源丢弃。信息体地址Information Object Address3 字节标识具体数据点。如遥测40 00 00低位在前0x000040 64即数据库中第 64 号遥测点。注意信息体地址的字节序是低位在前Little-Endian这是初学者最大陷阱。01 00 00不是 1而是0x000001 100 01 00才是0x000100 256。# Python 解析信息体地址3 字节低位在前 def parse_ioa(hex_bytes): # hex_bytes 为 bytes 类型如 b\x40\x00\x00 return int.from_bytes(hex_bytes, byteorderlittle) # 返回整数 64 # 示例 ioa_bytes bytes([0x40, 0x00, 0x00]) print(parse_ioa(ioa_bytes)) # 输出: 64代码说明byteorderlittle强制按低位在前解析。若误用big0x400000会解析为 4194304完全偏离实际点号。3. 遥测/遥信/遥控报文实战解析从十六进制到业务语义的映射表光懂结构不够必须建立“字节序列 ↔ 业务动作”的快速映射。本节聚焦三类最高频报文给出可直接用于调试的解析路径与验证方法。3.1 变化遥测报文如何从09 08帧中精准定位“第 5 个点的值”变化遥测报文68 3A 00 00 00 00 09 08 03 00 01 0B 01 40 00 45 00 00 02 40 00 CD FF 00 ...的解析流程如下确认 ASDU 头部09遥测、08非连续地址、03变化上送、0001公共地址低字节、0B高字节 →0x0B01提取首个信息体地址40 00 00→0x000040 64提取首个遥测值45 00 00→0x000045 69十进制定位第 5 个点因0x08指示非连续模式需跳过前 4 组地址值。每组占 6 字节3 字节地址 3 字节值故第 5 组起始位置 头部偏移 4×6 09 08 03 00 01 0B 017 字节 24 第 31 字节。第 31-33 字节0F 40 00→ 地址0x00400F 16399第 34-36 字节88 13 00→ 值0x001388 5000验证技巧用 Excel 列出所有地址与值检查是否符合现场点表。若第 5 个地址0x00400F对应“#5 主变油温”则值5000应为 50.00℃假设缩放系数为 0.01。3.2 遥信变位报文单点/双点的字节差异与状态解码遥信报文关键在ASDU 类型标识和信息体元素长度单点遥信类型0x01信息体元素 1 字节0x00或0x01双点遥信类型0x03信息体元素 1 字节0x00中间态,0x01分,0x02合,0x03无效报文68 15 F8 05 DA 00 01 03 03 00 01 0B 72 00 00 01 75 00 00 00 78 00 00 01解析0x01单点遥信0x03可变结构限定词非连续0x0003传送原因 变化上送0x0B01公共地址01 00 00首个信息体地址 0x000001点 100状态 0分闸75 00 00第二个地址 0x000075 11700状态 0分闸而双点报文68 15 F8 05 DA 00 03 03 03 00 01 0B 72 00 00 01 75 00 00 02 78 00 00 010x03双点遥信最后一个字节01状态 1分闸而非单点的0/1二值注意双点状态0x02表示“合闸”0x01表示“分闸”0x00表示“中间态”如断路器储能未满0x03表示“无效”。误将双点0x02当作单点0x02非法值会导致状态误判。3.3 遥控报文选择、执行、确认的三次握手与状态机校验遥控是典型的状态机交互必须按序验证三类报文报文类型主站发子站回关键字段选择命令68 0E 4A 01 88 09 2E 01 06 00 07 11 01 60 00 8268 0E 96 09 4C 01 2E 01 07 00 07 11 01 60 00 822E双点遥控,06激活,82合闸选择执行命令68 0E 4C 01 8C 09 2E 01 06 00 07 11 01 60 00 0268 0E 9A 09 4E 01 2E 01 07 00 07 11 01 60 00 0202合闸执行结束报文—68 0E 9C 09 4E 01 2E 01 0A 00 07 11 01 60 00 020A激活终止关键校验点传送原因一致性主站选0x06子站回0x07确认主站执0x06子站回0x07子站终0x0A。若子站回0x06说明未进入执行态。信息体地址匹配60 00低位在前0x0060 96必须与遥控点表中“#96 断路器”一致。遥控码含义双点遥控0x82 选择合闸高位1表示选择低两位10表示合0x02 执行合闸高位0表示执行低两位10表示合。# 快速校验遥控报文序列Linux bash # 提取所有遥控相关报文ASDU 类型 0x2E 或 0x2D tshark -r iec104.pcap -Y iec104.asdu_type 0x2e || iec104.asdu_type 0x2d -T fields -e frame.number -e iec104.asdu_type -e iec104.cause_of_transmission -e iec104.ioa -e iec104.value代码说明tshark直接解析 pcap输出帧号、ASDU 类型、传送原因、信息体地址、值。可导出 CSV 用 Excel 排序验证0x06→0x07→0x06→0x07→0x0A是否成对出现。4. 常见问题排查5 条血泪经验总结的“报文解析翻车现场”解析 IEC104 报文时90% 的问题不在于协议本身而在于环境、工具或认知偏差。以下是我在多个现场联调中反复踩过的坑按“现象→原因→解决”整理拒绝玄学。4.1 现象Wireshark 显示IEC104协议但 ASDU 解析为空或乱码原因Wireshark 默认使用IEC 60870-5-104解析器但该解析器依赖 TCP 端口识别默认 2404。若主站/子站使用非标端口如 8080Wireshark 无法关联到 IEC104 解析器导致仅显示原始 TCP 流。解决强制指定解码协议。右键报文 →Decode As...→Transport标签页 → 选择对应 TCP 流 →Current列下拉选IEC104→OK。或永久设置Edit → Preferences → Protocols → TCP → Add填入自定义端口如8080, iec104。4.2 现象总召唤报文里遥测值全为00 00 00但子站本地数据正常原因可变结构限定词最高位为1连续地址模式但子站错误地将0x91解析为0x19字节序颠倒导致起始地址计算错误。例如70 40 00正确被读作00 40 700x704000远超点表范围后续值全为 0。解决用十六进制编辑器如 HxD打开 pcap定位 ASDU 起始位置人工核对可变结构限定词字节及后续地址字节顺序。确认70 40 00是0x00004070而非0x704000。4.3 现象遥控执行后子站返回0x0A激活终止但设备无动作原因主站下发的信息体地址与子站遥控点表不匹配。例如主站发60 000x0060但子站点表中“#96 断路器”对应地址为0x0000603 字节而主站只发了 2 字节子站补零后为0x000060看似一致。但若子站严格校验地址长度2 字节地址会被判为非法。解决检查主站配置确保遥控点地址按3 字节填充。0x0060应发送为60 00 00低位在前而非60 00。4.4 现象SOE 时间戳解析为2000 年 1 月 1 日但实际是2023 年原因IEC104 SOE 时间戳的“年”字段是模 100 的压缩年份0x00 2000,0x17 2023而非绝对年份。若解析脚本直接取0x00作为 0 年结果必错。解决时间戳解析必须加基准年。标准做法year 2000 (raw_year 0xFF)。例如0x17→2000 23 2023。同时注意0x64100应视为2100但实际设备极少用到。4.5 现象链路测试帧68 04 43 00 00 00发出后无响应或响应超时原因控制域字节 1的DIR位设置错误。主站发测试帧应设DIR0主站发但配置错误设为DIR1子站发子站收到后认为是非法源静默丢弃。解决抓包确认测试帧的DIR位。0x4301000011DIR1高位说明是子站发的测试帧。主站应发0x0300000011DIR0。修改主站配置重发68 04 03 00 00 00。5. 进阶技巧用 Python 构建轻量级报文解析器实现“粘贴即解析”手动查表效率低尤其面对海量报文。我一般会写一个 50 行以内的 Python 脚本支持从剪贴板粘贴十六进制字符串秒级输出结构化解析。核心是封装 ASDU 解析逻辑避免重复造轮子。5.1 解析器设计模块化处理 ASDU 各字段脚本分为三层输入层接收空格分隔的十六进制字符串如68 3A 00 00 00 00 09 08 03 00解析层按 IEC104 结构切片调用专用函数解析各字段输出层格式化打印高亮关键业务字段如“遥测值69”# iec104_parser.py import sys def parse_asdu_type(type_id): mapping {0x01: 单点遥信, 0x03: 双点遥信, 0x09: 遥测数据, 0x2D: 单点遥控, 0x2E: 双点遥控} return mapping.get(type_id, f未知类型 0x{type_id:02X}) def parse_cause(cause_bytes): # 传送原因2 字节低位在前 cause int.from_bytes(cause_bytes, little) causes {1: 自发, 3: 变化上送, 20: 召唤上送, 6: 激活, 7: 激活确认, 10: 激活终止} return causes.get(cause, f未知原因 {cause}) def parse_ioa(ioa_bytes): return int.from_bytes(ioa_bytes, little) def parse_value(value_bytes, type_id): if type_id in [0x01, 0x03]: # 遥信1 字节 return value_bytes[0] elif type_id 0x09: # 遥测3 字节 return int.from_bytes(value_bytes, little) else: return N/A def main(): if len(sys.argv) ! 2: print(用法: python iec104_parser.py \68 3A 00 00 00 00 09 08 03 00 01 0B 01 40 00 45 00 00\) return hex_str sys.argv[1] try: bytes_list [int(x, 16) for x in hex_str.split()] if len(bytes_list) 10: print(报文过短至少需 10 字节) return # 提取 ASDU 部分跳过启动字符、长度、控制域共 6 字节 asdu_start 6 if bytes_list[0] ! 0x68: print(警告非 IEC104 启动字符) type_id bytes_list[asdu_start] vsq bytes_list[asdu_start 1] # 可变结构限定词 cause_bytes bytes_list[asdu_start 2: asdu_start 4] common_addr bytes_list[asdu_start 4: asdu_start 6] ioa_start asdu_start 6 print(fASDU 类型: {parse_asdu_type(type_id)} (0x{type_id:02X})) print(f传送原因: {parse_cause(cause_bytes)} (0x{int.from_bytes(cause_bytes, little):02X})) print(f公共地址: 0x{int.from_bytes(common_addr, little):04X}) # 解析首个信息体简化版仅处理非连续模式 if vsq 0x80 0: # 最高位为 0非连续 ioa_bytes bytes_list[ioa_start: ioa_start 3] value_bytes bytes_list[ioa_start 3: ioa_start 6] ioa parse_ioa(ioa_bytes) value parse_value(value_bytes, type_id) print(f信息体地址: {ioa} (0x{ioa:06X})) print(f遥测值: {value}) except Exception as e: print(f解析错误: {e}) if __name__ __main__: main()5.2 使用流程与参数定制保存脚本将上述代码存为iec104_parser.py运行解析python iec104_parser.py 68 3A 00 00 00 00 09 08 03 00 01 0B 01 40 00 45 00 00输出ASDU 类型: 遥测数据 (0x09) 传送原因: 变化上送 (0x03) 公共地址: 0x0B01 信息体地址: 64 (0x000040) 遥测值: 69参数扩展如需支持连续地址模式只需在if vsq 0x80 0:分支外添加else:分支按ioa_start提取起始地址再根据vsq 0x7F低 7 位获取数据个数循环解析。从那以后我每次遇到新报文都先复制到文本编辑器删掉空格粘贴进命令行跑一遍python iec104_parser.py。5 秒内就能确认“是不是遥测”“值对不对”“地址有没有溢出”比翻 PDF 快 10 倍。这个脚本没有依赖库Windows/Linux/macOS 全平台可用连离线环境都能跑。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。