pub(crate) mod os_adapter;
pub(crate) mod state;
use core::{
cell::{RefCell, RefMut},
fmt::Debug,
mem,
mem::MaybeUninit,
ptr::addr_of,
time::Duration,
};
use critical_section::{CriticalSection, Mutex};
use enumset::{EnumSet, EnumSetType};
use esp_wifi_sys::include::{
WIFI_PROTOCOL_11AX,
WIFI_PROTOCOL_11B,
WIFI_PROTOCOL_11G,
WIFI_PROTOCOL_11N,
WIFI_PROTOCOL_LR,
};
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
#[doc(hidden)]
pub(crate) use os_adapter::*;
use portable_atomic::{AtomicUsize, Ordering};
#[cfg(feature = "smoltcp")]
use smoltcp::phy::{Device, DeviceCapabilities, RxToken, TxToken};
pub use state::*;
use crate::{
common_adapter::*,
esp_wifi_result,
hal::{
macros::ram,
peripheral::{Peripheral, PeripheralRef},
},
EspWifiInitialization,
};
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};
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_init,
esp_wifi_connect,
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_ps,
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,
},
},
compat::queue::SimpleQueue,
};
#[derive(EnumSetType, Debug, PartialOrd)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[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))]
#[derive(Default)]
pub enum Protocol {
P802D11B,
P802D11BG,
#[default]
P802D11BGN,
P802D11BGNLR,
P802D11LR,
P802D11BGNAX,
}
#[derive(EnumSetType, Debug, PartialOrd)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Default)]
pub enum SecondaryChannel {
#[default]
None,
Above,
Below,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
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))]
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: "iot-device".try_into().unwrap(),
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 = "use_serde", derive(Serialize, Deserialize))]
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,
}
}
}
#[derive(EnumSetType, Debug, PartialOrd)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Capability {
Client,
AccessPoint,
Mixed,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Default)]
pub enum Configuration {
#[default]
None,
Client(ClientConfiguration),
AccessPoint(AccessPointConfiguration),
Mixed(ClientConfiguration, AccessPointConfiguration),
}
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()
}
}
}
}
pub mod ipv4 {
use core::{fmt::Display, str::FromStr};
pub use no_std_net::*;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Mask(pub u8);
impl FromStr for Mask {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse::<u8>()
.map_err(|_| "Invalid subnet mask")
.map_or_else(Err, |mask| {
if (1..=32).contains(&mask) {
Ok(Mask(mask))
} else {
Err("Mask should be a number between 1 and 32")
}
})
}
}
impl Display for Mask {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.0)
}
}
impl TryFrom<Ipv4Addr> for Mask {
type Error = ();
fn try_from(ip: Ipv4Addr) -> Result<Self, Self::Error> {
let octets = ip.octets();
let addr: u32 = ((octets[0] as u32 & 0xff) << 24)
| ((octets[1] as u32 & 0xff) << 16)
| ((octets[2] as u32 & 0xff) << 8)
| (octets[3] as u32 & 0xff);
if addr.leading_ones() + addr.trailing_zeros() == 32 {
Ok(Mask(addr.leading_ones() as u8))
} else {
Err(())
}
}
}
impl From<Mask> for Ipv4Addr {
fn from(mask: Mask) -> Self {
let addr: u32 = ((1 << (32 - mask.0)) - 1) ^ 0xffffffffu32;
let (a, b, c, d) = (
((addr >> 24) & 0xff) as u8,
((addr >> 16) & 0xff) as u8,
((addr >> 8) & 0xff) as u8,
(addr & 0xff) as u8,
);
Ipv4Addr::new(a, b, c, d)
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Subnet {
#[cfg_attr(feature = "defmt", defmt(Debug2Format))]
pub gateway: Ipv4Addr,
pub mask: Mask,
}
impl Display for Subnet {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}/{}", self.gateway, self.mask)
}
}
impl FromStr for Subnet {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut split = s.split('/');
if let Some(gateway_str) = split.next() {
if let Some(mask_str) = split.next() {
if split.next().is_none() {
if let Ok(gateway) = gateway_str.parse::<Ipv4Addr>() {
return mask_str.parse::<Mask>().map(|mask| Self { gateway, mask });
} else {
return Err("Invalid IP address format, expected XXX.XXX.XXX.XXX");
}
}
}
}
Err("Expected <gateway-ip-address>/<mask>")
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ClientSettings {
#[cfg_attr(feature = "defmt", defmt(Debug2Format))]
pub ip: Ipv4Addr,
pub subnet: Subnet,
#[cfg_attr(feature = "defmt", defmt(Debug2Format))]
pub dns: Option<Ipv4Addr>,
#[cfg_attr(feature = "defmt", defmt(Debug2Format))]
pub secondary_dns: Option<Ipv4Addr>,
}
impl Default for ClientSettings {
fn default() -> ClientSettings {
ClientSettings {
ip: Ipv4Addr::new(192, 168, 71, 200),
subnet: Subnet {
gateway: Ipv4Addr::new(192, 168, 71, 1),
mask: Mask(24),
},
dns: Some(Ipv4Addr::new(8, 8, 8, 8)),
secondary_dns: Some(Ipv4Addr::new(8, 8, 4, 4)),
}
}
}
#[derive(Default, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct DHCPClientSettings {
pub hostname: Option<heapless::String<30>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ClientConfiguration {
DHCP(DHCPClientSettings),
Fixed(ClientSettings),
}
impl ClientConfiguration {
pub fn as_fixed_settings_ref(&self) -> Option<&ClientSettings> {
match self {
Self::Fixed(client_settings) => Some(client_settings),
_ => None,
}
}
pub fn as_fixed_settings_mut(&mut self) -> &mut ClientSettings {
match self {
Self::Fixed(client_settings) => client_settings,
_ => {
*self = ClientConfiguration::Fixed(Default::default());
self.as_fixed_settings_mut()
}
}
}
}
impl Default for ClientConfiguration {
fn default() -> ClientConfiguration {
ClientConfiguration::DHCP(Default::default())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct RouterConfiguration {
pub subnet: Subnet,
pub dhcp_enabled: bool,
#[cfg_attr(feature = "defmt", defmt(Debug2Format))]
pub dns: Option<Ipv4Addr>,
#[cfg_attr(feature = "defmt", defmt(Debug2Format))]
pub secondary_dns: Option<Ipv4Addr>,
}
impl Default for RouterConfiguration {
fn default() -> RouterConfiguration {
RouterConfiguration {
subnet: Subnet {
gateway: Ipv4Addr::new(192, 168, 71, 1),
mask: Mask(24),
},
dhcp_enabled: true,
dns: Some(Ipv4Addr::new(8, 8, 8, 8)),
secondary_dns: Some(Ipv4Addr::new(8, 8, 4, 4)),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Configuration {
Client(ClientConfiguration),
Router(RouterConfiguration),
}
impl Default for Configuration {
fn default() -> Self {
Self::Client(Default::default())
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct IpInfo {
#[cfg_attr(feature = "defmt", defmt(Debug2Format))]
pub ip: Ipv4Addr,
pub subnet: Subnet,
#[cfg_attr(feature = "defmt", defmt(Debug2Format))]
pub dns: Option<Ipv4Addr>,
#[cfg_attr(feature = "defmt", defmt(Debug2Format))]
pub secondary_dns: Option<Ipv4Addr>,
}
}
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))]
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,
};
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<SimpleQueue<EspWifiPacketBuffer, RX_QUEUE_SIZE>>,
> = Mutex::new(RefCell::new(SimpleQueue::new()));
pub(crate) static DATA_QUEUE_RX_STA: Mutex<
RefCell<SimpleQueue<EspWifiPacketBuffer, RX_QUEUE_SIZE>>,
> = Mutex::new(RefCell::new(SimpleQueue::new()));
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum WifiError {
NotInitialized,
InternalError(InternalWifiError),
Disconnected,
UnknownWifiMode,
}
#[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,
}
#[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");
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 = "wifi-logs")]
_log_write: Some(log_write),
#[cfg(not(feature = "wifi-logs"))]
_log_write: None,
#[cfg(feature = "wifi-logs")]
_log_writev: Some(log_writev),
#[cfg(not(feature = "wifi-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,
),
#[cfg(esp32c6)]
_sleep_retention_entries_create: Some(
os_adapter_chip_specific::sleep_retention_entries_create_dummy,
),
#[cfg(esp32c6)]
_sleep_retention_entries_destroy: Some(
os_adapter_chip_specific::sleep_retention_entries_destroy_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,
};
const CONFIG_FEATURE_WPA3_SAE_BIT: u64 = 1 << 0;
const WIFI_FEATURE_CAPS: u64 = CONFIG_FEATURE_WPA3_SAE_BIT;
#[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: 1,
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: 0_i32,
rx_mgmt_buf_num: 0_i32,
cache_tx_buf_num: 0,
csi_enable: 1,
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: 752,
mgmt_sbuf_num: 32,
feature_caps: WIFI_FEATURE_CAPS,
sta_disconnected_pm: false,
espnow_max_encrypt_num: 7, magic: WIFI_INIT_CONFIG_MAGIC as i32,
};
pub fn get_sta_mac(mac: &mut [u8; 6]) {
unsafe {
read_mac(mac as *mut u8, 0);
}
}
pub fn get_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;
}
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 };
match critical_section::with(|cs| DATA_QUEUE_RX_STA.borrow_ref_mut(cs).enqueue(packet)) {
Ok(_) => {
#[cfg(feature = "embassy-net")]
embassy::STA_RECEIVE_WAKER.wake();
include::ESP_OK as esp_err_t
}
Err(_) => {
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 };
match critical_section::with(|cs| DATA_QUEUE_RX_AP.borrow_ref_mut(cs).enqueue(packet)) {
Ok(_) => {
#[cfg(feature = "embassy-net")]
embassy::AP_RECEIVE_WAKER.wake();
include::ESP_OK as esp_err_t
}
Err(_) => {
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() {
WIFI_TX_INFLIGHT
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| {
Some(x.saturating_sub(1))
})
.unwrap();
}
#[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();
#[cfg(feature = "embassy-net")]
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
))?;
};
cfg_if::cfg_if! {
if #[cfg(feature = "ps-min-modem")] {
let ps_mode = include::wifi_ps_type_t_WIFI_PS_MIN_MODEM;
} else if #[cfg(feature = "ps-max-modem")] {
let ps_mode = include::wifi_ps_type_t_WIFI_PS_MAX_MODEM;
} else if #[cfg(coex)] {
let ps_mode = include::wifi_ps_type_t_WIFI_PS_MIN_MODEM;
} else {
let ps_mode = include::wifi_ps_type_t_WIFI_PS_NONE;
}
};
esp_wifi_result!(esp_wifi_set_ps(ps_mode))?;
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(())
}
unsafe extern "C" fn coex_register_start_cb(
_cb: Option<unsafe extern "C" fn() -> c_types::c_int>,
) -> c_types::c_int {
#[cfg(coex)]
return include::coex_register_start_cb(_cb);
#[cfg(not(coex))]
0
}
#[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,
};
unsafe { esp_wifi_scan_start(&scan_config, block) }
}
pub fn new_with_config<'d, MODE: WifiDeviceMode>(
inited: &EspWifiInitialization,
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: &EspWifiInitialization,
device: impl crate::hal::peripheral::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: &EspWifiInitialization,
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: &EspWifiInitialization,
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<'_, SimpleQueue<EspWifiPacketBuffer, RX_QUEUE_SIZE>>;
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;
#[cfg(feature = "embassy-net")]
fn register_transmit_waker(self, cx: &mut core::task::Context) {
embassy::TRANSMIT_WAKER.register(cx.waker())
}
#[cfg(feature = "embassy-net")]
fn register_receive_waker(self, cx: &mut core::task::Context);
#[cfg(feature = "embassy-net")]
fn register_link_state_waker(self, cx: &mut core::task::Context);
#[cfg(feature = "embassy-net")]
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<'_, SimpleQueue<EspWifiPacketBuffer, RX_QUEUE_SIZE>> {
DATA_QUEUE_RX_STA.borrow_ref_mut(cs)
}
fn interface(self) -> wifi_interface_t {
wifi_interface_t_WIFI_IF_STA
}
#[cfg(feature = "embassy-net")]
fn register_receive_waker(self, cx: &mut core::task::Context) {
embassy::STA_RECEIVE_WAKER.register(cx.waker());
}
#[cfg(feature = "embassy-net")]
fn register_link_state_waker(self, cx: &mut core::task::Context) {
embassy::STA_LINK_STATE_WAKER.register(cx.waker());
}
#[cfg(feature = "embassy-net")]
fn link_state(self) -> embassy_net_driver::LinkState {
if matches!(get_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<'_, SimpleQueue<EspWifiPacketBuffer, RX_QUEUE_SIZE>> {
DATA_QUEUE_RX_AP.borrow_ref_mut(cs)
}
fn interface(self) -> wifi_interface_t {
wifi_interface_t_WIFI_IF_AP
}
#[cfg(feature = "embassy-net")]
fn register_receive_waker(self, cx: &mut core::task::Context) {
embassy::AP_RECEIVE_WAKER.register(cx.waker());
}
#[cfg(feature = "embassy-net")]
fn register_link_state_waker(self, cx: &mut core::task::Context) {
embassy::AP_LINK_STATE_WAKER.register(cx.waker());
}
#[cfg(feature = "embassy-net")]
fn link_state(self) -> embassy_net_driver::LinkState {
if matches!(get_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];
get_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];
get_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)),
}
}
pub struct WifiController<'d> {
_device: PeripheralRef<'d, crate::hal::peripherals::WIFI>,
config: Configuration,
}
impl<'d> WifiController<'d> {
pub(crate) fn new_with_config(
inited: &EspWifiInitialization,
_device: PeripheralRef<'d, crate::hal::peripherals::WIFI>,
config: Configuration,
) -> Result<Self, WifiError> {
if !inited.is_wifi() {
return Err(WifiError::NotInitialized);
}
let mut this = Self {
_device,
config: Default::default(),
};
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)
}
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(())
}
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)
}
}
#[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.dequeue(),
"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,
},
};
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],
},
};
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))
}
}
impl WifiController<'_> {
pub fn get_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
}
};
Ok(caps)
}
pub fn get_configuration(&self) -> Result<Configuration, WifiError> {
Ok(self.config.clone())
}
pub fn set_configuration(&mut self, conf: &Configuration) -> Result<(), WifiError> {
match self.config {
Configuration::None => self.config = conf.clone(), Configuration::Client(ref mut client) => {
if let Configuration::Client(conf) = conf {
*client = conf.clone();
} else {
return Err(WifiError::InternalError(
InternalWifiError::EspErrInvalidArg,
));
}
}
Configuration::AccessPoint(ref mut ap) => {
if let Configuration::AccessPoint(conf) = conf {
*ap = conf.clone();
} else {
return Err(WifiError::InternalError(
InternalWifiError::EspErrInvalidArg,
));
}
}
Configuration::Mixed(ref mut client, ref mut ap) => match conf {
Configuration::None => {
return Err(WifiError::InternalError(
InternalWifiError::EspErrInvalidArg,
));
}
Configuration::Mixed(_, _) => self.config = conf.clone(),
Configuration::Client(conf) => *client = conf.clone(),
Configuration::AccessPoint(conf) => *ap = 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)?;
}
};
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::get_sta_state(),
WifiState::StaStarted | WifiState::StaConnected | WifiState::StaDisconnected
) {
return Ok(true);
}
if matches!(crate::wifi::get_ap_state(), WifiState::ApStarted) {
return Ok(true);
}
Ok(false)
}
pub fn is_connected(&self) -> Result<bool, WifiError> {
match crate::wifi::get_sta_state() {
crate::wifi::WifiState::StaConnected => Ok(true),
crate::wifi::WifiState::StaDisconnected => Err(WifiError::Disconnected),
_ => Ok(false),
}
}
}
#[cfg(not(feature = "async"))]
impl WifiController<'_> {
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()
}
}
fn dump_packet_info(_buffer: &[u8]) {
#[cfg(feature = "dump-packets")]
{
info!("@WIFIFRAME {:?}", _buffer);
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! esp_wifi_result {
($value:expr) => {{
let result = $value;
if result != include::ESP_OK as i32 {
warn!("{} returned an error: {}", stringify!($value), result);
Err(WifiError::InternalError(unwrap!(FromPrimitive::from_i32(
result
))))
} else {
Ok::<(), WifiError>(())
}
}};
}
#[cfg(feature = "embassy-net")]
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(feature = "async")]
mod asynch {
use core::task::Poll;
use embassy_sync::waitqueue::AtomicWaker;
use super::*;
impl<'d> WifiController<'d> {
pub async fn scan_n<const N: usize>(
&mut self,
) -> Result<(heapless::Vec<AccessPointInfo, N>, usize), WifiError> {
self.scan_with_config(Default::default()).await
}
pub async fn scan_with_config<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(&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(&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(&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(&mut self) -> Result<(), WifiError> {
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 {
match self {
WifiEvent::ScanDone => {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
WifiEvent::StaStart => {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
WifiEvent::StaConnected => {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
WifiEvent::StaDisconnected => {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
WifiEvent::StaStop => {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
WifiEvent::WifiReady => {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
WifiEvent::StaAuthmodeChange => {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
WifiEvent::StaWpsErSuccess => {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
WifiEvent::StaWpsErFailed => {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
WifiEvent::StaWpsErTimeout => {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
WifiEvent::StaWpsErPin => {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
WifiEvent::StaWpsErPbcOverlap => {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
WifiEvent::ApStart => {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
WifiEvent::ApStop => {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
WifiEvent::ApStaconnected => {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
WifiEvent::ApStadisconnected => {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
WifiEvent::ApProbereqrecved => {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
WifiEvent::FtmReport => {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
WifiEvent::StaBssRssiLow => {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
WifiEvent::ActionTxStatus => {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
WifiEvent::RocDone => {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
WifiEvent::StaBeaconTimeout => {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
}
}
}
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
}
}
}
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();
}
}
}
#[cfg(feature = "embedded-svc")]
mod embedded_svc_compat {
use super::*;
impl Into<embedded_svc::wifi::Capability> for Capability {
fn into(self) -> embedded_svc::wifi::Capability {
match self {
Capability::Client => embedded_svc::wifi::Capability::Client,
Capability::AccessPoint => embedded_svc::wifi::Capability::AccessPoint,
Capability::Mixed => embedded_svc::wifi::Capability::Mixed,
}
}
}
impl Into<embedded_svc::wifi::AuthMethod> for AuthMethod {
fn into(self) -> embedded_svc::wifi::AuthMethod {
match self {
AuthMethod::None => embedded_svc::wifi::AuthMethod::None,
AuthMethod::WEP => embedded_svc::wifi::AuthMethod::WEP,
AuthMethod::WPA => embedded_svc::wifi::AuthMethod::WPA,
AuthMethod::WPA2Personal => embedded_svc::wifi::AuthMethod::WPA2Personal,
AuthMethod::WPAWPA2Personal => embedded_svc::wifi::AuthMethod::WPAWPA2Personal,
AuthMethod::WPA2Enterprise => embedded_svc::wifi::AuthMethod::WPA2Enterprise,
AuthMethod::WPA3Personal => embedded_svc::wifi::AuthMethod::WPA3Personal,
AuthMethod::WPA2WPA3Personal => embedded_svc::wifi::AuthMethod::WPA2WPA3Personal,
AuthMethod::WAPIPersonal => embedded_svc::wifi::AuthMethod::WAPIPersonal,
}
}
}
impl From<embedded_svc::wifi::AuthMethod> for AuthMethod {
fn from(value: embedded_svc::wifi::AuthMethod) -> Self {
match value {
embedded_svc::wifi::AuthMethod::None => AuthMethod::None,
embedded_svc::wifi::AuthMethod::WEP => AuthMethod::WEP,
embedded_svc::wifi::AuthMethod::WPA => AuthMethod::WPA,
embedded_svc::wifi::AuthMethod::WPA2Personal => AuthMethod::WPA2Personal,
embedded_svc::wifi::AuthMethod::WPAWPA2Personal => AuthMethod::WPAWPA2Personal,
embedded_svc::wifi::AuthMethod::WPA2Enterprise => AuthMethod::WPA2Enterprise,
embedded_svc::wifi::AuthMethod::WPA3Personal => AuthMethod::WPA3Personal,
embedded_svc::wifi::AuthMethod::WPA2WPA3Personal => AuthMethod::WPA2WPA3Personal,
embedded_svc::wifi::AuthMethod::WAPIPersonal => AuthMethod::WAPIPersonal,
}
}
}
impl Into<embedded_svc::wifi::Protocol> for Protocol {
fn into(self) -> embedded_svc::wifi::Protocol {
match self {
Protocol::P802D11B => embedded_svc::wifi::Protocol::P802D11B,
Protocol::P802D11BG => embedded_svc::wifi::Protocol::P802D11BG,
Protocol::P802D11BGN => embedded_svc::wifi::Protocol::P802D11BGN,
Protocol::P802D11BGNLR => embedded_svc::wifi::Protocol::P802D11BGNLR,
Protocol::P802D11LR => embedded_svc::wifi::Protocol::P802D11LR,
Protocol::P802D11BGNAX => embedded_svc::wifi::Protocol::P802D11BGN,
}
}
}
impl From<embedded_svc::wifi::Protocol> for Protocol {
fn from(value: embedded_svc::wifi::Protocol) -> Self {
match value {
embedded_svc::wifi::Protocol::P802D11B => Protocol::P802D11B,
embedded_svc::wifi::Protocol::P802D11BG => Protocol::P802D11BG,
embedded_svc::wifi::Protocol::P802D11BGN => Protocol::P802D11BGN,
embedded_svc::wifi::Protocol::P802D11BGNLR => Protocol::P802D11BGNLR,
embedded_svc::wifi::Protocol::P802D11LR => Protocol::P802D11LR,
}
}
}
impl Into<embedded_svc::wifi::Configuration> for Configuration {
fn into(self) -> embedded_svc::wifi::Configuration {
match self {
Configuration::None => embedded_svc::wifi::Configuration::None,
Configuration::Client(conf) => embedded_svc::wifi::Configuration::Client(
embedded_svc::wifi::ClientConfiguration {
ssid: conf.ssid,
bssid: conf.bssid,
auth_method: conf.auth_method.into(),
password: conf.password,
channel: conf.channel,
},
),
Configuration::AccessPoint(conf) => embedded_svc::wifi::Configuration::AccessPoint(
embedded_svc::wifi::AccessPointConfiguration {
ssid: conf.ssid,
ssid_hidden: conf.ssid_hidden,
channel: conf.channel,
secondary_channel: conf.secondary_channel,
protocols: {
let mut res = EnumSet::<embedded_svc::wifi::Protocol>::new();
conf.protocols.into_iter().for_each(|v| {
res.insert(v.into());
});
res
},
auth_method: conf.auth_method.into(),
password: conf.password,
max_connections: conf.max_connections,
},
),
Configuration::Mixed(client, ap) => embedded_svc::wifi::Configuration::Mixed(
embedded_svc::wifi::ClientConfiguration {
ssid: client.ssid,
bssid: client.bssid,
auth_method: client.auth_method.into(),
password: client.password,
channel: client.channel,
},
embedded_svc::wifi::AccessPointConfiguration {
ssid: ap.ssid,
ssid_hidden: ap.ssid_hidden,
channel: ap.channel,
secondary_channel: ap.secondary_channel,
protocols: {
let mut res = EnumSet::<embedded_svc::wifi::Protocol>::new();
ap.protocols.into_iter().for_each(|v| {
res.insert(v.into());
});
res
},
auth_method: ap.auth_method.into(),
password: ap.password,
max_connections: ap.max_connections,
},
),
}
}
}
impl From<&embedded_svc::wifi::Configuration> for Configuration {
fn from(value: &embedded_svc::wifi::Configuration) -> Self {
match value {
embedded_svc::wifi::Configuration::None => Configuration::None,
embedded_svc::wifi::Configuration::Client(conf) => {
Configuration::Client(ClientConfiguration {
ssid: conf.ssid.clone(),
bssid: conf.bssid.clone(),
auth_method: conf.auth_method.into(),
password: conf.password.clone(),
channel: conf.channel.clone(),
})
}
embedded_svc::wifi::Configuration::AccessPoint(conf) => {
Configuration::AccessPoint(AccessPointConfiguration {
ssid: conf.ssid.clone(),
ssid_hidden: conf.ssid_hidden,
channel: conf.channel,
secondary_channel: conf.secondary_channel,
protocols: {
let mut res = EnumSet::<Protocol>::new();
conf.protocols.into_iter().for_each(|v| {
res.insert(v.into());
});
res
},
auth_method: conf.auth_method.into(),
password: conf.password.clone(),
max_connections: conf.max_connections,
})
}
embedded_svc::wifi::Configuration::Mixed(client, ap) => Configuration::Mixed(
ClientConfiguration {
ssid: client.ssid.clone(),
bssid: client.bssid.clone(),
auth_method: client.auth_method.into(),
password: client.password.clone(),
channel: client.channel,
},
AccessPointConfiguration {
ssid: ap.ssid.clone(),
ssid_hidden: ap.ssid_hidden.clone(),
channel: ap.channel,
secondary_channel: ap.secondary_channel,
protocols: {
let mut res = EnumSet::<Protocol>::new();
ap.protocols.into_iter().for_each(|v| {
res.insert(v.into());
});
res
},
auth_method: ap.auth_method.into(),
password: ap.password.clone(),
max_connections: ap.max_connections,
},
),
}
}
}
impl Into<embedded_svc::wifi::AccessPointInfo> for AccessPointInfo {
fn into(self) -> embedded_svc::wifi::AccessPointInfo {
embedded_svc::wifi::AccessPointInfo {
ssid: self.ssid.clone(),
bssid: self.bssid.clone(),
channel: self.channel,
secondary_channel: self.secondary_channel.into(),
signal_strength: self.signal_strength,
protocols: {
let mut res = EnumSet::<embedded_svc::wifi::Protocol>::new();
self.protocols.into_iter().for_each(|v| {
res.insert(v.into());
});
res
},
auth_method: self.auth_method.map(|v| v.into()),
}
}
}
impl Into<embedded_svc::wifi::SecondaryChannel> for SecondaryChannel {
fn into(self) -> embedded_svc::wifi::SecondaryChannel {
match self {
SecondaryChannel::None => embedded_svc::wifi::SecondaryChannel::None,
SecondaryChannel::Above => embedded_svc::wifi::SecondaryChannel::Above,
SecondaryChannel::Below => embedded_svc::wifi::SecondaryChannel::Below,
}
}
}
impl Into<embedded_svc::ipv4::Subnet> for crate::wifi::ipv4::Subnet {
fn into(self) -> embedded_svc::ipv4::Subnet {
embedded_svc::ipv4::Subnet {
gateway: embedded_svc::ipv4::Ipv4Addr::from(self.gateway.octets()),
mask: embedded_svc::ipv4::Mask(self.mask.0),
}
}
}
impl From<embedded_svc::ipv4::Subnet> for crate::wifi::ipv4::Subnet {
fn from(value: embedded_svc::ipv4::Subnet) -> Self {
Self {
gateway: super::ipv4::Ipv4Addr::from(value.gateway.octets()),
mask: super::ipv4::Mask(value.mask.0),
}
}
}
impl Into<embedded_svc::ipv4::IpInfo> for super::ipv4::IpInfo {
fn into(self) -> embedded_svc::ipv4::IpInfo {
embedded_svc::ipv4::IpInfo {
ip: embedded_svc::ipv4::Ipv4Addr::from(self.ip.octets()),
subnet: self.subnet.into(),
dns: self
.dns
.map(|v| embedded_svc::ipv4::Ipv4Addr::from(v.octets())),
secondary_dns: self
.secondary_dns
.map(|v| embedded_svc::ipv4::Ipv4Addr::from(v.octets())),
}
}
}
impl From<&embedded_svc::ipv4::Configuration> for super::ipv4::Configuration {
fn from(value: &embedded_svc::ipv4::Configuration) -> Self {
match value {
embedded_svc::ipv4::Configuration::Client(client) => {
let config = match client {
embedded_svc::ipv4::ClientConfiguration::DHCP(dhcp) => {
super::ipv4::ClientConfiguration::DHCP(
super::ipv4::DHCPClientSettings {
hostname: dhcp.hostname.clone(),
},
)
}
embedded_svc::ipv4::ClientConfiguration::Fixed(fixed) => {
super::ipv4::ClientConfiguration::Fixed(super::ipv4::ClientSettings {
ip: super::ipv4::Ipv4Addr::from(fixed.ip.octets()),
subnet: fixed.subnet.into(),
dns: fixed.dns.map(|v| super::ipv4::Ipv4Addr::from(v.octets())),
secondary_dns: fixed
.secondary_dns
.map(|v| super::ipv4::Ipv4Addr::from(v.octets())),
})
}
};
super::ipv4::Configuration::Client(config)
}
embedded_svc::ipv4::Configuration::Router(router) => {
let config = super::ipv4::RouterConfiguration {
subnet: router.subnet.into(),
dhcp_enabled: router.dhcp_enabled,
dns: router.dns.map(|v| super::ipv4::Ipv4Addr::from(v.octets())),
secondary_dns: router
.secondary_dns
.map(|v| super::ipv4::Ipv4Addr::from(v.octets())),
};
super::ipv4::Configuration::Router(config)
}
}
}
}
impl Into<embedded_svc::ipv4::Configuration> for super::ipv4::Configuration {
fn into(self) -> embedded_svc::ipv4::Configuration {
match self {
super::ipv4::Configuration::Client(client) => {
let config = match client {
super::ipv4::ClientConfiguration::DHCP(dhcp) => {
embedded_svc::ipv4::ClientConfiguration::DHCP(
embedded_svc::ipv4::DHCPClientSettings {
hostname: dhcp.hostname.clone(),
},
)
}
super::ipv4::ClientConfiguration::Fixed(fixed) => {
embedded_svc::ipv4::ClientConfiguration::Fixed(
embedded_svc::ipv4::ClientSettings {
ip: embedded_svc::ipv4::Ipv4Addr::from(fixed.ip.octets()),
subnet: fixed.subnet.into(),
dns: fixed
.dns
.map(|v| embedded_svc::ipv4::Ipv4Addr::from(v.octets())),
secondary_dns: fixed
.secondary_dns
.map(|v| embedded_svc::ipv4::Ipv4Addr::from(v.octets())),
},
)
}
};
embedded_svc::ipv4::Configuration::Client(config)
}
super::ipv4::Configuration::Router(router) => {
let config = embedded_svc::ipv4::RouterConfiguration {
subnet: router.subnet.into(),
dhcp_enabled: router.dhcp_enabled,
dns: router
.dns
.map(|v| embedded_svc::ipv4::Ipv4Addr::from(v.octets())),
secondary_dns: router
.secondary_dns
.map(|v| embedded_svc::ipv4::Ipv4Addr::from(v.octets())),
};
embedded_svc::ipv4::Configuration::Router(config)
}
}
}
}
#[cfg(not(feature = "async"))]
impl embedded_svc::wifi::Wifi for WifiController<'_> {
type Error = WifiError;
fn get_capabilities(&self) -> Result<EnumSet<embedded_svc::wifi::Capability>, Self::Error> {
self.get_capabilities().map(|v| {
let mut res = EnumSet::<embedded_svc::wifi::Capability>::new();
v.into_iter().for_each(|v| {
res.insert(v.into());
});
res
})
}
fn get_configuration(&self) -> Result<embedded_svc::wifi::Configuration, Self::Error> {
self.get_configuration().map(|v| v.into())
}
fn set_configuration(
&mut self,
conf: &embedded_svc::wifi::Configuration,
) -> Result<(), Self::Error> {
let conf = conf.into();
self.set_configuration(&conf)
}
fn start(&mut self) -> Result<(), Self::Error> {
self.start()
}
fn stop(&mut self) -> Result<(), Self::Error> {
self.stop()
}
fn connect(&mut self) -> Result<(), Self::Error> {
self.connect()
}
fn disconnect(&mut self) -> Result<(), Self::Error> {
self.disconnect()
}
fn is_started(&self) -> Result<bool, Self::Error> {
self.is_started()
}
fn is_connected(&self) -> Result<bool, Self::Error> {
self.is_connected()
}
fn scan_n<const N: usize>(
&mut self,
) -> Result<(heapless::Vec<embedded_svc::wifi::AccessPointInfo, N>, usize), Self::Error>
{
self.scan_n::<N>().map(|(v, l)| {
let mut res: heapless::Vec<embedded_svc::wifi::AccessPointInfo, N> =
heapless::Vec::new();
for ap in v {
res.push(ap.into()).ok();
}
(res, l)
})
}
}
#[cfg(feature = "async")]
impl embedded_svc::wifi::asynch::Wifi for WifiController<'_> {
type Error = WifiError;
async fn get_capabilities(
&self,
) -> Result<EnumSet<embedded_svc::wifi::Capability>, Self::Error> {
self.get_capabilities().map(|v| {
let mut res = EnumSet::<embedded_svc::wifi::Capability>::new();
v.into_iter().for_each(|v| {
res.insert(v.into());
});
res
})
}
async fn get_configuration(
&self,
) -> Result<embedded_svc::wifi::Configuration, Self::Error> {
WifiController::get_configuration(self).map(|v| v.into())
}
async fn set_configuration(
&mut self,
conf: &embedded_svc::wifi::Configuration,
) -> Result<(), Self::Error> {
let conf = conf.into();
self.set_configuration(&conf)
}
async fn start(&mut self) -> Result<(), Self::Error> {
self.start().await
}
async fn stop(&mut self) -> Result<(), Self::Error> {
self.stop().await
}
async fn connect(&mut self) -> Result<(), Self::Error> {
self.connect().await
}
async fn disconnect(&mut self) -> Result<(), Self::Error> {
self.disconnect().await
}
async fn is_started(&self) -> Result<bool, Self::Error> {
self.is_started()
}
async fn is_connected(&self) -> Result<bool, Self::Error> {
self.is_connected()
}
async fn scan_n<const N: usize>(
&mut self,
) -> Result<(heapless::Vec<embedded_svc::wifi::AccessPointInfo, N>, usize), Self::Error>
{
self.scan_n::<N>().await.map(|(v, l)| {
let mut res: heapless::Vec<embedded_svc::wifi::AccessPointInfo, N> =
heapless::Vec::new();
for ap in v {
res.push(ap.into()).ok();
}
(res, l)
})
}
}
impl<'a, MODE: WifiDeviceMode> embedded_svc::ipv4::Interface
for crate::wifi_interface::WifiStack<'a, MODE>
{
type Error = crate::wifi_interface::WifiStackError;
fn get_iface_configuration(
&self,
) -> Result<embedded_svc::ipv4::Configuration, Self::Error> {
self.get_iface_configuration().map(|v| v.into())
}
fn set_iface_configuration(
&mut self,
conf: &embedded_svc::ipv4::Configuration,
) -> Result<(), Self::Error> {
self.set_iface_configuration(&super::ipv4::Configuration::from(conf))
}
fn is_iface_up(&self) -> bool {
self.is_iface_up()
}
fn get_ip_info(&self) -> Result<embedded_svc::ipv4::IpInfo, Self::Error> {
self.get_ip_info().map(|v| v.into())
}
}
}