From a498e4edeaf75ac5c86a508b548b68b1c89cbac5 Mon Sep 17 00:00:00 2001 From: lambda Date: Tue, 4 Nov 2025 00:29:32 +0300 Subject: [PATCH] Save ble adapter in state - start to use state - move to Tauri windows - remove old js --- src-tauri/src/ble/scanner.rs | 15 ++++---- src-tauri/src/device_repo/error.rs | 14 -------- src-tauri/src/device_repo/mod.rs | 3 +- src-tauri/src/device_repo/repo.rs | 21 +---------- src-tauri/src/lib.rs | 45 ++++++++++++----------- src-tauri/src/state.rs | 6 ++++ src/connect.html | 18 ++++++++++ src/connect.js | 7 ++++ src/init.js | 7 ++++ src/main.js | 54 +--------------------------- src/splashscreen.html | 18 ++++++++++ src/state.js | 58 ------------------------------ src/ui.js | 32 ----------------- 13 files changed, 89 insertions(+), 209 deletions(-) delete mode 100644 src-tauri/src/device_repo/error.rs create mode 100644 src-tauri/src/state.rs create mode 100644 src/connect.html create mode 100644 src/connect.js create mode 100644 src/init.js create mode 100644 src/splashscreen.html delete mode 100644 src/state.js delete mode 100644 src/ui.js diff --git a/src-tauri/src/ble/scanner.rs b/src-tauri/src/ble/scanner.rs index c40d474..c613da9 100644 --- a/src-tauri/src/ble/scanner.rs +++ b/src-tauri/src/ble/scanner.rs @@ -1,5 +1,5 @@ -use btleplug::api::{Central, Manager as _, Peripheral, ScanFilter, bleuuid::uuid_from_u16}; -use btleplug::platform::Manager; +use btleplug::api::{Central, Peripheral, ScanFilter, bleuuid::uuid_from_u16}; +use btleplug::platform::Adapter; use std::time::Duration; use tokio::time; use crate::ble::error::BleError; @@ -16,18 +16,15 @@ static TARGET_UUIDS: &[Uuid] = &[ uuid_from_u16(0x1818), // Power meter ]; -pub async fn scan_devices(scan_seconds: u64) -> Result, BleError> { - let manager = Manager::new().await?; - let adapters = manager.adapters().await?; - let central = adapters.into_iter().next().ok_or(BleError::SystemBleError)?; +pub async fn scan_devices(adapter: &Adapter, scan_seconds: u64) -> Result, BleError> { let target_vec: Vec = TARGET_UUIDS.to_vec(); - central.start_scan(ScanFilter { services: target_vec.clone() }).await?; + adapter.start_scan(ScanFilter { services: target_vec.clone() }).await?; time::sleep(Duration::from_secs(scan_seconds)).await; let mut devices = Vec::new(); - for peripheral in central.peripherals().await? { + for peripheral in adapter.peripherals().await? { if let Some(props) = peripheral.properties().await? { let svc_match = props.services.iter().any(|s| TARGET_UUIDS.iter().any(|t| t == s)); if !svc_match { continue; } @@ -75,7 +72,7 @@ pub async fn scan_devices(scan_seconds: u64) -> Result, BleError> { } } - central.stop_scan().await.ok(); + adapter.stop_scan().await.ok(); println!("{:?}", devices); Ok(devices) } diff --git a/src-tauri/src/device_repo/error.rs b/src-tauri/src/device_repo/error.rs deleted file mode 100644 index 00f8aa2..0000000 --- a/src-tauri/src/device_repo/error.rs +++ /dev/null @@ -1,14 +0,0 @@ -#[derive(Debug)] -pub enum RepoError { - Creation, - Empty, - Serialization -} - -impl From for RepoError { - fn from(_: std::io::Error) -> Self { RepoError::Creation } -} - -impl From for RepoError { - fn from(_: serde_json::Error) -> Self { RepoError::Serialization } -} diff --git a/src-tauri/src/device_repo/mod.rs b/src-tauri/src/device_repo/mod.rs index af7aa42..66d6016 100644 --- a/src-tauri/src/device_repo/mod.rs +++ b/src-tauri/src/device_repo/mod.rs @@ -1,4 +1,3 @@ mod repo; -mod error; -pub use repo::{try_from_file, try_write, repo_from_devices, Repo}; +pub use repo::repo_from_devices; diff --git a/src-tauri/src/device_repo/repo.rs b/src-tauri/src/device_repo/repo.rs index 9a1cbce..1c6848f 100644 --- a/src-tauri/src/device_repo/repo.rs +++ b/src-tauri/src/device_repo/repo.rs @@ -1,6 +1,5 @@ -use std::{fs::{read_to_string, write}}; use serde::{Deserialize, Serialize}; -use crate::{device::{device::{get_device_types, DeviceType}, Device}, device_repo::error::RepoError}; +use crate::{device::{device::{get_device_types, DeviceType}, Device}}; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Repo { @@ -22,24 +21,6 @@ impl Repo { } } -const STATIC_PATH: &str = "devices.json"; - -pub fn try_write(repo: &Repo) -> Result { - let json = serde_json::to_string_pretty(repo)?; - write(STATIC_PATH, json)?; - Ok(repo.clone()) -} - -pub fn try_from_file() -> Result { - let contents = match read_to_string(STATIC_PATH) { - Ok(s) if !s.trim().is_empty() => s, - _ => return Err(RepoError::Empty), - }; - - let repo: Repo = serde_json::from_str(&contents)?; - Ok(repo) -} - pub fn repo_from_devices(devices: Vec) -> Repo { let mut repo = Repo::new(); diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 50ea820..74d7a09 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,44 +1,47 @@ mod device; mod ble; mod device_repo; +mod state; -use tauri::{AppHandle, Manager}; - -use crate::{ble::scan_devices, device_repo::{repo_from_devices, try_from_file, Repo}}; - -// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ -#[tauri::command] -async fn read_repo() -> Repo { - match try_from_file() { - Ok(repo) => repo, - Err(_) => Repo::new() - } -} - -#[tauri::command] -async fn scan() -> Repo { - println!("kek"); - repo_from_devices((scan_devices(5).await).unwrap_or_default()) -} +use std::sync::Arc; +use tauri::{AppHandle, Emitter, Manager, async_runtime}; +use crate::{ble::scan_devices, device_repo::{repo_from_devices}}; +use crate::state::State; +use btleplug::api::{Manager as ApiManager}; +use btleplug::platform::Manager as BleManager; #[tauri::command] async fn init(app: AppHandle) -> Result<(),()> { println!("init rideinn app"); let splash = app.get_webview_window("splashscreen").unwrap(); let connect = app.get_webview_window("connect").unwrap(); - let repo = repo_from_devices((scan_devices(5).await).unwrap_or_default()); + let state = app.state::(); + let adapter = state.adapter.clone(); + let repo = repo_from_devices((scan_devices(&adapter, 5).await).unwrap_or_default()); splash.close().unwrap(); connect.show().unwrap(); + app.emit_to("connect", "devices-loaded", repo).unwrap(); + Ok(()) } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() - - .invoke_handler(tauri::generate_handler![init, read_repo, scan]) + .setup(|app| { + async_runtime::block_on(async move { + let manager = BleManager::new().await?; + let adapters = manager.adapters().await?; + let adapter = adapters.into_iter().next() + .ok_or_else(|| Box::::from("No BLE adapter found"))?; + + app.manage(State { adapter: Arc::new(adapter) } ); + Ok::<(), Box>(()) + }) + }) + .invoke_handler(tauri::generate_handler![init]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/src-tauri/src/state.rs b/src-tauri/src/state.rs new file mode 100644 index 0000000..026bb9f --- /dev/null +++ b/src-tauri/src/state.rs @@ -0,0 +1,6 @@ +use btleplug::platform::Adapter; +use std::sync::Arc; + +pub struct State { + pub adapter: Arc +} diff --git a/src/connect.html b/src/connect.html new file mode 100644 index 0000000..b0c5389 --- /dev/null +++ b/src/connect.html @@ -0,0 +1,18 @@ + + + + + + + Tauri App + + + + +
+

Connect

+
+
+
+ + diff --git a/src/connect.js b/src/connect.js new file mode 100644 index 0000000..d63a8e0 --- /dev/null +++ b/src/connect.js @@ -0,0 +1,7 @@ +const { once } = window.__TAURI__.event; +const { getCurrentWebviewWindow } = window.__TAURI__.webviewWindow; + +const appWebview = getCurrentWebviewWindow(); +appWebview.once("devices-loaded", (devices) => { + console.log("Devices are loaded", devices); +}); diff --git a/src/init.js b/src/init.js new file mode 100644 index 0000000..f00f754 --- /dev/null +++ b/src/init.js @@ -0,0 +1,7 @@ +const { invoke } = window.__TAURI__.core; + +async function init() { + return await invoke("init"); +} + +window.addEventListener("DOMContentLoaded", init); diff --git a/src/main.js b/src/main.js index 8b231f2..906574e 100644 --- a/src/main.js +++ b/src/main.js @@ -1,53 +1 @@ -import { EVENTS } from "./state.js"; -import { fsm } from "./ui.js"; -const { invoke } = window.__TAURI__.core; - -async function readRepo() { - return await invoke("read_repo"); -} - -async function scan() { - return await invoke("scan"); -} - -const isArray = (maybeArray) => - maybeArray?.constructor && maybeArray?.constructor === Array; - -const isEmptyArray = (maybeArray) => - isArray(maybeArray) && maybeArray.length === 0; - -const getClosestDevices = (repo) => { - const sortByRssi = (d1, d2) => d1.rssi > d2.rssi; - - const cadSensors = repo.cad_sensors.sort(sortByRssi); - const hrSensors = repo.hr_sensors.sort(sortByRssi); - const trainers = repo.trainers.sort(sortByRssi); - - return [hrSensors[0], cadSensors[0], trainers[0]]; -}; - -const isRepoEmpty = (repo) => - (!repo.cad_sensors || isEmptyArray(repo.cad_sensors)) && - (!repo.hr_sensors || isEmptyArray(repo.hr_sensors)) && - (!repo.trainers || isEmptyArray(repo.trainers)); - -window.addEventListener("DOMContentLoaded", async () => { - console.log("hello from main js"); - const [savedDevices, scannedDevices] = await Promise.all([ - readRepo(), - scan(), - ]); - - console.log(savedDevices, scannedDevices); - - if (isRepoEmpty(savedDevices)) { - const closest = getClosestDevices(scannedDevices); - if (closest.length === 0) fsm.dispatch(EVENTS.TO_ERROR); - } else { - if (isRepoEmpty(scannedDevices)) { - fsm.dispatch(EVENTS.TO_MANUAL_CONNECT); - } else { - fsm.dispatch(EVENTS.TO_READY); - } - } -}); +console.log("hello from main.js"); diff --git a/src/splashscreen.html b/src/splashscreen.html new file mode 100644 index 0000000..0e5f935 --- /dev/null +++ b/src/splashscreen.html @@ -0,0 +1,18 @@ + + + + + + + Tauri App + + + + +
+

SplashScreen

+
+
+
+ + diff --git a/src/state.js b/src/state.js deleted file mode 100644 index 3235b55..0000000 --- a/src/state.js +++ /dev/null @@ -1,58 +0,0 @@ -const STATES = Object.freeze({ - INITIAL: "initial", - SEARCH: "search", - MANUAL_CONNECT: "manualConnect", - READY_TO_ROLL: "ready", - ERROR: "error", -}); - -const EVENTS = Object.freeze({ - TO_SEARCH: "toSearch", - TO_MANUAL_CONNECT: "toManualConnect", - TO_READY: "toReady", - TO_ERROR: "toError", -}); - -const transitions = new Map([ - [[STATES.INITIAL, EVENTS.TO_SEARCH], STATES.SEARCH], - [[STATES.INITIAL, EVENTS.TO_ERROR], STATES.ERROR], - - [[STATES.SEARCH, EVENTS.TO_MANUAL_CONNECT], STATES.MANUAL_CONNECT], - [[STATES.SEARCH, EVENTS.TO_READY], STATES.READY_TO_ROLL], - - [[STATES.MANUAL_CONNECT, EVENTS.TO_READY], STATES.READY_TO_ROLL], - [[STATES.MANUAL_CONNECT, EVENTS.TO_ERROR], STATES.ERROR], -]); - -function createFSM(initialState = STATES.INITIAL) { - const fsm = new EventTarget(); - let state = initialState; - - fsm.getState = () => state; - - fsm.dispatch = (eventName) => { - const key = JSON.stringify([state, eventName]); - const next = [...transitions.entries()].find( - ([k]) => JSON.stringify(k) === key, - )?.[1]; - - if (next) { - const prev = state; - state = next; - - fsm.dispatchEvent( - new CustomEvent("statechange", { - detail: { prev, next, trigger: eventName }, - }), - ); - - return true; - } - console.warn(`No transition from "${state}" on "${eventName}"`); - return false; - }; - - return fsm; -} - -export { createFSM, STATES, EVENTS }; diff --git a/src/ui.js b/src/ui.js deleted file mode 100644 index 7be32bd..0000000 --- a/src/ui.js +++ /dev/null @@ -1,32 +0,0 @@ -import { createFSM, STATES } from "./state.js"; - -const fsm = createFSM(); -const container = document.getElementById("app"); - -const pages = { - [STATES.INITIAL]: ` -

INITIAL

- `, - [STATES.SEARCH]: ` -

SEARCH

- `, - [STATES.MANUAL_CONNECT]: ` -

MANUAL CONNECT

- `, - [STATES.ERROR]: ` -

ERROR

- `, -}; - -function render(state) { - container.innerHTML = pages[state] ?? "

Unknown state

"; -} - -fsm.addEventListener("statechange", (e) => { - const { next } = e.detail; - render(next); -}); - -render(fsm.getState()); - -export { fsm };