rylr998-embassy 0.1.0

Embassy-based no_std driver for the REYAX RYLR998 LoRa radio module (RP2040 / RP2350 / any embedded-io-async UART).
#![no_std]
//! `no_std` driver for the REYAX RYLR998 LoRa radio module, built on
//! [embassy](https://embassy.dev/) and the [`embedded_io_async`] traits.
//!
//! Wrap any UART that implements `embedded_io_async::Read + Write` and you
//! get an async `Radio<UART>` exposing the same surface as
//! [`rylr998-std`](https://crates.io/crates/rylr998-std) /
//! [`rylr998-tokio`](https://crates.io/crates/rylr998-tokio):
//! `ping`, `set_address` / `address`, `set_network_id` / `network_id`,
//! `set_band` / `band`, `set_parameters` / `parameters`, `crfop`,
//! `factory_reset`, `send`, and a callback-style `next_event`.
//!
//! ## Example
//!
//! ```ignore
//! let uart = uart::BufferedUart::new(/* … */);
//! let mut radio = rylr998_embassy::Radio::new(uart);
//! radio.ping().await?;
//! radio.set_address(5).await?;
//! radio.next_event(Duration::from_secs(60), |e| {
//!     defmt::info!("event: {:?}", defmt::Debug2Format(&e));
//!     None::<()>
//! }).await.ok();
//! ```
//!
//! See `examples/pico_smoke.rs` for an end-to-end Pico 2 + RYLR998 sketch.

use embassy_time::{Duration, Instant};
use rylr998_core::{Command, Driver, Event, Poll, Response, RfParams};

const DEFAULT_TIMEOUT: Duration = Duration::from_secs(1);
const FACTORY_RESET_TIMEOUT: Duration = Duration::from_secs(2);

#[derive(Debug)]
pub enum Error<E> {
    Core(rylr998_core::Error),
    Io(E),
    Timeout,
    Radio(u8),
}

impl<E> From<rylr998_core::Error> for Error<E> {
    fn from(e: rylr998_core::Error) -> Self {
        Self::Core(e)
    }
}

pub struct Radio<UART> {
    driver: Driver,
    uart: UART,
}

fn wait_ok<E>(r: Response<'_>) -> Option<Result<(), Error<E>>> {
    match r {
        Response::Ok => Some(Ok(())),
        Response::Err(n) => Some(Err(Error::Radio(n))),
        _ => None,
    }
}

fn log_event(e: Event<'_>) {
    match e {
        Event::Recv { from, data, rssi, snr } => {
            defmt::info!(
                "recv from={} len={} rssi={} snr={}",
                from,
                data.len(),
                rssi,
                snr,
            );
        }
        Event::Ready => defmt::info!("event: ready"),
    }
}

