bumble 0.0.211__py3-none-any.whl → 0.0.213__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 +6 -0
- bumble/apps/README.md +0 -3
- bumble/apps/auracast.py +11 -9
- bumble/apps/bench.py +482 -31
- bumble/apps/console.py +5 -5
- bumble/apps/controller_info.py +47 -10
- bumble/apps/controller_loopback.py +7 -3
- bumble/apps/controllers.py +2 -2
- bumble/apps/device_info.py +2 -2
- bumble/apps/gatt_dump.py +2 -2
- bumble/apps/gg_bridge.py +2 -2
- bumble/apps/hci_bridge.py +2 -2
- bumble/apps/l2cap_bridge.py +2 -2
- bumble/apps/lea_unicast/app.py +6 -1
- bumble/apps/pair.py +204 -43
- bumble/apps/pandora_server.py +2 -2
- bumble/apps/rfcomm_bridge.py +1 -1
- bumble/apps/scan.py +2 -2
- bumble/apps/show.py +4 -2
- bumble/apps/speaker/speaker.html +1 -0
- bumble/apps/speaker/speaker.js +113 -62
- bumble/apps/speaker/speaker.py +126 -18
- bumble/at.py +4 -4
- bumble/att.py +15 -18
- bumble/avc.py +7 -7
- bumble/avctp.py +5 -5
- bumble/avdtp.py +138 -88
- bumble/avrcp.py +52 -58
- bumble/colors.py +2 -2
- bumble/controller.py +84 -23
- bumble/core.py +13 -7
- bumble/{crypto.py → crypto/__init__.py} +11 -95
- bumble/crypto/builtin.py +652 -0
- bumble/crypto/cryptography.py +84 -0
- bumble/device.py +688 -345
- bumble/drivers/__init__.py +2 -2
- bumble/drivers/common.py +0 -2
- bumble/drivers/intel.py +40 -40
- bumble/drivers/rtk.py +28 -35
- bumble/gatt.py +7 -9
- bumble/gatt_adapters.py +4 -5
- bumble/gatt_client.py +31 -34
- bumble/gatt_server.py +15 -17
- bumble/hci.py +2635 -2878
- bumble/helpers.py +4 -5
- bumble/hfp.py +76 -57
- bumble/hid.py +24 -12
- bumble/host.py +117 -34
- bumble/keys.py +68 -52
- bumble/l2cap.py +329 -403
- bumble/link.py +6 -270
- bumble/pairing.py +23 -20
- bumble/pandora/__init__.py +1 -1
- bumble/pandora/config.py +2 -2
- bumble/pandora/device.py +6 -6
- bumble/pandora/host.py +38 -39
- bumble/pandora/l2cap.py +4 -4
- bumble/pandora/security.py +73 -57
- bumble/pandora/utils.py +3 -3
- bumble/profiles/aics.py +3 -5
- bumble/profiles/ancs.py +3 -1
- bumble/profiles/ascs.py +143 -136
- bumble/profiles/asha.py +13 -8
- bumble/profiles/bap.py +3 -4
- bumble/profiles/csip.py +3 -5
- bumble/profiles/device_information_service.py +2 -2
- bumble/profiles/gap.py +2 -2
- bumble/profiles/gatt_service.py +1 -3
- bumble/profiles/hap.py +42 -58
- bumble/profiles/le_audio.py +4 -4
- bumble/profiles/mcp.py +16 -13
- bumble/profiles/vcs.py +8 -10
- bumble/profiles/vocs.py +6 -9
- bumble/rfcomm.py +27 -18
- bumble/rtp.py +1 -2
- bumble/sdp.py +2 -2
- bumble/smp.py +71 -69
- bumble/tools/rtk_util.py +2 -2
- bumble/transport/__init__.py +2 -16
- bumble/transport/android_netsim.py +5 -5
- bumble/transport/common.py +4 -4
- bumble/transport/pyusb.py +2 -2
- bumble/utils.py +2 -5
- bumble/vendor/android/hci.py +118 -200
- bumble/vendor/zephyr/hci.py +32 -27
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/METADATA +5 -5
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/RECORD +92 -93
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/WHEEL +1 -1
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/entry_points.txt +0 -1
- bumble/apps/link_relay/__init__.py +0 -0
- bumble/apps/link_relay/link_relay.py +0 -289
- bumble/apps/link_relay/logging.yml +0 -21
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/top_level.txt +0 -0
bumble/drivers/__init__.py
CHANGED
|
@@ -23,7 +23,7 @@ from __future__ import annotations
|
|
|
23
23
|
import logging
|
|
24
24
|
import pathlib
|
|
25
25
|
import platform
|
|
26
|
-
from typing import
|
|
26
|
+
from typing import Iterable, Optional, TYPE_CHECKING
|
|
27
27
|
|
|
28
28
|
from bumble.drivers import rtk, intel
|
|
29
29
|
from bumble.drivers.common import Driver
|
|
@@ -45,7 +45,7 @@ async def get_driver_for_host(host: Host) -> Optional[Driver]:
|
|
|
45
45
|
found.
|
|
46
46
|
If a "driver" HCI metadata entry is present, only that driver class will be probed.
|
|
47
47
|
"""
|
|
48
|
-
driver_classes:
|
|
48
|
+
driver_classes: dict[str, type[Driver]] = {"rtk": rtk.Driver, "intel": intel.Driver}
|
|
49
49
|
probe_list: Iterable[str]
|
|
50
50
|
if driver_name := host.hci_metadata.get("driver"):
|
|
51
51
|
# Only probe a single driver
|
bumble/drivers/common.py
CHANGED
bumble/drivers/intel.py
CHANGED
|
@@ -28,7 +28,7 @@ import os
|
|
|
28
28
|
import pathlib
|
|
29
29
|
import platform
|
|
30
30
|
import struct
|
|
31
|
-
from typing import Any,
|
|
31
|
+
from typing import Any, Optional, TYPE_CHECKING
|
|
32
32
|
|
|
33
33
|
from bumble import core
|
|
34
34
|
from bumble.drivers import common
|
|
@@ -50,6 +50,7 @@ logger = logging.getLogger(__name__)
|
|
|
50
50
|
|
|
51
51
|
INTEL_USB_PRODUCTS = {
|
|
52
52
|
(0x8087, 0x0032), # AX210
|
|
53
|
+
(0x8087, 0x0033), # AX211
|
|
53
54
|
(0x8087, 0x0036), # BE200
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -89,54 +90,51 @@ HCI_INTEL_WRITE_BOOT_PARAMS_COMMAND = hci.hci_vendor_command_op_code(0x000E)
|
|
|
89
90
|
hci.HCI_Command.register_commands(globals())
|
|
90
91
|
|
|
91
92
|
|
|
92
|
-
@hci.HCI_Command.command
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
@hci.HCI_Command.command
|
|
94
|
+
@dataclasses.dataclass
|
|
95
|
+
class HCI_Intel_Read_Version_Command(hci.HCI_Command):
|
|
96
|
+
param0: int = dataclasses.field(metadata=hci.metadata(1))
|
|
97
|
+
|
|
98
|
+
return_parameters_fields = [
|
|
97
99
|
("status", hci.STATUS_SPEC),
|
|
98
100
|
("tlv", "*"),
|
|
99
|
-
]
|
|
100
|
-
)
|
|
101
|
-
class HCI_Intel_Read_Version_Command(hci.HCI_Command):
|
|
102
|
-
pass
|
|
101
|
+
]
|
|
103
102
|
|
|
104
103
|
|
|
105
|
-
@hci.HCI_Command.command
|
|
106
|
-
|
|
107
|
-
return_parameters_fields=[
|
|
108
|
-
("status", 1),
|
|
109
|
-
],
|
|
110
|
-
)
|
|
104
|
+
@hci.HCI_Command.command
|
|
105
|
+
@dataclasses.dataclass
|
|
111
106
|
class Hci_Intel_Secure_Send_Command(hci.HCI_Command):
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
],
|
|
123
|
-
return_parameters_fields=[
|
|
124
|
-
("data", "*"),
|
|
125
|
-
],
|
|
126
|
-
)
|
|
107
|
+
data_type: int = dataclasses.field(metadata=hci.metadata(1))
|
|
108
|
+
data: bytes = dataclasses.field(metadata=hci.metadata("*"))
|
|
109
|
+
|
|
110
|
+
return_parameters_fields = [
|
|
111
|
+
("status", 1),
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@hci.HCI_Command.command
|
|
116
|
+
@dataclasses.dataclass
|
|
127
117
|
class HCI_Intel_Reset_Command(hci.HCI_Command):
|
|
128
|
-
|
|
118
|
+
reset_type: int = dataclasses.field(metadata=hci.metadata(1))
|
|
119
|
+
patch_enable: int = dataclasses.field(metadata=hci.metadata(1))
|
|
120
|
+
ddc_reload: int = dataclasses.field(metadata=hci.metadata(1))
|
|
121
|
+
boot_option: int = dataclasses.field(metadata=hci.metadata(1))
|
|
122
|
+
boot_address: int = dataclasses.field(metadata=hci.metadata(4))
|
|
123
|
+
|
|
124
|
+
return_parameters_fields = [
|
|
125
|
+
("data", "*"),
|
|
126
|
+
]
|
|
129
127
|
|
|
130
128
|
|
|
131
|
-
@hci.HCI_Command.command
|
|
132
|
-
|
|
133
|
-
|
|
129
|
+
@hci.HCI_Command.command
|
|
130
|
+
@dataclasses.dataclass
|
|
131
|
+
class Hci_Intel_Write_Device_Config_Command(hci.HCI_Command):
|
|
132
|
+
data: bytes = dataclasses.field(metadata=hci.metadata("*"))
|
|
133
|
+
|
|
134
|
+
return_parameters_fields = [
|
|
134
135
|
("status", hci.STATUS_SPEC),
|
|
135
136
|
("params", "*"),
|
|
136
|
-
]
|
|
137
|
-
)
|
|
138
|
-
class Hci_Intel_Write_Device_Config_Command(hci.HCI_Command):
|
|
139
|
-
pass
|
|
137
|
+
]
|
|
140
138
|
|
|
141
139
|
|
|
142
140
|
# -----------------------------------------------------------------------------
|
|
@@ -293,6 +291,7 @@ class HardwareVariant(utils.OpenIntEnum):
|
|
|
293
291
|
# This is a just a partial list.
|
|
294
292
|
# Add other constants here as new hardware is encountered and tested.
|
|
295
293
|
TYPHOON_PEAK = 0x17
|
|
294
|
+
GARFIELD_PEAK = 0x19
|
|
296
295
|
GALE_PEAK = 0x1C
|
|
297
296
|
|
|
298
297
|
|
|
@@ -346,7 +345,7 @@ class Driver(common.Driver):
|
|
|
346
345
|
def __init__(self, host: Host) -> None:
|
|
347
346
|
self.host = host
|
|
348
347
|
self.max_in_flight_firmware_load_commands = 1
|
|
349
|
-
self.pending_firmware_load_commands:
|
|
348
|
+
self.pending_firmware_load_commands: collections.deque[hci.HCI_Command] = (
|
|
350
349
|
collections.deque()
|
|
351
350
|
)
|
|
352
351
|
self.can_send_firmware_load_command = asyncio.Event()
|
|
@@ -471,6 +470,7 @@ class Driver(common.Driver):
|
|
|
471
470
|
raise DriverError("hardware platform not supported")
|
|
472
471
|
if hardware_info.variant not in (
|
|
473
472
|
HardwareVariant.TYPHOON_PEAK,
|
|
473
|
+
HardwareVariant.GARFIELD_PEAK,
|
|
474
474
|
HardwareVariant.GALE_PEAK,
|
|
475
475
|
):
|
|
476
476
|
raise DriverError("hardware variant not supported")
|
bumble/drivers/rtk.py
CHANGED
|
@@ -20,7 +20,7 @@ Based on various online bits of information, including the Linux kernel.
|
|
|
20
20
|
# -----------------------------------------------------------------------------
|
|
21
21
|
# Imports
|
|
22
22
|
# -----------------------------------------------------------------------------
|
|
23
|
-
from dataclasses import dataclass
|
|
23
|
+
from dataclasses import dataclass, field
|
|
24
24
|
import asyncio
|
|
25
25
|
import enum
|
|
26
26
|
import logging
|
|
@@ -29,19 +29,11 @@ import os
|
|
|
29
29
|
import pathlib
|
|
30
30
|
import platform
|
|
31
31
|
import struct
|
|
32
|
-
from typing import Tuple
|
|
33
32
|
import weakref
|
|
34
33
|
|
|
35
34
|
|
|
36
35
|
from bumble import core
|
|
37
|
-
from bumble
|
|
38
|
-
hci_vendor_command_op_code,
|
|
39
|
-
STATUS_SPEC,
|
|
40
|
-
HCI_SUCCESS,
|
|
41
|
-
HCI_Command,
|
|
42
|
-
HCI_Reset_Command,
|
|
43
|
-
HCI_Read_Local_Version_Information_Command,
|
|
44
|
-
)
|
|
36
|
+
from bumble import hci
|
|
45
37
|
from bumble.drivers import common
|
|
46
38
|
|
|
47
39
|
# -----------------------------------------------------------------------------
|
|
@@ -183,27 +175,29 @@ RTK_USB_PRODUCTS = {
|
|
|
183
175
|
# -----------------------------------------------------------------------------
|
|
184
176
|
# HCI Commands
|
|
185
177
|
# -----------------------------------------------------------------------------
|
|
186
|
-
HCI_RTK_READ_ROM_VERSION_COMMAND = hci_vendor_command_op_code(0x6D)
|
|
187
|
-
HCI_RTK_DOWNLOAD_COMMAND = hci_vendor_command_op_code(0x20)
|
|
188
|
-
HCI_RTK_DROP_FIRMWARE_COMMAND = hci_vendor_command_op_code(0x66)
|
|
189
|
-
HCI_Command.register_commands(globals())
|
|
178
|
+
HCI_RTK_READ_ROM_VERSION_COMMAND = hci.hci_vendor_command_op_code(0x6D)
|
|
179
|
+
HCI_RTK_DOWNLOAD_COMMAND = hci.hci_vendor_command_op_code(0x20)
|
|
180
|
+
HCI_RTK_DROP_FIRMWARE_COMMAND = hci.hci_vendor_command_op_code(0x66)
|
|
181
|
+
hci.HCI_Command.register_commands(globals())
|
|
190
182
|
|
|
191
183
|
|
|
192
|
-
@HCI_Command.command
|
|
193
|
-
|
|
194
|
-
|
|
184
|
+
@hci.HCI_Command.command
|
|
185
|
+
@dataclass
|
|
186
|
+
class HCI_RTK_Read_ROM_Version_Command(hci.HCI_Command):
|
|
187
|
+
return_parameters_fields = [("status", hci.STATUS_SPEC), ("version", 1)]
|
|
195
188
|
|
|
196
189
|
|
|
197
|
-
@HCI_Command.command
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
|
|
190
|
+
@hci.HCI_Command.command
|
|
191
|
+
@dataclass
|
|
192
|
+
class HCI_RTK_Download_Command(hci.HCI_Command):
|
|
193
|
+
index: int = field(metadata=hci.metadata(1))
|
|
194
|
+
payload: bytes = field(metadata=hci.metadata(RTK_FRAGMENT_LENGTH))
|
|
195
|
+
return_parameters_fields = [("status", hci.STATUS_SPEC), ("index", 1)]
|
|
203
196
|
|
|
204
197
|
|
|
205
|
-
@HCI_Command.command
|
|
206
|
-
|
|
198
|
+
@hci.HCI_Command.command
|
|
199
|
+
@dataclass
|
|
200
|
+
class HCI_RTK_Drop_Firmware_Command(hci.HCI_Command):
|
|
207
201
|
pass
|
|
208
202
|
|
|
209
203
|
|
|
@@ -294,7 +288,7 @@ class Driver(common.Driver):
|
|
|
294
288
|
@dataclass
|
|
295
289
|
class DriverInfo:
|
|
296
290
|
rom: int
|
|
297
|
-
hci:
|
|
291
|
+
hci: tuple[int, int]
|
|
298
292
|
config_needed: bool
|
|
299
293
|
has_rom_version: bool
|
|
300
294
|
has_msft_ext: bool = False
|
|
@@ -499,17 +493,17 @@ class Driver(common.Driver):
|
|
|
499
493
|
async def driver_info_for_host(cls, host):
|
|
500
494
|
try:
|
|
501
495
|
await host.send_command(
|
|
502
|
-
HCI_Reset_Command(),
|
|
496
|
+
hci.HCI_Reset_Command(),
|
|
503
497
|
check_result=True,
|
|
504
498
|
response_timeout=cls.POST_RESET_DELAY,
|
|
505
499
|
)
|
|
506
500
|
host.ready = True # Needed to let the host know the controller is ready.
|
|
507
501
|
except asyncio.exceptions.TimeoutError:
|
|
508
502
|
logger.warning("timeout waiting for hci reset, retrying")
|
|
509
|
-
await host.send_command(HCI_Reset_Command(), check_result=True)
|
|
503
|
+
await host.send_command(hci.HCI_Reset_Command(), check_result=True)
|
|
510
504
|
host.ready = True
|
|
511
505
|
|
|
512
|
-
command = HCI_Read_Local_Version_Information_Command()
|
|
506
|
+
command = hci.HCI_Read_Local_Version_Information_Command()
|
|
513
507
|
response = await host.send_command(command, check_result=True)
|
|
514
508
|
if response.command_opcode != command.op_code:
|
|
515
509
|
logger.error("failed to probe local version information")
|
|
@@ -596,7 +590,7 @@ class Driver(common.Driver):
|
|
|
596
590
|
response = await self.host.send_command(
|
|
597
591
|
HCI_RTK_Read_ROM_Version_Command(), check_result=True
|
|
598
592
|
)
|
|
599
|
-
if response.return_parameters.status != HCI_SUCCESS:
|
|
593
|
+
if response.return_parameters.status != hci.HCI_SUCCESS:
|
|
600
594
|
logger.warning("can't get ROM version")
|
|
601
595
|
return
|
|
602
596
|
rom_version = response.return_parameters.version
|
|
@@ -634,9 +628,8 @@ class Driver(common.Driver):
|
|
|
634
628
|
fragment = payload[fragment_offset : fragment_offset + RTK_FRAGMENT_LENGTH]
|
|
635
629
|
logger.debug(f"downloading fragment {fragment_index}")
|
|
636
630
|
await self.host.send_command(
|
|
637
|
-
HCI_RTK_Download_Command(
|
|
638
|
-
|
|
639
|
-
)
|
|
631
|
+
HCI_RTK_Download_Command(index=download_index, payload=fragment),
|
|
632
|
+
check_result=True,
|
|
640
633
|
)
|
|
641
634
|
|
|
642
635
|
logger.debug("download complete!")
|
|
@@ -645,7 +638,7 @@ class Driver(common.Driver):
|
|
|
645
638
|
response = await self.host.send_command(
|
|
646
639
|
HCI_RTK_Read_ROM_Version_Command(), check_result=True
|
|
647
640
|
)
|
|
648
|
-
if response.return_parameters.status != HCI_SUCCESS:
|
|
641
|
+
if response.return_parameters.status != hci.HCI_SUCCESS:
|
|
649
642
|
logger.warning("can't get ROM version")
|
|
650
643
|
else:
|
|
651
644
|
rom_version = response.return_parameters.version
|
|
@@ -668,7 +661,7 @@ class Driver(common.Driver):
|
|
|
668
661
|
|
|
669
662
|
async def init_controller(self):
|
|
670
663
|
await self.download_firmware()
|
|
671
|
-
await self.host.send_command(HCI_Reset_Command(), check_result=True)
|
|
664
|
+
await self.host.send_command(hci.HCI_Reset_Command(), check_result=True)
|
|
672
665
|
logger.info(f"loaded FW image {self.driver_info.fw_name}")
|
|
673
666
|
|
|
674
667
|
|
bumble/gatt.py
CHANGED
|
@@ -27,7 +27,7 @@ import enum
|
|
|
27
27
|
import functools
|
|
28
28
|
import logging
|
|
29
29
|
import struct
|
|
30
|
-
from typing import Iterable,
|
|
30
|
+
from typing import Iterable, Optional, Sequence, TypeVar, Union
|
|
31
31
|
|
|
32
32
|
from bumble.colors import color
|
|
33
33
|
from bumble.core import BaseBumbleError, UUID
|
|
@@ -350,8 +350,8 @@ class Service(Attribute):
|
|
|
350
350
|
'''
|
|
351
351
|
|
|
352
352
|
uuid: UUID
|
|
353
|
-
characteristics:
|
|
354
|
-
included_services:
|
|
353
|
+
characteristics: list[Characteristic]
|
|
354
|
+
included_services: list[Service]
|
|
355
355
|
|
|
356
356
|
def __init__(
|
|
357
357
|
self,
|
|
@@ -448,6 +448,8 @@ class Characteristic(Attribute[_T]):
|
|
|
448
448
|
uuid: UUID
|
|
449
449
|
properties: Characteristic.Properties
|
|
450
450
|
|
|
451
|
+
EVENT_SUBSCRIPTION = "subscription"
|
|
452
|
+
|
|
451
453
|
class Properties(enum.IntFlag):
|
|
452
454
|
"""Property flags"""
|
|
453
455
|
|
|
@@ -472,7 +474,7 @@ class Characteristic(Attribute[_T]):
|
|
|
472
474
|
# The check for `p.name is not None` here is needed because for InFlag
|
|
473
475
|
# enums, the .name property can be None, when the enum value is 0,
|
|
474
476
|
# so the type hint for .name is Optional[str].
|
|
475
|
-
enum_list:
|
|
477
|
+
enum_list: list[str] = [p.name for p in cls if p.name is not None]
|
|
476
478
|
enum_list_str = ",".join(enum_list)
|
|
477
479
|
raise TypeError(
|
|
478
480
|
f"Characteristic.Properties::from_string() error:\nExpected a string containing any of the keys, separated by , or |: {enum_list_str}\nGot: {properties_str}"
|
|
@@ -577,11 +579,7 @@ class Descriptor(Attribute):
|
|
|
577
579
|
if isinstance(self.value, bytes):
|
|
578
580
|
value_str = self.value.hex()
|
|
579
581
|
elif isinstance(self.value, CharacteristicValue):
|
|
580
|
-
|
|
581
|
-
if isinstance(value, bytes):
|
|
582
|
-
value_str = value.hex()
|
|
583
|
-
else:
|
|
584
|
-
value_str = '<async>'
|
|
582
|
+
value_str = '<dynamic>'
|
|
585
583
|
else:
|
|
586
584
|
value_str = '<...>'
|
|
587
585
|
return (
|
bumble/gatt_adapters.py
CHANGED
|
@@ -28,7 +28,6 @@ from typing import (
|
|
|
28
28
|
Iterable,
|
|
29
29
|
Literal,
|
|
30
30
|
Optional,
|
|
31
|
-
Type,
|
|
32
31
|
TypeVar,
|
|
33
32
|
)
|
|
34
33
|
|
|
@@ -270,7 +269,7 @@ class SerializableCharacteristicAdapter(CharacteristicAdapter[_T2]):
|
|
|
270
269
|
`to_bytes` and `__bytes__` methods, respectively.
|
|
271
270
|
'''
|
|
272
271
|
|
|
273
|
-
def __init__(self, characteristic: Characteristic, cls:
|
|
272
|
+
def __init__(self, characteristic: Characteristic, cls: type[_T2]) -> None:
|
|
274
273
|
super().__init__(characteristic)
|
|
275
274
|
self.cls = cls
|
|
276
275
|
|
|
@@ -289,7 +288,7 @@ class SerializableCharacteristicProxyAdapter(CharacteristicProxyAdapter[_T2]):
|
|
|
289
288
|
'''
|
|
290
289
|
|
|
291
290
|
def __init__(
|
|
292
|
-
self, characteristic_proxy: CharacteristicProxy, cls:
|
|
291
|
+
self, characteristic_proxy: CharacteristicProxy, cls: type[_T2]
|
|
293
292
|
) -> None:
|
|
294
293
|
super().__init__(characteristic_proxy)
|
|
295
294
|
self.cls = cls
|
|
@@ -311,7 +310,7 @@ class EnumCharacteristicAdapter(CharacteristicAdapter[_T3]):
|
|
|
311
310
|
def __init__(
|
|
312
311
|
self,
|
|
313
312
|
characteristic: Characteristic,
|
|
314
|
-
cls:
|
|
313
|
+
cls: type[_T3],
|
|
315
314
|
length: int,
|
|
316
315
|
byteorder: Literal['little', 'big'] = 'little',
|
|
317
316
|
):
|
|
@@ -347,7 +346,7 @@ class EnumCharacteristicProxyAdapter(CharacteristicProxyAdapter[_T3]):
|
|
|
347
346
|
def __init__(
|
|
348
347
|
self,
|
|
349
348
|
characteristic_proxy: CharacteristicProxy,
|
|
350
|
-
cls:
|
|
349
|
+
cls: type[_T3],
|
|
351
350
|
length: int,
|
|
352
351
|
byteorder: Literal['little', 'big'] = 'little',
|
|
353
352
|
):
|
bumble/gatt_client.py
CHANGED
|
@@ -31,15 +31,10 @@ from datetime import datetime
|
|
|
31
31
|
from typing import (
|
|
32
32
|
Any,
|
|
33
33
|
Callable,
|
|
34
|
-
Dict,
|
|
35
34
|
Generic,
|
|
36
35
|
Iterable,
|
|
37
|
-
List,
|
|
38
36
|
Optional,
|
|
39
|
-
Set,
|
|
40
|
-
Tuple,
|
|
41
37
|
Union,
|
|
42
|
-
Type,
|
|
43
38
|
TypeVar,
|
|
44
39
|
TYPE_CHECKING,
|
|
45
40
|
)
|
|
@@ -149,8 +144,8 @@ class AttributeProxy(utils.EventEmitter, Generic[_T]):
|
|
|
149
144
|
|
|
150
145
|
class ServiceProxy(AttributeProxy):
|
|
151
146
|
uuid: UUID
|
|
152
|
-
characteristics:
|
|
153
|
-
included_services:
|
|
147
|
+
characteristics: list[CharacteristicProxy[bytes]]
|
|
148
|
+
included_services: list[ServiceProxy]
|
|
154
149
|
|
|
155
150
|
@staticmethod
|
|
156
151
|
def from_client(service_class, client: Client, service_uuid: UUID):
|
|
@@ -199,8 +194,10 @@ class ServiceProxy(AttributeProxy):
|
|
|
199
194
|
|
|
200
195
|
class CharacteristicProxy(AttributeProxy[_T]):
|
|
201
196
|
properties: Characteristic.Properties
|
|
202
|
-
descriptors:
|
|
203
|
-
subscribers:
|
|
197
|
+
descriptors: list[DescriptorProxy]
|
|
198
|
+
subscribers: dict[Any, Callable[[_T], Any]]
|
|
199
|
+
|
|
200
|
+
EVENT_UPDATE = "update"
|
|
204
201
|
|
|
205
202
|
def __init__(
|
|
206
203
|
self,
|
|
@@ -275,7 +272,7 @@ class ProfileServiceProxy:
|
|
|
275
272
|
Base class for profile-specific service proxies
|
|
276
273
|
'''
|
|
277
274
|
|
|
278
|
-
SERVICE_CLASS:
|
|
275
|
+
SERVICE_CLASS: type[TemplateService]
|
|
279
276
|
|
|
280
277
|
@classmethod
|
|
281
278
|
def from_client(cls, client: Client) -> Optional[ProfileServiceProxy]:
|
|
@@ -286,13 +283,13 @@ class ProfileServiceProxy:
|
|
|
286
283
|
# GATT Client
|
|
287
284
|
# -----------------------------------------------------------------------------
|
|
288
285
|
class Client:
|
|
289
|
-
services:
|
|
290
|
-
cached_values:
|
|
291
|
-
notification_subscribers:
|
|
292
|
-
int,
|
|
286
|
+
services: list[ServiceProxy]
|
|
287
|
+
cached_values: dict[int, tuple[datetime, bytes]]
|
|
288
|
+
notification_subscribers: dict[
|
|
289
|
+
int, set[Union[CharacteristicProxy, Callable[[bytes], Any]]]
|
|
293
290
|
]
|
|
294
|
-
indication_subscribers:
|
|
295
|
-
int,
|
|
291
|
+
indication_subscribers: dict[
|
|
292
|
+
int, set[Union[CharacteristicProxy, Callable[[bytes], Any]]]
|
|
296
293
|
]
|
|
297
294
|
pending_response: Optional[asyncio.futures.Future[ATT_PDU]]
|
|
298
295
|
pending_request: Optional[ATT_PDU]
|
|
@@ -308,7 +305,7 @@ class Client:
|
|
|
308
305
|
self.services = []
|
|
309
306
|
self.cached_values = {}
|
|
310
307
|
|
|
311
|
-
connection.on(
|
|
308
|
+
connection.on(connection.EVENT_DISCONNECTION, self.on_disconnection)
|
|
312
309
|
|
|
313
310
|
def send_gatt_pdu(self, pdu: bytes) -> None:
|
|
314
311
|
self.connection.send_l2cap_pdu(ATT_CID, pdu)
|
|
@@ -377,12 +374,12 @@ class Client:
|
|
|
377
374
|
|
|
378
375
|
return self.connection.att_mtu
|
|
379
376
|
|
|
380
|
-
def get_services_by_uuid(self, uuid: UUID) ->
|
|
377
|
+
def get_services_by_uuid(self, uuid: UUID) -> list[ServiceProxy]:
|
|
381
378
|
return [service for service in self.services if service.uuid == uuid]
|
|
382
379
|
|
|
383
380
|
def get_characteristics_by_uuid(
|
|
384
381
|
self, uuid: UUID, service: Optional[ServiceProxy] = None
|
|
385
|
-
) ->
|
|
382
|
+
) -> list[CharacteristicProxy[bytes]]:
|
|
386
383
|
services = [service] if service else self.services
|
|
387
384
|
return [
|
|
388
385
|
c
|
|
@@ -393,8 +390,8 @@ class Client:
|
|
|
393
390
|
def get_attribute_grouping(self, attribute_handle: int) -> Optional[
|
|
394
391
|
Union[
|
|
395
392
|
ServiceProxy,
|
|
396
|
-
|
|
397
|
-
|
|
393
|
+
tuple[ServiceProxy, CharacteristicProxy],
|
|
394
|
+
tuple[ServiceProxy, CharacteristicProxy, DescriptorProxy],
|
|
398
395
|
]
|
|
399
396
|
]:
|
|
400
397
|
"""
|
|
@@ -427,7 +424,7 @@ class Client:
|
|
|
427
424
|
if not already_known:
|
|
428
425
|
self.services.append(service)
|
|
429
426
|
|
|
430
|
-
async def discover_services(self, uuids: Iterable[UUID] = ()) ->
|
|
427
|
+
async def discover_services(self, uuids: Iterable[UUID] = ()) -> list[ServiceProxy]:
|
|
431
428
|
'''
|
|
432
429
|
See Vol 3, Part G - 4.4.1 Discover All Primary Services
|
|
433
430
|
'''
|
|
@@ -499,7 +496,7 @@ class Client:
|
|
|
499
496
|
|
|
500
497
|
return services
|
|
501
498
|
|
|
502
|
-
async def discover_service(self, uuid: Union[str, UUID]) ->
|
|
499
|
+
async def discover_service(self, uuid: Union[str, UUID]) -> list[ServiceProxy]:
|
|
503
500
|
'''
|
|
504
501
|
See Vol 3, Part G - 4.4.2 Discover Primary Service by Service UUID
|
|
505
502
|
'''
|
|
@@ -570,7 +567,7 @@ class Client:
|
|
|
570
567
|
|
|
571
568
|
async def discover_included_services(
|
|
572
569
|
self, service: ServiceProxy
|
|
573
|
-
) ->
|
|
570
|
+
) -> list[ServiceProxy]:
|
|
574
571
|
'''
|
|
575
572
|
See Vol 3, Part G - 4.5.1 Find Included Services
|
|
576
573
|
'''
|
|
@@ -578,7 +575,7 @@ class Client:
|
|
|
578
575
|
starting_handle = service.handle
|
|
579
576
|
ending_handle = service.end_group_handle
|
|
580
577
|
|
|
581
|
-
included_services:
|
|
578
|
+
included_services: list[ServiceProxy] = []
|
|
582
579
|
while starting_handle <= ending_handle:
|
|
583
580
|
response = await self.send_request(
|
|
584
581
|
ATT_Read_By_Type_Request(
|
|
@@ -634,7 +631,7 @@ class Client:
|
|
|
634
631
|
|
|
635
632
|
async def discover_characteristics(
|
|
636
633
|
self, uuids, service: Optional[ServiceProxy]
|
|
637
|
-
) ->
|
|
634
|
+
) -> list[CharacteristicProxy[bytes]]:
|
|
638
635
|
'''
|
|
639
636
|
See Vol 3, Part G - 4.6.1 Discover All Characteristics of a Service and 4.6.2
|
|
640
637
|
Discover Characteristics by UUID
|
|
@@ -647,12 +644,12 @@ class Client:
|
|
|
647
644
|
services = [service] if service else self.services
|
|
648
645
|
|
|
649
646
|
# Perform characteristic discovery for each service
|
|
650
|
-
discovered_characteristics:
|
|
647
|
+
discovered_characteristics: list[CharacteristicProxy[bytes]] = []
|
|
651
648
|
for service in services:
|
|
652
649
|
starting_handle = service.handle
|
|
653
650
|
ending_handle = service.end_group_handle
|
|
654
651
|
|
|
655
|
-
characteristics:
|
|
652
|
+
characteristics: list[CharacteristicProxy[bytes]] = []
|
|
656
653
|
while starting_handle <= ending_handle:
|
|
657
654
|
response = await self.send_request(
|
|
658
655
|
ATT_Read_By_Type_Request(
|
|
@@ -723,7 +720,7 @@ class Client:
|
|
|
723
720
|
characteristic: Optional[CharacteristicProxy] = None,
|
|
724
721
|
start_handle: Optional[int] = None,
|
|
725
722
|
end_handle: Optional[int] = None,
|
|
726
|
-
) ->
|
|
723
|
+
) -> list[DescriptorProxy]:
|
|
727
724
|
'''
|
|
728
725
|
See Vol 3, Part G - 4.7.1 Discover All Characteristic Descriptors
|
|
729
726
|
'''
|
|
@@ -736,7 +733,7 @@ class Client:
|
|
|
736
733
|
else:
|
|
737
734
|
return []
|
|
738
735
|
|
|
739
|
-
descriptors:
|
|
736
|
+
descriptors: list[DescriptorProxy] = []
|
|
740
737
|
while starting_handle <= ending_handle:
|
|
741
738
|
response = await self.send_request(
|
|
742
739
|
ATT_Find_Information_Request(
|
|
@@ -785,7 +782,7 @@ class Client:
|
|
|
785
782
|
|
|
786
783
|
return descriptors
|
|
787
784
|
|
|
788
|
-
async def discover_attributes(self) ->
|
|
785
|
+
async def discover_attributes(self) -> list[AttributeProxy[bytes]]:
|
|
789
786
|
'''
|
|
790
787
|
Discover all attributes, regardless of type
|
|
791
788
|
'''
|
|
@@ -1000,7 +997,7 @@ class Client:
|
|
|
1000
997
|
|
|
1001
998
|
async def read_characteristics_by_uuid(
|
|
1002
999
|
self, uuid: UUID, service: Optional[ServiceProxy]
|
|
1003
|
-
) ->
|
|
1000
|
+
) -> list[bytes]:
|
|
1004
1001
|
'''
|
|
1005
1002
|
See Vol 3, Part G - 4.8.2 Read Using Characteristic UUID
|
|
1006
1003
|
'''
|
|
@@ -1142,7 +1139,7 @@ class Client:
|
|
|
1142
1139
|
if callable(subscriber):
|
|
1143
1140
|
subscriber(notification.attribute_value)
|
|
1144
1141
|
else:
|
|
1145
|
-
subscriber.emit(
|
|
1142
|
+
subscriber.emit(subscriber.EVENT_UPDATE, notification.attribute_value)
|
|
1146
1143
|
|
|
1147
1144
|
def on_att_handle_value_indication(self, indication):
|
|
1148
1145
|
# Call all subscribers
|
|
@@ -1157,7 +1154,7 @@ class Client:
|
|
|
1157
1154
|
if callable(subscriber):
|
|
1158
1155
|
subscriber(indication.attribute_value)
|
|
1159
1156
|
else:
|
|
1160
|
-
subscriber.emit(
|
|
1157
|
+
subscriber.emit(subscriber.EVENT_UPDATE, indication.attribute_value)
|
|
1161
1158
|
|
|
1162
1159
|
# Confirm that we received the indication
|
|
1163
1160
|
self.send_confirmation(ATT_Handle_Value_Confirmation())
|