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

1"""Average Current characteristic implementation.""" 

2 

3from __future__ import annotations 

4 

5import math 

6 

7import msgspec 

8 

9from ...types.units import ElectricalUnit 

10from ..context import CharacteristicContext 

11from .base import BaseCharacteristic 

12from .utils import DataParser 

13 

14_CURRENT_RESOLUTION = 0.01 # 0.01 A per raw unit (uint16) 

15_TIME_EXP_BASE = 1.1 

16_TIME_EXP_OFFSET = 64 

17 

18 

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) 

24 

25 

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)) 

32 

33 

34class AverageCurrentData(msgspec.Struct, frozen=True, kw_only=True): # pylint: disable=too-few-public-methods 

35 """Parsed data from Average Current characteristic.""" 

36 

37 current: float # Electric current in Amperes 

38 sensing_duration: float # Sensing duration in seconds (Time Exponential 8) 

39 

40 

41class AverageCurrentCharacteristic(BaseCharacteristic[AverageCurrentData]): 

42 """Average Current characteristic (0x2AE0). 

43 

44 org.bluetooth.characteristic.average_current 

45 

46 Average electric current over a sensing duration. 

47 

48 Format per GSS YAML: Electric Current Value (uint16, 0.01 A/unit) + 

49 Sensing Duration (uint8, Time Exponential 8). 

50 """ 

51 

52 _manual_unit: str = ElectricalUnit.AMPS.value 

53 expected_length: int = 3 

54 min_length: int = 3 

55 

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. 

60 

61 Args: 

62 data: Raw bytearray from BLE characteristic. 

63 ctx: Optional CharacteristicContext (may be None). 

64 validate: Whether to validate ranges (default True). 

65 

66 Returns: 

67 AverageCurrentData with current in Amperes and sensing_duration in seconds. 

68 

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 ) 

76 

77 def _encode_value(self, data: AverageCurrentData) -> bytearray: 

78 """Encode AverageCurrentData to bytes. 

79 

80 Args: 

81 data: AverageCurrentData instance to encode. 

82 

83 Returns: 

84 Encoded 3-byte bytearray. 

85 

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