修改框架内容

This commit is contained in:
孙小云 2025-12-18 13:22:34 +08:00
parent 425ba5eba4
commit e44347eec9
17 changed files with 190 additions and 187 deletions

View File

@ -52,7 +52,7 @@ public class MachineCommandManager {
*/
public void bindMachine(String sn, String vendorType) {
vendorRegistry.bindSnToVendor(sn, vendorType);
log.info("绑定设备到厂家: sn=, vendorType={}", sn, vendorType);
log.info("绑定设备到厂家: sn={}, vendorType={}", sn, vendorType);
}
/**

View File

@ -9,8 +9,6 @@ public enum CommandType {
*/
TAKE_OFF,
/**
* 返航
*/

View File

@ -26,7 +26,7 @@ public class Transaction {
/**
* 事务超时时间毫秒
*/
private long timeoutMs = 120000; // 默认2分钟
private long timeoutMs = 10000; // 默认10秒
public Transaction(String name, CommandType commandType) {
this.name = name;

View File

@ -1,5 +1,6 @@
package com.tuoheng.machine.instruction;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@ -38,7 +39,7 @@ public class CallbackConfig {
/**
* 超时时间毫秒
*/
private long timeoutMs = 30000;
private long timeoutMs = 3000;
/**
* 判断消息是否匹配
@ -60,8 +61,19 @@ public class CallbackConfig {
return null;
}
String[] parts = path.split("\\.");
// 如果 messageBody 是字符串尝试解析为 JSON
Object current = messageBody;
if (messageBody instanceof String) {
try {
ObjectMapper objectMapper = new ObjectMapper();
current = objectMapper.readValue((String) messageBody, Object.class);
} catch (Exception e) {
// 解析失败返回 null
return null;
}
}
String[] parts = path.split("\\.");
for (String part : parts) {
if (current == null) {

View File

@ -9,7 +9,6 @@ public enum AirportState {
*/
UNKNOWN,
/**
* 在线
*/

View File

@ -52,59 +52,13 @@ public class DjiVendorConfig implements VendorConfig {
switch (commandType) {
case TAKE_OFF:
return droneState == DroneState.ONLINE ;
return droneState == DroneState.ONLINE && airportState == AirportState.ONLINE;
case RETURN_HOME:
return droneState == DroneState.FLYING ;
case EMERGENCY_STOP:
// 飞行中返航中可以急停未处于急停状态
return (droneState == DroneState.FLYING || droneState == DroneState.RETURNING) &&
stopState != StopState.ENTERED;
case RESUME_FLIGHT:
// 急停状态可以继续飞行
return stopState == StopState.ENTERED;
case POINT_FLY:
// 飞行中到达目的地返航中可以指点飞行未急停
return (droneState == DroneState.FLYING ||
droneState == DroneState.ARRIVED ||
droneState == DroneState.RETURNING) &&
stopState != StopState.ENTERED;
case CANCEL_POINT:
// 飞行中可以取消指点
return droneState == DroneState.FLYING;
case START_MISSION:
// 未知状态或离线状态且未急停时可以开始航线任务
return (droneState == DroneState.UNKNOWN || droneState == DroneState.ONLINE) &&
stopState != StopState.ENTERED;
case OPEN_COVER:
// 舱门关闭状态可以打开
return coverState == CoverState.CLOSED;
case CLOSE_COVER:
// 舱门打开状态可以关闭
return coverState == CoverState.OPENED;
case ENTER_DEBUG_MODE:
// 在线状态且未处于调试模式时可以进入调试模式
return airportState == AirportState.ONLINE &&
debugModeState == DebugModeState.UNKNOWN;
case EXIT_DEBUG_MODE:
// 调试模式可以退出
return debugModeState == DebugModeState.ENTERED;
case REBOOT_AIRPORT:
// 在线状态可以重启
return airportState == AirportState.ONLINE;
return droneState == DroneState.FLYING || droneState == DroneState.ARRIVED;
default:
return false;
return true;
}
}
@ -117,7 +71,6 @@ public class DjiVendorConfig implements VendorConfig {
availableCommands.add(commandType);
}
}
return availableCommands;
}
@ -128,56 +81,9 @@ public class DjiVendorConfig implements VendorConfig {
// 起飞命令
Transaction takeOffTransaction = new Transaction("起飞", CommandType.TAKE_OFF)
.root(new DjiTakeOffInstruction())
.setTimeout(90000);
.setTimeout(10000);
transactionMap.put(CommandType.TAKE_OFF, takeOffTransaction);
// 返航命令
Transaction returnHomeTransaction = new Transaction("返航", CommandType.RETURN_HOME)
.root(new DjiReturnHomeInstruction())
.setTimeout(120000);
transactionMap.put(CommandType.RETURN_HOME, returnHomeTransaction);
// 急停命令
Transaction emergencyStopTransaction = new Transaction("急停", CommandType.EMERGENCY_STOP)
.root(new DjiEmergencyStopInstruction())
.setTimeout(30000);
transactionMap.put(CommandType.EMERGENCY_STOP, emergencyStopTransaction);
// 继续飞行命令
Transaction resumeFlightTransaction = new Transaction("继续飞行", CommandType.RESUME_FLIGHT)
.root(new DjiResumeFlightInstruction())
.setTimeout(60000);
transactionMap.put(CommandType.RESUME_FLIGHT, resumeFlightTransaction);
// 指点飞行命令
Transaction pointFlyTransaction = new Transaction("指点飞行", CommandType.POINT_FLY)
.root(new DjiPointFlyInstruction())
.setTimeout(90000);
transactionMap.put(CommandType.POINT_FLY, pointFlyTransaction);
// 取消指点命令
Transaction cancelPointTransaction = new Transaction("取消指点", CommandType.CANCEL_POINT)
.root(new DjiCancelPointInstruction())
.setTimeout(30000);
transactionMap.put(CommandType.CANCEL_POINT, cancelPointTransaction);
// 开始航线任务命令
Transaction startMissionTransaction = new Transaction("开始航线任务", CommandType.START_MISSION)
.root(new DjiStartMissionInstruction())
.setTimeout(120000);
transactionMap.put(CommandType.START_MISSION, startMissionTransaction);
// 打开舱门命令
Transaction openCoverTransaction = new Transaction("打开舱门", CommandType.OPEN_COVER)
.root(new DjiOpenCoverInstruction())
.setTimeout(60000);
transactionMap.put(CommandType.OPEN_COVER, openCoverTransaction);
// 关闭舱门命令
Transaction closeCoverTransaction = new Transaction("关闭舱门", CommandType.CLOSE_COVER)
.root(new DjiCloseCoverInstruction())
.setTimeout(60000);
transactionMap.put(CommandType.CLOSE_COVER, closeCoverTransaction);
log.info("大疆厂家配置初始化完成,共配置{}个命令", transactionMap.size());
}

View File

@ -34,7 +34,6 @@ public class DjiCancelPointInstruction extends AbstractInstruction {
.topic("dji/" + sn + "/response")
.fieldPath("cmd")
.expectedValue("cancelPoint")
.canShortCircuit(false)
.timeoutMs(10000)
.build();
}
@ -47,7 +46,6 @@ public class DjiCancelPointInstruction extends AbstractInstruction {
.topic("dji/" + sn + "/state")
.fieldPath("droneState")
.expectedValue("POINT_CANCELLED")
.canShortCircuit(false)
.timeoutMs(30000)
.build();
}

View File

@ -34,7 +34,6 @@ public class DjiCloseCoverInstruction extends AbstractInstruction {
.topic("dji/" + sn + "/response")
.fieldPath("cmd")
.expectedValue("closeCover")
.canShortCircuit(false)
.timeoutMs(10000)
.build();
}
@ -47,7 +46,6 @@ public class DjiCloseCoverInstruction extends AbstractInstruction {
.topic("dji/" + sn + "/state")
.fieldPath("coverState")
.expectedValue("CLOSED")
.canShortCircuit(false)
.timeoutMs(60000)
.build();
}

View File

@ -34,7 +34,6 @@ public class DjiEmergencyStopInstruction extends AbstractInstruction {
.topic("dji/" + sn + "/response")
.fieldPath("cmd")
.expectedValue("emergencyStop")
.canShortCircuit(false)
.timeoutMs(5000)
.build();
}
@ -50,7 +49,6 @@ public class DjiEmergencyStopInstruction extends AbstractInstruction {
// 急停状态可能是 EMERGENCY_STOP RETURN_EMERGENCY_STOP
return "EMERGENCY_STOP".equals(state) || "RETURN_EMERGENCY_STOP".equals(state);
})
.canShortCircuit(false)
.timeoutMs(30000)
.build();
}

