#[cfg(any(feature = "async-print", feature = "auto"))]
use embassy_time::with_timeout;
use embassy_futures::join::{join, join3};
use embassy_futures::select::{Either, select};
use embassy_time::{Duration, Timer};
use enumset::EnumSet;
#[cfg(feature = "esp32c5")]
use esp_radio::wifi::BandMode;
use esp_radio::wifi::sta::StationConfig;
use esp_radio::wifi::{Protocol, Protocols, SecondaryChannel, WifiController};
use crate::radio::RadioInterfaces;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::signal::Signal;
use portable_atomic::Ordering;
use crate::wifi::ap::{ap_init, run_ap};
use crate::wifi::sta::{run_sta_connect, sta_init};
use crate::CsiConfig;
use crate::MeasurementSetup;
use crate::emitter::{EmitterConfig, run_emitter};
use crate::initiator::esp_now::run_esp_now_initiator;
use crate::initiator::esp_now_simplex::run_esp_now_simplex_source;
use crate::model::{Mode, NodeView, SimplexConfig};
use crate::responder::esp_now::run_esp_now_responder;
use crate::responder::esp_now_simplex::run_esp_now_simplex_peer;
use crate::profile::{RadioProfile, StandardProfile};
use crate::csi::delivery::{
CsiNodeClient, build_csi_config, run_process_csi_packet, set_csi,
};
use crate::log_ln;
use crate::radio::{apply_ht40_channel, idle_station_for_espnow, suppress_espnow_rx};
#[cfg(feature = "esp32c5")]
use crate::radio::{apply_band_auto, apply_band_for_channel};
use crate::stats::set_seq_drop_detection;
pub(crate) static STOP_SIGNAL: Signal<CriticalSectionRawMutex, ()> = Signal::new();
#[cfg(feature = "esp32c5")]
const C5_RADIO_SETTLE_MS: u64 = 60;
async fn c5_radio_settle() {
#[cfg(feature = "esp32c5")]
Timer::after(Duration::from_millis(C5_RADIO_SETTLE_MS)).await;
}
async fn csi_data_collection(client: &mut CsiNodeClient, duration: u64) {
#[cfg(any(feature = "async-print", feature = "auto"))]
if crate::logging::logging::is_async_logging_active() {
with_timeout(Duration::from_secs(duration), async {
loop {
client.print_csi_w_metadata().await;
}
})
.await
.unwrap_err();
client.send_stop().await;
return;
}
#[cfg(not(any(feature = "async-print", feature = "auto")))]
{
let _ = client;
}
Timer::after(Duration::from_secs(duration)).await;
client.send_stop().await;
}
async fn wait_for_stop() {
STOP_SIGNAL.wait().await;
STOP_SIGNAL.signal(());
}
async fn stop_after_duration(duration: u64) {
match select(
STOP_SIGNAL.wait(),
Timer::after(Duration::from_secs(duration)),
)
.await
{
Either::First(_) | Either::Second(_) => STOP_SIGNAL.signal(()),
}
}
#[derive(Debug, Clone)]
pub struct WifiSnifferConfig {
#[allow(dead_code)]
mac_filter: Option<[u8; 6]>,
channel: u8,
reporting: crate::Reporting,
}
impl WifiSnifferConfig {
pub const fn session_role() -> Option<crate::SessionRole> {
None
}
pub const fn sensing_role() -> crate::SensingRole {
crate::SensingRole::Receiver
}
pub fn get_reporting(&self) -> crate::Reporting {
self.reporting
}
pub fn with_report_to(mut self, to: crate::ReportTo) -> Self {
self.reporting = crate::Reporting::Report { to };
self
}
pub fn with_threshold(mut self, threshold: crate::Threshold) -> Self {
self.reporting = crate::Reporting::ThresholdBased(threshold);
self
}
pub fn with_decimation(mut self, n: u16) -> Self {
self.reporting = crate::Reporting::Decimate(n.max(1));
self
}
}
impl Default for WifiSnifferConfig {
fn default() -> Self {
Self {
mac_filter: None,
reporting: crate::Reporting::HOST,
channel: 1,
}
}
}
impl WifiSnifferConfig {
pub fn with_channel(mut self, channel: u8) -> Self {
self.channel = channel;
self
}
pub fn channel(&self) -> u8 {
self.channel
}
}
#[derive(Debug, Clone)]
pub struct WifiStationConfig {
pub client_config: StationConfig,
pub channel_hint: Option<u8>,
session: Option<crate::SessionRole>,
reporting: crate::Reporting,
}
impl WifiStationConfig {
pub fn new(client_config: StationConfig) -> Self {
Self {
client_config,
channel_hint: None,
session: Some(crate::SessionRole::Initiator),
reporting: crate::Reporting::HOST,
}
}
pub fn session(mut self, role: Option<crate::SessionRole>) -> Self {
self.session = role;
self
}
pub fn reporting(mut self, reporting: crate::Reporting) -> Self {
self.reporting = reporting;
self
}
pub fn session_role(&self) -> Option<crate::SessionRole> {
self.session
}
pub fn sensing_role(&self) -> crate::SensingRole {
match self.session {
Some(crate::SessionRole::Initiator) => crate::SensingRole::TransmitterReceiver,
_ => crate::SensingRole::Receiver,
}
}
pub fn get_reporting(&self) -> crate::Reporting {
self.reporting
}
pub fn with_channel_hint(mut self, channel: u8) -> Self {
self.channel_hint = Some(channel);
self
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for WifiStationConfig {
fn format(&self, fmt: defmt::Formatter<'_>) {
defmt::write!(fmt, "WifiStationConfig {{ client_config: <opaque> }}");
}
}
pub struct WifiApConfig {
pub ap_config: esp_radio::wifi::ap::AccessPointConfig,
pub channel: u8,
pub secondary_channel: Option<SecondaryChannel>,
pub ap_ipv4: core::net::Ipv4Addr,
pub lease_ipv4: core::net::Ipv4Addr,
pub lease_count: u8,
pub serve_dhcp: bool,
reporting: crate::Reporting,
pub sync_burst: bool,
}
impl WifiApConfig {
pub fn new(
ap_config: esp_radio::wifi::ap::AccessPointConfig,
channel: u8,
secondary: Option<SecondaryChannel>,
) -> Self {
Self {
ap_config,
channel,
secondary_channel: secondary,
ap_ipv4: core::net::Ipv4Addr::new(192, 168, 13, 1),
lease_ipv4: core::net::Ipv4Addr::new(192, 168, 13, 2),
lease_count: 1,
serve_dhcp: true,
sync_burst: false,
reporting: crate::Reporting::HOST,
}
}
pub const fn session_role() -> Option<crate::SessionRole> {
Some(crate::SessionRole::Initiator)
}
pub const fn sensing_role() -> crate::SensingRole {
crate::SensingRole::Receiver
}
pub fn reporting(mut self, reporting: crate::Reporting) -> Self {
self.reporting = reporting;
self
}
pub fn get_reporting(&self) -> crate::Reporting {
self.reporting
}
pub fn with_ipv4(mut self, ap: core::net::Ipv4Addr, lease: core::net::Ipv4Addr) -> Self {
self.ap_ipv4 = ap;
self.lease_ipv4 = lease;
self
}
pub fn with_lease_pool(mut self, count: u8) -> Self {
self.lease_count = count.max(1);
self
}
pub fn lease_ip_at(&self, index: u8) -> core::net::Ipv4Addr {
let idx = index.min(self.lease_count.saturating_sub(1));
let mut oct = self.lease_ipv4.octets();
oct[3] = oct[3].saturating_add(idx);
core::net::Ipv4Addr::from(oct)
}
pub fn lease_pool(&self) -> heapless::Vec<core::net::Ipv4Addr, 8> {
let mut v = heapless::Vec::new();
for i in 0..self.lease_count.min(8) {
let _ = v.push(self.lease_ip_at(i));
}
v
}
pub fn with_dhcp_server(mut self, enabled: bool) -> Self {
self.serve_dhcp = enabled;
self
}
pub fn with_sync_burst(mut self, enabled: bool) -> Self {
self.sync_burst = enabled;
self
}
pub fn channel(&self) -> u8 {
self.channel
}
pub fn secondary_channel(&self) -> Option<SecondaryChannel> {
self.secondary_channel
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for WifiApConfig {
fn format(&self, fmt: defmt::Formatter<'_>) {
defmt::write!(fmt, "WifiApConfig {{ ap_config: <opaque> }}");
}
}
const UNSET_MAC: [u8; 6] = [0; 6];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IOTaskConfig {
pub tx_enabled: bool,
pub rx_enabled: bool,
}
impl IOTaskConfig {
pub const fn new(tx_enabled: bool, rx_enabled: bool) -> Self {
Self {
tx_enabled,
rx_enabled,
}
}
}
impl Default for IOTaskConfig {
fn default() -> Self {
Self::new(true, true)
}
}
pub struct NodeHardware<'a> {
interfaces: RadioInterfaces,
controller: &'a mut WifiController<'static>,
}
impl<'a> NodeHardware<'a> {
pub fn new(controller: &'a mut WifiController<'static>) -> Self {
Self {
interfaces: RadioInterfaces::claim(controller),
controller,
}
}
}
pub(crate) fn reset_globals() {
crate::csi::delivery::reset();
}
pub struct CsiNode<'a> {
mode: Mode,
io_tasks: IOTaskConfig,
setup: MeasurementSetup,
hardware: NodeHardware<'a>,
protocol: Option<Protocol>,
flood_unsolicited_reply: bool,
profile: &'static dyn RadioProfile,
}
impl<'a> CsiNode<'a> {
pub fn new(mode: Mode, setup: MeasurementSetup, hardware: NodeHardware<'a>) -> Self {
Self {
mode,
io_tasks: IOTaskConfig::default(),
setup,
hardware,
protocol: None,
flood_unsolicited_reply: false,
profile: &StandardProfile,
}
}
pub fn esp_now(
config: crate::EspNowConfig,
setup: MeasurementSetup,
hardware: NodeHardware<'a>,
) -> Self {
Self::new(Mode::EspNow(config), setup, hardware)
}
pub fn esp_now_simplex_source(
config: crate::EspNowConfig,
setup: MeasurementSetup,
hardware: NodeHardware<'a>,
) -> Self {
Self::new(
Mode::EspNowSimplex(SimplexConfig::source(config)),
setup,
hardware,
)
}
pub fn esp_now_simplex_peer(
channel: u8,
setup: MeasurementSetup,
hardware: NodeHardware<'a>,
) -> Self {
Self::new(
Mode::EspNowSimplex(SimplexConfig::peer(channel)),
setup,
hardware,
)
}
pub fn sniffer(
config: WifiSnifferConfig,
setup: MeasurementSetup,
hardware: NodeHardware<'a>,
) -> Self {
Self::new(Mode::Sniffer(config), setup, hardware)
}
pub fn station(
config: WifiStationConfig,
setup: MeasurementSetup,
hardware: NodeHardware<'a>,
) -> Self {
Self::new(Mode::Station(config), setup, hardware)
}
pub fn access_point(
config: WifiApConfig,
setup: MeasurementSetup,
hardware: NodeHardware<'a>,
) -> Self {
Self::new(Mode::AccessPoint(config), setup, hardware)
}
pub fn emitter(config: EmitterConfig, hardware: NodeHardware<'a>) -> Self {
Self::new(Mode::Emitter(config), MeasurementSetup::new(), hardware)
}
pub fn mode(&self) -> &Mode {
&self.mode
}
pub fn session_role(&self) -> Option<crate::SessionRole> {
self.mode.session_role()
}
pub fn sensing_role(&self) -> crate::SensingRole {
self.mode.sensing_role()
}
pub fn reporting(&self) -> crate::Reporting {
self.mode.reporting()
}
pub fn measurement_setup(&self) -> &MeasurementSetup {
&self.setup
}
pub fn get_emitter_config(&self) -> Option<&EmitterConfig> {
match &self.mode {
Mode::Emitter(config) => Some(config),
_ => None,
}
}
pub fn apply_measurement_setup(
&mut self,
setup: &crate::wire::MeasurementSetup,
) -> Result<(), crate::wire::SetupError> {
use crate::wire::{ReportType, SetupError};
let width = self.configured_bandwidth();
let sounds = match &self.mode {
Mode::EspNow(c) => c.session_role() == crate::SessionRole::Initiator,
Mode::EspNowSimplex(c) => c.is_source(),
Mode::Station(c) => c.session_role() == Some(crate::SessionRole::Initiator),
Mode::AccessPoint(_) | Mode::Emitter(_) => true,
Mode::Sniffer(_) => false,
};
let acq = &setup.acquisition;
if let (Some(want), Some(have)) = (acq.bandwidth, width)
&& want != have
{
return Err(SetupError::UnsupportedAcquisition("bandwidth"));
}
let formats = acq.ppdu_formats;
let he_only = formats.only_he();
if he_only && !cfg!(any(feature = "esp32c5", feature = "esp32c6")) {
return Err(SetupError::UnsupportedAcquisition("ppdu_formats"));
}
if !formats.is_any() && !he_only && !formats.excludes_legacy() {
return Err(SetupError::UnsupportedAcquisition("ppdu_formats"));
}
let mut filter = acq.transmitter;
let mut rate = None;
if let Some(st) = &setup.stimulus {
if !sounds {
return Err(SetupError::NoStimulus);
}
if st.report != ReportType::Raw {
return Err(SetupError::UnsupportedStimulus("report"));
}
if st.ng != 1 || st.nb != 8 {
return Err(SetupError::UnsupportedStimulus("ng/nb"));
}
if st.trigger_based {
return Err(SetupError::UnsupportedStimulus("trigger_based"));
}
if width.is_some_and(|w| w != st.bandwidth) {
return Err(SetupError::UnsupportedStimulus("bandwidth"));
}
match st.responders.as_slice() {
[] => {}
[one] => match filter {
Some(t) if t != *one => {
return Err(SetupError::UnsupportedStimulus("responders"));
}
_ => filter = Some(*one),
},
_ => return Err(SetupError::UnsupportedStimulus("responders")),
}
rate = Some(st.rate_hz());
}
if let Some(ch) = acq.channel {
self.set_mode_channel(ch);
}
if let Some(mac) = filter {
crate::set_csi_peer_filter(mac);
}
if he_only {
#[cfg(any(feature = "esp32c5", feature = "esp32c6"))]
{
self.setup.set_acquisition(Some(CsiConfig::he20()));
}
} else if formats.excludes_legacy() {
#[cfg(any(feature = "esp32c5", feature = "esp32c6"))]
{
let mut c = self.setup.get_acquisition().cloned().unwrap_or_default();
c.acquire_csi_legacy = 0;
#[cfg(feature = "esp32c5")]
{
c.acquire_csi_force_lltf = false;
}
self.setup.set_acquisition(Some(c));
}
#[cfg(not(any(feature = "esp32c5", feature = "esp32c6")))]
crate::set_csi_min_sig_mode(1);
}
if let Some(hz) = rate {
match &mut self.mode {
Mode::Emitter(c) => {
c.period = Duration::from_micros(1_000_000 / hz.max(1) as u64);
}
_ => self.setup.set_periodicity_hz(Some(hz)),
}
}
self.setup.set_id(setup.id);
Ok(())
}
fn configured_bandwidth(&self) -> Option<crate::wire::Bandwidth> {
use crate::wire::Bandwidth;
let forty = match &self.mode {
Mode::EspNow(c) => c.secondary_channel().is_some(),
Mode::EspNowSimplex(c) => c.inner().secondary_channel().is_some(),
Mode::AccessPoint(c) => c.secondary_channel.is_some(),
Mode::Emitter(c) => c.phy.is_forty(),
Mode::Station(_) | Mode::Sniffer(_) => return None,
};
Some(if forty { Bandwidth::Mhz40 } else { Bandwidth::Mhz20 })
}
fn set_mode_channel(&mut self, ch: u8) {
match &mut self.mode {
Mode::EspNow(c) => c.channel = ch,
Mode::EspNowSimplex(c) => c.set_channel(ch),
Mode::Sniffer(c) => c.channel = ch,
Mode::Station(c) => c.channel_hint = Some(ch),
Mode::AccessPoint(c) => {
c.channel = ch;
c.ap_config = c.ap_config.clone().with_channel(ch);
}
Mode::Emitter(c) => c.channel = ch,
}
}
pub fn set_measurement_setup(&mut self, setup: MeasurementSetup) {
self.setup = setup;
}
pub fn set_csi_config(&mut self, config: CsiConfig) {
self.setup.set_acquisition(Some(config));
}
pub fn set_station_config(&mut self, config: WifiStationConfig) {
if let Mode::Station(_) = &self.mode {
self.mode = Mode::Station(config);
}
}
pub fn set_periodicity_hz(&mut self, hz: u16) {
self.setup.set_periodicity_hz(Some(hz));
}
pub fn set_io_tasks(&mut self, io_tasks: IOTaskConfig) {
self.io_tasks = io_tasks;
}
pub fn set_tx_enabled(&mut self, enabled: bool) {
self.io_tasks.tx_enabled = enabled;
}
pub fn set_rx_enabled(&mut self, enabled: bool) {
self.io_tasks.rx_enabled = enabled;
}
pub fn get_io_tasks(&self) -> IOTaskConfig {
self.io_tasks
}
pub fn set_mode(&mut self, mode: Mode) {
self.mode = mode;
}
pub fn set_protocol(&mut self, protocol: Protocol) {
self.protocol = Some(protocol);
}
pub fn set_radio_profile(&mut self, profile: &'static dyn RadioProfile) {
self.profile = profile;
}
pub fn set_flood_unsolicited_reply(&mut self, enabled: bool) {
self.flood_unsolicited_reply = enabled;
}
pub async fn run_duration(&mut self, duration: u64, client: &mut CsiNodeClient) {
self.run_inner(Some(duration), Some(client)).await;
}
async fn run_inner(&mut self, duration: Option<u64>, client: Option<&mut CsiNodeClient>) {
#[cfg(feature = "statistics")]
crate::stats::stats_begin_run();
if let (Some(want), Some(have)) = (self.setup.get_bandwidth(), self.configured_bandwidth())
&& want != have
{
let mhz = |b: crate::wire::Bandwidth| -> u16 {
use crate::wire::Bandwidth::*;
match b {
Mhz20 => 20,
Mhz40 => 40,
Mhz80 => 80,
Mhz160 => 160,
Mhz320 => 320,
}
};
log_ln!(
"Measurement setup asks for {} MHz but the mode is configured for {} MHz; running at {} MHz",
mhz(want),
mhz(have),
mhz(have)
);
}
let interfaces = &mut self.hardware.interfaces;
let controller = &mut self.hardware.controller;
crate::wifi::sta::set_icmp_flood_unsolicited(self.flood_unsolicited_reply);
crate::set_runtime_reporting(self.mode.reporting());
crate::csi::session::begin_run(
matches!(
self.mode,
Mode::EspNow(_) | Mode::EspNowSimplex(_)
),
matches!(self.mode, Mode::Sniffer(_)),
);
crate::csi::session::set_setup_id(self.setup.get_id());
if matches!(
&self.mode,
Mode::EspNow(_) | Mode::EspNowSimplex(_)
) {
crate::esp_now_pool::install();
idle_station_for_espnow(controller).await;
} else {
suppress_espnow_rx();
}
c5_radio_settle().await;
let is_ap = matches!(&self.mode, Mode::AccessPoint(_));
let is_sniffer = matches!(&self.mode, Mode::Sniffer(_));
let is_emitter = matches!(&self.mode, Mode::Emitter(_));
let rx_enabled = self.io_tasks.rx_enabled && !is_emitter;
let profile = self.profile;
#[cfg(any(feature = "esp32c5", feature = "esp32c6"))]
if self.protocol.is_none() {
let he20 = match &self.mode {
Mode::EspNow(c) => c.he20(),
Mode::EspNowSimplex(c) => c.inner().he20(),
_ => false,
};
if he20 {
self.protocol = Some(Protocol::AX);
}
}
let bringup = profile.wants_bringup(NodeView::new(&self.mode), self.protocol);
if let Some(protocol) = self.protocol.take() {
if !is_emitter {
let base = Protocols::default().with_2_4(protocol_ladder_2_4(protocol));
let protocols =
profile.tune_protocols(NodeView::new(&self.mode), protocol, base);
controller.set_protocols(protocols).unwrap();
c5_radio_settle().await;
}
self.protocol = Some(protocol);
}
if bringup && !is_emitter {
profile.apply_bandwidth(controller);
c5_radio_settle().await;
}
let sta_interface =
if let Mode::Station(config) = &self.mode {
let ifaces = sta_init(
&mut interfaces.station,
config,
controller,
profile,
bringup,
);
#[cfg(feature = "esp32c5")]
{
match config.channel_hint {
Some(channel) => apply_band_for_channel(controller, channel),
None => apply_band_auto(controller),
}
c5_radio_settle().await;
}
Some(ifaces)
} else {
None
};
if bringup && sta_interface.is_some() {
profile.apply_protocols_post(controller);
c5_radio_settle().await;
}
let ap_interface = if let Mode::AccessPoint(config) = &self.mode {
#[cfg(feature = "esp32c5")]
if config.secondary_channel().is_none() {
apply_band_for_channel(controller, config.channel());
}
if let Some(secondary) = config.secondary_channel() {
apply_ht40_channel(controller, config.channel(), secondary);
c5_radio_settle().await;
}
let ifaces = ap_init(
&mut interfaces.access_point,
config,
controller,
profile,
bringup,
);
if bringup {
profile.apply_protocols_post(controller);
}
c5_radio_settle().await;
Some(ifaces)
} else {
None
};
let mut config = match self.setup.get_acquisition() {
Some(config) => {
log_ln!("CSI Configuration Set: {:?}", config);
build_csi_config(config)
}
None => {
let default_config = CsiConfig::default();
log_ln!(
"No CSI Configuration Provided. Going with defaults: {:?}",
default_config
);
build_csi_config(&default_config)
}
};
profile.tune_csi_acquisition(&mut config);
log_ln!("Wi-Fi Controller Started");
set_seq_drop_detection(!is_emitter);
let csi_config_for_recovery = config.clone();
if rx_enabled && !is_sniffer && !is_ap {
set_csi(controller, config.clone());
c5_radio_settle().await;
}
let sniffer = &interfaces.sniffer;
match &self.mode {
Mode::Emitter(emitter_config) => {
let main_task = run_emitter(controller, interfaces, emitter_config);
drive_main(main_task, false, duration, client).await;
}
Mode::Sniffer(sniffer_config) => {
#[cfg(feature = "esp32c5")]
{
let band = if sniffer_config.channel() >= 36 {
BandMode::_5G
} else {
BandMode::_2_4G
};
controller.set_band_mode(band).unwrap();
}
sniffer.set_promiscuous_mode(true).unwrap();
controller
.set_channel(sniffer_config.channel(), SecondaryChannel::None)
.unwrap();
if bringup {
profile.apply_sniffer_radio(controller);
c5_radio_settle().await;
}
if rx_enabled {
set_csi(controller, config.clone());
}
match (duration, rx_enabled) {
(Some(d), true) => {
join(
run_process_csi_packet(),
csi_data_collection(client.unwrap(), d),
)
.await;
run_process_csi_packet().await;
}
(Some(d), false) => stop_after_duration(d).await,
(None, true) => run_process_csi_packet().await,
(None, false) => wait_for_stop().await,
}
sniffer.set_promiscuous_mode(false).unwrap();
}
Mode::AccessPoint(ap_config) => {
let (ap_stack, ap_runner) = ap_interface.unwrap();
let main_task = run_ap(
controller,
ap_stack,
ap_runner,
ap_config,
csi_config_for_recovery,
self.io_tasks,
self.setup.get_periodicity_hz(),
);
drive_main(main_task, rx_enabled, duration, client).await;
sniffer.set_promiscuous_mode(false).unwrap();
}
Mode::Station(_sta_config) => {
let (sta_stack, sta_runner) = sta_interface.unwrap();
let main_task = run_sta_connect(
controller,
self.setup.get_periodicity_hz(),
sta_stack,
sta_runner,
csi_config_for_recovery,
self.io_tasks,
);
drive_main(main_task, rx_enabled, duration, client).await;
sniffer.set_promiscuous_mode(false).unwrap();
}
Mode::EspNow(cfg) => {
match cfg.session_role() {
crate::SessionRole::Initiator => {
let main_task = run_esp_now_initiator(
&mut interfaces.esp_now,
UNSET_MAC,
cfg,
self.setup.get_periodicity_hz(),
crate::REPORTS.load(Ordering::Relaxed),
self.io_tasks,
);
drive_main(main_task, rx_enabled, duration, client).await;
}
crate::SessionRole::Responder => {
let main_task = run_esp_now_responder(
&mut interfaces.esp_now,
cfg,
self.setup.get_periodicity_hz(),
self.io_tasks,
);
drive_main(main_task, rx_enabled, duration, client).await;
}
}
}
Mode::EspNowSimplex(cfg) => {
if cfg.is_source() {
let main_task = run_esp_now_simplex_source(
&mut interfaces.esp_now,
cfg.inner(),
self.setup.get_periodicity_hz(),
self.io_tasks,
);
drive_main(main_task, false, duration, client).await;
} else {
let main_task = run_esp_now_simplex_peer(
&mut interfaces.esp_now,
cfg.inner(),
self.io_tasks,
);
drive_main(main_task, rx_enabled, duration, client).await;
}
}
}
STOP_SIGNAL.reset();
reset_globals();
}
pub async fn run(&mut self) {
self.run_inner(None, None).await;
}
}
async fn drive_main(
main_task: impl core::future::Future,
rx_enabled: bool,
duration: Option<u64>,
client: Option<&mut CsiNodeClient>,
) {
match (duration, rx_enabled) {
(Some(d), true) => {
join3(
main_task,
run_process_csi_packet(),
csi_data_collection(client.unwrap(), d),
)
.await;
}
(Some(d), false) => {
join3(main_task, wait_for_stop(), stop_after_duration(d)).await;
}
(None, true) => {
join(main_task, run_process_csi_packet()).await;
}
(None, false) => {
join(main_task, wait_for_stop()).await;
}
}
}
fn protocol_ladder_2_4(protocol: Protocol) -> EnumSet<Protocol> {
match protocol {
Protocol::B => EnumSet::only(Protocol::B),
Protocol::G => Protocol::B | Protocol::G,
Protocol::N => Protocol::B | Protocol::G | Protocol::N,
Protocol::LR | Protocol::A | Protocol::AC => EnumSet::only(protocol),
higher => Protocol::B | Protocol::G | Protocol::N | higher,
}
}
#[cfg(test)]
mod protocol_ladder_tests {
use super::*;
#[test]
fn each_rung_carries_the_ones_beneath_it() {
assert_eq!(protocol_ladder_2_4(Protocol::B), EnumSet::only(Protocol::B));
assert_eq!(protocol_ladder_2_4(Protocol::G), Protocol::B | Protocol::G);
assert_eq!(
protocol_ladder_2_4(Protocol::N),
Protocol::B | Protocol::G | Protocol::N
);
for p in EnumSet::<Protocol>::all().iter().filter(|p| {
!matches!(
p,
Protocol::B | Protocol::G | Protocol::N | Protocol::LR | Protocol::A | Protocol::AC
)
}) {
assert_eq!(protocol_ladder_2_4(p), Protocol::B | Protocol::G | Protocol::N | p);
}
}
#[test]
fn n_is_never_advertised_alone() {
let set = protocol_ladder_2_4(Protocol::N);
assert!(set.contains(Protocol::N));
assert!(set.contains(Protocol::G), "11n needs 11g beneath it");
assert!(set.contains(Protocol::B), "11n needs 11b beneath it");
assert_ne!(set, EnumSet::only(Protocol::N));
}
#[test]
fn the_ht_rung_matches_the_emitters_own_set() {
assert_eq!(
protocol_ladder_2_4(Protocol::N),
Protocol::B | Protocol::G | Protocol::N
);
}
#[test]
fn lr_stays_on_its_own() {
assert_eq!(protocol_ladder_2_4(Protocol::LR), EnumSet::only(Protocol::LR));
}
#[test]
fn five_ghz_rungs_are_untouched() {
for p in [Protocol::A, Protocol::AC] {
assert_eq!(protocol_ladder_2_4(p), EnumSet::only(p));
}
}
}