xpanse_api/interfaces/leds.rs
1//! GPIO LED output wrappers.
2//!
3//! Provides logical LED roles that drivers can publish through the
4//! [`crate::registry::Registry`] and that apps can control for on/off/toggle
5//! state. Each role is a zero-sized marker type; the physical pin is wired to
6//! a role at startup using `pin_led`.
7
8use alloc::boxed::Box;
9use core::marker::PhantomData;
10
11use embassy_rp::{
12 Peri,
13 gpio::{AnyPin, Output, Level},
14};
15
16mod private {
17 pub trait Sealed {}
18}
19
20/// Marker trait for a logical LED role (e.g. `Generic`).
21///
22/// The type itself is zero-sized; it exists only to name a resource slot in the
23/// [`crate::registry::Registry`].
24pub trait LedRole: private::Sealed + 'static + Send {}
25
26macro_rules! role {
27 ($($n:ident),* $(,)?) => {
28 $(
29 pub struct $n;
30 impl private::Sealed for $n {}
31 impl LedRole for $n {}
32 )*
33 };
34}
35
36role!(Generic);
37
38/// Interface for controlling an LED's on/off state.
39pub trait Led<R: LedRole>: Send {
40 /// Turn the LED on.
41 fn on(&mut self);
42 /// Turn the LED off.
43 fn off(&mut self);
44 /// Set the LED state explicitly.
45 fn set(&mut self, on: bool);
46 /// Toggle the LED state.
47 fn toggle(&mut self);
48 /// Returns `true` if the LED is currently on.
49 fn is_on(&self) -> bool;
50}
51
52/// A single LED backed by one GPIO output.
53pub struct SingleLed<R: LedRole> {
54 pin: Output<'static>,
55 active_low: bool,
56 _role: PhantomData<R>,
57}
58
59impl<R: LedRole> SingleLed<R> {
60 /// Create a `SingleLed` from a GPIO pin.
61 ///
62 /// If `active_low` is `true`, the LED is driven by a low signal (common for
63 /// open-drain or active-low circuits). If `false`, a high signal turns the LED on.
64 pub fn new(pin: Peri<'static, AnyPin>, active_low: bool) -> Self {
65 let initial_level = if active_low { Level::High } else { Level::Low };
66 Self {
67 pin: Output::new(pin, initial_level),
68 active_low,
69 _role: PhantomData,
70 }
71 }
72}
73
74impl<R: LedRole> Led<R> for SingleLed<R> {
75 fn on(&mut self) {
76 let level = if self.active_low { Level::Low } else { Level::High };
77 self.pin.set_level(level);
78 }
79
80 fn off(&mut self) {
81 let level = if self.active_low { Level::High } else { Level::Low };
82 self.pin.set_level(level);
83 }
84
85 fn set(&mut self, on: bool) {
86 if on {
87 self.on();
88 } else {
89 self.off();
90 }
91 }
92
93 fn toggle(&mut self) {
94 self.pin.toggle();
95 }
96
97 fn is_on(&self) -> bool {
98 let is_high = self.pin.is_set_high();
99 if self.active_low {
100 !is_high
101 } else {
102 is_high
103 }
104 }
105}
106
107/// Create a boxed [`Led`] of role `R` from a single GPIO pin.
108///
109/// The pin is consumed and configured internally as an output.
110///
111/// # Arguments
112/// * `pin` - The GPIO pin to use.
113/// * `active_low` - If `true`, the LED is driven by a low signal; if `false`, by a high signal.
114///
115/// # Example
116///
117/// ```ignore
118/// use embassy_rp::gpio::AnyPin;
119/// use embassy_rp::Peri;
120/// use xpanse_api::interfaces::leds::{Generic, pin_led};
121/// use xpanse_api::registry::Registry;
122///
123/// # async fn example(
124/// # pin: Peri<'static, AnyPin>,
125/// # registry: &mut Registry,
126/// # slot: xpanse_api::metadata::ModuleSlot,
127/// # ) {
128/// let mut led = pin_led::<Generic>(pin, false);
129/// led.on();
130/// // registry.register(slot, id, led);
131/// # }
132/// ```
133pub fn pin_led<R: LedRole>(
134 pin: Peri<'static, AnyPin>,
135 active_low: bool,
136) -> Box<dyn Led<R>> {
137 Box::new(SingleLed::<R>::new(pin, active_low))
138}