bumble 0.0.207__py3-none-any.whl → 0.0.208__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,374 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # -----------------------------------------------------------------------------
16
+ # GATT - Type Adapters
17
+ # -----------------------------------------------------------------------------
18
+
19
+ # -----------------------------------------------------------------------------
20
+ # Imports
21
+ # -----------------------------------------------------------------------------
22
+ from __future__ import annotations
23
+ import struct
24
+ from typing import (
25
+ Any,
26
+ Callable,
27
+ Generic,
28
+ Iterable,
29
+ Literal,
30
+ Optional,
31
+ Type,
32
+ TypeVar,
33
+ )
34
+
35
+ from bumble.core import InvalidOperationError
36
+ from bumble.gatt import Characteristic
37
+ from bumble.gatt_client import CharacteristicProxy
38
+ from bumble.utils import ByteSerializable, IntConvertible
39
+
40
+
41
+ # -----------------------------------------------------------------------------
42
+ # Typing
43
+ # -----------------------------------------------------------------------------
44
+ _T = TypeVar('_T')
45
+ _T2 = TypeVar('_T2', bound=ByteSerializable)
46
+ _T3 = TypeVar('_T3', bound=IntConvertible)
47
+
48
+
49
+ # -----------------------------------------------------------------------------
50
+ class CharacteristicAdapter(Characteristic, Generic[_T]):
51
+ '''Base class for GATT Characteristic adapters.'''
52
+
53
+ def __init__(self, characteristic: Characteristic) -> None:
54
+ super().__init__(
55
+ characteristic.uuid,
56
+ characteristic.properties,
57
+ characteristic.permissions,
58
+ characteristic.value,
59
+ characteristic.descriptors,
60
+ )
61
+
62
+
63
+ # -----------------------------------------------------------------------------
64
+ class CharacteristicProxyAdapter(CharacteristicProxy[_T]):
65
+ '''Base class for GATT CharacteristicProxy adapters.'''
66
+
67
+ def __init__(self, characteristic_proxy: CharacteristicProxy):
68
+ super().__init__(
69
+ characteristic_proxy.client,
70
+ characteristic_proxy.handle,
71
+ characteristic_proxy.end_group_handle,
72
+ characteristic_proxy.uuid,
73
+ characteristic_proxy.properties,
74
+ )
75
+
76
+
77
+ # -----------------------------------------------------------------------------
78
+ class DelegatedCharacteristicAdapter(CharacteristicAdapter[_T]):
79
+ '''
80
+ Adapter that converts bytes values using an encode and/or a decode function.
81
+ '''
82
+
83
+ def __init__(
84
+ self,
85
+ characteristic: Characteristic,
86
+ encode: Optional[Callable[[_T], bytes]] = None,
87
+ decode: Optional[Callable[[bytes], _T]] = None,
88
+ ):
89
+ super().__init__(characteristic)
90
+ self.encode = encode
91
+ self.decode = decode
92
+
93
+ def encode_value(self, value: _T) -> bytes:
94
+ if self.encode is None:
95
+ raise InvalidOperationError('delegated adapter does not have an encoder')
96
+ return self.encode(value)
97
+
98
+ def decode_value(self, value: bytes) -> _T:
99
+ if self.decode is None:
100
+ raise InvalidOperationError('delegate adapter does not have a decoder')
101
+ return self.decode(value)
102
+
103
+
104
+ # -----------------------------------------------------------------------------
105
+ class DelegatedCharacteristicProxyAdapter(CharacteristicProxyAdapter[_T]):
106
+ '''
107
+ Adapter that converts bytes values using an encode and a decode function.
108
+ '''
109
+
110
+ def __init__(
111
+ self,
112
+ characteristic_proxy: CharacteristicProxy,
113
+ encode: Optional[Callable[[_T], bytes]] = None,
114
+ decode: Optional[Callable[[bytes], _T]] = None,
115
+ ):
116
+ super().__init__(characteristic_proxy)
117
+ self.encode = encode
118
+ self.decode = decode
119
+
120
+ def encode_value(self, value: _T) -> bytes:
121
+ if self.encode is None:
122
+ raise InvalidOperationError('delegated adapter does not have an encoder')
123
+ return self.encode(value)
124
+
125
+ def decode_value(self, value: bytes) -> _T:
126
+ if self.decode is None:
127
+ raise InvalidOperationError('delegate adapter does not have a decoder')
128
+ return self.decode(value)
129
+
130
+
131
+ # -----------------------------------------------------------------------------
132
+ class PackedCharacteristicAdapter(CharacteristicAdapter):
133
+ '''
134
+ Adapter that packs/unpacks characteristic values according to a standard
135
+ Python `struct` format.
136
+ For formats with a single value, the adapted `read_value` and `write_value`
137
+ methods return/accept single values. For formats with multiple values,
138
+ they return/accept a tuple with the same number of elements as is required for
139
+ the format.
140
+ '''
141
+
142
+ def __init__(self, characteristic: Characteristic, pack_format: str) -> None:
143
+ super().__init__(characteristic)
144
+ self.struct = struct.Struct(pack_format)
145
+
146
+ def pack(self, *values) -> bytes:
147
+ return self.struct.pack(*values)
148
+
149
+ def unpack(self, buffer: bytes) -> tuple:
150
+ return self.struct.unpack(buffer)
151
+
152
+ def encode_value(self, value: Any) -> bytes:
153
+ return self.pack(*value if isinstance(value, tuple) else (value,))
154
+
155
+ def decode_value(self, value: bytes) -> Any:
156
+ unpacked = self.unpack(value)
157
+ return unpacked[0] if len(unpacked) == 1 else unpacked
158
+
159
+
160
+ # -----------------------------------------------------------------------------
161
+ class PackedCharacteristicProxyAdapter(CharacteristicProxyAdapter):
162
+ '''
163
+ Adapter that packs/unpacks characteristic values according to a standard
164
+ Python `struct` format.
165
+ For formats with a single value, the adapted `read_value` and `write_value`
166
+ methods return/accept single values. For formats with multiple values,
167
+ they return/accept a tuple with the same number of elements as is required for
168
+ the format.
169
+ '''
170
+
171
+ def __init__(self, characteristic_proxy, pack_format):
172
+ super().__init__(characteristic_proxy)
173
+ self.struct = struct.Struct(pack_format)
174
+
175
+ def pack(self, *values) -> bytes:
176
+ return self.struct.pack(*values)
177
+
178
+ def unpack(self, buffer: bytes) -> tuple:
179
+ return self.struct.unpack(buffer)
180
+
181
+ def encode_value(self, value: Any) -> bytes:
182
+ return self.pack(*value if isinstance(value, tuple) else (value,))
183
+
184
+ def decode_value(self, value: bytes) -> Any:
185
+ unpacked = self.unpack(value)
186
+ return unpacked[0] if len(unpacked) == 1 else unpacked
187
+
188
+
189
+ # -----------------------------------------------------------------------------
190
+ class MappedCharacteristicAdapter(PackedCharacteristicAdapter):
191
+ '''
192
+ Adapter that packs/unpacks characteristic values according to a standard
193
+ Python `struct` format.
194
+ The adapted `read_value` and `write_value` methods return/accept a dictionary which
195
+ is packed/unpacked according to format, with the arguments extracted from the
196
+ dictionary by key, in the same order as they occur in the `keys` parameter.
197
+ '''
198
+
199
+ def __init__(
200
+ self, characteristic: Characteristic, pack_format: str, keys: Iterable[str]
201
+ ) -> None:
202
+ super().__init__(characteristic, pack_format)
203
+ self.keys = keys
204
+
205
+ # pylint: disable=arguments-differ
206
+ def pack(self, values) -> bytes:
207
+ return super().pack(*(values[key] for key in self.keys))
208
+
209
+ def unpack(self, buffer: bytes) -> Any:
210
+ return dict(zip(self.keys, super().unpack(buffer)))
211
+
212
+
213
+ # -----------------------------------------------------------------------------
214
+ class MappedCharacteristicProxyAdapter(PackedCharacteristicProxyAdapter):
215
+ '''
216
+ Adapter that packs/unpacks characteristic values according to a standard
217
+ Python `struct` format.
218
+ The adapted `read_value` and `write_value` methods return/accept a dictionary which
219
+ is packed/unpacked according to format, with the arguments extracted from the
220
+ dictionary by key, in the same order as they occur in the `keys` parameter.
221
+ '''
222
+
223
+ def __init__(
224
+ self,
225
+ characteristic_proxy: CharacteristicProxy,
226
+ pack_format: str,
227
+ keys: Iterable[str],
228
+ ) -> None:
229
+ super().__init__(characteristic_proxy, pack_format)
230
+ self.keys = keys
231
+
232
+ # pylint: disable=arguments-differ
233
+ def pack(self, values) -> bytes:
234
+ return super().pack(*(values[key] for key in self.keys))
235
+
236
+ def unpack(self, buffer: bytes) -> Any:
237
+ return dict(zip(self.keys, super().unpack(buffer)))
238
+
239
+
240
+ # -----------------------------------------------------------------------------
241
+ class UTF8CharacteristicAdapter(CharacteristicAdapter[str]):
242
+ '''
243
+ Adapter that converts strings to/from bytes using UTF-8 encoding
244
+ '''
245
+
246
+ def encode_value(self, value: str) -> bytes:
247
+ return value.encode('utf-8')
248
+
249
+ def decode_value(self, value: bytes) -> str:
250
+ return value.decode('utf-8')
251
+
252
+
253
+ # -----------------------------------------------------------------------------
254
+ class UTF8CharacteristicProxyAdapter(CharacteristicProxyAdapter[str]):
255
+ '''
256
+ Adapter that converts strings to/from bytes using UTF-8 encoding
257
+ '''
258
+
259
+ def encode_value(self, value: str) -> bytes:
260
+ return value.encode('utf-8')
261
+
262
+ def decode_value(self, value: bytes) -> str:
263
+ return value.decode('utf-8')
264
+
265
+
266
+ # -----------------------------------------------------------------------------
267
+ class SerializableCharacteristicAdapter(CharacteristicAdapter[_T2]):
268
+ '''
269
+ Adapter that converts any class to/from bytes using the class'
270
+ `to_bytes` and `__bytes__` methods, respectively.
271
+ '''
272
+
273
+ def __init__(self, characteristic: Characteristic, cls: Type[_T2]) -> None:
274
+ super().__init__(characteristic)
275
+ self.cls = cls
276
+
277
+ def encode_value(self, value: _T2) -> bytes:
278
+ return bytes(value)
279
+
280
+ def decode_value(self, value: bytes) -> _T2:
281
+ return self.cls.from_bytes(value)
282
+
283
+
284
+ # -----------------------------------------------------------------------------
285
+ class SerializableCharacteristicProxyAdapter(CharacteristicProxyAdapter[_T2]):
286
+ '''
287
+ Adapter that converts any class to/from bytes using the class'
288
+ `to_bytes` and `__bytes__` methods, respectively.
289
+ '''
290
+
291
+ def __init__(
292
+ self, characteristic_proxy: CharacteristicProxy, cls: Type[_T2]
293
+ ) -> None:
294
+ super().__init__(characteristic_proxy)
295
+ self.cls = cls
296
+
297
+ def encode_value(self, value: _T2) -> bytes:
298
+ return bytes(value)
299
+
300
+ def decode_value(self, value: bytes) -> _T2:
301
+ return self.cls.from_bytes(value)
302
+
303
+
304
+ # -----------------------------------------------------------------------------
305
+ class EnumCharacteristicAdapter(CharacteristicAdapter[_T3]):
306
+ '''
307
+ Adapter that converts int-enum-like classes to/from bytes using the class'
308
+ `int().to_bytes()` and `from_bytes()` methods, respectively.
309
+ '''
310
+
311
+ def __init__(
312
+ self,
313
+ characteristic: Characteristic,
314
+ cls: Type[_T3],
315
+ length: int,
316
+ byteorder: Literal['little', 'big'] = 'little',
317
+ ):
318
+ """
319
+ Initialize an instance.
320
+
321
+ Params:
322
+ characteristic: the Characteristic to adapt to/from
323
+ cls: the class to/from which to convert integer values
324
+ length: number of bytes used to represent integer values
325
+ byteorder: byte order of the byte representation of integers.
326
+ """
327
+ super().__init__(characteristic)
328
+ self.cls = cls
329
+ self.length = length
330
+ self.byteorder = byteorder
331
+
332
+ def encode_value(self, value: _T3) -> bytes:
333
+ return int(value).to_bytes(self.length, self.byteorder)
334
+
335
+ def decode_value(self, value: bytes) -> _T3:
336
+ int_value = int.from_bytes(value, self.byteorder)
337
+ return self.cls(int_value)
338
+
339
+
340
+ # -----------------------------------------------------------------------------
341
+ class EnumCharacteristicProxyAdapter(CharacteristicProxyAdapter[_T3]):
342
+ '''
343
+ Adapter that converts int-enum-like classes to/from bytes using the class'
344
+ `int().to_bytes()` and `from_bytes()` methods, respectively.
345
+ '''
346
+
347
+ def __init__(
348
+ self,
349
+ characteristic_proxy: CharacteristicProxy,
350
+ cls: Type[_T3],
351
+ length: int,
352
+ byteorder: Literal['little', 'big'] = 'little',
353
+ ):
354
+ """
355
+ Initialize an instance.
356
+
357
+ Params:
358
+ characteristic_proxy: the CharacteristicProxy to adapt to/from
359
+ cls: the class to/from which to convert integer values
360
+ length: number of bytes used to represent integer values
361
+ byteorder: byte order of the byte representation of integers.
362
+ """
363
+ super().__init__(characteristic_proxy)
364
+ self.cls = cls
365
+ self.length = length
366
+ self.byteorder = byteorder
367
+
368
+ def encode_value(self, value: _T3) -> bytes:
369
+ return int(value).to_bytes(self.length, self.byteorder)
370
+
371
+ def decode_value(self, value: bytes) -> _T3:
372
+ int_value = int.from_bytes(value, self.byteorder)
373
+ a = self.cls(int_value)
374
+ return self.cls(int_value)
bumble/gatt_client.py CHANGED
@@ -29,16 +29,18 @@ import logging
29
29
  import struct
