Skip to content

Commit 329bd2c

Browse files
authored
Merge pull request #5 from bitcraze/Aris/led_driver
led_driver memory
2 parents 43dc1ae + c1f16a5 commit 329bd2c

6 files changed

Lines changed: 297 additions & 6 deletions

File tree

cflib2/_rust.pyi

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,86 @@ class InvalidParameterError(CrazyflieError):
736736

737737
...
738738

739+
@typing.final
740+
class LedRingColor:
741+
r"""
742+
A single LED color and intensity for the Crazyflie LED ring.
743+
744+
Used to build the list of 12 LED values passed to `Memory.write_led_ring()`.
745+
"""
746+
@property
747+
def r(self) -> builtins.int:
748+
r"""
749+
Red component (0-255)
750+
"""
751+
@r.setter
752+
def r(self, value: builtins.int) -> None:
753+
r"""
754+
Red component (0-255)
755+
"""
756+
@property
757+
def g(self) -> builtins.int:
758+
r"""
759+
Green component (0-255)
760+
"""
761+
@g.setter
762+
def g(self, value: builtins.int) -> None:
763+
r"""
764+
Green component (0-255)
765+
"""
766+
@property
767+
def b(self) -> builtins.int:
768+
r"""
769+
Blue component (0-255)
770+
"""
771+
@b.setter
772+
def b(self, value: builtins.int) -> None:
773+
r"""
774+
Blue component (0-255)
775+
"""
776+
@property
777+
def intensity(self) -> builtins.int:
778+
r"""
779+
Intensity percentage (0-100); values above 100 are clamped to 100
780+
"""
781+
@intensity.setter
782+
def intensity(self, value: builtins.int) -> None:
783+
r"""
784+
Intensity percentage (0-100); values above 100 are clamped to 100
785+
"""
786+
def __new__(
787+
cls,
788+
r: builtins.int = 0,
789+
g: builtins.int = 0,
790+
b: builtins.int = 0,
791+
intensity: builtins.int = 100,
792+
) -> LedRingColor:
793+
r"""
794+
Create a new LedRingColor.
795+
796+
# Arguments
797+
* `r` - Red component (0-255, default 0)
798+
* `g` - Green component (0-255, default 0)
799+
* `b` - Blue component (0-255, default 0)
800+
* `intensity` - Intensity percentage (0-100, default 100); clamped to 100 if higher
801+
"""
802+
def set(
803+
self,
804+
r: builtins.int,
805+
g: builtins.int,
806+
b: builtins.int,
807+
intensity: typing.Optional[builtins.int] = None,
808+
) -> None:
809+
r"""
810+
Set R/G/B and optionally intensity in one call.
811+
812+
# Arguments
813+
* `r` - Red component (0-255)
814+
* `g` - Green component (0-255)
815+
* `b` - Blue component (0-255)
816+
* `intensity` - Intensity percentage (0-100); if None, keeps current value; clamped to 100 if higher
817+
"""
818+
739819
@typing.final
740820
class Lighthouse:
741821
r"""
@@ -1096,6 +1176,16 @@ class Memory:
10961176
* `segments` - List of CompressedSegment instances
10971177
* `start_addr` - Address in trajectory memory (default 0)
10981178
"""
1179+
async def write_led_ring(self, leds: typing.Sequence[LedRingColor]) -> None:
1180+
r"""
1181+
Write LED colors to the Crazyflie LED ring.
1182+
1183+
Opens the LED driver memory, sets all 12 LED values, writes them to
1184+
the ring, and closes the memory.
1185+
1186+
# Arguments
1187+
* `leds` - List of exactly 12 LedRingColor instances
1188+
"""
10991189
def get_memories(
11001190
self, memory_type: typing.Optional[builtins.int] = None
11011191
) -> builtins.list[tuple[builtins.int, builtins.int, builtins.int]]:

cflib2/memory.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@
2222
# along with this program. If not, see <http://www.gnu.org/licenses/>.
2323
"""Memory subsystem types"""
2424

25-
from cflib2._rust import CompressedSegment, CompressedStart, Memory, Poly, Poly4D
25+
from cflib2._rust import (
26+
CompressedSegment,
27+
CompressedStart,
28+
LedRingColor,
29+
Memory,
30+
Poly,
31+
Poly4D,
32+
)
2633

27-
__all__ = ["CompressedSegment", "CompressedStart", "Memory", "Poly", "Poly4D"]
34+
__all__ = [
35+
"CompressedSegment",
36+
"CompressedStart",
37+
"LedRingColor",
38+
"Memory",
39+
"Poly",
40+
"Poly4D",
41+
]

