#![doc = concat!("- Station mode (aka STA mode or Wi-Fi client mode). ", chip_pretty!(), " connects to an access point.")]
#![doc = concat!("- AP mode (aka Soft-AP mode or Access Point mode). Stations connect to the ", chip_pretty!(),".")]
#![doc = concat!("- Station/AP-coexistence mode (", chip_pretty!(), " is concurrently an access point and a station connected to another access point).")]
use alloc::{borrow::ToOwned, collections::vec_deque::VecDeque, str, vec::Vec};
use core::{
fmt::{Debug, Write},
marker::PhantomData,
mem::MaybeUninit,
ptr::addr_of,
};
use docsplay::Display;
use enumset::{EnumSet, EnumSetType};
use esp_config::esp_config_int;
use esp_hal::system::Cpu;
#[cfg(all(any(feature = "esp-now", feature = "sniffer"), feature = "unstable"))]
use esp_hal::time::{Duration, Instant};
use esp_sync::NonReentrantMutex;
use event::EVENT_CHANNEL;
use portable_atomic::{AtomicUsize, Ordering};
use procmacros::BuilderLite;
pub(crate) use self::os_adapter::*;
#[cfg(all(feature = "sniffer", feature = "unstable"))]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
use self::sniffer::Sniffer;
#[cfg(feature = "wifi-eap")]
use self::sta::eap::EapStationConfig;
use self::{
ap::{AccessPointConfig, AccessPointInfo, convert_ap_info},
private::PacketBuffer,
scan::{FreeApListOnDrop, ScanConfig, ScanResults, ScanTypeConfig},
sta::StationConfig,
state::*,
};
use crate::{
RadioRefGuard,
hal::ram,
sys::{
c_types,
include::{self, *},
},
wifi::event::{EventInfo, WifiEvent},
};
pub mod ap;
unstable_module!(
#[cfg(all(feature = "csi", wifi_csi_supported))]
#[cfg_attr(docsrs, doc(cfg(feature = "csi")))]
pub mod csi;
pub mod event;
#[cfg(feature = "sniffer")]
#[cfg_attr(docsrs, doc(cfg(feature = "sniffer")))]
pub mod sniffer;
);
pub mod scan;
pub mod sta;
pub(crate) mod os_adapter;
pub(crate) mod state;
mod internal;
const MTU: usize = esp_config_int!(usize, "ESP_RADIO_CONFIG_WIFI_MTU");
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum AuthenticationMethod {
None,
Wep,
Wpa,
#[default]
Wpa2Personal,
WpaWpa2Personal,
Wpa2Enterprise,
Wpa3Personal,
Wpa2Wpa3Personal,
WapiPersonal,
Owe,
Wpa3EntSuiteB192Bit,
Wpa3ExtPsk,
Wpa3ExtPskMixed,
Dpp,
Wpa3Enterprise,
Wpa2Wpa3Enterprise,
WpaEnterprise,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, BuilderLite)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct Protocols {
_2_4: EnumSet<Protocol>,
#[cfg(wifi_has_5g)]
_5: EnumSet<Protocol>,
}
impl Default for Protocols {
fn default() -> Self {
Self {
_2_4: Protocol::B | Protocol::G | Protocol::N,
#[cfg(wifi_has_5g)]
_5: Protocol::AC | Protocol::A | Protocol::AX,
}
}
}
impl Protocols {
fn to_raw(self) -> wifi_protocols_t {
wifi_protocols_t {
ghz_2g: to_mask(self._2_4),
#[cfg(wifi_has_5g)]
ghz_5g: to_mask(self._5),
#[cfg(not(wifi_has_5g))]
ghz_5g: 0,
}
}
}
#[cfg_attr(docsrs, procmacros::doc_replace(
"hint_5g" => {
cfg(wifi_has_5g) => "The default protocol is AC/A/AX for band mode 5G.",
_ => ""
},
))]
#[derive(Debug, PartialOrd, Hash, EnumSetType)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Protocol {
B,
G,
N,
LR,
A,
AC,
AX,
}
impl Protocol {
fn to_mask(self) -> u16 {
let mask = match self {
Protocol::B => WIFI_PROTOCOL_11B,
Protocol::G => WIFI_PROTOCOL_11G,
Protocol::N => WIFI_PROTOCOL_11N,
Protocol::LR => WIFI_PROTOCOL_LR,
Protocol::A => WIFI_PROTOCOL_11A,
Protocol::AC => WIFI_PROTOCOL_11AC,
Protocol::AX => WIFI_PROTOCOL_11AX,
};
mask as _
}
}
fn to_mask(protocols: EnumSet<Protocol>) -> u16 {
protocols.iter().fold(0, |acc, p| acc | p.to_mask())
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, PartialOrd, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum SecondaryChannel {
#[default]
None,
Above,
Below,
}
impl SecondaryChannel {
fn from_raw(raw: u32) -> Self {
match raw {
0 => SecondaryChannel::None,
1 => SecondaryChannel::Above,
2 => SecondaryChannel::Below,
_ => panic!("Invalid secondary channel value: {}", raw),
}
}
#[cfg(any(feature = "sniffer", feature = "esp-now"))]
fn from_raw_or_default(raw: u32) -> Self {
match raw {
0 => SecondaryChannel::None,
1 => SecondaryChannel::Above,
2 => SecondaryChannel::Below,
_ => SecondaryChannel::None,
}
}
}
#[cfg_attr(docsrs, procmacros::doc_replace(
"default_band_mode" => {
cfg(wifi_has_5g) => "BandMode::Auto",
_ => "BandMode::_2_4G"
},
))]
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum BandMode {
#[cfg_attr(not(wifi_has_5g), default)]
_2_4G,
#[cfg(wifi_has_5g)]
_5G,
#[cfg_attr(wifi_has_5g, default)]
#[cfg(wifi_has_5g)]
Auto,
}
impl BandMode {
fn to_raw(&self) -> u32 {
match self {
BandMode::_2_4G => wifi_band_mode_t_WIFI_BAND_MODE_2G_ONLY,
#[cfg(wifi_has_5g)]
BandMode::_5G => wifi_band_mode_t_WIFI_BAND_MODE_5G_ONLY,
#[cfg(wifi_has_5g)]
BandMode::Auto => wifi_band_mode_t_WIFI_BAND_MODE_AUTO,
}
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Config {
Station(StationConfig),
AccessPoint(AccessPointConfig),
AccessPointStation(StationConfig, AccessPointConfig),
#[cfg(feature = "wifi-eap")]
EapStation(EapStationConfig),
}
impl Config {
fn validate(&self) -> Result<(), WifiError> {
match self {
Config::Station(station_configuration) => station_configuration.validate(),
Config::AccessPoint(access_point_configuration) => {
access_point_configuration.validate()
}
Config::AccessPointStation(station_configuration, access_point_configuration) => {
station_configuration.validate()?;
access_point_configuration.validate()
}
#[cfg(feature = "wifi-eap")]
Config::EapStation(eap_station_configuration) => eap_station_configuration.validate(),
}
}
}
impl AuthenticationMethod {
fn to_raw(self) -> wifi_auth_mode_t {
match self {
AuthenticationMethod::None => include::wifi_auth_mode_t_WIFI_AUTH_OPEN,
AuthenticationMethod::Wep => include::wifi_auth_mode_t_WIFI_AUTH_WEP,
AuthenticationMethod::Wpa => include::wifi_auth_mode_t_WIFI_AUTH_WPA_PSK,
AuthenticationMethod::Wpa2Personal => include::wifi_auth_mode_t_WIFI_AUTH_WPA2_PSK,
AuthenticationMethod::WpaWpa2Personal => {
include::wifi_auth_mode_t_WIFI_AUTH_WPA_WPA2_PSK
}
AuthenticationMethod::Wpa2Enterprise => {
include::wifi_auth_mode_t_WIFI_AUTH_WPA2_ENTERPRISE
}
AuthenticationMethod::Wpa3Personal => include::wifi_auth_mode_t_WIFI_AUTH_WPA3_PSK,
AuthenticationMethod::Wpa2Wpa3Personal => {
include::wifi_auth_mode_t_WIFI_AUTH_WPA2_WPA3_PSK
}
AuthenticationMethod::WapiPersonal => include::wifi_auth_mode_t_WIFI_AUTH_WAPI_PSK,
AuthenticationMethod::Owe => include::wifi_auth_mode_t_WIFI_AUTH_OWE,
AuthenticationMethod::Wpa3EntSuiteB192Bit => {
include::wifi_auth_mode_t_WIFI_AUTH_WPA3_ENT_192
}
AuthenticationMethod::Wpa3ExtPsk => include::wifi_auth_mode_t_WIFI_AUTH_WPA3_EXT_PSK,
AuthenticationMethod::Wpa3ExtPskMixed => {
include::wifi_auth_mode_t_WIFI_AUTH_WPA3_EXT_PSK_MIXED_MODE
}
AuthenticationMethod::Dpp => include::wifi_auth_mode_t_WIFI_AUTH_DPP,
AuthenticationMethod::Wpa3Enterprise => {
include::wifi_auth_mode_t_WIFI_AUTH_WPA3_ENTERPRISE
}
AuthenticationMethod::Wpa2Wpa3Enterprise => {
include::wifi_auth_mode_t_WIFI_AUTH_WPA2_WPA3_ENTERPRISE
}
AuthenticationMethod::WpaEnterprise => {
include::wifi_auth_mode_t_WIFI_AUTH_WPA_ENTERPRISE
}
}
}
fn from_raw(raw: wifi_auth_mode_t) -> Self {
match raw {
include::wifi_auth_mode_t_WIFI_AUTH_OPEN => AuthenticationMethod::None,
include::wifi_auth_mode_t_WIFI_AUTH_WEP => AuthenticationMethod::Wep,
include::wifi_auth_mode_t_WIFI_AUTH_WPA_PSK => AuthenticationMethod::Wpa,
include::wifi_auth_mode_t_WIFI_AUTH_WPA2_PSK => AuthenticationMethod::Wpa2Personal,
include::wifi_auth_mode_t_WIFI_AUTH_WPA_WPA2_PSK => {
AuthenticationMethod::WpaWpa2Personal
}
include::wifi_auth_mode_t_WIFI_AUTH_WPA2_ENTERPRISE => {
AuthenticationMethod::Wpa2Enterprise
}
include::wifi_auth_mode_t_WIFI_AUTH_WPA3_PSK => AuthenticationMethod::Wpa3Personal,
include::wifi_auth_mode_t_WIFI_AUTH_WPA2_WPA3_PSK => {
AuthenticationMethod::Wpa2Wpa3Personal
}
include::wifi_auth_mode_t_WIFI_AUTH_WAPI_PSK => AuthenticationMethod::WapiPersonal,
include::wifi_auth_mode_t_WIFI_AUTH_OWE => AuthenticationMethod::Owe,
include::wifi_auth_mode_t_WIFI_AUTH_WPA3_ENT_192 => {
AuthenticationMethod::Wpa3EntSuiteB192Bit
}
include::wifi_auth_mode_t_WIFI_AUTH_WPA3_EXT_PSK => AuthenticationMethod::Wpa3ExtPsk,
include::wifi_auth_mode_t_WIFI_AUTH_WPA3_EXT_PSK_MIXED_MODE => {
AuthenticationMethod::Wpa3ExtPskMixed
}
include::wifi_auth_mode_t_WIFI_AUTH_DPP => AuthenticationMethod::Dpp,
include::wifi_auth_mode_t_WIFI_AUTH_WPA3_ENTERPRISE => {
AuthenticationMethod::Wpa3Enterprise
}
include::wifi_auth_mode_t_WIFI_AUTH_WPA2_WPA3_ENTERPRISE => {
AuthenticationMethod::Wpa2Wpa3Enterprise
}
include::wifi_auth_mode_t_WIFI_AUTH_WPA_ENTERPRISE => {
AuthenticationMethod::WpaEnterprise
}
_ => AuthenticationMethod::None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
enum WifiMode {
Station,
AccessPoint,
AccessPointStation,
}
impl WifiMode {
pub(crate) fn current() -> Result<Self, WifiError> {
let mut mode = wifi_mode_t_WIFI_MODE_NULL;
esp_wifi_result!(unsafe { esp_wifi_get_mode(&mut mode) })?;
Ok(Self::from_raw(mode))
}
fn is_station(&self) -> bool {
match self {
Self::Station | Self::AccessPointStation => true,
Self::AccessPoint => false,
}
}
fn is_access_point(&self) -> bool {
match self {
Self::Station => false,
Self::AccessPoint | Self::AccessPointStation => true,
}
}
fn from_raw(value: wifi_mode_t) -> Self {
#[allow(non_upper_case_globals)]
match value {
include::wifi_mode_t_WIFI_MODE_STA => Self::Station,
include::wifi_mode_t_WIFI_MODE_AP => Self::AccessPoint,
include::wifi_mode_t_WIFI_MODE_APSTA => Self::AccessPointStation,
_ => panic!("Invalid wifi mode value: {}", value),
}
}
}
impl From<&Config> for WifiMode {
fn from(config: &Config) -> Self {
match config {
Config::AccessPoint(_) => Self::AccessPoint,
Config::Station(_) => Self::Station,
Config::AccessPointStation(_, _) => Self::AccessPointStation,
#[cfg(feature = "wifi-eap")]
Config::EapStation(_) => Self::Station,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum DisconnectReason {
Unspecified,
AuthenticationExpired,
AuthenticationLeave,
DisassociatedDueToInactivity,
AssociationTooMany,
Class2FrameFromNonAuthenticatedStation,
Class3FrameFromNonAssociatedStation,
AssociationLeave,
AssociationNotAuthenticated,
DisassociatedPowerCapabilityBad,
DisassociatedUnsupportedChannel,
BssTransitionDisassociated,
IeInvalid,
MicFailure,
FourWayHandshakeTimeout,
GroupKeyUpdateTimeout,
IeIn4wayDiffers,
GroupCipherInvalid,
PairwiseCipherInvalid,
AkmpInvalid,
UnsupportedRsnIeVersion,
InvalidRsnIeCapabilities,
_802_1xAuthenticationFailed,
CipherSuiteRejected,
TdlsPeerUnreachable,
TdlsUnspecified,
SspRequestedDisassociation,
NoSspRoamingAgreement,
BadCipherOrAkm,
NotAuthorizedThisLocation,
ServiceChangePercludesTs,
UnspecifiedQos,
NotEnoughBandwidth,
MissingAcks,
ExceededTxOp,
StationLeaving,
EndBlockAck,
UnknownBlockAck,
Timeout,
PeerInitiated,
AccessPointInitiatedDisassociation,
InvalidFtActionFrameCount,
InvalidPmkid,
InvalidMde,
InvalidFte,
TransmissionLinkEstablishmentFailed,
AlterativeChannelOccupied,
BeaconTimeout,
NoAccessPointFound,
AuthenticationFailed,
AssociationFailed,
HandshakeTimeout,
ConnectionFailed,
AccessPointTsfReset,
Roaming,
AssociationComebackTimeTooLong,
SaQueryTimeout,
NoAccessPointFoundWithCompatibleSecurity,
NoAccessPointFoundInAuthmodeThreshold,
NoAccessPointFoundInRssiThreshold,
}
impl DisconnectReason {
fn from_raw(id: u16) -> Self {
match id {
1 => Self::Unspecified,
2 => Self::AuthenticationExpired,
3 => Self::AuthenticationLeave,
4 => Self::DisassociatedDueToInactivity,
5 => Self::AssociationTooMany,
6 => Self::Class2FrameFromNonAuthenticatedStation,
7 => Self::Class3FrameFromNonAssociatedStation,
8 => Self::AssociationLeave,
9 => Self::AssociationNotAuthenticated,
10 => Self::DisassociatedPowerCapabilityBad,
11 => Self::DisassociatedUnsupportedChannel,
12 => Self::BssTransitionDisassociated,
13 => Self::IeInvalid,
14 => Self::MicFailure,
15 => Self::FourWayHandshakeTimeout,
16 => Self::GroupKeyUpdateTimeout,
17 => Self::IeIn4wayDiffers,
18 => Self::GroupCipherInvalid,
19 => Self::PairwiseCipherInvalid,
20 => Self::AkmpInvalid,
21 => Self::UnsupportedRsnIeVersion,
22 => Self::InvalidRsnIeCapabilities,
23 => Self::_802_1xAuthenticationFailed,
24 => Self::CipherSuiteRejected,
25 => Self::TdlsPeerUnreachable,
26 => Self::TdlsUnspecified,
27 => Self::SspRequestedDisassociation,
28 => Self::NoSspRoamingAgreement,
29 => Self::BadCipherOrAkm,
30 => Self::NotAuthorizedThisLocation,
31 => Self::ServiceChangePercludesTs,
32 => Self::UnspecifiedQos,
33 => Self::NotEnoughBandwidth,
34 => Self::MissingAcks,
35 => Self::ExceededTxOp,
36 => Self::StationLeaving,
37 => Self::EndBlockAck,
38 => Self::UnknownBlockAck,
39 => Self::Timeout,
46 => Self::PeerInitiated,
47 => Self::AccessPointInitiatedDisassociation,
48 => Self::InvalidFtActionFrameCount,
49 => Self::InvalidPmkid,
50 => Self::InvalidMde,
51 => Self::InvalidFte,
67 => Self::TransmissionLinkEstablishmentFailed,
68 => Self::AlterativeChannelOccupied,
200 => Self::BeaconTimeout,
201 => Self::NoAccessPointFound,
202 => Self::AuthenticationFailed,
203 => Self::AssociationFailed,
204 => Self::HandshakeTimeout,
205 => Self::ConnectionFailed,
206 => Self::AccessPointTsfReset,
207 => Self::Roaming,
208 => Self::AssociationComebackTimeTooLong,
209 => Self::SaQueryTimeout,
210 => Self::NoAccessPointFoundWithCompatibleSecurity,
211 => Self::NoAccessPointFoundInAuthmodeThreshold,
212 => Self::NoAccessPointFoundInRssiThreshold,
_ => Self::Unspecified,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Ssid {
ssid: [u8; 32],
len: u8,
}
impl Ssid {
pub(crate) fn new(ssid: &str) -> Self {
let mut ssid_bytes = [0u8; 32];
let bytes = ssid.as_bytes();
let len = usize::min(32, bytes.len());
ssid_bytes[..len].copy_from_slice(bytes);
Self::from_raw(&ssid_bytes, len as u8)
}
pub(crate) fn from_raw(ssid: &[u8], len: u8) -> Self {
let mut ssid_bytes = [0u8; 32];
let len = usize::min(32, len as usize);
ssid_bytes[..len].copy_from_slice(&ssid[..len]);
Self {
ssid: ssid_bytes,
len: len as u8,
}
}
pub(crate) fn as_bytes(&self) -> &[u8] {
&self.ssid[..self.len as usize]
}
pub fn len(&self) -> usize {
self.len as usize
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn as_str(&self) -> &str {
let part = &self.ssid[..self.len as usize];
match str::from_utf8(part) {
Ok(s) => s,
Err(e) => {
let (valid, _) = part.split_at(e.valid_up_to());
unsafe { str::from_utf8_unchecked(valid) }
}
}
}
}
impl Debug for Ssid {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_char('"')?;
f.write_str(self.as_str())?;
f.write_char('"')
}
}
impl From<alloc::string::String> for Ssid {
fn from(ssid: alloc::string::String) -> Self {
Self::new(&ssid)
}
}
impl From<&str> for Ssid {
fn from(ssid: &str) -> Self {
Self::new(ssid)
}
}
impl From<&[u8]> for Ssid {
fn from(ssid: &[u8]) -> Self {
Self::from_raw(ssid, ssid.len() as u8)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct ConnectedStationInfo {
pub ssid: Ssid,
pub bssid: [u8; 6],
pub channel: u8,
pub authmode: AuthenticationMethod,
pub aid: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct DisconnectedStationInfo {
pub ssid: Ssid,
pub bssid: [u8; 6],
pub reason: DisconnectReason,
pub rssi: i8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct AccessPointStationConnectedInfo {
pub mac: [u8; 6],
pub aid: u16,
pub is_mesh_child: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct AccessPointStationDisconnectedInfo {
pub mac: [u8; 6],
pub aid: u16,
pub is_mesh_child: bool,
pub reason: DisconnectReason,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum AccessPointStationEventInfo {
Connected(AccessPointStationConnectedInfo),
Disconnected(AccessPointStationDisconnectedInfo),
}
static RX_QUEUE_SIZE: AtomicUsize = AtomicUsize::new(0);
static TX_QUEUE_SIZE: AtomicUsize = AtomicUsize::new(0);
pub(crate) static DATA_QUEUE_RX_AP: NonReentrantMutex<VecDeque<PacketBuffer>> =
NonReentrantMutex::new(VecDeque::new());
pub(crate) static DATA_QUEUE_RX_STA: NonReentrantMutex<VecDeque<PacketBuffer>> =
NonReentrantMutex::new(VecDeque::new());
#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum WifiError {
Disconnected(DisconnectedStationInfo),
Unsupported,
InvalidArguments,
Failed,
OutOfMemory,
InvalidSsid,
InvalidPassword,
NotConnected,
}
impl WifiError {
fn from_error_code(code: i32) -> Self {
if crate::sys::include::ESP_FAIL == code {
return WifiError::Failed;
}
match code as u32 {
crate::sys::include::ESP_ERR_NO_MEM => WifiError::OutOfMemory,
crate::sys::include::ESP_ERR_INVALID_ARG => WifiError::InvalidArguments,
crate::sys::include::ESP_ERR_WIFI_SSID => WifiError::InvalidSsid,
crate::sys::include::ESP_ERR_WIFI_PASSWORD => WifiError::InvalidPassword,
crate::sys::include::ESP_ERR_WIFI_NOT_CONNECT => WifiError::NotConnected,
_ => panic!("Unknown error code: {}", code),
}
}
}
impl core::error::Error for WifiError {}
#[cfg(esp32)]
fn set_mac_time_update_cb(_wifi: crate::hal::peripherals::WIFI<'_>) {
use crate::sys::include::esp_wifi_internal_update_mac_time;
unsafe {
esp_phy::set_mac_time_update_cb(|duration| {
esp_wifi_internal_update_mac_time(duration.as_micros() as u32);
});
}
}
pub(crate) fn wifi_init(_wifi: crate::hal::peripherals::WIFI<'_>) -> Result<(), WifiError> {
#[cfg(esp32)]
set_mac_time_update_cb(_wifi);
unsafe {
#[cfg(feature = "coex")]
esp_wifi_result!(coex_init())?;
esp_wifi_result!(esp_wifi_init_internal(addr_of!(internal::G_CONFIG)))?;
esp_wifi_result!(esp_wifi_set_mode(wifi_mode_t_WIFI_MODE_NULL))?;
esp_wifi_result!(esp_supplicant_init())?;
esp_wifi_result!(esp_wifi_set_tx_done_cb(Some(esp_wifi_tx_done_cb)))?;
esp_wifi_result!(esp_wifi_internal_reg_rxcb(
esp_interface_t_ESP_IF_WIFI_STA,
Some(recv_cb_sta)
))?;
esp_wifi_result!(esp_wifi_internal_reg_rxcb(
esp_interface_t_ESP_IF_WIFI_AP,
Some(recv_cb_ap)
))?;
Ok(())
}
}
#[cfg(feature = "coex")]
pub(crate) fn coex_initialize() -> i32 {
debug!("call coex-initialize");
unsafe {
let res = crate::sys::include::esp_coex_adapter_register(
core::ptr::addr_of_mut!(internal::G_COEX_ADAPTER_FUNCS).cast(),
);
if res != 0 {
error!("Error: esp_coex_adapter_register {}", res);
return res;
}
let res = crate::sys::include::coex_pre_init();
if res != 0 {
error!("Error: coex_pre_init {}", res);
return res;
}
0
}
}
pub(crate) unsafe extern "C" fn coex_init() -> i32 {
debug!("coex-init");
cfg_if::cfg_if! {
if #[cfg(feature = "coex")] {
unsafe { crate::sys::include::coex_init() }
} else {
0
}
}
}
fn wifi_deinit() -> Result<(), crate::WifiError> {
esp_wifi_result!(unsafe { esp_wifi_stop() })?;
esp_wifi_result!(unsafe { esp_wifi_deinit_internal() })?;
esp_wifi_result!(unsafe { esp_supplicant_deinit() })?;
Ok(())
}
unsafe extern "C" fn recv_cb_sta(
buffer: *mut c_types::c_void,
len: u16,
eb: *mut c_types::c_void,
) -> esp_err_t {
let packet = PacketBuffer { buffer, len, eb };
match DATA_QUEUE_RX_STA.with(|queue| {
if queue.len() < RX_QUEUE_SIZE.load(Ordering::Relaxed) {
queue.push_back(packet);
Ok(())
} else {
Err(packet)
}
}) {
Ok(()) => {
embassy::STA_RECEIVE_WAKER.wake();
include::ESP_OK as esp_err_t
}
_ => {
debug!("RX QUEUE FULL");
include::ESP_ERR_NO_MEM as esp_err_t
}
}
}
unsafe extern "C" fn recv_cb_ap(
buffer: *mut c_types::c_void,
len: u16,
eb: *mut c_types::c_void,
) -> esp_err_t {
let packet = PacketBuffer { buffer, len, eb };
match DATA_QUEUE_RX_AP.with(|queue| {
if queue.len() < RX_QUEUE_SIZE.load(Ordering::Relaxed) {
queue.push_back(packet);
Ok(())
} else {
Err(packet)
}
}) {
Ok(()) => {
embassy::AP_RECEIVE_WAKER.wake();
include::ESP_OK as esp_err_t
}
_ => {
debug!("RX QUEUE FULL");
include::ESP_ERR_NO_MEM as esp_err_t
}
}
}
pub(crate) static WIFI_TX_INFLIGHT: AtomicUsize = AtomicUsize::new(0);
fn decrement_inflight_counter() {
unwrap!(
WIFI_TX_INFLIGHT.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| {
Some(x.saturating_sub(1))
})
);
}
#[ram]
unsafe extern "C" fn esp_wifi_tx_done_cb(
_ifidx: u8,
_data: *mut u8,
_data_len: *mut u16,
_tx_status: bool,
) {
trace!("esp_wifi_tx_done_cb");
decrement_inflight_counter();
embassy::TRANSMIT_WAKER.wake();
}
pub(crate) fn wifi_start_scan(
block: bool,
ScanConfig {
ssid,
mut bssid,
channel,
show_hidden,
scan_type,
..
}: ScanConfig,
) -> i32 {
scan_type.validate();
let (scan_time, scan_type) = match scan_type {
ScanTypeConfig::Active { min, max } => (
wifi_scan_time_t {
active: wifi_active_scan_time_t {
min: min.as_millis() as u32,
max: max.as_millis() as u32,
},
passive: 0,
},
wifi_scan_type_t_WIFI_SCAN_TYPE_ACTIVE,
),
ScanTypeConfig::Passive(dur) => (
wifi_scan_time_t {
active: wifi_active_scan_time_t { min: 0, max: 0 },
passive: dur.as_millis() as u32,
},
wifi_scan_type_t_WIFI_SCAN_TYPE_PASSIVE,
),
};
let mut ssid_buf = ssid.map(|m| {
let mut buf = Vec::from_iter(m.as_bytes().to_owned());
buf.push(b'\0');
buf
});
let ssid = ssid_buf
.as_mut()
.map(|e| e.as_mut_ptr())
.unwrap_or_else(core::ptr::null_mut);
let bssid = bssid
.as_mut()
.map(|e| e.as_mut_ptr())
.unwrap_or_else(core::ptr::null_mut);
let scan_config = wifi_scan_config_t {
ssid,
bssid,
channel: channel.unwrap_or(0),
show_hidden,
scan_type,
scan_time,
home_chan_dwell_time: 0,
channel_bitmap: wifi_scan_channel_bitmap_t {
ghz_2_channels: 0,
ghz_5_channels: 0,
},
coex_background_scan: false,
};
unsafe { esp_wifi_scan_start(&scan_config, block) }
}
mod private {
use super::*;
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PacketBuffer {
pub(crate) buffer: *mut c_types::c_void,
pub(crate) len: u16,
pub(crate) eb: *mut c_types::c_void,
}
unsafe impl Send for PacketBuffer {}
impl Drop for PacketBuffer {
fn drop(&mut self) {
trace!("Dropping PacketBuffer, freeing memory");
unsafe { esp_wifi_internal_free_rx_buffer(self.eb) };
}
}
impl PacketBuffer {
pub fn as_slice_mut(&mut self) -> &mut [u8] {
unsafe { core::slice::from_raw_parts_mut(self.buffer as *mut u8, self.len as usize) }
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum InterfaceType {
Station,
AccessPoint,
}
impl InterfaceType {
fn mac_address(&self) -> [u8; 6] {
use esp_hal::efuse::InterfaceMacAddress;
let mac = match self {
InterfaceType::Station => {
esp_hal::efuse::interface_mac_address(InterfaceMacAddress::Station)
}
InterfaceType::AccessPoint => {
esp_hal::efuse::interface_mac_address(InterfaceMacAddress::AccessPoint)
}
};
let mut out = [0u8; 6];
out.copy_from_slice(mac.as_bytes());
out
}
fn data_queue_rx(&self) -> &'static NonReentrantMutex<VecDeque<PacketBuffer>> {
match self {
InterfaceType::Station => &DATA_QUEUE_RX_STA,
InterfaceType::AccessPoint => &DATA_QUEUE_RX_AP,
}
}
fn can_send(&self) -> bool {
WIFI_TX_INFLIGHT.load(Ordering::SeqCst) < TX_QUEUE_SIZE.load(Ordering::Relaxed)
}
fn increase_in_flight_counter(&self) {
WIFI_TX_INFLIGHT.fetch_add(1, Ordering::SeqCst);
}
fn tx_token(&self) -> Option<WifiTxToken> {
if !self.can_send() {
crate::preempt::yield_task();
}
if self.can_send() {
if self.link_state() == embassy_net_driver::LinkState::Up {
return Some(WifiTxToken { mode: *self });
}
}
None
}
fn rx_token(&self) -> Option<(WifiRxToken, WifiTxToken)> {
let is_empty = self.data_queue_rx().with(|q| q.is_empty());
if is_empty || !self.can_send() {
crate::preempt::yield_task();
}
let is_empty = is_empty && self.data_queue_rx().with(|q| q.is_empty());
if !is_empty {
self.tx_token().map(|tx| (WifiRxToken { mode: *self }, tx))
} else {
None
}
}
fn interface(&self) -> wifi_interface_t {
match self {
InterfaceType::Station => wifi_interface_t_WIFI_IF_STA,
InterfaceType::AccessPoint => wifi_interface_t_WIFI_IF_AP,
}
}
fn register_transmit_waker(&self, cx: &mut core::task::Context<'_>) {
embassy::TRANSMIT_WAKER.register(cx.waker())
}
fn register_receive_waker(&self, cx: &mut core::task::Context<'_>) {
match self {
InterfaceType::Station => embassy::STA_RECEIVE_WAKER.register(cx.waker()),
InterfaceType::AccessPoint => embassy::AP_RECEIVE_WAKER.register(cx.waker()),
}
}
fn register_link_state_waker(&self, cx: &mut core::task::Context<'_>) {
match self {
InterfaceType::Station => embassy::STA_LINK_STATE_WAKER.register(cx.waker()),
InterfaceType::AccessPoint => embassy::AP_LINK_STATE_WAKER.register(cx.waker()),
}
}
fn link_state(&self) -> embassy_net_driver::LinkState {
let is_up = match self {
InterfaceType::Station => {
matches!(station_state(), WifiStationState::Connected)
}
InterfaceType::AccessPoint => {
matches!(access_point_state(), WifiAccessPointState::Started)
}
};
if is_up {
embassy_net_driver::LinkState::Up
} else {
embassy_net_driver::LinkState::Down
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct Interface<'d> {
_phantom: PhantomData<&'d ()>,
mode: InterfaceType,
}
impl Interface<'_> {
#[procmacros::doc_replace]
pub fn mac_address(&self) -> [u8; 6] {
self.mode.mac_address()
}
#[doc(hidden)]
pub fn receive(&mut self) -> Option<(WifiRxToken, WifiTxToken)> {
self.mode.rx_token()
}
#[doc(hidden)]
pub fn transmit(&mut self) -> Option<WifiTxToken> {
self.mode.tx_token()
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, BuilderLite)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct Bandwidths {
_2_4: Bandwidth,
#[cfg(wifi_has_5g)]
_5: Bandwidth,
}
impl Bandwidths {
fn to_raw(self) -> wifi_bandwidths_t {
wifi_bandwidths_t {
ghz_2g: self._2_4.to_raw(),
#[cfg(wifi_has_5g)]
ghz_5g: self._5.to_raw(),
#[cfg(not(wifi_has_5g))]
ghz_5g: 0,
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[allow(
clippy::enum_variant_names,
reason = "MHz suffix indicates physical unit."
)]
#[non_exhaustive]
pub enum Bandwidth {
_20MHz,
_40MHz,
_80MHz,
_160MHz,
_80_80MHz,
}
impl Bandwidth {
fn to_raw(self) -> wifi_bandwidth_t {
match self {
Bandwidth::_20MHz => wifi_bandwidth_t_WIFI_BW_HT20,
Bandwidth::_40MHz => wifi_bandwidth_t_WIFI_BW_HT40,
Bandwidth::_80MHz => wifi_bandwidth_t_WIFI_BW80,
Bandwidth::_160MHz => wifi_bandwidth_t_WIFI_BW160,
Bandwidth::_80_80MHz => wifi_bandwidth_t_WIFI_BW80_BW80,
}
}
fn from_raw(raw: wifi_bandwidth_t) -> Self {
match raw {
raw if raw == wifi_bandwidth_t_WIFI_BW_HT20 => Bandwidth::_20MHz,
raw if raw == wifi_bandwidth_t_WIFI_BW_HT40 => Bandwidth::_40MHz,
raw if raw == wifi_bandwidth_t_WIFI_BW80 => Bandwidth::_80MHz,
raw if raw == wifi_bandwidth_t_WIFI_BW160 => Bandwidth::_160MHz,
raw if raw == wifi_bandwidth_t_WIFI_BW80_BW80 => Bandwidth::_80_80MHz,
_ => Bandwidth::_20MHz,
}
}
}
#[cfg(wifi_mac_version = "1")]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg(all(any(feature = "esp-now", feature = "sniffer"), feature = "unstable"))]
#[instability::unstable]
pub struct RxControlInfo {
pub rssi: i32,
pub rate: u32,
pub sig_mode: u32,
pub mcs: u32,
pub cwb: u32,
pub smoothing: u32,
pub not_sounding: u32,
pub aggregation: u32,
pub stbc: u32,
pub fec_coding: u32,
pub sgi: u32,
pub ampdu_cnt: u32,
pub channel: u32,
pub secondary_channel: SecondaryChannel,
pub timestamp: Instant,
pub noise_floor: i32,
pub ant: u32,
pub sig_len: u32,
pub rx_state: u32,
}
#[cfg(wifi_mac_version = "2")]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg(all(any(feature = "esp-now", feature = "sniffer"), feature = "unstable"))]
#[instability::unstable]
pub struct RxControlInfo {
pub rssi: i32,
pub rate: u32,
pub sig_len: u32,
pub rx_state: u32,
pub dump_len: u32,
pub he_sigb_len: u32,
pub cur_single_mpdu: u32,
pub cur_bb_format: u32,
pub rx_channel_estimate_info_vld: u32,
pub rx_channel_estimate_len: u32,
pub secondary_channel: SecondaryChannel,
pub channel: u32,
pub noise_floor: i32,
pub is_group: u32,
pub rxend_state: u32,
pub rxmatch3: u32,
pub rxmatch2: u32,
pub rxmatch1: u32,
pub rxmatch0: u32,
pub timestamp: Instant,
}
#[cfg(wifi_mac_version = "3")]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg(all(any(feature = "esp-now", feature = "sniffer"), feature = "unstable"))]
#[instability::unstable]
pub struct RxControlInfo {
pub rssi: i32,
pub rate: u32,
pub sig_len: u32,
pub rx_state: u32,
pub dump_len: u32,
pub he_sigb_len: u32,
pub cur_bb_format: u32,
pub rx_channel_estimate_info_vld: u32,
pub rx_channel_estimate_len: u32,
pub secondary_channel: SecondaryChannel,
pub channel: u32,
pub noise_floor: i32,
pub is_group: u32,
pub rxend_state: u32,
pub rxmatch3: u32,
pub rxmatch2: u32,
pub rxmatch1: u32,
pub rxmatch0: u32,
pub timestamp: Instant,
}
#[cfg(all(any(feature = "esp-now", feature = "sniffer"), feature = "unstable"))]
impl RxControlInfo {
pub(super) unsafe fn from_raw(rx_cntl: *const wifi_pkt_rx_ctrl_t) -> Self {
#[cfg(wifi_mac_version = "1")]
let rx_control_info = unsafe {
RxControlInfo {
rssi: (*rx_cntl).rssi(),
rate: (*rx_cntl).rate(),
sig_mode: (*rx_cntl).sig_mode(),
mcs: (*rx_cntl).mcs(),
cwb: (*rx_cntl).cwb(),
smoothing: (*rx_cntl).smoothing(),
not_sounding: (*rx_cntl).not_sounding(),
aggregation: (*rx_cntl).aggregation(),
stbc: (*rx_cntl).stbc(),
fec_coding: (*rx_cntl).fec_coding(),
sgi: (*rx_cntl).sgi(),
ampdu_cnt: (*rx_cntl).ampdu_cnt(),
channel: (*rx_cntl).channel(),
secondary_channel: SecondaryChannel::from_raw_or_default(
(*rx_cntl).secondary_channel(),
),
timestamp: Instant::EPOCH + Duration::from_micros((*rx_cntl).timestamp() as u64),
noise_floor: (*rx_cntl).noise_floor(),
ant: (*rx_cntl).ant(),
sig_len: (*rx_cntl).sig_len(),
rx_state: (*rx_cntl).rx_state(),
}
};
#[cfg(wifi_mac_version = "2")]
let rx_control_info = unsafe {
RxControlInfo {
rssi: (*rx_cntl).rssi(),
rate: (*rx_cntl).rate(),
sig_len: (*rx_cntl).sig_len(),
rx_state: (*rx_cntl).rx_state(),
dump_len: (*rx_cntl).dump_len(),
he_sigb_len: (*rx_cntl).he_sigb_len(),
cur_single_mpdu: (*rx_cntl).cur_single_mpdu(),
cur_bb_format: (*rx_cntl).cur_bb_format(),
rx_channel_estimate_info_vld: (*rx_cntl).rx_channel_estimate_info_vld(),
rx_channel_estimate_len: (*rx_cntl).rx_channel_estimate_len(),
secondary_channel: SecondaryChannel::from_raw_or_default((*rx_cntl).second()),
channel: (*rx_cntl).channel(),
noise_floor: (*rx_cntl).noise_floor() as _,
is_group: (*rx_cntl).is_group(),
rxend_state: (*rx_cntl).rxend_state(),
rxmatch3: (*rx_cntl).rxmatch3(),
rxmatch2: (*rx_cntl).rxmatch2(),
rxmatch1: (*rx_cntl).rxmatch1(),
rxmatch0: (*rx_cntl).rxmatch0(),
timestamp: Instant::EPOCH + Duration::from_micros((*rx_cntl).timestamp() as u64),
}
};
#[cfg(wifi_mac_version = "3")]
let rx_control_info = unsafe {
RxControlInfo {
rssi: (*rx_cntl).rssi(),
rate: (*rx_cntl).rate(),
sig_len: (*rx_cntl).sig_len(),
rx_state: (*rx_cntl).rx_state(),
dump_len: (*rx_cntl).dump_len(),
he_sigb_len: (*rx_cntl).sigb_len(),
cur_bb_format: (*rx_cntl).cur_bb_format(),
rx_channel_estimate_info_vld: (*rx_cntl).rx_channel_estimate_info_vld(),
rx_channel_estimate_len: (*rx_cntl).rx_channel_estimate_len(),
secondary_channel: SecondaryChannel::from_raw_or_default((*rx_cntl).second()),
channel: (*rx_cntl).channel(),
noise_floor: (*rx_cntl).noise_floor() as _,
is_group: (*rx_cntl).is_group(),
rxend_state: (*rx_cntl).rxend_state(),
rxmatch3: (*rx_cntl).rxmatch3(),
rxmatch2: (*rx_cntl).rxmatch2(),
rxmatch1: (*rx_cntl).rxmatch1(),
rxmatch0: (*rx_cntl).rxmatch0(),
timestamp: Instant::EPOCH + Duration::from_micros((*rx_cntl).timestamp() as u64),
}
};
rx_control_info
}
}
#[doc(hidden)]
pub struct WifiRxToken {
mode: InterfaceType,
}
impl WifiRxToken {
pub fn consume_token<R, F>(self, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
let mut data = self.mode.data_queue_rx().with(|queue| {
unwrap!(
queue.pop_front(),
"unreachable: transmit()/receive() ensures there is a packet to process"
)
});
let buffer = data.as_slice_mut();
dump_packet_info(buffer);
f(buffer)
}
}
#[doc(hidden)]
pub struct WifiTxToken {
mode: InterfaceType,
}
impl WifiTxToken {
pub fn consume_token<R, F>(self, len: usize, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
self.mode.increase_in_flight_counter();
let mut buffer: [u8; MTU] = [0u8; MTU];
let buffer = &mut buffer[..len];
let res = f(buffer);
esp_wifi_send_data(self.mode.interface(), buffer);
res
}
}
pub(crate) fn esp_wifi_send_data(interface: wifi_interface_t, data: &mut [u8]) {
state::locked(|| {
if (interface == wifi_interface_t_WIFI_IF_STA
&& !matches!(station_state(), WifiStationState::Connected))
|| (interface == wifi_interface_t_WIFI_IF_AP
&& !matches!(access_point_state(), WifiAccessPointState::Started))
{
return;
}
trace!("sending... {} bytes", data.len());
dump_packet_info(data);
let len = data.len() as u16;
let ptr = data.as_mut_ptr().cast();
let res = unsafe { esp_wifi_internal_tx(interface, ptr, len) };
if res != include::ESP_OK as i32 {
warn!("esp_wifi_internal_tx returned error: {}", res);
decrement_inflight_counter();
}
})
}
fn dump_packet_info(_buffer: &mut [u8]) {
#[cfg(dump_packets)]
{
info!("@WIFIFRAME {:?}", _buffer);
}
}
macro_rules! esp_wifi_result {
($value:expr) => {{
use num_traits::FromPrimitive;
let result = $value;
if result != $crate::sys::include::ESP_OK as i32 {
let error = unwrap!(FromPrimitive::from_i32(result));
warn!(
"{} returned an error: {:?} ({}). If this error is unmapped, please open an issue at <https://github.com/esp-rs/esp-hal/issues>.",
stringify!($value),
error,
result
);
Err(WifiError::from_error_code(error))
} else {
Ok::<(), WifiError>(())
}
}};
}
pub(crate) use esp_wifi_result;
pub(crate) mod embassy {
use embassy_net_driver::{Capabilities, Driver, HardwareAddress, RxToken, TxToken};
use super::*;
use crate::asynch::AtomicWaker;
pub(crate) static TRANSMIT_WAKER: AtomicWaker = AtomicWaker::new();
pub(crate) static AP_RECEIVE_WAKER: AtomicWaker = AtomicWaker::new();
pub(crate) static AP_LINK_STATE_WAKER: AtomicWaker = AtomicWaker::new();
pub(crate) static STA_RECEIVE_WAKER: AtomicWaker = AtomicWaker::new();
pub(crate) static STA_LINK_STATE_WAKER: AtomicWaker = AtomicWaker::new();
impl RxToken for WifiRxToken {
fn consume<R, F>(self, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
self.consume_token(f)
}
}
impl TxToken for WifiTxToken {
fn consume<R, F>(self, len: usize, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
self.consume_token(len, f)
}
}
impl Driver for Interface<'_> {
type RxToken<'a>
= WifiRxToken
where
Self: 'a;
type TxToken<'a>
= WifiTxToken
where
Self: 'a;
fn receive(
&mut self,
cx: &mut core::task::Context<'_>,
) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
self.mode.register_receive_waker(cx);
self.mode.register_transmit_waker(cx);
self.mode.rx_token()
}
fn transmit(&mut self, cx: &mut core::task::Context<'_>) -> Option<Self::TxToken<'_>> {
self.mode.register_transmit_waker(cx);
self.mode.tx_token()
}
fn link_state(
&mut self,
cx: &mut core::task::Context<'_>,
) -> embassy_net_driver::LinkState {
self.mode.register_link_state_waker(cx);
self.mode.link_state()
}
fn capabilities(&self) -> Capabilities {
let mut caps = Capabilities::default();
caps.max_transmission_unit = MTU;
caps.max_burst_size =
if esp_config_int!(usize, "ESP_RADIO_CONFIG_WIFI_MAX_BURST_SIZE") == 0 {
None
} else {
Some(esp_config_int!(
usize,
"ESP_RADIO_CONFIG_WIFI_MAX_BURST_SIZE"
))
};
caps
}
fn hardware_address(&self) -> HardwareAddress {
HardwareAddress::Ethernet(self.mac_address())
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[instability::unstable]
#[non_exhaustive]
pub enum PowerSaveMode {
#[default]
None,
Minimum,
Maximum,
}
pub(crate) fn apply_power_saving(ps: PowerSaveMode) -> Result<(), WifiError> {
esp_wifi_result!(unsafe {
crate::sys::include::esp_wifi_set_ps(match ps {
PowerSaveMode::None => crate::sys::include::wifi_ps_type_t_WIFI_PS_NONE,
PowerSaveMode::Minimum => crate::sys::include::wifi_ps_type_t_WIFI_PS_MIN_MODEM,
PowerSaveMode::Maximum => crate::sys::include::wifi_ps_type_t_WIFI_PS_MAX_MODEM,
})
})?;
Ok(())
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct Interfaces<'d> {
pub station: Interface<'d>,
pub access_point: Interface<'d>,
#[cfg(all(feature = "esp-now", feature = "unstable"))]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
pub esp_now: crate::esp_now::EspNow<'d>,
#[cfg(all(feature = "sniffer", feature = "unstable"))]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
pub sniffer: Sniffer<'d>,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[instability::unstable]
pub enum OperatingClass {
AllEnvironments,
Outdoors,
Indoors,
NonCountryEntity,
Repr(u8),
}
impl Default for OperatingClass {
fn default() -> Self {
OperatingClass::Repr(0) }
}
impl OperatingClass {
fn into_code(self) -> u8 {
match self {
OperatingClass::AllEnvironments => b' ',
OperatingClass::Outdoors => b'O',
OperatingClass::Indoors => b'I',
OperatingClass::NonCountryEntity => b'X',
OperatingClass::Repr(code) => code,
}
}
fn from_code(code: u8) -> Option<Self> {
match code {
b' ' => Some(OperatingClass::AllEnvironments),
b'O' => Some(OperatingClass::Outdoors),
b'I' => Some(OperatingClass::Indoors),
b'X' => Some(OperatingClass::NonCountryEntity),
code => Some(OperatingClass::Repr(code)),
}
}
}
#[procmacros::doc_replace]
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, BuilderLite)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[instability::unstable]
pub struct CountryInfo {
#[builder_lite(skip)]
country: [u8; 2],
#[builder_lite(unstable)]
operating_class: OperatingClass,
}
impl From<[u8; 2]> for CountryInfo {
fn from(country: [u8; 2]) -> Self {
Self {
country,
operating_class: OperatingClass::default(),
}
}
}
impl CountryInfo {
fn into_blob(self) -> wifi_country_t {
wifi_country_t {
cc: [
self.country[0],
self.country[1],
self.operating_class.into_code(),
],
schan: 1,
nchan: 13,
max_tx_power: 20,
policy: wifi_country_policy_t_WIFI_COUNTRY_POLICY_MANUAL,
#[cfg(wifi_has_5g)]
wifi_5g_channel_mask: 0,
}
}
#[cfg_attr(not(feature = "unstable"), expect(dead_code))]
fn try_from_c(info: &wifi_country_t) -> Option<Self> {
let cc = &info.cc;
let operating_class = OperatingClass::from_code(cc[2])?;
Some(Self {
country: [cc[0], cc[1]],
operating_class,
})
}
}
#[derive(Clone, BuilderLite, Debug, Hash, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct ControllerConfig {
#[builder_lite(into)]
#[builder_lite(unstable)]
country_info: CountryInfo,
#[builder_lite(unstable)]
rx_queue_size: usize,
#[builder_lite(unstable)]
tx_queue_size: usize,
#[builder_lite(unstable)]
static_rx_buf_num: u8,
#[builder_lite(unstable)]
dynamic_rx_buf_num: u16,
#[builder_lite(unstable)]
static_tx_buf_num: u8,
#[builder_lite(unstable)]
dynamic_tx_buf_num: u16,
#[builder_lite(unstable)]
ampdu_rx_enable: bool,
#[builder_lite(unstable)]
ampdu_tx_enable: bool,
#[builder_lite(unstable)]
amsdu_tx_enable: bool,
#[builder_lite(unstable)]
rx_ba_win: u8,
#[builder_lite(reference)]
initial_config: Config,
}
impl Default for ControllerConfig {
fn default() -> Self {
Self {
rx_queue_size: 5,
tx_queue_size: 3,
static_rx_buf_num: 10,
dynamic_rx_buf_num: 32,
static_tx_buf_num: 0,
dynamic_tx_buf_num: 32,
ampdu_rx_enable: true,
ampdu_tx_enable: true,
amsdu_tx_enable: false,
rx_ba_win: 6,
country_info: CountryInfo::from(*b"CN"),
initial_config: Config::Station(StationConfig::default()),
}
}
}
impl ControllerConfig {
fn validate(&self) {
if self.rx_ba_win as u16 >= self.dynamic_rx_buf_num {
warn!("RX BA window size should be less than the number of dynamic RX buffers.");
}
if self.rx_ba_win as u16 >= 2 * (self.static_rx_buf_num as u16) {
warn!("RX BA window size should be less than twice the number of static RX buffers.");
}
}
}
#[procmacros::doc_replace]
pub fn new<'d>(
device: crate::hal::peripherals::WIFI<'d>,
config: ControllerConfig,
) -> Result<(WifiController<'d>, Interfaces<'d>), WifiError> {
let _guard = RadioRefGuard::new();
config.validate();
event::enable_wifi_events(
WifiEvent::StationStart
| WifiEvent::StationStop
| WifiEvent::StationConnected
| WifiEvent::StationDisconnected
| WifiEvent::AccessPointStart
| WifiEvent::AccessPointStop
| WifiEvent::AccessPointStationConnected
| WifiEvent::AccessPointStationDisconnected
| WifiEvent::ScanDone,
);
unsafe {
internal::G_CONFIG = wifi_init_config_t {
osi_funcs: (&raw const internal::__ESP_RADIO_G_WIFI_OSI_FUNCS).cast_mut(),
wpa_crypto_funcs: g_wifi_default_wpa_crypto_funcs,
static_rx_buf_num: config.static_rx_buf_num as _,
dynamic_rx_buf_num: config.dynamic_rx_buf_num as _,
tx_buf_type: crate::sys::include::CONFIG_ESP_WIFI_TX_BUFFER_TYPE as i32,
static_tx_buf_num: config.static_tx_buf_num as _,
dynamic_tx_buf_num: config.dynamic_tx_buf_num as _,
rx_mgmt_buf_type: crate::sys::include::CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF as i32,
rx_mgmt_buf_num: crate::sys::include::CONFIG_ESP_WIFI_RX_MGMT_BUF_NUM_DEF as i32,
cache_tx_buf_num: crate::sys::include::WIFI_CACHE_TX_BUFFER_NUM as i32,
csi_enable: cfg!(feature = "csi") as i32,
ampdu_rx_enable: config.ampdu_rx_enable as _,
ampdu_tx_enable: config.ampdu_tx_enable as _,
amsdu_tx_enable: config.amsdu_tx_enable as _,
nvs_enable: 0,
nano_enable: 0,
rx_ba_win: config.rx_ba_win as _,
wifi_task_core_id: Cpu::current() as _,
beacon_max_len: crate::sys::include::WIFI_SOFTAP_BEACON_MAX_LEN as i32,
mgmt_sbuf_num: crate::sys::include::WIFI_MGMT_SBUF_NUM as i32,
feature_caps: internal::__ESP_RADIO_G_WIFI_FEATURE_CAPS,
sta_disconnected_pm: false,
espnow_max_encrypt_num: crate::sys::include::CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM
as i32,
tx_hetb_queue_num: 3,
dump_hesigb_enable: false,
magic: WIFI_INIT_CONFIG_MAGIC as i32,
};
RX_QUEUE_SIZE.store(config.rx_queue_size, Ordering::Relaxed);
TX_QUEUE_SIZE.store(config.tx_queue_size, Ordering::Relaxed);
};
crate::wifi::wifi_init(device)?;
#[cfg(all(rng_trng_supported, feature = "unstable"))]
unsafe {
esp_hal::rng::TrngSource::increase_entropy_source_counter()
};
let mut controller = WifiController {
_guard,
_phantom: Default::default(),
};
controller.set_country_info(&config.country_info)?;
controller.set_power_saving(PowerSaveMode::default())?;
controller.set_config(&config.initial_config)?;
Ok((
controller,
Interfaces {
station: Interface {
_phantom: Default::default(),
mode: InterfaceType::Station,
},
access_point: Interface {
_phantom: Default::default(),
mode: InterfaceType::AccessPoint,
},
#[cfg(all(feature = "esp-now", feature = "unstable"))]
esp_now: crate::esp_now::EspNow::new_internal(),
#[cfg(all(feature = "sniffer", feature = "unstable"))]
sniffer: Sniffer::new(),
},
))
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct WifiController<'d> {
_guard: RadioRefGuard,
_phantom: PhantomData<&'d ()>,
}
impl Drop for WifiController<'_> {
fn drop(&mut self) {
state::locked(|| {
if let Err(e) = crate::wifi::wifi_deinit() {
warn!("Failed to cleanly deinit wifi: {:?}", e);
}
set_access_point_state(WifiAccessPointState::Uninitialized);
set_station_state(WifiStationState::Uninitialized);
#[cfg(all(rng_trng_supported, feature = "unstable"))]
esp_hal::rng::TrngSource::decrease_entropy_source_counter(unsafe {
esp_hal::Internal::conjure()
});
})
}
}
impl WifiController<'_> {
#[cfg(all(feature = "csi", feature = "unstable"))]
#[instability::unstable]
pub fn set_csi(
&mut self,
mut csi: csi::CsiConfig,
cb: impl FnMut(crate::wifi::csi::WifiCsiInfo<'_>) + Send,
) -> Result<(), WifiError> {
csi.apply_config()?;
csi.set_receive_cb(cb)?;
csi.set_csi(true)?;
Ok(())
}
#[procmacros::doc_replace]
#[instability::unstable]
pub fn set_protocols(&mut self, protocols: Protocols) -> Result<(), WifiError> {
let mode = self.mode()?;
if mode.is_station() {
esp_wifi_result!(unsafe {
esp_wifi_set_protocols(wifi_interface_t_WIFI_IF_STA, &mut protocols.to_raw())
})?;
}
if mode.is_access_point() {
esp_wifi_result!(unsafe {
esp_wifi_set_protocols(wifi_interface_t_WIFI_IF_AP, &mut protocols.to_raw())
})?;
}
Ok(())
}
fn apply_protocols(iface: wifi_interface_t, protocols: &Protocols) -> Result<(), WifiError> {
esp_wifi_result!(unsafe { esp_wifi_set_protocols(iface, &mut protocols.to_raw()) })?;
Ok(())
}
#[procmacros::doc_replace]
#[instability::unstable]
pub fn set_power_saving(&mut self, ps: PowerSaveMode) -> Result<(), WifiError> {
apply_power_saving(ps)
}
fn set_country_info(&mut self, country: &CountryInfo) -> Result<(), WifiError> {
unsafe {
let country = country.into_blob();
esp_wifi_result!(esp_wifi_set_country(&country))?;
}
Ok(())
}
#[procmacros::doc_replace]
pub fn rssi(&self) -> Result<i32, WifiError> {
if self.mode()?.is_station() {
let mut rssi: i32 = 0;
esp_wifi_result!(unsafe { esp_wifi_sta_get_rssi(&mut rssi) })?;
Ok(rssi)
} else {
Err(WifiError::Unsupported)
}
}
#[procmacros::doc_replace]
pub fn ap_info(&self) -> Result<AccessPointInfo, WifiError> {
if self.mode()?.is_station() {
let mut record: MaybeUninit<include::wifi_ap_record_t> = MaybeUninit::uninit();
esp_wifi_result!(unsafe { esp_wifi_sta_get_ap_info(record.as_mut_ptr()) })?;
let record = unsafe { MaybeUninit::assume_init(record) };
let ap_info = convert_ap_info(&record);
Ok(ap_info)
} else {
Err(WifiError::Unsupported)
}
}
#[procmacros::doc_replace]
pub fn set_config(&mut self, conf: &Config) -> Result<(), WifiError> {
struct ResetModeOnDrop;
impl ResetModeOnDrop {
fn defuse(self) {
core::mem::forget(self);
}
}
impl Drop for ResetModeOnDrop {
fn drop(&mut self) {
unsafe { esp_wifi_set_mode(wifi_mode_t_WIFI_MODE_NULL) };
unwrap!(WifiController::stop_impl());
}
}
let reset_mode_on_error = ResetModeOnDrop;
conf.validate()?;
let mut previous_mode = 0u32;
esp_wifi_result!(unsafe { esp_wifi_get_mode(&mut previous_mode) })?;
let mode = match conf {
Config::Station(_) => wifi_mode_t_WIFI_MODE_STA,
Config::AccessPoint(_) => wifi_mode_t_WIFI_MODE_AP,
Config::AccessPointStation(_, _) => wifi_mode_t_WIFI_MODE_APSTA,
#[cfg(feature = "wifi-eap")]
Config::EapStation(_) => wifi_mode_t_WIFI_MODE_STA,
};
if previous_mode != mode {
Self::stop_impl()?;
}
esp_wifi_result!(unsafe { esp_wifi_set_mode(mode) })?;
match conf {
Config::Station(config) => {
self.apply_sta_config(config)?;
Self::apply_protocols(wifi_interface_t_WIFI_IF_STA, &config.protocols)?;
}
Config::AccessPoint(config) => {
self.apply_ap_config(config)?;
Self::apply_protocols(wifi_interface_t_WIFI_IF_AP, &config.protocols)?;
}
Config::AccessPointStation(sta_config, ap_config) => {
self.apply_ap_config(ap_config)?;
Self::apply_protocols(wifi_interface_t_WIFI_IF_AP, &ap_config.protocols)?;
self.apply_sta_config(sta_config)?;
Self::apply_protocols(wifi_interface_t_WIFI_IF_STA, &sta_config.protocols)?;
}
#[cfg(feature = "wifi-eap")]
Config::EapStation(config) => {
self.apply_sta_eap_config(config)?;
Self::apply_protocols(wifi_interface_t_WIFI_IF_STA, &config.protocols)?;
}
}
if previous_mode != mode {
set_access_point_state(WifiAccessPointState::Starting);
set_station_state(WifiStationState::Starting);
esp_wifi_result!(unsafe { esp_wifi_start() })?;
}
reset_mode_on_error.defuse();
Ok(())
}
#[cfg_attr(
wifi_has_5g,
doc = r"
When the WiFi band mode is set to [`BandMode::_5G`], it operates exclusively on the 5GHz channels.
When the WiFi band mode is set to [`BandMode::Auto`], it can operate on both the 2.4GHz and
5GHz channels.
When a WiFi band mode change triggers a band change, if no channel is set for the current
band, a default channel will be assigned: channel 1 for 2.4G band and channel 36 for 5G
band.
"
)]
#[instability::unstable]
pub fn set_band_mode(&mut self, band_mode: BandMode) -> Result<(), WifiError> {
esp_wifi_result!(unsafe { esp_wifi_set_band_mode(band_mode.to_raw()) })
}
#[instability::unstable]
pub fn set_bandwidths(&mut self, bandwidths: Bandwidths) -> Result<(), WifiError> {
let mode = self.mode()?;
if mode.is_station() {
esp_wifi_result!(unsafe {
esp_wifi_set_bandwidths(wifi_interface_t_WIFI_IF_STA, &mut bandwidths.to_raw())
})?;
}
if mode.is_access_point() {
esp_wifi_result!(unsafe {
esp_wifi_set_bandwidths(wifi_interface_t_WIFI_IF_AP, &mut bandwidths.to_raw())
})?;
}
Ok(())
}
#[instability::unstable]
pub fn bandwidths(&self) -> Result<Bandwidths, WifiError> {
let mut bw = wifi_bandwidths_t {
ghz_2g: 0,
ghz_5g: 0,
};
let mode = self.mode()?;
if mode.is_station() {
esp_wifi_result!(unsafe {
esp_wifi_get_bandwidths(wifi_interface_t_WIFI_IF_STA, &mut bw)
})?;
}
if mode.is_access_point() {
esp_wifi_result!(unsafe {
esp_wifi_get_bandwidths(wifi_interface_t_WIFI_IF_AP, &mut bw)
})?;
}
Ok(Bandwidths {
_2_4: Bandwidth::from_raw(bw.ghz_2g),
#[cfg(wifi_has_5g)]
_5: Bandwidth::from_raw(bw.ghz_5g),
})
}
#[instability::unstable]
pub fn channel(&self) -> Result<(u8, SecondaryChannel), WifiError> {
let mut primary = 0;
let mut secondary = 0;
esp_wifi_result!(unsafe { esp_wifi_get_channel(&mut primary, &mut secondary) })?;
Ok((primary, SecondaryChannel::from_raw(secondary)))
}
#[cfg_attr(
wifi_has_5g,
doc = r"
When operating in 5 GHz band, the second channel is automatically determined by the primary
channel according to the 802.11 standard. Any manually configured second channel will be
ignored."
)]
#[instability::unstable]
pub fn set_channel(
&mut self,
primary: u8,
secondary: SecondaryChannel,
) -> Result<(), WifiError> {
esp_wifi_result!(unsafe { esp_wifi_set_channel(primary, secondary as u32) })?;
Ok(())
}
#[instability::unstable]
pub fn set_max_tx_power(&mut self, power: i8) -> Result<(), WifiError> {
esp_wifi_result!(unsafe { esp_wifi_set_max_tx_power(power) })
}
fn stop_impl() -> Result<(), WifiError> {
set_access_point_state(WifiAccessPointState::Stopping);
set_station_state(WifiStationState::Stopping);
esp_wifi_result!(unsafe { esp_wifi_stop() })
}
fn connect_impl(&mut self) -> Result<(), WifiError> {
set_station_state(WifiStationState::Connecting);
esp_wifi_result!(unsafe { esp_wifi_connect_internal() })
}
fn disconnect_impl(&mut self) -> Result<(), WifiError> {
set_station_state(WifiStationState::Disconnecting);
esp_wifi_result!(unsafe { esp_wifi_disconnect_internal() })
}
#[procmacros::doc_replace]
#[instability::unstable]
pub fn is_connected(&self) -> bool {
matches!(
crate::wifi::station_state(),
crate::wifi::WifiStationState::Connected
)
}
fn mode(&self) -> Result<WifiMode, WifiError> {
WifiMode::current()
}
#[procmacros::doc_replace]
pub async fn scan_async(
&mut self,
config: &ScanConfig,
) -> Result<Vec<AccessPointInfo>, WifiError> {
let mut subscriber = EVENT_CHANNEL
.subscriber()
.expect("Unable to subscribe to events - consider increasing the internal event channel subscriber count");
esp_wifi_result!(wifi_start_scan(false, *config))?;
let guard = FreeApListOnDrop;
loop {
let event = subscriber.next_message_pure().await;
if let EventInfo::ScanDone {
status: _status,
number: _number,
scan_id: _scan_id,
} = event
{
break;
}
}
guard.defuse();
Ok(ScanResults::new(self)?.collect::<Vec<_>>())
}
#[procmacros::doc_replace]
pub async fn connect_async(&mut self) -> Result<ConnectedStationInfo, WifiError> {
let mut subscriber = EVENT_CHANNEL
.subscriber()
.expect("Unable to subscribe to events - consider increasing the internal event channel subscriber count");
self.connect_impl()?;
let result = loop {
let event = subscriber.next_message().await;
if let embassy_sync::pubsub::WaitResult::Message(event) = event {
match event {
EventInfo::StationConnected { .. } => {
break event;
}
EventInfo::StationDisconnected { .. } => {
break event;
}
_ => (),
}
}
};
match result {
event::EventInfo::StationConnected {
ssid,
bssid,
channel,
authmode,
aid,
} => Ok(ConnectedStationInfo {
ssid,
bssid,
channel,
authmode: AuthenticationMethod::from_raw(authmode),
aid,
}),
event::EventInfo::StationDisconnected {
ssid,
bssid,
reason,
rssi,
} => Err(WifiError::Disconnected(DisconnectedStationInfo {
ssid,
bssid,
reason: DisconnectReason::from_raw(reason),
rssi,
})),
_ => unreachable!(),
}
}
#[procmacros::doc_replace]
pub async fn disconnect_async(&mut self) -> Result<DisconnectedStationInfo, WifiError> {
if !self.is_connected() {
return Err(WifiError::NotConnected);
}
let mut subscriber = EVENT_CHANNEL
.subscriber()
.expect("Unable to subscribe to events - consider increasing the internal event channel subscriber count");
self.disconnect_impl()?;
loop {
let event = subscriber.next_message_pure().await;
if let event::EventInfo::StationDisconnected {
ssid,
bssid,
reason,
rssi,
} = event
{
break Ok(DisconnectedStationInfo {
ssid,
bssid,
reason: DisconnectReason::from_raw(reason),
rssi,
});
}
}
}
pub async fn wait_for_disconnect_async(&self) -> Result<DisconnectedStationInfo, WifiError> {
if !self.is_connected() {
return Err(WifiError::NotConnected);
}
let mut subscriber = EVENT_CHANNEL
.subscriber()
.expect("Unable to subscribe to events - consider increasing the internal event channel subscriber count");
loop {
let event = subscriber.next_message_pure().await;
if let event::EventInfo::StationDisconnected {
ssid,
bssid,
reason,
rssi,
} = event
{
break Ok(DisconnectedStationInfo {
ssid,
bssid,
reason: DisconnectReason::from_raw(reason),
rssi,
});
}
}
}
pub async fn wait_for_access_point_connected_event_async(
&self,
) -> Result<AccessPointStationEventInfo, WifiError> {
let mut subscriber = EVENT_CHANNEL
.subscriber()
.expect("Unable to subscribe to events - consider increasing the internal event channel subscriber count");
loop {
let event = subscriber.next_message_pure().await;
match event {
event::EventInfo::AccessPointStationConnected {
mac,
aid,
is_mesh_child,
} => {
break Ok(AccessPointStationEventInfo::Connected(
AccessPointStationConnectedInfo {
mac,
aid,
is_mesh_child,
},
));
}
event::EventInfo::AccessPointStationDisconnected {
mac,
aid,
is_mesh_child,
reason,
} => {
break Ok(AccessPointStationEventInfo::Disconnected(
AccessPointStationDisconnectedInfo {
mac,
aid: aid as u16,
is_mesh_child,
reason: DisconnectReason::from_raw(reason),
},
));
}
_ => (),
}
}
}
#[instability::unstable]
pub fn subscribe<'a>(&'a self) -> Result<event::EventSubscriber<'a>, WifiError> {
if let Ok(subscriber) = EVENT_CHANNEL.subscriber() {
return Ok(event::EventSubscriber::new(subscriber));
}
Err(WifiError::Failed)
}
fn apply_ap_config(&mut self, config: &AccessPointConfig) -> Result<(), WifiError> {
let mut cfg = wifi_config_t {
ap: wifi_ap_config_t {
ssid: [0; 32],
password: [0; 64],
ssid_len: 0,
channel: config.channel,
authmode: config.auth_method.to_raw(),
ssid_hidden: if config.ssid_hidden { 1 } else { 0 },
max_connection: config.max_connections as u8,
beacon_interval: 100,
pairwise_cipher: wifi_cipher_type_t_WIFI_CIPHER_TYPE_CCMP,
ftm_responder: false,
pmf_cfg: wifi_pmf_config_t {
capable: true,
required: false,
},
sae_pwe_h2e: 0,
csa_count: 3,
dtim_period: config.dtim_period,
transition_disable: 0,
sae_ext: 0,
bss_max_idle_cfg: include::wifi_bss_max_idle_config_t {
period: 0,
protected_keep_alive: false,
},
gtk_rekey_interval: 0,
},
};
if config.auth_method == AuthenticationMethod::None && !config.password.is_empty() {
return Err(WifiError::InvalidArguments);
}
unsafe {
cfg.ap.ssid[0..(config.ssid.len())].copy_from_slice(config.ssid.as_bytes());
cfg.ap.ssid_len = config.ssid.len() as u8;
cfg.ap.password[0..(config.password.len())].copy_from_slice(config.password.as_bytes());
esp_wifi_result!(esp_wifi_set_config(wifi_interface_t_WIFI_IF_AP, &mut cfg))
}
}
fn apply_sta_config(&mut self, config: &StationConfig) -> Result<(), WifiError> {
let mut cfg = wifi_config_t {
sta: wifi_sta_config_t {
ssid: [0; 32],
password: [0; 64],
scan_method: config.scan_method as c_types::c_uint,
bssid_set: config.bssid.is_some(),
bssid: config.bssid.unwrap_or_default(),
channel: config.channel.unwrap_or(0),
listen_interval: config.listen_interval,
sort_method: wifi_sort_method_t_WIFI_CONNECT_AP_BY_SIGNAL,
threshold: wifi_scan_threshold_t {
rssi: -99,
authmode: config.auth_method.to_raw(),
rssi_5g_adjustment: 0,
},
pmf_cfg: wifi_pmf_config_t {
capable: true,
required: false,
},
sae_pwe_h2e: 3,
_bitfield_align_1: [0; 0],
_bitfield_1: __BindgenBitfieldUnit::new([0; 4]),
failure_retry_cnt: config.failure_retry_cnt,
_bitfield_align_2: [0; 0],
_bitfield_2: __BindgenBitfieldUnit::new([0; 4]),
sae_pk_mode: 0, sae_h2e_identifier: [0; 32],
},
};
if config.auth_method == AuthenticationMethod::None && !config.password.is_empty() {
return Err(WifiError::InvalidArguments);
}
unsafe {
cfg.sta.ssid[0..(config.ssid.len())].copy_from_slice(config.ssid.as_bytes());
cfg.sta.password[0..(config.password.len())]
.copy_from_slice(config.password.as_bytes());
esp_wifi_result!(esp_wifi_set_config(wifi_interface_t_WIFI_IF_STA, &mut cfg))
}
}
#[cfg(feature = "wifi-eap")]
fn apply_sta_eap_config(&mut self, config: &EapStationConfig) -> Result<(), WifiError> {
let mut cfg = wifi_config_t {
sta: wifi_sta_config_t {
ssid: [0; 32],
password: [0; 64],
scan_method: config.scan_method as c_types::c_uint,
bssid_set: config.bssid.is_some(),
bssid: config.bssid.unwrap_or_default(),
channel: config.channel.unwrap_or(0),
listen_interval: config.listen_interval,
sort_method: wifi_sort_method_t_WIFI_CONNECT_AP_BY_SIGNAL,
threshold: wifi_scan_threshold_t {
rssi: -99,
authmode: config.auth_method.to_raw(),
rssi_5g_adjustment: 0,
},
pmf_cfg: wifi_pmf_config_t {
capable: true,
required: false,
},
sae_pwe_h2e: 3,
_bitfield_align_1: [0; 0],
_bitfield_1: __BindgenBitfieldUnit::new([0; 4]),
failure_retry_cnt: config.failure_retry_cnt,
_bitfield_align_2: [0; 0],
_bitfield_2: __BindgenBitfieldUnit::new([0; 4]),
sae_pk_mode: 0, sae_h2e_identifier: [0; 32],
},
};
unsafe {
cfg.sta.ssid[0..(config.ssid.len())].copy_from_slice(config.ssid.as_bytes());
esp_wifi_result!(esp_wifi_set_config(wifi_interface_t_WIFI_IF_STA, &mut cfg))?;
if let Some(identity) = &config.identity {
esp_wifi_result!(esp_eap_client_set_identity(
identity.as_str().as_ptr(),
identity.len() as i32
))?;
} else {
esp_eap_client_clear_identity();
}
if let Some(username) = &config.username {
esp_wifi_result!(esp_eap_client_set_username(
username.as_str().as_ptr(),
username.len() as i32
))?;
} else {
esp_eap_client_clear_username();
}
if let Some(password) = &config.password {
esp_wifi_result!(esp_eap_client_set_password(
password.as_str().as_ptr(),
password.len() as i32
))?;
} else {
esp_eap_client_clear_password();
}
if let Some(new_password) = &config.new_password {
esp_wifi_result!(esp_eap_client_set_new_password(
new_password.as_str().as_ptr(),
new_password.len() as i32
))?;
} else {
esp_eap_client_clear_new_password();
}
if let Some(pac_file) = &config.pac_file {
esp_wifi_result!(esp_eap_client_set_pac_file(
pac_file.as_ptr(),
pac_file.len() as i32
))?;
}
if let Some(phase2_method) = &config.ttls_phase2_method {
esp_wifi_result!(esp_eap_client_set_ttls_phase2_method(
phase2_method.to_raw()
))?;
}
if let Some(ca_cert) = config.ca_cert {
esp_wifi_result!(esp_eap_client_set_ca_cert(
ca_cert.as_ptr(),
ca_cert.len() as i32
))?;
} else {
esp_eap_client_clear_ca_cert();
}
if let Some((cert, key, password)) = config.certificate_and_key {
let (pwd, pwd_len) = if let Some(pwd) = password {
(pwd.as_ptr(), pwd.len() as i32)
} else {
(core::ptr::null(), 0)
};
esp_wifi_result!(esp_eap_client_set_certificate_and_key(
cert.as_ptr(),
cert.len() as i32,
key.as_ptr(),
key.len() as i32,
pwd,
pwd_len,
))?;
} else {
esp_eap_client_clear_certificate_and_key();
}
if let Some(cfg) = &config.eap_fast_config {
let params = esp_eap_fast_config {
fast_provisioning: cfg.fast_provisioning as i32,
fast_max_pac_list_len: cfg.fast_max_pac_list_len as i32,
fast_pac_format_binary: cfg.fast_pac_format_binary,
};
esp_wifi_result!(esp_eap_client_set_fast_params(params))?;
}
esp_wifi_result!(esp_eap_client_set_disable_time_check(!&config.time_check))?;
esp_wifi_result!(esp_wifi_sta_enterprise_enable())?;
Ok(())
}
}
}