Flight supervisor#
Description#
Flight supervisor contains the state machine of the autopilot. It receives commands from and sends events to the Mission UI, and reacts to events from Guidance, Services and Drone controller. It also monitors the drone battery level to trigger emergency actions (return to home, landing).
Flight supervisor is written in Python
and uses the opensource package
pytransitions.

In addition to the state machine, Flight supervisor relies on a set of manager objects to keep track of different features, indenpendantly of the current state of the state machine.
Managers are split into two categories: the core managers
in the fsup.features
package, which are always present, and
mission-specific managers that are activated/deactivated along with
the currently active mission.
Managers can observe and send messages, and typically expose an API called from state machine methods, making the code in each state simpler.
State machine structure#
The top level of the state machine (level 0 L0) contains the mandatory stages required by Flight supervisor:
Ground states that are performed on the ground. Ex: Idle, Magneto Calibration.
Takeoff various takeoff strategies. Ex: Normal, Hand.
Hovering when the drone is flying and holding fix point (not receiving any piloting command).
Flying all flying states. Ex: Manual, FlightPlan, RTH.
Landing various landing strategies. Ex: Normal, Hand.
Critical when a critical condition is detected. Ex: Emergency Landing, Critical Rth.
Every mission need to have those 6 stages. You can write new ones or reuse states from the default mission.
Note
Switching missions requires changing the state machine in Flight supervisor.
How to modify the state machine#
The mission’s code of the state machine shall respect a directory structure
(See Directory structure). Each mission directory contains at least a
file named mission.py
with a class name Mission
. This class
contains the mission state machine (States and Transitions).
- class Mission#
- on_load()#
- on_unload()#
- on_activate()#
- on_deactivate()#
- can_activate()#
- can_deactivate()#
- states() list #
- transitions() list #
class Mission(object):
def __init__(self, mission_environment):
self.env = mission_environment
def on_load(self):
pass # Objects creation, configuration, etc.
def on_unload(self):
pass # Cleanup
def on_activate(self):
pass # Enable services, MessageCenter channels etc.
def on_deactivate(self):
pass # Cleanup
def can_activate(self, current_state):
return can_activate
def can_deactivate(self, current_state):
return can_deactivate
def states(self):
return _STATES
def transitions(self):
return _TRANSITIONS
On load#
on_load()
is called when the mission is loaded.
It is used to create custom objects required for the mission.
On unload#
on_unload()
is called when the mission is unloaded.
It is used to destroy custom objects required for the mission.
On activate#
on_activate()
is called when the mission is activated.
It is used to create message channels with other components on the
system.
import mymission.service.messages_pb2 as svc
def on_activate(self):
self.svc_channel = self.mc.start_client_channel('unix:/tmp/svc')
self.svc = self.mc.attach_client_service_pair( \
self.svc_channel, svc, True)
On deactivate#
on_deactivate()
is called when the mission is deactivated.
It is used to destroy message channels with other components on the
system.
def on_deactivate(self):
self.mc.detach_client_service_pair(self.svc)
self.svc = None
self.svc_channel = None
Can activate#
can_activate()
is called when a request to activate this
mission is received.
By default, missions can be activated only if current stage is ‘ground’ but
missions can override the behavior if it is safe to activate in other stages
(like hovering for example)
Note
Note: the given state is the current state of the active mission (so not a state of the requested new mission, so only the stage should be used).
def can_activate(self, current_state):
return current_state.get_stage() in ['ground', 'hovering']
Can deactivate#
can_deactivate()
is called when a request to deactivate this
mission is received.
By default, missions can be deactivated only if current stage is ‘ground’ but
missions can override the behavior if it is safe to deactivate in other stages
(like hovering for example)
def can_deactivate(self, current_state):
return current_state.get_stage() == 'ground'
States#
states()
shall return an array of all the states of the state machine.
The top level of the state machine shall contain the six predefined stages as follow:
from .ground.stage import GROUND_STAGE
from .takeoff.stage import TAKEOFF_STAGE
from .hovering.stage import HOVERING_STAGE
from .flying.stage import FLYING_STAGE
from .landing.stage import LANDING_STAGE
from .critical.stage import CRITICAL_STAGE
_STATES = [
GROUND_STAGE,
TAKEOFF_STAGE,
HOVERING_STAGE,
FLYING_STAGE,
LANDING_STAGE,
CRITICAL_STAGE,
]
Each stage is described in its own directory in a file named stage.py
.
To reuse a complete stage of the default mission, simply
import the corresponding stage description and use it in the _STATES variable.
from fsup.missions.default.ground.stage import GROUND_STAGE
The stage is then a description of the level 1 states and possibly level 2 states. Each state is a python dictionary with the following keys:
name: name of the state, in snake case. This name will be used in the table of transitions.
class: optional name of the python class that will contain code managing the state.
children: optional array with a list of nested states.
initial: when children stage is defined. initial refers to the first children state entered when his parent state is entered.
The python class with the code managing the state shall derive the base class
fsup.genstate.State
and can override the following methods:
enter()
: called when the state is entered with the message that triggered the transition.exit()
: called when the state is exited with the message that triggered the transition.step()
: called when a message is received while the state is active.can_enter()
: block transition if this returns False on the target state.can_exit()
: block transition if this returns False on the source state.
For nested states, the above methods are called for each class of the hierarchy, from top to bottom.
For level 1 states, it is also possible to reuse code from an existing one in the default mission by just importing it’s description or classes.
from fsup.missions.default.flying.rth import RTH_STATE as FLYING_RTH_STATE
CRITICAL_STAGE = {
# ...
'children': [
{
'name': 'critical_rth',
# Reuse level 1/2 states from 'flying' RTH
'class': FLYING_RTH_STATE['class'],
'initial': FLYING_RTH_STATE['initial'],
'children': FLYING_RTH_STATE['children'],
},
# ...
]
}
The full name of a state described in this manner will be: <stage>.<state_l0>.<state_l1>.
From the example above, critical.critical_rth.waypoint_path, if we consider the l1 children waypoint_path
Configuration files#
Create configuration files#
The state may have its own configuration which is located at [PRODUCT_ROOT_CFG]/etc/fsup/[STAGE]/[name_of_state].cfg For ex: for the state takeoff/normal, the location of its configuration file will be: /etc/fsup/takeoff/normal.cfg
If the state has children, the configuration of these children will be included in the configuration file of the state. For ex: state takeoff/normal has a children state ascent, the configuration file of this state will be as below:
File: /etc/fsup/takeoff/normal.cfg
ascent: {
param_1: value_1;
param_2: value_2;
...
param_n: value_n;
};
For all custom AirSDK mission which imports states of mission default, there are 2 possible situations: * If the imported state is situated at the first level, it is not necessary to re-create a new configuration file because the configuration of default mission is automatically read * If the imported state is at the second/third/fourth/etc. level, it is necessary to re-create a configuration file and copy all params of this imported state For example: flying/STATE_A state imports hovering/fixed of mission default as its fourth level state
from fsup.missions.default.hovering.fixed import FIXED_STATE
STATE_A = {
"name": "state_a",
"class": StateAClass,
"initial": "state_b",
"children": [
{
"name": "state_b",
"class": StateBClass,
},
{
"name": "state_c",
"class": StateCClass,
"initial": "state_d",
"children": [
{
"name": "state_d",
"class": StateD,
},
# The imported state of mission default
FIXED_STATE,
],
}
]
}
For this case, the configuration file of flying/STATE_A is like:
File [MISSION_PRODUCT_DIR]/etc/fsup/flying/state_a.cfg
state_c: {
fixed: {
param_1: value_1;
param_2: value_2;
...
param_n: value_n;
}
};
Read configuration files#
In abstract mission, all mission specific configuration files are automatically loaded on __init__ Then, in the __init__ method of State, it retrieve only its own configuration from the mission or it can get the drone general configuration.
Get mission configuration with keys: use method self.get_config(keys)
(keys: list of keys, key type “a.b.c” is not supported) * Get drone general configuration with keys: use method self.get_drone_config(keys) (keys: list of keys, key type “a.b.c” is supported)
Transitions#
transitions()
shall return an array of all the possible transitions
in the state machine. Each transition is a triplet with:
Message triggering the transition.
Source state or list of source states including all possible states from which a transition is possible. If the list includes a stage (with no sub-state specified), it will apply to every children of the given stage (ex: ‘flying’ will also include ‘flying.rth’).
Destination state, or
None
if the transition must not occur (to exclude special cases if a more generic transition exists below). If the list includes a stage (with no sub-state specified), the destination state will be determined using the initial children. (ex: ‘flying’ will refer to ‘flying.manual’, manual being the initial state of ‘flying’ state).
When a message is received, the transition will be parsed in the table from top to bottom and the first matching transition found will be executed.
Note
If a transition’s source state can_enter()
or the target can_exit()
method returns None,
the next available transition will be executed.
ANY_STATE = ['ground', 'takeoff', 'hovering', 'flying', 'landing', 'critical']
_TRANSITIONS = [
# ground.* -> critical.emergency_ground
['DroneController.too_much_angle_detected', 'ground', 'critical.emergency_ground'],
# Ignore message if currently in ground.*
['DroneController.too_much_angle_detected', 'critical.emergency_ground', None],
# Fallback transition: * -> critical.emergency_landing
# Even though 'ground' is in ANY_STATE, the transition above has a higher priority.
['DroneController.too_much_angle_detected', ANY_STATE, 'critical.emergency_landing'],
# ...
]
To reuse the transition table from the default mission, it is possible to add it before the default transition table.
from fsup.missions.default.mission import TRANSITIONS as DEFAULT_TRANSITIONS
ANY_STATE = ['ground', 'takeoff', 'hovering', 'flying', 'landing', 'critical']
_TRANSITIONS = [
# This will disable any default transition using this message,
# as it will have a higher priority (at the top of transition table)
['Autopilot.emergency', ANY_STATE, None],
# ...
]
def transitions(self):
return _TRANSITIONS + DEFAULT_TRANSITIONS
Note
A transition from state to itself is possible, it has to be explicitly forbidden if you want to prevent it.
flying.move_to -> flying.move_to can occur, it will call exit()
,
enter()
and step()
.
Note
More precise transitions will be executed first regardless of order.
For instance: [event, hovering.fixed, some_state] [event, hovering.fixed.scan, some_other_state]
The second transition will always be called when the current state is hovering.fixed.scan.
Note
Wildcard (“*”) transitions are not allowed, the behavior is undefined.
Note
For new messages, the message protobuf need to be attached to the
fsup.genstate.message_center.message_center.MessageCenter
in the
state machine.
Managers#
There are core features that are necessary for Flight supervisor to work, and mission-specific (e.g: Default Mission) features that can be defined in missions.
Core features#
Each feature is allocated and stored as a member of a unique instance of fsup.supervisor.Supervisor
.
The table below maps the attribute name in the Supervisor
object, and the associated attribute type.
Attribute name in Supervisor |
Feature manager class name |
---|---|
geofence_manager |
|
obstacle_avoidance_manager |
|
statuses |
|
takeoff_readyness_manager |
|
video_manager |
Directory structure#
The complete directory structure and files of a state machine for a mission is described below. If a stage reuses an existing one from the default mission, its matching directory will not be there.
Sample#
Messages#
Flight supervisor receives Commands from and sends Events to the Mission UI and reacts to events from Guidance, Services and Drone controller.
Settings#
The following settings allow to fine-tune drone behavior. They are persistent when the drone reboots, and can be accessed by any process running on the drone.
All the following settings should be preceded by “autopilot.” when referenced. They can be modified and subscribed to through Shsettings API
Settings |
Type |
Default |
Min |
Max |
Unit |
Description |
---|---|---|---|---|---|---|
active_geofence |
Bool |
False |
Allow / prevent drone from flying further than maximum allowed distance (geofence) |
|||
altitude |
double |
4000 |
0.5 |
4000 |
m |
Maximum altitude the drone is allowed to reach |
angular_rate |
Double |
150 |
40 |
300 |
deg/s |
Maximum drone rotation speed |
banked_turn |
Bool |
False |
Allow banked turn (change inclination when turning) during manual flight |
|||
geofence_distance |
Double |
100 |
10 |
4000 |
m |
Maximum allowed distance from home for geofence |
hull_protection |
Bool |
False |
Whether the drone has an external hull installed |
|||
preferred_home |
string |
“takeoff” |
Preferred home type for RTH |
|||
rotation_speed |
Double |
70 |
3 |
200 |
deg/s |
Maximum rotation speed allowed for manual flight |
rth_end_altitude |
Double |
2 |
1 |
10 |
m |
End altitude for RTH final descent |
rth_land_at_end |
Bool |
False |
Trigger landing at the end of RTH |
|||
rth_land_delay_s |
Int |
0 |
0 |
1800 |
s |
Delay before landing at the end of RTH if rth_land_at_end is True |
rth_min_altitude |
double |
20 |
20 |
100 |
m |
Altitude used for RTH (relative to takeoff) |
tilt |
Double |
20 |
1 |
40 |
deg |
Maximum tilt allowed for manual flight. Correspond to the tilt range of joysticks in flightplan |
vertical_speed |
Double |
1 |
0.1 |
4 |
m/s |
Maximum vertical speed allowed for manual flight |
zoom_limits_rotation_speed |
Bool |
True |
Limit rotation speed relative to zoom level when camera is zoomed |