Coverage for src / bluetooth_sig / gatt / characteristics / average_current.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 Current 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_CURRENT_RESOLUTION = 0.01 # 0.01 A 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 AverageCurrentData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods
35 """Parsed data from Average Current characteristic."""
37 current: float # Electric current in Amperes
38 sensing_duration: float # Sensing duration in seconds (Time Exponential 8)
41class AverageCurrentCharacteristic(BaseCharacteristic[AverageCurrentData]):
42 """Average Current characteristic (0x2AE0).
44 org.bluetooth.characteristic.average_current
46 Average electric current over a sensing duration.
48 Format per GSS YAML: Electric Current Value (uint16, 0.01 A/unit) +
49 Sensing Duration (uint8, Time Exponential 8).
50 """
52 _manual_unit: str = ElectricalUnit.AMPS.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 ) -> AverageCurrentData:
59 """Parse average current 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 AverageCurrentData with current in Amperes and sensing_duration in seconds.
69 """
70 raw_current = DataParser.parse_int16(data, 0, signed=False)
71 raw_duration = DataParser.parse_int8(data, 2, signed=False)
72 return AverageCurrentData(
73 current=raw_current * _CURRENT_RESOLUTION,
74 sensing_duration=_decode_time_exponential(raw_duration),
75 )
77 def _encode_value(self, data: AverageCurrentData) -> bytearray:
78 """Encode AverageCurrentData to bytes.
80 Args:
81 data: AverageCurrentData instance to encode.
83 Returns:
84 Encoded 3-byte bytearray.
86 """
87 raw_current = round(data.current / _CURRENT_RESOLUTION)
88 result = DataParser.encode_int16(raw_current, signed=False)
89 result += DataParser.encode_int8(_encode_time_exponential(data.sensing_duration))
90 return result