Coverage for src / bluetooth_sig / types / advertising / result.py: 85%
39 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-18 11:17 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-18 11:17 +0000
1"""Final composed advertising data result types."""
3from __future__ import annotations
5from typing import Any
7import msgspec
9from bluetooth_sig.types.advertising.ad_structures import (
10 AdvertisingDataStructures,
11 ExtendedAdvertisingData,
12)
13from bluetooth_sig.types.company import ManufacturerData
14from bluetooth_sig.types.uuid import BluetoothUUID
17class AdvertisingData(msgspec.Struct, kw_only=True):
18 """Complete BLE advertising data with device information and metadata.
20 Attributes:
21 raw_data: Raw bytes from the advertising packet
22 ad_structures: Parsed AD structures organized by category
23 extended: Extended advertising data (BLE 5.0+)
24 rssi: Received signal strength indicator in dBm
25 """
27 raw_data: bytes
28 ad_structures: AdvertisingDataStructures = msgspec.field(default_factory=AdvertisingDataStructures)
29 extended: ExtendedAdvertisingData = msgspec.field(default_factory=ExtendedAdvertisingData)
30 rssi: int | None = None
32 @property
33 def is_extended_advertising(self) -> bool:
34 """Check if this advertisement uses extended advertising."""
35 return bool(self.extended.extended_payload) or bool(self.extended.auxiliary_packets)
37 @property
38 def total_payload_size(self) -> int:
39 """Get total payload size including extended data."""
40 base_size = len(self.raw_data)
41 if self.extended.extended_payload:
42 base_size += len(self.extended.extended_payload)
43 for aux_packet in self.extended.auxiliary_packets:
44 base_size += len(aux_packet.payload)
45 return base_size
48class AdvertisementData(msgspec.Struct, kw_only=True):
49 """Complete parsed advertisement with PDU structures and interpreted data.
51 This is the unified result from Device.update_advertisement(), containing
52 both low-level AD structures and high-level vendor-specific interpretation.
54 The interpreted_data field is typed as Any to maintain msgspec.Struct compatibility
55 while supporting generic vendor-specific result types at runtime.
57 Attributes:
58 ad_structures: Parsed AD structures (manufacturer_data, service_data, etc.)
59 interpreted_data: Vendor-specific typed result (e.g., sensor readings), or None
60 interpreter_name: Name of the interpreter used (e.g., "BTHome", "Xiaomi"), or None
61 rssi: Received signal strength indicator in dBm
63 Example::
64 # Using connection manager (recommended)
65 ad_data = BleakConnectionManager.convert_advertisement(bleak_advertisement)
66 result = device.update_advertisement(ad_data)
68 # Access low-level AD structures
69 print(result.ad_structures.core.manufacturer_data) # {0x0499: b'...'}
70 print(result.ad_structures.properties.flags)
72 # Access vendor-specific interpreted data
73 if result.interpreted_data:
74 print(f"Interpreter: {result.interpreter_name}")
75 print(f"Temperature: {result.interpreted_data.temperature}")
77 """
79 ad_structures: AdvertisingDataStructures = msgspec.field(default_factory=AdvertisingDataStructures)
80 interpreted_data: Any = None
81 interpreter_name: str | None = None
82 rssi: int | None = None
84 @property
85 def manufacturer_data(self) -> dict[int, ManufacturerData]:
86 """Convenience accessor for manufacturer data (company_id → ManufacturerData)."""
87 return self.ad_structures.core.manufacturer_data
89 @property
90 def service_data(self) -> dict[BluetoothUUID, bytes]:
91 """Convenience accessor for service data (UUID → payload)."""
92 return self.ad_structures.core.service_data
94 @property
95 def local_name(self) -> str:
96 """Convenience accessor for device local name."""
97 return self.ad_structures.core.local_name
99 @property
100 def has_interpretation(self) -> bool:
101 """Check if vendor-specific interpretation was applied."""
102 return self.interpreted_data is not None