use embassy_futures::select::{Either, select};
use embassy_time::{Instant, Timer};
use esp_radio::esp_now::{
Error as EspNowInnerError, EspNow, EspNowError, EspNowWifiInterface, PeerInfo, WifiPhyRate,
};
use crate::espnow_phy::with_espnow_recv_suspended;
use crate::{
CENTRAL_MAGIC_NUMBER, ControlPacket, EspNowConfig, IOTaskConfig, PERIPHERAL_MAGIC_NUMBER,
PeripheralPacket, STOP_SIGNAL, apply_peer_espnow_phy, log_ln, parse_with_magic,
serialize_with_magic,
};
#[cfg(feature = "statistics")]
use crate::STATS;
#[cfg(feature = "statistics")]
use portable_atomic::Ordering;
const TX_BACKOFF_US: u64 = 200;
const PEER_HEALTHCHECK_PERIOD: u16 = 256;
const FLOOD_BUF_LEN: usize = 16;
fn add_collector_peer(esp_now: &EspNow<'static>, mac: &[u8; 6], channel: u8) -> bool {
if esp_now.peer_exists(mac) {
return true;
}
esp_now
.add_peer(PeerInfo {
interface: EspNowWifiInterface::Station,
peer_address: *mac,
lmk: None,
channel: Some(channel),
encrypt: false,
})
.is_ok()
}
pub async fn run_esp_now_fast_source(
esp_now: &mut EspNow<'static>,
config: &EspNowConfig,
freq_hz: Option<u16>,
_io_tasks: IOTaskConfig,
) {
let peer_mac = config.peer_mac();
let send_magic = peer_mac.is_none();
if config.secondary_channel().is_none() {
with_espnow_recv_suspended(|| {
esp_now.set_channel(config.channel).unwrap();
});
}
log_ln!("esp-now version {}", esp_now.version().unwrap());
let rate = if config.force_phy() {
*config.phy_rate()
} else {
WifiPhyRate::RateMcs7Lgi
};
let collector_mac: [u8; 6] = match peer_mac {
Some(mac) => mac,
None => loop {
match select(STOP_SIGNAL.wait(), crate::esp_now_pool::receive_async()).await {
Either::First(_) => {
log_ln!("STOP signal received, shutting down fast source...");
STOP_SIGNAL.signal(());
return;
}
Either::Second(r) => {
if parse_with_magic::<ControlPacket>(r.data(), CENTRAL_MAGIC_NUMBER, true)
.is_some()
{
break r.info.src_address;
}
}
}
},
};
add_collector_peer(esp_now, &collector_mac, config.channel);
apply_peer_espnow_phy(&collector_mac, rate, config.secondary_channel());
log_ln!(
"ESP-NOW fast source: locked collector {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}, unicast flood rate {:?}",
collector_mac[0],
collector_mac[1],
collector_mac[2],
collector_mac[3],
collector_mac[4],
collector_mac[5],
rate,
);
if send_magic {
let mut hello_buf = [0u8; 8];
if let Ok(hello) = serialize_with_magic(
&PeripheralPacket::new(),
PERIPHERAL_MAGIC_NUMBER,
true,
&mut hello_buf,
) {
let _ = esp_now.send_async(&collector_mac, hello).await;
}
}
let cap_interval_us = freq_hz.map(|f| 1_000_000u64 / (f.max(1) as u64));
let mut next_tx_us = Instant::now().as_micros();
#[cfg(feature = "statistics")]
let mut seq: u32 = 0;
let mut tx_buf = [0u8; FLOOD_BUF_LEN];
let mut healthcheck: u16 = 0;
loop {
if STOP_SIGNAL.signaled() {
log_ln!("STOP signal received, shutting down fast source...");
STOP_SIGNAL.signal(());
break;
}
healthcheck = healthcheck.wrapping_add(1);
if (healthcheck & (PEER_HEALTHCHECK_PERIOD - 1)) == 0 && !esp_now.peer_exists(&collector_mac)
{
add_collector_peer(esp_now, &collector_mac, config.channel);
apply_peer_espnow_phy(&collector_mac, rate, config.secondary_channel());
}
let pkt = ControlPacket::new(
false,
#[cfg(feature = "statistics")]
seq,
);
let msg = match serialize_with_magic(&pkt, CENTRAL_MAGIC_NUMBER, send_magic, &mut tx_buf) {
Ok(m) => m,
Err(_) => {
log_ln!("Failed to serialize ESP-NOW flood packet");
break;
}
};
match esp_now.send_async(&collector_mac, msg).await {
Ok(()) => {
#[cfg(feature = "statistics")]
{
STATS.tx_count.fetch_add(1, Ordering::Relaxed);
seq = seq.wrapping_add(1);
}
}
Err(EspNowError::Error(EspNowInnerError::OutOfMemory) | EspNowError::SendFailed) => {
match select(STOP_SIGNAL.wait(), Timer::after_micros(TX_BACKOFF_US)).await {
Either::First(_) => {
STOP_SIGNAL.signal(());
break;
}
Either::Second(_) => {}
}
}
Err(e) => {
log_ln!("Failed to send ESP-NOW flood packet: {:?}", e);
}
}
if let Some(interval) = cap_interval_us {
next_tx_us = next_tx_us.saturating_add(interval);
let now = Instant::now().as_micros();
if next_tx_us > now {
match select(STOP_SIGNAL.wait(), Timer::after_micros(next_tx_us - now)).await {
Either::First(_) => {
STOP_SIGNAL.signal(());
break;
}
Either::Second(_) => {}
}
}
}
}
log_ln!("Node Stopped. Halting flood.");
}