iec104测试工具实战:从APDU报文解析到自动化验收

iec104测试工具实战:从APDU报文解析到自动化验收 简介面向电力系统自动化及工业现场调试人员的IEC 104规约客户端测试工具基于C#开发解决了同类软件不适配、难上手的问题也免去了自行寻找协议的繁琐。软件支持遥测、遥信、遥控、对时、SOE等报文的实时解释与显示解释信息与报文一体化显示可随时开关并允许灵活调整规约型式以适配不同厂商的厂站设备适合日常联调与协议学习。资源包共13个文件包含exe主程序、DLL运行组件、xlsx参数模板、ini配置与运行日志文本等整体仅2.17MB部署轻便已有6654人学习/下载适用于电力专业调试人员及协议爱好者。工具另支持参数在线修改与导入导出新增语音报警和工具栏提醒表格菜单中的等量赋值、增量赋值也更加顺手能明显提升批量配置与测试效率。作者保留版权仅作测试用途无内置说明书需使用者具备IEC 104协议基础。1. iec104 测试工具从“能发能收”到“能通过验收”设备上电、网线插好对侧主站却还没送到现场手头只有一个解压即用的 iec104测试工具.zip。用模拟主站回传几路数据后不少人觉得链路是通的正式验收时却被一致性测试打回总召唤不完整、带时标数据格式错、I 帧序号跳跃。反直觉的结论是合格的 iec104 测试工具本身就是个守规矩的“假主站”或“假从站”既能模拟对侧行为也能校验收到报文的序号和传送原因而不只是盲发盲收。这里不介绍某个工具的按钮布局按“协议要点→最小从站→参数排错→自动化验证”的顺序讲清楚这类 zip 工具的验证思路。2. iec104 测试工具的选型判断先读 APCI再看界面2.1 为什么选型先看 APDU 控制域不管是 iec104 协议详解还是工具操作手册第一句话都是104 在传输层上只是 TCP 加端口 2404真正决定规约栈质量的是 APDU 的前六个字节。除启动字符 0x68 和长度外四个控制域八位位组构成 APCI应用规约控制信息后面才是 ASDU。这四种帧类型里I 帧携带 ASDUS 帧只做接收确认U 帧负责启停和链路测试。I 帧控制域里同时携带发送序号 N(S) 和接收序号 N(R)各占 15 位以模 32768 计数但存储时被左移一位。这个细节直接决定工具好不好用。很多测试工具在做模拟主站时每发一帧都把 N(S) 递增却从不解析从站回复里的 N(R)从站发现收到缺失帧或重复帧后会按规约回 S 帧或直接断开链路。于是界面里“已发送帧数”一直涨对侧早就不认了。选型时唯一可靠的办法是打开工具的抓包日志查它发出的第一个 I 帧控制域是不是00 00 00 00第二个是不是02 00 00 00第三个是不是04 00 00 00。这种按 2 递增的十六进制序列说明 N(S) 的维护没有偷懒。接收方向也要看对侧回的 S 帧01 00 04 00第三、四字节解析出 N(R)2确认收到序号 0 和 1。用几行 Python 就能把控制域拆开import struct def parse_control(ctrl: bytes): # ctrl 是 APDU 里的 4 个控制域字节 ns (struct.unpack(H, ctrl[0:2])[0] 1) 0x7FFF nr (struct.unpack(H, ctrl[2:4])[0] 1) 0x7FFF return ns, nr对00 00 00 00这组字节返回 (0, 0)。对02 00 04 00返回 (1, 2)说明本端发送序号 1确认到对端序号 2。左移一位存储是 IEC 104 一个常见的坑如果把控制域当普通整数读序号永远是偶数的 2、4、6很容易误以为“序号跳变”。注意 N(S) 是 15 位序号所以解析时要与 0x7FFF 做与操作否则回绕后高位会带进来。下面这张表是三种帧的判断入口控制域首字节hex帧类型数据内容调试关注点00 / 02 / 04I 帧携带 ASDU序号各占 15 位左移一位存储01S 帧仅确认不带 ASDUN(R) 在第 3、4 字节07 / 0B / 43 / 83U 帧启停、测试链路STARTDT act/con、TESTFR act/con2.2 解压 zip 后先看这几个文件和配置入口拿到这类 zip 包后先别急着双击 exe。把压缩包完整解压到非中文、无空格的路径比如D:\tools\iec104然后打开目录常见做法是找四类东西主站模拟器程序一般叫 Simulator、Client 或 Master用于测试设备提供的从站功能。从站模拟器程序常叫 Server、Slave 或 RTU用于在没有真实设备时验证主站后台。报文记录或回放程序把 2404 端口的 TCP 数据记录成文件验收时当证据用。配置文件重点里的重点是 IP、端口、公共地址和点表。配置入口重点关注公共地址和点表文件里的信息体地址。104 规约里公共地址占两个字节很多设备出厂默认是 1但实际工程里每台测控装置都有独立地址。工具界面如果只有一个“站地址”输入框要确认它同时覆盖 ASDU 里的公共地址而不是仅用于界面显示。顺带回应一下常见的搜索词搜“iec104 client simulator 破解码”得到的多数是老版本注册问题。破解版本地生效后常伴随两个软问题内置协议栈不支持带时标数据或者完全不维护 t3 链路测试跑一晚上就断链。现在很多厂家提供全功能评估版开源环境下也有库可以做从站仿真没必要把测试工具放进不可信的执行环境。2.3 判断标准用“断链重连 序号保持”做筛选界面之外我常用一个更难的动作来筛选工具在模拟从站时把主站强制断开30 秒后重连。合格的模拟从站会重新等待 STARTDT act 流程收到之后才再次发送 I 帧并且发送序号 N(S) 要么和断开前保持衔接要么按明确配置复位。如果工具在重连后直接发序号为零的帧而主站认为链路应该继续连接马上被掐。把筛选动作固化成一句话断开连接、重连、看工具日志里有没有对端发来的链路关闭或序号异常提示。能走到这一步的 iec104 测试工具才值得进入后面的最小联调。3. 用 iec104 测试工具搭出最小模拟从站并跑通总召唤3.1 把模拟从站跑起来的最小三步最常见的使用场景是把工具当从站测对侧主站。解压后打开从站模拟器按三步做顺序不能乱设置本机监听地址和端口。默认监听 0.0.0.0:2404先不加任何代理直接在 TCP 层监听。填公共地址与点表。公共地址先填 1点表只配 2 路遥信、1 路浮点遥测不要一次配几千点。点击启动监听观察日志窗口里是否出现 “TCP listening on 0.0.0.0:2404” 或等价输出再用对侧主站发起连接。这中间最值得强调的是 K/W 窗口。下表是 IEC 104 里的常见默认值参数常见默认值作用K12发送窗口上限未确认的 I 帧最多 12 帧W8确认窗口收到 8 个 I 帧必须回一次 S 帧t320s链路测试帧周期t030sTCP 连接建立超时K12、W8 是绝大多数设备的默认值。很多 zip 工具把这两个参数隐藏只留一个“窗口大小”输入框这种工具通常把 K 和 W 混为一谈调试批量收发时会掩盖时序问题。先用默认值跑通再人为改小 K 值验证从站行为是后面第 4 章里要做的事。3.2 不用现成工具时用裸 socket 写出最小 104 从站当 zip 工具的协议行为可疑或者只想验证某一个报文细节时常见做法是丢掉图形界面用裸 socket 写一个最小从站。下面这份代码能跑通“建链 → STARTDT → 总召唤应答”这一段import socket import struct def recv_exact(conn, n): buf b while len(buf) n: chunk conn.recv(n - len(buf)) if not chunk: raise ConnectionError(链路已断开) buf chunk return buf def u_frame(cmd): # U 帧固定 6 字节启动符、长度 04、控制域三字节、补 0 return bytes([0x68, 0x04, cmd, 0x00, 0x00, 0x00]) def i_frame(ns, nr, asdu): # I 帧控制域ns/nr 左移一位后按小端写入两个 16 位字段 ctrl struct.pack(HH, (ns 1) 0xFFFF, (nr 1) 0xFFFF) return bytes([0x68, 4 len(asdu)]) ctrl asdu def build_asdu(type_id, cause, common_addr, info): # 这里只处理单信息对象可变结构限定词固定为 1 return bytes([type_id, 0x01]) struct.pack(H, cause) \ struct.pack(H, common_addr) info def handle(conn): send_seq 0 while True: head recv_exact(conn, 2) if head[0] ! 0x68: continue length head[1] apdu head recv_exact(conn, length) ctrl apdu[2:6] if length 4: cmd ctrl[0] if cmd 0x07: # STARTDT act conn.sendall(u_frame(0x0B)) # 回 STARTDT con elif cmd 0x43: # TESTFR act conn.sendall(u_frame(0x83)) # 回 TESTFR con # S 帧在这里不做处理完整实现要解析 N(R) 并维护发送窗口 else: # 主站的接收序号 N(R) 在控制域第 3、4 字节 nr (struct.unpack(H, ctrl[2:4])[0] 1) 0x7FFF asdu apdu[6:] if asdu[0] 0x64: # 总召唤 C_IC_NA # 第一步回激活确认传送原因 7 conn.sendall(i_frame(send_seq, nr, build_asdu(0x64, 7, 1, b\x00\x00\x00\x14))) send_seq (send_seq 1) % 32768 # 第二步上送一路单点遥信传送原因 20 info bytes([0x00, 0x00, 0x00, 0x01]) conn.sendall(i_frame(send_seq, nr, build_asdu(0x01, 20, 1, info))) send_seq (send_seq 1) % 32768 # 第三步回激活终止传送原因 10 conn.sendall(i_frame(send_seq, nr, build_asdu(0x64, 10, 1, b\x00\x00\x00\x14))) srv socket.socket(socket.AF_INET, socket.SOCK_STREAM) srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) srv.bind((0.0.0.0, 2404)) srv.listen(1) while True: conn, addr srv.accept() handle(conn) # 单连接版本多个主站并发时要加 accept 循环代码逻辑分三块。u_frame生成 U 帧长度固定 6 字节不带任何序号0x07/0x0B 对应 STARTDT act/con0x43/0x83 对应 TESTFR act/con。i_frame组装 I 帧用HH小端把序号左移一位后的值写进控制域。build_asdu只支持单信息对象可变结构限定词固定为 1所以整个 ASDU 就是类型标识、限定词、传送原因、公共地址和原始信息体字节。总召唤应答分三步先回传送原因 7 的激活确认再上送数据这里用 cause20 表示响应总召唤最后回传送原因 10 的激活终止。最后一步最容易漏漏掉终止帧主站会一直等待后续数据界面上表现为“总召唤超时”。注意信息体地址是 3 字节小端最后的0x14是召唤限定词表示这是总召唤而不是特定组召唤。3.3 连接失败时的三处排查按顺序来跑不通时按顺序排查不要上来就怀疑工具先确认 TCP 状态。Windows 用netstat -an | findstr 2404Linux 用ss -tnp | grep 2404看连接是 ESTABLISHED 还是 LISTEN。端口没起来说明程序没有真正 bind 到 0.0.0.0或者防火墙拦截了入站。再确认有没有 STARTDT 交换。抓包里如果主站一直在发 I 帧从站却没有回 STARTDT con通常是把“连接建立”和“链路启动”混为一谈。104 必须由主站先发 STARTDT act从站回 con 之后才可以传 ASDU。最后查公共地址。设备日志里反复出现“未知公共地址”或总召唤68 0E ... 64 01 06 00 01 00 ...中的公共地址此处是 0100小端对应 0x0001对不上时多半是工具里配的站地址和主站发来的 ASDU 公共地址不一致。抓包命令在这里能直接定位问题tcpdump -i eth0 -s 0 -w /tmp/iec104.pcap port 2404 tshark -r /tmp/iec104.pcap -Y iec104 -V | head -100注意如果抓包里只看到对端主动断链的一串 RST说明上面三个阶段至少有一个没走完常见原因是序号没对上而不是物理链路问题。4. iec104 测试工具的参数调优K/W 窗口、t0~t3 与报文验证4.1 ASDU 常见类型遥信、遥测、遥控、总召唤调试点表之前先把类型标识背熟。模拟工具的点表里类型标识是理解报文的主键类型标识hex名称含义调试关注点0x01M_SP_NA单点遥信信息体地址后第一字节 0分1合0x0DM_ME_NC浮点遥测4 字节 IEEE 754 浮点带品质描述0x1EM_SP_TB带时标单点遥信采用 CP56Time2a共 7 字节0x2DC_SC_NA单点遥控从站必须回传送原因 7 的确认0x64C_IC_NA总召唤激活 6、确认 7、终止 10 三次握手调试时最常见的错误是把 0x0D 当成两个字节的“短浮点”或者把带时标的 0x1E 和无时标格式混用。0x1E 的时标是 CP56Time2a最前是 2 字节毫秒小端后面依次是分、时、日、月、年五个字节最容易被看错的不是年月日而是毫秒字段的字节序。4.2 K/W 与 t0~t3默认值不是免检值第 3 章的参数表给了默认值这里展开讲“为什么默认值会埋雷”。t3 是链路测试周期常见默认 20 秒。如果测试工具的 t3 比对侧短就会在对侧打算空闲时提前发 TESTFR把链路占满对侧有时会把它当成异常处理反过来 t3 太长链路空闲时不发测试帧超过对侧 t0 后 TCP 连接被静默拆除。做稳定性测试时把两侧 t3 错开 10 秒以上是暴露链路维持缺陷的常用手段。t1 是发送确认超时默认 15 秒等不到对端确认就重试。调试阶段可以把 t1 调到 3 秒快速验证从站有没有正确回 S 帧。t2 是接收确认超时默认 10 秒代表“收到 W 个 I 帧后必须在 t2 内回确认”。调参时要注意 t2 必须小于 t3否则还没来得及回确认就先被 t3 测试帧打断形成周期性的链路抖动。K/W 的坑更隐蔽。模拟主站连续下发 1000 点遥测时如果从站不回 S 帧主站发满 W 帧就停下来等确认界面上却显示“已发送 1000”。判断工具有没有在维护发送窗口看它统计面板里的“发帧数”和“未确认数”是否同步增长只看发帧数等于没做规约层面的确认。4.3 用 tshark 验证总召唤三要素抓包之后验证总召唤是否真正成功盯住三类帧就够tcpdump -i eth0 -s 0 -w /tmp/iec104.pcap port 2404 tshark -r /tmp/iec104.pcap -Y iec104.asdu.type 0x64 -V | head -120预期会看到三次传送原因的转变主站发出的 6激活从站回 7激活确认最后从站回 10激活终止在上述三类帧之间穿插 cause20 的遥信、遥测数据帧。如果只有 6 没有 7说明从站对总召唤 ASDU 不识别优先检查公共地址和点表地址如果有 6、7 但永远没有 10说明从站一直在循环上送数据终止条件写错回到 3.2 的代码里检查最后的0x64 10帧是否真的发出。把参数调小后的验证也是一样思路K 改成 2W 改成 2发 10 个遥测点抓包里应该能数出五组“I 帧 S 帧”的交替而不是一口气发完 10 个 I 帧。这种交替模式是判断工具是否真正遵守窗口的硬证据。5. 让 iec104 测试工具替你完成自动化回归验证5.1 把 N(S)/N(R) 序列导出改掉“肉眼盯日志”的坏习惯如果工具能把日志导出成文本先写一行命令把序号序列抽出来grep -E NS[0-9] iec104.log \ | awk -FNS {split($2,a, ); print a[1]} \ | awk {if (NR1 $1 ! prev1) print NR: 序号跳变 prev - $1; prev$1}中间的命令按实际日志格式调整分隔符。目的不是统计而是在回归时发现那个不连续的跳变。I 帧序号每发一帧加 1十六进制控制域表现为 00 00、02 00、04 00 这样的序列。一旦出现 06 00 之后跟 0A 00少了一个偶数步进就意味着对侧序号校验会直接断链。5.2 用“伪错序”验证主站工具是否真的在检查序号更有价值的回归用例是人为制造错误。把 3.2 的从站代码改一处第二帧的发送序号从 1 改成 3然后让主站模拟器接收。合格的主站工具应该在第二帧就报“接收序号不连续”把错误写进日志不合格的工具会当作正常帧继续解析后续报文全部错位。这个用例能一票否决掉“界面漂亮但协议栈不完整”的工具。5.3 收工前最后再做一步自动化回归结束再把 pcap 里总召唤的三类帧逐帧对一遍——重点看第二条 I 帧的 N(R)1 是否等于主站下一条 N(S)以及带时标遥信 0x1E 的毫秒字段是否符合预期。把这两处写进回归断言里下次换一个 iec104 测试工具包直接跑同一组断言十分钟就能判断它能不能上现场。本文还有配套的精品资源点击获取