### Install PyFTMS with pip Source: https://github.com/dudanov/python-pyftms/blob/main/README.md Install the PyFTMS library using pip. Ensure you have Python and pip installed. ```bash pip install pyftms ``` -------------------------------- ### Control Fitness Machine Session State Source: https://context7.com/dudanov/python-pyftms/llms.txt Demonstrates how to start, pause, resume, and stop a training session. All commands are asynchronous and return a ResultCode. ```python import asyncio from pyftms import get_client_from_address, ResultCode async def session_control_demo(): async with await get_client_from_address("29:84:5A:22:A4:11") as machine: # Start or resume a paused session r = await machine.start_resume() print(f"start_resume → {r}") # ResultCode.SUCCESS await asyncio.sleep(30) # Pause the session r = await machine.pause() print(f"pause → {r}") # ResultCode.SUCCESS await asyncio.sleep(5) # Resume r = await machine.start_resume() print(f"resume → {r}") # ResultCode.SUCCESS await asyncio.sleep(30) # Stop r = await machine.stop() print(f"stop → {r}") # ResultCode.SUCCESS # Reset all controllable settings to defaults r = await machine.reset() print(f"reset → {r}") # ResultCode.SUCCESS asyncio.run(session_control_demo()) ``` -------------------------------- ### Initiate Spin-Down Calibration with PyFTMS Source: https://context7.com/dudanov/python-pyftms/llms.txt Starts the spin-down calibration procedure on compatible indoor bikes. The `SpinDownEvent` will be triggered with speed targets upon success. Ensure the bike supports this feature. ```python import asyncio from pyftms import get_client_from_address, FtmsEvents, SpinDownEventData def on_event(event: FtmsEvents): if event.event_id == "spin_down": data: SpinDownEventData = event.event_data if "target_speed" in data: spd = data["target_speed"] print(f"Spin-down speed window: {spd.low}–{spd.high} km/h") elif "status" in data: print(f"Spin-down status: {data['status'].name}") async def calibrate(): async with await get_client_from_address( "29:84:5A:22:A4:11", on_ftms_event=on_event ) as bike: r = await bike.spin_down_start() print(f"Spin-down started → {r}") # Accelerate to target speed window, then stop pedaling await asyncio.sleep(30) asyncio.run(calibrate()) ``` -------------------------------- ### Handle FTMS Training Status Updates Source: https://context7.com/dudanov/python-pyftms/llms.txt This snippet demonstrates how to process `UpdateEvent` to get the machine's current training status using the `TrainingStatusCode` enum. It matches different status codes to print informative messages. ```python import asyncio from pyftms import get_client_from_address, FtmsEvents, TrainingStatusCode def on_event(event: FtmsEvents): if event.event_id == "update": status = event.event_data.get("training_status") if status is not None: match status: case TrainingStatusCode.IDLE: print("Machine is idle") case TrainingStatusCode.WARMING_UP: print("Warming up...") case TrainingStatusCode.MANUAL_MODE: print("Manual / quick-start mode") case TrainingStatusCode.WATT_CONTROL: print("ERG / watt control mode") case TrainingStatusCode.COOL_DOWN: print("Cool down phase") case _: print(f"Training status: {status.name}") async def main(): async with await get_client_from_address( "29:84:5A:22:A4:11", on_ftms_event=on_event ) as m: await m.start_resume() await asyncio.sleep(60) asyncio.run(main()) ``` -------------------------------- ### Handle FTMS Events with PyFTMS Callback Source: https://context7.com/dudanov/python-pyftms/llms.txt Demonstrates how to handle various FTMS events using a single callback function. The `FtmsEvents` union type is discriminated by `event_id` to process different event types like updates, setup changes, control signals, and spin-down events. ```python import asyncio from pyftms import ( get_client_from_address, FtmsEvents, UpdateEvent, UpdateEventData, SetupEvent, SetupEventData, ControlEvent, SpinDownEvent, TrainingStatusCode, ) def on_event(event: FtmsEvents): match event: case UpdateEvent(event_id="update", event_data=data): # Fires on each BLE notification with changed values only for key, value in data.items(): print(f" [{key}] = {value}") case SetupEvent(event_id="setup", event_data=data, event_source=src): # Fires when a setting changes (our command or machine-side) print(f" Setup changed by '{src}': {data}") # e.g. Setup changed by 'callback': {'target_speed': 10.0} # e.g. Setup changed by 'user': {'target_resistance': 8} case ControlEvent(event_id=eid, event_source=src): # Fires for start / stop / pause / reset print(f" Control: '{eid}' by '{src}'") # e.g. Control: 'start' by 'callback' # e.g. Control: 'stop' by 'user' case SpinDownEvent(event_data=data): print(f" Spin-down: {data}") async def main(): async with await get_client_from_address( "29:84:5A:22:A4:11", on_ftms_event=on_event ) as machine: await machine.start_resume() await asyncio.sleep(60) await machine.stop() asyncio.run(main()) ``` -------------------------------- ### Configure Indoor Bike Simulation Parameters Source: https://context7.com/dudanov/python-pyftms/llms.txt This snippet shows how to create an `IndoorBikeSimulationParameters` object to simulate outdoor ride physics. It includes parameters for wind speed, grade, rolling resistance, and wind resistance. ```python from pyftms import IndoorBikeSimulationParameters # Simulate a 5% uphill climb with a tailwind params = IndoorBikeSimulationParameters( wind_speed=5.0, # m/s positive = tailwind grade=5.0, # % positive = uphill rolling_resistance=0.004, # unitless coefficient (~asphalt road) wind_resistance=0.51, # kg/m (~upright rider on road bike) ) print(params.wind_speed) # 5.0 print(params.grade) # 5.0 print(params.rolling_resistance)# 0.004 print(params.wind_resistance) # 0.51 ``` -------------------------------- ### Simulate Outdoor Cycling Physics Source: https://context7.com/dudanov/python-pyftms/llms.txt Configures the indoor bike to simulate outdoor riding conditions using wind speed, road grade, rolling resistance, and wind resistance coefficients. Requires `IndoorBikeSimulationParameters`. ```python import asyncio from pyftms import get_client_from_address, IndoorBikeSimulationParameters async def simulate_ride(): async with await get_client_from_address("29:84:5A:22:A4:11") as bike: await bike.start_resume() # Flat road, light headwind params = IndoorBikeSimulationParameters( wind_speed=-3.0, # m/s (negative = headwind) grade=0.0, # % (0 = flat) rolling_resistance=0.004, # unitless (typical road) wind_resistance=0.51, # kg/m (typical upright rider) ) r = await bike.set_indoor_bike_simulation(params) print(f"Flat road simulation → {r}") await asyncio.sleep(300) # Uphill climb, 8% grade climb = IndoorBikeSimulationParameters( wind_speed=0.0, grade=8.0, rolling_resistance=0.004, wind_resistance=0.51, ) r = await bike.set_indoor_bike_simulation(climb) print(f"8% climb simulation → {r}") await asyncio.sleep(300) await bike.stop() asyncio.run(simulate_ride()) ``` -------------------------------- ### Inspect Setting Ranges with SettingRange Source: https://context7.com/dudanov/python-pyftms/llms.txt Connect to a fitness machine and iterate through its supported settings to display their minimum, maximum, and step values. This helps in understanding the controllable parameters. ```python import asyncio from pyftms import get_client_from_address, SettingRange async def inspect_ranges(): async with await get_client_from_address("29:84:5A:22:A4:11") as m: for setting_id, rng in m.supported_ranges.items(): rng: SettingRange print( f"{setting_id:30s} " f"min={rng.min_value:6.2f} " f"max={rng.max_value:6.2f} " f"step={rng.step:.3f}" ) # target_speed min= 0.50 max= 22.00 step=0.100 # target_inclination min= -3.00 max= 15.00 step=0.100 # target_heart_rate min= 60.00 max=220.00 step=1.000 asyncio.run(inspect_ranges()) ``` -------------------------------- ### get_client Source: https://context7.com/dudanov/python-pyftms/llms.txt Creates a `FitnessMachine` client instance for a discovered BLE device. It automatically determines the correct machine subclass and allows for optional event and disconnection callbacks. ```APIDOC ## get_client ### Description Factory function that constructs the correct `FitnessMachine` subclass (`Treadmill`, `IndoorBike`, `CrossTrainer`, or `Rower`) from a `BLEDevice` and either `AdvertisementData` or an explicit `MachineType`. Accepts optional callbacks for FTMS events and disconnection. ### Parameters #### Path Parameters - **device** (BLEDevice) - Required - The discovered BLE device object. - **adv_data** (AdvertisementData or MachineType) - Required - Advertisement data or the explicit machine type. #### Query Parameters - **timeout** (float) - Optional - GATT write timeout in seconds. Defaults to 2.0. - **on_ftms_event** (callable) - Optional - Callback function for FTMS events. - **on_disconnect** (callable) - Optional - Callback function for disconnections. ### Request Example ```python import asyncio from bleak import BleakScanner from pyftms import get_client, FtmsEvents, FitnessMachine def on_event(event: FtmsEvents): match event.event_id: case "update": data = event.event_data print(f"Speed: {data.get('speed_instant')} km/h Power: {data.get('power_instant')} W HR: {data.get('heart_rate')} bpm") case "setup": print(f"Setting changed: {event.event_data} (source: {event.event_data})") case "start" | "stop" | "pause" | "reset": print(f"Control event: {event.event_id} (source: {event.event_source})") def on_disconnect(machine: FitnessMachine): print(f"Disconnected from {machine.name}") async def main(): device, adv_data = await BleakScanner.find_device_by_filter( lambda d, a: "FitnessMachine" in (d.name or ""), timeout=10, ) client = get_client( device, adv_data, # or pass MachineType.INDOOR_BIKE directly timeout=2.0, # GATT write timeout in seconds on_ftms_event=on_event, on_disconnect=on_disconnect, ) async with client: await asyncio.sleep(60) # receive telemetry for 60 seconds asyncio.run(main()) ``` ### Response #### Success Response - **client** (FitnessMachine) - An instance of a `FitnessMachine` subclass. ### Response Example ``` # Example output depends on device events and callbacks Setting changed: {{'resistance': 50}} (source: IndoorBike) Speed: 15.0 km/h Power: 100 W HR: 120 bpm Disconnected from MySpinBike ``` ``` -------------------------------- ### FitnessMachine Client for FTMS Interaction Source: https://context7.com/dudanov/python-pyftms/llms.txt Demonstrates using the FitnessMachine as an async context manager to connect, inspect device capabilities, start/stop training sessions, and read live telemetry. This client handles GATT subscriptions and connection management internally. ```python import asyncio import logging from pyftms import get_client_from_address, FitnessMachine, FtmsEvents, ResultCode logging.basicConfig(level=logging.DEBUG) async def main(): machine: FitnessMachine = await get_client_from_address("29:84:5A:22:A4:11") async with machine: # --- Inspect static device capabilities --- print("Supported properties:", machine.supported_properties) # e.g. ['speed_instant', 'speed_average', 'cadence_instant', # 'power_instant', 'heart_rate', 'distance_total', ...] print("Supported settings:", machine.supported_settings) # e.g. ['target_speed', 'target_resistance', 'target_power', ...] print("Setting ranges:", dict(machine.supported_ranges)) # e.g. {'target_speed': SettingRange(min_value=0.5, max_value=22.0, step=0.1), # 'target_power': SettingRange(min_value=0, max_value=400, step=1)} # --- Start a training session --- result: ResultCode = await machine.start_resume() assert result == ResultCode.SUCCESS await asyncio.sleep(10) # --- Read live telemetry via properties --- print(f"Speed: {machine.speed_instant} km/h") print(f"Cadence: {machine.cadence_instant} rpm") print(f"Power: {machine.power_instant} W") print(f"HR: {machine.heart_rate} bpm") print(f"Distance: {machine.distance_total} m") print(f"Energy: {machine.energy_total} kcal") print(f"Elapsed: {machine.time_elapsed} s") # --- Stop session --- await machine.stop() asyncio.run(main()) ``` -------------------------------- ### Read Real-time Training Data with PropertiesManager Source: https://context7.com/dudanov/python-pyftms/llms.txt Connect to a fitness machine and read various real-time training data properties. Ensure a delay to allow notifications to arrive. ```python import asyncio from pyftms import get_client_from_address async def read_properties(): async with await get_client_from_address("29:84:5A:22:A4:11") as m: await m.start_resume() await asyncio.sleep(5) # let a few notifications arrive # Real-time training data properties print(m.speed_instant) # float, km/h print(m.speed_average) # float, km/h print(m.cadence_instant) # float, rpm print(m.cadence_average) # float, rpm print(m.power_instant) # int, W print(m.power_average) # int, W print(m.heart_rate) # int, bpm print(m.distance_total) # int, m print(m.energy_total) # int, kcal print(m.energy_per_hour) # int, kcal/h print(m.time_elapsed) # int, s print(m.time_remaining) # int, s print(m.inclination) # float, % print(m.resistance_level) # int|float, unitless print(m.training_status) # TrainingStatusCode enum # Settings state (reflects last set values) print(m.target_speed) # float, km/h (or None) print(m.target_resistance) # float, unitless (or None) print(m.target_power) # int, W (or None) # Properties that have had a non-zero value at least once print(m.live_properties) # tuple[str, ...] # Full snapshot dict print(dict(m.properties)) print(dict(m.settings)) asyncio.run(read_properties()) ``` -------------------------------- ### Set Generic Settings on Indoor Bike with PyFTMS Source: https://context7.com/dudanov/python-pyftms/llms.txt Allows setting any supported bike parameter using its string identifier. This is useful for dynamic configuration or when iterating through `supported_settings`. The code iterates through available settings and attempts to set each to its midpoint value. ```python import asyncio from pyftms import get_client_from_address, ResultCode async def generic_settings(): async with await get_client_from_address("29:84:5A:22:A4:11") as machine: print("Available settings:", machine.supported_settings) for setting in machine.supported_settings: rng = machine.supported_ranges.get(setting) if rng: mid = (rng.min_value + rng.max_value) / 2 r: ResultCode = await machine.set_setting(setting, mid) print(f" set {setting} = {mid} → {r}") asyncio.run(generic_settings()) ``` -------------------------------- ### Session Control Commands Source: https://context7.com/dudanov/python-pyftms/llms.txt Control the fitness machine's training session state. All commands are async, automatically request GATT control on first use, and return a `ResultCode` indicating success or failure. ```APIDOC ## Session Control Commands — `start_resume`, `stop`, `pause`, `reset` ### Description Control the fitness machine's training session state. All commands are async, automatically request GATT control on first use, and return a `ResultCode` indicating success or failure. ### Methods - `start_resume()`: Starts or resumes a paused session. - `pause()`: Pauses the current training session. - `stop()`: Stops the current training session. - `reset()`: Resets all controllable settings to their default values. ### Example Usage ```python import asyncio from pyftms import get_client_from_address, ResultCode async def session_control_demo(): async with await get_client_from_address("29:84:5A:22:A4:11") as machine: # Start or resume a paused session r = await machine.start_resume() print(f"start_resume → {r}") # ResultCode.SUCCESS await asyncio.sleep(30) # Pause the session r = await machine.pause() print(f"pause → {r}") # ResultCode.SUCCESS await asyncio.sleep(5) # Resume r = await machine.start_resume() print(f"resume → {r}") # ResultCode.SUCCESS await asyncio.sleep(30) # Stop r = await machine.stop() print(f"stop → {r}") # ResultCode.SUCCESS # Reset all controllable settings to defaults r = await machine.reset() print(f"reset → {r}") # ResultCode.SUCCESS asyncio.run(session_control_demo()) ``` ``` -------------------------------- ### Set Target Power in Watts Source: https://context7.com/dudanov/python-pyftms/llms.txt Sets the target wattage for ergometer-style power control on compatible bikes or cross-trainers. ```python import asyncio from pyftms import get_client_from_address async def power_intervals(): async with await get_client_from_address("29:84:5A:22:A4:11") as bike: await bike.start_resume() # 4×4 interval: 4 min at 250W, 4 min recovery at 100W for _ in range(4): r = await bike.set_target_power(250) print(f"WORK 250 W → {r}") await asyncio.sleep(240) r = await bike.set_target_power(100) print(f"REST 100 W → {r}") await asyncio.sleep(240) await bike.stop() asyncio.run(power_intervals()) ``` -------------------------------- ### Retrieve Device Information with DeviceInfo Source: https://context7.com/dudanov/python-pyftms/llms.txt Connect to a fitness machine and display static device metadata obtained from the BLE Device Information Service. Accesses manufacturer, model, serial number, and versions. ```python import asyncio from pyftms import get_client_from_address, DeviceInfo async def show_device_info(): async with await get_client_from_address("29:84:5A:22:A4:11") as m: info: DeviceInfo = m.device_info print(f"Manufacturer: {info.get('manufacturer', 'N/A')}") print(f"Model: {info.get('model', 'N/A')}") print(f"Serial number: {info.get('serial_number', 'N/A')}") print(f"HW version: {info.get('hw_version', 'N/A')}") print(f"SW version: {info.get('sw_version', 'N/A')}") print(f"Unique ID: {m.unique_id}") print(f"BLE address: {m.address}") print(f"RSSI: {m.rssi} dBm") asyncio.run(show_device_info()) ``` -------------------------------- ### set_indoor_bike_simulation Source: https://context7.com/dudanov/python-pyftms/llms.txt Configures the indoor bike to simulate outdoor riding conditions using wind speed, road grade, rolling resistance, and wind resistance coefficients. Defined by `IndoorBikeSimulationParameters`. ```APIDOC ## `set_indoor_bike_simulation` — Simulate outdoor cycling physics Configures the indoor bike to simulate outdoor riding conditions using wind speed, road grade, rolling resistance, and wind resistance coefficients. Defined by `IndoorBikeSimulationParameters`. ### Method Asynchronous function call on an `IndoorBike` object. ### Parameters - `set_indoor_bike_simulation(params: IndoorBikeSimulationParameters)`: Sets the simulation parameters. - `wind_speed` (float): Wind speed in m/s. Negative values indicate headwind. - `grade` (float): Road grade in percent (0.0 for flat). - `rolling_resistance` (float): Unitless rolling resistance coefficient. - `wind_resistance` (float): Wind resistance coefficient (kg/m). ### Request Example ```python import asyncio from pyftms import get_client_from_address, IndoorBikeSimulationParameters async def simulate_ride(): async with await get_client_from_address("29:84:5A:22:A4:11") as bike: await bike.start_resume() # Flat road, light headwind params = IndoorBikeSimulationParameters( wind_speed=-3.0, # m/s (negative = headwind) grade=0.0, # % (0 = flat) rolling_resistance=0.004, # unitless (typical road) wind_resistance=0.51, # kg/m (typical upright rider) ) r = await bike.set_indoor_bike_simulation(params) print(f"Flat road simulation → {r}") await asyncio.sleep(300) # Uphill climb, 8% grade climb = IndoorBikeSimulationParameters( wind_speed=0.0, grade=8.0, rolling_resistance=0.004, wind_resistance=0.51, ) r = await bike.set_indoor_bike_simulation(climb) print(f"8% climb simulation → {r}") await asyncio.sleep(300) await bike.stop() asyncio.run(simulate_ride()) ``` ``` -------------------------------- ### Set Target Cadence for Indoor Bike or Cross Trainer Source: https://context7.com/dudanov/python-pyftms/llms.txt Sets the targeted cadence in revolutions per minute (rpm). This is applicable to `IndoorBike` and `CrossTrainer` machines. ```python import asyncio from pyftms import get_client_from_address async def cadence_demo(): async with await get_client_from_address("29:84:5A:22:A4:11") as bike: await bike.start_resume() r = await bike.set_target_cadence(90.0) # 90 rpm print(f"Target cadence: 90 rpm → {r}") await asyncio.sleep(600) print(f"Actual cadence: {bike.cadence_instant} rpm") await bike.stop() asyncio.run(cadence_demo()) ``` -------------------------------- ### Connect to FTMS Device and Receive Telemetry Source: https://context7.com/dudanov/python-pyftms/llms.txt Create a FitnessMachine client from a discovered BLE device and receive telemetry data via callbacks. Handles device connection, event subscription, and disconnection. ```python import asyncio from bleak import BleakScanner from pyftms import get_client, FtmsEvents, FitnessMachine def on_event(event: FtmsEvents): match event.event_id: case "update": data = event.event_data print(f"Speed: {data.get('speed_instant')} km/h " f"Power: {data.get('power_instant')} W " f"HR: {data.get('heart_rate')} bpm") case "setup": print(f"Setting changed: {event.event_data} (source: {event.event_source})") case "start" | "stop" | "pause" | "reset": print(f"Control event: {event.event_id} (source: {event.event_source})") def on_disconnect(machine: FitnessMachine): print(f"Disconnected from {machine.name}") async def main(): device, adv_data = await BleakScanner.find_device_by_filter( lambda d, a: "FitnessMachine" in (d.name or ""), timeout=10, ) client = get_client( device, adv_data, # or pass MachineType.INDOOR_BIKE directly timeout=2.0, # GATT write timeout in seconds on_ftms_event=on_event, on_disconnect=on_disconnect, ) async with client: await asyncio.sleep(60) # receive telemetry for 60 seconds asyncio.run(main()) ``` -------------------------------- ### FitnessMachine Source: https://context7.com/dudanov/python-pyftms/llms.txt The base asynchronous context manager client for FTMS machines. It handles connection management, provides access to device capabilities and settings, and manages GATT subscriptions and automatic re-acquisition of control. ```APIDOC ## FitnessMachine ### Description Abstract base class for all machine-specific clients. Provides connection management, property/settings access, and all control commands. Used via `async with` or explicit `connect()`/`disconnect()` calls. Internally handles GATT subscriptions, stale-connection cleanup, and automatic re-acquisition of control. ### Usage Can be used as an asynchronous context manager (`async with`) or by explicitly calling `connect()` and `disconnect()`. ### Properties - `supported_properties` (list): A list of supported static device properties (e.g., 'speed_instant', 'heart_rate'). - `supported_settings` (list): A list of supported settings (e.g., 'target_speed', 'target_resistance'). - `supported_ranges` (dict): A dictionary mapping settings to their supported value ranges. - `name` (str): The name of the connected machine. - `machine_type` (MachineType): The type of the FTMS machine. - `device_info` (dict): Static information about the device (manufacturer, model, serial number, etc.). - `speed_instant` (float): The current instant speed. - `cadence_instant` (float): The current instant cadence. - `power_instant` (float): The current instant power output. - `heart_rate` (int): The current heart rate. - `distance_total` (float): The total distance covered. - `energy_total` (float): The total energy expended. - `time_elapsed` (float): The total elapsed time of the current session. ### Methods - `start_resume()`: Starts or resumes the training session. Returns `ResultCode`. - `stop()`: Stops the current training session. ### Request Example ```python import asyncio import logging from pyftms import get_client_from_address, FitnessMachine, FtmsEvents, ResultCode logging.basicConfig(level=logging.DEBUG) async def main(): machine: FitnessMachine = await get_client_from_address("29:84:5A:22:A4:11") async with machine: # --- Inspect static device capabilities --- print("Supported properties:", machine.supported_properties) print("Supported settings:", machine.supported_settings) print("Setting ranges:", dict(machine.supported_ranges)) # --- Start a training session --- result: ResultCode = await machine.start_resume() assert result == ResultCode.SUCCESS await asyncio.sleep(10) # --- Read live telemetry via properties --- print(f"Speed: {machine.speed_instant} km/h") print(f"Cadence: {machine.cadence_instant} rpm") print(f"Power: {machine.power_instant} W") print(f"HR: {machine.heart_rate} bpm") print(f"Distance: {machine.distance_total} m") print(f"Energy: {machine.energy_total} kcal") print(f"Elapsed: {machine.time_elapsed} s") # --- Stop session --- await machine.stop() asyncio.run(main()) ``` ``` -------------------------------- ### set_setting Source: https://context7.com/dudanov/python-pyftms/llms.txt Generic method to set any supported setting by its string identifier. Useful for dynamic configuration or when iterating over `supported_settings`. ```APIDOC ## `set_setting` — Generic setting by string ID ### Description Generic method to set any supported setting by its string identifier. Useful for dynamic configuration or when iterating over `supported_settings`. ### Method Signature ```python async def set_setting(setting_id: str, value: float) -> ResultCode ``` ### Parameters - **setting_id** (str) - The string identifier of the setting to change. - **value** (float) - The desired value for the setting. ### Returns - **ResultCode** - Indicates the success or failure of the operation. ### Example Usage ```python import asyncio from pyftms import get_client_from_address, ResultCode async def generic_settings(): async with await get_client_from_address("29:84:5A:22:A4:11") as machine: print("Available settings:", machine.supported_settings) for setting in machine.supported_settings: rng = machine.supported_ranges.get(setting) if rng: mid = (rng.min_value + rng.max_value) / 2 r: ResultCode = await machine.set_setting(setting, mid) print(f" set {setting} = {mid} → {r}") asyncio.run(generic_settings()) ``` ``` -------------------------------- ### Set Step or Stride Goals Source: https://context7.com/dudanov/python-pyftms/llms.txt Set targeted step count for Treadmills/Step Climbers or stride count for CrossTrainers. ```python import asyncio from pyftms import get_client_from_address async def step_goal(): async with await get_client_from_address("29:84:5A:22:A4:11") as treadmill: r = await treadmill.set_target_steps(10000) # 10,000 steps print(f"Target steps → {r}") await treadmill.start_resume() await asyncio.sleep(3600) print(f"Steps taken: {treadmill.step_count}") await treadmill.stop() asyncio.run(step_goal()) ``` -------------------------------- ### Set Resistance Level Source: https://context7.com/dudanov/python-pyftms/llms.txt Sets the resistance level on indoor bikes, cross-trainers, or rowers. The scale is machine-specific. Check `supported_ranges` for min/max values and step. ```python import asyncio from pyftms import get_client_from_address async def resistance_demo(): async with await get_client_from_address("29:84:5A:22:A4:11") as bike: rng = bike.supported_ranges.get("target_resistance") if rng: print(f"Resistance range: {rng.min_value} – {rng.max_value} (step {rng.step})") await bike.start_resume() for level in [1, 5, 10, 15, 10, 5, 1]: r = await bike.set_target_resistance(level) print(f"Resistance level {level} → {r}") await asyncio.sleep(60) await bike.stop() asyncio.run(resistance_demo()) ``` -------------------------------- ### Set Workout Goals: Energy, Distance, or Time Source: https://context7.com/dudanov/python-pyftms/llms.txt Use these methods to set specific targets for a training session. `set_target_time` can accept one, two, or three integer values for single-zone, two-zone, or three-zone heart rate time targets, respectively. ```python import asyncio from pyftms import get_client_from_address async def goal_workout(): async with await get_client_from_address("29:84:5A:22:A4:11") as machine: # Burn 500 kcal await machine.set_target_energy(500) # Or target 5 km await machine.set_target_distance(5000) # Or target 30 minutes await machine.set_target_time(1800) # Or two-zone heart rate time targets (zone1=600s, zone2=600s) await machine.set_target_time(600, 600) await machine.start_resume() await asyncio.sleep(3600) await machine.stop() asyncio.run(goal_workout()) ``` -------------------------------- ### Set Treadmill Incline Source: https://context7.com/dudanov/python-pyftms/llms.txt Sets the target inclination in percent for compatible treadmills. Requires the machine to support `target_inclination`. ```python import asyncio from pyftms import get_client_from_address async def incline_demo(): async with await get_client_from_address("29:84:5A:22:A4:11") as t: await t.start_resume() await t.set_target_speed(8.0) # Hill interval: ramp up then back to flat for grade in [0.0, 2.0, 5.0, 8.0, 5.0, 2.0, 0.0]: r = await t.set_target_inclination(grade) print(f"Inclination: {grade}% → {r}") await asyncio.sleep(120) await t.stop() asyncio.run(incline_demo()) ``` -------------------------------- ### Handle FTMS Spin-Down Calibration Events Source: https://context7.com/dudanov/python-pyftms/llms.txt This snippet demonstrates how to handle `SpinDownEvent` data, specifically the `SpinDownSpeedData` which provides the target speed range for calibration. It also shows the client control codes and machine status codes for spin-down. ```python from pyftms import SpinDownSpeedData, SpinDownControlCode, SpinDownStatusCode # SpinDownSpeedData is returned inside SpinDownEventData['target_speed'] # when spin_down_start() succeeds def on_spin_down(data): if "target_speed" in data: spd: SpinDownSpeedData = data["target_speed"] print(f"Accelerate to {spd.low}–{spd.high} km/h, then stop pedaling") # SpinDownControlCode values sent by client: print(SpinDownControlCode.START) # 1 print(SpinDownControlCode.IGNORE) # 2 # SpinDownStatusCode values received from machine: print(SpinDownStatusCode.REQUESTED) # 1 print(SpinDownStatusCode.SUCCESS) # 2 print(SpinDownStatusCode.ERROR) # 3 print(SpinDownStatusCode.STOP_PEDALING) # 4 ``` -------------------------------- ### Set Treadmill Belt Speed Source: https://context7.com/dudanov/python-pyftms/llms.txt Sets the target speed for a treadmill in km/h. Ensure the machine is a Treadmill and check supported ranges before use. ```python import asyncio from pyftms import get_client_from_address, ResultCode async def speed_control(): async with await get_client_from_address("29:84:5A:22:A4:11") as t: await t.start_resume() for speed in [5.0, 8.0, 10.0, 6.0]: r: ResultCode = await t.set_target_speed(speed) print(f"Speed set to {speed} km/h → {r}") # Speed set to 5.0 km/h → ResultCode.SUCCESS await asyncio.sleep(60) await t.stop() asyncio.run(speed_control()) ``` -------------------------------- ### spin_down_start / spin_down_ignore Source: https://context7.com/dudanov/python-pyftms/llms.txt Initiates or ignores the spin-down calibration procedure on indoor bikes that support it. SpinDownEvent fires with speed targets (SpinDownSpeedData) on success. ```APIDOC ## `spin_down_start` / `spin_down_ignore` — Spin-down calibration procedure ### Description Initiates or ignores the spin-down calibration procedure on indoor bikes that support it. `SpinDownEvent` fires with speed targets (`SpinDownSpeedData`) on success. ### Method ```python await bike.spin_down_start() ``` ### Event Handling ```python def on_event(event: FtmsEvents): if event.event_id == "spin_down": data: SpinDownEventData = event.event_data if "target_speed" in data: spd = data["target_speed"] print(f"Spin-down speed window: {spd.low}–{spd.high} km/h") elif "status" in data: print(f"Spin-down status: {data['status'].name}") ``` ### Example Usage ```python import asyncio from pyftms import get_client_from_address, FtmsEvents, SpinDownEventData def on_event(event: FtmsEvents): # ... event handling logic ... pass async def calibrate(): async with await get_client_from_address( "29:84:5A:22:A4:11", on_ftms_event=on_event ) as bike: r = await bike.spin_down_start() print(f"Spin-down started → {r}") # Accelerate to target speed window, then stop pedaling await asyncio.sleep(30) asyncio.run(calibrate()) ``` ``` -------------------------------- ### Set Target Heart Rate Source: https://context7.com/dudanov/python-pyftms/llms.txt Sets the target heart rate in bpm to enable heart-rate-controlled mode on supported machines. ```python import asyncio from pyftms import get_client_from_address async def hr_control(): async with await get_client_from_address("29:84:5A:22:A4:11") as machine: await machine.start_resume() r = await machine.set_target_heart_rate(140) # 140 bpm zone print(f"Target HR set to 140 bpm → {r}") await asyncio.sleep(1800) await machine.stop() asyncio.run(hr_control()) ``` -------------------------------- ### set_target_energy / set_target_distance / set_target_time Source: https://context7.com/dudanov/python-pyftms/llms.txt Set workout goals for calories, distance, or time. `set_target_time` can accept one, two, or three integer values in seconds for single, two-zone, or three-zone heart rate time targets respectively. ```APIDOC ## `set_target_energy` / `set_target_distance` / `set_target_time` — Set workout goals Set calorie, distance, or time targets for a training session. `set_target_time` accepts 1, 2, or 3 integer values in seconds (for single, two-zone, or three-zone heart rate time targets). ### Method Asynchronous function calls on a `machine` object. ### Parameters - `set_target_energy(energy: int)`: Sets the target energy expenditure in kcal. - `set_target_distance(distance: int)`: Sets the target distance in meters. - `set_target_time(time1: int, time2: Optional[int] = None, time3: Optional[int] = None)`: Sets the target time in seconds. Can be used for single, two-zone, or three-zone targets. ### Request Example ```python import asyncio from pyftms import get_client_from_address async def goal_workout(): async with await get_client_from_address("29:84:5A:22:A4:11") as machine: # Burn 500 kcal await machine.set_target_energy(500) # Or target 5 km await machine.set_target_distance(5000) # Or target 30 minutes await machine.set_target_time(1800) # Or two-zone heart rate time targets (zone1=600s, zone2=600s) await machine.set_target_time(600, 600) await machine.start_resume() await asyncio.sleep(3600) await machine.stop() asyncio.run(goal_workout()) ``` ``` -------------------------------- ### get_client_from_address Source: https://context7.com/dudanov/python-pyftms/llms.txt Connects directly to an FTMS device using its Bluetooth address. It scans for the device and returns a FitnessMachine instance, raising an error if the device is not found within the specified timeout. ```APIDOC ## get_client_from_address ### Description Scans for an FTMS device with the given BLE address (or UUID on macOS) and returns a ready-to-use `FitnessMachine` instance. Raises `BleakDeviceNotFoundError` if the device is not found within the scan timeout. ### Parameters - `address` (str): The Bluetooth address or UUID of the device. - `scan_timeout` (int, optional): Seconds to scan for the device. Defaults to 10. - `timeout` (float, optional): GATT operation timeout in seconds. Defaults to 2.0. - `on_ftms_event` (callable, optional): Callback function for FTMS events. - `on_disconnect` (callable, optional): Callback function for device disconnection. ### Request Example ```python import asyncio from pyftms import get_client_from_address, FitnessMachine, FtmsEvents ADDRESS = "29:84:5A:22:A4:11" # Use UUID string on macOS def on_event(event: FtmsEvents): if event.event_id == "update": print(event.event_data) def on_disconnect(m: FitnessMachine): print("Device disconnected.") async def main(): try: client = await get_client_from_address( ADDRESS, scan_timeout=10, # seconds to scan timeout=2.0, # GATT operation timeout on_ftms_event=on_event, on_disconnect=on_disconnect, ) except Exception as e: print(f"Device not found: {e}") return async with client: print(f"Connected to: {client.name}") print(f"Machine type: {client.machine_type.name}") print(f"Device info: {client.device_info}") await asyncio.sleep(30) asyncio.run(main()) ``` ``` -------------------------------- ### Set Target Power Source: https://context7.com/dudanov/python-pyftms/llms.txt Sets the target wattage for ergometer-style power control on `IndoorBike` or `CrossTrainer`. ```APIDOC ## `set_target_power` — Set target power in watts ### Description Sets the target wattage for ergometer-style power control on `IndoorBike` or `CrossTrainer`. ### Method `set_target_power(watts: int)` ### Parameters - **watts** (int) - The target power in watts. ### Request Example ```python import asyncio from pyftms import get_client_from_address async def power_intervals(): async with await get_client_from_address("29:84:5A:22:A4:11") as bike: await bike.start_resume() # 4×4 interval: 4 min at 250W, 4 min recovery at 100W for _ in range(4): r = await bike.set_target_power(250) print(f"WORK 250 W → {r}") await asyncio.sleep(240) r = await bike.set_target_power(100) print(f"REST 100 W → {r}") await asyncio.sleep(240) await bike.stop() asyncio.run(power_intervals()) ``` ``` -------------------------------- ### Set Target Resistance Source: https://context7.com/dudanov/python-pyftms/llms.txt Sets the target resistance level (unitless, machine-specific scale) on `IndoorBike`, `CrossTrainer`, or `Rower` machines. ```APIDOC ## `set_target_resistance` — Set resistance level ### Description Sets the target resistance level (unitless, machine-specific scale) on `IndoorBike`, `CrossTrainer`, or `Rower` machines. ### Method `set_target_resistance(level: int)` ### Parameters - **level** (int) - The target resistance level. ### Request Example ```python import asyncio from pyftms import get_client_from_address async def resistance_demo(): async with await get_client_from_address("29:84:5A:22:A4:11") as bike: rng = bike.supported_ranges.get("target_resistance") if rng: print(f"Resistance range: {rng.min_value} – {rng.max_value} (step {rng.step})") await bike.start_resume() for level in [1, 5, 10, 15, 10, 5, 1]: r = await bike.set_target_resistance(level) print(f"Resistance level {level} → {r}") await asyncio.sleep(60) await bike.stop() asyncio.run(resistance_demo()) ``` ``` -------------------------------- ### discover_ftms_devices Source: https://context7.com/dudanov/python-pyftms/llms.txt Scans for nearby FTMS Bluetooth devices and yields discovered devices along with their machine types. The scan runs for a specified duration, and devices are deduplicated by address. ```APIDOC ## discover_ftms_devices ### Description Asynchronous generator that scans for BLE devices advertising the FTMS service UUID. Yields `(BLEDevice, MachineType)` tuples for each unique fitness machine discovered during the scan window. Deduplicates by address so each device is yielded only once. ### Parameters #### Query Parameters - **discover_time** (int) - Required - Duration in seconds to scan for devices. ### Request Example ```python import asyncio from pyftms import discover_ftms_devices, MachineType async def scan(): print("Scanning for FTMS devices (10 seconds)...") async for device, machine_type in discover_ftms_devices(discover_time=10): print(f"Found: {machine_type.name}") print(f" Name: {device.name}") print(f" Address: {device.address}") asyncio.run(scan()) ``` ### Response #### Success Response - **device** (BLEDevice) - The discovered Bluetooth device. - **machine_type** (MachineType) - The type of the fitness machine (e.g., Treadmill, Indoor Bike). ### Response Example ``` Found: INDOOR_BIKE Name: MySpinBike Address: 29:84:5A:22:A4:11 ``` ```