use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CsiFrame {
pub metadata: CsiMetadata,
pub subcarriers: Vec<SubcarrierData>,
}
impl CsiFrame {
pub fn subcarrier_count(&self) -> usize {
self.subcarriers.len()
}
pub fn to_amplitude_phase(&self) -> (Vec<f64>, Vec<f64>) {
let amplitudes: Vec<f64> = self
.subcarriers
.iter()
.map(|sc| (sc.i as f64 * sc.i as f64 + sc.q as f64 * sc.q as f64).sqrt())
.collect();
let phases: Vec<f64> = self
.subcarriers
.iter()
.map(|sc| (sc.q as f64).atan2(sc.i as f64))
.collect();
(amplitudes, phases)
}
pub fn mean_amplitude(&self) -> f64 {
if self.subcarriers.is_empty() {
return 0.0;
}
let sum: f64 = self
.subcarriers
.iter()
.map(|sc| (sc.i as f64 * sc.i as f64 + sc.q as f64 * sc.q as f64).sqrt())
.sum();
sum / self.subcarriers.len() as f64
}
pub fn is_valid(&self) -> bool {
!self.subcarriers.is_empty() && self.subcarriers.iter().any(|sc| sc.i != 0 || sc.q != 0)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CsiMetadata {
pub timestamp: DateTime<Utc>,
pub node_id: u8,
pub n_antennas: u8,
pub n_subcarriers: u16,
pub channel_freq_mhz: u32,
pub rssi_dbm: i8,
pub noise_floor_dbm: i8,
pub bandwidth: Bandwidth,
pub antenna_config: AntennaConfig,
pub sequence: u32,
pub ppdu_type: PpduType,
pub adr018_flags: Adr018Flags,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PpduType {
HtLegacy,
HeSu,
HeMu,
HeTb,
Unknown,
}
impl PpduType {
pub fn from_byte(b: u8) -> Self {
match b {
0 => Self::HtLegacy,
1 => Self::HeSu,
2 => Self::HeMu,
3 => Self::HeTb,
_ => Self::Unknown,
}
}
pub fn to_byte(self) -> u8 {
match self {
Self::HtLegacy => 0,
Self::HeSu => 1,
Self::HeMu => 2,
Self::HeTb => 3,
Self::Unknown => 0xFF,
}
}
pub fn is_he(self) -> bool {
matches!(self, Self::HeSu | Self::HeMu | Self::HeTb)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Adr018Flags {
pub bw40: bool,
pub stbc: bool,
pub ldpc: bool,
pub ieee802154_sync_valid: bool,
}
impl Adr018Flags {
pub fn from_byte(b: u8) -> Self {
Self {
bw40: (b & 0x01) != 0,
stbc: (b & 0x04) != 0,
ldpc: (b & 0x08) != 0,
ieee802154_sync_valid: (b & 0x10) != 0,
}
}
pub fn to_byte(self) -> u8 {
let mut b = 0u8;
if self.bw40 { b |= 0x01; }
if self.stbc { b |= 0x04; }
if self.ldpc { b |= 0x08; }
if self.ieee802154_sync_valid { b |= 0x10; }
b
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Bandwidth {
Bw20,
Bw40,
Bw80,
Bw160,
}
impl Bandwidth {
pub fn expected_subcarriers(&self) -> usize {
match self {
Bandwidth::Bw20 => 56,
Bandwidth::Bw40 => 114,
Bandwidth::Bw80 => 242,
Bandwidth::Bw160 => 484,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct AntennaConfig {
pub tx_antennas: u8,
pub rx_antennas: u8,
}
impl Default for AntennaConfig {
fn default() -> Self {
Self {
tx_antennas: 1,
rx_antennas: 1,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct SubcarrierData {
pub i: i16,
pub q: i16,
pub index: i16,
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
fn make_test_frame() -> CsiFrame {
CsiFrame {
metadata: CsiMetadata {
timestamp: Utc::now(),
node_id: 1,
n_antennas: 1,
n_subcarriers: 3,
channel_freq_mhz: 2437,
rssi_dbm: -50,
noise_floor_dbm: -95,
bandwidth: Bandwidth::Bw20,
antenna_config: AntennaConfig::default(),
sequence: 1,
ppdu_type: PpduType::HtLegacy,
adr018_flags: Adr018Flags::default(),
},
subcarriers: vec![
SubcarrierData {
i: 100,
q: 0,
index: -28,
},
SubcarrierData {
i: 0,
q: 50,
index: -27,
},
SubcarrierData {
i: 30,
q: 40,
index: -26,
},
],
}
}
#[test]
fn test_amplitude_phase_conversion() {
let frame = make_test_frame();
let (amps, phases) = frame.to_amplitude_phase();
assert_eq!(amps.len(), 3);
assert_eq!(phases.len(), 3);
assert_relative_eq!(amps[0], 100.0, epsilon = 0.01);
assert_relative_eq!(phases[0], 0.0, epsilon = 0.01);
assert_relative_eq!(amps[1], 50.0, epsilon = 0.01);
assert_relative_eq!(phases[1], std::f64::consts::FRAC_PI_2, epsilon = 0.01);
assert_relative_eq!(amps[2], 50.0, epsilon = 0.01);
}
#[test]
fn test_mean_amplitude() {
let frame = make_test_frame();
let mean = frame.mean_amplitude();
assert_relative_eq!(mean, 200.0 / 3.0, epsilon = 0.1);
}
#[test]
fn test_is_valid() {
let frame = make_test_frame();
assert!(frame.is_valid());
let empty = CsiFrame {
metadata: frame.metadata.clone(),
subcarriers: vec![],
};
assert!(!empty.is_valid());
}
#[test]
fn test_bandwidth_subcarriers() {
assert_eq!(Bandwidth::Bw20.expected_subcarriers(), 56);
assert_eq!(Bandwidth::Bw40.expected_subcarriers(), 114);
}
}