bumble 0.0.220__py3-none-any.whl → 0.0.221__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.
Files changed (102) hide show
  1. bumble/_version.py +2 -2
  2. bumble/a2dp.py +5 -5
  3. bumble/apps/auracast.py +746 -473
  4. bumble/apps/bench.py +4 -5
  5. bumble/apps/console.py +5 -10
  6. bumble/apps/controller_info.py +12 -7
  7. bumble/apps/controller_loopback.py +1 -2
  8. bumble/apps/device_info.py +2 -3
  9. bumble/apps/gatt_dump.py +0 -1
  10. bumble/apps/lea_unicast/app.py +1 -1
  11. bumble/apps/pair.py +49 -46
  12. bumble/apps/pandora_server.py +2 -2
  13. bumble/apps/player/player.py +10 -12
  14. bumble/apps/rfcomm_bridge.py +10 -11
  15. bumble/apps/scan.py +1 -3
  16. bumble/apps/speaker/speaker.py +3 -4
  17. bumble/at.py +4 -5
  18. bumble/att.py +91 -25
  19. bumble/audio/io.py +5 -3
  20. bumble/avc.py +1 -2
  21. bumble/avctp.py +2 -3
  22. bumble/avdtp.py +53 -57
  23. bumble/avrcp.py +25 -27
  24. bumble/codecs.py +15 -15
  25. bumble/colors.py +7 -8
  26. bumble/controller.py +663 -391
  27. bumble/core.py +41 -49
  28. bumble/crypto/__init__.py +2 -1
  29. bumble/crypto/builtin.py +2 -8
  30. bumble/data_types.py +2 -1
  31. bumble/decoder.py +2 -3
  32. bumble/device.py +171 -142
  33. bumble/drivers/__init__.py +3 -2
  34. bumble/drivers/intel.py +6 -8
  35. bumble/drivers/rtk.py +1 -1
  36. bumble/gatt.py +9 -9
  37. bumble/gatt_adapters.py +6 -6
  38. bumble/gatt_client.py +110 -60
  39. bumble/gatt_server.py +209 -139
  40. bumble/hci.py +87 -74
  41. bumble/helpers.py +5 -5
  42. bumble/hfp.py +27 -26
  43. bumble/hid.py +9 -9
  44. bumble/host.py +44 -50
  45. bumble/keys.py +17 -17
  46. bumble/l2cap.py +1015 -218
  47. bumble/link.py +26 -159
  48. bumble/ll.py +200 -0
  49. bumble/pairing.py +14 -15
  50. bumble/pandora/__init__.py +2 -2
  51. bumble/pandora/device.py +6 -4
  52. bumble/pandora/host.py +19 -10
  53. bumble/pandora/l2cap.py +8 -9
  54. bumble/pandora/security.py +18 -16
  55. bumble/pandora/utils.py +4 -4
  56. bumble/profiles/aics.py +6 -8
  57. bumble/profiles/ams.py +3 -5
  58. bumble/profiles/ancs.py +11 -11
  59. bumble/profiles/ascs.py +5 -5
  60. bumble/profiles/asha.py +10 -9
  61. bumble/profiles/bass.py +9 -3
  62. bumble/profiles/battery_service.py +1 -2
  63. bumble/profiles/csip.py +9 -10
  64. bumble/profiles/device_information_service.py +16 -17
  65. bumble/profiles/gap.py +3 -4
  66. bumble/profiles/gatt_service.py +0 -1
  67. bumble/profiles/gmap.py +12 -13
  68. bumble/profiles/hap.py +3 -3
  69. bumble/profiles/heart_rate_service.py +7 -8
  70. bumble/profiles/le_audio.py +1 -1
  71. bumble/profiles/mcp.py +28 -28
  72. bumble/profiles/pacs.py +13 -17
  73. bumble/profiles/pbp.py +16 -0
  74. bumble/profiles/vcs.py +2 -2
  75. bumble/profiles/vocs.py +6 -9
  76. bumble/rfcomm.py +19 -18
  77. bumble/sdp.py +12 -11
  78. bumble/smp.py +20 -30
  79. bumble/snoop.py +2 -1
  80. bumble/tools/generate_company_id_list.py +1 -1
  81. bumble/tools/intel_util.py +2 -2
  82. bumble/tools/rtk_fw_download.py +1 -1
  83. bumble/tools/rtk_util.py +1 -1
  84. bumble/transport/__init__.py +1 -2
  85. bumble/transport/android_emulator.py +2 -3
  86. bumble/transport/android_netsim.py +49 -40
  87. bumble/transport/common.py +9 -9
  88. bumble/transport/file.py +1 -2
  89. bumble/transport/hci_socket.py +2 -3
  90. bumble/transport/pty.py +3 -5
  91. bumble/transport/pyusb.py +8 -5
  92. bumble/transport/serial.py +1 -2
  93. bumble/transport/vhci.py +1 -2
  94. bumble/transport/ws_server.py +2 -3
  95. bumble/utils.py +22 -9
  96. bumble/vendor/android/hci.py +4 -2
  97. {bumble-0.0.220.dist-info → bumble-0.0.221.dist-info}/METADATA +3 -2
  98. {bumble-0.0.220.dist-info → bumble-0.0.221.dist-info}/RECORD +102 -101
  99. {bumble-0.0.220.dist-info → bumble-0.0.221.dist-info}/WHEEL +0 -0
  100. {bumble-0.0.220.dist-info → bumble-0.0.221.dist-info}/entry_points.txt +0 -0
  101. {bumble-0.0.220.dist-info → bumble-0.0.221.dist-info}/licenses/LICENSE +0 -0
  102. {bumble-0.0.220.dist-info → bumble-0.0.221.dist-info}/top_level.txt +0 -0
bumble/device.py CHANGED
@@ -25,20 +25,15 @@ import itertools
25
25
  import json
26
26
  import logging
27
27
  import secrets
28
- import sys
29
- from collections.abc import Iterable, Sequence
28
+ from collections.abc import Awaitable, Callable, Iterable, Sequence
30
29
  from contextlib import AsyncExitStack, asynccontextmanager, closing
31
30
  from dataclasses import dataclass, field
32
31
  from enum import Enum, IntEnum
33
32
  from typing import (
34
33
  TYPE_CHECKING,
35
34
  Any,
36
- Awaitable,
37
- Callable,
38
35
  ClassVar,
39
- Optional,
40
36
  TypeVar,
41
- Union,
42
37
  cast,
43
38
  overload,
44
39
  )
