1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
use anyhow::{anyhow, Result};
use embedded_svc::wifi::{AuthMethod, ClientConfiguration, Configuration};
use esp_idf_svc::wifi::{BlockingWifi, EspWifi};
/// Wi-Fi network configuration containing SSID, password, and authentication method.
///
/// # Fields
/// * `ssid` - The network SSID.
/// * `password` - The network password.
/// * `auth` - The authentication method (e.g., `WPA2Personal`).
pub struct Config {
ssid: &'static str,
password: &'static str,
auth: AuthMethod,
}
impl Config {
fn new(ssid: &'static str, password: &'static str, auth: AuthMethod) -> Self {
Self {
ssid,
password,
auth,
}
}
/// Returns the configured Wi-Fi SSID.
///
/// # Returns
/// The SSID as a string slice.
#[must_use]
pub fn ssid(&self) -> &str {
self.ssid
}
/// Returns the configured Wi-Fi password.
///
/// # Returns
/// The password as a string slice.
#[must_use]
pub fn password(&self) -> &str {
self.password
}
/// Returns the configured authentication method.
///
/// # Returns
/// The [`AuthMethod`] variant for this configuration.
#[must_use]
pub fn auth(&self) -> AuthMethod {
self.auth
}
/// Creates a `Config` from compile-time environment variables.
///
/// Reads `WIFI_SSID` and `WIFI_PASSWORD` via `option_env!` and defaults
/// to `WPA2Personal` authentication.
///
/// # Returns
/// A `Config` populated from environment variables.
///
/// # Errors
/// Returns an error if `WIFI_SSID` or `WIFI_PASSWORD` is not set at compile time.
pub fn from_env() -> Result<Self> {
let ssid = option_env!("WIFI_SSID")
.ok_or_else(|| anyhow!("WIFI_SSID environment variable not set"))?;
let password = option_env!("WIFI_PASSWORD")
.ok_or_else(|| anyhow!("WIFI_PASSWORD environment variable not set"))?;
Ok(Self::new(ssid, password, AuthMethod::WPA2Personal))
}
}
/// Represents a Wi-Fi connection, handling its configuration and state management.
///
/// This struct leverages the `BlockingWifi` handler from the ESP-IDF framework for managing the connection.
pub struct Connection<'a> {
handler: BlockingWifi<EspWifi<'a>>,
}
impl<'a> Connection<'a> {
/// Creates a new `Connection` instance with the given Wi-Fi handler and configuration.
///
/// Configures, starts, connects, and waits for the network interface to come up.
///
/// # Arguments
///
/// * `handler` - The Wi-Fi handler to manage the connection.
/// * `config` - The Wi-Fi configuration containing SSID, password, and authentication method.
///
/// # Returns
///
/// A connected `Connection` instance ready for use.
///
/// # Errors
///
/// Returns an error if the configuration cannot be set, SSID/password conversion fails,
/// or the connection cannot be established.
pub fn new(handler: BlockingWifi<EspWifi<'a>>, config: &Config) -> Result<Self> {
let configuration: Configuration =
Configuration::Client(ClientConfiguration {
auth_method: config.auth(),
ssid: config
.ssid()
.try_into()
.map_err(|()| anyhow!("Failed to convert SSID"))?,
password: config
.password()
.try_into()
.map_err(|()| anyhow!("Failed to convert password"))?,
..Default::default()
});
let mut handler = handler;
handler.set_configuration(&configuration)?;
handler.start()?;
handler.connect()?;
handler.wait_netif_up()?;
Ok(Self { handler })
}
/// Checks if the Wi-Fi connection is currently on.
///
/// # Returns
///
/// `true` if the connection is on, `false` otherwise.
///
/// # Errors
///
/// Returns an error if checking the state fails.
pub fn is_on(&self) -> Result<bool> {
Ok(self.handler.is_connected()?)
}
}