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/link.py CHANGED
@@ -19,9 +19,12 @@ import asyncio
19
19
  # Imports
20
20
  # -----------------------------------------------------------------------------
21
21
  import logging
22
- from typing import Optional
22
+ from typing import TYPE_CHECKING
23
23
 
24
- from bumble import controller, core, hci, lmp
24
+ from bumble import core, hci, ll, lmp
25
+
26
+ if TYPE_CHECKING:
27
+ from bumble import controller
25
28
 
26
29
  # -----------------------------------------------------------------------------
27
30
  # Logging
@@ -29,11 +32,6 @@ from bumble import controller, core, hci, lmp
29
32
  logger = logging.getLogger(__name__)
30
33
 
31
34
 
32
- # -----------------------------------------------------------------------------
33
- # Utils
34
- # -----------------------------------------------------------------------------
35
-
36
-
37
35
  # -----------------------------------------------------------------------------
38
36
  # TODO: add more support for various LL exchanges
39
37
  # (see Vol 6, Part B - 2.4 DATA CHANNEL PDU)
@@ -47,7 +45,6 @@ class LocalLink:
47
45
 
48
46
  def __init__(self):
49
47
  self.controllers = set()
50
- self.pending_connection = None
51
48
  self.pending_classic_connection = None
52
49
 
53
50
  ############################################################
@@ -61,23 +58,21 @@ class LocalLink:
61
58
  def remove_controller(self, controller: controller.Controller):
62
59
  self.controllers.remove(controller)
63
60
 
64
- def find_controller(self, address: hci.Address) -> controller.Controller | None:
61
+ def find_le_controller(self, address: hci.Address) -> controller.Controller | None:
65
62
  for controller in self.controllers:
66
- if controller.random_address == address:
67
- return controller
63
+ for connection in controller.le_connections.values():
64
+ if connection.self_address == address:
65
+ return controller
68
66
  return None
69
67
 
70
68
  def find_classic_controller(
71
69
  self, address: hci.Address
72
- ) -> Optional[controller.Controller]:
70
+ ) -> controller.Controller | None:
73
71
  for controller in self.controllers:
74
72
  if controller.public_address == address:
75
73
  return controller
76
74
  return None
77
75
 
78
- def get_pending_connection(self):
79
- return self.pending_connection
80
-
81
76
  ############################################################
82
77
  # LE handlers
83
78
  ############################################################
@@ -85,12 +80,6 @@ class LocalLink:
85
80
  def on_address_changed(self, controller):
86
81
  pass
87
82
 
88
- def send_advertising_data(self, sender_address: hci.Address, data: bytes):
89
- # Send the advertising data to all controllers, except the sender
90
- for controller in self.controllers:
91
- if controller.random_address != sender_address:
92
- controller.on_link_advertising_data(sender_address, data)
93
-
94
83
  def send_acl_data(
95
84
  self,
96
85
  sender_controller: controller.Controller,
@@ -100,7 +89,7 @@ class LocalLink:
100
89
  ):
101
90
  # Send the data to the first controller with a matching address
102
91
  if transport == core.PhysicalTransport.LE:
103
- destination_controller = self.find_controller(destination_address)
92
+ destination_controller = self.find_le_controller(destination_address)
104
93
  source_address = sender_controller.random_address
105
94
  elif transport == core.PhysicalTransport.BR_EDR:
106
95
  destination_controller = self.find_classic_controller(destination_address)
@@ -115,152 +104,30 @@ class LocalLink:
115
104
  )
116
105
  )
117
106
 
