当前位置: 首页 > news >正文

手把手教你用Java解析DLMS/HDLC协议帧(附完整代码与报文示例)

手把手教你用Java解析DLMS/HDLC协议帧(附完整代码与报文示例)

在物联网设备通信领域,DLMS/COSEM协议栈中的HDLC帧解析是每个开发者必须掌握的硬核技能。当你的智能电表或传感器返回一串类似7E A0 46...的十六进制报文时,如何快速定位帧头、提取有效数据并验证完整性?本文将用可立即复用的Java代码,带你拆解HDLC帧的每个比特位。

1. 环境准备与基础工具类

工欲善其事,必先利其器。我们先构建两个基础工具方法:

public class HexUtils { // 十六进制字符串转字节数组 public static byte[] hexToBytes(String hex) { hex = hex.replaceAll("\\s", ""); byte[] bytes = new byte[hex.length() / 2]; for (int i = 0; i < bytes.length; i++) { bytes[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16); } return bytes; } // 字节数组转二进制字符串(调试用) public static String byteToBinary(byte b) { return String.format("%8s", Integer.toBinaryString(b & 0xFF)) .replace(' ', '0'); } }

注意:实际项目中建议使用Apache Commons Codec的Hex类,这里为展示原理采用手动实现。

2. 帧边界定位与标志位处理

HDLC帧以0x7E作为开始和结束标志。我们需要处理比特填充规则——当数据中出现连续五个1时,发送方会自动插入一个0。

public class HdlcParser { private static final byte FLAG = 0x7E; public static byte[] extractFrame(byte[] data) { List<Byte> frame = new ArrayList<>(); boolean inFrame = false; for (int i = 0; i < data.length; i++) { if (data[i] == FLAG) { if (!inFrame) { inFrame = true; } else { break; // 遇到结束标志 } } else if (inFrame) { frame.add(data[i]); } } return toByteArray(frame); } private static byte[] toByteArray(List<Byte> list) { byte[] arr = new byte[list.size()]; for (int i = 0; i < arr.length; i++) { arr[i] = list.get(i); } return arr; } }

常见陷阱

  • 原始数据可能包含多个连续0x7E
  • 比特填充可能导致校验失败(需反向处理)

3. 解析帧控制域与地址域

HDLC控制域包含帧类型(I/S/U帧)和序列号,地址域则采用可变长度:

public class FrameHeader { private byte control; private byte[] address; public static FrameHeader parse(byte[] frame) { FrameHeader header = new FrameHeader(); // 地址域长度判断(LSB为1表示结束) int addrLen = 1; while ((frame[addrLen - 1] & 0x01) == 0) { addrLen++; } header.address = Arrays.copyOfRange(frame, 0, addrLen); header.control = frame[addrLen]; return header; } public boolean isInformationFrame() { return (control & 0x01) == 0; } // 其他getter方法... }

关键位操作技巧:

