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()?)
    }
}