bumble 0.0.179__py3-none-any.whl → 0.0.181__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
bumble/profiles/cap.py ADDED
@@ -0,0 +1,52 @@
1
+ # Copyright 2021-2023 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
+ # -----------------------------------------------------------------------------
17
+ # Imports
18
+ # -----------------------------------------------------------------------------
19
+ from __future__ import annotations
20
+
21
+ from bumble import gatt
22
+ from bumble import gatt_client
23
+ from bumble.profiles import csip
24
+
25
+
26
+ # -----------------------------------------------------------------------------
27
+ # Server
28
+ # -----------------------------------------------------------------------------
29
+ class CommonAudioServiceService(gatt.TemplateService):
30
+ UUID = gatt.GATT_COMMON_AUDIO_SERVICE
31
+
32
+ def __init__(
33
+ self,
34
+ coordinated_set_identification_service: csip.CoordinatedSetIdentificationService,
35
+ ) -> None:
36
+ self.coordinated_set_identification_service = (
37
+ coordinated_set_identification_service
38
+ )
39
+ super().__init__(
40
+ characteristics=[],
41
+ included_services=[coordinated_set_identification_service],
42
+ )
43
+
44
+
45
+ # -----------------------------------------------------------------------------
46
+ # Client
47
+ # -----------------------------------------------------------------------------
48
+ class CommonAudioServiceServiceProxy(gatt_client.ProfileServiceProxy):
49
+ SERVICE_CLASS = CommonAudioServiceService
50
+
51
+ def __init__(self, service_proxy: gatt_client.ServiceProxy) -> None:
52
+ self.service_proxy = service_proxy
@@ -0,0 +1,205 @@
1
+ # Copyright 2021-2023 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
+ # -----------------------------------------------------------------------------
17
+ # Imports
18
+ # -----------------------------------------------------------------------------
19
+ from __future__ import annotations
20
+ import enum
21
+ import struct
22
+ from typing import Optional
23
+
24
+ from bumble import core
25
+ from bumble import crypto
26
+ from bumble import device
27
+ from bumble import gatt
28
+ from bumble import gatt_client
29
+
30
+
31
+ # -----------------------------------------------------------------------------
32
+ # Constants
33
+ # -----------------------------------------------------------------------------
34
+ class SirkType(enum.IntEnum):
35
+ '''Coordinated Set Identification Service - 5.1 Set Identity Resolving Key.'''
36
+
37
+ ENCRYPTED = 0x00
38
+ PLAINTEXT = 0x01
39
+
40
+
41
+ class MemberLock(enum.IntEnum):
42
+ '''Coordinated Set Identification Service - 5.3 Set Member Lock.'''
43
+
44
+ UNLOCKED = 0x01
45
+ LOCKED = 0x02
46
+
47
+
48
+ # -----------------------------------------------------------------------------
49
+ # Crypto Toolbox
50
+ # -----------------------------------------------------------------------------
51
+ def s1(m: bytes) -> bytes:
52
+ '''
53
+ Coordinated Set Identification Service - 4.3 s1 SALT generation function.
54
+ '''
55
+ return crypto.aes_cmac(m[::-1], bytes(16))[::-1]
56
+
57
+
58
+ def k1(n: bytes, salt: bytes, p: bytes) -> bytes:
59
+ '''
60
+ Coordinated Set Identification Service - 4.4 k1 derivation function.
61
+ '''
62
+ t = crypto.aes_cmac(n[::-1], salt[::-1])
63
+ return crypto.aes_cmac(p[::-1], t)[::-1]
64
+
65
+
66
+ def sef(k: bytes, r: bytes) -> bytes:
67
+ '''
68
+ Coordinated Set Identification Service - 4.5 SIRK encryption function sef.
69
+ '''
70
+ return crypto.xor(k1(k, s1(b'SIRKenc'[::-1]), b'csis'[::-1]), r)
71
+
72
+
73
+ def sih(k: bytes, r: bytes) -> bytes:
74
+ '''
75
+ Coordinated Set Identification Service - 4.7 Resolvable Set Identifier hash function sih.
76
+ '''
77
+ return crypto.e(k, r + bytes(13))[:3]
78
+
79
+
80
+ def generate_rsi(sirk: bytes) -> bytes:
81
+ '''
82
+ Coordinated Set Identification Service - 4.8 Resolvable Set Identifier generation operation.
83
+ '''
84
+ prand = crypto.generate_prand()
85
+ return sih(sirk, prand) + prand
86
+
87
+
88
+ # -----------------------------------------------------------------------------
89
+ # Server
90
+ # -----------------------------------------------------------------------------
91
+ class CoordinatedSetIdentificationService(gatt.TemplateService):
92
+ UUID = gatt.GATT_COORDINATED_SET_IDENTIFICATION_SERVICE
93
+
94
+ set_identity_resolving_key: bytes
95
+ set_identity_resolving_key_characteristic: gatt.Characteristic
96
+ coordinated_set_size_characteristic: Optional[gatt.Characteristic] = None
97
+ set_member_lock_characteristic: Optional[gatt.Characteristic] = None
98
+ set_member_rank_characteristic: Optional[gatt.Characteristic] = None
99
+
100
+ def __init__(
101
+ self,
102
+ set_identity_resolving_key: bytes,
103
+ set_identity_resolving_key_type: SirkType,
104
+ coordinated_set_size: Optional[int] = None,
105
+ set_member_lock: Optional[MemberLock] = None,
106
+ set_member_rank: Optional[int] = None,
107
+ ) -> None:
108
+ characteristics = []
109
+
110
+ self.set_identity_resolving_key = set_identity_resolving_key
111
+ self.set_identity_resolving_key_type = set_identity_resolving_key_type
112
+ self.set_identity_resolving_key_characteristic = gatt.Characteristic(
113
+ uuid=gatt.GATT_SET_IDENTITY_RESOLVING_KEY_CHARACTERISTIC,
114
+ properties=gatt.Characteristic.Properties.READ
115
+ | gatt.Characteristic.Properties.NOTIFY,
116
+ permissions=gatt.Characteristic.Permissions.READABLE,
117
+ value=gatt.CharacteristicValue(read=self.on_sirk_read),
118
+ )
119
+ characteristics.append(self.set_identity_resolving_key_characteristic)
120
+
121
+ if coordinated_set_size is not None:
122
+ self.coordinated_set_size_characteristic = gatt.Characteristic(
123
+ uuid=gatt.GATT_COORDINATED_SET_SIZE_CHARACTERISTIC,
124
+ properties=gatt.Characteristic.Properties.READ
125
+ | gatt.Characteristic.Properties.NOTIFY,
126
+ permissions=gatt.Characteristic.Permissions.READABLE,
127
+ value=struct.pack('B', coordinated_set_size),
128
+ )
129
+ characteristics.append(self.coordinated_set_size_characteristic)
130
+
131
+ if set_member_lock is not None:
132
+ self.set_member_lock_characteristic = gatt.Characteristic(
133
+ uuid=gatt.GATT_SET_MEMBER_LOCK_CHARACTERISTIC,
134
+ properties=gatt.Characteristic.Properties.READ
135
+ | gatt.Characteristic.Properties.NOTIFY
136
+ | gatt.Characteristic.Properties.WRITE,
137
+ permissions=gatt.Characteristic.Permissions.READABLE
138
+ | gatt.Characteristic.Permissions.WRITEABLE,
139
+ value=struct.pack('B', set_member_lock),
140
+ )
141
+ characteristics.append(self.set_member_lock_characteristic)
142
+
143
+ if set_member_rank is not None:
144
+ self.set_member_rank_characteristic = gatt.Characteristic(
145
+ uuid=gatt.GATT_SET_MEMBER_RANK_CHARACTERISTIC,
146
+ properties=gatt.Characteristic.Properties.READ
147
+ | gatt.Characteristic.Properties.NOTIFY,
148
+ permissions=gatt.Characteristic.Permissions.READABLE,
149
+ value=struct.pack('B', set_member_rank),
150
+ )
151
+ characteristics.append(self.set_member_rank_characteristic)
152
+
153
+ super().__init__(characteristics)
154
+
155
+ def on_sirk_read(self, _connection: Optional[device.Connection]) -> bytes:
156
+ if self.set_identity_resolving_key_type == SirkType.PLAINTEXT:
157
+ return bytes([SirkType.PLAINTEXT]) + self.set_identity_resolving_key
158
+ else:
159
+ raise NotImplementedError('TODO: Pending async Characteristic read.')
160
+
161
+ def get_advertising_data(self) -> bytes:
162
+ return bytes(
163
+ core.AdvertisingData(
164
+ [
165
+ (
166
+ core.AdvertisingData.RESOLVABLE_SET_IDENTIFIER,
167
+ generate_rsi(self.set_identity_resolving_key),
168
+ ),
169
+ ]
170
+ )
171
+ )
172
+
173
+
174
+ # -----------------------------------------------------------------------------
175
+ # Client
176
+ # -----------------------------------------------------------------------------
177
+ class CoordinatedSetIdentificationProxy(gatt_client.ProfileServiceProxy):
178
+ SERVICE_CLASS = CoordinatedSetIdentificationService
179
+
180
+ set_identity_resolving_key: gatt_client.CharacteristicProxy
181
+ coordinated_set_size: Optional[gatt_client.CharacteristicProxy] = None
182
+ set_member_lock: Optional[gatt_client.CharacteristicProxy] = None
183
+ set_member_rank: Optional[gatt_client.CharacteristicProxy] = None
184
+
185
+ def __init__(self, service_proxy: gatt_client.ServiceProxy) -> None:
186
+ self.service_proxy = service_proxy
187
+
188
+ self.set_identity_resolving_key = service_proxy.get_characteristics_by_uuid(
189
+ gatt.GATT_SET_IDENTITY_RESOLVING_KEY_CHARACTERISTIC
190
+ )[0]
191
+
192
+ if characteristics := service_proxy.get_characteristics_by_uuid(
193
+ gatt.GATT_COORDINATED_SET_SIZE_CHARACTERISTIC
194
+ ):
195
+ self.coordinated_set_size = characteristics[0]
196
+
197
+ if characteristics := service_proxy.get_characteristics_by_uuid(
198
+ gatt.GATT_SET_MEMBER_LOCK_CHARACTERISTIC
199
+ ):
200
+ self.set_member_lock = characteristics[0]
201
+
202
+ if characteristics := service_proxy.get_characteristics_by_uuid(
203
+ gatt.GATT_SET_MEMBER_RANK_CHARACTERISTIC
204
+ ):
205
+ self.set_member_rank = characteristics[0]
bumble/rfcomm.py CHANGED
@@ -118,8 +118,8 @@ CRC_TABLE = bytes([
118
118
  0XBA, 0X2B, 0X59, 0XC8, 0XBD, 0X2C, 0X5E, 0XCF
119
119
  ])
