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
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-03 16:41 +0000
1"""Fitness Machine Feature characteristic (0x2ACC)."""
3from __future__ import annotations
5from enum import IntFlag
7import msgspec
9from ..context import CharacteristicContext
10from .base import BaseCharacteristic
11from .utils import DataParser
14class FitnessMachineFeatures(IntFlag):
15 """Fitness Machine feature flags (bits 0-16 of first uint32)."""
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
36class TargetSettingFeatures(IntFlag):
37 """Target Setting feature flags (bits 0-13 of second uint32)."""
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
55class FitnessMachineFeatureData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods
56 """Data class for Fitness Machine Feature characteristic.
58 Contains two bitfields: machine features and target setting features.
59 """
61 fitness_machine_features: FitnessMachineFeatures
62 target_setting_features: TargetSettingFeatures
65class FitnessMachineFeatureCharacteristic(BaseCharacteristic[FitnessMachineFeatureData]):
66 """Fitness Machine Feature characteristic (0x2ACC).
68 org.bluetooth.characteristic.fitness_machine_feature
70 Describes the supported features of the fitness machine and
71 the supported target settings.
72 """
74 expected_length: int = 8 # 2 x uint32
75 min_length: int = 8
76 expected_type = FitnessMachineFeatureData
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).
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).
88 Returns:
89 FitnessMachineFeatureData with machine and target setting features.
91 """
92 machine_raw = DataParser.parse_int32(data, 0, signed=False)
93 target_raw = DataParser.parse_int32(data, 4, signed=False)
95 return FitnessMachineFeatureData(
96 fitness_machine_features=FitnessMachineFeatures(machine_raw),
97 target_setting_features=TargetSettingFeatures(target_raw),
98 )
100 def _encode_value(self, data: FitnessMachineFeatureData) -> bytearray:
101 """Encode Fitness Machine Feature data to bytes.
103 Args:
104 data: FitnessMachineFeatureData instance.
106 Returns:
107 Encoded bytes (2 x uint32, little-endian).
109 """
110 if not isinstance(data, FitnessMachineFeatureData):
111 raise TypeError(f"Expected FitnessMachineFeatureData, got {type(data).__name__}")
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