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

30 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-18 11:17 +0000

1"""CSC 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 CSCFeatures(IntFlag): 

15 """CSC Feature flags as per Bluetooth SIG specification.""" 

16 

17 WHEEL_REVOLUTION_DATA_SUPPORTED = 0x01 

18 CRANK_REVOLUTION_DATA_SUPPORTED = 0x02 

19 MULTIPLE_SENSOR_LOCATIONS_SUPPORTED = 0x04 

20 

21 

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

23 """Parsed data from CSC Feature characteristic.""" 

24 

25 features: CSCFeatures 

26 wheel_revolution_data_supported: bool 

27 crank_revolution_data_supported: bool 

28 multiple_sensor_locations_supported: bool 

29 

30 

31class CSCFeatureCharacteristic(BaseCharacteristic[CSCFeatureData]): 

32 """CSC Feature characteristic (0x2A5C). 

33 

34 Used to expose the supported features of a CSC sensor. 

35 Contains a 16-bit bitmask indicating supported measurement 

36 capabilities. 

37 """ 

38 

39 expected_length: int = 2 

40 min_length: int = 2 

41 

42 def _decode_value( 

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

44 ) -> CSCFeatureData: 

45 """Parse CSC feature data. 

46 

47 Format: 16-bit feature bitmask (little endian). 

48 

49 Args: 

50 data: Raw bytearray from BLE characteristic. 

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

52 validate: Whether to validate ranges (default True) 

53 

54 Returns: 

55 CSCFeatureData containing parsed feature flags. 

56 

57 Raises: 

58 ValueError: If data format is invalid. 

59 

60 """ 

61 # Parse 16-bit unsigned integer (little endian) 

62 feature_mask: int = DataParser.parse_int16(data, 0, signed=False) 

63 

64 # Parse feature flags according to specification 

65 return CSCFeatureData( 

66 features=CSCFeatures(feature_mask), 

67 wheel_revolution_data_supported=bool(feature_mask & CSCFeatures.WHEEL_REVOLUTION_DATA_SUPPORTED), 

68 crank_revolution_data_supported=bool(feature_mask & CSCFeatures.CRANK_REVOLUTION_DATA_SUPPORTED), 

69 multiple_sensor_locations_supported=bool(feature_mask & CSCFeatures.MULTIPLE_SENSOR_LOCATIONS_SUPPORTED), 

70 ) 

71 

72 def _encode_value(self, data: CSCFeatureData) -> bytearray: 

73 """Encode CSC feature value back to bytes. 

74 

75 Args: 

76 data: CSCFeatureData containing CSC feature data 

77 

78 Returns: 

79 Encoded bytes representing the CSC features (uint16) 

80 

81 """ 

82 # Reconstruct the features bitmap from individual flags 

83 features_bitmap = 0 

84 if data.wheel_revolution_data_supported: 

85 features_bitmap |= CSCFeatures.WHEEL_REVOLUTION_DATA_SUPPORTED 

86 if data.crank_revolution_data_supported: 

87 features_bitmap |= CSCFeatures.CRANK_REVOLUTION_DATA_SUPPORTED 

88 if data.multiple_sensor_locations_supported: 

89 features_bitmap |= CSCFeatures.MULTIPLE_SENSOR_LOCATIONS_SUPPORTED 

90 

91 return DataParser.encode_int16(features_bitmap, signed=False)