mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-16 07:36:10 -04:00
Merge pull request #1957 from TheShepord:fpwinrs_pair
PiperOrigin-RevId: 552656377
This commit is contained in:
@@ -20,15 +20,18 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
futures = { version = "0.3", features = ["executor"] }
|
||||
tracing = "0.1.37"
|
||||
cfg-if = "1.0.0"
|
||||
async-trait = "0.1"
|
||||
thiserror = "1.0.43"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows = { version = "0.48", features = [
|
||||
"Devices_Bluetooth",
|
||||
"Devices_Enumeration",
|
||||
"Devices_Bluetooth_Advertisement",
|
||||
"Foundation",
|
||||
"Foundation_Collections",
|
||||
"Storage_Streams",
|
||||
] }
|
||||
|
||||
+14
-17
@@ -14,29 +14,26 @@
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::bluetooth::common::{
|
||||
BleAdvertisement, BleDataTypeId, BluetoothError,
|
||||
};
|
||||
|
||||
/// Concrete types implementing this trait are Bluetooth Central devices.
|
||||
/// They provide methods for retrieving nearby connections and device info.
|
||||
#[async_trait]
|
||||
pub trait Adapter: Sized {
|
||||
type Device: Device;
|
||||
|
||||
pub trait BleAdapter: Sized {
|
||||
/// Retrieve the system-default Bluetooth adapter.
|
||||
async fn default() -> Result<Self, anyhow::Error>;
|
||||
async fn default() -> Result<Self, BluetoothError>;
|
||||
|
||||
/// Begin scanning for nearby devices.
|
||||
fn start_scan_devices(&mut self) -> Result<(), anyhow::Error>;
|
||||
/// Begin scanning for nearby advertisements.
|
||||
fn start_scan(&mut self) -> Result<(), BluetoothError>;
|
||||
|
||||
/// Stop scanning for nearby devices.
|
||||
fn stop_scan_devices(&mut self) -> Result<(), anyhow::Error>;
|
||||
/// 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, anyhow::Error>;
|
||||
}
|
||||
|
||||
/// Concrete types implementing this trait represent Bluetooth Peripheral devices.
|
||||
/// They provide methods for retrieving device info and running device actions,
|
||||
/// such as pairing.
|
||||
pub trait Device {
|
||||
/// Retrieve the name advertised by this device.
|
||||
fn name(&self) -> Result<String, anyhow::Error>;
|
||||
async fn next_advertisement(
|
||||
&mut self,
|
||||
data_selector: Option<&Vec<BleDataTypeId>>,
|
||||
) -> Result<BleAdvertisement, BluetoothError>;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::bluetooth::common::{
|
||||
BleAddress, BluetoothError, ClassicAddress, PairingResult,
|
||||
};
|
||||
|
||||
/// Concrete types implementing this trait represent BLE Peripheral devices.
|
||||
/// They provide methods for retrieving device info and running device actions,
|
||||
/// such as pairing.
|
||||
#[async_trait]
|
||||
pub trait BleDevice: Sized {
|
||||
/// Create a new `BleDevice` instance from a `BleAddress`, typically
|
||||
/// enabled through locally cached data retrieved from a Bluetooth adapter's
|
||||
/// scanning functionality.
|
||||
async fn new(addr: BleAddress) -> Result<Self, BluetoothError>;
|
||||
|
||||
/// Retrieve the name advertised by this device.
|
||||
fn name(&self) -> Result<String, BluetoothError>;
|
||||
|
||||
/// Retrieve this device's Bluetooth address information.
|
||||
fn address(&self) -> BleAddress;
|
||||
}
|
||||
|
||||
/// Concrete types implementing this trait represent BT Classic Peripheral
|
||||
/// devices. They provide methods for retrieving device info and running device
|
||||
/// actions, such as pairing.
|
||||
#[async_trait]
|
||||
pub trait ClassicDevice: Sized {
|
||||
/// Create a new `ClassicDevice` instance from a `ClassicAddress`, typically
|
||||
/// enabled through locally cached data retrieved from a Bluetooth adapter's
|
||||
/// scanning functionality.
|
||||
async fn new(addr: ClassicAddress) -> Result<Self, BluetoothError>;
|
||||
|
||||
/// Retrieve the name advertised by this device.
|
||||
fn name(&self) -> Result<String, BluetoothError>;
|
||||
|
||||
/// Retrieve this device's Bluetooth address information.
|
||||
fn address(&self) -> ClassicAddress;
|
||||
|
||||
/// Attempt pairing with the peripheral device.
|
||||
async fn pair(&self) -> Result<PairingResult, BluetoothError>;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
mod adapter;
|
||||
mod device;
|
||||
|
||||
pub use adapter::*;
|
||||
pub use device::*;
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// 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, Hash)]
|
||||
pub enum BleAddressKind {
|
||||
Public,
|
||||
Random,
|
||||
}
|
||||
|
||||
/// Struct representing a 48-bit BLE Address and its type.
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)]
|
||||
pub struct BleAddress {
|
||||
val: [u8; 6],
|
||||
kind: BleAddressKind,
|
||||
}
|
||||
|
||||
/// Struct representing a 48-bit BT Classic address.
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)]
|
||||
pub struct ClassicAddress([u8; 6]);
|
||||
|
||||
impl BleAddress {
|
||||
/// `BleAddress` constructor.
|
||||
pub fn new(addr: u64, kind: BleAddressKind) -> Self {
|
||||
let addr = u64_to_6lsb(addr);
|
||||
|
||||
BleAddress { val: addr, kind }
|
||||
}
|
||||
|
||||
/// Retrieve the type of BLE Address (public or random).
|
||||
pub fn get_kind(&self) -> BleAddressKind {
|
||||
self.kind
|
||||
}
|
||||
}
|
||||
|
||||
/// Function for converting the six LSB of a u64 into a 6-byte array.
|
||||
#[inline]
|
||||
fn u64_to_6lsb(num: u64) -> [u8; 6] {
|
||||
num.to_le_bytes()[..6]
|
||||
.try_into()
|
||||
.expect("Sanity check, slice length matches array length")
|
||||
}
|
||||
|
||||
impl From<u64> for ClassicAddress {
|
||||
fn from(addr: u64) -> Self {
|
||||
let addr = u64_to_6lsb(addr);
|
||||
|
||||
ClassicAddress(addr)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<BleAddress> for ClassicAddress {
|
||||
type Error = BluetoothError;
|
||||
|
||||
fn try_from(addr: BleAddress) -> Result<Self, Self::Error> {
|
||||
match addr.kind {
|
||||
BleAddressKind::Public => Ok(ClassicAddress(addr.val)),
|
||||
BleAddressKind::Random => Err(BluetoothError::BadTypeConversion(String::from(
|
||||
"can't convert BLE Random address to Bluetooth Classic address."
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BleAddress> for u64 {
|
||||
fn from(addr: BleAddress) -> Self {
|
||||
let mut bytes = [0u8; 8];
|
||||
bytes[..6].copy_from_slice(&addr.val);
|
||||
|
||||
u64::from_le_bytes(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ClassicAddress> for u64 {
|
||||
fn from(addr: ClassicAddress) -> Self {
|
||||
let mut bytes = [0u8; 8];
|
||||
bytes[..6].copy_from_slice(&addr.0);
|
||||
|
||||
u64::from_le_bytes(bytes)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::{BleAddress, BluetoothError};
|
||||
|
||||
/// Holds data related to an incoming BLE Advertisement. This includes
|
||||
/// information about the advertisement (e.g. address of sender) as well as
|
||||
/// data sections extracted from the advertisement. Platform-specific methods
|
||||
/// should be written to load in data sections from incoming advertisements.
|
||||
pub struct BleAdvertisement {
|
||||
address: BleAddress,
|
||||
service_data_16bit_uuid: Option<Vec<ServiceData<u16>>>,
|
||||
}
|
||||
|
||||
impl BleAdvertisement {
|
||||
/// Construct a new `BleAdvertisement` instance.
|
||||
pub(crate) fn new(address: BleAddress) -> Self {
|
||||
BleAdvertisement {
|
||||
address,
|
||||
service_data_16bit_uuid: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve the `BleAddress` that emitted this advertisement.
|
||||
pub fn address(&self) -> BleAddress {
|
||||
self.address
|
||||
}
|
||||
|
||||
/// Setter for `ServiceData` field with 16bit UUID.
|
||||
pub(crate) fn set_service_data_16bit_uuid(
|
||||
&mut self,
|
||||
data_sections: Vec<ServiceData<u16>>,
|
||||
) {
|
||||
self.service_data_16bit_uuid = Some(data_sections);
|
||||
}
|
||||
|
||||
/// Getter for `ServiceData` field with 16bit UUID.
|
||||
pub fn service_data_16bit_uuid(
|
||||
&self,
|
||||
) -> Result<&Vec<ServiceData<u16>>, BluetoothError> {
|
||||
match &self.service_data_16bit_uuid {
|
||||
Some(service_data) => Ok(&service_data),
|
||||
None => Err(BluetoothError::FailedPrecondition(String::from(
|
||||
"No service data has been loaded into this advertisement.",
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Enum denoting the assigned number of Bluetooth common data types. Used for
|
||||
/// fetching specific data sections from a Bluetooth advertisement.
|
||||
/// Bluetooth Assigned Numbers, Section 2.3
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum BleDataTypeId {
|
||||
ServiceData16BitUuid = 0x16,
|
||||
}
|
||||
|
||||
/// Struct representing the Bluetooth Service Data common data type. `U` should
|
||||
/// be one of the valid uuid sizes, specified in:
|
||||
/// Bluetooth Supplement to the Core Specification, Part A, Section 1.11.
|
||||
pub struct ServiceData<U: Copy> {
|
||||
uuid: U,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl<U: Copy> ServiceData<U> {
|
||||
pub fn new(uuid: U, data: Vec<u8>) -> Self {
|
||||
ServiceData { uuid, data }
|
||||
}
|
||||
|
||||
pub fn uuid(&self) -> U {
|
||||
self.uuid
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &Vec<u8> {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Library error type.
|
||||
#[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),
|
||||
/// Indicates that the operation was rejected because the system is not in
|
||||
/// a state required for the operation's execution.
|
||||
/// E.g. The user calls `stop_scan()` or polls the advertisement stream
|
||||
#[error("failed precondition: {0}")]
|
||||
FailedPrecondition(String),
|
||||
/// Reported when the user calls an operation that is supported by their
|
||||
/// Operating System, but is not supported by their device.
|
||||
/// E.g. a Windows machine with an old BT Classic adapter that
|
||||
/// doesn't support BLE).
|
||||
#[error("bluetooth operation not supported by system: {0}")]
|
||||
NotSupported(String),
|
||||
/// Wrapper around OS-level errors, e.g. `windows::core::Error` for Windows.
|
||||
/// These typically mean something is very wrong with the system (e.g. OOM).
|
||||
#[error("bluetooth system-level error: {0}")]
|
||||
System(String),
|
||||
/// Reported when a bug occurs inside the library. Whenever a seemingly
|
||||
/// impossible error condition arises where you could call `expect()`,
|
||||
/// return this error instead.
|
||||
#[error("internal error: {0}")]
|
||||
Internal(String),
|
||||
}
|
||||
|
||||
/// Abstraction around platform-specific pairing status enums.
|
||||
/// `PairingResult::Failure` should eventually be converted to
|
||||
/// `BluetoothError::PairingFailed`.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug)]
|
||||
pub enum PairingResult {
|
||||
Success,
|
||||
AlreadyPaired,
|
||||
AlreadyInProgress,
|
||||
Failure(String),
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/// Module for shared functionality between all Bluetooth platforms.
|
||||
mod address;
|
||||
mod advertisement;
|
||||
mod error;
|
||||
|
||||
pub use address::*;
|
||||
pub use advertisement::*;
|
||||
pub use error::*;
|
||||
@@ -16,20 +16,41 @@
|
||||
// instead of using anyhow.
|
||||
// b/290070686
|
||||
|
||||
pub mod api;
|
||||
pub mod common;
|
||||
|
||||
pub use common::{Adapter, Device};
|
||||
pub use api::{BleAdapter, BleDevice, ClassicDevice};
|
||||
pub use common::{
|
||||
BleAddress, BleAdvertisement, BleDataTypeId, BluetoothError, ClassicAddress,
|
||||
};
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(windows)] {
|
||||
mod windows_ble;
|
||||
use windows_ble::BleAdapter;
|
||||
mod windows;
|
||||
use self::windows as platform;
|
||||
} else {
|
||||
mod unsupported;
|
||||
use unsupported::BleAdapter;
|
||||
use unsupported as platform;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn default_adapter() -> Result<impl Adapter, anyhow::Error> {
|
||||
BleAdapter::default().await
|
||||
pub struct Platform;
|
||||
|
||||
impl Platform {
|
||||
pub async fn default_adapter(
|
||||
) -> Result<impl api::BleAdapter, BluetoothError> {
|
||||
platform::BleAdapter::default().await
|
||||
}
|
||||
|
||||
pub async fn new_ble_device(
|
||||
addr: BleAddress,
|
||||
) -> Result<impl api::BleDevice, BluetoothError> {
|
||||
platform::BleDevice::new(addr).await
|
||||
}
|
||||
|
||||
pub async fn new_classic_device(
|
||||
addr: ClassicAddress,
|
||||
) -> Result<impl api::ClassicDevice, BluetoothError> {
|
||||
platform::ClassicDevice::new(addr).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,30 +15,33 @@
|
||||
use async_trait::async_trait;
|
||||
|
||||
use super::BleDevice;
|
||||
use crate::bluetooth::common::Adapter;
|
||||
use crate::bluetooth::{
|
||||
api, common::BluetoothError, BleAdvertisement, BleDataTypeId,
|
||||
};
|
||||
|
||||
/// Concrete type implementing `Adapter`, used for unsupported devices.
|
||||
/// Every method should panic.
|
||||
pub struct BleAdapter;
|
||||
|
||||
#[async_trait]
|
||||
impl Adapter for BleAdapter {
|
||||
type Device = BleDevice;
|
||||
|
||||
async fn default() -> Result<Self, anyhow::Error> {
|
||||
impl api::BleAdapter for BleAdapter {
|
||||
async fn default() -> Result<Self, BluetoothError> {
|
||||
panic!("Unsupported target platform.");
|
||||
}
|
||||
|
||||
fn start_scan_devices(&mut self) -> Result<(), anyhow::Error> {
|
||||
fn start_scan(&mut self) -> Result<(), BluetoothError> {
|
||||
panic!("Unsupported target platform.");
|
||||
}
|
||||
|
||||
fn stop_scan_devices(&mut self) -> Result<(), anyhow::Error> {
|
||||
fn stop_scan(&mut self) -> Result<(), BluetoothError> {
|
||||
panic!("Unsupported target platform.");
|
||||
}
|
||||
|
||||
async fn next_device(&mut self) -> Result<Self::Device, anyhow::Error> {
|
||||
panic!("Unsupported target platform.");
|
||||
async fn next_advertisement(
|
||||
&mut self,
|
||||
datatype_selector: Option<&Vec<BleDataTypeId>>,
|
||||
) -> Result<BleAdvertisement, BluetoothError> {
|
||||
panic!("Unsupported target platform");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,15 +12,52 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::bluetooth::common::Device;
|
||||
use async_trait::async_trait;
|
||||
|
||||
/// Concrete type implementing `Device`, used for unsupported devices.
|
||||
use crate::bluetooth::{
|
||||
api,
|
||||
common::{BleAddress, BluetoothError, ClassicAddress, PairingResult},
|
||||
};
|
||||
|
||||
/// Concrete type implementing `api::BleDevice` for unsupported platforms.
|
||||
/// Every method should panic.
|
||||
pub struct BleDevice;
|
||||
|
||||
impl Device for BleDevice {
|
||||
fn name(&self) -> Result<String, anyhow::Error> {
|
||||
panic!("Unsupported target platform.")
|
||||
#[async_trait]
|
||||
impl api::BleDevice for BleDevice {
|
||||
async fn new(addr: BleAddress) -> Result<Self, BluetoothError> {
|
||||
panic!("Unsupported target platform.");
|
||||
}
|
||||
|
||||
fn name(&self) -> Result<String, BluetoothError> {
|
||||
panic!("Unsupported target platform.");
|
||||
}
|
||||
|
||||
fn address(&self) -> BleAddress {
|
||||
panic!("Unsupported target platform.");
|
||||
}
|
||||
}
|
||||
|
||||
/// Concrete type implementing `api::ClassicDevice` for unsupported platforms.
|
||||
/// Every method should panic.
|
||||
pub struct ClassicDevice;
|
||||
|
||||
#[async_trait]
|
||||
impl api::ClassicDevice for ClassicDevice {
|
||||
async fn new(addr: ClassicAddress) -> Result<Self, BluetoothError> {
|
||||
panic!("Unsupported target platform.");
|
||||
}
|
||||
|
||||
fn name(&self) -> Result<String, BluetoothError> {
|
||||
panic!("Unsupported target platform.");
|
||||
}
|
||||
|
||||
fn address(&self) -> ClassicAddress {
|
||||
panic!("Unsupported target platform.");
|
||||
}
|
||||
|
||||
async fn pair(&self) -> Result<PairingResult, BluetoothError> {
|
||||
panic!("Unsupported target platform.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+70
-67
@@ -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::{
|
||||
@@ -54,44 +53,49 @@ use windows::{
|
||||
Foundation::TypedEventHandler,
|
||||
};
|
||||
|
||||
use super::BleDevice;
|
||||
use crate::bluetooth::common::Adapter;
|
||||
use crate::bluetooth::{
|
||||
api,
|
||||
common::{BleAdvertisement, BleDataTypeId, BluetoothError},
|
||||
};
|
||||
|
||||
/// Concrete type implementing `Adapter`, used for Windows BLE.
|
||||
/// 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 `api::BleAdapter`, 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]
|
||||
impl Adapter for BleAdapter {
|
||||
type Device = BleDevice;
|
||||
|
||||
async fn default() -> Result<Self, anyhow::Error> {
|
||||
impl api::BleAdapter for BleAdapter {
|
||||
async fn default() -> Result<Self, BluetoothError> {
|
||||
let inner = BluetoothAdapter::GetDefaultAsync()?.await?;
|
||||
|
||||
if !inner.IsLowEnergySupported()? {
|
||||
return Err(anyhow::anyhow!(
|
||||
"This device's Bluetooth Adapter doesn't support Bluetooth LE Transport type."
|
||||
));
|
||||
return Err(BluetoothError::NotSupported(String::from(
|
||||
"LE transport type",
|
||||
)));
|
||||
}
|
||||
if !inner.IsCentralRoleSupported()? {
|
||||
return Err(anyhow::anyhow!(
|
||||
"This device's Bluetooth Adapter doesn't support Bluetooth LE central role."
|
||||
));
|
||||
return Err(BluetoothError::NotSupported(String::from(
|
||||
"central role",
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(BleAdapter {
|
||||
inner,
|
||||
device_stream: None,
|
||||
listener: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn start_scan_devices(&mut self) -> Result<(), anyhow::Error> {
|
||||
fn start_scan(&mut self) -> Result<(), BluetoothError> {
|
||||
let watcher = BluetoothLEAdvertisementWatcher::new()?;
|
||||
match watcher.SetScanningMode(BluetoothLEScanningMode::Active) {
|
||||
Ok(_) => (),
|
||||
@@ -149,7 +153,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,59 +162,58 @@ 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 addr = event_args.BluetoothAddress().ok()?;
|
||||
let kind = event_args.BluetoothAddressType().ok()?;
|
||||
|
||||
match BleDevice::from_addr(addr, kind).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<(), anyhow::Error> {
|
||||
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(anyhow::anyhow!("Device scanning hasn't started."))
|
||||
Err(BluetoothError::FailedPrecondition(String::from(
|
||||
"device scanning hasn't started, please call `start_scan()`",
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
async fn next_device(&mut self) -> Result<Self::Device, anyhow::Error> {
|
||||
if let Some(stream) = &mut self.device_stream {
|
||||
stream
|
||||
.next()
|
||||
.await
|
||||
.ok_or(anyhow::anyhow!("Device returned from stream is None."))
|
||||
async fn next_advertisement(
|
||||
&mut self,
|
||||
datatype_selector: Option<&Vec<BleDataTypeId>>,
|
||||
) -> Result<BleAdvertisement, BluetoothError> {
|
||||
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 mut advertisement =
|
||||
BleAdvertisement::try_from(&event_args)?;
|
||||
|
||||
if let Some(datatype_selector) = datatype_selector {
|
||||
advertisement
|
||||
.load_data(&event_args, datatype_selector)?;
|
||||
}
|
||||
|
||||
break Ok(advertisement);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Device scanning hasn't started."))
|
||||
Err(BluetoothError::FailedPrecondition(String::from(
|
||||
"device scanning hasn't started, please call `start_scan()`",
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Whether the Bluetooth advertisement is Public, Random or Unspecified.
|
||||
//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, BluetoothError};
|
||||
|
||||
// Convenience for converting from Windows API to crate API.
|
||||
impl TryFrom<BluetoothAddressType> for BleAddressKind {
|
||||
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(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,
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience for converting from crate API to Windows API.
|
||||
impl From<BleAddressKind> for BluetoothAddressType {
|
||||
fn from(kind: BleAddressKind) -> Self {
|
||||
match kind {
|
||||
BleAddressKind::Public => BluetoothAddressType::Public,
|
||||
BleAddressKind::Random => BluetoothAddressType::Random,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use windows::{
|
||||
// Struct that receives Bluetooth Low Energy (LE) advertisements.
|
||||
// https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.advertisement.bluetoothleadvertisementwatcher?view=winrt-22621
|
||||
Devices::Bluetooth::Advertisement::{
|
||||
BluetoothLEAdvertisementDataSection,
|
||||
BluetoothLEAdvertisementReceivedEventArgs,
|
||||
},
|
||||
|
||||
// Struct representing an immutable view into a vector.
|
||||
// https://learn.microsoft.com/en-us/uwp/api/windows.foundation.collections.ivectorview-1?view=winrt-22621
|
||||
Foundation::Collections::IVectorView,
|
||||
|
||||
// Struct for reading data from a Windows stream, like an IVectorView.
|
||||
// https://learn.microsoft.com/en-us/uwp/api/windows.storage.streams.datareader?view=winrt-22621
|
||||
Storage::Streams::DataReader,
|
||||
};
|
||||
|
||||
use crate::bluetooth::common::{
|
||||
BleAddress, BleAddressKind, BleAdvertisement, BleDataTypeId,
|
||||
BluetoothError, ServiceData,
|
||||
};
|
||||
|
||||
impl TryFrom<&BluetoothLEAdvertisementReceivedEventArgs> for BleAdvertisement {
|
||||
type Error = BluetoothError;
|
||||
|
||||
fn try_from(
|
||||
adv: &BluetoothLEAdvertisementReceivedEventArgs,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let addr = adv.BluetoothAddress()?;
|
||||
let kind = BleAddressKind::try_from(adv.BluetoothAddressType()?)?;
|
||||
|
||||
let addr = BleAddress::new(addr, kind);
|
||||
|
||||
Ok(BleAdvertisement::new(addr))
|
||||
}
|
||||
}
|
||||
|
||||
impl BleAdvertisement {
|
||||
/// Load data of selected data types into self by parsing the raw Windows
|
||||
/// advertisement.
|
||||
/// See: Supplement to the Bluetooth Core Specification Part A, Section 1.
|
||||
pub(crate) fn load_data(
|
||||
&mut self,
|
||||
adv: &BluetoothLEAdvertisementReceivedEventArgs,
|
||||
datatype_ids: &[BleDataTypeId],
|
||||
) -> Result<(), BluetoothError> {
|
||||
let adv = adv.Advertisement()?;
|
||||
|
||||
for datatype_id in datatype_ids {
|
||||
// Note `raw_data_sections` is `!Send` and `!Sync`. This means
|
||||
// processing must occur in a synchronous environment. The compiler
|
||||
// will complain if parsing is done in an async function.
|
||||
let raw_data_sections =
|
||||
adv.GetSectionsByType((*datatype_id) as u8)?;
|
||||
match datatype_id {
|
||||
BleDataTypeId::ServiceData16BitUuid => {
|
||||
let service_data =
|
||||
parse_service_data_16bit_uuid(raw_data_sections)?;
|
||||
self.set_service_data_16bit_uuid(service_data)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the advertisement's service data.
|
||||
/// Further Reading:
|
||||
/// * `BleMedium::AdvertisementReceivedHandler` under
|
||||
/// github.com/google/nearby/internal/platform/implementation/windows_ble/ble_medium.cc.
|
||||
/// * Bluetooth Supplement to the Core Specification, Part A, Section 1.11.
|
||||
/// * go/fast_pair_windows_data_parse.
|
||||
#[inline]
|
||||
fn parse_service_data_16bit_uuid(
|
||||
raw_data_sections: IVectorView<BluetoothLEAdvertisementDataSection>,
|
||||
) -> Result<Vec<ServiceData<u16>>, BluetoothError> {
|
||||
let mut data_vec = Vec::new();
|
||||
|
||||
for raw_data in raw_data_sections {
|
||||
let data_reader = DataReader::FromBuffer(&raw_data.Data()?)?;
|
||||
let uuid = data_reader.ReadUInt16()?;
|
||||
|
||||
let unconsumed_buffer_len =
|
||||
data_reader.UnconsumedBufferLength()? as usize;
|
||||
|
||||
let mut data = vec![0u8; unconsumed_buffer_len];
|
||||
data_reader.ReadBytes(&mut data)?;
|
||||
|
||||
data_vec.push(ServiceData::new(uuid, data));
|
||||
}
|
||||
|
||||
Ok(data_vec)
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use async_trait::async_trait;
|
||||
use tracing::{info, warn};
|
||||
use windows::{
|
||||
Devices::{
|
||||
Bluetooth::{
|
||||
// Tuple struct describing the type of address (public, random, unspecified).
|
||||
// https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.bluetoothaddresstype?view=winrt-22621
|
||||
BluetoothAddressType,
|
||||
|
||||
// Struct for interacting with a discovered BT Classic device.
|
||||
// https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.bluetoothdevice?view=winrt-22621
|
||||
BluetoothDevice,
|
||||
|
||||
// Struct for interacting with a discovered BLE device.
|
||||
// https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.bluetoothledevice?view=winrt-22621
|
||||
BluetoothLEDevice,
|
||||
},
|
||||
Enumeration::{
|
||||
// Struct for custom pairing with a device.
|
||||
// https://learn.microsoft.com/en-us/uwp/api/windows.devices.enumeration.deviceinformationcustompairing?view=winrt-22621
|
||||
DeviceInformationCustomPairing,
|
||||
|
||||
// Tuple struct to indicate the kinds of pairing supported by the application.
|
||||
// https://learn.microsoft.com/en-us/uwp/api/windows.devices.enumeration.devicepairingkinds?view=winrt-22621
|
||||
DevicePairingKinds,
|
||||
|
||||
// Struct for retrieving data about a PairingRequested event.
|
||||
// https://learn.microsoft.com/en-us/uwp/api/windows.devices.enumeration.devicepairingrequestedeventargs?view=winrt-22621
|
||||
DevicePairingRequestedEventArgs,
|
||||
},
|
||||
},
|
||||
// Wraps a closure for handling events associated with a struct
|
||||
// (e.g. PairingRequested event in `DeviceInformationCustomPairing`).
|
||||
// https://learn.microsoft.com/en-us/uwp/api/windows.foundation.typedeventhandler-2?view=winrt-22621
|
||||
Foundation::TypedEventHandler,
|
||||
};
|
||||
|
||||
use crate::bluetooth::{api, common::{BleAddress, ClassicAddress, BluetoothError, PairingResult}};
|
||||
|
||||
/// Concrete type implementing `Device`, used for Windows BLE.
|
||||
pub struct BleDevice {
|
||||
inner: BluetoothLEDevice,
|
||||
addr: BleAddress,
|
||||
}
|
||||
|
||||
/// Concrete type implementing `Device`, used for Windows Bluetooth Classic.
|
||||
pub struct ClassicDevice {
|
||||
inner: BluetoothDevice,
|
||||
addr: ClassicAddress,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl api::BleDevice for BleDevice {
|
||||
async fn new(addr: BleAddress) -> Result<Self, BluetoothError> {
|
||||
let kind = BluetoothAddressType::from(addr.get_kind());
|
||||
let raw_addr = u64::from(addr);
|
||||
|
||||
let inner = BluetoothLEDevice::FromBluetoothAddressWithBluetoothAddressTypeAsync(
|
||||
raw_addr, kind,
|
||||
)?
|
||||
.await?;
|
||||
|
||||
Ok(BleDevice { inner, addr })
|
||||
}
|
||||
|
||||
fn name(&self) -> Result<String, BluetoothError> {
|
||||
Ok(self.inner.Name()?.to_string())
|
||||
}
|
||||
|
||||
fn address(&self) -> BleAddress {
|
||||
self.addr
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl api::ClassicDevice for ClassicDevice {
|
||||
async fn new(addr: ClassicAddress) -> Result<Self, BluetoothError> {
|
||||
let raw_addr = u64::from(addr);
|
||||
|
||||
let inner = BluetoothDevice::FromBluetoothAddressAsync(
|
||||
raw_addr,
|
||||
)?
|
||||
.await?;
|
||||
|
||||
Ok(ClassicDevice { inner, addr })
|
||||
}
|
||||
|
||||
fn name(&self) -> Result<String, BluetoothError> {
|
||||
Ok(self.inner.Name()?.to_string_lossy())
|
||||
}
|
||||
|
||||
fn address(&self) -> ClassicAddress {
|
||||
self.addr
|
||||
}
|
||||
|
||||
async fn pair(&self) -> Result<PairingResult, BluetoothError> {
|
||||
let pair_info = self.inner.DeviceInformation()?.Pairing()?;
|
||||
if pair_info.IsPaired()? {
|
||||
info!("Device already paired");
|
||||
Ok(PairingResult::AlreadyPaired)
|
||||
} else if !pair_info.CanPair()? {
|
||||
info!("Device can't pair");
|
||||
Err(BluetoothError::PairingFailed(String::from("device can't pair")))
|
||||
} else {
|
||||
let custom = pair_info.Custom()?;
|
||||
custom.PairingRequested(&TypedEventHandler::new(
|
||||
|_custom: &Option<DeviceInformationCustomPairing>,
|
||||
event_args: &Option<DevicePairingRequestedEventArgs>,
|
||||
| {
|
||||
if let Some(event_args) = event_args {
|
||||
match event_args.PairingKind()? {
|
||||
DevicePairingKinds::ConfirmOnly => {
|
||||
event_args.Accept()
|
||||
}
|
||||
_ => {
|
||||
warn!("Unsupported pairing kind {:?}", event_args.PairingKind());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("Empty pairing event arguments");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
},
|
||||
))?;
|
||||
let res = custom
|
||||
.PairAsync(
|
||||
DevicePairingKinds::ConfirmOnly
|
||||
| DevicePairingKinds::ProvidePin
|
||||
| DevicePairingKinds::ConfirmPinMatch
|
||||
| DevicePairingKinds::DisplayPin,
|
||||
)?
|
||||
.await?;
|
||||
let status = PairingResult::from(res.Status()?);
|
||||
|
||||
match status {
|
||||
PairingResult::Failure(msg) => Err(BluetoothError::PairingFailed(msg)),
|
||||
_ => Ok(status),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// TODO b/288592509 unit tests
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use windows::Devices::Enumeration::DevicePairingResultStatus;
|
||||
|
||||
use crate::bluetooth::common::{BluetoothError, PairingResult};
|
||||
|
||||
impl From<windows::core::Error> for BluetoothError {
|
||||
fn from(err: windows::core::Error) -> Self {
|
||||
BluetoothError::System(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/uwp/api/windows.devices.enumeration.devicepairingresultstatus?view=winrt-22621
|
||||
impl From<DevicePairingResultStatus> for PairingResult {
|
||||
fn from(status: DevicePairingResultStatus) -> Self {
|
||||
match status {
|
||||
DevicePairingResultStatus::Paired => PairingResult::Success,
|
||||
DevicePairingResultStatus::AlreadyPaired => {
|
||||
PairingResult::AlreadyPaired
|
||||
}
|
||||
DevicePairingResultStatus::OperationAlreadyInProgress => {
|
||||
PairingResult::AlreadyInProgress
|
||||
}
|
||||
DevicePairingResultStatus::NotReadyToPair => PairingResult::Failure(
|
||||
String::from("the device object is not in a state where it can be paired"),
|
||||
),
|
||||
DevicePairingResultStatus::NotPaired => PairingResult::Failure(String::from("the device object is not currently paired.")),
|
||||
DevicePairingResultStatus::ConnectionRejected => PairingResult::Failure(String::from("the device object rejected the connection.")),
|
||||
DevicePairingResultStatus::TooManyConnections => PairingResult::Failure(String::from("the device object indicated it cannot accept any more incoming connections.")),
|
||||
DevicePairingResultStatus::HardwareFailure => PairingResult::Failure(String::from("the device object indicated there was a hardware failure.")),
|
||||
DevicePairingResultStatus::AuthenticationTimeout => PairingResult::Failure(String::from("the authentication process timed out before it could complete.")),
|
||||
DevicePairingResultStatus::AuthenticationNotAllowed => PairingResult::Failure(String::from("the authentication protocol is not supported, so the device is not paired.")),
|
||||
DevicePairingResultStatus::AuthenticationFailure => PairingResult::Failure(String::from("authentication failed, so the device is not paired. Either the device object or the application rejected the authentication.")),
|
||||
DevicePairingResultStatus::NoSupportedProfiles => PairingResult::Failure(String::from("there are no network profiles for this device object to use.")),
|
||||
DevicePairingResultStatus::ProtectionLevelCouldNotBeMet => PairingResult::Failure(String::from("the minimum level of protection is not supported by the device object or the application.")),
|
||||
DevicePairingResultStatus::AccessDenied => PairingResult::Failure(String::from("your application does not have the appropriate permissions level to pair the device object.")),
|
||||
DevicePairingResultStatus::InvalidCeremonyData => PairingResult::Failure(String::from("the ceremony data was incorrect.")),
|
||||
DevicePairingResultStatus::PairingCanceled => PairingResult::Failure(String::from("the pairing action was canceled before completion.")),
|
||||
DevicePairingResultStatus::RequiredHandlerNotRegistered => PairingResult::Failure(String::from("either the event handler wasn't registered or a required DevicePairingKinds was not supported.",)),
|
||||
DevicePairingResultStatus::RejectedByHandler => PairingResult::Failure(String::from("the application handler rejected the pairing.")),
|
||||
DevicePairingResultStatus::RemoteDeviceHasAssociation => PairingResult::Failure(String::from("the remote device already has an association.")),
|
||||
DevicePairingResultStatus::Failed | _ => PairingResult::Failure(String::from("an unknown failure occurred.")),
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
@@ -14,7 +14,12 @@
|
||||
|
||||
/// Bluetooth LE module for Windows devices.
|
||||
mod adapter;
|
||||
mod address;
|
||||
mod advertisement;
|
||||
mod device;
|
||||
mod error;
|
||||
|
||||
pub use adapter::*;
|
||||
pub use address::*;
|
||||
pub use advertisement::*;
|
||||
pub use device::*;
|
||||
@@ -1,58 +0,0 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use async_trait::async_trait;
|
||||
use windows::Devices::Bluetooth::{
|
||||
// Enum describing the type of address (public, random, unspecified).
|
||||
// https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.bluetoothaddresstype?view=winrt-22621
|
||||
BluetoothAddressType,
|
||||
|
||||
// Struct for interacting with and pairing to a discovered BLE device.
|
||||
// https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.bluetoothledevice?view=winrt-22621
|
||||
BluetoothLEDevice,
|
||||
};
|
||||
|
||||
use crate::bluetooth::common::Device;
|
||||
|
||||
/// Concrete type implementing `Device`, used for Windows BLE.
|
||||
pub struct BleDevice {
|
||||
inner: BluetoothLEDevice,
|
||||
}
|
||||
|
||||
impl BleDevice {
|
||||
/// Create a `BleDevice` instance from the raw bluetooth address information.
|
||||
pub(super) async fn from_addr(
|
||||
addr: u64,
|
||||
kind: BluetoothAddressType,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let inner =
|
||||
BluetoothLEDevice::FromBluetoothAddressWithBluetoothAddressTypeAsync(addr, kind)?
|
||||
.await?;
|
||||
|
||||
Ok(BleDevice { inner })
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Device for BleDevice {
|
||||
fn name(&self) -> Result<String, anyhow::Error> {
|
||||
Ok(self.inner.Name()?.to_string_lossy())
|
||||
}
|
||||
}
|
||||
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// TODO b/288592509 unit tests
|
||||
}
|
||||
@@ -12,22 +12,106 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use futures::executor;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
error::Error,
|
||||
io::{self, Write},
|
||||
sync::Arc,
|
||||
thread,
|
||||
};
|
||||
|
||||
use futures::{
|
||||
executor::{self, block_on},
|
||||
lock::Mutex,
|
||||
};
|
||||
|
||||
mod bluetooth;
|
||||
|
||||
use bluetooth::{Adapter, Device};
|
||||
use crate::bluetooth::{
|
||||
BleAdapter, BleDataTypeId, BleDevice, ClassicAddress, ClassicDevice,
|
||||
Platform,
|
||||
};
|
||||
|
||||
fn main() -> Result<(), anyhow::Error> {
|
||||
async fn get_user_input(
|
||||
device_vec: Arc<Mutex<Vec<impl BleDevice>>>,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut buffer = String::new();
|
||||
loop {
|
||||
io::stdout().flush()?;
|
||||
buffer.clear();
|
||||
io::stdin().read_line(&mut buffer)?;
|
||||
|
||||
let val = match buffer.trim().parse::<usize>() {
|
||||
Ok(val) => val,
|
||||
Err(_) => {
|
||||
println!("Please enter a valid digit.");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let index_to_device = device_vec.lock().await;
|
||||
match index_to_device.get(val) {
|
||||
Some(device) => {
|
||||
let addr = device.address();
|
||||
let classic_addr = ClassicAddress::try_from(addr)?;
|
||||
|
||||
let classic_device =
|
||||
Platform::new_classic_device(classic_addr).await?;
|
||||
|
||||
match classic_device.pair().await {
|
||||
Ok(_) => {
|
||||
println!("Pairing success!");
|
||||
}
|
||||
Err(err) => println!("Error {}", err),
|
||||
}
|
||||
break Ok(());
|
||||
}
|
||||
None => println!("Please enter a valid digit."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let run = async {
|
||||
let mut adapter = bluetooth::default_adapter().await?;
|
||||
adapter.start_scan_devices()?;
|
||||
let mut adapter = Platform::default_adapter().await?;
|
||||
adapter.start_scan()?;
|
||||
|
||||
while let Ok(device) = adapter.next_device().await {
|
||||
println!("found {}", device.name()?)
|
||||
let mut addr_set = HashSet::new();
|
||||
let device_vec = Arc::new(Mutex::new(Vec::new()));
|
||||
|
||||
{
|
||||
// Process user input in a separate thread.
|
||||
let device_vec = device_vec.clone();
|
||||
thread::spawn(|| block_on(get_user_input(device_vec)).unwrap());
|
||||
}
|
||||
|
||||
unreachable!("Done scanning");
|
||||
let mut counter: u32 = 0;
|
||||
let datatype_selector = vec![BleDataTypeId::ServiceData16BitUuid];
|
||||
// Retrieve incoming device advertisements.
|
||||
while let Ok(advertisement) =
|
||||
adapter.next_advertisement(Some(&datatype_selector)).await
|
||||
{
|
||||
for service_data in advertisement.service_data_16bit_uuid()? {
|
||||
let uuid = service_data.uuid();
|
||||
|
||||
// This is a Fast Pair device.
|
||||
if uuid == 0x2cfe {
|
||||
let addr = advertisement.address();
|
||||
let ble_device = Platform::new_ble_device(addr).await?;
|
||||
let name = ble_device.name()?;
|
||||
|
||||
if addr_set.insert(addr) {
|
||||
// New FP device discovered.
|
||||
println!("{}: {}", counter, name);
|
||||
device_vec.lock().await.push(ble_device);
|
||||
counter += 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("Done scanning");
|
||||
Ok(())
|
||||
};
|
||||
|
||||
executor::block_on(run)
|
||||
|
||||
Reference in New Issue
Block a user