@@ -46,6 +41,7 @@ from typing import (
46
41
  from typing_extensions import Self
47
42
 
48
43
  from bumble import (
44
+ att,
49
45
  core,
50
46
  data_types,
51
47
  gatt,
@@ -58,7 +54,6 @@ from bumble import (
58
54
  smp,
59
55
  utils,
60
56
  )
61
- from bumble.att import ATT_CID, ATT_DEFAULT_MTU, ATT_PDU
62
57
  from bumble.colors import color
63
58
  from bumble.core import (
64
59
  AdvertisingData,
@@ -176,6 +171,7 @@ class Advertisement:
176
171
  )
177
172
  sid: int = 0
178
173
  data_bytes: bytes = b''
174
+ data: AdvertisingData = field(init=False)
179
175
 
180
176
  # Constants
181
177
  TX_POWER_NOT_AVAILABLE: ClassVar[int] = (
@@ -189,7 +185,7 @@ class Advertisement:
189
185
  self.data = AdvertisingData.from_bytes(self.data_bytes)
190
186
 
191
187
  @classmethod
192
- def from_advertising_report(cls, report) -> Optional[Advertisement]:
188
+ def from_advertising_report(cls, report) -> Advertisement | None:
193
189
  if isinstance(report, hci.HCI_LE_Advertising_Report_Event.Report):
194
190
  return LegacyAdvertisement.from_advertising_report(report)
195
191
 
@@ -265,12 +261,22 @@ class ExtendedAdvertisement(Advertisement):
265
261
 
266
262
  # -----------------------------------------------------------------------------
267
263
  class AdvertisementDataAccumulator:
264
+ last_advertisement: Advertisement | None
265
+ last_data: bytes
266
+ passive: bool
267
+
268
268
  def __init__(self, passive: bool = False):
269
269
  self.passive = passive
270
270
  self.last_advertisement = None
271
271
  self.last_data = b''
272
272
 
273
- def update(self, report):
273
+ def update(
274
+ self,
275
+ report: (
276
+ hci.HCI_LE_Advertising_Report_Event.Report
277
+ | hci.HCI_LE_Extended_Advertising_Report_Event.Report
278
+ ),
279
+ ) -> Advertisement | None:
274
280
  advertisement = Advertisement.from_advertising_report(report)
275
281
  if advertisement is None:
276
282
  return None
@@ -283,10 +289,12 @@ class AdvertisementDataAccumulator:
283
289
  and not self.last_advertisement.is_scan_response
284
290
  ):
285
291
  # This is the response to a scannable advertisement
286
- result = Advertisement.from_advertising_report(report)
287
- result.is_connectable = self.last_advertisement.is_connectable
288
- result.is_scannable = True
289
- result.data = AdvertisingData.from_bytes(self.last_data + report.data)
292
+ if result := Advertisement.from_advertising_report(report):
293
+ result.is_connectable = self.last_advertisement.is_connectable
294
+ result.is_scannable = True
295
+ result.data = AdvertisingData.from_bytes(
296
+ self.last_data + report.data
297
+ )
290
298
  self.last_data = b''
291
299
  else:
292
300
  if (
@@ -473,6 +481,7 @@ class PeriodicAdvertisement:
473
481
  rssi: int = hci.HCI_LE_Periodic_Advertising_Report_Event.RSSI_NOT_AVAILABLE
474
482
  is_truncated: bool = False
475
483
  data_bytes: bytes = b''
484
+ data: AdvertisingData | None = field(init=False)
476
485
 
477
486
  # Constants
478
487
  TX_POWER_NOT_AVAILABLE: ClassVar[int] = (
@@ -593,11 +602,11 @@ class AdvertisingSet(utils.EventEmitter):
593
602
  device: Device
594
603
  advertising_handle: int
595
604
  auto_restart: bool
596
- random_address: Optional[hci.Address]
605
+ random_address: hci.Address | None
597
606
  advertising_parameters: AdvertisingParameters
598
607
  advertising_data: bytes
599
608
  scan_response_data: bytes
600
- periodic_advertising_parameters: Optional[PeriodicAdvertisingParameters]
609
+ periodic_advertising_parameters: PeriodicAdvertisingParameters | None
601
610
  periodic_advertising_data: bytes
602
611
  selected_tx_power: int = 0
603
612
  enabled: bool = False
@@ -844,7 +853,7 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
844
853
  TERMINATED = 6
845
854
 
846
855
  _state: State
847
- sync_handle: Optional[int]
856
+ sync_handle: int | None
848
857
  advertiser_address: hci.Address
849
858
  sid: int
850
859
  skip: int
@@ -857,8 +866,8 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
857
866
 
858
867
  EVENT_STATE_CHANGE = "state_change"
859
868
  EVENT_ESTABLISHMENT = "establishment"
869
+ EVENT_ESTABLISHMENT_ERROR = "establishment_error"
860
870
  EVENT_CANCELLATION = "cancellation"
861
- EVENT_ERROR = "error"
862
871
  EVENT_LOSS = "loss"
863
872
  EVENT_PERIODIC_ADVERTISEMENT = "periodic_advertisement"
864
873
  EVENT_BIGINFO_ADVERTISEMENT = "biginfo_advertisement"
@@ -991,7 +1000,7 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
991
1000
  return
992
1001
 
993
1002
  self.state = self.State.ERROR
994
- self.emit(self.EVENT_ERROR)
1003
+ self.emit(self.EVENT_ESTABLISHMENT_ERROR)
995
1004
 
996
1005
  def on_loss(self):
997
1006
  self.state = self.State.LOST
@@ -1092,6 +1101,7 @@ class Big(utils.EventEmitter):
1092
1101
  max_pdu: int = 0
1093
1102
  iso_interval: float = 0.0 # ISO interval, in milliseconds
1094
1103
  bis_links: Sequence[BisLink] = ()
1104
+ device: Device = field(init=False)
1095
1105
 
1096
1106
  def __post_init__(self) -> None:
1097
1107
  super().__init__()
@@ -1153,6 +1163,7 @@ class BigSync(utils.EventEmitter):
1153
1163
  max_pdu: int = 0
1154
1164
  iso_interval: float = 0.0
1155
1165
  bis_links: Sequence[BisLink] = ()
1166
+ device: Device = field(init=False)
1156
1167
 
1157
1168
  def __post_init__(self) -> None:
1158
1169
  super().__init__()
@@ -1271,7 +1282,7 @@ class Peer:
1271
1282
  return mtu
1272
1283
 
1273
1284
  async def discover_service(
1274
- self, uuid: Union[core.UUID, str]
1285
+ self, uuid: core.UUID | str
1275
1286
  ) -> list[gatt_client.ServiceProxy]:
1276
1287
  return await self.gatt_client.discover_service(uuid)
1277
1288
 
@@ -1287,8 +1298,8 @@ class Peer:
1287
1298
 
1288
1299
  async def discover_characteristics(
1289
1300
  self,
1290
- uuids: Iterable[Union[core.UUID, str]] = (),
1291
- service: Optional[gatt_client.ServiceProxy] = None,
1301
+ uuids: Iterable[core.UUID | str] = (),
1302
+ service: gatt_client.ServiceProxy | None = None,
1292
1303
  ) -> list[gatt_client.CharacteristicProxy[bytes]]:
1293
1304
  return await self.gatt_client.discover_characteristics(
1294
1305
  uuids=uuids, service=service
@@ -1296,9 +1307,9 @@ class Peer:
1296
1307
 
1297
1308
  async def discover_descriptors(
1298
1309
  self,
1299
- characteristic: Optional[gatt_client.CharacteristicProxy] = None,
1300
- start_handle: Optional[int] = None,
1301
- end_handle: Optional[int] = None,
1310
+ characteristic: gatt_client.CharacteristicProxy | None = None,
1311
+ start_handle: int | None = None,
1312
+ end_handle: int | None = None,
1302
1313
  ):
1303
1314
  return await self.gatt_client.discover_descriptors(
1304
1315
  characteristic, start_handle, end_handle
@@ -1319,7 +1330,7 @@ class Peer:
1319
1330
  async def subscribe(
1320
1331
  self,
1321
1332
  characteristic: gatt_client.CharacteristicProxy,
1322
- subscriber: Optional[Callable[[bytes], Any]] = None,
1333
+ subscriber: Callable[[bytes], Any] | None = None,
1323
1334
  prefer_notify: bool = True,
1324
1335
  ) -> None:
1325
1336
  return await self.gatt_client.subscribe(
@@ -1329,25 +1340,23 @@ class Peer:
1329
1340
  async def unsubscribe(
1330
1341
  self,
1331
1342
  characteristic: gatt_client.CharacteristicProxy,
1332
- subscriber: Optional[Callable[[bytes], Any]] = None,
1343
+ subscriber: Callable[[bytes], Any] | None = None,
1333
1344
  ) -> None:
1334
1345
  return await self.gatt_client.unsubscribe(characteristic, subscriber)
1335
1346
 
1336
- async def read_value(
1337
- self, attribute: Union[int, gatt_client.AttributeProxy]
1338
- ) -> bytes:
1347
+ async def read_value(self, attribute: int | gatt_client.AttributeProxy) -> bytes:
1339
1348
  return await self.gatt_client.read_value(attribute)
1340
1349
 
1341
1350
  async def write_value(
1342
1351
  self,
1343
- attribute: Union[int, gatt_client.AttributeProxy],
1352
+ attribute: int | gatt_client.AttributeProxy,
1344
1353
  value: bytes,
1345
1354
  with_response: bool = False,
1346
1355
  ) -> None:
1347
1356
  return await self.gatt_client.write_value(attribute, value, with_response)
1348
1357
 
1349
1358
  async def read_characteristics_by_uuid(
1350
- self, uuid: core.UUID, service: Optional[gatt_client.ServiceProxy] = None
1359
+ self, uuid: core.UUID, service: gatt_client.ServiceProxy | None = None
1351
1360
  ) -> list[bytes]:
1352
1361
  return await self.gatt_client.read_characteristics_by_uuid(uuid, service)
1353
1362
 
@@ -1357,7 +1366,7 @@ class Peer:
1357
1366
  def get_characteristics_by_uuid(
1358
1367
  self,
1359
1368
  uuid: core.UUID,
1360
- service: Optional[Union[gatt_client.ServiceProxy, core.UUID]] = None,
1369
+ service: gatt_client.ServiceProxy | core.UUID | None = None,
1361
1370
  ) -> list[gatt_client.CharacteristicProxy[bytes]]:
1362
1371
  if isinstance(service, core.UUID):
1363
1372
  return list(
@@ -1373,7 +1382,7 @@ class Peer:
1373
1382
 
1374
1383
  def create_service_proxy(
1375
1384
  self, proxy_class: type[_PROXY_CLASS]
1376
- ) -> Optional[_PROXY_CLASS]:
1385
+ ) -> _PROXY_CLASS | None:
1377
1386
  if proxy := proxy_class.from_client(self.gatt_client):
1378
1387
  return cast(_PROXY_CLASS, proxy)
1379
1388
 
@@ -1381,7 +1390,7 @@ class Peer:
1381
1390
 
1382
1391
  async def discover_service_and_create_proxy(
1383
1392
  self, proxy_class: type[_PROXY_CLASS]
1384
- ) -> Optional[_PROXY_CLASS]:
1393
+ ) -> _PROXY_CLASS | None:
1385
1394
  # Discover the first matching service and its characteristics
1386
1395
  services = await self.discover_service(proxy_class.SERVICE_CLASS.UUID)
1387
1396
  if services:
@@ -1390,7 +1399,7 @@ class Peer:
1390
1399
  return self.create_service_proxy(proxy_class)
1391
1400
  return None
1392
1401
 
1393
- async def sustain(self, timeout: Optional[float] = None) -> None:
1402
+ async def sustain(self, timeout: float | None = None) -> None:
1394
1403
  await self.connection.sustain(timeout)
1395
1404
 
1396
1405
  # [Classic only]
@@ -1433,7 +1442,7 @@ class ScoLink(utils.CompositeEventEmitter):
1433
1442
  acl_connection: Connection
1434
1443
  handle: int
1435
1444
  link_type: int
1436
- sink: Optional[Callable[[hci.HCI_SynchronousDataPacket], Any]] = None
1445
+ sink: Callable[[hci.HCI_SynchronousDataPacket], Any] | None = None
1437
1446
 
1438
1447
  EVENT_DISCONNECTION: ClassVar[str] = "disconnection"
1439
1448
  EVENT_DISCONNECTION_FAILURE: ClassVar[str] = "disconnection_failure"
@@ -1616,8 +1625,8 @@ class CisLink(utils.EventEmitter, _IsoLink):
1616
1625
  cis_sync_delay: int = 0 # CIS sync delay, in microseconds
1617
1626
  transport_latency_c_to_p: int = 0 # C->P transport latency, in microseconds
1618
1627
  transport_latency_p_to_c: int = 0 # P->C transport latency, in microseconds
1619
- phy_c_to_p: Optional[hci.Phy] = None
1620
- phy_p_to_c: Optional[hci.Phy] = None
1628
+ phy_c_to_p: hci.Phy | None = None
1629
+ phy_p_to_c: hci.Phy | None = None
1621
1630
  nse: int = 0
1622
1631
  bn_c_to_p: int = 0
1623
1632
  bn_p_to_c: int = 0
@@ -1650,6 +1659,7 @@ class BisLink(_IsoLink):
1650
1659
  handle: int
1651
1660
  big: Big | BigSync
1652
1661
  sink: Callable[[hci.HCI_IsoDataPacket], Any] | None = None
1662
+ device: Device = field(init=False)
1653
1663
 
1654
1664
  def __post_init__(self) -> None:
1655
1665
  super().__init__()
@@ -1705,11 +1715,11 @@ class Connection(utils.CompositeEventEmitter):
1705
1715
  handle: int
1706
1716
  transport: core.PhysicalTransport
1707
1717
  self_address: hci.Address
1708
- self_resolvable_address: Optional[hci.Address]
1718
+ self_resolvable_address: hci.Address | None
1709
1719
  peer_address: hci.Address
1710
- peer_name: Optional[str]
1711
- peer_resolvable_address: Optional[hci.Address]
1712
- peer_le_features: Optional[hci.LeFeatureMask]
1720
+ peer_name: str | None
1721
+ peer_resolvable_address: hci.Address | None
1722
+ peer_le_features: hci.LeFeatureMask | None
1713
1723
  role: hci.Role
1714
1724
  parameters: Parameters
1715
1725
  encryption: int
@@ -1717,8 +1727,8 @@ class Connection(utils.CompositeEventEmitter):
1717
1727
  authenticated: bool
1718
1728
  sc: bool
1719
1729
  gatt_client: gatt_client.Client
1720
- pairing_peer_io_capability: Optional[int]
1721
- pairing_peer_authentication_requirements: Optional[int]
1730
+ pairing_peer_io_capability: int | None
1731
+ pairing_peer_authentication_requirements: int | None
1722
1732
  cs_configs: dict[int, ChannelSoundingConfig] # Config ID to Configuration
1723
1733
  cs_procedures: dict[int, ChannelSoundingProcedure] # Config ID to Procedures
1724
1734
  classic_mode: int = hci.HCI_Mode_Change_Event.Mode.ACTIVE
@@ -1738,7 +1748,6 @@ class Connection(utils.CompositeEventEmitter):
1738
1748
  EVENT_CONNECTION_PARAMETERS_UPDATE_FAILURE = "connection_parameters_update_failure"
1739
1749
  EVENT_CONNECTION_PHY_UPDATE = "connection_phy_update"
1740
1750
  EVENT_CONNECTION_PHY_UPDATE_FAILURE = "connection_phy_update_failure"
1741
- EVENT_CONNECTION_ATT_MTU_UPDATE = "connection_att_mtu_update"
1742
1751
  EVENT_CONNECTION_DATA_LENGTH_CHANGE = "connection_data_length_change"
1743
1752
  EVENT_CHANNEL_SOUNDING_CAPABILITIES_FAILURE = (
1744
1753
  "channel_sounding_capabilities_failure"
@@ -1820,9 +1829,9 @@ class Connection(utils.CompositeEventEmitter):
1820
1829
  handle: int,
1821
1830
  transport: core.PhysicalTransport,
1822
1831
  self_address: hci.Address,
1823
- self_resolvable_address: Optional[hci.Address],
1832
+ self_resolvable_address: hci.Address | None,
1824
1833
  peer_address: hci.Address,
1825
- peer_resolvable_address: Optional[hci.Address],
1834
+ peer_resolvable_address: hci.Address | None,
1826
1835
  role: hci.Role,
1827
1836
  parameters: Parameters,
1828
1837
  ):
@@ -1841,7 +1850,7 @@ class Connection(utils.CompositeEventEmitter):
1841
1850
  self.encryption_key_size = 0
1842
1851
  self.authenticated = False
1843
1852
  self.sc = False
1844
- self.att_mtu = ATT_DEFAULT_MTU
1853
+ self.att_mtu = att.ATT_DEFAULT_MTU
1845
1854
  self.data_length = DEVICE_DEFAULT_DATA_LENGTH
1846
1855
  self.gatt_client = gatt_client.Client(self) # Per-connection client
1847
1856
  self.gatt_server = (
@@ -1885,8 +1894,8 @@ class Connection(utils.CompositeEventEmitter):
1885
1894
  ) -> l2cap.LeCreditBasedChannel: ...
1886
1895
 
1887
1896
  async def create_l2cap_channel(
1888
- self, spec: Union[l2cap.ClassicChannelSpec, l2cap.LeCreditBasedChannelSpec]
1889
- ) -> Union[l2cap.ClassicChannel, l2cap.LeCreditBasedChannel]:
1897
+ self, spec: l2cap.ClassicChannelSpec | l2cap.LeCreditBasedChannelSpec
1898
+ ) -> l2cap.ClassicChannel | l2cap.LeCreditBasedChannel:
1890
1899
  return await self.device.create_l2cap_channel(connection=self, spec=spec)
1891
1900
 
1892
1901
  async def disconnect(
@@ -1910,7 +1919,7 @@ class Connection(utils.CompositeEventEmitter):
1910
1919
  async def switch_role(self, role: hci.Role) -> None:
1911
1920
  return await self.device.switch_role(self, role)
1912
1921
 
1913
- async def sustain(self, timeout: Optional[float] = None) -> None:
1922
+ async def sustain(self, timeout: float | None = None) -> None:
1914
1923
  """Idles the current task waiting for a disconnect or timeout"""
1915
1924
 
1916
1925
  abort = asyncio.get_running_loop().create_future()
@@ -1954,8 +1963,8 @@ class Connection(utils.CompositeEventEmitter):
1954
1963
 
1955
1964
  async def set_phy(
1956
1965
  self,
1957
- tx_phys: Optional[Iterable[hci.Phy]] = None,
1958
- rx_phys: Optional[Iterable[hci.Phy]] = None,
1966
+ tx_phys: Iterable[hci.Phy] | None = None,
1967
+ rx_phys: Iterable[hci.Phy] | None = None,
1959
1968
  phy_options: int = 0,
1960
1969
  ):
1961
1970
  return await self.device.set_connection_phy(self, tx_phys, rx_phys, phy_options)
@@ -1991,6 +2000,15 @@ class Connection(utils.CompositeEventEmitter):
1991
2000
  self.peer_le_features = await self.device.get_remote_le_features(self)
1992
2001
  return self.peer_le_features
1993
2002
 
2003
+ def on_att_mtu_update(self, mtu: int):
2004
+ logger.debug(
2005
+ f'*** Connection ATT MTU Update: [0x{self.handle:04X}] '
2006
+ f'{self.peer_address} as {self.role_name}, '
2007
+ f'{mtu}'
2008
+ )
2009
+ self.att_mtu = mtu
2010
+ self.emit(self.EVENT_CONNECTION_ATT_MTU_UPDATE)
2011
+
1994
2012
  @property
1995
2013
  def data_packet_queue(self) -> DataPacketQueue | None:
1996
2014
  return self.device.host.get_data_packet_queue(self.handle)
@@ -2059,18 +2077,26 @@ class DeviceConfiguration:
2059
2077
  AdvertisingData([data_types.CompleteLocalName(DEVICE_DEFAULT_NAME)])
2060
2078
  )
2061
2079
  irk: bytes = bytes(16) # This really must be changed for any level of security
2062
- keystore: Optional[str] = None
2080
+ keystore: str | None = None
2063
2081
  address_resolution_offload: bool = False
2064
2082
  address_generation_offload: bool = False
2065
2083
  cis_enabled: bool = False
2066
2084
  channel_sounding_enabled: bool = False
2067
- identity_address_type: Optional[int] = None
2085
+ identity_address_type: int | None = None
2068
2086
  io_capability: int = pairing.PairingDelegate.IoCapability.NO_OUTPUT_NO_INPUT
2069
2087
  gap_service_enabled: bool = True
2070
2088
  gatt_service_enabled: bool = True
2089
+ enhanced_retransmission_supported: bool = False
2090
+ l2cap_extended_features: Sequence[int] = (
2091
+ l2cap.L2CAP_Information_Request.ExtendedFeatures.FIXED_CHANNELS,
2092
+ l2cap.L2CAP_Information_Request.ExtendedFeatures.FCS_OPTION,
2093
+ l2cap.L2CAP_Information_Request.ExtendedFeatures.ENHANCED_RETRANSMISSION_MODE,
2094
+ )
2095
+ eatt_enabled: bool = False
2096
+ gatt_services: list[dict[str, Any]] = field(init=False)
2071
2097
 
2072
2098
  def __post_init__(self) -> None:
2073
- self.gatt_services: list[dict[str, Any]] = []
2099
+ self.gatt_services = []
2074
2100
 
2075
2101
  def load_from_dict(self, config: dict[str, Any]) -> None:
2076
2102
  config = copy.deepcopy(config)
@@ -2126,7 +2152,7 @@ class DeviceConfiguration:
2126
2152
  setattr(self, key, value)
2127
2153
 
2128
2154
  def load_from_file(self, filename: str) -> None:
2129
- with open(filename, 'r', encoding='utf-8') as file:
2155
+ with open(filename, encoding='utf-8') as file:
2130
2156
  self.load_from_dict(json.load(file))
2131
2157
 
2132
2158
  @classmethod
@@ -2237,12 +2263,12 @@ class Device(utils.CompositeEventEmitter):
2237
2263
  pending_connections: dict[hci.Address, Connection]
2238
2264
  classic_pending_accepts: dict[
2239
2265
  hci.Address,
2240
- list[asyncio.Future[Union[Connection, tuple[hci.Address, int, int]]]],
2266
+ list[asyncio.Future[Connection | tuple[hci.Address, int, int]]],
2241
2267
  ]
2242
2268
  advertisement_accumulators: dict[hci.Address, AdvertisementDataAccumulator]
2243
2269
  periodic_advertising_syncs: list[PeriodicAdvertisingSync]
2244
2270
  config: DeviceConfiguration
2245
- legacy_advertiser: Optional[LegacyAdvertiser]
2271
+ legacy_advertiser: LegacyAdvertiser | None
2246
2272
  sco_links: dict[int, ScoLink]
2247
2273
  cis_links: dict[int, CisLink]
2248
2274
  bigs: dict[int, Big]
@@ -2250,6 +2276,7 @@ class Device(utils.CompositeEventEmitter):
2250
2276
  big_syncs: dict[int, BigSync]
2251
2277
  _pending_cis: dict[int, tuple[int, int]]
2252
2278
  gatt_service: gatt_service.GenericAttributeProfileService | None = None
2279
+ keystore: KeyStore | None = None
2253
2280
 
2254
2281
  EVENT_ADVERTISEMENT = "advertisement"
2255
2282
  EVENT_PERIODIC_ADVERTISING_SYNC_TRANSFER = "periodic_advertising_sync_transfer"
@@ -2330,13 +2357,17 @@ class Device(utils.CompositeEventEmitter):
2330
2357
 
2331
2358
  def __init__(
2332
2359
  self,
2333
- name: Optional[str] = None,
2334
- address: Optional[hci.Address] = None,
2335
- config: Optional[DeviceConfiguration] = None,
2336
- host: Optional[Host] = None,
2360
+ name: str | None = None,
2361
+ address: hci.Address | None = None,
2362
+ config: DeviceConfiguration | None = None,
2363
+ host: Host | None = None,
2337
2364
  ) -> None:
2338
2365
  super().__init__()
2339
2366
 
2367
+ # Use the initial config or a default
2368
+ config = config or DeviceConfiguration()
2369
+ self.config = config
2370
+
2340
2371
  self._host = None
2341
2372
  self.powered_on = False
2342
2373
  self.auto_restart_inquiry = True
@@ -2344,7 +2375,7 @@ class Device(utils.CompositeEventEmitter):
2344
2375
  self.gatt_server = gatt_server.Server(self)
2345
2376
  self.sdp_server = sdp.Server(self)
2346
2377
  self.l2cap_channel_manager = l2cap.ChannelManager(
2347
- [l2cap.L2CAP_Information_Request.EXTENDED_FEATURE_FIXED_CHANNELS]
2378
+ config.l2cap_extended_features
2348
2379
  )
2349
2380
  self.advertisement_accumulators = {} # Accumulators, by address
2350
2381
  self.periodic_advertising_syncs = []
@@ -2375,10 +2406,6 @@ class Device(utils.CompositeEventEmitter):
2375
2406
  # Own address type cache
2376
2407
  self.connect_own_address_type = None
2377
2408
 
2378
- # Use the initial config or a default
2379
- config = config or DeviceConfiguration()
2380
- self.config = config
2381
-
2382
2409
  self.name = config.name
2383
2410
  self.public_address = hci.Address.ANY
2384
2411
  self.random_address = config.address
@@ -2390,7 +2417,7 @@ class Device(utils.CompositeEventEmitter):
2390
2417
  self.le_simultaneous_enabled = config.le_simultaneous_enabled
2391
2418
  self.le_privacy_enabled = config.le_privacy_enabled
2392
2419
  self.le_rpa_timeout = config.le_rpa_timeout
2393
- self.le_rpa_periodic_update_task: Optional[asyncio.Task] = None
2420
+ self.le_rpa_periodic_update_task: asyncio.Task | None = None
2394
2421
  self.le_subrate_enabled = config.le_subrate_enabled
2395
2422
  self.classic_enabled = config.classic_enabled
2396
2423
  self.cis_enabled = config.cis_enabled
@@ -2414,8 +2441,8 @@ class Device(utils.CompositeEventEmitter):
2414
2441
  # can be initialized from a config object, and for backward compatibility for
2415
2442
  # client code that may set those values directly before calling
2416
2443
  # start_advertising().
2417
- self.legacy_advertising_set: Optional[AdvertisingSet] = None
2418
- self.legacy_advertiser: Optional[LegacyAdvertiser] = None
2444
+ self.legacy_advertising_set: AdvertisingSet | None = None
2445
+ self.legacy_advertiser: LegacyAdvertiser | None = None
2419
2446
  self.advertising_data = config.advertising_data
2420
2447
  self.scan_response_data = config.scan_response_data
2421
2448
  self.advertising_interval_min = config.advertising_interval_min
@@ -2486,7 +2513,10 @@ class Device(utils.CompositeEventEmitter):
2486
2513
  add_gap_service=config.gap_service_enabled,
2487
2514
  add_gatt_service=config.gatt_service_enabled,
2488
2515
  )
2489
- self.l2cap_channel_manager.register_fixed_channel(ATT_CID, self.on_gatt_pdu)
2516
+ self.l2cap_channel_manager.register_fixed_channel(att.ATT_CID, self.on_gatt_pdu)
2517
+
2518
+ if self.config.eatt_enabled:
2519
+ self.gatt_server.register_eatt()
2490
2520
 
2491
2521
  # Forward some events
2492
2522
  utils.setup_event_forwarding(
@@ -2533,7 +2563,7 @@ class Device(utils.CompositeEventEmitter):
2533
2563
  def sdp_service_records(self, service_records):
2534
2564
  self.sdp_server.service_records = service_records
2535
2565
 
2536
- def lookup_connection(self, connection_handle: int) -> Optional[Connection]:
2566
+ def lookup_connection(self, connection_handle: int) -> Connection | None:
2537
2567
  if connection := self.connections.get(connection_handle):
2538
2568
  return connection
2539
2569
 
@@ -2542,9 +2572,9 @@ class Device(utils.CompositeEventEmitter):
2542
2572
  def find_connection_by_bd_addr(
2543
2573
  self,
2544
2574
  bd_addr: hci.Address,
2545
- transport: Optional[int] = None,
2575
+ transport: int | None = None,
2546
2576
  check_address_type: bool = False,
2547
- ) -> Optional[Connection]:
2577
+ ) -> Connection | None:
2548
2578
  for connection in self.connections.values():
2549
2579
  if bytes(connection.peer_address) == bytes(bd_addr):
2550
2580
  if (
@@ -2559,7 +2589,7 @@ class Device(utils.CompositeEventEmitter):
2559
2589
 
2560
2590
  def lookup_periodic_advertising_sync(
2561
2591
  self, sync_handle: int
2562
- ) -> Optional[PeriodicAdvertisingSync]:
2592
+ ) -> PeriodicAdvertisingSync | None:
2563
2593
  return next(
2564
2594
  (
2565
2595
  sync
@@ -2597,8 +2627,8 @@ class Device(utils.CompositeEventEmitter):
2597
2627
  async def create_l2cap_channel(
2598
2628
  self,
2599
2629
  connection: Connection,
2600
- spec: Union[l2cap.ClassicChannelSpec, l2cap.LeCreditBasedChannelSpec],
2601
- ) -> Union[l2cap.ClassicChannel, l2cap.LeCreditBasedChannel]:
2630
+ spec: l2cap.ClassicChannelSpec | l2cap.LeCreditBasedChannelSpec,
2631
+ ) -> l2cap.ClassicChannel | l2cap.LeCreditBasedChannel:
2602
2632
  if isinstance(spec, l2cap.ClassicChannelSpec):
2603
2633
  return await self.l2cap_channel_manager.create_classic_channel(
2604
2634
  connection=connection, spec=spec
@@ -2612,25 +2642,25 @@ class Device(utils.CompositeEventEmitter):
2612
2642
  def create_l2cap_server(
2613
2643
  self,
2614
2644
  spec: l2cap.ClassicChannelSpec,
2615
- handler: Optional[Callable[[l2cap.ClassicChannel], Any]] = None,
2645
+ handler: Callable[[l2cap.ClassicChannel], Any] | None = None,
2616
2646
  ) -> l2cap.ClassicChannelServer: ...
2617
2647
 
2618
2648
  @overload
2619
2649
  def create_l2cap_server(
2620
2650
  self,
2621
2651
  spec: l2cap.LeCreditBasedChannelSpec,
2622
- handler: Optional[Callable[[l2cap.LeCreditBasedChannel], Any]] = None,
2652
+ handler: Callable[[l2cap.LeCreditBasedChannel], Any] | None = None,
2623
2653
  ) -> l2cap.LeCreditBasedChannelServer: ...
2624
2654
 
2625
2655
  def create_l2cap_server(
2626
2656
  self,
2627
- spec: Union[l2cap.ClassicChannelSpec, l2cap.LeCreditBasedChannelSpec],
2628
- handler: Union[
2629
- Callable[[l2cap.ClassicChannel], Any],
2630
- Callable[[l2cap.LeCreditBasedChannel], Any],
2631
- None,
2632
- ] = None,
2633
- ) -> Union[l2cap.ClassicChannelServer, l2cap.LeCreditBasedChannelServer]:
2657
+ spec: l2cap.ClassicChannelSpec | l2cap.LeCreditBasedChannelSpec,
2658
+ handler: (
2659
+ Callable[[l2cap.ClassicChannel], Any]
2660
+ | Callable[[l2cap.LeCreditBasedChannel], Any]
2661
+ | None
2662
+ ) = None,
2663
+ ) -> l2cap.ClassicChannelServer | l2cap.LeCreditBasedChannelServer:
2634
2664
  if isinstance(spec, l2cap.ClassicChannelSpec):
2635
2665
  return self.l2cap_channel_manager.create_classic_server(
2636
2666
  spec=spec,
@@ -2932,13 +2962,13 @@ class Device(utils.CompositeEventEmitter):
2932
2962
  async def start_advertising(
2933
2963
  self,
2934
2964
  advertising_type: AdvertisingType = AdvertisingType.UNDIRECTED_CONNECTABLE_SCANNABLE,
2935
- target: Optional[hci.Address] = None,
2965
+ target: hci.Address | None = None,
2936
2966
  own_address_type: hci.OwnAddressType = hci.OwnAddressType.RANDOM,
2937
2967
  auto_restart: bool = False,
2938
- advertising_data: Optional[bytes] = None,
2939
- scan_response_data: Optional[bytes] = None,
2940
- advertising_interval_min: Optional[float] = None,
2941
- advertising_interval_max: Optional[float] = None,
2968
+ advertising_data: bytes | None = None,
2969
+ scan_response_data: bytes | None = None,
2970
+ advertising_interval_min: float | None = None,
2971
+ advertising_interval_max: float | None = None,
2942
2972
  ) -> None:
2943
2973
  """Start legacy advertising.
2944
2974
 
@@ -3042,11 +3072,11 @@ class Device(utils.CompositeEventEmitter):
3042
3072
 
3043
3073
  async def create_advertising_set(
3044
3074
  self,
3045
- advertising_parameters: Optional[AdvertisingParameters] = None,
3046
- random_address: Optional[hci.Address] = None,
3075
+ advertising_parameters: AdvertisingParameters | None = None,
3076
+ random_address: hci.Address | None = None,
3047
3077
  advertising_data: bytes = b'',
3048
3078
  scan_response_data: bytes = b'',
3049
- periodic_advertising_parameters: Optional[PeriodicAdvertisingParameters] = None,
3079
+ periodic_advertising_parameters: PeriodicAdvertisingParameters | None = None,
3050
3080
  periodic_advertising_data: bytes = b'',
3051
3081
  auto_start: bool = True,
3052
3082
  auto_restart: bool = False,
@@ -3333,7 +3363,13 @@ class Device(utils.CompositeEventEmitter):
3333
3363
  return self.scanning
3334
3364
 
3335
3365
  @host_event_handler
3336
- def on_advertising_report(self, report):
3366
+ def on_advertising_report(
3367
+ self,
3368
+ report: (
3369
+ hci.HCI_LE_Advertising_Report_Event.Report
3370
+ | hci.HCI_LE_Extended_Advertising_Report_Event.Report
3371
+ ),
3372
+ ) -> None:
3337
3373
  if not (accumulator := self.advertisement_accumulators.get(report.address)):
3338
3374
  accumulator = AdvertisementDataAccumulator(passive=self.scanning_is_passive)
3339
3375
  self.advertisement_accumulators[report.address] = accumulator
@@ -3576,13 +3612,13 @@ class Device(utils.CompositeEventEmitter):
3576
3612
 
3577
3613
  async def connect(
3578
3614
  self,
3579
- peer_address: Union[hci.Address, str],
3615
+ peer_address: hci.Address | str,
3580
3616
  transport: core.PhysicalTransport = PhysicalTransport.LE,
3581
- connection_parameters_preferences: Optional[
3582
- dict[hci.Phy, ConnectionParametersPreferences]
3583
- ] = None,
3617
+ connection_parameters_preferences: (
3618
+ dict[hci.Phy, ConnectionParametersPreferences] | None
3619
+ ) = None,
3584
3620
  own_address_type: hci.OwnAddressType = hci.OwnAddressType.RANDOM,
3585
- timeout: Optional[float] = DEVICE_DEFAULT_CONNECT_TIMEOUT,
3621
+ timeout: float | None = DEVICE_DEFAULT_CONNECT_TIMEOUT,
3586
3622
  always_resolve: bool = False,
3587
3623
  ) -> Connection:
3588
3624
  '''
@@ -3892,9 +3928,9 @@ class Device(utils.CompositeEventEmitter):
3892
3928
 
3893
3929
  async def accept(
3894
3930
  self,
3895
- peer_address: Union[hci.Address, str] = hci.Address.ANY,
3931
+ peer_address: hci.Address | str = hci.Address.ANY,
3896
3932
  role: hci.Role = hci.Role.PERIPHERAL,
3897
- timeout: Optional[float] = DEVICE_DEFAULT_CONNECT_TIMEOUT,
3933
+ timeout: float | None = DEVICE_DEFAULT_CONNECT_TIMEOUT,
3898
3934
  ) -> Connection:
3899
3935
  '''
3900
3936
  Wait and accept any incoming connection or a connection from `peer_address` when
@@ -4018,7 +4054,7 @@ class Device(utils.CompositeEventEmitter):
4018
4054
  self.pending_connections.pop(peer_address, None)
4019
4055
 
4020
4056
  @asynccontextmanager
4021
- async def connect_as_gatt(self, peer_address: Union[hci.Address, str]):
4057
+ async def connect_as_gatt(self, peer_address: hci.Address | str):
4022
4058
  async with AsyncExitStack() as stack:
4023
4059
  connection = await stack.enter_async_context(
4024
4060
  await self.connect(peer_address)
@@ -4065,7 +4101,7 @@ class Device(utils.CompositeEventEmitter):
4065
4101
  )
4066
4102
 
4067
4103
  async def disconnect(
4068
- self, connection: Union[Connection, ScoLink, CisLink], reason: int
4104
+ self, connection: Connection | ScoLink | CisLink, reason: int
4069
4105
  ) -> None:
4070
4106
  # Create a future so that we can wait for the disconnection's result
4071
4107
  pending_disconnection = asyncio.get_running_loop().create_future()
@@ -4204,8 +4240,8 @@ class Device(utils.CompositeEventEmitter):
4204
4240
  async def set_connection_phy(
4205
4241
  self,
4206
4242
  connection: Connection,
4207
- tx_phys: Optional[Iterable[hci.Phy]] = None,
4208
- rx_phys: Optional[Iterable[hci.Phy]] = None,
4243
+ tx_phys: Iterable[hci.Phy] | None = None,
4244
+ rx_phys: Iterable[hci.Phy] | None = None,
4209
4245
  phy_options: int = 0,
4210
4246
  ):
4211
4247
  if not self.host.supports_command(hci.HCI_LE_SET_PHY_COMMAND):
@@ -4229,8 +4265,8 @@ class Device(utils.CompositeEventEmitter):
4229
4265
 
4230
4266
  async def set_default_phy(
4231
4267
  self,
4232
- tx_phys: Optional[Iterable[hci.Phy]] = None,
4233
- rx_phys: Optional[Iterable[hci.Phy]] = None,
4268
+ tx_phys: Iterable[hci.Phy] | None = None,
4269
+ rx_phys: Iterable[hci.Phy] | None = None,
4234
4270
  ):
4235
4271
  all_phys_bits = (1 if tx_phys is None else 0) | (
4236
4272
  (1 if rx_phys is None else 0) << 1
@@ -4284,7 +4320,7 @@ class Device(utils.CompositeEventEmitter):
4284
4320
  if local_name == name:
4285
4321
  peer_address.set_result(address)
4286
4322
 
4287
- listener: Optional[Callable[..., None]] = None
4323
+ listener: Callable[..., None] | None = None
4288
4324
  was_scanning = self.scanning
4289
4325
  was_discovering = self.discovering
4290
4326
  try:
@@ -4398,7 +4434,7 @@ class Device(utils.CompositeEventEmitter):
4398
4434
 
4399
4435
  async def get_long_term_key(
4400
4436
  self, connection_handle: int, rand: bytes, ediv: int
4401
- ) -> Optional[bytes]:
4437
+ ) -> bytes | None:
4402
4438
  if (connection := self.lookup_connection(connection_handle)) is None:
4403
4439
  return None
4404
4440
 
@@ -4422,7 +4458,7 @@ class Device(utils.CompositeEventEmitter):
4422
4458
  return keys.ltk_peripheral.value
4423
4459
  return None
4424
4460
 
4425
- async def get_link_key(self, address: hci.Address) -> Optional[bytes]:
4461
+ async def get_link_key(self, address: hci.Address) -> bytes | None:
4426
4462
  if self.keystore is None:
4427
4463
  return None
4428
4464
 
@@ -4498,8 +4534,8 @@ class Device(utils.CompositeEventEmitter):
4498
4534
  ediv = 0
4499
4535
  elif keys.ltk_central is not None:
4500
4536
  ltk = keys.ltk_central.value
4501
- rand = keys.ltk_central.rand
4502
- ediv = keys.ltk_central.ediv
4537
+ rand = keys.ltk_central.rand or b''
4538
+ ediv = keys.ltk_central.ediv or 0
4503
4539
  else:
4504
4540
  raise InvalidOperationError('no LTK found for peer')
4505
4541
 
@@ -4560,7 +4596,7 @@ class Device(utils.CompositeEventEmitter):
4560
4596
  await connection.cancel_on_disconnection(pending_role_change)
4561
4597
 
4562
4598
  # [Classic only]
4563
- async def request_remote_name(self, remote: Union[hci.Address, Connection]) -> str:
4599
+ async def request_remote_name(self, remote: hci.Address | Connection) -> str:
4564
4600
  # Set up event handlers
4565
4601
  pending_name: asyncio.Future[str] = asyncio.get_running_loop().create_future()
4566
4602
 
@@ -5123,14 +5159,18 @@ class Device(utils.CompositeEventEmitter):
5123
5159
  if add_gap_service:
5124
5160
  self.gatt_server.add_service(GenericAccessService(self.name))
5125
5161
  if add_gatt_service:
5126
- self.gatt_service = gatt_service.GenericAttributeProfileService()
5162
+ self.gatt_service = gatt_service.GenericAttributeProfileService(
5163
+ gatt.ServerSupportedFeatures.EATT_SUPPORTED
5164
+ if self.config.eatt_enabled
5165
+ else None
5166
+ )
5127
5167
  self.gatt_server.add_service(self.gatt_service)
5128
5168
 
5129
5169
  async def notify_subscriber(
5130
5170
  self,
5131
5171
  connection: Connection,
5132
5172
  attribute: Attribute,
5133
- value: Optional[Any] = None,
5173
+ value: Any | None = None,
5134
5174
  force: bool = False,
5135
5175
  ) -> None:
5136
5176
  """
@@ -5149,7 +5189,7 @@ class Device(utils.CompositeEventEmitter):
5149
5189
  await self.gatt_server.notify_subscriber(connection, attribute, value, force)
5150
5190
 
5151
5191
  async def notify_subscribers(
5152
- self, attribute: Attribute, value=None, force=False
5192
+ self, attribute: Attribute, value: Any | None = None, force: bool = False
5153
5193
  ) -> None:
5154
5194
  """
5155
5195
  Send a notification to all the subscribers of an attribute.
@@ -5169,7 +5209,7 @@ class Device(utils.CompositeEventEmitter):
5169
5209
  self,
5170
5210
  connection: Connection,
5171
5211
  attribute: Attribute,
5172
- value: Optional[Any] = None,
5212
+ value: Any | None = None,
5173
5213
  force: bool = False,
5174
5214
  ):
5175
5215
  """
@@ -5190,7 +5230,7 @@ class Device(utils.CompositeEventEmitter):
5190
5230
  await self.gatt_server.indicate_subscriber(connection, attribute, value, force)
5191
5231
 
5192
5232
  async def indicate_subscribers(
5193
- self, attribute: Attribute, value: Optional[Any] = None, force: bool = False
5233
+ self, attribute: Attribute, value: Any | None = None, force: bool = False
5194
5234
  ):
5195
5235
  """
5196
5236
  Send an indication to all the subscribers of an attribute.
@@ -5225,8 +5265,7 @@ class Device(utils.CompositeEventEmitter):
5225
5265
 
5226
5266
  if status != hci.HCI_SUCCESS:
5227
5267
  logger.debug(
5228
- f'advertising set {advertising_handle} '
5229
- f'terminated with status {status}'
5268
+ f'advertising set {advertising_handle} terminated with status {status}'
5230
5269
  )
5231
5270
  return
5232
5271
 
@@ -5415,8 +5454,8 @@ class Device(utils.CompositeEventEmitter):
5415
5454
  self,
5416
5455
  connection_handle: int,
5417
5456
  peer_address: hci.Address,
5418
- self_resolvable_address: Optional[hci.Address],
5419
- peer_resolvable_address: Optional[hci.Address],
5457
+ self_resolvable_address: hci.Address | None,
5458
+ peer_resolvable_address: hci.Address | None,
5420
5459
  role: hci.Role,
5421
5460
  connection_interval: int,
5422
5461
  peripheral_latency: int,
@@ -5451,7 +5490,7 @@ class Device(utils.CompositeEventEmitter):
5451
5490
  peer_address = resolved_address
5452
5491
 
5453
5492
  self_address = None
5454
- own_address_type: Optional[hci.OwnAddressType] = None
5493
+ own_address_type: hci.OwnAddressType | None = None
5455
5494
  if role == hci.Role.CENTRAL:
5456
5495
  own_address_type = self.connect_own_address_type
5457
5496
  assert own_address_type is not None
@@ -5605,7 +5644,8 @@ class Device(utils.CompositeEventEmitter):
5605
5644
 
5606
5645
  self.host.send_command_sync(
5607
5646
  hci.HCI_Accept_Connection_Request_Command(
5608
- bd_addr=bd_addr, role=0x01 # Remain the peripheral
5647
+ bd_addr=bd_addr,
5648
+ role=0x01, # Remain the peripheral
5609
5649
  )
5610
5650
  )
5611
5651
 
@@ -5915,7 +5955,7 @@ class Device(utils.CompositeEventEmitter):
5915
5955
  @host_event_handler
5916
5956
  @try_with_connection_from_address
5917
5957
  def on_remote_name(
5918
- self, connection: Optional[Connection], address: hci.Address, remote_name: bytes
5958
+ self, connection: Connection | None, address: hci.Address, remote_name: bytes
5919
5959
  ):
5920
5960
  # Try to decode the name
5921
5961
  try:
@@ -5934,7 +5974,7 @@ class Device(utils.CompositeEventEmitter):
5934
5974
  @host_event_handler
5935
5975
  @try_with_connection_from_address
5936
5976
  def on_remote_name_failure(
5937
- self, connection: Optional[Connection], address: hci.Address, error: int
5977
+ self, connection: Connection | None, address: hci.Address, error: int
5938
5978
  ):
5939
5979
  if connection:
5940
5980
  connection.emit(connection.EVENT_REMOTE_NAME_FAILURE, error)
@@ -6223,17 +6263,6 @@ class Device(utils.CompositeEventEmitter):
6223
6263
  )
6224
6264
  connection.emit(connection.EVENT_LE_SUBRATE_CHANGE)
6225
6265
 
6226
- @host_event_handler
6227
- @with_connection_from_handle
6228
- def on_connection_att_mtu_update(self, connection: Connection, att_mtu: int):
6229
- logger.debug(
6230
- f'*** Connection ATT MTU Update: [0x{connection.handle:04X}] '
6231
- f'{connection.peer_address} as {connection.role_name}, '
6232
- f'{att_mtu}'
6233
- )
6234
- connection.att_mtu = att_mtu
6235
- connection.emit(connection.EVENT_CONNECTION_ATT_MTU_UPDATE)
6236
-
6237
6266
  @host_event_handler
6238
6267
  @with_connection_from_handle
6239
6268
  def on_connection_data_length_change(
@@ -6379,7 +6408,7 @@ class Device(utils.CompositeEventEmitter):
6379
6408
  @host_event_handler
6380
6409
  @try_with_connection_from_address
6381
6410
  def on_role_change_failure(
6382
- self, connection: Optional[Connection], address: hci.Address, error: int
6411
+ self, connection: Connection | None, address: hci.Address, error: int
6383
6412
  ):
6384
6413
  if connection:
6385
6414
  connection.emit(connection.EVENT_ROLE_CHANGE_FAILURE, error)
@@ -6403,7 +6432,7 @@ class Device(utils.CompositeEventEmitter):
6403
6432
  def on_pairing(
6404
6433
  self,
6405
6434
  connection: Connection,
6406
- identity_address: Optional[hci.Address],
6435
+ identity_address: hci.Address | None,
6407
6436
  keys: PairingKeys,
6408
6437
  sc: bool,
6409
6438
  ) -> None:
@@ -6420,7 +6449,7 @@ class Device(utils.CompositeEventEmitter):
6420
6449
  @with_connection_from_handle
6421
6450
  def on_gatt_pdu(self, connection: Connection, pdu: bytes):
6422
6451
  # Parse the L2CAP payload into an ATT PDU object
6423
- att_pdu = ATT_PDU.from_bytes(pdu)
6452
+ att_pdu = att.ATT_PDU.from_bytes(pdu)
6424
6453
 
6425
6454
  # Conveniently, even-numbered op codes are client->server and
6426
6455
  # odd-numbered ones are server->client