Coverage for src / bluetooth_sig / gatt / characteristics / average_voltage.py: 91%
35 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"""Average Voltage characteristic implementation."""
3from __future__ import annotations
5import math
7import msgspec
9from ...types.units import ElectricalUnit
10from ..context import CharacteristicContext
11from .base import BaseCharacteristic
12from .utils import DataParser
14_VOLTAGE_RESOLUTION = 1 / 64.0 # 1/64 V per raw unit (uint16)
15_TIME_EXP_BASE = 1.1
16_TIME_EXP_OFFSET = 64
19def _decode_time_exponential(raw: int) -> float:
20 """Decode Time Exponential 8 raw uint8 to seconds."""
21 if raw == 0:
22 return 0.0
23 return _TIME_EXP_BASE ** (raw - _TIME_EXP_OFFSET)
26def _encode_time_exponential(seconds: float) -> int:
27 """Encode seconds to Time Exponential 8 raw uint8."""
28 if seconds <= 0.0:
29 return 0
30 n = round(math.log(seconds) / math.log(_TIME_EXP_BASE) + _TIME_EXP_OFFSET)
31 return max(1, min(n, 0xFD))
34class AverageVoltageData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods
35 """Parsed data from Average Voltage characteristic."""
37 voltage: float # Voltage in Volts
38 sensing_duration: float # Sensing duration in seconds (Time Exponential 8)
41class AverageVoltageCharacteristic(BaseCharacteristic[AverageVoltageData]):
42 """Average Voltage characteristic (0x2AE1).
44 org.bluetooth.characteristic.average_voltage
46 Average voltage over a sensing duration.
48 Format per GSS YAML: Voltage Value (uint16, 1/64 V/unit) +
49 Sensing Duration (uint8, Time Exponential 8).
50 """
52 _manual_unit: str = ElectricalUnit.VOLTS.value
53 expected_length: int = 3
54 min_length: int = 3
56 def _decode_value(
57 self, data: bytearray, ctx: CharacteristicContext | None = None, *, validate: bool = True
58 ) -> AverageVoltageData:
59 """Parse average voltage data per Bluetooth SIG GSS spec.
61 Args:
62 data: Raw bytearray from BLE characteristic.
63 ctx: Optional CharacteristicContext (may be None).
64 validate: Whether to validate ranges (default True).
66 Returns:
67 AverageVoltageData with voltage in Volts and sensing_duration in seconds.
69 """
70 raw_voltage = DataParser.parse_int16(data, 0, signed=False)
71 raw_duration = DataParser.parse_int8(data, 2, signed=False)
72 return AverageVoltageData(
73 voltage=raw_voltage * _VOLTAGE_RESOLUTION,
74 sensing_duration=_decode_time_exponential(raw_duration),
75 )
77 def _encode_value(self, data: AverageVoltageData) -> bytearray:
78 """Encode AverageVoltageData to bytes.
80 Args:
81 data: AverageVoltageData instance to encode.
83 Returns:
84 Encoded 3-byte bytearray.
86 """
87 raw_voltage = round(data.voltage / _VOLTAGE_RESOLUTION)
88 result = DataParser.encode_int16(raw_voltage, signed=False)
89 result += DataParser.encode_int8(_encode_time_exponential(data.sensing_duration))
90 return result