Coverage for src / bluetooth_sig / gatt / characteristics / electric_current_specification.py: 92%
38 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-03 16:41 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-03 16:41 +0000
1"""Electric Current Specification characteristic implementation."""
3from __future__ import annotations
5import msgspec
7from ..constants import UINT16_MAX
8from ..context import CharacteristicContext
9from .base import BaseCharacteristic
10from .utils import DataParser
12_CURRENT_RESOLUTION = 0.01 # 0.01 A per raw unit
13_MAX_CURRENT = UINT16_MAX * _CURRENT_RESOLUTION # 655.35 A
16class ElectricCurrentSpecificationData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods
17 """Data class for electric current specification.
19 Three current values (0.01 A resolution): Minimum, Typical, Maximum.
20 """
22 minimum: float # Minimum current in Amperes
23 typical: float # Typical current in Amperes
24 maximum: float # Maximum current in Amperes
26 def __post_init__(self) -> None:
27 """Validate current specification data."""
28 if self.minimum > self.maximum:
29 raise ValueError(f"Minimum current {self.minimum} A cannot be greater than maximum {self.maximum} A")
31 for name, current in [
32 ("minimum", self.minimum),
33 ("typical", self.typical),
34 ("maximum", self.maximum),
35 ]:
36 if not 0.0 <= current <= _MAX_CURRENT:
37 raise ValueError(
38 f"{name.capitalize()} current {current} A is outside valid range (0.0 to {_MAX_CURRENT} A)"
39 )
42class ElectricCurrentSpecificationCharacteristic(BaseCharacteristic[ElectricCurrentSpecificationData]):
43 """Electric Current Specification characteristic (0x2AF0).
45 org.bluetooth.characteristic.electric_current_specification
47 Specifies minimum, typical, and maximum current values (uint16, 0.01 A resolution).
48 """
50 expected_length: int = 6 # 3x uint16
51 min_length: int = 6
53 def _decode_value(
54 self, data: bytearray, _ctx: CharacteristicContext | None = None, *, validate: bool = True
55 ) -> ElectricCurrentSpecificationData:
56 """Parse current specification data (3x uint16 in units of 0.01 A)."""
57 min_current_raw = DataParser.parse_int16(data, 0, signed=False)
58 typical_current_raw = DataParser.parse_int16(data, 2, signed=False)
59 max_current_raw = DataParser.parse_int16(data, 4, signed=False)
61 return ElectricCurrentSpecificationData(
62 minimum=min_current_raw * _CURRENT_RESOLUTION,
63 typical=typical_current_raw * _CURRENT_RESOLUTION,
64 maximum=max_current_raw * _CURRENT_RESOLUTION,
65 )
67 def _encode_value(self, data: ElectricCurrentSpecificationData) -> bytearray:
68 """Encode electric current specification value back to bytes."""
69 min_current_raw = round(data.minimum * 100)
70 typical_current_raw = round(data.typical * 100)
71 max_current_raw = round(data.maximum * 100)
73 for name, value in [
74 ("minimum", min_current_raw),
75 ("typical", typical_current_raw),
76 ("maximum", max_current_raw),
77 ]:
78 if not 0 <= value <= UINT16_MAX:
79 raise ValueError(f"Current {name} value {value} exceeds uint16 range")
81 result = bytearray()
82 result.extend(DataParser.encode_int16(min_current_raw, signed=False))
83 result.extend(DataParser.encode_int16(typical_current_raw, signed=False))
84 result.extend(DataParser.encode_int16(max_current_raw, signed=False))
86 return result