1use alloc::{boxed::Box, sync::Arc, vec::Vec};
2use bitflags::bitflags;
3use core::{cell::UnsafeCell, ffi::c_void};
4use esp_idf_svc::sys;
5#[cfg(not(cpfd))]
6use zerocopy::IntoBytes;
7
8use crate::{
9 AttValue, BLEConnDesc, BLEDescriptor, BLEDevice, BLEError, DescriptorProperties, OnWriteArgs,
10 ble,
11 cpfd::Cpfd,
12 utilities::{
13 BleUuid, OsMBuf, ble_npl_hw_enter_critical, ble_npl_hw_exit_critical, mutex::Mutex,
14 voidp_to_ref,
15 },
16};
17
18cfg_if::cfg_if! {
19 if #[cfg(any(
20 all(
21 esp_idf_version_major = "5",
22 esp_idf_version_minor = "2",
23 not(any(esp_idf_version_patch = "0", esp_idf_version_patch = "1", esp_idf_version_patch="2"))),
24 all(
25 esp_idf_version_major = "5",
26 esp_idf_version_minor = "3",
27 not(any(esp_idf_version_patch = "0", esp_idf_version_patch = "1"))),
28 all(
29 esp_idf_version_major = "5",
30 esp_idf_version_minor = "4"),
31 all(
32 esp_idf_version_major = "5",
33 esp_idf_version_minor = "5"),
34 ))] {
35 type NotifyTxType = sys::ble_gap_event__bindgen_ty_1__bindgen_ty_12;
36 type Subscribe = sys::ble_gap_event__bindgen_ty_1__bindgen_ty_13;
37 } else {
38 type NotifyTxType = sys::ble_gap_event__bindgen_ty_1__bindgen_ty_11;
39 type Subscribe = sys::ble_gap_event__bindgen_ty_1__bindgen_ty_12;
40 }
41}
42
43const NULL_HANDLE: u16 = 0xFFFF;
44
45cfg_if::cfg_if! {
46 if #[cfg(any(
47 all(
48 esp_idf_version_major = "5",
49 esp_idf_version_minor = "4",
50 not(any(esp_idf_version_patch = "0", esp_idf_version_patch = "1"))),
51 all(
52 esp_idf_version_major = "5",
53 esp_idf_version_minor = "5",
54 )
55 ))] {
56 type NimblePropertiesType = u32;
57 } else {
58 type NimblePropertiesType = u16;
59 }
60}
61
62bitflags! {
63 #[repr(transparent)]
64 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
65 pub struct NimbleProperties: NimblePropertiesType {
66 const READ = sys::BLE_GATT_CHR_F_READ as _;
68 const READ_ENC = sys::BLE_GATT_CHR_F_READ_ENC as _;
70 const READ_AUTHEN = sys::BLE_GATT_CHR_F_READ_AUTHEN as _;
72 const READ_AUTHOR = sys::BLE_GATT_CHR_F_READ_AUTHOR as _;
74 const WRITE = sys::BLE_GATT_CHR_F_WRITE as _;
76 const WRITE_NO_RSP = sys::BLE_GATT_CHR_F_WRITE_NO_RSP as _;
78 const WRITE_ENC = sys::BLE_GATT_CHR_F_WRITE_ENC as _;
80 const WRITE_AUTHEN = sys::BLE_GATT_CHR_F_WRITE_AUTHEN as _;
82 const WRITE_AUTHOR = sys::BLE_GATT_CHR_F_WRITE_AUTHOR as _;
84 const BROADCAST = sys::BLE_GATT_CHR_F_BROADCAST as _;
86 const NOTIFY = sys::BLE_GATT_CHR_F_NOTIFY as _;
88 const INDICATE = sys::BLE_GATT_CHR_F_INDICATE as _;
90
91 #[cfg(all(
92 esp_idf_version_major = "5",
93 esp_idf_version_minor = "4",
94 not(any(esp_idf_version_patch = "0", esp_idf_version_patch = "1"))))]
95 const NOTIFY_INDICATE_ENC = sys::BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC as _;
97
98 #[cfg(all(
99 esp_idf_version_major = "5",
100 esp_idf_version_minor = "4",
101 not(any(esp_idf_version_patch = "0", esp_idf_version_patch = "1"))))]
102 const NOTIFY_INDICATE_AUTHEN = sys::BLE_GATT_CHR_F_NOTIFY_INDICATE_AUTHEN as _;
104
105 #[cfg(all(
106 esp_idf_version_major = "5",
107 esp_idf_version_minor = "4",
108 not(any(esp_idf_version_patch = "0", esp_idf_version_patch = "1"))))]
109 const NOTIFY_INDICATE_AUTHOR = sys::BLE_GATT_CHR_F_NOTIFY_INDICATE_AUTHOR as _;
111 }
112}
113
114#[derive(PartialEq, Debug)]
115pub enum NotifyTxStatus {
116 SuccessIndicate,
117 SuccessNotify,
118 ErrorIndicateDisabled,
119 ErrorNotifyDisabled,
120 ErrorGatt,
121 ErrorNoClient,
122 ErrorIndicateTimeout,
123 ErrorIndicateFailure,
124}
125
126pub struct NotifyTx<'a> {
127 pub(crate) notify_tx: &'a NotifyTxType,
128}
129
130impl NotifyTx<'_> {
131 pub fn status(&self) -> NotifyTxStatus {
132 if self.notify_tx.indication() > 0 {
133 match self.notify_tx.status as _ {
134 sys::BLE_HS_EDONE => NotifyTxStatus::SuccessIndicate,
135 sys::BLE_HS_ETIMEOUT => NotifyTxStatus::ErrorIndicateTimeout,
136 _ => NotifyTxStatus::ErrorIndicateFailure,
137 }
138 } else {
139 #[allow(clippy::collapsible_else_if)]
140 if self.notify_tx.status == 0 {
141 NotifyTxStatus::SuccessNotify
142 } else {
143 NotifyTxStatus::ErrorGatt
144 }
145 }
146 }
147
148 pub fn desc(&self) -> Result<BLEConnDesc, crate::BLEError> {
149 crate::utilities::ble_gap_conn_find(self.notify_tx.conn_handle)
150 }
151}
152
153bitflags! {
154 #[repr(transparent)]
155 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
156 pub struct NimbleSub: u16 {
158 const NOTIFY = 0x0001;
160 const INDICATE = 0x0002;
162 }
163}
164
165#[allow(clippy::type_complexity)]
166pub struct BLECharacteristic {
167 pub(crate) uuid: sys::ble_uuid_any_t,
168 pub(crate) handle: u16,
169 pub(crate) properties: NimbleProperties,
170 value: AttValue,
171 on_read: Option<Box<dyn FnMut(&mut Self, &BLEConnDesc) + Send + Sync>>,
172 on_write: Option<Box<dyn FnMut(&mut OnWriteArgs) + Send + Sync>>,
173 pub(crate) on_notify_tx: Option<Box<dyn FnMut(NotifyTx) + Send + Sync>>,
174 descriptors: Vec<Arc<Mutex<BLEDescriptor>>>,
175 svc_def_descriptors: Vec<sys::ble_gatt_dsc_def>,
176 subscribed_list: Vec<(u16, NimbleSub)>,
177 on_subscribe: Option<Box<dyn FnMut(&Self, &BLEConnDesc, NimbleSub) + Send + Sync>>,
178 #[cfg(cpfd)]
179 pub(crate) cpfd: [sys::ble_gatt_cpfd; 2],
180}
181
182impl BLECharacteristic {
183 pub(crate) fn new(uuid: BleUuid, properties: NimbleProperties) -> Self {
184 Self {
185 uuid: sys::ble_uuid_any_t::from(uuid),
186 handle: NULL_HANDLE,
187 properties,
188 value: AttValue::new(),
189 on_read: None,
190 on_write: None,
191 on_notify_tx: None,
192 descriptors: Vec::new(),
193 svc_def_descriptors: Vec::new(),
194 subscribed_list: Vec::new(),
195 on_subscribe: None,
196 #[cfg(cpfd)]
197 cpfd: [Default::default(); 2],
198 }
199 }
200
201 pub fn uuid(&self) -> BleUuid {
202 BleUuid::from(self.uuid)
203 }
204
205 pub fn set_value(&mut self, value: &[u8]) -> &mut Self {
206 self.value.set_value(value);
207 self
208 }
209
210 #[deprecated(note = "Please use `set_value` + zerocopy::IntoBytes")]
211 pub fn set_from<T: Sized>(&mut self, value: &T) -> &mut Self {
212 #[allow(deprecated)]
213 self.value.set_from(value);
214 self
215 }
216
217 pub fn value_mut(&mut self) -> &mut AttValue {
218 &mut self.value
219 }
220
221 pub fn on_read(
222 &mut self,
223 callback: impl FnMut(&mut Self, &BLEConnDesc) + Send + Sync + 'static,
224 ) -> &mut Self {
225 self.on_read = Some(Box::new(callback));
226 self
227 }
228
229 pub fn on_write(
231 &mut self,
232 callback: impl FnMut(&mut OnWriteArgs) + Send + Sync + 'static,
233 ) -> &mut Self {
234 self.on_write = Some(Box::new(callback));
235 self
236 }
237
238 pub fn on_notify_tx(
239 &mut self,
240 callback: impl FnMut(NotifyTx) + Send + Sync + 'static,
241 ) -> &mut Self {
242 self.on_notify_tx = Some(Box::new(callback));
243 self
244 }
245
246 pub fn create_descriptor(
247 &mut self,
248 uuid: BleUuid,
249 properties: DescriptorProperties,
250 ) -> Arc<Mutex<BLEDescriptor>> {
251 if uuid == BleUuid::Uuid16(sys::BLE_GATT_DSC_CLT_CFG_UUID16 as _) {
252 panic!("0x2902 descriptors cannot be manually created");
253 }
254
255 let descriptor = Arc::new(Mutex::new(BLEDescriptor::new(uuid, properties)));
256 self.descriptors.push(descriptor.clone());
257 descriptor
258 }
259
260 pub(crate) fn construct_svc_def_descriptors(&mut self) -> *mut sys::ble_gatt_dsc_def {
261 if self.descriptors.is_empty() {
262 return core::ptr::null_mut();
263 }
264 self.svc_def_descriptors.clear();
265
266 for dsc in &mut self.descriptors {
267 let arg = unsafe { Arc::get_mut_unchecked(dsc) } as *mut Mutex<BLEDescriptor>;
268 let dsc = dsc.lock();
269 self.svc_def_descriptors.push(sys::ble_gatt_dsc_def {
270 uuid: unsafe { &dsc.uuid.u },
271 att_flags: dsc.properties.bits(),
272 min_key_size: 0,
273 access_cb: Some(BLEDescriptor::handle_gap_event),
274 arg: arg as _,
275 });
276 }
277 self.svc_def_descriptors
278 .push(sys::ble_gatt_dsc_def::default());
279 self.svc_def_descriptors.as_mut_ptr()
280 }
281
282 pub fn notify_with(&self, value: &[u8], conn_handle: u16) -> Result<(), BLEError> {
283 if let Some((_, flag)) = self.subscribed_list.iter().find(|x| x.0 == conn_handle) {
284 self.send_value(value, conn_handle, *flag)
285 } else {
286 BLEError::convert(sys::BLE_HS_EINVAL)
287 }
288 }
289
290 pub fn notify(&self) {
291 for it in &self.subscribed_list {
292 if let Err(err) = self.send_value(self.value.as_slice(), it.0, it.1) {
293 ::log::warn!("notify error({}): {:?}", it.0, err);
294 }
295 }
296 }
297
298 fn send_value(&self, value: &[u8], conn_handle: u16, flag: NimbleSub) -> Result<(), BLEError> {
299 let mtu = unsafe { sys::ble_att_mtu(conn_handle) - 3 };
300 if mtu == 0 || flag.is_empty() {
301 return BLEError::convert(sys::BLE_HS_EINVAL);
302 }
303 let server = BLEDevice::take().get_server();
304
305 if flag.contains(NimbleSub::INDICATE)
306 && self.properties.contains(NimbleProperties::INDICATE)
307 {
308 if !server.set_indicate_wait(conn_handle) {
309 ::log::error!("prior Indication in progress");
310 return BLEError::convert(sys::BLE_HS_EBUSY);
311 }
312
313 let om = OsMBuf::from_flat(value);
314 let rc = unsafe { sys::ble_gatts_indicate_custom(conn_handle, self.handle, om.0) };
315 if rc != 0 {
316 server.clear_indicate_wait(conn_handle);
317 }
318 BLEError::convert(rc as _)
319 } else if flag.contains(NimbleSub::NOTIFY)
320 && self.properties.contains(NimbleProperties::NOTIFY)
321 {
322 let om = OsMBuf::from_flat(value);
323 ble!(unsafe { sys::ble_gatts_notify_custom(conn_handle, self.handle, om.0) })
324 } else {
325 BLEError::convert(sys::BLE_HS_EINVAL)
326 }
327 }
328
329 #[cfg(cpfd)]
330 pub fn cpfd(&mut self, cpfd: Cpfd) {
332 if cpfd.name_space == (sys::BLE_GATT_CHR_NAMESPACE_BT_SIG as _) {
333 debug_assert!(cpfd.description <= (sys::BLE_GATT_CHR_BT_SIG_DESC_EXTERNAL as _));
334 }
335
336 self.cpfd[0].format = cpfd.format.into();
337 self.cpfd[0].exponent = cpfd.exponent;
338 self.cpfd[0].unit = cpfd.unit.into();
339 self.cpfd[0].name_space = cpfd.name_space;
340 self.cpfd[0].description = cpfd.description;
341 }
342
343 #[cfg(not(cpfd))]
344 pub fn cpfd(&mut self, cpfd: Cpfd) {
346 let descriptor = Arc::new(Mutex::new(BLEDescriptor::new(
347 BleUuid::Uuid16(0x2904),
348 DescriptorProperties::READ,
349 )));
350 descriptor.lock().set_value(cpfd.as_bytes());
351 self.descriptors.push(descriptor);
352 }
353
354 pub(super) extern "C" fn handle_gap_event(
355 conn_handle: u16,
356 _attr_handle: u16,
357 ctxt: *mut sys::ble_gatt_access_ctxt,
358 arg: *mut c_void,
359 ) -> i32 {
360 let ctxt = unsafe { &*ctxt };
361
362 let mutex = unsafe { voidp_to_ref::<Mutex<Self>>(arg) };
363
364 if crate::utilities::ble_gap_conn_find(conn_handle).is_err() {
365 ::log::warn!("the conn handle does not exist");
366 return sys::BLE_ATT_ERR_UNLIKELY as _;
367 }
368
369 let mut characteristic = mutex.lock();
370 if unsafe {
371 sys::ble_uuid_cmp((*ctxt.__bindgen_anon_1.chr).uuid, &characteristic.uuid.u) != 0
372 } {
373 return sys::BLE_ATT_ERR_UNLIKELY as _;
374 }
375
376 match ctxt.op as _ {
377 sys::BLE_GATT_ACCESS_OP_READ_CHR => {
378 let desc = crate::utilities::ble_gap_conn_find(conn_handle).unwrap();
379
380 unsafe {
381 if (*(ctxt.om)).om_pkthdr_len > 8
382 || characteristic.value.len() <= (desc.mtu() - 3) as _
383 {
384 let characteristic = UnsafeCell::new(&mut characteristic);
385 if let Some(callback) = &mut (&mut (*characteristic.get())).on_read {
386 callback(*characteristic.get(), &desc);
387 }
388 }
389 }
390
391 ble_npl_hw_enter_critical();
392 let value = characteristic.value.as_slice();
393 let rc = OsMBuf(ctxt.om).append(value);
394 ble_npl_hw_exit_critical();
395 if rc == 0 {
396 0
397 } else {
398 sys::BLE_ATT_ERR_INSUFFICIENT_RES as _
399 }
400 }
401 sys::BLE_GATT_ACCESS_OP_WRITE_CHR => {
402 let om = OsMBuf(ctxt.om);
403 let buf = om.as_flat();
404
405 let mut notify = false;
406
407 unsafe {
408 let characteristic = UnsafeCell::new(&mut characteristic);
409 if let Some(callback) = &mut (&mut (*characteristic.get())).on_write {
410 let desc = crate::utilities::ble_gap_conn_find(conn_handle).unwrap();
411 let mut arg = OnWriteArgs {
412 current_data: (&(*characteristic.get())).value.as_slice(),
413 recv_data: buf.as_slice(),
414 desc: &desc,
415 reject: false,
416 error_code: 0,
417 notify: false,
418 };
419 callback(&mut arg);
420
421 if arg.reject {
422 return arg.error_code as _;
423 }
424 notify = arg.notify;
425 }
426 }
427 characteristic.set_value(buf.as_slice());
428 if notify {
429 characteristic.notify();
430 }
431
432 0
433 }
434 _ => sys::BLE_ATT_ERR_UNLIKELY as _,
435 }
436 }
437
438 pub(super) fn subscribe(&mut self, subscribe: &Subscribe) {
439 let Ok(desc) = crate::utilities::ble_gap_conn_find(subscribe.conn_handle) else {
440 return;
441 };
442
443 let mut sub_val = NimbleSub::empty();
444 if subscribe.cur_notify() > 0 && (self.properties.contains(NimbleProperties::NOTIFY)) {
445 sub_val.insert(NimbleSub::NOTIFY);
446 }
447 if subscribe.cur_indicate() > 0 && (self.properties.contains(NimbleProperties::INDICATE)) {
448 sub_val.insert(NimbleSub::INDICATE);
449 }
450
451 if let Some(idx) = self
452 .subscribed_list
453 .iter()
454 .position(|x| x.0 == subscribe.conn_handle)
455 {
456 if !sub_val.is_empty() {
457 self.subscribed_list[idx].1 = sub_val;
458 } else {
459 self.subscribed_list.swap_remove(idx);
460 }
461 } else if !sub_val.is_empty() {
462 self.subscribed_list.push((subscribe.conn_handle, sub_val));
463 }
464
465 unsafe {
466 let self_ = UnsafeCell::new(self);
467 if let Some(callback) = &mut (*self_.get()).on_subscribe {
468 callback(*self_.get(), &desc, sub_val);
469 }
470 }
471 }
472
473 pub fn on_subscribe(
478 &mut self,
479 callback: impl FnMut(&Self, &BLEConnDesc, NimbleSub) + Send + Sync + 'static,
480 ) -> &mut Self {
481 self.on_subscribe = Some(Box::new(callback));
482 self
483 }
484
485 pub fn subscribed_count(&self) -> usize {
486 self.subscribed_list.len()
487 }
488}
489
490impl core::fmt::Debug for BLECharacteristic {
491 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
492 f.debug_struct("BLECharacteristic")
493 .field("uuid", &BleUuid::from(self.uuid))
494 .field("properties", &self.properties)
495 .finish()
496 }
497}
498
499unsafe impl Send for BLECharacteristic {}