//! `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, cli::Command::Listen { device, duration, handshake, } => commands::listen(&device, duration, handshake, SCAN_TIMEOUT).await, cli::Command::Zwift { device, duration, no_handshake, buttons_only, } => { commands::zwift_cmd(&device, duration, no_handshake, buttons_only, 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(); }