bumble 0.0.223__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/avrcp.py +366 -190
- bumble/bridge.py +10 -2
- bumble/controller.py +14 -1
- bumble/core.py +1 -1
- bumble/device.py +998 -573
- bumble/drivers/intel.py +45 -39
- bumble/drivers/rtk.py +76 -40
- bumble/hci.py +1318 -796
- bumble/host.py +329 -157
- bumble/l2cap.py +10 -5
- 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.223.dist-info → bumble-0.0.224.dist-info}/METADATA +4 -3
- {bumble-0.0.223.dist-info → bumble-0.0.224.dist-info}/RECORD +30 -30
- {bumble-0.0.223.dist-info → bumble-0.0.224.dist-info}/WHEEL +1 -1
- {bumble-0.0.223.dist-info → bumble-0.0.224.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.223.dist-info → bumble-0.0.224.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.223.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,7 +856,9 @@ 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
|
|
|
@@ -826,16 +956,18 @@ class Host(utils.EventEmitter):
|
|
|
826
956
|
if self.local_supported_commands & mask
|
|
827
957
|
)
|
|
828
958
|
|
|
829
|
-
def supports_le_features(self,
|
|
830
|
-
return (self.local_le_features &
|
|
959
|
+
def supports_le_features(self, features: hci.LeFeatureMask) -> bool:
|
|
960
|
+
return (self.local_le_features & features) == features
|
|
831
961
|
|
|
832
|
-
def supports_lmp_features(self,
|
|
833
|
-
return self.local_lmp_features & (
|
|
962
|
+
def supports_lmp_features(self, features: hci.LmpFeatureMask) -> bool:
|
|
963
|
+
return self.local_lmp_features & (features) == features
|
|
834
964
|
|
|
835
965
|
@property
|
|
836
|
-
def supported_le_features(self):
|
|
966
|
+
def supported_le_features(self) -> list[hci.LeFeature]:
|
|
837
967
|
return [
|
|
838
|
-
feature
|
|
968
|
+
feature
|
|
969
|
+
for feature in hci.LeFeature
|
|
970
|
+
if self.local_le_features & (1 << feature)
|
|
839
971
|
]
|
|
840
972
|
|
|
841
973
|
# Packet Sink protocol (packets coming from the controller via HCI)
|
|
@@ -924,6 +1056,8 @@ class Host(utils.EventEmitter):
|
|
|
924
1056
|
self.pending_response.set_result(event)
|
|
925
1057
|
else:
|
|
926
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()
|
|
927
1061
|
|
|
928
1062
|
############################################################
|
|
929
1063
|
# HCI handlers
|
|
@@ -935,7 +1069,13 @@ class Host(utils.EventEmitter):
|
|
|
935
1069
|
if event.command_opcode == 0:
|
|
936
1070
|
# This is used just for the Num_HCI_Command_Packets field, not related to
|
|
937
1071
|
# an actual command
|
|
938
|
-
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
|
+
|
|
939
1079
|
return
|
|
940
1080
|
|
|
941
1081
|
return self.on_command_processed(event)
|
|
@@ -1116,7 +1256,7 @@ class Host(utils.EventEmitter):
|
|
|
1116
1256
|
self, event: hci.HCI_LE_Connection_Update_Complete_Event
|
|
1117
1257
|
):
|
|
1118
1258
|
if (connection := self.connections.get(event.connection_handle)) is None:
|
|
1119
|
-
logger.warning('!!! CONNECTION
|
|
1259
|
+
logger.warning('!!! CONNECTION UPDATE COMPLETE: unknown handle')
|
|
1120
1260
|
return
|
|
1121
1261
|
|
|
1122
1262
|
# Notify the client
|
|
@@ -1133,6 +1273,29 @@ class Host(utils.EventEmitter):
|
|
|
1133
1273
|
'connection_parameters_update_failure', connection.handle, event.status
|
|
1134
1274
|
)
|
|
1135
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
|
+
|
|
1136
1299
|
def on_hci_le_phy_update_complete_event(
|
|
1137
1300
|
self, event: hci.HCI_LE_PHY_Update_Complete_Event
|
|
1138
1301
|
):
|
|
@@ -1348,15 +1511,17 @@ class Host(utils.EventEmitter):
|
|
|
1348
1511
|
|
|
1349
1512
|
# For now, just accept everything
|
|
1350
1513
|
# TODO: delegate the decision
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
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
|
+
)
|
|
1360
1525
|
)
|
|
1361
1526
|
)
|
|
1362
1527
|
|
|
@@ -1392,9 +1557,9 @@ class Host(utils.EventEmitter):
|
|
|
1392
1557
|
connection_handle=event.connection_handle
|
|
1393
1558
|
)
|
|
1394
1559
|
|
|
1395
|
-
await self.
|
|
1560
|
+
await self.send_sync_command(response)
|
|
1396
1561
|
|
|
1397
|
-
|
|
1562
|
+
utils.AsyncRunner.spawn(send_long_term_key())
|
|
1398
1563
|
|
|
1399
1564
|
def on_hci_synchronous_connection_complete_event(
|
|
1400
1565
|
self, event: hci.HCI_Synchronous_Connection_Complete_Event
|
|
@@ -1593,9 +1758,9 @@ class Host(utils.EventEmitter):
|
|
|
1593
1758
|
bd_addr=event.bd_addr
|
|
1594
1759
|
)
|
|
1595
1760
|
|
|
1596
|
-
await self.
|
|
1761
|
+
await self.send_sync_command(response)
|
|
1597
1762
|
|
|
1598
|
-
|
|
1763
|
+
utils.AsyncRunner.spawn(send_link_key())
|
|
1599
1764
|
|
|
1600
1765
|
def on_hci_io_capability_request_event(
|
|
1601
1766
|
self, event: hci.HCI_IO_Capability_Request_Event
|
|
@@ -1690,12 +1855,13 @@ class Host(utils.EventEmitter):
|
|
|
1690
1855
|
self.emit(
|
|
1691
1856
|
'le_remote_features_failure', event.connection_handle, event.status
|
|
1692
1857
|
)
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
)
|
|
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
|
+
)
|
|
1699
1865
|
|
|
1700
1866
|
def on_hci_le_cs_read_remote_supported_capabilities_complete_event(
|
|
1701
1867
|
self, event: hci.HCI_LE_CS_Read_Remote_Supported_Capabilities_Complete_Event
|
|
@@ -1728,6 +1894,12 @@ class Host(utils.EventEmitter):
|
|
|
1728
1894
|
self.emit('cs_subevent_result_continue', event)
|
|
1729
1895
|
|
|
1730
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
|
+
|
|
1731
1903
|
self.emit(
|
|
1732
1904
|
'le_subrate_change',
|
|
1733
1905
|
event.connection_handle,
|