diff --git a/cflib2/_rust.pyi b/cflib2/_rust.pyi
index a7ad243..76a64a0 100644
--- a/cflib2/_rust.pyi
+++ b/cflib2/_rust.pyi
@@ -393,6 +393,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: ...
@@ -423,6 +427,8 @@ class EmergencyControl:
Immediately stops all motors and puts the Crazyflie into a locked state.
The drone will require a reboot before it can fly again.
+
+ Deprecated: Use `crazyflie.supervisor().send_emergency_stop()` instead.
"""
async def send_emergency_stop_watchdog(self) -> None:
r"""
@@ -432,6 +438,8 @@ class EmergencyControl:
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.
+
+ Deprecated: Use `crazyflie.supervisor().send_emergency_stop_watchdog()` instead.
"""
@typing.final
@@ -1469,6 +1477,8 @@ class Platform:
Arms or disarms the Crazyflie's safety systems. When disarmed, the motors
will not spin even if thrust commands are sent.
+ Deprecated: Use `crazyflie.supervisor().send_arming_request()` instead.
+
# Arguments
* `do_arm` - true to arm, false to disarm
"""
@@ -1477,6 +1487,8 @@ class Platform:
Send crash recovery request
Requests recovery from a crash state detected by the Crazyflie.
+
+ Deprecated: Use `crazyflie.supervisor().send_crash_recovery_request()` instead.
"""
async def get_app_channel(self) -> typing.Optional[AppChannel]:
r"""
@@ -1560,6 +1572,104 @@ class ProtocolVersionNotSupportedError(CrazyflieError):
...
+@typing.final
+class Supervisor:
+ r"""
+ Supervisor subsystem
+
+ Monitors the Crazyflie's system state and exposes arming, crash recovery,
+ and emergency stop controls. Obtain via `crazyflie.supervisor()`.
+ """
+ async def read_bitfield(self) -> builtins.int:
+ r"""
+ Read the raw supervisor state bitfield
+
+ Returns the raw bitfield as an integer. Uses time-based caching
+ (100 ms) to avoid flooding the link.
+ """
+ async def active_states(self) -> builtins.list[builtins.str]:
+ r"""
+ Names of all currently active states
+ """
+ async def send_arming_request(self, do_arm: builtins.bool) -> None:
+ r"""
+ Send system 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 crashed state. The firmware may allow
+ recovery without a full reboot depending on the crash type.
+ """
+ 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.
+ """
+ async def can_be_armed(self) -> builtins.bool:
+ r"""
+ System can be armed - will accept an arming command
+ """
+ async def is_armed(self) -> builtins.bool:
+ r"""
+ System is armed
+ """
+ async def is_auto_armed(self) -> builtins.bool:
+ r"""
+ System is configured to automatically arm
+ """
+ async def can_fly(self) -> builtins.bool:
+ r"""
+ The Crazyflie is ready to fly
+ """
+ async def is_flying(self) -> builtins.bool:
+ r"""
+ The Crazyflie is flying
+ """
+ async def is_tumbled(self) -> builtins.bool:
+ r"""
+ The Crazyflie is tumbled (upside down)
+ """
+ async def is_locked(self) -> builtins.bool:
+ r"""
+ The Crazyflie is in the locked state and must be restarted
+ """
+ async def is_crashed(self) -> builtins.bool:
+ r"""
+ The Crazyflie has crashed
+ """
+ async def hl_control_active(self) -> builtins.bool:
+ r"""
+ High level commander is actively flying the drone
+ """
+ async def hl_traj_finished(self) -> builtins.bool:
+ r"""
+ High level commander trajectory has finished
+ """
+ async def hl_control_disabled(self) -> builtins.bool:
+ r"""
+ High level commander is disabled and not producing setpoints
+ """
+
class SystemError(CrazyflieError):
r"""
Async executor error.
diff --git a/cflib2/supervisor.py b/cflib2/supervisor.py
new file mode 100644
index 0000000..6ca9cb9
--- /dev/null
+++ b/cflib2/supervisor.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+#
+# ,---------, ____ _ __
+# | ,-^-, | / __ )(_) /_______________ _____ ___
+# | ( O ) | / __ / / __/ ___/ ___/ __ `/_ / / _ \
+# | / ,--' | / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
+# +------` /_____/_/\__/\___/_/ \__,_/ /___/\___/
+#
+# Copyright (C) 2025 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
+
+__all__ = ["Supervisor"]
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/flying_with_supervisor.py b/examples/flying_with_supervisor.py
new file mode 100644
index 0000000..6778a21
--- /dev/null
+++ b/examples/flying_with_supervisor.py
@@ -0,0 +1,108 @@
+# ,---------, ____ _ __
+# | ,-^-, | / __ )(_) /_______________ _____ ___
+# | ( 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 .
+"""
+Simple example showing how to fly the Crazyflie using supervisor state.
+
+Based on the current state, the Crazyflie will arm (if it can be armed),
+take off (if it can fly), and land (if it is flying). A safety check is
+performed before each action.
+
+Tested with the Flow deck V2 and the Lighthouse positioning system.
+
+Change the URI variable to your Crazyflie configuration.
+"""
+
+import asyncio
+from dataclasses import dataclass
+
+import tyro
+
+from cflib2 import Crazyflie, LinkContext
+from cflib2.supervisor import Supervisor
+
+
+@dataclass
+class Args:
+ uri: str = "radio://0/80/2M/E7E7E7E7E7"
+ """Crazyflie URI"""
+
+
+async def safety_check(supervisor: Supervisor) -> None:
+ if await supervisor.is_crashed():
+ raise RuntimeError("Crazyflie crashed!")
+ if await supervisor.is_locked():
+ raise RuntimeError("Crazyflie locked!")
+ if await supervisor.is_tumbled():
+ raise RuntimeError("Crazyflie tumbled!")
+
+
+async def run_sequence(cf: Crazyflie) -> None:
+ supervisor = cf.supervisor()
+ hlc = cf.high_level_commander()
+
+ await safety_check(supervisor)
+
+ if await supervisor.can_be_armed():
+ print("The Crazyflie can be armed...arming!")
+ await safety_check(supervisor)
+ await supervisor.send_arming_request(True)
+ await asyncio.sleep(1)
+
+ await safety_check(supervisor)
+
+ if await supervisor.can_fly():
+ print("The Crazyflie can fly...taking off!")
+ await hlc.take_off(1.0, None, 2.0, None)
+ await asyncio.sleep(3)
+
+ await safety_check(supervisor)
+
+ if await supervisor.is_flying():
+ print("The Crazyflie is flying...landing!")
+ await hlc.land(0.0, None, 2.0, None)
+ await asyncio.sleep(3)
+
+ await safety_check(supervisor)
+
+
+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!")
+
+ await asyncio.sleep(0.5)
+
+ try:
+ await run_sequence(cf)
+ print("Sequence completed successfully!")
+ except RuntimeError as e:
+ print(f"Safety check failed: {e}")
+ finally:
+ print("Disconnecting...")
+ await cf.disconnect()
+ print("Done!")
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/examples/reading_supervisor.py b/examples/reading_supervisor.py
new file mode 100644
index 0000000..0ed035f
--- /dev/null
+++ b/examples/reading_supervisor.py
@@ -0,0 +1,77 @@
+# ,---------, ____ _ __
+# | ,-^-, | / __ )(_) /_______________ _____ ___
+# | ( 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)
+ # Gather all state data at once to minimize redundant calls
+ bitfield = await supervisor.read_bitfield()
+ can_fly = await supervisor.can_fly()
+ is_tumbled = await supervisor.is_tumbled()
+ active_states = await supervisor.active_states()
+ # Print the gathered data
+ print(f"Can fly: {can_fly}")
+ print(f"Is tumbled: {is_tumbled}")
+ print(f"Bitfield: 0x{bitfield:04x}")
+ print(f"Active states: {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..b80c283 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -39,6 +39,7 @@ use subsystems::{
Localization, EmergencyControl, ExternalPose, Lighthouse, LocoPositioning,
LighthouseAngleData, LighthouseAngles,
Memory, Poly, Poly4D, CompressedStart, CompressedSegment, LedRingColor,
+ Supervisor,
};
use toc_cache::{NoTocCache, InMemoryTocCache, FileTocCache};
@@ -70,6 +71,7 @@ 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::()?;
diff --git a/rust/src/subsystems/localization.rs b/rust/src/subsystems/localization.rs
index 497001e..cf7183a 100644
--- a/rust/src/subsystems/localization.rs
+++ b/rust/src/subsystems/localization.rs
@@ -22,6 +22,7 @@
//! Localization subsystem - emergency stop, external pose, lighthouse, and loco positioning
use pyo3::prelude::*;
+use pyo3::PyTypeInfo;
use pyo3_stub_gen::derive::*;
use std::sync::Arc;
use futures::stream::Stream;
@@ -82,11 +83,15 @@ impl EmergencyControl {
///
/// Immediately stops all motors and puts the Crazyflie into a locked state.
/// The drone will require a reboot before it can fly again.
+ ///
+ /// Deprecated: use `crazyflie.supervisor().send_emergency_stop()` instead.
#[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> {
+ // Issue deprecation warning (using UserWarning for visibility)
+ PyErr::warn(py, &pyo3::exceptions::PyUserWarning::type_object(py), c"localization.emergency().send_emergency_stop() is deprecated. Use supervisor.send_emergency_stop() instead.", 2)?;
let cf = self.cf.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
- cf.localization.emergency.send_emergency_stop().await
+ cf.supervisor.send_emergency_stop().await
.map_err(crate::error::to_pyerr)?;
Ok(())
})
@@ -98,11 +103,15 @@ impl EmergencyControl {
/// 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.
+ ///
+ /// Deprecated: use `crazyflie.supervisor().send_emergency_stop_watchdog()` instead.
#[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> {
+ // Issue deprecation warning (using UserWarning for visibility)
+ PyErr::warn(py, &pyo3::exceptions::PyUserWarning::type_object(py), c"localization.emergency().send_emergency_stop_watchdog() is deprecated. Use supervisor.send_emergency_stop_watchdog() instead.", 2)?;
let cf = self.cf.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
- cf.localization.emergency.send_emergency_stop_watchdog().await
+ cf.supervisor.send_emergency_stop_watchdog().await
.map_err(crate::error::to_pyerr)?;
Ok(())
})
diff --git a/rust/src/subsystems/mod.rs b/rust/src/subsystems/mod.rs
index 4f2a722..4e6ae2b 100644
--- a/rust/src/subsystems/mod.rs
+++ b/rust/src/subsystems/mod.rs
@@ -29,6 +29,7 @@ mod log;
pub mod memory;
mod param;
mod platform;
+mod supervisor;
pub use commander::Commander;
pub use console::Console;
@@ -38,3 +39,4 @@ 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;
diff --git a/rust/src/subsystems/platform.rs b/rust/src/subsystems/platform.rs
index ff14c85..d58af84 100644
--- a/rust/src/subsystems/platform.rs
+++ b/rust/src/subsystems/platform.rs
@@ -23,6 +23,7 @@
use pyo3::prelude::*;
use pyo3::exceptions::PyValueError;
+use pyo3::PyTypeInfo;
use pyo3_stub_gen_derive::*;
use std::sync::Arc;
use tokio::sync::Mutex;
@@ -107,13 +108,17 @@ impl Platform {
/// Arms or disarms the Crazyflie's safety systems. When disarmed, the motors
/// will not spin even if thrust commands are sent.
///
+ /// Deprecated: use `crazyflie.supervisor().send_arming_request()` instead.
+ ///
/// # 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> {
+ // Issue deprecation warning (using UserWarning for visibility)
+ PyErr::warn(py, &pyo3::exceptions::PyUserWarning::type_object(py), c"platform.send_arming_request() is deprecated. Use supervisor.send_arming_request() instead.", 2)?;
let cf = self.cf.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
- cf.platform.send_arming_request(do_arm).await.map_err(to_pyerr)?;
+ cf.supervisor.send_arming_request(do_arm).await.map_err(to_pyerr)?;
Ok(())
})
}
@@ -121,11 +126,15 @@ impl Platform {
/// Send crash recovery request
///
/// Requests recovery from a crash state detected by the Crazyflie.
+ ///
+ /// Deprecated: use `crazyflie.supervisor().send_crash_recovery_request()` instead.
#[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> {
+ // Issue deprecation warning (using UserWarning for visibility)
+ PyErr::warn(py, &pyo3::exceptions::PyUserWarning::type_object(py), c"platform.send_crash_recovery_request() is deprecated. Use supervisor.send_crash_recovery_request() instead.", 2)?;
let cf = self.cf.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
- cf.platform.send_crash_recovery_request().await.map_err(to_pyerr)?;
+ cf.supervisor.send_crash_recovery_request().await.map_err(to_pyerr)?;
Ok(())
})
}
diff --git a/rust/src/subsystems/supervisor.rs b/rust/src/subsystems/supervisor.rs
new file mode 100644
index 0000000..cda0de3
--- /dev/null
+++ b/rust/src/subsystems/supervisor.rs
@@ -0,0 +1,226 @@
+// ,---------, ____ _ __
+// | ,-^-, | / __ )(_) /_______________ _____ ___
+// | ( O ) | / __ / / __/ ___/ ___/ __ `/_ / / _ \
+// | / ,--' | / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
+// +------` /_____/_/\__/\___/_/ \__,_/ /___/\___/
+//
+// Copyright (C) 2025 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 - system 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's system 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 the raw supervisor state bitfield
+ ///
+ /// Returns the raw bitfield as an integer. Uses time-based caching (100 ms)
+ /// to avoid flooding the link.
+ #[gen_stub(override_return_type(type_repr = "collections.abc.Coroutine[typing.Any, typing.Any, builtins.int]"))]
+ fn read_bitfield<'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(info.raw)
+ })
+ }
+
+ /// Names of all currently active states
+ #[gen_stub(override_return_type(type_repr = "collections.abc.Coroutine[typing.Any, typing.Any, builtins.list[builtins.str]]"))]
+ fn active_states<'py>(&self, py: Python<'py>) -> PyResult> {
+ let cf = self.cf.clone();
+ pyo3_async_runtimes::tokio::future_into_py(py, async move {
+ let states = cf.supervisor.read_bitfield().await.map_err(to_pyerr)?
+ .active_states()
+ .into_iter()
+ .map(|s| s.to_string())
+ .collect::>();
+ Ok(states)
+ })
+ }
+
+ /// System can be armed - will accept an arming command
+ #[gen_stub(override_return_type(type_repr = "collections.abc.Coroutine[typing.Any, typing.Any, builtins.bool]"))]
+ fn can_be_armed<'py>(&self, py: Python<'py>) -> PyResult> {
+ let cf = self.cf.clone();
+ pyo3_async_runtimes::tokio::future_into_py(py, async move {
+ Ok(cf.supervisor.read_bitfield().await.map_err(to_pyerr)?.can_be_armed())
+ })
+ }
+
+ /// System is armed
+ #[gen_stub(override_return_type(type_repr = "collections.abc.Coroutine[typing.Any, typing.Any, builtins.bool]"))]
+ fn is_armed<'py>(&self, py: Python<'py>) -> PyResult> {
+ let cf = self.cf.clone();
+ pyo3_async_runtimes::tokio::future_into_py(py, async move {
+ Ok(cf.supervisor.read_bitfield().await.map_err(to_pyerr)?.is_armed())
+ })
+ }
+
+ /// System is configured to automatically arm
+ #[gen_stub(override_return_type(type_repr = "collections.abc.Coroutine[typing.Any, typing.Any, builtins.bool]"))]
+ fn is_auto_armed<'py>(&self, py: Python<'py>) -> PyResult> {
+ let cf = self.cf.clone();
+ pyo3_async_runtimes::tokio::future_into_py(py, async move {
+ Ok(cf.supervisor.read_bitfield().await.map_err(to_pyerr)?.is_auto_armed())
+ })
+ }
+
+ /// The Crazyflie is ready to fly
+ #[gen_stub(override_return_type(type_repr = "collections.abc.Coroutine[typing.Any, typing.Any, builtins.bool]"))]
+ fn can_fly<'py>(&self, py: Python<'py>) -> PyResult> {
+ let cf = self.cf.clone();
+ pyo3_async_runtimes::tokio::future_into_py(py, async move {
+ Ok(cf.supervisor.read_bitfield().await.map_err(to_pyerr)?.can_fly())
+ })
+ }
+
+ /// The Crazyflie is flying
+ #[gen_stub(override_return_type(type_repr = "collections.abc.Coroutine[typing.Any, typing.Any, builtins.bool]"))]
+ fn is_flying<'py>(&self, py: Python<'py>) -> PyResult> {
+ let cf = self.cf.clone();
+ pyo3_async_runtimes::tokio::future_into_py(py, async move {
+ Ok(cf.supervisor.read_bitfield().await.map_err(to_pyerr)?.is_flying())
+ })
+ }
+
+ /// The Crazyflie is tumbled (upside down)
+ #[gen_stub(override_return_type(type_repr = "collections.abc.Coroutine[typing.Any, typing.Any, builtins.bool]"))]
+ fn is_tumbled<'py>(&self, py: Python<'py>) -> PyResult> {
+ let cf = self.cf.clone();
+ pyo3_async_runtimes::tokio::future_into_py(py, async move {
+ Ok(cf.supervisor.read_bitfield().await.map_err(to_pyerr)?.is_tumbled())
+ })
+ }
+
+ /// The Crazyflie is in the locked state and must be restarted
+ #[gen_stub(override_return_type(type_repr = "collections.abc.Coroutine[typing.Any, typing.Any, builtins.bool]"))]
+ fn is_locked<'py>(&self, py: Python<'py>) -> PyResult> {
+ let cf = self.cf.clone();
+ pyo3_async_runtimes::tokio::future_into_py(py, async move {
+ Ok(cf.supervisor.read_bitfield().await.map_err(to_pyerr)?.is_locked())
+ })
+ }
+
+ /// The Crazyflie has crashed
+ #[gen_stub(override_return_type(type_repr = "collections.abc.Coroutine[typing.Any, typing.Any, builtins.bool]"))]
+ fn is_crashed<'py>(&self, py: Python<'py>) -> PyResult> {
+ let cf = self.cf.clone();
+ pyo3_async_runtimes::tokio::future_into_py(py, async move {
+ Ok(cf.supervisor.read_bitfield().await.map_err(to_pyerr)?.is_crashed())
+ })
+ }
+
+ /// High level commander is actively flying the drone
+ #[gen_stub(override_return_type(type_repr = "collections.abc.Coroutine[typing.Any, typing.Any, builtins.bool]"))]
+ fn hl_control_active<'py>(&self, py: Python<'py>) -> PyResult> {
+ let cf = self.cf.clone();
+ pyo3_async_runtimes::tokio::future_into_py(py, async move {
+ Ok(cf.supervisor.read_bitfield().await.map_err(to_pyerr)?.hl_control_active())
+ })
+ }
+
+ /// High level commander trajectory has finished
+ #[gen_stub(override_return_type(type_repr = "collections.abc.Coroutine[typing.Any, typing.Any, builtins.bool]"))]
+ fn hl_traj_finished<'py>(&self, py: Python<'py>) -> PyResult> {
+ let cf = self.cf.clone();
+ pyo3_async_runtimes::tokio::future_into_py(py, async move {
+ Ok(cf.supervisor.read_bitfield().await.map_err(to_pyerr)?.hl_traj_finished())
+ })
+ }
+
+ /// High level commander is disabled and not producing setpoints
+ #[gen_stub(override_return_type(type_repr = "collections.abc.Coroutine[typing.Any, typing.Any, builtins.bool]"))]
+ fn hl_control_disabled<'py>(&self, py: Python<'py>) -> PyResult> {
+ let cf = self.cf.clone();
+ pyo3_async_runtimes::tokio::future_into_py(py, async move {
+ Ok(cf.supervisor.read_bitfield().await.map_err(to_pyerr)?.hl_control_disabled())
+ })
+ }
+
+ /// Send system 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 crashed state. The firmware may allow
+ /// recovery without a full reboot depending on the crash type.
+ #[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(())
+ })
+ }
+}