Coverage for src / bluetooth_sig / gatt / characteristics / electric_current_specification.py: 92%

38 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-03 16:41 +0000

1"""Electric Current Specification characteristic implementation.""" 

2 

3from __future__ import annotations 

4 

5import msgspec 

6 

7from ..constants import UINT16_MAX 

8from ..context import CharacteristicContext 

9from .base import BaseCharacteristic 

10from .utils import DataParser 

11 

12_CURRENT_RESOLUTION = 0.01 # 0.01 A per raw unit 

13_MAX_CURRENT = UINT16_MAX * _CURRENT_RESOLUTION # 655.35 A 

14 

15 

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

17 """Data class for electric current specification. 

18 

19 Three current values (0.01 A resolution): Minimum, Typical, Maximum. 

20 """ 

21 

22 minimum: float # Minimum current in Amperes 

23 typical: float # Typical current in Amperes 

24 maximum: float # Maximum current in Amperes 

25 

26 def __post_init__(self) -> None: 

27 """Validate current specification data.""" 

28 if self.minimum > self.maximum: 

29 raise ValueError(f"Minimum current {self.minimum} A cannot be greater than maximum {self.maximum} A") 

30 

31 for name, current in [ 

32 ("minimum", self.minimum), 

33 ("typical", self.typical), 

34 ("maximum", self.maximum), 

35 ]: 

36 if not 0.0 <= current <= _MAX_CURRENT: 

37 raise ValueError( 

38 f"{name.capitalize()} current {current} A is outside valid range (0.0 to {_MAX_CURRENT} A)" 

39 ) 

40 

41 

42class ElectricCurrentSpecificationCharacteristic(BaseCharacteristic[ElectricCurrentSpecificationData]): 

43 """Electric Current Specification characteristic (0x2AF0). 

44 

45 org.bluetooth.characteristic.electric_current_specification 

46 

47 Specifies minimum, typical, and maximum current values (uint16, 0.01 A resolution). 

48 """ 

49 

50 expected_length: int = 6 # 3x uint16 

51 min_length: int = 6 

52 

53 def _decode_value( 

54 self, data: bytearray, _ctx: CharacteristicContext | None = None, *, validate: bool = True 

55 ) -> ElectricCurrentSpecificationData: 

56 """Parse current specification data (3x uint16 in units of 0.01 A).""" 

57 min_current_raw = DataParser.parse_int16(data, 0, signed=False) 

58 typical_current_raw = DataParser.parse_int16(data, 2, signed=False) 

59 max_current_raw = DataParser.parse_int16(data, 4, signed=False) 

60 

61 return ElectricCurrentSpecificationData( 

62 minimum=min_current_raw * _CURRENT_RESOLUTION, 

63 typical=typical_current_raw * _CURRENT_RESOLUTION, 

64 maximum=max_current_raw * _CURRENT_RESOLUTION, 

65 ) 

66 

67 def _encode_value(self, data: ElectricCurrentSpecificationData) -> bytearray: 

68 """Encode electric current specification value back to bytes.""" 

69 min_current_raw = round(data.minimum * 100) 

70 typical_current_raw = round(data.typical * 100) 

71 max_current_raw = round(data.maximum * 100) 

72 

73 for name, value in [ 

74 ("minimum", min_current_raw), 

75 ("typical", typical_current_raw), 

76 ("maximum", max_current_raw), 

77 ]: 

78 if not 0 <= value <= UINT16_MAX: 

79 raise ValueError(f"Current {name} value {value} exceeds uint16 range") 

80 

81 result = bytearray() 

82 result.extend(DataParser.encode_int16(min_current_raw, signed=False)) 

83 result.extend(DataParser.encode_int16(typical_current_raw, signed=False)) 

84 result.extend(DataParser.encode_int16(max_current_raw, signed=False)) 

85 

86 return result