use core::marker::PhantomData;
use num::rational::Ratio;
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PhyRate<D: RateDirection> {
HrDsss(HrDsssRate),
Ofdm(OfdmRate),
Ht(HtRate<D>),
}
impl<D: RateDirection> PhyRate<D> {
pub const fn as_hardware_rate(&self) -> u8 {
match self {
Self::HrDsss(hr_dsss) => hr_dsss.rate + 4 * (hr_dsss.short_preamble as u8),
Self::Ofdm(ofdm) => *ofdm as u8,
Self::Ht(ht) => 0x10 + ht.mcs_index * (1 + ht.short_gi as u8),
}
}
}
impl<D: RateDirection> Default for PhyRate<D> {
fn default() -> Self {
Self::Ofdm(OfdmRate::default())
}
}
impl<D: RateDirection> From<HrDsssRate> for PhyRate<D> {
fn from(value: HrDsssRate) -> Self {
Self::HrDsss(value)
}
}
impl<D: RateDirection> From<OfdmRate> for PhyRate<D> {
fn from(value: OfdmRate) -> Self {
Self::Ofdm(value)
}
}
impl<D: RateDirection> From<HtRate<D>> for PhyRate<D> {
fn from(value: HtRate<D>) -> Self {
Self::Ht(value)
}
}
impl<D: RateDirection> Rate for PhyRate<D> {
fn bandwidth(&self) -> usize {
match self {
Self::HrDsss(rate) => rate.bandwidth(),
Self::Ofdm(rate) => rate.bandwidth(),
Self::Ht(rate) => rate.bandwidth(),
}
}
fn constellation(&self) -> Constellation {
match self {
Self::HrDsss(rate) => rate.constellation(),
Self::Ofdm(rate) => rate.constellation(),
Self::Ht(rate) => rate.constellation(),
}
}
fn data_rate(&self) -> Ratio<usize> {
match self {
Self::HrDsss(rate) => rate.data_rate(),
Self::Ofdm(rate) => rate.data_rate(),
Self::Ht(rate) => rate.data_rate(),
}
}
}
pub type TxPhyRate = PhyRate<Tx>;
pub type RxPhyRate = PhyRate<Rx>;
const KILO: usize = 1_000;
const MEGA: usize = 1_000_000;
const GIGA: usize = 1_000_000_000;
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Constellation {
Bpsk = 1,
Qpsk = 2,
Qam16 = 4,
Qam64 = 6,
}
pub trait Rate {
fn bandwidth(&self) -> usize;
fn constellation(&self) -> Constellation;
fn data_rate(&self) -> Ratio<usize>;
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Default)]
pub struct HrDsssRate {
rate: u8,
short_preamble: bool,
}
impl HrDsssRate {
pub const fn new(rate: u8, short_preamble: bool) -> Option<Self> {
if rate > 3 || (rate == 0 && short_preamble) {
None
} else {
Some(Self {
rate,
short_preamble,
})
}
}
pub const fn set_rate(&mut self, rate: u8) {
if (rate == 0 && !self.short_preamble) || rate <= 3 {
self.rate = rate;
}
}
pub const fn set_short_preamble(&mut self, short_preamble: bool) {
if self.rate != 0 {
self.short_preamble = short_preamble;
}
}
}
impl Rate for HrDsssRate {
fn constellation(&self) -> Constellation {
if self.rate.is_multiple_of(2) {
Constellation::Bpsk
} else {
Constellation::Qpsk
}
}
fn bandwidth(&self) -> usize {
22 * MEGA
}
fn data_rate(&self) -> Ratio<usize> {
(match self.rate {
0 => MEGA,
1 => 2 * MEGA,
2 => (11 * MEGA) / 2,
3 => 11 * MEGA,
_ => unreachable!(),
})
.into()
}
}
pub trait OfdmBasedRate: Rate {
fn data_subcarriers(&self) -> usize;
fn pilot_subcarriers(&self) -> usize;
fn modulated_subcarriers(&self) -> usize {
self.data_subcarriers() + self.pilot_subcarriers()
}
fn total_subcarriers(&self) -> usize;
fn subcarrier_spacing(&self) -> usize {
self.bandwidth() / self.total_subcarriers()
}
fn coding_rate(&self) -> Ratio<usize>;
fn guard_interval_duration(&self) -> usize;
fn dft_period(&self) -> usize {
(self.total_subcarriers() * KILO) / 20
}
fn symbol_duration(&self) -> usize {
self.dft_period() + self.guard_interval_duration()
}
fn symbol_rate(&self) -> Ratio<usize> {
Ratio::new(GIGA, self.symbol_duration())
}
fn coded_bits_per_ofdm_symbol(&self) -> usize {
self.constellation() as usize * self.data_subcarriers()
}
fn data_bits_per_ofdm_symbol(&self) -> usize {
(self.coding_rate() * self.coded_bits_per_ofdm_symbol()).to_integer()
}
}
#[allow(missing_docs)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum OfdmRate {
#[default]
Mbits6 = 0x0b,
Mbits9 = 0x0f,
Mbits12 = 0x0a,
Mbits18 = 0x0e,
Mbits24 = 0x09,
Mbits36 = 0x0d,
Mbits48 = 0x08,
Mbits54 = 0x0c,
}
impl OfdmRate {}
impl Rate for OfdmRate {
fn constellation(&self) -> Constellation {
match 3 - ((*self as usize - 8) & 0b11) {
0 => Constellation::Bpsk,
1 => Constellation::Qpsk,
2 => Constellation::Qam16,
3 => Constellation::Qam64,
_ => unreachable!(),
}
}
fn bandwidth(&self) -> usize {
20 * MEGA
}
fn data_rate(&self) -> Ratio<usize> {
self.symbol_rate() * self.data_bits_per_ofdm_symbol()
}
}
impl OfdmBasedRate for OfdmRate {
fn data_subcarriers(&self) -> usize {
48
}
fn pilot_subcarriers(&self) -> usize {
4
}
fn total_subcarriers(&self) -> usize {
64
}
fn coding_rate(&self) -> Ratio<usize> {
if *self == Self::Mbits48 {
Ratio::new(2, 3)
} else if (*self as usize - 8) & 0b100 != 0 {
Ratio::new(3, 4)
} else {
Ratio::new(1, 2)
}
}
fn guard_interval_duration(&self) -> usize {
800
}
}
pub trait RateDirection {
const MAX_HT_RATE: u8;
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Tx;
impl RateDirection for Tx {
const MAX_HT_RATE: u8 = 7;
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Rx;
impl RateDirection for Rx {
const MAX_HT_RATE: u8 = 32;
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HtRate<D: RateDirection> {
mcs_index: u8,
short_gi: bool,
cbw40: bool,
_phantom: PhantomData<D>,
}
impl<D: RateDirection> HtRate<D> {
pub const fn new(mcs_index: u8, short_gi: bool, cbw40: bool) -> Option<Self> {
if mcs_index <= D::MAX_HT_RATE {
Some(Self {
mcs_index,
short_gi,
cbw40,
_phantom: PhantomData,
})
} else {
None
}
}
pub const fn spatial_streams(&self) -> usize {
if self.mcs_index < 32 {
self.mcs_index as usize / 8
} else {
match self.mcs_index {
32 => 1,
33..=38 => 2,
39..=52 => 3,
53..=76 => 4,
_ => unreachable!(),
}
}
}
pub const fn mcs_index(&self) -> u8 {
self.mcs_index
}
pub const fn set_mcs_index(&mut self, mcs_index: u8) {
if mcs_index <= D::MAX_HT_RATE {
self.mcs_index = mcs_index;
}
}
pub const fn short_gi(&self) -> bool {
self.short_gi
}
pub const fn set_short_gi(&mut self, short_gi: bool) {
self.short_gi = short_gi;
}
pub const fn cbw40(&self) -> bool {
self.cbw40
}
pub const fn set_cbw40(&mut self, cbw40: bool) {
self.cbw40 = cbw40;
}
}
impl<D: RateDirection> Default for HtRate<D> {
fn default() -> Self {
Self {
mcs_index: 0,
short_gi: false,
cbw40: false,
_phantom: PhantomData,
}
}
}
impl<D: RateDirection> Rate for HtRate<D> {
fn constellation(&self) -> Constellation {
if self.mcs_index == 32 {
return Constellation::Bpsk;
}
match self.mcs_index % 8 {
0 => Constellation::Bpsk,
1 | 2 => Constellation::Qpsk,
3 | 4 => Constellation::Qam16,
5..=7 => Constellation::Qam64,
_ => {
unreachable!();
}
}
}
fn bandwidth(&self) -> usize {
(if self.cbw40 { 40 } else { 20 }) * MEGA
}
fn data_rate(&self) -> Ratio<usize> {
self.symbol_rate() * self.data_bits_per_ofdm_symbol()
}
}
impl<D: RateDirection> OfdmBasedRate for HtRate<D> {
fn data_subcarriers(&self) -> usize {
if self.cbw40 {
108
} else if self.mcs_index == 32 {
48
} else {
52
}
}
fn pilot_subcarriers(&self) -> usize {
if self.cbw40 { 6 } else { 4 }
}
fn total_subcarriers(&self) -> usize {
if self.cbw40 && self.mcs_index != 32 {
128
} else {
64
}
}
fn coding_rate(&self) -> Ratio<usize> {
if self.mcs_index == 32 {
return Ratio::new(1, 2);
}
let (numer, denom) = match self.mcs_index % 8 {
0 | 1 | 3 => (1, 2),
5 => (2, 3),
2 | 4 | 6 => (3, 4),
7 => (5, 6),
_ => {
unreachable!();
}
};
Ratio::new(numer, denom)
}
fn guard_interval_duration(&self) -> usize {
if self.short_gi { 400 } else { 800 }
}
}