Skip to main content

haptic_skin_firmware/drivers/
status_led.rs

1//! LED de statut de la controller box.
2//!
3//! Tâche async pilotée par un `Signal` : le reste du firmware appelle
4//! [`set`] pour changer d'état, la tâche [`run`] traduit l'état courant en
5//! clignotement sans jamais bloquer la boucle de commandes.
6//!
7//! Code couleur (monochrome, 1 LED) :
8//! - [`Status::Boot`]      : clignotement rapide pendant le self-test
9//! - [`Status::Idle`]      : battement lent (en attente d'un hôte USB)
10//! - [`Status::Connected`] : allumée fixe (hôte connecté, prêt à vibrer)
11//! - [`Status::Error`]     : clignotement très rapide (trame/USB invalide)
12//!
13//! ⚠️ LED **externe** sur `config::STATUS_LED_PIN_DESC` (GP16) : sur le Pico W
14//! la LED on-board est derrière la puce CYW43, pas sur un GPIO (cf. config.rs).
15
16use embassy_futures::select::{select, Either};
17use embassy_rp::gpio::Output;
18use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
19use embassy_sync::signal::Signal;
20use embassy_time::Timer;
21
22/// État affiché par la LED de statut.
23#[derive(Clone, Copy, PartialEq, Eq, defmt::Format)]
24pub enum Status {
25    Boot,
26    Idle,
27    Connected,
28    Error,
29}
30
31/// Canal état courant → tâche LED. `Signal` garde seulement la dernière valeur
32/// (on ne veut afficher que l'état le plus récent, pas une file d'historique).
33static STATUS: Signal<CriticalSectionRawMutex, Status> = Signal::new();
34
35/// Publie un nouvel état (appelé depuis la boucle de commandes / le boot).
36pub fn set(status: Status) {
37    STATUS.signal(status);
38}
39
40/// Demi-période de clignotement interruptible : allume `on_ms`, éteint `off_ms`,
41/// mais retourne immédiatement le nouvel état dès qu'un [`set`] arrive.
42async fn blink(led: &mut Output<'static>, on_ms: u64, off_ms: u64, current: Status) -> Status {
43    led.set_high();
44    if let Either::Second(s) = select(Timer::after_millis(on_ms), STATUS.wait()).await {
45        led.set_low();
46        return s;
47    }
48    led.set_low();
49    if let Either::Second(s) = select(Timer::after_millis(off_ms), STATUS.wait()).await {
50        return s;
51    }
52    current
53}
54
55/// Tâche LED : à spawn une fois au boot avec la sortie GPIO de la LED.
56#[embassy_executor::task]
57pub async fn run(mut led: Output<'static>) {
58    let mut status = Status::Boot;
59    loop {
60        status = match status {
61            Status::Boot => blink(&mut led, 120, 120, Status::Boot).await,
62            Status::Idle => blink(&mut led, 80, 1400, Status::Idle).await,
63            Status::Error => blink(&mut led, 60, 60, Status::Error).await,
64            Status::Connected => {
65                // allumée fixe : rien à faire tant que l'état ne change pas
66                led.set_high();
67                STATUS.wait().await
68            }
69        };
70    }
71}