examples/led_ring.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# ,---------, ____ _ __
2+
# | ,-^-, | / __ )(_) /_______________ _____ ___
3+
# | ( O ) | / __ / / __/ ___/ ___/ __ `/_ / / _ \
4+
# | / ,--' | / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
5+
# +------` /_____/_/\__/\___/_/ \__,_/ /___/\___/
6+
#
7+
# Copyright (C) 2026 Bitcraze AB
8+
#
9+
# This program is free software: you can redistribute it and/or modify
10+
# it under the terms of the GNU General Public License as published by
11+
# the Free Software Foundation, either version 3 of the License, or
12+
# (at your option) any later version.
13+
#
14+
# This program is distributed in the hope that it will be useful,
15+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
# GNU General Public License for more details.
18+
#
19+
# You should have received a copy of the GNU General Public License
20+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
"""
22+
Simple example that connects to the crazyflie at `URI` and writes to
23+
the LED memory so that individual leds in the LED-ring can be set,
24+
it has been tested with (and designed for) the LED-ring deck.
25+
26+
Change the URI variable to your Crazyflie configuration.
27+
"""
28+
29+
import asyncio
30+
from dataclasses import dataclass
31+
32+
import tyro
33+
34+
from cflib2 import Crazyflie, LinkContext
35+
from cflib2.memory import LedRingColor
36+
37+
38+
@dataclass
39+
class Args:
40+
uri: str = "radio://0/80/2M/E7E7E7E7E7"
41+
"""Crazyflie URI"""
42+
43+
44+
async def main() -> None:
45+
args = tyro.cli(Args)
46+
47+
print(f"Connecting to {args.uri}...")
48+
ctx = LinkContext()
49+
cf = await Crazyflie.connect_from_uri(ctx, args.uri)
50+
print("Connected!")
51+
52+
try:
53+
# Set virtual mem effect
54+
await cf.param().set("ring.effect", 13)
55+
56+
# Build LED list and set individual LEDs
57+
leds = [LedRingColor() for _ in range(12)]
58+
leds[0].set(r=0, g=100, b=0)
59+
leds[3].set(r=0, g=0, b=100)
60+
leds[6].set(r=100, g=0, b=0)
61+
leds[9].set(r=100, g=100, b=100)
62+
await cf.memory().write_led_ring(leds)
63+
64+
await asyncio.sleep(2)
65+
66+
finally:
67+
print("Disconnecting...")
68+
await cf.disconnect()
69+
print("Done!")
70+
71+
72+
if __name__ == "__main__":
73+
asyncio.run(main())

rust/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use subsystems::{
3838
Commander, Console, Log, LogBlock, LogData, LogStream, Param, PersistentParamState, Platform, AppChannel,
3939
Localization, EmergencyControl, ExternalPose, Lighthouse, LocoPositioning,
4040
LighthouseAngleData, LighthouseAngles,
41-
Memory, Poly, Poly4D, CompressedStart, CompressedSegment,
41+
Memory, Poly, Poly4D, CompressedStart, CompressedSegment, LedRingColor,
4242
};
4343
use toc_cache::{NoTocCache, InMemoryTocCache, FileTocCache};
4444

@@ -69,6 +69,7 @@ fn _rust(m: &Bound<'_, PyModule>) -> PyResult<()> {
6969
m.add_class::<Poly4D>()?;
7070
m.add_class::<CompressedStart>()?;
7171
m.add_class::<CompressedSegment>()?;
72+
m.add_class::<LedRingColor>()?;
7273
m.add_class::<NoTocCache>()?;
7374
m.add_class::<InMemoryTocCache>()?;
7475
m.add_class::<FileTocCache>()?;

rust/src/subsystems/memory.rs

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@
2121

2222
//! # Memory subsystem bindings
2323
//!
24-
//! Provides Python bindings for trajectory memory operations.
24+
//! Provides Python bindings for memory operations.
2525
//! Trajectory data is built in Python using [`Poly`], [`Poly4D`],
2626
//! [`CompressedStart`], and [`CompressedSegment`], then uploaded
27-
//! via the [`Memory`] subsystem.
27+
//! via the [`Memory`] subsystem. LED ring colors are set using
28+
//! [`LedRingColor`] and written via [`Memory::write_led_ring`].
2829
2930
use pyo3::prelude::*;
3031
use pyo3::exceptions::PyValueError;
@@ -34,6 +35,66 @@ use std::sync::Arc;
3435
use crate::error::to_pyerr;
3536
use crazyflie_lib::subsystems::memory::MemoryType;
3637

38+
/// A single LED color and intensity for the Crazyflie LED ring.
39+
///
40+
/// Used to build the list of 12 LED values passed to `Memory.write_led_ring()`.
41+
#[gen_stub_pyclass]
42+
#[pyclass]
43+
#[derive(Clone, Debug)]
44+
pub struct LedRingColor {
45+
/// Red component (0-255)
46+
#[pyo3(get, set)]
47+
r: u8,
48+
/// Green component (0-255)
49+
#[pyo3(get, set)]
50+
g: u8,
51+
/// Blue component (0-255)
52+
#[pyo3(get, set)]
53+
b: u8,
54+
/// Intensity percentage (0-100); values above 100 are clamped to 100
55+
#[pyo3(get)]
56+
intensity: u8,
57+
}
58+
59+
#[gen_stub_pymethods]
60+
#[pymethods]
61+
impl LedRingColor {
62+
/// Create a new LedRingColor.
63+
///
64+
/// # Arguments
65+
/// * `r` - Red component (0-255, default 0)
66+
/// * `g` - Green component (0-255, default 0)
67+
/// * `b` - Blue component (0-255, default 0)
68+
/// * `intensity` - Intensity percentage (0-100, default 100); values above 100 are clamped to 100
69+
#[new]
70+
#[pyo3(signature = (r=0, g=0, b=0, intensity=100))]
71+
fn new(r: u8, g: u8, b: u8, intensity: u8) -> Self {
72+
Self { r, g, b, intensity: intensity.min(100) }
73+
}
74+
75+
#[setter]
76+
fn set_intensity(&mut self, value: u8) {
77+
self.intensity = value.min(100);
78+
}
79+
80+
/// Set R/G/B and optionally intensity in one call.
81+
///
82+
/// # Arguments
83+
/// * `r` - Red component (0-255)
84+
/// * `g` - Green component (0-255)
85+
/// * `b` - Blue component (0-255)
86+
/// * `intensity` - Intensity percentage (0-100); if None, keeps current value; clamped to 100 if higher
87+
#[pyo3(signature = (r, g, b, intensity=None))]
88+
fn set(&mut self, r: u8, g: u8, b: u8, intensity: Option<u8>) {
89+
self.r = r;
90+
self.g = g;
91+
self.b = b;
92+
if let Some(i) = intensity {
93+
self.intensity = i.min(100);
94+
}
95+
}
96+
}
97+
3798
/// A polynomial with up to 8 coefficients.
3899
///
39100
/// Coefficients beyond the provided values are zero-filled.
@@ -352,6 +413,58 @@ impl Memory {
352413
})
353414
}
354415

