添加拓恒机场和无人机状态
This commit is contained in:
parent
7a7af0a496
commit
20f11d581b
|
|
@ -8,22 +8,32 @@ import com.ruoyi.device.api.domain.DroneRealtimeInfoVO;
|
||||||
import com.ruoyi.device.api.domain.DroneTakeoffResponseVO;
|
import com.ruoyi.device.api.domain.DroneTakeoffResponseVO;
|
||||||
import com.ruoyi.device.api.enums.DroneCurrentStatusEnum;
|
import com.ruoyi.device.api.enums.DroneCurrentStatusEnum;
|
||||||
import com.ruoyi.device.api.enums.DroneMissionStatusEnum;
|
import com.ruoyi.device.api.enums.DroneMissionStatusEnum;
|
||||||
|
import com.ruoyi.device.domain.impl.machine.MachineCommandManager;
|
||||||
|
import com.ruoyi.device.domain.impl.machine.command.CommandResult;
|
||||||
|
import com.ruoyi.device.domain.impl.machine.command.CommandType;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 无人机飞控Controller
|
* 无人机飞控Controller
|
||||||
*
|
*
|
||||||
* @author ruoyi
|
* @author ruoyi
|
||||||
* @date 2026-02-04
|
* @date 2026-02-04
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Tag(name = "无人机飞控管理", description = "无人机飞控相关接口")
|
@Tag(name = "无人机飞控管理", description = "无人机飞控相关接口")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/drone")
|
@RequestMapping("/drone")
|
||||||
public class AircraftFlyController extends BaseController
|
public class AircraftFlyController extends BaseController
|
||||||
{
|
{
|
||||||
|
@Autowired
|
||||||
|
private MachineCommandManager machineCommandManager;
|
||||||
/**
|
/**
|
||||||
* 无人机飞控命令
|
* 无人机飞控命令
|
||||||
*
|
*
|
||||||
|
|
@ -101,4 +111,38 @@ public class AircraftFlyController extends BaseController
|
||||||
vo.setMissionStatus(DroneMissionStatusEnum.TAKING_OFF);
|
vo.setMissionStatus(DroneMissionStatusEnum.TAKING_OFF);
|
||||||
return R.ok(vo);
|
return R.ok(vo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 无人机开机接口
|
||||||
|
*
|
||||||
|
* @param sn 机场SN号
|
||||||
|
* @return 开机响应
|
||||||
|
*/
|
||||||
|
@Operation(summary = "无人机开机", description = "控制指定机场的无人机执行开机操作")
|
||||||
|
@PostMapping("/power-on/{sn}")
|
||||||
|
public R<String> powerOn(
|
||||||
|
@Parameter(description = "机场SN号", required = true, example = "THJSQ03B2309DN7VQN43")
|
||||||
|
@PathVariable("sn") String sn)
|
||||||
|
{
|
||||||
|
log.info("收到无人机开机请求: sn={}", sn);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 调用机器命令管理器执行开机命令
|
||||||
|
CompletableFuture<CommandResult> future = machineCommandManager.executeCommand(sn, CommandType.POWER_ON);
|
||||||
|
|
||||||
|
// 等待命令执行完成
|
||||||
|
CommandResult result = future.get();
|
||||||
|
|
||||||
|
if (result.isSuccess()) {
|
||||||
|
log.info("无人机开机成功: sn={}", sn);
|
||||||
|
return R.ok("开机命令执行成功");
|
||||||
|
} else {
|
||||||
|
log.error("无人机开机失败: sn={}, reason={}", sn, result.getErrorMessage());
|
||||||
|
return R.fail("开机命令执行失败: " + result.getErrorMessage());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("无人机开机异常: sn={}", sn, e);
|
||||||
|
return R.fail("开机命令执行异常: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ package com.ruoyi.device.domain.impl.machine.config;
|
||||||
|
|
||||||
import com.ruoyi.device.domain.impl.machine.vendor.VendorRegistry;
|
import com.ruoyi.device.domain.impl.machine.vendor.VendorRegistry;
|
||||||
import com.ruoyi.device.domain.impl.machine.vendor.dji.DjiVendorConfig;
|
import com.ruoyi.device.domain.impl.machine.vendor.dji.DjiVendorConfig;
|
||||||
|
import com.ruoyi.device.domain.impl.machine.vendor.tuoheng.TuohengVendorConfig;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.boot.CommandLineRunner;
|
import org.springframework.boot.CommandLineRunner;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
@ -19,10 +20,16 @@ public class MachineFrameworkConfig {
|
||||||
* 自动注册所有厂家配置
|
* 自动注册所有厂家配置
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public CommandLineRunner registerVendors(VendorRegistry vendorRegistry, DjiVendorConfig djiVendorConfig) {
|
public CommandLineRunner registerVendors(VendorRegistry vendorRegistry,
|
||||||
|
DjiVendorConfig djiVendorConfig,
|
||||||
|
TuohengVendorConfig tuohengVendorConfig) {
|
||||||
return args -> {
|
return args -> {
|
||||||
// 注册大疆厂家配置
|
// 注册大疆厂家配置
|
||||||
vendorRegistry.registerVendor(djiVendorConfig);
|
vendorRegistry.registerVendor(djiVendorConfig);
|
||||||
|
|
||||||
|
// 注册拓恒厂家配置
|
||||||
|
vendorRegistry.registerVendor(tuohengVendorConfig);
|
||||||
|
|
||||||
log.info("设备框架初始化完成,已注册厂家: {}", vendorRegistry.getAllVendorTypes());
|
log.info("设备框架初始化完成,已注册厂家: {}", vendorRegistry.getAllVendorTypes());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,17 @@ package com.ruoyi.device.domain.impl.machine.state;
|
||||||
*/
|
*/
|
||||||
public enum AirportState {
|
public enum AirportState {
|
||||||
/**
|
/**
|
||||||
* 未知状态(服务器重启后的初始状态,等待第一次心跳同步),同时也是离线状态
|
* 未知状态(服务器重启后的初始状态,等待第一次心跳同步)
|
||||||
*/
|
*/
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在线
|
* 在线
|
||||||
*/
|
*/
|
||||||
ONLINE
|
ONLINE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 离线(心跳超时)
|
||||||
|
*/
|
||||||
|
OFFLINE
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,17 @@ package com.ruoyi.device.domain.impl.machine.state;
|
||||||
*/
|
*/
|
||||||
public enum DroneState {
|
public enum DroneState {
|
||||||
/**
|
/**
|
||||||
* 未知状态(服务器重启后的初始状态,等待第一次心跳同步),同时也是离线状态
|
* 未知状态(服务器重启后的初始状态,等待第一次心跳同步)
|
||||||
*/
|
*/
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在线
|
* 关机状态
|
||||||
|
*/
|
||||||
|
POWER_OFF,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在线(已开机)
|
||||||
*/
|
*/
|
||||||
ONLINE,
|
ONLINE,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,25 @@ public class InMemorySnVendorMappingStore implements SnVendorMappingStore {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getVendorType(String sn) {
|
public String getVendorType(String sn) {
|
||||||
|
// 先从缓存中获取
|
||||||
String vendorType = snToVendorMap.get(sn);
|
String vendorType = snToVendorMap.get(sn);
|
||||||
log.debug("从内存获取 SN 映射: sn={}, vendorType={}", sn, vendorType);
|
|
||||||
|
if (vendorType != null) {
|
||||||
|
log.debug("从内存缓存获取 SN 映射: sn={}, vendorType={}", sn, vendorType);
|
||||||
|
return vendorType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据 SN 前缀自动判断厂商类型
|
||||||
|
vendorType = detectVendorTypeBySn(sn);
|
||||||
|
|
||||||
|
if (vendorType != null) {
|
||||||
|
// 缓存判断结果
|
||||||
|
snToVendorMap.put(sn, vendorType);
|
||||||
|
log.debug("根据 SN 前缀自动识别厂商: sn={}, vendorType={}", sn, vendorType);
|
||||||
|
} else {
|
||||||
|
log.warn("无法识别 SN 对应的厂商类型: sn={}", sn);
|
||||||
|
}
|
||||||
|
|
||||||
return vendorType;
|
return vendorType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,4 +61,24 @@ public class InMemorySnVendorMappingStore implements SnVendorMappingStore {
|
||||||
public boolean exists(String sn) {
|
public boolean exists(String sn) {
|
||||||
return snToVendorMap.containsKey(sn);
|
return snToVendorMap.containsKey(sn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 SN 前缀自动识别厂商类型
|
||||||
|
*
|
||||||
|
* @param sn 设备SN号
|
||||||
|
* @return 厂商类型,无法识别返回 null
|
||||||
|
*/
|
||||||
|
private String detectVendorTypeBySn(String sn) {
|
||||||
|
if (sn == null || sn.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拓恒设备:SN 以 "TH" 开头
|
||||||
|
if (sn.startsWith("TH")) {
|
||||||
|
return "TUOHENG";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 大疆设备:其他情况默认为大疆
|
||||||
|
return "DJI";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
95
src/main/java/com/ruoyi/device/domain/impl/machine/vendor/tuoheng/TuohengVendorConfig.java
vendored
Normal file
95
src/main/java/com/ruoyi/device/domain/impl/machine/vendor/tuoheng/TuohengVendorConfig.java
vendored
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
package com.ruoyi.device.domain.impl.machine.vendor.tuoheng;
|
||||||
|
|
||||||
|
import com.ruoyi.device.domain.impl.machine.command.CommandType;
|
||||||
|
import com.ruoyi.device.domain.impl.machine.command.Transaction;
|
||||||
|
import com.ruoyi.device.domain.impl.machine.state.*;
|
||||||
|
import com.ruoyi.device.domain.impl.machine.vendor.VendorConfig;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拓恒无人机厂家配置
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class TuohengVendorConfig implements VendorConfig {
|
||||||
|
|
||||||
|
private final Map<CommandType, Transaction> transactionMap = new HashMap<>();
|
||||||
|
|
||||||
|
public TuohengVendorConfig() {
|
||||||
|
initTransactions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getVendorType() {
|
||||||
|
return "TUOHENG";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getVendorName() {
|
||||||
|
return "拓恒";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Transaction getTransaction(CommandType commandType) {
|
||||||
|
return transactionMap.get(commandType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canExecuteCommand(MachineStates currentStates, CommandType commandType) {
|
||||||
|
DroneState droneState = currentStates.getDroneState();
|
||||||
|
AirportState airportState = currentStates.getAirportState();
|
||||||
|
DebugModeState debugModeState = currentStates.getDebugModeState();
|
||||||
|
|
||||||
|
switch (commandType) {
|
||||||
|
case POWER_ON:
|
||||||
|
// 开机前置条件:机场在线、无人机关机
|
||||||
|
// 注:拓恒无人机没有调试模式概念
|
||||||
|
return airportState == AirportState.ONLINE
|
||||||
|
&& droneState == DroneState.POWER_OFF;
|
||||||
|
|
||||||
|
case TAKE_OFF:
|
||||||
|
// 起飞前置条件:无人机已开机、机场在线
|
||||||
|
return droneState == DroneState.ONLINE
|
||||||
|
&& airportState == AirportState.ONLINE;
|
||||||
|
|
||||||
|
case RETURN_HOME:
|
||||||
|
// 返航前置条件:无人机飞行中
|
||||||
|
return droneState == DroneState.FLYING
|
||||||
|
|| droneState == DroneState.ARRIVED;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CommandType> getAvailableCommands(MachineStates currentStates) {
|
||||||
|
List<CommandType> availableCommands = new ArrayList<>();
|
||||||
|
|
||||||
|
for (CommandType commandType : CommandType.values()) {
|
||||||
|
if (canExecuteCommand(currentStates, commandType)) {
|
||||||
|
availableCommands.add(commandType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return availableCommands;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化事务定义
|
||||||
|
*/
|
||||||
|
private void initTransactions() {
|
||||||
|
// 开机命令
|
||||||
|
Transaction powerOnTransaction = new Transaction("开机", CommandType.POWER_ON)
|
||||||
|
.root(new com.ruoyi.device.domain.impl.machine.vendor.tuoheng.instruction.TuohengPowerOnInstruction())
|
||||||
|
.setTimeout(60000);
|
||||||
|
transactionMap.put(powerOnTransaction.getCommandType(), powerOnTransaction);
|
||||||
|
|
||||||
|
log.info("拓恒厂家配置初始化完成,共配置{}个命令", transactionMap.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
package com.ruoyi.device.domain.impl.machine.vendor.tuoheng.instruction;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.ruoyi.device.domain.impl.machine.instruction.AbstractInstruction;
|
||||||
|
import com.ruoyi.device.domain.impl.machine.instruction.CallbackConfig;
|
||||||
|
import com.ruoyi.device.domain.impl.machine.instruction.InstructionContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拓恒无人机开机指令
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class TuohengPowerOnInstruction extends AbstractInstruction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "TUOHENG_POWER_ON";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeRemoteCall(InstructionContext context) throws Exception {
|
||||||
|
String sn = context.getSn();
|
||||||
|
log.info("发送拓恒无人机开机指令: sn={}", sn);
|
||||||
|
|
||||||
|
// 构建MQTT消息
|
||||||
|
JSONObject payload = new JSONObject();
|
||||||
|
payload.put("messageID", System.currentTimeMillis());
|
||||||
|
payload.put("timestamp", System.currentTimeMillis());
|
||||||
|
payload.put("code", "DronePower");
|
||||||
|
payload.put("value", "1"); // 1=开机, 0=关机
|
||||||
|
payload.put("channel", 1);
|
||||||
|
|
||||||
|
String topic = "/topic/v1/airportControl/" + sn;
|
||||||
|
|
||||||
|
context.getMqttClient().sendMessage(topic, payload.toJSONString());
|
||||||
|
log.info("拓恒开机指令发送成功: topic={}, payload={}", topic, payload.toJSONString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getMethodCallbackConfig(InstructionContext context) {
|
||||||
|
String sn = context.getSn();
|
||||||
|
|
||||||
|
// 监听机场确认消息
|
||||||
|
return CallbackConfig.builder()
|
||||||
|
.topic("/topic/v1/airportNest/" + sn + "/confirm")
|
||||||
|
.fieldPath("code")
|
||||||
|
.expectedValue("DronePower")
|
||||||
|
.timeoutMs(10000)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getStateCallbackConfig(InstructionContext context) {
|
||||||
|
String sn = context.getSn();
|
||||||
|
|
||||||
|
// 监听无人机开机状态变化
|
||||||
|
return CallbackConfig.builder()
|
||||||
|
.topic("/topic/v1/airportNest/" + sn + "/realTime/data")
|
||||||
|
.fieldPath("droneBattery.bPowerON")
|
||||||
|
.expectedValue("2") // 2表示已开机
|
||||||
|
.timeoutMs(60000)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTimeoutMs() {
|
||||||
|
return 60000; // 总超时时间60秒
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,7 +12,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
* @author ruoyi
|
* @author ruoyi
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
|
||||||
public class AirportOsdData {
|
public class AirportOsdData {
|
||||||
|
|
||||||
@JsonProperty("working_current")
|
@JsonProperty("working_current")
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,10 @@ import com.ruoyi.device.domain.api.IDockAircraftDomain;
|
||||||
import com.ruoyi.device.domain.api.IDockDomain;
|
import com.ruoyi.device.domain.api.IDockDomain;
|
||||||
import com.ruoyi.device.domain.api.IAircraftDomain;
|
import com.ruoyi.device.domain.api.IAircraftDomain;
|
||||||
import com.ruoyi.device.domain.api.IDeviceDomain;
|
import com.ruoyi.device.domain.api.IDeviceDomain;
|
||||||
|
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;
|
||||||
import com.ruoyi.device.domain.impl.tuohengmqtt.callback.ITuohengEventsCallback;
|
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.ITuohengOsdCallback;
|
||||||
import com.ruoyi.device.domain.impl.tuohengmqtt.callback.ITuohengRealTimeDataCallback;
|
import com.ruoyi.device.domain.impl.tuohengmqtt.callback.ITuohengRealTimeDataCallback;
|
||||||
|
|
@ -23,6 +27,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||||
import org.springframework.context.event.EventListener;
|
import org.springframework.context.event.EventListener;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
@ -51,8 +56,21 @@ public class TuohengService {
|
||||||
@Autowired
|
@Autowired
|
||||||
private IDeviceDomain deviceDomain;
|
private IDeviceDomain deviceDomain;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MachineStateManager stateManager;
|
||||||
|
|
||||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 机场心跳时间戳记录 (deviceSn -> lastHeartbeatTime)
|
||||||
|
*/
|
||||||
|
private final Map<String, Long> airportHeartbeatMap = new java.util.concurrent.ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 心跳超时时间:5分钟
|
||||||
|
*/
|
||||||
|
private static final long HEARTBEAT_TIMEOUT = 5 * 60 * 1000;
|
||||||
|
|
||||||
@EventListener(ApplicationReadyEvent.class)
|
@EventListener(ApplicationReadyEvent.class)
|
||||||
public void onApplicationReady() {
|
public void onApplicationReady() {
|
||||||
TuohengMqttClientConfig config = TuohengMqttClientConfig.builder()
|
TuohengMqttClientConfig config = TuohengMqttClientConfig.builder()
|
||||||
|
|
@ -82,8 +100,15 @@ public class TuohengService {
|
||||||
log.info("设备SN: {}", deviceSn);
|
log.info("设备SN: {}", deviceSn);
|
||||||
try {
|
try {
|
||||||
log.info("数据内容: {}", objectMapper.writeValueAsString(data));
|
log.info("数据内容: {}", objectMapper.writeValueAsString(data));
|
||||||
|
|
||||||
|
// 更新机场心跳时间戳
|
||||||
|
updateAirportHeartbeat(deviceSn);
|
||||||
|
|
||||||
|
// 同步无人机开关机状态
|
||||||
|
syncDronePowerState(deviceSn, data);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("序列化数据失败", e);
|
log.error("处理实时数据失败", e);
|
||||||
}
|
}
|
||||||
log.info("=====================================");
|
log.info("=====================================");
|
||||||
}
|
}
|
||||||
|
|
@ -96,6 +121,10 @@ public class TuohengService {
|
||||||
log.info("设备SN: {}", deviceSn);
|
log.info("设备SN: {}", deviceSn);
|
||||||
try {
|
try {
|
||||||
log.info("数据内容: {}", objectMapper.writeValueAsString(data));
|
log.info("数据内容: {}", objectMapper.writeValueAsString(data));
|
||||||
|
|
||||||
|
// 同步飞行状态到 MachineStateManager
|
||||||
|
syncFlightState(deviceSn, data);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("序列化数据失败", e);
|
log.error("序列化数据失败", e);
|
||||||
}
|
}
|
||||||
|
|
@ -157,4 +186,129 @@ public class TuohengService {
|
||||||
|
|
||||||
return mapping;
|
return mapping;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步飞行状态到 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue