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
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 20:14 +0000
1"""Descriptor context utility functions.
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"""
8from __future__ import annotations
10from typing import Any
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
23def get_descriptors_from_context(ctx: CharacteristicContext | None) -> dict[str, Any]:
24 """Extract descriptor data from the parsing context.
26 Args:
27 ctx: The characteristic context containing descriptor information
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)
37def get_descriptor_from_context(
38 ctx: CharacteristicContext | None, descriptor_class: type[BaseDescriptor]
39) -> DescriptorData | None:
40 """Get a specific descriptor from context.
42 Args:
43 ctx: Characteristic context containing descriptors
44 descriptor_class: Descriptor class to look for
46 Returns:
47 DescriptorData if found, None otherwise
48 """
49 if not ctx or not ctx.descriptors:
50 return None
52 try:
53 descriptor_instance = descriptor_class()
54 descriptor_uuid = str(descriptor_instance.uuid)
55 except (ValueError, TypeError, AttributeError):
56 return None
58 return ctx.descriptors.get(descriptor_uuid)
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.
64 Args:
65 ctx: Characteristic context containing descriptors
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
76def get_presentation_format_from_context(
77 ctx: CharacteristicContext | None = None,
78) -> CharacteristicPresentationFormatData | None:
79 """Get presentation format from descriptor context if available.
81 Args:
82 ctx: Characteristic context containing descriptors
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
93def get_user_description_from_context(ctx: CharacteristicContext | None = None) -> str | None:
94 """Get user description from descriptor context if available.
96 Args:
97 ctx: Characteristic context containing descriptors
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
108def validate_value_against_descriptor_range(value: int | float, ctx: CharacteristicContext | None = None) -> bool:
109 """Validate a value against descriptor-defined valid range.
111 Args:
112 value: Value to validate
113 ctx: Characteristic context containing descriptors
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
125def enhance_error_message_with_descriptors(base_message: str, ctx: CharacteristicContext | None = None) -> str:
126 """Enhance error message with descriptor information for better debugging.
128 Args:
129 base_message: Original error message
130 ctx: Characteristic context containing descriptors
132 Returns:
133 Enhanced error message with descriptor context
134 """
135 enhancements: list[str] = []
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}")
142 user_desc = get_user_description_from_context(ctx)
143 if user_desc:
144 enhancements.append(f"Description: {user_desc}")
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})")
152 if enhancements:
153 return f"{base_message} ({'; '.join(enhancements)})"
154 return base_message