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

1"""Resolve advertised service UUIDs to GATT service classes. 

2 

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. 

6 

7Based on Bluetooth SIG Core Specification Supplement for advertising data 

8AD Type categories. 

9""" 

10 

11from __future__ import annotations 

12 

13from bluetooth_sig.gatt.services.base import BaseGattService 

14from bluetooth_sig.gatt.services.registry import GattServiceRegistry 

15from bluetooth_sig.types.uuid import BluetoothUUID 

16 

17 

18class ResolvedService: 

19 """Information about a resolved advertised service. 

20 

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. 

26 

27 """ 

28 

29 __slots__ = ("is_sig_defined", "name", "service_class", "uuid") 

30 

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. 

40 

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. 

46 

47 """ 

48 self.uuid = uuid 

49 self.service_class = service_class 

50 self.name = name 

51 self.is_sig_defined = is_sig_defined 

52 

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})" 

57 

58 

59class AdvertisingServiceResolver: 

60 """Resolves advertised service UUIDs to GATT service classes. 

61 

62 Maps service UUIDs advertised in BLE advertisements to their 

63 corresponding GATT service classes from the registry. 

64 

65 Example:: 

66 resolver = AdvertisingServiceResolver() 

67 

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}") 

72 

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) 

79 

80 """ 

81 

82 def resolve(self, uuid: BluetoothUUID | str) -> ResolvedService: 

83 """Resolve a single service UUID to its GATT service class. 

84 

85 Args: 

86 uuid: The service UUID to resolve. 

87 

88 Returns: 

89 ResolvedService with service class if found, or None if unknown. 

90 

91 """ 

92 if isinstance(uuid, str): 

93 uuid = BluetoothUUID(uuid) 

94 

95 service_class = GattServiceRegistry.get_service_class_by_uuid(uuid) 

96 

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 ) 

108 

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 ) 

116 

117 def resolve_all( 

118 self, 

119 uuids: list[BluetoothUUID] | list[str], 

120 ) -> list[ResolvedService]: 

121 """Resolve multiple service UUIDs. 

122 

123 Args: 

124 uuids: List of service UUIDs to resolve. 

125 

126 Returns: 

127 List of ResolvedService objects, one per input UUID. 

128 

129 """ 

130 return [self.resolve(uuid) for uuid in uuids] 

131 

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. 

137 

138 Filters out unknown services from the result. 

139 

140 Args: 

141 uuids: List of service UUIDs to check. 

142 

143 Returns: 

144 List of ResolvedService objects for known services only. 

145 

146 """ 

147 return [resolved for resolved in self.resolve_all(uuids) if resolved.service_class is not None] 

148 

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. 

154 

155 Filters to return only services with SIG-assigned 16-bit UUIDs. 

156 

157 Args: 

158 uuids: List of service UUIDs to check. 

159 

160 Returns: 

161 List of ResolvedService objects for SIG-defined services only. 

162 

163 """ 

164 return [resolved for resolved in self.resolve_all(uuids) if resolved.is_sig_defined]