use ruff for linting and import sorting

This commit is contained in:
Gilles Boccon-Gibod
2025-12-29 19:28:45 -08:00
parent ad0753b959
commit 4fb501a0ef
59 changed files with 182 additions and 274 deletions

View File

@@ -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:

View File

@@ -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(

View File

@@ -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',

View File

@@ -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(

View File

@@ -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(

View File

@@ -268,7 +268,6 @@ class UiServer:
# -----------------------------------------------------------------------------
class Speaker:
def __init__(
self,
device_config_path: str | None,

View File

@@ -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,

View File

@@ -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',

View File

@@ -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))}"

View File

@@ -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(

View File

@@ -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()})',
]
)

View File

@@ -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()

View File

@@ -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'')

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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
)
)

View File

@@ -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}")

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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}'

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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'),

View File

@@ -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:

View File

@@ -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,

View File

@@ -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)
)

View File

@@ -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
)

View File

@@ -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})'
)

View File

@@ -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,

View File

@@ -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

View File

@@ -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))

View File

@@ -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)

View File

@@ -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]

View File

@@ -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

View File

@@ -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(

View File

@@ -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

View File

@@ -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))}'

View File

@@ -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()}')

View File

@@ -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())

View File

@@ -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...')

View File

@@ -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

View File

@@ -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

View File

@@ -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]

View File

@@ -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...')

View File

@@ -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

View File

@@ -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...')

View File

@@ -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"

View File

@@ -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:

View File

@@ -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)
# -----------------------------------------------------------------------------

View File

@@ -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()

View File

@@ -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 = [

View File

@@ -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