简介:这份资源面向电力行业技术人员、电表与集中器开发调试人员及电力院校学习者,聚焦用电信息采集系统通信协议的落地应用,汇集376.1、376.2、376.3、645、698等常用规约资料与配套测试软件,帮助解决协议理解、报文解析与设备联调中的实际问题。压缩包共89个文件,约105.57MB,以37个txt协议说明与报文样例、8个dll与7个exe测试程序、6个pdf规范文档及doc、ini配置等为主,兼顾查阅与实操。已有3558人学习下载,说明其在电力采集通信领域具有较高的参考价值。读者可从中获得各协议的帧结构、功能码与安全机制说明,借助模拟器、分析仪类工具验证数据交换的正确性,并参考Q/GDW1376.2解析、DLT698与645测试等实例排查通信故障,适合作为协议学习与现场调试的辅助资料。
1. 电力用户用电信息采集系统通信协议集合:从规约文本到可跑通的测试链路
手里拿到一份“电力用户用电信息采集系统通信协议集合加测试工具”的资源包,多数人的第一反应是解压、翻目录、找 README,然后卡在“这堆规约到底哪个对应现场哪台设备”上。这份资源解决的不是“协议是什么”的科普问题,而是把采集主站、集中器、智能电表之间那套报文交互,从纸面条款变成能抓、能发、能比对的东西。它适合三类人:做用电信息采集终端联调的现场工程师、需要给采集系统做协议一致性验证的测试人员、以及刚接手 376.1 / 645 规约、想拿真实报文练手的开发。资源里既有规约文档,也有配套的测试工具,意味着你不用先啃完几百页文本再动手,可以边发帧边对照条款。下面按“先立规约模型、再跑通收发、最后抠异常帧”的顺序拆。
2. 规约分层与帧结构:先把 376.1 和 645 的边界划清楚
用电信息采集系统里最容易被混为一谈的,是主站与集中器之间的 376.1 规约,和集中器/掌机与电表之间的 645 规约。这两套东西解决的是不同链路上的问题,帧格式、地址长度、校验方式都不一样。资源包里把两类规约文档和测试工具放在一起,如果不先划边界,很容易拿 645 的帧去套 376.1 的解析器,然后对着“校验错”怀疑人生。
2.1 376.1 的帧结构与控制域含义
376.1 面向的是主站与终端(集中器、采集器)之间的问答式通信,帧头是固定的68H,后面跟长度域 L,再是控制域 C、地址域 A,然后是链路用户数据,最后是帧校验和 CS 与结束符16H。控制域 C 这一个字节里塞了传输方向、启动/从动标志、帧计数位和功能码,是整帧解析的第一道坎。常见做法是先按位拆 C 域,判断这是主站下发的请求还是终端上送的回答,再决定后续按哪种功能码去解 APDU。
# 376.1 控制域按位拆解,DIR/PRM/FCB/FCV/功能码 def parse_ctrl(c_byte): dir_bit = (c_byte >> 7) & 0x01 # 传输方向:0 主站->终端,1 终端->主站 prm_bit = (c_byte >> 6) & 0x01 # 启动标志:1 启动站,0 从动站 fcb = (c_byte >> 5) & 0x01 # 帧计数位,防重复 fcv = (c_byte >> 4) & 0x01 # 帧计数有效位 func = c_byte & 0x0F # 功能码,决定 APDU 类型 return {"DIR": dir_bit, "PRM": prm_bit, "FCB": fcb, "FCV": fcv, "FUNC": func}这段拆解的逻辑说明:DIR和PRM组合起来才能确定这一帧是谁发起的,单看功能码会误判。参数上,FCB只在FCV=1时才有意义,做重发去重时要用到它;FUNC的低四位对应链路管理、数据转发等不同类别,解析 APDU 前必须先拿到它。我一般会把这个函数放在解析入口第一行,先把 C 域打成字典,后面所有分支都基于它走。
2.2 645 规约的地址域与帧格式差异
645 规约跑在电表侧,帧格式是68H + 地址域(6字节BCD) + 68H + 控制码 + 数据长度 + 数据域 + 校验 + 16H。注意它有两个68H,中间夹着表地址,这和 376.1 只有一个帧头完全不同。地址域是 6 字节 BCD 码,低位在前,做表号匹配时如果按字符串直接比会翻车,必须先反转再转十进制。控制码里区分读、写、读后续、广播等操作,数据域长度随控制码变化,没有统一长度。
| 对比项 | 376.1 | 645 |
|---|---|---|
| 帧头 | 单个 68H | 68H + 地址 + 68H |
| 地址长度 | 可变(常见 6 字节) | 固定 6 字节 BCD |
| 校验 | 帧校验和 CS | 数据域累加和 |
| 典型链路 | 主站—集中器 | 集中器/掌机—电表 |
这张表不是让你背,而是联调时贴在显示器边上用的。现场最常见的误用,是把 645 的地址域当成 376.1 的地址域去解析,结果表号永远对不上。资源包里的测试工具通常会把两类帧分开建会话,用之前先确认当前会话绑的是哪套规约。
2.3 用测试工具建立第一条可回环的会话
拿到工具后不要急着连真实设备,先做本地回环。常见做法是工具里起一个虚拟从站,主站侧发一条链路请求帧,看从站能否正确回帧并让主站解析出功能码。这一步验证的是帧组装和校验逻辑,不涉及现场信道。
# 以命令行方式启动测试工具的虚拟从站(参数名以实际工具为准) tool_cli --mode slave --protocol 3761 --port 9000 --addr 000000000001 # 另开一个终端,主站侧发一条链路登录请求 tool_cli --mode master --protocol 3761 --peer 127.0.0.1:9000 --cmd link_login逻辑说明:--mode slave让工具扮演终端,--protocol 3761锁定规约解析器,--addr是终端地址,必须和主站侧配置一致,否则从站会直接丢帧不回。--cmd link_login触发一条链路管理帧,回环成功后你能同时看到发送帧和接收帧的十六进制,这是后面排查一切问题的基准。参数上,端口只要不冲突即可,地址建议先用全零加序号,避免和现场真实地址撞车。
3. 报文收发与解析:把规约条款落成可复现的脚本
规约文档看得再熟,不落到“发出去、收回来、解出来”这三步,联调时还是抓瞎。这一章的重点是把资源包里的测试工具和自写解析脚本接起来,形成一条能反复跑的验证链路。核心思路是:工具负责物理收发和帧级抓取,脚本负责按规约拆字段、做断言。两者分工清楚,出问题时才能定位是链路层丢了还是应用层解错了。
3.1 组帧与校验:376.1 的 CS 计算与常见错位
376.1 的帧校验和 CS 是从长度域 L 开始到用户数据结束的算术累加和对 256 取模。听起来简单,但错位是高频翻车点:有人从帧头 68H 开始累加,有人把结束符 16H 也算进去,结果校验永远差一截。正确范围是 L 域首字节到 APDU 末字节,不含帧头和结束符。
def calc_cs(frame_bytes): # frame_bytes 为完整帧,含 68H 帧头和 16H 结束符 # CS 覆盖范围:长度域 L 起,到用户数据末字节止 body = frame_bytes[1:-2] # 去掉帧头 68H 和末尾 CS+16H return sum(body) & 0xFF def build_frame(addr, ctrl, apdu): l = len(apdu) + 1 + len(addr) # 长度域含控制域+地址+APDU body = bytes([l, ctrl]) + addr + apdu cs = sum(body) & 0xFF return b'\x68' + body + bytes([cs, 0x16])逻辑说明:calc_cs里frame_bytes[1:-2]这个切片是关键,[1:]跳过帧头,[:-2]去掉 CS 和结束符两个字节,剩下的正好是 CS 覆盖范围。build_frame里长度域 L 的算法容易错,它统计的是控制域、地址域、APDU 的总字节数,不含 L 自身。参数上,addr要和规约要求的地址长度一致,ctrl是已经按 2.1 节拼好的控制域字节。我一般会在组帧后立刻回算一次 CS 并打印,确认切片范围没写偏。
3.2 645 读表帧的组装与数据域解析
645 的读表帧相对固定:控制码11H表示读数据,数据域里放数据标识(如电能量、需量等)。回帧的控制码通常是91H(最高位置 1 表示从动回答),数据域前面是数据标识,后面是数据内容,长度由回帧的数据长度域给出。
def build_645_read(meter_addr_bcd, data_id): # meter_addr_bcd: 6 字节 BCD,低位在前 frame = b'\x68' + meter_addr_bcd + b'\x68' + b'\x11' frame += bytes([len(data_id)]) + data_id cs = sum(frame[0:]) & 0xFF # 645 校验为帧内累加和 return frame + bytes([cs, 0x16]) def parse_645_resp(frame): ctrl = frame[8] data_len = frame[9] data = frame[10:10 + data_len] data_id, value = data[:4], data[4:] return {"ctrl": hex(ctrl), "data_id": data_id.hex(), "value": value.hex()}逻辑说明:build_645_read里地址域直接按 BCD 字节拼,不做字符串转换,避免反转出错;控制码11H是读,len(data_id)是数据标识字节数,常见为 4 字节。parse_645_resp里frame[8]是控制码位置,frame[9]是数据长度,这两个偏移由 645 帧结构固定,但前提是地址域确实是 6 字节,否则整体偏移全错。参数上,data_id要按规约附录查,不同数据项标识不同,别凭记忆写。
3.3 用测试工具做批量帧比对与断言
单帧跑通之后,下一步是把一批用例喂进去做回归。资源包里的测试工具一般支持导入帧样本或脚本化发送,常见做法是准备一份“请求帧—期望回帧关键字段”的对照表,让工具逐条发、逐条比对,把不一致的标出来。
| 用例编号 | 请求功能码 | 期望回帧控制码 | 断言字段 |
|---|---|---|---|
| C01 | 链路登录 | 从动确认 | 地址域一致 |
| C02 | 读电能量 | 91H | 数据标识匹配 |
| C03 | 读需量 | 91H | 数据长度合法 |
这张表的作用是把“人工看报文”变成“机器判对错”。参数上,断言字段不要贪多,先卡地址、控制码、数据标识这三个最稳的,数据内容因为涉及倍率和冻结周期,初期只做长度校验。工具跑完一轮后,失败用例的原始帧要留档,这是后面排查的输入。
4. 避坑与排查:联调现场最容易翻车的五件事
规约联调的坑,八成不是协议本身难,而是细节没对齐。下面五条是我在类似资源上反复踩过的,按“现象 → 原因 → 解决”写,照着对能省不少时间。
4.1 校验和永远对不上
现象:组好的帧发出去,从站不回或回否认帧,抓包看 CS 字段和工具算的不一致。原因:CS 覆盖范围搞错,最常见是把帧头 68H 或结束符 16H 算进去,或者长度域 L 本身算错导致切片偏移。解决:先单独打印 CS 覆盖区间的十六进制,和规约条款逐字节对;长度域 L 用“控制域+地址+APDU”重新数一遍,别用整帧长度减固定值。
4.2 表号匹配不上
现象:645 读表帧发出去,回帧地址和预期表号对不上,或者干脆无回。原因:地址域是 6 字节 BCD 且低位在前,直接按字符串比较或按大端解析都会错位。解决:统一在解析入口做一次字节反转再转十进制,建一个表号映射表,发送前和接收后都过一遍同一个转换函数。
4.3 控制域功能码解错分支
现象:帧收全了,但 APDU 解析报“未知功能码”,或者把回答帧当请求帧处理。原因:只看了功能码低四位,没结合 DIR 和 PRM 判断帧的发起方向。解决:按 2.1 节的parse_ctrl先出完整控制域字典,所有分支基于DIR+PRM+FUNC三元组判断,别单看 FUNC。
4.4 测试工具会话绑错规约
现象:工具里发 376.1 帧,回环正常,一接真实电表就全错。原因:会话建立时--protocol参数没切,工具仍按 376.1 解析 645 帧,帧头两个 68H 直接被当成异常帧丢弃。解决:每建一个会话先确认规约类型,工具支持多会话时给会话命名带上规约前缀,比如3761-main、645-meter,避免手滑。
4.5 批量用例里混入脏帧
现象:回归跑一轮,个别用例失败但单帧手工发又正常。原因:用例文件里混入了带空格、换行或大小写不一致的十六进制串,工具解析时截断或补位。解决:导入前统一做一次清洗,去掉空白、转大写、校验字节数为偶数,清洗脚本作为用例入库的前置步骤固定下来。
5. 进阶:用脚本化回环把规约一致性验证跑成习惯
资源包里的测试工具能手工发帧,但真正省时间的是把它脚本化,做成一条“改一版解析器就跑一轮回环”的流水线。我的做法是:用工具的命令行模式起虚拟从站,用自写脚本按用例表逐条组帧、发送、收帧、断言,输出一份带原始帧的失败清单。这样每次改完解析逻辑,不用连现场设备就能知道有没有回归。
import subprocess, time def run_regression(cases, slave_port=9000): # 起虚拟从站,绑定 376.1 规约 slave = subprocess.Popen( ["tool_cli", "--mode", "slave", "--protocol", "3761", "--port", str(slave_port), "--addr", "000000000001"]) time.sleep(1) # 等从站就绪,实际可轮询端口 failures = [] for case in cases: frame = build_frame(case["addr"], case["ctrl"], case["apdu"]) resp = send_and_recv(frame, ("127.0.0.1", slave_port)) if not assert_resp(resp, case["expect"]): failures.append({"case": case["id"], "tx": frame.hex(), "rx": resp.hex()}) slave.terminate() return failures逻辑说明:subprocess.Popen起从站后必须留出就绪时间,time.sleep(1)是保守值,更稳的做法是轮询端口可连。build_frame复用第 3 章的组帧函数,保证回环和真实发送走同一套逻辑,否则回环通过不代表现场通过。assert_resp里按用例表的断言字段比对,失败时把tx和rx的十六进制一起留下,这是排查的唯一线索。参数上,slave_port每次跑换一个,避免上一轮进程没退干净导致端口占用。
验证方法上,我习惯先跑“全通过基线”,也就是用一组已知正确的帧确认流水线本身没问题,再换新用例。基线帧可以从资源包的示例报文里取,也可以从一次成功的现场抓包里截。基线不稳,后面所有失败都不可信。
还有一个具体技巧:把失败清单按“校验错 / 地址错 / 功能码错”分类统计,而不是按用例编号排。分类之后你会发现某类错误集中出现,往往指向同一个函数,比逐条看快得多。这个统计脚本十几行就够,但省下的时间是以小时计的。
从那以后我每次动解析器,哪怕只改一个切片下标,都强制先跑一遍脚本化回环再碰真实设备。血泪经验是,现场调试窗口很短,把能本地验证的都验证掉,剩下的才是真问题。希望帮到你。
本文还有配套的精品资源,点击获取