diff --git a/esp-wifi/CHANGELOG.md b/esp-wifi/CHANGELOG.md index 1280fa9c3..6d2275777 100644 --- a/esp-wifi/CHANGELOG.md +++ b/esp-wifi/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `serde` support through the `serde` feature (#2346) - Added `PowerSaveMode` and `set_power_saving` methods on `EspNowManager` & `WifiController` (#2446) - Added CSI support (#2422) +- Enable setting event handlers for wifi events (#2453) ### Changed diff --git a/esp-wifi/src/wifi/event.rs b/esp-wifi/src/wifi/event.rs new file mode 100644 index 000000000..f0daf78d4 --- /dev/null +++ b/esp-wifi/src/wifi/event.rs @@ -0,0 +1,348 @@ +use alloc::boxed::Box; +use core::cell::RefCell; + +use critical_section::Mutex; + +use super::WifiEvent; + +pub(crate) mod sealed { + use super::*; + + pub trait Event { + /// Get the static reference to the handler for this event. + fn handler() -> &'static Mutex>>>>; + /// # Safety + /// `ptr` must be a valid for casting to this event's inner event data. + unsafe fn from_raw_event_data(ptr: *mut crate::binary::c_types::c_void) -> Self; + } +} +/// The type of handlers of events. +pub type Handler = dyn FnMut(critical_section::CriticalSection<'_>, &T) + Sync + Send; + +fn default_handler() -> Box> { + fn drop_ref(_: critical_section::CriticalSection<'_>, _: &T) {} + // perf: `drop_ref` is a ZST [function item](https://doc.rust-lang.org/reference/types/function-item.html) + // so this doesn't actually allocate. + Box::new(drop_ref) +} + +/// Extension trait for setting handlers for an event. +/// +/// Register a new event handler like: +/// ``` +/// # use esp_wifi::wifi::event::{self, *}; +/// # fn new_handler(_: critical_section::CriticalSection<'_>, _: &ApStaconnected) {} +/// event::ApStaconnected::update_handler(|_cs, event| { +/// new_handler(event); +/// }) +/// ``` +// Implemented like this instead of free functions because the syntax would be +// ``` +// event::update_handler::(...) +// ``` +pub trait EventExt: sealed::Event + Sized + 'static { + /// Get the handler for this event, replacing it with the default handler. + fn take_handler() -> Box> { + critical_section::with(|cs| { + Self::handler() + .borrow_ref_mut(cs) + .take() + .unwrap_or_else(default_handler::) + }) + } + /// Set the handler for this event, returning the old handler. + fn replace_handler< + F: FnMut(critical_section::CriticalSection<'_>, &Self) + Sync + Send + 'static, + >( + f: F, + ) -> Box> { + critical_section::with(|cs| { + Self::handler() + .borrow_ref_mut(cs) + .replace(Box::new(f)) + .unwrap_or_else(default_handler::) + }) + } + /// Atomic combination of [`take_handler`] and [`replace_handler`]. Use this + /// to add a new handler which runs after the previously registered + /// handlers. + fn update_handler< + F: FnMut(critical_section::CriticalSection<'_>, &Self) + Sync + Send + 'static, + >( + mut f: F, + ) { + critical_section::with(move |cs| { + let mut handler: Box> = Self::handler() + .borrow_ref_mut(cs) + .take() + .unwrap_or_else(default_handler::); + Self::handler().borrow_ref_mut(cs).replace(Box::new( + move |cs: critical_section::CriticalSection<'_>, event| { + handler(cs, event); + f(cs, event) + }, + )); + }); + } +} +impl EventExt for T {} + +macro_rules! impl_wifi_event { + // no data + ($newtype:ident) => { + /// See [`WifiEvent`]. + #[derive(Copy, Clone)] + pub struct $newtype; + impl sealed::Event for $newtype { + unsafe fn from_raw_event_data(_: *mut crate::binary::c_types::c_void) -> Self { + Self + } + fn handler() -> &'static Mutex>>>> { + static HANDLE: Mutex>>>> = + Mutex::new(RefCell::new(None)); + &HANDLE + } + } + }; + // data + ($newtype:ident, $data:ident) => { + pub use esp_wifi_sys::include::$data; + /// See [`WifiEvent`]. + #[derive(Copy, Clone)] + pub struct $newtype(pub $data); + impl sealed::Event for $newtype { + unsafe fn from_raw_event_data(ptr: *mut crate::binary::c_types::c_void) -> Self { + Self(unsafe { *ptr.cast() }) + } + fn handler() -> &'static Mutex>>>> { + static HANDLE: Mutex>>>> = + Mutex::new(RefCell::new(None)); + &HANDLE + } + } + }; +} + +impl_wifi_event!(WifiReady); +impl_wifi_event!(ScanDone); +impl_wifi_event!(StaStart); +impl_wifi_event!(StaStop); +impl_wifi_event!(StaConnected, wifi_event_sta_connected_t); +impl_wifi_event!(StaDisconnected, wifi_event_sta_disconnected_t); +impl_wifi_event!(StaAuthmodeChange, wifi_event_sta_authmode_change_t); +impl_wifi_event!(StaWpsErSuccess, wifi_event_sta_wps_er_success_t); +impl_wifi_event!(StaWpsErFailed); +impl_wifi_event!(StaWpsErTimeout); +impl_wifi_event!(StaWpsErPin, wifi_event_sta_wps_er_pin_t); +impl_wifi_event!(StaWpsErPbcOverlap); +impl_wifi_event!(ApStart); +impl_wifi_event!(ApStop); +impl_wifi_event!(ApStaconnected, wifi_event_ap_staconnected_t); +impl_wifi_event!(ApStadisconnected, wifi_event_ap_stadisconnected_t); +impl_wifi_event!(ApProbereqrecved, wifi_event_ap_probe_req_rx_t); +impl_wifi_event!(FtmReport, wifi_event_ftm_report_t); +impl_wifi_event!(StaBssRssiLow, wifi_event_bss_rssi_low_t); +impl_wifi_event!(ActionTxStatus, wifi_event_action_tx_status_t); +impl_wifi_event!(RocDone, wifi_event_roc_done_t); +impl_wifi_event!(StaBeaconTimeout); +impl_wifi_event!(ConnectionlessModuleWakeIntervalStart); +impl_wifi_event!(ApWpsRgSuccess, wifi_event_ap_wps_rg_success_t); +impl_wifi_event!(ApWpsRgFailed, wifi_event_ap_wps_rg_fail_reason_t); +impl_wifi_event!(ApWpsRgTimeout); +impl_wifi_event!(ApWpsRgPin, wifi_event_ap_wps_rg_pin_t); +impl_wifi_event!(ApWpsRgPbcOverlap); +impl_wifi_event!(ItwtSetup); +impl_wifi_event!(ItwtTeardown); +impl_wifi_event!(ItwtProbe); +impl_wifi_event!(ItwtSuspend); +impl_wifi_event!(TwtWakeup); +impl_wifi_event!(BtwtSetup); +impl_wifi_event!(BtwtTeardown); +impl_wifi_event!(NanStarted); +impl_wifi_event!(NanStopped); +impl_wifi_event!(NanSvcMatch, wifi_event_nan_svc_match_t); +impl_wifi_event!(NanReplied, wifi_event_nan_replied_t); +impl_wifi_event!(NanReceive, wifi_event_nan_receive_t); +impl_wifi_event!(NdpIndication, wifi_event_ndp_indication_t); +impl_wifi_event!(NdpConfirm, wifi_event_ndp_confirm_t); +impl_wifi_event!(NdpTerminated, wifi_event_ndp_terminated_t); +impl_wifi_event!(HomeChannelChange, wifi_event_home_channel_change_t); +impl_wifi_event!(StaNeighborRep, wifi_event_neighbor_report_t); + +/// Handle the given event using the registered event handlers. +pub fn handle( + cs: critical_section::CriticalSection<'_>, + event_data: &Event, +) -> bool { + if let Some(handler) = &mut *Event::handler().borrow_ref_mut(cs) { + handler(cs, event_data); + true + } else { + false + } +} + +/// Handle an event given the raw pointers. +/// # Safety +/// The pointer should be valid to cast to `Event`'s inner type (if it has one) +pub(crate) unsafe fn handle_raw( + cs: critical_section::CriticalSection<'_>, + event_data: *mut crate::binary::c_types::c_void, + event_data_size: usize, +) -> bool { + debug_assert_eq!( + event_data_size, + core::mem::size_of::(), + "wrong size event data" + ); + handle::(cs, unsafe { &Event::from_raw_event_data(event_data) }) +} + +/// Handle event regardless of its type. +/// # Safety +/// Arguments should be self-consistent. +#[rustfmt::skip] +pub(crate) unsafe fn dispatch_event_handler( + cs: critical_section::CriticalSection<'_>, + event: WifiEvent, + event_data: *mut crate::binary::c_types::c_void, + event_data_size: usize, +) -> bool { + match event { + WifiEvent::WifiReady => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::ScanDone => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::StaStart => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::StaStop => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::StaConnected => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::StaDisconnected => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::StaAuthmodeChange => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::StaWpsErSuccess => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::StaWpsErFailed => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::StaWpsErTimeout => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::StaWpsErPin => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::StaWpsErPbcOverlap => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::ApStart => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::ApStop => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::ApStaconnected => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::ApStadisconnected => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::ApProbereqrecved => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::FtmReport => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::StaBssRssiLow => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::ActionTxStatus => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::RocDone => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::StaBeaconTimeout => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::ConnectionlessModuleWakeIntervalStart => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::ApWpsRgSuccess => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::ApWpsRgFailed => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::ApWpsRgTimeout => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::ApWpsRgPin => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::ApWpsRgPbcOverlap => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::ItwtSetup => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::ItwtTeardown => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::ItwtProbe => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::ItwtSuspend => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::TwtWakeup => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::BtwtSetup => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::BtwtTeardown => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::NanStarted => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::NanStopped => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::NanSvcMatch => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::NanReplied => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::NanReceive => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::NdpIndication => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::NdpConfirm => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::NdpTerminated => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::HomeChannelChange => { + handle_raw::(cs, event_data, event_data_size) + } + WifiEvent::StaNeighborRep => { + handle_raw::(cs, event_data, event_data_size) + } + } +} diff --git a/esp-wifi/src/wifi/mod.rs b/esp-wifi/src/wifi/mod.rs index b2e2abafc..cad2758a2 100644 --- a/esp-wifi/src/wifi/mod.rs +++ b/esp-wifi/src/wifi/mod.rs @@ -1,5 +1,6 @@ //! WiFi +pub mod event; pub(crate) mod os_adapter; pub(crate) mod state; diff --git a/esp-wifi/src/wifi/os_adapter.rs b/esp-wifi/src/wifi/os_adapter.rs index cd87ab663..862ae5cb2 100644 --- a/esp-wifi/src/wifi/os_adapter.rs +++ b/esp-wifi/src/wifi/os_adapter.rs @@ -866,9 +866,14 @@ pub unsafe extern "C" fn event_post( let event = unwrap!(WifiEvent::from_i32(event_id)); trace!("EVENT: {:?}", event); - critical_section::with(|cs| WIFI_EVENTS.borrow_ref_mut(cs).insert(event)); - super::state::update_state(event); + let mut handled = false; + critical_section::with(|cs| { + WIFI_EVENTS.borrow_ref_mut(cs).insert(event); + handled = super::event::dispatch_event_handler(cs, event, event_data, event_data_size); + }); + + super::state::update_state(event, handled); event.waker().wake(); diff --git a/esp-wifi/src/wifi/state.rs b/esp-wifi/src/wifi/state.rs index e11f584f5..507209148 100644 --- a/esp-wifi/src/wifi/state.rs +++ b/esp-wifi/src/wifi/state.rs @@ -47,7 +47,7 @@ pub fn get_sta_state() -> WifiState { STA_STATE.load(Ordering::Relaxed) } -pub(crate) fn update_state(event: WifiEvent) { +pub(crate) fn update_state(event: WifiEvent, handled: bool) { match event { WifiEvent::StaConnected | WifiEvent::StaDisconnected @@ -58,7 +58,11 @@ pub(crate) fn update_state(event: WifiEvent) { AP_STATE.store(WifiState::from(event), Ordering::Relaxed) } - other => debug!("Unhandled event: {:?}", other), + other => { + if !handled { + debug!("Unhandled event: {:?}", other) + } + } } } diff --git a/examples/src/bin/wifi_access_point.rs b/examples/src/bin/wifi_access_point.rs index 8a5c1387b..fcd699cd0 100644 --- a/examples/src/bin/wifi_access_point.rs +++ b/examples/src/bin/wifi_access_point.rs @@ -28,6 +28,7 @@ use esp_println::{print, println}; use esp_wifi::{ init, wifi::{ + event::{self, EventExt}, utils::create_network_interface, AccessPointConfiguration, Configuration, @@ -49,6 +50,24 @@ fn main() -> ! { let timg0 = TimerGroup::new(peripherals.TIMG0); + // Set event handlers for wifi before init to avoid missing any. + let mut connections = 0u32; + _ = event::ApStart::replace_handler(|_, _| esp_println::println!("ap start event")); + event::ApStaconnected::update_handler(move |_, event| { + connections += 1; + esp_println::println!("connected {}, mac: {:?}", connections, event.0.mac); + }); + event::ApStaconnected::update_handler(|_, event| { + esp_println::println!("connected aid: {}", event.0.aid); + }); + event::ApStadisconnected::update_handler(|_, event| { + esp_println::println!( + "disconnected mac: {:?}, reason: {:?}", + event.0.mac, + event.0.reason + ); + }); + let mut rng = Rng::new(peripherals.RNG); let init = init(timg0.timer0, rng.clone(), peripherals.RADIO_CLK).unwrap();