添加无人机状态

This commit is contained in:
孙小云 2025-12-16 13:47:32 +08:00
parent a0160c237c
commit a53c531b47
62 changed files with 1851 additions and 3014 deletions

View File

@ -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<DroneState, DroneEvent> {
}

View File

@ -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<DroneState, DroneEvent> {
}

View File

@ -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<DroneState, DroneEvent> {
}

View File

@ -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<DroneState, DroneEvent> {
}

View File

@ -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<DroneState, DroneEvent> {
}

View File

@ -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<DroneState, DroneEvent> {
}

View File

@ -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<DroneState, DroneEvent> {
}

View File

@ -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<DroneState, DroneEvent> {
}

View File

@ -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<DroneState, DroneEvent> {
}

View File

@ -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<DroneState, DroneEvent> {
}

View File

@ -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<DroneState, DroneEvent> {
}

View File

@ -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<DroneState, DroneEvent> {
}

View File

@ -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<DroneState, DroneEvent> {
}

View File

@ -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<DroneState, DroneEvent> {
}

View File

@ -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<DroneState, DroneEvent> {
}

View File

@ -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<DroneState, DroneEvent> {
}

View File

@ -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<DroneState, DroneEvent> {
}

View File

@ -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动态获取平台特定的GuardAction和Listener
*/
@Configuration
public class DroneMachineConfig {
@Autowired
private PlatformStrategyFactory platformStrategyFactory;
@Bean(name = "droneStateMachineFactory")
public StateMachineFactory<DroneState, DroneEvent> droneStateMachineFactory() throws Exception {
return new StateMachineFactory<DroneState, DroneEvent>() {
@Override
public StateMachine<DroneState, DroneEvent> getStateMachine() {
return null;
}
@Override
public StateMachine<DroneState, DroneEvent> getStateMachine(String machineId) {
try {
// 根据无人机SN获取平台策略
DronePlatformStrategy strategy = platformStrategyFactory.getDroneStrategy(machineId);
StateMachineBuilder.Builder<DroneState, DroneEvent> builder = StateMachineBuilder.builder();
configureStateMachine(builder, strategy);
configureStates(builder);
configureTransitions(builder, strategy);
StateMachine<DroneState, DroneEvent> 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<DroneState, DroneEvent> getStateMachine(UUID uuid) {
return null;
}
};
}
private void configureStateMachine(
StateMachineBuilder.Builder<DroneState, DroneEvent> builder,
DronePlatformStrategy strategy) throws Exception {
builder.configureConfiguration()
.withConfiguration()
.autoStartup(true)
.listener(strategy.getListener());
}
private void configureStates(StateMachineBuilder.Builder<DroneState, DroneEvent> 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<DroneState, DroneEvent> 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());
}
}

View File

@ -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
}

View File

@ -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<DroneState, DroneEvent> {
}

View File

@ -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<DroneState, DroneEvent> {
@Override
public void stateChanged(State<DroneState, DroneEvent> from, State<DroneState, DroneEvent> to) {
if (from != null && to != null) {
System.out.println(String.format("[%s] 状态变化: %s -> %s",
getName(), from.getId(), to.getId()));
}
}
@Override
public void stateEntered(State<DroneState, DroneEvent> state) {
System.out.println(String.format("[%s] 进入状态: %s", getName(), state.getId()));
}
@Override
public void stateExited(State<DroneState, DroneEvent> state) {
System.out.println(String.format("[%s] 退出状态: %s", getName(), state.getId()));
}
@Override
public void eventNotAccepted(Message<DroneEvent> event) {
System.out.println(String.format("[%s] 事件未被接受: %s", getName(), event.getPayload()));
}
@Override
public void transition(Transition<DroneState, DroneEvent> transition) {
// 默认不处理
}
@Override
public void transitionStarted(Transition<DroneState, DroneEvent> 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<DroneState, DroneEvent> 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<DroneState, DroneEvent> stateMachine) {
String machineId = (String) stateMachine.getExtendedState().getVariables().get("machineId");
System.out.println(String.format("[%s] 状态机启动: %s", getName(), machineId));
}
@Override
public void stateMachineStopped(StateMachine<DroneState, DroneEvent> stateMachine) {
String machineId = (String) stateMachine.getExtendedState().getVariables().get("machineId");
System.out.println(String.format("[%s] 状态机停止: %s", getName(), machineId));
}
@Override
public void stateMachineError(StateMachine<DroneState, DroneEvent> 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<DroneState, DroneEvent> stateContext) {
// 默认不处理
}
}

