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/host.py
CHANGED
|
@@ -21,13 +21,16 @@ import asyncio
|
|
|
21
21
|
import collections
|
|
22
22
|
import dataclasses
|
|
23
23
|
import logging
|
|
24
|
-
import struct
|
|
25
24
|
from collections.abc import Awaitable, Callable
|
|
26
|
-
from typing import TYPE_CHECKING, Any, cast
|
|
25
|
+
from typing import TYPE_CHECKING, Any, TypeVar, cast, overload
|
|
27
26
|
|
|
28
27
|
from bumble import drivers, hci, utils
|
|
29
28
|
from bumble.colors import color
|
|
30
|
-
from bumble.core import
|
|
29
|
+
from bumble.core import (
|
|
30
|
+
ConnectionPHY,
|
|
31
|
+
InvalidStateError,
|
|
32
|
+
PhysicalTransport,
|
|
33
|
+
)
|
|
31
34
|
from bumble.l2cap import L2CAP_PDU
|
|
32
35
|
from bumble.snoop import Snooper
|
|
33
36
|
from bumble.transport.common import TransportLostError
|
|
@@ -35,7 +38,6 @@ from bumble.transport.common import TransportLostError
|
|
|
35
38
|
if TYPE_CHECKING:
|
|
36
39
|
from bumble.transport.common import TransportSink, TransportSource
|
|
37
40
|
|
|
38
|
-
|
|
39
41
|
# -----------------------------------------------------------------------------
|
|
40
42
|
# Logging
|
|
41
43
|
# -----------------------------------------------------------------------------
|
|
@@ -236,6 +238,9 @@ class IsoLink:
|
|
|
236
238
|
|
|
237
239
|
|
|
238
240
|
# -----------------------------------------------------------------------------
|
|
241
|
+
_RP = TypeVar('_RP', bound=hci.HCI_ReturnParameters)
|
|
242
|
+
|
|
243
|
+
|
|
239
244
|
class Host(utils.EventEmitter):
|
|
240
245
|
connections: dict[int, Connection]
|
|
241
246
|
cis_links: dict[int, IsoLink]
|
|
@@ -264,13 +269,20 @@ class Host(utils.EventEmitter):
|
|
|
264
269
|
self.bis_links = {} # BIS links, by connection handle
|
|
265
270
|
self.sco_links = {} # SCO links, by connection handle
|
|
266
271
|
self.bigs = {} # BIG Handle to BIS Handles
|
|
267
|
-
self.pending_command = None
|
|
268
|
-
self.pending_response:
|
|
272
|
+
self.pending_command: hci.HCI_SyncCommand | hci.HCI_AsyncCommand | None = None
|
|
273
|
+
self.pending_response: (
|
|
274
|
+
asyncio.Future[
|
|
275
|
+
hci.HCI_Command_Complete_Event | hci.HCI_Command_Status_Event
|
|
276
|
+
]
|
|
277
|
+
| None
|
|
278
|
+
) = None
|
|
269
279
|
self.number_of_supported_advertising_sets = 0
|
|
270
280
|
self.maximum_advertising_data_length = 31
|
|
271
|
-
self.local_version
|
|
281
|
+
self.local_version: (
|
|
282
|
+
hci.HCI_Read_Local_Version_Information_ReturnParameters | None
|
|
283
|
+
) = None
|
|
272
284
|
self.local_supported_commands = 0
|
|
273
|
-
self.local_le_features = 0
|
|
285
|
+
self.local_le_features = hci.LeFeatureMask(0) # LE features
|
|
274
286
|
self.local_lmp_features = hci.LmpFeatureMask(0) # Classic LMP features
|
|
275
287
|
self.suggested_max_tx_octets = 251 # Max allowed
|
|
276
288
|
self.suggested_max_tx_time = 2120 # Max allowed
|
|
@@ -312,7 +324,7 @@ class Host(utils.EventEmitter):
|
|
|
312
324
|
self.emit('flush')
|
|
313
325
|
self.command_semaphore.release()
|
|
314
326
|
|
|
315
|
-
async def reset(self, driver_factory=drivers.get_driver_for_host):
|
|
327
|
+
async def reset(self, driver_factory=drivers.get_driver_for_host) -> None:
|
|
316
328
|
if self.ready:
|
|
317
329
|
self.ready = False
|
|
318
330
|
await self.flush()
|
|
@@ -330,57 +342,61 @@ class Host(utils.EventEmitter):
|
|
|
330
342
|
|
|
331
343
|
# Send a reset command unless a driver has already done so.
|
|
332
344
|
if reset_needed:
|
|
333
|
-
await self.
|
|
345
|
+
await self.send_sync_command(hci.HCI_Reset_Command())
|
|
334
346
|
self.ready = True
|
|
335
347
|
|
|
336
|
-
|
|
337
|
-
hci.HCI_Read_Local_Supported_Commands_Command()
|
|
348
|
+
response1 = await self.send_sync_command(
|
|
349
|
+
hci.HCI_Read_Local_Supported_Commands_Command()
|
|
338
350
|
)
|
|
339
351
|
self.local_supported_commands = int.from_bytes(
|
|
340
|
-
|
|
352
|
+
response1.supported_commands, 'little'
|
|
341
353
|
)
|
|
342
354
|
|
|
343
|
-
if self.supports_command(hci.
|
|
344
|
-
|
|
345
|
-
hci.
|
|
355
|
+
if self.supports_command(hci.HCI_READ_LOCAL_VERSION_INFORMATION_COMMAND):
|
|
356
|
+
self.local_version = await self.send_sync_command(
|
|
357
|
+
hci.HCI_Read_Local_Version_Information_Command()
|
|
346
358
|
)
|
|
347
|
-
self.local_le_features = struct.unpack(
|
|
348
|
-
'<Q', response.return_parameters.le_features
|
|
349
|
-
)[0]
|
|
350
359
|
|
|
351
|
-
if self.supports_command(hci.
|
|
352
|
-
|
|
353
|
-
hci.
|
|
360
|
+
if self.supports_command(hci.HCI_LE_READ_ALL_LOCAL_SUPPORTED_FEATURES_COMMAND):
|
|
361
|
+
response2 = await self.send_sync_command(
|
|
362
|
+
hci.HCI_LE_Read_All_Local_Supported_Features_Command()
|
|
363
|
+
)
|
|
364
|
+
self.local_le_features = hci.LeFeatureMask(
|
|
365
|
+
int.from_bytes(response2.le_features, 'little')
|
|
366
|
+
)
|
|
367
|
+
elif self.supports_command(hci.HCI_LE_READ_LOCAL_SUPPORTED_FEATURES_COMMAND):
|
|
368
|
+
response3 = await self.send_sync_command(
|
|
369
|
+
hci.HCI_LE_Read_Local_Supported_Features_Command()
|
|
370
|
+
)
|
|
371
|
+
self.local_le_features = hci.LeFeatureMask(
|
|
372
|
+
int.from_bytes(response3.le_features, 'little')
|
|
354
373
|
)
|
|
355
|
-
self.local_version = response.return_parameters
|
|
356
374
|
|
|
357
375
|
if self.supports_command(hci.HCI_READ_LOCAL_EXTENDED_FEATURES_COMMAND):
|
|
358
376
|
max_page_number = 0
|
|
359
377
|
page_number = 0
|
|
360
378
|
lmp_features = 0
|
|
361
379
|
while page_number <= max_page_number:
|
|
362
|
-
|
|
380
|
+
response4 = await self.send_sync_command(
|
|
363
381
|
hci.HCI_Read_Local_Extended_Features_Command(
|
|
364
382
|
page_number=page_number
|
|
365
|
-
)
|
|
366
|
-
check_result=True,
|
|
383
|
+
)
|
|
367
384
|
)
|
|
368
385
|
lmp_features |= int.from_bytes(
|
|
369
|
-
|
|
386
|
+
response4.extended_lmp_features, 'little'
|
|
370
387
|
) << (64 * page_number)
|
|
371
|
-
max_page_number =
|
|
388
|
+
max_page_number = response4.maximum_page_number
|
|
372
389
|
page_number += 1
|
|
373
390
|
self.local_lmp_features = hci.LmpFeatureMask(lmp_features)
|
|
374
|
-
|
|
375
391
|
elif self.supports_command(hci.HCI_READ_LOCAL_SUPPORTED_FEATURES_COMMAND):
|
|
376
|
-
|
|
377
|
-
hci.HCI_Read_Local_Supported_Features_Command()
|
|
392
|
+
response5 = await self.send_sync_command(
|
|
393
|
+
hci.HCI_Read_Local_Supported_Features_Command()
|
|
378
394
|
)
|
|
379
395
|
self.local_lmp_features = hci.LmpFeatureMask(
|
|
380
|
-
int.from_bytes(
|
|
396
|
+
int.from_bytes(response5.lmp_features, 'little')
|
|
381
397
|
)
|
|
382
398
|
|
|
383
|
-
await self.
|
|
399
|
+
await self.send_sync_command(
|
|
384
400
|
hci.HCI_Set_Event_Mask_Command(
|
|
385
401
|
event_mask=hci.HCI_Set_Event_Mask_Command.mask(
|
|
386
402
|
[
|
|
@@ -437,7 +453,7 @@ class Host(utils.EventEmitter):
|
|
|
437
453
|
)
|
|
438
454
|
)
|
|
439
455
|
if self.supports_command(hci.HCI_SET_EVENT_MASK_PAGE_2_COMMAND):
|
|
440
|
-
await self.
|
|
456
|
+
await self.send_sync_command(
|
|
441
457
|
hci.HCI_Set_Event_Mask_Page_2_Command(
|
|
442
458
|
event_mask_page_2=hci.HCI_Set_Event_Mask_Page_2_Command.mask(
|
|
443
459
|
[hci.HCI_ENCRYPTION_CHANGE_V2_EVENT]
|
|
@@ -490,29 +506,28 @@ class Host(utils.EventEmitter):
|
|
|
490
506
|
hci.HCI_LE_TRANSMIT_POWER_REPORTING_EVENT,
|
|
491
507
|
hci.HCI_LE_BIGINFO_ADVERTISING_REPORT_EVENT,
|
|
492
508
|
hci.HCI_LE_SUBRATE_CHANGE_EVENT,
|
|
509
|
+
hci.HCI_LE_READ_ALL_REMOTE_FEATURES_COMPLETE_EVENT,
|
|
493
510
|
hci.HCI_LE_CS_READ_REMOTE_SUPPORTED_CAPABILITIES_COMPLETE_EVENT,
|
|
494
511
|
hci.HCI_LE_CS_PROCEDURE_ENABLE_COMPLETE_EVENT,
|
|
495
512
|
hci.HCI_LE_CS_SECURITY_ENABLE_COMPLETE_EVENT,
|
|
496
513
|
hci.HCI_LE_CS_CONFIG_COMPLETE_EVENT,
|
|
497
514
|
hci.HCI_LE_CS_SUBEVENT_RESULT_EVENT,
|
|
498
515
|
hci.HCI_LE_CS_SUBEVENT_RESULT_CONTINUE_EVENT,
|
|
516
|
+
hci.HCI_LE_MONITORED_ADVERTISERS_REPORT_EVENT,
|
|
517
|
+
hci.HCI_LE_FRAME_SPACE_UPDATE_COMPLETE_EVENT,
|
|
518
|
+
hci.HCI_LE_UTP_RECEIVE_EVENT,
|
|
519
|
+
hci.HCI_LE_CONNECTION_RATE_CHANGE_EVENT,
|
|
499
520
|
]
|
|
500
521
|
)
|
|
501
522
|
|
|
502
|
-
await self.
|
|
523
|
+
await self.send_sync_command(
|
|
503
524
|
hci.HCI_LE_Set_Event_Mask_Command(le_event_mask=le_event_mask)
|
|
504
525
|
)
|
|
505
526
|
|
|
506
527
|
if self.supports_command(hci.HCI_READ_BUFFER_SIZE_COMMAND):
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
hc_acl_data_packet_length = (
|
|
511
|
-
response.return_parameters.hc_acl_data_packet_length
|
|
512
|
-
)
|
|
513
|
-
hc_total_num_acl_data_packets = (
|
|
514
|
-
response.return_parameters.hc_total_num_acl_data_packets
|
|
515
|
-
)
|
|
528
|
+
response6 = await self.send_sync_command(hci.HCI_Read_Buffer_Size_Command())
|
|
529
|
+
hc_acl_data_packet_length = response6.hc_acl_data_packet_length
|
|
530
|
+
hc_total_num_acl_data_packets = response6.hc_total_num_acl_data_packets
|
|
516
531
|
|
|
517
532
|
logger.debug(
|
|
518
533
|
'HCI ACL flow control: '
|
|
@@ -531,19 +546,13 @@ class Host(utils.EventEmitter):
|
|
|
531
546
|
iso_data_packet_length = 0
|
|
532
547
|
total_num_iso_data_packets = 0
|
|
533
548
|
if self.supports_command(hci.HCI_LE_READ_BUFFER_SIZE_V2_COMMAND):
|
|
534
|
-
|
|
535
|
-
hci.HCI_LE_Read_Buffer_Size_V2_Command()
|
|
536
|
-
)
|
|
537
|
-
le_acl_data_packet_length = (
|
|
538
|
-
response.return_parameters.le_acl_data_packet_length
|
|
539
|
-
)
|
|
540
|
-
total_num_le_acl_data_packets = (
|
|
541
|
-
response.return_parameters.total_num_le_acl_data_packets
|
|
542
|
-
)
|
|
543
|
-
iso_data_packet_length = response.return_parameters.iso_data_packet_length
|
|
544
|
-
total_num_iso_data_packets = (
|
|
545
|
-
response.return_parameters.total_num_iso_data_packets
|
|
549
|
+
response7 = await self.send_sync_command(
|
|
550
|
+
hci.HCI_LE_Read_Buffer_Size_V2_Command()
|
|
546
551
|
)
|
|
552
|
+
le_acl_data_packet_length = response7.le_acl_data_packet_length
|
|
553
|
+
total_num_le_acl_data_packets = response7.total_num_le_acl_data_packets
|
|
554
|
+
iso_data_packet_length = response7.iso_data_packet_length
|
|
555
|
+
total_num_iso_data_packets = response7.total_num_iso_data_packets
|
|
547
556
|
|
|
548
557
|
logger.debug(
|
|
549
558
|
'HCI LE flow control: '
|
|
@@ -553,15 +562,11 @@ class Host(utils.EventEmitter):
|
|
|
553
562
|
f'total_num_iso_data_packets={total_num_iso_data_packets}'
|
|
554
563
|
)
|
|
555
564
|
elif self.supports_command(hci.HCI_LE_READ_BUFFER_SIZE_COMMAND):
|
|
556
|
-
|
|
557
|
-
hci.HCI_LE_Read_Buffer_Size_Command()
|
|
558
|
-
)
|
|
559
|
-
le_acl_data_packet_length = (
|
|
560
|
-
response.return_parameters.le_acl_data_packet_length
|
|
561
|
-
)
|
|
562
|
-
total_num_le_acl_data_packets = (
|
|
563
|
-
response.return_parameters.total_num_le_acl_data_packets
|
|
565
|
+
response8 = await self.send_sync_command(
|
|
566
|
+
hci.HCI_LE_Read_Buffer_Size_Command()
|
|
564
567
|
)
|
|
568
|
+
le_acl_data_packet_length = response8.le_acl_data_packet_length
|
|
569
|
+
total_num_le_acl_data_packets = response8.total_num_le_acl_data_packets
|
|
565
570
|
|
|
566
571
|
logger.debug(
|
|
567
572
|
'HCI LE ACL flow control: '
|
|
@@ -592,16 +597,16 @@ class Host(utils.EventEmitter):
|
|
|
592
597
|
) and self.supports_command(
|
|
593
598
|
hci.HCI_LE_WRITE_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND
|
|
594
599
|
):
|
|
595
|
-
|
|
600
|
+
response9 = await self.send_sync_command(
|
|
596
601
|
hci.HCI_LE_Read_Suggested_Default_Data_Length_Command()
|
|
597
602
|
)
|
|
598
|
-
suggested_max_tx_octets =
|
|
599
|
-
suggested_max_tx_time =
|
|
603
|
+
suggested_max_tx_octets = response9.suggested_max_tx_octets
|
|
604
|
+
suggested_max_tx_time = response9.suggested_max_tx_time
|
|
600
605
|
if (
|
|
601
606
|
suggested_max_tx_octets != self.suggested_max_tx_octets
|
|
602
607
|
or suggested_max_tx_time != self.suggested_max_tx_time
|
|
603
608
|
):
|
|
604
|
-
await self.
|
|
609
|
+
await self.send_sync_command(
|
|
605
610
|
hci.HCI_LE_Write_Suggested_Default_Data_Length_Command(
|
|
606
611
|
suggested_max_tx_octets=self.suggested_max_tx_octets,
|
|
607
612
|
suggested_max_tx_time=self.suggested_max_tx_time,
|
|
@@ -611,24 +616,28 @@ class Host(utils.EventEmitter):
|
|
|
611
616
|
if self.supports_command(
|
|
612
617
|
hci.HCI_LE_READ_NUMBER_OF_SUPPORTED_ADVERTISING_SETS_COMMAND
|
|
613
618
|
):
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
619
|
+
try:
|
|
620
|
+
response10 = await self.send_sync_command(
|
|
621
|
+
hci.HCI_LE_Read_Number_Of_Supported_Advertising_Sets_Command()
|
|
622
|
+
)
|
|
623
|
+
self.number_of_supported_advertising_sets = (
|
|
624
|
+
response10.num_supported_advertising_sets
|
|
625
|
+
)
|
|
626
|
+
except hci.HCI_Error:
|
|
627
|
+
logger.warning('Failed to read number of supported advertising sets')
|
|
621
628
|
|
|
622
629
|
if self.supports_command(
|
|
623
630
|
hci.HCI_LE_READ_MAXIMUM_ADVERTISING_DATA_LENGTH_COMMAND
|
|
624
631
|
):
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
+
try:
|
|
633
|
+
response11 = await self.send_sync_command(
|
|
634
|
+
hci.HCI_LE_Read_Maximum_Advertising_Data_Length_Command()
|
|
635
|
+
)
|
|
636
|
+
self.maximum_advertising_data_length = (
|
|
637
|
+
response11.max_advertising_data_length
|
|
638
|
+
)
|
|
639
|
+
except hci.HCI_Error:
|
|
640
|
+
logger.warning('Failed to read maximum advertising data length')
|
|
632
641
|
|
|
633
642
|
@property
|
|
634
643
|
def controller(self) -> TransportSink | None:
|
|
@@ -654,56 +663,175 @@ class Host(utils.EventEmitter):
|
|
|
654
663
|
if self.hci_sink:
|
|
655
664
|
self.hci_sink.on_packet(bytes(packet))
|
|
656
665
|
|
|
657
|
-
async def
|
|
658
|
-
self,
|
|
659
|
-
|
|
666
|
+
async def _send_command(
|
|
667
|
+
self,
|
|
668
|
+
command: hci.HCI_SyncCommand | hci.HCI_AsyncCommand,
|
|
669
|
+
response_timeout: float | None = None,
|
|
670
|
+
) -> hci.HCI_Command_Complete_Event | hci.HCI_Command_Status_Event:
|
|
660
671
|
# Wait until we can send (only one pending command at a time)
|
|
661
|
-
|
|
662
|
-
assert self.pending_command is None
|
|
663
|
-
assert self.pending_response is None
|
|
672
|
+
await self.command_semaphore.acquire()
|
|
664
673
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
674
|
+
# Create a future value to hold the eventual response
|
|
675
|
+
assert self.pending_command is None
|
|
676
|
+
assert self.pending_response is None
|
|
677
|
+
self.pending_response = asyncio.get_running_loop().create_future()
|
|
678
|
+
self.pending_command = command
|
|
668
679
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
680
|
+
response: (
|
|
681
|
+
hci.HCI_Command_Complete_Event | hci.HCI_Command_Status_Event | None
|
|
682
|
+
) = None
|
|
683
|
+
try:
|
|
684
|
+
self.send_hci_packet(command)
|
|
685
|
+
response = await asyncio.wait_for(
|
|
686
|
+
self.pending_response, timeout=response_timeout
|
|
687
|
+
)
|
|
688
|
+
return response
|
|
689
|
+
except Exception:
|
|
690
|
+
logger.exception(color("!!! Exception while sending command:", "red"))
|
|
691
|
+
raise
|
|
692
|
+
finally:
|
|
693
|
+
self.pending_command = None
|
|
694
|
+
self.pending_response = None
|
|
695
|
+
if (
|
|
696
|
+
response is not None
|
|
697
|
+
and response.num_hci_command_packets
|
|
698
|
+
and self.command_semaphore.locked()
|
|
699
|
+
):
|
|
700
|
+
self.command_semaphore.release()
|
|
701
|
+
|
|
702
|
+
@overload
|
|
703
|
+
async def send_command(
|
|
704
|
+
self,
|
|
705
|
+
command: hci.HCI_SyncCommand[_RP],
|
|
706
|
+
check_result: bool = False,
|
|
707
|
+
response_timeout: float | None = None,
|
|
708
|
+
) -> hci.HCI_Command_Complete_Event[_RP]: ...
|
|
709
|
+
|
|
710
|
+
@overload
|
|
711
|
+
async def send_command(
|
|
712
|
+
self,
|
|
713
|
+
command: hci.HCI_AsyncCommand,
|
|
714
|
+
check_result: bool = False,
|
|
715
|
+
response_timeout: float | None = None,
|
|
716
|
+
) -> hci.HCI_Command_Status_Event: ...
|
|
717
|
+
|
|
718
|
+
async def send_command(
|
|
719
|
+
self,
|
|
720
|
+
command: hci.HCI_SyncCommand[_RP] | hci.HCI_AsyncCommand,
|
|
721
|
+
check_result: bool = False,
|
|
722
|
+
response_timeout: float | None = None,
|
|
723
|
+
) -> hci.HCI_Command_Complete_Event[_RP] | hci.HCI_Command_Status_Event:
|
|
724
|
+
response = await self._send_command(command, response_timeout)
|
|
725
|
+
|
|
726
|
+
# Check the return parameters if required
|
|
727
|
+
if check_result:
|
|
728
|
+
if isinstance(response, hci.HCI_Command_Status_Event):
|
|
729
|
+
status = response.status # type: ignore[attr-defined]
|
|
730
|
+
elif isinstance(response.return_parameters, int):
|
|
731
|
+
status = response.return_parameters
|
|
732
|
+
elif isinstance(response.return_parameters, bytes):
|
|
733
|
+
# return parameters first field is a one byte status code
|
|
734
|
+
status = response.return_parameters[0]
|
|
735
|
+
elif isinstance(
|
|
736
|
+
response.return_parameters, hci.HCI_GenericReturnParameters
|
|
737
|
+
):
|
|
738
|
+
# FIXME: temporary workaround
|
|
739
|
+
# NO STATUS
|
|
740
|
+
status = hci.HCI_SUCCESS
|
|
741
|
+
else:
|
|
742
|
+
status = response.return_parameters.status
|
|
743
|
+
|
|
744
|
+
if status != hci.HCI_SUCCESS:
|
|
745
|
+
logger.warning(
|
|
746
|
+
f'{command.name} failed ' f'({hci.HCI_Constant.error_name(status)})'
|
|
747
|
+
)
|
|
748
|
+
raise hci.HCI_Error(status)
|
|
749
|
+
|
|
750
|
+
return response
|
|
751
|
+
|
|
752
|
+
async def send_sync_command(
|
|
753
|
+
self, command: hci.HCI_SyncCommand[_RP], response_timeout: float | None = None
|
|
754
|
+
) -> _RP:
|
|
755
|
+
response = await self.send_sync_command_raw(command, response_timeout)
|
|
756
|
+
return_parameters = response.return_parameters
|
|
757
|
+
|
|
758
|
+
# Check the return parameters's status
|
|
759
|
+
if isinstance(return_parameters, hci.HCI_StatusReturnParameters):
|
|
760
|
+
status = return_parameters.status
|
|
761
|
+
elif isinstance(return_parameters, hci.HCI_GenericReturnParameters):
|
|
762
|
+
# if the payload has at least one byte, assume the first byte is the status
|
|
763
|
+
if not return_parameters.data:
|
|
764
|
+
raise RuntimeError('no status byte in return parameters')
|
|
765
|
+
status = hci.HCI_ErrorCode(return_parameters.data[0])
|
|
766
|
+
else:
|
|
767
|
+
raise RuntimeError(
|
|
768
|
+
f'unexpected return parameters type ({type(return_parameters)})'
|
|
769
|
+
)
|
|
770
|
+
if status != hci.HCI_ErrorCode.SUCCESS:
|
|
771
|
+
logger.warning(
|
|
772
|
+
f'{command.name} failed ' f'({hci.HCI_Constant.error_name(status)})'
|
|
773
|
+
)
|
|
774
|
+
raise hci.HCI_Error(status)
|
|
775
|
+
|
|
776
|
+
return return_parameters
|
|
777
|
+
|
|
778
|
+
async def send_sync_command_raw(
|
|
779
|
+
self,
|
|
780
|
+
command: hci.HCI_SyncCommand[_RP],
|
|
781
|
+
response_timeout: float | None = None,
|
|
782
|
+
) -> hci.HCI_Command_Complete_Event[_RP]:
|
|
783
|
+
response = await self._send_command(command, response_timeout)
|
|
784
|
+
|
|
785
|
+
# For unknown HCI commands, some controllers return Command Status instead of
|
|
786
|
+
# Command Complete.
|
|
787
|
+
if (
|
|
788
|
+
isinstance(response, hci.HCI_Command_Status_Event)
|
|
789
|
+
and response.status == hci.HCI_ErrorCode.UNKNOWN_HCI_COMMAND_ERROR
|
|
790
|
+
):
|
|
791
|
+
return hci.HCI_Command_Complete_Event(
|
|
792
|
+
num_hci_command_packets=response.num_hci_command_packets,
|
|
793
|
+
command_opcode=command.op_code,
|
|
794
|
+
return_parameters=hci.HCI_StatusReturnParameters(
|
|
795
|
+
status=hci.HCI_ErrorCode(response.status)
|
|
796
|
+
), # type: ignore
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
# Check that the response is of the expected type
|
|
800
|
+
assert isinstance(response, hci.HCI_Command_Complete_Event)
|
|
801
|
+
|
|
802
|
+
return response
|
|
803
|
+
|
|
804
|
+
async def send_async_command(
|
|
805
|
+
self,
|
|
806
|
+
command: hci.HCI_AsyncCommand,
|
|
807
|
+
check_status: bool = True,
|
|
808
|
+
response_timeout: float | None = None,
|
|
809
|
+
) -> hci.HCI_ErrorCode:
|
|
810
|
+
response = await self._send_command(command, response_timeout)
|
|
811
|
+
|
|
812
|
+
# For unknown HCI commands, some controllers return Command Complete instead of
|
|
813
|
+
# Command Status.
|
|
814
|
+
if isinstance(response, hci.HCI_Command_Complete_Event):
|
|
815
|
+
# Assume the first byte of the return parameters is the status
|
|
816
|
+
if (
|
|
817
|
+
status := hci.HCI_ErrorCode(response.parameters[3])
|
|
818
|
+
) != hci.HCI_ErrorCode.UNKNOWN_HCI_COMMAND_ERROR:
|
|
819
|
+
logger.warning(f'unexpected return paramerers status {status}')
|
|
820
|
+
else:
|
|
821
|
+
assert isinstance(response, hci.HCI_Command_Status_Event)
|
|
822
|
+
status = hci.HCI_ErrorCode(response.status)
|
|
823
|
+
|
|
824
|
+
# Check the status if required
|
|
825
|
+
if check_status:
|
|
826
|
+
if status != hci.HCI_CommandStatus.PENDING:
|
|
827
|
+
logger.warning(f'{command.name} failed ' f'({status.name})')
|
|
828
|
+
raise hci.HCI_Error(status)
|
|
829
|
+
|
|
830
|
+
return status
|
|
831
|
+
|
|
832
|
+
@utils.deprecated("Use utils.AsyncRunner.spawn() instead.")
|
|
833
|
+
def send_command_sync(self, command: hci.HCI_AsyncCommand) -> None:
|
|
834
|
+
utils.AsyncRunner.spawn(self.send_async_command(command))
|
|
707
835
|
|
|
708
836
|
def send_acl_sdu(self, connection_handle: int, sdu: bytes) -> None:
|
|
709
837
|
if not (connection := self.connections.get(connection_handle)):
|
|
@@ -728,10 +856,22 @@ class Host(utils.EventEmitter):
|
|
|
728
856
|
data=pdu,
|
|
729
857
|
)
|
|
730
858
|
logger.debug(
|
|
731
|
-
'>>> ACL packet enqueue: (
|
|
859
|
+
'>>> ACL packet enqueue: (handle=0x%04X) %s',
|
|
860
|
+
connection_handle,
|
|
861
|
+
pdu.hex(),
|
|
732
862
|
)
|
|
733
863
|
packet_queue.enqueue(acl_packet, connection_handle)
|
|
734
864
|
|
|
865
|
+
def send_sco_sdu(self, connection_handle: int, sdu: bytes) -> None:
|
|
866
|
+
self.send_hci_packet(
|
|
867
|
+
hci.HCI_SynchronousDataPacket(
|
|
868
|
+
connection_handle=connection_handle,
|
|
869
|
+
packet_status=0,
|
|
870
|
+
data_total_length=len(sdu),
|
|
871
|
+
data=sdu,
|
|
872
|
+
)
|
|
873
|
+
)
|
|
874
|
+
|
|
735
875
|
def send_l2cap_pdu(self, connection_handle: int, cid: int, pdu: bytes) -> None:
|
|
736
876
|
self.send_acl_sdu(connection_handle, bytes(L2CAP_PDU(cid, pdu)))
|
|
737
877
|
|
|
@@ -816,16 +956,18 @@ class Host(utils.EventEmitter):
|
|
|
816
956
|
if self.local_supported_commands & mask
|
|
817
957
|
)
|
|
818
958
|
|
|
819
|
-
def supports_le_features(self,
|
|
820
|
-
return (self.local_le_features &
|
|
959
|
+
def supports_le_features(self, features: hci.LeFeatureMask) -> bool:
|
|
960
|
+
return (self.local_le_features & features) == features
|
|
821
961
|
|
|
822
|
-
def supports_lmp_features(self,
|
|
823
|
-
return self.local_lmp_features & (
|
|
962
|
+
def supports_lmp_features(self, features: hci.LmpFeatureMask) -> bool:
|
|
963
|
+
return self.local_lmp_features & (features) == features
|
|
824
964
|
|
|
825
965
|
@property
|
|
826
|
-
def supported_le_features(self):
|
|
966
|
+
def supported_le_features(self) -> list[hci.LeFeature]:
|
|
827
967
|
return [
|
|
828
|
-
feature
|
|
968
|
+
feature
|
|
969
|
+
for feature in hci.LeFeature
|
|
970
|
+
if self.local_le_features & (1 << feature)
|
|
829
971
|
]
|
|
830
972
|
|
|
831
973
|
# Packet Sink protocol (packets coming from the controller via HCI)
|
|
@@ -914,6 +1056,8 @@ class Host(utils.EventEmitter):
|
|
|
914
1056
|
self.pending_response.set_result(event)
|
|
915
1057
|
else:
|
|
916
1058
|
logger.warning('!!! no pending response future to set')
|
|
1059
|
+
if event.num_hci_command_packets and self.command_semaphore.locked():
|
|
1060
|
+
self.command_semaphore.release()
|
|
917
1061
|
|
|
918
1062
|
############################################################
|
|
919
1063
|
# HCI handlers
|
|
@@ -925,7 +1069,13 @@ class Host(utils.EventEmitter):
|
|
|
925
1069
|
if event.command_opcode == 0:
|
|
926
1070
|
# This is used just for the Num_HCI_Command_Packets field, not related to
|
|
927
1071
|
# an actual command
|
|
928
|
-
logger.debug('no-command event')
|
|
1072
|
+
logger.debug('no-command event for flow control')
|
|
1073
|
+
|
|
1074
|
+
# Release the command semaphore if needed
|
|
1075
|
+
if event.num_hci_command_packets and self.command_semaphore.locked():
|
|
1076
|
+
logger.debug('command complete event releasing semaphore')
|
|
1077
|
+
self.command_semaphore.release()
|
|
1078
|
+
|
|
929
1079
|
return
|
|
930
1080
|
|
|
931
1081
|
return self.on_command_processed(event)
|
|
@@ -1106,7 +1256,7 @@ class Host(utils.EventEmitter):
|
|
|
1106
1256
|
self, event: hci.HCI_LE_Connection_Update_Complete_Event
|
|
1107
1257
|
):
|
|
1108
1258
|
if (connection := self.connections.get(event.connection_handle)) is None:
|
|
1109
|
-
logger.warning('!!! CONNECTION
|
|
1259
|
+
logger.warning('!!! CONNECTION UPDATE COMPLETE: unknown handle')
|
|
1110
1260
|
return
|
|
1111
1261
|
|
|
1112
1262
|
# Notify the client
|
|
@@ -1123,6 +1273,29 @@ class Host(utils.EventEmitter):
|
|
|
1123
1273
|
'connection_parameters_update_failure', connection.handle, event.status
|
|
1124
1274
|
)
|
|
1125
1275
|
|
|
1276
|
+
def on_hci_le_connection_rate_change_event(
|
|
1277
|
+
self, event: hci.HCI_LE_Connection_Rate_Change_Event
|
|
1278
|
+
):
|
|
1279
|
+
if (connection := self.connections.get(event.connection_handle)) is None:
|
|
1280
|
+
logger.warning('!!! CONNECTION RATE CHANGE: unknown handle')
|
|
1281
|
+
return
|
|
1282
|
+
|
|
1283
|
+
# Notify the client
|
|
1284
|
+
if event.status == hci.HCI_SUCCESS:
|
|
1285
|
+
self.emit(
|
|
1286
|
+
'le_connection_rate_change',
|
|
1287
|
+
connection.handle,
|
|
1288
|
+
event.connection_interval,
|
|
1289
|
+
event.subrate_factor,
|
|
1290
|
+
event.peripheral_latency,
|
|
1291
|
+
event.continuation_number,
|
|
1292
|
+
event.supervision_timeout,
|
|
1293
|
+
)
|
|
1294
|
+
else:
|
|
1295
|
+
self.emit(
|
|
1296
|
+
'le_connection_rate_change_failure', connection.handle, event.status
|
|
1297
|
+
)
|
|
1298
|
+
|
|
1126
1299
|
def on_hci_le_phy_update_complete_event(
|
|
1127
1300
|
self, event: hci.HCI_LE_PHY_Update_Complete_Event
|
|
1128
1301
|
):
|
|
@@ -1338,15 +1511,17 @@ class Host(utils.EventEmitter):
|
|
|
1338
1511
|
|
|
1339
1512
|
# For now, just accept everything
|
|
1340
1513
|
# TODO: delegate the decision
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1514
|
+
utils.AsyncRunner.spawn(
|
|
1515
|
+
self.send_sync_command(
|
|
1516
|
+
hci.HCI_LE_Remote_Connection_Parameter_Request_Reply_Command(
|
|
1517
|
+
connection_handle=event.connection_handle,
|
|
1518
|
+
interval_min=event.interval_min,
|
|
1519
|
+
interval_max=event.interval_max,
|
|
1520
|
+
max_latency=event.max_latency,
|
|
1521
|
+
timeout=event.timeout,
|
|
1522
|
+
min_ce_length=0,
|
|
1523
|
+
max_ce_length=0,
|
|
1524
|
+
)
|
|
1350
1525
|
)
|
|
1351
1526
|
)
|
|
1352
1527
|
|
|
@@ -1382,9 +1557,9 @@ class Host(utils.EventEmitter):
|
|
|
1382
1557
|
connection_handle=event.connection_handle
|
|
1383
1558
|
)
|
|
1384
1559
|
|
|
1385
|
-
await self.
|
|
1560
|
+
await self.send_sync_command(response)
|
|
1386
1561
|
|
|
1387
|
-
|
|
1562
|
+
utils.AsyncRunner.spawn(send_long_term_key())
|
|
1388
1563
|
|
|
1389
1564
|
def on_hci_synchronous_connection_complete_event(
|
|
1390
1565
|
self, event: hci.HCI_Synchronous_Connection_Complete_Event
|
|
@@ -1583,9 +1758,9 @@ class Host(utils.EventEmitter):
|
|
|
1583
1758
|
bd_addr=event.bd_addr
|
|
1584
1759
|
)
|
|
1585
1760
|
|
|
1586
|
-
await self.
|
|
1761
|
+
await self.send_sync_command(response)
|
|
1587
1762
|
|
|
1588
|
-
|
|
1763
|
+
utils.AsyncRunner.spawn(send_link_key())
|
|
1589
1764
|
|
|
1590
1765
|
def on_hci_io_capability_request_event(
|
|
1591
1766
|
self, event: hci.HCI_IO_Capability_Request_Event
|
|
@@ -1680,12 +1855,13 @@ class Host(utils.EventEmitter):
|
|
|
1680
1855
|
self.emit(
|
|
1681
1856
|
'le_remote_features_failure', event.connection_handle, event.status
|
|
1682
1857
|
)
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
)
|
|
1858
|
+
return
|
|
1859
|
+
|
|
1860
|
+
self.emit(
|
|
1861
|
+
'le_remote_features',
|
|
1862
|
+
event.connection_handle,
|
|
1863
|
+
hci.LeFeatureMask(int.from_bytes(event.le_features, 'little')),
|
|
1864
|
+
)
|
|
1689
1865
|
|
|
1690
1866
|
def on_hci_le_cs_read_remote_supported_capabilities_complete_event(
|
|
1691
1867
|
self, event: hci.HCI_LE_CS_Read_Remote_Supported_Capabilities_Complete_Event
|
|
@@ -1718,6 +1894,12 @@ class Host(utils.EventEmitter):
|
|
|
1718
1894
|
self.emit('cs_subevent_result_continue', event)
|
|
1719
1895
|
|
|
1720
1896
|
def on_hci_le_subrate_change_event(self, event: hci.HCI_LE_Subrate_Change_Event):
|
|
1897
|
+
if event.status != hci.HCI_SUCCESS:
|
|
1898
|
+
self.emit(
|
|
1899
|
+
'le_subrate_change_failure', event.connection_handle, event.status
|
|
1900
|
+
)
|
|
1901
|
+
return
|
|
1902
|
+
|
|
1721
1903
|
self.emit(
|
|
1722
1904
|
'le_subrate_change',
|
|
1723
1905
|
event.connection_handle,
|