30
30
  from datetime import datetime
31
31
  from typing import (
32
+ Any,
33
+ Callable,
34
+ Dict,
35
+ Generic,
36
+ Iterable,
32
37
  List,
33
38
  Optional,
34
- Dict,
39
+ Set,
35
40
  Tuple,
36
- Callable,
37
41
  Union,
38
- Any,
39
- Iterable,
40
42
  Type,
41
- Set,
43
+ TypeVar,
42
44
  TYPE_CHECKING,
43
45
  )
44
46
 
@@ -82,9 +84,14 @@ from .gatt import (
82
84
  TemplateService,
83
85
  )
84
86
 
87
+ # -----------------------------------------------------------------------------
88
+ # Typing
89
+ # -----------------------------------------------------------------------------
85
90
  if TYPE_CHECKING:
86
91
  from bumble.device import Connection
87
92
 
93
+ _T = TypeVar('_T')
94
+
88
95
  # -----------------------------------------------------------------------------
89
96
  # Logging
90
97
  # -----------------------------------------------------------------------------
@@ -110,7 +117,7 @@ def show_services(services: Iterable[ServiceProxy]) -> None:
110
117
  # -----------------------------------------------------------------------------
111
118
  # Proxies
112
119
  # -----------------------------------------------------------------------------
113
- class AttributeProxy(EventEmitter):
120
+ class AttributeProxy(EventEmitter, Generic[_T]):
114
121
  def __init__(
115
122
  self, client: Client, handle: int, end_group_handle: int, attribute_type: UUID
116
123
  ) -> None:
