a-tuoheng-device/src/main/java/com/ruoyi/device/service/impl/TuohengService.java

315 lines
12 KiB
Java
Raw Normal View History

2026-02-10 10:51:42 +08:00
package com.ruoyi.device.service.impl;
2026-02-10 12:26:52 +08:00
import com.fasterxml.jackson.databind.ObjectMapper;
2026-02-10 10:51:42 +08:00
import com.ruoyi.device.domain.api.IDockAircraftDomain;
import com.ruoyi.device.domain.api.IDockDomain;
import com.ruoyi.device.domain.api.IAircraftDomain;
import com.ruoyi.device.domain.api.IDeviceDomain;
2026-02-10 15:07:15 +08:00
import com.ruoyi.device.domain.impl.machine.state.DroneState;
import com.ruoyi.device.domain.impl.machine.state.AirportState;
import com.ruoyi.device.domain.impl.machine.state.MachineStates;
import com.ruoyi.device.domain.impl.machine.statemachine.MachineStateManager;
2026-02-10 12:26:52 +08:00
import com.ruoyi.device.domain.impl.tuohengmqtt.callback.ITuohengEventsCallback;
import com.ruoyi.device.domain.impl.tuohengmqtt.callback.ITuohengOsdCallback;
import com.ruoyi.device.domain.impl.tuohengmqtt.callback.ITuohengRealTimeDataCallback;
2026-02-10 10:51:42 +08:00
import com.ruoyi.device.domain.impl.tuohengmqtt.config.TuohengMqttClientConfig;
import com.ruoyi.device.domain.impl.tuohengmqtt.handler.TuohengMqttMessageHandler;
import com.ruoyi.device.domain.impl.tuohengmqtt.manager.TuohengMqttClientManager;
import com.ruoyi.device.domain.impl.tuohengmqtt.model.AirportOsdData;
import com.ruoyi.device.domain.impl.tuohengmqtt.model.EventsData;
2026-02-10 12:26:52 +08:00
import com.ruoyi.device.domain.impl.tuohengmqtt.model.TuohengRealTimeData;
2026-02-10 10:51:42 +08:00
import com.ruoyi.device.domain.model.Aircraft;
import com.ruoyi.device.domain.model.Device;
import com.ruoyi.device.domain.model.Dock;
2026-02-10 12:26:52 +08:00
import com.ruoyi.device.domain.model.DockAircraft;
2026-02-10 10:51:42 +08:00
import com.ruoyi.device.service.config.TuohengMqttProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
2026-02-10 15:07:15 +08:00
import org.springframework.scheduling.annotation.Scheduled;
2026-02-10 10:51:42 +08:00
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
@Slf4j
public class TuohengService {
@Autowired
private TuohengMqttClientManager clientManager;
@Autowired
private TuohengMqttProperties mqttProperties;
@Autowired
private IDockAircraftDomain dockAircraftDomain;
@Autowired
private IDockDomain dockDomain;
@Autowired
private IAircraftDomain aircraftDomain;
@Autowired
private IDeviceDomain deviceDomain;
2026-02-10 15:07:15 +08:00
@Autowired
private MachineStateManager stateManager;
2026-02-10 10:51:42 +08:00
private final ObjectMapper objectMapper = new ObjectMapper();
2026-02-10 15:07:15 +08:00
/**
* 机场心跳时间戳记录 (deviceSn -> lastHeartbeatTime)
*/
private final Map<String, Long> airportHeartbeatMap = new java.util.concurrent.ConcurrentHashMap<>();
/**
* 心跳超时时间5分钟
*/
private static final long HEARTBEAT_TIMEOUT = 5 * 60 * 1000;
2026-02-10 10:51:42 +08:00
@EventListener(ApplicationReadyEvent.class)
public void onApplicationReady() {
TuohengMqttClientConfig config = TuohengMqttClientConfig.builder()
.host(mqttProperties.getHost())
.port(mqttProperties.getPort())
.clientId(mqttProperties.getClientId())
.username(mqttProperties.getUsername())
.password(mqttProperties.getPassword())
.connectionTimeout(mqttProperties.getConnectionTimeout())
.keepAliveInterval(mqttProperties.getKeepAliveInterval())
.autoReconnect(mqttProperties.getAutoReconnect())
.cleanSession(mqttProperties.getCleanSession())
.useSharedSubscription(true)
.sharedGroupName("tuoheng-group")
.build();
clientManager.initClient(config);
TuohengMqttMessageHandler handler = clientManager.getHandler();
Map<String, String> mapping = loadAirportDroneMapping();
2026-02-10 12:26:52 +08:00
handler.registerRealTimeDataCallback(new ITuohengRealTimeDataCallback() {
2026-02-10 10:51:42 +08:00
@Override
2026-02-10 12:26:52 +08:00
public void onRealTimeData(String deviceSn, TuohengRealTimeData data) {
log.info("========== 收到拓恒实时数据 ==========");
log.info("设备SN: {}", deviceSn);
2026-02-10 10:51:42 +08:00
try {
log.info("数据内容: {}", objectMapper.writeValueAsString(data));
2026-02-10 15:07:15 +08:00
// 更新机场心跳时间戳
updateAirportHeartbeat(deviceSn);
// 同步无人机开关机状态
syncDronePowerState(deviceSn, data);
2026-02-10 10:51:42 +08:00
} catch (Exception e) {
2026-02-10 15:07:15 +08:00
log.error("处理实时数据失败", e);
2026-02-10 10:51:42 +08:00
}
log.info("=====================================");
}
});
2026-02-10 12:26:52 +08:00
handler.registerOsdCallback(new ITuohengOsdCallback() {
2026-02-10 10:51:42 +08:00
@Override
2026-02-10 12:26:52 +08:00
public void onOsdData(String deviceSn, AirportOsdData data) {
log.info("========== 收到拓恒OSD数据 ==========");
log.info("设备SN: {}", deviceSn);
2026-02-10 10:51:42 +08:00
try {
log.info("数据内容: {}", objectMapper.writeValueAsString(data));
2026-02-10 15:07:15 +08:00
// 同步飞行状态到 MachineStateManager
syncFlightState(deviceSn, data);
2026-02-10 10:51:42 +08:00
} catch (Exception e) {
log.error("序列化数据失败", e);
}
log.info("=====================================");
}
});
2026-02-10 12:26:52 +08:00
handler.registerEventsCallback(new ITuohengEventsCallback() {
2026-02-10 10:51:42 +08:00
@Override
2026-02-10 12:26:52 +08:00
public void onEventsData(String deviceSn, EventsData data) {
log.info("========== 收到拓恒Events数据 ==========");
log.info("设备SN: {}", deviceSn);
2026-02-10 10:51:42 +08:00
try {
log.info("数据内容: {}", objectMapper.writeValueAsString(data));
} catch (Exception e) {
log.error("序列化数据失败", e);
}
log.info("=====================================");
}
});
log.info("TuohengService 初始化完成,已注册所有回调");
}
private Map<String, String> loadAirportDroneMapping() {
Map<String, String> mapping = new HashMap<>();
try {
List<DockAircraft> dockAircraftList = dockAircraftDomain.selectDockAircraftList(new DockAircraft());
for (DockAircraft dockAircraft : dockAircraftList) {
Dock dock = dockDomain.selectDockByDockId(dockAircraft.getDockId());
Aircraft aircraft = aircraftDomain.selectAircraftByAircraftId(dockAircraft.getAircraftId());
if (dock != null && aircraft != null) {
Device dockDevice = deviceDomain.selectDeviceByDeviceId(dock.getDeviceId());
Device aircraftDevice = deviceDomain.selectDeviceByDeviceId(aircraft.getDeviceId());
if (dockDevice != null && aircraftDevice != null) {
String airportSn = dockDevice.getDeviceSn();
String droneSn = aircraftDevice.getDeviceSn();
2026-02-10 10:54:54 +08:00
2026-02-10 10:51:42 +08:00
if (airportSn != null && droneSn != null) {
2026-02-10 10:54:54 +08:00
if (airportSn.startsWith("THJS") || droneSn.startsWith("THJS")) {
log.debug("跳过大疆设备 - 机场SN: {}, 无人机SN: {}", airportSn, droneSn);
continue;
}
2026-02-10 10:51:42 +08:00
mapping.put(airportSn, droneSn);
log.info("加载机场-无人机映射: {} -> {}", airportSn, droneSn);
}
}
}
}
log.info("从数据库加载机场-无人机映射完成,共 {} 条记录", mapping.size());
} catch (Exception e) {
log.error("从数据库加载机场-无人机映射失败", e);
}
return mapping;
}
2026-02-10 15:07:15 +08:00
/**
* 同步飞行状态到 MachineStateManager
* 根据 OSD 数据中的 flighttask_step_code mode_code 判断飞行状态
*/
private void syncFlightState(String deviceSn, AirportOsdData data) {
try {
if (data == null) {
return;
}
String flighttaskStepCode = data.getFlighttaskStepCode();
String modeCode = data.getModeCode();
// 同步无人机状态
DroneState droneState = determineDroneState(flighttaskStepCode, modeCode);
if (droneState != null) {
stateManager.setDroneState(deviceSn, droneState);
log.debug("同步飞行状态: sn={}, flighttaskStepCode={}, modeCode={}, state={}",
deviceSn, flighttaskStepCode, modeCode, droneState);
}
// 注意:机场在线状态由 IOT 平台的心跳机制判断5分钟超时
// 不在这里简单地根据收到数据就判断为在线
} catch (Exception e) {
log.error("同步飞行状态失败: sn={}", deviceSn, e);
}
}
/**
* 根据任务状态码和模式码判断无人机状态
*/
private DroneState determineDroneState(String flighttaskStepCode, String modeCode) {
// 优先根据 flighttask_step_code 判断
if (flighttaskStepCode != null) {
switch (flighttaskStepCode) {
case "1":
// 飞行作业中
return DroneState.FLYING;
case "2":
// 作业后状态恢复,可能是返航或已到达
return DroneState.RETURNING;
case "5":
// 任务空闲
return DroneState.ONLINE;
case "255":
// 飞行器异常
return DroneState.UNKNOWN;
}
}
// 根据 mode_code 辅助判断
if (modeCode != null) {
if (modeCode.equals("3") || modeCode.equals("4") || modeCode.equals("5")) {
// 飞行中状态
return DroneState.FLYING;
}
}
return null;
}
/**
* 更新机场心跳时间戳并设置在线状态
*/
private void updateAirportHeartbeat(String deviceSn) {
long currentTime = System.currentTimeMillis();
airportHeartbeatMap.put(deviceSn, currentTime);
// 收到心跳,设置机场为在线
stateManager.setAirportState(deviceSn, AirportState.ONLINE);
log.debug("更新机场心跳: sn={}, time={}", deviceSn, currentTime);
}
/**
* 同步无人机开关机状态
* 注意只有关机时才更新状态其他情况保持当前状态不变
*/
private void syncDronePowerState(String deviceSn, TuohengRealTimeData data) {
try {
if (data == null || data.getDroneBattery() == null || data.getDroneBattery().getData() == null) {
return;
}
Integer powerOn = data.getDroneBattery().getData().getBPowerON();
if (powerOn == null) {
return;
}
// 只有关机时才更新状态为 POWER_OFF
// 其他情况(开机、飞行中等)保持当前状态不变
if (powerOn == 2) {
stateManager.setDroneState(deviceSn, DroneState.POWER_OFF);
log.debug("同步无人机关机状态: sn={}, powerOn={}", deviceSn, powerOn);
}
} catch (Exception e) {
log.error("同步无人机开关机状态失败: sn={}", deviceSn, e);
}
}
/**
* 定时检查机场心跳超时
* 每分钟执行一次
*/
@Scheduled(fixedRate = 60000)
public void checkAirportHeartbeatTimeout() {
long currentTime = System.currentTimeMillis();
for (Map.Entry<String, Long> entry : airportHeartbeatMap.entrySet()) {
String deviceSn = entry.getKey();
Long lastHeartbeatTime = entry.getValue();
long timeDiff = currentTime - lastHeartbeatTime;
if (timeDiff > HEARTBEAT_TIMEOUT) {
// 超时,设置为离线
stateManager.setAirportState(deviceSn, AirportState.OFFLINE);
log.warn("机场心跳超时,设置为离线: sn={}, 超时时长={}秒",
deviceSn, timeDiff / 1000);
}
}
}
2026-02-10 10:51:42 +08:00
}