Coverage for src / bluetooth_sig / gatt / characteristics / luminous_flux_range.py: 94%

32 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-18 11:17 +0000

1"""Luminous Flux Range 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 

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

14 """Data class for luminous flux range. 

15 

16 Each value is a luminous flux in lumens (resolution 1 lm). 

17 """ 

18 

19 minimum: int # Minimum luminous flux in lumens 

20 maximum: int # Maximum luminous flux in lumens 

21 

22 def __post_init__(self) -> None: 

23 """Validate luminous flux range data.""" 

24 if self.minimum > self.maximum: 

25 raise ValueError( 

26 f"Minimum luminous flux {self.minimum} lm cannot be greater than maximum {self.maximum} lm" 

27 ) 

28 for name, val in [("minimum", self.minimum), ("maximum", self.maximum)]: 

29 if not 0 <= val <= UINT16_MAX: 

30 raise ValueError( 

31 f"{name.capitalize()} luminous flux {val} lm is outside valid range (0 to {UINT16_MAX})" 

32 ) 

33 

34 

35class LuminousFluxRangeCharacteristic(BaseCharacteristic[LuminousFluxRangeData]): 

36 """Luminous Flux Range characteristic (0x2B00). 

37 

38 org.bluetooth.characteristic.luminous_flux_range 

39 

40 Represents a luminous flux range as a pair of Luminous Flux values. 

41 Each field is a uint16 with resolution 1 lumen. 

42 """ 

43 

44 # Validation attributes 

45 expected_length: int = 4 # 2 x uint16 

46 min_length: int = 4 

47 

48 def _decode_value( 

49 self, data: bytearray, ctx: CharacteristicContext | None = None, *, validate: bool = True 

50 ) -> LuminousFluxRangeData: 

51 """Parse luminous flux range data (2 x uint16, resolution 1 lm). 

52 

53 Args: 

54 data: Raw bytes from the characteristic read. 

55 ctx: Optional CharacteristicContext (may be None). 

56 validate: Whether to validate ranges (default True). 

57 

58 Returns: 

59 LuminousFluxRangeData with minimum and maximum luminous flux in lumens. 

60 

61 """ 

62 min_raw = DataParser.parse_int16(data, 0, signed=False) 

63 max_raw = DataParser.parse_int16(data, 2, signed=False) 

64 

65 return LuminousFluxRangeData(minimum=min_raw, maximum=max_raw) 

66 

67 def _encode_value(self, data: LuminousFluxRangeData) -> bytearray: 

68 """Encode luminous flux range to bytes. 

69 

70 Args: 

71 data: LuminousFluxRangeData instance. 

72 

73 Returns: 

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

75 

76 """ 

77 if not isinstance(data, LuminousFluxRangeData): 

78 raise TypeError(f"Expected LuminousFluxRangeData, got {type(data).__name__}") 

79 

80 for name, value in [("minimum", data.minimum), ("maximum", data.maximum)]: 

81 if not 0 <= value <= UINT16_MAX: 

82 raise ValueError(f"Luminous flux {name} value {value} exceeds uint16 range") 

83 

84 result = bytearray() 

85 result.extend(DataParser.encode_int16(data.minimum, signed=False)) 

86 result.extend(DataParser.encode_int16(data.maximum, signed=False)) 

87 return result