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 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 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 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 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 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 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 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 fieldMap) { // TODO: 如果有详细的单体温度数据,在这里处理 } /** * 保存充放电数据 */ private void saveChargeDischargeData(String deviceSn, String dischargeEnergy, String chargeEnergy, String time) { // TODO: 实现充放电数据保存逻辑 } /** * 更新设备数据 * * @param device 设备对象 * @param data 数据 */ private void updateDeviceData(BmsDevice device, Map 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 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; } }