Coverage for src / bluetooth_sig / gatt / characteristics / fitness_machine_status.py: 100%
45 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-03 16:41 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-03 16:41 +0000
1"""Fitness Machine Status characteristic (0x2ADA)."""
3from __future__ import annotations
5from enum import IntEnum
7import msgspec
9from ..context import CharacteristicContext
10from .base import BaseCharacteristic
11from .utils import DataParser
14class FitnessMachineStatusOpCode(IntEnum):
15 """Fitness Machine Status operation codes per FTMS specification."""
17 RESET = 0x01
18 FITNESS_MACHINE_STOPPED_OR_PAUSED_BY_USER = 0x02
19 FITNESS_MACHINE_STOPPED_BY_SAFETY_KEY = 0x03
20 FITNESS_MACHINE_STARTED_OR_RESUMED_BY_USER = 0x04
21 TARGET_SPEED_CHANGED = 0x05
22 TARGET_INCLINATION_CHANGED = 0x06
23 TARGET_RESISTANCE_LEVEL_CHANGED = 0x07
24 TARGET_POWER_CHANGED = 0x08
25 TARGET_HEART_RATE_CHANGED = 0x09
26 TARGETED_EXPENDED_ENERGY_CHANGED = 0x0A
27 TARGETED_NUMBER_OF_STEPS_CHANGED = 0x0B
28 TARGETED_NUMBER_OF_STRIDES_CHANGED = 0x0C
29 TARGETED_DISTANCE_CHANGED = 0x0D
30 TARGETED_TRAINING_TIME_CHANGED = 0x0E
31 TARGETED_TIME_IN_TWO_HEART_RATE_ZONES_CHANGED = 0x0F
32 TARGETED_TIME_IN_THREE_HEART_RATE_ZONES_CHANGED = 0x10
33 TARGETED_TIME_IN_FIVE_HEART_RATE_ZONES_CHANGED = 0x11
34 INDOOR_BIKE_SIMULATION_PARAMETERS_CHANGED = 0x12
35 WHEEL_CIRCUMFERENCE_CHANGED = 0x13
36 SPIN_DOWN_STATUS = 0x14
37 TARGETED_CADENCE_CHANGED = 0x15
38 CONTROL_PERMISSION_LOST = 0xFF
41class FitnessMachineStatusData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods
42 """Parsed data from Fitness Machine Status characteristic.
44 The parameter field contains opcode-specific data as raw bytes,
45 or None for opcodes with no parameters.
46 """
48 op_code: FitnessMachineStatusOpCode
49 parameter: bytes | None = None
52class FitnessMachineStatusCharacteristic(BaseCharacteristic[FitnessMachineStatusData]):
53 """Fitness Machine Status characteristic (0x2ADA).
55 org.bluetooth.characteristic.fitness_machine_status
57 Notifies the client about status changes of the fitness machine,
58 including target setting changes and machine state transitions.
59 """
61 min_length = 1
62 allow_variable_length = True
64 def _decode_value(
65 self, data: bytearray, ctx: CharacteristicContext | None = None, *, validate: bool = True
66 ) -> FitnessMachineStatusData:
67 """Parse Fitness Machine Status data.
69 Format: Op Code (uint8) + optional parameter (variable).
71 Args:
72 data: Raw bytearray from BLE characteristic.
73 ctx: Optional CharacteristicContext (may be None).
74 validate: Whether to validate ranges (default True).
76 Returns:
77 FitnessMachineStatusData containing parsed status data.
79 """
80 op_code_raw = DataParser.parse_int8(data, 0, signed=False)
81 op_code = FitnessMachineStatusOpCode(op_code_raw)
83 parameter = bytes(data[1:]) if len(data) > 1 else None
85 return FitnessMachineStatusData(
86 op_code=op_code,
87 parameter=parameter,
88 )
90 def _encode_value(self, data: FitnessMachineStatusData) -> bytearray:
91 """Encode Fitness Machine Status data to bytes.
93 Args:
94 data: FitnessMachineStatusData instance.
96 Returns:
97 Encoded bytes representing the status notification.
99 """
100 result = bytearray([int(data.op_code)])
102 if data.parameter is not None:
103 result.extend(data.parameter)
105 return result