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}