lora-e5 0.1.1

Tokio-based runtime and library for the SEEED LoRa-E5 module
Documentation
use crate::{AppEui, Credentials, DevEui, Mode, Region, DR};
use crate::{Downlink, Error as LoraE5Error, JoinResponse, LoraE5};
use std::sync::{Arc, Mutex};
use tokio::{
    sync::{mpsc, oneshot},
    task,
    time::Duration,
};

pub type Result<T = ()> = std::result::Result<T, Error>;

#[derive(Debug)]
pub enum Request {
    At(String, Duration, oneshot::Sender<Result<String>>),
    Join(bool, oneshot::Sender<Result<JoinResponse>>),
    Configure(Credentials, oneshot::Sender<Result>),
    GetAppEui(oneshot::Sender<Result<AppEui>>),
    GetDevEui(oneshot::Sender<Result<DevEui>>),
    DataRate(DR, oneshot::Sender<Result>),
    Region(Region, oneshot::Sender<Result>),
    Shutdown,
    SendData(Vec<u8>, u8, bool, oneshot::Sender<Result<Option<Downlink>>>),
    SendAscii(String, u8, bool, oneshot::Sender<Result<Option<Downlink>>>),
}

pub struct Client {
    sender: mpsc::Sender<Request>,
}

impl Client {
    pub async fn at_command(&self, cmd: &str, timeout: Duration) -> Result<String> {
        let (tx, rx) = oneshot::channel();
        let mut cmd = cmd.to_string();
        cmd.push('\n');
        self.sender.send(Request::At(cmd, timeout, tx)).await?;
        rx.await?
    }

    pub async fn join(&self, force: bool) -> Result<JoinResponse> {
        let (tx, rx) = oneshot::channel();
        self.sender.send(Request::Join(force, tx)).await?;
        rx.await?
    }

    pub async fn region(&self, region: Region) -> Result {
        let (tx, rx) = oneshot::channel();
        self.sender.send(Request::Region(region, tx)).await?;
        rx.await?
    }

    pub async fn data_rate(&self, dr: DR) -> Result {
        let (tx, rx) = oneshot::channel();
        self.sender.send(Request::DataRate(dr, tx)).await?;
        rx.await?
    }

    pub async fn configure(&self, credentials: Credentials) -> Result {
        let (tx, rx) = oneshot::channel();
        self.sender
            .send(Request::Configure(credentials, tx))
            .await?;
        rx.await?
    }

    pub async fn get_app_eui(&self) -> Result<AppEui> {
        let (tx, rx) = oneshot::channel();
        self.sender.send(Request::GetAppEui(tx)).await?;
        rx.await?
    }

    pub async fn get_dev_eui(&self) -> Result<DevEui> {
        let (tx, rx) = oneshot::channel();
        self.sender.send(Request::GetDevEui(tx)).await?;
        rx.await?
    }

    pub async fn send(&self, data: Vec<u8>, port: u8, confirmed: bool) -> Result<Option<Downlink>> {
        let (tx, rx) = oneshot::channel();
        self.sender
            .send(Request::SendData(data, port, confirmed, tx))
            .await?;
        rx.await?
    }
    pub async fn send_ascii(
        &self,
        data: String,
        port: u8,
        confirmed: bool,
    ) -> Result<Option<Downlink>> {
        let (tx, rx) = oneshot::channel();
        self.sender
            .send(Request::SendAscii(data, port, confirmed, tx))
            .await?;
        rx.await?
    }

    pub async fn send_shutdown(&self) -> Result {
        Ok(self.sender.send(Request::Shutdown).await?)
    }
}

pub struct Setup {
    sender: mpsc::Sender<Request>,
    receiver: mpsc::Receiver<Request>,
}

impl Default for Setup {
    fn default() -> Self {
        Self::new::<32>()
    }
}

impl Setup {
    pub fn new<const C: usize>() -> Self {
        let (sender, receiver) = mpsc::channel(C);
        Self { sender, receiver }
    }

    pub fn get_client(&self) -> Client {
        Client {
            sender: self.sender.clone(),
        }
    }

    pub fn complete(self) -> Runtime {
        Runtime {
            receiver: self.receiver,
        }
    }
}

pub struct Runtime {
    receiver: mpsc::Receiver<Request>,
}