impl<UART> Radio<UART>
where
    UART: embedded_io_async::Read + embedded_io_async::Write,
{
    pub fn new(uart: UART) -> Self {
        let driver = Driver::new();
        Self { uart, driver }
    }

    fn deadline(d: Duration) -> Instant {
        Instant::now() + d
    }

    pub async fn ping(&mut self) -> Result<(), Error<UART::Error>> {
        self.driver.submit(Command::Ping)?;
        self.pump_until(Self::deadline(DEFAULT_TIMEOUT), wait_ok).await
    }

    pub async fn set_address(&mut self, n: u16) -> Result<(), Error<UART::Error>> {
        self.driver.submit(Command::SetAddress(n))?;
        self.pump_until(Self::deadline(DEFAULT_TIMEOUT), wait_ok).await
    }

    pub async fn address(&mut self) -> Result<u16, Error<UART::Error>> {
        self.driver.submit(Command::GetAddress)?;
        self.pump_until(Self::deadline(DEFAULT_TIMEOUT), |r| match r {
            Response::Address(n) => Some(Ok(n)),
            Response::Err(n) => Some(Err(Error::Radio(n))),
            _ => None,
        })
        .await
    }

    pub async fn set_network_id(&mut self, n: u8) -> Result<(), Error<UART::Error>> {
        self.driver.submit(Command::SetNetworkId(n))?;
        self.pump_until(Self::deadline(DEFAULT_TIMEOUT), wait_ok).await
    }

    pub async fn network_id(&mut self) -> Result<u8, Error<UART::Error>> {
        self.driver.submit(Command::GetNetworkId)?;
        self.pump_until(Self::deadline(DEFAULT_TIMEOUT), |r| match r {
            Response::NetworkId(n) => Some(Ok(n)),
            Response::Err(n) => Some(Err(Error::Radio(n))),
            _ => None,
        })
        .await
    }

    pub async fn set_band(&mut self, hz: u32) -> Result<(), Error<UART::Error>> {
        self.driver.submit(Command::SetBand(hz))?;
        self.pump_until(Self::deadline(DEFAULT_TIMEOUT), wait_ok).await
    }

    pub async fn band(&mut self) -> Result<u32, Error<UART::Error>> {
        self.driver.submit(Command::GetBand)?;
        self.pump_until(Self::deadline(DEFAULT_TIMEOUT), |r| match r {
            Response::Band(n) => Some(Ok(n)),
            Response::Err(n) => Some(Err(Error::Radio(n))),
            _ => None,
        })
        .await
    }

    pub async fn set_parameters(&mut self, p: RfParams) -> Result<(), Error<UART::Error>> {
        self.driver.submit(Command::SetParameters(p))?;
        self.pump_until(Self::deadline(DEFAULT_TIMEOUT), wait_ok).await
    }

    pub async fn parameters(&mut self) -> Result<RfParams, Error<UART::Error>> {
        self.driver.submit(Command::GetParameters)?;
        self.pump_until(Self::deadline(DEFAULT_TIMEOUT), |r| match r {
            Response::Parameters(p) => Some(Ok(p)),
            Response::Err(n) => Some(Err(Error::Radio(n))),
            _ => None,
        })
        .await
    }

    pub async fn crfop(&mut self) -> Result<u8, Error<UART::Error>> {
        self.driver.submit(Command::GetCrfop)?;
        self.pump_until(Self::deadline(DEFAULT_TIMEOUT), |r| match r {
            Response::Crfop(n) => Some(Ok(n)),
            Response::Err(n) => Some(Err(Error::Radio(n))),
            _ => None,
        })
        .await
    }

    // `uid` and `version` return owned Strings in the std/tokio ports; here we'd
    // need a `heapless::String<N>` to avoid alloc. Skipped for the smoke test.

    pub async fn factory_reset(&mut self) -> Result<(), Error<UART::Error>> {
        self.driver.submit(Command::FactoryReset)?;
        self.pump_until(Self::deadline(FACTORY_RESET_TIMEOUT), wait_ok).await
    }

    pub async fn send(&mut self, to: u16, data: &[u8]) -> Result<(), Error<UART::Error>> {
        self.driver.submit(Command::Send { to, data })?;
        self.pump_until(Self::deadline(DEFAULT_TIMEOUT), wait_ok).await
    }

    /// Wait for the next unsolicited event. The handler is invoked with each
    /// `Event` as it arrives; return `Some(r)` from the handler to terminate
    /// and yield `r`, or `None` to continue waiting.
    ///
    /// `Event<'_>` borrows from the driver's line buffer, so the handler
    /// can't store it. Copy what you need out (e.g., `from`, `rssi`, `snr`,
    /// or a slice of `data`) before returning.
    pub async fn next_event<F, R>(
        &mut self,
        timeout: Duration,
        mut handler: F,
    ) -> Result<R, Error<UART::Error>>
    where
        F: FnMut(Event<'_>) -> Option<R>,
    {
        let deadline = Self::deadline(timeout);
        loop {
            loop {
                match self.driver.poll() {
                    Poll::NeedTx(bytes) => {
                        let n = bytes.len();
                        self.uart.write_all(bytes).await.map_err(Error::Io)?;
                        self.driver.ack_tx(n);
                    }
                    Poll::Event(e) => {
                        if let Some(r) = handler(e) {
                            return Ok(r);
                        }
                    }
                    Poll::Response(_) => {} // unexpected without an in-flight command
                    Poll::Idle => break,
                }
            }
            let mut buf = [0u8; 256];
            let remaining = deadline.saturating_duration_since(Instant::now());
            if remaining.as_ticks() == 0 {
                return Err(Error::Timeout);
            }
            match embassy_time::with_timeout(remaining, self.uart.read(&mut buf)).await {
                Ok(Ok(n)) => {
                    self.driver.push_rx(&buf[..n])?;
                }
                Ok(Err(e)) => return Err(Error::Io(e)),
                Err(_) => {} // outer loop will check deadline next iteration
            }
        }
    }

    pub(crate) async fn pump_until<R, F>(
        &mut self,
        deadline: Instant,
        mut want: F,
    ) -> Result<R, Error<UART::Error>>
    where
        F: FnMut(Response<'_>) -> Option<Result<R, Error<UART::Error>>>,
    {
        loop {
            loop {
                match self.driver.poll() {
                    Poll::NeedTx(bytes) => {
                        let n = bytes.len();
                        self.uart.write_all(bytes).await.map_err(Error::Io)?;
                        self.driver.ack_tx(n);
                    }
                    Poll::Response(r) => {
                        if let Some(out) = want(r) {
                            return out;
                        }
                    }
                    Poll::Event(e) => log_event(e),
                    Poll::Idle => break,
                }
            }
            let mut buf = [0u8; 256];
            let remaining = deadline.saturating_duration_since(Instant::now());
            if remaining.as_ticks() == 0 {
                return Err(Error::Timeout);
            }

            match embassy_time::with_timeout(remaining, self.uart.read(&mut buf)).await {
                Ok(Ok(n)) => {
                    self.driver.push_rx(&buf[..n])?;
                }
                Ok(Err(e)) => return Err(Error::Io(e)),
                Err(_) => {} // unhandled for now
            }
        }
    }
}