bumble 0.0.222__py3-none-any.whl → 0.0.224__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/apps/controller_info.py +90 -114
- bumble/apps/controller_loopback.py +11 -9
- bumble/apps/gg_bridge.py +1 -1
- bumble/apps/hci_bridge.py +3 -1
- bumble/apps/l2cap_bridge.py +1 -1
- bumble/apps/rfcomm_bridge.py +1 -1
- bumble/apps/scan.py +10 -4
- bumble/apps/speaker/speaker.py +1 -1
- bumble/apps/usb_probe.py +15 -2
- bumble/att.py +97 -32
- bumble/avctp.py +1 -1
- bumble/avdtp.py +3 -3
- bumble/avrcp.py +366 -190
- bumble/bridge.py +10 -2
- bumble/controller.py +14 -1
- bumble/core.py +1 -1
- bumble/device.py +999 -577
- bumble/drivers/intel.py +45 -39
- bumble/drivers/rtk.py +102 -43
- bumble/gatt.py +2 -2
- bumble/gatt_client.py +5 -4
- bumble/gatt_server.py +100 -1
- bumble/hci.py +1367 -844
- bumble/hid.py +2 -2
- bumble/host.py +339 -157
- bumble/l2cap.py +13 -6
- bumble/pandora/l2cap.py +1 -1
- bumble/profiles/battery_service.py +25 -34
- bumble/profiles/heart_rate_service.py +130 -121
- bumble/rfcomm.py +1 -1
- bumble/sdp.py +2 -2
- bumble/smp.py +8 -3
- bumble/snoop.py +111 -1
- bumble/transport/android_netsim.py +1 -1
- bumble/vendor/android/hci.py +108 -86
- bumble/vendor/zephyr/hci.py +24 -18
- {bumble-0.0.222.dist-info → bumble-0.0.224.dist-info}/METADATA +4 -3
- {bumble-0.0.222.dist-info → bumble-0.0.224.dist-info}/RECORD +43 -43
- {bumble-0.0.222.dist-info → bumble-0.0.224.dist-info}/WHEEL +1 -1
- {bumble-0.0.222.dist-info → bumble-0.0.224.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.222.dist-info → bumble-0.0.224.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.222.dist-info → bumble-0.0.224.dist-info}/top_level.txt +0 -0
bumble/drivers/intel.py
CHANGED
|
@@ -89,52 +89,55 @@ HCI_INTEL_WRITE_BOOT_PARAMS_COMMAND = hci.hci_vendor_command_op_code(0x000E)
|
|
|
89
89
|
hci.HCI_Command.register_commands(globals())
|
|
90
90
|
|
|
91
91
|
|
|
92
|
-
@hci.HCI_Command.command
|
|
93
92
|
@dataclasses.dataclass
|
|
94
|
-
class
|
|
95
|
-
|
|
93
|
+
class HCI_Intel_Read_Version_ReturnParameters(hci.HCI_StatusReturnParameters):
|
|
94
|
+
tlv: bytes = hci.field(metadata=hci.metadata('*'))
|
|
95
|
+
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
]
|
|
97
|
+
@hci.HCI_SyncCommand.sync_command(HCI_Intel_Read_Version_ReturnParameters)
|
|
98
|
+
@dataclasses.dataclass
|
|
99
|
+
class HCI_Intel_Read_Version_Command(
|
|
100
|
+
hci.HCI_SyncCommand[HCI_Intel_Read_Version_ReturnParameters]
|
|
101
|
+
):
|
|
102
|
+
param0: int = dataclasses.field(metadata=hci.metadata(1))
|
|
101
103
|
|
|
102
104
|
|
|
103
|
-
@hci.
|
|
105
|
+
@hci.HCI_SyncCommand.sync_command(hci.HCI_StatusReturnParameters)
|
|
104
106
|
@dataclasses.dataclass
|
|
105
|
-
class Hci_Intel_Secure_Send_Command(
|
|
107
|
+
class Hci_Intel_Secure_Send_Command(
|
|
108
|
+
hci.HCI_SyncCommand[hci.HCI_StatusReturnParameters]
|
|
109
|
+
):
|
|
106
110
|
data_type: int = dataclasses.field(metadata=hci.metadata(1))
|
|
107
111
|
data: bytes = dataclasses.field(metadata=hci.metadata("*"))
|
|
108
112
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
113
|
+
|
|
114
|
+
@dataclasses.dataclass
|
|
115
|
+
class HCI_Intel_Reset_ReturnParameters(hci.HCI_ReturnParameters):
|
|
116
|
+
data: bytes = hci.field(metadata=hci.metadata('*'))
|
|
112
117
|
|
|
113
118
|
|
|
114
|
-
@hci.
|
|
119
|
+
@hci.HCI_SyncCommand.sync_command(HCI_Intel_Reset_ReturnParameters)
|
|
115
120
|
@dataclasses.dataclass
|
|
116
|
-
class HCI_Intel_Reset_Command(hci.
|
|
121
|
+
class HCI_Intel_Reset_Command(hci.HCI_SyncCommand[HCI_Intel_Reset_ReturnParameters]):
|
|
117
122
|
reset_type: int = dataclasses.field(metadata=hci.metadata(1))
|
|
118
123
|
patch_enable: int = dataclasses.field(metadata=hci.metadata(1))
|
|
119
124
|
ddc_reload: int = dataclasses.field(metadata=hci.metadata(1))
|
|
120
125
|
boot_option: int = dataclasses.field(metadata=hci.metadata(1))
|
|
121
126
|
boot_address: int = dataclasses.field(metadata=hci.metadata(4))
|
|
122
127
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
128
|
+
|
|
129
|
+
@dataclasses.dataclass
|
|
130
|
+
class HCI_Intel_Write_Device_Config_ReturnParameters(hci.HCI_StatusReturnParameters):
|
|
131
|
+
params: bytes = hci.field(metadata=hci.metadata('*'))
|
|
126
132
|
|
|
127
133
|
|
|
128
|
-
@hci.
|
|
134
|
+
@hci.HCI_SyncCommand.sync_command(HCI_Intel_Write_Device_Config_ReturnParameters)
|
|
129
135
|
@dataclasses.dataclass
|
|
130
|
-
class
|
|
136
|
+
class HCI_Intel_Write_Device_Config_Command(
|
|
137
|
+
hci.HCI_SyncCommand[HCI_Intel_Write_Device_Config_ReturnParameters]
|
|
138
|
+
):
|
|
131
139
|
data: bytes = dataclasses.field(metadata=hci.metadata("*"))
|
|
132
140
|
|
|
133
|
-
return_parameters_fields = [
|
|
134
|
-
("status", hci.STATUS_SPEC),
|
|
135
|
-
("params", "*"),
|
|
136
|
-
]
|
|
137
|
-
|
|
138
141
|
|
|
139
142
|
# -----------------------------------------------------------------------------
|
|
140
143
|
# Functions
|
|
@@ -402,7 +405,7 @@ class Driver(common.Driver):
|
|
|
402
405
|
self.host.on_hci_event_packet(event)
|
|
403
406
|
return
|
|
404
407
|
|
|
405
|
-
if not event.return_parameters == hci.HCI_SUCCESS:
|
|
408
|
+
if not event.return_parameters.status == hci.HCI_SUCCESS:
|
|
406
409
|
raise DriverError("HCI_Command_Complete_Event error")
|
|
407
410
|
|
|
408
411
|
if self.max_in_flight_firmware_load_commands != event.num_hci_command_packets:
|
|
@@ -641,8 +644,8 @@ class Driver(common.Driver):
|
|
|
641
644
|
while ddc_data:
|
|
642
645
|
ddc_len = 1 + ddc_data[0]
|
|
643
646
|
ddc_payload = ddc_data[:ddc_len]
|
|
644
|
-
await self.host.
|
|
645
|
-
|
|
647
|
+
await self.host.send_sync_command(
|
|
648
|
+
HCI_Intel_Write_Device_Config_Command(data=ddc_payload)
|
|
646
649
|
)
|
|
647
650
|
ddc_data = ddc_data[ddc_len:]
|
|
648
651
|
|
|
@@ -660,31 +663,34 @@ class Driver(common.Driver):
|
|
|
660
663
|
|
|
661
664
|
async def read_device_info(self) -> dict[ValueType, Any]:
|
|
662
665
|
self.host.ready = True
|
|
663
|
-
|
|
664
|
-
if not (
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
666
|
+
response1 = await self.host.send_sync_command_raw(hci.HCI_Reset_Command())
|
|
667
|
+
if not isinstance(
|
|
668
|
+
response1.return_parameters, hci.HCI_StatusReturnParameters
|
|
669
|
+
) or response1.return_parameters.status not in (
|
|
670
|
+
hci.HCI_UNKNOWN_HCI_COMMAND_ERROR,
|
|
671
|
+
hci.HCI_SUCCESS,
|
|
668
672
|
):
|
|
669
673
|
# When the controller is in operational mode, the response is a
|
|
670
674
|
# successful response.
|
|
671
675
|
# When the controller is in bootloader mode,
|
|
672
676
|
# HCI_UNKNOWN_HCI_COMMAND_ERROR is the expected response. Anything
|
|
673
677
|
# else is a failure.
|
|
674
|
-
logger.warning(f"unexpected response: {
|
|
678
|
+
logger.warning(f"unexpected response: {response1}")
|
|
675
679
|
raise DriverError("unexpected HCI response")
|
|
676
680
|
|
|
677
681
|
# Read the firmware version.
|
|
678
|
-
|
|
682
|
+
response2 = await self.host.send_sync_command_raw(
|
|
679
683
|
HCI_Intel_Read_Version_Command(param0=0xFF)
|
|
680
684
|
)
|
|
681
|
-
if
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
+
if (
|
|
686
|
+
not isinstance(
|
|
687
|
+
response2.return_parameters, HCI_Intel_Read_Version_ReturnParameters
|
|
688
|
+
)
|
|
689
|
+
or response2.return_parameters.status != 0
|
|
690
|
+
):
|
|
685
691
|
raise DriverError("HCI_Intel_Read_Version_Command error")
|
|
686
692
|
|
|
687
|
-
tlvs = _parse_tlv(
|
|
693
|
+
tlvs = _parse_tlv(response2.return_parameters.tlv) # type: ignore
|
|
688
694
|
|
|
689
695
|
# Convert the list to a dict. That's Ok here because we only expect each type
|
|
690
696
|
# to appear just once.
|
bumble/drivers/rtk.py
CHANGED
|
@@ -16,6 +16,7 @@ Support for Realtek USB dongles.
|
|
|
16
16
|
Based on various online bits of information, including the Linux kernel.
|
|
17
17
|
(see `drivers/bluetooth/btrtl.c`)
|
|
18
18
|
"""
|
|
19
|
+
from __future__ import annotations
|
|
19
20
|
|
|
20
21
|
import asyncio
|
|
21
22
|
import enum
|
|
@@ -31,10 +32,14 @@ import weakref
|
|
|
31
32
|
# Imports
|
|
32
33
|
# -----------------------------------------------------------------------------
|
|
33
34
|
from dataclasses import dataclass, field
|
|
35
|
+
from typing import TYPE_CHECKING
|
|
34
36
|
|
|
35
37
|
from bumble import core, hci
|
|
36
38
|
from bumble.drivers import common
|
|
37
39
|
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
from bumble.host import Host
|
|
42
|
+
|
|
38
43
|
# -----------------------------------------------------------------------------
|
|
39
44
|
# Logging
|
|
40
45
|
# -----------------------------------------------------------------------------
|
|
@@ -77,6 +82,7 @@ class RtlProjectId(enum.IntEnum):
|
|
|
77
82
|
PROJECT_ID_8852A = 18
|
|
78
83
|
PROJECT_ID_8852B = 20
|
|
79
84
|
PROJECT_ID_8852C = 25
|
|
85
|
+
PROJECT_ID_8761C = 51
|
|
80
86
|
|
|
81
87
|
|
|
82
88
|
RTK_PROJECT_ID_TO_ROM = {
|
|
@@ -92,6 +98,7 @@ RTK_PROJECT_ID_TO_ROM = {
|
|
|
92
98
|
18: RTK_ROM_LMP_8852A,
|
|
93
99
|
20: RTK_ROM_LMP_8852A,
|
|
94
100
|
25: RTK_ROM_LMP_8852A,
|
|
101
|
+
51: RTK_ROM_LMP_8761A,
|
|
95
102
|
}
|
|
96
103
|
|
|
97
104
|
# List of USB (VendorID, ProductID) for Realtek-based devices.
|
|
@@ -122,7 +129,12 @@ RTK_USB_PRODUCTS = {
|
|
|
122
129
|
(0x2357, 0x0604),
|
|
123
130
|
(0x2550, 0x8761),
|
|
124
131
|
(0x2B89, 0x8761),
|
|
132
|
+
(0x2C0A, 0x8761),
|
|
125
133
|
(0x7392, 0xC611),
|
|
134
|
+
# Realtek 8761CUV
|
|
135
|
+
(0x0B05, 0x1BF6),
|
|
136
|
+
(0x0BDA, 0xC761),
|
|
137
|
+
(0x7392, 0xF611),
|
|
126
138
|
# Realtek 8821AE
|
|
127
139
|
(0x0B05, 0x17DC),
|
|
128
140
|
(0x13D3, 0x3414),
|
|
@@ -182,23 +194,36 @@ HCI_RTK_DROP_FIRMWARE_COMMAND = hci.hci_vendor_command_op_code(0x66)
|
|
|
182
194
|
hci.HCI_Command.register_commands(globals())
|
|
183
195
|
|
|
184
196
|
|
|
185
|
-
@hci.HCI_Command.command
|
|
186
197
|
@dataclass
|
|
187
|
-
class
|
|
188
|
-
|
|
198
|
+
class HCI_RTK_Read_ROM_Version_ReturnParameters(hci.HCI_StatusReturnParameters):
|
|
199
|
+
version: int = field(metadata=hci.metadata(1))
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@hci.HCI_SyncCommand.sync_command(HCI_RTK_Read_ROM_Version_ReturnParameters)
|
|
203
|
+
@dataclass
|
|
204
|
+
class HCI_RTK_Read_ROM_Version_Command(
|
|
205
|
+
hci.HCI_SyncCommand[HCI_RTK_Read_ROM_Version_ReturnParameters]
|
|
206
|
+
):
|
|
207
|
+
pass
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@dataclass
|
|
211
|
+
class HCI_RTK_Download_ReturnParameters(hci.HCI_StatusReturnParameters):
|
|
212
|
+
index: int = field(metadata=hci.metadata(1))
|
|
189
213
|
|
|
190
214
|
|
|
191
|
-
@hci.
|
|
215
|
+
@hci.HCI_SyncCommand.sync_command(HCI_RTK_Download_ReturnParameters)
|
|
192
216
|
@dataclass
|
|
193
|
-
class HCI_RTK_Download_Command(hci.
|
|
217
|
+
class HCI_RTK_Download_Command(hci.HCI_SyncCommand[HCI_RTK_Download_ReturnParameters]):
|
|
194
218
|
index: int = field(metadata=hci.metadata(1))
|
|
195
219
|
payload: bytes = field(metadata=hci.metadata(RTK_FRAGMENT_LENGTH))
|
|
196
|
-
return_parameters_fields = [("status", hci.STATUS_SPEC), ("index", 1)]
|
|
197
220
|
|
|
198
221
|
|
|
199
|
-
@hci.
|
|
222
|
+
@hci.HCI_SyncCommand.sync_command(hci.HCI_GenericReturnParameters)
|
|
200
223
|
@dataclass
|
|
201
|
-
class HCI_RTK_Drop_Firmware_Command(
|
|
224
|
+
class HCI_RTK_Drop_Firmware_Command(
|
|
225
|
+
hci.HCI_SyncCommand[hci.HCI_GenericReturnParameters]
|
|
226
|
+
):
|
|
202
227
|
pass
|
|
203
228
|
|
|
204
229
|
|
|
@@ -363,6 +388,15 @@ class Driver(common.Driver):
|
|
|
363
388
|
fw_name="rtl8761bu_fw.bin",
|
|
364
389
|
config_name="rtl8761bu_config.bin",
|
|
365
390
|
),
|
|
391
|
+
# 8761CU
|
|
392
|
+
DriverInfo(
|
|
393
|
+
rom=RTK_ROM_LMP_8761A,
|
|
394
|
+
hci=(0x0E, 0x00),
|
|
395
|
+
config_needed=False,
|
|
396
|
+
has_rom_version=True,
|
|
397
|
+
fw_name="rtl8761cu_fw.bin",
|
|
398
|
+
config_name="rtl8761cu_config.bin",
|
|
399
|
+
),
|
|
366
400
|
# 8822C
|
|
367
401
|
DriverInfo(
|
|
368
402
|
rom=RTK_ROM_LMP_8822B,
|
|
@@ -420,9 +454,17 @@ class Driver(common.Driver):
|
|
|
420
454
|
@staticmethod
|
|
421
455
|
def find_driver_info(hci_version, hci_subversion, lmp_subversion):
|
|
422
456
|
for driver_info in Driver.DRIVER_INFOS:
|
|
423
|
-
if driver_info.rom == lmp_subversion and
|
|
424
|
-
|
|
425
|
-
|
|
457
|
+
if driver_info.rom == lmp_subversion and (
|
|
458
|
+
driver_info.hci
|
|
459
|
+
== (
|
|
460
|
+
hci_subversion,
|
|
461
|
+
hci_version,
|
|
462
|
+
)
|
|
463
|
+
or driver_info.hci
|
|
464
|
+
== (
|
|
465
|
+
hci_subversion,
|
|
466
|
+
0x0,
|
|
467
|
+
)
|
|
426
468
|
):
|
|
427
469
|
return driver_info
|
|
428
470
|
|
|
@@ -467,7 +509,7 @@ class Driver(common.Driver):
|
|
|
467
509
|
return None
|
|
468
510
|
|
|
469
511
|
@staticmethod
|
|
470
|
-
def check(host):
|
|
512
|
+
def check(host: Host) -> bool:
|
|
471
513
|
if not host.hci_metadata:
|
|
472
514
|
logger.debug("USB metadata not found")
|
|
473
515
|
return False
|
|
@@ -491,37 +533,44 @@ class Driver(common.Driver):
|
|
|
491
533
|
return True
|
|
492
534
|
|
|
493
535
|
@staticmethod
|
|
494
|
-
async def get_loaded_firmware_version(host):
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
536
|
+
async def get_loaded_firmware_version(host: Host) -> int | None:
|
|
537
|
+
response1 = await host.send_sync_command_raw(HCI_RTK_Read_ROM_Version_Command())
|
|
538
|
+
if (
|
|
539
|
+
not isinstance(
|
|
540
|
+
response1.return_parameters, HCI_RTK_Read_ROM_Version_ReturnParameters
|
|
541
|
+
)
|
|
542
|
+
or response1.return_parameters.status != hci.HCI_SUCCESS
|
|
543
|
+
):
|
|
498
544
|
return None
|
|
499
545
|
|
|
500
|
-
|
|
501
|
-
hci.HCI_Read_Local_Version_Information_Command()
|
|
502
|
-
)
|
|
503
|
-
return (
|
|
504
|
-
response.return_parameters.hci_subversion << 16
|
|
505
|
-
| response.return_parameters.lmp_subversion
|
|
546
|
+
response2 = await host.send_sync_command(
|
|
547
|
+
hci.HCI_Read_Local_Version_Information_Command()
|
|
506
548
|
)
|
|
549
|
+
return response2.hci_subversion << 16 | response2.lmp_subversion
|
|
507
550
|
|
|
508
551
|
@classmethod
|
|
509
|
-
async def driver_info_for_host(cls, host):
|
|
552
|
+
async def driver_info_for_host(cls, host: Host) -> DriverInfo | None:
|
|
510
553
|
try:
|
|
511
|
-
await host.
|
|
554
|
+
await host.send_sync_command(
|
|
512
555
|
hci.HCI_Reset_Command(),
|
|
513
|
-
check_result=True,
|
|
514
556
|
response_timeout=cls.POST_RESET_DELAY,
|
|
515
557
|
)
|
|
516
558
|
host.ready = True # Needed to let the host know the controller is ready.
|
|
517
559
|
except asyncio.exceptions.TimeoutError:
|
|
518
560
|
logger.warning("timeout waiting for hci reset, retrying")
|
|
519
|
-
await host.
|
|
561
|
+
await host.send_sync_command(hci.HCI_Reset_Command())
|
|
520
562
|
host.ready = True
|
|
521
563
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
564
|
+
response = await host.send_sync_command_raw(
|
|
565
|
+
hci.HCI_Read_Local_Version_Information_Command()
|
|
566
|
+
)
|
|
567
|
+
if (
|
|
568
|
+
not isinstance(
|
|
569
|
+
response.return_parameters,
|
|
570
|
+
hci.HCI_Read_Local_Version_Information_ReturnParameters,
|
|
571
|
+
)
|
|
572
|
+
or response.return_parameters.status != hci.HCI_SUCCESS
|
|
573
|
+
):
|
|
525
574
|
logger.error("failed to probe local version information")
|
|
526
575
|
return None
|
|
527
576
|
|
|
@@ -546,7 +595,7 @@ class Driver(common.Driver):
|
|
|
546
595
|
return driver_info
|
|
547
596
|
|
|
548
597
|
@classmethod
|
|
549
|
-
async def for_host(cls, host, force=False):
|
|
598
|
+
async def for_host(cls, host: Host, force: bool = False):
|
|
550
599
|
# Check that a driver is needed for this host
|
|
551
600
|
if not force and not cls.check(host):
|
|
552
601
|
return None
|
|
@@ -601,15 +650,21 @@ class Driver(common.Driver):
|
|
|
601
650
|
|
|
602
651
|
# TODO: load the firmware
|
|
603
652
|
|
|
604
|
-
async def download_for_rtl8723b(self):
|
|
653
|
+
async def download_for_rtl8723b(self) -> int | None:
|
|
605
654
|
if self.driver_info.has_rom_version:
|
|
606
|
-
|
|
607
|
-
HCI_RTK_Read_ROM_Version_Command()
|
|
655
|
+
response1 = await self.host.send_sync_command_raw(
|
|
656
|
+
HCI_RTK_Read_ROM_Version_Command()
|
|
608
657
|
)
|
|
609
|
-
if
|
|
658
|
+
if (
|
|
659
|
+
not isinstance(
|
|
660
|
+
response1.return_parameters,
|
|
661
|
+
HCI_RTK_Read_ROM_Version_ReturnParameters,
|
|
662
|
+
)
|
|
663
|
+
or response1.return_parameters.status != hci.HCI_SUCCESS
|
|
664
|
+
):
|
|
610
665
|
logger.warning("can't get ROM version")
|
|
611
666
|
return None
|
|
612
|
-
rom_version =
|
|
667
|
+
rom_version = response1.return_parameters.version
|
|
613
668
|
logger.debug(f"ROM version before download: {rom_version:04X}")
|
|
614
669
|
else:
|
|
615
670
|
rom_version = 0
|
|
@@ -644,21 +699,25 @@ class Driver(common.Driver):
|
|
|
644
699
|
fragment_offset = fragment_index * RTK_FRAGMENT_LENGTH
|
|
645
700
|
fragment = payload[fragment_offset : fragment_offset + RTK_FRAGMENT_LENGTH]
|
|
646
701
|
logger.debug(f"downloading fragment {fragment_index}")
|
|
647
|
-
await self.host.
|
|
648
|
-
HCI_RTK_Download_Command(index=download_index, payload=fragment)
|
|
649
|
-
check_result=True,
|
|
702
|
+
await self.host.send_sync_command(
|
|
703
|
+
HCI_RTK_Download_Command(index=download_index, payload=fragment)
|
|
650
704
|
)
|
|
651
705
|
|
|
652
706
|
logger.debug("download complete!")
|
|
653
707
|
|
|
654
708
|
# Read the version again
|
|
655
|
-
|
|
656
|
-
HCI_RTK_Read_ROM_Version_Command()
|
|
709
|
+
response2 = await self.host.send_sync_command_raw(
|
|
710
|
+
HCI_RTK_Read_ROM_Version_Command()
|
|
657
711
|
)
|
|
658
|
-
if
|
|
712
|
+
if (
|
|
713
|
+
not isinstance(
|
|
714
|
+
response2.return_parameters, HCI_RTK_Read_ROM_Version_ReturnParameters
|
|
715
|
+
)
|
|
716
|
+
or response2.return_parameters.status != hci.HCI_SUCCESS
|
|
717
|
+
):
|
|
659
718
|
logger.warning("can't get ROM version")
|
|
660
719
|
else:
|
|
661
|
-
rom_version =
|
|
720
|
+
rom_version = response2.return_parameters.version
|
|
662
721
|
logger.debug(f"ROM version after download: {rom_version:02X}")
|
|
663
722
|
|
|
664
723
|
return firmware.version
|
|
@@ -680,7 +739,7 @@ class Driver(common.Driver):
|
|
|
680
739
|
|
|
681
740
|
async def init_controller(self):
|
|
682
741
|
await self.download_firmware()
|
|
683
|
-
await self.host.
|
|
742
|
+
await self.host.send_sync_command(hci.HCI_Reset_Command())
|
|
684
743
|
logger.info(f"loaded FW image {self.driver_info.fw_name}")
|
|
685
744
|
|
|
686
745
|
|
bumble/gatt.py
CHANGED
|
@@ -29,7 +29,7 @@ import functools
|
|
|
29
29
|
import logging
|
|
30
30
|
import struct
|
|
31
31
|
from collections.abc import Iterable, Sequence
|
|
32
|
-
from typing import TypeVar
|
|
32
|
+
from typing import ClassVar, TypeVar
|
|
33
33
|
|
|
34
34
|
from bumble.att import Attribute, AttributeValue, AttributeValueV2
|
|
35
35
|
from bumble.colors import color
|
|
@@ -403,7 +403,7 @@ class TemplateService(Service):
|
|
|
403
403
|
to expose their UUID as a class property
|
|
404
404
|
'''
|
|
405
405
|
|
|
406
|
-
UUID: UUID
|
|
406
|
+
UUID: ClassVar[UUID]
|
|
407
407
|
|
|
408
408
|
def __init__(
|
|
409
409
|
self,
|
bumble/gatt_client.py
CHANGED
|
@@ -34,11 +34,14 @@ from datetime import datetime
|
|
|
34
34
|
from typing import (
|
|
35
35
|
TYPE_CHECKING,
|
|
36
36
|
Any,
|
|
37
|
+
ClassVar,
|
|
37
38
|
Generic,
|
|
38
39
|
TypeVar,
|
|
39
40
|
overload,
|
|
40
41
|
)
|
|
41
42
|
|
|
43
|
+
from typing_extensions import Self
|
|
44
|
+
|
|
42
45
|
from bumble import att, core, l2cap, utils
|
|
43
46
|
from bumble.colors import color
|
|
44
47
|
from bumble.core import UUID, InvalidStateError
|
|
@@ -249,10 +252,10 @@ class ProfileServiceProxy:
|
|
|
249
252
|
Base class for profile-specific service proxies
|
|
250
253
|
'''
|
|
251
254
|
|
|
252
|
-
SERVICE_CLASS: type[TemplateService]
|
|
255
|
+
SERVICE_CLASS: ClassVar[type[TemplateService]]
|
|
253
256
|
|
|
254
257
|
@classmethod
|
|
255
|
-
def from_client(cls, client: Client) ->
|
|
258
|
+
def from_client(cls, client: Client) -> Self | None:
|
|
256
259
|
return ServiceProxy.from_client(cls, client, cls.SERVICE_CLASS.UUID)
|
|
257
260
|
|
|
258
261
|
|
|
@@ -285,8 +288,6 @@ class Client:
|
|
|
285
288
|
self._bearer_id = (
|
|
286
289
|
f'[0x{bearer.connection.handle:04X}|CID=0x{bearer.source_cid:04X}]'
|
|
287
290
|
)
|
|
288
|
-
# Fill the mtu.
|
|
289
|
-
bearer.on_att_mtu_update(att.ATT_DEFAULT_MTU)
|
|
290
291
|
self.connection = bearer.connection
|
|
291
292
|
else:
|
|
292
293
|
bearer.on(bearer.EVENT_DISCONNECTION, self.on_disconnection)
|
bumble/gatt_server.py
CHANGED
|
@@ -115,7 +115,6 @@ class Server(utils.EventEmitter):
|
|
|
115
115
|
channel.connection.handle,
|
|
116
116
|
channel.source_cid,
|
|
117
117
|
)
|
|
118
|
-
channel.att_mtu = att.ATT_DEFAULT_MTU
|
|
119
118
|
channel.sink = lambda pdu: self.on_gatt_pdu(
|
|
120
119
|
channel, att.ATT_PDU.from_bytes(pdu)
|
|
121
120
|
)
|
|
@@ -777,6 +776,18 @@ class Server(utils.EventEmitter):
|
|
|
777
776
|
error_code=att.ATT_ATTRIBUTE_NOT_FOUND_ERROR,
|
|
778
777
|
)
|
|
779
778
|
|
|
779
|
+
if (
|
|
780
|
+
request.starting_handle == 0x0000
|
|
781
|
+
or request.starting_handle > request.ending_handle
|
|
782
|
+
):
|
|
783
|
+
response = att.ATT_Error_Response(
|
|
784
|
+
request_opcode_in_error=request.op_code,
|
|
785
|
+
attribute_handle_in_error=request.starting_handle,
|
|
786
|
+
error_code=att.ATT_INVALID_HANDLE_ERROR,
|
|
787
|
+
)
|
|
788
|
+
self.send_response(bearer, response)
|
|
789
|
+
return
|
|
790
|
+
|
|
780
791
|
attributes: list[tuple[int, bytes]] = []
|
|
781
792
|
for attribute in (
|
|
782
793
|
attribute
|
|
@@ -977,6 +988,94 @@ class Server(utils.EventEmitter):
|
|
|
977
988
|
|
|
978
989
|
self.send_response(bearer, response)
|
|
979
990
|
|
|
991
|
+
@utils.AsyncRunner.run_in_task()
|
|
992
|
+
async def on_att_read_multiple_request(
|
|
993
|
+
self, bearer: att.Bearer, request: att.ATT_Read_Multiple_Request
|
|
994
|
+
):
|
|
995
|
+
'''
|
|
996
|
+
See Bluetooth spec Vol 3, Part F - 3.4.4.7 Read Multiple Request.
|
|
997
|
+
'''
|
|
998
|
+
response: att.ATT_PDU
|
|
999
|
+
|
|
1000
|
+
pdu_space_available = bearer.att_mtu - 1
|
|
1001
|
+
values: list[bytes] = []
|
|
1002
|
+
|
|
1003
|
+
for handle in request.set_of_handles:
|
|
1004
|
+
if not (attribute := self.get_attribute(handle)):
|
|
1005
|
+
response = att.ATT_Error_Response(
|
|
1006
|
+
request_opcode_in_error=request.op_code,
|
|
1007
|
+
attribute_handle_in_error=handle,
|
|
1008
|
+
error_code=att.ATT_ATTRIBUTE_NOT_FOUND_ERROR,
|
|
1009
|
+
)
|
|
1010
|
+
self.send_response(bearer, response)
|
|
1011
|
+
return
|
|
1012
|
+
# No need to catch permission errors here, since these attributes
|
|
1013
|
+
# must all be world-readable
|
|
1014
|
+
attribute_value = await attribute.read_value(bearer)
|
|
1015
|
+
# Check the attribute value size
|
|
1016
|
+
max_attribute_size = min(bearer.att_mtu - 1, 251)
|
|
1017
|
+
if len(attribute_value) > max_attribute_size:
|
|
1018
|
+
# We need to truncate
|
|
1019
|
+
attribute_value = attribute_value[:max_attribute_size]
|
|
1020
|
+
|
|
1021
|
+
# Check if there is enough space
|
|
1022
|
+
entry_size = len(attribute_value)
|
|
1023
|
+
if pdu_space_available < entry_size:
|
|
1024
|
+
break
|
|
1025
|
+
|
|
1026
|
+
# Add the attribute to the list
|
|
1027
|
+
values.append(attribute_value)
|
|
1028
|
+
pdu_space_available -= entry_size
|
|
1029
|
+
|
|
1030
|
+
response = att.ATT_Read_Multiple_Response(set_of_values=b''.join(values))
|
|
1031
|
+
self.send_response(bearer, response)
|
|
1032
|
+
|
|
1033
|
+
@utils.AsyncRunner.run_in_task()
|
|
1034
|
+
async def on_att_read_multiple_variable_request(
|
|
1035
|
+
self, bearer: att.Bearer, request: att.ATT_Read_Multiple_Variable_Request
|
|
1036
|
+
):
|
|
1037
|
+
'''
|
|
1038
|
+
See Bluetooth spec Vol 3, Part F - 3.4.4.11 Read Multiple Variable Request.
|
|
1039
|
+
'''
|
|
1040
|
+
response: att.ATT_PDU
|
|
1041
|
+
|
|
1042
|
+
pdu_space_available = bearer.att_mtu - 1
|
|
1043
|
+
length_value_tuple_list: list[tuple[int, bytes]] = []
|
|
1044
|
+
|
|
1045
|
+
for handle in request.set_of_handles:
|
|
1046
|
+
if not (attribute := self.get_attribute(handle)):
|
|
1047
|
+
response = att.ATT_Error_Response(
|
|
1048
|
+
request_opcode_in_error=request.op_code,
|
|
1049
|
+
attribute_handle_in_error=handle,
|
|
1050
|
+
error_code=att.ATT_ATTRIBUTE_NOT_FOUND_ERROR,
|
|
1051
|
+
)
|
|
1052
|
+
self.send_response(bearer, response)
|
|
1053
|
+
return
|
|
1054
|
+
# No need to catch permission errors here, since these attributes
|
|
1055
|
+
# must all be world-readable
|
|
1056
|
+
attribute_value = await attribute.read_value(bearer)
|
|
1057
|
+
length = len(attribute_value)
|
|
1058
|
+
# Check the attribute value size
|
|
1059
|
+
max_attribute_size = min(bearer.att_mtu - 3, 251)
|
|
1060
|
+
if len(attribute_value) > max_attribute_size:
|
|
1061
|
+
# We need to truncate
|
|
1062
|
+
attribute_value = attribute_value[:max_attribute_size]
|
|
1063
|
+
|
|
1064
|
+
# Check if there is enough space
|
|
1065
|
+
entry_size = 2 + len(attribute_value)
|
|
1066
|
+
|
|
1067
|
+
# Add the attribute to the list
|
|
1068
|
+
length_value_tuple_list.append((length, attribute_value))
|
|
1069
|
+
pdu_space_available -= entry_size
|
|
1070
|
+
|
|
1071
|
+
if pdu_space_available <= 0:
|
|
1072
|
+
break
|
|
1073
|
+
|
|
1074
|
+
response = att.ATT_Read_Multiple_Variable_Response(
|
|
1075
|
+
length_value_tuple_list=length_value_tuple_list
|
|
1076
|
+
)
|
|
1077
|
+
self.send_response(bearer, response)
|
|
1078
|
+
|
|
980
1079
|
@utils.AsyncRunner.run_in_task()
|
|
981
1080
|
async def on_att_write_request(
|
|
982
1081
|
self, bearer: att.Bearer, request: att.ATT_Write_Request
|