Coverage for src / bluetooth_sig / gatt / characteristics / fitness_machine_feature.py: 98%

57 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-03 16:41 +0000

1"""Fitness Machine Feature characteristic (0x2ACC).""" 

2 

3from __future__ import annotations 

4 

5from enum import IntFlag 

6 

7import msgspec 

8 

9from ..context import CharacteristicContext 

10from .base import BaseCharacteristic 

11from .utils import DataParser 

12 

13 

14class FitnessMachineFeatures(IntFlag): 

15 """Fitness Machine feature flags (bits 0-16 of first uint32).""" 

16 

17 AVERAGE_SPEED_SUPPORTED = 0x0001 

18 CADENCE_SUPPORTED = 0x0002 

19 TOTAL_DISTANCE_SUPPORTED = 0x0004 

20 INCLINATION_SUPPORTED = 0x0008 

21 ELEVATION_GAIN_SUPPORTED = 0x0010 

22 PACE_SUPPORTED = 0x0020 

23 STEP_COUNT_SUPPORTED = 0x0040 

24 RESISTANCE_LEVEL_SUPPORTED = 0x0080 

25 STAIR_COUNT_SUPPORTED = 0x0100 

26 EXPENDED_ENERGY_SUPPORTED = 0x0200 

27 HEART_RATE_MEASUREMENT_SUPPORTED = 0x0400 

28 METABOLIC_EQUIVALENT_SUPPORTED = 0x0800 

29 ELAPSED_TIME_SUPPORTED = 0x1000 

30 REMAINING_TIME_SUPPORTED = 0x2000 

31 POWER_MEASUREMENT_SUPPORTED = 0x4000 

32 FORCE_ON_BELT_AND_POWER_OUTPUT_SUPPORTED = 0x8000 

33 USER_DATA_RETENTION_SUPPORTED = 0x10000 

34 

35 

36class TargetSettingFeatures(IntFlag): 

37 """Target Setting feature flags (bits 0-13 of second uint32).""" 

38 

39 SPEED_TARGET_SETTING_SUPPORTED = 0x0001 

40 INCLINATION_TARGET_SETTING_SUPPORTED = 0x0002 

41 RESISTANCE_TARGET_SETTING_SUPPORTED = 0x0004 

42 POWER_TARGET_SETTING_SUPPORTED = 0x0008 

43 HEART_RATE_TARGET_SETTING_SUPPORTED = 0x0010 

44 TARGETED_EXPENDED_ENERGY_CONFIGURATION_SUPPORTED = 0x0020 

45 TARGETED_STEP_NUMBER_CONFIGURATION_SUPPORTED = 0x0040 

46 TARGETED_STRIDE_NUMBER_CONFIGURATION_SUPPORTED = 0x0080 

47 TARGETED_DISTANCE_CONFIGURATION_SUPPORTED = 0x0100 

48 TARGETED_TRAINING_TIME_CONFIGURATION_SUPPORTED = 0x0200 

49 TARGETED_TIME_IN_TWO_HEART_RATE_ZONES_CONFIGURATION_SUPPORTED = 0x0400 

50 TARGETED_TIME_IN_THREE_HEART_RATE_ZONES_CONFIGURATION_SUPPORTED = 0x0800 

51 TARGETED_TIME_IN_FIVE_HEART_RATE_ZONES_CONFIGURATION_SUPPORTED = 0x1000 

52 INDOOR_BIKE_SIMULATION_PARAMETERS_SUPPORTED = 0x2000 

53 

54 

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

56 """Data class for Fitness Machine Feature characteristic. 

57 

58 Contains two bitfields: machine features and target setting features. 

59 """ 

60 

61 fitness_machine_features: FitnessMachineFeatures 

62 target_setting_features: TargetSettingFeatures 

63 

64 

65class FitnessMachineFeatureCharacteristic(BaseCharacteristic[FitnessMachineFeatureData]): 

66 """Fitness Machine Feature characteristic (0x2ACC). 

67 

68 org.bluetooth.characteristic.fitness_machine_feature 

69 

70 Describes the supported features of the fitness machine and 

71 the supported target settings. 

72 """ 

73 

74 expected_length: int = 8 # 2 x uint32 

75 min_length: int = 8 

76 expected_type = FitnessMachineFeatureData 

77 

78 def _decode_value( 

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

80 ) -> FitnessMachineFeatureData: 

81 """Parse Fitness Machine Feature data (2 x uint32 bitfields). 

82 

83 Args: 

84 data: Raw bytes from the characteristic read. 

85 ctx: Optional CharacteristicContext (may be None). 

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

87 

88 Returns: 

89 FitnessMachineFeatureData with machine and target setting features. 

90 

91 """ 

92 machine_raw = DataParser.parse_int32(data, 0, signed=False) 

93 target_raw = DataParser.parse_int32(data, 4, signed=False) 

94 

95 return FitnessMachineFeatureData( 

96 fitness_machine_features=FitnessMachineFeatures(machine_raw), 

97 target_setting_features=TargetSettingFeatures(target_raw), 

98 ) 

99 

100 def _encode_value(self, data: FitnessMachineFeatureData) -> bytearray: 

101 """Encode Fitness Machine Feature data to bytes. 

102 

103 Args: 

104 data: FitnessMachineFeatureData instance. 

105 

106 Returns: 

107 Encoded bytes (2 x uint32, little-endian). 

108 

109 """ 

110 if not isinstance(data, FitnessMachineFeatureData): 

111 raise TypeError(f"Expected FitnessMachineFeatureData, got {type(data).__name__}") 

112 

113 result = bytearray() 

114 result.extend(DataParser.encode_int32(int(data.fitness_machine_features), signed=False)) 

115 result.extend(DataParser.encode_int32(int(data.target_setting_features), signed=False)) 

116 return result