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

34 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-11 20:14 +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 

11from .utils import DataParser 

12 

13 

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

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

16 

17 minimum: float # Minimum current in Amperes 

18 maximum: float # Maximum current in Amperes 

19 

20 def __post_init__(self) -> None: 

21 """Validate current specification data.""" 

22 if self.minimum > self.maximum: 

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

24 

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

26 max_current_value = UINT16_MAX * 0.01 

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

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

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

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

31 

32 

33class ElectricCurrentSpecificationCharacteristic(BaseCharacteristic[ElectricCurrentSpecificationData]): 

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

35 

36 org.bluetooth.characteristic.electric_current_specification 

37 

38 Electric Current Specification characteristic. 

39 

40 Specifies minimum and maximum current values for electrical 

41 specifications. 

42 """ 

43 

44 # Validation attributes 

45 expected_length: int = 4 # 2x uint16 

46 

47 # Override since decode_value returns structured ElectricCurrentSpecificationData 

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

49 

50 def _decode_value( 

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

52 ) -> ElectricCurrentSpecificationData: 

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

54 

55 Args: 

56 data: Raw bytes from the characteristic read 

57 

58 Returns: 

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

60 

61 Raises: 

62 ValueError: If data is insufficient 

63 

64 """ 

65 if len(data) < 4: 

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

67 

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

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

70 max_current_raw = DataParser.parse_int16(data, 2, signed=False) 

71 

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

73 

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

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

76 

77 Args: 

78 data: ElectricCurrentSpecificationData instance 

79 

80 Returns: 

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

82 

83 """ 

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

85 min_current_raw = round(data.minimum * 100) 

86 max_current_raw = round(data.maximum * 100) 

87 

88 # Encode as 2 uint16 values (little endian) 

89 result = bytearray() 

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

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

92 

93 return result