use super::mac::Mac;
use super::mac::{self, Frame, Window};
pub use super::{
mac::{NetworkCredentials, SendData, Session},
region::{self, Region},
Downlink, JoinMode,
};
use crate::log;
use core::marker::PhantomData;
use futures::{future::select, future::Either, pin_mut};
use heapless::Vec;
use lorawan::{self, keys::CryptoFactory};
use rand_core::RngCore;
pub use crate::region::DR;
use crate::{radio::RadioBuffer, rng};
pub mod radio;
#[cfg(feature = "embassy-time")]
mod embassy_time;
#[cfg(feature = "embassy-time")]
pub use embassy_time::EmbassyTimer;
#[cfg(test)]
mod test;
use self::radio::{RxQuality, RxStatus};
pub struct Device<R, C, T, G, const N: usize = 256, const D: usize = 1>
where
R: radio::PhyRxTx + Timings,
T: radio::Timer,
C: CryptoFactory + Default,
G: RngCore,
{
crypto: PhantomData<C>,
radio: R,
rng: G,
timer: T,
mac: Mac,
radio_buffer: RadioBuffer<N>,
downlink: Vec<Downlink, D>,
class_c: bool,
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug)]
pub enum Error<R> {
Radio(R),
Mac(mac::Error),
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug)]
pub enum SendResponse {
DownlinkReceived(mac::FcntDown),
SessionExpired,
NoAck,
RxComplete,
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug)]
pub enum JoinResponse {
JoinSuccess,
NoJoinAccept,
}
impl<R> From<mac::Error> for Error<R> {
fn from(e: mac::Error) -> Self {
Error::Mac(e)
}
}
impl<R, C, T, const N: usize> Device<R, C, T, rng::Prng, N>
where
R: radio::PhyRxTx + Timings,
C: CryptoFactory + Default,
T: radio::Timer,
{
pub fn new_with_seed(region: region::Configuration, radio: R, timer: T, seed: u64) -> Self {
Device::new_with_seed_and_session(region, radio, timer, seed, None)
}
pub fn new_with_seed_and_session(
region: region::Configuration,
radio: R,
timer: T,
seed: u64,
session: Option<Session>,
) -> Self {
let rng = rng::Prng::new(seed);
Device::new_with_session(region, radio, timer, rng, session)
}
}
impl<R, C, T, G, const N: usize, const D: usize> Device<R, C, T, G, N, D>
where
R: radio::PhyRxTx + Timings,
C: CryptoFactory + Default,
T: radio::Timer,
G: RngCore,
{
pub fn new(region: region::Configuration, radio: R, timer: T, rng: G) -> Self {
Device::new_with_session(region, radio, timer, rng, None)
}
pub fn new_with_session(
region: region::Configuration,
radio: R,
timer: T,
rng: G,
session: Option<Session>,
) -> Self {
let mut mac = Mac::new(region, R::MAX_RADIO_POWER, R::ANTENNA_GAIN);
if let Some(session) = session {
mac.set_session(session);
}
Self {
crypto: PhantomData,
radio,
rng,
mac,
radio_buffer: RadioBuffer::new(),
timer,
downlink: Vec::new(),
class_c: false,
}
}
pub fn enable_class_c(&mut self) {
self.class_c = true;
}
pub fn disable_class_c(&mut self) {
self.class_c = false;
}
pub fn get_session(&mut self) -> Option<&Session> {
self.mac.get_session()
}
pub fn get_region(&mut self) -> ®ion::Configuration {
&self.mac.region
}
pub fn get_radio(&mut self) -> &R {
&self.radio
}
pub fn get_mut_radio(&mut self) -> &mut R {
&mut self.radio
}
pub fn get_datarate(&mut self) -> DR {
self.mac.configuration.data_rate
}
pub fn set_datarate(&mut self, datarate: DR) {
self.mac.configuration.data_rate = datarate;
}
pub async fn join(&mut self, join_mode: &JoinMode) -> Result<JoinResponse, Error<R::PhyError>> {
match join_mode {
JoinMode::OTAA { deveui, appeui, appkey } => {
let (tx_config, _) = self.mac.join_otaa::<C, G, N>(
&mut self.rng,
NetworkCredentials::new(*appeui, *deveui, *appkey),
&mut self.radio_buffer,
);
let ms = self
.radio
.tx(tx_config, self.radio_buffer.as_ref_for_read())
.await
.map_err(Error::Radio)?;
self.timer.reset();
Ok(self.rx_downlink(&Frame::Join, ms).await?.try_into()?)
}
JoinMode::ABP { newskey, appskey, devaddr } => {
self.mac.join_abp(*newskey, *appskey, *devaddr);
Ok(JoinResponse::JoinSuccess)
}
}
}
pub async fn send(
&mut self,
data: &[u8],
fport: u8,
confirmed: bool,
) -> Result<SendResponse, Error<R::PhyError>> {
let (tx_config, _fcnt_up) = self.mac.send::<C, G, N>(
&mut self.rng,
&mut self.radio_buffer,
&SendData { data, fport, confirmed },
)?;
let ms = self
.radio
.tx(tx_config, self.radio_buffer.as_ref_for_read())
.await
.map_err(Error::Radio)?;
self.timer.reset();
Ok(self.rx_downlink(&Frame::Data, ms).await?.try_into()?)
}
pub fn take_downlink(&mut self) -> Option<Downlink> {
self.downlink.pop()
}
async fn window_complete(&mut self) -> Result<(), Error<R::PhyError>> {
if self.class_c {
let rf_config = self.mac.get_rxc_config();
self.radio.setup_rx(rf_config).await.map_err(Error::Radio)
} else {
self.radio.low_power().await.map_err(Error::Radio)
}
}
async fn between_windows(
&mut self,
duration: u32,
) -> Result<Option<mac::Response>, Error<R::PhyError>> {
if !self.class_c {
self.radio.low_power().await.map_err(Error::Radio)?;
self.timer.at(duration.into()).await;
return Ok(None);
}
#[allow(unused)]
enum RxcWindowResponse<F: futures::Future<Output = ()> + Sized + Unpin> {
Rx(usize, RxQuality, F),
Timeout(u32),
}
async fn rxc_listen_until_timeout<F, R, const N: usize>(
radio: &mut R,
rx_buf: &mut RadioBuffer<N>,
window_duration: u32,
timeout_fut: F,
) -> RxcWindowResponse<F>
where
F: futures::Future<Output = ()> + Sized + Unpin,
R: radio::PhyRxTx + Timings,
{
let rx_fut = radio.rx_continuous(rx_buf.as_mut());
pin_mut!(rx_fut);
match select(rx_fut, timeout_fut).await {
Either::Left((r, timeout_fut)) => match r {
Ok((sz, q)) => RxcWindowResponse::Rx(sz, q, timeout_fut),
_ => {
timeout_fut.await;
RxcWindowResponse::Timeout(0)
}
},
Either::Right(_) => RxcWindowResponse::Timeout(window_duration),
}
}
let rx_config = self.mac.get_rxc_config();
log::debug!("Configuring RXC window with config {}.", rx_config);
self.radio.setup_rx(rx_config).await.map_err(Error::Radio)?;
let mut response = None;
let timeout_fut = self.timer.at(duration.into());
pin_mut!(timeout_fut);
let mut maybe_timeout_fut = Some(timeout_fut);
while let Some(timeout_fut) = maybe_timeout_fut.take() {
match rxc_listen_until_timeout(
&mut self.radio,
&mut self.radio_buffer,
duration,
timeout_fut,
)
.await
{
RxcWindowResponse::Rx(sz, _, timeout_fut) => {
log::debug!("RXC window received {} bytes.", sz);
self.radio_buffer.set_pos(sz);
match self
.mac
.handle_rxc::<C, N, D>(&mut self.radio_buffer, &mut self.downlink)?
{
mac::Response::NoUpdate => {
log::debug!("RXC frame was invalid.");
self.radio_buffer.clear();
maybe_timeout_fut = Some(timeout_fut);
}
r => {
log::debug!("Valid RXC frame received.");
self.radio_buffer.clear();
response = Some(r);
maybe_timeout_fut = Some(timeout_fut);
}
}
}
RxcWindowResponse::Timeout(_) => return Ok(response),
};
}
Ok(response)
}
async fn rx_downlink(
&mut self,
frame: &Frame,
window_delay: u32,
) -> Result<mac::Response, Error<R::PhyError>> {
self.radio_buffer.clear();
let rx1_start_delay = self.mac.get_rx_delay(frame, &Window::_1) + window_delay
- self.radio.get_rx_window_lead_time_ms();
log::debug!("Starting RX1 in {} ms.", rx1_start_delay);
let _ = self.between_windows(rx1_start_delay).await?;
let rx_config =
self.mac.get_rx_config(self.radio.get_rx_window_buffer(), frame, &Window::_1);
log::debug!("Configuring RX1 window with config {}.", rx_config);
self.radio.setup_rx(rx_config).await.map_err(Error::Radio)?;
if let Some(response) = self.rx_listen().await? {
log::debug!("RX1 received {}", response);
return Ok(response);
}
let rx2_start_delay = self.mac.get_rx_delay(frame, &Window::_2) + window_delay
- self.radio.get_rx_window_lead_time_ms();
log::debug!("RX1 did not receive anything. Awaiting RX2 for {} ms.", rx2_start_delay);
let _ = self.between_windows(rx2_start_delay).await?;
let rx_config =
self.mac.get_rx_config(self.radio.get_rx_window_buffer(), frame, &Window::_2);
log::debug!("Configuring RX2 window with config {}.", rx_config);
self.radio.setup_rx(rx_config).await.map_err(Error::Radio)?;
if let Some(response) = self.rx_listen().await? {
log::debug!("RX2 received {}", response);
return Ok(response);
}
log::debug!("RX2 did not receive anything.");
Ok(self.mac.rx2_complete())
}
async fn rx_listen(&mut self) -> Result<Option<mac::Response>, Error<R::PhyError>> {
let response =
match self.radio.rx_single(self.radio_buffer.as_mut()).await.map_err(Error::Radio)? {
RxStatus::Rx(s, _q) => {
self.radio_buffer.set_pos(s);
match self.mac.handle_rx::<C, N, D>(&mut self.radio_buffer, &mut self.downlink)
{
mac::Response::NoUpdate => None,
r => Some(r),
}
}
RxStatus::RxTimeout => None,
};
self.radio_buffer.clear();
self.window_complete().await?;
Ok(response)
}
pub async fn rxc_listen(&mut self) -> Result<mac::Response, Error<R::PhyError>> {
loop {
let (sz, _rx_quality) =
self.radio.rx_continuous(self.radio_buffer.as_mut()).await.map_err(Error::Radio)?;
self.radio_buffer.set_pos(sz);
match self.mac.handle_rxc::<C, N, D>(&mut self.radio_buffer, &mut self.downlink)? {
mac::Response::NoUpdate => {
self.radio_buffer.clear();
}
r => {
self.radio_buffer.clear();
return Ok(r);
}
}
}
}
}
pub trait Timings {
fn get_rx_window_lead_time_ms(&self) -> u32;
fn get_rx_window_buffer(&self) -> u32 {
self.get_rx_window_lead_time_ms()
}
}