Coverage for src / bluetooth_sig / gatt / characteristics / voltage_specification.py: 90%
40 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"""Voltage 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_VOLTAGE_RESOLUTION = 1 / 64.0 # 1/64 V per raw unit
13_MAX_VOLTAGE = UINT16_MAX * _VOLTAGE_RESOLUTION # ~1023.98 V
16class VoltageSpecificationData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods
17 """Data class for voltage specification.
19 Three voltage values (1/64 V resolution): Minimum, Typical, Maximum.
20 """
22 minimum: float # Minimum voltage in Volts
23 typical: float # Typical voltage in Volts
24 maximum: float # Maximum voltage in Volts
26 def __post_init__(self) -> None:
27 """Validate voltage specification data."""
28 if self.minimum > self.maximum:
29 raise ValueError(f"Minimum voltage {self.minimum} V cannot be greater than maximum {self.maximum} V")
31 for name, voltage in [
32 ("minimum", self.minimum),
33 ("typical", self.typical),
34 ("maximum", self.maximum),
35 ]:
36 if not 0.0 <= voltage <= _MAX_VOLTAGE:
37 raise ValueError(
38 f"{name.capitalize()} voltage {voltage} V is outside valid range (0.0 to {_MAX_VOLTAGE:.2f} V)"
39 )
42class VoltageSpecificationCharacteristic(BaseCharacteristic[VoltageSpecificationData]):
43 """Voltage Specification characteristic (0x2B19).
45 org.bluetooth.characteristic.voltage_specification
47 Specifies minimum, typical, and maximum voltage values (uint16, 1/64 V 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 ) -> VoltageSpecificationData:
56 """Parse voltage specification data (3x uint16 in units of 1/64 V)."""
57 min_voltage_raw = DataParser.parse_int16(data, 0, signed=False)
58 typical_voltage_raw = DataParser.parse_int16(data, 2, signed=False)
59 max_voltage_raw = DataParser.parse_int16(data, 4, signed=False)
61 return VoltageSpecificationData(
62 minimum=min_voltage_raw / 64.0,
63 typical=typical_voltage_raw / 64.0,
64 maximum=max_voltage_raw / 64.0,
65 )
67 def _encode_value(self, data: VoltageSpecificationData) -> bytearray:
68 """Encode voltage specification value back to bytes."""
69 if not isinstance(data, VoltageSpecificationData):
70 raise TypeError(f"Voltage specification data must be a VoltageSpecificationData, got {type(data).__name__}")
72 min_voltage_raw = round(data.minimum * 64)
73 typical_voltage_raw = round(data.typical * 64)
74 max_voltage_raw = round(data.maximum * 64)
76 # pylint: disable=duplicate-code
77 for name, value in [
78 ("minimum", min_voltage_raw),
79 ("typical", typical_voltage_raw),
80 ("maximum", max_voltage_raw),
81 ]:
82 if not 0 <= value <= UINT16_MAX:
83 raise ValueError(f"Voltage {name} value {value} exceeds uint16 range")
85 result = bytearray()
86 result.extend(DataParser.encode_int16(min_voltage_raw, signed=False))
87 result.extend(DataParser.encode_int16(typical_voltage_raw, signed=False))
88 result.extend(DataParser.encode_int16(max_voltage_raw, signed=False))
90 return result