Virtual gearing, trainer-speed blend, and cadence decode
Gears are expressed as an offset to the commanded gradient, leaving the physics on the route's true gradient so shifting changes effort, not speed. Neutral gear commands exactly the route gradient, so an un-shifted ride is unchanged. Cadence is not in FTMS on this trainer but is on its Zwift channel, decoded against captured frames. The undeclared FTMS trailing bytes were ruled out: wheel RPM restated at a fixed 73.8x speed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,9 @@ use bikecontrol_core::profile::Profile;
|
||||
use bikecontrol_core::types::{ControlMode, RiderConfig, SafetyLimits};
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
use bikecontrol_ble::TrainerSelector;
|
||||
|
||||
use crate::controller::ControllerStatus;
|
||||
use crate::devices::DeviceInfo;
|
||||
use crate::events::{DeviceList, LapSummary, Notice, RideState, RideStatus};
|
||||
use crate::profile_view::{self, ProfileView};
|
||||
@@ -421,3 +424,37 @@ pub fn forget_device(app: AppHandle, state: State<'_, AppState>, device_id: Stri
|
||||
pub fn trainer_controllable(state: State<'_, AppState>) -> bool {
|
||||
state.lock().devices.trainer_controllable()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Controller (Zwift Click)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[tauri::command]
|
||||
pub fn controller_status(state: State<'_, AppState>) -> ControllerStatus {
|
||||
state.controller().status()
|
||||
}
|
||||
|
||||
/// Connect to a Click. `device_id` is an address; omit it to take the first pod
|
||||
/// that advertises.
|
||||
///
|
||||
/// Fire-and-forget: the supervisor owns the radio and the result arrives on
|
||||
/// `controller://status`. A Click sleeps within seconds and only advertises
|
||||
/// after a button press (A-4), so this routinely takes a few attempts — which
|
||||
/// is why it must not block the UI thread waiting for one.
|
||||
#[tauri::command]
|
||||
pub fn connect_controller(state: State<'_, AppState>, device_id: Option<String>) -> Cmd<()> {
|
||||
let selector = match device_id {
|
||||
Some(id) if !id.trim().is_empty() => TrainerSelector::Address(id),
|
||||
// Every pod so far advertises as "Zwift Click".
|
||||
_ => TrainerSelector::NameContains("Zwift Click".into()),
|
||||
};
|
||||
state.controller().connect(selector);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn disconnect_controller(app: AppHandle, state: State<'_, AppState>) -> Cmd<()> {
|
||||
state.controller().disconnect();
|
||||
notify(&app, Notice::info("Controller disconnected"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user