Skip to main content

esp_radio/wifi/
scan.rs

1//! Wi-Fi scanning.
2
3use core::{marker::PhantomData, mem::MaybeUninit};
4
5use esp_hal::time::Duration;
6use procmacros::BuilderLite;
7
8use crate::{
9    sys::include,
10    wifi::{
11        Ssid,
12        WifiController,
13        WifiError,
14        ap::{AccessPointInfo, convert_ap_info},
15        esp_wifi_result,
16    },
17};
18
19/// Configuration for active or passive scan.
20///
21/// # Comparison of active and passive scan
22///
23/// |                                      | **Active** | **Passive** |
24/// |--------------------------------------|------------|-------------|
25/// | **Power consumption**                |    High    |     Low     |
26/// | **Time required (typical behavior)** |     Low    |     High    |
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28#[cfg_attr(feature = "defmt", derive(defmt::Format))]
29#[non_exhaustive]
30pub enum ScanTypeConfig {
31    /// Active scan with min and max scan time per channel. This is the default
32    /// and recommended if you are unsure.
33    ///
34    /// # Procedure
35    /// 1. Send probe request on each channel.
36    /// 2. Wait for probe response. Wait at least `min` time, but if no response is received, wait
37    ///    up to `max` time.
38    /// 3. Switch channel.
39    /// 4. Repeat from 1.
40    Active {
41        /// Minimum scan time per channel. Defaults to 10ms.
42        min: Duration,
43        /// Maximum scan time per channel. Defaults to 20ms.
44        max: Duration,
45    },
46    /// Passive scan
47    ///
48    /// # Procedure
49    /// 1. Wait for beacon for given duration.
50    /// 2. Switch channel.
51    /// 3. Repeat from 1.
52    ///
53    /// # Note
54    /// It is recommended to avoid duration longer than 1500ms, as it may cause
55    /// a station to disconnect from the Access Point.
56    Passive(Duration),
57}
58
59impl Default for ScanTypeConfig {
60    fn default() -> Self {
61        Self::Active {
62            min: Duration::from_millis(10),
63            max: Duration::from_millis(20),
64        }
65    }
66}
67
68impl ScanTypeConfig {
69    pub(crate) fn validate(&self) {
70        if matches!(self, Self::Passive(dur) if *dur > Duration::from_millis(1500)) {
71            warn!(
72                "Passive scan duration longer than 1500ms may cause a station to disconnect from the access point"
73            );
74        }
75    }
76}
77
78/// Scan configuration.
79#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, BuilderLite)]
80#[cfg_attr(feature = "defmt", derive(defmt::Format))]
81#[non_exhaustive]
82pub struct ScanConfig {
83    /// SSID to filter for.
84    /// If [`None`] is passed, all SSIDs will be returned.
85    /// If [`Some`] is passed, only the APs matching the given SSID will be
86    /// returned.
87    pub(crate) ssid: Option<Ssid>,
88    /// BSSID to filter for.
89    /// If [`None`] is passed, all BSSIDs will be returned.
90    /// If [`Some`] is passed, only the APs matching the given BSSID will be
91    /// returned.
92    pub(crate) bssid: Option<[u8; 6]>,
93    /// Channel to filter for.
94    /// If [`None`] is passed, all channels will be returned.
95    /// If [`Some`] is passed, only the APs on the given channel will be
96    /// returned.
97    pub(crate) channel: Option<u8>,
98    /// Whether to show hidden networks.
99    pub(crate) show_hidden: bool,
100    /// Scan type, active or passive.
101    pub(crate) scan_type: ScanTypeConfig,
102    /// The maximum number of networks to return when scanning.
103    /// If [`None`] is passed, all networks will be returned.
104    /// If [`Some`] is passed, the specified number of networks will be returned.
105    pub(crate) max: Option<usize>,
106}
107
108/// Wi-Fi scan results.
109#[derive(Debug)]
110#[cfg_attr(feature = "defmt", derive(defmt::Format))]
111#[non_exhaustive]
112pub struct ScanResults<'d> {
113    /// Number of APs to return
114    remaining: usize,
115    /// Ensures the result list is free'd when this struct is dropped.
116    _drop_guard: FreeApListOnDrop,
117    /// Hold a lifetime to ensure the scan list is freed before a new scan is started.
118    _marker: PhantomData<&'d mut ()>,
119}
120
121impl<'d> ScanResults<'d> {
122    /// Create new Wi-Fi scan results.
123    pub fn new(_controller: &'d mut WifiController<'_>) -> Result<Self, WifiError> {
124        // Construct Self first. This ensures we'll free the result list even if `get_ap_num`
125        // returns an error.
126        let mut this = Self {
127            remaining: 0,
128            _drop_guard: FreeApListOnDrop,
129            _marker: PhantomData,
130        };
131
132        let mut bss_total = 0;
133        unsafe { esp_wifi_result!(include::esp_wifi_scan_get_ap_num(&mut bss_total))? };
134
135        this.remaining = bss_total as usize;
136
137        Ok(this)
138    }
139}
140
141impl Iterator for ScanResults<'_> {
142    type Item = AccessPointInfo;
143
144    fn next(&mut self) -> Option<Self::Item> {
145        if self.remaining == 0 {
146            return None;
147        }
148
149        self.remaining -= 1;
150
151        let mut record: MaybeUninit<include::wifi_ap_record_t> = MaybeUninit::uninit();
152
153        // We could detect ESP_FAIL to see if we've exhausted the list, but we know the number of
154        // results. Reading the number of results also ensures we're in the correct state, so
155        // unwrapping here should never fail.
156        unwrap!(unsafe {
157            esp_wifi_result!(include::esp_wifi_scan_get_ap_record(record.as_mut_ptr()))
158        });
159
160        Some(convert_ap_info(unsafe { record.assume_init_ref() }))
161    }
162}
163
164/// AP list on-drop guard.
165#[derive(Debug)]
166#[cfg_attr(feature = "defmt", derive(defmt::Format))]
167pub(super) struct FreeApListOnDrop;
168
169impl FreeApListOnDrop {
170    /// Do not automatically free the AP list when the guard is dropped.
171    pub fn defuse(self) {
172        core::mem::forget(self);
173    }
174}
175
176impl Drop for FreeApListOnDrop {
177    fn drop(&mut self) {
178        unsafe {
179            include::esp_wifi_clear_ap_list();
180        }
181    }
182}