feat: device scanning

This commit is contained in:
2024-07-16 21:14:19 +08:00
parent 1d213eae57
commit 09e2ab50bb
25 changed files with 725 additions and 223 deletions

2
src-tauri/Cargo.lock generated
View File

@@ -1319,7 +1319,7 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "heartbeat-cat"
version = "0.0.0"
version = "0.0.1"
dependencies = [
"btleplug",
"futures",

View File

@@ -1,8 +1,8 @@
[package]
name = "heartbeat-cat"
version = "0.0.0"
description = "A Tauri App"
authors = ["you"]
version = "0.0.1"
description = "Catch your heartbeats"
authors = ["TimothyYin"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@@ -6,9 +6,11 @@ use btleplug::api::CentralEvent::{
DeviceConnected, DeviceDisconnected, DeviceDiscovered, DeviceUpdated,
ManufacturerDataAdvertisement, ServiceDataAdvertisement, ServicesAdvertisement,
};
use btleplug::api::{Central, Manager as _, Peripheral as _, ScanFilter};
use btleplug::platform::{Adapter, Manager as BtleManager, Peripheral};
use btleplug::api::{BDAddr, Central, Manager as _, Peripheral as _, ScanFilter};
use btleplug::platform::{Adapter, Manager as BtleManager, Peripheral, PeripheralId};
use futures::StreamExt;
use std::error::Error;
use std::sync::Arc;
use tauri::{AppHandle, Manager, State};
use tokio;
use tokio::sync::Mutex;
@@ -16,177 +18,249 @@ use tokio::sync::Mutex;
#[derive(serde::Serialize, Clone)]
struct BleDevice {
name: String,
address: String,
address: BDAddr,
rssi: i16,
}
struct BleConnection<'a> {
central: Mutex<Option<Adapter>>,
peripheral: &'a Mutex<Option<Peripheral>>,
scan_devices: Mutex<Vec<BleDevice>>,
struct BleConnection {
_is_events_registered: Arc<Mutex<bool>>,
central: Arc<Mutex<Option<Adapter>>>,
peripheral: Arc<Mutex<Option<Peripheral>>>,
}
#[tauri::command]
async fn request_central_events<'a>(
app_handle: AppHandle,
connection: State<'a, BleConnection<'a>>,
) -> Result<bool, String> {
let central = connection.central.lock().await;
let peripheral = connection.peripheral.lock().await;
let central = central.as_ref().unwrap();
let central = central.clone();
impl BleConnection {
fn new(central: Adapter) -> Self {
Self {
_is_events_registered: Arc::new(Mutex::new(false)),
central: Arc::new(Mutex::new(Some(central))),
peripheral: Arc::new(Mutex::new(None)),
}
}
let mut event_stream = central.events().await.unwrap();
async fn set_peripheral(&self, peripheral: Option<Peripheral>) {
let mut p = self.peripheral.lock().await;
*p = peripheral;
}
tauri::async_runtime::spawn(async move {
while let Some(event) = event_stream.next().await {
if let DeviceDiscovered(_) | DeviceUpdated(_) = event.clone() {
let mut devices = vec![];
for p in central.peripherals().await.unwrap() {
devices.push(BleDevice {
name: p
.properties()
pub async fn start_scan(&self) -> Result<(), String> {
let central = self.central.lock().await;
if let Err(e) = central
.as_ref()
.unwrap()
.start_scan(ScanFilter { services: vec![] })
.await
{
return Err(e.to_string());
} else {
Ok(())
}
}
pub async fn stop_scan(&self) -> Result<(), String> {
let central = self.central.lock().await;
if let Err(e) = central.as_ref().unwrap().stop_scan().await {
return Err(e.to_string());
} else {
Ok(())
}
}
pub async fn is_connected(&self) -> bool {
let peripheral = self.peripheral.lock().await;
peripheral.is_some()
}
pub async fn connect(&self, address: BDAddr, app: &AppHandle) -> Result<(), Box<dyn Error>> {
self.stop_scan().await.unwrap();
let central = self.central.lock().await;
let peripheral = central
.as_ref()
.unwrap()
.peripheral(&PeripheralId::from(address))
.await?;
peripheral.connect().await?;
peripheral.discover_services().await?;
// 如果 peripheral.services() 不包含 0x180D 服务,则返回错误
if !peripheral
.services()
.iter()
.any(|s| s.uuid == uuid_from_u16(0x180D))
{
return Err("Peripheral does not have the required service".into());
}
self.set_peripheral(Some(peripheral)).await;
let peripheral = self.peripheral.lock().await;
let device = BleDevice {
name: peripheral
.as_ref()
.unwrap()
.properties()
.await?
.unwrap()
.local_name
.unwrap_or("Unknown".to_string()),
address: peripheral.as_ref().unwrap().address(),
rssi: peripheral
.as_ref()
.unwrap()
.properties()
.await?
.unwrap()
.rssi
.unwrap(),
};
app.emit_all("device-connected", device).unwrap();
Ok(())
}
pub async fn disconnect(&self) -> Result<(), Box<dyn Error>> {
let mut peripheral = self.peripheral.lock().await;
peripheral.as_ref().unwrap().disconnect().await?;
*peripheral = None;
Ok(())
}
pub async fn register_central_events(&self, app: &AppHandle) {
let central = self.central.lock().await;
let central_clone = central.clone(); // Clone the central variable
let app_handle = app.clone(); // Clone the AppHandle to move into the tokio::spawn closure
let mut event_stream = central.as_ref().unwrap().events().await.unwrap();
let mut self_clone = self.clone(); // Clone the BleConnection to move into the tokio::spawn closure
tokio::spawn(async move {
while let Some(event) = event_stream.next().await {
match event {
DeviceDiscovered(peripheral) | DeviceUpdated(peripheral) => {
let p = central_clone
.as_ref()
.unwrap()
.peripheral(&peripheral)
.await
.unwrap()
.unwrap()
.local_name
.unwrap_or("Unknown".to_string()),
address: p.address().to_string(),
});
}
println!("Devices: {:?}", serde_json::to_string(&devices).unwrap());
app_handle
.emit_all("scan-list-update", serde_json::to_string(&devices).unwrap())
.unwrap();
}
if let DeviceConnected(_) = event.clone() {
let peripheral = peripheral.as_ref().unwrap().clone();
app_handle
.emit_all(
"device-connected",
serde_json::to_string(&BleDevice {
name: peripheral
.unwrap();
let device = BleDevice {
name: p
.properties()
.await
.unwrap()
.unwrap()
.local_name
.unwrap_or("Unknown".to_string()),
address: peripheral.address().to_string(),
})
.unwrap(),
)
.unwrap();
address: p.address(),
rssi: p.properties().await.unwrap().unwrap().rssi.unwrap(),
};
app_handle
.emit_all("device-discovered", Some(device))
.unwrap();
}
DeviceDisconnected(_) => {
// 在这里引用 self
let mut peripheral = self_clone.peripheral.lock().await;
*peripheral = None;
}
_ => {}
}
}
});
}
// implements a Clone
pub fn clone(&self) -> Self {
Self {
_is_events_registered: self._is_events_registered.clone(),
central: self.central.clone(),
peripheral: self.peripheral.clone(),
}
});
Ok(true)
}
}
#[tauri::command]
async fn register_central_events<'a>(
app_handle: AppHandle,
connection: State<'a, BleConnection>,
) -> Result<bool, String> {
if *connection._is_events_registered.lock().await {
return Ok(false);
} else {
connection.register_central_events(&app_handle).await;
*connection._is_events_registered.lock().await = true;
Ok(true)
}
}
#[tauri::command]
async fn start_scan(connection: State<'_, BleConnection>) -> Result<bool, String> {
let central = connection.central.lock().await;
let central = central.as_ref().unwrap();
central
.start_scan(ScanFilter::default())
.await
.unwrap_or_else(|_| {
println!("Failed to start scan");
});
Ok(true)
let err = connection.start_scan().await;
if let Err(e) = err {
return Err(e.to_string());
} else {
Ok(true)
}
}
#[tauri::command]
async fn stop_scan(connection: State<'_, BleConnection>) -> Result<bool, String> {
let central = connection.central.lock().await;
let central = central.as_ref().unwrap();
central.stop_scan().await.unwrap_or_else(|_| {
println!("Failed to stop scan");
});
Ok(true)
let err = connection.stop_scan().await;
if let Err(e) = err {
return Err(e.to_string());
} else {
Ok(true)
}
}
#[tauri::command]
async fn connect(
address: String,
connection: State<'_, BleConnection>,
app_handle: AppHandle,
) -> Result<bool, String> {
let central = connection.central.lock().await;
let central = central.as_ref().unwrap();
let peripheral = central
.peripherals()
.await
.unwrap()
.into_iter()
.find(|p| p.address().to_string() == address)
.unwrap()
.clone();
peripheral.connect().await.unwrap();
*connection.peripheral.lock().await = Some(peripheral.clone());
peripheral.discover_services().await.unwrap_or_else(|_| {
println!("Failed to discover services");
});
let service = peripheral
.services()
.into_iter()
.find(|s| s.uuid == uuid_from_u16(0x180D))
.unwrap();
let characteristic = service
.characteristics
.into_iter()
.find(|c| c.uuid == uuid_from_u16(0x2A37))
.unwrap();
peripheral
.subscribe(&characteristic)
.await
.unwrap_or_else(|_| {
println!("Failed to subscribe to characteristic");
});
tokio::spawn(async move {
let mut notification_stream = peripheral.notifications().await.unwrap();
while let Some(notification) = notification_stream.next().await {
if notification.uuid == uuid_from_u16(0x2A37) {
app_handle
.emit_all("heart-rate", notification.value[1])
.unwrap();
}
}
});
Ok(true)
async fn is_connected(connection: State<'_, BleConnection>) -> Result<bool, String> {
let status = connection.is_connected().await;
Ok(status)
}
#[tauri::command]
async fn disconnect(connection: State<'_, BleConnection>) -> Result<bool, String> {
let connection = connection.peripheral.lock().await;
let connection = connection.as_ref().unwrap();
connection.disconnect().await.unwrap();
Ok(true)
}
#[tauri::command]
async fn get_connected_device(connection: State<'_, BleConnection>) -> Result<String, String> {
let connection = connection.peripheral.lock().await;
let connection = connection.as_ref().unwrap();
Ok(serde_json::to_string(&BleDevice {
name: connection
async fn get_connected_device(connection: State<'_, BleConnection>) -> Result<BleDevice, String> {
let peripheral = connection.peripheral.lock().await;
let device = BleDevice {
name: peripheral
.as_ref()
.unwrap()
.properties()
.await
.unwrap()
.unwrap()
.local_name
.unwrap_or("Unknown".to_string()),
address: connection.address().to_string(),
})
.unwrap())
address: peripheral.as_ref().unwrap().address(),
rssi: peripheral
.as_ref()
.unwrap()
.properties()
.await
.unwrap()
.unwrap()
.rssi
.unwrap(),
};
Ok(device)
}
#[tauri::command]
async fn connect(
address: BDAddr,
connection: State<'_, BleConnection>,
app_handle: AppHandle,
) -> Result<bool, String> {
if let Err(e) = connection.connect(address, &app_handle).await {
Err(e.to_string())
} else {
Ok(true)
}
}
#[tauri::command]
async fn disconnect(connection: State<'_, BleConnection>) -> Result<bool, String> {
if let Err(e) = connection.disconnect().await {
Err(e.to_string())
} else {
Ok(true)
}
}
#[tokio::main]
@@ -197,19 +271,16 @@ async fn main() {
.await
.unwrap()
.into_iter()
.next()
.nth(0)
.unwrap();
tauri::Builder::default()
.manage(BleConnection {
central: Mutex::new(Some(central)),
peripheral: Default::default(),
scan_devices: Default::default(),
})
.manage(BleConnection::new(central))
.invoke_handler(tauri::generate_handler![
request_central_events,
register_central_events,
start_scan,
stop_scan,
is_connected,
connect,
disconnect,
get_connected_device