  • control & 0x01判断最低位
  • (control >> 1) & 0x07提取序列号

4. CRC校验与完整解析流程

HDLC使用16位CRC校验(CCITT标准),以下是完整解析示例:

public class HdlcDemo { public static void main(String[] args) { String rawData = "7E A0 46 01 00 01 02 03 04 05 06 07 08 09 7E"; byte[] bytes = HexUtils.hexToBytes(rawData); byte[] frame = HdlcParser.extractFrame(bytes); FrameHeader header = FrameHeader.parse(frame); int payloadStart = 1 + header.getAddress().length + 1; byte[] payload = Arrays.copyOfRange( frame, payloadStart, frame.length - 2 // 排除FCS ); // CRC校验 int receivedCrc = ((frame[frame.length - 2] & 0xFF) << 8) | (frame[frame.length - 1] & 0xFF); int calculatedCrc = calculateCrc(frame, 0, frame.length - 2); System.out.println("地址域: " + HexUtils.bytesToHex(header.getAddress())); System.out.println("有效载荷: " + HexUtils.bytesToHex(payload)); System.out.println("CRC校验: " + (receivedCrc == calculatedCrc ? "通过" : "失败")); } private static int calculateCrc(byte[] data, int start, int end) { // 实现CCITT CRC-16算法 int crc = 0xFFFF; for (int i = start; i < end; i++) { crc ^= (data[i] & 0xFF) << 8; for (int j = 0; j < 8; j++) { if ((crc & 0x8000) != 0) { crc = (crc << 1) ^ 0x1021; } else { crc <<= 1; } } } return crc & 0xFFFF; } }

调试技巧

  • 使用Wireshark捕获原始报文对比
  • 打印每个阶段的字节数组(HexUtils.bytesToHex()
  • 特别注意字节序(大端/小端)

5. 高级话题:异常处理与性能优化

工业级实现还需要考虑:

// 1. 比特填充还原 private static byte[] removeBitStuffing(byte[] data) { BitSet bitSet = BitSet.valueOf(data); int consecutiveOnes = 0; for (int i = 0; i < bitSet.length(); i++) { if (bitSet.get(i)) { consecutiveOnes++; if (consecutiveOnes == 5) { bitSet.clear(i + 1); // 移除填充位 consecutiveOnes = 0; } } else { consecutiveOnes = 0; } } return bitSet.toByteArray(); } // 2. 使用ByteBuffer提升性能 ByteBuffer buffer = ByteBuffer.wrap(frame) .order(ByteOrder.BIG_ENDIAN); byte address = buffer.get(); byte control = buffer.get();

实际项目中遇到的典型问题:

  • 报文分段传输(需缓冲处理)
  • 超时重发机制
  • 多设备地址冲突检测
http://www.jsqmd.com/news/789563/

相关文章:

  • STM32H7实战:用FMC+DMA双缓冲搞定AD7606,8通道同步采样避坑指南(附代码)
  • 【STM32H7 DSP实战】IAR8环境下的CMSIS-DSP库移植与性能调优指南
  • 从零构建AI知识助手:基于RAG与本地大模型的个人知识管理系统实战
  • 手把手教你用两块DWM1000模块玩转UWB测距:从硬件接线到TWR算法代码逐行解析
  • 144Hz艾尔登法环体验:帧率解锁、视野扩展与宽屏支持的终极指南
  • 从“能量搬运工”视角看Boost电路:连续、断续、空载时,电感里的能量都去哪儿了?
  • ContextCapture集群实战:基于SMB共享的Windows多机协同建模配置详解
  • AI工具搭建自动化视频生成PromptLayer
  • 终极指南:用DXVK驱动在Linux上流畅运行Windows游戏
  • 【儿童蜡笔推荐】儿童蜡笔品牌实测评测:五大核心维度实力排名解析 - 得赢
  • LinkSwift:如何用浏览器脚本轻松获取网盘直链下载地址
  • Navicat与DBeaver连接Oracle数据库实战:从配置到避坑全解析
  • 2025年九大网盘直链下载助手:免费高效的文件下载终极方案
  • 为什么你的KV缓存正在拖垮大模型推理?SITS专家现场演示4种反模式及实时修复路径
  • 如何用SMUDebugTool解锁AMD Ryzen隐藏性能:5个突破传统限制的技巧
  • 【正点原子I.MX6ULL】从零构建:交叉编译环境搭建与U-boot、Linux内核编译实战
  • OPENSSL生成非对称加密公私钥
  • DLSS Swapper深度解析:游戏超采样技术的智能管理架构
  • 三步轻松下载B站4K大会员视频:免费开源工具完全指南
  • 释放网易云音乐:ncmToMp3解密工具完全指南
  • 如何实现跨平台局域网文件传输:LAN Share完整使用指南
  • Excel数据检索革命:5分钟搞定100个文件的批量查询神器
  • 抖音评论采集神器:3分钟获取完整评论数据的终极方案
  • 一键解锁九大网盘高速下载:告别限速困扰的本地化解决方案
  • Android车载人工智能系统开发实践
  • 终极效率革命:Rusted PackFile Manager如何将全面战争MOD开发效率提升500%
  • 别急着换HBA卡!Linux服务器messages日志狂刷multipath报错,先按这个流程查存储
  • Origin Pro 2023保姆级教程:从数据导入到论文配图,手把手教你搞定科研绘图
  • 保姆级教程:手把手教你用CANdela Studio配置车载诊断数据库(CDD文件)
  • 嵌入式开发者如何利用Taotoken管理多个大模型API密钥