bumble 0.0.179__py3-none-any.whl → 0.0.181__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/bench.py +110 -81
- bumble/apps/ble_rpa_tool.py +63 -0
- bumble/apps/console.py +4 -4
- bumble/apps/controller_info.py +34 -2
- bumble/apps/pair.py +6 -8
- bumble/att.py +53 -11
- bumble/controller.py +28 -1
- bumble/crypto.py +10 -0
- bumble/device.py +630 -134
- bumble/drivers/__init__.py +27 -31
- bumble/drivers/common.py +45 -0
- bumble/drivers/rtk.py +11 -4
- bumble/gatt.py +183 -58
- bumble/gatt_client.py +56 -20
- bumble/gatt_server.py +30 -22
- bumble/hci.py +227 -92
- bumble/helpers.py +81 -42
- bumble/hfp.py +37 -27
- bumble/hid.py +282 -61
- bumble/host.py +158 -93
- bumble/l2cap.py +11 -3
- bumble/profiles/asha_service.py +2 -2
- bumble/profiles/bap.py +1247 -0
- bumble/profiles/cap.py +52 -0
- bumble/profiles/csip.py +205 -0
- bumble/rfcomm.py +24 -17
- bumble/smp.py +1 -1
- bumble/transport/__init__.py +49 -21
- bumble/transport/android_emulator.py +1 -1
- bumble/transport/common.py +3 -2
- bumble/transport/hci_socket.py +1 -4
- bumble/transport/usb.py +1 -1
- bumble/utils.py +3 -6
- {bumble-0.0.179.dist-info → bumble-0.0.181.dist-info}/METADATA +1 -1
- {bumble-0.0.179.dist-info → bumble-0.0.181.dist-info}/RECORD +40 -35
- {bumble-0.0.179.dist-info → bumble-0.0.181.dist-info}/entry_points.txt +1 -0
- {bumble-0.0.179.dist-info → bumble-0.0.181.dist-info}/LICENSE +0 -0
- {bumble-0.0.179.dist-info → bumble-0.0.181.dist-info}/WHEEL +0 -0
- {bumble-0.0.179.dist-info → bumble-0.0.181.dist-info}/top_level.txt +0 -0
bumble/device.py
CHANGED
|
@@ -21,8 +21,9 @@ import functools
|
|
|
21
21
|
import json
|
|
22
22
|
import asyncio
|
|
23
23
|
import logging
|
|
24
|
-
from contextlib import asynccontextmanager, AsyncExitStack
|
|
24
|
+
from contextlib import asynccontextmanager, AsyncExitStack, closing
|
|
25
25
|
from dataclasses import dataclass
|
|
26
|
+
from collections.abc import Iterable
|
|
26
27
|
from typing import (
|
|
27
28
|
Any,
|
|
28
29
|
Callable,
|
|
@@ -32,6 +33,7 @@ from typing import (
|
|
|
32
33
|
Optional,
|
|
33
34
|
Tuple,
|
|
34
35
|
Type,
|
|
36
|
+
TypeVar,
|
|
35
37
|
Set,
|
|
36
38
|
Union,
|
|
37
39
|
cast,
|
|
@@ -47,6 +49,7 @@ from .hci import (
|
|
|
47
49
|
HCI_AUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P_256_TYPE,
|
|
48
50
|
HCI_CENTRAL_ROLE,
|
|
49
51
|
HCI_COMMAND_STATUS_PENDING,
|
|
52
|
+
HCI_CONNECTED_ISOCHRONOUS_STREAM_LE_SUPPORTED_FEATURE,
|
|
50
53
|
HCI_CONNECTION_REJECTED_DUE_TO_LIMITED_RESOURCES_ERROR,
|
|
51
54
|
HCI_DISPLAY_YES_NO_IO_CAPABILITY,
|
|
52
55
|
HCI_DISPLAY_ONLY_IO_CAPABILITY,
|
|
@@ -83,29 +86,35 @@ from .hci import (
|
|
|
83
86
|
HCI_Constant,
|
|
84
87
|
HCI_Create_Connection_Cancel_Command,
|
|
85
88
|
HCI_Create_Connection_Command,
|
|
89
|
+
HCI_Create_Connection_Command,
|
|
86
90
|
HCI_Disconnect_Command,
|
|
87
91
|
HCI_Encryption_Change_Event,
|
|
88
92
|
HCI_Error,
|
|
89
93
|
HCI_IO_Capability_Request_Reply_Command,
|
|
90
94
|
HCI_Inquiry_Cancel_Command,
|
|
91
95
|
HCI_Inquiry_Command,
|
|
96
|
+
HCI_IsoDataPacket,
|
|
97
|
+
HCI_LE_Accept_CIS_Request_Command,
|
|
92
98
|
HCI_LE_Add_Device_To_Resolving_List_Command,
|
|
93
99
|
HCI_LE_Advertising_Report_Event,
|
|
94
100
|
HCI_LE_Clear_Resolving_List_Command,
|
|
95
101
|
HCI_LE_Connection_Update_Command,
|
|
96
102
|
HCI_LE_Create_Connection_Cancel_Command,
|
|
97
103
|
HCI_LE_Create_Connection_Command,
|
|
104
|
+
HCI_LE_Create_CIS_Command,
|
|
98
105
|
HCI_LE_Enable_Encryption_Command,
|
|
99
106
|
HCI_LE_Extended_Advertising_Report_Event,
|
|
100
107
|
HCI_LE_Extended_Create_Connection_Command,
|
|
101
108
|
HCI_LE_Rand_Command,
|
|
102
109
|
HCI_LE_Read_PHY_Command,
|
|
110
|
+
HCI_LE_Reject_CIS_Request_Command,
|
|
103
111
|
HCI_LE_Remove_Advertising_Set_Command,
|
|
104
112
|
HCI_LE_Set_Address_Resolution_Enable_Command,
|
|
105
113
|
HCI_LE_Set_Advertising_Data_Command,
|
|
106
114
|
HCI_LE_Set_Advertising_Enable_Command,
|
|
107
115
|
HCI_LE_Set_Advertising_Parameters_Command,
|
|
108
116
|
HCI_LE_Set_Advertising_Set_Random_Address_Command,
|
|
117
|
+
HCI_LE_Set_CIG_Parameters_Command,
|
|
109
118
|
HCI_LE_Set_Data_Length_Command,
|
|
110
119
|
HCI_LE_Set_Default_PHY_Command,
|
|
111
120
|
HCI_LE_Set_Extended_Scan_Enable_Command,
|
|
@@ -114,6 +123,7 @@ from .hci import (
|
|
|
114
123
|
HCI_LE_Set_Extended_Advertising_Data_Command,
|
|
115
124
|
HCI_LE_Set_Extended_Advertising_Enable_Command,
|
|
116
125
|
HCI_LE_Set_Extended_Advertising_Parameters_Command,
|
|
126
|
+
HCI_LE_Set_Host_Feature_Command,
|
|
117
127
|
HCI_LE_Set_PHY_Command,
|
|
118
128
|
HCI_LE_Set_Random_Address_Command,
|
|
119
129
|
HCI_LE_Set_Scan_Enable_Command,
|
|
@@ -128,6 +138,7 @@ from .hci import (
|
|
|
128
138
|
HCI_Switch_Role_Command,
|
|
129
139
|
HCI_Set_Connection_Encryption_Command,
|
|
130
140
|
HCI_StatusError,
|
|
141
|
+
HCI_SynchronousDataPacket,
|
|
131
142
|
HCI_User_Confirmation_Request_Negative_Reply_Command,
|
|
132
143
|
HCI_User_Confirmation_Request_Reply_Command,
|
|
133
144
|
HCI_User_Passkey_Request_Negative_Reply_Command,
|
|
@@ -159,6 +170,7 @@ from .core import (
|
|
|
159
170
|
from .utils import (
|
|
160
171
|
AsyncRunner,
|
|
161
172
|
CompositeEventEmitter,
|
|
173
|
+
EventWatcher,
|
|
162
174
|
setup_event_forwarding,
|
|
163
175
|
composite_listener,
|
|
164
176
|
deprecated,
|
|
@@ -425,6 +437,38 @@ class AdvertisingType(IntEnum):
|
|
|
425
437
|
)
|
|
426
438
|
|
|
427
439
|
|
|
440
|
+
# -----------------------------------------------------------------------------
|
|
441
|
+
@dataclass
|
|
442
|
+
class LegacyAdvertiser:
|
|
443
|
+
device: Device
|
|
444
|
+
advertising_type: AdvertisingType
|
|
445
|
+
own_address_type: OwnAddressType
|
|
446
|
+
auto_restart: bool
|
|
447
|
+
advertising_data: Optional[bytes]
|
|
448
|
+
scan_response_data: Optional[bytes]
|
|
449
|
+
|
|
450
|
+
async def stop(self) -> None:
|
|
451
|
+
await self.device.stop_legacy_advertising()
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
# -----------------------------------------------------------------------------
|
|
455
|
+
@dataclass
|
|
456
|
+
class ExtendedAdvertiser(CompositeEventEmitter):
|
|
457
|
+
device: Device
|
|
458
|
+
handle: int
|
|
459
|
+
advertising_properties: HCI_LE_Set_Extended_Advertising_Parameters_Command.AdvertisingProperties
|
|
460
|
+
own_address_type: OwnAddressType
|
|
461
|
+
auto_restart: bool
|
|
462
|
+
advertising_data: Optional[bytes]
|
|
463
|
+
scan_response_data: Optional[bytes]
|
|
464
|
+
|
|
465
|
+
def __post_init__(self) -> None:
|
|
466
|
+
super().__init__()
|
|
467
|
+
|
|
468
|
+
async def stop(self) -> None:
|
|
469
|
+
await self.device.stop_extended_advertising(self.handle)
|
|
470
|
+
|
|
471
|
+
|
|
428
472
|
# -----------------------------------------------------------------------------
|
|
429
473
|
class LePhyOptions:
|
|
430
474
|
# Coded PHY preference
|
|
@@ -440,8 +484,11 @@ class LePhyOptions:
|
|
|
440
484
|
|
|
441
485
|
|
|
442
486
|
# -----------------------------------------------------------------------------
|
|
487
|
+
_PROXY_CLASS = TypeVar('_PROXY_CLASS', bound=gatt_client.ProfileServiceProxy)
|
|
488
|
+
|
|
489
|
+
|
|
443
490
|
class Peer:
|
|
444
|
-
def __init__(self, connection):
|
|
491
|
+
def __init__(self, connection: Connection) -> None:
|
|
445
492
|
self.connection = connection
|
|
446
493
|
|
|
447
494
|
# Create a GATT client for the connection
|
|
@@ -449,77 +496,113 @@ class Peer:
|
|
|
449
496
|
connection.gatt_client = self.gatt_client
|
|
450
497
|
|
|
451
498
|
@property
|
|
452
|
-
def services(self):
|
|
499
|
+
def services(self) -> List[gatt_client.ServiceProxy]:
|
|
453
500
|
return self.gatt_client.services
|
|
454
501
|
|
|
455
|
-
async def request_mtu(self, mtu):
|
|
502
|
+
async def request_mtu(self, mtu: int) -> int:
|
|
456
503
|
mtu = await self.gatt_client.request_mtu(mtu)
|
|
457
504
|
self.connection.emit('connection_att_mtu_update')
|
|
458
505
|
return mtu
|
|
459
506
|
|
|
460
|
-
async def discover_service(
|
|
507
|
+
async def discover_service(
|
|
508
|
+
self, uuid: Union[core.UUID, str]
|
|
509
|
+
) -> List[gatt_client.ServiceProxy]:
|
|
461
510
|
return await self.gatt_client.discover_service(uuid)
|
|
462
511
|
|
|
463
|
-
async def discover_services(
|
|
512
|
+
async def discover_services(
|
|
513
|
+
self, uuids: Iterable[core.UUID] = ()
|
|
514
|
+
) -> List[gatt_client.ServiceProxy]:
|
|
464
515
|
return await self.gatt_client.discover_services(uuids)
|
|
465
516
|
|
|
466
|
-
async def discover_included_services(
|
|
517
|
+
async def discover_included_services(
|
|
518
|
+
self, service: gatt_client.ServiceProxy
|
|
519
|
+
) -> List[gatt_client.ServiceProxy]:
|
|
467
520
|
return await self.gatt_client.discover_included_services(service)
|
|
468
521
|
|
|
469
|
-
async def discover_characteristics(
|
|
522
|
+
async def discover_characteristics(
|
|
523
|
+
self,
|
|
524
|
+
uuids: Iterable[Union[core.UUID, str]] = (),
|
|
525
|
+
service: Optional[gatt_client.ServiceProxy] = None,
|
|
526
|
+
) -> List[gatt_client.CharacteristicProxy]:
|
|
470
527
|
return await self.gatt_client.discover_characteristics(
|
|
471
528
|
uuids=uuids, service=service
|
|
472
529
|
)
|
|
473
530
|
|
|
474
531
|
async def discover_descriptors(
|
|
475
|
-
self,
|
|
532
|
+
self,
|
|
533
|
+
characteristic: Optional[gatt_client.CharacteristicProxy] = None,
|
|
534
|
+
start_handle: Optional[int] = None,
|
|
535
|
+
end_handle: Optional[int] = None,
|
|
476
536
|
):
|
|
477
537
|
return await self.gatt_client.discover_descriptors(
|
|
478
538
|
characteristic, start_handle, end_handle
|
|
479
539
|
)
|
|
480
540
|
|
|
481
|
-
async def discover_attributes(self):
|
|
541
|
+
async def discover_attributes(self) -> List[gatt_client.AttributeProxy]:
|
|
482
542
|
return await self.gatt_client.discover_attributes()
|
|
483
543
|
|
|
484
|
-
async def subscribe(
|
|
544
|
+
async def subscribe(
|
|
545
|
+
self,
|
|
546
|
+
characteristic: gatt_client.CharacteristicProxy,
|
|
547
|
+
subscriber: Optional[Callable[[bytes], Any]] = None,
|
|
548
|
+
prefer_notify: bool = True,
|
|
549
|
+
) -> None:
|
|
485
550
|
return await self.gatt_client.subscribe(
|
|
486
551
|
characteristic, subscriber, prefer_notify
|
|
487
552
|
)
|
|
488
553
|
|
|
489
|
-
async def unsubscribe(
|
|
554
|
+
async def unsubscribe(
|
|
555
|
+
self,
|
|
556
|
+
characteristic: gatt_client.CharacteristicProxy,
|
|
557
|
+
subscriber: Optional[Callable[[bytes], Any]] = None,
|
|
558
|
+
) -> None:
|
|
490
559
|
return await self.gatt_client.unsubscribe(characteristic, subscriber)
|
|
491
560
|
|
|
492
|
-
async def read_value(
|
|
561
|
+
async def read_value(
|
|
562
|
+
self, attribute: Union[int, gatt_client.AttributeProxy]
|
|
563
|
+
) -> bytes:
|
|
493
564
|
return await self.gatt_client.read_value(attribute)
|
|
494
565
|
|
|
495
|
-
async def write_value(
|
|
566
|
+
async def write_value(
|
|
567
|
+
self,
|
|
568
|
+
attribute: Union[int, gatt_client.AttributeProxy],
|
|
569
|
+
value: bytes,
|
|
570
|
+
with_response: bool = False,
|
|
571
|
+
) -> None:
|
|
496
572
|
return await self.gatt_client.write_value(attribute, value, with_response)
|
|
497
573
|
|
|
498
|
-
async def read_characteristics_by_uuid(
|
|
574
|
+
async def read_characteristics_by_uuid(
|
|
575
|
+
self, uuid: core.UUID, service: Optional[gatt_client.ServiceProxy] = None
|
|
576
|
+
) -> List[bytes]:
|
|
499
577
|
return await self.gatt_client.read_characteristics_by_uuid(uuid, service)
|
|
500
578
|
|
|
501
|
-
def get_services_by_uuid(self, uuid):
|
|
579
|
+
def get_services_by_uuid(self, uuid: core.UUID) -> List[gatt_client.ServiceProxy]:
|
|
502
580
|
return self.gatt_client.get_services_by_uuid(uuid)
|
|
503
581
|
|
|
504
|
-
def get_characteristics_by_uuid(
|
|
582
|
+
def get_characteristics_by_uuid(
|
|
583
|
+
self, uuid: core.UUID, service: Optional[gatt_client.ServiceProxy] = None
|
|
584
|
+
) -> List[gatt_client.CharacteristicProxy]:
|
|
505
585
|
return self.gatt_client.get_characteristics_by_uuid(uuid, service)
|
|
506
586
|
|
|
507
|
-
def create_service_proxy(self, proxy_class):
|
|
508
|
-
return proxy_class.from_client(self.gatt_client)
|
|
587
|
+
def create_service_proxy(self, proxy_class: Type[_PROXY_CLASS]) -> _PROXY_CLASS:
|
|
588
|
+
return cast(_PROXY_CLASS, proxy_class.from_client(self.gatt_client))
|
|
509
589
|
|
|
510
|
-
async def discover_service_and_create_proxy(
|
|
590
|
+
async def discover_service_and_create_proxy(
|
|
591
|
+
self, proxy_class: Type[_PROXY_CLASS]
|
|
592
|
+
) -> Optional[_PROXY_CLASS]:
|
|
511
593
|
# Discover the first matching service and its characteristics
|
|
512
594
|
services = await self.discover_service(proxy_class.SERVICE_CLASS.UUID)
|
|
513
595
|
if services:
|
|
514
596
|
service = services[0]
|
|
515
597
|
await service.discover_characteristics()
|
|
516
598
|
return self.create_service_proxy(proxy_class)
|
|
599
|
+
return None
|
|
517
600
|
|
|
518
|
-
async def sustain(self, timeout=None):
|
|
601
|
+
async def sustain(self, timeout: Optional[float] = None) -> None:
|
|
519
602
|
await self.connection.sustain(timeout)
|
|
520
603
|
|
|
521
604
|
# [Classic only]
|
|
522
|
-
async def request_name(self):
|
|
605
|
+
async def request_name(self) -> str:
|
|
523
606
|
return await self.connection.request_remote_name()
|
|
524
607
|
|
|
525
608
|
async def __aenter__(self):
|
|
@@ -532,7 +615,7 @@ class Peer:
|
|
|
532
615
|
async def __aexit__(self, exc_type, exc_value, traceback):
|
|
533
616
|
pass
|
|
534
617
|
|
|
535
|
-
def __str__(self):
|
|
618
|
+
def __str__(self) -> str:
|
|
536
619
|
return f'{self.connection.peer_address} as {self.connection.role_name}'
|
|
537
620
|
|
|
538
621
|
|
|
@@ -551,6 +634,46 @@ class ConnectionParametersPreferences:
|
|
|
551
634
|
ConnectionParametersPreferences.default = ConnectionParametersPreferences()
|
|
552
635
|
|
|
553
636
|
|
|
637
|
+
# -----------------------------------------------------------------------------
|
|
638
|
+
@dataclass
|
|
639
|
+
class ScoLink(CompositeEventEmitter):
|
|
640
|
+
device: Device
|
|
641
|
+
acl_connection: Connection
|
|
642
|
+
handle: int
|
|
643
|
+
link_type: int
|
|
644
|
+
|
|
645
|
+
def __post_init__(self):
|
|
646
|
+
super().__init__()
|
|
647
|
+
|
|
648
|
+
async def disconnect(
|
|
649
|
+
self, reason: int = HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR
|
|
650
|
+
) -> None:
|
|
651
|
+
await self.device.disconnect(self, reason)
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
# -----------------------------------------------------------------------------
|
|
655
|
+
@dataclass
|
|
656
|
+
class CisLink(CompositeEventEmitter):
|
|
657
|
+
class State(IntEnum):
|
|
658
|
+
PENDING = 0
|
|
659
|
+
ESTABLISHED = 1
|
|
660
|
+
|
|
661
|
+
device: Device
|
|
662
|
+
acl_connection: Connection # Based ACL connection
|
|
663
|
+
handle: int # CIS handle assigned by Controller (in LE_Set_CIG_Parameters Complete or LE_CIS_Request events)
|
|
664
|
+
cis_id: int # CIS ID assigned by Central device
|
|
665
|
+
cig_id: int # CIG ID assigned by Central device
|
|
666
|
+
state: State = State.PENDING
|
|
667
|
+
|
|
668
|
+
def __post_init__(self):
|
|
669
|
+
super().__init__()
|
|
670
|
+
|
|
671
|
+
async def disconnect(
|
|
672
|
+
self, reason: int = HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR
|
|
673
|
+
) -> None:
|
|
674
|
+
await self.device.disconnect(self, reason)
|
|
675
|
+
|
|
676
|
+
|
|
554
677
|
# -----------------------------------------------------------------------------
|
|
555
678
|
class Connection(CompositeEventEmitter):
|
|
556
679
|
device: Device
|
|
@@ -567,6 +690,9 @@ class Connection(CompositeEventEmitter):
|
|
|
567
690
|
gatt_client: gatt_client.Client
|
|
568
691
|
pairing_peer_io_capability: Optional[int]
|
|
569
692
|
pairing_peer_authentication_requirements: Optional[int]
|
|
693
|
+
advertiser_after_disconnection: Union[
|
|
694
|
+
LegacyAdvertiser, ExtendedAdvertiser, None
|
|
695
|
+
] = None
|
|
570
696
|
|
|
571
697
|
@composite_listener
|
|
572
698
|
class Listener:
|
|
@@ -732,7 +858,7 @@ class Connection(CompositeEventEmitter):
|
|
|
732
858
|
async def switch_role(self, role: int) -> None:
|
|
733
859
|
return await self.device.switch_role(self, role)
|
|
734
860
|
|
|
735
|
-
async def sustain(self, timeout=None):
|
|
861
|
+
async def sustain(self, timeout: Optional[float] = None) -> None:
|
|
736
862
|
"""Idles the current task waiting for a disconnect or timeout"""
|
|
737
863
|
|
|
738
864
|
abort = asyncio.get_running_loop().create_future()
|
|
@@ -829,6 +955,7 @@ class DeviceConfiguration:
|
|
|
829
955
|
self.keystore = None
|
|
830
956
|
self.gatt_services: List[Dict[str, Any]] = []
|
|
831
957
|
self.address_resolution_offload = False
|
|
958
|
+
self.cis_enabled = False
|
|
832
959
|
|
|
833
960
|
def load_from_dict(self, config: Dict[str, Any]) -> None:
|
|
834
961
|
# Load simple properties
|
|
@@ -864,6 +991,7 @@ class DeviceConfiguration:
|
|
|
864
991
|
self.address_resolution_offload = config.get(
|
|
865
992
|
'address_resolution_offload', self.address_resolution_offload
|
|
866
993
|
)
|
|
994
|
+
self.cis_enabled = config.get('cis_enabled', self.cis_enabled)
|
|
867
995
|
|
|
868
996
|
# Load or synthesize an IRK
|
|
869
997
|
irk = config.get('irk')
|
|
@@ -970,7 +1098,11 @@ class Device(CompositeEventEmitter):
|
|
|
970
1098
|
]
|
|
971
1099
|
advertisement_accumulators: Dict[Address, AdvertisementDataAccumulator]
|
|
972
1100
|
config: DeviceConfiguration
|
|
973
|
-
|
|
1101
|
+
legacy_advertiser: Optional[LegacyAdvertiser]
|
|
1102
|
+
extended_advertisers: Dict[int, ExtendedAdvertiser]
|
|
1103
|
+
sco_links: Dict[int, ScoLink]
|
|
1104
|
+
cis_links: Dict[int, CisLink]
|
|
1105
|
+
_pending_cis: Dict[int, Tuple[int, int]]
|
|
974
1106
|
|
|
975
1107
|
@composite_listener
|
|
976
1108
|
class Listener:
|
|
@@ -1045,10 +1177,7 @@ class Device(CompositeEventEmitter):
|
|
|
1045
1177
|
|
|
1046
1178
|
self._host = None
|
|
1047
1179
|
self.powered_on = False
|
|
1048
|
-
self.advertising = False
|
|
1049
|
-
self.advertising_type = None
|
|
1050
1180
|
self.auto_restart_inquiry = True
|
|
1051
|
-
self.auto_restart_advertising = False
|
|
1052
1181
|
self.command_timeout = 10 # seconds
|
|
1053
1182
|
self.gatt_server = gatt_server.Server(self)
|
|
1054
1183
|
self.sdp_server = sdp.Server(self)
|
|
@@ -1063,16 +1192,19 @@ class Device(CompositeEventEmitter):
|
|
|
1063
1192
|
self.disconnecting = False
|
|
1064
1193
|
self.connections = {} # Connections, by connection handle
|
|
1065
1194
|
self.pending_connections = {} # Connections, by BD address (BR/EDR only)
|
|
1195
|
+
self.sco_links = {} # ScoLinks, by connection handle (BR/EDR only)
|
|
1196
|
+
self.cis_links = {} # CisLinks, by connection handle (LE only)
|
|
1197
|
+
self._pending_cis = {} # (CIS_ID, CIG_ID), by CIS_handle
|
|
1066
1198
|
self.classic_enabled = False
|
|
1067
1199
|
self.inquiry_response = None
|
|
1068
1200
|
self.address_resolver = None
|
|
1069
1201
|
self.classic_pending_accepts = {
|
|
1070
1202
|
Address.ANY: []
|
|
1071
1203
|
} # Futures, by BD address OR [Futures] for Address.ANY
|
|
1072
|
-
self.
|
|
1204
|
+
self.legacy_advertiser = None
|
|
1205
|
+
self.extended_advertisers = {}
|
|
1073
1206
|
|
|
1074
1207
|
# Own address type cache
|
|
1075
|
-
self.advertising_own_address_type = None
|
|
1076
1208
|
self.connect_own_address_type = None
|
|
1077
1209
|
|
|
1078
1210
|
# Use the initial config or a default
|
|
@@ -1092,6 +1224,7 @@ class Device(CompositeEventEmitter):
|
|
|
1092
1224
|
self.le_enabled = config.le_enabled
|
|
1093
1225
|
self.classic_enabled = config.classic_enabled
|
|
1094
1226
|
self.le_simultaneous_enabled = config.le_simultaneous_enabled
|
|
1227
|
+
self.cis_enabled = config.cis_enabled
|
|
1095
1228
|
self.classic_sc_enabled = config.classic_sc_enabled
|
|
1096
1229
|
self.classic_ssp_enabled = config.classic_ssp_enabled
|
|
1097
1230
|
self.classic_smp_enabled = config.classic_smp_enabled
|
|
@@ -1332,7 +1465,7 @@ class Device(CompositeEventEmitter):
|
|
|
1332
1465
|
await self.host.reset()
|
|
1333
1466
|
|
|
1334
1467
|
# Try to get the public address from the controller
|
|
1335
|
-
response = await self.send_command(HCI_Read_BD_ADDR_Command())
|
|
1468
|
+
response = await self.send_command(HCI_Read_BD_ADDR_Command())
|
|
1336
1469
|
if response.return_parameters.status == HCI_SUCCESS:
|
|
1337
1470
|
logger.debug(
|
|
1338
1471
|
color(f'BD_ADDR: {response.return_parameters.bd_addr}', 'yellow')
|
|
@@ -1355,7 +1488,7 @@ class Device(CompositeEventEmitter):
|
|
|
1355
1488
|
HCI_Write_LE_Host_Support_Command(
|
|
1356
1489
|
le_supported_host=int(self.le_enabled),
|
|
1357
1490
|
simultaneous_le_host=int(self.le_simultaneous_enabled),
|
|
1358
|
-
)
|
|
1491
|
+
)
|
|
1359
1492
|
)
|
|
1360
1493
|
|
|
1361
1494
|
if self.le_enabled:
|
|
@@ -1365,7 +1498,7 @@ class Device(CompositeEventEmitter):
|
|
|
1365
1498
|
if self.host.supports_command(HCI_LE_RAND_COMMAND):
|
|
1366
1499
|
# Get 8 random bytes
|
|
1367
1500
|
response = await self.send_command(
|
|
1368
|
-
HCI_LE_Rand_Command(), check_result=True
|
|
1501
|
+
HCI_LE_Rand_Command(), check_result=True
|
|
1369
1502
|
)
|
|
1370
1503
|
|
|
1371
1504
|
# Ensure the address bytes can be a static random address
|
|
@@ -1386,7 +1519,7 @@ class Device(CompositeEventEmitter):
|
|
|
1386
1519
|
await self.send_command(
|
|
1387
1520
|
HCI_LE_Set_Random_Address_Command(
|
|
1388
1521
|
random_address=self.random_address
|
|
1389
|
-
),
|
|
1522
|
+
),
|
|
1390
1523
|
check_result=True,
|
|
1391
1524
|
)
|
|
1392
1525
|
|
|
@@ -1399,25 +1532,35 @@ class Device(CompositeEventEmitter):
|
|
|
1399
1532
|
await self.send_command(
|
|
1400
1533
|
HCI_LE_Set_Address_Resolution_Enable_Command(
|
|
1401
1534
|
address_resolution_enable=1
|
|
1402
|
-
)
|
|
1535
|
+
)
|
|
1536
|
+
)
|
|
1537
|
+
|
|
1538
|
+
if self.cis_enabled:
|
|
1539
|
+
await self.send_command(
|
|
1540
|
+
HCI_LE_Set_Host_Feature_Command(
|
|
1541
|
+
bit_number=(
|
|
1542
|
+
HCI_CONNECTED_ISOCHRONOUS_STREAM_LE_SUPPORTED_FEATURE
|
|
1543
|
+
),
|
|
1544
|
+
bit_value=1,
|
|
1545
|
+
)
|
|
1403
1546
|
)
|
|
1404
1547
|
|
|
1405
1548
|
if self.classic_enabled:
|
|
1406
1549
|
await self.send_command(
|
|
1407
|
-
HCI_Write_Local_Name_Command(local_name=self.name.encode('utf8'))
|
|
1550
|
+
HCI_Write_Local_Name_Command(local_name=self.name.encode('utf8'))
|
|
1408
1551
|
)
|
|
1409
1552
|
await self.send_command(
|
|
1410
|
-
HCI_Write_Class_Of_Device_Command(class_of_device=self.class_of_device)
|
|
1553
|
+
HCI_Write_Class_Of_Device_Command(class_of_device=self.class_of_device)
|
|
1411
1554
|
)
|
|
1412
1555
|
await self.send_command(
|
|
1413
1556
|
HCI_Write_Simple_Pairing_Mode_Command(
|
|
1414
1557
|
simple_pairing_mode=int(self.classic_ssp_enabled)
|
|
1415
|
-
)
|
|
1558
|
+
)
|
|
1416
1559
|
)
|
|
1417
1560
|
await self.send_command(
|
|
1418
1561
|
HCI_Write_Secure_Connections_Host_Support_Command(
|
|
1419
1562
|
secure_connections_host_support=int(self.classic_sc_enabled)
|
|
1420
|
-
)
|
|
1563
|
+
)
|
|
1421
1564
|
)
|
|
1422
1565
|
await self.set_connectable(self.connectable)
|
|
1423
1566
|
await self.set_discoverable(self.discoverable)
|
|
@@ -1441,7 +1584,7 @@ class Device(CompositeEventEmitter):
|
|
|
1441
1584
|
self.address_resolver = smp.AddressResolver(resolving_keys)
|
|
1442
1585
|
|
|
1443
1586
|
if self.address_resolution_offload:
|
|
1444
|
-
await self.send_command(HCI_LE_Clear_Resolving_List_Command())
|
|
1587
|
+
await self.send_command(HCI_LE_Clear_Resolving_List_Command())
|
|
1445
1588
|
|
|
1446
1589
|
for irk, address in resolving_keys:
|
|
1447
1590
|
await self.send_command(
|
|
@@ -1450,7 +1593,7 @@ class Device(CompositeEventEmitter):
|
|
|
1450
1593
|
peer_identity_address=address,
|
|
1451
1594
|
peer_irk=irk,
|
|
1452
1595
|
local_irk=self.irk,
|
|
1453
|
-
)
|
|
1596
|
+
)
|
|
1454
1597
|
)
|
|
1455
1598
|
|
|
1456
1599
|
def supports_le_feature(self, feature):
|
|
@@ -1469,6 +1612,7 @@ class Device(CompositeEventEmitter):
|
|
|
1469
1612
|
|
|
1470
1613
|
return self.host.supports_le_feature(feature_map[phy])
|
|
1471
1614
|
|
|
1615
|
+
@deprecated("Please use start_legacy_advertising.")
|
|
1472
1616
|
async def start_advertising(
|
|
1473
1617
|
self,
|
|
1474
1618
|
advertising_type: AdvertisingType = AdvertisingType.UNDIRECTED_CONNECTABLE_SCANNABLE,
|
|
@@ -1476,16 +1620,50 @@ class Device(CompositeEventEmitter):
|
|
|
1476
1620
|
own_address_type: int = OwnAddressType.RANDOM,
|
|
1477
1621
|
auto_restart: bool = False,
|
|
1478
1622
|
) -> None:
|
|
1623
|
+
await self.start_legacy_advertising(
|
|
1624
|
+
advertising_type=advertising_type,
|
|
1625
|
+
target=target,
|
|
1626
|
+
own_address_type=OwnAddressType(own_address_type),
|
|
1627
|
+
auto_restart=auto_restart,
|
|
1628
|
+
)
|
|
1629
|
+
|
|
1630
|
+
async def start_legacy_advertising(
|
|
1631
|
+
self,
|
|
1632
|
+
advertising_type: AdvertisingType = AdvertisingType.UNDIRECTED_CONNECTABLE_SCANNABLE,
|
|
1633
|
+
target: Optional[Address] = None,
|
|
1634
|
+
own_address_type: OwnAddressType = OwnAddressType.RANDOM,
|
|
1635
|
+
auto_restart: bool = False,
|
|
1636
|
+
advertising_data: Optional[bytes] = None,
|
|
1637
|
+
scan_response_data: Optional[bytes] = None,
|
|
1638
|
+
) -> LegacyAdvertiser:
|
|
1639
|
+
"""Starts an legacy advertisement.
|
|
1640
|
+
|
|
1641
|
+
Args:
|
|
1642
|
+
advertising_type: Advertising type passed to HCI_LE_Set_Advertising_Parameters_Command.
|
|
1643
|
+
target: Directed advertising target. Directed type should be set in advertising_type arg.
|
|
1644
|
+
own_address_type: own address type to use in the advertising.
|
|
1645
|
+
auto_restart: whether the advertisement will be restarted after disconnection.
|
|
1646
|
+
scan_response_data: raw scan response.
|
|
1647
|
+
advertising_data: raw advertising data.
|
|
1648
|
+
|
|
1649
|
+
Returns:
|
|
1650
|
+
LegacyAdvertiser object containing the metadata of advertisement.
|
|
1651
|
+
"""
|
|
1652
|
+
if self.extended_advertisers:
|
|
1653
|
+
logger.warning(
|
|
1654
|
+
'Trying to start Legacy and Extended Advertising at the same time!'
|
|
1655
|
+
)
|
|
1656
|
+
|
|
1479
1657
|
# If we're advertising, stop first
|
|
1480
|
-
if self.
|
|
1658
|
+
if self.legacy_advertiser:
|
|
1481
1659
|
await self.stop_advertising()
|
|
1482
1660
|
|
|
1483
1661
|
# Set/update the advertising data if the advertising type allows it
|
|
1484
1662
|
if advertising_type.has_data:
|
|
1485
1663
|
await self.send_command(
|
|
1486
1664
|
HCI_LE_Set_Advertising_Data_Command(
|
|
1487
|
-
advertising_data=self.advertising_data
|
|
1488
|
-
),
|
|
1665
|
+
advertising_data=advertising_data or self.advertising_data or b''
|
|
1666
|
+
),
|
|
1489
1667
|
check_result=True,
|
|
1490
1668
|
)
|
|
1491
1669
|
|
|
@@ -1493,8 +1671,10 @@ class Device(CompositeEventEmitter):
|
|
|
1493
1671
|
if advertising_type.is_scannable:
|
|
1494
1672
|
await self.send_command(
|
|
1495
1673
|
HCI_LE_Set_Scan_Response_Data_Command(
|
|
1496
|
-
scan_response_data=
|
|
1497
|
-
|
|
1674
|
+
scan_response_data=scan_response_data
|
|
1675
|
+
or self.scan_response_data
|
|
1676
|
+
or b''
|
|
1677
|
+
),
|
|
1498
1678
|
check_result=True,
|
|
1499
1679
|
)
|
|
1500
1680
|
|
|
@@ -1520,55 +1700,67 @@ class Device(CompositeEventEmitter):
|
|
|
1520
1700
|
peer_address=peer_address,
|
|
1521
1701
|
advertising_channel_map=7,
|
|
1522
1702
|
advertising_filter_policy=0,
|
|
1523
|
-
),
|
|
1703
|
+
),
|
|
1524
1704
|
check_result=True,
|
|
1525
1705
|
)
|
|
1526
1706
|
|
|
1527
1707
|
# Enable advertising
|
|
1528
1708
|
await self.send_command(
|
|
1529
|
-
HCI_LE_Set_Advertising_Enable_Command(advertising_enable=1),
|
|
1709
|
+
HCI_LE_Set_Advertising_Enable_Command(advertising_enable=1),
|
|
1530
1710
|
check_result=True,
|
|
1531
1711
|
)
|
|
1532
1712
|
|
|
1533
|
-
self.
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1713
|
+
self.legacy_advertiser = LegacyAdvertiser(
|
|
1714
|
+
device=self,
|
|
1715
|
+
advertising_type=advertising_type,
|
|
1716
|
+
own_address_type=own_address_type,
|
|
1717
|
+
auto_restart=auto_restart,
|
|
1718
|
+
advertising_data=advertising_data,
|
|
1719
|
+
scan_response_data=scan_response_data,
|
|
1720
|
+
)
|
|
1721
|
+
return self.legacy_advertiser
|
|
1537
1722
|
|
|
1723
|
+
@deprecated("Please use stop_legacy_advertising.")
|
|
1538
1724
|
async def stop_advertising(self) -> None:
|
|
1725
|
+
await self.stop_legacy_advertising()
|
|
1726
|
+
|
|
1727
|
+
async def stop_legacy_advertising(self) -> None:
|
|
1539
1728
|
# Disable advertising
|
|
1540
|
-
if self.
|
|
1729
|
+
if self.legacy_advertiser:
|
|
1541
1730
|
await self.send_command(
|
|
1542
|
-
HCI_LE_Set_Advertising_Enable_Command(advertising_enable=0),
|
|
1731
|
+
HCI_LE_Set_Advertising_Enable_Command(advertising_enable=0),
|
|
1543
1732
|
check_result=True,
|
|
1544
1733
|
)
|
|
1545
1734
|
|
|
1546
|
-
self.
|
|
1547
|
-
self.advertising_own_address_type = None
|
|
1548
|
-
self.advertising = False
|
|
1549
|
-
self.auto_restart_advertising = False
|
|
1735
|
+
self.legacy_advertiser = None
|
|
1550
1736
|
|
|
1551
1737
|
@experimental('Extended Advertising is still experimental - Might be changed soon.')
|
|
1552
1738
|
async def start_extended_advertising(
|
|
1553
1739
|
self,
|
|
1554
1740
|
advertising_properties: HCI_LE_Set_Extended_Advertising_Parameters_Command.AdvertisingProperties = HCI_LE_Set_Extended_Advertising_Parameters_Command.AdvertisingProperties.CONNECTABLE_ADVERTISING,
|
|
1555
1741
|
target: Address = Address.ANY,
|
|
1556
|
-
own_address_type:
|
|
1557
|
-
|
|
1742
|
+
own_address_type: OwnAddressType = OwnAddressType.RANDOM,
|
|
1743
|
+
auto_restart: bool = True,
|
|
1558
1744
|
advertising_data: Optional[bytes] = None,
|
|
1559
|
-
|
|
1745
|
+
scan_response_data: Optional[bytes] = None,
|
|
1746
|
+
) -> ExtendedAdvertiser:
|
|
1560
1747
|
"""Starts an extended advertising set.
|
|
1561
1748
|
|
|
1562
1749
|
Args:
|
|
1563
1750
|
advertising_properties: Properties to pass in HCI_LE_Set_Extended_Advertising_Parameters_Command
|
|
1564
1751
|
target: Directed advertising target. Directed property should be set in advertising_properties arg.
|
|
1565
1752
|
own_address_type: own address type to use in the advertising.
|
|
1566
|
-
|
|
1753
|
+
auto_restart: whether the advertisement will be restarted after disconnection.
|
|
1567
1754
|
advertising_data: raw advertising data. When a non-none value is set, HCI_LE_Set_Advertising_Set_Random_Address_Command will be sent.
|
|
1755
|
+
scan_response_data: raw scan response. When a non-none value is set, HCI_LE_Set_Extended_Scan_Response_Data_Command will be sent.
|
|
1568
1756
|
|
|
1569
1757
|
Returns:
|
|
1570
|
-
|
|
1758
|
+
ExtendedAdvertiser object containing the metadata of advertisement.
|
|
1571
1759
|
"""
|
|
1760
|
+
if self.legacy_advertiser:
|
|
1761
|
+
logger.warning(
|
|
1762
|
+
'Trying to start Legacy and Extended Advertising at the same time!'
|
|
1763
|
+
)
|
|
1572
1764
|
|
|
1573
1765
|
adv_handle = -1
|
|
1574
1766
|
# Find a free handle
|
|
@@ -1576,7 +1768,7 @@ class Device(CompositeEventEmitter):
|
|
|
1576
1768
|
DEVICE_MIN_EXTENDED_ADVERTISING_SET_HANDLE,
|
|
1577
1769
|
DEVICE_MAX_EXTENDED_ADVERTISING_SET_HANDLE + 1,
|
|
1578
1770
|
):
|
|
1579
|
-
if i not in self.
|
|
1771
|
+
if i not in self.extended_advertisers:
|
|
1580
1772
|
adv_handle = i
|
|
1581
1773
|
break
|
|
1582
1774
|
|
|
@@ -1606,7 +1798,7 @@ class Device(CompositeEventEmitter):
|
|
|
1606
1798
|
secondary_advertising_phy=1, # LE 1M
|
|
1607
1799
|
advertising_sid=0,
|
|
1608
1800
|
scan_request_notification_enable=0,
|
|
1609
|
-
),
|
|
1801
|
+
),
|
|
1610
1802
|
check_result=True,
|
|
1611
1803
|
)
|
|
1612
1804
|
|
|
@@ -1618,19 +1810,19 @@ class Device(CompositeEventEmitter):
|
|
|
1618
1810
|
operation=HCI_LE_Set_Extended_Advertising_Data_Command.Operation.COMPLETE_DATA,
|
|
1619
1811
|
fragment_preference=0x01, # Should not fragment
|
|
1620
1812
|
advertising_data=advertising_data,
|
|
1621
|
-
),
|
|
1813
|
+
),
|
|
1622
1814
|
check_result=True,
|
|
1623
1815
|
)
|
|
1624
1816
|
|
|
1625
1817
|
# Set the scan response if present
|
|
1626
|
-
if
|
|
1818
|
+
if scan_response_data is not None:
|
|
1627
1819
|
await self.send_command(
|
|
1628
1820
|
HCI_LE_Set_Extended_Scan_Response_Data_Command(
|
|
1629
1821
|
advertising_handle=adv_handle,
|
|
1630
1822
|
operation=HCI_LE_Set_Extended_Advertising_Data_Command.Operation.COMPLETE_DATA,
|
|
1631
1823
|
fragment_preference=0x01, # Should not fragment
|
|
1632
|
-
scan_response_data=
|
|
1633
|
-
),
|
|
1824
|
+
scan_response_data=scan_response_data,
|
|
1825
|
+
),
|
|
1634
1826
|
check_result=True,
|
|
1635
1827
|
)
|
|
1636
1828
|
|
|
@@ -1642,7 +1834,7 @@ class Device(CompositeEventEmitter):
|
|
|
1642
1834
|
HCI_LE_Set_Advertising_Set_Random_Address_Command(
|
|
1643
1835
|
advertising_handle=adv_handle,
|
|
1644
1836
|
random_address=self.random_address,
|
|
1645
|
-
),
|
|
1837
|
+
),
|
|
1646
1838
|
check_result=True,
|
|
1647
1839
|
)
|
|
1648
1840
|
|
|
@@ -1653,19 +1845,27 @@ class Device(CompositeEventEmitter):
|
|
|
1653
1845
|
advertising_handles=[adv_handle],
|
|
1654
1846
|
durations=[0], # Forever
|
|
1655
1847
|
max_extended_advertising_events=[0], # Infinite
|
|
1656
|
-
),
|
|
1848
|
+
),
|
|
1657
1849
|
check_result=True,
|
|
1658
1850
|
)
|
|
1659
1851
|
except HCI_Error as error:
|
|
1660
1852
|
# When any step fails, cleanup the advertising handle.
|
|
1661
1853
|
await self.send_command(
|
|
1662
|
-
HCI_LE_Remove_Advertising_Set_Command(advertising_handle=adv_handle),
|
|
1854
|
+
HCI_LE_Remove_Advertising_Set_Command(advertising_handle=adv_handle),
|
|
1663
1855
|
check_result=False,
|
|
1664
1856
|
)
|
|
1665
1857
|
raise error
|
|
1666
1858
|
|
|
1667
|
-
self.
|
|
1668
|
-
|
|
1859
|
+
advertiser = self.extended_advertisers[adv_handle] = ExtendedAdvertiser(
|
|
1860
|
+
device=self,
|
|
1861
|
+
handle=adv_handle,
|
|
1862
|
+
advertising_properties=advertising_properties,
|
|
1863
|
+
own_address_type=own_address_type,
|
|
1864
|
+
auto_restart=auto_restart,
|
|
1865
|
+
advertising_data=advertising_data,
|
|
1866
|
+
scan_response_data=scan_response_data,
|
|
1867
|
+
)
|
|
1868
|
+
return advertiser
|
|
1669
1869
|
|
|
1670
1870
|
@experimental('Extended Advertising is still experimental - Might be changed soon.')
|
|
1671
1871
|
async def stop_extended_advertising(self, adv_handle: int) -> None:
|
|
@@ -1681,19 +1881,19 @@ class Device(CompositeEventEmitter):
|
|
|
1681
1881
|
advertising_handles=[adv_handle],
|
|
1682
1882
|
durations=[0],
|
|
1683
1883
|
max_extended_advertising_events=[0],
|
|
1684
|
-
),
|
|
1884
|
+
),
|
|
1685
1885
|
check_result=True,
|
|
1686
1886
|
)
|
|
1687
1887
|
# Remove advertising set
|
|
1688
1888
|
await self.send_command(
|
|
1689
|
-
HCI_LE_Remove_Advertising_Set_Command(advertising_handle=adv_handle),
|
|
1889
|
+
HCI_LE_Remove_Advertising_Set_Command(advertising_handle=adv_handle),
|
|
1690
1890
|
check_result=True,
|
|
1691
1891
|
)
|
|
1692
|
-
self.
|
|
1892
|
+
del self.extended_advertisers[adv_handle]
|
|
1693
1893
|
|
|
1694
1894
|
@property
|
|
1695
1895
|
def is_advertising(self):
|
|
1696
|
-
return self.
|
|
1896
|
+
return self.legacy_advertiser or self.extended_advertisers
|
|
1697
1897
|
|
|
1698
1898
|
async def start_scanning(
|
|
1699
1899
|
self,
|
|
@@ -1754,7 +1954,7 @@ class Device(CompositeEventEmitter):
|
|
|
1754
1954
|
scan_types=[scan_type] * scanning_phy_count,
|
|
1755
1955
|
scan_intervals=[int(scan_window / 0.625)] * scanning_phy_count,
|
|
1756
1956
|
scan_windows=[int(scan_window / 0.625)] * scanning_phy_count,
|
|
1757
|
-
),
|
|
1957
|
+
),
|
|
1758
1958
|
check_result=True,
|
|
1759
1959
|
)
|
|
1760
1960
|
|
|
@@ -1765,7 +1965,7 @@ class Device(CompositeEventEmitter):
|
|
|
1765
1965
|
filter_duplicates=1 if filter_duplicates else 0,
|
|
1766
1966
|
duration=0, # TODO allow other values
|
|
1767
1967
|
period=0, # TODO allow other values
|
|
1768
|
-
),
|
|
1968
|
+
),
|
|
1769
1969
|
check_result=True,
|
|
1770
1970
|
)
|
|
1771
1971
|
else:
|
|
@@ -1783,7 +1983,7 @@ class Device(CompositeEventEmitter):
|
|
|
1783
1983
|
le_scan_window=int(scan_window / 0.625),
|
|
1784
1984
|
own_address_type=own_address_type,
|
|
1785
1985
|
scanning_filter_policy=HCI_LE_Set_Scan_Parameters_Command.BASIC_UNFILTERED_POLICY,
|
|
1786
|
-
),
|
|
1986
|
+
),
|
|
1787
1987
|
check_result=True,
|
|
1788
1988
|
)
|
|
1789
1989
|
|
|
@@ -1791,7 +1991,7 @@ class Device(CompositeEventEmitter):
|
|
|
1791
1991
|
await self.send_command(
|
|
1792
1992
|
HCI_LE_Set_Scan_Enable_Command(
|
|
1793
1993
|
le_scan_enable=1, filter_duplicates=1 if filter_duplicates else 0
|
|
1794
|
-
),
|
|
1994
|
+
),
|
|
1795
1995
|
check_result=True,
|
|
1796
1996
|
)
|
|
1797
1997
|
|
|
@@ -1804,12 +2004,12 @@ class Device(CompositeEventEmitter):
|
|
|
1804
2004
|
await self.send_command(
|
|
1805
2005
|
HCI_LE_Set_Extended_Scan_Enable_Command(
|
|
1806
2006
|
enable=0, filter_duplicates=0, duration=0, period=0
|
|
1807
|
-
),
|
|
2007
|
+
),
|
|
1808
2008
|
check_result=True,
|
|
1809
2009
|
)
|
|
1810
2010
|
else:
|
|
1811
2011
|
await self.send_command(
|
|
1812
|
-
HCI_LE_Set_Scan_Enable_Command(le_scan_enable=0, filter_duplicates=0),
|
|
2012
|
+
HCI_LE_Set_Scan_Enable_Command(le_scan_enable=0, filter_duplicates=0),
|
|
1813
2013
|
check_result=True,
|
|
1814
2014
|
)
|
|
1815
2015
|
|
|
@@ -1829,7 +2029,7 @@ class Device(CompositeEventEmitter):
|
|
|
1829
2029
|
|
|
1830
2030
|
async def start_discovery(self, auto_restart: bool = True) -> None:
|
|
1831
2031
|
await self.send_command(
|
|
1832
|
-
HCI_Write_Inquiry_Mode_Command(inquiry_mode=HCI_EXTENDED_INQUIRY_MODE),
|
|
2032
|
+
HCI_Write_Inquiry_Mode_Command(inquiry_mode=HCI_EXTENDED_INQUIRY_MODE),
|
|
1833
2033
|
check_result=True,
|
|
1834
2034
|
)
|
|
1835
2035
|
|
|
@@ -1838,7 +2038,7 @@ class Device(CompositeEventEmitter):
|
|
|
1838
2038
|
lap=HCI_GENERAL_INQUIRY_LAP,
|
|
1839
2039
|
inquiry_length=DEVICE_DEFAULT_INQUIRY_LENGTH,
|
|
1840
2040
|
num_responses=0, # Unlimited number of responses.
|
|
1841
|
-
)
|
|
2041
|
+
)
|
|
1842
2042
|
)
|
|
1843
2043
|
if response.status != HCI_Command_Status_Event.PENDING:
|
|
1844
2044
|
self.discovering = False
|
|
@@ -1849,7 +2049,7 @@ class Device(CompositeEventEmitter):
|
|
|
1849
2049
|
|
|
1850
2050
|
async def stop_discovery(self) -> None:
|
|
1851
2051
|
if self.discovering:
|
|
1852
|
-
await self.send_command(HCI_Inquiry_Cancel_Command(), check_result=True)
|
|
2052
|
+
await self.send_command(HCI_Inquiry_Cancel_Command(), check_result=True)
|
|
1853
2053
|
self.auto_restart_inquiry = True
|
|
1854
2054
|
self.discovering = False
|
|
1855
2055
|
|
|
@@ -1897,7 +2097,7 @@ class Device(CompositeEventEmitter):
|
|
|
1897
2097
|
await self.send_command(
|
|
1898
2098
|
HCI_Write_Extended_Inquiry_Response_Command(
|
|
1899
2099
|
fec_required=0, extended_inquiry_response=self.inquiry_response
|
|
1900
|
-
),
|
|
2100
|
+
),
|
|
1901
2101
|
check_result=True,
|
|
1902
2102
|
)
|
|
1903
2103
|
await self.set_scan_enable(
|
|
@@ -2086,7 +2286,7 @@ class Device(CompositeEventEmitter):
|
|
|
2086
2286
|
supervision_timeouts=supervision_timeouts,
|
|
2087
2287
|
min_ce_lengths=min_ce_lengths,
|
|
2088
2288
|
max_ce_lengths=max_ce_lengths,
|
|
2089
|
-
)
|
|
2289
|
+
)
|
|
2090
2290
|
)
|
|
2091
2291
|
else:
|
|
2092
2292
|
if HCI_LE_1M_PHY not in connection_parameters_preferences:
|
|
@@ -2115,7 +2315,7 @@ class Device(CompositeEventEmitter):
|
|
|
2115
2315
|
supervision_timeout=int(prefs.supervision_timeout / 10),
|
|
2116
2316
|
min_ce_length=int(prefs.min_ce_length / 0.625),
|
|
2117
2317
|
max_ce_length=int(prefs.max_ce_length / 0.625),
|
|
2118
|
-
)
|
|
2318
|
+
)
|
|
2119
2319
|
)
|
|
2120
2320
|
else:
|
|
2121
2321
|
# Save pending connection
|
|
@@ -2132,7 +2332,7 @@ class Device(CompositeEventEmitter):
|
|
|
2132
2332
|
clock_offset=0x0000,
|
|
2133
2333
|
allow_role_switch=0x01,
|
|
2134
2334
|
reserved=0,
|
|
2135
|
-
)
|
|
2335
|
+
)
|
|
2136
2336
|
)
|
|
2137
2337
|
|
|
2138
2338
|
if result.status != HCI_Command_Status_Event.PENDING:
|
|
@@ -2151,10 +2351,10 @@ class Device(CompositeEventEmitter):
|
|
|
2151
2351
|
)
|
|
2152
2352
|
except asyncio.TimeoutError:
|
|
2153
2353
|
if transport == BT_LE_TRANSPORT:
|
|
2154
|
-
await self.send_command(HCI_LE_Create_Connection_Cancel_Command())
|
|
2354
|
+
await self.send_command(HCI_LE_Create_Connection_Cancel_Command())
|
|
2155
2355
|
else:
|
|
2156
2356
|
await self.send_command(
|
|
2157
|
-
HCI_Create_Connection_Cancel_Command(bd_addr=peer_address)
|
|
2357
|
+
HCI_Create_Connection_Cancel_Command(bd_addr=peer_address)
|
|
2158
2358
|
)
|
|
2159
2359
|
|
|
2160
2360
|
try:
|
|
@@ -2268,7 +2468,7 @@ class Device(CompositeEventEmitter):
|
|
|
2268
2468
|
try:
|
|
2269
2469
|
# Accept connection request
|
|
2270
2470
|
await self.send_command(
|
|
2271
|
-
HCI_Accept_Connection_Request_Command(bd_addr=peer_address, role=role)
|
|
2471
|
+
HCI_Accept_Connection_Request_Command(bd_addr=peer_address, role=role)
|
|
2272
2472
|
)
|
|
2273
2473
|
|
|
2274
2474
|
# Wait for connection complete
|
|
@@ -2325,7 +2525,9 @@ class Device(CompositeEventEmitter):
|
|
|
2325
2525
|
check_result=True,
|
|
2326
2526
|
)
|
|
2327
2527
|
|
|
2328
|
-
async def disconnect(
|
|
2528
|
+
async def disconnect(
|
|
2529
|
+
self, connection: Union[Connection, ScoLink, CisLink], reason: int
|
|
2530
|
+
) -> None:
|
|
2329
2531
|
# Create a future so that we can wait for the disconnection's result
|
|
2330
2532
|
pending_disconnection = asyncio.get_running_loop().create_future()
|
|
2331
2533
|
connection.on('disconnection', pending_disconnection.set_result)
|
|
@@ -2364,7 +2566,7 @@ class Device(CompositeEventEmitter):
|
|
|
2364
2566
|
connection_handle=connection.handle,
|
|
2365
2567
|
tx_octets=tx_octets,
|
|
2366
2568
|
tx_time=tx_time,
|
|
2367
|
-
),
|
|
2569
|
+
),
|
|
2368
2570
|
check_result=True,
|
|
2369
2571
|
)
|
|
2370
2572
|
|
|
@@ -2410,7 +2612,7 @@ class Device(CompositeEventEmitter):
|
|
|
2410
2612
|
supervision_timeout=supervision_timeout,
|
|
2411
2613
|
min_ce_length=min_ce_length,
|
|
2412
2614
|
max_ce_length=max_ce_length,
|
|
2413
|
-
)
|
|
2615
|
+
)
|
|
2414
2616
|
)
|
|
2415
2617
|
if result.status != HCI_Command_Status_Event.PENDING:
|
|
2416
2618
|
raise HCI_StatusError(result)
|
|
@@ -2738,7 +2940,7 @@ class Device(CompositeEventEmitter):
|
|
|
2738
2940
|
|
|
2739
2941
|
try:
|
|
2740
2942
|
result = await self.send_command(
|
|
2741
|
-
HCI_Switch_Role_Command(bd_addr=connection.peer_address, role=role)
|
|
2943
|
+
HCI_Switch_Role_Command(bd_addr=connection.peer_address, role=role)
|
|
2742
2944
|
)
|
|
2743
2945
|
if result.status != HCI_COMMAND_STATUS_PENDING:
|
|
2744
2946
|
logger.warning(
|
|
@@ -2780,7 +2982,7 @@ class Device(CompositeEventEmitter):
|
|
|
2780
2982
|
page_scan_repetition_mode=HCI_Remote_Name_Request_Command.R2,
|
|
2781
2983
|
reserved=0,
|
|
2782
2984
|
clock_offset=0, # TODO investigate non-0 values
|
|
2783
|
-
)
|
|
2985
|
+
)
|
|
2784
2986
|
)
|
|
2785
2987
|
|
|
2786
2988
|
if result.status != HCI_COMMAND_STATUS_PENDING:
|
|
@@ -2796,6 +2998,150 @@ class Device(CompositeEventEmitter):
|
|
|
2796
2998
|
self.remove_listener('remote_name', handler)
|
|
2797
2999
|
self.remove_listener('remote_name_failure', failure_handler)
|
|
2798
3000
|
|
|
3001
|
+
# [LE only]
|
|
3002
|
+
@experimental('Only for testing.')
|
|
3003
|
+
async def setup_cig(
|
|
3004
|
+
self,
|
|
3005
|
+
cig_id: int,
|
|
3006
|
+
cis_id: List[int],
|
|
3007
|
+
sdu_interval: Tuple[int, int],
|
|
3008
|
+
framing: int,
|
|
3009
|
+
max_sdu: Tuple[int, int],
|
|
3010
|
+
retransmission_number: int,
|
|
3011
|
+
max_transport_latency: Tuple[int, int],
|
|
3012
|
+
) -> List[int]:
|
|
3013
|
+
"""Sends HCI_LE_Set_CIG_Parameters_Command.
|
|
3014
|
+
|
|
3015
|
+
Args:
|
|
3016
|
+
cig_id: CIG_ID.
|
|
3017
|
+
cis_id: CID ID list.
|
|
3018
|
+
sdu_interval: SDU intervals of (Central->Peripheral, Peripheral->Cental).
|
|
3019
|
+
framing: Un-framing(0) or Framing(1).
|
|
3020
|
+
max_sdu: Max SDU counts of (Central->Peripheral, Peripheral->Cental).
|
|
3021
|
+
retransmission_number: retransmission_number.
|
|
3022
|
+
max_transport_latency: Max transport latencies of
|
|
3023
|
+
(Central->Peripheral, Peripheral->Cental).
|
|
3024
|
+
|
|
3025
|
+
Returns:
|
|
3026
|
+
List of created CIS handles corresponding to the same order of [cid_id].
|
|
3027
|
+
"""
|
|
3028
|
+
num_cis = len(cis_id)
|
|
3029
|
+
|
|
3030
|
+
response = await self.send_command(
|
|
3031
|
+
HCI_LE_Set_CIG_Parameters_Command(
|
|
3032
|
+
cig_id=cig_id,
|
|
3033
|
+
sdu_interval_c_to_p=sdu_interval[0],
|
|
3034
|
+
sdu_interval_p_to_c=sdu_interval[1],
|
|
3035
|
+
worst_case_sca=0x00, # 251-500 ppm
|
|
3036
|
+
packing=0x00, # Sequential
|
|
3037
|
+
framing=framing,
|
|
3038
|
+
max_transport_latency_c_to_p=max_transport_latency[0],
|
|
3039
|
+
max_transport_latency_p_to_c=max_transport_latency[1],
|
|
3040
|
+
cis_id=cis_id,
|
|
3041
|
+
max_sdu_c_to_p=[max_sdu[0]] * num_cis,
|
|
3042
|
+
max_sdu_p_to_c=[max_sdu[1]] * num_cis,
|
|
3043
|
+
phy_c_to_p=[HCI_LE_2M_PHY] * num_cis,
|
|
3044
|
+
phy_p_to_c=[HCI_LE_2M_PHY] * num_cis,
|
|
3045
|
+
rtn_c_to_p=[retransmission_number] * num_cis,
|
|
3046
|
+
rtn_p_to_c=[retransmission_number] * num_cis,
|
|
3047
|
+
),
|
|
3048
|
+
check_result=True,
|
|
3049
|
+
)
|
|
3050
|
+
|
|
3051
|
+
# Ideally, we should manage CIG lifecycle, but they are not useful for Unicast
|
|
3052
|
+
# Server, so here it only provides a basic functionality for testing.
|
|
3053
|
+
cis_handles = response.return_parameters.connection_handle[:]
|
|
3054
|
+
for id, cis_handle in zip(cis_id, cis_handles):
|
|
3055
|
+
self._pending_cis[cis_handle] = (id, cig_id)
|
|
3056
|
+
|
|
3057
|
+
return cis_handles
|
|
3058
|
+
|
|
3059
|
+
# [LE only]
|
|
3060
|
+
@experimental('Only for testing.')
|
|
3061
|
+
async def create_cis(self, cis_acl_pairs: List[Tuple[int, int]]) -> List[CisLink]:
|
|
3062
|
+
for cis_handle, acl_handle in cis_acl_pairs:
|
|
3063
|
+
acl_connection = self.lookup_connection(acl_handle)
|
|
3064
|
+
assert acl_connection
|
|
3065
|
+
cis_id, cig_id = self._pending_cis.pop(cis_handle)
|
|
3066
|
+
self.cis_links[cis_handle] = CisLink(
|
|
3067
|
+
device=self,
|
|
3068
|
+
acl_connection=acl_connection,
|
|
3069
|
+
handle=cis_handle,
|
|
3070
|
+
cis_id=cis_id,
|
|
3071
|
+
cig_id=cig_id,
|
|
3072
|
+
)
|
|
3073
|
+
|
|
3074
|
+
result = await self.send_command(
|
|
3075
|
+
HCI_LE_Create_CIS_Command(
|
|
3076
|
+
cis_connection_handle=[p[0] for p in cis_acl_pairs],
|
|
3077
|
+
acl_connection_handle=[p[1] for p in cis_acl_pairs],
|
|
3078
|
+
),
|
|
3079
|
+
)
|
|
3080
|
+
if result.status != HCI_COMMAND_STATUS_PENDING:
|
|
3081
|
+
logger.warning(
|
|
3082
|
+
'HCI_LE_Create_CIS_Command failed: '
|
|
3083
|
+
f'{HCI_Constant.error_name(result.status)}'
|
|
3084
|
+
)
|
|
3085
|
+
raise HCI_StatusError(result)
|
|
3086
|
+
|
|
3087
|
+
pending_cis_establishments: Dict[int, asyncio.Future[CisLink]] = {}
|
|
3088
|
+
for cis_handle, _ in cis_acl_pairs:
|
|
3089
|
+
pending_cis_establishments[
|
|
3090
|
+
cis_handle
|
|
3091
|
+
] = asyncio.get_running_loop().create_future()
|
|
3092
|
+
|
|
3093
|
+
with closing(EventWatcher()) as watcher:
|
|
3094
|
+
|
|
3095
|
+
@watcher.on(self, 'cis_establishment')
|
|
3096
|
+
def on_cis_establishment(cis_link: CisLink) -> None:
|
|
3097
|
+
if pending_future := pending_cis_establishments.get(
|
|
3098
|
+
cis_link.handle, None
|
|
3099
|
+
):
|
|
3100
|
+
pending_future.set_result(cis_link)
|
|
3101
|
+
|
|
3102
|
+
return await asyncio.gather(*pending_cis_establishments.values())
|
|
3103
|
+
|
|
3104
|
+
# [LE only]
|
|
3105
|
+
@experimental('Only for testing.')
|
|
3106
|
+
async def accept_cis_request(self, handle: int) -> CisLink:
|
|
3107
|
+
result = await self.send_command(
|
|
3108
|
+
HCI_LE_Accept_CIS_Request_Command(connection_handle=handle),
|
|
3109
|
+
)
|
|
3110
|
+
if result.status != HCI_COMMAND_STATUS_PENDING:
|
|
3111
|
+
logger.warning(
|
|
3112
|
+
'HCI_LE_Accept_CIS_Request_Command failed: '
|
|
3113
|
+
f'{HCI_Constant.error_name(result.status)}'
|
|
3114
|
+
)
|
|
3115
|
+
raise HCI_StatusError(result)
|
|
3116
|
+
|
|
3117
|
+
pending_cis_establishment = asyncio.get_running_loop().create_future()
|
|
3118
|
+
|
|
3119
|
+
with closing(EventWatcher()) as watcher:
|
|
3120
|
+
|
|
3121
|
+
@watcher.on(self, 'cis_establishment')
|
|
3122
|
+
def on_cis_establishment(cis_link: CisLink) -> None:
|
|
3123
|
+
if cis_link.handle == handle:
|
|
3124
|
+
pending_cis_establishment.set_result(cis_link)
|
|
3125
|
+
|
|
3126
|
+
return await pending_cis_establishment
|
|
3127
|
+
|
|
3128
|
+
# [LE only]
|
|
3129
|
+
@experimental('Only for testing.')
|
|
3130
|
+
async def reject_cis_request(
|
|
3131
|
+
self,
|
|
3132
|
+
handle: int,
|
|
3133
|
+
reason: int = HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR,
|
|
3134
|
+
) -> None:
|
|
3135
|
+
result = await self.send_command(
|
|
3136
|
+
HCI_LE_Reject_CIS_Request_Command(connection_handle=handle, reason=reason),
|
|
3137
|
+
)
|
|
3138
|
+
if result.status != HCI_COMMAND_STATUS_PENDING:
|
|
3139
|
+
logger.warning(
|
|
3140
|
+
'HCI_LE_Reject_CIS_Request_Command failed: '
|
|
3141
|
+
f'{HCI_Constant.error_name(result.status)}'
|
|
3142
|
+
)
|
|
3143
|
+
raise HCI_StatusError(result)
|
|
3144
|
+
|
|
2799
3145
|
@host_event_handler
|
|
2800
3146
|
def on_flush(self):
|
|
2801
3147
|
self.emit('flush')
|
|
@@ -2888,13 +3234,18 @@ class Device(CompositeEventEmitter):
|
|
|
2888
3234
|
# Guess which own address type is used for this connection.
|
|
2889
3235
|
# This logic is somewhat correct but may need to be improved
|
|
2890
3236
|
# when multiple advertising are run simultaneously.
|
|
3237
|
+
advertiser = None
|
|
2891
3238
|
if self.connect_own_address_type is not None:
|
|
2892
3239
|
own_address_type = self.connect_own_address_type
|
|
3240
|
+
elif self.legacy_advertiser:
|
|
3241
|
+
own_address_type = self.legacy_advertiser.own_address_type
|
|
3242
|
+
# Store advertiser for restarting - it's only required for legacy, since
|
|
3243
|
+
# extended advertisement produces HCI_Advertising_Set_Terminated.
|
|
3244
|
+
if self.legacy_advertiser.auto_restart:
|
|
3245
|
+
advertiser = self.legacy_advertiser
|
|
2893
3246
|
else:
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
# We are no longer advertising
|
|
2897
|
-
self.advertising = False
|
|
3247
|
+
# For extended advertisement, determining own address type later.
|
|
3248
|
+
own_address_type = OwnAddressType.RANDOM
|
|
2898
3249
|
|
|
2899
3250
|
if own_address_type in (
|
|
2900
3251
|
OwnAddressType.PUBLIC,
|
|
@@ -2916,6 +3267,7 @@ class Device(CompositeEventEmitter):
|
|
|
2916
3267
|
connection_parameters,
|
|
2917
3268
|
ConnectionPHY(HCI_LE_1M_PHY, HCI_LE_1M_PHY),
|
|
2918
3269
|
)
|
|
3270
|
+
connection.advertiser_after_disconnection = advertiser
|
|
2919
3271
|
self.connections[connection_handle] = connection
|
|
2920
3272
|
|
|
2921
3273
|
# If supported, read which PHY we're connected with before
|
|
@@ -2947,10 +3299,10 @@ class Device(CompositeEventEmitter):
|
|
|
2947
3299
|
# For directed advertising, this means a timeout
|
|
2948
3300
|
if (
|
|
2949
3301
|
transport == BT_LE_TRANSPORT
|
|
2950
|
-
and self.
|
|
2951
|
-
and self.advertising_type.is_directed
|
|
3302
|
+
and self.legacy_advertiser
|
|
3303
|
+
and self.legacy_advertiser.advertising_type.is_directed
|
|
2952
3304
|
):
|
|
2953
|
-
self.
|
|
3305
|
+
self.legacy_advertiser = None
|
|
2954
3306
|
|
|
2955
3307
|
# Notify listeners
|
|
2956
3308
|
error = core.ConnectionError(
|
|
@@ -3000,30 +3352,49 @@ class Device(CompositeEventEmitter):
|
|
|
3000
3352
|
)
|
|
3001
3353
|
|
|
3002
3354
|
@host_event_handler
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3355
|
+
def on_disconnection(self, connection_handle: int, reason: int) -> None:
|
|
3356
|
+
if connection := self.connections.pop(connection_handle, None):
|
|
3357
|
+
logger.debug(
|
|
3358
|
+
f'*** Disconnection: [0x{connection.handle:04X}] '
|
|
3359
|
+
f'{connection.peer_address} as {connection.role_name}, reason={reason}'
|
|
3360
|
+
)
|
|
3361
|
+
connection.emit('disconnection', reason)
|
|
3362
|
+
|
|
3363
|
+
# Cleanup subsystems that maintain per-connection state
|
|
3364
|
+
self.gatt_server.on_disconnection(connection)
|
|
3365
|
+
|
|
3366
|
+
# Restart advertising if auto-restart is enabled
|
|
3367
|
+
if advertiser := connection.advertiser_after_disconnection:
|
|
3368
|
+
logger.debug('restarting advertising')
|
|
3369
|
+
if isinstance(advertiser, LegacyAdvertiser):
|
|
3370
|
+
self.abort_on(
|
|
3371
|
+
'flush',
|
|
3372
|
+
self.start_legacy_advertising(
|
|
3373
|
+
advertising_type=advertiser.advertising_type,
|
|
3374
|
+
own_address_type=advertiser.own_address_type,
|
|
3375
|
+
advertising_data=advertiser.advertising_data,
|
|
3376
|
+
scan_response_data=advertiser.scan_response_data,
|
|
3377
|
+
auto_restart=True,
|
|
3378
|
+
),
|
|
3379
|
+
)
|
|
3380
|
+
elif isinstance(advertiser, ExtendedAdvertiser):
|
|
3381
|
+
self.abort_on(
|
|
3382
|
+
'flush',
|
|
3383
|
+
self.start_extended_advertising(
|
|
3384
|
+
advertising_properties=advertiser.advertising_properties,
|
|
3385
|
+
own_address_type=advertiser.own_address_type,
|
|
3386
|
+
advertising_data=advertiser.advertising_data,
|
|
3387
|
+
scan_response_data=advertiser.scan_response_data,
|
|
3388
|
+
auto_restart=True,
|
|
3389
|
+
),
|
|
3390
|
+
)
|
|
3391
|
+
elif sco_link := self.sco_links.pop(connection_handle, None):
|
|
3392
|
+
sco_link.emit('disconnection', reason)
|
|
3393
|
+
elif cis_link := self.cis_links.pop(connection_handle, None):
|
|
3394
|
+
cis_link.emit('disconnection', reason)
|
|
3395
|
+
else:
|
|
3396
|
+
logger.error(
|
|
3397
|
+
f'*** Unknown disconnection handle=0x{connection_handle}, reason={reason} ***'
|
|
3027
3398
|
)
|
|
3028
3399
|
|
|
3029
3400
|
@host_event_handler
|
|
@@ -3174,7 +3545,7 @@ class Device(CompositeEventEmitter):
|
|
|
3174
3545
|
try:
|
|
3175
3546
|
if await connection.abort_on('disconnection', method()):
|
|
3176
3547
|
await self.host.send_command(
|
|
3177
|
-
HCI_User_Confirmation_Request_Reply_Command(
|
|
3548
|
+
HCI_User_Confirmation_Request_Reply_Command(
|
|
3178
3549
|
bd_addr=connection.peer_address
|
|
3179
3550
|
)
|
|
3180
3551
|
)
|
|
@@ -3183,7 +3554,7 @@ class Device(CompositeEventEmitter):
|
|
|
3183
3554
|
logger.warning(f'exception while confirming: {error}')
|
|
3184
3555
|
|
|
3185
3556
|
await self.host.send_command(
|
|
3186
|
-
HCI_User_Confirmation_Request_Negative_Reply_Command(
|
|
3557
|
+
HCI_User_Confirmation_Request_Negative_Reply_Command(
|
|
3187
3558
|
bd_addr=connection.peer_address
|
|
3188
3559
|
)
|
|
3189
3560
|
)
|
|
@@ -3204,7 +3575,7 @@ class Device(CompositeEventEmitter):
|
|
|
3204
3575
|
)
|
|
3205
3576
|
if number is not None:
|
|
3206
3577
|
await self.host.send_command(
|
|
3207
|
-
HCI_User_Passkey_Request_Reply_Command(
|
|
3578
|
+
HCI_User_Passkey_Request_Reply_Command(
|
|
3208
3579
|
bd_addr=connection.peer_address, numeric_value=number
|
|
3209
3580
|
)
|
|
3210
3581
|
)
|
|
@@ -3213,7 +3584,7 @@ class Device(CompositeEventEmitter):
|
|
|
3213
3584
|
logger.warning(f'exception while asking for pass-key: {error}')
|
|
3214
3585
|
|
|
3215
3586
|
await self.host.send_command(
|
|
3216
|
-
HCI_User_Passkey_Request_Negative_Reply_Command(
|
|
3587
|
+
HCI_User_Passkey_Request_Negative_Reply_Command(
|
|
3217
3588
|
bd_addr=connection.peer_address
|
|
3218
3589
|
)
|
|
3219
3590
|
)
|
|
@@ -3302,6 +3673,131 @@ class Device(CompositeEventEmitter):
|
|
|
3302
3673
|
connection.emit('remote_name_failure', error)
|
|
3303
3674
|
self.emit('remote_name_failure', address, error)
|
|
3304
3675
|
|
|
3676
|
+
# [Classic only]
|
|
3677
|
+
@host_event_handler
|
|
3678
|
+
@with_connection_from_address
|
|
3679
|
+
@experimental('Only for testing.')
|
|
3680
|
+
def on_sco_connection(
|
|
3681
|
+
self, acl_connection: Connection, sco_handle: int, link_type: int
|
|
3682
|
+
) -> None:
|
|
3683
|
+
logger.debug(
|
|
3684
|
+
f'*** SCO connected: {acl_connection.peer_address}, '
|
|
3685
|
+
f'sco_handle=[0x{sco_handle:04X}], '
|
|
3686
|
+
f'link_type=[0x{link_type:02X}] ***'
|
|
3687
|
+
)
|
|
3688
|
+
sco_link = self.sco_links[sco_handle] = ScoLink(
|
|
3689
|
+
device=self,
|
|
3690
|
+
acl_connection=acl_connection,
|
|
3691
|
+
handle=sco_handle,
|
|
3692
|
+
link_type=link_type,
|
|
3693
|
+
)
|
|
3694
|
+
self.emit('sco_connection', sco_link)
|
|
3695
|
+
|
|
3696
|
+
# [Classic only]
|
|
3697
|
+
@host_event_handler
|
|
3698
|
+
@with_connection_from_address
|
|
3699
|
+
@experimental('Only for testing.')
|
|
3700
|
+
def on_sco_connection_failure(
|
|
3701
|
+
self, acl_connection: Connection, status: int
|
|
3702
|
+
) -> None:
|
|
3703
|
+
logger.debug(f'*** SCO connection failure: {acl_connection.peer_address}***')
|
|
3704
|
+
self.emit('sco_connection_failure')
|
|
3705
|
+
|
|
3706
|
+
# [Classic only]
|
|
3707
|
+
@host_event_handler
|
|
3708
|
+
@experimental('Only for testing')
|
|
3709
|
+
def on_sco_packet(self, sco_handle: int, packet: HCI_SynchronousDataPacket) -> None:
|
|
3710
|
+
if sco_link := self.sco_links.get(sco_handle, None):
|
|
3711
|
+
sco_link.emit('pdu', packet)
|
|
3712
|
+
|
|
3713
|
+
# [LE only]
|
|
3714
|
+
@host_event_handler
|
|
3715
|
+
@experimental('Only for testing')
|
|
3716
|
+
def on_advertising_set_termination(
|
|
3717
|
+
self,
|
|
3718
|
+
status: int,
|
|
3719
|
+
advertising_handle: int,
|
|
3720
|
+
connection_handle: int,
|
|
3721
|
+
) -> None:
|
|
3722
|
+
if status == HCI_SUCCESS:
|
|
3723
|
+
connection = self.lookup_connection(connection_handle)
|
|
3724
|
+
if advertiser := self.extended_advertisers.pop(advertising_handle, None):
|
|
3725
|
+
if connection:
|
|
3726
|
+
if advertiser.auto_restart:
|
|
3727
|
+
connection.advertiser_after_disconnection = advertiser
|
|
3728
|
+
if advertiser.own_address_type in (
|
|
3729
|
+
OwnAddressType.PUBLIC,
|
|
3730
|
+
OwnAddressType.RESOLVABLE_OR_PUBLIC,
|
|
3731
|
+
):
|
|
3732
|
+
connection.self_address = self.public_address
|
|
3733
|
+
else:
|
|
3734
|
+
connection.self_address = self.random_address
|
|
3735
|
+
advertiser.emit('termination', status)
|
|
3736
|
+
|
|
3737
|
+
# [LE only]
|
|
3738
|
+
@host_event_handler
|
|
3739
|
+
@with_connection_from_handle
|
|
3740
|
+
@experimental('Only for testing')
|
|
3741
|
+
def on_cis_request(
|
|
3742
|
+
self,
|
|
3743
|
+
acl_connection: Connection,
|
|
3744
|
+
cis_handle: int,
|
|
3745
|
+
cig_id: int,
|
|
3746
|
+
cis_id: int,
|
|
3747
|
+
) -> None:
|
|
3748
|
+
logger.debug(
|
|
3749
|
+
f'*** CIS Request '
|
|
3750
|
+
f'acl_handle=[0x{acl_connection.handle:04X}]{acl_connection.peer_address}, '
|
|
3751
|
+
f'cis_handle=[0x{cis_handle:04X}], '
|
|
3752
|
+
f'cig_id=[0x{cig_id:02X}], '
|
|
3753
|
+
f'cis_id=[0x{cis_id:02X}] ***'
|
|
3754
|
+
)
|
|
3755
|
+
# LE_CIS_Established event doesn't provide info, so we must store them here.
|
|
3756
|
+
self.cis_links[cis_handle] = CisLink(
|
|
3757
|
+
device=self,
|
|
3758
|
+
acl_connection=acl_connection,
|
|
3759
|
+
handle=cis_handle,
|
|
3760
|
+
cig_id=cig_id,
|
|
3761
|
+
cis_id=cis_id,
|
|
3762
|
+
)
|
|
3763
|
+
self.emit('cis_request', acl_connection, cis_handle, cig_id, cis_id)
|
|
3764
|
+
|
|
3765
|
+
# [LE only]
|
|
3766
|
+
@host_event_handler
|
|
3767
|
+
@experimental('Only for testing')
|
|
3768
|
+
def on_cis_establishment(self, cis_handle: int) -> None:
|
|
3769
|
+
cis_link = self.cis_links[cis_handle]
|
|
3770
|
+
cis_link.state = CisLink.State.ESTABLISHED
|
|
3771
|
+
|
|
3772
|
+
assert cis_link.acl_connection
|
|
3773
|
+
|
|
3774
|
+
logger.debug(
|
|
3775
|
+
f'*** CIS Establishment '
|
|
3776
|
+
f'{cis_link.acl_connection.peer_address}, '
|
|
3777
|
+
f'cis_handle=[0x{cis_handle:04X}], '
|
|
3778
|
+
f'cig_id=[0x{cis_link.cig_id:02X}], '
|
|
3779
|
+
f'cis_id=[0x{cis_link.cis_id:02X}] ***'
|
|
3780
|
+
)
|
|
3781
|
+
|
|
3782
|
+
cis_link.emit('establishment')
|
|
3783
|
+
self.emit('cis_establishment', cis_link)
|
|
3784
|
+
|
|
3785
|
+
# [LE only]
|
|
3786
|
+
@host_event_handler
|
|
3787
|
+
@experimental('Only for testing')
|
|
3788
|
+
def on_cis_establishment_failure(self, cis_handle: int, status: int) -> None:
|
|
3789
|
+
logger.debug(f'*** CIS Establishment Failure: cis=[0x{cis_handle:04X}] ***')
|
|
3790
|
+
if cis_link := self.cis_links.pop(cis_handle, None):
|
|
3791
|
+
cis_link.emit('establishment_failure')
|
|
3792
|
+
self.emit('cis_establishment_failure', cis_handle, status)
|
|
3793
|
+
|
|
3794
|
+
# [LE only]
|
|
3795
|
+
@host_event_handler
|
|
3796
|
+
@experimental('Only for testing')
|
|
3797
|
+
def on_iso_packet(self, handle: int, packet: HCI_IsoDataPacket) -> None:
|
|
3798
|
+
if cis_link := self.cis_links.get(handle, None):
|
|
3799
|
+
cis_link.emit('pdu', packet)
|
|
3800
|
+
|
|
3305
3801
|
@host_event_handler
|
|
3306
3802
|
@with_connection_from_handle
|
|
3307
3803
|
def on_connection_encryption_change(self, connection, encryption):
|