diff --git a/README.md b/README.md index 51d3f7e..d0bbc58 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,22 @@ Add this to your `Cargo.toml`: dot15d4 = "0.1.0" ``` +### Configurable features + +* `std`: Enables `std` only features +* `log`: Use the `log` crate for structured logging +* `defmt`: Use the `defmt` crate for structured logging + +### Configurable environment variables + +* `DOT15D4_MAC_MIN_BE` (default: 0): Minimum backoff exponent used in `CSMA` +* `DOT15D4_MAC_MAX_BE` (default: 8): Maximum backoff exponent used in `CSMA` +* `DOT15D4_MAC_UNIT_BACKOFF_DURATION` (default: 320us): The time of one backoff period +* `DOT15D4_MAC_MAX_FRAME_RETRIES` (default: 3): Maximum CCA/ACK rounds +* `DOT15D4_MAC_AIFS_PERIOD` (default: 1ms): The minimal time for the receiving end to go from transmitting to receiving mode when sending an ACK +* `DOT15D4_MAC_SIFS_PERIOD` (default: 1ms): The inter-frame spacing time for short frames +* `DOT15D4_MAC_LIFS_PERIOD` (default: 10ms): The inter-frame spacing time for long frames + For more information, see the [API documentation](https://docs.rs/dot15d4). ## dot15d4 as a binary diff --git a/dot15d4/build.rs b/dot15d4/build.rs new file mode 100644 index 0000000..54c3d3e --- /dev/null +++ b/dot15d4/build.rs @@ -0,0 +1,66 @@ +use std::collections::HashMap; +use std::env; +use std::fmt::Write; +use std::path::PathBuf; + +fn main() { + // (Variable, Type, Default value) + let mut configs: HashMap<&str, (&str, &str)> = HashMap::from([ + ("MAC_MIN_BE", ("u16", "0")), + ("MAC_MAX_BE", ("u16", "8")), + ("MAC_MAX_CSMA_BACKOFFS", ("u16", "16")), + ( + "MAC_UNIT_BACKOFF_DURATION", + ( + "Duration", + "Duration::from_us((UNIT_BACKOFF_PERIOD * SYMBOL_RATE_INV_US) as i64)", + ), + ), + ("MAC_MAX_FRAME_RETIES", ("u16", "3")), + ( + "CSMA_INTER_FRAME_TIME", + ("Duration", "Duration::from_us(1000)"), + ), + ("MAC_AIFS_PERIOD", ("Duration", "Duration::from_us(1000)")), + ("MAC_SIFS_PERIOD", ("Duration", "Duration::from_us(1000)")), + ("MAC_LIFS_PERIOD", ("Duration", "Duration::from_us(10_000)")), + ]); + + // Make sure we get rerun if needed + println!("cargo:rerun-if-changed=build.rs"); + for name in configs.keys() { + println!("cargo:rerun-if-env-changed=DOT15D4_{name}"); + } + + // Collect environment variables + let mut data = String::new(); + // Write preamble + writeln!(data, "use crate::time::Duration;").unwrap(); + writeln!( + data, + "use crate::csma::{{SYMBOL_RATE_INV_US, UNIT_BACKOFF_PERIOD}};" + ) + .unwrap(); + + for (var, value) in std::env::vars() { + if let Some(name) = var.strip_prefix("DOT15D4_") { + // discard from hashmap as a way of consuming the setting + let Some((ty, _)) = configs.remove_entry(name) else { + panic!("Wrong configuration name {name}"); + }; + + // write to file + writeln!(data, "pub const {name}: {ty} = {value};").unwrap(); + } + } + + // Take the remaining configs and write the default value to the file + for (name, (ty, value)) in configs.iter() { + writeln!(data, "pub const {name}: {ty} = {value};").unwrap(); + } + + // Now that we have the code of the configuration, actually write it to a file + let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + let out_file = out_dir.join("config.rs"); + std::fs::write(out_file, data).unwrap(); +} diff --git a/dot15d4/src/csma/mod.rs b/dot15d4/src/csma/mod.rs index f23647c..b3bf55e 100644 --- a/dot15d4/src/csma/mod.rs +++ b/dot15d4/src/csma/mod.rs @@ -248,7 +248,7 @@ where // guaranteed to be exact. This is due to how Rust futures // work and the timer becomes an 'at least this waiting time' // The goal is to transmit an ACK between 1ms and 2ms. - let delay = ACKNOWLEDGEMENT_INTERFRAME_SPACING / 2; + let delay = MAC_AIFS_PERIOD / 2; timer.delay_us(delay.as_us() as u32).await; // We already have the lock on the radio, so start transmitting and do not @@ -383,7 +383,7 @@ where } let mut radio_guard = None; - 'ack: for i_ack in 0..MAC_MAX_FRAME_RETIES + 1 { + 'ack: for i_ack in 1..MAC_MAX_FRAME_RETIES + 1 { // Set vars for CCA let backoff_strategy = transmission::CCABackoffStrategy::new_exponential_backoff(&self.rng); @@ -396,6 +396,7 @@ where &mut tx, &mut timer, backoff_strategy, + &self.driver, ) .await { @@ -413,10 +414,10 @@ where .await; // We expect an ACK to come back AIFS + time for an ACK to travel + SIFS (guard) - // An ACK is 3 bytes + 6 bytes (PHY header) long and should take around 288us at 250kbps to get back - let delay = ACKNOWLEDGEMENT_INTERFRAME_SPACING - + MAC_SIFT_PERIOD - + Duration::from_us(288); + // An ACK is 3 bytes + 6 bytes (PHY header) long + // and should take around 288us at 250kbps to get back + let delay = MAC_AIFS_PERIOD + MAC_SIFS_PERIOD + Duration::from_us(288); + match select::select( Self::wait_for_valid_ack( &mut *radio_guard.unwrap(), @@ -449,7 +450,7 @@ where radio_guard = None; // Wait for SIFS here - let delay = MAC_SIFT_PERIOD.max(Duration::from_us( + let delay = MAC_SIFS_PERIOD.max(Duration::from_us( (TURNAROUND_TIME * SYMBOL_RATE_INV_US) as i64, )); timer.delay_us(delay.as_us() as u32).await; @@ -459,6 +460,8 @@ where // Fail transmission self.driver.error(driver::Error::AckFailed).await; break 'ack; + } else { + self.driver.error(driver::Error::AckRetry(i_ack)).await; } } } @@ -841,9 +844,11 @@ pub mod tests { ); }); radio.wait_until_asserts_are_consumed().await; - assert_eq!( - monitor.errors.receive().await, - driver::Error::CcaFailed, // CCA has failed, so we propagate an error up + assert!( + matches!( + monitor.errors.receive().await, + driver::Error::CcaFailed | driver::Error::CcaBackoff(_), // CCA has failed, so we propagate an error up + ), "Packet transmission should fail due to CCA" ); }) @@ -915,9 +920,11 @@ pub mod tests { inner.total_event_count = 0; }); radio.wait_until_asserts_are_consumed().await; - assert_eq!( - monitor.errors.receive().await, - driver::Error::AckFailed, // ACK has failed, so we propagate an error up + assert!( + matches!( + monitor.errors.receive().await, + driver::Error::AckFailed | driver::Error::AckRetry(_), // ACK has failed, so we propagate an error up + ), "Packet transmission should fail due to ACK not received after to many times" ); }) diff --git a/dot15d4/src/csma/transmission.rs b/dot15d4/src/csma/transmission.rs index 8cdcb5d..5a89865 100644 --- a/dot15d4/src/csma/transmission.rs +++ b/dot15d4/src/csma/transmission.rs @@ -6,10 +6,13 @@ use super::utils; use crate::phy::config; use crate::phy::config::TxConfig; +use crate::phy::driver; +use crate::phy::driver::Driver; use crate::phy::driver::FrameBuffer; use crate::phy::radio::futures::transmit; use crate::phy::radio::Radio; use crate::sync::channel::Sender; +use crate::sync::join::join; use crate::sync::mutex::Mutex; use crate::sync::mutex::MutexGuard; @@ -19,7 +22,8 @@ pub enum TransmissionError { CcaError, } -pub async fn transmit_cca<'m, R, TIMER, Rng>( +#[allow(clippy::too_many_arguments)] +pub async fn transmit_cca<'m, R, TIMER, Rng, D>( radio: &'m Mutex, radio_guard: &mut Option>, channel: config::Channel, @@ -27,11 +31,13 @@ pub async fn transmit_cca<'m, R, TIMER, Rng>( tx_frame: &mut FrameBuffer, timer: &mut TIMER, mut backoff_strategy: CCABackoffStrategy<'_, Rng>, + driver: &D, ) -> Result<(), TransmissionError> where R: Radio, TIMER: DelayNs, Rng: RngCore, + D: Driver, { 'cca: for number_of_backoffs in 1..MAC_MAX_CSMA_BACKOFFS + 1 { // try to transmit @@ -59,9 +65,14 @@ where // Was this the last attempt? if number_of_backoffs == MAC_MAX_CSMA_BACKOFFS { return Err(TransmissionError::CcaError); // Fail transmission + } else { + // Perform backoff and report current status to driver + join( + backoff_strategy.perform_backoff(timer), + driver.error(driver::Error::CcaBackoff(number_of_backoffs)), + ) + .await; } - - backoff_strategy.perform_backoff(timer).await; } Ok(()) diff --git a/dot15d4/src/csma/user_configurable_constants.rs b/dot15d4/src/csma/user_configurable_constants.rs index 173ece0..3539b8d 100644 --- a/dot15d4/src/csma/user_configurable_constants.rs +++ b/dot15d4/src/csma/user_configurable_constants.rs @@ -1,19 +1,29 @@ -#![allow(dead_code)] +/// Export all user definable constants +pub use constants::*; -use crate::time::Duration; +#[cfg(test)] +mod constants { + #![allow(dead_code)] + use crate::csma::{SYMBOL_RATE_INV_US, UNIT_BACKOFF_PERIOD}; + use crate::time::Duration; -use super::constants::{SYMBOL_RATE_INV_US, UNIT_BACKOFF_PERIOD}; + // XXX These are just random numbers I picked by fair dice roll; what should + // they be? + pub const MAC_MIN_BE: u16 = 0; + pub const MAC_MAX_BE: u16 = 8; + pub const MAC_MAX_CSMA_BACKOFFS: u16 = 16; + pub const MAC_UNIT_BACKOFF_DURATION: Duration = + Duration::from_us((UNIT_BACKOFF_PERIOD * SYMBOL_RATE_INV_US) as i64); + pub const MAC_MAX_FRAME_RETIES: u16 = 3; // 0-7 + pub const MAC_INTER_FRAME_TIME: Duration = Duration::from_us(1000); // TODO: XXX + /// AIFS=1ms, for SUN PHY, LECIM PHY, TVWS PHY + pub const MAC_AIFS_PERIOD: Duration = Duration::from_us(1000); + pub const MAC_SIFS_PERIOD: Duration = Duration::from_us(1000); // TODO: SIFS=XXX + pub const MAC_LIFS_PERIOD: Duration = Duration::from_us(10_000); // TODO: LIFS=XXX +} -// XXX These are just random numbers I picked by fair dice roll; what should -// they be? -pub const MAC_MIN_BE: u16 = 0; -pub const MAC_MAX_BE: u16 = 8; -pub const MAC_MAX_CSMA_BACKOFFS: u16 = 16; -pub const MAC_UNIT_BACKOFF_DURATION: Duration = - Duration::from_us((UNIT_BACKOFF_PERIOD * SYMBOL_RATE_INV_US) as i64); -pub const MAC_MAX_FRAME_RETIES: u16 = 3; // 0-7 -pub const _MAC_INTER_FRAME_TIME: Duration = Duration::from_us(1000); // TODO: XXX -/// AIFS=1ms, for SUN PHY, LECIM PHY, TVWS PHY -pub const ACKNOWLEDGEMENT_INTERFRAME_SPACING: Duration = Duration::from_us(1000); -pub const MAC_SIFT_PERIOD: Duration = Duration::from_us(1000); // TODO: SIFS=XXX -pub const MAC_LIFS_PERIOD: Duration = Duration::from_us(10_000); // TODO: LIFS=XXX +#[cfg(not(test))] +mod constants { + #![allow(unused)] + include!(concat!(env!("OUT_DIR"), "/config.rs")); +} diff --git a/dot15d4/src/phy/driver.rs b/dot15d4/src/phy/driver.rs index 4adda31..c4f7efd 100644 --- a/dot15d4/src/phy/driver.rs +++ b/dot15d4/src/phy/driver.rs @@ -3,8 +3,12 @@ use core::future::Future; #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[derive(Debug, PartialEq, Clone, Copy)] pub enum Error { + /// Cca failed, resulting in a backoff (nth try) + CcaBackoff(u16), /// Cca failed after to many fallbacks CcaFailed, + /// Ack failed, resulting in a retry later (nth try) + AckRetry(u16), /// Ack failed, after to many retransmissions AckFailed, /// The buffer did not follow the correct device structure