use core::{net::Ipv4Addr};
use embassy_futures::join::{join3, join4};
use embassy_futures::select::{select, select3, Either, Either3};
use embassy_net::raw::{IpProtocol, IpVersion, PacketMetadata, RawSocket};
use embassy_net::{Ipv4Address, Ipv4Cidr, Runner, Stack, StackResources};
use embassy_time::{with_timeout, Duration, Timer};
use enumset::enum_set;
use esp_radio::wifi::{CsiConfig, ModeConfig, WifiController, WifiDevice, WifiEvent};
use smoltcp::phy::ChecksumCapabilities;
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal};
use smoltcp::wire::{Icmpv4Packet, Icmpv4Repr, Ipv4Packet, Ipv4Repr};
use crate::log_ln;
use crate::{set_csi, IOTaskConfig, WifiStationConfig, STOP_SIGNAL};
static DHCP_CLIENT_INFO: Signal<CriticalSectionRawMutex, IpInfo> = Signal::new();
macro_rules! mk_static {
($t:ty,$val:expr) => {{
static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
#[deny(unused_attributes)]
let x = STATIC_CELL.uninit().write(($val));
x
}};
}
#[derive(Debug, Clone)]
struct IpInfo {
pub local_address: Ipv4Cidr,
pub gateway_address: Ipv4Address,
}
pub fn sta_init<'a>(
interfaces: &'a mut WifiDevice<'static>,
config: &WifiStationConfig,
controller: &mut WifiController<'static>,
) -> (Stack<'a>, Runner<'a, &'a mut WifiDevice<'static>>) {
let sta_ip_config = embassy_net::Config::dhcpv4(Default::default());
let seed = 123456_u64;
let (sta_stack, sta_runner) = embassy_net::new(
interfaces,
sta_ip_config,
mk_static!(StackResources<6>, StackResources::<6>::new()),
seed,
);
let station_config = ModeConfig::Client(config.client_config.clone());
match controller.set_config(&station_config) {
Ok(_) => log_ln!("WiFi Configuration Set: {:?}", config),
Err(_) => {
log_ln!("WiFi Configuration Error");
log_ln!("Error Config: {:?}", config);
}
}
(sta_stack, sta_runner)
}
pub async fn run_sta_connect(
controller: &mut WifiController<'_>,
freq: Option<u16>,
sta_stack: Stack<'_>,
sta_runner: Runner<'_, &mut WifiDevice<'_>>,
csi_config: CsiConfig,
io_tasks: IOTaskConfig,
) {
const CONNECT_TIMEOUT_SECS: u64 = 10;
const FAILURES_BEFORE_RADIO_CYCLE: u8 = 1;
let mut consecutive_failures: u8 = 0;
match select(STOP_SIGNAL.wait(), Timer::after(Duration::from_secs(2))).await {
Either::First(_) => {
STOP_SIGNAL.signal(());
return;
}
Either::Second(_) => {}
}
loop {
let connect_fut = with_timeout(
Duration::from_secs(CONNECT_TIMEOUT_SECS),
controller.connect_async(),
);
let failure_kind: &str = match select(STOP_SIGNAL.wait(), connect_fut).await {
Either::First(_) => {
STOP_SIGNAL.signal(());
return;
}
Either::Second(Ok(Ok(_))) => {
log_ln!("WiFi Connected");
break;
}
Either::Second(Ok(Err(e))) => {
log_ln!("Connect failed: {:?}", e);
"error"
}
Either::Second(Err(_)) => {
log_ln!("connect_async timed out after {}s", CONNECT_TIMEOUT_SECS);
"timeout"
}
};
consecutive_failures = consecutive_failures.saturating_add(1);
let _ = controller.disconnect_async().await;
if consecutive_failures >= FAILURES_BEFORE_RADIO_CYCLE {
log_ln!(
"Cycling Wi-Fi controller after {} failures (last: {}) to clear stale state",
consecutive_failures,
failure_kind
);
let _ = controller.stop_async().await;
Timer::after(Duration::from_millis(300)).await;
match controller.start_async().await {
Ok(()) => {
set_csi(controller, csi_config.clone());
}
Err(e) => log_ln!("Controller restart failed: {:?}", e),
}
consecutive_failures = 0;
}
match select(STOP_SIGNAL.wait(), Timer::after(Duration::from_secs(1))).await {
Either::First(_) => {
STOP_SIGNAL.signal(());
return;
}
Either::Second(_) => {}
}
}
if io_tasks.tx_enabled {
join4(
sta_connection(controller),
sta_network_ops(sta_stack, freq),
run_net_task(sta_runner),
run_dhcp_client(sta_stack),
)
.await;
} else {
join3(
sta_connection(controller),
run_net_task(sta_runner),
run_dhcp_client(sta_stack),
)
.await;
}
}
async fn run_net_task(mut sta_runner: Runner<'_, &mut WifiDevice<'_>>) {
loop {
match select(STOP_SIGNAL.wait(), sta_runner.run()).await {
Either::First(_) => {
STOP_SIGNAL.signal(());
break;
}
Either::Second(_) => {}
}
}
}
async fn run_dhcp_client(sta_stack: Stack<'_>) {
log_ln!("Running DHCP Client");
loop {
sta_stack.wait_link_up().await;
log_ln!("Link is up!");
let mut ip_info = IpInfo {
local_address: Ipv4Cidr::new(Ipv4Addr::UNSPECIFIED, 24),
gateway_address: Ipv4Address::UNSPECIFIED,
};
log_ln!("Acquiring config...");
sta_stack.wait_config_up().await;
log_ln!("Config Acquired");
loop {
if let Some(config) = sta_stack.config_v4() {
ip_info.local_address = config.address;
ip_info.gateway_address = config.gateway.unwrap_or(Ipv4Address::UNSPECIFIED);
log_ln!("Local IP: {:?}", ip_info.local_address);
log_ln!("Gateway IP: {:?}", ip_info.gateway_address);
break;
}
Timer::after(Duration::from_millis(500)).await;
}
DHCP_CLIENT_INFO.signal(ip_info);
while sta_stack.is_link_up() {
Timer::after(Duration::from_millis(250)).await;
}
log_ln!("Link down, waiting to reacquire DHCP config...");
}
}
pub async fn sta_connection(controller: &mut WifiController<'_>) {
let sta_events =
enum_set!(WifiEvent::StaDisconnected | WifiEvent::StaStop | WifiEvent::StaConnected);
loop {
match select(
STOP_SIGNAL.wait(),
controller.wait_for_events(sta_events, true),
)
.await
{
Either::First(_) => {
STOP_SIGNAL.signal(());
break;
}
Either::Second(mut wait_event_fut) => {
if wait_event_fut.contains(WifiEvent::StaDisconnected) {
log_ln!("STA Disconnected");
loop {
match select(STOP_SIGNAL.wait(), controller.connect_async()).await {
Either::First(_) => {
STOP_SIGNAL.signal(());
return;
}
Either::Second(Ok(_)) => {
log_ln!("STA Reconnected");
break;
}
Either::Second(Err(e)) => {
log_ln!("STA reconnect failed: {:?}", e);
match select(
STOP_SIGNAL.wait(),
Timer::after(Duration::from_secs(1)),
)
.await
{
Either::First(_) => {
STOP_SIGNAL.signal(());
return;
}
Either::Second(_) => {}
}
}
}
}
}
if wait_event_fut.contains(WifiEvent::StaStop) {
log_ln!("STA Stopped");
}
wait_event_fut.clear();
}
}
}
}
pub async fn sta_network_ops(sta_stack: Stack<'_>, frequency_hz: Option<u16>) {
let mut ip_info = DHCP_CLIENT_INFO.wait().await;
let mut rx_buffer = [0; 64];
let mut tx_buffer = [0; 64];
let mut rx_meta: [PacketMetadata; 1] = [PacketMetadata::EMPTY; 1];
let mut tx_meta: [PacketMetadata; 1] = [PacketMetadata::EMPTY; 1];
let raw_socket = RawSocket::new::<WifiDevice<'_>>(
sta_stack,
IpVersion::Ipv4,
IpProtocol::Icmp,
&mut rx_meta,
&mut rx_buffer,
&mut tx_meta,
&mut tx_buffer,
);
let mut icmp_buffer = [0u8; 12];
let mut tx_ipv4_buffer = [0u8; 64];
let freq = match frequency_hz {
Some(freq) => freq as u64,
None => 100,
};
let mut seq_counter: u16 = 0;
log_ln!("Starting Trigger Traffic");
loop {
match select3(
STOP_SIGNAL.wait(),
Timer::after(Duration::from_hz(freq)),
DHCP_CLIENT_INFO.wait(),
)
.await
{
Either3::First(_) => {
STOP_SIGNAL.signal(());
break;
}
Either3::Second(_) => {
seq_counter = seq_counter.wrapping_add(1);
let mut icmp_packet = Icmpv4Packet::new_unchecked(&mut icmp_buffer[..]);
let icmp_repr = Icmpv4Repr::EchoRequest {
ident: 0x22b,
seq_no: seq_counter, data: &[0xDE, 0xAD, 0xBE, 0xEF],
};
icmp_repr.emit(&mut icmp_packet, &ChecksumCapabilities::default());
let ipv4_repr = Ipv4Repr {
src_addr: ip_info.local_address.address(),
dst_addr: ip_info.gateway_address,
payload_len: icmp_repr.buffer_len(),
hop_limit: 64, next_header: IpProtocol::Icmp,
};
let mut ipv4_packet = Ipv4Packet::new_unchecked(&mut tx_ipv4_buffer);
ipv4_repr.emit(&mut ipv4_packet, &ChecksumCapabilities::default());
ipv4_packet
.payload_mut()
.copy_from_slice(icmp_packet.into_inner());
let ipv4_packet_buffer = ipv4_packet.into_inner();
raw_socket.send(ipv4_packet_buffer).await;
}
Either3::Third(new_ip_info) => {
ip_info = new_ip_info;
log_ln!("Updated station IP context for trigger traffic");
}
}
}
}