diff --git a/cflib2/__init__.py b/cflib2/__init__.py
index 5fa176a..dac0343 100644
--- a/cflib2/__init__.py
+++ b/cflib2/__init__.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# ,---------, ____ _ __
# | ,-^-, | / __ )(_) /_______________ _____ ___
# | ( O ) | / __ / / __/ ___/ ___/ __ `/_ / / _ \
diff --git a/cflib2/_rust.pyi b/cflib2/_rust.pyi
index b55d7cf..341b808 100644
--- a/cflib2/_rust.pyi
+++ b/cflib2/_rust.pyi
@@ -411,6 +411,10 @@ class Crazyflie:
r"""
Get the platform subsystem
"""
+ def supervisor(self) -> Supervisor:
+ r"""
+ Get the supervisor subsystem
+ """
def __str__(self) -> builtins.str: ...
def __repr__(self) -> builtins.str: ...
@@ -428,30 +432,6 @@ class DisconnectedError(CrazyflieError):
...
-@typing.final
-class EmergencyControl:
- r"""
- Emergency control interface
-
- Provides emergency stop functionality that immediately stops all motors.
- """
- async def send_emergency_stop(self) -> None:
- r"""
- Send emergency stop command
-
- Immediately stops all motors and puts the Crazyflie into a locked state.
- The drone will require a reboot before it can fly again.
- """
- async def send_emergency_stop_watchdog(self) -> None:
- r"""
- Send emergency stop watchdog
-
- Activates/resets a watchdog failsafe that will automatically emergency stop
- the drone if this message isn't sent every 1000ms. Once activated by the first
- call, you must continue sending this periodically forever or the drone will
- automatically emergency stop. Use only if you need automatic failsafe behavior.
- """
-
@typing.final
class ExternalPose:
r"""
@@ -797,10 +777,7 @@ class LedRingColor:
Intensity percentage (0-100); values above 100 are clamped to 100
"""
@intensity.setter
- def intensity(self, value: builtins.int) -> None:
- r"""
- Intensity percentage (0-100); values above 100 are clamped to 100
- """
+ def intensity(self, value: builtins.int) -> None: ...
def __new__(
cls,
r: builtins.int = 0,
@@ -815,7 +792,7 @@ class LedRingColor:
* `r` - Red component (0-255, default 0)
* `g` - Green component (0-255, default 0)
* `b` - Blue component (0-255, default 0)
- * `intensity` - Intensity percentage (0-100, default 100); clamped to 100 if higher
+ * `intensity` - Intensity percentage (0-100, default 100); values above 100 are clamped to 100
"""
def set(
self,
@@ -970,10 +947,6 @@ class Localization:
r"""
Localization subsystem wrapper
"""
- def emergency(self) -> EmergencyControl:
- r"""
- Get the emergency control interface
- """
def external_pose(self) -> ExternalPose:
r"""
Get the external pose interface
@@ -1480,22 +1453,6 @@ class Platform:
As such, this shall only be used for test purpose in a controlled environment.
"""
- async def send_arming_request(self, do_arm: builtins.bool) -> None:
- r"""
- Send system arm/disarm request
-
- Arms or disarms the Crazyflie's safety systems. When disarmed, the motors
- will not spin even if thrust commands are sent.
-
- # Arguments
- * `do_arm` - true to arm, false to disarm
- """
- async def send_crash_recovery_request(self) -> None:
- r"""
- Send crash recovery request
-
- Requests recovery from a crash state detected by the Crazyflie.
- """
async def get_app_channel(self) -> typing.Optional[AppChannel]:
r"""
Get the bidirectional app channel for custom communication
@@ -1578,6 +1535,143 @@ class ProtocolVersionNotSupportedError(CrazyflieError):
...
+@typing.final
+class Supervisor:
+ r"""
+ Supervisor subsystem
+
+ Monitors the Crazyflie state and exposes arming, crash recovery,
+ and emergency stop controls. Obtain via `crazyflie.supervisor()`.
+ """
+ async def read(self) -> SupervisorState:
+ r"""
+ Read a consistent snapshot of the supervisor state
+
+ All flags on the returned snapshot are decoded from a single bitfield
+ read, so they are from the same moment and mutually consistent. Uses
+ time-based caching to avoid flooding the link.
+
+ The snapshot does not update itself: re-read to get fresh state, for
+ example on every iteration when polling.
+
+ Example:
+ state = await cf.supervisor().read()
+ if state.can_be_armed and not state.is_armed:
+ await cf.supervisor().send_arming_request(True)
+
+ # When polling, read inside the loop:
+ while not (await cf.supervisor().read()).is_armed:
+ await asyncio.sleep(0.5)
+ """
+ async def send_arming_request(self, do_arm: builtins.bool) -> None:
+ r"""
+ Send arm/disarm request
+
+ Arms or disarms the Crazyflie's motors. When disarmed, the motors
+ will not spin even if thrust commands are sent.
+
+ Args:
+ do_arm: True to arm, False to disarm
+ """
+ async def send_crash_recovery_request(self) -> None:
+ r"""
+ Send crash recovery request
+
+ Requests recovery from a crash state detected by the Crazyflie.
+ """
+ async def send_emergency_stop(self) -> None:
+ r"""
+ Send emergency stop
+
+ Immediately stops all motors and puts the Crazyflie into a locked state.
+ The drone will require a reboot before it can fly again.
+ """
+ async def send_emergency_stop_watchdog(self) -> None:
+ r"""
+ Send emergency stop watchdog
+
+ Activates/resets a watchdog failsafe that will automatically emergency
+ stop the drone if this message is not sent every 1000 ms. Once
+ activated, you must keep sending this periodically or the drone will
+ stop. Use only when you need automatic failsafe behaviour on
+ communication loss.
+ """
+
+@typing.final
+class SupervisorState:
+ r"""
+ A snapshot of the supervisor state
+
+ Decoded from a single supervisor bitfield read: all flags on one snapshot
+ are from the same moment and mutually consistent. The snapshot does not
+ update itself - call `Supervisor.read()` again for fresh state.
+ """
+ @property
+ def raw(self) -> builtins.int:
+ r"""
+ Raw bitfield value
+ """
+ @property
+ def can_be_armed(self) -> builtins.bool:
+ r"""
+ The Crazyflie can be armed - will accept an arming command
+ """
+ @property
+ def is_armed(self) -> builtins.bool:
+ r"""
+ The Crazyflie is armed
+ """
+ @property
+ def is_auto_armed(self) -> builtins.bool:
+ r"""
+ The Crazyflie is configured to automatically arm
+ """
+ @property
+ def can_fly(self) -> builtins.bool:
+ r"""
+ The Crazyflie is ready to fly
+ """
+ @property
+ def is_flying(self) -> builtins.bool:
+ r"""
+ The Crazyflie is flying
+ """
+ @property
+ def is_tumbled(self) -> builtins.bool:
+ r"""
+ The Crazyflie is tumbled (upside down)
+ """
+ @property
+ def is_locked(self) -> builtins.bool:
+ r"""
+ The Crazyflie is in the locked state and must be restarted
+ """
+ @property
+ def is_crashed(self) -> builtins.bool:
+ r"""
+ The Crazyflie has crashed
+ """
+ @property
+ def hl_control_active(self) -> builtins.bool:
+ r"""
+ High level commander is actively flying the drone
+ """
+ @property
+ def hl_traj_finished(self) -> builtins.bool:
+ r"""
+ High level commander trajectory has finished
+ """
+ @property
+ def hl_control_disabled(self) -> builtins.bool:
+ r"""
+ High level commander is disabled and not producing setpoints
+ """
+ def active_states(self) -> builtins.list[builtins.str]:
+ r"""
+ Names of all active states in this snapshot
+ """
+ def __repr__(self) -> builtins.str: ...
+
class SystemError(CrazyflieError):
r"""
Async executor error.
diff --git a/cflib2/commander.py b/cflib2/commander.py
index 03d06b2..bf9b636 100644
--- a/cflib2/commander.py
+++ b/cflib2/commander.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# ,---------, ____ _ __
# | ,-^-, | / __ )(_) /_______________ _____ ___
# | ( O ) | / __ / / __/ ___/ ___/ __ `/_ / / _ \
diff --git a/cflib2/console.py b/cflib2/console.py
index 59f7781..f392c41 100644
--- a/cflib2/console.py
+++ b/cflib2/console.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# ,---------, ____ _ __
# | ,-^-, | / __ )(_) /_______________ _____ ___
# | ( O ) | / __ / / __/ ___/ ___/ __ `/_ / / _ \
diff --git a/cflib2/error.py b/cflib2/error.py
index 0706496..ae90760 100644
--- a/cflib2/error.py
+++ b/cflib2/error.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# ,---------, ____ _ __
# | ,-^-, | / __ )(_) /_______________ _____ ___
# | ( O ) | / __ / / __/ ___/ ___/ __ `/_ / / _ \
diff --git a/cflib2/high_level_commander.py b/cflib2/high_level_commander.py
index 25462e7..4a6bff1 100644
--- a/cflib2/high_level_commander.py
+++ b/cflib2/high_level_commander.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# ,---------, ____ _ __
# | ,-^-, | / __ )(_) /_______________ _____ ___
# | ( O ) | / __ / / __/ ___/ ___/ __ `/_ / / _ \
diff --git a/cflib2/localization.py b/cflib2/localization.py
index a8a41d3..2027145 100644
--- a/cflib2/localization.py
+++ b/cflib2/localization.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# ,---------, ____ _ __
# | ,-^-, | / __ )(_) /_______________ _____ ___
# | ( O ) | / __ / / __/ ___/ ___/ __ `/_ / / _ \
@@ -23,7 +21,6 @@
"""Localization subsystem types"""
from cflib2._rust import (
- EmergencyControl,
ExternalPose,
Lighthouse,
LighthouseAngleData,
@@ -33,7 +30,6 @@
)
__all__ = [
- "EmergencyControl",
"ExternalPose",
"Lighthouse",
"LighthouseAngleData",
diff --git a/cflib2/log.py b/cflib2/log.py
index c30100f..9a86491 100644
--- a/cflib2/log.py
+++ b/cflib2/log.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# ,---------, ____ _ __
# | ,-^-, | / __ )(_) /_______________ _____ ___
# | ( O ) | / __ / / __/ ___/ ___/ __ `/_ / / _ \
diff --git a/cflib2/memory.py b/cflib2/memory.py
index df9e175..8d68865 100644
--- a/cflib2/memory.py
+++ b/cflib2/memory.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# ,---------, ____ _ __
# | ,-^-, | / __ )(_) /_______________ _____ ___
# | ( O ) | / __ / / __/ ___/ ___/ __ `/_ / / _ \
diff --git a/cflib2/param.py b/cflib2/param.py
index e7f0874..38b754f 100644
--- a/cflib2/param.py
+++ b/cflib2/param.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# ,---------, ____ _ __
# | ,-^-, | / __ )(_) /_______________ _____ ___
# | ( O ) | / __ / / __/ ___/ ___/ __ `/_ / / _ \
diff --git a/cflib2/platform.py b/cflib2/platform.py
index 6e1fa33..b643278 100644
--- a/cflib2/platform.py
+++ b/cflib2/platform.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# ,---------, ____ _ __
# | ,-^-, | / __ )(_) /_______________ _____ ___
# | ( O ) | / __ / / __/ ___/ ___/ __ `/_ / / _ \
diff --git a/cflib2/supervisor.py b/cflib2/supervisor.py
new file mode 100644
index 0000000..10e385a
--- /dev/null
+++ b/cflib2/supervisor.py
@@ -0,0 +1,25 @@
+# ,---------, ____ _ __
+# | ,-^-, | / __ )(_) /_______________ _____ ___
+# | ( O ) | / __ / / __/ ___/ ___/ __ `/_ / / _ \
+# | / ,--' | / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
+# +------` /_____/_/\__/\___/_/ \__,_/ /___/\___/
+#
+# Copyright (C) 2026 Bitcraze AB
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+"""Supervisor subsystem types"""
+
+from cflib2._rust import Supervisor, SupervisorState
+
+__all__ = ["Supervisor", "SupervisorState"]
diff --git a/cflib2/toc_cache.py b/cflib2/toc_cache.py
index 88b0585..ee6016d 100644
--- a/cflib2/toc_cache.py
+++ b/cflib2/toc_cache.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# ,---------, ____ _ __
# | ,-^-, | / __ )(_) /_______________ _____ ___
# | ( O ) | / __ / / __/ ___/ ___/ __ `/_ / / _ \
diff --git a/examples/arming.py b/examples/arming.py
index 3f83551..a9868c8 100644
--- a/examples/arming.py
+++ b/examples/arming.py
@@ -54,7 +54,7 @@ async def main() -> None:
cf = await Crazyflie.connect_from_uri(context, args.uri)
print("Connected!")
- platform = cf.platform()
+ supervisor = cf.supervisor()
try:
print("\n⚠️ WARNING: This will ARM the Crazyflie!")
@@ -64,7 +64,7 @@ async def main() -> None:
# Arm the Crazyflie
print("\n1. Arming the Crazyflie...")
- await platform.send_arming_request(do_arm=True)
+ await supervisor.send_arming_request(do_arm=True)
print(" ✓ Armed! Motors can now spin.")
# Wait a few seconds
@@ -76,14 +76,14 @@ async def main() -> None:
# Disarm the Crazyflie
print("\n3. Disarming the Crazyflie...")
- await platform.send_arming_request(do_arm=False)
+ await supervisor.send_arming_request(do_arm=False)
print(" ✓ Disarmed! Motors are now disabled.")
print("\n✓ Arming cycle complete!")
except KeyboardInterrupt:
print("\n\n⚠️ Interrupted! Disarming for safety...")
- await platform.send_arming_request(do_arm=False)
+ await supervisor.send_arming_request(do_arm=False)
print(" ✓ Disarmed!")
finally:
diff --git a/examples/emergency_stop.py b/examples/emergency_stop.py
index 4152c3e..5c65249 100644
--- a/examples/emergency_stop.py
+++ b/examples/emergency_stop.py
@@ -55,10 +55,8 @@ async def main() -> None:
cf = await Crazyflie.connect_from_uri(context, args.uri)
print("Connected!")
- platform = cf.platform()
+ supervisor = cf.supervisor()
commander = cf.commander()
- localization = cf.localization()
- emergency = localization.emergency()
try:
print("\n⚠️ WARNING: This will ARM and SPIN the motors!")
@@ -69,7 +67,7 @@ async def main() -> None:
# Arm the Crazyflie
print("\n1. Arming the Crazyflie...")
- await platform.send_arming_request(do_arm=True)
+ await supervisor.send_arming_request(do_arm=True)
await asyncio.sleep(0.3)
print(" ✓ Armed!")
@@ -91,7 +89,7 @@ async def main() -> None:
# Send emergency stop
print("\n4. Sending emergency stop command...")
- await emergency.send_emergency_stop()
+ await supervisor.send_emergency_stop()
await asyncio.sleep(0.5)
print(" ✓ Emergency stop sent!")
@@ -101,7 +99,7 @@ async def main() -> None:
except KeyboardInterrupt:
print("\n\n⚠️ Interrupted! Attempting to disarm for safety...")
try:
- await platform.send_arming_request(do_arm=False)
+ await supervisor.send_arming_request(do_arm=False)
print(" ✓ Disarmed!")
except Exception:
print(" ⚠️ Could not disarm (may already be in emergency state)")
diff --git a/examples/emergency_watchdog.py b/examples/emergency_watchdog.py
index 9b6b13e..ecbd9fa 100644
--- a/examples/emergency_watchdog.py
+++ b/examples/emergency_watchdog.py
@@ -61,10 +61,8 @@ async def main() -> None:
cf = await Crazyflie.connect_from_uri(context, args.uri)
print("Connected!")
- platform = cf.platform()
+ supervisor = cf.supervisor()
commander = cf.commander()
- localization = cf.localization()
- emergency = localization.emergency()
try:
print("\n⚠️ WARNING: This will ARM and SPIN the motors!")
@@ -75,7 +73,7 @@ async def main() -> None:
# Arm the Crazyflie
print("\n1. Arming the Crazyflie...")
- await platform.send_arming_request(do_arm=True)
+ await supervisor.send_arming_request(do_arm=True)
await asyncio.sleep(0.3)
print(" ✓ Armed!")
@@ -96,7 +94,7 @@ async def main() -> None:
# Activate watchdog
print("\n4. Activating watchdog (1000ms timeout)...")
- await emergency.send_emergency_stop_watchdog()
+ await supervisor.send_emergency_stop_watchdog()
print(" ✓ Watchdog activated!")
print(
@@ -111,7 +109,7 @@ async def main() -> None:
sys.stdout.flush()
# Send watchdog message
- await emergency.send_emergency_stop_watchdog()
+ await supervisor.send_emergency_stop_watchdog()
# Keep motors spinning and wait 800ms
for _ in range(8):
@@ -143,7 +141,7 @@ async def main() -> None:
except KeyboardInterrupt:
print("\n\n⚠️ Interrupted! Attempting to disarm for safety...")
try:
- await platform.send_arming_request(do_arm=False)
+ await supervisor.send_arming_request(do_arm=False)
print(" ✓ Disarmed!")
except Exception:
print(" ⚠️ Could not disarm (may already be in emergency state)")
diff --git a/examples/reading_supervisor.py b/examples/reading_supervisor.py
new file mode 100644
index 0000000..4c5354a
--- /dev/null
+++ b/examples/reading_supervisor.py
@@ -0,0 +1,73 @@
+# ,---------, ____ _ __
+# | ,-^-, | / __ )(_) /_______________ _____ ___
+# | ( O ) | / __ / / __/ ___/ ___/ __ `/_ / / _ \
+# | / ,--' | / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
+# +------` /_____/_/\__/\___/_/ \__,_/ /___/\___/
+#
+# Copyright (C) 2026 Bitcraze AB
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+"""
+Reads the Crazyflie's supervisor state 20 times at 0.5 s intervals.
+
+Hold the Crazyflie in your hand and tilt it upside down to observe state
+changes. Once the tilt exceeds ~90°, can_fly becomes False and is_tumbled
+becomes True.
+
+Change the URI variable to your Crazyflie configuration.
+"""
+
+import asyncio
+from dataclasses import dataclass
+
+import tyro
+
+from cflib2 import Crazyflie, LinkContext
+
+
+@dataclass
+class Args:
+ uri: str = "radio://0/80/2M/E7E7E7E7E7"
+ """Crazyflie URI"""
+
+
+async def main() -> None:
+ args = tyro.cli(Args)
+
+ print(f"Connecting to {args.uri}...")
+ ctx = LinkContext()
+ cf = await Crazyflie.connect_from_uri(ctx, args.uri)
+ print("Connected!")
+
+ try:
+ supervisor = cf.supervisor()
+ print("Reading supervisor state:")
+ for _ in range(20):
+ print("=" * 78)
+ # One read gives a consistent snapshot of all state flags
+ state = await supervisor.read()
+ print(f"Can fly: {state.can_fly}")
+ print(f"Is tumbled: {state.is_tumbled}")
+ print(f"Bitfield: 0x{state.raw:04x}")
+ print(f"Active states: {state.active_states()}")
+ print("=" * 78)
+ await asyncio.sleep(0.5)
+ finally:
+ print("Disconnecting...")
+ await cf.disconnect()
+ print("Done!")
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/examples/trajectory.py b/examples/trajectory.py
index 5c4b18d..ee7a29a 100644
--- a/examples/trajectory.py
+++ b/examples/trajectory.py
@@ -454,7 +454,7 @@ async def main() -> None:
# Arm the Crazyflie
print("Arming...")
- await cf.platform().send_arming_request(True)
+ await cf.supervisor().send_arming_request(True)
await asyncio.sleep(1.0)
takeoff_yaw = 3.14 / 2 if args.relative_yaw else 0.0
diff --git a/rust/src/crazyflie.rs b/rust/src/crazyflie.rs
index 1b2b4f9..67cddd3 100644
--- a/rust/src/crazyflie.rs
+++ b/rust/src/crazyflie.rs
@@ -28,7 +28,7 @@ use std::sync::Arc;
use crate::error::to_pyerr;
use crate::link_context::LinkContext;
-use crate::subsystems::{Commander, Console, HighLevelCommander, Localization, Memory, Param, Platform, Log};
+use crate::subsystems::{Commander, Console, HighLevelCommander, Localization, Memory, Param, Platform, Log, Supervisor};
use crate::toc_cache::{NoTocCache, InMemoryTocCache, FileTocCache};
@@ -245,6 +245,13 @@ impl Crazyflie {
}
}
+ /// Get the supervisor subsystem
+ fn supervisor(&self) -> Supervisor {
+ Supervisor {
+ cf: self.inner.clone(),
+ }
+ }
+
/// The URI used to connect to this Crazyflie
#[getter]
fn uri(&self) -> &str {
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index 3a23fa8..6c152f2 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -36,9 +36,10 @@ use crazyflie::Crazyflie;
use link_context::LinkContext;
use subsystems::{
Commander, Console, Log, LogBlock, LogData, LogStream, Param, PersistentParamState, Platform, AppChannel,
- Localization, EmergencyControl, ExternalPose, Lighthouse, LocoPositioning,
+ Localization, ExternalPose, Lighthouse, LocoPositioning,
LighthouseAngleData, LighthouseAngles,
Memory, Poly, Poly4D, CompressedStart, CompressedSegment, LedRingColor,
+ Supervisor, SupervisorState,
};
use toc_cache::{NoTocCache, InMemoryTocCache, FileTocCache};
@@ -58,7 +59,6 @@ fn _rust(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::()?;
m.add_class::()?;
m.add_class::()?;
- m.add_class::()?;
m.add_class::()?;
m.add_class::()?;
m.add_class::()?;
@@ -70,6 +70,8 @@ fn _rust(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::()?;
m.add_class::()?;
m.add_class::()?;
+ m.add_class::()?;
+ m.add_class::()?;
m.add_class::()?;
m.add_class::()?;
m.add_class::()?;
diff --git a/rust/src/subsystems/localization.rs b/rust/src/subsystems/localization.rs
index 2be68ac..717e4ed 100644
--- a/rust/src/subsystems/localization.rs
+++ b/rust/src/subsystems/localization.rs
@@ -19,7 +19,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-//! Localization subsystem - emergency stop, external pose, lighthouse, and loco positioning
+//! Localization subsystem - external pose, lighthouse, and loco positioning
use pyo3::prelude::*;
use pyo3_stub_gen::derive::*;
@@ -39,13 +39,6 @@ pub struct Localization {
#[gen_stub_pymethods]
#[pymethods]
impl Localization {
- /// Get the emergency control interface
- fn emergency(&self) -> EmergencyControl {
- EmergencyControl {
- cf: self.cf.clone(),
- }
- }
-
/// Get the external pose interface
fn external_pose(&self) -> ExternalPose {
ExternalPose {
@@ -66,49 +59,6 @@ impl Localization {
}
}
-/// Emergency control interface
-///
-/// Provides emergency stop functionality that immediately stops all motors.
-#[gen_stub_pyclass]
-#[pyclass]
-pub struct EmergencyControl {
- cf: Arc,
-}
-
-#[gen_stub_pymethods]
-#[pymethods]
-impl EmergencyControl {
- /// Send emergency stop command
- ///
- /// Immediately stops all motors and puts the Crazyflie into a locked state.
- /// The drone will require a reboot before it can fly again.
- #[gen_stub(override_return_type(type_repr = "collections.abc.Coroutine[typing.Any, typing.Any, None]"))]
- fn send_emergency_stop<'py>(&self, py: Python<'py>) -> PyResult> {
- let cf = self.cf.clone();
- pyo3_async_runtimes::tokio::future_into_py(py, async move {
- cf.supervisor.send_emergency_stop().await
- .map_err(crate::error::to_pyerr)?;
- Ok(())
- })
- }
-
- /// Send emergency stop watchdog
- ///
- /// Activates/resets a watchdog failsafe that will automatically emergency stop
- /// the drone if this message isn't sent every 1000ms. Once activated by the first
- /// call, you must continue sending this periodically forever or the drone will
- /// automatically emergency stop. Use only if you need automatic failsafe behavior.
- #[gen_stub(override_return_type(type_repr = "collections.abc.Coroutine[typing.Any, typing.Any, None]"))]
- fn send_emergency_stop_watchdog<'py>(&self, py: Python<'py>) -> PyResult> {
- let cf = self.cf.clone();
- pyo3_async_runtimes::tokio::future_into_py(py, async move {
- cf.supervisor.send_emergency_stop_watchdog().await
- .map_err(crate::error::to_pyerr)?;
- Ok(())
- })
- }
-}
-
/// External pose interface
///
/// Provides functionality to send external position and pose data from motion
diff --git a/rust/src/subsystems/mod.rs b/rust/src/subsystems/mod.rs
index 4f2a722..d0579b0 100644
--- a/rust/src/subsystems/mod.rs
+++ b/rust/src/subsystems/mod.rs
@@ -29,12 +29,14 @@ mod log;
pub mod memory;
mod param;
mod platform;
+mod supervisor;
pub use commander::Commander;
pub use console::Console;
pub use high_level_commander::HighLevelCommander;
-pub use localization::{Localization, EmergencyControl, ExternalPose, Lighthouse, LocoPositioning, LighthouseAngleData, LighthouseAngles};
+pub use localization::{Localization, ExternalPose, Lighthouse, LocoPositioning, LighthouseAngleData, LighthouseAngles};
pub use log::{Log, LogBlock, LogData, LogStream};
pub use memory::{Memory, Poly, Poly4D, CompressedStart, CompressedSegment, LedRingColor};
pub use param::{Param, PersistentParamState};
pub use platform::{Platform, AppChannel};
+pub use supervisor::{Supervisor, SupervisorState};
diff --git a/rust/src/subsystems/platform.rs b/rust/src/subsystems/platform.rs
index 3cdc80b..f70f52b 100644
--- a/rust/src/subsystems/platform.rs
+++ b/rust/src/subsystems/platform.rs
@@ -102,34 +102,6 @@ impl Platform {
})
}
- /// Send system arm/disarm request
- ///
- /// Arms or disarms the Crazyflie's safety systems. When disarmed, the motors
- /// will not spin even if thrust commands are sent.
- ///
- /// # Arguments
- /// * `do_arm` - true to arm, false to disarm
- #[gen_stub(override_return_type(type_repr = "collections.abc.Coroutine[typing.Any, typing.Any, None]"))]
- fn send_arming_request<'py>(&self, py: Python<'py>, do_arm: bool) -> PyResult> {
- let cf = self.cf.clone();
- pyo3_async_runtimes::tokio::future_into_py(py, async move {
- cf.supervisor.send_arming_request(do_arm).await.map_err(to_pyerr)?;
- Ok(())
- })
- }
-
- /// Send crash recovery request
- ///
- /// Requests recovery from a crash state detected by the Crazyflie.
- #[gen_stub(override_return_type(type_repr = "collections.abc.Coroutine[typing.Any, typing.Any, None]"))]
- fn send_crash_recovery_request<'py>(&self, py: Python<'py>) -> PyResult> {
- let cf = self.cf.clone();
- pyo3_async_runtimes::tokio::future_into_py(py, async move {
- cf.supervisor.send_crash_recovery_request().await.map_err(to_pyerr)?;
- Ok(())
- })
- }
-
/// Get the bidirectional app channel for custom communication
///
/// The app channel allows bidirectional communication between ground software
diff --git a/rust/src/subsystems/supervisor.rs b/rust/src/subsystems/supervisor.rs
new file mode 100644
index 0000000..79ef36f
--- /dev/null
+++ b/rust/src/subsystems/supervisor.rs
@@ -0,0 +1,229 @@
+// ,---------, ____ _ __
+// | ,-^-, | / __ )(_) /_______________ _____ ___
+// | ( O ) | / __ / / __/ ___/ ___/ __ `/_ / / _ \
+// | / ,--' | / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
+// +------` /_____/_/\__/\___/_/ \__,_/ /___/\___/
+//
+// Copyright (C) 2026 Bitcraze AB
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+//! Supervisor subsystem - Crazyflie state, arming, crash recovery, and emergency stop
+
+use pyo3::prelude::*;
+use pyo3_stub_gen::derive::*;
+use std::sync::Arc;
+
+use crate::error::to_pyerr;
+
+/// Supervisor subsystem
+///
+/// Monitors the Crazyflie state and exposes arming, crash recovery,
+/// and emergency stop controls. Obtain via `crazyflie.supervisor()`.
+#[gen_stub_pyclass]
+#[pyclass]
+pub struct Supervisor {
+ pub(crate) cf: Arc,
+}
+
+#[gen_stub_pymethods]
+#[pymethods]
+impl Supervisor {
+ /// Read a consistent snapshot of the supervisor state
+ ///
+ /// All flags on the returned snapshot are decoded from a single bitfield
+ /// read, so they are from the same moment and mutually consistent. Uses
+ /// time-based caching to avoid flooding the link.
+ ///
+ /// The snapshot does not update itself: re-read to get fresh state, for
+ /// example on every iteration when polling.
+ ///
+ /// Example:
+ /// state = await cf.supervisor().read()
+ /// if state.can_be_armed and not state.is_armed:
+ /// await cf.supervisor().send_arming_request(True)
+ ///
+ /// # When polling, read inside the loop:
+ /// while not (await cf.supervisor().read()).is_armed:
+ /// await asyncio.sleep(0.5)
+ #[gen_stub(override_return_type(type_repr = "collections.abc.Coroutine[typing.Any, typing.Any, SupervisorState]"))]
+ fn read<'py>(&self, py: Python<'py>) -> PyResult> {
+ let cf = self.cf.clone();
+ pyo3_async_runtimes::tokio::future_into_py(py, async move {
+ let info = cf.supervisor.read_bitfield().await.map_err(to_pyerr)?;
+ Ok(SupervisorState { info })
+ })
+ }
+
+ /// Send arm/disarm request
+ ///
+ /// Arms or disarms the Crazyflie's motors. When disarmed, the motors
+ /// will not spin even if thrust commands are sent.
+ ///
+ /// Args:
+ /// do_arm: True to arm, False to disarm
+ #[gen_stub(override_return_type(type_repr = "collections.abc.Coroutine[typing.Any, typing.Any, None]"))]
+ fn send_arming_request<'py>(&self, py: Python<'py>, do_arm: bool) -> PyResult> {
+ let cf = self.cf.clone();
+ pyo3_async_runtimes::tokio::future_into_py(py, async move {
+ cf.supervisor.send_arming_request(do_arm).await.map_err(to_pyerr)?;
+ Ok(())
+ })
+ }
+
+ /// Send crash recovery request
+ ///
+ /// Requests recovery from a crash state detected by the Crazyflie.
+ #[gen_stub(override_return_type(type_repr = "collections.abc.Coroutine[typing.Any, typing.Any, None]"))]
+ fn send_crash_recovery_request<'py>(&self, py: Python<'py>) -> PyResult> {
+ let cf = self.cf.clone();
+ pyo3_async_runtimes::tokio::future_into_py(py, async move {
+ cf.supervisor.send_crash_recovery_request().await.map_err(to_pyerr)?;
+ Ok(())
+ })
+ }
+
+ /// Send emergency stop
+ ///
+ /// Immediately stops all motors and puts the Crazyflie into a locked state.
+ /// The drone will require a reboot before it can fly again.
+ #[gen_stub(override_return_type(type_repr = "collections.abc.Coroutine[typing.Any, typing.Any, None]"))]
+ fn send_emergency_stop<'py>(&self, py: Python<'py>) -> PyResult> {
+ let cf = self.cf.clone();
+ pyo3_async_runtimes::tokio::future_into_py(py, async move {
+ cf.supervisor.send_emergency_stop().await.map_err(to_pyerr)?;
+ Ok(())
+ })
+ }
+
+ /// Send emergency stop watchdog
+ ///
+ /// Activates/resets a watchdog failsafe that will automatically emergency
+ /// stop the drone if this message is not sent every 1000 ms. Once
+ /// activated, you must keep sending this periodically or the drone will
+ /// stop. Use only when you need automatic failsafe behaviour on
+ /// communication loss.
+ #[gen_stub(override_return_type(type_repr = "collections.abc.Coroutine[typing.Any, typing.Any, None]"))]
+ fn send_emergency_stop_watchdog<'py>(&self, py: Python<'py>) -> PyResult> {
+ let cf = self.cf.clone();
+ pyo3_async_runtimes::tokio::future_into_py(py, async move {
+ cf.supervisor.send_emergency_stop_watchdog().await.map_err(to_pyerr)?;
+ Ok(())
+ })
+ }
+}
+
+/// A snapshot of the supervisor state
+///
+/// Decoded from a single supervisor bitfield read: all flags on one snapshot
+/// are from the same moment and mutually consistent. The snapshot does not
+/// update itself - call `Supervisor.read()` again for fresh state.
+#[gen_stub_pyclass]
+#[pyclass(frozen)]
+pub struct SupervisorState {
+ info: crazyflie_lib::subsystems::supervisor::SupervisorInfo,
+}
+
+#[gen_stub_pymethods]
+#[pymethods]
+impl SupervisorState {
+ /// Raw bitfield value
+ #[getter]
+ fn raw(&self) -> u16 {
+ self.info.raw
+ }
+
+ /// The Crazyflie can be armed - will accept an arming command
+ #[getter]
+ fn can_be_armed(&self) -> bool {
+ self.info.can_be_armed()
+ }
+
+ /// The Crazyflie is armed
+ #[getter]
+ fn is_armed(&self) -> bool {
+ self.info.is_armed()
+ }
+
+ /// The Crazyflie is configured to automatically arm
+ #[getter]
+ fn is_auto_armed(&self) -> bool {
+ self.info.is_auto_armed()
+ }
+
+ /// The Crazyflie is ready to fly
+ #[getter]
+ fn can_fly(&self) -> bool {
+ self.info.can_fly()
+ }
+
+ /// The Crazyflie is flying
+ #[getter]
+ fn is_flying(&self) -> bool {
+ self.info.is_flying()
+ }
+
+ /// The Crazyflie is tumbled (upside down)
+ #[getter]
+ fn is_tumbled(&self) -> bool {
+ self.info.is_tumbled()
+ }
+
+ /// The Crazyflie is in the locked state and must be restarted
+ #[getter]
+ fn is_locked(&self) -> bool {
+ self.info.is_locked()
+ }
+
+ /// The Crazyflie has crashed
+ #[getter]
+ fn is_crashed(&self) -> bool {
+ self.info.is_crashed()
+ }
+
+ /// High level commander is actively flying the drone
+ #[getter]
+ fn hl_control_active(&self) -> bool {
+ self.info.hl_control_active()
+ }
+
+ /// High level commander trajectory has finished
+ #[getter]
+ fn hl_traj_finished(&self) -> bool {
+ self.info.hl_traj_finished()
+ }
+
+ /// High level commander is disabled and not producing setpoints
+ #[getter]
+ fn hl_control_disabled(&self) -> bool {
+ self.info.hl_control_disabled()
+ }
+
+ /// Names of all active states in this snapshot
+ fn active_states(&self) -> Vec {
+ self.info
+ .active_states()
+ .into_iter()
+ .map(|s| s.to_string())
+ .collect()
+ }
+
+ fn __repr__(&self) -> String {
+ format!(
+ "SupervisorState(raw=0x{:04x}, active=[{}])",
+ self.info.raw,
+ self.active_states().join(", ")
+ )
+ }
+}