forked from auracaster/bumble_mirror
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7e4948d9ef | |||
| 32d448edf3 | |||
| 3d615b13ce | |||
| 1ad92dc759 | |||
| aacfd4328c | |||
| 6aa1f5211c | |||
| df8e454ee5 | |||
| aec50ac616 | |||
| 6a3eaa457f | |||
| 6e6b4cd4b2 | |||
| aa1d7933da | |||
| 34e0f293c2 | |||
| 85215df2c3 | |||
| f8223ca81f | |||
| 2b0b1ad726 | |||
| 58debcd8bb | |||
| 6eba81e3dd | |||
| 8a5f6a61d5 |
+3
-1
@@ -41,6 +41,7 @@ import bumble.transport
|
|||||||
import bumble.utils
|
import bumble.utils
|
||||||
from bumble import company_ids, core, data_types, gatt, hci
|
from bumble import company_ids, core, data_types, gatt, hci
|
||||||
from bumble.audio import io as audio_io
|
from bumble.audio import io as audio_io
|
||||||
|
from bumble.audio import io_asrc as audio_io_asrc
|
||||||
from bumble.colors import color
|
from bumble.colors import color
|
||||||
from bumble.profiles import bap, bass, le_audio, pbp
|
from bumble.profiles import bap, bass, le_audio, pbp
|
||||||
|
|
||||||
@@ -891,7 +892,8 @@ async def run_transmit(
|
|||||||
print('Start Periodic Advertising')
|
print('Start Periodic Advertising')
|
||||||
await advertising_set.start_periodic()
|
await advertising_set.start_periodic()
|
||||||
|
|
||||||
audio_input = await audio_io.create_audio_input(input, input_format)
|
#audio_input = await audio_io.create_audio_input(input, input_format)
|
||||||
|
audio_input = audio_io_asrc.SoundDeviceAudioInputAsrc(input[7:], input_format)
|
||||||
pcm_format = await audio_input.open()
|
pcm_format = await audio_input.open()
|
||||||
# This try should be replaced with contextlib.aclosing() when python 3.9 is no
|
# This try should be replaced with contextlib.aclosing() when python 3.9 is no
|
||||||
# longer needed.
|
# longer needed.
|
||||||
|
|||||||
@@ -0,0 +1,339 @@
|
|||||||
|
# Copyright 2025
|
||||||
|
#
|
||||||
|
# Drop-in replacement for `SoundDeviceAudioInput` that adds a tiny ASRC stage.
|
||||||
|
#
|
||||||
|
# Constraints per request:
|
||||||
|
# - Only import io_bumble.py at module level.
|
||||||
|
# - Reuse the ASRC functionality from asrc.py conceptually (PI control + FIFO +
|
||||||
|
# linear/sinc resampling behavior). We implement a minimal, dependency-free
|
||||||
|
# variant (linear interpolation with a small PI loop) so this module does not
|
||||||
|
# import anything else at top-level.
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# - Input stream is captured via sounddevice (imported lazily inside methods).
|
||||||
|
# - Input is mono float32 for simplicity; output matches the original class
|
||||||
|
# signature: INT16, stereo, at the same nominal sample rate as requested.
|
||||||
|
|
||||||
|
from .io import PcmFormat, ThreadedAudioInput, logger # only top-level import
|
||||||
|
|
||||||
|
|
||||||
|
class SoundDeviceAudioInputAsrc(ThreadedAudioInput):
|
||||||
|
"""Sound device audio input with a simple ASRC stage.
|
||||||
|
|
||||||
|
Interface-compatible with `io_bumble.SoundDeviceAudioInput`:
|
||||||
|
- __init__(device_name: str, pcm_format: PcmFormat)
|
||||||
|
- _open() -> PcmFormat
|
||||||
|
- _read(frame_size: int) -> bytes
|
||||||
|
- _close() -> None
|
||||||
|
|
||||||
|
Behavior:
|
||||||
|
- Captures mono float32 frames from the device.
|
||||||
|
- Buffers into an internal ring buffer.
|
||||||
|
- Produces stereo INT16 frames using a linear-interp resampler whose
|
||||||
|
ratio is adjusted by a tiny PI loop to hold FIFO depth near a target.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, device_name: str, pcm_format: str) -> None:
|
||||||
|
super().__init__()
|
||||||
|
# Device & format
|
||||||
|
self._device = int(device_name) if device_name else None
|
||||||
|
pcm_format: PcmFormat | None
|
||||||
|
if pcm_format == 'auto':
|
||||||
|
pcm_format = None
|
||||||
|
else:
|
||||||
|
pcm_format = PcmFormat.from_str(pcm_format)
|
||||||
|
self._pcm_format_in = pcm_format
|
||||||
|
# We always output stereo INT16 at the same nominal sample rate.
|
||||||
|
self._pcm_format_out = PcmFormat(
|
||||||
|
PcmFormat.Endianness.LITTLE,
|
||||||
|
PcmFormat.SampleType.INT16,
|
||||||
|
pcm_format.sample_rate,
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
|
||||||
|
# sounddevice stream (created in _open)
|
||||||
|
self._stream = None # type: ignore[assignment]
|
||||||
|
|
||||||
|
# --- ASRC state (inspired by asrc.py) ---
|
||||||
|
# Nominal input/output rate ratio
|
||||||
|
self._r = 1.0
|
||||||
|
self._integral = 0.0
|
||||||
|
self._phi = 0.0 # fractional read position within current chunk
|
||||||
|
|
||||||
|
# PI gains (tiny to avoid warble)
|
||||||
|
self._Kp = 2e-6
|
||||||
|
self._Ki = 5e-8
|
||||||
|
self._R0 = 1.0
|
||||||
|
|
||||||
|
# Target FIFO level and deadband (≈10 ms target, 0.5 ms deadband)
|
||||||
|
fs = float(self._pcm_format_in.sample_rate)
|
||||||
|
self._target_samples = max(1, int(0.010 * fs))
|
||||||
|
self._deadband = max(1, int(0.0005 * fs))
|
||||||
|
|
||||||
|
# Ring buffer for mono float32 samples
|
||||||
|
# Capacity ~2 seconds for headroom
|
||||||
|
self._rb_cap = max(self._target_samples * 32, int(2 * fs))
|
||||||
|
self._rb = None # created in _init_rb()
|
||||||
|
self._ridx = 0
|
||||||
|
self._size = 0
|
||||||
|
self._lock = None # created in _init_rb()
|
||||||
|
self._init_rb()
|
||||||
|
|
||||||
|
# Light logging timer
|
||||||
|
self._last_log = 0.0
|
||||||
|
|
||||||
|
# Streaming resampler and internal output buffer (lazy init)
|
||||||
|
self._rs = None # samplerate.Resampler
|
||||||
|
self._out_buf = None # numpy.ndarray float32
|
||||||
|
|
||||||
|
# ---------------- Internal helpers -----------------
|
||||||
|
def _init_rb(self) -> None:
|
||||||
|
# Lazy import standard libs to keep only io_bumble imported at top level
|
||||||
|
import threading
|
||||||
|
from array import array
|
||||||
|
|
||||||
|
self._rb = array('f', [0.0] * self._rb_cap) # float32 ring buffer
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
self._ridx = 0
|
||||||
|
self._size = 0
|
||||||
|
|
||||||
|
def _fifo_len(self) -> int:
|
||||||
|
with self._lock:
|
||||||
|
return self._size
|
||||||
|
|
||||||
|
def _fifo_write(self, x_f32) -> None:
|
||||||
|
# x_f32: 1-D float32-like iterable
|
||||||
|
k = len(x_f32)
|
||||||
|
if k <= 0:
|
||||||
|
return
|
||||||
|
rb = self._rb
|
||||||
|
if rb is None:
|
||||||
|
return
|
||||||
|
with self._lock:
|
||||||
|
# Trim if larger than capacity: keep last N
|
||||||
|
if k >= self._rb_cap:
|
||||||
|
x_f32 = x_f32[-self._rb_cap:]
|
||||||
|
k = self._rb_cap
|
||||||
|
# Make room on overflow (drop oldest)
|
||||||
|
excess = max(0, self._size + k - self._rb_cap)
|
||||||
|
if excess:
|
||||||
|
self._ridx = (self._ridx + excess) % self._rb_cap
|
||||||
|
self._size -= excess
|
||||||
|
# Write at tail position
|
||||||
|
wpos = (self._ridx + self._size) % self._rb_cap
|
||||||
|
first = min(k, self._rb_cap - wpos)
|
||||||
|
# Write first chunk
|
||||||
|
from array import array as _array # lazy import
|
||||||
|
rb[wpos:wpos + first] = _array('f', x_f32[:first])
|
||||||
|
# Wrap if needed
|
||||||
|
second = k - first
|
||||||
|
if second:
|
||||||
|
rb[0:second] = _array('f', x_f32[first:])
|
||||||
|
self._size += k
|
||||||
|
|
||||||
|
def _fifo_peek_array(self, n: int):
|
||||||
|
# Returns a Python list[float] copy of up to n samples
|
||||||
|
rb = self._rb
|
||||||
|
if rb is None:
|
||||||
|
return []
|
||||||
|
m = max(0, min(n, self._fifo_len()))
|
||||||
|
if m <= 0:
|
||||||
|
return []
|
||||||
|
pos = self._ridx
|
||||||
|
first = min(m, self._rb_cap - pos)
|
||||||
|
# Copy out
|
||||||
|
out = [0.0] * m
|
||||||
|
# First chunk
|
||||||
|
out[:first] = rb[pos:pos + first]
|
||||||
|
# Second chunk if wrap
|
||||||
|
second = m - first
|
||||||
|
if second > 0:
|
||||||
|
out[first:] = rb[0:second]
|
||||||
|
return out
|
||||||
|
|
||||||
|
def _fifo_discard(self, n: int) -> None:
|
||||||
|
with self._lock:
|
||||||
|
d = max(0, min(n, self._size))
|
||||||
|
self._ridx = (self._ridx + d) % self._rb_cap
|
||||||
|
self._size -= d
|
||||||
|
|
||||||
|
def _update_ratio(self) -> None:
|
||||||
|
# PI loop to hold buffer near target
|
||||||
|
e = self._target_samples - self._fifo_len()
|
||||||
|
if -self._deadband <= e <= self._deadband:
|
||||||
|
e = 0.0
|
||||||
|
cand_integral = self._integral + e
|
||||||
|
r_unclamped = self._R0 * (1.0 + self._Kp * e + self._Ki * cand_integral)
|
||||||
|
# Limit to ±1000 ppm vs nominal
|
||||||
|
ppm_unclamped = 1e6 * (r_unclamped / self._R0 - 1.0)
|
||||||
|
saturated_high = ppm_unclamped > 1000.0
|
||||||
|
saturated_low = ppm_unclamped < -1000.0
|
||||||
|
if saturated_high:
|
||||||
|
self._r = self._R0 * (1 + 1000e-6)
|
||||||
|
if e <= 0:
|
||||||
|
self._integral = cand_integral
|
||||||
|
self._integral *= 0.99
|
||||||
|
elif saturated_low:
|
||||||
|
self._r = self._R0 * (1 - 1000e-6)
|
||||||
|
if e >= 0:
|
||||||
|
self._integral = cand_integral
|
||||||
|
self._integral *= 0.99
|
||||||
|
else:
|
||||||
|
self._integral = cand_integral
|
||||||
|
self._r = r_unclamped
|
||||||
|
|
||||||
|
# Occasional log
|
||||||
|
try:
|
||||||
|
import time as _time
|
||||||
|
now = _time.time()
|
||||||
|
if now - self._last_log > 1.0:
|
||||||
|
buf_ms = 1000.0 * self._fifo_len() / float(self._pcm_format_in.sample_rate)
|
||||||
|
print(
|
||||||
|
f"\nASRC buf={buf_ms:5.1f} ms r={self._r:.9f} corr={1e6 * (self._r / self._R0 - 1.0):+7.1f} ppm"
|
||||||
|
)
|
||||||
|
self._last_log = now
|
||||||
|
except Exception:
|
||||||
|
# Logging must never break audio
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _process(self, n_out: int) -> list[float]:
|
||||||
|
# Accumulate at least n_out samples using samplerate.Resampler
|
||||||
|
if n_out <= 0:
|
||||||
|
return []
|
||||||
|
# Lazy imports
|
||||||
|
import numpy as np # type: ignore
|
||||||
|
|
||||||
|
# Lazy init output buffer
|
||||||
|
if self._out_buf is None:
|
||||||
|
self._out_buf = np.zeros(0, dtype=np.float32)
|
||||||
|
|
||||||
|
# Choose chunk so we don't take too much from FIFO each time
|
||||||
|
max_chunk = max(256, int(np.ceil(n_out / max(1e-9, self._r))))
|
||||||
|
safety_iters = 0
|
||||||
|
while self._out_buf.size < n_out and safety_iters < 16:
|
||||||
|
safety_iters += 1
|
||||||
|
available = self._fifo_len()
|
||||||
|
if available <= 0:
|
||||||
|
break
|
||||||
|
take = min(available, max_chunk)
|
||||||
|
x = self._fifo_peek_array(take)
|
||||||
|
self._fifo_discard(take)
|
||||||
|
if not x:
|
||||||
|
break
|
||||||
|
x_arr = np.asarray(x, dtype=np.float32)
|
||||||
|
if self._rs is not None:
|
||||||
|
try:
|
||||||
|
y = self._rs.process(x_arr, ratio=float(self._r), end_of_input=False)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("ASRC resampler error")
|
||||||
|
y = None
|
||||||
|
else:
|
||||||
|
y = None
|
||||||
|
if y is not None and getattr(y, 'size', 0):
|
||||||
|
y = y.astype(np.float32, copy=False)
|
||||||
|
if self._out_buf.size == 0:
|
||||||
|
self._out_buf = y
|
||||||
|
else:
|
||||||
|
self._out_buf = np.concatenate((self._out_buf, y))
|
||||||
|
|
||||||
|
if self._out_buf.size >= n_out:
|
||||||
|
out = self._out_buf[:n_out]
|
||||||
|
self._out_buf = self._out_buf[n_out:]
|
||||||
|
return out.tolist()
|
||||||
|
else:
|
||||||
|
# Not enough data produced; pad with zeros
|
||||||
|
out = np.zeros(n_out, dtype=np.float32)
|
||||||
|
if self._out_buf.size:
|
||||||
|
out[: self._out_buf.size] = self._out_buf
|
||||||
|
self._out_buf = np.zeros(0, dtype=np.float32)
|
||||||
|
return out.tolist()
|
||||||
|
|
||||||
|
def _mono_to_stereo_int16_bytes(self, mono_f32: list[float]) -> bytes:
|
||||||
|
# Convert [-1,1] float list to stereo int16 little-endian bytes
|
||||||
|
import struct
|
||||||
|
ba = bytearray()
|
||||||
|
for v in mono_f32:
|
||||||
|
# clip
|
||||||
|
if v > 1.0:
|
||||||
|
v = 1.0
|
||||||
|
elif v < -1.0:
|
||||||
|
v = -1.0
|
||||||
|
i16 = int(v * 32767.0)
|
||||||
|
ba += struct.pack('<hh', i16, i16)
|
||||||
|
return bytes(ba)
|
||||||
|
|
||||||
|
# ---------------- ThreadedAudioInput hooks -----------------
|
||||||
|
def _open(self) -> PcmFormat:
|
||||||
|
# Set up sounddevice RawInputStream (int16) and start callback producer
|
||||||
|
import sounddevice # pylint: disable=import-error
|
||||||
|
import math
|
||||||
|
import samplerate as sr # type: ignore
|
||||||
|
|
||||||
|
# We capture mono regardless of requested channels, then output stereo.
|
||||||
|
channels = 1
|
||||||
|
samplerate = int(self._pcm_format_in.sample_rate)
|
||||||
|
|
||||||
|
def _callback(indata, frames, time_info, status): # noqa: ARG001 (signature is fixed)
|
||||||
|
# indata: raw int16 bytes-like buffer of shape (frames, channels)
|
||||||
|
try:
|
||||||
|
if status:
|
||||||
|
logger.warning("Input status: %s", status)
|
||||||
|
if frames <= 0:
|
||||||
|
return
|
||||||
|
# Interpret raw bytes as little-endian int16 mono
|
||||||
|
mv = memoryview(indata).cast('h') # len == frames * channels
|
||||||
|
# Convert to float in [-1, 1]
|
||||||
|
# Avoid division errors; protect NaN/Inf
|
||||||
|
mono = []
|
||||||
|
for i in range(frames):
|
||||||
|
v = mv[i]
|
||||||
|
f = float(v) / 32768.0
|
||||||
|
if not (f == f) or math.isinf(f):
|
||||||
|
f = 0.0
|
||||||
|
mono.append(f)
|
||||||
|
self._fifo_write(mono)
|
||||||
|
except Exception: # never let callback raise
|
||||||
|
logger.exception("Audio input callback error")
|
||||||
|
|
||||||
|
# Create streaming resampler (mono)
|
||||||
|
try:
|
||||||
|
self._rs = sr.Resampler(converter_type="sinc_fastest", channels=1)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Failed to create samplerate.Resampler; audio may be silent")
|
||||||
|
self._rs = None
|
||||||
|
|
||||||
|
self._stream = sounddevice.RawInputStream(
|
||||||
|
samplerate=samplerate,
|
||||||
|
device=self._device,
|
||||||
|
channels=channels,
|
||||||
|
dtype='int16',
|
||||||
|
callback=_callback,
|
||||||
|
)
|
||||||
|
self._stream.start()
|
||||||
|
|
||||||
|
return self._pcm_format_out
|
||||||
|
|
||||||
|
def _read(self, frame_size: int) -> bytes:
|
||||||
|
# Produce 'frame_size' output frames (stereo INT16)
|
||||||
|
if frame_size <= 0:
|
||||||
|
return b''
|
||||||
|
# Update resampling ratio based on FIFO level
|
||||||
|
try:
|
||||||
|
self._update_ratio()
|
||||||
|
except Exception:
|
||||||
|
# keep going even if update failed
|
||||||
|
pass
|
||||||
|
# Process mono float32
|
||||||
|
mono = self._process(frame_size)
|
||||||
|
# Convert to stereo int16 LE bytes
|
||||||
|
return self._mono_to_stereo_int16_bytes(mono)
|
||||||
|
|
||||||
|
def _close(self) -> None:
|
||||||
|
try:
|
||||||
|
if self._stream is not None:
|
||||||
|
self._stream.stop()
|
||||||
|
self._stream.close()
|
||||||
|
except Exception:
|
||||||
|
logger.exception('Error closing input stream')
|
||||||
|
finally:
|
||||||
|
self._stream = None
|
||||||
+79
-45
@@ -2169,10 +2169,12 @@ def with_connection_from_handle(function):
|
|||||||
# Decorator that converts the first argument from a bluetooth address to a connection
|
# Decorator that converts the first argument from a bluetooth address to a connection
|
||||||
def with_connection_from_address(function):
|
def with_connection_from_address(function):
|
||||||
@functools.wraps(function)
|
@functools.wraps(function)
|
||||||
def wrapper(self, address: hci.Address, *args, **kwargs):
|
def wrapper(device: Device, address: hci.Address, *args, **kwargs):
|
||||||
for connection in self.connections.values():
|
if connection := device.pending_connections.get(address):
|
||||||
|
return function(device, connection, *args, **kwargs)
|
||||||
|
for connection in device.connections.values():
|
||||||
if connection.peer_address == address:
|
if connection.peer_address == address:
|
||||||
return function(self, connection, *args, **kwargs)
|
return function(device, connection, *args, **kwargs)
|
||||||
raise ObjectLookupError('no connection for address')
|
raise ObjectLookupError('no connection for address')
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
@@ -2182,11 +2184,13 @@ def with_connection_from_address(function):
|
|||||||
# connection
|
# connection
|
||||||
def try_with_connection_from_address(function):
|
def try_with_connection_from_address(function):
|
||||||
@functools.wraps(function)
|
@functools.wraps(function)
|
||||||
def wrapper(self, address, *args, **kwargs):
|
def wrapper(device: Device, address: hci.Address, *args, **kwargs):
|
||||||
for connection in self.connections.values():
|
if connection := device.pending_connections.get(address):
|
||||||
|
return function(device, connection, address, *args, **kwargs)
|
||||||
|
for connection in device.connections.values():
|
||||||
if connection.peer_address == address:
|
if connection.peer_address == address:
|
||||||
return function(self, connection, address, *args, **kwargs)
|
return function(device, connection, address, *args, **kwargs)
|
||||||
return function(self, None, address, *args, **kwargs)
|
return function(device, None, address, *args, **kwargs)
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
@@ -2234,7 +2238,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
scan_response_data: bytes
|
scan_response_data: bytes
|
||||||
cs_capabilities: ChannelSoundingCapabilities | None = None
|
cs_capabilities: ChannelSoundingCapabilities | None = None
|
||||||
connections: dict[int, Connection]
|
connections: dict[int, Connection]
|
||||||
connection_roles: dict[hci.Address, hci.Role]
|
pending_connections: dict[hci.Address, Connection]
|
||||||
classic_pending_accepts: dict[
|
classic_pending_accepts: dict[
|
||||||
hci.Address,
|
hci.Address,
|
||||||
list[asyncio.Future[Union[Connection, tuple[hci.Address, int, int]]]],
|
list[asyncio.Future[Union[Connection, tuple[hci.Address, int, int]]]],
|
||||||
@@ -2356,9 +2360,9 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
self.le_connecting = False
|
self.le_connecting = False
|
||||||
self.disconnecting = False
|
self.disconnecting = False
|
||||||
self.connections = {} # Connections, by connection handle
|
self.connections = {} # Connections, by connection handle
|
||||||
self.connection_roles = (
|
self.pending_connections = (
|
||||||
{}
|
{}
|
||||||
) # Local connection roles, by BD address (BR/EDR only)
|
) # Pending connections, by BD address (BR/EDR only)
|
||||||
self.sco_links = {} # ScoLinks, by connection handle (BR/EDR only)
|
self.sco_links = {} # ScoLinks, by connection handle (BR/EDR only)
|
||||||
self.cis_links = {} # CisLinks, by connection handle (LE only)
|
self.cis_links = {} # CisLinks, by connection handle (LE only)
|
||||||
self._pending_cis = {} # (CIS_ID, CIG_ID), by CIS_handle
|
self._pending_cis = {} # (CIS_ID, CIG_ID), by CIS_handle
|
||||||
@@ -3827,7 +3831,17 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Save pending connection
|
# Save pending connection
|
||||||
self.connection_roles[peer_address] = hci.Role.CENTRAL
|
self.pending_connections[peer_address] = Connection(
|
||||||
|
device=self,
|
||||||
|
handle=0,
|
||||||
|
transport=core.PhysicalTransport.BR_EDR,
|
||||||
|
self_address=self.public_address,
|
||||||
|
self_resolvable_address=None,
|
||||||
|
peer_address=peer_address,
|
||||||
|
peer_resolvable_address=None,
|
||||||
|
role=hci.Role.CENTRAL,
|
||||||
|
parameters=Connection.Parameters(0, 0, 0),
|
||||||
|
)
|
||||||
|
|
||||||
# TODO: allow passing other settings
|
# TODO: allow passing other settings
|
||||||
result = await self.send_command(
|
result = await self.send_command(
|
||||||
@@ -3880,7 +3894,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
self.le_connecting = False
|
self.le_connecting = False
|
||||||
self.connect_own_address_type = None
|
self.connect_own_address_type = None
|
||||||
else:
|
else:
|
||||||
self.connection_roles.pop(peer_address, None)
|
self.pending_connections.pop(peer_address, None)
|
||||||
|
|
||||||
async def accept(
|
async def accept(
|
||||||
self,
|
self,
|
||||||
@@ -3978,7 +3992,17 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
# Even if we requested a role switch in the hci.HCI_Accept_Connection_Request
|
# Even if we requested a role switch in the hci.HCI_Accept_Connection_Request
|
||||||
# command, this connection is still considered Peripheral until an eventual
|
# command, this connection is still considered Peripheral until an eventual
|
||||||
# role change event.
|
# role change event.
|
||||||
self.connection_roles[peer_address] = hci.Role.PERIPHERAL
|
self.pending_connections[peer_address] = Connection(
|
||||||
|
device=self,
|
||||||
|
handle=0,
|
||||||
|
transport=core.PhysicalTransport.BR_EDR,
|
||||||
|
self_address=self.public_address,
|
||||||
|
self_resolvable_address=None,
|
||||||
|
peer_address=peer_address,
|
||||||
|
peer_resolvable_address=None,
|
||||||
|
role=hci.Role.PERIPHERAL,
|
||||||
|
parameters=Connection.Parameters(0, 0, 0),
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Accept connection request
|
# Accept connection request
|
||||||
@@ -3996,7 +4020,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
finally:
|
finally:
|
||||||
self.remove_listener(self.EVENT_CONNECTION, on_connection)
|
self.remove_listener(self.EVENT_CONNECTION, on_connection)
|
||||||
self.remove_listener(self.EVENT_CONNECTION_FAILURE, on_connection_failure)
|
self.remove_listener(self.EVENT_CONNECTION_FAILURE, on_connection_failure)
|
||||||
self.connection_roles.pop(peer_address, None)
|
self.pending_connections.pop(peer_address, None)
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def connect_as_gatt(self, peer_address: Union[hci.Address, str]):
|
async def connect_as_gatt(self, peer_address: Union[hci.Address, str]):
|
||||||
@@ -4703,7 +4727,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
self, cis_acl_pairs: Sequence[tuple[int, Connection]]
|
self, cis_acl_pairs: Sequence[tuple[int, Connection]]
|
||||||
) -> list[CisLink]:
|
) -> list[CisLink]:
|
||||||
for cis_handle, acl_connection in cis_acl_pairs:
|
for cis_handle, acl_connection in cis_acl_pairs:
|
||||||
cis_id, cig_id = self._pending_cis.pop(cis_handle)
|
cis_id, cig_id = self._pending_cis[cis_handle]
|
||||||
self.cis_links[cis_handle] = CisLink(
|
self.cis_links[cis_handle] = CisLink(
|
||||||
device=self,
|
device=self,
|
||||||
acl_connection=acl_connection,
|
acl_connection=acl_connection,
|
||||||
@@ -4719,6 +4743,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def on_cis_establishment(cis_link: CisLink) -> None:
|
def on_cis_establishment(cis_link: CisLink) -> None:
|
||||||
|
self._pending_cis.pop(cis_link.handle)
|
||||||
if pending_future := pending_cis_establishments.get(cis_link.handle):
|
if pending_future := pending_cis_establishments.get(cis_link.handle):
|
||||||
pending_future.set_result(cis_link)
|
pending_future.set_result(cis_link)
|
||||||
|
|
||||||
@@ -5441,29 +5466,27 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
connection_handle: int,
|
connection_handle: int,
|
||||||
peer_address: hci.Address,
|
peer_address: hci.Address,
|
||||||
) -> None:
|
) -> None:
|
||||||
connection_role = self.connection_roles.pop(peer_address, hci.Role.PERIPHERAL)
|
if connection := self.pending_connections.pop(peer_address, None):
|
||||||
|
connection.handle = connection_handle
|
||||||
|
else:
|
||||||
|
# Create a new connection
|
||||||
|
connection = Connection(
|
||||||
|
device=self,
|
||||||
|
handle=connection_handle,
|
||||||
|
transport=PhysicalTransport.BR_EDR,
|
||||||
|
self_address=self.public_address,
|
||||||
|
self_resolvable_address=None,
|
||||||
|
peer_address=peer_address,
|
||||||
|
peer_resolvable_address=None,
|
||||||
|
role=hci.Role.PERIPHERAL,
|
||||||
|
parameters=Connection.Parameters(0.0, 0, 0.0),
|
||||||
|
)
|
||||||
|
|
||||||
logger.debug(
|
logger.debug('*** %s', connection)
|
||||||
f'*** Connection: [0x{connection_handle:04X}] '
|
|
||||||
f'{peer_address} {hci.HCI_Constant.role_name(connection_role)}'
|
|
||||||
)
|
|
||||||
if connection_handle in self.connections:
|
if connection_handle in self.connections:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
'new connection reuses the same handle as a previous connection'
|
'new connection reuses the same handle as a previous connection'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a new connection
|
|
||||||
connection = Connection(
|
|
||||||
device=self,
|
|
||||||
handle=connection_handle,
|
|
||||||
transport=PhysicalTransport.BR_EDR,
|
|
||||||
self_address=self.public_address,
|
|
||||||
self_resolvable_address=None,
|
|
||||||
peer_address=peer_address,
|
|
||||||
peer_resolvable_address=None,
|
|
||||||
role=connection_role,
|
|
||||||
parameters=Connection.Parameters(0.0, 0, 0.0),
|
|
||||||
)
|
|
||||||
self.connections[connection_handle] = connection
|
self.connections[connection_handle] = connection
|
||||||
|
|
||||||
self.emit(self.EVENT_CONNECTION, connection)
|
self.emit(self.EVENT_CONNECTION, connection)
|
||||||
@@ -5618,7 +5641,9 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
|
|
||||||
# FIXME: Explore a delegate-model for BR/EDR wait connection #56.
|
# FIXME: Explore a delegate-model for BR/EDR wait connection #56.
|
||||||
@host_event_handler
|
@host_event_handler
|
||||||
def on_connection_request(self, bd_addr, class_of_device, link_type):
|
def on_connection_request(
|
||||||
|
self, bd_addr: hci.Address, class_of_device: int, link_type: int
|
||||||
|
):
|
||||||
logger.debug(f'*** Connection request: {bd_addr}')
|
logger.debug(f'*** Connection request: {bd_addr}')
|
||||||
|
|
||||||
# Handle SCO request.
|
# Handle SCO request.
|
||||||
@@ -5647,7 +5672,17 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
# device configuration is set to accept any incoming connection
|
# device configuration is set to accept any incoming connection
|
||||||
elif self.classic_accept_any:
|
elif self.classic_accept_any:
|
||||||
# Save pending connection
|
# Save pending connection
|
||||||
self.connection_roles[bd_addr] = hci.Role.PERIPHERAL
|
self.pending_connections[bd_addr] = Connection(
|
||||||
|
device=self,
|
||||||
|
handle=0,
|
||||||
|
transport=core.PhysicalTransport.BR_EDR,
|
||||||
|
self_address=self.public_address,
|
||||||
|
self_resolvable_address=None,
|
||||||
|
peer_address=bd_addr,
|
||||||
|
peer_resolvable_address=None,
|
||||||
|
role=hci.Role.PERIPHERAL,
|
||||||
|
parameters=Connection.Parameters(0, 0, 0),
|
||||||
|
)
|
||||||
|
|
||||||
self.host.send_command_sync(
|
self.host.send_command_sync(
|
||||||
hci.HCI_Accept_Connection_Request_Command(
|
hci.HCI_Accept_Connection_Request_Command(
|
||||||
@@ -5958,7 +5993,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
@host_event_handler
|
@host_event_handler
|
||||||
@try_with_connection_from_address
|
@try_with_connection_from_address
|
||||||
def on_remote_name(
|
def on_remote_name(
|
||||||
self, connection: Connection, address: hci.Address, remote_name: bytes
|
self, connection: Optional[Connection], address: hci.Address, remote_name: bytes
|
||||||
):
|
):
|
||||||
# Try to decode the name
|
# Try to decode the name
|
||||||
try:
|
try:
|
||||||
@@ -5977,7 +6012,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
@host_event_handler
|
@host_event_handler
|
||||||
@try_with_connection_from_address
|
@try_with_connection_from_address
|
||||||
def on_remote_name_failure(
|
def on_remote_name_failure(
|
||||||
self, connection: Connection, address: hci.Address, error: int
|
self, connection: Optional[Connection], address: hci.Address, error: int
|
||||||
):
|
):
|
||||||
if connection:
|
if connection:
|
||||||
connection.emit(connection.EVENT_REMOTE_NAME_FAILURE, error)
|
connection.emit(connection.EVENT_REMOTE_NAME_FAILURE, error)
|
||||||
@@ -6409,21 +6444,20 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
|
|
||||||
# [Classic only]
|
# [Classic only]
|
||||||
@host_event_handler
|
@host_event_handler
|
||||||
@try_with_connection_from_address
|
@with_connection_from_address
|
||||||
def on_role_change(
|
def on_role_change(
|
||||||
self, connection: Connection, peer_address: hci.Address, new_role: hci.Role
|
self,
|
||||||
|
connection: Connection,
|
||||||
|
new_role: hci.Role,
|
||||||
):
|
):
|
||||||
if connection:
|
connection.role = new_role
|
||||||
connection.role = new_role
|
connection.emit(connection.EVENT_ROLE_CHANGE, new_role)
|
||||||
connection.emit(connection.EVENT_ROLE_CHANGE, new_role)
|
|
||||||
else:
|
|
||||||
self.connection_roles[peer_address] = new_role
|
|
||||||
|
|
||||||
# [Classic only]
|
# [Classic only]
|
||||||
@host_event_handler
|
@host_event_handler
|
||||||
@try_with_connection_from_address
|
@try_with_connection_from_address
|
||||||
def on_role_change_failure(
|
def on_role_change_failure(
|
||||||
self, connection: Connection, address: hci.Address, error: int
|
self, connection: Optional[Connection], address: hci.Address, error: int
|
||||||
):
|
):
|
||||||
if connection:
|
if connection:
|
||||||
connection.emit(connection.EVENT_ROLE_CHANGE_FAILURE, error)
|
connection.emit(connection.EVENT_ROLE_CHANGE_FAILURE, error)
|
||||||
|
|||||||
+1
-1
@@ -550,7 +550,7 @@ class Host(utils.EventEmitter):
|
|||||||
logger.debug(
|
logger.debug(
|
||||||
'HCI LE flow control: '
|
'HCI LE flow control: '
|
||||||
f'le_acl_data_packet_length={le_acl_data_packet_length},'
|
f'le_acl_data_packet_length={le_acl_data_packet_length},'
|
||||||
f'total_num_le_acl_data_packets={total_num_le_acl_data_packets}'
|
f'total_num_le_acl_data_packets={total_num_le_acl_data_packets},'
|
||||||
f'iso_data_packet_length={iso_data_packet_length},'
|
f'iso_data_packet_length={iso_data_packet_length},'
|
||||||
f'total_num_iso_data_packets={total_num_iso_data_packets}'
|
f'total_num_iso_data_packets={total_num_iso_data_packets}'
|
||||||
)
|
)
|
||||||
|
|||||||
+33
-4
@@ -273,12 +273,19 @@ class HearingAccessService(gatt.TemplateService):
|
|||||||
def on_disconnection(_reason) -> None:
|
def on_disconnection(_reason) -> None:
|
||||||
self.currently_connected_clients.discard(connection)
|
self.currently_connected_clients.discard(connection)
|
||||||
|
|
||||||
|
@connection.on(connection.EVENT_CONNECTION_ATT_MTU_UPDATE)
|
||||||
|
def on_mtu_update(*_: Any) -> None:
|
||||||
|
self.on_incoming_connection(connection)
|
||||||
|
|
||||||
|
@connection.on(connection.EVENT_CONNECTION_ENCRYPTION_CHANGE)
|
||||||
|
def on_encryption_change(*_: Any) -> None:
|
||||||
|
self.on_incoming_connection(connection)
|
||||||
|
|
||||||
@connection.on(connection.EVENT_PAIRING)
|
@connection.on(connection.EVENT_PAIRING)
|
||||||
def on_pairing(*_: Any) -> None:
|
def on_pairing(*_: Any) -> None:
|
||||||
self.on_incoming_paired_connection(connection)
|
self.on_incoming_connection(connection)
|
||||||
|
|
||||||
if connection.peer_resolvable_address:
|
self.on_incoming_connection(connection)
|
||||||
self.on_incoming_paired_connection(connection)
|
|
||||||
|
|
||||||
self.hearing_aid_features_characteristic = gatt.Characteristic(
|
self.hearing_aid_features_characteristic = gatt.Characteristic(
|
||||||
uuid=gatt.GATT_HEARING_AID_FEATURES_CHARACTERISTIC,
|
uuid=gatt.GATT_HEARING_AID_FEATURES_CHARACTERISTIC,
|
||||||
@@ -315,9 +322,30 @@ class HearingAccessService(gatt.TemplateService):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_incoming_paired_connection(self, connection: Connection):
|
def on_incoming_connection(self, connection: Connection):
|
||||||
'''Setup initial operations to handle a remote bonded HAP device'''
|
'''Setup initial operations to handle a remote bonded HAP device'''
|
||||||
# TODO Should we filter on HAP device only ?
|
# TODO Should we filter on HAP device only ?
|
||||||
|
|
||||||
|
if not connection.is_encrypted:
|
||||||
|
logging.debug(f'HAS: {connection.peer_address} is not encrypted')
|
||||||
|
return
|
||||||
|
|
||||||
|
if not connection.peer_resolvable_address:
|
||||||
|
logging.debug(f'HAS: {connection.peer_address} is not paired')
|
||||||
|
return
|
||||||
|
|
||||||
|
if connection.att_mtu < 49:
|
||||||
|
logging.debug(
|
||||||
|
f'HAS: {connection.peer_address} invalid MTU={connection.att_mtu}'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if connection.peer_address in self.currently_connected_clients:
|
||||||
|
logging.debug(
|
||||||
|
f'HAS: Already connected to {connection.peer_address} nothing to do'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
self.currently_connected_clients.add(connection)
|
self.currently_connected_clients.add(connection)
|
||||||
if (
|
if (
|
||||||
connection.peer_address
|
connection.peer_address
|
||||||
@@ -457,6 +485,7 @@ class HearingAccessService(gatt.TemplateService):
|
|||||||
connection,
|
connection,
|
||||||
self.hearing_aid_preset_control_point,
|
self.hearing_aid_preset_control_point,
|
||||||
value=op_list[0].to_bytes(len(op_list) == 1),
|
value=op_list[0].to_bytes(len(op_list) == 1),
|
||||||
|
force=True, # TODO GATT notification subscription should be persistent
|
||||||
)
|
)
|
||||||
# Remove item once sent, and keep the non sent item in the list
|
# Remove item once sent, and keep the non sent item in the list
|
||||||
op_list.pop(0)
|
op_list.pop(0)
|
||||||
|
|||||||
@@ -131,7 +131,11 @@ def publish_grpc_port(grpc_port: int, instance_number: int) -> bool:
|
|||||||
|
|
||||||
def cleanup():
|
def cleanup():
|
||||||
logger.debug("removing .ini file")
|
logger.debug("removing .ini file")
|
||||||
ini_file.unlink()
|
try:
|
||||||
|
ini_file.unlink()
|
||||||
|
except OSError as error:
|
||||||
|
# Don't log at exception level, since this may happen normally.
|
||||||
|
logger.debug(f'failed to remove .ini file ({error})')
|
||||||
|
|
||||||
atexit.register(cleanup)
|
atexit.register(cleanup)
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import serial_asyncio
|
import serial_asyncio
|
||||||
|
|
||||||
@@ -28,25 +29,56 @@ from bumble.transport.common import StreamPacketSink, StreamPacketSource, Transp
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Constants
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
DEFAULT_POST_OPEN_DELAY = 0.5 # in seconds
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Classes and Functions
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
class SerialPacketSource(StreamPacketSource):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self._ready = asyncio.Event()
|
||||||
|
|
||||||
|
async def wait_until_ready(self) -> None:
|
||||||
|
await self._ready.wait()
|
||||||
|
|
||||||
|
def connection_made(self, transport: asyncio.BaseTransport) -> None:
|
||||||
|
logger.debug('connection made')
|
||||||
|
self._ready.set()
|
||||||
|
|
||||||
|
def connection_lost(self, exc: Optional[Exception]) -> None:
|
||||||
|
logger.debug('connection lost')
|
||||||
|
self.on_transport_lost()
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
async def open_serial_transport(spec: str) -> Transport:
|
async def open_serial_transport(spec: str) -> Transport:
|
||||||
'''
|
'''
|
||||||
Open a serial port transport.
|
Open a serial port transport.
|
||||||
The parameter string has this syntax:
|
The parameter string has this syntax:
|
||||||
<device-path>[,<speed>][,rtscts][,dsrdtr]
|
<device-path>[,<speed>][,rtscts][,dsrdtr][,delay]
|
||||||
When <speed> is omitted, the default value of 1000000 is used
|
When <speed> is omitted, the default value of 1000000 is used
|
||||||
When "rtscts" is specified, RTS/CTS hardware flow control is enabled
|
When "rtscts" is specified, RTS/CTS hardware flow control is enabled
|
||||||
When "dsrdtr" is specified, DSR/DTR hardware flow control is enabled
|
When "dsrdtr" is specified, DSR/DTR hardware flow control is enabled
|
||||||
|
When "delay" is specified, a short delay is added after opening the port
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
/dev/tty.usbmodem0006839912172
|
/dev/tty.usbmodem0006839912172
|
||||||
/dev/tty.usbmodem0006839912172,1000000
|
/dev/tty.usbmodem0006839912172,1000000
|
||||||
/dev/tty.usbmodem0006839912172,rtscts
|
/dev/tty.usbmodem0006839912172,rtscts
|
||||||
|
/dev/tty.usbmodem0006839912172,rtscts,delay
|
||||||
'''
|
'''
|
||||||
|
|
||||||
speed = 1000000
|
speed = 1000000
|
||||||
rtscts = False
|
rtscts = False
|
||||||
dsrdtr = False
|
dsrdtr = False
|
||||||
|
delay = 0.0
|
||||||
if ',' in spec:
|
if ',' in spec:
|
||||||
parts = spec.split(',')
|
parts = spec.split(',')
|
||||||
device = parts[0]
|
device = parts[0]
|
||||||
@@ -55,13 +87,16 @@ async def open_serial_transport(spec: str) -> Transport:
|
|||||||
rtscts = True
|
rtscts = True
|
||||||
elif part == 'dsrdtr':
|
elif part == 'dsrdtr':
|
||||||
dsrdtr = True
|
dsrdtr = True
|
||||||
|
elif part == 'delay':
|
||||||
|
delay = DEFAULT_POST_OPEN_DELAY
|
||||||
elif part.isnumeric():
|
elif part.isnumeric():
|
||||||
speed = int(part)
|
speed = int(part)
|
||||||
else:
|
else:
|
||||||
device = spec
|
device = spec
|
||||||
|
|
||||||
serial_transport, packet_source = await serial_asyncio.create_serial_connection(
|
serial_transport, packet_source = await serial_asyncio.create_serial_connection(
|
||||||
asyncio.get_running_loop(),
|
asyncio.get_running_loop(),
|
||||||
StreamPacketSource,
|
SerialPacketSource,
|
||||||
device,
|
device,
|
||||||
baudrate=speed,
|
baudrate=speed,
|
||||||
rtscts=rtscts,
|
rtscts=rtscts,
|
||||||
@@ -69,4 +104,23 @@ async def open_serial_transport(spec: str) -> Transport:
|
|||||||
)
|
)
|
||||||
packet_sink = StreamPacketSink(serial_transport)
|
packet_sink = StreamPacketSink(serial_transport)
|
||||||
|
|
||||||
|
logger.debug('waiting for the port to be ready')
|
||||||
|
await packet_source.wait_until_ready()
|
||||||
|
logger.debug('port is ready')
|
||||||
|
|
||||||
|
# Try to assert DTR
|
||||||
|
assert serial_transport.serial is not None
|
||||||
|
try:
|
||||||
|
serial_transport.serial.dtr = True
|
||||||
|
logger.debug(
|
||||||
|
f"DSR={serial_transport.serial.dsr}, DTR={serial_transport.serial.dtr}"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f'could not assert DTR: {e}')
|
||||||
|
|
||||||
|
# Wait a bit after opening the port, if requested
|
||||||
|
if delay > 0.0:
|
||||||
|
logger.debug(f'waiting {delay} seconds after opening the port')
|
||||||
|
await asyncio.sleep(delay)
|
||||||
|
|
||||||
return Transport(packet_source, packet_sink)
|
return Transport(packet_source, packet_sink)
|
||||||
|
|||||||
@@ -4,9 +4,18 @@ SERIAL TRANSPORT
|
|||||||
The serial transport implements sending/receiving HCI packets over a UART (a.k.a serial port).
|
The serial transport implements sending/receiving HCI packets over a UART (a.k.a serial port).
|
||||||
|
|
||||||
## Moniker
|
## Moniker
|
||||||
The moniker syntax for a serial transport is: `serial:<device-path>[,<speed>]`
|
The moniker syntax for a serial transport is:
|
||||||
When `<speed>` is omitted, the default value of 1000000 is used
|
`<device-path>[,<speed>][,rtscts][,dsrdtr][,delay]`
|
||||||
|
|
||||||
|
When `<speed>` is omitted, the default value of 1000000 is used.
|
||||||
|
When `rtscts` is specified, RTS/CTS hardware flow control is enabled.
|
||||||
|
When `dsrdtr` is specified, DSR/DTR hardware flow control is enabled.
|
||||||
|
When `delay` is specified, a short delay is added after opening the port.
|
||||||
|
|
||||||
!!! example
|
!!! example
|
||||||
`serial:/dev/tty.usbmodem0006839912172,1000000`
|
```
|
||||||
Opens the serial port `/dev/tty.usbmodem0006839912172` at `1000000`bps
|
/dev/tty.usbmodem0006839912172
|
||||||
|
/dev/tty.usbmodem0006839912172,1000000
|
||||||
|
/dev/tty.usbmodem0006839912172,rtscts
|
||||||
|
/dev/tty.usbmodem0006839912172,rtscts,delay
|
||||||
|
```
|
||||||
@@ -82,3 +82,7 @@ services and characteristics.
|
|||||||
|
|
||||||
# `run_scanner.py`
|
# `run_scanner.py`
|
||||||
Run a host application connected to a 'real' BLE controller over a UART HCI to a dev board running Zephyr in HCI mode (could be any other UART BLE controller, or BlueZ over a virtual UART), that starts scanning and prints out the scan results.
|
Run a host application connected to a 'real' BLE controller over a UART HCI to a dev board running Zephyr in HCI mode (could be any other UART BLE controller, or BlueZ over a virtual UART), that starts scanning and prints out the scan results.
|
||||||
|
|
||||||
|
|
||||||
|
# run auracast with usb stick
|
||||||
|
bumble-auracast transmit 'serial:/dev/serial/by-id/usb-ZEPHYR_Zephyr_HCI_UART_sample_CC69A2912F84AE5E-if00,1000000,rtscts' --input device
|
||||||
|
|||||||
Generated
+140
-56
@@ -61,7 +61,7 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
|
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -71,7 +71,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
|
checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"windows-sys",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -249,7 +249,7 @@ dependencies = [
|
|||||||
"atty",
|
"atty",
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"clap_lex 0.2.4",
|
"clap_lex 0.2.4",
|
||||||
"indexmap",
|
"indexmap 1.9.3",
|
||||||
"strsim",
|
"strsim",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
"textwrap",
|
"textwrap",
|
||||||
@@ -451,7 +451,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"option-ext",
|
"option-ext",
|
||||||
"redox_users",
|
"redox_users",
|
||||||
"windows-sys",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -483,24 +483,19 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "equivalent"
|
||||||
version = "0.3.3"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd"
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
dependencies = [
|
|
||||||
"errno-dragonfly",
|
|
||||||
"libc",
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno-dragonfly"
|
name = "errno"
|
||||||
version = "0.1.2"
|
version = "0.3.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
|
||||||
"libc",
|
"libc",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -683,9 +678,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.3.21"
|
version = "0.3.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833"
|
checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
@@ -693,7 +688,7 @@ dependencies = [
|
|||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
"indexmap",
|
"indexmap 2.11.3",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
@@ -706,6 +701,12 @@ version = "0.12.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.15.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
@@ -827,7 +828,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"hashbrown",
|
"hashbrown 0.12.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "2.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92119844f513ffa41556430369ab02c295a3578af21cf945caa3e9e0c2481ac3"
|
||||||
|
dependencies = [
|
||||||
|
"equivalent",
|
||||||
|
"hashbrown 0.15.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -856,7 +867,7 @@ checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi 0.3.2",
|
"hermit-abi 0.3.2",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -891,9 +902,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.147"
|
version = "0.2.175"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libusb1-sys"
|
name = "libusb1-sys"
|
||||||
@@ -920,9 +931,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.4.5"
|
version = "0.4.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
|
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
@@ -996,13 +1007,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "0.8.8"
|
version = "0.8.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
|
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
"windows-sys",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1073,9 +1084,9 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
version = "0.10.60"
|
version = "0.10.73"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800"
|
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.0",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
@@ -1105,9 +1116,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-sys"
|
name = "openssl-sys"
|
||||||
version = "0.9.96"
|
version = "0.9.109"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f"
|
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -1153,7 +1164,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"redox_syscall 0.3.5",
|
"redox_syscall 0.3.5",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"windows-targets",
|
"windows-targets 0.48.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1542,15 +1553,15 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.10"
|
version = "0.38.44"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed6248e1caa625eb708e266e06159f135e8c26f2bb7ceb72dc4b2766d0340964"
|
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.0",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1580,7 +1591,7 @@ version = "0.1.22"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
|
checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1702,12 +1713,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.5.3"
|
version = "0.5.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877"
|
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1773,7 +1784,7 @@ dependencies = [
|
|||||||
"fastrand",
|
"fastrand",
|
||||||
"redox_syscall 0.3.5",
|
"redox_syscall 0.3.5",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1828,9 +1839,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.32.0"
|
version = "1.38.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9"
|
checksum = "68722da18b0fc4a05fdc1120b302b82051265792a1e1b399086e9b204b10ad3d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -1840,16 +1851,16 @@ dependencies = [
|
|||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"socket2 0.5.3",
|
"socket2 0.5.10",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"windows-sys",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-macros"
|
name = "tokio-macros"
|
||||||
version = "2.1.0"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
|
checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2130,7 +2141,16 @@ version = "0.48.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-targets",
|
"windows-targets 0.48.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2139,13 +2159,29 @@ version = "0.48.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows_aarch64_gnullvm",
|
"windows_aarch64_gnullvm 0.48.5",
|
||||||
"windows_aarch64_msvc",
|
"windows_aarch64_msvc 0.48.5",
|
||||||
"windows_i686_gnu",
|
"windows_i686_gnu 0.48.5",
|
||||||
"windows_i686_msvc",
|
"windows_i686_msvc 0.48.5",
|
||||||
"windows_x86_64_gnu",
|
"windows_x86_64_gnu 0.48.5",
|
||||||
"windows_x86_64_gnullvm",
|
"windows_x86_64_gnullvm 0.48.5",
|
||||||
"windows_x86_64_msvc",
|
"windows_x86_64_msvc 0.48.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm 0.52.6",
|
||||||
|
"windows_aarch64_msvc 0.52.6",
|
||||||
|
"windows_i686_gnu 0.52.6",
|
||||||
|
"windows_i686_gnullvm",
|
||||||
|
"windows_i686_msvc 0.52.6",
|
||||||
|
"windows_x86_64_gnu 0.52.6",
|
||||||
|
"windows_x86_64_gnullvm 0.52.6",
|
||||||
|
"windows_x86_64_msvc 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2154,42 +2190,90 @@ version = "0.48.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winreg"
|
name = "winreg"
|
||||||
version = "0.50.0"
|
version = "0.50.0"
|
||||||
@@ -2197,5 +2281,5 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
|
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"windows-sys",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|||||||
+2
-2
@@ -22,7 +22,7 @@ always_include_features = ["anyhow", "pyo3-asyncio-attributes", "dev-tools", "bu
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
pyo3 = { version = "0.18.3", features = ["macros"] }
|
pyo3 = { version = "0.18.3", features = ["macros"] }
|
||||||
pyo3-asyncio = { version = "0.18.0", features = ["tokio-runtime"] }
|
pyo3-asyncio = { version = "0.18.0", features = ["tokio-runtime"] }
|
||||||
tokio = { version = "1.28.2", features = ["macros", "signal"] }
|
tokio = { version = "1.38.2", features = ["macros", "signal"] }
|
||||||
nom = "7.1.3"
|
nom = "7.1.3"
|
||||||
strum = "0.25.0"
|
strum = "0.25.0"
|
||||||
strum_macros = "0.25.0"
|
strum_macros = "0.25.0"
|
||||||
@@ -50,7 +50,7 @@ reqwest = { version = "0.11.20", features = ["blocking"], optional = true }
|
|||||||
rusb = { version = "0.9.2", optional = true }
|
rusb = { version = "0.9.2", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { version = "1.28.2", features = ["full"] }
|
tokio = { version = "1.38.2", features = ["full"] }
|
||||||
tempfile = "3.6.0"
|
tempfile = "3.6.0"
|
||||||
nix = "0.26.2"
|
nix = "0.26.2"
|
||||||
anyhow = "1.0.71"
|
anyhow = "1.0.71"
|
||||||
|
|||||||
@@ -761,6 +761,34 @@ async def test_inquiry_result_with_rssi():
|
|||||||
m.assert_called_with(hci.Address("00:11:22:33:44:55/P"), 3, mock.ANY, 5)
|
m.assert_called_with(hci.Address("00:11:22:33:44:55/P"), 3, mock.ANY, 5)
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"roles",
|
||||||
|
(
|
||||||
|
(hci.Role.PERIPHERAL, hci.Role.CENTRAL),
|
||||||
|
(hci.Role.CENTRAL, hci.Role.PERIPHERAL),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_accept_classic_connection(roles: tuple[hci.Role, hci.Role]):
|
||||||
|
devices = TwoDevices()
|
||||||
|
devices[0].classic_enabled = True
|
||||||
|
devices[1].classic_enabled = True
|
||||||
|
await devices[0].power_on()
|
||||||
|
await devices[1].power_on()
|
||||||
|
|
||||||
|
accept_task = asyncio.create_task(devices[1].accept(role=roles[1]))
|
||||||
|
await devices[0].connect(
|
||||||
|
devices[1].public_address, transport=PhysicalTransport.BR_EDR
|
||||||
|
)
|
||||||
|
await accept_task
|
||||||
|
|
||||||
|
assert devices.connections[0]
|
||||||
|
assert devices.connections[0].role == roles[0]
|
||||||
|
assert devices.connections[1]
|
||||||
|
assert devices.connections[1].role == roles[1]
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
async def run_test_device():
|
async def run_test_device():
|
||||||
await test_device_connect_parallel()
|
await test_device_connect_parallel()
|
||||||
|
|||||||
+3
-1
@@ -82,7 +82,6 @@ async def hap_client():
|
|||||||
)
|
)
|
||||||
|
|
||||||
await devices.setup_connection()
|
await devices.setup_connection()
|
||||||
# TODO negotiate MTU > 49 to not truncate preset names
|
|
||||||
|
|
||||||
# Mock encryption.
|
# Mock encryption.
|
||||||
devices.connections[0].encryption = 1 # type: ignore
|
devices.connections[0].encryption = 1 # type: ignore
|
||||||
@@ -93,6 +92,9 @@ async def hap_client():
|
|||||||
)
|
)
|
||||||
|
|
||||||
peer = device.Peer(devices.connections[1]) # type: ignore
|
peer = device.Peer(devices.connections[1]) # type: ignore
|
||||||
|
await peer.request_mtu(49)
|
||||||
|
peer2 = device.Peer(devices.connections[0]) # type: ignore
|
||||||
|
await peer2.request_mtu(49)
|
||||||
hap_client = await peer.discover_service_and_create_proxy(
|
hap_client = await peer.discover_service_and_create_proxy(
|
||||||
hap.HearingAccessServiceProxy
|
hap.HearingAccessServiceProxy
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user