120
120
 
121
- RFCOMM_DEFAULT_INITIAL_RX_CREDITS = 7
122
- RFCOMM_DEFAULT_PREFERRED_MTU = 1280
121
+ RFCOMM_DEFAULT_WINDOW_SIZE = 16
122
+ RFCOMM_DEFAULT_MAX_FRAME_SIZE = 2000
123
123
 
124
124
  RFCOMM_DYNAMIC_CHANNEL_NUMBER_START = 1
125
125
  RFCOMM_DYNAMIC_CHANNEL_NUMBER_END = 30
@@ -438,14 +438,16 @@ class DLC(EventEmitter):
438
438
  multiplexer: Multiplexer,
439
439
  dlci: int,
440
440
  max_frame_size: int,
441
- initial_tx_credits: int,
441
+ window_size: int,
442
442
  ) -> None:
443
443
  super().__init__()
444
444
  self.multiplexer = multiplexer
445
445
  self.dlci = dlci
446
- self.rx_credits = RFCOMM_DEFAULT_INITIAL_RX_CREDITS
447
- self.rx_threshold = self.rx_credits // 2
448
- self.tx_credits = initial_tx_credits
446
+ self.max_frame_size = max_frame_size
447
+ self.window_size = window_size
448
+ self.rx_credits = window_size
449
+ self.rx_threshold = window_size // 2
450
+ self.tx_credits = window_size
449
451
  self.tx_buffer = b''
