diff --git a/esp-wifi/CHANGELOG.md b/esp-wifi/CHANGELOG.md index 7351d11a835..96050adf2da 100644 --- a/esp-wifi/CHANGELOG.md +++ b/esp-wifi/CHANGELOG.md @@ -11,6 +11,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) ### Changed diff --git a/esp-wifi/build.rs b/esp-wifi/build.rs index 23be6c6ab3b..4384034181b 100644 --- a/esp-wifi/build.rs +++ b/esp-wifi/build.rs @@ -105,6 +105,7 @@ fn main() -> Result<(), Box> { ("dynamic_rx_buf_num", Value::UnsignedInteger(32), "WiFi dynamic RX buffer number. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)"), ("static_tx_buf_num", Value::UnsignedInteger(0), "WiFi static TX buffer number. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)"), ("dynamic_tx_buf_num", Value::UnsignedInteger(32), "WiFi dynamic TX buffer number. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)"), + ("csi_enable", Value::Bool(false), "WiFi channel state information enable flag. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)"), ("ampdu_rx_enable", Value::Bool(true), "WiFi AMPDU RX feature enable flag. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)"), ("ampdu_tx_enable", Value::Bool(true), "WiFi AMPDU TX feature enable flag. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)"), ("amsdu_tx_enable", Value::Bool(false), "WiFi AMSDU TX feature enable flag. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)"), diff --git a/esp-wifi/src/config.rs b/esp-wifi/src/config.rs index 92f2a97e167..0b9c77fecb4 100644 --- a/esp-wifi/src/config.rs +++ b/esp-wifi/src/config.rs @@ -9,6 +9,7 @@ pub(crate) struct EspWifiConfig { pub(crate) dynamic_rx_buf_num: usize, pub(crate) static_tx_buf_num: usize, pub(crate) dynamic_tx_buf_num: usize, + pub(crate) csi_enable: bool, pub(crate) ampdu_rx_enable: bool, pub(crate) ampdu_tx_enable: bool, pub(crate) amsdu_tx_enable: bool, diff --git a/esp-wifi/src/esp_now/mod.rs b/esp-wifi/src/esp_now/mod.rs index cc65c355577..d4f26fdb6fe 100644 --- a/esp-wifi/src/esp_now/mod.rs +++ b/esp-wifi/src/esp_now/mod.rs @@ -18,6 +18,8 @@ use portable_atomic::{AtomicBool, AtomicU8, Ordering}; #[cfg(not(coex))] use crate::config::PowerSaveMode; +#[cfg(csi_enable)] +use crate::wifi::CsiConfig; use crate::{ binary::include::*, hal::peripheral::{Peripheral, PeripheralRef}, @@ -369,6 +371,20 @@ impl EspNowManager<'_> { check_error!({ esp_now_add_peer(&raw_peer as *const _) }) } + /// Set CSI configuration and register the receiving callback. + #[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(()) + } + /// Remove the given peer. pub fn remove_peer(&self, peer_address: &[u8; 6]) -> Result<(), EspNowError> { check_error!({ esp_now_del_peer(peer_address.as_ptr()) }) diff --git a/esp-wifi/src/lib.rs b/esp-wifi/src/lib.rs index 3a7d1e195b5..2d5849da440 100644 --- a/esp-wifi/src/lib.rs +++ b/esp-wifi/src/lib.rs @@ -164,6 +164,34 @@ const _: () = { }; }; +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Tunable parameters for the WiFi driver +#[allow(unused)] // currently there are no ble tunables +struct Config { + rx_queue_size: usize, + tx_queue_size: usize, + static_rx_buf_num: usize, + dynamic_rx_buf_num: usize, + static_tx_buf_num: usize, + dynamic_tx_buf_num: usize, + csi_enable: bool, + ampdu_rx_enable: bool, + ampdu_tx_enable: bool, + amsdu_tx_enable: bool, + rx_ba_win: usize, + max_burst_size: usize, + country_code: &'static str, + country_code_operating_class: u8, + mtu: usize, + tick_rate_hz: u32, + listen_interval: u16, + beacon_timeout: u16, + ap_beacon_timeout: u16, + failure_retry_cnt: u8, + scan_method: u32, +} + pub(crate) const CONFIG: config::EspWifiConfig = config::EspWifiConfig { rx_queue_size: esp_config_int!(usize, "ESP_WIFI_RX_QUEUE_SIZE"), tx_queue_size: esp_config_int!(usize, "ESP_WIFI_TX_QUEUE_SIZE"), @@ -171,6 +199,7 @@ pub(crate) const CONFIG: config::EspWifiConfig = config::EspWifiConfig { dynamic_rx_buf_num: esp_config_int!(usize, "ESP_WIFI_DYNAMIC_RX_BUF_NUM"), static_tx_buf_num: esp_config_int!(usize, "ESP_WIFI_STATIC_TX_BUF_NUM"), dynamic_tx_buf_num: esp_config_int!(usize, "ESP_WIFI_DYNAMIC_TX_BUF_NUM"), + csi_enable: esp_config_bool!("ESP_WIFI_CSI_ENABLE"), ampdu_rx_enable: esp_config_bool!("ESP_WIFI_AMPDU_RX_ENABLE"), ampdu_tx_enable: esp_config_bool!("ESP_WIFI_AMPDU_TX_ENABLE"), amsdu_tx_enable: esp_config_bool!("ESP_WIFI_AMSDU_TX_ENABLE"), diff --git a/esp-wifi/src/wifi/mod.rs b/esp-wifi/src/wifi/mod.rs index cfc22ef2ddb..e5b4f494268 100644 --- a/esp-wifi/src/wifi/mod.rs +++ b/esp-wifi/src/wifi/mod.rs @@ -83,6 +83,17 @@ 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::{ @@ -340,6 +351,191 @@ impl Default for ClientConfiguration { } } +#[cfg(csi_enable)] +pub(crate) trait CsiCallback: FnMut(crate::binary::include::wifi_csi_info_t) {} + +#[cfg(csi_enable)] +impl CsiCallback for T where T: FnMut(crate::binary::include::wifi_csi_info_t) {} + +#[cfg(csi_enable)] +unsafe extern "C" fn csi_rx_cb( + 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)] +// https://github.com/esp-rs/esp-wifi-sys/blob/main/esp-wifi-sys/headers/local/esp_wifi_types_native.h#L94 +/// Channel state information(CSI) configuration +#[cfg(all(not(esp32c6), csi_enable))] +pub struct CsiConfig { + /// Enable to receive legacy long training field(lltf) data. + pub lltf_en: bool, + /// Enable to receive HT long training field(htltf) data. + pub htltf_en: bool, + /// Enable to receive space time block code HT long training + /// field(stbc-htltf2) data. + pub stbc_htltf2_en: bool, + /// Enable to generate htlft data by averaging lltf and ht_ltf data when + /// receiving HT packet. Otherwise, use ht_ltf data directly. + pub ltf_merge_en: bool, + /// Enable to turn on channel filter to smooth adjacent sub-carrier. Disable + /// it to keep independence of adjacent sub-carrier. + pub channel_filter_en: bool, + /// Manually scale the CSI data by left shifting or automatically scale the + /// CSI data. If set true, please set the shift bits. false: automatically. + /// true: manually. + pub manu_scale: bool, + /// Manually left shift bits of the scale of the CSI data. The range of the + /// left shift bits is 0~15. + pub shift: u8, + /// Enable to dump 802.11 ACK frame. + pub dump_ack_en: bool, +} + +#[derive(Clone, PartialEq, Eq)] +#[cfg(all(esp32c6, csi_enable))] +// See https://github.com/esp-rs/esp-wifi-sys/blob/2a466d96fe8119d49852fc794aea0216b106ba7b/esp-wifi-sys/src/include/esp32c6.rs#L5702-L5705 +pub struct CsiConfig { + /// Enable to acquire CSI. + pub enable: u32, + /// Enable to acquire L-LTF when receiving a 11g PPDU. + pub acquire_csi_legacy: u32, + /// Enable to acquire HT-LTF when receiving an HT20 PPDU. + pub acquire_csi_ht20: u32, + /// Enable to acquire HT-LTF when receiving an HT40 PPDU. + pub acquire_csi_ht40: u32, + /// Enable to acquire HE-LTF when receiving an HE20 SU PPDU. + pub acquire_csi_su: u32, + /// Enable to acquire HE-LTF when receiving an HE20 MU PPDU. + pub acquire_csi_mu: u32, + /// Enable to acquire HE-LTF when receiving an HE20 DCM applied PPDU. + pub acquire_csi_dcm: u32, + /// Enable to acquire HE-LTF when receiving an HE20 Beamformed applied PPDU. + pub acquire_csi_beamformed: u32, + /// Wwhen receiving an STBC applied HE PPDU, 0- acquire the complete + /// HE-LTF1, 1- acquire the complete HE-LTF2, 2- sample evenly among the + /// HE-LTF1 and HE-LTF2. + pub acquire_csi_he_stbc: u32, + /// Vvalue 0-3. + pub val_scale_cfg: u32, + /// Enable to dump 802.11 ACK frame, default disabled. + pub dump_ack_en: u32, + /// Reserved. + 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 { + // https://github.com/esp-rs/esp-wifi-sys/blob/2a466d96fe8119d49852fc794aea0216b106ba7b/esp-wifi-sys/headers/esp_wifi_he_types.h#L67-L82 + 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 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 { + /// Set CSI data configuration + 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(()) + } + + /// Register the RX callback function of CSI data. Each time a CSI data is + /// received, the callback function will be called. + pub(crate) fn set_receive_cb(&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::), cb_ptr))?; + } + Ok(()) + } + + /// Enable or disable CSI + pub(crate) fn set_csi(&self, enable: bool) -> Result<(), WifiError> { + // https://github.com/esp-rs/esp-wifi-sys/blob/2a466d96fe8119d49852fc794aea0216b106ba7b/esp-wifi-sys/headers/esp_wifi.h#L1241 + unsafe { + esp_wifi_result!(esp_wifi_set_csi(enable))?; + } + Ok(()) + } +} + /// Configuration for EAP-FAST authentication protocol. #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -1524,7 +1720,7 @@ static mut G_CONFIG: wifi_init_config_t = wifi_init_config_t { 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: esp_wifi_sys::include::WIFI_CSI_ENABLED 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, @@ -2536,6 +2732,20 @@ impl<'d> WifiController<'d> { } } + /// Set CSI configuration and register the receiving callback. + #[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(()) + } + /// Set the wifi protocol. /// /// This will set the wifi protocol to the desired protocol, the default for diff --git a/examples/.cargo/config.toml b/examples/.cargo/config.toml index 7fcbcbfad75..600f74b2ea4 100644 --- a/examples/.cargo/config.toml +++ b/examples/.cargo/config.toml @@ -33,6 +33,7 @@ PASSWORD = "PASSWORD" STATIC_IP = "1.1.1.1 " GATEWAY_IP = "1.1.1.1" HOST_IP = "1.1.1.1" +ESP_WIFI_CSI_ENABLE = "true" [unstable] build-std = ["alloc", "core"] diff --git a/examples/src/bin/wifi_csi.rs b/examples/src/bin/wifi_csi.rs new file mode 100644 index 00000000000..e330c3d6cf8 --- /dev/null +++ b/examples/src/bin/wifi_csi.rs @@ -0,0 +1,133 @@ +//! CSI Example +//! +//! +//! Set SSID and PASSWORD env variable before running this example. +//! + +//% FEATURES: esp-wifi esp-wifi/wifi-default esp-wifi/wifi esp-wifi/utils esp-wifi/log +//% CHIPS: esp32 esp32s2 esp32s3 esp32c2 esp32c3 esp32c6 + +#![no_std] +#![no_main] + +extern crate alloc; + +use esp_alloc as _; +use esp_backtrace as _; +use esp_hal::{ + prelude::*, + rng::Rng, + time::{self}, + timer::timg::TimerGroup, +}; +use esp_println::println; +use esp_wifi::{ + init, + wifi::{ + utils::create_network_interface, + AccessPointInfo, + ClientConfiguration, + Configuration, + CsiConfig, + WifiError, + WifiStaDevice, + }, + wifi_interface::WifiStack, + // EspWifiInitFor, +}; +use smoltcp::iface::SocketStorage; + +const SSID: &str = env!("SSID"); +const PASSWORD: &str = env!("PASSWORD"); + +#[entry] +fn main() -> ! { + esp_println::logger::init_logger_from_env(); + let peripherals = esp_hal::init({ + let mut config = esp_hal::Config::default(); + config.cpu_clock = CpuClock::max(); + config + }); + + esp_alloc::heap_allocator!(72 * 1024); + + let timg0 = TimerGroup::new(peripherals.TIMG0); + let init = init( + timg0.timer0, + Rng::new(peripherals.RNG), + peripherals.RADIO_CLK, + ) + .unwrap(); + + let mut wifi = peripherals.WIFI; + let mut socket_set_entries: [SocketStorage; 3] = Default::default(); + let (iface, device, mut controller, sockets) = + create_network_interface(&init, &mut wifi, WifiStaDevice, &mut socket_set_entries).unwrap(); + let now = || time::now().duration_since_epoch().to_millis(); + let wifi_stack = WifiStack::new(iface, device, sockets, now); + + let client_config = Configuration::Client(ClientConfiguration { + ssid: SSID.try_into().unwrap(), + password: PASSWORD.try_into().unwrap(), + ..Default::default() + }); + let res = controller.set_configuration(&client_config); + println!("wifi_set_configuration returned {:?}", res); + + controller.start().unwrap(); + println!("is wifi started: {:?}", controller.is_started()); + + let csi = CsiConfig::default(); + controller + .set_csi(csi, |data: esp_wifi::wifi::wifi_csi_info_t| { + let rx_ctrl = data.rx_ctrl; + // Signed bitfields are broken in rust-bingen, see https://github.com/esp-rs/esp-wifi-sys/issues/482 + let rssi = if rx_ctrl.rssi() > 127 { + rx_ctrl.rssi() - 256 + } else { + rx_ctrl.rssi() + }; + println!("rssi: {:?} rate: {}", rssi, rx_ctrl.rate()); + }) + .unwrap(); + + println!("Waiting for CSI data..."); + println!("Start Wifi Scan"); + let res: Result<(heapless::Vec, usize), WifiError> = controller.scan_n(); + if let Ok((res, _count)) = res { + for ap in res { + println!("{:?}", ap); + } + } + + println!("{:?}", controller.get_capabilities()); + println!("wifi_connect {:?}", controller.connect()); + + // wait to get connected + println!("Wait to get connected"); + loop { + match controller.is_connected() { + Ok(true) => break, + Ok(false) => {} + Err(err) => { + println!("{:?}", err); + loop {} + } + } + } + println!("{:?}", controller.is_connected()); + + // wait for getting an ip address + println!("Wait to get an ip address"); + loop { + wifi_stack.work(); + + if wifi_stack.is_iface_up() { + println!("got ip {:?}", wifi_stack.get_ip_info()); + break; + } + } + + println!("Start busy loop on main"); + loop {} +}