esp32-nimble 0.12.0

A wrapper for the ESP32 NimBLE Bluetooth stack.
use core::ffi::{c_int, c_void};
use esp_idf_svc::sys as esp_idf_sys;

use crate::{
    BLEAdvertisementData, BLEError, BLEServer, ble,
    enums::*,
    utilities::{as_void_ptr, voidp_to_ref},
};
use alloc::boxed::Box;
use once_cell::sync::Lazy;

const BLE_HS_ADV_MAX_SZ: usize = esp_idf_sys::BLE_HS_ADV_MAX_SZ as usize;

// Copied from ble_hs.h, for some reason esp_idf_sys didn't pick this up.
const BLE_HS_FOREVER: i32 = i32::MAX;

pub struct BLEAdvertising {
    adv_params: esp_idf_sys::ble_gap_adv_params,
    scan_response: bool,
    on_complete: Option<Box<dyn FnMut(c_int) + Send + Sync>>,
}

impl BLEAdvertising {
    #[allow(dead_code)]
    pub(crate) fn new() -> Self {
        let mut ret = Self {
            adv_params: esp_idf_sys::ble_gap_adv_params::default(),
            scan_response: true,
            on_complete: None,
        };

        ret.reset().unwrap();
        ret
    }

    pub fn reset(&mut self) -> Result<(), BLEError> {
        if self.is_advertising() {
            self.stop()?;
        }

        self.adv_params.conn_mode = esp_idf_sys::BLE_GAP_CONN_MODE_UND as _;
        self.adv_params.disc_mode = esp_idf_sys::BLE_GAP_DISC_MODE_GEN as _;
        self.scan_response = true;

        Ok(())
    }

    pub fn set_data(&mut self, data: &mut BLEAdvertisementData) -> Result<(), BLEError> {
        if self.adv_params.conn_mode == (ConnMode::Non as _) && !self.scan_response {
            data.flags = 0;
        } else {
            data.flags =
                (esp_idf_sys::BLE_HS_ADV_F_DISC_GEN | esp_idf_sys::BLE_HS_ADV_F_BREDR_UNSUP) as _;
        }

        let mut adv_data = data.as_ble_hs_adv_fields();
        let mut scan_data = esp_idf_sys::ble_hs_adv_fields::default();

        let mut payload_len = data.payload_len();

        if payload_len > BLE_HS_ADV_MAX_SZ {
            if self.scan_response {
                scan_data.name = adv_data.name;
                scan_data.name_len = adv_data.name_len;
                if scan_data.name_len > (BLE_HS_ADV_MAX_SZ - 2) as _ {
                    scan_data.name_len = (BLE_HS_ADV_MAX_SZ - 2) as _;
                    scan_data.set_name_is_complete(0);
                } else {
                    scan_data.set_name_is_complete(1);
                }

                adv_data.name = core::ptr::null();
                adv_data.name_len = 0;
                adv_data.set_name_is_complete(0);
            } else {
                if adv_data.tx_pwr_lvl_is_present() > 0 {
                    adv_data.set_tx_pwr_lvl_is_present(0);
                    payload_len -= 2 + 1;
                }

                if payload_len > BLE_HS_ADV_MAX_SZ {
                    adv_data.name_len -= (payload_len - BLE_HS_ADV_MAX_SZ) as u8;
                    adv_data.set_name_is_complete(0);
                }
            }
        }

        unsafe {
            if self.scan_response {
                ble!(esp_idf_sys::ble_gap_adv_rsp_set_fields(&scan_data))?;
            }

            ble!(esp_idf_sys::ble_gap_adv_set_fields(&adv_data))
        }
    }

    pub fn set_raw_data(&mut self, data: &[u8]) -> Result<(), BLEError> {
        let rc =
            unsafe { esp_idf_sys::ble_gap_adv_set_data(data.as_ptr(), data.len() as i32) } as u32;

        // convert BLE_ERR_INV_HCI_CMD_PARMS to BLE_HS_EINVAL
        // https://github.com/taks/esp32-nimble/issues/104
        // https://github.com/apache/mynewt-nimble/issues/1717
        ble!(match rc {
            esp_idf_sys::ble_error_codes_BLE_ERR_INV_HCI_CMD_PARMS => esp_idf_sys::BLE_HS_EINVAL,
            rc => rc,
        })
    }

