DLMS电能表抄表协议拆解:HDLC帧、三层建链与AXDR编码

发布时间:2026/10/27 4:52:53
DLMS电能表抄表协议拆解:HDLC帧、三层建链与AXDR编码 简介《DMLS协议中文版》是一份面向电能量数据采集终端开发、调试与协议对接人员的IEC62056协议族中文说明手册重点解决采集器与电能表通讯中的协议理解、报文分析与联调排错问题。文档按物理层、链路层、应用层三层模型展开梳理Client-Server通讯流程、HDLC链路层的地址校验、帧长校验、CRC校验及长帧拆包组包机制并深入讲解应用层的ASN.1语法、BER与AXDR编码、AARQ/AARE连接帧以及数据请求过程。资源包共1个doc文件约620KB篇幅集中适合按协议层次查阅。已有870人学习可作为协议入门和现场联调的参考资料其中请求电量、瞬时量电压、电流、功率、负荷曲线和时间的实例报文能帮助读者对照理解实际交互细节提升报文解析与问题定位效率。适合采集终端研发、电表协议适配及现场运维人员作为案头参考。1. 一帧 25 字节的报文里没有半个电量值却决定了整次抄表打开一份电能表串口抓包第一条有实际内容的帧长这样7e a0 21 00 22 00 23 03 93 0b 14 81 80 12 05 01 80 06 01 80 07 04 00 00 00 01 08 04 00 00 00 01 07 65 5e 7e。三十多个字节里没有电压、没有电流、没有电量全是地址、控制字和一堆05 01 80这样的参数标识——可要是这一帧发不出去后面的数据一个都读不回来。这就是 DLMS 协议族资源标题写作 DMLS正文通篇是 DLMS指的是同一套 IEC 62056 协议族最反直觉的地方它把「线路通」和「拿到数」彻底切开物理层通、链路层通、应用层连接建立三层各自确认完才轮到 class_id 和 OBIS 出场。这份中文说明手册的价值正在于它用抓包把这三层怎么一层层扣起来讲清楚了而不是丢一堆 ASN.1 定义让人自己猜。2. HDLC 帧解剖0x7e 之间的每个字节都在解决一个具体问题链路层用的是 HDLC一帧用两个0x7e夹住。很多人第一次看会疑惑既然帧尾已经有 16 位 CRC 了为什么帧头还要再加一个 HCS答案藏在「分段」这件事上——一帧的帧头如果坏了接收方不必等整帧收完、算完 FCS 才发现问题当场就能丢弃省掉一次无效等待。这种「提前判错」的思路在窗口小、超时长的电力通道里非常划算。整帧的字段布局是这样的从帧头到帧尾依次是帧类型与帧长、目的地址、源地址、控制字、HCS、信息域、FCS位置字段长度作用1Flag1 字节固定0x7e2帧类型与帧长2 字节帧类型判别、分段位 S、帧长3目的地址域1/2/4 字节由扩展编址规则决定4源地址域1/2/4 字节同上5控制字1 字节帧类别、帧计数、P/F 位6HCS2 字节帧头 CRC7信息域0N 字节可含 LLC 头 应用层数据8FCS2 字节整帧 CRC9Flag1 字节固定0x7e2.1 帧类型与帧长字段S 位如何表达「这只是一段」这两个字节不是随便拆的位域划分相当紧凑高 4 位恒为1010也就是 Frame Type 3这是 DLMS 选定的帧格式第 11 位单独留给 S低 11 位才是长度单位是字节且不含首尾两个0x7e。bit15 bit14 bit13 bit12 | bit11 | bit10 .................. bit0 1 0 1 0 | S | Frame Length Frame Type A 分段位 帧长不含 0x7e拿建链报文里的a0 21验算一下a0展开是1010 0000高四位1010对得上S 位是 0 表示这是一段完整的、没有被分割的帧0x021等于 33正好是从目的地址到 FCS 的字节数。如果把这两个字节换成a8 21那 S 位就是 1接收方立刻知道后面还有人得先挂起等下一段。提示帧长只统计两个0x7e之间的内容。自己写解析器时最容易在这一步翻车——把 Flag 也算进去长度永远差 2校验自然对不上。2.2 地址域扩展编址把 client 锁死在 128 个地址HDLC 用的是扩展编址一个地址字节的最低位是「还有没有后续字节」的标志0 表示没完、后面还有1 表示到此结束。这样地址长度就能自由伸缩但同时带来一个硬约束——每个地址字节只剩 7 位有效数据。client 端地址永远只占一个字节最低位恒为 1所以整个系统里 client 最多只能有 128 个。server 端为了做到「一个物理地址对应多个逻辑地址」把地址拆成 upper HDLC address逻辑和 lower HDLC address物理两段组合方式有三种结构字节数组成只有 upper1upper 一字节upper lower2upper 一字节、lower 一字节upper lower 加宽4upper 两字节、lower 两字节理论上还能更长但实践中有上限。现场某款三相表经实测只有四字节的 server 地址结构可用其余两种发过去要么不回 UA要么回的地址域对不上。建链报文里00 22 00 23就是这样一个四字节目的地址而03是 client 的一字节地址。这里有个反直觉的细节地址域的顺序是目的在前、源在后所以 client 发出去的帧第一个地址是 server 的server 回的 UA 帧第一个地址反而变成了 client 的0300 22 00 23挪到了后面。抓包对照的时候如果没意识到这一点很容易以为电表回错地址了。2.3 控制域RRR/SSS 帧计数与 P/F 位控制字这一字节承担三件事区分帧类别、维护收发帧计数、携带 P/F 位。HDLC 的帧分三类DLMS 用到的都在下面这张表里帧类型控制字示例用途Ibit0 0信息传输帧真正携带用户数据RR0x01/0x11准备接收用来要下一段或确认RNR—接收未就绪相当于忙帧SNRM0x93设置正常响应模式建链请求UA0x73对 SNRM 和 DISC 的响应DISC0x53结束链路DM0x1F已处于断开状态UI0x03保活不影响链路状态FRMR0x87拒绝接收原因不明I 帧的控制字里bit7bit5 是发送帧计数原文记作 SSSbit3bit1 是接收帧计数原文记作 RRRbit4 是 P/F。三个比特意味着计数范围只有 07超过就回绕。计数的推进规则是这套协议里最容易出错的地方值得单独说清楚链路层连接建立之后client 第一次请求数据包括发 AARQ 那一帧RRR 置 0、SSS 置 0server 收到后返回响应RRR 变成 1、SSS 还是 0client 再次请求时两个计数各加 1变成 RRR1、SSS1server 再回RRR2、SSS1。如此往复直到数据取完。整个过程完全靠 I 帧完成RR 帧不推进这个序列它只做确认。P/F 位分两个方向理解Poll 位由 client 置置 1 表示「请你回话」置 0 表示「不用回」Final 位由 server 置置 1 表示这一轮发送结束置 0 表示后面还有。要注意 Final 位只有在窗口大于 1 时才有意义窗口等于 1 的时候server 返回的每一帧这一位都恒为 1因为一帧就是一整轮。2.4 帧头校验与 16 位 CRC为什么头部要单独兜一层HCS 覆盖的范围是帧类型与帧长、目的地址、源地址、控制字这四段FCS 覆盖的是两个0x7e之间的全部内容。两者用的都是 16 位 CRC多项式是x^16 x^12 x^5 1也就是常说的 CRC-16-CCITT具体实现可以参考 IEC 62056-46 的附录 A。def hdlc_fcs(data: bytes) - int: HDLC FCSCRC-16-CCITTpoly0x1021init0xFFFF结果取反。 采用反射算法0x8408 是 0x1021 的位反转形式。 crc 0xFFFF for byte in data: crc ^ byte for _ in range(8): if crc 1: crc (crc 1) ^ 0x8408 else: crc 1 return crc ^ 0xFFFF frame_body bytes.fromhex( 002200230393 0b14 # 地址 控制字 HCS 81801205018006018007040000000108040000000107 ) print(hex(hdlc_fcs(frame_body))) # 与帧尾两字节比对这段代码有三个地方值得盯住初值必须是0xFFFF而不是 0多项式要用反射形式0x8408直接拿0x1021右移会算错最后结果要取反。发送时 CRC 的低字节在前所以抓包里看到65 5e实际数值是0x5e65。如果算出来和帧尾对不上先检查是不是把首尾两个0x7e也喂进去了——这是血泪经验里排名第一的低级错误。3. 建链、参数协商与长帧重组一次抄表为什么要握手三次物理层通了不等于能通信链路层通了也不等于能取数。DLMS 的三层结构决定了它必须建立两次连接一次在链路层靠 SNRM 和 UA 完成一次在应用层靠 AARQ 和 AARE 完成这个过程叫 Negotiation。两次握手各管一摊事缺一次都拿不到数据。3.1 SNRM 与 UA逐字节拆一帧建链报文把开头那帧建链报文摊开看每个字段的职责一目了然字节值含义Flag7e帧头类型与长度a0 21Frame Type AS0长度 33目的地址00 22 00 23server 地址四字节结构源地址03client 地址一字节控制字93SNRMHCS0b 14帧头校验运行控制域81 80SNRM 标识参数组长度1218 字节参数 105 01 80最大发送信息域长度 128参数 206 01 80最大接收信息域长度 128参数 307 04 00 00 00 01发送窗口 1参数 408 04 00 00 00 01接收窗口 1FCS65 5e整帧校验Flag7e帧尾信息域的编码方式很规整一个字节的参数标识、一个字节的参数长度、然后是参数值。05表示「最大发送信息域长度」长度域是01值只有一字节80也就是 128 字节。窗口参数不一样长度域是04值占四个字节00 00 00 01按大端读就是 1。SNRM 里的用户信息其实可以整段不写含义是「我接受你server已经配置好的参数」。这在现场调试时很有用不确定电表支持多大的帧就先发一个不带参数的 SNRM 探路。server 回的 UA 帧长得很像但地址顺序反了过来7e a0 21 03 00 22 00 23 73 28 f0 ... 53 3b 7e。源地址成了03目的地址成了00 22 00 23控制字73是 UAHCS 从0b 14变成了28 f0。UA 后面跟着的参数组就是协商结果client 必须按这个结果调整后续发送不能还按自己原来的想法打包。3.2 四个链路参数为什么默认窗口只有 1UA 帧里能协商的链路参数一共四个分成发送和接收两个方向参数标识名称默认值说明05transmit maximum information field length128 字节本方一帧能装多少用户数据06receive maximum information field length128 字节本方一帧能收多少用户数据07transmit window size1本方一次能连发几帧08receive window size1本方一次能收几帧窗口默认给 1 不是偷懒是权衡。窗口大于 1 意味着发送方要缓存在途帧、接收方要能重排乱序、双方还要维护更复杂的确认逻辑而电表主控的 RAM 通常以 KB 计多缓几帧就是实打实的成本。窗口为 1 时协议退化成停等模式一发一收逻辑最简单出错也最好定位。真正决定吞吐的是信息域长度从 128 往上谈比调窗口划算得多。3.3 长数据帧S 位与 RR 帧的接力用户数据超过 128 字节一次装不下就得启分段流程。规则不复杂发送方把大数据包切成若干段除最后一段外每段的 S 位置 1接收方看到 S1就知道还没完回一个 RR 帧要下一段直到收到 S0 的那一段为止。def reassemble(frames): 把带 S 位的 HDLC 分段帧拼回完整应用层数据 buf bytearray() for f in frames: seg_bit (f[1] 4) 0x01 # 帧长字段高字节的第 4 位即 S 位 buf.extend(f[payload_start:f[1] 0x0F]) # 这里只示意取信息域 if not seg_bit: # S0 表示最后一段 return bytes(buf) # S1此处应向对端回 RR 帧请求下一段 return None # 没等到最后一段调用方须按超时处理真正的工程实现里这个循环要配三重保护段序号连续性检查、单段超时重发、总段数上限。读负荷曲线一定会走分段因为曲线数据动辄几百上千字节而信息域默认只有 128 字节。曲线读不全的故障十有八九是这里没处理干净——要么把第一段当成了全部要么 RR 发早了导致段序错乱。3.4 帧计数与结束链路为什么 DISC 之前要先发 RR数据取完之后不能直接甩一个 DISC 过去。手册里明确写了请求数据结束后还要再发一个 RR 帧收到确认之后才可以发 DISC 结束链路。这一帧 RR 的作用是「清账」。前面每个 I 帧都推进了收发计数最后发一个 RR等于告诉 server我这边所有帧都收到了你可以把状态清掉。少了这一步server 端的计数停在半路下次建链时如果 client 从头计数两边就可能对不上表现就是「第一次抄成功、第二次一直超时」这种玄学故障。DISC 发出去之后server 回 UA 表示接到指令后正常断开回 DM 表示它本来就已经断开——这两种响应都算正常抓包时别把 DM 当成错误。断开之后如果还要再抄一次必须重新走完整的 SNRM/UA 流程不能复用上一次的链路状态。4. 应用层ASN.1 语法、BER/AXDR 双编码与 OBIS 寻址链路通了只是修好了路跑什么车由应用层决定。DLMS 应用层最特别的一点是它不用固定表格定义帧格式而是用 ASN.1 这门抽象语法语言描述结构再靠 BER 和 AXDR 两套编码把结构落成字节。抽象带来通用性也带来了理解成本——但一旦看懂后面所有帧都是同一套语法的实例。4.1 ASN.1 语法骨架tag、IMPLICIT/EXPLICIT 与 SEQUENCE/CHOICE一个 ASN.1 描述大致长这样名字、标签、数据类型大括号里是各个数据项。标签里的 class type 分四种决定了这个名字的适用范围class type取值含义Universal00在所有 DLMS 应用中含义唯一Application01含义与具体应用相关Context-specific10含义依赖上下文同一结构里可能代表不同东西Private11厂家自定义范围IMPLICIT 和 EXPLICIT 描述的是子结构对父结构标签的继承关系IMPLICIT 会替换掉父结构的 tagEXPLICIT 则保留。不写默认为 EXPLICIT。这个区别直接决定编码时要不要多一个包装层写错一个关键字解析就整体偏移。数据类型分简单型和复合型简单型的 tag 是固定的比如 INTEGER 是0x02、OCTET STRING 是0x04、OBJECT IDENTIFIER 是0x06、SEQUENCE 是0x10、SET 是0x11。复合型里最常用的是两个SEQUENCE 表示内容按顺序排列CHOICE 表示从若干候选里挑一个。请求数据时最常用的 GET-Request 就是一个典型的 CHOICEGET-Request :: CHOICE { get-request-normal [1] IMPLICIT Get-Request-Normal, get-request-next [2] IMPLICIT Get-Request-Next, get-request-with-list [3] IMPLICIT Get-Request-With-List }三个选项只能填一个。取单个属性用 normal连续读用 next批量读用 with-list。看懂这个 CHOICE就等于看懂了应用层请求的分类方式。4.2 BER 编码TLV 三元组与标识字节的位域BER 的编码结构是「标识 长度 内容」三层内容里可以再嵌一层同样的结构。标识字节的前几位有明确分工高两位是 class第三位区分简单类型和复合类型剩下的是长度。拿 AARE 响应帧的开头几个字节实拆一遍61 AARE 的 tag[APPLICATION 1] 42 整体长度 0x42 A1 application-context-name 的 tag 09 长度 9 06 OBJECT IDENTIFIER 07 长度 7 60 85 74 05 08 01 01 OID 内容 A2 result 的 tag 03 02 01 00 Association-result 0接受 A3 result-source-diagnostic 的 tag 05 A1 03 02 01 00 Associate-source-diagnostic61这个字节拆开看01是 Application class1是复合类型标志00001是 tag 编号 1合起来就是[APPLICATION 1]。后面的A1、A2、A3都是 Context-specific 的标签编号分别是 1、2、3正好对上 ASN.1 定义里的[1]、[2]、[3]。长度域有个坑小于 128 时就是一个字节大于等于 128 时要用长形式首字节最高位置 1低位表示后面跟几个字节的长度。AARE 这种小帧碰不到但一旦协商出更大的 PDU请求帧里就会出现81 xx这样的编码解析器如果只认单字节长度当场就崩。4.3 AXDR 编码为什么 XDLMS 放弃「标识 长度」DLMS 里其实有两套协议用 ASN.1 描述的 DLMS 用 BER 编码用 ASN.1 描述的 XDLMS 用 AXDR 编码。只有 AARQ 和 AARE 的部分内容走 DLMS绝大多数应用层数据通信走 XDLMS。AXDR 是对 XDR 的扩展核心思路是「语法已经静态确定了编码里就别再重复类型和长度」。同样两个整数0x1234和0x5678两套编码的差别非常直观BER 编码 30 08 SEQUENCE长度 8 02 02 12 34 INTEGER长度 2值 0x1234 02 02 56 78 INTEGER长度 2值 0x5678 共 12 字节 AXDR 编码 11 SEQUENCE 标识 12 34 A 的值大端直接写 56 78 B 的值大端直接写 共 5 字节省掉的正是「每项的类型标识」和「每项的长度」。因为解析方手里有同一份 ASN.1 定义知道第一个字段一定是 Integer16、第二个一定是 Unsigned16照着顺序读就行。代价是编码和解码必须严格对齐同一份语法任何一方版本不一致读出来的就是一堆垃圾数。注意AXDR 里的整数是大端21 34就是 0x2134不做任何包装也不做字节序转换。把 OCTET STRING 当 INTEGER 解、或者把大端按小端读是数值异常的常见来源。4.4 一帧 AARE 的完整拆解从 61 42 到 21 34最有意思的是 AARE 这帧——它的前半段是 BER后半段的 user-information 里装的是 AXDR一帧里两套编码并存。61 42 AARE tag 长度 A1 09 06 07 60 85 74 05 08 01 01 application-context-name A2 03 02 01 00 result 0 A3 05 A1 03 02 01 00 result-source-diagnostic 88 02 07 80 ACSE-requirements 89 07 60 85 74 05 08 02 01 mechanism-name AA 0A 80 08 41 42 43 44 45 46 47 48 authentication-value BE 04 user-information 的 CHOICEOCTET STRING 0E 内容长度 08 InitiateResponse 的 explicit tag 00 proposed-quality-of-service 未使用 06 negotiated-dlms-version-number 5F 1F [APPLICATION 31] tag 04 长度 00 00 08 1D conformance 位串 21 34 server 端 PDU 尺寸上限大端读作 0x2134 00 07 00 07 LN2 相关的两个 Unsigned16BE 04这两个字节是切换点BE是 user-information 的 CHOICE 标签04说明里面装的是 Universal 的 OCTET STRING。从这里往后的08 00 06 5F 1F ...全部按 AXDR 解析08是 InitiateResponse 的 explicit tag5F 1F是[APPLICATION 31]。这就是为什么很多解析库要把一帧拆成两段处理外层按 BER 走进了 user-information 换 AXDR 的解码器。用一套解码器硬啃整帧前面能过、后面必错。4.5 OBIS 与 class_id应用层怎么唯一定位一个数据项链路通了、应用层连接也建好了接下来的问题变成「我要的那个数在哪儿」。DLMS 的答案是二维坐标class_id 说明这是哪一类对象OBIS 说明是这一类里的哪一个。请求电量、请求瞬时量电压、电流、功率、请求负荷曲线、请求本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询