Coverage for src / bluetooth_sig / gatt / characteristics / relative_value_in_an_illuminance_range.py: 89%
46 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"""Relative Value in an Illuminance Range characteristic implementation."""
3from __future__ import annotations
5import msgspec
7from ..constants import UINT8_MAX, UINT24_MAX
8from ..context import CharacteristicContext
9from .base import BaseCharacteristic
10from .utils import DataParser
12_PERCENTAGE_RESOLUTION = 0.5 # Percentage 8: M=1, d=0, b=-1 -> 0.5%
13_ILLUMINANCE_RESOLUTION = 0.01 # Illuminance: M=1, d=-2, b=0 -> 0.01 lux
16class RelativeValueInAnIlluminanceRangeData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods
17 """Data class for relative value in an illuminance range.
19 Combines a percentage (0.5% resolution) with an illuminance range
20 (min/max in lux, 0.01 lux resolution).
21 """
23 relative_value: float # Percentage (0.5% resolution)
24 minimum_illuminance: float # Minimum illuminance in lux
25 maximum_illuminance: float # Maximum illuminance in lux
27 def __post_init__(self) -> None:
28 """Validate data fields."""
29 max_pct = UINT8_MAX * _PERCENTAGE_RESOLUTION
30 if not 0.0 <= self.relative_value <= max_pct:
31 raise ValueError(f"Relative value {self.relative_value}% is outside valid range (0.0 to {max_pct})")
32 if self.minimum_illuminance > self.maximum_illuminance:
33 raise ValueError(
34 f"Minimum illuminance {self.minimum_illuminance} lux "
35 f"cannot exceed maximum {self.maximum_illuminance} lux"
36 )
37 max_lux = UINT24_MAX * _ILLUMINANCE_RESOLUTION
38 for name, val in [
39 ("minimum_illuminance", self.minimum_illuminance),
40 ("maximum_illuminance", self.maximum_illuminance),
41 ]:
42 if not 0.0 <= val <= max_lux:
43 raise ValueError(f"{name} {val} lux is outside valid range (0.0 to {max_lux})")
46class RelativeValueInAnIlluminanceRangeCharacteristic(
47 BaseCharacteristic[RelativeValueInAnIlluminanceRangeData],
48):
49 """Relative Value in an Illuminance Range characteristic (0x2B0A).
51 org.bluetooth.characteristic.relative_value_in_an_illuminance_range
53 Represents a relative value within an illuminance range. Fields:
54 Percentage 8 (uint8, 0.5%), min illuminance (uint24, 0.01 lux),
55 max illuminance (uint24, 0.01 lux).
56 """
58 expected_length: int = 7 # uint8 + 2 x uint24
59 min_length: int = 7
60 expected_type = RelativeValueInAnIlluminanceRangeData
62 def _decode_value(
63 self, data: bytearray, ctx: CharacteristicContext | None = None, *, validate: bool = True
64 ) -> RelativeValueInAnIlluminanceRangeData:
65 """Parse relative value in an illuminance range.
67 Args:
68 data: Raw bytes (7 bytes).
69 ctx: Optional CharacteristicContext.
70 validate: Whether to validate ranges (default True).
72 Returns:
73 RelativeValueInAnIlluminanceRangeData.
75 """
76 pct_raw = DataParser.parse_int8(data, 0, signed=False)
77 min_raw = DataParser.parse_int24(data, 1, signed=False)
78 max_raw = DataParser.parse_int24(data, 4, signed=False)
80 return RelativeValueInAnIlluminanceRangeData(
81 relative_value=pct_raw * _PERCENTAGE_RESOLUTION,
82 minimum_illuminance=min_raw * _ILLUMINANCE_RESOLUTION,
83 maximum_illuminance=max_raw * _ILLUMINANCE_RESOLUTION,
84 )
86 def _encode_value(self, data: RelativeValueInAnIlluminanceRangeData) -> bytearray:
87 """Encode relative value in an illuminance range.
89 Args:
90 data: RelativeValueInAnIlluminanceRangeData instance.
92 Returns:
93 Encoded bytes (7 bytes).
95 """
96 pct_raw = round(data.relative_value / _PERCENTAGE_RESOLUTION)
97 min_raw = round(data.minimum_illuminance / _ILLUMINANCE_RESOLUTION)
98 max_raw = round(data.maximum_illuminance / _ILLUMINANCE_RESOLUTION)
100 if not 0 <= pct_raw <= UINT8_MAX:
101 raise ValueError(f"Percentage raw {pct_raw} exceeds uint8 range")
102 if not 0 <= min_raw <= UINT24_MAX:
103 raise ValueError(f"Min illuminance raw {min_raw} exceeds uint24 range")
104 if not 0 <= max_raw <= UINT24_MAX:
105 raise ValueError(f"Max illuminance raw {max_raw} exceeds uint24 range")
107 result = bytearray()
108 result.extend(DataParser.encode_int8(pct_raw, signed=False))
109 result.extend(DataParser.encode_int24(min_raw, signed=False))
110 result.extend(DataParser.encode_int24(max_raw, signed=False))
111 return result