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

1"""Fitness Machine Status characteristic (0x2ADA).""" 

2 

3from __future__ import annotations 

4 

5from enum import IntEnum 

6 

7import msgspec 

8 

9from ..context import CharacteristicContext 

10from .base import BaseCharacteristic 

11from .utils import DataParser 

12 

13 

14class FitnessMachineStatusOpCode(IntEnum): 

15 """Fitness Machine Status operation codes per FTMS specification.""" 

16 

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 

39 

40 

41class FitnessMachineStatusData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods 

42 """Parsed data from Fitness Machine Status characteristic. 

43 

44 The parameter field contains opcode-specific data as raw bytes, 

45 or None for opcodes with no parameters. 

46 """ 

47 

48 op_code: FitnessMachineStatusOpCode 

49 parameter: bytes | None = None 

50 

51 

52class FitnessMachineStatusCharacteristic(BaseCharacteristic[FitnessMachineStatusData]): 

53 """Fitness Machine Status characteristic (0x2ADA). 

54 

55 org.bluetooth.characteristic.fitness_machine_status 

56 

57 Notifies the client about status changes of the fitness machine, 

58 including target setting changes and machine state transitions. 

59 """ 

60 

61 min_length = 1 

62 allow_variable_length = True 

63 

64 def _decode_value( 

65 self, data: bytearray, ctx: CharacteristicContext | None = None, *, validate: bool = True 

66 ) -> FitnessMachineStatusData: 

67 """Parse Fitness Machine Status data. 

68 

69 Format: Op Code (uint8) + optional parameter (variable). 

70 

71 Args: 

72 data: Raw bytearray from BLE characteristic. 

73 ctx: Optional CharacteristicContext (may be None). 

74 validate: Whether to validate ranges (default True). 

75 

76 Returns: 

77 FitnessMachineStatusData containing parsed status data. 

78 

79 """ 

80 op_code_raw = DataParser.parse_int8(data, 0, signed=False) 

81 op_code = FitnessMachineStatusOpCode(op_code_raw) 

82 

83 parameter = bytes(data[1:]) if len(data) > 1 else None 

84 

85 return FitnessMachineStatusData( 

86 op_code=op_code, 

87 parameter=parameter, 

88 ) 

89 

90 def _encode_value(self, data: FitnessMachineStatusData) -> bytearray: 

91 """Encode Fitness Machine Status data to bytes. 

92 

93 Args: 

94 data: FitnessMachineStatusData instance. 

95 

96 Returns: 

97 Encoded bytes representing the status notification. 

98 

99 """ 

100 result = bytearray([int(data.op_code)]) 

101 

102 if data.parameter is not None: 

103 result.extend(data.parameter) 

104 

105 return result