mirror of
https://github.com/google/bumble.git
synced 2026-04-16 00:25:31 +00:00
use ruff for linting and import sorting
This commit is contained in:
@@ -646,7 +646,6 @@ async def run_assist(
|
||||
|
||||
async def run_pair(transport: str, address: str) -> None:
|
||||
async with create_device(transport) as device:
|
||||
|
||||
# Connect to the server
|
||||
print(f'=== Connecting to {address}...')
|
||||
async with device.connect_as_gatt(address) as peer:
|
||||
|
||||
@@ -1096,9 +1096,7 @@ class DeviceListener(Device.Listener, Connection.Listener):
|
||||
if self.app.connected_peer.connection.is_encrypted
|
||||
else 'not encrypted'
|
||||
)
|
||||
self.app.append_to_output(
|
||||
'connection encryption change: ' f'{encryption_state}'
|
||||
)
|
||||
self.app.append_to_output(f'connection encryption change: {encryption_state}')
|
||||
|
||||
def on_connection_data_length_change(self):
|
||||
self.app.append_to_output(
|
||||
|
||||
@@ -275,7 +275,7 @@ async def async_main(
|
||||
(
|
||||
f'min={min(latencies):.2f}, '
|
||||
f'max={max(latencies):.2f}, '
|
||||
f'average={sum(latencies)/len(latencies):.2f},'
|
||||
f'average={sum(latencies) / len(latencies):.2f},'
|
||||
),
|
||||
[f'{latency:.4}' for latency in latencies],
|
||||
'\n',
|
||||
|
||||
@@ -215,7 +215,6 @@ async def show_device_info(peer, done: Optional[asyncio.Future]) -> None:
|
||||
# -----------------------------------------------------------------------------
|
||||
async def async_main(device_config, encrypt, transport, address_or_name):
|
||||
async with await open_transport(transport) as (hci_source, hci_sink):
|
||||
|
||||
# Create a device
|
||||
if device_config:
|
||||
device = Device.from_config_file_with_hci(
|
||||
|
||||
@@ -61,7 +61,6 @@ async def dump_gatt_db(peer, done):
|
||||
# -----------------------------------------------------------------------------
|
||||
async def async_main(device_config, encrypt, transport, address_or_name):
|
||||
async with await open_transport(transport) as (hci_source, hci_sink):
|
||||
|
||||
# Create a device
|
||||
if device_config:
|
||||
device = Device.from_config_file_with_hci(
|
||||
|
||||
@@ -268,7 +268,6 @@ class UiServer:
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
class Speaker:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
device_config_path: str | None,
|
||||
|
||||
13
apps/pair.py
13
apps/pair.py
@@ -527,7 +527,9 @@ async def pair(
|
||||
if advertise_appearance:
|
||||
advertise_appearance = advertise_appearance.upper()
|
||||
try:
|
||||
advertise_appearance_int = int(advertise_appearance)
|
||||
appearance = data_types.Appearance.from_int(
|
||||
int(advertise_appearance)
|
||||
)
|
||||
except ValueError:
|
||||
category, subcategory = advertise_appearance.split('/')
|
||||
try:
|
||||
@@ -545,12 +547,11 @@ async def pair(
|
||||
except ValueError:
|
||||
print(color(f'Invalid subcategory {subcategory}', 'red'))
|
||||
return
|
||||
advertise_appearance_int = int(
|
||||
Appearance(category_enum, subcategory_enum)
|
||||
appearance = data_types.Appearance(
|
||||
category_enum, subcategory_enum
|
||||
)
|
||||
advertising_data_types.append(
|
||||
data_types.Appearance(category_enum, subcategory_enum)
|
||||
)
|
||||
|
||||
advertising_data_types.append(appearance)
|
||||
device.advertising_data = bytes(AdvertisingData(advertising_data_types))
|
||||
await device.start_advertising(
|
||||
auto_restart=True,
|
||||
|
||||
@@ -19,7 +19,7 @@ ROOTCANAL_PORT_CUTTLEFISH = 7300
|
||||
@click.option(
|
||||
'--transport',
|
||||
help='HCI transport',
|
||||
default=f'tcp-client:127.0.0.1:<rootcanal-port>',
|
||||
default='tcp-client:127.0.0.1:<rootcanal-port>',
|
||||
)
|
||||
@click.option(
|
||||
'--config',
|
||||
|
||||
@@ -47,14 +47,13 @@ from bumble.avdtp import (
|
||||
AVDTP_DELAY_REPORTING_SERVICE_CATEGORY,
|
||||
MediaCodecCapabilities,
|
||||
MediaPacketPump,
|
||||
find_avdtp_service_with_connection,
|
||||
)
|
||||
from bumble.avdtp import Protocol as AvdtpProtocol
|
||||
from bumble.avdtp import find_avdtp_service_with_connection
|
||||
from bumble.avrcp import Protocol as AvrcpProtocol
|
||||
from bumble.colors import color
|
||||
from bumble.core import AdvertisingData
|
||||
from bumble.core import AdvertisingData, DeviceClass, PhysicalTransport
|
||||
from bumble.core import ConnectionError as BumbleConnectionError
|
||||
from bumble.core import DeviceClass, PhysicalTransport
|
||||
from bumble.device import Connection, Device, DeviceConfiguration
|
||||
from bumble.hci import HCI_CONNECTION_ALREADY_EXISTS_ERROR, Address, HCI_Constant
|
||||
from bumble.pairing import PairingConfig
|
||||
@@ -381,11 +380,11 @@ class Player:
|
||||
print(f">>> {color(address.to_string(False), 'yellow')}:")
|
||||
print(f" Device Class (raw): {class_of_device:06X}")
|
||||
major_class_name = DeviceClass.major_device_class_name(major_device_class)
|
||||
print(" Device Major Class: " f"{major_class_name}")
|
||||
print(f" Device Major Class: {major_class_name}")
|
||||
minor_class_name = DeviceClass.minor_device_class_name(
|
||||
major_device_class, minor_device_class
|
||||
)
|
||||
print(" Device Minor Class: " f"{minor_class_name}")
|
||||
print(f" Device Minor Class: {minor_class_name}")
|
||||
print(
|
||||
" Device Services: "
|
||||
f"{', '.join(DeviceClass.service_class_labels(service_classes))}"
|
||||
|
||||
@@ -217,9 +217,7 @@ async def scan(
|
||||
@click.option(
|
||||
'--irk',
|
||||
metavar='<IRK_HEX>:<ADDRESS>',
|
||||
help=(
|
||||
'Use this IRK for resolving private addresses ' '(may be used more than once)'
|
||||
),
|
||||
help=('Use this IRK for resolving private addresses (may be used more than once)'),
|
||||
multiple=True,
|
||||
)
|
||||
@click.option(
|
||||
|
||||
@@ -490,7 +490,7 @@ class VendorSpecificMediaCodecInformation(MediaCodecInformation):
|
||||
'VendorSpecificMediaCodecInformation(',
|
||||
f' vendor_id: {self.vendor_id:08X} ({name_or_number(COMPANY_IDENTIFIERS, self.vendor_id & 0xFFFF)})',
|
||||
f' codec_id: {self.codec_id:04X}',
|
||||
f' value: {self.value.hex()}' ')',
|
||||
f' value: {self.value.hex()})',
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ def tokenize_parameters(buffer: bytes) -> list[bytes]:
|
||||
|
||||
if in_quotes:
|
||||
token.extend(char)
|
||||
if char == b'\"':
|
||||
if char == b'"':
|
||||
in_quotes = False
|
||||
tokens.append(token[1:-1])
|
||||
token = bytearray()
|
||||
|
||||
@@ -258,7 +258,7 @@ class Protocol:
|
||||
|
||||
def send_ipid(self, transaction_label: int, pid: int) -> None:
|
||||
logger.debug(
|
||||
">>> AVCTP ipid: " f"transaction_label={transaction_label}, " f"pid={pid}"
|
||||
f">>> AVCTP ipid: transaction_label={transaction_label}, pid={pid}"
|
||||
)
|
||||
self.send_message(transaction_label, False, True, pid, b'')
|
||||
|
||||
|
||||
@@ -2011,7 +2011,7 @@ class Protocol(utils.EventEmitter):
|
||||
f"{command} is not a valid AV/C Command Frame"
|
||||
)
|
||||
logger.debug(
|
||||
f"<<< AVCTP Command, transaction_label={transaction_label}: " f"{command}"
|
||||
f"<<< AVCTP Command, transaction_label={transaction_label}: {command}"
|
||||
)
|
||||
|
||||
# Only addressing the unit, or the PANEL subunit with subunit ID 0 is supported
|
||||
|
||||
@@ -163,23 +163,23 @@ class AacAudioRtpPacket:
|
||||
cls, reader: BitReader, channel_configuration: int, audio_object_type: int
|
||||
) -> Self:
|
||||
# GASpecificConfig - ISO/EIC 14496-3 Table 4.1
|
||||
frame_length_flag = reader.read(1)
|
||||
reader.read(1) # frame_length_flag
|
||||
depends_on_core_coder = reader.read(1)
|
||||
if depends_on_core_coder:
|
||||
core_coder_delay = reader.read(14)
|
||||
reader.read(14) # core_coder_delay
|
||||
extension_flag = reader.read(1)
|
||||
if not channel_configuration:
|
||||
raise core.InvalidPacketError('program_config_element not supported')
|
||||
if audio_object_type in (6, 20):
|
||||
layer_nr = reader.read(3)
|
||||
reader.read(3) # layer_nr
|
||||
if extension_flag:
|
||||
if audio_object_type == 22:
|
||||
num_of_sub_frame = reader.read(5)
|
||||
layer_length = reader.read(11)
|
||||
reader.read(5) # num_of_sub_frame
|
||||
reader.read(11) # layer_length
|
||||
if audio_object_type in (17, 19, 20, 23):
|
||||
aac_section_data_resilience_flags = reader.read(1)
|
||||
aac_scale_factor_data_resilience_flags = reader.read(1)
|
||||
aac_spectral_data_resilience_flags = reader.read(1)
|
||||
reader.read(1) # aac_section_data_resilience_flags
|
||||
reader.read(1) # aac_scale_factor_data_resilience_flags
|
||||
reader.read(1) # aac_spectral_data_resilience_flags
|
||||
extension_flag_3 = reader.read(1)
|
||||
if extension_flag_3 == 1:
|
||||
raise core.InvalidPacketError('extensionFlag3 == 1 not supported')
|
||||
@@ -364,10 +364,10 @@ class AacAudioRtpPacket:
|
||||
if audio_mux_version_a != 0:
|
||||
raise core.InvalidPacketError('audioMuxVersionA != 0 not supported')
|
||||
if audio_mux_version == 1:
|
||||
tara_buffer_fullness = AacAudioRtpPacket.read_latm_value(reader)
|
||||
stream_cnt = 0
|
||||
all_streams_same_time_framing = reader.read(1)
|
||||
num_sub_frames = reader.read(6)
|
||||
AacAudioRtpPacket.read_latm_value(reader) # tara_buffer_fullness
|
||||
# stream_cnt = 0
|
||||
reader.read(1) # all_streams_same_time_framing
|
||||
reader.read(6) # num_sub_frames
|
||||
num_program = reader.read(4)
|
||||
if num_program != 0:
|
||||
raise core.InvalidPacketError('num_program != 0 not supported')
|
||||
@@ -391,9 +391,9 @@ class AacAudioRtpPacket:
|
||||
reader.skip(asc_len)
|
||||
frame_length_type = reader.read(3)
|
||||
if frame_length_type == 0:
|
||||
latm_buffer_fullness = reader.read(8)
|
||||
reader.read(8) # latm_buffer_fullness
|
||||
elif frame_length_type == 1:
|
||||
frame_length = reader.read(9)
|
||||
reader.read(9) # frame_length
|
||||
else:
|
||||
raise core.InvalidPacketError(
|
||||
f'frame_length_type {frame_length_type} not supported'
|
||||
@@ -413,7 +413,7 @@ class AacAudioRtpPacket:
|
||||
break
|
||||
crc_check_present = reader.read(1)
|
||||
if crc_check_present:
|
||||
crc_checksum = reader.read(8)
|
||||
reader.read(8) # crc_checksum
|
||||
|
||||
return cls(other_data_present, other_data_len_bits, audio_specific_config)
|
||||
|
||||
|
||||
@@ -25,10 +25,8 @@ import random
|
||||
import struct
|
||||
from typing import TYPE_CHECKING, Any, Optional, Union, cast
|
||||
|
||||
from bumble import hci
|
||||
from bumble import link
|
||||
from bumble import hci, link, ll, lmp
|
||||
from bumble import link as bumble_link
|
||||
from bumble import ll, lmp
|
||||
from bumble.colors import color
|
||||
from bumble.core import PhysicalTransport
|
||||
|
||||
@@ -170,10 +168,6 @@ class AdvertisingSet:
|
||||
|
||||
def send_extended_advertising_data(self) -> None:
|
||||
if self.controller.link:
|
||||
properties = (
|
||||
self.parameters.advertising_event_properties if self.parameters else 0
|
||||
)
|
||||
|
||||
address = self.address
|
||||
assert address
|
||||
|
||||
|
||||
@@ -25,10 +25,11 @@ try:
|
||||
from bumble.crypto.cryptography import EccKey, aes_cmac, e
|
||||
except ImportError:
|
||||
logging.getLogger(__name__).debug(
|
||||
"Unable to import cryptography, use built-in primitives."
|
||||
"Unable to import cryptography, using built-in primitives."
|
||||
)
|
||||
from bumble.crypto.builtin import EccKey, aes_cmac, e # type: ignore[assignment]
|
||||
|
||||
_EccKey = EccKey # For the linter only
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
|
||||
@@ -85,7 +85,6 @@ class _AES:
|
||||
# fmt: on
|
||||
|
||||
def __init__(self, key: bytes) -> None:
|
||||
|
||||
if len(key) not in (16, 24, 32):
|
||||
raise core.InvalidArgumentError(f'Invalid key size {len(key)}')
|
||||
|
||||
@@ -112,7 +111,6 @@ class _AES:
|
||||
r_con_pointer = 0
|
||||
t = kc
|
||||
while t < round_key_count:
|
||||
|
||||
tt = tk[kc - 1]
|
||||
tk[0] ^= (
|
||||
(self._S[(tt >> 16) & 0xFF] << 24)
|
||||
@@ -269,7 +267,6 @@ class _ECB:
|
||||
|
||||
|
||||
class _CBC:
|
||||
|
||||
def __init__(self, key: bytes, iv: bytes = bytes(16)) -> None:
|
||||
if len(iv) != 16:
|
||||
raise core.InvalidArgumentError(
|
||||
@@ -302,7 +299,6 @@ class _CBC:
|
||||
|
||||
|
||||
class _CMAC:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
key: bytes,
|
||||
@@ -414,7 +410,6 @@ class _CMAC:
|
||||
self._last_pt = _xor(second_last, data_block[-bs:])
|
||||
|
||||
def digest(self) -> bytes:
|
||||
|
||||
bs = self._block_size
|
||||
|
||||
if self._mac_tag is not None and not self._update_after_digest:
|
||||
|
||||
@@ -5248,8 +5248,7 @@ class Device(utils.CompositeEventEmitter):
|
||||
|
||||
if status != hci.HCI_SUCCESS:
|
||||
logger.debug(
|
||||
f'advertising set {advertising_handle} '
|
||||
f'terminated with status {status}'
|
||||
f'advertising set {advertising_handle} terminated with status {status}'
|
||||
)
|
||||
return
|
||||
|
||||
@@ -5628,7 +5627,8 @@ class Device(utils.CompositeEventEmitter):
|
||||
|
||||
self.host.send_command_sync(
|
||||
hci.HCI_Accept_Connection_Request_Command(
|
||||
bd_addr=bd_addr, role=0x01 # Remain the peripheral
|
||||
bd_addr=bd_addr,
|
||||
role=0x01, # Remain the peripheral
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -380,7 +380,7 @@ class Driver(common.Driver):
|
||||
|
||||
if (vendor_id, product_id) not in INTEL_USB_PRODUCTS:
|
||||
logger.debug(
|
||||
f"USB device ({vendor_id:04X}, {product_id:04X}) " "not in known list"
|
||||
f"USB device ({vendor_id:04X}, {product_id:04X}) not in known list"
|
||||
)
|
||||
return False
|
||||
|
||||
@@ -483,9 +483,7 @@ class Driver(common.Driver):
|
||||
raise DriverError("insufficient device info, missing CNVI or CNVR")
|
||||
|
||||
firmware_base_name = (
|
||||
"ibt-"
|
||||
f"{device_info[ValueType.CNVI]:04X}-"
|
||||
f"{device_info[ValueType.CNVR]:04X}"
|
||||
f"ibt-{device_info[ValueType.CNVI]:04X}-{device_info[ValueType.CNVR]:04X}"
|
||||
)
|
||||
logger.debug(f"FW base name: {firmware_base_name}")
|
||||
|
||||
|
||||
@@ -484,7 +484,7 @@ class Driver(common.Driver):
|
||||
|
||||
if (vendor_id, product_id) not in RTK_USB_PRODUCTS:
|
||||
logger.debug(
|
||||
f"USB device ({vendor_id:04X}, {product_id:04X}) " "not in known list"
|
||||
f"USB device ({vendor_id:04X}, {product_id:04X}) not in known list"
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
@@ -361,5 +361,4 @@ class EnumCharacteristicProxyAdapter(CharacteristicProxyAdapter[_T3]):
|
||||
|
||||
def decode_value(self, value: bytes) -> _T3:
|
||||
int_value = int.from_bytes(value, self.byteorder)
|
||||
a = self.cls(int_value)
|
||||
return self.cls(int_value)
|
||||
|
||||
@@ -119,7 +119,6 @@ def phy_list_to_bits(phys: Optional[Iterable[Phy]]) -> int:
|
||||
|
||||
|
||||
class SpecableEnum(utils.OpenIntEnum):
|
||||
|
||||
@classmethod
|
||||
def type_spec(cls, size: int, byteorder: Literal['little', 'big'] = 'little'):
|
||||
return {
|
||||
@@ -147,7 +146,6 @@ class SpecableEnum(utils.OpenIntEnum):
|
||||
|
||||
|
||||
class SpecableFlag(enum.IntFlag):
|
||||
|
||||
@classmethod
|
||||
def type_spec(cls, size: int, byteorder: Literal['little', 'big'] = 'little'):
|
||||
return {
|
||||
@@ -1786,20 +1784,20 @@ class HCI_Object:
|
||||
|
||||
@classmethod
|
||||
def dict_and_offset_from_bytes(
|
||||
cls, data: bytes, offset: int, fields: Fields
|
||||
cls, data: bytes, offset: int, object_fields: Fields
|
||||
) -> tuple[int, collections.OrderedDict[str, Any]]:
|
||||
result = collections.OrderedDict[str, Any]()
|
||||
for field in fields:
|
||||
if isinstance(field, list):
|
||||
for object_field in object_fields:
|
||||
if isinstance(object_field, list):
|
||||
# This is an array field, starting with a 1-byte item count.
|
||||
item_count = data[offset]
|
||||
offset += 1
|
||||
# Set fields first, because item_count might be 0.
|
||||
for sub_field_name, _ in field:
|
||||
for sub_field_name, _ in object_field:
|
||||
result[sub_field_name] = []
|
||||
|
||||
for _ in range(item_count):
|
||||
for sub_field_name, sub_field_type in field:
|
||||
for sub_field_name, sub_field_type in object_field:
|
||||
value, size = HCI_Object.parse_field(
|
||||
data, offset, sub_field_type
|
||||
)
|
||||
@@ -1807,7 +1805,7 @@ class HCI_Object:
|
||||
offset += size
|
||||
continue
|
||||
|
||||
field_name, field_type = field
|
||||
field_name, field_type = object_field
|
||||
assert isinstance(field_name, str)
|
||||
field_value, field_size = HCI_Object.parse_field(
|
||||
data, offset, cast(FieldSpec, field_type)
|
||||
@@ -1890,26 +1888,26 @@ class HCI_Object:
|
||||
return field_bytes
|
||||
|
||||
@staticmethod
|
||||
def dict_to_bytes(hci_object, fields):
|
||||
def dict_to_bytes(hci_object, object_fields):
|
||||
result = bytearray()
|
||||
for field in fields:
|
||||
if isinstance(field, list):
|
||||
for object_field in object_fields:
|
||||
if isinstance(object_field, list):
|
||||
# The field is an array. The serialized form starts with a 1-byte
|
||||
# item count. We use the length of the first array field as the
|
||||
# array count, since all array fields have the same number of items.
|
||||
item_count = len(hci_object[field[0][0]])
|
||||
item_count = len(hci_object[object_field[0][0]])
|
||||
result += bytes([item_count]) + b''.join(
|
||||
b''.join(
|
||||
HCI_Object.serialize_field(
|
||||
hci_object[sub_field_name][i], sub_field_type
|
||||
)
|
||||
for sub_field_name, sub_field_type in field
|
||||
for sub_field_name, sub_field_type in object_field
|
||||
)
|
||||
for i in range(item_count)
|
||||
)
|
||||
continue
|
||||
|
||||
(field_name, field_type) = field
|
||||
(field_name, field_type) = object_field
|
||||
result += HCI_Object.serialize_field(hci_object[field_name], field_type)
|
||||
|
||||
return bytes(result)
|
||||
@@ -1967,15 +1965,15 @@ class HCI_Object:
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def format_fields(hci_object, fields, indentation='', value_mappers=None):
|
||||
if not fields:
|
||||
def format_fields(hci_object, object_fields, indentation='', value_mappers=None):
|
||||
if not object_fields:
|
||||
return ''
|
||||
|
||||
# Build array of formatted key:value pairs
|
||||
field_strings = []
|
||||
for field in fields:
|
||||
if isinstance(field, list):
|
||||
for sub_field in field:
|
||||
for object_field in object_fields:
|
||||
if isinstance(object_field, list):
|
||||
for sub_field in object_field:
|
||||
sub_field_name, sub_field_type = sub_field
|
||||
item_count = len(hci_object[sub_field_name])
|
||||
for i in range(item_count):
|
||||
@@ -1993,7 +1991,7 @@ class HCI_Object:
|
||||
)
|
||||
continue
|
||||
|
||||
field_name, field_type = field
|
||||
field_name, field_type = object_field
|
||||
field_value = hci_object[field_name]
|
||||
field_strings.append(
|
||||
(
|
||||
@@ -2016,16 +2014,16 @@ class HCI_Object:
|
||||
@classmethod
|
||||
def fields_from_dataclass(cls, obj: Any) -> list[Any]:
|
||||
stack: list[list[Any]] = [[]]
|
||||
for field in dataclasses.fields(obj):
|
||||
for object_field in dataclasses.fields(obj):
|
||||
# Fields without metadata should be ignored.
|
||||
if not isinstance(
|
||||
(metadata := field.metadata.get("bumble.hci")), FieldMetadata
|
||||
(metadata := object_field.metadata.get("bumble.hci")), FieldMetadata
|
||||
):
|
||||
continue
|
||||
if metadata.list_begin:
|
||||
stack.append([])
|
||||
if metadata.spec:
|
||||
stack[-1].append((field.name, metadata.spec))
|
||||
stack[-1].append((object_field.name, metadata.spec))
|
||||
if metadata.list_end:
|
||||
top = stack.pop()
|
||||
stack[-1].append(top)
|
||||
|
||||
@@ -597,7 +597,7 @@ class AgIndicatorState:
|
||||
supported_values_text = (
|
||||
f'({",".join(str(v) for v in self.supported_values)})'
|
||||
)
|
||||
return f'(\"{self.indicator.value}\",{supported_values_text})'
|
||||
return f'("{self.indicator.value}",{supported_values_text})'
|
||||
|
||||
@classmethod
|
||||
def call(cls: type[Self]) -> Self:
|
||||
@@ -1351,7 +1351,7 @@ class AgProtocol(utils.EventEmitter):
|
||||
logger.warning(f'AG indicator {indicator} is disabled')
|
||||
|
||||
indicator_state.current_status = value
|
||||
self.send_response(f'+CIEV: {index+1},{value}')
|
||||
self.send_response(f'+CIEV: {index + 1},{value}')
|
||||
|
||||
async def negotiate_codec(self, codec: AudioCodec) -> None:
|
||||
"""Starts codec negotiation."""
|
||||
@@ -1417,7 +1417,7 @@ class AgProtocol(utils.EventEmitter):
|
||||
operation_code = operation_code[:1] + b'x'
|
||||
try:
|
||||
operation = CallHoldOperation(operation_code.decode())
|
||||
except:
|
||||
except Exception:
|
||||
logger.error(f'Invalid operation: {operation_code.decode()}')
|
||||
self.send_cme_error(CmeError.OPERATION_NOT_SUPPORTED)
|
||||
return
|
||||
@@ -1589,7 +1589,7 @@ class AgProtocol(utils.EventEmitter):
|
||||
|
||||
def _on_clcc(self) -> None:
|
||||
for call in self.calls:
|
||||
number_text = f',\"{call.number}\"' if call.number is not None else ''
|
||||
number_text = f',"{call.number}"' if call.number is not None else ''
|
||||
type_text = f',{call.type}' if call.type is not None else ''
|
||||
response = (
|
||||
f'+CLCC: {call.index}'
|
||||
|
||||
@@ -108,8 +108,7 @@ class DataPacketQueue(utils.EventEmitter):
|
||||
|
||||
if self._packets:
|
||||
logger.debug(
|
||||
f'{self._in_flight} packets in flight, '
|
||||
f'{len(self._packets)} in queue'
|
||||
f'{self._in_flight} packets in flight, {len(self._packets)} in queue'
|
||||
)
|
||||
|
||||
def flush(self, connection_handle: int) -> None:
|
||||
@@ -1394,8 +1393,7 @@ class Host(utils.EventEmitter):
|
||||
if event.status == hci.HCI_SUCCESS:
|
||||
# Create/update the connection
|
||||
logger.debug(
|
||||
f'### SCO CONNECTION: [0x{event.connection_handle:04X}] '
|
||||
f'{event.bd_addr}'
|
||||
f'### SCO CONNECTION: [0x{event.connection_handle:04X}] {event.bd_addr}'
|
||||
)
|
||||
|
||||
self.sco_links[event.connection_handle] = ScoLink(
|
||||
@@ -1447,7 +1445,7 @@ class Host(utils.EventEmitter):
|
||||
def on_hci_le_data_length_change_event(
|
||||
self, event: hci.HCI_LE_Data_Length_Change_Event
|
||||
):
|
||||
if (connection := self.connections.get(event.connection_handle)) is None:
|
||||
if event.connection_handle not in self.connections:
|
||||
logger.warning('!!! DATA LENGTH CHANGE: unknown handle')
|
||||
return
|
||||
|
||||
|
||||
@@ -331,7 +331,6 @@ class InformationEnhancedControlField(EnhancedControlField):
|
||||
|
||||
@dataclasses.dataclass
|
||||
class SupervisoryEnhancedControlField(EnhancedControlField):
|
||||
|
||||
supervision_function: int = ControlField.SupervisoryFunction.RR
|
||||
poll: int = 0
|
||||
req_seq: int = 0
|
||||
@@ -884,7 +883,6 @@ class Processor:
|
||||
|
||||
# TODO: Handle retransmission
|
||||
class EnhancedRetransmissionProcessor(Processor):
|
||||
|
||||
MAX_SEQ_NUM = 64
|
||||
|
||||
@dataclasses.dataclass
|
||||
|
||||
@@ -74,7 +74,9 @@ class PandoraDevice:
|
||||
|
||||
# open HCI transport & set device host.
|
||||
self._hci = await transport.open_transport(self._hci_name)
|
||||
self.device.host = Host(controller_source=self._hci.source, controller_sink=self._hci.sink) # type: ignore[no-untyped-call]
|
||||
self.device.host = Host(
|
||||
controller_source=self._hci.source, controller_sink=self._hci.sink
|
||||
) # type: ignore[no-untyped-call]
|
||||
|
||||
# power-on.
|
||||
await self.device.power_on()
|
||||
|
||||
@@ -21,8 +21,10 @@ from typing import AsyncGenerator, Optional, cast
|
||||
|
||||
import grpc
|
||||
import grpc.aio
|
||||
from google.protobuf import any_pb2 # pytype: disable=pyi-error
|
||||
from google.protobuf import empty_pb2 # pytype: disable=pyi-error
|
||||
from google.protobuf import (
|
||||
any_pb2, # pytype: disable=pyi-error
|
||||
empty_pb2, # pytype: disable=pyi-error
|
||||
)
|
||||
from pandora import host_pb2
|
||||
from pandora.host_grpc_aio import HostServicer
|
||||
from pandora.host_pb2 import (
|
||||
@@ -302,7 +304,9 @@ class HostService(HostServicer):
|
||||
await disconnection_future
|
||||
self.log.debug("Disconnected")
|
||||
finally:
|
||||
connection.remove_listener(connection.EVENT_DISCONNECTION, on_disconnection) # type: ignore
|
||||
connection.remove_listener(
|
||||
connection.EVENT_DISCONNECTION, on_disconnection
|
||||
) # type: ignore
|
||||
|
||||
return empty_pb2.Empty()
|
||||
|
||||
@@ -539,7 +543,7 @@ class HostService(HostServicer):
|
||||
await bumble.utils.cancel_on_event(
|
||||
self.device, 'flush', self.device.stop_advertising()
|
||||
)
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@utils.rpc
|
||||
@@ -609,7 +613,7 @@ class HostService(HostServicer):
|
||||
await bumble.utils.cancel_on_event(
|
||||
self.device, 'flush', self.device.stop_scanning()
|
||||
)
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@utils.rpc
|
||||
@@ -644,14 +648,18 @@ class HostService(HostServicer):
|
||||
)
|
||||
|
||||
finally:
|
||||
self.device.remove_listener(self.device.EVENT_INQUIRY_COMPLETE, complete_handler) # type: ignore
|
||||
self.device.remove_listener(self.device.EVENT_INQUIRY_RESULT, result_handler) # type: ignore
|
||||
self.device.remove_listener(
|
||||
self.device.EVENT_INQUIRY_COMPLETE, complete_handler
|
||||
) # type: ignore
|
||||
self.device.remove_listener(
|
||||
self.device.EVENT_INQUIRY_RESULT, result_handler
|
||||
) # type: ignore
|
||||
try:
|
||||
self.log.debug('Stop inquiry')
|
||||
await bumble.utils.cancel_on_event(
|
||||
self.device, 'flush', self.device.stop_discovery()
|
||||
)
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@utils.rpc
|
||||
|
||||
@@ -24,9 +24,9 @@ from typing import AsyncGenerator, Optional, Union
|
||||
import grpc
|
||||
from google.protobuf import any_pb2, empty_pb2 # pytype: disable=pyi-error
|
||||
from pandora.l2cap_grpc_aio import L2CAPServicer # pytype: disable=pyi-error
|
||||
from pandora.l2cap_pb2 import COMMAND_NOT_UNDERSTOOD, INVALID_CID_IN_REQUEST
|
||||
from pandora.l2cap_pb2 import Channel as PandoraChannel # pytype: disable=pyi-error
|
||||
from pandora.l2cap_pb2 import (
|
||||
COMMAND_NOT_UNDERSTOOD,
|
||||
INVALID_CID_IN_REQUEST,
|
||||
ConnectRequest,
|
||||
ConnectResponse,
|
||||
CreditBasedChannelRequest,
|
||||
@@ -41,6 +41,7 @@ from pandora.l2cap_pb2 import (
|
||||
WaitDisconnectionRequest,
|
||||
WaitDisconnectionResponse,
|
||||
)
|
||||
from pandora.l2cap_pb2 import Channel as PandoraChannel # pytype: disable=pyi-error
|
||||
|
||||
from bumble.core import InvalidArgumentError, OutOfResourcesError
|
||||
from bumble.device import Device
|
||||
|
||||
@@ -21,9 +21,11 @@ from collections.abc import Awaitable
|
||||
from typing import Any, AsyncGenerator, AsyncIterator, Callable, Optional, Union
|
||||
|
||||
import grpc
|
||||
from google.protobuf import any_pb2 # pytype: disable=pyi-error
|
||||
from google.protobuf import empty_pb2 # pytype: disable=pyi-error
|
||||
from google.protobuf import wrappers_pb2 # pytype: disable=pyi-error
|
||||
from google.protobuf import (
|
||||
any_pb2, # pytype: disable=pyi-error
|
||||
empty_pb2, # pytype: disable=pyi-error
|
||||
wrappers_pb2, # pytype: disable=pyi-error
|
||||
)
|
||||
from pandora.host_pb2 import Connection
|
||||
from pandora.security_grpc_aio import SecurityServicer, SecurityStorageServicer
|
||||
from pandora.security_pb2 import (
|
||||
@@ -455,7 +457,7 @@ class SecurityService(SecurityServicer):
|
||||
|
||||
def pair(*_: Any) -> None:
|
||||
if self.need_pairing(connection, level):
|
||||
pair_task = asyncio.create_task(connection.pair())
|
||||
bumble.utils.AsyncRunner.spawn(connection.pair())
|
||||
|
||||
listeners: dict[str, Callable[..., Union[None, Awaitable[None]]]] = {
|
||||
'disconnection': set_failure('connection_died'),
|
||||
|
||||
@@ -199,7 +199,6 @@ class AudioInputControlPoint:
|
||||
gain_settings_properties: GainSettingsProperties
|
||||
|
||||
async def on_write(self, connection: Connection, value: bytes) -> None:
|
||||
|
||||
opcode = AudioInputControlPointOpCode(value[0])
|
||||
|
||||
if opcode == AudioInputControlPointOpCode.SET_GAIN_SETTING:
|
||||
|
||||
@@ -40,7 +40,6 @@ class GenericAttributeProfileService(gatt.TemplateService):
|
||||
database_hash_enabled: bool = True,
|
||||
service_change_enabled: bool = True,
|
||||
) -> None:
|
||||
|
||||
if server_supported_features is not None:
|
||||
self.server_supported_features_characteristic = gatt.Characteristic(
|
||||
uuid=gatt.GATT_SERVER_SUPPORTED_FEATURES_CHARACTERISTIC,
|
||||
|
||||
@@ -137,7 +137,7 @@ class Metadata:
|
||||
values.append(str(decoded))
|
||||
|
||||
return '\n'.join(
|
||||
f'{indent}{key}: {" " * (max_key_length-len(key))}{value}'
|
||||
f'{indent}{key}: {" " * (max_key_length - len(key))}{value}'
|
||||
for key, value in zip(keys, values)
|
||||
)
|
||||
|
||||
|
||||
@@ -118,7 +118,6 @@ class VolumeOffsetControlPoint:
|
||||
volume_offset_state: VolumeOffsetState
|
||||
|
||||
async def on_write(self, connection: Connection, value: bytes) -> None:
|
||||
|
||||
opcode = value[0]
|
||||
if opcode != SetVolumeOffsetOpCode.SET_VOLUME_OFFSET:
|
||||
raise ATT_Error(ErrorCode.OPCODE_NOT_SUPPORTED)
|
||||
@@ -177,7 +176,6 @@ class VolumeOffsetControlService(TemplateService):
|
||||
audio_location: Optional[VocsAudioLocation] = None,
|
||||
audio_output_description: Optional[AudioOutputDescription] = None,
|
||||
) -> None:
|
||||
|
||||
self.volume_offset_state = (
|
||||
VolumeOffsetState() if volume_offset_state is None else volume_offset_state
|
||||
)
|
||||
|
||||
@@ -528,7 +528,7 @@ class ServiceAttribute:
|
||||
def to_string(self, with_colors=False):
|
||||
if with_colors:
|
||||
return (
|
||||
f'Attribute(id={color(self.id_name(self.id),"magenta")},'
|
||||
f'Attribute(id={color(self.id_name(self.id), "magenta")},'
|
||||
f'value={self.value})'
|
||||
)
|
||||
|
||||
|
||||
@@ -997,7 +997,6 @@ class Session:
|
||||
self.send_command(response)
|
||||
|
||||
def send_pairing_confirm_command(self) -> None:
|
||||
|
||||
if self.pairing_method != PairingMethod.OOB:
|
||||
self.r = crypto.r()
|
||||
logger.debug(f'generated random: {self.r.hex()}')
|
||||
@@ -1833,7 +1832,6 @@ class Session:
|
||||
self.send_public_key_command()
|
||||
|
||||
def next_steps() -> None:
|
||||
|
||||
if self.pairing_method in (
|
||||
PairingMethod.JUST_WORKS,
|
||||
PairingMethod.NUMERIC_COMPARISON,
|
||||
|
||||
@@ -284,7 +284,9 @@ async def open_pyusb_transport(spec: str) -> Transport:
|
||||
device = await _power_cycle(device) # type: ignore
|
||||
except Exception as e:
|
||||
logging.debug(e, stack_info=True)
|
||||
logging.info(f"Unable to power cycle {hex(device.idVendor)} {hex(device.idProduct)}") # type: ignore
|
||||
logging.info(
|
||||
f"Unable to power cycle {hex(device.idVendor)} {hex(device.idProduct)}"
|
||||
) # type: ignore
|
||||
|
||||
# Collect the metadata
|
||||
device_metadata = {'vendor_id': device.idVendor, 'product_id': device.idProduct}
|
||||
@@ -370,7 +372,9 @@ async def _power_cycle(device: UsbDevice) -> UsbDevice:
|
||||
# Device needs to be find again otherwise it will appear as disconnected
|
||||
return usb.core.find(idVendor=device.idVendor, idProduct=device.idProduct) # type: ignore
|
||||
except USBError:
|
||||
logger.exception(f"Adjustment needed: Please revise the udev rule for device {hex(device.idVendor)}:{hex(device.idProduct)} for proper recognition.") # type: ignore
|
||||
logger.exception(
|
||||
f"Adjustment needed: Please revise the udev rule for device {hex(device.idVendor)}:{hex(device.idProduct)} for proper recognition."
|
||||
) # type: ignore
|
||||
|
||||
return device
|
||||
|
||||
|
||||
3
bumble/vendor/android/hci.py
vendored
3
bumble/vendor/android/hci.py
vendored
@@ -51,6 +51,7 @@ class HCI_LE_Get_Vendor_Capabilities_Command(hci.HCI_Command):
|
||||
'''
|
||||
See https://source.android.com/docs/core/connect/bluetooth/hci_requirements#vendor-specific-capabilities
|
||||
'''
|
||||
|
||||
return_parameters_fields = [
|
||||
('status', hci.STATUS_SPEC),
|
||||
('max_advt_instances', 1),
|
||||
@@ -137,6 +138,7 @@ class HCI_Get_Controller_Activity_Energy_Info_Command(hci.HCI_Command):
|
||||
'''
|
||||
See https://source.android.com/docs/core/connect/bluetooth/hci_requirements#le_get_controller_activity_energy_info
|
||||
'''
|
||||
|
||||
return_parameters_fields = [
|
||||
('status', hci.STATUS_SPEC),
|
||||
('total_tx_time_ms', 4),
|
||||
@@ -229,6 +231,7 @@ class HCI_Bluetooth_Quality_Report_Event(HCI_Android_Vendor_Event):
|
||||
'''
|
||||
See https://source.android.com/docs/core/connect/bluetooth/hci_requirements#bluetooth-quality-report-sub-event
|
||||
'''
|
||||
|
||||
quality_report_id: int = field(metadata=hci.metadata(1))
|
||||
packet_types: int = field(metadata=hci.metadata(1))
|
||||
connection_handle: int = field(metadata=hci.metadata(2))
|
||||
|
||||
@@ -59,28 +59,28 @@ class Keyboard:
|
||||
|
||||
def print_keyboard_report(self) -> None:
|
||||
print(color('\tKeyboard Input Received', 'green', None, 'bold'))
|
||||
print(color(f'Keys:', 'white', None, 'bold'))
|
||||
print(color('Keys:', 'white', None, 'bold'))
|
||||
for i in range(1, 7):
|
||||
print(
|
||||
color(f' Key{i}{" ":>8s}= ', 'cyan', None, 'bold'), self.report[i + 1]
|
||||
)
|
||||
print(color(f'\nModifier Keys:', 'white', None, 'bold'))
|
||||
print(color('\nModifier Keys:', 'white', None, 'bold'))
|
||||
print(
|
||||
color(f' Left Ctrl : ', 'cyan'),
|
||||
color(' Left Ctrl : ', 'cyan'),
|
||||
f'{self.report[0][0] == 1!s:<5}', # type: ignore
|
||||
color(f' Left Shift : ', 'cyan'),
|
||||
color(' Left Shift : ', 'cyan'),
|
||||
f'{self.report[0][1] == 1!s:<5}', # type: ignore
|
||||
color(f' Left ALT : ', 'cyan'),
|
||||
color(' Left ALT : ', 'cyan'),
|
||||
f'{self.report[0][2] == 1!s:<5}', # type: ignore
|
||||
color(f' Left GUI : ', 'cyan'),
|
||||
color(' Left GUI : ', 'cyan'),
|
||||
f'{self.report[0][3] == 1!s:<5}\n', # type: ignore
|
||||
color(f' Right Ctrl : ', 'cyan'),
|
||||
color(' Right Ctrl : ', 'cyan'),
|
||||
f'{self.report[0][4] == 1!s:<5}', # type: ignore
|
||||
color(f' Right Shift : ', 'cyan'),
|
||||
color(' Right Shift : ', 'cyan'),
|
||||
f'{self.report[0][5] == 1!s:<5}', # type: ignore
|
||||
color(f' Right ALT : ', 'cyan'),
|
||||
color(' Right ALT : ', 'cyan'),
|
||||
f'{self.report[0][6] == 1!s:<5}', # type: ignore
|
||||
color(f' Right GUI : ', 'cyan'),
|
||||
color(' Right GUI : ', 'cyan'),
|
||||
f'{self.report[0][7] == 1!s:<5}', # type: ignore
|
||||
)
|
||||
|
||||
@@ -117,23 +117,23 @@ class Mouse:
|
||||
def print_mouse_report(self) -> None:
|
||||
print(color('\tMouse Input Received', 'green', None, 'bold'))
|
||||
print(
|
||||
color(f' Button 1 (primary/trigger) = ', 'cyan'),
|
||||
color(' Button 1 (primary/trigger) = ', 'cyan'),
|
||||
self.report[0][0] == 1, # type: ignore
|
||||
color(f'\n Button 2 (secondary) = ', 'cyan'),
|
||||
color('\n Button 2 (secondary) = ', 'cyan'),
|
||||
self.report[0][1] == 1, # type: ignore
|
||||
color(f'\n Button 3 (tertiary) = ', 'cyan'),
|
||||
color('\n Button 3 (tertiary) = ', 'cyan'),
|
||||
self.report[0][2] == 1, # type: ignore
|
||||
color(f'\n Button4 = ', 'cyan'),
|
||||
color('\n Button4 = ', 'cyan'),
|
||||
self.report[0][3] == 1, # type: ignore
|
||||
color(f'\n Button5 = ', 'cyan'),
|
||||
color('\n Button5 = ', 'cyan'),
|
||||
self.report[0][4] == 1, # type: ignore
|
||||
color(f'\n X (X-axis displacement) = ', 'cyan'),
|
||||
color('\n X (X-axis displacement) = ', 'cyan'),
|
||||
self.report[1],
|
||||
color(f'\n Y (Y-axis displacement) = ', 'cyan'),
|
||||
color('\n Y (Y-axis displacement) = ', 'cyan'),
|
||||
self.report[2],
|
||||
color(f'\n Wheel = ', 'cyan'),
|
||||
color('\n Wheel = ', 'cyan'),
|
||||
self.report[3],
|
||||
color(f'\n AC PAN = ', 'cyan'),
|
||||
color('\n AC PAN = ', 'cyan'),
|
||||
self.report[4],
|
||||
)
|
||||
|
||||
@@ -142,7 +142,6 @@ class Mouse:
|
||||
class ReportParser:
|
||||
@staticmethod
|
||||
def parse_input_report(input_report: bytes) -> None:
|
||||
|
||||
report_id = input_report[0] # pylint: disable=unsubscriptable-object
|
||||
report_length = len(input_report)
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ from mobly.controllers import android_device
|
||||
|
||||
|
||||
class OneDeviceBenchTest(base_test.BaseTestClass):
|
||||
|
||||
def setup_class(self):
|
||||
self.ads = self.register_controller(android_device)
|
||||
self.dut = self.ads[0]
|
||||
|
||||
@@ -33,9 +33,10 @@ from bumble.sdp import (
|
||||
SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
||||
SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
||||
SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID,
|
||||
DataElement,
|
||||
ServiceAttribute,
|
||||
)
|
||||
from bumble.sdp import Client as SDP_Client
|
||||
from bumble.sdp import DataElement, ServiceAttribute
|
||||
from bumble.transport import open_transport
|
||||
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ from bumble.profiles.ancs import (
|
||||
NotificationAttributeId,
|
||||
)
|
||||
from bumble.transport import open_transport
|
||||
from bumble.utils import AsyncRunner
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
_cached_app_names: dict[str, str] = {}
|
||||
@@ -192,9 +193,7 @@ async def main() -> None:
|
||||
ancs_client.on("notification", on_ancs_notification)
|
||||
|
||||
# Process all notifications in a task.
|
||||
notification_processing_task = asyncio.create_task(
|
||||
process_notifications(ancs_client)
|
||||
)
|
||||
AsyncRunner.spawn(process_notifications(ancs_client))
|
||||
|
||||
# Accept a TCP connection to handle commands.
|
||||
tcp_server = await asyncio.start_server(
|
||||
|
||||
@@ -51,7 +51,6 @@ async def main() -> None:
|
||||
devices[1].cis_enabled = True
|
||||
|
||||
await asyncio.gather(*[device.power_on() for device in devices])
|
||||
advertising_set = await devices[0].create_advertising_set()
|
||||
|
||||
connection = await devices[1].connect(
|
||||
devices[0].random_address, own_address_type=OwnAddressType.RANDOM
|
||||
|
||||
@@ -38,11 +38,11 @@ class DiscoveryListener(Device.Listener):
|
||||
print(f'>>> {color(address, "yellow")}:')
|
||||
print(f' Device Class (raw): {class_of_device:06X}')
|
||||
major_class_name = DeviceClass.major_device_class_name(major_device_class)
|
||||
print(' Device Major Class: ' f'{major_class_name}')
|
||||
print(f' Device Major Class: {major_class_name}')
|
||||
minor_class_name = DeviceClass.minor_device_class_name(
|
||||
major_device_class, minor_device_class
|
||||
)
|
||||
print(' Device Minor Class: ' f'{minor_class_name}')
|
||||
print(f' Device Minor Class: {minor_class_name}')
|
||||
print(
|
||||
' Device Services: '
|
||||
f'{", ".join(DeviceClass.service_class_labels(service_classes))}'
|
||||
|
||||
@@ -32,7 +32,6 @@ from bumble.transport import open_transport
|
||||
async def main(
|
||||
config_file: str, transport: str, mode: int, peer_address: str, psm: int
|
||||
) -> None:
|
||||
|
||||
print('<<< connecting to HCI...')
|
||||
async with await open_transport(transport) as hci_transport:
|
||||
print('<<< connected')
|
||||
@@ -59,7 +58,6 @@ async def main(
|
||||
active_channel: l2cap.ClassicChannel | None = None
|
||||
|
||||
def on_connection(channel: l2cap.ClassicChannel):
|
||||
|
||||
def on_sdu(sdu: bytes):
|
||||
print(f'<<< {sdu.decode()}')
|
||||
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
# Copyright 2021-2022 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
import bumble.logging
|
||||
from bumble.colors import color
|
||||
from bumble.controller import Controller
|
||||
from bumble.device import Device
|
||||
from bumble.hci import Address
|
||||
from bumble.link import LocalLink
|
||||
from bumble.transport import open_transport
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
class ScannerListener(Device.Listener):
|
||||
def on_advertisement(self, advertisement):
|
||||
address_type_string = ('P', 'R', 'PI', 'RI')[advertisement.address.address_type]
|
||||
address_color = 'yellow' if advertisement.is_connectable else 'red'
|
||||
if address_type_string.startswith('P'):
|
||||
type_color = 'green'
|
||||
else:
|
||||
type_color = 'cyan'
|
||||
|
||||
print(
|
||||
f'>>> {color(advertisement.address, address_color)} '
|
||||
f'[{color(address_type_string, type_color)}]: '
|
||||
f'RSSI={advertisement.rssi}, {advertisement.data}'
|
||||
)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
async def main() -> None:
|
||||
if len(sys.argv) != 2:
|
||||
print('Usage: run_controller.py <transport-spec>')
|
||||
print('example: run_controller_with_scanner.py serial:/dev/pts/14,1000000')
|
||||
return
|
||||
|
||||
print('>>> connecting to HCI...')
|
||||
async with await open_transport(sys.argv[1]) as hci_transport:
|
||||
print('>>> connected')
|
||||
|
||||
# Create a local link
|
||||
link = LocalLink()
|
||||
|
||||
# Create a first controller using the packet source/sink as its host interface
|
||||
controller1 = Controller(
|
||||
'C1',
|
||||
host_source=hci_transport.source,
|
||||
host_sink=hci_transport.sink,
|
||||
link=link,
|
||||
public_address='E0:E1:E2:E3:E4:E5',
|
||||
)
|
||||
|
||||
# Create a second controller using the same link
|
||||
controller2 = Controller('C2', link=link)
|
||||
|
||||
# Create a device with a scanner listener
|
||||
device = Device.with_hci(
|
||||
'Bumble', Address('F0:F1:F2:F3:F4:F5'), controller2, controller2
|
||||
)
|
||||
device.listener = ScannerListener()
|
||||
await device.power_on()
|
||||
await device.start_scanning()
|
||||
|
||||
await hci_transport.source.wait_for_termination()
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
bumble.logging.setup_basic_logging('DEBUG')
|
||||
asyncio.run(main())
|
||||
@@ -36,7 +36,7 @@ async def main() -> None:
|
||||
'Usage: run_csis_servers.py <config-file> '
|
||||
'<transport-spec-for-device-1> <transport-spec-for-device-2>'
|
||||
)
|
||||
print('example: run_csis_servers.py device1.json ' 'hci-socket:0 hci-socket:1')
|
||||
print('example: run_csis_servers.py device1.json hci-socket:0 hci-socket:1')
|
||||
return
|
||||
|
||||
print('<<< connecting to HCI...')
|
||||
|
||||
@@ -90,7 +90,7 @@ async def main() -> None:
|
||||
),
|
||||
scan_response_data=bytes(scan_response_data3),
|
||||
)
|
||||
print("Selected TX power 3:", set2.selected_tx_power)
|
||||
print("Selected TX power 3:", set3.selected_tx_power)
|
||||
|
||||
await hci_transport.source.terminated
|
||||
|
||||
|
||||
@@ -30,9 +30,8 @@ from bumble.core import (
|
||||
PhysicalTransport,
|
||||
)
|
||||
from bumble.device import Device
|
||||
from bumble.hid import HID_CONTROL_PSM, HID_INTERRUPT_PSM
|
||||
from bumble.hid import HID_CONTROL_PSM, HID_INTERRUPT_PSM, Message
|
||||
from bumble.hid import Device as HID_Device
|
||||
from bumble.hid import Message
|
||||
from bumble.sdp import (
|
||||
SDP_ADDITIONAL_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
||||
SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
||||
@@ -423,7 +422,6 @@ deviceData = DeviceData()
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
async def keyboard_device(hid_device: HID_Device):
|
||||
|
||||
# Start a Websocket server to receive events from a web page
|
||||
async def serve(websocket: websockets.asyncio.server.ServerConnection):
|
||||
global deviceData
|
||||
|
||||
@@ -67,7 +67,6 @@ SDP_HID_SSR_HOST_MIN_TIMEOUT_ATTRIBUTE_ID = 0x0210
|
||||
|
||||
|
||||
async def get_hid_device_sdp_record(connection):
|
||||
|
||||
# Connect to the SDP Server
|
||||
sdp_client = SDP_Client(connection)
|
||||
await sdp_client.connect()
|
||||
@@ -84,7 +83,7 @@ async def get_hid_device_sdp_record(connection):
|
||||
if len(service_record_handles) < 1:
|
||||
await sdp_client.disconnect()
|
||||
raise Exception(
|
||||
color(f'BT HID Device service not found on peer device!!!!', 'red')
|
||||
color('BT HID Device service not found on peer device!!!!', 'red')
|
||||
)
|
||||
|
||||
# For BT_HUMAN_INTERFACE_DEVICE_SERVICE service, get all its attributes
|
||||
@@ -92,8 +91,8 @@ async def get_hid_device_sdp_record(connection):
|
||||
attributes = await sdp_client.get_attributes(
|
||||
service_record_handle, [SDP_ALL_ATTRIBUTES_RANGE]
|
||||
)
|
||||
print(color(f'SERVICE {service_record_handle:04X} attributes:', 'yellow'))
|
||||
print(color(f'SDP attributes for HID device', 'magenta'))
|
||||
print(color('SERVICE {service_record_handle:04X} attributes:', 'yellow'))
|
||||
print(color('SDP attributes for HID device', 'magenta'))
|
||||
for attribute in attributes:
|
||||
if attribute.id == SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID:
|
||||
print(
|
||||
@@ -287,7 +286,7 @@ async def main() -> None:
|
||||
def on_hid_interrupt_data_cb(pdu: bytes):
|
||||
report_type = pdu[0] & 0x0F
|
||||
if len(pdu) == 1:
|
||||
print(color(f'Warning: No report received', 'yellow'))
|
||||
print(color('Warning: No report received', 'yellow'))
|
||||
return
|
||||
report_length = len(pdu[1:])
|
||||
report_id = pdu[1]
|
||||
|
||||
@@ -55,7 +55,7 @@ from bumble.transport import open_transport
|
||||
# -----------------------------------------------------------------------------
|
||||
async def main() -> None:
|
||||
if len(sys.argv) < 3:
|
||||
print('Usage: run_mcp_client.py <config-file>' '<transport-spec-for-device>')
|
||||
print('Usage: run_mcp_client.py <config-file><transport-spec-for-device>')
|
||||
return
|
||||
|
||||
print('<<< connecting to HCI...')
|
||||
|
||||
@@ -28,9 +28,10 @@ from bumble.sdp import (
|
||||
SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
||||
SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
||||
SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID,
|
||||
DataElement,
|
||||
ServiceAttribute,
|
||||
)
|
||||
from bumble.sdp import Client as SDP_Client
|
||||
from bumble.sdp import DataElement, ServiceAttribute
|
||||
from bumble.transport import open_transport
|
||||
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ def dumps_volume_state(volume_setting: int, muted: int, change_counter: int) ->
|
||||
# -----------------------------------------------------------------------------
|
||||
async def main() -> None:
|
||||
if len(sys.argv) < 3:
|
||||
print('Usage: run_vcp_renderer.py <config-file>' '<transport-spec-for-device>')
|
||||
print('Usage: run_vcp_renderer.py <config-file><transport-spec-for-device>')
|
||||
return
|
||||
|
||||
print('<<< connecting to HCI...')
|
||||
|
||||
@@ -48,12 +48,12 @@ development = [
|
||||
"bt-test-interfaces >= 0.0.6",
|
||||
"grpcio-tools >= 1.62.1",
|
||||
"invoke >= 1.7.3",
|
||||
"isort ~= 5.13.2",
|
||||
"mobly >= 1.12.2",
|
||||
"mypy == 1.12.0",
|
||||
"nox >= 2022",
|
||||
"pylint == 3.3.1",
|
||||
"pyyaml >= 6.0",
|
||||
"ruff == 0.14.10",
|
||||
"types-appdirs >= 1.4.3",
|
||||
"types-invoke >= 1.7.3",
|
||||
"types-protobuf >= 4.21.0",
|
||||
@@ -208,3 +208,11 @@ ignore_missing_imports = true
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
skip = ["_version.py", "grpc_protobuf"]
|
||||
|
||||
[tool.ruff]
|
||||
exclude = [
|
||||
"bumble/transport/grpc_protobuf"
|
||||
]
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "preserve"
|
||||
21
tasks.py
21
tasks.py
@@ -127,6 +127,9 @@ def lint(ctx, disable='C,R', errors_only=False):
|
||||
|
||||
print(f">>> Running the linter{qualifier}...")
|
||||
try:
|
||||
print("+++ Checking with ruff...")
|
||||
ctx.run("ruff check")
|
||||
print("+++ Checking with pylint...")
|
||||
ctx.run(f"pylint {' '.join(options)} bumble apps examples tasks.py")
|
||||
print("The linter is happy. ✅ 😊 🐝")
|
||||
except UnexpectedExit as exc:
|
||||
@@ -138,20 +141,24 @@ def lint(ctx, disable='C,R', errors_only=False):
|
||||
@task
|
||||
def format_code(ctx, check=False, diff=False):
|
||||
|
||||
options = []
|
||||
if check:
|
||||
options.append("--check")
|
||||
if diff:
|
||||
options.append("--diff")
|
||||
|
||||
print(">>> Sorting imports...")
|
||||
options = []
|
||||
try:
|
||||
ctx.run(f"isort {' '.join(options)} .")
|
||||
if diff:
|
||||
options.append("--diff")
|
||||
else:
|
||||
options.append("--fix")
|
||||
ctx.run(f"ruff check --select I {' '.join(options)} .")
|
||||
except UnexpectedExit as exc:
|
||||
print("Please run 'invoke project.format' or 'isort .' to format the code. ❌")
|
||||
raise Exit(code=1) from exc
|
||||
|
||||
print(">>> Running the formatter...")
|
||||
options = []
|
||||
if check:
|
||||
options.append("--check")
|
||||
if diff:
|
||||
options.append("--diff")
|
||||
try:
|
||||
ctx.run(f"black -S {' '.join(options)} .")
|
||||
except UnexpectedExit as exc:
|
||||
|
||||
@@ -301,9 +301,7 @@ async def test_pacs():
|
||||
|
||||
await devices.setup_connection()
|
||||
peer = device.Peer(devices.connections[1])
|
||||
pacs_client = await peer.discover_service_and_create_proxy(
|
||||
PublishedAudioCapabilitiesServiceProxy
|
||||
)
|
||||
await peer.discover_service_and_create_proxy(PublishedAudioCapabilitiesServiceProxy)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -23,9 +23,10 @@ from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from bumble import device, gatt, hci, utils
|
||||
from bumble import gatt, hci, utils
|
||||
from bumble.core import PhysicalTransport
|
||||
from bumble.device import (
|
||||
Advertisement,
|
||||
AdvertisingEventProperties,
|
||||
AdvertisingParameters,
|
||||
CigParameters,
|
||||
@@ -146,7 +147,7 @@ async def test_device_connect_parallel():
|
||||
)
|
||||
)
|
||||
|
||||
assert (yield) == None
|
||||
assert (yield) is None
|
||||
|
||||
def d1_flow():
|
||||
packet = HCI_Packet.from_bytes((yield))
|
||||
@@ -180,7 +181,7 @@ async def test_device_connect_parallel():
|
||||
)
|
||||
)
|
||||
|
||||
assert (yield) == None
|
||||
assert (yield) is None
|
||||
|
||||
def d2_flow():
|
||||
packet = HCI_Packet.from_bytes((yield))
|
||||
@@ -214,7 +215,7 @@ async def test_device_connect_parallel():
|
||||
)
|
||||
)
|
||||
|
||||
assert (yield) == None
|
||||
assert (yield) is None
|
||||
|
||||
d0.host.set_packet_sink(Sink(d0_flow()))
|
||||
d1.host.set_packet_sink(Sink(d1_flow()))
|
||||
@@ -239,10 +240,10 @@ async def test_device_connect_parallel():
|
||||
]
|
||||
)
|
||||
|
||||
assert type(c01) == Connection
|
||||
assert type(c02) == Connection
|
||||
assert type(a10) == Connection
|
||||
assert type(a20) == Connection
|
||||
assert isinstance(c01, Connection)
|
||||
assert isinstance(c02, Connection)
|
||||
assert isinstance(a10, Connection)
|
||||
assert isinstance(a20, Connection)
|
||||
|
||||
assert c01.handle == a10.handle and c01.handle == 0x100
|
||||
assert c02.handle == a20.handle and c02.handle == 0x101
|
||||
@@ -317,7 +318,7 @@ async def test_advertising_and_scanning():
|
||||
await dev.power_on()
|
||||
|
||||
# Start scanning
|
||||
advertisements = asyncio.Queue[device.Advertisement]()
|
||||
advertisements = asyncio.Queue[Advertisement]()
|
||||
devices[1].on(devices[1].EVENT_ADVERTISEMENT, advertisements.put_nowait)
|
||||
await devices[1].start_scanning()
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ class Devices:
|
||||
self.link = LocalLink()
|
||||
addresses = [":".join([f"F{i}"] * 6) for i in range(num_devices)]
|
||||
self.controllers = [
|
||||
Controller(f'C{i+i}', link=self.link, public_address=addresses[i])
|
||||
Controller(f'C{i + i}', link=self.link, public_address=addresses[i])
|
||||
for i in range(num_devices)
|
||||
]
|
||||
self.devices = [
|
||||
|
||||
@@ -21,11 +21,11 @@ import urllib.error
|
||||
import urllib.request
|
||||
|
||||
import click
|
||||
from bumble.tools import rtk_util
|
||||
|
||||
import bumble.logging
|
||||
from bumble.colors import color
|
||||
from bumble.drivers import rtk
|
||||
from bumble.tools import rtk_util
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
|
||||
Reference in New Issue
Block a user