evo-BMS/src/main/java/com/evobms/project/bms/service/MqttService.java

969 lines
39 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.service;
import com.alibaba.fastjson2.JSON;
import com.evobms.project.Battery.domain.ExtremeValues;
import com.evobms.project.Battery.domain.SubsystemVoltage;
import com.evobms.project.Battery.domain.SubsystemTemperature;
import com.evobms.project.Battery.service.IExtremeValuesService;
import com.evobms.project.Battery.service.ISubsystemVoltageService;
import com.evobms.project.Battery.service.ISubsystemTemperatureService;
import com.evobms.project.Battery.domain.ChargeDischargeSummary;
import com.evobms.project.Battery.service.IChargeDischargeSummaryService;
import com.evobms.project.VehicleData.domain.VehicleData;
import com.evobms.project.VehicleData.domain.VehicleLocation;
import com.evobms.project.VehicleData.service.IVehicleDataService;
import com.evobms.project.VehicleData.service.IVehicleLocationService;
import com.evobms.project.bms.domain.BmsDevice;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.MessageChannel;
import org.springframework.stereotype.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.util.*;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.stream.Collectors;
import com.evobms.project.system.service.IBmsDevicesService;
import com.evobms.project.system.domain.BmsDevices;
import static com.evobms.project.bms.controller.GBT32960FullDecoder.parseGBT32960Data;
import static oshi.util.ParseUtil.hexStringToByteArray;
/**
* MQTT服务类
* 只有在mqtt.enabled=true时才会加载此服务
*
* @author evobms
*/
@Service
@ConditionalOnProperty(name = "mqtt.enabled", havingValue = "true")
public class MqttService {
private static final Logger log = LoggerFactory.getLogger(MqttService.class);
@Autowired
private MessageChannel mqttOutboundChannel;
@Autowired
private IBmsDevicesService bmsDevicesService;
@Autowired
private IVehicleDataService vehicleDataService;
@Autowired
private IVehicleLocationService vehicleLocationService;
@Autowired
private IExtremeValuesService extremeValuesService;
@Autowired
private ISubsystemVoltageService subsystemVoltageService;
@Autowired
private ISubsystemTemperatureService subsystemTemperatureService;
@Autowired
private IChargeDischargeSummaryService chargeDischargeSummaryService;
@Value("${mqtt.publishTopic}")
private String publishTopic;
@Value("${mqtt.defaultQos}")
private int defaultQos;
@Value("${mqtt.dataHeaderLength:12}")
private int dataHeaderLength;
/**
* 发送MQTT消息
*
* @param topic 主题
* @param payload 消息内容
*/
public void sendMessage(String topic, String payload) {
log.info("发送MQTT消息 -> 主题: {}, QOS: {}, 载荷: {}", topic, defaultQos, payload);
mqttOutboundChannel.send(MessageBuilder.withPayload(payload)
.setHeader("mqtt_topic", topic)
.setHeader("mqtt_qos", defaultQos)
.build());
}
/**
* 发送MQTT字节消息
*
* @param topic 主题
* @param payload 原始字节
*/
public void sendMessageBytes(String topic, byte[] payload) {
log.info("发送MQTT字节消息 -> 主题: {}, QOS: {}, 长度: {}, HEX: {}", topic, defaultQos, payload.length, toHex(payload));
mqttOutboundChannel.send(MessageBuilder.withPayload(payload)
.setHeader("mqtt_topic", topic)
.setHeader("mqtt_qos", defaultQos)
.build());
}
private static String toHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X ", b));
}
return sb.toString().trim();
}
private static byte[] parseHex(String hex) {
String cleaned = hex.replaceAll("[^0-9A-Fa-f]", "");
if ((cleaned.length() % 2) != 0) {
throw new IllegalArgumentException("HEX长度不是偶数");
}
int len = cleaned.length() / 2;
byte[] out = new byte[len];
for (int i = 0; i < len; i++) {
int hi = Character.digit(cleaned.charAt(2 * i), 16);
int lo = Character.digit(cleaned.charAt(2 * i + 1), 16);
out[i] = (byte) ((hi << 4) | lo);
}
return out;
}
private static byte computeBcc(byte[] buf, int startInclusive, int endExclusive) {
byte b = 0;
for (int i = startInclusive; i < endExclusive; i++) {
b ^= buf[i];
}
return b;
}
private static byte[] deriveKeyFromSn(String sn) {
byte[] key = new byte[16];
byte[] snBytes = sn == null ? new byte[0] : sn.getBytes(StandardCharsets.UTF_8);
int copy = Math.min(snBytes.length, 16);
System.arraycopy(snBytes, 0, key, 0, copy);
return key;
}
private static byte[] aesEcbPkcs5Decrypt(byte[] ciphertext, byte[] key) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec spec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, spec);
return cipher.doFinal(ciphertext);
}
private static byte[] aesEcbPkcs5Encrypt(byte[] plaintext, byte[] key) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec spec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.ENCRYPT_MODE, spec);
return cipher.doFinal(plaintext);
}
/**
* 发送设备控制命令
*
* @param deviceCode 设备编号
* @param command 控制命令
*/
public void sendDeviceCommand(String deviceCode, String command) {
Map<String, Object> commandData = new HashMap<>();
commandData.put("deviceCode", deviceCode);
commandData.put("command", command);
commandData.put("timestamp", new Date());
String topic = "evobms/" + deviceCode + "/command";
String payload = JSON.toJSONString(commandData);
sendMessage(topic, payload);
}
/**
* 处理接收到的设备数据
*
* @param topic 主题
* @param message 消息内容
*/
public void handleDeviceData(String topic, byte[] message) {
try {
// 从主题提取设备唯一编号(紧跟在"bbox"后面的段)
String[] topicParts = topic.split("/");
String deviceCode = null;
for (int i = 0; i < topicParts.length - 1; i++) {
if ("bbox".equalsIgnoreCase(topicParts[i])) {
deviceCode = topicParts[i + 1];
break;
}
}
if (deviceCode == null) {
// 兜底:考虑有无前导斜杠
deviceCode = topicParts[topic.startsWith("/") ? 2 : 1];
}
log.info("处理设备数据 -> 设备: {}, 主题: {}, 长度: {}", deviceCode, topic, message == null ? 0 : message.length);
// 根据设备唯一编号查询系统表的SN码查询不到则回退使用deviceCode
String deviceSn = lookupDeviceSnByCode(deviceCode);
if (deviceSn == null || deviceSn.isEmpty()) {
deviceSn = deviceCode;
}
// 直接解析GB/T 32960二进制帧
String data = parseGBT32960Data(message, deviceSn);
// 逐字段解析返回的data字符串
parseDataFields(data, deviceSn);
} catch (Exception e) {
log.error("处理设备数据时发生错误: {}", e.getMessage(), e);
}
}
/**
* 逐字段解析数据字符串 - 直接解析十六进制数据
*
* @param data 十六进制数据字符串
* @param deviceSn 设备SN码
*/
private void parseDataFields(String data, String deviceSn) {
if (data == null || data.isEmpty()) {
log.warn("设备 {}: 无有效数据可解析", deviceSn);
return;
}
try {
log.info("设备 {} 原始数据: {}", deviceSn, data);
// 清理数据,移除非十六进制字符
String cleanData = data.replaceAll("[^0-9A-Fa-f]", "");
if (cleanData.length() < 4) {
log.warn("设备 {}: 数据长度不足", deviceSn);
return;
}
// 将十六进制字符串转换为字节数组
byte[] message = hexStringToByteArray(cleanData);
// 按照协议索引逐段解析
parseByProtocolIndex(message, deviceSn);
} catch (Exception e) {
log.error("设备 {} 解析数据字段时发生错误: {}", deviceSn, e.getMessage(), e);
}
}
/**
* 按照协议定义的索引位置解析各个数据段
*/
private void parseByProtocolIndex(byte[] message, String deviceSn) {
try {
// 1. 时间信息 - 索引24-29 (6字节)
Date frameTime = parseTimeAtIndex(message, 0, deviceSn);
if (frameTime == null) {
frameTime = new Date();
}
// 2. 整车数据 - 索引30-49 (20字节)
parseVehicleDataAtIndex(message, 6, deviceSn, frameTime);
// 3. 位置信息 - 索引51-60 (10字节)
parseLocationDataAtIndex(message, 27, deviceSn, frameTime);
// 4. 极值数据 - 索引61-75 (14字节)
parseExtremeDataAtIndex(message, 37, deviceSn, frameTime);
// 5. 单体电压数据 - 索引76-447 (372字节)
parseCellVoltageAtIndex(message, 52, deviceSn, frameTime);
// 6. 单体温度数据 - 索引448-542 (95字节)
parseCellTemperatureAtIndex(message, 424, deviceSn, frameTime);
// 7. 累计充放电量 - 索引543-553 (11字节)
parseChargeDischargeAtIndex(message, 519, deviceSn, frameTime);
log.info("设备 {} 所有数据段解析完成", deviceSn);
} catch (Exception e) {
log.error("设备 {} 按索引解析时发生错误: {}", deviceSn, e.getMessage(), e);
}
}
/**
* 解析时间信息 - 索引24-29 (6字节)
* 格式: 年(偏移2000) + 月 + 日 + 时 + 分 + 秒
*/
private Date parseTimeAtIndex(byte[] message, int startIndex, String deviceSn) {
try {
if (message.length < startIndex + 6) {
log.warn("设备 {}: 时间数据索引越界", deviceSn);
return null;
}
int year = (message[startIndex] & 0xFF) + 2000; // 索引24: 年
int month = message[startIndex + 1] & 0xFF; // 索引25: 月
int day = message[startIndex + 2] & 0xFF; // 索引26: 日
int hour = message[startIndex + 3] & 0xFF; // 索引27: 时
int minute = message[startIndex + 4] & 0xFF; // 索引28: 分
int second = message[startIndex + 5] & 0xFF; // 索引29: 秒
if (isValidTime(year, month, day, hour, minute, second)) {
String time = String.format("%04d-%02d-%02d %02d:%02d:%02d",
year, month, day, hour, minute, second);
log.info("设备 {} - 时间: {}", deviceSn, time);
saveTimeData(deviceSn, time);
Calendar cal = Calendar.getInstance(TimeZone.getDefault());
cal.set(year, month - 1, day, hour, minute, second);
cal.set(Calendar.MILLISECOND, 0);
return cal.getTime();
} else {
log.info("设备 {} - 时间: 无效数据({}-{}-{} {}:{}:{})",
deviceSn, year, month, day, hour, minute, second);
return null;
}
} catch (Exception e) {
log.error("设备 {} 解析时间数据时发生错误: {}", deviceSn, e.getMessage());
return null;
}
}
/**
* 解析整车数据 - 索引30-49 (20字节)
*/
private void parseVehicleDataAtIndex(byte[] message, int startIndex, String deviceSn, Date frameTime) {
try {
if (message.length < startIndex + 20) {
log.warn("设备 {}: 整车数据索引越界", deviceSn);
return;
}
// 索引30: 数据标识 (应该是0x01)
byte dataFlag = message[startIndex];
if (dataFlag != 0x01) {
log.warn("设备 {}: 整车数据标识错误: 0x{}", deviceSn, String.format("%02X", dataFlag));
return;
}
log.info("设备 {} - 发现整车数据", deviceSn);
// 索引32: 充电状态
byte chargeStatus = message[startIndex + 2];
log.info("设备 {} - 充电状态: 0x{} ({})", deviceSn,
String.format("%02X", chargeStatus), getChargeStatusDescription(chargeStatus));
// 索引40-41: 总电压 (0.1V)
int totalVoltage = ((message[startIndex + 10] & 0xFF) << 8) | (message[startIndex + 11] & 0xFF);
double voltageValue = totalVoltage * 0.1;
log.info("设备 {} - 总电压: {} V", deviceSn, voltageValue);
// 索引42-43: 总电流 (0.1A, 偏移10000)
int totalCurrent = ((message[startIndex + 12] & 0xFF) << 8) | (message[startIndex + 13] & 0xFF);
double currentValue = (totalCurrent - 10000) * 0.1;
log.info("设备 {} - 总电流: {} A", deviceSn, currentValue);
// 索引44: SOC (%)
int soc = message[startIndex + 14] & 0xFF;
log.info("设备 {} - SOC: {} %", deviceSn, soc);
// 索引47-48: 正极绝缘电阻 (Ω)
int insulationResistance = ((message[startIndex + 17] & 0xFF) << 8) | (message[startIndex + 18] & 0xFF);
log.info("设备 {} - 正极绝缘电阻: {} Ω", deviceSn, insulationResistance);
// 保存整车数据
saveVehicleData(deviceSn, voltageValue, currentValue, soc, chargeStatus, insulationResistance, frameTime);
} catch (Exception e) {
log.error("设备 {} 解析整车数据时发生错误: {}", deviceSn, e.getMessage());
}
}
/**
* 解析位置信息 - 索引51-60 (10字节)
*/
private void parseLocationDataAtIndex(byte[] message, int startIndex, String deviceSn, Date frameTime) {
try {
if (message.length < startIndex + 10) {
log.warn("设备 {}: 位置数据索引越界", deviceSn);
return;
}
// 索引51: 数据标识 (应该是0x05)
byte dataFlag = message[startIndex];
if (dataFlag != 0x05) {
log.warn("设备 {}: 位置数据标识错误: 0x{}", deviceSn, String.format("%02X", dataFlag));
return;
}
log.info("设备 {} - 发现位置信息", deviceSn);
// 索引52: 位置状态
byte locationStatus = message[startIndex + 1];
// 转成十六进制字符串,例如 "00", "01"
String locationStatusHex = String.format("%02X", locationStatus);
// 索引53-56: 经度 (0.000001°)
long longitude = ((message[startIndex + 2] & 0xFFL) << 24) |
((message[startIndex + 3] & 0xFFL) << 16) |
((message[startIndex + 4] & 0xFFL) << 8) |
(message[startIndex + 5] & 0xFFL);
// 索引57-60: 纬度 (0.000001°)
long latitude = ((message[startIndex + 6] & 0xFFL) << 24) |
((message[startIndex + 7] & 0xFFL) << 16) |
((message[startIndex + 8] & 0xFFL) << 8) |
(message[startIndex + 9] & 0xFFL);
if (longitude != 0 || latitude != 0) {
double lon = longitude / 1000000.0;
double lat = latitude / 1000000.0;
log.info("设备 {} - 位置: 经度={}°, 纬度={}° (状态:0x{})", deviceSn, lon, lat, locationStatusHex);
processLocationData(deviceSn, lon, lat, locationStatusHex, frameTime);
} else {
log.info("设备 {} - 位置: 无效数据", deviceSn);
}
} catch (Exception e) {
log.error("设备 {} 解析位置数据时发生错误: {}", deviceSn, e.getMessage());
}
}
/**
* 解析极值数据 - 索引61-75 (14字节)
*/
private void parseExtremeDataAtIndex(byte[] message, int startIndex, String deviceSn, Date frameTime) {
try {
if (message.length < startIndex + 14) {
log.warn("设备 {}: 极值数据索引越界", deviceSn);
return;
}
// 索引61: 数据标识 (应该是0x06)
byte dataFlag = message[startIndex];
if (dataFlag != 0x06) {
log.warn("设备 {}: 极值数据标识错误: 0x{}", deviceSn, String.format("%02X", dataFlag));
return;
}
log.info("设备 {} - 发现极值数据", deviceSn);
// 索引62: 最高电压子系统号
byte maxVoltageSystem = message[startIndex + 1];
// 索引63: 最高电压探针号
byte maxVoltageProbe = message[startIndex + 2];
// 索引64-65: 最高单体电压 (mV)
double maxVoltage = ((message[startIndex + 3] & 0xFF) << 8) | (message[startIndex + 4] & 0xFF);
double maxVoltageValue = maxVoltage * 0.001; // 转换为V
log.info("设备 {} - 最高电压: 子系统{}-探针{}, {}V", deviceSn, maxVoltageSystem, maxVoltageProbe, maxVoltageValue);
// 索引66: 最低电压子系统号
byte minVoltageSystem = message[startIndex + 5];
// 索引67: 最低电压探针号
byte minVoltageProbe = message[startIndex + 6];
// 索引68-69: 最低单体电压 (mV)
double minVoltage = ((message[startIndex + 7] & 0xFF) << 8) | (message[startIndex + 8] & 0xFF);
double minVoltageValue = minVoltage * 0.001; // 转换为V
log.info("设备 {} - 最低电压: 子系统{}-探针{}, {}V", deviceSn, minVoltageSystem, minVoltageProbe, minVoltageValue);
// 索引70: 最高温度子系统号
byte maxTempSystem = message[startIndex + 9];
// 索引71: 最高温度探针号
byte maxTempProbe = message[startIndex + 10];
// 索引72: 最高单体温度 (偏移40°C)
int maxTemperature = message[startIndex + 11] & 0xFF;
int maxTempValue = maxTemperature - 40;
log.info("设备 {} - 最高温度: 子系统{}-探针{}, {}°C", deviceSn, maxTempSystem, maxTempProbe, maxTempValue);
// 索引73: 最低温度子系统号
byte minTempSystem = message[startIndex + 12];
// 索引74: 最低温度探针号
byte minTempProbe = message[startIndex + 13];
// 索引75: 最低单体温度 (偏移40°C)
int minTemperature = message[startIndex + 14] & 0xFF;
int minTempValue = minTemperature - 40;
log.info("设备 {} - 最低温度: 子系统{}-探针{}, {}°C", deviceSn, minTempSystem, minTempProbe, minTempValue);
// 保存极值数据
saveExtremeData(deviceSn, (maxVoltageSystem & 0xFF), (maxVoltageProbe & 0xFF), maxVoltageValue, (minVoltageSystem & 0xFF), (minVoltageProbe & 0xFF), minVoltageValue, (maxTempSystem & 0xFF), (maxTempProbe & 0xFF), maxTempValue, (minTempSystem & 0xFF), (minTempProbe & 0xFF), minTempValue, frameTime);
} catch (Exception e) {
log.error("设备 {} 解析极值数据时发生错误: {}", deviceSn, e.getMessage());
}
}
/**
* 解析单体电压数据 - 索引76-447 (372字节)
*/
private void parseCellVoltageAtIndex(byte[] message, int startIndex, String deviceSn, Date frameTime) {
try {
if (message.length < startIndex + 372) {
log.warn("设备 {}: 单体电压数据索引越界", deviceSn);
return;
}
// 索引76: 数据标识 (应该是0x08)
byte dataFlag = message[startIndex];
if (dataFlag != 0x08) {
log.warn("设备 {}: 单体电压数据标识错误: 0x{}", deviceSn, String.format("%02X", dataFlag));
return;
}
log.info("设备 {} - 发现单体电压数据", deviceSn);
// 索引77: 可充电储能子系统个数
byte subsystemCount = message[startIndex + 1];
// 索引78: 可充电储能子系统号
byte subsystemNum = message[startIndex + 2];
// 索引79-80: 可充电储能装置总压 (0.1V)
int storageVoltage = ((message[startIndex + 3] & 0xFF) << 8) | (message[startIndex + 4] & 0xFF);
double storageVoltageValue = storageVoltage * 0.1;
// 索引81-82: 可充电储能装置总流 (0.1A, 偏移10000)
int storageCurrent = ((message[startIndex + 5] & 0xFF) << 8) | (message[startIndex + 6] & 0xFF);
double storageCurrentValue = (storageCurrent - 10000) * 0.1;
log.info("设备 {} - 储能装置: 子系统{}/{}, 电压{}V, 电流{}A", deviceSn,
subsystemNum, subsystemCount, storageVoltageValue, storageCurrentValue);
// 索引83-84: 单体电压总数
int totalCellCount = ((message[startIndex + 7] & 0xFF) << 8) | (message[startIndex + 8] & 0xFF);
// 索引85-86: 本帧起始电芯序号
int startCellIndex = ((message[startIndex + 9] & 0xFF) << 8) | (message[startIndex + 10] & 0xFF);
// 索引87: 本帧电芯总数
int frameCellCount = message[startIndex + 11] & 0xFF;
log.info("设备 {} - 电芯信息: 总数{}个, 本帧{}-{}", deviceSn,
totalCellCount, startCellIndex, startCellIndex + frameCellCount - 1);
// 索引88开始: 单体电压数据 (每个电芯2字节)
int voltageDataStart = startIndex + 12;
boolean hasValidVoltage = checkCellVoltagesValid(message, voltageDataStart, frameCellCount);
if (hasValidVoltage) {
log.info("设备 {} - 发现有效单体电压数据", deviceSn);
List<Double> voltages = new ArrayList<>();
for (int i = 0; i < frameCellCount; i++) {
int v = ((message[voltageDataStart + i * 2] & 0xFF) << 8) | (message[voltageDataStart + i * 2 + 1] & 0xFF);
double vv = v * 0.001;
voltages.add(vv);
}
SubsystemVoltage sv = new SubsystemVoltage();
sv.setDeviceId(deviceSn);
sv.setTimestamp(frameTime);
sv.setSubsystemCount((int) subsystemCount);
sv.setSubsystemNo((int) subsystemNum);
sv.setSubsystemVoltage(BigDecimal.valueOf(storageVoltageValue));
sv.setSubsystemCurrent(BigDecimal.valueOf(storageCurrentValue));
sv.setTotalBatteryCount(totalCellCount);
sv.setFrameStartBatteryNo(startCellIndex);
sv.setFrameBatteryCount(frameCellCount);
sv.setCreateTime(new Date());
sv.setCreateBy(deviceSn);
String voltagesStr = voltages.stream().map(Object::toString).collect(java.util.stream.Collectors.joining(","));
sv.setBatteryVoltages(JSON.toJSONString(voltagesStr));
subsystemVoltageService.insertSubsystemVoltage(sv);
parseCellVoltagesDetail(message, voltageDataStart, deviceSn, startCellIndex, frameCellCount);
} else {
log.info("设备 {} - 单体电压数据: 全部为0V", deviceSn);
}
} catch (Exception e) {
log.error("设备 {} 解析单体电压数据时发生错误: {}", deviceSn, e.getMessage());
}
}
/**
* 解析单体温度数据 - 索引448-542 (95字节)
*/
private void parseCellTemperatureAtIndex(byte[] message, int startIndex, String deviceSn, Date frameTime) {
try {
if (message.length < startIndex + 89) {
log.warn("设备 {}: 单体温度数据索引越界", deviceSn);
return;
}
// 索引448: 数据标识 (应该是0x09)
byte dataFlag = message[startIndex];
if (dataFlag != 0x09) {
log.warn("设备 {}: 单体温度数据标识错误: 0x{}", deviceSn, String.format("%02X", dataFlag));
return;
}
log.info("设备 {} - 发现单体温度数据", deviceSn);
// 索引449: 可充电储能子系统个数
byte subsystemCount = message[startIndex + 1];
// 索引450: 可充电储能子系统号
byte subsystemNum = message[startIndex + 2];
// 索引451-452: 单体温度总数
int totalTempCount = ((message[startIndex + 3] & 0xFF) << 8) | (message[startIndex + 4] & 0xFF);
log.info("设备 {} - 温度探头: 子系统{}/{}, 总数{}个", deviceSn,
subsystemNum, subsystemCount, totalTempCount);
// 索引453开始: 单体温度数据 (每个探头1字节)
int tempDataStart = startIndex + 5;
boolean hasValidTemperature = checkCellTemperaturesValid(message, tempDataStart, totalTempCount);
if (hasValidTemperature) {
log.info("设备 {} - 发现有效单体温度数据", deviceSn);
List<Integer> temperatures = new ArrayList<>();
for (int i = 0; i < totalTempCount; i++) {
int t = message[tempDataStart + i] & 0xFF;
int tv = t - 40;
temperatures.add(tv);
}
SubsystemTemperature st = new SubsystemTemperature();
st.setDeviceId(deviceSn);
st.setTimestamp(frameTime);
st.setSubsystemCount((int) subsystemCount);
st.setSubsystemNo((int) subsystemNum);
st.setTempProbeCount(totalTempCount);
st.setCreateTime(new Date());
st.setCreateBy(deviceSn);
String tempsStr = temperatures.stream().map(Object::toString).collect(Collectors.joining(","));
st.setTemperatureValues(JSON.toJSONString(tempsStr));
subsystemTemperatureService.insertSubsystemTemperature(st);
parseCellTemperaturesDetail(message, tempDataStart, deviceSn, totalTempCount);
} else {
log.info("设备 {} - 单体温度数据: 全部为无效值", deviceSn);
}
} catch (Exception e) {
log.error("设备 {} 解析单体温度数据时发生错误: {}", deviceSn, e.getMessage());
}
}
/**
* 解析累计充放电量 - 索引543-553 (11字节)
*/
private void parseChargeDischargeAtIndex(byte[] message, int startIndex, String deviceSn, Date frameTime) {
try {
if (message.length < startIndex + 11) {
log.warn("设备 {}: 充放电数据索引越界", deviceSn);
return;
}
// 索引543: 数据标识 (应该是0x80)
byte dataFlag = message[startIndex];
if (dataFlag != (byte) 0x80) {
log.warn("设备 {}: 充放电数据标识错误: 0x{}", deviceSn, String.format("%02X", dataFlag));
return;
}
log.info("设备 {} - 发现累计充放电数据", deviceSn);
// 跳过索引544-545的保留字节
// 索引546-549: 累计放电电量 (0.1kWh)
long dischargeEnergy = ((message[startIndex + 3] & 0xFFL) << 24) |
((message[startIndex + 4] & 0xFFL) << 16) |
((message[startIndex + 5] & 0xFFL) << 8) |
(message[startIndex + 6] & 0xFFL);
// 索引550-553: 累计充电电量 (0.1kWh)
long chargeEnergy = ((message[startIndex + 7] & 0xFFL) << 24) |
((message[startIndex + 8] & 0xFFL) << 16) |
((message[startIndex + 9] & 0xFFL) << 8) |
(message[startIndex + 10] & 0xFFL);
double dischargeKwh = dischargeEnergy * 0.1;
double chargeKwh = chargeEnergy * 0.1;
log.info("设备 {} - 累计充放电量: 放电{}kWh, 充电{}kWh", deviceSn, dischargeKwh, chargeKwh);
saveChargeDischargeSummary(deviceSn, dischargeKwh, chargeKwh, frameTime);
} catch (Exception e) {
log.error("设备 {} 解析充放电数据时发生错误: {}", deviceSn, e.getMessage());
}
}
/**
* 解析详细的单体温度数据
*/
private void parseCellTemperaturesDetail(byte[] message, int startIndex, String deviceSn, int tempCount) {
List<Integer> temperatures = new ArrayList<>();
for (int i = 0; i < tempCount; i++) {
int temperature = message[startIndex + i] & 0xFF;
int tempValue = temperature - 40; // 温度偏移量
temperatures.add(tempValue);
// 记录前几个温度用于调试
if (i < 5) {
log.debug("设备 {} - 温度探头{}: {}°C", deviceSn, i + 1, tempValue);
}
}
// 计算统计信息
int minTemp = temperatures.stream().mapToInt(v -> v).min().orElse(0);
int maxTemp = temperatures.stream().mapToInt(v -> v).max().orElse(0);
double avgTemp = temperatures.stream().mapToInt(v -> v).average().orElse(0);
log.info("设备 {} - 温度统计: 最小值{}°C, 最大值{}°C, 平均值{}°C",
deviceSn, minTemp, maxTemp, avgTemp);
}
/**
* 检查单体温度数据有效性
*/
private boolean checkCellTemperaturesValid(byte[] message, int startIndex, int tempCount) {
for (int i = 0; i < tempCount; i++) {
int temperature = message[startIndex + i] & 0xFF;
if (temperature != 0 && temperature != 0xFF) {
return true;
}
}
return false;
}
/**
* 解析详细的单体电压数据
*/
private void parseCellVoltagesDetail(byte[] message, int startIndex, String deviceSn, int startCellIndex, int cellCount) {
List<Double> voltages = new ArrayList<>();
for (int i = 0; i < cellCount; i++) {
int voltage = ((message[startIndex + i * 2] & 0xFF) << 8) |
(message[startIndex + i * 2 + 1] & 0xFF);
double voltageValue = voltage * 0.001; // mV转V
voltages.add(voltageValue);
// 记录前几个电芯电压用于调试
if (i < 5) {
log.debug("设备 {} - 电芯{}电压: {}V", deviceSn, startCellIndex + i, voltageValue);
}
}
// 计算统计信息
double minVoltage = voltages.stream().mapToDouble(v -> v).min().orElse(0);
double maxVoltage = voltages.stream().mapToDouble(v -> v).max().orElse(0);
double avgVoltage = voltages.stream().mapToDouble(v -> v).average().orElse(0);
log.info("设备 {} - 电压统计: 最小值{}V, 最大值{}V, 平均值{}V", deviceSn, minVoltage, maxVoltage, avgVoltage);
}
/**
* 检查单体电压数据有效性
*/
private boolean checkCellVoltagesValid(byte[] message, int startIndex, int cellCount) {
for (int i = 0; i < cellCount * 2; i += 2) {
int voltage = ((message[startIndex + i] & 0xFF) << 8) | (message[startIndex + i + 1] & 0xFF);
if (voltage > 0) {
return true;
}
}
return false;
}
/**
* 充电状态描述
*/
private int getChargeStatusDescription(byte status) {
/** 充电状态 (1:停车充电 2:行驶充电 3:未充电 4:充电完成 254:异常 255:无效) */
switch (status & 0xFF) {
case 0x01:
return 1;
case 0x02:
return 2;
case 0x03:
return 3;
case 0x04:
return 4;
case 0xFE:
return 254;
case 0xFF:
return 255;
}
return 0;
}
/**
* 检查时间有效性
*/
private boolean isValidTime(int year, int month, int day, int hour, int minute, int second) {
return year >= 2000 && year <= 2100 &&
month >= 1 && month <= 12 &&
day >= 1 && day <= 31 &&
hour >= 0 && hour <= 23 &&
minute >= 0 && minute <= 59 &&
second >= 0 && second <= 59;
}
/**
* 十六进制字符串转字节数组
*/
private byte[] hexStringToByteArray(String hex) {
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;
}
/**
* 保存单体电压数据
*/
private void saveCellVoltageData(String deviceSn, byte subsystemNum, int startIndex, int count, List<Double> voltages, double min, double max, double avg) {
// TODO: 实现单体电压数据保存逻辑
log.debug("保存设备 {} 子系统 {} 电压数据: 起始{}, 数量{}, 范围{}-{}V", deviceSn, subsystemNum, startIndex, count, min, max);
}
/**
* 保存单体温度数据
*/
private void saveCellTemperatureData(String deviceSn, byte subsystemNum, int count, List<Integer> temperatures, int min, int max, double avg) {
// TODO: 实现单体温度数据保存逻辑
log.debug("保存设备 {} 子系统 {} 温度数据: 数量{}, 范围{}-{}°C", deviceSn, subsystemNum, count, min, max);
}
/**
* 保存时间数据
*/
private void saveTimeData(String deviceSn, String time) {
// TODO: 实现时间数据保存逻辑
log.debug("保存设备 {} 时间数据: {}", deviceSn, time);
}
/**
* 保存整车数据
*/
private void saveVehicleData(String deviceSn, double voltage, double current, int soc, byte chargeStatus,int insulationResistance, Date frameTime) {
// 保存整车数据到数据库
VehicleData ve = new VehicleData();
ve.setDeviceId(deviceSn);
ve.setTimestamp(frameTime);
ve.setTotalVoltage(BigDecimal.valueOf(voltage));
ve.setTotalCurrent(BigDecimal.valueOf(current));
ve.setSoc(soc);
ve.setChargingStatus(getChargeStatusDescription(chargeStatus));
ve.setInsulationResistance(insulationResistance);
ve.setCreateTime(new Date());
ve.setCreateBy(deviceSn);
vehicleDataService.insertVehicleData(ve);
}
private void processLocationData(String deviceSn, double longitude, double latitude,String locationStatus, Date frameTime) {
VehicleLocation ve = new VehicleLocation();
ve.setDeviceId(deviceSn);
ve.setTimestamp(frameTime);
// 十六进制字符串(如 "00")转成整数(如 0
int status = Integer.parseInt(locationStatus, 16);
ve.setPositioningStatus(status);
ve.setLongitude(BigDecimal.valueOf(longitude));
ve.setLatitude(BigDecimal.valueOf(latitude));
ve.setCreateTime(new Date());
ve.setCreateBy(deviceSn);
vehicleLocationService.insertVehicleLocation(ve);
}
private void saveExtremeData(String deviceSn, int maxVoltageSubsystemNo, int maxVoltageBatteryNo, double maxVoltageValue, int minVoltageSubsystemNo, int minVoltageBatteryNo, double minVoltageValue, int maxTempSubsystemNo,
int maxTempProbeNo, int maxTempValue, int minTempSubsystemNo, int minTempProbeNo, int minTempValue, Date frameTime) {
ExtremeValues ex = new ExtremeValues();
ex.setDeviceId(deviceSn);
ex.setTimestamp(frameTime);
ex.setMaxVoltageSubsystemNo(maxVoltageSubsystemNo);
ex.setMaxVoltageBatteryNo(maxVoltageBatteryNo);
ex.setMaxVoltageValue(BigDecimal.valueOf(maxVoltageValue));
ex.setMinVoltageSubsystemNo(minVoltageSubsystemNo);
ex.setMinVoltageBatteryNo(minVoltageBatteryNo);
ex.setMinVoltageValue(BigDecimal.valueOf(minVoltageValue));
ex.setMaxTempSubsystemNo(maxTempSubsystemNo);
ex.setMaxTempProbeNo(maxTempProbeNo);
ex.setMaxTempValue(BigDecimal.valueOf(maxTempValue));
ex.setMinTempSubsystemNo(minTempSubsystemNo);
ex.setMinTempProbeNo(minTempProbeNo);
ex.setMinTempValue(BigDecimal.valueOf(minTempValue));
extremeValuesService.insertExtremeValues(ex);
}
private void saveChargeDischargeSummary(String deviceSn, double dischargeKwh, double chargeKwh, Date frameTime) {
ChargeDischargeSummary s = new ChargeDischargeSummary();
s.setDeviceId(deviceSn);
s.setTimestamp(frameTime);
s.setDischargeKwh(BigDecimal.valueOf(dischargeKwh));
s.setChargeKwh(BigDecimal.valueOf(chargeKwh));
s.setCreateTime(new Date());
s.setCreateBy(deviceSn);
chargeDischargeSummaryService.insertChargeDischargeSummary(s);
}
/**
* 处理单体温度数据
*/
private void processCellTemperatureData(String deviceSn, Map<String, String> fieldMap) {
// TODO: 如果有详细的单体温度数据,在这里处理
}
/**
* 保存充放电数据
*/
private void saveChargeDischargeData(String deviceSn, String dischargeEnergy, String chargeEnergy, String time) {
// TODO: 实现充放电数据保存逻辑
}
/**
* 更新设备数据
*
* @param device 设备对象
* @param data 数据
*/
private void updateDeviceData(BmsDevice device, Map<String, Object> data) {
if (data.containsKey("voltage")) {
device.setCurrentVoltage(Double.valueOf(data.get("voltage").toString()));
}
if (data.containsKey("current")) {
device.setCurrentCurrent(Double.valueOf(data.get("current").toString()));
}
if (data.containsKey("capacity")) {
device.setRemainingCapacity(Double.valueOf(data.get("capacity").toString()));
}
if (data.containsKey("temperature")) {
device.setTemperature(Double.valueOf(data.get("temperature").toString()));
}
device.setLastCommTime(new Date());
}
/**
* 发送设备状态查询命令
*
* @param deviceCode 设备编号
*/
public void queryDeviceStatus(String deviceCode) {
sendDeviceCommand(deviceCode, "STATUS_QUERY");
}
/**
* 根据设备唯一编号(deviceCode)查询系统设备表的SN码
*/
private String lookupDeviceSnByCode(String deviceCode) {
try {
BmsDevices query = new BmsDevices();
query.setDeviceId(deviceCode);
List<BmsDevices> list = bmsDevicesService.selectBmsDevicesList(query);
if (list != null && !list.isEmpty()) {
String sn = list.get(0).getDeviceSn();
if (sn != null && !sn.isEmpty()) {
return sn;
}
}
} catch (Exception e) {
log.warn("查询设备SN失败: {}", e.getMessage());
}
return null;
}
}