Core ride logic, FTMS client, FIT encoder and probe CLI
Adds backing state for Resistance and Erg control modes, which had no value to hold and so could never satisfy FR-4.3/FR-4.6. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
//! BikeControl desktop shell.
|
||||
//!
|
||||
//! This crate is *only* wiring: it owns the ride loop, exposes intents as Tauri
|
||||
//! commands, and pushes state to the webview as events. The ride logic proper
|
||||
//! lives in `bikecontrol-core`, and device I/O in `bikecontrol-ble` — the
|
||||
//! webview reaches neither directly (§4.3).
|
||||
|
||||
pub mod backend;
|
||||
pub mod commands;
|
||||
pub mod derive;
|
||||
pub mod devices;
|
||||
pub mod events;
|
||||
pub mod mock;
|
||||
pub mod profile_view;
|
||||
pub mod samples;
|
||||
pub mod session_backend;
|
||||
pub mod state;
|
||||
|
||||
use tauri::{Manager, RunEvent, WindowEvent};
|
||||
|
||||
use crate::state::AppState;
|
||||
|
||||
pub fn run() {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| "info,bikecontrol_app_lib=debug".into()),
|
||||
)
|
||||
.init();
|
||||
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.manage(AppState::new())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
// ride lifecycle
|
||||
commands::ride_state,
|
||||
commands::start_ride,
|
||||
commands::pause_ride,
|
||||
commands::resume_ride,
|
||||
commands::toggle_pause,
|
||||
commands::stop_ride,
|
||||
commands::reset_ride,
|
||||
// control modes and targets
|
||||
commands::set_control_mode,
|
||||
commands::cycle_control_mode,
|
||||
commands::nudge_gradient,
|
||||
commands::set_gradient,
|
||||
commands::reset_gradient,
|
||||
commands::set_target_resistance,
|
||||
commands::set_target_power,
|
||||
commands::mark_lap,
|
||||
// configuration
|
||||
commands::rider_config,
|
||||
commands::set_rider_config,
|
||||
commands::safety_limits,
|
||||
commands::set_safety_limits,
|
||||
// profiles
|
||||
commands::load_profile_from_path,
|
||||
commands::load_profile_from_text,
|
||||
commands::preview_profile_yaml,
|
||||
commands::clear_profile,
|
||||
commands::sample_profiles,
|
||||
// devices
|
||||
commands::device_list,
|
||||
commands::start_scan,
|
||||
commands::stop_scan,
|
||||
commands::connect_device,
|
||||
commands::disconnect_device,
|
||||
commands::forget_device,
|
||||
commands::trainer_controllable,
|
||||
])
|
||||
.setup(|app| {
|
||||
let handle = app.handle().clone();
|
||||
// NFR-7: scanning starts immediately, not on a user click.
|
||||
handle.state::<AppState>().lock().devices.start_scan();
|
||||
state::spawn_ride_loop(handle.clone());
|
||||
state::spawn_device_loop(handle.clone());
|
||||
state::emit_devices(&handle);
|
||||
state::emit_ride_state(&handle);
|
||||
Ok(())
|
||||
})
|
||||
.build(tauri::generate_context!())
|
||||
.expect("failed to start BikeControl")
|
||||
.run(|app, event| {
|
||||
// SAF-2 — on any exit path, hand the trainer back at zero load.
|
||||
if let RunEvent::ExitRequested { .. } = &event {
|
||||
state::release_trainer(app);
|
||||
}
|
||||
if let RunEvent::WindowEvent { event: WindowEvent::Destroyed, .. } = &event {
|
||||
state::release_trainer(app);
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user