Coverage for src / bluetooth_sig / gatt / characteristics / electric_current_statistics.py: 90%
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 Statistics 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 ElectricCurrentStatisticsData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods
15 """Data class for electric current statistics."""
17 minimum: float # Minimum current in Amperes
18 maximum: float # Maximum current in Amperes
19 average: float # Average current in Amperes
21 def __post_init__(self) -> None:
22 """Validate current statistics data."""
23 # Validate logical order
24 if self.minimum > self.maximum:
25 raise ValueError(f"Minimum current {self.minimum} A cannot be greater than maximum {self.maximum} A")
26 if not self.minimum <= self.average <= self.maximum:
27 raise ValueError(
28 f"Average current {self.average} A must be between "
29 f"minimum {self.minimum} A and maximum {self.maximum} A"
30 )
32 # Validate range for uint16 with 0.01 A resolution (0 to 655.35 A)
33 max_current_value = UINT16_MAX * 0.01
34 for name, current in [
35 ("minimum", self.minimum),
36 ("maximum", self.maximum),
37 ("average", self.average),
38 ]:
39 if not 0.0 <= current <= max_current_value:
40 raise ValueError(
41 f"{name.capitalize()} current {current} A is outside valid range (0.0 to {max_current_value} A)"
42 )
45class ElectricCurrentStatisticsCharacteristic(BaseCharacteristic[ElectricCurrentStatisticsData]):
46 """Electric Current Statistics characteristic (0x2AF1).
48 org.bluetooth.characteristic.electric_current_statistics
50 Electric Current Statistics characteristic.
52 Provides statistical current data (min, max, average over time).
53 """
55 # Validation attributes
56 expected_length: int = 6 # 3x uint16
58 # Override since decode_value returns structured ElectricCurrentStatisticsData
59 _manual_value_type: ValueType | str | None = ValueType.DICT
61 def _decode_value(self, data: bytearray, ctx: CharacteristicContext | None = None) -> ElectricCurrentStatisticsData:
62 """Parse current statistics data (3x uint16 in units of 0.01 A).
64 Args:
65 data: Raw bytes from the characteristic read.
66 ctx: Optional CharacteristicContext providing surrounding context (may be None).
68 Returns:
69 ElectricCurrentStatisticsData with 'minimum', 'maximum', and 'average' current values in Amperes.
71 Raises:
72 ValueError: If data is insufficient.
74 """
75 if len(data) < 6:
76 raise ValueError("Electric current statistics data must be at least 6 bytes")
78 # Convert 3x uint16 (little endian) to current statistics in Amperes
79 min_current_raw = DataParser.parse_int16(data, 0, signed=False)
80 max_current_raw = DataParser.parse_int16(data, 2, signed=False)
81 avg_current_raw = DataParser.parse_int16(data, 4, signed=False)
83 return ElectricCurrentStatisticsData(
84 minimum=min_current_raw * 0.01,
85 maximum=max_current_raw * 0.01,
86 average=avg_current_raw * 0.01,
87 )
89 def _encode_value(self, data: ElectricCurrentStatisticsData) -> bytearray:
90 """Encode electric current statistics value back to bytes.
92 Args:
93 data: ElectricCurrentStatisticsData instance
95 Returns:
96 Encoded bytes representing the current statistics (3x uint16, 0.01 A resolution)
98 """
99 # Convert Amperes to raw values (multiply by 100 for 0.01 A resolution)
100 min_current_raw = round(data.minimum * 100)
101 max_current_raw = round(data.maximum * 100)
102 avg_current_raw = round(data.average * 100)
104 # Encode as 3 uint16 values (little endian)
105 result = bytearray()
106 result.extend(DataParser.encode_int16(min_current_raw, signed=False))
107 result.extend(DataParser.encode_int16(max_current_raw, signed=False))
108 result.extend(DataParser.encode_int16(avg_current_raw, signed=False))
110 return result