parent
17c1aafd5e
commit
a498e4edea
@ -1,14 +0,0 @@
|
|||||||
#[derive(Debug)]
|
|
||||||
pub enum RepoError {
|
|
||||||
Creation,
|
|
||||||
Empty,
|
|
||||||
Serialization
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<std::io::Error> for RepoError {
|
|
||||||
fn from(_: std::io::Error) -> Self { RepoError::Creation }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<serde_json::Error> for RepoError {
|
|
||||||
fn from(_: serde_json::Error) -> Self { RepoError::Serialization }
|
|
||||||
}
|
|
||||||
@ -1,4 +1,3 @@
|
|||||||
mod repo;
|
mod repo;
|
||||||
mod error;
|
|
||||||
|
|
||||||
pub use repo::{try_from_file, try_write, repo_from_devices, Repo};
|
pub use repo::repo_from_devices;
|
||||||
|
|||||||
@ -1,44 +1,47 @@
|
|||||||
mod device;
|
mod device;
|
||||||
mod ble;
|
mod ble;
|
||||||
mod device_repo;
|
mod device_repo;
|
||||||
|
mod state;
|
||||||
|
|
||||||
use tauri::{AppHandle, Manager};
|
use std::sync::Arc;
|
||||||
|
use tauri::{AppHandle, Emitter, Manager, async_runtime};
|
||||||
use crate::{ble::scan_devices, device_repo::{repo_from_devices, try_from_file, Repo}};
|
use crate::{ble::scan_devices, device_repo::{repo_from_devices}};
|
||||||
|
use crate::state::State;
|
||||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
use btleplug::api::{Manager as ApiManager};
|
||||||
#[tauri::command]
|
use btleplug::platform::Manager as BleManager;
|
||||||
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())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn init(app: AppHandle) -> Result<(),()> {
|
async fn init(app: AppHandle) -> Result<(),()> {
|
||||||
println!("init rideinn app");
|
println!("init rideinn app");
|
||||||
let splash = app.get_webview_window("splashscreen").unwrap();
|
let splash = app.get_webview_window("splashscreen").unwrap();
|
||||||
let connect = app.get_webview_window("connect").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::<State>();
|
||||||
|
let adapter = state.adapter.clone();
|
||||||
|
let repo = repo_from_devices((scan_devices(&adapter, 5).await).unwrap_or_default());
|
||||||
|
|
||||||
splash.close().unwrap();
|
splash.close().unwrap();
|
||||||
connect.show().unwrap();
|
connect.show().unwrap();
|
||||||
|
|
||||||
|
app.emit_to("connect", "devices-loaded", repo).unwrap();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
|
.setup(|app| {
|
||||||
.invoke_handler(tauri::generate_handler![init, read_repo, scan])
|
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::<dyn std::error::Error>::from("No BLE adapter found"))?;
|
||||||
|
|
||||||
|
app.manage(State { adapter: Arc::new(adapter) } );
|
||||||
|
Ok::<(), Box<dyn std::error::Error>>(())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.invoke_handler(tauri::generate_handler![init])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
use btleplug::platform::Adapter;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub struct State {
|
||||||
|
pub adapter: Arc<Adapter>
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="stylesheet" href="styles.css" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Tauri App</title>
|
||||||
|
<script type="module" src="/connect.js" defer></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main class="container">
|
||||||
|
<h1>Connect</h1>
|
||||||
|
<div id="app">
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -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);
|
||||||
|
});
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
const { invoke } = window.__TAURI__.core;
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
return await invoke("init");
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("DOMContentLoaded", init);
|
||||||
@ -1,53 +1 @@
|
|||||||
import { EVENTS } from "./state.js";
|
console.log("hello from main.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="stylesheet" href="styles.css" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Tauri App</title>
|
||||||
|
<script type="module" src="/init.js" defer></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main class="container">
|
||||||
|
<h1>SplashScreen</h1>
|
||||||
|
<div id="app">
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -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 };
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
import { createFSM, STATES } from "./state.js";
|
|
||||||
|
|
||||||
const fsm = createFSM();
|
|
||||||
const container = document.getElementById("app");
|
|
||||||
|
|
||||||
const pages = {
|
|
||||||
[STATES.INITIAL]: `
|
|
||||||
<h1>INITIAL</h1>
|
|
||||||
`,
|
|
||||||
[STATES.SEARCH]: `
|
|
||||||
<h1>SEARCH</h1>
|
|
||||||
`,
|
|
||||||
[STATES.MANUAL_CONNECT]: `
|
|
||||||
<h1>MANUAL CONNECT</h1>
|
|
||||||
`,
|
|
||||||
[STATES.ERROR]: `
|
|
||||||
<h1>ERROR</h1>
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
|
|
||||||
function render(state) {
|
|
||||||
container.innerHTML = pages[state] ?? "<p>Unknown state</p>";
|
|
||||||
}
|
|
||||||
|
|
||||||
fsm.addEventListener("statechange", (e) => {
|
|
||||||
const { next } = e.detail;
|
|
||||||
render(next);
|
|
||||||
});
|
|
||||||
|
|
||||||
render(fsm.getState());
|
|
||||||
|
|
||||||
export { fsm };
|
|
||||||
Loading…
Reference in new issue