#[cfg(feature = "async-print")]
use embassy_time::with_timeout;
use embassy_futures::join::{join, join3};
use embassy_futures::select::{select, Either};
use embassy_time::{Duration, Timer};
use enumset::EnumSet;
use esp_radio::esp_now::WifiPhyRate;
use esp_radio::wifi::sta::StationConfig;
use esp_radio::wifi::{Interfaces, Protocol, Protocols, SecondaryChannel, WifiController};
#[cfg(feature = "esp32c5")]
use esp_radio::wifi::BandMode;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::signal::Signal;
use portable_atomic::Ordering;
use crate::central::esp_now::run_esp_now_central;
use crate::central::sta::{run_sta_connect, sta_init};
use crate::config::CsiConfig as CsiConfiguration;
use crate::peripheral::esp_now::run_esp_now_peripheral;
use crate::csi::delivery::{build_csi_config, run_process_csi_packet, set_csi, CSINodeClient, IS_COLLECTOR};
use crate::espnow_phy::{apply_espnow_ht40, bring_up_espnow_sta, takeover_esp_now_recv};
use crate::log_ln;
use crate::stats::set_seq_drop_detection;
pub(crate) static STOP_SIGNAL: Signal<CriticalSectionRawMutex, ()> = Signal::new();
#[cfg(feature = "async-print")]
async fn csi_data_collection(client: &mut CSINodeClient, duration: u64) {
with_timeout(Duration::from_secs(duration), async {
loop {
client.print_csi_w_metadata().await;
}
})
.await
.unwrap_err();
client.send_stop().await;
}
#[cfg(not(feature = "async-print"))]
async fn csi_data_collection(client: &mut CSINodeClient, duration: u64) {
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(()),
}
}
pub struct EspNowConfig {
phy_rate: WifiPhyRate,
pub(crate) channel: u8,
peer_mac: Option<[u8; 6]>,
secondary_channel: Option<SecondaryChannel>,
force_phy: bool,
}
impl Default for EspNowConfig {
fn default() -> Self {
Self {
phy_rate: WifiPhyRate::RateMcs0Lgi,
channel: 1,
peer_mac: None,
secondary_channel: None,
force_phy: false,
}
}
}
impl EspNowConfig {
pub fn with_channel(mut self, channel: u8) -> Self {
self.channel = channel;
self
}
pub fn with_phy_rate(mut self, phy_rate: WifiPhyRate) -> Self {
self.phy_rate = phy_rate;
self.force_phy = true;
self
}
pub fn with_peer_mac(mut self, peer_mac: [u8; 6]) -> Self {
self.peer_mac = Some(peer_mac);
self
}
pub fn channel(&self) -> u8 {
self.channel
}
pub fn phy_rate(&self) -> &WifiPhyRate {
&self.phy_rate
}
pub fn peer_mac(&self) -> Option<[u8; 6]> {
self.peer_mac
}
pub fn with_ht40(mut self, secondary: SecondaryChannel) -> Self {
self.secondary_channel = Some(secondary);
self.force_phy = true;
self
}
pub fn secondary_channel(&self) -> Option<SecondaryChannel> {
self.secondary_channel
}
pub fn force_phy(&self) -> bool {
self.force_phy
}
}
#[derive(Debug, Clone)]
pub struct WifiSnifferConfig {
#[allow(dead_code)]
mac_filter: Option<[u8; 6]>,
channel: u8,
}
impl Default for WifiSnifferConfig {
fn default() -> Self {
Self {
mac_filter: None,
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,
}
#[cfg(feature = "defmt")]
impl defmt::Format for WifiStationConfig {
fn format(&self, fmt: defmt::Formatter<'_>) {
defmt::write!(fmt, "WifiStationConfig {{ client_config: <opaque> }}");
}
}
pub enum CentralOpMode {
EspNow(EspNowConfig),
WifiStation(WifiStationConfig),
}
pub enum PeripheralOpMode {
EspNow(EspNowConfig),
WifiSniffer(WifiSnifferConfig),
}
pub enum Node {
Peripheral(PeripheralOpMode),
Central(CentralOpMode),
}
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum CollectionMode {
Collector,
Listener,
}
#[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 CSINodeHardware<'a> {
interfaces: &'a mut Interfaces<'static>,
controller: &'a mut WifiController<'static>,
}
impl<'a> CSINodeHardware<'a> {
pub fn new(
interfaces: &'a mut Interfaces<'static>,
controller: &'a mut WifiController<'static>,
) -> Self {
Self {
interfaces,
controller,
}
}
}
pub(crate) fn reset_globals() {
crate::csi::delivery::reset();
crate::stats::reset();
}
pub struct CSINode<'a> {
kind: Node,
collection_mode: CollectionMode,
io_tasks: IOTaskConfig,
csi_config: Option<CsiConfiguration>,
traffic_freq_hz: Option<u16>,
hardware: CSINodeHardware<'a>,
protocol: Option<Protocol>,
}
impl<'a> CSINode<'a> {
pub fn new(
kind: Node,
collection_mode: CollectionMode,
csi_config: Option<CsiConfiguration>,
traffic_freq_hz: Option<u16>,
hardware: CSINodeHardware<'a>,
) -> Self {
Self {
kind,
collection_mode,
io_tasks: IOTaskConfig::default(),
csi_config,
traffic_freq_hz,
hardware,
protocol: None,
}
}
pub fn new_central_node(
op_mode: CentralOpMode,
collection_mode: CollectionMode,
csi_config: Option<CsiConfiguration>,
traffic_freq_hz: Option<u16>,
hardware: CSINodeHardware<'a>,
) -> Self {
Self {
kind: Node::Central(op_mode),
collection_mode,
io_tasks: IOTaskConfig::default(),
csi_config,
traffic_freq_hz,
hardware,
protocol: None,
}
}
pub fn get_node_type(&self) -> &Node {
&self.kind
}
pub fn get_collection_mode(&self) -> CollectionMode {
self.collection_mode
}
pub fn get_central_op_mode(&self) -> Option<&CentralOpMode> {
match &self.kind {
Node::Central(mode) => Some(mode),
Node::Peripheral(_) => None,
}
}
pub fn get_peripheral_op_mode(&self) -> Option<&PeripheralOpMode> {
match &self.kind {
Node::Peripheral(mode) => Some(mode),
Node::Central(_) => None,
}
}
pub fn set_csi_config(&mut self, config: CsiConfiguration) {
self.csi_config = Some(config);
}
pub fn set_station_config(&mut self, config: WifiStationConfig) {
if let Node::Central(CentralOpMode::WifiStation(_)) = &mut self.kind {
self.kind = Node::Central(CentralOpMode::WifiStation(config));
}
}
pub fn set_traffic_frequency(&mut self, freq_hz: u16) {
self.traffic_freq_hz = Some(freq_hz);
}
pub fn set_collection_mode(&mut self, mode: CollectionMode) {
self.collection_mode = mode;
}
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_op_mode(&mut self, mode: Node) {
self.kind = mode;
}
pub fn set_protocol(&mut self, protocol: Protocol) {
self.protocol = Some(protocol);
}
pub fn set_rate(&mut self, rate: WifiPhyRate) {
match &mut self.kind {
Node::Central(CentralOpMode::EspNow(cfg))
| Node::Peripheral(PeripheralOpMode::EspNow(cfg)) => {
cfg.phy_rate = rate;
cfg.force_phy = true;
}
_ => {}
}
}
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>) {
let interfaces = &mut self.hardware.interfaces;
let controller = &mut self.hardware.controller;
takeover_esp_now_recv(matches!(
&self.kind,
Node::Peripheral(PeripheralOpMode::WifiSniffer(_))
));
if matches!(
&self.kind,
Node::Peripheral(PeripheralOpMode::EspNow(c)) | Node::Central(CentralOpMode::EspNow(c))
if c.force_phy()
) {
bring_up_espnow_sta(controller);
}
let sta_interface = if let Node::Central(CentralOpMode::WifiStation(config)) = &self.kind {
Some(sta_init(&mut interfaces.station, config, controller))
} else {
None
};
let config = match self.csi_config {
Some(ref config) => {
log_ln!("CSI Configuration Set: {:?}", config);
build_csi_config(config)
}
None => {
let default_config = CsiConfiguration::default();
log_ln!(
"No CSI Configuration Provided. Going with defaults: {:?}",
default_config
);
build_csi_config(&default_config)
}
};
if let Some(protocol) = self.protocol.take() {
let old_protocol = reconstruct_protocol(&protocol);
let protocols = Protocols::default().with_2_4(EnumSet::only(protocol));
controller.set_protocols(protocols).unwrap();
self.protocol = Some(old_protocol);
}
log_ln!("Wi-Fi Controller Started");
let is_collector = self.collection_mode == CollectionMode::Collector;
IS_COLLECTOR.store(is_collector, Ordering::Relaxed);
set_seq_drop_detection(matches!(
&self.kind,
Node::Peripheral(PeripheralOpMode::EspNow(_))
| Node::Central(CentralOpMode::EspNow(_))
));
let csi_config_for_recovery = config.clone();
let is_sniffer = matches!(
&self.kind,
Node::Peripheral(PeripheralOpMode::WifiSniffer(_))
);
if self.io_tasks.rx_enabled && !is_sniffer {
set_csi(controller, config.clone());
}
let rx_enabled = self.io_tasks.rx_enabled;
let sniffer = &interfaces.sniffer;
match &self.kind {
Node::Peripheral(op_mode) => match op_mode {
PeripheralOpMode::EspNow(esp_now_config) => {
if let Some(secondary) = esp_now_config.secondary_channel() {
apply_espnow_ht40(controller, esp_now_config.channel(), secondary);
}
let main_task = run_esp_now_peripheral(
&mut interfaces.esp_now,
esp_now_config,
self.traffic_freq_hz,
self.io_tasks,
);
drive_main(main_task, rx_enabled, duration, client).await;
}
PeripheralOpMode::WifiSniffer(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 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();
}
},
Node::Central(op_mode) => match op_mode {
CentralOpMode::EspNow(esp_now_config) => {
if let Some(secondary) = esp_now_config.secondary_channel() {
apply_espnow_ht40(controller, esp_now_config.channel(), secondary);
}
let main_task = run_esp_now_central(
&mut interfaces.esp_now,
interfaces.station.mac_address(),
esp_now_config,
self.traffic_freq_hz,
is_collector,
self.io_tasks,
);
drive_main(main_task, rx_enabled, duration, client).await;
}
CentralOpMode::WifiStation(_sta_config) => {
let (sta_stack, sta_runner) = sta_interface.unwrap();
let main_task = run_sta_connect(
controller,
self.traffic_freq_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();
}
},
}
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 reconstruct_protocol(protocol: &Protocol) -> Protocol {
match protocol {
Protocol::B => Protocol::B,
Protocol::G => Protocol::G,
Protocol::N => Protocol::N,
Protocol::LR => Protocol::LR,
Protocol::A => Protocol::A,
Protocol::AC => Protocol::AC,
Protocol::AX => Protocol::AX,
_ => Protocol::N,
}
}