bumble 0.0.219__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.
- bumble/_version.py +2 -2
- bumble/a2dp.py +5 -5
- bumble/apps/auracast.py +746 -479
- bumble/apps/bench.py +4 -5
- bumble/apps/console.py +5 -10
- bumble/apps/controller_info.py +12 -7
- bumble/apps/controller_loopback.py +1 -2
- bumble/apps/device_info.py +2 -3
- bumble/apps/gatt_dump.py +0 -1
- bumble/apps/lea_unicast/app.py +1 -1
- bumble/apps/pair.py +49 -46
- bumble/apps/pandora_server.py +2 -2
- bumble/apps/player/player.py +10 -12
- bumble/apps/rfcomm_bridge.py +10 -11
- bumble/apps/scan.py +1 -3
- bumble/apps/speaker/speaker.py +3 -4
- bumble/at.py +4 -5
- bumble/att.py +91 -25
- bumble/audio/io.py +8 -6
- bumble/avc.py +1 -2
- bumble/avctp.py +2 -3
- bumble/avdtp.py +53 -57
- bumble/avrcp.py +25 -27
- bumble/codecs.py +15 -15
- bumble/colors.py +7 -8
- bumble/controller.py +1201 -643
- bumble/core.py +41 -49
- bumble/crypto/__init__.py +2 -1
- bumble/crypto/builtin.py +2 -8
- bumble/data_types.py +2 -1
- bumble/decoder.py +2 -3
- bumble/device.py +278 -325
- bumble/drivers/__init__.py +3 -2
- bumble/drivers/intel.py +6 -8
- bumble/drivers/rtk.py +1 -1
- bumble/gatt.py +9 -9
- bumble/gatt_adapters.py +6 -6
- bumble/gatt_client.py +110 -60
- bumble/gatt_server.py +209 -139
- bumble/hci.py +87 -74
- bumble/helpers.py +5 -5
- bumble/hfp.py +27 -26
- bumble/hid.py +9 -9
- bumble/host.py +44 -50
- bumble/keys.py +17 -17
- bumble/l2cap.py +1015 -218
- bumble/link.py +54 -284
- bumble/ll.py +200 -0
- bumble/lmp.py +324 -0
- bumble/pairing.py +14 -15
- bumble/pandora/__init__.py +2 -2
- bumble/pandora/device.py +6 -4
- bumble/pandora/host.py +19 -10
- bumble/pandora/l2cap.py +8 -9
- bumble/pandora/security.py +18 -16
- bumble/pandora/utils.py +4 -4
- bumble/profiles/aics.py +6 -8
- bumble/profiles/ams.py +3 -5
- bumble/profiles/ancs.py +11 -11
- bumble/profiles/ascs.py +5 -5
- bumble/profiles/asha.py +10 -9
- bumble/profiles/bass.py +9 -3
- bumble/profiles/battery_service.py +1 -2
- bumble/profiles/csip.py +9 -10
- bumble/profiles/device_information_service.py +16 -17
- bumble/profiles/gap.py +3 -4
- bumble/profiles/gatt_service.py +0 -1
- bumble/profiles/gmap.py +12 -13
- bumble/profiles/hap.py +3 -3
- bumble/profiles/heart_rate_service.py +7 -8
- bumble/profiles/le_audio.py +1 -1
- bumble/profiles/mcp.py +28 -28
- bumble/profiles/pacs.py +13 -17
- bumble/profiles/pbp.py +16 -0
- bumble/profiles/vcs.py +2 -2
- bumble/profiles/vocs.py +6 -9
- bumble/rfcomm.py +19 -18
- bumble/sdp.py +12 -11
- bumble/smp.py +20 -30
- bumble/snoop.py +12 -5
- bumble/tools/generate_company_id_list.py +1 -1
- bumble/tools/intel_util.py +2 -2
- bumble/tools/rtk_fw_download.py +1 -1
- bumble/tools/rtk_util.py +1 -1
- bumble/transport/__init__.py +1 -2
- bumble/transport/android_emulator.py +2 -3
- bumble/transport/android_netsim.py +49 -40
- bumble/transport/common.py +9 -9
- bumble/transport/file.py +1 -2
- bumble/transport/hci_socket.py +2 -3
- bumble/transport/pty.py +3 -5
- bumble/transport/pyusb.py +8 -5
- bumble/transport/serial.py +1 -2
- bumble/transport/vhci.py +1 -2
- bumble/transport/ws_server.py +2 -3
- bumble/utils.py +23 -14
- bumble/vendor/android/hci.py +4 -2
- {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/METADATA +4 -3
- bumble-0.0.221.dist-info/RECORD +185 -0
- bumble-0.0.219.dist-info/RECORD +0 -183
- {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/WHEEL +0 -0
- {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/top_level.txt +0 -0
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
# Imports
|
|
17
17
|
# -----------------------------------------------------------------------------
|
|
18
18
|
import logging
|
|
19
|
-
from typing import Optional, Union
|
|
20
19
|
|
|
21
20
|
import grpc.aio
|
|
22
21
|
|
|
@@ -44,7 +43,7 @@ logger = logging.getLogger(__name__)
|
|
|
44
43
|
|
|
45
44
|
|
|
46
45
|
# -----------------------------------------------------------------------------
|
|
47
|
-
async def open_android_emulator_transport(spec:
|
|
46
|
+
async def open_android_emulator_transport(spec: str | None) -> Transport:
|
|
48
47
|
'''
|
|
49
48
|
Open a transport connection to an Android emulator via its gRPC interface.
|
|
50
49
|
The parameter string has this syntax:
|
|
@@ -89,7 +88,7 @@ async def open_android_emulator_transport(spec: Optional[str]) -> Transport:
|
|
|
89
88
|
logger.debug('connecting to gRPC server at %s', server_address)
|
|
90
89
|
channel = grpc.aio.insecure_channel(server_address)
|
|
91
90
|
|
|
92
|
-
service:
|
|
91
|
+
service: EmulatedBluetoothServiceStub | VhciForwardingServiceStub
|
|
93
92
|
if mode == 'host':
|
|
94
93
|
# Connect as a host
|
|
95
94
|
service = EmulatedBluetoothServiceStub(channel)
|
|
@@ -22,7 +22,6 @@ import os
|
|
|
22
22
|
import pathlib
|
|
23
23
|
import platform
|
|
24
24
|
import sys
|
|
25
|
-
from typing import Optional
|
|
26
25
|
|
|
27
26
|
import grpc.aio
|
|
28
27
|
|
|
@@ -66,7 +65,7 @@ DEFAULT_VARIANT = ''
|
|
|
66
65
|
|
|
67
66
|
|
|
68
67
|
# -----------------------------------------------------------------------------
|
|
69
|
-
def get_ini_dir() ->
|
|
68
|
+
def get_ini_dir() -> pathlib.Path | None:
|
|
70
69
|
if sys.platform == 'darwin':
|
|
71
70
|
if tmpdir := os.getenv('TMPDIR', None):
|
|
72
71
|
return pathlib.Path(tmpdir)
|
|
@@ -100,7 +99,7 @@ def find_grpc_port(instance_number: int) -> int:
|
|
|
100
99
|
ini_file = ini_dir / ini_file_name(instance_number)
|
|
101
100
|
logger.debug(f'Looking for .ini file at {ini_file}')
|
|
102
101
|
if ini_file.is_file():
|
|
103
|
-
with open(ini_file
|
|
102
|
+
with open(ini_file) as ini_file_data:
|
|
104
103
|
for line in ini_file_data.readlines():
|
|
105
104
|
if '=' in line:
|
|
106
105
|
key, value = line.split('=')
|
|
@@ -146,7 +145,7 @@ def publish_grpc_port(grpc_port: int, instance_number: int) -> bool:
|
|
|
146
145
|
|
|
147
146
|
# -----------------------------------------------------------------------------
|
|
148
147
|
async def open_android_netsim_controller_transport(
|
|
149
|
-
server_host:
|
|
148
|
+
server_host: str | None, server_port: int, options: dict[str, str]
|
|
150
149
|
) -> Transport:
|
|
151
150
|
if server_host == '_' or not server_host:
|
|
152
151
|
server_host = 'localhost'
|
|
@@ -156,21 +155,26 @@ async def open_android_netsim_controller_transport(
|
|
|
156
155
|
logger.warning("unable to publish gRPC port")
|
|
157
156
|
|
|
158
157
|
class HciDevice:
|
|
159
|
-
def __init__(self, context,
|
|
158
|
+
def __init__(self, context, server):
|
|
160
159
|
self.context = context
|
|
161
|
-
self.
|
|
160
|
+
self.server = server
|
|
162
161
|
self.name = None
|
|
162
|
+
self.sink = None
|
|
163
163
|
self.loop = asyncio.get_running_loop()
|
|
164
164
|
self.done = self.loop.create_future()
|
|
165
|
-
self.task = self.loop.create_task(self.pump())
|
|
166
165
|
|
|
167
166
|
async def pump(self):
|
|
168
167
|
try:
|
|
169
168
|
await self.pump_loop()
|
|
170
169
|
except asyncio.CancelledError:
|
|
171
170
|
logger.debug('Pump task canceled')
|
|
172
|
-
|
|
173
|
-
|
|
171
|
+
finally:
|
|
172
|
+
if self.sink:
|
|
173
|
+
logger.debug('Releasing sink')
|
|
174
|
+
self.server.release_sink()
|
|
175
|
+
self.sink = None
|
|
176
|
+
|
|
177
|
+
logger.debug('Pump task terminated')
|
|
174
178
|
|
|
175
179
|
async def pump_loop(self):
|
|
176
180
|
while True:
|
|
@@ -186,15 +190,26 @@ async def open_android_netsim_controller_transport(
|
|
|
186
190
|
if request.WhichOneof('request_type') == 'initial_info':
|
|
187
191
|
logger.debug(f'Received initial info: {request}')
|
|
188
192
|
|
|
193
|
+
self.name = request.initial_info.name
|
|
194
|
+
|
|
189
195
|
# We only accept BLUETOOTH
|
|
190
196
|
if request.initial_info.chip.kind != ChipKind.BLUETOOTH:
|
|
191
197
|
logger.warning('Unsupported chip type')
|
|
192
198
|
error = PacketResponse(error='Unsupported chip type')
|
|
193
199
|
await self.context.write(error)
|
|
194
|
-
return
|
|
200
|
+
# return
|
|
201
|
+
continue
|
|
202
|
+
|
|
203
|
+
# Lease the sink so that no other device can send
|
|
204
|
+
self.sink = self.server.lease_sink(self)
|
|
205
|
+
if self.sink is None:
|
|
206
|
+
logger.warning('Another device is already connected')
|
|
207
|
+
error = PacketResponse(error='Device busy')
|
|
208
|
+
await self.context.write(error)
|
|
209
|
+
# return
|
|
210
|
+
continue
|
|
195
211
|
|
|
196
|
-
|
|
197
|
-
continue
|
|
212
|
+
continue
|
|
198
213
|
|
|
199
214
|
# Expect a data packet
|
|
200
215
|
request_type = request.WhichOneof('request_type')
|
|
@@ -205,10 +220,10 @@ async def open_android_netsim_controller_transport(
|
|
|
205
220
|
continue
|
|
206
221
|
|
|
207
222
|
# Process the packet
|
|
208
|
-
|
|
223
|
+
assert self.sink is not None
|
|
224
|
+
self.sink(
|
|
209
225
|
bytes([request.hci_packet.packet_type]) + request.hci_packet.packet
|
|
210
226
|
)
|
|
211
|
-
self.on_data_received(data)
|
|
212
227
|
|
|
213
228
|
async def send_packet(self, data):
|
|
214
229
|
return await self.context.write(
|
|
@@ -217,12 +232,6 @@ async def open_android_netsim_controller_transport(
|
|
|
217
232
|
)
|
|
218
233
|
)
|
|
219
234
|
|
|
220
|
-
def terminate(self):
|
|
221
|
-
self.task.cancel()
|
|
222
|
-
|
|
223
|
-
async def wait_for_termination(self):
|
|
224
|
-
await self.done
|
|
225
|
-
|
|
226
235
|
server_address = f'{server_host}:{server_port}'
|
|
227
236
|
|
|
228
237
|
class Server(PacketStreamerServicer, ParserSource):
|
|
@@ -258,27 +267,27 @@ async def open_android_netsim_controller_transport(
|
|
|
258
267
|
|
|
259
268
|
return await self.device.send_packet(packet)
|
|
260
269
|
|
|
261
|
-
|
|
262
|
-
logger.debug('StreamPackets request')
|
|
263
|
-
|
|
264
|
-
# Check that we don't already have a device
|
|
270
|
+
def lease_sink(self, device):
|
|
265
271
|
if self.device:
|
|
266
|
-
|
|
267
|
-
|
|
272
|
+
return None
|
|
273
|
+
self.device = device
|
|
274
|
+
return self.parser.feed_data
|
|
275
|
+
|
|
276
|
+
def release_sink(self):
|
|
277
|
+
self.device = None
|
|
278
|
+
|
|
279
|
+
async def StreamPackets(self, request_iterator, context):
|
|
280
|
+
logger.debug('StreamPackets request')
|
|
268
281
|
|
|
269
282
|
# Instantiate a new device
|
|
270
|
-
|
|
283
|
+
device = HciDevice(context, self)
|
|
271
284
|
|
|
272
|
-
#
|
|
273
|
-
logger.debug('
|
|
285
|
+
# Pump packets to/from the device
|
|
286
|
+
logger.debug('Pumping device packets')
|
|
274
287
|
try:
|
|
275
|
-
await
|
|
276
|
-
|
|
277
|
-
logger.debug('
|
|
278
|
-
self.device.terminate()
|
|
279
|
-
|
|
280
|
-
logger.debug('Device terminated')
|
|
281
|
-
self.device = None
|
|
288
|
+
await device.pump()
|
|
289
|
+
finally:
|
|
290
|
+
logger.debug('Pump terminated')
|
|
282
291
|
|
|
283
292
|
server = Server()
|
|
284
293
|
await server.start()
|
|
@@ -291,9 +300,9 @@ async def open_android_netsim_controller_transport(
|
|
|
291
300
|
|
|
292
301
|
# -----------------------------------------------------------------------------
|
|
293
302
|
async def open_android_netsim_host_transport_with_address(
|
|
294
|
-
server_host:
|
|
303
|
+
server_host: str | None,
|
|
295
304
|
server_port: int,
|
|
296
|
-
options:
|
|
305
|
+
options: dict[str, str] | None = None,
|
|
297
306
|
):
|
|
298
307
|
if server_host == '_' or not server_host:
|
|
299
308
|
server_host = 'localhost'
|
|
@@ -318,7 +327,7 @@ async def open_android_netsim_host_transport_with_address(
|
|
|
318
327
|
|
|
319
328
|
# -----------------------------------------------------------------------------
|
|
320
329
|
async def open_android_netsim_host_transport_with_channel(
|
|
321
|
-
channel, options:
|
|
330
|
+
channel, options: dict[str, str] | None = None
|
|
322
331
|
):
|
|
323
332
|
# Wrapper for I/O operations
|
|
324
333
|
class HciDevice:
|
|
@@ -398,7 +407,7 @@ async def open_android_netsim_host_transport_with_channel(
|
|
|
398
407
|
|
|
399
408
|
|
|
400
409
|
# -----------------------------------------------------------------------------
|
|
401
|
-
async def open_android_netsim_transport(spec:
|
|
410
|
+
async def open_android_netsim_transport(spec: str | None) -> Transport:
|
|
402
411
|
'''
|
|
403
412
|
Open a transport connection as a client or server, implementing Android's `netsim`
|
|
404
413
|
simulator protocol over gRPC.
|
bumble/transport/common.py
CHANGED
|
@@ -23,7 +23,7 @@ import io
|
|
|
23
23
|
import logging
|
|
24
24
|
import struct
|
|
25
25
|
from collections.abc import Awaitable, Callable
|
|
26
|
-
from typing import Any,
|
|
26
|
+
from typing import Any, Protocol
|
|
27
27
|
|
|
28
28
|
from bumble import core, hci
|
|
29
29
|
from bumble.colors import color
|
|
@@ -107,11 +107,11 @@ class PacketParser:
|
|
|
107
107
|
NEED_LENGTH = 1
|
|
108
108
|
NEED_BODY = 2
|
|
109
109
|
|
|
110
|
-
sink:
|
|
110
|
+
sink: TransportSink | None
|
|
111
111
|
extended_packet_info: dict[int, tuple[int, int, str]]
|
|
112
|
-
packet_info:
|
|
112
|
+
packet_info: tuple[int, int, str] | None = None
|
|
113
113
|
|
|
114
|
-
def __init__(self, sink:
|
|
114
|
+
def __init__(self, sink: TransportSink | None = None) -> None:
|
|
115
115
|
self.sink = sink
|
|
116
116
|
self.extended_packet_info = {}
|
|
117
117
|
self.reset()
|
|
@@ -176,7 +176,7 @@ class PacketReader:
|
|
|
176
176
|
self.source = source
|
|
177
177
|
self.at_end = False
|
|
178
178
|
|
|
179
|
-
def next_packet(self) ->
|
|
179
|
+
def next_packet(self) -> bytes | None:
|
|
180
180
|
# Get the packet type
|
|
181
181
|
packet_type = self.source.read(1)
|
|
182
182
|
if len(packet_type) != 1:
|
|
@@ -253,7 +253,7 @@ class BaseSource:
|
|
|
253
253
|
"""
|
|
254
254
|
|
|
255
255
|
terminated: asyncio.Future[None]
|
|
256
|
-
sink:
|
|
256
|
+
sink: TransportSink | None
|
|
257
257
|
|
|
258
258
|
def __init__(self) -> None:
|
|
259
259
|
self.terminated = asyncio.get_running_loop().create_future()
|
|
@@ -357,7 +357,7 @@ class Transport:
|
|
|
357
357
|
|
|
358
358
|
# -----------------------------------------------------------------------------
|
|
359
359
|
class PumpedPacketSource(ParserSource):
|
|
360
|
-
pump_task:
|
|
360
|
+
pump_task: asyncio.Task[None] | None
|
|
361
361
|
|
|
362
362
|
def __init__(self, receive) -> None:
|
|
363
363
|
super().__init__()
|
|
@@ -390,7 +390,7 @@ class PumpedPacketSource(ParserSource):
|
|
|
390
390
|
|
|
391
391
|
# -----------------------------------------------------------------------------
|
|
392
392
|
class PumpedPacketSink:
|
|
393
|
-
pump_task:
|
|
393
|
+
pump_task: asyncio.Task[None] | None
|
|
394
394
|
|
|
395
395
|
def __init__(self, send: Callable[[bytes], Awaitable[Any]]):
|
|
396
396
|
self.send_function = send
|
|
@@ -443,7 +443,7 @@ class SnoopingTransport(Transport):
|
|
|
443
443
|
|
|
444
444
|
@staticmethod
|
|
445
445
|
def create_with(
|
|
446
|
-
transport: Transport, snooper:
|
|
446
|
+
transport: Transport, snooper: contextlib.AbstractContextManager[Snooper]
|
|
447
447
|
) -> SnoopingTransport:
|
|
448
448
|
"""
|
|
449
449
|
Create an instance given a snooper that works as as context manager.
|
bumble/transport/file.py
CHANGED
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
# Imports
|
|
17
17
|
# -----------------------------------------------------------------------------
|
|
18
18
|
import asyncio
|
|
19
|
-
import io
|
|
20
19
|
import logging
|
|
21
20
|
|
|
22
21
|
from bumble.transport.common import StreamPacketSink, StreamPacketSource, Transport
|
|
@@ -36,7 +35,7 @@ async def open_file_transport(spec: str) -> Transport:
|
|
|
36
35
|
'''
|
|
37
36
|
|
|
38
37
|
# Open the file
|
|
39
|
-
file =
|
|
38
|
+
file = open(spec, 'r+b', buffering=0)
|
|
40
39
|
|
|
41
40
|
# Setup reading
|
|
42
41
|
read_transport, packet_source = await asyncio.get_running_loop().connect_read_pipe(
|
bumble/transport/hci_socket.py
CHANGED
|
@@ -22,7 +22,6 @@ import logging
|
|
|
22
22
|
import os
|
|
23
23
|
import socket
|
|
24
24
|
import struct
|
|
25
|
-
from typing import Optional
|
|
26
25
|
|
|
27
26
|
from bumble.transport.common import ParserSource, Transport
|
|
28
27
|
|
|
@@ -33,7 +32,7 @@ logger = logging.getLogger(__name__)
|
|
|
33
32
|
|
|
34
33
|
|
|
35
34
|
# -----------------------------------------------------------------------------
|
|
36
|
-
async def open_hci_socket_transport(spec:
|
|
35
|
+
async def open_hci_socket_transport(spec: str | None) -> Transport:
|
|
37
36
|
'''
|
|
38
37
|
Open an HCI Socket (only available on some platforms).
|
|
39
38
|
The parameter string is either empty (to use the first/default Bluetooth adapter)
|
|
@@ -87,7 +86,7 @@ async def open_hci_socket_transport(spec: Optional[str]) -> Transport:
|
|
|
87
86
|
)
|
|
88
87
|
!= 0
|
|
89
88
|
):
|
|
90
|
-
raise
|
|
89
|
+
raise OSError(ctypes.get_errno(), os.strerror(ctypes.get_errno()))
|
|
91
90
|
|
|
92
91
|
class HciSocketSource(ParserSource):
|
|
93
92
|
def __init__(self, hci_socket):
|
bumble/transport/pty.py
CHANGED
|
@@ -17,12 +17,10 @@
|
|
|
17
17
|
# -----------------------------------------------------------------------------
|
|
18
18
|
import asyncio
|
|
19
19
|
import atexit
|
|
20
|
-
import io
|
|
21
20
|
import logging
|
|
22
21
|
import os
|
|
23
22
|
import pty
|
|
24
23
|
import tty
|
|
25
|
-
from typing import Optional
|
|
26
24
|
|
|
27
25
|
from bumble.transport.common import StreamPacketSink, StreamPacketSource, Transport
|
|
28
26
|
|
|
@@ -33,7 +31,7 @@ logger = logging.getLogger(__name__)
|
|
|
33
31
|
|
|
34
32
|
|
|
35
33
|
# -----------------------------------------------------------------------------
|
|
36
|
-
async def open_pty_transport(spec:
|
|
34
|
+
async def open_pty_transport(spec: str | None) -> Transport:
|
|
37
35
|
'''
|
|
38
36
|
Open a PTY transport.
|
|
39
37
|
The parameter string may be empty, or a path name where a symbolic link
|
|
@@ -48,11 +46,11 @@ async def open_pty_transport(spec: Optional[str]) -> Transport:
|
|
|
48
46
|
tty.setraw(replica)
|
|
49
47
|
|
|
50
48
|
read_transport, packet_source = await asyncio.get_running_loop().connect_read_pipe(
|
|
51
|
-
StreamPacketSource,
|
|
49
|
+
StreamPacketSource, open(primary, 'rb', closefd=False)
|
|
52
50
|
)
|
|
53
51
|
|
|
54
52
|
write_transport, _ = await asyncio.get_running_loop().connect_write_pipe(
|
|
55
|
-
asyncio.BaseProtocol,
|
|
53
|
+
asyncio.BaseProtocol, open(primary, 'wb', closefd=False)
|
|
56
54
|
)
|
|
57
55
|
packet_sink = StreamPacketSink(write_transport)
|
|
58
56
|
|
bumble/transport/pyusb.py
CHANGED
|
@@ -19,7 +19,6 @@ import asyncio
|
|
|
19
19
|
import logging
|
|
20
20
|
import threading
|
|
21
21
|
import time
|
|
22
|
-
from typing import Optional
|
|
23
22
|
|
|
24
23
|
import usb.core
|
|
25
24
|
import usb.util
|
|
@@ -284,7 +283,9 @@ async def open_pyusb_transport(spec: str) -> Transport:
|
|
|
284
283
|
device = await _power_cycle(device) # type: ignore
|
|
285
284
|
except Exception as e:
|
|
286
285
|
logging.debug(e, stack_info=True)
|
|
287
|
-
logging.info(
|
|
286
|
+
logging.info(
|
|
287
|
+
f"Unable to power cycle {hex(device.idVendor)} {hex(device.idProduct)}"
|
|
288
|
+
) # type: ignore
|
|
288
289
|
|
|
289
290
|
# Collect the metadata
|
|
290
291
|
device_metadata = {'vendor_id': device.idVendor, 'product_id': device.idProduct}
|
|
@@ -370,7 +371,9 @@ async def _power_cycle(device: UsbDevice) -> UsbDevice:
|
|
|
370
371
|
# Device needs to be find again otherwise it will appear as disconnected
|
|
371
372
|
return usb.core.find(idVendor=device.idVendor, idProduct=device.idProduct) # type: ignore
|
|
372
373
|
except USBError:
|
|
373
|
-
logger.exception(
|
|
374
|
+
logger.exception(
|
|
375
|
+
f"Adjustment needed: Please revise the udev rule for device {hex(device.idVendor)}:{hex(device.idProduct)} for proper recognition."
|
|
376
|
+
) # type: ignore
|
|
374
377
|
|
|
375
378
|
return device
|
|
376
379
|
|
|
@@ -385,7 +388,7 @@ def _set_port_status(device: UsbDevice, port: int, on: bool):
|
|
|
385
388
|
)
|
|
386
389
|
|
|
387
390
|
|
|
388
|
-
def _find_device_by_path(sys_path: str) ->
|
|
391
|
+
def _find_device_by_path(sys_path: str) -> UsbDevice | None:
|
|
389
392
|
"""Finds a USB device based on its system path."""
|
|
390
393
|
bus_num, *port_parts = sys_path.split('-')
|
|
391
394
|
ports = [int(port) for port in port_parts[0].split('.')]
|
|
@@ -398,7 +401,7 @@ def _find_device_by_path(sys_path: str) -> Optional[UsbDevice]:
|
|
|
398
401
|
return None
|
|
399
402
|
|
|
400
403
|
|
|
401
|
-
def _find_hub_by_device_path(sys_path: str) ->
|
|
404
|
+
def _find_hub_by_device_path(sys_path: str) -> UsbDevice | None:
|
|
402
405
|
"""Finds the USB hub associated with a specific device path."""
|
|
403
406
|
hub_sys_path = sys_path.rsplit('.', 1)[0]
|
|
404
407
|
hub_device = _find_device_by_path(hub_sys_path)
|
bumble/transport/serial.py
CHANGED
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
# -----------------------------------------------------------------------------
|
|
18
18
|
import asyncio
|
|
19
19
|
import logging
|
|
20
|
-
from typing import Optional
|
|
21
20
|
|
|
22
21
|
import serial_asyncio
|
|
23
22
|
|
|
@@ -52,7 +51,7 @@ class SerialPacketSource(StreamPacketSource):
|
|
|
52
51
|
logger.debug('connection made')
|
|
53
52
|
self._ready.set()
|
|
54
53
|
|
|
55
|
-
def connection_lost(self, exc:
|
|
54
|
+
def connection_lost(self, exc: Exception | None) -> None:
|
|
56
55
|
logger.debug('connection lost')
|
|
57
56
|
self.on_transport_lost()
|
|
58
57
|
|
bumble/transport/vhci.py
CHANGED
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
# Imports
|
|
17
17
|
# -----------------------------------------------------------------------------
|
|
18
18
|
import logging
|
|
19
|
-
from typing import Optional
|
|
20
19
|
|
|
21
20
|
from bumble.transport.common import Transport
|
|
22
21
|
from bumble.transport.file import open_file_transport
|
|
@@ -28,7 +27,7 @@ logger = logging.getLogger(__name__)
|
|
|
28
27
|
|
|
29
28
|
|
|
30
29
|
# -----------------------------------------------------------------------------
|
|
31
|
-
async def open_vhci_transport(spec:
|
|
30
|
+
async def open_vhci_transport(spec: str | None) -> Transport:
|
|
32
31
|
'''
|
|
33
32
|
Open a VHCI transport (only available on some platforms).
|
|
34
33
|
The parameter string is either empty (to use the default VHCI device
|
bumble/transport/ws_server.py
CHANGED
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
# Imports
|
|
17
17
|
# -----------------------------------------------------------------------------
|
|
18
18
|
import logging
|
|
19
|
-
from typing import Optional
|
|
20
19
|
|
|
21
20
|
import websockets.asyncio.server
|
|
22
21
|
|
|
@@ -43,8 +42,8 @@ async def open_ws_server_transport(spec: str) -> Transport:
|
|
|
43
42
|
class WsServerTransport(Transport):
|
|
44
43
|
sink: PumpedPacketSink
|
|
45
44
|
source: ParserSource
|
|
46
|
-
connection:
|
|
47
|
-
server:
|
|
45
|
+
connection: websockets.asyncio.server.ServerConnection | None
|
|
46
|
+
server: websockets.asyncio.server.Server | None
|
|
48
47
|
|
|
49
48
|
def __init__(self) -> None:
|
|
50
49
|
source = ParserSource()
|
bumble/utils.py
CHANGED
|
@@ -22,16 +22,12 @@ import collections
|
|
|
22
22
|
import enum
|
|
23
23
|
import functools
|
|
24
24
|
import logging
|
|
25
|
-
import sys
|
|
26
25
|
import warnings
|
|
26
|
+
from collections.abc import Awaitable, Callable
|
|
27
27
|
from typing import (
|
|
28
28
|
Any,
|
|
29
|
-
Awaitable,
|
|
30
|
-
Callable,
|
|
31
|
-
Optional,
|
|
32
29
|
Protocol,
|
|
33
30
|
TypeVar,
|
|
34
|
-
Union,
|
|
35
31
|
overload,
|
|
36
32
|
)
|
|
37
33
|
|
|
@@ -170,8 +166,8 @@ class EventWatcher:
|
|
|
170
166
|
) -> _Handler: ...
|
|
171
167
|
|
|
172
168
|
def on(
|
|
173
|
-
self, emitter: pyee.EventEmitter, event: str, handler:
|
|
174
|
-
) ->
|
|
169
|
+
self, emitter: pyee.EventEmitter, event: str, handler: _Handler | None = None
|
|
170
|
+
) -> _Handler | Callable[[_Handler], _Handler]:
|
|
175
171
|
'''Watch an event until the context is closed.
|
|
176
172
|
|
|
177
173
|
Args:
|
|
@@ -199,8 +195,8 @@ class EventWatcher:
|
|
|
199
195
|
) -> _Handler: ...
|
|
200
196
|
|
|
201
197
|
def once(
|
|
202
|
-
self, emitter: pyee.EventEmitter, event: str, handler:
|
|
203
|
-
) ->
|
|
198
|
+
self, emitter: pyee.EventEmitter, event: str, handler: _Handler | None = None
|
|
199
|
+
) -> _Handler | Callable[[_Handler], _Handler]:
|
|
204
200
|
'''Watch an event for once.
|
|
205
201
|
|
|
206
202
|
Args:
|
|
@@ -241,11 +237,7 @@ def cancel_on_event(
|
|
|
241
237
|
return
|
|
242
238
|
msg = f'abort: {event} event occurred.'
|
|
243
239
|
if isinstance(future, asyncio.Task):
|
|
244
|
-
|
|
245
|
-
if sys.version_info < (3, 9, 0):
|
|
246
|
-
future.cancel()
|
|
247
|
-
else:
|
|
248
|
-
future.cancel(msg)
|
|
240
|
+
future.cancel(msg)
|
|
249
241
|
else:
|
|
250
242
|
future.set_exception(asyncio.CancelledError(msg))
|
|
251
243
|
|
|
@@ -537,3 +529,20 @@ class IntConvertible(Protocol):
|
|
|
537
529
|
|
|
538
530
|
def __init__(self, value: int) -> None: ...
|
|
539
531
|
def __int__(self) -> int: ...
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
# -----------------------------------------------------------------------------
|
|
535
|
+
def crc_16(data: bytes) -> int:
|
|
536
|
+
"""Calculate CRC-16-IBM of given data.
|
|
537
|
+
|
|
538
|
+
Polynomial = x^16 + x^15 + x^2 + 1 = 0x8005 or 0xA001(Reversed)
|
|
539
|
+
"""
|
|
540
|
+
crc = 0x0000
|
|
541
|
+
for byte in data:
|
|
542
|
+
crc ^= byte
|
|
543
|
+
for _ in range(8):
|
|
544
|
+
if (crc & 0x0001) > 0:
|
|
545
|
+
crc = (crc >> 1) ^ 0xA001
|
|
546
|
+
else:
|
|
547
|
+
crc = crc >> 1
|
|
548
|
+
return crc
|
bumble/vendor/android/hci.py
CHANGED
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
import dataclasses
|
|
19
19
|
import struct
|
|
20
20
|
from dataclasses import field
|
|
21
|
-
from typing import Optional
|
|
22
21
|
|
|
23
22
|
from bumble import hci
|
|
24
23
|
|
|
@@ -51,6 +50,7 @@ class HCI_LE_Get_Vendor_Capabilities_Command(hci.HCI_Command):
|
|
|
51
50
|
'''
|
|
52
51
|
See https://source.android.com/docs/core/connect/bluetooth/hci_requirements#vendor-specific-capabilities
|
|
53
52
|
'''
|
|
53
|
+
|
|
54
54
|
return_parameters_fields = [
|
|
55
55
|
('status', hci.STATUS_SPEC),
|
|
56
56
|
('max_advt_instances', 1),
|
|
@@ -137,6 +137,7 @@ class HCI_Get_Controller_Activity_Energy_Info_Command(hci.HCI_Command):
|
|
|
137
137
|
'''
|
|
138
138
|
See https://source.android.com/docs/core/connect/bluetooth/hci_requirements#le_get_controller_activity_energy_info
|
|
139
139
|
'''
|
|
140
|
+
|
|
140
141
|
return_parameters_fields = [
|
|
141
142
|
('status', hci.STATUS_SPEC),
|
|
142
143
|
('total_tx_time_ms', 4),
|
|
@@ -207,7 +208,7 @@ class HCI_Android_Vendor_Event(hci.HCI_Extended_Event):
|
|
|
207
208
|
@classmethod
|
|
208
209
|
def subclass_from_parameters(
|
|
209
210
|
cls, parameters: bytes
|
|
210
|
-
) ->
|
|
211
|
+
) -> hci.HCI_Extended_Event | None:
|
|
211
212
|
subevent_code = parameters[0]
|
|
212
213
|
if subevent_code == HCI_BLUETOOTH_QUALITY_REPORT_EVENT:
|
|
213
214
|
quality_report_id = parameters[1]
|
|
@@ -229,6 +230,7 @@ class HCI_Bluetooth_Quality_Report_Event(HCI_Android_Vendor_Event):
|
|
|
229
230
|
'''
|
|
230
231
|
See https://source.android.com/docs/core/connect/bluetooth/hci_requirements#bluetooth-quality-report-sub-event
|
|
231
232
|
'''
|
|
233
|
+
|
|
232
234
|
quality_report_id: int = field(metadata=hci.metadata(1))
|
|
233
235
|
packet_types: int = field(metadata=hci.metadata(1))
|
|
234
236
|
connection_handle: int = field(metadata=hci.metadata(2))
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bumble
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.221
|
|
4
4
|
Summary: Bluetooth Stack for Apps, Emulation, Test and Experimentation
|
|
5
5
|
Author-email: Google <bumble-dev@google.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
7
7
|
Project-URL: Homepage, https://github.com/google/bumble
|
|
8
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Requires-Dist: aiohttp~=3.8; platform_system != "Emscripten"
|
|
@@ -25,6 +25,7 @@ Requires-Dist: pyee>=13.0.0
|
|
|
25
25
|
Requires-Dist: pyserial-asyncio>=0.5; platform_system != "Emscripten"
|
|
26
26
|
Requires-Dist: pyserial>=3.5; platform_system != "Emscripten"
|
|
27
27
|
Requires-Dist: pyusb>=1.2; platform_system != "Emscripten"
|
|
28
|
+
Requires-Dist: tomli~=2.2.1; platform_system != "Emscripten"
|
|
28
29
|
Requires-Dist: websockets>=15.0.1; platform_system != "Emscripten"
|
|
29
30
|
Provides-Extra: build
|
|
30
31
|
Requires-Dist: build>=0.7; extra == "build"
|
|
@@ -38,12 +39,12 @@ Requires-Dist: black~=25.1; extra == "development"
|
|
|
38
39
|
Requires-Dist: bt-test-interfaces>=0.0.6; extra == "development"
|
|
39
40
|
Requires-Dist: grpcio-tools>=1.62.1; extra == "development"
|
|
40
41
|
Requires-Dist: invoke>=1.7.3; extra == "development"
|
|
41
|
-
Requires-Dist: isort~=5.13.2; extra == "development"
|
|
42
42
|
Requires-Dist: mobly>=1.12.2; extra == "development"
|
|
43
43
|
Requires-Dist: mypy==1.12.0; extra == "development"
|
|
44
44
|
Requires-Dist: nox>=2022; extra == "development"
|
|
45
45
|
Requires-Dist: pylint==3.3.1; extra == "development"
|
|
46
46
|
Requires-Dist: pyyaml>=6.0; extra == "development"
|
|
47
|
+
Requires-Dist: ruff==0.14.10; extra == "development"
|
|
47
48
|
Requires-Dist: types-appdirs>=1.4.3; extra == "development"
|
|
48
49
|
Requires-Dist: types-invoke>=1.7.3; extra == "development"
|
|
49
50
|
Requires-Dist: types-protobuf>=4.21.0; extra == "development"
|