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.
- bumble/_version.py +9 -4
- bumble/apps/auracast.py +631 -98
- bumble/apps/bench.py +238 -157
- bumble/apps/console.py +19 -12
- bumble/apps/controller_info.py +23 -7
- bumble/apps/device_info.py +50 -4
- bumble/apps/gg_bridge.py +1 -1
- bumble/apps/lea_unicast/app.py +61 -201
- bumble/att.py +51 -37
- bumble/audio/__init__.py +17 -0
- bumble/audio/io.py +553 -0
- bumble/controller.py +24 -9
- bumble/core.py +305 -156
- bumble/device.py +1090 -99
- bumble/gatt.py +36 -226
- bumble/gatt_adapters.py +374 -0
- bumble/gatt_client.py +52 -33
- bumble/gatt_server.py +5 -5
- bumble/hci.py +812 -14
- bumble/host.py +367 -65
- bumble/l2cap.py +3 -16
- bumble/pairing.py +5 -5
- bumble/pandora/host.py +7 -12
- bumble/profiles/aics.py +48 -57
- bumble/profiles/ascs.py +8 -19
- bumble/profiles/asha.py +16 -14
- bumble/profiles/bass.py +16 -22
- bumble/profiles/battery_service.py +13 -3
- bumble/profiles/device_information_service.py +16 -14
- bumble/profiles/gap.py +12 -8
- bumble/profiles/gatt_service.py +167 -0
- bumble/profiles/gmap.py +198 -0
- bumble/profiles/hap.py +8 -6
- bumble/profiles/heart_rate_service.py +20 -4
- bumble/profiles/le_audio.py +87 -4
- bumble/profiles/mcp.py +11 -9
- bumble/profiles/pacs.py +61 -16
- bumble/profiles/tmap.py +8 -12
- bumble/profiles/{vcp.py → vcs.py} +35 -29
- bumble/profiles/vocs.py +62 -85
- bumble/sdp.py +223 -93
- bumble/smp.py +1 -1
- bumble/utils.py +12 -2
- bumble/vendor/android/hci.py +1 -1
- {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/METADATA +13 -11
- {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/RECORD +50 -46
- {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/WHEEL +1 -1
- {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/entry_points.txt +1 -0
- bumble/apps/lea_unicast/liblc3.wasm +0 -0
- {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/LICENSE +0 -0
- {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
|
-
|
|
39
|
+
Set,
|
|
35
40
|
Tuple,
|
|
36
|
-
Callable,
|
|
37
41
|
Union,
|
|
38
|
-
Any,
|
|
39
|
-
Iterable,
|
|
40
42
|
Type,
|
|
41
|
-
|
|
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) ->
|
|
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:
|
|
133
|
-
return value
|
|
140
|
+
def encode_value(self, value: _T) -> bytes:
|
|
141
|
+
return value # type: ignore
|
|
134
142
|
|
|
135
|
-
def decode_value(self,
|
|
136
|
-
return
|
|
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[[
|
|
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[[
|
|
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
|
-
|
|
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
|
-
|
|
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[[
|
|
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[[
|
|
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
|
|
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.
|
|
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.
|
|
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:
|