From fbf45a628b73ad6f177b986d0ebbb5b477642045 Mon Sep 17 00:00:00 2001 From: ArisMorgens Date: Tue, 5 May 2026 17:02:15 +0200 Subject: [PATCH 1/9] Added the supervisor subsystem and moved commands from platform and localization --- cflib2/_rust.pyi | 110 ++++++++++++++ cflib2/supervisor.py | 27 ++++ rust/src/crazyflie.rs | 9 +- rust/src/lib.rs | 2 + rust/src/subsystems/localization.rs | 9 ++ rust/src/subsystems/mod.rs | 2 + rust/src/subsystems/platform.rs | 9 ++ rust/src/subsystems/supervisor.rs | 226 ++++++++++++++++++++++++++++ 8 files changed, 393 insertions(+), 1 deletion(-) create mode 100644 cflib2/supervisor.py create mode 100644 rust/src/subsystems/supervisor.rs diff --git a/cflib2/_rust.pyi b/cflib2/_rust.pyi index b55d7cf..3d1a69e 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: ... @@ -441,6 +445,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""" @@ -450,6 +456,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 @@ -1487,6 +1495,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 """ @@ -1495,6 +1505,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""" @@ -1578,6 +1590,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/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 2be68ac..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,8 +83,12 @@ 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.supervisor.send_emergency_stop().await @@ -98,8 +103,12 @@ 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.supervisor.send_emergency_stop_watchdog().await 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 3cdc80b..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,10 +108,14 @@ 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.supervisor.send_arming_request(do_arm).await.map_err(to_pyerr)?; @@ -121,8 +126,12 @@ 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.supervisor.send_crash_recovery_request().await.map_err(to_pyerr)?; 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(()) + }) + } +} From 6c5e21ece206f7ab15ab1d86f98882960d7dfdc1 Mon Sep 17 00:00:00 2001 From: ArisMorgens Date: Tue, 5 May 2026 17:05:59 +0200 Subject: [PATCH 2/9] Added supervisor examples --- examples/flying_with_supervisor.py | 108 +++++++++++++++++++++++++++++ examples/reading_supervisor.py | 77 ++++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 examples/flying_with_supervisor.py create mode 100644 examples/reading_supervisor.py 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()) From d198781ef4918e649d5330e4991aae0016352bd5 Mon Sep 17 00:00:00 2001 From: ArisMorgens Date: Tue, 5 May 2026 17:12:44 +0200 Subject: [PATCH 3/9] Updated the existing examples with supervisor commands --- examples/arming.py | 8 ++++---- examples/emergency_stop.py | 10 ++++------ examples/emergency_watchdog.py | 12 +++++------- examples/trajectory.py | 2 +- 4 files changed, 14 insertions(+), 18 deletions(-) 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/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 From 6d9a172b7419cf78a143b32e33cc245a4e9d646c Mon Sep 17 00:00:00 2001 From: Rik Bouwmeester Date: Thu, 16 Jul 2026 13:33:26 +0200 Subject: [PATCH 4/9] Remove arming and emergency stop from their old locations The previous commits moved these commands to the supervisor subsystem and kept deprecated copies under platform and localization.emergency with runtime warnings. There is no released version of this library yet, so nobody depends on the old locations - remove them instead of carrying deprecation shims from the first release onward. --- cflib2/_rust.pyi | 131 ++++++++-------------------- cflib2/localization.py | 2 - rust/src/lib.rs | 3 +- rust/src/subsystems/localization.rs | 61 +------------ rust/src/subsystems/mod.rs | 2 +- rust/src/subsystems/platform.rs | 37 -------- 6 files changed, 41 insertions(+), 195 deletions(-) diff --git a/cflib2/_rust.pyi b/cflib2/_rust.pyi index 3d1a69e..bbd9fea 100644 --- a/cflib2/_rust.pyi +++ b/cflib2/_rust.pyi @@ -432,34 +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. - - Deprecated: Use `crazyflie.supervisor().send_emergency_stop()` instead. - """ - 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. - - Deprecated: Use `crazyflie.supervisor().send_emergency_stop_watchdog()` instead. - """ - @typing.final class ExternalPose: r""" @@ -805,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, @@ -823,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, @@ -978,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 @@ -1488,26 +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. - - Deprecated: Use `crazyflie.supervisor().send_arming_request()` instead. - - # 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. - - Deprecated: Use `crazyflie.supervisor().send_crash_recovery_request()` instead. - """ async def get_app_channel(self) -> typing.Optional[AppChannel]: r""" Get the bidirectional app channel for custom communication @@ -1602,47 +1547,13 @@ class Supervisor: 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. + 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 @@ -1687,6 +1598,40 @@ class Supervisor: r""" High level commander is disabled and not producing setpoints """ + 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. + """ class SystemError(CrazyflieError): r""" diff --git a/cflib2/localization.py b/cflib2/localization.py index a8a41d3..0637155 100644 --- a/cflib2/localization.py +++ b/cflib2/localization.py @@ -23,7 +23,6 @@ """Localization subsystem types""" from cflib2._rust import ( - EmergencyControl, ExternalPose, Lighthouse, LighthouseAngleData, @@ -33,7 +32,6 @@ ) __all__ = [ - "EmergencyControl", "ExternalPose", "Lighthouse", "LighthouseAngleData", diff --git a/rust/src/lib.rs b/rust/src/lib.rs index b80c283..00f1179 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -36,7 +36,7 @@ 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, @@ -59,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::()?; diff --git a/rust/src/subsystems/localization.rs b/rust/src/subsystems/localization.rs index cf7183a..717e4ed 100644 --- a/rust/src/subsystems/localization.rs +++ b/rust/src/subsystems/localization.rs @@ -19,10 +19,9 @@ // 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::PyTypeInfo; use pyo3_stub_gen::derive::*; use std::sync::Arc; use futures::stream::Stream; @@ -40,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 { @@ -67,57 +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. - /// - /// 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.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. - /// - /// 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.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 4e6ae2b..cb97d9e 100644 --- a/rust/src/subsystems/mod.rs +++ b/rust/src/subsystems/mod.rs @@ -34,7 +34,7 @@ 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}; diff --git a/rust/src/subsystems/platform.rs b/rust/src/subsystems/platform.rs index d58af84..f70f52b 100644 --- a/rust/src/subsystems/platform.rs +++ b/rust/src/subsystems/platform.rs @@ -23,7 +23,6 @@ use pyo3::prelude::*; use pyo3::exceptions::PyValueError; -use pyo3::PyTypeInfo; use pyo3_stub_gen_derive::*; use std::sync::Arc; use tokio::sync::Mutex; @@ -103,42 +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. - /// - /// 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.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. - /// - /// 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.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 From b2c8890fea84b00e626449f19464760cda22ca1c Mon Sep 17 00:00:00 2001 From: Rik Bouwmeester Date: Thu, 16 Jul 2026 13:39:05 +0200 Subject: [PATCH 5/9] Remove the flying supervisor example The example is correct, but it is a fairly involved flight scenario - arming, takeoff, in-flight state monitoring, landing - which makes it more of a demo than a focused API example. reading_supervisor.py already shows the supervisor API without flying; move this one to the demos repo instead. --- examples/flying_with_supervisor.py | 108 ----------------------------- 1 file changed, 108 deletions(-) delete mode 100644 examples/flying_with_supervisor.py diff --git a/examples/flying_with_supervisor.py b/examples/flying_with_supervisor.py deleted file mode 100644 index 6778a21..0000000 --- a/examples/flying_with_supervisor.py +++ /dev/null @@ -1,108 +0,0 @@ -# ,---------, ____ _ __ -# | ,-^-, | / __ )(_) /_______________ _____ ___ -# | ( 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()) From 8820901d78f53aac4b06cc147964e174d9475980 Mon Sep 17 00:00:00 2001 From: Rik Bouwmeester Date: Thu, 16 Jul 2026 14:15:08 +0200 Subject: [PATCH 6/9] Clean up supervisor docstrings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix the crash recovery description: it claimed recovery depends on the crash type, but the firmware only has a single crashed state. Use the crazyflie-lib wording instead. Also use "The Crazyflie" consistently instead of mixing it with "the system", and drop the cache duration from read_bitfield's docstring — it hardcoded a crazyflie-lib internal constant that can change without this binding noticing. Regenerate the type stubs accordingly. --- cflib2/_rust.pyi | 15 +++++++-------- rust/src/subsystems/supervisor.rs | 17 ++++++++--------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/cflib2/_rust.pyi b/cflib2/_rust.pyi index bbd9fea..0a36ab5 100644 --- a/cflib2/_rust.pyi +++ b/cflib2/_rust.pyi @@ -1540,14 +1540,14 @@ class Supervisor: r""" Supervisor subsystem - Monitors the Crazyflie's system state and exposes arming, crash recovery, + Monitors the Crazyflie 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) + Returns the raw bitfield as an integer. Uses time-based caching to avoid flooding the link. """ async def active_states(self) -> builtins.list[builtins.str]: @@ -1556,15 +1556,15 @@ class Supervisor: """ async def can_be_armed(self) -> builtins.bool: r""" - System can be armed - will accept an arming command + The Crazyflie can be armed - will accept an arming command """ async def is_armed(self) -> builtins.bool: r""" - System is armed + The Crazyflie is armed """ async def is_auto_armed(self) -> builtins.bool: r""" - System is configured to automatically arm + The Crazyflie is configured to automatically arm """ async def can_fly(self) -> builtins.bool: r""" @@ -1600,7 +1600,7 @@ class Supervisor: """ async def send_arming_request(self, do_arm: builtins.bool) -> None: r""" - Send system arm/disarm request + Send arm/disarm request Arms or disarms the Crazyflie's motors. When disarmed, the motors will not spin even if thrust commands are sent. @@ -1612,8 +1612,7 @@ class Supervisor: 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. + Requests recovery from a crash state detected by the Crazyflie. """ async def send_emergency_stop(self) -> None: r""" diff --git a/rust/src/subsystems/supervisor.rs b/rust/src/subsystems/supervisor.rs index cda0de3..918a490 100644 --- a/rust/src/subsystems/supervisor.rs +++ b/rust/src/subsystems/supervisor.rs @@ -19,7 +19,7 @@ // 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 +//! Supervisor subsystem - Crazyflie state, arming, crash recovery, and emergency stop use pyo3::prelude::*; use pyo3_stub_gen::derive::*; @@ -29,7 +29,7 @@ use crate::error::to_pyerr; /// Supervisor subsystem /// -/// Monitors the Crazyflie's system state and exposes arming, crash recovery, +/// Monitors the Crazyflie state and exposes arming, crash recovery, /// and emergency stop controls. Obtain via `crazyflie.supervisor()`. #[gen_stub_pyclass] #[pyclass] @@ -42,7 +42,7 @@ pub struct Supervisor { impl Supervisor { /// Read the raw supervisor state bitfield /// - /// Returns the raw bitfield as an integer. Uses time-based caching (100 ms) + /// Returns the raw bitfield as an integer. Uses time-based caching /// 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> { @@ -67,7 +67,7 @@ impl Supervisor { }) } - /// System can be armed - will accept an arming command + /// The Crazyflie 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(); @@ -76,7 +76,7 @@ impl Supervisor { }) } - /// System is armed + /// The Crazyflie 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(); @@ -85,7 +85,7 @@ impl Supervisor { }) } - /// System is configured to automatically arm + /// The Crazyflie 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(); @@ -166,7 +166,7 @@ impl Supervisor { }) } - /// Send system arm/disarm request + /// Send arm/disarm request /// /// Arms or disarms the Crazyflie's motors. When disarmed, the motors /// will not spin even if thrust commands are sent. @@ -184,8 +184,7 @@ impl Supervisor { /// Send crash recovery request /// - /// Requests recovery from a crashed state. The firmware may allow - /// recovery without a full reboot depending on the crash type. + /// 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(); From 3b9c79ed10d4f7d259252babfab118b43b082068 Mon Sep 17 00:00:00 2001 From: Rik Bouwmeester Date: Thu, 16 Jul 2026 14:15:28 +0200 Subject: [PATCH 7/9] Return a state snapshot from the supervisor instead of per-flag reads Each state query method independently awaited a bitfield read, so two consecutive checks could decode different radio reads and report mutually inconsistent flags - exactly what supervisor-based safety logic must not do. Replace them with a single read() returning a SupervisorState snapshot: all flags on one snapshot decode the same bitfield, so they are consistent by construction, and compound checks need one await instead of several. This also mirrors the crazyflie-lib design, where read_bitfield() returns a SupervisorInfo value that is queried synchronously. The raw bitfield is available as SupervisorState.raw. --- cflib2/_rust.pyi | 136 +++++++++++------ cflib2/supervisor.py | 4 +- examples/reading_supervisor.py | 16 +- rust/src/lib.rs | 3 +- rust/src/subsystems/mod.rs | 2 +- rust/src/subsystems/supervisor.rs | 242 +++++++++++++++--------------- 6 files changed, 222 insertions(+), 181 deletions(-) diff --git a/cflib2/_rust.pyi b/cflib2/_rust.pyi index 0a36ab5..341b808 100644 --- a/cflib2/_rust.pyi +++ b/cflib2/_rust.pyi @@ -1543,94 +1543,134 @@ class Supervisor: Monitors the Crazyflie state and exposes arming, crash recovery, and emergency stop controls. Obtain via `crazyflie.supervisor()`. """ - async def read_bitfield(self) -> builtins.int: + async def read(self) -> SupervisorState: r""" - Read the raw supervisor state bitfield + Read a consistent snapshot of the supervisor state - Returns the raw bitfield as an integer. Uses time-based caching - to avoid flooding the link. + 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 active_states(self) -> builtins.list[builtins.str]: + async def send_crash_recovery_request(self) -> None: r""" - Names of all currently active states + Send crash recovery request + + Requests recovery from a crash state detected by the Crazyflie. """ - async def can_be_armed(self) -> builtins.bool: + 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 """ - async def is_armed(self) -> builtins.bool: + @property + def is_armed(self) -> builtins.bool: r""" The Crazyflie is armed """ - async def is_auto_armed(self) -> builtins.bool: + @property + def is_auto_armed(self) -> builtins.bool: r""" The Crazyflie is configured to automatically arm """ - async def can_fly(self) -> builtins.bool: + @property + def can_fly(self) -> builtins.bool: r""" The Crazyflie is ready to fly """ - async def is_flying(self) -> builtins.bool: + @property + def is_flying(self) -> builtins.bool: r""" The Crazyflie is flying """ - async def is_tumbled(self) -> builtins.bool: + @property + def is_tumbled(self) -> builtins.bool: r""" The Crazyflie is tumbled (upside down) """ - async def is_locked(self) -> builtins.bool: + @property + 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: + @property + def is_crashed(self) -> builtins.bool: r""" The Crazyflie has crashed """ - async def hl_control_active(self) -> builtins.bool: + @property + def hl_control_active(self) -> builtins.bool: r""" High level commander is actively flying the drone """ - async def hl_traj_finished(self) -> builtins.bool: + @property + def hl_traj_finished(self) -> builtins.bool: r""" High level commander trajectory has finished """ - async def hl_control_disabled(self) -> builtins.bool: + @property + def hl_control_disabled(self) -> builtins.bool: r""" High level commander is disabled and not producing setpoints """ - 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: + def active_states(self) -> builtins.list[builtins.str]: 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. + Names of all active states in this snapshot """ + def __repr__(self) -> builtins.str: ... class SystemError(CrazyflieError): r""" diff --git a/cflib2/supervisor.py b/cflib2/supervisor.py index 6ca9cb9..34ec70f 100644 --- a/cflib2/supervisor.py +++ b/cflib2/supervisor.py @@ -22,6 +22,6 @@ # along with this program. If not, see . """Supervisor subsystem types""" -from cflib2._rust import Supervisor +from cflib2._rust import Supervisor, SupervisorState -__all__ = ["Supervisor"] +__all__ = ["Supervisor", "SupervisorState"] diff --git a/examples/reading_supervisor.py b/examples/reading_supervisor.py index 0ed035f..4c5354a 100644 --- a/examples/reading_supervisor.py +++ b/examples/reading_supervisor.py @@ -55,16 +55,12 @@ async def main() -> None: 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}") + # 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: diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 00f1179..6c152f2 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -39,7 +39,7 @@ use subsystems::{ Localization, ExternalPose, Lighthouse, LocoPositioning, LighthouseAngleData, LighthouseAngles, Memory, Poly, Poly4D, CompressedStart, CompressedSegment, LedRingColor, - Supervisor, + Supervisor, SupervisorState, }; use toc_cache::{NoTocCache, InMemoryTocCache, FileTocCache}; @@ -71,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/mod.rs b/rust/src/subsystems/mod.rs index cb97d9e..d0579b0 100644 --- a/rust/src/subsystems/mod.rs +++ b/rust/src/subsystems/mod.rs @@ -39,4 +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; +pub use supervisor::{Supervisor, SupervisorState}; diff --git a/rust/src/subsystems/supervisor.rs b/rust/src/subsystems/supervisor.rs index 918a490..5198ea5 100644 --- a/rust/src/subsystems/supervisor.rs +++ b/rust/src/subsystems/supervisor.rs @@ -40,129 +40,29 @@ pub struct Supervisor { #[gen_stub_pymethods] #[pymethods] impl Supervisor { - /// Read the raw supervisor state bitfield + /// Read a consistent snapshot of the supervisor state /// - /// Returns the raw bitfield as an integer. Uses time-based caching - /// 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> { + /// 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(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) - }) - } - - /// The Crazyflie 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()) - }) - } - - /// The Crazyflie 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()) - }) - } - - /// The Crazyflie 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()) + Ok(SupervisorState { info }) }) } @@ -223,3 +123,107 @@ impl Supervisor { }) } } + +/// 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(", ") + ) + } +} From 39e5aaf756ca4b77f61554dd18ef2430663001e3 Mon Sep 17 00:00:00 2001 From: Rik Bouwmeester Date: Thu, 16 Jul 2026 14:43:44 +0200 Subject: [PATCH 8/9] Fix copyright year in new supervisor files --- cflib2/supervisor.py | 2 +- rust/src/subsystems/supervisor.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cflib2/supervisor.py b/cflib2/supervisor.py index 34ec70f..87792e1 100644 --- a/cflib2/supervisor.py +++ b/cflib2/supervisor.py @@ -6,7 +6,7 @@ # | / ,--' | / /_/ / / /_/ /__/ / / /_/ / / /_/ __/ # +------` /_____/_/\__/\___/_/ \__,_/ /___/\___/ # -# Copyright (C) 2025 Bitcraze AB +# 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 diff --git a/rust/src/subsystems/supervisor.rs b/rust/src/subsystems/supervisor.rs index 5198ea5..79ef36f 100644 --- a/rust/src/subsystems/supervisor.rs +++ b/rust/src/subsystems/supervisor.rs @@ -4,7 +4,7 @@ // | / ,--' | / /_/ / / /_/ /__/ / / /_/ / / /_/ __/ // +------` /_____/_/\__/\___/_/ \__,_/ /___/\___/ // -// Copyright (C) 2025 Bitcraze AB +// 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 From 33fede65821b54b32d719732a85afb618da89892 Mon Sep 17 00:00:00 2001 From: Rik Bouwmeester Date: Thu, 16 Jul 2026 14:48:00 +0200 Subject: [PATCH 9/9] Drop the coding: utf-8 declarations --- cflib2/__init__.py | 2 -- cflib2/commander.py | 2 -- cflib2/console.py | 2 -- cflib2/error.py | 2 -- cflib2/high_level_commander.py | 2 -- cflib2/localization.py | 2 -- cflib2/log.py | 2 -- cflib2/memory.py | 2 -- cflib2/param.py | 2 -- cflib2/platform.py | 2 -- cflib2/supervisor.py | 2 -- cflib2/toc_cache.py | 2 -- 12 files changed, 24 deletions(-) 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/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 0637155..2027145 100644 --- a/cflib2/localization.py +++ b/cflib2/localization.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # ,---------, ____ _ __ # | ,-^-, | / __ )(_) /_______________ _____ ___ # | ( O ) | / __ / / __/ ___/ ___/ __ `/_ / / _ \ 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 index 87792e1..10e385a 100644 --- a/cflib2/supervisor.py +++ b/cflib2/supervisor.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # ,---------, ____ _ __ # | ,-^-, | / __ )(_) /_______________ _____ ___ # | ( O ) | / __ / / __/ ___/ ___/ __ `/_ / / _ \ 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 ) | / __ / / __/ ___/ ___/ __ `/_ / / _ \