Skip to main content

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}