//! FTMS client and BLE transport. See REQUIREMENTS.md §5.1–5.2.
//!
//! # Layout
//!
//! The crate is split so that everything protocol-shaped is a pure function
//! over bytes, and only [`client`] and [`scan`] touch a radio. That is what
//! lets the whole wire format be tested without a trainer on the desk:
//!
//! | Module | Contents | Needs hardware |
//! |--------|----------|----------------|
//! | [`uuids`] | FTMS assigned numbers | no |
//! | [`indoor_bike_data`] | `0x2AD2` decoder | no |
//! | [`control_point`] | `0x2AD9` encoders and response decoding | no |
//! | [`capabilities`] | `0x2ACC`/`0x2AD5`/`0x2AD6`/`0x2AD8` decoding, and the safety gate | no |
//! | [`zwift`] | Zwift's proprietary protocol (§2.3.1) | no |
//! | [`heart_rate`] | `0x2A37` decoder (pure) and the HRM connection actor | decoder no, actor yes |
//! | [`scan`] | discovery | yes |
//! | [`client`] | the connection actor | yes |
//!
//! # Usage
//!
//! ```no_run
//! use bikecontrol_ble::{FtmsClient, FtmsConfig, TrainerSelector};
//! use bikecontrol_core::types::ControlTarget;
//!
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
//! let client = FtmsClient::connect(TrainerSelector::Any, FtmsConfig::default()).await?;
//!
//! let mut telemetry = client.telemetry();
//! tokio::spawn(async move {
//!     while let Ok(sample) = telemetry.recv().await {
//!         println!("{:?} W", sample.power_w);
//!     }
//! });
//!
//! client.set_target(ControlTarget::Gradient { percent: 4.0 }).await?;
//!
//! // SAF-2: always leave the trainer at zero.
//! client.shutdown().await?;
//! # Ok(())
//! # }
//! ```
//!
//! # Attribution
//!
//! The FTMS field ordering, scaling and control-point encodings are ported from
//! [`obostjancic/smart-trainer-control`](https://github.com/obostjancic/smart-trainer-control),
//! MIT licensed, Copyright (c) 2025 Ogi — a working Van Rysel D100 client
//! (REQUIREMENTS.md §3.2). Per-module attribution notes mark where.

pub mod capabilities;
pub mod click;
pub mod client;
pub mod control_point;
pub mod error;
pub mod heart_rate;
pub mod indoor_bike_data;
pub mod scan;
pub mod uuids;
pub mod zwift;

pub use click::{ClickClient, ClickConfig, ClickEvent, PodSelector};
pub use heart_rate::{
    decode_heart_rate, HeartRateClient, HeartRateConfig, HeartRateEvent, HeartRateMeasurement,
    HrSelector,
};
pub use capabilities::{
    FitnessMachineFeature, InclinationRange, PowerRange, ResistanceLevelRange, TrainerCapabilities,
    UnsupportedTarget,
};
pub use client::{
    safety_reset_commands, Backoff, ControlOutcome, FtmsClient, FtmsConfig, FtmsEvent, Procedure,
};
pub use control_point::{ControlPointResponse, OpCode, ResultCode, SimulationParameters, StopOrPause};
pub use error::FtmsError;
pub use indoor_bike_data::{DecodeError, IndoorBikeData};
pub use scan::{
    default_adapter, scan, scan_trainers, DiscoveredDevice, ScanKind, TrainerSelector,
};
pub use uuids::FITNESS_MACHINE_SERVICE;
pub use zwift::{
    Button, ButtonBitmask, ClickButtons, DeviceKind as ZwiftDeviceKind,
    MessageType as ZwiftMessageType, PodId,
};
