Coverage for src / bluetooth_sig / gatt / characteristics / voltage_specification.py: 86%

35 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-18 11:17 +0000

1"""Voltage 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 

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

14 """Data class for voltage specification.""" 

15 

16 minimum: float # Minimum voltage in Volts 

17 maximum: float # Maximum voltage in Volts 

18 

19 def __post_init__(self) -> None: 

20 """Validate voltage specification data.""" 

21 if self.minimum > self.maximum: 

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

23 

24 # Validate range for uint16 with 1/64 V resolution (0 to ~1024 V) 

25 max_voltage_value = UINT16_MAX / 64.0 # ~1024 V 

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

27 raise ValueError( 

28 f"Minimum voltage {self.minimum} V is outside valid range (0.0 to {max_voltage_value:.2f} V)" 

29 ) 

30 if not 0.0 <= self.maximum <= max_voltage_value: 

31 raise ValueError( 

32 f"Maximum voltage {self.maximum} V is outside valid range (0.0 to {max_voltage_value:.2f} V)" 

33 ) 

34 

35 

36class VoltageSpecificationCharacteristic(BaseCharacteristic[VoltageSpecificationData]): 

37 """Voltage Specification characteristic (0x2B19). 

38 

39 org.bluetooth.characteristic.voltage_specification 

40 

41 Voltage Specification characteristic. 

42 

43 Specifies minimum and maximum voltage values for electrical 

44 specifications. 

45 """ 

46 

47 min_length = 4 

48 

49 def _decode_value( 

50 self, data: bytearray, ctx: CharacteristicContext | None = None, *, validate: bool = True 

51 ) -> VoltageSpecificationData: 

52 """Parse voltage specification data (2x uint16 in units of 1/64 V). 

53 

54 Args: 

55 data: Raw bytes from the characteristic read. 

56 ctx: Optional CharacteristicContext providing surrounding context (may be None). 

57 validate: Whether to validate ranges (default True) 

58 

59 Returns: 

60 VoltageSpecificationData with 'minimum' and 'maximum' voltage specification values in Volts. 

61 

62 Raises: 

63 ValueError: If data is insufficient. 

64 

65 """ 

66 # Convert 2x uint16 (little endian) to voltage specification in Volts 

67 min_voltage_raw = DataParser.parse_int16(data, 0, signed=False) 

68 max_voltage_raw = DataParser.parse_int16(data, 2, signed=False) 

69 

70 return VoltageSpecificationData(minimum=min_voltage_raw / 64.0, maximum=max_voltage_raw / 64.0) 

71 

72 def _encode_value(self, data: VoltageSpecificationData) -> bytearray: 

73 """Encode voltage specification value back to bytes. 

74 

75 Args: 

76 data: VoltageSpecificationData instance with 'minimum' and 'maximum' voltage values in Volts 

77 

78 Returns: 

79 Encoded bytes representing the voltage specification (2x uint16, 1/64 V resolution) 

80 

81 """ 

82 if not isinstance(data, VoltageSpecificationData): 

83 raise TypeError(f"Voltage specification data must be a VoltageSpecificationData, got {type(data).__name__}") 

84 # Convert Volts to raw values (multiply by 64 for 1/64 V resolution) 

85 min_voltage_raw = round(data.minimum * 64) 

86 max_voltage_raw = round(data.maximum * 64) 

87 

88 # Validate range for uint16 (0 to UINT16_MAX) 

89 # pylint: disable=duplicate-code 

90 # NOTE: This uint16 validation and encoding pattern is shared with VoltageStatisticsCharacteristic. 

91 # Both characteristics encode voltage values using the same 1/64V resolution and uint16 little-endian format 

92 # per Bluetooth SIG spec. Consolidation not practical as each has different field structures. 

93 for name, value in [("minimum", min_voltage_raw), ("maximum", max_voltage_raw)]: 

94 if not 0 <= value <= UINT16_MAX: 

95 raise ValueError(f"Voltage {name} value {value} exceeds uint16 range") 

96 

97 # Encode as 2 uint16 values (little endian) 

98 result = bytearray() 

99 result.extend(DataParser.encode_int16(min_voltage_raw, signed=False)) 

100 result.extend(DataParser.encode_int16(max_voltage_raw, signed=False)) 

101 

102 return result