View File

@ -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<PlatformType, CoverPlatformStrategy> coverStrategyMap = new ConcurrentHashMap<>();
/**
* 存储所有无人机平台策略实现
* Key: PlatformType
* Value: DronePlatformStrategy实现
*/
private final Map<PlatformType, DronePlatformStrategy> droneStrategyMap = new ConcurrentHashMap<>();
/**
* 存储各平台对应的系统管理器实现
* Key: PlatformType
@ -45,12 +53,13 @@ public class PlatformStrategyFactory {
/**
* 注册所有平台策略
* Spring会自动注入所有实现了AirportPlatformStrategy和CoverPlatformStrategy的Bean
* Spring会自动注入所有实现了AirportPlatformStrategyCoverPlatformStrategy和DronePlatformStrategy的Bean
*/
@Autowired
public void registerStrategies(
List<AirportPlatformStrategy> airportStrategies,
List<CoverPlatformStrategy> coverStrategies,
List<DronePlatformStrategy> droneStrategies,
List<AirportSystemManager> 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);
}
/**
* 获取机巢的平台类型
*

View File

@ -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<DroneState, DroneEvent> getCanPointGuard() {
return canPointGuard;
}
@Override
public PlatformAction<DroneState, DroneEvent> getStartPrepareAction() {
return startPrepareAction;
}
@Override
public PlatformAction<DroneState, DroneEvent> getPrepareCompletedAction() {
return prepareCompletedAction;
}
@Override
public PlatformAction<DroneState, DroneEvent> getStartFlyingAction() {
return startFlyingAction;
}
@Override
public PlatformAction<DroneState, DroneEvent> getEmergencyStopAction() {
return emergencyStopAction;
}
@Override
public PlatformAction<DroneState, DroneEvent> getResumeFlyingAction() {
return resumeFlyingAction;
}
@Override
public PlatformAction<DroneState, DroneEvent> getArriveAction() {
return arriveAction;
}
@Override
public PlatformAction<DroneState, DroneEvent> getStartReturnAction() {
return startReturnAction;
}
@Override
public PlatformAction<DroneState, DroneEvent> getReturnEmergencyStopAction() {
return returnEmergencyStopAction;
}
@Override
public PlatformAction<DroneState, DroneEvent> getResumeReturnAction() {
return resumeReturnAction;
}
@Override
public PlatformAction<DroneState, DroneEvent> getReturnCompletedAction() {
return returnCompletedAction;
}
@Override
public PlatformAction<DroneState, DroneEvent> getStartPointingAction() {
return startPointingAction;
}
@Override
public PlatformAction<DroneState, DroneEvent> getPointPrepareCompletedAction() {
return pointPrepareCompletedAction;
}
@Override
public PlatformAction<DroneState, DroneEvent> getPointFlyingCompletedAction() {
return pointFlyingCompletedAction;
}
@Override
public PlatformAction<DroneState, DroneEvent> getCancelPointAction() {
return cancelPointAction;
}
@Override
public PlatformAction<DroneState, DroneEvent> getPointToFlyingAction() {
return pointToFlyingAction;
}
@Override
public PlatformAction<DroneState, DroneEvent> getPointToReturnAction() {
return pointToReturnAction;
}
@Override
public PlatformAction<DroneState, DroneEvent> getOfflineAction() {
return offlineAction;
}
@Override
public PlatformListener<DroneState, DroneEvent> getListener() {
return djiDroneListener;
}
}

View File

@ -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<DroneState, DroneEvent> context) {
String machineId = (String) context.getExtendedState().getVariables().get("machineId");
System.out.println(String.format("[DJI] 无人机到达目的地: %s", machineId));
}
}

View File

@ -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<DroneState, DroneEvent> context) {
String machineId = (String) context.getExtendedState().getVariables().get("machineId");
System.out.println(String.format("[DJI] 无人机取消指点: %s", machineId));
}
}

View File

@ -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<DroneState, DroneEvent> context) {
String machineId = (String) context.getExtendedState().getVariables().get("machineId");
System.out.println(String.format("[DJI] 无人机离线: %s", machineId));
}
}

View File

@ -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<DroneState, DroneEvent> context) {
String machineId = (String) context.getExtendedState().getVariables().get("machineId");
System.out.println(String.format("[DJI] 无人机急停: %s", machineId));
}
}

View File

@ -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<DroneState, DroneEvent> context) {
String machineId = (String) context.getExtendedState().getVariables().get("machineId");
System.out.println(String.format("[DJI] 无人机指点飞行完成: %s", machineId));
}
}

View File

@ -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<DroneState, DroneEvent> context) {
String machineId = (String) context.getExtendedState().getVariables().get("machineId");
System.out.println(String.format("[DJI] 无人机指点准备完成: %s", machineId));
}
}

View File

@ -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<DroneState, DroneEvent> context) {
String machineId = (String) context.getExtendedState().getVariables().get("machineId");
System.out.println(String.format("[DJI] 无人机从指点返回飞行: %s", machineId));
}
}

View File

@ -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<DroneState, DroneEvent> context) {
String machineId = (String) context.getExtendedState().getVariables().get("machineId");
System.out.println(String.format("[DJI] 无人机从指点开始返航: %s", machineId));
}
}

View File

@ -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<DroneState, DroneEvent> context) {
String machineId = (String) context.getExtendedState().getVariables().get("machineId");
System.out.println(String.format("[DJI] 无人机准备完成: %s", machineId));
}
}

View File

@ -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<DroneState, DroneEvent> context) {
String machineId = (String) context.getExtendedState().getVariables().get("machineId");
System.out.println(String.format("[DJI] 无人机恢复飞行: %s", machineId));
}
}

View File

@ -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<DroneState, DroneEvent> context) {
String machineId = (String) context.getExtendedState().getVariables().get("machineId");
System.out.println(String.format("[DJI] 无人机恢复返航: %s", machineId));
}
}

View File

@ -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<DroneState, DroneEvent> context) {
String machineId = (String) context.getExtendedState().getVariables().get("machineId");
System.out.println(String.format("[DJI] 无人机返航完成: %s", machineId));
}
}

View File

@ -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<DroneState, DroneEvent> context) {
String machineId = (String) context.getExtendedState().getVariables().get("machineId");
System.out.println(String.format("[DJI] 无人机返航急停: %s", machineId));
}
}

View File

@ -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<DroneState, DroneEvent> context) {
String machineId = (String) context.getExtendedState().getVariables().get("machineId");
System.out.println(String.format("[DJI] 无人机开始飞行: %s", machineId));
}
}

View File

@ -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<DroneState, DroneEvent> context) {
String machineId = (String) context.getExtendedState().getVariables().get("machineId");
System.out.println(String.format("[DJI] 无人机开始指点操作: %s", machineId));
}
}

View File

@ -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<DroneState, DroneEvent> context) {
String machineId = (String) context.getExtendedState().getVariables().get("machineId");
System.out.println(String.format("[DJI] 无人机开始准备: %s", machineId));
}
}

View File

@ -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<DroneState, DroneEvent> context) {
String machineId = (String) context.getExtendedState().getVariables().get("machineId");
System.out.println(String.format("[DJI] 无人机开始返航: %s", machineId));
}
}

View File

@ -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<DroneState, DroneEvent> context) {
DroneState currentState = context.getStateMachine().getState().getId();
// 返航中不能指点
if (currentState == DroneState.RETURNING) {
System.out.println("[DJI] 返航中不能进行指点操作");
return false;
}
return true;
}
}

View File

@ -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";
}
}

View File

@ -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;
/**
* 无人机平台策略接口
* 定义各平台需要实现的GuardAction和Listener
*/
public interface DronePlatformStrategy {
/**
* 获取平台类型
*/
PlatformType getPlatformType();
// ==================== Guards ====================
/**
* 是否可以指点
*/
PlatformGuard<DroneState, DroneEvent> getCanPointGuard();
// ==================== Actions ====================
// 准备阶段
PlatformAction<DroneState, DroneEvent> getStartPrepareAction();
PlatformAction<DroneState, DroneEvent> getPrepareCompletedAction();
// 飞行阶段
PlatformAction<DroneState, DroneEvent> getStartFlyingAction();
PlatformAction<DroneState, DroneEvent> getEmergencyStopAction();
PlatformAction<DroneState, DroneEvent> getResumeFlyingAction();
PlatformAction<DroneState, DroneEvent> getArriveAction();
// 返航阶段
PlatformAction<DroneState, DroneEvent> getStartReturnAction();
PlatformAction<DroneState, DroneEvent> getReturnEmergencyStopAction();
PlatformAction<DroneState, DroneEvent> getResumeReturnAction();
PlatformAction<DroneState, DroneEvent> getReturnCompletedAction();
// 指点操作
PlatformAction<DroneState, DroneEvent> getStartPointingAction();
PlatformAction<DroneState, DroneEvent> getPointPrepareCompletedAction();
PlatformAction<DroneState, DroneEvent> getPointFlyingCompletedAction();
PlatformAction<DroneState, DroneEvent> getCancelPointAction();
PlatformAction<DroneState, DroneEvent> getPointToFlyingAction();
PlatformAction<DroneState, DroneEvent> getPointToReturnAction();
// 离线
PlatformAction<DroneState, DroneEvent> getOfflineAction();
// ==================== Listener ====================
/**
* 获取平台Listener
* 每个平台有一个Listener实例所有该平台的无人机共享
*/
PlatformListener<DroneState, DroneEvent> getListener();
}

