简介:面向自动抄表与计量应用场景,资源包整理并集成了DLMS/COSEM通信规范的中英文标准文档及配套实现源码,并收录HDLC链路层协议相关资料,覆盖数据采集、设备部署、运维管理到系统集成等完整环节,便于嵌入式开发者、协议研究者与电力计量相关专业学生按需取用。DLMS/COSEM已被纳入IEC 62056系列,是当前电能计量领域公认的完整通信标准体系,资源内含基于该标准设计的智能电能表实现脉络,可借助源码与文档对照理解关联认证、定时调度、加密通信等关键机制。压缩包共195个文件,约31.28MB,以h/c源码、mk/makefile构建脚本、pdf/doc规范文档为主,辅以项目工程配置与说明文件,目录结构便于按模块检索研读。已有68人学习下载,适合电力计量、智能表计及AMR自动抄表方向的嵌入式开发者或协议研究者作为标准落地参考。
1. 拆这套 DLMS/COSEM 与 HDLC 协议资源前,先看清它在协议栈里的位置
做过电力集抄、充电桩或能效管理项目的人都清楚,DLMS/COSEM 不是普通通信协议,它是一整套“对象模型 + 网络协议 + 帧格式”的组合。初次接触时,连 SNRM 握手和 UA 响应都容易卡住,更别说把 IEC 62056 标准文档和软件实现源码对照着读了。这套资源打包了标准文档和源码,解决了我们最缺的东西:不用再满网找零散 PDF,也不用对着黑匣子猜报文。它适合正在做电能表接入、能源管理平台、或想彻底搞懂智能电表通信协议的开发者,看完可以在虚拟表计上跑通建链、读数据、解析响应的完整流程。
我拆完这套资源后的最大感受是:文档负责建立体系,源码负责把体系变成可运行的代码。两者必须对照着看,否则要么读不懂标准里的字节定义,要么看不懂源码里为什么那样处理地址字段。接下来我从协议分层开始,把这条线捋清楚。
2. HDLC 与 DLMS/COSEM 的边界:分层模型、帧格式与标准文档读法
2.1 为什么要分层:DLMS 是应用层,COSEM 是对象模型,HDLC 是承载帧
DLMS(Device Language Message Specification)的全称容易让人误以为它只是“报文格式”,其实它规定的是应用层服务机制,包括读数据、写数据、认证、断开连接这一整套动作。COSEM(Companion Specification for Energy Metering)则定义了一组对象模型,用接口类和实例的方式把表计内部的寄存器组织成可访问的对象树。而 HDLC 是这层的承载协议,负责把 DLMS 消息切成帧,加上地址和校验字段,通过光电头、RS-485 或 TCP 透传发出去。
这套协议之所以显得复杂,是因为它天生为计量计费设计:既要支持不同厂商表计的功能抽象,又要保证读到的数值带单位、带时标、带费率号。相比 Modbus RTU 那种裸寄存器地址,DLMS/COSEM 更像电力领域里的 SNMP,访问的是一棵带类型的对象树。所以写程序前必须先分清这三层关系,否则拿到十六进制报文时根本无从解释。
拿到这套资源时,我的建议是按“物理层 → 数据链路层 → 应用层”的顺序去读。物理层是光电头或串口,一般走现成硬件;数据链路层就是 HDLC;应用层是 DLMS/COSEM。源码里也通常会按这三层拆模块,对照着看不至于一头扎进代码细节。分层的另一个实际好处是排错时可以二分定位:链路层挂了,应用层写得再对也是白搭。
2.2 HDLC 帧格式拆开看:Flag、地址字段与校验序列
DLMS/COSEM 里的 HDLC 帧不是教科书里的标准帧,而是经过裁剪的异步帧。一帧的典型结构是:Flag(0x7E)、帧格式字段(Frame Format)、目的地址、源地址、HCS 校验、Information 字段、FCS 校验,最后再补一个收尾 Flag。帧格式字段里最容易忽略的是分段标志位和块号,它决定当前帧是不是数据块传输的一个分片。调试时若只盯着数据部分看,往往会漏掉关键信息。
下面这张表是我在实际调试中反复用到的字段说明,标准文档里的定义分散在多个部分,整理出来能省不少翻页时间:
| 字段 | 长度与说明 | 调试关注点 |
|---|---|---|
| Flag | 1 字节 0x7E | 连续两个 0x7E 表示空闲,不是报文 |
| Frame Format | 2 字节,含分段位、帧类型 | 第一字节 bit4 为 1 表示分片帧 |
| Dest / Src Addr | 通常各 2 字节,可扩展 | 大小端和按位拆分是头号坑 |
| HCS / FCS | 各 2 字节,CRC16 | 计算范围不含 Flag,但含地址字段 |
| Information | 长度不定,内含 APDU | 必须在 FCS 之前,不能用 0x7E 扫描定位 |
地址字段是 HDLC 里最不直观的部分。客户端和服务端的地址不是直接写入一个字节,而是把逻辑地址按位拆分后再移位拼接。举例来说,表计逻辑地址 0x10 在帧里可能表现为 0x21 0x08 这类值,具体规则要查 IEC 62056-46 的地址章节。我一般会先用源码里的地址生成函数打印一遍,再和抓包结果比对,而不是靠手算。地址错了,后续所有交互都会落到错误的设备上,这是新手最容易翻车的位置。
2.3 标准文档族怎么读:IEC 62056 系列与调试的关键手册
这套资源里最值钱的就是标准文档。IEC 62056 被拆成了多个子标准,各自的侧重点完全不同。做客户端接入时,最重要的是 62056-46(HDLC 数据链路层)和 62056-53(COSEM 应用层);要理解对象含义,则要看 62056-62(接口类)和 62056-61(OBIS 码定义)。如果走 TCP/IP 采集方式,还要额外读 62056-47 的 IP 承载规则。
阅读顺序我建议是“先 61/62,再 53,最后 46”。理由很直接:面对一台表计,你首先得知道它能提供哪些对象,然后才谈得上怎么发请求,最后才轮到帧怎么封装。反过来读容易被底层细节拖住。现在能下载到的标准文档版本往往覆盖了 2017 年后的勘误,字段定义比早期版本严谨得多,调试时建议以文档中的表格为准,不要轻信论坛里的二手解释。
3. COSEM 对象模型实战:接口类、OBIS 码与 Register 读取映射
3.1 接口类与实例:从“寄存器地址”到“对象树”的思维切换
用 Modbus 读表计,本质是往一串地址发请求,数值含义全凭寄存器映射表。DLMS/COSEM 换了一套思路:每个测量值都是一个对象实例,实例有类型(接口类)和唯一编号(OBIS 码),访问时必须同时指定类和实例。例如测电压的 Register 和测电量的累计量 Register,虽然同属接口类 3,但 OBIS 码不同,读出来的属性结构和单位也不同。
源码里通常把这层映射做成注册表(Register Table),客户端发出“读类 ID=3,属性 ID=2,OBIS=1.0.32.7.0.255”后,表计先查实例表,再返回带类型的数值。这套机制最大的好处是不同厂商的表计只要符合标准,行为完全一致,不再需要为每台表单独维护映射表;代价是初次上手确实要花一两天适应对象树的概念。习惯了以后,你会发现它比 Modbus 的可维护性强得多,尤其面对几十种型号表计的时候。
以下是我整理出的常用接口类,实际编码时可以照抄:
| 类 ID | 接口类名称 | 典型用途 | 常读属性 |
|---|---|---|---|
| 1 | Data | 设备名、版本号等简单数据 | 属性 2(Value) |
| 3 | Register | 电压、电流、功率瞬时值 | 属性 2(Value)、属性 6(Unit) |
| 4 | Extended Register | 带时标和标度值的电量 | 属性 2(Value)、属性 5(Scaler) |
| 8 | Profile Generic | 日冻结、事件记录、负荷曲线 | 属性 2(Buffer)、属性 7(Entries) |
3.2 OBIS 码的六段含义:A-B-C-D-E-F 与常见码表
OBIS 码由 A、B、C、D、E、F 六段组成,A 表示值类型(0 是抽象对象,1 是电流,2 是电压,3 是功率),B 表示通道号,C 表示分类,D 表示处理方法,E 表示费率,F 固定为 255。所以 1.0.32.7.0.255 的中文含义是“电压通道 1,物理量类型,瞬时值”。查码时容易把 C 段和 D 段看反,会直接得到错误对象。
下面三组 OBIS 码是做集抄项目时的常用常量,建议直接放进代码公共定义里:
| OBIS 码(完整) | 含义 | 常读属性 |
|---|---|---|
| 1.0.32.7.0.255 | 瞬时电压(单位 V) | 属性 2 |
| 1.0.1.8.0.255 | 正向有功电量(单位 kWh) | 属性 2 |
| 0.0.1.0.0.255 | 逻辑设备名 | 属性 2 |
读 OBIS 时要留意,C 段在不同方言里既可能表示测量种类,也可能表示处理方式,具体以 62056-61 的编号表为准。我遇到过一台表把 1.0.32.7.0.255 里的 7 换成了 0,后来确认是厂商私有扩展。这种情况不算协议 bug,但在源码层预留一个纠错映射表会更稳妥,尤其当项目要对接多品牌表计时。
3.3 由一次读请求反推服务流程:Get-Request 与对象属性
理解了对象模型后,再回看代码会顺很多。客户端发起的读操作在 DLMS 里叫 Get-Request,它携带 Invoke Id 和优先级,外加访问目标:类 ID、实例 ID(OBIS)、属性 ID。服务端响应叫 Get-Response,读取成功时返回 DataAccessResult 与数值,失败则会返回对象未找到或权限不足的状态码。
这个流程在逻辑上很像一次远程调用,和 HDLC 帧的地址字段没有直接关系。HDLC 地址解决的是“发给哪台表”,GET 参数解决的是“读哪个对象”。把这两层分开,排错范围就能立刻缩小一半:数据没回来,先判断是 APDU 层面的问题还是链路层面的问题,而不是从第一个字节开始逐字段猜。
源码里的对应关系通常是:一个接口负责把上层请求编码成 APDU,另一个接口负责把 APDU 封进 HDLC 帧并发送。若只看要字节流,很容易被帧格式绑定住,忽略对象模型的调用关系。我习惯先跑一个成功示例,抓包后把 APDU 层的内容单独剥离出来,再倒推它对应的对象操作,这样对“协议”和“模型”的理解就能合到一条线上。
4. 源码落地实战:连一台虚拟表计读“瞬时电压”的完整流程
4.1 源码工程先看哪一层:目录结构与层次关系
打开下载包里的源码目录,你通常会看到协议核心库、通信适配层和示例程序三大块。协议核心库负责 APDU 编解码、对象模型和认证加密;通信适配层负责串口或 TCP 的字节流收发,包括 HDLC 帧拼装与 CRC 计算;示例程序则是跑通流程的入口。我第一次拆这类源码时,直接扎进核心库,结果被抽象层次绕晕,浪费了不少时间。正确顺序是先把目录树打出来,按“示例 → 通信层 → 核心库”去读。
以常见的 DLMS 开源实现为例,目录结构大致如下:
dlms/ core/ # APDU 编解码、接口类定义、认证 transport/ # HDLC 帧封装、串口/TCP 收发 examples/ # 连表、读表、断开连接的完整示例 tools/ # 抓包解析辅助脚本transport 层是排错重点,因为地址生成和 CRC16 都集中在这里。很多实现把 CRC 函数藏得较深,找不到时直接在代码库里搜“crc16”或“MCRF”,不要从入口函数一路追下去。核心库里的接口类定义最好泛读,先知道有 Register、Profile Generic 这些类型,不急着看每个方法的实现。
4.2 用 Python 跑通一次读取:建立连接、认证与读取瞬时电压
下面这段代码按 Python 环境常见的 gurux_dlms 风格来写,和资源里多数源码是同一套逻辑,可以直接改造为项目骨架。我用串口光探头连接虚拟表计来演示,换成 TCP 时只需替换掉传输层对象。
from gurux_dlms import GXDLMSClient, GXDLMSReader from gurux_serial import GXSerial # 串口配置:光电头常见参数为 2400 波特率 serial = GXSerial("COM3", 2400) # 初始化客户端:地址和认证参数是关键 client = GXDLMSClient( clientAddress=0x10, # HDLC 客户端逻辑地址 serverAddress=16, # 表计逻辑设备地址,常见 1/16/128 authentication=2, # 认证等级 2:Low Level Security password="12345678", # LLS 密码,按表计铭牌填写 ) # 建链并读取电压寄存器:OBIS 为 1.0.32.7.0.255 reader = GXDLMSReader(client, serial, 1024) # 1024 为最大 PDU 长度 reader.initialize() data = reader.read(client.getObis(1, 0, 32, 7, 0, 255), 3, 2) print(data) # data 是带 value 和 unit 的对象代码里的参数有几个需要解释。clientAddress 填 0x10 时,实际会按 HDLC 地址规则拆成帧里的地址字节;serverAddress 填的是表计的逻辑设备地址,不是 RS-485 站号,填错会导致请求发给错误设备。authentication 取 2 表示低级别认证,要求密码按 ASCII 补齐;如果表计启用了更高级别认证,这里要换成密钥对象,多数开发场景暂时不用碰。maxReceivePDUSize 决定一次能接收多长的响应,读电压这类短数据 1024 足够,读日冻结这类长数据时要调到 4096,否则会触发分片逻辑。read 调用里两个实参分别是接口类 ID 和属性 ID,OBIS 的六段按顺序传入,不能颠倒。
4.3 调整参数的边界:地址规则、PDU 尺寸与认证等级的适配
实际项目里最常见的失败原因是“默认参数跑通,换一台表就失败”。不同厂商对逻辑设备地址的习惯不同,有的从 1 开始,有的从 16 开始,有的用 0x0f 这类非常规值。HDLC 地址字段是按位拆分的,0x10 的客户端地址和 0x10 的服务端地址在同一帧里会编码成不同字节,建议在初始化时把生成的地址字节打印出来,和抓包结果对一次再上线。
maxReceivePDUSize 与 PDU 协商也直接影响成功率。表计会在 SNRM 握手阶段通告自己的最大接收尺寸,客户端申请过大或过小,都可能让后续读长表失败。常见做法是统一设成 4096,并显式打开数据块传输,这样即使读取结果超过单帧容量,也能用分段机制继续收。认证等级的原则是“开发用低压,生产看需求”。低压认证用一个字符串密码就能通过,适合联调;生产环境若涉及计费关口,建议评估带签名的认证等级,并提前把密钥管理系统接入项目。
5. 避坑与排查:地址字段、分段传输与认证异常的 5 个真实翻车点
5.1 SNRM 握手成功后一读数据就超时
现象:客户端能收到 UA 响应,也能正常发出 AARQ,但随后发出 ReadRequest 后一直等不到回复,直到超时。 原因:这是我最早遇到的一个坑。检查后确认是 HDLC 帧格式字段没设对,分段标志位被置 1,表计以为后面还有后续分片,就一直等待而不应答;另一种常见原因是 Information 字段里出现了 0x7E,透明传输后的转义字节没有还原。 解决:检查发送帧的 Frame Format 第一字节,确保分段标志为 0;同时核对 0x7D 转义逻辑是否在发送前做了填充。我习惯把发送帧的十六进制串打出来,对着 62056-46 的帧格式表人工比对一轮,比直接看代码定位快得多。
5.2 HDLC 地址字段大小端写反导致“发错对象”
现象:抓包看到表计确实回了响应,但响应内容来自另一台设备,或者直接提示 object not found。 原因:客户端地址和服务端地址是两段独立编码,不少示例把 serverAddress 当成整型地址后按大端写入,实际标准是按小端拆分后再移位拼接。 解决:把 serverAddress 的十六进制打印出来,对照文档中“地址字段”的编码算法检查。资源里的源码一般有 BuildHDLCAddress 一类的函数,直接调用它生成地址字节,不要手工拼。这个函数生成的字节和抓包结果能对上,基本就能排除地址问题。
5.3 读日冻结或事件记录时数据被截断
现象:读取 Profile Generic 的 Buffer 属性时,返回数据只有前半段,或者程序抛出“数据长度超限”。 原因:客户端与表计对最大接收 PDU 长度的协商结果太小,数据超过一帧容量后协议自动启用数据块传输,而客户端没有正确处理后续分片帧。 解决:把 maxReceivePDUSize 和 block size 都显式设置,不要用默认值。最简单做法是把客户端能收的 PDU 上限调成与表计一致,并确保循环处理 AppendDataBlockRequest 的后续帧。源码里如果只有阻塞式 reader,没有分片循环逻辑,就换用异步接口,或者在循环里手动收完所有分片再拼装。
5.4 LLS 密码认证一直失败
现象:密码对照表计铭牌填写无误,但认证在 AARQ/AARE 阶段被拒,错误码指向安全等级不匹配。 原因:Low Level Security 的密码不是直接以 UTF-8 字符串发送的,要先转成字节数组并补齐,再按 DLMS 规定的算法处理。 解决:查看源码里 GXDLMSClient 对密码的预处理函数,确认填的是“可见字符串”类型。国产表在这个点上尤其不一致,有些厂商要求把 ASCII 码逐字节补 0x00,格式不对就直接拒绝。建议先在低等级认证模式下联调,排除密码问题后再考虑升级认证。
5.5 OBIS 码在文档里有,但读出来是空值
现象:按标准拿 OBIS 码去读,返回 success,但数据项的 value 是空。 原因:C 段或 E 段在具体表计里被替换成了私有定义,或者该实例根本没有注册到当前逻辑设备上。 解决:先枚举关联逻辑设备上的所有对象,确认实际注册的 OBIS 后再读。这个操作在源码里一般对应“GetAssociations”或“scan”,表计支持的话会返回一份完整对象列表。拿到列表后,再看目标对象是否真的存在,比对着标准文档猜实际表计型号更靠谱。
6. 进阶验证技巧:用抓包工具把 HDLC 帧拆开验一次建链
6.1 搭建无表计的验证环境
没有真实表计时,不要直接跳到现场。可以用资源里的虚拟表计程序,或者自己写一个收到 SNRM 就回 UA、收到 AARQ 就回 AARE 的模拟器。把客户端、模拟器和抓包工具串在同一条逻辑链路里,重点验证三件事:地址字节是否与公式一致、CRC16 是否通过、APDU 里的对象名是否与 OBIS 段对应。
具体做法是抓包工具监听串口或 TCP 透传端口,定位到以 0x7E 开头的帧。如果流量较多,按帧结构先筛出 Frame Format 字段,快速排除非 DLMS 报文。抓到 SNRM 帧时,客户端地址会出现在目的地址位置;模拟器回发的 UA 帧里,源地址位置就变成服务端地址。这份双向对照能一次性验证地址拆分的正确性。
6.2 手工校验一次 HCS/FCS,把链路层疑点钉死
写一个几十行的小脚本对抓到的帧做离线校验,是很有性价比的验证动作。HCS 的校验范围是帧格式字段加两个地址字段,FCS 的校验范围则包括前面所有字段加信息字段,多项式常用 CRC16-MCRF4XX。如果 HCS 和 FCS 都通过,链路层基本可认为没问题;如果 HCS 通过但 FCS 不通过,问题大概率出在帧尾的数据填充或转义处理上。
from crcmod.predefined import mkCrcFun crc16 = mkCrcFun("crc-16-mcrf4xx") # DLMS 常用多项式 # frame 为抓包拿到的完整帧,已还原 0x7D 转义 frame = bytes.fromhex("7e 23 21 08 10 01 07 ... 7e") pos = frame.index(0x7e, 1) body = frame[1:pos] hcs = crc16(body[2:-2]) print("HCS:", hcs.to_bytes(2, "big").hex())脚本里的切片范围要对应抓包内容,抓包工具的显示通常已经去掉了 Flag 转义,结算前要把 0x7D 填充还原成原始字节,否则字节数变了,CRC 永远对不上。从那以后,我每次改协议参数都会强制走一遍这个抓包加离线校验的流程,先证明链路层没问题,再讨论上层应用逻辑。这套习惯帮我节省了大量现场排查时间,希望帮到你。
本文还有配套的精品资源,点击获取