Coverage for src / bluetooth_sig / gatt / characteristics / chromaticity_coordinates.py: 94%
34 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 Coordinates characteristic implementation."""
3from __future__ import annotations
5import msgspec
7from ..constants import UINT16_MAX
8from ..context import CharacteristicContext
9from .base import BaseCharacteristic
10from .utils import DataParser
12# Resolution: 1/65535 (same as Chromaticity Coordinate characteristic)
13_RESOLUTION = 2**-16
16class ChromaticityCoordinatesData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods
17 """Data class for chromaticity coordinates.
19 Each coordinate is a CIE 1931 chromaticity value in the range [0, 1).
20 """
22 x: float # Chromaticity x-coordinate
23 y: float # Chromaticity y-coordinate
25 def __post_init__(self) -> None:
26 """Validate chromaticity coordinate data."""
27 max_value = UINT16_MAX * _RESOLUTION
28 for name, val in [("x", self.x), ("y", self.y)]:
29 if not 0.0 <= val <= max_value:
30 raise ValueError(f"Chromaticity {name}-coordinate {val} is outside valid range (0.0 to {max_value})")
33class ChromaticityCoordinatesCharacteristic(BaseCharacteristic[ChromaticityCoordinatesData]):
34 """Chromaticity Coordinates characteristic (0x2AE4).
36 org.bluetooth.characteristic.chromaticity_coordinates
38 Represents a pair of CIE 1931 chromaticity coordinates (x, y).
39 Each coordinate is a uint16 with resolution 1/65535.
40 """
42 # Validation attributes
43 expected_length: int = 4 # 2 x uint16
44 min_length: int = 4
46 def _decode_value(
47 self, data: bytearray, ctx: CharacteristicContext | None = None, *, validate: bool = True
48 ) -> ChromaticityCoordinatesData:
49 """Parse chromaticity coordinates data (2 x uint16, resolution 1/65535).
51 Args:
52 data: Raw bytes from the characteristic read.
53 ctx: Optional CharacteristicContext (may be None).
54 validate: Whether to validate ranges (default True).
56 Returns:
57 ChromaticityCoordinatesData with x and y coordinate values.
59 """
60 x_raw = DataParser.parse_int16(data, 0, signed=False)
61 y_raw = DataParser.parse_int16(data, 2, signed=False)
63 return ChromaticityCoordinatesData(
64 x=x_raw * _RESOLUTION,
65 y=y_raw * _RESOLUTION,
66 )
68 def _encode_value(self, data: ChromaticityCoordinatesData) -> bytearray:
69 """Encode chromaticity coordinates to bytes.
71 Args:
72 data: ChromaticityCoordinatesData instance.
74 Returns:
75 Encoded bytes (2 x uint16, little-endian).
77 """
78 if not isinstance(data, ChromaticityCoordinatesData):
79 raise TypeError(f"Expected ChromaticityCoordinatesData, got {type(data).__name__}")
81 x_raw = round(data.x / _RESOLUTION)
82 y_raw = round(data.y / _RESOLUTION)
84 for name, value in [("x", x_raw), ("y", y_raw)]:
85 if not 0 <= value <= UINT16_MAX:
86 raise ValueError(f"Chromaticity {name}-coordinate raw value {value} exceeds uint16 range")
88 result = bytearray()
89 result.extend(DataParser.encode_int16(x_raw, signed=False))
90 result.extend(DataParser.encode_int16(y_raw, signed=False))
91 return result