@@ -120,21 +127,21 @@ class AttributeProxy(EventEmitter):
120
127
  self.end_group_handle = end_group_handle
121
128
  self.type = attribute_type
122
129
 
123
- async def read_value(self, no_long_read: bool = False) -> bytes:
130
+ async def read_value(self, no_long_read: bool = False) -> _T:
124
131
  return self.decode_value(
125
132
  await self.client.read_value(self.handle, no_long_read)
126
133
  )
127
134
 
128
- async def write_value(self, value, with_response=False):
135
+ async def write_value(self, value: _T, with_response=False):
129
136
  return await self.client.write_value(
130
137
  self.handle, self.encode_value(value), with_response
131
138
  )
132
139
 
133
- def encode_value(self, value: Any) -> bytes:
134
- return value
140
+ def encode_value(self, value: _T) -> bytes:
141
+ return value # type: ignore
135
142
 
136
- def decode_value(self, value_bytes: bytes) -> Any:
137
- return value_bytes
143
+ def decode_value(self, value: bytes) -> _T:
144
+ return value # type: ignore
138
145
 
139
146
  def __str__(self) -> str:
140
147
  return f'Attribute(handle=0x{self.handle:04X}, type={self.type})'
@@ -184,19 +191,19 @@ class ServiceProxy(AttributeProxy):
184
191
  return f'Service(handle=0x{self.handle:04X}, uuid={self.uuid})'
