bumble 0.0.204__py3-none-any.whl → 0.0.207__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/auracast.py +626 -87
- bumble/apps/bench.py +225 -147
- bumble/apps/controller_info.py +23 -7
- bumble/apps/device_info.py +50 -4
- bumble/apps/lea_unicast/app.py +61 -201
- bumble/audio/__init__.py +17 -0
- bumble/audio/io.py +553 -0
- bumble/controller.py +24 -9
- bumble/core.py +4 -1
- bumble/device.py +993 -48
- bumble/gatt.py +35 -6
- bumble/gatt_client.py +14 -2
- bumble/hci.py +812 -14
- bumble/host.py +359 -63
- bumble/l2cap.py +3 -16
- bumble/profiles/aics.py +19 -38
- bumble/profiles/ascs.py +6 -18
- bumble/profiles/asha.py +5 -5
- bumble/profiles/bass.py +10 -19
- bumble/profiles/gatt_service.py +166 -0
- bumble/profiles/gmap.py +193 -0
- bumble/profiles/le_audio.py +87 -4
- bumble/profiles/pacs.py +48 -16
- bumble/profiles/tmap.py +3 -9
- bumble/profiles/{vcp.py → vcs.py} +33 -28
- bumble/profiles/vocs.py +54 -85
- bumble/sdp.py +223 -93
- bumble/smp.py +1 -1
- bumble/utils.py +2 -2
- bumble/vendor/android/hci.py +1 -1
- {bumble-0.0.204.dist-info → bumble-0.0.207.dist-info}/METADATA +12 -10
- {bumble-0.0.204.dist-info → bumble-0.0.207.dist-info}/RECORD +37 -34
- {bumble-0.0.204.dist-info → bumble-0.0.207.dist-info}/WHEEL +1 -1
- {bumble-0.0.204.dist-info → bumble-0.0.207.dist-info}/entry_points.txt +1 -0
- bumble/apps/lea_unicast/liblc3.wasm +0 -0
- {bumble-0.0.204.dist-info → bumble-0.0.207.dist-info}/LICENSE +0 -0
- {bumble-0.0.204.dist-info → bumble-0.0.207.dist-info}/top_level.txt +0 -0
bumble/host.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2021-
|
|
1
|
+
# Copyright 2021-2025 Google LLC
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -34,6 +34,8 @@ from typing import (
|
|
|
34
34
|
TYPE_CHECKING,
|
|
35
35
|
)
|
|
36
36
|
|
|
37
|
+
import pyee
|
|
38
|
+
|
|
37
39
|
from bumble.colors import color
|
|
38
40
|
from bumble.l2cap import L2CAP_PDU
|
|
39
41
|
from bumble.snoop import Snooper
|
|
@@ -59,7 +61,19 @@ logger = logging.getLogger(__name__)
|
|
|
59
61
|
|
|
60
62
|
|
|
61
63
|
# -----------------------------------------------------------------------------
|
|
62
|
-
class
|
|
64
|
+
class DataPacketQueue(pyee.EventEmitter):
|
|
65
|
+
"""
|
|
66
|
+
Flow-control queue for host->controller data packets (ACL, ISO).
|
|
67
|
+
|
|
68
|
+
The queue holds packets associated with a connection handle. The packets
|
|
69
|
+
are sent to the controller, up to a maximum total number of packets in flight.
|
|
70
|
+
A packet is considered to be "in flight" when it has been sent to the controller
|
|
71
|
+
but not completed yet. Packets are no longer "in flight" when the controller
|
|
72
|
+
declares them as completed.
|
|
73
|
+
|
|
74
|
+
The queue emits a 'flow' event whenever one or more packets are completed.
|
|
75
|
+
"""
|
|
76
|
+
|
|
63
77
|
max_packet_size: int
|
|
64
78
|
|
|
65
79
|
def __init__(
|
|
@@ -68,40 +82,105 @@ class AclPacketQueue:
|
|
|
68
82
|
max_in_flight: int,
|
|
69
83
|
send: Callable[[hci.HCI_Packet], None],
|
|
70
84
|
) -> None:
|
|
85
|
+
super().__init__()
|
|
71
86
|
self.max_packet_size = max_packet_size
|
|
72
87
|
self.max_in_flight = max_in_flight
|
|
73
|
-
self.
|
|
74
|
-
self.
|
|
75
|
-
|
|
88
|
+
self._in_flight = 0 # Total number of packets in flight across all connections
|
|
89
|
+
self._in_flight_per_connection: dict[int, int] = collections.defaultdict(
|
|
90
|
+
int
|
|
91
|
+
) # Number of packets in flight per connection
|
|
92
|
+
self._send = send
|
|
93
|
+
self._packets: Deque[tuple[hci.HCI_Packet, int]] = collections.deque()
|
|
94
|
+
self._queued = 0
|
|
95
|
+
self._completed = 0
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def queued(self) -> int:
|
|
99
|
+
"""Total number of packets queued since creation."""
|
|
100
|
+
return self._queued
|
|
76
101
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
102
|
+
@property
|
|
103
|
+
def completed(self) -> int:
|
|
104
|
+
"""Total number of packets completed since creation."""
|
|
105
|
+
return self._completed
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def pending(self) -> int:
|
|
109
|
+
"""Number of packets that have been queued but not completed."""
|
|
110
|
+
return self._queued - self._completed
|
|
111
|
+
|
|
112
|
+
def enqueue(self, packet: hci.HCI_Packet, connection_handle: int) -> None:
|
|
113
|
+
"""Enqueue a packet associated with a connection"""
|
|
114
|
+
self._packets.appendleft((packet, connection_handle))
|
|
115
|
+
self._queued += 1
|
|
116
|
+
self._check_queue()
|
|
80
117
|
|
|
81
|
-
if self.
|
|
118
|
+
if self._packets:
|
|
82
119
|
logger.debug(
|
|
83
|
-
f'{self.
|
|
84
|
-
f'{len(self.
|
|
120
|
+
f'{self._in_flight} packets in flight, '
|
|
121
|
+
f'{len(self._packets)} in queue'
|
|
85
122
|
)
|
|
86
123
|
|
|
87
|
-
def
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
self.send(packet)
|
|
91
|
-
self.in_flight += 1
|
|
124
|
+
def flush(self, connection_handle: int) -> None:
|
|
125
|
+
"""
|
|
126
|
+
Remove all packets associated with a connection.
|
|
92
127
|
|
|
93
|
-
|
|
94
|
-
|
|
128
|
+
All packets associated with the connection that are in flight are implicitly
|
|
129
|
+
marked as completed, but no 'flow' event is emitted.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
packets_to_keep = [
|
|
133
|
+
(packet, handle)
|
|
134
|
+
for (packet, handle) in self._packets
|
|
135
|
+
if handle != connection_handle
|
|
136
|
+
]
|
|
137
|
+
if flushed_count := len(self._packets) - len(packets_to_keep):
|
|
138
|
+
self._completed += flushed_count
|
|
139
|
+
self._packets = collections.deque(packets_to_keep)
|
|
140
|
+
|
|
141
|
+
if connection_handle in self._in_flight_per_connection:
|
|
142
|
+
in_flight = self._in_flight_per_connection[connection_handle]
|
|
143
|
+
self._completed += in_flight
|
|
144
|
+
self._in_flight -= in_flight
|
|
145
|
+
del self._in_flight_per_connection[connection_handle]
|
|
146
|
+
|
|
147
|
+
def _check_queue(self) -> None:
|
|
148
|
+
while self._packets and self._in_flight < self.max_in_flight:
|
|
149
|
+
packet, connection_handle = self._packets.pop()
|
|
150
|
+
self._send(packet)
|
|
151
|
+
self._in_flight += 1
|
|
152
|
+
self._in_flight_per_connection[connection_handle] += 1
|
|
153
|
+
|
|
154
|
+
def on_packets_completed(self, packet_count: int, connection_handle: int) -> None:
|
|
155
|
+
"""Mark one or more packets associated with a connection as completed."""
|
|
156
|
+
if connection_handle not in self._in_flight_per_connection:
|
|
95
157
|
logger.warning(
|
|
96
|
-
|
|
97
|
-
'!!! {packet_count} completed but only '
|
|
98
|
-
f'{self.in_flight} in flight'
|
|
99
|
-
)
|
|
158
|
+
f'received completion for unknown connection {connection_handle}'
|
|
100
159
|
)
|
|
101
|
-
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
in_flight_for_connection = self._in_flight_per_connection[connection_handle]
|
|
163
|
+
if packet_count <= in_flight_for_connection:
|
|
164
|
+
self._in_flight_per_connection[connection_handle] -= packet_count
|
|
165
|
+
else:
|
|
166
|
+
logger.warning(
|
|
167
|
+
f'{packet_count} completed for {connection_handle} '
|
|
168
|
+
f'but only {in_flight_for_connection} in flight'
|
|
169
|
+
)
|
|
170
|
+
self._in_flight_per_connection[connection_handle] = 0
|
|
171
|
+
|
|
172
|
+
if packet_count <= self._in_flight:
|
|
173
|
+
self._in_flight -= packet_count
|
|
174
|
+
self._completed += packet_count
|
|
175
|
+
else:
|
|
176
|
+
logger.warning(
|
|
177
|
+
f'{packet_count} completed but only {self._in_flight} in flight'
|
|
178
|
+
)
|
|
179
|
+
self._in_flight = 0
|
|
180
|
+
self._completed = self._queued
|
|
102
181
|
|
|
103
|
-
self.
|
|
104
|
-
self.
|
|
182
|
+
self._check_queue()
|
|
183
|
+
self.emit('flow')
|
|
105
184
|
|
|
106
185
|
|
|
107
186
|
# -----------------------------------------------------------------------------
|
|
@@ -114,7 +193,7 @@ class Connection:
|
|
|
114
193
|
self.peer_address = peer_address
|
|
115
194
|
self.assembler = hci.HCI_AclDataPacketAssembler(self.on_acl_pdu)
|
|
116
195
|
self.transport = transport
|
|
117
|
-
acl_packet_queue: Optional[
|
|
196
|
+
acl_packet_queue: Optional[DataPacketQueue] = (
|
|
118
197
|
host.le_acl_packet_queue
|
|
119
198
|
if transport == BT_LE_TRANSPORT
|
|
120
199
|
else host.acl_packet_queue
|
|
@@ -129,28 +208,37 @@ class Connection:
|
|
|
129
208
|
l2cap_pdu = L2CAP_PDU.from_bytes(pdu)
|
|
130
209
|
self.host.on_l2cap_pdu(self, l2cap_pdu.cid, l2cap_pdu.payload)
|
|
131
210
|
|
|
211
|
+
def __str__(self) -> str:
|
|
212
|
+
return (
|
|
213
|
+
f'Connection(transport={self.transport}, peer_address={self.peer_address})'
|
|
214
|
+
)
|
|
215
|
+
|
|
132
216
|
|
|
133
217
|
# -----------------------------------------------------------------------------
|
|
134
218
|
@dataclasses.dataclass
|
|
135
219
|
class ScoLink:
|
|
136
220
|
peer_address: hci.Address
|
|
137
|
-
|
|
221
|
+
connection_handle: int
|
|
138
222
|
|
|
139
223
|
|
|
140
224
|
# -----------------------------------------------------------------------------
|
|
141
225
|
@dataclasses.dataclass
|
|
142
|
-
class
|
|
143
|
-
peer_address: hci.Address
|
|
226
|
+
class IsoLink:
|
|
144
227
|
handle: int
|
|
228
|
+
packet_queue: DataPacketQueue = dataclasses.field(repr=False)
|
|
229
|
+
packet_sequence_number: int = 0
|
|
145
230
|
|
|
146
231
|
|
|
147
232
|
# -----------------------------------------------------------------------------
|
|
148
233
|
class Host(AbortableEventEmitter):
|
|
149
234
|
connections: Dict[int, Connection]
|
|
150
|
-
cis_links: Dict[int,
|
|
235
|
+
cis_links: Dict[int, IsoLink]
|
|
236
|
+
bis_links: Dict[int, IsoLink]
|
|
151
237
|
sco_links: Dict[int, ScoLink]
|
|
152
|
-
|
|
153
|
-
|
|
238
|
+
bigs: dict[int, set[int]] = {} # BIG Handle to BIS Handles
|
|
239
|
+
acl_packet_queue: Optional[DataPacketQueue] = None
|
|
240
|
+
le_acl_packet_queue: Optional[DataPacketQueue] = None
|
|
241
|
+
iso_packet_queue: Optional[DataPacketQueue] = None
|
|
154
242
|
hci_sink: Optional[TransportSink] = None
|
|
155
243
|
hci_metadata: Dict[str, Any]
|
|
156
244
|
long_term_key_provider: Optional[
|
|
@@ -169,6 +257,7 @@ class Host(AbortableEventEmitter):
|
|
|
169
257
|
self.ready = False # True when we can accept incoming packets
|
|
170
258
|
self.connections = {} # Connections, by connection handle
|
|
171
259
|
self.cis_links = {} # CIS links, by connection handle
|
|
260
|
+
self.bis_links = {} # BIS links, by connection handle
|
|
172
261
|
self.sco_links = {} # SCO links, by connection handle
|
|
173
262
|
self.pending_command = None
|
|
174
263
|
self.pending_response: Optional[asyncio.Future[Any]] = None
|
|
@@ -387,6 +476,12 @@ class Host(AbortableEventEmitter):
|
|
|
387
476
|
hci.HCI_LE_TRANSMIT_POWER_REPORTING_EVENT,
|
|
388
477
|
hci.HCI_LE_BIGINFO_ADVERTISING_REPORT_EVENT,
|
|
389
478
|
hci.HCI_LE_SUBRATE_CHANGE_EVENT,
|
|
479
|
+
hci.HCI_LE_CS_READ_REMOTE_SUPPORTED_CAPABILITIES_COMPLETE_EVENT,
|
|
480
|
+
hci.HCI_LE_CS_PROCEDURE_ENABLE_COMPLETE_EVENT,
|
|
481
|
+
hci.HCI_LE_CS_SECURITY_ENABLE_COMPLETE_EVENT,
|
|
482
|
+
hci.HCI_LE_CS_CONFIG_COMPLETE_EVENT,
|
|
483
|
+
hci.HCI_LE_CS_SUBEVENT_RESULT_EVENT,
|
|
484
|
+
hci.HCI_LE_CS_SUBEVENT_RESULT_CONTINUE_EVENT,
|
|
390
485
|
]
|
|
391
486
|
)
|
|
392
487
|
|
|
@@ -411,39 +506,70 @@ class Host(AbortableEventEmitter):
|
|
|
411
506
|
f'hc_total_num_acl_data_packets={hc_total_num_acl_data_packets}'
|
|
412
507
|
)
|
|
413
508
|
|
|
414
|
-
self.acl_packet_queue =
|
|
509
|
+
self.acl_packet_queue = DataPacketQueue(
|
|
415
510
|
max_packet_size=hc_acl_data_packet_length,
|
|
416
511
|
max_in_flight=hc_total_num_acl_data_packets,
|
|
417
512
|
send=self.send_hci_packet,
|
|
418
513
|
)
|
|
419
514
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
515
|
+
le_acl_data_packet_length = 0
|
|
516
|
+
total_num_le_acl_data_packets = 0
|
|
517
|
+
iso_data_packet_length = 0
|
|
518
|
+
total_num_iso_data_packets = 0
|
|
519
|
+
if self.supports_command(hci.HCI_LE_READ_BUFFER_SIZE_V2_COMMAND):
|
|
520
|
+
response = await self.send_command(
|
|
521
|
+
hci.HCI_LE_Read_Buffer_Size_V2_Command(), check_result=True
|
|
522
|
+
)
|
|
523
|
+
le_acl_data_packet_length = (
|
|
524
|
+
response.return_parameters.le_acl_data_packet_length
|
|
525
|
+
)
|
|
526
|
+
total_num_le_acl_data_packets = (
|
|
527
|
+
response.return_parameters.total_num_le_acl_data_packets
|
|
528
|
+
)
|
|
529
|
+
iso_data_packet_length = response.return_parameters.iso_data_packet_length
|
|
530
|
+
total_num_iso_data_packets = (
|
|
531
|
+
response.return_parameters.total_num_iso_data_packets
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
logger.debug(
|
|
535
|
+
'HCI LE flow control: '
|
|
536
|
+
f'le_acl_data_packet_length={le_acl_data_packet_length},'
|
|
537
|
+
f'total_num_le_acl_data_packets={total_num_le_acl_data_packets}'
|
|
538
|
+
f'iso_data_packet_length={iso_data_packet_length},'
|
|
539
|
+
f'total_num_iso_data_packets={total_num_iso_data_packets}'
|
|
540
|
+
)
|
|
541
|
+
elif self.supports_command(hci.HCI_LE_READ_BUFFER_SIZE_COMMAND):
|
|
423
542
|
response = await self.send_command(
|
|
424
543
|
hci.HCI_LE_Read_Buffer_Size_Command(), check_result=True
|
|
425
544
|
)
|
|
426
|
-
|
|
427
|
-
response.return_parameters.
|
|
545
|
+
le_acl_data_packet_length = (
|
|
546
|
+
response.return_parameters.le_acl_data_packet_length
|
|
428
547
|
)
|
|
429
|
-
|
|
430
|
-
response.return_parameters.
|
|
548
|
+
total_num_le_acl_data_packets = (
|
|
549
|
+
response.return_parameters.total_num_le_acl_data_packets
|
|
431
550
|
)
|
|
432
551
|
|
|
433
552
|
logger.debug(
|
|
434
553
|
'HCI LE ACL flow control: '
|
|
435
|
-
f'
|
|
436
|
-
f'
|
|
554
|
+
f'le_acl_data_packet_length={le_acl_data_packet_length},'
|
|
555
|
+
f'total_num_le_acl_data_packets={total_num_le_acl_data_packets}'
|
|
437
556
|
)
|
|
438
557
|
|
|
439
|
-
if
|
|
558
|
+
if le_acl_data_packet_length == 0 or total_num_le_acl_data_packets == 0:
|
|
440
559
|
# LE and Classic share the same queue
|
|
441
560
|
self.le_acl_packet_queue = self.acl_packet_queue
|
|
442
561
|
else:
|
|
443
562
|
# Create a separate queue for LE
|
|
444
|
-
self.le_acl_packet_queue =
|
|
445
|
-
max_packet_size=
|
|
446
|
-
max_in_flight=
|
|
563
|
+
self.le_acl_packet_queue = DataPacketQueue(
|
|
564
|
+
max_packet_size=le_acl_data_packet_length,
|
|
565
|
+
max_in_flight=total_num_le_acl_data_packets,
|
|
566
|
+
send=self.send_hci_packet,
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
if iso_data_packet_length and total_num_iso_data_packets:
|
|
570
|
+
self.iso_packet_queue = DataPacketQueue(
|
|
571
|
+
max_packet_size=iso_data_packet_length,
|
|
572
|
+
max_in_flight=total_num_iso_data_packets,
|
|
447
573
|
send=self.send_hci_packet,
|
|
448
574
|
)
|
|
449
575
|
|
|
@@ -595,11 +721,78 @@ class Host(AbortableEventEmitter):
|
|
|
595
721
|
data=l2cap_pdu[offset : offset + data_total_length],
|
|
596
722
|
)
|
|
597
723
|
logger.debug(f'>>> ACL packet enqueue: (CID={cid}) {acl_packet}')
|
|
598
|
-
packet_queue.enqueue(acl_packet)
|
|
724
|
+
packet_queue.enqueue(acl_packet, connection_handle)
|
|
599
725
|
pb_flag = 1
|
|
600
726
|
offset += data_total_length
|
|
601
727
|
bytes_remaining -= data_total_length
|
|
602
728
|
|
|
729
|
+
def get_data_packet_queue(self, connection_handle: int) -> DataPacketQueue | None:
|
|
730
|
+
if connection := self.connections.get(connection_handle):
|
|
731
|
+
return connection.acl_packet_queue
|
|
732
|
+
|
|
733
|
+
if iso_link := self.cis_links.get(connection_handle) or self.bis_links.get(
|
|
734
|
+
connection_handle
|
|
735
|
+
):
|
|
736
|
+
return iso_link.packet_queue
|
|
737
|
+
|
|
738
|
+
return None
|
|
739
|
+
|
|
740
|
+
def send_iso_sdu(self, connection_handle: int, sdu: bytes) -> None:
|
|
741
|
+
if not (
|
|
742
|
+
iso_link := self.cis_links.get(connection_handle)
|
|
743
|
+
or self.bis_links.get(connection_handle)
|
|
744
|
+
):
|
|
745
|
+
logger.warning(f"no ISO link for connection handle {connection_handle}")
|
|
746
|
+
return
|
|
747
|
+
|
|
748
|
+
if iso_link.packet_queue is None:
|
|
749
|
+
logger.warning("ISO link has no data packet queue")
|
|
750
|
+
return
|
|
751
|
+
|
|
752
|
+
bytes_remaining = len(sdu)
|
|
753
|
+
offset = 0
|
|
754
|
+
while bytes_remaining:
|
|
755
|
+
is_first_fragment = offset == 0
|
|
756
|
+
header_length = 4 if is_first_fragment else 0
|
|
757
|
+
assert iso_link.packet_queue.max_packet_size > header_length
|
|
758
|
+
fragment_length = min(
|
|
759
|
+
bytes_remaining, iso_link.packet_queue.max_packet_size - header_length
|
|
760
|
+
)
|
|
761
|
+
is_last_fragment = bytes_remaining == fragment_length
|
|
762
|
+
iso_sdu_fragment = sdu[offset : offset + fragment_length]
|
|
763
|
+
iso_link.packet_queue.enqueue(
|
|
764
|
+
(
|
|
765
|
+
hci.HCI_IsoDataPacket(
|
|
766
|
+
connection_handle=connection_handle,
|
|
767
|
+
data_total_length=header_length + fragment_length,
|
|
768
|
+
packet_sequence_number=iso_link.packet_sequence_number,
|
|
769
|
+
pb_flag=0b10 if is_last_fragment else 0b00,
|
|
770
|
+
packet_status_flag=0,
|
|
771
|
+
iso_sdu_length=len(sdu),
|
|
772
|
+
iso_sdu_fragment=iso_sdu_fragment,
|
|
773
|
+
)
|
|
774
|
+
if is_first_fragment
|
|
775
|
+
else hci.HCI_IsoDataPacket(
|
|
776
|
+
connection_handle=connection_handle,
|
|
777
|
+
data_total_length=fragment_length,
|
|
778
|
+
pb_flag=0b11 if is_last_fragment else 0b01,
|
|
779
|
+
iso_sdu_fragment=iso_sdu_fragment,
|
|
780
|
+
)
|
|
781
|
+
),
|
|
782
|
+
connection_handle,
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
offset += fragment_length
|
|
786
|
+
bytes_remaining -= fragment_length
|
|
787
|
+
|
|
788
|
+
iso_link.packet_sequence_number = (iso_link.packet_sequence_number + 1) & 0xFFFF
|
|
789
|
+
|
|
790
|
+
def remove_big(self, big_handle: int) -> None:
|
|
791
|
+
if big := self.bigs.pop(big_handle, None):
|
|
792
|
+
for connection_handle in big:
|
|
793
|
+
if bis_link := self.bis_links.pop(connection_handle, None):
|
|
794
|
+
bis_link.packet_queue.flush(bis_link.handle)
|
|
795
|
+
|
|
603
796
|
def supports_command(self, op_code: int) -> bool:
|
|
604
797
|
return (
|
|
605
798
|
self.local_supported_commands
|
|
@@ -727,16 +920,17 @@ class Host(AbortableEventEmitter):
|
|
|
727
920
|
def on_hci_command_status_event(self, event):
|
|
728
921
|
return self.on_command_processed(event)
|
|
729
922
|
|
|
730
|
-
def on_hci_number_of_completed_packets_event(
|
|
923
|
+
def on_hci_number_of_completed_packets_event(
|
|
924
|
+
self, event: hci.HCI_Number_Of_Completed_Packets_Event
|
|
925
|
+
) -> None:
|
|
731
926
|
for connection_handle, num_completed_packets in zip(
|
|
732
927
|
event.connection_handles, event.num_completed_packets
|
|
733
928
|
):
|
|
734
|
-
if
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
):
|
|
929
|
+
if queue := self.get_data_packet_queue(connection_handle):
|
|
930
|
+
queue.on_packets_completed(num_completed_packets, connection_handle)
|
|
931
|
+
continue
|
|
932
|
+
|
|
933
|
+
if connection_handle not in self.sco_links:
|
|
740
934
|
logger.warning(
|
|
741
935
|
'received packet completion event for unknown handle '
|
|
742
936
|
f'0x{connection_handle:04X}'
|
|
@@ -854,11 +1048,7 @@ class Host(AbortableEventEmitter):
|
|
|
854
1048
|
return
|
|
855
1049
|
|
|
856
1050
|
if event.status == hci.HCI_SUCCESS:
|
|
857
|
-
logger.debug(
|
|
858
|
-
f'### DISCONNECTION: [0x{handle:04X}] '
|
|
859
|
-
f'{connection.peer_address} '
|
|
860
|
-
f'reason={event.reason}'
|
|
861
|
-
)
|
|
1051
|
+
logger.debug(f'### DISCONNECTION: {connection}, reason={event.reason}')
|
|
862
1052
|
|
|
863
1053
|
# Notify the listeners
|
|
864
1054
|
self.emit('disconnection', handle, event.reason)
|
|
@@ -869,6 +1059,12 @@ class Host(AbortableEventEmitter):
|
|
|
869
1059
|
or self.cis_links.pop(handle, 0)
|
|
870
1060
|
or self.sco_links.pop(handle, 0)
|
|
871
1061
|
)
|
|
1062
|
+
|
|
1063
|
+
# Flush the data queues
|
|
1064
|
+
self.acl_packet_queue.flush(handle)
|
|
1065
|
+
self.le_acl_packet_queue.flush(handle)
|
|
1066
|
+
if self.iso_packet_queue:
|
|
1067
|
+
self.iso_packet_queue.flush(handle)
|
|
872
1068
|
else:
|
|
873
1069
|
logger.debug(f'### DISCONNECTION FAILED: {event.status}')
|
|
874
1070
|
|
|
@@ -953,12 +1149,94 @@ class Host(AbortableEventEmitter):
|
|
|
953
1149
|
event.cis_id,
|
|
954
1150
|
)
|
|
955
1151
|
|
|
1152
|
+
def on_hci_le_create_big_complete_event(self, event):
|
|
1153
|
+
self.bigs[event.big_handle] = set(event.connection_handle)
|
|
1154
|
+
if self.iso_packet_queue is None:
|
|
1155
|
+
logger.warning("BIS established but ISO packets not supported")
|
|
1156
|
+
|
|
1157
|
+
for connection_handle in event.connection_handle:
|
|
1158
|
+
self.bis_links[connection_handle] = IsoLink(
|
|
1159
|
+
connection_handle, self.iso_packet_queue
|
|
1160
|
+
)
|
|
1161
|
+
|
|
1162
|
+
self.emit(
|
|
1163
|
+
'big_establishment',
|
|
1164
|
+
event.status,
|
|
1165
|
+
event.big_handle,
|
|
1166
|
+
event.connection_handle,
|
|
1167
|
+
event.big_sync_delay,
|
|
1168
|
+
event.transport_latency_big,
|
|
1169
|
+
event.phy,
|
|
1170
|
+
event.nse,
|
|
1171
|
+
event.bn,
|
|
1172
|
+
event.pto,
|
|
1173
|
+
event.irc,
|
|
1174
|
+
event.max_pdu,
|
|
1175
|
+
event.iso_interval,
|
|
1176
|
+
)
|
|
1177
|
+
|
|
1178
|
+
def on_hci_le_big_sync_established_event(self, event):
|
|
1179
|
+
self.bigs[event.big_handle] = set(event.connection_handle)
|
|
1180
|
+
for connection_handle in event.connection_handle:
|
|
1181
|
+
self.bis_links[connection_handle] = IsoLink(
|
|
1182
|
+
connection_handle, self.iso_packet_queue
|
|
1183
|
+
)
|
|
1184
|
+
|
|
1185
|
+
self.emit(
|
|
1186
|
+
'big_sync_establishment',
|
|
1187
|
+
event.status,
|
|
1188
|
+
event.big_handle,
|
|
1189
|
+
event.transport_latency_big,
|
|
1190
|
+
event.nse,
|
|
1191
|
+
event.bn,
|
|
1192
|
+
event.pto,
|
|
1193
|
+
event.irc,
|
|
1194
|
+
event.max_pdu,
|
|
1195
|
+
event.iso_interval,
|
|
1196
|
+
event.connection_handle,
|
|
1197
|
+
)
|
|
1198
|
+
|
|
1199
|
+
def on_hci_le_big_sync_lost_event(self, event):
|
|
1200
|
+
self.remove_big(event.big_handle)
|
|
1201
|
+
self.emit('big_sync_lost', event.big_handle, event.reason)
|
|
1202
|
+
|
|
1203
|
+
def on_hci_le_terminate_big_complete_event(self, event):
|
|
1204
|
+
self.remove_big(event.big_handle)
|
|
1205
|
+
self.emit('big_termination', event.reason, event.big_handle)
|
|
1206
|
+
|
|
1207
|
+
def on_hci_le_periodic_advertising_sync_transfer_received_event(self, event):
|
|
1208
|
+
self.emit(
|
|
1209
|
+
'periodic_advertising_sync_transfer',
|
|
1210
|
+
event.status,
|
|
1211
|
+
event.connection_handle,
|
|
1212
|
+
event.sync_handle,
|
|
1213
|
+
event.advertising_sid,
|
|
1214
|
+
event.advertiser_address,
|
|
1215
|
+
event.advertiser_phy,
|
|
1216
|
+
event.periodic_advertising_interval,
|
|
1217
|
+
event.advertiser_clock_accuracy,
|
|
1218
|
+
)
|
|
1219
|
+
|
|
1220
|
+
def on_hci_le_periodic_advertising_sync_transfer_received_v2_event(self, event):
|
|
1221
|
+
self.emit(
|
|
1222
|
+
'periodic_advertising_sync_transfer',
|
|
1223
|
+
event.status,
|
|
1224
|
+
event.connection_handle,
|
|
1225
|
+
event.sync_handle,
|
|
1226
|
+
event.advertising_sid,
|
|
1227
|
+
event.advertiser_address,
|
|
1228
|
+
event.advertiser_phy,
|
|
1229
|
+
event.periodic_advertising_interval,
|
|
1230
|
+
event.advertiser_clock_accuracy,
|
|
1231
|
+
)
|
|
1232
|
+
|
|
956
1233
|
def on_hci_le_cis_established_event(self, event):
|
|
957
1234
|
# The remaining parameters are unused for now.
|
|
958
1235
|
if event.status == hci.HCI_SUCCESS:
|
|
959
|
-
self.
|
|
960
|
-
|
|
961
|
-
|
|
1236
|
+
if self.iso_packet_queue is None:
|
|
1237
|
+
logger.warning("CIS established but ISO packets not supported")
|
|
1238
|
+
self.cis_links[event.connection_handle] = IsoLink(
|
|
1239
|
+
handle=event.connection_handle, packet_queue=self.iso_packet_queue
|
|
962
1240
|
)
|
|
963
1241
|
self.emit('cis_establishment', event.connection_handle)
|
|
964
1242
|
else:
|
|
@@ -1028,7 +1306,7 @@ class Host(AbortableEventEmitter):
|
|
|
1028
1306
|
|
|
1029
1307
|
self.sco_links[event.connection_handle] = ScoLink(
|
|
1030
1308
|
peer_address=event.bd_addr,
|
|
1031
|
-
|
|
1309
|
+
connection_handle=event.connection_handle,
|
|
1032
1310
|
)
|
|
1033
1311
|
|
|
1034
1312
|
# Notify the client
|
|
@@ -1249,5 +1527,23 @@ class Host(AbortableEventEmitter):
|
|
|
1249
1527
|
int.from_bytes(event.le_features, 'little'),
|
|
1250
1528
|
)
|
|
1251
1529
|
|
|
1530
|
+
def on_hci_le_cs_read_remote_supported_capabilities_complete_event(self, event):
|
|
1531
|
+
self.emit('cs_remote_supported_capabilities', event)
|
|
1532
|
+
|
|
1533
|
+
def on_hci_le_cs_security_enable_complete_event(self, event):
|
|
1534
|
+
self.emit('cs_security', event)
|
|
1535
|
+
|
|
1536
|
+
def on_hci_le_cs_config_complete_event(self, event):
|
|
1537
|
+
self.emit('cs_config', event)
|
|
1538
|
+
|
|
1539
|
+
def on_hci_le_cs_procedure_enable_complete_event(self, event):
|
|
1540
|
+
self.emit('cs_procedure', event)
|
|
1541
|
+
|
|
1542
|
+
def on_hci_le_cs_subevent_result_event(self, event):
|
|
1543
|
+
self.emit('cs_subevent_result', event)
|
|
1544
|
+
|
|
1545
|
+
def on_hci_le_cs_subevent_result_continue_event(self, event):
|
|
1546
|
+
self.emit('cs_subevent_result_continue', event)
|
|
1547
|
+
|
|
1252
1548
|
def on_hci_vendor_event(self, event):
|
|
1253
1549
|
self.emit('vendor_event', event)
|
bumble/l2cap.py
CHANGED
|
@@ -773,7 +773,6 @@ class ClassicChannel(EventEmitter):
|
|
|
773
773
|
self.psm = psm
|
|
774
774
|
self.source_cid = source_cid
|
|
775
775
|
self.destination_cid = 0
|
|
776
|
-
self.response = None
|
|
777
776
|
self.connection_result = None
|
|
778
777
|
self.disconnection_result = None
|
|
779
778
|
self.sink = None
|
|
@@ -783,27 +782,15 @@ class ClassicChannel(EventEmitter):
|
|
|
783
782
|
self.state = new_state
|
|
784
783
|
|
|
785
784
|
def send_pdu(self, pdu: Union[SupportsBytes, bytes]) -> None:
|
|
785
|
+
if self.state != self.State.OPEN:
|
|
786
|
+
raise InvalidStateError('channel not open')
|
|
786
787
|
self.manager.send_pdu(self.connection, self.destination_cid, pdu)
|
|
787
788
|
|
|
788
789
|
def send_control_frame(self, frame: L2CAP_Control_Frame) -> None:
|
|
789
790
|
self.manager.send_control_frame(self.connection, self.signaling_cid, frame)
|
|
790
791
|
|
|
791
|
-
async def send_request(self, request: SupportsBytes) -> bytes:
|
|
792
|
-
# Check that there isn't already a request pending
|
|
793
|
-
if self.response:
|
|
794
|
-
raise InvalidStateError('request already pending')
|
|
795
|
-
if self.state != self.State.OPEN:
|
|
796
|
-
raise InvalidStateError('channel not open')
|
|
797
|
-
|
|
798
|
-
self.response = asyncio.get_running_loop().create_future()
|
|
799
|
-
self.send_pdu(request)
|
|
800
|
-
return await self.response
|
|
801
|
-
|
|
802
792
|
def on_pdu(self, pdu: bytes) -> None:
|
|
803
|
-
if self.
|
|
804
|
-
self.response.set_result(pdu)
|
|
805
|
-
self.response = None
|
|
806
|
-
elif self.sink:
|
|
793
|
+
if self.sink:
|
|
807
794
|
# pylint: disable=not-callable
|
|
808
795
|
self.sink(pdu)
|
|
809
796
|
else:
|