esp32_nimble/client/
ble_scan.rs

1use crate::{BLEAdvertisedData, BLEDevice};
2use crate::{BLEAdvertisedDevice, BLEError, Signal, ble, enums::*, utilities::voidp_to_ref};
3use core::ffi::c_void;
4use esp_idf_svc::sys;
5
6/// Scan for ble devices.
7///
8/// # Examples
9///
10/// ```
11/// let ble_device = BLEDevice::take();
12/// let ble_scan = BLEScan::new();
13/// let name = "Device Name To Be Found";
14/// let device = ble_scan
15///   .start(ble_device, 10000, |device, data| {
16///     if let Some(device_name) = data.name() {
17///       if device_name == name {
18///         return Some(*device);
19///       }
20///     }
21///     None
22///   })
23///   .await
24///   .unwrap();
25/// ```
26pub struct BLEScan {
27    scan_params: sys::ble_gap_disc_params,
28    signal: Signal<()>,
29}
30
31type CbArgType<'a> = (
32    &'a mut BLEScan,
33    &'a mut dyn FnMut(&mut BLEScan, &BLEAdvertisedDevice, BLEAdvertisedData<&[u8]>),
34);
35
36impl BLEScan {
37    pub fn new() -> Self {
38        let mut ret = Self {
39            scan_params: sys::ble_gap_disc_params {
40                itvl: 0,
41                window: 0,
42                filter_policy: sys::BLE_HCI_SCAN_FILT_NO_WL as _,
43                ..Default::default()
44            },
45            signal: Signal::new(),
46        };
47        ret.limited(false);
48        ret.filter_duplicates(true);
49        ret.active_scan(false).interval(100).window(100);
50        ret
51    }
52
53    pub fn active_scan(&mut self, active: bool) -> &mut Self {
54        self.scan_params.set_passive((!active) as _);
55        self
56    }
57
58    pub fn filter_duplicates(&mut self, val: bool) -> &mut Self {
59        self.scan_params.set_filter_duplicates(val as _);
60        self
61    }
62
63    /// Set whether or not the BLE controller only report scan results
64    /// from devices advertising in limited discovery mode, i.e. directed advertising.
65    pub fn limited(&mut self, val: bool) -> &mut Self {
66        self.scan_params.set_limited(val as _);
67        self
68    }
69
70    /// Sets the scan filter policy.
71    pub fn filter_policy(&mut self, val: ScanFilterPolicy) -> &mut Self {
72        self.scan_params.filter_policy = val.into();
73        self
74    }
75
76    /// Set the interval to scan.
77    pub fn interval(&mut self, interval_msecs: u16) -> &mut Self {
78        self.scan_params.itvl = ((interval_msecs as f32) / 0.625) as u16;
79        self
80    }
81
82    /// Set the window to actively scan.
83    pub fn window(&mut self, window_msecs: u16) -> &mut Self {
84        self.scan_params.window = ((window_msecs as f32) / 0.625) as u16;
85        self
86    }
87
88    /// The callback function must return Option type.
89    /// If it returns None, the scan continues.
90    /// If Some(r) is returned, the scan stops and the start function returns the return value of the callback.
91    pub async fn start<F, R>(
92        &mut self,
93        _ble_device: &BLEDevice,
94        duration_ms: i32,
95        mut callback: F,
96    ) -> Result<Option<R>, BLEError>
97    where
98        F: FnMut(&BLEAdvertisedDevice, BLEAdvertisedData<&[u8]>) -> Option<R>,
99    {
100        let mut result: Option<R> = None;
101
102        let mut on_result =
103            |scan: &mut Self, device: &BLEAdvertisedDevice, data: BLEAdvertisedData<&[u8]>| {
104                if let Some(res) = callback(device, data) {
105                    result = Some(res);
106
107                    if let Err(err) = Self::stop() {
108                        ::log::warn!("scan stop err: {err:?}");
109                    }
110                    scan.signal.signal(());
111                }
112            };
113
114        let cb_arg: CbArgType = (self, &mut on_result);
115
116        #[cfg(esp_idf_bt_nimble_ext_adv)]
117        {
118            let mut scan_params = sys::ble_gap_ext_disc_params {
119                itvl: cb_arg.0.scan_params.itvl,
120                window: cb_arg.0.scan_params.window,
121                ..Default::default()
122            };
123            scan_params.set_passive(cb_arg.0.scan_params.passive());
124            unsafe {
125                ble!(sys::ble_gap_ext_disc(
126                    crate::ble_device::OWN_ADDR_TYPE as _,
127                    (duration_ms / 10) as _,
128                    0,
129                    cb_arg.0.scan_params.filter_duplicates(),
130                    cb_arg.0.scan_params.filter_policy,
131                    cb_arg.0.scan_params.limited(),
132                    &scan_params,
133                    &scan_params,
134                    Some(Self::handle_gap_event),
135                    core::ptr::addr_of!(cb_arg) as _,
136                ))?;
137            }
138        }
139
140        #[cfg(not(esp_idf_bt_nimble_ext_adv))]
141        unsafe {
142            ble!(sys::ble_gap_disc(
143                crate::ble_device::OWN_ADDR_TYPE as _,
144                duration_ms,
145                &cb_arg.0.scan_params,
146                Some(Self::handle_gap_event),
147                core::ptr::addr_of!(cb_arg) as _,
148            ))?;
149        }
150
151        cb_arg.0.signal.wait().await;
152
153        Ok(result)
154    }
155
156    fn stop() -> Result<(), BLEError> {
157        let rc = unsafe { sys::ble_gap_disc_cancel() };
158        if rc != 0 && rc != (sys::BLE_HS_EALREADY as _) {
159            return BLEError::convert(rc as _);
160        }
161
162        Ok(())
163    }
164
165    extern "C" fn handle_gap_event(event: *mut sys::ble_gap_event, arg: *mut c_void) -> i32 {
166        let event = unsafe { &*event };
167        let (scan, on_result) = unsafe { voidp_to_ref::<CbArgType>(arg) };
168
169        match event.type_ as u32 {
170            sys::BLE_GAP_EVENT_EXT_DISC | sys::BLE_GAP_EVENT_DISC => {
171                #[cfg(esp_idf_bt_nimble_ext_adv)]
172                let disc = unsafe { &event.__bindgen_anon_1.ext_disc };
173
174                #[cfg(not(esp_idf_bt_nimble_ext_adv))]
175                let disc = unsafe { &event.__bindgen_anon_1.disc };
176
177                let data = BLEAdvertisedData::new(unsafe {
178                    core::slice::from_raw_parts(disc.data, disc.length_data as _)
179                });
180
181                let advertised_device: &BLEAdvertisedDevice = unsafe { core::mem::transmute(disc) };
182
183                on_result(scan, advertised_device, data);
184            }
185            sys::BLE_GAP_EVENT_DISC_COMPLETE => {
186                scan.signal.signal(());
187            }
188            _ => {}
189        }
190        0
191    }
192}