Coverage for src / bluetooth_sig / gatt / characteristics / rsc_feature.py: 100%
38 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"""RSC 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 RSCFeatures(IntFlag):
15 """RSC Feature flags as per Bluetooth SIG specification."""
17 INSTANTANEOUS_STRIDE_LENGTH_SUPPORTED = 0x01
18 TOTAL_DISTANCE_SUPPORTED = 0x02
19 WALKING_OR_RUNNING_STATUS_SUPPORTED = 0x04
20 CALIBRATION_PROCEDURE_SUPPORTED = 0x08
21 MULTIPLE_SENSOR_LOCATIONS_SUPPORTED = 0x10
24class RSCFeatureData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods
25 """Parsed data from RSC Feature characteristic."""
27 features: RSCFeatures
28 instantaneous_stride_length_supported: bool
29 total_distance_supported: bool
30 walking_or_running_status_supported: bool
31 calibration_procedure_supported: bool
32 multiple_sensor_locations_supported: bool
35class RSCFeatureCharacteristic(BaseCharacteristic[RSCFeatureData]):
36 """RSC Feature characteristic (0x2A54).
38 Used to expose the supported features of an RSC sensor.
39 Contains a 16-bit bitmask indicating supported measurement
40 capabilities.
41 """
43 expected_length: int = 2
44 min_length: int = 2
46 def _decode_value(
47 self, data: bytearray, ctx: CharacteristicContext | None = None, *, validate: bool = True
48 ) -> RSCFeatureData:
49 """Parse RSC feature data.
51 Format: 16-bit feature bitmask (little endian).
53 Args:
54 data: Raw bytearray from BLE characteristic.
55 ctx: Optional CharacteristicContext providing surrounding context (may be None).
56 validate: Whether to validate ranges (default True)
58 Returns:
59 RSCFeatureData containing parsed feature flags.
61 Raises:
62 ValueError: If data format is invalid.
64 """
65 # Parse 16-bit unsigned integer (little endian)
66 feature_mask: int = DataParser.parse_int16(data, 0, signed=False)
68 # Parse feature flags according to specification
69 return RSCFeatureData(
70 features=RSCFeatures(feature_mask),
71 instantaneous_stride_length_supported=bool(
72 feature_mask & RSCFeatures.INSTANTANEOUS_STRIDE_LENGTH_SUPPORTED
73 ),
74 total_distance_supported=bool(feature_mask & RSCFeatures.TOTAL_DISTANCE_SUPPORTED),
75 walking_or_running_status_supported=bool(feature_mask & RSCFeatures.WALKING_OR_RUNNING_STATUS_SUPPORTED),
76 calibration_procedure_supported=bool(feature_mask & RSCFeatures.CALIBRATION_PROCEDURE_SUPPORTED),
77 multiple_sensor_locations_supported=bool(feature_mask & RSCFeatures.MULTIPLE_SENSOR_LOCATIONS_SUPPORTED),
78 )
80 def _encode_value(self, data: RSCFeatureData) -> bytearray:
81 """Encode RSC feature value back to bytes.
83 Args:
84 data: RSCFeatureData containing RSC feature data
86 Returns:
87 Encoded bytes representing the RSC features (uint16)
89 """
90 # Reconstruct the features bitmap from individual flags
91 features_bitmap = 0
92 if data.instantaneous_stride_length_supported:
93 features_bitmap |= RSCFeatures.INSTANTANEOUS_STRIDE_LENGTH_SUPPORTED
94 if data.total_distance_supported:
95 features_bitmap |= RSCFeatures.TOTAL_DISTANCE_SUPPORTED
96 if data.walking_or_running_status_supported:
97 features_bitmap |= RSCFeatures.WALKING_OR_RUNNING_STATUS_SUPPORTED
98 if data.calibration_procedure_supported:
99 features_bitmap |= RSCFeatures.CALIBRATION_PROCEDURE_SUPPORTED
100 if data.multiple_sensor_locations_supported:
101 features_bitmap |= RSCFeatures.MULTIPLE_SENSOR_LOCATIONS_SUPPORTED
103 return DataParser.encode_int16(features_bitmap, signed=False)