450
452
  self.state = DLC.State.INIT
451
453
  self.role = multiplexer.role
@@ -537,11 +539,11 @@ class DLC(EventEmitter):
537
539
  if len(data) and self.sink:
538
540
  self.sink(data) # pylint: disable=not-callable
539
541
 
540
- # Update the credits
541
- if self.rx_credits > 0:
542
- self.rx_credits -= 1
543
- else:
544
- logger.warning(color('!!! received frame with no rx credits', 'red'))
542
+ # Update the credits
543
+ if self.rx_credits > 0:
544
+ self.rx_credits -= 1
545
+ else:
546
+ logger.warning(color('!!! received frame with no rx credits', 'red'))
545
547
 
546
548
  # Check if there's anything to send (including credits)
547
549
  self.process_tx()
@@ -580,9 +582,9 @@ class DLC(EventEmitter):
580
582
  cl=0xE0,
581
583
  priority=7,
582
584
  ack_timer=0,
583
- max_frame_size=RFCOMM_DEFAULT_PREFERRED_MTU,
585
+ max_frame_size=self.max_frame_size,
584
586
  max_retransmissions=0,
585
- window_size=RFCOMM_DEFAULT_INITIAL_RX_CREDITS,
587
+ window_size=self.window_size,
586
588
  )
