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 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 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::<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::<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!())
|
||||
.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";
|
||||
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");
|
||||
|
||||
@ -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