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

32 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-30 00:10 +0000

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

2 

3from __future__ import annotations 

4 

5import msgspec 

6 

7from ...types.gatt_enums import ValueType 

8from ..constants import UINT16_MAX 

9from ..context import CharacteristicContext 

10from .base import BaseCharacteristic 

11 

12 

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

14 """Data class for electric current specification.""" 

15 

16 minimum: float # Minimum current in Amperes 

17 maximum: float # Maximum current in Amperes 

18 

19 def __post_init__(self) -> None: 

20 """Validate current specification data.""" 

21 if self.minimum > self.maximum: 

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

23 

24 # Validate range for uint16 with 0.01 A resolution (0 to 655.35 A) 

25 max_current_value = UINT16_MAX * 0.01 

26 if not 0.0 <= self.minimum <= max_current_value: 

27 raise ValueError(f"Minimum current {self.minimum} A is outside valid range (0.0 to {max_current_value} A)") 

28 if not 0.0 <= self.maximum <= max_current_value: 

29 raise ValueError(f"Maximum current {self.maximum} A is outside valid range (0.0 to {max_current_value} A)") 

30 

31 

32class ElectricCurrentSpecificationCharacteristic(BaseCharacteristic): 

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

34 

35 org.bluetooth.characteristic.electric_current_specification 

36 

37 Electric Current Specification characteristic. 

38 

39 Specifies minimum and maximum current values for electrical 

40 specifications. 

41 """ 

42 

43 # Override since decode_value returns structured ElectricCurrentSpecificationData 

44 _manual_value_type: ValueType | str | None = ValueType.DICT 

45 

46 def decode_value( 

47 self, data: bytearray, _ctx: CharacteristicContext | None = None 

48 ) -> ElectricCurrentSpecificationData: 

49 """Parse current specification data (2x uint16 in units of 0.01 A). 

50 

51 Args: 

52 data: Raw bytes from the characteristic read 

53 

54 Returns: 

55 ElectricCurrentSpecificationData with 'minimum' and 'maximum' current specification values in Amperes 

56 

57 Raises: 

58 ValueError: If data is insufficient 

59 

60 """ 

61 if len(data) < 4: 

62 raise ValueError("Electric current specification data must be at least 4 bytes") 

63 

64 # Convert 2x uint16 (little endian) to current specification in Amperes 

65 min_current_raw = int.from_bytes(data[:2], byteorder="little", signed=False) 

66 max_current_raw = int.from_bytes(data[2:4], byteorder="little", signed=False) 

67 

68 return ElectricCurrentSpecificationData(minimum=min_current_raw * 0.01, maximum=max_current_raw * 0.01) 

69 

70 def encode_value(self, data: ElectricCurrentSpecificationData) -> bytearray: 

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

72 

73 Args: 

74 data: ElectricCurrentSpecificationData instance 

75 

76 Returns: 

77 Encoded bytes representing the current specification (2x uint16, 0.01 A resolution) 

78 

79 """ 

80 # Convert Amperes to raw values (multiply by 100 for 0.01 A resolution) 

81 min_current_raw = round(data.minimum * 100) 

82 max_current_raw = round(data.maximum * 100) 

83 

84 # Encode as 2 uint16 values (little endian) 

85 result = bytearray() 

86 result.extend(min_current_raw.to_bytes(2, byteorder="little", signed=False)) 

87 result.extend(max_current_raw.to_bytes(2, byteorder="little", signed=False)) 

88 

89 return result