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:
2026-08-05 13:34:27 +02:00
co-authored by Claude Opus 5
parent 3e106de2c5
commit 7c17ca6158
61 changed files with 20933 additions and 55 deletions
+68 -1
View File
@@ -1 +1,68 @@
fn main() { println!("probe: not yet implemented"); }
//! `probe` — BLE protocol discovery against real hardware.
//!
//! This is the Phase 0 tool from REQUIREMENTS.md §9: TASK-1 (enumerate the
//! D100's services, dump its capability characteristics, log decoded Indoor
//! Bike Data, resolve whether op code `0x11` works) and TASK-2 (write a control
//! command and confirm a physical resistance change).
//!
//! It is intentionally separate from the app: it prints raw bytes next to
//! decoded values (NFR-8) so that a decoder bug shows up as a disagreement on
//! screen rather than as a strange number in a chart.
mod cli;
mod commands;
use std::time::Duration;
use anyhow::Result;
use tracing_subscriber::EnvFilter;
/// How long to look for the device before giving up.
const SCAN_TIMEOUT: Duration = Duration::from_secs(20);
#[tokio::main]
async fn main() -> Result<()> {
let args = match cli::parse(std::env::args().skip(1)) {
Ok(args) => args,
Err(e) => {
eprintln!("error: {e}\n");
eprint!("{}", cli::USAGE);
std::process::exit(2);
}
};
init_logging(args.verbose);
match args.command {
cli::Command::Help => {
print!("{}", cli::USAGE);
Ok(())
}
cli::Command::Scan { duration, all } => commands::scan_cmd(duration, all).await,
cli::Command::Inspect { device } => commands::inspect(&device, SCAN_TIMEOUT).await,
cli::Command::Monitor { device, duration } => {
commands::monitor(&device, duration, SCAN_TIMEOUT).await
}
cli::Command::Set {
device,
target,
simulation,
hold,
} => commands::set(&device, target, simulation, hold, SCAN_TIMEOUT).await,
}
}
fn init_logging(verbose: bool) {
// `-v` turns on the raw-frame logging required by NFR-8. RUST_LOG still
// wins, so `RUST_LOG=trace` gets every notification.
let default = if verbose {
"bikecontrol_ble=debug,probe=debug,info"
} else {
"warn"
};
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::try_from_default_env().unwrap_or_else(|_| default.into()))
.with_target(false)
.without_time()
.init();
}