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
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-18 11:17 +0000
1"""CSC Feature characteristic implementation."""
3from __future__ import annotations
5from enum import IntFlag
7import msgspec
9from ..context import CharacteristicContext
10from .base import BaseCharacteristic
11from .utils import DataParser
14class CSCFeatures(IntFlag):
15 """CSC Feature flags as per Bluetooth SIG specification."""
17 WHEEL_REVOLUTION_DATA_SUPPORTED = 0x01
18 CRANK_REVOLUTION_DATA_SUPPORTED = 0x02
19 MULTIPLE_SENSOR_LOCATIONS_SUPPORTED = 0x04
22class CSCFeatureData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods
23 """Parsed data from CSC Feature characteristic."""
25 features: CSCFeatures
26 wheel_revolution_data_supported: bool
27 crank_revolution_data_supported: bool
28 multiple_sensor_locations_supported: bool
31class CSCFeatureCharacteristic(BaseCharacteristic[CSCFeatureData]):
32 """CSC Feature characteristic (0x2A5C).
34 Used to expose the supported features of a CSC sensor.
35 Contains a 16-bit bitmask indicating supported measurement
36 capabilities.
37 """
39 expected_length: int = 2
40 min_length: int = 2
42 def _decode_value(
43 self, data: bytearray, ctx: CharacteristicContext | None = None, *, validate: bool = True
44 ) -> CSCFeatureData:
45 """Parse CSC feature data.
47 Format: 16-bit feature bitmask (little endian).
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)
54 Returns:
55 CSCFeatureData containing parsed feature flags.
57 Raises:
58 ValueError: If data format is invalid.
60 """
61 # Parse 16-bit unsigned integer (little endian)
62 feature_mask: int = DataParser.parse_int16(data, 0, signed=False)
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 )
72 def _encode_value(self, data: CSCFeatureData) -> bytearray:
73 """Encode CSC feature value back to bytes.
75 Args:
76 data: CSCFeatureData containing CSC feature data
78 Returns:
79 Encoded bytes representing the CSC features (uint16)
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
91 return DataParser.encode_int16(features_bitmap, signed=False)