use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use windows::Devices::Bluetooth::Advertisement::{
BluetoothLEAdvertisementReceivedEventArgs, BluetoothLEAdvertisementWatcher,
BluetoothLEAdvertisementWatcherStatus, BluetoothLEScanningMode,
};
use windows::Foundation::{EventRegistrationToken, TypedEventHandler};
use crate::config::DiscoveryConfig;
use crate::discovery::PeatBeacon;
use crate::error::{BleError, Result};
use crate::platform::DiscoveredDevice;
use crate::NodeId;
#[derive(Debug, Clone)]
pub struct DiscoveredPeripheral {
pub address: u64,
pub address_string: String,
pub name: Option<String>,
pub rssi: i16,
pub is_peat_node: bool,
pub node_id: Option<NodeId>,
pub adv_data: Vec<u8>,
pub timestamp: i64,
}
struct WatcherState {
peripherals: HashMap<u64, DiscoveredPeripheral>,
peat_peripherals: Vec<DiscoveredPeripheral>,
}
impl Default for WatcherState {
fn default() -> Self {
Self {
peripherals: HashMap::new(),
peat_peripherals: Vec::new(),
}
}
}
pub struct BleWatcher {
watcher: BluetoothLEAdvertisementWatcher,
received_token: Option<EventRegistrationToken>,
state: Arc<Mutex<WatcherState>>,
is_scanning: bool,
}
impl BleWatcher {
pub fn new() -> Result<Self> {
let watcher = BluetoothLEAdvertisementWatcher::new()
.map_err(|e| BleError::PlatformError(format!("Failed to create watcher: {}", e)))?;
Ok(Self {
watcher,
received_token: None,
state: Arc::new(Mutex::new(WatcherState::default())),
is_scanning: false,
})
}
pub fn start_scan(&mut self, config: &DiscoveryConfig) -> Result<()> {
if self.is_scanning {
return Ok(());
}
let mode = if config.active_scan {
BluetoothLEScanningMode::Active
} else {
BluetoothLEScanningMode::Passive
};
self.watcher
.SetScanningMode(mode)
.map_err(|e| BleError::PlatformError(format!("Failed to set scanning mode: {}", e)))?;
let state = self.state.clone();
let handler = TypedEventHandler::new(
move |_watcher: &Option<BluetoothLEAdvertisementWatcher>,
args: &Option<BluetoothLEAdvertisementReceivedEventArgs>| {
if let Some(args) = args {
if let Err(e) = Self::handle_advertisement(&state, args) {
log::warn!("Error handling advertisement: {}", e);
}
}
Ok(())
},
);
let token = self
.watcher
.Received(&handler)
.map_err(|e| BleError::PlatformError(format!("Failed to register handler: {}", e)))?;
self.received_token = Some(token);
self.watcher
.Start()
.map_err(|e| BleError::PlatformError(format!("Failed to start watcher: {}", e)))?;
self.is_scanning = true;
log::info!("BLE scanning started");
Ok(())
}
pub fn stop_scan(&mut self) -> Result<()> {
if !self.is_scanning {
return Ok(());
}
self.watcher
.Stop()
.map_err(|e| BleError::PlatformError(format!("Failed to stop watcher: {}", e)))?;
if let Some(token) = self.received_token.take() {
let _ = self.watcher.RemoveReceived(token);
}
self.is_scanning = false;
log::info!("BLE scanning stopped");
Ok(())
}
pub fn is_scanning(&self) -> bool {
self.is_scanning
}
pub fn status(&self) -> Result<BluetoothLEAdvertisementWatcherStatus> {
self.watcher
.Status()
.map_err(|e| BleError::PlatformError(format!("Failed to get status: {}", e)))
}
pub fn get_peat_peripherals(&self) -> Vec<DiscoveredPeripheral> {
if let Ok(state) = self.state.lock() {
state.peat_peripherals.clone()
} else {
Vec::new()
}
}
pub fn get_all_peripherals(&self) -> Vec<DiscoveredPeripheral> {
if let Ok(state) = self.state.lock() {
state.peripherals.values().cloned().collect()
} else {
Vec::new()
}
}
pub fn clear_peripherals(&self) {
if let Ok(mut state) = self.state.lock() {
state.peripherals.clear();
state.peat_peripherals.clear();
}
}
fn handle_advertisement(
state: &Arc<Mutex<WatcherState>>,
args: &BluetoothLEAdvertisementReceivedEventArgs,
) -> Result<()> {
let address = args
.BluetoothAddress()
.map_err(|e| BleError::PlatformError(format!("Failed to get address: {}", e)))?;
let address_string = format!(
"{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
(address >> 40) & 0xFF,
(address >> 32) & 0xFF,
(address >> 24) & 0xFF,
(address >> 16) & 0xFF,
(address >> 8) & 0xFF,
address & 0xFF
);
let rssi = args
.RawSignalStrengthInDBm()
.map_err(|e| BleError::PlatformError(format!("Failed to get RSSI: {}", e)))?;
let timestamp = args
.Timestamp()
.map_err(|e| BleError::PlatformError(format!("Failed to get timestamp: {}", e)))?
.UniversalTime;
let advertisement = args
.Advertisement()
.map_err(|e| BleError::PlatformError(format!("Failed to get advertisement: {}", e)))?;
let name = advertisement.LocalName().ok().and_then(|s| {
let s = s.to_string();
if s.is_empty() {
None
} else {
Some(s)
}
});
let mut adv_data = Vec::new();
let mut is_peat_node = false;
let mut node_id = None;
if let Ok(manufacturer_data) = advertisement.ManufacturerData() {
if let Ok(size) = manufacturer_data.Size() {
for i in 0..size {
if let Ok(data) = manufacturer_data.GetAt(i) {
if let Ok(company_id) = data.CompanyId() {
if company_id == 0xFFFF {
if let Ok(buffer) = data.Data() {
if let Ok(reader) =
windows::Storage::Streams::DataReader::FromBuffer(&buffer)
{
if let Ok(len) = reader.UnconsumedBufferLength() {
let mut bytes = vec![0u8; len as usize];
if reader.ReadBytes(&mut bytes).is_ok() {
adv_data = bytes.clone();
if let Some(beacon) = PeatBeacon::decode(&bytes) {
is_peat_node = true;
node_id = Some(beacon.node_id);
}
}
}
}
}
}
}
}
}
}
}
if let Ok(service_uuids) = advertisement.ServiceUuids() {
if let Ok(size) = service_uuids.Size() {
for i in 0..size {
if let Ok(uuid) = service_uuids.GetAt(i) {
let uuid_str = format!("{:?}", uuid);
if uuid_str.contains("f47ac10b-58cc-4372-a567-0e02b2c3d479") {
is_peat_node = true;
break;
}
}
}
}
}
let peripheral = DiscoveredPeripheral {
address,
address_string,
name,
rssi,
is_peat_node,
node_id,
adv_data,
timestamp,
};
if let Ok(mut state) = state.lock() {
state.peripherals.insert(address, peripheral.clone());
if is_peat_node {
if let Some(existing) = state
.peat_peripherals
.iter_mut()
.find(|p| p.address == address)
{
*existing = peripheral;
} else {
state.peat_peripherals.push(peripheral);
}
}
}
Ok(())
}
}
impl Drop for BleWatcher {
fn drop(&mut self) {
let _ = self.stop_scan();
}
}
impl From<DiscoveredPeripheral> for DiscoveredDevice {
fn from(peripheral: DiscoveredPeripheral) -> Self {
DiscoveredDevice {
address: peripheral.address_string,
name: peripheral.name,
rssi: peripheral.rssi as i8,
is_peat_node: peripheral.is_peat_node,
node_id: peripheral.node_id,
adv_data: peripheral.adv_data,
}
}
}