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
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-03 16:41 +0000
1"""Cycling Power 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 CyclingPowerFeatures(IntFlag):
15 """Cycling Power Feature flags as per CPS v1.1 (bits 0-21)."""
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
41class CyclingPowerFeatureData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods
42 """Parsed data from Cycling Power Feature characteristic."""
44 features: CyclingPowerFeatures
47class CyclingPowerFeatureCharacteristic(BaseCharacteristic[CyclingPowerFeatureData]):
48 """Cycling Power Feature characteristic (0x2A65).
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 """
55 expected_length: int = 4
56 min_length: int = 4
58 def _decode_value(
59 self, data: bytearray, ctx: CharacteristicContext | None = None, *, validate: bool = True
60 ) -> CyclingPowerFeatureData:
61 """Parse cycling power feature data.
63 Format: 32-bit feature bitmask (little endian).
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)
70 Returns:
71 CyclingPowerFeatureData containing parsed feature flags.
73 Raises:
74 ValueError: If data format is invalid.
76 """
77 feature_mask: int = DataParser.parse_int32(data, 0, signed=False)
79 return CyclingPowerFeatureData(
80 features=CyclingPowerFeatures(feature_mask),
81 )
83 def _encode_value(self, data: CyclingPowerFeatureData) -> bytearray:
84 """Encode cycling power feature value back to bytes.
86 Args:
87 data: CyclingPowerFeatureData containing cycling power feature data
89 Returns:
90 Encoded bytes representing the cycling power features (uint32)
92 """
93 return DataParser.encode_int32(int(data.features), signed=False)