| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use anyhow::{format_err, Context as _, Error}; |
| use fidl; |
| use fidl::endpoints; |
| use fidl_fuchsia_bluetooth_gatt::{Characteristic, ReliableMode, RemoteServiceProxy, WriteOptions}; |
| use fidl_fuchsia_bluetooth_gatt::{ClientProxy, ServiceInfo}; |
| use fidl_fuchsia_bluetooth_le::RemoteDevice; |
| use fidl_fuchsia_bluetooth_le::{ |
| CentralEvent, CentralMarker, CentralProxy, ConnectionOptions, ScanFilter, |
| }; |
| use fuchsia_async as fasync; |
| use fuchsia_component as app; |
| use fuchsia_syslog::macros::*; |
| use futures::future::{Future, TryFutureExt}; |
| use futures::stream::TryStreamExt; |
| use futures::task::Waker; |
| use parking_lot::RwLock; |
| use slab::Slab; |
| use std::collections::HashMap; |
| use std::str::FromStr; |
| use std::sync::Arc; |
| |
| use fidl_fuchsia_bluetooth; |
| use fuchsia_bluetooth::types::Uuid; |
| |
| use crate::bluetooth::types::{BleScanResponse, SerializableReadByTypeResult}; |
| use crate::common_utils::common::macros::{fx_err_and_bail, with_line}; |
| use crate::common_utils::error::Sl4fError; |
| |
| #[derive(Debug)] |
| pub struct InnerGattClientFacade { |
| // FIDL proxy to the currently connected service, if any. |
| active_proxy: Option<RemoteServiceProxy>, |
| |
| // central: CentralProxy used for Bluetooth connections |
| central: Option<CentralProxy>, |
| |
| // devices: HashMap of key = device id and val = RemoteDevice structs discovered from a scan |
| devices: HashMap<String, RemoteDevice>, |
| |
| // Pending requests to obtain a host |
| host_requests: Slab<Waker>, |
| |
| // peripheral_ids: The identifier for the peripheral of a ConnectPeripheral FIDL call |
| // Key = peripheral id, value = ClientProxy |
| peripheral_ids: HashMap<String, ClientProxy>, |
| } |
| |
| /// Perform Gatt Client operations. |
| /// |
| /// Note this object is shared among all threads created by server. |
| /// |
| #[derive(Debug)] |
| pub struct GattClientFacade { |
| inner: Arc<RwLock<InnerGattClientFacade>>, |
| } |
| |
| impl GattClientFacade { |
| pub fn new() -> GattClientFacade { |
| GattClientFacade { |
| inner: Arc::new(RwLock::new(InnerGattClientFacade { |
| central: None, |
| devices: HashMap::new(), |
| host_requests: Slab::new(), |
| peripheral_ids: HashMap::new(), |
| active_proxy: None, |
| })), |
| } |
| } |
| |
| pub async fn start_scan(&self, mut filter: Option<ScanFilter>) -> Result<(), Error> { |
| self.cleanup_devices(); |
| // Set the central proxy if necessary and start a central_listener |
| GattClientFacade::set_central_proxy(self.inner.clone()); |
| |
| match &self.inner.read().central { |
| Some(c) => { |
| let status = c.start_scan(filter.as_mut()).await?; |
| match status.error { |
| Some(e) => { |
| return Err(format_err!("Failed to start scan: {}", Sl4fError::from(*e))) |
| } |
| None => Ok(()), |
| } |
| } |
| None => return Err(format_err!("No central proxy created.")), |
| } |
| } |
| |
| pub async fn gattc_connect_to_service( |
| &self, |
| periph_id: String, |
| service_id: u64, |
| ) -> Result<(), Error> { |
| let tag = "GattClientFacade::gattc_connect_to_service"; |
| let client_proxy = self.get_client_from_peripherals(periph_id); |
| let (proxy, server) = endpoints::create_proxy()?; |
| |
| // First close the connection to the currently active service. |
| if self.inner.read().active_proxy.is_some() { |
| self.inner.write().active_proxy = None; |
| } |
| match client_proxy { |
| Some(c) => { |
| c.connect_to_service(service_id, server)?; |
| self.inner.write().active_proxy = Some(proxy); |
| Ok(()) |
| } |
| None => { |
| fx_log_err!(tag: &with_line!(tag), "Unable to connect to service."); |
| return Err(format_err!("No peripheral proxy created.")); |
| } |
| } |
| } |
| |
| pub async fn gattc_discover_characteristics(&self) -> Result<Vec<Characteristic>, Error> { |
| let tag = "GattClientFacade::gattc_discover_characteristics"; |
| let discover_characteristics = match &self.inner.read().active_proxy { |
| Some(proxy) => proxy.discover_characteristics(), |
| None => fx_err_and_bail!(&with_line!(tag), "Central proxy not available."), |
| }; |
| |
| let (status, chrcs) = |
| discover_characteristics.await.map_err(|_| Sl4fError::new("Failed to send message"))?; |
| if let Some(e) = status.error { |
| let err_msg = format!("Failed to read characteristics: {}", Sl4fError::from(*e)); |
| fx_err_and_bail!(&with_line!(tag), err_msg) |
| } |
| Ok(chrcs) |
| } |
| |
| pub async fn gattc_write_char_by_id(&self, id: u64, write_value: Vec<u8>) -> Result<(), Error> { |
| let tag = "GattClientFacade::gattc_write_char_by_id"; |
| |
| let write_characteristic = match &self.inner.read().active_proxy { |
| Some(proxy) => proxy.write_characteristic(id, &write_value), |
| None => fx_err_and_bail!(&with_line!(tag), "Central proxy not available."), |
| }; |
| |
| let status = |
| write_characteristic.await.map_err(|_| Sl4fError::new("Failed to send message"))?; |
| |
| match status.error { |
| Some(e) => { |
| let err_msg = format!("Failed to write to characteristic: {}", Sl4fError::from(*e)); |
| fx_err_and_bail!(&with_line!(tag), err_msg) |
| } |
| None => Ok(()), |
| } |
| } |
| |
| pub async fn gattc_write_long_char_by_id( |
| &self, |
| id: u64, |
| offset: u16, |
| write_value: Vec<u8>, |
| reliable_mode: bool, |
| ) -> Result<(), Error> { |
| let tag = "GattClientFacade::gattc_write_long_char_by_id"; |
| |
| let reliable_mode = match reliable_mode { |
| true => ReliableMode::Enabled, |
| false => ReliableMode::Disabled, |
| }; |
| |
| let write_long_characteristic = match &self.inner.read().active_proxy { |
| Some(proxy) => proxy.write_long_characteristic( |
| id, |
| offset, |
| &write_value, |
| WriteOptions { reliable_mode: Some(reliable_mode), ..WriteOptions::EMPTY }, |
| ), |
| None => fx_err_and_bail!(&with_line!(tag), "Central proxy not available."), |
| }; |
| |
| let status = write_long_characteristic |
| .await |
| .map_err(|_| Sl4fError::new("Failed to send message"))?; |
| |
| match status.error { |
| Some(e) => { |
| let err_msg = |
| format!("Failed to write long characteristic: {}", Sl4fError::from(*e)); |
| fx_err_and_bail!(&with_line!(tag), err_msg) |
| } |
| None => Ok(()), |
| } |
| } |
| |
| pub async fn gattc_write_char_by_id_without_response( |
| &self, |
| id: u64, |
| write_value: Vec<u8>, |
| ) -> Result<(), Error> { |
| let tag = "GattClientFacade::gattc_write_char_by_id_without_response"; |
| |
| match &self.inner.read().active_proxy { |
| Some(proxy) => proxy |
| .write_characteristic_without_response(id, &write_value) |
| .map_err(|_| Sl4fError::new("Failed to send message").into()), |
| None => fx_err_and_bail!(&with_line!(tag), "Central proxy not available."), |
| } |
| } |
| |
| pub async fn gattc_read_char_by_id(&self, id: u64) -> Result<Vec<u8>, Error> { |
| let tag = "GattClientFacade::gattc_read_char_by_id"; |
| |
| let read_characteristic = match &self.inner.read().active_proxy { |
| Some(proxy) => proxy.read_characteristic(id), |
| None => fx_err_and_bail!(&with_line!(tag), "Central proxy not available."), |
| }; |
| |
| let (status, value) = |
| read_characteristic.await.map_err(|_| Sl4fError::new("Failed to send message"))?; |
| |
| match status.error { |
| Some(e) => { |
| let err_msg = format!("Failed to read characteristic: {}", Sl4fError::from(*e)); |
| fx_err_and_bail!(&with_line!(tag), err_msg) |
| } |
| None => Ok(value), |
| } |
| } |
| |
| pub async fn gattc_read_char_by_type( |
| &self, |
| raw_uuid: String, |
| ) -> Result<Vec<SerializableReadByTypeResult>, Error> { |
| let tag = "GattClientFacade::gattc_read_char_by_type"; |
| |
| let uuid = match Uuid::from_str(&raw_uuid) { |
| Ok(uuid) => uuid, |
| Err(e) => { |
| fx_err_and_bail!( |
| &with_line!(tag), |
| format_err!("Unable to convert to Uuid: {:?}", e) |
| ); |
| } |
| }; |
| |
| let mut fidl_uuid = fidl_fuchsia_bluetooth::Uuid::from(uuid); |
| |
| match &self.inner.read().active_proxy { |
| Some(proxy) => match proxy.read_by_type(&mut fidl_uuid).await { |
| Ok(value) => { |
| let responses = value.unwrap(); |
| let mut read_by_type_response_list = Vec::new(); |
| for response in responses { |
| read_by_type_response_list |
| .push(SerializableReadByTypeResult::new(&response)); |
| } |
| Ok(read_by_type_response_list) |
| } |
| Err(e) => { |
| let err_msg = format!("Failed to read characteristic by type: {}", e); |
| fx_err_and_bail!(&with_line!(tag), err_msg) |
| } |
| }, |
| None => fx_err_and_bail!(&with_line!(tag), "Central proxy not available."), |
| } |
| } |
| |
| pub async fn gattc_read_long_char_by_id( |
| &self, |
| id: u64, |
| offset: u16, |
| max_bytes: u16, |
| ) -> Result<Vec<u8>, Error> { |
| let tag = "GattClientFacade::gattc_read_long_char_by_id"; |
| |
| let read_long_characteristic = match &self.inner.read().active_proxy { |
| Some(proxy) => proxy.read_long_characteristic(id, offset, max_bytes), |
| None => fx_err_and_bail!(&with_line!(tag), "Central proxy not available."), |
| }; |
| |
| let (status, value) = |
| read_long_characteristic.await.map_err(|_| Sl4fError::new("Failed to send message"))?; |
| |
| match status.error { |
| Some(e) => { |
| let err_msg = |
| format!("Failed to read long characteristic: {}", Sl4fError::from(*e)); |
| fx_err_and_bail!(&with_line!(tag), err_msg) |
| } |
| None => Ok(value), |
| } |
| } |
| |
| pub async fn gattc_read_desc_by_id(&self, id: u64) -> Result<Vec<u8>, Error> { |
| let tag = "GattClientFacade::gattc_read_desc_by_id"; |
| let read_descriptor = match &self.inner.read().active_proxy { |
| Some(proxy) => proxy.read_descriptor(id), |
| None => fx_err_and_bail!(&with_line!(tag), "Central proxy not available."), |
| }; |
| |
| let (status, value) = |
| read_descriptor.await.map_err(|_| Sl4fError::new("Failed to send message"))?; |
| |
| match status.error { |
| Some(e) => { |
| let err_msg = format!("Failed to read descriptor: {}", Sl4fError::from(*e)); |
| fx_err_and_bail!(&with_line!(tag), err_msg) |
| } |
| None => Ok(value), |
| } |
| } |
| |
| pub async fn gattc_read_long_desc_by_id( |
| &self, |
| id: u64, |
| offset: u16, |
| max_bytes: u16, |
| ) -> Result<Vec<u8>, Error> { |
| let tag = "GattClientFacade::gattc_read_long_desc_by_id"; |
| let read_long_descriptor = match &self.inner.read().active_proxy { |
| Some(proxy) => proxy.read_long_descriptor(id, offset, max_bytes), |
| None => fx_err_and_bail!(&with_line!(tag), "Central proxy not available."), |
| }; |
| |
| let (status, value) = |
| read_long_descriptor.await.map_err(|_| Sl4fError::new("Failed to send message"))?; |
| |
| match status.error { |
| Some(e) => { |
| let err_msg = format!("Failed to read long descriptor: {}", Sl4fError::from(*e)); |
| fx_err_and_bail!(&with_line!(tag), err_msg) |
| } |
| None => Ok(value), |
| } |
| } |
| |
| pub async fn gattc_write_desc_by_id(&self, id: u64, write_value: Vec<u8>) -> Result<(), Error> { |
| let tag = "GattClientFacade::gattc_write_desc_by_id"; |
| |
| let write_descriptor = match &self.inner.read().active_proxy { |
| Some(proxy) => proxy.write_descriptor(id, &write_value), |
| None => fx_err_and_bail!(&with_line!(tag), "Central proxy not available."), |
| }; |
| |
| let status = |
| write_descriptor.await.map_err(|_| Sl4fError::new("Failed to send message"))?; |
| |
| match status.error { |
| Some(e) => { |
| let err_msg = format!("Failed to write to descriptor: {}", Sl4fError::from(*e)); |
| fx_err_and_bail!(&with_line!(tag), err_msg) |
| } |
| None => Ok(()), |
| } |
| } |
| |
| pub async fn gattc_write_long_desc_by_id( |
| &self, |
| id: u64, |
| offset: u16, |
| write_value: Vec<u8>, |
| ) -> Result<(), Error> { |
| let tag = "GattClientFacade::gattc_write_long_desc_by_id"; |
| |
| let write_long_descriptor = match &self.inner.read().active_proxy { |
| Some(proxy) => proxy.write_long_descriptor(id, offset, &write_value), |
| None => fx_err_and_bail!(&with_line!(tag), "Central proxy not available."), |
| }; |
| |
| let status = |
| write_long_descriptor.await.map_err(|_| Sl4fError::new("Failed to send message"))?; |
| |
| match status.error { |
| Some(e) => { |
| let err_msg = format!("Failed to write long descriptor: {}", Sl4fError::from(*e)); |
| fx_err_and_bail!(&with_line!(tag), err_msg) |
| } |
| None => Ok(()), |
| } |
| } |
| |
| pub async fn gattc_toggle_notify_characteristic( |
| &self, |
| id: u64, |
| value: bool, |
| ) -> Result<(), Error> { |
| let tag = "GattClientFacade::gattc_toggle_notify_characteristic"; |
| |
| let notify_characteristic = match &self.inner.read().active_proxy { |
| Some(proxy) => proxy.notify_characteristic(id, value), |
| None => fx_err_and_bail!(&with_line!(tag), "Central proxy not available."), |
| }; |
| |
| let status = |
| notify_characteristic.await.map_err(|_| Sl4fError::new("Failed to send message"))?; |
| |
| match status.error { |
| Some(e) => { |
| let err_msg = format!("Failed to enable notifications: {}", Sl4fError::from(*e)); |
| fx_err_and_bail!(&with_line!(tag), err_msg) |
| } |
| None => {} |
| }; |
| Ok(()) |
| } |
| |
| pub async fn list_services(&self, id: String) -> Result<Vec<ServiceInfo>, Error> { |
| let tag = "GattClientFacade::list_services"; |
| let client_proxy = self.get_client_from_peripherals(id); |
| |
| match client_proxy { |
| Some(c) => { |
| let (status, services) = c.list_services(None).await?; |
| match status.error { |
| None => { |
| fx_log_info!(tag: &with_line!(tag), "Found services: {:?}", services); |
| Ok(services) |
| } |
| Some(e) => { |
| let err_msg = format!( |
| "Error found while listing services: {:?}", |
| Sl4fError::from(*e) |
| ); |
| fx_err_and_bail!(&with_line!(tag), err_msg) |
| } |
| } |
| } |
| None => bail!("No client exists with provided device id"), |
| } |
| } |
| |
| // Given a device id, return its ClientProxy, if existing, otherwise None |
| pub fn get_client_from_peripherals(&self, id: String) -> Option<ClientProxy> { |
| self.inner.read().peripheral_ids.get(&id).map(|c| c.clone()) |
| } |
| |
| // Given a device id, insert it into the map. If it exists, don't overwrite |
| // TODO(aniramakri): Is this right behavior? If the device id already exists, don't |
| // overwrite ClientProxy? |
| pub fn update_peripheral_id(&self, id: &String, client: ClientProxy) { |
| let tag = "GattClientFacade::update_peripheral_id"; |
| if self.inner.read().peripheral_ids.contains_key(id) { |
| fx_log_warn!(tag: &with_line!(tag), "Overwriting existing id: {}", id); |
| self.inner.write().peripheral_ids.insert(id.clone(), client); |
| } else { |
| fx_log_info!(tag: &with_line!(tag), "Added {:?} to peripheral ids", id); |
| self.inner.write().peripheral_ids.insert(id.clone(), client); |
| } |
| |
| fx_log_info!( |
| tag: &with_line!(tag), |
| "Peripheral ids: {:?}", |
| self.inner.read().peripheral_ids |
| ); |
| } |
| |
| pub fn remove_peripheral_id(&self, id: &String) { |
| let tag = "GattClientFacade::remove_peripheral_id"; |
| self.inner.write().peripheral_ids.remove(id); |
| fx_log_info!( |
| tag: &with_line!(tag), |
| "After removing peripheral id: {:?}", |
| self.inner.read().peripheral_ids |
| ); |
| } |
| |
| // Update the central proxy if none exists, otherwise raise error |
| // If no proxy exists, set up central server to listen for events. This central listener will |
| // wake up any wakers who may be interested in RemoteDevices discovered |
| pub fn set_central_proxy(inner: Arc<RwLock<InnerGattClientFacade>>) { |
| let tag = "GattClientFacade::set_central_proxy"; |
| let mut central_modified = false; |
| let new_central = match inner.read().central.clone() { |
| Some(c) => { |
| fx_log_warn!(tag: &with_line!(tag), "Current central: {:?}.", c); |
| central_modified = true; |
| Some(c) |
| } |
| None => { |
| let central_svc: CentralProxy = app::client::connect_to_service::<CentralMarker>() |
| .context("Failed to connect to BLE Central service.") |
| .unwrap(); |
| Some(central_svc) |
| } |
| }; |
| |
| // Update the central with the (potentially) newly created proxy |
| inner.write().central = new_central; |
| // Only spawn if a central hadn't been created |
| if !central_modified { |
| fasync::Task::spawn(GattClientFacade::listen_central_events(inner.clone())).detach() |
| } |
| } |
| |
| // Update the devices dictionary with a discovered RemoteDevice |
| pub fn update_devices( |
| inner: &Arc<RwLock<InnerGattClientFacade>>, |
| id: String, |
| device: RemoteDevice, |
| ) { |
| let tag = "GattClientFacade::update_devices"; |
| if inner.read().devices.contains_key(&id) { |
| fx_log_warn!(tag: &with_line!(tag), "Already discovered: {:?}", id); |
| } else { |
| inner.write().devices.insert(id, device); |
| } |
| } |
| |
| pub fn listen_central_events( |
| inner: Arc<RwLock<InnerGattClientFacade>>, |
| ) -> impl Future<Output = ()> { |
| let tag = "GattClientFacade::listen_central_events"; |
| let evt_stream = match inner.read().central.clone() { |
| Some(c) => c.take_event_stream(), |
| None => panic!("No central created!"), |
| }; |
| |
| evt_stream |
| .map_ok(move |evt| { |
| match evt { |
| CentralEvent::OnScanStateChanged { scanning } => { |
| fx_log_info!(tag: &with_line!(tag), "Scan state changed: {:?}", scanning); |
| } |
| CentralEvent::OnDeviceDiscovered { device } => { |
| let id = device.identifier.clone(); |
| let name = device.advertising_data.as_ref().map(|adv| &adv.name); |
| // Update the device discovered list |
| fx_log_info!( |
| tag: &with_line!(tag), |
| "Device discovered: id: {:?}, name: {:?}", |
| id, |
| name |
| ); |
| GattClientFacade::update_devices(&inner, id, device); |
| |
| // In the event that we need to short-circuit the stream, wake up all |
| // wakers in the host_requests Slab |
| for waker in &inner.read().host_requests { |
| waker.1.wake_by_ref(); |
| } |
| } |
| CentralEvent::OnPeripheralDisconnected { identifier } => { |
| fx_log_info!(tag: &with_line!(tag), "Peer disconnected: {:?}", identifier); |
| } |
| } |
| }) |
| .try_collect::<()>() |
| .unwrap_or_else(|e| { |
| fx_log_err!( |
| tag: &with_line!("GattClientFacade::listen_central_events"), |
| "Failed to subscribe to BLE Central events: {:?}", |
| e |
| ) |
| }) |
| } |
| |
| pub async fn connect_peripheral(&self, id: String) -> Result<(), Error> { |
| let tag = "GattClientFacade::connect_peripheral"; |
| // Set the central proxy if necessary |
| GattClientFacade::set_central_proxy(self.inner.clone()); |
| |
| // TODO(fxbug.dev/875): Move to private method? |
| // Create server endpoints |
| let (proxy, server_end) = match fidl::endpoints::create_proxy() { |
| Err(e) => { |
| let err_msg = format!("Failed to create proxy endpoint: {:?}", e); |
| fx_err_and_bail!(&with_line!(tag), err_msg) |
| } |
| Ok(x) => x, |
| }; |
| let mut identifier = id.clone(); |
| self.update_peripheral_id(&identifier, proxy); |
| match &self.inner.read().central { |
| Some(c) => { |
| let conn_opts = ConnectionOptions { |
| bondable_mode: Some(true), |
| service_filter: None, |
| ..ConnectionOptions::EMPTY |
| }; |
| let status = c.connect_peripheral(&mut identifier, conn_opts, server_end).await?; |
| match status.error { |
| Some(e) => { |
| let err_msg = |
| format!("Failed to connect to peripheral: {:?}", Sl4fError::from(*e)); |
| fx_err_and_bail!(&with_line!(tag), err_msg) |
| } |
| None => {} |
| } |
| } |
| None => fx_err_and_bail!(&with_line!(tag), "No central proxy created."), |
| }; |
| Ok(()) |
| } |
| |
| pub async fn disconnect_peripheral(&self, id: String) -> Result<(), Error> { |
| let tag = "GattClientFacade::disconnect_peripheral"; |
| match &self.inner.read().central { |
| Some(c) => { |
| let status = c.disconnect_peripheral(&id).await?; |
| match status.error { |
| None => {} |
| Some(e) => { |
| fx_log_err!(tag: &with_line!(tag), "Failed to disconnect: {:?}", e); |
| bail!("Failed to disconnect: {:?}", e) |
| } |
| } |
| } |
| None => fx_err_and_bail!(&with_line!(tag), "Failed to disconnect from perpheral."), |
| }; |
| // Remove current id from map of peripheral_ids |
| self.remove_peripheral_id(&id); |
| Ok(()) |
| } |
| |
| // Return the central proxy |
| pub fn get_central_proxy(&self) -> Option<CentralProxy> { |
| self.inner.read().central.clone() |
| } |
| |
| pub fn get_periph_ids(&self) -> HashMap<String, ClientProxy> { |
| self.inner.read().peripheral_ids.clone() |
| } |
| |
| // Given the devices accrued from scan, returns list of (id, name) devices |
| // TODO(fxbug.dev/869): Return list of RemoteDevices (unsupported right now |
| // because Clone() not implemented for RemoteDevice) |
| pub fn get_devices(&self) -> Vec<BleScanResponse> { |
| const EMPTY_DEVICE: &str = ""; |
| let mut devices = Vec::new(); |
| for val in self.inner.read().devices.keys() { |
| let name = match &self.inner.read().devices[val].advertising_data { |
| Some(adv) => adv.name.clone().unwrap_or(EMPTY_DEVICE.to_string()), |
| None => EMPTY_DEVICE.to_string(), |
| }; |
| let connectable = self.inner.read().devices[val].connectable; |
| devices.push(BleScanResponse::new(val.clone(), name, connectable)); |
| } |
| |
| devices |
| } |
| |
| pub fn cleanup_central_proxy(&self) { |
| self.inner.write().central = None |
| } |
| |
| pub fn cleanup_devices(&self) { |
| self.inner.write().devices.clear() |
| } |
| |
| pub fn cleanup_central(&self) { |
| self.cleanup_central_proxy(); |
| self.cleanup_devices(); |
| } |
| |
| pub fn cleanup_peripheral_ids(&self) { |
| self.inner.write().peripheral_ids.clear() |
| } |
| |
| pub fn print(&self) { |
| let tag = "GattClientFacade::print"; |
| fx_log_info!( |
| tag: &with_line!(tag), |
| "BluetoothFacade: Central: {:?}, Devices: {:?}, Periph_ids: {:?}", |
| self.get_central_proxy(), |
| self.get_devices(), |
| self.get_periph_ids(), |
| ); |
| } |
| |
| // Close both central proxies |
| pub fn cleanup(&self) { |
| self.cleanup_central(); |
| self.cleanup_peripheral_ids(); |
| } |
| } |