use core::ffi::{c_int, c_void};
use esp_idf_svc::sys as esp_idf_sys;
use crate::{
ble,
enums::*,
utilities::{as_void_ptr, voidp_to_ref},
BLEAdvertisementData, BLEError, BLEServer,
};
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;
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;
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
))
}
}
pub fn advertisement_type(&mut self, adv_type: ConnMode) -> &mut Self {
self.adv_params.conn_mode = adv_type as _;
self
}
pub fn disc_mode(&mut self, mode: DiscMode) -> &mut Self {
self.adv_params.disc_mode = mode as _;
self
}
pub fn high_duty_cycle(&mut self, val: bool) -> &mut Self {
self.adv_params.set_high_duty_cycle(val as _);
self
}
pub fn min_interval(&mut self, interval: u16) -> &mut Self {
self.adv_params.itvl_min = interval;
self
}
pub fn max_interval(&mut self, interval: u16) -> &mut Self {
self.adv_params.itvl_max = interval;
self
}
pub fn scan_response(&mut self, value: bool) -> &mut Self {
self.scan_response = value;
self
}
pub fn filter_policy(&mut self, value: AdvFilterPolicy) -> &mut Self {
self.adv_params.filter_policy = value.into();
self
}
pub fn start(&mut self) -> Result<(), BLEError> {
self.start_with_duration(BLE_HS_FOREVER)
}
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() {
if !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 _ {
if let Some(callback) = adv.on_complete.as_mut() {
callback(unsafe { event.__bindgen_anon_1.adv_complete.reason });
}
}
0
}
}
unsafe impl Send for BLEAdvertising {}