bumble 0.0.220__py3-none-any.whl → 0.0.222__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 +2 -2
- bumble/a2dp.py +5 -5
- bumble/apps/auracast.py +746 -473
- bumble/apps/bench.py +4 -5
- bumble/apps/console.py +5 -10
- bumble/apps/controller_info.py +12 -7
- bumble/apps/controller_loopback.py +1 -2
- bumble/apps/device_info.py +2 -3
- bumble/apps/gatt_dump.py +0 -1
- bumble/apps/lea_unicast/app.py +1 -1
- bumble/apps/pair.py +49 -46
- bumble/apps/pandora_server.py +2 -2
- bumble/apps/player/player.py +10 -12
- bumble/apps/rfcomm_bridge.py +10 -11
- bumble/apps/scan.py +1 -3
- bumble/apps/speaker/speaker.py +3 -4
- bumble/at.py +4 -5
- bumble/att.py +91 -25
- bumble/audio/io.py +5 -3
- bumble/avc.py +1 -2
- bumble/avctp.py +2 -3
- bumble/avdtp.py +53 -57
- bumble/avrcp.py +25 -27
- bumble/codecs.py +15 -15
- bumble/colors.py +7 -8
- bumble/controller.py +663 -391
- bumble/core.py +41 -49
- bumble/crypto/__init__.py +2 -1
- bumble/crypto/builtin.py +2 -8
- bumble/data_types.py +2 -1
- bumble/decoder.py +2 -3
- bumble/device.py +171 -142
- bumble/drivers/__init__.py +3 -2
- bumble/drivers/intel.py +6 -8
- bumble/drivers/rtk.py +1 -1
- bumble/gatt.py +9 -9
- bumble/gatt_adapters.py +6 -6
- bumble/gatt_client.py +110 -60
- bumble/gatt_server.py +209 -139
- bumble/hci.py +87 -74
- bumble/helpers.py +5 -5
- bumble/hfp.py +27 -26
- bumble/hid.py +9 -9
- bumble/host.py +44 -50
- bumble/keys.py +17 -17
- bumble/l2cap.py +1070 -218
- bumble/link.py +26 -159
- bumble/ll.py +200 -0
- bumble/pairing.py +14 -15
- bumble/pandora/__init__.py +2 -2
- bumble/pandora/device.py +6 -4
- bumble/pandora/host.py +19 -10
- bumble/pandora/l2cap.py +8 -9
- bumble/pandora/security.py +18 -16
- bumble/pandora/utils.py +4 -4
- bumble/profiles/aics.py +6 -8
- bumble/profiles/ams.py +3 -5
- bumble/profiles/ancs.py +11 -11
- bumble/profiles/ascs.py +5 -5
- bumble/profiles/asha.py +10 -9
- bumble/profiles/bass.py +9 -3
- bumble/profiles/battery_service.py +1 -2
- bumble/profiles/csip.py +9 -10
- bumble/profiles/device_information_service.py +16 -17
- bumble/profiles/gap.py +3 -4
- bumble/profiles/gatt_service.py +0 -1
- bumble/profiles/gmap.py +12 -13
- bumble/profiles/hap.py +3 -3
- bumble/profiles/heart_rate_service.py +7 -8
- bumble/profiles/le_audio.py +1 -1
- bumble/profiles/mcp.py +28 -28
- bumble/profiles/pacs.py +13 -17
- bumble/profiles/pbp.py +16 -0
- bumble/profiles/vcs.py +2 -2
- bumble/profiles/vocs.py +6 -9
- bumble/rfcomm.py +19 -18
- bumble/sdp.py +12 -11
- bumble/smp.py +20 -30
- bumble/snoop.py +2 -1
- bumble/tools/generate_company_id_list.py +1 -1
- bumble/tools/intel_util.py +2 -2
- bumble/tools/rtk_fw_download.py +1 -1
- bumble/tools/rtk_util.py +1 -1
- bumble/transport/__init__.py +1 -2
- bumble/transport/android_emulator.py +2 -3
- bumble/transport/android_netsim.py +49 -40
- bumble/transport/common.py +9 -9
- bumble/transport/file.py +1 -2
- bumble/transport/hci_socket.py +2 -3
- bumble/transport/pty.py +3 -5
- bumble/transport/pyusb.py +8 -5
- bumble/transport/serial.py +1 -2
- bumble/transport/vhci.py +1 -2
- bumble/transport/ws_server.py +2 -3
- bumble/utils.py +22 -9
- bumble/vendor/android/hci.py +4 -2
- {bumble-0.0.220.dist-info → bumble-0.0.222.dist-info}/METADATA +3 -2
- {bumble-0.0.220.dist-info → bumble-0.0.222.dist-info}/RECORD +102 -101
- {bumble-0.0.220.dist-info → bumble-0.0.222.dist-info}/WHEEL +0 -0
- {bumble-0.0.220.dist-info → bumble-0.0.222.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.220.dist-info → bumble-0.0.222.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.220.dist-info → bumble-0.0.222.dist-info}/top_level.txt +0 -0
bumble/drivers/__init__.py
CHANGED
|
@@ -24,7 +24,8 @@ from __future__ import annotations
|
|
|
24
24
|
import logging
|
|
25
25
|
import pathlib
|
|
26
26
|
import platform
|
|
27
|
-
from
|
|
27
|
+
from collections.abc import Iterable
|
|
28
|
+
from typing import TYPE_CHECKING
|
|
28
29
|
|
|
29
30
|
from bumble.drivers import intel, rtk
|
|
30
31
|
from bumble.drivers.common import Driver
|
|
@@ -41,7 +42,7 @@ logger = logging.getLogger(__name__)
|
|
|
41
42
|
# -----------------------------------------------------------------------------
|
|
42
43
|
# Functions
|
|
43
44
|
# -----------------------------------------------------------------------------
|
|
44
|
-
async def get_driver_for_host(host: Host) ->
|
|
45
|
+
async def get_driver_for_host(host: Host) -> Driver | None:
|
|
45
46
|
"""Probe diver classes until one returns a valid instance for a host, or none is
|
|
46
47
|
found.
|
|
47
48
|
If a "driver" HCI metadata entry is present, only that driver class will be probed.
|
bumble/drivers/intel.py
CHANGED
|
@@ -29,7 +29,7 @@ import os
|
|
|
29
29
|
import pathlib
|
|
30
30
|
import platform
|
|
31
31
|
import struct
|
|
32
|
-
from typing import TYPE_CHECKING, Any
|
|
32
|
+
from typing import TYPE_CHECKING, Any
|
|
33
33
|
|
|
34
34
|
from bumble import core, hci, utils
|
|
35
35
|
from bumble.drivers import common
|
|
@@ -353,8 +353,8 @@ class Driver(common.Driver):
|
|
|
353
353
|
self.reset_complete = asyncio.Event()
|
|
354
354
|
|
|
355
355
|
# Parse configuration options from the driver name.
|
|
356
|
-
self.ddc_addon:
|
|
357
|
-
self.ddc_override:
|
|
356
|
+
self.ddc_addon: bytes | None = None
|
|
357
|
+
self.ddc_override: bytes | None = None
|
|
358
358
|
driver = host.hci_metadata.get("driver")
|
|
359
359
|
if driver is not None and driver.startswith("intel/"):
|
|
360
360
|
for key, value in [
|
|
@@ -380,7 +380,7 @@ class Driver(common.Driver):
|
|
|
380
380
|
|
|
381
381
|
if (vendor_id, product_id) not in INTEL_USB_PRODUCTS:
|
|
382
382
|
logger.debug(
|
|
383
|
-
f"USB device ({vendor_id:04X}, {product_id:04X})
|
|
383
|
+
f"USB device ({vendor_id:04X}, {product_id:04X}) not in known list"
|
|
384
384
|
)
|
|
385
385
|
return False
|
|
386
386
|
|
|
@@ -483,9 +483,7 @@ class Driver(common.Driver):
|
|
|
483
483
|
raise DriverError("insufficient device info, missing CNVI or CNVR")
|
|
484
484
|
|
|
485
485
|
firmware_base_name = (
|
|
486
|
-
"ibt-"
|
|
487
|
-
f"{device_info[ValueType.CNVI]:04X}-"
|
|
488
|
-
f"{device_info[ValueType.CNVR]:04X}"
|
|
486
|
+
f"ibt-{device_info[ValueType.CNVI]:04X}-{device_info[ValueType.CNVR]:04X}"
|
|
489
487
|
)
|
|
490
488
|
logger.debug(f"FW base name: {firmware_base_name}")
|
|
491
489
|
|
|
@@ -604,7 +602,7 @@ class Driver(common.Driver):
|
|
|
604
602
|
|
|
605
603
|
await self.load_ddc_if_any(firmware_base_name)
|
|
606
604
|
|
|
607
|
-
async def load_ddc_if_any(self, firmware_base_name:
|
|
605
|
+
async def load_ddc_if_any(self, firmware_base_name: str | None = None) -> None:
|
|
608
606
|
"""
|
|
609
607
|
Check for and load any Device Data Configuration (DDC) blobs.
|
|
610
608
|
|
bumble/drivers/rtk.py
CHANGED
|
@@ -484,7 +484,7 @@ class Driver(common.Driver):
|
|
|
484
484
|
|
|
485
485
|
if (vendor_id, product_id) not in RTK_USB_PRODUCTS:
|
|
486
486
|
logger.debug(
|
|
487
|
-
f"USB device ({vendor_id:04X}, {product_id:04X})
|
|
487
|
+
f"USB device ({vendor_id:04X}, {product_id:04X}) not in known list"
|
|
488
488
|
)
|
|
489
489
|
return False
|
|
490
490
|
|
bumble/gatt.py
CHANGED
|
@@ -28,9 +28,10 @@ import enum
|
|
|
28
28
|
import functools
|
|
29
29
|
import logging
|
|
30
30
|
import struct
|
|
31
|
-
from
|
|
31
|
+
from collections.abc import Iterable, Sequence
|
|
32
|
+
from typing import TypeVar
|
|
32
33
|
|
|
33
|
-
from bumble.att import Attribute, AttributeValue
|
|
34
|
+
from bumble.att import Attribute, AttributeValue, AttributeValueV2
|
|
34
35
|
from bumble.colors import color
|
|
35
36
|
from bumble.core import UUID, BaseBumbleError
|
|
36
37
|
|
|
@@ -227,7 +228,6 @@ GATT_MEDIA_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x
|
|
|
227
228
|
GATT_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED_CHARACTERISTIC = UUID.from_16_bits(0x2BA5, 'Media Control Point Opcodes Supported')
|
|
228
229
|
GATT_SEARCH_RESULTS_OBJECT_ID_CHARACTERISTIC = UUID.from_16_bits(0x2BA6, 'Search Results Object ID')
|
|
229
230
|
GATT_SEARCH_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2BA7, 'Search Control Point')
|
|
230
|
-
GATT_CONTENT_CONTROL_ID_CHARACTERISTIC = UUID.from_16_bits(0x2BBA, 'Content Control Id')
|
|
231
231
|
|
|
232
232
|
# Telephone Bearer Service (TBS)
|
|
233
233
|
GATT_BEARER_PROVIDER_NAME_CHARACTERISTIC = UUID.from_16_bits(0x2BB3, 'Bearer Provider Name')
|
|
@@ -356,7 +356,7 @@ class Service(Attribute):
|
|
|
356
356
|
|
|
357
357
|
def __init__(
|
|
358
358
|
self,
|
|
359
|
-
uuid:
|
|
359
|
+
uuid: str | UUID,
|
|
360
360
|
characteristics: Iterable[Characteristic],
|
|
361
361
|
primary=True,
|
|
362
362
|
included_services: Iterable[Service] = (),
|
|
@@ -379,7 +379,7 @@ class Service(Attribute):
|
|
|
379
379
|
self.characteristics = list(characteristics)
|
|
380
380
|
self.primary = primary
|
|
381
381
|
|
|
382
|
-
def get_advertising_data(self) ->
|
|
382
|
+
def get_advertising_data(self) -> bytes | None:
|
|
383
383
|
"""
|
|
384
384
|
Get Service specific advertising data
|
|
385
385
|
Defined by each Service, default value is empty
|
|
@@ -503,10 +503,10 @@ class Characteristic(Attribute[_T]):
|
|
|
503
503
|
|
|
504
504
|
def __init__(
|
|
505
505
|
self,
|
|
506
|
-
uuid:
|
|
506
|
+
uuid: str | bytes | UUID,
|
|
507
507
|
properties: Characteristic.Properties,
|
|
508
|
-
permissions:
|
|
509
|
-
value:
|
|
508
|
+
permissions: str | Attribute.Permissions,
|
|
509
|
+
value: AttributeValue[_T] | _T | None = None,
|
|
510
510
|
descriptors: Sequence[Descriptor] = (),
|
|
511
511
|
):
|
|
512
512
|
super().__init__(uuid, permissions, value)
|
|
@@ -579,7 +579,7 @@ class Descriptor(Attribute):
|
|
|
579
579
|
def __str__(self) -> str:
|
|
580
580
|
if isinstance(self.value, bytes):
|
|
581
581
|
value_str = self.value.hex()
|
|
582
|
-
elif isinstance(self.value,
|
|
582
|
+
elif isinstance(self.value, (AttributeValue, AttributeValueV2)):
|
|
583
583
|
value_str = '<dynamic>'
|
|
584
584
|
else:
|
|
585
585
|
value_str = '<...>'
|
bumble/gatt_adapters.py
CHANGED
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
from __future__ import annotations
|
|
23
23
|
|
|
24
24
|
import struct
|
|
25
|
-
from
|
|
25
|
+
from collections.abc import Callable, Iterable
|
|
26
|
+
from typing import Any, Generic, Literal, TypeVar
|
|
26
27
|
|
|
27
28
|
from bumble import utils
|
|
28
29
|
from bumble.core import InvalidOperationError
|
|
@@ -74,8 +75,8 @@ class DelegatedCharacteristicAdapter(CharacteristicAdapter[_T]):
|
|
|
74
75
|
def __init__(
|
|
75
76
|
self,
|
|
76
77
|
characteristic: Characteristic,
|
|
77
|
-
encode:
|
|
78
|
-
decode:
|
|
78
|
+
encode: Callable[[_T], bytes] | None = None,
|
|
79
|
+
decode: Callable[[bytes], _T] | None = None,
|
|
79
80
|
):
|
|
80
81
|
super().__init__(characteristic)
|
|
81
82
|
self.encode = encode
|
|
@@ -101,8 +102,8 @@ class DelegatedCharacteristicProxyAdapter(CharacteristicProxyAdapter[_T]):
|
|
|
101
102
|
def __init__(
|
|
102
103
|
self,
|
|
103
104
|
characteristic_proxy: CharacteristicProxy,
|
|
104
|
-
encode:
|
|
105
|
-
decode:
|
|
105
|
+
encode: Callable[[_T], bytes] | None = None,
|
|
106
|
+
decode: Callable[[bytes], _T] | None = None,
|
|
106
107
|
):
|
|
107
108
|
super().__init__(characteristic_proxy)
|
|
108
109
|
self.encode = encode
|
|
@@ -361,5 +362,4 @@ class EnumCharacteristicProxyAdapter(CharacteristicProxyAdapter[_T3]):
|
|
|
361
362
|
|
|
362
363
|
def decode_value(self, value: bytes) -> _T3:
|
|
363
364
|
int_value = int.from_bytes(value, self.byteorder)
|
|
364
|
-
a = self.cls(int_value)
|
|
365
365
|
return self.cls(int_value)
|
bumble/gatt_client.py
CHANGED
|
@@ -26,21 +26,20 @@
|
|
|
26
26
|
from __future__ import annotations
|
|
27
27
|
|
|
28
28
|
import asyncio
|
|
29
|
+
import functools
|
|
29
30
|
import logging
|
|
30
31
|
import struct
|
|
32
|
+
from collections.abc import Callable, Iterable
|
|
31
33
|
from datetime import datetime
|
|
32
34
|
from typing import (
|
|
33
35
|
TYPE_CHECKING,
|
|
34
36
|
Any,
|
|
35
|
-
Callable,
|
|
36
37
|
Generic,
|
|
37
|
-
Iterable,
|
|
38
|
-
Optional,
|
|
39
38
|
TypeVar,
|
|
40
|
-
|
|
39
|
+
overload,
|
|
41
40
|
)
|
|
42
41
|
|
|
43
|
-
from bumble import att, core, utils
|
|
42
|
+
from bumble import att, core, l2cap, utils
|
|
44
43
|
from bumble.colors import color
|
|
45
44
|
from bumble.core import UUID, InvalidStateError
|
|
46
45
|
from bumble.gatt import (
|
|
@@ -57,12 +56,12 @@ from bumble.gatt import (
|
|
|
57
56
|
)
|
|
58
57
|
from bumble.hci import HCI_Constant
|
|
59
58
|
|
|
59
|
+
if TYPE_CHECKING:
|
|
60
|
+
from bumble import device as device_module
|
|
61
|
+
|
|
60
62
|
# -----------------------------------------------------------------------------
|
|
61
63
|
# Typing
|
|
62
64
|
# -----------------------------------------------------------------------------
|
|
63
|
-
if TYPE_CHECKING:
|
|
64
|
-
from bumble.device import Connection
|
|
65
|
-
|
|
66
65
|
_T = TypeVar('_T')
|
|
67
66
|
|
|
68
67
|
# -----------------------------------------------------------------------------
|
|
@@ -192,7 +191,7 @@ class CharacteristicProxy(AttributeProxy[_T]):
|
|
|
192
191
|
self.descriptors_discovered = False
|
|
193
192
|
self.subscribers = {} # Map from subscriber to proxy subscriber
|
|
194
193
|
|
|
195
|
-
def get_descriptor(self, descriptor_type: UUID) ->
|
|
194
|
+
def get_descriptor(self, descriptor_type: UUID) -> DescriptorProxy | None:
|
|
196
195
|
for descriptor in self.descriptors:
|
|
197
196
|
if descriptor.type == descriptor_type:
|
|
198
197
|
return descriptor
|
|
@@ -204,7 +203,7 @@ class CharacteristicProxy(AttributeProxy[_T]):
|
|
|
204
203
|
|
|
205
204
|
async def subscribe(
|
|
206
205
|
self,
|
|
207
|
-
subscriber:
|
|
206
|
+
subscriber: Callable[[_T], Any] | None = None,
|
|
208
207
|
prefer_notify: bool = True,
|
|
209
208
|
) -> None:
|
|
210
209
|
if subscriber is not None:
|
|
@@ -253,7 +252,7 @@ class ProfileServiceProxy:
|
|
|
253
252
|
SERVICE_CLASS: type[TemplateService]
|
|
254
253
|
|
|
255
254
|
@classmethod
|
|
256
|
-
def from_client(cls, client: Client) ->
|
|
255
|
+
def from_client(cls, client: Client) -> ProfileServiceProxy | None:
|
|
257
256
|
return ServiceProxy.from_client(cls, client, cls.SERVICE_CLASS.UUID)
|
|
258
257
|
|
|
259
258
|
|
|
@@ -264,16 +263,14 @@ class Client:
|
|
|
264
263
|
services: list[ServiceProxy]
|
|
265
264
|
cached_values: dict[int, tuple[datetime, bytes]]
|
|
266
265
|
notification_subscribers: dict[
|
|
267
|
-
int, set[
|
|
266
|
+
int, set[CharacteristicProxy | Callable[[bytes], Any]]
|
|
268
267
|
]
|
|
269
|
-
indication_subscribers: dict[
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
pending_response: Optional[asyncio.futures.Future[att.ATT_PDU]]
|
|
273
|
-
pending_request: Optional[att.ATT_PDU]
|
|
268
|
+
indication_subscribers: dict[int, set[CharacteristicProxy | Callable[[bytes], Any]]]
|
|
269
|
+
pending_response: asyncio.futures.Future[att.ATT_PDU] | None
|
|
270
|
+
pending_request: att.ATT_PDU | None
|
|
274
271
|
|
|
275
|
-
def __init__(self,
|
|
276
|
-
self.
|
|
272
|
+
def __init__(self, bearer: att.Bearer) -> None:
|
|
273
|
+
self.bearer = bearer
|
|
277
274
|
self.mtu_exchange_done = False
|
|
278
275
|
self.request_semaphore = asyncio.Semaphore(1)
|
|
279
276
|
self.pending_request = None
|
|
@@ -283,21 +280,78 @@ class Client:
|
|
|
283
280
|
self.services = []
|
|
284
281
|
self.cached_values = {}
|
|
285
282
|
|
|
286
|
-
|
|
283
|
+
if att.is_enhanced_bearer(bearer):
|
|
284
|
+
bearer.on(bearer.EVENT_CLOSE, self.on_disconnection)
|
|
285
|
+
self._bearer_id = (
|
|
286
|
+
f'[0x{bearer.connection.handle:04X}|CID=0x{bearer.source_cid:04X}]'
|
|
287
|
+
)
|
|
288
|
+
# Fill the mtu.
|
|
289
|
+
bearer.on_att_mtu_update(att.ATT_DEFAULT_MTU)
|
|
290
|
+
self.connection = bearer.connection
|
|
291
|
+
else:
|
|
292
|
+
bearer.on(bearer.EVENT_DISCONNECTION, self.on_disconnection)
|
|
293
|
+
self._bearer_id = f'[0x{bearer.handle:04X}]'
|
|
294
|
+
self.connection = bearer
|
|
295
|
+
|
|
296
|
+
@overload
|
|
297
|
+
@classmethod
|
|
298
|
+
async def connect_eatt(
|
|
299
|
+
cls,
|
|
300
|
+
connection: device_module.Connection,
|
|
301
|
+
spec: l2cap.LeCreditBasedChannelSpec | None = None,
|
|
302
|
+
) -> Client: ...
|
|
303
|
+
|
|
304
|
+
@overload
|
|
305
|
+
@classmethod
|
|
306
|
+
async def connect_eatt(
|
|
307
|
+
cls,
|
|
308
|
+
connection: device_module.Connection,
|
|
309
|
+
spec: l2cap.LeCreditBasedChannelSpec | None = None,
|
|
310
|
+
count: int = 1,
|
|
311
|
+
) -> list[Client]: ...
|
|
312
|
+
|
|
313
|
+
@classmethod
|
|
314
|
+
async def connect_eatt(
|
|
315
|
+
cls,
|
|
316
|
+
connection: device_module.Connection,
|
|
317
|
+
spec: l2cap.LeCreditBasedChannelSpec | None = None,
|
|
318
|
+
count: int = 1,
|
|
319
|
+
) -> list[Client] | Client:
|
|
320
|
+
channels = await connection.device.l2cap_channel_manager.create_enhanced_credit_based_channels(
|
|
321
|
+
connection,
|
|
322
|
+
spec or l2cap.LeCreditBasedChannelSpec(psm=att.EATT_PSM),
|
|
323
|
+
count,
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
def on_pdu(client: Client, pdu: bytes):
|
|
327
|
+
client.on_gatt_pdu(att.ATT_PDU.from_bytes(pdu))
|
|
328
|
+
|
|
329
|
+
clients = [cls(channel) for channel in channels]
|
|
330
|
+
for channel, client in zip(channels, clients):
|
|
331
|
+
channel.sink = functools.partial(on_pdu, client)
|
|
332
|
+
channel.att_mtu = att.ATT_DEFAULT_MTU
|
|
333
|
+
return clients[0] if count == 1 else clients
|
|
334
|
+
|
|
335
|
+
@property
|
|
336
|
+
def mtu(self) -> int:
|
|
337
|
+
return self.bearer.att_mtu
|
|
338
|
+
|
|
339
|
+
@mtu.setter
|
|
340
|
+
def mtu(self, value: int) -> None:
|
|
341
|
+
self.bearer.on_att_mtu_update(value)
|
|
287
342
|
|
|
288
343
|
def send_gatt_pdu(self, pdu: bytes) -> None:
|
|
289
|
-
|
|
344
|
+
if att.is_enhanced_bearer(self.bearer):
|
|
345
|
+
self.bearer.write(pdu)
|
|
346
|
+
else:
|
|
347
|
+
self.bearer.send_l2cap_pdu(att.ATT_CID, pdu)
|
|
290
348
|
|
|
291
349
|
async def send_command(self, command: att.ATT_PDU) -> None:
|
|
292
|
-
logger.debug(
|
|
293
|
-
f'GATT Command from client: [0x{self.connection.handle:04X}] {command}'
|
|
294
|
-
)
|
|
350
|
+
logger.debug(f'GATT Command from client: {self._bearer_id} {command}')
|
|
295
351
|
self.send_gatt_pdu(bytes(command))
|
|
296
352
|
|
|
297
353
|
async def send_request(self, request: att.ATT_PDU):
|
|
298
|
-
logger.debug(
|
|
299
|
-
f'GATT Request from client: [0x{self.connection.handle:04X}] {request}'
|
|
300
|
-
)
|
|
354
|
+
logger.debug(f'GATT Request from client: {self._bearer_id} {request}')
|
|
301
355
|
|
|
302
356
|
# Wait until we can send (only one pending command at a time for the connection)
|
|
303
357
|
response = None
|
|
@@ -326,10 +380,7 @@ class Client:
|
|
|
326
380
|
def send_confirmation(
|
|
327
381
|
self, confirmation: att.ATT_Handle_Value_Confirmation
|
|
328
382
|
) -> None:
|
|
329
|
-
logger.debug(
|
|
330
|
-
f'GATT Confirmation from client: [0x{self.connection.handle:04X}] '
|
|
331
|
-
f'{confirmation}'
|
|
332
|
-
)
|
|
383
|
+
logger.debug(f'GATT Confirmation from client: {self._bearer_id} {confirmation}')
|
|
333
384
|
self.send_gatt_pdu(bytes(confirmation))
|
|
334
385
|
|
|
335
386
|
async def request_mtu(self, mtu: int) -> int:
|
|
@@ -341,7 +392,7 @@ class Client:
|
|
|
341
392
|
|
|
342
393
|
# We can only send one request per connection
|
|
343
394
|
if self.mtu_exchange_done:
|
|
344
|
-
return self.
|
|
395
|
+
return self.mtu
|
|
345
396
|
|
|
346
397
|
# Send the request
|
|
347
398
|
self.mtu_exchange_done = True
|
|
@@ -352,15 +403,15 @@ class Client:
|
|
|
352
403
|
raise att.ATT_Error(error_code=response.error_code, message=response)
|
|
353
404
|
|
|
354
405
|
# Compute the final MTU
|
|
355
|
-
self.
|
|
406
|
+
self.mtu = min(mtu, response.server_rx_mtu)
|
|
356
407
|
|
|
357
|
-
return self.
|
|
408
|
+
return self.mtu
|
|
358
409
|
|
|
359
410
|
def get_services_by_uuid(self, uuid: UUID) -> list[ServiceProxy]:
|
|
360
411
|
return [service for service in self.services if service.uuid == uuid]
|
|
361
412
|
|
|
362
413
|
def get_characteristics_by_uuid(
|
|
363
|
-
self, uuid: UUID, service:
|
|
414
|
+
self, uuid: UUID, service: ServiceProxy | None = None
|
|
364
415
|
) -> list[CharacteristicProxy[bytes]]:
|
|
365
416
|
services = [service] if service else self.services
|
|
366
417
|
return [
|
|
@@ -369,13 +420,14 @@ class Client:
|
|
|
369
420
|
if c.uuid == uuid
|
|
370
421
|
]
|
|
371
422
|
|
|
372
|
-
def get_attribute_grouping(
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
]
|
|
378
|
-
|
|
423
|
+
def get_attribute_grouping(
|
|
424
|
+
self, attribute_handle: int
|
|
425
|
+
) -> (
|
|
426
|
+
ServiceProxy
|
|
427
|
+
| tuple[ServiceProxy, CharacteristicProxy]
|
|
428
|
+
| tuple[ServiceProxy, CharacteristicProxy, DescriptorProxy]
|
|
429
|
+
| None
|
|
430
|
+
):
|
|
379
431
|
"""
|
|
380
432
|
Get the attribute(s) associated with an attribute handle
|
|
381
433
|
"""
|
|
@@ -478,7 +530,7 @@ class Client:
|
|
|
478
530
|
|
|
479
531
|
return services
|
|
480
532
|
|
|
481
|
-
async def discover_service(self, uuid:
|
|
533
|
+
async def discover_service(self, uuid: str | UUID) -> list[ServiceProxy]:
|
|
482
534
|
'''
|
|
483
535
|
See Vol 3, Part G - 4.4.2 Discover Primary Service by Service UUID
|
|
484
536
|
'''
|
|
@@ -612,7 +664,7 @@ class Client:
|
|
|
612
664
|
return included_services
|
|
613
665
|
|
|
614
666
|
async def discover_characteristics(
|
|
615
|
-
self, uuids, service:
|
|
667
|
+
self, uuids, service: ServiceProxy | None
|
|
616
668
|
) -> list[CharacteristicProxy[bytes]]:
|
|
617
669
|
'''
|
|
618
670
|
See Vol 3, Part G - 4.6.1 Discover All Characteristics of a Service and 4.6.2
|
|
@@ -699,9 +751,9 @@ class Client:
|
|
|
699
751
|
|
|
700
752
|
async def discover_descriptors(
|
|
701
753
|
self,
|
|
702
|
-
characteristic:
|
|
703
|
-
start_handle:
|
|
704
|
-
end_handle:
|
|
754
|
+
characteristic: CharacteristicProxy | None = None,
|
|
755
|
+
start_handle: int | None = None,
|
|
756
|
+
end_handle: int | None = None,
|
|
705
757
|
) -> list[DescriptorProxy]:
|
|
706
758
|
'''
|
|
707
759
|
See Vol 3, Part G - 4.7.1 Discover All Characteristic Descriptors
|
|
@@ -810,7 +862,7 @@ class Client:
|
|
|
810
862
|
async def subscribe(
|
|
811
863
|
self,
|
|
812
864
|
characteristic: CharacteristicProxy,
|
|
813
|
-
subscriber:
|
|
865
|
+
subscriber: Callable[[Any], Any] | None = None,
|
|
814
866
|
prefer_notify: bool = True,
|
|
815
867
|
) -> None:
|
|
816
868
|
# If we haven't already discovered the descriptors for this characteristic,
|
|
@@ -860,7 +912,7 @@ class Client:
|
|
|
860
912
|
async def unsubscribe(
|
|
861
913
|
self,
|
|
862
914
|
characteristic: CharacteristicProxy,
|
|
863
|
-
subscriber:
|
|
915
|
+
subscriber: Callable[[Any], Any] | None = None,
|
|
864
916
|
force: bool = False,
|
|
865
917
|
) -> None:
|
|
866
918
|
'''
|
|
@@ -925,7 +977,7 @@ class Client:
|
|
|
925
977
|
await self.write_value(cccd, b'\x00\x00', with_response=True)
|
|
926
978
|
|
|
927
979
|
async def read_value(
|
|
928
|
-
self, attribute:
|
|
980
|
+
self, attribute: int | AttributeProxy, no_long_read: bool = False
|
|
929
981
|
) -> bytes:
|
|
930
982
|
'''
|
|
931
983
|
See Vol 3, Part G - 4.8.1 Read Characteristic Value
|
|
@@ -946,7 +998,7 @@ class Client:
|
|
|
946
998
|
# If the value is the max size for the MTU, try to read more unless the caller
|
|
947
999
|
# specifically asked not to do that
|
|
948
1000
|
attribute_value = response.attribute_value
|
|
949
|
-
if not no_long_read and len(attribute_value) == self.
|
|
1001
|
+
if not no_long_read and len(attribute_value) == self.mtu - 1:
|
|
950
1002
|
logger.debug('using READ BLOB to get the rest of the value')
|
|
951
1003
|
offset = len(attribute_value)
|
|
952
1004
|
while True:
|
|
@@ -970,7 +1022,7 @@ class Client:
|
|
|
970
1022
|
part = response.part_attribute_value
|
|
971
1023
|
attribute_value += part
|
|
972
1024
|
|
|
973
|
-
if len(part) < self.
|
|
1025
|
+
if len(part) < self.mtu - 1:
|
|
974
1026
|
break
|
|
975
1027
|
|
|
976
1028
|
offset += len(part)
|
|
@@ -980,7 +1032,7 @@ class Client:
|
|
|
980
1032
|
return attribute_value
|
|
981
1033
|
|
|
982
1034
|
async def read_characteristics_by_uuid(
|
|
983
|
-
self, uuid: UUID, service:
|
|
1035
|
+
self, uuid: UUID, service: ServiceProxy | None
|
|
984
1036
|
) -> list[bytes]:
|
|
985
1037
|
'''
|
|
986
1038
|
See Vol 3, Part G - 4.8.2 Read Using Characteristic UUID
|
|
@@ -1038,7 +1090,7 @@ class Client:
|
|
|
1038
1090
|
|
|
1039
1091
|
async def write_value(
|
|
1040
1092
|
self,
|
|
1041
|
-
attribute:
|
|
1093
|
+
attribute: int | AttributeProxy,
|
|
1042
1094
|
value: bytes,
|
|
1043
1095
|
with_response: bool = False,
|
|
1044
1096
|
) -> None:
|
|
@@ -1066,14 +1118,13 @@ class Client:
|
|
|
1066
1118
|
)
|
|
1067
1119
|
)
|
|
1068
1120
|
|
|
1069
|
-
def on_disconnection(self,
|
|
1121
|
+
def on_disconnection(self, *args) -> None:
|
|
1122
|
+
del args # unused.
|
|
1070
1123
|
if self.pending_response and not self.pending_response.done():
|
|
1071
1124
|
self.pending_response.cancel()
|
|
1072
1125
|
|
|
1073
1126
|
def on_gatt_pdu(self, att_pdu: att.ATT_PDU) -> None:
|
|
1074
|
-
logger.debug(
|
|
1075
|
-
f'GATT Response to client: [0x{self.connection.handle:04X}] {att_pdu}'
|
|
1076
|
-
)
|
|
1127
|
+
logger.debug(f'GATT Response to client: {self._bearer_id} {att_pdu}')
|
|
1077
1128
|
if att_pdu.op_code in att.ATT_RESPONSES:
|
|
1078
1129
|
if self.pending_request is None:
|
|
1079
1130
|
# Not expected!
|
|
@@ -1103,8 +1154,7 @@ class Client:
|
|
|
1103
1154
|
else:
|
|
1104
1155
|
logger.warning(
|
|
1105
1156
|
color(
|
|
1106
|
-
'--- Ignoring GATT Response from '
|
|
1107
|
-
f'[0x{self.connection.handle:04X}]: ',
|
|
1157
|
+
'--- Ignoring GATT Response from ' f'{self._bearer_id}: ',
|
|
1108
1158
|
'red',
|
|
1109
1159
|
)
|
|
1110
1160
|
+ str(att_pdu)
|