Tutorial 10 — Multi-Stage Missions
This tutorial brings together everything you have learned to study two complete multi-stage state machines: sm_multi_stage_1 (a complex hierarchical demo) and sm_cl_px4_mr_test_1 (a real-world PX4 flight mission).
sm_multi_stage_1: Architecture
sm_multi_stage_1 demonstrates the largest SMACC2 hierarchical pattern:
SmMultiStage1
├── MsMode1
│ ├── SequenceA (loop: StiLoop → Sti1 → Sti2 → ... → Sti9)
│ ├── SequenceB
│ ├── SequenceC
│ └── SequenceD → EvLoopEnd → MsMode2
├── MsMode2
│ ├── SequenceA → SequenceB → SequenceC → SequenceD → MsMode3
├── MsMode3
│ └── ... → MsMode4
├── MsMode4
│ └── ... → MsMode5
├── MsMode5
│ └── ... (terminal or loop back)
├── MsRecovery1
│ └── Recovery sequences → MsMode::deep_history
└── MsRecovery2
└── Recovery sequences → MsMode::deep_history
Key patterns:
5 operational modes with multiple sequences each
Recovery modes that restore the previous state via deep history
Sequence super states with inner loop states (each sequence loops through 9 steps)
Mode-to-mode transitions via
EvLoopEnd<SequenceD>
Mode State Transitions
struct MsMode1 : smacc2::SmaccState<MsMode1, SmMultiStage1, SequenceA>
{
using SmaccState::SmaccState;
typedef mpl::list<
Transition<EvLoopEnd<SequenceD>, MsMode2>
>reactions;
};
When SequenceD inside MsMode1 finishes its loop iterations, it posts EvLoopEnd<SequenceD>. The mode state catches this and transitions to MsMode2.
sm_cl_px4_mr_test_1: Real-World Mission
This state machine flies a PX4 multirotor through a complete mission: arm → takeoff → navigate → orbit → return → land.
State Machine Definition
// sm_cl_px4_mr_test_1.hpp
struct SmClPx4MrTest1
: public smacc2::SmaccStateMachineBase<SmClPx4MrTest1, MsDisarmedOnGround>
{
using SmaccStateMachineBase::SmaccStateMachineBase;
virtual void onInitialize() override
{
this->createOrthogonal<OrPx4>();
this->createOrthogonal<OrTimer>();
}
};
Mode States
The mission is organized into 6 flight phases, each a mode state:
Mode State |
Initial Child |
Purpose |
|---|---|---|
|
|
Wait for PX4 topics |
|
|
Arm the vehicle |
|
|
Climb to target altitude |
|
|
Execute mission waypoints |
|
|
Descend and touch down |
|
(terminal) |
Mission complete |
Mission Flow
MsDisarmedOnGround
└── StWaitForReady ──[EvTimer]──→ MsArmedOnGround
└── StArmPx4 ──[EvCbSuccess]──→ MsTakeoff
└── StTakeoff(5.0m) ──[EvCbSuccess]──→ MsInFlight
├── StGoToWaypoint1(10,0,-5) ──[EvCbSuccess]──→
├── StOrbitLocation(r=5,n=3) ──[EvCbSuccess]──→
└── StReturnToBase(0,0,-5) ──[EvCbSuccess]──→ MsLanding
└── StLand ──[EvCbSuccess]──→ MsLanded
Each state uses a single behavior that posts EvCbSuccess on completion:
// states/in_flight/st_go_to_waypoint_1.hpp
struct StGoToWaypoint1 : smacc2::SmaccState<StGoToWaypoint1, MsInFlight>
{
using SmaccState::SmaccState;
typedef mpl::list<
Transition<EvCbSuccess<CbGoToLocation, OrPx4>, StOrbitLocation, SUCCESS>
>reactions;
static void staticConfigure()
{
// NED coordinates: 10m North, 0m East, 5m altitude (negative Z = up)
configure_orthogonal<OrPx4, CbGoToLocation>(10.0f, 0.0f, -5.0f);
}
};
// states/in_flight/st_orbit_location.hpp
struct StOrbitLocation : smacc2::SmaccState<StOrbitLocation, MsInFlight>
{
using SmaccState::SmaccState;
typedef mpl::list<
Transition<EvCbSuccess<CbOrbitLocation, OrPx4>, StReturnToBase, SUCCESS>
>reactions;
static void staticConfigure()
{
// center=(10,0), alt=5m, radius=5m, angular_vel=0.5 rad/s, 3 orbits
configure_orthogonal<OrPx4, CbOrbitLocation>(
10.0f, 0.0f, 5.0f, 5.0f, 0.5f, 3);
}
};
Signal Wiring End-to-End
The complete chain from state configuration to event:
State calls
configure_orthogonal<OrPx4, CbGoToLocation>(10.0f, 0.0f, -5.0f)CbGoToLocation::onEntry() calls
requiresComponent(goalChecker_)andrequiresComponent(trajectorySetpoint_)CbGoToLocation sets the goal on
CpGoalCheckerand the setpoint onCpTrajectorySetpointCpGoalChecker::update() (called at 20 Hz) compares current position vs goal
When within tolerance,
CpGoalCheckerfiresonGoalReached_signalCbGoToLocation::onGoalReachedCallback() calls
postSuccessEvent()EvCbSuccess<CbGoToLocation, OrPx4>matches the transition → next state
Cross-Orthogonal Access
Sometimes a behavior needs data from a component on a different orthogonal. You can access any orthogonal’s client and components through the state machine:
void onEntry() override
{
// Access a component from a different orthogonal
auto * otherClient = this->getStateMachine()
->getOrthogonal<OrOther>()
->getClient<ClOtherClient>();
ClOtherComponent * comp;
otherClient->requiresComponent(comp);
}
Designing Your Own Multi-Stage Mission
When designing a complex mission:
Identify the major phases — these become mode states (e.g., Initialization, Active, Recovery, Shutdown)
Break each phase into steps — these become states within the mode state
Identify repeating patterns — these become super state loops
Define recovery strategies — use mode states with deep history transitions
Assign orthogonals — one per independent concern (navigation, manipulation, monitoring, etc.)
Summary
You learned:
How
sm_multi_stage_1uses 5 modes with sequence super states for large-scale missionsHow
sm_cl_px4_mr_test_1organizes a real flight mission into 6 flight-phase mode statesThe complete signal wiring chain from state configuration to event-driven transition
Cross-orthogonal component access
Design principles for multi-stage missions
Further Reading
HSM Architecture — Hierarchical state machine architecture
Substate Architecture — Substates, orthogonals, events, components
How to Use PX4 with SMACC2 — PX4 integration reference
How to Use Nav2 with SMACC2 — Nav2 integration reference