View File

@ -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<String, AirportState> airportStateMap = new ConcurrentHashMap<>();
private final Map<String, CoverState> coverStateMap = new ConcurrentHashMap<>();
private final Map<String, DroneState> 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<AirportState> loadAirportState(String airportSn) {
return Optional.ofNullable(airportStateMap.get(airportSn));
}
@ -41,6 +49,10 @@ public class RedisStateStore {
return Optional.ofNullable(coverStateMap.get(airportSn));
}
public Optional<DroneState> loadDroneState(String droneSn) {
return Optional.ofNullable(droneStateMap.get(droneSn));
}
public Set<String> allAirportIds() {
// 合并两张表的 key防止有只存机场或只存舱门的情况
Set<String> ids = ConcurrentHashMap.newKeySet();
@ -48,5 +60,9 @@ public class RedisStateStore {
ids.addAll(coverStateMap.keySet());
return ids;
}
public Set<String> allDroneIds() {
return droneStateMap.keySet();
}
}

View File

@ -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<DroneState, DroneEvent> droneStateMachineFactory;
@Autowired
private RedisStateStore redisStateStore;
/**
* 存储所有无人机的状态机实例
* Key: 无人机ID (droneSn)
* Value: 状态机实例
*/
private final Map<String, StateMachine<DroneState, DroneEvent>> stateMachineMap = new ConcurrentHashMap<>();
/**
* 获取或创建状态机
* 如果状态机不存在则创建新的状态机实例
*
* @param droneSn 无人机序列号
* @return 状态机实例
*/
public StateMachine<DroneState, DroneEvent> getOrCreateStateMachine(String droneSn) {
return stateMachineMap.computeIfAbsent(droneSn, id -> {
StateMachine<DroneState, DroneEvent> stateMachine = droneStateMachineFactory.getStateMachine(id);
// 服务器重启后状态机初始化为 UNKNOWN 状态
// 不从 Redis 恢复旧状态等待第一次心跳同步真实状态
// 这样可以避免服务器重启期间丢失心跳导致的状态不一致问题
stateMachine.start();
System.out.println(String.format("创建并启动状态机: %s, 初始状态: UNKNOWN (等待心跳同步)", id));
return stateMachine;
});
}
/**
* 获取状态机不创建
*
* @param droneSn 无人机序列号
* @return 状态机实例如果不存在返回null
*/
public StateMachine<DroneState, DroneEvent> getStateMachine(String droneSn) {
return stateMachineMap.get(droneSn);
}
/**
* 获取状态机的当前状态
*
* @param droneSn 无人机序列号
* @return 当前状态如果状态机不存在返回null
*/
public DroneState getCurrentState(String droneSn) {
StateMachine<DroneState, DroneEvent> 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<DroneState, DroneEvent> 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<DroneState, DroneEvent> 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<DroneState, DroneEvent> 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<String> getAllDroneIds() {
return stateMachineMap.keySet();
}
/**
* 检查状态机是否处于指定状态
*
* @param droneSn 无人机序列号
* @param state 要检查的状态
* @return 是否处于指定状态
*/
public boolean isInState(String droneSn, DroneState state) {
StateMachine<DroneState, DroneEvent> 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);
}
}

