use crate::button::Button;
use core::future::Future;
mod fields;
mod portal;
pub use portal::FormData;
#[doc(hidden)] pub use portal::HtmlBuffer;
pub use portal::WifiAutoField;
#[doc(hidden)] pub use portal::generate_config_page;
#[doc(hidden)] pub use portal::parse_post;
pub type WifiStack = &'static embassy_net::Stack<'static>;
#[doc(hidden)]
#[macro_export]
macro_rules! __impl_wifi_auto_connect {
(
$(#[$meta:meta])*
fn $name:ident (&self as $self_ident:ident, $on_event:ident) -> $return_ty:ty $body:block
) => {
$(#[$meta])*
pub async fn $name<OnEvent, OnEventFuture>(
&self,
mut $on_event: OnEvent,
) -> $return_ty
where
OnEvent: FnMut($crate::wifi_auto::WifiAutoEvent) -> OnEventFuture,
OnEventFuture: core::future::Future<Output = crate::Result<()>>,
{
let $self_ident = self;
$body
}
};
(
$(#[$meta:meta])*
fn $name:ident (self as $self_ident:ident, $on_event:ident) -> $return_ty:ty $body:block
) => {
$(#[$meta])*
pub async fn $name<OnEvent, OnEventFuture>(
self,
mut $on_event: OnEvent,
) -> $return_ty
where
OnEvent: FnMut($crate::wifi_auto::WifiAutoEvent) -> OnEventFuture,
OnEventFuture: core::future::Future<Output = crate::Result<()>>,
{
let $self_ident = self;
$body
}
};
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum WifiAutoEvent {
CaptivePortalReady,
Connecting {
try_index: u8,
try_count: u8,
},
ConnectionFailed,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum WifiAutoError {
FormatError,
StorageCorrupted,
MissingCustomWifiAutoField,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
#[doc(hidden)] pub enum WifiStartMode {
Client,
CaptivePortal,
}
impl Default for WifiStartMode {
fn default() -> Self {
Self::Client
}
}
#[must_use]
#[doc(hidden)] pub const fn should_enter_captive_portal(
wifi_start_mode: WifiStartMode,
force_captive_portal: bool,
has_persisted_credentials: bool,
custom_fields_satisfied: bool,
) -> bool {
force_captive_portal
|| !custom_fields_satisfied
|| !has_persisted_credentials
|| matches!(wifi_start_mode, WifiStartMode::CaptivePortal)
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
#[doc(hidden)] pub struct WifiCredentials {
pub ssid: heapless::String<32>,
pub password: heapless::String<64>,
}
impl WifiCredentials {
#[must_use]
pub fn new(ssid: &str, password: &str) -> Self {
assert!(!ssid.is_empty(), "ssid must not be empty");
let mut ssid_string = heapless::String::<32>::new();
ssid_string
.push_str(ssid)
.expect("ssid exceeds 32 characters");
let mut password_string = heapless::String::<64>::new();
password_string
.push_str(password)
.expect("password exceeds 64 characters");
Self {
ssid: ssid_string,
password: password_string,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
#[doc(hidden)] pub struct WifiAutoPersistedState {
pub wifi_credentials: Option<WifiCredentials>,
pub wifi_start_mode: WifiStartMode,
}
impl Default for WifiAutoPersistedState {
fn default() -> Self {
Self {
wifi_credentials: None,
wifi_start_mode: WifiStartMode::Client,
}
}
}
#[allow(async_fn_in_trait)]
pub trait WifiAuto {
type Error;
async fn connect<OnEvent, OnEventFuture>(
self,
button: &mut impl Button,
on_event: OnEvent,
) -> Result<WifiStack, Self::Error>
where
OnEvent: FnMut(WifiAutoEvent) -> OnEventFuture,
OnEventFuture: Future<Output = Result<(), Self::Error>>;
}
#[doc(hidden)] pub trait WifiAutoBackend {
type Error;
fn force_captive_portal(&self) -> bool;
fn try_count(&self) -> u8;
fn load_start_mode(&self) -> Result<WifiStartMode, Self::Error>;
fn custom_fields_satisfied(&self) -> Result<bool, Self::Error>;
fn load_persisted_credentials(&self) -> Result<Option<WifiCredentials>, Self::Error>;
fn persist_credentials(&self, wifi_credentials: &WifiCredentials) -> Result<(), Self::Error>;
fn set_start_mode(&self, wifi_start_mode: WifiStartMode) -> Result<(), Self::Error>;
fn run_captive_portal(
&mut self,
) -> impl Future<Output = Result<WifiCredentials, Self::Error>> + '_;
fn on_resolved_credentials(
&mut self,
wifi_credentials: &WifiCredentials,
) -> impl Future<Output = Result<(), Self::Error>> + '_;
fn on_connect_attempt(
&mut self,
try_index: u8,
) -> impl Future<Output = Result<bool, Self::Error>> + '_;
}
#[doc(hidden)] pub async fn connect_with_backend<Backend, OnEvent, OnEventFuture>(
backend: &mut Backend,
on_event: &mut OnEvent,
) -> Result<bool, Backend::Error>
where
Backend: WifiAutoBackend,
OnEvent: FnMut(WifiAutoEvent) -> OnEventFuture,
OnEventFuture: Future<Output = Result<(), Backend::Error>>,
{
let wifi_start_mode = backend.load_start_mode()?;
let custom_fields_satisfied = backend.custom_fields_satisfied()?;
let mut wifi_credentials = backend.load_persisted_credentials()?;
let has_persisted_credentials = wifi_credentials.is_some();
if should_enter_captive_portal(
wifi_start_mode,
backend.force_captive_portal(),
has_persisted_credentials,
custom_fields_satisfied,
) {
on_event(WifiAutoEvent::CaptivePortalReady).await?;
let portal_wifi_credentials = backend.run_captive_portal().await?;
backend.persist_credentials(&portal_wifi_credentials)?;
backend.set_start_mode(WifiStartMode::Client)?;
wifi_credentials = Some(portal_wifi_credentials);
}
let wifi_credentials =
wifi_credentials.expect("wifi credentials should exist after captive portal fallback");
backend.on_resolved_credentials(&wifi_credentials).await?;
for try_index in 0..backend.try_count() {
on_event(WifiAutoEvent::Connecting {
try_index,
try_count: backend.try_count(),
})
.await?;
if backend.on_connect_attempt(try_index).await? {
return Ok(true);
}
}
on_event(WifiAutoEvent::ConnectionFailed).await?;
Ok(false)
}