From b126a3105fdb9e01dd19722564725e2f9b72560b Mon Sep 17 00:00:00 2001 From: Vixalie Date: Mon, 3 Mar 2025 09:53:58 +0800 Subject: [PATCH] refactor to global static variables to store application states. --- src-tauri/src/bluetooth.rs | 72 +++++++++--------- src-tauri/src/cmd/mod.rs | 71 +++++++++--------- src-tauri/src/cmd/state.rs | 81 +++++++++++++++++++- src-tauri/src/errors.rs | 2 + src-tauri/src/state.rs | 141 ++++++++++++++++++++--------------- src/context/EstimContext.tsx | 4 +- 6 files changed, 236 insertions(+), 135 deletions(-) diff --git a/src-tauri/src/bluetooth.rs b/src-tauri/src/bluetooth.rs index ee7af03..1227ed0 100644 --- a/src-tauri/src/bluetooth.rs +++ b/src-tauri/src/bluetooth.rs @@ -1,52 +1,60 @@ -use std::sync::Arc; +use std::{collections::HashSet, sync::Arc}; use btleplug::{ api::{Central, CentralState, Manager as _, Peripheral, ScanFilter}, - platform::{Adapter, Manager}, + platform::{Adapter, Manager, PeripheralId}, }; use futures::StreamExt; use tauri::{ - async_runtime::{self, JoinHandle, RwLock}, - AppHandle, Emitter, State, + async_runtime::{self, JoinHandle}, + AppHandle, Emitter, }; -use crate::{cmd::PeripheralItem, errors, state::AppState}; +use crate::{cmd::PeripheralItem, errors, state}; + +async fn notify_peripheral_update(app_handle: Arc, peirpheral: &PeripheralId) { + let adapter = state::get_central_adapter().await; + if let Some(adapter) = adapter { + let peripheral = adapter.peripheral(peirpheral).await; + if let Ok(peripheral) = peripheral { + let properties = peripheral.properties().await.ok().flatten(); + let services = state::get_found_setvices(peirpheral).await; + let item = + PeripheralItem::new_with_cache(&peripheral, properties.as_ref(), services).await; + app_handle.emit("peripheral_updated", item).unwrap(); + } + } +} pub async fn handle_bluetooth_events( app_handle: Arc, - app_state: Arc>, ) -> Result, errors::AppError> { - let state = app_state.read().await; - let adapter = state.get_central_adapter().await; + let adapter = state::get_central_adapter().await; let app = Arc::clone(&app_handle); if let Some(adapter) = adapter { let adapter = adapter.clone(); - let app_state = Arc::clone(&app_state); Ok(async_runtime::spawn(async move { let mut event_stream = adapter.events().await.unwrap(); while let Some(event) = event_stream.next().await { match event { btleplug::api::CentralEvent::DeviceDiscovered(id) | btleplug::api::CentralEvent::DeviceUpdated(id) => { - let peripheral = adapter.peripheral(&id).await; - if let Ok(peripheral) = peripheral { - let properties = peripheral.properties().await.ok().flatten(); - let state = app_state.write().await; - state.update_found_peripherals(id.clone()).await; - let item = PeripheralItem::new(&peripheral, properties.as_ref()).await; - app.emit("peripheral_found", item).unwrap(); - } + notify_peripheral_update(Arc::clone(&app_handle), &id).await; + state::update_found_peripherals(id.clone()).await; } btleplug::api::CentralEvent::DeviceConnected(id) => { - let state = app_state.write().await; - state.set_connected_peripheral(id).await; + state::set_connected_peripheral(id).await; app.emit("peripheral_connected", ()).unwrap(); } btleplug::api::CentralEvent::DeviceDisconnected(_id) => { - let state = app_state.write().await; - state.clear_connected_peripheral().await; + state::clear_connected_peripheral().await; app.emit("peripheral_disconnected", ()).unwrap(); } + btleplug::api::CentralEvent::ServicesAdvertisement { id, services } => { + let services = HashSet::from_iter(services); + state::update_found_services(id.clone(), services).await; + notify_peripheral_update(Arc::clone(&app_handle), &id).await; + } _ => {} } } @@ -80,19 +88,15 @@ pub async fn get_central_adapter() -> Result { found_adapter.ok_or(errors::AppError::NoAvailableBluetoothAdapter) } -pub async fn start_scan( - app_handle: Arc, - app_state: State<'_, Arc>>, -) -> Result<(), errors::AppError> { - let state = app_state.read().await; - let adapter = state.get_central_adapter().await; +pub async fn start_scan(app_handle: Arc) -> Result<(), errors::AppError> { + let adapter = state::get_central_adapter().await; if let Some(adapter) = adapter { adapter .start_scan(ScanFilter::default()) .await .map_err(|_| errors::AppError::UnableToStartScan)?; - state.set_scanning(true).await; - state.clear_found_peripherals().await; + state::set_scanning(true).await; + state::clear_found_peripherals().await; app_handle.emit("scanning_started", ()).unwrap(); Ok(()) } else { @@ -100,18 +104,14 @@ pub async fn start_scan( } } -pub async fn stop_scan( - app_handle: Arc, - app_state: State<'_, Arc>>, -) -> Result<(), errors::AppError> { - let state = app_state.read().await; - let adapter = state.get_central_adapter().await; +pub async fn stop_scan(app_handle: Arc) -> Result<(), errors::AppError> { + let adapter = state::get_central_adapter().await; if let Some(adapter) = adapter { adapter .stop_scan() .await .map_err(|_| errors::AppError::UnableToStopScan)?; - state.set_scanning(false).await; + state::set_scanning(false).await; app_handle.emit("scanning_stopped", ()).unwrap(); Ok(()) } else { diff --git a/src-tauri/src/cmd/mod.rs b/src-tauri/src/cmd/mod.rs index 6012e1c..12f1b8b 100644 --- a/src-tauri/src/cmd/mod.rs +++ b/src-tauri/src/cmd/mod.rs @@ -7,7 +7,10 @@ use btleplug::{ pub use state::{CentralState, ChannelState, PeripheralItem}; use tauri::{async_runtime::RwLock, AppHandle, Emitter, State}; -use crate::{bluetooth, errors, state::AppState}; +use crate::{ + bluetooth, errors, + state::{self as global, AppState}, +}; mod state; @@ -20,11 +23,10 @@ pub async fn activate_central_adapter( let adapter = bluetooth::get_central_adapter().await?; { let state = app_state.read().await; - state.set_central_adapter(adapter).await; + global::set_central_adapter(adapter).await; state.clear_central_event_handler().await; } - let handle = - bluetooth::handle_bluetooth_events(Arc::clone(&app), Arc::clone(&app_state)).await?; + let handle = bluetooth::handle_bluetooth_events(Arc::clone(&app)).await?; { let state = app_state.write().await; state.set_central_event_handler(handle).await; @@ -34,11 +36,8 @@ pub async fn activate_central_adapter( } #[tauri::command] -pub async fn central_device_state( - app_state: State<'_, Arc>>, -) -> Result { - let state = app_state.read().await; - let central = state.get_central_adapter().await; +pub async fn central_device_state() -> Result { + let central = global::get_central_adapter().await; if let Some(central) = central { let central_device_state = central .adapter_state() @@ -46,8 +45,8 @@ pub async fn central_device_state( .map_err(|_| errors::AppError::BluetoothNotReady)?; Ok(CentralState { is_ready: central_device_state == btleplug::api::CentralState::PoweredOn, - is_scanning: *state.scanning.lock().await, - connected: state.get_connected_peripheral().await, + is_scanning: global::get_scanning().await, + connected: global::get_connected_peripheral().await, }) } else { Ok(CentralState::default()) @@ -55,18 +54,12 @@ pub async fn central_device_state( } #[tauri::command] -pub async fn connected_peripheral_state( - app_state: State<'_, Arc>>, -) -> Result, errors::AppError> { - let state = app_state.read().await; - let connected_peripheral = state - .connected_peripheral - .lock() +pub async fn connected_peripheral_state() -> Result, errors::AppError> { + let connected_peripheral = global::get_connected_peripheral() .await .clone() .ok_or(errors::AppError::NoConnectedPeripheral)?; - let central = state - .get_central_adapter() + let central = global::get_central_adapter() .await .ok_or(errors::AppError::BluetoothAdapterNotFound)?; if let Ok(peripheral) = central.peripheral(&connected_peripheral).await { @@ -74,22 +67,23 @@ pub async fn connected_peripheral_state( .properties() .await .map_err(|_| errors::AppError::UnableToRetrievePeripheralProperties)?; + peripheral + .discover_services() + .await + .map_err(|_| errors::AppError::UnableToRetrievePeripheralServices)?; Ok(Some( - PeripheralItem::new(&peripheral, properties.as_ref()).await, + PeripheralItem::new_form_peripheral(&peripheral, properties.as_ref()).await, )) } else { - Ok(None) + Err(errors::AppError::NoConnectedPeripheral) } } #[tauri::command] pub async fn specific_peripheral_state( - app_state: State<'_, Arc>>, id: PeripheralId, ) -> Result { - let state = app_state.read().await; - let central = state - .get_central_adapter() + let central = global::get_central_adapter() .await .ok_or(errors::AppError::BluetoothAdapterNotFound)?; let peripheral = central @@ -97,7 +91,16 @@ pub async fn specific_peripheral_state( .await .map_err(|_| errors::AppError::UnableToRetrievePeripheralState)?; let properties = peripheral.properties().await.unwrap_or(None); - Ok(PeripheralItem::new(&peripheral, properties.as_ref()).await) + let peripheral_item = if peripheral.connect().await.is_ok() { + peripheral.discover_services().await.unwrap_or_default(); + let item = PeripheralItem::new_form_peripheral(&peripheral, properties.as_ref()).await; + peripheral.disconnect().await.unwrap(); + item + } else { + let services = global::get_found_setvices(&id).await; + PeripheralItem::new_with_cache(&peripheral, properties.as_ref(), services).await + }; + Ok(peripheral_item) } #[tauri::command] @@ -115,19 +118,13 @@ pub async fn channel_b_state( } #[tauri::command] -pub async fn start_scan_devices( - app_handle: AppHandle, - app_state: State<'_, Arc>>, -) -> Result<(), errors::AppError> { - bluetooth::start_scan(Arc::new(app_handle), app_state).await?; +pub async fn start_scan_devices(app_handle: AppHandle) -> Result<(), errors::AppError> { + bluetooth::start_scan(Arc::new(app_handle)).await?; Ok(()) } #[tauri::command] -pub async fn stop_scan_devices( - app_handle: AppHandle, - app_state: State<'_, Arc>>, -) -> Result<(), errors::AppError> { - bluetooth::stop_scan(Arc::new(app_handle), app_state).await?; +pub async fn stop_scan_devices(app_handle: AppHandle) -> Result<(), errors::AppError> { + bluetooth::stop_scan(Arc::new(app_handle)).await?; Ok(()) } diff --git a/src-tauri/src/cmd/state.rs b/src-tauri/src/cmd/state.rs index 373bd19..09cfa6e 100644 --- a/src-tauri/src/cmd/state.rs +++ b/src-tauri/src/cmd/state.rs @@ -1,8 +1,11 @@ +use std::{collections::HashSet, hash::Hash}; + use btleplug::{ api::{Peripheral as _, PeripheralProperties}, platform::{Peripheral, PeripheralId}, }; use serde::Serialize; +use uuid::Uuid; use crate::playlist::PlayMode; @@ -24,6 +27,14 @@ pub struct PeripheralItem { pub is_connected: bool, pub rssi: Option, pub battery: Option, + pub services: HashSet, +} + +#[derive(Debug, Clone, Default, Eq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PeripheralService { + pub uuid: Uuid, + pub characteristics: HashSet, } #[derive(Debug, Clone, Default, Serialize)] @@ -38,8 +49,24 @@ pub struct ChannelState { pub play_mode: PlayMode, } +impl Hash for PeripheralService { + fn hash(&self, state: &mut H) { + self.uuid.hash(state); + } +} + +impl PartialEq for PeripheralService { + fn eq(&self, other: &Self) -> bool { + self.uuid == other.uuid + } +} + impl PeripheralItem { - pub async fn new(periperial: &Peripheral, properties: Option<&PeripheralProperties>) -> Self { + pub async fn new( + periperial: &Peripheral, + properties: Option<&PeripheralProperties>, + services: HashSet, + ) -> Self { Self { id: periperial.id(), address: periperial.address().to_string(), @@ -48,6 +75,58 @@ impl PeripheralItem { is_connected: periperial.is_connected().await.unwrap_or(false), rssi: properties.and_then(|p| p.rssi), battery: properties.and_then(|p| p.tx_power_level), + services, + } + } + + pub async fn new_with_cache( + periperial: &Peripheral, + properties: Option<&PeripheralProperties>, + services: HashSet, + ) -> Self { + let service = services + .into_iter() + .map(|uuid| PeripheralService { + uuid, + ..Default::default() + }) + .collect(); + Self { + id: periperial.id(), + address: periperial.address().to_string(), + represent: properties.and_then(|p| p.local_name.clone()), + is_unknown: properties.and_then(|p| p.local_name.clone()).is_none(), + is_connected: periperial.is_connected().await.unwrap_or(false), + rssi: properties.and_then(|p| p.rssi), + battery: properties.and_then(|p| p.tx_power_level), + services: service, + } + } + + pub async fn new_form_peripheral( + periperial: &Peripheral, + properties: Option<&PeripheralProperties>, + ) -> Self { + let services = periperial + .services() + .iter() + .map(|s| { + let characteristics = s.characteristics.iter().map(|c| c.uuid).collect(); + PeripheralService { + uuid: s.uuid, + characteristics, + } + }) + .collect(); + Self { + id: periperial.id(), + address: periperial.address().to_string(), + represent: properties.and_then(|p| p.local_name.clone()), + is_unknown: properties.and_then(|p| p.local_name.clone()).is_none(), + is_connected: periperial.is_connected().await.unwrap_or(false), + rssi: properties.and_then(|p| p.rssi), + battery: properties.and_then(|p| p.tx_power_level), + services, } } } diff --git a/src-tauri/src/errors.rs b/src-tauri/src/errors.rs index eed7a58..e4e49b5 100644 --- a/src-tauri/src/errors.rs +++ b/src-tauri/src/errors.rs @@ -21,4 +21,6 @@ pub enum AppError { UnableToRetrievePeripheralProperties, #[error("Unable to retrieve peripheral state")] UnableToRetrievePeripheralState, + #[error("Unable to retrieve peripheral services")] + UnableToRetrievePeripheralServices, } diff --git a/src-tauri/src/state.rs b/src-tauri/src/state.rs index 4abe5fe..75c1049 100644 --- a/src-tauri/src/state.rs +++ b/src-tauri/src/state.rs @@ -1,54 +1,45 @@ -use std::{collections::HashSet, sync::Arc}; +use std::{ + collections::{HashMap, HashSet}, + sync::{Arc, LazyLock}, +}; use btleplug::platform::{Adapter, PeripheralId}; use tauri::{ async_runtime::{JoinHandle, Mutex, RwLock}, AppHandle, Runtime, }; +use uuid::Uuid; use crate::config_db; pub struct AppState { pub db: Arc>, - pub central_adapter: Arc>>, pub central_event_handler: Arc>>>, - pub found_peripherals: Arc>>, - pub scanning: Arc>, - pub connected_peripheral: Arc>>, } unsafe impl Send for AppState {} unsafe impl Sync for AppState {} +static CENTRAL_ADAPTER: LazyLock>>> = + LazyLock::new(|| Arc::new(RwLock::new(None))); +static CACHED_SERVICES: LazyLock>>>> = + LazyLock::new(|| Arc::new(RwLock::new(HashMap::new()))); +static FOUND_PERIPHERALS: LazyLock>>> = + LazyLock::new(|| Arc::new(RwLock::new(HashSet::new()))); +static SCANNING: LazyLock>> = LazyLock::new(|| Arc::new(Mutex::new(false))); +static CONNECTED_PERIPHERAL: LazyLock>>> = + LazyLock::new(|| Arc::new(Mutex::new(None))); + impl AppState { pub async fn new(app: &AppHandle) -> anyhow::Result { let db = config_db::open_config_db(app)?; Ok(Self { db: Arc::new(Mutex::new(db)), - central_adapter: Arc::new(RwLock::new(None)), central_event_handler: Arc::new(Mutex::new(None)), - found_peripherals: Arc::new(RwLock::new(HashSet::new())), - scanning: Arc::new(Mutex::new(false)), - connected_peripheral: Arc::new(Mutex::new(None)), }) } - pub async fn set_central_adapter(&self, adapter: Adapter) { - let mut central_adapter = self.central_adapter.write().await; - *central_adapter = Some(adapter); - } - - pub async fn clear_central_adapter(&self) { - let mut central_adapter = self.central_adapter.write().await; - *central_adapter = None; - } - - pub async fn get_central_adapter(&self) -> Option { - let central_adapter = self.central_adapter.read().await; - central_adapter.clone() - } - pub async fn set_central_event_handler(&self, handler: JoinHandle<()>) { let mut central_event_handler = self.central_event_handler.lock().await; *central_event_handler = Some(handler); @@ -60,39 +51,71 @@ impl AppState { handler.abort(); } } - - pub async fn get_found_peripherals(&self) -> HashSet { - let found_peripherals = self.found_peripherals.read().await; - found_peripherals.clone() - } - - pub async fn update_found_peripherals(&self, peripheral: PeripheralId) { - let mut found_peripherals = self.found_peripherals.write().await; - found_peripherals.insert(peripheral); - } - - pub async fn clear_found_peripherals(&self) { - let mut found_peripherals = self.found_peripherals.write().await; - found_peripherals.clear(); - } - - pub async fn set_scanning(&self, scanning_state: bool) { - let mut scanning = self.scanning.lock().await; - *scanning = scanning_state; - } - - pub async fn set_connected_peripheral(&self, peripheral: PeripheralId) { - let mut connected_peripheral = self.connected_peripheral.lock().await; - *connected_peripheral = Some(peripheral); - } - - pub async fn clear_connected_peripheral(&self) { - let mut connected_peripheral = self.connected_peripheral.lock().await; - *connected_peripheral = None; - } - - pub async fn get_connected_peripheral(&self) -> Option { - let connected_peripheral = self.connected_peripheral.lock().await; - connected_peripheral.clone() - } +} + +pub async fn get_central_adapter() -> Option { + let central_adapter = CENTRAL_ADAPTER.read().await; + central_adapter.clone() +} + +pub async fn set_central_adapter(adapter: Adapter) { + let mut central_adapter = CENTRAL_ADAPTER.write().await; + *central_adapter = Some(adapter); +} + +pub async fn cleaar_central_adapter() { + let mut central_adapter = CENTRAL_ADAPTER.write().await; + *central_adapter = None; +} + +pub async fn get_found_peripherals() -> HashSet { + let found_peripherals = FOUND_PERIPHERALS.read().await; + found_peripherals.clone() +} + +pub async fn update_found_peripherals(peripheral: PeripheralId) { + let mut found_peripherals = FOUND_PERIPHERALS.write().await; + found_peripherals.insert(peripheral); +} + +pub async fn clear_found_peripherals() { + let mut found_peripherals = FOUND_PERIPHERALS.write().await; + found_peripherals.clear(); + let mut cached_services = CACHED_SERVICES.write().await; + cached_services.clear(); +} + +pub async fn get_found_setvices(peripheral: &PeripheralId) -> HashSet { + let cached_services = CACHED_SERVICES.read().await; + cached_services.get(peripheral).cloned().unwrap_or_default() +} + +pub async fn update_found_services(peripheral: PeripheralId, services: HashSet) { + let mut cached_services = CACHED_SERVICES.write().await; + cached_services.insert(peripheral, services); +} + +pub async fn get_scanning() -> bool { + let scanning = SCANNING.lock().await; + *scanning +} + +pub async fn set_scanning(scanning: bool) { + let mut scanning_state = SCANNING.lock().await; + *scanning_state = scanning; +} + +pub async fn get_connected_peripheral() -> Option { + let connected_peripheral = CONNECTED_PERIPHERAL.lock().await; + connected_peripheral.clone() +} + +pub async fn set_connected_peripheral(peripheral: PeripheralId) { + let mut connected_peripheral = CONNECTED_PERIPHERAL.lock().await; + *connected_peripheral = Some(peripheral); +} + +pub async fn clear_connected_peripheral() { + let mut connected_peripheral = CONNECTED_PERIPHERAL.lock().await; + *connected_peripheral = None; } diff --git a/src/context/EstimContext.tsx b/src/context/EstimContext.tsx index 1d7b86e..2c83d55 100644 --- a/src/context/EstimContext.tsx +++ b/src/context/EstimContext.tsx @@ -23,6 +23,7 @@ type PeripheralItem = { isConnected: boolean; rssi: number | null; battery: number | null; + services: { uuid: string; characteristics: string[] }[]; }; type BluetoothState = { ready: boolean | null; @@ -84,7 +85,6 @@ export const AvailablePeripherals = atom((get) => { }); export const UnknownPeripherals = atom((get) => { const peripherals = get(Peripherals); - console.debug('[origin peripherals]', peripherals); return peripherals.filter((i) => i.isUnknown); }); const Channels: Record> = { @@ -158,7 +158,7 @@ const EstimWatchProvider: FC<{ children?: ReactNode }> = ({ children }) => { try { unlistenBle.current = await listen('central_state_updated', () => refreshBle()); unlistenDevice.current = await listen('peripheral_connected', () => refreshDevice()); - unlistenPreipherals.current = await listen('peripheral_found', (event) => { + unlistenPreipherals.current = await listen('peripheral_updated', (event) => { refreshPeripherals(event.payload); }); unlistenChannelA.current = await listen('channel_a_updated', () => refreshChannelA());