[fp-rs] Updated device scanning interface to facilitate adding more functionality to device constructor.

This commit is contained in:
Lucas Silva Shepard
2023-07-29 17:18:46 -07:00
parent fc07423925
commit 4be9872f6d
7 changed files with 79 additions and 76 deletions
@@ -25,11 +25,11 @@ pub trait Adapter: Sized {
/// Retrieve the system-default Bluetooth adapter.
async fn default() -> Result<Self, BluetoothError>;
/// Begin scanning for nearby devices.
fn start_scan_devices(&mut self) -> Result<(), BluetoothError>;
/// Begin scanning for nearby advertisements.
fn start_scan(&mut self) -> Result<(), BluetoothError>;
/// Stop scanning for nearby devices.
fn stop_scan_devices(&mut self) -> Result<(), BluetoothError>;
/// Stop scanning for nearby advertisements.
fn stop_scan(&mut self) -> Result<(), BluetoothError>;
/// Poll next discovered device.
async fn next_device(&mut self) -> Result<Self::Device, BluetoothError>;
@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::bluetooth::common::BluetoothError;
/// BLE Addresses can either be the peripheral's public MAC address, or various
/// types of random addresses.
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
@@ -68,15 +70,14 @@ impl From<u64> for ClassicAddress {
}
impl TryFrom<BleAddress> for ClassicAddress {
// TODO proper error handling b/291931475
type Error = anyhow::Error;
type Error = BluetoothError;
fn try_from(addr: BleAddress) -> Result<Self, Self::Error> {
match addr.kind {
BleAddressKind::Public => Ok(ClassicAddress(addr.val)),
BleAddressKind::Random => Err(anyhow::anyhow!(
"Can't convert BLE Random address to Bluetooth Classic address."
)),
BleAddressKind::Random => Err(BluetoothError::BadTypeConversion(String::from(
"can't convert BLE Random address to Bluetooth Classic address."
))),
}
}
}
@@ -18,6 +18,10 @@ use thiserror::Error;
#[non_exhaustive]
#[derive(Error, Debug)]
pub enum BluetoothError {
/// Reported when the user attempts a bad type conversion, e.g. converting
/// a BLE random address to a BT Classic address.
#[error("bad type conversion: {0}")]
BadTypeConversion(String),
/// Reported when Bluetooth device pairing fails.
#[error("pairing error: {0}")]
PairingFailed(String),
@@ -29,11 +29,11 @@ impl Adapter for BleAdapter {
panic!("Unsupported target platform.");
}
fn start_scan_devices(&mut self) -> Result<(), BluetoothError> {
fn start_scan(&mut self) -> Result<(), BluetoothError> {
panic!("Unsupported target platform.");
}
fn stop_scan_devices(&mut self) -> Result<(), BluetoothError> {
fn stop_scan(&mut self) -> Result<(), BluetoothError> {
panic!("Unsupported target platform.");
}
+53 -57
View File
@@ -12,12 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::pin::Pin;
use std::sync::Arc;
use async_trait::async_trait;
use futures::{stream::Stream, StreamExt};
use tracing::{error, warn};
use futures::{channel::mpsc::Receiver, StreamExt};
use tracing::{error, info, warn};
use windows::{
Devices::Bluetooth::{
Advertisement::{
@@ -55,16 +54,23 @@ use windows::{
};
use super::BleDevice;
use crate::bluetooth::common::{Adapter, BleAddress, BleAddressKind, BluetoothError};
use crate::bluetooth::common::{
Adapter, BleAddress, BleAddressKind, BluetoothError,
};
/// Struct holding the necessary fields for listening to and handling incoming
/// BLE advertisements.
struct AdvListener {
/// Holds callback for sending received advertisement events to `receiver`.
watcher: BluetoothLEAdvertisementWatcher,
/// Can be polled to consume incoming advertisement events.
receiver: Receiver<BluetoothLEAdvertisementReceivedEventArgs>,
}
/// Concrete type implementing `Adapter`, used for Windows BLE.
pub struct BleAdapter {
inner: BluetoothAdapter,
// NOTE: Using Boxed dyn here is silly because only one concrete type ever
// used. Change this to `impl Stream` once impl trait return types
// stabilized for existential types.
// b/289224233.
device_stream: Option<Pin<Box<dyn Stream<Item = BleDevice> + Send + Sync>>>,
listener: Option<AdvListener>,
}
#[async_trait]
@@ -87,11 +93,11 @@ impl Adapter for BleAdapter {
Ok(BleAdapter {
inner,
device_stream: None,
listener: None,
})
}
fn start_scan_devices(&mut self) -> Result<(), BluetoothError> {
fn start_scan(&mut self) -> Result<(), BluetoothError> {
let watcher = BluetoothLEAdvertisementWatcher::new()?;
match watcher.SetScanningMode(BluetoothLEScanningMode::Active) {
Ok(_) => (),
@@ -149,7 +155,7 @@ impl Adapter for BleAdapter {
>| {
// Drop `sender`, closing the channel.
let _sender = sender.take();
println!("Watcher stopped receiving BLE advertisements.");
info!("Watcher stopped receiving BLE advertisements.");
Ok(())
},
);
@@ -158,48 +164,14 @@ impl Adapter for BleAdapter {
watcher.Stopped(&stopped_handler)?;
watcher.Start()?;
// `receiver` is a `futures::channel::mpsc::Receiver`, which implements
// `futures::stream::Stream`. This is essentially an async Iterator.
// We apply a FilterMap to map from advertisement packet to a future
// returning `BleDevice` and filter out undesired connections. We need a
// pinned box to satisfy trait bounds for `Stream`.
self.device_stream =
Some(Box::pin(receiver.filter_map(move |event_args| {
// Move `watcher` into `FilterMap` closure. This ensures `watcher`
// is only dropped when the stream is closed.
let _watcher = &watcher;
// Move `event_args` into async block.
async move {
match event_args.AdvertisementType().ok()? {
BluetoothLEAdvertisementType::NonConnectableUndirected => {
None
}
_ => {
let kind = event_args.BluetoothAddressType().ok()?;
let addr = event_args.BluetoothAddress().ok()?;
let kind = BleAddressKind::try_from(kind).ok()?;
let addr = BleAddress::new(addr, kind);
match BleDevice::new(addr).await {
Ok(device) => Some(device),
Err(err) => {
warn!("Error creating device: {:?}", err);
None
}
}
}
}
}
})));
self.listener = Some(AdvListener { watcher, receiver });
Ok(())
}
fn stop_scan_devices(&mut self) -> Result<(), BluetoothError> {
if let Some(_) = &self.device_stream {
self.device_stream.take();
fn stop_scan(&mut self) -> Result<(), BluetoothError> {
if let Some(listener) = self.listener.take() {
listener.watcher.Stop()?;
Ok(())
} else {
Err(BluetoothError::FailedPrecondition(String::from(
@@ -209,13 +181,37 @@ impl Adapter for BleAdapter {
}
async fn next_device(&mut self) -> Result<Self::Device, BluetoothError> {
if let Some(stream) = &mut self.device_stream {
stream
.next()
.await
.ok_or(BluetoothError::Internal(String::from(
"device returned from Stream is None",
)))
if let Some(listener) = &mut self.listener {
let stream = &mut listener.receiver;
// We don't want the end-user to receive empty devices, so this is a
// loop to catch and skip trivial errors from advertisements that
// can't be turned into devices.
loop {
let event_args =
stream.next().await.ok_or(BluetoothError::Internal(
String::from("Event returned from stream is None."),
))?;
match event_args.AdvertisementType()? {
BluetoothLEAdvertisementType::NonConnectableUndirected => {
()
}
_ => {
let kind = event_args.BluetoothAddressType()?;
let addr = event_args.BluetoothAddress()?;
let kind = BleAddressKind::try_from(kind)?;
let addr = BleAddress::new(addr, kind);
match BleDevice::new(addr).await {
Ok(device) => break Ok(device),
Err(err) => {
warn!("Error creating device: {:?}", err);
}
}
}
}
}
} else {
Err(BluetoothError::FailedPrecondition(String::from(
"device scanning hasn't started, please call `start_scan()`",
@@ -16,21 +16,23 @@
//https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.bluetoothaddresstype?view=winrt-22621
use windows::Devices::Bluetooth::BluetoothAddressType;
use crate::bluetooth::common::BleAddressKind;
use crate::bluetooth::common::{BleAddressKind, BluetoothError};
// Convenience for converting from Windows API to crate API.
impl TryFrom<BluetoothAddressType> for BleAddressKind {
type Error = anyhow::Error;
type Error = BluetoothError;
fn try_from(kind: BluetoothAddressType) -> Result<Self, Self::Error> {
match kind {
BluetoothAddressType::Public => Ok(BleAddressKind::Public),
BluetoothAddressType::Random => Ok(BleAddressKind::Random),
BluetoothAddressType::Unspecified => Err(anyhow::anyhow!(
"Attempting to construct `BleAddressKind` with device \
advertising Unspecified address type."
)),
_ => Err(anyhow::anyhow!(format!(
BluetoothAddressType::Unspecified => {
Err(BluetoothError::BadTypeConversion(String::from(
"Attempting to construct `BleAddressKind` with device \
advertising Unspecified address type.",
)))
}
_ => Err(BluetoothError::BadTypeConversion(format!(
"Attempting to construct `BleAddressKind` with device \
advertising invalid address type {}.",
kind.0,
+1 -1
View File
@@ -23,7 +23,7 @@ use bluetooth::{Adapter, Address, ClassicAddress, Device};
fn main() -> Result<(), Box<dyn Error>> {
let run = async {
let mut adapter = bluetooth::default_adapter().await?;
adapter.start_scan_devices()?;
adapter.start_scan()?;
while let Ok(ble_device) = adapter.next_device().await {
let name = ble_device.name()?;