View File

@ -35,7 +35,6 @@ public class DjiLandInstruction extends AbstractInstruction {
.topic("dji/" + sn + "/response")
.fieldPath("cmd")
.expectedValue("land")
.canShortCircuit(false)
.timeoutMs(10000)
.build();
}
@ -48,7 +47,6 @@ public class DjiLandInstruction extends AbstractInstruction {
.topic("dji/" + sn + "/state")
.fieldPath("droneState")
.expectedValue("PREPARING")
.canShortCircuit(false)
.timeoutMs(60000)
.build();
}

View File

@ -34,7 +34,6 @@ public class DjiOpenCoverInstruction extends AbstractInstruction {
.topic("dji/" + sn + "/response")
.fieldPath("cmd")
.expectedValue("openCover")
.canShortCircuit(false)
.timeoutMs(10000)
.build();
}
@ -47,7 +46,6 @@ public class DjiOpenCoverInstruction extends AbstractInstruction {
.topic("dji/" + sn + "/state")
.fieldPath("coverState")
.expectedValue("OPENED")
.canShortCircuit(false)
.timeoutMs(60000)
.build();
}

View File

@ -39,7 +39,6 @@ public class DjiPointFlyInstruction extends AbstractInstruction {
.topic("dji/" + sn + "/response")
.fieldPath("cmd")
.expectedValue("pointFly")
.canShortCircuit(false)
.timeoutMs(10000)
.build();
}
@ -52,7 +51,6 @@ public class DjiPointFlyInstruction extends AbstractInstruction {
.topic("dji/" + sn + "/state")
.fieldPath("droneState")
.expectedValue("POINT_FLYING")
.canShortCircuit(false)
.timeoutMs(30000)
.build();
}

