package com.tuoheng.old; 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.junit.jupiter.api.*; import org.springframework.util.Assert; import java.util.HashMap; import java.util.concurrent.*; import static org.junit.jupiter.api.Assertions.*; /** * DRC状态机测试 * 测试DRC模式的完整状态转换流程 */ @SpringBootTest @Slf4j @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class DrcStateMachineTest { @Autowired MachineCommandManager machineCommandManager; @Autowired MqttCallbackRegistry mqttCallbackRegistry; private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); private Boolean initState = false; private static final String SN = "SN9527"; @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 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 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 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 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 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()); } /** * 测试开仓命令 - 场景1:设备已在调试模式,直接开仓成功 * 流程: * 1. 检查调试模式(成功) * 2. 执行开仓命令 * 3. 收到开仓命令ACK * 4. 收到舱门状态变为OPENED */ @Test @Order(6) public void testOpenCoverWithDebugModeEnabled() throws ExecutionException, InterruptedException { log.info("=== 测试开仓命令 - 场景1:设备已在调试模式 ==="); CompletableFuture future = machineCommandManager.executeCommand(SN, CommandType.OPEN_COVER, new HashMap<>()); scheduler.schedule(() -> { try { // 1. 模拟设备已在调试模式的状态回调(100ms后) String response = "{\"debugMode\":\"ENABLED\"}"; mqttCallbackRegistry.handleMessage("dji/SN9527/state", response); log.info("发送调试模式状态: {}", response); Thread.sleep(50); // 2. 模拟开仓命令的ACK响应 response = "{\"cmd\":\"openCover\"}"; mqttCallbackRegistry.handleMessage("dji/SN9527/response", response); log.info("发送开仓命令ACK: {}", response); Thread.sleep(50); // 3. 模拟舱门状态变为OPENED response = "{\"coverState\":\"OPENED\"}"; mqttCallbackRegistry.handleMessage("dji/SN9527/state", response); log.info("发送舱门状态: {}", response); } catch (InterruptedException e) { e.printStackTrace(); } }, 100, TimeUnit.MILLISECONDS); CommandResult result = future.get(); assertTrue(result.isSuccess(), "开仓命令应该执行成功"); log.info("=== 测试通过:设备已在调试模式,开仓成功 ==="); } /** * 测试开仓命令 - 场景2:设备不在调试模式,先开启调试模式再开仓 * 流程: * 1. 检查调试模式(失败/超时) * 2. 开启调试模式 * 3. 收到开启调试模式ACK * 4. 执行开仓命令 * 5. 收到开仓命令ACK * 6. 收到舱门状态变为OPENED */ @Test @Order(7) public void testOpenCoverWithDebugModeDisabled() throws ExecutionException, InterruptedException { log.info("=== 测试开仓命令 - 场景2:设备不在调试模式 ==="); CompletableFuture future = machineCommandManager.executeCommand(SN, CommandType.OPEN_COVER, new HashMap<>()); scheduler.schedule(() -> { try { // 1. 不发送调试模式状态,让检查调试模式超时(等待3秒超时) log.info("等待检查调试模式超时..."); Thread.sleep(3500); // 等待超过3秒,让检查调试模式超时 // 2. 模拟开启调试模式命令的ACK响应 String response = "{\"cmd\":\"enableDebugMode\"}"; mqttCallbackRegistry.handleMessage("dji/SN9527/response", response); log.info("发送开启调试模式ACK: {}", response); Thread.sleep(50); // 3. 模拟开仓命令的ACK响应 response = "{\"cmd\":\"openCover\"}"; mqttCallbackRegistry.handleMessage("dji/SN9527/response", response); log.info("发送开仓命令ACK: {}", response); Thread.sleep(50); // 4. 模拟舱门状态变为OPENED response = "{\"coverState\":\"OPENED\"}"; mqttCallbackRegistry.handleMessage("dji/SN9527/state", response); log.info("发送舱门状态: {}", response); } catch (InterruptedException e) { e.printStackTrace(); } }, 100, TimeUnit.MILLISECONDS); CommandResult result = future.get(); assertTrue(result.isSuccess(), "开仓命令应该执行成功(先开启调试模式再开仓)"); log.info("=== 测试通过:设备不在调试模式,先开启调试模式再开仓成功 ==="); } }