iot对接
This commit is contained in:
parent
0a55e44330
commit
88c81c9010
|
|
@ -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("位置信息不完整");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
Loading…
Reference in New Issue