#![no_std] #![no_main] #![deny( clippy::mem_forget, reason = "mem::forget is generally not safe to do with esp_hal types, especially those \ holding buffers for the duration of a data transfer." )] #![deny(clippy::large_stack_frames)] use embassy_executor::Spawner; use embassy_time::{Duration, Timer}; use esp_backtrace as _; use esp_hal::clock::CpuClock; use esp_hal::timer::timg::TimerGroup; use log::{debug, error, info, warn}; extern crate alloc; const WIFI_CHANNEL: core::ffi::c_int = 180; unsafe extern "C" { /// Changes the 802.11 PHY channel /// /// C function signature: `void phy_change_channel(int,int,int,int)` /// /// Options: /// - `primary`: Channel frequency in MHz /// - `ignored1`: Ignored (set to 1) /// - `ignored2`: Ignored (set to 0) /// - `ht_mode`: Probably HT mode pub unsafe fn phy_change_channel( primary: core::ffi::c_int, ignored1: core::ffi::c_int, ignored2: core::ffi::c_int, ht_mode: core::ffi::c_int, ) -> core::ffi::c_int; /// Enables 802.11p mode on the ESP32-C5 /// /// C function signature: `void phy_11p_set(int,int)` /// /// Options: /// - `enable`: Boolean option to enable 802.11p mode /// - `zero`: Unknown, but need to be zero pub unsafe fn phy_11p_set(enable: u8, zero: u8); } #[repr(u8)] #[derive(Debug)] #[allow(dead_code)] enum EspWifiPromiscuousPktType { /// Management frame, indicates 'buf' argument is wifi_promiscuous_pkt_t MGMT = 0, /// Control frame, indicates 'buf' argument is wifi_promiscuous_pkt_t CTRL = 1, /// Data frame, indicates 'buf' argument is wifi_promiscuous_pkt_t DATA = 2, /// Other type, such as MIMO etc. 'buf' argument is wifi_promiscuous_pkt_t but the payload is zero length. MISC = 3, } impl EspWifiPromiscuousPktType { #[allow(dead_code)] pub fn as_repr(self) -> u8 { self as u8 } /// Converts to underlying type of `wifi_promiscuous_pkt_type_t` (used by `esp_radio::wifi::sniffer::PromiscuousPkt::frame_type`) pub fn as_frame_type(self) -> core::ffi::c_uint { (self as u8) as core::ffi::c_uint } } // This creates a default app-descriptor required by the esp-idf bootloader. // For more information see: esp_bootloader_esp_idf::esp_app_desc!(); #[allow( clippy::large_stack_frames, reason = "it's not unusual to allocate larger buffers etc. in main" )] #[esp_rtos::main] async fn main(spawner: Spawner) -> ! { // generator version: 1.3.0 // generator parameters: --chip esp32c5 -o esp32c5-wroom-1-psram -o alloc -o log -o unstable-hal -o wifi -o esp-backtrace -o embassy esp_println::logger::init_logger_from_env(); let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); let peripherals = esp_hal::init(config); // The following pins are used to bootstrap the chip. They are available // for use, but check the datasheet of the module for more information on them. // - GPIO2 // - GPIO3 // - GPIO7 // - GPIO25 // - GPIO26 // - GPIO27 // - GPIO28 esp_alloc::heap_allocator!(#[esp_hal::ram(reclaimed)] size: 65536); let timg0 = TimerGroup::new(peripherals.TIMG0); let sw_interrupt = esp_hal::interrupt::software::SoftwareInterruptControl::new(peripherals.SW_INTERRUPT); esp_rtos::start(timg0.timer0, sw_interrupt.software_interrupt0); info!("Embassy initialized!"); let (_wifi_controller, interfaces) = esp_radio::wifi::new( peripherals.WIFI, esp_radio::wifi::ControllerConfig::default(), ) .expect("Failed to initialize Wi-Fi controller"); let mut sniffer = interfaces.sniffer; sniffer .set_promiscuous_mode(true) .expect("Failed to enable promiscuous mode"); sniffer.set_receive_cb(|packet| { // Ignore frames with errors and non-data frames if packet.rx_cntl.rx_state != 0 { warn!("Received frame has RX error: {}", packet.rx_cntl.rx_state); return; } if packet.frame_type != EspWifiPromiscuousPktType::DATA.as_frame_type() { debug!("Received frame is not a DATA frame: {}", packet.frame_type); return; } // TODO: handle 802.11 frame info!("Received frame with {} bytes", packet.len); }); unsafe { phy_11p_set(1, 0); match phy_change_channel(5000 + (WIFI_CHANNEL * 5), 1, 0, 0) { 0 => {} ret => { error!("ERROR: Failed to change channel: {ret}"); } } } info!("WiFi promiscuous mode on channel {WIFI_CHANNEL} running"); // TODO: Spawn some tasks let _ = spawner; loop { // nothing to do here, all data is handled in RX callback for now // but use a timer so that this is yields to other tasks Timer::after(Duration::from_secs(1)).await; } }