View File

@ -34,7 +34,6 @@ public class DjiResumeFlightInstruction extends AbstractInstruction {
.topic("dji/" + sn + "/response")
.fieldPath("cmd")
.expectedValue("resumeFlight")
.canShortCircuit(false)
.timeoutMs(10000)
.build();
}
@ -50,7 +49,6 @@ public class DjiResumeFlightInstruction extends AbstractInstruction {
// 继续飞行后可能变为 FLYING RETURNING
return "FLYING".equals(state) || "RETURNING".equals(state);
})
.canShortCircuit(false)
.timeoutMs(30000)
.build();
}

View File

@ -34,7 +34,6 @@ public class DjiReturnHomeInstruction extends AbstractInstruction {
.topic("dji/" + sn + "/response")
.fieldPath("cmd")
.expectedValue("returnHome")
.canShortCircuit(false)
.timeoutMs(10000)
.build();
}
@ -47,7 +46,6 @@ public class DjiReturnHomeInstruction extends AbstractInstruction {
.topic("dji/" + sn + "/state")
.fieldPath("droneState")
.expectedValue("RETURNING")
.canShortCircuit(false)
.timeoutMs(60000)
.build();
}

View File

@ -36,7 +36,6 @@ public class DjiStartMissionInstruction extends AbstractInstruction {
.topic("dji/" + sn + "/response")
.fieldPath("cmd")
.expectedValue("startMission")
.canShortCircuit(false)
.timeoutMs(10000)
.build();
}
@ -49,7 +48,6 @@ public class DjiStartMissionInstruction extends AbstractInstruction {
.topic("dji/" + sn + "/state")
.fieldPath("droneState")
.expectedValue("FLYING")
.canShortCircuit(false)
.timeoutMs(60000)
.build();
}

View File

