Coverage for src / bluetooth_sig / gatt / characteristics / supported_resistance_level_range.py: 95%
40 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 Resistance Level 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
12# Resolution: M=1, d=1, b=0 -> 10 unitless per raw unit
13_RESOLUTION = 10.0
16class SupportedResistanceLevelRangeData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods
17 """Data class for supported resistance level range.
19 All values are unitless with resolution of 10 per raw unit.
20 """
22 minimum: float # Minimum resistance level (unitless)
23 maximum: float # Maximum resistance level (unitless)
24 minimum_increment: float # Minimum increment (unitless)
26 def __post_init__(self) -> None:
27 """Validate resistance level range data."""
28 if self.minimum > self.maximum:
29 raise ValueError(f"Minimum resistance level {self.minimum} cannot be greater than maximum {self.maximum}")
30 max_value = UINT8_MAX * _RESOLUTION
31 for name, val in [
32 ("minimum", self.minimum),
33 ("maximum", self.maximum),
34 ("minimum_increment", self.minimum_increment),
35 ]:
36 if not 0.0 <= val <= max_value:
37 raise ValueError(f"{name} {val} is outside valid range (0.0 to {max_value})")
40class SupportedResistanceLevelRangeCharacteristic(
41 BaseCharacteristic[SupportedResistanceLevelRangeData],
42):
43 """Supported Resistance Level Range characteristic (0x2AD6).
45 org.bluetooth.characteristic.supported_resistance_level_range
47 Represents the resistance level range supported by a fitness machine.
48 Three fields: minimum resistance level, maximum resistance level,
49 and minimum increment. Each is a uint8 with M=1, d=1, b=0 (resolution 10).
50 """
52 # Validation attributes
53 expected_length: int = 3 # 3 x uint8
54 min_length: int = 3
56 def _decode_value(
57 self, data: bytearray, ctx: CharacteristicContext | None = None, *, validate: bool = True
58 ) -> SupportedResistanceLevelRangeData:
59 """Parse supported resistance level range data.
61 Args:
62 data: Raw bytes from the characteristic read.
63 ctx: Optional CharacteristicContext (may be None).
64 validate: Whether to validate ranges (default True).
66 Returns:
67 SupportedResistanceLevelRangeData with minimum, maximum, and
68 increment values.
70 """
71 min_raw = DataParser.parse_int8(data, 0, signed=False)
72 max_raw = DataParser.parse_int8(data, 1, signed=False)
73 inc_raw = DataParser.parse_int8(data, 2, signed=False)
75 return SupportedResistanceLevelRangeData(
76 minimum=min_raw * _RESOLUTION,
77 maximum=max_raw * _RESOLUTION,
78 minimum_increment=inc_raw * _RESOLUTION,
79 )
81 def _encode_value(self, data: SupportedResistanceLevelRangeData) -> bytearray:
82 """Encode supported resistance level range to bytes.
84 Args:
85 data: SupportedResistanceLevelRangeData instance.
87 Returns:
88 Encoded bytes (3 x uint8).
90 """
91 if not isinstance(data, SupportedResistanceLevelRangeData):
92 raise TypeError(f"Expected SupportedResistanceLevelRangeData, got {type(data).__name__}")
94 min_raw = round(data.minimum / _RESOLUTION)
95 max_raw = round(data.maximum / _RESOLUTION)
96 inc_raw = round(data.minimum_increment / _RESOLUTION)
98 for name, value in [
99 ("minimum", min_raw),
100 ("maximum", max_raw),
101 ("increment", inc_raw),
102 ]:
103 if not 0 <= value <= UINT8_MAX:
104 raise ValueError(f"Resistance {name} raw value {value} exceeds uint8 range (0 to {UINT8_MAX})")
106 result = bytearray()
107 result.extend(DataParser.encode_int8(min_raw, signed=False))
108 result.extend(DataParser.encode_int8(max_raw, signed=False))
109 result.extend(DataParser.encode_int8(inc_raw, signed=False))
110 return result