| | |
| | | import java.util.Arrays; |
| | | |
| | | public class Dell55AA1BParser { |
| | | // 常量定义 |
| | | private static final String EXPECTED_HEADER = "55AA1B"; // 协议头 |
| | | private static final int MIN_PACKET_LENGTH = 36; // 最小包长度(字符数): 固定字段18字节=36字符 |
| | | private static final int FIXED_FIELDS_LENGTH = 11; // 从标签ID到保留字段的固定长度(字节) |
| | | // 常量定义 |
| | | private static final String EXPECTED_HEADER = "55AA1B"; // 协议头 |
| | | private static final int MIN_PACKET_LENGTH = 36; // 最小包长度(字符数): 固定字段18字节=36字符 |
| | | private static final int FIXED_FIELDS_LENGTH = 11; // 从标签ID到保留字段的固定长度(字节) |
| | | private static final ThreadLocal<ParseResult> RESULT_CACHE = |
| | | ThreadLocal.withInitial(ParseResult::new); |
| | | |
| | | // 解析结果类 |
| | | // 解析结果类 |
| | | public static class ParseResult { |
| | | public int dataLength; // 数据长度 |
| | | public int messageType; // 消息类型 |
| | | public String tagId; // 标签ID(2字节) |
| | | public int distance; // 距离(厘米) |
| | | public int angle; // 角度(度) |
| | | public int signalQuality; // 信号质量(0-255) |
| | | public int buttonPressed; // 按钮状态 |
| | | public int power; // 电量 |
| | | public String reserved; // 保留字段 |
| | | public String userData; // 用户数据 |
| | | public String checksum; // 校验和 |
| | | public int dataLength; // 数据长度 |
| | | public int messageType; // 消息类型 |
| | | public String tagId; // 标签ID(2字节) |
| | | public int distance; // 距离(厘米) |
| | | public int angle; // 角度(度) |
| | | public int signalQuality; // 信号质量(0-255) |
| | | public int buttonPressed; // 按钮状态 |
| | | public int power; // 电量 |
| | | public String reserved; // 保留字段 |
| | | public String userData; // 用户数据 |
| | | public String checksum; // 校验和 |
| | | |
| | | public void reset() { |
| | | dataLength = 0; |
| | |
| | | } |
| | | |
| | | /** |
| | | * 解析55AA1F协议数据 |
| | | * @param message 十六进制字符串 |
| | | * @return 解析结果(错误时返回null) |
| | | * 解析55AA1F协议数据 |
| | | * @param message 十六进制字符串 |
| | | * @return 解析结果(错误时返回null) |
| | | */ |
| | | public static ParseResult parse(String message, String ip, int port) { |
| | | if (message == null || message.isEmpty()) { |
| | |
| | | return null; |
| | | } |
| | | |
| | | // 清洗数据:移除所有非十六进制字符 |
| | | // 清洗数据:移除所有非十六进制字符 |
| | | char[] cleanedMessage = cleanMessage(message); |
| | | |
| | | if (cleanedMessage == null) { |
| | |
| | | return null; |
| | | } |
| | | |
| | | // 检查最小长度 |
| | | // 检查最小长度 |
| | | if (cleanedMessage.length < MIN_PACKET_LENGTH) { |
| | | System.err.println("Message too short from " + ip + ":" + port + |
| | | ". Expected at least " + MIN_PACKET_LENGTH + |
| | |
| | | return null; |
| | | } |
| | | |
| | | // 协议头校验 (55AA1B) |
| | | // 协议头校验 (55AA1B) |
| | | if (cleanedMessage.length < 6 || |
| | | !new String(cleanedMessage, 0, 6).equals(EXPECTED_HEADER)) { |
| | | System.err.println("Invalid header in message from " + ip + ":" + port); |
| | |
| | | result.reset(); |
| | | |
| | | try { |
| | | // 解析数据长度 (位置6-7) |
| | | // 解析数据长度 (位置6-7) |
| | | result.dataLength = HexUtils.fastHexToByte(cleanedMessage[6], cleanedMessage[7]); |
| | | |
| | | // 计算期望的总字符长度 |
| | | int expectedCharLength = 14 + result.dataLength * 2; // 14=包头6+消息类型2+数据长度2+校验和4 |
| | | // 计算期望的总字符长度 |
| | | int expectedCharLength = 14 + result.dataLength * 2; // 14=包头6+消息类型2+数据长度2+校验和4 |
| | | |
| | | // if (cleanedMessage.length < expectedCharLength) { |
| | | // System.err.println("Incomplete message from " + ip + ":" + port + |
| | |
| | | // return null; |
| | | // } |
| | | |
| | | // 解析消息类型 (位置4-5) |
| | | // 解析消息类型 (位置4-5) |
| | | result.messageType = HexUtils.fastHexToByte(cleanedMessage[4], cleanedMessage[5]); |
| | | |
| | | // 解析标签ID (位置8-11),直接取字符串 |
| | | // 解析标签ID (位置8-11),直接取字符串 |
| | | result.tagId = new String(cleanedMessage, 8, 4); |
| | | |
| | | // 检查是否有足够长度解析固定字段 |
| | | if (cleanedMessage.length < 30) { // 需要至少15字节=30字符 |
| | | // 检查是否有足够长度解析固定字段 |
| | | if (cleanedMessage.length < 30) { // 需要至少15字节=30字符 |
| | | System.err.println("Message too short for fixed fields from " + ip + ":" + port); |
| | | return null; |
| | | } |
| | | |
| | | // 解析距离 (位置12-15, 2字节小端整数) |
| | | // 解析距离 (位置12-15, 2字节小端整数) |
| | | int distLow = HexUtils.fastHexToByte(cleanedMessage[12], cleanedMessage[13]); |
| | | int distHigh = HexUtils.fastHexToByte(cleanedMessage[14], cleanedMessage[15]); |
| | | result.distance = (distHigh << 8) | distLow; |
| | | |
| | | // 解析角度 (位置16-19, 2字节小端有符号整数) |
| | | // 解析角度 (位置16-19, 2字节小端有符号整数) |
| | | int angleLow = HexUtils.fastHexToByte(cleanedMessage[16], cleanedMessage[17]); |
| | | int angleHigh = HexUtils.fastHexToByte(cleanedMessage[18], cleanedMessage[19]); |
| | | short angleShort = (short) ((angleHigh << 8) | angleLow); |
| | | result.angle = angleShort; |
| | | |
| | | // 解析信号质量 (位置20-21) |
| | | // 解析信号质量 (位置20-21) |
| | | result.signalQuality = HexUtils.fastHexToByte(cleanedMessage[20], cleanedMessage[21]); |
| | | |
| | | // 解析按钮状态 (位置22-23) |
| | | // 解析按钮状态 (位置22-23) |
| | | result.buttonPressed = HexUtils.fastHexToByte(cleanedMessage[22], cleanedMessage[23]); |
| | | |
| | | // 解析电量 (位置24-25) |
| | | // 解析电量 (位置24-25) |
| | | result.power = HexUtils.fastHexToByte(cleanedMessage[24], cleanedMessage[25]); |
| | | |
| | | // 解析保留字段 (位置26-29) |
| | | // 解析保留字段 (位置26-29) |
| | | result.reserved = new String(cleanedMessage, 26, 4); |
| | | |
| | | // 解析用户数据 |
| | | // 解析用户数据 |
| | | // int userDataStart = 30; |
| | | // int userDataCharLength = (result.dataLength - FIXED_FIELDS_LENGTH) * 2; |
| | | // if (userDataCharLength > 0) { |
| | |
| | | // result.userData = ""; |
| | | // } |
| | | |
| | | // 解析校验和 (最后4个字符) |
| | | // 解析校验和 (最后4个字符) |
| | | result.checksum = new String(cleanedMessage, cleanedMessage.length - 4, 4); |
| | | |
| | | // 验证校验和 |
| | | // 验证校验和 |
| | | // byte[] packetBytes = hexStringToByteArray(new String(cleanedMessage)); |
| | | // if (!verifyChecksum(packetBytes)) { |
| | | // System.err.println("Checksum verification failed for packet from " + ip + ":" + port); |
| | |
| | | } |
| | | |
| | | /** |
| | | * 将十六进制字符串转换为字节数组 |
| | | * 将十六进制字符串转换为字节数组 |
| | | */ |
| | | private static byte[] hexStringToByteArray(String s) { |
| | | int len = s.length(); |
| | |
| | | } |
| | | |
| | | /** |
| | | * 校验数据包 |
| | | * 去掉包头求和取反,校验码为小端模式 |
| | | * 校验数据包 |
| | | * 去掉包头求和取反,校验码为小端模式 |
| | | */ |
| | | private static boolean verifyChecksum(byte[] packet) { |
| | | int len = packet.length; |
| | | if (len < 4) return false; |
| | | |
| | | int sum = 0; |
| | | // 从消息类型开始到校验码前结束 (跳过包头2字节) |
| | | // 从消息类型开始到校验码前结束 (跳过包头2字节) |
| | | for (int i = 2; i < len - 2; i++) { |
| | | sum += packet[i] & 0xFF; |
| | | } |
| | | sum = ~sum & 0xFFFF; // 取反并保留16位 |
| | | sum = ~sum & 0xFFFF; // 取反并保留16位 |
| | | |
| | | // 获取包中的校验码 (小端模式) |
| | | // 获取包中的校验码 (小端模式) |
| | | int receivedChecksum = ((packet[len - 2] & 0xFF) << 8) | (packet[len - 1] & 0xFF); |
| | | |
| | | return sum == receivedChecksum; |