Coverage for src / bluetooth_sig / gatt / characteristics / power_specification.py: 95%

44 statements  

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

1"""Power Specification characteristic implementation.""" 

2 

3from __future__ import annotations 

4 

5from ..context import CharacteristicContext 

6from .base import BaseCharacteristic 

7from .utils.data_parser import DataParser 

8 

9# Special value constants for Power Specification characteristic 

10VALUE_NOT_VALID = 0xFFFFFE # Indicates value is not valid 

11VALUE_UNKNOWN = 0xFFFFFF # Indicates value is not known 

12 

13 

14class PowerSpecificationData: 

15 """Data class for Power Specification characteristic.""" 

16 

17 def __init__( 

18 self, 

19 minimum: float | None, 

20 typical: float | None, 

21 maximum: float | None, 

22 ) -> None: 

23 """Initialize Power Specification data. 

24 

25 Args: 

26 minimum: Minimum power value in watts, or None if not valid/known 

27 typical: Typical power value in watts, or None if not valid/known 

28 maximum: Maximum power value in watts, or None if not valid/known 

29 """ 

30 self.minimum = minimum 

31 self.typical = typical 

32 self.maximum = maximum 

33 

34 def __eq__(self, other: object) -> bool: 

35 """Check equality with another PowerSpecificationData.""" 

36 if not isinstance(other, PowerSpecificationData): 

37 return NotImplemented 

38 return self.minimum == other.minimum and self.typical == other.typical and self.maximum == other.maximum 

39 

40 def __hash__(self) -> int: 

41 """Return hash for PowerSpecificationData.""" 

42 return hash((self.minimum, self.typical, self.maximum)) 

43 

44 def __repr__(self) -> str: 

45 """Return string representation.""" 

46 return f"PowerSpecificationData(minimum={self.minimum}, typical={self.typical}, maximum={self.maximum})" 

47 

48 

49class PowerSpecificationCharacteristic(BaseCharacteristic[PowerSpecificationData]): 

50 """Power Specification characteristic (0x2B06). 

51 

52 org.bluetooth.characteristic.power_specification 

53 

54 The Power Specification characteristic is used to represent a specification of power values. 

55 """ 

56 

57 def _decode_value( 

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

59 ) -> PowerSpecificationData: 

60 """Decode the power specification values.""" 

61 # Parse three uint24 values (little-endian) 

62 minimum_raw = DataParser.parse_int24(data, 0, signed=False) 

63 typical_raw = DataParser.parse_int24(data, 3, signed=False) 

64 maximum_raw = DataParser.parse_int24(data, 6, signed=False) 

65 

66 # Convert to float values with 0.1 W resolution, handling special values 

67 def _convert_value(raw: int) -> float | None: 

68 if raw == VALUE_NOT_VALID: 

69 return None # Value is not valid 

70 if raw == VALUE_UNKNOWN: 

71 return None # Value is not known 

72 return raw * 0.1 # Resolution 0.1 W 

73 

74 return PowerSpecificationData( 

75 minimum=_convert_value(minimum_raw), 

76 typical=_convert_value(typical_raw), 

77 maximum=_convert_value(maximum_raw), 

78 ) 

79 

80 def _encode_value(self, data: PowerSpecificationData) -> bytearray: 

81 """Encode the power specification values.""" 

82 

83 # Convert float values to uint24 with 0.1 W resolution, handling special values 

84 def _convert_to_raw(value: float | None) -> int: 

85 if value is None: 

86 return VALUE_UNKNOWN # Use "not known" as default for None 

87 return round(value / 0.1) 

88 

89 minimum_raw = _convert_to_raw(data.minimum) 

90 typical_raw = _convert_to_raw(data.typical) 

91 maximum_raw = _convert_to_raw(data.maximum) 

92 

93 # Encode three uint24 values (little-endian) 

94 result = bytearray() 

95 result.extend(DataParser.encode_int24(minimum_raw, signed=False)) 

96 result.extend(DataParser.encode_int24(typical_raw, signed=False)) 

97 result.extend(DataParser.encode_int24(maximum_raw, signed=False)) 

98 return result