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

31 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-11 20:14 +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 

41 def _decode_value(self, data: bytearray, ctx: CharacteristicContext | None = None) -> CSCFeatureData: 

42 """Parse CSC feature data. 

43 

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

45 

46 Args: 

47 data: Raw bytearray from BLE characteristic. 

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

49 

50 Returns: 

51 CSCFeatureData containing parsed feature flags. 

52 

53 Raises: 

54 ValueError: If data format is invalid. 

55 

56 """ 

57 if len(data) < 2: 

58 raise ValueError("CSC Feature data must be at least 2 bytes") 

59 

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

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

62 

63 # Parse feature flags according to specification 

64 return CSCFeatureData( 

65 features=CSCFeatures(feature_mask), 

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

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

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

69 ) 

70 

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

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

73 

74 Args: 

75 data: CSCFeatureData containing CSC feature data 

76 

77 Returns: 

78 Encoded bytes representing the CSC features (uint16) 

79 

80 """ 

81 # Reconstruct the features bitmap from individual flags 

82 features_bitmap = 0 

83 if data.wheel_revolution_data_supported: 

84 features_bitmap |= CSCFeatures.WHEEL_REVOLUTION_DATA_SUPPORTED 

85 if data.crank_revolution_data_supported: 

86 features_bitmap |= CSCFeatures.CRANK_REVOLUTION_DATA_SUPPORTED 

87 if data.multiple_sensor_locations_supported: 

88 features_bitmap |= CSCFeatures.MULTIPLE_SENSOR_LOCATIONS_SUPPORTED 

89 

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