esp_idf_svc/ble/gatt/client.rs
1//! NimBLE GATT client: connection, discovery, read/write, and received notifications, as client
2//! operations on the [`BleDriver`].
3//!
4//! Every `ble_gattc_*` operation is initiate-now, complete-later: you start it, and NimBLE invokes
5//! a callback when it finishes. We route *all* of those per-operation callbacks through one shared
6//! [`gattc_subscribe`](BleDriver::gattc_subscribe) hook, correlated by `conn_handle` (GATT serializes
7//! one transaction per connection). Received notifications/indications ([`GattcEvent::Notify`])
8//! arrive on the connection's GAP callback and are demuxed here.
9
10use core::ffi::{c_int, c_void};
11use core::ptr;
12
13use crate::sys::*;
14
15use super::super::mbuf::Mbuf;
16use super::super::{BleAddr, BleDriver, BleError, BleUuid, ConnHandle};
17use super::AttrHandle;
18
19/// A GATT-client event, delivered on the host task to the single
20/// [`gattc_subscribe`](BleDriver::gattc_subscribe) hook.
21///
22/// The discovery variants fire once per discovered item and then once more with a `None` payload
23/// to signal completion. `status` is the raw ATT/`BLE_HS_*` status (`0` on success).
24pub enum GattcEvent<'a> {
25 /// A service discovered by [`discover_services`](BleDriver::discover_services).
26 Service {
27 conn_handle: ConnHandle,
28 status: u16,
29 service: Option<GattcService>,
30 },
31 /// A characteristic discovered by
32 /// [`discover_characteristics`](BleDriver::discover_characteristics).
33 Characteristic {
34 conn_handle: ConnHandle,
35 status: u16,
36 chr: Option<GattcChr>,
37 },
38 /// Completion of a [`read`](BleDriver::read). On success `data` holds the value; check `status`
39 /// before reading it.
40 ReadComplete {
41 conn_handle: ConnHandle,
42 status: u16,
43 attr_handle: AttrHandle,
44 data: Mbuf<'a>,
45 },
46 /// Completion of a [`write`](BleDriver::write).
47 WriteComplete {
48 conn_handle: ConnHandle,
49 status: u16,
50 attr_handle: AttrHandle,
51 },
52 /// A notification or indication pushed by the peer (after subscribing by writing its CCCD).
53 Notify {
54 conn_handle: ConnHandle,
55 attr_handle: AttrHandle,
56 indication: bool,
57 data: Mbuf<'a>,
58 },
59}
60
61impl<'a> GattcEvent<'a> {
62 /// Build [`Notify`](Self::Notify) from a raw GAP event. Called from the GAP trampoline's
63 /// demux for `BLE_GAP_EVENT_NOTIFY_RX`.
64 pub(crate) fn from_notify_rx(event: &'a ble_gap_event) -> Self {
65 let notify_rx = unsafe { &event.__bindgen_anon_1.notify_rx };
66
67 Self::Notify {
68 conn_handle: notify_rx.conn_handle,
69 attr_handle: notify_rx.attr_handle,
70 indication: notify_rx.indication() != 0,
71 data: Mbuf::from_raw(notify_rx.om),
72 }
73 }
74}
75
76/// A remote service discovered on a peer (safe view of `ble_gatt_svc`).
77pub struct GattcService {
78 pub start_handle: AttrHandle,
79 pub end_handle: AttrHandle,
80 pub uuid: BleUuid,
81}
82
83impl From<&ble_gatt_svc> for GattcService {
84 fn from(svc: &ble_gatt_svc) -> Self {
85 Self {
86 start_handle: svc.start_handle,
87 end_handle: svc.end_handle,
88 // `ble_uuid_any_t`'s first union member is the `ble_uuid_t` header, at offset 0.
89 uuid: unsafe { BleUuid::from_raw((&svc.uuid as *const ble_uuid_any_t).cast()) },
90 }
91 }
92}
93
94/// A remote characteristic discovered on a peer (safe view of `ble_gatt_chr`).
95pub struct GattcChr {
96 pub def_handle: AttrHandle,
97 pub val_handle: AttrHandle,
98 /// Characteristic properties bitmask (`BLE_GATT_CHR_PROP_*`).
99 pub properties: u8,
100 pub uuid: BleUuid,
101}
102
103impl From<&ble_gatt_chr> for GattcChr {
104 fn from(chr: &ble_gatt_chr) -> Self {
105 Self {
106 def_handle: chr.def_handle,
107 val_handle: chr.val_handle,
108 properties: chr.properties,
109 uuid: unsafe { BleUuid::from_raw((&chr.uuid as *const ble_uuid_any_t).cast()) },
110 }
111 }
112}
113
114/// GATT-client operations on the [`BleDriver`]. Available for any role (`S`) — a device can be
115/// both a server and a client. `&self`, so callable re-entrantly from within the client callback.
116impl<'d, S> BleDriver<'d, S> {
117 /// Subscribe to GATT-client events ([`GattcEvent`]): per-operation completions plus received
118 /// notifications/indications.
119 pub fn gattc_subscribe<F>(&self, callback: F)
120 where
121 F: for<'a> FnMut(GattcEvent<'a>) + Send + 'static,
122 {
123 unsafe { self.gattc_subscribe_nonstatic(callback) }
124 }
125
126 /// # Safety
127 ///
128 /// The non-`'static` counterpart of [`gattc_subscribe`](Self::gattc_subscribe). See
129 /// [`BleDriver::host_subscribe_nonstatic`](crate::ble::BleDriver::host_subscribe_nonstatic) for the borrowing
130 /// rules and the `core::mem::forget` hazard.
131 pub unsafe fn gattc_subscribe_nonstatic<F>(&self, callback: F)
132 where
133 F: for<'a> FnMut(GattcEvent<'a>) + Send + 'd,
134 {
135 unsafe { super::super::SINGLETON.gattc.subscribe_nonstatic(callback) };
136 }
137
138 /// Stop delivering GATT-client events to the subscribed hook.
139 pub fn gattc_unsubscribe(&self) {
140 super::super::SINGLETON.gattc.unsubscribe();
141 }
142
143 /// Initiate a connection to `peer`. The connect/disconnect outcome arrives on the GAP hook
144 /// ([`gap_subscribe`](BleDriver::gap_subscribe)); the connection's received notifications arrive
145 /// on the GATTC hook.
146 pub fn connect(&self, own_addr_type: u8, peer: &BleAddr) -> Result<(), BleError> {
147 // bindgen does not emit `BLE_HS_FOREVER` (its C macro is `INT32_MAX`); inline it.
148 const BLE_HS_FOREVER: c_int = i32::MAX;
149
150 BleError::from_raw(unsafe {
151 ble_gap_connect(
152 own_addr_type,
153 peer.raw() as *const _,
154 BLE_HS_FOREVER,
155 ptr::null(),
156 Some(super::super::BleSingleton::gap_event_cb),
157 ptr::null_mut(),
158 )
159 })
160 }
161
162 /// Terminate the connection `conn_handle`.
163 pub fn disconnect(&self, conn_handle: ConnHandle) -> Result<(), BleError> {
164 // 0x13 = BLE_ERR_REM_USER_CONN_TERM ("remote user terminated connection").
165 BleError::from_raw(unsafe { ble_gap_terminate(conn_handle, 0x13) })
166 }
167
168 /// Discover all of the peer's primary services. Results arrive as [`GattcEvent::Service`].
169 pub fn discover_services(&self, conn_handle: ConnHandle) -> Result<(), BleError> {
170 BleError::from_raw(unsafe {
171 ble_gattc_disc_all_svcs(
172 conn_handle,
173 Some(super::super::BleSingleton::gattc_disc_svc_cb),
174 ptr::null_mut(),
175 )
176 })
177 }
178
179 /// Discover the peer's characteristics in the attribute-handle range `[start_handle,
180 /// end_handle]` (e.g. a service's range). Results arrive as [`GattcEvent::Characteristic`].
181 pub fn discover_characteristics(
182 &self,
183 conn_handle: ConnHandle,
184 start_handle: AttrHandle,
185 end_handle: AttrHandle,
186 ) -> Result<(), BleError> {
187 BleError::from_raw(unsafe {
188 ble_gattc_disc_all_chrs(
189 conn_handle,
190 start_handle,
191 end_handle,
192 Some(super::super::BleSingleton::gattc_disc_chr_cb),
193 ptr::null_mut(),
194 )
195 })
196 }
197
198 /// Read the value of `attr_handle` on the peer (a Read Request). The result arrives as
199 /// [`GattcEvent::ReadComplete`].
200 pub fn read(&self, conn_handle: ConnHandle, attr_handle: AttrHandle) -> Result<(), BleError> {
201 BleError::from_raw(unsafe {
202 ble_gattc_read(
203 conn_handle,
204 attr_handle,
205 Some(super::super::BleSingleton::gattc_read_cb),
206 ptr::null_mut(),
207 )
208 })
209 }
210
211 /// Write `data` to `attr_handle` on the peer as a **Write Request** (acknowledged): the peer
212 /// sends a Write Response, and its completion arrives as [`GattcEvent::WriteComplete`].
213 pub fn write(
214 &self,
215 conn_handle: ConnHandle,
216 attr_handle: AttrHandle,
217 data: &[u8],
218 ) -> Result<(), BleError> {
219 // NimBLE copies `data` into its own mbuf, so it need not outlive the call.
220 BleError::from_raw(unsafe {
221 ble_gattc_write_flat(
222 conn_handle,
223 attr_handle,
224 data.as_ptr() as *const c_void,
225 data.len() as u16,
226 Some(super::super::BleSingleton::gattc_write_cb),
227 ptr::null_mut(),
228 )
229 })
230 }
231
232 /// Write `data` to `attr_handle` on the peer as a **Write Command** (unacknowledged): the peer
233 /// sends no response, so this is fire-and-forget — there is **no** completion event. The
234 /// returned `Result` only reflects whether the command was accepted for transmission.
235 pub fn write_cmd(
236 &self,
237 conn_handle: ConnHandle,
238 attr_handle: AttrHandle,
239 data: &[u8],
240 ) -> Result<(), BleError> {
241 BleError::from_raw(unsafe {
242 ble_gattc_write_no_rsp_flat(
243 conn_handle,
244 attr_handle,
245 data.as_ptr() as *const c_void,
246 data.len() as u16,
247 )
248 })
249 }
250}