bumble 0.0.212__py3-none-any.whl → 0.0.214__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 +14 -11
- bumble/apps/bench.py +482 -37
- bumble/apps/console.py +3 -3
- bumble/apps/controller_info.py +44 -12
- bumble/apps/controller_loopback.py +7 -7
- bumble/apps/controllers.py +4 -5
- bumble/apps/device_info.py +4 -5
- bumble/apps/gatt_dump.py +5 -5
- bumble/apps/gg_bridge.py +5 -5
- bumble/apps/hci_bridge.py +5 -4
- bumble/apps/l2cap_bridge.py +5 -5
- bumble/apps/lea_unicast/app.py +8 -3
- bumble/apps/pair.py +19 -11
- bumble/apps/pandora_server.py +2 -2
- bumble/apps/player/player.py +2 -3
- bumble/apps/rfcomm_bridge.py +3 -4
- bumble/apps/scan.py +4 -5
- bumble/apps/show.py +6 -4
- bumble/apps/speaker/speaker.html +1 -0
- bumble/apps/speaker/speaker.js +113 -62
- bumble/apps/speaker/speaker.py +123 -19
- bumble/apps/unbond.py +2 -3
- bumble/apps/usb_probe.py +2 -3
- bumble/at.py +4 -4
- bumble/att.py +2 -6
- bumble/avc.py +7 -7
- bumble/avctp.py +3 -3
- bumble/avdtp.py +16 -20
- bumble/avrcp.py +42 -54
- bumble/colors.py +2 -2
- bumble/controller.py +174 -45
- bumble/device.py +398 -182
- bumble/drivers/__init__.py +2 -2
- bumble/drivers/common.py +0 -2
- bumble/drivers/intel.py +37 -40
- bumble/drivers/rtk.py +28 -35
- bumble/gatt.py +4 -4
- bumble/gatt_adapters.py +4 -5
- bumble/gatt_client.py +26 -31
- bumble/gatt_server.py +7 -11
- bumble/hci.py +2648 -2909
- bumble/helpers.py +4 -5
- bumble/hfp.py +32 -37
- bumble/host.py +104 -35
- bumble/keys.py +5 -5
- bumble/l2cap.py +312 -409
- bumble/link.py +16 -280
- bumble/logging.py +65 -0
- bumble/pairing.py +23 -20
- bumble/pandora/__init__.py +2 -2
- bumble/pandora/config.py +2 -2
- bumble/pandora/device.py +6 -6
- bumble/pandora/host.py +27 -28
- bumble/pandora/l2cap.py +2 -2
- bumble/pandora/security.py +6 -6
- bumble/pandora/utils.py +3 -3
- bumble/profiles/ams.py +404 -0
- bumble/profiles/ascs.py +142 -131
- bumble/profiles/asha.py +2 -2
- bumble/profiles/bap.py +3 -4
- bumble/profiles/csip.py +2 -2
- bumble/profiles/device_information_service.py +2 -2
- bumble/profiles/gap.py +2 -2
- bumble/profiles/hap.py +34 -33
- bumble/profiles/le_audio.py +4 -4
- bumble/profiles/mcp.py +4 -4
- bumble/profiles/vcs.py +3 -5
- bumble/rfcomm.py +10 -10
- bumble/rtp.py +1 -2
- bumble/sdp.py +2 -2
- bumble/smp.py +62 -63
- bumble/tools/intel_util.py +3 -2
- bumble/tools/rtk_util.py +6 -5
- 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.212.dist-info → bumble-0.0.214.dist-info}/METADATA +4 -3
- {bumble-0.0.212.dist-info → bumble-0.0.214.dist-info}/RECORD +89 -90
- {bumble-0.0.212.dist-info → bumble-0.0.214.dist-info}/WHEEL +1 -1
- {bumble-0.0.212.dist-info → bumble-0.0.214.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.212.dist-info → bumble-0.0.214.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.212.dist-info → bumble-0.0.214.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
|
|
@@ -90,54 +90,51 @@ HCI_INTEL_WRITE_BOOT_PARAMS_COMMAND = hci.hci_vendor_command_op_code(0x000E)
|
|
|
90
90
|
hci.HCI_Command.register_commands(globals())
|
|
91
91
|
|
|
92
92
|
|
|
93
|
-
@hci.HCI_Command.command
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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 = [
|
|
98
99
|
("status", hci.STATUS_SPEC),
|
|
99
100
|
("tlv", "*"),
|
|
100
|
-
]
|
|
101
|
-
)
|
|
102
|
-
class HCI_Intel_Read_Version_Command(hci.HCI_Command):
|
|
103
|
-
pass
|
|
101
|
+
]
|
|
104
102
|
|
|
105
103
|
|
|
106
|
-
@hci.HCI_Command.command
|
|
107
|
-
|
|
108
|
-
return_parameters_fields=[
|
|
109
|
-
("status", 1),
|
|
110
|
-
],
|
|
111
|
-
)
|
|
104
|
+
@hci.HCI_Command.command
|
|
105
|
+
@dataclasses.dataclass
|
|
112
106
|
class Hci_Intel_Secure_Send_Command(hci.HCI_Command):
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
],
|
|
124
|
-
return_parameters_fields=[
|
|
125
|
-
("data", "*"),
|
|
126
|
-
],
|
|
127
|
-
)
|
|
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
|
|
128
117
|
class HCI_Intel_Reset_Command(hci.HCI_Command):
|
|
129
|
-
|
|
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
|
+
]
|
|
130
127
|
|
|
131
128
|
|
|
132
|
-
@hci.HCI_Command.command
|
|
133
|
-
|
|
134
|
-
|
|
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 = [
|
|
135
135
|
("status", hci.STATUS_SPEC),
|
|
136
136
|
("params", "*"),
|
|
137
|
-
]
|
|
138
|
-
)
|
|
139
|
-
class Hci_Intel_Write_Device_Config_Command(hci.HCI_Command):
|
|
140
|
-
pass
|
|
137
|
+
]
|
|
141
138
|
|
|
142
139
|
|
|
143
140
|
# -----------------------------------------------------------------------------
|
|
@@ -348,7 +345,7 @@ class Driver(common.Driver):
|
|
|
348
345
|
def __init__(self, host: Host) -> None:
|
|
349
346
|
self.host = host
|
|
350
347
|
self.max_in_flight_firmware_load_commands = 1
|
|
351
|
-
self.pending_firmware_load_commands:
|
|
348
|
+
self.pending_firmware_load_commands: collections.deque[hci.HCI_Command] = (
|
|
352
349
|
collections.deque()
|
|
353
350
|
)
|
|
354
351
|
self.can_send_firmware_load_command = asyncio.Event()
|
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,
|
|
@@ -474,7 +474,7 @@ class Characteristic(Attribute[_T]):
|
|
|
474
474
|
# The check for `p.name is not None` here is needed because for InFlag
|
|
475
475
|
# enums, the .name property can be None, when the enum value is 0,
|
|
476
476
|
# so the type hint for .name is Optional[str].
|
|
477
|
-
enum_list:
|
|
477
|
+
enum_list: list[str] = [p.name for p in cls if p.name is not None]
|
|
478
478
|
enum_list_str = ",".join(enum_list)
|
|
479
479
|
raise TypeError(
|
|
480
480
|
f"Characteristic.Properties::from_string() error:\nExpected a string containing any of the keys, separated by , or |: {enum_list_str}\nGot: {properties_str}"
|
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,8 @@ 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]]
|
|
204
199
|
|
|
205
200
|
EVENT_UPDATE = "update"
|
|
206
201
|
|
|
@@ -277,7 +272,7 @@ class ProfileServiceProxy:
|
|
|
277
272
|
Base class for profile-specific service proxies
|
|
278
273
|
'''
|
|
279
274
|
|
|
280
|
-
SERVICE_CLASS:
|
|
275
|
+
SERVICE_CLASS: type[TemplateService]
|
|
281
276
|
|
|
282
277
|
@classmethod
|
|
283
278
|
def from_client(cls, client: Client) -> Optional[ProfileServiceProxy]:
|
|
@@ -288,13 +283,13 @@ class ProfileServiceProxy:
|
|
|
288
283
|
# GATT Client
|
|
289
284
|
# -----------------------------------------------------------------------------
|
|
290
285
|
class Client:
|
|
291
|
-
services:
|
|
292
|
-
cached_values:
|
|
293
|
-
notification_subscribers:
|
|
294
|
-
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]]]
|
|
295
290
|
]
|
|
296
|
-
indication_subscribers:
|
|
297
|
-
int,
|
|
291
|
+
indication_subscribers: dict[
|
|
292
|
+
int, set[Union[CharacteristicProxy, Callable[[bytes], Any]]]
|
|
298
293
|
]
|
|
299
294
|
pending_response: Optional[asyncio.futures.Future[ATT_PDU]]
|
|
300
295
|
pending_request: Optional[ATT_PDU]
|
|
@@ -379,12 +374,12 @@ class Client:
|
|
|
379
374
|
|
|
380
375
|
return self.connection.att_mtu
|
|
381
376
|
|
|
382
|
-
def get_services_by_uuid(self, uuid: UUID) ->
|
|
377
|
+
def get_services_by_uuid(self, uuid: UUID) -> list[ServiceProxy]:
|
|
383
378
|
return [service for service in self.services if service.uuid == uuid]
|
|
384
379
|
|
|
385
380
|
def get_characteristics_by_uuid(
|
|
386
381
|
self, uuid: UUID, service: Optional[ServiceProxy] = None
|
|
387
|
-
) ->
|
|
382
|
+
) -> list[CharacteristicProxy[bytes]]:
|
|
388
383
|
services = [service] if service else self.services
|
|
389
384
|
return [
|
|
390
385
|
c
|
|
@@ -395,8 +390,8 @@ class Client:
|
|
|
395
390
|
def get_attribute_grouping(self, attribute_handle: int) -> Optional[
|
|
396
391
|
Union[
|
|
397
392
|
ServiceProxy,
|
|
398
|
-
|
|
399
|
-
|
|
393
|
+
tuple[ServiceProxy, CharacteristicProxy],
|
|
394
|
+
tuple[ServiceProxy, CharacteristicProxy, DescriptorProxy],
|
|
400
395
|
]
|
|
401
396
|
]:
|
|
402
397
|
"""
|
|
@@ -429,7 +424,7 @@ class Client:
|
|
|
429
424
|
if not already_known:
|
|
430
425
|
self.services.append(service)
|
|
431
426
|
|
|
432
|
-
async def discover_services(self, uuids: Iterable[UUID] = ()) ->
|
|
427
|
+
async def discover_services(self, uuids: Iterable[UUID] = ()) -> list[ServiceProxy]:
|
|
433
428
|
'''
|
|
434
429
|
See Vol 3, Part G - 4.4.1 Discover All Primary Services
|
|
435
430
|
'''
|
|
@@ -501,7 +496,7 @@ class Client:
|
|
|
501
496
|
|
|
502
497
|
return services
|
|
503
498
|
|
|
504
|
-
async def discover_service(self, uuid: Union[str, UUID]) ->
|
|
499
|
+
async def discover_service(self, uuid: Union[str, UUID]) -> list[ServiceProxy]:
|
|
505
500
|
'''
|
|
506
501
|
See Vol 3, Part G - 4.4.2 Discover Primary Service by Service UUID
|
|
507
502
|
'''
|
|
@@ -572,7 +567,7 @@ class Client:
|
|
|
572
567
|
|
|
573
568
|
async def discover_included_services(
|
|
574
569
|
self, service: ServiceProxy
|
|
575
|
-
) ->
|
|
570
|
+
) -> list[ServiceProxy]:
|
|
576
571
|
'''
|
|
577
572
|
See Vol 3, Part G - 4.5.1 Find Included Services
|
|
578
573
|
'''
|
|
@@ -580,7 +575,7 @@ class Client:
|
|
|
580
575
|
starting_handle = service.handle
|
|
581
576
|
ending_handle = service.end_group_handle
|
|
582
577
|
|
|
583
|
-
included_services:
|
|
578
|
+
included_services: list[ServiceProxy] = []
|
|
584
579
|
while starting_handle <= ending_handle:
|
|
585
580
|
response = await self.send_request(
|
|
586
581
|
ATT_Read_By_Type_Request(
|
|
@@ -636,7 +631,7 @@ class Client:
|
|
|
636
631
|
|
|
637
632
|
async def discover_characteristics(
|
|
638
633
|
self, uuids, service: Optional[ServiceProxy]
|
|
639
|
-
) ->
|
|
634
|
+
) -> list[CharacteristicProxy[bytes]]:
|
|
640
635
|
'''
|
|
641
636
|
See Vol 3, Part G - 4.6.1 Discover All Characteristics of a Service and 4.6.2
|
|
642
637
|
Discover Characteristics by UUID
|
|
@@ -649,12 +644,12 @@ class Client:
|
|
|
649
644
|
services = [service] if service else self.services
|
|
650
645
|
|
|
651
646
|
# Perform characteristic discovery for each service
|
|
652
|
-
discovered_characteristics:
|
|
647
|
+
discovered_characteristics: list[CharacteristicProxy[bytes]] = []
|
|
653
648
|
for service in services:
|
|
654
649
|
starting_handle = service.handle
|
|
655
650
|
ending_handle = service.end_group_handle
|
|
656
651
|
|
|
657
|
-
characteristics:
|
|
652
|
+
characteristics: list[CharacteristicProxy[bytes]] = []
|
|
658
653
|
while starting_handle <= ending_handle:
|
|
659
654
|
response = await self.send_request(
|
|
660
655
|
ATT_Read_By_Type_Request(
|
|
@@ -725,7 +720,7 @@ class Client:
|
|
|
725
720
|
characteristic: Optional[CharacteristicProxy] = None,
|
|
726
721
|
start_handle: Optional[int] = None,
|
|
727
722
|
end_handle: Optional[int] = None,
|
|
728
|
-
) ->
|
|
723
|
+
) -> list[DescriptorProxy]:
|
|
729
724
|
'''
|
|
730
725
|
See Vol 3, Part G - 4.7.1 Discover All Characteristic Descriptors
|
|
731
726
|
'''
|
|
@@ -738,7 +733,7 @@ class Client:
|
|
|
738
733
|
else:
|
|
739
734
|
return []
|
|
740
735
|
|
|
741
|
-
descriptors:
|
|
736
|
+
descriptors: list[DescriptorProxy] = []
|
|
742
737
|
while starting_handle <= ending_handle:
|
|
743
738
|
response = await self.send_request(
|
|
744
739
|
ATT_Find_Information_Request(
|
|
@@ -787,7 +782,7 @@ class Client:
|
|
|
787
782
|
|
|
788
783
|
return descriptors
|
|
789
784
|
|
|
790
|
-
async def discover_attributes(self) ->
|
|
785
|
+
async def discover_attributes(self) -> list[AttributeProxy[bytes]]:
|
|
791
786
|
'''
|
|
792
787
|
Discover all attributes, regardless of type
|
|
793
788
|
'''
|
|
@@ -1002,7 +997,7 @@ class Client:
|
|
|
1002
997
|
|
|
1003
998
|
async def read_characteristics_by_uuid(
|
|
1004
999
|
self, uuid: UUID, service: Optional[ServiceProxy]
|
|
1005
|
-
) ->
|
|
1000
|
+
) -> list[bytes]:
|
|
1006
1001
|
'''
|
|
1007
1002
|
See Vol 3, Part G - 4.8.2 Read Using Characteristic UUID
|
|
1008
1003
|
'''
|
bumble/gatt_server.py
CHANGED
|
@@ -29,13 +29,9 @@ import logging
|
|
|
29
29
|
from collections import defaultdict
|
|
30
30
|
import struct
|
|
31
31
|
from typing import (
|
|
32
|
-
Dict,
|
|
33
32
|
Iterable,
|
|
34
|
-
List,
|
|
35
33
|
Optional,
|
|
36
|
-
Tuple,
|
|
37
34
|
TypeVar,
|
|
38
|
-
Type,
|
|
39
35
|
TYPE_CHECKING,
|
|
40
36
|
)
|
|
41
37
|
|
|
@@ -103,10 +99,10 @@ GATT_SERVER_DEFAULT_MAX_MTU = 517
|
|
|
103
99
|
# GATT Server
|
|
104
100
|
# -----------------------------------------------------------------------------
|
|
105
101
|
class Server(utils.EventEmitter):
|
|
106
|
-
attributes:
|
|
107
|
-
services:
|
|
108
|
-
attributes_by_handle:
|
|
109
|
-
subscribers:
|
|
102
|
+
attributes: list[Attribute]
|
|
103
|
+
services: list[Service]
|
|
104
|
+
attributes_by_handle: dict[int, Attribute]
|
|
105
|
+
subscribers: dict[int, dict[int, bytes]]
|
|
110
106
|
indication_semaphores: defaultdict[int, asyncio.Semaphore]
|
|
111
107
|
pending_confirmations: defaultdict[int, Optional[asyncio.futures.Future]]
|
|
112
108
|
|
|
@@ -136,7 +132,7 @@ class Server(utils.EventEmitter):
|
|
|
136
132
|
def next_handle(self) -> int:
|
|
137
133
|
return 1 + len(self.attributes)
|
|
138
134
|
|
|
139
|
-
def get_advertising_service_data(self) ->
|
|
135
|
+
def get_advertising_service_data(self) -> dict[Attribute, bytes]:
|
|
140
136
|
return {
|
|
141
137
|
attribute: data
|
|
142
138
|
for attribute in self.attributes
|
|
@@ -160,7 +156,7 @@ class Server(utils.EventEmitter):
|
|
|
160
156
|
AttributeGroupType = TypeVar('AttributeGroupType', Service, Characteristic)
|
|
161
157
|
|
|
162
158
|
def get_attribute_group(
|
|
163
|
-
self, handle: int, group_type:
|
|
159
|
+
self, handle: int, group_type: type[AttributeGroupType]
|
|
164
160
|
) -> Optional[AttributeGroupType]:
|
|
165
161
|
return next(
|
|
166
162
|
(
|
|
@@ -186,7 +182,7 @@ class Server(utils.EventEmitter):
|
|
|
186
182
|
|
|
187
183
|
def get_characteristic_attributes(
|
|
188
184
|
self, service_uuid: UUID, characteristic_uuid: UUID
|
|
189
|
-
) -> Optional[
|
|
185
|
+
) -> Optional[tuple[CharacteristicDeclaration, Characteristic]]:
|
|
190
186
|
service_handle = self.get_service_attribute(service_uuid)
|
|
191
187
|
if not service_handle:
|
|
192
188
|
return None
|