Coverage for src / bluetooth_sig / advertising / sig_characteristic_interpreter.py: 100%
37 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"""SIG characteristic interpreter for standard service data.
3Built-in interpreter that uses CharacteristicRegistry to parse SIG-standard
4service data from BLE advertisements. Automatically handles any UUID
5registered in CharacteristicRegistry.
7Based on Bluetooth SIG GATT Specification Supplement for characteristic
8data formats.
9"""
11from __future__ import annotations
13import logging
14from typing import Any
16import msgspec
18from bluetooth_sig.advertising.base import (
19 AdvertisingData,
20 DataSource,
21 InterpreterInfo,
22 PayloadInterpreter,
23)
24from bluetooth_sig.advertising.exceptions import AdvertisingParseError
25from bluetooth_sig.advertising.state import DeviceAdvertisingState
26from bluetooth_sig.gatt.characteristics.registry import CharacteristicRegistry
27from bluetooth_sig.types.uuid import BluetoothUUID
29logger = logging.getLogger(__name__)
32class SIGCharacteristicData(msgspec.Struct, kw_only=True, frozen=True):
33 """Parsed SIG characteristic data from service data advertisement.
35 Attributes:
36 uuid: The characteristic UUID that was parsed.
37 characteristic_name: Human-readable characteristic name.
38 parsed_value: The parsed characteristic value (type varies by characteristic).
40 """
42 uuid: BluetoothUUID
43 characteristic_name: str
44 # Any is justified: Runtime UUID dispatch means return type varies per characteristic
45 parsed_value: Any
48class SIGCharacteristicInterpreter(PayloadInterpreter[SIGCharacteristicData]):
49 """Interprets service data using SIG characteristic definitions.
51 Automatically handles any UUID registered in CharacteristicRegistry.
52 No encryption support (SIG characteristics are not encrypted in service data).
54 This interpreter checks service data UUIDs against the CharacteristicRegistry
55 and parses the payload using the corresponding characteristic class.
57 Example::
58 from bluetooth_sig.advertising.base import AdvertisingData
60 interpreter = SIGCharacteristicInterpreter("AA:BB:CC:DD:EE:FF")
61 state = DeviceAdvertisingState(address="AA:BB:CC:DD:EE:FF")
63 # Create advertising data from BLE packet
64 ad_data = AdvertisingData(
65 service_data={BluetoothUUID("0000180f-0000-1000-8000-00805f9b34fb"): bytes([75])},
66 rssi=-60,
67 )
69 try:
70 data = interpreter.interpret(ad_data, state)
71 print(f"Parsed: {data.parsed_value}")
72 except AdvertisingParseError as e:
73 print(f"Parse failed: {e}")
75 """
77 _info = InterpreterInfo(
78 name="SIG Characteristic",
79 data_source=DataSource.SERVICE,
80 )
81 _is_base_class = False
83 @classmethod
84 def supports(cls, advertising_data: AdvertisingData) -> bool:
85 """Check if any service data UUID matches a registered characteristic.
87 Args:
88 advertising_data: Complete advertising data from BLE packet.
90 Returns:
91 True if at least one service data UUID has a registered characteristic.
93 """
94 for uuid in advertising_data.service_data:
95 if CharacteristicRegistry.get_characteristic_class_by_uuid(uuid) is not None:
96 return True
97 return False
99 def interpret(
100 self,
101 advertising_data: AdvertisingData,
102 state: DeviceAdvertisingState,
103 ) -> SIGCharacteristicData:
104 """Interpret service data using SIG characteristic definitions.
106 Finds the first service data UUID that matches a registered characteristic
107 and parses the payload using that characteristic class.
109 Args:
110 advertising_data: Complete advertising data from BLE packet.
111 state: Current device advertising state (unused, no encryption).
113 Returns:
114 SIGCharacteristicData with parsed characteristic value.
116 Raises:
117 AdvertisingParseError: If no matching characteristic or parsing fails.
119 """
120 del state # Required by interface but not used in this implementation
121 for uuid, payload in advertising_data.service_data.items():
122 char_class = CharacteristicRegistry.get_characteristic_class_by_uuid(uuid)
123 if char_class is None:
124 continue
126 try:
127 char_instance = char_class()
128 parsed_value = char_instance.parse_value(payload)
130 return SIGCharacteristicData(
131 uuid=uuid,
132 characteristic_name=char_class.__name__,
133 parsed_value=parsed_value,
134 )
135 except Exception as e:
136 logger.warning(
137 "Failed to parse SIG characteristic %s: %s",
138 char_class.__name__,
139 e,
140 )
141 raise AdvertisingParseError(
142 message=f"Failed to parse {char_class.__name__}: {e}",
143 raw_data=payload,
144 interpreter_name=self._info.name,
145 ) from e
147 # No matching characteristic found
148 raise AdvertisingParseError(
149 message="No matching SIG characteristic found in service data",
150 interpreter_name=self._info.name,
151 )