use crate::{
gatt_server::descriptor::Descriptor,
leaky_box_raw,
utilities::{AttributeControl, AttributePermissions, BleUuid, CharacteristicProperties},
};
use esp_idf_sys::{
esp_attr_control_t, esp_attr_value_t, esp_ble_gatts_add_char,
esp_ble_gatts_cb_param_t_gatts_read_evt_param, esp_ble_gatts_cb_param_t_gatts_write_evt_param,
esp_ble_gatts_set_attr_value, esp_nofail,
};
use log::{debug, warn};
use std::{
fmt::Formatter,
sync::{Arc, RwLock},
};
type WriteCallback = dyn Fn(Vec<u8>, esp_ble_gatts_cb_param_t_gatts_write_evt_param) + Send + Sync;
#[derive(Clone)]
pub struct Characteristic {
name: Option<String>,
pub(crate) uuid: BleUuid,
pub(crate) write_callback: Option<Arc<WriteCallback>>,
pub(crate) descriptors: Vec<Arc<RwLock<Descriptor>>>,
pub(crate) attribute_handle: Option<u16>,
service_handle: Option<u16>,
permissions: AttributePermissions,
pub(crate) properties: CharacteristicProperties,
pub(crate) control: AttributeControl,
pub(crate) internal_value: Vec<u8>,
max_value_length: Option<u16>,
internal_control: esp_attr_control_t,
}
impl Characteristic {
#[must_use]
pub fn new(uuid: BleUuid) -> Self {
Self {
name: None,
uuid,
internal_value: vec![0],
write_callback: None,
descriptors: Vec::new(),
attribute_handle: None,
service_handle: None,
permissions: AttributePermissions::default(),
properties: CharacteristicProperties::default(),
control: AttributeControl::AutomaticResponse(vec![0]),
internal_control: AttributeControl::AutomaticResponse(vec![0]).into(),
max_value_length: None,
}
}
pub fn descriptor(&mut self, descriptor: &Arc<RwLock<Descriptor>>) -> &mut Self {
self.descriptors.push(descriptor.clone());
self
}
pub fn name<S: Into<String>>(&mut self, name: S) -> &mut Self {
self.name = Some(name.into());
self
}
pub fn permissions(&mut self, permissions: AttributePermissions) -> &mut Self {
self.permissions = permissions;
self
}
pub fn properties(&mut self, properties: CharacteristicProperties) -> &mut Self {
self.properties = properties;
self
}
pub fn max_value_length(&mut self, length: u16) -> &mut Self {
self.max_value_length = Some(length);
self
}
pub fn on_read<
C: Fn(esp_ble_gatts_cb_param_t_gatts_read_evt_param) -> Vec<u8> + Send + Sync + 'static,
>(
&mut self,
callback: C,
) -> &mut Self {
if !self.properties.read || !self.permissions.read_access {
warn!(
"Characteristic {} does not have read permissions. Ignoring read callback.",
self
);
return self;
}
self.control = AttributeControl::ResponseByApp(Arc::new(callback));
self.internal_control = self.control.clone().into();
self
}
pub fn on_write(
&mut self,
callback: impl Fn(Vec<u8>, 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)
{
warn!(
"Characteristic {} does not have write permissions. Ignoring write callback.",
self
);
return self;
}
self.write_callback = Some(Arc::new(callback));
self
}
pub fn show_name(&mut self) -> &mut Self {
if let Some(name) = self.name.clone() {
self.descriptor(&Arc::new(RwLock::new(Descriptor::user_description(name))));
}
if let BleUuid::Uuid16(_) = self.uuid {
warn!("You're specifying a user description for a standard characteristic. This might be useless.");
}
self
}
pub fn set_value<T: Into<Vec<u8>>>(&mut self, value: T) -> &mut Self {
let value: Vec<u8> = value.into();
#[allow(clippy::manual_assert)]
if let Some(max_value_length) = self.max_value_length {
if value.len() > max_value_length as usize {
panic!(
"Value is too long for characteristic {}. The explicitly set maximum length is {} bytes.",
self, max_value_length
);
}
} else if self.attribute_handle.is_some() && value.len() > self.internal_value.len() {
panic!(
"Value is too long for characteristic {}. The implicitly set maximum length is {} bytes.",
self,
self.internal_value.len()
);
}
self.internal_value = value;
self.control = AttributeControl::AutomaticResponse(self.internal_value.clone());
self.internal_control = self.control.clone().into();
debug!(
"Trying to set value of {} to {:02X?}.",
self, self.internal_value
);
if let Some(handle) = self.attribute_handle {
#[allow(clippy::cast_possible_truncation)]
unsafe {
esp_nofail!(esp_ble_gatts_set_attr_value(
handle,
self.internal_value.len() as u16,
self.internal_value.as_slice().as_ptr()
));
}
}
self
}
#[must_use]
pub fn build(&self) -> Arc<RwLock<Self>> {
Arc::new(RwLock::new(self.clone()))
}
pub(crate) fn register_self(&mut self, service_handle: u16) {
debug!(
"Registering {} into service at handle 0x{:04x}.",
self, service_handle
);
self.service_handle = Some(service_handle);
#[allow(clippy::manual_assert)]
if let AttributeControl::AutomaticResponse(_) = self.control {
if self.internal_value.is_empty() {
panic!("Automatic response requires a value to be set.");
}
}
if self.properties.notify || self.properties.indicate {
self.descriptor(&Descriptor::cccd().build());
}
#[allow(clippy::cast_possible_truncation)]
unsafe {
esp_nofail!(esp_ble_gatts_add_char(
service_handle,
leaky_box_raw!(self.uuid.into()),
self.permissions.into(),
self.properties.into(),
leaky_box_raw!(esp_attr_value_t {
attr_max_len: self
.max_value_length
.unwrap_or(self.internal_value.len() as u16),
attr_len: self.internal_value.len() as u16,
attr_value: self.internal_value.as_mut_slice().as_mut_ptr(),
}),
&mut self.internal_control,
));
}
}
pub(crate) fn register_descriptors(&mut self) {
debug!("Registering {}'s descriptors.", &self);
self.descriptors.iter_mut().for_each(|descriptor| {
descriptor
.write()
.unwrap()
.register_self(self.service_handle.expect(
"Cannot register a descriptor to a characteristic without a service handle.",
));
});
}
pub(crate) fn get_cccd_status(
&self,
param: esp_ble_gatts_cb_param_t_gatts_read_evt_param,
) -> Option<(bool, bool)> {
if let Some(cccd) = self
.descriptors
.iter()
.find(|desc| desc.read().unwrap().uuid == BleUuid::Uuid16(0x2902))
{
if let AttributeControl::ResponseByApp(callback) = &cccd.read().unwrap().control {
let value = callback(param);
return Some((
value[0] & 0b0000_0001 == 0b0000_0001,
value[0] & 0b0000_0010 == 0b0000_0010,
));
}
}
None
}
}
impl std::fmt::Display for Characteristic {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} ({})",
self.name
.clone()
.unwrap_or_else(|| "Unnamed characteristic".to_string()),
self.uuid
)
}
}
impl std::fmt::Debug for Characteristic {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
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()
}
}