diff --git a/src/main/java/com/tuoheng/status/machine/action/drone/ArriveAction.java b/src/main/java/com/tuoheng/status/machine/action/drone/ArriveAction.java new file mode 100644 index 0000000..9e64e81 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/action/drone/ArriveAction.java @@ -0,0 +1,11 @@ +package com.tuoheng.status.machine.action.drone; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.platform.strategy.PlatformAction; +import com.tuoheng.status.machine.status.DroneState; + +/** + * Base action for drone arrive at destination handling; platform implementations extend this. + */ +public abstract class ArriveAction implements PlatformAction { +} \ No newline at end of file diff --git a/src/main/java/com/tuoheng/status/machine/action/drone/CancelPointAction.java b/src/main/java/com/tuoheng/status/machine/action/drone/CancelPointAction.java new file mode 100644 index 0000000..c7a07e7 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/action/drone/CancelPointAction.java @@ -0,0 +1,11 @@ +package com.tuoheng.status.machine.action.drone; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.platform.strategy.PlatformAction; +import com.tuoheng.status.machine.status.DroneState; + +/** + * Base action for drone cancel point operation handling; platform implementations extend this. + */ +public abstract class CancelPointAction implements PlatformAction { +} \ No newline at end of file diff --git a/src/main/java/com/tuoheng/status/machine/action/drone/EmergencyStopAction.java b/src/main/java/com/tuoheng/status/machine/action/drone/EmergencyStopAction.java new file mode 100644 index 0000000..45c74bd --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/action/drone/EmergencyStopAction.java @@ -0,0 +1,11 @@ +package com.tuoheng.status.machine.action.drone; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.platform.strategy.PlatformAction; +import com.tuoheng.status.machine.status.DroneState; + +/** + * Base action for drone emergency stop handling; platform implementations extend this. + */ +public abstract class EmergencyStopAction implements PlatformAction { +} \ No newline at end of file diff --git a/src/main/java/com/tuoheng/status/machine/action/drone/OfflineAction.java b/src/main/java/com/tuoheng/status/machine/action/drone/OfflineAction.java new file mode 100644 index 0000000..8fd96e5 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/action/drone/OfflineAction.java @@ -0,0 +1,11 @@ +package com.tuoheng.status.machine.action.drone; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.platform.strategy.PlatformAction; +import com.tuoheng.status.machine.status.DroneState; + +/** + * Base action for drone offline handling; platform implementations extend this. + */ +public abstract class OfflineAction implements PlatformAction { +} \ No newline at end of file diff --git a/src/main/java/com/tuoheng/status/machine/action/drone/PointFlyingCompletedAction.java b/src/main/java/com/tuoheng/status/machine/action/drone/PointFlyingCompletedAction.java new file mode 100644 index 0000000..5f76f45 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/action/drone/PointFlyingCompletedAction.java @@ -0,0 +1,11 @@ +package com.tuoheng.status.machine.action.drone; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.platform.strategy.PlatformAction; +import com.tuoheng.status.machine.status.DroneState; + +/** + * Base action for drone point flying completed handling; platform implementations extend this. + */ +public abstract class PointFlyingCompletedAction implements PlatformAction { +} \ No newline at end of file diff --git a/src/main/java/com/tuoheng/status/machine/action/drone/PointPrepareCompletedAction.java b/src/main/java/com/tuoheng/status/machine/action/drone/PointPrepareCompletedAction.java new file mode 100644 index 0000000..7362245 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/action/drone/PointPrepareCompletedAction.java @@ -0,0 +1,11 @@ +package com.tuoheng.status.machine.action.drone; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.platform.strategy.PlatformAction; +import com.tuoheng.status.machine.status.DroneState; + +/** + * Base action for drone point prepare completed handling; platform implementations extend this. + */ +public abstract class PointPrepareCompletedAction implements PlatformAction { +} \ No newline at end of file diff --git a/src/main/java/com/tuoheng/status/machine/action/drone/PointToFlyingAction.java b/src/main/java/com/tuoheng/status/machine/action/drone/PointToFlyingAction.java new file mode 100644 index 0000000..9e99ab2 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/action/drone/PointToFlyingAction.java @@ -0,0 +1,11 @@ +package com.tuoheng.status.machine.action.drone; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.platform.strategy.PlatformAction; +import com.tuoheng.status.machine.status.DroneState; + +/** + * Base action for drone point to flying handling; platform implementations extend this. + */ +public abstract class PointToFlyingAction implements PlatformAction { +} \ No newline at end of file diff --git a/src/main/java/com/tuoheng/status/machine/action/drone/PointToReturnAction.java b/src/main/java/com/tuoheng/status/machine/action/drone/PointToReturnAction.java new file mode 100644 index 0000000..73bdf91 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/action/drone/PointToReturnAction.java @@ -0,0 +1,11 @@ +package com.tuoheng.status.machine.action.drone; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.platform.strategy.PlatformAction; +import com.tuoheng.status.machine.status.DroneState; + +/** + * Base action for drone point to return handling; platform implementations extend this. + */ +public abstract class PointToReturnAction implements PlatformAction { +} \ No newline at end of file diff --git a/src/main/java/com/tuoheng/status/machine/action/drone/PrepareCompletedAction.java b/src/main/java/com/tuoheng/status/machine/action/drone/PrepareCompletedAction.java new file mode 100644 index 0000000..38b9eb6 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/action/drone/PrepareCompletedAction.java @@ -0,0 +1,11 @@ +package com.tuoheng.status.machine.action.drone; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.platform.strategy.PlatformAction; +import com.tuoheng.status.machine.status.DroneState; + +/** + * Base action for drone prepare completed handling; platform implementations extend this. + */ +public abstract class PrepareCompletedAction implements PlatformAction { +} \ No newline at end of file diff --git a/src/main/java/com/tuoheng/status/machine/action/drone/ResumeFlyingAction.java b/src/main/java/com/tuoheng/status/machine/action/drone/ResumeFlyingAction.java new file mode 100644 index 0000000..2674bdd --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/action/drone/ResumeFlyingAction.java @@ -0,0 +1,11 @@ +package com.tuoheng.status.machine.action.drone; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.platform.strategy.PlatformAction; +import com.tuoheng.status.machine.status.DroneState; + +/** + * Base action for drone resume flying handling; platform implementations extend this. + */ +public abstract class ResumeFlyingAction implements PlatformAction { +} \ No newline at end of file diff --git a/src/main/java/com/tuoheng/status/machine/action/drone/ResumeReturnAction.java b/src/main/java/com/tuoheng/status/machine/action/drone/ResumeReturnAction.java new file mode 100644 index 0000000..8974ea0 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/action/drone/ResumeReturnAction.java @@ -0,0 +1,11 @@ +package com.tuoheng.status.machine.action.drone; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.platform.strategy.PlatformAction; +import com.tuoheng.status.machine.status.DroneState; + +/** + * Base action for drone resume return handling; platform implementations extend this. + */ +public abstract class ResumeReturnAction implements PlatformAction { +} \ No newline at end of file diff --git a/src/main/java/com/tuoheng/status/machine/action/drone/ReturnCompletedAction.java b/src/main/java/com/tuoheng/status/machine/action/drone/ReturnCompletedAction.java new file mode 100644 index 0000000..218ad50 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/action/drone/ReturnCompletedAction.java @@ -0,0 +1,11 @@ +package com.tuoheng.status.machine.action.drone; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.platform.strategy.PlatformAction; +import com.tuoheng.status.machine.status.DroneState; + +/** + * Base action for drone return completed handling; platform implementations extend this. + */ +public abstract class ReturnCompletedAction implements PlatformAction { +} \ No newline at end of file diff --git a/src/main/java/com/tuoheng/status/machine/action/drone/ReturnEmergencyStopAction.java b/src/main/java/com/tuoheng/status/machine/action/drone/ReturnEmergencyStopAction.java new file mode 100644 index 0000000..76b11ba --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/action/drone/ReturnEmergencyStopAction.java @@ -0,0 +1,11 @@ +package com.tuoheng.status.machine.action.drone; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.platform.strategy.PlatformAction; +import com.tuoheng.status.machine.status.DroneState; + +/** + * Base action for drone return emergency stop handling; platform implementations extend this. + */ +public abstract class ReturnEmergencyStopAction implements PlatformAction { +} \ No newline at end of file diff --git a/src/main/java/com/tuoheng/status/machine/action/drone/StartFlyingAction.java b/src/main/java/com/tuoheng/status/machine/action/drone/StartFlyingAction.java new file mode 100644 index 0000000..574d957 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/action/drone/StartFlyingAction.java @@ -0,0 +1,11 @@ +package com.tuoheng.status.machine.action.drone; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.platform.strategy.PlatformAction; +import com.tuoheng.status.machine.status.DroneState; + +/** + * Base action for drone start flying handling; platform implementations extend this. + */ +public abstract class StartFlyingAction implements PlatformAction { +} \ No newline at end of file diff --git a/src/main/java/com/tuoheng/status/machine/action/drone/StartPointingAction.java b/src/main/java/com/tuoheng/status/machine/action/drone/StartPointingAction.java new file mode 100644 index 0000000..526473e --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/action/drone/StartPointingAction.java @@ -0,0 +1,11 @@ +package com.tuoheng.status.machine.action.drone; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.platform.strategy.PlatformAction; +import com.tuoheng.status.machine.status.DroneState; + +/** + * Base action for drone start pointing operation handling; platform implementations extend this. + */ +public abstract class StartPointingAction implements PlatformAction { +} \ No newline at end of file diff --git a/src/main/java/com/tuoheng/status/machine/action/drone/StartPrepareAction.java b/src/main/java/com/tuoheng/status/machine/action/drone/StartPrepareAction.java new file mode 100644 index 0000000..5ff9102 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/action/drone/StartPrepareAction.java @@ -0,0 +1,11 @@ +package com.tuoheng.status.machine.action.drone; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.platform.strategy.PlatformAction; +import com.tuoheng.status.machine.status.DroneState; + +/** + * Base action for drone start prepare handling; platform implementations extend this. + */ +public abstract class StartPrepareAction implements PlatformAction { +} \ No newline at end of file diff --git a/src/main/java/com/tuoheng/status/machine/action/drone/StartReturnAction.java b/src/main/java/com/tuoheng/status/machine/action/drone/StartReturnAction.java new file mode 100644 index 0000000..eaf723a --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/action/drone/StartReturnAction.java @@ -0,0 +1,11 @@ +package com.tuoheng.status.machine.action.drone; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.platform.strategy.PlatformAction; +import com.tuoheng.status.machine.status.DroneState; + +/** + * Base action for drone start return handling; platform implementations extend this. + */ +public abstract class StartReturnAction implements PlatformAction { +} \ No newline at end of file diff --git a/src/main/java/com/tuoheng/status/machine/config/DroneMachineConfig.java b/src/main/java/com/tuoheng/status/machine/config/DroneMachineConfig.java new file mode 100644 index 0000000..e608c60 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/config/DroneMachineConfig.java @@ -0,0 +1,393 @@ +package com.tuoheng.status.machine.config; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.platform.factory.PlatformStrategyFactory; +import com.tuoheng.status.machine.platform.strategy.DronePlatformStrategy; +import com.tuoheng.status.machine.status.DroneState; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.config.StateMachineBuilder; +import org.springframework.statemachine.config.StateMachineFactory; + +import java.util.EnumSet; +import java.util.UUID; + +/** + * 无人机状态机配置(多平台支持版本) + * 通过PlatformStrategyFactory动态获取平台特定的Guard、Action和Listener + */ +@Configuration +public class DroneMachineConfig { + + @Autowired + private PlatformStrategyFactory platformStrategyFactory; + + @Bean(name = "droneStateMachineFactory") + public StateMachineFactory droneStateMachineFactory() throws Exception { + return new StateMachineFactory() { + @Override + public StateMachine getStateMachine() { + return null; + } + + @Override + public StateMachine getStateMachine(String machineId) { + try { + // 根据无人机SN获取平台策略 + DronePlatformStrategy strategy = platformStrategyFactory.getDroneStrategy(machineId); + + StateMachineBuilder.Builder builder = StateMachineBuilder.builder(); + configureStateMachine(builder, strategy); + configureStates(builder); + configureTransitions(builder, strategy); + + StateMachine stateMachine = builder.build(); + stateMachine.getExtendedState().getVariables().put("machineId", machineId); + return stateMachine; + } catch (Exception e) { + throw new RuntimeException("Failed to create drone state machine for: " + machineId, e); + } + } + + @Override + public StateMachine getStateMachine(UUID uuid) { + return null; + } + }; + } + + private void configureStateMachine( + StateMachineBuilder.Builder builder, + DronePlatformStrategy strategy) throws Exception { + builder.configureConfiguration() + .withConfiguration() + .autoStartup(true) + .listener(strategy.getListener()); + } + + private void configureStates(StateMachineBuilder.Builder builder) throws Exception { + builder.configureStates() + .withStates() + .initial(DroneState.UNKNOWN) + .states(EnumSet.of( + DroneState.UNKNOWN, + DroneState.OFFLINE, + DroneState.PREPARING, + DroneState.FLYING_PARENT, + DroneState.RETURNING_PARENT, + DroneState.POINTING_PARENT + )) + // 飞行阶段子状态 + .and() + .withStates() + .parent(DroneState.FLYING_PARENT) + .initial(DroneState.FLYING) + .states(EnumSet.of( + DroneState.FLYING, + DroneState.EMERGENCY_STOP, + DroneState.ARRIVED + )) + // 返航阶段子状态 + .and() + .withStates() + .parent(DroneState.RETURNING_PARENT) + .initial(DroneState.RETURNING) + .states(EnumSet.of( + DroneState.RETURNING, + DroneState.RETURN_EMERGENCY_STOP, + DroneState.RETURN_COMPLETED + )) + // 指点操作子状态 + .and() + .withStates() + .parent(DroneState.POINTING_PARENT) + .initial(DroneState.POINT_PREPARING) + .states(EnumSet.of( + DroneState.POINT_PREPARING, + DroneState.POINT_FLYING, + DroneState.POINT_COMPLETED, + DroneState.POINT_CANCELLED + )); + } + + private void configureTransitions( + StateMachineBuilder.Builder builder, + DronePlatformStrategy strategy) throws Exception { + builder.configureTransitions() + // ========== 从 UNKNOWN 到所有状态的转换(服务器重启后状态同步) ========== + // UNKNOWN -> OFFLINE + .withExternal() + .source(DroneState.UNKNOWN) + .target(DroneState.OFFLINE) + .event(DroneEvent.DRONE_OFFLINE) + .and() + + // UNKNOWN -> PREPARING + .withExternal() + .source(DroneState.UNKNOWN) + .target(DroneState.PREPARING) + .event(DroneEvent.START_PREPARE) + .and() + + // UNKNOWN -> FLYING_PARENT + .withExternal() + .source(DroneState.UNKNOWN) + .target(DroneState.FLYING_PARENT) + .event(DroneEvent.START_FLYING) + .and() + + // UNKNOWN -> RETURNING_PARENT + .withExternal() + .source(DroneState.UNKNOWN) + .target(DroneState.RETURNING_PARENT) + .event(DroneEvent.START_RETURN) + .and() + + // ========== 基本状态转换 ========== + // OFFLINE -> PREPARING + .withExternal() + .source(DroneState.OFFLINE) + .target(DroneState.PREPARING) + .event(DroneEvent.START_PREPARE) + .action(strategy.getStartPrepareAction()) + .and() + + // PREPARING -> FLYING_PARENT + .withExternal() + .source(DroneState.PREPARING) + .target(DroneState.FLYING_PARENT) + .event(DroneEvent.PREPARE_COMPLETED) + .action(strategy.getPrepareCompletedAction()) + .and() + + // ========== 飞行阶段内部转换 ========== + // FLYING -> EMERGENCY_STOP + .withExternal() + .source(DroneState.FLYING) + .target(DroneState.EMERGENCY_STOP) + .event(DroneEvent.EMERGENCY_STOP) + .action(strategy.getEmergencyStopAction()) + .and() + + // EMERGENCY_STOP -> FLYING (恢复飞行) + .withExternal() + .source(DroneState.EMERGENCY_STOP) + .target(DroneState.FLYING) + .event(DroneEvent.RESUME_FLYING) + .action(strategy.getResumeFlyingAction()) + .and() + + // FLYING -> ARRIVED + .withExternal() + .source(DroneState.FLYING) + .target(DroneState.ARRIVED) + .event(DroneEvent.ARRIVE) + .action(strategy.getArriveAction()) + .and() + + // 注意:EMERGENCY_STOP 不能直接到 ARRIVED(根据注释限制) + + // ========== 飞行阶段 -> 指点操作 ========== + // FLYING -> POINTING_PARENT + .withExternal() + .source(DroneState.FLYING) + .target(DroneState.POINTING_PARENT) + .event(DroneEvent.START_POINTING) + .action(strategy.getStartPointingAction()) + .guard(strategy.getCanPointGuard()) + .and() + + // EMERGENCY_STOP -> POINTING_PARENT + .withExternal() + .source(DroneState.EMERGENCY_STOP) + .target(DroneState.POINTING_PARENT) + .event(DroneEvent.START_POINTING) + .action(strategy.getStartPointingAction()) + .guard(strategy.getCanPointGuard()) + .and() + + // ARRIVED -> POINTING_PARENT + .withExternal() + .source(DroneState.ARRIVED) + .target(DroneState.POINTING_PARENT) + .event(DroneEvent.START_POINTING) + .action(strategy.getStartPointingAction()) + .guard(strategy.getCanPointGuard()) + .and() + + // ========== 飞行阶段 -> 返航 ========== + // FLYING -> RETURNING_PARENT + .withExternal() + .source(DroneState.FLYING) + .target(DroneState.RETURNING_PARENT) + .event(DroneEvent.START_RETURN) + .action(strategy.getStartReturnAction()) + .and() + + // EMERGENCY_STOP -> RETURNING_PARENT + .withExternal() + .source(DroneState.EMERGENCY_STOP) + .target(DroneState.RETURNING_PARENT) + .event(DroneEvent.START_RETURN) + .action(strategy.getStartReturnAction()) + .and() + + // ARRIVED -> RETURNING_PARENT + .withExternal() + .source(DroneState.ARRIVED) + .target(DroneState.RETURNING_PARENT) + .event(DroneEvent.START_RETURN) + .action(strategy.getStartReturnAction()) + .and() + + // ========== 返航阶段内部转换 ========== + // RETURNING -> RETURN_EMERGENCY_STOP + .withExternal() + .source(DroneState.RETURNING) + .target(DroneState.RETURN_EMERGENCY_STOP) + .event(DroneEvent.RETURN_EMERGENCY_STOP) + .action(strategy.getReturnEmergencyStopAction()) + .and() + + // RETURN_EMERGENCY_STOP -> RETURNING (恢复返航) + .withExternal() + .source(DroneState.RETURN_EMERGENCY_STOP) + .target(DroneState.RETURNING) + .event(DroneEvent.RESUME_RETURN) + .action(strategy.getResumeReturnAction()) + .and() + + // RETURNING -> RETURN_COMPLETED + .withExternal() + .source(DroneState.RETURNING) + .target(DroneState.RETURN_COMPLETED) + .event(DroneEvent.RETURN_COMPLETED) + .action(strategy.getReturnCompletedAction()) + .and() + + // 注意:返航中不能指点(根据注释限制) + // 注意:RETURN_COMPLETED 不能回退到 RETURNING 或 RETURN_EMERGENCY_STOP(根据注释限制) + + // ========== 返航急停 -> 指点操作 ========== + // RETURN_EMERGENCY_STOP -> POINTING_PARENT (一键起飞和航线飞行都可以指点) + .withExternal() + .source(DroneState.RETURN_EMERGENCY_STOP) + .target(DroneState.POINTING_PARENT) + .event(DroneEvent.START_POINTING) + .action(strategy.getStartPointingAction()) + .guard(strategy.getCanPointGuard()) + .and() + + // ========== 指点操作内部转换 ========== + // POINT_PREPARING -> POINT_FLYING + .withExternal() + .source(DroneState.POINT_PREPARING) + .target(DroneState.POINT_FLYING) + .event(DroneEvent.POINT_PREPARE_COMPLETED) + .action(strategy.getPointPrepareCompletedAction()) + .and() + + // POINT_FLYING -> POINT_COMPLETED + .withExternal() + .source(DroneState.POINT_FLYING) + .target(DroneState.POINT_COMPLETED) + .event(DroneEvent.POINT_FLYING_COMPLETED) + .action(strategy.getPointFlyingCompletedAction()) + .and() + + // 任何指点阶段 -> POINT_CANCELLED + .withExternal() + .source(DroneState.POINT_PREPARING) + .target(DroneState.POINT_CANCELLED) + .event(DroneEvent.CANCEL_POINT) + .action(strategy.getCancelPointAction()) + .and() + + .withExternal() + .source(DroneState.POINT_FLYING) + .target(DroneState.POINT_CANCELLED) + .event(DroneEvent.CANCEL_POINT) + .action(strategy.getCancelPointAction()) + .and() + + .withExternal() + .source(DroneState.POINT_COMPLETED) + .target(DroneState.POINT_CANCELLED) + .event(DroneEvent.CANCEL_POINT) + .action(strategy.getCancelPointAction()) + .and() + + // ========== 指点操作 -> 飞行/返航 ========== + // POINT_COMPLETED -> FLYING + .withExternal() + .source(DroneState.POINT_COMPLETED) + .target(DroneState.FLYING) + .event(DroneEvent.POINT_TO_FLYING) + .action(strategy.getPointToFlyingAction()) + .and() + + // POINT_CANCELLED -> FLYING + .withExternal() + .source(DroneState.POINT_CANCELLED) + .target(DroneState.FLYING) + .event(DroneEvent.POINT_TO_FLYING) + .action(strategy.getPointToFlyingAction()) + .and() + + // POINT_COMPLETED -> RETURNING_PARENT + .withExternal() + .source(DroneState.POINT_COMPLETED) + .target(DroneState.RETURNING_PARENT) + .event(DroneEvent.POINT_TO_RETURN) + .action(strategy.getPointToReturnAction()) + .and() + + // POINT_CANCELLED -> RETURNING_PARENT + .withExternal() + .source(DroneState.POINT_CANCELLED) + .target(DroneState.RETURNING_PARENT) + .event(DroneEvent.POINT_TO_RETURN) + .action(strategy.getPointToReturnAction()) + .and() + + // POINT_COMPLETED -> RETURN_EMERGENCY_STOP + .withExternal() + .source(DroneState.POINT_COMPLETED) + .target(DroneState.RETURN_EMERGENCY_STOP) + .event(DroneEvent.RETURN_EMERGENCY_STOP) + .action(strategy.getReturnEmergencyStopAction()) + .and() + + // POINT_COMPLETED -> RETURN_COMPLETED + .withExternal() + .source(DroneState.POINT_COMPLETED) + .target(DroneState.RETURN_COMPLETED) + .event(DroneEvent.RETURN_COMPLETED) + .action(strategy.getReturnCompletedAction()) + .and() + + // ========== 离线处理 ========== + // 任何状态 -> OFFLINE + .withExternal() + .source(DroneState.FLYING_PARENT) + .target(DroneState.OFFLINE) + .event(DroneEvent.DRONE_OFFLINE) + .action(strategy.getOfflineAction()) + .and() + + .withExternal() + .source(DroneState.RETURNING_PARENT) + .target(DroneState.OFFLINE) + .event(DroneEvent.DRONE_OFFLINE) + .action(strategy.getOfflineAction()) + .and() + + .withExternal() + .source(DroneState.POINTING_PARENT) + .target(DroneState.OFFLINE) + .event(DroneEvent.DRONE_OFFLINE) + .action(strategy.getOfflineAction()); + } +} \ No newline at end of file diff --git a/src/main/java/com/tuoheng/status/machine/events/DroneEvent.java b/src/main/java/com/tuoheng/status/machine/events/DroneEvent.java new file mode 100644 index 0000000..521bbd1 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/events/DroneEvent.java @@ -0,0 +1,121 @@ +package com.tuoheng.status.machine.events; + +/** + * 无人机事件枚举 + * 定义无人机状态机中的所有事件 + */ +public enum DroneEvent { + + // ==================== 无人机基本事件 ==================== + /** + * 无人机上线事件 + * 触发源: OSD心跳 + */ + DRONE_ONLINE, + + /** + * 无人机离线事件 + * 触发源: OSD心跳 + */ + DRONE_OFFLINE, + + // ==================== 准备阶段事件 ==================== + /** + * 开始准备 + * 触发源: 用户指令 + */ + START_PREPARE, + + /** + * 准备完成 + * 触发源: Events事件 + */ + PREPARE_COMPLETED, + + // ==================== 飞行阶段事件 ==================== + /** + * 开始飞行 + * 触发源: 用户指令 + */ + START_FLYING, + + /** + * 急停(飞行阶段) + * 触发源: 用户指令/自动 + */ + EMERGENCY_STOP, + + /** + * 恢复飞行(从急停恢复) + * 触发源: 用户指令 + */ + RESUME_FLYING, + + /** + * 到达目的地 + * 触发源: Events事件 + */ + ARRIVE, + + // ==================== 返航阶段事件 ==================== + /** + * 开始返航 + * 触发源: 用户指令/自动 + */ + START_RETURN, + + /** + * 急停(返航阶段) + * 触发源: 用户指令/自动 + */ + RETURN_EMERGENCY_STOP, + + /** + * 恢复返航(从返航急停恢复) + * 触发源: 用户指令 + */ + RESUME_RETURN, + + /** + * 返航完成 + * 触发源: Events事件 + */ + RETURN_COMPLETED, + + // ==================== 指点操作事件 ==================== + /** + * 开始指点操作 + * 触发源: 用户指令 + */ + START_POINTING, + + /** + * 指点准备完成 + * 触发源: Events事件 + */ + POINT_PREPARE_COMPLETED, + + /** + * 指点飞行完成 + * 触发源: Events事件 + */ + POINT_FLYING_COMPLETED, + + /** + * 取消指点 + * 触发源: 用户指令 + */ + CANCEL_POINT, + + /** + * 指点完成后返回飞行 + * 触发源: 自动/用户指令 + */ + POINT_TO_FLYING, + + /** + * 指点完成后返航 + * 触发源: 用户指令 + */ + POINT_TO_RETURN +} \ No newline at end of file diff --git a/src/main/java/com/tuoheng/status/machine/guard/drone/CanPointGuard.java b/src/main/java/com/tuoheng/status/machine/guard/drone/CanPointGuard.java new file mode 100644 index 0000000..0c8e361 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/guard/drone/CanPointGuard.java @@ -0,0 +1,13 @@ +package com.tuoheng.status.machine.guard.drone; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.platform.strategy.PlatformGuard; +import com.tuoheng.status.machine.status.DroneState; + +/** + * Base guard for checking if drone can start pointing operation; platform implementations extend this. + * 检查无人机是否可以进行指点操作 + * 注意:返航中不可以指点 + */ +public abstract class CanPointGuard implements PlatformGuard { +} \ No newline at end of file diff --git a/src/main/java/com/tuoheng/status/machine/listener/DefaultDroneListener.java b/src/main/java/com/tuoheng/status/machine/listener/DefaultDroneListener.java new file mode 100644 index 0000000..489ac96 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/listener/DefaultDroneListener.java @@ -0,0 +1,90 @@ +package com.tuoheng.status.machine.listener; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.platform.strategy.PlatformListener; +import com.tuoheng.status.machine.status.DroneState; +import org.springframework.messaging.Message; +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.state.State; +import org.springframework.statemachine.transition.Transition; + +/** + * 默认无人机状态监听器 + * 提供基础的状态变化监听功能,各平台可以继承并定制 + */ +public abstract class DefaultDroneListener implements PlatformListener { + + @Override + public void stateChanged(State from, State to) { + if (from != null && to != null) { + System.out.println(String.format("[%s] 状态变化: %s -> %s", + getName(), from.getId(), to.getId())); + } + } + + @Override + public void stateEntered(State state) { + System.out.println(String.format("[%s] 进入状态: %s", getName(), state.getId())); + } + + @Override + public void stateExited(State state) { + System.out.println(String.format("[%s] 退出状态: %s", getName(), state.getId())); + } + + @Override + public void eventNotAccepted(Message event) { + System.out.println(String.format("[%s] 事件未被接受: %s", getName(), event.getPayload())); + } + + @Override + public void transition(Transition transition) { + // 默认不处理 + } + + @Override + public void transitionStarted(Transition transition) { + if (transition.getSource() != null && transition.getTarget() != null) { + System.out.println(String.format("[%s] 转换开始: %s -> %s", + getName(), transition.getSource().getId(), transition.getTarget().getId())); + } + } + + @Override + public void transitionEnded(Transition transition) { + if (transition.getSource() != null && transition.getTarget() != null) { + System.out.println(String.format("[%s] 转换结束: %s -> %s", + getName(), transition.getSource().getId(), transition.getTarget().getId())); + } + } + + @Override + public void stateMachineStarted(StateMachine stateMachine) { + String machineId = (String) stateMachine.getExtendedState().getVariables().get("machineId"); + System.out.println(String.format("[%s] 状态机启动: %s", getName(), machineId)); + } + + @Override + public void stateMachineStopped(StateMachine stateMachine) { + String machineId = (String) stateMachine.getExtendedState().getVariables().get("machineId"); + System.out.println(String.format("[%s] 状态机停止: %s", getName(), machineId)); + } + + @Override + public void stateMachineError(StateMachine stateMachine, Exception exception) { + String machineId = (String) stateMachine.getExtendedState().getVariables().get("machineId"); + System.err.println(String.format("[%s] 状态机错误: %s, 异常: %s", + getName(), machineId, exception.getMessage())); + } + + @Override + public void extendedStateChanged(Object key, Object value) { + System.out.println(String.format("[%s] 扩展状态变化: %s = %s", getName(), key, value)); + } + + @Override + public void stateContext(StateContext stateContext) { + // 默认不处理 + } +} \ No newline at end of file diff --git a/src/main/java/com/tuoheng/status/machine/platform/factory/PlatformStrategyFactory.java b/src/main/java/com/tuoheng/status/machine/platform/factory/PlatformStrategyFactory.java index de87f2c..e2fe033 100644 --- a/src/main/java/com/tuoheng/status/machine/platform/factory/PlatformStrategyFactory.java +++ b/src/main/java/com/tuoheng/status/machine/platform/factory/PlatformStrategyFactory.java @@ -5,6 +5,7 @@ import com.tuoheng.status.machine.platform.PlatformType; import com.tuoheng.status.machine.repository.AirportPlatformRepository; import com.tuoheng.status.machine.platform.strategy.AirportPlatformStrategy; import com.tuoheng.status.machine.platform.strategy.CoverPlatformStrategy; +import com.tuoheng.status.machine.platform.strategy.DronePlatformStrategy; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -14,7 +15,7 @@ import java.util.concurrent.ConcurrentHashMap; /** * 平台策略工厂 - * 根据机巢SN从数据库查询平台类型,返回对应的平台策略 + * 根据机巢/无人机SN从数据库查询平台类型,返回对应的平台策略 */ @Component public class PlatformStrategyFactory { @@ -36,6 +37,13 @@ public class PlatformStrategyFactory { */ private final Map coverStrategyMap = new ConcurrentHashMap<>(); + /** + * 存储所有无人机平台策略实现 + * Key: PlatformType + * Value: DronePlatformStrategy实现 + */ + private final Map droneStrategyMap = new ConcurrentHashMap<>(); + /** * 存储各平台对应的系统管理器实现 * Key: PlatformType @@ -45,12 +53,13 @@ public class PlatformStrategyFactory { /** * 注册所有平台策略 - * Spring会自动注入所有实现了AirportPlatformStrategy和CoverPlatformStrategy的Bean + * Spring会自动注入所有实现了AirportPlatformStrategy、CoverPlatformStrategy和DronePlatformStrategy的Bean */ @Autowired public void registerStrategies( List airportStrategies, List coverStrategies, + List droneStrategies, List systemManagers) { // 注册机巢策略 @@ -67,6 +76,13 @@ public class PlatformStrategyFactory { strategy.getPlatformType().getName(), strategy.getClass().getSimpleName())); } + // 注册无人机策略 + for (DronePlatformStrategy strategy : droneStrategies) { + droneStrategyMap.put(strategy.getPlatformType(), strategy); + System.out.println(String.format("注册无人机平台策略: %s -> %s", + strategy.getPlatformType().getName(), strategy.getClass().getSimpleName())); + } + // 注册系统管理器 for (AirportSystemManager manager : systemManagers) { managerMap.put(manager.getPlatformType(), manager); @@ -171,6 +187,42 @@ public class PlatformStrategyFactory { return coverStrategyMap.get(platformType); } + /** + * 根据无人机SN获取无人机平台策略 + * + * @param droneSn 无人机序列号 + * @return 无人机平台策略 + * @throws IllegalArgumentException 如果无人机未注册或平台策略不存在 + */ + public DronePlatformStrategy getDroneStrategy(String droneSn) { + // 从数据库查询平台类型 + PlatformType platformType = airportPlatformRepository.getPlatformType(droneSn); + + if (platformType == null) { + throw new IllegalArgumentException( + String.format("无人机未注册或平台类型未配置: %s", droneSn)); + } + + DronePlatformStrategy strategy = droneStrategyMap.get(platformType); + + if (strategy == null) { + throw new IllegalArgumentException( + String.format("未找到平台策略: %s (无人机: %s)", platformType.getName(), droneSn)); + } + + return strategy; + } + + /** + * 根据平台类型获取无人机平台策略 + * + * @param platformType 平台类型 + * @return 无人机平台策略 + */ + public DronePlatformStrategy getDroneStrategyByType(PlatformType platformType) { + return droneStrategyMap.get(platformType); + } + /** * 获取机巢的平台类型 * diff --git a/src/main/java/com/tuoheng/status/machine/platform/impl/dji/DjiDronePlatformStrategy.java b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/DjiDronePlatformStrategy.java new file mode 100644 index 0000000..d00ea8f --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/DjiDronePlatformStrategy.java @@ -0,0 +1,178 @@ +package com.tuoheng.status.machine.platform.impl.dji; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.platform.PlatformType; +import com.tuoheng.status.machine.platform.impl.dji.action.drone.*; +import com.tuoheng.status.machine.platform.impl.dji.guard.drone.*; +import com.tuoheng.status.machine.platform.impl.dji.listener.DjiDroneListener; +import com.tuoheng.status.machine.platform.strategy.DronePlatformStrategy; +import com.tuoheng.status.machine.platform.strategy.PlatformAction; +import com.tuoheng.status.machine.platform.strategy.PlatformGuard; +import com.tuoheng.status.machine.platform.strategy.PlatformListener; +import com.tuoheng.status.machine.status.DroneState; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * DJI平台无人机策略实现 + */ +@Component +public class DjiDronePlatformStrategy implements DronePlatformStrategy { + + @Autowired + private DjiCanPointGuard canPointGuard; + + @Autowired + private DjiStartPrepareAction startPrepareAction; + + @Autowired + private DjiPrepareCompletedAction prepareCompletedAction; + + @Autowired + private DjiStartFlyingAction startFlyingAction; + + @Autowired + private DjiEmergencyStopAction emergencyStopAction; + + @Autowired + private DjiResumeFlyingAction resumeFlyingAction; + + @Autowired + private DjiArriveAction arriveAction; + + @Autowired + private DjiStartReturnAction startReturnAction; + + @Autowired + private DjiReturnEmergencyStopAction returnEmergencyStopAction; + + @Autowired + private DjiResumeReturnAction resumeReturnAction; + + @Autowired + private DjiReturnCompletedAction returnCompletedAction; + + @Autowired + private DjiStartPointingAction startPointingAction; + + @Autowired + private DjiPointPrepareCompletedAction pointPrepareCompletedAction; + + @Autowired + private DjiPointFlyingCompletedAction pointFlyingCompletedAction; + + @Autowired + private DjiCancelPointAction cancelPointAction; + + @Autowired + private DjiPointToFlyingAction pointToFlyingAction; + + @Autowired + private DjiPointToReturnAction pointToReturnAction; + + @Autowired + private DjiDroneOfflineAction offlineAction; + + @Autowired + private DjiDroneListener djiDroneListener; + + @Override + public PlatformType getPlatformType() { + return PlatformType.DJI; + } + + @Override + public PlatformGuard getCanPointGuard() { + return canPointGuard; + } + + @Override + public PlatformAction getStartPrepareAction() { + return startPrepareAction; + } + + @Override + public PlatformAction getPrepareCompletedAction() { + return prepareCompletedAction; + } + + @Override + public PlatformAction getStartFlyingAction() { + return startFlyingAction; + } + + @Override + public PlatformAction getEmergencyStopAction() { + return emergencyStopAction; + } + + @Override + public PlatformAction getResumeFlyingAction() { + return resumeFlyingAction; + } + + @Override + public PlatformAction getArriveAction() { + return arriveAction; + } + + @Override + public PlatformAction getStartReturnAction() { + return startReturnAction; + } + + @Override + public PlatformAction getReturnEmergencyStopAction() { + return returnEmergencyStopAction; + } + + @Override + public PlatformAction getResumeReturnAction() { + return resumeReturnAction; + } + + @Override + public PlatformAction getReturnCompletedAction() { + return returnCompletedAction; + } + + @Override + public PlatformAction getStartPointingAction() { + return startPointingAction; + } + + @Override + public PlatformAction getPointPrepareCompletedAction() { + return pointPrepareCompletedAction; + } + + @Override + public PlatformAction getPointFlyingCompletedAction() { + return pointFlyingCompletedAction; + } + + @Override + public PlatformAction getCancelPointAction() { + return cancelPointAction; + } + + @Override + public PlatformAction getPointToFlyingAction() { + return pointToFlyingAction; + } + + @Override + public PlatformAction getPointToReturnAction() { + return pointToReturnAction; + } + + @Override + public PlatformAction getOfflineAction() { + return offlineAction; + } + + @Override + public PlatformListener getListener() { + return djiDroneListener; + } +} diff --git a/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiArriveAction.java b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiArriveAction.java new file mode 100644 index 0000000..8564f0c --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiArriveAction.java @@ -0,0 +1,22 @@ +package com.tuoheng.status.machine.platform.impl.dji.action.drone; + +import com.tuoheng.status.machine.action.drone.ArriveAction; +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.status.DroneState; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; + +@Component +public class DjiArriveAction extends ArriveAction { + + @Override + public String getName() { + return "DjiArriveAction"; + } + + @Override + public void execute(StateContext context) { + String machineId = (String) context.getExtendedState().getVariables().get("machineId"); + System.out.println(String.format("[DJI] 无人机到达目的地: %s", machineId)); + } +} diff --git a/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiCancelPointAction.java b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiCancelPointAction.java new file mode 100644 index 0000000..20e2737 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiCancelPointAction.java @@ -0,0 +1,22 @@ +package com.tuoheng.status.machine.platform.impl.dji.action.drone; + +import com.tuoheng.status.machine.action.drone.CancelPointAction; +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.status.DroneState; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; + +@Component +public class DjiCancelPointAction extends CancelPointAction { + + @Override + public String getName() { + return "DjiCancelPointAction"; + } + + @Override + public void execute(StateContext context) { + String machineId = (String) context.getExtendedState().getVariables().get("machineId"); + System.out.println(String.format("[DJI] 无人机取消指点: %s", machineId)); + } +} diff --git a/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiDroneOfflineAction.java b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiDroneOfflineAction.java new file mode 100644 index 0000000..d4e9eea --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiDroneOfflineAction.java @@ -0,0 +1,22 @@ +package com.tuoheng.status.machine.platform.impl.dji.action.drone; + +import com.tuoheng.status.machine.action.drone.OfflineAction; +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.status.DroneState; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; + +@Component +public class DjiDroneOfflineAction extends OfflineAction { + + @Override + public String getName() { + return "DjiDroneOfflineAction"; + } + + @Override + public void execute(StateContext context) { + String machineId = (String) context.getExtendedState().getVariables().get("machineId"); + System.out.println(String.format("[DJI] 无人机离线: %s", machineId)); + } +} diff --git a/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiEmergencyStopAction.java b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiEmergencyStopAction.java new file mode 100644 index 0000000..e4d688d --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiEmergencyStopAction.java @@ -0,0 +1,22 @@ +package com.tuoheng.status.machine.platform.impl.dji.action.drone; + +import com.tuoheng.status.machine.action.drone.EmergencyStopAction; +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.status.DroneState; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; + +@Component +public class DjiEmergencyStopAction extends EmergencyStopAction { + + @Override + public String getName() { + return "DjiEmergencyStopAction"; + } + + @Override + public void execute(StateContext context) { + String machineId = (String) context.getExtendedState().getVariables().get("machineId"); + System.out.println(String.format("[DJI] 无人机急停: %s", machineId)); + } +} diff --git a/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiPointFlyingCompletedAction.java b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiPointFlyingCompletedAction.java new file mode 100644 index 0000000..9f7e2da --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiPointFlyingCompletedAction.java @@ -0,0 +1,22 @@ +package com.tuoheng.status.machine.platform.impl.dji.action.drone; + +import com.tuoheng.status.machine.action.drone.PointFlyingCompletedAction; +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.status.DroneState; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; + +@Component +public class DjiPointFlyingCompletedAction extends PointFlyingCompletedAction { + + @Override + public String getName() { + return "DjiPointFlyingCompletedAction"; + } + + @Override + public void execute(StateContext context) { + String machineId = (String) context.getExtendedState().getVariables().get("machineId"); + System.out.println(String.format("[DJI] 无人机指点飞行完成: %s", machineId)); + } +} diff --git a/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiPointPrepareCompletedAction.java b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiPointPrepareCompletedAction.java new file mode 100644 index 0000000..aa923bc --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiPointPrepareCompletedAction.java @@ -0,0 +1,22 @@ +package com.tuoheng.status.machine.platform.impl.dji.action.drone; + +import com.tuoheng.status.machine.action.drone.PointPrepareCompletedAction; +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.status.DroneState; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; + +@Component +public class DjiPointPrepareCompletedAction extends PointPrepareCompletedAction { + + @Override + public String getName() { + return "DjiPointPrepareCompletedAction"; + } + + @Override + public void execute(StateContext context) { + String machineId = (String) context.getExtendedState().getVariables().get("machineId"); + System.out.println(String.format("[DJI] 无人机指点准备完成: %s", machineId)); + } +} diff --git a/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiPointToFlyingAction.java b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiPointToFlyingAction.java new file mode 100644 index 0000000..9d98400 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiPointToFlyingAction.java @@ -0,0 +1,22 @@ +package com.tuoheng.status.machine.platform.impl.dji.action.drone; + +import com.tuoheng.status.machine.action.drone.PointToFlyingAction; +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.status.DroneState; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; + +@Component +public class DjiPointToFlyingAction extends PointToFlyingAction { + + @Override + public String getName() { + return "DjiPointToFlyingAction"; + } + + @Override + public void execute(StateContext context) { + String machineId = (String) context.getExtendedState().getVariables().get("machineId"); + System.out.println(String.format("[DJI] 无人机从指点返回飞行: %s", machineId)); + } +} diff --git a/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiPointToReturnAction.java b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiPointToReturnAction.java new file mode 100644 index 0000000..3db6ff8 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiPointToReturnAction.java @@ -0,0 +1,22 @@ +package com.tuoheng.status.machine.platform.impl.dji.action.drone; + +import com.tuoheng.status.machine.action.drone.PointToReturnAction; +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.status.DroneState; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; + +@Component +public class DjiPointToReturnAction extends PointToReturnAction { + + @Override + public String getName() { + return "DjiPointToReturnAction"; + } + + @Override + public void execute(StateContext context) { + String machineId = (String) context.getExtendedState().getVariables().get("machineId"); + System.out.println(String.format("[DJI] 无人机从指点开始返航: %s", machineId)); + } +} diff --git a/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiPrepareCompletedAction.java b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiPrepareCompletedAction.java new file mode 100644 index 0000000..785fc68 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiPrepareCompletedAction.java @@ -0,0 +1,22 @@ +package com.tuoheng.status.machine.platform.impl.dji.action.drone; + +import com.tuoheng.status.machine.action.drone.PrepareCompletedAction; +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.status.DroneState; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; + +@Component +public class DjiPrepareCompletedAction extends PrepareCompletedAction { + + @Override + public String getName() { + return "DjiPrepareCompletedAction"; + } + + @Override + public void execute(StateContext context) { + String machineId = (String) context.getExtendedState().getVariables().get("machineId"); + System.out.println(String.format("[DJI] 无人机准备完成: %s", machineId)); + } +} diff --git a/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiResumeFlyingAction.java b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiResumeFlyingAction.java new file mode 100644 index 0000000..b08d32e --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiResumeFlyingAction.java @@ -0,0 +1,22 @@ +package com.tuoheng.status.machine.platform.impl.dji.action.drone; + +import com.tuoheng.status.machine.action.drone.ResumeFlyingAction; +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.status.DroneState; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; + +@Component +public class DjiResumeFlyingAction extends ResumeFlyingAction { + + @Override + public String getName() { + return "DjiResumeFlyingAction"; + } + + @Override + public void execute(StateContext context) { + String machineId = (String) context.getExtendedState().getVariables().get("machineId"); + System.out.println(String.format("[DJI] 无人机恢复飞行: %s", machineId)); + } +} diff --git a/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiResumeReturnAction.java b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiResumeReturnAction.java new file mode 100644 index 0000000..56d8b4e --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiResumeReturnAction.java @@ -0,0 +1,22 @@ +package com.tuoheng.status.machine.platform.impl.dji.action.drone; + +import com.tuoheng.status.machine.action.drone.ResumeReturnAction; +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.status.DroneState; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; + +@Component +public class DjiResumeReturnAction extends ResumeReturnAction { + + @Override + public String getName() { + return "DjiResumeReturnAction"; + } + + @Override + public void execute(StateContext context) { + String machineId = (String) context.getExtendedState().getVariables().get("machineId"); + System.out.println(String.format("[DJI] 无人机恢复返航: %s", machineId)); + } +} diff --git a/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiReturnCompletedAction.java b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiReturnCompletedAction.java new file mode 100644 index 0000000..43af23d --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiReturnCompletedAction.java @@ -0,0 +1,22 @@ +package com.tuoheng.status.machine.platform.impl.dji.action.drone; + +import com.tuoheng.status.machine.action.drone.ReturnCompletedAction; +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.status.DroneState; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; + +@Component +public class DjiReturnCompletedAction extends ReturnCompletedAction { + + @Override + public String getName() { + return "DjiReturnCompletedAction"; + } + + @Override + public void execute(StateContext context) { + String machineId = (String) context.getExtendedState().getVariables().get("machineId"); + System.out.println(String.format("[DJI] 无人机返航完成: %s", machineId)); + } +} diff --git a/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiReturnEmergencyStopAction.java b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiReturnEmergencyStopAction.java new file mode 100644 index 0000000..9765bb6 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiReturnEmergencyStopAction.java @@ -0,0 +1,22 @@ +package com.tuoheng.status.machine.platform.impl.dji.action.drone; + +import com.tuoheng.status.machine.action.drone.ReturnEmergencyStopAction; +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.status.DroneState; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; + +@Component +public class DjiReturnEmergencyStopAction extends ReturnEmergencyStopAction { + + @Override + public String getName() { + return "DjiReturnEmergencyStopAction"; + } + + @Override + public void execute(StateContext context) { + String machineId = (String) context.getExtendedState().getVariables().get("machineId"); + System.out.println(String.format("[DJI] 无人机返航急停: %s", machineId)); + } +} diff --git a/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiStartFlyingAction.java b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiStartFlyingAction.java new file mode 100644 index 0000000..42b6778 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiStartFlyingAction.java @@ -0,0 +1,22 @@ +package com.tuoheng.status.machine.platform.impl.dji.action.drone; + +import com.tuoheng.status.machine.action.drone.StartFlyingAction; +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.status.DroneState; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; + +@Component +public class DjiStartFlyingAction extends StartFlyingAction { + + @Override + public String getName() { + return "DjiStartFlyingAction"; + } + + @Override + public void execute(StateContext context) { + String machineId = (String) context.getExtendedState().getVariables().get("machineId"); + System.out.println(String.format("[DJI] 无人机开始飞行: %s", machineId)); + } +} diff --git a/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiStartPointingAction.java b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiStartPointingAction.java new file mode 100644 index 0000000..1b09792 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiStartPointingAction.java @@ -0,0 +1,22 @@ +package com.tuoheng.status.machine.platform.impl.dji.action.drone; + +import com.tuoheng.status.machine.action.drone.StartPointingAction; +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.status.DroneState; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; + +@Component +public class DjiStartPointingAction extends StartPointingAction { + + @Override + public String getName() { + return "DjiStartPointingAction"; + } + + @Override + public void execute(StateContext context) { + String machineId = (String) context.getExtendedState().getVariables().get("machineId"); + System.out.println(String.format("[DJI] 无人机开始指点操作: %s", machineId)); + } +} diff --git a/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiStartPrepareAction.java b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiStartPrepareAction.java new file mode 100644 index 0000000..2ee6dd4 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiStartPrepareAction.java @@ -0,0 +1,22 @@ +package com.tuoheng.status.machine.platform.impl.dji.action.drone; + +import com.tuoheng.status.machine.action.drone.StartPrepareAction; +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.status.DroneState; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; + +@Component +public class DjiStartPrepareAction extends StartPrepareAction { + + @Override + public String getName() { + return "DjiStartPrepareAction"; + } + + @Override + public void execute(StateContext context) { + String machineId = (String) context.getExtendedState().getVariables().get("machineId"); + System.out.println(String.format("[DJI] 无人机开始准备: %s", machineId)); + } +} diff --git a/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiStartReturnAction.java b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiStartReturnAction.java new file mode 100644 index 0000000..26f8c37 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/action/drone/DjiStartReturnAction.java @@ -0,0 +1,22 @@ +package com.tuoheng.status.machine.platform.impl.dji.action.drone; + +import com.tuoheng.status.machine.action.drone.StartReturnAction; +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.status.DroneState; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; + +@Component +public class DjiStartReturnAction extends StartReturnAction { + + @Override + public String getName() { + return "DjiStartReturnAction"; + } + + @Override + public void execute(StateContext context) { + String machineId = (String) context.getExtendedState().getVariables().get("machineId"); + System.out.println(String.format("[DJI] 无人机开始返航: %s", machineId)); + } +} diff --git a/src/main/java/com/tuoheng/status/machine/platform/impl/dji/guard/drone/DjiCanPointGuard.java b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/guard/drone/DjiCanPointGuard.java new file mode 100644 index 0000000..d7e997b --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/guard/drone/DjiCanPointGuard.java @@ -0,0 +1,33 @@ +package com.tuoheng.status.machine.platform.impl.dji.guard.drone; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.guard.drone.CanPointGuard; +import com.tuoheng.status.machine.status.DroneState; +import org.springframework.statemachine.StateContext; +import org.springframework.stereotype.Component; + +/** + * DJI平台:检查无人机是否可以进行指点操作 + * 注意:返航中不可以指点 + */ +@Component +public class DjiCanPointGuard extends CanPointGuard { + + @Override + public String getName() { + return "DjiCanPointGuard"; + } + + @Override + public boolean evaluate(StateContext context) { + DroneState currentState = context.getStateMachine().getState().getId(); + + // 返航中不能指点 + if (currentState == DroneState.RETURNING) { + System.out.println("[DJI] 返航中不能进行指点操作"); + return false; + } + + return true; + } +} diff --git a/src/main/java/com/tuoheng/status/machine/platform/impl/dji/listener/DjiDroneListener.java b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/listener/DjiDroneListener.java new file mode 100644 index 0000000..3b02ec0 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/platform/impl/dji/listener/DjiDroneListener.java @@ -0,0 +1,18 @@ +package com.tuoheng.status.machine.platform.impl.dji.listener; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.listener.DefaultDroneListener; +import com.tuoheng.status.machine.status.DroneState; +import org.springframework.stereotype.Component; + +/** + * DJI平台无人机状态监听器 + */ +@Component +public class DjiDroneListener extends DefaultDroneListener { + + @Override + public String getName() { + return "DJI-Drone"; + } +} diff --git a/src/main/java/com/tuoheng/status/machine/platform/strategy/DronePlatformStrategy.java b/src/main/java/com/tuoheng/status/machine/platform/strategy/DronePlatformStrategy.java new file mode 100644 index 0000000..2745367 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/platform/strategy/DronePlatformStrategy.java @@ -0,0 +1,73 @@ +package com.tuoheng.status.machine.platform.strategy; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.platform.PlatformType; +import com.tuoheng.status.machine.status.DroneState; + +/** + * 无人机平台策略接口 + * 定义各平台需要实现的Guard、Action和Listener + */ +public interface DronePlatformStrategy { + + /** + * 获取平台类型 + */ + PlatformType getPlatformType(); + + // ==================== Guards ==================== + + /** + * 是否可以指点 + */ + PlatformGuard getCanPointGuard(); + + // ==================== Actions ==================== + + // 准备阶段 + PlatformAction getStartPrepareAction(); + + PlatformAction getPrepareCompletedAction(); + + // 飞行阶段 + PlatformAction getStartFlyingAction(); + + PlatformAction getEmergencyStopAction(); + + PlatformAction getResumeFlyingAction(); + + PlatformAction getArriveAction(); + + // 返航阶段 + PlatformAction getStartReturnAction(); + + PlatformAction getReturnEmergencyStopAction(); + + PlatformAction getResumeReturnAction(); + + PlatformAction getReturnCompletedAction(); + + // 指点操作 + PlatformAction getStartPointingAction(); + + PlatformAction getPointPrepareCompletedAction(); + + PlatformAction getPointFlyingCompletedAction(); + + PlatformAction getCancelPointAction(); + + PlatformAction getPointToFlyingAction(); + + PlatformAction getPointToReturnAction(); + + // 离线 + PlatformAction getOfflineAction(); + + // ==================== Listener ==================== + + /** + * 获取平台Listener + * 每个平台有一个Listener实例,所有该平台的无人机共享 + */ + PlatformListener getListener(); +} \ No newline at end of file diff --git a/src/main/java/com/tuoheng/status/machine/redis/RedisStateStore.java b/src/main/java/com/tuoheng/status/machine/redis/RedisStateStore.java index 679a0b8..01fc75c 100644 --- a/src/main/java/com/tuoheng/status/machine/redis/RedisStateStore.java +++ b/src/main/java/com/tuoheng/status/machine/redis/RedisStateStore.java @@ -2,6 +2,7 @@ package com.tuoheng.status.machine.redis; import com.tuoheng.status.machine.status.AirportState; import com.tuoheng.status.machine.status.CoverState; +import com.tuoheng.status.machine.status.DroneState; import org.springframework.stereotype.Component; import java.util.Map; @@ -10,7 +11,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** - * 使用 Redis 记录和恢复机巢/舱门状态的存储组件。 + * 使用 Redis 记录和恢复机巢/舱门/无人机状态的存储组件。 * * 当前实现采用内存 Map 占位,便于无 Redis 环境下直接运行。 * 如果接入真正的 Redis,只需将存取逻辑替换为 RedisTemplate 等实现。 @@ -20,6 +21,7 @@ public class RedisStateStore { private final Map airportStateMap = new ConcurrentHashMap<>(); private final Map coverStateMap = new ConcurrentHashMap<>(); + private final Map droneStateMap = new ConcurrentHashMap<>(); public void saveAirportState(String airportSn, AirportState state) { if (airportSn != null && state != null) { @@ -33,6 +35,12 @@ public class RedisStateStore { } } + public void saveDroneState(String droneSn, DroneState state) { + if (droneSn != null && state != null) { + droneStateMap.put(droneSn, state); + } + } + public Optional loadAirportState(String airportSn) { return Optional.ofNullable(airportStateMap.get(airportSn)); } @@ -41,6 +49,10 @@ public class RedisStateStore { return Optional.ofNullable(coverStateMap.get(airportSn)); } + public Optional loadDroneState(String droneSn) { + return Optional.ofNullable(droneStateMap.get(droneSn)); + } + public Set allAirportIds() { // 合并两张表的 key,防止有只存机场或只存舱门的情况 Set ids = ConcurrentHashMap.newKeySet(); @@ -48,5 +60,9 @@ public class RedisStateStore { ids.addAll(coverStateMap.keySet()); return ids; } + + public Set allDroneIds() { + return droneStateMap.keySet(); + } } diff --git a/src/main/java/com/tuoheng/status/machine/service/DroneMachineService.java b/src/main/java/com/tuoheng/status/machine/service/DroneMachineService.java new file mode 100644 index 0000000..9a3a3ec --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/service/DroneMachineService.java @@ -0,0 +1,191 @@ +package com.tuoheng.status.machine.service; + +import com.tuoheng.status.machine.events.DroneEvent; +import com.tuoheng.status.machine.redis.RedisStateStore; +import com.tuoheng.status.machine.status.DroneState; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.config.StateMachineFactory; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 无人机状态机管理器 + * 负责管理多个无人机的状态机实例 + */ +@Component +public class DroneMachineService { + + @Autowired + StateMachineFactory droneStateMachineFactory; + + @Autowired + private RedisStateStore redisStateStore; + + /** + * 存储所有无人机的状态机实例 + * Key: 无人机ID (droneSn) + * Value: 状态机实例 + */ + private final Map> stateMachineMap = new ConcurrentHashMap<>(); + + /** + * 获取或创建状态机 + * 如果状态机不存在,则创建新的状态机实例 + * + * @param droneSn 无人机序列号 + * @return 状态机实例 + */ + public StateMachine getOrCreateStateMachine(String droneSn) { + return stateMachineMap.computeIfAbsent(droneSn, id -> { + StateMachine stateMachine = droneStateMachineFactory.getStateMachine(id); + // 服务器重启后,状态机初始化为 UNKNOWN 状态 + // 不从 Redis 恢复旧状态,等待第一次心跳同步真实状态 + // 这样可以避免服务器重启期间丢失心跳导致的状态不一致问题 + stateMachine.start(); + System.out.println(String.format("创建并启动状态机: %s, 初始状态: UNKNOWN (等待心跳同步)", id)); + return stateMachine; + }); + } + + /** + * 获取状态机(不创建) + * + * @param droneSn 无人机序列号 + * @return 状态机实例,如果不存在返回null + */ + public StateMachine getStateMachine(String droneSn) { + return stateMachineMap.get(droneSn); + } + + /** + * 获取状态机的当前状态 + * + * @param droneSn 无人机序列号 + * @return 当前状态,如果状态机不存在返回null + */ + public DroneState getCurrentState(String droneSn) { + StateMachine stateMachine = stateMachineMap.get(droneSn); + if (stateMachine == null) { + System.out.println("状态机不存在: " + droneSn); + return null; + } + return stateMachine.getState().getId(); + } + + /** + * 获取状态机的所有当前状态(包括子状态) + * + * @param droneSn 无人机序列号 + * @return 当前状态集合的字符串表示 + */ + public String getCurrentStates(String droneSn) { + StateMachine stateMachine = stateMachineMap.get(droneSn); + if (stateMachine == null) { + return "状态机不存在"; + } + + StringBuilder states = new StringBuilder(); + stateMachine.getState().getIds().forEach(state -> { + if (states.length() > 0) { + states.append(" -> "); + } + states.append(state); + }); + + return states.toString(); + } + + /** + * 发送事件到状态机 + * + * @param droneSn 无人机序列号 + * @param event 事件 + * @return 是否发送成功 + */ + public boolean sendEvent(String droneSn, DroneEvent event) { + StateMachine stateMachine = getOrCreateStateMachine(droneSn); + boolean result = stateMachine.sendEvent(event); + + if (result) { + // 持久化最新状态 + redisStateStore.saveDroneState(droneSn, stateMachine.getState().getId()); + System.out.println(String.format("事件发送成功 - 无人机: %s, 事件: %s, 当前状态: %s", + droneSn, event, getCurrentStates(droneSn))); + } else { + System.out.println(String.format("事件发送失败 - 无人机: %s, 事件: %s, 当前状态: %s", + droneSn, event, getCurrentStates(droneSn))); + } + + return result; + } + + /** + * 移除状态机 + * + * @param droneSn 无人机序列号 + */ + public void removeStateMachine(String droneSn) { + StateMachine stateMachine = stateMachineMap.remove(droneSn); + if (stateMachine != null) { + stateMachine.stop(); + System.out.println("停止并移除状态机: " + droneSn); + } + } + + /** + * 检查状态机是否存在 + * + * @param droneSn 无人机序列号 + * @return 是否存在 + */ + public boolean hasStateMachine(String droneSn) { + return stateMachineMap.containsKey(droneSn); + } + + /** + * 获取所有状态机的数量 + * + * @return 状态机数量 + */ + public int getStateMachineCount() { + return stateMachineMap.size(); + } + + /** + * 获取所有无人机ID + * + * @return 无人机ID集合 + */ + public java.util.Set getAllDroneIds() { + return stateMachineMap.keySet(); + } + + /** + * 检查状态机是否处于指定状态 + * + * @param droneSn 无人机序列号 + * @param state 要检查的状态 + * @return 是否处于指定状态 + */ + public boolean isInState(String droneSn, DroneState state) { + StateMachine stateMachine = stateMachineMap.get(droneSn); + if (stateMachine == null) { + return false; + } + return stateMachine.getState().getIds().contains(state); + } + + /** + * 重启状态机 + * + * @param droneSn 无人机序列号 + */ + public void restartStateMachine(String droneSn) { + removeStateMachine(droneSn); + getOrCreateStateMachine(droneSn); + System.out.println("重启状态机: " + droneSn); + } +} \ No newline at end of file diff --git a/src/main/java/com/tuoheng/status/machine/status/DrcState.java b/src/main/java/com/tuoheng/status/machine/status/DrcState.java new file mode 100644 index 0000000..24b2ba7 --- /dev/null +++ b/src/main/java/com/tuoheng/status/machine/status/DrcState.java @@ -0,0 +1,9 @@ +package com.tuoheng.status.machine.status; + +/** + * 飞行控制模式(DRC) + * 未知状态 退出状态 进入中 进入状态 退出中 + */ +public enum DrcState { + +} diff --git a/src/main/java/com/tuoheng/status/machine/status/DroneState.java b/src/main/java/com/tuoheng/status/machine/status/DroneState.java index 69100c2..90d57bc 100644 --- a/src/main/java/com/tuoheng/status/machine/status/DroneState.java +++ b/src/main/java/com/tuoheng/status/machine/status/DroneState.java @@ -1,4 +1,104 @@ package com.tuoheng.status.machine.status; +/** + * 分为:准备中 -> 飞行中 -> 返航 三个大状态 + * + * 飞行中 + * 飞行中 (可以变到指点) + * 指点操作 (可以返航,指点 和 飞行中) + * 急停 (可以返航,指点,飞行中) + * 到达目的地 (可以指点和返航) + * + * 返航 + * 返航中 (不可以指点) + * 急停 (一键起飞和航线飞行都可以指点) + * 指点操作 (可以返航中,急停,返航完成) + * 返航完成 (不可往回变为返航中和急停) + * + * 指点操作细分为:准备指点 指点飞行中 指点完成 指点被取消 + */ public enum DroneState { + /** + * 未知状态(服务器重启后的初始状态,等待第一次心跳同步) + */ + UNKNOWN, + + /** + * 离线 + */ + OFFLINE, + + // ==================== 准备阶段 ==================== + /** + * 准备中 + */ + PREPARING, + + // ==================== 飞行阶段(父状态) ==================== + /** + * 飞行中(父状态) + */ + FLYING_PARENT, + + /** + * 飞行中 + */ + FLYING, + + /** + * 急停(飞行阶段) + */ + EMERGENCY_STOP, + + /** + * 到达目的地 + */ + ARRIVED, + + // ==================== 返航阶段(父状态) ==================== + /** + * 返航(父状态) + */ + RETURNING_PARENT, + + /** + * 返航中 + */ + RETURNING, + + /** + * 急停(返航阶段) + */ + RETURN_EMERGENCY_STOP, + + /** + * 返航完成 + */ + RETURN_COMPLETED, + + // ==================== 指点操作(可在飞行和返航阶段使用) ==================== + /** + * 指点操作(父状态) + */ + POINTING_PARENT, + + /** + * 准备指点 + */ + POINT_PREPARING, + + /** + * 指点飞行中 + */ + POINT_FLYING, + + /** + * 指点完成 + */ + POINT_COMPLETED, + + /** + * 指点被取消 + */ + POINT_CANCELLED } diff --git a/src/main/java/com/tuoheng/status/statemachine/README.md b/src/main/java/com/tuoheng/status/statemachine/README.md deleted file mode 100644 index 1f8838a..0000000 --- a/src/main/java/com/tuoheng/status/statemachine/README.md +++ /dev/null @@ -1,506 +0,0 @@ -# Spring StateMachine 使用文档 - -本文档详细介绍了如何在项目中使用 Spring StateMachine Core 来构建状态机,包括子状态、Action、Guard、事件监听器等核心功能。 - -## 目录 - -- [项目结构](#项目结构) -- [核心概念](#核心概念) -- [状态机配置](#状态机配置) -- [使用示例](#使用示例) -- [运行演示](#运行演示) -- [常见问题](#常见问题) - -## 项目结构 - -``` -com.tuoheng.status.statemachine/ -├── status/ -│ └── Status.java # 状态枚举(包含主状态和子状态) -├── events/ -│ └── Event.java # 事件枚举 -├── action/ -│ └── TaskAction.java # Action 实现类 -├── guard/ -│ └── TaskGuard.java # Guard 实现类 -├── listener/ -│ └── TaskStateMachineListener.java # 状态机事件监听器 -├── config/ -│ └── TaskStateMachineConfig.java # 状态机配置类 -├── service/ -│ └── TaskStateMachineService.java # 服务类(使用示例) -└── demo/ - └── StateMachineDemo.java # 演示主类 -``` - -## 核心概念 - -### 1. 状态(State) - -状态定义了状态机可能处于的各种情况。本示例包含: - -**主状态:** -- `IDLE` - 空闲状态(初始状态) -- `PROCESSING` - 处理中(父状态,包含子状态) -- `COMPLETED` - 已完成 -- `FAILED` - 失败 - -**子状态(PROCESSING 的子状态):** -- `PREPARING` - 准备中 -- `EXECUTING` - 执行中 -- `VALIDATING` - 验证中 - -### 2. 事件(Event) - -事件是触发状态转换的触发器: - -- `START` - 开始处理 -- `PREPARE` - 准备就绪 -- `EXECUTE` - 执行 -- `VALIDATE` - 验证 -- `COMPLETE` - 完成 -- `FAIL` - 失败 -- `RETRY` - 重试 -- `RESET` - 重置 - -### 3. Action(动作) - -Action 在状态转换时执行,用于执行业务逻辑。Action 在转换**过程中**执行。 - -**示例:** -```java -public static class StartAction implements Action { - @Override - public void execute(StateContext context) { - // 执行业务逻辑 - logger.info("开始处理任务"); - Object taskId = context.getExtendedState().getVariables().get("taskId"); - // ... - } -} -``` - -### 4. Guard(守卫) - -Guard 用于条件判断,决定是否允许状态转换。Guard 在转换**之前**执行,如果返回 `false`,转换将被阻止。 - -**示例:** -```java -public static class CanStartGuard implements Guard { - @Override - public boolean evaluate(StateContext context) { - // 检查条件 - Object taskId = context.getExtendedState().getVariables().get("taskId"); - return taskId != null; // 只有 taskId 存在时才允许转换 - } -} -``` - -### 5. 事件监听器(Listener) - -监听器用于监听状态机的各种事件,如状态变化、转换等。 - -**监听的事件类型:** -- 状态机启动/停止 -- 状态进入/退出 -- 状态改变 -- 转换开始/结束 -- 事件未接受 -- 扩展状态改变 - -## 状态机配置 - -### 状态转换流程 - -``` -IDLE - └─[START]─> PREPARING (子状态) - └─[PREPARE]─> EXECUTING (子状态) - └─[EXECUTE]─> VALIDATING (子状态) - └─[VALIDATE]─> COMPLETED - └─[RESET]─> IDLE - -PREPARING/EXECUTING/VALIDATING - └─[FAIL]─> FAILED - └─[RETRY]─> PREPARING (重试) - └─[RESET]─> IDLE (重置) -``` - -### 配置类说明 - -`TaskStateMachineConfig` 类负责配置状态机: - -1. **配置状态和子状态:** -```java -builder.configureStates() - .withStates() - .initial(Status.IDLE) // 初始状态 - .states(EnumSet.of(Status.IDLE, Status.COMPLETED, Status.FAILED)) - .and() - .withStates() - .parent(Status.PROCESSING) // 设置父状态 - .initial(Status.PREPARING) // 子状态的初始状态 - .states(EnumSet.of(Status.PREPARING, Status.EXECUTING, Status.VALIDATING)); -``` - -2. **配置转换:** -```java -builder.configureTransitions() - .withExternal() - .source(Status.IDLE) - .target(Status.PREPARING) - .event(Event.START) - .action(new TaskAction.StartAction()) // 添加 Action - .guard(new TaskGuard.CanStartGuard()) // 添加 Guard - .and() - // ... 更多转换配置 -``` - -## 状态机管理器 - -当需要管理多个状态机实例时,可以使用 `StateMachineManager` 来统一管理。 - -### 核心功能 - -- **通过ID获取状态机**:`getStateMachine(machineId)` -- **查询当前状态**:`getCurrentStatus(machineId)` -- **获取详细信息**:`getStateMachineInfo(machineId)` -- **创建/获取状态机**:`getOrCreateStateMachine(machineId)` -- **移除状态机**:`removeStateMachine(machineId)` - -### 使用示例 - -```java -@Autowired -private StateMachineManager stateMachineManager; - -// 1. 创建或获取状态机 -StateMachine stateMachine = - stateMachineManager.getOrCreateStateMachine("machine-001"); - -// 2. 通过ID获取状态机 -StateMachine sm = - stateMachineManager.getStateMachine("machine-001"); - -// 3. 查询当前状态 -Status currentStatus = stateMachineManager.getCurrentStatus("machine-001"); -System.out.println("当前状态: " + currentStatus); - -// 4. 获取详细信息 -StateMachineManager.StateMachineInfo info = - stateMachineManager.getStateMachineInfo("machine-001"); -System.out.println("状态机ID: " + info.getMachineId()); -System.out.println("当前状态: " + info.getCurrentStatus()); -System.out.println("是否运行中: " + info.isRunning()); -System.out.println("扩展状态: " + info.getExtendedState()); - -// 5. 发送事件 -stateMachine.sendEvent(Event.START); - -// 6. 移除状态机 -stateMachineManager.removeStateMachine("machine-001"); -``` - -### 使用服务类(推荐) - -`StateMachineManagerService` 提供了更便捷的方法: - -```java -@Autowired -private StateMachineManagerService stateMachineManagerService; - -// 启动任务(自动创建状态机) -stateMachineManagerService.startTask("machine-001", "task-001"); - -// 发送事件 -stateMachineManagerService.sendEvent("machine-001", Event.PREPARE); - -// 查询状态 -Status status = stateMachineManagerService.getCurrentStatus("machine-001"); - -// 获取详细信息 -StateMachineManager.StateMachineInfo info = - stateMachineManagerService.getStateMachineInfo("machine-001"); - -// 打印状态机信息(调试用) -stateMachineManagerService.printStateMachineInfo("machine-001"); - -// 获取所有状态机ID -Set allIds = stateMachineManagerService.getAllMachineIds(); -``` - -### 管理多个状态机 - -```java -// 创建多个状态机 -stateMachineManager.getOrCreateStateMachine("machine-001"); -stateMachineManager.getOrCreateStateMachine("machine-002"); -stateMachineManager.getOrCreateStateMachine("machine-003"); - -// 获取所有状态机ID -Set allIds = stateMachineManager.getAllMachineIds(); -System.out.println("管理的状态机数量: " + allIds.size()); - -// 为不同状态机设置不同状态 -stateMachineManager.getStateMachine("machine-001") - .getExtendedState().getVariables().put("taskId", "task-001"); -stateMachineManagerService.sendEvent("machine-001", Event.START); - -// 查询不同状态机的状态 -Status status1 = stateMachineManager.getCurrentStatus("machine-001"); -Status status2 = stateMachineManager.getCurrentStatus("machine-002"); -``` - -## 使用示例 - -### 1. 基本使用 - -```java -@Autowired -private StateMachineFactory stateMachineFactory; - -public void processTask(String taskId) { - // 创建状态机实例 - StateMachine stateMachine = stateMachineFactory.getStateMachine(); - - try { - // 设置扩展状态变量 - stateMachine.getExtendedState().getVariables().put("taskId", taskId); - - // 启动状态机 - stateMachine.start(); - - // 发送事件触发转换 - stateMachine.sendEvent(Event.START); - - // 继续处理... - stateMachine.getExtendedState().getVariables().put("prepared", true); - stateMachine.sendEvent(Event.PREPARE); - - } finally { - // 停止状态机 - stateMachine.stop(); - } -} -``` - -### 2. 使用 Guard 控制转换 - -```java -// Guard 会在转换前检查条件 -public static class CanStartGuard implements Guard { - @Override - public boolean evaluate(StateContext context) { - Object taskId = context.getExtendedState().getVariables().get("taskId"); - return taskId != null; // 只有满足条件才允许转换 - } -} - -// 使用:如果 taskId 不存在,START 事件将被拒绝 -stateMachine.sendEvent(Event.START); // Guard 检查失败,转换不会发生 -``` - -### 3. 使用 Action 执行业务逻辑 - -```java -// Action 在转换时执行 -public static class ExecuteAction implements Action { - @Override - public void execute(StateContext context) { - // 执行业务逻辑 - logger.info("执行任务"); - // 可以访问和修改扩展状态 - context.getExtendedState().getVariables().put("executionResult", "success"); - } -} -``` - -### 4. 监听状态机事件 - -监听器会自动监听所有状态机事件: - -```java -// 监听器会自动记录: -// - 状态机启动/停止 -// - 状态进入/退出 -// - 状态改变 -// - 转换开始/结束 -// - 事件未接受 -``` - -## 运行演示 - -### 方式一:运行演示主类 - -直接运行 `StateMachineDemo` 类的 `main` 方法: - -```bash -# 在 IDE 中运行 -com.tuoheng.status.statemachine.demo.StateMachineDemo - -# 或使用 Maven -mvn exec:java -Dexec.mainClass="com.tuoheng.status.statemachine.demo.StateMachineDemo" -``` - -演示内容包括: -1. **正常流程**:IDLE → PREPARING → EXECUTING → VALIDATING → COMPLETED -2. **失败和重试**:演示失败后的重试机制 -3. **Guard 条件不满足**:演示 Guard 如何阻止转换 -4. **重置流程**:演示如何重置状态机 - -### 方式二:运行测试类 - -运行 `TaskStateMachineTest` 测试类: - -```bash -mvn test -Dtest=TaskStateMachineTest -``` - -### 方式三:在服务中使用 - -```java -@Service -public class YourService { - @Autowired - private TaskStateMachineService taskStateMachineService; - - public void handleTask(String taskId) { - // 使用预定义的服务方法 - taskStateMachineService.startTask(taskId); - } -} -``` - -## 扩展状态(Extended State) - -扩展状态用于存储状态机相关的变量,可以在 Action、Guard 和监听器中访问: - -```java -// 设置变量 -stateMachine.getExtendedState().getVariables().put("taskId", "task-001"); -stateMachine.getExtendedState().getVariables().put("retryCount", 0); - -// 在 Action 或 Guard 中访问 -Object taskId = context.getExtendedState().getVariables().get("taskId"); -Integer retryCount = (Integer) context.getExtendedState().getVariables().getOrDefault("retryCount", 0); -``` - -## 常见问题 - -### 1. Guard 返回 false,转换不执行 - -**原因:** Guard 在转换前检查条件,如果返回 `false`,转换将被阻止。 - -**解决:** 确保在发送事件前,扩展状态中的变量已正确设置。 - -```java -// 错误示例:Guard 检查失败 -stateMachine.sendEvent(Event.START); // taskId 未设置,Guard 返回 false - -// 正确示例:先设置变量 -stateMachine.getExtendedState().getVariables().put("taskId", "task-001"); -stateMachine.sendEvent(Event.START); // Guard 检查通过 -``` - -### 2. 事件未接受 - -**原因:** 当前状态不支持该事件,或没有匹配的转换配置。 - -**解决:** -- 检查状态机当前状态 -- 确认配置中是否有对应的转换 -- 检查 Guard 条件是否满足 - -```java -// 检查事件是否被接受 -boolean accepted = stateMachine.sendEvent(Event.START); -if (!accepted) { - logger.warning("事件未被接受,当前状态: " + stateMachine.getState().getId()); -} -``` - -### 3. 子状态的使用 - -子状态是父状态的内部状态,当进入父状态时,会自动进入子状态的初始状态。 - -```java -// 从 IDLE 转换到 PREPARING(PROCESSING 的子状态) -stateMachine.sendEvent(Event.START); -// 状态机现在处于 PROCESSING 父状态,具体是 PREPARING 子状态 -``` - -### 4. 状态机实例管理 - -每次调用 `getStateMachine()` 都会创建新的状态机实例。如果需要复用实例,需要自己管理: - -```java -// 方式一:每次创建新实例(推荐用于独立任务) -StateMachine sm1 = stateMachineFactory.getStateMachine(); -StateMachine sm2 = stateMachineFactory.getStateMachine(); - -// 方式二:复用实例(需要自己管理生命周期) -StateMachine sm = stateMachineFactory.getStateMachine(); -// ... 使用 sm -sm.stop(); // 使用完后停止 -``` - -### 5. 监听器中的空指针异常 - -某些转换(如初始转换)可能没有 trigger,需要检查 null: - -```java -@Override -public void transitionStarted(Transition transition) { - if (transition.getTrigger() != null) { - logger.info("事件: " + transition.getTrigger().getEvent()); - } else { - logger.info("事件: null (可能是初始转换)"); - } -} -``` - -### 6. 通过ID获取状态机 - -使用 `StateMachineManager` 可以方便地管理多个状态机: - -```java -@Autowired -private StateMachineManager stateMachineManager; - -// 获取状态机 -StateMachine sm = stateMachineManager.getStateMachine("machine-id"); - -// 如果不存在,创建新实例 -StateMachine sm = stateMachineManager.getOrCreateStateMachine("machine-id"); - -// 查询当前状态 -Status status = stateMachineManager.getCurrentStatus("machine-id"); - -// 获取详细信息 -StateMachineManager.StateMachineInfo info = - stateMachineManager.getStateMachineInfo("machine-id"); -``` - -## 最佳实践 - -1. **使用 Guard 进行条件检查**:在转换前验证条件,避免无效转换 -2. **使用 Action 执行业务逻辑**:将业务逻辑放在 Action 中,保持代码清晰 -3. **合理使用扩展状态**:存储任务相关的变量,便于在 Action 和 Guard 中使用 -4. **添加监听器**:监听状态机事件,便于调试和监控 -5. **管理状态机生命周期**:及时启动和停止状态机,避免资源泄漏 -6. **处理异常情况**:在 Guard 中检查条件,在 Action 中处理异常 - -## 参考资源 - -- [Spring StateMachine 官方文档](https://docs.spring.io/spring-statemachine/docs/current/reference/) -- [Spring StateMachine API 文档](https://docs.spring.io/spring-statemachine/docs/current/api/) - -## 示例代码位置 - -- **配置类**:`com.tuoheng.status.statemachine.config.TaskStateMachineConfig` -- **状态机管理器**:`com.tuoheng.status.statemachine.manager.StateMachineManager` -- **管理器服务类**:`com.tuoheng.status.statemachine.service.StateMachineManagerService` -- **服务类**:`com.tuoheng.status.statemachine.service.TaskStateMachineService` -- **演示类**:`com.tuoheng.status.statemachine.demo.StateMachineDemo` -- **测试类**:`com.tuoheng.status.statemachine.TaskStateMachineTest` -- **管理器测试类**:`com.tuoheng.status.statemachine.StateMachineManagerTest` diff --git a/src/main/java/com/tuoheng/status/statemachine/action/TaskAction.java b/src/main/java/com/tuoheng/status/statemachine/action/TaskAction.java deleted file mode 100644 index 15e21ed..0000000 --- a/src/main/java/com/tuoheng/status/statemachine/action/TaskAction.java +++ /dev/null @@ -1,142 +0,0 @@ -package com.tuoheng.status.statemachine.action; - -import com.tuoheng.status.statemachine.events.Event; -import com.tuoheng.status.statemachine.status.Status; -import org.springframework.statemachine.StateContext; -import org.springframework.statemachine.action.Action; - -import java.util.logging.Logger; - -/** - * 任务 Action 示例 - * Action 在状态转换时执行,用于执行业务逻辑 - */ -public class TaskAction { - - private static final Logger logger = Logger.getLogger(TaskAction.class.getName()); - - /** - * 开始处理 Action - */ - public static class StartAction implements Action { - @Override - public void execute(StateContext context) { - logger.info("=== Action: 开始处理任务 ==="); - logger.info("从状态: " + context.getSource().getId()); - logger.info("到状态: " + context.getTarget().getId()); - logger.info("事件: " + context.getEvent()); - - // 可以在这里执行业务逻辑,比如初始化任务数据 - Object taskId = context.getExtendedState().getVariables().get("taskId"); - logger.info("任务ID: " + taskId); - } - } - - /** - * 准备 Action - */ - public static class PrepareAction implements Action { - @Override - public void execute(StateContext context) { - logger.info("=== Action: 准备任务资源 ==="); - logger.info("当前状态: " + context.getStateMachine().getState().getId()); - - // 模拟准备资源 - try { - Thread.sleep(100); // 模拟耗时操作 - logger.info("资源准备完成"); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - } - - /** - * 执行 Action - */ - public static class ExecuteAction implements Action { - @Override - public void execute(StateContext context) { - logger.info("=== Action: 执行任务 ==="); - logger.info("当前状态: " + context.getStateMachine().getState().getId()); - - // 模拟执行任务 - try { - Thread.sleep(200); // 模拟耗时操作 - logger.info("任务执行完成"); - - // 可以在扩展状态中存储执行结果 - context.getExtendedState().getVariables().put("executionResult", "success"); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - } - - /** - * 验证 Action - */ - public static class ValidateAction implements Action { - @Override - public void execute(StateContext context) { - logger.info("=== Action: 验证任务结果 ==="); - logger.info("当前状态: " + context.getStateMachine().getState().getId()); - - // 获取执行结果 - Object result = context.getExtendedState().getVariables().get("executionResult"); - logger.info("执行结果: " + result); - - // 模拟验证 - boolean isValid = result != null && "success".equals(result); - context.getExtendedState().getVariables().put("validationResult", isValid); - logger.info("验证结果: " + isValid); - } - } - - /** - * 完成 Action - */ - public static class CompleteAction implements Action { - @Override - public void execute(StateContext context) { - logger.info("=== Action: 任务完成 ==="); - logger.info("从状态: " + context.getSource().getId()); - logger.info("到状态: " + context.getTarget().getId()); - - // 清理资源或执行完成后的操作 - logger.info("清理资源..."); - } - } - - /** - * 失败 Action - */ - public static class FailAction implements Action { - @Override - public void execute(StateContext context) { - logger.warning("=== Action: 任务失败 ==="); - logger.warning("从状态: " + context.getSource().getId()); - logger.warning("到状态: " + context.getTarget().getId()); - - // 记录失败信息 - Exception exception = context.getException(); - if (exception != null) { - logger.warning("失败原因: " + exception.getMessage()); - } - } - } - - /** - * 重置 Action - */ - public static class ResetAction implements Action { - @Override - public void execute(StateContext context) { - logger.info("=== Action: 重置状态机 ==="); - - // 清理扩展状态中的变量 - context.getExtendedState().getVariables().clear(); - logger.info("状态机已重置"); - } - } -} diff --git a/src/main/java/com/tuoheng/status/statemachine/config/TaskStateMachineConfig.java b/src/main/java/com/tuoheng/status/statemachine/config/TaskStateMachineConfig.java deleted file mode 100644 index 7e63342..0000000 --- a/src/main/java/com/tuoheng/status/statemachine/config/TaskStateMachineConfig.java +++ /dev/null @@ -1,182 +0,0 @@ -package com.tuoheng.status.statemachine.config; - -import com.tuoheng.status.statemachine.action.TaskAction; -import com.tuoheng.status.statemachine.events.Event; -import com.tuoheng.status.statemachine.guard.TaskGuard; -import com.tuoheng.status.statemachine.listener.TaskStateMachineListener; -import com.tuoheng.status.statemachine.status.Status; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.statemachine.StateMachine; -import org.springframework.statemachine.config.StateMachineBuilder; -import org.springframework.statemachine.config.StateMachineFactory; -import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; -import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; -import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; - -import java.util.EnumSet; -import java.util.UUID; -import java.util.logging.Logger; - -/** - * 状态机配置类 - * 配置状态、子状态、转换、Action、Guard 等 - */ -@Configuration -public class TaskStateMachineConfig { - - private static final Logger logger = Logger.getLogger(TaskStateMachineConfig.class.getName()); - - /** - * 构建状态机工厂 - * 使用工厂模式可以为每个任务创建独立的状态机实例 - */ - @Bean - public StateMachineFactory stateMachineFactory() throws Exception { - - // 返回一个简单的 StateMachineFactory 实现 - return new StateMachineFactory() { - @Override - public StateMachine getStateMachine() { - return getStateMachine(UUID.randomUUID().toString()); - } - - @Override - public StateMachine getStateMachine(String machineId) { - try { - // 为每个请求创建新的状态机实例 - StateMachineBuilder.Builder newBuilder = StateMachineBuilder.builder(); - configureStateMachine(newBuilder); - configureStates(newBuilder); - configureTransitions(newBuilder); - StateMachine stateMachine = newBuilder.build(); - stateMachine.getExtendedState().getVariables().put("machineId", machineId); - return stateMachine; - } catch (Exception e) { - throw new RuntimeException("Failed to create state machine", e); - } - } - - @Override - public StateMachine getStateMachine(UUID uuid) { - return getStateMachine(uuid.toString()); - } - }; - } - - /** - * 配置状态机基本设置 - */ - private void configureStateMachine(StateMachineBuilder.Builder builder) throws Exception { - builder.configureConfiguration() - .withConfiguration() - .autoStartup(true) // 自动启动 - .listener(new TaskStateMachineListener()); // 添加监听器 - } - - /** - * 配置状态和子状态 - */ - private void configureStates(StateMachineBuilder.Builder builder) throws Exception { - builder.configureStates() - .withStates() - // 初始状态 - .initial(Status.IDLE) - - // 定义主状态 - .states(EnumSet.of(Status.IDLE, Status.COMPLETED, Status.FAILED)) - - // PROCESSING 是父状态,包含子状态 - .and() - .withStates() - .parent(Status.PROCESSING) // 设置 PROCESSING 为父状态 - .initial(Status.PREPARING) // 子状态的初始状态是 PREPARING - .states(EnumSet.of(Status.PREPARING, Status.EXECUTING, Status.VALIDATING)); - } - - /** - * 配置状态转换 - */ - private void configureTransitions(StateMachineBuilder.Builder builder) throws Exception { - builder.configureTransitions() - // IDLE -> PROCESSING (PREPARING) - .withExternal() - .source(Status.IDLE) - .target(Status.PREPARING) - .event(Event.START) - .action(new TaskAction.StartAction()) - .guard(new TaskGuard.CanStartGuard()) - .and() - - // PREPARING -> EXECUTING - .withExternal() - .source(Status.PREPARING) - .target(Status.EXECUTING) - .event(Event.PREPARE) - .action(new TaskAction.PrepareAction()) - .guard(new TaskGuard.PrepareCompleteGuard()) - .and() - - // EXECUTING -> VALIDATING - .withExternal() - .source(Status.EXECUTING) - .target(Status.VALIDATING) - .event(Event.EXECUTE) - .action(new TaskAction.ExecuteAction()) - .guard(new TaskGuard.ExecutionSuccessGuard()) - .and() - - // VALIDATING -> COMPLETED (从子状态退出到主状态) - .withExternal() - .source(Status.VALIDATING) - .target(Status.COMPLETED) - .event(Event.VALIDATE) - .action(new TaskAction.ValidateAction()) - .guard(new TaskGuard.ValidationPassGuard()) - .and() - - // 任何 PROCESSING 子状态 -> FAILED - .withExternal() - .source(Status.PREPARING) - .target(Status.FAILED) - .event(Event.FAIL) - .action(new TaskAction.FailAction()) - .and() - .withExternal() - .source(Status.EXECUTING) - .target(Status.FAILED) - .event(Event.FAIL) - .action(new TaskAction.FailAction()) - .and() - .withExternal() - .source(Status.VALIDATING) - .target(Status.FAILED) - .event(Event.FAIL) - .action(new TaskAction.FailAction()) - .and() - - // FAILED -> PREPARING (重试) - .withExternal() - .source(Status.FAILED) - .target(Status.PREPARING) - .event(Event.RETRY) - .action(new TaskAction.StartAction()) - .guard(new TaskGuard.CanRetryGuard()) - .and() - - // COMPLETED -> IDLE (重置) - .withExternal() - .source(Status.COMPLETED) - .target(Status.IDLE) - .event(Event.RESET) - .action(new TaskAction.ResetAction()) - .and() - - // FAILED -> IDLE (重置) - .withExternal() - .source(Status.FAILED) - .target(Status.IDLE) - .event(Event.RESET) - .action(new TaskAction.ResetAction()); - } -} diff --git a/src/main/java/com/tuoheng/status/statemachine/demo/StateMachineDemo.java b/src/main/java/com/tuoheng/status/statemachine/demo/StateMachineDemo.java deleted file mode 100644 index c5ebc39..0000000 --- a/src/main/java/com/tuoheng/status/statemachine/demo/StateMachineDemo.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.tuoheng.status.statemachine.demo; - -import com.tuoheng.status.statemachine.service.TaskStateMachineService; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; - -/** - * 状态机演示主类 - * 可以直接运行此类的 main 方法来查看状态机的运行效果 - */ -public class StateMachineDemo { - - public static void main(String[] args) { - // 创建 Spring 上下文 - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - - // 扫描配置类和服务类 - context.scan( - "com.tuoheng.status.statemachine.config", - "com.tuoheng.status.statemachine.service" - ); - context.refresh(); - - // 获取服务实例 - TaskStateMachineService service = context.getBean(TaskStateMachineService.class); - - System.out.println("\n\n"); - System.out.println("╔════════════════════════════════════════════════════════════╗"); - System.out.println("║ Spring StateMachine 完整示例演示 ║"); - System.out.println("║ 包含:子状态、Action、Guard、事件监听器 ║"); - System.out.println("╚════════════════════════════════════════════════════════════╝"); - System.out.println("\n"); - - // 演示1: 正常流程 - System.out.println("═══════════════════════════════════════════════════════════════"); - System.out.println("演示1: 正常流程"); - System.out.println("流程: IDLE -> PREPARING -> EXECUTING -> VALIDATING -> COMPLETED"); - System.out.println("═══════════════════════════════════════════════════════════════"); - service.startTask("demo-task-001"); - - // 等待一下 - try { - Thread.sleep(500); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - - // 演示2: 失败和重试 - System.out.println("\n"); - System.out.println("═══════════════════════════════════════════════════════════════"); - System.out.println("演示2: 失败和重试流程"); - System.out.println("流程: IDLE -> PREPARING -> FAILED -> PREPARING (重试)"); - System.out.println("═══════════════════════════════════════════════════════════════"); - service.demonstrateFailureAndRetry("demo-task-002"); - - // 等待一下 - try { - Thread.sleep(500); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - - // 演示3: Guard 条件不满足 - System.out.println("\n"); - System.out.println("═══════════════════════════════════════════════════════════════"); - System.out.println("演示3: Guard 条件不满足"); - System.out.println("说明: 不设置 taskId,Guard 会阻止状态转换"); - System.out.println("═══════════════════════════════════════════════════════════════"); - service.demonstrateGuardFailure("demo-task-003"); - - // 等待一下 - try { - Thread.sleep(500); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - - // 演示4: 重置流程 - System.out.println("\n"); - System.out.println("═══════════════════════════════════════════════════════════════"); - System.out.println("演示4: 重置流程"); - System.out.println("流程: COMPLETED -> IDLE (重置)"); - System.out.println("═══════════════════════════════════════════════════════════════"); - service.demonstrateReset("demo-task-004"); - - System.out.println("\n\n"); - System.out.println("╔════════════════════════════════════════════════════════════╗"); - System.out.println("║ 所有演示完成! ║"); - System.out.println("╚════════════════════════════════════════════════════════════╝"); - System.out.println("\n"); - - // 关闭上下文 - context.close(); - } -} diff --git a/src/main/java/com/tuoheng/status/statemachine/demo/StateMachineManagerDemo.java b/src/main/java/com/tuoheng/status/statemachine/demo/StateMachineManagerDemo.java deleted file mode 100644 index 619d4f9..0000000 --- a/src/main/java/com/tuoheng/status/statemachine/demo/StateMachineManagerDemo.java +++ /dev/null @@ -1,161 +0,0 @@ -package com.tuoheng.status.statemachine.demo; - -import com.tuoheng.status.statemachine.events.Event; -import com.tuoheng.status.statemachine.manager.StateMachineManager; -import com.tuoheng.status.statemachine.service.StateMachineManagerService; -import com.tuoheng.status.statemachine.status.Status; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; - -import java.util.Set; - -/** - * 状态机管理器演示类 - * 演示如何通过ID管理多个状态机实例 - */ -public class StateMachineManagerDemo { - - public static void main(String[] args) { - // 创建 Spring 上下文 - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - context.scan( - "com.tuoheng.status.statemachine.config", - "com.tuoheng.status.statemachine.manager", - "com.tuoheng.status.statemachine.service" - ); - context.refresh(); - - // 获取服务实例 - StateMachineManagerService service = context.getBean(StateMachineManagerService.class); - StateMachineManager manager = context.getBean(StateMachineManager.class); - - System.out.println("\n\n"); - System.out.println("╔════════════════════════════════════════════════════════════╗"); - System.out.println("║ Spring StateMachine 管理器演示 ║"); - System.out.println("║ 演示如何通过ID管理多个状态机实例 ║"); - System.out.println("╚════════════════════════════════════════════════════════════╝"); - System.out.println("\n"); - - // 演示1: 创建多个状态机 - System.out.println("═══════════════════════════════════════════════════════════════"); - System.out.println("演示1: 创建多个状态机实例"); - System.out.println("═══════════════════════════════════════════════════════════════"); - - String machineId1 = "machine-001"; - String machineId2 = "machine-002"; - String machineId3 = "machine-003"; - - // 创建状态机 - service.startTask(machineId1, "task-001"); - service.startTask(machineId2, "task-002"); - service.startTask(machineId3, "task-003"); - - System.out.println("创建了3个状态机:"); - System.out.println(" - " + machineId1); - System.out.println(" - " + machineId2); - System.out.println(" - " + machineId3); - System.out.println("管理的状态机数量: " + manager.getStateMachineCount()); - - // 等待状态转换 - try { - Thread.sleep(200); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - - // 演示2: 查询不同状态机的状态 - System.out.println("\n"); - System.out.println("═══════════════════════════════════════════════════════════════"); - System.out.println("演示2: 查询不同状态机的当前状态"); - System.out.println("═══════════════════════════════════════════════════════════════"); - - Status status1 = service.getCurrentStatus(machineId1); - Status status2 = service.getCurrentStatus(machineId2); - Status status3 = service.getCurrentStatus(machineId3); - - System.out.println("状态机 " + machineId1 + " 当前状态: " + status1); - System.out.println("状态机 " + machineId2 + " 当前状态: " + status2); - System.out.println("状态机 " + machineId3 + " 当前状态: " + status3); - - // 演示3: 获取状态机详细信息 - System.out.println("\n"); - System.out.println("═══════════════════════════════════════════════════════════════"); - System.out.println("演示3: 获取状态机详细信息"); - System.out.println("═══════════════════════════════════════════════════════════════"); - - StateMachineManager.StateMachineInfo info1 = service.getStateMachineInfo(machineId1); - System.out.println("状态机1详细信息:"); - System.out.println(" ID: " + info1.getMachineId()); - System.out.println(" 当前状态: " + info1.getCurrentStatus()); - System.out.println(" 是否子状态: " + info1.isSubState()); - System.out.println(" 是否运行中: " + info1.isRunning()); - System.out.println(" 扩展状态: " + info1.getExtendedState()); - - // 演示4: 为不同状态机发送不同事件 - System.out.println("\n"); - System.out.println("═══════════════════════════════════════════════════════════════"); - System.out.println("演示4: 为不同状态机发送不同事件"); - System.out.println("═══════════════════════════════════════════════════════════════"); - - // 为 machine-001 继续处理 - manager.getStateMachine(machineId1) - .getExtendedState().getVariables().put("prepared", true); - service.sendEvent(machineId1, Event.PREPARE); - - // 为 machine-002 发送失败事件 - service.sendEvent(machineId2, Event.FAIL); - - try { - Thread.sleep(200); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - - System.out.println("machine-001 发送 PREPARE 事件后状态: " + service.getCurrentStatus(machineId1)); - System.out.println("machine-002 发送 FAIL 事件后状态: " + service.getCurrentStatus(machineId2)); - System.out.println("machine-003 状态保持不变: " + service.getCurrentStatus(machineId3)); - - // 演示5: 获取所有状态机ID - System.out.println("\n"); - System.out.println("═══════════════════════════════════════════════════════════════"); - System.out.println("演示5: 获取所有状态机ID"); - System.out.println("═══════════════════════════════════════════════════════════════"); - - Set allIds = service.getAllMachineIds(); - System.out.println("所有状态机ID: " + allIds); - System.out.println("状态机数量: " + allIds.size()); - - // 演示6: 通过ID直接获取状态机并操作 - System.out.println("\n"); - System.out.println("═══════════════════════════════════════════════════════════════"); - System.out.println("演示6: 通过ID直接获取状态机并操作"); - System.out.println("═══════════════════════════════════════════════════════════════"); - - var stateMachine = manager.getStateMachine(machineId1); - if (stateMachine != null) { - System.out.println("成功获取状态机: " + machineId1); - System.out.println("当前状态: " + stateMachine.getState().getId()); - System.out.println("扩展状态中的 taskId: " + - stateMachine.getExtendedState().getVariables().get("taskId")); - } - - // 演示7: 移除状态机 - System.out.println("\n"); - System.out.println("═══════════════════════════════════════════════════════════════"); - System.out.println("演示7: 移除状态机"); - System.out.println("═══════════════════════════════════════════════════════════════"); - - boolean removed = service.removeStateMachine(machineId3); - System.out.println("移除状态机 " + machineId3 + ": " + (removed ? "成功" : "失败")); - System.out.println("移除后管理的状态机数量: " + manager.getStateMachineCount()); - System.out.println("状态机 " + machineId3 + " 是否存在: " + service.exists(machineId3)); - - System.out.println("\n\n"); - System.out.println("╔════════════════════════════════════════════════════════════╗"); - System.out.println("║ 所有演示完成! ║"); - System.out.println("╚════════════════════════════════════════════════════════════╝"); - System.out.println("\n"); - - // 关闭上下文 - context.close(); - } -} diff --git a/src/main/java/com/tuoheng/status/statemachine/events/Event.java b/src/main/java/com/tuoheng/status/statemachine/events/Event.java deleted file mode 100644 index 80a4925..0000000 --- a/src/main/java/com/tuoheng/status/statemachine/events/Event.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.tuoheng.status.statemachine.events; - -/** - * 事件枚举 - */ -public enum Event { - START, // 开始处理 - PREPARE, // 准备就绪 - EXECUTE, // 执行 - VALIDATE, // 验证 - COMPLETE, // 完成 - FAIL, // 失败 - RETRY, // 重试 - RESET // 重置 -} diff --git a/src/main/java/com/tuoheng/status/statemachine/guard/TaskGuard.java b/src/main/java/com/tuoheng/status/statemachine/guard/TaskGuard.java deleted file mode 100644 index b0bb45c..0000000 --- a/src/main/java/com/tuoheng/status/statemachine/guard/TaskGuard.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.tuoheng.status.statemachine.guard; - -import com.tuoheng.status.statemachine.events.Event; -import com.tuoheng.status.statemachine.status.Status; -import org.springframework.statemachine.StateContext; -import org.springframework.statemachine.guard.Guard; - -import java.util.logging.Logger; - -/** - * 任务 Guard 示例 - * Guard 用于条件判断,决定是否允许状态转换 - */ -public class TaskGuard { - - private static final Logger logger = Logger.getLogger(TaskGuard.class.getName()); - - /** - * 检查是否可以开始处理 - */ - public static class CanStartGuard implements Guard { - @Override - public boolean evaluate(StateContext context) { - logger.info("=== Guard: 检查是否可以开始处理 ==="); - - // 检查是否有任务ID - Object taskId = context.getExtendedState().getVariables().get("taskId"); - boolean canStart = taskId != null; - - logger.info("任务ID存在: " + canStart); - return canStart; - } - } - - /** - * 检查准备是否完成 - */ - public static class PrepareCompleteGuard implements Guard { - @Override - public boolean evaluate(StateContext context) { - logger.info("=== Guard: 检查准备是否完成 ==="); - - // 检查准备状态 - Object prepared = context.getExtendedState().getVariables().get("prepared"); - boolean isPrepared = prepared != null && Boolean.TRUE.equals(prepared); - - logger.info("准备完成: " + isPrepared); - return isPrepared; - } - } - - /** - * 检查执行是否成功 - */ - public static class ExecutionSuccessGuard implements Guard { - @Override - public boolean evaluate(StateContext context) { - logger.info("=== Guard: 检查执行是否成功 ==="); - - // 检查执行结果 - Object result = context.getExtendedState().getVariables().get("executionResult"); - boolean isSuccess = result != null && "success".equals(result); - - logger.info("执行成功: " + isSuccess); - return isSuccess; - } - } - - /** - * 检查验证是否通过 - */ - public static class ValidationPassGuard implements Guard { - @Override - public boolean evaluate(StateContext context) { - logger.info("=== Guard: 检查验证是否通过 ==="); - - // 检查验证结果 - Object validationResult = context.getExtendedState().getVariables().get("validationResult"); - boolean isValid = validationResult != null && Boolean.TRUE.equals(validationResult); - - logger.info("验证通过: " + isValid); - return isValid; - } - } - - /** - * 检查是否可以重试 - */ - public static class CanRetryGuard implements Guard { - @Override - public boolean evaluate(StateContext context) { - logger.info("=== Guard: 检查是否可以重试 ==="); - - // 检查重试次数 - Integer retryCount = (Integer) context.getExtendedState().getVariables().getOrDefault("retryCount", 0); - boolean canRetry = retryCount < 3; // 最多重试3次 - - logger.info("当前重试次数: " + retryCount + ", 可以重试: " + canRetry); - return canRetry; - } - } - - /** - * 总是返回 true 的 Guard(用于无条件转换) - */ - public static class AlwaysTrueGuard implements Guard { - @Override - public boolean evaluate(StateContext context) { - return true; - } - } -} diff --git a/src/main/java/com/tuoheng/status/statemachine/listener/TaskStateMachineListener.java b/src/main/java/com/tuoheng/status/statemachine/listener/TaskStateMachineListener.java deleted file mode 100644 index a097901..0000000 --- a/src/main/java/com/tuoheng/status/statemachine/listener/TaskStateMachineListener.java +++ /dev/null @@ -1,157 +0,0 @@ -package com.tuoheng.status.statemachine.listener; - -import com.tuoheng.status.statemachine.events.Event; -import com.tuoheng.status.statemachine.status.Status; -import org.springframework.statemachine.StateContext; -import org.springframework.statemachine.StateMachine; -import org.springframework.statemachine.listener.StateMachineListener; -import org.springframework.statemachine.state.State; -import org.springframework.statemachine.transition.Transition; - -import java.util.logging.Logger; - -/** - * 状态机事件监听器 - * 监听状态机的各种事件,如状态变化、转换等 - */ -public class TaskStateMachineListener implements StateMachineListener { - - private static final Logger logger = Logger.getLogger(TaskStateMachineListener.class.getName()); - - /** - * 状态机启动时调用 - */ - @Override - public void stateMachineStarted(StateMachine stateMachine) { - logger.info("========== 状态机启动 =========="); - if (stateMachine.getInitialState() != null) { - logger.info("初始状态: " + stateMachine.getInitialState().getId()); - } else { - logger.info("初始状态: null"); - } - } - - /** - * 状态机停止时调用 - */ - @Override - public void stateMachineStopped(StateMachine stateMachine) { - logger.info("========== 状态机停止 =========="); - if (stateMachine.getState() != null) { - logger.info("最终状态: " + stateMachine.getState().getId()); - } else { - logger.info("最终状态: null"); - } - } - - /** - * 状态机错误时调用 - */ - @Override - public void stateMachineError(StateMachine stateMachine, Exception exception) { - logger.severe("========== 状态机错误 =========="); - logger.severe("错误信息: " + exception.getMessage()); - exception.printStackTrace(); - } - - /** - * 状态进入时调用 - */ - @Override - public void stateEntered(State state) { - logger.info("========== 进入状态: " + state.getId() + " =========="); - } - - /** - * 状态退出时调用 - */ - @Override - public void stateExited(State state) { - logger.info("========== 退出状态: " + state.getId() + " =========="); - } - - /** - * 状态改变时调用 - */ - @Override - public void stateChanged(State from, State to) { - logger.info("========== 状态改变 =========="); - logger.info("从: " + (from != null ? from.getId() : "null")); - logger.info("到: " + (to != null ? to.getId() : "null")); - } - - /** - * 转换开始时调用 - */ - @Override - public void transitionStarted(Transition transition) { - logger.info("========== 转换开始 =========="); - logger.info("从: " + (transition.getSource() != null ? transition.getSource().getId() : "null")); - logger.info("到: " + (transition.getTarget() != null ? transition.getTarget().getId() : "null")); - // 检查 trigger 是否为 null(某些转换可能没有 trigger,比如初始转换) - if (transition.getTrigger() != null) { - logger.info("事件: " + transition.getTrigger().getEvent()); - } else { - logger.info("事件: null (可能是初始转换或内部转换)"); - } - } - - /** - * 转换结束时调用 - */ - @Override - public void transitionEnded(Transition transition) { - logger.info("========== 转换结束 =========="); - logger.info("从: " + (transition.getSource() != null ? transition.getSource().getId() : "null")); - logger.info("到: " + (transition.getTarget() != null ? transition.getTarget().getId() : "null")); - // 检查 trigger 是否为 null - if (transition.getTrigger() != null) { - logger.info("事件: " + transition.getTrigger().getEvent()); - } else { - logger.info("事件: null (可能是初始转换或内部转换)"); - } - } - - /** - * 转换选择时调用 - */ - @Override - public void transition(Transition transition) { - logger.info("========== 转换选择 =========="); - logger.info("从: " + (transition.getSource() != null ? transition.getSource().getId() : "null")); - logger.info("到: " + (transition.getTarget() != null ? transition.getTarget().getId() : "null")); - // 检查 trigger 是否为 null - if (transition.getTrigger() != null) { - logger.info("事件: " + transition.getTrigger().getEvent()); - } else { - logger.info("事件: null (可能是初始转换或内部转换)"); - } - } - - /** - * 事件未接受时调用(没有匹配的转换) - */ - @Override - public void eventNotAccepted(org.springframework.messaging.Message event) { - logger.warning("========== 事件未接受 =========="); - logger.warning("事件: " + event.getPayload()); - logger.warning("当前状态可能不支持此事件"); - } - - /** - * 扩展状态改变时调用 - */ - @Override - public void extendedStateChanged(Object key, Object value) { - logger.info("========== 扩展状态改变 =========="); - logger.info("键: " + key + ", 值: " + value); - } - - /** - * 状态上下文入口时调用 - */ - @Override - public void stateContext(StateContext stateContext) { - // 可以在这里记录状态上下文信息 - } -} diff --git a/src/main/java/com/tuoheng/status/statemachine/manager/StateMachineManager.java b/src/main/java/com/tuoheng/status/statemachine/manager/StateMachineManager.java deleted file mode 100644 index 641bdd9..0000000 --- a/src/main/java/com/tuoheng/status/statemachine/manager/StateMachineManager.java +++ /dev/null @@ -1,262 +0,0 @@ -package com.tuoheng.status.statemachine.manager; - -import com.tuoheng.status.statemachine.events.Event; -import com.tuoheng.status.statemachine.status.Status; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.statemachine.StateMachine; -import org.springframework.statemachine.config.StateMachineFactory; -import org.springframework.statemachine.state.State; -import org.springframework.stereotype.Component; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Logger; - -/** - * 状态机管理器 - * 用于管理多个状态机实例,通过ID获取和查询状态机 - */ -@Component -public class StateMachineManager { - - private static final Logger logger = Logger.getLogger(StateMachineManager.class.getName()); - - @Autowired - private StateMachineFactory stateMachineFactory; - - // 存储状态机实例的Map,key为状态机ID - private final Map> stateMachineMap = new ConcurrentHashMap<>(); - - /** - * 创建或获取状态机实例 - * 如果ID对应的状态机不存在,则创建新实例;如果存在,则返回现有实例 - * - * @param machineId 状态机ID - * @return 状态机实例 - */ - public StateMachine getOrCreateStateMachine(String machineId) { - return stateMachineMap.computeIfAbsent(machineId, id -> { - logger.info("创建新状态机实例,ID: " + id); - StateMachine stateMachine = stateMachineFactory.getStateMachine(id); - // 将machineId存储到扩展状态中 - stateMachine.getExtendedState().getVariables().put("machineId", id); - // 启动状态机 - stateMachine.start(); - return stateMachine; - }); - } - - /** - * 获取状态机实例(如果不存在则返回null) - * - * @param machineId 状态机ID - * @return 状态机实例,如果不存在则返回null - */ - public StateMachine getStateMachine(String machineId) { - return stateMachineMap.get(machineId); - } - - /** - * 创建新的状态机实例(如果已存在则先停止并移除旧的) - * - * @param machineId 状态机ID - * @return 新创建的状态机实例 - */ - public StateMachine createStateMachine(String machineId) { - // 如果已存在,先停止并移除 - StateMachine existing = stateMachineMap.remove(machineId); - if (existing != null) { - logger.info("停止并移除已存在的状态机,ID: " + machineId); - try { - existing.stop(); - } catch (Exception e) { - logger.warning("停止状态机时发生错误: " + e.getMessage()); - } - } - - // 创建新实例 - logger.info("创建新状态机实例,ID: " + machineId); - StateMachine stateMachine = stateMachineFactory.getStateMachine(machineId); - stateMachine.getExtendedState().getVariables().put("machineId", machineId); - stateMachine.start(); - stateMachineMap.put(machineId, stateMachine); - return stateMachine; - } - - /** - * 获取状态机的当前状态 - * - * @param machineId 状态机ID - * @return 当前状态,如果状态机不存在则返回null - */ - public Status getCurrentStatus(String machineId) { - StateMachine stateMachine = stateMachineMap.get(machineId); - if (stateMachine == null) { - logger.warning("状态机不存在,ID: " + machineId); - return null; - } - - State state = stateMachine.getState(); - if (state == null) { - return null; - } - - return state.getId(); - } - - /** - * 获取状态机的详细信息(包括当前状态和扩展状态) - * - * @param machineId 状态机ID - * @return 状态机信息,如果状态机不存在则返回null - */ - public StateMachineInfo getStateMachineInfo(String machineId) { - StateMachine stateMachine = stateMachineMap.get(machineId); - if (stateMachine == null) { - return null; - } - - StateMachineInfo info = new StateMachineInfo(); - info.setMachineId(machineId); - - State state = stateMachine.getState(); - if (state != null) { - info.setCurrentStatus(state.getId()); - info.setIsSubState(state.isSubmachineState()); - } - - info.setExtendedState(stateMachine.getExtendedState().getVariables()); - // 通过检查状态机是否有状态来判断是否在运行 - // Spring StateMachine 3.2.0 没有 isRunning() 方法,使用状态判断 - info.setIsRunning(state != null); - - return info; - } - - /** - * 移除状态机实例 - * - * @param machineId 状态机ID - * @return 是否成功移除 - */ - public boolean removeStateMachine(String machineId) { - StateMachine stateMachine = stateMachineMap.remove(machineId); - if (stateMachine != null) { - logger.info("移除状态机实例,ID: " + machineId); - try { - stateMachine.stop(); - return true; - } catch (Exception e) { - logger.warning("停止状态机时发生错误: " + e.getMessage()); - return false; - } - } - return false; - } - - /** - * 检查状态机是否存在 - * - * @param machineId 状态机ID - * @return 是否存在 - */ - public boolean exists(String machineId) { - return stateMachineMap.containsKey(machineId); - } - - /** - * 获取所有状态机的ID列表 - * - * @return 状态机ID集合 - */ - public java.util.Set getAllMachineIds() { - return stateMachineMap.keySet(); - } - - /** - * 获取当前管理的状态机数量 - * - * @return 状态机数量 - */ - public int getStateMachineCount() { - return stateMachineMap.size(); - } - - /** - * 清空所有状态机实例 - */ - public void clearAll() { - logger.info("清空所有状态机实例,数量: " + stateMachineMap.size()); - for (Map.Entry> entry : stateMachineMap.entrySet()) { - try { - entry.getValue().stop(); - } catch (Exception e) { - logger.warning("停止状态机时发生错误,ID: " + entry.getKey() + ", 错误: " + e.getMessage()); - } - } - stateMachineMap.clear(); - } - - /** - * 状态机信息类 - */ - public static class StateMachineInfo { - private String machineId; - private Status currentStatus; - private boolean isSubState; - private boolean isRunning; - private Map extendedState; - - // Getters and Setters - public String getMachineId() { - return machineId; - } - - public void setMachineId(String machineId) { - this.machineId = machineId; - } - - public Status getCurrentStatus() { - return currentStatus; - } - - public void setCurrentStatus(Status currentStatus) { - this.currentStatus = currentStatus; - } - - public boolean isSubState() { - return isSubState; - } - - public void setIsSubState(boolean subState) { - isSubState = subState; - } - - public boolean isRunning() { - return isRunning; - } - - public void setIsRunning(boolean running) { - isRunning = running; - } - - public Map getExtendedState() { - return extendedState; - } - - public void setExtendedState(Map extendedState) { - this.extendedState = extendedState; - } - - @Override - public String toString() { - return "StateMachineInfo{" + - "machineId='" + machineId + '\'' + - ", currentStatus=" + currentStatus + - ", isSubState=" + isSubState + - ", isRunning=" + isRunning + - ", extendedState=" + extendedState + - '}'; - } - } -} diff --git a/src/main/java/com/tuoheng/status/statemachine/service/StateMachineManagerService.java b/src/main/java/com/tuoheng/status/statemachine/service/StateMachineManagerService.java deleted file mode 100644 index 717d863..0000000 --- a/src/main/java/com/tuoheng/status/statemachine/service/StateMachineManagerService.java +++ /dev/null @@ -1,128 +0,0 @@ -package com.tuoheng.status.statemachine.service; - -import com.tuoheng.status.statemachine.events.Event; -import com.tuoheng.status.statemachine.manager.StateMachineManager; -import com.tuoheng.status.statemachine.status.Status; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.statemachine.StateMachine; -import org.springframework.stereotype.Service; - -import java.util.Set; -import java.util.logging.Logger; - -/** - * 状态机管理器服务类 - * 提供便捷的方法来使用状态机管理器 - */ -@Service -public class StateMachineManagerService { - - private static final Logger logger = Logger.getLogger(StateMachineManagerService.class.getName()); - - @Autowired - private StateMachineManager stateMachineManager; - - /** - * 通过ID获取状态机并发送事件 - * - * @param machineId 状态机ID - * @param event 事件 - * @return 事件是否被接受 - */ - public boolean sendEvent(String machineId, Event event) { - StateMachine stateMachine = stateMachineManager.getStateMachine(machineId); - if (stateMachine == null) { - logger.warning("状态机不存在,ID: " + machineId); - return false; - } - return stateMachine.sendEvent(event); - } - - /** - * 通过ID获取状态机的当前状态 - * - * @param machineId 状态机ID - * @return 当前状态 - */ - public Status getCurrentStatus(String machineId) { - return stateMachineManager.getCurrentStatus(machineId); - } - - /** - * 通过ID获取状态机的详细信息 - * - * @param machineId 状态机ID - * @return 状态机信息 - */ - public StateMachineManager.StateMachineInfo getStateMachineInfo(String machineId) { - return stateMachineManager.getStateMachineInfo(machineId); - } - - /** - * 创建或获取状态机,并发送START事件 - * - * @param machineId 状态机ID - * @param taskId 任务ID(可选) - * @return 是否成功 - */ - public boolean startTask(String machineId, String taskId) { - StateMachine stateMachine = stateMachineManager.getOrCreateStateMachine(machineId); - - // 设置任务ID到扩展状态 - if (taskId != null) { - stateMachine.getExtendedState().getVariables().put("taskId", taskId); - } - - return stateMachine.sendEvent(Event.START); - } - - /** - * 获取所有状态机的ID列表 - * - * @return 状态机ID集合 - */ - public Set getAllMachineIds() { - return stateMachineManager.getAllMachineIds(); - } - - /** - * 移除状态机 - * - * @param machineId 状态机ID - * @return 是否成功移除 - */ - public boolean removeStateMachine(String machineId) { - return stateMachineManager.removeStateMachine(machineId); - } - - /** - * 检查状态机是否存在 - * - * @param machineId 状态机ID - * @return 是否存在 - */ - public boolean exists(String machineId) { - return stateMachineManager.exists(machineId); - } - - /** - * 打印状态机信息(用于调试) - * - * @param machineId 状态机ID - */ - public void printStateMachineInfo(String machineId) { - StateMachineManager.StateMachineInfo info = stateMachineManager.getStateMachineInfo(machineId); - if (info == null) { - logger.warning("状态机不存在,ID: " + machineId); - return; - } - - logger.info("========== 状态机信息 =========="); - logger.info("ID: " + info.getMachineId()); - logger.info("当前状态: " + info.getCurrentStatus()); - logger.info("是否子状态: " + info.isSubState()); - logger.info("是否运行中: " + info.isRunning()); - logger.info("扩展状态: " + info.getExtendedState()); - logger.info("================================"); - } -} diff --git a/src/main/java/com/tuoheng/status/statemachine/service/TaskStateMachineService.java b/src/main/java/com/tuoheng/status/statemachine/service/TaskStateMachineService.java deleted file mode 100644 index beba3ef..0000000 --- a/src/main/java/com/tuoheng/status/statemachine/service/TaskStateMachineService.java +++ /dev/null @@ -1,194 +0,0 @@ -package com.tuoheng.status.statemachine.service; - -import com.tuoheng.status.statemachine.events.Event; -import com.tuoheng.status.statemachine.status.Status; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.statemachine.StateMachine; -import org.springframework.statemachine.config.StateMachineFactory; -import org.springframework.stereotype.Service; - -import java.util.logging.Logger; - -/** - * 任务状态机服务示例 - * 演示如何使用状态机 - */ -@Service -public class TaskStateMachineService { - - private static final Logger logger = Logger.getLogger(TaskStateMachineService.class.getName()); - - @Autowired - private StateMachineFactory stateMachineFactory; - - /** - * 启动任务处理流程 - */ - public void startTask(String taskId) { - logger.info("\n========== 开始任务处理流程 =========="); - - StateMachine stateMachine = stateMachineFactory.getStateMachine(); - - try { - // 设置任务ID到扩展状态 - stateMachine.getExtendedState().getVariables().put("taskId", taskId); - stateMachine.getExtendedState().getVariables().put("retryCount", 0); - - // 启动状态机 - stateMachine.start(); - logger.info("状态机已启动,当前状态: " + stateMachine.getState().getId()); - - // 发送 START 事件 - logger.info("\n--- 发送 START 事件 ---"); - stateMachine.sendEvent(Event.START); - logger.info("当前状态: " + stateMachine.getState().getId()); - - // 模拟准备完成 - Thread.sleep(100); - stateMachine.getExtendedState().getVariables().put("prepared", true); - logger.info("\n--- 发送 PREPARE 事件 ---"); - stateMachine.sendEvent(Event.PREPARE); - logger.info("当前状态: " + stateMachine.getState().getId()); - - // 模拟执行完成(在发送事件之前设置结果,以便 Guard 可以检查) - Thread.sleep(200); - stateMachine.getExtendedState().getVariables().put("executionResult", "success"); - logger.info("\n--- 发送 EXECUTE 事件 ---"); - stateMachine.sendEvent(Event.EXECUTE); - logger.info("当前状态: " + stateMachine.getState().getId()); - - // 模拟验证完成(在发送事件之前设置验证结果,以便 Guard 可以检查) - Thread.sleep(100); - stateMachine.getExtendedState().getVariables().put("validationResult", true); - logger.info("\n--- 发送 VALIDATE 事件 ---"); - stateMachine.sendEvent(Event.VALIDATE); - logger.info("当前状态: " + stateMachine.getState().getId()); - - logger.info("\n========== 任务处理流程完成 ==========\n"); - - } catch (Exception e) { - logger.severe("处理任务时发生错误: " + e.getMessage()); - e.printStackTrace(); - } finally { - // 停止状态机 - stateMachine.stop(); - } - } - - /** - * 演示失败和重试流程 - */ - public void demonstrateFailureAndRetry(String taskId) { - logger.info("\n========== 演示失败和重试流程 =========="); - - StateMachine stateMachine = stateMachineFactory.getStateMachine(); - - try { - // 设置任务ID - stateMachine.getExtendedState().getVariables().put("taskId", taskId); - stateMachine.getExtendedState().getVariables().put("retryCount", 0); - - // 启动状态机 - stateMachine.start(); - logger.info("状态机已启动,当前状态: " + stateMachine.getState().getId()); - - // 发送 START 事件 - logger.info("\n--- 发送 START 事件 ---"); - stateMachine.sendEvent(Event.START); - logger.info("当前状态: " + stateMachine.getState().getId()); - - // 模拟失败 - logger.info("\n--- 发送 FAIL 事件(模拟失败)---"); - stateMachine.sendEvent(Event.FAIL); - logger.info("当前状态: " + stateMachine.getState().getId()); - - // 增加重试次数 - Integer retryCount = (Integer) stateMachine.getExtendedState().getVariables().getOrDefault("retryCount", 0); - stateMachine.getExtendedState().getVariables().put("retryCount", retryCount + 1); - - // 重试 - logger.info("\n--- 发送 RETRY 事件 ---"); - stateMachine.sendEvent(Event.RETRY); - logger.info("当前状态: " + stateMachine.getState().getId()); - - logger.info("\n========== 失败和重试流程演示完成 ==========\n"); - - } catch (Exception e) { - logger.severe("演示失败和重试流程时发生错误: " + e.getMessage()); - e.printStackTrace(); - } finally { - stateMachine.stop(); - } - } - - /** - * 演示 Guard 条件不满足的情况 - */ - public void demonstrateGuardFailure(String taskId) { - logger.info("\n========== 演示 Guard 条件不满足 =========="); - - StateMachine stateMachine = stateMachineFactory.getStateMachine(); - - try { - // 不设置 taskId,导致 CanStartGuard 失败 - // stateMachine.getExtendedState().getVariables().put("taskId", taskId); - - // 启动状态机 - stateMachine.start(); - logger.info("状态机已启动,当前状态: " + stateMachine.getState().getId()); - - // 发送 START 事件(应该失败,因为 Guard 条件不满足) - logger.info("\n--- 发送 START 事件(没有 taskId,Guard 应该失败)---"); - boolean accepted = stateMachine.sendEvent(Event.START); - logger.info("事件是否被接受: " + accepted); - logger.info("当前状态: " + stateMachine.getState().getId()); - - logger.info("\n========== Guard 条件不满足演示完成 ==========\n"); - - } catch (Exception e) { - logger.severe("演示 Guard 失败时发生错误: " + e.getMessage()); - e.printStackTrace(); - } finally { - stateMachine.stop(); - } - } - - /** - * 演示重置流程 - */ - public void demonstrateReset(String taskId) { - logger.info("\n========== 演示重置流程 =========="); - - StateMachine stateMachine = stateMachineFactory.getStateMachine(); - - try { - // 设置任务ID - stateMachine.getExtendedState().getVariables().put("taskId", taskId); - - // 启动状态机并完成流程 - stateMachine.start(); - stateMachine.sendEvent(Event.START); - stateMachine.getExtendedState().getVariables().put("prepared", true); - stateMachine.sendEvent(Event.PREPARE); - stateMachine.getExtendedState().getVariables().put("executionResult", "success"); - stateMachine.sendEvent(Event.EXECUTE); - stateMachine.getExtendedState().getVariables().put("validationResult", true); - stateMachine.sendEvent(Event.VALIDATE); - - logger.info("当前状态: " + stateMachine.getState().getId()); - - // 重置 - logger.info("\n--- 发送 RESET 事件 ---"); - stateMachine.sendEvent(Event.RESET); - logger.info("当前状态: " + stateMachine.getState().getId()); - - logger.info("\n========== 重置流程演示完成 ==========\n"); - - } catch (Exception e) { - logger.severe("演示重置流程时发生错误: " + e.getMessage()); - e.printStackTrace(); - } finally { - stateMachine.stop(); - } - } -} diff --git a/src/main/java/com/tuoheng/status/statemachine/status/Status.java b/src/main/java/com/tuoheng/status/statemachine/status/Status.java deleted file mode 100644 index 3f86a48..0000000 --- a/src/main/java/com/tuoheng/status/statemachine/status/Status.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.tuoheng.status.statemachine.status; - -/** - * 状态枚举 - * 主状态:IDLE, PROCESSING, COMPLETED, FAILED - * PROCESSING 包含子状态:PREPARING, EXECUTING, VALIDATING - */ -public enum Status { - // 主状态 - IDLE, // 空闲状态 - PROCESSING, // 处理中(父状态,包含子状态) - COMPLETED, // 已完成 - FAILED, // 失败 - - // PROCESSING 的子状态 - PREPARING, // 准备中(PROCESSING 的子状态) - EXECUTING, // 执行中(PROCESSING 的子状态) - VALIDATING // 验证中(PROCESSING 的子状态) -} diff --git a/src/main/java/com/tuoheng/status/statemachine/无人机控制状态机设计文档.md b/src/main/java/com/tuoheng/status/statemachine/无人机控制状态机设计文档.md deleted file mode 100644 index 6c83ec0..0000000 --- a/src/main/java/com/tuoheng/status/statemachine/无人机控制状态机设计文档.md +++ /dev/null @@ -1,765 +0,0 @@ -# 大疆无人机巢状态机设计文档 - -## 一、概述 - -本文档基于大疆无人机巢功能统计清单,设计了一套完整的状态机模型,用于管理无人机和机巢的各种状态转换,确保操作的安全性和正确性。 - -### 1.1 设计目标 - -- **状态管理**:清晰定义无人机和机巢的所有可能状态 -- **状态转换**:明确状态之间的转换条件和规则 -- **业务规则**:将业务限制条件转化为状态机 Guard 和 Action -- **安全控制**:通过状态机确保操作的安全执行顺序 - -### 1.2 状态机层次结构 - -状态机采用**分层设计**,包含以下层次: - -1. **顶层状态机**:机巢整体状态 -2. **机巢子状态机**:机巢设备状态(舱门、电源等) -3. **无人机子状态机**:无人机状态(电源、飞行、任务等) -4. **模式状态机**:DRC模式、调试模式等 - -## 二、状态定义 - -### 2.1 机巢状态(AirportState) - -#### 主状态 - -| 状态 | 枚举值 | 说明 | -|------|--------|------| -| 离线 | `OFFLINE` | 机巢设备离线,无法通信 | -| 在线 | `ONLINE` | 机巢设备在线,可接收指令 | -| 重启中 | `REBOOTING` | 机巢正在重启 | - -#### 子状态(ONLINE 的子状态) - -| 状态 | 枚举值 | 说明 | -|------|--------|------| -| 待机 | `STANDBY` | 机巢待机状态(初始子状态) | -| 调试模式 | `DEBUG_MODE` | 调试模式开启中 | -| 操作中 | `OPERATING` | 正在执行操作(开舱、关舱等) | - -#### 舱门状态(OPERATING 的子状态) - -| 状态 | 枚举值 | 说明 | -|------|--------|------| -| 已关闭 | `COVER_CLOSED` | 舱门已关闭 | -| 已打开 | `COVER_OPENED` | 舱门已打开 | -| 半开 | `COVER_HALF_OPEN` | 舱门半开 | -| 状态异常 | `COVER_ERROR` | 舱门状态异常 | -| 开舱中 | `COVER_OPENING` | 正在打开舱门 | -| 关舱中 | `COVER_CLOSING` | 正在关闭舱门 | - -### 2.2 无人机状态(DroneState) - -#### 主状态 - -| 状态 | 枚举值 | 说明 | -|------|--------|------| -| 关机 | `POWER_OFF` | 无人机电源关闭(初始状态) | -| 开机 | `POWER_ON` | 无人机电源已开启 | -| 飞行中 | `FLYING` | 无人机正在飞行(父状态) | -| 返航中 | `RETURNING_HOME` | 无人机正在返航 | -| 降落中 | `LANDING` | 无人机正在降落 | -| 已降落 | `LANDED` | 无人机已降落 | - -#### 飞行子状态(FLYING 的子状态) - -| 状态 | 枚举值 | 说明 | -|------|--------|------| -| 待飞 | `READY_TO_FLY` | 准备起飞(初始子状态) | -| 一键起飞模式 | `ONE_KEY_TAKEOFF_MODE` | 一键起飞飞行模式(父状态) | -| 航线飞行模式 | `WAYLINE_FLIGHT_MODE` | 航线飞行模式(父状态) | - -#### 一键起飞子状态(ONE_KEY_TAKEOFF_MODE 的子状态) - -| 状态 | 枚举值 | 说明 | -|------|--------|------| -| 起飞准备 | `TAKEOFF_PREPARING` | 一键起飞准备中(初始子状态) | -| 起飞飞行中 | `TAKEOFF_FLYING` | 正常起飞飞行中 | -| 起飞指点飞行 | `TAKEOFF_POINT_FLYING` | 起飞过程中的指点飞行(子状态) | -| 起飞急停 | `TAKEOFF_EMERGENCY_STOP` | 起飞过程中的急停(子状态) | -| 起飞完成 | `TAKEOFF_COMPLETED` | 一键起飞完成 | -| 起飞失败 | `TAKEOFF_FAILED` | 一键起飞失败 | - -#### 航线飞行子状态(WAYLINE_FLIGHT_MODE 的子状态) - -| 状态 | 枚举值 | 说明 | -|------|--------|------| -| 航线准备 | `WAYLINE_PREPARING` | 航线任务准备中(初始子状态) | -| 航线飞行中 | `WAYLINE_FLYING` | 正常航线飞行中 | -| 航线指点飞行 | `WAYLINE_POINT_FLYING` | 航线过程中的指点飞行(子状态) | -| 航线急停 | `WAYLINE_EMERGENCY_STOP` | 航线过程中的急停(子状态) | -| 航线悬停 | `WAYLINE_HOVER` | 航线飞行中的悬停状态 | -| 航线完成 | `WAYLINE_COMPLETED` | 航线任务完成 | -| 航线失败 | `WAYLINE_FAILED` | 航线任务失败 | - -##### 指点飞行子状态(WAYLINE_POINT_FLYING 的子状态) - -| 状态 | 枚举值 | 说明 | -|------|--------|------| -| 指点飞行中 | `POINT_FLYING` | 正在飞向指定目标点(初始子状态) | -| 到达目的地 | `POINT_ARRIVED` | 已到达指定目标点 | -| 指点急停 | `POINT_EMERGENCY_STOP` | 指点飞行过程中的急停 | - -> 说明: -> - **一键起飞**和**航线飞行**是两种不同的飞行模式,作为FLYING的并列子状态 -> - **指点飞行**在两种模式中都作为子状态存在: -> - 起飞指点飞行(TAKEOFF_POINT_FLYING):在一键起飞过程中插入的指点飞行 -> - 航线指点飞行(WAYLINE_POINT_FLYING):在航线飞行过程中插入的指点飞行 -> - **悬停状态**(WAYLINE_HOVER): -> - 仅存在于航线飞行模式中 -> - 急停或暂停后进入悬停状态 -> - 悬停后可以恢复继续执行航线 -> - **急停**有三个层级: -> - FLYING层级的急停(FLYING_EMERGENCY_STOP):可以在任何飞行状态下触发 -> - 起飞急停(TAKEOFF_EMERGENCY_STOP):一键起飞模式内的急停子状态 -> - 航线急停(WAYLINE_EMERGENCY_STOP):航线飞行模式内的急停子状态,急停后进入WAYLINE_HOVER -> - **航线暂停**(WAYLINE_PAUSED)与急停不同,暂停后也会进入WAYLINE_HOVER悬停状态 - -### 2.3 模式状态(ModeState) - -#### DRC模式状态 - -| 状态 | 枚举值 | 说明 | -|------|--------|------| -| DRC未激活 | `DRC_INACTIVE` | DRC模式未激活(初始状态) | -| DRC激活中 | `DRC_ENTERING` | 正在进入DRC模式 | -| DRC已激活 | `DRC_ACTIVE` | DRC模式已激活 | -| DRC退出中 | `DRC_EXITING` | 正在退出DRC模式 | - -#### 调试模式状态 - -| 状态 | 枚举值 | 说明 | -|------|--------|------| -| 调试模式关闭 | `DEBUG_OFF` | 调试模式关闭(初始状态) | -| 调试模式开启中 | `DEBUG_OPENING` | 正在开启调试模式 | -| 调试模式已开启 | `DEBUG_ON` | 调试模式已开启 | -| 调试模式关闭中 | `DEBUG_CLOSING` | 正在关闭调试模式 | - -#### 控制权状态 - -| 状态 | 枚举值 | 说明 | -|------|--------|------| -| 未获取 | `AUTHORITY_NONE` | 未获取控制权(初始状态) | -| 飞行控制权已获取 | `FLIGHT_AUTHORITY` | 已获取飞行控制权 | -| 负载控制权已获取 | `PAYLOAD_AUTHORITY` | 已获取负载控制权 | -| 全部控制权已获取 | `ALL_AUTHORITY` | 已获取飞行和负载控制权 | - -## 三、事件定义 - -### 3.1 机巢事件(AirportEvent) - -| 事件 | 枚举值 | 说明 | 触发源 | -|------|--------|------|---------| -| 机巢上线 | `AIRPORT_ONLINE` | 机巢设备上线 | OSD心跳 | -| 机巢离线 | `AIRPORT_OFFLINE` | 机巢设备离线 | OSD心跳 | -| 开启调试模式 | `DEBUG_MODE_OPEN` | 开启调试模式 | 用户指令 | -| 关闭调试模式 | `DEBUG_MODE_CLOSE` | 关闭调试模式 | 用户指令/自动 | -| 开舱指令 | `COVER_OPEN` | 开舱指令 | 用户指令 | -| 关舱指令 | `COVER_CLOSE` | 关舱指令 | 用户指令 | -| 开舱完成 | `COVER_OPENED` | 舱门已打开 | OSD状态 | -| 关舱完成 | `COVER_CLOSED` | 舱门已关闭 | OSD状态 | -| 机巢重启 | `AIRPORT_REBOOT` | 机巢重启指令 | 用户指令 | -| 重启完成 | `REBOOT_COMPLETED` | 重启完成 | Events事件 | - -### 3.2 无人机事件(DroneEvent) - -| 事件 | 枚举值 | 说明 | 触发源 | -|------|--------|------|---------| -| 无人机开机 | `DRONE_POWER_ON` | 无人机开机指令 | 用户指令 | -| 无人机关机 | `DRONE_POWER_OFF` | 无人机关机指令 | 用户指令 | -| 开机完成 | `POWER_ON_COMPLETED` | 开机完成 | OSD状态 | -| 关机完成 | `POWER_OFF_COMPLETED` | 关机完成 | OSD状态 | -| 获取飞行控制权 | `GRAB_FLIGHT_AUTHORITY` | 获取飞行控制权 | 自动/用户指令 | -| 获取负载控制权 | `GRAB_PAYLOAD_AUTHORITY` | 获取负载控制权 | 自动/用户指令 | -| 控制权获取完成 | `AUTHORITY_GRABBED` | 控制权获取完成 | Services回复 | - -### 3.3 飞行事件(FlightEvent) - -| 事件 | 枚举值 | 说明 | 触发源 | -|------|--------|------|---------| -| 准备起飞 | `READY_TO_TAKEOFF` | 准备起飞 | 任务准备完成 | -| 开始飞行 | `START_FLIGHT` | 开始飞行 | 用户指令/自动 | -| 进入手动模式 | `ENTER_MANUAL_MODE` | 进入手动飞行模式 | DRC模式激活 | -| 进入自动模式 | `ENTER_AUTO_MODE` | 进入自动飞行模式 | 任务开始 | -| 急停指令 | `EMERGENCY_STOP` | 急停指令 | 用户指令 | -| 取消急停 | `CANCEL_EMERGENCY_STOP` | 取消急停 | 用户指令/自动 | -| 返航指令 | `RETURN_HOME` | 返航指令 | 用户指令/自动 | -| 返航完成 | `RETURN_HOME_COMPLETED` | 返航完成 | OSD状态 | -| 开始降落 | `START_LANDING` | 开始降落 | 自动/用户指令 | -| 降落完成 | `LANDING_COMPLETED` | 降落完成 | OSD状态 | - -### 3.4 任务事件(TaskEvent) - -| 事件 | 枚举值 | 说明 | 触发源 | -|------|--------|------|---------| -| 准备航线任务 | `PREPARE_WAYLINE` | 准备航线任务 | 用户指令 | -| 执行航线任务 | `EXECUTE_WAYLINE` | 执行航线任务 | 用户指令 | -| 准备完成 | `PREPARE_COMPLETED` | 任务准备完成 | Services回复 | -| 任务开始 | `TASK_STARTED` | 任务开始执行 | Events事件 | -| 任务暂停 | `TASK_PAUSE` | 暂停任务 | 用户指令 | -| 任务恢复 | `TASK_RECOVERY` | 恢复任务 | 用户指令 | -| 任务完成 | `TASK_COMPLETED` | 任务完成 | Events事件 | -| 任务失败 | `TASK_FAILED` | 任务失败 | Events事件 | -| 指点飞行 | `FLY_TO_POINT` | 指点飞行指令 | 用户指令 | -| 更新指点目标 | `UPDATE_FLY_TO_POINT` | 更新指点飞行目标 | 用户指令 | -| 一键起飞 | `TAKEOFF_TO_POINT` | 一键起飞指令 | 用户指令 | - -### 3.5 DRC模式事件(DRCEvent) - -| 事件 | 枚举值 | 说明 | 触发源 | -|------|--------|------|---------| -| 进入DRC模式 | `ENTER_DRC_MODE` | 进入DRC模式指令 | 用户指令/自动 | -| 退出DRC模式 | `EXIT_DRC_MODE` | 退出DRC模式指令 | 用户指令/自动 | -| DRC模式已激活 | `DRC_ACTIVATED` | DRC模式已激活 | Services回复 | -| DRC模式已退出 | `DRC_DEACTIVATED` | DRC模式已退出 | Services回复 | - -## 四、状态转换规则 - -### 4.1 机巢状态转换 - -``` -OFFLINE - └─[AIRPORT_ONLINE]─> ONLINE(STANDBY) - └─[DEBUG_MODE_OPEN]─> ONLINE(DEBUG_MODE) - └─[COVER_OPEN]─> ONLINE(OPERATING(COVER_OPENING)) - └─[COVER_OPENED]─> ONLINE(OPERATING(COVER_OPENED)) - └─[DEBUG_MODE_CLOSE]─> ONLINE(STANDBY) - - └─[AIRPORT_REBOOT]─> REBOOTING - └─[REBOOT_COMPLETED]─> ONLINE(STANDBY) - -ONLINE - └─[AIRPORT_OFFLINE]─> OFFLINE -``` - -**Guard 规则:** -- `COVER_OPEN`:检查当前舱门状态不是已打开 -- `COVER_CLOSE`:检查当前舱门状态不是已关闭 -- `DEBUG_MODE_OPEN`:检查当前不是调试模式 -- `AIRPORT_REBOOT`:检查当前是调试模式 - -### 4.2 无人机电源状态转换 - -``` -POWER_OFF (初始状态) - └─[DRONE_POWER_ON]─> POWER_ON - └─[POWER_ON_COMPLETED]─> POWER_ON(READY_TO_FLY) - -POWER_ON - └─[DRONE_POWER_OFF]─> POWER_OFF - └─[POWER_OFF_COMPLETED]─> POWER_OFF -``` - -**Guard 规则:** -- `DRONE_POWER_ON`:检查机巢在线且处于调试模式 -- `DRONE_POWER_OFF`:检查机巢在线且处于调试模式 - -### 4.3 飞行状态转换 - -``` -READY_TO_FLY - └─[START_FLIGHT]─> FLYING - ├─[ENTER_MANUAL_MODE]─> FLYING(MANUAL_FLIGHT) - │ └─[EMERGENCY_STOP]─> FLYING(EMERGENCY_STOP) - │ └─[CANCEL_EMERGENCY_STOP]─> FLYING(MANUAL_FLIGHT) - │ - └─[ENTER_AUTO_MODE]─> FLYING(AUTO_FLIGHT(TASK_PREPARING)) - └─[TASK_STARTED]─> FLYING(AUTO_FLIGHT(TASK_EXECUTING)) - ├─[TASK_PAUSE]─> FLYING(AUTO_FLIGHT(TASK_PAUSED)) - │ └─[TASK_RECOVERY]─> FLYING(AUTO_FLIGHT(TASK_EXECUTING)) - ├─[TASK_COMPLETED]─> FLYING(AUTO_FLIGHT(TASK_COMPLETED)) - └─[TASK_FAILED]─> FLYING(AUTO_FLIGHT(TASK_FAILED)) - -FLYING - └─[RETURN_HOME]─> RETURNING_HOME - └─[RETURN_HOME_COMPLETED]─> RETURNING_HOME - └─[START_LANDING]─> LANDING - └─[LANDING_COMPLETED]─> LANDED - └─[DRONE_POWER_OFF]─> POWER_OFF -``` - -**Guard 规则:** -- `START_FLIGHT`:检查无人机已开机且机巢在线 -- `ENTER_MANUAL_MODE`:检查DRC模式已激活 -- `ENTER_AUTO_MODE`:检查任务准备完成 -- `RETURN_HOME`:检查不在指点飞行中(如果是指点飞行,需要先停止) -- `EMERGENCY_STOP`:检查DRC模式已激活且电量充足 - -### 4.4 DRC模式状态转换 - -``` -DRC_INACTIVE (初始状态) - └─[ENTER_DRC_MODE]─> DRC_ENTERING - └─[DRC_ACTIVATED]─> DRC_ACTIVE - └─[EXIT_DRC_MODE]─> DRC_EXITING - └─[DRC_DEACTIVATED]─> DRC_INACTIVE -``` - -**自动转换规则(Action):** -- 指点飞行中:自动退出DRC模式 -- 一键起飞中:自动退出DRC模式 -- 返航中:自动退出DRC模式 -- 降落中:自动退出DRC模式 -- 指点飞行停止后:自动进入DRC模式 -- 一键起飞停止后:自动进入DRC模式 -- 急停操作:自动进入DRC模式 -- 摇杆控制:自动进入DRC模式 - -### 4.5 调试模式状态转换 - -``` -DEBUG_OFF (初始状态) - └─[DEBUG_MODE_OPEN]─> DEBUG_OPENING - └─[DEBUG_OPENED]─> DEBUG_ON - └─[DEBUG_MODE_CLOSE]─> DEBUG_CLOSING - └─[DEBUG_CLOSED]─> DEBUG_OFF -``` - -**自动转换规则(Action):** -- 开舱/关舱操作:自动开启调试模式 → 执行操作 → 2秒后自动关闭 -- 无人机开机/关机:自动开启调试模式 → 执行操作 → 2秒后自动关闭 -- 机巢重启:自动开启调试模式 → 执行操作 → 2秒后自动关闭 - -### 4.6 控制权状态转换 - -``` -AUTHORITY_NONE (初始状态) - └─[GRAB_FLIGHT_AUTHORITY]─> FLIGHT_AUTHORITY - └─[GRAB_PAYLOAD_AUTHORITY]─> ALL_AUTHORITY -``` - -**自动转换规则(Action):** -- 无人机起飞准备时:自动获取飞行控制权和负载控制权 - -## 五、Guard(守卫)设计 - -### 5.1 机巢操作 Guard - -| Guard名称 | 检查条件 | 失败原因 | -|-----------|----------|----------| -| `CanOpenCover` | 舱门状态不是已打开 | 舱门已经打开 | -| `CanCloseCover` | 舱门状态不是已关闭 | 舱门已经关闭 | -| `IsDebugMode` | 当前处于调试模式 | 未开启调试模式 | -| `IsNotDebugMode` | 当前不处于调试模式 | 处于调试模式中 | -| `IsAirportOnline` | 机巢在线 | 机巢离线 | - -### 5.2 无人机操作 Guard - -| Guard名称 | 检查条件 | 失败原因 | -|-----------|----------|----------| -| `IsDronePowerOn` | 无人机已开机 | 无人机未开机 | -| `IsDronePowerOff` | 无人机已关机 | 无人机未关机 | -| `IsNotFlying` | 无人机不在飞行中 | 无人机正在飞行 | -| `IsNotReturningHome` | 无人机不在返航中 | 无人机正在返航 | -| `IsNotLanding` | 无人机不在降落中 | 无人机正在降落 | -| `CanFlyToPoint` | 不在返航且不在降落 | 返航中或降落中 | -| `HasEnoughBattery` | 电量充足(不低于返航电量) | 电量不足 | -| `IsDRCActive` | DRC模式已激活 | DRC模式未激活 | -| `IsNotDRCActive` | DRC模式未激活 | DRC模式已激活 | - -### 5.3 任务操作 Guard - -| Guard名称 | 检查条件 | 失败原因 | -|-----------|----------|----------| -| `IsValidReturnHeight` | 返航高度≥30米 | 返航高度不足30米 | -| `IsTaskPrepared` | 任务已准备完成 | 任务未准备 | -| `IsTaskExecuting` | 任务执行中 | 任务未执行 | -| `IsTaskPaused` | 任务已暂停 | 任务未暂停 | -| `HasFlightAuthority` | 已获取飞行控制权 | 未获取飞行控制权 | -| `HasPayloadAuthority` | 已获取负载控制权 | 未获取负载控制权 | - -## 六、Action(动作)设计 - -### 6.1 机巢操作 Action - -| Action名称 | 执行内容 | 说明 | -|------------|----------|------| -| `OpenDebugModeAction` | 发送 `debug_mode_open` 指令 | 开启调试模式 | -| `CloseDebugModeAction` | 发送 `debug_mode_close` 指令 | 关闭调试模式 | -| `OpenCoverAction` | 发送 `cover_open` 指令 | 开舱操作 | -| `CloseCoverAction` | 发送 `cover_close` 指令 | 关舱操作 | -| `RebootAirportAction` | 发送 `device_reboot` 指令 | 重启机巢 | -| `AutoCloseDebugModeAction` | 等待2秒后关闭调试模式 | 自动关闭调试模式 | - -### 6.2 无人机操作 Action - -| Action名称 | 执行内容 | 说明 | -|------------|----------|------| -| `PowerOnDroneAction` | 发送 `drone_open` 指令 | 无人机开机 | -| `PowerOffDroneAction` | 发送 `drone_close` 指令 | 无人机关机 | -| `GrabFlightAuthorityAction` | 发送 `flight_authority_grab` 指令 | 获取飞行控制权 | -| `GrabPayloadAuthorityAction` | 发送 `payload_authority_grab` 指令 | 获取负载控制权 | -| `ClearLiveStreamCacheAction` | 清除直播相关缓存 | 清除缓存 | - -### 6.3 飞行操作 Action - -| Action名称 | 执行内容 | 说明 | -|------------|----------|------| -| `EnterDRCModeAction` | 发送 `drc_mode_enter` 指令 | 进入DRC模式 | -| `ExitDRCModeAction` | 发送 `drc_mode_exit` 指令 | 退出DRC模式 | -| `EmergencyStopAction` | 发送 `drone_emergency_stop` 属性设置 | 急停操作 | -| `ReturnHomeAction` | 发送 `return_home` 指令 | 返航操作 | -| `StartLiveStreamAction` | 发送 `live_start_push` 指令 | 开启直播推流 | -| `StopLiveStreamAction` | 发送 `live_stop_push` 指令 | 关闭直播推流 | - -### 6.4 任务操作 Action - -| Action名称 | 执行内容 | 说明 | -|------------|----------|------| -| `PrepareWaylineTaskAction` | 发送 `flighttask_prepare` 指令 | 准备航线任务 | -| `ExecuteWaylineTaskAction` | 等待1秒 → 开启机巢推流 → 发送 `flighttask_execute` 指令 | 执行航线任务 | -| `PauseTaskAction` | 发送 `flighttask_pause` 指令 | 暂停任务 | -| `RecoveryTaskAction` | 发送 `flighttask_recovery` 指令 | 恢复任务 | -| `FlyToPointAction` | 检查是否在指点飞行中 → 发送 `fly_to_point` 或 `fly_to_point_update` 指令 | 指点飞行 | -| `TakeoffToPointAction` | 发送 `takeoff_to_point` 指令 | 一键起飞 | -| `AdjustReturnHeightAction` | 检查返航高度,<100米调整为100米 | 调整返航高度 | - -### 6.5 自动管理 Action - -| Action名称 | 执行内容 | 说明 | -|------------|----------|------| -| `AutoEnterDRCModeAction` | 自动进入DRC模式 | 指点飞行停止后、一键起飞停止后 | -| `AutoExitDRCModeAction` | 自动退出DRC模式 | 指点飞行中、一键起飞中、返航中、降落中 | -| `AutoGrabAuthorityAction` | 自动获取控制权 | 无人机起飞准备时 | -| `UpdateFlightStatusAction` | 更新飞行状态 | 根据OSD数据更新状态(急停状态下不更新) | - -## 七、状态机监听器设计 - -### 7.1 OSD心跳监听器 - -监听 `thing/product/{airportSn}/osd` Topic,处理: - -- **机巢状态更新**:`device_online_status` → 触发 `AIRPORT_ONLINE`/`AIRPORT_OFFLINE` -- **舱门状态更新**:`cover_state` → 触发 `COVER_OPENED`/`COVER_CLOSED` -- **无人机电源状态**:`device_online_status` → 触发 `POWER_ON_COMPLETED`/`POWER_OFF_COMPLETED` -- **飞行模式**:`drone_mode_code` → 更新飞行状态 -- **返航状态**:`mode_code = "9"` → 触发 `RETURN_HOME` -- **降落状态**:`mode_code = "LAND"` → 触发 `START_LANDING` - -### 7.2 Events事件监听器 - -监听 `thing/product/{airportSn}/events` Topic,处理: - -- **任务进度**:`flighttask_progress` → 触发任务状态事件 -- **指点飞行进度**:`fly_to_point_progress` → 触发指点飞行状态事件 -- **一键起飞进度**:`takeoff_to_point_progress` → 触发一键起飞状态事件 -- **设备重启**:`device_reboot` → 触发重启状态事件 -- **文件上传**:`file_upload_callback` → 处理拍照文件上传 - -### 7.3 Services回复监听器 - -监听 `thing/product/{airportSn}/services_reply` Topic,处理: - -- **指令接受确认**:`result=0` → 确认指令已接受 -- **DRC模式状态**:`drc_mode_enter`/`drc_mode_exit` 回复 → 触发DRC模式状态事件 -- **控制权获取**:`flight_authority_grab`/`payload_authority_grab` 回复 → 触发控制权状态事件 - -### 7.4 Set回复监听器 - -监听 `thing/product/{airportSn}/set_reply` Topic,处理: - -- **属性设置确认**:急停、调色盘等属性设置的确认 - -## 八、业务规则实现 - -### 8.1 调试模式规则 - -**规则:** 开舱、关舱、无人机开机、无人机关机、机巢重启需要先开启调试模式 - -**实现:** -``` -Action: OpenDebugModeAction (Guard: IsNotDebugMode) - → 执行操作 (Guard: IsDebugMode) - → AutoCloseDebugModeAction (等待2秒后关闭) -``` - -### 8.2 飞行状态限制规则 - -**规则:** -- 返航中或降落中不可执行指点飞行 -- 一键起飞与航线飞行过程中允许插入指点飞行(实时更新目标) -- 航线/一键起飞的“非悬停”状态下均可急停,急停后进入悬停(Hover) - -**实现:** -``` -Guard: CanFlyToPoint - - 检查状态不是 RETURNING_HOME - - 检查状态不是 LANDING - - 允许在 TAKEOFF_TO_POINT / WAYLINE_TASK 中触发 FLY_TO_POINT 或 UPDATE_FLY_TO_POINT - -Guard: CanEmergencyStop - - 当前飞行状态非悬停即可触发急停 -Action: EmergencyStopAction - - 发送急停指令,进入 EMERGENCY_STOP→HOVER -``` - -### 8.3 航线任务规则 - -**规则:** 返航高度必须≥30米,<100米时自动调整为100米 - -**实现:** -``` -Guard: IsValidReturnHeight (检查≥30米) -Action: AdjustReturnHeightAction (检查<100米时调整为100米) -``` - -### 8.4 DRC模式规则 - -**规则:** 急停、摇杆控制依赖 DRC 模式;指点飞行中、一键起飞中、返航中、降落中自动退出 DRC 模式 - -**实现:** -``` -自动退出DRC模式: - - 指点飞行中:Action监听任务状态,自动发送 EXIT_DRC_MODE - - 一键起飞中:Action监听任务状态,自动发送 EXIT_DRC_MODE - - 返航中:Action监听返航状态,自动发送 EXIT_DRC_MODE - - 降落中:Action监听降落状态,自动发送 EXIT_DRC_MODE - -自动进入DRC模式: - - 指点飞行停止后:Action监听任务完成,自动发送 ENTER_DRC_MODE - - 一键起飞停止后:Action监听任务完成,自动发送 ENTER_DRC_MODE - - 急停操作:Action自动发送 ENTER_DRC_MODE(急停前确保 DRC 已激活) - - 摇杆控制:Action自动发送 ENTER_DRC_MODE -``` - -### 8.5 控制权规则 - -**规则:** 无人机起飞准备时自动获取飞行控制权和负载控制权 - -**实现:** -``` -Action: AutoGrabAuthorityAction - - 监听任务准备完成事件 - - 自动发送 GRAB_FLIGHT_AUTHORITY - - 自动发送 GRAB_PAYLOAD_AUTHORITY -``` - -### 8.6 急停规则 - -**规则:** -- 急停依赖 DRC 模式;飞行中(非悬停)都可急停,急停后进入悬停 -- 航线飞行的“非指点、非悬停”阶段可暂停,暂停后进入悬停(与急停后的悬停一致,但语义不同) -- 急停状态下不更新飞行状态,保持急停/悬停 - -**实现:** -``` -Guard: CanEmergencyStop - - 检查 DRC_ACTIVE - - 检查当前飞行状态非悬停即可触发 -Action: EmergencyStopAction - - 发送 drone_emergency_stop → 状态切换至 EMERGENCY_STOP → HOVER - -Guard: CanPauseWayline - - 当前在 WAYLINE_TASK 且非指点、非悬停 -Action: PauseTaskAction - - 发送 flighttask_pause → 状态切换至 TASK_PAUSED → HOVER - -Guard: UpdateFlightStatusGuard - - 检查当前状态不是 EMERGENCY_STOP - - 如果是急停/悬停,阻止飞行状态更新 -``` - -## 九、状态机配置示例 - -### 9.1 状态定义示例 - -```java -public enum AirportState { - // 主状态 - OFFLINE, - ONLINE, // 父状态 - REBOOTING, - - // ONLINE 的子状态 - STANDBY, // 初始子状态 - DEBUG_MODE, - OPERATING, // 父状态 - - // OPERATING 的子状态(舱门状态) - COVER_CLOSED, - COVER_OPENED, - COVER_HALF_OPEN, - COVER_ERROR, - COVER_OPENING, - COVER_CLOSING -} - -public enum DroneState { - // 主状态 - POWER_OFF, // 初始状态 - POWER_ON, - FLYING, // 父状态 - RETURNING_HOME, - LANDING, - LANDED, - - // FLYING 的子状态 - READY_TO_FLY, // 初始子状态 - MANUAL_FLIGHT, - AUTO_FLIGHT, // 父状态 - EMERGENCY_STOP, - - // AUTO_FLIGHT 的子状态 - TASK_PREPARING, // 初始子状态 - TASK_EXECUTING, // 父状态 - TASK_PAUSED, - TASK_COMPLETED, - TASK_FAILED, - - // TASK_EXECUTING 的子状态 - WAYLINE_TASK, - FLY_TO_POINT, - TAKEOFF_TO_POINT -} -``` - -### 9.2 转换配置示例 - -```java -// 机巢状态转换 -.withExternal() - .source(AirportState.OFFLINE) - .target(AirportState.STANDBY) - .event(AirportEvent.AIRPORT_ONLINE) - .guard(new IsAirportOnlineGuard()) - .and() - -// 开舱操作(需要调试模式) -.withExternal() - .source(AirportState.STANDBY) - .target(AirportState.DEBUG_MODE) - .event(AirportEvent.DEBUG_MODE_OPEN) - .action(new OpenDebugModeAction()) - .guard(new IsNotDebugModeGuard()) - .and() -.withExternal() - .source(AirportState.DEBUG_MODE) - .target(AirportState.COVER_OPENING) - .event(AirportEvent.COVER_OPEN) - .action(new OpenCoverAction()) - .guard(new CanOpenCoverGuard()) - .and() - -// 无人机开机(需要调试模式) -.withExternal() - .source(DroneState.POWER_OFF) - .target(DroneState.POWER_ON) - .event(DroneEvent.DRONE_POWER_ON) - .action(new PowerOnDroneAction()) - .guard(new IsDebugModeGuard()) - .and() - -// 进入DRC模式 -.withExternal() - .source(ModeState.DRC_INACTIVE) - .target(ModeState.DRC_ENTERING) - .event(DRCEvent.ENTER_DRC_MODE) - .action(new EnterDRCModeAction()) - .and() -``` - -## 十、使用场景示例 - -### 10.1 开舱场景 - -``` -1. 用户点击开舱 -2. 检查当前舱门状态(Guard: CanOpenCover) -3. 检查是否在调试模式(Guard: IsNotDebugMode) -4. 开启调试模式(Action: OpenDebugModeAction) -5. 发送开舱指令(Action: OpenCoverAction) -6. 监听OSD状态,等待舱门打开 -7. 2秒后自动关闭调试模式(Action: AutoCloseDebugModeAction) -``` - -### 10.2 航线任务场景 - -``` -1. 用户下发航线任务 -2. 检查返航高度≥30米(Guard: IsValidReturnHeight) -3. 调整返航高度<100米为100米(Action: AdjustReturnHeightAction) -4. 准备任务(Action: PrepareWaylineTaskAction) -5. 等待准备完成(监听services_reply) -6. 等待1秒 → 开启机巢推流 → 执行任务(Action: ExecuteWaylineTaskAction) -7. 监听任务进度(events: flighttask_progress) -8. 任务完成/失败后更新状态 -``` - -### 10.3 指点飞行场景 - -``` -1. 用户点击指点飞行 -2. 检查不在返航中且不在降落中(Guard: CanFlyToPoint) -3. 检查是否已在指点飞行中(判断events中的status) -4. 如果在指点飞行中:发送更新指令(fly_to_point_update) -5. 如果不在指点飞行中:发送新指令(fly_to_point) -6. 自动退出DRC模式(Action: AutoExitDRCModeAction) -7. 监听飞行进度(events: fly_to_point_progress) -8. 飞行停止后自动进入DRC模式(Action: AutoEnterDRCModeAction) -``` - -### 10.4 急停场景 - -``` -1. 用户点击急停 -2. 检查无人机已开机(Guard: IsDronePowerOn) -3. 检查电量充足(Guard: HasEnoughBattery) -4. 检查是否在DRC模式(Guard: IsNotDRCActive) -5. 如果不在DRC模式:先进入DRC模式(Action: EnterDRCModeAction) -6. 等待DRC模式激活(监听services_reply) -7. 发送急停指令(Action: EmergencyStopAction) -8. 进入急停状态,阻止状态更新(Guard: UpdateFlightStatusGuard) -``` - -## 十一、状态机管理 - -### 11.1 多状态机实例管理 - -每个机巢(airportSn)对应一个状态机实例: - -```java -StateMachineManager manager = new StateMachineManager(); - -// 获取或创建状态机 -StateMachine stateMachine = manager.getOrCreateStateMachine(airportSn); - -// 发送事件 -stateMachine.sendEvent(AirportEvent.COVER_OPEN); - -// 查询状态 -AirportState currentState = manager.getCurrentState(airportSn); -``` - -### 11.2 状态持久化 - -状态机状态可以持久化到数据库或Redis: - -- **状态快照**:定期保存状态机状态 -- **事件日志**:记录所有状态转换事件 -- **恢复机制**:系统重启后恢复状态机状态 - -## 十二、总结 - -### 12.1 设计特点 - -1. **分层设计**:主状态、子状态、子子状态,清晰表达复杂状态关系 -2. **业务规则**:通过Guard和Action实现业务限制条件 -3. **自动管理**:DRC模式、控制权等自动管理,减少人工干预 -4. **安全控制**:通过状态机确保操作的安全执行顺序 -5. **事件驱动**:基于MQTT事件驱动状态转换 - -### 12.2 扩展性 - -- **新增功能**:通过添加新状态、事件、转换即可扩展 -- **业务规则变更**:修改Guard和Action即可适应规则变化 -- **多设备支持**:每个机巢独立的状态机实例 - -### 12.3 注意事项 - -1. **状态同步**:确保MQTT事件与状态机状态同步 -2. **异常处理**:处理网络异常、设备异常等情况 -3. **超时处理**:操作超时后的状态恢复机制 -4. **并发控制**:同一状态机的并发操作控制 diff --git a/src/test/java/com/tuoheng/status/statemachine/StateMachineManagerTest.java b/src/test/java/com/tuoheng/status/statemachine/StateMachineManagerTest.java deleted file mode 100644 index e5f37fd..0000000 --- a/src/test/java/com/tuoheng/status/statemachine/StateMachineManagerTest.java +++ /dev/null @@ -1,209 +0,0 @@ -package com.tuoheng.status.statemachine; - -import com.tuoheng.status.statemachine.events.Event; -import com.tuoheng.status.statemachine.manager.StateMachineManager; -import com.tuoheng.status.statemachine.service.StateMachineManagerService; -import com.tuoheng.status.statemachine.status.Status; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.testng.AbstractTestNGSpringContextTests; -import org.testng.annotations.Test; - -import static org.testng.Assert.*; - -/** - * 状态机管理器测试类 - */ -@ContextConfiguration(classes = { - com.tuoheng.status.statemachine.config.TaskStateMachineConfig.class, - com.tuoheng.status.statemachine.manager.StateMachineManager.class, - StateMachineManagerService.class -}) -public class StateMachineManagerTest extends AbstractTestNGSpringContextTests { - - @Autowired - private StateMachineManager stateMachineManager; - - @Autowired - private StateMachineManagerService stateMachineManagerService; - - @Test - public void testCreateAndGetStateMachine() { - System.out.println("\n\n========================================"); - System.out.println("测试1: 创建和获取状态机"); - System.out.println("========================================\n"); - - String machineId = "test-machine-001"; - - // 创建状态机 - assertFalse(stateMachineManager.exists(machineId), "状态机应该不存在"); - - Status status = stateMachineManager.getCurrentStatus(machineId); - assertNull(status, "不存在的状态机应该返回null"); - - // 创建状态机 - stateMachineManager.getOrCreateStateMachine(machineId); - assertTrue(stateMachineManager.exists(machineId), "状态机应该存在"); - - // 获取状态 - status = stateMachineManager.getCurrentStatus(machineId); - assertNotNull(status, "状态机状态不应该为null"); - assertEquals(status, Status.IDLE, "初始状态应该是IDLE"); - - System.out.println("状态机ID: " + machineId); - System.out.println("当前状态: " + status); - } - - @Test - public void testGetStateMachineInfo() { - System.out.println("\n\n========================================"); - System.out.println("测试2: 获取状态机详细信息"); - System.out.println("========================================\n"); - - String machineId = "test-machine-002"; - stateMachineManager.getOrCreateStateMachine(machineId); - - StateMachineManager.StateMachineInfo info = stateMachineManager.getStateMachineInfo(machineId); - assertNotNull(info, "状态机信息不应该为null"); - assertEquals(info.getMachineId(), machineId); - assertEquals(info.getCurrentStatus(), Status.IDLE); - assertTrue(info.isRunning(), "状态机应该正在运行"); - - System.out.println("状态机信息: " + info); - } - - @Test - public void testSendEventAndCheckStatus() { - System.out.println("\n\n========================================"); - System.out.println("测试3: 发送事件并检查状态变化"); - System.out.println("========================================\n"); - - String machineId = "test-machine-003"; - - // 创建状态机并设置taskId - stateMachineManager.getOrCreateStateMachine(machineId); - stateMachineManager.getStateMachine(machineId) - .getExtendedState().getVariables().put("taskId", "task-001"); - - // 初始状态应该是IDLE - Status status = stateMachineManager.getCurrentStatus(machineId); - assertEquals(status, Status.IDLE); - - // 发送START事件 - boolean accepted = stateMachineManagerService.sendEvent(machineId, Event.START); - assertTrue(accepted, "START事件应该被接受"); - - // 等待状态转换 - try { - Thread.sleep(100); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - - // 检查状态应该变为PREPARING - status = stateMachineManager.getCurrentStatus(machineId); - assertEquals(status, Status.PREPARING, "状态应该变为PREPARING"); - - System.out.println("状态机ID: " + machineId); - System.out.println("发送事件: START"); - System.out.println("当前状态: " + status); - } - - @Test - public void testMultipleStateMachines() { - System.out.println("\n\n========================================"); - System.out.println("测试4: 管理多个状态机"); - System.out.println("========================================\n"); - - String machineId1 = "test-machine-004-1"; - String machineId2 = "test-machine-004-2"; - String machineId3 = "test-machine-004-3"; - - // 创建多个状态机 - stateMachineManager.getOrCreateStateMachine(machineId1); - stateMachineManager.getOrCreateStateMachine(machineId2); - stateMachineManager.getOrCreateStateMachine(machineId3); - - // 验证所有状态机都存在 - assertTrue(stateMachineManager.exists(machineId1)); - assertTrue(stateMachineManager.exists(machineId2)); - assertTrue(stateMachineManager.exists(machineId3)); - - // 获取所有状态机ID - java.util.Set allIds = stateMachineManager.getAllMachineIds(); - assertTrue(allIds.contains(machineId1)); - assertTrue(allIds.contains(machineId2)); - assertTrue(allIds.contains(machineId3)); - assertEquals(stateMachineManager.getStateMachineCount(), 3); - - System.out.println("管理的状态机数量: " + stateMachineManager.getStateMachineCount()); - System.out.println("所有状态机ID: " + allIds); - - // 为不同的状态机设置不同的状态 - stateMachineManager.getStateMachine(machineId1) - .getExtendedState().getVariables().put("taskId", "task-001"); - stateMachineManagerService.sendEvent(machineId1, Event.START); - - try { - Thread.sleep(100); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - - Status status1 = stateMachineManager.getCurrentStatus(machineId1); - Status status2 = stateMachineManager.getCurrentStatus(machineId2); - Status status3 = stateMachineManager.getCurrentStatus(machineId3); - - System.out.println("状态机1 (" + machineId1 + ") 状态: " + status1); - System.out.println("状态机2 (" + machineId2 + ") 状态: " + status2); - System.out.println("状态机3 (" + machineId3 + ") 状态: " + status3); - - assertNotEquals(status1, status2, "不同状态机应该有不同状态"); - } - - @Test - public void testRemoveStateMachine() { - System.out.println("\n\n========================================"); - System.out.println("测试5: 移除状态机"); - System.out.println("========================================\n"); - - String machineId = "test-machine-005"; - - // 创建状态机 - stateMachineManager.getOrCreateStateMachine(machineId); - assertTrue(stateMachineManager.exists(machineId)); - - // 移除状态机 - boolean removed = stateMachineManager.removeStateMachine(machineId); - assertTrue(removed, "应该成功移除状态机"); - assertFalse(stateMachineManager.exists(machineId), "状态机应该不存在了"); - - System.out.println("状态机已移除: " + machineId); - } - - @Test - public void testStateMachineService() { - System.out.println("\n\n========================================"); - System.out.println("测试6: 使用StateMachineManagerService"); - System.out.println("========================================\n"); - - String machineId = "test-machine-006"; - String taskId = "task-006"; - - // 使用服务类启动任务 - boolean started = stateMachineManagerService.startTask(machineId, taskId); - assertTrue(started, "任务应该成功启动"); - - // 检查状态 - Status status = stateMachineManagerService.getCurrentStatus(machineId); - assertEquals(status, Status.PREPARING, "状态应该是PREPARING"); - - // 获取详细信息 - StateMachineManager.StateMachineInfo info = stateMachineManagerService.getStateMachineInfo(machineId); - assertNotNull(info); - assertEquals(info.getCurrentStatus(), Status.PREPARING); - - // 打印信息 - stateMachineManagerService.printStateMachineInfo(machineId); - } -} diff --git a/src/test/java/com/tuoheng/status/statemachine/TaskStateMachineTest.java b/src/test/java/com/tuoheng/status/statemachine/TaskStateMachineTest.java deleted file mode 100644 index caa0676..0000000 --- a/src/test/java/com/tuoheng/status/statemachine/TaskStateMachineTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.tuoheng.status.statemachine; - -import com.tuoheng.status.statemachine.service.TaskStateMachineService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.testng.AbstractTestNGSpringContextTests; -import org.testng.annotations.Test; - -/** - * 状态机测试类 - * 演示 spring-statemachine-core 的各种功能 - */ -@ContextConfiguration(classes = { - com.tuoheng.status.statemachine.config.TaskStateMachineConfig.class, - TaskStateMachineService.class -}) -public class TaskStateMachineTest extends AbstractTestNGSpringContextTests { - - @Autowired - private TaskStateMachineService taskStateMachineService; - - /** - * 测试正常流程:IDLE -> PREPARING -> EXECUTING -> VALIDATING -> COMPLETED - */ - @Test - public void testNormalFlow() { - System.out.println("\n\n========================================"); - System.out.println("测试1: 正常流程"); - System.out.println("========================================\n"); - taskStateMachineService.startTask("task-001"); - } - - /** - * 测试失败和重试流程 - */ - @Test - public void testFailureAndRetry() { - System.out.println("\n\n========================================"); - System.out.println("测试2: 失败和重试流程"); - System.out.println("========================================\n"); - taskStateMachineService.demonstrateFailureAndRetry("task-002"); - } - - /** - * 测试 Guard 条件不满足的情况 - */ - @Test - public void testGuardFailure() { - System.out.println("\n\n========================================"); - System.out.println("测试3: Guard 条件不满足"); - System.out.println("========================================\n"); - taskStateMachineService.demonstrateGuardFailure("task-003"); - } - - /** - * 测试重置流程 - */ - @Test - public void testReset() { - System.out.println("\n\n========================================"); - System.out.println("测试4: 重置流程"); - System.out.println("========================================\n"); - taskStateMachineService.demonstrateReset("task-004"); - } -}