资讯动态

DLMS/COSEM协议实战精讲:从HDLC帧到智能电表联调

发布时间:2026/10/27 15:18:56 来源:尧图企业网站定制
简介这是一套面向自动抄表系统开发者的DLMS/COSEM通信协议完整资料包同时附带HDLC协议文档与实现源码适用于电能表通信、AMI/AMR系统集成及IEC 62056标准学习也适合电力计量方向的嵌入式工程师系统查阅。压缩包共161个文件大小约31.15MB以C语言源码为主含60个.h头文件与39个.c源文件覆盖任务队列、定时器、AES加密、关联管理、事件组等底层协议栈模块另有mk/makefile构建脚本、PDF与DOC格式的中英文协议规范、CPP示例及说明文档方便对照标准代码进行二次开发。目前已有802人学习资源内中英文文档兼备并给出基于该标准设计满足AMR发展要求电能表的完整思路。读者可从中获取协议栈实现、HDLC链路层帧处理、构建配置及协议解读理解应用层数据模型与链路层传输流程为自主移植和定制提供扎实参考。1. DLMS/COSEM 通信协议到底是什么为什么电表项目绕不开它做智能电表集中器或者能源管理系统的工程师十有八九会在项目中期被 DLMS/COSEM 通信协议绊一跤。它不是像 Modbus 那样“发地址、读寄存器”的简单主从协议而是 IEC 62056 标准体系下的一整套计量设备通信规范DLMS 负责应用层会话怎么建、数据怎么请求COSEM 负责计量数据怎么建模、OBIS 编码怎么编。再加上底层用到 HDLC 帧传输三块内容叠在一起让不少初次接触的人盯着文档看了两周也不敢动手。这个协议解决什么问题呢一句话把电表、水表、气表、热量表的数据采集方式统一起来。带电能的读电能带瞬时量的读瞬时量跨厂商跨型号都一样。这篇笔记按我自己的落地路径来写先把协议栈拆开再搭一个最小联调环境最后把手上的文档和源码真正跑起来。适合正在做表计嵌入式开发、智能网关采集或者想从 Modbus 方案往标准规约迁移的团队。2. 先拆协议栈DLMS/COSEM 的分层结构与 HDLC 帧格式拿到 DLMS/COSEM 文档和源码第一步不是编译而是先把协议栈的分层看清楚。这个协议常常让新手觉得“看着像 TCP/IP又像串口协议”因为它在不同章节里同时出现了 HDLC、APDU、接口类、OBIS 这些词。实际上 DLMS/COSEM 是一个完整的分层体系HDLC 只是其中一条数据链路搞清楚这一层后面看报文才不会乱。2.1 DLMS 和 COSEM 的关系一个管“会话”一个管“数据模型”DLMSDevice Language Message Specification定义的是“设备怎么说话”它规定了一次数据访问的会话流程怎么建立连接、怎么认证、怎么发请求、怎么收响应。COSEMCompanion Specification for Energy Metering定义的是“数据长什么样”它把电表里的电压、电流、电能量、费率、事件统统抽象成接口类Interface Class和属性Attribute再用 OBIS 编码给每个数据项一个唯一坐标。打个比方COSEM 是数据库的表结构DLMS 是访问这个数据库的 SQL 引擎。两者合起来才叫 DLMS/COSEM。对应的规范文档也很固定DLMS User Association 发布的标准里蓝色书Blue Book讲 COSEM 接口类黄色书Yellow Book讲整体架构与协议绿色书Green Book讲应用层细节而 IEC 62056-46 单独定义 HDLC 链路层IEC 62056-47 定义 TCP/IP 传输。这里要强调一点DLMS/COSEM 不绑定某一种物理层。常见做法里它既可以跑在 TCP 端口 4059 上也可以跑在串口上。我见过不少项目把 HDLC 跑在 uart 通信协议之上用于 UART 接入的载波模块或者直连表计在嵌入式端把 DLMS/COSEM 跑在 STM32 通信协议栈上也是成熟方案。所以你在移植源码时重心应该放在协议栈本身物理层反而是最容易替换的部分。2.2 HDLC 帧结构从标志字段到 FCS一个字节都不要看错HDLCHigh-Level Data Link Control在 DLMS/COSEM 里承担数据链路层职责。它的帧结构很紧凑每一个字节都有明确职责少了转义处理或者算错一个 FCS整个帧就会被丢。DLMS/COSEM 的 HDLC 帧体大致如下字段长度说明开始标志1 字节固定 0x7E帧格式1 字节bit7 表示分帧标志bit6 表示是否还有后续分帧目的地址变长分段编码最高位表示地址是否继续源地址变长同上控制字段1 字节区分 I 帧、S 帧、U 帧包含发送序号和接收序号HCS2 字节头部校验覆盖到控制字段为止信息字段长度1~2 字节LPDU 长度信息字段变长应用层 APDU 数据FCS2 字节帧校验覆盖整个帧体结束标志1 字节固定 0x7E最容易踩的坑是转义。帧体内如果出现 0x7E 或 0x7D必须做转义处理避免和帧标志冲突。常见做法是 0x7E 编码成 0x7D 0x5E0x7D 编码成 0x7D 0x5D。抓包时如果不先还原转义直接按 0x7E 切帧会把一帧切成好几段。地址字段也很容易看花眼。DLMS 用的是分段地址编码每一字节的 bit7 是扩展位bit0 是校验位真正有效的只有中间 6 位。所以报文里看到的地址字节和表计上配置的逻辑地址不是直接相等的。客户端地址、服务端地址必须分别配置抓包时也要先按规则解码再核对配置。控制字段里则标明了当前帧是信息帧还是监控帧分帧报文的帧序号也在里面这些在下一章联调时都会用到。2.3 一次完整的数据读取AARQ/AARE 协商然后才是 Read/Response新手最容易犯的错是拿 TCP 调试工具直接往 4059 端口发字节期望立刻收到数据。DLMS/COSEM 不是这样的它先要建立应用层关联也就是 AARQ/AARE 协商。一次完整的读数据流程是这样的客户端先发送 AARQAssociation Request里面带上了协议版本、协商的最大帧长度、认证方式等参数服务端收到后回 AAREAssociation Response表示接受或拒绝这次关联。AARE 成功后客户端才能发 Read Request请求里指定 OBIS 码和属性编号服务端返回 Read Response里面才是真正的计量数据。读完之后客户端还要发 Release Request 释放连接。用序列表达就是Client - Server: AARQ Server - Client: AARE Client - Server: ReadRequest (OBIS: 1.0.1.8.0.255, attribute 2) Server - Client: ReadResponse (data: 123456) Client - Server: ReleaseRequest Server - Client: ReleaseResponseOBIS 码 1.0.1.8.0.255 表示正向有功电能这是电表项目里最常读的一项。注意这里属性编号 2 通常表示“值”不同接口类对属性编号的定义不同具体要对照蓝色书里的接口类定义。很多表计项目卡在“读不到数”最后查下来都是 AARQ 没协商成功或者 OBIS 码写到了不存在的属性上。所以联调的第一步不是读数据而是把 AARQ/AARE 跑通。只要这一步通了协议栈的大半问题都解决了。3. 把文档与源码变成可运行的工具最小联调环境搭建资料包再全不变成能跑的东西就是一堆 PDF 和代码目录。我每次拿到新的 DLMS/COSEM 源码都会先花半天搭一个最小联调环境把服务端和客户端拉起来亲眼看到 AARQ/AARE 交互再往里面加业务逻辑。这个环境不需要真实电表全部在本地完成。3.1 资料包里常见的内容与目录结构你手上的 DLMS/COSEM 通信协议文档资料通常是按“标准文档 源码 工具”三块组织的。标准文档部分重点留意蓝色书、黄色书、绿色书以及 IEC 62056-46 和 IEC 62056-47源码部分一般会拆成协议栈核心、HDLC 链路层实现、客户端示例、服务端示例几个目录工具部分则可能有报文生成器、模拟器、抓包样例。拿到手先别急着看代码按这个顺序整理先打开黄色书把整体架构图看明白再打开 IEC 62056-46只看 HDLC 帧格式那一章源码里找到 hdlc 目录和 client/server 示例目录确认它们的编译方式。蓝色书可以放旁边当字典用遇到不认识的接口类再查不要从头读。这套动作做完你对资料包的结构就有数了哪些是参考文档、哪些能直接编译、哪些是辅助工具。我一般还会把源码里的协议栈核心目录和示例目录在 Finder 或文件管理器里单独建两个快捷方式后面改代码时频繁切换会快很多。3.2 四件套工具没有它们你只会看着文档发呆要跑通 DLMS/COSEM最少需要四样东西一个虚拟串口或者回环网络入口、一个服务端模拟器、一个客户端调试工具、一个抓包器。虚拟串口在串口调试场景下特别有用常见做法是用 socat 在 Linux 下创建一对虚拟串口# 创建一对虚拟串口/dev/pts/3 和 /dev/pts/4 会连在一起 socat -d -d pty,raw,echo0 pty,raw,echo0这样客户端连 /dev/pts/3服务端连 /dev/pts/4两边就能互通。如果源码支持 TCP 传输更简单直接用 127.0.0.1 的 4059 端口就行这也是我推荐的起步方式。服务端模拟器优先用源码包里的 server demo因为它跟你手里的客户端代码是同一套协议栈联调时出现兼容性问题的概率最小。客户端调试工具同样先用源码包里的 client demo。抓包器用 Wireshark打开 loopback 接口过滤 TCP 4059 端口就能看到完整的 AARQ/AARE 交互。抓包是后续避坑最多的环节一定要装好并确认能抓到回环流量。3.3 用 Python 从原始字节流里提取 HDLC 帧最小脚本与校验逻辑联调刚开始最需要的不是读数据而是确认链路层收发的帧是完整的。我习惯直接从 Wireshark Follow TCP Stream 导出的原始字节流里用脚本提取每一帧。下面这个脚本就是干这个活的它处理了 0x7D 转义还原# extract_hdlc_frames.py # 从串口或网络抓取的原始字节流中提取 DLMS/COSEM 的 HDLC 帧 # 用法: python extract_hdlc_frames.py stream.bin import sys FRAME_FLAG 0x7E ESCAPE_FLAG 0x7D def extract_frames(raw: bytes): frames [] i 0 while i len(raw): if raw[i] ! FRAME_FLAG: i 1 continue i 1 # 跳过起始标志 0x7E body bytearray() while i len(raw) and raw[i] ! FRAME_FLAG: b raw[i] if b ESCAPE_FLAG: # 转义符下一字节异或 0x20 还原 i 1 if i len(raw): break b raw[i] ^ 0x20 body.append(b) i 1 if i len(raw) and len(body) 6: # 最少格式地址控制HCSFCS frames.append(bytes(body)) return frames def main(): with open(sys.argv[1], rb) as f: raw f.read() frames extract_frames(raw) print(f提取到 {len(frames)} 帧) for idx, frame in enumerate(frames): print(f帧 {idx}: {frame.hex()}) if __name__ __main__: main()这段代码的逻辑很简单碰到 0x7E 就认为是一个帧的开始然后一直读下一个 0x7E 作为帧结束中间遇到 0x7D 时把后面一个字节异或 0x20 还原成原始值。为什么要异或 0x20因为 0x5E ^ 0x20 0x7E0x5D ^ 0x20 0x7D恰好对应两种转义情况一行代码就统一处理了。帧体长度小于 6 的帧直接丢弃因为一个合法的 DLMS HDLC 帧至少有帧格式、地址、控制、HCS、FCS 六个字节。这个脚本不校验 FCS只负责拆帧适合联调早期快速确认“有没有完整帧、一共几帧”。配合第 4 章的客户端日志你也能用它来验证客户端源码发出来的帧是否完整。3.4 用 Wireshark 对照验证你的拆包逻辑脚本拆完帧之后一定要拿 Wireshark 的解析结果做一次对照。在 Wireshark 里打开抓包文件选中任意一个 DLMS/COSEM 帧左侧协议树会展开 HDLC 层的各个字段包括帧格式、地址、控制字段、HCS、FCS。把你的脚本输出打印出来逐项核对。第一次对不上百分之百是转义没处理干净或者地址解码方式不对脚本调整好之后后面所有帧都能对上这时候拆包逻辑才算可靠。还有一个很实用的对照办法Wireshark 里右键某个 DLMS/COSEM 帧导出为十六进制流再喂给你的提取脚本。脚本提取出的帧数量、帧长度应该和 Wireshark 显示的完全一致。这个闭环验证比单纯看代码可靠得多能帮你快速发现边界问题。4. 把软件源码跑起来编译服务端、客户端与一次完整的读数据文档看完了帧格式也认得了接下来就是真正编译源码。很多 DLMS/COSEM 源码包是 C 或 C 写的少数是 Java 或 C#。我以最常见的 CMake 工程为例来讲你手里的源码如果用的是 Makefile 或者 dotnet 工程换对应命令就行概念都一样。4.1 先看源码结构别一上来就 make拿到源码先不要敲 make先花十分钟把目录结构过一遍。通常你会看到这样的划分协议栈核心目录里是 DLMS 应用层和 COSEM 对象模型HDLC 链路层目录里是帧收发和分帧重组示例工程目录里是 client 和 server 两个独立的可执行程序。有些源码还会带一个 transport 目录专门适配 TCP、串口等不同底层传输。这一步的目的是搞清楚哪些代码是库、哪些代码是入口。我见过有同事把示例工程的 main 函数写在协议栈核心目录里结果编译时把示例和库混在一起链接了半天都过不了。正确做法是先把库编出来再编示例。如果源码自带 CMakeLists.txt通常会在顶层帮你处理这个依赖关系你只需要在 README 里找到“Build”一节按里面的命令操作。4.2 编译服务端表端模拟器并跑起来服务端就是模拟表计的一端。它内部维护一份 COSEM 数据模型等待客户端来读。编译命令很常规cd server_demo cmake -B build -DCMAKE_BUILD_TYPERelease cmake --build build -j4 ./build/server --port 4059 --server-address 1参数里 --port 指定 TCP 端口DLMS/COSEM 的约定端口是 4059--server-address 指定服务端逻辑地址常见配置为 1。跑起来之后服务端会阻塞等待客户端连接。如果服务端源码支持串口传输你可能会看到 --serial-port 之类的参数这个在 uart 通信协议的项目里会用到连接到上一章 socat 创建的虚拟串口即可。服务端起来后日志一般会打印“waiting for connection”之类的信息。这时候不要继续往下操作先开着。接下来编译客户端。4.3 编译客户端并建立连接地址、端口、OBIS 码怎么设客户端的编译方式和服务端完全一样但启动参数会多几个cd client_demo cmake -B build -DCMAKE_BUILD_TYPERelease cmake --build build -j4 ./build/client \ --host 127.0.0.1 \ --port 4059 \ --server-address 1 \ --client-address 16 \ --obis 1.0.1.8.0.255这里每一个参数都有讲究整理成表格方便你在不同源码包之间迁移参数含义常见值注意事项--host服务端 IP127.0.0.1跨设备联调时改成目标 IP--portTCP 端口4059IEC 62056-47 约定端口--server-address服务端 HDLC 地址1分段编码需按 3.3 的规则解码--client-address客户端 HDLC 地址16常见值为 0x10--obis待读数据项1.0.1.8.0.255正向有功电能--auth认证方式None / Low / High先 None跑通再加认证很多源码包的参数名是 -a、-b 这种短名称但含义都对应上面这几项。你只需要替换成相应参数就行。客户端跑起来后如果 AARQ 协商成功服务端日志会打印收到关联请求客户端会显示读到的电能值。如果卡住不动大概率是地址配置不一致回到上一章用抓包看一看到底卡在哪一步。4.4 读取一个标度值并确认分帧逻辑读电能量是单帧能搞定的事不一定会触发分帧。但要确认分帧逻辑可以故意读一个大的数据集比如读全事件日志或者读多费率曲线数据。当 LPDU 长度超过 HDLC 单帧承载上限时协议栈会自动把 APDU 拆成多个 HDLC 分帧每帧有自己的帧序号对端全部收齐后才重组 Application Layer 报文。判断是否分帧很简单看抓包里连续出现多个目的地址相同、控制字段不同的短帧帧格式字节的 bit7 和 bit6 会标记分帧状态。客户端日志里通常也会标明 frame 序号。我建议你在联调环境里至少触发一次分帧亲眼看到多帧发送、乱序重组的现象再去真表上遇到长报文心里就不慌了。真表上的事件数据往往几百字节分帧几乎是必走路径。5. 避坑指南DLMS/COSEM 开发中常见的 5 个抓狂问题DLMS/COSEM 这玩意儿光看文档觉得挺清楚一上手全是玄学。下面这 5 个问题我基本都踩过有些还是同一个项目里反复翻车。每条都按现象、原因、解决三部分写你对照排查就行。5.1 连接建立阶段的三个拦路问题问题一TCP 端口通了但发什么都被无视。现象客户端连接 4059 端口成功但发出去的字节没有收到任何响应服务端日志也没有输出。原因最常见的是没有发 AARQ。连接建立只是 TCP 层通了DLMS 应用层还没协商服务端根本不处理后续报文。另外地址段不对也会被静默丢弃。解决先抓包看第一包是不是 AARQ再核对服务端地址、客户端地址。将来在真实表计上联调时还要确认表计手册里的逻辑地址和物理地址很多表计只认固定地址。问题二抓包里出现一堆 7E 7D 5E手工切帧切得乱七八糟。现象你按 0x7E 切帧后帧长度忽长忽短FCS 对不上。原因没处理 0x7D 转义。数据里出现的 0x7E 被编码成了 0x7D 0x5E直接按 0x7E 切帧当然错。解决用 3.3 的脚本提取帧或者用 Wireshark 的 HDLC 解析结果做基准。千万别手工在一个个字节里数标志位你会数到怀疑人生。问题三AARQ 发出后服务端回了个 AARE但状态是 not accepted。现象客户端报错“association rejected”或类似信息服务端日志状态码不是 success。原因双方协议版本、密码套件、最大帧长度不一致。少数情况是认证方式不匹配比如客户端配了 High Authentication服务端只配了 None。解决先把两端认证都设为 None协议版本尽量用默认值。跑通之后再逐项开启加密和认证不要一上来就全上。5.2 数据读取阶段的两个坑问题四读某个 OBIS 码返回“data not available”。现象请求发出去响应帧正常回来但数据内容是错误信息。原因OBIS 码在这个表计上不存在或者该接口类的属性编号不对。不同表厂在实现时可能只实现了部分对象还有些把同一个 OBIS 码放在了不同类里。解决先用服务端模拟器把目标对象配出来读一次确认成功再去真表上试。真表上可以先读“对象列表”来枚举它支持的数据项而不是拿着文档里的表硬套。问题五分帧重组后解析出来的数据少了一段。现象数据长度对不上值明显少一截但每一帧的 FCS 都是对的。原因分帧重组时丢帧了或者顺序错了。帧格式里的分帧号不是从 0 开始的有些实现用 1 开头重组逻辑写错就会漏帧。解决先在模拟器里强制触发大报文验证重组逻辑覆盖了多帧乱序的情况。日志里打印每一帧的 seg_no人工对照一遍确认重组后长度和端到端校验结果一致。6. 进阶用抓包日志反向校准你的协议栈联调跑通只是开始真正的考验是协议栈在边界情况下的可靠性。我常用的一个验证手法是把客户端日志里的每一帧导出成文本再用脚本自动检查所有帧的 FCS不通过的直接标红。这样能快速定位是发送端帧构造错了还是接收端拆帧拆错了。配合第 3 章的拆帧脚本整个链路就具备可回放、可检查的能力。6.1 把 FCS 计算做成自检脚本DLMS/COSEM 的 HDLC 帧校验多数实现按 CRC-16/X-25 处理多项式 0x1021初值 0xFFFF输出异或 0xFFFF。下面这个函数可以直接当自检工具用# crc16_x25.py # 计算 DLMS/COSEM HDLC 帧的 FCS并校验一个帧体是否完整 def crc16_x25(data: bytes) - int: crc 0xFFFF for byte in data: crc ^ (byte 8) for _ in range(8): if crc 0x8000: crc ((crc 1) ^ 0x1021) 0xFFFF else: crc (crc 1) 0xFFFF return crc ^ 0xFFFF def fcs_ok(frame: bytes) - bool: # frame 是不含起始/结束标志的完整帧体最后两字节为 FCS if len(frame) 2: return False body, expected frame[:-2], (frame[-2] 8) | frame[-1] return crc16_x25(body) expected把第 3 章拆出来的每一帧喂给 fcs_ok返回 True 说明帧完整。如果个别帧一直校验失败优先检查是不是转义没还原或者抓包时丢字节了。注意不同源码实现可能在最后异或 0xFFFF 这步有差异遇到全部帧都校验失败的情况翻一下你手里文档的 HDLC 校验章节以文档为准。我现在的习惯是每修改一次协议栈代码就把联调日志跑一遍回归确认所有帧 FCS 通过、AARQ/AARE 流程正常、分帧重组无丢失。三个检查都通过我才敢拿着这版协议栈去接真实表计。真表上的问题千奇百怪但协议栈本身的可靠性必须先用工具锁死不然出了问题你根本分不清是现场网络问题还是协议栈问题。希望这套方法能帮你少走点弯路。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