fn respond<T>(response_sender: oneshot::Sender<Result<T>>, response: Result<T>) -> Result {
    let _ = response_sender.send(response);
    Ok(())
}

impl Runtime {
    pub async fn run<const N: usize>(mut self, lora_e5: LoraE5<N>) -> Result {
        let lora_e5 = Arc::new(Mutex::new(lora_e5));
        while let Some(request) = self.receiver.recv().await {
            let lora_e5 = lora_e5.clone();
            match request {
                Request::At(cmd, timeout, sender) => {
                    let response = task::spawn_blocking(move || {
                        let mut lora_e5 = lora_e5.lock().unwrap();
                        lora_e5.write_command(&cmd)?;
                        let n = lora_e5.read_until_break(timeout)?;
                        Ok(std::str::from_utf8(&lora_e5.buf[..n])?.to_string())
                    })
                    .await?;
                    respond(sender, response)?;
                }
                Request::Configure(credentials, response_sender) => {
                    let result = task::spawn_blocking(move || {
                        let mut lora_e5 = lora_e5.lock().unwrap();
                        lora_e5.set_mode(Mode::Otaa)?;
                        lora_e5.set_region(Region::Us915)?;
                        lora_e5.set_credentials(&credentials)?;
                        lora_e5.subband2_only()?;
                        Ok(())
                    })
                    .await?;
                    respond(response_sender, result)?;
                }
                Request::GetAppEui(response_sender) => {
                    let result = task::spawn_blocking(move || {
                        let mut lora_e5 = lora_e5.lock().unwrap();
                        Ok(lora_e5.get_app_eui()?)
                    })
                    .await?;
                    respond(response_sender, result)?;
                }
                Request::GetDevEui(response_sender) => {
                    let result = task::spawn_blocking(move || {
                        let mut lora_e5 = lora_e5.lock().unwrap();
                        Ok(lora_e5.get_dev_eui()?)
                    })
                    .await?;
                    respond(response_sender, result)?;
                }
                Request::Join(force, sender) => {
                    let result = task::spawn_blocking(move || {
                        let mut lora_e5 = lora_e5.lock().unwrap();
                        if force {
                            lora_e5.force_join()
                        } else {
                            lora_e5.join()
                        }
                    })
                    .await?;
                    respond(sender, result.map_err(|e| e.into()))?;
                }
                Request::DataRate(dr, sender) => {
                    let result = task::spawn_blocking(move || {
                        let mut lora_e5 = lora_e5.lock().unwrap();
                        lora_e5.set_datarate(dr)
                    })
                    .await?;
                    respond(sender, result.map_err(|e| e.into()))?;
                }
                Request::Region(region, sender) => {
                    let result = task::spawn_blocking(move || {
                        let mut lora_e5 = lora_e5.lock().unwrap();
                        lora_e5.set_region(region)
                    })
                    .await?;
                    respond(sender, result.map_err(|e| e.into()))?;
                }
                Request::SendData(data, port, confirmed, sender) => {
                    let result = task::spawn_blocking(move || {
                        let mut lora_e5 = lora_e5.lock().unwrap();
                        lora_e5.send(&data, port, confirmed)
                    })
                    .await?;
                    respond(sender, result.map_err(|e| e.into()))?;
                }
                Request::SendAscii(data, port, confirmed, sender) => {
                    let result = task::spawn_blocking(move || {
                        let mut lora_e5 = lora_e5.lock().unwrap();
                        lora_e5.send_ascii(&data, port, confirmed)
                    })
                    .await?;
                    respond(sender, result.map_err(|e| e.into()))?;
                }
                Request::Shutdown => {
                    return Ok(());
                }
            }
        }
        Ok(())
    }
}

use thiserror::Error;

#[derive(Error, Debug)]
pub enum Error {
    #[error("lora e5: {0}")]
    LoraE5(#[from] LoraE5Error),
    #[error("join error: {0}")]
    Join(#[from] tokio::task::JoinError),
    #[error("utf8 error: {0}")]
    Utf8(#[from] std::str::Utf8Error),
    #[error("request send error: {0}")]
    RequestSendError(#[from] mpsc::error::SendError<Request>),
    #[error("response receive error: {0}")]
    ResponseReceiveError(#[from] oneshot::error::RecvError),
}