资讯详情

资讯详情

南瑞IEC 101规约报文解析与调试实战:从RTU101.rar到Python解析工具

简介这份资源面向电力自动化、远动通信方向的开发者与运维人员聚焦IEC 101规约IEC 60870-5系列在RTU与主站通信中的报文解析与实现尤其涉及南瑞设备的101规约应用。包内共2个文件以1个cpp源码和1个txt说明文档为主压缩包约13KB体量轻便便于直接阅读与二次开发。源码可用于对照理解101报文的帧头、控制域、地址域、信息域与帧尾结构以及遥信变位20报文、遥测数据上报等典型交互逻辑txt文档则提供配套说明与参考信息帮助快速定位关键实现。已有789人学习下载说明该主题在电力通信调试与协议学习中具有一定关注度。对于需要排查南瑞101设备通信问题、梳理报文收发流程或搭建协议解析原型的读者这份小而实用的代码与文档组合可作为入门与排错的参考素材。1. 南瑞101规约报文解析从RTU101.rar里翻出可复现的调试路径手上拿到一个叫rtu101.rar的压缩包里面是南瑞的 IEC 101 规约实现配套还有一堆抓包报文这种场景在变电站自动化调试里太常见了。IEC 101 规约也叫 IEC 60870-5-101是国内电力系统运动通信的底层标准之一南瑞的 RTU 设备大量使用它做串口主从通信。问题在于规约文档厚得像砖头报文十六进制看着像天书现场调试时链路不通、总召唤没响应、遥测值跳变往往只能靠经验和抓包工具硬扛。这篇笔记面向的是需要对接南瑞 101 规约的嵌入式工程师、自动化调试人员和协议开发新手目标是把「报文长什么样、怎么解析、参数怎么配、坑在哪」这条链路走通让你拿到rtu101.rar这类资料后能自己拆出可用的调试方法而不是对着文档干瞪眼。2. IEC 101 规约的帧结构与南瑞实现的报文特征2.1 三种帧格式固定帧、可变帧、单字符IEC 101 规约在链路层定义了三种帧格式这是解析任何一条报文的第一道门槛。固定帧长 5 字节用于链路控制如请求链路状态、复位链路可变帧长度不固定承载应用层数据如总召唤、遥测上送单字符帧就是一个 0xE5用于确认。南瑞的 RTU 实现严格遵循这套格式但在具体字段填充上有自己的习惯比如链路地址的默认值和信息体地址的起始偏移这些在rtu101.rar的配置文件中通常能找到线索。固定帧的结构是启动字符 0x10 控制域 1 字节 链路地址 1 字节 校验和 1 字节 结束字符 0x16。可变帧则是启动字符 0x68 长度域 1 字节 长度域重复 1 字节 启动字符 0x68 控制域 1 字节 链路地址 1 字节 应用层数据 校验和 结束字符 0x16。校验和的计算范围是从控制域第一个字节到应用层数据最后一个字节的算术和取低 8 位。很多新手第一次解析时会把长度域算错导致后面所有字段偏移全乱。南瑞的 101 实现里控制域的功能码定义和标准一致但发送序号和接收序号的翻转逻辑在调试中容易让人迷惑。控制域的低 4 位是功能码高 4 位在信息传输格式中拆分为发送序号和接收序号。I ㊣ 格式帧用于编号的信息传输S 格式帧用于编号的监视功能U 格式帧用于未编号的控制功能。南瑞设备在链路建立阶段通常先发 U 格式的请求链路状态帧收到确认后再进入 I 格式的数据传输。2.2 应用层数据单元ASDU 的类型标识与信息体ASDU 是应用服务数据单元是可变帧里真正承载业务数据的部分。它的结构是类型标识 1 字节 可变结构限定词 1 字节 传送原因 2 字节 公共地址 2 字节 信息体地址 3 字节 信息体 时标可选。类型标识决定了这条报文是干什么的比如 0x64 是总召唤0x01 是单点遥信0x09 是归一化遥测0x0B 是标度化遥测0x0D 是浮点遥测。可变结构限定词的最高位表示信息体地址是否连续低 7 位表示信息体个数。这个字节是解析时最容易翻车的地方如果最高位是 1表示后续信息体地址连续只需要解析第一个地址然后递增如果是 0表示每个信息体都带独立地址需要逐个读取。南瑞的 RTU 在遥测上送时通常使用连续地址模式但在遥信变位上报时可能使用离散模式具体取决于设备配置。传送原因占 2 字节低字节是原因码高字节是源发站地址。常见原因码0x03 是突发0x05 是请求或被请求0x06 是激活0x07 是激活确认0x0A 是激活终止0x14 是响应站召唤。公共地址在 101 规约里通常对应 RTU 的站地址南瑞设备默认可能是 1 或 255需要和主站配置一致。信息体地址是 3 字节小端序遥测通常从 0x4001 开始遥信从 0x0001 开始电度从 0x6401 开始这些偏移量在南瑞的规约点表里有明确定义。2.3 南瑞 RTU101 的报文抓取与初步识别拿到rtu101.rar后第一步不是急着看代码而是把里面的报文样本文件找出来。常见的格式是.txt或.log里面是十六进制字符串可能带时间戳。用串口调试助手或专门的 101 规约分析工具打开先看有没有 0x68 或 0x10 开头的行。南瑞的报文样本通常会标注方向比如「主站→RTU」和「RTU→主站」这对理解交互流程很关键。识别一条报文时先看第一个字节。如果是 0x68就是可变帧接着读第二个字节得到长度然后跳过两个字节长度重复和第二个 0x68从第五个字节开始是控制域。如果是 0x10就是固定帧直接读控制域和链路地址。如果是 0xE5就是单字符确认。这个判断逻辑用几行 Python 就能实现后面会给出代码。南瑞的 101 实现里有一个细节链路地址在固定帧和可变帧里都是 1 字节但有些老版本设备会把它扩展成 2 字节这会导致按标准解析时校验和对不上。遇到校验和错误时先确认链路地址长度再检查校验和计算范围是否包含了启动字符和长度域——标准规定校验和不包含启动字符、长度域和结束字符只算控制域到应用层数据。3. 用 Python 解析 101 报文从十六进制到结构化数据3.1 搭建最小解析环境与报文输入不需要复杂的框架一个 Python 3 环境加标准库就够了。我一般会建一个parse_101.py把报文样本作为字符串列表硬编码进去方便反复调试。如果你有rtu101.rar里的实际抓包文件可以用open()读取后按行处理。关键是要把十六进制字符串转成bytes对象注意去掉空格和换行。# parse_101.py # 最小 101 报文解析环境输入为十六进制字符串 import struct # 示例报文南瑞 RTU 总召唤激活帧可变帧 # 68 09 09 68 73 01 64 01 06 00 01 00 00 00 14 16 raw_hex 68 09 09 68 73 01 64 01 06 00 01 00 00 00 14 16 def hex_to_bytes(hex_str): 去掉空格转成 bytes clean hex_str.replace( , ).replace(\n, ) return bytes.fromhex(clean) frame hex_to_bytes(raw_hex) print(f帧总长度: {len(frame)} 字节) print(f原始字节: {frame.hex( ).upper()})这段代码的输出会告诉你帧的实际长度和十六进制表示。注意bytes.fromhex()要求字符串长度是偶数如果抓包数据里有奇数个字符需要先补零或检查是否漏读。参数说明raw_hex替换成你自己的报文hex_to_bytes里的replace链可以扩展处理制表符等空白字符。3.2 帧类型判断与校验和验证拿到bytes后先判断帧类型。固定帧以 0x10 开头可变帧以 0x68 开头单字符帧就是 0xE5。判断完类型后可变帧需要验证校验和。校验和的计算范围是从控制域第 5 个字节索引 4到应用层数据最后一个字节倒数第 3 个字节索引 -3不包含结束字符 0x16。def check_frame(frame): 判断帧类型并验证校验和 if len(frame) 1 and frame[0] 0xE5: return 单字符帧, True if frame[0] 0x10: # 固定帧10 控制域 链路地址 校验和 16 if len(frame) ! 5: return 固定帧长度错误, False calc_sum sum(frame[1:3]) 0xFF return 固定帧, calc_sum frame[3] if frame[0] 0x68: # 可变帧68 长度 长度 68 控制域 链路地址 ASDU... 校验和 16 if len(frame) 6: return 可变帧长度不足, False length frame[1] if frame[2] ! length or frame[3] ! 0x68: return 可变帧长度域或启动字符错误, False # 校验和范围索引 4 到 4length-1即倒数第3个字节之前 calc_sum sum(frame[4:4length]) 0xFF recv_sum frame[4length] return 可变帧, calc_sum recv_sum return 未知帧类型, False frame_type, checksum_ok check_frame(frame) print(f帧类型: {frame_type}, 校验和: {通过 if checksum_ok else 失败})逻辑说明固定帧的校验和只算控制域和链路地址两个字节可变帧的校验和算控制域开始、长度为length的所有字节。参数length是长度域的值表示从控制域到应用层数据最后一个字节的字节数。如果校验和不通过先检查报文是否完整再检查长度域是否读对。南瑞设备在某些版本里长度域可能包含校验和本身这是血泪经验——遇到校验和总是差一个字节时试试把长度域加 1 再算。3.3 解析控制域与 ASDU 核心字段校验通过后开始拆控制域和 ASDU。控制域的格式取决于帧是 I 格式、S 格式还是 U 格式。判断方法看控制域最低两位。如果最低位是 0是 I 格式如果最低两位是 01是 S 格式如果最低两位是 11是 U 格式。I 格式里控制域的第 1 字节低 7 位是发送序号第 2 字节低 7 位是接收序号最高位分别是两个序号的最高位。def parse_control(ctrl_bytes): 解析控制域返回格式和序号 ctrl ctrl_bytes[0] | (ctrl_bytes[1] 8) if (ctrl 0x01) 0: # I 格式 tx ((ctrl 1) 0x7F) | ((ctrl 8) 0x80) rx ((ctrl 9) 0x7F) | ((ctrl 8) 0x80) # 简化处理实际需按位拆 return I, tx, rx elif (ctrl 0x03) 0x01: return S, None, None elif (ctrl 0x03) 0x03: return U, None, None return 未知, None, None # 对示例报文控制域在索引 4 和 5 ctrl_fmt, tx_seq, rx_seq parse_control(frame[4:6]) print(f控制域格式: {ctrl_fmt}, 发送序号: {tx_seq}, 接收序号: {rx_seq})ASDU 从控制域和链路地址之后开始。对于可变帧链路地址在索引 6ASDU 从索引 7 开始。类型标识是第一个字节可变结构限定词是第二个字节传送原因从第三个字节开始占 2 字节公共地址占 2 字节信息体地址占 3 字节。解析时注意小端序传送原因、公共地址、信息体地址都是低字节在前。def parse_asdu(asdu_bytes): 解析 ASDU 头部 type_id asdu_bytes[0] vsq asdu_bytes[1] cot asdu_bytes[2] | (asdu_bytes[3] 8) ca asdu_bytes[4] | (asdu_bytes[5] 8) ioa asdu_bytes[6] | (asdu_bytes[7] 8) | (asdu_bytes[8] 16) num_info vsq 0x7F is_continuous (vsq 0x80) ! 0 return { 类型标识: hex(type_id), 信息体个数: num_info, 地址连续: is_continuous, 传送原因: hex(cot), 公共地址: ca, 信息体地址: hex(ioa) } # 示例报文中 ASDU 从索引 7 开始 asdu_info parse_asdu(frame[7:]) for k, v in asdu_info.items(): print(f{k}: {v})参数说明vsq的最高位是地址连续标志低 7 位是信息体个数。cot的低字节是原因码高字节是源发站地址。ioa是 3 字节小端序南瑞的遥测点表里这个地址通常对应具体的测量量。如果num_info大于 1 且is_continuous为真后续信息体地址依次加 1否则需要按每个信息体单独解析地址。4. 南瑞 101 规约调试避坑链路、参数与报文异常排查4.1 链路始终建不起来先查物理层再查链路地址现象主站发请求链路状态帧RTU 无响应串口调试助手上看不到任何回码。原因通常有三层物理层接线错误RS-232 的 2、3 脚交叉RS-485 的 A、B 反接、串口参数不匹配波特率、数据位、停止位、校验位、链路地址不一致。解决步骤先用万用表确认 RS-485 的 A 对 B 有差分电压再用示波器看 RTU 的发送脚有没有波形。如果物理层正常检查主站和 RTU 的链路地址是否一致南瑞设备默认链路地址可能是 1但有些工程会改成 255。在rtu101.rar的配置文件里搜索link_addr或链路地址关键字确认实际值。4.2 总召唤无响应或响应超时公共地址与传送原因要对齐现象链路建立后主站发总召唤激活帧类型标识 0x64传送原因 0x06RTU 回了激活确认0x640x07但后续的总召唤数据帧一直不来。原因可能是公共地址不匹配。总召唤帧里的公共地址必须和 RTU 配置的站地址一致南瑞设备有些版本要求公共地址等于链路地址有些则允许独立配置。解决抓包对比主站发出的公共地址和 RTU 配置文件里的common_addr改成一致。另外总召唤的传送原因在激活帧里是 0x06激活确认是 0x07激活终止是 0x0A如果 RTU 回了 0x47原因码 0x47 表示未知类型标识说明 RTU 不支持该类型标识需要检查点表配置。4.3 遥测值跳变或明显错误信息体地址与数据类型要核对现象总召唤上来的遥测值一会儿是 0一会儿是满量程或者数值明显不对。原因通常是信息体地址对错了或者数据类型解析错了。南瑞的遥测类型标识可能是 0x09归一化值、0x0B标度化值、0x0D浮点数。归一化值是 2 字节有符号数范围 -1 到 1-2^-15需要乘以量程系数标度化值是 2 字节有符号整数直接对应工程值浮点数是 4 字节 IEEE 754。解决先确认 RTU 点表里每个遥测点的类型标识和信息体地址再在解析代码里按对应格式解包。如果地址对不上遥测值会串到别的测量量上看起来就是跳变。4.4 校验和频繁失败长度域和链路地址长度是重灾区现象抓包工具显示校验和错误但报文看起来完整。原因有两个常见坑一是长度域的计算范围理解错误南瑞某些版本的长度域包含了校验和字节本身导致按标准算时差一个字节二是链路地址长度不是 1 字节有些老设备用 2 字节链路地址导致后续所有字段偏移错位。解决先用已知正确的报文反推长度域和校验和的关系写一个小的校验和计算函数遍历可能的长度偏移找到能通过校验的那个。如果链路地址是 2 字节解析时把链路地址后的所有字段偏移加 1。4.5 报文粘包与半包串口读取要按帧头切分现象串口调试助手收到的数据一坨一坨的有时候两条报文粘在一起有时候一条报文被截断。原因串口是字节流没有消息边界RTU 和主站的发送节奏不一致时就会粘包或半包。解决在接收端维护一个缓冲区按帧头0x68 或 0x10切分。遇到 0x68 时读取后续的长度域然后等缓冲区里凑够长度 6个字节再解析。遇到 0x10 时等凑够 5 字节。这个逻辑在 Python 里用bytearray实现最方便每次recv后追加到缓冲区然后循环尝试解析完整帧。5. 从报文到点表用解析结果反向验证南瑞 RTU 配置5.1 构建报文-点表映射表解析出 ASDU 后最有价值的动作是把信息体地址和实际物理量对应起来。南瑞的 RTU 点表通常是一个 Excel 或 CSV里面列了每个遥测、遥信、电度的信息体地址、类型标识、量程系数和描述。我一般会写一个脚本把抓包解析出的所有信息体地址去重点出来然后和点表做交集快速定位哪些点没上送、哪些点上送了但点表里没有。信息体地址类型标识解析值点表描述量程系数工程值0x40010x090x1234A相电压0.0146.600x40020x090x2345B相电压0.0190.290x40030x0D0x42F60000频率1.0123.00x00010x010x01断路器位置无合位这张表是调试时的核心依据。如果某个地址在报文里出现了但点表里没有说明点表漏配如果点表里有但报文里没出现说明 RTU 没上送该点需要检查 RTU 的配置里该点是否使能。5.2 用解析脚本批量处理抓包文件rtu101.rar里的报文样本可能有很多条手动解析不现实。写一个批量处理脚本读取整个抓包文件按行解析输出每条报文的摘要信息。这样能快速看出总召唤的完整流程主站发激活帧 → RTU 回激活确认 → RTU 发数据帧 → 主站回确认 → RTU 发激活终止。def batch_parse(file_path): 批量解析抓包文件每行一条十六进制报文 with open(file_path, r, encodingutf-8) as f: for line_num, line in enumerate(f, 1): line line.strip() if not line or line.startswith(#): continue try: frame hex_to_bytes(line) ftype, ok check_frame(frame) if ftype 可变帧 and ok: asdu parse_asdu(frame[7:]) print(f行{line_num}: {ftype} | 类型{asdu[类型标识]} f原因{asdu[传送原因]} 地址{asdu[信息体地址]} f个数{asdu[信息体个数]}) else: print(f行{line_num}: {ftype} | 校验{通过 if ok else 失败}) except Exception as e: print(f行{line_num}: 解析异常 {e}) # 用法batch_parse(rtu101_capture.txt)参数说明file_path是抓包文件路径每行一条报文支持#开头的注释行。这个脚本的输出能让你一眼看出总召唤流程是否完整哪一步断了。如果发现 RTU 只回了激活确认但没有后续数据帧重点检查 RTU 的点表配置和总召唤使能位。5.3 反向验证用已知工程值反推量程系数如果点表里的量程系数不确定可以用已知的工程值反推。比如你现场用万用表量到 A 相电压是 220V报文里解析出的原始归一化值是 0x5C28十进制 23592归一化值的满量程是 32767 对应 1.0那么工程值 23592 / 32767 * 量程。反推量程 220 / (23592 / 32767) ≈ 305.5。这个值应该和点表里的量程系数一致如果不一致说明点表配错了或者报文解析的类型标识搞错了。这个方法在调试现场特别管用不需要等主站工程师确认点表自己就能验证。5.4 把解析逻辑固化成一个可复用的调试工具每次调试都重写解析代码太累我习惯把上面的函数封装成一个命令行工具支持三种模式单条报文解析、批量文件解析、实时串口解析。实时模式用pyserial读串口按帧头切分后调用解析函数输出到控制台。这样在现场接上串口就能看报文不用依赖主站软件。工具的核心是帧切分逻辑必须处理粘包和半包否则实时解析会频繁报错。一个稳定的做法是维护一个bytearray缓冲区每次读到新数据就追加然后循环检查缓冲区开头是否是 0x68 或 0x10如果是 0x68 就等长度域指示的完整帧如果是 0x10 就等 5 字节凑够就弹出解析不够就继续等。提示实时解析时串口的超时设置建议为 0.1 秒避免阻塞。如果 RTU 发送频率高控制台输出可能刷屏可以加一个过滤条件只打印类型标识为 0x64、0x09、0x0D 的报文。6. 进阶用 101 规约的时标与品质位做故障定位6.1 解析时标CP56Time2a 的 7 字节结构带时标的 ASDU 在信息体后面会附加 7 字节的 CP56Time2a 时间。结构是毫秒低字节、毫秒高字节0-59999、分钟低 6 位有效、小时低 5 位有效、日低 5 位有效、月低 4 位有效、年低 7 位有效范围 0-99。南瑞的 RTU 在遥信变位和 SOE 上报时会带时标遥测总召唤通常不带。解析时标能帮你定位事件发生的精确时间对比主站收到的时间就能算出通信延迟。def parse_cp56time2a(data): 解析 7 字节 CP56Time2a ms data[0] | (data[1] 8) minute data[2] 0x3F hour data[3] 0x1F day data[4] 0x1F month data[5] 0x0F year data[6] 0x7F return f{year:02d}-{month:02d}-{day:02d} {hour:02d}:{minute:02d}:{ms//1000:02d}.{ms%1000:03d} # 假设时标从某个偏移开始 # ts parse_cp56time2a(frame[offset:offset7])参数说明毫秒值范围 0-59999除以 1000 得到秒取余得到毫秒。分钟、小时、日、月、年都做了位掩码因为高位可能被置位表示无效或夏令时。如果解析出的年份是 0说明时标无效RTU 可能没有对时。6.2 品质位IV、NT、SB、BL 的含义与排查遥测和遥信信息体里通常有一个品质位字节QDS位定义IV无效、NT非当前值、SB被取代、BL被闭锁、OV溢出。如果 IV 位为 1说明这个测量值无效主站应该丢弃。NT 位为 1 表示值不是当前采样值可能是缓存的历史值。SB 位为 1 表示值被人工置数取代。BL 位为 1 表示值被闭锁不参与逻辑计算。调试时如果发现某个遥测值一直是 0 但品质位 IV 为 1说明 RTU 的该通道故障或未接线。如果 NT 为 1说明 RTU 的采样周期和上送周期不匹配需要检查 RTU 的采集配置。6.3 用 Wireshark 插件或自定义解析器做可视化如果抓包数据量大控制台输出不够直观可以用 Wireshark 的自定义解析器功能把 101 报文按字段着色显示。Wireshark 支持 Lua 脚本解析自定义协议写一个简单的 Lua 脚本识别 0x68 和 0x10 帧头然后按字段偏移标注类型标识、传送原因、信息体地址。这样在 Wireshark 里就能像看 TCP 报文一样看 101 报文过滤特定类型标识的帧特别方便。不过 Lua 脚本的调试成本不低如果只是偶尔用Python 脚本加表格输出已经够了。6.4 一个具体技巧用总召唤的激活终止帧判断点表完整性总召唤流程的最后一步是 RTU 发激活终止帧类型标识 0x64传送原因 0x0A。这个帧本身不携带数据但它标志着本轮总召唤结束。我习惯在解析脚本里统计激活终止帧之前收到的所有数据帧的信息体个数然后和点表里的总点数对比。如果少了说明有些点没上送如果多了说明点表里有重复地址或者 RTU 上送了未定义的点。这个对比能在调试初期快速发现点表配置问题比逐个点核对快得多。血泪经验南瑞某些版本的 RTU 在总召唤时会把所有点都上送一遍包括品质位为 IV 的点所以统计时要把 IV 为 1 的点排除否则会误判为点表多了。6.5 我踩过的坑与留下的习惯调试 101 规约这些年最大的教训是不要相信任何「默认配置」。南瑞的 RTU 出厂默认链路地址、公共地址、波特率都可能和现场要求不一致拿到设备第一件事就是读配置、对点表、抓报文。第二个教训是校验和失败不要慌先怀疑长度域和链路地址长度这两个地方翻车概率最高。第三个习惯是每次调试都保留完整的抓包文件用时间戳命名事后复盘时能快速定位问题。最后一个习惯是把解析脚本和点表放在同一个目录脚本输出直接和点表做 diff减少人工核对。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →