pub mod event;
pub(crate) mod os_adapter;
pub(crate) mod state;
use alloc::collections::vec_deque::VecDeque;
use core::{
cell::{RefCell, RefMut},
fmt::Debug,
mem::{self, MaybeUninit},
ptr::addr_of,
time::Duration,
};
use critical_section::{CriticalSection, Mutex};
use enumset::{EnumSet, EnumSetType};
use esp_wifi_sys::include::{
esp_eap_client_clear_ca_cert,
esp_eap_client_clear_certificate_and_key,
esp_eap_client_clear_identity,
esp_eap_client_clear_new_password,
esp_eap_client_clear_password,
esp_eap_client_clear_username,
esp_eap_client_set_ca_cert,
esp_eap_client_set_certificate_and_key,
esp_eap_client_set_disable_time_check,
esp_eap_client_set_fast_params,
esp_eap_client_set_identity,
esp_eap_client_set_new_password,
esp_eap_client_set_pac_file,
esp_eap_client_set_password,
esp_eap_client_set_ttls_phase2_method,
esp_eap_client_set_username,
esp_eap_fast_config,
esp_wifi_sta_enterprise_enable,
wifi_pkt_rx_ctrl_t,
wifi_scan_channel_bitmap_t,
WIFI_PROTOCOL_11AX,
WIFI_PROTOCOL_11B,
WIFI_PROTOCOL_11G,
WIFI_PROTOCOL_11N,
WIFI_PROTOCOL_LR,
};
#[cfg(feature = "sniffer")]
use esp_wifi_sys::include::{
esp_wifi_80211_tx,
esp_wifi_set_promiscuous,
esp_wifi_set_promiscuous_rx_cb,
wifi_promiscuous_pkt_t,
wifi_promiscuous_pkt_type_t,
};
use num_derive::FromPrimitive;
#[doc(hidden)]
pub(crate) use os_adapter::*;
#[cfg(feature = "sniffer")]
use portable_atomic::AtomicBool;
use portable_atomic::{AtomicUsize, Ordering};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "smoltcp")]
use smoltcp::phy::{Device, DeviceCapabilities, RxToken, TxToken};
pub use state::*;
#[cfg(not(coex))]
use crate::config::PowerSaveMode;
use crate::{
common_adapter::*,
esp_wifi_result,
hal::{
macros::ram,
peripheral::{Peripheral, PeripheralRef},
},
EspWifiController,
};
const ETHERNET_FRAME_HEADER_SIZE: usize = 18;
const MTU: usize = crate::CONFIG.mtu;
#[cfg(feature = "utils")]
pub mod utils;
#[cfg(coex)]
use include::{coex_adapter_funcs_t, coex_pre_init, esp_coex_adapter_register};
#[cfg(all(csi_enable, esp32c6))]
use crate::binary::include::wifi_csi_acquire_config_t;
#[cfg(csi_enable)]
pub use crate::binary::include::wifi_csi_info_t;
#[cfg(csi_enable)]
use crate::binary::include::{
esp_wifi_set_csi,
esp_wifi_set_csi_config,
esp_wifi_set_csi_rx_cb,
wifi_csi_config_t,
};
use crate::binary::{
c_types,
include::{
self,
__BindgenBitfieldUnit,
esp_err_t,
esp_interface_t_ESP_IF_WIFI_AP,
esp_interface_t_ESP_IF_WIFI_STA,
esp_supplicant_deinit,
esp_supplicant_init,
esp_wifi_connect,
esp_wifi_deinit_internal,
esp_wifi_disconnect,
esp_wifi_get_mode,
esp_wifi_init_internal,
esp_wifi_internal_free_rx_buffer,
esp_wifi_internal_reg_rxcb,
esp_wifi_internal_tx,
esp_wifi_scan_start,
esp_wifi_set_config,
esp_wifi_set_country,
esp_wifi_set_mode,
esp_wifi_set_protocol,
esp_wifi_set_tx_done_cb,
esp_wifi_start,
esp_wifi_stop,
g_wifi_default_wpa_crypto_funcs,
wifi_active_scan_time_t,
wifi_ap_config_t,
wifi_auth_mode_t,
wifi_cipher_type_t_WIFI_CIPHER_TYPE_CCMP,
wifi_config_t,
wifi_country_policy_t_WIFI_COUNTRY_POLICY_MANUAL,
wifi_country_t,
wifi_init_config_t,
wifi_interface_t,
wifi_interface_t_WIFI_IF_AP,
wifi_interface_t_WIFI_IF_STA,
wifi_mode_t,
wifi_mode_t_WIFI_MODE_AP,
wifi_mode_t_WIFI_MODE_APSTA,
wifi_mode_t_WIFI_MODE_NULL,
wifi_mode_t_WIFI_MODE_STA,
wifi_osi_funcs_t,
wifi_pmf_config_t,
wifi_scan_config_t,
wifi_scan_threshold_t,
wifi_scan_time_t,
wifi_scan_type_t_WIFI_SCAN_TYPE_ACTIVE,
wifi_scan_type_t_WIFI_SCAN_TYPE_PASSIVE,
wifi_sort_method_t_WIFI_CONNECT_AP_BY_SIGNAL,
wifi_sta_config_t,
wpa_crypto_funcs_t,
ESP_WIFI_OS_ADAPTER_MAGIC,
ESP_WIFI_OS_ADAPTER_VERSION,
WIFI_INIT_CONFIG_MAGIC,
},
};
#[derive(EnumSetType, Debug, PartialOrd)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[derive(Default)]
pub enum AuthMethod {
None,
WEP,
WPA,
#[default]
WPA2Personal,
WPAWPA2Personal,
WPA2Enterprise,
WPA3Personal,
WPA2WPA3Personal,
WAPIPersonal,
}
#[derive(EnumSetType, Debug, PartialOrd)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[derive(Default)]
pub enum Protocol {
P802D11B,
P802D11BG,
#[default]
P802D11BGN,
P802D11BGNLR,
P802D11LR,
P802D11BGNAX,
}
#[derive(EnumSetType, Debug, PartialOrd)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[derive(Default)]
pub enum SecondaryChannel {
#[default]
None,
Above,
Below,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct AccessPointInfo {
pub ssid: heapless::String<32>,
pub bssid: [u8; 6],
pub channel: u8,
pub secondary_channel: SecondaryChannel,
pub signal_strength: i8,
#[cfg_attr(feature = "defmt", defmt(Debug2Format))]
pub protocols: EnumSet<Protocol>,
pub auth_method: Option<AuthMethod>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct AccessPointConfiguration {
pub ssid: heapless::String<32>,
pub ssid_hidden: bool,
pub channel: u8,
pub secondary_channel: Option<u8>,
#[cfg_attr(feature = "defmt", defmt(Debug2Format))]
pub protocols: EnumSet<Protocol>,
pub auth_method: AuthMethod,
pub password: heapless::String<64>,
pub max_connections: u16,
}
impl Default for AccessPointConfiguration {
fn default() -> Self {
Self {
ssid: unwrap!("iot-device".try_into()),
ssid_hidden: false,
channel: 1,
secondary_channel: None,
protocols: Protocol::P802D11B | Protocol::P802D11BG | Protocol::P802D11BGN,
auth_method: AuthMethod::None,
password: heapless::String::new(),
max_connections: 255,
}
}
}
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct ClientConfiguration {
pub ssid: heapless::String<32>,
pub bssid: Option<[u8; 6]>,
pub auth_method: AuthMethod,
pub password: heapless::String<64>,
pub channel: Option<u8>,
}
impl Debug for ClientConfiguration {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ClientConfiguration")
.field("ssid", &self.ssid)
.field("bssid", &self.bssid)
.field("auth_method", &self.auth_method)
.field("channel", &self.channel)
.finish()
}
}
impl Default for ClientConfiguration {
fn default() -> Self {
ClientConfiguration {
ssid: heapless::String::new(),
bssid: None,
auth_method: Default::default(),
password: heapless::String::new(),
channel: None,
}
}
}
#[cfg(csi_enable)]
pub(crate) trait CsiCallback: FnMut(crate::binary::include::wifi_csi_info_t) {}
#[cfg(csi_enable)]
impl<T> CsiCallback for T where T: FnMut(crate::binary::include::wifi_csi_info_t) {}
#[cfg(csi_enable)]
unsafe extern "C" fn csi_rx_cb<C: CsiCallback>(
ctx: *mut crate::wifi::c_types::c_void,
data: *mut crate::binary::include::wifi_csi_info_t,
) {
let csi_callback = unsafe { &mut *(ctx as *mut C) };
csi_callback(*data);
}
#[derive(Clone, PartialEq, Eq)]
#[cfg(all(not(esp32c6), csi_enable))]
pub struct CsiConfig {
pub lltf_en: bool,
pub htltf_en: bool,
pub stbc_htltf2_en: bool,
pub ltf_merge_en: bool,
pub channel_filter_en: bool,
pub manu_scale: bool,
pub shift: u8,
pub dump_ack_en: bool,
}
#[derive(Clone, PartialEq, Eq)]
#[cfg(all(esp32c6, csi_enable))]
pub struct CsiConfig {
pub enable: u32,
pub acquire_csi_legacy: u32,
pub acquire_csi_ht20: u32,
pub acquire_csi_ht40: u32,
pub acquire_csi_su: u32,
pub acquire_csi_mu: u32,
pub acquire_csi_dcm: u32,
pub acquire_csi_beamformed: u32,
pub acquire_csi_he_stbc: u32,
pub val_scale_cfg: u32,
pub dump_ack_en: u32,
pub reserved: u32,
}
#[cfg(csi_enable)]
impl Default for CsiConfig {
#[cfg(not(esp32c6))]
fn default() -> Self {
Self {
lltf_en: true,
htltf_en: true,
stbc_htltf2_en: true,
ltf_merge_en: true,
channel_filter_en: true,
manu_scale: false,
shift: 0,
dump_ack_en: false,
}
}
#[cfg(esp32c6)]
fn default() -> Self {
Self {
enable: 1,
acquire_csi_legacy: 1,
acquire_csi_ht20: 1,
acquire_csi_ht40: 1,
acquire_csi_su: 1,
acquire_csi_mu: 1,
acquire_csi_dcm: 1,
acquire_csi_beamformed: 1,
acquire_csi_he_stbc: 2,
val_scale_cfg: 2,
dump_ack_en: 1,
reserved: 19,
}
}
}
#[cfg(csi_enable)]
impl From<CsiConfig> for wifi_csi_config_t {
fn from(config: CsiConfig) -> Self {
#[cfg(not(esp32c6))]
{
wifi_csi_config_t {
lltf_en: config.lltf_en,
htltf_en: config.htltf_en,
stbc_htltf2_en: config.stbc_htltf2_en,
ltf_merge_en: config.ltf_merge_en,
channel_filter_en: config.channel_filter_en,
manu_scale: config.manu_scale,
shift: config.shift,
dump_ack_en: config.dump_ack_en,
}
}
#[cfg(esp32c6)]
{
wifi_csi_acquire_config_t {
_bitfield_align_1: [0; 0],
_bitfield_1: wifi_csi_acquire_config_t::new_bitfield_1(
config.enable,
config.acquire_csi_legacy,
config.acquire_csi_ht20,
config.acquire_csi_ht40,
config.acquire_csi_su,
config.acquire_csi_mu,
config.acquire_csi_dcm,
config.acquire_csi_beamformed,
config.acquire_csi_he_stbc,
config.val_scale_cfg,
config.dump_ack_en,
config.reserved,
),
}
}
}
}
#[cfg(csi_enable)]
impl CsiConfig {
pub(crate) fn apply_config(&self) -> Result<(), WifiError> {
let conf: wifi_csi_config_t = self.clone().into();
unsafe {
esp_wifi_result!(esp_wifi_set_csi_config(&conf))?;
}
Ok(())
}
pub(crate) fn set_receive_cb<C: CsiCallback>(&mut self, cb: C) -> Result<(), WifiError> {
let cb = alloc::boxed::Box::new(cb);
let cb_ptr = alloc::boxed::Box::into_raw(cb) as *mut crate::wifi::c_types::c_void;
unsafe {
esp_wifi_result!(esp_wifi_set_csi_rx_cb(Some(csi_rx_cb::<C>), cb_ptr))?;
}
Ok(())
}
pub(crate) fn set_csi(&self, enable: bool) -> Result<(), WifiError> {
unsafe {
esp_wifi_result!(esp_wifi_set_csi(enable))?;
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct EapFastConfig {
pub fast_provisioning: u8,
pub fast_max_pac_list_len: u8,
pub fast_pac_format_binary: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum TtlsPhase2Method {
Eap,
Mschapv2,
Mschap,
Pap,
Chap,
}
impl TtlsPhase2Method {
fn to_raw(&self) -> u32 {
match self {
TtlsPhase2Method::Eap => {
esp_wifi_sys::include::esp_eap_ttls_phase2_types_ESP_EAP_TTLS_PHASE2_EAP
}
TtlsPhase2Method::Mschapv2 => {
esp_wifi_sys::include::esp_eap_ttls_phase2_types_ESP_EAP_TTLS_PHASE2_MSCHAPV2
}
TtlsPhase2Method::Mschap => {
esp_wifi_sys::include::esp_eap_ttls_phase2_types_ESP_EAP_TTLS_PHASE2_MSCHAP
}
TtlsPhase2Method::Pap => {
esp_wifi_sys::include::esp_eap_ttls_phase2_types_ESP_EAP_TTLS_PHASE2_PAP
}
TtlsPhase2Method::Chap => {
esp_wifi_sys::include::esp_eap_ttls_phase2_types_ESP_EAP_TTLS_PHASE2_CHAP
}
}
}
}
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct EapClientConfiguration {
pub ssid: heapless::String<32>,
pub bssid: Option<[u8; 6]>,
pub auth_method: AuthMethod,
pub identity: Option<heapless::String<128>>,
pub username: Option<heapless::String<128>>,
pub password: Option<heapless::String<64>>,
pub new_password: Option<heapless::String<64>>,
pub eap_fast_config: Option<EapFastConfig>,
pub pac_file: Option<&'static [u8]>,
pub time_check: bool,
pub ca_cert: Option<&'static [u8]>,
#[allow(clippy::type_complexity)]
pub certificate_and_key: Option<(&'static [u8], &'static [u8], Option<&'static [u8]>)>,
pub ttls_phase2_method: Option<TtlsPhase2Method>,
pub channel: Option<u8>,
}
impl Debug for EapClientConfiguration {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("EapClientConfiguration")
.field("ssid", &self.ssid)
.field("bssid", &self.bssid)
.field("auth_method", &self.auth_method)
.field("channel", &self.channel)
.field("identity", &self.identity)
.field("username", &self.username)
.field("eap_fast_config", &self.eap_fast_config)
.field("time_check", &self.time_check)
.field("pac_file set", &self.pac_file.is_some())
.field("ca_cert set", &self.ca_cert.is_some())
.field(
"certificate_and_key set",
&self.certificate_and_key.is_some(),
)
.field("ttls_phase2_method", &self.ttls_phase2_method)
.finish()
}
}
impl Default for EapClientConfiguration {
fn default() -> Self {
EapClientConfiguration {
ssid: heapless::String::new(),
bssid: None,
auth_method: AuthMethod::WPA2Enterprise,
identity: None,
username: None,
password: None,
channel: None,
eap_fast_config: None,
time_check: false,
new_password: None,
pac_file: None,
ca_cert: None,
certificate_and_key: None,
ttls_phase2_method: None,
}
}
}
#[derive(EnumSetType, Debug, PartialOrd)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum Capability {
Client,
AccessPoint,
Mixed,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[derive(Default)]
#[allow(clippy::large_enum_variant)]
pub enum Configuration {
#[default]
None,
Client(ClientConfiguration),
AccessPoint(AccessPointConfiguration),
Mixed(ClientConfiguration, AccessPointConfiguration),
#[cfg_attr(feature = "serde", serde(skip))]
EapClient(EapClientConfiguration),
}
impl Configuration {
pub fn as_client_conf_ref(&self) -> Option<&ClientConfiguration> {
match self {
Self::Client(client_conf) | Self::Mixed(client_conf, _) => Some(client_conf),
_ => None,
}
}
pub fn as_ap_conf_ref(&self) -> Option<&AccessPointConfiguration> {
match self {
Self::AccessPoint(ap_conf) | Self::Mixed(_, ap_conf) => Some(ap_conf),
_ => None,
}
}
pub fn as_client_conf_mut(&mut self) -> &mut ClientConfiguration {
match self {
Self::Client(client_conf) => client_conf,
Self::Mixed(_, _) => {
let prev = mem::replace(self, Self::None);
match prev {
Self::Mixed(client_conf, _) => {
*self = Self::Client(client_conf);
self.as_client_conf_mut()
}
_ => unreachable!(),
}
}
_ => {
*self = Self::Client(Default::default());
self.as_client_conf_mut()
}
}
}
pub fn as_ap_conf_mut(&mut self) -> &mut AccessPointConfiguration {
match self {
Self::AccessPoint(ap_conf) => ap_conf,
Self::Mixed(_, _) => {
let prev = mem::replace(self, Self::None);
match prev {
Self::Mixed(_, ap_conf) => {
*self = Self::AccessPoint(ap_conf);
self.as_ap_conf_mut()
}
_ => unreachable!(),
}
}
_ => {
*self = Self::AccessPoint(Default::default());
self.as_ap_conf_mut()
}
}
}
pub fn as_mixed_conf_mut(
&mut self,
) -> (&mut ClientConfiguration, &mut AccessPointConfiguration) {
match self {
Self::Mixed(client_conf, ref mut ap_conf) => (client_conf, ap_conf),
Self::AccessPoint(_) => {
let prev = mem::replace(self, Self::None);
match prev {
Self::AccessPoint(ap_conf) => {
*self = Self::Mixed(Default::default(), ap_conf);
self.as_mixed_conf_mut()
}
_ => unreachable!(),
}
}
Self::Client(_) => {
let prev = mem::replace(self, Self::None);
match prev {
Self::Client(client_conf) => {
*self = Self::Mixed(client_conf, Default::default());
self.as_mixed_conf_mut()
}
_ => unreachable!(),
}
}
_ => {
*self = Self::Mixed(Default::default(), Default::default());
self.as_mixed_conf_mut()
}
}
}
}
trait AuthMethodExt {
fn to_raw(&self) -> wifi_auth_mode_t;
fn from_raw(raw: wifi_auth_mode_t) -> Self;
}
impl AuthMethodExt for AuthMethod {
fn to_raw(&self) -> wifi_auth_mode_t {
match self {
AuthMethod::None => include::wifi_auth_mode_t_WIFI_AUTH_OPEN,
AuthMethod::WEP => include::wifi_auth_mode_t_WIFI_AUTH_WEP,
AuthMethod::WPA => include::wifi_auth_mode_t_WIFI_AUTH_WPA_PSK,
AuthMethod::WPA2Personal => include::wifi_auth_mode_t_WIFI_AUTH_WPA2_PSK,
AuthMethod::WPAWPA2Personal => include::wifi_auth_mode_t_WIFI_AUTH_WPA_WPA2_PSK,
AuthMethod::WPA2Enterprise => include::wifi_auth_mode_t_WIFI_AUTH_WPA2_ENTERPRISE,
AuthMethod::WPA3Personal => include::wifi_auth_mode_t_WIFI_AUTH_WPA3_PSK,
AuthMethod::WPA2WPA3Personal => include::wifi_auth_mode_t_WIFI_AUTH_WPA2_WPA3_PSK,
AuthMethod::WAPIPersonal => include::wifi_auth_mode_t_WIFI_AUTH_WAPI_PSK,
}
}
fn from_raw(raw: wifi_auth_mode_t) -> Self {
match raw {
include::wifi_auth_mode_t_WIFI_AUTH_OPEN => AuthMethod::None,
include::wifi_auth_mode_t_WIFI_AUTH_WEP => AuthMethod::WEP,
include::wifi_auth_mode_t_WIFI_AUTH_WPA_PSK => AuthMethod::WPA,
include::wifi_auth_mode_t_WIFI_AUTH_WPA2_PSK => AuthMethod::WPA2Personal,
include::wifi_auth_mode_t_WIFI_AUTH_WPA_WPA2_PSK => AuthMethod::WPAWPA2Personal,
include::wifi_auth_mode_t_WIFI_AUTH_WPA2_ENTERPRISE => AuthMethod::WPA2Enterprise,
include::wifi_auth_mode_t_WIFI_AUTH_WPA3_PSK => AuthMethod::WPA3Personal,
include::wifi_auth_mode_t_WIFI_AUTH_WPA2_WPA3_PSK => AuthMethod::WPA2WPA3Personal,
include::wifi_auth_mode_t_WIFI_AUTH_WAPI_PSK => AuthMethod::WAPIPersonal,
_ => unreachable!(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum WifiMode {
Sta,
Ap,
ApSta,
}
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) })?;
Self::try_from(mode)
}
pub fn is_sta(&self) -> bool {
match self {
Self::Sta | Self::ApSta => true,
Self::Ap => false,
}
}
pub fn is_ap(&self) -> bool {
match self {
Self::Sta => false,
Self::Ap | Self::ApSta => true,
}
}
}
impl TryFrom<&Configuration> for WifiMode {
type Error = WifiError;
fn try_from(config: &Configuration) -> Result<Self, Self::Error> {
let mode = match config {
Configuration::None => return Err(WifiError::UnknownWifiMode),
Configuration::AccessPoint(_) => Self::Ap,
Configuration::Client(_) => Self::Sta,
Configuration::Mixed(_, _) => Self::ApSta,
Configuration::EapClient(_) => Self::Sta,
};
Ok(mode)
}
}
impl TryFrom<wifi_mode_t> for WifiMode {
type Error = WifiError;
fn try_from(value: wifi_mode_t) -> Result<Self, Self::Error> {
#[allow(non_upper_case_globals)]
match value {
include::wifi_mode_t_WIFI_MODE_STA => Ok(Self::Sta),
include::wifi_mode_t_WIFI_MODE_AP => Ok(Self::Ap),
include::wifi_mode_t_WIFI_MODE_APSTA => Ok(Self::ApSta),
_ => Err(WifiError::UnknownWifiMode),
}
}
}
impl From<WifiMode> for wifi_mode_t {
fn from(val: WifiMode) -> Self {
#[allow(non_upper_case_globals)]
match val {
WifiMode::Sta => wifi_mode_t_WIFI_MODE_STA,
WifiMode::Ap => wifi_mode_t_WIFI_MODE_AP,
WifiMode::ApSta => wifi_mode_t_WIFI_MODE_APSTA,
}
}
}
const DATA_FRAME_SIZE: usize = MTU + ETHERNET_FRAME_HEADER_SIZE;
const RX_QUEUE_SIZE: usize = crate::CONFIG.rx_queue_size;
const TX_QUEUE_SIZE: usize = crate::CONFIG.tx_queue_size;
pub(crate) static DATA_QUEUE_RX_AP: Mutex<RefCell<VecDeque<EspWifiPacketBuffer>>> =
Mutex::new(RefCell::new(VecDeque::new()));
pub(crate) static DATA_QUEUE_RX_STA: Mutex<RefCell<VecDeque<EspWifiPacketBuffer>>> =
Mutex::new(RefCell::new(VecDeque::new()));
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum WifiError {
NotInitialized,
InternalError(InternalWifiError),
Disconnected,
UnknownWifiMode,
Unsupported,
}
#[repr(i32)]
#[derive(Debug, FromPrimitive, EnumSetType)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum WifiEvent {
WifiReady = 0,
ScanDone,
StaStart,
StaStop,
StaConnected,
StaDisconnected,
StaAuthmodeChange,
StaWpsErSuccess,
StaWpsErFailed,
StaWpsErTimeout,
StaWpsErPin,
StaWpsErPbcOverlap,
ApStart,
ApStop,
ApStaconnected,
ApStadisconnected,
ApProbereqrecved,
FtmReport,
StaBssRssiLow,
ActionTxStatus,
RocDone,
StaBeaconTimeout,
ConnectionlessModuleWakeIntervalStart,
ApWpsRgSuccess,
ApWpsRgFailed,
ApWpsRgTimeout,
ApWpsRgPin,
ApWpsRgPbcOverlap,
ItwtSetup,
ItwtTeardown,
ItwtProbe,
ItwtSuspend,
TwtWakeup,
BtwtSetup,
BtwtTeardown,
NanStarted,
NanStopped,
NanSvcMatch,
NanReplied,
NanReceive,
NdpIndication,
NdpConfirm,
NdpTerminated,
HomeChannelChange,
StaNeighborRep,
}
#[repr(i32)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InternalWifiError {
EspErrNoMem = 0x101,
EspErrInvalidArg = 0x102,
EspErrWifiNotInit = 0x3001,
EspErrWifiNotStarted = 0x3002,
EspErrWifiNotStopped = 0x3003,
EspErrWifiIf = 0x3004,
EspErrWifiMode = 0x3005,
EspErrWifiState = 0x3006,
EspErrWifiConn = 0x3007,
EspErrWifiNvs = 0x3008,
EspErrWifiMac = 0x3009,
EspErrWifiSsid = 0x300A,
EspErrWifiPassword = 0x300B,
EspErrWifiTimeout = 0x300C,
EspErrWifiWakeFail = 0x300D,
EspErrWifiWouldBlock = 0x300E,
EspErrWifiNotConnect = 0x300F,
EspErrWifiPost = 0x3012,
EspErrWifiInitState = 0x3013,
EspErrWifiStopState = 0x3014,
EspErrWifiNotAssoc = 0x3015,
EspErrWifiTxDisallow = 0x3016,
}
#[cfg(all(coex, any(esp32, esp32c2, esp32c3, esp32c6, esp32s3)))]
static mut G_COEX_ADAPTER_FUNCS: coex_adapter_funcs_t = coex_adapter_funcs_t {
_version: include::COEX_ADAPTER_VERSION as i32,
_task_yield_from_isr: Some(task_yield_from_isr),
_semphr_create: Some(semphr_create),
_semphr_delete: Some(semphr_delete),
_semphr_take_from_isr: Some(semphr_take_from_isr_wrapper),
_semphr_give_from_isr: Some(semphr_give_from_isr_wrapper),
_semphr_take: Some(semphr_take),
_semphr_give: Some(semphr_give),
_is_in_isr: Some(is_in_isr_wrapper),
_malloc_internal: Some(malloc),
_free: Some(free),
_esp_timer_get_time: Some(esp_timer_get_time),
_env_is_chip: Some(env_is_chip),
_magic: include::COEX_ADAPTER_MAGIC as i32,
_timer_disarm: Some(ets_timer_disarm),
_timer_done: Some(ets_timer_done),
_timer_setfn: Some(ets_timer_setfn),
_timer_arm_us: Some(ets_timer_arm_us),
#[cfg(esp32)]
_spin_lock_create: Some(spin_lock_create),
#[cfg(esp32)]
_spin_lock_delete: Some(spin_lock_delete),
#[cfg(esp32)]
_int_disable: Some(wifi_int_disable),
#[cfg(esp32)]
_int_enable: Some(wifi_int_restore),
#[cfg(esp32c2)]
_slowclk_cal_get: Some(slowclk_cal_get),
};
#[cfg(coex)]
unsafe extern "C" fn semphr_take_from_isr_wrapper(
semphr: *mut c_types::c_void,
hptw: *mut c_types::c_void,
) -> i32 {
crate::common_adapter::semphr_take_from_isr(semphr as *const (), hptw as *const ())
}
#[cfg(coex)]
unsafe extern "C" fn semphr_give_from_isr_wrapper(
semphr: *mut c_types::c_void,
hptw: *mut c_types::c_void,
) -> i32 {
crate::common_adapter::semphr_give_from_isr(semphr as *const (), hptw as *const ())
}
#[cfg(coex)]
unsafe extern "C" fn is_in_isr_wrapper() -> i32 {
0
}
#[cfg(coex)]
pub(crate) fn coex_initialize() -> i32 {
debug!("call coex-initialize");
unsafe {
let res = esp_coex_adapter_register(core::ptr::addr_of_mut!(G_COEX_ADAPTER_FUNCS).cast());
if res != 0 {
error!("Error: esp_coex_adapter_register {}", res);
return res;
}
let res = coex_pre_init();
if res != 0 {
error!("Error: coex_pre_init {}", res);
return res;
}
0
}
}
pub(crate) unsafe extern "C" fn coex_init() -> i32 {
#[cfg(coex)]
{
debug!("coex-init");
#[allow(clippy::needless_return)]
return include::coex_init();
}
#[cfg(not(coex))]
0
}
#[no_mangle]
static g_wifi_osi_funcs: wifi_osi_funcs_t = wifi_osi_funcs_t {
_version: ESP_WIFI_OS_ADAPTER_VERSION as i32,
_env_is_chip: Some(env_is_chip),
_set_intr: Some(set_intr),
_clear_intr: Some(clear_intr),
_set_isr: Some(os_adapter_chip_specific::set_isr),
_ints_on: Some(ints_on),
_ints_off: Some(ints_off),
_is_from_isr: Some(is_from_isr),
_spin_lock_create: Some(spin_lock_create),
_spin_lock_delete: Some(spin_lock_delete),
_wifi_int_disable: Some(wifi_int_disable),
_wifi_int_restore: Some(wifi_int_restore),
_task_yield_from_isr: Some(task_yield_from_isr),
_semphr_create: Some(semphr_create),
_semphr_delete: Some(semphr_delete),
_semphr_take: Some(semphr_take),
_semphr_give: Some(semphr_give),
_wifi_thread_semphr_get: Some(wifi_thread_semphr_get),
_mutex_create: Some(mutex_create),
_recursive_mutex_create: Some(recursive_mutex_create),
_mutex_delete: Some(mutex_delete),
_mutex_lock: Some(mutex_lock),
_mutex_unlock: Some(mutex_unlock),
_queue_create: Some(queue_create),
_queue_delete: Some(queue_delete),
_queue_send: Some(queue_send),
_queue_send_from_isr: Some(queue_send_from_isr),
_queue_send_to_back: Some(queue_send_to_back),
_queue_send_to_front: Some(queue_send_to_front),
_queue_recv: Some(queue_recv),
_queue_msg_waiting: Some(queue_msg_waiting),
_event_group_create: Some(event_group_create),
_event_group_delete: Some(event_group_delete),
_event_group_set_bits: Some(event_group_set_bits),
_event_group_clear_bits: Some(event_group_clear_bits),
_event_group_wait_bits: Some(event_group_wait_bits),
_task_create_pinned_to_core: Some(task_create_pinned_to_core),
_task_create: Some(task_create),
_task_delete: Some(task_delete),
_task_delay: Some(task_delay),
_task_ms_to_tick: Some(task_ms_to_tick),
_task_get_current_task: Some(task_get_current_task),
_task_get_max_priority: Some(task_get_max_priority),
_malloc: Some(malloc),
_free: Some(free),
_event_post: Some(event_post),
_get_free_heap_size: Some(get_free_heap_size),
_rand: Some(rand),
_dport_access_stall_other_cpu_start_wrap: Some(dport_access_stall_other_cpu_start_wrap),
_dport_access_stall_other_cpu_end_wrap: Some(dport_access_stall_other_cpu_end_wrap),
_wifi_apb80m_request: Some(wifi_apb80m_request),
_wifi_apb80m_release: Some(wifi_apb80m_release),
_phy_disable: Some(phy_disable),
_phy_enable: Some(phy_enable),
_phy_update_country_info: Some(phy_update_country_info),
_read_mac: Some(read_mac),
_timer_arm: Some(ets_timer_arm),
_timer_disarm: Some(ets_timer_disarm),
_timer_done: Some(ets_timer_done),
_timer_setfn: Some(ets_timer_setfn),
_timer_arm_us: Some(ets_timer_arm_us),
_wifi_reset_mac: Some(wifi_reset_mac),
_wifi_clock_enable: Some(wifi_clock_enable),
_wifi_clock_disable: Some(wifi_clock_disable),
_wifi_rtc_enable_iso: Some(wifi_rtc_enable_iso),
_wifi_rtc_disable_iso: Some(wifi_rtc_disable_iso),
_esp_timer_get_time: Some(esp_timer_get_time),
_nvs_set_i8: Some(nvs_set_i8),
_nvs_get_i8: Some(nvs_get_i8),
_nvs_set_u8: Some(nvs_set_u8),
_nvs_get_u8: Some(nvs_get_u8),
_nvs_set_u16: Some(nvs_set_u16),
_nvs_get_u16: Some(nvs_get_u16),
_nvs_open: Some(nvs_open),
_nvs_close: Some(nvs_close),
_nvs_commit: Some(nvs_commit),
_nvs_set_blob: Some(nvs_set_blob),
_nvs_get_blob: Some(nvs_get_blob),
_nvs_erase_key: Some(nvs_erase_key),
_get_random: Some(get_random),
_get_time: Some(get_time),
_random: Some(random),
#[cfg(feature = "sys-logs")]
_log_write: Some(log_write),
#[cfg(not(feature = "sys-logs"))]
_log_write: None,
#[cfg(feature = "sys-logs")]
_log_writev: Some(log_writev),
#[cfg(not(feature = "sys-logs"))]
_log_writev: None,
_log_timestamp: Some(log_timestamp),
_malloc_internal: Some(malloc_internal),
_realloc_internal: Some(realloc_internal),
_calloc_internal: Some(calloc_internal),
_zalloc_internal: Some(zalloc_internal),
_wifi_malloc: Some(wifi_malloc),
_wifi_realloc: Some(wifi_realloc),
_wifi_calloc: Some(wifi_calloc),
_wifi_zalloc: Some(wifi_zalloc),
_wifi_create_queue: Some(wifi_create_queue),
_wifi_delete_queue: Some(wifi_delete_queue),
_coex_init: Some(coex_init),
_coex_deinit: Some(coex_deinit),
_coex_enable: Some(coex_enable),
_coex_disable: Some(coex_disable),
_coex_status_get: Some(coex_status_get),
_coex_condition_set: None,
_coex_wifi_request: Some(coex_wifi_request),
_coex_wifi_release: Some(coex_wifi_release),
_coex_wifi_channel_set: Some(coex_wifi_channel_set),
_coex_event_duration_get: Some(coex_event_duration_get),
_coex_pti_get: Some(coex_pti_get),
_coex_schm_status_bit_clear: Some(coex_schm_status_bit_clear),
_coex_schm_status_bit_set: Some(coex_schm_status_bit_set),
_coex_schm_interval_set: Some(coex_schm_interval_set),
_coex_schm_interval_get: Some(coex_schm_interval_get),
_coex_schm_curr_period_get: Some(coex_schm_curr_period_get),
_coex_schm_curr_phase_get: Some(coex_schm_curr_phase_get),
#[cfg(any(esp32c3, esp32c2, esp32c6, esp32h2, esp32s3, esp32s2))]
_slowclk_cal_get: Some(slowclk_cal_get),
#[cfg(any(esp32, esp32s2))]
_phy_common_clock_disable: Some(os_adapter_chip_specific::phy_common_clock_disable),
#[cfg(any(esp32, esp32s2))]
_phy_common_clock_enable: Some(os_adapter_chip_specific::phy_common_clock_enable),
_coex_register_start_cb: Some(coex_register_start_cb),
#[cfg(esp32c6)]
_regdma_link_set_write_wait_content: Some(
os_adapter_chip_specific::regdma_link_set_write_wait_content_dummy,
),
#[cfg(esp32c6)]
_sleep_retention_find_link_by_id: Some(
os_adapter_chip_specific::sleep_retention_find_link_by_id_dummy,
),
_coex_schm_process_restart: Some(coex_schm_process_restart_wrapper),
_coex_schm_register_cb: Some(coex_schm_register_cb_wrapper),
_magic: ESP_WIFI_OS_ADAPTER_MAGIC as i32,
_coex_schm_flexible_period_set: Some(coex_schm_flexible_period_set),
_coex_schm_flexible_period_get: Some(coex_schm_flexible_period_get),
};
const WIFI_ENABLE_WPA3_SAE: u64 = 1 << 0;
const WIFI_ENABLE_ENTERPRISE: u64 = 1 << 7;
const WIFI_FEATURE_CAPS: u64 = WIFI_ENABLE_WPA3_SAE | WIFI_ENABLE_ENTERPRISE;
#[no_mangle]
static mut g_wifi_feature_caps: u64 = WIFI_FEATURE_CAPS;
static mut G_CONFIG: wifi_init_config_t = wifi_init_config_t {
osi_funcs: addr_of!(g_wifi_osi_funcs).cast_mut(),
wpa_crypto_funcs: wpa_crypto_funcs_t {
size: 0,
version: 1,
aes_wrap: None,
aes_unwrap: None,
hmac_sha256_vector: None,
sha256_prf: None,
hmac_md5: None,
hamc_md5_vector: None,
hmac_sha1: None,
hmac_sha1_vector: None,
sha1_prf: None,
sha1_vector: None,
pbkdf2_sha1: None,
rc4_skip: None,
md5_vector: None,
aes_encrypt: None,
aes_encrypt_init: None,
aes_encrypt_deinit: None,
aes_decrypt: None,
aes_decrypt_init: None,
aes_decrypt_deinit: None,
aes_128_encrypt: None,
aes_128_decrypt: None,
omac1_aes_128: None,
ccmp_decrypt: None,
ccmp_encrypt: None,
aes_gmac: None,
sha256_vector: None,
crc32: None,
},
static_rx_buf_num: crate::CONFIG.static_rx_buf_num as i32,
dynamic_rx_buf_num: crate::CONFIG.dynamic_rx_buf_num as i32,
tx_buf_type: esp_wifi_sys::include::CONFIG_ESP_WIFI_TX_BUFFER_TYPE as i32,
static_tx_buf_num: crate::CONFIG.static_tx_buf_num as i32,
dynamic_tx_buf_num: crate::CONFIG.dynamic_tx_buf_num as i32,
rx_mgmt_buf_type: esp_wifi_sys::include::CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF as i32,
rx_mgmt_buf_num: esp_wifi_sys::include::CONFIG_ESP_WIFI_RX_MGMT_BUF_NUM_DEF as i32,
cache_tx_buf_num: esp_wifi_sys::include::WIFI_CACHE_TX_BUFFER_NUM as i32,
csi_enable: crate::CONFIG.csi_enable as i32,
ampdu_rx_enable: crate::CONFIG.ampdu_rx_enable as i32,
ampdu_tx_enable: crate::CONFIG.ampdu_tx_enable as i32,
amsdu_tx_enable: crate::CONFIG.amsdu_tx_enable as i32,
nvs_enable: 0,
nano_enable: 0,
rx_ba_win: crate::CONFIG.rx_ba_win as i32,
wifi_task_core_id: 0,
beacon_max_len: esp_wifi_sys::include::WIFI_SOFTAP_BEACON_MAX_LEN as i32,
mgmt_sbuf_num: esp_wifi_sys::include::WIFI_MGMT_SBUF_NUM as i32,
feature_caps: WIFI_FEATURE_CAPS,
sta_disconnected_pm: false,
espnow_max_encrypt_num: esp_wifi_sys::include::CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM as i32,
magic: WIFI_INIT_CONFIG_MAGIC as i32,
tx_hetb_queue_num: 3,
dump_hesigb_enable: false,
};
pub fn sta_mac(mac: &mut [u8; 6]) {
unsafe {
read_mac(mac as *mut u8, 0);
}
}
pub fn ap_mac(mac: &mut [u8; 6]) {
unsafe {
read_mac(mac as *mut u8, 1);
}
}
pub(crate) fn wifi_init() -> Result<(), WifiError> {
unsafe {
G_CONFIG.wpa_crypto_funcs = g_wifi_default_wpa_crypto_funcs;
G_CONFIG.feature_caps = g_wifi_feature_caps;
#[cfg(coex)]
esp_wifi_result!(coex_init())?;
esp_wifi_result!(esp_wifi_init_internal(addr_of!(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)
))?;
#[cfg(any(esp32, esp32s3))]
{
static mut NVS_STRUCT: [u32; 12] = [0; 12];
chip_specific::g_misc_nvs = addr_of!(NVS_STRUCT) as u32;
}
crate::flags::WIFI.fetch_add(1, Ordering::SeqCst);
Ok(())
}
}
pub(crate) fn wifi_deinit() -> Result<(), crate::InitializationError> {
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 = EspWifiPacketBuffer { buffer, len, eb };
if critical_section::with(|cs| {
let mut queue = DATA_QUEUE_RX_STA.borrow_ref_mut(cs);
if queue.len() < RX_QUEUE_SIZE {
queue.push_back(packet);
true
} else {
false
}
}) {
embassy::STA_RECEIVE_WAKER.wake();
include::ESP_OK as esp_err_t
} else {
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 = EspWifiPacketBuffer { buffer, len, eb };
if critical_section::with(|cs| {
let mut queue = DATA_QUEUE_RX_AP.borrow_ref_mut(cs);
if queue.len() < RX_QUEUE_SIZE {
queue.push_back(packet);
true
} else {
false
}
}) {
embassy::AP_RECEIVE_WAKER.wake();
include::ESP_OK as esp_err_t
} else {
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() -> Result<(), WifiError> {
unsafe {
esp_wifi_result!(esp_wifi_start())?;
let mode = WifiMode::current()?;
if mode.is_ap() {
esp_wifi_result!(include::esp_wifi_set_inactive_time(
wifi_interface_t_WIFI_IF_AP,
crate::CONFIG.ap_beacon_timeout
))?;
}
if mode.is_sta() {
esp_wifi_result!(include::esp_wifi_set_inactive_time(
wifi_interface_t_WIFI_IF_STA,
crate::CONFIG.beacon_timeout
))?;
};
let mut cntry_code = [0u8; 3];
cntry_code[..crate::CONFIG.country_code.len()]
.copy_from_slice(crate::CONFIG.country_code.as_bytes());
cntry_code[2] = crate::CONFIG.country_code_operating_class;
let country = wifi_country_t {
cc: core::mem::transmute::<[u8; 3], [i8; 3]>(cntry_code), schan: 1,
nchan: 13,
max_tx_power: 20,
policy: wifi_country_policy_t_WIFI_COUNTRY_POLICY_MANUAL,
};
esp_wifi_result!(esp_wifi_set_country(&country))?;
}
Ok(())
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum ScanTypeConfig {
Active {
min: Duration,
max: Duration,
},
Passive(Duration),
}
impl Default for ScanTypeConfig {
fn default() -> Self {
Self::Active {
min: Duration::from_millis(10),
max: Duration::from_millis(20),
}
}
}
impl ScanTypeConfig {
fn validate(&self) {
if matches!(self, Self::Passive(dur) if *dur > Duration::from_millis(1500)) {
warn!("Passive scan duration longer than 1500ms may cause a station to disconnect from the AP");
}
}
}
#[derive(Clone, Copy, Default, PartialEq, Eq)]
pub struct ScanConfig<'a> {
pub ssid: Option<&'a str>,
pub bssid: Option<[u8; 6]>,
pub channel: Option<u8>,
pub show_hidden: bool,
pub scan_type: ScanTypeConfig,
}
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 = heapless::Vec::<u8, 33>::from_iter(m.bytes());
unwrap!(buf.push(b'\0').ok());
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,
},
};
unsafe { esp_wifi_scan_start(&scan_config, block) }
}
pub fn new_with_config<'d, MODE: WifiDeviceMode>(
inited: &'d EspWifiController<'d>,
device: impl Peripheral<P = crate::hal::peripherals::WIFI> + 'd,
config: MODE::Config,
) -> Result<(WifiDevice<'d, MODE>, WifiController<'d>), WifiError> {
crate::hal::into_ref!(device);
Ok((
WifiDevice::new(unsafe { device.clone_unchecked() }, MODE::new()),
WifiController::new_with_config(inited, device, MODE::wrap_config(config))?,
))
}
pub fn new_with_mode<'d, MODE: WifiDeviceMode>(
inited: &'d EspWifiController<'d>,
device: impl Peripheral<P = crate::hal::peripherals::WIFI> + 'd,
_mode: MODE,
) -> Result<(WifiDevice<'d, MODE>, WifiController<'d>), WifiError> {
new_with_config(inited, device, <MODE as Sealed>::Config::default())
}
pub fn new_ap_sta<'d>(
inited: &'d EspWifiController<'d>,
device: impl Peripheral<P = crate::hal::peripherals::WIFI> + 'd,
) -> Result<
(
WifiDevice<'d, WifiApDevice>,
WifiDevice<'d, WifiStaDevice>,
WifiController<'d>,
),
WifiError,
> {
new_ap_sta_with_config(inited, device, Default::default(), Default::default())
}
pub fn new_ap_sta_with_config<'d>(
inited: &'d EspWifiController<'d>,
device: impl Peripheral<P = crate::hal::peripherals::WIFI> + 'd,
sta_config: crate::wifi::ClientConfiguration,
ap_config: crate::wifi::AccessPointConfiguration,
) -> Result<
(
WifiDevice<'d, WifiApDevice>,
WifiDevice<'d, WifiStaDevice>,
WifiController<'d>,
),
WifiError,
> {
crate::hal::into_ref!(device);
Ok((
WifiDevice::new(unsafe { device.clone_unchecked() }, WifiApDevice),
WifiDevice::new(unsafe { device.clone_unchecked() }, WifiStaDevice),
WifiController::new_with_config(
inited,
device,
Configuration::Mixed(sta_config, ap_config),
)?,
))
}
mod sealed {
use super::*;
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct EspWifiPacketBuffer {
pub(crate) buffer: *mut c_types::c_void,
pub(crate) len: u16,
pub(crate) eb: *mut c_types::c_void,
}
unsafe impl Send for EspWifiPacketBuffer {}
impl Drop for EspWifiPacketBuffer {
fn drop(&mut self) {
trace!("Dropping EspWifiPacketBuffer, freeing memory");
unsafe { esp_wifi_internal_free_rx_buffer(self.eb) };
}
}
impl EspWifiPacketBuffer {
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) }
}
}
pub trait Sealed: Copy + Sized {
type Config: Default;
fn new() -> Self;
fn wrap_config(config: Self::Config) -> Configuration;
fn data_queue_rx(self, cs: CriticalSection) -> RefMut<'_, VecDeque<EspWifiPacketBuffer>>;
fn can_send(self) -> bool {
WIFI_TX_INFLIGHT.load(Ordering::SeqCst) < TX_QUEUE_SIZE
}
fn increase_in_flight_counter(self) {
WIFI_TX_INFLIGHT.fetch_add(1, Ordering::SeqCst);
}
fn tx_token(self) -> Option<WifiTxToken<Self>> {
if !self.can_send() {
crate::timer::yield_task();
}
if self.can_send() {
Some(WifiTxToken { mode: self })
} else {
None
}
}
fn rx_token(self) -> Option<(WifiRxToken<Self>, WifiTxToken<Self>)> {
let is_empty = critical_section::with(|cs| self.data_queue_rx(cs).is_empty());
if is_empty || !self.can_send() {
crate::timer::yield_task();
}
let is_empty =
is_empty && critical_section::with(|cs| self.data_queue_rx(cs).is_empty());
if !is_empty {
self.tx_token().map(|tx| (WifiRxToken { mode: self }, tx))
} else {
None
}
}
fn interface(self) -> wifi_interface_t;
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);
fn register_link_state_waker(self, cx: &mut core::task::Context);
fn link_state(self) -> embassy_net_driver::LinkState;
}
impl Sealed for WifiStaDevice {
type Config = ClientConfiguration;
fn new() -> Self {
Self
}
fn wrap_config(config: ClientConfiguration) -> Configuration {
Configuration::Client(config)
}
fn data_queue_rx(self, cs: CriticalSection) -> RefMut<'_, VecDeque<EspWifiPacketBuffer>> {
DATA_QUEUE_RX_STA.borrow_ref_mut(cs)
}
fn interface(self) -> wifi_interface_t {
wifi_interface_t_WIFI_IF_STA
}
fn register_receive_waker(self, cx: &mut core::task::Context) {
embassy::STA_RECEIVE_WAKER.register(cx.waker());
}
fn register_link_state_waker(self, cx: &mut core::task::Context) {
embassy::STA_LINK_STATE_WAKER.register(cx.waker());
}
fn link_state(self) -> embassy_net_driver::LinkState {
if matches!(sta_state(), WifiState::StaConnected) {
embassy_net_driver::LinkState::Up
} else {
embassy_net_driver::LinkState::Down
}
}
}
impl Sealed for WifiApDevice {
type Config = AccessPointConfiguration;
fn new() -> Self {
Self
}
fn wrap_config(config: AccessPointConfiguration) -> Configuration {
Configuration::AccessPoint(config)
}
fn data_queue_rx(self, cs: CriticalSection) -> RefMut<'_, VecDeque<EspWifiPacketBuffer>> {
DATA_QUEUE_RX_AP.borrow_ref_mut(cs)
}
fn interface(self) -> wifi_interface_t {
wifi_interface_t_WIFI_IF_AP
}
fn register_receive_waker(self, cx: &mut core::task::Context) {
embassy::AP_RECEIVE_WAKER.register(cx.waker());
}
fn register_link_state_waker(self, cx: &mut core::task::Context) {
embassy::AP_LINK_STATE_WAKER.register(cx.waker());
}
fn link_state(self) -> embassy_net_driver::LinkState {
if matches!(ap_state(), WifiState::ApStarted) {
embassy_net_driver::LinkState::Up
} else {
embassy_net_driver::LinkState::Down
}
}
}
}
use sealed::*;
pub trait WifiDeviceMode: Sealed {
fn mode(self) -> WifiMode;
fn mac_address(self) -> [u8; 6];
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct WifiStaDevice;
impl WifiDeviceMode for WifiStaDevice {
fn mode(self) -> WifiMode {
WifiMode::Sta
}
fn mac_address(self) -> [u8; 6] {
let mut mac = [0; 6];
sta_mac(&mut mac);
mac
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct WifiApDevice;
impl WifiDeviceMode for WifiApDevice {
fn mode(self) -> WifiMode {
WifiMode::Ap
}
fn mac_address(self) -> [u8; 6] {
let mut mac = [0; 6];
ap_mac(&mut mac);
mac
}
}
pub struct WifiDevice<'d, MODE: WifiDeviceMode> {
_device: PeripheralRef<'d, crate::hal::peripherals::WIFI>,
mode: MODE,
}
impl<'d, MODE: WifiDeviceMode> WifiDevice<'d, MODE> {
pub(crate) fn new(
_device: PeripheralRef<'d, crate::hal::peripherals::WIFI>,
mode: MODE,
) -> Self {
Self { _device, mode }
}
pub fn mac_address(&self) -> [u8; 6] {
self.mode.mac_address()
}
#[cfg(not(feature = "smoltcp"))]
pub fn receive(&mut self) -> Option<(WifiRxToken<MODE>, WifiTxToken<MODE>)> {
self.mode.rx_token()
}
#[cfg(not(feature = "smoltcp"))]
pub fn transmit(&mut self) -> Option<WifiTxToken<MODE>> {
self.mode.tx_token()
}
}
fn convert_ap_info(record: &include::wifi_ap_record_t) -> AccessPointInfo {
let str_len = record
.ssid
.iter()
.position(|&c| c == 0)
.unwrap_or(record.ssid.len());
let ssid_ref = unsafe { core::str::from_utf8_unchecked(&record.ssid[..str_len]) };
let mut ssid = heapless::String::<32>::new();
unwrap!(ssid.push_str(ssid_ref));
AccessPointInfo {
ssid,
bssid: record.bssid,
channel: record.primary,
secondary_channel: match record.second {
include::wifi_second_chan_t_WIFI_SECOND_CHAN_NONE => SecondaryChannel::None,
include::wifi_second_chan_t_WIFI_SECOND_CHAN_ABOVE => SecondaryChannel::Above,
include::wifi_second_chan_t_WIFI_SECOND_CHAN_BELOW => SecondaryChannel::Below,
_ => panic!(),
},
signal_strength: record.rssi,
protocols: EnumSet::empty(), auth_method: Some(AuthMethod::from_raw(record.authmode)),
}
}
#[cfg(not(any(esp32c6)))]
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
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: u32,
pub timestamp: u32,
pub noise_floor: i32,
pub ant: u32,
pub sig_len: u32,
pub rx_state: u32,
}
#[cfg(esp32c6)]
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
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 second: u32,
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,
}
impl RxControlInfo {
pub unsafe fn from_raw(rx_cntl: *const wifi_pkt_rx_ctrl_t) -> Self {
#[cfg(not(esp32c6))]
let rx_control_info = 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: (*rx_cntl).secondary_channel(),
timestamp: (*rx_cntl).timestamp(),
noise_floor: (*rx_cntl).noise_floor(),
ant: (*rx_cntl).ant(),
sig_len: (*rx_cntl).sig_len(),
rx_state: (*rx_cntl).rx_state(),
};
#[cfg(esp32c6)]
let rx_control_info = 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(),
second: (*rx_cntl).second(),
channel: (*rx_cntl).channel(),
noise_floor: (*rx_cntl).noise_floor(),
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(),
};
rx_control_info
}
}
#[cfg(feature = "sniffer")]
pub struct PromiscuousPkt<'a> {
pub rx_cntl: RxControlInfo,
pub frame_type: wifi_promiscuous_pkt_type_t,
pub len: usize,
pub data: &'a [u8],
}
#[cfg(feature = "sniffer")]
impl PromiscuousPkt<'_> {
pub(crate) unsafe fn from_raw(
buf: *const wifi_promiscuous_pkt_t,
frame_type: wifi_promiscuous_pkt_type_t,
) -> Self {
let rx_cntl = RxControlInfo::from_raw(&(*buf).rx_ctrl);
let len = rx_cntl.sig_len as usize;
PromiscuousPkt {
rx_cntl,
frame_type,
len,
data: core::slice::from_raw_parts(
(buf as *const u8).add(size_of::<wifi_pkt_rx_ctrl_t>()),
len,
),
}
}
}
#[cfg(feature = "sniffer")]
#[allow(clippy::type_complexity)]
static SNIFFER_CB: Mutex<RefCell<Option<fn(PromiscuousPkt)>>> = Mutex::new(RefCell::new(None));
#[cfg(feature = "sniffer")]
unsafe extern "C" fn promiscuous_rx_cb(buf: *mut core::ffi::c_void, frame_type: u32) {
critical_section::with(|cs| {
let Some(sniffer_callback) = *SNIFFER_CB.borrow_ref(cs) else {
return;
};
let promiscuous_pkt = PromiscuousPkt::from_raw(buf as *const _, frame_type);
sniffer_callback(promiscuous_pkt);
});
}
#[cfg(feature = "sniffer")]
pub struct Sniffer {
promiscuous_mode_enabled: AtomicBool,
}
#[cfg(feature = "sniffer")]
impl Sniffer {
pub(crate) fn new() -> Self {
unwrap!(esp_wifi_result!(unsafe {
esp_wifi_set_promiscuous_rx_cb(Some(promiscuous_rx_cb))
}));
Self {
promiscuous_mode_enabled: AtomicBool::new(false),
}
}
pub fn set_promiscuous_mode(&self, enabled: bool) -> Result<(), WifiError> {
esp_wifi_result!(unsafe { esp_wifi_set_promiscuous(enabled) })?;
self.promiscuous_mode_enabled
.store(enabled, Ordering::Relaxed);
Ok(())
}
pub fn send_raw_frame(
&mut self,
use_sta_interface: bool,
buffer: &[u8],
use_internal_seq_num: bool,
) -> Result<(), WifiError> {
esp_wifi_result!(unsafe {
esp_wifi_80211_tx(
if use_sta_interface { 0 } else { 1 } as wifi_interface_t,
buffer.as_ptr() as *const _,
buffer.len() as i32,
use_internal_seq_num,
)
})
}
pub fn set_receive_cb(&mut self, cb: fn(PromiscuousPkt)) {
critical_section::with(|cs| {
*SNIFFER_CB.borrow_ref_mut(cs) = Some(cb);
});
}
}
pub struct WifiController<'d> {
_device: PeripheralRef<'d, crate::hal::peripherals::WIFI>,
config: Configuration,
#[cfg(feature = "sniffer")]
sniffer_taken: AtomicBool,
}
impl Drop for WifiController<'_> {
fn drop(&mut self) {
if unwrap!(
crate::flags::WIFI.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| {
Some(x.saturating_sub(1))
})
) == 0
{
if let Err(e) = crate::wifi::wifi_deinit() {
warn!("Failed to cleanly deinit wifi: {:?}", e);
}
}
}
}
impl<'d> WifiController<'d> {
pub(crate) fn new_with_config(
inited: &'d EspWifiController<'d>,
_device: PeripheralRef<'d, crate::hal::peripherals::WIFI>,
config: Configuration,
) -> Result<Self, WifiError> {
if !inited.wifi() {
crate::wifi::wifi_init()?;
}
let mut this = Self {
_device,
config: Default::default(),
#[cfg(feature = "sniffer")]
sniffer_taken: AtomicBool::new(false),
};
let mode = WifiMode::try_from(&config)?;
esp_wifi_result!(unsafe { esp_wifi_set_mode(mode.into()) })?;
debug!("Wifi mode {:?} set", mode);
this.set_configuration(&config)?;
Ok(this)
}
#[cfg(feature = "sniffer")]
pub fn take_sniffer(&self) -> Option<Sniffer> {
if self
.sniffer_taken
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
== Ok(false)
{
Some(Sniffer::new())
} else {
None
}
}
#[cfg(csi_enable)]
pub fn set_csi(
&mut self,
mut csi: CsiConfig,
cb: impl FnMut(crate::wifi::wifi_csi_info_t) + Send,
) -> Result<(), WifiError> {
csi.apply_config()?;
csi.set_receive_cb(cb)?;
csi.set_csi(true)?;
Ok(())
}
pub fn set_protocol(&mut self, protocols: EnumSet<Protocol>) -> Result<(), WifiError> {
let mut protocol = 0u8;
protocols.into_iter().for_each(|v| match v {
Protocol::P802D11B => protocol |= WIFI_PROTOCOL_11B as u8,
Protocol::P802D11BG => protocol |= WIFI_PROTOCOL_11B as u8 | WIFI_PROTOCOL_11G as u8,
Protocol::P802D11BGN => {
protocol |=
WIFI_PROTOCOL_11B as u8 | WIFI_PROTOCOL_11G as u8 | WIFI_PROTOCOL_11N as u8
}
Protocol::P802D11BGNLR => {
protocol |= WIFI_PROTOCOL_11B as u8
| WIFI_PROTOCOL_11G as u8
| WIFI_PROTOCOL_11N as u8
| WIFI_PROTOCOL_LR as u8
}
Protocol::P802D11LR => protocol |= WIFI_PROTOCOL_LR as u8,
Protocol::P802D11BGNAX => {
protocol |= WIFI_PROTOCOL_11B as u8
| WIFI_PROTOCOL_11G as u8
| WIFI_PROTOCOL_11N as u8
| WIFI_PROTOCOL_11AX as u8
}
});
let mut mode = wifi_mode_t_WIFI_MODE_NULL;
esp_wifi_result!(unsafe { esp_wifi_get_mode(&mut mode) })?;
if mode == wifi_mode_t_WIFI_MODE_STA || mode == wifi_mode_t_WIFI_MODE_APSTA {
esp_wifi_result!(unsafe {
esp_wifi_set_protocol(wifi_interface_t_WIFI_IF_STA, protocol)
})?;
}
if mode == wifi_mode_t_WIFI_MODE_AP || mode == wifi_mode_t_WIFI_MODE_APSTA {
esp_wifi_result!(unsafe {
esp_wifi_set_protocol(wifi_interface_t_WIFI_IF_AP, protocol)
})?;
}
Ok(())
}
#[cfg(not(coex))]
pub fn set_power_saving(&mut self, ps: PowerSaveMode) -> Result<(), WifiError> {
apply_power_saving(ps)
}
pub fn is_sta_enabled(&self) -> Result<bool, WifiError> {
WifiMode::try_from(&self.config).map(|m| m.is_sta())
}
pub fn is_ap_enabled(&self) -> Result<bool, WifiError> {
WifiMode::try_from(&self.config).map(|m| m.is_ap())
}
pub fn scan_with_config_sync<const N: usize>(
&mut self,
config: ScanConfig<'_>,
) -> Result<(heapless::Vec<AccessPointInfo, N>, usize), WifiError> {
esp_wifi_result!(crate::wifi::wifi_start_scan(true, config))?;
let count = self.scan_result_count()?;
let result = self.scan_results()?;
Ok((result, count))
}
fn scan_result_count(&mut self) -> Result<usize, WifiError> {
let mut bss_total: u16 = 0;
let guard = FreeApListOnDrop;
unsafe { esp_wifi_result!(include::esp_wifi_scan_get_ap_num(&mut bss_total))? };
guard.defuse();
Ok(bss_total as usize)
}
fn scan_results<const N: usize>(
&mut self,
) -> Result<heapless::Vec<AccessPointInfo, N>, WifiError> {
let mut scanned = heapless::Vec::<AccessPointInfo, N>::new();
let mut bss_total: u16 = N as u16;
let mut records: [MaybeUninit<include::wifi_ap_record_t>; N] = [MaybeUninit::uninit(); N];
let guard = FreeApListOnDrop;
unsafe {
esp_wifi_result!(include::esp_wifi_scan_get_ap_records(
&mut bss_total,
records[0].as_mut_ptr(),
))?
};
guard.defuse();
for i in 0..bss_total {
let record = unsafe { MaybeUninit::assume_init_ref(&records[i as usize]) };
let ap_info = convert_ap_info(record);
scanned.push(ap_info).ok();
}
Ok(scanned)
}
pub fn scan_n<const N: usize>(
&mut self,
) -> Result<(heapless::Vec<AccessPointInfo, N>, usize), WifiError> {
self.scan_with_config_sync(Default::default())
}
pub fn start(&mut self) -> Result<(), WifiError> {
crate::wifi::wifi_start()
}
pub fn stop(&mut self) -> Result<(), WifiError> {
self.stop_impl()
}
pub fn connect(&mut self) -> Result<(), WifiError> {
self.connect_impl()
}
pub fn disconnect(&mut self) -> Result<(), WifiError> {
self.disconnect_impl()
}
}
#[cfg(feature = "smoltcp")]
impl<MODE: WifiDeviceMode> Device for WifiDevice<'_, MODE> {
type RxToken<'a>
= WifiRxToken<MODE>
where
Self: 'a;
type TxToken<'a>
= WifiTxToken<MODE>
where
Self: 'a;
fn receive(
&mut self,
_instant: smoltcp::time::Instant,
) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
self.mode.rx_token()
}
fn transmit(&mut self, _instant: smoltcp::time::Instant) -> Option<Self::TxToken<'_>> {
self.mode.tx_token()
}
fn capabilities(&self) -> smoltcp::phy::DeviceCapabilities {
let mut caps = DeviceCapabilities::default();
caps.max_transmission_unit = MTU;
caps.max_burst_size = if crate::CONFIG.max_burst_size == 0 {
None
} else {
Some(crate::CONFIG.max_burst_size)
};
caps
}
}
#[doc(hidden)]
#[derive(Debug)]
pub struct WifiRxToken<MODE: Sealed> {
mode: MODE,
}
impl<MODE: Sealed> WifiRxToken<MODE> {
pub fn consume_token<R, F>(self, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
let mut data = critical_section::with(|cs| {
let mut queue = self.mode.data_queue_rx(cs);
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)
}
}
#[cfg(feature = "smoltcp")]
impl<MODE: Sealed> RxToken for WifiRxToken<MODE> {
fn consume<R, F>(self, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
self.consume_token(f)
}
}
#[doc(hidden)]
#[derive(Debug)]
pub struct WifiTxToken<MODE: Sealed> {
mode: MODE,
}
impl<MODE: Sealed> WifiTxToken<MODE> {
pub fn consume_token<R, F>(self, len: usize, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
self.mode.increase_in_flight_counter();
static mut BUFFER: [u8; DATA_FRAME_SIZE] = [0u8; DATA_FRAME_SIZE];
let buffer = unsafe { &mut BUFFER[..len] };
let res = f(buffer);
esp_wifi_send_data(self.mode.interface(), buffer);
res
}
}
#[cfg(feature = "smoltcp")]
impl<MODE: Sealed> TxToken for WifiTxToken<MODE> {
fn consume<R, F>(self, len: usize, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
self.consume_token(len, f)
}
}
pub(crate) fn esp_wifi_send_data(interface: wifi_interface_t, data: &mut [u8]) {
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 != 0 {
warn!("esp_wifi_internal_tx {}", res);
decrement_inflight_counter();
} else {
trace!("esp_wifi_internal_tx ok");
}
}
fn apply_ap_config(config: &AccessPointConfiguration) -> 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: 2,
},
};
if config.auth_method == AuthMethod::None && !config.password.is_empty() {
return Err(WifiError::InternalError(
InternalWifiError::EspErrInvalidArg,
));
}
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(config: &ClientConfiguration) -> Result<(), WifiError> {
let mut cfg = wifi_config_t {
sta: wifi_sta_config_t {
ssid: [0; 32],
password: [0; 64],
scan_method: crate::CONFIG.scan_method,
bssid_set: config.bssid.is_some(),
bssid: config.bssid.unwrap_or_default(),
channel: config.channel.unwrap_or(0),
listen_interval: crate::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(),
},
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: crate::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 == AuthMethod::None && !config.password.is_empty() {
return Err(WifiError::InternalError(
InternalWifiError::EspErrInvalidArg,
));
}
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))
}
}
fn apply_sta_eap_config(config: &EapClientConfiguration) -> Result<(), WifiError> {
let mut cfg = wifi_config_t {
sta: wifi_sta_config_t {
ssid: [0; 32],
password: [0; 64],
scan_method: crate::CONFIG.scan_method,
bssid_set: config.bssid.is_some(),
bssid: config.bssid.unwrap_or_default(),
channel: config.channel.unwrap_or(0),
listen_interval: crate::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(),
},
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: crate::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(())
}
}
impl WifiController<'_> {
pub fn capabilities(&self) -> Result<EnumSet<crate::wifi::Capability>, WifiError> {
let caps = match self.config {
Configuration::None => unreachable!(),
Configuration::Client(_) => enumset::enum_set! { Capability::Client },
Configuration::AccessPoint(_) => enumset::enum_set! { Capability::AccessPoint },
Configuration::Mixed(_, _) => {
Capability::Client | Capability::AccessPoint | Capability::Mixed
}
Configuration::EapClient(_) => enumset::enum_set! { Capability::Client },
};
Ok(caps)
}
pub fn configuration(&self) -> Result<Configuration, WifiError> {
Ok(self.config.clone())
}
pub fn set_configuration(&mut self, conf: &Configuration) -> Result<(), WifiError> {
let wifi_mode = WifiMode::current().unwrap_or(WifiMode::Sta);
let sta_enabled = wifi_mode.is_sta();
let ap_enabled = wifi_mode.is_ap();
match conf {
Configuration::Client(_) if !sta_enabled => {
return Err(WifiError::InternalError(
InternalWifiError::EspErrInvalidArg,
))
}
Configuration::AccessPoint(_) if !ap_enabled => {
return Err(WifiError::InternalError(
InternalWifiError::EspErrInvalidArg,
))
}
Configuration::EapClient(_) if !sta_enabled => {
return Err(WifiError::InternalError(
InternalWifiError::EspErrInvalidArg,
))
}
_ => (),
}
self.config = conf.clone();
match conf {
Configuration::None => {
return Err(WifiError::InternalError(
InternalWifiError::EspErrInvalidArg,
));
}
Configuration::Client(config) => apply_sta_config(config)?,
Configuration::AccessPoint(config) => apply_ap_config(config)?,
Configuration::Mixed(sta_config, ap_config) => {
apply_ap_config(ap_config)?;
apply_sta_config(sta_config)?;
}
Configuration::EapClient(config) => apply_sta_eap_config(config)?,
};
Ok(())
}
pub(crate) fn stop_impl(&mut self) -> Result<(), WifiError> {
esp_wifi_result!(unsafe { esp_wifi_stop() })
}
pub(crate) fn connect_impl(&mut self) -> Result<(), WifiError> {
esp_wifi_result!(unsafe { esp_wifi_connect() })
}
pub(crate) fn disconnect_impl(&mut self) -> Result<(), WifiError> {
esp_wifi_result!(unsafe { esp_wifi_disconnect() })
}
pub fn is_started(&self) -> Result<bool, WifiError> {
if matches!(
crate::wifi::sta_state(),
WifiState::StaStarted | WifiState::StaConnected | WifiState::StaDisconnected
) {
return Ok(true);
}
if matches!(crate::wifi::ap_state(), WifiState::ApStarted) {
return Ok(true);
}
Ok(false)
}
pub fn is_connected(&self) -> Result<bool, WifiError> {
match crate::wifi::sta_state() {
crate::wifi::WifiState::StaConnected => Ok(true),
crate::wifi::WifiState::StaDisconnected => Err(WifiError::Disconnected),
_ => Ok(false),
}
}
}
fn dump_packet_info(_buffer: &[u8]) {
#[cfg(dump_packets)]
{
info!("@WIFIFRAME {:?}", _buffer);
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! esp_wifi_result {
($value:expr) => {{
use num_traits::FromPrimitive;
let result = $value;
if result != esp_wifi_sys::include::ESP_OK as i32 {
warn!("{} returned an error: {}", stringify!($value), result);
Err(WifiError::InternalError(unwrap!(FromPrimitive::from_i32(
result
))))
} else {
Ok::<(), WifiError>(())
}
}};
}
pub(crate) mod embassy {
use embassy_net_driver::{Capabilities, Driver, HardwareAddress, RxToken, TxToken};
use embassy_sync::waitqueue::AtomicWaker;
use super::*;
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<MODE: WifiDeviceMode> RxToken for WifiRxToken<MODE> {
fn consume<R, F>(self, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
self.consume_token(f)
}
}
impl<MODE: WifiDeviceMode> TxToken for WifiTxToken<MODE> {
fn consume<R, F>(self, len: usize, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
self.consume_token(len, f)
}
}
impl<MODE: WifiDeviceMode> Driver for WifiDevice<'_, MODE> {
type RxToken<'a>
= WifiRxToken<MODE>
where
Self: 'a;
type TxToken<'a>
= WifiTxToken<MODE>
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 crate::CONFIG.max_burst_size == 0 {
None
} else {
Some(crate::CONFIG.max_burst_size)
};
caps
}
fn hardware_address(&self) -> HardwareAddress {
HardwareAddress::Ethernet(self.mac_address())
}
}
}
#[cfg(not(coex))]
pub(crate) fn apply_power_saving(ps: PowerSaveMode) -> Result<(), WifiError> {
esp_wifi_result!(unsafe { esp_wifi_sys::include::esp_wifi_set_ps(ps.into()) })?;
Ok(())
}
mod asynch {
use core::task::Poll;
use embassy_sync::waitqueue::AtomicWaker;
use super::*;
impl WifiController<'_> {
pub async fn scan_n_async<const N: usize>(
&mut self,
) -> Result<(heapless::Vec<AccessPointInfo, N>, usize), WifiError> {
self.scan_with_config_async(Default::default()).await
}
pub async fn scan_with_config_async<const N: usize>(
&mut self,
config: ScanConfig<'_>,
) -> Result<(heapless::Vec<AccessPointInfo, N>, usize), WifiError> {
Self::clear_events(WifiEvent::ScanDone);
esp_wifi_result!(wifi_start_scan(false, config))?;
let guard = FreeApListOnDrop;
WifiEventFuture::new(WifiEvent::ScanDone).await;
guard.defuse();
let count = self.scan_result_count()?;
let result = self.scan_results()?;
Ok((result, count))
}
pub async fn start_async(&mut self) -> Result<(), WifiError> {
let mode = WifiMode::try_from(&self.config)?;
let mut events = enumset::enum_set! {};
if mode.is_ap() {
events |= WifiEvent::ApStart;
}
if mode.is_sta() {
events |= WifiEvent::StaStart;
}
Self::clear_events(events);
wifi_start()?;
self.wait_for_all_events(events, false).await;
Ok(())
}
pub async fn stop_async(&mut self) -> Result<(), WifiError> {
let mode = WifiMode::try_from(&self.config)?;
let mut events = enumset::enum_set! {};
if mode.is_ap() {
events |= WifiEvent::ApStop;
}
if mode.is_sta() {
events |= WifiEvent::StaStop;
}
Self::clear_events(events);
crate::wifi::WifiController::stop_impl(self)?;
self.wait_for_all_events(events, false).await;
reset_ap_state();
reset_sta_state();
Ok(())
}
pub async fn connect_async(&mut self) -> Result<(), WifiError> {
Self::clear_events(WifiEvent::StaConnected | WifiEvent::StaDisconnected);
let err = crate::wifi::WifiController::connect_impl(self).err();
if MultiWifiEventFuture::new(WifiEvent::StaConnected | WifiEvent::StaDisconnected)
.await
.contains(WifiEvent::StaDisconnected)
{
Err(err.unwrap_or(WifiError::Disconnected))
} else {
Ok(())
}
}
pub async fn disconnect_async(&mut self) -> Result<(), WifiError> {
if !matches!(self.is_connected(), Ok(true)) {
return Ok(());
}
Self::clear_events(WifiEvent::StaDisconnected);
crate::wifi::WifiController::disconnect_impl(self)?;
WifiEventFuture::new(WifiEvent::StaDisconnected).await;
Ok(())
}
fn clear_events(events: impl Into<EnumSet<WifiEvent>>) {
critical_section::with(|cs| WIFI_EVENTS.borrow_ref_mut(cs).remove_all(events.into()));
}
pub async fn wait_for_event(&mut self, event: WifiEvent) {
Self::clear_events(event);
WifiEventFuture::new(event).await
}
pub async fn wait_for_events(
&mut self,
events: EnumSet<WifiEvent>,
clear_pending: bool,
) -> EnumSet<WifiEvent> {
if clear_pending {
Self::clear_events(events);
}
MultiWifiEventFuture::new(events).await
}
pub async fn wait_for_all_events(
&mut self,
mut events: EnumSet<WifiEvent>,
clear_pending: bool,
) {
if clear_pending {
Self::clear_events(events);
}
while !events.is_empty() {
let fired = MultiWifiEventFuture::new(events).await;
events -= fired;
}
}
}
impl WifiEvent {
pub(crate) fn waker(&self) -> &'static AtomicWaker {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
}
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub(crate) struct WifiEventFuture {
event: WifiEvent,
}
impl WifiEventFuture {
pub fn new(event: WifiEvent) -> Self {
Self { event }
}
}
impl core::future::Future for WifiEventFuture {
type Output = ();
fn poll(
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> Poll<Self::Output> {
self.event.waker().register(cx.waker());
if critical_section::with(|cs| WIFI_EVENTS.borrow_ref_mut(cs).remove(self.event)) {
Poll::Ready(())
} else {
Poll::Pending
}
}
}
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub(crate) struct MultiWifiEventFuture {
event: EnumSet<WifiEvent>,
}
impl MultiWifiEventFuture {
pub fn new(event: EnumSet<WifiEvent>) -> Self {
Self { event }
}
}
impl core::future::Future for MultiWifiEventFuture {
type Output = EnumSet<WifiEvent>;
fn poll(
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> Poll<Self::Output> {
let output = critical_section::with(|cs| {
let mut events = WIFI_EVENTS.borrow_ref_mut(cs);
let active = events.intersection(self.event);
events.remove_all(active);
active
});
if output.is_empty() {
for event in self.event.iter() {
event.waker().register(cx.waker());
}
Poll::Pending
} else {
Poll::Ready(output)
}
}
}
}
struct FreeApListOnDrop;
impl FreeApListOnDrop {
pub fn defuse(self) {
core::mem::forget(self);
}
}
impl Drop for FreeApListOnDrop {
fn drop(&mut self) {
unsafe {
include::esp_wifi_clear_ap_list();
}
}
}