use core::cell::UnsafeCell;
use core::ffi::{c_int, c_void};
use core::fmt;
use core::marker::PhantomData;
use core::sync::atomic::{AtomicBool, Ordering};
use alloc::boxed::Box;
use alloc::sync::Arc;
use crate::hal::modem::BluetoothModemPeripheral;
use crate::private::mutex::Mutex;
use crate::sys::*;
pub mod gap;
pub mod gatt;
#[cfg(not(esp_idf_bt_nimble_l2cap_coc_max_num = "0"))]
pub mod l2cap;
#[cfg(any(
esp_idf_bt_nimble_gatt_server,
esp_idf_bt_nimble_gatt_client,
not(esp_idf_bt_nimble_l2cap_coc_max_num = "0")
))]
pub mod mbuf;
pub type ConnHandle = u16;
pub const CONN_HANDLE_NONE: ConnHandle = BLE_HS_CONN_HANDLE_NONE as ConnHandle;
#[derive(Clone, Copy, Debug)]
pub enum BleUuid {
Uuid16(ble_uuid16_t),
Uuid128(ble_uuid128_t),
}
impl BleUuid {
pub const fn uuid16(uuid: u16) -> Self {
Self::Uuid16(ble_uuid16_t {
u: ble_uuid_t {
type_: BLE_UUID_TYPE_16 as u8,
},
value: uuid,
})
}
pub const fn uuid128(uuid: u128) -> Self {
Self::Uuid128(ble_uuid128_t {
u: ble_uuid_t {
type_: BLE_UUID_TYPE_128 as u8,
},
value: uuid.to_le_bytes(),
})
}
pub const fn as_ptr(&self) -> *const ble_uuid_t {
match self {
Self::Uuid16(uuid) => &uuid.u as *const ble_uuid_t,
Self::Uuid128(uuid) => &uuid.u as *const ble_uuid_t,
}
}
pub(crate) unsafe fn from_raw(uuid: *const ble_uuid_t) -> Self {
match unsafe { (*uuid).type_ } as u32 {
BLE_UUID_TYPE_128 => Self::Uuid128(unsafe { *uuid.cast::<ble_uuid128_t>() }),
_ => Self::Uuid16(unsafe { *uuid.cast::<ble_uuid16_t>() }),
}
}
}
impl PartialEq for BleUuid {
fn eq(&self, other: &Self) -> bool {
unsafe { ble_uuid_cmp(self.as_ptr(), other.as_ptr()) == 0 }
}
}
impl Eq for BleUuid {}
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct BleAddr(ble_addr_t);
impl BleAddr {
pub const fn new(kind: u8, val: [u8; 6]) -> Self {
Self(ble_addr_t { type_: kind, val })
}
pub const fn raw(&self) -> &ble_addr_t {
&self.0
}
pub const fn kind(&self) -> u8 {
self.0.type_
}
pub const fn val(&self) -> [u8; 6] {
self.0.val
}
}
impl fmt::Display for BleAddr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let v = &self.0.val;
write!(
f,
"{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
v[5], v[4], v[3], v[2], v[1], v[0]
)
}
}
impl From<ble_addr_t> for BleAddr {
fn from(addr: ble_addr_t) -> Self {
Self(addr)
}
}
impl From<BleAddr> for ble_addr_t {
fn from(addr: BleAddr) -> Self {
addr.0
}
}
pub fn ensure_addr(prefer_random: bool) -> Result<(), BleError> {
BleError::from_raw(unsafe { ble_hs_util_ensure_addr(prefer_random as c_int) })
}
pub fn id_copy_addr(kind: u8) -> Result<BleAddr, BleError> {
let mut val = [0u8; 6];
BleError::from_raw(unsafe {
ble_hs_id_copy_addr(kind, val.as_mut_ptr(), core::ptr::null_mut())
})?;
Ok(BleAddr::new(kind, val))
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct BleError(c_int);
impl BleError {
pub const fn new(rc: c_int) -> Self {
Self(rc)
}
pub const fn code(&self) -> c_int {
self.0
}
pub fn from_raw(rc: c_int) -> Result<(), Self> {
if rc == 0 {
Ok(())
} else {
Err(Self(rc))
}
}
fn name(&self) -> &'static str {
match self.0 as u32 {
BLE_HS_EALREADY => "BLE_HS_EALREADY",
BLE_HS_EDONE => "BLE_HS_EDONE",
BLE_HS_ENOMEM => "BLE_HS_ENOMEM",
BLE_HS_ETIMEOUT => "BLE_HS_ETIMEOUT",
_ => "BLE_HS_E*",
}
}
}
impl fmt::Debug for BleError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "BleError({}, {})", self.0, self.name())
}
}
impl fmt::Display for BleError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "NimBLE error {} ({})", self.0, self.name())
}
}
#[cfg(feature = "std")]
impl std::error::Error for BleError {}
impl From<BleError> for EspError {
fn from(_err: BleError) -> Self {
EspError::from_infallible::<ESP_FAIL>()
}
}
#[derive(Clone, Copy)]
pub struct BleSecurity {
pub io_cap: u8,
pub oob_data_flag: bool,
pub bonding: bool,
pub mitm: bool,
pub secure_connections: bool,
pub secure_connections_only: bool,
pub keypress: bool,
pub min_sec_level: u8,
pub our_key_dist: u8,
pub their_key_dist: u8,
}
impl BleSecurity {
pub const fn new() -> Self {
Self {
io_cap: BLE_HS_IO_NO_INPUT_OUTPUT as u8,
oob_data_flag: false,
bonding: false,
mitm: false,
secure_connections: false,
secure_connections_only: false,
keypress: false,
min_sec_level: 0,
our_key_dist: 0,
their_key_dist: 0,
}
}
}
impl Default for BleSecurity {
fn default() -> Self {
Self::new()
}
}
pub enum HostEvent {
Sync,
Reset { reason: i32 },
}
#[allow(dead_code)]
#[allow(clippy::type_complexity)]
pub(crate) struct BleCallback<A, R> {
callback: Mutex<Option<Arc<UnsafeCell<Box<dyn FnMut(A) -> R>>>>>,
default_result: R,
}
#[allow(dead_code)]
impl<A, R> BleCallback<A, R>
where
R: Clone,
{
pub const fn new(default_result: R) -> Self {
Self {
callback: Mutex::new(None),
default_result,
}
}
pub fn subscribe<F>(&self, callback: F)
where
F: FnMut(A) -> R + Send + 'static,
{
unsafe { self.subscribe_nonstatic(callback) }
}
pub unsafe fn subscribe_nonstatic<'a, F>(&self, callback: F)
where
F: FnMut(A) -> R + Send + 'a,
{
let callback: Box<dyn FnMut(A) -> R + 'a> = Box::new(callback);
let callback: Box<dyn FnMut(A) -> R + 'static> = unsafe { core::mem::transmute(callback) };
*self.callback.lock() = Some(Arc::new(UnsafeCell::new(callback)));
}
pub fn unsubscribe(&self) {
*self.callback.lock() = None;
}
pub unsafe fn call(&self, arg: A) -> R {
let callback = self
.callback
.lock()
.as_ref()
.map(|callback| callback.clone());
if let Some(callback) = callback {
((callback.get()).as_mut().unwrap())(arg)
} else {
self.default_result.clone()
}
}
}
unsafe impl<A, R> Sync for BleCallback<A, R> {}
unsafe impl<A, R> Send for BleCallback<A, R> {}
#[cfg(esp_idf_bt_nimble_gatt_server)]
#[allow(clippy::type_complexity)]
pub(crate) struct GattsCallback {
callback: Mutex<
Option<Arc<UnsafeCell<Box<dyn for<'a> FnMut(gatt::server::GattsEvent<'a>) -> u8 + Send>>>>,
>,
}
#[cfg(esp_idf_bt_nimble_gatt_server)]
impl GattsCallback {
pub const fn new() -> Self {
Self {
callback: Mutex::new(None),
}
}
#[allow(clippy::arc_with_non_send_sync)]
pub unsafe fn subscribe_nonstatic<'a, F>(&self, callback: F)
where
F: for<'e> FnMut(gatt::server::GattsEvent<'e>) -> u8 + Send + 'a,
{
let callback: Box<dyn for<'e> FnMut(gatt::server::GattsEvent<'e>) -> u8 + Send + 'a> =
Box::new(callback);
let callback: Box<dyn for<'e> FnMut(gatt::server::GattsEvent<'e>) -> u8 + Send + 'static> =
unsafe { core::mem::transmute(callback) };
*self.callback.lock() = Some(Arc::new(UnsafeCell::new(callback)));
}
pub fn unsubscribe(&self) {
*self.callback.lock() = None;
}
pub unsafe fn call(&self, event: gatt::server::GattsEvent<'_>) -> u8 {
let callback = self
.callback
.lock()
.as_ref()
.map(|callback| callback.clone());
if let Some(callback) = callback {
unsafe { ((callback.get()).as_mut().unwrap())(event) }
} else {
0
}
}
}
#[cfg(esp_idf_bt_nimble_gatt_server)]
unsafe impl Sync for GattsCallback {}
#[cfg(esp_idf_bt_nimble_gatt_server)]
unsafe impl Send for GattsCallback {}
#[cfg(esp_idf_bt_nimble_gatt_client)]
#[allow(clippy::type_complexity)]
pub(crate) struct GattcCallback {
callback:
Mutex<Option<Arc<UnsafeCell<Box<dyn for<'a> FnMut(gatt::client::GattcEvent<'a>) + Send>>>>>,
}
#[cfg(esp_idf_bt_nimble_gatt_client)]
impl GattcCallback {
pub const fn new() -> Self {
Self {
callback: Mutex::new(None),
}
}
#[allow(clippy::arc_with_non_send_sync)]
pub unsafe fn subscribe_nonstatic<'a, F>(&self, callback: F)
where
F: for<'e> FnMut(gatt::client::GattcEvent<'e>) + Send + 'a,
{
let callback: Box<dyn for<'e> FnMut(gatt::client::GattcEvent<'e>) + Send + 'a> =
Box::new(callback);
let callback: Box<dyn for<'e> FnMut(gatt::client::GattcEvent<'e>) + Send + 'static> =
unsafe { core::mem::transmute(callback) };
*self.callback.lock() = Some(Arc::new(UnsafeCell::new(callback)));
}
pub fn unsubscribe(&self) {
*self.callback.lock() = None;
}
pub unsafe fn call(&self, event: gatt::client::GattcEvent<'_>) {
let callback = self
.callback
.lock()
.as_ref()
.map(|callback| callback.clone());
if let Some(callback) = callback {
unsafe { ((callback.get()).as_mut().unwrap())(event) }
}
}
}
#[cfg(esp_idf_bt_nimble_gatt_client)]
unsafe impl Sync for GattcCallback {}
#[cfg(esp_idf_bt_nimble_gatt_client)]
unsafe impl Send for GattcCallback {}
#[cfg(not(esp_idf_bt_nimble_l2cap_coc_max_num = "0"))]
#[allow(clippy::type_complexity)]
pub(crate) struct L2capCallback {
callback:
Mutex<Option<Arc<UnsafeCell<Box<dyn for<'a> FnMut(l2cap::L2capEvent<'a>) -> i32 + Send>>>>>,
}
#[cfg(not(esp_idf_bt_nimble_l2cap_coc_max_num = "0"))]
impl L2capCallback {
pub const fn new() -> Self {
Self {
callback: Mutex::new(None),
}
}
#[allow(clippy::arc_with_non_send_sync)]
pub unsafe fn subscribe_nonstatic<'a, F>(&self, callback: F)
where
F: for<'e> FnMut(l2cap::L2capEvent<'e>) -> i32 + Send + 'a,
{
let callback: Box<dyn for<'e> FnMut(l2cap::L2capEvent<'e>) -> i32 + Send + 'a> =
Box::new(callback);
let callback: Box<dyn for<'e> FnMut(l2cap::L2capEvent<'e>) -> i32 + Send + 'static> =
unsafe { core::mem::transmute(callback) };
*self.callback.lock() = Some(Arc::new(UnsafeCell::new(callback)));
}
pub fn unsubscribe(&self) {
*self.callback.lock() = None;
}
pub unsafe fn call(&self, event: l2cap::L2capEvent<'_>) -> i32 {
let callback = self
.callback
.lock()
.as_ref()
.map(|callback| callback.clone());
if let Some(callback) = callback {
unsafe { ((callback.get()).as_mut().unwrap())(event) }
} else {
0
}
}
}
#[cfg(not(esp_idf_bt_nimble_l2cap_coc_max_num = "0"))]
unsafe impl Sync for L2capCallback {}
#[cfg(not(esp_idf_bt_nimble_l2cap_coc_max_num = "0"))]
unsafe impl Send for L2capCallback {}
#[allow(dead_code)]
pub(crate) struct BleSingleton {
initialized: AtomicBool,
host: BleCallback<HostEvent, ()>,
gap: BleCallback<gap::GapEvent, i32>,
#[cfg(esp_idf_bt_nimble_gatt_server)]
gatts: GattsCallback,
#[cfg(esp_idf_bt_nimble_gatt_client)]
gattc: GattcCallback,
#[cfg(not(esp_idf_bt_nimble_l2cap_coc_max_num = "0"))]
l2cap: L2capCallback,
}
#[allow(dead_code)]
impl BleSingleton {
pub const fn new() -> Self {
Self {
initialized: AtomicBool::new(false),
host: BleCallback::new(()),
gap: BleCallback::new(0),
#[cfg(esp_idf_bt_nimble_gatt_server)]
gatts: GattsCallback::new(),
#[cfg(esp_idf_bt_nimble_gatt_client)]
gattc: GattcCallback::new(),
#[cfg(not(esp_idf_bt_nimble_l2cap_coc_max_num = "0"))]
l2cap: L2capCallback::new(),
}
}
pub fn take(&self) -> Result<(), EspError> {
self.initialized
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
.map_err(|_| EspError::from_infallible::<ESP_ERR_INVALID_STATE>())?;
Ok(())
}
pub fn release(&self) -> Result<(), EspError> {
self.initialized
.compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
.map_err(|_| EspError::from_infallible::<ESP_ERR_INVALID_STATE>())?;
Ok(())
}
unsafe extern "C" fn host_sync_cb() {
unsafe { SINGLETON.host.call(HostEvent::Sync) }
}
unsafe extern "C" fn host_reset_cb(reason: i32) {
unsafe { SINGLETON.host.call(HostEvent::Reset { reason }) }
}
unsafe extern "C" fn gap_event_cb(event: *mut ble_gap_event, _arg: *mut c_void) -> c_int {
let event = unsafe { &*event };
match event.type_ as u32 {
#[cfg(esp_idf_bt_nimble_gatt_server)]
BLE_GAP_EVENT_SUBSCRIBE | BLE_GAP_EVENT_NOTIFY_TX => {
if let Some(event) = gatt::server::GattsEvent::from_gap(event) {
unsafe { SINGLETON.gatts.call(event) };
}
0
}
#[cfg(esp_idf_bt_nimble_gatt_client)]
BLE_GAP_EVENT_NOTIFY_RX => {
unsafe {
SINGLETON
.gattc
.call(gatt::client::GattcEvent::from_notify_rx(event))
};
0
}
_ => unsafe { SINGLETON.gap.call(gap::GapEvent::from(event)) },
}
}
#[cfg(esp_idf_bt_nimble_gatt_server)]
unsafe extern "C" fn gatts_register_cb(ctxt: *mut ble_gatt_register_ctxt, _arg: *mut c_void) {
let event =
gatt::server::GattsEvent::Register(gatt::server::BleGattRegister::from(unsafe {
&*ctxt
}));
unsafe {
SINGLETON.gatts.call(event);
}
}
#[cfg(esp_idf_bt_nimble_gatt_server)]
unsafe extern "C" fn gatts_access_cb(
conn_handle: u16,
attr_handle: u16,
ctxt: *mut ble_gatt_access_ctxt,
_arg: *mut c_void,
) -> c_int {
let mbuf = mbuf::Mbuf::from_raw(unsafe { (*ctxt).om });
let event = match unsafe { (*ctxt).op } as u32 {
BLE_GATT_ACCESS_OP_READ_CHR => {
#[cfg(esp_idf_version_at_least_5_3_0)]
let offset = unsafe { (*ctxt).offset };
#[cfg(not(esp_idf_version_at_least_5_3_0))]
let offset = 0;
gatt::server::GattsEvent::Read {
conn_handle,
attr_handle,
offset,
reply: mbuf,
}
}
BLE_GATT_ACCESS_OP_WRITE_CHR => gatt::server::GattsEvent::Write {
conn_handle,
attr_handle,
data: mbuf,
},
_ => return BLE_ATT_ERR_UNLIKELY as c_int,
};
unsafe { SINGLETON.gatts.call(event) as c_int }
}
#[cfg(esp_idf_bt_nimble_gatt_client)]
unsafe extern "C" fn gattc_disc_svc_cb(
conn_handle: u16,
error: *const ble_gatt_error,
service: *const ble_gatt_svc,
_arg: *mut c_void,
) -> c_int {
let status = if error.is_null() {
0
} else {
unsafe { (*error).status }
};
let service =
(!service.is_null()).then(|| gatt::client::GattcService::from(unsafe { &*service }));
unsafe {
SINGLETON.gattc.call(gatt::client::GattcEvent::Service {
conn_handle,
status,
service,
});
}
0
}
#[cfg(esp_idf_bt_nimble_gatt_client)]
unsafe extern "C" fn gattc_disc_chr_cb(
conn_handle: u16,
error: *const ble_gatt_error,
chr: *const ble_gatt_chr,
_arg: *mut c_void,
) -> c_int {
let status = if error.is_null() {
0
} else {
unsafe { (*error).status }
};
let chr = (!chr.is_null()).then(|| gatt::client::GattcChr::from(unsafe { &*chr }));
unsafe {
SINGLETON
.gattc
.call(gatt::client::GattcEvent::Characteristic {
conn_handle,
status,
chr,
});
}
0
}
#[cfg(esp_idf_bt_nimble_gatt_client)]
unsafe extern "C" fn gattc_read_cb(
conn_handle: u16,
error: *const ble_gatt_error,
attr: *mut ble_gatt_attr,
_arg: *mut c_void,
) -> c_int {
let status = if error.is_null() {
0
} else {
unsafe { (*error).status }
};
let (attr_handle, om) = if attr.is_null() {
(0, core::ptr::null_mut())
} else {
unsafe { ((*attr).handle, (*attr).om) }
};
unsafe {
SINGLETON
.gattc
.call(gatt::client::GattcEvent::ReadComplete {
conn_handle,
status,
attr_handle,
data: mbuf::Mbuf::from_raw(om),
});
}
0
}
#[cfg(esp_idf_bt_nimble_gatt_client)]
unsafe extern "C" fn gattc_write_cb(
conn_handle: u16,
error: *const ble_gatt_error,
attr: *mut ble_gatt_attr,
_arg: *mut c_void,
) -> c_int {
let status = if error.is_null() {
0
} else {
unsafe { (*error).status }
};
let attr_handle = if attr.is_null() {
0
} else {
unsafe { (*attr).handle }
};
unsafe {
SINGLETON
.gattc
.call(gatt::client::GattcEvent::WriteComplete {
conn_handle,
status,
attr_handle,
});
}
0
}
#[cfg(not(esp_idf_bt_nimble_l2cap_coc_max_num = "0"))]
unsafe extern "C" fn l2cap_event_cb(event: *mut ble_l2cap_event, _arg: *mut c_void) -> c_int {
let event = unsafe { &*event };
let received_sdu = if event.type_ as u32 == BLE_L2CAP_EVENT_COC_DATA_RECEIVED {
Some(unsafe { event.__bindgen_anon_1.receive.sdu_rx })
} else {
None
};
let status = match l2cap::L2capEvent::from_raw(event) {
Some(event) => unsafe { SINGLETON.l2cap.call(event) },
None => 0,
};
if let Some(om) = received_sdu {
l2cap::free_mbuf(om);
}
status as c_int
}
unsafe extern "C" fn host_task(_arg: *mut c_void) {
unsafe {
nimble_port_run();
nimble_port_freertos_deinit();
}
}
}
static SINGLETON: BleSingleton = BleSingleton::new();
pub struct BleDriver<'ble, S = ()> {
started: AtomicBool,
#[allow(dead_code)]
services: S,
_p: PhantomData<&'ble mut ()>,
}
impl<'ble> BleDriver<'ble, ()> {
pub fn new<M: BluetoothModemPeripheral + 'ble>(modem: M) -> Result<Self, EspError> {
Self::host_init(modem, ())
}
}
#[cfg(esp_idf_bt_nimble_gatt_server)]
impl<'ble, S> BleDriver<'ble, S>
where
S: AsRef<[ble_gatt_svc_def]>,
{
pub fn new_with_services<M: BluetoothModemPeripheral + 'ble>(
modem: M,
services: S,
) -> Result<Self, EspError> {
let this = Self::host_init(modem, services)?;
unsafe {
(*core::ptr::addr_of_mut!(ble_hs_cfg)).gatts_register_cb =
Some(BleSingleton::gatts_register_cb);
}
let defs = this.services.as_ref().as_ptr();
BleError::from_raw(unsafe { ble_gatts_count_cfg(defs) })?;
BleError::from_raw(unsafe { ble_gatts_add_svcs(defs) })?;
Ok(this)
}
}
impl<'ble, S> BleDriver<'ble, S> {
pub fn host_subscribe<F>(&self, callback: F)
where
F: FnMut(HostEvent) + Send + 'static,
{
unsafe { self.host_subscribe_nonstatic(callback) }
}
pub unsafe fn host_subscribe_nonstatic<F>(&self, callback: F)
where
F: FnMut(HostEvent) + Send + 'ble,
{
unsafe { SINGLETON.host.subscribe_nonstatic(callback) };
}
pub fn host_unsubscribe(&self) {
SINGLETON.host.unsubscribe();
}
pub fn set_security(&mut self, security: &BleSecurity) -> Result<(), EspError> {
if self.started.load(Ordering::SeqCst) {
return Err(EspError::from_infallible::<ESP_ERR_INVALID_STATE>());
}
unsafe {
let cfg = core::ptr::addr_of_mut!(ble_hs_cfg);
(*cfg).sm_io_cap = security.io_cap;
(*cfg).set_sm_oob_data_flag(security.oob_data_flag as _);
(*cfg).set_sm_bonding(security.bonding as _);
(*cfg).set_sm_mitm(security.mitm as _);
(*cfg).set_sm_sc(security.secure_connections as _);
(*cfg).set_sm_sc_only(security.secure_connections_only as _);
(*cfg).set_sm_keypress(security.keypress as _);
(*cfg).sm_sec_lvl = security.min_sec_level;
(*cfg).sm_our_key_dist = security.our_key_dist;
(*cfg).sm_their_key_dist = security.their_key_dist;
}
Ok(())
}
pub fn start(&self) -> Result<(), EspError> {
if !self.started.swap(true, Ordering::SeqCst) {
unsafe { nimble_port_freertos_init(Some(BleSingleton::host_task)) };
}
Ok(())
}
pub fn stop(&self) -> Result<(), EspError> {
if self.started.swap(false, Ordering::SeqCst) {
let _ = unsafe { nimble_port_stop() };
}
Ok(())
}
fn host_init<M: BluetoothModemPeripheral>(_modem: M, services: S) -> Result<Self, EspError> {
SINGLETON.take()?;
esp!(unsafe { nimble_port_init() })?;
unsafe {
ble_svc_gap_init();
ble_svc_gatt_init();
let cfg = core::ptr::addr_of_mut!(ble_hs_cfg);
(*cfg).sync_cb = Some(BleSingleton::host_sync_cb);
(*cfg).reset_cb = Some(BleSingleton::host_reset_cb);
}
let mut this = Self {
started: AtomicBool::new(false),
services,
_p: PhantomData,
};
this.set_security(&BleSecurity::new())?;
Ok(this)
}
}
unsafe impl<S> Send for BleDriver<'_, S> {}
unsafe impl<S> Sync for BleDriver<'_, S> {}
impl<S> Drop for BleDriver<'_, S> {
fn drop(&mut self) {
let _ = self.stop();
esp!(unsafe { nimble_port_deinit() }).unwrap();
unsafe {
let cfg = core::ptr::addr_of_mut!(ble_hs_cfg);
(*cfg).sync_cb = None;
(*cfg).reset_cb = None;
#[cfg(esp_idf_bt_nimble_gatt_server)]
{
(*cfg).gatts_register_cb = None;
}
}
SINGLETON.host.unsubscribe();
SINGLETON.gap.unsubscribe();
#[cfg(esp_idf_bt_nimble_gatt_server)]
SINGLETON.gatts.unsubscribe();
#[cfg(esp_idf_bt_nimble_gatt_client)]
SINGLETON.gattc.unsubscribe();
#[cfg(not(esp_idf_bt_nimble_l2cap_coc_max_num = "0"))]
SINGLETON.l2cap.unsubscribe();
let _ = SINGLETON.release();
}
}