Skip to main content

esp_idf_svc/
wifi.rs

1//! WiFi support
2use core::marker::PhantomData;
3use core::str::Utf8Error;
4use core::time::Duration;
5use core::{cmp, ffi, fmt, ops};
6
7extern crate alloc;
8use alloc::boxed::Box;
9use alloc::sync::Arc;
10
11use enumset::*;
12
13use embedded_svc::wifi::Wifi;
14
15use crate::hal::modem::WifiModemPeripheral;
16
17use crate::sys::*;
18
19use crate::eventloop::EspEventLoop;
20use crate::eventloop::{
21    EspEventDeserializer, EspEventSource, EspSubscription, EspSystemEventLoop, System, Wait,
22};
23use crate::handle::RawHandle;
24#[cfg(esp_idf_comp_esp_netif_enabled)]
25use crate::netif::*;
26use crate::nvs::EspDefaultNvsPartition;
27use crate::private::common::*;
28use crate::private::cstr::*;
29use crate::private::mutex;
30#[cfg(all(feature = "alloc", esp_idf_comp_esp_timer_enabled))]
31use crate::timer::EspTaskTimerService;
32
33pub use embedded_svc::wifi::{
34    AccessPointConfiguration, AccessPointInfo, AuthMethod, Capability, ClientConfiguration,
35    Configuration, PmfConfiguration, Protocol, ScanMethod, ScanSortMethod, SecondaryChannel,
36};
37
38pub mod config {
39    use core::time::Duration;
40
41    use crate::sys::*;
42
43    #[derive(Clone, Debug, PartialEq, Eq)]
44    pub enum ScanType {
45        Active { min: Duration, max: Duration },
46        Passive(Duration),
47    }
48
49    impl ScanType {
50        pub const fn new() -> Self {
51            Self::Active {
52                min: Duration::from_secs(0),
53                max: Duration::from_secs(0),
54            }
55        }
56    }
57
58    impl Default for ScanType {
59        fn default() -> Self {
60            Self::new()
61        }
62    }
63
64    #[derive(Clone, Debug, PartialEq, Eq)]
65    pub struct ScanConfig {
66        pub bssid: Option<[u8; 6]>,
67        pub ssid: Option<heapless::String<32>>,
68        pub channel: Option<u8>,
69        pub scan_type: ScanType,
70        pub show_hidden: bool,
71    }
72
73    impl ScanConfig {
74        pub const fn new() -> Self {
75            Self {
76                bssid: None,
77                ssid: None,
78                channel: None,
79                scan_type: ScanType::new(),
80                show_hidden: false,
81            }
82        }
83    }
84
85    impl Default for ScanConfig {
86        fn default() -> Self {
87            Self::new()
88        }
89    }
90
91    impl From<&ScanConfig> for wifi_scan_config_t {
92        fn from(s: &ScanConfig) -> Self {
93            #[allow(clippy::needless_update)]
94            Self {
95                bssid: s.bssid.map_or(core::ptr::null(), |v| v.as_ptr()) as *mut u8,
96                ssid: s.ssid.as_ref().map_or(core::ptr::null(), |v| v.as_ptr()) as *mut u8,
97                scan_time: wifi_scan_time_t {
98                    active: wifi_active_scan_time_t {
99                        min: match s.scan_type {
100                            ScanType::Active { min, .. } => min.as_millis() as _,
101                            _ => 0,
102                        },
103                        max: match s.scan_type {
104                            ScanType::Active { max, .. } => max.as_millis() as _,
105                            _ => 0,
106                        },
107                    },
108                    passive: match s.scan_type {
109                        ScanType::Passive(time) => time.as_millis() as _,
110                        _ => 0,
111                    },
112                },
113                channel: s.channel.unwrap_or_default(),
114                scan_type: matches!(s.scan_type, ScanType::Passive { .. }).into(),
115                show_hidden: s.show_hidden,
116                ..Default::default()
117            }
118        }
119    }
120}
121
122impl From<AuthMethod> for Newtype<wifi_auth_mode_t> {
123    fn from(method: AuthMethod) -> Self {
124        Newtype(match method {
125            AuthMethod::None => wifi_auth_mode_t_WIFI_AUTH_OPEN,
126            AuthMethod::WEP => wifi_auth_mode_t_WIFI_AUTH_WEP,
127            AuthMethod::WPA => wifi_auth_mode_t_WIFI_AUTH_WPA_PSK,
128            AuthMethod::WPA2Personal => wifi_auth_mode_t_WIFI_AUTH_WPA2_PSK,
129            AuthMethod::WPAWPA2Personal => wifi_auth_mode_t_WIFI_AUTH_WPA_WPA2_PSK,
130            AuthMethod::WPA2Enterprise => wifi_auth_mode_t_WIFI_AUTH_WPA2_ENTERPRISE,
131            AuthMethod::WPA3Personal => wifi_auth_mode_t_WIFI_AUTH_WPA3_PSK,
132            AuthMethod::WPA2WPA3Personal => wifi_auth_mode_t_WIFI_AUTH_WPA2_WPA3_PSK,
133            AuthMethod::WAPIPersonal => wifi_auth_mode_t_WIFI_AUTH_WAPI_PSK,
134        })
135    }
136}
137
138impl From<Newtype<wifi_auth_mode_t>> for Option<AuthMethod> {
139    #[allow(non_upper_case_globals)]
140    #[allow(non_snake_case)]
141    fn from(mode: Newtype<wifi_auth_mode_t>) -> Self {
142        match mode.0 {
143            wifi_auth_mode_t_WIFI_AUTH_OPEN => Some(AuthMethod::None),
144            wifi_auth_mode_t_WIFI_AUTH_WEP => Some(AuthMethod::WEP),
145            wifi_auth_mode_t_WIFI_AUTH_WPA_PSK => Some(AuthMethod::WPA),
146            wifi_auth_mode_t_WIFI_AUTH_WPA2_PSK => Some(AuthMethod::WPA2Personal),
147            wifi_auth_mode_t_WIFI_AUTH_WPA_WPA2_PSK => Some(AuthMethod::WPAWPA2Personal),
148            wifi_auth_mode_t_WIFI_AUTH_WPA2_ENTERPRISE => Some(AuthMethod::WPA2Enterprise),
149            wifi_auth_mode_t_WIFI_AUTH_WPA3_PSK => Some(AuthMethod::WPA3Personal),
150            wifi_auth_mode_t_WIFI_AUTH_WPA2_WPA3_PSK => Some(AuthMethod::WPA2WPA3Personal),
151            wifi_auth_mode_t_WIFI_AUTH_WAPI_PSK => Some(AuthMethod::WAPIPersonal),
152            _ => None,
153        }
154    }
155}
156
157impl TryFrom<&ClientConfiguration> for Newtype<wifi_sta_config_t> {
158    type Error = EspError;
159
160    fn try_from(conf: &ClientConfiguration) -> Result<Self, Self::Error> {
161        let bssid: [u8; 6] = match &conf.bssid {
162            Some(bssid_ref) => *bssid_ref,
163            None => [0; 6],
164        };
165
166        #[allow(clippy::needless_update)]
167        let mut result = wifi_sta_config_t {
168            ssid: [0; 32],
169            password: [0; 64],
170            scan_method: match conf.scan_method {
171                ScanMethod::CompleteScan(_) => wifi_scan_method_t_WIFI_ALL_CHANNEL_SCAN,
172                ScanMethod::FastScan => wifi_scan_method_t_WIFI_FAST_SCAN,
173                _ => wifi_scan_method_t_WIFI_ALL_CHANNEL_SCAN,
174            },
175            bssid_set: conf.bssid.is_some(),
176            bssid,
177            channel: conf.channel.unwrap_or(0u8),
178            listen_interval: 0,
179            sort_method: match conf.scan_method {
180                ScanMethod::CompleteScan(ScanSortMethod::Signal) => {
181                    wifi_sort_method_t_WIFI_CONNECT_AP_BY_SIGNAL
182                }
183                ScanMethod::CompleteScan(ScanSortMethod::Security) => {
184                    wifi_sort_method_t_WIFI_CONNECT_AP_BY_SECURITY
185                }
186                _ => wifi_sort_method_t_WIFI_CONNECT_AP_BY_SIGNAL,
187            },
188            threshold: wifi_scan_threshold_t {
189                rssi: -127,
190                authmode: Newtype::<wifi_auth_mode_t>::from(conf.auth_method).0,
191                ..Default::default()
192            },
193            pmf_cfg: wifi_pmf_config_t {
194                capable: false,
195                required: false,
196            },
197            ..Default::default()
198        };
199
200        set_str_no_termination_requirement(&mut result.ssid, conf.ssid.as_ref())?;
201        set_str_no_termination_requirement(&mut result.password, conf.password.as_ref())?;
202
203        Ok(Newtype(result))
204    }
205}
206
207impl From<Newtype<wifi_sta_config_t>> for ClientConfiguration {
208    fn from(conf: Newtype<wifi_sta_config_t>) -> Self {
209        Self {
210            ssid: array_to_heapless_string(conf.0.ssid),
211            bssid: if conf.0.bssid_set {
212                Some(conf.0.bssid)
213            } else {
214                None
215            },
216            auth_method: Option::<AuthMethod>::from(Newtype(conf.0.threshold.authmode)).unwrap(),
217            password: array_to_heapless_string(conf.0.password),
218            channel: if conf.0.channel != 0 {
219                Some(conf.0.channel)
220            } else {
221                None
222            },
223            #[allow(non_upper_case_globals)]
224            #[allow(non_snake_case)]
225            scan_method: match conf.0.scan_method {
226                wifi_scan_method_t_WIFI_FAST_SCAN => ScanMethod::FastScan,
227                wifi_scan_method_t_WIFI_ALL_CHANNEL_SCAN => match conf.0.sort_method {
228                    wifi_sort_method_t_WIFI_CONNECT_AP_BY_SIGNAL => {
229                        ScanMethod::CompleteScan(ScanSortMethod::Signal)
230                    }
231                    wifi_sort_method_t_WIFI_CONNECT_AP_BY_SECURITY => {
232                        ScanMethod::CompleteScan(ScanSortMethod::Security)
233                    }
234                    _ => ScanMethod::default(),
235                },
236                _ => ScanMethod::default(),
237            },
238            pmf_cfg: match conf.0.pmf_cfg {
239                wifi_pmf_config_t {
240                    capable: false,
241                    required: _,
242                } => PmfConfiguration::NotCapable,
243                wifi_pmf_config_t {
244                    capable: true,
245                    required,
246                } => PmfConfiguration::Capable { required },
247            },
248        }
249    }
250}
251
252impl TryFrom<&AccessPointConfiguration> for Newtype<wifi_ap_config_t> {
253    type Error = EspError;
254
255    fn try_from(conf: &AccessPointConfiguration) -> Result<Self, Self::Error> {
256        let mut result = wifi_ap_config_t {
257            ssid: [0; 32],
258            password: [0; 64],
259            ssid_len: conf.ssid.len() as u8,
260            channel: conf.channel,
261            authmode: Newtype::<wifi_auth_mode_t>::from(conf.auth_method).0,
262            ssid_hidden: u8::from(conf.ssid_hidden),
263            max_connection: cmp::min(conf.max_connections, 16) as u8,
264            beacon_interval: 100,
265            ..Default::default()
266        };
267
268        set_str(&mut result.ssid, conf.ssid.as_ref())?;
269        set_str(&mut result.password, conf.password.as_ref())?;
270
271        Ok(Newtype(result))
272    }
273}
274
275impl From<Newtype<wifi_ap_config_t>> for AccessPointConfiguration {
276    fn from(conf: Newtype<wifi_ap_config_t>) -> Self {
277        Self {
278            ssid: if conf.0.ssid_len == 0 {
279                Default::default()
280            } else {
281                unsafe {
282                    core::str::from_utf8_unchecked(&conf.0.ssid[0..conf.0.ssid_len as usize])
283                        .try_into()
284                        .unwrap()
285                }
286            },
287            ssid_hidden: conf.0.ssid_hidden != 0,
288            channel: conf.0.channel,
289            secondary_channel: None,
290            auth_method: Option::<AuthMethod>::from(Newtype(conf.0.authmode)).unwrap(),
291            protocols: EnumSet::<Protocol>::empty(), // TODO
292            password: array_to_heapless_string(conf.0.password),
293            max_connections: conf.0.max_connection as u16,
294        }
295    }
296}
297
298impl TryFrom<Newtype<&wifi_ap_record_t>> for AccessPointInfo {
299    type Error = Utf8Error;
300
301    #[allow(non_upper_case_globals)]
302    #[allow(non_snake_case)]
303    fn try_from(ap_info: Newtype<&wifi_ap_record_t>) -> Result<Self, Self::Error> {
304        let a = ap_info.0;
305
306        Ok(Self {
307            ssid: from_cstr_fallible(&a.ssid)?.try_into().unwrap(),
308            bssid: a.bssid,
309            channel: a.primary,
310            secondary_channel: match a.second {
311                wifi_second_chan_t_WIFI_SECOND_CHAN_NONE => SecondaryChannel::None,
312                wifi_second_chan_t_WIFI_SECOND_CHAN_ABOVE => SecondaryChannel::Above,
313                wifi_second_chan_t_WIFI_SECOND_CHAN_BELOW => SecondaryChannel::Below,
314                _ => panic!(),
315            },
316            signal_strength: a.rssi,
317            protocols: EnumSet::<Protocol>::empty(), // TODO
318            auth_method: Option::<AuthMethod>::from(Newtype::<wifi_auth_mode_t>(a.authmode)),
319        })
320    }
321}
322
323#[derive(Copy, Clone, Debug, Eq, PartialEq)]
324pub enum WifiDeviceId {
325    Ap,
326    Sta,
327}
328
329impl From<WifiDeviceId> for wifi_interface_t {
330    fn from(id: WifiDeviceId) -> Self {
331        match id {
332            WifiDeviceId::Ap => wifi_interface_t_WIFI_IF_AP,
333            WifiDeviceId::Sta => wifi_interface_t_WIFI_IF_STA,
334        }
335    }
336}
337
338#[allow(non_upper_case_globals)]
339impl From<wifi_interface_t> for WifiDeviceId {
340    fn from(id: wifi_interface_t) -> Self {
341        match id {
342            wifi_interface_t_WIFI_IF_AP => WifiDeviceId::Ap,
343            wifi_interface_t_WIFI_IF_STA => WifiDeviceId::Sta,
344            _ => unreachable!(),
345        }
346    }
347}
348
349extern "C" {
350    fn esp_wifi_internal_reg_rxcb(
351        ifx: wifi_interface_t,
352        rxcb: Option<
353            unsafe extern "C" fn(
354                buffer: *mut ffi::c_void,
355                len: u16,
356                eb: *mut ffi::c_void,
357            ) -> esp_err_t,
358        >,
359    ) -> esp_err_t;
360
361    fn esp_wifi_internal_free_rx_buffer(buffer: *mut ffi::c_void);
362
363    fn esp_wifi_internal_tx(
364        wifi_if: wifi_interface_t,
365        buffer: *mut ffi::c_void,
366        len: u16,
367    ) -> esp_err_t;
368}
369
370#[allow(clippy::type_complexity)]
371static mut RX_CALLBACK: Option<
372    Box<dyn FnMut(WifiDeviceId, WifiFrame) -> Result<(), EspError> + 'static>,
373> = None;
374#[allow(clippy::type_complexity)]
375static mut TX_CALLBACK: Option<Box<dyn FnMut(WifiDeviceId, &[u8], bool) + 'static>> = None;
376
377pub trait NonBlocking {
378    fn is_scan_done(&self) -> Result<bool, EspError>;
379
380    fn start_scan(
381        &mut self,
382        scan_config: &config::ScanConfig,
383        blocking: bool,
384    ) -> Result<(), EspError>;
385
386    fn stop_scan(&mut self) -> Result<(), EspError>;
387
388    fn get_scan_result_n<const N: usize>(
389        &mut self,
390    ) -> Result<(heapless::Vec<AccessPointInfo, N>, usize), EspError>;
391
392    #[cfg(feature = "alloc")]
393    fn get_scan_result(&mut self) -> Result<alloc::vec::Vec<AccessPointInfo>, EspError>;
394
395    fn start_wps(&mut self, config: &WpsConfig) -> Result<(), EspError>;
396
397    fn stop_wps(&mut self) -> Result<WpsStatus, EspError>;
398
399    fn is_wps_finished(&self) -> Result<bool, EspError>;
400}
401
402impl<T> NonBlocking for &mut T
403where
404    T: NonBlocking,
405{
406    fn is_scan_done(&self) -> Result<bool, EspError> {
407        (**self).is_scan_done()
408    }
409
410    fn start_scan(
411        &mut self,
412        scan_config: &config::ScanConfig,
413        blocking: bool,
414    ) -> Result<(), EspError> {
415        (**self).start_scan(scan_config, blocking)
416    }
417
418    fn stop_scan(&mut self) -> Result<(), EspError> {
419        (**self).stop_scan()
420    }
421
422    fn get_scan_result_n<const N: usize>(
423        &mut self,
424    ) -> Result<(heapless::Vec<AccessPointInfo, N>, usize), EspError> {
425        (**self).get_scan_result_n()
426    }
427
428    #[cfg(feature = "alloc")]
429    fn get_scan_result(&mut self) -> Result<alloc::vec::Vec<AccessPointInfo>, EspError> {
430        (**self).get_scan_result()
431    }
432
433    fn start_wps(&mut self, config: &WpsConfig) -> Result<(), EspError> {
434        (**self).start_wps(config)
435    }
436
437    fn stop_wps(&mut self) -> Result<WpsStatus, EspError> {
438        (**self).stop_wps()
439    }
440
441    fn is_wps_finished(&self) -> Result<bool, EspError> {
442        (**self).is_wps_finished()
443    }
444}
445
446/// This struct provides a safe wrapper over the ESP IDF Wifi C driver.
447///
448/// The driver works on Layer 2 (Data Link) in the OSI model, in that it provides
449/// facilities for sending and receiving ethernet packets over the WiFi radio.
450///
451/// For most use cases, utilizing `EspWifi` - which provides a networking (IP)
452/// layer as well - should be preferred. Using `WifiDriver` directly is beneficial
453/// only when one would like to utilize a custom, non-STD network stack like `smoltcp`.
454pub struct WifiDriver<'d> {
455    status: Arc<mutex::Mutex<WifiDriverStatus>>,
456    _subscription: EspSubscription<'static, System>,
457    #[cfg(all(feature = "alloc", esp_idf_comp_nvs_flash_enabled))]
458    _nvs: Option<EspDefaultNvsPartition>,
459    _p: PhantomData<&'d mut ()>,
460}
461
462#[derive(Clone, Debug)]
463struct WifiDriverStatus {
464    pub sta: WifiStaStatus,
465    pub scan: WifiScanStatus,
466    pub ap: WifiApStatus,
467    pub wps: Option<WpsStatus>,
468}
469
470impl<'d> WifiDriver<'d> {
471    #[cfg(all(feature = "alloc", esp_idf_comp_nvs_flash_enabled))]
472    pub fn new<M: WifiModemPeripheral + 'd>(
473        _modem: M,
474        sysloop: EspSystemEventLoop,
475        nvs: Option<EspDefaultNvsPartition>,
476    ) -> Result<Self, EspError> {
477        Self::init(nvs.is_some())?;
478
479        let (status, subscription) = Self::subscribe(&sysloop)?;
480
481        Ok(Self {
482            status,
483            _subscription: subscription,
484            _nvs: nvs,
485            _p: PhantomData,
486        })
487    }
488
489    #[cfg(not(all(feature = "alloc", esp_idf_comp_nvs_flash_enabled)))]
490    pub fn new<M: WifiModemPeripheral + 'd>(
491        _modem: M,
492        sysloop: EspSystemEventLoop,
493    ) -> Result<Self, EspError> {
494        Self::init(false)?;
495
496        let (status, subscription) = Self::subscribe(&sysloop)?;
497
498        Ok(Self {
499            status,
500            _subscription: subscription,
501            _p: PhantomData,
502        })
503    }
504
505    #[allow(clippy::type_complexity)]
506    fn subscribe(
507        sysloop: &EspEventLoop<System>,
508    ) -> Result<
509        (
510            Arc<mutex::Mutex<WifiDriverStatus>>,
511            EspSubscription<'static, System>,
512        ),
513        EspError,
514    > {
515        let status = Arc::new(mutex::Mutex::new(WifiDriverStatus {
516            sta: WifiStaStatus::Stopped,
517            ap: WifiApStatus::Stopped,
518            scan: WifiScanStatus::Idle,
519            wps: None,
520        }));
521        let s_status = status.clone();
522
523        let subscription = sysloop.subscribe::<WifiEvent, _>(move |event: WifiEvent| {
524            let mut guard = s_status.lock();
525
526            match event {
527                WifiEvent::ApStarted => guard.ap = WifiApStatus::Started,
528                WifiEvent::ApStopped => guard.ap = WifiApStatus::Stopped,
529                WifiEvent::StaStarted => guard.sta = WifiStaStatus::Started,
530                WifiEvent::StaStopped => guard.sta = WifiStaStatus::Stopped,
531                WifiEvent::StaConnected(_) => guard.sta = WifiStaStatus::Connected,
532                WifiEvent::StaDisconnected(_) => guard.sta = WifiStaStatus::Started,
533                WifiEvent::ScanDone(_) => guard.scan = WifiScanStatus::Done,
534                WifiEvent::StaWpsSuccess(_)
535                | WifiEvent::StaWpsFailed
536                | WifiEvent::StaWpsTimeout
537                | WifiEvent::StaWpsPin(_)
538                | WifiEvent::StaWpsPbcOverlap => guard.wps = Some((&event).try_into().unwrap()),
539                _ => (),
540            };
541        })?;
542
543        Ok((status, subscription))
544    }
545
546    fn init(nvs_enabled: bool) -> Result<(), EspError> {
547        #[allow(clippy::needless_update)]
548        #[allow(unused_unsafe)]
549        let cfg = wifi_init_config_t {
550            #[cfg(esp_idf_version_major = "4")]
551            event_handler: Some(esp_event_send_internal),
552            osi_funcs: unsafe { core::ptr::addr_of_mut!(g_wifi_osi_funcs) },
553            wpa_crypto_funcs: unsafe { g_wifi_default_wpa_crypto_funcs },
554            static_rx_buf_num: CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM as _,
555            dynamic_rx_buf_num: CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM as _,
556            tx_buf_type: CONFIG_ESP32_WIFI_TX_BUFFER_TYPE as _,
557            static_tx_buf_num: WIFI_STATIC_TX_BUFFER_NUM as _,
558            dynamic_tx_buf_num: WIFI_DYNAMIC_TX_BUFFER_NUM as _,
559            rx_mgmt_buf_type: CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF as _,
560            rx_mgmt_buf_num: WIFI_RX_MGMT_BUF_NUM_DEF as _,
561            cache_tx_buf_num: WIFI_CACHE_TX_BUFFER_NUM as _,
562            csi_enable: WIFI_CSI_ENABLED as _,
563            ampdu_rx_enable: WIFI_AMPDU_RX_ENABLED as _,
564            ampdu_tx_enable: WIFI_AMPDU_TX_ENABLED as _,
565            amsdu_tx_enable: WIFI_AMSDU_TX_ENABLED as _,
566            nvs_enable: i32::from(nvs_enabled),
567            nano_enable: WIFI_NANO_FORMAT_ENABLED as _,
568            rx_ba_win: WIFI_DEFAULT_RX_BA_WIN as _,
569            wifi_task_core_id: WIFI_TASK_CORE_ID as _,
570            beacon_max_len: WIFI_SOFTAP_BEACON_MAX_LEN as _,
571            mgmt_sbuf_num: WIFI_MGMT_SBUF_NUM as _,
572            #[cfg(any(
573                esp_idf_version_major = "4",
574                all(esp_idf_version_major = "5", esp_idf_version_minor = "0"),
575                esp_idf_version_full = "5.1.0",
576                esp_idf_version_full = "5.1.1",
577                esp_idf_version_full = "5.1.2"
578            ))]
579            feature_caps: unsafe { g_wifi_feature_caps },
580            #[cfg(not(any(
581                esp_idf_version_major = "4",
582                all(esp_idf_version_major = "5", esp_idf_version_minor = "0"),
583                esp_idf_version_full = "5.1.0",
584                esp_idf_version_full = "5.1.1",
585                esp_idf_version_full = "5.1.2"
586            )))]
587            feature_caps: WIFI_FEATURE_CAPS as _,
588            sta_disconnected_pm: WIFI_STA_DISCONNECTED_PM_ENABLED != 0,
589            // Available since ESP IDF V4.4.4+
590            #[cfg(any(
591                not(esp_idf_version_major = "4"),
592                all(
593                    esp_idf_version_major = "4",
594                    any(
595                        not(esp_idf_version_minor = "4"),
596                        all(
597                            not(esp_idf_version_patch = "0"),
598                            not(esp_idf_version_patch = "1"),
599                            not(esp_idf_version_patch = "2"),
600                            not(esp_idf_version_patch = "3")
601                        )
602                    )
603                )
604            ))]
605            espnow_max_encrypt_num: CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM as i32,
606            magic: WIFI_INIT_CONFIG_MAGIC as _,
607            // Available since ESP IDF V5.3.0
608            #[cfg(any(
609                all(not(esp_idf_version_major = "4"), not(esp_idf_version_major = "5")),
610                all(
611                    esp_idf_version_major = "5",
612                    not(esp_idf_version = "5.0"),
613                    not(esp_idf_version = "5.1"),
614                    not(esp_idf_version = "5.2"),
615                ),
616            ))]
617            tx_hetb_queue_num: WIFI_TX_HETB_QUEUE_NUM as _,
618            // Available since ESP IDF V5.3.0
619            #[cfg(any(
620                all(not(esp_idf_version_major = "4"), not(esp_idf_version_major = "5")),
621                all(
622                    esp_idf_version_major = "5",
623                    not(esp_idf_version = "5.0"),
624                    not(esp_idf_version = "5.1"),
625                    not(esp_idf_version = "5.2"),
626                ),
627            ))]
628            dump_hesigb_enable: WIFI_DUMP_HESIGB_ENABLED != 0,
629            ..Default::default()
630        };
631        esp!(unsafe { esp_wifi_init(&cfg) })?;
632
633        ::log::debug!("Driver initialized");
634
635        Ok(())
636    }
637
638    /// Returns the set of [`Capabilities`] for this driver. In `esp-idf`, all
639    /// drivers always have Client, AP and Mixed capabilities.
640    pub fn get_capabilities(&self) -> Result<EnumSet<Capability>, EspError> {
641        let caps = Capability::Client | Capability::AccessPoint | Capability::Mixed;
642
643        ::log::debug!("Providing capabilities: {caps:?}");
644
645        Ok(caps)
646    }
647
648    /// As per [`crate::sys::esp_wifi_start`](crate::sys::esp_wifi_start)
649    pub fn start(&mut self) -> Result<(), EspError> {
650        ::log::debug!("Start requested");
651
652        esp!(unsafe { esp_wifi_start() })?;
653
654        ::log::debug!("Starting");
655
656        Ok(())
657    }
658
659    /// As per [`crate::sys::esp_wifi_stop`](crate::sys::esp_wifi_stop)
660    pub fn stop(&mut self) -> Result<(), EspError> {
661        ::log::debug!("Stop requested");
662
663        esp!(unsafe { esp_wifi_stop() })?;
664
665        ::log::debug!("Stopping");
666
667        Ok(())
668    }
669
670    /// As per [`crate::sys::esp_wifi_connect`](crate::sys::esp_wifi_connect)
671    pub fn connect(&mut self) -> Result<(), EspError> {
672        ::log::debug!("Connect requested");
673
674        esp!(unsafe { esp_wifi_connect() })?;
675
676        ::log::debug!("Connecting");
677
678        Ok(())
679    }
680
681    /// As per [`crate::sys::esp_wifi_disconnect`](crate::sys::esp_wifi_disconnect)
682    pub fn disconnect(&mut self) -> Result<(), EspError> {
683        ::log::debug!("Disconnect requested");
684
685        esp!(unsafe { esp_wifi_disconnect() })?;
686
687        ::log::debug!("Disconnecting");
688
689        Ok(())
690    }
691
692    /// Returns `true` if the driver is in Access Point (AP) mode, as reported by
693    /// [`crate::sys::esp_wifi_get_mode`](crate::sys::esp_wifi_get_mode)
694    pub fn is_ap_enabled(&self) -> Result<bool, EspError> {
695        let mut mode: wifi_mode_t = 0;
696        esp!(unsafe { esp_wifi_get_mode(&mut mode) })?;
697
698        Ok(mode == wifi_mode_t_WIFI_MODE_AP || mode == wifi_mode_t_WIFI_MODE_APSTA)
699    }
700
701    /// Returns `true` if the driver is in Client (station or STA) mode, as
702    /// reported by [`crate::sys::esp_wifi_get_mode`](crate::sys::esp_wifi_get_mode)
703    pub fn is_sta_enabled(&self) -> Result<bool, EspError> {
704        let mut mode: wifi_mode_t = 0;
705        esp!(unsafe { esp_wifi_get_mode(&mut mode) })?;
706
707        Ok(mode == wifi_mode_t_WIFI_MODE_STA || mode == wifi_mode_t_WIFI_MODE_APSTA)
708    }
709
710    pub fn is_ap_started(&self) -> Result<bool, EspError> {
711        Ok(matches!(self.status.lock().ap, WifiApStatus::Started))
712    }
713
714    pub fn is_sta_started(&self) -> Result<bool, EspError> {
715        let guard = self.status.lock();
716
717        Ok(matches!(
718            guard.sta,
719            WifiStaStatus::Started | WifiStaStatus::Connected
720        ))
721    }
722
723    pub fn is_sta_connected(&self) -> Result<bool, EspError> {
724        Ok(matches!(self.status.lock().sta, WifiStaStatus::Connected))
725    }
726
727    pub fn is_started(&self) -> Result<bool, EspError> {
728        let ap_enabled = self.is_ap_enabled()?;
729        let sta_enabled = self.is_sta_enabled()?;
730
731        if !ap_enabled && !sta_enabled {
732            Ok(false)
733        } else {
734            Ok(
735                (!ap_enabled || self.is_ap_started()?)
736                    && (!sta_enabled || self.is_sta_started()?),
737            )
738        }
739    }
740
741    pub fn is_connected(&self) -> Result<bool, EspError> {
742        let ap_enabled = self.is_ap_enabled()?;
743        let sta_enabled = self.is_sta_enabled()?;
744
745        if !ap_enabled && !sta_enabled {
746            Ok(false)
747        } else {
748            let guard = self.status.lock();
749
750            Ok((!ap_enabled || matches!(guard.ap, WifiApStatus::Started))
751                && (!sta_enabled || matches!(guard.sta, WifiStaStatus::Connected)))
752        }
753    }
754
755    pub fn is_scan_done(&self) -> Result<bool, EspError> {
756        let guard = self.status.lock();
757
758        Ok(matches!(guard.scan, WifiScanStatus::Done))
759    }
760
761    #[allow(non_upper_case_globals)]
762    #[allow(non_snake_case)]
763    /// Returns the <`Configuration`> currently in use
764    pub fn get_configuration(&self) -> Result<Configuration, EspError> {
765        ::log::debug!("Getting configuration");
766
767        let mut mode: wifi_mode_t = 0;
768        esp!(unsafe { esp_wifi_get_mode(&mut mode) })?;
769
770        let conf = match mode {
771            wifi_mode_t_WIFI_MODE_NULL => Configuration::None,
772            wifi_mode_t_WIFI_MODE_AP => Configuration::AccessPoint(self.get_ap_conf()?),
773            wifi_mode_t_WIFI_MODE_STA => Configuration::Client(self.get_sta_conf()?),
774            wifi_mode_t_WIFI_MODE_APSTA => {
775                Configuration::Mixed(self.get_sta_conf()?, self.get_ap_conf()?)
776            }
777            _ => panic!(),
778        };
779
780        ::log::debug!("Configuration gotten: {:?}", conf);
781
782        Ok(conf)
783    }
784
785    /// Sets the <`Configuration`> (SSID, channel, etc). This also defines whether
786    /// the driver will work in AP mode, client mode, client+AP mode, or none.
787    ///
788    /// Calls [`crate::sys::esp_wifi_set_mode`](crate::sys::esp_wifi_set_mode)
789    /// and [`crate::sys::esp_wifi_set_config`](crate::sys::esp_wifi_set_config)
790    pub fn set_configuration(&mut self, conf: &Configuration) -> Result<(), EspError> {
791        ::log::debug!("Setting configuration: {conf:?}");
792
793        match conf {
794            Configuration::None => {
795                unsafe {
796                    esp!(esp_wifi_set_mode(wifi_mode_t_WIFI_MODE_NULL))?;
797                }
798                ::log::debug!("Wifi mode NULL set");
799            }
800            Configuration::AccessPoint(ap_conf) => {
801                unsafe {
802                    esp!(esp_wifi_set_mode(wifi_mode_t_WIFI_MODE_AP))?;
803                }
804                ::log::debug!("Wifi mode AP set");
805
806                self.set_ap_conf(ap_conf)?;
807            }
808            Configuration::Client(client_conf) => {
809                unsafe {
810                    esp!(esp_wifi_set_mode(wifi_mode_t_WIFI_MODE_STA))?;
811                }
812                ::log::debug!("Wifi mode STA set");
813
814                self.set_sta_conf(client_conf)?;
815            }
816            Configuration::Mixed(client_conf, ap_conf) => {
817                unsafe {
818                    esp!(esp_wifi_set_mode(wifi_mode_t_WIFI_MODE_APSTA))?;
819                }
820                ::log::debug!("Wifi mode APSTA set");
821
822                self.set_sta_conf(client_conf)?;
823                self.set_ap_conf(ap_conf)?;
824            }
825        }
826
827        ::log::debug!("Configuration set");
828
829        Ok(())
830    }
831
832    /// Scan for nearby, visible access points.
833    ///
834    /// It scans for all available access points nearby, but returns only the first `N` access points found.
835    /// In addition, it returns the actual amount it found. The function blocks until the scan is done.
836    ///
837    /// Before calling this function the Wifi driver must be configured and started in either Client or Mixed mode.
838    ///
839    /// # Example
840    /// ```ignore
841    /// let mut wifi_driver = WifiDriver::new(peripherals.modem, sysloop.clone());
842    /// wifi_driver.set_configuration(
843    ///     &Configuration::Client(ClientConfiguration::default())
844    /// )
845    /// .unwrap();
846    /// wifi_driver.start().unwrap();
847    ///
848    /// let (scan_result, found_aps) = wifi_driver.scan_n::<10>().unwrap();
849    /// ```
850    // For backwards compatibility
851    pub fn scan_n<const N: usize>(
852        &mut self,
853    ) -> Result<(heapless::Vec<AccessPointInfo, N>, usize), EspError> {
854        self.start_scan(&Default::default(), true)?;
855        self.get_scan_result_n()
856    }
857
858    /// Scan for nearby, visible access points.
859    ///
860    /// Unlike [`WifiDriver::scan_n()`], it returns all found access points by allocating memory
861    /// dynamically.
862    ///
863    /// For more details see [`WifiDriver::scan_n()`].
864    // For backwards compatibility
865    #[cfg(feature = "alloc")]
866    pub fn scan(&mut self) -> Result<alloc::vec::Vec<AccessPointInfo>, EspError> {
867        self.start_scan(&Default::default(), true)?;
868        self.get_scan_result()
869    }
870
871    /// Start scanning for nearby, visible access points.
872    ///
873    /// Unlike [`WifiDriver::scan_n()`] or [`WifiDriver::scan()`] it can be called as either blocking or not blocking.
874    /// A [`ScanConfig`] can be provided as well. To get the scan result call either [`WifiDriver::get_scan_result_n()`] or
875    /// [`WifiDriver::get_scan_result()`].
876    ///
877    /// This function can be used in `async` context, when the current thread shouldn't be blocked.
878    ///
879    /// # Example
880    ///
881    /// This example shows how to use it in a `async` context.
882    ///
883    /// ```ignore
884    /// let mut wifi_driver = WifiDriver::new(peripherals.modem, sysloop.clone());
885    /// wifi_driver.set_configuration(
886    ///     &Configuration::Client(ClientConfiguration::default())
887    /// )
888    /// .unwrap();
889    /// wifi_driver.start().unwrap();
890    ///
891    /// let scan_finish_signal = Arc::new(channel_bridge::notification::Notification::new());
892    /// let _sub = {
893    ///     let scan_finish_signal = scan_finish_signal.clone();
894    ///     sysloop.subscribe::<WifiEvent>(move |event| {
895    ///         if *event == WifiEvent::ScanDone {
896    ///             scan_finish_signal.notify();
897    ///         }
898    ///     }).unwrap()
899    /// };
900    ///
901    /// wifi_driver.start_scan(&ScanConfig::default(), false).unwrap();
902    ///
903    /// scan_finish_signal.wait().await;
904    ///
905    /// let res = wifi_driver.get_scan_result().unwrap();
906    /// ```
907    pub fn start_scan(
908        &mut self,
909        scan_config: &config::ScanConfig,
910        blocking: bool,
911    ) -> Result<(), EspError> {
912        ::log::debug!("About to scan for access points");
913
914        let scan_config: wifi_scan_config_t = scan_config.into();
915        esp!(unsafe { esp_wifi_scan_start(&scan_config as *const wifi_scan_config_t, blocking) })?;
916
917        self.status.lock().scan = WifiScanStatus::Started;
918
919        Ok(())
920    }
921
922    /// Stops a previous started access point scan.
923    pub fn stop_scan(&mut self) -> Result<(), EspError> {
924        ::log::debug!("About to stop scan for access points");
925
926        esp!(unsafe { esp_wifi_scan_stop() })?;
927
928        self.status.lock().scan = WifiScanStatus::Idle;
929
930        Ok(())
931    }
932
933    /// Get the results of an access point scan.
934    ///
935    /// This call returns a list of the first `N` found access points. A scan can be started with [`WifiDriver::start_scan()`].
936    /// As [`WifiDriver::scan_n()`] it returns the actual amount of found access points as well.
937    pub fn get_scan_result_n<const N: usize>(
938        &mut self,
939    ) -> Result<(heapless::Vec<AccessPointInfo, N>, usize), EspError> {
940        let scanned_count = self.get_scan_count()?;
941
942        let mut ap_infos_raw: heapless::Vec<wifi_ap_record_t, N> = heapless::Vec::new();
943        unsafe {
944            ap_infos_raw.set_len(scanned_count.min(N));
945        }
946
947        let fetched_count = self.fetch_scan_result(&mut ap_infos_raw)?;
948
949        let result = ap_infos_raw[..fetched_count]
950            .iter()
951            .map::<Result<AccessPointInfo, Utf8Error>, _>(|ap_info_raw| {
952                Newtype(ap_info_raw).try_into()
953            })
954            .filter_map(|r| r.ok())
955            .inspect(|ap_info| ::log::debug!("Found access point {ap_info:?}"))
956            .collect();
957
958        Ok((result, scanned_count))
959    }
960
961    /// Get the results of an access point scan.
962    ///
963    /// Unlike [`WifiDriver::get_scan_result_n()`], it returns all found access points by allocating memory
964    /// dynamically.
965    ///
966    /// For more details see [`WifiDriver::get_scan_result_n()`].
967    #[cfg(feature = "alloc")]
968    pub fn get_scan_result(&mut self) -> Result<alloc::vec::Vec<AccessPointInfo>, EspError> {
969        let scanned_count = self.get_scan_count()?;
970
971        let mut ap_infos_raw: alloc::vec::Vec<wifi_ap_record_t> =
972            alloc::vec::Vec::with_capacity(scanned_count);
973        #[allow(clippy::uninit_vec)]
974        // ... because we are filling it in on the next line and only reading the initialized members
975        unsafe {
976            ap_infos_raw.set_len(scanned_count)
977        };
978
979        let fetched_count = self.fetch_scan_result(&mut ap_infos_raw)?;
980
981        let result = ap_infos_raw[..fetched_count]
982            .iter()
983            .map::<Result<AccessPointInfo, Utf8Error>, _>(|ap_info_raw| {
984                Newtype(ap_info_raw).try_into()
985            })
986            .filter_map(|r| r.ok())
987            .inspect(|ap_info| ::log::debug!("Found access point {ap_info:?}"))
988            .collect();
989
990        Ok(result)
991    }
992
993    /// Sets callback functions for receiving and sending data, as per
994    /// [`crate::sys::esp_wifi_internal_reg_rxcb`](crate::sys::esp_wifi_internal_reg_rxcb) and
995    /// [`crate::sys::esp_wifi_set_tx_done_cb`](crate::sys::esp_wifi_set_tx_done_cb)
996    pub fn set_callbacks<R, T>(&mut self, rx_callback: R, tx_callback: T) -> Result<(), EspError>
997    where
998        R: FnMut(WifiDeviceId, WifiFrame) -> Result<(), EspError> + Send + 'static,
999        T: FnMut(WifiDeviceId, &[u8], bool) + Send + 'static,
1000    {
1001        self.internal_set_callbacks(rx_callback, tx_callback)
1002    }
1003
1004    /// Sets callback functions for receiving and sending data, as per
1005    /// [`crate::sys::esp_wifi_internal_reg_rxcb`](crate::sys::esp_wifi_internal_reg_rxcb) and
1006    /// [`crate::sys::esp_wifi_set_tx_done_cb`](crate::sys::esp_wifi_set_tx_done_cb)
1007    ///
1008    /// # Safety
1009    ///
1010    /// This method - in contrast to method `set_callbacks` - allows the user to pass
1011    /// non-static callbacks/closures. This enables users to borrow
1012    /// - in the closure - variables that live on the stack - or more generally - in the same
1013    ///   scope where the service is created.
1014    ///
1015    /// HOWEVER: care should be taken NOT to call `core::mem::forget()` on the service,
1016    /// as that would immediately lead to an UB (crash).
1017    /// Also note that forgetting the service might happen with `Rc` and `Arc`
1018    /// when circular references are introduced: <https://github.com/rust-lang/rust/issues/24456>
1019    ///
1020    /// The reason is that the closure is actually sent to a hidden ESP IDF thread.
1021    /// This means that if the service is forgotten, Rust is free to e.g. unwind the stack
1022    /// and the closure now owned by this other thread will end up with references to variables that no longer exist.
1023    ///
1024    /// The destructor of the service takes care - prior to the service being dropped and e.g.
1025    /// the stack being unwind - to remove the closure from the hidden thread and destroy it.
1026    /// Unfortunately, when the service is forgotten, the un-subscription does not happen
1027    /// and invalid references are left dangling.
1028    ///
1029    /// This "local borrowing" will only be possible to express in a safe way once/if `!Leak` types
1030    /// are introduced to Rust (i.e. the impossibility to "forget" a type and thus not call its destructor).
1031    pub unsafe fn set_nonstatic_callbacks<R, T>(
1032        &mut self,
1033        rx_callback: R,
1034        tx_callback: T,
1035    ) -> Result<(), EspError>
1036    where
1037        R: FnMut(WifiDeviceId, WifiFrame) -> Result<(), EspError> + Send + 'd,
1038        T: FnMut(WifiDeviceId, &[u8], bool) + Send + 'd,
1039    {
1040        self.internal_set_callbacks(rx_callback, tx_callback)
1041    }
1042
1043    fn internal_set_callbacks<R, T>(
1044        &mut self,
1045        mut rx_callback: R,
1046        mut tx_callback: T,
1047    ) -> Result<(), EspError>
1048    where
1049        R: FnMut(WifiDeviceId, WifiFrame) -> Result<(), EspError> + Send + 'd,
1050        T: FnMut(WifiDeviceId, &[u8], bool) + Send + 'd,
1051    {
1052        let _ = self.disconnect();
1053        let _ = self.stop();
1054
1055        #[allow(clippy::type_complexity)]
1056        let rx_callback: Box<
1057            Box<dyn FnMut(WifiDeviceId, WifiFrame) -> Result<(), EspError> + Send + 'd>,
1058        > = Box::new(Box::new(move |device_id, data| {
1059            rx_callback(device_id, data)
1060        }));
1061
1062        #[allow(clippy::type_complexity)]
1063        let tx_callback: Box<Box<dyn FnMut(WifiDeviceId, &[u8], bool) + Send + 'd>> =
1064            Box::new(Box::new(move |device_id, data, status| {
1065                tx_callback(device_id, data, status)
1066            }));
1067
1068        #[allow(clippy::type_complexity)]
1069        let rx_callback: Box<
1070            Box<dyn FnMut(WifiDeviceId, WifiFrame) -> Result<(), EspError> + Send + 'static>,
1071        > = unsafe { core::mem::transmute(rx_callback) };
1072
1073        #[allow(clippy::type_complexity)]
1074        let tx_callback: Box<Box<dyn FnMut(WifiDeviceId, &[u8], bool) + Send + 'static>> =
1075            unsafe { core::mem::transmute(tx_callback) };
1076
1077        unsafe {
1078            RX_CALLBACK = Some(rx_callback);
1079            TX_CALLBACK = Some(tx_callback);
1080
1081            esp!(esp_wifi_internal_reg_rxcb(
1082                WifiDeviceId::Ap.into(),
1083                Some(Self::handle_rx_ap),
1084            ))?;
1085
1086            esp!(esp_wifi_internal_reg_rxcb(
1087                WifiDeviceId::Sta.into(),
1088                Some(Self::handle_rx_sta),
1089            ))?;
1090
1091            esp!(esp_wifi_set_tx_done_cb(Some(Self::handle_tx)))?;
1092        }
1093
1094        Ok(())
1095    }
1096
1097    /// As per [`crate::sys::esp_wifi_internal_tx`](crate::sys::esp_wifi_internal_tx)
1098    pub fn send(&mut self, device_id: WifiDeviceId, frame: &[u8]) -> Result<(), EspError> {
1099        esp!(unsafe {
1100            esp_wifi_internal_tx(device_id.into(), frame.as_ptr() as *mut _, frame.len() as _)
1101        })
1102    }
1103
1104    /// Get information about the AP with which the station is associated.
1105    /// Useful to get the current signal strength of the AP.
1106    pub fn get_ap_info(&self) -> Result<AccessPointInfo, EspError> {
1107        let mut ap_info_raw: wifi_ap_record_t = wifi_ap_record_t::default();
1108        // If STA is not connected EspError(12303) is returned
1109        esp!(unsafe { esp_wifi_sta_get_ap_info(&mut ap_info_raw) })?;
1110        let ap_info: AccessPointInfo = Newtype(&ap_info_raw).try_into().unwrap();
1111
1112        ::log::debug!("AP Info: {ap_info:?}");
1113        Ok(ap_info)
1114    }
1115
1116    /// Set RSSI threshold below which APP will get an WifiEvent::StaBssRssiLow,
1117    /// as per [`crate::sys::esp_wifi_set_rssi_threshold`](crate::sys::esp_wifi_set_rssi_threshold)
1118    /// `rssi_threshold`: threshold value in dbm between -100 to 0
1119    ///
1120    /// # Example
1121    ///
1122    /// This example shows how to use it in a `async` context.
1123    ///
1124    /// ```ignore
1125    /// let mut wifi_driver = WifiDriver::new(peripherals.modem, sysloop.clone());
1126    /// wifi_driver.set_configuration(
1127    ///     &Configuration::Client(ClientConfiguration::default())
1128    /// )
1129    /// .unwrap();
1130    /// wifi_driver.start().unwrap();
1131    /// wifi_driver.connect().unwrap();
1132    ///
1133    /// let rssi_low = -40;
1134    /// wifi_driver.driver_mut().set_sta_rssi_low(rssi_low).unwrap();
1135    ///
1136    /// // Subscribe to RSSI events.
1137    /// let rssi_low_signal = Arc::new(channel_bridge::notification::Notification::new());
1138    /// let _sub = {
1139    ///     let rssi_low_signal = rssi_low_signal.clone();
1140    ///     sysloop.subscribe::<WifiEvent>(move |event| {
1141    ///         if *event == WifiEvent::StaBssRssiLow {
1142    ///             rssi_low_signal.notify();
1143    ///         }
1144    ///     }).unwrap()
1145    /// };
1146    ///
1147    /// rssi_low_signal.wait().await;
1148    /// // do stuff with the information
1149    ///
1150    /// // set_rssi_threshold() has to be called again after every StaBssRssiLow event received.
1151    /// ```
1152    pub fn set_rssi_threshold(&mut self, rssi_threshold: i8) -> Result<(), EspError> {
1153        esp!(unsafe { esp_wifi_set_rssi_threshold(rssi_threshold.into()) })
1154    }
1155
1156    /// Returns the MAC address of the interface, as per
1157    /// [`crate::sys::esp_wifi_get_mac`](crate::sys::esp_wifi_get_mac)
1158    pub fn get_mac(&self, interface: WifiDeviceId) -> Result<[u8; 6], EspError> {
1159        let mut mac = [0u8; 6];
1160
1161        esp!(unsafe { esp_wifi_get_mac(interface.into(), mac.as_mut_ptr() as *mut _) })?;
1162
1163        Ok(mac)
1164    }
1165
1166    /// Seta the MAC address of the interface, as per
1167    /// [`crate::sys::esp_wifi_set_mac`](crate::sys::esp_wifi_set_mac)
1168    pub fn set_mac(&mut self, interface: WifiDeviceId, mac: [u8; 6]) -> Result<(), EspError> {
1169        esp!(unsafe { esp_wifi_set_mac(interface.into(), mac.as_ptr() as *mut _) })
1170    }
1171
1172    /// Enable and start WPS
1173    pub fn start_wps(&mut self, config: &WpsConfig) -> Result<(), EspError> {
1174        let config = Newtype::<esp_wps_config_t>::try_from(config)?;
1175
1176        match self.get_configuration()? {
1177            Configuration::None => esp!(unsafe { esp_wifi_set_mode(wifi_mode_t_WIFI_MODE_STA) })?,
1178            Configuration::AccessPoint(_) => {
1179                esp!(unsafe { esp_wifi_set_mode(wifi_mode_t_WIFI_MODE_APSTA) })?
1180            }
1181            _ => (),
1182        }
1183
1184        esp!(unsafe { esp_wifi_wps_enable(&config.0 as *const _) })?;
1185        #[cfg(not(esp_idf_version_at_least_6_0_0))]
1186        esp!(unsafe { esp_wifi_wps_start(0) })?;
1187        #[cfg(esp_idf_version_at_least_6_0_0)]
1188        esp!(unsafe { esp_wifi_wps_start() })?;
1189
1190        self.status.lock().wps = None;
1191
1192        Ok(())
1193    }
1194
1195    /// Enables or disables promiscuous mode for the [`WifiDriver`].
1196    ///
1197    /// When promiscuous mode is enabled, the driver captures all Wifi frames
1198    /// on the network, regardless of their destination MAC address. This is useful for
1199    /// debugging or monitoring purposes.
1200    pub fn set_promiscuous(&mut self, state: bool) -> Result<(), EspError> {
1201        esp!(unsafe { esp_wifi_set_promiscuous(state) })?;
1202
1203        if state {
1204            ::log::info!("Driver set in promiscuous mode");
1205        } else {
1206            ::log::info!("Driver set in non-promiscuous mode");
1207        }
1208
1209        Ok(())
1210    }
1211
1212    /// Gets the state of the promiscuous mode for the [`WifiDriver`].
1213    pub fn is_promiscuous(&self) -> Result<bool, EspError> {
1214        let mut en: bool = false;
1215
1216        esp!(unsafe { esp_wifi_get_promiscuous(&mut en) })?;
1217
1218        Ok(en)
1219    }
1220
1221    //TODO: add safe wrappers for these three functions
1222    //https://docs.rs/esp-idf-sys/latest/esp_idf_sys/fn.esp_wifi_set_promiscuous_ctrl_filter.html
1223    //https://docs.rs/esp-idf-sys/latest/esp_idf_sys/fn.esp_wifi_set_promiscuous_filter.html
1224    //https://docs.rs/esp-idf-sys/latest/esp_idf_sys/fn.esp_wifi_set_promiscuous_rx_cb.html
1225
1226    /// Gets the WPS status as a [`WPS Event`] and disables WPS.
1227    fn stop_wps(&mut self) -> Result<WpsStatus, EspError> {
1228        let mut status = self.status.lock();
1229        if let Some(status) = status.wps.take() {
1230            esp!(unsafe { esp_wifi_wps_disable() })?;
1231            Ok(status)
1232        } else {
1233            Err(EspError::from_infallible::<ESP_ERR_INVALID_STATE>())
1234        }
1235    }
1236
1237    fn is_wps_finished(&self) -> Result<bool, EspError> {
1238        Ok(self.status.lock().wps.is_some())
1239    }
1240
1241    fn get_sta_conf(&self) -> Result<ClientConfiguration, EspError> {
1242        let mut wifi_config: wifi_config_t = Default::default();
1243        esp!(unsafe { esp_wifi_get_config(wifi_interface_t_WIFI_IF_STA, &mut wifi_config) })?;
1244
1245        let result: ClientConfiguration = unsafe { Newtype(wifi_config.sta).into() };
1246
1247        ::log::debug!("Providing STA configuration: {:?}", result);
1248
1249        Ok(result)
1250    }
1251
1252    fn set_sta_conf(&mut self, conf: &ClientConfiguration) -> Result<(), EspError> {
1253        ::log::debug!("Checking current STA configuration");
1254        let current_config = self.get_sta_conf()?;
1255
1256        if current_config != *conf {
1257            ::log::debug!("Setting STA configuration: {conf:?}");
1258
1259            let mut wifi_config = wifi_config_t {
1260                sta: Newtype::<wifi_sta_config_t>::try_from(conf)?.0,
1261            };
1262
1263            esp!(unsafe { esp_wifi_set_config(wifi_interface_t_WIFI_IF_STA, &mut wifi_config) })?;
1264        } else {
1265            ::log::debug!("Same STA configuration already present");
1266        }
1267
1268        ::log::debug!("STA configuration done");
1269
1270        Ok(())
1271    }
1272
1273    fn get_ap_conf(&self) -> Result<AccessPointConfiguration, EspError> {
1274        let mut wifi_config: wifi_config_t = Default::default();
1275        esp!(unsafe { esp_wifi_get_config(wifi_interface_t_WIFI_IF_AP, &mut wifi_config) })?;
1276
1277        let result: AccessPointConfiguration = unsafe { Newtype(wifi_config.ap).into() };
1278
1279        ::log::debug!("Providing AP configuration: {:?}", result);
1280
1281        Ok(result)
1282    }
1283
1284    fn set_ap_conf(&mut self, conf: &AccessPointConfiguration) -> Result<(), EspError> {
1285        ::log::debug!("Checking current AP configuration");
1286        let current_config = self.get_ap_conf()?;
1287
1288        if current_config != *conf {
1289            ::log::debug!("Setting AP configuration: {conf:?}");
1290
1291            let mut wifi_config = wifi_config_t {
1292                ap: Newtype::<wifi_ap_config_t>::try_from(conf)?.0,
1293            };
1294
1295            esp!(unsafe { esp_wifi_set_config(wifi_interface_t_WIFI_IF_AP, &mut wifi_config) })?;
1296        } else {
1297            ::log::debug!("Same AP configuration already present");
1298        }
1299
1300        ::log::debug!("AP configuration done");
1301
1302        Ok(())
1303    }
1304
1305    fn clear_all(&mut self) -> Result<(), EspError> {
1306        let _ = self.disconnect();
1307        let _ = self.stop();
1308
1309        unsafe {
1310            esp!(esp_wifi_deinit())?;
1311        }
1312
1313        unsafe {
1314            // Callbacks are already deregistered by `esp_wifi_deinit`, just null-ify our own refs
1315            RX_CALLBACK = None;
1316            TX_CALLBACK = None;
1317        }
1318
1319        ::log::debug!("Driver deinitialized");
1320
1321        Ok(())
1322    }
1323
1324    fn get_scan_count(&mut self) -> Result<usize, EspError> {
1325        let mut found_ap: u16 = 0;
1326        esp!(unsafe { esp_wifi_scan_get_ap_num(&mut found_ap as *mut _) })?;
1327
1328        ::log::debug!("Found {found_ap} access points");
1329
1330        Ok(found_ap as usize)
1331    }
1332
1333    fn fetch_scan_result(
1334        &mut self,
1335        ap_infos_raw: &mut [wifi_ap_record_t],
1336    ) -> Result<usize, EspError> {
1337        ::log::debug!("About to get info for found access points");
1338
1339        let mut ap_count: u16 = ap_infos_raw.len() as u16;
1340
1341        esp!(unsafe { esp_wifi_scan_get_ap_records(&mut ap_count, ap_infos_raw.as_mut_ptr(),) })?;
1342
1343        ::log::debug!("Got info for {ap_count} access points");
1344
1345        Ok(ap_count as usize)
1346    }
1347
1348    unsafe extern "C" fn handle_rx_ap(
1349        buf: *mut ffi::c_void,
1350        len: u16,
1351        eb: *mut ffi::c_void,
1352    ) -> esp_err_t {
1353        Self::handle_rx(WifiDeviceId::Ap, buf, len, eb)
1354    }
1355
1356    unsafe extern "C" fn handle_rx_sta(
1357        buf: *mut ffi::c_void,
1358        len: u16,
1359        eb: *mut ffi::c_void,
1360    ) -> esp_err_t {
1361        Self::handle_rx(WifiDeviceId::Sta, buf, len, eb)
1362    }
1363
1364    unsafe fn handle_rx(
1365        device_id: WifiDeviceId,
1366        buf: *mut ffi::c_void,
1367        len: u16,
1368        eb: *mut ffi::c_void,
1369    ) -> esp_err_t {
1370        #[allow(static_mut_refs)]
1371        let res = RX_CALLBACK.as_mut().unwrap()(device_id, WifiFrame::new(buf.cast(), len, eb));
1372
1373        match res {
1374            Ok(_) => ESP_OK,
1375            Err(e) => e.code(),
1376        }
1377    }
1378
1379    unsafe extern "C" fn handle_tx(ifidx: u8, data: *mut u8, len: *mut u16, tx_status: bool) {
1380        #[allow(static_mut_refs)]
1381        TX_CALLBACK.as_mut().unwrap()(
1382            (ifidx as wifi_interface_t).into(),
1383            core::slice::from_raw_parts(data as *const _, len as usize),
1384            tx_status,
1385        );
1386    }
1387
1388    pub fn get_rssi(&self) -> Result<i32, EspError> {
1389        let mut rssi: core::ffi::c_int = 0;
1390        unsafe {
1391            esp_wifi_sta_get_rssi(&mut rssi as *mut core::ffi::c_int);
1392        };
1393        Ok(rssi as i32)
1394    }
1395}
1396
1397unsafe impl Send for WifiDriver<'_> {}
1398
1399impl NonBlocking for WifiDriver<'_> {
1400    fn is_scan_done(&self) -> Result<bool, EspError> {
1401        WifiDriver::is_scan_done(self)
1402    }
1403
1404    fn start_scan(
1405        &mut self,
1406        scan_config: &config::ScanConfig,
1407        blocking: bool,
1408    ) -> Result<(), EspError> {
1409        WifiDriver::start_scan(self, scan_config, blocking)
1410    }
1411
1412    fn stop_scan(&mut self) -> Result<(), EspError> {
1413        WifiDriver::stop_scan(self)
1414    }
1415
1416    fn get_scan_result_n<const N: usize>(
1417        &mut self,
1418    ) -> Result<(heapless::Vec<AccessPointInfo, N>, usize), EspError> {
1419        WifiDriver::get_scan_result_n(self)
1420    }
1421
1422    #[cfg(feature = "alloc")]
1423    fn get_scan_result(&mut self) -> Result<alloc::vec::Vec<AccessPointInfo>, EspError> {
1424        WifiDriver::get_scan_result(self)
1425    }
1426
1427    fn start_wps(&mut self, config: &WpsConfig) -> Result<(), EspError> {
1428        WifiDriver::start_wps(self, config)
1429    }
1430
1431    fn stop_wps(&mut self) -> Result<WpsStatus, EspError> {
1432        WifiDriver::stop_wps(self)
1433    }
1434
1435    fn is_wps_finished(&self) -> Result<bool, EspError> {
1436        WifiDriver::is_wps_finished(self)
1437    }
1438}
1439
1440impl Drop for WifiDriver<'_> {
1441    fn drop(&mut self) {
1442        self.clear_all().unwrap();
1443
1444        ::log::debug!("WifiDriver Dropped");
1445    }
1446}
1447
1448impl Wifi for WifiDriver<'_> {
1449    type Error = EspError;
1450
1451    fn get_capabilities(&self) -> Result<EnumSet<Capability>, Self::Error> {
1452        WifiDriver::get_capabilities(self)
1453    }
1454
1455    fn is_started(&self) -> Result<bool, Self::Error> {
1456        WifiDriver::is_started(self)
1457    }
1458
1459    fn is_connected(&self) -> Result<bool, Self::Error> {
1460        WifiDriver::is_connected(self)
1461    }
1462
1463    fn get_configuration(&self) -> Result<Configuration, Self::Error> {
1464        WifiDriver::get_configuration(self)
1465    }
1466
1467    fn set_configuration(&mut self, conf: &Configuration) -> Result<(), Self::Error> {
1468        WifiDriver::set_configuration(self, conf)
1469    }
1470
1471    fn start(&mut self) -> Result<(), Self::Error> {
1472        WifiDriver::start(self)
1473    }
1474
1475    fn stop(&mut self) -> Result<(), Self::Error> {
1476        WifiDriver::stop(self)
1477    }
1478
1479    fn connect(&mut self) -> Result<(), Self::Error> {
1480        WifiDriver::connect(self)
1481    }
1482
1483    fn disconnect(&mut self) -> Result<(), Self::Error> {
1484        WifiDriver::disconnect(self)
1485    }
1486
1487    fn scan_n<const N: usize>(
1488        &mut self,
1489    ) -> Result<(heapless::Vec<AccessPointInfo, N>, usize), Self::Error> {
1490        WifiDriver::scan_n(self)
1491    }
1492
1493    #[cfg(feature = "alloc")]
1494    fn scan(&mut self) -> Result<alloc::vec::Vec<AccessPointInfo>, Self::Error> {
1495        WifiDriver::scan(self)
1496    }
1497}
1498
1499pub struct WifiFrame {
1500    buf: *mut u8,
1501    len: u16,
1502    eb: *mut ffi::c_void,
1503}
1504
1505unsafe impl Send for WifiFrame {}
1506
1507impl WifiFrame {
1508    const unsafe fn new(buf: *mut u8, len: u16, eb: *mut ffi::c_void) -> Self {
1509        Self { buf, len, eb }
1510    }
1511
1512    pub const fn as_slice(&self) -> &[u8] {
1513        unsafe { core::slice::from_raw_parts(self.buf, self.len as _) }
1514    }
1515
1516    pub fn as_mut_slice(&mut self) -> &mut [u8] {
1517        unsafe { core::slice::from_raw_parts_mut(self.buf, self.len as _) }
1518    }
1519}
1520
1521impl ops::Deref for WifiFrame {
1522    type Target = [u8];
1523
1524    fn deref(&self) -> &[u8] {
1525        unsafe { core::slice::from_raw_parts(self.buf, self.len as _) }
1526    }
1527}
1528
1529impl ops::DerefMut for WifiFrame {
1530    fn deref_mut(&mut self) -> &mut [u8] {
1531        unsafe { core::slice::from_raw_parts_mut(self.buf, self.len as _) }
1532    }
1533}
1534
1535impl Drop for WifiFrame {
1536    fn drop(&mut self) {
1537        unsafe { esp_wifi_internal_free_rx_buffer(self.eb) };
1538    }
1539}
1540
1541/// `EspWifi` wraps a `WifiDriver` Data Link layer instance, and binds the OSI
1542/// Layer 3 (network) facilities of ESP IDF to it.
1543///
1544/// In other words, it connects the ESP IDF AP and STA Netif interfaces to the
1545/// Wifi driver. This allows users to utilize the Rust STD APIs for working with
1546/// TCP and UDP sockets.
1547///
1548/// This struct should be the default option for a Wifi driver in all use cases
1549/// but the niche one where bypassing the ESP IDF Netif and lwIP stacks is
1550/// desirable. E.g., using `smoltcp` or other custom IP stacks on top of the
1551/// ESP IDF Wifi radio.
1552#[cfg(esp_idf_comp_esp_netif_enabled)]
1553pub struct EspWifi<'d> {
1554    #[cfg(esp_idf_esp_wifi_softap_support)]
1555    ap_netif: EspNetif,
1556    sta_netif: EspNetif,
1557    driver: WifiDriver<'d>,
1558}
1559
1560#[cfg(esp_idf_comp_esp_netif_enabled)]
1561impl<'d> EspWifi<'d> {
1562    #[cfg(all(feature = "alloc", esp_idf_comp_nvs_flash_enabled))]
1563    pub fn new<M: WifiModemPeripheral + 'd>(
1564        modem: M,
1565        sysloop: EspSystemEventLoop,
1566        nvs: Option<EspDefaultNvsPartition>,
1567    ) -> Result<Self, EspError> {
1568        Self::wrap(WifiDriver::new(modem, sysloop, nvs)?)
1569    }
1570
1571    #[cfg(not(all(feature = "alloc", esp_idf_comp_nvs_flash_enabled)))]
1572    pub fn new<M: WifiModemPeripheral + 'd>(
1573        modem: M,
1574        sysloop: EspSystemEventLoop,
1575    ) -> Result<Self, EspError> {
1576        Self::wrap(WifiDriver::new(modem, sysloop)?)
1577    }
1578
1579    pub fn wrap(driver: WifiDriver<'d>) -> Result<Self, EspError> {
1580        Self::wrap_all(
1581            driver,
1582            EspNetif::new(NetifStack::Sta)?,
1583            #[cfg(esp_idf_esp_wifi_softap_support)]
1584            EspNetif::new(NetifStack::Ap)?,
1585        )
1586    }
1587
1588    pub fn wrap_all(
1589        driver: WifiDriver<'d>,
1590        sta_netif: EspNetif,
1591        #[cfg(esp_idf_esp_wifi_softap_support)] ap_netif: EspNetif,
1592    ) -> Result<Self, EspError> {
1593        let mut this = Self {
1594            driver,
1595            sta_netif,
1596            #[cfg(esp_idf_esp_wifi_softap_support)]
1597            ap_netif,
1598        };
1599
1600        this.attach_netif()?;
1601
1602        Ok(this)
1603    }
1604
1605    #[cfg(esp_idf_esp_wifi_softap_support)]
1606    /// Replaces the network interfaces with the given ones. Returns the old ones.
1607    pub fn swap_netif(
1608        &mut self,
1609        sta_netif: EspNetif,
1610        ap_netif: EspNetif,
1611    ) -> Result<(EspNetif, EspNetif), EspError> {
1612        self.detach_netif()?;
1613
1614        let old_sta = core::mem::replace(&mut self.sta_netif, sta_netif);
1615        let old_ap = core::mem::replace(&mut self.ap_netif, ap_netif);
1616
1617        self.attach_netif()?;
1618
1619        Ok((old_sta, old_ap))
1620    }
1621
1622    /// Replaces the STA network interface with the provided one and returns the
1623    /// existing network interface.
1624    pub fn swap_netif_sta(&mut self, sta_netif: EspNetif) -> Result<EspNetif, EspError> {
1625        self.detach_netif()?;
1626
1627        let old = core::mem::replace(&mut self.sta_netif, sta_netif);
1628
1629        self.attach_netif()?;
1630
1631        Ok(old)
1632    }
1633
1634    #[cfg(esp_idf_esp_wifi_softap_support)]
1635    /// Replaces the AP network interface with the provided one and returns the
1636    /// existing network interface.
1637    pub fn swap_netif_ap(&mut self, ap_netif: EspNetif) -> Result<EspNetif, EspError> {
1638        self.detach_netif()?;
1639
1640        let old = core::mem::replace(&mut self.ap_netif, ap_netif);
1641
1642        self.attach_netif()?;
1643
1644        Ok(old)
1645    }
1646
1647    /// Returns the underlying [`WifiDriver`]
1648    pub fn driver(&self) -> &WifiDriver<'d> {
1649        &self.driver
1650    }
1651
1652    /// Returns the underlying [`WifiDriver`], as mutable
1653    pub fn driver_mut(&mut self) -> &mut WifiDriver<'d> {
1654        &mut self.driver
1655    }
1656
1657    /// Returns the underlying [`EspNetif`] for client mode
1658    pub fn sta_netif(&self) -> &EspNetif {
1659        &self.sta_netif
1660    }
1661
1662    /// Returns the underlying [`EspNetif`] for client mode, as mutable
1663    pub fn sta_netif_mut(&mut self) -> &mut EspNetif {
1664        &mut self.sta_netif
1665    }
1666
1667    #[cfg(esp_idf_esp_wifi_softap_support)]
1668    /// Returns the underlying [`EspNetif`] for AP mode
1669    pub fn ap_netif(&self) -> &EspNetif {
1670        &self.ap_netif
1671    }
1672
1673    #[cfg(esp_idf_esp_wifi_softap_support)]
1674    /// Returns the underlying [`EspNetif`] for AP mode, as mutable
1675    pub fn ap_netif_mut(&mut self) -> &mut EspNetif {
1676        &mut self.ap_netif
1677    }
1678
1679    /// As per [`WifiDriver::get_capabilities()`]
1680    pub fn get_capabilities(&self) -> Result<EnumSet<Capability>, EspError> {
1681        self.driver().get_capabilities()
1682    }
1683
1684    /// As per [`WifiDriver::is_started()`]
1685    pub fn is_started(&self) -> Result<bool, EspError> {
1686        self.driver().is_started()
1687    }
1688
1689    /// As per [`WifiDriver::is_connected()`]
1690    pub fn is_connected(&self) -> Result<bool, EspError> {
1691        self.driver().is_connected()
1692    }
1693
1694    /// Returns `true` when the driver has a connection, it has enabled either
1695    /// client or AP mode, and either the client or AP network interface is up.
1696    pub fn is_up(&self) -> Result<bool, EspError> {
1697        if !self.driver().is_connected()? {
1698            Ok(false)
1699        } else {
1700            let sta_enabled = self.driver().is_sta_enabled()?;
1701            let ok = !sta_enabled || self.sta_netif().is_up()?;
1702
1703            #[cfg(esp_idf_esp_wifi_softap_support)]
1704            let ok = ok && {
1705                let ap_enabled = self.driver().is_ap_enabled()?;
1706                !ap_enabled || self.ap_netif().is_up()?
1707            };
1708            Ok(ok)
1709        }
1710    }
1711
1712    /// As per [`WifiDriver::get_configuration()`]
1713    pub fn get_configuration(&self) -> Result<Configuration, EspError> {
1714        self.driver().get_configuration()
1715    }
1716
1717    /// As per [`WifiDriver::set_configuration()`]
1718    pub fn set_configuration(&mut self, conf: &Configuration) -> Result<(), EspError> {
1719        self.driver_mut().set_configuration(conf)
1720    }
1721
1722    /// As per [`WifiDriver::start()`]
1723    pub fn start(&mut self) -> Result<(), EspError> {
1724        self.driver_mut().start()
1725    }
1726
1727    /// As per [`WifiDriver::stop()`]
1728    pub fn stop(&mut self) -> Result<(), EspError> {
1729        self.driver_mut().stop()
1730    }
1731
1732    /// As per [`WifiDriver::connect()`]
1733    pub fn connect(&mut self) -> Result<(), EspError> {
1734        self.driver_mut().connect()
1735    }
1736
1737    /// As per [`WifiDriver::disconnect()`]
1738    pub fn disconnect(&mut self) -> Result<(), EspError> {
1739        self.driver_mut().disconnect()
1740    }
1741
1742    /// As per [`WifiDriver::is_scan_done()`]
1743    pub fn is_scan_done(&self) -> Result<bool, EspError> {
1744        self.driver().is_scan_done()
1745    }
1746
1747    /// As per [`WifiDriver::scan_n()`]
1748    pub fn scan_n<const N: usize>(
1749        &mut self,
1750    ) -> Result<(heapless::Vec<AccessPointInfo, N>, usize), EspError> {
1751        self.driver_mut().scan_n()
1752    }
1753
1754    /// As per [`WifiDriver::scan()`]
1755    #[cfg(feature = "alloc")]
1756    pub fn scan(&mut self) -> Result<alloc::vec::Vec<AccessPointInfo>, EspError> {
1757        self.driver_mut().scan()
1758    }
1759
1760    /// As per [`WifiDriver::start_scan()`].
1761    pub fn start_scan(
1762        &mut self,
1763        scan_config: &config::ScanConfig,
1764        blocking: bool,
1765    ) -> Result<(), EspError> {
1766        self.driver_mut().start_scan(scan_config, blocking)
1767    }
1768
1769    /// As per [`WifiDriver::stop_scan()`].
1770    pub fn stop_scan(&mut self) -> Result<(), EspError> {
1771        self.driver_mut().stop_scan()
1772    }
1773
1774    /// As per [`WifiDriver::get_scan_result_n()`].
1775    pub fn get_scan_result_n<const N: usize>(
1776        &mut self,
1777    ) -> Result<(heapless::Vec<AccessPointInfo, N>, usize), EspError> {
1778        self.driver_mut().get_scan_result_n()
1779    }
1780
1781    /// As per [`WifiDriver::get_scan_result()`].
1782    #[cfg(feature = "alloc")]
1783    pub fn get_scan_result(&mut self) -> Result<alloc::vec::Vec<AccessPointInfo>, EspError> {
1784        self.driver_mut().get_scan_result()
1785    }
1786
1787    /// As per [`WifiDriver::start_wps()`]
1788    pub fn start_wps(&mut self, config: &WpsConfig) -> Result<(), EspError> {
1789        self.driver_mut().start_wps(config)
1790    }
1791
1792    pub fn stop_wps(&mut self) -> Result<WpsStatus, EspError> {
1793        self.driver_mut().stop_wps()
1794    }
1795
1796    pub fn is_wps_finished(&self) -> Result<bool, EspError> {
1797        self.driver().is_wps_finished()
1798    }
1799
1800    /// As per [`WifiDriver::get_mac()`].
1801    pub fn get_mac(&self, interface: WifiDeviceId) -> Result<[u8; 6], EspError> {
1802        self.driver().get_mac(interface)
1803    }
1804
1805    /// As per [`WifiDriver::set_mac()`].
1806    pub fn set_mac(&mut self, interface: WifiDeviceId, mac: [u8; 6]) -> Result<(), EspError> {
1807        self.driver_mut().set_mac(interface, mac)
1808    }
1809
1810    fn attach_netif(&mut self) -> Result<(), EspError> {
1811        let _ = self.driver.stop();
1812
1813        #[cfg(esp_idf_esp_wifi_softap_support)]
1814        {
1815            esp!(unsafe { esp_netif_attach_wifi_ap(self.ap_netif.handle()) })?;
1816            esp!(unsafe { esp_wifi_set_default_wifi_ap_handlers() })?;
1817        }
1818
1819        esp!(unsafe { esp_netif_attach_wifi_station(self.sta_netif.handle()) })?;
1820        esp!(unsafe { esp_wifi_set_default_wifi_sta_handlers() })?;
1821
1822        Ok(())
1823    }
1824
1825    fn detach_netif(&mut self) -> Result<(), EspError> {
1826        let _ = self.driver.stop();
1827
1828        #[cfg(esp_idf_esp_wifi_softap_support)]
1829        esp!(unsafe {
1830            esp_wifi_clear_default_wifi_driver_and_handlers(
1831                self.ap_netif.handle() as *mut ffi::c_void
1832            )
1833        })?;
1834
1835        esp!(unsafe {
1836            esp_wifi_clear_default_wifi_driver_and_handlers(
1837                self.sta_netif.handle() as *mut ffi::c_void
1838            )
1839        })?;
1840
1841        Ok(())
1842    }
1843
1844    pub fn get_rssi(&self) -> Result<i32, EspError> {
1845        self.driver().get_rssi()
1846    }
1847
1848    /// Get information about the AP with which the station is associated.
1849    /// Useful to get the current signal strength of the AP.
1850    pub fn get_ap_info(&self) -> Result<AccessPointInfo, EspError> {
1851        self.driver().get_ap_info()
1852    }
1853}
1854
1855#[cfg(esp_idf_comp_esp_netif_enabled)]
1856impl Drop for EspWifi<'_> {
1857    fn drop(&mut self) {
1858        self.detach_netif().unwrap();
1859
1860        ::log::info!("EspWifi dropped");
1861    }
1862}
1863
1864#[cfg(esp_idf_comp_esp_netif_enabled)]
1865unsafe impl Send for EspWifi<'_> {}
1866
1867#[cfg(esp_idf_comp_esp_netif_enabled)]
1868impl NonBlocking for EspWifi<'_> {
1869    fn is_scan_done(&self) -> Result<bool, EspError> {
1870        EspWifi::is_scan_done(self)
1871    }
1872
1873    fn start_scan(
1874        &mut self,
1875        scan_config: &config::ScanConfig,
1876        blocking: bool,
1877    ) -> Result<(), EspError> {
1878        EspWifi::start_scan(self, scan_config, blocking)
1879    }
1880
1881    fn stop_scan(&mut self) -> Result<(), EspError> {
1882        EspWifi::stop_scan(self)
1883    }
1884
1885    fn get_scan_result_n<const N: usize>(
1886        &mut self,
1887    ) -> Result<(heapless::Vec<AccessPointInfo, N>, usize), EspError> {
1888        EspWifi::get_scan_result_n(self)
1889    }
1890
1891    #[cfg(feature = "alloc")]
1892    fn get_scan_result(&mut self) -> Result<alloc::vec::Vec<AccessPointInfo>, EspError> {
1893        EspWifi::get_scan_result(self)
1894    }
1895
1896    fn start_wps(&mut self, config: &WpsConfig) -> Result<(), EspError> {
1897        EspWifi::start_wps(self, config)
1898    }
1899
1900    fn stop_wps(&mut self) -> Result<WpsStatus, EspError> {
1901        EspWifi::stop_wps(self)
1902    }
1903
1904    fn is_wps_finished(&self) -> Result<bool, EspError> {
1905        EspWifi::is_wps_finished(self)
1906    }
1907}
1908
1909#[cfg(esp_idf_comp_esp_netif_enabled)]
1910impl Wifi for EspWifi<'_> {
1911    type Error = EspError;
1912
1913    fn get_capabilities(&self) -> Result<EnumSet<Capability>, Self::Error> {
1914        EspWifi::get_capabilities(self)
1915    }
1916
1917    fn is_started(&self) -> Result<bool, Self::Error> {
1918        EspWifi::is_started(self)
1919    }
1920
1921    fn is_connected(&self) -> Result<bool, Self::Error> {
1922        EspWifi::is_connected(self)
1923    }
1924
1925    fn get_configuration(&self) -> Result<Configuration, Self::Error> {
1926        EspWifi::get_configuration(self)
1927    }
1928
1929    fn set_configuration(&mut self, conf: &Configuration) -> Result<(), Self::Error> {
1930        EspWifi::set_configuration(self, conf)
1931    }
1932
1933    fn start(&mut self) -> Result<(), Self::Error> {
1934        EspWifi::start(self)
1935    }
1936
1937    fn stop(&mut self) -> Result<(), Self::Error> {
1938        EspWifi::stop(self)
1939    }
1940
1941    fn connect(&mut self) -> Result<(), Self::Error> {
1942        EspWifi::connect(self)
1943    }
1944
1945    fn disconnect(&mut self) -> Result<(), Self::Error> {
1946        EspWifi::disconnect(self)
1947    }
1948
1949    fn scan_n<const N: usize>(
1950        &mut self,
1951    ) -> Result<(heapless::Vec<AccessPointInfo, N>, usize), Self::Error> {
1952        EspWifi::scan_n(self)
1953    }
1954
1955    fn scan(&mut self) -> Result<alloc::vec::Vec<AccessPointInfo>, Self::Error> {
1956        EspWifi::scan(self)
1957    }
1958}
1959
1960#[cfg(esp_idf_comp_esp_netif_enabled)]
1961impl NetifStatus for EspWifi<'_> {
1962    fn is_up(&self) -> Result<bool, EspError> {
1963        EspWifi::is_up(self)
1964    }
1965}
1966
1967#[derive(Copy, Clone)]
1968#[repr(transparent)]
1969pub struct StaScanDoneRef(wifi_event_sta_scan_done_t);
1970
1971impl StaScanDoneRef {
1972    /// Whether this scan is a success or not
1973    pub fn is_successful(&self) -> bool {
1974        self.0.status == 0
1975    }
1976
1977    /// Number of scan results
1978    pub fn len(&self) -> usize {
1979        self.0.number as usize
1980    }
1981
1982    pub fn is_empty(&self) -> bool {
1983        self.len() == 0
1984    }
1985
1986    /// Sequential identifier of the scan
1987    pub fn id(&self) -> u8 {
1988        self.0.scan_id
1989    }
1990}
1991
1992impl fmt::Debug for StaScanDoneRef {
1993    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1994        f.debug_struct("StaScanDoneRef")
1995            .field("is_successful", &self.is_successful())
1996            .field("len", &self.len())
1997            .field("id", &self.id())
1998            .finish()
1999    }
2000}
2001
2002#[derive(Copy, Clone)]
2003#[repr(transparent)]
2004pub struct StaConnectedRef(wifi_event_sta_connected_t);
2005
2006impl StaConnectedRef {
2007    /// SSID of the AP we connected to
2008    pub fn ssid(&self) -> &[u8] {
2009        &self.0.ssid.as_slice()[..self.0.ssid_len as usize]
2010    }
2011
2012    /// BSSID (or MAC address) of the AP we connected to
2013    pub fn bssid(&self) -> [u8; 6] {
2014        self.0.bssid
2015    }
2016
2017    /// Channel used for the connection to the AP
2018    pub fn channel(&self) -> u8 {
2019        self.0.channel
2020    }
2021
2022    /// Authentication method used for connecting to the AP
2023    pub fn authmode(&self) -> AuthMethod {
2024        Option::<AuthMethod>::from(Newtype(self.0.authmode)).unwrap()
2025    }
2026
2027    #[cfg(not(esp_idf_version_major = "4"))]
2028    /// Association ID given by the AP
2029    pub fn aid(&self) -> u16 {
2030        self.0.aid
2031    }
2032}
2033
2034impl fmt::Debug for StaConnectedRef {
2035    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2036        let mut d = f.debug_struct("StaConnectedRef");
2037
2038        let d = d
2039            .field("ssid", &alloc::string::String::from_utf8_lossy(self.ssid()))
2040            .field("bssid", &self.bssid())
2041            .field("channel", &self.channel())
2042            .field("authmode", &self.authmode());
2043
2044        #[cfg(not(esp_idf_version_major = "4"))]
2045        let d = d.field("aid", &self.aid());
2046
2047        d.finish()
2048    }
2049}
2050
2051#[derive(Copy, Clone)]
2052#[repr(transparent)]
2053pub struct StaDisconnectedRef(wifi_event_sta_disconnected_t);
2054
2055impl StaDisconnectedRef {
2056    /// SSID of the AP we disconnected from
2057    pub fn ssid(&self) -> &[u8] {
2058        &self.0.ssid.as_slice()[..self.0.ssid_len as usize]
2059    }
2060
2061    /// BSSID (or MAC address) of the AP we disconnected from
2062    pub fn bssid(&self) -> [u8; 6] {
2063        self.0.bssid
2064    }
2065
2066    /// The reason for disconnection
2067    pub fn reason(&self) -> u16 {
2068        self.0.reason as u16
2069    }
2070
2071    /// RSSI at the time of disconnection
2072    pub fn rssi(&self) -> i8 {
2073        self.0.rssi
2074    }
2075}
2076
2077impl fmt::Debug for StaDisconnectedRef {
2078    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2079        f.debug_struct("StaDisconnectedRef")
2080            .field("ssid", &alloc::string::String::from_utf8_lossy(self.ssid()))
2081            .field("bssid", &self.bssid())
2082            .field("reason", &self.reason())
2083            .field("rssi", &self.rssi())
2084            .finish()
2085    }
2086}
2087
2088#[derive(Copy, Clone)]
2089#[repr(transparent)]
2090pub struct ApStaConnectedRef(wifi_event_ap_staconnected_t);
2091
2092impl ApStaConnectedRef {
2093    /// MAC address of the station connected to ESP32 soft-AP
2094    pub fn mac(&self) -> [u8; 6] {
2095        self.0.mac
2096    }
2097
2098    /// the aid that ESP32 soft-AP gives to the station connected to
2099    pub fn aid(&self) -> u8 {
2100        self.0.aid
2101    }
2102
2103    /// flag to identify mesh child
2104    pub fn is_mesh_child(&self) -> bool {
2105        self.0.is_mesh_child
2106    }
2107}
2108
2109#[derive(Copy, Clone)]
2110#[repr(transparent)]
2111pub struct ApStaDisconnectedRef(wifi_event_ap_stadisconnected_t);
2112
2113impl ApStaDisconnectedRef {
2114    /// MAC address of the station disconnects to ESP32 soft-AP
2115    pub fn mac(&self) -> [u8; 6] {
2116        self.0.mac
2117    }
2118
2119    /// the aid that ESP32 soft-AP gave to the station disconnects to
2120    pub fn aid(&self) -> u8 {
2121        self.0.aid
2122    }
2123
2124    /// flag to identify mesh child
2125    pub fn is_mesh_child(&self) -> bool {
2126        self.0.is_mesh_child
2127    }
2128
2129    /// reason of disconnection
2130    #[cfg(not(any(
2131        esp_idf_version_major = "4",
2132        all(esp_idf_version_major = "5", esp_idf_version_minor = "0"),
2133    )))]
2134    #[allow(clippy::unnecessary_cast)]
2135    pub fn reason(&self) -> u16 {
2136        self.0.reason as u16
2137    }
2138}
2139
2140impl fmt::Debug for ApStaDisconnectedRef {
2141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2142        let mut ds = f.debug_struct("ApStaDisconnectedRef");
2143        ds.field("mac", &self.mac())
2144            .field("aid", &self.aid())
2145            .field("is_mesh_child", &self.is_mesh_child());
2146
2147        #[cfg(not(any(
2148            esp_idf_version_major = "4",
2149            all(esp_idf_version_major = "5", esp_idf_version_minor = "0"),
2150        )))]
2151        ds.field("reason", &self.reason());
2152
2153        ds.finish()
2154    }
2155}
2156
2157#[cfg(not(any(
2158    esp_idf_version_major = "4",
2159    all(
2160        esp_idf_version_major = "5",
2161        any(esp_idf_version_minor = "0", esp_idf_version_minor = "1")
2162    ),
2163)))]
2164#[derive(Copy, Clone, Debug)]
2165#[repr(C)]
2166pub struct HomeChannelChange {
2167    old_chan: u8,
2168    old_snd: Option<WifiSecondChan>,
2169    new_chan: u8,
2170    new_snd: Option<WifiSecondChan>,
2171}
2172
2173#[cfg(not(any(
2174    esp_idf_version_major = "4",
2175    all(
2176        esp_idf_version_major = "5",
2177        any(esp_idf_version_minor = "0", esp_idf_version_minor = "1")
2178    ),
2179)))]
2180#[derive(Copy, Clone, Debug)]
2181enum WifiSecondChan {
2182    None = 0,
2183    Above,
2184    Below,
2185}
2186
2187#[cfg(not(any(
2188    esp_idf_version_major = "4",
2189    all(
2190        esp_idf_version_major = "5",
2191        any(esp_idf_version_minor = "0", esp_idf_version_minor = "1")
2192    ),
2193)))]
2194impl TryFrom<u32> for WifiSecondChan {
2195    type Error = &'static str;
2196
2197    fn try_from(value: u32) -> Result<Self, Self::Error> {
2198        #![allow(non_upper_case_globals)]
2199        #[allow(non_snake_case)]
2200        match value {
2201            wifi_second_chan_t_WIFI_SECOND_CHAN_NONE => Ok(Self::None),
2202            wifi_second_chan_t_WIFI_SECOND_CHAN_ABOVE => Ok(Self::Above),
2203            wifi_second_chan_t_WIFI_SECOND_CHAN_BELOW => Ok(Self::Below),
2204            _ => Err("Invalid"),
2205        }
2206    }
2207}
2208
2209/// Payload reference for [`WifiEvent::StaNeighborRep`].
2210///
2211/// The neighbor report bytes are stored in a flexible array member that
2212/// immediately follows the fixed header in memory.
2213#[cfg(esp_idf_version_at_least_5_3_0)]
2214#[repr(transparent)]
2215pub struct StaNeighborRepRef(wifi_event_neighbor_report_t);
2216
2217#[cfg(esp_idf_version_at_least_5_3_0)]
2218impl StaNeighborRepRef {
2219    /// Number of bytes in the neighbor report.
2220    pub fn report_len(&self) -> usize {
2221        self.0.report_len as usize
2222    }
2223
2224    /// The raw neighbor report bytes received from the AP.
2225    ///
2226    /// The slice is reconstructed from the flexible array member
2227    /// (`n_report`) that immediately follows the fixed header in memory.
2228    pub fn report(&self) -> &[u8] {
2229        // SAFETY: `wifi_event_neighbor_report_t` is followed in memory by
2230        // exactly `report_len` bytes of report data (the `n_report` FAM).
2231        // The pointer arithmetic is safe because `as_payload()` hands us a
2232        // reference to the full event payload buffer whose size is at least
2233        // `sizeof(wifi_event_neighbor_report_t) + report_len`.
2234        unsafe { core::slice::from_raw_parts(self.0.n_report.as_ptr(), self.report_len()) }
2235    }
2236}
2237
2238#[cfg(esp_idf_version_at_least_5_3_0)]
2239impl fmt::Debug for StaNeighborRepRef {
2240    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2241        f.debug_struct("StaNeighborRepRef")
2242            .field("report_len", &self.report_len())
2243            .field("report", &self.report())
2244            .finish()
2245    }
2246}
2247
2248/// Payload reference for [`WifiEvent::ApWrongPassword`].
2249#[cfg(any(
2250    esp_idf_version_patch_at_least_5_3_3,
2251    esp_idf_version_patch_at_least_5_4_1,
2252    esp_idf_version_at_least_5_5_0,
2253))]
2254#[derive(Copy, Clone)]
2255#[repr(transparent)]
2256pub struct ApWrongPasswordRef(wifi_event_ap_wrong_password_t);
2257
2258#[cfg(any(
2259    esp_idf_version_patch_at_least_5_3_3,
2260    esp_idf_version_patch_at_least_5_4_1,
2261    esp_idf_version_at_least_5_5_0,
2262))]
2263impl ApWrongPasswordRef {
2264    /// MAC address of the station that supplied a wrong password.
2265    pub fn mac(&self) -> [u8; 6] {
2266        self.0.mac
2267    }
2268}
2269
2270#[cfg(any(
2271    esp_idf_version_patch_at_least_5_3_3,
2272    esp_idf_version_patch_at_least_5_4_1,
2273    esp_idf_version_at_least_5_5_0,
2274))]
2275impl fmt::Debug for ApWrongPasswordRef {
2276    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2277        f.debug_struct("ApWrongPasswordRef")
2278            .field("mac", &self.mac())
2279            .finish()
2280    }
2281}
2282
2283/// Payload reference for [`WifiEvent::StaBeaconOffsetUnstable`].
2284#[cfg(esp_idf_version_at_least_5_5_0)]
2285#[derive(Copy, Clone)]
2286#[repr(transparent)]
2287pub struct StaBeaconOffsetUnstableRef(wifi_event_sta_beacon_offset_unstable_t);
2288
2289#[cfg(esp_idf_version_at_least_5_5_0)]
2290impl StaBeaconOffsetUnstableRef {
2291    /// Fraction of beacons successfully received during the unstable period.
2292    pub fn beacon_success_rate(&self) -> f32 {
2293        self.0.beacon_success_rate
2294    }
2295}
2296
2297#[cfg(esp_idf_version_at_least_5_5_0)]
2298impl fmt::Debug for StaBeaconOffsetUnstableRef {
2299    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2300        f.debug_struct("StaBeaconOffsetUnstableRef")
2301            .field("beacon_success_rate", &self.beacon_success_rate())
2302            .finish()
2303    }
2304}
2305
2306/// Payload reference for [`WifiEvent::DppUriReady`].
2307///
2308/// The URI string is stored in a flexible array member that immediately
2309/// follows the fixed header; `uri_data_len` includes the null terminator.
2310/// [`DppUriReadyRef::uri`] strips the terminator and returns the URI as a `&str`.
2311#[cfg(esp_idf_version_at_least_5_5_0)]
2312#[repr(transparent)]
2313pub struct DppUriReadyRef(wifi_event_dpp_uri_ready_t);
2314
2315#[cfg(esp_idf_version_at_least_5_5_0)]
2316impl DppUriReadyRef {
2317    /// Length of the URI string **excluding** the null terminator.
2318    pub fn uri_len(&self) -> usize {
2319        (self.0.uri_data_len as usize).saturating_sub(1)
2320    }
2321
2322    /// The DPP bootstrapping URI as a string slice (no null terminator).
2323    ///
2324    /// The slice is reconstructed from the flexible array member
2325    /// (`uri`) that immediately follows the fixed header in memory.
2326    ///
2327    /// DPP bootstrapping URIs are defined by the Wi-Fi Alliance DPP
2328    /// specification to be ASCII-printable, so the bytes are always
2329    /// valid UTF-8.
2330    pub fn uri(&self) -> &str {
2331        // SAFETY: `uri_data_len` includes the null terminator; we subtract 1
2332        // to get the string length without it. DPP URIs are always ASCII so
2333        // `from_utf8_unchecked` is safe.
2334        unsafe {
2335            core::str::from_utf8_unchecked(core::slice::from_raw_parts(
2336                self.0.uri.as_ptr().cast::<u8>(),
2337                self.uri_len(),
2338            ))
2339        }
2340    }
2341}
2342
2343#[cfg(esp_idf_version_at_least_5_5_0)]
2344impl fmt::Debug for DppUriReadyRef {
2345    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2346        f.debug_struct("DppUriReadyRef")
2347            .field("uri", &self.uri())
2348            .finish()
2349    }
2350}
2351
2352/// Payload reference for [`WifiEvent::DppCfgRecvd`].
2353///
2354/// Not `Copy`/`Clone`: recent ESP-IDF added a flexible-array-member
2355/// field (`configs[]`) to `wifi_event_dpp_config_received_t`, which
2356/// bindgen surfaces as `__IncompleteArrayField` (intentionally not
2357/// `Clone`). The newtype is always borrowed via `&'a DppCfgRecvdRef`
2358/// inside event callbacks, so the derive was never load-bearing.
2359#[cfg(esp_idf_version_at_least_5_5_0)]
2360#[repr(transparent)]
2361pub struct DppCfgRecvdRef(wifi_event_dpp_config_received_t);
2362
2363#[cfg(esp_idf_version_at_least_5_5_0)]
2364impl DppCfgRecvdRef {
2365    /// The WiFi configuration received via DPP authentication.
2366    pub fn wifi_cfg(&self) -> &wifi_config_t {
2367        &self.0.wifi_cfg
2368    }
2369}
2370
2371#[cfg(esp_idf_version_at_least_5_5_0)]
2372impl fmt::Debug for DppCfgRecvdRef {
2373    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2374        f.debug_struct("DppCfgRecvdRef").finish_non_exhaustive()
2375    }
2376}
2377
2378/// Payload reference for [`WifiEvent::DppFailed`].
2379#[cfg(esp_idf_version_at_least_5_5_0)]
2380#[derive(Copy, Clone)]
2381#[repr(transparent)]
2382pub struct DppFailedRef(wifi_event_dpp_failed_t);
2383
2384#[cfg(esp_idf_version_at_least_5_5_0)]
2385impl DppFailedRef {
2386    /// The DPP failure reason code.
2387    pub fn failure_reason(&self) -> i32 {
2388        self.0.failure_reason
2389    }
2390}
2391
2392#[cfg(esp_idf_version_at_least_5_5_0)]
2393impl fmt::Debug for DppFailedRef {
2394    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2395        f.debug_struct("DppFailedRef")
2396            .field("failure_reason", &self.failure_reason())
2397            .finish()
2398    }
2399}
2400
2401#[derive(Copy, Clone)]
2402#[repr(transparent)]
2403pub struct WpsCredentialsRef(wifi_event_sta_wps_er_success_t__bindgen_ty_1);
2404
2405impl WpsCredentialsRef {
2406    pub fn ssid(&self) -> &CStr {
2407        unsafe { CStr::from_ptr(self.0.ssid.as_ptr() as *const _) }
2408    }
2409
2410    pub fn passphrase(&self) -> &CStr {
2411        unsafe { CStr::from_ptr(self.0.passphrase.as_ptr() as *const _) }
2412    }
2413}
2414
2415impl fmt::Debug for WpsCredentialsRef {
2416    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2417        f.debug_struct("WpsCredentialsRef")
2418            .field("ssid", &self.ssid())
2419            .finish()
2420    }
2421}
2422
2423#[derive(Clone, Debug, Eq, PartialEq)]
2424pub struct WpsCredentials {
2425    pub ssid: heapless::String<32>,
2426    pub passphrase: heapless::String<64>,
2427}
2428
2429impl TryFrom<&WpsCredentialsRef> for WpsCredentials {
2430    type Error = EspError;
2431
2432    fn try_from(credentials: &WpsCredentialsRef) -> Result<Self, Self::Error> {
2433        let err = EspError::from_infallible::<ESP_ERR_INVALID_ARG>();
2434
2435        Ok(Self {
2436            ssid: credentials
2437                .ssid()
2438                .to_str()
2439                .map_err(|_| err)
2440                .and_then(|credentials| credentials.try_into().map_err(|_| err))?,
2441            passphrase: credentials
2442                .passphrase()
2443                .to_str()
2444                .map_err(|_| err)
2445                .and_then(|credentials| credentials.try_into().map_err(|_| err))?,
2446        })
2447    }
2448}
2449
2450impl fmt::Debug for ApStaConnectedRef {
2451    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2452        f.debug_struct("ApStaConnectedRef")
2453            .field("mac", &self.mac())
2454            .field("aid", &self.aid())
2455            .field("is_mesh_child", &self.is_mesh_child())
2456            .finish()
2457    }
2458}
2459
2460#[derive(Copy, Clone, Debug)]
2461pub enum WifiEvent<'a> {
2462    Ready,
2463
2464    ScanDone(&'a StaScanDoneRef),
2465
2466    StaStarted,
2467    StaStopped,
2468    StaConnected(&'a StaConnectedRef),
2469    StaDisconnected(&'a StaDisconnectedRef),
2470    StaAuthmodeChanged,
2471    StaBssRssiLow,
2472    StaBeaconTimeout,
2473    StaWpsSuccess(&'a [WpsCredentialsRef]),
2474    StaWpsFailed,
2475    StaWpsTimeout,
2476    StaWpsPin(Option<u32>),
2477    StaWpsPbcOverlap,
2478
2479    ApStarted,
2480    ApStopped,
2481    ApStaConnected(&'a ApStaConnectedRef),
2482    ApStaDisconnected(&'a ApStaDisconnectedRef),
2483    ApProbeRequestReceived,
2484
2485    FtmReport,
2486    ActionTxStatus,
2487    RocDone,
2488
2489    #[cfg(not(any(
2490        esp_idf_version_major = "4",
2491        all(
2492            esp_idf_version_major = "5",
2493            any(esp_idf_version_minor = "0", esp_idf_version_minor = "1")
2494        ),
2495    )))]
2496    HomeChannelChange(HomeChannelChange),
2497
2498    /// Neighbor report received from the AP in response to a neighbor report
2499    /// request from the station.
2500    #[cfg(esp_idf_version_at_least_5_3_0)]
2501    StaNeighborRep(&'a StaNeighborRepRef),
2502
2503    /// A station attempted to connect to the soft-AP with the wrong password.
2504    #[cfg(any(
2505        esp_idf_version_patch_at_least_5_3_3,
2506        esp_idf_version_patch_at_least_5_4_1,
2507        esp_idf_version_at_least_5_5_0,
2508    ))]
2509    ApWrongPassword(&'a ApWrongPasswordRef),
2510
2511    /// The station's beacon offset has become unstable.
2512    #[cfg(esp_idf_version_at_least_5_5_0)]
2513    StaBeaconOffsetUnstable(&'a StaBeaconOffsetUnstableRef),
2514
2515    /// A DPP bootstrapping URI is ready to be scanned by the peer.
2516    #[cfg(esp_idf_version_at_least_5_5_0)]
2517    DppUriReady(&'a DppUriReadyRef),
2518
2519    /// DPP authentication succeeded and a WiFi configuration was received.
2520    #[cfg(esp_idf_version_at_least_5_5_0)]
2521    DppCfgRecvd(&'a DppCfgRecvdRef),
2522
2523    /// DPP authentication or configuration exchange failed.
2524    #[cfg(esp_idf_version_at_least_5_5_0)]
2525    DppFailed(&'a DppFailedRef),
2526
2527    /// An event ID not recognised by this version of the library was received.
2528    ///
2529    /// This variant is produced instead of panicking when an unknown event ID
2530    /// arrives, allowing applications to remain forward-compatible with
2531    /// ESP-IDF versions that introduce new WiFi events.
2532    Other(i32),
2533}
2534
2535unsafe impl EspEventSource for WifiEvent<'_> {
2536    fn source() -> Option<&'static ffi::CStr> {
2537        Some(unsafe { ffi::CStr::from_ptr(WIFI_EVENT) })
2538    }
2539}
2540
2541impl EspEventDeserializer for WifiEvent<'_> {
2542    type Data<'d> = WifiEvent<'d>;
2543
2544    #[allow(non_upper_case_globals, non_snake_case)]
2545    fn deserialize<'d>(data: &crate::eventloop::EspEvent<'d>) -> WifiEvent<'d> {
2546        let event_id = data.event_id as u32;
2547
2548        match event_id {
2549            wifi_event_t_WIFI_EVENT_WIFI_READY => WifiEvent::Ready,
2550            wifi_event_t_WIFI_EVENT_SCAN_DONE => WifiEvent::ScanDone(unsafe { data.as_payload() }),
2551            wifi_event_t_WIFI_EVENT_STA_START => WifiEvent::StaStarted,
2552            wifi_event_t_WIFI_EVENT_STA_STOP => WifiEvent::StaStopped,
2553            wifi_event_t_WIFI_EVENT_STA_CONNECTED => {
2554                WifiEvent::StaConnected(unsafe { data.as_payload() })
2555            }
2556            wifi_event_t_WIFI_EVENT_STA_DISCONNECTED => {
2557                WifiEvent::StaDisconnected(unsafe { data.as_payload() })
2558            }
2559            wifi_event_t_WIFI_EVENT_STA_AUTHMODE_CHANGE => WifiEvent::StaAuthmodeChanged,
2560            wifi_event_t_WIFI_EVENT_STA_WPS_ER_SUCCESS => {
2561                let credentials: &[wifi_event_sta_wps_er_success_t__bindgen_ty_1] = data
2562                    .payload
2563                    .map(|x| x as *const _ as *const wifi_event_sta_wps_er_success_t)
2564                    .and_then(|x| unsafe { x.as_ref() })
2565                    .map(|x| &x.ap_cred[0..x.ap_cred_cnt as usize])
2566                    .unwrap_or(&[]);
2567                // SAFETY: transparent representation of target type
2568                let credentials: &[WpsCredentialsRef] =
2569                    unsafe { core::mem::transmute(credentials) };
2570
2571                WifiEvent::StaWpsSuccess(credentials)
2572            }
2573            wifi_event_t_WIFI_EVENT_STA_WPS_ER_FAILED => WifiEvent::StaWpsFailed,
2574            wifi_event_t_WIFI_EVENT_STA_WPS_ER_TIMEOUT => WifiEvent::StaWpsTimeout,
2575            wifi_event_t_WIFI_EVENT_STA_WPS_ER_PIN => {
2576                let pin = data
2577                    .payload
2578                    .map(|x| x as *const _ as *const wifi_event_sta_wps_er_pin_t)
2579                    .and_then(|x| unsafe { x.as_ref() })
2580                    .and_then(|x| core::str::from_utf8(&x.pin_code).ok())
2581                    .and_then(|x| x.parse().ok());
2582                WifiEvent::StaWpsPin(pin)
2583            }
2584            wifi_event_t_WIFI_EVENT_STA_WPS_ER_PBC_OVERLAP => WifiEvent::StaWpsPbcOverlap,
2585            wifi_event_t_WIFI_EVENT_AP_START => WifiEvent::ApStarted,
2586            wifi_event_t_WIFI_EVENT_AP_STOP => WifiEvent::ApStopped,
2587            wifi_event_t_WIFI_EVENT_AP_STACONNECTED => {
2588                WifiEvent::ApStaConnected(unsafe { data.as_payload() })
2589            }
2590            wifi_event_t_WIFI_EVENT_AP_STADISCONNECTED => {
2591                WifiEvent::ApStaDisconnected(unsafe { data.as_payload() })
2592            }
2593            wifi_event_t_WIFI_EVENT_AP_PROBEREQRECVED => WifiEvent::ApProbeRequestReceived,
2594            wifi_event_t_WIFI_EVENT_FTM_REPORT => WifiEvent::FtmReport,
2595            wifi_event_t_WIFI_EVENT_STA_BSS_RSSI_LOW => WifiEvent::StaBssRssiLow,
2596            wifi_event_t_WIFI_EVENT_ACTION_TX_STATUS => WifiEvent::ActionTxStatus,
2597            wifi_event_t_WIFI_EVENT_STA_BEACON_TIMEOUT => WifiEvent::StaBeaconTimeout,
2598            wifi_event_t_WIFI_EVENT_ROC_DONE => WifiEvent::RocDone,
2599            #[cfg(not(any(
2600                esp_idf_version_major = "4",
2601                all(
2602                    esp_idf_version_major = "5",
2603                    any(esp_idf_version_minor = "0", esp_idf_version_minor = "1")
2604                ),
2605            )))]
2606            wifi_event_t_WIFI_EVENT_HOME_CHANNEL_CHANGE => {
2607                #[cfg(esp_idf_soc_wifi_supported)]
2608                {
2609                    let payload = unsafe {
2610                        (data.payload.unwrap() as *const _
2611                            as *const wifi_event_home_channel_change_t)
2612                            .as_ref()
2613                    }
2614                    .unwrap();
2615
2616                    WifiEvent::HomeChannelChange(HomeChannelChange {
2617                        old_chan: payload.old_chan,
2618                        old_snd: payload.old_snd.try_into().ok(),
2619                        new_chan: payload.new_chan,
2620                        new_snd: payload.new_snd.try_into().ok(),
2621                    })
2622                }
2623
2624                // On SoCs without native WiFi (using esp_wifi_remote), this
2625                // event may fire without a payload.
2626                #[cfg(not(esp_idf_soc_wifi_supported))]
2627                {
2628                    let payload = data
2629                        .payload
2630                        .map(|p| p as *const _ as *const wifi_event_home_channel_change_t)
2631                        .and_then(|p| unsafe { p.as_ref() });
2632
2633                    WifiEvent::HomeChannelChange(HomeChannelChange {
2634                        old_chan: payload.map_or(0, |p| p.old_chan),
2635                        old_snd: payload.and_then(|p| p.old_snd.try_into().ok()),
2636                        new_chan: payload.map_or(0, |p| p.new_chan),
2637                        new_snd: payload.and_then(|p| p.new_snd.try_into().ok()),
2638                    })
2639                }
2640            }
2641            #[cfg(esp_idf_version_at_least_5_3_0)]
2642            wifi_event_t_WIFI_EVENT_STA_NEIGHBOR_REP => {
2643                // `wifi_event_neighbor_report_t` contains `__IncompleteArrayField`
2644                // which prevents deriving `Copy`, so we cannot use `as_payload()`.
2645                // The cast is safe for the same reason as `as_payload` internaly:
2646                // we only produce a shared reference into the event buffer.
2647                WifiEvent::StaNeighborRep(unsafe {
2648                    (data.payload.unwrap() as *const _ as *const StaNeighborRepRef)
2649                        .as_ref()
2650                        .unwrap()
2651                })
2652            }
2653            #[cfg(any(
2654                esp_idf_version_patch_at_least_5_3_3,
2655                esp_idf_version_patch_at_least_5_4_1,
2656                esp_idf_version_at_least_5_5_0,
2657            ))]
2658            wifi_event_t_WIFI_EVENT_AP_WRONG_PASSWORD => {
2659                WifiEvent::ApWrongPassword(unsafe { data.as_payload() })
2660            }
2661            #[cfg(esp_idf_version_at_least_5_5_0)]
2662            wifi_event_t_WIFI_EVENT_STA_BEACON_OFFSET_UNSTABLE => {
2663                WifiEvent::StaBeaconOffsetUnstable(unsafe { data.as_payload() })
2664            }
2665            #[cfg(esp_idf_version_at_least_5_5_0)]
2666            wifi_event_t_WIFI_EVENT_DPP_URI_READY => {
2667                // `wifi_event_dpp_uri_ready_t` contains `__IncompleteArrayField`
2668                // which prevents deriving `Copy`, so we cannot use `as_payload()`.
2669                WifiEvent::DppUriReady(unsafe {
2670                    (data.payload.unwrap() as *const _ as *const DppUriReadyRef)
2671                        .as_ref()
2672                        .unwrap()
2673                })
2674            }
2675            #[cfg(esp_idf_version_at_least_5_5_0)]
2676            wifi_event_t_WIFI_EVENT_DPP_CFG_RECVD => {
2677                // `wifi_event_dpp_config_received_t` contains
2678                // `__IncompleteArrayField` which prevents deriving `Copy`,
2679                // so we cannot use `as_payload()`.
2680                WifiEvent::DppCfgRecvd(unsafe {
2681                    (data.payload.unwrap() as *const _ as *const DppCfgRecvdRef)
2682                        .as_ref()
2683                        .unwrap()
2684                })
2685            }
2686            #[cfg(esp_idf_version_at_least_5_5_0)]
2687            wifi_event_t_WIFI_EVENT_DPP_FAILED => {
2688                WifiEvent::DppFailed(unsafe { data.as_payload() })
2689            }
2690            _ => {
2691                ::log::warn!("WifiEvent: unknown event ID {event_id}, ignoring");
2692                WifiEvent::Other(event_id as i32)
2693            }
2694        }
2695    }
2696}
2697
2698const CONNECT_TIMEOUT: Duration = Duration::from_secs(15);
2699const WPS_TIMEOUT: Duration = Duration::from_secs(120);
2700
2701/// Wraps a [`WifiDriver`] or [`EspWifi`], and offers strictly synchronous (blocking)
2702/// function calls for their functionality.
2703// TODO: add an example about wrapping an existing instance of wifidriver/espwifi,
2704// as well as using that instance once the BlockingWifi drops it.
2705pub struct BlockingWifi<T> {
2706    wifi: T,
2707    event_loop: crate::eventloop::EspSystemEventLoop,
2708}
2709
2710impl<T> BlockingWifi<T>
2711where
2712    T: Wifi<Error = EspError> + NonBlocking,
2713{
2714    pub fn wrap(wifi: T, event_loop: EspSystemEventLoop) -> Result<Self, EspError> {
2715        Ok(Self { wifi, event_loop })
2716    }
2717
2718    /// Returns the underlying [`WifiDriver`] or [`EspWifi`]
2719    pub fn wifi(&self) -> &T {
2720        &self.wifi
2721    }
2722
2723    /// Returns the underlying [`WifiDriver`] or [`EspWifi`], as mutable
2724    pub fn wifi_mut(&mut self) -> &mut T {
2725        &mut self.wifi
2726    }
2727
2728    /// As per [`WifiDriver::get_capabilities()`]
2729    pub fn get_capabilities(&self) -> Result<EnumSet<Capability>, EspError> {
2730        self.wifi.get_capabilities()
2731    }
2732
2733    /// As per [`WifiDriver::get_configuration()`]
2734    pub fn get_configuration(&self) -> Result<Configuration, EspError> {
2735        self.wifi.get_configuration()
2736    }
2737
2738    /// As per [`WifiDriver::set_configuration()`]
2739    pub fn set_configuration(&mut self, conf: &Configuration) -> Result<(), EspError> {
2740        self.wifi.set_configuration(conf)
2741    }
2742
2743    /// As per [`WifiDriver::is_started()`]
2744    pub fn is_started(&self) -> Result<bool, EspError> {
2745        self.wifi.is_started()
2746    }
2747
2748    /// As per [`WifiDriver::is_connected()`]
2749    pub fn is_connected(&self) -> Result<bool, EspError> {
2750        self.wifi.is_connected()
2751    }
2752
2753    /// As per [`WifiDriver::start()`], but as a blocking call that returns
2754    /// once the wifi driver has started.
2755    pub fn start(&mut self) -> Result<(), EspError> {
2756        self.wifi.start()?;
2757        self.wifi_wait_while(|| self.wifi.is_started().map(|s| !s), None)
2758    }
2759
2760    /// As per [`WifiDriver::stop()`], but as a blocking call that returns
2761    /// once the wifi driver has stopped.
2762    pub fn stop(&mut self) -> Result<(), EspError> {
2763        self.wifi.stop()?;
2764        self.wifi_wait_while(|| self.wifi.is_started(), None)
2765    }
2766
2767    /// As per [`WifiDriver::connect()`], but as a blocking call that returns
2768    /// once the wifi driver is connected.
2769    pub fn connect(&mut self) -> Result<(), EspError> {
2770        self.wifi.connect()?;
2771        self.wifi_wait_while(
2772            || self.wifi.is_connected().map(|s| !s),
2773            Some(CONNECT_TIMEOUT),
2774        )
2775    }
2776
2777    /// As per [`WifiDriver::disconnect()`], but as a blocking call that returns
2778    /// once the wifi driver has disconnected.
2779    pub fn disconnect(&mut self) -> Result<(), EspError> {
2780        self.wifi.disconnect()?;
2781        self.wifi_wait_while(|| self.wifi.is_connected(), None)
2782    }
2783
2784    /// As per [`WifiDriver::scan_n()`]
2785    pub fn scan_n<const N: usize>(
2786        &mut self,
2787    ) -> Result<(heapless::Vec<AccessPointInfo, N>, usize), EspError> {
2788        self.wifi.scan_n()
2789    }
2790
2791    /// As per [`WifiDriver::scan()`]
2792    #[cfg(feature = "alloc")]
2793    pub fn scan(&mut self) -> Result<alloc::vec::Vec<AccessPointInfo>, EspError> {
2794        self.wifi.scan()
2795    }
2796
2797    /// Performs a blocking wait until certain condition provided by the user
2798    /// in the form of a `matcher` callback becomes false. Most often than not
2799    /// that condition would be related to the state of the Wifi driver. In
2800    /// other words, whether the driver is started, stopped, (dis)connected and
2801    /// so on.
2802    ///
2803    /// Note that the waiting is not done internally using busy-looping and/or
2804    /// timeouts. Rather, the condition (`matcher`) is evaluated initially, and
2805    /// if it returns `true`, it is re-evaluated each time the ESP IDF C Wifi
2806    /// driver posts a Wifi event on the system event loop. The reasoning behind
2807    /// this is that changes to the state of the Wifi driver are always
2808    /// accompanied by posting Wifi events.
2809    pub fn wifi_wait_while<F: Fn() -> Result<bool, EspError>>(
2810        &self,
2811        matcher: F,
2812        timeout: Option<Duration>,
2813    ) -> Result<(), EspError> {
2814        let wait = Wait::new::<WifiEvent>(&self.event_loop)?;
2815
2816        wait.wait_while(matcher, timeout)
2817    }
2818
2819    /// Start WPS and perform a blocking wait until it connects, fails or times
2820    /// out. A [`WpsStatus`] is returned that contains the success status of the
2821    /// WPS connection. When the credentials of only one access point are
2822    /// received, the WiFi driver configuration will automatically be set to
2823    /// that access point. If multiple credentials were received, a
2824    /// `WpsStatus::Success` will be returned with a vector containing those
2825    /// credentials. The caller must then handle those credentials and set the
2826    /// configuration manually.
2827    pub fn start_wps(&mut self, config: &WpsConfig) -> Result<WpsStatus, EspError> {
2828        self.wifi.start_wps(config)?;
2829        self.wifi_wait_while(
2830            || self.wifi.is_wps_finished().map(|x| !x),
2831            Some(WPS_TIMEOUT),
2832        )?;
2833        Ok(self.wifi.stop_wps().unwrap_or(WpsStatus::Timeout))
2834    }
2835}
2836
2837#[cfg(esp_idf_comp_esp_netif_enabled)]
2838impl<T> BlockingWifi<T>
2839where
2840    T: NetifStatus,
2841{
2842    /// As per [`EspWifi::is_up()`].
2843    pub fn is_up(&self) -> Result<bool, EspError> {
2844        self.wifi.is_up()
2845    }
2846
2847    /// Waits until the underlaying network interface is up.
2848    pub fn wait_netif_up(&self) -> Result<(), EspError> {
2849        self.ip_wait_while(|| self.wifi.is_up().map(|s| !s), Some(CONNECT_TIMEOUT))
2850    }
2851
2852    /// As [`BlockingWifi::wifi_wait_while()`], but for `EspWifi` events
2853    /// related to the IP layer, instead of `WifiDriver` events on the data link layer.
2854    pub fn ip_wait_while<F: Fn() -> Result<bool, EspError>>(
2855        &self,
2856        matcher: F,
2857        timeout: Option<core::time::Duration>,
2858    ) -> Result<(), EspError> {
2859        let wait = crate::eventloop::Wait::new::<IpEvent>(&self.event_loop)?;
2860
2861        wait.wait_while(matcher, timeout)
2862    }
2863}
2864
2865impl<T> Wifi for BlockingWifi<T>
2866where
2867    T: Wifi<Error = EspError> + NonBlocking,
2868{
2869    type Error = EspError;
2870
2871    fn get_capabilities(&self) -> Result<EnumSet<Capability>, Self::Error> {
2872        BlockingWifi::get_capabilities(self)
2873    }
2874
2875    fn get_configuration(&self) -> Result<Configuration, Self::Error> {
2876        BlockingWifi::get_configuration(self)
2877    }
2878
2879    fn set_configuration(&mut self, conf: &Configuration) -> Result<(), Self::Error> {
2880        BlockingWifi::set_configuration(self, conf)
2881    }
2882
2883    fn is_started(&self) -> Result<bool, Self::Error> {
2884        BlockingWifi::is_started(self)
2885    }
2886
2887    fn is_connected(&self) -> Result<bool, Self::Error> {
2888        BlockingWifi::is_connected(self)
2889    }
2890
2891    fn start(&mut self) -> Result<(), Self::Error> {
2892        BlockingWifi::start(self)
2893    }
2894
2895    fn stop(&mut self) -> Result<(), Self::Error> {
2896        BlockingWifi::stop(self)
2897    }
2898
2899    fn connect(&mut self) -> Result<(), Self::Error> {
2900        BlockingWifi::connect(self)
2901    }
2902
2903    fn disconnect(&mut self) -> Result<(), Self::Error> {
2904        BlockingWifi::disconnect(self)
2905    }
2906
2907    fn scan_n<const N: usize>(
2908        &mut self,
2909    ) -> Result<(heapless::Vec<AccessPointInfo, N>, usize), Self::Error> {
2910        BlockingWifi::scan_n(self)
2911    }
2912
2913    #[cfg(feature = "alloc")]
2914    fn scan(&mut self) -> Result<alloc::vec::Vec<AccessPointInfo>, Self::Error> {
2915        BlockingWifi::scan(self)
2916    }
2917}
2918
2919#[cfg(esp_idf_comp_esp_netif_enabled)]
2920impl<T> NetifStatus for BlockingWifi<T>
2921where
2922    T: NetifStatus,
2923{
2924    fn is_up(&self) -> Result<bool, EspError> {
2925        BlockingWifi::is_up(self)
2926    }
2927}
2928
2929/// Wraps a [`WifiDriver`] or [`EspWifi`], and offers strictly `async`
2930/// (non-blocking) function calls for their functionality.
2931#[cfg(all(feature = "alloc", esp_idf_comp_esp_timer_enabled))]
2932pub struct AsyncWifi<T> {
2933    wifi: T,
2934    event_loop: crate::eventloop::EspSystemEventLoop,
2935    timer_service: crate::timer::EspTaskTimerService,
2936}
2937
2938#[cfg(all(feature = "alloc", esp_idf_comp_esp_timer_enabled))]
2939impl<T> AsyncWifi<T>
2940where
2941    T: Wifi<Error = EspError> + NonBlocking,
2942{
2943    pub fn wrap(
2944        wifi: T,
2945        event_loop: EspSystemEventLoop,
2946        timer_service: EspTaskTimerService,
2947    ) -> Result<Self, EspError> {
2948        Ok(Self {
2949            wifi,
2950            event_loop,
2951            timer_service,
2952        })
2953    }
2954
2955    /// Returns the underlying [`WifiDriver`] or [`EspWifi`]
2956    pub fn wifi(&self) -> &T {
2957        &self.wifi
2958    }
2959
2960    /// Returns the underlying [`WifiDriver`] or [`EspWifi`], as mutable
2961    pub fn wifi_mut(&mut self) -> &mut T {
2962        &mut self.wifi
2963    }
2964
2965    /// As per [`WifiDriver::get_capabilities()`]
2966    pub fn get_capabilities(&self) -> Result<EnumSet<Capability>, EspError> {
2967        self.wifi.get_capabilities()
2968    }
2969
2970    /// As per [`WifiDriver::get_configuration()`]
2971    pub fn get_configuration(&self) -> Result<Configuration, EspError> {
2972        self.wifi.get_configuration()
2973    }
2974
2975    /// As per [`WifiDriver::set_configuration()`]
2976    pub fn set_configuration(&mut self, conf: &Configuration) -> Result<(), EspError> {
2977        self.wifi.set_configuration(conf)
2978    }
2979
2980    /// As per [`WifiDriver::is_started()`]
2981    pub fn is_started(&self) -> Result<bool, EspError> {
2982        self.wifi.is_started()
2983    }
2984
2985    /// As per [`WifiDriver::is_connected()`]
2986    pub fn is_connected(&self) -> Result<bool, EspError> {
2987        self.wifi.is_connected()
2988    }
2989
2990    /// As per [`WifiDriver::start()`], but as an async call that awaits until
2991    /// the wifi driver has started.
2992    pub async fn start(&mut self) -> Result<(), EspError> {
2993        self.wifi.start()?;
2994        self.wifi_wait(|this| this.wifi.is_started().map(|s| !s), None)
2995            .await
2996    }
2997
2998    /// As per [`WifiDriver::stop()`], but as an async call that awaits until
2999    /// the wifi driver has stopped.
3000    pub async fn stop(&mut self) -> Result<(), EspError> {
3001        self.wifi.stop()?;
3002        self.wifi_wait(|this| this.wifi.is_started(), None).await
3003    }
3004
3005    /// As per [`WifiDriver::connect()`], but as an async call that awaits until
3006    /// the wifi driver has connected.
3007    pub async fn connect(&mut self) -> Result<(), EspError> {
3008        self.wifi.connect()?;
3009        self.wifi_wait(
3010            |this| this.wifi.is_connected().map(|s| !s),
3011            Some(CONNECT_TIMEOUT),
3012        )
3013        .await
3014    }
3015
3016    /// As per [`WifiDriver::disconnect()`], but as an async call that awaits until
3017    /// the wifi driver has disconnected.
3018    pub async fn disconnect(&mut self) -> Result<(), EspError> {
3019        self.wifi.disconnect()?;
3020        self.wifi_wait(|this| this.wifi.is_connected(), None).await
3021    }
3022
3023    /// As per [`WifiDriver::start_scan()`] plus [`WifiDriver::get_scan_result_n()`],
3024    /// as an async call that awaits until the scan is complete.
3025    pub async fn scan_n<const N: usize>(
3026        &mut self,
3027    ) -> Result<(heapless::Vec<AccessPointInfo, N>, usize), EspError> {
3028        self.wifi.start_scan(&Default::default(), false)?;
3029
3030        self.wifi_wait(|this| this.wifi.is_scan_done().map(|s| !s), None)
3031            .await?;
3032
3033        self.wifi.get_scan_result_n()
3034    }
3035
3036    /// As per [`WifiDriver::start_scan()`] plus [`WifiDriver::get_scan_result()`],
3037    /// as an async call that awaits until the scan is complete.
3038    #[cfg(feature = "alloc")]
3039    pub async fn scan(&mut self) -> Result<alloc::vec::Vec<AccessPointInfo>, EspError> {
3040        self.wifi.start_scan(&Default::default(), false)?;
3041
3042        self.wifi_wait(|this| this.wifi.is_scan_done().map(|s| !s), None)
3043            .await?;
3044
3045        self.wifi.get_scan_result()
3046    }
3047
3048    /// Awaits for a certain condition provided by the user in the form of a
3049    /// `matcher` callback to become false. Most often than not that condition
3050    /// would be related to the state of the Wifi driver. In other words,
3051    /// whether the driver is started, stopped, (dis)connected and so on.
3052    ///
3053    /// Note that the waiting is not done internally using busy-looping and/or
3054    /// timeouts. Rather, the condition (`matcher`) is evaluated initially, and
3055    /// if it returns `true`, it is re-evaluated each time the ESP IDF C Wifi
3056    /// driver posts a Wifi event on the system event loop. The reasoning behind
3057    /// this is that changes to the state of the Wifi driver are always
3058    /// accompanied by posting Wifi events.
3059    pub async fn wifi_wait<F: FnMut(&mut Self) -> Result<bool, EspError>>(
3060        &mut self,
3061        mut matcher: F,
3062        timeout: Option<Duration>,
3063    ) -> Result<(), EspError> {
3064        let mut wait = crate::eventloop::AsyncWait::<WifiEvent, _>::new(
3065            &self.event_loop,
3066            &self.timer_service,
3067        )?;
3068
3069        wait.wait_while(|| matcher(self), timeout).await
3070    }
3071
3072    /// Start WPS and perform a wait asynchronously until it connects, fails or
3073    /// times out. A [`WpsStatus`] is returned that contains the success status
3074    /// of the WPS connection. When the credentials of only one access point are
3075    /// received, the WiFi driver configuration will automatically be set to
3076    /// that access point. If multiple credentials were received, a
3077    /// `WpsStatus::Success` will be returned with a vector containing those
3078    /// credentials. The caller must then handle those credentials and set the
3079    /// configuration manually.
3080    pub async fn start_wps(&mut self, config: &WpsConfig<'_>) -> Result<WpsStatus, EspError> {
3081        self.wifi.start_wps(config)?;
3082        self.wifi_wait(
3083            |this| this.wifi.is_wps_finished().map(|x| !x),
3084            Some(WPS_TIMEOUT),
3085        )
3086        .await?;
3087        Ok(self.wifi.stop_wps().unwrap_or(WpsStatus::Timeout))
3088    }
3089}
3090
3091#[cfg(all(feature = "alloc", esp_idf_comp_esp_timer_enabled))]
3092#[cfg(esp_idf_comp_esp_netif_enabled)]
3093impl<T> AsyncWifi<T>
3094where
3095    T: NetifStatus,
3096{
3097    /// As per [`EspWifi::is_up()`].
3098    pub fn is_up(&self) -> Result<bool, EspError> {
3099        self.wifi.is_up()
3100    }
3101
3102    /// Waits until the underlaying network interface is up.
3103    pub async fn wait_netif_up(&mut self) -> Result<(), EspError> {
3104        self.ip_wait_while(|this| this.wifi.is_up().map(|s| !s), Some(CONNECT_TIMEOUT))
3105            .await
3106    }
3107
3108    /// As [`AsyncWifi::wifi_wait()`], but for `EspWifi` events related to the
3109    /// IP layer, instead of `WifiDriver` events on the data link layer.
3110    pub async fn ip_wait_while<F: FnMut(&mut Self) -> Result<bool, EspError>>(
3111        &mut self,
3112        mut matcher: F,
3113        timeout: Option<core::time::Duration>,
3114    ) -> Result<(), EspError> {
3115        let mut wait =
3116            crate::eventloop::AsyncWait::<IpEvent, _>::new(&self.event_loop, &self.timer_service)?;
3117
3118        wait.wait_while(|| matcher(self), timeout).await
3119    }
3120}
3121
3122#[cfg(all(feature = "alloc", esp_idf_comp_esp_timer_enabled))]
3123impl<T> embedded_svc::wifi::asynch::Wifi for AsyncWifi<T>
3124where
3125    T: Wifi<Error = EspError> + NonBlocking,
3126{
3127    type Error = T::Error;
3128
3129    async fn get_capabilities(&self) -> Result<EnumSet<Capability>, Self::Error> {
3130        AsyncWifi::get_capabilities(self)
3131    }
3132
3133    async fn get_configuration(&self) -> Result<Configuration, Self::Error> {
3134        AsyncWifi::get_configuration(self)
3135    }
3136
3137    async fn set_configuration(&mut self, conf: &Configuration) -> Result<(), Self::Error> {
3138        AsyncWifi::set_configuration(self, conf)
3139    }
3140
3141    async fn start(&mut self) -> Result<(), Self::Error> {
3142        AsyncWifi::start(self).await
3143    }
3144
3145    async fn stop(&mut self) -> Result<(), Self::Error> {
3146        AsyncWifi::stop(self).await
3147    }
3148
3149    async fn connect(&mut self) -> Result<(), Self::Error> {
3150        AsyncWifi::connect(self).await
3151    }
3152
3153    async fn disconnect(&mut self) -> Result<(), Self::Error> {
3154        AsyncWifi::disconnect(self).await
3155    }
3156
3157    async fn is_started(&self) -> Result<bool, Self::Error> {
3158        AsyncWifi::is_started(self)
3159    }
3160
3161    async fn is_connected(&self) -> Result<bool, Self::Error> {
3162        AsyncWifi::is_connected(self)
3163    }
3164
3165    async fn scan_n<const N: usize>(
3166        &mut self,
3167    ) -> Result<(heapless::Vec<AccessPointInfo, N>, usize), Self::Error> {
3168        AsyncWifi::scan_n(self).await
3169    }
3170
3171    #[cfg(feature = "alloc")]
3172    async fn scan(&mut self) -> Result<alloc::vec::Vec<AccessPointInfo>, Self::Error> {
3173        AsyncWifi::scan(self).await
3174    }
3175}
3176
3177#[cfg(esp_idf_comp_esp_netif_enabled)]
3178impl crate::netif::asynch::NetifStatus for EspWifi<'_> {
3179    async fn is_up(&self) -> Result<bool, EspError> {
3180        EspWifi::is_up(self)
3181    }
3182}
3183
3184#[cfg(all(feature = "alloc", esp_idf_comp_esp_timer_enabled))]
3185#[cfg(esp_idf_comp_esp_netif_enabled)]
3186impl<T> crate::netif::asynch::NetifStatus for AsyncWifi<T>
3187where
3188    T: NetifStatus,
3189{
3190    async fn is_up(&self) -> Result<bool, EspError> {
3191        AsyncWifi::is_up(self)
3192    }
3193}
3194
3195#[derive(Copy, Clone, Debug, Eq, PartialEq)]
3196enum WifiStaStatus {
3197    Stopped,
3198    Started,
3199    Connected,
3200}
3201
3202#[derive(Copy, Clone, Debug, Eq, PartialEq)]
3203enum WifiApStatus {
3204    Stopped,
3205    Started,
3206}
3207
3208#[derive(Copy, Clone, Debug, Eq, PartialEq)]
3209enum WifiScanStatus {
3210    Idle,
3211    Started,
3212    Done,
3213}
3214
3215#[derive(Debug)]
3216pub struct WpsConfig<'a> {
3217    pub wps_type: WpsType,
3218    pub factory_info: WpsFactoryInfo<'a>,
3219}
3220
3221impl TryFrom<&WpsConfig<'_>> for Newtype<esp_wps_config_t> {
3222    type Error = EspError;
3223
3224    fn try_from(config: &WpsConfig<'_>) -> Result<Self, Self::Error> {
3225        let factory_info = Newtype::<wps_factory_information_t>::try_from(&config.factory_info)?.0;
3226        Ok(Newtype(esp_wps_config_t {
3227            wps_type: config.wps_type.as_raw_type(),
3228            factory_info,
3229            #[cfg(not(esp_idf_version_major = "4"))]
3230            pin: config.wps_type.as_pin(),
3231        }))
3232    }
3233}
3234
3235#[derive(Clone, Debug)]
3236pub struct WpsFactoryInfo<'a> {
3237    pub manufacturer: &'a str,
3238    pub model_number: &'a str,
3239    pub model_name: &'a str,
3240    pub device_name: &'a str,
3241}
3242
3243impl TryFrom<&WpsFactoryInfo<'_>> for Newtype<wps_factory_information_t> {
3244    type Error = EspError;
3245
3246    fn try_from(info: &WpsFactoryInfo<'_>) -> Result<Self, Self::Error> {
3247        let mut result = Newtype(wps_factory_information_t {
3248            manufacturer: [0; 65],
3249            model_number: [0; 33],
3250            model_name: [0; 33],
3251            device_name: [0; 33],
3252        });
3253
3254        set_str(
3255            c_char_to_u8_slice_mut(&mut result.0.manufacturer),
3256            info.manufacturer,
3257        )?;
3258        set_str(
3259            c_char_to_u8_slice_mut(&mut result.0.model_number),
3260            info.model_number,
3261        )?;
3262        set_str(
3263            c_char_to_u8_slice_mut(&mut result.0.model_name),
3264            info.model_name,
3265        )?;
3266        set_str(
3267            c_char_to_u8_slice_mut(&mut result.0.device_name),
3268            info.device_name,
3269        )?;
3270
3271        Ok(result)
3272    }
3273}
3274
3275#[derive(Copy, Clone, Debug, Eq, PartialEq)]
3276pub enum WpsType {
3277    Pbc,
3278    Pin(u32),
3279}
3280
3281impl WpsType {
3282    fn as_raw_type(&self) -> wps_type_t {
3283        match self {
3284            WpsType::Pbc => wps_type_WPS_TYPE_PBC,
3285            WpsType::Pin(_) => wps_type_WPS_TYPE_PIN,
3286        }
3287    }
3288
3289    #[cfg(not(esp_idf_version_major = "4"))]
3290    fn as_pin(&self) -> [ffi::c_char; 9] {
3291        match self {
3292            WpsType::Pbc => [0; 9],
3293            WpsType::Pin(pin) => {
3294                let mut result = [0; 9];
3295                let mut pin = *pin;
3296                let mut rem: u32;
3297                for i in 0..8 {
3298                    rem = pin % 10;
3299                    pin /= 10;
3300                    result[7 - i] = (rem as ffi::c_char) + 48;
3301                }
3302                result
3303            }
3304        }
3305    }
3306}
3307
3308#[derive(Clone, Debug)]
3309pub enum WpsStatus {
3310    SuccessConnected,
3311    SuccessMultipleAccessPoints(alloc::vec::Vec<WpsCredentials>),
3312    Failure,
3313    Timeout,
3314    Pin(Option<u32>),
3315    PbcOverlap,
3316}
3317
3318impl TryFrom<&WifiEvent<'_>> for WpsStatus {
3319    type Error = EspError;
3320
3321    fn try_from(event: &WifiEvent) -> Result<Self, Self::Error> {
3322        match event {
3323            WifiEvent::StaWpsSuccess(credentials) => {
3324                if credentials.is_empty() {
3325                    Ok(WpsStatus::SuccessConnected)
3326                } else {
3327                    Ok(WpsStatus::SuccessMultipleAccessPoints(
3328                        credentials
3329                            .iter()
3330                            .filter_map(|c| c.try_into().ok())
3331                            .collect::<alloc::vec::Vec<WpsCredentials>>(),
3332                    ))
3333                }
3334            }
3335            WifiEvent::StaWpsFailed => Ok(WpsStatus::Failure),
3336            WifiEvent::StaWpsTimeout => Ok(WpsStatus::Timeout),
3337            WifiEvent::StaWpsPin(pin) => Ok(WpsStatus::Pin(*pin)),
3338            WifiEvent::StaWpsPbcOverlap => Ok(WpsStatus::PbcOverlap),
3339            _ => Err(EspError::from_infallible::<ESP_ERR_INVALID_ARG>()),
3340        }
3341    }
3342}