evo-BMS/src/main/java/com/evobms/project/bms/controller/GBT32960FullDecoder.java

411 lines
16 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.evobms.project.bms.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
public class GBT32960FullDecoder {
private static final Logger log = LoggerFactory.getLogger(GBT32960FullDecoder.class);
// 从SN码生成AES密钥
private static byte[] generateKeyFromSn(String sn) {
String s = sn == null ? "" : sn;
String key;
if ("EVO0001".equals(s)) {
StringBuilder sb = new StringBuilder(16);
sb.append("EVO0001");
while (sb.length() < 16) sb.append('0');
key = sb.toString();
} else if (s.length() >= 16) {
key = s.substring(s.length() - 16);
} else {
StringBuilder sb = new StringBuilder(16);
sb.append(s);
while (sb.length() < 16) sb.append('0');
key = sb.toString();
}
return key.getBytes(StandardCharsets.UTF_8);
}
// AES解密
private static byte[] decryptData(byte[] encryptedData, byte[] key) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return cipher.doFinal(encryptedData);
}
// BCC校验
private static byte calculateBCC(byte[] data, int start, int end) {
byte bcc = 0;
for (int i = start; i < end; i++) {
bcc ^= data[i];
}
return bcc;
}
// 16进制字符串转字节数组
private static byte[] hexStringToByteArray(String hex) {
hex = hex.replaceAll("\\s", ""); // 移除空格
int len = hex.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
+ Character.digit(hex.charAt(i + 1), 16));
}
return data;
}
// 字节数组转16进制字符串
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X", b));
}
return sb.toString();
}
// 详细的协议解析方法
public static String parseGBT32960Data(byte[] receivedData, String deviceSn) {
String bytesData = null;
try {
log.info("=== GBT32960协议数据解析 ===");
log.info("实际数据长度: " + receivedData.length + " 字节");
// 1. 检查起始位
if (receivedData.length < 2 || receivedData[0] != 0x23 || receivedData[1] != 0x23) {
log.error("无效的起始位或数据过短");
return null;
}
log.info("起始位: 0x2323 ✓");
// 2. 打印完整数据用于调试
log.info("完整数据: " + bytesToHex(receivedData));
// 3. 分析协议结构
int offset = 2; // 跳过起始位
// 命令标识
byte commandId = receivedData[offset++];
log.info("命令标识: 0x" + String.format("%02X", commandId));
// 应答标志
byte responseFlag = receivedData[offset++];
log.info("应答标志: 0x" + String.format("%02X", responseFlag));
// VIN码 (17字节)
byte[] vin = Arrays.copyOfRange(receivedData, offset, offset + 17);
offset += 17;
log.info("VIN码: " + bytesToHex(vin) + " (" + new String(vin).trim() + ")");
// 加密方式
byte encryptionMethod = receivedData[offset++];
log.info("加密方式: 0x" + String.format("%02X", encryptionMethod));
if (encryptionMethod != 0x03) {
log.error("不支持的解密方式期望AES128(0x03)");
return null;
}
// 数据单元长度 (WORD, 大端序)
int dataUnitLength = ((receivedData[offset] & 0xFF) << 8) | (receivedData[offset + 1] & 0xFF);
offset += 2;
log.info("声明的数据单元长度: " + dataUnitLength + " 字节");
// 计算当前头部长度
int headerLength = offset;
log.info("协议头部长度: " + headerLength + " 字节");
// 剩余数据长度
int remainingDataLength = receivedData.length - headerLength;
log.info("剩余数据长度: " + remainingDataLength + " 字节");
// 4. 重新计算期望长度
// 方案1: 如果BCC是1字节
int expectedLength1 = headerLength + dataUnitLength + 1;
// 方案2: 如果BCC是2字节根据您最初描述
int expectedLength2 = headerLength + dataUnitLength + 2;
log.info("期望长度 (BCC=1字节): " + expectedLength1);
log.info("期望长度 (BCC=2字节): " + expectedLength2);
log.info("实际长度: " + receivedData.length);
// 5. 确定正确的数据范围
int encryptedDataStart = offset;
int encryptedDataEnd;
int bccStart;
if (receivedData.length == expectedLength1) {
// BCC为1字节
encryptedDataEnd = encryptedDataStart + dataUnitLength;
bccStart = encryptedDataEnd;
log.info("采用方案: BCC为1字节");
} else if (receivedData.length == expectedLength2) {
// BCC为2字节
encryptedDataEnd = encryptedDataStart + dataUnitLength;
bccStart = encryptedDataEnd;
log.info("采用方案: BCC为2字节");
} else {
// 自动适应:使用剩余的所有数据作为加密数据
encryptedDataEnd = receivedData.length - 1; // 假设BCC为1字节
int actualDataUnitLength = encryptedDataEnd - encryptedDataStart;
log.info("长度不匹配,使用实际数据单元长度: " + actualDataUnitLength + " 字节");
dataUnitLength = actualDataUnitLength;
bccStart = encryptedDataEnd;
}
// 6. 提取加密数据
byte[] encryptedData = Arrays.copyOfRange(receivedData, encryptedDataStart, encryptedDataStart + dataUnitLength);
log.info("加密数据长度: " + encryptedData.length + " 字节");
log.info("加密数据 (前64字节): " + bytesToHex(Arrays.copyOf(encryptedData, Math.min(64, encryptedData.length))) + "...");
if (encryptedData.length % 16 != 0) {
log.error("AES密文长度非法不是16倍数length=" + encryptedData.length);
return null;
}
// 7. 提取BCC校验码
int bccLength = receivedData.length - (encryptedDataStart + dataUnitLength);
byte[] receivedBCC = Arrays.copyOfRange(receivedData, encryptedDataStart + dataUnitLength, receivedData.length);
//log.info("BCC长度: " + bccLength + " 字节");
//log.info("接收到的BCC: " + bytesToHex(receivedBCC));
// 8. 计算BCC校验范围从命令单元开始到加密数据结束
int bccCalcStart = 2; // 从命令标识开始 (跳过0x2323)
int bccCalcEnd = encryptedDataStart + dataUnitLength;
byte calculatedBCC = calculateBCC(receivedData, bccCalcStart, bccCalcEnd);
log.info("BCC计算范围: 字节[" + bccCalcStart + "~" + bccCalcEnd + ")");
log.info("计算出的BCC: 0x" + String.format("%02X", calculatedBCC));
// 验证BCC (只比较第一个字节)
if (receivedBCC.length > 0 && calculatedBCC == receivedBCC[0]) {
log.info("BCC校验通过 ✓");
} else {
log.error("BCC校验失败!");
}
//使用的AES密钥
byte[] aesKey = generateKeyFromSn(deviceSn);
System.out.println(Arrays.toString(aesKey));
System.out.println(aesKey.length);
log.info("AES密钥: " + bytesToHex(aesKey));
byte[] decryptedData = decryptData(encryptedData, aesKey);
bytesData = bytesToHex(decryptedData);
log.info("解密成功!");
log.info("解密后数据长度: " + decryptedData.length + " 字节");
log.info("解密后数据 (完整): " + bytesData);
// 10. 解析解密后的数据单元
// parseDecryptedDataUnit(decryptedData);
} catch (Exception e) {
log.error("解析过程中发生错误: " + e.getMessage());
e.printStackTrace();
}
return bytesData;
}
/* public static String parseGBT32960Data(byte[] frame, String deviceSn) throws Exception {
if (frame == null || frame.length < 25) {
throw new IllegalArgumentException("报文长度非法");
}
int offset = 0;
// 1. 起始符校验 0x23 0x23
if (frame[offset++] != 0x23 || frame[offset++] != 0x23) {
throw new IllegalArgumentException("非法起始位非0x2323");
}
// 2. 命令标识
byte commandId = frame[offset++];
// 3. 应答标志
byte responseFlag = frame[offset++];
// 4. VIN (17字节)
byte[] vinBytes = Arrays.copyOfRange(frame, offset, offset + 17);
String vin = new String(vinBytes).trim();
offset += 17;
// 5. 加密方式
byte encryptType = frame[offset++];
// 6. 数据单元长度 (大端)
if (offset + 2 > frame.length) {
throw new IllegalArgumentException("数据单元长度字段越界");
}
int dataUnitLength = ((frame[offset] & 0xFF) << 8) | (frame[offset + 1] & 0xFF);
offset += 2;
int headerLength = offset;
// 7. 校验整体长度
// 标准格式:
// 头部 + 数据单元 + 1字节BCC
int expectedLength = headerLength + dataUnitLength + 1;
if (frame.length != expectedLength) {
throw new IllegalArgumentException("报文长度不匹配,声明长度=" + dataUnitLength + ",实际长度=" + frame.length + ",期望总长度=" + expectedLength);
}
// 8. 提取数据单元
byte[] dataUnit = Arrays.copyOfRange(frame, headerLength, headerLength + dataUnitLength
);
// 9. BCC 校验
// BCC范围从命令标识开始
byte receivedBcc = frame[frame.length - 1];
byte calculatedBcc = calculateBCC(frame, 2, headerLength + dataUnitLength
);
if (receivedBcc != calculatedBcc) {
throw new IllegalStateException(
String.format("BCC校验失败收到=0x%02X 计算=0x%02X",
receivedBcc, calculatedBcc)
);
}
// 10. 加密处理
if (encryptType == 0x01) {
// 不加密
return bytesToHex(dataUnit);
}
if (encryptType != 0x03) {
throw new UnsupportedOperationException(
"不支持的加密方式: " + encryptType);
}
// AES 必须 16倍数
if (dataUnitLength % 16 != 0) {
throw new IllegalStateException(
"AES密文长度非法不是16倍数length=" + dataUnitLength);
}
// 11. AES解密
byte[] aesKey = generateKeyFromSn(deviceSn);
byte[] decrypted = decryptData(dataUnit, aesKey);
return bytesToHex(decrypted);
}
*/
private static void parseDecryptedDataUnit(byte[] decryptedData) {
log.info("\n=== 数据单元解析 ===");
if (decryptedData == null || decryptedData.length == 0) {
log.info("解密数据为空");
return;
}
log.info("数据单元总长度: " + decryptedData.length + " 字节");
log.info("数据单元内容: " + bytesToHex(decryptedData));
// 尝试解析采集时间(前6字节BCD)
if (decryptedData.length >= 6) {
String ts = parseTimestampFromBCD(Arrays.copyOfRange(decryptedData, 0, 6));
log.info("采集时间: " + ts + " (BCD)");
}
// 扫描数据块: [类型ID(1)] [长度(1)]
int offset = 6; // 时间之后
int itemIndex = 0;
while (offset + 2 <= decryptedData.length) {
int typeId = decryptedData[offset] & 0xFF;
int length = decryptedData[offset + 1] & 0xFF;
int payloadStart = offset + 2;
int payloadEnd = payloadStart + length;
// 长度异常时尝试步进1字节继续
if (payloadEnd > decryptedData.length) {
// 可能当前字节并非数据块起点(例如信息标志位),小步扫描跳过
offset += 1;
continue;
}
byte[] payload = Arrays.copyOfRange(decryptedData, payloadStart, payloadEnd);
log.info(String.format("数据块[%d] 类型: %s, 长度: %d, 起止: %d-%d", itemIndex++, typeName(typeId), length, payloadStart, payloadEnd - 1));
log.info("数据块HEX: " + bytesToHex(Arrays.copyOf(payload, Math.min(64, payload.length))) + (payload.length > 64 ? "..." : ""));
// 极值数据尝试详细解析
if (typeId == 0x06) {
try {
GBT32960ExtremeDataParser.ExtremeData extreme = GBT32960ExtremeDataParser.parseExtremeData(Arrays.copyOfRange(decryptedData, offset, payloadEnd), 0);
if (extreme != null) {
log.info("极值数据解析结果: " + extreme.toString());
}
} catch (Exception ex) {
log.warn("极值数据解析异常: " + ex.getMessage());
}
}
offset = payloadEnd;
}
// 兼容旧的极值扫描逻辑
GBT32960ExtremeDataExtractor.ExtremeData andParseExtremeData = GBT32960ExtremeDataExtractor.findAndParseExtremeData(decryptedData);
if (andParseExtremeData != null) {
log.info(andParseExtremeData.toString());
} else {
log.info("未找到极值数据或解析失败");
}
}
/**
* 跳过数据项
*/
private static int skipDataItem(byte[] data, int offset) {
if (offset >= data.length) return offset;
offset++; // 跳过数据类型
if (offset < data.length) {
byte length = data[offset];
offset += 1 + (length & 0xFF); // 跳过长度字节和数据内容
}
return offset;
}
private static String typeName(int typeId) {
switch (typeId & 0xFF) {
case 0x01:
return "整车数据";
case 0x02:
return "驱动电机数据";
case 0x03:
return "燃料电池数据";
case 0x04:
return "发动机数据";
case 0x05:
return "车辆位置数据";
case 0x06:
return "极值数据";
case 0x07:
return "报警数据";
case 0x08:
return "储能装置电压数据";
case 0x09:
return "储能装置温度数据";
default:
return String.format("未知类型(0x%02X)", typeId);
}
}
// BCD字节转十进制整数
private static int bcdToInt(byte b) {
return ((b >> 4) & 0x0F) * 10 + (b & 0x0F);
}
// 解析采集时间(BCD YY MM DD hh mm ss)
private static String parseTimestampFromBCD(byte[] data) {
if (data == null || data.length < 6) return "未知";
int year = 2000 + bcdToInt(data[0]);
int month = bcdToInt(data[1]);
int day = bcdToInt(data[2]);
int hour = bcdToInt(data[3]);
int minute = bcdToInt(data[4]);
int second = bcdToInt(data[5]);
return String.format("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second);
}
}