Coverage for src / bluetooth_sig / gatt / characteristics / cycling_power_feature.py: 100%

39 statements  

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

1"""Cycling Power Feature characteristic implementation.""" 

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 CyclingPowerFeatures(IntFlag): 

15 """Cycling Power Feature flags as per CPS v1.1 (bits 0-21).""" 

16 

17 PEDAL_POWER_BALANCE_SUPPORTED = 0x00000001 

18 ACCUMULATED_TORQUE_SUPPORTED = 0x00000002 

19 WHEEL_REVOLUTION_DATA_SUPPORTED = 0x00000004 

20 CRANK_REVOLUTION_DATA_SUPPORTED = 0x00000008 

21 EXTREME_MAGNITUDES_SUPPORTED = 0x00000010 

22 EXTREME_ANGLES_SUPPORTED = 0x00000020 

23 TOP_AND_BOTTOM_DEAD_SPOT_ANGLES_SUPPORTED = 0x00000040 

24 ACCUMULATED_ENERGY_SUPPORTED = 0x00000080 

25 OFFSET_COMPENSATION_INDICATOR_SUPPORTED = 0x00000100 

26 OFFSET_COMPENSATION_SUPPORTED = 0x00000200 

27 CONTENT_MASKING_SUPPORTED = 0x00000400 

28 MULTIPLE_SENSOR_LOCATIONS_SUPPORTED = 0x00000800 

29 CRANK_LENGTH_ADJUSTMENT_SUPPORTED = 0x00001000 

30 CHAIN_LENGTH_ADJUSTMENT_SUPPORTED = 0x00002000 

31 CHAIN_WEIGHT_ADJUSTMENT_SUPPORTED = 0x00004000 

32 SPAN_LENGTH_ADJUSTMENT_SUPPORTED = 0x00008000 

33 SENSOR_MEASUREMENT_CONTEXT = 0x00010000 

34 INSTANTANEOUS_MEASUREMENT_DIRECTION_SUPPORTED = 0x00020000 

35 FACTORY_CALIBRATION_DATE_SUPPORTED = 0x00040000 

36 ENHANCED_OFFSET_COMPENSATION_SUPPORTED = 0x00080000 

37 DISTRIBUTED_SYSTEM_SUPPORT_BIT0 = 0x00100000 

38 DISTRIBUTED_SYSTEM_SUPPORT_BIT1 = 0x00200000 

39 

40 

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

42 """Parsed data from Cycling Power Feature characteristic.""" 

43 

44 features: CyclingPowerFeatures 

45 

46 

47class CyclingPowerFeatureCharacteristic(BaseCharacteristic[CyclingPowerFeatureData]): 

48 """Cycling Power Feature characteristic (0x2A65). 

49 

50 Used to expose the supported features of a cycling power sensor. 

51 Contains a 32-bit bitmask indicating supported measurement 

52 capabilities (bits 0-21 per CPS v1.1). 

53 """ 

54 

55 expected_length: int = 4 

56 min_length: int = 4 

57 

58 def _decode_value( 

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

60 ) -> CyclingPowerFeatureData: 

61 """Parse cycling power feature data. 

62 

63 Format: 32-bit feature bitmask (little endian). 

64 

65 Args: 

66 data: Raw bytearray from BLE characteristic. 

67 ctx: Optional CharacteristicContext providing surrounding context (may be None). 

68 validate: Whether to validate ranges (default True) 

69 

70 Returns: 

71 CyclingPowerFeatureData containing parsed feature flags. 

72 

73 Raises: 

74 ValueError: If data format is invalid. 

75 

76 """ 

77 feature_mask: int = DataParser.parse_int32(data, 0, signed=False) 

78 

79 return CyclingPowerFeatureData( 

80 features=CyclingPowerFeatures(feature_mask), 

81 ) 

82 

83 def _encode_value(self, data: CyclingPowerFeatureData) -> bytearray: 

84 """Encode cycling power feature value back to bytes. 

85 

86 Args: 

87 data: CyclingPowerFeatureData containing cycling power feature data 

88 

89 Returns: 

90 Encoded bytes representing the cycling power features (uint32) 

91 

92 """ 

93 return DataParser.encode_int32(int(data.features), signed=False)