iot对接

This commit is contained in:
jiang 2025-09-14 17:33:22 +08:00
parent 0a55e44330
commit 88c81c9010
2 changed files with 698 additions and 223 deletions

View File

@ -3,8 +3,15 @@ package com.bonus.material.iot.controller;
import com.bonus.common.core.web.domain.AjaxResult;
import com.bonus.material.iot.dto.GpsData;
import com.bonus.material.iot.dto.PhaseData;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@ -15,219 +22,446 @@ import java.util.concurrent.ConcurrentMap;
* @Description: 贵州iot设备GPS解析接口
*/
@RestController
@RequestMapping("/api/mqtt")
public class IotDataController {
private static final Logger logger = LoggerFactory.getLogger(IotDataController.class);
// 常量定义
private static final String GNSS_GNRMC_PREFIX = "$GNRMC:";
private static final String GNSS_GNGGA_PREFIX = "$GNGGA:";
private static final double MIN_VALID_VOLTAGE = 0.0;
private static final double MAX_VALID_VOLTAGE = 300.0;
// 使用线程安全的Map临时存储最新数据
private static final ConcurrentMap<String, GpsData> LATEST_DATA = new ConcurrentHashMap<>();
// 接收MQTT数据的接口
@PostMapping("/api/mqtt/data")
public String processMqttData(@RequestBody String mqttData) {
// 用于JSON解析的ObjectMapper单例模式
private static final ObjectMapper objectMapper = new ObjectMapper();
/**
* 接收MQTT数据的接口
*
* @param mqttData MQTT发送的原始数据
* @return 处理结果
*/
@PostMapping("/data")
public ResponseEntity<String> processMqttData(@RequestBody String mqttData) {
try {
logger.info("接收到MQTT数据: {}", mqttData);
GpsData gpsData = parseMqttData(mqttData);
// 存储最新数据key可以用设备UUID
if (gpsData.getUuid() == null || gpsData.getUuid().isEmpty()) {
String errorMsg = "数据中未包含有效的UUID";
logger.error(errorMsg);
return new ResponseEntity<>(errorMsg, HttpStatus.BAD_REQUEST);
}
// 存储最新数据key为设备UUID
LATEST_DATA.put(gpsData.getUuid(), gpsData);
return "数据接收并存储成功";
logger.info("成功处理设备[{}]的数据", gpsData.getUuid());
return new ResponseEntity<>("数据接收并存储成功", HttpStatus.OK);
} catch (IllegalArgumentException e) {
logger.error("数据验证失败: {}", e.getMessage(), e);
return new ResponseEntity<>("数据验证失败: " + e.getMessage(), HttpStatus.BAD_REQUEST);
} catch (Exception e) {
return "数据解析失败: " + e.getMessage();
logger.error("数据处理异常: {}", e.getMessage(), e);
return new ResponseEntity<>("数据处理失败: " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
// 新增接口供HTML页面获取数据
@GetMapping("/api/mqtt/latest-data")
public GpsData getLatestData(@RequestParam(required = false) String uuid) {
if (uuid == null && !LATEST_DATA.isEmpty()) {
//如果没有指定UUID返回第一个设备的数据
return LATEST_DATA.values().iterator().next();
/**
* 供HTML页面获取最新数据的接口
*
* @param uuid 设备唯一标识可为空
* @return 最新的GPS数据
*/
/**
* 获取设备最新数据
* - uuid返回指定设备数据
* - 不传 uuid返回全部设备数据
*/
@GetMapping("/latest-data")
public ResponseEntity<List<GpsData>> getLatestData(
@RequestParam(required = false) String uuid
) {
try {
// 1. uuid返回全部数据
if (uuid == null || uuid.trim().isEmpty()) {
// 若数据为空返回 204 No Content
if (LATEST_DATA.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
// 转换 Map 的值为 List保持返回格式统一为集合
List<GpsData> allData = new ArrayList<>(LATEST_DATA.values());
return new ResponseEntity<>(allData, HttpStatus.OK);
}
// 2. uuid返回指定设备数据
GpsData singleData = LATEST_DATA.get(uuid);
if (singleData == null) {
// 设备不存在返回 404 Not Found
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
// 包装为 List返回全部的格式统一减少前端适配成本
// 替代 List.of(singleData)
List<GpsData> singleDataList = Collections.singletonList(singleData);
return new ResponseEntity<>(singleDataList, HttpStatus.OK);
} catch (Exception e) {
logger.error("获取最新数据失败: {}", e.getMessage(), e);
// 服务器异常返回 500 Internal Server Error
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
return LATEST_DATA.get(uuid);
}
private GpsData parseMqttData(String rawData) {
/**
* 获取所有设备的UUID列表
*
* @return 设备UUID列表
*/
@GetMapping("/device-uuids")
public ResponseEntity<Map<String, Object>> getDeviceUuids() {
try {
Map<String, Object> response = new HashMap<>();
response.put("count", LATEST_DATA.size());
response.put("uuids", LATEST_DATA.keySet());
return new ResponseEntity<>(response, HttpStatus.OK);
} catch (Exception e) {
logger.error("获取设备UUID列表失败: {}", e.getMessage(), e);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
/**
* 解析MQTT原始数据为GpsData对象
*
* @param rawData 原始MQTT数据
* @return 解析后的GpsData对象
* @throws Exception 解析过程中发生的异常
*/
private GpsData parseMqttData(String rawData) throws Exception {
GpsData data = new GpsData();
try {
// 1. 解析UUID
int uuidStart = rawData.indexOf("uuid:") + 5;
int uuidEnd = rawData.indexOf("/", uuidStart);
data.setUuid(rawData.substring(uuidStart, uuidEnd));
// 解析 JSON 数据
JsonNode rootNode = objectMapper.readTree(rawData);
// 2. 解析$GNRMC部分
int gnrmcStart = rawData.indexOf("$GNRMC:") + 7;
int gnrmcEnd = rawData.indexOf("$GNGGA:", gnrmcStart);
String gnrmc = rawData.substring(gnrmcStart, gnrmcEnd);
// 1. 解析 UUID
parseUuid(rootNode, data);
// 处理校验和
String[] gnrmcMainAndChecksum = gnrmc.split("\\*");
String checksum = gnrmcMainAndChecksum.length > 1 ? gnrmcMainAndChecksum[1] : "";
data.setChecksumRMC(checksum);
// 2. 解析位置信息
parsePositionData(rootNode, data);
// 分割主数据部分
String[] gnrmcParts = gnrmcMainAndChecksum[0].split(":", -1);
// 3. 解析传感器数据
parseSensorData(rootNode, data);
// 安全设置字段
if(gnrmcParts.length > 0) {
data.setUtcTime(gnrmcParts[0]);
}
if(gnrmcParts.length > 1) {
data.setStatus(gnrmcParts[1]);
}
if(gnrmcParts.length > 2) {
data.setLatitude(gnrmcParts[2]);
}
if(gnrmcParts.length > 3) {
data.setLatDirection(gnrmcParts[3]);
}
if(gnrmcParts.length > 4) {
data.setLongitude(gnrmcParts[4]);
}
if(gnrmcParts.length > 5) {
data.setLonDirection(gnrmcParts[5]);
}
if(gnrmcParts.length > 6) {
data.setSpeedOverGround(gnrmcParts[6]);
}
if(gnrmcParts.length > 7) {
data.setCourseOverGround(gnrmcParts[7]);
}
if(gnrmcParts.length > 8) {
data.setDate(gnrmcParts[8]);
}
if(gnrmcParts.length > 11) {
data.setModeIndicator(gnrmcParts[11]);
}
if(gnrmcParts.length > 12) {
data.setNavStatus(gnrmcParts[12]);
}
// 4. 解析电参数数据
parseElectricData(rootNode, data);
// 3. 解析$GNGGA部分
int gnggaStart = gnrmcEnd + 7;
int gnggaEnd = rawData.indexOf("/humi_temp:", gnggaStart);
String gngga = rawData.substring(gnggaStart, gnggaEnd);
String[] gnggaParts = gngga.split(":", -1);
// 5. 解析 BMS 数据
parseBmsData(rootNode, data);
if(gnggaParts.length > 0) {
data.setUtcTime(gnggaParts[0]);
}
if(gnggaParts.length > 1) {
data.setLatitude(gnggaParts[1]);
}
if(gnggaParts.length > 2) {
data.setLatDirection(gnggaParts[2]);
}
if(gnggaParts.length > 3) {
data.setLongitude(gnggaParts[3]);
}
if(gnggaParts.length > 4) {
data.setLonDirection(gnggaParts[4]);
}
if(gnggaParts.length > 5) {
data.setQuality(gnggaParts[5]);
}
if(gnggaParts.length > 6) {
data.setNumSatellitesUsed(gnggaParts[6]);
}
if(gnggaParts.length > 7) {
data.setHdop(gnggaParts[7]);
}
if(gnggaParts.length > 8) {
data.setAltitude(gnggaParts[8]);
}
if(gnggaParts.length > 10) {
data.setGeoidSeparation(gnggaParts[10]);
}
// 6. 解析电池电量
parseSocData(rootNode, data);
// 4. 解析humi_temp
int humiTempStart = gnggaEnd + 11;
int humiTempEnd = rawData.indexOf(":/acc_gry:", humiTempStart);
String humiTemp = rawData.substring(humiTempStart, humiTempEnd);
String[] humiTempParts = humiTemp.split(":");
if(humiTempParts.length > 0) {
data.setHumidity(humiTempParts[0]);
}
if(humiTempParts.length > 1) {
data.setTemperature(humiTempParts[1]);
}
// 5. 解析acc_gry
int accGryStart = humiTempEnd + 10;
int accGryEnd = rawData.length();
// 假设是最后一部分
if(rawData.indexOf(":/", accGryStart) > 0) {
accGryEnd = rawData.indexOf(":/", accGryStart);
}
String accGry = rawData.substring(accGryStart, accGryEnd);
String[] accGryParts = accGry.split(":");
if(accGryParts.length > 0) {
data.setAccX(accGryParts[0]);
}
if(accGryParts.length > 1) {
data.setAccY(accGryParts[1]);
}
if(accGryParts.length > 2) {
data.setAccZ(accGryParts[2]);
}
if(accGryParts.length > 3) {
data.setGyroX(accGryParts[3]);
}
if(accGryParts.length > 4) {
data.setGyroY(accGryParts[4]);
}
if(accGryParts.length > 5) {
data.setGyroZ(accGryParts[5]);
}
// 解析A相电数据
int aPhaseStart = rawData.indexOf("/A_PHEASE:") + 10;
int aPhaseEnd = rawData.indexOf(":/B_PHEASE:", aPhaseStart);
if (aPhaseStart > 0 && aPhaseEnd > aPhaseStart) {
String aPhaseStr = rawData.substring(aPhaseStart, aPhaseEnd);
data.setAPhase(parsePhaseData(aPhaseStr));
}
// 解析B相电数据
int bPhaseStart = rawData.indexOf("/B_PHEASE:") + 10;
int bPhaseEnd = rawData.indexOf(":/C_PHEASE:", bPhaseStart);
if (bPhaseStart > 0 && bPhaseEnd > bPhaseStart) {
String bPhaseStr = rawData.substring(bPhaseStart, bPhaseEnd);
data.setBPhase(parsePhaseData(bPhaseStr));
}
// 解析C相电数据
int cPhaseStart = rawData.indexOf("/C_PHEASE:") + 10;
int cPhaseEnd = rawData.indexOf(":/", cPhaseStart);
if (cPhaseStart > 0 && cPhaseEnd > cPhaseStart) {
String cPhaseStr = rawData.substring(cPhaseStart, cPhaseEnd);
data.setCPhase(parsePhaseData(cPhaseStr));
}
// 验证三相数据
validatePhaseData(data.getAPhase());
validatePhaseData(data.getBPhase());
validatePhaseData(data.getCPhase());
// 验证解析结果
validateParsedData(data);
} catch (Exception e) {
System.err.println("解析过程中发生错误: " + e.getMessage());
throw new RuntimeException("数据解析失败: " + e.getMessage());
logger.error("解析MQTT数据时发生错误: {}", e.getMessage(), e);
throw new Exception("数据解析失败: " + e.getMessage());
}
return data;
}
// 验证三项电数据
/**
* 解析UUID
*/
private void parseUuid(JsonNode rootNode, GpsData data) {
if (rootNode.has("uuid")) {
data.setUuid(rootNode.get("uuid").asText());
}
}
/**
* 解析电池电量数据
*/
private void parseSocData(JsonNode rootNode, GpsData data) {
if (rootNode.has("soc")) {
data.setSoc(rootNode.get("soc").asText());
}
}
/**
* 解析位置信息
*/
private void parsePositionData(JsonNode rootNode, GpsData data) {
// 优先解析 GNSS 数据
if (rootNode.has("position") && rootNode.get("position").has("gnss")) {
String gnssStr = rootNode.get("position").get("gnss").asText();
parseGnssData(gnssStr, data);
}
// 如果没有有效的 GNSS 数据尝试解析 LBS WiFi
if ((data.getLatitude() == null || data.getLatitude().isEmpty()) && rootNode.has("lbs")) {
String lbsStr = rootNode.get("lbs").asText();
parseLbsWifiData(lbsStr, data, "lbs");
}
if ((data.getLatitude() == null || data.getLatitude().isEmpty()) && rootNode.has("wifi")) {
String wifiStr = rootNode.get("wifi").asText();
parseLbsWifiData(wifiStr, data, "wifi");
}
}
/**
* 解析GNSS数据
*/
private void parseGnssData(String gnssStr, GpsData data) {
try {
// 解析 GNRMC 部分
if (gnssStr.contains(GNSS_GNRMC_PREFIX)) {
parseGnrmcData(gnssStr, data);
}
// 解析 GNGGA 部分
if (gnssStr.contains(GNSS_GNGGA_PREFIX)) {
parseGnggaData(gnssStr, data);
}
} catch (Exception e) {
logger.error("解析GNSS数据时出错: {}", e.getMessage(), e);
}
}
/**
* 解析GNRMC格式数据
*/
private void parseGnrmcData(String gnssStr, GpsData data) {
int gnrmcStart = gnssStr.indexOf(GNSS_GNRMC_PREFIX) + GNSS_GNRMC_PREFIX.length();
int gnrmcEnd = gnssStr.indexOf("$", gnrmcStart);
if (gnrmcEnd == -1) {
gnrmcEnd = gnssStr.length();
}
String gnrmc = gnssStr.substring(gnrmcStart, gnrmcEnd);
// 处理校验和
String[] gnrmcMainAndChecksum = gnrmc.split("\\*");
String checksum = gnrmcMainAndChecksum.length > 1 ? gnrmcMainAndChecksum[1] : "";
data.setChecksumRMC(checksum);
// 分割主数据部分
String[] gnrmcParts = gnrmcMainAndChecksum[0].split(":", -1);
// 安全设置字段
if (gnrmcParts.length > 0) data.setUtcTime(gnrmcParts[0]);
if (gnrmcParts.length > 1) data.setStatus(gnrmcParts[1]);
if (gnrmcParts.length > 2) data.setLatitude(gnrmcParts[2]);
if (gnrmcParts.length > 3) data.setLatDirection(gnrmcParts[3]);
if (gnrmcParts.length > 4) data.setLongitude(gnrmcParts[4]);
if (gnrmcParts.length > 5) data.setLonDirection(gnrmcParts[5]);
if (gnrmcParts.length > 6) data.setSpeedOverGround(gnrmcParts[6]);
if (gnrmcParts.length > 7) data.setCourseOverGround(gnrmcParts[7]);
if (gnrmcParts.length > 8) data.setDate(gnrmcParts[8]);
if (gnrmcParts.length > 11) data.setModeIndicator(gnrmcParts[11]);
if (gnrmcParts.length > 12) data.setNavStatus(gnrmcParts[12]);
}
/**
* 解析GNGGA格式数据
*/
private void parseGnggaData(String gnssStr, GpsData data) {
int gnggaStart = gnssStr.indexOf(GNSS_GNGGA_PREFIX) + GNSS_GNGGA_PREFIX.length();
int gnggaEnd = gnssStr.indexOf("$", gnggaStart);
if (gnggaEnd == -1) {
gnggaEnd = gnssStr.length();
}
String gngga = gnssStr.substring(gnggaStart, gnggaEnd);
String[] gnggaParts = gngga.split(":", -1);
if (gnggaParts.length > 0) data.setUtcTime(gnggaParts[0]);
if (gnggaParts.length > 1) data.setLatitude(gnggaParts[1]);
if (gnggaParts.length > 2) data.setLatDirection(gnggaParts[2]);
if (gnggaParts.length > 3) data.setLongitude(gnggaParts[3]);
if (gnggaParts.length > 4) data.setLonDirection(gnggaParts[4]);
if (gnggaParts.length > 5) data.setQuality(gnggaParts[5]);
if (gnggaParts.length > 6) data.setNumSatellitesUsed(gnggaParts[6]);
if (gnggaParts.length > 7) data.setHdop(gnggaParts[7]);
if (gnggaParts.length > 8) data.setAltitude(gnggaParts[8]);
if (gnggaParts.length > 10) data.setGeoidSeparation(gnggaParts[10]);
}
/**
* 解析LBS或WiFi位置数据
*/
private void parseLbsWifiData(String dataStr, GpsData data, String source) {
try {
String[] parts = dataStr.split(",");
if (parts.length >= 3) {
data.setLatitude(parts[0]);
data.setLongitude(parts[1]);
data.setAccuracy(parts[2]);
data.setPositionSource(source);
} else {
logger.warn("{}数据格式不正确,部分不足: {}", source, dataStr);
}
} catch (Exception e) {
logger.error("解析{}数据时出错: {}", source, e.getMessage(), e);
}
}
/**
* 解析传感器数据
*/
private void parseSensorData(JsonNode rootNode, GpsData data) {
if (rootNode.has("sensor")) {
JsonNode sensorNode = rootNode.get("sensor");
// 解析 AHT20 温湿度传感器数据
parseAht20Data(sensorNode, data);
// 解析 IMU 传感器数据
parseImuData(sensorNode, data);
}
}
/**
* 解析AHT20温湿度传感器数据
*/
private void parseAht20Data(JsonNode sensorNode, GpsData data) {
if (sensorNode.has("aht20")) {
JsonNode aht20Node = sensorNode.get("aht20");
if (aht20Node.has("temp")) {
data.setTemperature(aht20Node.get("temp").asText());
}
if (aht20Node.has("humi")) {
data.setHumidity(aht20Node.get("humi").asText());
}
}
}
/**
* 解析IMU传感器数据
*/
private void parseImuData(JsonNode sensorNode, GpsData data) {
if (sensorNode.has("imu")) {
JsonNode imuNode = sensorNode.get("imu");
if (imuNode.has("a_x")) data.setAccX(imuNode.get("a_x").asText());
if (imuNode.has("a_y")) data.setAccY(imuNode.get("a_y").asText());
if (imuNode.has("a_z")) data.setAccZ(imuNode.get("a_z").asText());
if (imuNode.has("g_x")) data.setGyroX(imuNode.get("g_x").asText());
if (imuNode.has("g_y")) data.setGyroY(imuNode.get("g_y").asText());
if (imuNode.has("g_z")) data.setGyroZ(imuNode.get("g_z").asText());
if (imuNode.has("roll")) data.setRoll(imuNode.get("roll").asText());
if (imuNode.has("pitch")) data.setPitch(imuNode.get("pitch").asText());
}
}
/**
* 解析电参数数据
*/
private void parseElectricData(JsonNode rootNode, GpsData data) {
if (rootNode.has("electric")) {
JsonNode electricNode = rootNode.get("electric");
// 获取电参数类型单相或三相
if (electricNode.has("name")) {
String name = electricNode.get("name").asText();
data.setElectricType(name);
}
// 解析各相数据
if (electricNode.has("A")) {
String aPhaseStr = electricNode.get("A").asText();
data.setAPhase(parsePhaseData(aPhaseStr));
}
if (electricNode.has("B")) {
String bPhaseStr = electricNode.get("B").asText();
data.setBPhase(parsePhaseData(bPhaseStr));
}
if (electricNode.has("C")) {
String cPhaseStr = electricNode.get("C").asText();
data.setCPhase(parsePhaseData(cPhaseStr));
}
}
}
/**
* 解析相数据
*/
private PhaseData parsePhaseData(String phaseStr) {
PhaseData phaseData = new PhaseData();
String[] parts = phaseStr.split(":");
if (parts.length >= 14) {
phaseData.setVoltage(parts[0]);
phaseData.setCurrent(parts[1]);
phaseData.setActivePower(parts[2]);
phaseData.setReactivePower(parts[3]);
phaseData.setPowerFactor(parts[4]);
phaseData.setFrequency(parts[5]);
phaseData.setForwardActiveEnergy(parts[6]);
phaseData.setForwardReactiveEnergy(parts[7]);
phaseData.setReverseActiveEnergy(parts[8]);
phaseData.setReverseReactiveEnergy(parts[9]);
phaseData.setApparentPower(parts[10]);
phaseData.setHarmonicActivePower(parts[11]);
phaseData.setFundamentalActivePower(parts[12]);
phaseData.setFundamentalReactivePower(parts[13]);
} else {
logger.warn("相数据格式不正确,部分不足: {}", phaseStr);
}
return phaseData;
}
/**
* 解析BMS数据
*/
private void parseBmsData(JsonNode rootNode, GpsData data) {
if (rootNode.has("bms")) {
String bmsStr = rootNode.get("bms").asText();
String[] parts = bmsStr.split(":");
Map<String, String> bmsData = new HashMap<>();
for (int i = 0; i < parts.length - 1; i += 2) {
bmsData.put(parts[i], parts[i + 1]);
}
data.setBmsData(bmsData);
// 验证三相电数据
validatePhaseData(data.getAPhase());
validatePhaseData(data.getBPhase());
validatePhaseData(data.getCPhase());
}
}
/**
* 验证相数据
*/
private void validatePhaseData(PhaseData phase) {
if (phase == null) {
return;
}
// 验证电压范围
if (phase.getVoltage() != null) {
double voltage = Double.parseDouble(phase.getVoltage());
if (voltage < 0 || voltage > 300) {
throw new IllegalArgumentException("电压值超出合理范围");
if (phase.getVoltage() != null && !phase.getVoltage().isEmpty()) {
try {
double voltage = Double.parseDouble(phase.getVoltage());
if (voltage < MIN_VALID_VOLTAGE || voltage > MAX_VALID_VOLTAGE) {
throw new IllegalArgumentException(
String.format("电压值超出合理范围: %fV (有效值范围: %.1f-%.1fV)",
voltage, MIN_VALID_VOLTAGE, MAX_VALID_VOLTAGE));
}
} catch (NumberFormatException e) {
logger.error("电压值格式不正确: {}", phase.getVoltage());
}
}
@ -235,29 +469,21 @@ public class IotDataController {
// 频率验证功率因数验证等
}
// 解析三项电数据
private PhaseData parsePhaseData(String phaseStr) {
String[] parts = phaseStr.split(":");
if (parts.length < 14) {
return null;
/**
* 验证解析后的数据
*/
private void validateParsedData(GpsData data) {
// 验证UUID是否存在
if (data.getUuid() == null || data.getUuid().isEmpty()) {
throw new IllegalArgumentException("设备UUID不能为空");
}
PhaseData phase = new PhaseData();
phase.setVoltage(parts[0]);
phase.setCurrent(parts[1]);
phase.setActivePower(parts[2]);
phase.setReactivePower(parts[3]);
phase.setPowerFactor(parts[4]);
phase.setFrequency(parts[5]);
phase.setForwardActiveEnergy(parts[6]);
phase.setForwardReactiveEnergy(parts[7]);
phase.setReverseActiveEnergy(parts[8]);
phase.setReverseReactiveEnergy(parts[9]);
phase.setApparentPower(parts[10]);
phase.setHarmonicActivePower(parts[11]);
phase.setFundamentalActivePower(parts[12]);
phase.setFundamentalReactivePower(parts[13]);
return phase;
// 验证位置信息是否存在
if ((data.getLatitude() == null || data.getLatitude().isEmpty()) ||
(data.getLongitude() == null || data.getLongitude().isEmpty())) {
logger.warn("设备[{}]的位置信息不完整", data.getUuid());
// 这里可以选择抛出异常或仅记录警告根据业务需求决定
// throw new IllegalArgumentException("位置信息不完整");
}
}
}
}

View File

@ -1,53 +1,302 @@
package com.bonus.material.iot.dto;
/**
* @author : 阮世耀
* @version : 1.0
* @PackagePath: com.tencent.wxcloudrun.model.dto
* @CreateTime: 2025-04-02 13:50
* @Description: iot定位设备上报信息
*/
import lombok.Data;
import java.util.Map;
/**
* IoT定位设备上报信息数据传输对象
* 用于解析和存储从MQTT设备接收到的定位传感器和电参数数据
*
* @author : 阮世耀
* @version : 1.0
* @CreateTime: 2025-04-02 13:50
*/
@Data
public class GpsData {
// UUID部分
/**
* 设备唯一标识符
* 来源: MQTT消息中的uuid字段
* 示例: "7586b278_5688_4e68_9d05_215d26842c60"
*/
private String uuid;
// $GNRMC部分
/**
* UTC时间(GPS时间)
* 来源: GNSS数据中的GNRMC或GNGGA语句
* 格式: HHMMSS.SSS(时分秒.毫秒)
* 示例: "104205.105"
*/
private String utcTime;
private String status;
private String latitude;
private String latDirection;
private String longitude;
private String lonDirection;
private String speedOverGround;
private String courseOverGround;
private String date;
private String modeIndicator;
private String navStatus;
private String checksumRMC;
// $GNGGA部分
/**
* 定位状态
* 来源: GNSS数据中的GNRMC语句
* 取值: A=有效定位, V=无效定位
* 示例: "A"
*/
private String status;
/**
* 纬度值
* 来源: GNSS数据中的GNRMC或GNGGA语句
* 格式: DDMM.MMMMM(度分.分分分分)
* 示例: "2233.1256"
*/
private String latitude;
/**
* 纬度方向
* 来源: GNSS数据中的GNRMC或GNGGA语句
* 取值: N=北纬, S=南纬
* 示例: "N"
*/
private String latDirection;
/**
* 经度值
* 来源: GNSS数据中的GNRMC或GNGGA语句
* 格式: DDDMM.MMMMM(度分.分分分分)
* 示例: "11405.1234"
*/
private String longitude;
/**
* 经度方向
* 来源: GNSS数据中的GNRMC或GNGGA语句
* 取值: E=东经, W=西经
* 示例: "E"
*/
private String lonDirection;
/**
* 地面速率()
* 来源: GNSS数据中的GNRMC语句
* 单位: (knots)
* 示例: "2.5"
*/
private String speedOverGround;
/**
* 地面航向()
* 来源: GNSS数据中的GNRMC语句
* 单位: (°)
* 示例: "180.5"
*/
private String courseOverGround;
/**
* UTC日期
* 来源: GNSS数据中的GNRMC语句
* 格式: DDMMYY(日月年)
* 示例: "110825" (表示2025年8月11日)
*/
private String date;
/**
* 模式指示器
* 来源: GNSS数据中的GNRMC语句
* 取值: A=自主模式, D=差分模式, E=估算模式, N=数据无效
* 示例: "A"
*/
private String modeIndicator;
/**
* 导航状态
* 来源: GNSS数据中的GNRMC语句
* 示例: "V" (表示无效)
*/
private String navStatus;
/**
* GPS质量指示
* 来源: GNSS数据中的GNGGA语句
* 取值: 0=无效, 1=GPS定位, 2=差分GPS定位
* 示例: "1"
*/
private String quality;
/**
* 使用的卫星数量
* 来源: GNSS数据中的GNGGA语句
* 示例: "08"
*/
private String numSatellitesUsed;
/**
* 水平精度因子
* 来源: GNSS数据中的GNGGA语句
* 示例: "1.2"
*/
private String hdop;
/**
* 海拔高度
* 来源: GNSS数据中的GNGGA语句
* 单位: (m)
* 示例: "100.5"
*/
private String altitude;
/**
* 大地水准面高度
* 来源: GNSS数据中的GNGGA语句
* 单位: (m)
* 示例: "-29.8"
*/
private String geoidSeparation;
// 环境数据
private String humidity;
/**
* GNRMC语句校验和
* 来源: GNSS数据中的GNRMC语句
* 格式: 十六进制
* 示例: "2A"
*/
private String checksumRMC;
/**
* GNGGA语句校验和
* 来源: GNSS数据中的GNGGA语句
* 格式: 十六进制
* 示例: "3F"
*/
private String checksumGGA;
/**
* 定位精度(用于LBS/WiFi定位)
* 来源: LBS或WiFi字段的第三部分
* 单位: (m)
* 示例: "30"
*/
private String accuracy;
/**
* 定位来源
* 取值: "gnss", "lbs", "wifi"
* 示例: "gnss"
*/
private String positionSource;
/**
* 环境温度
* 来源: sensor.aht20.temp字段
* 单位: 摄氏度()
* 示例: "25.6"
*/
private String temperature;
// 加速度和陀螺仪数据
/**
* 环境湿度
* 来源: sensor.aht20.humi字段
* 单位: 百分比(%)
* 示例: "45.2"
*/
private String humidity;
/**
* X轴加速度
* 来源: sensor.imu.a_x字段
* 单位: m/s²
* 示例: "-0.01"
*/
private String accX;
/**
* Y轴加速度
* 来源: sensor.imu.a_y字段
* 单位: m/s²
* 示例: "0.76"
*/
private String accY;
/**
* Z轴加速度
* 来源: sensor.imu.a_z字段
* 单位: m/s²
* 示例: "-10.01"
*/
private String accZ;
/**
* X轴角速度
* 来源: sensor.imu.g_x字段
* 单位: rad/s
* 示例: "-0.0"
*/
private String gyroX;
/**
* Y轴角速度
* 来源: sensor.imu.g_y字段
* 单位: rad/s
* 示例: "0.03"
*/
private String gyroY;
/**
* Z轴角速度
* 来源: sensor.imu.g_z字段
* 单位: rad/s
* 示例: "-0.01"
*/
private String gyroZ;
// 新增三相电数据
/**
* 翻滚角
* 来源: sensor.imu.roll字段
* 单位: (°)
* 示例: "4.33"
*/
private String roll;
/**
* 俯仰角
* 来源: sensor.imu.pitch字段
* 单位: (°)
* 示例: "179.93"
*/
private String pitch;
/**
* 电池电量
* 来源: soc字段
* 单位: 百分比(%)
* 示例: "56"
*/
private String soc;
/**
* 电参数类型
* 来源: electric.name字段
* 取值: "single"(单相), "three"(三相)
* 示例: "single"
*/
private String electricType;
/**
* A相电参数数据
* 来源: electric.A字段(解析后的PhaseData对象)
*/
private PhaseData aPhase;
/**
* B相电参数数据(仅三相时存在)
* 来源: electric.B字段(解析后的PhaseData对象)
*/
private PhaseData bPhase;
/**
* C相电参数数据(仅三相时存在)
* 来源: electric.C字段(解析后的PhaseData对象)
*/
private PhaseData cPhase;
}
/**
* BMS电池管理系统数据
* 来源: bms字段(解析后的键值对)
* 包含: 电池电压(bat1-bat8), 总电压(V), 功率(P), 电流(I), 温度(temp1,temp2), 循环次数(times)
* 示例: {"bat1":"0.00", "bat2":"0.00", ..., "V":"0.00", "P":"0.00", "I":"0.00", "temp1":"0.00", "temp2":"0.00", "times":"0"}
*/
private Map<String, String> bmsData;
}