    pub fn set_raw_scan_response_data(&mut self, data: &[u8]) -> Result<(), BLEError> {
        unsafe {
            ble!(esp_idf_sys::ble_gap_adv_rsp_set_data(
                data.as_ptr(),
                data.len() as i32
            ))
        }
    }

    /// Set the type of advertisment to use.
    pub fn advertisement_type(&mut self, adv_type: ConnMode) -> &mut Self {
        self.adv_params.conn_mode = adv_type as _;
        self
    }

    /// Set discoverable mode.
    pub fn disc_mode(&mut self, mode: DiscMode) -> &mut Self {
        self.adv_params.disc_mode = mode as _;
        self
    }

    /// Set the duty cycle for advertisement_type.
    ///
    /// Valid only if advertisement_type is directed-connectable.
    pub fn high_duty_cycle(&mut self, val: bool) -> &mut Self {
        self.adv_params.set_high_duty_cycle(val as _);
        self
    }

    /// Set the minimum advertising interval.
    ///
    /// * `interval`: advertising interval in 0.625ms units, 0 = use default.
    pub fn min_interval(&mut self, interval: u16) -> &mut Self {
        self.adv_params.itvl_min = interval;
        self
    }

    /// Set the maximum advertising interval.
    ///
    /// * `interval`: advertising interval in 0.625ms units, 0 = use default.
    pub fn max_interval(&mut self, interval: u16) -> &mut Self {
        self.adv_params.itvl_max = interval;
        self
    }

    /// Set if scan response is available.
    pub fn scan_response(&mut self, value: bool) -> &mut Self {
        self.scan_response = value;
        self
    }

    /// Set the filtering for the scan filter.
    pub fn filter_policy(&mut self, value: AdvFilterPolicy) -> &mut Self {
        self.adv_params.filter_policy = value.into();
        self
    }

    /// Start advertising.
    /// Advertising not stop until it is manually stopped.
    pub fn start(&mut self) -> Result<(), BLEError> {
        self.start_with_duration(BLE_HS_FOREVER)
    }

    /// Start advertising.
    pub fn start_with_duration(&mut self, duration_ms: i32) -> Result<(), BLEError> {
        let mut server = unsafe { Lazy::get_mut(&mut crate::ble_device::BLE_SERVER) };
        if let Some(server) = server.as_mut()
            && !server.started
        {
            server.start()?;
        }

        if self.adv_params.conn_mode == (ConnMode::Non as _) && !self.scan_response {
            self.adv_params.disc_mode = esp_idf_sys::BLE_GAP_DISC_MODE_NON as _;
        } else {
            self.adv_params.disc_mode = esp_idf_sys::BLE_GAP_DISC_MODE_GEN as _;
        }

        let handle_gap_event = if server.is_some() {
            BLEServer::handle_gap_event
        } else {
            Self::handle_gap_event
        };
        unsafe {
            ble!(esp_idf_sys::ble_gap_adv_start(
                crate::ble_device::OWN_ADDR_TYPE as _,
                core::ptr::null(),
                duration_ms,
                &self.adv_params,
                Some(handle_gap_event),
                as_void_ptr(self),
            ))?;
        }

        Ok(())
    }

    pub fn stop(&self) -> Result<(), BLEError> {
        unsafe { ble!(esp_idf_sys::ble_gap_adv_stop()) }
    }

    pub fn is_advertising(&self) -> bool {
        unsafe { esp_idf_sys::ble_gap_adv_active() != 0 }
    }

    pub fn on_complete(
        &mut self,
        callback: impl FnMut(c_int) + Send + Sync + 'static,
    ) -> &mut Self {
        self.on_complete = Some(Box::new(callback));
        self
    }

    pub(crate) extern "C" fn handle_gap_event(
        event: *mut esp_idf_sys::ble_gap_event,
        arg: *mut c_void,
    ) -> i32 {
        let event = unsafe { &*event };
        let adv = unsafe { voidp_to_ref::<Self>(arg) };

        if event.type_ == esp_idf_sys::BLE_GAP_EVENT_ADV_COMPLETE as _
            && let Some(callback) = adv.on_complete.as_mut()
        {
            callback(unsafe { event.__bindgen_anon_1.adv_complete.reason });
        }

        0
    }
}

unsafe impl Send for BLEAdvertising {}