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

1"""Chromaticity Coordinates characteristic implementation.""" 

2 

3from __future__ import annotations 

4 

5import msgspec 

6 

7from ..constants import UINT16_MAX 

8from ..context import CharacteristicContext 

9from .base import BaseCharacteristic 

10from .utils import DataParser 

11 

12# Resolution: 1/65535 (same as Chromaticity Coordinate characteristic) 

13_RESOLUTION = 2**-16 

14 

15 

16class ChromaticityCoordinatesData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods 

17 """Data class for chromaticity coordinates. 

18 

19 Each coordinate is a CIE 1931 chromaticity value in the range [0, 1). 

20 """ 

21 

22 x: float # Chromaticity x-coordinate 

23 y: float # Chromaticity y-coordinate 

24 

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})") 

31 

32 

33class ChromaticityCoordinatesCharacteristic(BaseCharacteristic[ChromaticityCoordinatesData]): 

34 """Chromaticity Coordinates characteristic (0x2AE4). 

35 

36 org.bluetooth.characteristic.chromaticity_coordinates 

37 

38 Represents a pair of CIE 1931 chromaticity coordinates (x, y). 

39 Each coordinate is a uint16 with resolution 1/65535. 

40 """ 

41 

42 # Validation attributes 

43 expected_length: int = 4 # 2 x uint16 

44 min_length: int = 4 

45 

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). 

50 

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). 

55 

56 Returns: 

57 ChromaticityCoordinatesData with x and y coordinate values. 

58 

59 """ 

60 x_raw = DataParser.parse_int16(data, 0, signed=False) 

61 y_raw = DataParser.parse_int16(data, 2, signed=False) 

62 

63 return ChromaticityCoordinatesData( 

64 x=x_raw * _RESOLUTION, 

65 y=y_raw * _RESOLUTION, 

66 ) 

67 

68 def _encode_value(self, data: ChromaticityCoordinatesData) -> bytearray: 

69 """Encode chromaticity coordinates to bytes. 

70 

71 Args: 

72 data: ChromaticityCoordinatesData instance. 

73 

74 Returns: 

75 Encoded bytes (2 x uint16, little-endian). 

76 

77 """ 

78 if not isinstance(data, ChromaticityCoordinatesData): 

79 raise TypeError(f"Expected ChromaticityCoordinatesData, got {type(data).__name__}") 

80 

81 x_raw = round(data.x / _RESOLUTION) 

82 y_raw = round(data.y / _RESOLUTION) 

83 

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") 

87 

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