forked from auracaster/bumble_mirror
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ca23d6b89a |
@@ -49,7 +49,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||||
rust-version: [ "1.80.0", "stable" ]
|
rust-version: [ "1.76.0", "stable" ]
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
- name: Check out from Git
|
- name: Check out from Git
|
||||||
@@ -72,7 +72,7 @@ jobs:
|
|||||||
- name: Check License Headers
|
- name: Check License Headers
|
||||||
run: cd rust && cargo run --features dev-tools --bin file-header check-all
|
run: cd rust && cargo run --features dev-tools --bin file-header check-all
|
||||||
- name: Rust Build
|
- name: Rust Build
|
||||||
run: cd rust && cargo build --all-targets && cargo build-all-features
|
run: cd rust && cargo build --all-targets && cargo build-all-features --all-targets
|
||||||
# Lints after build so what clippy needs is already built
|
# Lints after build so what clippy needs is already built
|
||||||
- name: Rust Lints
|
- name: Rust Lints
|
||||||
run: cd rust && cargo fmt --check && cargo clippy --all-targets -- --deny warnings && cargo clippy --all-features --all-targets -- --deny warnings
|
run: cd rust && cargo fmt --check && cargo clippy --all-targets -- --deny warnings && cargo clippy --all-features --all-targets -- --deny warnings
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ Bumble is easiest to use with a dedicated USB dongle.
|
|||||||
This is because internal Bluetooth interfaces tend to be locked down by the operating system.
|
This is because internal Bluetooth interfaces tend to be locked down by the operating system.
|
||||||
You can use the [usb_probe](/docs/mkdocs/src/apps_and_tools/usb_probe.md) tool (all platforms) or `lsusb` (Linux or macOS) to list the available USB devices on your system.
|
You can use the [usb_probe](/docs/mkdocs/src/apps_and_tools/usb_probe.md) tool (all platforms) or `lsusb` (Linux or macOS) to list the available USB devices on your system.
|
||||||
|
|
||||||
See the [USB Transport](/docs/mkdocs/src/transports/usb.md) page for details on how to refer to USB devices. Also, if you are on a mac, see [these instructions](docs/mkdocs/src/platforms/macos.md).
|
See the [USB Transport](/docs/mkdocs/src/transports/usb.md) page for details on how to refer to USB devices. Also, if your are on a mac, see [these instructions](docs/mkdocs/src/platforms/macos.md).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
+3
-43
@@ -33,6 +33,7 @@ from bumble.hci import (
|
|||||||
HCI_COMMAND_DISALLOWED_ERROR,
|
HCI_COMMAND_DISALLOWED_ERROR,
|
||||||
HCI_COMMAND_PACKET,
|
HCI_COMMAND_PACKET,
|
||||||
HCI_COMMAND_STATUS_PENDING,
|
HCI_COMMAND_STATUS_PENDING,
|
||||||
|
HCI_CONNECTION_TIMEOUT_ERROR,
|
||||||
HCI_CONTROLLER_BUSY_ERROR,
|
HCI_CONTROLLER_BUSY_ERROR,
|
||||||
HCI_EVENT_PACKET,
|
HCI_EVENT_PACKET,
|
||||||
HCI_INVALID_HCI_COMMAND_PARAMETERS_ERROR,
|
HCI_INVALID_HCI_COMMAND_PARAMETERS_ERROR,
|
||||||
@@ -87,7 +88,6 @@ class CisLink:
|
|||||||
cis_id: int
|
cis_id: int
|
||||||
cig_id: int
|
cig_id: int
|
||||||
acl_connection: Optional[Connection] = None
|
acl_connection: Optional[Connection] = None
|
||||||
data_paths: set[int] = dataclasses.field(default_factory=set)
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -381,11 +381,6 @@ class Controller:
|
|||||||
return connection
|
return connection
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def find_iso_link_by_handle(self, handle: int) -> Optional[CisLink]:
|
|
||||||
return self.central_cis_links.get(handle) or self.peripheral_cis_links.get(
|
|
||||||
handle
|
|
||||||
)
|
|
||||||
|
|
||||||
def on_link_central_connected(self, central_address):
|
def on_link_central_connected(self, central_address):
|
||||||
'''
|
'''
|
||||||
Called when an incoming connection occurs from a central on the link
|
Called when an incoming connection occurs from a central on the link
|
||||||
@@ -1858,51 +1853,16 @@ class Controller:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_hci_le_setup_iso_data_path_command(
|
def on_hci_le_setup_iso_data_path_command(self, command):
|
||||||
self, command: hci.HCI_LE_Setup_ISO_Data_Path_Command
|
|
||||||
) -> bytes:
|
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.109 LE Setup ISO Data Path Command
|
See Bluetooth spec Vol 4, Part E - 7.8.109 LE Setup ISO Data Path Command
|
||||||
'''
|
'''
|
||||||
if not (iso_link := self.find_iso_link_by_handle(command.connection_handle)):
|
|
||||||
return struct.pack(
|
|
||||||
'<BH',
|
|
||||||
HCI_UNKNOWN_CONNECTION_IDENTIFIER_ERROR,
|
|
||||||
command.connection_handle,
|
|
||||||
)
|
|
||||||
if command.data_path_direction in iso_link.data_paths:
|
|
||||||
return struct.pack(
|
|
||||||
'<BH',
|
|
||||||
HCI_COMMAND_DISALLOWED_ERROR,
|
|
||||||
command.connection_handle,
|
|
||||||
)
|
|
||||||
iso_link.data_paths.add(command.data_path_direction)
|
|
||||||
return struct.pack('<BH', HCI_SUCCESS, command.connection_handle)
|
return struct.pack('<BH', HCI_SUCCESS, command.connection_handle)
|
||||||
|
|
||||||
def on_hci_le_remove_iso_data_path_command(
|
def on_hci_le_remove_iso_data_path_command(self, command):
|
||||||
self, command: hci.HCI_LE_Remove_ISO_Data_Path_Command
|
|
||||||
) -> bytes:
|
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.110 LE Remove ISO Data Path Command
|
See Bluetooth spec Vol 4, Part E - 7.8.110 LE Remove ISO Data Path Command
|
||||||
'''
|
'''
|
||||||
if not (iso_link := self.find_iso_link_by_handle(command.connection_handle)):
|
|
||||||
return struct.pack(
|
|
||||||
'<BH',
|
|
||||||
HCI_UNKNOWN_CONNECTION_IDENTIFIER_ERROR,
|
|
||||||
command.connection_handle,
|
|
||||||
)
|
|
||||||
data_paths: set[int] = set(
|
|
||||||
direction
|
|
||||||
for direction in hci.HCI_LE_Setup_ISO_Data_Path_Command.Direction
|
|
||||||
if (1 << direction) & command.data_path_direction
|
|
||||||
)
|
|
||||||
if not data_paths.issubset(iso_link.data_paths):
|
|
||||||
return struct.pack(
|
|
||||||
'<BH',
|
|
||||||
HCI_COMMAND_DISALLOWED_ERROR,
|
|
||||||
command.connection_handle,
|
|
||||||
)
|
|
||||||
iso_link.data_paths.difference_update(data_paths)
|
|
||||||
return struct.pack('<BH', HCI_SUCCESS, command.connection_handle)
|
return struct.pack('<BH', HCI_SUCCESS, command.connection_handle)
|
||||||
|
|
||||||
def on_hci_le_set_host_feature_command(
|
def on_hci_le_set_host_feature_command(
|
||||||
|
|||||||
@@ -2110,6 +2110,23 @@ class AdvertisingData:
|
|||||||
return self.to_string()
|
return self.to_string()
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Connection Parameters
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
class ConnectionParameters:
|
||||||
|
def __init__(self, connection_interval, peripheral_latency, supervision_timeout):
|
||||||
|
self.connection_interval = connection_interval
|
||||||
|
self.peripheral_latency = peripheral_latency
|
||||||
|
self.supervision_timeout = supervision_timeout
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return (
|
||||||
|
f'ConnectionParameters(connection_interval={self.connection_interval}, '
|
||||||
|
f'peripheral_latency={self.peripheral_latency}, '
|
||||||
|
f'supervision_timeout={self.supervision_timeout}'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Connection PHY
|
# Connection PHY
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
+123
-166
@@ -1453,8 +1453,6 @@ class _IsoLink:
|
|||||||
handle: int
|
handle: int
|
||||||
device: Device
|
device: Device
|
||||||
sink: Callable[[hci.HCI_IsoDataPacket], Any] | None = None
|
sink: Callable[[hci.HCI_IsoDataPacket], Any] | None = None
|
||||||
data_paths: set[_IsoLink.Direction]
|
|
||||||
_data_path_lock: asyncio.Lock
|
|
||||||
|
|
||||||
class Direction(IntEnum):
|
class Direction(IntEnum):
|
||||||
HOST_TO_CONTROLLER = (
|
HOST_TO_CONTROLLER = (
|
||||||
@@ -1464,10 +1462,6 @@ class _IsoLink:
|
|||||||
hci.HCI_LE_Setup_ISO_Data_Path_Command.Direction.CONTROLLER_TO_HOST
|
hci.HCI_LE_Setup_ISO_Data_Path_Command.Direction.CONTROLLER_TO_HOST
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self._data_path_lock = asyncio.Lock()
|
|
||||||
self.data_paths = set()
|
|
||||||
|
|
||||||
async def setup_data_path(
|
async def setup_data_path(
|
||||||
self,
|
self,
|
||||||
direction: _IsoLink.Direction,
|
direction: _IsoLink.Direction,
|
||||||
@@ -1488,45 +1482,37 @@ class _IsoLink:
|
|||||||
Raises:
|
Raises:
|
||||||
HCI_Error: When command complete status is not HCI_SUCCESS.
|
HCI_Error: When command complete status is not HCI_SUCCESS.
|
||||||
"""
|
"""
|
||||||
async with self._data_path_lock:
|
await self.device.send_command(
|
||||||
if direction in self.data_paths:
|
hci.HCI_LE_Setup_ISO_Data_Path_Command(
|
||||||
return
|
connection_handle=self.handle,
|
||||||
await self.device.send_command(
|
data_path_direction=direction,
|
||||||
hci.HCI_LE_Setup_ISO_Data_Path_Command(
|
data_path_id=data_path_id,
|
||||||
connection_handle=self.handle,
|
codec_id=codec_id or hci.CodingFormat(hci.CodecID.TRANSPARENT),
|
||||||
data_path_direction=direction,
|
controller_delay=controller_delay,
|
||||||
data_path_id=data_path_id,
|
codec_configuration=codec_configuration,
|
||||||
codec_id=codec_id or hci.CodingFormat(hci.CodecID.TRANSPARENT),
|
),
|
||||||
controller_delay=controller_delay,
|
check_result=True,
|
||||||
codec_configuration=codec_configuration,
|
)
|
||||||
),
|
|
||||||
check_result=True,
|
|
||||||
)
|
|
||||||
self.data_paths.add(direction)
|
|
||||||
|
|
||||||
async def remove_data_path(self, directions: Iterable[_IsoLink.Direction]) -> None:
|
async def remove_data_path(self, directions: Iterable[_IsoLink.Direction]) -> int:
|
||||||
"""Remove a data path with controller on given direction.
|
"""Remove a data path with controller on given direction.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
direction: Direction of data path.
|
direction: Direction of data path.
|
||||||
|
|
||||||
Raises:
|
Returns:
|
||||||
HCI_Error: When command complete status is not HCI_SUCCESS.
|
Command status.
|
||||||
"""
|
"""
|
||||||
async with self._data_path_lock:
|
response = await self.device.send_command(
|
||||||
directions_to_remove = set(directions).intersection(self.data_paths)
|
hci.HCI_LE_Remove_ISO_Data_Path_Command(
|
||||||
if not directions_to_remove:
|
connection_handle=self.handle,
|
||||||
return
|
data_path_direction=sum(
|
||||||
await self.device.send_command(
|
1 << direction for direction in set(directions)
|
||||||
hci.HCI_LE_Remove_ISO_Data_Path_Command(
|
|
||||||
connection_handle=self.handle,
|
|
||||||
data_path_direction=sum(
|
|
||||||
1 << direction for direction in directions_to_remove
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
check_result=True,
|
),
|
||||||
)
|
check_result=False,
|
||||||
self.data_paths.difference_update(directions_to_remove)
|
)
|
||||||
|
return response.return_parameters.status
|
||||||
|
|
||||||
def write(self, sdu: bytes) -> None:
|
def write(self, sdu: bytes) -> None:
|
||||||
"""Write an ISO SDU."""
|
"""Write an ISO SDU."""
|
||||||
@@ -1636,8 +1622,7 @@ class CisLink(utils.EventEmitter, _IsoLink):
|
|||||||
EVENT_ESTABLISHMENT_FAILURE: ClassVar[str] = "establishment_failure"
|
EVENT_ESTABLISHMENT_FAILURE: ClassVar[str] = "establishment_failure"
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
utils.EventEmitter.__init__(self)
|
super().__init__()
|
||||||
_IsoLink.__init__(self)
|
|
||||||
|
|
||||||
async def disconnect(
|
async def disconnect(
|
||||||
self, reason: int = hci.HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR
|
self, reason: int = hci.HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR
|
||||||
@@ -1653,7 +1638,6 @@ class BisLink(_IsoLink):
|
|||||||
sink: Callable[[hci.HCI_IsoDataPacket], Any] | None = None
|
sink: Callable[[hci.HCI_IsoDataPacket], Any] | None = None
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
super().__init__()
|
|
||||||
self.device = self.big.device
|
self.device = self.big.device
|
||||||
|
|
||||||
|
|
||||||
@@ -1798,22 +1782,15 @@ class Connection(utils.CompositeEventEmitter):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Parameters:
|
class Parameters:
|
||||||
"""
|
connection_interval: float # Connection interval, in milliseconds. [LE only]
|
||||||
LE connection parameters.
|
peripheral_latency: int # Peripheral latency, in number of intervals. [LE only]
|
||||||
|
supervision_timeout: float # Supervision timeout, in milliseconds.
|
||||||
Attributes:
|
subrate_factor: int = (
|
||||||
connection_interval: Connection interval, in milliseconds.
|
1 # See Bluetooth spec Vol 6, Part B - 4.5.1 Connection events
|
||||||
peripheral_latency: Peripheral latency, in number of intervals.
|
)
|
||||||
supervision_timeout: Supervision timeout, in milliseconds.
|
continuation_number: int = (
|
||||||
subrate_factor: See Bluetooth spec Vol 6, Part B - 4.5.1 Connection events
|
0 # See Bluetooth spec Vol 6, Part B - 4.5.1 Connection events
|
||||||
continuation_number: See Bluetooth spec Vol 6, Part B - 4.5.1 Connection events
|
)
|
||||||
"""
|
|
||||||
|
|
||||||
connection_interval: float
|
|
||||||
peripheral_latency: int
|
|
||||||
supervision_timeout: float
|
|
||||||
subrate_factor: int = 1
|
|
||||||
continuation_number: int = 0
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -1854,6 +1831,36 @@ class Connection(utils.CompositeEventEmitter):
|
|||||||
self.cs_configs = {}
|
self.cs_configs = {}
|
||||||
self.cs_procedures = {}
|
self.cs_procedures = {}
|
||||||
|
|
||||||
|
# [Classic only]
|
||||||
|
@classmethod
|
||||||
|
def incomplete(cls, device, peer_address, role):
|
||||||
|
"""
|
||||||
|
Instantiate an incomplete connection (ie. one waiting for a HCI Connection
|
||||||
|
Complete event).
|
||||||
|
Once received it shall be completed using the `.complete` method.
|
||||||
|
"""
|
||||||
|
return cls(
|
||||||
|
device,
|
||||||
|
None,
|
||||||
|
PhysicalTransport.BR_EDR,
|
||||||
|
device.public_address,
|
||||||
|
None,
|
||||||
|
peer_address,
|
||||||
|
None,
|
||||||
|
role,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# [Classic only]
|
||||||
|
def complete(self, handle, parameters):
|
||||||
|
"""
|
||||||
|
Finish an incomplete connection upon completion.
|
||||||
|
"""
|
||||||
|
assert self.handle is None
|
||||||
|
assert self.transport == PhysicalTransport.BR_EDR
|
||||||
|
self.handle = handle
|
||||||
|
self.parameters = parameters
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def role_name(self):
|
def role_name(self):
|
||||||
if self.role is None:
|
if self.role is None:
|
||||||
@@ -1865,7 +1872,7 @@ class Connection(utils.CompositeEventEmitter):
|
|||||||
return f'UNKNOWN[{self.role}]'
|
return f'UNKNOWN[{self.role}]'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_encrypted(self) -> bool:
|
def is_encrypted(self):
|
||||||
return self.encryption != 0
|
return self.encryption != 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -2169,12 +2176,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(device: Device, address: hci.Address, *args, **kwargs):
|
def wrapper(self, address: hci.Address, *args, **kwargs):
|
||||||
if connection := device.pending_connections.get(address):
|
if connection := self.pending_connections.get(address, False):
|
||||||
return function(device, connection, *args, **kwargs)
|
return function(self, connection, *args, **kwargs)
|
||||||
for connection in device.connections.values():
|
for connection in self.connections.values():
|
||||||
if connection.peer_address == address:
|
if connection.peer_address == address:
|
||||||
return function(device, connection, *args, **kwargs)
|
return function(self, connection, *args, **kwargs)
|
||||||
raise ObjectLookupError('no connection for address')
|
raise ObjectLookupError('no connection for address')
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
@@ -2184,13 +2191,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(device: Device, address: hci.Address, *args, **kwargs):
|
def wrapper(self, address, *args, **kwargs):
|
||||||
if connection := device.pending_connections.get(address):
|
if connection := self.pending_connections.get(address, False):
|
||||||
return function(device, connection, address, *args, **kwargs)
|
return function(self, connection, address, *args, **kwargs)
|
||||||
for connection in device.connections.values():
|
for connection in self.connections.values():
|
||||||
if connection.peer_address == address:
|
if connection.peer_address == address:
|
||||||
return function(device, connection, address, *args, **kwargs)
|
return function(self, connection, address, *args, **kwargs)
|
||||||
return function(device, None, address, *args, **kwargs)
|
return function(self, None, address, *args, **kwargs)
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
@@ -2263,6 +2270,8 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
EVENT_CONNECTION_FAILURE = "connection_failure"
|
EVENT_CONNECTION_FAILURE = "connection_failure"
|
||||||
EVENT_SCO_REQUEST = "sco_request"
|
EVENT_SCO_REQUEST = "sco_request"
|
||||||
EVENT_INQUIRY_COMPLETE = "inquiry_complete"
|
EVENT_INQUIRY_COMPLETE = "inquiry_complete"
|
||||||
|
EVENT_REMOTE_NAME = "remote_name"
|
||||||
|
EVENT_REMOTE_NAME_FAILURE = "remote_name_failure"
|
||||||
EVENT_SCO_CONNECTION = "sco_connection"
|
EVENT_SCO_CONNECTION = "sco_connection"
|
||||||
EVENT_SCO_CONNECTION_FAILURE = "sco_connection_failure"
|
EVENT_SCO_CONNECTION_FAILURE = "sco_connection_failure"
|
||||||
EVENT_CIS_REQUEST = "cis_request"
|
EVENT_CIS_REQUEST = "cis_request"
|
||||||
@@ -2358,9 +2367,7 @@ 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.pending_connections = (
|
self.pending_connections = {} # Connections, 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
|
||||||
@@ -3829,16 +3836,8 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Save pending connection
|
# Save pending connection
|
||||||
self.pending_connections[peer_address] = Connection(
|
self.pending_connections[peer_address] = Connection.incomplete(
|
||||||
device=self,
|
self, peer_address, hci.Role.CENTRAL
|
||||||
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
|
||||||
@@ -3986,20 +3985,12 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
self.on(self.EVENT_CONNECTION, on_connection)
|
self.on(self.EVENT_CONNECTION, on_connection)
|
||||||
self.on(self.EVENT_CONNECTION_FAILURE, on_connection_failure)
|
self.on(self.EVENT_CONNECTION_FAILURE, on_connection_failure)
|
||||||
|
|
||||||
# Save Peripheral hci.role.
|
# Save pending connection, with the Peripheral hci.role.
|
||||||
# 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.pending_connections[peer_address] = Connection(
|
self.pending_connections[peer_address] = Connection.incomplete(
|
||||||
device=self,
|
self, peer_address, hci.Role.PERIPHERAL
|
||||||
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:
|
||||||
@@ -4725,7 +4716,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[cis_handle]
|
cis_id, cig_id = self._pending_cis.pop(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,
|
||||||
@@ -4741,7 +4732,6 @@ 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)
|
||||||
|
|
||||||
@@ -5459,47 +5449,15 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
self.emit(self.EVENT_CONNECTION, connection)
|
self.emit(self.EVENT_CONNECTION, connection)
|
||||||
|
|
||||||
@host_event_handler
|
@host_event_handler
|
||||||
def on_classic_connection(
|
def on_connection(
|
||||||
self,
|
|
||||||
connection_handle: int,
|
|
||||||
peer_address: hci.Address,
|
|
||||||
) -> None:
|
|
||||||
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('*** %s', connection)
|
|
||||||
if connection_handle in self.connections:
|
|
||||||
logger.warning(
|
|
||||||
'new connection reuses the same handle as a previous connection'
|
|
||||||
)
|
|
||||||
self.connections[connection_handle] = connection
|
|
||||||
|
|
||||||
self.emit(self.EVENT_CONNECTION, connection)
|
|
||||||
|
|
||||||
@host_event_handler
|
|
||||||
def on_le_connection(
|
|
||||||
self,
|
self,
|
||||||
connection_handle: int,
|
connection_handle: int,
|
||||||
|
transport: core.PhysicalTransport,
|
||||||
peer_address: hci.Address,
|
peer_address: hci.Address,
|
||||||
self_resolvable_address: Optional[hci.Address],
|
self_resolvable_address: Optional[hci.Address],
|
||||||
peer_resolvable_address: Optional[hci.Address],
|
peer_resolvable_address: Optional[hci.Address],
|
||||||
role: hci.Role,
|
role: hci.Role,
|
||||||
connection_interval: int,
|
connection_parameters: Optional[core.ConnectionParameters],
|
||||||
peripheral_latency: int,
|
|
||||||
supervision_timeout: int,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
# Convert all-zeros addresses into None.
|
# Convert all-zeros addresses into None.
|
||||||
if self_resolvable_address == hci.Address.ANY_RANDOM:
|
if self_resolvable_address == hci.Address.ANY_RANDOM:
|
||||||
@@ -5519,6 +5477,19 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
'new connection reuses the same handle as a previous connection'
|
'new connection reuses the same handle as a previous connection'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if transport == PhysicalTransport.BR_EDR:
|
||||||
|
# Create a new connection
|
||||||
|
connection = self.pending_connections.pop(peer_address)
|
||||||
|
connection.complete(connection_handle, connection_parameters)
|
||||||
|
self.connections[connection_handle] = connection
|
||||||
|
|
||||||
|
# Emit an event to notify listeners of the new connection
|
||||||
|
self.emit(self.EVENT_CONNECTION, connection)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
assert connection_parameters is not None
|
||||||
|
|
||||||
if peer_resolvable_address is None:
|
if peer_resolvable_address is None:
|
||||||
# Resolve the peer address if we can
|
# Resolve the peer address if we can
|
||||||
if self.address_resolver:
|
if self.address_resolver:
|
||||||
@@ -5568,16 +5539,16 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
connection = Connection(
|
connection = Connection(
|
||||||
self,
|
self,
|
||||||
connection_handle,
|
connection_handle,
|
||||||
PhysicalTransport.LE,
|
transport,
|
||||||
self_address,
|
self_address,
|
||||||
self_resolvable_address,
|
self_resolvable_address,
|
||||||
peer_address,
|
peer_address,
|
||||||
peer_resolvable_address,
|
peer_resolvable_address,
|
||||||
role,
|
role,
|
||||||
Connection.Parameters(
|
Connection.Parameters(
|
||||||
connection_interval * 1.25,
|
connection_parameters.connection_interval * 1.25,
|
||||||
peripheral_latency,
|
connection_parameters.peripheral_latency,
|
||||||
supervision_timeout * 10.0,
|
connection_parameters.supervision_timeout * 10.0,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.connections[connection_handle] = connection
|
self.connections[connection_handle] = connection
|
||||||
@@ -5639,9 +5610,7 @@ 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(
|
def on_connection_request(self, bd_addr, class_of_device, link_type):
|
||||||
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.
|
||||||
@@ -5670,16 +5639,8 @@ 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.pending_connections[bd_addr] = Connection(
|
self.pending_connections[bd_addr] = Connection.incomplete(
|
||||||
device=self,
|
self, bd_addr, hci.Role.PERIPHERAL
|
||||||
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(
|
||||||
@@ -5991,7 +5952,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: Optional[Connection], address: hci.Address, remote_name: bytes
|
self, connection: Connection, address: hci.Address, remote_name: bytes
|
||||||
):
|
):
|
||||||
# Try to decode the name
|
# Try to decode the name
|
||||||
try:
|
try:
|
||||||
@@ -6010,7 +5971,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: Optional[Connection], address: hci.Address, error: int
|
self, connection: 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)
|
||||||
@@ -6222,27 +6183,27 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
@host_event_handler
|
@host_event_handler
|
||||||
@with_connection_from_handle
|
@with_connection_from_handle
|
||||||
def on_connection_parameters_update(
|
def on_connection_parameters_update(
|
||||||
self,
|
self, connection: Connection, connection_parameters: core.ConnectionParameters
|
||||||
connection: Connection,
|
|
||||||
connection_interval: int,
|
|
||||||
peripheral_latency: int,
|
|
||||||
supervision_timeout: int,
|
|
||||||
):
|
):
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f'*** Connection Parameters Update: [0x{connection.handle:04X}] '
|
f'*** Connection Parameters Update: [0x{connection.handle:04X}] '
|
||||||
f'{connection.peer_address} as {connection.role_name}, '
|
f'{connection.peer_address} as {connection.role_name}, '
|
||||||
|
f'{connection_parameters}'
|
||||||
)
|
)
|
||||||
if connection.parameters.connection_interval != connection_interval * 1.25:
|
if (
|
||||||
|
connection.parameters.connection_interval
|
||||||
|
!= connection_parameters.connection_interval * 1.25
|
||||||
|
):
|
||||||
connection.parameters = Connection.Parameters(
|
connection.parameters = Connection.Parameters(
|
||||||
connection_interval * 1.25,
|
connection_parameters.connection_interval * 1.25,
|
||||||
peripheral_latency,
|
connection_parameters.peripheral_latency,
|
||||||
supervision_timeout * 10.0,
|
connection_parameters.supervision_timeout * 10.0,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
connection.parameters = Connection.Parameters(
|
connection.parameters = Connection.Parameters(
|
||||||
connection_interval * 1.25,
|
connection_parameters.connection_interval * 1.25,
|
||||||
peripheral_latency,
|
connection_parameters.peripheral_latency,
|
||||||
supervision_timeout * 10.0,
|
connection_parameters.supervision_timeout * 10.0,
|
||||||
connection.parameters.subrate_factor,
|
connection.parameters.subrate_factor,
|
||||||
connection.parameters.continuation_number,
|
connection.parameters.continuation_number,
|
||||||
)
|
)
|
||||||
@@ -6443,11 +6404,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
# [Classic only]
|
# [Classic only]
|
||||||
@host_event_handler
|
@host_event_handler
|
||||||
@with_connection_from_address
|
@with_connection_from_address
|
||||||
def on_role_change(
|
def on_role_change(self, connection: Connection, new_role: hci.Role):
|
||||||
self,
|
|
||||||
connection: Connection,
|
|
||||||
new_role: hci.Role,
|
|
||||||
):
|
|
||||||
connection.role = new_role
|
connection.role = new_role
|
||||||
connection.emit(connection.EVENT_ROLE_CHANGE, new_role)
|
connection.emit(connection.EVENT_ROLE_CHANGE, new_role)
|
||||||
|
|
||||||
@@ -6455,7 +6412,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
@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: Optional[Connection], address: hci.Address, error: int
|
self, connection: 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)
|
||||||
|
|||||||
+25
-10
@@ -26,7 +26,12 @@ from typing import TYPE_CHECKING, Any, Awaitable, Callable, Optional, Union, cas
|
|||||||
|
|
||||||
from bumble import drivers, hci, utils
|
from bumble import drivers, hci, utils
|
||||||
from bumble.colors import color
|
from bumble.colors import color
|
||||||
from bumble.core import ConnectionPHY, InvalidStateError, PhysicalTransport
|
from bumble.core import (
|
||||||
|
ConnectionParameters,
|
||||||
|
ConnectionPHY,
|
||||||
|
InvalidStateError,
|
||||||
|
PhysicalTransport,
|
||||||
|
)
|
||||||
from bumble.l2cap import L2CAP_PDU
|
from bumble.l2cap import L2CAP_PDU
|
||||||
from bumble.snoop import Snooper
|
from bumble.snoop import Snooper
|
||||||
from bumble.transport.common import TransportLostError
|
from bumble.transport.common import TransportLostError
|
||||||
@@ -550,7 +555,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}'
|
||||||
)
|
)
|
||||||
@@ -991,16 +996,20 @@ class Host(utils.EventEmitter):
|
|||||||
self.connections[event.connection_handle] = connection
|
self.connections[event.connection_handle] = connection
|
||||||
|
|
||||||
# Notify the client
|
# Notify the client
|
||||||
|
connection_parameters = ConnectionParameters(
|
||||||
|
event.connection_interval,
|
||||||
|
event.peripheral_latency,
|
||||||
|
event.supervision_timeout,
|
||||||
|
)
|
||||||
self.emit(
|
self.emit(
|
||||||
'le_connection',
|
'connection',
|
||||||
event.connection_handle,
|
event.connection_handle,
|
||||||
|
PhysicalTransport.LE,
|
||||||
event.peer_address,
|
event.peer_address,
|
||||||
getattr(event, 'local_resolvable_private_address', None),
|
getattr(event, 'local_resolvable_private_address', None),
|
||||||
getattr(event, 'peer_resolvable_private_address', None),
|
getattr(event, 'peer_resolvable_private_address', None),
|
||||||
hci.Role(event.role),
|
hci.Role(event.role),
|
||||||
event.connection_interval,
|
connection_parameters,
|
||||||
event.peripheral_latency,
|
|
||||||
event.supervision_timeout,
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.debug(f'### CONNECTION FAILED: {event.status}')
|
logger.debug(f'### CONNECTION FAILED: {event.status}')
|
||||||
@@ -1051,9 +1060,14 @@ class Host(utils.EventEmitter):
|
|||||||
|
|
||||||
# Notify the client
|
# Notify the client
|
||||||
self.emit(
|
self.emit(
|
||||||
'classic_connection',
|
'connection',
|
||||||
event.connection_handle,
|
event.connection_handle,
|
||||||
|
PhysicalTransport.BR_EDR,
|
||||||
event.bd_addr,
|
event.bd_addr,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.debug(f'### BR/EDR CONNECTION FAILED: {event.status}')
|
logger.debug(f'### BR/EDR CONNECTION FAILED: {event.status}')
|
||||||
@@ -1116,13 +1130,14 @@ class Host(utils.EventEmitter):
|
|||||||
|
|
||||||
# Notify the client
|
# Notify the client
|
||||||
if event.status == hci.HCI_SUCCESS:
|
if event.status == hci.HCI_SUCCESS:
|
||||||
self.emit(
|
connection_parameters = ConnectionParameters(
|
||||||
'connection_parameters_update',
|
|
||||||
connection.handle,
|
|
||||||
event.connection_interval,
|
event.connection_interval,
|
||||||
event.peripheral_latency,
|
event.peripheral_latency,
|
||||||
event.supervision_timeout,
|
event.supervision_timeout,
|
||||||
)
|
)
|
||||||
|
self.emit(
|
||||||
|
'connection_parameters_update', connection.handle, connection_parameters
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.emit(
|
self.emit(
|
||||||
'connection_parameters_update_failure', connection.handle, event.status
|
'connection_parameters_update_failure', connection.handle, event.status
|
||||||
|
|||||||
+4
-33
@@ -273,19 +273,12 @@ 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_connection(connection)
|
self.on_incoming_paired_connection(connection)
|
||||||
|
|
||||||
self.on_incoming_connection(connection)
|
if connection.peer_resolvable_address:
|
||||||
|
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,
|
||||||
@@ -322,30 +315,9 @@ class HearingAccessService(gatt.TemplateService):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_incoming_connection(self, connection: Connection):
|
def on_incoming_paired_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
|
||||||
@@ -485,7 +457,6 @@ 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)
|
||||||
|
|||||||
@@ -12,12 +12,13 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Imports
|
# Imports
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
import logging
|
from contextlib import asynccontextmanager
|
||||||
import os
|
|
||||||
import re
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from bumble import utils
|
from bumble import utils
|
||||||
@@ -84,14 +85,12 @@ async def open_transport(name: str) -> Transport:
|
|||||||
scheme, *tail = name.split(':', 1)
|
scheme, *tail = name.split(':', 1)
|
||||||
spec = tail[0] if tail else None
|
spec = tail[0] if tail else None
|
||||||
metadata = None
|
metadata = None
|
||||||
if spec and (m := re.search(r'\[(\w+=\w+(?:,\w+=\w+)*,?)\]', spec)):
|
if spec:
|
||||||
metadata_str = m.group(1)
|
# Metadata may precede the spec
|
||||||
if m.start() == 0:
|
if spec.startswith('['):
|
||||||
# <metadata><spec>
|
metadata_str, *tail = spec[1:].split(']')
|
||||||
spec = spec[m.end() :]
|
spec = tail[0] if tail else None
|
||||||
else:
|
metadata = dict([entry.split('=') for entry in metadata_str.split(',')])
|
||||||
spec = spec[: m.start()]
|
|
||||||
metadata = dict([entry.split('=') for entry in metadata_str.split(',')])
|
|
||||||
|
|
||||||
transport = await _open_transport(scheme, spec)
|
transport = await _open_transport(scheme, spec)
|
||||||
if metadata:
|
if metadata:
|
||||||
|
|||||||
@@ -131,11 +131,7 @@ 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")
|
||||||
try:
|
ini_file.unlink()
|
||||||
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,7 +17,6 @@
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import serial_asyncio
|
import serial_asyncio
|
||||||
|
|
||||||
@@ -29,56 +28,25 @@ 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][,delay]
|
<device-path>[,<speed>][,rtscts][,dsrdtr]
|
||||||
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]
|
||||||
@@ -87,16 +55,13 @@ 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(),
|
||||||
SerialPacketSource,
|
StreamPacketSource,
|
||||||
device,
|
device,
|
||||||
baudrate=speed,
|
baudrate=speed,
|
||||||
rtscts=rtscts,
|
rtscts=rtscts,
|
||||||
@@ -104,23 +69,4 @@ 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,18 +4,9 @@ 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:
|
The moniker syntax for a serial transport is: `serial:<device-path>[,<speed>]`
|
||||||
`<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 `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`
|
||||||
/dev/tty.usbmodem0006839912172
|
Opens the serial port `/dev/tty.usbmodem0006839912172` at `1000000`bps
|
||||||
/dev/tty.usbmodem0006839912172,1000000
|
|
||||||
/dev/tty.usbmodem0006839912172,rtscts
|
|
||||||
/dev/tty.usbmodem0006839912172,rtscts,delay
|
|
||||||
```
|
|
||||||
|
|||||||
Generated
+56
-140
@@ -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 0.48.0",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[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 0.48.0",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[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 1.9.3",
|
"indexmap",
|
||||||
"strsim",
|
"strsim",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
"textwrap",
|
"textwrap",
|
||||||
@@ -451,7 +451,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"option-ext",
|
"option-ext",
|
||||||
"redox_users",
|
"redox_users",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -483,19 +483,24 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "errno"
|
||||||
version = "1.0.2"
|
version = "0.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd"
|
||||||
|
dependencies = [
|
||||||
|
"errno-dragonfly",
|
||||||
|
"libc",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno-dragonfly"
|
||||||
version = "0.3.14"
|
version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.52.0",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -678,9 +683,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.3.27"
|
version = "0.3.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d"
|
checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
@@ -688,7 +693,7 @@ dependencies = [
|
|||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
"indexmap 2.11.3",
|
"indexmap",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
@@ -701,12 +706,6 @@ 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"
|
||||||
@@ -828,17 +827,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"hashbrown 0.12.3",
|
"hashbrown",
|
||||||
]
|
|
||||||
|
|
||||||
[[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]]
|
||||||
@@ -867,7 +856,7 @@ checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi 0.3.2",
|
"hermit-abi 0.3.2",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -902,9 +891,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.175"
|
version = "0.2.147"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libusb1-sys"
|
name = "libusb1-sys"
|
||||||
@@ -931,9 +920,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.4.15"
|
version = "0.4.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
@@ -1007,13 +996,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "0.8.11"
|
version = "0.8.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1084,9 +1073,9 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
version = "0.10.73"
|
version = "0.10.60"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
|
checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.0",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
@@ -1116,9 +1105,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-sys"
|
name = "openssl-sys"
|
||||||
version = "0.9.109"
|
version = "0.9.96"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
|
checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -1164,7 +1153,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"redox_syscall 0.3.5",
|
"redox_syscall 0.3.5",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"windows-targets 0.48.5",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1553,15 +1542,15 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.44"
|
version = "0.38.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
checksum = "ed6248e1caa625eb708e266e06159f135e8c26f2bb7ceb72dc4b2766d0340964"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.0",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1591,7 +1580,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 0.48.0",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1713,12 +1702,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.5.10"
|
version = "0.5.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
|
checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1784,7 +1773,7 @@ dependencies = [
|
|||||||
"fastrand",
|
"fastrand",
|
||||||
"redox_syscall 0.3.5",
|
"redox_syscall 0.3.5",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1839,9 +1828,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.38.2"
|
version = "1.32.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68722da18b0fc4a05fdc1120b302b82051265792a1e1b399086e9b204b10ad3d"
|
checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -1851,16 +1840,16 @@ dependencies = [
|
|||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"socket2 0.5.10",
|
"socket2 0.5.3",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-macros"
|
name = "tokio-macros"
|
||||||
version = "2.3.0"
|
version = "2.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
|
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2141,16 +2130,7 @@ 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 0.48.5",
|
"windows-targets",
|
||||||
]
|
|
||||||
|
|
||||||
[[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]]
|
||||||
@@ -2159,29 +2139,13 @@ 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 0.48.5",
|
"windows_aarch64_gnullvm",
|
||||||
"windows_aarch64_msvc 0.48.5",
|
"windows_aarch64_msvc",
|
||||||
"windows_i686_gnu 0.48.5",
|
"windows_i686_gnu",
|
||||||
"windows_i686_msvc 0.48.5",
|
"windows_i686_msvc",
|
||||||
"windows_x86_64_gnu 0.48.5",
|
"windows_x86_64_gnu",
|
||||||
"windows_x86_64_gnullvm 0.48.5",
|
"windows_x86_64_gnullvm",
|
||||||
"windows_x86_64_msvc 0.48.5",
|
"windows_x86_64_msvc",
|
||||||
]
|
|
||||||
|
|
||||||
[[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]]
|
||||||
@@ -2190,90 +2154,42 @@ 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"
|
||||||
@@ -2281,5 +2197,5 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
|
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|||||||
+3
-3
@@ -10,7 +10,7 @@ documentation = "https://docs.rs/crate/bumble"
|
|||||||
authors = ["Marshall Pierce <marshallpierce@google.com>"]
|
authors = ["Marshall Pierce <marshallpierce@google.com>"]
|
||||||
keywords = ["bluetooth", "ble"]
|
keywords = ["bluetooth", "ble"]
|
||||||
categories = ["api-bindings", "network-programming"]
|
categories = ["api-bindings", "network-programming"]
|
||||||
rust-version = "1.80.0"
|
rust-version = "1.76.0"
|
||||||
|
|
||||||
# https://github.com/frewsxcv/cargo-all-features#options
|
# https://github.com/frewsxcv/cargo-all-features#options
|
||||||
[package.metadata.cargo-all-features]
|
[package.metadata.cargo-all-features]
|
||||||
@@ -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.38.2", features = ["macros", "signal"] }
|
tokio = { version = "1.28.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.38.2", features = ["full"] }
|
tokio = { version = "1.28.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"
|
||||||
|
|||||||
+84
-242
@@ -18,12 +18,14 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import struct
|
|
||||||
from typing import Awaitable
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from bumble import a2dp
|
from bumble.a2dp import (
|
||||||
|
AacMediaCodecInformation,
|
||||||
|
OpusMediaCodecInformation,
|
||||||
|
SbcMediaCodecInformation,
|
||||||
|
)
|
||||||
from bumble.avdtp import (
|
from bumble.avdtp import (
|
||||||
A2DP_SBC_CODEC_TYPE,
|
A2DP_SBC_CODEC_TYPE,
|
||||||
AVDTP_AUDIO_MEDIA_TYPE,
|
AVDTP_AUDIO_MEDIA_TYPE,
|
||||||
@@ -80,24 +82,6 @@ class TwoDevices:
|
|||||||
self.paired[which] = keys
|
self.paired[which] = keys
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
class Data:
|
|
||||||
pointer: int = 0
|
|
||||||
data: bytes
|
|
||||||
|
|
||||||
def __init__(self, data: bytes):
|
|
||||||
self.data = data
|
|
||||||
|
|
||||||
async def read(self, length: int) -> Awaitable[bytes]:
|
|
||||||
def generate_read():
|
|
||||||
end = min(self.pointer + length, len(self.data))
|
|
||||||
chunk = self.data[self.pointer : end]
|
|
||||||
self.pointer = end
|
|
||||||
return chunk
|
|
||||||
|
|
||||||
return generate_read()
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_self_connection():
|
async def test_self_connection():
|
||||||
@@ -138,12 +122,12 @@ def source_codec_capabilities():
|
|||||||
return MediaCodecCapabilities(
|
return MediaCodecCapabilities(
|
||||||
media_type=AVDTP_AUDIO_MEDIA_TYPE,
|
media_type=AVDTP_AUDIO_MEDIA_TYPE,
|
||||||
media_codec_type=A2DP_SBC_CODEC_TYPE,
|
media_codec_type=A2DP_SBC_CODEC_TYPE,
|
||||||
media_codec_information=a2dp.SbcMediaCodecInformation(
|
media_codec_information=SbcMediaCodecInformation(
|
||||||
sampling_frequency=a2dp.SbcMediaCodecInformation.SamplingFrequency.SF_44100,
|
sampling_frequency=SbcMediaCodecInformation.SamplingFrequency.SF_44100,
|
||||||
channel_mode=a2dp.SbcMediaCodecInformation.ChannelMode.JOINT_STEREO,
|
channel_mode=SbcMediaCodecInformation.ChannelMode.JOINT_STEREO,
|
||||||
block_length=a2dp.SbcMediaCodecInformation.BlockLength.BL_16,
|
block_length=SbcMediaCodecInformation.BlockLength.BL_16,
|
||||||
subbands=a2dp.SbcMediaCodecInformation.Subbands.S_8,
|
subbands=SbcMediaCodecInformation.Subbands.S_8,
|
||||||
allocation_method=a2dp.SbcMediaCodecInformation.AllocationMethod.LOUDNESS,
|
allocation_method=SbcMediaCodecInformation.AllocationMethod.LOUDNESS,
|
||||||
minimum_bitpool_value=2,
|
minimum_bitpool_value=2,
|
||||||
maximum_bitpool_value=53,
|
maximum_bitpool_value=53,
|
||||||
),
|
),
|
||||||
@@ -155,23 +139,23 @@ def sink_codec_capabilities():
|
|||||||
return MediaCodecCapabilities(
|
return MediaCodecCapabilities(
|
||||||
media_type=AVDTP_AUDIO_MEDIA_TYPE,
|
media_type=AVDTP_AUDIO_MEDIA_TYPE,
|
||||||
media_codec_type=A2DP_SBC_CODEC_TYPE,
|
media_codec_type=A2DP_SBC_CODEC_TYPE,
|
||||||
media_codec_information=a2dp.SbcMediaCodecInformation(
|
media_codec_information=SbcMediaCodecInformation(
|
||||||
sampling_frequency=a2dp.SbcMediaCodecInformation.SamplingFrequency.SF_48000
|
sampling_frequency=SbcMediaCodecInformation.SamplingFrequency.SF_48000
|
||||||
| a2dp.SbcMediaCodecInformation.SamplingFrequency.SF_44100
|
| SbcMediaCodecInformation.SamplingFrequency.SF_44100
|
||||||
| a2dp.SbcMediaCodecInformation.SamplingFrequency.SF_32000
|
| SbcMediaCodecInformation.SamplingFrequency.SF_32000
|
||||||
| a2dp.SbcMediaCodecInformation.SamplingFrequency.SF_16000,
|
| SbcMediaCodecInformation.SamplingFrequency.SF_16000,
|
||||||
channel_mode=a2dp.SbcMediaCodecInformation.ChannelMode.MONO
|
channel_mode=SbcMediaCodecInformation.ChannelMode.MONO
|
||||||
| a2dp.SbcMediaCodecInformation.ChannelMode.DUAL_CHANNEL
|
| SbcMediaCodecInformation.ChannelMode.DUAL_CHANNEL
|
||||||
| a2dp.SbcMediaCodecInformation.ChannelMode.STEREO
|
| SbcMediaCodecInformation.ChannelMode.STEREO
|
||||||
| a2dp.SbcMediaCodecInformation.ChannelMode.JOINT_STEREO,
|
| SbcMediaCodecInformation.ChannelMode.JOINT_STEREO,
|
||||||
block_length=a2dp.SbcMediaCodecInformation.BlockLength.BL_4
|
block_length=SbcMediaCodecInformation.BlockLength.BL_4
|
||||||
| a2dp.SbcMediaCodecInformation.BlockLength.BL_8
|
| SbcMediaCodecInformation.BlockLength.BL_8
|
||||||
| a2dp.SbcMediaCodecInformation.BlockLength.BL_12
|
| SbcMediaCodecInformation.BlockLength.BL_12
|
||||||
| a2dp.SbcMediaCodecInformation.BlockLength.BL_16,
|
| SbcMediaCodecInformation.BlockLength.BL_16,
|
||||||
subbands=a2dp.SbcMediaCodecInformation.Subbands.S_4
|
subbands=SbcMediaCodecInformation.Subbands.S_4
|
||||||
| a2dp.SbcMediaCodecInformation.Subbands.S_8,
|
| SbcMediaCodecInformation.Subbands.S_8,
|
||||||
allocation_method=a2dp.SbcMediaCodecInformation.AllocationMethod.LOUDNESS
|
allocation_method=SbcMediaCodecInformation.AllocationMethod.LOUDNESS
|
||||||
| a2dp.SbcMediaCodecInformation.AllocationMethod.SNR,
|
| SbcMediaCodecInformation.AllocationMethod.SNR,
|
||||||
minimum_bitpool_value=2,
|
minimum_bitpool_value=2,
|
||||||
maximum_bitpool_value=53,
|
maximum_bitpool_value=53,
|
||||||
),
|
),
|
||||||
@@ -290,54 +274,52 @@ async def test_source_sink_1():
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
def test_sbc_codec_specific_information():
|
def test_sbc_codec_specific_information():
|
||||||
sbc_info = a2dp.SbcMediaCodecInformation.from_bytes(bytes.fromhex("3fff0235"))
|
sbc_info = SbcMediaCodecInformation.from_bytes(bytes.fromhex("3fff0235"))
|
||||||
assert (
|
assert (
|
||||||
sbc_info.sampling_frequency
|
sbc_info.sampling_frequency
|
||||||
== a2dp.SbcMediaCodecInformation.SamplingFrequency.SF_44100
|
== SbcMediaCodecInformation.SamplingFrequency.SF_44100
|
||||||
| a2dp.SbcMediaCodecInformation.SamplingFrequency.SF_48000
|
| SbcMediaCodecInformation.SamplingFrequency.SF_48000
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
sbc_info.channel_mode
|
sbc_info.channel_mode
|
||||||
== a2dp.SbcMediaCodecInformation.ChannelMode.MONO
|
== SbcMediaCodecInformation.ChannelMode.MONO
|
||||||
| a2dp.SbcMediaCodecInformation.ChannelMode.DUAL_CHANNEL
|
| SbcMediaCodecInformation.ChannelMode.DUAL_CHANNEL
|
||||||
| a2dp.SbcMediaCodecInformation.ChannelMode.STEREO
|
| SbcMediaCodecInformation.ChannelMode.STEREO
|
||||||
| a2dp.SbcMediaCodecInformation.ChannelMode.JOINT_STEREO
|
| SbcMediaCodecInformation.ChannelMode.JOINT_STEREO
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
sbc_info.block_length
|
sbc_info.block_length
|
||||||
== a2dp.SbcMediaCodecInformation.BlockLength.BL_4
|
== SbcMediaCodecInformation.BlockLength.BL_4
|
||||||
| a2dp.SbcMediaCodecInformation.BlockLength.BL_8
|
| SbcMediaCodecInformation.BlockLength.BL_8
|
||||||
| a2dp.SbcMediaCodecInformation.BlockLength.BL_12
|
| SbcMediaCodecInformation.BlockLength.BL_12
|
||||||
| a2dp.SbcMediaCodecInformation.BlockLength.BL_16
|
| SbcMediaCodecInformation.BlockLength.BL_16
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
sbc_info.subbands
|
sbc_info.subbands
|
||||||
== a2dp.SbcMediaCodecInformation.Subbands.S_4
|
== SbcMediaCodecInformation.Subbands.S_4 | SbcMediaCodecInformation.Subbands.S_8
|
||||||
| a2dp.SbcMediaCodecInformation.Subbands.S_8
|
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
sbc_info.allocation_method
|
sbc_info.allocation_method
|
||||||
== a2dp.SbcMediaCodecInformation.AllocationMethod.SNR
|
== SbcMediaCodecInformation.AllocationMethod.SNR
|
||||||
| a2dp.SbcMediaCodecInformation.AllocationMethod.LOUDNESS
|
| SbcMediaCodecInformation.AllocationMethod.LOUDNESS
|
||||||
)
|
)
|
||||||
assert sbc_info.minimum_bitpool_value == 2
|
assert sbc_info.minimum_bitpool_value == 2
|
||||||
assert sbc_info.maximum_bitpool_value == 53
|
assert sbc_info.maximum_bitpool_value == 53
|
||||||
|
|
||||||
sbc_info2 = a2dp.SbcMediaCodecInformation(
|
sbc_info2 = SbcMediaCodecInformation(
|
||||||
a2dp.SbcMediaCodecInformation.SamplingFrequency.SF_44100
|
SbcMediaCodecInformation.SamplingFrequency.SF_44100
|
||||||
| a2dp.SbcMediaCodecInformation.SamplingFrequency.SF_48000,
|
| SbcMediaCodecInformation.SamplingFrequency.SF_48000,
|
||||||
a2dp.SbcMediaCodecInformation.ChannelMode.MONO
|
SbcMediaCodecInformation.ChannelMode.MONO
|
||||||
| a2dp.SbcMediaCodecInformation.ChannelMode.DUAL_CHANNEL
|
| SbcMediaCodecInformation.ChannelMode.DUAL_CHANNEL
|
||||||
| a2dp.SbcMediaCodecInformation.ChannelMode.STEREO
|
| SbcMediaCodecInformation.ChannelMode.STEREO
|
||||||
| a2dp.SbcMediaCodecInformation.ChannelMode.JOINT_STEREO,
|
| SbcMediaCodecInformation.ChannelMode.JOINT_STEREO,
|
||||||
a2dp.SbcMediaCodecInformation.BlockLength.BL_4
|
SbcMediaCodecInformation.BlockLength.BL_4
|
||||||
| a2dp.SbcMediaCodecInformation.BlockLength.BL_8
|
| SbcMediaCodecInformation.BlockLength.BL_8
|
||||||
| a2dp.SbcMediaCodecInformation.BlockLength.BL_12
|
| SbcMediaCodecInformation.BlockLength.BL_12
|
||||||
| a2dp.SbcMediaCodecInformation.BlockLength.BL_16,
|
| SbcMediaCodecInformation.BlockLength.BL_16,
|
||||||
a2dp.SbcMediaCodecInformation.Subbands.S_4
|
SbcMediaCodecInformation.Subbands.S_4 | SbcMediaCodecInformation.Subbands.S_8,
|
||||||
| a2dp.SbcMediaCodecInformation.Subbands.S_8,
|
SbcMediaCodecInformation.AllocationMethod.SNR
|
||||||
a2dp.SbcMediaCodecInformation.AllocationMethod.SNR
|
| SbcMediaCodecInformation.AllocationMethod.LOUDNESS,
|
||||||
| a2dp.SbcMediaCodecInformation.AllocationMethod.LOUDNESS,
|
|
||||||
2,
|
2,
|
||||||
53,
|
53,
|
||||||
)
|
)
|
||||||
@@ -347,36 +329,36 @@ def test_sbc_codec_specific_information():
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
def test_aac_codec_specific_information():
|
def test_aac_codec_specific_information():
|
||||||
aac_info = a2dp.AacMediaCodecInformation.from_bytes(bytes.fromhex("f0018c83e800"))
|
aac_info = AacMediaCodecInformation.from_bytes(bytes.fromhex("f0018c83e800"))
|
||||||
assert (
|
assert (
|
||||||
aac_info.object_type
|
aac_info.object_type
|
||||||
== a2dp.AacMediaCodecInformation.ObjectType.MPEG_2_AAC_LC
|
== AacMediaCodecInformation.ObjectType.MPEG_2_AAC_LC
|
||||||
| a2dp.AacMediaCodecInformation.ObjectType.MPEG_4_AAC_LC
|
| AacMediaCodecInformation.ObjectType.MPEG_4_AAC_LC
|
||||||
| a2dp.AacMediaCodecInformation.ObjectType.MPEG_4_AAC_LTP
|
| AacMediaCodecInformation.ObjectType.MPEG_4_AAC_LTP
|
||||||
| a2dp.AacMediaCodecInformation.ObjectType.MPEG_4_AAC_SCALABLE
|
| AacMediaCodecInformation.ObjectType.MPEG_4_AAC_SCALABLE
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
aac_info.sampling_frequency
|
aac_info.sampling_frequency
|
||||||
== a2dp.AacMediaCodecInformation.SamplingFrequency.SF_44100
|
== AacMediaCodecInformation.SamplingFrequency.SF_44100
|
||||||
| a2dp.AacMediaCodecInformation.SamplingFrequency.SF_48000
|
| AacMediaCodecInformation.SamplingFrequency.SF_48000
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
aac_info.channels
|
aac_info.channels
|
||||||
== a2dp.AacMediaCodecInformation.Channels.MONO
|
== AacMediaCodecInformation.Channels.MONO
|
||||||
| a2dp.AacMediaCodecInformation.Channels.STEREO
|
| AacMediaCodecInformation.Channels.STEREO
|
||||||
)
|
)
|
||||||
assert aac_info.vbr == 1
|
assert aac_info.vbr == 1
|
||||||
assert aac_info.bitrate == 256000
|
assert aac_info.bitrate == 256000
|
||||||
|
|
||||||
aac_info2 = a2dp.AacMediaCodecInformation(
|
aac_info2 = AacMediaCodecInformation(
|
||||||
a2dp.AacMediaCodecInformation.ObjectType.MPEG_2_AAC_LC
|
AacMediaCodecInformation.ObjectType.MPEG_2_AAC_LC
|
||||||
| a2dp.AacMediaCodecInformation.ObjectType.MPEG_4_AAC_LC
|
| AacMediaCodecInformation.ObjectType.MPEG_4_AAC_LC
|
||||||
| a2dp.AacMediaCodecInformation.ObjectType.MPEG_4_AAC_LTP
|
| AacMediaCodecInformation.ObjectType.MPEG_4_AAC_LTP
|
||||||
| a2dp.AacMediaCodecInformation.ObjectType.MPEG_4_AAC_SCALABLE,
|
| AacMediaCodecInformation.ObjectType.MPEG_4_AAC_SCALABLE,
|
||||||
a2dp.AacMediaCodecInformation.SamplingFrequency.SF_44100
|
AacMediaCodecInformation.SamplingFrequency.SF_44100
|
||||||
| a2dp.AacMediaCodecInformation.SamplingFrequency.SF_48000,
|
| AacMediaCodecInformation.SamplingFrequency.SF_48000,
|
||||||
a2dp.AacMediaCodecInformation.Channels.MONO
|
AacMediaCodecInformation.Channels.MONO
|
||||||
| a2dp.AacMediaCodecInformation.Channels.STEREO,
|
| AacMediaCodecInformation.Channels.STEREO,
|
||||||
1,
|
1,
|
||||||
256000,
|
256000,
|
||||||
)
|
)
|
||||||
@@ -386,159 +368,25 @@ def test_aac_codec_specific_information():
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
def test_opus_codec_specific_information():
|
def test_opus_codec_specific_information():
|
||||||
opus_info = a2dp.OpusMediaCodecInformation.from_bytes(bytes([0x92]))
|
opus_info = OpusMediaCodecInformation.from_bytes(bytes([0x92]))
|
||||||
assert opus_info.vendor_id == a2dp.OpusMediaCodecInformation.VENDOR_ID
|
assert opus_info.vendor_id == OpusMediaCodecInformation.VENDOR_ID
|
||||||
assert opus_info.codec_id == a2dp.OpusMediaCodecInformation.CODEC_ID
|
assert opus_info.codec_id == OpusMediaCodecInformation.CODEC_ID
|
||||||
assert opus_info.frame_size == a2dp.OpusMediaCodecInformation.FrameSize.FS_20MS
|
assert opus_info.frame_size == OpusMediaCodecInformation.FrameSize.FS_20MS
|
||||||
assert opus_info.channel_mode == a2dp.OpusMediaCodecInformation.ChannelMode.STEREO
|
assert opus_info.channel_mode == OpusMediaCodecInformation.ChannelMode.STEREO
|
||||||
assert (
|
assert (
|
||||||
opus_info.sampling_frequency
|
opus_info.sampling_frequency
|
||||||
== a2dp.OpusMediaCodecInformation.SamplingFrequency.SF_48000
|
== OpusMediaCodecInformation.SamplingFrequency.SF_48000
|
||||||
)
|
)
|
||||||
|
|
||||||
opus_info2 = a2dp.OpusMediaCodecInformation(
|
opus_info2 = OpusMediaCodecInformation(
|
||||||
a2dp.OpusMediaCodecInformation.ChannelMode.STEREO,
|
OpusMediaCodecInformation.ChannelMode.STEREO,
|
||||||
a2dp.OpusMediaCodecInformation.FrameSize.FS_20MS,
|
OpusMediaCodecInformation.FrameSize.FS_20MS,
|
||||||
a2dp.OpusMediaCodecInformation.SamplingFrequency.SF_48000,
|
OpusMediaCodecInformation.SamplingFrequency.SF_48000,
|
||||||
)
|
)
|
||||||
assert opus_info2 == opus_info
|
assert opus_info2 == opus_info
|
||||||
assert opus_info2.value == bytes([0x92])
|
assert opus_info2.value == bytes([0x92])
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
async def test_sbc_parser():
|
|
||||||
header = b'\x9c\x80\x08\x00'
|
|
||||||
payload = b'\x00\x00\x00\x00\x00\x00'
|
|
||||||
data = Data(header + payload)
|
|
||||||
|
|
||||||
parser = a2dp.SbcParser(data.read)
|
|
||||||
async for frame in parser.frames:
|
|
||||||
assert frame.sampling_frequency == 44100
|
|
||||||
assert frame.block_count == 4
|
|
||||||
assert frame.channel_mode == 0
|
|
||||||
assert frame.allocation_method == 0
|
|
||||||
assert frame.subband_count == 4
|
|
||||||
assert frame.bitpool == 8
|
|
||||||
assert frame.payload == header + payload
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
async def test_sbc_packet_source():
|
|
||||||
header = b'\x9c\x80\x08\x00'
|
|
||||||
payload = b'\x00\x00\x00\x00\x00\x00'
|
|
||||||
data = Data((header + payload) * 2)
|
|
||||||
|
|
||||||
packet_source = a2dp.SbcPacketSource(data.read, 23)
|
|
||||||
async for packet in packet_source.packets:
|
|
||||||
assert packet.sequence_number == 0
|
|
||||||
assert packet.timestamp == 0
|
|
||||||
assert packet.payload == b'\x01' + header + payload
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
async def test_aac_parser():
|
|
||||||
header = b'\xff\xf0\x10\x00\x01\xa0\x00'
|
|
||||||
payload = b'\x00\x00\x00\x00\x00\x00'
|
|
||||||
data = Data(header + payload)
|
|
||||||
|
|
||||||
parser = a2dp.AacParser(data.read)
|
|
||||||
async for frame in parser.frames:
|
|
||||||
assert frame.profile == a2dp.AacFrame.Profile.MAIN
|
|
||||||
assert frame.sampling_frequency == 44100
|
|
||||||
assert frame.channel_configuration == 0
|
|
||||||
assert frame.payload == payload
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
async def test_aac_packet_source():
|
|
||||||
header = b'\xff\xf0\x10\x00\x01\xa0\x00'
|
|
||||||
payload = b'\x00\x00\x00\x00\x00\x00'
|
|
||||||
data = Data(header + payload)
|
|
||||||
|
|
||||||
packet_source = a2dp.AacPacketSource(data.read, 0)
|
|
||||||
async for packet in packet_source.packets:
|
|
||||||
assert packet.sequence_number == 0
|
|
||||||
assert packet.timestamp == 0
|
|
||||||
assert packet.payload == b' \x00\x12\x00\x00\x000\x00\x00\x00\x00\x00\x00'
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
async def test_opus_parser():
|
|
||||||
packed_header_data_revised = struct.pack(
|
|
||||||
"<QIIIB",
|
|
||||||
0, # granule_position
|
|
||||||
2, # bitstream_serial_number
|
|
||||||
2, # page_sequence_number
|
|
||||||
0, # crc_checksum
|
|
||||||
3, # page_segments
|
|
||||||
)
|
|
||||||
|
|
||||||
first_page_header_revised = (
|
|
||||||
b'OggS' # Capture pattern
|
|
||||||
+ b'\x00' # Version
|
|
||||||
+ b'\x02' # Header type
|
|
||||||
+ packed_header_data_revised
|
|
||||||
)
|
|
||||||
|
|
||||||
segment_table_revised = b'\x0a\x08\x0a'
|
|
||||||
|
|
||||||
opus_head_packet_data = b'OpusHead' + b'\x00' + b'\x00'
|
|
||||||
opus_tags_packet_data = b'OpusTags'
|
|
||||||
audio_data_packet = b'0123456789'
|
|
||||||
|
|
||||||
data = Data(
|
|
||||||
first_page_header_revised
|
|
||||||
+ segment_table_revised
|
|
||||||
+ opus_head_packet_data
|
|
||||||
+ opus_tags_packet_data
|
|
||||||
+ audio_data_packet
|
|
||||||
)
|
|
||||||
|
|
||||||
parser = a2dp.OpusParser(data.read)
|
|
||||||
async for packet in parser.packets:
|
|
||||||
assert packet.channel_mode == a2dp.OpusPacket.ChannelMode.STEREO
|
|
||||||
assert packet.payload == audio_data_packet
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
async def test_opus_packet_source():
|
|
||||||
packed_header_data_revised = struct.pack(
|
|
||||||
"<QIIIB",
|
|
||||||
0, # granule_position
|
|
||||||
2, # bitstream_serial_number
|
|
||||||
2, # page_sequence_number
|
|
||||||
0, # crc_checksum
|
|
||||||
3, # page_segments
|
|
||||||
)
|
|
||||||
|
|
||||||
first_page_header_revised = (
|
|
||||||
b'OggS' # Capture pattern
|
|
||||||
+ b'\x00' # Version
|
|
||||||
+ b'\x02' # Header type
|
|
||||||
+ packed_header_data_revised
|
|
||||||
)
|
|
||||||
|
|
||||||
segment_table_revised = b'\x0a\x08\x0a'
|
|
||||||
|
|
||||||
opus_head_packet_data = b'OpusHead' + b'\x00' + b'\x00'
|
|
||||||
opus_tags_packet_data = b'OpusTags'
|
|
||||||
audio_data_packet = b'0123456789'
|
|
||||||
|
|
||||||
data = Data(
|
|
||||||
first_page_header_revised
|
|
||||||
+ segment_table_revised
|
|
||||||
+ opus_head_packet_data
|
|
||||||
+ opus_tags_packet_data
|
|
||||||
+ audio_data_packet
|
|
||||||
)
|
|
||||||
|
|
||||||
parser = a2dp.OpusPacketSource(data.read, 0)
|
|
||||||
async for packet in parser.packets:
|
|
||||||
assert packet.sequence_number == 0
|
|
||||||
assert packet.timestamp == 0
|
|
||||||
assert packet.payload == b'\x01' + audio_data_packet
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
async def async_main():
|
async def async_main():
|
||||||
test_sbc_codec_specific_information()
|
test_sbc_codec_specific_information()
|
||||||
@@ -546,12 +394,6 @@ async def async_main():
|
|||||||
test_opus_codec_specific_information()
|
test_opus_codec_specific_information()
|
||||||
await test_self_connection()
|
await test_self_connection()
|
||||||
await test_source_sink_1()
|
await test_source_sink_1()
|
||||||
test_sbc_parser()
|
|
||||||
test_sbc_packet_source()
|
|
||||||
test_aac_parser()
|
|
||||||
test_aac_packet_source()
|
|
||||||
test_opus_parser()
|
|
||||||
test_opus_packet_source()
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
+21
-15
@@ -310,12 +310,12 @@ async def test_pacs():
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_ascs():
|
async def test_ascs():
|
||||||
devices = TwoDevices()
|
devices = TwoDevices()
|
||||||
devices[1].add_service(
|
devices[0].add_service(
|
||||||
AudioStreamControlService(device=devices[1], sink_ase_id=[1, 2])
|
AudioStreamControlService(device=devices[0], sink_ase_id=[1, 2])
|
||||||
)
|
)
|
||||||
|
|
||||||
await devices.setup_connection()
|
await devices.setup_connection()
|
||||||
peer = device.Peer(devices.connections[0])
|
peer = device.Peer(devices.connections[1])
|
||||||
ascs_client = await peer.discover_service_and_create_proxy(
|
ascs_client = await peer.discover_service_and_create_proxy(
|
||||||
AudioStreamControlServiceProxy
|
AudioStreamControlServiceProxy
|
||||||
)
|
)
|
||||||
@@ -369,7 +369,7 @@ async def test_ascs():
|
|||||||
await ascs_client.ase_control_point.write_value(
|
await ascs_client.ase_control_point.write_value(
|
||||||
ASE_Config_QOS(
|
ASE_Config_QOS(
|
||||||
ase_id=[1, 2],
|
ase_id=[1, 2],
|
||||||
cig_id=[1, 1],
|
cig_id=[1, 2],
|
||||||
cis_id=[3, 4],
|
cis_id=[3, 4],
|
||||||
sdu_interval=[5, 6],
|
sdu_interval=[5, 6],
|
||||||
framing=[0, 1],
|
framing=[0, 1],
|
||||||
@@ -402,19 +402,25 @@ async def test_ascs():
|
|||||||
)
|
)
|
||||||
|
|
||||||
# CIS establishment
|
# CIS establishment
|
||||||
cis_handles = await devices[0].setup_cig(
|
devices[0].emit(
|
||||||
device.CigParameters(
|
'cis_establishment',
|
||||||
|
device.CisLink(
|
||||||
|
device=devices[0],
|
||||||
|
acl_connection=devices.connections[0],
|
||||||
|
handle=5,
|
||||||
|
cis_id=3,
|
||||||
cig_id=1,
|
cig_id=1,
|
||||||
cis_parameters=[
|
),
|
||||||
device.CigParameters.CisParameters(cis_id=3),
|
|
||||||
device.CigParameters.CisParameters(cis_id=4),
|
|
||||||
],
|
|
||||||
sdu_interval_c_to_p=0,
|
|
||||||
sdu_interval_p_to_c=0,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
await devices[0].create_cis(
|
devices[0].emit(
|
||||||
[(cis_handle, devices.connections[0]) for cis_handle in cis_handles]
|
'cis_establishment',
|
||||||
|
device.CisLink(
|
||||||
|
device=devices[0],
|
||||||
|
acl_connection=devices.connections[0],
|
||||||
|
handle=6,
|
||||||
|
cis_id=4,
|
||||||
|
cig_id=2,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
assert (await notifications[1].get())[:2] == bytes(
|
assert (await notifications[1].get())[:2] == bytes(
|
||||||
[1, AseStateMachine.State.STREAMING]
|
[1, AseStateMachine.State.STREAMING]
|
||||||
|
|||||||
+10
-41
@@ -24,7 +24,7 @@ from unittest import mock
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from bumble import device, gatt, hci, utils
|
from bumble import device, gatt, hci, utils
|
||||||
from bumble.core import PhysicalTransport
|
from bumble.core import ConnectionParameters, PhysicalTransport
|
||||||
from bumble.device import (
|
from bumble.device import (
|
||||||
AdvertisingEventProperties,
|
AdvertisingEventProperties,
|
||||||
AdvertisingParameters,
|
AdvertisingParameters,
|
||||||
@@ -289,15 +289,14 @@ async def test_legacy_advertising_disconnection(auto_restart):
|
|||||||
await device.power_on()
|
await device.power_on()
|
||||||
peer_address = Address('F0:F1:F2:F3:F4:F5')
|
peer_address = Address('F0:F1:F2:F3:F4:F5')
|
||||||
await device.start_advertising(auto_restart=auto_restart)
|
await device.start_advertising(auto_restart=auto_restart)
|
||||||
device.on_le_connection(
|
device.on_connection(
|
||||||
0x0001,
|
0x0001,
|
||||||
|
PhysicalTransport.LE,
|
||||||
peer_address,
|
peer_address,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
Role.PERIPHERAL,
|
Role.PERIPHERAL,
|
||||||
0,
|
ConnectionParameters(0, 0, 0),
|
||||||
0,
|
|
||||||
0,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
device.on_advertising_set_termination(
|
device.on_advertising_set_termination(
|
||||||
@@ -348,15 +347,14 @@ async def test_extended_advertising_connection(own_address_type):
|
|||||||
advertising_set = await device.create_advertising_set(
|
advertising_set = await device.create_advertising_set(
|
||||||
advertising_parameters=AdvertisingParameters(own_address_type=own_address_type)
|
advertising_parameters=AdvertisingParameters(own_address_type=own_address_type)
|
||||||
)
|
)
|
||||||
device.on_le_connection(
|
device.on_connection(
|
||||||
0x0001,
|
0x0001,
|
||||||
|
PhysicalTransport.LE,
|
||||||
peer_address,
|
peer_address,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
Role.PERIPHERAL,
|
Role.PERIPHERAL,
|
||||||
0,
|
ConnectionParameters(0, 0, 0),
|
||||||
0,
|
|
||||||
0,
|
|
||||||
)
|
)
|
||||||
device.on_advertising_set_termination(
|
device.on_advertising_set_termination(
|
||||||
HCI_SUCCESS,
|
HCI_SUCCESS,
|
||||||
@@ -393,15 +391,14 @@ async def test_extended_advertising_connection_out_of_order(own_address_type):
|
|||||||
0x0001,
|
0x0001,
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
device.on_le_connection(
|
device.on_connection(
|
||||||
0x0001,
|
0x0001,
|
||||||
|
PhysicalTransport.LE,
|
||||||
Address('F0:F1:F2:F3:F4:F5'),
|
Address('F0:F1:F2:F3:F4:F5'),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
Role.PERIPHERAL,
|
Role.PERIPHERAL,
|
||||||
0,
|
ConnectionParameters(0, 0, 0),
|
||||||
0,
|
|
||||||
0,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if own_address_type == OwnAddressType.PUBLIC:
|
if own_address_type == OwnAddressType.PUBLIC:
|
||||||
@@ -761,34 +758,6 @@ 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()
|
||||||
|
|||||||
+1
-3
@@ -82,6 +82,7 @@ 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
|
||||||
@@ -92,9 +93,6 @@ 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
|
||||||
)
|
)
|
||||||
|
|||||||
+5
-32
@@ -24,7 +24,7 @@ import sys
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from bumble import controller, device, hci, link, transport
|
from bumble import controller, device, hci, link, transport
|
||||||
from bumble.transport import common
|
from bumble.transport.common import PacketParser
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -61,9 +61,9 @@ class Sink:
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
def test_parser():
|
def test_parser():
|
||||||
sink1 = Sink()
|
sink1 = Sink()
|
||||||
parser1 = common.PacketParser(sink1)
|
parser1 = PacketParser(sink1)
|
||||||
sink2 = Sink()
|
sink2 = Sink()
|
||||||
parser2 = common.PacketParser(sink2)
|
parser2 = PacketParser(sink2)
|
||||||
|
|
||||||
for parser in [parser1, parser2]:
|
for parser in [parser1, parser2]:
|
||||||
with open(
|
with open(
|
||||||
@@ -82,7 +82,7 @@ def test_parser():
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
def test_parser_extensions():
|
def test_parser_extensions():
|
||||||
sink = Sink()
|
sink = Sink()
|
||||||
parser = common.PacketParser(sink)
|
parser = PacketParser(sink)
|
||||||
|
|
||||||
# Check that an exception is thrown for an unknown type
|
# Check that an exception is thrown for an unknown type
|
||||||
try:
|
try:
|
||||||
@@ -206,7 +206,7 @@ async def test_unix_connection_abstract():
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"address,",
|
"address,",
|
||||||
("127.0.0.1", "[::1]"),
|
("127.0.0.1",),
|
||||||
)
|
)
|
||||||
async def test_android_netsim_connection(address):
|
async def test_android_netsim_connection(address):
|
||||||
controller_transport = await transport.open_transport(
|
controller_transport = await transport.open_transport(
|
||||||
@@ -222,33 +222,6 @@ async def test_android_netsim_connection(address):
|
|||||||
await client_device.power_on()
|
await client_device.power_on()
|
||||||
|
|
||||||
await client_transport.close()
|
await client_transport.close()
|
||||||
await controller_transport.source.grpc_server.stop(None)
|
|
||||||
await controller_transport.close()
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"spec,",
|
|
||||||
(
|
|
||||||
"android-netsim:[::1]:{port},mode=host[a=b,c=d]",
|
|
||||||
"android-netsim:localhost:{port},mode=host[a=b,c=d]",
|
|
||||||
"android-netsim:[a=b,c=d][::1]:{port},mode=host",
|
|
||||||
"android-netsim:[a=b,c=d]localhost:{port},mode=host",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
async def test_open_transport_with_metadata(spec):
|
|
||||||
controller_transport = await transport.open_transport(
|
|
||||||
"android-netsim:_:0,mode=controller"
|
|
||||||
)
|
|
||||||
port = controller_transport.source.port
|
|
||||||
_make_controller_from_transport(controller_transport)
|
|
||||||
|
|
||||||
client_transport = await transport.open_transport(spec.format(port=port))
|
|
||||||
assert client_transport.source.metadata['a'] == 'b'
|
|
||||||
assert client_transport.source.metadata['c'] == 'd'
|
|
||||||
|
|
||||||
await client_transport.close()
|
|
||||||
await controller_transport.source.grpc_server.stop(None)
|
|
||||||
await controller_transport.close()
|
await controller_transport.close()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user