esp32_nimble/client/
ble_client.rs

1use crate::{
2    BLEAddress, BLEConnDesc, BLEDevice, BLEError, BLERemoteService, Signal, ble,
3    ble_device::OWN_ADDR_TYPE,
4    utilities::{ArcUnsafeCell, BleUuid, as_void_ptr, voidp_to_ref},
5};
6use alloc::{boxed::Box, vec::Vec};
7use core::{cell::UnsafeCell, ffi::c_void, ptr};
8use esp_idf_svc::sys as esp_idf_sys;
9use esp_idf_sys::*;
10
11#[allow(clippy::type_complexity)]
12pub(crate) struct BLEClientState {
13    address: Option<BLEAddress>,
14    pub(crate) conn_handle: u16,
15    services: Option<Vec<BLERemoteService>>,
16    signal: Signal<u32>,
17    connect_timeout_ms: u32,
18    ble_gap_conn_params: ble_gap_conn_params,
19    on_passkey_request: Option<Box<dyn Fn() -> u32 + Send + Sync>>,
20    on_confirm_pin: Option<Box<dyn Fn(u32) -> bool + Send + Sync>>,
21    on_connect: Option<Box<dyn Fn(&mut BLEClient) + Send + Sync>>,
22    on_disconnect: Option<Box<dyn Fn(i32) + Send + Sync>>,
23}
24
25pub struct BLEClient {
26    state: ArcUnsafeCell<BLEClientState>,
27}
28
29impl BLEClient {
30    pub(crate) fn new() -> Self {
31        Self {
32            state: ArcUnsafeCell::new(BLEClientState {
33                address: None,
34                conn_handle: esp_idf_sys::BLE_HS_CONN_HANDLE_NONE as _,
35                services: None,
36                connect_timeout_ms: 30000,
37                ble_gap_conn_params: ble_gap_conn_params {
38                    scan_itvl: 16,
39                    scan_window: 16,
40                    itvl_min: (30 * 1000 / BLE_HCI_CONN_ITVL) as _,
41                    itvl_max: (50 * 1000 / BLE_HCI_CONN_ITVL) as _,
42                    latency: BLE_GAP_INITIAL_CONN_LATENCY as _,
43                    supervision_timeout: BLE_GAP_INITIAL_SUPERVISION_TIMEOUT as _,
44                    min_ce_len: BLE_GAP_INITIAL_CONN_MIN_CE_LEN as _,
45                    max_ce_len: BLE_GAP_INITIAL_CONN_MAX_CE_LEN as _,
46                },
47                signal: Signal::new(),
48                on_passkey_request: None,
49                on_confirm_pin: None,
50                on_disconnect: None,
51                on_connect: None,
52            }),
53        }
54    }
55
56    pub(crate) fn conn_handle(&self) -> u16 {
57        self.state.conn_handle
58    }
59
60    pub fn on_passkey_request(
61        &mut self,
62        callback: impl Fn() -> u32 + Send + Sync + 'static,
63    ) -> &mut Self {
64        self.state.on_passkey_request = Some(Box::new(callback));
65        self
66    }
67
68    pub fn on_confirm_pin(
69        &mut self,
70        callback: impl Fn(u32) -> bool + Send + Sync + 'static,
71    ) -> &mut Self {
72        self.state.on_confirm_pin = Some(Box::new(callback));
73        self
74    }
75
76    pub fn on_connect(
77        &mut self,
78        callback: impl Fn(&mut Self) + Send + Sync + 'static,
79    ) -> &mut Self {
80        self.state.on_connect = Some(Box::new(callback));
81        self
82    }
83
84    pub fn on_disconnect(&mut self, callback: impl Fn(i32) + Send + Sync + 'static) -> &mut Self {
85        self.state.on_disconnect = Some(Box::new(callback));
86        self
87    }
88
89    pub async fn connect(&mut self, addr: &BLEAddress) -> Result<(), BLEError> {
90        unsafe {
91            if esp_idf_sys::ble_gap_conn_find_by_addr(&addr.value, core::ptr::null_mut()) == 0 {
92                ::log::warn!("A connection to {addr:?} already exists");
93                return BLEError::fail();
94            }
95
96            ble!(esp_idf_sys::ble_gap_connect(
97                OWN_ADDR_TYPE as _,
98                &addr.value,
99                self.state.connect_timeout_ms as _,
100                &self.state.ble_gap_conn_params,
101                Some(Self::handle_gap_event),
102                as_void_ptr(self),
103            ))?;
104        }
105
106        ble!(self.state.signal.wait().await)?;
107        self.state.address = Some(*addr);
108
109        let mut client = UnsafeCell::new(self);
110        unsafe {
111            if let Some(callback) = &(&(*client.get())).state.on_connect {
112                callback(client.get_mut());
113            }
114        }
115
116        Ok(())
117    }
118
119    pub async fn secure_connection(&mut self) -> Result<(), BLEError> {
120        unsafe {
121            ble!(esp_idf_sys::ble_gap_security_initiate(
122                self.state.conn_handle
123            ))?;
124        }
125        ble!(self.state.signal.wait().await)?;
126
127        ::log::info!("secure_connection: success");
128
129        Ok(())
130    }
131
132    /// Disconnect from the peer.
133    pub fn disconnect(&mut self) -> Result<(), BLEError> {
134        self.disconnect_with_reason(esp_idf_sys::ble_error_codes_BLE_ERR_REM_USER_CONN_TERM as _)
135    }
136
137    /// Disconnect from the peer with optional reason.
138    pub fn disconnect_with_reason(&mut self, reason: u8) -> Result<(), BLEError> {
139        if !self.connected() {
140            return Ok(());
141        }
142
143        unsafe {
144            ble!(esp_idf_sys::ble_gap_terminate(
145                self.state.conn_handle,
146                reason
147            ))
148        }
149    }
150
151    pub fn connected(&self) -> bool {
152        self.state.conn_handle != (esp_idf_sys::BLE_HS_CONN_HANDLE_NONE as _)
153    }
154
155    /// Set the connection parameters to use when connecting to a server.
156    ///
157    /// * `min_interval`: The minimum connection interval in 1.25ms units.
158    /// * `max_interval`: The maximum connection interval in 1.25ms units.
159    /// * `latency`: The number of packets allowed to skip (extends max interval).
160    /// * `timeout`: The timeout time in 10ms units before disconnecting.
161    /// * `scan_interval`: The scan interval to use when attempting to connect in 0.625ms units.
162    /// * `scan_window`: The scan window to use when attempting to connect in 0.625ms units.
163    pub fn set_connection_params(
164        &mut self,
165        min_interval: u16,
166        max_interval: u16,
167        latency: u16,
168        timeout: u16,
169        scan_interval: u16,
170        scan_window: u16,
171    ) {
172        self.state.ble_gap_conn_params.scan_itvl = scan_interval;
173        self.state.ble_gap_conn_params.scan_window = scan_window;
174        self.state.ble_gap_conn_params.itvl_min = min_interval;
175        self.state.ble_gap_conn_params.itvl_max = max_interval;
176        self.state.ble_gap_conn_params.latency = latency;
177        self.state.ble_gap_conn_params.supervision_timeout = timeout;
178    }
179
180    /// Request an Update the connection parameters:
181    /// Can only be used after a connection has been established.
182    ///
183    /// * `min_interval`: The minimum connection interval in 1.25ms units.
184    /// * `max_interval`: The maximum connection interval in 1.25ms units.
185    /// * `latency`: The number of packets allowed to skip (extends max interval).
186    /// * `timeout`: The timeout time in 10ms units before disconnecting.
187    pub fn update_conn_params(
188        &mut self,
189        min_interval: u16,
190        max_interval: u16,
191        latency: u16,
192        timeout: u16,
193    ) -> Result<(), BLEError> {
194        let params = esp_idf_sys::ble_gap_upd_params {
195            itvl_min: min_interval,
196            itvl_max: max_interval,
197            latency,
198            supervision_timeout: timeout,
199            min_ce_len: esp_idf_sys::BLE_GAP_INITIAL_CONN_MIN_CE_LEN as _,
200            max_ce_len: esp_idf_sys::BLE_GAP_INITIAL_CONN_MAX_CE_LEN as _,
201        };
202
203        unsafe {
204            ble!(esp_idf_sys::ble_gap_update_params(
205                self.state.conn_handle,
206                &params
207            ))
208        }
209    }
210
211    pub fn desc(&self) -> Result<BLEConnDesc, crate::BLEError> {
212        crate::utilities::ble_gap_conn_find(self.conn_handle())
213    }
214
215    /// Retrieves the most-recently measured RSSI.
216    /// A connection’s RSSI is updated whenever a data channel PDU is received.
217    pub fn get_rssi(&self) -> Result<i8, BLEError> {
218        let mut rssi: i8 = 0;
219        unsafe {
220            ble!(esp_idf_sys::ble_gap_conn_rssi(
221                self.conn_handle(),
222                &mut rssi
223            ))?;
224        }
225        Ok(rssi)
226    }
227
228    pub async fn get_services(
229        &mut self,
230    ) -> Result<core::slice::IterMut<'_, BLERemoteService>, BLEError> {
231        if self.state.services.is_none() {
232            self.state.services = Some(Vec::new());
233            unsafe {
234                esp_idf_sys::ble_gattc_disc_all_svcs(
235                    self.state.conn_handle,
236                    Some(Self::service_discovered_cb),
237                    as_void_ptr(self),
238                );
239            }
240            ble!(self.state.signal.wait().await)?;
241        }
242
243        Ok(self.state.services.as_mut().unwrap().iter_mut())
244    }
245
246    pub async fn get_service(&mut self, uuid: BleUuid) -> Result<&mut BLERemoteService, BLEError> {
247        let mut iter = self.get_services().await?;
248        iter.find(|x| x.uuid() == uuid)
249            .ok_or_else(|| BLEError::fail().unwrap_err())
250    }
251
252    extern "C" fn handle_gap_event(
253        event: *mut esp_idf_sys::ble_gap_event,
254        arg: *mut c_void,
255    ) -> i32 {
256        let event = unsafe { &*event };
257        let client = unsafe { voidp_to_ref::<Self>(arg) };
258
259        match event.type_ as _ {
260            BLE_GAP_EVENT_CONNECT => {
261                let connect = unsafe { &event.__bindgen_anon_1.connect };
262
263                if connect.status == 0 {
264                    client.state.conn_handle = connect.conn_handle;
265
266                    let rc = unsafe {
267                        ble_gattc_exchange_mtu(connect.conn_handle, None, core::ptr::null_mut())
268                    };
269
270                    if rc != 0 {
271                        client.state.signal.signal(rc as _);
272                    }
273                } else {
274                    ::log::info!("connect_status {}", connect.status);
275                    client.state.conn_handle = esp_idf_sys::BLE_HS_CONN_HANDLE_NONE as _;
276                    client.state.signal.signal(connect.status as _);
277                }
278            }
279            BLE_GAP_EVENT_DISCONNECT => {
280                let disconnect = unsafe { &event.__bindgen_anon_1.disconnect };
281                if client.state.conn_handle != disconnect.conn.conn_handle {
282                    return 0;
283                }
284                client.state.conn_handle = esp_idf_sys::BLE_HS_CONN_HANDLE_NONE as _;
285
286                ::log::info!(
287                    "Disconnected: {:?}",
288                    BLEError::convert(disconnect.reason as _)
289                );
290
291                if let Some(callback) = &client.state.on_disconnect {
292                    callback(disconnect.reason);
293                }
294            }
295            BLE_GAP_EVENT_ENC_CHANGE => {
296                let enc_change = unsafe { &event.__bindgen_anon_1.enc_change };
297                if client.state.conn_handle != enc_change.conn_handle {
298                    return 0;
299                }
300
301                if enc_change.status
302                    == ((BLE_HS_ERR_HCI_BASE + ble_error_codes_BLE_ERR_PINKEY_MISSING) as _)
303                {
304                    let desc = crate::utilities::ble_gap_conn_find(enc_change.conn_handle).unwrap();
305                    unsafe { esp_idf_sys::ble_store_util_delete_peer(&desc.0.peer_id_addr) };
306                }
307
308                client.state.signal.signal(enc_change.status as _);
309            }
310            BLE_GAP_EVENT_MTU => {
311                let mtu = unsafe { &event.__bindgen_anon_1.mtu };
312                if client.state.conn_handle != mtu.conn_handle {
313                    return 0;
314                }
315                ::log::info!(
316                    "mtu update event; conn_handle={} mtu={}",
317                    mtu.conn_handle,
318                    mtu.value
319                );
320                client.state.signal.signal(0);
321            }
322            BLE_GAP_EVENT_NOTIFY_RX => {
323                let notify_rx = unsafe { &event.__bindgen_anon_1.notify_rx };
324                if client.state.conn_handle != notify_rx.conn_handle {
325                    return 0;
326                }
327
328                if let Some(services) = &mut client.state.services {
329                    for service in services {
330                        if service.state.end_handle < notify_rx.attr_handle {
331                            continue;
332                        }
333
334                        if let Some(characteristics) = &mut service.state.characteristics {
335                            for characteristic in characteristics {
336                                if characteristic.state().handle == notify_rx.attr_handle {
337                                    unsafe {
338                                        characteristic.notify(notify_rx.om);
339                                    }
340                                    return 0;
341                                }
342                            }
343                        }
344                    }
345                }
346            }
347            BLE_GAP_EVENT_CONN_UPDATE_REQ | BLE_GAP_EVENT_L2CAP_UPDATE_REQ => {
348                let conn_update_req = unsafe { &event.__bindgen_anon_1.conn_update_req };
349                if client.state.conn_handle != conn_update_req.conn_handle {
350                    return 0;
351                }
352                unsafe {
353                    ::log::debug!("Peer requesting to update connection parameters");
354                    ::log::debug!(
355                        "MinInterval: {}, MaxInterval: {}, Latency: {}, Timeout: {}",
356                        (*conn_update_req.peer_params).itvl_min,
357                        (*conn_update_req.peer_params).itvl_max,
358                        (*conn_update_req.peer_params).latency,
359                        (*conn_update_req.peer_params).supervision_timeout
360                    );
361                }
362            }
363            BLE_GAP_EVENT_PASSKEY_ACTION => {
364                let passkey = unsafe { &event.__bindgen_anon_1.passkey };
365                if client.state.conn_handle != passkey.conn_handle {
366                    return 0;
367                }
368                let mut pkey = esp_idf_sys::ble_sm_io {
369                    action: passkey.params.action,
370                    ..Default::default()
371                };
372                match passkey.params.action as _ {
373                    esp_idf_sys::BLE_SM_IOACT_DISP => {
374                        pkey.__bindgen_anon_1.passkey = BLEDevice::take().security().get_passkey();
375                        let rc = unsafe {
376                            esp_idf_sys::ble_sm_inject_io(passkey.conn_handle, &mut pkey)
377                        };
378                        ::log::debug!("BLE_SM_IOACT_DISP; ble_sm_inject_io result: {rc}");
379                    }
380                    esp_idf_sys::BLE_SM_IOACT_NUMCMP => {
381                        if let Some(callback) = &client.state.on_confirm_pin {
382                            pkey.__bindgen_anon_1.numcmp_accept =
383                                callback(passkey.params.numcmp) as _;
384                        } else {
385                            ::log::warn!("on_passkey_request is not setted");
386                        }
387                        let rc = unsafe {
388                            esp_idf_sys::ble_sm_inject_io(passkey.conn_handle, &mut pkey)
389                        };
390                        ::log::debug!("BLE_SM_IOACT_NUMCMP; ble_sm_inject_io result: {rc}");
391                    }
392                    esp_idf_sys::BLE_SM_IOACT_INPUT => {
393                        if let Some(callback) = &client.state.on_passkey_request {
394                            pkey.__bindgen_anon_1.passkey = callback();
395                        } else {
396                            ::log::warn!("on_passkey_request is not setted");
397                        }
398                        let rc = unsafe {
399                            esp_idf_sys::ble_sm_inject_io(passkey.conn_handle, &mut pkey)
400                        };
401                        ::log::debug!("BLE_SM_IOACT_INPUT; ble_sm_inject_io result: {rc}");
402                    }
403                    esp_idf_sys::BLE_SM_IOACT_NONE => {
404                        ::log::debug!("BLE_SM_IOACT_NONE; No passkey action required");
405                    }
406                    action => {
407                        todo!("implementation required: {}", action);
408                    }
409                }
410            }
411            _ => {
412                ::log::warn!("unhandled event: {}", event.type_);
413            }
414        }
415        0
416    }
417
418    extern "C" fn service_discovered_cb(
419        conn_handle: u16,
420        error: *const esp_idf_sys::ble_gatt_error,
421        service: *const esp_idf_sys::ble_gatt_svc,
422        arg: *mut c_void,
423    ) -> i32 {
424        let client = unsafe { voidp_to_ref::<Self>(arg) };
425        if client.state.conn_handle != conn_handle {
426            return 0;
427        }
428
429        let error = unsafe { &*error };
430
431        if error.status == 0 {
432            let service = unsafe { &*service };
433            // Found a service - add it to the vector
434            let service = BLERemoteService::new(ArcUnsafeCell::downgrade(&client.state), service);
435            client.state.services.as_mut().unwrap().push(service);
436            return 0;
437        }
438
439        let ret = if error.status == (esp_idf_sys::BLE_HS_EDONE as _) {
440            0
441        } else {
442            error.status.into()
443        };
444
445        client.state.signal.signal(ret);
446        ret as _
447    }
448}
449
450impl Drop for BLEClient {
451    fn drop(&mut self) {
452        if self.connected() {
453            unsafe {
454                esp_idf_sys::ble_gap_set_event_cb(self.conn_handle(), None, ptr::null_mut());
455            }
456        }
457    }
458}