use heapless::Vec;
use postcard::experimental::max_size::MaxSize;
use serde::{Deserialize, Serialize};
pub mod layout;
pub mod setup;
#[cfg(feature = "synthetic")]
pub mod synthetic;
pub use layout::LayoutId;
pub use setup::{AcquisitionFilter, MeasurementSetup, PpduSet, ReportType, SetupError, StimulusParams};
pub const WIRE_VERSION: u8 = 1;
pub const MAX_CSI_BYTES: usize = 612;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, MaxSize)]
#[non_exhaustive]
pub struct Envelope {
pub version: u8,
pub node_id: [u8; 6],
pub session_id: u32,
pub source: SourceKind,
pub stream_seq: u32,
}
impl Envelope {
pub const fn new(node_id: [u8; 6], session_id: u32, source: SourceKind, stream_seq: u32) -> Self {
Self {
version: WIRE_VERSION,
node_id,
session_id,
source,
stream_seq,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, MaxSize)]
#[non_exhaustive]
pub enum SourceKind {
EspVendor,
Ieee80211bf,
Synthetic,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, MaxSize)]
#[non_exhaustive]
pub enum Body {
Csi(CsiFrame),
Session(SessionInfo),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, MaxSize)]
#[non_exhaustive]
pub struct SessionInfo {
pub chip: Chip,
pub crate_version: [u8; 3],
pub timestamp_us: u64,
pub epoch_unix_us: Option<u64>,
}
impl SessionInfo {
pub const fn new(
chip: Chip,
crate_version: [u8; 3],
timestamp_us: u64,
epoch_unix_us: Option<u64>,
) -> Self {
Self {
chip,
crate_version,
timestamp_us,
epoch_unix_us,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, MaxSize)]
#[non_exhaustive]
pub struct CsiFrame {
pub meta: RxMeta,
pub stimulus: Stimulus,
pub header: Option<HeaderDigest>,
pub payload: CsiPayload,
}
impl CsiFrame {
pub fn new(
meta: RxMeta,
stimulus: Stimulus,
header: Option<HeaderDigest>,
payload: CsiPayload,
) -> Self {
Self {
meta,
stimulus,
header,
payload,
}
}
pub fn transmitter(&self) -> Option<[u8; 6]> {
if let Some(h) = &self.header {
return Some(h.addr2);
}
match self.stimulus {
Stimulus::Ambient { ta }
| Stimulus::Observed { ta, .. }
| Stimulus::Controlled { ta, .. } => Some(ta),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, MaxSize)]
#[non_exhaustive]
pub struct RxMeta {
pub timestamp_us: u64,
pub rssi: i8,
pub noise_floor: i8,
pub channel: u8,
pub secondary: Secondary,
pub bandwidth: Option<Bandwidth>,
pub ppdu: PpduFormat,
pub mcs: Option<u8>,
pub stbc: Option<bool>,
pub sgi: Option<bool>,
pub n_rx: u8,
pub n_ss: Option<u8>,
pub antenna: Option<u8>,
pub sig_len: u16,
pub rx_state: u8,
pub not_sounding: Option<bool>,
pub aggregation: Option<bool>,
pub frame_seq: Option<u16>,
pub vendor: VendorRx,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, MaxSize)]
#[non_exhaustive]
pub enum Secondary {
None,
Above,
Below,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, MaxSize)]
#[non_exhaustive]
pub enum Bandwidth {
Mhz20,
Mhz40,
Mhz80,
Mhz160,
Mhz320,
}
impl Bandwidth {
pub const fn mhz(self) -> u16 {
match self {
Self::Mhz20 => 20,
Self::Mhz40 => 40,
Self::Mhz80 => 80,
Self::Mhz160 => 160,
Self::Mhz320 => 320,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, MaxSize)]
#[non_exhaustive]
pub enum PpduFormat {
Unknown,
Dsss,
NonHt,
Ht,
Vht,
VhtMu,
HeSu,
HeMu,
HeErSu,
HeTb,
Eht,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, MaxSize)]
#[non_exhaustive]
pub enum Stimulus {
Controlled {
setup_id: u8,
instance_id: u16,
ta: [u8; 6],
},
Ambient {
ta: [u8; 6],
},
Observed {
ta: [u8; 6],
dialog_token: u8,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, MaxSize)]
pub struct HeaderDigest {
pub frame_control: u16,
pub addr1: [u8; 6],
pub addr2: [u8; 6],
pub addr3: [u8; 6],
pub seq_ctrl: u16,
}
impl HeaderDigest {
pub const HEADER_LEN: usize = 24;
pub fn parse(hdr: &[u8]) -> Option<Self> {
if hdr.len() < Self::HEADER_LEN {
return None;
}
let frame_control = u16::from_le_bytes([hdr[0], hdr[1]]);
let mut a = [[0u8; 6]; 3];
for (i, addr) in a.iter_mut().enumerate() {
addr.copy_from_slice(&hdr[4 + 6 * i..10 + 6 * i]);
}
let digest = Self {
frame_control,
addr1: a[0],
addr2: a[1],
addr3: a[2],
seq_ctrl: u16::from_le_bytes([hdr[22], hdr[23]]),
};
if digest.has_transmitter() { Some(digest) } else { None }
}
pub const fn frame_type(&self) -> u8 {
((self.frame_control >> 2) & 0b11) as u8
}
pub const fn subtype(&self) -> u8 {
((self.frame_control >> 4) & 0b1111) as u8
}
pub const fn is_retry(&self) -> bool {
self.frame_control & (1 << 11) != 0
}
pub const fn has_transmitter(&self) -> bool {
!(self.frame_type() == 1 && matches!(self.subtype(), 12 | 13))
}
pub const fn sequence_number(&self) -> u16 {
self.seq_ctrl >> 4
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, MaxSize)]
#[non_exhaustive]
pub enum CsiPayload {
EspRaw {
chip: Chip,
layout: LayoutId,
first_word_invalid: bool,
bytes: Vec<i8, MAX_CSI_BYTES>,
},
Grouped {
ng: u8,
nb: u8,
sc_start: i16,
n_sc: u16,
n_rx: u8,
n_tx: u8,
data: Vec<u8, MAX_CSI_BYTES>,
},
Variation {
value: u16,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, MaxSize)]
#[non_exhaustive]
pub enum VendorRx {
None,
EspClassic {
rate: u8,
sig_mode: u8,
smoothing: bool,
fec_ldpc: bool,
ampdu_cnt: u8,
},
EspHe {
rate: u8,
cur_bb_format: u8,
estimate_valid: bool,
estimate_len: u16,
dump_len: u16,
is_group: bool,
rxend_state: u8,
rxmatch: u8,
he_siga1: u32,
he_siga2: u16,
sigb_len: u8,
single_mpdu: bool,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, MaxSize)]
#[non_exhaustive]
pub enum Chip {
Esp32,
Esp32S2,
Esp32S3,
Esp32C3,
Esp32C5,
Esp32C6,
}
pub const MAX_ENCODED_LEN: usize = {
let raw = Envelope::POSTCARD_MAX_SIZE + Body::POSTCARD_MAX_SIZE;
raw + raw / 254 + 2
};
pub fn encode_cobs<'b>(
envelope: &Envelope,
body: &Body,
buf: &'b mut [u8],
) -> Result<&'b mut [u8], postcard::Error> {
postcard::to_slice_cobs(&(envelope, body), buf)
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum DecodeError {
UnsupportedVersion(u8),
Malformed(postcard::Error),
}
impl From<postcard::Error> for DecodeError {
fn from(e: postcard::Error) -> Self {
Self::Malformed(e)
}
}
pub fn decode_cobs(frame: &mut [u8]) -> Result<(Envelope, Body), DecodeError> {
let len = cobs::decode_in_place(frame)
.map_err(|_| DecodeError::Malformed(postcard::Error::DeserializeBadEncoding))?;
decode(&frame[..len])
}
pub fn decode(bytes: &[u8]) -> Result<(Envelope, Body), DecodeError> {
let (envelope, rest) = postcard::take_from_bytes::<Envelope>(bytes)?;
if envelope.version != WIRE_VERSION {
return Err(DecodeError::UnsupportedVersion(envelope.version));
}
let (body, _) = postcard::take_from_bytes::<Body>(rest)?;
Ok((envelope, body))
}