416+
/// Write LED colors to the Crazyflie LED ring.
417+
///
418+
/// Opens the LED driver memory, sets all 12 LED values, writes them to
419+
/// the ring, and closes the memory.
420+
///
421+
/// # Arguments
422+
/// * `leds` - List of exactly 12 LedRingColor instances
423+
#[gen_stub(override_return_type(type_repr = "collections.abc.Coroutine[typing.Any, typing.Any, None]"))]
424+
fn write_led_ring<'py>(
425+
&self,
426+
py: Python<'py>,
427+
leds: Vec<LedRingColor>,
428+
) -> PyResult<Bound<'py, PyAny>> {
429+
let cf = self.cf.clone();
430+
pyo3_async_runtimes::tokio::future_into_py(py, async move {
431+
if leds.len() != 12 {
432+
return Err(PyValueError::new_err(
433+
format!("Expected 12 LEDs, got {}", leds.len())
434+
));
435+
}
436+
437+
let memories = cf.memory.get_memories(Some(MemoryType::DriverLed));
438+
let mem_device = (*memories.first()
439+
.ok_or_else(|| to_pyerr(crazyflie_lib::Error::MemoryError(
440+
"No LED driver memory found on Crazyflie".to_owned()
441+
)))?)
442+
.clone();
443+
444+
let mut led_mem: crazyflie_lib::subsystems::memory::LedDriverMemory =
445+
cf.memory.initialize_memory(mem_device).await
446+
.ok_or_else(|| to_pyerr(crazyflie_lib::Error::MemoryError(
447+
"Failed to open LED driver memory".to_owned()
448+
)))?
449+
.map_err(to_pyerr)?;
450+
451+
for (i, led) in leds.iter().enumerate() {
452+
led_mem.leds[i].r = led.r;
453+
led_mem.leds[i].g = led.g;
454+
led_mem.leds[i].b = led.b;
455+
led_mem.leds[i].intensity = led.intensity;
456+
}
457+
458+
let write_result = led_mem.write_leds().await.map_err(to_pyerr);
459+
let close_result = cf.memory.close_memory(led_mem).await.map_err(to_pyerr);
460+
461+
write_result?;
462+
close_result?;
463+
464+
Ok(())
465+
})
466+
}
467+
355468
/// List all memories available on the Crazyflie.
356469
///
357470
/// Returns a list of tuples `(id, type, size)`:

rust/src/subsystems/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,6 @@ pub use console::Console;
3535
pub use high_level_commander::HighLevelCommander;
3636
pub use localization::{Localization, EmergencyControl, ExternalPose, Lighthouse, LocoPositioning, LighthouseAngleData, LighthouseAngles};
3737
pub use log::{Log, LogBlock, LogData, LogStream};
38-
pub use memory::{Memory, Poly, Poly4D, CompressedStart, CompressedSegment};
38+
pub use memory::{Memory, Poly, Poly4D, CompressedStart, CompressedSegment, LedRingColor};
3939
pub use param::{Param, PersistentParamState};
4040
pub use platform::{Platform, AppChannel};

0 commit comments

Comments
 (0)