Coverage for src / bluetooth_sig / gatt / characteristics / power_specification.py: 98%
42 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 20:14 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 20:14 +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 __repr__(self) -> str:
41 """Return string representation."""
42 return f"PowerSpecificationData(minimum={self.minimum}, typical={self.typical}, maximum={self.maximum})"
45class PowerSpecificationCharacteristic(BaseCharacteristic[PowerSpecificationData]):
46 """Power Specification characteristic (0x2B06).
48 org.bluetooth.characteristic.power_specification
50 The Power Specification characteristic is used to represent a specification of power values.
51 """
53 def _decode_value(self, data: bytearray, ctx: CharacteristicContext | None = None) -> PowerSpecificationData:
54 """Decode the power specification values."""
55 # Parse three uint24 values (little-endian)
56 minimum_raw = DataParser.parse_int24(data, 0, signed=False)
57 typical_raw = DataParser.parse_int24(data, 3, signed=False)
58 maximum_raw = DataParser.parse_int24(data, 6, signed=False)
60 # Convert to float values with 0.1 W resolution, handling special values
61 def _convert_value(raw: int) -> float | None:
62 if raw == VALUE_NOT_VALID:
63 return None # Value is not valid
64 if raw == VALUE_UNKNOWN:
65 return None # Value is not known
66 return raw * 0.1 # Resolution 0.1 W
68 return PowerSpecificationData(
69 minimum=_convert_value(minimum_raw),
70 typical=_convert_value(typical_raw),
71 maximum=_convert_value(maximum_raw),
72 )
74 def _encode_value(self, data: PowerSpecificationData) -> bytearray: # noqa: D202
75 """Encode the power specification values."""
77 # Convert float values to uint24 with 0.1 W resolution, handling special values
78 def _convert_to_raw(value: float | None) -> int:
79 # NOTE: D202 disabled on method - blank line required by black formatter for nested function
80 if value is None:
81 return VALUE_UNKNOWN # Use "not known" as default for None
82 return round(value / 0.1)
84 minimum_raw = _convert_to_raw(data.minimum)
85 typical_raw = _convert_to_raw(data.typical)
86 maximum_raw = _convert_to_raw(data.maximum)
88 # Encode three uint24 values (little-endian)
89 result = bytearray()
90 result.extend(DataParser.encode_int24(minimum_raw, signed=False))
91 result.extend(DataParser.encode_int24(typical_raw, signed=False))
92 result.extend(DataParser.encode_int24(maximum_raw, signed=False))
93 return result