587
589
  mcc = RFCOMM_Frame.make_mcc(mcc_type=RFCOMM_MCC_PN_TYPE, c_r=0, data=bytes(pn))
588
590
  logger.debug(f'>>> PN Response: {pn}')
@@ -591,7 +593,7 @@ class DLC(EventEmitter):
591
593
 
592
594
  def rx_credits_needed(self) -> int:
593
595
  if self.rx_credits <= self.rx_threshold:
594
- return RFCOMM_DEFAULT_INITIAL_RX_CREDITS - self.rx_credits
596
+ return self.window_size - self.rx_credits
595
597
 
596
598
  return 0
597
599
 
@@ -843,7 +845,12 @@ class Multiplexer(EventEmitter):
843
845
  )
844
846
  await self.disconnection_result
845
847
 
846
- async def open_dlc(self, channel: int) -> DLC:
848
+ async def open_dlc(
849
+ self,
850
+ channel: int,
851
+ max_frame_size: int = RFCOMM_DEFAULT_MAX_FRAME_SIZE,
852
+ window_size: int = RFCOMM_DEFAULT_WINDOW_SIZE,
853
+ ) -> DLC:
847
854
  if self.state != Multiplexer.State.CONNECTED:
848
855
  if self.state == Multiplexer.State.OPENING:
849
856
  raise InvalidStateError('open already in progress')
@@ -855,9 +862,9 @@ class Multiplexer(EventEmitter):
855
862
  cl=0xF0,
856
863
  priority=7,
857
864
  ack_timer=0,
858
- max_frame_size=RFCOMM_DEFAULT_PREFERRED_MTU,
865
+ max_frame_size=max_frame_size,
859
866
  max_retransmissions=0,
860
- window_size=RFCOMM_DEFAULT_INITIAL_RX_CREDITS,
867
+ window_size=window_size,
861
868
  )
862
869
  mcc = RFCOMM_Frame.make_mcc(mcc_type=RFCOMM_MCC_PN_TYPE, c_r=1, data=bytes(pn))
863
870
  logger.debug(f'>>> Sending MCC: {pn}')
bumble/smp.py CHANGED
@@ -1090,7 +1090,7 @@ class Session:
1090
1090
  # We can now encrypt the connection with the short term key, so that we can
1091
1091
  # distribute the long term and/or other keys over an encrypted connection
