State Machine¶
The state machine library provides a generic interface for initializing,
updating, and querying the flight state.
As almost everything else in AURORA, it features a dynamic selection of state
machine types via Kconfig CONFIG_AURORA_STATE_MACHINE_TYPE.
Currently only the simple state machine is implemented and it uses the
following flight sequence:
Simple State Machine¶
The simple state machine implementation defines a 9-state flight sequence driven by sensor thresholds.
Signal |
Comment |
|---|---|
ARM |
ARM signal from extern. Arms the pyro channels as well |
DISARM |
DISARM signal from extern. Disarms the pyro channels as well |
Sensor Reading |
Comment |
|---|---|
TAB |
Acceleration needed to go from ARMED to BOOST |
TH |
Altitude needed to go from ARMED to BOOST |
TBB |
Acceleration needed to go from BOOST to BURNOUT |
TM |
Altitude needed to go from APOGEE to MAIN |
TL |
Velocity needed to signal LANDED |
Timer |
Comment |
|---|---|
DTAB |
Time that T_AB and T_H shall be asserted for |
DTL |
Time that T_L shall be asserted |
Timeout |
Comment |
|---|---|
TOA |
Timeout for APOGEE state |
TOR |
Timeout for REDUNDANT state |
State transitions are also driven by sensor thresholds configured via Kconfig (boost acceleration, main descent height, apogee timeout, etc.).
Remove Before Flight¶
The ARM/DISARM signal normally comes from the application, through
sm_inputs.armed. Setting CONFIG_AURORA_STATE_MACHINE_RBF takes it from
hardware instead: the state machine reads the mechanical safety lock (the key
or shorting plug that is pulled off the rocket on the pad) and substitutes the
pin for armed on every update. Whatever the application writes into that
field is ignored, so software arming and the physical “is the streamer still
in?” check can never disagree.
The GPIO is selected by the auxspace,rbf chosen node and follows the usual
“safe when made” convention:
/ {
chosen {
auxspace,rbf = &rbf_in;
};
buttons {
compatible = "gpio-keys";
rbf_in: rbf_in {
gpios = <&gpio1 6 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
label = "RBF Button";
};
};
}
“Remove Before Flight”-Plug |
Line |
Vehicle |
|---|---|---|
installed |
asserted |
|
removed |
deasserted |
|
Every edge restarts a debounce window
(CONFIG_AURORA_STATE_MACHINE_RBF_DEBOUNCE_MS, default 50 ms) and the level is
only sampled once the contact has been quiet for a full window, so a chattering
plug cannot arm and disarm the machine repeatedly. Changes are recorded in the
audit log.
The level is also not latched during sm_init(), before the interrupt is
enabled: a board that boots with the interlock already pulled never produces
an edge and will sit disarmed forever waiting for one. If the pin
cannot be brought up at all the interlock reports safe, holding the machine in
IDLE rather than arming on an input it cannot read.
Note
This used to be done by pointing the powerfail subsystem at the RBF pin and treating “power failing” as “disarmed”. That is no longer the case: powerfail watches the battery, the state machine watches the interlock, and the two are independent.
Flight Thresholds¶
The thresholds that drive the transitions are per-vehicle data, not a firmware constant: a 200 m model and a 1 km vehicle disagree on every altitude and timeout in the set. The Kconfig options (see the sensor_board tables) are the factory defaults; the running values are edited from the shell and persisted, so a board flashed with one firmware image can fly either rocket.
The store is a single flash erase page selected by the auxspace,sm-config
chosen node, holding one record: a magic, a layout version, a CRC and the
threshold struct. Every save erases and rewrites the whole page. A blank
page, a firmware update that reshuffled struct sm_thresholds, or a write
cut short by a power loss all fail their check and fall back to the
defaults, so a half-written set is never flown.
Reserve the page in the board devicetree, e.g. for micrometer rev.2:
/ {
chosen {
auxspace,sm-config = &sm_config_partition;
};
};
&flash0 {
partitions {
sm_config_partition: partition@3df000 {
compatible = "zephyr,mapped-partition";
label = "sm-config";
reg = <0x3df000 DT_SIZE_K(4)>;
};
};
};
Without the chosen node (or with CONFIG_AURORA_STATE_MACHINE_CONFIG_STORE
disabled) the thresholds still change at runtime, but the shell warns that
the change is lost on reboot.
Thresholds can only be changed in IDLE. Swapping one mid-flight would
compare fresh limits against timers already started under the old ones, so
sm_set_thresholds() returns -EBUSY outside IDLE.
Flight State Recovery¶
With CONFIG_AURORA_STATE_MACHINE_RETAIN, the active flight state survives a
watchdog reset. Without it, a board that reboots mid-flight comes back in
IDLE — a silently disarmed vehicle, with the recovery charges no longer
going to fire.
The record lives in RTC slow memory (.rtc_noinit), which the linker marks
NOLOAD and the startup code does not clear, so it survives the warm reset a
watchdog produces. It is written on every transition
through sm_transition(), which is the only sanctioned way to change state
and therefore the one place the record cannot drift out of sync.
Why not flash¶
Flash would also survive losing power, but the state machine writes on
every transition, and the transitions that matter most (APOGEE, MAIN,
REDUNDANT) are exactly the ones that must not stall. A page erase blocks
for milliseconds with interrupts locked and contends with the flight
recorder already writing that device. The write that matters is the one
issued while the system is already misbehaving, which is the worst possible
moment to start erasing pages.
RTC memory costs a few stores, has no wear and needs no erase. The trade-off is that it does not survive a power cycle or a brownout — see powerfail for that failure, which needs a different mitigation.
When recovery happens¶
All of the following must hold, or the machine starts in IDLE:
the reset cause latched at boot includes
RESET_WATCHDOG,the record carries the expected magic, version and payload size,
the CRC matches,
it was written by the same state-machine backend (
sm_type), so swapping implementations cannot resurrect a state ID that now means something else,the retained state is not
IDLE, andthe RBF interlock reads removed when re-sampled.
A power-up, a reset button or a debugger attach therefore always lands in
IDLE. That is deliberate: the operator needs a reliable way to reach a
known-safe state, and holding reset must not resurrect a live flight.
The interlock has to be re-sampled¶
sm_rbf_init() leaves the vehicle reported safe at boot and waits for an
edge, so that forgetting to install the plug cannot arm the machine. That is
right for a cold boot and wrong here: in flight the plug was pulled long
before the reset, no further edge is coming, and the machine would disarm
itself on its first update — undoing the recovery a few milliseconds after
making it.
The recovery path therefore calls sm_rbf_resync() to adopt the pin’s
present level, but only once it knows the reset came from the watchdog and a
valid in-flight record exists. Under those conditions the vehicle was
demonstrably armed already, which makes the pin the authority rather than a
guess. A plug that is still installed still reads safe, so resetting on the
pad still disarms.
Attitude calibration comes back too¶
Calibration only runs while the machine is in IDLE (see the
sensor board IMU handling), so a board
that resumes into a flight state can never redo it. Losing it is not
cosmetic: vertical acceleration would stay pinned at zero, leaving boost and
apogee detection blind, and orientation would integrate from a zero reference
— reading horizontal, which trips the elevation gate and disarms within
N_OI samples.
The calibration is therefore snapshotted into the same record the moment it completes, while the vehicle is stationary and the result is known good, and handed back on recovery. The biases were measured on the pad and do not change across a reset, and there is no opportunity to measure them again mid-flight.
If the reset landed before calibration ever finished there is nothing to restore. The flight continues, but degraded, and says so in the log.
Flight timers¶
Timers restart from zero. There is no way to know how long the board was
absent, so phase timeouts (TO_A, TO_R) are necessarily generous after a
recovery rather than wrong in the unsafe direction.
Reading the audit log¶
A recovery leaves a recognisable trace:
transition IDLE ARMED
event ARMED recovered after watchdog reset
A refusal leaves recovery refused: interlock safe instead. If you see the
resume followed by an ARMED -> IDLE transition with no event between
them, that is the disarm check in the backend, not the orientation gate —
the orientation path logs orientation below threshold and the log-offline
path logs arm aborted: flight log offline, so an eventless transition
means .armed went false.
Shell Commands¶
Enabling CONFIG_AURORA_STATE_MACHINE_SHELL registers the
state_machine command group. Audit-log commands are only available when
CONFIG_AURORA_STATE_MACHINE_AUDIT is also enabled.
Command |
Description |
|---|---|
|
Print the active state-machine implementation and its current state. |
|
Force a transition. The state name completes via tab. Because the state machine exposes no arbitrary setter, this deinitializes and reinitializes the machine, landing it in |
|
List the running thresholds next to the compiled-in defaults. |
|
Set one threshold, apply it and save the whole set. The name completes via tab and is case-insensitive; out-of-range values are rejected. |
|
Restore the factory (Kconfig) thresholds and save them. |
|
Save the running thresholds as they are. |
|
Dump the audit log (timestamped transitions and events). Requires |
|
Clear the audit log. Requires |
Setting the main deployment altitude to 400 m and keeping it:
uart:~$ state_machine config set T_M 400
T_M = 400 m
Applied and saved
Valid state names for transition are IDLE, ARMED, BOOST,
BURNOUT, APOGEE, MAIN, REDUNDANT, LANDED and ERROR.
Warning
state_machine transition bypasses normal flight logic and resets the
machine. Do not use in flight.
API Reference¶
-
enum sm_type¶
Identifier of the active state machine implementation.
Each implementation defines its own
sm_stateenum, so any external consumer (ground station, post-flight tooling) needs to know which implementation produced a given state value before it can decode it.Values:
-
enumerator SM_TYPE_SIMPLE¶
Simple backend (lib/state/simple.c).
-
enumerator SM_TYPE_SIMPLE¶
-
enum sm_error_reason¶
Reasons the state machine can enter
SM_ERROR.Passed to the error callback so the application can decide per cause whether to recover (return 0 → back to IDLE) or hold the error state (non-zero, e.g. a pre-flight interlock the operator must acknowledge by disarming) and how to signal the operator (LED, buzzer, …).
Values:
-
enumerator SM_ERR_UNKNOWN¶
Unspecified error.
-
enumerator SM_ERR_LOG_OFFLINE¶
Flight recorder unavailable while arming or armed.
-
enumerator SM_ERR_APOGEE_TIMEOUT¶
No descent detected within TO_A.
-
enumerator SM_ERR_REDUNDANT_TIMEOUT¶
No landing detected within TO_R.
-
enumerator SM_ERR_UNKNOWN¶
-
typedef int (*sm_error_cb_t)(enum sm_error_reason reason, void *args)¶
Callback invoked when the state machine encounters an error.
The implementation can define specific recovery logic.
- Param reason:
Why the state machine entered
SM_ERROR.- Param args:
Pointer to an implementation-specific config structure.
- Return:
0 if the error was mitigated (state machine returns to IDLE), negative errno to hold
SM_ERROR(the callback is re-invoked on every update until it mitigates or the system is disarmed).
-
const char *sm_state_str(enum sm_state state)¶
Return a human-readable name for the given state.
- Parameters:
state – State value.
- Returns:
Pointer to a static string, or “UNKNOWN” if invalid.
-
const char *sm_error_reason_str(enum sm_error_reason reason)¶
Return a human-readable name for the given error reason.
- Parameters:
reason – Error reason value.
- Returns:
Pointer to a static string, or “UNKNOWN” if invalid.
-
void sm_init(const struct sm_thresholds *cfg, struct sm_error_handling_args *err_hdl)¶
Initialize the rocket state machine.
This function prepares the state machine, loads the threshold configuration, initializes internal timers, and sets the initial state to
SM_IDLE.- Parameters:
cfg – Pointer to a threshold configuration structure.
err_hdl – Pointer to an error handling configuration (callback + args), or NULL.
-
void sm_deinit(void)¶
Deinitialize the rocket state machine.
This function resets the state machine, unloads the threshold configuration, stops internal timers, and sets the initial state.
-
void sm_update(const struct sm_inputs *inputs)¶
Update the state machine using current sensor readings.
Function evaluates sensor data and executes state transitions according to the flight logic diagram. Must be called regularly (e.g. at sensor update rate).
- Parameters:
inputs – Pointer to populated sensor readings.
-
enum sm_state sm_get_state(void)¶
Retrieve the current state of the state machine.
- Returns:
Current state (usually an enum implementation in state implementation).
-
bool sm_inflight(void)¶
Return if the state machine is in flight.
True from liftoff detection until touchdown. The pre-liftoff states, the landed state and
SM_ERRORall report false.- Returns:
True if it is in flight, false if it’s on the pad.
-
bool sm_on_pad(void)¶
Return whether the vehicle is still sitting on the pad.
True in the pre-liftoff states only (IDLE and, where the backend has one, ARMED). Everything from liftoff onward reports false, and so do the terminal states: after touchdown the flight is over, and
SM_ERRORmay have been entered mid-flight, so neither may be treated as “on the pad”.Note this is deliberately not the complement of sm_inflight(): the landed and error states report false to both.
Consumers use it to decide whether a pad-referenced quantity may still be re-zeroed – the barometric ground reference does exactly that, and must stay frozen from liftoff onward so that altitude keeps meaning height above the pad.
- Returns:
True while the vehicle is on the pad, false otherwise.
-
enum sm_type sm_get_type(void)¶
Identify which state machine implementation is active.
External consumers use it to pick the right
sm_stateenum mapping.- Returns:
Active state machine type ID.
-
void sm_get_inputs(struct sm_inputs *out)¶
Retrieve the most recent inputs the state machine evaluated.
When CONFIG_FILTER is enabled,
altitudeandvelocityare the Kalman-filtered values; the remaining fields are passed through from the last sm_update() call. When CONFIG_FILTER is disabled, all fields are the raw sm_update() inputs (andvelocityis whatever the caller set, typically 0).Before the first sm_update() call the returned struct is zeroed.
- Parameters:
out – Destination struct, must be non-NULL.
-
int sm_get_armed()¶
Get the state machine armed state.
With the RBF arm source (CONFIG_AURORA_STATE_MACHINE_RBF) this returns the mechanical interlock’s debounced state. With tilt arming (CONFIG_AURORA_STATE_MACHINE_ARM_TILT) there is no input-level interlock, so it always reports armed and the orientation gate in the backend is the real arm/disarm control.
- Return values:
0 – if disarmed, 1 if armed.
-
void sm_backend_get_thresholds(struct sm_thresholds *out)¶
Copy the thresholds that the backend is currently running with.
- Parameters:
out – Buffer for the active thresholds.
-
int sm_set_thresholds(const struct sm_thresholds *cfg)¶
Replace the running thresholds.
Refused outside
SM_IDLE:swapping a threshold under a running flight would compare fresh limits against timers already started under the old ones. Persisting the new set is up to the caller (sm_config_save()).- Parameters:
cfg – New thresholds, must be non-NULL.
- Return values:
0 – on success.
-EBUSY – if the machine is not in
SM_IDLE.
-
void sm_update_force(enum sm_state transition_to)¶
Update the state machine with force. No further checks are done.
- Parameters:
transition_to – State to transition.
-
double sm_orientation_elevation_deg(const double orientation[3])¶
Elevation of the configured up axis from horizontal (degrees).
Exposed beyond the backend so the shell can report the arm/disarm angle gate against the same number the transition logic uses.
- Parameters:
orientation – Orientation reading (yaw, pitch, roll) in degrees.
- Returns:
Elevation in degrees, clamped to [-90, 90]. +90 = up axis points to the sky, 0 = horizontal, -90 = inverted.
-
AURORA_STATE_BACKEND_INTERNAL¶
-
struct sm_thresholds¶
- #include <simple.h>
Threshold configuration for the rocket state machine.
Defines thresholds for state transitions based on orientation, altitude, acceleration, and timing.
-
struct sm_inputs¶
- #include <simple.h>
Sensor input structure for the state machine.
These values must be filled each update cycle to evaluate state transitions.
-
struct sm_error_handling_args¶
- #include <state.h>
Error handling configuration for the state machine.
-
void sm_config_defaults(struct sm_thresholds *out)¶
Fill
outwith the compile-time (Kconfig) defaults.- Parameters:
out – Destination, must be non-NULL.
-
int sm_config_load(struct sm_thresholds *out)¶
Load the persisted thresholds, falling back to the defaults.
outis always left with a usable threshold set, so the caller can pass it straight to sm_init and only treat the return value as diagnostics.- Parameters:
out – Destination, must be non-NULL.
- Return values:
0 – A stored record was found and used.
-ENOENT – No (or no valid) record stored, defaults used.
-ENOTSUP – No threshold store configured, defaults used.
-errno – Read failed, defaults used.
-
int sm_config_save(const struct sm_thresholds *cfg)¶
Persist
cfg, replacing whatever was stored before.- Parameters:
cfg – Thresholds to store, must be non-NULL.
- Return values:
0 – on success.
-ENOTSUP – No threshold store configured.
-errno – on erase/write failure.
-
int sm_config_erase(void)¶
Drop the persisted record, so the next boot uses the defaults.
- Return values:
0 – on success, -ENOTSUP without a store, negative errno on failure.
-
const struct sm_config_field *sm_config_fields(size_t *count)¶
Table of editable thresholds.
- Parameters:
count – Written with the number of entries, must be non-NULL.
- Returns:
Pointer to a static table of
countentries.
-
const struct sm_config_field *sm_config_field_find(const char *name)¶
Look up a threshold by name (case-insensitive).
- Parameters:
name – Field name, e.g. “T_M”.
- Returns:
Matching entry, or NULL if
nameis unknown.
-
int sm_config_field_get(const struct sm_thresholds *cfg, const struct sm_config_field *field)¶
Read a threshold through its descriptor.
- Parameters:
cfg – Threshold set to read from.
field – Descriptor from sm_config_fields.
- Returns:
The stored value.
-
int sm_config_field_set(struct sm_thresholds *cfg, const struct sm_config_field *field, int value)¶
Write a threshold through its descriptor, with range checking.
- Parameters:
cfg – Threshold set to modify.
field – Descriptor from sm_config_fields.
value – New value.
- Return values:
0 – on success, -ERANGE if
valueis outside the field’s range.
-
struct sm_config_field¶
- #include <config.h>
One editable threshold, for generic (shell) access.
-
void sm_retain_save(enum sm_state state)¶
Persist
stateso a watchdog reset can resume from it.Called from the state core on every transition. Writing to RTC RAM is a handful of stores, so this is cheap enough for the transition path and involves no flash erase.
- Parameters:
state – State now active.
-
int sm_retain_restore(enum sm_state *out)¶
Recover the flight state latched before a watchdog reset.
Succeeds only when all of the following apply:
the reset cause latched at boot includes
RESET_WATCHDOG,the record carries the expected magic, version and payload size,
the CRC matches,
the record was written by the same state machine backend, and
the retained state is one a flight can actually be interrupted in (not
SM_IDLE, which means nothing was in progress).
- Parameters:
out – Receives the state to resume. Untouched on failure.
- Return values:
0 – on success.
-ENOTSUP – if the reset was not caused by the watchdog.
-ENOENT – if no valid record is present.
-EINVAL – if the record is stale, corrupt or from another backend.
-
void sm_retain_invalidate(void)¶
Discard the retained record.
Called when the machine reaches a state that must not be resumed into, so a later watchdog reset starts clean.
-
uint16_t sm_retain_recovery_count(void)¶
Number of watchdog recoveries since the last power cycle.
Survives in the same record. A climbing count means the board is resetting repeatedly rather than recovering, which is worth surfacing.
- Returns:
Recovery count, or 0 if no valid record exists.
-
bool sm_retain_recovered(void)¶
Whether this boot resumed a flight.
Lets the application restore anything that lives outside the state machine, most importantly the attitude calibration.
- Return values:
true – if sm_retain_restore recovered a state this boot.
-
int sm_retain_save_blob(const void *data, size_t len)¶
Store an opaque payload alongside the retained state.
Used for the attitude calibration. Losing it across a reset is not a cosmetic problem: calibration only runs in
SM_IDLE, so a machine that resumes into a flight state would never recalibrate. Orientation would integrate from a zero reference (reading horizontal, which disarms on the elevation gate) and vertical acceleration would stay pinned at zero, leaving boost and apogee detection blind.- Parameters:
data – Payload to copy in.
len – Payload size, at most
SM_RETAIN_BLOB_SIZE.
- Return values:
0 – on success.
-EINVAL – if
datais NULL orlenexceeds the capacity.
-
int sm_retain_load_blob(void *data, size_t len)¶
Retrieve the payload stored by sm_retain_save_blob.
- Parameters:
data – Destination buffer.
len – Expected size
- Return values:
0 – on success.
-EINVAL – if
datais NULL orlenis not the stored size.-ENOENT – if no valid record or no payload is present.
-
SM_RETAIN_BLOB_SIZE¶
Capacity of the opaque payload carried alongside the state.
Sized for the attitude calibration with room to spare; RTC slow memory is 8 KiB and this whole subsystem uses a fraction of it.