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
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-18 11:17 +0000
1"""Power Specification characteristic implementation."""
3from __future__ import annotations
5from ..context import CharacteristicContext
6from .base import BaseCharacteristic
7from .utils.data_parser import DataParser
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
14class PowerSpecificationData:
15 """Data class for Power Specification characteristic."""
17 def __init__(
18 self,
19 minimum: float | None,
20 typical: float | None,
21 maximum: float | None,
22 ) -> None:
23 """Initialize Power Specification data.
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
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
40 def __hash__(self) -> int:
41 """Return hash for PowerSpecificationData."""
42 return hash((self.minimum, self.typical, self.maximum))
44 def __repr__(self) -> str:
45 """Return string representation."""
46 return f"PowerSpecificationData(minimum={self.minimum}, typical={self.typical}, maximum={self.maximum})"
49class PowerSpecificationCharacteristic(BaseCharacteristic[PowerSpecificationData]):
50 """Power Specification characteristic (0x2B06).
52 org.bluetooth.characteristic.power_specification
54 The Power Specification characteristic is used to represent a specification of power values.
55 """
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)
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
74 return PowerSpecificationData(
75 minimum=_convert_value(minimum_raw),
76 typical=_convert_value(typical_raw),
77 maximum=_convert_value(maximum_raw),
78 )
80 def _encode_value(self, data: PowerSpecificationData) -> bytearray:
81 """Encode the power specification values."""
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)
89 minimum_raw = _convert_to_raw(data.minimum)
90 typical_raw = _convert_to_raw(data.typical)
91 maximum_raw = _convert_to_raw(data.maximum)
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