118
- def on_connection_complete(self) -> None:
119
- # Check that we expect this call
120
- if not self.pending_connection:
121
- logger.warning('on_connection_complete with no pending connection')
122
- return
123
-
124
- central_address, le_create_connection_command = self.pending_connection
125
- self.pending_connection = None
126
-
127
- # Find the controller that initiated the connection
128
- if not (central_controller := self.find_controller(central_address)):
129
- logger.warning('!!! Initiating controller not found')
130
- return
131
-
132
- # Connect to the first controller with a matching address
133
- if peripheral_controller := self.find_controller(
134
- le_create_connection_command.peer_address
135
- ):
136
- central_controller.on_link_peripheral_connection_complete(
137
- le_create_connection_command, hci.HCI_SUCCESS
138
- )
139
- peripheral_controller.on_link_central_connected(central_address)
140
- return
141
-
142
- # No peripheral found
143
- central_controller.on_link_peripheral_connection_complete(
144
- le_create_connection_command, hci.HCI_CONNECTION_ACCEPT_TIMEOUT_ERROR
145
- )
146
-
147
- def connect(
107
+ def send_advertising_pdu(
148
108
  self,
149
- central_address: hci.Address,
150
- le_create_connection_command: hci.HCI_LE_Create_Connection_Command,
109
+ sender_controller: controller.Controller,
110
+ packet: ll.AdvertisingPdu,
151
111
  ):
152
- logger.debug(
153
- f'$$$ CONNECTION {central_address} -> '
154
- f'{le_create_connection_command.peer_address}'
155
- )
156
- self.pending_connection = (central_address, le_create_connection_command)
157
- asyncio.get_running_loop().call_soon(self.on_connection_complete)
112
+ loop = asyncio.get_running_loop()
113
+ for c in self.controllers:
114
+ if c != sender_controller:
115
+ loop.call_soon(c.on_ll_advertising_pdu, packet)
158
116
 
159
- def on_disconnection_complete(
117
+ def send_ll_control_pdu(
160
118
  self,
161
- initiating_address: hci.Address,
162
- target_address: hci.Address,
163
- disconnect_command: hci.HCI_Disconnect_Command,
119
+ sender_address: hci.Address,
120
+ receiver_address: hci.Address,
121
+ packet: ll.ControlPdu,
164
122
  ):
165
- # Find the controller that initiated the disconnection
166
- if not (initiating_controller := self.find_controller(initiating_address)):
167
- logger.warning('!!! Initiating controller not found')
168
- return
169
-
170
- # Disconnect from the first controller with a matching address
171
- if target_controller := self.find_controller(target_address):
172
- target_controller.on_link_disconnected(
173
- initiating_address, disconnect_command.reason
123
+ if not (receiver_controller := self.find_le_controller(receiver_address)):
124
+ raise core.InvalidArgumentError(
125
+ f"Unable to find controller for address {receiver_address}"
174
126
  )
175
-
176
- initiating_controller.on_link_disconnection_complete(
177
- disconnect_command, hci.HCI_SUCCESS
178
- )
179
-
180
- def disconnect(
181
- self,
182
- initiating_address: hci.Address,
183
- target_address: hci.Address,
184
- disconnect_command: hci.HCI_Disconnect_Command,
185
- ):
186
- logger.debug(
187
- f'$$$ DISCONNECTION {initiating_address} -> '
188
- f'{target_address}: reason = {disconnect_command.reason}'
189
- )
190
127
  asyncio.get_running_loop().call_soon(
191
- lambda: self.on_disconnection_complete(
192
- initiating_address, target_address, disconnect_command
193
- )
128
+ lambda: receiver_controller.on_ll_control_pdu(sender_address, packet)
194
129
  )
195
130
 
196
- def on_connection_encrypted(
197
- self,
198
- central_address: hci.Address,
199
- peripheral_address: hci.Address,
200
- rand: bytes,
201
- ediv: int,
202
- ltk: bytes,
203
- ):
204
- logger.debug(f'*** ENCRYPTION {central_address} -> {peripheral_address}')
205
-
206
- if central_controller := self.find_controller(central_address):
207
- central_controller.on_link_encrypted(peripheral_address, rand, ediv, ltk)
208
-
209
- if peripheral_controller := self.find_controller(peripheral_address):
210
- peripheral_controller.on_link_encrypted(central_address, rand, ediv, ltk)
211
-
212
- def create_cis(
213
- self,
214
- central_controller: controller.Controller,
215
- peripheral_address: hci.Address,
216
- cig_id: int,
217
- cis_id: int,
218
- ) -> None:
219
- logger.debug(
220
- f'$$$ CIS Request {central_controller.random_address} -> {peripheral_address}'
221
- )
222
- if peripheral_controller := self.find_controller(peripheral_address):
223
- asyncio.get_running_loop().call_soon(
224
- peripheral_controller.on_link_cis_request,
225
- central_controller.random_address,
226
- cig_id,
227
- cis_id,
228
- )
229
-
230
- def accept_cis(
231
- self,
232
- peripheral_controller: controller.Controller,
233
- central_address: hci.Address,
234
- cig_id: int,
235
- cis_id: int,
236
- ) -> None:
237
- logger.debug(
238
- f'$$$ CIS Accept {peripheral_controller.random_address} -> {central_address}'
239
- )
240
- if central_controller := self.find_controller(central_address):
241
- loop = asyncio.get_running_loop()
242
- loop.call_soon(central_controller.on_link_cis_established, cig_id, cis_id)
243
- loop.call_soon(
244
- peripheral_controller.on_link_cis_established, cig_id, cis_id
245
- )
246
-
247
- def disconnect_cis(
248
- self,
249
- initiator_controller: controller.Controller,
250
- peer_address: hci.Address,
251
- cig_id: int,
252
- cis_id: int,
253
- ) -> None:
254
- logger.debug(
255
- f'$$$ CIS Disconnect {initiator_controller.random_address} -> {peer_address}'
256
- )
257
- if peer_controller := self.find_controller(peer_address):
258
- loop = asyncio.get_running_loop()
259
- loop.call_soon(
260
- initiator_controller.on_link_cis_disconnected, cig_id, cis_id
261
- )
262
- loop.call_soon(peer_controller.on_link_cis_disconnected, cig_id, cis_id)
263
-
264
131
  ############################################################
265
132
  # Classic handlers
266
133
  ############################################################
bumble/ll.py ADDED
@@ -0,0 +1,200 @@
1
+ # Copyright 2021-2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # -----------------------------------------------------------------------------
16
+ # Imports
17
+ # -----------------------------------------------------------------------------
18
+ from __future__ import annotations
19
+
20
+ import dataclasses
21
+ from typing import ClassVar
22
+
23
+ from bumble import hci
24
+
25
+
26
+ # -----------------------------------------------------------------------------
27
+ # Advertising PDU
28
+ # -----------------------------------------------------------------------------
29
+ class AdvertisingPdu:
30
+ """Base Advertising Physical Channel PDU class.
31
+
32
+ See Core Spec 6.0, Volume 6, Part B, 2.3. Advertising physical channel PDU.
33
+
34
+ Currently these messages don't really follow the LL spec, because LL protocol is
35
+ context-aware and we don't have real physical transport.
36
+ """
37
+
38
+
39
+ @dataclasses.dataclass
40
+ class ConnectInd(AdvertisingPdu):
41
+ initiator_address: hci.Address
42
+ advertiser_address: hci.Address
43
+ interval: int
44
+ latency: int
45
+ timeout: int
46
+
47
+
48
+ @dataclasses.dataclass
49
+ class AdvInd(AdvertisingPdu):
50
+ advertiser_address: hci.Address
51
+ data: bytes
52
+
53
+
54
+ @dataclasses.dataclass
55
+ class AdvDirectInd(AdvertisingPdu):
56
+ advertiser_address: hci.Address
57
+ target_address: hci.Address
58
+
59
+
60
+ @dataclasses.dataclass
61
+ class AdvNonConnInd(AdvertisingPdu):
62
+ advertiser_address: hci.Address
63
+ data: bytes
64
+
65
+
66
+ @dataclasses.dataclass
67
+ class AdvExtInd(AdvertisingPdu):
68
+ advertiser_address: hci.Address
69
+ data: bytes
70
+
71
+ target_address: hci.Address | None = None
72
+ adi: int | None = None
73
+ tx_power: int | None = None
74
+
75
+
76
+ # -----------------------------------------------------------------------------
77
+ # LL Control PDU
78
+ # -----------------------------------------------------------------------------
79
+ class ControlPdu:
80
+ """Base LL Control PDU Class.
81
+
82
+ See Core Spec 6.0, Volume 6, Part B, 2.4.2. LL Control PDU.
83
+
84
+ Currently these messages don't really follow the LL spec, because LL protocol is
85
+ context-aware and we don't have real physical transport.
86
+ """
87
+
88
+ class Opcode(hci.SpecableEnum):
89
+ LL_CONNECTION_UPDATE_IND = 0x00
90
+ LL_CHANNEL_MAP_IND = 0x01
91
+ LL_TERMINATE_IND = 0x02
92
+ LL_ENC_REQ = 0x03
93
+ LL_ENC_RSP = 0x04
94
+ LL_START_ENC_REQ = 0x05
95
+ LL_START_ENC_RSP = 0x06
96
+ LL_UNKNOWN_RSP = 0x07
97
+ LL_FEATURE_REQ = 0x08
98
+ LL_FEATURE_RSP = 0x09
99
+ LL_PAUSE_ENC_REQ = 0x0A
100
+ LL_PAUSE_ENC_RSP = 0x0B
101
+ LL_VERSION_IND = 0x0C
102
+ LL_REJECT_IND = 0x0D
103
+ LL_PERIPHERAL_FEATURE_REQ = 0x0E
104
+ LL_CONNECTION_PARAM_REQ = 0x0F
105
+ LL_CONNECTION_PARAM_RSP = 0x10
106
+ LL_REJECT_EXT_IND = 0x11
107
+ LL_PING_REQ = 0x12
108
+ LL_PING_RSP = 0x13
109
+ LL_LENGTH_REQ = 0x14
110
+ LL_LENGTH_RSP = 0x15
111
+ LL_PHY_REQ = 0x16
112
+ LL_PHY_RSP = 0x17
113
+ LL_PHY_UPDATE_IND = 0x18
114
+ LL_MIN_USED_CHANNELS_IND = 0x19
115
+ LL_CTE_REQ = 0x1A
116
+ LL_CTE_RSP = 0x1B
117
+ LL_PERIODIC_SYNC_IND = 0x1C
118
+ LL_CLOCK_ACCURACY_REQ = 0x1D
119
+ LL_CLOCK_ACCURACY_RSP = 0x1E
120
+ LL_CIS_REQ = 0x1F
121
+ LL_CIS_RSP = 0x20
122
+ LL_CIS_IND = 0x21
123
+ LL_CIS_TERMINATE_IND = 0x22
124
+ LL_POWER_CONTROL_REQ = 0x23
125
+ LL_POWER_CONTROL_RSP = 0x24
126
+ LL_POWER_CHANGE_IND = 0x25
127
+ LL_SUBRATE_REQ = 0x26
128
+ LL_SUBRATE_IND = 0x27
129
+ LL_CHANNEL_REPORTING_IND = 0x28
130
+ LL_CHANNEL_STATUS_IND = 0x29
131
+ LL_PERIODIC_SYNC_WR_IND = 0x2A
132
+ LL_FEATURE_EXT_REQ = 0x2B
133
+ LL_FEATURE_EXT_RSP = 0x2C
134
+ LL_CS_SEC_RSP = 0x2D
135
+ LL_CS_CAPABILITIES_REQ = 0x2E
136
+ LL_CS_CAPABILITIES_RSP = 0x2F
137
+ LL_CS_CONFIG_REQ = 0x30
138
+ LL_CS_CONFIG_RSP = 0x31
139
+ LL_CS_REQ = 0x32
140
+ LL_CS_RSP = 0x33
141
+ LL_CS_IND = 0x34
142
+ LL_CS_TERMINATE_REQ = 0x35
143
+ LL_CS_FAE_REQ = 0x36
144
+ LL_CS_FAE_RSP = 0x37
145
+ LL_CS_CHANNEL_MAP_IND = 0x38
146
+ LL_CS_SEC_REQ = 0x39
147
+ LL_CS_TERMINATE_RSP = 0x3A
148
+ LL_FRAME_SPACE_REQ = 0x3B
149
+ LL_FRAME_SPACE_RSP = 0x3C
150
+
151
+ opcode: ClassVar[Opcode]
152
+
153
+
154
+ @dataclasses.dataclass
155
+ class TerminateInd(ControlPdu):
156
+ opcode = ControlPdu.Opcode.LL_TERMINATE_IND
157
+
158
+ error_code: int
159
+
160
+
161
+ @dataclasses.dataclass
162
+ class EncReq(ControlPdu):
163
+ opcode = ControlPdu.Opcode.LL_ENC_REQ
164
+
165
+ rand: bytes
166
+ ediv: int
167
+ ltk: bytes
168
+
169
+
170
+ @dataclasses.dataclass
171
+ class CisReq(ControlPdu):
172
+ opcode = ControlPdu.Opcode.LL_CIS_REQ
173
+
174
+ cig_id: int
175
+ cis_id: int
176
+
177
+
178
+ @dataclasses.dataclass
179
+ class CisRsp(ControlPdu):
180
+ opcode = ControlPdu.Opcode.LL_CIS_REQ
181
+
182
+ cig_id: int
183
+ cis_id: int
184
+
185
+
186
+ @dataclasses.dataclass
187
+ class CisInd(ControlPdu):
188
+ opcode = ControlPdu.Opcode.LL_CIS_REQ
189
+
190
+ cig_id: int
191
+ cis_id: int
192
+
193
+
194
+ @dataclasses.dataclass
195
+ class CisTerminateInd(ControlPdu):
196
+ opcode = ControlPdu.Opcode.LL_CIS_TERMINATE_IND
197
+
198
+ cig_id: int
199
+ cis_id: int
200
+ error_code: int
bumble/pairing.py CHANGED
@@ -20,7 +20,6 @@ from __future__ import annotations
20
20
  import enum
21
21
  import secrets
22
22
  from dataclasses import dataclass
23
- from typing import Optional
24
23
 
25
24
  from bumble import hci
26
25
  from bumble.core import AdvertisingData, LeRole
@@ -45,16 +44,16 @@ from bumble.smp import (
45
44
  class OobData:
46
45
  """OOB data that can be sent from one device to another."""
47
46
 
48
- address: Optional[hci.Address] = None
49
- role: Optional[LeRole] = None
50
- shared_data: Optional[OobSharedData] = None
51
- legacy_context: Optional[OobLegacyContext] = None
47
+ address: hci.Address | None = None
48
+ role: LeRole | None = None
49
+ shared_data: OobSharedData | None = None
50
+ legacy_context: OobLegacyContext | None = None
52
51
 
53
52
  @classmethod
54
53
  def from_ad(cls, ad: AdvertisingData) -> OobData:
55
54
  instance = cls()
56
- shared_data_c: Optional[bytes] = None
57
- shared_data_r: Optional[bytes] = None
55
+ shared_data_c: bytes | None = None
56
+ shared_data_r: bytes | None = None
58
57
  for ad_type, ad_data in ad.ad_structures:
59
58
  if ad_type == AdvertisingData.LE_BLUETOOTH_DEVICE_ADDRESS:
60
59
  instance.address = hci.Address(ad_data)
@@ -181,14 +180,14 @@ class PairingDelegate:
181
180
  """Compare two numbers."""
182
181
  return True
183
182
 
184
- async def get_number(self) -> Optional[int]:
183
+ async def get_number(self) -> int | None:
185
184
  """
186
185
  Return an optional number as an answer to a passkey request.
187
186
  Returning `None` will result in a negative reply.
188
187
  """
189
188
  return 0
190
189
 
191
- async def get_string(self, max_length: int) -> Optional[str]:
190
+ async def get_string(self, max_length: int) -> str | None:
192
191
  """
193
192
  Return a string whose utf-8 encoding is up to max_length bytes.
194
193
  """
@@ -239,18 +238,18 @@ class PairingConfig:
239
238
  class OobConfig:
240
239
  """Config for OOB pairing."""
241
240
 
242
- our_context: Optional[OobContext]
243
- peer_data: Optional[OobSharedData]
244
- legacy_context: Optional[OobLegacyContext]
241
+ our_context: OobContext | None
242
+ peer_data: OobSharedData | None
243
+ legacy_context: OobLegacyContext | None
245
244
 
246
245
  def __init__(
247
246
  self,
248
247
  sc: bool = True,
249
248
  mitm: bool = True,
250
249
  bonding: bool = True,
251
- delegate: Optional[PairingDelegate] = None,
252
- identity_address_type: Optional[AddressType] = None,
253
- oob: Optional[OobConfig] = None,
250
+ delegate: PairingDelegate | None = None,
251
+ identity_address_type: AddressType | None = None,
252
+ oob: OobConfig | None = None,
254
253
  ) -> None:
255
254
  self.sc = sc
256
255
  self.mitm = mitm
@@ -19,7 +19,7 @@ This module implement the Pandora Bluetooth test APIs for the Bumble stack.
19
19
 
20
20
  __version__ = "0.0.1"
21
21
 
22
- from typing import Callable, List, Optional
22
+ from collections.abc import Callable
23
23
 
24
24
  import grpc
25
25
  import grpc.aio
@@ -58,7 +58,7 @@ def register_servicer_hook(
58
58
  async def serve(
59
59
  bumble: PandoraDevice,
60
60
  config: Config = Config(),
61
- grpc_server: Optional[grpc.aio.Server] = None,
61
+ grpc_server: grpc.aio.Server | None = None,
62
62
  port: int = 0,
63
63
  ) -> None:
64
64
  # initialize a gRPC server if not provided.
bumble/pandora/device.py CHANGED
@@ -16,7 +16,7 @@
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- from typing import Any, Optional
19
+ from typing import Any
20
20
 
21
21
  from bumble import transport
22
22
  from bumble.core import (
@@ -54,7 +54,7 @@ class PandoraDevice:
54
54
 
55
55
  # HCI transport name & instance.
56
56
  _hci_name: str
57
- _hci: Optional[transport.Transport] # type: ignore[name-defined]
57
+ _hci: transport.Transport | None # type: ignore[name-defined]
58
58
 
59
59
  def __init__(self, config: dict[str, Any]) -> None:
60
60
  self.config = config
@@ -74,7 +74,9 @@ class PandoraDevice:
74
74
 
75
75
  # open HCI transport & set device host.
76
76
  self._hci = await transport.open_transport(self._hci_name)
77
- self.device.host = Host(controller_source=self._hci.source, controller_sink=self._hci.sink) # type: ignore[no-untyped-call]
77
+ self.device.host = Host(
78
+ controller_source=self._hci.source, controller_sink=self._hci.sink
79
+ ) # type: ignore[no-untyped-call]
78
80
 
79
81
  # power-on.
80
82
  await self.device.power_on()
@@ -96,7 +98,7 @@ class PandoraDevice:
96
98
  await self.close()
97
99
  await self.open()
98
100
 
99
- def info(self) -> Optional[dict[str, str]]:
101
+ def info(self) -> dict[str, str] | None:
100
102
  return {
101
103
  'public_bd_address': str(self.device.public_address),
102
104
  'random_address': str(self.device.random_address),
bumble/pandora/host.py CHANGED
@@ -17,12 +17,15 @@ from __future__ import annotations
17
17
  import asyncio
18
18
  import logging
19
19
  import struct
20
- from typing import AsyncGenerator, Optional, cast
20
+ from collections.abc import AsyncGenerator
21
+ from typing import cast
21
22
 
22
23
  import grpc
23
24
  import grpc.aio
24
- from google.protobuf import any_pb2 # pytype: disable=pyi-error
25
- from google.protobuf import empty_pb2 # pytype: disable=pyi-error
25
+ from google.protobuf import (
26
+ any_pb2, # pytype: disable=pyi-error
27
+ empty_pb2, # pytype: disable=pyi-error
28
+ )
26
29
  from pandora import host_pb2
27
30
  from pandora.host_grpc_aio import HostServicer
28
31
  from pandora.host_pb2 import (
@@ -302,7 +305,9 @@ class HostService(HostServicer):
302
305
  await disconnection_future
303
306
  self.log.debug("Disconnected")
304
307
  finally:
305
- connection.remove_listener(connection.EVENT_DISCONNECTION, on_disconnection) # type: ignore
308
+ connection.remove_listener(
309
+ connection.EVENT_DISCONNECTION, on_disconnection
310
+ ) # type: ignore
306
311
 
307
312
  return empty_pb2.Empty()
308
313
 
@@ -539,7 +544,7 @@ class HostService(HostServicer):
539
544
  await bumble.utils.cancel_on_event(
540
545
  self.device, 'flush', self.device.stop_advertising()
541
546
  )
542
- except:
547
+ except Exception:
543
548
  pass
544
549
 
545
550
  @utils.rpc
@@ -609,7 +614,7 @@ class HostService(HostServicer):
609
614
  await bumble.utils.cancel_on_event(
610
615
  self.device, 'flush', self.device.stop_scanning()
611
616
  )
612
- except:
617
+ except Exception:
613
618
  pass
614
619
 
615
620
  @utils.rpc
@@ -619,7 +624,7 @@ class HostService(HostServicer):
619
624
  self.log.debug('Inquiry')
620
625
 
621
626
  inquiry_queue: asyncio.Queue[
622
- Optional[tuple[Address, int, AdvertisingData, int]]
627
+ tuple[Address, int, AdvertisingData, int] | None
623
628
  ] = asyncio.Queue()
624
629
  complete_handler = self.device.on(
625
630
  self.device.EVENT_INQUIRY_COMPLETE, lambda: inquiry_queue.put_nowait(None)
@@ -644,14 +649,18 @@ class HostService(HostServicer):
644
649
  )
645
650
 
646
651
  finally:
647
- self.device.remove_listener(self.device.EVENT_INQUIRY_COMPLETE, complete_handler) # type: ignore
648
- self.device.remove_listener(self.device.EVENT_INQUIRY_RESULT, result_handler) # type: ignore
652
+ self.device.remove_listener(
653
+ self.device.EVENT_INQUIRY_COMPLETE, complete_handler
654
+ ) # type: ignore
655
+ self.device.remove_listener(
656
+ self.device.EVENT_INQUIRY_RESULT, result_handler
657
+ ) # type: ignore
649
658
  try:
650
659
  self.log.debug('Stop inquiry')
651
660
  await bumble.utils.cancel_on_event(
652
661
  self.device, 'flush', self.device.stop_discovery()
653
662
  )
654
- except:
663
+ except Exception:
655
664
  pass
656
665
 
657
666
  @utils.rpc