资讯动态

DLMS/COSEM电能表通讯协议调试实战:从建链到OBIS读取

发布时间:2026/10/16 16:19:44 来源:尧图企业网站定制
简介一份面向电能量数据采集终端研发与调试人员的DLMS电能表通讯协议中文说明手册内容对应IEC62056协议族主要解决DLMS电能表数据交互中的协议理解与落地难题。资源共1个doc文件压缩包约592KB虽体量精简但结构完整从物理层、链路层到应用层逐层拆解包含HDLC链路控制、ASN.1语法、BER与AXDR编码、AARQ/AARE连接协商并给出请求电量、瞬时量、负荷曲线、时间等数据帧实例。文档还列出COSEM、OBIS及IEC62056-42/46/53/61等参考规范方便开发者对照实现和排查问题。已有304人学习下载适合智能电网、用电信息采集等领域的初中级开发者系统掌握DLMS协议要点并可作为日常开发与现场调试的速查手册。1. DLMS电能表通讯协议一块表卡在串口助手上的现场第一次正经调 DLMS 电能表是在实验室接一块支持 DLMS/COSEM 的智能表。我以为手里有串口助手、有 USB 转 485 线就能像读 Modbus 一样把电量读出来。结果表纹丝不动换波特率、换校验位、换接线顺序全都没反应。后来才明白DLMS电能表通讯协议不是“报文格式复杂一点”那么简单它把电能表抽象成一组对象通讯前还要先建链接、再做应用关联整个流程像登录系统而不是直接读寄存器。这块协议主要解决一件事让主站系统稳定地从电能表里读出电量、电压、电流同时把费率、时钟、事件等参数管理起来。适合正在做表计集抄、能耗管理、充电桩计费对接的工程师。你需要的工具不多一块支持 DLMS 的表、一个串口/网络调试工具、一点耐心就能把最容易黑匣子的几步走通。这篇笔记按我自己调试时的顺序来写先立概念再谈建链最后把最容易翻车的几个点位挨个说清楚。2. 认识DLMS/COSEM协议栈数据对象、OBIS码和通信层次先立住想调通 DLMS第一件事不是打开抓包工具而是建立“电表是一个对象容器”的心智。DLMS/COSEM 的设计核心是把计量能力拆成一个个 COSEM 对象主站通过读这些对象来拿业务数据。报文、帧格式、波特率都是后面的事对象模型没理解后面每一步都会走偏。2.1 数据模型COSEM对象是如何描述一块电能表的COSEM 对象可以理解成表计内部的一个“功能模块”。每个对象由四部分描述类别Class、OBIS 码、属性Attribute、方法Method。类别决定这个对象是什么类型比如存储数值的 Register 类、带缩放的 ExtendedRegister 类、记录曲线数据的 ProfileGeneric 类OBIS 码是这个对象在全表里的“身份证号”属性是你可以读取的数据项方法是你可以触发的动作比如复位最大需量。以最常用的“正向有功总电能”为例它是一个 Register 类对象OBIS 码通常是 1.0.1.8.0.255。属性 2 存的是累计电量的数值属性 3 存缩放系数属性 4 存单位。主站要做的就是定位到这个对象、发起读取、把返回的字节按数据类型解析出来。对象要素说明例子类别 Class决定对象的行为和可用属性Register / ExtendedRegister / ProfileGenericOBIS 码对象在全表中的唯一标识1.0.1.8.0.255属性 Attribute可读取的数据项数值、缩放系数、单位方法 Method可触发的动作清零、冻结、复位要提醒的是不同厂商的注册表里同一个 OBIS 码对应属性的细节可能有差异。不要只凭经验写死“属性 2 就是数值”接新表时先读一遍对象清单再确认每个属性到底是什么。2.2 通信栈四层与你的表对应DLMS/COSEM 不是单层协议而是一组协议栈。从上到下可以分成四层对象模型层、应用层、数据链路层、物理层。对象模型层回答“我读的是什么东西”应用层回答“服务器返回了什么”数据链路层负责把请求变成一帧一帧的 HDLC 报文物理层就是光口、RS-485、TCP/IP 这些实际介质。调不通的时候先判断是哪一层出了问题能省掉大量瞎试的时间。层次职责出问题时的现象对象模型层用 OBIS 码定位数据对象能通讯但读出的数据对不上应用层APDU 请求/响应、关联管理AARE 拒绝或拿到错误响应数据链路层HDLC 帧组包、拆包、确认帧抓到了但校验不对或没有 UA物理层电平、光信号、串口参数电表完全没有反应HDLC 帧可以理解成“信封”APDU 是“信纸”。信封负责把信纸完整送到对方手里信纸的内容才是你真正要的读数据请求。很多人一上来就在信封里折腾字节却忽略了一个前提HDLC 链路要先建立起来应用关联要成功建立否则信纸根本送不进去。2.3 为什么拿Modbus思路调DLMS会翻车做过 Modbus 的人很容易带着老经验上 DLMS。Modbus 是寄存器模型你直接给个地址、发个功能码从机就回你两个字节DLMS 是对象模型你要先发 SNRM 建 HDLC 链路再发 AARQ 做应用关联关联通过后才能发 Get 请求。流程多了一个“会话协商”这是两套协议最本质的差别。对比项ModbusDLMS/COSEM数据寻址寄存器地址OBIS 码 类别 属性访问方式直接读寄存器先建链、再关联、再读写报文格式定长功能码报文HDLC 帧 BER 编码 APDU安全机制基本无认证可配置 HLS 高级认证多费率支持需要应用层自己约定对象模型天然支持所以用串口助手手动发一串 hex 去读 Modbus 寄存器没问题但对 DLMS 来说远远不够你得先组织 SNRM 帧、解析 UA 响应、拼装 AARQ、解析 AARE还要处理分块确认。这不是什么玄学只是协议设计目标不同DLMS 从一开始就考虑多表种、多费率、多安全级别的场景。理解这一点之后下一章就能动真格了。3. 建立DLMS链路物理参数、SNRM握手与应用关联的最小流程建 DLMS 链路分两步先在 HDLC 层建链再在应用层做关联。很多初学者把这两步混为一谈只看得到“连不上”却分不清是物理层不响应、链路层没建起来、还是应用层被拒绝。这一章按顺序把每一步说透。3.1 物理层参数与连接准备DLMS 表常见的接入方式有三种光口、RS-485、TCP/IP。光口一般用在现场抄表用红外探头对准表计的光通讯窗波特率常见从 300、600、1200、2400 开始试偶校验、1 停止位是很多表计的默认组合。RS-485 适合集中安装场景半双工常见 9600 或 19200接线要注意 A/B 别反。TCP/IP 适合厂站或实验室很多表计会开放专用的通讯端口。下表是我平时上场前会先确认的参数清单每一项都直接影响能不能建立链路。接入方式关键参数说明光口波特率、校验位、探头对准常见 300~2400偶校验 8E1RS-485波特率、数据位、停止位、终端电阻常见 9600 8N1长线需终端电阻TCP/IPIP、端口、TCP 连接超时端口号以表计文档为准准备工作里有一件事值得专门做向表计厂家确认“客户端地址、服务端地址、应用上下文名、认证方式”。这些参数不匹配后面 AARQ 必然被拒。我见过有人在现场花了两个小时调波特率最后发现只是服务端地址填错了一位纯属浪费时间。3.2 抓一次SNRM/UA握手先让HDLC链路通起来HDLC 层的建链通常从主站发送 SNRMSet Normal Response Mode开始表计收到后回一个 UAUnnumbered Acknowledgement。这一步能通说明物理参数、地址字段、控制字段基本没问题。SNRM 的构成可以拆成几段起始分隔符、长度字段、格式字符、目的地址、源地址、控制字段、校验字段。地址字段里目的地址是这帧要发给谁源地址是谁发的。我用一个示意帧来看结构实际帧的校验字段需要按帧内容计算这里用占位表示。字段长度SNRM 示意值含义起始分隔符1 字节0x7EHDLC 帧起始长度字段2 字节0xA0 0x0A后续内容长度格式字符1 字节0x93U 帧SNRM目的地址2 字节0x00 0x01服务端地址源地址2 字节0x00 0x10客户端地址控制字段1 字节0x21SNRM 控制字HCS/FCS各 2 字节按实际计算校验拿到串口助手或抓包工具里的原始 hex 后建议先写个小脚本把它拆开看而不是肉眼盯着十六进制猜。下面这段脚本按“2 字节地址 无扩展地址段”的常见情况拆帧现场抓回来的帧直接替换 hexstr 就行。def hdlc_field_dump(hexstr: str) - None: raw bytes.fromhex(hexstr) if len(raw) 12 or raw[0] ! 0x7e or raw[-1] ! 0x7e: print(不是以 7E 包裹的 HDLC 帧先检查抓包) return # 长度字段占两个字节帧内长度用低 4 位叠加 frame_len ((raw[1] 0x0F) 8) | raw[2] print(帧内容长度:, frame_len) print(格式字符:, hex(raw[3])) print(目的地址:, raw[4:6].hex()) print(源地址:, raw[6:8].hex()) print(控制字段:, hex(raw[8])) print(HCS:, raw[9:11].hex()) print(APDU:, raw[11:len(raw) - 3].hex()) print(FCS:, raw[len(raw) - 3:len(raw) - 1].hex()) if __name__ __main__: # 示意帧7E A0 0A 93 00 01 00 10 21 HCS FCS 7E hdlc_field_dump(7ea00a930001001021aabbccdd7e)脚本里最关键的是偏移计算。帧起始占了 1 字节长度字段占 2 字节格式字符占 1 字节之后才是地址。因为地址按 2 字节处理所以控制字段固定在索引 8HCS 在 9、10APDU 从 11 开始FCS 在倒数第 3、第 2 字节。如果你遇到的表计用了扩展地址段这个偏移会变输出里“目的地址”会明显不对这时候以表计实际报文格式为准调整切片即可。3.3 从UA到AARE应用关联是“登录”不是“连接”SNRM 通了只代表“线路能连通”还不代表你能读数据。接下来主站要发 AARQ应用关联请求表计回复 AARE。只有 AARE 里返回成功应用关联才算建立之后才能发 Get、Set、Action 这些业务请求。把 SNRM 想成 TCP 建连把 AARQ 想成登录认证这个类比很方便理解。下面是模拟这个时序的 Python 伪代码。实际工程中帧收发依赖串口库或网络库但流程骨架是固定的。# 伪代码只表达 DLMS 连接时序具体编解码依赖协议栈 link open_transport() # 打开串口/TCP link.send(encode_hdlc(0x93, client_addr, server_addr)) # SNRM ua link.wait_frame(timeout5) assert ua.control 0x73, UA 未返回HDLC 链路没建立 link.send(encode_hdlc(0x60, client_addr, server_addr, aarq_payload)) # AARQ aare link.wait_frame(timeout10) if aare.result_value() ! 0: raise RuntimeError(关联被拒错误码: %X % aare.result_value()) print(关联成功接下来可以发 Get/Set/Action 请求)这里的 0x60 是 AARQ 的常见应用层标签UA 的控制字段 0x73 也是 HDLC 层常见约定。实际表计里AARQ 里还会带应用上下文名、客户端 SAP、服务器 SAP、认证方式等字段任何一个和表计配置不一致AARE 都可能回拒绝。所以做工程对接时不要只想着“把帧拼出来”先确认这组协商参数拿到没有。注意有些表计要求必须先收到合法的 SNRM 才响应 AARQ。跳过 SNRM 直接发 AARQ表计可能直接丢弃整帧这也是很多“发请求没响应”案例的原因之一。4. 用OBIS码组织读取请求从Get命令到数值解析的完整链路链路建立起来之后真正干活的部分是业务数据的读取。这一章从 OBIS 码的组成讲起再拆一次 Get 请求的字段最后落到响应解析。读完这一章你应该能独立完成“读一块表的正向有功电量”这个小任务。4.1 OBIS码找到你要读的对象OBIS 码由 6 组数字组成每组代表不同含义。A 组表示用途电能是 1B 组表示通道或费率C 组最重要表示被测量的物理量比如 1.8 是电量、2.8 也是电量但代表反方向、31.7 是电流、32.7 是电压D 组表示数据的聚合方式E 组表示费率F 组一般是 255代表总或者当前。对象含义OBIS 码正向有功总电能1.0.1.8.0.255反向有功总电能1.0.2.8.0.255正向无功总电能1.0.3.8.0.255反向无功总电能1.0.4.8.0.255A 相电压1.0.32.7.0.255A 相电流1.0.31.7.0.255这个表是大家常用的对象映射但不是所有厂商都负责把对象按这里一一配齐尤其电流、电压这类瞬时量不同表计可能在通道号上有出入。新表到手先读对象清单不要假设某个 OBIS 码一定存在。4.2 一次Get请求怎么组织DLMS 里读数据的核心请求是 Get-Request。以读正向有功总电能为例对象是 1.0.1.8.0.255属性通常取 2。请求里要包含对象类别、OBIS 码、属性编号还要带 invoke-id 来关联请求和响应。这里发出去的是 APDU 内容实际传输时还要包上 HDLC 帧头帧尾产生校验字段如果启用了加密还要先加密再组包。字段区域说明典型值/取值Get-Request 标签标识这是一次普通读请求0xC0 附近invoke-id请求关联号响应里原样返回0xC1、0xC2 递增object class-id对象类别编号按对象清单OBIS 码要读的数据对象1.0.1.8.0.255attribute-id读第几个属性2组装的时候最常犯的错是把 OBIS 码写成十进制 ASCII比如把 1.0.1.8.0.255 直接转成字符串字节。OBIS 码在报文中应该按 6 个字节的裸数字编码而不是文本。这也是很多人照着文档拼报文却一直失败的原因。4.3 响应里的数值解析不要上来就转float请求发出去表计返回的是一段 BER 编码的响应数据本身带类型标签。读电能数据时常见的是 double-long-unsigned也就是 4 字节无符号整数标签 0x06读设备标识时常见 octet-string标签 0x09。解析时要先看标签再决定按什么方式解码不能拿到 4 个字节直接用 float 去解释。def dlms_value_from_payload(payload_hex: str): data bytes.fromhex(payload_hex) # 简化示意跳过 Get-Response 容器标签后定位数据标签 tag data[0] if tag 0x06: # double-long-unsigned1字节标签 1字节长度 4字节数值 length data[1] if length 4: return int.from_bytes(data[2:6], big) return None if tag 0x09: # octet-string按长度读取原始字节 length data[1] return data[2:2 length].hex() if tag 0x0C: # visible-string可能是 ASCII 文本 length data[1] return data[2:2 length].decode(ascii, errorsignore) raise ValueError(暂不支持的标签: 0x%02X % tag) if __name__ __main__: print(dlms_value_from_payload(0604 000003E8.replace( , )))这段代码的价值不是让你直接抄进生产项目而是演示一个关键习惯按数据标签解析。电能值一般是大端无符号整数解析出来的数还要乘上对象的缩放系数才是业务值。漏掉缩放系数是读出来的数字“看起来差不多但永远差几位”的主要原因属于典型的血泪经验。注意真实响应最外层还有 Get-Response、Data-Result 等容器标签需要一层层解到数据标签。上面代码为了表达解析逻辑省略了外层剥壳工程实现时要逐步定位不能直接取 payload[0]。4.4 先读对象清单再建正式任务拿到一只陌生表别急着按需求清单去读。先读一遍对象列表把支持的类、OBIS 码、属性定义拉出来和需求对照后再开发。对象列表在不少表计上可以通过读固定 OBIS 码拿到具体码位以表计文档为准。这个步骤能把“读错了对象”“属性编号猜错”“数据类型理解错”这三个问题一次性排除掉一大半。读对象列表这一步完成后你就知道这只表支持哪些业务量、哪些在哪个属性、每个属性返回什么类型。之后再写正式的采集任务基本就是一锤定音。把这一步放在所有开发前面是我个人在 DLMS 项目里学到的最值得坚持的做事顺序。5. DLMS调试避坑五连从链路不通到读数错位的排查路径DLMS 调试难不是因为帧格式真的复杂到学不会而是问题会伪装成各种样子。这一章把最常见的五个问题按“现象、原因、解决”写清楚每一条都是现场真实会遇到的。对照一下你卡在哪一步能少走很多弯路。5.1 现象SNRM 发出后电表无响应现象很直白主站发了 SNRM帧看起来没问题但表计完全没有反应UA 一直不来。这时候最容易被怀疑成“协议栈写错了”实际上大部分原因是物理层参数没对上。原因有几种光口探头没对准红外收发位置偏了波特率和表计不一致常见表计默认 300、600、1200、2400不是随便选 9600 就能通RS-485 的 A/B 线接反TCP 场景下 IP 通但端口不对。还有一种少见但存在的情况表计刚上电还在启动没有进入通讯服务状态。解决思路是先排除物理层。光口场景从 300 波特开始逐档试同时确认偶校验、停止位RS-485 场景用万用表量一下 A/B 间电压正常空闲差压在 1V 以上TCP 场景先 ping 通再 telnet 到约定端口看能不通。物理层确认没问题再去抓帧看是否发出了正确的 HDLC 帧头 0x7E。5.2 现象SNRM/UA 正常但 AARE 被拒SNRM 和 UA 都成功了说明 HDLC 链路已经建好但发完 AARQ 之后返回的 AARE 结果不是成功状态或者干脆超时。这是“物理层通了、应用层被拒”的典型表现。原因是协商参数不一致客户端地址和服务端地址没有对齐AARQ 里带的认证方式表计不支持应用上下文名不匹配或者表计已经有一个未释放的关联会话。还有些表计默认要求高等级安全认证你以无认证方式发起关联自然会被拒。解决方法是把 AARQ 里的每一项配置都核对一遍尤其是应用上下文名和认证方式。如果表计文档提供了“客户端连接参数确认表”照着逐项填比猜更靠谱。遇到“关联会话未释放”的情况先发 ABORT 释放旧会话等几秒重新发起关联大多能恢复。5.3 现象读请求超时或收到分块长度超限应用关联建立成功发 Get 请求也正常但某些对象读不出来一直超时或者抓到的响应里有分块标记但客户端没处理。这通常是块大小和窗口大小协商不一致导致的。原因在于DLMS 的大数据对象比如负荷曲线、事件记录返回数据可能很长。主站和表计之间有一个 negotiated block size一次只能传一块块传完要客户端确认后再传下一块。如果客户端没有按块发送确认帧服务端会一直等表现出来就是超时。解决办法是把客户端的块大小调小常见从 16 或 32 开始先保证小数据量对象能读通同时确认客户端实现了分块确认逻辑不要只处理单块响应。在抓包里能明显看到“response 带分块标记客户端没有下一步动作”时优先补这个逻辑。5.4 现象通讯正常但读出的数值和表显对不上链路全通AARE 成功Get 也拿到响应但读到的数字和表上显示的不一致。比如表上显示 1234.56你读回来的是 123456或者完全错位。能通讯但数据错问题几乎都出在对象映射和解析环节。原因通常有几类OBIS 码选错比如把反向有功当成正向有功读了属性编号选错读到的不是当前值而是其他数据没有按数据类型解析把 4 字节当成 8 字节读更常见的是漏了缩放系数读出来的是整数原始值没有乘上表计定义的比例因子。解决方法是回到对象清单确认目标对象的 OBIS 码、类 ID、属性 ID、数据类型、缩放系数再按清单逐项解析。解析时不要眼睛盯着 hex 猜把标签识别做出来每个对象单独验证数据对不上的问题很快能定位到具体环节。5.5 现象运行一段时间后连接悄悄断掉采集程序跑了一段时间突然开始报错重连也连不上。重启程序又能恢复过几小时又断。这种情况多半不是网络问题而是应用关联会话有超时表计主动断开了会话。原因有两个一是表计侧对关联会话设置了空闲超时一段时间没有请求就自动释放二是客户端断线后没有正确释放旧关联重新发起 AARQ 时表计还在维护旧会话因此拒绝新关联。解决方法是给客户端加心跳机制定时发送一个轻量的读请求比如读当前时间或一个标量对象保持会话活跃同时在重连逻辑里收到 AARE 拒绝时先发送 ABORT 释放旧会话再重新走关联流程。生产环境的采集程序应该把重连做成状态机而不是简单循环发请求。6. 进阶验证用模拟服务端跑通时序再养成抓包习惯DLMS 客户端开发最危险的阶段是直接带着代码上现场面对一个不会告诉你“你哪里错了”的黑盒子。更稳的做法是在实验室先用模拟服务端把客户端时序跑通再把抓包变成习惯。实验环境里可以搭一个最小模拟服务端按状态机回包收到 SNRM 回 UA收到 AARQ 回成功 AARE收到 Get 请求回固定数据。客户端的时序正确性在这个环境里能快速验证比直接上真表高效得多。下面这个对照表可以用来快速定位客户端状态。客户端当前状态期望服务端响应收不到时先查什么刚发 SNRMUA物理参数、地址字段刚发 AARQAARE应用上下文名、认证方式刚发 GetGet-ResponseOBIS、属性编号、分块逻辑另一个值得坚持的习惯是把“抓包”放在“撸代码”前面。到现场无论多急先抓一帧表计返回的原始报文确认帧结构、地址、标签都符合文档再开始写业务代码。很多通讯问题不是逻辑复杂而是从一开始就在错误的方向上反复试错。我自己第一次做 DLMS 接入时上来就写完整客户端结果在光口波特率上耗了整整两天后来才发现只是 2400 和 4800 的档位和表计配置不一致。那次以后我养成了习惯进现场先抓一帧对着帧结构确认参数再谈后续开发。协议是不会骗人的骗人的往往是我们跳过步骤的急躁。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