简介:面向电力系统自动化及工业现场调试人员的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 帧携带 ASDU,S 帧只做接收确认,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 / 04 | I 帧 | 携带 ASDU | 序号各占 15 位,左移一位存储 |
| 01 | S 帧 | 仅确认 | 不带 ASDU,N(R) 在第 3、4 字节 |
| 07 / 0B / 43 / 83 | U 帧 | 启停、测试链路 | STARTDT act/con、TESTFR act/con |
2.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 里的常见默认值:
| 参数 | 常见默认值 | 作用 |
|---|---|---|
| K | 12 | 发送窗口上限,未确认的 I 帧最多 12 帧 |
| W | 8 | 确认窗口,收到 8 个 I 帧必须回一次 S 帧 |
| t3 | 20s | 链路测试帧周期 |
| t0 | 30s | TCP 连接建立超时 |
K=12、W=8 是绝大多数设备的默认值。很多 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/con,0x43/0x83 对应 TESTFR act/con。i_frame组装 I 帧,用<HH小端把序号左移一位后的值写进控制域。build_asdu只支持单信息对象,可变结构限定词固定为 1,所以整个 ASDU 就是类型标识、限定词、传送原因、公共地址和原始信息体字节。
总召唤应答分三步:先回传送原因 7 的激活确认,再上送数据(这里用 cause=20 表示响应总召唤),最后回传送原因 10 的激活终止。最后一步最容易漏:漏掉终止帧,主站会一直等待后续数据,界面上表现为“总召唤超时”。注意信息体地址是 3 字节小端,最后的0x14是召唤限定词,表示这是总召唤而不是特定组召唤。
3.3 连接失败时的三处排查,按顺序来
跑不通时按顺序排查,不要上来就怀疑工具:
- 先确认 TCP 状态。Windows 用
netstat -an | findstr 2404,Linux 用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) | 名称 | 含义 | 调试关注点 |
|---|---|---|---|
| 0x01 | M_SP_NA | 单点遥信 | 信息体地址后第一字节 0=分,1=合 |
| 0x0D | M_ME_NC | 浮点遥测 | 4 字节 IEEE 754 浮点,带品质描述 |
| 0x1E | M_SP_TB | 带时标单点遥信 | 采用 CP56Time2a,共 7 字节 |
| 0x2D | C_SC_NA | 单点遥控 | 从站必须回传送原因 7 的确认 |
| 0x64 | C_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(激活终止);在上述三类帧之间,穿插 cause=20 的遥信、遥测数据帧。如果只有 6 没有 7,说明从站对总召唤 ASDU 不识别,优先检查公共地址和点表地址;如果有 6、7 但永远没有 10,说明从站一直在循环上送数据,终止条件写错,回到 3.2 的代码里检查最后的0x64 10帧是否真的发出。
把参数调小后的验证也是一样思路:K 改成 2,W 改成 2,发 10 个遥测点,抓包里应该能数出五组“I 帧 + S 帧”的交替,而不是一口气发完 10 个 I 帧。这种交替模式是判断工具是否真正遵守窗口的硬证据。
5. 让 iec104 测试工具替你完成自动化回归验证
5.1 把 N(S)/N(R) 序列导出,改掉“肉眼盯日志”的坏习惯
如果工具能把日志导出成文本,先写一行命令把序号序列抽出来:
grep -E 'NS=[0-9]+' iec104.log \ | awk -F'NS=' '{split($2,a," "); print a[1]}' \ | awk '{if (NR>1 && $1 != prev+1) 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 测试工具包,直接跑同一组断言,十分钟就能判断它能不能上现场。
本文还有配套的精品资源,点击获取