简介:电力用户用电信息采集系统通信协议集合与测试工具,面向电力行业研发、运维及测试人员,覆盖DL/T 645、376.1/376.2/376.3、Q/GDW 698等主流规约的标准文档与解析调试软件。其中376.1定义基本命令格式,376.2扩展功能码与数据项,376.3加入安全认证和加密机制;645规约侧重远程抄表控制,698规约则涵盖事件记录、安全防护、时钟同步等复杂交互。资源共89个文件,压缩包约105.57MB,包含PDF/DOC协议说明、TXT数据帧解析样例、EXE/DLL可执行测试工具与动态库,以及INI/LOG等配置文件,适合从协议学习到现场联调、故障排查的完整链路。已有3553人学习/下载。工具内含645标准测试程序、376.2集中器下行本地接口协议调试软件、DLT698-45(1.0)测试解析及国网电表测试软件等,可模拟通信场景验证数据交互准确性,辅助开发者理解帧结构、功能码与安全机制。对于需要落实智能电网数据采集、提升远程抄表与运维效率的技术人员,这份资源兼具参考价值和实用工具属性。
1. 电力用户用电信息采集系统联调,缺的不是协议文本,而是一套能拆帧、注错、比对的工具集合
接入电力用户用电信息采集系统的联调现场,最磨人的往往不是接线,而是报文。手边 DL/T 645、DL/T 698.45,甚至 IEC 62056 的协议文本一沓,发一帧抄表命令,终端回了十几个字节,能一眼分清哪个是地址域、哪个是控制码、哪个是数据域的人其实不多。真正拖住进度的不是不知道协议规定,而是没有一套能同时做报文拆解、异常注入、字段比对的测试工具集合。这篇内容就是把这套系统的协议集合拆开,讲清楚测试工具怎么设计、代码怎么落地,以及现场最容易翻车的几个环节,适合做主站接入、终端研发和现场运维的工程师照着搭一套自己的工具。
2. 先拆协议集合:645、698.45、62056 各管一段,测试点完全不同
用电信息采集系统不是单一协议跑到底,而是分层协作。主站和采集终端之间走系统级协议,通常是 DL/T 698.45;采集终端和电能表之间走表计级协议,最典型的是 DL/T 645;海外项目或出口表计,则大概率落在 IEC 62056/DLMS。这三类协议的帧格式、数据模型、测试重点完全不一样,工具集合必须先认清自己在测哪一层。
2.1 DL/T 645:点表式规约,六段帧结构里最容易错的两处
DL/T 645 是国内电能表计量最常碰到的规约,2007 版应用最广。它的核心思路是点表式:用固定数据标识 DI 指代某个数据项,主站发读数据请求,表计按数据标识返回对应内容。帧结构很规整,从前往后依次是起始符 68H、6 字节地址域 A0~A5、起始符 68H、控制码 C、数据域长度 L、数据域 DATA、校验和 CS、结束符 16H。
初学者在这里会踩两个很典型的坑。第一个是地址域,6 字节 A0~A5 是低位字节在前,一个 12 位表号在帧里看到的字节顺序和铭牌上读出来是反的,拿抓包结果直接和协议文档对不上。第二个是数据域加 0x33 处理,发送前每个字节加 0x33,接收后要减回来,目的和“转义”类似,防止数据域里出现 0x68 被误判成帧起始符。这两条属于 645 的条件反射,解析工具必须内建,不能靠人工心算。
控制码 C 是另一个必须逐位解析的字段。D7 表示传送方向,0 是主站发命令,1 是从站应答;D6 表示应答类型,0 是正常应答,1 是异常应答;D5 是后续帧标志;D4~D0 是功能码。常见几个值建议直接记住:0x11 是主站读数据请求,0x91 是从站正常应答,0xD1 是从站异常应答。测试工具拿到一帧后,先看 D7 判断来向,再看 D6 判断这次通信是成功还是被拒绝,逻辑比硬凑整帧对比清晰得多。
数据标识 DI 是点表式协议的测试重点。读请求的数据域里放的就是 DI,表计根据 DI 决定返回哪个数据。常见对象表标准里有定义,但厂家的实际实现常常有裁剪,工具里要把 DI 映射做成可配置,放到 JSON 或 YAML 里,联调时按设备型号切换。
2.2 DL/T 698.45:面向对象的分层协议,SA/CA 地址和 APDU 是测试重点
DL/T 698.45 系列是当前国内用电信息采集系统中主站与采集终端之间的主流协议。它和 645 最大的区别是数据模型从点表切到了面向对象:不再用一串 DI 指数据项,而是寻址到“逻辑设备、对象、属性、方法”,应用层 APDU 用 TLV 方式编码,对象访问更灵活,扩展新业务不需要重新定义一组点表。
协议栈分为应用层、传输层、数据链路层和物理层。数据链路层帧里除了起始符、长度、控制域,还有服务器地址 SA、客户机地址 CA、帧头校验和帧尾校验,用户数据部分由 LLC 再包传输层和应用层。联调时最常出问题的三个点是:SA/CA 地址配置反了导致终端直接不应答;APDU 里 Tag 值标错,对象属性访问被拒;数据较长时协议会分帧传输,测试工具如果没有做分帧重组,解析结果就是断的。
和 645 的分工要弄清楚。645 解决的是“终端怎么把电能表读数读上来”,698.45 解决的是“主站怎么把整个采集系统的数据管起来”。所以现场联调时,通常在终端侧把 698.45 报文往下解,底层再挂 645 去抄表。工具集合里这两个解析器是配合关系,不是二选一。
2.3 IEC 62056/DLMS:海外互操作场景,OBIS 对象编码怎么读
海外项目和出口表计通常走 IEC 62056 系列,协议栈核心是 DLMS/COSEM。这个体系里,计量数据被建模成对象,对象用 OBIS 编码标识。例如 1.0.1.8.0.255 是正向有功电能,0.0.1.0.0.255 是逻辑设备名。这种编码方式比 645 的数据标识更抽象,但对象目录是标准化的,不同厂家表计读同一类数据的路径基本一致。
DLMS 客户端向服务器发 xDLMS APDU,对象编解码走 ASN.1 风格的 T-L-V 结构,和 645 的定长数据域风格差别很大。链路层用 HDLC,存在帧的拆分与重组,测试时要做到三层:HDLC 帧重组、APDU 解码、OBIS 对象目录读取。国内 698.45 在设计上吸收了 COSEM 的面向对象思想,但做了本土化裁剪,两者不能直接混用。
做海外项目认证时,一般直接用成熟的商业测试组件做一致性测试,自研工具更多是配合被测设备做回归。如果团队没有海外交付需求,这块投入可以往后放。
2.4 测试工具先支持哪个协议:按被测对象定优先级
工具集合不需要第一天就支持全部协议。我一般先问被测对象是谁:被测是电能表,先做 645;被测是采集终端或主站接入,先做 698.45;有海外交付再评估 62056。优先级按“联调现场哪个报文最难看懂”来排,哪个让你加班最多,就先做哪个。
| 协议项 | DL/T 645 | DL/T 698.45 | IEC 62056/DLMS |
|---|---|---|---|
| 数据模型 | 点表式,DI 数据标识 | 面向对象,对象-属性-方法 | 面向对象,OBIS 编码 |
| 典型角色 | 电能表与采集终端 | 采集终端与主站 | 表计与计量系统,海外项目 |
| 帧特点 | 六段固定帧 | 分层帧 + APDU(TLV) | HDLC 帧 + APDU(BER/TLV) |
| 测试重点 | 地址域、加 0x33、校验和、DI 映射 | SA/CA 地址、APDU 解码、分帧重组 | HDLC 重组、OBIS 解码、对象目录 |
| 上手成本 | 低 | 中 | 高 |
架构上把协议栈做成插件式,解析器按协议名注册,后面新加协议不推倒重来,这是工具集合能长期用的前提。
3. 工具集合怎么搭:五个模块加三层用例库,就能覆盖联调主链路
协议拆清楚之后,测试工具的边界就出来了。它不需要是一个大而全的自动化平台,只需要把联调中最耗时间的四件事自动化:构造报文、解析报文、注入异常、比对结果。围绕这四件事,我把工具集合拆成五个模块,再加一套用例组织方式。
3.1 五个核心模块:模拟器、解析器、注入器、比对器、报告器
模拟器负责扮演主站或终端,按协议构造合法报文并收发。解析器负责把收到的字节流拆成结构化字段,输出一个字典或对象。注入器负责故意改坏报文,比如改地址域、篡改校验和、改长度字段。比对器负责把实际解析结果和期望值逐字段对比,支持掩码忽略动态字节。报告器把一轮测试的结果聚合成可读输出。
| 模块 | 职责 | 输出物 |
|---|---|---|
| 协议模拟器 | 按角色构造报文,管理收发会话 | 可发送的字节流 |
| 报文解析器 | 按协议拆帧,解码数据域 | 结构化字段字典 |
| 异常注入器 | 对合法帧做字节级变异 | 变异后的报文 |
| 一致性比对器 | 期望值与实际值逐字段比对 | 差异列表 |
| 报告器 | 汇总通过率、失败字段,保存原始报文 | CSV/JSON/HTML 报告 |
模块之间用一个统一的帧数据模型串联。不管哪种协议,解析结果都归一到“起始符、地址、控制、长度、数据、校验、结束”这些字段名,协议差异下沉到各自的解析器里。这样比对器和报告器不用关心协议细节,新协议接入时只需要写一个 parser 注册进去。
3.2 模拟主站还是模拟终端:先测哪一侧由被测对象决定
测试布局有两种基本形态。被测对象是终端时,用模拟主站去连它,主站按流程发请求,终端应答,工具验证应答是否符合协议;被测对象是主站时,用模拟终端去接它,模拟终端要能主动上报数据,比如停电事件上报,然后验证主站是否正确回确认、是否正确落库。
我习惯先做一次模拟主站对模拟终端的回环自测,把协议栈本身的正确性跑通,再替换其中一侧为真实设备。这样出了问题能快速定位是工具的问题还是设备的问题,不用在联调现场两头怀疑。模拟终端还要支持定时自动上报,否则事件类场景根本没法测。
3.3 用例按帧-会话-场景三层建库,回归测试才有底
测试用例按三层组织。帧层是最小粒度,只测单帧合法性和异常帧,比如错误校验和、错误长度、非法控制码;会话层测协议交互时序,比如登录、心跳、校时、读写数据;场景层模拟真实业务,比如停电上报、费控跳闸、透抄。三层分开建库,回归时可以按需选择范围。
一个帧层用例在 YAML 里大致长这样:
- name: 正常读表-正向有功电能 protocol: dlt645 role: master send: ctrl: 0x11 di: "00-00" expect: response_ctrl_mask: 0x91 data_parsed: value_type: BCD这个示例强调两个设计点:send 只描述语义层参数,实际字节由模拟器按协议生成;expect 里不比对整帧,而是比对语义字段,比如应答控制码是不是 0x91、数据是不是 BCD 类型。这样用例写起来像在描述业务,不是在抄 hex。
4. 本地跑通最小可用工具集:645 报文解析与构造的落地代码
协议和模块方案定下来后,直接动手写最小可用实现。下面这段代码以 DL/T 645 为例,因为它的帧结构最简单,最适合验证整套思路。代码按“解析一帧、构造一帧、跑一个闭环”三个步骤展开。
4.1 写一个 645 帧解析器:先验帧壳,再减 0x33,最后验校验和
# dlt645_parse.py def parse_645_frame(raw: bytes) -> dict: if len(raw) < 12 or raw[0] != 0x68: raise ValueError("bad frame head") addr = raw[1:7] # 地址域 A0~A5 ctrl = raw[8] # 控制码 C length = raw[9] # 数据域长度 L data_start = 10 data_end = data_start + length if data_end + 2 > len(raw): raise ValueError("length over frame") cs = raw[data_end] calc_cs = sum(raw[:data_end]) & 0xFF if cs != calc_cs: raise ValueError(f"cs mismatch: calc {calc_cs:#04x}, got {cs:#04x}") # 数据域先减 0x33 还原 fields = [(b - 0x33) & 0xFF for b in raw[data_start:data_end]] return { "addr": addr.hex(), "ctrl": ctrl, "ctrl_desc": decode_ctrl(ctrl), "len": length, "data": fields, "cs": cs, }这里有几个参数需要说明白。索引关系是:第 0 字节是第一个 68H,第 1~6 字节是地址域,第 7 字节是第二个 68H,第 8 字节才是控制码,第 9 字节是长度 L,第 10 字节开始是数据域。length 是数据域字节数,不是整帧长度,这个和 698.45 的帧长字段定义不一样,解析器里不能用错。
校验和从帧起始符累加到数据域最后一个字节,取低 8 位,不进位。数据域还原必须在校验和验证之后做,因为校验和算的是加 0x33 之前的原始帧字节,先减 0x33 再算校验和会把结果算错。这是很多自研解析器翻车的细节。
4.2 构造一帧抄表请求:控制码和数据标识是两个关键参数
# build_645_request.py def build_read_request(addr_bytes: bytes, di: bytes, ctrl: int = 0x11) -> bytes: # 数据域 = 数据标识,发送前每个字节加 0x33 data = bytes((b + 0x33) & 0xFF for b in di) head = bytes([0x68]) + addr_bytes + bytes([0x68, ctrl, len(data)]) frame = head + data cs = sum(frame) & 0xFF return frame + bytes([cs, 0x16])addr_bytes 是 6 字节地址域,按低位字节在前传入。di 是数据标识字节序列,常见的读当前正向有功电能是 2 字节或 4 字节,取决于协议版本和厂家实现,工具里把 di 作为参数传入,不要在函数里写死。ctrl 默认 0x11,对应主站读数据请求。
构造逻辑里最关键的是发送前加 0x33。通信链路上传输的字节都是加过 0x33 的,这样数据域里即使出现 0x68、0x16 也不会和帧起始符、结束符冲突。测试工具如果抓包后直接把这一帧复现出去,会重复加 0x33,这是常见错误,一定要区分“链路字节”和“解码后的数据”。
4.3 跑一次发-收-断言的最小闭环:打印解析结果定位失败帧
# round_trip.py def one_round(ser, addr_bytes: bytes, di: bytes, timeout: float = 3.0) -> dict: req = build_read_request(addr_bytes, di) ser.write(req) resp = ser.read_until(b"\x16", timeout) # 按结束符收一帧 parsed = parse_645_frame(resp) # 控制码 D6 位为 1 表示从站异常应答 if parsed["ctrl"] & 0x40: raise AssertionError(f"slave abnormal response, ctrl={parsed['ctrl']:#04x}") return parsed这个闭环函数做了三件事:按构造器发包,按结束符收包,最后断言应答控制码。read_until 按结束符 0x16 收帧,比固定读 N 个字节稳妥,能避开串口分片问题。断言只检查 D6 位,不判断整个控制码相等,因为 0x91 和 0xD1 的 D4~D0 都是读数据功能码,区别只在应答位,按掩码判断语义更准确。
失败时不要只打印 parsed 字典,要把原始 hex 和解码后的数据域并排打出来。比如原始帧显示 68 11 04 AA BB CC DD……,解码后显示数据域是 0x00 0x00,这样可以立刻看出是加 0x33 的问题还是 DI 映射的问题。
4.4 698.45 的 APDU 长什么样:用一个通用 TLV 扫描器拆层
# tlv_scan.py def scan_tlv(buf: bytes, pos: int = 0): items = [] while pos < len(buf): tag = buf[pos] length = buf[pos + 1] value = buf[pos + 2 : pos + 2 + length] items.append({"tag": tag, "length": length, "value": value}) pos += 2 + length return items698.45 的应用层 APDU 本质是嵌套 TLV。外层 Tag 表示操作类型,比如 Get 请求、Set 请求、Action 请求;内层 Tag 表示对象标识、属性标识;再内层是数据。上面这个扫描器是第一层拆解,把 APDU 拆成一串 Tag-Length-Value 后,再对 value 递归调用,就能一层层看到完整寻址路径。
实际调试 698.45 时,先用帧解析器把数据链路层剥掉,拿到用户数据后,再跑这个 TLV 扫描器,能快速定位是哪个 Tag 写错。很多联调问题最后都落在一层 Tag 值对不上,而不是整个报文结构错。
5. 联调现场那些坑:终端不回、数据乱码、成功率上不去的排查
代码能跑通只是第一步,现场联调才是真正考验工具的地方。下面这几条是高频踩坑记录,每条都按现象、原因、解决三个层次写,能帮你在现场少走弯路。
5.1 现象:解析结果全是“乱码”,数据域为什么长这样
拿着工具解析一帧 645 应答,数据域打印出来是一串 0x55 0x77 0xAA,完全看不出 BCD 数。原因大概率是没做加 0x33 的逆运算。链路层收到的字节是加过 0x33 的,直接按原始字节转 BCD,等于看密文。
解决:解析器里先减 0x33,再按 BCD 组数。更稳妥的做法是同时打印两行,一行是链路层原始 hex,一行是解码后的数据域,两个版本并排对比,排查时一眼就能分辨是工具问题还是设备问题。
5.2 现象:校验和正确,主站还是丢帧
单点调试用串口助手发包收包都正常,换到模拟主站里就丢帧。一种常见原因是串口分片,一帧数据被拆成两次收到,工具如果按固定字节数读包,会把半截帧当完整帧处理,校验和自然对不上。另一种原因是解析器按整个帧长度截取,但长度字段在 645 里是数据域长度,在 698.45 里是别的含义,混用后把后面的校验字节算进了数据域。
解决:串口读取一定要做组包,按结束符 0x16 或超时机制收完整帧再解析;解析函数里严格按照协议定义取长度字段,不要写一个通用的“按帧长截取”逻辑给所有协议用。
5.3 现象:抄表成功率时高时低,拨码和通信参数没对齐
同一条 485 总线上,抄表成功率在 70% 到 90% 之间波动。先查物理层,多半是波特率不一致,表侧是 1200,工具或主站设成 9600;或者 A/B 线接反。再查地址域,多块表共线时地址重复或拨码错位,终端会互相抢应答。
解决:测试工具的模拟主站支持多波特率扫描,用例里记录通信参数组,换现场时一键切换。现场先单点抄表确认通,再批量并发,成功率问题基本能定位到物理层或地址冲突,不是协议栈问题。
5.4 现象:只测合法帧就放行,异常帧一打就哑火
协议一致性测试“全过”的终端,到现场遇到错误校验和或非法控制码的帧时,直接哑火不回,主站就一直超时重试。原因是用例库里只有正确路径,没测设备在异常输入下的表现。
解决:帧层用例里加一组变异帧,比如错误校验和、错误长度、非法控制码、错误地址域。判断依据不是“必须回正常数据”,而是看设备行为是否符合预期,该回异常应答的回异常应答,该不回的不回,但要有明确超时时间。这个习惯能把联调后期一半的隐性故障提前暴露出来。
6. 从手工点测到一键回归:把工具集嵌进持续测试,报告按帧输出
工具集合的价值在反复回归时才最大。协议改动、固件升级、主站版本变更,任何一次变更都可能引入字段级偏差,手工点测根本覆盖不过来。
6.1 三层用例库和一条命令跑完回归
把第 3 章的三层用例库落到目录里,每层一个子目录,跑回归时按层选择。命令大概是这样:
python run_suite.py --cases cases/frame_layer \ --serial /dev/ttyS0 \ --report report/consistency.csvrunner 脚本逻辑很简单:遍历用例目录下的 YAML,逐条调用模拟器发帧、解析器收帧、比对器断言,最后把结果写进 CSV。用例通过与否只由比对器决定,不靠人工看日志。
6.2 报告怎么读:失败字段、原始报文与回放技巧
报告里每一行包含用例名、角色、协议、期望结果、实际结果、失败字段、原始报文。看报告时先扫失败字段这一列,通常问题集中在两个地方:控制码掩码不匹配,或校验和错误。再打开原始报文,对比失败字段的位置,能直接判断是工具构造问题还是设备应答问题。
我常用的一个技巧是给工具加回放能力:把联调现场抓到的原始报文存成日志,回归时重新喂给解析器和比对器。这比重新模拟主站更贴近实际故障现场,固件升级后回放一遍历史报文,能快速确认行为是否变过。这也是我一直保留原始报文输出到报告里的原因——踩过最大的坑就是只留结论不留现场,等设备返厂后再想复现已经晚了。
工具集合做到这个程度,基本就是一套可以长期积累的测试资产了。希望帮到你。
本文还有配套的精品资源,点击获取