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

38 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-11 20:14 +0000

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

2 

3from __future__ import annotations 

4 

5import msgspec 

6 

7from ...types.gatt_enums import ValueType 

8from ..constants import SINT16_MAX, SINT16_MIN 

9from ..context import CharacteristicContext 

10from .base import BaseCharacteristic 

11from .utils import DataParser 

12 

13 

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

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

16 

17 minimum: int # Minimum power in Watts 

18 maximum: int # Maximum power in Watts 

19 

20 def __post_init__(self) -> None: 

21 """Validate power range data.""" 

22 if self.minimum > self.maximum: 

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

24 

25 # Validate range for sint16 (SINT16_MIN to SINT16_MAX) 

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

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

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

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

30 

31 

32class SupportedPowerRangeCharacteristic(BaseCharacteristic[SupportedPowerRangeData]): 

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

34 

35 org.bluetooth.characteristic.supported_power_range 

36 

37 Supported Power Range characteristic. 

38 

39 Specifies minimum and maximum power values for power capability 

40 specification. 

41 """ 

42 

43 min_length = 4 

44 _characteristic_name: str = "Supported Power Range" 

45 # Override since decode_value returns structured SupportedPowerRangeData 

46 _manual_value_type: ValueType | str | None = ValueType.DICT 

47 

48 def _decode_value(self, data: bytearray, ctx: CharacteristicContext | None = None) -> SupportedPowerRangeData: 

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

50 

51 Args: 

52 data: Raw bytes from the characteristic read. 

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

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 if len(data) < 4: 

63 raise ValueError("Supported power range data must be at least 4 bytes") 

64 

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

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

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

68 

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

70 

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

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

73 

74 Args: 

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

76 

77 Returns: 

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

79 

80 """ 

81 if not isinstance(data, SupportedPowerRangeData): 

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

83 

84 # Validate range for sint16 (SINT16_MIN to SINT16_MAX) 

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

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

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

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

89 # Encode as 2 sint16 values (little endian) 

90 result = bytearray() 

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

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

93 

94 return result