185
192
 
186
193
 
187
- class CharacteristicProxy(AttributeProxy):
194
+ class CharacteristicProxy(AttributeProxy[_T]):
188
195
  properties: Characteristic.Properties
189
196
  descriptors: List[DescriptorProxy]
190
- subscribers: Dict[Any, Callable[[bytes], Any]]
197
+ subscribers: Dict[Any, Callable[[_T], Any]]
191
198
 
192
199
  def __init__(
193
200
  self,
194
- client,
195
- handle,
196
- end_group_handle,
197
- uuid,
201
+ client: Client,
202
+ handle: int,
203
+ end_group_handle: int,
204
+ uuid: UUID,
198
205
  properties: int,
199
- ):
206
+ ) -> None:
200
207
  super().__init__(client, handle, end_group_handle, uuid)
201
208
  self.uuid = uuid
202
209
  self.properties = Characteristic.Properties(properties)
@@ -204,21 +211,21 @@ class CharacteristicProxy(AttributeProxy):
204
211
  self.descriptors_discovered = False
205
212
  self.subscribers = {} # Map from subscriber to proxy subscriber
206
213
 
207
- def get_descriptor(self, descriptor_type):
214
+ def get_descriptor(self, descriptor_type: UUID) -> Optional[DescriptorProxy]:
208
215
  for descriptor in self.descriptors:
