Skip to main content

pico_de_gallo_hal/
lib.rs

1//! [`embedded-hal`](https://docs.rs/embedded-hal) and
2//! [`embedded-hal-async`](https://docs.rs/embedded-hal-async) implementations
3//! backed by a Pico de Gallo USB bridge.
4//!
5//! This crate lets you run embedded Rust drivers on a host machine by
6//! forwarding I2C, SPI, GPIO, PWM, ADC, 1-Wire, and delay operations to a Pico de Gallo
7//! device over USB.
8//!
9//! # Quick Start
10//!
11//! ```no_run
12//! use pico_de_gallo_hal::Hal;
13//! use embedded_hal::i2c::I2c;
14//!
15//! let hal = Hal::new();
16//! let mut i2c = hal.i2c();
17//!
18//! // Read 2 bytes from a TMP102 temperature sensor
19//! let mut buf = [0u8; 2];
20//! i2c.write_read(0x48, &[0x00], &mut buf).unwrap();
21//! ```
22//!
23//! # Blocking vs. Async
24//!
25//! Both `embedded-hal` (blocking) and `embedded-hal-async` traits are
26//! implemented. The HAL automatically detects whether it is running inside a
27//! tokio runtime and adjusts its execution strategy:
28//!
29//! - **Inside tokio**: Uses [`tokio::task::block_in_place`] to avoid blocking
30//!   the async executor while waiting for USB responses.
31//! - **Outside tokio**: Blocks directly on the tokio handle.
32//!
33//! This means the same `Hal` instance works in both synchronous test code
34//! and async application code.
35//!
36//! # Implemented Traits
37//!
38//! | Peripheral | Blocking Trait | Async Trait |
39//! |------------|---------------|-------------|
40//! | GPIO | [`OutputPin`](embedded_hal::digital::OutputPin), [`InputPin`](embedded_hal::digital::InputPin), [`StatefulOutputPin`](embedded_hal::digital::StatefulOutputPin) | [`Wait`](embedded_hal_async::digital::Wait) |
41//! | I2C | [`I2c`](embedded_hal::i2c::I2c) | [`I2c`](embedded_hal_async::i2c::I2c) |
42//! | SPI | [`SpiBus`](embedded_hal::spi::SpiBus), [`SpiDevice`](embedded_hal::spi::SpiDevice) | [`SpiBus`](embedded_hal_async::spi::SpiBus), [`SpiDevice`](embedded_hal_async::spi::SpiDevice) |
43//! | PWM | [`SetDutyCycle`](embedded_hal::pwm::SetDutyCycle) | — |
44//! | Delay | [`DelayNs`](embedded_hal::delay::DelayNs) | [`DelayNs`](embedded_hal_async::delay::DelayNs) |
45
46use pico_de_gallo_lib::{
47    AdcChannel, AdcConfigurationInfo, AdcError, GpioDirection, GpioEdge, GpioError, GpioPull,
48    GpioState, I2cError, OneWireError, PicoDeGallo, PicoDeGalloError, PwmError, SpiError,
49    UartError,
50};
51use std::sync::Arc;
52use tokio::runtime::{Handle, Runtime};
53use tokio::sync::Mutex;
54use tokio::task::block_in_place;
55
56pub use pico_de_gallo_lib::{
57    GpioEvent, I2cFrequency, SpiConfigurationInfo, SpiPhase, SpiPolarity, UartConfigurationInfo,
58};
59
60/// Top-level HAL context for a Pico de Gallo device.
61///
62/// Holds the USB connection and tokio runtime handle. Create peripheral
63/// handles using the accessor methods: [`gpio`](Self::gpio),
64/// [`i2c`](Self::i2c), [`spi`](Self::spi), [`delay`](Self::delay).
65pub struct Hal {
66    gallo: Arc<Mutex<PicoDeGallo>>,
67    _runtime: Option<Runtime>,
68    handle: Handle,
69}
70
71impl Default for Hal {
72    fn default() -> Self {
73        Self::new()
74    }
75}
76
77impl Hal {
78    /// Instantiate the library context.
79    pub fn new() -> Self {
80        Self::new_inner(None)
81    }
82
83    /// Instantiate the library context for the device with the given
84    /// `serial_number`.
85    pub fn new_with_serial_number(serial_number: &str) -> Self {
86        Self::new_inner(Some(serial_number))
87    }
88
89    fn new_inner(serial_number: Option<&str>) -> Self {
90        let (runtime, handle) = match Handle::try_current() {
91            Ok(handle) => (None, handle),
92            Err(_) => {
93                let runtime = Runtime::new().unwrap();
94                let handle = runtime.handle().clone();
95                (Some(runtime), handle)
96            }
97        };
98
99        let gallo = if runtime.is_none() {
100            if let Some(serial_number) = serial_number {
101                PicoDeGallo::new_with_serial_number(serial_number)
102            } else {
103                PicoDeGallo::new()
104            }
105        } else {
106            handle.block_on(async {
107                if let Some(serial_number) = serial_number {
108                    PicoDeGallo::new_with_serial_number(serial_number)
109                } else {
110                    PicoDeGallo::new()
111                }
112            })
113        };
114
115        Self {
116            gallo: Arc::new(Mutex::new(gallo)),
117            _runtime: runtime,
118            handle,
119        }
120    }
121
122    /// Set I2C bus configuration parameters.
123    pub fn i2c_set_config(&mut self, frequency: I2cFrequency) -> Result<(), I2cHalError> {
124        if Self::in_async_context() {
125            block_in_place(|| self.i2c_set_config_inner(frequency))
126        } else {
127            self.i2c_set_config_inner(frequency)
128        }
129    }
130
131    fn i2c_set_config_inner(&mut self, frequency: I2cFrequency) -> Result<(), I2cHalError> {
132        let handle = self.handle.clone();
133        let gallo = handle.block_on(self.gallo.lock());
134        handle
135            .block_on(gallo.i2c_set_config(frequency))
136            .map_err(I2cHalError::from)
137    }
138
139    /// Scan the I2C bus and return the addresses of all responding devices.
140    ///
141    /// The firmware probes each 7-bit address by attempting a 1-byte read.
142    /// When `include_reserved` is `false`, only the standard range (0x08–0x77)
143    /// is probed; when `true`, the full range (0x00–0x7F) is scanned.
144    pub fn i2c_scan(&self, include_reserved: bool) -> Result<Vec<u8>, I2cHalError> {
145        if Self::in_async_context() {
146            block_in_place(|| self.i2c_scan_inner(include_reserved))
147        } else {
148            self.i2c_scan_inner(include_reserved)
149        }
150    }
151
152    fn i2c_scan_inner(&self, include_reserved: bool) -> Result<Vec<u8>, I2cHalError> {
153        let handle = self.handle.clone();
154        let gallo = handle.block_on(self.gallo.lock());
155        handle
156            .block_on(gallo.i2c_scan(include_reserved))
157            .map_err(I2cHalError::from)
158    }
159
160    /// Set SPI bus configuration parameters.
161    pub fn spi_set_config(
162        &mut self,
163        spi_frequency: u32,
164        spi_phase: SpiPhase,
165        spi_polarity: SpiPolarity,
166    ) -> Result<(), SpiHalError> {
167        if Self::in_async_context() {
168            block_in_place(|| self.spi_set_config_inner(spi_frequency, spi_phase, spi_polarity))
169        } else {
170            self.spi_set_config_inner(spi_frequency, spi_phase, spi_polarity)
171        }
172    }
173
174    fn spi_set_config_inner(
175        &mut self,
176        spi_frequency: u32,
177        spi_phase: SpiPhase,
178        spi_polarity: SpiPolarity,
179    ) -> Result<(), SpiHalError> {
180        let handle = self.handle.clone();
181        let gallo = handle.block_on(self.gallo.lock());
182        handle
183            .block_on(gallo.spi_set_config(spi_frequency, spi_phase, spi_polarity))
184            .map_err(SpiHalError::from)
185    }
186
187    /// Query the current I2C bus configuration.
188    ///
189    /// Returns the [`I2cFrequency`] value active on the firmware
190    /// (default: `Standard` / 100 kHz).
191    pub fn i2c_get_config(&self) -> Result<I2cFrequency, I2cHalError> {
192        if Self::in_async_context() {
193            block_in_place(|| self.i2c_get_config_inner())
194        } else {
195            self.i2c_get_config_inner()
196        }
197    }
198
199    fn i2c_get_config_inner(&self) -> Result<I2cFrequency, I2cHalError> {
200        let handle = self.handle.clone();
201        let gallo = handle.block_on(self.gallo.lock());
202        handle
203            .block_on(gallo.i2c_get_config())
204            .map_err(|e| match e {
205                PicoDeGalloError::Comms(c) => I2cHalError::Comms(format!("{c:?}")),
206                PicoDeGalloError::Endpoint(never) => match never {},
207            })
208    }
209
210    /// Query the current SPI bus configuration.
211    ///
212    /// Returns a [`SpiConfigurationInfo`] with the active frequency, phase,
213    /// and polarity (defaults: 1 MHz, `CaptureOnFirstTransition`, `IdleLow`).
214    pub fn spi_get_config(&self) -> Result<SpiConfigurationInfo, SpiHalError> {
215        if Self::in_async_context() {
216            block_in_place(|| self.spi_get_config_inner())
217        } else {
218            self.spi_get_config_inner()
219        }
220    }
221
222    fn spi_get_config_inner(&self) -> Result<SpiConfigurationInfo, SpiHalError> {
223        let handle = self.handle.clone();
224        let gallo = handle.block_on(self.gallo.lock());
225        handle
226            .block_on(gallo.spi_get_config())
227            .map_err(|e| match e {
228                PicoDeGalloError::Comms(c) => SpiHalError::Comms(format!("{c:?}")),
229                PicoDeGalloError::Endpoint(never) => match never {},
230            })
231    }
232
233    /// Set the PWM configuration for a channel's slice.
234    ///
235    /// Configures the output `frequency_hz` and `phase_correct` mode.
236    /// Existing duty-cycle values are scaled proportionally.
237    pub fn pwm_set_config(
238        &mut self,
239        channel: u8,
240        frequency_hz: u32,
241        phase_correct: bool,
242    ) -> Result<(), PwmHalError> {
243        if Self::in_async_context() {
244            block_in_place(|| self.pwm_set_config_inner(channel, frequency_hz, phase_correct))
245        } else {
246            self.pwm_set_config_inner(channel, frequency_hz, phase_correct)
247        }
248    }
249
250    fn pwm_set_config_inner(
251        &self,
252        channel: u8,
253        frequency_hz: u32,
254        phase_correct: bool,
255    ) -> Result<(), PwmHalError> {
256        let handle = self.handle.clone();
257        let gallo = handle.block_on(self.gallo.lock());
258        handle
259            .block_on(gallo.pwm_set_config(channel, frequency_hz, phase_correct))
260            .map_err(PwmHalError::from)
261    }
262
263    /// Query the current PWM configuration for a channel's slice.
264    pub fn pwm_get_config(
265        &self,
266        channel: u8,
267    ) -> Result<pico_de_gallo_lib::PwmConfigurationInfo, PwmHalError> {
268        if Self::in_async_context() {
269            block_in_place(|| self.pwm_get_config_inner(channel))
270        } else {
271            self.pwm_get_config_inner(channel)
272        }
273    }
274
275    fn pwm_get_config_inner(
276        &self,
277        channel: u8,
278    ) -> Result<pico_de_gallo_lib::PwmConfigurationInfo, PwmHalError> {
279        let handle = self.handle.clone();
280        let gallo = handle.block_on(self.gallo.lock());
281        handle
282            .block_on(gallo.pwm_get_config(channel))
283            .map_err(PwmHalError::from)
284    }
285
286    /// Gpio
287    pub fn gpio(&self, pin: u8) -> Gpio {
288        let gallo = Arc::clone(&self.gallo);
289        let handle = self.handle.clone();
290        Gpio { pin, gallo, handle }
291    }
292
293    /// I2c
294    pub fn i2c(&self) -> I2c {
295        let gallo = Arc::clone(&self.gallo);
296        let handle = self.handle.clone();
297        I2c { gallo, handle }
298    }
299
300    /// Spi
301    pub fn spi(&self) -> Spi {
302        let gallo = Arc::clone(&self.gallo);
303        let handle = self.handle.clone();
304        Spi { gallo, handle }
305    }
306
307    /// Uart
308    pub fn uart(&self) -> Uart {
309        let gallo = Arc::clone(&self.gallo);
310        let handle = self.handle.clone();
311        Uart {
312            gallo,
313            handle,
314            timeout_ms: 1000,
315        }
316    }
317
318    /// Obtain a [`PwmChannel`] handle for the given channel (0–3).
319    ///
320    /// Channels 0–1 are on PWM slice 6 (GPIO 12–13), channels 2–3 on
321    /// slice 7 (GPIO 14–15). The returned handle implements
322    /// [`SetDutyCycle`](embedded_hal::pwm::SetDutyCycle).
323    pub fn pwm_channel(&self, channel: u8) -> PwmChannel {
324        let gallo = Arc::clone(&self.gallo);
325        let handle = self.handle.clone();
326        PwmChannel {
327            channel,
328            gallo,
329            handle,
330        }
331    }
332
333    /// Perform a single-shot ADC read on the specified channel.
334    ///
335    /// Returns a raw 12-bit value (0–4095). Convert to approximate voltage
336    /// with `V ≈ raw × 3.3 / 4096`.
337    ///
338    /// There is no standard `embedded-hal` ADC trait in 1.0, so this is
339    /// exposed as a project-specific method.
340    pub fn adc_read(&self, channel: AdcChannel) -> Result<u16, AdcHalError> {
341        if Self::in_async_context() {
342            block_in_place(|| self.adc_read_inner(channel))
343        } else {
344            self.adc_read_inner(channel)
345        }
346    }
347
348    fn adc_read_inner(&self, channel: AdcChannel) -> Result<u16, AdcHalError> {
349        let handle = self.handle.clone();
350        let gallo = handle.block_on(self.gallo.lock());
351        handle
352            .block_on(gallo.adc_read(channel))
353            .map_err(AdcHalError::from)
354    }
355
356    /// Query the ADC configuration (resolution, reference, channel count).
357    pub fn adc_get_config(&self) -> Result<AdcConfigurationInfo, AdcHalError> {
358        if Self::in_async_context() {
359            block_in_place(|| self.adc_get_config_inner())
360        } else {
361            self.adc_get_config_inner()
362        }
363    }
364
365    fn adc_get_config_inner(&self) -> Result<AdcConfigurationInfo, AdcHalError> {
366        let handle = self.handle.clone();
367        let gallo = handle.block_on(self.gallo.lock());
368        handle
369            .block_on(gallo.adc_get_config())
370            .map_err(|e| match e {
371                PicoDeGalloError::Comms(c) => AdcHalError::Comms(format!("{c:?}")),
372                PicoDeGalloError::Endpoint(e) => AdcHalError::Adc(e),
373            })
374    }
375
376    /// Subscribe to GPIO edge events on a pin.
377    ///
378    /// Starts push-based monitoring for the specified edge type. While subscribed,
379    /// the pin cannot be used by other GPIO operations. Use
380    /// [`gpio_unsubscribe`](Self::gpio_unsubscribe) to release the pin.
381    pub fn gpio_subscribe(&self, pin: u8, edge: GpioEdge) -> Result<(), GpioHalError> {
382        if Self::in_async_context() {
383            block_in_place(|| self.gpio_subscribe_inner(pin, edge))
384        } else {
385            self.gpio_subscribe_inner(pin, edge)
386        }
387    }
388
389    fn gpio_subscribe_inner(&self, pin: u8, edge: GpioEdge) -> Result<(), GpioHalError> {
390        let handle = self.handle.clone();
391        let gallo = handle.block_on(self.gallo.lock());
392        handle
393            .block_on(gallo.gpio_subscribe(pin, edge))
394            .map_err(GpioHalError::from)
395    }
396
397    /// Unsubscribe from GPIO edge events on a pin.
398    ///
399    /// Stops monitoring and returns the pin to normal operation.
400    pub fn gpio_unsubscribe(&self, pin: u8) -> Result<(), GpioHalError> {
401        if Self::in_async_context() {
402            block_in_place(|| self.gpio_unsubscribe_inner(pin))
403        } else {
404            self.gpio_unsubscribe_inner(pin)
405        }
406    }
407
408    fn gpio_unsubscribe_inner(&self, pin: u8) -> Result<(), GpioHalError> {
409        let handle = self.handle.clone();
410        let gallo = handle.block_on(self.gallo.lock());
411        handle
412            .block_on(gallo.gpio_unsubscribe(pin))
413            .map_err(GpioHalError::from)
414    }
415
416    /// Create an [`SpiDevice`] that manages chip-select on `cs_pin`.
417    ///
418    /// The CS pin is driven high (deasserted) immediately. Each
419    /// [`SpiDevice::transaction`](embedded_hal::spi::SpiDevice::transaction)
420    /// call will assert CS low, perform the operations, flush, then deassert
421    /// CS high.
422    ///
423    /// # Errors
424    ///
425    /// Returns `SpiHalError` if the initial CS-high drive fails (e.g. the
426    /// device is not connected or `cs_pin` is out of range).
427    pub fn spi_device(&self, cs_pin: u8) -> Result<SpiDev, SpiHalError> {
428        let gallo = Arc::clone(&self.gallo);
429        let handle = self.handle.clone();
430
431        // Drive CS high so the line starts deasserted.
432        let guard = handle.block_on(gallo.lock());
433        handle
434            .block_on(guard.gpio_put(cs_pin, GpioState::High))
435            .map_err(|e| SpiHalError::Comms(format!("CS init failed: {e:?}")))?;
436        drop(guard);
437
438        Ok(SpiDev {
439            gallo,
440            handle,
441            cs_pin,
442        })
443    }
444
445    /// Delay
446    pub fn delay(&self) -> Delay {
447        Delay
448    }
449
450    /// Create a 1-Wire bus handle.
451    ///
452    /// The returned [`OneWire`] provides blocking methods for 1-Wire bus operations
453    /// (reset, read, write, search). There is no embedded-hal trait for 1-Wire, so
454    /// this is a custom API.
455    pub fn onewire(&self) -> OneWire {
456        OneWire {
457            gallo: self.gallo.clone(),
458            handle: self.handle.clone(),
459        }
460    }
461
462    /// Returns true if we are currently inside a tokio async context.
463    fn in_async_context() -> bool {
464        Handle::try_current().is_ok()
465    }
466}
467
468// ----------------------------- Error Types -----------------------------
469
470/// Error type for GPIO HAL operations.
471#[derive(Debug)]
472pub enum GpioHalError {
473    /// A GPIO-specific error from the device firmware.
474    Gpio(GpioError),
475    /// A USB communication error.
476    Comms(String),
477}
478
479impl core::fmt::Display for GpioHalError {
480    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
481        match self {
482            Self::Gpio(e) => write!(f, "{e}"),
483            Self::Comms(msg) => write!(f, "communication error: {msg}"),
484        }
485    }
486}
487
488impl std::error::Error for GpioHalError {}
489
490impl From<PicoDeGalloError<GpioError>> for GpioHalError {
491    fn from(e: PicoDeGalloError<GpioError>) -> Self {
492        match e {
493            PicoDeGalloError::Endpoint(e) => Self::Gpio(e),
494            PicoDeGalloError::Comms(c) => Self::Comms(format!("{c:?}")),
495        }
496    }
497}
498
499impl embedded_hal::digital::Error for GpioHalError {
500    fn kind(&self) -> embedded_hal::digital::ErrorKind {
501        embedded_hal::digital::ErrorKind::Other
502    }
503}
504
505/// Error type for I2C HAL operations.
506#[derive(Debug)]
507pub enum I2cHalError {
508    /// An I2C-specific error from the device firmware.
509    I2c(I2cError),
510    /// A USB communication error.
511    Comms(String),
512}
513
514impl core::fmt::Display for I2cHalError {
515    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
516        match self {
517            Self::I2c(e) => write!(f, "{e}"),
518            Self::Comms(msg) => write!(f, "communication error: {msg}"),
519        }
520    }
521}
522
523impl std::error::Error for I2cHalError {}
524
525impl From<PicoDeGalloError<I2cError>> for I2cHalError {
526    fn from(e: PicoDeGalloError<I2cError>) -> Self {
527        match e {
528            PicoDeGalloError::Endpoint(e) => Self::I2c(e),
529            PicoDeGalloError::Comms(c) => Self::Comms(format!("{c:?}")),
530        }
531    }
532}
533
534impl From<PicoDeGalloError<pico_de_gallo_lib::I2cBatchError>> for I2cHalError {
535    fn from(e: PicoDeGalloError<pico_de_gallo_lib::I2cBatchError>) -> Self {
536        match e {
537            PicoDeGalloError::Endpoint(batch_err) => Self::I2c(batch_err.kind),
538            PicoDeGalloError::Comms(c) => Self::Comms(format!("{c:?}")),
539        }
540    }
541}
542
543impl embedded_hal::i2c::Error for I2cHalError {
544    fn kind(&self) -> embedded_hal::i2c::ErrorKind {
545        match self {
546            Self::I2c(I2cError::NoAcknowledge) => embedded_hal::i2c::ErrorKind::NoAcknowledge(
547                embedded_hal::i2c::NoAcknowledgeSource::Unknown,
548            ),
549            Self::I2c(I2cError::ArbitrationLoss) => embedded_hal::i2c::ErrorKind::ArbitrationLoss,
550            Self::I2c(I2cError::Bus) => embedded_hal::i2c::ErrorKind::Bus,
551            Self::I2c(I2cError::Overrun) => embedded_hal::i2c::ErrorKind::Overrun,
552            _ => embedded_hal::i2c::ErrorKind::Other,
553        }
554    }
555}
556
557/// Error type for SPI HAL operations.
558#[derive(Debug)]
559pub enum SpiHalError {
560    /// An SPI-specific error from the device firmware.
561    Spi(SpiError),
562    /// A USB communication error.
563    Comms(String),
564}
565
566impl core::fmt::Display for SpiHalError {
567    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
568        match self {
569            Self::Spi(e) => write!(f, "{e}"),
570            Self::Comms(msg) => write!(f, "communication error: {msg}"),
571        }
572    }
573}
574
575impl std::error::Error for SpiHalError {}
576
577impl From<PicoDeGalloError<SpiError>> for SpiHalError {
578    fn from(e: PicoDeGalloError<SpiError>) -> Self {
579        match e {
580            PicoDeGalloError::Endpoint(e) => Self::Spi(e),
581            PicoDeGalloError::Comms(c) => Self::Comms(format!("{c:?}")),
582        }
583    }
584}
585
586impl From<PicoDeGalloError<pico_de_gallo_lib::SpiBatchError>> for SpiHalError {
587    fn from(e: PicoDeGalloError<pico_de_gallo_lib::SpiBatchError>) -> Self {
588        match e {
589            PicoDeGalloError::Endpoint(batch_err) => Self::Spi(batch_err.kind),
590            PicoDeGalloError::Comms(c) => Self::Comms(format!("{c:?}")),
591        }
592    }
593}
594
595impl embedded_hal::spi::Error for SpiHalError {
596    fn kind(&self) -> embedded_hal::spi::ErrorKind {
597        embedded_hal::spi::ErrorKind::Other
598    }
599}
600
601/// Error type for UART HAL operations.
602#[derive(Debug)]
603pub enum UartHalError {
604    /// A UART-specific error from the device firmware.
605    Uart(UartError),
606    /// A USB communication error.
607    Comms(String),
608}
609
610impl core::fmt::Display for UartHalError {
611    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
612        match self {
613            Self::Uart(e) => write!(f, "{e}"),
614            Self::Comms(msg) => write!(f, "communication error: {msg}"),
615        }
616    }
617}
618
619impl std::error::Error for UartHalError {}
620
621impl From<PicoDeGalloError<UartError>> for UartHalError {
622    fn from(e: PicoDeGalloError<UartError>) -> Self {
623        match e {
624            PicoDeGalloError::Endpoint(e) => Self::Uart(e),
625            PicoDeGalloError::Comms(c) => Self::Comms(format!("{c:?}")),
626        }
627    }
628}
629
630impl embedded_io::Error for UartHalError {
631    fn kind(&self) -> embedded_io::ErrorKind {
632        match self {
633            Self::Uart(UartError::Overrun) => embedded_io::ErrorKind::Other,
634            Self::Uart(UartError::Break) => embedded_io::ErrorKind::Other,
635            Self::Uart(UartError::Parity) => embedded_io::ErrorKind::Other,
636            Self::Uart(UartError::Framing) => embedded_io::ErrorKind::Other,
637            Self::Uart(UartError::InvalidBaudRate) => embedded_io::ErrorKind::InvalidInput,
638            _ => embedded_io::ErrorKind::Other,
639        }
640    }
641}
642
643// ----------------------------- Gpio -----------------------------
644
645/// GPIO pin handle implementing [`embedded-hal`] digital traits.
646///
647/// Obtained from [`Hal::gpio`]. Each `Gpio` instance is bound to a specific
648/// pin number (0–3) and can be used as both an input and output.
649pub struct Gpio {
650    pin: u8,
651    gallo: Arc<Mutex<PicoDeGallo>>,
652    handle: Handle,
653}
654
655impl Gpio {
656    /// Configure the pin's direction and internal pull resistor.
657    ///
658    /// After configuration, `set_low`/`set_high` on an input pin or
659    /// `is_low`/`is_high` on an output pin will return
660    /// [`GpioHalError::Gpio(GpioError::WrongDirection)`].
661    pub fn set_config(
662        &mut self,
663        direction: GpioDirection,
664        pull: GpioPull,
665    ) -> std::result::Result<(), GpioHalError> {
666        if Hal::in_async_context() {
667            block_in_place(|| self.set_config_inner(direction, pull))
668        } else {
669            self.set_config_inner(direction, pull)
670        }
671    }
672
673    fn set_config_inner(
674        &mut self,
675        direction: GpioDirection,
676        pull: GpioPull,
677    ) -> std::result::Result<(), GpioHalError> {
678        let handle = &self.handle;
679        let gallo = handle.block_on(self.gallo.lock());
680        handle
681            .block_on(gallo.gpio_set_config(self.pin, direction, pull))
682            .map_err(GpioHalError::from)
683    }
684
685    fn set_low_inner(&mut self) -> std::result::Result<(), GpioHalError> {
686        let handle = &self.handle;
687        let gallo = handle.block_on(self.gallo.lock());
688        handle
689            .block_on(gallo.gpio_put(self.pin, GpioState::Low))
690            .map_err(GpioHalError::from)
691    }
692
693    fn set_high_inner(&mut self) -> std::result::Result<(), GpioHalError> {
694        let handle = &self.handle;
695        let gallo = handle.block_on(self.gallo.lock());
696        handle
697            .block_on(gallo.gpio_put(self.pin, GpioState::High))
698            .map_err(GpioHalError::from)
699    }
700
701    fn is_low_inner(&mut self) -> std::result::Result<bool, GpioHalError> {
702        let handle = &self.handle;
703        let gallo = handle.block_on(self.gallo.lock());
704        handle
705            .block_on(gallo.gpio_get(self.pin))
706            .map_err(GpioHalError::from)
707            .map(|s| s == GpioState::Low)
708    }
709
710    fn is_high_inner(&mut self) -> std::result::Result<bool, GpioHalError> {
711        let handle = &self.handle;
712        let gallo = handle.block_on(self.gallo.lock());
713        handle
714            .block_on(gallo.gpio_get(self.pin))
715            .map_err(GpioHalError::from)
716            .map(|s| s == GpioState::High)
717    }
718}
719
720impl embedded_hal::digital::ErrorType for Gpio {
721    type Error = GpioHalError;
722}
723
724impl embedded_hal::digital::OutputPin for Gpio {
725    fn set_low(&mut self) -> std::result::Result<(), Self::Error> {
726        if Hal::in_async_context() {
727            block_in_place(|| self.set_low_inner())
728        } else {
729            self.set_low_inner()
730        }
731    }
732
733    fn set_high(&mut self) -> std::result::Result<(), Self::Error> {
734        if Hal::in_async_context() {
735            block_in_place(|| self.set_high_inner())
736        } else {
737            self.set_high_inner()
738        }
739    }
740}
741
742impl embedded_hal::digital::InputPin for Gpio {
743    fn is_low(&mut self) -> std::result::Result<bool, Self::Error> {
744        if Hal::in_async_context() {
745            block_in_place(|| self.is_low_inner())
746        } else {
747            self.is_low_inner()
748        }
749    }
750
751    fn is_high(&mut self) -> std::result::Result<bool, Self::Error> {
752        if Hal::in_async_context() {
753            block_in_place(|| self.is_high_inner())
754        } else {
755            self.is_high_inner()
756        }
757    }
758}
759
760impl embedded_hal::digital::StatefulOutputPin for Gpio {
761    fn is_set_low(&mut self) -> std::result::Result<bool, Self::Error> {
762        self.is_low_inner()
763    }
764
765    fn is_set_high(&mut self) -> std::result::Result<bool, Self::Error> {
766        self.is_high_inner()
767    }
768}
769
770impl embedded_hal_async::digital::Wait for Gpio {
771    async fn wait_for_high(&mut self) -> Result<(), Self::Error> {
772        let gallo = self.gallo.lock().await;
773        gallo
774            .gpio_wait_for_high(self.pin)
775            .await
776            .map_err(GpioHalError::from)
777    }
778
779    async fn wait_for_low(&mut self) -> Result<(), Self::Error> {
780        let gallo = self.gallo.lock().await;
781        gallo
782            .gpio_wait_for_low(self.pin)
783            .await
784            .map_err(GpioHalError::from)
785    }
786
787    async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> {
788        let gallo = self.gallo.lock().await;
789        gallo
790            .gpio_wait_for_rising_edge(self.pin)
791            .await
792            .map_err(GpioHalError::from)
793    }
794
795    async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> {
796        let gallo = self.gallo.lock().await;
797        gallo
798            .gpio_wait_for_falling_edge(self.pin)
799            .await
800            .map_err(GpioHalError::from)
801    }
802
803    async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> {
804        let gallo = self.gallo.lock().await;
805        gallo
806            .gpio_wait_for_any_edge(self.pin)
807            .await
808            .map_err(GpioHalError::from)
809    }
810}
811
812// ----------------------------- I2c -----------------------------
813
814/// I2C bus handle implementing [`embedded-hal`] I2C traits.
815///
816/// Obtained from [`Hal::i2c`]. Supports 7-bit addressing. The I2C bus clock
817/// frequency can be changed at runtime with [`Hal::i2c_set_config`].
818pub struct I2c {
819    gallo: Arc<Mutex<PicoDeGallo>>,
820    handle: Handle,
821}
822
823impl I2c {
824    fn transaction_inner(
825        &mut self,
826        address: embedded_hal::i2c::SevenBitAddress,
827        operations: &mut [embedded_hal::i2c::Operation<'_>],
828    ) -> std::result::Result<(), I2cHalError> {
829        use pico_de_gallo_lib::I2cBatchOp;
830
831        let handle = &self.handle;
832        let gallo = handle.block_on(self.gallo.lock());
833
834        // Build batch ops
835        let batch_ops: Vec<I2cBatchOp<'_>> = operations
836            .iter()
837            .map(|op| match op {
838                embedded_hal::i2c::Operation::Read(buf) => I2cBatchOp::Read {
839                    len: buf.len() as u16,
840                },
841                embedded_hal::i2c::Operation::Write(data) => I2cBatchOp::Write { data },
842            })
843            .collect();
844
845        let result = handle
846            .block_on(gallo.i2c_batch(address, &batch_ops))
847            .map_err(I2cHalError::from)?;
848
849        // Distribute read data back into the caller's buffers
850        let mut offset = 0usize;
851        for op in operations.iter_mut() {
852            if let embedded_hal::i2c::Operation::Read(buf) = op {
853                let len = buf.len();
854                buf.copy_from_slice(&result[offset..offset + len]);
855                offset += len;
856            }
857        }
858
859        Ok(())
860    }
861}
862
863impl embedded_hal::i2c::ErrorType for I2c {
864    type Error = I2cHalError;
865}
866
867impl embedded_hal::i2c::I2c<embedded_hal::i2c::SevenBitAddress> for I2c {
868    fn transaction(
869        &mut self,
870        address: embedded_hal::i2c::SevenBitAddress,
871        operations: &mut [embedded_hal::i2c::Operation<'_>],
872    ) -> std::result::Result<(), Self::Error> {
873        if Hal::in_async_context() {
874            block_in_place(|| self.transaction_inner(address, operations))
875        } else {
876            self.transaction_inner(address, operations)
877        }
878    }
879}
880
881impl embedded_hal_async::i2c::I2c<embedded_hal_async::i2c::SevenBitAddress> for I2c {
882    async fn transaction(
883        &mut self,
884        address: embedded_hal_async::i2c::SevenBitAddress,
885        operations: &mut [embedded_hal_async::i2c::Operation<'_>],
886    ) -> std::result::Result<(), Self::Error> {
887        use pico_de_gallo_lib::I2cBatchOp;
888
889        let gallo = self.gallo.lock().await;
890
891        let batch_ops: Vec<I2cBatchOp<'_>> = operations
892            .iter()
893            .map(|op| match op {
894                embedded_hal_async::i2c::Operation::Read(buf) => I2cBatchOp::Read {
895                    len: buf.len() as u16,
896                },
897                embedded_hal_async::i2c::Operation::Write(data) => I2cBatchOp::Write { data },
898            })
899            .collect();
900
901        let result = gallo
902            .i2c_batch(address, &batch_ops)
903            .await
904            .map_err(I2cHalError::from)?;
905
906        let mut offset = 0usize;
907        for op in operations.iter_mut() {
908            if let embedded_hal_async::i2c::Operation::Read(buf) = op {
909                let len = buf.len();
910                buf.copy_from_slice(&result[offset..offset + len]);
911                offset += len;
912            }
913        }
914
915        Ok(())
916    }
917}
918
919// ----------------------------- Spi -----------------------------
920
921/// SPI bus handle implementing [`embedded-hal`] SPI traits.
922///
923/// Obtained from [`Hal::spi`]. Supports full-duplex transfers. The SPI clock
924/// frequency, phase, and polarity can be changed at runtime with
925/// [`Hal::spi_set_config`].
926pub struct Spi {
927    gallo: Arc<Mutex<PicoDeGallo>>,
928    handle: Handle,
929}
930
931impl Spi {
932    fn read_inner(&mut self, words: &mut [u8]) -> std::result::Result<(), SpiHalError> {
933        let handle = &self.handle;
934        let gallo = handle.block_on(self.gallo.lock());
935        let contents = handle
936            .block_on(gallo.spi_read(words.len() as u16))
937            .map_err(SpiHalError::from)?;
938        words.copy_from_slice(&contents);
939        Ok(())
940    }
941
942    fn write_inner(&mut self, words: &[u8]) -> std::result::Result<(), SpiHalError> {
943        let handle = &self.handle;
944        let gallo = handle.block_on(self.gallo.lock());
945        handle
946            .block_on(gallo.spi_write(words))
947            .map_err(SpiHalError::from)
948    }
949
950    fn transfer_inner(
951        &mut self,
952        read: &mut [u8],
953        write: &[u8],
954    ) -> std::result::Result<(), SpiHalError> {
955        let handle = &self.handle;
956        let gallo = handle.block_on(self.gallo.lock());
957        let contents = handle
958            .block_on(gallo.spi_transfer(write))
959            .map_err(SpiHalError::from)?;
960        let len = read.len().min(contents.len());
961        read[..len].copy_from_slice(&contents[..len]);
962        Ok(())
963    }
964
965    fn flush_inner(&mut self) -> std::result::Result<(), SpiHalError> {
966        let handle = &self.handle;
967        let gallo = handle.block_on(self.gallo.lock());
968        handle
969            .block_on(gallo.spi_flush())
970            .map_err(SpiHalError::from)
971    }
972}
973
974impl embedded_hal::spi::ErrorType for Spi {
975    type Error = SpiHalError;
976}
977
978impl embedded_hal::spi::SpiBus for Spi {
979    fn read(&mut self, words: &mut [u8]) -> std::result::Result<(), Self::Error> {
980        if Hal::in_async_context() {
981            block_in_place(|| self.read_inner(words))
982        } else {
983            self.read_inner(words)
984        }
985    }
986
987    fn write(&mut self, words: &[u8]) -> std::result::Result<(), Self::Error> {
988        if Hal::in_async_context() {
989            block_in_place(|| self.write_inner(words))
990        } else {
991            self.write_inner(words)
992        }
993    }
994
995    fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> std::result::Result<(), Self::Error> {
996        if Hal::in_async_context() {
997            block_in_place(|| self.transfer_inner(read, write))
998        } else {
999            self.transfer_inner(read, write)
1000        }
1001    }
1002
1003    fn transfer_in_place(&mut self, words: &mut [u8]) -> std::result::Result<(), Self::Error> {
1004        if Hal::in_async_context() {
1005            block_in_place(|| {
1006                let write_copy = words.to_vec();
1007                self.transfer_inner(words, &write_copy)
1008            })
1009        } else {
1010            let write_copy = words.to_vec();
1011            self.transfer_inner(words, &write_copy)
1012        }
1013    }
1014
1015    fn flush(&mut self) -> std::result::Result<(), Self::Error> {
1016        if Hal::in_async_context() {
1017            block_in_place(|| self.flush_inner())
1018        } else {
1019            self.flush_inner()
1020        }
1021    }
1022}
1023
1024impl embedded_hal_async::spi::SpiBus for Spi {
1025    async fn read(&mut self, words: &mut [u8]) -> std::result::Result<(), Self::Error> {
1026        let gallo = self.gallo.lock().await;
1027        let contents = gallo
1028            .spi_read(words.len() as u16)
1029            .await
1030            .map_err(SpiHalError::from)?;
1031        words.copy_from_slice(&contents);
1032        Ok(())
1033    }
1034
1035    async fn write(&mut self, words: &[u8]) -> std::result::Result<(), Self::Error> {
1036        let gallo = self.gallo.lock().await;
1037        gallo.spi_write(words).await.map_err(SpiHalError::from)
1038    }
1039
1040    async fn transfer(
1041        &mut self,
1042        read: &mut [u8],
1043        write: &[u8],
1044    ) -> std::result::Result<(), Self::Error> {
1045        let gallo = self.gallo.lock().await;
1046        let contents = gallo.spi_transfer(write).await.map_err(SpiHalError::from)?;
1047        let len = read.len().min(contents.len());
1048        read[..len].copy_from_slice(&contents[..len]);
1049        Ok(())
1050    }
1051
1052    async fn transfer_in_place(
1053        &mut self,
1054        words: &mut [u8],
1055    ) -> std::result::Result<(), Self::Error> {
1056        let gallo = self.gallo.lock().await;
1057        let write_copy = words.to_vec();
1058        let contents = gallo
1059            .spi_transfer(&write_copy)
1060            .await
1061            .map_err(SpiHalError::from)?;
1062        let len = words.len().min(contents.len());
1063        words[..len].copy_from_slice(&contents[..len]);
1064        Ok(())
1065    }
1066
1067    async fn flush(&mut self) -> std::result::Result<(), Self::Error> {
1068        let gallo = self.gallo.lock().await;
1069        gallo.spi_flush().await.map_err(SpiHalError::from)
1070    }
1071}
1072
1073// ----------------------------- SpiDevice ----------------------------
1074
1075/// SPI device handle implementing [`embedded-hal`] [`SpiDevice`](embedded_hal::spi::SpiDevice) traits.
1076///
1077/// Obtained from [`Hal::spi_device`]. Wraps the SPI bus with firmware-managed
1078/// chip-select (CS) assertion via a GPIO pin. Each call to
1079/// [`transaction`](embedded_hal::spi::SpiDevice::transaction) will:
1080///
1081/// 1. Assert CS (drive low)
1082/// 2. Perform all requested operations
1083/// 3. Flush the bus
1084/// 4. Deassert CS (drive high)
1085///
1086/// # Cancellation Safety
1087///
1088/// The async [`SpiDevice`](embedded_hal_async::spi::SpiDevice) implementation
1089/// is **not** cancellation-safe. If the future returned by `transaction()` is
1090/// dropped after CS is asserted but before it is deasserted, the CS line will
1091/// remain low. This matches the behavior of `embedded-hal-bus::ExclusiveDevice`.
1092///
1093/// # CS Pin Ownership
1094///
1095/// The caller is responsible for ensuring that the CS pin is not used
1096/// concurrently by other [`Gpio`] handles or [`SpiDev`] instances.
1097pub struct SpiDev {
1098    gallo: Arc<Mutex<PicoDeGallo>>,
1099    handle: Handle,
1100    cs_pin: u8,
1101}
1102
1103impl SpiDev {
1104    /// Execute a blocking SPI transaction with CS management.
1105    fn transaction_inner(
1106        &mut self,
1107        operations: &mut [embedded_hal::spi::Operation<'_, u8>],
1108    ) -> std::result::Result<(), SpiHalError> {
1109        use pico_de_gallo_lib::SpiBatchOp;
1110
1111        let handle = &self.handle;
1112        let gallo = handle.block_on(self.gallo.lock());
1113
1114        // Build batch ops. TransferInPlace needs a copy of the write data
1115        // so we can reference it in the batch. Track which indices need fixup.
1116        let mut in_place_bufs: Vec<(usize, Vec<u8>)> = Vec::new();
1117        let mut batch_ops: Vec<SpiBatchOp<'_>> = operations
1118            .iter()
1119            .enumerate()
1120            .map(|(i, op)| match op {
1121                embedded_hal::spi::Operation::Read(buf) => SpiBatchOp::Read {
1122                    len: buf.len() as u16,
1123                },
1124                embedded_hal::spi::Operation::Write(data) => SpiBatchOp::Write { data },
1125                embedded_hal::spi::Operation::Transfer(_read, write) => {
1126                    SpiBatchOp::Transfer { data: write }
1127                }
1128                embedded_hal::spi::Operation::TransferInPlace(buf) => {
1129                    in_place_bufs.push((i, buf.to_vec()));
1130                    SpiBatchOp::Transfer { data: &[] }
1131                }
1132                embedded_hal::spi::Operation::DelayNs(ns) => SpiBatchOp::DelayNs { ns: *ns },
1133            })
1134            .collect();
1135
1136        // Fix up TransferInPlace references to point at the saved copies
1137        for (idx, buf) in &in_place_bufs {
1138            batch_ops[*idx] = SpiBatchOp::Transfer { data: buf };
1139        }
1140
1141        let result = handle
1142            .block_on(gallo.spi_batch(self.cs_pin, &batch_ops))
1143            .map_err(SpiHalError::from)?;
1144
1145        // Distribute read/transfer data back into the caller's buffers
1146        let mut offset = 0usize;
1147        for op in operations.iter_mut() {
1148            match op {
1149                embedded_hal::spi::Operation::Read(buf) => {
1150                    let len = buf.len();
1151                    buf.copy_from_slice(&result[offset..offset + len]);
1152                    offset += len;
1153                }
1154                embedded_hal::spi::Operation::Transfer(read, _write) => {
1155                    let len = read.len();
1156                    read.copy_from_slice(&result[offset..offset + len]);
1157                    offset += len;
1158                }
1159                embedded_hal::spi::Operation::TransferInPlace(buf) => {
1160                    let len = buf.len();
1161                    buf.copy_from_slice(&result[offset..offset + len]);
1162                    offset += len;
1163                }
1164                _ => {}
1165            }
1166        }
1167
1168        Ok(())
1169    }
1170}
1171
1172impl embedded_hal::spi::ErrorType for SpiDev {
1173    type Error = SpiHalError;
1174}
1175
1176impl embedded_hal::spi::SpiDevice for SpiDev {
1177    fn transaction(
1178        &mut self,
1179        operations: &mut [embedded_hal::spi::Operation<'_, u8>],
1180    ) -> std::result::Result<(), Self::Error> {
1181        if Hal::in_async_context() {
1182            block_in_place(|| self.transaction_inner(operations))
1183        } else {
1184            self.transaction_inner(operations)
1185        }
1186    }
1187}
1188
1189impl embedded_hal_async::spi::SpiDevice for SpiDev {
1190    async fn transaction(
1191        &mut self,
1192        operations: &mut [embedded_hal_async::spi::Operation<'_, u8>],
1193    ) -> std::result::Result<(), Self::Error> {
1194        use pico_de_gallo_lib::SpiBatchOp;
1195
1196        let gallo = self.gallo.lock().await;
1197
1198        // Build batch ops. TransferInPlace needs a copy of the write data.
1199        let mut in_place_bufs: Vec<(usize, Vec<u8>)> = Vec::new();
1200        let mut batch_ops: Vec<SpiBatchOp<'_>> = operations
1201            .iter()
1202            .enumerate()
1203            .map(|(i, op)| match op {
1204                embedded_hal_async::spi::Operation::Read(buf) => SpiBatchOp::Read {
1205                    len: buf.len() as u16,
1206                },
1207                embedded_hal_async::spi::Operation::Write(data) => SpiBatchOp::Write { data },
1208                embedded_hal_async::spi::Operation::Transfer(_read, write) => {
1209                    SpiBatchOp::Transfer { data: write }
1210                }
1211                embedded_hal_async::spi::Operation::TransferInPlace(buf) => {
1212                    in_place_bufs.push((i, buf.to_vec()));
1213                    SpiBatchOp::Transfer { data: &[] }
1214                }
1215                embedded_hal_async::spi::Operation::DelayNs(ns) => SpiBatchOp::DelayNs { ns: *ns },
1216            })
1217            .collect();
1218
1219        // Fix up TransferInPlace references to point at the saved copies
1220        for (idx, buf) in &in_place_bufs {
1221            batch_ops[*idx] = SpiBatchOp::Transfer { data: buf };
1222        }
1223
1224        let result = gallo
1225            .spi_batch(self.cs_pin, &batch_ops)
1226            .await
1227            .map_err(SpiHalError::from)?;
1228
1229        // Distribute read/transfer data back into the caller's buffers
1230        let mut offset = 0usize;
1231        for op in operations.iter_mut() {
1232            match op {
1233                embedded_hal_async::spi::Operation::Read(buf) => {
1234                    let len = buf.len();
1235                    buf.copy_from_slice(&result[offset..offset + len]);
1236                    offset += len;
1237                }
1238                embedded_hal_async::spi::Operation::Transfer(read, _write) => {
1239                    let len = read.len();
1240                    read.copy_from_slice(&result[offset..offset + len]);
1241                    offset += len;
1242                }
1243                embedded_hal_async::spi::Operation::TransferInPlace(buf) => {
1244                    let len = buf.len();
1245                    buf.copy_from_slice(&result[offset..offset + len]);
1246                    offset += len;
1247                }
1248                _ => {}
1249            }
1250        }
1251
1252        Ok(())
1253    }
1254}
1255
1256// ----------------------------- Delay -----------------------------
1257
1258/// Delay provider using host-side timers.
1259///
1260/// Obtained from [`Hal::delay`]. Uses [`std::thread::sleep`] for blocking
1261/// delays and [`tokio::time::sleep`] for async delays.
1262pub struct Delay;
1263
1264impl embedded_hal::delay::DelayNs for Delay {
1265    fn delay_ns(&mut self, ns: u32) {
1266        std::thread::sleep(std::time::Duration::from_nanos(ns.into()))
1267    }
1268}
1269
1270impl embedded_hal_async::delay::DelayNs for Delay {
1271    async fn delay_ns(&mut self, ns: u32) {
1272        tokio::time::sleep(tokio::time::Duration::from_nanos(ns.into())).await
1273    }
1274}
1275
1276// ----------------------------- Uart -----------------------------
1277
1278/// UART handle implementing [`embedded-io`] traits.
1279///
1280/// Obtained from [`Hal::uart`]. Supports blocking and async read/write.
1281/// The baud rate can be changed at runtime with
1282/// [`Hal::uart_set_config`].
1283///
1284/// **Read timeout**: UART reads use a configurable timeout (in
1285/// milliseconds) to avoid blocking the USB bridge indefinitely. The
1286/// default timeout is 1000 ms. Adjust with [`Uart::set_timeout_ms`].
1287pub struct Uart {
1288    gallo: Arc<Mutex<PicoDeGallo>>,
1289    handle: Handle,
1290    timeout_ms: u32,
1291}
1292
1293impl Uart {
1294    /// Set the read timeout in milliseconds.
1295    ///
1296    /// This controls how long [`embedded_io::Read::read`] waits for
1297    /// data before returning an empty result.  A value of 0 means
1298    /// non-blocking: return whatever is buffered immediately.
1299    pub fn set_timeout_ms(&mut self, timeout_ms: u32) {
1300        self.timeout_ms = timeout_ms;
1301    }
1302
1303    fn read_inner(&mut self, buf: &mut [u8]) -> std::result::Result<usize, UartHalError> {
1304        let handle = &self.handle;
1305        let gallo = handle.block_on(self.gallo.lock());
1306        let contents = handle
1307            .block_on(gallo.uart_read(buf.len() as u16, self.timeout_ms))
1308            .map_err(UartHalError::from)?;
1309        let n = contents.len().min(buf.len());
1310        buf[..n].copy_from_slice(&contents[..n]);
1311        Ok(n)
1312    }
1313
1314    fn write_inner(&mut self, buf: &[u8]) -> std::result::Result<usize, UartHalError> {
1315        let handle = &self.handle;
1316        let gallo = handle.block_on(self.gallo.lock());
1317        handle
1318            .block_on(gallo.uart_write(buf))
1319            .map_err(UartHalError::from)?;
1320        Ok(buf.len())
1321    }
1322
1323    fn flush_inner(&mut self) -> std::result::Result<(), UartHalError> {
1324        let handle = &self.handle;
1325        let gallo = handle.block_on(self.gallo.lock());
1326        handle
1327            .block_on(gallo.uart_flush())
1328            .map_err(UartHalError::from)
1329    }
1330}
1331
1332impl embedded_io::ErrorType for Uart {
1333    type Error = UartHalError;
1334}
1335
1336impl embedded_io::Read for Uart {
1337    fn read(&mut self, buf: &mut [u8]) -> std::result::Result<usize, Self::Error> {
1338        if Hal::in_async_context() {
1339            block_in_place(|| self.read_inner(buf))
1340        } else {
1341            self.read_inner(buf)
1342        }
1343    }
1344}
1345
1346impl embedded_io::Write for Uart {
1347    fn write(&mut self, buf: &[u8]) -> std::result::Result<usize, Self::Error> {
1348        if Hal::in_async_context() {
1349            block_in_place(|| self.write_inner(buf))
1350        } else {
1351            self.write_inner(buf)
1352        }
1353    }
1354
1355    fn flush(&mut self) -> std::result::Result<(), Self::Error> {
1356        if Hal::in_async_context() {
1357            block_in_place(|| self.flush_inner())
1358        } else {
1359            self.flush_inner()
1360        }
1361    }
1362}
1363
1364impl embedded_io_async::Read for Uart {
1365    async fn read(&mut self, buf: &mut [u8]) -> std::result::Result<usize, Self::Error> {
1366        let gallo = self.gallo.lock().await;
1367        let contents = gallo
1368            .uart_read(buf.len() as u16, self.timeout_ms)
1369            .await
1370            .map_err(UartHalError::from)?;
1371        let n = contents.len().min(buf.len());
1372        buf[..n].copy_from_slice(&contents[..n]);
1373        Ok(n)
1374    }
1375}
1376
1377impl embedded_io_async::Write for Uart {
1378    async fn write(&mut self, buf: &[u8]) -> std::result::Result<usize, Self::Error> {
1379        let gallo = self.gallo.lock().await;
1380        gallo.uart_write(buf).await.map_err(UartHalError::from)?;
1381        Ok(buf.len())
1382    }
1383
1384    async fn flush(&mut self) -> std::result::Result<(), Self::Error> {
1385        let gallo = self.gallo.lock().await;
1386        gallo.uart_flush().await.map_err(UartHalError::from)
1387    }
1388}
1389
1390// ---------------------------------------------------------------------------
1391// PWM
1392// ---------------------------------------------------------------------------
1393
1394/// Error type for PWM HAL operations.
1395#[derive(Debug)]
1396pub enum PwmHalError {
1397    /// A PWM-specific error from the device firmware.
1398    Pwm(PwmError),
1399    /// A USB communication error.
1400    Comms(String),
1401}
1402
1403impl core::fmt::Display for PwmHalError {
1404    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1405        match self {
1406            Self::Pwm(e) => write!(f, "{e}"),
1407            Self::Comms(msg) => write!(f, "communication error: {msg}"),
1408        }
1409    }
1410}
1411
1412impl std::error::Error for PwmHalError {}
1413
1414impl From<PicoDeGalloError<PwmError>> for PwmHalError {
1415    fn from(e: PicoDeGalloError<PwmError>) -> Self {
1416        match e {
1417            PicoDeGalloError::Endpoint(e) => Self::Pwm(e),
1418            PicoDeGalloError::Comms(c) => Self::Comms(format!("{c:?}")),
1419        }
1420    }
1421}
1422
1423impl embedded_hal::pwm::Error for PwmHalError {
1424    fn kind(&self) -> embedded_hal::pwm::ErrorKind {
1425        embedded_hal::pwm::ErrorKind::Other
1426    }
1427}
1428
1429/// A single PWM channel on the Pico de Gallo board.
1430///
1431/// Obtained from [`Hal::pwm_channel`]. Implements the [`embedded_hal::pwm::SetDutyCycle`]
1432/// trait.
1433///
1434/// Channels 0–1 share PWM slice 6, channels 2–3 share slice 7.
1435/// Enable/disable and configuration changes affect the entire slice.
1436pub struct PwmChannel {
1437    channel: u8,
1438    gallo: Arc<tokio::sync::Mutex<PicoDeGallo>>,
1439    handle: Handle,
1440}
1441
1442impl embedded_hal::pwm::ErrorType for PwmChannel {
1443    type Error = PwmHalError;
1444}
1445
1446impl embedded_hal::pwm::SetDutyCycle for PwmChannel {
1447    fn max_duty_cycle(&self) -> u16 {
1448        let handle = &self.handle;
1449        let gallo = handle.block_on(self.gallo.lock());
1450        handle
1451            .block_on(gallo.pwm_get_duty_cycle(self.channel))
1452            .map(|info| info.max_duty)
1453            .unwrap_or(u16::MAX)
1454    }
1455
1456    fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
1457        let handle = &self.handle;
1458        let gallo = handle.block_on(self.gallo.lock());
1459        handle
1460            .block_on(gallo.pwm_set_duty_cycle(self.channel, duty))
1461            .map_err(PwmHalError::from)
1462    }
1463}
1464
1465// ---------------------------------------------------------------------------
1466// ADC
1467// ---------------------------------------------------------------------------
1468
1469/// Error type for ADC HAL operations.
1470#[derive(Debug)]
1471pub enum AdcHalError {
1472    /// An ADC-specific error from the device firmware.
1473    Adc(AdcError),
1474    /// A USB communication error.
1475    Comms(String),
1476}
1477
1478impl core::fmt::Display for AdcHalError {
1479    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1480        match self {
1481            Self::Adc(e) => write!(f, "{e}"),
1482            Self::Comms(msg) => write!(f, "communication error: {msg}"),
1483        }
1484    }
1485}
1486
1487impl std::error::Error for AdcHalError {}
1488
1489impl From<PicoDeGalloError<AdcError>> for AdcHalError {
1490    fn from(e: PicoDeGalloError<AdcError>) -> Self {
1491        match e {
1492            PicoDeGalloError::Endpoint(e) => Self::Adc(e),
1493            PicoDeGalloError::Comms(c) => Self::Comms(format!("{c:?}")),
1494        }
1495    }
1496}
1497
1498/// Error type for 1-Wire HAL operations.
1499#[derive(Debug)]
1500pub enum OneWireHalError {
1501    /// A 1-Wire-specific error from the device firmware.
1502    OneWire(OneWireError),
1503    /// A USB communication error.
1504    Comms(String),
1505}
1506
1507impl core::fmt::Display for OneWireHalError {
1508    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1509        match self {
1510            Self::OneWire(e) => write!(f, "{e}"),
1511            Self::Comms(msg) => write!(f, "communication error: {msg}"),
1512        }
1513    }
1514}
1515
1516impl std::error::Error for OneWireHalError {}
1517
1518impl From<PicoDeGalloError<OneWireError>> for OneWireHalError {
1519    fn from(e: PicoDeGalloError<OneWireError>) -> Self {
1520        match e {
1521            PicoDeGalloError::Endpoint(e) => Self::OneWire(e),
1522            PicoDeGalloError::Comms(c) => Self::Comms(format!("{c:?}")),
1523        }
1524    }
1525}
1526
1527/// 1-Wire bus handle backed by PIO hardware on the firmware.
1528///
1529/// There is no standard embedded-hal trait for 1-Wire. This type provides
1530/// blocking methods that mirror the async methods on [`PicoDeGallo`](pico_de_gallo_lib::PicoDeGallo).
1531pub struct OneWire {
1532    gallo: Arc<Mutex<PicoDeGallo>>,
1533    handle: Handle,
1534}
1535
1536impl OneWire {
1537    /// Perform a bus reset and detect device presence.
1538    ///
1539    /// Returns `true` if one or more devices responded with a presence pulse.
1540    pub fn reset(&self) -> Result<bool, OneWireHalError> {
1541        if Hal::in_async_context() {
1542            block_in_place(|| self.handle.block_on(self.reset_inner()))
1543        } else {
1544            self.handle.block_on(self.reset_inner())
1545        }
1546    }
1547
1548    async fn reset_inner(&self) -> Result<bool, OneWireHalError> {
1549        self.gallo
1550            .lock()
1551            .await
1552            .onewire_reset()
1553            .await
1554            .map_err(OneWireHalError::from)
1555    }
1556
1557    /// Read `len` bytes from the 1-Wire bus.
1558    pub fn read(&self, len: u16) -> Result<Vec<u8>, OneWireHalError> {
1559        if Hal::in_async_context() {
1560            block_in_place(|| self.handle.block_on(self.read_inner(len)))
1561        } else {
1562            self.handle.block_on(self.read_inner(len))
1563        }
1564    }
1565
1566    async fn read_inner(&self, len: u16) -> Result<Vec<u8>, OneWireHalError> {
1567        self.gallo
1568            .lock()
1569            .await
1570            .onewire_read(len)
1571            .await
1572            .map_err(OneWireHalError::from)
1573    }
1574
1575    /// Write raw bytes to the 1-Wire bus.
1576    pub fn write(&self, data: &[u8]) -> Result<(), OneWireHalError> {
1577        if Hal::in_async_context() {
1578            block_in_place(|| self.handle.block_on(self.write_inner(data)))
1579        } else {
1580            self.handle.block_on(self.write_inner(data))
1581        }
1582    }
1583
1584    async fn write_inner(&self, data: &[u8]) -> Result<(), OneWireHalError> {
1585        self.gallo
1586            .lock()
1587            .await
1588            .onewire_write(data)
1589            .await
1590            .map_err(OneWireHalError::from)
1591    }
1592
1593    /// Write bytes then apply a strong pullup for parasitic-power devices.
1594    pub fn write_pullup(
1595        &self,
1596        data: &[u8],
1597        pullup_duration_ms: u16,
1598    ) -> Result<(), OneWireHalError> {
1599        if Hal::in_async_context() {
1600            block_in_place(|| {
1601                self.handle
1602                    .block_on(self.write_pullup_inner(data, pullup_duration_ms))
1603            })
1604        } else {
1605            self.handle
1606                .block_on(self.write_pullup_inner(data, pullup_duration_ms))
1607        }
1608    }
1609
1610    async fn write_pullup_inner(
1611        &self,
1612        data: &[u8],
1613        pullup_duration_ms: u16,
1614    ) -> Result<(), OneWireHalError> {
1615        self.gallo
1616            .lock()
1617            .await
1618            .onewire_write_pullup(data, pullup_duration_ms)
1619            .await
1620            .map_err(OneWireHalError::from)
1621    }
1622
1623    /// Start a new ROM search and return the first device address.
1624    pub fn search(&self) -> Result<Option<u64>, OneWireHalError> {
1625        if Hal::in_async_context() {
1626            block_in_place(|| self.handle.block_on(self.search_inner()))
1627        } else {
1628            self.handle.block_on(self.search_inner())
1629        }
1630    }
1631
1632    async fn search_inner(&self) -> Result<Option<u64>, OneWireHalError> {
1633        self.gallo
1634            .lock()
1635            .await
1636            .onewire_search()
1637            .await
1638            .map_err(OneWireHalError::from)
1639    }
1640
1641    /// Continue the current ROM search and return the next device address.
1642    pub fn search_next(&self) -> Result<Option<u64>, OneWireHalError> {
1643        if Hal::in_async_context() {
1644            block_in_place(|| self.handle.block_on(self.search_next_inner()))
1645        } else {
1646            self.handle.block_on(self.search_next_inner())
1647        }
1648    }
1649
1650    async fn search_next_inner(&self) -> Result<Option<u64>, OneWireHalError> {
1651        self.gallo
1652            .lock()
1653            .await
1654            .onewire_search_next()
1655            .await
1656            .map_err(OneWireHalError::from)
1657    }
1658}
1659
1660#[cfg(test)]
1661mod tests {
1662    use super::*;
1663
1664    // --- Error kind tests ---
1665
1666    #[test]
1667    fn digital_error_kind_is_other() {
1668        use embedded_hal::digital::Error as _;
1669        let err = GpioHalError::Gpio(GpioError::Other);
1670        assert_eq!(err.kind(), embedded_hal::digital::ErrorKind::Other);
1671    }
1672
1673    #[test]
1674    fn i2c_error_kind_nack() {
1675        use embedded_hal::i2c::Error as _;
1676        let err = I2cHalError::I2c(I2cError::NoAcknowledge);
1677        assert_eq!(
1678            err.kind(),
1679            embedded_hal::i2c::ErrorKind::NoAcknowledge(
1680                embedded_hal::i2c::NoAcknowledgeSource::Unknown
1681            )
1682        );
1683    }
1684
1685    #[test]
1686    fn i2c_error_kind_bus() {
1687        use embedded_hal::i2c::Error as _;
1688        let err = I2cHalError::I2c(I2cError::Bus);
1689        assert_eq!(err.kind(), embedded_hal::i2c::ErrorKind::Bus);
1690    }
1691
1692    #[test]
1693    fn i2c_error_kind_arbitration_loss() {
1694        use embedded_hal::i2c::Error as _;
1695        let err = I2cHalError::I2c(I2cError::ArbitrationLoss);
1696        assert_eq!(err.kind(), embedded_hal::i2c::ErrorKind::ArbitrationLoss);
1697    }
1698
1699    #[test]
1700    fn i2c_error_kind_overrun() {
1701        use embedded_hal::i2c::Error as _;
1702        let err = I2cHalError::I2c(I2cError::Overrun);
1703        assert_eq!(err.kind(), embedded_hal::i2c::ErrorKind::Overrun);
1704    }
1705
1706    #[test]
1707    fn i2c_error_kind_other_for_comms() {
1708        use embedded_hal::i2c::Error as _;
1709        let err = I2cHalError::Comms("USB disconnected".into());
1710        assert_eq!(err.kind(), embedded_hal::i2c::ErrorKind::Other);
1711    }
1712
1713    #[test]
1714    fn spi_error_kind_is_other() {
1715        use embedded_hal::spi::Error as _;
1716        let err = SpiHalError::Spi(SpiError::Other);
1717        assert_eq!(err.kind(), embedded_hal::spi::ErrorKind::Other);
1718    }
1719
1720    #[test]
1721    fn spi_device_error_type_matches_spi_bus() {
1722        // SpiDev and Spi share the same error type (SpiHalError),
1723        // so drivers can mix SpiBus and SpiDevice errors.
1724        fn assert_error_type<T: embedded_hal::spi::ErrorType<Error = SpiHalError>>() {}
1725        assert_error_type::<Spi>();
1726        assert_error_type::<SpiDev>();
1727    }
1728
1729    #[test]
1730    fn spi_device_comms_error_kind_is_other() {
1731        use embedded_hal::spi::Error as _;
1732        let err = SpiHalError::Comms("CS assert failed".into());
1733        assert_eq!(err.kind(), embedded_hal::spi::ErrorKind::Other);
1734    }
1735
1736    #[test]
1737    fn uart_error_kind_invalid_baud_rate() {
1738        use embedded_io::Error as _;
1739        let err = UartHalError::Uart(UartError::InvalidBaudRate);
1740        assert_eq!(err.kind(), embedded_io::ErrorKind::InvalidInput);
1741    }
1742
1743    #[test]
1744    fn uart_error_kind_overrun() {
1745        use embedded_io::Error as _;
1746        let err = UartHalError::Uart(UartError::Overrun);
1747        assert_eq!(err.kind(), embedded_io::ErrorKind::Other);
1748    }
1749
1750    #[test]
1751    fn uart_error_kind_comms() {
1752        use embedded_io::Error as _;
1753        let err = UartHalError::Comms("USB disconnected".into());
1754        assert_eq!(err.kind(), embedded_io::ErrorKind::Other);
1755    }
1756
1757    #[test]
1758    fn uart_hal_error_display() {
1759        let err = UartHalError::Uart(UartError::Framing);
1760        assert_eq!(format!("{err}"), "UART framing error");
1761
1762        let err = UartHalError::Comms("timeout".into());
1763        assert_eq!(format!("{err}"), "communication error: timeout");
1764    }
1765
1766    #[test]
1767    fn uart_hal_error_from_endpoint() {
1768        let e: UartHalError = PicoDeGalloError::Endpoint(UartError::Break).into();
1769        assert!(matches!(e, UartHalError::Uart(UartError::Break)));
1770    }
1771
1772    #[test]
1773    fn uart_hal_error_from_comms() {
1774        let e = UartHalError::Comms("USB disconnected".into());
1775        assert!(matches!(e, UartHalError::Comms(_)));
1776    }
1777
1778    // --- PWM error tests ---
1779
1780    #[test]
1781    fn pwm_error_kind_is_other() {
1782        use embedded_hal::pwm::Error as _;
1783        let err = PwmHalError::Pwm(PwmError::InvalidChannel);
1784        assert_eq!(err.kind(), embedded_hal::pwm::ErrorKind::Other);
1785    }
1786
1787    #[test]
1788    fn pwm_comms_error_kind_is_other() {
1789        use embedded_hal::pwm::Error as _;
1790        let err = PwmHalError::Comms("timeout".into());
1791        assert_eq!(err.kind(), embedded_hal::pwm::ErrorKind::Other);
1792    }
1793
1794    #[test]
1795    fn pwm_hal_error_display_endpoint() {
1796        let err = PwmHalError::Pwm(PwmError::InvalidChannel);
1797        assert_eq!(format!("{err}"), "invalid PWM channel");
1798    }
1799
1800    #[test]
1801    fn pwm_hal_error_display_comms() {
1802        let err = PwmHalError::Comms("USB gone".into());
1803        assert_eq!(format!("{err}"), "communication error: USB gone");
1804    }
1805
1806    // --- Runtime detection tests ---
1807
1808    #[test]
1809    fn handle_try_current_fails_outside_tokio() {
1810        // Outside any tokio runtime, try_current should fail.
1811        // This is the code path that causes Hal::new_inner to create
1812        // its own Runtime.
1813        let result = Handle::try_current();
1814        assert!(result.is_err());
1815    }
1816
1817    #[tokio::test]
1818    async fn handle_try_current_succeeds_inside_tokio() {
1819        // Inside a tokio runtime, try_current should succeed.
1820        // This is the code path where Hal::new_inner reuses the
1821        // existing runtime handle.
1822        let result = Handle::try_current();
1823        assert!(result.is_ok());
1824    }
1825
1826    // --- Delay unit tests ---
1827
1828    #[test]
1829    fn delay_ns_does_not_panic() {
1830        use embedded_hal::delay::DelayNs;
1831        let mut delay = Delay;
1832        // Just verify it doesn't panic for a tiny delay
1833        delay.delay_ns(1);
1834    }
1835
1836    #[tokio::test]
1837    async fn async_delay_ns_does_not_panic() {
1838        use embedded_hal_async::delay::DelayNs;
1839        let mut delay = Delay;
1840        delay.delay_ns(1).await;
1841    }
1842
1843    // --- ADC error tests ---
1844
1845    #[test]
1846    fn adc_hal_error_display_conversion_failed() {
1847        let err = AdcHalError::Adc(AdcError::ConversionFailed);
1848        assert_eq!(format!("{err}"), "ADC conversion failed");
1849    }
1850
1851    #[test]
1852    fn adc_hal_error_display_comms() {
1853        let err = AdcHalError::Comms("timeout".into());
1854        assert_eq!(format!("{err}"), "communication error: timeout");
1855    }
1856
1857    #[test]
1858    fn adc_hal_error_from_endpoint() {
1859        let e: PicoDeGalloError<AdcError> = PicoDeGalloError::Endpoint(AdcError::Other);
1860        let hal_err = AdcHalError::from(e);
1861        match hal_err {
1862            AdcHalError::Adc(AdcError::Other) => {}
1863            other => panic!("expected Adc(Other), got {other:?}"),
1864        }
1865    }
1866
1867    // --- 1-Wire error tests ---
1868
1869    #[test]
1870    fn onewire_hal_error_display_no_presence() {
1871        let err = OneWireHalError::OneWire(OneWireError::NoPresence);
1872        assert_eq!(format!("{err}"), "no device present on 1-Wire bus");
1873    }
1874
1875    #[test]
1876    fn onewire_hal_error_display_comms() {
1877        let err = OneWireHalError::Comms("timeout".into());
1878        assert_eq!(format!("{err}"), "communication error: timeout");
1879    }
1880
1881    #[test]
1882    fn onewire_hal_error_from_endpoint() {
1883        let e: PicoDeGalloError<OneWireError> = PicoDeGalloError::Endpoint(OneWireError::BusError);
1884        let hal_err = OneWireHalError::from(e);
1885        match hal_err {
1886            OneWireHalError::OneWire(OneWireError::BusError) => {}
1887            other => panic!("expected OneWire(BusError), got {other:?}"),
1888        }
1889    }
1890}