Coverage for src / bluetooth_sig / gatt / characteristics / supported_heart_rate_range.py: 97%
32 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"""Supported Heart Rate Range characteristic implementation."""
3from __future__ import annotations
5import msgspec
7from ..constants import UINT8_MAX
8from ..context import CharacteristicContext
9from .base import BaseCharacteristic
10from .utils import DataParser
13class SupportedHeartRateRangeData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods
14 """Data class for supported heart rate range.
16 All values are in beats per minute (BPM), integer precision.
17 """
19 minimum: int # Minimum heart rate in BPM
20 maximum: int # Maximum heart rate in BPM
21 minimum_increment: int # Minimum increment in BPM
23 def __post_init__(self) -> None:
24 """Validate heart rate range data."""
25 if self.minimum > self.maximum:
26 raise ValueError(f"Minimum heart rate {self.minimum} BPM cannot be greater than maximum {self.maximum} BPM")
27 for name, val in [
28 ("minimum", self.minimum),
29 ("maximum", self.maximum),
30 ("minimum_increment", self.minimum_increment),
31 ]:
32 if not 0 <= val <= UINT8_MAX:
33 raise ValueError(f"{name} {val} BPM is outside valid range (0 to {UINT8_MAX})")
36class SupportedHeartRateRangeCharacteristic(BaseCharacteristic[SupportedHeartRateRangeData]):
37 """Supported Heart Rate Range characteristic (0x2AD7).
39 org.bluetooth.characteristic.supported_heart_rate_range
41 Represents the heart rate range supported by a fitness machine.
42 Three fields: minimum heart rate, maximum heart rate, and minimum
43 increment. Each is a uint8 in beats per minute (no scaling).
44 """
46 # Validation attributes
47 expected_length: int = 3 # 3 x uint8
48 min_length: int = 3
50 def _decode_value(
51 self, data: bytearray, ctx: CharacteristicContext | None = None, *, validate: bool = True
52 ) -> SupportedHeartRateRangeData:
53 """Parse supported heart rate range data.
55 Args:
56 data: Raw bytes from the characteristic read.
57 ctx: Optional CharacteristicContext (may be None).
58 validate: Whether to validate ranges (default True).
60 Returns:
61 SupportedHeartRateRangeData with minimum, maximum, and increment.
63 """
64 min_raw = DataParser.parse_int8(data, 0, signed=False)
65 max_raw = DataParser.parse_int8(data, 1, signed=False)
66 inc_raw = DataParser.parse_int8(data, 2, signed=False)
68 return SupportedHeartRateRangeData(
69 minimum=min_raw,
70 maximum=max_raw,
71 minimum_increment=inc_raw,
72 )
74 def _encode_value(self, data: SupportedHeartRateRangeData) -> bytearray:
75 """Encode supported heart rate range to bytes.
77 Args:
78 data: SupportedHeartRateRangeData instance.
80 Returns:
81 Encoded bytes (3 x uint8).
83 """
84 if not isinstance(data, SupportedHeartRateRangeData):
85 raise TypeError(f"Expected SupportedHeartRateRangeData, got {type(data).__name__}")
87 result = bytearray()
88 result.extend(DataParser.encode_int8(data.minimum, signed=False))
89 result.extend(DataParser.encode_int8(data.maximum, signed=False))
90 result.extend(DataParser.encode_int8(data.minimum_increment, signed=False))
91 return result