#![allow(clippy::future_not_send, reason = "single-threaded")]
#![allow(
unsafe_code,
reason = "StackStorage uses UnsafeCell in single-threaded context"
)]
use core::cell::{RefCell, UnsafeCell};
use cyw43::JoinOptions;
use cyw43_pio::{DEFAULT_CLOCK_DIVIDER, PioSpi};
use defmt::*;
use embassy_executor::Spawner;
use embassy_net::{Config, Stack, StackResources};
use embassy_rp::peripherals;
use embassy_rp::pio::Pio;
use embassy_rp::{
Peri,
dma::{Channel as DmaChannel, ChannelInstance},
gpio::{Level, Output},
peripherals::{PIN_23, PIN_24, PIN_25, PIN_29},
};
use embassy_sync::blocking_mutex::{Mutex, raw::CriticalSectionRawMutex};
use embassy_sync::signal::Signal;
use embassy_time::Timer;
use portable_atomic::{AtomicBool, Ordering};
use static_cell::StaticCell;
use super::dhcp::dhcp_server_task;
use crate::flash_block::{FlashBlock as _, FlashBlockRp};
use device_envoy_core::wifi_auto::{WifiCredentials, WifiStartMode};
const CYW43_NVRAM: &[u8; 746] = b"
NVRAMRev=$Rev$\x00\
manfid=0x2d0\x00\
prodid=0x0727\x00\
vendid=0x14e4\x00\
devid=0x43e2\x00\
boardtype=0x0887\x00\
boardrev=0x1100\x00\
boardnum=22\x00\
macaddr=00:A0:50:b5:59:5e\x00\
sromrev=11\x00\
boardflags=0x00404001\x00\
boardflags3=0x04000000\x00\
xtalfreq=37400\x00\
nocrc=1\x00\
ag0=255\x00\
aa2g=1\x00\
ccode=ALL\x00\
pa0itssit=0x20\x00\
extpagain2g=0\x00\
pa2ga0=-168,6649,-778\x00\
AvVmid_c0=0x0,0xc8\x00\
cckpwroffset0=5\x00\
maxp2ga0=84\x00\
txpwrbckof=6\x00\
cckbw202gpo=0\x00\
legofdmbw202gpo=0x66111111\x00\
mcsbw202gpo=0x77711111\x00\
propbw202gpo=0xdd\x00\
ofdmdigfilttype=18\x00\
ofdmdigfilttypebe=18\x00\
papdmode=1\x00\
papdvalidtest=1\x00\
pacalidx2g=45\x00\
papdepsoffset=-30\x00\
papdendidx=58\x00\
ltecxmux=0\x00\
ltecxpadnum=0x0102\x00\
ltecxfnsel=0x44\x00\
ltecxgcigpio=0x01\x00\
il0macaddr=00:90:4c:c5:12:38\x00\
wl0id=0x431b\x00\
deadman_to=0xffffffff\x00\
muxenab=0x100\x00\
spurconfig=0x3\x00\
glitch_based_crsmin=1\x00\
btc_mode=1\x00\
\x00";
pub enum WifiEvent {
CaptivePortalReady,
ClientReady,
}
#[derive(Clone, serde::Serialize, serde::Deserialize)]
struct WifiStoredState {
credentials: Option<WifiCredentials>,
start_mode: WifiStartMode,
}
impl Default for WifiStoredState {
fn default() -> Self {
Self {
credentials: None,
start_mode: WifiStartMode::CaptivePortal,
}
}
}
#[derive(Clone, PartialEq, Eq)]
#[doc(hidden)]
pub enum WifiMode {
CaptivePortal,
ClientConfigured(WifiCredentials),
}
pub struct StackStorage {
initialized: AtomicBool,
ready: Signal<CriticalSectionRawMutex, ()>,
value: UnsafeCell<Option<&'static Stack<'static>>>,
}
unsafe impl Sync for StackStorage {}
impl StackStorage {
#[must_use]
pub const fn new() -> Self {
Self {
initialized: AtomicBool::new(false),
ready: Signal::new(),
value: UnsafeCell::new(None),
}
}
pub fn init(&self, stack: &'static Stack<'static>) {
unsafe {
*self.value.get() = Some(stack);
}
self.initialized.store(true, Ordering::Release);
self.ready.signal(());
}
pub async fn get(&self) -> &'static Stack<'static> {
if !self.initialized.load(Ordering::Acquire) {
self.ready.wait().await;
}
unsafe { (*self.value.get()).unwrap() }
}
}
pub type WifiEvents = Signal<CriticalSectionRawMutex, WifiEvent>;
#[doc(hidden)]
pub trait WifiPio: crate::pio_irqs::PioIrqMap {
fn spawn_wifi_task(
spawner: Spawner,
runner: cyw43::Runner<'static, cyw43::SpiBus<Output<'static>, PioSpi<'static, Self, 0>>>,
);
fn spawn_device_loop(
spawner: Spawner,
pin_23: Peri<'static, PIN_23>,
pin_24: Peri<'static, PIN_24>,
pin_25: Peri<'static, PIN_25>,
pin_29: Peri<'static, PIN_29>,
pio: Peri<'static, Self>,
dma: DmaChannel<'static>,
mode: WifiMode,
captive_portal_ssid: &'static str,
wifi_events: &'static WifiEvents,
stack_storage: &'static StackStorage,
);
}
pub struct WifiStatic {
events: WifiEvents,
stack: StackStorage,
wifi_cell: StaticCell<Wifi>,
}
pub struct Wifi {
events: &'static WifiEvents,
stack: &'static StackStorage,
credential_store: Mutex<CriticalSectionRawMutex, RefCell<FlashBlockRp>>,
}
impl Wifi {
#[must_use]
pub const fn new_static() -> WifiStatic {
WifiStatic {
events: Signal::new(),
stack: StackStorage::new(),
wifi_cell: StaticCell::new(),
}
}
pub async fn wait_for_stack(&self) -> &'static Stack<'static> {
self.stack.get().await
}
pub async fn wait_for_wifi_event(&self) -> WifiEvent {
self.events.wait().await
}
pub fn new_with_captive_portal_ssid<PIO: WifiPio, DMA: ChannelInstance>(
wifi_static: &'static WifiStatic,
pin_23: Peri<'static, PIN_23>,
pin_24: Peri<'static, PIN_24>,
pin_25: Peri<'static, PIN_25>,
pin_29: Peri<'static, PIN_29>,
pio: Peri<'static, PIO>,
dma: Peri<'static, DMA>,
credential_store: FlashBlockRp,
captive_portal_ssid: &'static str,
spawner: Spawner,
) -> &'static Self
where
crate::pio_irqs::DmaAllIrqs: embassy_rp::interrupt::typelevel::Binding<
DMA::Interrupt,
embassy_rp::dma::InterruptHandler<DMA>,
>,
{
let mut store_block = credential_store;
let stored_state = load_state_from_block(&mut store_block);
let mode = match stored_state.start_mode {
WifiStartMode::CaptivePortal => WifiMode::CaptivePortal,
WifiStartMode::Client => {
if let Some(wifi_credentials) = stored_state.credentials.clone() {
WifiMode::ClientConfigured(wifi_credentials)
} else {
WifiMode::CaptivePortal
}
}
};
let dma = DmaChannel::new(dma, crate::pio_irqs::DmaAllIrqs);
PIO::spawn_device_loop(
spawner,
pin_23,
pin_24,
pin_25,
pin_29,
pio,
dma,
mode,
captive_portal_ssid,
&wifi_static.events,
&wifi_static.stack,
);
wifi_static.wifi_cell.init(Self {
events: &wifi_static.events,
stack: &wifi_static.stack,
credential_store: Mutex::new(RefCell::new(store_block)),
})
}
fn update_state<F>(&self, f: F) -> Result<(), &'static str>
where
F: FnOnce(&mut WifiStoredState),
{
self.credential_store.lock(|cell| {
let mut block = cell.borrow_mut();
let mut state = load_state_from_block(&mut block);
f(&mut state);
save_state_to_block(&mut block, &state)
})
}
fn read_state<R>(&self, f: impl FnOnce(&WifiStoredState) -> R) -> R {
self.credential_store.lock(|cell| {
let mut block = cell.borrow_mut();
let state = load_state_from_block(&mut block);
f(&state)
})
}
pub fn persist_credentials(&self, credentials: &WifiCredentials) -> Result<(), &'static str> {
let cloned = credentials.clone();
self.update_state(|state| {
state.credentials = Some(cloned.clone());
state.start_mode = WifiStartMode::Client;
})
}
pub fn load_persisted_credentials(&self) -> Option<WifiCredentials> {
self.read_state(|state| state.credentials.clone())
}
pub fn current_start_mode(&self) -> WifiStartMode {
self.read_state(|state| state.start_mode)
}
pub fn set_start_mode(&self, mode: WifiStartMode) -> Result<(), &'static str> {
self.update_state(|state| {
state.start_mode = mode;
})
}
pub fn prepare_start_mode(
block: &mut FlashBlockRp,
mode: WifiStartMode,
) -> Result<(), &'static str> {
let mut state = load_state_from_block(block);
state.start_mode = mode;
save_state_to_block(block, &state)
}
pub fn peek_credentials(block: &mut FlashBlockRp) -> Option<WifiCredentials> {
load_state_from_block(block).credentials
}
pub fn peek_start_mode(block: &mut FlashBlockRp) -> WifiStartMode {
load_state_from_block(block).start_mode
}
}
fn load_state_from_block(block: &mut FlashBlockRp) -> WifiStoredState {
match block.load::<WifiStoredState>() {
Ok(Some(state)) => state,
Ok(None) => WifiStoredState::default(),
Err(_) => {
warn!("Failed to load stored WiFi state");
WifiStoredState::default()
}
}
}
fn save_state_to_block(
block: &mut FlashBlockRp,
state: &WifiStoredState,
) -> Result<(), &'static str> {
block
.save(state)
.map_err(|_| "Failed to save WiFi state to flash")
}
async fn wifi_task_impl<PIO: WifiPio>(
runner: cyw43::Runner<'static, cyw43::SpiBus<Output<'static>, PioSpi<'static, PIO, 0>>>,
) -> ! {
runner.run().await
}
async fn wifi_device_loop_impl<PIO: WifiPio>(
pin_23: Peri<'static, PIN_23>,
pin_24: Peri<'static, PIN_24>,
pin_25: Peri<'static, PIN_25>,
pin_29: Peri<'static, PIN_29>,
pio: Peri<'static, PIO>,
dma: DmaChannel<'static>,
mode: WifiMode,
captive_portal_ssid: &'static str,
wifi_events: &'static WifiEvents,
stack_storage: &'static StackStorage,
spawner: Spawner,
) -> ! {
match mode {
WifiMode::CaptivePortal => {
wifi_device_loop_captive_portal(
pin_23,
pin_24,
pin_25,
pin_29,
pio,
dma,
captive_portal_ssid,
wifi_events,
stack_storage,
spawner,
)
.await
}
WifiMode::ClientConfigured(credentials) => {
wifi_device_loop_client_configured(
pin_23,
pin_24,
pin_25,
pin_29,
pio,
dma,
wifi_events,
stack_storage,
spawner,
credentials,
)
.await
}
}
}
async fn wifi_device_loop_captive_portal<PIO: WifiPio>(
pin_23: Peri<'static, PIN_23>,
pin_24: Peri<'static, PIN_24>,
pin_25: Peri<'static, PIN_25>,
pin_29: Peri<'static, PIN_29>,
pio: Peri<'static, PIO>,
dma: DmaChannel<'static>,
captive_portal_ssid: &'static str,
wifi_events: &'static WifiEvents,
stack_storage: &'static StackStorage,
spawner: Spawner,
) -> ! {
info!(
"WiFi device initializing in captive portal mode ({})",
captive_portal_ssid
);
static FW: cyw43::Aligned<cyw43::A4, [u8; cyw43_firmware::CYW43_43439A0.len()]> =
cyw43::Aligned(*cyw43_firmware::CYW43_43439A0);
let clm = cyw43_firmware::CYW43_43439A0_CLM;
static NVRAM: cyw43::Aligned<cyw43::A4, [u8; CYW43_NVRAM.len()]> = cyw43::Aligned(*CYW43_NVRAM);
let pwr = Output::new(pin_23, Level::Low);
let cs = Output::new(pin_25, Level::High);
let mut pio = Pio::new(pio, <PIO as crate::pio_irqs::PioIrqMap>::irqs());
let spi = PioSpi::new(
&mut pio.common,
pio.sm0,
DEFAULT_CLOCK_DIVIDER,
pio.irq0,
cs,
pin_24,
pin_29,
dma,
);
static STATE: StaticCell<cyw43::State> = StaticCell::new();
let state = STATE.init(cyw43::State::new());
let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, &FW, &NVRAM).await;
PIO::spawn_wifi_task(spawner, runner);
control.init(clm).await;
control
.set_power_management(cyw43::PowerManagementMode::PowerSave)
.await;
const CAPTIVE_PORTAL_PASSWORD: &str = "";
info!("Starting captive portal mode: {}", captive_portal_ssid);
let config = Config::ipv4_static(embassy_net::StaticConfigV4 {
address: embassy_net::Ipv4Cidr::new(embassy_net::Ipv4Address::new(192, 168, 4, 1), 24),
gateway: Some(embassy_net::Ipv4Address::new(192, 168, 4, 1)),
dns_servers: heapless::Vec::from_slice(&[embassy_net::Ipv4Address::new(192, 168, 4, 1)])
.unwrap(),
});
let seed = 0x0bad_cafe_dead_beef;
static RESOURCES: StaticCell<StackResources<5>> = StaticCell::new();
static STACK: StaticCell<Stack<'static>> = StaticCell::new();
let (stack_val, runner) = embassy_net::new(
net_device,
config,
RESOURCES.init(StackResources::<5>::new()),
seed,
);
let stack = STACK.init(stack_val);
let net_task_token = unwrap!(net_task(runner));
spawner.spawn(net_task_token);
if CAPTIVE_PORTAL_PASSWORD.is_empty() {
control.start_ap_open(captive_portal_ssid, 1).await;
} else {
control
.start_ap_wpa2(captive_portal_ssid, CAPTIVE_PORTAL_PASSWORD, 1)
.await;
}
info!("Captive portal mode started! SSID: {}", captive_portal_ssid);
stack.wait_config_up().await;
if let Some(config) = stack.config_v4() {
info!("Captive portal IP Address: {}", config.address);
}
let server_ip = embassy_net::Ipv4Address::new(192, 168, 4, 1);
let netmask = embassy_net::Ipv4Address::new(255, 255, 255, 0);
let pool_start = embassy_net::Ipv4Address::new(192, 168, 4, 2);
let pool_size = 253;
let dhcp_server_token = unwrap!(dhcp_server_task(
stack, server_ip, netmask, pool_start, pool_size,
));
spawner.spawn(dhcp_server_token);
info!("DHCP server started (pool: 192.168.4.2-254)");
info!(
"WiFi captive portal ready - connect to '{}'",
captive_portal_ssid
);
stack_storage.init(stack);
wifi_events.signal(WifiEvent::CaptivePortalReady);
loop {
Timer::after_secs(3600).await;
}
}
async fn wifi_device_loop_client_configured<PIO: WifiPio>(
pin_23: Peri<'static, PIN_23>,
pin_24: Peri<'static, PIN_24>,
pin_25: Peri<'static, PIN_25>,
pin_29: Peri<'static, PIN_29>,
pio: Peri<'static, PIO>,
dma: DmaChannel<'static>,
wifi_events: &'static WifiEvents,
stack_storage: &'static StackStorage,
spawner: Spawner,
credentials: WifiCredentials,
) -> ! {
let WifiCredentials { ssid, password } = credentials;
wifi_device_loop_client_impl(
pin_23,
pin_24,
pin_25,
pin_29,
pio,
dma,
wifi_events,
stack_storage,
spawner,
ssid,
password,
)
.await
}
async fn wifi_device_loop_client_impl<PIO: WifiPio>(
pin_23: Peri<'static, PIN_23>,
pin_24: Peri<'static, PIN_24>,
pin_25: Peri<'static, PIN_25>,
pin_29: Peri<'static, PIN_29>,
pio: Peri<'static, PIO>,
dma: DmaChannel<'static>,
wifi_events: &'static WifiEvents,
stack_storage: &'static StackStorage,
spawner: Spawner,
ssid: heapless::String<32>,
password: heapless::String<64>,
) -> ! {
info!("WiFi device initializing in client mode");
let ssid_str = ssid;
let password_str = password;
static FW: cyw43::Aligned<cyw43::A4, [u8; cyw43_firmware::CYW43_43439A0.len()]> =
cyw43::Aligned(*cyw43_firmware::CYW43_43439A0);
let clm = cyw43_firmware::CYW43_43439A0_CLM;
static NVRAM: cyw43::Aligned<cyw43::A4, [u8; CYW43_NVRAM.len()]> = cyw43::Aligned(*CYW43_NVRAM);
let pwr = Output::new(pin_23, Level::Low);
let cs = Output::new(pin_25, Level::High);
let mut pio = Pio::new(pio, <PIO as crate::pio_irqs::PioIrqMap>::irqs());
let spi = PioSpi::new(
&mut pio.common,
pio.sm0,
DEFAULT_CLOCK_DIVIDER,
pio.irq0,
cs,
pin_24,
pin_29,
dma,
);
static STATE: StaticCell<cyw43::State> = StaticCell::new();
let state = STATE.init(cyw43::State::new());
let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, &FW, &NVRAM).await;
PIO::spawn_wifi_task(spawner, runner);
control.init(clm).await;
control
.set_power_management(cyw43::PowerManagementMode::PowerSave)
.await;
let config = Config::dhcpv4(Default::default());
let seed = 0x7c8f_3a2e_9d14_6b5a;
static RESOURCES: StaticCell<StackResources<5>> = StaticCell::new();
static STACK: StaticCell<Stack<'static>> = StaticCell::new();
let (stack_val, runner) = embassy_net::new(
net_device,
config,
RESOURCES.init(StackResources::<5>::new()),
seed,
);
let stack = STACK.init(stack_val);
let net_task_token = unwrap!(net_task(runner));
spawner.spawn(net_task_token);
info!(
"Connecting to WiFi: {} (password length: {})",
ssid_str,
password_str.len()
);
match control
.join(ssid_str.as_str(), JoinOptions::new(password_str.as_bytes()))
.await
{
Ok(_) => {
info!("WiFi join succeeded");
}
Err(err) => {
info!("WiFi join failed: {:?}", defmt::Debug2Format(&err));
info!("Not retrying - letting timeout handle this to avoid driver bugs");
loop {
Timer::after_secs(3600).await;
}
}
}
info!("WiFi connected! Waiting for DHCP...");
let mut dhcp_wait_seconds = 0_u32;
let config = loop {
if let Some(config) = stack.config_v4() {
break config;
}
if dhcp_wait_seconds % 5 == 0 {
info!(
"DHCP wait {}s (link_up={} config_up={})",
dhcp_wait_seconds,
stack.is_link_up(),
stack.is_config_up()
);
}
Timer::after_secs(1).await;
dhcp_wait_seconds += 1;
};
info!("IP Address: {}", config.address);
info!("WiFi client ready");
stack_storage.init(stack);
wifi_events.signal(WifiEvent::ClientReady);
loop {
Timer::after_secs(3600).await;
}
}
#[embassy_executor::task]
async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! {
runner.run().await
}
macro_rules! impl_wifi_pio {
($pio:ident, $suffix:ident) => {
paste::paste! {
impl WifiPio for peripherals::$pio {
fn spawn_wifi_task(
spawner: Spawner,
runner: cyw43::Runner<'static, cyw43::SpiBus<Output<'static>, PioSpi<'static, Self, 0>>>,
) {
let wifi_task_token = defmt::unwrap!([<wifi_task_ $suffix>](runner));
spawner.spawn(wifi_task_token);
}
fn spawn_device_loop(
spawner: Spawner,
pin_23: Peri<'static, PIN_23>,
pin_24: Peri<'static, PIN_24>,
pin_25: Peri<'static, PIN_25>,
pin_29: Peri<'static, PIN_29>,
pio: Peri<'static, Self>,
dma: DmaChannel<'static>,
mode: WifiMode,
captive_portal_ssid: &'static str,
wifi_events: &'static WifiEvents,
stack_storage: &'static StackStorage,
) {
let wifi_device_loop_token = defmt::unwrap!([<wifi_device_loop_ $suffix>](
pin_23,
pin_24,
pin_25,
pin_29,
pio,
dma,
mode,
captive_portal_ssid,
wifi_events,
stack_storage,
spawner,
));
spawner.spawn(wifi_device_loop_token);
}
}
#[embassy_executor::task]
async fn [<wifi_device_loop_ $suffix>](
pin_23: Peri<'static, PIN_23>,
pin_24: Peri<'static, PIN_24>,
pin_25: Peri<'static, PIN_25>,
pin_29: Peri<'static, PIN_29>,
pio: Peri<'static, peripherals::$pio>,
dma: DmaChannel<'static>,
mode: WifiMode,
captive_portal_ssid: &'static str,
wifi_events: &'static WifiEvents,
stack_storage: &'static StackStorage,
spawner: Spawner,
) -> ! {
wifi_device_loop_impl::<peripherals::$pio>(
pin_23,
pin_24,
pin_25,
pin_29,
pio,
dma,
mode,
captive_portal_ssid,
wifi_events,
stack_storage,
spawner,
)
.await
}
#[embassy_executor::task]
async fn [<wifi_task_ $suffix>](
runner: cyw43::Runner<'static, cyw43::SpiBus<Output<'static>, PioSpi<'static, peripherals::$pio, 0>>>,
) -> ! {
wifi_task_impl::<peripherals::$pio>(runner).await
}
}
};
}
impl_wifi_pio!(PIO0, pio0);
impl_wifi_pio!(PIO1, pio1);
#[cfg(feature = "pico2")]
impl_wifi_pio!(PIO2, pio2);