Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cflib2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from cflib2._rust import (
Crazyflie,
LinkContext,
LedRingColor,
# TOC cache classes (passed to Crazyflie.connect_from_uri)
NoTocCache,
InMemoryTocCache,
Expand All @@ -34,6 +35,7 @@
__all__ = [
"Crazyflie",
"LinkContext",
"LedRingColor",
"NoTocCache",
"InMemoryTocCache",
"FileTocCache",
Expand Down
90 changes: 90 additions & 0 deletions cflib2/_rust.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,86 @@ class InMemoryTocCache:
Get the number of cached TOCs
"""

@typing.final
class LedRingColor:
r"""
A single LED color and intensity for the Crazyflie LED ring.

Used to build the list of 12 LED values passed to `Memory.write_led_ring()`.
"""
@property
def r(self) -> builtins.int:
r"""
Red component (0-255)
"""
@r.setter
def r(self, value: builtins.int) -> None:
r"""
Red component (0-255)
"""
@property
def g(self) -> builtins.int:
r"""
Green component (0-255)
"""
@g.setter
def g(self, value: builtins.int) -> None:
r"""
Green component (0-255)
"""
@property
def b(self) -> builtins.int:
r"""
Blue component (0-255)
"""
@b.setter
def b(self, value: builtins.int) -> None:
r"""
Blue component (0-255)
"""
@property
def intensity(self) -> builtins.int:
r"""
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 __new__(
cls,
r: builtins.int = 0,
g: builtins.int = 0,
b: builtins.int = 0,
intensity: builtins.int = 100,
) -> LedRingColor:
r"""
Create a new LedRingColor.

# Arguments
* `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
"""
def set(
self,
r: builtins.int,
g: builtins.int,
b: builtins.int,
intensity: typing.Optional[builtins.int] = None,
) -> None:
r"""
Set R/G/B and optionally intensity in one call.

# Arguments
* `r` - Red component (0-255)
* `g` - Green component (0-255)
* `b` - Blue component (0-255)
* `intensity` - Intensity percentage (0-100); if None, keeps current value; clamped to 100 if higher
"""

@typing.final
class Lighthouse:
r"""
Expand Down Expand Up @@ -985,6 +1065,16 @@ class Memory:
* `segments` - List of CompressedSegment instances
* `start_addr` - Address in trajectory memory (default 0)
"""
async def write_led_ring(self, leds: typing.Sequence[LedRingColor]) -> None:
r"""
Write LED colors to the Crazyflie LED ring.

Opens the LED driver memory, sets all 12 LED values, writes them to
the ring, and closes the memory.

# Arguments
* `leds` - List of exactly 12 LedRingColor instances
"""
def get_memories(
self, memory_type: typing.Optional[builtins.int] = None
) -> builtins.list[tuple[builtins.int, builtins.int, builtins.int]]:
Expand Down
72 changes: 72 additions & 0 deletions examples/led_ring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# ,---------, ____ _ __
# | ,-^-, | / __ )(_) /_______________ _____ ___
# | ( 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 <http://www.gnu.org/licenses/>.
"""
Simple example that connects to the crazyflie at `URI` and writes to
the LED memory so that individual leds in the LED-ring can be set,
it has been tested with (and designed for) the LED-ring deck.

Change the URI variable to your Crazyflie configuration.
"""

import asyncio
from dataclasses import dataclass

import tyro

from cflib2 import Crazyflie, LedRingColor, 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:
# Set virtual mem effect
await cf.param().set("ring.effect", 13)

# Build LED list and set individual LEDs
leds = [LedRingColor() for _ in range(12)]
leds[0].set(r=0, g=100, b=0)
leds[3].set(r=0, g=0, b=100)
leds[6].set(r=100, g=0, b=0)
leds[9].set(r=100, g=100, b=100)
await cf.memory().write_led_ring(leds)

await asyncio.sleep(2)

finally:
print("Disconnecting...")
await cf.disconnect()
print("Done!")


if __name__ == "__main__":
asyncio.run(main())
3 changes: 2 additions & 1 deletion rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use subsystems::{
Commander, Console, Log, LogBlock, LogData, LogStream, Param, Platform, AppChannel,
Localization, EmergencyControl, ExternalPose, Lighthouse, LocoPositioning,
LighthouseAngleData, LighthouseAngles,
Memory, Poly, Poly4D, CompressedStart, CompressedSegment,
Memory, Poly, Poly4D, CompressedStart, CompressedSegment, LedRingColor,
};
use toc_cache::{NoTocCache, InMemoryTocCache, FileTocCache};

Expand Down Expand Up @@ -67,6 +67,7 @@ fn _rust(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<Poly4D>()?;
m.add_class::<CompressedStart>()?;
m.add_class::<CompressedSegment>()?;
m.add_class::<LedRingColor>()?;
m.add_class::<NoTocCache>()?;
m.add_class::<InMemoryTocCache>()?;
m.add_class::<FileTocCache>()?;
Expand Down
109 changes: 107 additions & 2 deletions rust/src/subsystems/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@

//! # Memory subsystem bindings
//!
//! Provides Python bindings for trajectory memory operations.
//! Provides Python bindings for memory operations.
//! Trajectory data is built in Python using [`Poly`], [`Poly4D`],
//! [`CompressedStart`], and [`CompressedSegment`], then uploaded
//! via the [`Memory`] subsystem.
//! via the [`Memory`] subsystem. LED ring colors are set using
//! [`LedRingColor`] and written via [`Memory::write_led_ring`].

use pyo3::prelude::*;
use pyo3::exceptions::PyValueError;
Expand All @@ -34,6 +35,61 @@ use std::sync::Arc;
use crate::error::to_pyerr;
use crazyflie_lib::subsystems::memory::MemoryType;

/// A single LED color and intensity for the Crazyflie LED ring.
///
/// Used to build the list of 12 LED values passed to `Memory.write_led_ring()`.
#[gen_stub_pyclass]
#[pyclass]
#[derive(Clone, Debug)]
pub struct LedRingColor {
/// Red component (0-255)
#[pyo3(get, set)]
r: u8,
/// Green component (0-255)
#[pyo3(get, set)]
g: u8,
/// Blue component (0-255)
#[pyo3(get, set)]
b: u8,
/// Intensity percentage (0-100); values above 100 are clamped to 100
#[pyo3(get, set)]
intensity: u8,
}

#[gen_stub_pymethods]
#[pymethods]
impl LedRingColor {
/// Create a new LedRingColor.
///
/// # Arguments
/// * `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
#[new]
#[pyo3(signature = (r=0, g=0, b=0, intensity=100))]
fn new(r: u8, g: u8, b: u8, intensity: u8) -> Self {
Self { r, g, b, intensity: intensity.min(100) }
}

/// Set R/G/B and optionally intensity in one call.
///
/// # Arguments
/// * `r` - Red component (0-255)
/// * `g` - Green component (0-255)
/// * `b` - Blue component (0-255)
/// * `intensity` - Intensity percentage (0-100); if None, keeps current value; clamped to 100 if higher
#[pyo3(signature = (r, g, b, intensity=None))]
fn set(&mut self, r: u8, g: u8, b: u8, intensity: Option<u8>) {
self.r = r;
self.g = g;
self.b = b;
if let Some(i) = intensity {
self.intensity = i.min(100);
}
}
}

/// A polynomial with up to 8 coefficients.
///
/// Coefficients beyond the provided values are zero-filled.
Expand Down Expand Up @@ -352,6 +408,55 @@ impl Memory {
})
}

/// Write LED colors to the Crazyflie LED ring.
///
/// Opens the LED driver memory, sets all 12 LED values, writes them to
/// the ring, and closes the memory.
///
/// # Arguments
/// * `leds` - List of exactly 12 LedRingColor instances
#[gen_stub(override_return_type(type_repr = "collections.abc.Coroutine[typing.Any, typing.Any, None]"))]
fn write_led_ring<'py>(
&self,
py: Python<'py>,
leds: Vec<LedRingColor>,
) -> PyResult<Bound<'py, PyAny>> {
let cf = self.cf.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
if leds.len() != 12 {
return Err(PyValueError::new_err(
format!("Expected 12 LEDs, got {}", leds.len())
));
}

