Coverage for src / bluetooth_sig / gatt / descriptor_utils.py: 34%

59 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-11 20:14 +0000

1"""Descriptor context utility functions. 

2 

3Provides helper functions for extracting and working with descriptor information 

4from CharacteristicContext. These functions serve as both standalone utilities 

5and are mirrored as methods in BaseCharacteristic for convenience. 

6""" 

7 

8from __future__ import annotations 

9 

10from typing import Any 

11 

12from ..types import DescriptorData 

13from .context import CharacteristicContext 

14from .descriptors.base import BaseDescriptor 

15from .descriptors.characteristic_presentation_format import ( 

16 CharacteristicPresentationFormatData, 

17 CharacteristicPresentationFormatDescriptor, 

18) 

19from .descriptors.characteristic_user_description import CharacteristicUserDescriptionDescriptor 

20from .descriptors.valid_range import ValidRangeDescriptor 

21 

22 

23def get_descriptors_from_context(ctx: CharacteristicContext | None) -> dict[str, Any]: 

24 """Extract descriptor data from the parsing context. 

25 

26 Args: 

27 ctx: The characteristic context containing descriptor information 

28 

29 Returns: 

30 Dictionary mapping descriptor UUIDs to DescriptorData objects 

31 """ 

32 if not ctx or not ctx.descriptors: 

33 return {} 

34 return dict(ctx.descriptors) 

35 

36 

37def get_descriptor_from_context( 

38 ctx: CharacteristicContext | None, descriptor_class: type[BaseDescriptor] 

39) -> DescriptorData | None: 

40 """Get a specific descriptor from context. 

41 

42 Args: 

43 ctx: Characteristic context containing descriptors 

44 descriptor_class: Descriptor class to look for 

45 

46 Returns: 

47 DescriptorData if found, None otherwise 

48 """ 

49 if not ctx or not ctx.descriptors: 

50 return None 

51 

52 try: 

53 descriptor_instance = descriptor_class() 

54 descriptor_uuid = str(descriptor_instance.uuid) 

55 except (ValueError, TypeError, AttributeError): 

56 return None 

57 

58 return ctx.descriptors.get(descriptor_uuid) 

59 

60 

61def get_valid_range_from_context(ctx: CharacteristicContext | None = None) -> tuple[int | float, int | float] | None: 

62 """Get valid range from descriptor context if available. 

63 

64 Args: 

65 ctx: Characteristic context containing descriptors 

66 

67 Returns: 

68 Tuple of (min, max) values if Valid Range descriptor present, None otherwise 

69 """ 

70 descriptor_data = get_descriptor_from_context(ctx, ValidRangeDescriptor) 

71 if descriptor_data and descriptor_data.value: 

72 return descriptor_data.value.min_value, descriptor_data.value.max_value 

73 return None 

74 

75 

76def get_presentation_format_from_context( 

77 ctx: CharacteristicContext | None = None, 

78) -> CharacteristicPresentationFormatData | None: 

79 """Get presentation format from descriptor context if available. 

80 

81 Args: 

82 ctx: Characteristic context containing descriptors 

83 

84 Returns: 

85 CharacteristicPresentationFormatData if present, None otherwise 

86 """ 

87 descriptor_data = get_descriptor_from_context(ctx, CharacteristicPresentationFormatDescriptor) 

88 if descriptor_data and descriptor_data.value: 

89 return descriptor_data.value # type: ignore[no-any-return] 

90 return None 

91 

92 

93def get_user_description_from_context(ctx: CharacteristicContext | None = None) -> str | None: 

94 """Get user description from descriptor context if available. 

95 

96 Args: 

97 ctx: Characteristic context containing descriptors 

98 

99 Returns: 

100 User description string if present, None otherwise 

101 """ 

102 descriptor_data = get_descriptor_from_context(ctx, CharacteristicUserDescriptionDescriptor) 

103 if descriptor_data and descriptor_data.value: 

104 return descriptor_data.value.description # type: ignore[no-any-return] 

105 return None 

106 

107 

108def validate_value_against_descriptor_range(value: int | float, ctx: CharacteristicContext | None = None) -> bool: 

109 """Validate a value against descriptor-defined valid range. 

110 

111 Args: 

112 value: Value to validate 

113 ctx: Characteristic context containing descriptors 

114 

115 Returns: 

116 True if value is within valid range or no range defined, False otherwise 

117 """ 

118 valid_range = get_valid_range_from_context(ctx) 

119 if valid_range is None: 

120 return True 

121 min_val, max_val = valid_range 

122 return min_val <= value <= max_val 

123 

124 

125def enhance_error_message_with_descriptors(base_message: str, ctx: CharacteristicContext | None = None) -> str: 

126 """Enhance error message with descriptor information for better debugging. 

127 

128 Args: 

129 base_message: Original error message 

130 ctx: Characteristic context containing descriptors 

131 

132 Returns: 

133 Enhanced error message with descriptor context 

134 """ 

135 enhancements: list[str] = [] 

136 

137 valid_range = get_valid_range_from_context(ctx) 

138 if valid_range: 

139 min_val, max_val = valid_range 

140 enhancements.append(f"Valid range: {min_val}-{max_val}") 

141 

142 user_desc = get_user_description_from_context(ctx) 

143 if user_desc: 

144 enhancements.append(f"Description: {user_desc}") 

145 

146 pres_format = get_presentation_format_from_context(ctx) 

147 if pres_format: 

148 format_str = pres_format.format_name or f"0x{pres_format.format:02X}" 

149 unit_str = pres_format.unit_name or f"0x{pres_format.unit:04X}" 

150 enhancements.append(f"Format: {format_str} (Unit: {unit_str})") 

151 

152 if enhancements: 

153 return f"{base_message} ({'; '.join(enhancements)})" 

154 return base_message