diff --git a/Cargo.toml b/Cargo.toml index ed77ba06..4ec4a882 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,7 +76,7 @@ objc2-core-bluetooth = { version = "0.2.2", default-features = false, features = ] } [target.'cfg(target_os = "windows")'.dependencies] -windows = { version = "0.61", features = ["Devices_Bluetooth", "Devices_Bluetooth_GenericAttributeProfile", "Devices_Bluetooth_Advertisement", "Devices_Radios", "Foundation_Collections", "Foundation", "Storage_Streams"] } +windows = { version = "0.61", features = ["Devices_Bluetooth", "Devices_Bluetooth_GenericAttributeProfile", "Devices_Bluetooth_Advertisement", "Devices_Enumeration", "Devices_Radios", "Foundation_Collections", "Foundation", "Storage_Streams"] } windows-future = "0.2.0" [dev-dependencies] diff --git a/src/api/mod.rs b/src/api/mod.rs index bb3acf68..c364320d 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -359,6 +359,10 @@ pub trait Central: Send + Sync + Clone { /// Stops scanning for BLE devices. async fn stop_scan(&self) -> Result<()>; + /// Retrieve connected peripherals matching the given filter. Same filter and discovery rules + /// apply as for start_scan. + async fn connected_peripherals(&self, filter: ScanFilter) -> Result<()>; + /// Returns the list of [`Peripheral`]s that have been discovered so far. Note that this list /// may contain peripherals that are no longer available. async fn peripherals(&self) -> Result>; diff --git a/src/corebluetooth/adapter.rs b/src/corebluetooth/adapter.rs index b8626bd1..ab10a0ed 100644 --- a/src/corebluetooth/adapter.rs +++ b/src/corebluetooth/adapter.rs @@ -118,6 +118,14 @@ impl Central for Adapter { Ok(()) } + async fn connected_peripherals(&self, filter: ScanFilter) -> Result<()> { + self.sender + .to_owned() + .send(CoreBluetoothMessage::RetrieveConnectedPeripherals { filter }) + .await?; + Ok(()) + } + async fn peripherals(&self) -> Result> { Ok(self.manager.peripherals()) } diff --git a/src/corebluetooth/internal.rs b/src/corebluetooth/internal.rs index 93597bc7..9ac28439 100644 --- a/src/corebluetooth/internal.rs +++ b/src/corebluetooth/internal.rs @@ -14,7 +14,7 @@ use super::{ future::{BtlePlugFuture, BtlePlugFutureStateShared}, utils::{ core_bluetooth::{cbuuid_to_uuid, uuid_to_cbuuid}, - nsuuid_to_uuid, + nsstring_to_string, nsuuid_to_uuid, }, }; use crate::api::{CharPropFlags, Characteristic, Descriptor, ScanFilter, Service, WriteType}; @@ -399,6 +399,9 @@ pub enum CoreBluetoothMessage { filter: ScanFilter, }, StopScanning, + RetrieveConnectedPeripherals { + filter: ScanFilter, + }, ConnectDevice { peripheral_uuid: Uuid, future: CoreBluetoothReplyStateShared, @@ -1169,6 +1172,9 @@ impl CoreBluetoothInternal { }, CoreBluetoothMessage::StartScanning{filter} => self.start_discovery(filter), CoreBluetoothMessage::StopScanning => self.stop_discovery(), + CoreBluetoothMessage::RetrieveConnectedPeripherals{filter} => { + self.retrieve_connected_peripherals(filter); + }, CoreBluetoothMessage::ConnectDevice{peripheral_uuid, future} => { trace!("got connectdevice msg!"); self.connect_peripheral(peripheral_uuid, future); @@ -1239,6 +1245,49 @@ impl CoreBluetoothInternal { trace!("BluetoothAdapter::stop_discovery"); unsafe { self.manager.stopScan() }; } + + fn retrieve_connected_peripherals(&mut self, filter: ScanFilter) { + trace!("BluetoothAdapter::retrieve_connected_peripherals"); + let service_uuids = scan_filter_to_service_uuids(filter); + if service_uuids.is_none() { + warn!("MacOS requires a filter of services to be provided, so we cannot continue."); + return; + } + let peripherals = unsafe { + self.manager + .retrieveConnectedPeripheralsWithServices(service_uuids.as_deref().unwrap()) + }; + + for peripheral in peripherals { + let uuid = nsuuid_to_uuid(unsafe { &peripheral.identifier() }); + trace!("Discovered connected peripheral: {}", uuid); + let (event_sender, event_receiver) = mpsc::channel(256); + let name: Option = unsafe { + match peripheral.name() { + Some(ns_name) => nsstring_to_string(ns_name.as_ref()), + None => None, + } + }; + if !self.peripherals.contains_key(&uuid) { + self.peripherals.insert( + uuid, + PeripheralInternal::new(Retained::from(peripheral), event_sender), + ); + } + let discovered_device = CoreBluetoothEvent::DeviceDiscovered { + uuid, + name, + event_receiver, + }; + // Must use a synchronous sender. + match self.event_sender.try_send(discovered_device) { + Ok(_) => (), + Err(e) => { + error!("Error sending discovered device event: {}", e); + } + } + } + } } /// Convert a `ScanFilter` to the appropriate `NSArray *` to use for discovery. If the diff --git a/src/winrtble/adapter.rs b/src/winrtble/adapter.rs index d840c403..4f77a634 100644 --- a/src/winrtble/adapter.rs +++ b/src/winrtble/adapter.rs @@ -19,11 +19,15 @@ use crate::{ }; use async_trait::async_trait; use futures::stream::Stream; +use log::trace; use std::convert::TryInto; use std::fmt::{self, Debug, Formatter}; use std::pin::Pin; use std::sync::{Arc, Mutex}; +use uuid::Uuid; use windows::{ + Devices::Bluetooth::BluetoothLEDevice, + Devices::Enumeration::DeviceInformation, Devices::Radios::{Radio, RadioState}, Foundation::TypedEventHandler, }; @@ -114,6 +118,94 @@ impl Central for Adapter { Ok(()) } + async fn connected_peripherals(&self, filter: ScanFilter) -> Result<()> { + let device_selector = BluetoothLEDevice::GetDeviceSelector()?; + let get_devices = match DeviceInformation::FindAllAsyncAqsFilter(&device_selector) { + Ok(devices) => devices, + Err(e) => { + return Err(Error::Other(format!("{:?}", e).into())); + } + }; + let devices = match get_devices.get() { + Ok(devices) => devices, + Err(e) => { + return Err(Error::Other(format!("{:?}", e).into())); + } + }; + let manager = self.manager.clone(); + let mut required_services = filter.clone().services; + required_services.sort(); + for device in devices { + let Ok(device_id) = device.Id() else { + continue; + }; + let Ok(ble_device) = BluetoothLEDevice::FromIdAsync(&device_id) else { + continue; + }; + let Ok(ble_device) = ble_device.get() else { + continue; + }; + let Ok(services_result_op) = ble_device.GetGattServicesAsync() else { + continue; + }; + let Ok(services_result) = services_result_op.get() else { + continue; + }; + let Ok(services) = services_result.Services() else { + continue; + }; + // Verify all services in filter exist on the device. + let mut found_services: Vec = Vec::new(); + for service in services { + let Ok(s_uuid) = service.Uuid() else { + continue; + }; + let service_uuid = Uuid::from_u128(s_uuid.to_u128()); + if filter.services.contains(&service_uuid) { + found_services.push(service_uuid); + } + } + found_services.sort(); + trace!( + "Found required services: {:?} of {:?}", + found_services.len(), + required_services.len() + ); + if (required_services.len() != found_services.len()) + || !(required_services + .iter() + .zip(found_services.iter()) + .all(|(l, r)| l == r)) + { + trace!("Not all required services are accounted for, continuing..."); + continue; + } + + let Ok(bluetooth_address) = ble_device.BluetoothAddress() else { + continue; + }; + let address: BDAddr = match bluetooth_address.try_into() { + Ok(address) => address, + Err(_) => { + continue; + } + }; + match manager.peripheral_mut(&address.into()) { + Some(_) => { + trace!("Skipping over existing peripheral: {:?}", address); + manager.emit(CentralEvent::DeviceDiscovered(address.into())); + } + None => { + let peripheral = Peripheral::new(Arc::downgrade(&manager), address); + manager.add_peripheral(peripheral); + manager.emit(CentralEvent::DeviceDiscovered(address.into())); + } + } + return Ok(()); + } + Ok(()) + } + async fn peripherals(&self) -> Result> { Ok(self.manager.peripherals()) }