forked from auracaster/bumble_mirror
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 30f89d5739 | |||
| 481cf40831 | |||
| d8e6700611 | |||
| 56eb5a933b | |||
| caacc0c133 | |||
| 5f377c024b | |||
| aeeff18428 | |||
| 8d46bc04d2 |
Vendored
+1
@@ -33,6 +33,7 @@
|
||||
"dhkey",
|
||||
"diversifier",
|
||||
"endianness",
|
||||
"ESCO",
|
||||
"Fitbit",
|
||||
"GATTLINK",
|
||||
"HANDSFREE",
|
||||
|
||||
@@ -513,6 +513,7 @@ class Ping:
|
||||
await self.packet_io.send_packet(bytes([PacketType.RESET]))
|
||||
|
||||
self.current_packet_index = 0
|
||||
self.latencies = []
|
||||
await self.send_next_ping()
|
||||
|
||||
await self.done.wait()
|
||||
|
||||
+31
-5
@@ -18,9 +18,11 @@
|
||||
import asyncio
|
||||
import os
|
||||
import logging
|
||||
import click
|
||||
from bumble.company_ids import COMPANY_IDENTIFIERS
|
||||
import time
|
||||
|
||||
import click
|
||||
|
||||
from bumble.company_ids import COMPANY_IDENTIFIERS
|
||||
from bumble.colors import color
|
||||
from bumble.core import name_or_number
|
||||
from bumble.hci import (
|
||||
@@ -48,6 +50,7 @@ from bumble.hci import (
|
||||
HCI_LE_Read_Maximum_Advertising_Data_Length_Command,
|
||||
HCI_LE_READ_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND,
|
||||
HCI_LE_Read_Suggested_Default_Data_Length_Command,
|
||||
HCI_Read_Local_Version_Information_Command,
|
||||
)
|
||||
from bumble.host import Host
|
||||
from bumble.transport import open_transport_or_link
|
||||
@@ -166,7 +169,7 @@ async def get_acl_flow_control_info(host: Host) -> None:
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
async def async_main(transport):
|
||||
async def async_main(latency_probes, transport):
|
||||
print('<<< connecting to HCI...')
|
||||
async with await open_transport_or_link(transport) as (hci_source, hci_sink):
|
||||
print('<<< connected')
|
||||
@@ -174,6 +177,23 @@ async def async_main(transport):
|
||||
host = Host(hci_source, hci_sink)
|
||||
await host.reset()
|
||||
|
||||
# Measure the latency if requested
|
||||
latencies = []
|
||||
if latency_probes:
|
||||
for _ in range(latency_probes):
|
||||
start = time.time()
|
||||
await host.send_command(HCI_Read_Local_Version_Information_Command())
|
||||
latencies.append(1000 * (time.time() - start))
|
||||
print(
|
||||
color('HCI Command Latency:', 'yellow'),
|
||||
(
|
||||
f'min={min(latencies):.2f}, '
|
||||
f'max={max(latencies):.2f}, '
|
||||
f'average={sum(latencies)/len(latencies):.2f}'
|
||||
),
|
||||
'\n',
|
||||
)
|
||||
|
||||
# Print version
|
||||
print(color('Version:', 'yellow'))
|
||||
print(
|
||||
@@ -209,10 +229,16 @@ async def async_main(transport):
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@click.command()
|
||||
@click.option(
|
||||
'--latency-probes',
|
||||
metavar='N',
|
||||
type=int,
|
||||
help='Send N commands to measure HCI transport latency statistics',
|
||||
)
|
||||
@click.argument('transport')
|
||||
def main(transport):
|
||||
def main(latency_probes, transport):
|
||||
logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'WARNING').upper())
|
||||
asyncio.run(async_main(transport))
|
||||
asyncio.run(async_main(latency_probes, transport))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
+131
-23
@@ -19,6 +19,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import asyncio
|
||||
import dataclasses
|
||||
import itertools
|
||||
import random
|
||||
import struct
|
||||
@@ -42,6 +43,7 @@ from bumble.hci import (
|
||||
HCI_LE_1M_PHY,
|
||||
HCI_SUCCESS,
|
||||
HCI_UNKNOWN_HCI_COMMAND_ERROR,
|
||||
HCI_UNKNOWN_CONNECTION_IDENTIFIER_ERROR,
|
||||
HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR,
|
||||
HCI_VERSION_BLUETOOTH_CORE_5_0,
|
||||
Address,
|
||||
@@ -53,6 +55,7 @@ from bumble.hci import (
|
||||
HCI_Connection_Request_Event,
|
||||
HCI_Disconnection_Complete_Event,
|
||||
HCI_Encryption_Change_Event,
|
||||
HCI_Synchronous_Connection_Complete_Event,
|
||||
HCI_LE_Advertising_Report_Event,
|
||||
HCI_LE_Connection_Complete_Event,
|
||||
HCI_LE_Read_Remote_Features_Complete_Event,
|
||||
@@ -60,10 +63,11 @@ from bumble.hci import (
|
||||
HCI_Packet,
|
||||
HCI_Role_Change_Event,
|
||||
)
|
||||
from typing import Optional, Union, Dict, TYPE_CHECKING
|
||||
from typing import Optional, Union, Dict, Any, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bumble.transport.common import TransportSink, TransportSource
|
||||
from bumble.link import LocalLink
|
||||
from bumble.transport.common import TransportSink
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
@@ -79,15 +83,18 @@ class DataObject:
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@dataclasses.dataclass
|
||||
class Connection:
|
||||
def __init__(self, controller, handle, role, peer_address, link, transport):
|
||||
self.controller = controller
|
||||
self.handle = handle
|
||||
self.role = role
|
||||
self.peer_address = peer_address
|
||||
self.link = link
|
||||
controller: Controller
|
||||
handle: int
|
||||
role: int
|
||||
peer_address: Address
|
||||
link: Any
|
||||
transport: int
|
||||
link_type: int
|
||||
|
||||
def __post_init__(self):
|
||||
self.assembler = HCI_AclDataPacketAssembler(self.on_acl_pdu)
|
||||
self.transport = transport
|
||||
|
||||
def on_hci_acl_data_packet(self, packet):
|
||||
self.assembler.feed_packet(packet)
|
||||
@@ -106,10 +113,10 @@ class Connection:
|
||||
class Controller:
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
name: str,
|
||||
host_source=None,
|
||||
host_sink: Optional[TransportSink] = None,
|
||||
link=None,
|
||||
link: Optional[LocalLink] = None,
|
||||
public_address: Optional[Union[bytes, str, Address]] = None,
|
||||
):
|
||||
self.name = name
|
||||
@@ -359,12 +366,13 @@ class Controller:
|
||||
if connection is None:
|
||||
connection_handle = self.allocate_connection_handle()
|
||||
connection = Connection(
|
||||
self,
|
||||
connection_handle,
|
||||
BT_PERIPHERAL_ROLE,
|
||||
peer_address,
|
||||
self.link,
|
||||
BT_LE_TRANSPORT,
|
||||
controller=self,
|
||||
handle=connection_handle,
|
||||
role=BT_PERIPHERAL_ROLE,
|
||||
peer_address=peer_address,
|
||||
link=self.link,
|
||||
transport=BT_LE_TRANSPORT,
|
||||
link_type=HCI_Connection_Complete_Event.ACL_LINK_TYPE,
|
||||
)
|
||||
self.peripheral_connections[peer_address] = connection
|
||||
logger.debug(f'New PERIPHERAL connection handle: 0x{connection_handle:04X}')
|
||||
@@ -418,12 +426,13 @@ class Controller:
|
||||
if connection is None:
|
||||
connection_handle = self.allocate_connection_handle()
|
||||
connection = Connection(
|
||||
self,
|
||||
connection_handle,
|
||||
BT_CENTRAL_ROLE,
|
||||
peer_address,
|
||||
self.link,
|
||||
BT_LE_TRANSPORT,
|
||||
controller=self,
|
||||
handle=connection_handle,
|
||||
role=BT_CENTRAL_ROLE,
|
||||
peer_address=peer_address,
|
||||
link=self.link,
|
||||
transport=BT_LE_TRANSPORT,
|
||||
link_type=HCI_Connection_Complete_Event.ACL_LINK_TYPE,
|
||||
)
|
||||
self.central_connections[peer_address] = connection
|
||||
logger.debug(
|
||||
@@ -568,6 +577,7 @@ class Controller:
|
||||
peer_address=peer_address,
|
||||
link=self.link,
|
||||
transport=BT_BR_EDR_TRANSPORT,
|
||||
link_type=HCI_Connection_Complete_Event.ACL_LINK_TYPE,
|
||||
)
|
||||
self.classic_connections[peer_address] = connection
|
||||
logger.debug(
|
||||
@@ -621,6 +631,42 @@ class Controller:
|
||||
)
|
||||
)
|
||||
|
||||
def on_classic_sco_connection_complete(
|
||||
self, peer_address: Address, status: int, link_type: int
|
||||
):
|
||||
if status == HCI_SUCCESS:
|
||||
# Allocate (or reuse) a connection handle
|
||||
connection_handle = self.allocate_connection_handle()
|
||||
connection = Connection(
|
||||
controller=self,
|
||||
handle=connection_handle,
|
||||
# Role doesn't matter in SCO.
|
||||
role=BT_CENTRAL_ROLE,
|
||||
peer_address=peer_address,
|
||||
link=self.link,
|
||||
transport=BT_BR_EDR_TRANSPORT,
|
||||
link_type=link_type,
|
||||
)
|
||||
self.classic_connections[peer_address] = connection
|
||||
logger.debug(f'New SCO connection handle: 0x{connection_handle:04X}')
|
||||
else:
|
||||
connection_handle = 0
|
||||
|
||||
self.send_hci_packet(
|
||||
HCI_Synchronous_Connection_Complete_Event(
|
||||
status=status,
|
||||
connection_handle=connection_handle,
|
||||
bd_addr=peer_address,
|
||||
link_type=link_type,
|
||||
# TODO: Provide SCO connection parameters.
|
||||
transmission_interval=0,
|
||||
retransmission_window=0,
|
||||
rx_packet_length=0,
|
||||
tx_packet_length=0,
|
||||
air_mode=0,
|
||||
)
|
||||
)
|
||||
|
||||
############################################################
|
||||
# Advertising support
|
||||
############################################################
|
||||
@@ -740,6 +786,68 @@ class Controller:
|
||||
)
|
||||
self.link.classic_accept_connection(self, command.bd_addr, command.role)
|
||||
|
||||
def on_hci_enhanced_setup_synchronous_connection_command(self, command):
|
||||
'''
|
||||
See Bluetooth spec Vol 4, Part E - 7.1.45 Enhanced Setup Synchronous Connection command
|
||||
'''
|
||||
|
||||
if self.link is None:
|
||||
return
|
||||
|
||||
if not (
|
||||
connection := self.find_classic_connection_by_handle(
|
||||
command.connection_handle
|
||||
)
|
||||
):
|
||||
self.send_hci_packet(
|
||||
HCI_Command_Status_Event(
|
||||
status=HCI_UNKNOWN_CONNECTION_IDENTIFIER_ERROR,
|
||||
num_hci_command_packets=1,
|
||||
command_opcode=command.op_code,
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
self.send_hci_packet(
|
||||
HCI_Command_Status_Event(
|
||||
status=HCI_SUCCESS,
|
||||
num_hci_command_packets=1,
|
||||
command_opcode=command.op_code,
|
||||
)
|
||||
)
|
||||
self.link.classic_sco_connect(
|
||||
self, connection.peer_address, HCI_Connection_Complete_Event.ESCO_LINK_TYPE
|
||||
)
|
||||
|
||||
def on_hci_enhanced_accept_synchronous_connection_request_command(self, command):
|
||||
'''
|
||||
See Bluetooth spec Vol 4, Part E - 7.1.46 Enhanced Accept Synchronous Connection Request command
|
||||
'''
|
||||
|
||||
if self.link is None:
|
||||
return
|
||||
|
||||
if not (connection := self.find_classic_connection_by_address(command.bd_addr)):
|
||||
self.send_hci_packet(
|
||||
HCI_Command_Status_Event(
|
||||
status=HCI_UNKNOWN_CONNECTION_IDENTIFIER_ERROR,
|
||||
num_hci_command_packets=1,
|
||||
command_opcode=command.op_code,
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
self.send_hci_packet(
|
||||
HCI_Command_Status_Event(
|
||||
status=HCI_SUCCESS,
|
||||
num_hci_command_packets=1,
|
||||
command_opcode=command.op_code,
|
||||
)
|
||||
)
|
||||
self.link.classic_accept_sco_connection(
|
||||
self, connection.peer_address, HCI_Connection_Complete_Event.ESCO_LINK_TYPE
|
||||
)
|
||||
|
||||
def on_hci_switch_role_command(self, command):
|
||||
'''
|
||||
See Bluetooth spec Vol 4, Part E - 7.2.8 Switch Role command
|
||||
|
||||
+15
-3
@@ -61,7 +61,6 @@ from .hci import (
|
||||
HCI_LE_1M_PHY_BIT,
|
||||
HCI_LE_2M_PHY,
|
||||
HCI_LE_2M_PHY_LE_SUPPORTED_FEATURE,
|
||||
HCI_LE_CLEAR_RESOLVING_LIST_COMMAND,
|
||||
HCI_LE_CODED_PHY,
|
||||
HCI_LE_CODED_PHY_BIT,
|
||||
HCI_LE_CODED_PHY_LE_SUPPORTED_FEATURE,
|
||||
@@ -86,7 +85,7 @@ from .hci import (
|
||||
HCI_Constant,
|
||||
HCI_Create_Connection_Cancel_Command,
|
||||
HCI_Create_Connection_Command,
|
||||
HCI_Create_Connection_Command,
|
||||
HCI_Connection_Complete_Event,
|
||||
HCI_Disconnect_Command,
|
||||
HCI_Encryption_Change_Event,
|
||||
HCI_Error,
|
||||
@@ -3319,8 +3318,21 @@ class Device(CompositeEventEmitter):
|
||||
def on_connection_request(self, bd_addr, class_of_device, link_type):
|
||||
logger.debug(f'*** Connection request: {bd_addr}')
|
||||
|
||||
# Handle SCO request.
|
||||
if link_type in (
|
||||
HCI_Connection_Complete_Event.SCO_LINK_TYPE,
|
||||
HCI_Connection_Complete_Event.ESCO_LINK_TYPE,
|
||||
):
|
||||
if connection := self.find_connection_by_bd_addr(
|
||||
bd_addr, transport=BT_BR_EDR_TRANSPORT
|
||||
):
|
||||
self.emit('sco_request', connection, link_type)
|
||||
else:
|
||||
logger.error(f'SCO request from a non-connected device {bd_addr}')
|
||||
return
|
||||
|
||||
# match a pending future using `bd_addr`
|
||||
if bd_addr in self.classic_pending_accepts:
|
||||
elif bd_addr in self.classic_pending_accepts:
|
||||
future, *_ = self.classic_pending_accepts.pop(bd_addr)
|
||||
future.set_result((bd_addr, class_of_device, link_type))
|
||||
|
||||
|
||||
+5
-1
@@ -4765,7 +4765,11 @@ class HCI_Event(HCI_Packet):
|
||||
HCI_Object.init_from_bytes(self, parameters, 0, fields)
|
||||
return self
|
||||
|
||||
def __init__(self, event_code, parameters=None, **kwargs):
|
||||
def __init__(self, event_code=-1, parameters=None, **kwargs):
|
||||
# Since the legacy implementation relies on an __init__ injector, typing always
|
||||
# complains that positional argument event_code is not passed, so here sets a
|
||||
# default value to allow building derived HCI_Event without event_code.
|
||||
assert event_code != -1
|
||||
super().__init__(HCI_Event.event_name(event_code))
|
||||
if (fields := getattr(self, 'fields', None)) and kwargs:
|
||||
HCI_Object.init_from_fields(self, fields, kwargs)
|
||||
|
||||
+3
-2
@@ -18,7 +18,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, MutableMapping
|
||||
from typing import cast, Any
|
||||
from typing import cast, Any, Optional
|
||||
import logging
|
||||
|
||||
from bumble import avdtp
|
||||
@@ -69,7 +69,7 @@ PSM_NAMES = {
|
||||
class PacketTracer:
|
||||
class AclStream:
|
||||
psms: MutableMapping[int, int]
|
||||
peer: PacketTracer.AclStream
|
||||
peer: Optional[PacketTracer.AclStream]
|
||||
avdtp_assemblers: MutableMapping[int, avdtp.MessageAssembler]
|
||||
|
||||
def __init__(self, analyzer: PacketTracer.Analyzer) -> None:
|
||||
@@ -77,6 +77,7 @@ class PacketTracer:
|
||||
self.packet_assembler = HCI_AclDataPacketAssembler(self.on_acl_pdu)
|
||||
self.avdtp_assemblers = {} # AVDTP assemblers, by source_cid
|
||||
self.psms = {} # PSM, by source_cid
|
||||
self.peer = None
|
||||
|
||||
# pylint: disable=too-many-nested-blocks
|
||||
def on_acl_pdu(self, pdu: bytes) -> None:
|
||||
|
||||
+55
-1
@@ -26,9 +26,13 @@ from bumble.hci import (
|
||||
HCI_SUCCESS,
|
||||
HCI_CONNECTION_ACCEPT_TIMEOUT_ERROR,
|
||||
HCI_CONNECTION_TIMEOUT_ERROR,
|
||||
HCI_UNKNOWN_CONNECTION_IDENTIFIER_ERROR,
|
||||
HCI_PAGE_TIMEOUT_ERROR,
|
||||
HCI_Connection_Complete_Event,
|
||||
)
|
||||
from bumble import controller
|
||||
|
||||
from typing import Optional, Set
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
@@ -57,6 +61,8 @@ class LocalLink:
|
||||
Link bus for controllers to communicate with each other
|
||||
'''
|
||||
|
||||
controllers: Set[controller.Controller]
|
||||
|
||||
def __init__(self):
|
||||
self.controllers = set()
|
||||
self.pending_connection = None
|
||||
@@ -79,7 +85,9 @@ class LocalLink:
|
||||
return controller
|
||||
return None
|
||||
|
||||
def find_classic_controller(self, address):
|
||||
def find_classic_controller(
|
||||
self, address: Address
|
||||
) -> Optional[controller.Controller]:
|
||||
for controller in self.controllers:
|
||||
if controller.public_address == address:
|
||||
return controller
|
||||
@@ -271,6 +279,52 @@ class LocalLink:
|
||||
initiator_controller.public_address, int(not (initiator_new_role))
|
||||
)
|
||||
|
||||
def classic_sco_connect(
|
||||
self,
|
||||
initiator_controller: controller.Controller,
|
||||
responder_address: Address,
|
||||
link_type: int,
|
||||
):
|
||||
logger.debug(
|
||||
f'[Classic] {initiator_controller.public_address} connects SCO to {responder_address}'
|
||||
)
|
||||
responder_controller = self.find_classic_controller(responder_address)
|
||||
# Initiator controller should handle it.
|
||||
assert responder_controller
|
||||
|
||||
responder_controller.on_classic_connection_request(
|
||||
initiator_controller.public_address,
|
||||
link_type,
|
||||
)
|
||||
|
||||
def classic_accept_sco_connection(
|
||||
self,
|
||||
responder_controller: controller.Controller,
|
||||
initiator_address: Address,
|
||||
link_type: int,
|
||||
):
|
||||
logger.debug(
|
||||
f'[Classic] {responder_controller.public_address} accepts to connect SCO {initiator_address}'
|
||||
)
|
||||
initiator_controller = self.find_classic_controller(initiator_address)
|
||||
if initiator_controller is None:
|
||||
responder_controller.on_classic_sco_connection_complete(
|
||||
responder_controller.public_address,
|
||||
HCI_UNKNOWN_CONNECTION_IDENTIFIER_ERROR,
|
||||
link_type,
|
||||
)
|
||||
return
|
||||
|
||||
async def task():
|
||||
initiator_controller.on_classic_sco_connection_complete(
|
||||
responder_controller.public_address, HCI_SUCCESS, link_type
|
||||
)
|
||||
|
||||
asyncio.create_task(task())
|
||||
responder_controller.on_classic_sco_connection_complete(
|
||||
initiator_controller.public_address, HCI_SUCCESS, link_type
|
||||
)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
class RemoteLink:
|
||||
|
||||
+3
-2
@@ -538,8 +538,9 @@ class DLC(EventEmitter):
|
||||
f'[{self.dlci}] {len(data)} bytes, '
|
||||
f'rx_credits={self.rx_credits}: {data.hex()}'
|
||||
)
|
||||
if len(data) and self.sink:
|
||||
self.sink(data) # pylint: disable=not-callable
|
||||
if data:
|
||||
if self.sink:
|
||||
self.sink(data) # pylint: disable=not-callable
|
||||
|
||||
# Update the credits
|
||||
if self.rx_credits > 0:
|
||||
|
||||
@@ -198,12 +198,13 @@ async def open_transport_or_link(name: str) -> Transport:
|
||||
|
||||
"""
|
||||
if name.startswith('link-relay:'):
|
||||
logger.warning('Link Relay has been deprecated.')
|
||||
from ..controller import Controller
|
||||
from ..link import RemoteLink # lazy import
|
||||
|
||||
link = RemoteLink(name[11:])
|
||||
await link.wait_until_connected()
|
||||
controller = Controller('remote', link=link)
|
||||
controller = Controller('remote', link=link) # type:ignore[arg-type]
|
||||
|
||||
class LinkTransport(Transport):
|
||||
async def close(self):
|
||||
|
||||
@@ -10,7 +10,7 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.github.google.bumble.remotehci"
|
||||
minSdk = 26
|
||||
minSdk = 29
|
||||
targetSdk = 33
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
|
||||
+21
@@ -4,6 +4,7 @@ import android.hardware.bluetooth.V1_0.Status;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.Trace;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -53,6 +54,7 @@ class HciHidlHal extends android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.
|
||||
private final android.hardware.bluetooth.V1_0.IBluetoothHci mHciService;
|
||||
private final HciHalCallback mHciCallbacks;
|
||||
private int mInitializationStatus = -1;
|
||||
private final boolean mTracingEnabled = Trace.isEnabled();
|
||||
|
||||
|
||||
public static HciHidlHal create(HciHalCallback hciCallbacks) {
|
||||
@@ -89,6 +91,7 @@ class HciHidlHal extends android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.
|
||||
}
|
||||
|
||||
// Map the status code.
|
||||
Log.d(TAG, "Initialization status = " + mInitializationStatus);
|
||||
switch (mInitializationStatus) {
|
||||
case android.hardware.bluetooth.V1_0.Status.SUCCESS:
|
||||
return Status.SUCCESS;
|
||||
@@ -108,6 +111,10 @@ class HciHidlHal extends android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.
|
||||
public void sendPacket(HciPacket.Type type, byte[] packet) {
|
||||
ArrayList<Byte> data = HciPacket.byteArrayToList(packet);
|
||||
|
||||
if (mTracingEnabled) {
|
||||
Trace.beginAsyncSection("SEND_PACKET_TO_HAL", 1);
|
||||
}
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case COMMAND:
|
||||
@@ -125,6 +132,10 @@ class HciHidlHal extends android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.
|
||||
} catch (RemoteException error) {
|
||||
Log.w(TAG, "failed to forward packet: " + error);
|
||||
}
|
||||
|
||||
if (mTracingEnabled) {
|
||||
Trace.endAsyncSection("SEND_PACKET_TO_HAL", 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -157,6 +168,7 @@ class HciAidlHal extends android.hardware.bluetooth.IBluetoothHciCallbacks.Stub
|
||||
private final android.hardware.bluetooth.IBluetoothHci mHciService;
|
||||
private final HciHalCallback mHciCallbacks;
|
||||
private int mInitializationStatus = android.hardware.bluetooth.Status.SUCCESS;
|
||||
private final boolean mTracingEnabled = Trace.isEnabled();
|
||||
|
||||
public static HciAidlHal create(HciHalCallback hciCallbacks) {
|
||||
IBinder binder = ServiceManager.getService("android.hardware.bluetooth.IBluetoothHci/default");
|
||||
@@ -187,6 +199,7 @@ class HciAidlHal extends android.hardware.bluetooth.IBluetoothHciCallbacks.Stub
|
||||
}
|
||||
|
||||
// Map the status code.
|
||||
Log.d(TAG, "Initialization status = " + mInitializationStatus);
|
||||
switch (mInitializationStatus) {
|
||||
case android.hardware.bluetooth.Status.SUCCESS:
|
||||
return Status.SUCCESS;
|
||||
@@ -208,6 +221,10 @@ class HciAidlHal extends android.hardware.bluetooth.IBluetoothHciCallbacks.Stub
|
||||
// HciHal methods.
|
||||
@Override
|
||||
public void sendPacket(HciPacket.Type type, byte[] packet) {
|
||||
if (mTracingEnabled) {
|
||||
Trace.beginAsyncSection("SEND_PACKET_TO_HAL", 1);
|
||||
}
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case COMMAND:
|
||||
@@ -229,6 +246,10 @@ class HciAidlHal extends android.hardware.bluetooth.IBluetoothHciCallbacks.Stub
|
||||
} catch (RemoteException error) {
|
||||
Log.w(TAG, "failed to forward packet: " + error);
|
||||
}
|
||||
|
||||
if (mTracingEnabled) {
|
||||
Trace.endAsyncSection("SEND_PACKET_TO_HAL", 1);
|
||||
}
|
||||
}
|
||||
|
||||
// IBluetoothHciCallbacks methods.
|
||||
|
||||
+12
@@ -1,5 +1,6 @@
|
||||
package com.github.google.bumble.remotehci;
|
||||
|
||||
import android.os.Trace;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -15,6 +16,7 @@ public class HciServer {
|
||||
private final int mPort;
|
||||
private final Listener mListener;
|
||||
private OutputStream mOutputStream;
|
||||
private final boolean mTracingEnabled = Trace.isEnabled();
|
||||
|
||||
public interface Listener extends HciParser.Sink {
|
||||
void onHostConnectionState(boolean connected);
|
||||
@@ -27,6 +29,8 @@ public class HciServer {
|
||||
}
|
||||
|
||||
public void run() throws IOException {
|
||||
Log.i(TAG, "Tracing enabled: " + mTracingEnabled);
|
||||
|
||||
for (;;) {
|
||||
try {
|
||||
loop();
|
||||
@@ -73,6 +77,10 @@ public class HciServer {
|
||||
}
|
||||
|
||||
public void sendPacket(HciPacket.Type type, byte[] packet) {
|
||||
if (mTracingEnabled) {
|
||||
Trace.beginAsyncSection("SEND_PACKET_FROM_HAL", 2);
|
||||
}
|
||||
|
||||
// Create a combined data buffer so we can write it out in a single call.
|
||||
byte[] data = new byte[packet.length + 1];
|
||||
data[0] = type.value;
|
||||
@@ -89,5 +97,9 @@ public class HciServer {
|
||||
Log.d(TAG, "no client, dropping packet");
|
||||
}
|
||||
}
|
||||
|
||||
if (mTracingEnabled) {
|
||||
Trace.endAsyncSection("SEND_PACKET_FROM_HAL", 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+60
-1
@@ -23,9 +23,11 @@ import pytest
|
||||
from typing import Tuple
|
||||
|
||||
from .test_utils import TwoDevices
|
||||
from bumble import core
|
||||
from bumble import device
|
||||
from bumble import hfp
|
||||
from bumble import rfcomm
|
||||
|
||||
from bumble import hci
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
@@ -87,6 +89,63 @@ async def test_slc():
|
||||
ag_task.cancel()
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@pytest.mark.asyncio
|
||||
async def test_sco_setup():
|
||||
devices = TwoDevices()
|
||||
|
||||
# Enable Classic connections
|
||||
devices[0].classic_enabled = True
|
||||
devices[1].classic_enabled = True
|
||||
|
||||
# Start
|
||||
await devices[0].power_on()
|
||||
await devices[1].power_on()
|
||||
|
||||
connections = await asyncio.gather(
|
||||
devices[0].connect(
|
||||
devices[1].public_address, transport=core.BT_BR_EDR_TRANSPORT
|
||||
),
|
||||
devices[1].accept(devices[0].public_address),
|
||||
)
|
||||
|
||||
def on_sco_request(_connection: device.Connection, _link_type: int):
|
||||
connections[1].abort_on(
|
||||
'disconnection',
|
||||
devices[1].send_command(
|
||||
hci.HCI_Enhanced_Accept_Synchronous_Connection_Request_Command(
|
||||
bd_addr=connections[1].peer_address,
|
||||
**hfp.ESCO_PARAMETERS[
|
||||
hfp.DefaultCodecParameters.ESCO_CVSD_S1
|
||||
].asdict(),
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
devices[1].on('sco_request', on_sco_request)
|
||||
|
||||
sco_connections = [
|
||||
asyncio.get_running_loop().create_future(),
|
||||
asyncio.get_running_loop().create_future(),
|
||||
]
|
||||
|
||||
devices[0].on(
|
||||
'sco_connection', lambda sco_link: sco_connections[0].set_result(sco_link)
|
||||
)
|
||||
devices[1].on(
|
||||
'sco_connection', lambda sco_link: sco_connections[1].set_result(sco_link)
|
||||
)
|
||||
|
||||
await devices[0].send_command(
|
||||
hci.HCI_Enhanced_Setup_Synchronous_Connection_Command(
|
||||
connection_handle=connections[0].handle,
|
||||
**hfp.ESCO_PARAMETERS[hfp.DefaultCodecParameters.ESCO_CVSD_S1].asdict(),
|
||||
)
|
||||
)
|
||||
|
||||
await asyncio.gather(*sco_connections)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
async def run():
|
||||
await test_slc()
|
||||
|
||||
+5
-4
@@ -29,17 +29,18 @@ class TwoDevices:
|
||||
self.connections = [None, None]
|
||||
|
||||
self.link = LocalLink()
|
||||
addresses = ['F0:F1:F2:F3:F4:F5', 'F5:F4:F3:F2:F1:F0']
|
||||
self.controllers = [
|
||||
Controller('C1', link=self.link),
|
||||
Controller('C2', link=self.link),
|
||||
Controller('C1', link=self.link, public_address=addresses[0]),
|
||||
Controller('C2', link=self.link, public_address=addresses[1]),
|
||||
]
|
||||
self.devices = [
|
||||
Device(
|
||||
address=Address('F0:F1:F2:F3:F4:F5'),
|
||||
address=Address(addresses[0]),
|
||||
host=Host(self.controllers[0], AsyncPipeSink(self.controllers[0])),
|
||||
),
|
||||
Device(
|
||||
address=Address('F5:F4:F3:F2:F1:F0'),
|
||||
address=Address(addresses[1]),
|
||||
host=Host(self.controllers[1], AsyncPipeSink(self.controllers[1])),
|
||||
),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user