Coverage for src / bluetooth_sig / gatt / characteristics / chromaticity_in_cct_and_duv_values.py: 90%
39 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"""Chromaticity in CCT and Duv Values characteristic implementation."""
3from __future__ import annotations
5import msgspec
7from ...types.gatt_enums import CharacteristicRole
8from ..constants import SINT16_MAX, SINT16_MIN, UINT16_MAX
9from ..context import CharacteristicContext
10from .base import BaseCharacteristic
11from .utils import DataParser
13# Duv resolution: M=1, d=-5, b=0 → 0.00001
14_DUV_RESOLUTION = 1e-5
17class ChromaticityInCCTAndDuvData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods
18 """Data class for Chromaticity in CCT and Duv Values.
20 Combines Correlated Color Temperature (Kelvin) with Chromatic
21 Distance from Planckian (unitless Duv).
22 """
24 correlated_color_temperature: int # Kelvin (uint16, raw)
25 chromaticity_distance_from_planckian: float # Unitless Duv (sint16, scaled)
27 def __post_init__(self) -> None:
28 """Validate CCT and Duv data."""
29 if not 0 <= self.correlated_color_temperature <= UINT16_MAX:
30 raise ValueError(f"CCT {self.correlated_color_temperature} K is outside valid range (0 to {UINT16_MAX})")
31 duv_min = SINT16_MIN * _DUV_RESOLUTION
32 duv_max = SINT16_MAX * _DUV_RESOLUTION
33 if not duv_min <= self.chromaticity_distance_from_planckian <= duv_max:
34 raise ValueError(
35 f"Duv {self.chromaticity_distance_from_planckian} is outside valid range ({duv_min} to {duv_max})"
36 )
39class ChromaticityInCCTAndDuvValuesCharacteristic(BaseCharacteristic[ChromaticityInCCTAndDuvData]):
40 """Chromaticity in CCT and Duv Values characteristic (0x2AE5).
42 org.bluetooth.characteristic.chromaticity_in_cct_and_duv_values
44 Combines Correlated Color Temperature and Chromatic Distance from
45 Planckian into a single composite characteristic.
47 Field 1: CCT — uint16, raw Kelvin (references Correlated Color Temperature).
48 Field 2: Duv — sint16, M=1 d=-5 b=0 (references Chromatic Distance From Planckian).
49 """
51 _manual_role = CharacteristicRole.MEASUREMENT
53 # Validation attributes
54 expected_length: int = 4 # uint16 + sint16
55 min_length: int = 4
57 def _decode_value(
58 self, data: bytearray, ctx: CharacteristicContext | None = None, *, validate: bool = True
59 ) -> ChromaticityInCCTAndDuvData:
60 """Parse CCT and Duv values.
62 Args:
63 data: Raw bytes from the characteristic read.
64 ctx: Optional CharacteristicContext (may be None).
65 validate: Whether to validate ranges (default True).
67 Returns:
68 ChromaticityInCCTAndDuvData with CCT and Duv fields.
70 """
71 cct_raw = DataParser.parse_int16(data, 0, signed=False)
72 duv_raw = DataParser.parse_int16(data, 2, signed=True)
74 return ChromaticityInCCTAndDuvData(
75 correlated_color_temperature=cct_raw,
76 chromaticity_distance_from_planckian=duv_raw * _DUV_RESOLUTION,
77 )
79 def _encode_value(self, data: ChromaticityInCCTAndDuvData) -> bytearray:
80 """Encode CCT and Duv values to bytes.
82 Args:
83 data: ChromaticityInCCTAndDuvData instance.
85 Returns:
86 Encoded bytes (uint16 + sint16, little-endian).
88 """
89 if not isinstance(data, ChromaticityInCCTAndDuvData):
90 raise TypeError(f"Expected ChromaticityInCCTAndDuvData, got {type(data).__name__}")
92 cct_raw = data.correlated_color_temperature
93 duv_raw = round(data.chromaticity_distance_from_planckian / _DUV_RESOLUTION)
95 if not 0 <= cct_raw <= UINT16_MAX:
96 raise ValueError(f"CCT raw value {cct_raw} exceeds uint16 range")
97 if not SINT16_MIN <= duv_raw <= SINT16_MAX:
98 raise ValueError(f"Duv raw value {duv_raw} exceeds sint16 range")
100 result = bytearray()
101 result.extend(DataParser.encode_int16(cct_raw, signed=False))
102 result.extend(DataParser.encode_int16(duv_raw, signed=True))
103 return result