Expose CSI API in esp-wifi (#2422)

* feat: (WIP) add CSI api

* feat: Enable G_CONFIG.csi_enable and update example

* fix: Allow user to set the dessired cb method

* fix: Clippy warnings

* fix: Add missing doccomments

* feat: Add csi_enable config

* refactor: Update CsiConfiguration c6 struct

* feat: Create set_csi WifiController and EspNowManager methods

* docs: Update changelog

* refactor: Rename CsiConfig struct

* docs: Document c6 version of CsiConfig

* feat: impl From<CsiConfig> for crate::include::wifi_csi_config_t

* style: Rustfmt

* docs: Fix comment

Co-authored-by: Dániel Buga <bugadani@gmail.com>

* docs: Fix typo

Co-authored-by: Juraj Sadel <jurajsadel@gmail.com>

* feat: Enable CSI on examples by default

* feat: Handle errors

* style: Rustfmt

* feat: Update error

* feat: Panic if csi config is not enabled

* feat: Cfg CSI stuff when CSI is disabled instead of panicing

* fix: Clippy lints

* feat: Fix signed bitfields

* feat: Pass the cb via ctx

* feat: Update CSI callback to use closures

* refactor: Rename promiscuous_csi_rx_cb to csi_rx_cb

* feat: Move extra boxing inside set_receive_cb

* feat: Refactor CSI callback to use generic types

* refactor: Remove Sized bound from CsiCallback trait

* feat: Add csi_enable field to EspWifiConfig and update CsiCallback trait for conditional compilation

* feat: Remove unnecessary boxes

* feat: Update callback type in set_csi to require Send trait

Co-authored-by: Dominic Fischer <14130965+Dominaezzz@users.noreply.github.com>

---------

Co-authored-by: Dániel Buga <bugadani@gmail.com>
Co-authored-by: Juraj Sadel <jurajsadel@gmail.com>
Co-authored-by: Dominic Fischer <14130965+Dominaezzz@users.noreply.github.com>
This commit is contained in:
Sergio Gasquez Arcos 2024-11-08 17:33:13 +01:00 committed by GitHub
parent 3c4b7f0f66
commit 7402ad61ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 393 additions and 1 deletions

View File

@ -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

View File

@ -105,6 +105,7 @@ fn main() -> Result<(), Box<dyn Error>> {
("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)"),

View File

@ -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,

View File

@ -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()) })

View File

@ -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"),

View File

@ -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<T> CsiCallback for T where T: FnMut(crate::binary::include::wifi_csi_info_t) {}
#[cfg(csi_enable)]
unsafe extern "C" fn csi_rx_cb<C: CsiCallback>(
ctx: *mut crate::wifi::c_types::c_void,
data: *mut crate::binary::include::wifi_csi_info_t,
) {
let csi_callback = unsafe { &mut *(ctx as *mut C) };
csi_callback(*data);
}
#[derive(Clone, PartialEq, Eq)]
// 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<CsiConfig> for wifi_csi_config_t {
fn from(config: CsiConfig) -> Self {
#[cfg(not(esp32c6))]
{
wifi_csi_config_t {
lltf_en: config.lltf_en,
htltf_en: config.htltf_en,
stbc_htltf2_en: config.stbc_htltf2_en,
ltf_merge_en: config.ltf_merge_en,
channel_filter_en: config.channel_filter_en,
manu_scale: config.manu_scale,
shift: config.shift,
dump_ack_en: config.dump_ack_en,
}
}
#[cfg(esp32c6)]
{
wifi_csi_acquire_config_t {
_bitfield_align_1: [0; 0],
_bitfield_1: wifi_csi_acquire_config_t::new_bitfield_1(
config.enable,
config.acquire_csi_legacy,
config.acquire_csi_ht20,
config.acquire_csi_ht40,
config.acquire_csi_su,
config.acquire_csi_mu,
config.acquire_csi_dcm,
config.acquire_csi_beamformed,
config.acquire_csi_he_stbc,
config.val_scale_cfg,
config.dump_ack_en,
config.reserved,
),
}
}
}
}
#[cfg(csi_enable)]
impl CsiConfig {
/// 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<C: CsiCallback>(&mut self, cb: C) -> Result<(), WifiError> {
let cb = alloc::boxed::Box::new(cb);
let cb_ptr = alloc::boxed::Box::into_raw(cb) as *mut crate::wifi::c_types::c_void;
unsafe {
esp_wifi_result!(esp_wifi_set_csi_rx_cb(Some(csi_rx_cb::<C>), cb_ptr))?;
}
Ok(())
}
/// 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

View File

@ -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"]

View File

@ -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<AccessPointInfo, 10>, 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 {}
}