Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
4 changes: 4 additions & 0 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<Self::Peripheral>>;
Expand Down
4 changes: 4 additions & 0 deletions src/common/adapter_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ where
.collect()
}

pub fn clear_peripherals(&self) {
self.peripherals.clear();
}

// Only used on windows and macOS/iOS, so turn off deadcode so we don't get warnings on android/linux.
#[allow(dead_code)]
pub fn peripheral_mut(
Expand Down
12 changes: 12 additions & 0 deletions src/corebluetooth/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ impl Adapter {
sender: adapter_sender,
})
}

pub fn clear_cache(&self) {
self.manager.clear_peripherals();
}
}

#[async_trait]
Expand All @@ -118,6 +122,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<Vec<Peripheral>> {
Ok(self.manager.peripherals())
}
Expand Down
51 changes: 50 additions & 1 deletion src/corebluetooth/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -399,6 +399,9 @@ pub enum CoreBluetoothMessage {
filter: ScanFilter,
},
StopScanning,
RetrieveConnectedPeripherals {
filter: ScanFilter,
},
ConnectDevice {
peripheral_uuid: Uuid,
future: CoreBluetoothReplyStateShared,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<String> = 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<CBUUID *> *` to use for discovery. If the
Expand Down
152 changes: 152 additions & 0 deletions src/winrtble/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -68,6 +72,10 @@ impl Adapter {
radio,
})
}

pub fn clear_cache(&self) {
self.manager.clear_peripherals();
}
}

impl Debug for Adapter {
Expand Down Expand Up @@ -114,6 +122,150 @@ impl Central for Adapter {
Ok(())
}

async fn connected_peripherals(&self, filter: ScanFilter) -> Result<()> {
let base_selector = BluetoothLEDevice::GetDeviceSelector()
.map_err(|e| Error::Other(format!("GetDeviceSelector failed: {:?}", e).into()))?;
let aqs = format!(
"{} AND System.Devices.Aep.IsConnected:=System.StructuredQueryType.Boolean#True",
base_selector.to_string()
);

// Query all BLE devices that are currently connected to the system
let devices = DeviceInformation::FindAllAsyncAqsFilter(&windows::core::HSTRING::from(aqs))
.map_err(|e| Error::Other(format!("FindAllAsyncAqsFilter failed: {:?}", e).into()))?
.get()
.map_err(|e| Error::Other(format!("FindAllAsync().get() failed: {:?}", e).into()))?;

let manager = self.manager.clone();
let required_services: Vec<Uuid> = filter.services.clone();

trace!(
"Scanning for connected peripherals with {} service filters",
required_services.len()
);

// Iterate through each connected device
for device in devices {
let device_id = match device.Id() {
Ok(id) => id,
Err(e) => {
trace!("Failed to get device ID: {:?}", e);
continue;
}
};
trace!("Checking connected device: {:?}", device_id);

// BluetoothLEDevice from the device ID
let ble_device = match BluetoothLEDevice::FromIdAsync(&device_id) {
Ok(async_op) => match async_op.get() {
Ok(dev) => dev,
Err(e) => {
trace!("FromIdAsync.get() failed for {:?}: {:?}", device_id, e);
continue;
}
},
Err(e) => {
trace!("FromIdAsync failed for {:?}: {:?}", device_id, e);
continue;
}
};

// Double-check the connection status
match ble_device.ConnectionStatus() {
Ok(status)
if status
== windows::Devices::Bluetooth::BluetoothConnectionStatus::Connected => {}
Ok(_) => {
trace!("Device {:?} not connected, skipping", device_id);
continue;
}
Err(e) => {
trace!("Failed to get connection status: {:?}", e);
continue;
}
}

// Service filtering logic:
// - If no services specified in filter, accept all connected devices
// - Otherwise, accept only if the device has at least one matching service
let mut accept_device = required_services.is_empty();

if !accept_device {
// Query the device's GATT services to check for matches
let services_result = match ble_device.GetGattServicesAsync() {
Ok(async_op) => async_op.get(),
Err(e) => {
trace!("GetGattServicesAsync failed: {:?}", e);
continue;
}
};

let services = match services_result {
Ok(gatt_services) => match gatt_services.Services() {
Ok(service_list) => service_list,
Err(e) => {
trace!("Failed to get Services list: {:?}", e);
continue;
}
},
Err(e) => {
trace!("GetGattServicesAsync.get() failed: {:?}", e);
continue;
}
};

// Check if any of the device's services match the filter
for service in &services {
if let Ok(guid) = service.Uuid() {
let service_uuid = Uuid::from_u128(guid.to_u128());
if required_services.contains(&service_uuid) {
trace!("Found matching service: {:?}", service_uuid);
accept_device = true;
break;
}
}
}
}

if !accept_device {
trace!("Device does not match service filter, skipping");
continue;
}

// Convert Bluetooth address to BDAddr
let address: BDAddr = match ble_device.BluetoothAddress() {
Ok(addr) => match (addr as u64).try_into() {
Ok(bd_addr) => bd_addr,
Err(_) => {
trace!("Failed to convert Bluetooth address: {}", addr);
continue;
}
},
Err(e) => {
trace!("BluetoothAddress() failed: {:?}", e);
continue;
}
};

// Update the peripheral in the manager
match manager.peripheral_mut(&address.into()) {
Some(_) => {
trace!("Peripheral already exists in manager: {:?}", address);
manager.emit(CentralEvent::DeviceDiscovered(address.into()));
}
None => {
trace!("Adding new peripheral: {:?}", address);
let peripheral = Peripheral::new(Arc::downgrade(&manager), address);
manager.add_peripheral(peripheral);
manager.emit(CentralEvent::DeviceDiscovered(address.into()));
}
}
}

trace!("Finished scanning for connected peripherals");
Ok(())
}

async fn peripherals(&self) -> Result<Vec<Peripheral>> {
Ok(self.manager.peripherals())
}
Expand Down
Loading