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 16:03:37 +08:00
|
|
|
|
import com.ruoyi.device.domain.impl.machine.mqtt.MqttCallbackRegistry;
|
2026-02-11 13:22:47 +08:00
|
|
|
|
import com.ruoyi.device.domain.impl.machine.state.CoverState;
|
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-11 13:22:47 +08:00
|
|
|
|
import com.ruoyi.device.domain.impl.tuohengmqtt.callback.IRealTimeBasicCallback;
|
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-11 13:22:47 +08:00
|
|
|
|
import com.ruoyi.device.domain.impl.tuohengmqtt.model.RealTimeBasicData;
|
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 16:03:37 +08:00
|
|
|
|
@Autowired
|
|
|
|
|
|
private MqttCallbackRegistry mqttCallbackRegistry;
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
2026-02-10 16:03:37 +08:00
|
|
|
|
// 设置 MqttCallbackRegistry 到 handler(用于指令回调)
|
|
|
|
|
|
handler.setMqttCallbackRegistry(mqttCallbackRegistry);
|
|
|
|
|
|
|
2026-02-10 10:51:42 +08:00
|
|
|
|
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) {
|
2026-02-11 10:47:58 +08:00
|
|
|
|
// log.info("========== 收到拓恒实时数据 ==========");
|
2026-02-11 10:54:06 +08:00
|
|
|
|
// log.info("收到拓恒实时数据 设备SN: {}", deviceSn);
|
2026-02-10 10:51:42 +08:00
|
|
|
|
try {
|
2026-02-11 10:47:58 +08:00
|
|
|
|
// log.info("数据内容: {}", objectMapper.writeValueAsString(data));
|
2026-02-10 15:07:15 +08:00
|
|
|
|
|
|
|
|
|
|
// 更新机场心跳时间戳
|
|
|
|
|
|
updateAirportHeartbeat(deviceSn);
|
|
|
|
|
|
|
|
|
|
|
|
// 同步无人机开关机状态
|
|
|
|
|
|
syncDronePowerState(deviceSn, data);
|
|
|
|
|
|
|
2026-02-11 13:33:47 +08:00
|
|
|
|
// 同步舱门状态
|
|
|
|
|
|
syncCoverStateFromRealTimeData(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
|
|
|
|
}
|
2026-02-11 10:47:58 +08:00
|
|
|
|
// log.info("=====================================");
|
2026-02-10 10:51:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
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) {
|
2026-02-11 10:47:58 +08:00
|
|
|
|
// log.info("========== 收到拓恒OSD数据 ==========");
|
2026-02-11 10:54:06 +08:00
|
|
|
|
// log.info("收到拓恒OSD数据 设备SN: {}", deviceSn);
|
2026-02-10 10:51:42 +08:00
|
|
|
|
try {
|
2026-02-11 10:47:58 +08:00
|
|
|
|
// 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);
|
|
|
|
|
|
}
|
2026-02-11 10:47:58 +08:00
|
|
|
|
// log.info("=====================================");
|
2026-02-10 10:51:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
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) {
|
2026-02-11 10:47:58 +08:00
|
|
|
|
// log.info("========== 收到拓恒Events数据 ==========");
|
2026-02-11 10:54:06 +08:00
|
|
|
|
// log.info("收到拓恒Events数据 设备SN: {}", deviceSn);
|
2026-02-10 10:51:42 +08:00
|
|
|
|
try {
|
2026-02-11 10:47:58 +08:00
|
|
|
|
// log.info("数据内容: {}", objectMapper.writeValueAsString(data));
|
2026-02-10 10:51:42 +08:00
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
log.error("序列化数据失败", e);
|
|
|
|
|
|
}
|
2026-02-11 10:47:58 +08:00
|
|
|
|
// log.info("=====================================");
|
2026-02-10 10:51:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-02-11 13:22:47 +08:00
|
|
|
|
handler.registerRealTimeBasicCallback(new IRealTimeBasicCallback() {
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void onRealTimeBasicData(String deviceSn, RealTimeBasicData data) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 同步舱门状态到 MachineStateManager
|
|
|
|
|
|
syncCoverState(deviceSn, data);
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
log.error("处理实时基础数据失败", e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-02-11 14:00:28 +08:00
|
|
|
|
handler.registerHeartbeatMessageCallback(new com.ruoyi.device.domain.impl.tuohengmqtt.callback.IHeartbeatMessageCallback() {
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void onHeartbeatMessage(String deviceSn, com.ruoyi.device.domain.impl.tuohengmqtt.model.HeartbeatMessageData data) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 同步舱门状态到 MachineStateManager
|
|
|
|
|
|
syncCoverStateFromHeartbeat(deviceSn, data);
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
log.error("处理心跳消息失败", e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-02-10 10:51:42 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-11 11:08:33 +08:00
|
|
|
|
log.info("【状态同步】收到无人机电源状态: sn={}, bPowerON={}", deviceSn, powerOn);
|
|
|
|
|
|
|
2026-02-10 17:14:39 +08:00
|
|
|
|
// 根据 bPowerON 值更新无人机状态
|
|
|
|
|
|
// 1 = 开机, 2 = 关机
|
|
|
|
|
|
if (powerOn == 1) {
|
|
|
|
|
|
// 开机时,只有在特定状态下才更新为 ONLINE
|
|
|
|
|
|
// 只有从 POWER_OFF、UNKNOWN、ARRIVED、RETURNING 这些状态才能转为 ONLINE
|
2026-02-10 17:17:04 +08:00
|
|
|
|
MachineStates currentStates = stateManager.getStates(deviceSn);
|
2026-02-10 17:14:39 +08:00
|
|
|
|
if (currentStates != null && currentStates.getDroneState() != null) {
|
|
|
|
|
|
DroneState currentState = currentStates.getDroneState();
|
|
|
|
|
|
if (currentState == DroneState.POWER_OFF ||
|
|
|
|
|
|
currentState == DroneState.UNKNOWN ||
|
|
|
|
|
|
currentState == DroneState.ARRIVED ||
|
|
|
|
|
|
currentState == DroneState.RETURNING) {
|
|
|
|
|
|
stateManager.setDroneState(deviceSn, DroneState.ONLINE);
|
2026-02-11 11:08:33 +08:00
|
|
|
|
log.info("【状态同步】同步无人机开机状态: sn={}, powerOn={}, 从 {} 更新为 ONLINE",
|
2026-02-10 17:14:39 +08:00
|
|
|
|
deviceSn, powerOn, currentState);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
log.debug("无人机已开机,但当前状态为 {},不更新为 ONLINE", currentState);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果没有当前状态,直接设置为 ONLINE
|
|
|
|
|
|
stateManager.setDroneState(deviceSn, DroneState.ONLINE);
|
2026-02-10 17:17:04 +08:00
|
|
|
|
log.debug("同步无人机开机状态: sn={}, powerOn={}, 初始化为 ONLINE", deviceSn, powerOn);
|
2026-02-10 17:14:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
} else if (powerOn == 2) {
|
2026-02-10 15:07:15 +08:00
|
|
|
|
stateManager.setDroneState(deviceSn, DroneState.POWER_OFF);
|
2026-02-11 11:08:33 +08:00
|
|
|
|
log.info("【状态同步】同步无人机关机状态: sn={}, powerOn={}", deviceSn, powerOn);
|
2026-02-10 15:07:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
log.error("同步无人机开关机状态失败: sn={}", deviceSn, e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-11 13:22:47 +08:00
|
|
|
|
/**
|
2026-02-11 13:33:47 +08:00
|
|
|
|
* 从 realTime/data 主题同步舱门状态
|
|
|
|
|
|
*/
|
|
|
|
|
|
private void syncCoverStateFromRealTimeData(String deviceSn, TuohengRealTimeData data) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (data == null || data.getNestDoor() == null || data.getNestDoor().getData() == null) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Integer status = data.getNestDoor().getData().getStatus();
|
|
|
|
|
|
if (status == null) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log.info("【状态同步】收到舱门状态: sn={}, status={}", deviceSn, status);
|
|
|
|
|
|
|
|
|
|
|
|
// 根据 status 值更新舱门状态
|
|
|
|
|
|
// 1 = 开仓, 2 = 关仓
|
|
|
|
|
|
CoverState coverState;
|
|
|
|
|
|
if (status == 1) {
|
|
|
|
|
|
coverState = CoverState.OPENED;
|
|
|
|
|
|
log.info("【状态同步】同步舱门开启状态: sn={}, status={}", deviceSn, status);
|
|
|
|
|
|
} else if (status == 2) {
|
|
|
|
|
|
coverState = CoverState.CLOSED;
|
|
|
|
|
|
log.info("【状态同步】同步舱门关闭状态: sn={}, status={}", deviceSn, status);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
log.warn("未知的舱门状态值: sn={}, status={}", deviceSn, status);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
stateManager.setCoverState(deviceSn, coverState);
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
log.error("同步舱门状态失败: sn={}", deviceSn, e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 同步舱门状态(从 realTime/basic 主题,已废弃)
|
2026-02-11 13:22:47 +08:00
|
|
|
|
*/
|
|
|
|
|
|
private void syncCoverState(String deviceSn, com.ruoyi.device.domain.impl.tuohengmqtt.model.RealTimeBasicData data) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (data == null || data.getData() == null || data.getData().getNestDoor() == null) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
RealTimeBasicData.NestDoorInfo nestDoor = data.getData().getNestDoor();
|
|
|
|
|
|
if (nestDoor.getData() == null) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Integer status = nestDoor.getData().getStatus();
|
|
|
|
|
|
if (status == null) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log.info("【状态同步】收到舱门状态: sn={}, status={}", deviceSn, status);
|
|
|
|
|
|
|
|
|
|
|
|
// 根据 status 值更新舱门状态
|
|
|
|
|
|
// 1 = 开仓, 2 = 关仓
|
|
|
|
|
|
CoverState coverState;
|
|
|
|
|
|
if (status == 1) {
|
|
|
|
|
|
coverState = CoverState.OPENED;
|
|
|
|
|
|
log.info("【状态同步】同步舱门开启状态: sn={}, status={}", deviceSn, status);
|
|
|
|
|
|
} else if (status == 2) {
|
|
|
|
|
|
coverState = CoverState.CLOSED;
|
|
|
|
|
|
log.info("【状态同步】同步舱门关闭状态: sn={}, status={}", deviceSn, status);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
log.warn("未知的舱门状态值: sn={}, status={}", deviceSn, status);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
stateManager.setCoverState(deviceSn, coverState);
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
log.error("同步舱门状态失败: sn={}", deviceSn, e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-11 14:00:28 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 从 heartbeat/message 主题同步舱门状态
|
|
|
|
|
|
*/
|
|
|
|
|
|
private void syncCoverStateFromHeartbeat(String deviceSn, com.ruoyi.device.domain.impl.tuohengmqtt.model.HeartbeatMessageData data) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (data == null || data.getNestDoor() == null || data.getNestDoor().getData() == null) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Integer status = data.getNestDoor().getData().getStatus();
|
|
|
|
|
|
if (status == null) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log.info("【状态同步】收到舱门状态(heartbeat): sn={}, status={}", deviceSn, status);
|
|
|
|
|
|
|
|
|
|
|
|
// 根据 status 值更新舱门状态
|
|
|
|
|
|
// 1 = 开仓, 2 = 关仓
|
|
|
|
|
|
CoverState coverState;
|
|
|
|
|
|
if (status == 1) {
|
|
|
|
|
|
coverState = CoverState.OPENED;
|
|
|
|
|
|
log.info("【状态同步】同步舱门开启状态(heartbeat): sn={}, status={}", deviceSn, status);
|
|
|
|
|
|
} else if (status == 2) {
|
|
|
|
|
|
coverState = CoverState.CLOSED;
|
|
|
|
|
|
log.info("【状态同步】同步舱门关闭状态(heartbeat): sn={}, status={}", deviceSn, status);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
log.warn("未知的舱门状态值(heartbeat): sn={}, status={}", deviceSn, status);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
stateManager.setCoverState(deviceSn, coverState);
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
log.error("同步舱门状态失败(heartbeat): sn={}", deviceSn, e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-10 15:07:15 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 定时检查机场心跳超时
|
|
|
|
|
|
* 每分钟执行一次
|
|
|
|
|
|
*/
|
|
|
|
|
|
@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
|
|
|
|
}
|