bumble 0.0.169__py3-none-any.whl → 0.0.172__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 +6 -2
- bumble/apps/console.py +1 -1
- bumble/apps/controller_info.py +2 -1
- bumble/apps/pandora_server.py +6 -4
- bumble/apps/speaker/speaker.py +2 -2
- bumble/att.py +77 -51
- bumble/device.py +5 -3
- bumble/gatt.py +22 -20
- bumble/gatt_client.py +67 -32
- bumble/gatt_server.py +75 -31
- bumble/l2cap.py +92 -125
- bumble/pandora/security.py +49 -17
- bumble/smp.py +20 -7
- bumble/transport/android_emulator.py +7 -4
- bumble/transport/android_netsim.py +84 -50
- bumble/transport/common.py +3 -8
- bumble/transport/ws_client.py +9 -7
- bumble/utils.py +109 -1
- {bumble-0.0.169.dist-info → bumble-0.0.172.dist-info}/METADATA +2 -1
- {bumble-0.0.169.dist-info → bumble-0.0.172.dist-info}/RECORD +24 -24
- {bumble-0.0.169.dist-info → bumble-0.0.172.dist-info}/LICENSE +0 -0
- {bumble-0.0.169.dist-info → bumble-0.0.172.dist-info}/WHEEL +0 -0
- {bumble-0.0.169.dist-info → bumble-0.0.172.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.169.dist-info → bumble-0.0.172.dist-info}/top_level.txt +0 -0
|
@@ -18,11 +18,12 @@
|
|
|
18
18
|
import asyncio
|
|
19
19
|
import atexit
|
|
20
20
|
import logging
|
|
21
|
-
import grpc.aio
|
|
22
21
|
import os
|
|
23
22
|
import pathlib
|
|
24
23
|
import sys
|
|
25
|
-
from typing import Optional
|
|
24
|
+
from typing import Dict, Optional
|
|
25
|
+
|
|
26
|
+
import grpc.aio
|
|
26
27
|
|
|
27
28
|
from .common import (
|
|
28
29
|
ParserSource,
|
|
@@ -33,8 +34,8 @@ from .common import (
|
|
|
33
34
|
)
|
|
34
35
|
|
|
35
36
|
# pylint: disable=no-name-in-module
|
|
36
|
-
from .grpc_protobuf.packet_streamer_pb2_grpc import PacketStreamerStub
|
|
37
37
|
from .grpc_protobuf.packet_streamer_pb2_grpc import (
|
|
38
|
+
PacketStreamerStub,
|
|
38
39
|
PacketStreamerServicer,
|
|
39
40
|
add_PacketStreamerServicer_to_server,
|
|
40
41
|
)
|
|
@@ -43,6 +44,7 @@ from .grpc_protobuf.hci_packet_pb2 import HCIPacket
|
|
|
43
44
|
from .grpc_protobuf.startup_pb2 import Chip, ChipInfo
|
|
44
45
|
from .grpc_protobuf.common_pb2 import ChipKind
|
|
45
46
|
|
|
47
|
+
|
|
46
48
|
# -----------------------------------------------------------------------------
|
|
47
49
|
# Logging
|
|
48
50
|
# -----------------------------------------------------------------------------
|
|
@@ -74,14 +76,20 @@ def get_ini_dir() -> Optional[pathlib.Path]:
|
|
|
74
76
|
|
|
75
77
|
|
|
76
78
|
# -----------------------------------------------------------------------------
|
|
77
|
-
def
|
|
79
|
+
def ini_file_name(instance_number: int) -> str:
|
|
80
|
+
suffix = f'_{instance_number}' if instance_number > 0 else ''
|
|
81
|
+
return f'netsim{suffix}.ini'
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# -----------------------------------------------------------------------------
|
|
85
|
+
def find_grpc_port(instance_number: int) -> int:
|
|
78
86
|
if not (ini_dir := get_ini_dir()):
|
|
79
87
|
logger.debug('no known directory for .ini file')
|
|
80
88
|
return 0
|
|
81
89
|
|
|
82
|
-
ini_file = ini_dir /
|
|
90
|
+
ini_file = ini_dir / ini_file_name(instance_number)
|
|
91
|
+
logger.debug(f'Looking for .ini file at {ini_file}')
|
|
83
92
|
if ini_file.is_file():
|
|
84
|
-
logger.debug(f'Found .ini file at {ini_file}')
|
|
85
93
|
with open(ini_file, 'r') as ini_file_data:
|
|
86
94
|
for line in ini_file_data.readlines():
|
|
87
95
|
if '=' in line:
|
|
@@ -90,12 +98,14 @@ def find_grpc_port() -> int:
|
|
|
90
98
|
logger.debug(f'gRPC port = {value}')
|
|
91
99
|
return int(value)
|
|
92
100
|
|
|
101
|
+
logger.debug('no grpc.port property found in .ini file')
|
|
102
|
+
|
|
93
103
|
# Not found
|
|
94
104
|
return 0
|
|
95
105
|
|
|
96
106
|
|
|
97
107
|
# -----------------------------------------------------------------------------
|
|
98
|
-
def publish_grpc_port(grpc_port) -> bool:
|
|
108
|
+
def publish_grpc_port(grpc_port: int, instance_number: int) -> bool:
|
|
99
109
|
if not (ini_dir := get_ini_dir()):
|
|
100
110
|
logger.debug('no known directory for .ini file')
|
|
101
111
|
return False
|
|
@@ -104,7 +114,7 @@ def publish_grpc_port(grpc_port) -> bool:
|
|
|
104
114
|
logger.debug('ini directory does not exist')
|
|
105
115
|
return False
|
|
106
116
|
|
|
107
|
-
ini_file = ini_dir /
|
|
117
|
+
ini_file = ini_dir / ini_file_name(instance_number)
|
|
108
118
|
try:
|
|
109
119
|
ini_file.write_text(f'grpc.port={grpc_port}\n')
|
|
110
120
|
logger.debug(f"published gRPC port at {ini_file}")
|
|
@@ -122,14 +132,15 @@ def publish_grpc_port(grpc_port) -> bool:
|
|
|
122
132
|
|
|
123
133
|
# -----------------------------------------------------------------------------
|
|
124
134
|
async def open_android_netsim_controller_transport(
|
|
125
|
-
server_host: Optional[str], server_port: int
|
|
135
|
+
server_host: Optional[str], server_port: int, options: Dict[str, str]
|
|
126
136
|
) -> Transport:
|
|
127
137
|
if not server_port:
|
|
128
138
|
raise ValueError('invalid port')
|
|
129
139
|
if server_host == '_' or not server_host:
|
|
130
140
|
server_host = 'localhost'
|
|
131
141
|
|
|
132
|
-
|
|
142
|
+
instance_number = int(options.get('instance', "0"))
|
|
143
|
+
if not publish_grpc_port(server_port, instance_number):
|
|
133
144
|
logger.warning("unable to publish gRPC port")
|
|
134
145
|
|
|
135
146
|
class HciDevice:
|
|
@@ -186,15 +197,12 @@ async def open_android_netsim_controller_transport(
|
|
|
186
197
|
logger.debug(f'<<< PACKET: {data.hex()}')
|
|
187
198
|
self.on_data_received(data)
|
|
188
199
|
|
|
189
|
-
def send_packet(self, data):
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
hci_packet=HCIPacket(packet_type=data[0], packet=data[1:])
|
|
194
|
-
)
|
|
200
|
+
async def send_packet(self, data):
|
|
201
|
+
return await self.context.write(
|
|
202
|
+
PacketResponse(
|
|
203
|
+
hci_packet=HCIPacket(packet_type=data[0], packet=data[1:])
|
|
195
204
|
)
|
|
196
|
-
|
|
197
|
-
self.loop.create_task(send())
|
|
205
|
+
)
|
|
198
206
|
|
|
199
207
|
def terminate(self):
|
|
200
208
|
self.task.cancel()
|
|
@@ -228,17 +236,17 @@ async def open_android_netsim_controller_transport(
|
|
|
228
236
|
logger.debug('gRPC server cancelled')
|
|
229
237
|
await self.grpc_server.stop(None)
|
|
230
238
|
|
|
231
|
-
def
|
|
239
|
+
async def send_packet(self, packet):
|
|
232
240
|
if not self.device:
|
|
233
241
|
logger.debug('no device, dropping packet')
|
|
234
242
|
return
|
|
235
243
|
|
|
236
|
-
self.device.send_packet(packet)
|
|
244
|
+
return await self.device.send_packet(packet)
|
|
237
245
|
|
|
238
246
|
async def StreamPackets(self, _request_iterator, context):
|
|
239
247
|
logger.debug('StreamPackets request')
|
|
240
248
|
|
|
241
|
-
# Check that we
|
|
249
|
+
# Check that we don't already have a device
|
|
242
250
|
if self.device:
|
|
243
251
|
logger.debug('busy, already serving a device')
|
|
244
252
|
return PacketResponse(error='Busy')
|
|
@@ -261,15 +269,42 @@ async def open_android_netsim_controller_transport(
|
|
|
261
269
|
await server.start()
|
|
262
270
|
asyncio.get_running_loop().create_task(server.serve())
|
|
263
271
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
272
|
+
sink = PumpedPacketSink(server.send_packet)
|
|
273
|
+
sink.start()
|
|
274
|
+
return Transport(server, sink)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
# -----------------------------------------------------------------------------
|
|
278
|
+
async def open_android_netsim_host_transport_with_address(
|
|
279
|
+
server_host: Optional[str],
|
|
280
|
+
server_port: int,
|
|
281
|
+
options: Optional[Dict[str, str]] = None,
|
|
282
|
+
):
|
|
283
|
+
if server_host == '_' or not server_host:
|
|
284
|
+
server_host = 'localhost'
|
|
285
|
+
|
|
286
|
+
if not server_port:
|
|
287
|
+
# Look for the gRPC config in a .ini file
|
|
288
|
+
instance_number = 0 if options is None else int(options.get('instance', '0'))
|
|
289
|
+
server_port = find_grpc_port(instance_number)
|
|
290
|
+
if not server_port:
|
|
291
|
+
raise RuntimeError('gRPC server port not found')
|
|
292
|
+
|
|
293
|
+
# Connect to the gRPC server
|
|
294
|
+
server_address = f'{server_host}:{server_port}'
|
|
295
|
+
logger.debug(f'Connecting to gRPC server at {server_address}')
|
|
296
|
+
channel = grpc.aio.insecure_channel(server_address)
|
|
267
297
|
|
|
268
|
-
return
|
|
298
|
+
return await open_android_netsim_host_transport_with_channel(
|
|
299
|
+
channel,
|
|
300
|
+
options,
|
|
301
|
+
)
|
|
269
302
|
|
|
270
303
|
|
|
271
304
|
# -----------------------------------------------------------------------------
|
|
272
|
-
async def
|
|
305
|
+
async def open_android_netsim_host_transport_with_channel(
|
|
306
|
+
channel, options: Optional[Dict[str, str]] = None
|
|
307
|
+
):
|
|
273
308
|
# Wrapper for I/O operations
|
|
274
309
|
class HciDevice:
|
|
275
310
|
def __init__(self, name, manufacturer, hci_device):
|
|
@@ -288,10 +323,12 @@ async def open_android_netsim_host_transport(server_host, server_port, options):
|
|
|
288
323
|
async def read(self):
|
|
289
324
|
response = await self.hci_device.read()
|
|
290
325
|
response_type = response.WhichOneof('response_type')
|
|
326
|
+
|
|
291
327
|
if response_type == 'error':
|
|
292
328
|
logger.warning(f'received error: {response.error}')
|
|
293
329
|
raise RuntimeError(response.error)
|
|
294
|
-
|
|
330
|
+
|
|
331
|
+
if response_type == 'hci_packet':
|
|
295
332
|
return (
|
|
296
333
|
bytes([response.hci_packet.packet_type])
|
|
297
334
|
+ response.hci_packet.packet
|
|
@@ -306,24 +343,9 @@ async def open_android_netsim_host_transport(server_host, server_port, options):
|
|
|
306
343
|
)
|
|
307
344
|
)
|
|
308
345
|
|
|
309
|
-
name = options.get('name', DEFAULT_NAME)
|
|
346
|
+
name = DEFAULT_NAME if options is None else options.get('name', DEFAULT_NAME)
|
|
310
347
|
manufacturer = DEFAULT_MANUFACTURER
|
|
311
348
|
|
|
312
|
-
if server_host == '_' or not server_host:
|
|
313
|
-
server_host = 'localhost'
|
|
314
|
-
|
|
315
|
-
if not server_port:
|
|
316
|
-
# Look for the gRPC config in a .ini file
|
|
317
|
-
server_host = 'localhost'
|
|
318
|
-
server_port = find_grpc_port()
|
|
319
|
-
if not server_port:
|
|
320
|
-
raise RuntimeError('gRPC server port not found')
|
|
321
|
-
|
|
322
|
-
# Connect to the gRPC server
|
|
323
|
-
server_address = f'{server_host}:{server_port}'
|
|
324
|
-
logger.debug(f'Connecting to gRPC server at {server_address}')
|
|
325
|
-
channel = grpc.aio.insecure_channel(server_address)
|
|
326
|
-
|
|
327
349
|
# Connect as a host
|
|
328
350
|
service = PacketStreamerStub(channel)
|
|
329
351
|
hci_device = HciDevice(
|
|
@@ -334,10 +356,14 @@ async def open_android_netsim_host_transport(server_host, server_port, options):
|
|
|
334
356
|
await hci_device.start()
|
|
335
357
|
|
|
336
358
|
# Create the transport object
|
|
337
|
-
|
|
359
|
+
class GrpcTransport(PumpedTransport):
|
|
360
|
+
async def close(self):
|
|
361
|
+
await super().close()
|
|
362
|
+
await channel.close()
|
|
363
|
+
|
|
364
|
+
transport = GrpcTransport(
|
|
338
365
|
PumpedPacketSource(hci_device.read),
|
|
339
366
|
PumpedPacketSink(hci_device.write),
|
|
340
|
-
channel.close,
|
|
341
367
|
)
|
|
342
368
|
transport.start()
|
|
343
369
|
|
|
@@ -345,7 +371,7 @@ async def open_android_netsim_host_transport(server_host, server_port, options):
|
|
|
345
371
|
|
|
346
372
|
|
|
347
373
|
# -----------------------------------------------------------------------------
|
|
348
|
-
async def open_android_netsim_transport(spec):
|
|
374
|
+
async def open_android_netsim_transport(spec: Optional[str]) -> Transport:
|
|
349
375
|
'''
|
|
350
376
|
Open a transport connection as a client or server, implementing Android's `netsim`
|
|
351
377
|
simulator protocol over gRPC.
|
|
@@ -359,6 +385,11 @@ async def open_android_netsim_transport(spec):
|
|
|
359
385
|
to connect *to* a netsim server (netsim is the controller), or accept
|
|
360
386
|
connections *as* a netsim-compatible server.
|
|
361
387
|
|
|
388
|
+
instance=<n>
|
|
389
|
+
Specifies an instance number, with <n> > 0. This is used to determine which
|
|
390
|
+
.init file to use. In `host` mode, it is ignored when the <host>:<port>
|
|
391
|
+
specifier is present, since in that case no .ini file is used.
|
|
392
|
+
|
|
362
393
|
In `host` mode:
|
|
363
394
|
The <host>:<port> part is optional. When not specified, the transport
|
|
364
395
|
looks for a netsim .ini file, from which it will read the `grpc.backend.port`
|
|
@@ -387,14 +418,15 @@ async def open_android_netsim_transport(spec):
|
|
|
387
418
|
params = spec.split(',') if spec else []
|
|
388
419
|
if params and ':' in params[0]:
|
|
389
420
|
# Explicit <host>:<port>
|
|
390
|
-
host,
|
|
421
|
+
host, port_str = params[0].split(':')
|
|
422
|
+
port = int(port_str)
|
|
391
423
|
params_offset = 1
|
|
392
424
|
else:
|
|
393
425
|
host = None
|
|
394
426
|
port = 0
|
|
395
427
|
params_offset = 0
|
|
396
428
|
|
|
397
|
-
options = {}
|
|
429
|
+
options: Dict[str, str] = {}
|
|
398
430
|
for param in params[params_offset:]:
|
|
399
431
|
if '=' not in param:
|
|
400
432
|
raise ValueError('invalid parameter, expected <name>=<value>')
|
|
@@ -403,10 +435,12 @@ async def open_android_netsim_transport(spec):
|
|
|
403
435
|
|
|
404
436
|
mode = options.get('mode', 'host')
|
|
405
437
|
if mode == 'host':
|
|
406
|
-
return await
|
|
438
|
+
return await open_android_netsim_host_transport_with_address(
|
|
439
|
+
host, port, options
|
|
440
|
+
)
|
|
407
441
|
if mode == 'controller':
|
|
408
442
|
if host is None:
|
|
409
443
|
raise ValueError('<host>:<port> missing')
|
|
410
|
-
return await open_android_netsim_controller_transport(host, port)
|
|
444
|
+
return await open_android_netsim_controller_transport(host, port, options)
|
|
411
445
|
|
|
412
446
|
raise ValueError('invalid mode option')
|
bumble/transport/common.py
CHANGED
|
@@ -339,8 +339,9 @@ class PumpedPacketSource(ParserSource):
|
|
|
339
339
|
try:
|
|
340
340
|
packet = await self.receive_function()
|
|
341
341
|
self.parser.feed_data(packet)
|
|
342
|
-
except asyncio.
|
|
342
|
+
except asyncio.CancelledError:
|
|
343
343
|
logger.debug('source pump task done')
|
|
344
|
+
self.terminated.set_result(None)
|
|
344
345
|
break
|
|
345
346
|
except Exception as error:
|
|
346
347
|
logger.warning(f'exception while waiting for packet: {error}')
|
|
@@ -370,7 +371,7 @@ class PumpedPacketSink:
|
|
|
370
371
|
try:
|
|
371
372
|
packet = await self.packet_queue.get()
|
|
372
373
|
await self.send_function(packet)
|
|
373
|
-
except asyncio.
|
|
374
|
+
except asyncio.CancelledError:
|
|
374
375
|
logger.debug('sink pump task done')
|
|
375
376
|
break
|
|
376
377
|
except Exception as error:
|
|
@@ -393,19 +394,13 @@ class PumpedTransport(Transport):
|
|
|
393
394
|
self,
|
|
394
395
|
source: PumpedPacketSource,
|
|
395
396
|
sink: PumpedPacketSink,
|
|
396
|
-
close_function,
|
|
397
397
|
) -> None:
|
|
398
398
|
super().__init__(source, sink)
|
|
399
|
-
self.close_function = close_function
|
|
400
399
|
|
|
401
400
|
def start(self) -> None:
|
|
402
401
|
self.source.start()
|
|
403
402
|
self.sink.start()
|
|
404
403
|
|
|
405
|
-
async def close(self) -> None:
|
|
406
|
-
await super().close()
|
|
407
|
-
await self.close_function()
|
|
408
|
-
|
|
409
404
|
|
|
410
405
|
# -----------------------------------------------------------------------------
|
|
411
406
|
class SnoopingTransport(Transport):
|
bumble/transport/ws_client.py
CHANGED
|
@@ -31,19 +31,21 @@ async def open_ws_client_transport(spec: str) -> Transport:
|
|
|
31
31
|
'''
|
|
32
32
|
Open a WebSocket client transport.
|
|
33
33
|
The parameter string has this syntax:
|
|
34
|
-
<
|
|
34
|
+
<websocket-url>
|
|
35
35
|
|
|
36
|
-
Example:
|
|
36
|
+
Example: ws://localhost:7681/v1/websocket/bt
|
|
37
37
|
'''
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
uri = f'ws://{remote_host}:{remote_port}'
|
|
41
|
-
websocket = await websockets.client.connect(uri)
|
|
39
|
+
websocket = await websockets.client.connect(spec)
|
|
42
40
|
|
|
43
|
-
|
|
41
|
+
class WsTransport(PumpedTransport):
|
|
42
|
+
async def close(self):
|
|
43
|
+
await super().close()
|
|
44
|
+
await websocket.close()
|
|
45
|
+
|
|
46
|
+
transport = WsTransport(
|
|
44
47
|
PumpedPacketSource(websocket.recv),
|
|
45
48
|
PumpedPacketSink(websocket.send),
|
|
46
|
-
websocket.close,
|
|
47
49
|
)
|
|
48
50
|
transport.start()
|
|
49
51
|
return transport
|
bumble/utils.py
CHANGED
|
@@ -15,12 +15,24 @@
|
|
|
15
15
|
# -----------------------------------------------------------------------------
|
|
16
16
|
# Imports
|
|
17
17
|
# -----------------------------------------------------------------------------
|
|
18
|
+
from __future__ import annotations
|
|
18
19
|
import asyncio
|
|
19
20
|
import logging
|
|
20
21
|
import traceback
|
|
21
22
|
import collections
|
|
22
23
|
import sys
|
|
23
|
-
from typing import
|
|
24
|
+
from typing import (
|
|
25
|
+
Awaitable,
|
|
26
|
+
Set,
|
|
27
|
+
TypeVar,
|
|
28
|
+
List,
|
|
29
|
+
Tuple,
|
|
30
|
+
Callable,
|
|
31
|
+
Any,
|
|
32
|
+
Optional,
|
|
33
|
+
Union,
|
|
34
|
+
overload,
|
|
35
|
+
)
|
|
24
36
|
from functools import wraps
|
|
25
37
|
from pyee import EventEmitter
|
|
26
38
|
|
|
@@ -64,6 +76,102 @@ def composite_listener(cls):
|
|
|
64
76
|
return cls
|
|
65
77
|
|
|
66
78
|
|
|
79
|
+
# -----------------------------------------------------------------------------
|
|
80
|
+
_Handler = TypeVar('_Handler', bound=Callable)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class EventWatcher:
|
|
84
|
+
'''A wrapper class to control the lifecycle of event handlers better.
|
|
85
|
+
|
|
86
|
+
Usage:
|
|
87
|
+
```
|
|
88
|
+
watcher = EventWatcher()
|
|
89
|
+
|
|
90
|
+
def on_foo():
|
|
91
|
+
...
|
|
92
|
+
watcher.on(emitter, 'foo', on_foo)
|
|
93
|
+
|
|
94
|
+
@watcher.on(emitter, 'bar')
|
|
95
|
+
def on_bar():
|
|
96
|
+
...
|
|
97
|
+
|
|
98
|
+
# Close all event handlers watching through this watcher
|
|
99
|
+
watcher.close()
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
As context:
|
|
103
|
+
```
|
|
104
|
+
with contextlib.closing(EventWatcher()) as context:
|
|
105
|
+
@context.on(emitter, 'foo')
|
|
106
|
+
def on_foo():
|
|
107
|
+
...
|
|
108
|
+
# on_foo() has been removed here!
|
|
109
|
+
```
|
|
110
|
+
'''
|
|
111
|
+
|
|
112
|
+
handlers: List[Tuple[EventEmitter, str, Callable[..., Any]]]
|
|
113
|
+
|
|
114
|
+
def __init__(self) -> None:
|
|
115
|
+
self.handlers = []
|
|
116
|
+
|
|
117
|
+
@overload
|
|
118
|
+
def on(self, emitter: EventEmitter, event: str) -> Callable[[_Handler], _Handler]:
|
|
119
|
+
...
|
|
120
|
+
|
|
121
|
+
@overload
|
|
122
|
+
def on(self, emitter: EventEmitter, event: str, handler: _Handler) -> _Handler:
|
|
123
|
+
...
|
|
124
|
+
|
|
125
|
+
def on(
|
|
126
|
+
self, emitter: EventEmitter, event: str, handler: Optional[_Handler] = None
|
|
127
|
+
) -> Union[_Handler, Callable[[_Handler], _Handler]]:
|
|
128
|
+
'''Watch an event until the context is closed.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
emitter: EventEmitter to watch
|
|
132
|
+
event: Event name
|
|
133
|
+
handler: (Optional) Event handler. When nothing is passed, this method works as a decorator.
|
|
134
|
+
'''
|
|
135
|
+
|
|
136
|
+
def wrapper(f: _Handler) -> _Handler:
|
|
137
|
+
self.handlers.append((emitter, event, f))
|
|
138
|
+
emitter.on(event, f)
|
|
139
|
+
return f
|
|
140
|
+
|
|
141
|
+
return wrapper if handler is None else wrapper(handler)
|
|
142
|
+
|
|
143
|
+
@overload
|
|
144
|
+
def once(self, emitter: EventEmitter, event: str) -> Callable[[_Handler], _Handler]:
|
|
145
|
+
...
|
|
146
|
+
|
|
147
|
+
@overload
|
|
148
|
+
def once(self, emitter: EventEmitter, event: str, handler: _Handler) -> _Handler:
|
|
149
|
+
...
|
|
150
|
+
|
|
151
|
+
def once(
|
|
152
|
+
self, emitter: EventEmitter, event: str, handler: Optional[_Handler] = None
|
|
153
|
+
) -> Union[_Handler, Callable[[_Handler], _Handler]]:
|
|
154
|
+
'''Watch an event for once.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
emitter: EventEmitter to watch
|
|
158
|
+
event: Event name
|
|
159
|
+
handler: (Optional) Event handler. When nothing passed, this method works as a decorator.
|
|
160
|
+
'''
|
|
161
|
+
|
|
162
|
+
def wrapper(f: _Handler) -> _Handler:
|
|
163
|
+
self.handlers.append((emitter, event, f))
|
|
164
|
+
emitter.once(event, f)
|
|
165
|
+
return f
|
|
166
|
+
|
|
167
|
+
return wrapper if handler is None else wrapper(handler)
|
|
168
|
+
|
|
169
|
+
def close(self) -> None:
|
|
170
|
+
for emitter, event, handler in self.handlers:
|
|
171
|
+
if handler in emitter.listeners(event):
|
|
172
|
+
emitter.remove_listener(event, handler)
|
|
173
|
+
|
|
174
|
+
|
|
67
175
|
# -----------------------------------------------------------------------------
|
|
68
176
|
_T = TypeVar('_T')
|
|
69
177
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: bumble
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.172
|
|
4
4
|
Summary: Bluetooth Stack for Apps, Emulation, Test and Experimentation
|
|
5
5
|
Home-page: https://github.com/google/bumble
|
|
6
6
|
Author: Google
|
|
@@ -26,6 +26,7 @@ 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
28
|
Requires-Dist: websockets >=8.1 ; platform_system != "Emscripten"
|
|
29
|
+
Requires-Dist: cryptography >=39.0 ; platform_system == "Emscripten"
|
|
29
30
|
Provides-Extra: build
|
|
30
31
|
Requires-Dist: build >=0.7 ; extra == 'build'
|
|
31
32
|
Provides-Extra: development
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
bumble/__init__.py,sha256=Q8jkz6rgl95IMAeInQVt_2GLoJl3DcEP2cxtrQ-ho5c,110
|
|
2
|
-
bumble/_version.py,sha256=
|
|
2
|
+
bumble/_version.py,sha256=UpV9fqZ_kZsyY5IMbYk4ZKCce4_J0wyp7QWGnUobjTc,278
|
|
3
3
|
bumble/a2dp.py,sha256=vwnKknvSKPR7IdTz8JHHNvHGObjepVy1kFRWm0kNWEI,21923
|
|
4
4
|
bumble/at.py,sha256=kdrcsx2C8Rg61EWESD2QHwpZntkXkRBJLrPn9auv9K8,2961
|
|
5
|
-
bumble/att.py,sha256=
|
|
5
|
+
bumble/att.py,sha256=emTsIoc_C067QE3osdyocic52Wr8N5TJdkpZeJJH6dk,31328
|
|
6
6
|
bumble/avdtp.py,sha256=sT0mFeFvp9jTEP-dOp4_ibdtKX3mtw1Gyi-72tHfkiE,72268
|
|
7
7
|
bumble/bridge.py,sha256=T6es5oS1dy8QgkxQ8iOD-YcZ0SWOv8jaqC7TGxqodk4,3003
|
|
8
8
|
bumble/codecs.py,sha256=Vc7FOo6d-6VCgDI0ibnLmX8vCZ4-jtX_-0vEUM-yOrI,15343
|
|
@@ -12,37 +12,37 @@ bumble/controller.py,sha256=5SVSyp8UsnXde5gAM5qF0QHLyyuHguShbNIL1rPlpHo,45114
|
|
|
12
12
|
bumble/core.py,sha256=s06LoynPDST5RgmaTjM3CYSbz9wKkoyjbc2jTmaH8rQ,52615
|
|
13
13
|
bumble/crypto.py,sha256=p0vF495jp9Kak5ILE11D2XaaHGiGuWLSutprglbOvHM,8543
|
|
14
14
|
bumble/decoder.py,sha256=N9nMvuVhuwpnfw7EDVuNe9uYY6B6c3RY2dh8RhRPC1U,9608
|
|
15
|
-
bumble/device.py,sha256=
|
|
15
|
+
bumble/device.py,sha256=usBavwHNTZRFBY62ngDEFG2O6-uAw_vZ1sedAZbmUac,124271
|
|
16
16
|
bumble/gap.py,sha256=axlOZIv99357Ehq2vMokeioU85z81qdQvplGn0pF70Q,2137
|
|
17
|
-
bumble/gatt.py,sha256=
|
|
18
|
-
bumble/gatt_client.py,sha256=
|
|
19
|
-
bumble/gatt_server.py,sha256=
|
|
17
|
+
bumble/gatt.py,sha256=PtyDIsswJrp8g96E_Hrp7HzvLe9DEv2YBO6yIEDQ5sg,28625
|
|
18
|
+
bumble/gatt_client.py,sha256=c7QzKx2sxJv7ZHq1cDsJSnnQpTPnuX9OZ-PNpmcwEKE,41493
|
|
19
|
+
bumble/gatt_server.py,sha256=pLkydlyqQTyi9g89-WH7tZTIVtOd8gi6bpUn2b-OzAU,36728
|
|
20
20
|
bumble/hci.py,sha256=UwiIwL-78O3aV1-vhcuirxlkFc9cjA9OAfpsnGOVOzM,220298
|
|
21
21
|
bumble/helpers.py,sha256=AgtOigAqQzzTEMMwHQJ2EO7dOTp77qhnyfHQ2LCDd-k,8896
|
|
22
22
|
bumble/hfp.py,sha256=iTanibth9cOH_8HWhHrDJg9cm9Wks0B_nI4jM3v3TX8,30508
|
|
23
23
|
bumble/host.py,sha256=WkKzZ1GnF6QduE9MoeYH0meC5jSeb2cYqx-ga1ffyhA,35891
|
|
24
24
|
bumble/keys.py,sha256=_ibaJ4CxM5zyxnTp7wnihIQSsEAEkCiPxx9qHsGA04Q,12601
|
|
25
|
-
bumble/l2cap.py,sha256=
|
|
25
|
+
bumble/l2cap.py,sha256=5pyQgeJxElIiEXSKVUvVPfVcXv1JRRoPY0ZmLPIrSpU,76332
|
|
26
26
|
bumble/link.py,sha256=dvb3fdmV8689A4rLhFVHHKMR3u4wQbUapFi-GeheK0M,20220
|
|
27
27
|
bumble/pairing.py,sha256=AGRUN1kf7IjE2yXepSjm8hmbR6JECJuUiTwuziORhq8,7335
|
|
28
28
|
bumble/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
29
|
bumble/rfcomm.py,sha256=OEQlpk1rnB2sIeliCPKdUTSSnQoxPYfdINWzCkGpVFw,33635
|
|
30
30
|
bumble/sdp.py,sha256=H8Ej7WXVFd7zbSo3DXjHhU7lcZF3YcwQMwkbAloZqdY,44805
|
|
31
|
-
bumble/smp.py,sha256=
|
|
31
|
+
bumble/smp.py,sha256=tHy7lKekfxgSJhZYFX7-Fi5A9ehigACYd8ICFMitd0w,70807
|
|
32
32
|
bumble/snoop.py,sha256=_QfF36eylBW6Snd-_KYOwKaGiM8i_Ed-B5XoFIPt3Dg,5631
|
|
33
|
-
bumble/utils.py,sha256=
|
|
33
|
+
bumble/utils.py,sha256=qFCH33fqUH93URv48YZv9YDoOmVG7FzYIe_PWtMrHXo,13060
|
|
34
34
|
bumble/apps/README.md,sha256=XTwjRAY-EJWDXpl1V8K3Mw8B7kIqzUIUizRjVBVhoIE,1769
|
|
35
35
|
bumble/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
36
|
bumble/apps/bench.py,sha256=Nm22A1PjrJFgjfHCTyw3crlMJFHRwyVgADa2uKtJgbE,38542
|
|
37
|
-
bumble/apps/console.py,sha256=
|
|
38
|
-
bumble/apps/controller_info.py,sha256=
|
|
37
|
+
bumble/apps/console.py,sha256=Qgv0OYwfriQHyoZ0Wj7qwrpnKzG4juRHkgvp0VdVpec,46069
|
|
38
|
+
bumble/apps/controller_info.py,sha256=uMIKrTgfFyLBQ8yl6ymy06QgQVOtilNgmXjvZJ38LbY,6541
|
|
39
39
|
bumble/apps/controllers.py,sha256=R6XJ1XpyuXlyqSCmI7PromVIcoYTcYfpmO-TqTYXnUI,2326
|
|
40
40
|
bumble/apps/gatt_dump.py,sha256=-dCvCgjuHAp0h1zxm-gmqB4lVlSdas1Kp4cpzzx4gGw,4245
|
|
41
41
|
bumble/apps/gg_bridge.py,sha256=G32XdCqYzt8Kqv9jZZDY29-ENjOxDMX_7CPolA8Jm1U,14555
|
|
42
42
|
bumble/apps/hci_bridge.py,sha256=KISv352tKnsQsoxjkDiCQbMFmhnPWdnug5wSFAAXxEs,4033
|
|
43
43
|
bumble/apps/l2cap_bridge.py,sha256=u0KREGRlH6bZtOr_yeOQWY3FJAuE6Y6it__tOevkbT8,12684
|
|
44
44
|
bumble/apps/pair.py,sha256=NVoc50KOMrCgdrs6tRkmWwucuUarV0lMpWJUVHzc6lQ,15601
|
|
45
|
-
bumble/apps/pandora_server.py,sha256=
|
|
45
|
+
bumble/apps/pandora_server.py,sha256=5qaoLCpcZE2KsGO21-7t6Vg4dBjBWbnyOQXwrLhxkuE,1397
|
|
46
46
|
bumble/apps/scan.py,sha256=_fMG_1j1HZQ_8SrJ0ZOxJaWB1OR4mKBaZuQMgm8viYI,7439
|
|
47
47
|
bumble/apps/show.py,sha256=lvy-_u_fRlswBRAd_peXbR9QQFC_NEaygRYFicmAGSg,4738
|
|
48
48
|
bumble/apps/unbond.py,sha256=P_zqdK_WsQHReqnhcaeeG3Kg6LhGBzCrRAyhcewQ7lU,3175
|
|
@@ -55,7 +55,7 @@ bumble/apps/speaker/logo.svg,sha256=SQ9XXIqhh07BnGaBZ653nJ7vOslm2Dogdqadhg2MdE4,
|
|
|
55
55
|
bumble/apps/speaker/speaker.css,sha256=nyM5TjzDYkkLwFzsaIOuTSngzSvgDnkLe0Z-fAn1_t4,1234
|
|
56
56
|
bumble/apps/speaker/speaker.html,sha256=kfAZ5oZSFc9ygBFIUuZEn5LUNQnHBvrnuHU6VAptyiU,1188
|
|
57
57
|
bumble/apps/speaker/speaker.js,sha256=DrT831yg3oBXKZ5usnfZjRU9X6Nw3zjIWSkz6sIgVtw,9373
|
|
58
|
-
bumble/apps/speaker/speaker.py,sha256=
|
|
58
|
+
bumble/apps/speaker/speaker.py,sha256=CeNj3AKl0USBacST2J950cwYg0bv_pD7nZFrngqYoTo,24212
|
|
59
59
|
bumble/drivers/__init__.py,sha256=KYfHln8_kyF66qylE4AFPZ-KXzmnE1raXETwmnUe-TY,3014
|
|
60
60
|
bumble/drivers/rtk.py,sha256=XhxXdpVEQGOrW3N1Us3IxmttsALL2gquerFYDtE4tQo,21072
|
|
61
61
|
bumble/pandora/__init__.py,sha256=5NBVmndeTulANawift0jPT9ISp562wyIHTZ-4uP34Mg,3283
|
|
@@ -63,7 +63,7 @@ bumble/pandora/config.py,sha256=Uq0H_YIF7o7BrJsqPeH-e1BAgBt4IwGNt8Ce_ziTu_k,2353
|
|
|
63
63
|
bumble/pandora/device.py,sha256=pnnfPsA0NePCP__chn79YV7YyUBf0daF-Vr45Upfv-s,5309
|
|
64
64
|
bumble/pandora/host.py,sha256=C-IIvKyphM87xCnfWadQZOR92pt2mWSNIdwaF4E_vIo,33417
|
|
65
65
|
bumble/pandora/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
66
|
-
bumble/pandora/security.py,sha256=
|
|
66
|
+
bumble/pandora/security.py,sha256=RB4JVUlpwb2pbAbqhFChmduJUUC7aP1LJ1fhzXdIp7I,22015
|
|
67
67
|
bumble/pandora/utils.py,sha256=2s2oZXbsVz16sYVyfiWE8ehOMHCIU5gIUAChSTULrW8,3914
|
|
68
68
|
bumble/profiles/__init__.py,sha256=yBGC8Ti5LvZuoh1F42XtfrBilb39T77_yuxESZeX2yI,581
|
|
69
69
|
bumble/profiles/asha_service.py,sha256=C7-xwG1DgdPbt6IKLglIP1VYAg1X5s61YPAiOzHHgVw,7089
|
|
@@ -76,9 +76,9 @@ bumble/tools/generate_company_id_list.py,sha256=8iG8DX9oGK13UGV9aRn5MULg4FOyKpI1
|
|
|
76
76
|
bumble/tools/rtk_fw_download.py,sha256=kTxR9UaNiD8VtzWVAVxsCx5c_tkfz9OKe-vxdlfKQRY,5454
|
|
77
77
|
bumble/tools/rtk_util.py,sha256=TwZhupHQrQYsYHLdRGyzXKd24pwCk8kkzqK1Rj2guco,5087
|
|
78
78
|
bumble/transport/__init__.py,sha256=Yz2aau52Hi0B6sh06WtfJzMwOYr1Y5VlqjkJCw4hcbA,5901
|
|
79
|
-
bumble/transport/android_emulator.py,sha256=
|
|
80
|
-
bumble/transport/android_netsim.py,sha256=
|
|
81
|
-
bumble/transport/common.py,sha256=
|
|
79
|
+
bumble/transport/android_emulator.py,sha256=n6nPti0eb6JqPkAj5-fdtiMfSzA2Hgd2q4B1arudIhM,4333
|
|
80
|
+
bumble/transport/android_netsim.py,sha256=SVh-IUZ2bhcIESZFGzOsofybsi4H0qoBRwBieeqUINE,16215
|
|
81
|
+
bumble/transport/common.py,sha256=WRAj8VPUGHx_xys1-vLfVXRcTO84pSSbRYwBdBERg1Q,15605
|
|
82
82
|
bumble/transport/file.py,sha256=eVM2V6Nk2nDAFdE7Rt01ZI3JdTovsH9OEU1gKYPJjpE,2010
|
|
83
83
|
bumble/transport/hci_socket.py,sha256=y9hrIY7QIgP994lffJHaAi2jfpC9FCANhzHO5F6k3vk,6377
|
|
84
84
|
bumble/transport/pty.py,sha256=grTl-yvjMWHflNwuME4ccVqDbk6NIEgQMgH6Y9lf1fU,2732
|
|
@@ -90,7 +90,7 @@ bumble/transport/tcp_server.py,sha256=hixsSzB-fmzR1yuiHDWd1WhqAia3UA4Cog1Wu6DCLe
|
|
|
90
90
|
bumble/transport/udp.py,sha256=di8I6HHACgBx3un-dzAahz9lTIUrh4LdeuYpeoifQEM,2239
|
|
91
91
|
bumble/transport/usb.py,sha256=GJ1CmQMarg6C6Lp0PfGZJ4Erp-7ucBf9IWSeBkjvC3g,21115
|
|
92
92
|
bumble/transport/vhci.py,sha256=iI2WpighnvIP5zeyJUFSbjEdmCo24CWMdICamIcyJck,2250
|
|
93
|
-
bumble/transport/ws_client.py,sha256=
|
|
93
|
+
bumble/transport/ws_client.py,sha256=9gqm5jlVT_H6LfwsQwPpky07CINhgOK96ef53SMAxms,1757
|
|
94
94
|
bumble/transport/ws_server.py,sha256=goe4xx7OnZiJy1a00Bg0CXM8uJhsGXbsijMYq2n62bI,3328
|
|
95
95
|
bumble/transport/grpc_protobuf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
96
96
|
bumble/transport/grpc_protobuf/common_pb2.py,sha256=ayV7r0mOrBnjY8E3Qk0_-7OMcrngS-V7v3rHz6ECUqE,1028
|
|
@@ -125,9 +125,9 @@ bumble/vendor/android/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
|
|
|
125
125
|
bumble/vendor/android/hci.py,sha256=GZrkhaWmcMt1JpnRhv0NoySGkf2H4lNUV2f_omRZW0I,10741
|
|
126
126
|
bumble/vendor/zephyr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
127
127
|
bumble/vendor/zephyr/hci.py,sha256=d83bC0TvT947eN4roFjLkQefWtHOoNsr4xib2ctSkvA,3195
|
|
128
|
-
bumble-0.0.
|
|
129
|
-
bumble-0.0.
|
|
130
|
-
bumble-0.0.
|
|
131
|
-
bumble-0.0.
|
|
132
|
-
bumble-0.0.
|
|
133
|
-
bumble-0.0.
|
|
128
|
+
bumble-0.0.172.dist-info/LICENSE,sha256=FvaYh4NRWIGgS_OwoBs5gFgkCmAghZ-DYnIGBZPuw-s,12142
|
|
129
|
+
bumble-0.0.172.dist-info/METADATA,sha256=3VMmZ_S-4QKiANRqX_bJCAbx3MH4_JsFyEzo3htiHM8,5463
|
|
130
|
+
bumble-0.0.172.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
|
131
|
+
bumble-0.0.172.dist-info/entry_points.txt,sha256=AjCwgm9SvZDOhV7T6jWwAhWdE728pd759LQCscMLjnM,765
|
|
132
|
+
bumble-0.0.172.dist-info/top_level.txt,sha256=tV6JJKaHPYMFiJYiBYFW24PCcfLxTJZdlu6BmH3Cb00,7
|
|
133
|
+
bumble-0.0.172.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|