View File

@ -0,0 +1,9 @@
package com.tuoheng.status.machine.status;
/**
* 飞行控制模式DRC
* 未知状态 退出状态 进入中 进入状态 退出中
*/
public enum DrcState {
}

View File

@ -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
}

View File

@ -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<Status, Event> {
@Override
public void execute(StateContext<Status, Event> context) {
// 执行业务逻辑
logger.info("开始处理任务");
Object taskId = context.getExtendedState().getVariables().get("taskId");
// ...
}
}
```
### 4. Guard守卫
Guard 用于条件判断决定是否允许状态转换。Guard 在转换**之前**执行,如果返回 `false`,转换将被阻止。
**示例:**
```java
public static class CanStartGuard implements Guard<Status, Event> {
@Override
public boolean evaluate(StateContext<Status, Event> 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<Status, Event> stateMachine =
stateMachineManager.getOrCreateStateMachine("machine-001");
// 2. 通过ID获取状态机
StateMachine<Status, Event> 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<String> allIds = stateMachineManagerService.getAllMachineIds();
```
### 管理多个状态机
```java
// 创建多个状态机
stateMachineManager.getOrCreateStateMachine("machine-001");
stateMachineManager.getOrCreateStateMachine("machine-002");
stateMachineManager.getOrCreateStateMachine("machine-003");
// 获取所有状态机ID
Set<String> 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<Status, Event> stateMachineFactory;
public void processTask(String taskId) {
// 创建状态机实例
StateMachine<Status, Event> 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<Status, Event> {
@Override
public boolean evaluate(StateContext<Status, Event> 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<Status, Event> {
@Override
public void execute(StateContext<Status, Event> 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 转换到 PREPARINGPROCESSING 的子状态)
stateMachine.sendEvent(Event.START);
// 状态机现在处于 PROCESSING 父状态,具体是 PREPARING 子状态
```
### 4. 状态机实例管理
每次调用 `getStateMachine()` 都会创建新的状态机实例。如果需要复用实例,需要自己管理:
```java
// 方式一:每次创建新实例(推荐用于独立任务)
StateMachine<Status, Event> sm1 = stateMachineFactory.getStateMachine();
StateMachine<Status, Event> sm2 = stateMachineFactory.getStateMachine();
// 方式二:复用实例(需要自己管理生命周期)
StateMachine<Status, Event> sm = stateMachineFactory.getStateMachine();
// ... 使用 sm
sm.stop(); // 使用完后停止
```
### 5. 监听器中的空指针异常
某些转换(如初始转换)可能没有 trigger需要检查 null
```java
@Override
public void transitionStarted(Transition<Status, Event> transition) {
if (transition.getTrigger() != null) {
logger.info("事件: " + transition.getTrigger().getEvent());
} else {
logger.info("事件: null (可能是初始转换)");
}
}
```
### 6. 通过ID获取状态机
使用 `StateMachineManager` 可以方便地管理多个状态机:
```java
@Autowired
private StateMachineManager stateMachineManager;
// 获取状态机
StateMachine<Status, Event> sm = stateMachineManager.getStateMachine("machine-id");
// 如果不存在,创建新实例
StateMachine<Status, Event> 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`

View File

@ -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<Status, Event> {
@Override
public void execute(StateContext<Status, Event> 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<Status, Event> {
@Override
public void execute(StateContext<Status, Event> 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<Status, Event> {
@Override
public void execute(StateContext<Status, Event> 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<Status, Event> {
@Override
public void execute(StateContext<Status, Event> 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<Status, Event> {
@Override
public void execute(StateContext<Status, Event> context) {
logger.info("=== Action: 任务完成 ===");
logger.info("从状态: " + context.getSource().getId());
logger.info("到状态: " + context.getTarget().getId());
// 清理资源或执行完成后的操作
logger.info("清理资源...");
}
}
/**
* 失败 Action
*/
public static class FailAction implements Action<Status, Event> {
@Override
public void execute(StateContext<Status, Event> 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<Status, Event> {
@Override
public void execute(StateContext<Status, Event> context) {
logger.info("=== Action: 重置状态机 ===");
// 清理扩展状态中的变量
context.getExtendedState().getVariables().clear();
logger.info("状态机已重置");
}
}
}

View File

@ -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;
/**
* 状态机配置类
* 配置状态子状态转换ActionGuard
*/
@Configuration
public class TaskStateMachineConfig {
private static final Logger logger = Logger.getLogger(TaskStateMachineConfig.class.getName());
/**
* 构建状态机工厂
* 使用工厂模式可以为每个任务创建独立的状态机实例
*/
@Bean
public StateMachineFactory<Status, Event> stateMachineFactory() throws Exception {
// 返回一个简单的 StateMachineFactory 实现
return new StateMachineFactory<Status, Event>() {
@Override
public StateMachine<Status, Event> getStateMachine() {
return getStateMachine(UUID.randomUUID().toString());
}
@Override
public StateMachine<Status, Event> getStateMachine(String machineId) {
try {
// 为每个请求创建新的状态机实例
StateMachineBuilder.Builder<Status, Event> newBuilder = StateMachineBuilder.builder();
configureStateMachine(newBuilder);
configureStates(newBuilder);
configureTransitions(newBuilder);
StateMachine<Status, Event> 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<Status, Event> getStateMachine(UUID uuid) {
return getStateMachine(uuid.toString());
}
};
}
/**
* 配置状态机基本设置
*/
private void configureStateMachine(StateMachineBuilder.Builder<Status, Event> builder) throws Exception {
builder.configureConfiguration()
.withConfiguration()
.autoStartup(true) // 自动启动
.listener(new TaskStateMachineListener()); // 添加监听器
}
/**
* 配置状态和子状态
*/
private void configureStates(StateMachineBuilder.Builder<Status, Event> 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<Status, Event> 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());
}
}

View File

@ -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("说明: 不设置 taskIdGuard 会阻止状态转换");
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();
}
}

View File

@ -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<String> 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();
}
}

View File

@ -1,15 +0,0 @@
package com.tuoheng.status.statemachine.events;
/**
* 事件枚举
*/
public enum Event {
START, // 开始处理
PREPARE, // 准备就绪
EXECUTE, // 执行
VALIDATE, // 验证
COMPLETE, // 完成
FAIL, // 失败
RETRY, // 重试
RESET // 重置
}

View File

@ -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<Status, Event> {
@Override
public boolean evaluate(StateContext<Status, Event> 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<Status, Event> {
@Override
public boolean evaluate(StateContext<Status, Event> 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<Status, Event> {
@Override
public boolean evaluate(StateContext<Status, Event> 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<Status, Event> {
@Override
public boolean evaluate(StateContext<Status, Event> 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<Status, Event> {
@Override
public boolean evaluate(StateContext<Status, Event> 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<Status, Event> {
@Override
public boolean evaluate(StateContext<Status, Event> context) {
return true;
}
}
}

View File

@ -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<Status, Event> {
private static final Logger logger = Logger.getLogger(TaskStateMachineListener.class.getName());
/**
* 状态机启动时调用
*/
@Override
public void stateMachineStarted(StateMachine<Status, Event> stateMachine) {
logger.info("========== 状态机启动 ==========");
if (stateMachine.getInitialState() != null) {
logger.info("初始状态: " + stateMachine.getInitialState().getId());
} else {
logger.info("初始状态: null");
}
}
/**
* 状态机停止时调用
*/
@Override
public void stateMachineStopped(StateMachine<Status, Event> stateMachine) {
logger.info("========== 状态机停止 ==========");
if (stateMachine.getState() != null) {
logger.info("最终状态: " + stateMachine.getState().getId());
} else {
logger.info("最终状态: null");
}
}
/**
* 状态机错误时调用
*/
@Override
public void stateMachineError(StateMachine<Status, Event> stateMachine, Exception exception) {
logger.severe("========== 状态机错误 ==========");
logger.severe("错误信息: " + exception.getMessage());
exception.printStackTrace();
}
/**
* 状态进入时调用
*/
@Override
public void stateEntered(State<Status, Event> state) {
logger.info("========== 进入状态: " + state.getId() + " ==========");
}
/**
* 状态退出时调用
*/
@Override
public void stateExited(State<Status, Event> state) {
logger.info("========== 退出状态: " + state.getId() + " ==========");
}
/**
* 状态改变时调用
*/
@Override
public void stateChanged(State<Status, Event> from, State<Status, Event> to) {
logger.info("========== 状态改变 ==========");
logger.info("从: " + (from != null ? from.getId() : "null"));
logger.info("到: " + (to != null ? to.getId() : "null"));
}
/**
* 转换开始时调用
*/
@Override
public void transitionStarted(Transition<Status, Event> 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<Status, Event> 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<Status, Event> 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> 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<Status, Event> stateContext) {
// 可以在这里记录状态上下文信息
}
}

View File

@ -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<Status, Event> stateMachineFactory;
// 存储状态机实例的Mapkey为状态机ID
private final Map<String, StateMachine<Status, Event>> stateMachineMap = new ConcurrentHashMap<>();
/**
* 创建或获取状态机实例
* 如果ID对应的状态机不存在则创建新实例如果存在则返回现有实例
*
* @param machineId 状态机ID
* @return 状态机实例
*/
public StateMachine<Status, Event> getOrCreateStateMachine(String machineId) {
return stateMachineMap.computeIfAbsent(machineId, id -> {
logger.info("创建新状态机实例ID: " + id);
StateMachine<Status, Event> stateMachine = stateMachineFactory.getStateMachine(id);
// 将machineId存储到扩展状态中
stateMachine.getExtendedState().getVariables().put("machineId", id);
// 启动状态机
stateMachine.start();
return stateMachine;
});
}
/**
* 获取状态机实例如果不存在则返回null
*
* @param machineId 状态机ID
* @return 状态机实例如果不存在则返回null
*/
public StateMachine<Status, Event> getStateMachine(String machineId) {
return stateMachineMap.get(machineId);
}
/**
* 创建新的状态机实例如果已存在则先停止并移除旧的
*
* @param machineId 状态机ID
* @return 新创建的状态机实例
*/
public StateMachine<Status, Event> createStateMachine(String machineId) {
// 如果已存在先停止并移除
StateMachine<Status, Event> 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<Status, Event> 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<Status, Event> stateMachine = stateMachineMap.get(machineId);
if (stateMachine == null) {
logger.warning("状态机不存在ID: " + machineId);
return null;
}
State<Status, Event> state = stateMachine.getState();
if (state == null) {
return null;
}
return state.getId();
}
/**
* 获取状态机的详细信息包括当前状态和扩展状态
*
* @param machineId 状态机ID
* @return 状态机信息如果状态机不存在则返回null
*/
public StateMachineInfo getStateMachineInfo(String machineId) {
StateMachine<Status, Event> stateMachine = stateMachineMap.get(machineId);
if (stateMachine == null) {
return null;
}
StateMachineInfo info = new StateMachineInfo();
info.setMachineId(machineId);
State<Status, Event> 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<Status, Event> 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<String> getAllMachineIds() {
return stateMachineMap.keySet();
}
/**
* 获取当前管理的状态机数量
*
* @return 状态机数量
*/
public int getStateMachineCount() {
return stateMachineMap.size();
}
/**
* 清空所有状态机实例
*/
public void clearAll() {
logger.info("清空所有状态机实例,数量: " + stateMachineMap.size());
for (Map.Entry<String, StateMachine<Status, Event>> 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<Object, Object> 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<Object, Object> getExtendedState() {
return extendedState;
}
public void setExtendedState(Map<Object, Object> extendedState) {
this.extendedState = extendedState;
}
@Override
public String toString() {
return "StateMachineInfo{" +
"machineId='" + machineId + '\'' +
", currentStatus=" + currentStatus +
", isSubState=" + isSubState +
", isRunning=" + isRunning +
", extendedState=" + extendedState +
'}';
}
}
}

View File

@ -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<Status, Event> 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<Status, Event> stateMachine = stateMachineManager.getOrCreateStateMachine(machineId);
// 设置任务ID到扩展状态
if (taskId != null) {
stateMachine.getExtendedState().getVariables().put("taskId", taskId);
}
return stateMachine.sendEvent(Event.START);
}
/**
* 获取所有状态机的ID列表
*
* @return 状态机ID集合
*/
public Set<String> 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("================================");
}
}

View File

@ -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<Status, Event> stateMachineFactory;
/**
* 启动任务处理流程
*/
public void startTask(String taskId) {
logger.info("\n========== 开始任务处理流程 ==========");
StateMachine<Status, Event> 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<Status, Event> 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<Status, Event> 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 事件(没有 taskIdGuard 应该失败)---");
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<Status, Event> 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();
}
}
}

View File

@ -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 的子状态
}

View File

@ -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. **并发控制**:同一状态机的并发操作控制

View File

@ -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<String> 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);
}
}

View File

@ -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");
}
}