209
216
  if descriptor.type == descriptor_type:
210
217
  return descriptor
211
218
 
212
219
  return None
213
220
 
214
- async def discover_descriptors(self):
221
+ async def discover_descriptors(self) -> list[DescriptorProxy]:
215
222
  return await self.client.discover_descriptors(self)
216
223
 
217
224
  async def subscribe(
218
225
  self,
219
- subscriber: Optional[Callable[[bytes], Any]] = None,
226
+ subscriber: Optional[Callable[[_T], Any]] = None,
220
227
  prefer_notify: bool = True,
221
- ):
228
+ ) -> None:
222
229
  if subscriber is not None:
223
230
  if subscriber in self.subscribers:
224
231
  # We already have a proxy subscriber
@@ -233,13 +240,13 @@ class CharacteristicProxy(AttributeProxy):
233
240
  self.subscribers[subscriber] = on_change
234
241
  subscriber = on_change
235
242
 
236
- return await self.client.subscribe(self, subscriber, prefer_notify)
243
+ await self.client.subscribe(self, subscriber, prefer_notify)
237
244
 
238
- async def unsubscribe(self, subscriber=None, force=False):
245
+ async def unsubscribe(self, subscriber=None, force=False) -> None:
239
246
  if subscriber in self.subscribers:
240
247
  subscriber = self.subscribers.pop(subscriber)
241
248
 
242
- return await self.client.unsubscribe(self, subscriber, force)
249
+ await self.client.unsubscribe(self, subscriber, force)
243
250
 
