Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
8 changes: 8 additions & 0 deletions src/corebluetooth/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<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: 148 additions & 4 deletions src/winrtble/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ use crate::{
};
use async_trait::async_trait;
use futures::stream::Stream;
use log::{debug, trace, warn};
use std::convert::TryInto;
use std::fmt::{self, Debug, Formatter};
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use windows::{
Devices::Radios::{Radio, RadioState},
Foundation::TypedEventHandler,
};
use uuid::Uuid;
use windows::Devices::Bluetooth::BluetoothLEDevice;
use windows::Devices::Enumeration::DeviceInformation;

/// Implementation of [api::Central](crate::api::Central).
#[derive(Clone)]
Expand Down Expand Up @@ -114,6 +114,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();

debug!(
"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) => {
warn!("Failed to get device ID: {:?}", e);
continue;
}
};
debug!("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) => {
warn!("FromIdAsync.get() failed for {:?}: {:?}", device_id, e);
continue;
}
},
Err(e) => {
warn!("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) => {
warn!("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) => {
warn!("GetGattServicesAsync failed: {:?}", e);
continue;
}
};

let services = match services_result {
Ok(gatt_services) => match gatt_services.Services() {
Ok(service_list) => service_list,
Err(e) => {
warn!("Failed to get Services list: {:?}", e);
continue;
}
},
Err(e) => {
warn!("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) {
debug!("Found matching service: {:?}", service_uuid);
accept_device = true;
break;
}
}
}
}

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

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

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

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

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