Coverage for src / bluetooth_sig / gatt / characteristics / supported_power_range.py: 82%

34 statements  

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

1"""Supported Power Range characteristic implementation.""" 

2 

3from __future__ import annotations 

4 

5import msgspec 

6 

7from ..constants import SINT16_MAX, SINT16_MIN 

8from ..context import CharacteristicContext 

9from .base import BaseCharacteristic 

10from .utils import DataParser 

11 

12 

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

14 """Data class for supported power range.""" 

15 

16 minimum: int # Minimum power in Watts 

17 maximum: int # Maximum power in Watts 

18 

19 def __post_init__(self) -> None: 

20 """Validate power range data.""" 

21 if self.minimum > self.maximum: 

22 raise ValueError(f"Minimum power {self.minimum} W cannot be greater than maximum {self.maximum} W") 

23 

24 # Validate range for sint16 (SINT16_MIN to SINT16_MAX) 

25 if not SINT16_MIN <= self.minimum <= SINT16_MAX: 

26 raise ValueError(f"Minimum power {self.minimum} W is outside valid range (SINT16_MIN to SINT16_MAX W)") 

27 if not SINT16_MIN <= self.maximum <= SINT16_MAX: 

28 raise ValueError(f"Maximum power {self.maximum} W is outside valid range (SINT16_MIN to SINT16_MAX W)") 

29 

30 

31class SupportedPowerRangeCharacteristic(BaseCharacteristic[SupportedPowerRangeData]): 

32 """Supported Power Range characteristic (0x2AD8). 

33 

34 org.bluetooth.characteristic.supported_power_range 

35 

36 Supported Power Range characteristic. 

37 

38 Specifies minimum and maximum power values for power capability 

39 specification. 

40 """ 

41 

42 min_length = 4 

43 _characteristic_name: str = "Supported Power Range" 

44 

45 def _decode_value( 

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

47 ) -> SupportedPowerRangeData: 

48 """Parse supported power range data (2x sint16 in watts). 

49 

50 Args: 

51 data: Raw bytes from the characteristic read. 

52 ctx: Optional CharacteristicContext providing surrounding context (may be None). 

53 validate: Whether to validate ranges (default True) 

54 

55 Returns: 

56 SupportedPowerRangeData with minimum and maximum power values in Watts. 

57 

58 Raises: 

59 ValueError: If data is insufficient. 

60 

61 """ 

62 # Convert 2x sint16 (little endian) to power range in Watts 

63 min_power_raw = DataParser.parse_int16(data, 0, signed=True) 

64 max_power_raw = DataParser.parse_int16(data, 2, signed=True) 

65 

66 return SupportedPowerRangeData(minimum=min_power_raw, maximum=max_power_raw) 

67 

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

69 """Encode supported power range value back to bytes. 

70 

71 Args: 

72 data: SupportedPowerRangeData instance with 'minimum' and 'maximum' power values in Watts 

73 

74 Returns: 

75 Encoded bytes representing the power range (2x sint16) 

76 

77 """ 

78 if not isinstance(data, SupportedPowerRangeData): 

79 raise TypeError(f"Supported power range data must be a SupportedPowerRangeData, got {type(data).__name__}") 

80 

81 # Validate range for sint16 (SINT16_MIN to SINT16_MAX) 

82 if not SINT16_MIN <= data.minimum <= SINT16_MAX: 

83 raise ValueError(f"Minimum power {data.minimum} exceeds sint16 range") 

84 if not SINT16_MIN <= data.maximum <= SINT16_MAX: 

85 raise ValueError(f"Maximum power {data.maximum} exceeds sint16 range") 

86 # Encode as 2 sint16 values (little endian) 

87 result = bytearray() 

88 result.extend(DataParser.encode_int16(data.minimum, signed=True)) 

89 result.extend(DataParser.encode_int16(data.maximum, signed=True)) 

90 

91 return result