244
251
  def __str__(self) -> str:
245
252
  return (
@@ -250,7 +257,7 @@ class CharacteristicProxy(AttributeProxy):
250
257
 
251
258
 
252
259
  class DescriptorProxy(AttributeProxy):
253
- def __init__(self, client, handle, descriptor_type):
260
+ def __init__(self, client: Client, handle: int, descriptor_type: UUID) -> None:
254
261
  super().__init__(client, handle, 0, descriptor_type)
255
262
 
256
263
  def __str__(self) -> str:
@@ -679,7 +686,7 @@ class Client:
679
686
 
680
687
  properties, handle = struct.unpack_from('<BH', attribute_value)
681
688
  characteristic_uuid = UUID.from_bytes(attribute_value[3:])
682
- characteristic = CharacteristicProxy(
689
+ characteristic: CharacteristicProxy = CharacteristicProxy(
683
690
  self, handle, 0, characteristic_uuid, properties
684
691
  )
685
692
 
@@ -805,7 +812,7 @@ class Client:
805
812
  logger.warning(f'bogus handle value: {attribute_handle}')
806
813
  return []
807
814
 
808
- attribute = AttributeProxy(
815
+ attribute: AttributeProxy = AttributeProxy(
809
816
  self, attribute_handle, 0, UUID.from_bytes(attribute_uuid)
810
817
  )
811
818
  attributes.append(attribute)
@@ -818,7 +825,7 @@ class Client:
818
825
  async def subscribe(
819
826
  self,
820
827
  characteristic: CharacteristicProxy,
821
- subscriber: Optional[Callable[[bytes], Any]] = None,
828
+ subscriber: Optional[Callable[[Any], Any]] = None,
822
829
  prefer_notify: bool = True,
823
830
  ) -> None:
824
831
  # If we haven't already discovered the descriptors for this characteristic,
@@ -868,7 +875,7 @@ class Client:
868
875
  async def unsubscribe(
869
876
  self,
870
877
  characteristic: CharacteristicProxy,
871
- subscriber: Optional[Callable[[bytes], Any]] = None,
878
+ subscriber: Optional[Callable[[Any], Any]] = None,
872
879
  force: bool = False,
873
880
  ) -> None:
874
881
  '''
bumble/gatt_server.py CHANGED
@@ -36,7 +36,6 @@ from typing import (
36
36
  Tuple,
37
37
  TypeVar,
38
38
  Type,
39
- Union,
40
39
  TYPE_CHECKING,
41
40
  )
42
41
  from pyee import EventEmitter
@@ -78,7 +77,6 @@ from bumble.gatt import (
78
77
  GATT_REQUEST_TIMEOUT,
79
78
  GATT_SECONDARY_SERVICE_ATTRIBUTE_TYPE,
80
79
  Characteristic,
81
- CharacteristicAdapter,
82
80
  CharacteristicDeclaration,
83
81
  CharacteristicValue,
84
82
  IncludedServiceDeclaration,
@@ -469,7 +467,7 @@ class Server(EventEmitter):
469
467
  finally:
470
468
  self.pending_confirmations[connection.handle] = None
471
469
 
472
- async def notify_or_indicate_subscribers(
470
+ async def _notify_or_indicate_subscribers(
473
471
  self,
474
472
  indicate: bool,
475
473
  attribute: Attribute,
@@ -503,7 +501,9 @@ class Server(EventEmitter):
503
501
  value: Optional[bytes] = None,
504
502
  force: bool = False,
505
503
  ):
506
- return await self.notify_or_indicate_subscribers(False, attribute, value, force)
504
+ return await self._notify_or_indicate_subscribers(
505
+ False, attribute, value, force
506
+ )
507
507
 
508
508
  async def indicate_subscribers(
509
509
  self,
@@ -511,7 +511,7 @@ class Server(EventEmitter):
511
511
  value: Optional[bytes] = None,
512
512
  force: bool = False,
513
513
  ):
514
- return await self.notify_or_indicate_subscribers(True, attribute, value, force)
514
+ return await self._notify_or_indicate_subscribers(True, attribute, value, force)
515
515
 
516
516
  def on_disconnection(self, connection: Connection) -> None:
517
517
  if connection.handle in self.subscribers:
bumble/host.py CHANGED
@@ -235,7 +235,7 @@ class Host(AbortableEventEmitter):
235
235
  cis_links: Dict[int, IsoLink]
236
236
  bis_links: Dict[int, IsoLink]
237
237
  sco_links: Dict[int, ScoLink]
238
- bigs: dict[int, set[int]] = {} # BIG Handle to BIS Handles
238
+ bigs: dict[int, set[int]]
239
239
  acl_packet_queue: Optional[DataPacketQueue] = None
240
240
  le_acl_packet_queue: Optional[DataPacketQueue] = None
241
241
  iso_packet_queue: Optional[DataPacketQueue] = None
@@ -259,6 +259,7 @@ class Host(AbortableEventEmitter):
259
259
  self.cis_links = {} # CIS links, by connection handle
260
260
  self.bis_links = {} # BIS links, by connection handle
261
261
  self.sco_links = {} # SCO links, by connection handle
262
+ self.bigs = {} # BIG Handle to BIS Handles
262
263
  self.pending_command = None
263
264
  self.pending_response: Optional[asyncio.Future[Any]] = None
264
265
  self.number_of_supported_advertising_sets = 0
@@ -1061,8 +1062,10 @@ class Host(AbortableEventEmitter):
1061
1062
  )
1062
1063
 
1063
1064
  # Flush the data queues
1064
- self.acl_packet_queue.flush(handle)
1065
- self.le_acl_packet_queue.flush(handle)
1065
+ if self.acl_packet_queue:
1066
+ self.acl_packet_queue.flush(handle)
1067
+ if self.le_acl_packet_queue:
1068
+ self.le_acl_packet_queue.flush(handle)
1066
1069
  if self.iso_packet_queue:
1067
1070
  self.iso_packet_queue.flush(handle)
1068
1071
  else:
@@ -1098,8 +1101,11 @@ class Host(AbortableEventEmitter):
1098
1101
 
1099
1102
  # Notify the client
1100
1103
  if event.status == hci.HCI_SUCCESS:
1101
- connection_phy = ConnectionPHY(event.tx_phy, event.rx_phy)
1102
- self.emit('connection_phy_update', connection.handle, connection_phy)
1104
+ self.emit(
1105
+ 'connection_phy_update',
1106
+ connection.handle,
1107
+ ConnectionPHY(event.tx_phy, event.rx_phy),
1108
+ )
1103
1109
  else:
1104
1110
  self.emit('connection_phy_update_failure', connection.handle, event.status)
1105
1111
 
bumble/pairing.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2021-2023 Google LLC
1
+ # Copyright 2021-2025 Google LLC
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -76,18 +76,18 @@ class OobData:
76
76
  return instance
77
77
 
78
78
  def to_ad(self) -> AdvertisingData:
79
- ad_structures = []
79
+ ad_structures: list[tuple[int, bytes]] = []
80
80
  if self.address is not None:
81
81
  ad_structures.append(
82
- (AdvertisingData.LE_BLUETOOTH_DEVICE_ADDRESS, bytes(self.address))
82
+ (AdvertisingData.Type.LE_BLUETOOTH_DEVICE_ADDRESS, bytes(self.address))
83
83
  )
84
84
  if self.role is not None:
85
- ad_structures.append((AdvertisingData.LE_ROLE, bytes([self.role])))
85
+ ad_structures.append((AdvertisingData.Type.LE_ROLE, bytes([self.role])))
86
86
  if self.shared_data is not None:
87
87
  ad_structures.extend(self.shared_data.to_ad().ad_structures)
88
88
  if self.legacy_context is not None:
89
89
  ad_structures.append(
90
- (AdvertisingData.SECURITY_MANAGER_TK_VALUE, self.legacy_context.tk)
90
+ (AdvertisingData.Type.SECURITY_MANAGER_TK_VALUE, self.legacy_context.tk)
91
91
  )
92
92
 
93
93
  return AdvertisingData(ad_structures)