bumble 0.0.211__py3-none-any.whl → 0.0.213__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/a2dp.py +6 -0
- bumble/apps/README.md +0 -3
- bumble/apps/auracast.py +11 -9
- bumble/apps/bench.py +482 -31
- bumble/apps/console.py +5 -5
- bumble/apps/controller_info.py +47 -10
- bumble/apps/controller_loopback.py +7 -3
- bumble/apps/controllers.py +2 -2
- bumble/apps/device_info.py +2 -2
- bumble/apps/gatt_dump.py +2 -2
- bumble/apps/gg_bridge.py +2 -2
- bumble/apps/hci_bridge.py +2 -2
- bumble/apps/l2cap_bridge.py +2 -2
- bumble/apps/lea_unicast/app.py +6 -1
- bumble/apps/pair.py +204 -43
- bumble/apps/pandora_server.py +2 -2
- bumble/apps/rfcomm_bridge.py +1 -1
- bumble/apps/scan.py +2 -2
- bumble/apps/show.py +4 -2
- bumble/apps/speaker/speaker.html +1 -0
- bumble/apps/speaker/speaker.js +113 -62
- bumble/apps/speaker/speaker.py +126 -18
- bumble/at.py +4 -4
- bumble/att.py +15 -18
- bumble/avc.py +7 -7
- bumble/avctp.py +5 -5
- bumble/avdtp.py +138 -88
- bumble/avrcp.py +52 -58
- bumble/colors.py +2 -2
- bumble/controller.py +84 -23
- bumble/core.py +13 -7
- bumble/{crypto.py → crypto/__init__.py} +11 -95
- bumble/crypto/builtin.py +652 -0
- bumble/crypto/cryptography.py +84 -0
- bumble/device.py +688 -345
- bumble/drivers/__init__.py +2 -2
- bumble/drivers/common.py +0 -2
- bumble/drivers/intel.py +40 -40
- bumble/drivers/rtk.py +28 -35
- bumble/gatt.py +7 -9
- bumble/gatt_adapters.py +4 -5
- bumble/gatt_client.py +31 -34
- bumble/gatt_server.py +15 -17
- bumble/hci.py +2635 -2878
- bumble/helpers.py +4 -5
- bumble/hfp.py +76 -57
- bumble/hid.py +24 -12
- bumble/host.py +117 -34
- bumble/keys.py +68 -52
- bumble/l2cap.py +329 -403
- bumble/link.py +6 -270
- bumble/pairing.py +23 -20
- bumble/pandora/__init__.py +1 -1
- bumble/pandora/config.py +2 -2
- bumble/pandora/device.py +6 -6
- bumble/pandora/host.py +38 -39
- bumble/pandora/l2cap.py +4 -4
- bumble/pandora/security.py +73 -57
- bumble/pandora/utils.py +3 -3
- bumble/profiles/aics.py +3 -5
- bumble/profiles/ancs.py +3 -1
- bumble/profiles/ascs.py +143 -136
- bumble/profiles/asha.py +13 -8
- bumble/profiles/bap.py +3 -4
- bumble/profiles/csip.py +3 -5
- bumble/profiles/device_information_service.py +2 -2
- bumble/profiles/gap.py +2 -2
- bumble/profiles/gatt_service.py +1 -3
- bumble/profiles/hap.py +42 -58
- bumble/profiles/le_audio.py +4 -4
- bumble/profiles/mcp.py +16 -13
- bumble/profiles/vcs.py +8 -10
- bumble/profiles/vocs.py +6 -9
- bumble/rfcomm.py +27 -18
- bumble/rtp.py +1 -2
- bumble/sdp.py +2 -2
- bumble/smp.py +71 -69
- bumble/tools/rtk_util.py +2 -2
- bumble/transport/__init__.py +2 -16
- bumble/transport/android_netsim.py +5 -5
- bumble/transport/common.py +4 -4
- bumble/transport/pyusb.py +2 -2
- bumble/utils.py +2 -5
- bumble/vendor/android/hci.py +118 -200
- bumble/vendor/zephyr/hci.py +32 -27
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/METADATA +5 -5
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/RECORD +92 -93
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/WHEEL +1 -1
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/entry_points.txt +0 -1
- bumble/apps/link_relay/__init__.py +0 -0
- bumble/apps/link_relay/link_relay.py +0 -289
- bumble/apps/link_relay/logging.yml +0 -21
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/top_level.txt +0 -0
bumble/device.py
CHANGED
|
@@ -35,12 +35,10 @@ import secrets
|
|
|
35
35
|
import sys
|
|
36
36
|
from typing import (
|
|
37
37
|
Any,
|
|
38
|
+
Awaitable,
|
|
38
39
|
Callable,
|
|
39
40
|
ClassVar,
|
|
40
|
-
Deque,
|
|
41
|
-
Dict,
|
|
42
41
|
Optional,
|
|
43
|
-
Type,
|
|
44
42
|
TypeVar,
|
|
45
43
|
Union,
|
|
46
44
|
cast,
|
|
@@ -61,7 +59,6 @@ from bumble.core import (
|
|
|
61
59
|
BaseBumbleError,
|
|
62
60
|
ConnectionParameterUpdateError,
|
|
63
61
|
CommandTimeoutError,
|
|
64
|
-
ConnectionParameters,
|
|
65
62
|
ConnectionPHY,
|
|
66
63
|
InvalidArgumentError,
|
|
67
64
|
InvalidOperationError,
|
|
@@ -88,6 +85,7 @@ from bumble.profiles import gatt_service
|
|
|
88
85
|
if TYPE_CHECKING:
|
|
89
86
|
from bumble.transport.common import TransportSource, TransportSink
|
|
90
87
|
|
|
88
|
+
_T = TypeVar('_T')
|
|
91
89
|
|
|
92
90
|
# -----------------------------------------------------------------------------
|
|
93
91
|
# Logging
|
|
@@ -100,9 +98,9 @@ logger = logging.getLogger(__name__)
|
|
|
100
98
|
# fmt: off
|
|
101
99
|
# pylint: disable=line-too-long
|
|
102
100
|
|
|
103
|
-
DEVICE_MIN_SCAN_INTERVAL =
|
|
101
|
+
DEVICE_MIN_SCAN_INTERVAL = 2.5
|
|
104
102
|
DEVICE_MAX_SCAN_INTERVAL = 10240
|
|
105
|
-
DEVICE_MIN_SCAN_WINDOW =
|
|
103
|
+
DEVICE_MIN_SCAN_WINDOW = 2.5
|
|
106
104
|
DEVICE_MAX_SCAN_WINDOW = 10240
|
|
107
105
|
DEVICE_MIN_LE_RSSI = -127
|
|
108
106
|
DEVICE_MAX_LE_RSSI = 20
|
|
@@ -141,6 +139,9 @@ DEVICE_DEFAULT_ADVERTISING_TX_POWER = (
|
|
|
141
139
|
DEVICE_DEFAULT_PERIODIC_ADVERTISING_SYNC_SKIP = 0
|
|
142
140
|
DEVICE_DEFAULT_PERIODIC_ADVERTISING_SYNC_TIMEOUT = 5.0
|
|
143
141
|
DEVICE_DEFAULT_LE_RPA_TIMEOUT = 15 * 60 # 15 minutes (in seconds)
|
|
142
|
+
DEVICE_DEFAULT_ISO_CIS_MAX_SDU = 251
|
|
143
|
+
DEVICE_DEFAULT_ISO_CIS_RTN = 10
|
|
144
|
+
DEVICE_DEFAULT_ISO_CIS_MAX_TRANSPORT_LATENCY = 100
|
|
144
145
|
|
|
145
146
|
# fmt: on
|
|
146
147
|
# pylint: enable=line-too-long
|
|
@@ -203,25 +204,35 @@ class Advertisement:
|
|
|
203
204
|
# -----------------------------------------------------------------------------
|
|
204
205
|
class LegacyAdvertisement(Advertisement):
|
|
205
206
|
@classmethod
|
|
206
|
-
def from_advertising_report(
|
|
207
|
+
def from_advertising_report(
|
|
208
|
+
cls, report: hci.HCI_LE_Advertising_Report_Event.Report
|
|
209
|
+
) -> Self:
|
|
207
210
|
return cls(
|
|
208
211
|
address=report.address,
|
|
209
212
|
rssi=report.rssi,
|
|
210
213
|
is_legacy=True,
|
|
211
|
-
is_connectable=
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
214
|
+
is_connectable=(
|
|
215
|
+
report.event_type
|
|
216
|
+
in (
|
|
217
|
+
hci.HCI_LE_Advertising_Report_Event.EventType.ADV_IND,
|
|
218
|
+
hci.HCI_LE_Advertising_Report_Event.EventType.ADV_DIRECT_IND,
|
|
219
|
+
)
|
|
220
|
+
),
|
|
221
|
+
is_directed=(
|
|
222
|
+
report.event_type
|
|
223
|
+
== hci.HCI_LE_Advertising_Report_Event.EventType.ADV_DIRECT_IND
|
|
224
|
+
),
|
|
225
|
+
is_scannable=(
|
|
226
|
+
report.event_type
|
|
227
|
+
in (
|
|
228
|
+
hci.HCI_LE_Advertising_Report_Event.EventType.ADV_IND,
|
|
229
|
+
hci.HCI_LE_Advertising_Report_Event.EventType.ADV_SCAN_IND,
|
|
230
|
+
)
|
|
215
231
|
),
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
in (
|
|
220
|
-
hci.HCI_LE_Advertising_Report_Event.ADV_IND,
|
|
221
|
-
hci.HCI_LE_Advertising_Report_Event.ADV_SCAN_IND,
|
|
232
|
+
is_scan_response=(
|
|
233
|
+
report.event_type
|
|
234
|
+
== hci.HCI_LE_Advertising_Report_Event.EventType.SCAN_RSP
|
|
222
235
|
),
|
|
223
|
-
is_scan_response=report.event_type
|
|
224
|
-
== hci.HCI_LE_Advertising_Report_Event.SCAN_RSP,
|
|
225
236
|
data_bytes=report.data,
|
|
226
237
|
)
|
|
227
238
|
|
|
@@ -229,18 +240,20 @@ class LegacyAdvertisement(Advertisement):
|
|
|
229
240
|
# -----------------------------------------------------------------------------
|
|
230
241
|
class ExtendedAdvertisement(Advertisement):
|
|
231
242
|
@classmethod
|
|
232
|
-
def from_advertising_report(
|
|
243
|
+
def from_advertising_report(
|
|
244
|
+
cls, report: hci.HCI_LE_Extended_Advertising_Report_Event.Report
|
|
245
|
+
) -> Self:
|
|
233
246
|
# fmt: off
|
|
234
247
|
# pylint: disable=line-too-long
|
|
235
248
|
return cls(
|
|
236
249
|
address = report.address,
|
|
237
250
|
rssi = report.rssi,
|
|
238
|
-
is_legacy = report.event_type &
|
|
251
|
+
is_legacy = (report.event_type & hci.HCI_LE_Extended_Advertising_Report_Event.EventType.LEGACY_ADVERTISING_PDU_USED) != 0,
|
|
239
252
|
is_anonymous = report.address.address_type == hci.HCI_LE_Extended_Advertising_Report_Event.ANONYMOUS_ADDRESS_TYPE,
|
|
240
|
-
is_connectable = report.event_type &
|
|
241
|
-
is_directed = report.event_type &
|
|
242
|
-
is_scannable = report.event_type &
|
|
243
|
-
is_scan_response = report.event_type &
|
|
253
|
+
is_connectable = (report.event_type & hci.HCI_LE_Extended_Advertising_Report_Event.EventType.CONNECTABLE_ADVERTISING) != 0,
|
|
254
|
+
is_directed = (report.event_type & hci.HCI_LE_Extended_Advertising_Report_Event.EventType.DIRECTED_ADVERTISING) != 0,
|
|
255
|
+
is_scannable = (report.event_type & hci.HCI_LE_Extended_Advertising_Report_Event.EventType.SCANNABLE_ADVERTISING) != 0,
|
|
256
|
+
is_scan_response = (report.event_type & hci.HCI_LE_Extended_Advertising_Report_Event.EventType.SCAN_RESPONSE) != 0,
|
|
244
257
|
is_complete = (report.event_type >> 5 & 3) == hci.HCI_LE_Extended_Advertising_Report_Event.DATA_COMPLETE,
|
|
245
258
|
is_truncated = (report.event_type >> 5 & 3) == hci.HCI_LE_Extended_Advertising_Report_Event.DATA_INCOMPLETE_TRUNCATED_NO_MORE_TO_COME,
|
|
246
259
|
primary_phy = report.primary_phy,
|
|
@@ -437,7 +450,7 @@ class AdvertisingEventProperties:
|
|
|
437
450
|
|
|
438
451
|
@classmethod
|
|
439
452
|
def from_advertising_type(
|
|
440
|
-
cls:
|
|
453
|
+
cls: type[AdvertisingEventProperties],
|
|
441
454
|
advertising_type: AdvertisingType,
|
|
442
455
|
) -> AdvertisingEventProperties:
|
|
443
456
|
return cls(
|
|
@@ -479,12 +492,23 @@ class PeriodicAdvertisement:
|
|
|
479
492
|
|
|
480
493
|
# -----------------------------------------------------------------------------
|
|
481
494
|
@dataclass
|
|
482
|
-
class
|
|
495
|
+
class BigInfoAdvertisement:
|
|
496
|
+
class Framing(utils.OpenIntEnum):
|
|
497
|
+
# fmt: off
|
|
498
|
+
UNFRAMED = 0X00
|
|
499
|
+
FRAMED_SEGMENTABLE_MODE = 0X01
|
|
500
|
+
FRAMED_UNSEGMENTED_MODE = 0X02
|
|
501
|
+
|
|
502
|
+
class Encryption(utils.OpenIntEnum):
|
|
503
|
+
# fmt: off
|
|
504
|
+
UNENCRYPTED = 0x00
|
|
505
|
+
ENCRYPTED = 0x01
|
|
506
|
+
|
|
483
507
|
address: hci.Address
|
|
484
508
|
sid: int
|
|
485
509
|
num_bis: int
|
|
486
510
|
nse: int
|
|
487
|
-
iso_interval:
|
|
511
|
+
iso_interval: float
|
|
488
512
|
bn: int
|
|
489
513
|
pto: int
|
|
490
514
|
irc: int
|
|
@@ -492,8 +516,8 @@ class BIGInfoAdvertisement:
|
|
|
492
516
|
sdu_interval: int
|
|
493
517
|
max_sdu: int
|
|
494
518
|
phy: hci.Phy
|
|
495
|
-
|
|
496
|
-
|
|
519
|
+
framing: Framing
|
|
520
|
+
encryption: Encryption
|
|
497
521
|
|
|
498
522
|
@classmethod
|
|
499
523
|
def from_report(cls, address: hci.Address, sid: int, report) -> Self:
|
|
@@ -502,7 +526,7 @@ class BIGInfoAdvertisement:
|
|
|
502
526
|
sid,
|
|
503
527
|
report.num_bis,
|
|
504
528
|
report.nse,
|
|
505
|
-
report.iso_interval,
|
|
529
|
+
report.iso_interval * 1.25,
|
|
506
530
|
report.bn,
|
|
507
531
|
report.pto,
|
|
508
532
|
report.irc,
|
|
@@ -510,8 +534,8 @@ class BIGInfoAdvertisement:
|
|
|
510
534
|
report.sdu_interval,
|
|
511
535
|
report.max_sdu,
|
|
512
536
|
hci.Phy(report.phy),
|
|
513
|
-
report.framing
|
|
514
|
-
report.encryption
|
|
537
|
+
cls.Framing(report.framing),
|
|
538
|
+
cls.Encryption(report.encryption),
|
|
515
539
|
)
|
|
516
540
|
|
|
517
541
|
|
|
@@ -529,8 +553,8 @@ class AdvertisingParameters:
|
|
|
529
553
|
advertising_event_properties: AdvertisingEventProperties = field(
|
|
530
554
|
default_factory=AdvertisingEventProperties
|
|
531
555
|
)
|
|
532
|
-
primary_advertising_interval_min:
|
|
533
|
-
primary_advertising_interval_max:
|
|
556
|
+
primary_advertising_interval_min: float = DEVICE_DEFAULT_ADVERTISING_INTERVAL
|
|
557
|
+
primary_advertising_interval_max: float = DEVICE_DEFAULT_ADVERTISING_INTERVAL
|
|
534
558
|
primary_advertising_channel_map: (
|
|
535
559
|
hci.HCI_LE_Set_Extended_Advertising_Parameters_Command.ChannelMap
|
|
536
560
|
) = (
|
|
@@ -554,8 +578,8 @@ class AdvertisingParameters:
|
|
|
554
578
|
# -----------------------------------------------------------------------------
|
|
555
579
|
@dataclass
|
|
556
580
|
class PeriodicAdvertisingParameters:
|
|
557
|
-
periodic_advertising_interval_min:
|
|
558
|
-
periodic_advertising_interval_max:
|
|
581
|
+
periodic_advertising_interval_min: float = DEVICE_DEFAULT_ADVERTISING_INTERVAL
|
|
582
|
+
periodic_advertising_interval_max: float = DEVICE_DEFAULT_ADVERTISING_INTERVAL
|
|
559
583
|
periodic_advertising_properties: (
|
|
560
584
|
hci.HCI_LE_Set_Periodic_Advertising_Parameters_Command.Properties
|
|
561
585
|
) = field(
|
|
@@ -581,6 +605,12 @@ class AdvertisingSet(utils.EventEmitter):
|
|
|
581
605
|
enabled: bool = False
|
|
582
606
|
periodic_enabled: bool = False
|
|
583
607
|
|
|
608
|
+
EVENT_START = "start"
|
|
609
|
+
EVENT_STOP = "stop"
|
|
610
|
+
EVENT_START_PERIODIC = "start_periodic"
|
|
611
|
+
EVENT_STOP_PERIODIC = "stop_periodic"
|
|
612
|
+
EVENT_TERMINATION = "termination"
|
|
613
|
+
|
|
584
614
|
def __post_init__(self) -> None:
|
|
585
615
|
super().__init__()
|
|
586
616
|
|
|
@@ -679,8 +709,12 @@ class AdvertisingSet(utils.EventEmitter):
|
|
|
679
709
|
await self.device.send_command(
|
|
680
710
|
hci.HCI_LE_Set_Periodic_Advertising_Parameters_Command(
|
|
681
711
|
advertising_handle=self.advertising_handle,
|
|
682
|
-
periodic_advertising_interval_min=
|
|
683
|
-
|
|
712
|
+
periodic_advertising_interval_min=int(
|
|
713
|
+
advertising_parameters.periodic_advertising_interval_min / 1.25
|
|
714
|
+
),
|
|
715
|
+
periodic_advertising_interval_max=int(
|
|
716
|
+
advertising_parameters.periodic_advertising_interval_max / 1.25
|
|
717
|
+
),
|
|
684
718
|
periodic_advertising_properties=advertising_parameters.periodic_advertising_properties,
|
|
685
719
|
),
|
|
686
720
|
check_result=True,
|
|
@@ -731,7 +765,7 @@ class AdvertisingSet(utils.EventEmitter):
|
|
|
731
765
|
)
|
|
732
766
|
self.enabled = True
|
|
733
767
|
|
|
734
|
-
self.emit(
|
|
768
|
+
self.emit(self.EVENT_START)
|
|
735
769
|
|
|
736
770
|
async def stop(self) -> None:
|
|
737
771
|
await self.device.send_command(
|
|
@@ -745,7 +779,7 @@ class AdvertisingSet(utils.EventEmitter):
|
|
|
745
779
|
)
|
|
746
780
|
self.enabled = False
|
|
747
781
|
|
|
748
|
-
self.emit(
|
|
782
|
+
self.emit(self.EVENT_STOP)
|
|
749
783
|
|
|
750
784
|
async def start_periodic(self, include_adi: bool = False) -> None:
|
|
751
785
|
if self.periodic_enabled:
|
|
@@ -759,7 +793,7 @@ class AdvertisingSet(utils.EventEmitter):
|
|
|
759
793
|
)
|
|
760
794
|
self.periodic_enabled = True
|
|
761
795
|
|
|
762
|
-
self.emit(
|
|
796
|
+
self.emit(self.EVENT_START_PERIODIC)
|
|
763
797
|
|
|
764
798
|
async def stop_periodic(self) -> None:
|
|
765
799
|
if not self.periodic_enabled:
|
|
@@ -773,7 +807,7 @@ class AdvertisingSet(utils.EventEmitter):
|
|
|
773
807
|
)
|
|
774
808
|
self.periodic_enabled = False
|
|
775
809
|
|
|
776
|
-
self.emit(
|
|
810
|
+
self.emit(self.EVENT_STOP_PERIODIC)
|
|
777
811
|
|
|
778
812
|
async def remove(self) -> None:
|
|
779
813
|
await self.device.send_command(
|
|
@@ -797,7 +831,7 @@ class AdvertisingSet(utils.EventEmitter):
|
|
|
797
831
|
|
|
798
832
|
def on_termination(self, status: int) -> None:
|
|
799
833
|
self.enabled = False
|
|
800
|
-
self.emit(
|
|
834
|
+
self.emit(self.EVENT_TERMINATION, status)
|
|
801
835
|
|
|
802
836
|
|
|
803
837
|
# -----------------------------------------------------------------------------
|
|
@@ -820,9 +854,17 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
|
|
|
820
854
|
filter_duplicates: bool
|
|
821
855
|
status: int
|
|
822
856
|
advertiser_phy: int
|
|
823
|
-
periodic_advertising_interval:
|
|
857
|
+
periodic_advertising_interval: float # Advertising interval, in milliseconds
|
|
824
858
|
advertiser_clock_accuracy: int
|
|
825
859
|
|
|
860
|
+
EVENT_STATE_CHANGE = "state_change"
|
|
861
|
+
EVENT_ESTABLISHMENT = "establishment"
|
|
862
|
+
EVENT_CANCELLATION = "cancellation"
|
|
863
|
+
EVENT_ERROR = "error"
|
|
864
|
+
EVENT_LOSS = "loss"
|
|
865
|
+
EVENT_PERIODIC_ADVERTISEMENT = "periodic_advertisement"
|
|
866
|
+
EVENT_BIGINFO_ADVERTISEMENT = "biginfo_advertisement"
|
|
867
|
+
|
|
826
868
|
def __init__(
|
|
827
869
|
self,
|
|
828
870
|
device: Device,
|
|
@@ -855,7 +897,7 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
|
|
|
855
897
|
def state(self, state: State) -> None:
|
|
856
898
|
logger.debug(f'{self} -> {state.name}')
|
|
857
899
|
self._state = state
|
|
858
|
-
self.emit(
|
|
900
|
+
self.emit(self.EVENT_STATE_CHANGE)
|
|
859
901
|
|
|
860
902
|
async def establish(self) -> None:
|
|
861
903
|
if self.state != self.State.INIT:
|
|
@@ -936,10 +978,10 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
|
|
|
936
978
|
if status == hci.HCI_SUCCESS:
|
|
937
979
|
self.sync_handle = sync_handle
|
|
938
980
|
self.advertiser_phy = advertiser_phy
|
|
939
|
-
self.periodic_advertising_interval = periodic_advertising_interval
|
|
981
|
+
self.periodic_advertising_interval = periodic_advertising_interval * 1.25
|
|
940
982
|
self.advertiser_clock_accuracy = advertiser_clock_accuracy
|
|
941
983
|
self.state = self.State.ESTABLISHED
|
|
942
|
-
self.emit(
|
|
984
|
+
self.emit(self.EVENT_ESTABLISHMENT)
|
|
943
985
|
return
|
|
944
986
|
|
|
945
987
|
# We don't need to keep a reference anymore
|
|
@@ -948,15 +990,15 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
|
|
|
948
990
|
|
|
949
991
|
if status == hci.HCI_OPERATION_CANCELLED_BY_HOST_ERROR:
|
|
950
992
|
self.state = self.State.CANCELLED
|
|
951
|
-
self.emit(
|
|
993
|
+
self.emit(self.EVENT_CANCELLATION)
|
|
952
994
|
return
|
|
953
995
|
|
|
954
996
|
self.state = self.State.ERROR
|
|
955
|
-
self.emit(
|
|
997
|
+
self.emit(self.EVENT_ERROR)
|
|
956
998
|
|
|
957
999
|
def on_loss(self):
|
|
958
1000
|
self.state = self.State.LOST
|
|
959
|
-
self.emit(
|
|
1001
|
+
self.emit(self.EVENT_LOSS)
|
|
960
1002
|
|
|
961
1003
|
def on_periodic_advertising_report(self, report) -> None:
|
|
962
1004
|
self.data_accumulator += report.data
|
|
@@ -967,7 +1009,7 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
|
|
|
967
1009
|
return
|
|
968
1010
|
|
|
969
1011
|
self.emit(
|
|
970
|
-
|
|
1012
|
+
self.EVENT_PERIODIC_ADVERTISEMENT,
|
|
971
1013
|
PeriodicAdvertisement(
|
|
972
1014
|
self.advertiser_address,
|
|
973
1015
|
self.sid,
|
|
@@ -984,8 +1026,8 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
|
|
|
984
1026
|
|
|
985
1027
|
def on_biginfo_advertising_report(self, report) -> None:
|
|
986
1028
|
self.emit(
|
|
987
|
-
|
|
988
|
-
|
|
1029
|
+
self.EVENT_BIGINFO_ADVERTISEMENT,
|
|
1030
|
+
BigInfoAdvertisement.from_report(self.advertiser_address, self.sid, report),
|
|
989
1031
|
)
|
|
990
1032
|
|
|
991
1033
|
def __str__(self) -> str:
|
|
@@ -1003,14 +1045,24 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
|
|
|
1003
1045
|
# -----------------------------------------------------------------------------
|
|
1004
1046
|
@dataclass
|
|
1005
1047
|
class BigParameters:
|
|
1048
|
+
class Packing(utils.OpenIntEnum):
|
|
1049
|
+
# fmt: off
|
|
1050
|
+
SEQUENTIAL = 0x00
|
|
1051
|
+
INTERLEAVED = 0x01
|
|
1052
|
+
|
|
1053
|
+
class Framing(utils.OpenIntEnum):
|
|
1054
|
+
# fmt: off
|
|
1055
|
+
UNFRAMED = 0x00
|
|
1056
|
+
FRAMED = 0x01
|
|
1057
|
+
|
|
1006
1058
|
num_bis: int
|
|
1007
|
-
sdu_interval: int
|
|
1059
|
+
sdu_interval: int # SDU interval, in microseconds
|
|
1008
1060
|
max_sdu: int
|
|
1009
|
-
max_transport_latency: int
|
|
1061
|
+
max_transport_latency: int # Max transport latency, in milliseconds
|
|
1010
1062
|
rtn: int
|
|
1011
1063
|
phy: hci.PhyBit = hci.PhyBit.LE_2M
|
|
1012
|
-
packing:
|
|
1013
|
-
framing:
|
|
1064
|
+
packing: Packing = Packing.SEQUENTIAL
|
|
1065
|
+
framing: Framing = Framing.UNFRAMED
|
|
1014
1066
|
broadcast_code: bytes | None = None
|
|
1015
1067
|
|
|
1016
1068
|
|
|
@@ -1033,15 +1085,15 @@ class Big(utils.EventEmitter):
|
|
|
1033
1085
|
state: State = State.PENDING
|
|
1034
1086
|
|
|
1035
1087
|
# Attributes provided by BIG Create Complete event
|
|
1036
|
-
big_sync_delay: int = 0
|
|
1037
|
-
transport_latency_big: int = 0
|
|
1038
|
-
phy:
|
|
1088
|
+
big_sync_delay: int = 0 # Sync delay, in microseconds
|
|
1089
|
+
transport_latency_big: int = 0 # Transport latency, in microseconds
|
|
1090
|
+
phy: hci.Phy = hci.Phy.LE_1M
|
|
1039
1091
|
nse: int = 0
|
|
1040
1092
|
bn: int = 0
|
|
1041
1093
|
pto: int = 0
|
|
1042
1094
|
irc: int = 0
|
|
1043
1095
|
max_pdu: int = 0
|
|
1044
|
-
iso_interval:
|
|
1096
|
+
iso_interval: float = 0.0 # ISO interval, in milliseconds
|
|
1045
1097
|
bis_links: Sequence[BisLink] = ()
|
|
1046
1098
|
|
|
1047
1099
|
def __post_init__(self) -> None:
|
|
@@ -1102,7 +1154,7 @@ class BigSync(utils.EventEmitter):
|
|
|
1102
1154
|
pto: int = 0
|
|
1103
1155
|
irc: int = 0
|
|
1104
1156
|
max_pdu: int = 0
|
|
1105
|
-
iso_interval:
|
|
1157
|
+
iso_interval: float = 0.0
|
|
1106
1158
|
bis_links: Sequence[BisLink] = ()
|
|
1107
1159
|
|
|
1108
1160
|
def __post_init__(self) -> None:
|
|
@@ -1183,11 +1235,11 @@ class ChannelSoundingProcedure:
|
|
|
1183
1235
|
selected_tx_power: int
|
|
1184
1236
|
subevent_len: int
|
|
1185
1237
|
subevents_per_event: int
|
|
1186
|
-
subevent_interval:
|
|
1238
|
+
subevent_interval: float # milliseconds.
|
|
1187
1239
|
event_interval: int
|
|
1188
1240
|
procedure_interval: int
|
|
1189
1241
|
procedure_count: int
|
|
1190
|
-
max_procedure_len:
|
|
1242
|
+
max_procedure_len: float # milliseconds.
|
|
1191
1243
|
|
|
1192
1244
|
|
|
1193
1245
|
# -----------------------------------------------------------------------------
|
|
@@ -1212,9 +1264,8 @@ class Peer:
|
|
|
1212
1264
|
def __init__(self, connection: Connection) -> None:
|
|
1213
1265
|
self.connection = connection
|
|
1214
1266
|
|
|
1215
|
-
#
|
|
1216
|
-
self.gatt_client = gatt_client
|
|
1217
|
-
connection.gatt_client = self.gatt_client
|
|
1267
|
+
# Shortcut to the connection's GATT client
|
|
1268
|
+
self.gatt_client = connection.gatt_client
|
|
1218
1269
|
|
|
1219
1270
|
@property
|
|
1220
1271
|
def services(self) -> list[gatt_client.ServiceProxy]:
|
|
@@ -1222,7 +1273,7 @@ class Peer:
|
|
|
1222
1273
|
|
|
1223
1274
|
async def request_mtu(self, mtu: int) -> int:
|
|
1224
1275
|
mtu = await self.gatt_client.request_mtu(mtu)
|
|
1225
|
-
self.connection.emit(
|
|
1276
|
+
self.connection.emit(self.connection.EVENT_CONNECTION_ATT_MTU_UPDATE)
|
|
1226
1277
|
return mtu
|
|
1227
1278
|
|
|
1228
1279
|
async def discover_service(
|
|
@@ -1327,7 +1378,7 @@ class Peer:
|
|
|
1327
1378
|
return self.gatt_client.get_characteristics_by_uuid(uuid, service)
|
|
1328
1379
|
|
|
1329
1380
|
def create_service_proxy(
|
|
1330
|
-
self, proxy_class:
|
|
1381
|
+
self, proxy_class: type[_PROXY_CLASS]
|
|
1331
1382
|
) -> Optional[_PROXY_CLASS]:
|
|
1332
1383
|
if proxy := proxy_class.from_client(self.gatt_client):
|
|
1333
1384
|
return cast(_PROXY_CLASS, proxy)
|
|
@@ -1335,7 +1386,7 @@ class Peer:
|
|
|
1335
1386
|
return None
|
|
1336
1387
|
|
|
1337
1388
|
async def discover_service_and_create_proxy(
|
|
1338
|
-
self, proxy_class:
|
|
1389
|
+
self, proxy_class: type[_PROXY_CLASS]
|
|
1339
1390
|
) -> Optional[_PROXY_CLASS]:
|
|
1340
1391
|
# Discover the first matching service and its characteristics
|
|
1341
1392
|
services = await self.discover_service(proxy_class.SERVICE_CLASS.UUID)
|
|
@@ -1390,6 +1441,9 @@ class ScoLink(utils.CompositeEventEmitter):
|
|
|
1390
1441
|
link_type: int
|
|
1391
1442
|
sink: Optional[Callable[[hci.HCI_SynchronousDataPacket], Any]] = None
|
|
1392
1443
|
|
|
1444
|
+
EVENT_DISCONNECTION: ClassVar[str] = "disconnection"
|
|
1445
|
+
EVENT_DISCONNECTION_FAILURE: ClassVar[str] = "disconnection_failure"
|
|
1446
|
+
|
|
1393
1447
|
def __post_init__(self) -> None:
|
|
1394
1448
|
super().__init__()
|
|
1395
1449
|
|
|
@@ -1445,7 +1499,7 @@ class _IsoLink:
|
|
|
1445
1499
|
check_result=True,
|
|
1446
1500
|
)
|
|
1447
1501
|
|
|
1448
|
-
async def remove_data_path(self,
|
|
1502
|
+
async def remove_data_path(self, directions: Iterable[_IsoLink.Direction]) -> int:
|
|
1449
1503
|
"""Remove a data path with controller on given direction.
|
|
1450
1504
|
|
|
1451
1505
|
Args:
|
|
@@ -1457,7 +1511,9 @@ class _IsoLink:
|
|
|
1457
1511
|
response = await self.device.send_command(
|
|
1458
1512
|
hci.HCI_LE_Remove_ISO_Data_Path_Command(
|
|
1459
1513
|
connection_handle=self.handle,
|
|
1460
|
-
data_path_direction=
|
|
1514
|
+
data_path_direction=sum(
|
|
1515
|
+
1 << direction for direction in set(directions)
|
|
1516
|
+
),
|
|
1461
1517
|
),
|
|
1462
1518
|
check_result=False,
|
|
1463
1519
|
)
|
|
@@ -1467,10 +1523,74 @@ class _IsoLink:
|
|
|
1467
1523
|
"""Write an ISO SDU."""
|
|
1468
1524
|
self.device.host.send_iso_sdu(connection_handle=self.handle, sdu=sdu)
|
|
1469
1525
|
|
|
1526
|
+
async def get_tx_time_stamp(self) -> tuple[int, int, int]:
|
|
1527
|
+
response = await self.device.host.send_command(
|
|
1528
|
+
hci.HCI_LE_Read_ISO_TX_Sync_Command(connection_handle=self.handle),
|
|
1529
|
+
check_result=True,
|
|
1530
|
+
)
|
|
1531
|
+
return (
|
|
1532
|
+
response.return_parameters.packet_sequence_number,
|
|
1533
|
+
response.return_parameters.tx_time_stamp,
|
|
1534
|
+
response.return_parameters.time_offset,
|
|
1535
|
+
)
|
|
1536
|
+
|
|
1470
1537
|
@property
|
|
1471
1538
|
def data_packet_queue(self) -> DataPacketQueue | None:
|
|
1472
1539
|
return self.device.host.get_data_packet_queue(self.handle)
|
|
1473
1540
|
|
|
1541
|
+
async def drain(self) -> None:
|
|
1542
|
+
if data_packet_queue := self.data_packet_queue:
|
|
1543
|
+
await data_packet_queue.drain(self.handle)
|
|
1544
|
+
|
|
1545
|
+
|
|
1546
|
+
# -----------------------------------------------------------------------------
|
|
1547
|
+
@dataclass
|
|
1548
|
+
class CigParameters:
|
|
1549
|
+
class WorstCaseSca(utils.OpenIntEnum):
|
|
1550
|
+
# fmt: off
|
|
1551
|
+
SCA_251_TO_500_PPM = 0x00
|
|
1552
|
+
SCA_151_TO_250_PPM = 0x01
|
|
1553
|
+
SCA_101_TO_150_PPM = 0x02
|
|
1554
|
+
SCA_76_TO_100_PPM = 0x03
|
|
1555
|
+
SCA_51_TO_75_PPM = 0x04
|
|
1556
|
+
SCA_31_TO_50_PPM = 0x05
|
|
1557
|
+
SCA_21_TO_30_PPM = 0x06
|
|
1558
|
+
SCA_0_TO_20_PPM = 0x07
|
|
1559
|
+
|
|
1560
|
+
class Packing(utils.OpenIntEnum):
|
|
1561
|
+
# fmt: off
|
|
1562
|
+
SEQUENTIAL = 0x00
|
|
1563
|
+
INTERLEAVED = 0x01
|
|
1564
|
+
|
|
1565
|
+
class Framing(utils.OpenIntEnum):
|
|
1566
|
+
# fmt: off
|
|
1567
|
+
UNFRAMED = 0x00
|
|
1568
|
+
FRAMED = 0x01
|
|
1569
|
+
|
|
1570
|
+
@dataclass
|
|
1571
|
+
class CisParameters:
|
|
1572
|
+
cis_id: int
|
|
1573
|
+
max_sdu_c_to_p: int = DEVICE_DEFAULT_ISO_CIS_MAX_SDU
|
|
1574
|
+
max_sdu_p_to_c: int = DEVICE_DEFAULT_ISO_CIS_MAX_SDU
|
|
1575
|
+
phy_c_to_p: hci.PhyBit = hci.PhyBit.LE_2M
|
|
1576
|
+
phy_p_to_c: hci.PhyBit = hci.PhyBit.LE_2M
|
|
1577
|
+
rtn_c_to_p: int = DEVICE_DEFAULT_ISO_CIS_RTN # Number of C->P retransmissions
|
|
1578
|
+
rtn_p_to_c: int = DEVICE_DEFAULT_ISO_CIS_RTN # Number of P->C retransmissions
|
|
1579
|
+
|
|
1580
|
+
cig_id: int
|
|
1581
|
+
cis_parameters: list[CisParameters]
|
|
1582
|
+
sdu_interval_c_to_p: int # C->P SDU interval, in microseconds
|
|
1583
|
+
sdu_interval_p_to_c: int # P->C SDU interval, in microseconds
|
|
1584
|
+
worst_case_sca: WorstCaseSca = WorstCaseSca.SCA_251_TO_500_PPM
|
|
1585
|
+
packing: Packing = Packing.SEQUENTIAL
|
|
1586
|
+
framing: Framing = Framing.UNFRAMED
|
|
1587
|
+
max_transport_latency_c_to_p: int = (
|
|
1588
|
+
DEVICE_DEFAULT_ISO_CIS_MAX_TRANSPORT_LATENCY # Max C->P transport latency, in milliseconds
|
|
1589
|
+
)
|
|
1590
|
+
max_transport_latency_p_to_c: int = (
|
|
1591
|
+
DEVICE_DEFAULT_ISO_CIS_MAX_TRANSPORT_LATENCY # Max C->P transport latency, in milliseconds
|
|
1592
|
+
)
|
|
1593
|
+
|
|
1474
1594
|
|
|
1475
1595
|
# -----------------------------------------------------------------------------
|
|
1476
1596
|
@dataclass
|
|
@@ -1484,9 +1604,28 @@ class CisLink(utils.EventEmitter, _IsoLink):
|
|
|
1484
1604
|
handle: int # CIS handle assigned by Controller (in LE_Set_CIG_Parameters Complete or LE_CIS_Request events)
|
|
1485
1605
|
cis_id: int # CIS ID assigned by Central device
|
|
1486
1606
|
cig_id: int # CIG ID assigned by Central device
|
|
1607
|
+
cig_sync_delay: int = 0 # CIG sync delay, in microseconds
|
|
1608
|
+
cis_sync_delay: int = 0 # CIS sync delay, in microseconds
|
|
1609
|
+
transport_latency_c_to_p: int = 0 # C->P transport latency, in microseconds
|
|
1610
|
+
transport_latency_p_to_c: int = 0 # P->C transport latency, in microseconds
|
|
1611
|
+
phy_c_to_p: Optional[hci.Phy] = None
|
|
1612
|
+
phy_p_to_c: Optional[hci.Phy] = None
|
|
1613
|
+
nse: int = 0
|
|
1614
|
+
bn_c_to_p: int = 0
|
|
1615
|
+
bn_p_to_c: int = 0
|
|
1616
|
+
ft_c_to_p: int = 0
|
|
1617
|
+
ft_p_to_c: int = 0
|
|
1618
|
+
max_pdu_c_to_p: int = 0
|
|
1619
|
+
max_pdu_p_to_c: int = 0
|
|
1620
|
+
iso_interval: float = 0.0 # ISO interval, in milliseconds
|
|
1487
1621
|
state: State = State.PENDING
|
|
1488
1622
|
sink: Callable[[hci.HCI_IsoDataPacket], Any] | None = None
|
|
1489
1623
|
|
|
1624
|
+
EVENT_DISCONNECTION: ClassVar[str] = "disconnection"
|
|
1625
|
+
EVENT_DISCONNECTION_FAILURE: ClassVar[str] = "disconnection_failure"
|
|
1626
|
+
EVENT_ESTABLISHMENT: ClassVar[str] = "establishment"
|
|
1627
|
+
EVENT_ESTABLISHMENT_FAILURE: ClassVar[str] = "establishment_failure"
|
|
1628
|
+
|
|
1490
1629
|
def __post_init__(self) -> None:
|
|
1491
1630
|
super().__init__()
|
|
1492
1631
|
|
|
@@ -1521,7 +1660,7 @@ class IsoPacketStream:
|
|
|
1521
1660
|
self.iso_link = iso_link
|
|
1522
1661
|
self.data_packet_queue = iso_link.data_packet_queue
|
|
1523
1662
|
self.data_packet_queue.on('flow', self._on_flow)
|
|
1524
|
-
self._thresholds:
|
|
1663
|
+
self._thresholds: collections.deque[int] = collections.deque()
|
|
1525
1664
|
self._semaphore = asyncio.Semaphore(max_queue_size)
|
|
1526
1665
|
|
|
1527
1666
|
def _on_flow(self) -> None:
|
|
@@ -1561,15 +1700,58 @@ class Connection(utils.CompositeEventEmitter):
|
|
|
1561
1700
|
peer_resolvable_address: Optional[hci.Address]
|
|
1562
1701
|
peer_le_features: Optional[hci.LeFeatureMask]
|
|
1563
1702
|
role: hci.Role
|
|
1703
|
+
parameters: Parameters
|
|
1564
1704
|
encryption: int
|
|
1705
|
+
encryption_key_size: int
|
|
1565
1706
|
authenticated: bool
|
|
1566
1707
|
sc: bool
|
|
1567
|
-
link_key_type: int
|
|
1568
1708
|
gatt_client: gatt_client.Client
|
|
1569
1709
|
pairing_peer_io_capability: Optional[int]
|
|
1570
1710
|
pairing_peer_authentication_requirements: Optional[int]
|
|
1571
1711
|
cs_configs: dict[int, ChannelSoundingConfig] # Config ID to Configuration
|
|
1572
1712
|
cs_procedures: dict[int, ChannelSoundingProcedure] # Config ID to Procedures
|
|
1713
|
+
classic_mode: int = hci.HCI_Mode_Change_Event.Mode.ACTIVE
|
|
1714
|
+
classic_interval: int = 0
|
|
1715
|
+
|
|
1716
|
+
EVENT_CONNECTION_ATT_MTU_UPDATE = "connection_att_mtu_update"
|
|
1717
|
+
EVENT_DISCONNECTION = "disconnection"
|
|
1718
|
+
EVENT_DISCONNECTION_FAILURE = "disconnection_failure"
|
|
1719
|
+
EVENT_CONNECTION_AUTHENTICATION = "connection_authentication"
|
|
1720
|
+
EVENT_CONNECTION_AUTHENTICATION_FAILURE = "connection_authentication_failure"
|
|
1721
|
+
EVENT_REMOTE_NAME = "remote_name"
|
|
1722
|
+
EVENT_REMOTE_NAME_FAILURE = "remote_name_failure"
|
|
1723
|
+
EVENT_CONNECTION_ENCRYPTION_CHANGE = "connection_encryption_change"
|
|
1724
|
+
EVENT_CONNECTION_ENCRYPTION_FAILURE = "connection_encryption_failure"
|
|
1725
|
+
EVENT_CONNECTION_ENCRYPTION_KEY_REFRESH = "connection_encryption_key_refresh"
|
|
1726
|
+
EVENT_CONNECTION_PARAMETERS_UPDATE = "connection_parameters_update"
|
|
1727
|
+
EVENT_CONNECTION_PARAMETERS_UPDATE_FAILURE = "connection_parameters_update_failure"
|
|
1728
|
+
EVENT_CONNECTION_PHY_UPDATE = "connection_phy_update"
|
|
1729
|
+
EVENT_CONNECTION_PHY_UPDATE_FAILURE = "connection_phy_update_failure"
|
|
1730
|
+
EVENT_CONNECTION_ATT_MTU_UPDATE = "connection_att_mtu_update"
|
|
1731
|
+
EVENT_CONNECTION_DATA_LENGTH_CHANGE = "connection_data_length_change"
|
|
1732
|
+
EVENT_CHANNEL_SOUNDING_CAPABILITIES_FAILURE = (
|
|
1733
|
+
"channel_sounding_capabilities_failure"
|
|
1734
|
+
)
|
|
1735
|
+
EVENT_CHANNEL_SOUNDING_CAPABILITIES = "channel_sounding_capabilities"
|
|
1736
|
+
EVENT_CHANNEL_SOUNDING_CONFIG_FAILURE = "channel_sounding_config_failure"
|
|
1737
|
+
EVENT_CHANNEL_SOUNDING_CONFIG = "channel_sounding_config"
|
|
1738
|
+
EVENT_CHANNEL_SOUNDING_CONFIG_REMOVED = "channel_sounding_config_removed"
|
|
1739
|
+
EVENT_CHANNEL_SOUNDING_PROCEDURE_FAILURE = "channel_sounding_procedure_failure"
|
|
1740
|
+
EVENT_CHANNEL_SOUNDING_PROCEDURE = "channel_sounding_procedure"
|
|
1741
|
+
EVENT_MODE_CHANGE = "mode_change"
|
|
1742
|
+
EVENT_MODE_CHANGE_FAILURE = "mode_change_failure"
|
|
1743
|
+
EVENT_ROLE_CHANGE = "role_change"
|
|
1744
|
+
EVENT_ROLE_CHANGE_FAILURE = "role_change_failure"
|
|
1745
|
+
EVENT_CLASSIC_PAIRING = "classic_pairing"
|
|
1746
|
+
EVENT_CLASSIC_PAIRING_FAILURE = "classic_pairing_failure"
|
|
1747
|
+
EVENT_PAIRING_START = "pairing_start"
|
|
1748
|
+
EVENT_PAIRING = "pairing"
|
|
1749
|
+
EVENT_PAIRING_FAILURE = "pairing_failure"
|
|
1750
|
+
EVENT_SECURITY_REQUEST = "security_request"
|
|
1751
|
+
EVENT_LINK_KEY = "link_key"
|
|
1752
|
+
EVENT_CIS_REQUEST = "cis_request"
|
|
1753
|
+
EVENT_CIS_ESTABLISHMENT = "cis_establishment"
|
|
1754
|
+
EVENT_CIS_ESTABLISHMENT_FAILURE = "cis_establishment_failure"
|
|
1573
1755
|
|
|
1574
1756
|
@utils.composite_listener
|
|
1575
1757
|
class Listener:
|
|
@@ -1600,17 +1782,23 @@ class Connection(utils.CompositeEventEmitter):
|
|
|
1600
1782
|
def on_connection_encryption_key_refresh(self):
|
|
1601
1783
|
pass
|
|
1602
1784
|
|
|
1785
|
+
@dataclass
|
|
1786
|
+
class Parameters:
|
|
1787
|
+
connection_interval: float # Connection interval, in milliseconds. [LE only]
|
|
1788
|
+
peripheral_latency: int # Peripheral latency, in number of intervals. [LE only]
|
|
1789
|
+
supervision_timeout: float # Supervision timeout, in milliseconds.
|
|
1790
|
+
|
|
1603
1791
|
def __init__(
|
|
1604
1792
|
self,
|
|
1605
|
-
device,
|
|
1606
|
-
handle,
|
|
1607
|
-
transport,
|
|
1608
|
-
self_address,
|
|
1609
|
-
self_resolvable_address,
|
|
1610
|
-
peer_address,
|
|
1611
|
-
peer_resolvable_address,
|
|
1612
|
-
role,
|
|
1613
|
-
parameters,
|
|
1793
|
+
device: Device,
|
|
1794
|
+
handle: int,
|
|
1795
|
+
transport: core.PhysicalTransport,
|
|
1796
|
+
self_address: hci.Address,
|
|
1797
|
+
self_resolvable_address: Optional[hci.Address],
|
|
1798
|
+
peer_address: hci.Address,
|
|
1799
|
+
peer_resolvable_address: Optional[hci.Address],
|
|
1800
|
+
role: hci.Role,
|
|
1801
|
+
parameters: Parameters,
|
|
1614
1802
|
):
|
|
1615
1803
|
super().__init__()
|
|
1616
1804
|
self.device = device
|
|
@@ -1624,12 +1812,12 @@ class Connection(utils.CompositeEventEmitter):
|
|
|
1624
1812
|
self.role = role
|
|
1625
1813
|
self.parameters = parameters
|
|
1626
1814
|
self.encryption = 0
|
|
1815
|
+
self.encryption_key_size = 0
|
|
1627
1816
|
self.authenticated = False
|
|
1628
1817
|
self.sc = False
|
|
1629
|
-
self.link_key_type = None
|
|
1630
1818
|
self.att_mtu = ATT_DEFAULT_MTU
|
|
1631
1819
|
self.data_length = DEVICE_DEFAULT_DATA_LENGTH
|
|
1632
|
-
self.gatt_client =
|
|
1820
|
+
self.gatt_client = gatt_client.Client(self) # Per-connection client
|
|
1633
1821
|
self.gatt_server = (
|
|
1634
1822
|
device.gatt_server
|
|
1635
1823
|
) # By default, use the device's shared server
|
|
@@ -1740,28 +1928,38 @@ class Connection(utils.CompositeEventEmitter):
|
|
|
1740
1928
|
"""Idles the current task waiting for a disconnect or timeout"""
|
|
1741
1929
|
|
|
1742
1930
|
abort = asyncio.get_running_loop().create_future()
|
|
1743
|
-
self.on(
|
|
1744
|
-
self.on(
|
|
1931
|
+
self.on(self.EVENT_DISCONNECTION, abort.set_result)
|
|
1932
|
+
self.on(self.EVENT_DISCONNECTION_FAILURE, abort.set_exception)
|
|
1745
1933
|
|
|
1746
1934
|
try:
|
|
1747
1935
|
await asyncio.wait_for(
|
|
1748
|
-
utils.cancel_on_event(self.device,
|
|
1936
|
+
utils.cancel_on_event(self.device, Device.EVENT_FLUSH, abort), timeout
|
|
1749
1937
|
)
|
|
1750
1938
|
finally:
|
|
1751
|
-
self.remove_listener(
|
|
1752
|
-
self.remove_listener(
|
|
1939
|
+
self.remove_listener(self.EVENT_DISCONNECTION, abort.set_result)
|
|
1940
|
+
self.remove_listener(self.EVENT_DISCONNECTION_FAILURE, abort.set_exception)
|
|
1753
1941
|
|
|
1754
1942
|
async def set_data_length(self, tx_octets, tx_time) -> None:
|
|
1755
1943
|
return await self.device.set_data_length(self, tx_octets, tx_time)
|
|
1756
1944
|
|
|
1757
1945
|
async def update_parameters(
|
|
1758
1946
|
self,
|
|
1759
|
-
connection_interval_min,
|
|
1760
|
-
connection_interval_max,
|
|
1761
|
-
max_latency,
|
|
1762
|
-
supervision_timeout,
|
|
1947
|
+
connection_interval_min: float,
|
|
1948
|
+
connection_interval_max: float,
|
|
1949
|
+
max_latency: int,
|
|
1950
|
+
supervision_timeout: float,
|
|
1763
1951
|
use_l2cap=False,
|
|
1764
|
-
):
|
|
1952
|
+
) -> None:
|
|
1953
|
+
"""
|
|
1954
|
+
Request an update of the connection parameters.
|
|
1955
|
+
|
|
1956
|
+
Args:
|
|
1957
|
+
connection_interval_min: Minimum interval, in milliseconds.
|
|
1958
|
+
connection_interval_max: Maximum interval, in milliseconds.
|
|
1959
|
+
max_latency: Latency, in number of intervals.
|
|
1960
|
+
supervision_timeout: Timeout, in milliseconds.
|
|
1961
|
+
use_l2cap: Request the update via L2CAP.
|
|
1962
|
+
"""
|
|
1765
1963
|
return await self.device.update_connection_parameters(
|
|
1766
1964
|
self,
|
|
1767
1965
|
connection_interval_min,
|
|
@@ -1809,6 +2007,12 @@ class Connection(utils.CompositeEventEmitter):
|
|
|
1809
2007
|
def data_packet_queue(self) -> DataPacketQueue | None:
|
|
1810
2008
|
return self.device.host.get_data_packet_queue(self.handle)
|
|
1811
2009
|
|
|
2010
|
+
def cancel_on_disconnection(self, awaitable: Awaitable[_T]) -> Awaitable[_T]:
|
|
2011
|
+
"""
|
|
2012
|
+
Helper method to call `utils.cancel_on_event` for the 'disconnection' event
|
|
2013
|
+
"""
|
|
2014
|
+
return utils.cancel_on_event(self, self.EVENT_DISCONNECTION, awaitable)
|
|
2015
|
+
|
|
1812
2016
|
async def __aenter__(self):
|
|
1813
2017
|
return self
|
|
1814
2018
|
|
|
@@ -1848,8 +2052,8 @@ class DeviceConfiguration:
|
|
|
1848
2052
|
address: hci.Address = hci.Address(DEVICE_DEFAULT_ADDRESS)
|
|
1849
2053
|
class_of_device: int = DEVICE_DEFAULT_CLASS_OF_DEVICE
|
|
1850
2054
|
scan_response_data: bytes = DEVICE_DEFAULT_SCAN_RESPONSE_DATA
|
|
1851
|
-
advertising_interval_min:
|
|
1852
|
-
advertising_interval_max:
|
|
2055
|
+
advertising_interval_min: float = DEVICE_DEFAULT_ADVERTISING_INTERVAL
|
|
2056
|
+
advertising_interval_max: float = DEVICE_DEFAULT_ADVERTISING_INTERVAL
|
|
1853
2057
|
le_enabled: bool = True
|
|
1854
2058
|
le_simultaneous_enabled: bool = False
|
|
1855
2059
|
le_privacy_enabled: bool = False
|
|
@@ -1879,9 +2083,9 @@ class DeviceConfiguration:
|
|
|
1879
2083
|
gatt_service_enabled: bool = True
|
|
1880
2084
|
|
|
1881
2085
|
def __post_init__(self) -> None:
|
|
1882
|
-
self.gatt_services: list[
|
|
2086
|
+
self.gatt_services: list[dict[str, Any]] = []
|
|
1883
2087
|
|
|
1884
|
-
def load_from_dict(self, config:
|
|
2088
|
+
def load_from_dict(self, config: dict[str, Any]) -> None:
|
|
1885
2089
|
config = copy.deepcopy(config)
|
|
1886
2090
|
|
|
1887
2091
|
# Load simple properties
|
|
@@ -1941,13 +2145,13 @@ class DeviceConfiguration:
|
|
|
1941
2145
|
self.load_from_dict(json.load(file))
|
|
1942
2146
|
|
|
1943
2147
|
@classmethod
|
|
1944
|
-
def from_file(cls:
|
|
2148
|
+
def from_file(cls: type[Self], filename: str) -> Self:
|
|
1945
2149
|
config = cls()
|
|
1946
2150
|
config.load_from_file(filename)
|
|
1947
2151
|
return config
|
|
1948
2152
|
|
|
1949
2153
|
@classmethod
|
|
1950
|
-
def from_dict(cls:
|
|
2154
|
+
def from_dict(cls: type[Self], config: dict[str, Any]) -> Self:
|
|
1951
2155
|
device_config = cls()
|
|
1952
2156
|
device_config.load_from_dict(config)
|
|
1953
2157
|
return device_config
|
|
@@ -2044,24 +2248,44 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2044
2248
|
advertising_data: bytes
|
|
2045
2249
|
scan_response_data: bytes
|
|
2046
2250
|
cs_capabilities: ChannelSoundingCapabilities | None = None
|
|
2047
|
-
connections:
|
|
2048
|
-
pending_connections:
|
|
2049
|
-
classic_pending_accepts:
|
|
2251
|
+
connections: dict[int, Connection]
|
|
2252
|
+
pending_connections: dict[hci.Address, Connection]
|
|
2253
|
+
classic_pending_accepts: dict[
|
|
2050
2254
|
hci.Address,
|
|
2051
2255
|
list[asyncio.Future[Union[Connection, tuple[hci.Address, int, int]]]],
|
|
2052
2256
|
]
|
|
2053
|
-
advertisement_accumulators:
|
|
2257
|
+
advertisement_accumulators: dict[hci.Address, AdvertisementDataAccumulator]
|
|
2054
2258
|
periodic_advertising_syncs: list[PeriodicAdvertisingSync]
|
|
2055
2259
|
config: DeviceConfiguration
|
|
2056
2260
|
legacy_advertiser: Optional[LegacyAdvertiser]
|
|
2057
|
-
sco_links:
|
|
2058
|
-
cis_links:
|
|
2261
|
+
sco_links: dict[int, ScoLink]
|
|
2262
|
+
cis_links: dict[int, CisLink]
|
|
2059
2263
|
bigs: dict[int, Big]
|
|
2060
2264
|
bis_links: dict[int, BisLink]
|
|
2061
2265
|
big_syncs: dict[int, BigSync]
|
|
2062
|
-
_pending_cis:
|
|
2266
|
+
_pending_cis: dict[int, tuple[int, int]]
|
|
2063
2267
|
gatt_service: gatt_service.GenericAttributeProfileService | None = None
|
|
2064
2268
|
|
|
2269
|
+
EVENT_ADVERTISEMENT = "advertisement"
|
|
2270
|
+
EVENT_PERIODIC_ADVERTISING_SYNC_TRANSFER = "periodic_advertising_sync_transfer"
|
|
2271
|
+
EVENT_KEY_STORE_UPDATE = "key_store_update"
|
|
2272
|
+
EVENT_FLUSH = "flush"
|
|
2273
|
+
EVENT_CONNECTION = "connection"
|
|
2274
|
+
EVENT_CONNECTION_FAILURE = "connection_failure"
|
|
2275
|
+
EVENT_SCO_REQUEST = "sco_request"
|
|
2276
|
+
EVENT_INQUIRY_COMPLETE = "inquiry_complete"
|
|
2277
|
+
EVENT_REMOTE_NAME = "remote_name"
|
|
2278
|
+
EVENT_REMOTE_NAME_FAILURE = "remote_name_failure"
|
|
2279
|
+
EVENT_SCO_CONNECTION = "sco_connection"
|
|
2280
|
+
EVENT_SCO_CONNECTION_FAILURE = "sco_connection_failure"
|
|
2281
|
+
EVENT_CIS_REQUEST = "cis_request"
|
|
2282
|
+
EVENT_CIS_ESTABLISHMENT = "cis_establishment"
|
|
2283
|
+
EVENT_CIS_ESTABLISHMENT_FAILURE = "cis_establishment_failure"
|
|
2284
|
+
EVENT_ROLE_CHANGE_FAILURE = "role_change_failure"
|
|
2285
|
+
EVENT_INQUIRY_RESULT = "inquiry_result"
|
|
2286
|
+
EVENT_REMOTE_NAME = "remote_name"
|
|
2287
|
+
EVENT_REMOTE_NAME_FAILURE = "remote_name_failure"
|
|
2288
|
+
|
|
2065
2289
|
@utils.composite_listener
|
|
2066
2290
|
class Listener:
|
|
2067
2291
|
def on_advertisement(self, advertisement):
|
|
@@ -2199,8 +2423,8 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2199
2423
|
self.address_generation_offload = config.address_generation_offload
|
|
2200
2424
|
|
|
2201
2425
|
# Extended advertising.
|
|
2202
|
-
self.extended_advertising_sets:
|
|
2203
|
-
self.connecting_extended_advertising_sets:
|
|
2426
|
+
self.extended_advertising_sets: dict[int, AdvertisingSet] = {}
|
|
2427
|
+
self.connecting_extended_advertising_sets: dict[int, AdvertisingSet] = {}
|
|
2204
2428
|
|
|
2205
2429
|
# Legacy advertising.
|
|
2206
2430
|
# The advertising and scan response data, as well as the advertising interval
|
|
@@ -2748,8 +2972,8 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2748
2972
|
auto_restart: bool = False,
|
|
2749
2973
|
advertising_data: Optional[bytes] = None,
|
|
2750
2974
|
scan_response_data: Optional[bytes] = None,
|
|
2751
|
-
advertising_interval_min: Optional[
|
|
2752
|
-
advertising_interval_max: Optional[
|
|
2975
|
+
advertising_interval_min: Optional[float] = None,
|
|
2976
|
+
advertising_interval_max: Optional[float] = None,
|
|
2753
2977
|
) -> None:
|
|
2754
2978
|
"""Start legacy advertising.
|
|
2755
2979
|
|
|
@@ -3149,7 +3373,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3149
3373
|
accumulator = AdvertisementDataAccumulator(passive=self.scanning_is_passive)
|
|
3150
3374
|
self.advertisement_accumulators[report.address] = accumulator
|
|
3151
3375
|
if advertisement := accumulator.update(report):
|
|
3152
|
-
self.emit(
|
|
3376
|
+
self.emit(self.EVENT_ADVERTISEMENT, advertisement)
|
|
3153
3377
|
|
|
3154
3378
|
async def create_periodic_advertising_sync(
|
|
3155
3379
|
self,
|
|
@@ -3273,7 +3497,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3273
3497
|
periodic_advertising_interval=periodic_advertising_interval,
|
|
3274
3498
|
advertiser_clock_accuracy=advertiser_clock_accuracy,
|
|
3275
3499
|
)
|
|
3276
|
-
self.emit(
|
|
3500
|
+
self.emit(self.EVENT_PERIODIC_ADVERTISING_SYNC_TRANSFER, pa_sync, connection)
|
|
3277
3501
|
|
|
3278
3502
|
@host_event_handler
|
|
3279
3503
|
@with_periodic_advertising_sync_from_handle
|
|
@@ -3331,7 +3555,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3331
3555
|
@host_event_handler
|
|
3332
3556
|
def on_inquiry_result(self, address, class_of_device, data, rssi):
|
|
3333
3557
|
self.emit(
|
|
3334
|
-
|
|
3558
|
+
self.EVENT_INQUIRY_RESULT,
|
|
3335
3559
|
address,
|
|
3336
3560
|
class_of_device,
|
|
3337
3561
|
AdvertisingData.from_bytes(data),
|
|
@@ -3508,8 +3732,8 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3508
3732
|
|
|
3509
3733
|
# Create a future so that we can wait for the connection's result
|
|
3510
3734
|
pending_connection = asyncio.get_running_loop().create_future()
|
|
3511
|
-
self.on(
|
|
3512
|
-
self.on(
|
|
3735
|
+
self.on(self.EVENT_CONNECTION, on_connection)
|
|
3736
|
+
self.on(self.EVENT_CONNECTION_FAILURE, on_connection_failure)
|
|
3513
3737
|
|
|
3514
3738
|
try:
|
|
3515
3739
|
# Tell the controller to connect
|
|
@@ -3662,7 +3886,9 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3662
3886
|
self.le_connecting = True
|
|
3663
3887
|
|
|
3664
3888
|
if timeout is None:
|
|
3665
|
-
return await utils.cancel_on_event(
|
|
3889
|
+
return await utils.cancel_on_event(
|
|
3890
|
+
self, Device.EVENT_FLUSH, pending_connection
|
|
3891
|
+
)
|
|
3666
3892
|
|
|
3667
3893
|
try:
|
|
3668
3894
|
return await asyncio.wait_for(
|
|
@@ -3680,13 +3906,13 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3680
3906
|
|
|
3681
3907
|
try:
|
|
3682
3908
|
return await utils.cancel_on_event(
|
|
3683
|
-
self,
|
|
3909
|
+
self, Device.EVENT_FLUSH, pending_connection
|
|
3684
3910
|
)
|
|
3685
3911
|
except core.ConnectionError as error:
|
|
3686
3912
|
raise core.TimeoutError() from error
|
|
3687
3913
|
finally:
|
|
3688
|
-
self.remove_listener(
|
|
3689
|
-
self.remove_listener(
|
|
3914
|
+
self.remove_listener(self.EVENT_CONNECTION, on_connection)
|
|
3915
|
+
self.remove_listener(self.EVENT_CONNECTION_FAILURE, on_connection_failure)
|
|
3690
3916
|
if transport == PhysicalTransport.LE:
|
|
3691
3917
|
self.le_connecting = False
|
|
3692
3918
|
self.connect_own_address_type = None
|
|
@@ -3737,7 +3963,9 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3737
3963
|
|
|
3738
3964
|
try:
|
|
3739
3965
|
# Wait for a request or a completed connection
|
|
3740
|
-
pending_request = utils.cancel_on_event(
|
|
3966
|
+
pending_request = utils.cancel_on_event(
|
|
3967
|
+
self, Device.EVENT_FLUSH, pending_request_fut
|
|
3968
|
+
)
|
|
3741
3969
|
result = await (
|
|
3742
3970
|
asyncio.wait_for(pending_request, timeout)
|
|
3743
3971
|
if timeout
|
|
@@ -3779,8 +4007,8 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3779
4007
|
):
|
|
3780
4008
|
pending_connection.set_exception(error)
|
|
3781
4009
|
|
|
3782
|
-
self.on(
|
|
3783
|
-
self.on(
|
|
4010
|
+
self.on(self.EVENT_CONNECTION, on_connection)
|
|
4011
|
+
self.on(self.EVENT_CONNECTION_FAILURE, on_connection_failure)
|
|
3784
4012
|
|
|
3785
4013
|
# Save pending connection, with the Peripheral hci.role.
|
|
3786
4014
|
# Even if we requested a role switch in the hci.HCI_Accept_Connection_Request
|
|
@@ -3799,11 +4027,13 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3799
4027
|
)
|
|
3800
4028
|
|
|
3801
4029
|
# Wait for connection complete
|
|
3802
|
-
return await utils.cancel_on_event(
|
|
4030
|
+
return await utils.cancel_on_event(
|
|
4031
|
+
self, Device.EVENT_FLUSH, pending_connection
|
|
4032
|
+
)
|
|
3803
4033
|
|
|
3804
4034
|
finally:
|
|
3805
|
-
self.remove_listener(
|
|
3806
|
-
self.remove_listener(
|
|
4035
|
+
self.remove_listener(self.EVENT_CONNECTION, on_connection)
|
|
4036
|
+
self.remove_listener(self.EVENT_CONNECTION_FAILURE, on_connection_failure)
|
|
3807
4037
|
self.pending_connections.pop(peer_address, None)
|
|
3808
4038
|
|
|
3809
4039
|
@asynccontextmanager
|
|
@@ -3857,8 +4087,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3857
4087
|
) -> None:
|
|
3858
4088
|
# Create a future so that we can wait for the disconnection's result
|
|
3859
4089
|
pending_disconnection = asyncio.get_running_loop().create_future()
|
|
3860
|
-
connection.on(
|
|
3861
|
-
connection.on(
|
|
4090
|
+
connection.on(connection.EVENT_DISCONNECTION, pending_disconnection.set_result)
|
|
4091
|
+
connection.on(
|
|
4092
|
+
connection.EVENT_DISCONNECTION_FAILURE, pending_disconnection.set_exception
|
|
4093
|
+
)
|
|
3862
4094
|
|
|
3863
4095
|
# Request a disconnection
|
|
3864
4096
|
result = await self.send_command(
|
|
@@ -3873,13 +4105,16 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3873
4105
|
|
|
3874
4106
|
# Wait for the disconnection process to complete
|
|
3875
4107
|
self.disconnecting = True
|
|
3876
|
-
return await utils.cancel_on_event(
|
|
4108
|
+
return await utils.cancel_on_event(
|
|
4109
|
+
self, Device.EVENT_FLUSH, pending_disconnection
|
|
4110
|
+
)
|
|
3877
4111
|
finally:
|
|
3878
4112
|
connection.remove_listener(
|
|
3879
|
-
|
|
4113
|
+
connection.EVENT_DISCONNECTION, pending_disconnection.set_result
|
|
3880
4114
|
)
|
|
3881
4115
|
connection.remove_listener(
|
|
3882
|
-
|
|
4116
|
+
connection.EVENT_DISCONNECTION_FAILURE,
|
|
4117
|
+
pending_disconnection.set_exception,
|
|
3883
4118
|
)
|
|
3884
4119
|
self.disconnecting = False
|
|
3885
4120
|
|
|
@@ -3901,20 +4136,39 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3901
4136
|
|
|
3902
4137
|
async def update_connection_parameters(
|
|
3903
4138
|
self,
|
|
3904
|
-
connection,
|
|
3905
|
-
connection_interval_min,
|
|
3906
|
-
connection_interval_max,
|
|
3907
|
-
max_latency,
|
|
3908
|
-
supervision_timeout,
|
|
3909
|
-
min_ce_length=0,
|
|
3910
|
-
max_ce_length=0,
|
|
3911
|
-
use_l2cap=False,
|
|
4139
|
+
connection: Connection,
|
|
4140
|
+
connection_interval_min: float,
|
|
4141
|
+
connection_interval_max: float,
|
|
4142
|
+
max_latency: int,
|
|
4143
|
+
supervision_timeout: float,
|
|
4144
|
+
min_ce_length: float = 0.0,
|
|
4145
|
+
max_ce_length: float = 0.0,
|
|
4146
|
+
use_l2cap: bool = False,
|
|
3912
4147
|
) -> None:
|
|
3913
4148
|
'''
|
|
4149
|
+
Request an update of the connection parameters.
|
|
4150
|
+
|
|
4151
|
+
Args:
|
|
4152
|
+
connection: The connection to update
|
|
4153
|
+
connection_interval_min: Minimum interval, in milliseconds.
|
|
4154
|
+
connection_interval_max: Maximum interval, in milliseconds.
|
|
4155
|
+
max_latency: Latency, in number of intervals.
|
|
4156
|
+
supervision_timeout: Timeout, in milliseconds.
|
|
4157
|
+
min_ce_length: Minimum connection event length, in milliseconds.
|
|
4158
|
+
max_ce_length: Maximum connection event length, in milliseconds.
|
|
4159
|
+
use_l2cap: Request the update via L2CAP.
|
|
4160
|
+
|
|
3914
4161
|
NOTE: the name of the parameters may look odd, but it just follows the names
|
|
3915
4162
|
used in the Bluetooth spec.
|
|
3916
4163
|
'''
|
|
3917
4164
|
|
|
4165
|
+
# Convert the input parameters
|
|
4166
|
+
connection_interval_min = int(connection_interval_min / 1.25)
|
|
4167
|
+
connection_interval_max = int(connection_interval_max / 1.25)
|
|
4168
|
+
supervision_timeout = int(supervision_timeout / 10)
|
|
4169
|
+
min_ce_length = int(min_ce_length / 0.625)
|
|
4170
|
+
max_ce_length = int(max_ce_length / 0.625)
|
|
4171
|
+
|
|
3918
4172
|
if use_l2cap:
|
|
3919
4173
|
if connection.role != hci.Role.PERIPHERAL:
|
|
3920
4174
|
raise InvalidStateError(
|
|
@@ -3932,6 +4186,8 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3932
4186
|
if l2cap_result != l2cap.L2CAP_CONNECTION_PARAMETERS_ACCEPTED_RESULT:
|
|
3933
4187
|
raise ConnectionParameterUpdateError(l2cap_result)
|
|
3934
4188
|
|
|
4189
|
+
return
|
|
4190
|
+
|
|
3935
4191
|
result = await self.send_command(
|
|
3936
4192
|
hci.HCI_LE_Connection_Update_Command(
|
|
3937
4193
|
connection_handle=connection.handle,
|
|
@@ -4075,7 +4331,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4075
4331
|
else:
|
|
4076
4332
|
return None
|
|
4077
4333
|
|
|
4078
|
-
return await utils.cancel_on_event(self,
|
|
4334
|
+
return await utils.cancel_on_event(self, Device.EVENT_FLUSH, peer_address)
|
|
4079
4335
|
finally:
|
|
4080
4336
|
if listener is not None:
|
|
4081
4337
|
self.remove_listener(event_name, listener)
|
|
@@ -4125,7 +4381,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4125
4381
|
if not self.scanning:
|
|
4126
4382
|
await self.start_scanning(filter_duplicates=True)
|
|
4127
4383
|
|
|
4128
|
-
return await utils.cancel_on_event(self,
|
|
4384
|
+
return await utils.cancel_on_event(self, Device.EVENT_FLUSH, peer_address)
|
|
4129
4385
|
finally:
|
|
4130
4386
|
if listener is not None:
|
|
4131
4387
|
self.remove_listener(event_name, listener)
|
|
@@ -4144,11 +4400,11 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4144
4400
|
self.smp_manager.pairing_config_factory = pairing_config_factory
|
|
4145
4401
|
|
|
4146
4402
|
@property
|
|
4147
|
-
def smp_session_proxy(self) ->
|
|
4403
|
+
def smp_session_proxy(self) -> type[smp.Session]:
|
|
4148
4404
|
return self.smp_manager.session_proxy
|
|
4149
4405
|
|
|
4150
4406
|
@smp_session_proxy.setter
|
|
4151
|
-
def smp_session_proxy(self, session_proxy:
|
|
4407
|
+
def smp_session_proxy(self, session_proxy: type[smp.Session]) -> None:
|
|
4152
4408
|
self.smp_manager.session_proxy = session_proxy
|
|
4153
4409
|
|
|
4154
4410
|
async def pair(self, connection):
|
|
@@ -4201,7 +4457,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4201
4457
|
return keys.link_key.value
|
|
4202
4458
|
|
|
4203
4459
|
# [Classic only]
|
|
4204
|
-
async def authenticate(self, connection):
|
|
4460
|
+
async def authenticate(self, connection: Connection) -> None:
|
|
4205
4461
|
# Set up event handlers
|
|
4206
4462
|
pending_authentication = asyncio.get_running_loop().create_future()
|
|
4207
4463
|
|
|
@@ -4211,8 +4467,11 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4211
4467
|
def on_authentication_failure(error_code):
|
|
4212
4468
|
pending_authentication.set_exception(hci.HCI_Error(error_code))
|
|
4213
4469
|
|
|
4214
|
-
connection.on(
|
|
4215
|
-
connection.on(
|
|
4470
|
+
connection.on(connection.EVENT_CONNECTION_AUTHENTICATION, on_authentication)
|
|
4471
|
+
connection.on(
|
|
4472
|
+
connection.EVENT_CONNECTION_AUTHENTICATION_FAILURE,
|
|
4473
|
+
on_authentication_failure,
|
|
4474
|
+
)
|
|
4216
4475
|
|
|
4217
4476
|
# Request the authentication
|
|
4218
4477
|
try:
|
|
@@ -4229,13 +4488,14 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4229
4488
|
raise hci.HCI_StatusError(result)
|
|
4230
4489
|
|
|
4231
4490
|
# Wait for the authentication to complete
|
|
4232
|
-
await
|
|
4233
|
-
connection, 'disconnection', pending_authentication
|
|
4234
|
-
)
|
|
4491
|
+
await connection.cancel_on_disconnection(pending_authentication)
|
|
4235
4492
|
finally:
|
|
4236
|
-
connection.remove_listener('connection_authentication', on_authentication)
|
|
4237
4493
|
connection.remove_listener(
|
|
4238
|
-
|
|
4494
|
+
connection.EVENT_CONNECTION_AUTHENTICATION, on_authentication
|
|
4495
|
+
)
|
|
4496
|
+
connection.remove_listener(
|
|
4497
|
+
connection.EVENT_CONNECTION_AUTHENTICATION_FAILURE,
|
|
4498
|
+
on_authentication_failure,
|
|
4239
4499
|
)
|
|
4240
4500
|
|
|
4241
4501
|
async def encrypt(self, connection, enable=True):
|
|
@@ -4251,8 +4511,12 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4251
4511
|
def on_encryption_failure(error_code):
|
|
4252
4512
|
pending_encryption.set_exception(hci.HCI_Error(error_code))
|
|
4253
4513
|
|
|
4254
|
-
connection.on(
|
|
4255
|
-
|
|
4514
|
+
connection.on(
|
|
4515
|
+
connection.EVENT_CONNECTION_ENCRYPTION_CHANGE, on_encryption_change
|
|
4516
|
+
)
|
|
4517
|
+
connection.on(
|
|
4518
|
+
connection.EVENT_CONNECTION_ENCRYPTION_FAILURE, on_encryption_failure
|
|
4519
|
+
)
|
|
4256
4520
|
|
|
4257
4521
|
# Request the encryption
|
|
4258
4522
|
try:
|
|
@@ -4311,13 +4575,13 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4311
4575
|
raise hci.HCI_StatusError(result)
|
|
4312
4576
|
|
|
4313
4577
|
# Wait for the result
|
|
4314
|
-
await
|
|
4578
|
+
await connection.cancel_on_disconnection(pending_encryption)
|
|
4315
4579
|
finally:
|
|
4316
4580
|
connection.remove_listener(
|
|
4317
|
-
|
|
4581
|
+
connection.EVENT_CONNECTION_ENCRYPTION_CHANGE, on_encryption_change
|
|
4318
4582
|
)
|
|
4319
4583
|
connection.remove_listener(
|
|
4320
|
-
|
|
4584
|
+
connection.EVENT_CONNECTION_ENCRYPTION_FAILURE, on_encryption_failure
|
|
4321
4585
|
)
|
|
4322
4586
|
|
|
4323
4587
|
async def update_keys(self, address: str, keys: PairingKeys) -> None:
|
|
@@ -4330,7 +4594,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4330
4594
|
except Exception as error:
|
|
4331
4595
|
logger.warning(f'!!! error while storing keys: {error}')
|
|
4332
4596
|
else:
|
|
4333
|
-
self.emit(
|
|
4597
|
+
self.emit(self.EVENT_KEY_STORE_UPDATE)
|
|
4334
4598
|
|
|
4335
4599
|
# [Classic only]
|
|
4336
4600
|
async def switch_role(self, connection: Connection, role: hci.Role):
|
|
@@ -4342,8 +4606,8 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4342
4606
|
def on_role_change_failure(error_code):
|
|
4343
4607
|
pending_role_change.set_exception(hci.HCI_Error(error_code))
|
|
4344
4608
|
|
|
4345
|
-
connection.on(
|
|
4346
|
-
connection.on(
|
|
4609
|
+
connection.on(connection.EVENT_ROLE_CHANGE, on_role_change)
|
|
4610
|
+
connection.on(connection.EVENT_ROLE_CHANGE_FAILURE, on_role_change_failure)
|
|
4347
4611
|
|
|
4348
4612
|
try:
|
|
4349
4613
|
result = await self.send_command(
|
|
@@ -4355,12 +4619,12 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4355
4619
|
f'{hci.HCI_Constant.error_name(result.status)}'
|
|
4356
4620
|
)
|
|
4357
4621
|
raise hci.HCI_StatusError(result)
|
|
4358
|
-
await
|
|
4359
|
-
connection, 'disconnection', pending_role_change
|
|
4360
|
-
)
|
|
4622
|
+
await connection.cancel_on_disconnection(pending_role_change)
|
|
4361
4623
|
finally:
|
|
4362
|
-
connection.remove_listener(
|
|
4363
|
-
connection.remove_listener(
|
|
4624
|
+
connection.remove_listener(connection.EVENT_ROLE_CHANGE, on_role_change)
|
|
4625
|
+
connection.remove_listener(
|
|
4626
|
+
connection.EVENT_ROLE_CHANGE_FAILURE, on_role_change_failure
|
|
4627
|
+
)
|
|
4364
4628
|
|
|
4365
4629
|
# [Classic only]
|
|
4366
4630
|
async def request_remote_name(self, remote: Union[hci.Address, Connection]) -> str:
|
|
@@ -4372,7 +4636,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4372
4636
|
)
|
|
4373
4637
|
|
|
4374
4638
|
handler = self.on(
|
|
4375
|
-
|
|
4639
|
+
self.EVENT_REMOTE_NAME,
|
|
4376
4640
|
lambda address, remote_name: (
|
|
4377
4641
|
pending_name.set_result(remote_name)
|
|
4378
4642
|
if address == peer_address
|
|
@@ -4380,7 +4644,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4380
4644
|
),
|
|
4381
4645
|
)
|
|
4382
4646
|
failure_handler = self.on(
|
|
4383
|
-
|
|
4647
|
+
self.EVENT_REMOTE_NAME_FAILURE,
|
|
4384
4648
|
lambda address, error_code: (
|
|
4385
4649
|
pending_name.set_exception(hci.HCI_Error(error_code))
|
|
4386
4650
|
if address == peer_address
|
|
@@ -4406,57 +4670,48 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4406
4670
|
raise hci.HCI_StatusError(result)
|
|
4407
4671
|
|
|
4408
4672
|
# Wait for the result
|
|
4409
|
-
return await utils.cancel_on_event(self,
|
|
4673
|
+
return await utils.cancel_on_event(self, Device.EVENT_FLUSH, pending_name)
|
|
4410
4674
|
finally:
|
|
4411
|
-
self.remove_listener(
|
|
4412
|
-
self.remove_listener(
|
|
4675
|
+
self.remove_listener(self.EVENT_REMOTE_NAME, handler)
|
|
4676
|
+
self.remove_listener(self.EVENT_REMOTE_NAME_FAILURE, failure_handler)
|
|
4413
4677
|
|
|
4414
4678
|
# [LE only]
|
|
4415
4679
|
@utils.experimental('Only for testing.')
|
|
4416
4680
|
async def setup_cig(
|
|
4417
4681
|
self,
|
|
4418
|
-
|
|
4419
|
-
cis_id: Sequence[int],
|
|
4420
|
-
sdu_interval: tuple[int, int],
|
|
4421
|
-
framing: int,
|
|
4422
|
-
max_sdu: tuple[int, int],
|
|
4423
|
-
retransmission_number: int,
|
|
4424
|
-
max_transport_latency: tuple[int, int],
|
|
4682
|
+
parameters: CigParameters,
|
|
4425
4683
|
) -> list[int]:
|
|
4426
4684
|
"""Sends hci.HCI_LE_Set_CIG_Parameters_Command.
|
|
4427
4685
|
|
|
4428
4686
|
Args:
|
|
4429
|
-
|
|
4430
|
-
cis_id: CID ID list.
|
|
4431
|
-
sdu_interval: SDU intervals of (Central->Peripheral, Peripheral->Cental).
|
|
4432
|
-
framing: Un-framing(0) or Framing(1).
|
|
4433
|
-
max_sdu: Max SDU counts of (Central->Peripheral, Peripheral->Cental).
|
|
4434
|
-
retransmission_number: retransmission_number.
|
|
4435
|
-
max_transport_latency: Max transport latencies of
|
|
4436
|
-
(Central->Peripheral, Peripheral->Cental).
|
|
4687
|
+
parameters: CIG parameters.
|
|
4437
4688
|
|
|
4438
4689
|
Returns:
|
|
4439
4690
|
List of created CIS handles corresponding to the same order of [cid_id].
|
|
4440
4691
|
"""
|
|
4441
|
-
num_cis = len(
|
|
4692
|
+
num_cis = len(parameters.cis_parameters)
|
|
4442
4693
|
|
|
4443
4694
|
response = await self.send_command(
|
|
4444
4695
|
hci.HCI_LE_Set_CIG_Parameters_Command(
|
|
4445
|
-
cig_id=cig_id,
|
|
4446
|
-
sdu_interval_c_to_p=
|
|
4447
|
-
sdu_interval_p_to_c=
|
|
4448
|
-
worst_case_sca=
|
|
4449
|
-
packing=
|
|
4450
|
-
framing=framing,
|
|
4451
|
-
max_transport_latency_c_to_p=
|
|
4452
|
-
max_transport_latency_p_to_c=
|
|
4453
|
-
cis_id=cis_id,
|
|
4454
|
-
max_sdu_c_to_p=[
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4696
|
+
cig_id=parameters.cig_id,
|
|
4697
|
+
sdu_interval_c_to_p=parameters.sdu_interval_c_to_p,
|
|
4698
|
+
sdu_interval_p_to_c=parameters.sdu_interval_p_to_c,
|
|
4699
|
+
worst_case_sca=parameters.worst_case_sca,
|
|
4700
|
+
packing=int(parameters.packing),
|
|
4701
|
+
framing=int(parameters.framing),
|
|
4702
|
+
max_transport_latency_c_to_p=parameters.max_transport_latency_c_to_p,
|
|
4703
|
+
max_transport_latency_p_to_c=parameters.max_transport_latency_p_to_c,
|
|
4704
|
+
cis_id=[cis.cis_id for cis in parameters.cis_parameters],
|
|
4705
|
+
max_sdu_c_to_p=[
|
|
4706
|
+
cis.max_sdu_c_to_p for cis in parameters.cis_parameters
|
|
4707
|
+
],
|
|
4708
|
+
max_sdu_p_to_c=[
|
|
4709
|
+
cis.max_sdu_p_to_c for cis in parameters.cis_parameters
|
|
4710
|
+
],
|
|
4711
|
+
phy_c_to_p=[cis.phy_c_to_p for cis in parameters.cis_parameters],
|
|
4712
|
+
phy_p_to_c=[cis.phy_p_to_c for cis in parameters.cis_parameters],
|
|
4713
|
+
rtn_c_to_p=[cis.rtn_c_to_p for cis in parameters.cis_parameters],
|
|
4714
|
+
rtn_p_to_c=[cis.rtn_p_to_c for cis in parameters.cis_parameters],
|
|
4460
4715
|
),
|
|
4461
4716
|
check_result=True,
|
|
4462
4717
|
)
|
|
@@ -4464,19 +4719,17 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4464
4719
|
# Ideally, we should manage CIG lifecycle, but they are not useful for Unicast
|
|
4465
4720
|
# Server, so here it only provides a basic functionality for testing.
|
|
4466
4721
|
cis_handles = response.return_parameters.connection_handle[:]
|
|
4467
|
-
for
|
|
4468
|
-
self._pending_cis[cis_handle] = (
|
|
4722
|
+
for cis, cis_handle in zip(parameters.cis_parameters, cis_handles):
|
|
4723
|
+
self._pending_cis[cis_handle] = (cis.cis_id, parameters.cig_id)
|
|
4469
4724
|
|
|
4470
4725
|
return cis_handles
|
|
4471
4726
|
|
|
4472
4727
|
# [LE only]
|
|
4473
4728
|
@utils.experimental('Only for testing.')
|
|
4474
4729
|
async def create_cis(
|
|
4475
|
-
self, cis_acl_pairs: Sequence[tuple[int,
|
|
4730
|
+
self, cis_acl_pairs: Sequence[tuple[int, Connection]]
|
|
4476
4731
|
) -> list[CisLink]:
|
|
4477
|
-
for cis_handle,
|
|
4478
|
-
acl_connection = self.lookup_connection(acl_handle)
|
|
4479
|
-
assert acl_connection
|
|
4732
|
+
for cis_handle, acl_connection in cis_acl_pairs:
|
|
4480
4733
|
cis_id, cig_id = self._pending_cis.pop(cis_handle)
|
|
4481
4734
|
self.cis_links[cis_handle] = CisLink(
|
|
4482
4735
|
device=self,
|
|
@@ -4496,16 +4749,18 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4496
4749
|
if pending_future := pending_cis_establishments.get(cis_link.handle):
|
|
4497
4750
|
pending_future.set_result(cis_link)
|
|
4498
4751
|
|
|
4499
|
-
def on_cis_establishment_failure(
|
|
4500
|
-
if pending_future := pending_cis_establishments.get(
|
|
4752
|
+
def on_cis_establishment_failure(cis_link: CisLink, status: int) -> None:
|
|
4753
|
+
if pending_future := pending_cis_establishments.get(cis_link.handle):
|
|
4501
4754
|
pending_future.set_exception(hci.HCI_Error(status))
|
|
4502
4755
|
|
|
4503
|
-
watcher.on(self,
|
|
4504
|
-
watcher.on(
|
|
4756
|
+
watcher.on(self, self.EVENT_CIS_ESTABLISHMENT, on_cis_establishment)
|
|
4757
|
+
watcher.on(
|
|
4758
|
+
self, self.EVENT_CIS_ESTABLISHMENT_FAILURE, on_cis_establishment_failure
|
|
4759
|
+
)
|
|
4505
4760
|
await self.send_command(
|
|
4506
4761
|
hci.HCI_LE_Create_CIS_Command(
|
|
4507
4762
|
cis_connection_handle=[p[0] for p in cis_acl_pairs],
|
|
4508
|
-
acl_connection_handle=[p[1] for p in cis_acl_pairs],
|
|
4763
|
+
acl_connection_handle=[p[1].handle for p in cis_acl_pairs],
|
|
4509
4764
|
),
|
|
4510
4765
|
check_result=True,
|
|
4511
4766
|
)
|
|
@@ -4514,26 +4769,21 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4514
4769
|
|
|
4515
4770
|
# [LE only]
|
|
4516
4771
|
@utils.experimental('Only for testing.')
|
|
4517
|
-
async def accept_cis_request(self,
|
|
4772
|
+
async def accept_cis_request(self, cis_link: CisLink) -> None:
|
|
4518
4773
|
"""[LE Only] Accepts an incoming CIS request.
|
|
4519
4774
|
|
|
4520
|
-
|
|
4521
|
-
|
|
4775
|
+
This method returns when the CIS is established, or raises an exception if
|
|
4776
|
+
the CIS establishment fails.
|
|
4522
4777
|
|
|
4523
4778
|
Args:
|
|
4524
4779
|
handle: CIS handle to accept.
|
|
4525
|
-
|
|
4526
|
-
Returns:
|
|
4527
|
-
CIS link object on the given handle.
|
|
4528
4780
|
"""
|
|
4529
|
-
if not (cis_link := self.cis_links.get(handle)):
|
|
4530
|
-
raise InvalidStateError(f'No pending CIS request of handle {handle}')
|
|
4531
4781
|
|
|
4532
4782
|
# There might be multiple ASE sharing a CIS channel.
|
|
4533
4783
|
# If one of them has accepted the request, the others should just leverage it.
|
|
4534
4784
|
async with self._cis_lock:
|
|
4535
4785
|
if cis_link.state == CisLink.State.ESTABLISHED:
|
|
4536
|
-
return
|
|
4786
|
+
return
|
|
4537
4787
|
|
|
4538
4788
|
with closing(utils.EventWatcher()) as watcher:
|
|
4539
4789
|
pending_establishment = asyncio.get_running_loop().create_future()
|
|
@@ -4544,30 +4794,32 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4544
4794
|
def on_establishment_failure(status: int) -> None:
|
|
4545
4795
|
pending_establishment.set_exception(hci.HCI_Error(status))
|
|
4546
4796
|
|
|
4547
|
-
watcher.on(cis_link,
|
|
4548
|
-
watcher.on(
|
|
4797
|
+
watcher.on(cis_link, cis_link.EVENT_ESTABLISHMENT, on_establishment)
|
|
4798
|
+
watcher.on(
|
|
4799
|
+
cis_link,
|
|
4800
|
+
cis_link.EVENT_ESTABLISHMENT_FAILURE,
|
|
4801
|
+
on_establishment_failure,
|
|
4802
|
+
)
|
|
4549
4803
|
|
|
4550
4804
|
await self.send_command(
|
|
4551
|
-
hci.HCI_LE_Accept_CIS_Request_Command(
|
|
4805
|
+
hci.HCI_LE_Accept_CIS_Request_Command(
|
|
4806
|
+
connection_handle=cis_link.handle
|
|
4807
|
+
),
|
|
4552
4808
|
check_result=True,
|
|
4553
4809
|
)
|
|
4554
4810
|
|
|
4555
4811
|
await pending_establishment
|
|
4556
|
-
return cis_link
|
|
4557
|
-
|
|
4558
|
-
# Mypy believes this is reachable when context is an ExitStack.
|
|
4559
|
-
raise UnreachableError()
|
|
4560
4812
|
|
|
4561
4813
|
# [LE only]
|
|
4562
4814
|
@utils.experimental('Only for testing.')
|
|
4563
4815
|
async def reject_cis_request(
|
|
4564
4816
|
self,
|
|
4565
|
-
|
|
4817
|
+
cis_link: CisLink,
|
|
4566
4818
|
reason: int = hci.HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR,
|
|
4567
4819
|
) -> None:
|
|
4568
4820
|
await self.send_command(
|
|
4569
4821
|
hci.HCI_LE_Reject_CIS_Request_Command(
|
|
4570
|
-
connection_handle=handle, reason=reason
|
|
4822
|
+
connection_handle=cis_link.handle, reason=reason
|
|
4571
4823
|
),
|
|
4572
4824
|
check_result=True,
|
|
4573
4825
|
)
|
|
@@ -4913,33 +5165,33 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4913
5165
|
|
|
4914
5166
|
@host_event_handler
|
|
4915
5167
|
def on_flush(self):
|
|
4916
|
-
self.emit(
|
|
5168
|
+
self.emit(self.EVENT_FLUSH)
|
|
4917
5169
|
for _, connection in self.connections.items():
|
|
4918
|
-
connection.emit(
|
|
5170
|
+
connection.emit(connection.EVENT_DISCONNECTION, 0)
|
|
4919
5171
|
self.connections = {}
|
|
4920
5172
|
|
|
4921
5173
|
# [Classic only]
|
|
4922
5174
|
@host_event_handler
|
|
4923
|
-
def on_link_key(self, bd_addr, link_key, key_type):
|
|
5175
|
+
def on_link_key(self, bd_addr: hci.Address, link_key: bytes, key_type: int) -> None:
|
|
4924
5176
|
# Store the keys in the key store
|
|
4925
5177
|
if self.keystore:
|
|
4926
5178
|
authenticated = key_type in (
|
|
4927
|
-
hci.
|
|
4928
|
-
hci.
|
|
5179
|
+
hci.LinkKeyType.AUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P_192,
|
|
5180
|
+
hci.LinkKeyType.AUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P_256,
|
|
4929
5181
|
)
|
|
4930
|
-
pairing_keys = PairingKeys(
|
|
4931
|
-
|
|
4932
|
-
|
|
5182
|
+
pairing_keys = PairingKeys(
|
|
5183
|
+
link_key=PairingKeys.Key(value=link_key, authenticated=authenticated),
|
|
5184
|
+
link_key_type=key_type,
|
|
4933
5185
|
)
|
|
4934
5186
|
|
|
4935
5187
|
utils.cancel_on_event(
|
|
4936
|
-
self,
|
|
5188
|
+
self, Device.EVENT_FLUSH, self.update_keys(str(bd_addr), pairing_keys)
|
|
4937
5189
|
)
|
|
4938
5190
|
|
|
4939
5191
|
if connection := self.find_connection_by_bd_addr(
|
|
4940
5192
|
bd_addr, transport=PhysicalTransport.BR_EDR
|
|
4941
5193
|
):
|
|
4942
|
-
connection.
|
|
5194
|
+
connection.emit(connection.EVENT_LINK_KEY)
|
|
4943
5195
|
|
|
4944
5196
|
def add_service(self, service):
|
|
4945
5197
|
self.gatt_server.add_service(service)
|
|
@@ -5105,13 +5357,13 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5105
5357
|
big.bis_links = [BisLink(handle=handle, big=big) for handle in bis_handles]
|
|
5106
5358
|
big.big_sync_delay = big_sync_delay
|
|
5107
5359
|
big.transport_latency_big = transport_latency_big
|
|
5108
|
-
big.phy = phy
|
|
5360
|
+
big.phy = hci.Phy(phy)
|
|
5109
5361
|
big.nse = nse
|
|
5110
5362
|
big.bn = bn
|
|
5111
5363
|
big.pto = pto
|
|
5112
5364
|
big.irc = irc
|
|
5113
5365
|
big.max_pdu = max_pdu
|
|
5114
|
-
big.iso_interval = iso_interval
|
|
5366
|
+
big.iso_interval = iso_interval * 1.25
|
|
5115
5367
|
big.state = Big.State.ACTIVE
|
|
5116
5368
|
|
|
5117
5369
|
for bis_link in big.bis_links:
|
|
@@ -5160,7 +5412,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5160
5412
|
big_sync.pto = pto
|
|
5161
5413
|
big_sync.irc = irc
|
|
5162
5414
|
big_sync.max_pdu = max_pdu
|
|
5163
|
-
big_sync.iso_interval = iso_interval
|
|
5415
|
+
big_sync.iso_interval = iso_interval * 1.25
|
|
5164
5416
|
big_sync.bis_links = [
|
|
5165
5417
|
BisLink(handle=handle, big=big_sync) for handle in bis_handles
|
|
5166
5418
|
]
|
|
@@ -5202,11 +5454,13 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5202
5454
|
# Setup auto-restart of the advertising set if needed.
|
|
5203
5455
|
if advertising_set.auto_restart:
|
|
5204
5456
|
connection.once(
|
|
5205
|
-
|
|
5206
|
-
lambda _: utils.cancel_on_event(
|
|
5457
|
+
Connection.EVENT_DISCONNECTION,
|
|
5458
|
+
lambda _: utils.cancel_on_event(
|
|
5459
|
+
self, Device.EVENT_FLUSH, advertising_set.start()
|
|
5460
|
+
),
|
|
5207
5461
|
)
|
|
5208
5462
|
|
|
5209
|
-
self.emit(
|
|
5463
|
+
self.emit(self.EVENT_CONNECTION, connection)
|
|
5210
5464
|
|
|
5211
5465
|
@host_event_handler
|
|
5212
5466
|
def on_connection(
|
|
@@ -5217,7 +5471,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5217
5471
|
self_resolvable_address: Optional[hci.Address],
|
|
5218
5472
|
peer_resolvable_address: Optional[hci.Address],
|
|
5219
5473
|
role: hci.Role,
|
|
5220
|
-
connection_parameters: ConnectionParameters,
|
|
5474
|
+
connection_parameters: Optional[core.ConnectionParameters],
|
|
5221
5475
|
) -> None:
|
|
5222
5476
|
# Convert all-zeros addresses into None.
|
|
5223
5477
|
if self_resolvable_address == hci.Address.ANY_RANDOM:
|
|
@@ -5244,10 +5498,12 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5244
5498
|
self.connections[connection_handle] = connection
|
|
5245
5499
|
|
|
5246
5500
|
# Emit an event to notify listeners of the new connection
|
|
5247
|
-
self.emit(
|
|
5501
|
+
self.emit(self.EVENT_CONNECTION, connection)
|
|
5248
5502
|
|
|
5249
5503
|
return
|
|
5250
5504
|
|
|
5505
|
+
assert connection_parameters is not None
|
|
5506
|
+
|
|
5251
5507
|
if peer_resolvable_address is None:
|
|
5252
5508
|
# Resolve the peer address if we can
|
|
5253
5509
|
if self.address_resolver:
|
|
@@ -5303,7 +5559,11 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5303
5559
|
peer_address,
|
|
5304
5560
|
peer_resolvable_address,
|
|
5305
5561
|
role,
|
|
5306
|
-
|
|
5562
|
+
Connection.Parameters(
|
|
5563
|
+
connection_parameters.connection_interval * 1.25,
|
|
5564
|
+
connection_parameters.peripheral_latency,
|
|
5565
|
+
connection_parameters.supervision_timeout * 10.0,
|
|
5566
|
+
),
|
|
5307
5567
|
)
|
|
5308
5568
|
self.connections[connection_handle] = connection
|
|
5309
5569
|
|
|
@@ -5311,15 +5571,17 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5311
5571
|
if self.legacy_advertiser.auto_restart:
|
|
5312
5572
|
advertiser = self.legacy_advertiser
|
|
5313
5573
|
connection.once(
|
|
5314
|
-
|
|
5315
|
-
lambda _: utils.cancel_on_event(
|
|
5574
|
+
Connection.EVENT_DISCONNECTION,
|
|
5575
|
+
lambda _: utils.cancel_on_event(
|
|
5576
|
+
self, Device.EVENT_FLUSH, advertiser.start()
|
|
5577
|
+
),
|
|
5316
5578
|
)
|
|
5317
5579
|
else:
|
|
5318
5580
|
self.legacy_advertiser = None
|
|
5319
5581
|
|
|
5320
5582
|
if role == hci.Role.CENTRAL or not self.supports_le_extended_advertising:
|
|
5321
5583
|
# We can emit now, we have all the info we need
|
|
5322
|
-
self.emit(
|
|
5584
|
+
self.emit(self.EVENT_CONNECTION, connection)
|
|
5323
5585
|
return
|
|
5324
5586
|
|
|
5325
5587
|
if role == hci.Role.PERIPHERAL and self.supports_le_extended_advertising:
|
|
@@ -5353,7 +5615,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5353
5615
|
'hci',
|
|
5354
5616
|
hci.HCI_Constant.error_name(error_code),
|
|
5355
5617
|
)
|
|
5356
|
-
self.emit(
|
|
5618
|
+
self.emit(self.EVENT_CONNECTION_FAILURE, error)
|
|
5357
5619
|
|
|
5358
5620
|
# FIXME: Explore a delegate-model for BR/EDR wait connection #56.
|
|
5359
5621
|
@host_event_handler
|
|
@@ -5362,13 +5624,13 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5362
5624
|
|
|
5363
5625
|
# Handle SCO request.
|
|
5364
5626
|
if link_type in (
|
|
5365
|
-
hci.HCI_Connection_Complete_Event.
|
|
5366
|
-
hci.HCI_Connection_Complete_Event.
|
|
5627
|
+
hci.HCI_Connection_Complete_Event.LinkType.SCO,
|
|
5628
|
+
hci.HCI_Connection_Complete_Event.LinkType.ESCO,
|
|
5367
5629
|
):
|
|
5368
5630
|
if connection := self.find_connection_by_bd_addr(
|
|
5369
5631
|
bd_addr, transport=PhysicalTransport.BR_EDR
|
|
5370
5632
|
):
|
|
5371
|
-
self.emit(
|
|
5633
|
+
self.emit(self.EVENT_SCO_REQUEST, connection, link_type)
|
|
5372
5634
|
else:
|
|
5373
5635
|
logger.error(f'SCO request from a non-connected device {bd_addr}')
|
|
5374
5636
|
return
|
|
@@ -5412,14 +5674,14 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5412
5674
|
f'*** Disconnection: [0x{connection.handle:04X}] '
|
|
5413
5675
|
f'{connection.peer_address} as {connection.role_name}, reason={reason}'
|
|
5414
5676
|
)
|
|
5415
|
-
connection.emit(
|
|
5677
|
+
connection.emit(connection.EVENT_DISCONNECTION, reason)
|
|
5416
5678
|
|
|
5417
5679
|
# Cleanup subsystems that maintain per-connection state
|
|
5418
5680
|
self.gatt_server.on_disconnection(connection)
|
|
5419
5681
|
elif sco_link := self.sco_links.pop(connection_handle, None):
|
|
5420
|
-
sco_link.emit(
|
|
5682
|
+
sco_link.emit(sco_link.EVENT_DISCONNECTION, reason)
|
|
5421
5683
|
elif cis_link := self.cis_links.pop(connection_handle, None):
|
|
5422
|
-
cis_link.emit(
|
|
5684
|
+
cis_link.emit(cis_link.EVENT_DISCONNECTION, reason)
|
|
5423
5685
|
else:
|
|
5424
5686
|
logger.error(
|
|
5425
5687
|
f'*** Unknown disconnection handle=0x{connection_handle}, reason={reason} ***'
|
|
@@ -5427,7 +5689,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5427
5689
|
|
|
5428
5690
|
@host_event_handler
|
|
5429
5691
|
@with_connection_from_handle
|
|
5430
|
-
def on_disconnection_failure(self, connection, error_code):
|
|
5692
|
+
def on_disconnection_failure(self, connection: Connection, error_code: int):
|
|
5431
5693
|
logger.debug(f'*** Disconnection failed: {error_code}')
|
|
5432
5694
|
error = core.ConnectionError(
|
|
5433
5695
|
error_code,
|
|
@@ -5436,7 +5698,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5436
5698
|
'hci',
|
|
5437
5699
|
hci.HCI_Constant.error_name(error_code),
|
|
5438
5700
|
)
|
|
5439
|
-
connection.emit(
|
|
5701
|
+
connection.emit(connection.EVENT_DISCONNECTION_FAILURE, error)
|
|
5440
5702
|
|
|
5441
5703
|
@host_event_handler
|
|
5442
5704
|
@utils.AsyncRunner.run_in_task()
|
|
@@ -5447,7 +5709,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5447
5709
|
else:
|
|
5448
5710
|
self.auto_restart_inquiry = True
|
|
5449
5711
|
self.discovering = False
|
|
5450
|
-
self.emit(
|
|
5712
|
+
self.emit(self.EVENT_INQUIRY_COMPLETE)
|
|
5451
5713
|
|
|
5452
5714
|
@host_event_handler
|
|
5453
5715
|
@with_connection_from_handle
|
|
@@ -5457,7 +5719,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5457
5719
|
f'{connection.peer_address} as {connection.role_name}'
|
|
5458
5720
|
)
|
|
5459
5721
|
connection.authenticated = True
|
|
5460
|
-
connection.emit(
|
|
5722
|
+
connection.emit(connection.EVENT_CONNECTION_AUTHENTICATION)
|
|
5461
5723
|
|
|
5462
5724
|
@host_event_handler
|
|
5463
5725
|
@with_connection_from_handle
|
|
@@ -5466,12 +5728,12 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5466
5728
|
f'*** Connection Authentication Failure: [0x{connection.handle:04X}] '
|
|
5467
5729
|
f'{connection.peer_address} as {connection.role_name}, error={error}'
|
|
5468
5730
|
)
|
|
5469
|
-
connection.emit(
|
|
5731
|
+
connection.emit(connection.EVENT_CONNECTION_AUTHENTICATION_FAILURE, error)
|
|
5470
5732
|
|
|
5471
5733
|
# [Classic only]
|
|
5472
5734
|
@host_event_handler
|
|
5473
5735
|
@with_connection_from_address
|
|
5474
|
-
def on_authentication_io_capability_request(self, connection):
|
|
5736
|
+
def on_authentication_io_capability_request(self, connection: Connection):
|
|
5475
5737
|
# Ask what the pairing config should be for this connection
|
|
5476
5738
|
pairing_config = self.pairing_config_factory(connection)
|
|
5477
5739
|
|
|
@@ -5479,13 +5741,13 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5479
5741
|
authentication_requirements = (
|
|
5480
5742
|
# No Bonding
|
|
5481
5743
|
(
|
|
5482
|
-
hci.
|
|
5483
|
-
hci.
|
|
5744
|
+
hci.AuthenticationRequirements.MITM_NOT_REQUIRED_NO_BONDING,
|
|
5745
|
+
hci.AuthenticationRequirements.MITM_REQUIRED_NO_BONDING,
|
|
5484
5746
|
),
|
|
5485
5747
|
# General Bonding
|
|
5486
5748
|
(
|
|
5487
|
-
hci.
|
|
5488
|
-
hci.
|
|
5749
|
+
hci.AuthenticationRequirements.MITM_NOT_REQUIRED_GENERAL_BONDING,
|
|
5750
|
+
hci.AuthenticationRequirements.MITM_REQUIRED_GENERAL_BONDING,
|
|
5489
5751
|
),
|
|
5490
5752
|
)[1 if pairing_config.bonding else 0][1 if pairing_config.mitm else 0]
|
|
5491
5753
|
|
|
@@ -5540,30 +5802,30 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5540
5802
|
raise UnreachableError()
|
|
5541
5803
|
|
|
5542
5804
|
# See Bluetooth spec @ Vol 3, Part C 5.2.2.6
|
|
5543
|
-
methods = {
|
|
5544
|
-
hci.
|
|
5545
|
-
hci.
|
|
5546
|
-
hci.
|
|
5547
|
-
hci.
|
|
5548
|
-
hci.
|
|
5805
|
+
methods: dict[int, dict[int, Callable[[], Awaitable[bool]]]] = {
|
|
5806
|
+
hci.IoCapability.DISPLAY_ONLY: {
|
|
5807
|
+
hci.IoCapability.DISPLAY_ONLY: display_auto_confirm,
|
|
5808
|
+
hci.IoCapability.DISPLAY_YES_NO: display_confirm,
|
|
5809
|
+
hci.IoCapability.KEYBOARD_ONLY: na,
|
|
5810
|
+
hci.IoCapability.NO_INPUT_NO_OUTPUT: auto_confirm,
|
|
5549
5811
|
},
|
|
5550
|
-
hci.
|
|
5551
|
-
hci.
|
|
5552
|
-
hci.
|
|
5553
|
-
hci.
|
|
5554
|
-
hci.
|
|
5812
|
+
hci.IoCapability.DISPLAY_YES_NO: {
|
|
5813
|
+
hci.IoCapability.DISPLAY_ONLY: display_auto_confirm,
|
|
5814
|
+
hci.IoCapability.DISPLAY_YES_NO: display_confirm,
|
|
5815
|
+
hci.IoCapability.KEYBOARD_ONLY: na,
|
|
5816
|
+
hci.IoCapability.NO_INPUT_NO_OUTPUT: auto_confirm,
|
|
5555
5817
|
},
|
|
5556
|
-
hci.
|
|
5557
|
-
hci.
|
|
5558
|
-
hci.
|
|
5559
|
-
hci.
|
|
5560
|
-
hci.
|
|
5818
|
+
hci.IoCapability.KEYBOARD_ONLY: {
|
|
5819
|
+
hci.IoCapability.DISPLAY_ONLY: na,
|
|
5820
|
+
hci.IoCapability.DISPLAY_YES_NO: na,
|
|
5821
|
+
hci.IoCapability.KEYBOARD_ONLY: na,
|
|
5822
|
+
hci.IoCapability.NO_INPUT_NO_OUTPUT: auto_confirm,
|
|
5561
5823
|
},
|
|
5562
|
-
hci.
|
|
5563
|
-
hci.
|
|
5564
|
-
hci.
|
|
5565
|
-
hci.
|
|
5566
|
-
hci.
|
|
5824
|
+
hci.IoCapability.NO_INPUT_NO_OUTPUT: {
|
|
5825
|
+
hci.IoCapability.DISPLAY_ONLY: confirm,
|
|
5826
|
+
hci.IoCapability.DISPLAY_YES_NO: confirm,
|
|
5827
|
+
hci.IoCapability.KEYBOARD_ONLY: auto_confirm,
|
|
5828
|
+
hci.IoCapability.NO_INPUT_NO_OUTPUT: auto_confirm,
|
|
5567
5829
|
},
|
|
5568
5830
|
}
|
|
5569
5831
|
|
|
@@ -5571,7 +5833,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5571
5833
|
|
|
5572
5834
|
async def reply() -> None:
|
|
5573
5835
|
try:
|
|
5574
|
-
if await
|
|
5836
|
+
if await connection.cancel_on_disconnection(method()):
|
|
5575
5837
|
await self.host.send_command(
|
|
5576
5838
|
hci.HCI_User_Confirmation_Request_Reply_Command(
|
|
5577
5839
|
bd_addr=connection.peer_address
|
|
@@ -5598,8 +5860,8 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5598
5860
|
|
|
5599
5861
|
async def reply() -> None:
|
|
5600
5862
|
try:
|
|
5601
|
-
number = await
|
|
5602
|
-
|
|
5863
|
+
number = await connection.cancel_on_disconnection(
|
|
5864
|
+
pairing_config.delegate.get_number()
|
|
5603
5865
|
)
|
|
5604
5866
|
if number is not None:
|
|
5605
5867
|
await self.host.send_command(
|
|
@@ -5619,6 +5881,19 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5619
5881
|
|
|
5620
5882
|
utils.AsyncRunner.spawn(reply())
|
|
5621
5883
|
|
|
5884
|
+
# [Classic only]
|
|
5885
|
+
@host_event_handler
|
|
5886
|
+
@with_connection_from_handle
|
|
5887
|
+
def on_mode_change(
|
|
5888
|
+
self, connection: Connection, status: int, current_mode: int, interval: int
|
|
5889
|
+
):
|
|
5890
|
+
if status == hci.HCI_SUCCESS:
|
|
5891
|
+
connection.classic_mode = current_mode
|
|
5892
|
+
connection.classic_interval = interval
|
|
5893
|
+
connection.emit(connection.EVENT_MODE_CHANGE)
|
|
5894
|
+
else:
|
|
5895
|
+
connection.emit(connection.EVENT_MODE_CHANGE_FAILURE, status)
|
|
5896
|
+
|
|
5622
5897
|
# [Classic only]
|
|
5623
5898
|
@host_event_handler
|
|
5624
5899
|
@with_connection_from_address
|
|
@@ -5629,11 +5904,11 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5629
5904
|
io_capability = pairing_config.delegate.classic_io_capability
|
|
5630
5905
|
|
|
5631
5906
|
# Respond
|
|
5632
|
-
if io_capability == hci.
|
|
5907
|
+
if io_capability == hci.IoCapability.KEYBOARD_ONLY:
|
|
5633
5908
|
# Ask the user to enter a string
|
|
5634
5909
|
async def get_pin_code():
|
|
5635
|
-
pin_code = await
|
|
5636
|
-
|
|
5910
|
+
pin_code = await connection.cancel_on_disconnection(
|
|
5911
|
+
pairing_config.delegate.get_string(16)
|
|
5637
5912
|
)
|
|
5638
5913
|
|
|
5639
5914
|
if pin_code is not None:
|
|
@@ -5671,8 +5946,8 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5671
5946
|
pairing_config = self.pairing_config_factory(connection)
|
|
5672
5947
|
|
|
5673
5948
|
# Show the passkey to the user
|
|
5674
|
-
|
|
5675
|
-
|
|
5949
|
+
connection.cancel_on_disconnection(
|
|
5950
|
+
pairing_config.delegate.display_number(passkey, digits=6)
|
|
5676
5951
|
)
|
|
5677
5952
|
|
|
5678
5953
|
# [Classic only]
|
|
@@ -5684,22 +5959,22 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5684
5959
|
remote_name = remote_name.decode('utf-8')
|
|
5685
5960
|
if connection:
|
|
5686
5961
|
connection.peer_name = remote_name
|
|
5687
|
-
connection.emit(
|
|
5688
|
-
self.emit(
|
|
5962
|
+
connection.emit(connection.EVENT_REMOTE_NAME)
|
|
5963
|
+
self.emit(self.EVENT_REMOTE_NAME, address, remote_name)
|
|
5689
5964
|
except UnicodeDecodeError as error:
|
|
5690
5965
|
logger.warning('peer name is not valid UTF-8')
|
|
5691
5966
|
if connection:
|
|
5692
|
-
connection.emit(
|
|
5967
|
+
connection.emit(connection.EVENT_REMOTE_NAME_FAILURE, error)
|
|
5693
5968
|
else:
|
|
5694
|
-
self.emit(
|
|
5969
|
+
self.emit(self.EVENT_REMOTE_NAME_FAILURE, address, error)
|
|
5695
5970
|
|
|
5696
5971
|
# [Classic only]
|
|
5697
5972
|
@host_event_handler
|
|
5698
5973
|
@try_with_connection_from_address
|
|
5699
5974
|
def on_remote_name_failure(self, connection: Connection, address, error):
|
|
5700
5975
|
if connection:
|
|
5701
|
-
connection.emit(
|
|
5702
|
-
self.emit(
|
|
5976
|
+
connection.emit(connection.EVENT_REMOTE_NAME_FAILURE, error)
|
|
5977
|
+
self.emit(self.EVENT_REMOTE_NAME_FAILURE, address, error)
|
|
5703
5978
|
|
|
5704
5979
|
# [Classic only]
|
|
5705
5980
|
@host_event_handler
|
|
@@ -5719,7 +5994,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5719
5994
|
handle=sco_handle,
|
|
5720
5995
|
link_type=link_type,
|
|
5721
5996
|
)
|
|
5722
|
-
self.emit(
|
|
5997
|
+
self.emit(self.EVENT_SCO_CONNECTION, sco_link)
|
|
5723
5998
|
|
|
5724
5999
|
# [Classic only]
|
|
5725
6000
|
@host_event_handler
|
|
@@ -5729,7 +6004,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5729
6004
|
self, acl_connection: Connection, status: int
|
|
5730
6005
|
) -> None:
|
|
5731
6006
|
logger.debug(f'*** SCO connection failure: {acl_connection.peer_address}***')
|
|
5732
|
-
self.emit(
|
|
6007
|
+
self.emit(self.EVENT_SCO_CONNECTION_FAILURE)
|
|
5733
6008
|
|
|
5734
6009
|
# [Classic only]
|
|
5735
6010
|
@host_event_handler
|
|
@@ -5759,24 +6034,63 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5759
6034
|
f'cis_id=[0x{cis_id:02X}] ***'
|
|
5760
6035
|
)
|
|
5761
6036
|
# LE_CIS_Established event doesn't provide info, so we must store them here.
|
|
5762
|
-
|
|
6037
|
+
cis_link = CisLink(
|
|
5763
6038
|
device=self,
|
|
5764
6039
|
acl_connection=acl_connection,
|
|
5765
6040
|
handle=cis_handle,
|
|
5766
6041
|
cig_id=cig_id,
|
|
5767
6042
|
cis_id=cis_id,
|
|
5768
6043
|
)
|
|
5769
|
-
self.
|
|
6044
|
+
self.cis_links[cis_handle] = cis_link
|
|
6045
|
+
acl_connection.emit(acl_connection.EVENT_CIS_REQUEST, cis_link)
|
|
6046
|
+
self.emit(self.EVENT_CIS_REQUEST, cis_link)
|
|
5770
6047
|
|
|
5771
6048
|
# [LE only]
|
|
5772
6049
|
@host_event_handler
|
|
5773
6050
|
@utils.experimental('Only for testing')
|
|
5774
|
-
def on_cis_establishment(
|
|
6051
|
+
def on_cis_establishment(
|
|
6052
|
+
self,
|
|
6053
|
+
cis_handle: int,
|
|
6054
|
+
cig_sync_delay: int,
|
|
6055
|
+
cis_sync_delay: int,
|
|
6056
|
+
transport_latency_c_to_p: int,
|
|
6057
|
+
transport_latency_p_to_c: int,
|
|
6058
|
+
phy_c_to_p: int,
|
|
6059
|
+
phy_p_to_c: int,
|
|
6060
|
+
nse: int,
|
|
6061
|
+
bn_c_to_p: int,
|
|
6062
|
+
bn_p_to_c: int,
|
|
6063
|
+
ft_c_to_p: int,
|
|
6064
|
+
ft_p_to_c: int,
|
|
6065
|
+
max_pdu_c_to_p: int,
|
|
6066
|
+
max_pdu_p_to_c: int,
|
|
6067
|
+
iso_interval: int,
|
|
6068
|
+
) -> None:
|
|
6069
|
+
if cis_handle not in self.cis_links:
|
|
6070
|
+
logger.warning("CIS link not found")
|
|
6071
|
+
return
|
|
6072
|
+
|
|
5775
6073
|
cis_link = self.cis_links[cis_handle]
|
|
5776
6074
|
cis_link.state = CisLink.State.ESTABLISHED
|
|
5777
6075
|
|
|
5778
6076
|
assert cis_link.acl_connection
|
|
5779
6077
|
|
|
6078
|
+
# Update the CIS
|
|
6079
|
+
cis_link.cig_sync_delay = cig_sync_delay
|
|
6080
|
+
cis_link.cis_sync_delay = cis_sync_delay
|
|
6081
|
+
cis_link.transport_latency_c_to_p = transport_latency_c_to_p
|
|
6082
|
+
cis_link.transport_latency_p_to_c = transport_latency_p_to_c
|
|
6083
|
+
cis_link.phy_c_to_p = hci.Phy(phy_c_to_p)
|
|
6084
|
+
cis_link.phy_p_to_c = hci.Phy(phy_p_to_c)
|
|
6085
|
+
cis_link.nse = nse
|
|
6086
|
+
cis_link.bn_c_to_p = bn_c_to_p
|
|
6087
|
+
cis_link.bn_p_to_c = bn_p_to_c
|
|
6088
|
+
cis_link.ft_c_to_p = ft_c_to_p
|
|
6089
|
+
cis_link.ft_p_to_c = ft_p_to_c
|
|
6090
|
+
cis_link.max_pdu_c_to_p = max_pdu_c_to_p
|
|
6091
|
+
cis_link.max_pdu_p_to_c = max_pdu_p_to_c
|
|
6092
|
+
cis_link.iso_interval = iso_interval * 1.25
|
|
6093
|
+
|
|
5780
6094
|
logger.debug(
|
|
5781
6095
|
f'*** CIS Establishment '
|
|
5782
6096
|
f'{cis_link.acl_connection.peer_address}, '
|
|
@@ -5785,17 +6099,28 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5785
6099
|
f'cis_id=[0x{cis_link.cis_id:02X}] ***'
|
|
5786
6100
|
)
|
|
5787
6101
|
|
|
5788
|
-
cis_link.emit(
|
|
5789
|
-
|
|
6102
|
+
cis_link.emit(cis_link.EVENT_ESTABLISHMENT)
|
|
6103
|
+
cis_link.acl_connection.emit(
|
|
6104
|
+
cis_link.acl_connection.EVENT_CIS_ESTABLISHMENT, cis_link
|
|
6105
|
+
)
|
|
6106
|
+
self.emit(self.EVENT_CIS_ESTABLISHMENT, cis_link)
|
|
5790
6107
|
|
|
5791
6108
|
# [LE only]
|
|
5792
6109
|
@host_event_handler
|
|
5793
6110
|
@utils.experimental('Only for testing')
|
|
5794
6111
|
def on_cis_establishment_failure(self, cis_handle: int, status: int) -> None:
|
|
6112
|
+
if (cis_link := self.cis_links.pop(cis_handle, None)) is None:
|
|
6113
|
+
logger.warning("CIS link not found")
|
|
6114
|
+
return
|
|
6115
|
+
|
|
5795
6116
|
logger.debug(f'*** CIS Establishment Failure: cis=[0x{cis_handle:04X}] ***')
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
6117
|
+
cis_link.emit(cis_link.EVENT_ESTABLISHMENT_FAILURE, status)
|
|
6118
|
+
cis_link.acl_connection.emit(
|
|
6119
|
+
cis_link.acl_connection.EVENT_CIS_ESTABLISHMENT_FAILURE,
|
|
6120
|
+
cis_link,
|
|
6121
|
+
status,
|
|
6122
|
+
)
|
|
6123
|
+
self.emit(self.EVENT_CIS_ESTABLISHMENT_FAILURE, cis_link, status)
|
|
5799
6124
|
|
|
5800
6125
|
# [LE only]
|
|
5801
6126
|
@host_event_handler
|
|
@@ -5808,28 +6133,32 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5808
6133
|
|
|
5809
6134
|
@host_event_handler
|
|
5810
6135
|
@with_connection_from_handle
|
|
5811
|
-
def on_connection_encryption_change(
|
|
6136
|
+
def on_connection_encryption_change(
|
|
6137
|
+
self, connection: Connection, encryption: int, encryption_key_size: int
|
|
6138
|
+
):
|
|
5812
6139
|
logger.debug(
|
|
5813
6140
|
f'*** Connection Encryption Change: [0x{connection.handle:04X}] '
|
|
5814
6141
|
f'{connection.peer_address} as {connection.role_name}, '
|
|
5815
|
-
f'encryption={encryption}'
|
|
6142
|
+
f'encryption={encryption}, '
|
|
6143
|
+
f'key_size={encryption_key_size}'
|
|
5816
6144
|
)
|
|
5817
6145
|
connection.encryption = encryption
|
|
6146
|
+
connection.encryption_key_size = encryption_key_size
|
|
5818
6147
|
if (
|
|
5819
6148
|
not connection.authenticated
|
|
5820
6149
|
and connection.transport == PhysicalTransport.BR_EDR
|
|
5821
|
-
and encryption == hci.HCI_Encryption_Change_Event.AES_CCM
|
|
6150
|
+
and encryption == hci.HCI_Encryption_Change_Event.Enabled.AES_CCM
|
|
5822
6151
|
):
|
|
5823
6152
|
connection.authenticated = True
|
|
5824
6153
|
connection.sc = True
|
|
5825
6154
|
if (
|
|
5826
6155
|
not connection.authenticated
|
|
5827
6156
|
and connection.transport == PhysicalTransport.LE
|
|
5828
|
-
and encryption == hci.HCI_Encryption_Change_Event.E0_OR_AES_CCM
|
|
6157
|
+
and encryption == hci.HCI_Encryption_Change_Event.Enabled.E0_OR_AES_CCM
|
|
5829
6158
|
):
|
|
5830
6159
|
connection.authenticated = True
|
|
5831
6160
|
connection.sc = True
|
|
5832
|
-
connection.emit(
|
|
6161
|
+
connection.emit(connection.EVENT_CONNECTION_ENCRYPTION_CHANGE)
|
|
5833
6162
|
|
|
5834
6163
|
@host_event_handler
|
|
5835
6164
|
@with_connection_from_handle
|
|
@@ -5839,7 +6168,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5839
6168
|
f'{connection.peer_address} as {connection.role_name}, '
|
|
5840
6169
|
f'error={error}'
|
|
5841
6170
|
)
|
|
5842
|
-
connection.emit(
|
|
6171
|
+
connection.emit(connection.EVENT_CONNECTION_ENCRYPTION_FAILURE, error)
|
|
5843
6172
|
|
|
5844
6173
|
@host_event_handler
|
|
5845
6174
|
@with_connection_from_handle
|
|
@@ -5848,18 +6177,24 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5848
6177
|
f'*** Connection Key Refresh: [0x{connection.handle:04X}] '
|
|
5849
6178
|
f'{connection.peer_address} as {connection.role_name}'
|
|
5850
6179
|
)
|
|
5851
|
-
connection.emit(
|
|
6180
|
+
connection.emit(connection.EVENT_CONNECTION_ENCRYPTION_KEY_REFRESH)
|
|
5852
6181
|
|
|
5853
6182
|
@host_event_handler
|
|
5854
6183
|
@with_connection_from_handle
|
|
5855
|
-
def on_connection_parameters_update(
|
|
6184
|
+
def on_connection_parameters_update(
|
|
6185
|
+
self, connection: Connection, connection_parameters: core.ConnectionParameters
|
|
6186
|
+
):
|
|
5856
6187
|
logger.debug(
|
|
5857
6188
|
f'*** Connection Parameters Update: [0x{connection.handle:04X}] '
|
|
5858
6189
|
f'{connection.peer_address} as {connection.role_name}, '
|
|
5859
6190
|
f'{connection_parameters}'
|
|
5860
6191
|
)
|
|
5861
|
-
connection.parameters =
|
|
5862
|
-
|
|
6192
|
+
connection.parameters = Connection.Parameters(
|
|
6193
|
+
connection_parameters.connection_interval * 1.25,
|
|
6194
|
+
connection_parameters.peripheral_latency,
|
|
6195
|
+
connection_parameters.supervision_timeout * 10.0,
|
|
6196
|
+
)
|
|
6197
|
+
connection.emit(connection.EVENT_CONNECTION_PARAMETERS_UPDATE)
|
|
5863
6198
|
|
|
5864
6199
|
@host_event_handler
|
|
5865
6200
|
@with_connection_from_handle
|
|
@@ -5869,7 +6204,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5869
6204
|
f'{connection.peer_address} as {connection.role_name}, '
|
|
5870
6205
|
f'error={error}'
|
|
5871
6206
|
)
|
|
5872
|
-
connection.emit(
|
|
6207
|
+
connection.emit(connection.EVENT_CONNECTION_PARAMETERS_UPDATE_FAILURE, error)
|
|
5873
6208
|
|
|
5874
6209
|
@host_event_handler
|
|
5875
6210
|
@with_connection_from_handle
|
|
@@ -5879,7 +6214,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5879
6214
|
f'{connection.peer_address} as {connection.role_name}, '
|
|
5880
6215
|
f'{phy}'
|
|
5881
6216
|
)
|
|
5882
|
-
connection.emit(
|
|
6217
|
+
connection.emit(connection.EVENT_CONNECTION_PHY_UPDATE, phy)
|
|
5883
6218
|
|
|
5884
6219
|
@host_event_handler
|
|
5885
6220
|
@with_connection_from_handle
|
|
@@ -5889,7 +6224,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5889
6224
|
f'{connection.peer_address} as {connection.role_name}, '
|
|
5890
6225
|
f'error={error}'
|
|
5891
6226
|
)
|
|
5892
|
-
connection.emit(
|
|
6227
|
+
connection.emit(connection.EVENT_CONNECTION_PHY_UPDATE_FAILURE, error)
|
|
5893
6228
|
|
|
5894
6229
|
@host_event_handler
|
|
5895
6230
|
@with_connection_from_handle
|
|
@@ -5900,7 +6235,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5900
6235
|
f'{att_mtu}'
|
|
5901
6236
|
)
|
|
5902
6237
|
connection.att_mtu = att_mtu
|
|
5903
|
-
connection.emit(
|
|
6238
|
+
connection.emit(connection.EVENT_CONNECTION_ATT_MTU_UPDATE)
|
|
5904
6239
|
|
|
5905
6240
|
@host_event_handler
|
|
5906
6241
|
@with_connection_from_handle
|
|
@@ -5917,7 +6252,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5917
6252
|
max_rx_octets,
|
|
5918
6253
|
max_rx_time,
|
|
5919
6254
|
)
|
|
5920
|
-
connection.emit(
|
|
6255
|
+
connection.emit(connection.EVENT_CONNECTION_DATA_LENGTH_CHANGE)
|
|
5921
6256
|
|
|
5922
6257
|
@host_event_handler
|
|
5923
6258
|
def on_cs_remote_supported_capabilities(
|
|
@@ -5927,7 +6262,9 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5927
6262
|
return
|
|
5928
6263
|
|
|
5929
6264
|
if event.status != hci.HCI_SUCCESS:
|
|
5930
|
-
connection.emit(
|
|
6265
|
+
connection.emit(
|
|
6266
|
+
connection.EVENT_CHANNEL_SOUNDING_CAPABILITIES_FAILURE, event.status
|
|
6267
|
+
)
|
|
5931
6268
|
return
|
|
5932
6269
|
|
|
5933
6270
|
capabilities = ChannelSoundingCapabilities(
|
|
@@ -5952,7 +6289,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5952
6289
|
t_sw_time_supported=event.t_sw_time_supported,
|
|
5953
6290
|
tx_snr_capability=event.tx_snr_capability,
|
|
5954
6291
|
)
|
|
5955
|
-
connection.emit(
|
|
6292
|
+
connection.emit(connection.EVENT_CHANNEL_SOUNDING_CAPABILITIES, capabilities)
|
|
5956
6293
|
|
|
5957
6294
|
@host_event_handler
|
|
5958
6295
|
def on_cs_config(self, event: hci.HCI_LE_CS_Config_Complete_Event):
|
|
@@ -5960,7 +6297,9 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5960
6297
|
return
|
|
5961
6298
|
|
|
5962
6299
|
if event.status != hci.HCI_SUCCESS:
|
|
5963
|
-
connection.emit(
|
|
6300
|
+
connection.emit(
|
|
6301
|
+
connection.EVENT_CHANNEL_SOUNDING_CONFIG_FAILURE, event.status
|
|
6302
|
+
)
|
|
5964
6303
|
return
|
|
5965
6304
|
if event.action == hci.HCI_LE_CS_Config_Complete_Event.Action.CREATED:
|
|
5966
6305
|
config = ChannelSoundingConfig(
|
|
@@ -5986,11 +6325,13 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5986
6325
|
t_pm_time=event.t_pm_time,
|
|
5987
6326
|
)
|
|
5988
6327
|
connection.cs_configs[event.config_id] = config
|
|
5989
|
-
connection.emit(
|
|
6328
|
+
connection.emit(connection.EVENT_CHANNEL_SOUNDING_CONFIG, config)
|
|
5990
6329
|
elif event.action == hci.HCI_LE_CS_Config_Complete_Event.Action.REMOVED:
|
|
5991
6330
|
try:
|
|
5992
6331
|
config = connection.cs_configs.pop(event.config_id)
|
|
5993
|
-
connection.emit(
|
|
6332
|
+
connection.emit(
|
|
6333
|
+
connection.EVENT_CHANNEL_SOUNDING_CONFIG_REMOVED, config.config_id
|
|
6334
|
+
)
|
|
5994
6335
|
except KeyError:
|
|
5995
6336
|
logger.error('Removing unknown config %d', event.config_id)
|
|
5996
6337
|
|
|
@@ -6000,7 +6341,9 @@ class Device(utils.CompositeEventEmitter):
|
|
|
6000
6341
|
return
|
|
6001
6342
|
|
|
6002
6343
|
if event.status != hci.HCI_SUCCESS:
|
|
6003
|
-
connection.emit(
|
|
6344
|
+
connection.emit(
|
|
6345
|
+
connection.EVENT_CHANNEL_SOUNDING_PROCEDURE_FAILURE, event.status
|
|
6346
|
+
)
|
|
6004
6347
|
return
|
|
6005
6348
|
|
|
6006
6349
|
procedure = ChannelSoundingProcedure(
|
|
@@ -6017,37 +6360,37 @@ class Device(utils.CompositeEventEmitter):
|
|
|
6017
6360
|
max_procedure_len=event.max_procedure_len,
|
|
6018
6361
|
)
|
|
6019
6362
|
connection.cs_procedures[procedure.config_id] = procedure
|
|
6020
|
-
connection.emit(
|
|
6363
|
+
connection.emit(connection.EVENT_CHANNEL_SOUNDING_PROCEDURE, procedure)
|
|
6021
6364
|
|
|
6022
6365
|
# [Classic only]
|
|
6023
6366
|
@host_event_handler
|
|
6024
6367
|
@with_connection_from_address
|
|
6025
6368
|
def on_role_change(self, connection, new_role):
|
|
6026
6369
|
connection.role = new_role
|
|
6027
|
-
connection.emit(
|
|
6370
|
+
connection.emit(connection.EVENT_ROLE_CHANGE, new_role)
|
|
6028
6371
|
|
|
6029
6372
|
# [Classic only]
|
|
6030
6373
|
@host_event_handler
|
|
6031
6374
|
@try_with_connection_from_address
|
|
6032
6375
|
def on_role_change_failure(self, connection, address, error):
|
|
6033
6376
|
if connection:
|
|
6034
|
-
connection.emit(
|
|
6035
|
-
self.emit(
|
|
6377
|
+
connection.emit(connection.EVENT_ROLE_CHANGE_FAILURE, error)
|
|
6378
|
+
self.emit(self.EVENT_ROLE_CHANGE_FAILURE, address, error)
|
|
6036
6379
|
|
|
6037
6380
|
# [Classic only]
|
|
6038
6381
|
@host_event_handler
|
|
6039
6382
|
@with_connection_from_address
|
|
6040
6383
|
def on_classic_pairing(self, connection: Connection) -> None:
|
|
6041
|
-
connection.emit(
|
|
6384
|
+
connection.emit(connection.EVENT_CLASSIC_PAIRING)
|
|
6042
6385
|
|
|
6043
6386
|
# [Classic only]
|
|
6044
6387
|
@host_event_handler
|
|
6045
6388
|
@with_connection_from_address
|
|
6046
6389
|
def on_classic_pairing_failure(self, connection: Connection, status) -> None:
|
|
6047
|
-
connection.emit(
|
|
6390
|
+
connection.emit(connection.EVENT_CLASSIC_PAIRING_FAILURE, status)
|
|
6048
6391
|
|
|
6049
6392
|
def on_pairing_start(self, connection: Connection) -> None:
|
|
6050
|
-
connection.emit(
|
|
6393
|
+
connection.emit(connection.EVENT_PAIRING_START)
|
|
6051
6394
|
|
|
6052
6395
|
def on_pairing(
|
|
6053
6396
|
self,
|
|
@@ -6061,10 +6404,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
6061
6404
|
connection.peer_address = identity_address
|
|
6062
6405
|
connection.sc = sc
|
|
6063
6406
|
connection.authenticated = True
|
|
6064
|
-
connection.emit(
|
|
6407
|
+
connection.emit(connection.EVENT_PAIRING, keys)
|
|
6065
6408
|
|
|
6066
6409
|
def on_pairing_failure(self, connection: Connection, reason: int) -> None:
|
|
6067
|
-
connection.emit(
|
|
6410
|
+
connection.emit(connection.EVENT_PAIRING_FAILURE, reason)
|
|
6068
6411
|
|
|
6069
6412
|
@with_connection_from_handle
|
|
6070
6413
|
def on_gatt_pdu(self, connection, pdu):
|