Coverage for src/bluetooth_sig/gatt/descriptors/characteristic_aggregate_format.py: 85%
26 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-30 00:10 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-30 00:10 +0000
1"""Characteristic Aggregate Format Descriptor implementation."""
3from __future__ import annotations
5import msgspec
7from ..characteristics.utils import DataParser
8from .base import BaseDescriptor
11class CharacteristicAggregateFormatData(msgspec.Struct, frozen=True, kw_only=True):
12 """Characteristic Aggregate Format descriptor data."""
14 attribute_handles: list[int]
17class CharacteristicAggregateFormatDescriptor(BaseDescriptor):
18 """Characteristic Aggregate Format Descriptor (0x2905).
20 Contains a list of attribute handles that collectively form an aggregate value.
21 Used to group multiple characteristics into a single logical value.
22 """
24 def _has_structured_data(self) -> bool:
25 return True
27 def _get_data_format(self) -> str:
28 return "uint16[]"
30 def _parse_descriptor_value(self, data: bytes) -> CharacteristicAggregateFormatData:
31 """Parse Characteristic Aggregate Format value.
33 Contains a list of 16-bit attribute handles (little-endian).
34 The number of handles is determined by data length / 2.
36 Args:
37 data: Raw bytes containing attribute handles
39 Returns:
40 CharacteristicAggregateFormatData with list of handles
42 Raises:
43 ValueError: If data length is not even (handles are 2 bytes each)
44 """
45 if len(data) % 2 != 0:
46 raise ValueError(f"Characteristic Aggregate Format data must have even length, got {len(data)}")
48 if len(data) == 0:
49 return CharacteristicAggregateFormatData(attribute_handles=[])
51 handles: list[int] = []
52 for i in range(0, len(data), 2):
53 handle = DataParser.parse_int16(data, offset=i, endian="little")
54 handles.append(handle)
56 return CharacteristicAggregateFormatData(attribute_handles=handles)
58 def get_attribute_handles(self, data: bytes) -> list[int]:
59 """Get the list of attribute handles."""
60 parsed = self._parse_descriptor_value(data)
61 return parsed.attribute_handles
63 def get_handle_count(self, data: bytes) -> int:
64 """Get the number of attribute handles."""
65 return len(self.get_attribute_handles(data))