Coverage for src / bluetooth_sig / advertising / service_resolver.py: 100%
30 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"""Resolve advertised service UUIDs to GATT service classes.
3Maps advertised service UUIDs (from AD types 0x02-0x07) to GATT service classes.
4This bridges advertising discovery with GATT service discovery, allowing
5callers to pre-plan which characteristics to read after connecting.
7Based on Bluetooth SIG Core Specification Supplement for advertising data
8AD Type categories.
9"""
11from __future__ import annotations
13from bluetooth_sig.gatt.services.base import BaseGattService
14from bluetooth_sig.gatt.services.registry import GattServiceRegistry
15from bluetooth_sig.types.uuid import BluetoothUUID
18class ResolvedService:
19 """Information about a resolved advertised service.
21 Attributes:
22 uuid: The service UUID from the advertisement.
23 service_class: The GATT service class, or None if not in registry.
24 name: Human-readable service name.
25 is_sig_defined: Whether this is a SIG-defined service.
27 """
29 __slots__ = ("is_sig_defined", "name", "service_class", "uuid")
31 def __init__(
32 self,
33 uuid: BluetoothUUID,
34 service_class: type[BaseGattService] | None,
35 name: str,
36 *,
37 is_sig_defined: bool,
38 ) -> None:
39 """Initialise resolved service.
41 Args:
42 uuid: The service UUID.
43 service_class: The GATT service class, or None.
44 name: Human-readable service name.
45 is_sig_defined: Whether this is a SIG-defined service.
47 """
48 self.uuid = uuid
49 self.service_class = service_class
50 self.name = name
51 self.is_sig_defined = is_sig_defined
53 def __repr__(self) -> str:
54 """Return string representation."""
55 class_name = self.service_class.__name__ if self.service_class else "None"
56 return f"ResolvedService(uuid={self.uuid}, service_class={class_name}, name={self.name!r})"
59class AdvertisingServiceResolver:
60 """Resolves advertised service UUIDs to GATT service classes.
62 Maps service UUIDs advertised in BLE advertisements to their
63 corresponding GATT service classes from the registry.
65 Example::
66 resolver = AdvertisingServiceResolver()
68 # Resolve a single UUID
69 resolved = resolver.resolve(BluetoothUUID("0000180f-0000-1000-8000-00805f9b34fb"))
70 if resolved.service_class:
71 print(f"Found service: {resolved.name}")
73 # Resolve multiple UUIDs from advertisement
74 service_uuids = [
75 BluetoothUUID("0000180f-0000-1000-8000-00805f9b34fb"), # Battery Service
76 BluetoothUUID("0000180d-0000-1000-8000-00805f9b34fb"), # Heart Rate Service
77 ]
78 resolved_services = resolver.resolve_all(service_uuids)
80 """
82 def resolve(self, uuid: BluetoothUUID | str) -> ResolvedService:
83 """Resolve a single service UUID to its GATT service class.
85 Args:
86 uuid: The service UUID to resolve.
88 Returns:
89 ResolvedService with service class if found, or None if unknown.
91 """
92 if isinstance(uuid, str):
93 uuid = BluetoothUUID(uuid)
95 service_class = GattServiceRegistry.get_service_class_by_uuid(uuid)
97 if service_class is not None:
98 # Get name from the service class
99 name = service_class.__name__
100 # Check if it's SIG-defined (uses SIG base UUID)
101 is_sig_defined = uuid.is_sig_service()
102 return ResolvedService(
103 uuid=uuid,
104 service_class=service_class,
105 name=name,
106 is_sig_defined=is_sig_defined,
107 )
109 # Unknown service - return with None service_class
110 return ResolvedService(
111 uuid=uuid,
112 service_class=None,
113 name=f"Unknown Service ({uuid})",
114 is_sig_defined=False,
115 )
117 def resolve_all(
118 self,
119 uuids: list[BluetoothUUID] | list[str],
120 ) -> list[ResolvedService]:
121 """Resolve multiple service UUIDs.
123 Args:
124 uuids: List of service UUIDs to resolve.
126 Returns:
127 List of ResolvedService objects, one per input UUID.
129 """
130 return [self.resolve(uuid) for uuid in uuids]
132 def get_known_services(
133 self,
134 uuids: list[BluetoothUUID] | list[str],
135 ) -> list[ResolvedService]:
136 """Get only the services that have known GATT service classes.
138 Filters out unknown services from the result.
140 Args:
141 uuids: List of service UUIDs to check.
143 Returns:
144 List of ResolvedService objects for known services only.
146 """
147 return [resolved for resolved in self.resolve_all(uuids) if resolved.service_class is not None]
149 def get_sig_services(
150 self,
151 uuids: list[BluetoothUUID] | list[str],
152 ) -> list[ResolvedService]:
153 """Get only the SIG-defined services from the list.
155 Filters to return only services with SIG-assigned 16-bit UUIDs.
157 Args:
158 uuids: List of service UUIDs to check.
160 Returns:
161 List of ResolvedService objects for SIG-defined services only.
163 """
164 return [resolved for resolved in self.resolve_all(uuids) if resolved.is_sig_defined]