diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index d075aec2..944beaac 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -143,4 +143,3 @@ async fn main(spawner: Spawner) { } } } - diff --git a/src/control.rs b/src/control.rs index 0c06009b..934bade2 100644 --- a/src/control.rs +++ b/src/control.rs @@ -6,11 +6,11 @@ use embassy_time::{Duration, Timer}; pub use crate::bus::SpiBusCyw43; use crate::consts::*; -use crate::events::{Event, Events}; +use crate::events::{Event, EventSubscriber, Events}; use crate::fmt::Bytes; use crate::ioctl::{IoctlState, IoctlType}; use crate::structs::*; -use crate::{countries, PowerManagementMode}; +use crate::{countries, events, PowerManagementMode}; pub struct Control<'a> { state_ch: ch::StateRunner<'a>, @@ -245,9 +245,13 @@ impl<'a> Control<'a> { } async fn set_iovar(&mut self, name: &str, val: &[u8]) { + self.set_iovar_v::<64>(name, val).await + } + + async fn set_iovar_v(&mut self, name: &str, val: &[u8]) { info!("set {} = {:02x}", name, Bytes(val)); - let mut buf = [0; 64]; + let mut buf = [0; BUFSIZE]; buf[..name.len()].copy_from_slice(name.as_bytes()); buf[name.len()] = 0; buf[name.len() + 1..][..val.len()].copy_from_slice(val); @@ -304,4 +308,69 @@ impl<'a> Control<'a> { resp_len } + + /// Start a wifi scan + /// + /// Returns a `Stream` of networks found by the device + /// + /// # Note + /// Device events are currently implemented using a bounded queue. + /// To not miss any events, you should make sure to always await the stream. + pub async fn scan(&mut self) -> Scanner<'_> { + const SCANTYPE_PASSIVE: u8 = 1; + + let scan_params = ScanParams { + version: 1, + action: 1, + sync_id: 1, + ssid_len: 0, + ssid: [0; 32], + bssid: [0xff; 6], + bss_type: 2, + scan_type: SCANTYPE_PASSIVE, + nprobes: !0, + active_time: !0, + passive_time: !0, + home_time: !0, + channel_num: 0, + channel_list: [0; 1], + }; + + self.events.mask.enable(&[Event::ESCAN_RESULT]); + let subscriber = self.events.queue.subscriber().unwrap(); + self.set_iovar_v::<256>("escan", &scan_params.to_bytes()).await; + + Scanner { + subscriber, + events: &self.events, + } + } +} + +pub struct Scanner<'a> { + subscriber: EventSubscriber<'a>, + events: &'a Events, +} + +impl Scanner<'_> { + /// wait for the next found network + pub async fn next(&mut self) -> Option { + let event = self.subscriber.next_message_pure().await; + if event.header.status != EStatus::PARTIAL { + self.events.mask.disable_all(); + return None; + } + + if let events::Payload::BssInfo(bss) = event.payload { + Some(bss) + } else { + None + } + } +} + +impl Drop for Scanner<'_> { + fn drop(&mut self) { + self.events.mask.disable_all(); + } } diff --git a/src/events.rs b/src/events.rs index d6f114ed..a94c49a0 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,10 +1,12 @@ -#![allow(unused)] +#![allow(dead_code)] #![allow(non_camel_case_types)] use core::cell::RefCell; use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embassy_sync::pubsub::{PubSubChannel, Publisher, Subscriber}; +use embassy_sync::pubsub::{PubSubChannel, Subscriber}; + +use crate::structs::BssInfo; #[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -286,7 +288,6 @@ pub enum Event { // TODO this PubSub can probably be replaced with shared memory to make it a bit more efficient. pub type EventQueue = PubSubChannel; -pub type EventPublisher<'a> = Publisher<'a, NoopRawMutex, Message, 2, 1, 1>; pub type EventSubscriber<'a> = Subscriber<'a, NoopRawMutex, Message, 2, 1, 1>; pub struct Events { @@ -313,6 +314,7 @@ pub struct Status { #[derive(Clone, Copy)] pub enum Payload { None, + BssInfo(BssInfo), } #[derive(Clone, Copy)] @@ -344,7 +346,7 @@ impl EventMask { let word = n / u32::BITS; let bit = n % u32::BITS; - self.mask[word as usize] |= (1 << bit); + self.mask[word as usize] |= 1 << bit; } fn disable(&mut self, event: Event) { @@ -378,6 +380,7 @@ impl SharedEventMask { } } + #[allow(dead_code)] pub fn disable(&self, events: &[Event]) { let mut mask = self.mask.borrow_mut(); for event in events { diff --git a/src/lib.rs b/src/lib.rs index f9244bdd..d437a882 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,7 @@ use crate::bus::Bus; pub use crate::bus::SpiBusCyw43; pub use crate::control::Control; pub use crate::runner::Runner; +pub use crate::structs::BssInfo; const MTU: usize = 1514; diff --git a/src/runner.rs b/src/runner.rs index 806ddfc4..9b99e174 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -7,7 +7,7 @@ use embedded_hal_1::digital::OutputPin; use crate::bus::Bus; pub use crate::bus::SpiBusCyw43; use crate::consts::*; -use crate::events::{Events, Status}; +use crate::events::{Event, Events, Status}; use crate::fmt::Bytes; use crate::ioctl::{IoctlState, IoctlType, PendingIoctl}; use crate::nvram::NVRAM; @@ -351,6 +351,8 @@ where panic!("IOCTL error {}", cdc_header.status as i32); } + info!("IOCTL Response: {:02x}", Bytes(response)); + self.ioctl_state.ioctl_done(response); } } @@ -404,7 +406,15 @@ where if self.events.mask.is_enabled(evt_type) { let status = event_packet.msg.status; - let event_payload = events::Payload::None; + let event_payload = match evt_type { + Event::ESCAN_RESULT if status == EStatus::PARTIAL => { + let Some((_, bss_info)) = ScanResults::parse(evt_data) else { return }; + let Some(bss_info) = BssInfo::parse(bss_info) else { return }; + events::Payload::BssInfo(*bss_info) + } + Event::ESCAN_RESULT => events::Payload::None, + _ => events::Payload::None, + }; // this intentionally uses the non-blocking publish immediate // publish() is a deadlock risk in the current design as awaiting here prevents ioctls diff --git a/src/structs.rs b/src/structs.rs index f54ec7fc..d01d5a65 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -404,3 +404,84 @@ impl EventMask { self.events[evt / 8] &= !(1 << (evt % 8)); } } + +/// Parameters for a wifi scan +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct ScanParams { + pub version: u32, + pub action: u16, + pub sync_id: u16, + pub ssid_len: u32, + pub ssid: [u8; 32], + pub bssid: [u8; 6], + pub bss_type: u8, + pub scan_type: u8, + pub nprobes: u32, + pub active_time: u32, + pub passive_time: u32, + pub home_time: u32, + pub channel_num: u32, + pub channel_list: [u16; 1], +} +impl_bytes!(ScanParams); + +/// Wifi Scan Results Header, followed by `bss_count` `BssInfo` +#[derive(Clone, Copy)] +// #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C, packed(2))] +pub struct ScanResults { + pub buflen: u32, + pub version: u32, + pub sync_id: u16, + pub bss_count: u16, +} +impl_bytes!(ScanResults); + +impl ScanResults { + pub fn parse(packet: &mut [u8]) -> Option<(&mut ScanResults, &mut [u8])> { + if packet.len() < ScanResults::SIZE { + return None; + } + + let (scan_results, bssinfo) = packet.split_at_mut(ScanResults::SIZE); + let scan_results = ScanResults::from_bytes_mut(scan_results.try_into().unwrap()); + + if scan_results.bss_count > 0 && bssinfo.len() < BssInfo::SIZE { + warn!("Scan result, incomplete BssInfo"); + return None; + } + + Some((scan_results, bssinfo)) + } +} + +/// Wifi Scan Result +#[derive(Clone, Copy)] +// #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C, packed(2))] +#[non_exhaustive] +pub struct BssInfo { + pub version: u32, + pub length: u32, + pub bssid: [u8; 6], + pub beacon_period: u16, + pub capability: u16, + pub ssid_len: u8, + pub ssid: [u8; 32], + // there will be more stuff here +} +impl_bytes!(BssInfo); + +impl BssInfo { + pub fn parse(packet: &mut [u8]) -> Option<&mut Self> { + if packet.len() < BssInfo::SIZE { + return None; + } + + Some(BssInfo::from_bytes_mut( + packet[..BssInfo::SIZE].as_mut().try_into().unwrap(), + )) + } +}