1092
1092
  self.manager.device.host.send_command_sync(
1093
- HCI_LE_Enable_Encryption_Command( # type: ignore[call-arg]
1093
+ HCI_LE_Enable_Encryption_Command(
1094
1094
  connection_handle=self.connection.handle,
1095
1095
  random_number=bytes(8),
1096
1096
  encrypted_diversifier=0,
@@ -18,6 +18,7 @@
18
18
  from contextlib import asynccontextmanager
19
19
  import logging
20
20
  import os
21
+ from typing import Optional
21
22
 
22
23
  from .common import Transport, AsyncPipeSink, SnoopingTransport
23
24
  from ..snoop import create_snooper
@@ -52,8 +53,16 @@ def _wrap_transport(transport: Transport) -> Transport:
52
53
  async def open_transport(name: str) -> Transport:
53
54
  """
54
55
  Open a transport by name.
55
- The name must be <type>:<parameters>
56
- Where <parameters> depend on the type (and may be empty for some types).
56
+ The name must be <type>:<metadata><parameters>
57
+ Where <parameters> depend on the type (and may be empty for some types), and
58
+ <metadata> is either omitted, or a ,-separated list of <key>=<value> pairs,
59
+ enclosed in [].
60
+ If there are not metadata or parameter, the : after the <type> may be omitted.
61
+ Examples:
62
+ * usb:0
63
+ * usb:[driver=rtk]0
64
+ * android-netsim
65
+
57
66
  The supported types are:
58
67
  * serial
59
68
  * udp
@@ -71,87 +80,106 @@ async def open_transport(name: str) -> Transport:
71
80
  * android-netsim
72
81
  """
73
82
 
74
- return _wrap_transport(await _open_transport(name))
83
+ scheme, *tail = name.split(':', 1)
84
+ spec = tail[0] if tail else None
85
+ if spec:
86
+ # Metadata may precede the spec
87
+ if spec.startswith('['):
88
+ metadata_str, *tail = spec[1:].split(']')
89
+ spec = tail[0] if tail else None
90
+ metadata = dict([entry.split('=') for entry in metadata_str.split(',')])
91
+ else:
92
+ metadata = None
93
+
94
+ transport = await _open_transport(scheme, spec)
95
+ if metadata:
96
+ transport.source.metadata = { # type: ignore[attr-defined]
97
+ **metadata,
98
+ **getattr(transport.source, 'metadata', {}),
99
+ }
100
+ # pylint: disable=line-too-long
101
+ logger.debug(f'HCI metadata: {transport.source.metadata}') # type: ignore[attr-defined]
102
+
103
+ return _wrap_transport(transport)
75
104
 
76
105
 
77
106
  # -----------------------------------------------------------------------------
78
- async def _open_transport(name: str) -> Transport:
107
+ async def _open_transport(scheme: str, spec: Optional[str]) -> Transport:
79
108
  # pylint: disable=import-outside-toplevel
80
109
  # pylint: disable=too-many-return-statements
81
110
 
82
- scheme, *spec = name.split(':', 1)
83
111
  if scheme == 'serial' and spec:
84
112
  from .serial import open_serial_transport
85
113
 
86
- return await open_serial_transport(spec[0])
114
+ return await open_serial_transport(spec)
87
115
 
88
116
  if scheme == 'udp' and spec:
89
117
  from .udp import open_udp_transport
90
118
 
91
- return await open_udp_transport(spec[0])
119
+ return await open_udp_transport(spec)
92
120
 
93
121
  if scheme == 'tcp-client' and spec:
94
122
  from .tcp_client import open_tcp_client_transport
95
123
 
96
- return await open_tcp_client_transport(spec[0])
124
+ return await open_tcp_client_transport(spec)
97
125
 
98
126
  if scheme == 'tcp-server' and spec:
99
127
  from .tcp_server import open_tcp_server_transport
100
128
 
101
- return await open_tcp_server_transport(spec[0])
129
+ return await open_tcp_server_transport(spec)
102
130
 
103
131
  if scheme == 'ws-client' and spec:
104
132
  from .ws_client import open_ws_client_transport
105
133
 
106
- return await open_ws_client_transport(spec[0])
134
+ return await open_ws_client_transport(spec)
107
135
 
108
136
  if scheme == 'ws-server' and spec:
109
137
  from .ws_server import open_ws_server_transport
110
138
 
111
- return await open_ws_server_transport(spec[0])
139
+ return await open_ws_server_transport(spec)
112
140
 
113
141
  if scheme == 'pty':
114
142
  from .pty import open_pty_transport
115
143
 
116
- return await open_pty_transport(spec[0] if spec else None)
144
+ return await open_pty_transport(spec)
117
145
 
118
146
  if scheme == 'file':
119
147
  from .file import open_file_transport
120
148
 
121
149
  assert spec is not None
122
- return await open_file_transport(spec[0])
150
+ return await open_file_transport(spec)
123
151
 
124
152
  if scheme == 'vhci':
125
153
  from .vhci import open_vhci_transport
126
154
 
127
- return await open_vhci_transport(spec[0] if spec else None)
155
+ return await open_vhci_transport(spec)
128
156
 
129
157
  if scheme == 'hci-socket':
130
158
  from .hci_socket import open_hci_socket_transport
131
159
 
132
- return await open_hci_socket_transport(spec[0] if spec else None)
160
+ return await open_hci_socket_transport(spec)
133
161
 
134
162
  if scheme == 'usb':
135
163
  from .usb import open_usb_transport
136
164
 
137
- assert spec is not None
138
- return await open_usb_transport(spec[0])
165
+ assert spec
166
+ return await open_usb_transport(spec)
139
167
 
140
168
  if scheme == 'pyusb':
141
169
  from .pyusb import open_pyusb_transport
142
170
 
143
- assert spec is not None
144
- return await open_pyusb_transport(spec[0])
171
+ assert spec
172
+ return await open_pyusb_transport(spec)
145
173
 
146
174
  if scheme == 'android-emulator':
147
175
  from .android_emulator import open_android_emulator_transport
148
176
 
149
- return await open_android_emulator_transport(spec[0] if spec else None)
177
+ return await open_android_emulator_transport(spec)
150
178
 
151
179
  if scheme == 'android-netsim':
152
180
  from .android_netsim import open_android_netsim_transport
153
181
 
154
- return await open_android_netsim_transport(spec[0] if spec else None)
182
+ return await open_android_netsim_transport(spec)
155
183
 
156
184
  raise ValueError('unknown transport scheme')
157
185
 
@@ -69,7 +69,7 @@ async def open_android_emulator_transport(spec: Optional[str]) -> Transport:
69
69
  mode = 'host'
70
70
  server_host = 'localhost'
71
71
  server_port = '8554'
72
- if spec is not None:
72
+ if spec:
73
73
  params = spec.split(',')
74
74
  for param in params:
75
75
  if param.startswith('mode='):
@@ -21,7 +21,7 @@ import struct
21
21
  import asyncio
22
22
  import logging
23
23
  import io
24
- from typing import ContextManager, Tuple, Optional, Protocol, Dict
24
+ from typing import Any, ContextManager, Tuple, Optional, Protocol, Dict
25
25
 
26
26
  from bumble import hci
27
27
  from bumble.colors import color
@@ -42,6 +42,7 @@ HCI_PACKET_INFO: Dict[int, Tuple[int, int, str]] = {
42
42
  hci.HCI_ACL_DATA_PACKET: (2, 2, 'H'),
43
43
  hci.HCI_SYNCHRONOUS_DATA_PACKET: (1, 2, 'B'),
44
44
  hci.HCI_EVENT_PACKET: (1, 1, 'B'),
45
+ hci.HCI_ISO_DATA_PACKET: (2, 2, 'H'),
45
46
  }
46
47
 
47
48
 
@@ -150,7 +151,7 @@ class PacketParser:
150
151
  try:
151
152
  self.sink.on_packet(bytes(self.packet))
152
153
  except Exception as error:
153
- logger.warning(
154
+ logger.exception(
154
155
  color(f'!!! Exception in on_packet: {error}', 'red')
155
156
  )
156
157
  self.reset()
@@ -59,10 +59,7 @@ async def open_hci_socket_transport(spec: Optional[str]) -> Transport:
59
59
  ) from error
60
60
 
61
61
  # Compute the adapter index
62
- if spec is None:
63
- adapter_index = 0
64
- else:
65
- adapter_index = int(spec)
62
+ adapter_index = int(spec) if spec else 0
66
63
 
67
64
  # Bind the socket
68
65
  # NOTE: since Python doesn't support binding with the required address format (yet),
bumble/transport/usb.py CHANGED
@@ -108,7 +108,7 @@ async def open_usb_transport(spec: str) -> Transport:
108
108
  USB_DEVICE_PROTOCOL_BLUETOOTH_PRIMARY_CONTROLLER,
109
109
  )
110
110
 
111
- READ_SIZE = 1024
111
+ READ_SIZE = 4096
112
112
 
113
113
  class UsbPacketSink:
114
114
  def __init__(self, device, acl_out):
bumble/utils.py CHANGED
@@ -280,17 +280,14 @@ class AsyncRunner:
280
280
  def wrapper(*args, **kwargs):
281
281
  coroutine = func(*args, **kwargs)
282
282
  if queue is None:
283
- # Create a task to run the coroutine
283
+ # Spawn the coroutine as a task
284
284
  async def run():
285
285
  try:
286
286
  await coroutine
287
287
  except Exception:
288
- logger.warning(
289
- f'{color("!!! Exception in wrapper:", "red")} '
290
- f'{traceback.format_exc()}'
291
- )
288
+ logger.exception(color("!!! Exception in wrapper:", "red"))
292
289
 
293
- asyncio.create_task(run())
290
+ AsyncRunner.spawn(run())
294
291
  else:
295
292
  # Queue the coroutine to be awaited by the work queue
296
293
  queue.enqueue(coroutine)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bumble
3
- Version: 0.0.179
3
+ Version: 0.0.181
4
4
  Summary: Bluetooth Stack for Apps, Emulation, Test and Experimentation
5
5
  Home-page: https://github.com/google/bumble
6
6
  Author: Google