bumble 0.0.222__py3-none-any.whl → 0.0.223__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/usb_probe.py +15 -2
- bumble/att.py +97 -32
- bumble/avctp.py +1 -1
- bumble/avdtp.py +3 -3
- bumble/device.py +2 -5
- bumble/drivers/rtk.py +26 -3
- bumble/gatt.py +2 -2
- bumble/gatt_client.py +5 -4
- bumble/gatt_server.py +100 -1
- bumble/hci.py +51 -50
- bumble/hid.py +2 -2
- bumble/host.py +10 -0
- bumble/l2cap.py +3 -1
- 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-0.0.222.dist-info → bumble-0.0.223.dist-info}/METADATA +1 -1
- {bumble-0.0.222.dist-info → bumble-0.0.223.dist-info}/RECORD +25 -25
- {bumble-0.0.222.dist-info → bumble-0.0.223.dist-info}/WHEEL +0 -0
- {bumble-0.0.222.dist-info → bumble-0.0.223.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.222.dist-info → bumble-0.0.223.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.222.dist-info → bumble-0.0.223.dist-info}/top_level.txt +0 -0
bumble/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.0.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 0,
|
|
31
|
+
__version__ = version = '0.0.223'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 0, 223)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
bumble/apps/usb_probe.py
CHANGED
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
# -----------------------------------------------------------------------------
|
|
27
27
|
# Imports
|
|
28
28
|
# -----------------------------------------------------------------------------
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
29
31
|
import click
|
|
30
32
|
import usb1
|
|
31
33
|
|
|
@@ -166,13 +168,16 @@ def is_bluetooth_hci(device):
|
|
|
166
168
|
# -----------------------------------------------------------------------------
|
|
167
169
|
@click.command()
|
|
168
170
|
@click.option('--verbose', is_flag=True, default=False, help='Print more details')
|
|
169
|
-
|
|
171
|
+
@click.option('--hci-only', is_flag=True, default=False, help='only show HCI device')
|
|
172
|
+
@click.option('--manufacturer', help='filter by manufacturer')
|
|
173
|
+
@click.option('--product', help='filter by product')
|
|
174
|
+
def main(verbose: bool, manufacturer: str, product: str, hci_only: bool):
|
|
170
175
|
bumble.logging.setup_basic_logging('WARNING')
|
|
171
176
|
|
|
172
177
|
load_libusb()
|
|
173
178
|
with usb1.USBContext() as context:
|
|
174
179
|
bluetooth_device_count = 0
|
|
175
|
-
devices = {}
|
|
180
|
+
devices: dict[tuple[Any, Any], list[str | None]] = {}
|
|
176
181
|
|
|
177
182
|
for device in context.getDeviceIterator(skip_on_error=True):
|
|
178
183
|
device_class = device.getDeviceClass()
|
|
@@ -234,6 +239,14 @@ def main(verbose):
|
|
|
234
239
|
f'{basic_transport_name}/{device_serial_number}'
|
|
235
240
|
)
|
|
236
241
|
|
|
242
|
+
# Filter
|
|
243
|
+
if product and device_product != product:
|
|
244
|
+
continue
|
|
245
|
+
if manufacturer and device_manufacturer != manufacturer:
|
|
246
|
+
continue
|
|
247
|
+
if not is_bluetooth_hci(device) and hci_only:
|
|
248
|
+
continue
|
|
249
|
+
|
|
237
250
|
# Print the results
|
|
238
251
|
print(
|
|
239
252
|
color(
|
bumble/att.py
CHANGED
|
@@ -29,7 +29,7 @@ import enum
|
|
|
29
29
|
import functools
|
|
30
30
|
import inspect
|
|
31
31
|
import struct
|
|
32
|
-
from collections.abc import Awaitable, Callable
|
|
32
|
+
from collections.abc import Awaitable, Callable, Sequence
|
|
33
33
|
from typing import (
|
|
34
34
|
TYPE_CHECKING,
|
|
35
35
|
ClassVar,
|
|
@@ -72,34 +72,36 @@ ATT_PSM = 0x001F
|
|
|
72
72
|
EATT_PSM = 0x0027
|
|
73
73
|
|
|
74
74
|
class Opcode(hci.SpecableEnum):
|
|
75
|
-
ATT_ERROR_RESPONSE
|
|
76
|
-
ATT_EXCHANGE_MTU_REQUEST
|
|
77
|
-
ATT_EXCHANGE_MTU_RESPONSE
|
|
78
|
-
ATT_FIND_INFORMATION_REQUEST
|
|
79
|
-
ATT_FIND_INFORMATION_RESPONSE
|
|
80
|
-
ATT_FIND_BY_TYPE_VALUE_REQUEST
|
|
81
|
-
ATT_FIND_BY_TYPE_VALUE_RESPONSE
|
|
82
|
-
ATT_READ_BY_TYPE_REQUEST
|
|
83
|
-
ATT_READ_BY_TYPE_RESPONSE
|
|
84
|
-
ATT_READ_REQUEST
|
|
85
|
-
ATT_READ_RESPONSE
|
|
86
|
-
ATT_READ_BLOB_REQUEST
|
|
87
|
-
ATT_READ_BLOB_RESPONSE
|
|
88
|
-
ATT_READ_MULTIPLE_REQUEST
|
|
89
|
-
ATT_READ_MULTIPLE_RESPONSE
|
|
90
|
-
ATT_READ_BY_GROUP_TYPE_REQUEST
|
|
91
|
-
ATT_READ_BY_GROUP_TYPE_RESPONSE
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
75
|
+
ATT_ERROR_RESPONSE = 0x01
|
|
76
|
+
ATT_EXCHANGE_MTU_REQUEST = 0x02
|
|
77
|
+
ATT_EXCHANGE_MTU_RESPONSE = 0x03
|
|
78
|
+
ATT_FIND_INFORMATION_REQUEST = 0x04
|
|
79
|
+
ATT_FIND_INFORMATION_RESPONSE = 0x05
|
|
80
|
+
ATT_FIND_BY_TYPE_VALUE_REQUEST = 0x06
|
|
81
|
+
ATT_FIND_BY_TYPE_VALUE_RESPONSE = 0x07
|
|
82
|
+
ATT_READ_BY_TYPE_REQUEST = 0x08
|
|
83
|
+
ATT_READ_BY_TYPE_RESPONSE = 0x09
|
|
84
|
+
ATT_READ_REQUEST = 0x0A
|
|
85
|
+
ATT_READ_RESPONSE = 0x0B
|
|
86
|
+
ATT_READ_BLOB_REQUEST = 0x0C
|
|
87
|
+
ATT_READ_BLOB_RESPONSE = 0x0D
|
|
88
|
+
ATT_READ_MULTIPLE_REQUEST = 0x0E
|
|
89
|
+
ATT_READ_MULTIPLE_RESPONSE = 0x0F
|
|
90
|
+
ATT_READ_BY_GROUP_TYPE_REQUEST = 0x10
|
|
91
|
+
ATT_READ_BY_GROUP_TYPE_RESPONSE = 0x11
|
|
92
|
+
ATT_READ_MULTIPLE_VARIABLE_REQUEST = 0x20
|
|
93
|
+
ATT_READ_MULTIPLE_VARIABLE_RESPONSE = 0x21
|
|
94
|
+
ATT_WRITE_REQUEST = 0x12
|
|
95
|
+
ATT_WRITE_RESPONSE = 0x13
|
|
96
|
+
ATT_WRITE_COMMAND = 0x52
|
|
97
|
+
ATT_SIGNED_WRITE_COMMAND = 0xD2
|
|
98
|
+
ATT_PREPARE_WRITE_REQUEST = 0x16
|
|
99
|
+
ATT_PREPARE_WRITE_RESPONSE = 0x17
|
|
100
|
+
ATT_EXECUTE_WRITE_REQUEST = 0x18
|
|
101
|
+
ATT_EXECUTE_WRITE_RESPONSE = 0x19
|
|
102
|
+
ATT_HANDLE_VALUE_NOTIFICATION = 0x1B
|
|
103
|
+
ATT_HANDLE_VALUE_INDICATION = 0x1D
|
|
104
|
+
ATT_HANDLE_VALUE_CONFIRMATION = 0x1E
|
|
103
105
|
|
|
104
106
|
ATT_REQUESTS = [
|
|
105
107
|
Opcode.ATT_EXCHANGE_MTU_REQUEST,
|
|
@@ -110,9 +112,10 @@ ATT_REQUESTS = [
|
|
|
110
112
|
Opcode.ATT_READ_BLOB_REQUEST,
|
|
111
113
|
Opcode.ATT_READ_MULTIPLE_REQUEST,
|
|
112
114
|
Opcode.ATT_READ_BY_GROUP_TYPE_REQUEST,
|
|
115
|
+
Opcode.ATT_READ_MULTIPLE_VARIABLE_REQUEST,
|
|
113
116
|
Opcode.ATT_WRITE_REQUEST,
|
|
114
117
|
Opcode.ATT_PREPARE_WRITE_REQUEST,
|
|
115
|
-
Opcode.ATT_EXECUTE_WRITE_REQUEST
|
|
118
|
+
Opcode.ATT_EXECUTE_WRITE_REQUEST,
|
|
116
119
|
]
|
|
117
120
|
|
|
118
121
|
ATT_RESPONSES = [
|
|
@@ -125,9 +128,10 @@ ATT_RESPONSES = [
|
|
|
125
128
|
Opcode.ATT_READ_BLOB_RESPONSE,
|
|
126
129
|
Opcode.ATT_READ_MULTIPLE_RESPONSE,
|
|
127
130
|
Opcode.ATT_READ_BY_GROUP_TYPE_RESPONSE,
|
|
131
|
+
Opcode.ATT_READ_MULTIPLE_VARIABLE_RESPONSE,
|
|
128
132
|
Opcode.ATT_WRITE_RESPONSE,
|
|
129
133
|
Opcode.ATT_PREPARE_WRITE_RESPONSE,
|
|
130
|
-
Opcode.ATT_EXECUTE_WRITE_RESPONSE
|
|
134
|
+
Opcode.ATT_EXECUTE_WRITE_RESPONSE,
|
|
131
135
|
]
|
|
132
136
|
|
|
133
137
|
class ErrorCode(hci.SpecableEnum):
|
|
@@ -185,6 +189,18 @@ ATT_INSUFFICIENT_RESOURCES_ERROR = ErrorCode.INSUFFICIENT_RESOURCES
|
|
|
185
189
|
ATT_DEFAULT_MTU = 23
|
|
186
190
|
|
|
187
191
|
HANDLE_FIELD_SPEC = {'size': 2, 'mapper': lambda x: f'0x{x:04X}'}
|
|
192
|
+
_SET_OF_HANDLES_METADATA = hci.metadata({
|
|
193
|
+
'parser': lambda data, offset: (
|
|
194
|
+
len(data),
|
|
195
|
+
[
|
|
196
|
+
struct.unpack_from('<H', data, i)[0]
|
|
197
|
+
for i in range(offset, len(data), 2)
|
|
198
|
+
],
|
|
199
|
+
),
|
|
200
|
+
'serializer': lambda handles: b''.join(
|
|
201
|
+
[struct.pack('<H', handle) for handle in handles]
|
|
202
|
+
),
|
|
203
|
+
})
|
|
188
204
|
|
|
189
205
|
# fmt: on
|
|
190
206
|
# pylint: enable=line-too-long
|
|
@@ -554,7 +570,7 @@ class ATT_Read_Multiple_Request(ATT_PDU):
|
|
|
554
570
|
See Bluetooth spec @ Vol 3, Part F - 3.4.4.7 Read Multiple Request
|
|
555
571
|
'''
|
|
556
572
|
|
|
557
|
-
set_of_handles:
|
|
573
|
+
set_of_handles: Sequence[int] = dataclasses.field(metadata=_SET_OF_HANDLES_METADATA)
|
|
558
574
|
|
|
559
575
|
|
|
560
576
|
# -----------------------------------------------------------------------------
|
|
@@ -635,6 +651,55 @@ class ATT_Read_By_Group_Type_Response(ATT_PDU):
|
|
|
635
651
|
return result
|
|
636
652
|
|
|
637
653
|
|
|
654
|
+
# -----------------------------------------------------------------------------
|
|
655
|
+
@ATT_PDU.subclass
|
|
656
|
+
@dataclasses.dataclass
|
|
657
|
+
class ATT_Read_Multiple_Variable_Request(ATT_PDU):
|
|
658
|
+
'''
|
|
659
|
+
See Bluetooth spec @ Vol 3, Part F - 3.4.4.11 Read Multiple Variable Request
|
|
660
|
+
'''
|
|
661
|
+
|
|
662
|
+
set_of_handles: Sequence[int] = dataclasses.field(metadata=_SET_OF_HANDLES_METADATA)
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
# -----------------------------------------------------------------------------
|
|
666
|
+
@ATT_PDU.subclass
|
|
667
|
+
@dataclasses.dataclass
|
|
668
|
+
class ATT_Read_Multiple_Variable_Response(ATT_PDU):
|
|
669
|
+
'''
|
|
670
|
+
See Bluetooth spec @ Vol 3, Part F - 3.4.4.12 Read Multiple Variable Response
|
|
671
|
+
'''
|
|
672
|
+
|
|
673
|
+
@classmethod
|
|
674
|
+
def _parse_length_value_tuples(
|
|
675
|
+
cls, data: bytes, offset: int
|
|
676
|
+
) -> tuple[int, list[tuple[int, bytes]]]:
|
|
677
|
+
length_value_tuple_list: list[tuple[int, bytes]] = []
|
|
678
|
+
while offset < len(data):
|
|
679
|
+
length = struct.unpack_from('<H', data, offset)[0]
|
|
680
|
+
length_value_tuple_list.append(
|
|
681
|
+
(length, data[offset + 2 : offset + 2 + length])
|
|
682
|
+
)
|
|
683
|
+
offset += 2 + length
|
|
684
|
+
return (len(data), length_value_tuple_list)
|
|
685
|
+
|
|
686
|
+
length_value_tuple_list: Sequence[tuple[int, bytes]] = dataclasses.field(
|
|
687
|
+
metadata=hci.metadata(
|
|
688
|
+
{
|
|
689
|
+
'parser': lambda data, offset: ATT_Read_Multiple_Variable_Response._parse_length_value_tuples(
|
|
690
|
+
data, offset
|
|
691
|
+
),
|
|
692
|
+
'serializer': lambda length_value_tuple_list: b''.join(
|
|
693
|
+
[
|
|
694
|
+
struct.pack('<H', length) + value
|
|
695
|
+
for length, value in length_value_tuple_list
|
|
696
|
+
]
|
|
697
|
+
),
|
|
698
|
+
}
|
|
699
|
+
)
|
|
700
|
+
)
|
|
701
|
+
|
|
702
|
+
|
|
638
703
|
# -----------------------------------------------------------------------------
|
|
639
704
|
@ATT_PDU.subclass
|
|
640
705
|
@dataclasses.dataclass
|
bumble/avctp.py
CHANGED
bumble/avdtp.py
CHANGED
|
@@ -268,7 +268,7 @@ class MediaPacketPump:
|
|
|
268
268
|
await self.clock.sleep(delay)
|
|
269
269
|
|
|
270
270
|
# Emit
|
|
271
|
-
rtp_channel.
|
|
271
|
+
rtp_channel.write(bytes(packet))
|
|
272
272
|
logger.debug(
|
|
273
273
|
f'{color(">>> sending RTP packet:", "green")} {packet}'
|
|
274
274
|
)
|
|
@@ -1519,7 +1519,7 @@ class Protocol(utils.EventEmitter):
|
|
|
1519
1519
|
header = bytes([first_header_byte])
|
|
1520
1520
|
|
|
1521
1521
|
# Send one packet
|
|
1522
|
-
self.l2cap_channel.
|
|
1522
|
+
self.l2cap_channel.write(header + payload[:max_fragment_size])
|
|
1523
1523
|
|
|
1524
1524
|
# Prepare for the next packet
|
|
1525
1525
|
payload = payload[max_fragment_size:]
|
|
@@ -1829,7 +1829,7 @@ class Stream:
|
|
|
1829
1829
|
|
|
1830
1830
|
def send_media_packet(self, packet: MediaPacket) -> None:
|
|
1831
1831
|
assert self.rtp_channel
|
|
1832
|
-
self.rtp_channel.
|
|
1832
|
+
self.rtp_channel.write(bytes(packet))
|
|
1833
1833
|
|
|
1834
1834
|
async def configure(self) -> None:
|
|
1835
1835
|
if self.state != State.IDLE:
|
bumble/device.py
CHANGED
|
@@ -1383,10 +1383,7 @@ class Peer:
|
|
|
1383
1383
|
def create_service_proxy(
|
|
1384
1384
|
self, proxy_class: type[_PROXY_CLASS]
|
|
1385
1385
|
) -> _PROXY_CLASS | None:
|
|
1386
|
-
|
|
1387
|
-
return cast(_PROXY_CLASS, proxy)
|
|
1388
|
-
|
|
1389
|
-
return None
|
|
1386
|
+
return proxy_class.from_client(self.gatt_client)
|
|
1390
1387
|
|
|
1391
1388
|
async def discover_service_and_create_proxy(
|
|
1392
1389
|
self, proxy_class: type[_PROXY_CLASS]
|
|
@@ -1406,7 +1403,7 @@ class Peer:
|
|
|
1406
1403
|
async def request_name(self) -> str:
|
|
1407
1404
|
return await self.connection.request_remote_name()
|
|
1408
1405
|
|
|
1409
|
-
async def __aenter__(self):
|
|
1406
|
+
async def __aenter__(self) -> Self:
|
|
1410
1407
|
await self.discover_services()
|
|
1411
1408
|
for service in self.services:
|
|
1412
1409
|
await service.discover_characteristics()
|
bumble/drivers/rtk.py
CHANGED
|
@@ -77,6 +77,7 @@ class RtlProjectId(enum.IntEnum):
|
|
|
77
77
|
PROJECT_ID_8852A = 18
|
|
78
78
|
PROJECT_ID_8852B = 20
|
|
79
79
|
PROJECT_ID_8852C = 25
|
|
80
|
+
PROJECT_ID_8761C = 51
|
|
80
81
|
|
|
81
82
|
|
|
82
83
|
RTK_PROJECT_ID_TO_ROM = {
|
|
@@ -92,6 +93,7 @@ RTK_PROJECT_ID_TO_ROM = {
|
|
|
92
93
|
18: RTK_ROM_LMP_8852A,
|
|
93
94
|
20: RTK_ROM_LMP_8852A,
|
|
94
95
|
25: RTK_ROM_LMP_8852A,
|
|
96
|
+
51: RTK_ROM_LMP_8761A,
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
# List of USB (VendorID, ProductID) for Realtek-based devices.
|
|
@@ -123,6 +125,10 @@ RTK_USB_PRODUCTS = {
|
|
|
123
125
|
(0x2550, 0x8761),
|
|
124
126
|
(0x2B89, 0x8761),
|
|
125
127
|
(0x7392, 0xC611),
|
|
128
|
+
# Realtek 8761CUV
|
|
129
|
+
(0x0B05, 0x1BF6),
|
|
130
|
+
(0x0BDA, 0xC761),
|
|
131
|
+
(0x7392, 0xF611),
|
|
126
132
|
# Realtek 8821AE
|
|
127
133
|
(0x0B05, 0x17DC),
|
|
128
134
|
(0x13D3, 0x3414),
|
|
@@ -363,6 +369,15 @@ class Driver(common.Driver):
|
|
|
363
369
|
fw_name="rtl8761bu_fw.bin",
|
|
364
370
|
config_name="rtl8761bu_config.bin",
|
|
365
371
|
),
|
|
372
|
+
# 8761CU
|
|
373
|
+
DriverInfo(
|
|
374
|
+
rom=RTK_ROM_LMP_8761A,
|
|
375
|
+
hci=(0x0E, 0x00),
|
|
376
|
+
config_needed=False,
|
|
377
|
+
has_rom_version=True,
|
|
378
|
+
fw_name="rtl8761cu_fw.bin",
|
|
379
|
+
config_name="rtl8761cu_config.bin",
|
|
380
|
+
),
|
|
366
381
|
# 8822C
|
|
367
382
|
DriverInfo(
|
|
368
383
|
rom=RTK_ROM_LMP_8822B,
|
|
@@ -420,9 +435,17 @@ class Driver(common.Driver):
|
|
|
420
435
|
@staticmethod
|
|
421
436
|
def find_driver_info(hci_version, hci_subversion, lmp_subversion):
|
|
422
437
|
for driver_info in Driver.DRIVER_INFOS:
|
|
423
|
-
if driver_info.rom == lmp_subversion and
|
|
424
|
-
|
|
425
|
-
|
|
438
|
+
if driver_info.rom == lmp_subversion and (
|
|
439
|
+
driver_info.hci
|
|
440
|
+
== (
|
|
441
|
+
hci_subversion,
|
|
442
|
+
hci_version,
|
|
443
|
+
)
|
|
444
|
+
or driver_info.hci
|
|
445
|
+
== (
|
|
446
|
+
hci_subversion,
|
|
447
|
+
0x0,
|
|
448
|
+
)
|
|
426
449
|
):
|
|
427
450
|
return driver_info
|
|
428
451
|
|
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
|