Skip to main content

esp_radio/wifi/
ap.rs

1//! Wi-Fi access point.
2
3use alloc::string::String;
4use core::fmt;
5
6use procmacros::BuilderLite;
7
8#[cfg(feature = "unstable")]
9use super::CountryInfo;
10use super::{AuthenticationMethod, DisconnectReason, Protocols, SecondaryChannel, Ssid};
11use crate::{WifiError, sys::include::wifi_ap_record_t};
12
13/// Information about a detected Wi-Fi access point.
14#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
15#[cfg_attr(feature = "defmt", derive(defmt::Format))]
16#[non_exhaustive]
17pub struct AccessPointInfo {
18    /// The SSID of the access point.
19    pub ssid: Ssid,
20    /// The BSSID (MAC address) of the access point.
21    pub bssid: [u8; 6],
22    /// The channel the access point is operating on.
23    pub channel: u8,
24    /// The secondary channel configuration of the access point.
25    pub secondary_channel: SecondaryChannel,
26    /// The signal strength of the access point (RSSI).
27    pub signal_strength: i8,
28    /// The authentication method used by the access point.
29    pub auth_method: Option<AuthenticationMethod>,
30    #[cfg(feature = "unstable")]
31    #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
32    /// The country information of the access point (if available from beacon frames).
33    pub country: Option<CountryInfo>,
34}
35
36/// Configuration for a Wi-Fi access point.
37#[derive(Clone, PartialEq, Eq, BuilderLite, Hash)]
38pub struct AccessPointConfig {
39    /// The SSID of the access point.
40    #[builder_lite(skip_setter)]
41    pub(crate) ssid: Ssid,
42    /// Whether the SSID is hidden or visible.
43    pub(crate) ssid_hidden: bool,
44    /// The channel the access point will operate on.
45    pub(crate) channel: u8,
46    /// The secondary channel configuration.
47    pub(crate) secondary_channel: Option<SecondaryChannel>,
48    /// The set of protocols supported by the access point.
49    pub(crate) protocols: Protocols,
50    /// The authentication method to be used by the access point.
51    pub(crate) auth_method: AuthenticationMethod,
52    /// The password for securing the access point (if applicable).
53    #[builder_lite(reference)]
54    pub(crate) password: String,
55    /// The maximum number of connections allowed on the access point.
56    #[builder_lite(unstable)]
57    pub(crate) max_connections: u16,
58    /// Dtim period of the access point (Range: 1 ~ 10).
59    #[builder_lite(unstable)]
60    pub(crate) dtim_period: u8,
61    /// Time to force deauth the station if the Soft-AccessPoint doesn't receive any data.
62    #[builder_lite(unstable)]
63    pub(crate) beacon_timeout: u16,
64}
65
66impl AccessPointConfig {
67    /// Set the SSID of the access point.
68    pub fn with_ssid(mut self, ssid: impl Into<Ssid>) -> Self {
69        self.ssid = ssid.into();
70        self
71    }
72
73    pub(crate) fn validate(&self) -> Result<(), WifiError> {
74        if self.ssid.len() > 32 {
75            return Err(WifiError::InvalidArguments);
76        }
77
78        if self.password.len() >= 64 {
79            return Err(WifiError::InvalidArguments);
80        }
81
82        if !(1..=10).contains(&self.dtim_period) {
83            return Err(WifiError::InvalidArguments);
84        }
85
86        Ok(())
87    }
88}
89
90impl Default for AccessPointConfig {
91    fn default() -> Self {
92        Self {
93            ssid: "iot-device".into(),
94            ssid_hidden: false,
95            channel: 1,
96            secondary_channel: None,
97            protocols: Protocols::default(),
98            auth_method: AuthenticationMethod::None,
99            password: String::new(),
100            max_connections: 255,
101            dtim_period: 2,
102            beacon_timeout: 300,
103        }
104    }
105}
106
107impl fmt::Debug for AccessPointConfig {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        f.debug_struct("AccessPointConfig")
110            .field("ssid", &self.ssid)
111            .field("ssid_hidden", &self.ssid_hidden)
112            .field("channel", &self.channel)
113            .field("secondary_channel", &self.secondary_channel)
114            .field("protocols", &self.protocols)
115            .field("auth_method", &self.auth_method)
116            .field("password", &"**REDACTED**")
117            .field("max_connections", &self.max_connections)
118            .field("dtim_period", &self.dtim_period)
119            .field("beacon_timeout", &self.beacon_timeout)
120            .finish()
121    }
122}
123
124#[cfg(feature = "defmt")]
125impl defmt::Format for AccessPointConfig {
126    fn format(&self, fmt: defmt::Formatter<'_>) {
127        defmt::write!(
128            fmt,
129            "AccessPointConfig {{\
130            ssid: {}, \
131            ssid_hidden: {}, \
132            channel: {}, \
133            secondary_channel: {}, \
134            protocols: {}, \
135            auth_method: {}, \
136            password: **REDACTED**, \
137            max_connections: {}, \
138            dtim_period: {}, \
139            beacon_timeout: {} \
140            }}",
141            self.ssid.as_str(),
142            self.ssid_hidden,
143            self.channel,
144            self.secondary_channel,
145            self.protocols,
146            self.auth_method,
147            self.max_connections,
148            self.dtim_period,
149            self.beacon_timeout
150        );
151    }
152}
153
154/// Information about a station connected to the access point.
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
156#[cfg_attr(feature = "defmt", derive(defmt::Format))]
157#[non_exhaustive]
158pub struct ConnectedInfo {
159    /// The MAC address.
160    pub mac: [u8; 6],
161    /// The Association ID (AID) of the connected station.
162    pub aid: u16,
163    /// If this is a mesh child.
164    pub is_mesh_child: bool,
165}
166
167/// Information about a station disconnected from the access point.
168#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
169#[cfg_attr(feature = "defmt", derive(defmt::Format))]
170#[non_exhaustive]
171pub struct DisconnectedInfo {
172    /// The MAC address.
173    pub mac: [u8; 6],
174    /// The Association ID (AID) of the connected station.
175    pub aid: u16,
176    /// If this is a mesh child.
177    pub is_mesh_child: bool,
178    /// The disconnect reason.
179    pub reason: DisconnectReason,
180}
181
182/// Either the [ConnectedInfo] or [DisconnectedInfo].
183#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
184#[cfg_attr(feature = "defmt", derive(defmt::Format))]
185pub enum EventInfo {
186    /// Information about a station connected to the access point.
187    Connected(ConnectedInfo),
188    /// Information about a station disconnected from the access point.
189    Disconnected(DisconnectedInfo),
190}
191
192#[allow(non_upper_case_globals)]
193pub(crate) fn convert_ap_info(record: &wifi_ap_record_t) -> AccessPointInfo {
194    let str_len = record
195        .ssid
196        .iter()
197        .position(|&c| c == 0)
198        .unwrap_or(record.ssid.len());
199    let ssid = Ssid::from(&record.ssid[..str_len]);
200
201    AccessPointInfo {
202        ssid,
203        bssid: record.bssid,
204        channel: record.primary,
205        secondary_channel: SecondaryChannel::from_raw(record.second),
206        signal_strength: record.rssi,
207        auth_method: Some(AuthenticationMethod::from_raw(record.authmode)),
208        #[cfg(feature = "unstable")]
209        #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
210        country: CountryInfo::try_from_c(&record.country),
211    }
212}