Skip to main content

embassy_rp_hc05/
lib.rs

1// Copyright (C) 2026 Jorge Andre Castro
2// GPL-2.0-or-later
3//! # embassy-rp-hc05
4//!
5//! Wrapper async `no_std` pour le module Bluetooth **HC-05** testé sur
6//! microcontrôleur **RP2040** / **RP235x**, basé sur le framework [Embassy](https://embassy.dev).
7//!
8//! ## Câblage minimal
9//!
10//! ```text
11//! RP2040          HC-05
12//! ──────          ─────
13//! TX (GP0)  ───►  RX
14//! RX (GP1)  ◄───  TX
15//! 3.3V      ───►  VCC  (ou 5V selon module)
16//! GND       ───►  GND
17//! GP2 (opt) ◄───  STATE  (HIGH = connecté)
18//! ```
19//!
20//! ## Utilisation rapide
21//!
22//! ```rust,ignore
23//! let uart = Uart::new(p.UART0, p.PIN_0, p.PIN_1, Irqs, p.DMA_CH0, p.DMA_CH1, uart_config);
24//! let mut bt = BluetoothHandler::new(uart, None);
25//! bt.send_line("Hello!").await.unwrap();
26//! ```
27
28#![no_std]
29#![forbid(unsafe_code)]
30
31use embassy_rp::gpio::Input;
32use embassy_rp::uart::{Async, Error as UartError, Uart};
33
34/// Erreurs possibles du wrapper HC-05.
35#[derive(Debug)]
36pub enum BluetoothError {
37    /// Erreur UART sous-jacente propagée depuis Embassy.
38    Uart(UartError),
39}
40
41impl From<UartError> for BluetoothError {
42    fn from(e: UartError) -> Self {
43        BluetoothError::Uart(e)
44    }
45}
46
47/// Wrapper principal pour le module HC-05.
48///
49/// Encapsule un [`Uart`] Embassy en mode asynchrone et, optionnellement,
50/// une pin `STATE` pour détecter la connexion Bluetooth active.
51pub struct BluetoothHandler<'d> {
52    uart: Uart<'d, Async>,
53    state_pin: Option<Input<'d>>,
54}
55
56impl<'d> BluetoothHandler<'d> {
57    /// Crée un nouveau `BluetoothHandler`.
58    ///
59    /// # Arguments
60    ///
61    /// * `uart`      – Instance [`Uart`] Embassy configurée (baudrate 9600 par défaut pour HC-05).
62    /// * `state_pin` – Pin `STATE` du HC-05 (`Some(pin)`) ou `None` si non câblée.
63    ///
64    /// # Exemple
65    ///
66    /// ```rust,ignore
67    /// let mut bt = BluetoothHandler::new(uart, Some(state_input));
68    /// ```
69    pub fn new(uart: Uart<'d, Async>, state_pin: Option<Input<'d>>) -> Self {
70        Self { uart, state_pin }
71    }
72
73    /// Retourne `true` si le HC-05 indique une connexion Bluetooth active.
74    ///
75    /// - Si la pin `STATE` est câblée : lit son état logique (`HIGH` = connecté).
76    /// - Si aucune pin n'est fournie : suppose toujours connecté (`true`).
77    ///
78    /// La pin `STATE` du HC-05 passe à `HIGH` (~3.3 V) quand un appareil
79    /// est appairé et connecté.
80    pub fn is_connected(&self) -> bool {
81        match &self.state_pin {
82            Some(pin) => pin.is_high(),
83            None => true,
84        }
85    }
86
87    /// Envoie une chaîne de caractères brute via UART.
88    ///
89    /// # Erreurs
90    ///
91    /// Retourne [`BluetoothError::Uart`] en cas d'échec de transmission.
92    ///
93    /// # Exemple
94    ///
95    /// ```rust,ignore
96    /// bt.send("OK").await?;
97    /// ```
98    pub async fn send(&mut self, message: &str) -> Result<(), BluetoothError> {
99        self.uart
100            .write(message.as_bytes())
101            .await
102            .map_err(BluetoothError::Uart)
103    }
104
105    /// Envoie un entier signé 16 bits (`i16`) converti en texte ASCII.
106    ///
107    /// Utilise [`itoa`] pour la conversion sans allocation.
108    ///
109    /// # Exemple
110    ///
111    /// ```rust,ignore
112    /// bt.send_i16(-1234).await?;  // envoie "-1234"
113    /// ```
114    pub async fn send_i16(&mut self, val: i16) -> Result<(), BluetoothError> {
115        let mut buffer = itoa::Buffer::new();
116        let text = buffer.format(val);
117        self.send(text).await
118    }
119
120    /// Envoie un entier non signé 16 bits (`u16`) converti en texte ASCII.
121    ///
122    /// Pratique pour envoyer des valeurs ADC brutes (0–4095 ou 0–16383).
123    ///
124    /// # Exemple
125    ///
126    /// ```rust,ignore
127    /// bt.send_u16(3012).await?;  // envoie "3012"
128    /// ```
129    pub async fn send_u16(&mut self, val: u16) -> Result<(), BluetoothError> {
130        let mut buffer = itoa::Buffer::new();
131        let text = buffer.format(val);
132        self.send(text).await
133    }
134
135    /// Envoie un entier non signé 32 bits (`u32`) converti en texte ASCII.
136    ///
137    /// # Exemple
138    ///
139    /// ```rust,ignore
140    /// bt.send_u32(123456).await?;
141    /// ```
142    pub async fn send_u32(&mut self, val: u32) -> Result<(), BluetoothError> {
143        let mut buffer = itoa::Buffer::new();
144        let text = buffer.format(val);
145        self.send(text).await
146    }
147
148    /// Envoie un message suivi d'un retour chariot `\r\n`.
149    ///
150    /// Equivalent à [`send`](Self::send) + `"\r\n"`. Utile pour les
151    /// terminaux série ou les parsers ligne-par-ligne côté récepteur.
152    ///
153    /// # Exemple
154    ///
155    /// ```rust,ignore
156    /// bt.send_line("Bonjour").await?;  // envoie "Bonjour\r\n"
157    /// ```
158    pub async fn send_line(&mut self, message: &str) -> Result<(), BluetoothError> {
159        self.send(message).await?;
160        self.send("\r\n").await
161    }
162
163    /// Envoie un entier `i16` suivi d'un retour chariot `\r\n`.
164    ///
165    /// # Exemple
166    ///
167    /// ```rust,ignore
168    /// bt.send_i16_line(-42).await?;  // envoie "-42\r\n"
169    /// ```
170    pub async fn send_i16_line(&mut self, val: i16) -> Result<(), BluetoothError> {
171        self.send_i16(val).await?;
172        self.send("\r\n").await
173    }
174
175    /// Envoie un entier `u16` suivi d'un retour chariot `\r\n`.
176    ///
177    /// # Exemple
178    ///
179    /// ```rust,ignore
180    /// bt.send_u16_line(4095).await?;  // envoie "4095\r\n"
181    /// ```
182    pub async fn send_u16_line(&mut self, val: u16) -> Result<(), BluetoothError> {
183        self.send_u16(val).await?;
184        self.send("\r\n").await
185    }
186
187    /// Lit des octets depuis l'UART dans le buffer fourni.
188    ///
189    /// Bloque jusqu'à ce que le buffer soit rempli ou qu'une erreur survienne.
190    ///
191    /// # Exemple
192    ///
193    /// ```rust,ignore
194    /// let mut buf = [0u8; 32];
195    /// bt.read(&mut buf).await?;
196    /// ```
197    pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), BluetoothError> {
198        self.uart
199            .read(buffer)
200            .await
201            .map_err(BluetoothError::Uart)
202    }
203
204    
205    /// Lit une ligne complète depuis le module Bluetooth.
206    ///
207    /// Cette méthode remplit le buffer fourni octet par octet jusqu'à ce que :
208    /// 1. Le caractère de saut de ligne `\n` (LF) soit détecté.
209    /// 2. Le buffer soit entièrement rempli (pour éviter tout débordement).
210    ///
211    /// # Fonctionnement asynchrone
212    /// 
213    /// Contrairement à `read`, cette méthode est réactive : elle retourne dès que la 
214    /// phrase est terminée, même si le buffer est beaucoup plus grand que le message.
215    ///
216    /// # Arguments
217    ///
218    /// * `buf` Un buffer mutable pour stocker la ligne reçue.
219    ///
220    /// # Retour
221    ///
222    /// Retourne `Ok(usize)` représentant le nombre d'octets réellement écrits dans 
223    /// le buffer (incluant le caractère `\n` s'il a été trouvé).
224    ///
225    /// # Exemple
226    ///
227    /// ```rust,ignore
228    /// let mut command_buf = [0u8; 64];
229    /// if let Ok(len) = bt.read_line(&mut command_buf).await {
230    ///     let command = core::str::from_utf8(&command_buf[..len]).unwrap_or("");
231    ///     if command.contains("START") {
232    ///         // Activer le système...
233    ///     }
234    /// }
235    /// ```
236    pub async fn read_line(&mut self, buf: &mut [u8]) -> Result<usize, BluetoothError> {
237        let mut i = 0;
238        while i < buf.len() {
239            let mut byte = [0u8; 1];
240            // On utilise l'opérateur '?' pour propager l'erreur UART si elle survient
241            self.uart.read(&mut byte).await?; 
242            
243            buf[i] = byte[0];
244            
245            // Détection du caractère de fin de ligne (Line Feed)
246            if byte[0] == b'\n' {
247                return Ok(i + 1);
248            }
249            i += 1;
250        }
251        // Retourne la taille remplie si le buffer est plein avant d'avoir trouvé '\n'
252        Ok(i)
253    }
254}