bumble 0.0.204__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.
Files changed (51) hide show
  1. bumble/_version.py +9 -4
  2. bumble/apps/auracast.py +631 -98
  3. bumble/apps/bench.py +238 -157
  4. bumble/apps/console.py +19 -12
  5. bumble/apps/controller_info.py +23 -7
  6. bumble/apps/device_info.py +50 -4
  7. bumble/apps/gg_bridge.py +1 -1
  8. bumble/apps/lea_unicast/app.py +61 -201
  9. bumble/att.py +51 -37
  10. bumble/audio/__init__.py +17 -0
  11. bumble/audio/io.py +553 -0
  12. bumble/controller.py +24 -9
  13. bumble/core.py +305 -156
  14. bumble/device.py +1090 -99
  15. bumble/gatt.py +36 -226
  16. bumble/gatt_adapters.py +374 -0
  17. bumble/gatt_client.py +52 -33
  18. bumble/gatt_server.py +5 -5
  19. bumble/hci.py +812 -14
  20. bumble/host.py +367 -65
  21. bumble/l2cap.py +3 -16
  22. bumble/pairing.py +5 -5
  23. bumble/pandora/host.py +7 -12
  24. bumble/profiles/aics.py +48 -57
  25. bumble/profiles/ascs.py +8 -19
  26. bumble/profiles/asha.py +16 -14
  27. bumble/profiles/bass.py +16 -22
  28. bumble/profiles/battery_service.py +13 -3
  29. bumble/profiles/device_information_service.py +16 -14
  30. bumble/profiles/gap.py +12 -8
  31. bumble/profiles/gatt_service.py +167 -0
  32. bumble/profiles/gmap.py +198 -0
  33. bumble/profiles/hap.py +8 -6
  34. bumble/profiles/heart_rate_service.py +20 -4
  35. bumble/profiles/le_audio.py +87 -4
  36. bumble/profiles/mcp.py +11 -9
  37. bumble/profiles/pacs.py +61 -16
  38. bumble/profiles/tmap.py +8 -12
  39. bumble/profiles/{vcp.py → vcs.py} +35 -29
  40. bumble/profiles/vocs.py +62 -85
  41. bumble/sdp.py +223 -93
  42. bumble/smp.py +1 -1
  43. bumble/utils.py +12 -2
  44. bumble/vendor/android/hci.py +1 -1
  45. {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/METADATA +13 -11
  46. {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/RECORD +50 -46
  47. {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/WHEEL +1 -1
  48. {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/entry_points.txt +1 -0
  49. bumble/apps/lea_unicast/liblc3.wasm +0 -0
  50. {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/LICENSE +0 -0
  51. {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/top_level.txt +0 -0
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
 
@@ -78,12 +80,18 @@ from .gatt import (
78
80
  GATT_INCLUDE_ATTRIBUTE_TYPE,
79
81
  Characteristic,
80
82
  ClientCharacteristicConfigurationBits,
83
+ InvalidServiceError,
81
84
  TemplateService,
82
85
  )
83
86
 
87
+ # -----------------------------------------------------------------------------
88
+ # Typing
89
+ # -----------------------------------------------------------------------------
84
90
  if TYPE_CHECKING:
85
91
  from bumble.device import Connection
86
92
 
93
+ _T = TypeVar('_T')
94
+
87
95
  # -----------------------------------------------------------------------------
88
96
  # Logging
89
97
  # -----------------------------------------------------------------------------
@@ -109,7 +117,7 @@ def show_services(services: Iterable[ServiceProxy]) -> None:
109
117
  # -----------------------------------------------------------------------------
110
118
  # Proxies
111
119
  # -----------------------------------------------------------------------------
112
- class AttributeProxy(EventEmitter):
120
+ class AttributeProxy(EventEmitter, Generic[_T]):
113
121
  def __init__(
114
122
  self, client: Client, handle: int, end_group_handle: int, attribute_type: UUID
115
123
  ) -> None:
@@ -119,21 +127,21 @@ class AttributeProxy(EventEmitter):
119
127
  self.end_group_handle = end_group_handle
120
128
  self.type = attribute_type
121
129
 
122
- async def read_value(self, no_long_read: bool = False) -> bytes:
130
+ async def read_value(self, no_long_read: bool = False) -> _T:
123
131
  return self.decode_value(
124
132
  await self.client.read_value(self.handle, no_long_read)
125
133
  )
126
134
 
127
- async def write_value(self, value, with_response=False):
135
+ async def write_value(self, value: _T, with_response=False):
128
136
  return await self.client.write_value(
129
137
  self.handle, self.encode_value(value), with_response
130
138
  )
131
139
 
132
- def encode_value(self, value: Any) -> bytes:
133
- return value
140
+ def encode_value(self, value: _T) -> bytes:
141
+ return value # type: ignore
134
142
 
135
- def decode_value(self, value_bytes: bytes) -> Any:
136
- return value_bytes
143
+ def decode_value(self, value: bytes) -> _T:
144
+ return value # type: ignore
137
145
 
138
146
  def __str__(self) -> str:
139
147
  return f'Attribute(handle=0x{self.handle:04X}, type={self.type})'
@@ -162,29 +170,40 @@ class ServiceProxy(AttributeProxy):
162
170
  self.uuid = uuid
163
171
  self.characteristics = []
164
172
 
165
- async def discover_characteristics(self, uuids=()):
173
+ async def discover_characteristics(self, uuids=()) -> list[CharacteristicProxy]:
166
174
  return await self.client.discover_characteristics(uuids, self)
167
175
 
168
- def get_characteristics_by_uuid(self, uuid):
176
+ def get_characteristics_by_uuid(self, uuid: UUID) -> list[CharacteristicProxy]:
177
+ """Get all the characteristics with a specified UUID."""
169
178
  return self.client.get_characteristics_by_uuid(uuid, self)
170
179
 
180
+ def get_required_characteristic_by_uuid(self, uuid: UUID) -> CharacteristicProxy:
181
+ """
182
+ Get the first characteristic with a specified UUID.
183
+
184
+ If no characteristic with that UUID is found, an InvalidServiceError is raised.
185
+ """
186
+ if not (characteristics := self.get_characteristics_by_uuid(uuid)):
187
+ raise InvalidServiceError(f'{uuid} characteristic not found')
188
+ return characteristics[0]
189
+
171
190
  def __str__(self) -> str:
172
191
  return f'Service(handle=0x{self.handle:04X}, uuid={self.uuid})'
173
192
 
174
193
 
175
- class CharacteristicProxy(AttributeProxy):
194
+ class CharacteristicProxy(AttributeProxy[_T]):
176
195
  properties: Characteristic.Properties
177
196
  descriptors: List[DescriptorProxy]
178
- subscribers: Dict[Any, Callable[[bytes], Any]]
197
+ subscribers: Dict[Any, Callable[[_T], Any]]
179
198
 
180
199
  def __init__(
181
200
  self,
182
- client,
183
- handle,
184
- end_group_handle,
185
- uuid,
201
+ client: Client,
202
+ handle: int,
203
+ end_group_handle: int,
204
+ uuid: UUID,
186
205
  properties: int,
187
- ):
206
+ ) -> None:
188
207
  super().__init__(client, handle, end_group_handle, uuid)
189
208
  self.uuid = uuid
190
209
  self.properties = Characteristic.Properties(properties)
@@ -192,21 +211,21 @@ class CharacteristicProxy(AttributeProxy):
192
211
  self.descriptors_discovered = False
193
212
  self.subscribers = {} # Map from subscriber to proxy subscriber
194
213
 
195
- def get_descriptor(self, descriptor_type):
214
+ def get_descriptor(self, descriptor_type: UUID) -> Optional[DescriptorProxy]:
196
215
  for descriptor in self.descriptors:
197
216
  if descriptor.type == descriptor_type:
198
217
  return descriptor
199
218
 
200
219
  return None
201
220
 
202
- async def discover_descriptors(self):
221
+ async def discover_descriptors(self) -> list[DescriptorProxy]:
203
222
  return await self.client.discover_descriptors(self)
204
223
 
205
224
  async def subscribe(
206
225
  self,
207
- subscriber: Optional[Callable[[bytes], Any]] = None,
226
+ subscriber: Optional[Callable[[_T], Any]] = None,
208
227
  prefer_notify: bool = True,
209
- ):
228
+ ) -> None:
210
229
  if subscriber is not None:
211
230
  if subscriber in self.subscribers:
212
231
  # We already have a proxy subscriber
@@ -221,13 +240,13 @@ class CharacteristicProxy(AttributeProxy):
221
240
  self.subscribers[subscriber] = on_change
222
241
  subscriber = on_change
223
242
 
224
- return await self.client.subscribe(self, subscriber, prefer_notify)
243
+ await self.client.subscribe(self, subscriber, prefer_notify)
225
244
 
226
- async def unsubscribe(self, subscriber=None, force=False):
245
+ async def unsubscribe(self, subscriber=None, force=False) -> None:
227
246
  if subscriber in self.subscribers:
228
247
  subscriber = self.subscribers.pop(subscriber)
229
248
 
230
- return await self.client.unsubscribe(self, subscriber, force)
249
+ await self.client.unsubscribe(self, subscriber, force)
231
250
 
232
251
  def __str__(self) -> str:
233
252
  return (
@@ -238,7 +257,7 @@ class CharacteristicProxy(AttributeProxy):
238
257
 
239
258
 
240
259
  class DescriptorProxy(AttributeProxy):
241
- def __init__(self, client, handle, descriptor_type):
260
+ def __init__(self, client: Client, handle: int, descriptor_type: UUID) -> None:
242
261
  super().__init__(client, handle, 0, descriptor_type)
243
262
 
244
263
  def __str__(self) -> str:
@@ -667,7 +686,7 @@ class Client:
667
686
 
668
687
  properties, handle = struct.unpack_from('<BH', attribute_value)
669
688
  characteristic_uuid = UUID.from_bytes(attribute_value[3:])
670
- characteristic = CharacteristicProxy(
689
+ characteristic: CharacteristicProxy = CharacteristicProxy(
671
690
  self, handle, 0, characteristic_uuid, properties
672
691
  )
673
692
 
@@ -793,7 +812,7 @@ class Client:
793
812
  logger.warning(f'bogus handle value: {attribute_handle}')
794
813
  return []
795
814
 
796
- attribute = AttributeProxy(
815
+ attribute: AttributeProxy = AttributeProxy(
797
816
  self, attribute_handle, 0, UUID.from_bytes(attribute_uuid)
798
817
  )
799
818
  attributes.append(attribute)
@@ -806,7 +825,7 @@ class Client:
806
825
  async def subscribe(
807
826
  self,
808
827
  characteristic: CharacteristicProxy,
809
- subscriber: Optional[Callable[[bytes], Any]] = None,
828
+ subscriber: Optional[Callable[[Any], Any]] = None,
810
829
  prefer_notify: bool = True,
811
830
  ) -> None:
812
831
  # If we haven't already discovered the descriptors for this characteristic,
@@ -856,7 +875,7 @@ class Client:
856
875
  async def unsubscribe(
857
876
  self,
858
877
  characteristic: CharacteristicProxy,
859
- subscriber: Optional[Callable[[bytes], Any]] = None,
878
+ subscriber: Optional[Callable[[Any], Any]] = None,
860
879
  force: bool = False,
861
880
  ) -> None:
862
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: