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.
@@ -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 find_grpc_port() -> int:
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 / 'netsim.ini'
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 / 'netsim.ini'
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
- if not publish_grpc_port(server_port):
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
- async def send():
191
- await self.context.write(
192
- PacketResponse(
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 on_packet(self, packet):
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 won't already have a device
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
- class GrpcServerTransport(Transport):
265
- async def close(self):
266
- await super().close()
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 GrpcServerTransport(server, server)
298
+ return await open_android_netsim_host_transport_with_channel(
299
+ channel,
300
+ options,
301
+ )
269
302
 
270
303
 
271
304
  # -----------------------------------------------------------------------------
272
- async def open_android_netsim_host_transport(server_host, server_port, options):
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
- elif response_type == 'hci_packet':
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
- transport = PumpedTransport(
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, port = params[0].split(':')
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 open_android_netsim_host_transport(host, port, options)
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')
@@ -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.exceptions.CancelledError:
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.exceptions.CancelledError:
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):
@@ -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
- <remote-host>:<remote-port>
34
+ <websocket-url>
35
35
 
36
- Example: 127.0.0.1:9001
36
+ Example: ws://localhost:7681/v1/websocket/bt
37
37
  '''
38
38
 
39
- remote_host, remote_port = spec.split(':')
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
- transport = PumpedTransport(
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 Awaitable, Set, TypeVar
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.169
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=JoAEk66EQxYMgNRR-NudWug9z1H-JKbAuTitWii1V4o,164
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=qXwC5gtC-4Bwjqul0sCm_bWA_BbK4NVf5xNlOCRE7LI,30231
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=QXLC8EKSkCBzxegn40UfzWXFMCzCcJKLJ5SzKXNl9BM,124164
15
+ bumble/device.py,sha256=usBavwHNTZRFBY62ngDEFG2O6-uAw_vZ1sedAZbmUac,124271
16
16
  bumble/gap.py,sha256=axlOZIv99357Ehq2vMokeioU85z81qdQvplGn0pF70Q,2137
17
- bumble/gatt.py,sha256=HwjxuFjuH0LvRyP4HUfslPd-qZ0P17e5Xx8E9usehz0,28295
18
- bumble/gatt_client.py,sha256=0NrwW7Y3oy1HUWisIyBLwnBpYNnuI9jtbcNkS6CD__U,40297
19
- bumble/gatt_server.py,sha256=vi_-sofp5NjPWDYX5K_hldsAaoFuNlt8GFA-eVzFkb8,35277
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=TxZhWvwFFTfKEvPsdxmVvWUCoED7MvOzIBi9uAS21ZA,77186
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=ooPMo2CJFBGiqe-qgmnTvp41NopxKPO2gZiQXT5ILi4,70303
31
+ bumble/smp.py,sha256=tHy7lKekfxgSJhZYFX7-Fi5A9ehigACYd8ICFMitd0w,70807
32
32
  bumble/snoop.py,sha256=_QfF36eylBW6Snd-_KYOwKaGiM8i_Ed-B5XoFIPt3Dg,5631
33
- bumble/utils.py,sha256=x7AKW6QD35Dl1OkROLJ6TX4dNN6ajwFjW8w_BZGGcA4,10195
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=Zko0fw4ByYsdGDOrtrXLX0id001Rq45nVImdYHxNKsQ,46055
38
- bumble/apps/controller_info.py,sha256=h6tBnpVe02vvoF-cyrdvE4DqGcxSfE5jcEuvCKMlr_g,6507
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=MwcUaw0dfcS9D7pnaoNdb2oFSpLlGEh3we1IyPg-xS0,1321
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=jGdNorAUK0LMEScKGPxF-uAOY39lHmlywMBccoB39LI,24224
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=VmHLqSB7bYL7Wupz_389Z_zRviXLB9475dT6NMyvCX4,20754
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=RXUpb-mik5FP8FX1p8Sxcq-wiAxBUqfvyMtzmu4fbCg,4217
80
- bumble/transport/android_netsim.py,sha256=nynQ9KNdokW2fUN9-O-AFbX7UA6a18a5Rj76NxTWb94,14908
81
- bumble/transport/common.py,sha256=2f_srefsEUttkqpGu4jRr5F8RX8hqZyY0x-3Ymmk9QM,15745
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=kLH7sqCab82sANTNE0SCtdD5UsffQ8Isl2v0Inz0DB0,1727
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.169.dist-info/LICENSE,sha256=FvaYh4NRWIGgS_OwoBs5gFgkCmAghZ-DYnIGBZPuw-s,12142
129
- bumble-0.0.169.dist-info/METADATA,sha256=YAgUahaaa2GAyTbAlqzHBfzta9ROtLvatLAVNVQV-MY,5394
130
- bumble-0.0.169.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
131
- bumble-0.0.169.dist-info/entry_points.txt,sha256=AjCwgm9SvZDOhV7T6jWwAhWdE728pd759LQCscMLjnM,765
132
- bumble-0.0.169.dist-info/top_level.txt,sha256=tV6JJKaHPYMFiJYiBYFW24PCcfLxTJZdlu6BmH3Cb00,7
133
- bumble-0.0.169.dist-info/RECORD,,
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,,