Skip to content

Commit

Permalink
feat: Scaffold Windows BLE backend support
Browse files Browse the repository at this point in the history
  • Loading branch information
gmallios committed Dec 23, 2023
1 parent 8e4b02c commit fd142f8
Show file tree
Hide file tree
Showing 8 changed files with 454 additions and 3 deletions.
20 changes: 17 additions & 3 deletions soundcore-lib/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[package]
name = "soundcore-lib"
version = "0.1.0"
version = "0.2.0"
edition = "2021"

[dependencies]
serde = { version = "1", features = ["derive", "rc"] }
tokio = { version = "1", features = ["time", "macros", "rt-multi-thread"] }
tokio = { version = "1", features = ["time", "macros", "rt-multi-thread", "sync"] }
strum = { version = "0.25", features = ["derive"] }
nom = "7"
enumflags2 = { version = "0.7.7", features = ["serde"] }
Expand All @@ -16,6 +16,20 @@ log = "0.4.20"
typeshare = "1.0.1"
phf = { version = "0.11", default-features = false, features = ["macros"] }
derive_more = { version = "0.99", features = ["from"] }
uuid = { version = "1.6.1", features = ["v4", "serde"] }

[dev-dependencies]
test_data = { path = "../test_data" }
test_data = { path = "../test_data" }


[target.'cfg(target_os = "windows")'.dependencies]
windows = { version = "0.52", features = [
"Storage_Streams",
"Foundation",
"implement",
"Foundation_Collections",
"Devices_Enumeration",
"Devices_Bluetooth",
"Devices_Bluetooth_GenericAttributeProfile",
"Devices_Bluetooth_Advertisement"
] }
73 changes: 73 additions & 0 deletions soundcore-lib/src/ble.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use async_trait::async_trait;
use serde::{Deserialize, Serialize};

use crate::error::SoundcoreLibResult;

mod ble;
mod windows;

/// The general flow should be:
/// BLEDeviceScanner -> BLEDeviceDescriptor -> BLEConnectionFactory -> BLEConnection -> SoundcoreDevice
#[async_trait]
pub trait BLEConnection {
async fn read_channel(&self) -> SoundcoreLibResult<tokio::sync::mpsc::Receiver<Vec<u8>>>;
async fn write(&self, bytes: &[u8], write_type: WriteType) -> SoundcoreLibResult<()>;
}

#[async_trait]
pub trait BLEConnectionFactory {
type Connection: BLEConnection + Send + Sync;
async fn connect(
&self,
mac_addr: &str,
uuid_set: BLEConnectionUuidSet,
) -> SoundcoreLibResult<Self::Connection>;
}

#[async_trait]
pub trait BLEDeviceScanner {
// type Descriptor: DeviceDescriptor + Clone + Send + Sync;

async fn scan(&self) -> SoundcoreLibResult<Vec<BLEDeviceDescriptor>>;
}

pub trait DeviceDescriptor {
fn mac_addr(&self) -> &str;
fn name(&self) -> &str;
}

pub struct BLEDeviceDescriptor {
pub mac_addr: String,
pub name: String,
}

impl BLEDeviceDescriptor {
pub fn new(mac_addr: impl Into<String>, name: impl Into<String>) -> Self {
Self {
mac_addr: mac_addr.into(),
name: name.into(),
}
}
}

impl DeviceDescriptor for BLEDeviceDescriptor {
fn mac_addr(&self) -> &str {
&self.mac_addr
}

fn name(&self) -> &str {
&self.name
}
}

