diff --git a/Cargo.toml b/Cargo.toml index e63d821..205a2cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bluedroid" -version = "0.3.3" +version = "0.3.4" edition = "2021" license = "MIT" description = "A wrapper for the ESP32 Bluedroid Bluetooth stack." diff --git a/examples/server.rs b/examples/server.rs index 5dc8fbc..08a2f81 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -1,23 +1,21 @@ -use std::sync::RwLock; +use std::sync::{RwLock, Arc}; use bluedroid::{ gatt_server::{Characteristic, Profile, Service, GLOBAL_GATT_SERVER}, utilities::{AttributePermissions, BleUuid, CharacteristicProperties}, }; -use lazy_static::lazy_static; use log::info; -lazy_static! { - static ref VALUE: RwLock> = RwLock::new("Initial value.".as_bytes().to_vec()); -} - fn main() { esp_idf_sys::link_patches(); esp_idf_svc::log::EspLogger::initialize_default(); info!("Logger initialised."); + let char_value_write: Arc>> = Arc::new(RwLock::new("Initial value".as_bytes().to_vec())); + let char_value_read = char_value_write.clone(); + // A static characteristic. let static_characteristic = Characteristic::new(BleUuid::from_uuid128_string( "d4e0e0d0-1a2b-11e9-ab14-d663bd873d93", @@ -61,13 +59,13 @@ fn main() { .name("Writable Characteristic") .permissions(AttributePermissions::new().read().write()) .properties(CharacteristicProperties::new().read().write()) - .on_read(|_param| { + .on_read(move |_param| { info!("Read from writable characteristic."); - return VALUE.read().unwrap().clone(); + return char_value_read.read().unwrap().clone(); }) - .on_write(|value, _param| { + .on_write(move |value, _param| { info!("Wrote to writable characteristic: {:?}", value); - *VALUE.write().unwrap() = value; + *char_value_write.write().unwrap() = value; }) .show_name() .build(); diff --git a/src/gatt_server/characteristic.rs b/src/gatt_server/characteristic.rs index 5ad046a..4e5c939 100644 --- a/src/gatt_server/characteristic.rs +++ b/src/gatt_server/characteristic.rs @@ -15,15 +15,17 @@ use std::{ sync::{Arc, RwLock}, }; +type WriteCallback = dyn Fn(Vec, esp_ble_gatts_cb_param_t_gatts_write_evt_param) + Send + Sync; + /// Represents a GATT characteristic. -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct Characteristic { /// The name of the characteristic, for debugging purposes. name: Option, /// The characteristic identifier. pub(crate) uuid: BleUuid, /// The function to be called when a write happens. This functions receives the written value in the first parameter, a `Vec`. - pub(crate) write_callback: Option, esp_ble_gatts_cb_param_t_gatts_write_evt_param)>, + pub(crate) write_callback: Option>, /// A list of descriptors for this characteristic. pub(crate) descriptors: Vec>>, /// The handle that the Bluetooth stack assigned to this characteristic. @@ -104,9 +106,9 @@ impl Characteristic { /// # Notes /// /// The callback will be called from the Bluetooth stack's context, so it must not block. - pub fn on_read( + pub fn on_read Vec + Send + Sync + 'static>( &mut self, - callback: fn(esp_ble_gatts_cb_param_t_gatts_read_evt_param) -> Vec, + callback: C, ) -> &mut Self { if !self.properties.read || !self.permissions.read_access { warn!( @@ -117,7 +119,7 @@ impl Characteristic { return self; } - self.control = AttributeControl::ResponseByApp(callback); + self.control = AttributeControl::ResponseByApp(Arc::new(callback)); self.internal_control = self.control.clone().into(); self @@ -130,7 +132,7 @@ impl Characteristic { /// It is up to the library user to decode the data into a meaningful format. pub fn on_write( &mut self, - callback: fn(Vec, esp_ble_gatts_cb_param_t_gatts_write_evt_param), + callback: impl Fn(Vec, esp_ble_gatts_cb_param_t_gatts_write_evt_param) + Send + Sync + 'static, ) -> &mut Self { if !((self.properties.write || self.properties.write_without_response) && self.permissions.write_access) @@ -143,7 +145,7 @@ impl Characteristic { return self; } - self.write_callback = Some(callback); + self.write_callback = Some(Arc::new(callback)); self } @@ -298,7 +300,7 @@ impl Characteristic { .iter() .find(|desc| desc.read().unwrap().uuid == BleUuid::Uuid16(0x2902)) { - if let AttributeControl::ResponseByApp(callback) = cccd.read().unwrap().control { + if let AttributeControl::ResponseByApp(callback) = &cccd.read().unwrap().control { let value = callback(param); return Some(( @@ -324,3 +326,23 @@ impl std::fmt::Display for Characteristic { ) } } + +impl std::fmt::Debug for Characteristic { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + // Debug representation of a characteristic. + f.debug_struct("Characteristic") + .field("name", &self.name) + .field("uuid", &self.uuid) + .field("write_callback", &self.write_callback.is_some()) + .field("descriptors", &self.descriptors) + .field("attribute_handle", &self.attribute_handle) + .field("service_handle", &self.service_handle) + .field("permissions", &self.permissions) + .field("properties", &self.properties) + .field("control", &self.control) + .field("internal_value", &self.internal_value) + .field("max_value_length", &self.max_value_length) + .field("internal_control", &self.internal_control) + .finish() + } +} diff --git a/src/gatt_server/custom_attributes.rs b/src/gatt_server/custom_attributes.rs index b40033b..127f7a2 100644 --- a/src/gatt_server/custom_attributes.rs +++ b/src/gatt_server/custom_attributes.rs @@ -12,7 +12,7 @@ use lazy_static::lazy_static; use log::debug; lazy_static! { - static ref STORAGE: Mutex = Mutex::new( + static ref STORAGE: Arc> = Arc::new(Mutex::new( EspNvsStorage::new_default( Arc::new( EspDefaultNvs::new() @@ -22,7 +22,7 @@ lazy_static! { true ) .expect("Cannot create a new NVS storage. Did you declare an NVS partition?") - ); + )); } impl Descriptor { @@ -53,36 +53,38 @@ impl Descriptor { Self::new(BleUuid::from_uuid16(0x2902)) .name("Client Characteristic Configuration") .permissions(AttributePermissions::new().read().write()) - .on_read(|param| { - let storage = STORAGE.lock().unwrap(); + .on_read( + |param: esp_idf_sys::esp_ble_gatts_cb_param_t_gatts_read_evt_param| { + let storage = STORAGE.lock().unwrap(); - // Get the descriptor handle. + // Get the descriptor handle. - // TODO: Find the characteristic that contains the handle. - // WARNING: Using the handle is incredibly stupid as the NVS is not erased across flashes. + // TODO: Find the characteristic that contains the handle. + // WARNING: Using the handle is incredibly stupid as the NVS is not erased across flashes. - // Create a key from the connection address. - let key = format!( - "{:02X}{:02X}{:02X}{:02X}-{:04X}", - /* param.bda[1], */ param.bda[2], - param.bda[3], - param.bda[4], - param.bda[5], - param.handle - ); + // Create a key from the connection address. + let key = format!( + "{:02X}{:02X}{:02X}{:02X}-{:04X}", + /* param.bda[1], */ param.bda[2], + param.bda[3], + param.bda[4], + param.bda[5], + param.handle + ); - // Prepare buffer and read correct CCCD value from non-volatile storage. - let mut buf: [u8; 2] = [0; 2]; - if let Some(value) = storage.get_raw(&key, &mut buf).unwrap() { - debug!("Read CCCD value: {:?} for key {}.", value, key); - value.0.to_vec() - } else { - debug!("No CCCD value found for key {}.", key); - vec![0, 0] - } - }) - .on_write(|value, param| { - let mut storage = STORAGE.lock().unwrap(); + // Prepare buffer and read correct CCCD value from non-volatile storage. + let mut buf: [u8; 2] = [0; 2]; + if let Some(value) = storage.get_raw(&key, &mut buf).unwrap() { + debug!("Read CCCD value: {:?} for key {}.", value, key); + value.0.to_vec() + } else { + debug!("No CCCD value found for key {}.", key); + vec![0, 0] + } + }, + ) + .on_write(|value, param| { + let mut storage = STORAGE.lock().unwrap(); // Create a key from the connection address. let key = format!( diff --git a/src/gatt_server/descriptor.rs b/src/gatt_server/descriptor.rs index 9a54813..a7fa4d3 100644 --- a/src/gatt_server/descriptor.rs +++ b/src/gatt_server/descriptor.rs @@ -56,9 +56,9 @@ impl Descriptor { } /// Sets the read callback for the [`Descriptor`]. - pub fn on_read( + pub fn on_read Vec + Send + Sync + 'static>( &mut self, - callback: fn(esp_ble_gatts_cb_param_t_gatts_read_evt_param) -> Vec, + callback: C, ) -> &mut Self { if !self.permissions.read_access { warn!( @@ -69,7 +69,7 @@ impl Descriptor { return self; } - self.control = AttributeControl::ResponseByApp(callback); + self.control = AttributeControl::ResponseByApp(Arc::new(callback)); self.internal_control = self.control.clone().into(); self diff --git a/src/gatt_server/gatts_event_handler.rs b/src/gatt_server/gatts_event_handler.rs index 720d0b9..2fabc19 100644 --- a/src/gatt_server/gatts_event_handler.rs +++ b/src/gatt_server/gatts_event_handler.rs @@ -1,4 +1,4 @@ -#![allow(clippy::too_many_lines, clippy::if_not_else)] +#![allow(clippy::too_many_lines)] use crate::{ gatt_server::{GattServer, Profile}, @@ -7,7 +7,7 @@ use crate::{ }; use esp_idf_sys::{ - esp_ble_gap_config_adv_data, esp_ble_gap_set_device_name, esp_ble_gap_start_advertising, + esp, esp_ble_gap_config_adv_data, esp_ble_gap_set_device_name, esp_ble_gap_start_advertising, esp_ble_gatts_cb_param_t, esp_ble_gatts_cb_param_t_gatts_read_evt_param, esp_ble_gatts_get_attr_value, esp_ble_gatts_send_indicate, esp_ble_gatts_send_response, esp_ble_gatts_start_service, esp_bt_status_t_ESP_BT_STATUS_SUCCESS, esp_gatt_if_t, @@ -169,15 +169,22 @@ impl GattServer { .unwrap() .internal_value .clone(); - unsafe { - esp_nofail!(esp_ble_gatts_send_indicate( + let result = unsafe { + esp!(esp_ble_gatts_send_indicate( gatts_if, connection.id, param.attr_handle, internal_value.len() as u16, internal_value.as_mut_slice().as_mut_ptr(), true - )); + )) + }; + + if result.is_err() { + warn!( + "Failed to indicate value change: {}.", + result.err().unwrap() + ); } } } @@ -222,15 +229,22 @@ impl GattServer { .unwrap() .internal_value .clone(); - unsafe { - esp_nofail!(esp_ble_gatts_send_indicate( + let result = unsafe { + esp!(esp_ble_gatts_send_indicate( gatts_if, connection.id, param.attr_handle, internal_value.len() as u16, internal_value.as_mut_slice().as_mut_ptr(), false - )); + )) + }; + + if result.is_err() { + warn!( + "Failed to notify value change: {}.", + result.err().unwrap() + ); } } } @@ -300,15 +314,15 @@ impl Profile { let param = unsafe { (*param).reg }; // Check status - if param.status != esp_bt_status_t_ESP_BT_STATUS_SUCCESS { - warn!("GATT profile registration failed."); - } else { + if param.status == esp_bt_status_t_ESP_BT_STATUS_SUCCESS { info!( "{} registered on interface {}.", &self, self.interface.unwrap() ); self.register_services(); + } else { + warn!("GATT profile registration failed."); } } esp_gatts_cb_event_t_ESP_GATTS_CREATE_EVT => { @@ -317,9 +331,7 @@ impl Profile { if let Some(service) = self.get_service_by_id(param.service_id.id) { service.write().unwrap().handle = Some(param.service_handle); - if param.status != esp_gatt_status_t_ESP_GATT_OK { - warn!("GATT service registration failed."); - } else { + if param.status == esp_gatt_status_t_ESP_GATT_OK { info!( "GATT service {} registered on handle 0x{:04x}.", service.read().unwrap(), @@ -333,6 +345,8 @@ impl Profile { } service.write().unwrap().register_characteristics(); + } else { + warn!("GATT service registration failed."); } } else { warn!("Cannot find service with service identifier {} received in service creation event.", BleUuid::from(param.service_id.id)); @@ -342,10 +356,10 @@ impl Profile { let param = unsafe { (*param).start }; if let Some(service) = self.get_service(param.service_handle) { - if param.status != esp_gatt_status_t_ESP_GATT_OK { - warn!("GATT service {} failed to start.", service.read().unwrap()); - } else { + if param.status == esp_gatt_status_t_ESP_GATT_OK { debug!("GATT service {} started.", service.read().unwrap()); + } else { + warn!("GATT service {} failed to start.", service.read().unwrap()); } } else { warn!("Cannot find service described by handle 0x{:04x} received in service start event.", param.service_handle); @@ -360,9 +374,7 @@ impl Profile { .unwrap() .get_characteristic_by_id(param.char_uuid) { - if param.status != esp_gatt_status_t_ESP_GATT_OK { - warn!("GATT characteristic registration failed."); - } else { + if param.status == esp_gatt_status_t_ESP_GATT_OK { info!( "GATT characteristic {} registered at attribute handle 0x{:04x}.", characteristic.read().unwrap(), @@ -371,6 +383,8 @@ impl Profile { characteristic.write().unwrap().attribute_handle = Some(param.attr_handle); characteristic.write().unwrap().register_descriptors(); + } else { + warn!("GATT characteristic registration failed."); } } else { warn!("Cannot find characteristic described by service handle 0x{:04x} and characteristic identifier {} received in characteristic creation event.", param.service_handle, BleUuid::from(param.char_uuid)); @@ -393,15 +407,15 @@ impl Profile { .iter() .find(|d| d.read().unwrap().attribute_handle.is_none()) { - if param.status != esp_gatt_status_t_ESP_GATT_OK { - warn!("GATT descriptor registration failed."); - } else { + if param.status == esp_gatt_status_t_ESP_GATT_OK { info!( "GATT descriptor {} registered at attribute handle 0x{:04x}.", descriptor.read().unwrap(), param.attr_handle ); descriptor.write().unwrap().attribute_handle = Some(param.attr_handle); + } else { + warn!("GATT descriptor registration failed."); } } else { warn!("Cannot find service described by identifier {} received in descriptor creation event.", BleUuid::from(param.descr_uuid)); @@ -423,7 +437,7 @@ impl Profile { // If the characteristic has a write handler, call it. if let Some(write_callback) = - characteristic.read().unwrap().write_callback + &characteristic.read().unwrap().write_callback { let value = unsafe { std::slice::from_raw_parts(param.value, param.len as usize) @@ -434,7 +448,7 @@ impl Profile { // Send response if needed. if param.need_rsp { - if let AttributeControl::ResponseByApp(read_callback) = characteristic.read().unwrap().control { + if let AttributeControl::ResponseByApp(read_callback) = &characteristic.read().unwrap().control { // Simulate a read operation. let param_as_read_operation = esp_ble_gatts_cb_param_t_gatts_read_evt_param { @@ -494,7 +508,7 @@ impl Profile { // Send response if needed. if param.need_rsp { - if let AttributeControl::ResponseByApp(read_callback) = descriptor.read().unwrap().control { + if let AttributeControl::ResponseByApp(read_callback) = &descriptor.read().unwrap().control { // Simulate a read operation. let param_as_read_operation = esp_ble_gatts_cb_param_t_gatts_read_evt_param { @@ -544,11 +558,6 @@ impl Profile { esp_gatts_cb_event_t_ESP_GATTS_READ_EVT => { let param = unsafe { (*param).read }; - debug!( - "MCC: Received read event for handle 0x{:04x}.", - param.handle - ); - for service in &self.services { service .read() @@ -571,7 +580,7 @@ impl Profile { // If the characteristic has a read handler, call it. if let AttributeControl::ResponseByApp(callback) = - characteristic.read().unwrap().control + &characteristic.read().unwrap().control { let value = callback(param); @@ -616,7 +625,7 @@ impl Profile { ); if let AttributeControl::ResponseByApp(callback) = - descriptor.read().unwrap().control + &descriptor.read().unwrap().control { let value = callback(param); diff --git a/src/utilities/attribute_control.rs b/src/utilities/attribute_control.rs index f281714..0d52c08 100644 --- a/src/utilities/attribute_control.rs +++ b/src/utilities/attribute_control.rs @@ -1,8 +1,9 @@ +use std::sync::Arc; use esp_idf_sys::*; -#[derive(Debug, Clone)] +#[derive(Clone)] pub(crate) enum AttributeControl { - ResponseByApp(fn(esp_ble_gatts_cb_param_t_gatts_read_evt_param) -> Vec), + ResponseByApp(Arc Vec + Send + Sync>), AutomaticResponse(Vec), } @@ -17,3 +18,12 @@ impl From for esp_attr_control_t { Self { auto_rsp: result } } } + +impl std::fmt::Debug for AttributeControl { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AttributeControl::AutomaticResponse(_) => write!(f, "automatic response"), + AttributeControl::ResponseByApp(_) => write!(f, "response by app"), + } + } +}