Skip to main content

embassy_piezo/
lib.rs

1// Copyright (C) 2026 Jorge Andre Castro
2// GPL-2.0-or-later
3
4//! # embassy-piezo
5//!
6//! Driver asynchrone `no_std` pour capteur de vibration piézoélectrique (DO).
7//!
8//! Portage **esp-hal** (ESP32 / ESP32-Cx / ESP32-Sx). La logique métier est
9//! inchangée par rapport à la version `embassy-rp` : seul le type `Input`
10//! provient désormais de `esp_hal::gpio` au lieu de `embassy_rp::gpio`.
11//!
12//! ## Caractéristiques
13//! - Async natif via GPIO `esp-hal` (trait `embedded-hal-async` `Wait`)
14//! - Détection front montant sans polling (zéro CPU en attente)
15//! - Comptage d'événements avec timeout configurable
16//! - Debounce configurable pour filtrer les rebonds électriques
17//! - Compatible signaux globaux inter-tâches
18//! - Zéro allocation, zéro `unsafe`
19//!
20//! ## Pré-requis (Cargo.toml)
21//! ```toml
22//! [dependencies]
23//! esp-hal          = { version = "1", features = ["esp32"] } # adapter la cible
24//! esp-hal-embassy  = "0.99"   # fournit l'executor + le time-driver embassy
25//! embassy-executor = "0.7"
26//! embassy-time     = "0.4"
27//! ```
28//! `embassy-time` reste utilisé tel quel : `esp-hal-embassy` fournit
29//! l'implémentation de l'horloge (`embassy_time::Timer` / `with_timeout`)
30//! pour toutes les puces supportées par `esp-hal`, exactement comme
31//! `embassy-rp` le fait pour le RP2040.
32//!
33//! ## Exemple minimal
34//! ```rust,ignore
35//! use esp_hal::gpio::{Input, InputConfig, Pull};
36//!
37//! let pin = Input::new(peripherals.GPIO15, InputConfig::default().with_pull(Pull::Down));
38//! let mut piezo = PiezoVibration::new(pin);
39//!
40//! piezo.wait_for_vibration().await;
41//! let count = piezo.count();
42//! ```
43//!
44//! ## Exemple avec debounce
45//! ```rust,ignore
46//! use esp_hal::gpio::{Input, InputConfig, Pull};
47//!
48//! // Chocs humains : ignorer les rebonds électriques sous 100 ms
49//! let pin = Input::new(peripherals.GPIO15, InputConfig::default().with_pull(Pull::Down));
50//! let mut piezo = PiezoVibration::new_with_debounce(pin, 100);
51//!
52//! let event = piezo.wait_for_vibration().await;
53//! defmt::info!("choc #{}", event.count);
54//! ```
55//!
56//! ## Initialisation requise côté application
57//! ```rust,ignore
58//! use esp_hal::timer::timg::TimerGroup;
59//!
60//! let peripherals = esp_hal::init(esp_hal::Config::default());
61//! let timg0 = TimerGroup::new(peripherals.TIMG0);
62//! esp_hal_embassy::init(timg0.timer0); // requis avant tout Timer::after / with_timeout
63//! ```
64
65#![no_std]
66#![forbid(unsafe_code)]
67
68pub mod error;
69pub mod signals;
70
71pub use error::PiezoError;
72pub use esp_hal::gpio::Level;
73
74use embassy_time::{with_timeout, Duration, Timer};
75use esp_hal::gpio::Input;
76
77/// Événement de vibration horodaté.
78#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
79pub struct VibrationEvent {
80    /// Nombre de vibrations détectées depuis la création du driver.
81    pub count: u32,
82    /// `true` si le pin est actuellement haut (vibration en cours).
83    pub active: bool,
84}
85
86/// Driver pour capteur de vibration piézoélectrique (sortie numérique DO).
87///
88/// Le pin DO du capteur passe à l'état haut lors d'une vibration.
89/// La sensibilité est réglable via le potentiomètre du module.
90///
91/// # Debounce
92///
93/// Le paramètre `debounce_ms` filtre les rebonds électriques après un front
94/// montant : si le pin est retombé avant la fin du délai, l'événement est
95/// ignoré et l'attente recommence.
96///
97/// | `debounce_ms` | Cas d'usage                                      |
98/// |---------------|--------------------------------------------------|
99/// | `0`           | Désactivé tout front montant est compté        |
100/// | `5`           | Filtrage électrique basique                      |
101/// | `100`         | Chocs humains (frappe, impact mécanique)         |
102///
103/// ⚠ Ne pas dépasser la durée physique du signal : une valeur trop élevée
104/// ferait manquer des événements légitimes.
105pub struct PiezoVibration<'a> {
106    pin: Input<'a>,
107    count: u32,
108    debounce_ms: u64,
109}
110
111impl<'a> PiezoVibration<'a> {
112    /// Crée un driver sans debounce (`debounce_ms = 0`).
113    ///
114    /// Tout front montant est immédiatement comptabilisé.
115    ///
116    /// # Arguments
117    /// * `pin` : `Input` esp-hal déjà configuré (DO du capteur).
118    ///   Utiliser `Pull::Down` si le capteur ne tire pas lui-même la ligne :
119    ///   `Input::new(gpio, InputConfig::default().with_pull(Pull::Down))`.
120    pub fn new(pin: Input<'a>) -> Self {
121        Self { pin, count: 0, debounce_ms: 0 }
122    }
123
124    /// Crée un driver avec debounce.
125    ///
126    /// Après chaque front montant, attend `debounce_ms` millisecondes avant
127    /// de valider l'événement. Si le pin est retombé entre-temps (rebond),
128    /// l'événement est ignoré silencieusement et l'attente recommence.
129    ///
130    /// # Arguments
131    /// * `pin`         : `Input` esp-hal déjà configuré (DO du capteur).
132    /// * `debounce_ms` : Délai de confirmation en millisecondes (`0` = désactivé).
133    ///
134    /// # Valeurs recommandées
135    /// - `0`   : tout voir : haute fréquence, signaux continus, tests
136    /// - `5`   : filtrage électrique basique
137    /// - `100` : chocs humains discrets (frappe, impact mécanique)
138    pub fn new_with_debounce(pin: Input<'a>, debounce_ms: u64) -> Self {
139        Self { pin, count: 0, debounce_ms }
140    }
141
142    //  Lecture instantanée 
143    /// `true` si une vibration est détectée à cet instant.
144    pub fn is_vibrating(&self) -> bool {
145        self.pin.is_high()
146    }
147
148    /// Nombre de vibrations comptées depuis la création du driver.
149    pub fn count(&self) -> u32 {
150        self.count
151    }
152
153    /// Remet le compteur à zéro.
154    pub fn reset_count(&mut self) {
155        self.count = 0;
156    }
157
158    /// Retourne un snapshot de l'état courant.
159    pub fn state(&self) -> VibrationEvent {
160        VibrationEvent {
161            count: self.count,
162            active: self.is_vibrating(),
163        }
164    }
165
166    //  Attente asynchrone 
167
168    /// Attend le prochain front montant (début de vibration).
169    ///
170    /// Ne consomme pas de CPU pendant l'attente.
171    ///
172    /// Si `debounce_ms > 0`, confirme que le pin est encore haut après le
173    /// délai avant de valider. Les rebonds (pin retombé avant la fin du délai)
174    /// sont ignorés et l'attente recommence automatiquement.
175    ///
176    /// Incrémente le compteur interne à chaque détection validée.
177    pub async fn wait_for_vibration(&mut self) -> VibrationEvent {
178        loop {
179            self.pin.wait_for_high().await;
180
181            if self.debounce_ms > 0 {
182                Timer::after(Duration::from_millis(self.debounce_ms)).await;
183                // Rebond : signal retombé avant la fin du délai → on ignore
184                if !self.is_vibrating() {
185                    continue;
186                }
187            }
188
189            self.count = self.count.saturating_add(1);
190            return self.state();
191        }
192    }
193
194    /// Attend le prochain front montant avec un timeout.
195    ///
196    /// Le timeout englobe l'intégralité de la détection, délai de debounce
197    /// inclus : un choc détecté à `t` ms doit laisser au moins `debounce_ms`
198    /// ms avant l'expiration du timeout pour être validé.
199    ///
200    /// # Retour
201    /// - `Ok(VibrationEvent)` : vibration détectée dans le délai
202    /// - `Err(PiezoError::Timeout)` : délai expiré sans vibration confirmée
203    pub async fn wait_for_vibration_timeout(
204        &mut self,
205        timeout: Duration,
206    ) -> Result<VibrationEvent, PiezoError> {
207        with_timeout(timeout, async {
208            loop {
209                self.pin.wait_for_high().await;
210
211                if self.debounce_ms > 0 {
212                    Timer::after(Duration::from_millis(self.debounce_ms)).await;
213                    if !self.is_vibrating() {
214                        continue;
215                    }
216                }
217
218                return;
219            }
220        })
221        .await
222        .map_err(|_| PiezoError::Timeout)?;
223
224        self.count = self.count.saturating_add(1);
225        Ok(self.state())
226    }
227
228    /// Attend la fin d'une vibration (front descendant).
229    ///
230    /// Utile pour mesurer la durée d'un événement.
231    pub async fn wait_for_silence(&mut self) {
232        self.pin.wait_for_low().await;
233    }
234
235    /// Attend la fin d'une vibration avec un timeout.
236    ///
237    /// # Retour
238    /// - `Ok(())` : silence atteint dans le délai
239    /// - `Err(PiezoError::Timeout)` : vibration toujours active après le délai
240    pub async fn wait_for_silence_timeout(
241        &mut self,
242        timeout: Duration,
243    ) -> Result<(), PiezoError> {
244        with_timeout(timeout, self.pin.wait_for_low())
245            .await
246            .map_err(|_| PiezoError::Timeout)
247    }
248
249    // Comptage sur fenêtre temporelle 
250
251    /// Compte les vibrations détectées pendant une durée donnée.
252    ///
253    /// Utile pour mesurer une fréquence ou détecter une activité soutenue.
254    ///
255    /// # Exemple
256    /// ```rust,ignore
257    /// // Combien de chocs en 2 secondes ?
258    /// let n = piezo.count_during(Duration::from_secs(2)).await;
259    /// ```
260    ///
261    /// ⚠ Cette méthode suppose que `wait_for_vibration()` est appelée de
262    /// manière concurrente (par exemple dans une autre tâche) pour incrémenter
263    /// le compteur interne pendant que le délai s'écoule.
264    pub async fn count_during(&mut self, window: Duration) -> u32 {
265        let start = self.count;
266        Timer::after(window).await;
267        // Les fronts montants sont comptés par wait_for_vibration()
268        // appelé dans une tâche parallèle  ici on lit juste le delta.
269        self.count.saturating_sub(start)
270    }
271}