@ -32,20 +32,21 @@ public class DjiTakeOffInstruction extends AbstractInstruction {
@Override
public CallbackConfig getMethodCallbackConfig(InstructionContext context) {
// return null;
String sn = context.getSn();
// 方法回调等待起飞指令的ACK响应
return CallbackConfig.builder()
.topic("dji/" + sn + "/response")
.fieldPath("cmd")
.fieldPath("data.result")
.expectedValue("takeoff")
.canShortCircuit(false) // 不可短路必须等待响应
.timeoutMs(10000) // 10秒超时
.build();
}
@Override
public CallbackConfig getStateCallbackConfig(InstructionContext context) {
// return null;
String sn = context.getSn();
// 状态回调等待无人机状态变为飞行中
@ -53,13 +54,12 @@ public class DjiTakeOffInstruction extends AbstractInstruction {
.topic("dji/" + sn + "/state")
.fieldPath("droneState")
.expectedValue("FLYING")
.canShortCircuit(false) // 不可短路必须等待状态变化
.timeoutMs(60000) // 60秒超时
.timeoutMs(10000) // 10秒超时
.build();
}
@Override
public long getTimeoutMs() {
return 90000; // 90秒总超时
return 10000; // 10秒总超时
}
}

View File

@ -1,13 +1,25 @@
package com.tuoheng.old;
import com.tuoheng.old.events.DrcEvent;
import com.tuoheng.old.service.DrcMachineService;
import com.tuoheng.old.status.DrcState;
import com.tuoheng.machine.MachineCommandManager;
import com.tuoheng.machine.command.CommandResult;
import com.tuoheng.machine.command.CommandType;
import com.tuoheng.machine.mqtt.MqttCallbackRegistry;
import com.tuoheng.machine.state.*;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.statemachine.StateMachine;
import org.junit.jupiter.api.*;
import org.springframework.util.Assert;
import java.util.HashMap;
import java.util.concurrent.*;
import static org.junit.jupiter.api.Assertions.*;
/**
* DRC状态机测试
@ -15,71 +27,165 @@ import org.springframework.statemachine.StateMachine;
*/
@SpringBootTest
@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class DrcStateMachineTest {
@Autowired
DrcMachineService drcMachineService;
MachineCommandManager machineCommandManager;
@Test
public void testDrcMachineService(){
@Autowired
MqttCallbackRegistry mqttCallbackRegistry;
String sn = "airport-001";
/**
* 不存在的会报错,需要在 MachinePlatTypeRepository 里面定义有这个机场编号
*/
try {
StateMachine<DrcState, DrcEvent> stateMachine =
drcMachineService.getOrCreateStateMachine("airport-001--2");
}catch (RuntimeException runtimeException){}
private static final ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(2);
StateMachine<DrcState, DrcEvent> stateMachine = drcMachineService.getStateMachine("airport-001");
drcMachineService.getOrCreateStateMachine("airport-001");
stateMachine = drcMachineService.getStateMachine("airport-001");
/**
* 打印一下当前的状态
*/
log.debug(drcMachineService.getCurrentStates("airport-001"));
private Boolean initState = false;
/**
* 从UnKnown状态可以走到任意状态
*/
drcMachineService.sendEvent(sn,DrcEvent.ENTERED);
private static final String SN = "SN9527";
/**
* ENTERED无法进入ENTERING DrcMachineConfig 里面配置的
*/
log.debug(String.valueOf(drcMachineService.sendEvent(sn,DrcEvent.ENTERING)));
/**
* 现在是 ENTERED,但是你还想进入ENTERED ,这个是不可以的
*/
log.debug(String.valueOf(drcMachineService.sendEvent(sn,DrcEvent.ENTERED)));
/**
* 打印一下当前的状态
*/
log.debug(drcMachineService.getCurrentStates("airport-001"));
/**
* 变成退出中;这个时候需要在
* DjiCanExitGuard 判断是否可以执行
* DjiExitAction 执行远程方法,远程方法本身执行成功则不抛出异常,否则抛出异常
*/
log.debug(String.valueOf(drcMachineService.sendEvent(sn,DrcEvent.EXITING)));
/**
* 打印一下当前的状态
* 修改 stateContext 里面的实现,就可以跳过某些状态
*/
log.debug(drcMachineService.getCurrentStates("airport-001"));
;
// log.debug(String.valueOf(drcMachineService.sendEvent(sn,DrcEvent.EXITED)));
//
// log.debug(drcMachineService.getCurrentStates("airport-001"));
@BeforeEach
public void init(){
if(initState){
return;
}
initState = true;
machineCommandManager.bindMachine(SN,"DJI");
}
@Test
@Order(1)
public void checkInitState(){
MachineStates machineStates = machineCommandManager.getMachineStates(SN);
assertNotNull(machineStates);
assertEquals(AirportState.UNKNOWN, machineStates.getAirportState());
assertEquals(DrcState.UNKNOWN, machineStates.getDrcState());
assertEquals(CoverState.UNKNOWN, machineStates.getCoverState());
assertEquals(DroneState.UNKNOWN, machineStates.getDroneState());
assertEquals(StopState.UNKNOWN, machineStates.getStopState());
}
/**
* 非在线状态下不可起飞
* @throws ExecutionException
* @throws InterruptedException
*/
@Test
@Order(2)
public void checkTakeOffCommand() throws ExecutionException, InterruptedException {
CompletableFuture<CommandResult> future =
machineCommandManager.executeCommand(SN, CommandType.TAKE_OFF,new HashMap<>());
assertFalse(future.get().isSuccess());
}
@Test
@Order(3)
public void setState() {
MachineStates machineStates = new MachineStates();
machineStates.setAirportState(AirportState.ONLINE);
machineStates.setDroneState(DroneState.ONLINE);
machineCommandManager.updateMachineStates(SN, machineStates);
machineStates = machineCommandManager.getMachineStates(SN);
assertNotNull(machineStates);
assertEquals(AirportState.ONLINE, machineStates.getAirportState());
assertEquals(DroneState.ONLINE, machineStates.getDroneState());
assertEquals(DrcState.UNKNOWN, machineStates.getDrcState());
assertEquals(CoverState.UNKNOWN, machineStates.getCoverState());
assertEquals(StopState.UNKNOWN, machineStates.getStopState());
}
// @Test
// @Order(4)
// public void checkTakeOffOverTime1() throws ExecutionException, InterruptedException, TimeoutException {
// /**
// * 指令执行超时;因为缺乏指令成功的回调
// */
// CompletableFuture<CommandResult> future =
// machineCommandManager.executeCommand(SN, CommandType.TAKE_OFF,new HashMap<>());
// assertFalse(future.get().isSuccess());
//
// }
/**
* 指令执行成功,因为mqttCallbackRegistry模拟了发送回调
* 需要将 DjiTakeOffInstruction getStateCallbackConfig 中直接返回null
*/
// @Test
// @Order(5)
// public void checkTakeOffOverTime2() throws ExecutionException, InterruptedException, TimeoutException {
//
// CompletableFuture<CommandResult> future =
// machineCommandManager.executeCommand(SN, CommandType.TAKE_OFF,new HashMap<>());
//
// scheduler.schedule(new Runnable() {
// @Override
// public void run() {
// String response = "{\"data\":{\"result\":\"takeoff\"}}";
// mqttCallbackRegistry.handleMessage("dji/SN9527/response",response);
// }
// },100,TimeUnit.MILLISECONDS);
//
// assertTrue(future.get().isSuccess());
//
// }
/**
* 指令执行失败
* 需要将 DjiTakeOffInstruction getStateCallbackConfig 中的注释放开
*/
// @Test
// @Order(5)
// public void checkTakeOffOverTime2() throws ExecutionException, InterruptedException, TimeoutException {
//
// CompletableFuture<CommandResult> future =
// machineCommandManager.executeCommand(SN, CommandType.TAKE_OFF,new HashMap<>());
//
// scheduler.schedule(new Runnable() {
// @Override
// public void run() {
// String response = "{\"data\":{\"result\":\"takeoff\"}}";
// mqttCallbackRegistry.handleMessage("dji/SN9527/response",response);
// }
// },100,TimeUnit.MILLISECONDS);
//
// assertFalse(future.get().isSuccess());
// }
/**
* 需要将 DjiTakeOffInstruction getStateCallbackConfig 中的注释放开
* @throws ExecutionException
* @throws InterruptedException
* @throws TimeoutException
*/
@Test
@Order(5)
public void checkTakeOffOverTime2() throws ExecutionException, InterruptedException, TimeoutException {
CompletableFuture<CommandResult> future =
machineCommandManager.executeCommand(SN, CommandType.TAKE_OFF,new HashMap<>());
scheduler.schedule(new Runnable() {
@Override
public void run() {
String response = "{\"data\":{\"result\":\"takeoff\"}}";
mqttCallbackRegistry.handleMessage("dji/SN9527/response",response);
// 添加延迟等待状态回调监听器注册
try {
Thread.sleep(50); // 等待50ms
} catch (InterruptedException e) {
e.printStackTrace();
}
response = "{\"droneState\":\"FLYING\"}";
mqttCallbackRegistry.handleMessage("dji/SN9527/state",response);
}
},100,TimeUnit.MILLISECONDS);
assertTrue(future.get().isSuccess());
}
}