let memories = cf.memory.get_memories(Some(MemoryType::DriverLed));
let mem_device = (*memories.first()
.ok_or_else(|| to_pyerr(crazyflie_lib::Error::MemoryError(
"No LED driver memory found on Crazyflie".to_owned()
)))?)
.clone();

let mut led_mem: crazyflie_lib::subsystems::memory::LedDriverMemory =
cf.memory.initialize_memory(mem_device).await
.ok_or_else(|| to_pyerr(crazyflie_lib::Error::MemoryError(
"Failed to open LED driver memory".to_owned()
)))?
.map_err(to_pyerr)?;

for (i, led) in leds.iter().enumerate() {
led_mem.leds[i].r = led.r;
led_mem.leds[i].g = led.g;
led_mem.leds[i].b = led.b;
led_mem.leds[i].intensity = led.intensity;
}

led_mem.write_leds().await.map_err(to_pyerr)?;
cf.memory.close_memory(led_mem).await.map_err(to_pyerr)?;

Ok(())
})
}

/// List all memories available on the Crazyflie.
///
/// Returns a list of tuples `(id, type, size)`:
Expand Down
2 changes: 1 addition & 1 deletion rust/src/subsystems/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@ pub use console::Console;
pub use high_level_commander::HighLevelCommander;
pub use localization::{Localization, EmergencyControl, ExternalPose, Lighthouse, LocoPositioning, LighthouseAngleData, LighthouseAngles};
pub use log::{Log, LogBlock, LogData, LogStream};
pub use memory::{Memory, Poly, Poly4D, CompressedStart, CompressedSegment};
pub use memory::{Memory, Poly, Poly4D, CompressedStart, CompressedSegment, LedRingColor};
pub use param::Param;
pub use platform::{Platform, AppChannel};
Loading