|
|
@@ -0,0 +1,260 @@
|
|
|
+package com.zksy.radar.utils;
|
|
|
+
|
|
|
+import com.zksy.common.exception.InvalidMessageException;
|
|
|
+import com.zksy.radar.domain.RadarData;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.util.Arrays;
|
|
|
+
|
|
|
+public class DataParser {
|
|
|
+
|
|
|
+ private static final Logger logger = LoggerFactory.getLogger(DataParser.class);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 平升6216协议完整校验:
|
|
|
+ * 1. 长度检查
|
|
|
+ * 2. 系统识别码检查
|
|
|
+ * 3. 异或校验(外层 PS 协议)
|
|
|
+ * 4. CRC16 校验(内层 历史记录协议)
|
|
|
+ */
|
|
|
+ public static void validateMessage(byte[] msgBytes) throws InvalidMessageException {
|
|
|
+ // 1. 长度检查
|
|
|
+ if (msgBytes == null || msgBytes.length < 10) {
|
|
|
+ throw new InvalidMessageException("数据帧长度过短");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 帧头与系统识别码检查
|
|
|
+ if (msgBytes[0] != 0x12 || msgBytes[1] != 0x34 || msgBytes[2] != 0x56) {
|
|
|
+ throw new InvalidMessageException("系统识别码错误,应为 12 34 56");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 校验整帧长度(从系统识别码到最后1字节)
|
|
|
+ int declaredLen = ((msgBytes[3] & 0xFF) << 8) | (msgBytes[4] & 0xFF);
|
|
|
+ if (declaredLen != msgBytes.length) {
|
|
|
+ throw new InvalidMessageException(String.format(
|
|
|
+ "帧长度不匹配:声明=%d 实际=%d", declaredLen, msgBytes.length));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 异或校验(最后一字节)
|
|
|
+ byte receivedXor = msgBytes[msgBytes.length - 1];
|
|
|
+ byte[] xorData = Arrays.copyOfRange(msgBytes, 0, msgBytes.length - 1);
|
|
|
+ byte calculatedXor = ProtocolUtils.calculateXorCheck(xorData);
|
|
|
+ if (receivedXor != calculatedXor) {
|
|
|
+ throw new InvalidMessageException(String.format(
|
|
|
+ "异或校验失败:计算=0x%02X 接收=0x%02X", calculatedXor, receivedXor));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 仅对上报历史记录帧(0x31)进行 CRC 校验
|
|
|
+ byte frameType = msgBytes[6];
|
|
|
+ if (frameType == 0x31) {
|
|
|
+ // CRC 高低字节在帧末尾的倒数第3、第2字节(倒数第1是异或)
|
|
|
+ int crcEndIndex = msgBytes.length - 3;
|
|
|
+ int crcHighIndex = crcEndIndex;
|
|
|
+ int crcLowIndex = crcEndIndex + 1;
|
|
|
+
|
|
|
+ if (crcHighIndex <= 0) {
|
|
|
+ throw new InvalidMessageException("CRC数据段索引错误");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设备编码开始(内层协议起点):
|
|
|
+ // 外层结构 3+2+1+1+1+n+1+m => 源地址/目的地址结束后紧接设备编码(0x01)
|
|
|
+ // 简化方式:找到第一个 0x01 2C 序列即可(设备编码 + 功能码)
|
|
|
+ int deviceCodeIndex = -1;
|
|
|
+ for (int i = 0; i < msgBytes.length - 1; i++) {
|
|
|
+ if (msgBytes[i] == 0x01 && msgBytes[i + 1] == 0x2C) {
|
|
|
+ deviceCodeIndex = i;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (deviceCodeIndex == -1) {
|
|
|
+ throw new InvalidMessageException("未找到设备编码起点 (0x01 2C)");
|
|
|
+ }
|
|
|
+
|
|
|
+ byte[] crcData = Arrays.copyOfRange(msgBytes, deviceCodeIndex, crcEndIndex);
|
|
|
+ int calculatedCrc = ProtocolUtils.calculateCRC16(crcData);
|
|
|
+ int receivedCrc = ((msgBytes[crcLowIndex] & 0xFF) << 8) | (msgBytes[crcHighIndex] & 0xFF);
|
|
|
+
|
|
|
+ if (calculatedCrc != receivedCrc) {
|
|
|
+ throw new InvalidMessageException(String.format(
|
|
|
+ "CRC校验失败:计算=0x%04X 接收=0x%04X", calculatedCrc, receivedCrc));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ logger.debug("消息通过所有校验");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析消息(支持历史记录帧0x31和结束通讯帧0x34,协议2.2.3)
|
|
|
+ */
|
|
|
+ public static RadarData parseMessage(byte[] msgBytes) {
|
|
|
+ RadarData data = new RadarData();
|
|
|
+ int index = 0;
|
|
|
+
|
|
|
+ // 1. 系统识别码
|
|
|
+ data.setSystemIdentifier(String.format("%02X%02X%02X", msgBytes[0], msgBytes[1], msgBytes[2]));
|
|
|
+ index += 3;
|
|
|
+
|
|
|
+ // 2. 帧长度
|
|
|
+ int frameLength = ((msgBytes[index] & 0xFF) << 8) | (msgBytes[index + 1] & 0xFF);
|
|
|
+ data.setFrameLength(String.valueOf(frameLength));
|
|
|
+ index += 2;
|
|
|
+
|
|
|
+ // 3. 包序号、帧类型
|
|
|
+ data.setPacketSequence(String.format("%02X", msgBytes[index++]));
|
|
|
+ byte frameType = msgBytes[index++];
|
|
|
+ data.setDataType(String.format("0x%02X", frameType));
|
|
|
+
|
|
|
+ // 4. 源地址:按规则计算长度(原地址标识转10进制,奇数+1后除2)
|
|
|
+ int srcLenFlag = msgBytes[index++] & 0xFF; // 源地址标识(10进制)
|
|
|
+ int srcAddrSize = calculateAddrLength(srcLenFlag); // 计算源地址字节长度
|
|
|
+ srcAddrSize = Math.min(srcAddrSize, 10); // 防止越界,最多10字节
|
|
|
+ byte[] srcAddr = Arrays.copyOfRange(msgBytes, index, index + srcAddrSize);
|
|
|
+ data.setSourceAddr(parseBcdToStr(srcAddr));
|
|
|
+ index += srcAddrSize;
|
|
|
+
|
|
|
+ // 5. 目的地址:按相同规则计算长度
|
|
|
+ int dstLenFlag = msgBytes[index++] & 0xFF; // 目的地址标识(10进制)
|
|
|
+ int dstAddrSize = calculateAddrLength(dstLenFlag); // 计算目的地址字节长度
|
|
|
+ dstAddrSize = Math.min(dstAddrSize, 10); // 防止越界,最多10字节
|
|
|
+ byte[] dstAddr = Arrays.copyOfRange(msgBytes, index, index + dstAddrSize);
|
|
|
+ data.setDestAddr(parseBcdToStr(dstAddr));
|
|
|
+ data.setDestAddrLength(String.valueOf(dstAddrSize));
|
|
|
+ index += dstAddrSize;
|
|
|
+
|
|
|
+ // 6. 帧类型 = 0x31
|
|
|
+ if (frameType == 0x31) {
|
|
|
+ // 设备ID
|
|
|
+ data.setDeviceCode(String.format("%02X", msgBytes[index++]));
|
|
|
+ // 功能码
|
|
|
+ data.setFunctionCode(String.format("%02X", msgBytes[index++]));
|
|
|
+
|
|
|
+ // 预留6字节(原代码拆成了4+2,合并为6字节)
|
|
|
+ data.setReserve1(String.format("%02X%02X%02X%02X%02X%02X",
|
|
|
+ msgBytes[index], msgBytes[index+1], msgBytes[index+2],
|
|
|
+ msgBytes[index+3], msgBytes[index+4], msgBytes[index+5]));
|
|
|
+ index += 6;
|
|
|
+
|
|
|
+ // 记录数量
|
|
|
+ int recordCount = msgBytes[index++] & 0xFF;
|
|
|
+ data.setRecordCount(String.valueOf(recordCount));
|
|
|
+
|
|
|
+ // 记录格式
|
|
|
+ int recordFormat = ((msgBytes[index] & 0xFF) << 8) | (msgBytes[index + 1] & 0xFF);
|
|
|
+ data.setRecordFormat(String.format("%04X", recordFormat));
|
|
|
+ index += 2;
|
|
|
+
|
|
|
+ // 电池电压(整型转浮点,除以100)
|
|
|
+ int powerVolt = ((msgBytes[index] & 0xFF) << 8) | (msgBytes[index + 1] & 0xFF);
|
|
|
+ data.setPowerVoltage(String.format("%.2f", powerVolt / 100.0));
|
|
|
+ index += 2;
|
|
|
+
|
|
|
+ // 现场状态(解析D3位判断是否最后一包)
|
|
|
+ byte fieldStatus = msgBytes[index++];
|
|
|
+ data.setFieldStatus(String.format("%02X", fieldStatus));
|
|
|
+ // D3位:0x08 对应二进制 00001000,按位与判断
|
|
|
+ data.setIsLastPacket((fieldStatus & 0x08) != 0);
|
|
|
+
|
|
|
+ // 协议版本(两个字节)
|
|
|
+ data.setProtocolVersion(String.format("%02X", msgBytes[index++]));
|
|
|
+ data.setParamVersion(String.format("%02X", msgBytes[index++]));
|
|
|
+ // 信号质量
|
|
|
+ data.setSignalQuality(String.valueOf(msgBytes[index++] & 0xFF));
|
|
|
+
|
|
|
+ // 预留3字节
|
|
|
+ data.setReserve3(String.format("%02X%02X%02X", msgBytes[index], msgBytes[index + 1], msgBytes[index + 2]));
|
|
|
+ index += 3;
|
|
|
+
|
|
|
+ // --- 历史记录部分 ---
|
|
|
+ if (recordCount > 0 && index + 4 <= msgBytes.length) {
|
|
|
+ // 数据采集时间(4字节)
|
|
|
+ int ts = ((msgBytes[index] & 0xFF) << 24) | ((msgBytes[index + 1] & 0xFF) << 16)
|
|
|
+ | ((msgBytes[index + 2] & 0xFF) << 8) | (msgBytes[index + 3] & 0xFF);
|
|
|
+ LocalDateTime recordTime = ProtocolUtils.convertToDateTime(ts);
|
|
|
+ data.setTimestampSince(recordTime);
|
|
|
+ index += 4;
|
|
|
+
|
|
|
+ // 表1净累计(4字节浮点)
|
|
|
+ float netTotal = bytesToFloat(msgBytes, index);
|
|
|
+ index += 4;
|
|
|
+ // 表1瞬时流量(4字节浮点)
|
|
|
+ float instantFlow = bytesToFloat(msgBytes, index);
|
|
|
+ index += 4;
|
|
|
+ // 流速(4字节浮点)
|
|
|
+ float flowSpeed = bytesToFloat(msgBytes, index);
|
|
|
+ index += 4;
|
|
|
+ // 水位(4字节浮点)
|
|
|
+ float waterLevel = bytesToFloat(msgBytes, index);
|
|
|
+ index += 4;
|
|
|
+ // 开关量(4字节整型)
|
|
|
+ long switchVal = bytesToUInt32(msgBytes, index);
|
|
|
+ index += 4;
|
|
|
+
|
|
|
+ // 设置浮点型数据(保留两位小数展示)
|
|
|
+ data.setMeter1NetTotal(String.format("%.2f", netTotal));
|
|
|
+ data.setMeter1InstantFlow(String.format("%.2f", instantFlow));
|
|
|
+ data.setFlowSpeed(String.format("%.2f", flowSpeed));
|
|
|
+ data.setWaterLevel(String.format("%.2f", waterLevel));
|
|
|
+ data.setSwitchValue(String.format("%08X", switchVal));
|
|
|
+
|
|
|
+ // 格式化历史记录字符串
|
|
|
+ data.setHistoryRecords(String.format(
|
|
|
+ "时间:%s,净累计:%.2f,瞬时流量:%.2f,流速:%.2f,水位:%.2f,开关量:%08X",
|
|
|
+ recordTime, netTotal, instantFlow, flowSpeed, waterLevel, switchVal));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ data.setCreateTime(LocalDateTime.now());
|
|
|
+ return data;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算地址字节长度:原地址标识转10进制,奇数+1后除2
|
|
|
+ * @param lenFlag 地址标识(10进制值)
|
|
|
+ * @return 地址字节长度
|
|
|
+ */
|
|
|
+ private static int calculateAddrLength(int lenFlag) {
|
|
|
+ int temp = lenFlag;
|
|
|
+ // 奇数则+1
|
|
|
+ if (temp % 2 != 0) {
|
|
|
+ temp += 1;
|
|
|
+ }
|
|
|
+ // 除2得到字节长度
|
|
|
+ return temp / 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 4字节(高字节在前)转单精度浮点型(IEEE 754)
|
|
|
+ * @param data 字节数组
|
|
|
+ * @param offset 起始偏移量
|
|
|
+ * @return 单精度浮点值
|
|
|
+ */
|
|
|
+ private static float bytesToFloat(byte[] data, int offset) {
|
|
|
+ // 先将4字节转为int(高字节在前)
|
|
|
+ int intValue = ((data[offset] & 0xFF) << 24)
|
|
|
+ | ((data[offset + 1] & 0xFF) << 16)
|
|
|
+ | ((data[offset + 2] & 0xFF) << 8)
|
|
|
+ | (data[offset + 3] & 0xFF);
|
|
|
+ // 按IEEE 754规则转换为float
|
|
|
+ return Float.intBitsToFloat(intValue);
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 工具方法:BCD转字符串 */
|
|
|
+ private static String parseBcdToStr(byte[] bytes) {
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ for (byte b : bytes) {
|
|
|
+ sb.append(String.format("%02X", b));
|
|
|
+ }
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 工具方法:4字节无符号整型 */
|
|
|
+ private static long bytesToUInt32(byte[] data, int offset) {
|
|
|
+ return ((long) (data[offset] & 0xFF) << 24)
|
|
|
+ | ((long) (data[offset + 1] & 0xFF) << 16)
|
|
|
+ | ((long) (data[offset + 2] & 0xFF) << 8)
|
|
|
+ | ((long) (data[offset + 3] & 0xFF));
|
|
|
+ }
|
|
|
+}
|