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