Coverage for src / bluetooth_sig / gatt / characteristics / electric_current_range.py: 85%
39 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"""Electric Current Range characteristic implementation."""
3from __future__ import annotations
5import msgspec
7from ...types.gatt_enums import ValueType
8from ..constants import UINT16_MAX
9from ..context import CharacteristicContext
10from .base import BaseCharacteristic
11from .utils import DataParser
14class ElectricCurrentRangeData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods
15 """Data class for electric current range."""
17 min: float # Minimum current in Amperes
18 max: float # Maximum current in Amperes
20 def __post_init__(self) -> None:
21 """Validate current range data."""
22 if self.min > self.max:
23 raise ValueError(f"Minimum current {self.min} A cannot be greater than maximum {self.max} A")
25 # Validate range for uint16 with 0.01 A resolution (0 to 655.35 A)
26 max_current_value = UINT16_MAX * 0.01
27 if not 0.0 <= self.min <= max_current_value:
28 raise ValueError(f"Minimum current {self.min} A is outside valid range (0.0 to {max_current_value} A)")
29 if not 0.0 <= self.max <= max_current_value:
30 raise ValueError(f"Maximum current {self.max} A is outside valid range (0.0 to {max_current_value} A)")
33class ElectricCurrentRangeCharacteristic(BaseCharacteristic[ElectricCurrentRangeData]):
34 """Electric Current Range characteristic (0x2AEF).
36 org.bluetooth.characteristic.electric_current_range
38 Electric Current Range characteristic.
40 Specifies lower and upper current bounds (2x uint16).
41 """
43 # Validation attributes
44 expected_length: int = 4 # 2x uint16
46 # Override since decode_value returns structured ElectricCurrentRangeData
47 _manual_value_type: ValueType | str | None = ValueType.DICT
49 def _decode_value(self, data: bytearray, ctx: CharacteristicContext | None = None) -> ElectricCurrentRangeData:
50 """Parse current range data (2x uint16 in units of 0.01 A).
52 Args:
53 data: Raw bytes from the characteristic read.
54 ctx: Optional CharacteristicContext providing surrounding context (may be None).
56 Returns:
57 ElectricCurrentRangeData with 'min' and 'max' current values in Amperes.
59 Raises:
60 ValueError: If data is insufficient.
62 """
63 if len(data) < 4:
64 raise ValueError("Electric current range data must be at least 4 bytes")
66 # Convert 2x uint16 (little endian) to current range in Amperes
67 min_current_raw = DataParser.parse_int16(data, 0, signed=False)
68 max_current_raw = DataParser.parse_int16(data, 2, signed=False)
70 return ElectricCurrentRangeData(min=min_current_raw * 0.01, max=max_current_raw * 0.01)
72 def _encode_value(self, data: ElectricCurrentRangeData) -> bytearray:
73 """Encode electric current range value back to bytes.
75 Args:
76 data: ElectricCurrentRangeData instance with 'min' and 'max' current values in Amperes
78 Returns:
79 Encoded bytes representing the current range (2x uint16, 0.01 A resolution)
81 """
82 if not isinstance(data, ElectricCurrentRangeData):
83 raise TypeError(
84 f"Electric current range data must be an ElectricCurrentRangeData, got {type(data).__name__}"
85 )
86 # Convert Amperes to raw values (multiply by 100 for 0.01 A resolution)
87 min_current_raw = round(data.min * 100)
88 max_current_raw = round(data.max * 100)
90 # Validate range for uint16 (0 to UINT16_MAX)
91 for name, value in [("minimum", min_current_raw), ("maximum", max_current_raw)]:
92 if not 0 <= value <= UINT16_MAX:
93 raise ValueError(f"Current {name} value {value} exceeds uint16 range")
95 # Encode as 2 uint16 values (little endian)
96 result = bytearray()
97 result.extend(DataParser.encode_int16(min_current_raw, signed=False))
98 result.extend(DataParser.encode_int16(max_current_raw, signed=False))
100 return result