ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

DLMS/IEC62056协议实战:从HDLC链路层到应用层建链全解析

DLMS/IEC62056协议实战:从HDLC链路层到应用层建链全解析 简介在电能表数据采集系统中DLMSIEC62056协议族是主流的通信标准用于实现集中器与电表之间的帧级数据交互。该协议采用三层架构物理层负责硬件通信链路层基于HDLC实现可靠传输应用层则通过ASN.1语法定义数据帧并通过BER/AXDR编码传输。理解SNRM/UA建链、AARQ/AARE协商、帧计数规则以及长帧分割机制是嵌入式工程师进行电表调试和采集终端开发的关键。从实际抓包出发解析地址扩展、CRC校验、RR拉取等工程细节能够有效规避四字节地址配置、长帧粘包等常见问题。本文结合报文实例梳理完整通讯流程与避坑经验助力快速掌握IEC62056协议栈应用。1. DMLS 协议中文版从一次凌晨抓包开始把 IEC62056 帧级通讯讲透如果你接过电能表通讯开发一定见过这种场景电表手册写着“支持 DLMS”抓包工具一开全是 0x7e 开头、看起来像 HDLC 又夹着一堆 00 22 00 23 的字节串没人说得清每一步该发什么、该验什么。这份“DMLS 协议中文版”其实是一份 IEC62056 协议族即 DLMS的中文说明手册面向电能量采集终端开发把物理层、HDLC 链路层、ASN.1 应用层编码、AARQ/AARE 建链、电量/瞬时量/负荷曲线等请求流程从头到尾讲了一遍。注意项目标题写的是 DMLS标准拼写是 DLMS检索时两个词都要试。它不覆盖 DLMS 全部内容但足够你把一次抄表会话完整跑通适合正在做采集终端、集中器、电表调试的嵌入式工程师也适合刚接手 DLMS 项目、想在帧级别建立概念的入门者。2. 先立整体模型三层结构、Client-Server 关系和通讯六步DLMS 协议模型从整体上看分为三层物理层、链路层、应用层。层与层之间使用指定的服务通讯通讯双方采用典型的 Client-Server 结构。数据请求端也就是采集器或集中器始终作为 Client数据提供端也就是电能表始终作为 Server。这意味着所有会话动作都由采集器主动发起电表只负责被动响应和按帧回复。2.1 三层分工物理层管硬件链路层管可靠应用层管语义物理层位于通讯模型最底层DLMS 规约可以建立在多种物理介质之上包括 PSTN 拨号网络、以太网、串行通道等。物理层的作用比较纯粹对底层通讯硬件进行操作比如 Modem 的初始化、打开、关闭或者串口的波特率、流控配置。在嵌入式系统里物理层大多对应到 BSP 驱动部分正常情况下不太会被通讯规约层直接控制。链路层是物理层与应用层的通道DLMS 链路层使用的是 HDLC 高速链路控制协议。链路层由两个子层构成LLC 子层和 MAC 子层。LLC 子层只做转发把 MAC 子层的数据转给应用层或把应用层的数据转给 MAC 子层不做任何数据处理。不过这里有个字段级细节链路层数据在交给应用层之前要加 LLC 帧头。Client 端应用层发送的数据加 0xe6、0xe6、0x00Server 端应用层发送的数据加 0xe6、0xe7、0x00。这个三字节头是判断当前帧是谁发出的重要标志抓包分析时经常靠它区分方向。MAC 子层负责的是数据传输可靠性具体包括地址检查、数据 CRC 校验、长数据帧的拆包组包。这些工作是链路层协议的核心也是后续章节里最容易踩坑的地方。2.2 通讯六步从物理建链到断链的完整周期一次完整的 DLMS 通讯按顺序可以拆成六个阶段。第一阶段是建立物理层连接对 Modem 或串口进行初始化。第二阶段是建立链路层连接由 Client 发送 SNRM 数据帧Server 响应 UA 帧表示连接建立成功响应 DM 帧则表示失败或未就绪。UA 帧中通常携带链路参数配置信息主要是 window size 和最大信息域长度两个参数。第三阶段是建立应用层连接这个步骤在 DLMS 中被称为 NegotiationClient 发 AARQ 帧Server 响应 AARE 帧协商的是应用层通讯参数。第四阶段才是真正的数据通讯Client 发送数据请求帧Server 以数据响应帧回答。这里有一个要点Client 在请求不同数据时必须用特定数据独有的 class id 和 OBIS 来标识数据类型比如电量、瞬时量、负荷曲线用的是不同的对象标识。第五阶段是通讯结束后的链路释放Client 发结束帧通常通过 DISC 帧断开链路。原文也明确提到如果不发任何帧可以依靠 Server 端的超时挂断机制来结束通讯但规范做法是主动发链路结束帧。第六阶段是解除物理层连接关闭物理端口结束整个通讯周期。阶段发起方关键帧或操作失败时抓包表现物理建链Client初始化 Modem/串口无任何字节响应物理层丢包链路层建链ClientSNRM → UA/DM发 SNRM 后无响应或直接回 DM应用层建链ClientAARQ → AAREAARE 中 result 非 0或直接超时数据通讯ClientGet/Set 请求与响应server 回 FRMR或响应帧 CRC 错链路释放ClientDISC → UA/DMDISC 后无 UA需依赖超时复位物理断开Client关闭端口/挂断 Modem无属本地操作2.3 两次连接要分开链路层连接和应用层连接不是一回事很多刚接触 DLMS 的开发者会把 SNRM 和 AARQ 混在一起以为都是“建立连接”。实际上链路层连接和应用层连接是两个独立的协商过程必须按顺序先后建立。链路层连接通过 SNRM/UA 建立协商的是 window size 和最大信息域长度应用层连接通过 AARQ/AARE 建立协商的是协议版本、conformance 集合、最大接收 PDU 等信息。这里有个容易忽略的细节链路层连接建立之后Client 端第一次发送 AARQ 时帧计数器的值要从零开始。换句话说AARQ 不是链路层意义上的第一帧但它的帧计数确实被当作建链后的首个 I 帧来处理。这个计数规则会在下一章详细展开。3. 拆 HDLC 链路层地址扩展、帧控制字、长帧分割和 SNRM 报文链路层是 DLMS 协议族里最工程化的部分。抓包分析 DLMS 报文大部分精力都会花在 HDLC 帧头、地址、控制字和 CRC 校验上。这一章按照帧结构、地址、控制字、长帧、建链报文五个层次逐个拆开讲。3.1 两种 HDLC 帧结构和帧类型与帧长字段HDLC 数据帧以两个 0x7e 作为帧头和帧尾两个 0x7e 之间是链路用户数据。不包含应用层数据时帧结构为0x7e、帧类型与帧长、目的地址域、源地址域、控制域、数据帧校验、0x7e。包含应用层数据时帧结构里会多出三项帧头校验、LLC 帧头、用户数据信息。帧头校验是为了增强通讯可靠性而增加的校验范围是帧头部分也就是帧类型与帧长、目的地址域、源地址域、控制域这四个字段。LLC 帧头就是上一章说的 0xe6 0xe6 0x00 或 0xe6 0xe7 0x00。用户数据信息则是应用层处理的数据。这里有个重要的默认值出于数据完整性考虑用户数据最大长度默认为 128 字节。如果需要更长可以在 SNRM 数据帧中协商后面讲长数据帧时再展开。帧类型与帧长字段共两个字节。Frame Type 用于指出当前数据帧类型DLMS 使用 Frame Type 3其值恒为 A即二进制 1010。S 位占一位用于说明数据帧是否被分割长数据帧传输时这位会被置 1。Frame Length 子字段说明当前数据帧的长度以字节为单位且不包括两个 0x7e。这两个字节是解析 HDLC 帧时最先要读的部分很多初学者会把 0x7e 之后的第一个字节当成地址域这是错的。帧结构字段不包含应用层数据包含应用层数据帧头/帧尾0x7e0x7e帧类型与帧长有有目的/源地址域有有控制域有有帧头校验无有LLC 帧头无有e6 e6 00 或 e6 e7 00用户数据无有数据帧校验有有3.2 扩展编址技术Client 一字节Server 四字节的实践来源地址域分为目的地址域和源地址域。对于 Client 端目的地址是 Server 地址源地址是 Client 地址对于 Server 端则正好相反。HDLC 使用扩展编址技术这是地址解析的关键某一个地址字节的最低位如果是 0说明该地址域没有结束后面仍有字节属于同一地址域如果最低位是 1则说明该地址域已经结束。Client 端的地址永远是一个字节因为最低位要置 1所以 Client 地址理论上只有 128 个可用值。Server 端为了实现一个物理地址对应多个逻辑地址把地址分成两部分upper HDLC address 用于表述逻辑地址lower HDLC address 用于表述物理地址。upper address 总是需要有的lower address 在确认不需要的情况下可以不出现但 SL7000 电表这两部分都需要。Server 端地址在使用扩展编址技术时虽然有理论上的无限扩展能力实践上是有上限的。常见结构有四种一字节只出现 upper 地址两字节出现 upper 一字节加 lower 一字节四字节出现 upper 两字节加 lower 两字节。SL7000 电表实测下来只有四字节 Server 地址结构可用这个结论在项目文档里写得很明确。抓包时看到 00 22 00 23 这样的目的地址00 22 是 upper 部分00 23 是 lower 部分每个字节的最低位都用于扩展判断。链路层还有一些被 HDLC 保留的特殊地址其中比较重要的是广播地址。DLMS 协议族支持这些地址结构中的任意一种也支持特殊地址但具体电表型号能用哪一种要以实测为准。3.3 帧控制字与 RRR/SSS 计数规则帧控制字段主要负责通讯中的帧计数和特殊数据帧的标识。字段结构里 RRR 为接收帧计数SSS 为发送帧计数另有 P/F 位用于轮询和结束标记。帧计数规则是链路层调试中最容易糊涂的地方这里要仔细说清楚。链路层连接建立之后Client 端第一次请求数据时包括发送 AARQRRR 置 0、SSS 置 0。Server 端收到这一帧后返回数据响应此时 RRR 为 1、SSS 为 0。Client 再次请求数据时RRR 加 1、SSS 加 1Server 收到后返回响应RRR 加 1 成为 2、SSS 加 1。如此反复直到 Client 得到所有需要的数据。整个数据传输过程以 I 数据帧完成请求和响应。值得重点提示的是请求数据结束之后还需要再发送 RR 帧收到确认之后才能发送 DISC 帧结束链路。这里 Client 端 RR 帧中的 RRR 值只需将 Client 的帧计数 RRR 加 1 得到。这个“先 RR 确认再 DISC”的顺序是很多人翻车的地方直接跳过 RR 发 DISCServer 端的状态机可能不会正确释放链路。P/F 位分为 poll bit 和 final bit。Poll bit 由 Client 发送置 1 时表示要求 Server 端回应置 0 时表示不允许回应。Final bit 由 Server 发送置 1 时表示一次数据帧的发送结束置 0 时表示还未发送完。当 window size 等于 1 时Server 返回的数据帧中 final bit 总是置 1这个特性可以用于判断分段长帧是否到达最后一帧。链路层的数据帧类型包括I 信息传输帧、RR 准备接收数据帧、RNR 接收未准备好帧、SNRM 设置正常响应模式帧、UA 对 SNRM 和 DISC 的响应帧、DISC 结束链路帧、DM 对 DISC 的响应帧、UI 用于保持链路的帧、FRMR 拒绝接收帧。每种帧在通讯中承担不同角色抓包时看到这些控制字要能立刻对应到状态机位置。3.4 长数据帧分割S 位、128 字节边界和 RR 拉取很多情况下一次请求和响应之间无法结束数据传输原因是用户数据默认不能超过 128 字节。这时必须启动长数据帧链路控制流程。请求负荷曲线时基本一定会走长帧流程因为曲线数据量大128 字节根本装不下。使用长数据帧时必须把长数据帧分割成多个短数据帧然后依次发送接收端逐个处理。数据帧被分割时帧类型与帧长字段中的 S 位会被置 1。接收端检测到 S 位置 1 后就知道当前帧是分段数据必须做相应处理。Client 端通过发送 RR 数据帧来请求被分割数据帧的后续其他部分。这个拉取机制结合了前面说的窗口机制Server 不会一次性把所有分段发完而是发一部分等 Client 的 RR再继续发下一部分。组包逻辑上要注意的是最后一段的 S 位为 0这代表长帧已经发送完成在分段的中间段S 位均为 1。如果设备端只判断 S 位而不处理 RR 拉取数据就永远收不完整。3.5 SNRM 与 UA 报文逐字节拆解链路层连接的建立由 Client 发送 SNRM 数据帧、Server 响应 UA 数据帧完成。UA 帧中的信息域包含链路参数配置具体到数据帧中有四个参数transmit maximum information field length、receive maximum information field length、transmit window size、receive window size。下面是这份资料里给出的 SNRM 报文实例我把它按字节拆开解析。frame bytes.fromhex(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 07 65 5e 7e) assert frame[0] 0x7e and frame[-1] 0x7e payload frame[1:-1] frame_type_len payload[0:2] dest_addr payload[2:6] src_addr payload[6:7] control payload[7:8] hcs payload[8:10] info payload[10:-2] fcs payload[-2:] print(帧类型与帧长:, frame_type_len.hex()) print(目的地址(4字节server):, dest_addr.hex()) print(源地址(1字节client):, src_addr.hex()) print(控制字:, control.hex(), SNRM) print(帧头校验HCS:, hcs.hex()) print(信息域:, info.hex()) print(帧校验FCS:, fcs.hex())这段代码先把 0x7e 剥离再把帧头各部分按固定偏移切开。帧类型与帧长字段 0xa0 21 中a0 的高 4 位是 Frame Type值为 A低 4 位和第二个字节构成帧长 0x021即总共 33 字节。目的地址 00 22 00 23 是四字节 Server 地址源地址 03 是 Client 地址。控制字 0x93 对应的就是 SNRM 帧标识。信息域从 0x81 80 12 开始0x81 80 是 SNRM 标识0x12 是组长度之后的 05 01 80 表示 transmit maximum information field length 为 128 字节06 01 80 表示 receive maximum information field length 为 128 字节07 04 00 00 00 01 表示 transmit window size 为 108 04 00 00 00 07 表示 receive window size 为 7具体值以实际电表支持为准。UA 响应报文的解析方法完全一致区别在于控制字变成 0x73且源地址和目的地址互换。UA 帧同样携带参数协商信息通常直接包含 client 请求的配置或 server 侧支持的值。4. 应用层与编码ASN.1 语法、BER/AXDR 编码和 AARQ/AARE 建链应用层是 DLMS 协议族里抽象度最高的一层。不同于普通通讯协议用固定表格描述帧格式DLMS 用 ASN.1 抽象语法描述应用层数据帧再用 BER 或 AXDR 编码把语法变成实际字节。理解这三者的关系是读懂应用层报文的前提。4.1 ASN.1 语法用抽象语法描述帧结构用 ASN.1 语法描述的数据帧结构上包含帧名、tag、IMPLICIT/EXPLICIT 关键字和数据类型。tag 包含 class type 和一个数字。Class type 有四种Universal 表示该数据帧在所有 DLMS 应用中的含义唯一Application 表示含义与具体应用有关Private 表示只在某一厂家自定义范围之内Context-specific 表示与上下文数据项有关同一数据在不同结构中可能有不同含义。tag 中的数字作为数据帧的标号也作为该数据帧的句柄出现在应用数据单元中。IMPLICIT 和 EXPLICIT 描述的是子数据帧与父数据帧的关系。IMPLICIT 会改变父数据帧的 tagEXPLICIT 不改变父数据帧的 tag。没有注明 IMPLICIT 的项即为 EXPLICIT。OPTIONAL 关键字表示数据项在用户认为需要的场合可以省略。数据类型分为简单型和复合型常出现的有 INTEGER、BIT STRING、OCTET STRING、NULL、OBJECT IDENTIFIER、SEQUENCE、SET、PrintableString、T61String、IA5String、UTCTime 等。比较重要的两种复合类型是 SEQUENCE 和 CHOICE。SEQUENCE 表示数据帧中的内容按顺序排列例如 Get-Request-Normal 数据帧包含 invoke-id-and-priority、cosem-attribute-descriptor以及可选的 access-selection-parameters。CHOICE 是选择类型表示当前数据帧从几个候选中选一个作为实际类型例如 GET-Request 在 get-request-normal、get-request-next、get-request-with-list 三个选择项中取一。4.2 BER 与 AXDR两种编码方式的适用位置ASN.1 只是语法要变成字节必须经过编码。DLMS 中用 ASN.1 描述的协议用 BER 编码实现用 ASN.1 描述的 XDLMS 协议用 AXDR 编码实现。这里有一个容易混淆的点DLMS 中只有 AARQ 与 AARE 数据帧的部分内容使用 DLMS 协议绝大多数应用层数据通讯使用的是 XDLMS两者使用不同的编码。BER 编码采用“数据标识、数据长度、数据内容”的三段式结构其中数据内容可以嵌套另一段 BER 编码。一个 BER 数据标识和一个 BER 数据长度构成一个 16 位的位串bit15 和 bit14 表示 datatype classesbit13 表示 Data typebit12 到 bit0 表示 data length。datatype classes 字段对 ASN.1 语法中的 class type 编码Universal 为 00Application 为 01Context-specific 为 10Private 为 11。Data type 字段描述内容结构Primitive 为 0 简单类型Constructed 为 1 复合类型。data length 字段描述内容长度以字节为单位。AXDR 是 A-XDR对 Unix XDR 编码的扩展语义上更紧凑。对比两个数的编码就能看出差异对值分别为 0x1234 和 0x5678 的两个数编码BER 会带序列标识、序列长度、每个数的类型标识和字长结构完整但冗余AXDR 则按顺序直接排列每个数的数值省掉了类型描述信息传输效率更高。代价是 AXDR 的解析必须依赖预先约定的 ASN.1 结构不能像 BER 那样自描述。4.3 AARE 报文编码实例与数据请求流程AARE 是 Server 端对 AARQ 的响应帧答的是应用层连接是否建立成功。这份资料里给出了一段 AARE 帧的 BER 编码以及后续 XDLMS 部分的 AXDR 编码这里是关键字段的拆解0x61 是 AARE 的 tag0x42 是长度A1 之后是 COSEM 应用上下文名称内容为 06 07 60 85 74 05 08 01 01其中 06 是 OBJECT IDENTIFIER 的类型标识。A2 之后是 Association-result03 02 01 00 表示 result 值为 0即连接成功。A3 是 Associate-source-diagnostic。之后从 88 开始是 ACSE requirements、mechanism-name、authentication-value可以用同样的方式逐字节解析。最后 BE 04 之后开始进入 XDLMS 部分由 AXDR 编码描述 InitiateResponse包括 negotiated-dlms-version-number、proposed-conformance 等字段。应用层连接建立后Client 发送数据请求帧Server 以数据响应帧应答。请求不同数据时要使用特定数据独有的 class id 和 OBIS。以常见的四类请求为例请求电量时重点是寄存器类对象常见配置用 class 3 或 class 4OBIS 以电表厂家对象映射表为准请求电压、电流、功率等瞬时量时需要定位数据对象的 OBIS 和属性编号请求负荷曲线时几乎必然触发长帧传输并且要指定 selective access 的时间范围请求时间时读的是时间对象。这里给出的 class 和 OBIS 都是常见配置示例实际项目必须以具体电表的协议映射表为准这也是 DLMS 调试中最需要厂商配合的部分。5. 实践避坑四字节地址、CRC 边界、长帧粘包和会话复位这一章集中记录 DLMS 调试中反复出现的几个坑。每一条都是先讲现象再分析原因最后给出解决办法全部来自工程实践。5.1 SL7000 只有四字节 Server 地址可用现象按厂家协议手册把 Server 地址配置成两字节结构抓包工具看着报文结构正常但电表对 SNRM 没有任何响应。原因SL7000 电表实测下来只有四字节 Server 地址结构可用upper 两字节加 lower 两字节。手册里虽然列出了多种地址结构但该型号实际只支持其中一种。扩展编址的判断依据不是固定长度而是每个地址字节的最低位最低位为 0 继续读下一字节最低位为 1 才结束。解决Server 端地址固定按四字节处理Client 端地址按一字节处理。同时把地址解析逻辑写成循环判断最低位不要写死地址长度这样以后换电表型号时不用改解析代码。抓包时如果发现目的地址里某字节最低位为 1 但仍然有后续字节说明抓包工具或解析脚本对扩展编址的判断逻辑有误。5.2 HCS/FCS 把 0x7e 算进去CRC 永远不对现象用标准 CRC16 算法计算帧校验发出去之后对端回 FRMR或者自己用抓包工具校验时 FCS 一直报错。原因0x7e 是 HDLC 帧标志不参与任何 CRC 计算。帧头校验 HCS 只覆盖帧类型与帧长、目的地址域、源地址域、控制域这四个字段。数据帧校验 FCS 覆盖的是 HCS 之后到 FCS 之前的字节包括 LLC 帧头和用户数据。如果把 0x7e 算进去或者把 HCS 的校验范围扩大到数据段校验结果必错。解决把 HCS 和 FCS 分开计算。HCS 的起始字节是帧类型与帧长字段第一字节结束于控制域最后一字节。FCS 的起始字节是 HCS 后一字节结束于 FCS 前一个字节。CRC 多项式按 IEC 62056-46 附录 A 的实现来做注意初值和输出是否需要异或不同实现之间经常差在这两个细节上。5.3 长帧 S 位不做 RR 拉取数据永远收不完整现象读取负荷曲线时收到第一帧数据后就不再有任何后续数据报文解析只拿到零散的几个分段。原因长数据帧被分割后S 位置 1 只是告诉接收端“这是分段帧”并不会让 Server 连续发送所有分段。Server 按 window size 发送完一批后必须等 Client 发 RR 帧才会继续传输下一批。如果没有实现 RR 拉取逻辑链路就停顿在第一段。解决在接收状态机里维护分段索引。收到 S1 的帧后解析完当前段就构造 RR 帧把 RRR 置为正确接收计数请求下一段收到 S0 的帧说明是最后一段把已收到的所有分段按顺序拼装后交给应用层。负荷曲线的帧长度超过 128 字节边界是常态这个逻辑必须在协议栈里提前实现。5.4 上次会话未正常释放下一次 AARQ 被直接拒绝现象第一次读数据成功后直接关闭串口或断开网络过一会儿再次连接时发 AARQServer 回 AARE 的 result 非 0或者链路层直接回 DM。原因链路层和应用层的会话状态都残留在 Server 端。Server 没有收到 DISC不清楚会话已经结束超时周期内如果收到新的 AARQ会被判定为异常会话而拒绝。解决正常流程必须走完 DISC收到 UA 或 DM 后再关物理端口。如果收到的是 DM说明 Server 端链路早已超时断开此时不要再继续发 AARQ直接回到 SNRM 重新建立链路层连接。把这个检查放进状态机里每次通讯结束都强制走“DISC → UA/DM → 关闭端口”的收尾路径能省掉很多“第二次连不上”的排查时间。6. 抓包验证把帧序列和字段解析做成一个可复用脚本DLMS 调试真正难的不是单个帧的解析而是完整会话中帧与帧之间的状态关系。我习惯的做法是写一个针对链路层的解析脚本把抓到的每一帧按 HDLC 字段拆开同时按状态机顺序校验当前帧是否合法。下面是这套思路的核心片段。def parse_dlms_frame(raw: bytes): assert raw[0] 0x7e and raw[-1] 0x7e p raw[1:-1] ft_len int.from_bytes(p[0:2], big) seg (ft_len 8) 0x08 # S 位 length ft_len 0x07ff if len(p) ! length: print(长度不符, len(p), length) # 读取目的地址按最低位判断是否结束 idx 2 dest bytearray() while True: b p[idx] dest.append(b) idx 1 if b 0x01: break src bytearray([p[idx]]) control p[idx 1] print(S位:, seg, 目标地址:, dest.hex(), 源地址:, src.hex()) print(控制字: 0x%02x % control) return control, dest这个脚本的逻辑很简单先剥离 0x7e再读帧类型与帧长字段从中取出 S 位和帧长度地址部分按扩展编址逐字节读取直到最低位为 1 为止然后取源地址和控制字。把这段逻辑固化成函数后抓包文件里的每一帧都能快速落成结构化的字段表对比手工分析能快很多。验证一个完整会话时我一般会在脚本里维护一个状态变量按“SNRM → UA → AARQ → AARE → 数据请求/响应 → RR → DISC → UA/DM”的顺序做断言。状态不匹配就打印当前帧和前序状态这样能立刻定位是哪个环节出了问题。以前有一次长帧粘包排了三个小时没头绪后来直接把帧序列按这个顺序打印出来发现 client 在收到 S1 的分段后没有发 RR而是在等下一段数据问题一目了然。从那以后我每次调试 DLMS 都强制先抓一轮完整帧序列做状态回归比对而不是只看当前这一帧。这个习惯救了我好几次。希望帮到你。本文还有配套的精品资源点击获取
返回列表