esp32-nimble 0.12.0

A wrapper for the ESP32 NimBLE Bluetooth stack.
use crate::{BLEAdvertisedData, BLEDevice};
use crate::{BLEAdvertisedDevice, BLEError, Signal, ble, enums::*, utilities::voidp_to_ref};
use core::ffi::c_void;
use esp_idf_svc::sys;

/// Scan for ble devices.
///
/// # Examples
///
/// ```
/// let ble_device = BLEDevice::take();
/// let ble_scan = BLEScan::new();
/// let name = "Device Name To Be Found";
/// let device = ble_scan
///   .start(ble_device, 10000, |device, data| {
///     if let Some(device_name) = data.name() {
///       if device_name == name {
///         return Some(*device);
///       }
///     }
///     None
///   })
///   .await
///   .unwrap();
/// ```
pub struct BLEScan {
    scan_params: sys::ble_gap_disc_params,
    signal: Signal<()>,
}

type CbArgType<'a> = (
    &'a mut BLEScan,
    &'a mut dyn FnMut(&mut BLEScan, &BLEAdvertisedDevice, BLEAdvertisedData<&[u8]>),
);

impl BLEScan {
    pub fn new() -> Self {
        let mut ret = Self {
            scan_params: sys::ble_gap_disc_params {
                itvl: 0,
                window: 0,
                filter_policy: sys::BLE_HCI_SCAN_FILT_NO_WL as _,
                ..Default::default()
            },
            signal: Signal::new(),
        };
        ret.limited(false);
        ret.filter_duplicates(true);
        ret.active_scan(false).interval(100).window(100);
        ret
    }

    pub fn active_scan(&mut self, active: bool) -> &mut Self {
        self.scan_params.set_passive((!active) as _);
        self
    }

    pub fn filter_duplicates(&mut self, val: bool) -> &mut Self {
        self.scan_params.set_filter_duplicates(val as _);
        self
    }

    /// Set whether or not the BLE controller only report scan results
    /// from devices advertising in limited discovery mode, i.e. directed advertising.
    pub fn limited(&mut self, val: bool) -> &mut Self {
        self.scan_params.set_limited(val as _);
        self
    }

    /// Sets the scan filter policy.
    pub fn filter_policy(&mut self, val: ScanFilterPolicy) -> &mut Self {
        self.scan_params.filter_policy = val.into();
        self
    }

    /// Set the interval to scan.
    pub fn interval(&mut self, interval_msecs: u16) -> &mut Self {
        self.scan_params.itvl = ((interval_msecs as f32) / 0.625) as u16;
        self
    }

    /// Set the window to actively scan.
    pub fn window(&mut self, window_msecs: u16) -> &mut Self {
        self.scan_params.window = ((window_msecs as f32) / 0.625) as u16;
        self
    }

    /// The callback function must return Option type.
    /// If it returns None, the scan continues.
    /// If Some(r) is returned, the scan stops and the start function returns the return value of the callback.
    pub async fn start<F, R>(
        &mut self,
        _ble_device: &BLEDevice,
        duration_ms: i32,
        mut callback: F,
    ) -> Result<Option<R>, BLEError>
    where
        F: FnMut(&BLEAdvertisedDevice, BLEAdvertisedData<&[u8]>) -> Option<R>,
    {
        let mut result: Option<R> = None;

        let mut on_result =
            |scan: &mut Self, device: &BLEAdvertisedDevice, data: BLEAdvertisedData<&[u8]>| {
                if let Some(res) = callback(device, data) {
                    result = Some(res);

                    if let Err(err) = Self::stop() {
                        ::log::warn!("scan stop err: {err:?}");
                    }
                    scan.signal.signal(());
                }
            };

        let cb_arg: CbArgType = (self, &mut on_result);

        #[cfg(esp_idf_bt_nimble_ext_adv)]
        {
            let mut scan_params = sys::ble_gap_ext_disc_params {
                itvl: cb_arg.0.scan_params.itvl,
                window: cb_arg.0.scan_params.window,
                ..Default::default()
            };
            scan_params.set_passive(cb_arg.0.scan_params.passive());
            unsafe {
                ble!(sys::ble_gap_ext_disc(
                    crate::ble_device::OWN_ADDR_TYPE as _,
                    (duration_ms / 10) as _,
                    0,
                    cb_arg.0.scan_params.filter_duplicates(),
                    cb_arg.0.scan_params.filter_policy,
                    cb_arg.0.scan_params.limited(),
                    &scan_params,
                    &scan_params,
                    Some(Self::handle_gap_event),
                    core::ptr::addr_of!(cb_arg) as _,
                ))?;
            }
        }

        #[cfg(not(esp_idf_bt_nimble_ext_adv))]
        unsafe {
            ble!(sys::ble_gap_disc(
                crate::ble_device::OWN_ADDR_TYPE as _,
                duration_ms,
                &cb_arg.0.scan_params,
                Some(Self::handle_gap_event),
                core::ptr::addr_of!(cb_arg) as _,
            ))?;
        }

        cb_arg.0.signal.wait().await;

        Ok(result)
    }

    fn stop() -> Result<(), BLEError> {
        let rc = unsafe { sys::ble_gap_disc_cancel() };
        if rc != 0 && rc != (sys::BLE_HS_EALREADY as _) {
            return BLEError::convert(rc as _);
        }

        Ok(())
    }

    extern "C" fn handle_gap_event(event: *mut sys::ble_gap_event, arg: *mut c_void) -> i32 {
        let event = unsafe { &*event };
        let (scan, on_result) = unsafe { voidp_to_ref::<CbArgType>(arg) };

        match event.type_ as u32 {
            sys::BLE_GAP_EVENT_EXT_DISC | sys::BLE_GAP_EVENT_DISC => {
                #[cfg(esp_idf_bt_nimble_ext_adv)]
                let disc = unsafe { &event.__bindgen_anon_1.ext_disc };

                #[cfg(not(esp_idf_bt_nimble_ext_adv))]
                let disc = unsafe { &event.__bindgen_anon_1.disc };

                let data = BLEAdvertisedData::new(unsafe {
                    core::slice::from_raw_parts(disc.data, disc.length_data as _)
                });

                let advertised_device: &BLEAdvertisedDevice = unsafe { core::mem::transmute(disc) };

                on_result(scan, advertised_device, data);
            }
            sys::BLE_GAP_EVENT_DISC_COMPLETE => {
                scan.signal.signal(());
            }
            _ => {}
        }
        0
    }
}