ModeManager Safe Mode Finite State Machine
This document describes the finite state machine (FSM) for the ModeManager component's Safe Mode transitions. The ModeManager controls system operational modes and orchestrates transitions between NORMAL and SAFE_MODE based on various triggers.
State Machine Diagram
stateDiagram-v2
[*] --> NORMAL: System Boot (clean shutdown)
[*] --> SAFE_MODE: System Boot (unintended reboot)
NORMAL --> SAFE_MODE: Entry Trigger
SAFE_MODE --> NORMAL: Exit Trigger
note right of NORMAL
Active Components:
- All 6 face load switches ON
- Payload switches OFF (manual control)
- Voltage monitoring active
- SafeModeReason = NONE
end note
note right of SAFE_MODE
Protected State:
- All 8 load switches OFF
- Power consumption minimized
- SafeModeReason tracks cause
- Safe mode sequence executed
end note
state NORMAL {
[*] --> VoltageMonitoring
VoltageMonitoring --> VoltageMonitoring: Voltage OK<br/>(Reset counter)
VoltageMonitoring --> LowVoltageDebounce: Voltage < 6.7V<br/>(Increment counter)
LowVoltageDebounce --> VoltageMonitoring: Voltage OK<br/>(Reset counter)
LowVoltageDebounce --> [*]: Counter >= 10s<br/>(Trigger Safe Mode)
}
state SAFE_MODE {
[*] --> ReasonCheck
ReasonCheck --> LOW_BATTERY_Mode: Reason = LOW_BATTERY
ReasonCheck --> MANUAL_EXIT_Mode: Reason != LOW_BATTERY
LOW_BATTERY_Mode --> RecoveryMonitoring
RecoveryMonitoring --> RecoveryMonitoring: Voltage < 8.0V<br/>(Reset counter)
RecoveryMonitoring --> VoltageRecoveryDebounce: Voltage > 8.0V<br/>(Increment counter)
VoltageRecoveryDebounce --> RecoveryMonitoring: Voltage < 8.0V<br/>(Reset counter)
VoltageRecoveryDebounce --> [*]: Counter >= 10s<br/>(Auto-Exit)
MANUAL_EXIT_Mode --> MANUAL_EXIT_Mode: Waiting for<br/>EXIT_SAFE_MODE<br/>command
MANUAL_EXIT_Mode --> [*]: EXIT_SAFE_MODE<br/>command received
}
State Transitions
Entry Transitions: NORMAL → SAFE_MODE
The ModeManager can enter SAFE_MODE from NORMAL mode through multiple triggers, each assigned a specific SafeModeReason:
| Trigger | SafeModeReason | Description | Debounce |
|---|---|---|---|
| Auto: Low Voltage | LOW_BATTERY (1) |
Voltage drops below SafeModeEntryVoltage parameter (default: 6.7V) |
10 seconds (configurable) |
| Command: FORCE_SAFE_MODE | GROUND_COMMAND (3) |
Ground operator issues FORCE_SAFE_MODE command | Immediate |
| Port: forceSafeMode | EXTERNAL_REQUEST (4) or custom |
External component calls forceSafeMode port (e.g., watchdog timeout) | Immediate |
| Auto: Unintended Reboot | SYSTEM_FAULT (2) |
System boots with cleanShutdown flag = 0 in NORMAL mode | At boot only |
| Port: forceSafeMode (LoRa) | LORA (5) |
LoRa driver detects communication timeout/fault | Immediate |
Entry Actions
When entering SAFE_MODE, the ModeManager:
1. Executes the safe mode radio sequence (/seq/enter_safe.bin)
2. Sets m_mode = SAFE_MODE
3. Increments m_safeModeEntryCount (persisted)
4. Sets m_safeModeReason to the trigger reason
5. Emits one or more events:
- EnteringSafeMode(reason: string) - Severity: WARNING_HI (always emitted with reason string)
- AutoSafeModeEntry(reason: SafeModeReason, voltage: F32) - Severity: WARNING_HI (for LOW_BATTERY trigger)
- UnintendedRebootDetected() - Severity: WARNING_HI (for SYSTEM_FAULT at boot)
- ManualSafeModeEntry() - Severity: ACTIVITY_HI (for FORCE_SAFE_MODE command)
- ExternalFaultDetected() - Severity: WARNING_HI (for forceSafeMode port call)
6. Turns OFF all 8 load switches via loadSwitchTurnOff ports
7. Notifies other components via modeChanged port with SAFE_MODE value
8. Executes safe mode sequence via runSequence port (may emit SafeModeSequenceCompleted or SafeModeSequenceFailed)
9. Saves state to persistent storage (/mode_state.bin)
Exit Transitions: SAFE_MODE → NORMAL
Exit from SAFE_MODE depends on the SafeModeReason:
| Exit Method | Conditions | Applicable Reasons |
|---|---|---|
| Auto-Recovery | Voltage > SafeModeRecoveryVoltage (default: 8.0V) for 10+ seconds |
LOW_BATTERY only |
| Manual Command | Ground operator issues EXIT_SAFE_MODE command | All reasons |
Exit Actions
When exiting SAFE_MODE, the ModeManager:
1. Sets m_mode = NORMAL
2. Clears m_safeModeReason = NONE
3. Emits exit event:
- ExitingSafeMode() - Severity: ACTIVITY_HI (manual EXIT_SAFE_MODE command)
- AutoSafeModeExit(voltage: F32) - Severity: ACTIVITY_HI (auto-recovery for LOW_BATTERY)
4. Turns ON face load switches (0-5) via loadSwitchTurnOn ports
- Note: Payload switches (6-7) remain OFF, requiring separate commands
5. Notifies other components via modeChanged port with NORMAL value
6. Saves state to persistent storage
Boot-Time State Restoration
On system initialization, the ModeManager:
1. Reads persistent state from /mode_state.bin
2. Restores m_mode, m_safeModeEntryCount, and m_safeModeReason
3. Checks cleanShutdown flag:
- If cleanShutdown = 1: Clean boot, restore physical hardware to match saved mode
- If cleanShutdown = 0 AND m_mode = NORMAL: Unintended reboot detected → Enter SAFE_MODE with reason SYSTEM_FAULT
4. Clears cleanShutdown flag (sets to 0) for next boot detection
Clean Shutdown Protocol:
- The prepareForReboot port handler sets cleanShutdown = 1 before intentional reboots
- This allows detection of crashes, watchdog resets, and power loss events
State Invariants
NORMAL Mode
m_mode = NORMAL (2)m_safeModeReason = NONE (0)- Face load switches (0-5) are ON
- Payload switches (6-7) are OFF (default)
- Voltage monitoring active (1Hz via
runhandler) - Low voltage counter (
m_safeModeVoltageCounter) tracks consecutive low readings - Recovery counter (
m_recoveryVoltageCounter) is reset to 0
SAFE_MODE Mode
m_mode = SAFE_MODE (1)m_safeModeReason= {LOW_BATTERY,SYSTEM_FAULT,GROUND_COMMAND,EXTERNAL_REQUEST,LORA}- All 8 load switches are OFF
- Voltage recovery monitoring active only if
reason = LOW_BATTERY - Recovery counter (
m_recoveryVoltageCounter) tracks consecutive recovery readings (if LOW_BATTERY) - Low voltage counter (
m_safeModeVoltageCounter) is reset to 0
Voltage Hysteresis
The ModeManager implements voltage hysteresis to prevent oscillation between modes:
- Entry Threshold: 6.7V (configurable via
SafeModeEntryVoltageparameter) - Recovery Threshold: 8.0V (configurable via
SafeModeRecoveryVoltageparameter) - Gap: 1.3V hysteresis prevents rapid mode switching
Rationale: Battery voltage may fluctuate under load. The higher recovery threshold ensures the system has sufficient margin before resuming normal operations.
Debouncing Logic
All voltage-triggered transitions use a configurable debounce period (default: 10 seconds):
NORMAL → SAFE_MODE (Low Voltage):
- Counter increments each second voltage < 6.7V
- Counter resets to 0 if voltage >= 6.7V
- Transition occurs when counter >= 10
SAFE_MODE → NORMAL (Auto-Recovery):
- Counter increments each second voltage > 8.0V AND reason = LOW_BATTERY
- Counter resets to 0 if voltage <= 8.0V
- Transition occurs when counter >= 10
Rationale: Debouncing prevents spurious transitions due to transient voltage spikes/dips, sensor noise, or momentary load changes.
Reason-Based Recovery Rules
Only LOW_BATTERY reason allows automatic recovery. Other reasons require manual intervention:
| Reason | Auto-Recovery | Rationale |
|---|---|---|
LOW_BATTERY |
✅ Yes | Condition is measurable and reversible; safe to auto-recover when voltage stabilizes |
SYSTEM_FAULT |
❌ No | Unintended reboot indicates unknown system issue; requires ground investigation |
GROUND_COMMAND |
❌ No | Operator explicitly commanded safe mode; requires operator approval to exit |
EXTERNAL_REQUEST |
❌ No | Another component detected a fault; requires component-specific recovery |
LORA |
❌ No | Communication fault may indicate antenna deployment issue or ground station unavailability |
Telemetry Channels
The ModeManager publishes telemetry every 1Hz via the run handler:
CurrentMode: U8 (1 = SAFE_MODE, 2 = NORMAL)CurrentSafeModeReason: SafeModeReason enum (0-5)SafeModeEntryCount: U32 (cumulative count, persisted across reboots)
Implementation Details
File Locations
- Component Definition:
PROVESFlightControllerReference/Components/ModeManager/ModeManager.fpp - Implementation:
PROVESFlightControllerReference/Components/ModeManager/ModeManager.cpp - Header:
PROVESFlightControllerReference/Components/ModeManager/ModeManager.hpp - Integration Tests:
PROVESFlightControllerReference/test/int/safe_mode_test.py
Key Methods
run_handler(): 1Hz periodic handler for voltage monitoring and telemetryenterSafeMode(reason): Transition to SAFE_MODE with specified reasonexitSafeMode(): Transition to NORMAL (manual command)exitSafeModeAutomatic(voltage): Transition to NORMAL (auto-recovery)forceSafeMode_handler(reason): Port handler for external safe mode requestsloadState(): Restore state from persistent storage at bootsaveState(): Persist state to non-volatile storageprepareForReboot_handler(): Set clean shutdown flag before intentional reboot
Persistent State Structure
struct PersistentState {
U8 mode; // Current mode (1 = SAFE_MODE, 2 = NORMAL)
U32 safeModeEntryCount; // Number of times safe mode entered
U8 safeModeReason; // Reason for safe mode entry (0-5)
U8 cleanShutdown; // Clean shutdown flag (1 = clean, 0 = unclean)
};
/mode_state.bin (size is architecture-dependent due to struct padding, typically 8-12 bytes)
Testing Validation
The Safe Mode FSM is validated through integration tests in safe_mode_test.py:
| Test | Validates | Status |
|---|---|---|
test_safe_01 |
Initial reason = NONE in NORMAL mode | ✅ Automated |
test_safe_02 |
FORCE_SAFE_MODE sets reason = GROUND_COMMAND | ✅ Automated |
test_safe_03 |
EXIT_SAFE_MODE clears reason to NONE | ✅ Automated |
test_safe_04 |
GROUND_COMMAND does not auto-recover | ✅ Automated |
test_safe_05 |
Auto-entry on low voltage → reason = LOW_BATTERY | ⏸️ Manual |
test_safe_06 |
Auto-recovery on voltage recovery (LOW_BATTERY only) | ⏸️ Manual |
test_safe_07 |
Unintended reboot → reason = SYSTEM_FAULT | ⏸️ Manual |
test_safe_08 |
Clean reboot → no safe mode entry | ⏸️ Manual |
Design Rationale
- Two-State System: Simple FSM with clear separation of concerns between operational and protected states
- Reason Tracking: Enables intelligent recovery decisions and diagnostics
- Voltage Hysteresis: Prevents mode oscillation under marginal battery conditions
- Debouncing: Filters out transient faults and sensor noise
- State Persistence: Allows unintended reboot detection and mode restoration across power cycles
- Selective Auto-Recovery: Only measurable/reversible conditions (LOW_BATTERY) auto-recover; others require human decision
- Load Switch Control: Minimizes power consumption in SAFE_MODE while preserving critical functions