pub enum WriteType {
WithResponse,
WithoutResponse,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct BLEConnectionUuidSet {
pub service_uuid: uuid::Uuid,
pub read_uuid: uuid::Uuid,
pub write_uuid: uuid::Uuid,
}
1 change: 1 addition & 0 deletions soundcore-lib/src/ble/ble.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

3 changes: 3 additions & 0 deletions soundcore-lib/src/ble/windows.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod connection;
mod descriptor;
mod scanner;
Empty file.
27 changes: 27 additions & 0 deletions soundcore-lib/src/ble/windows/descriptor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use crate::ble::{BLEDeviceDescriptor, DeviceDescriptor};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WindowsBLEDescriptor {
name: String,
mac_addr: String,
}

impl WindowsBLEDescriptor {
pub fn new(name: impl Into<String>, mac_addr: impl Into<String>) -> Self {
Self {
name: name.into(),
mac_addr: mac_addr.into(),
}
}
}

impl DeviceDescriptor for WindowsBLEDescriptor {
fn mac_addr(&self) -> &str {
&self.mac_addr
}

fn name(&self) -> &str {
&self.name
}
}
135 changes: 135 additions & 0 deletions soundcore-lib/src/ble/windows/scanner.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use collections::HashMap;
use std::{collections, sync};
use sync::{Arc, Mutex};

use async_trait::async_trait;
use log::trace;
use tokio::task::spawn_blocking;
use windows::Devices::Bluetooth::Advertisement::{
BluetoothLEAdvertisementReceivedEventArgs, BluetoothLEAdvertisementWatcher,
};
use windows::Devices::Bluetooth::BluetoothDevice;
use windows::Devices::Enumeration::DeviceInformation;
use windows::Foundation::TypedEventHandler;
use windows::Storage::Streams::DataReader;

use crate::ble::{BLEDeviceDescriptor, BLEDeviceScanner};
use crate::btaddr::BluetoothAdrr;
use crate::error::{SoundcoreLibError, SoundcoreLibResult};

const WATCH_DURATION: u64 = 10;

pub struct WindowsBLEDeviceScanner {}

impl WindowsBLEDeviceScanner {
pub fn new() -> Self {
Self {}
}
}

#[async_trait]
impl BLEDeviceScanner for WindowsBLEDeviceScanner {
// type Descriptor = WindowsBLEDescriptor;

async fn scan(&self) -> SoundcoreLibResult<Vec<BLEDeviceDescriptor>> {
spawn_blocking(move || {
let addr_swap_map =
Arc::new(Mutex::new(HashMap::<BluetoothAdrr, BluetoothAdrr>::new()));

let device_watcher = BluetoothLEAdvertisementWatcher::new()?;
let handler = TypedEventHandler::new(
move |_sender: &Option<BluetoothLEAdvertisementWatcher>,
args: &Option<BluetoothLEAdvertisementReceivedEventArgs>|
-> Result<(), windows::core::Error> {
event_handler(addr_swap_map.clone(), _sender, args)
},
);

// Register the event handler
device_watcher.Received(&handler)?;

// Scan for devices
device_watcher.Start()?;
std::thread::sleep(std::time::Duration::from_secs(WATCH_DURATION));
device_watcher.Stop()?;

let scan_result =
DeviceInformation::FindAllAsyncAqsFilter(&BluetoothDevice::GetDeviceSelector()?)?
.get()?;

Ok(scan_result
.into_iter()
.map(|info| BluetoothDevice::FromIdAsync(&info.Id()?)?.get())
.filter_map(|device| device.ok())
.map(|device| {
let mut addr = BluetoothAdrr::from(device.BluetoothAddress()?);
match addr_swap_map.lock().unwrap().get(&addr) {
Some(new_addr) => {
trace!("Swapping MAC address {:?} with {:?}", addr, new_addr);
addr = new_addr.clone();
}
None => {}
}
Ok(BLEDeviceDescriptor::new(
device.Name()?.to_string(),
addr.to_string(),
)) as SoundcoreLibResult<BLEDeviceDescriptor>
})
.filter_map(|descriptor_result| descriptor_result.ok())
.collect::<Vec<BLEDeviceDescriptor>>())
})
.await
.map_err(|_e| SoundcoreLibError::Unknown)?
}
}

/// This is a hack to replace the address with the one that is in the BLE advertisment
/// frames and not the one return by the device information.
/// This HashMap has the original address as the key and the new address as the value.
fn event_handler(
swap_map: Arc<Mutex<HashMap<BluetoothAdrr, BluetoothAdrr>>>,
_sender: &Option<BluetoothLEAdvertisementWatcher>,
args: &Option<BluetoothLEAdvertisementReceivedEventArgs>,
) -> Result<(), windows::core::Error> {
if let Some(args) = args {
let addr = BluetoothAdrr::from(
BluetoothDevice::FromBluetoothAddressAsync(args.BluetoothAddress()?)?
.get()?
.BluetoothAddress()?,
);
let mut swap_map = swap_map.lock().unwrap();
if addr.is_soundcore_mac() {
trace!(
"Found candidate device {:?} for swapping MACs, checking advertisement data sections...",
addr
);
let data_sections = args.Advertisement()?.DataSections()?.into_iter();

for section in data_sections {
let data_buf = section.Data()?;
let data_reader = DataReader::FromBuffer(&data_buf)?;
let mut data = vec![0_u8; data_buf.Length()? as usize];
data_reader.ReadBytes(&mut data)?;
trace!("Found advertisement data section: {:?}", data);

match BluetoothAdrr::SOUNDCORE_MAC_PREFIXES
.iter()
.any(|prefix| data.starts_with(prefix))
{
true => {
let addr_to_swap = BluetoothAdrr::from_bytes(&data[0..6]).unwrap();
if addr_to_swap != addr {
trace!("Found advertisement data section with MAC address, swapping {:?} with {:?}", addr_to_swap, addr);
swap_map.insert(addr_to_swap, addr.clone());
}
}
false => {
trace!("Found advertisement data section that does not contain a MAC address, skipping...");
}
}
}
}
drop(swap_map);
}
Ok(())
}
Loading

0 comments on commit fd142f8

Please sign in to comment.