//! Event channel from Rust to the webview. //! //! The frontend is a *view* (ยง4.3): it never computes ride state, it renders //! what arrives here. Every event name is declared once, in this module, and //! mirrored in `ui/src/lib/events.ts`. use bikecontrol_core::types::{ConnectionState, ControlMode, ControlTarget}; use serde::Serialize; use crate::devices::DeviceInfo; use crate::profile_view::ProfileView; use crate::trainer::TrainerStatus; /// `RideSnapshot`, pushed at [`crate::engine::TICK_HZ`]. pub const RIDE_SNAPSHOT: &str = "ride://snapshot"; /// Low-frequency ride state: status, mode, targets, laps, loaded profile. pub const RIDE_STATE: &str = "ride://state"; /// A lap marker was inserted (FR-3.19 / FR-8.7). pub const RIDE_LAP: &str = "ride://lap"; /// The ride ended and its activity was written (FR-9.13). Carries a /// `crate::recording::RideSummary`. pub const RIDE_SUMMARY: &str = "ride://summary"; /// The full device list changed (FR-9.1). pub const DEVICES_UPDATED: &str = "devices://updated"; /// One device changed connection or control state (FR-1.7, FR-9.3). pub const DEVICE_CONNECTION: &str = "devices://connection"; /// User-facing message: confirmation, warning or error (FR-9.2). pub const APP_NOTICE: &str = "app://notice"; /// Acknowledgement that an input registered, so the UI can flash (FR-9.9). pub const INPUT_ACK: &str = "app://input-ack"; /// A Zwift Click button edge. The webview routes these to the same intents as /// the equivalent keypress, so controller and keyboard cannot drift apart. pub const CONTROLLER_INPUT: &str = "controller://input"; /// Controller link state and battery. pub const CONTROLLER_STATUS: &str = "controller://status"; /// Ride lifecycle, mirroring `bikecontrol_core::session::RideStatus` but /// serialisable across the IPC boundary. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] #[serde(rename_all = "lowercase")] pub enum RideStatus { Idle, Running, Paused, Finished, } /// Everything the ride screen needs that is *not* in a `RideSnapshot`. /// Emitted on change, not on a timer. #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct RideState { pub status: RideStatus, pub mode: ControlMode, pub target: Option, /// Manual gradient trim on top of the profile's base gradient (FR-4.2). pub gradient_offset_pct: f32, pub manual_gradient_pct: f32, pub resistance_level: i16, pub power_target_w: u16, /// Selected virtual gear, one-based, and how many there are (FR-4.1). pub gear: usize, pub gear_count: usize, pub lap: u32, pub laps: Vec, pub profile: Option, /// Trainer link, so the ride screen can say when the numbers stopped being /// real rather than quietly showing zeros (FR-1.8, FR-9.3). pub trainer: TrainerStatus, } #[derive(Debug, Clone, Copy, Serialize)] #[serde(rename_all = "camelCase")] pub struct LapSummary { pub index: u32, pub elapsed_ms: u64, pub distance_m: f64, pub avg_power_w: f32, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConnectionEvent { pub device_id: String, pub state: ConnectionState, /// FTMS control point acquired. Connected is *not* controllable (FR-9.3). pub control_acquired: bool, pub error: Option, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] #[serde(rename_all = "lowercase")] pub enum NoticeLevel { Info, Warn, Error, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct Notice { pub level: NoticeLevel, pub message: String, } impl Notice { pub fn info(message: impl Into) -> Self { Self { level: NoticeLevel::Info, message: message.into(), } } pub fn warn(message: impl Into) -> Self { Self { level: NoticeLevel::Warn, message: message.into(), } } pub fn error(message: impl Into) -> Self { Self { level: NoticeLevel::Error, message: message.into(), } } } /// Confirms an intent was accepted, so the UI can flash the control (FR-9.9). #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct InputAck { pub action: String, pub detail: Option, } /// The full device list. #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct DeviceList { pub scanning: bool, pub devices: Vec, }