forked from auracaster/bumble_mirror
Compare commits
25 Commits
v0.0.136
...
barbibulle
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9b7072423 | ||
|
|
fa4be1958f | ||
|
|
f1686d8a9a | ||
|
|
5c6a7f2036 | ||
|
|
99758e4b7d | ||
|
|
7385de6a69 | ||
|
|
bb297e7516 | ||
|
|
8a91c614c7 | ||
|
|
70a50a74b7 | ||
|
|
6a16c61c5f | ||
|
|
0a22f2f7c7 | ||
|
|
422b05ad51 | ||
|
|
16e926a216 | ||
|
|
e94dc66d0c | ||
|
|
e37c77532b | ||
|
|
8b9ce03e86 | ||
|
|
7e854efbbb | ||
|
|
64b75be29b | ||
|
|
06018211fe | ||
|
|
e640991608 | ||
|
|
1068a6858d | ||
|
|
17db5dd4ff | ||
|
|
ea0a7e2347 | ||
|
|
6febd1ba35 | ||
|
|
ce049865a4 |
@@ -57,7 +57,7 @@ from bumble.core import UUID, AdvertisingData, BT_LE_TRANSPORT
|
||||
from bumble.device import ConnectionParametersPreferences, Device, Connection, Peer
|
||||
from bumble.utils import AsyncRunner
|
||||
from bumble.transport import open_transport_or_link
|
||||
from bumble.gatt import Characteristic
|
||||
from bumble.gatt import Characteristic, Service, CharacteristicDeclaration, Descriptor
|
||||
from bumble.hci import (
|
||||
HCI_Constant,
|
||||
HCI_LE_1M_PHY,
|
||||
@@ -154,10 +154,10 @@ class ConsoleApp:
|
||||
'rssi': {'on': None, 'off': None},
|
||||
'show': {
|
||||
'scan': None,
|
||||
'services': None,
|
||||
'attributes': None,
|
||||
'log': None,
|
||||
'device': None,
|
||||
'local-services': None,
|
||||
'remote-services': None,
|
||||
},
|
||||
'filter': {
|
||||
'address': None,
|
||||
@@ -197,8 +197,8 @@ class ConsoleApp:
|
||||
)
|
||||
self.output_max_lines = 20
|
||||
self.scan_results_text = FormattedTextControl()
|
||||
self.services_text = FormattedTextControl()
|
||||
self.attributes_text = FormattedTextControl()
|
||||
self.local_services_text = FormattedTextControl()
|
||||
self.remote_services_text = FormattedTextControl()
|
||||
self.device_text = FormattedTextControl()
|
||||
self.log_text = FormattedTextControl(
|
||||
get_cursor_position=lambda: Point(0, max(0, len(self.log_lines) - 1))
|
||||
@@ -214,12 +214,12 @@ class ConsoleApp:
|
||||
filter=Condition(lambda: self.top_tab == 'scan'),
|
||||
),
|
||||
ConditionalContainer(
|
||||
Frame(Window(self.services_text), title='Services'),
|
||||
filter=Condition(lambda: self.top_tab == 'services'),
|
||||
Frame(Window(self.local_services_text), title='Local Services'),
|
||||
filter=Condition(lambda: self.top_tab == 'local-services'),
|
||||
),
|
||||
ConditionalContainer(
|
||||
Frame(Window(self.attributes_text), title='Attributes'),
|
||||
filter=Condition(lambda: self.top_tab == 'attributes'),
|
||||
Frame(Window(self.remote_services_text), title='Remove Services'),
|
||||
filter=Condition(lambda: self.top_tab == 'remote-services'),
|
||||
),
|
||||
ConditionalContainer(
|
||||
Frame(Window(self.log_text, height=self.log_height), title='Log'),
|
||||
@@ -281,6 +281,7 @@ class ConsoleApp:
|
||||
self.device.listener = DeviceListener(self)
|
||||
await self.device.power_on()
|
||||
self.show_device(self.device)
|
||||
self.show_local_services(self.device.gatt_server.attributes)
|
||||
|
||||
# Run the UI
|
||||
await self.ui.run_async()
|
||||
@@ -359,32 +360,38 @@ class ConsoleApp:
|
||||
self.scan_results_text.text = ANSI('\n'.join(lines))
|
||||
self.ui.invalidate()
|
||||
|
||||
def show_services(self, services):
|
||||
def show_remote_services(self, services):
|
||||
lines = []
|
||||
del self.known_attributes[:]
|
||||
for service in services:
|
||||
lines.append(('ansicyan', str(service) + '\n'))
|
||||
lines.append(("ansicyan", f"{service}\n"))
|
||||
|
||||
for characteristic in service.characteristics:
|
||||
lines.append(('ansimagenta', ' ' + str(characteristic) + '\n'))
|
||||
lines.append(('ansimagenta', f' {characteristic} + \n'))
|
||||
self.known_attributes.append(
|
||||
f'{service.uuid.to_hex_str()}.{characteristic.uuid.to_hex_str()}'
|
||||
)
|
||||
self.known_attributes.append(f'*.{characteristic.uuid.to_hex_str()}')
|
||||
self.known_attributes.append(f'#{characteristic.handle:X}')
|
||||
for descriptor in characteristic.descriptors:
|
||||
lines.append(('ansigreen', ' ' + str(descriptor) + '\n'))
|
||||
lines.append(("ansigreen", f" {descriptor}\n"))
|
||||
|
||||
self.services_text.text = lines
|
||||
self.remote_services_text.text = lines
|
||||
self.ui.invalidate()
|
||||
|
||||
def show_attributes(self, attributes):
|
||||
def show_local_services(self, attributes):
|
||||
lines = []
|
||||
|
||||
for attribute in attributes:
|
||||
lines.append(('ansicyan', f'{attribute}\n'))
|
||||
if isinstance(attribute, Service):
|
||||
lines.append(("ansicyan", f"{attribute}\n"))
|
||||
elif isinstance(attribute, (Characteristic, CharacteristicDeclaration)):
|
||||
lines.append(("ansimagenta", f" {attribute}\n"))
|
||||
elif isinstance(attribute, Descriptor):
|
||||
lines.append(("ansigreen", f" {attribute}\n"))
|
||||
else:
|
||||
lines.append(("ansiyellow", f"{attribute}\n"))
|
||||
|
||||
self.attributes_text.text = lines
|
||||
self.local_services_text.text = lines
|
||||
self.ui.invalidate()
|
||||
|
||||
def show_device(self, device):
|
||||
@@ -469,7 +476,7 @@ class ConsoleApp:
|
||||
await self.connected_peer.discover_descriptors(characteristic)
|
||||
self.append_to_output('discovery completed')
|
||||
|
||||
self.show_services(self.connected_peer.services)
|
||||
self.show_remote_services(self.connected_peer.services)
|
||||
|
||||
async def discover_attributes(self):
|
||||
if not self.connected_peer:
|
||||
@@ -655,7 +662,13 @@ class ConsoleApp:
|
||||
|
||||
async def do_show(self, params):
|
||||
if params:
|
||||
if params[0] in {'scan', 'services', 'attributes', 'log', 'device'}:
|
||||
if params[0] in {
|
||||
'scan',
|
||||
'log',
|
||||
'device',
|
||||
'local-services',
|
||||
'remote-services',
|
||||
}:
|
||||
self.top_tab = params[0]
|
||||
self.ui.invalidate()
|
||||
|
||||
|
||||
@@ -22,9 +22,11 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
import struct
|
||||
from colors import color
|
||||
from pyee import EventEmitter
|
||||
from typing import Dict, Type
|
||||
|
||||
from bumble.core import UUID, name_or_number
|
||||
from bumble.hci import HCI_Object, key_with_value
|
||||
@@ -197,7 +199,7 @@ class ATT_PDU:
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.3 ATTRIBUTE PDU
|
||||
'''
|
||||
|
||||
pdu_classes = {}
|
||||
pdu_classes: Dict[int, Type[ATT_PDU]] = {}
|
||||
op_code = 0
|
||||
name = None
|
||||
|
||||
|
||||
@@ -15,12 +15,14 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
import asyncio
|
||||
import struct
|
||||
import time
|
||||
import logging
|
||||
from colors import color
|
||||
from pyee import EventEmitter
|
||||
from typing import Dict, Type
|
||||
|
||||
from .core import (
|
||||
BT_ADVANCED_AUDIO_DISTRIBUTION_SERVICE,
|
||||
@@ -627,7 +629,8 @@ class Message: # pylint:disable=attribute-defined-outside-init
|
||||
RESPONSE_REJECT: 'RESPONSE_REJECT',
|
||||
}
|
||||
|
||||
subclasses = {} # Subclasses, by signal identifier and message type
|
||||
# Subclasses, by signal identifier and message type
|
||||
subclasses: Dict[int, Dict[int, Type[Message]]] = {}
|
||||
|
||||
@staticmethod
|
||||
def message_type_name(message_type):
|
||||
|
||||
@@ -46,7 +46,6 @@ from bumble.hci import (
|
||||
HCI_LE_Connection_Complete_Event,
|
||||
HCI_LE_Read_Remote_Features_Complete_Event,
|
||||
HCI_Number_Of_Completed_Packets_Event,
|
||||
HCI_Object,
|
||||
HCI_Packet,
|
||||
)
|
||||
|
||||
@@ -458,8 +457,8 @@ class Controller:
|
||||
return
|
||||
|
||||
# Send a scan report
|
||||
report = HCI_Object(
|
||||
HCI_LE_Advertising_Report_Event.REPORT_FIELDS,
|
||||
report = HCI_LE_Advertising_Report_Event.Report(
|
||||
HCI_LE_Advertising_Report_Event.Report.FIELDS,
|
||||
event_type=HCI_LE_Advertising_Report_Event.ADV_IND,
|
||||
address_type=sender_address.address_type,
|
||||
address=sender_address,
|
||||
@@ -469,8 +468,8 @@ class Controller:
|
||||
self.send_hci_packet(HCI_LE_Advertising_Report_Event([report]))
|
||||
|
||||
# Simulate a scan response
|
||||
report = HCI_Object(
|
||||
HCI_LE_Advertising_Report_Event.REPORT_FIELDS,
|
||||
report = HCI_LE_Advertising_Report_Event.Report(
|
||||
HCI_LE_Advertising_Report_Event.Report.FIELDS,
|
||||
event_type=HCI_LE_Advertising_Report_Event.SCAN_RSP,
|
||||
address_type=sender_address.address_type,
|
||||
address=sender_address,
|
||||
@@ -738,10 +737,10 @@ class Controller:
|
||||
self.advertising_parameters = command
|
||||
return bytes([HCI_SUCCESS])
|
||||
|
||||
def on_hci_le_read_advertising_channel_tx_power_command(self, _command):
|
||||
def on_hci_le_read_advertising_physical_channel_tx_power_command(self, _command):
|
||||
'''
|
||||
See Bluetooth spec Vol 2, Part E - 7.8.6 LE Read Advertising Channel Tx Power
|
||||
Command
|
||||
See Bluetooth spec Vol 2, Part E - 7.8.6 LE Read Advertising Physical Channel
|
||||
Tx Power Command
|
||||
'''
|
||||
return bytes([HCI_SUCCESS, self.advertising_channel_tx_power])
|
||||
|
||||
@@ -1008,7 +1007,7 @@ class Controller:
|
||||
|
||||
def on_hci_le_read_phy_command(self, command):
|
||||
'''
|
||||
See Bluetooth spec Vol 2, Part E - 7.8.47 LE Read PHY command
|
||||
See Bluetooth spec Vol 2, Part E - 7.8.47 LE Read PHY Command
|
||||
'''
|
||||
return struct.pack(
|
||||
'<BHBB',
|
||||
@@ -1028,3 +1027,9 @@ class Controller:
|
||||
'rx_phys': command.rx_phys,
|
||||
}
|
||||
return bytes([HCI_SUCCESS])
|
||||
|
||||
def on_hci_le_read_transmit_power_command(self, _command):
|
||||
'''
|
||||
See Bluetooth spec Vol 2, Part E - 7.8.74 LE Read Transmit Power Command
|
||||
'''
|
||||
return struct.pack('<BBB', HCI_SUCCESS, 0, 0)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
import struct
|
||||
|
||||
from .company_ids import COMPANY_IDENTIFIERS
|
||||
@@ -145,7 +146,7 @@ class UUID:
|
||||
'''
|
||||
|
||||
BASE_UUID = bytes.fromhex('00001000800000805F9B34FB')
|
||||
UUIDS = [] # Registry of all instances created
|
||||
UUIDS: list[UUID] = [] # Registry of all instances created
|
||||
|
||||
def __init__(self, uuid_str_or_int, name=None):
|
||||
if isinstance(uuid_str_or_int, int):
|
||||
|
||||
118
bumble/device.py
118
bumble/device.py
@@ -15,6 +15,7 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
from enum import IntEnum
|
||||
import functools
|
||||
import json
|
||||
@@ -22,6 +23,8 @@ import asyncio
|
||||
import logging
|
||||
from contextlib import asynccontextmanager, AsyncExitStack
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar
|
||||
|
||||
from colors import color
|
||||
|
||||
from .att import ATT_CID, ATT_DEFAULT_MTU, ATT_PDU
|
||||
@@ -46,6 +49,7 @@ from .hci import (
|
||||
HCI_LE_CODED_PHY_LE_SUPPORTED_FEATURE,
|
||||
HCI_LE_EXTENDED_ADVERTISING_LE_SUPPORTED_FEATURE,
|
||||
HCI_LE_EXTENDED_CREATE_CONNECTION_COMMAND,
|
||||
HCI_LE_RAND_COMMAND,
|
||||
HCI_LE_READ_PHY_COMMAND,
|
||||
HCI_MITM_NOT_REQUIRED_GENERAL_BONDING_AUTHENTICATION_REQUIREMENTS,
|
||||
HCI_MITM_NOT_REQUIRED_NO_BONDING_AUTHENTICATION_REQUIREMENTS,
|
||||
@@ -78,6 +82,7 @@ from .hci import (
|
||||
HCI_LE_Enable_Encryption_Command,
|
||||
HCI_LE_Extended_Advertising_Report_Event,
|
||||
HCI_LE_Extended_Create_Connection_Command,
|
||||
HCI_LE_Rand_Command,
|
||||
HCI_LE_Read_PHY_Command,
|
||||
HCI_LE_Set_Advertising_Data_Command,
|
||||
HCI_LE_Set_Advertising_Enable_Command,
|
||||
@@ -492,6 +497,7 @@ class Peer:
|
||||
# -----------------------------------------------------------------------------
|
||||
@dataclass
|
||||
class ConnectionParametersPreferences:
|
||||
default: ClassVar[ConnectionParametersPreferences]
|
||||
connection_interval_min: int = DEVICE_DEFAULT_CONNECTION_INTERVAL_MIN
|
||||
connection_interval_max: int = DEVICE_DEFAULT_CONNECTION_INTERVAL_MAX
|
||||
max_latency: int = DEVICE_DEFAULT_CONNECTION_MAX_LATENCY
|
||||
@@ -831,7 +837,7 @@ def host_event_handler(function):
|
||||
# List of host event handlers for the Device class.
|
||||
# (we define this list outside the class, because referencing a class in method
|
||||
# decorators is not straightforward)
|
||||
device_host_event_handlers = []
|
||||
device_host_event_handlers: list[str] = []
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -1113,10 +1119,35 @@ class Device(CompositeEventEmitter):
|
||||
|
||||
if self.le_enabled:
|
||||
# Set the controller address
|
||||
await self.send_command(
|
||||
HCI_LE_Set_Random_Address_Command(random_address=self.random_address),
|
||||
check_result=True,
|
||||
)
|
||||
if self.random_address == Address.ANY_RANDOM:
|
||||
# Try to use an address generated at random by the controller
|
||||
if self.host.supports_command(HCI_LE_RAND_COMMAND):
|
||||
# Get 8 random bytes
|
||||
response = await self.send_command(
|
||||
HCI_LE_Rand_Command(), check_result=True
|
||||
)
|
||||
|
||||
# Ensure the address bytes can be a static random address
|
||||
address_bytes = response.return_parameters.random_number[
|
||||
:5
|
||||
] + bytes([response.return_parameters.random_number[5] | 0xC0])
|
||||
|
||||
# Create a static random address from the random bytes
|
||||
self.random_address = Address(address_bytes)
|
||||
|
||||
if self.random_address != Address.ANY_RANDOM:
|
||||
logger.debug(
|
||||
color(
|
||||
f'LE Random Address: {self.random_address}',
|
||||
'yellow',
|
||||
)
|
||||
)
|
||||
await self.send_command(
|
||||
HCI_LE_Set_Random_Address_Command(
|
||||
random_address=self.random_address
|
||||
),
|
||||
check_result=True,
|
||||
)
|
||||
|
||||
# Load the address resolving list
|
||||
if self.keystore and self.host.supports_command(
|
||||
@@ -2237,8 +2268,7 @@ class Device(CompositeEventEmitter):
|
||||
result = await self.send_command(
|
||||
HCI_Remote_Name_Request_Command(
|
||||
bd_addr=peer_address,
|
||||
# TODO investigate other options
|
||||
page_scan_repetition_mode=HCI_Remote_Name_Request_Command.R0,
|
||||
page_scan_repetition_mode=HCI_Remote_Name_Request_Command.R2,
|
||||
reserved=0,
|
||||
clock_offset=0, # TODO investigate non-0 values
|
||||
)
|
||||
@@ -2329,7 +2359,7 @@ class Device(CompositeEventEmitter):
|
||||
|
||||
if transport == BT_BR_EDR_TRANSPORT:
|
||||
# Create a new connection
|
||||
connection: Connection = self.pending_connections.pop(peer_address)
|
||||
connection = self.pending_connections.pop(peer_address)
|
||||
connection.complete(
|
||||
connection_handle, peer_resolvable_address, role, connection_parameters
|
||||
)
|
||||
@@ -2369,50 +2399,50 @@ class Device(CompositeEventEmitter):
|
||||
self.advertising_own_address_type = None
|
||||
self.advertising = False
|
||||
|
||||
# Create and notify of the new connection asynchronously
|
||||
async def new_connection():
|
||||
# Figure out which PHY we're connected with
|
||||
if self.host.supports_command(HCI_LE_READ_PHY_COMMAND):
|
||||
result = await asyncio.shield(
|
||||
self.send_command(
|
||||
HCI_LE_Read_PHY_Command(
|
||||
connection_handle=connection_handle
|
||||
),
|
||||
check_result=True,
|
||||
)
|
||||
if own_address_type in (
|
||||
OwnAddressType.PUBLIC,
|
||||
OwnAddressType.RESOLVABLE_OR_PUBLIC,
|
||||
):
|
||||
self_address = self.public_address
|
||||
else:
|
||||
self_address = self.random_address
|
||||
|
||||
# Create a new connection
|
||||
connection = Connection(
|
||||
self,
|
||||
connection_handle,
|
||||
transport,
|
||||
self_address,
|
||||
peer_address,
|
||||
peer_resolvable_address,
|
||||
role,
|
||||
connection_parameters,
|
||||
ConnectionPHY(HCI_LE_1M_PHY, HCI_LE_1M_PHY),
|
||||
)
|
||||
self.connections[connection_handle] = connection
|
||||
|
||||
# If supported, read which PHY we're connected with before
|
||||
# notifying listeners of the new connection.
|
||||
if self.host.supports_command(HCI_LE_READ_PHY_COMMAND):
|
||||
|
||||
async def read_phy():
|
||||
result = await self.send_command(
|
||||
HCI_LE_Read_PHY_Command(connection_handle=connection_handle),
|
||||
check_result=True,
|
||||
)
|
||||
phy = ConnectionPHY(
|
||||
connection.phy = ConnectionPHY(
|
||||
result.return_parameters.tx_phy, result.return_parameters.rx_phy
|
||||
)
|
||||
else:
|
||||
phy = ConnectionPHY(HCI_LE_1M_PHY, HCI_LE_1M_PHY)
|
||||
# Emit an event to notify listeners of the new connection
|
||||
self.emit('connection', connection)
|
||||
|
||||
self_address = self.random_address
|
||||
if own_address_type in (
|
||||
OwnAddressType.PUBLIC,
|
||||
OwnAddressType.RESOLVABLE_OR_PUBLIC,
|
||||
):
|
||||
self_address = self.public_address
|
||||
|
||||
# Create a new connection
|
||||
connection = Connection(
|
||||
self,
|
||||
connection_handle,
|
||||
transport,
|
||||
self_address,
|
||||
peer_address,
|
||||
peer_resolvable_address,
|
||||
role,
|
||||
connection_parameters,
|
||||
phy,
|
||||
)
|
||||
self.connections[connection_handle] = connection
|
||||
# Do so asynchronously to not block the current event handler
|
||||
connection.abort_on('disconnection', read_phy())
|
||||
|
||||
else:
|
||||
# Emit an event to notify listeners of the new connection
|
||||
self.emit('connection', connection)
|
||||
|
||||
self.abort_on('flush', new_connection())
|
||||
|
||||
@host_event_handler
|
||||
def on_connection_failure(self, transport, peer_address, error_code):
|
||||
logger.debug(f'*** Connection failed: {HCI_Constant.error_name(error_code)}')
|
||||
|
||||
@@ -217,7 +217,7 @@ class Service(Attribute):
|
||||
uuid.to_pdu_bytes(),
|
||||
)
|
||||
self.uuid = uuid
|
||||
self.included_services = []
|
||||
# self.included_services = []
|
||||
self.characteristics = characteristics[:]
|
||||
self.primary = primary
|
||||
|
||||
|
||||
@@ -15,11 +15,13 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
import struct
|
||||
import collections
|
||||
import logging
|
||||
import functools
|
||||
from colors import color
|
||||
from typing import Dict, Type
|
||||
|
||||
from .core import (
|
||||
BT_BR_EDR_TRANSPORT,
|
||||
@@ -1638,8 +1640,8 @@ class HCI_Object:
|
||||
# Map the value if needed
|
||||
if value_mappers:
|
||||
value_mapper = value_mappers.get(key, value_mapper)
|
||||
if value_mapper is not None:
|
||||
value = value_mapper(value)
|
||||
if value_mapper is not None:
|
||||
value = value_mapper(value)
|
||||
|
||||
# Get the string representation of the value
|
||||
value_str = HCI_Object.format_field_value(
|
||||
@@ -1690,6 +1692,11 @@ class Address:
|
||||
RANDOM_IDENTITY_ADDRESS: 'RANDOM_IDENTITY_ADDRESS',
|
||||
}
|
||||
|
||||
# Type declarations
|
||||
NIL: Address
|
||||
ANY: Address
|
||||
ANY_RANDOM: Address
|
||||
|
||||
# pylint: disable-next=unnecessary-lambda
|
||||
ADDRESS_TYPE_SPEC = {'size': 1, 'mapper': lambda x: Address.address_type_name(x)}
|
||||
|
||||
@@ -1807,6 +1814,7 @@ class Address:
|
||||
# Predefined address values
|
||||
Address.NIL = Address(b"\xff\xff\xff\xff\xff\xff", Address.PUBLIC_DEVICE_ADDRESS)
|
||||
Address.ANY = Address(b"\x00\x00\x00\x00\x00\x00", Address.PUBLIC_DEVICE_ADDRESS)
|
||||
Address.ANY_RANDOM = Address(b"\x00\x00\x00\x00\x00\x00", Address.RANDOM_DEVICE_ADDRESS)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
class OwnAddressType:
|
||||
@@ -1873,7 +1881,7 @@ class HCI_Command(HCI_Packet):
|
||||
'''
|
||||
|
||||
hci_packet_type = HCI_COMMAND_PACKET
|
||||
command_classes = {}
|
||||
command_classes: Dict[int, Type[HCI_Command]] = {}
|
||||
|
||||
@staticmethod
|
||||
def command(fields=(), return_parameters_fields=()):
|
||||
@@ -3104,6 +3112,16 @@ class HCI_LE_Read_Remote_Features_Command(HCI_Command):
|
||||
'''
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@HCI_Command.command(
|
||||
return_parameters_fields=[("status", STATUS_SPEC), ("random_number", 8)]
|
||||
)
|
||||
class HCI_LE_Rand_Command(HCI_Command):
|
||||
"""
|
||||
See Bluetooth spec @ 7.8.23 LE Rand Command
|
||||
"""
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@HCI_Command.command(
|
||||
[
|
||||
@@ -4009,8 +4027,8 @@ class HCI_Event(HCI_Packet):
|
||||
'''
|
||||
|
||||
hci_packet_type = HCI_EVENT_PACKET
|
||||
event_classes = {}
|
||||
meta_event_classes = {}
|
||||
event_classes: Dict[int, Type[HCI_Event]] = {}
|
||||
meta_event_classes: Dict[int, Type[HCI_LE_Meta_Event]] = {}
|
||||
|
||||
@staticmethod
|
||||
def event(fields=()):
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
import asyncio
|
||||
import logging
|
||||
import struct
|
||||
@@ -22,6 +23,7 @@ import struct
|
||||
from collections import deque
|
||||
from colors import color
|
||||
from pyee import EventEmitter
|
||||
from typing import Dict, Type
|
||||
|
||||
from .core import BT_CENTRAL_ROLE, InvalidStateError, ProtocolError
|
||||
from .hci import (
|
||||
@@ -184,7 +186,7 @@ class L2CAP_Control_Frame:
|
||||
See Bluetooth spec @ Vol 3, Part A - 4 SIGNALING PACKET FORMATS
|
||||
'''
|
||||
|
||||
classes = {}
|
||||
classes: Dict[int, Type[L2CAP_Control_Frame]] = {}
|
||||
code = 0
|
||||
name = None
|
||||
|
||||
@@ -383,7 +385,7 @@ class L2CAP_Connection_Response(L2CAP_Control_Frame):
|
||||
|
||||
CONNECTION_SUCCESSFUL = 0x0000
|
||||
CONNECTION_PENDING = 0x0001
|
||||
CONNECTION_REFUSED_LE_PSM_NOT_SUPPORTED = 0x0002
|
||||
CONNECTION_REFUSED_PSM_NOT_SUPPORTED = 0x0002
|
||||
CONNECTION_REFUSED_SECURITY_BLOCK = 0x0003
|
||||
CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE = 0x0004
|
||||
CONNECTION_REFUSED_INVALID_SOURCE_CID = 0x0006
|
||||
@@ -394,7 +396,7 @@ class L2CAP_Connection_Response(L2CAP_Control_Frame):
|
||||
RESULT_NAMES = {
|
||||
CONNECTION_SUCCESSFUL: 'CONNECTION_SUCCESSFUL',
|
||||
CONNECTION_PENDING: 'CONNECTION_PENDING',
|
||||
CONNECTION_REFUSED_LE_PSM_NOT_SUPPORTED: 'CONNECTION_REFUSED_LE_PSM_NOT_SUPPORTED',
|
||||
CONNECTION_REFUSED_PSM_NOT_SUPPORTED: 'CONNECTION_REFUSED_PSM_NOT_SUPPORTED',
|
||||
CONNECTION_REFUSED_SECURITY_BLOCK: 'CONNECTION_REFUSED_SECURITY_BLOCK',
|
||||
CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE: 'CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE',
|
||||
CONNECTION_REFUSED_INVALID_SOURCE_CID: 'CONNECTION_REFUSED_INVALID_SOURCE_CID',
|
||||
@@ -1619,7 +1621,7 @@ class ChannelManager:
|
||||
destination_cid=request.source_cid,
|
||||
source_cid=0,
|
||||
# pylint: disable=line-too-long
|
||||
result=L2CAP_Connection_Response.CONNECTION_REFUSED_LE_PSM_NOT_SUPPORTED,
|
||||
result=L2CAP_Connection_Response.CONNECTION_REFUSED_PSM_NOT_SUPPORTED,
|
||||
status=0x0000,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -31,6 +31,7 @@ from ..gatt import (
|
||||
Characteristic,
|
||||
CharacteristicValue,
|
||||
)
|
||||
from ..device import Device
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
@@ -50,9 +51,13 @@ class AshaService(TemplateService):
|
||||
SUPPORTED_CODEC_ID = [0x02, 0x01] # Codec IDs [G.722 at 16 kHz]
|
||||
RENDER_DELAY = [00, 00]
|
||||
|
||||
def __init__(self, capability: int, hisyncid: List[int]):
|
||||
def __init__(self, capability: int, hisyncid: List[int], device: Device, psm=0):
|
||||
self.hisyncid = hisyncid
|
||||
self.capability = capability # Device Capabilities [Left, Monaural]
|
||||
self.device = device
|
||||
self.emitted_data_name = 'ASHA_data_' + str(self.capability)
|
||||
self.audio_out_data = b''
|
||||
self.psm = psm # a non-zero psm is mainly for testing purpose
|
||||
|
||||
# Handler for volume control
|
||||
def on_volume_write(_connection, value):
|
||||
@@ -116,9 +121,18 @@ class AshaService(TemplateService):
|
||||
CharacteristicValue(write=on_volume_write),
|
||||
)
|
||||
|
||||
# TODO add real psm value
|
||||
self.psm = 0x0080
|
||||
# self.psm = device.register_l2cap_channel_server(0, on_coc, 8)
|
||||
# Register an L2CAP CoC server
|
||||
def on_coc(channel):
|
||||
def on_data(data):
|
||||
logging.debug(f'<<< data received:{data}')
|
||||
|
||||
self.emit(self.emitted_data_name, data)
|
||||
self.audio_out_data += data
|
||||
|
||||
channel.sink = on_data
|
||||
|
||||
# let the server find a free PSM
|
||||
self.psm = self.device.register_l2cap_channel_server(self.psm, on_coc, 8)
|
||||
self.le_psm_out_characteristic = Characteristic(
|
||||
GATT_ASHA_LE_PSM_OUT_CHARACTERISTIC,
|
||||
Characteristic.READ,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
import struct
|
||||
from typing import Tuple
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from ..gatt_client import ProfileServiceProxy
|
||||
from ..gatt import (
|
||||
@@ -52,14 +52,14 @@ class DeviceInformationService(TemplateService):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
manufacturer_name: str = None,
|
||||
model_number: str = None,
|
||||
serial_number: str = None,
|
||||
hardware_revision: str = None,
|
||||
firmware_revision: str = None,
|
||||
software_revision: str = None,
|
||||
system_id: Tuple[int, int] = None, # (OUI, Manufacturer ID)
|
||||
ieee_regulatory_certification_data_list: bytes = None
|
||||
manufacturer_name: Optional[str] = None,
|
||||
model_number: Optional[str] = None,
|
||||
serial_number: Optional[str] = None,
|
||||
hardware_revision: Optional[str] = None,
|
||||
firmware_revision: Optional[str] = None,
|
||||
software_revision: Optional[str] = None,
|
||||
system_id: Optional[Tuple[int, int]] = None, # (OUI, Manufacturer ID)
|
||||
ieee_regulatory_certification_data_list: Optional[bytes] = None
|
||||
# TODO: pnp_id
|
||||
):
|
||||
characteristics = [
|
||||
|
||||
0
bumble/profiles/py.typed
Normal file
0
bumble/profiles/py.typed
Normal file
0
bumble/py.typed
Normal file
0
bumble/py.typed
Normal file
@@ -15,10 +15,12 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
import struct
|
||||
from colors import color
|
||||
import colors
|
||||
from typing import Dict, Type
|
||||
|
||||
from . import core
|
||||
from .core import InvalidStateError
|
||||
@@ -520,7 +522,7 @@ class SDP_PDU:
|
||||
See Bluetooth spec @ Vol 3, Part B - 4.2 PROTOCOL DATA UNIT FORMAT
|
||||
'''
|
||||
|
||||
sdp_pdu_classes = {}
|
||||
sdp_pdu_classes: Dict[int, Type[SDP_PDU]] = {}
|
||||
name = None
|
||||
pdu_id = 0
|
||||
|
||||
|
||||
@@ -22,9 +22,12 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
import asyncio
|
||||
import secrets
|
||||
from typing import Dict, Type
|
||||
|
||||
from pyee import EventEmitter
|
||||
from colors import color
|
||||
|
||||
@@ -184,7 +187,7 @@ class SMP_Command:
|
||||
See Bluetooth spec @ Vol 3, Part H - 3 SECURITY MANAGER PROTOCOL
|
||||
'''
|
||||
|
||||
smp_classes = {}
|
||||
smp_classes: Dict[int, Type[SMP_Command]] = {}
|
||||
code = 0
|
||||
name = ''
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright 2021-2022 Google LLC
|
||||
# Copyright 2021-2023 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -20,9 +20,11 @@ import grpc
|
||||
|
||||
from .common import PumpedTransport, PumpedPacketSource, PumpedPacketSink
|
||||
from .emulated_bluetooth_pb2_grpc import EmulatedBluetoothServiceStub
|
||||
from .emulated_bluetooth_packets_pb2 import HCIPacket
|
||||
from .emulated_bluetooth_vhci_pb2_grpc import VhciForwardingServiceStub
|
||||
|
||||
# pylint: disable-next=no-name-in-module
|
||||
from .emulated_bluetooth_packets_pb2 import HCIPacket
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright 2021-2022 Google LLC
|
||||
# Copyright 2021-2023 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -16,10 +16,9 @@
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: emulated_bluetooth_packets.proto
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf.internal import builder as _builder
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import message as _message
|
||||
from google.protobuf import reflection as _reflection
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
|
||||
# @@protoc_insertion_point(imports)
|
||||
@@ -31,20 +30,10 @@ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
|
||||
b'\n emulated_bluetooth_packets.proto\x12\x1b\x61ndroid.emulation.bluetooth\"\xfb\x01\n\tHCIPacket\x12?\n\x04type\x18\x01 \x01(\x0e\x32\x31.android.emulation.bluetooth.HCIPacket.PacketType\x12\x0e\n\x06packet\x18\x02 \x01(\x0c\"\x9c\x01\n\nPacketType\x12\x1b\n\x17PACKET_TYPE_UNSPECIFIED\x10\x00\x12\x1b\n\x17PACKET_TYPE_HCI_COMMAND\x10\x01\x12\x13\n\x0fPACKET_TYPE_ACL\x10\x02\x12\x13\n\x0fPACKET_TYPE_SCO\x10\x03\x12\x15\n\x11PACKET_TYPE_EVENT\x10\x04\x12\x13\n\x0fPACKET_TYPE_ISO\x10\x05\x42J\n\x1f\x63om.android.emulation.bluetoothP\x01\xf8\x01\x01\xa2\x02\x03\x41\x45\x42\xaa\x02\x1b\x41ndroid.Emulation.Bluetoothb\x06proto3'
|
||||
)
|
||||
|
||||
|
||||
_HCIPACKET = DESCRIPTOR.message_types_by_name['HCIPacket']
|
||||
_HCIPACKET_PACKETTYPE = _HCIPACKET.enum_types_by_name['PacketType']
|
||||
HCIPacket = _reflection.GeneratedProtocolMessageType(
|
||||
'HCIPacket',
|
||||
(_message.Message,),
|
||||
{
|
||||
'DESCRIPTOR': _HCIPACKET,
|
||||
'__module__': 'emulated_bluetooth_packets_pb2'
|
||||
# @@protoc_insertion_point(class_scope:android.emulation.bluetooth.HCIPacket)
|
||||
},
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
|
||||
_builder.BuildTopDescriptorsAndMessages(
|
||||
DESCRIPTOR, 'emulated_bluetooth_packets_pb2', globals()
|
||||
)
|
||||
_sym_db.RegisterMessage(HCIPacket)
|
||||
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
|
||||
DESCRIPTOR._options = None
|
||||
|
||||
41
bumble/transport/emulated_bluetooth_packets_pb2.pyi
Normal file
41
bumble/transport/emulated_bluetooth_packets_pb2.pyi
Normal file
@@ -0,0 +1,41 @@
|
||||
# Copyright 2021-2023 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.
|
||||
|
||||
from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import message as _message
|
||||
from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union
|
||||
|
||||
DESCRIPTOR: _descriptor.FileDescriptor
|
||||
|
||||
class HCIPacket(_message.Message):
|
||||
__slots__ = ["packet", "type"]
|
||||
|
||||
class PacketType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = []
|
||||
PACKET_FIELD_NUMBER: _ClassVar[int]
|
||||
PACKET_TYPE_ACL: HCIPacket.PacketType
|
||||
PACKET_TYPE_EVENT: HCIPacket.PacketType
|
||||
PACKET_TYPE_HCI_COMMAND: HCIPacket.PacketType
|
||||
PACKET_TYPE_ISO: HCIPacket.PacketType
|
||||
PACKET_TYPE_SCO: HCIPacket.PacketType
|
||||
PACKET_TYPE_UNSPECIFIED: HCIPacket.PacketType
|
||||
TYPE_FIELD_NUMBER: _ClassVar[int]
|
||||
packet: bytes
|
||||
type: HCIPacket.PacketType
|
||||
def __init__(
|
||||
self,
|
||||
type: _Optional[_Union[HCIPacket.PacketType, str]] = ...,
|
||||
packet: _Optional[bytes] = ...,
|
||||
) -> None: ...
|
||||
17
bumble/transport/emulated_bluetooth_packets_pb2_grpc.py
Normal file
17
bumble/transport/emulated_bluetooth_packets_pb2_grpc.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# Copyright 2021-2023 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.
|
||||
|
||||
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
|
||||
"""Client and server classes corresponding to protobuf-defined services."""
|
||||
import grpc
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright 2021-2022 Google LLC
|
||||
# Copyright 2021-2023 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -16,10 +16,9 @@
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: emulated_bluetooth.proto
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf.internal import builder as _builder
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import message as _message
|
||||
from google.protobuf import reflection as _reflection
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
|
||||
# @@protoc_insertion_point(imports)
|
||||
@@ -34,20 +33,8 @@ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
|
||||
b'\n\x18\x65mulated_bluetooth.proto\x12\x1b\x61ndroid.emulation.bluetooth\x1a emulated_bluetooth_packets.proto\"\x19\n\x07RawData\x12\x0e\n\x06packet\x18\x01 \x01(\x0c\x32\xcb\x02\n\x18\x45mulatedBluetoothService\x12\x64\n\x12registerClassicPhy\x12$.android.emulation.bluetooth.RawData\x1a$.android.emulation.bluetooth.RawData(\x01\x30\x01\x12`\n\x0eregisterBlePhy\x12$.android.emulation.bluetooth.RawData\x1a$.android.emulation.bluetooth.RawData(\x01\x30\x01\x12g\n\x11registerHCIDevice\x12&.android.emulation.bluetooth.HCIPacket\x1a&.android.emulation.bluetooth.HCIPacket(\x01\x30\x01\x42\"\n\x1e\x63om.android.emulator.bluetoothP\x01\x62\x06proto3'
|
||||
)
|
||||
|
||||
|
||||
_RAWDATA = DESCRIPTOR.message_types_by_name['RawData']
|
||||
RawData = _reflection.GeneratedProtocolMessageType(
|
||||
'RawData',
|
||||
(_message.Message,),
|
||||
{
|
||||
'DESCRIPTOR': _RAWDATA,
|
||||
'__module__': 'emulated_bluetooth_pb2'
|
||||
# @@protoc_insertion_point(class_scope:android.emulation.bluetooth.RawData)
|
||||
},
|
||||
)
|
||||
_sym_db.RegisterMessage(RawData)
|
||||
|
||||
_EMULATEDBLUETOOTHSERVICE = DESCRIPTOR.services_by_name['EmulatedBluetoothService']
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'emulated_bluetooth_pb2', globals())
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
|
||||
DESCRIPTOR._options = None
|
||||
|
||||
26
bumble/transport/emulated_bluetooth_pb2.pyi
Normal file
26
bumble/transport/emulated_bluetooth_pb2.pyi
Normal file
@@ -0,0 +1,26 @@
|
||||
# Copyright 2021-2023 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.
|
||||
|
||||
import emulated_bluetooth_packets_pb2 as _emulated_bluetooth_packets_pb2
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import message as _message
|
||||
from typing import ClassVar as _ClassVar, Optional as _Optional
|
||||
|
||||
DESCRIPTOR: _descriptor.FileDescriptor
|
||||
|
||||
class RawData(_message.Message):
|
||||
__slots__ = ["packet"]
|
||||
PACKET_FIELD_NUMBER: _ClassVar[int]
|
||||
packet: bytes
|
||||
def __init__(self, packet: _Optional[bytes] = ...) -> None: ...
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright 2021-2022 Google LLC
|
||||
# Copyright 2021-2023 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright 2021-2022 Google LLC
|
||||
# Copyright 2021-2023 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -16,10 +16,9 @@
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: emulated_bluetooth_vhci.proto
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf.internal import builder as _builder
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import message as _message
|
||||
from google.protobuf import reflection as _reflection
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
|
||||
# @@protoc_insertion_point(imports)
|
||||
@@ -27,15 +26,17 @@ from google.protobuf import symbol_database as _symbol_database
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
import emulated_bluetooth_packets_pb2 as emulated__bluetooth__packets__pb2
|
||||
from . import emulated_bluetooth_packets_pb2 as emulated__bluetooth__packets__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
|
||||
b'\n\x1d\x65mulated_bluetooth_vhci.proto\x12\x1b\x61ndroid.emulation.bluetooth\x1a emulated_bluetooth_packets.proto2y\n\x15VhciForwardingService\x12`\n\nattachVhci\x12&.android.emulation.bluetooth.HCIPacket\x1a&.android.emulation.bluetooth.HCIPacket(\x01\x30\x01\x42J\n\x1f\x63om.android.emulation.bluetoothP\x01\xf8\x01\x01\xa2\x02\x03\x41\x45\x42\xaa\x02\x1b\x41ndroid.Emulation.Bluetoothb\x06proto3'
|
||||
)
|
||||
|
||||
|
||||
_VHCIFORWARDINGSERVICE = DESCRIPTOR.services_by_name['VhciForwardingService']
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
|
||||
_builder.BuildTopDescriptorsAndMessages(
|
||||
DESCRIPTOR, 'emulated_bluetooth_vhci_pb2', globals()
|
||||
)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
|
||||
DESCRIPTOR._options = None
|
||||
|
||||
19
bumble/transport/emulated_bluetooth_vhci_pb2.pyi
Normal file
19
bumble/transport/emulated_bluetooth_vhci_pb2.pyi
Normal file
@@ -0,0 +1,19 @@
|
||||
# Copyright 2021-2023 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.
|
||||
|
||||
import emulated_bluetooth_packets_pb2 as _emulated_bluetooth_packets_pb2
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from typing import ClassVar as _ClassVar
|
||||
|
||||
DESCRIPTOR: _descriptor.FileDescriptor
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright 2021-2022 Google LLC
|
||||
# Copyright 2021-2023 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -97,7 +97,7 @@ async def open_hci_socket_transport(spec):
|
||||
super().__init__()
|
||||
self.socket = hci_socket
|
||||
asyncio.get_running_loop().add_reader(
|
||||
socket.fileno(), self.recv_until_would_block
|
||||
self.socket.fileno(), self.recv_until_would_block
|
||||
)
|
||||
|
||||
def recv_until_would_block(self):
|
||||
@@ -140,7 +140,7 @@ async def open_hci_socket_transport(spec):
|
||||
if not self.writer_added:
|
||||
asyncio.get_running_loop().add_writer(
|
||||
# pylint: disable=no-member
|
||||
socket.fileno(),
|
||||
self.socket.fileno(),
|
||||
self.send_until_would_block,
|
||||
)
|
||||
self.writer_added = True
|
||||
|
||||
0
bumble/transport/py.typed
Normal file
0
bumble/transport/py.typed
Normal file
@@ -20,7 +20,6 @@ import logging
|
||||
import threading
|
||||
import time
|
||||
|
||||
import libusb_package
|
||||
import usb.core
|
||||
import usb.util
|
||||
from colors import color
|
||||
@@ -205,16 +204,22 @@ async def open_pyusb_transport(spec):
|
||||
await self.sink.stop()
|
||||
usb.util.release_interface(self.device, 0)
|
||||
|
||||
usb_find = usb.core.find
|
||||
try:
|
||||
import libusb_package
|
||||
except ImportError:
|
||||
logger.debug('libusb_package is not available')
|
||||
else:
|
||||
usb_find = libusb_package.find
|
||||
|
||||
# Find the device according to the spec moniker
|
||||
if ':' in spec:
|
||||
vendor_id, product_id = spec.split(':')
|
||||
device = libusb_package.find(
|
||||
idVendor=int(vendor_id, 16), idProduct=int(product_id, 16)
|
||||
)
|
||||
device = usb_find(idVendor=int(vendor_id, 16), idProduct=int(product_id, 16))
|
||||
else:
|
||||
device_index = int(spec)
|
||||
devices = list(
|
||||
libusb_package.find(
|
||||
usb_find(
|
||||
find_all=1,
|
||||
bDeviceClass=USB_DEVICE_CLASS_WIRELESS_CONTROLLER,
|
||||
bDeviceSubClass=USB_DEVICE_SUBCLASS_RF_CONTROLLER,
|
||||
|
||||
@@ -22,7 +22,6 @@ import collections
|
||||
import ctypes
|
||||
import platform
|
||||
|
||||
import libusb_package
|
||||
import usb1
|
||||
from colors import color
|
||||
|
||||
@@ -45,11 +44,20 @@ def load_libusb():
|
||||
If the library does not exists, do nothing and usb1 will search default system paths
|
||||
when usb1.USBContext is created.
|
||||
'''
|
||||
if libusb_path := libusb_package.get_library_path():
|
||||
logger.debug(f'loading libusb library at {libusb_path}')
|
||||
dll_loader = ctypes.WinDLL if platform.system() == 'Windows' else ctypes.CDLL
|
||||
libusb_dll = dll_loader(str(libusb_path), use_errno=True, use_last_error=True)
|
||||
usb1.loadLibrary(libusb_dll)
|
||||
try:
|
||||
import libusb_package
|
||||
except ImportError:
|
||||
logger.debug('libusb_package is not available')
|
||||
else:
|
||||
if libusb_path := libusb_package.get_library_path():
|
||||
logger.debug(f'loading libusb library at {libusb_path}')
|
||||
dll_loader = (
|
||||
ctypes.WinDLL if platform.system() == 'Windows' else ctypes.CDLL
|
||||
)
|
||||
libusb_dll = dll_loader(
|
||||
str(libusb_path), use_errno=True, use_last_error=True
|
||||
)
|
||||
usb1.loadLibrary(libusb_dll)
|
||||
|
||||
|
||||
async def open_usb_transport(spec):
|
||||
|
||||
@@ -50,3 +50,44 @@ signature-mutators="AsyncRunner.run_in_task"
|
||||
|
||||
[tool.black]
|
||||
skip-string-normalization = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "bumble.transport.emulated_bluetooth_pb2_grpc"
|
||||
ignore_missing_imports = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "bumble.transport.emulated_bluetooth_packets_pb2"
|
||||
ignore_errors = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "aioconsole.*"
|
||||
ignore_missing_imports = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "bitstruct.*"
|
||||
ignore_missing_imports = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "colors.*"
|
||||
ignore_missing_imports = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "emulated_bluetooth_packets_pb2.*"
|
||||
ignore_missing_imports = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "grpc.*"
|
||||
ignore_missing_imports = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "serial_asyncio.*"
|
||||
ignore_missing_imports = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "usb.*"
|
||||
ignore_missing_imports = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "usb1.*"
|
||||
ignore_missing_imports = true
|
||||
|
||||
|
||||
27
scripts/process_android_emulator_protos.sh
Normal file
27
scripts/process_android_emulator_protos.sh
Normal file
@@ -0,0 +1,27 @@
|
||||
# Invoke this script with an argument pointing to where the Android emulator .proto files are.
|
||||
# The .proto files should be slightly modified from their original version (as distributed with
|
||||
# the Android emulator):
|
||||
# --> Remove unused types/methods from emulated_bluetooth.proto
|
||||
|
||||
PROTOC_OUT=bumble/transport
|
||||
LICENSE_FILE_INPUT=bumble/transport/android_emulator.py
|
||||
|
||||
proto_files=(emulated_bluetooth.proto emulated_bluetooth_vhci.proto emulated_bluetooth_packets.proto)
|
||||
for proto_file in "${proto_files[@]}"
|
||||
do
|
||||
python -m grpc_tools.protoc -I$1 --proto_path=bumble/transport --python_out=$PROTOC_OUT --pyi_out=$PROTOC_OUT --grpc_python_out=$PROTOC_OUT $1/$proto_file
|
||||
done
|
||||
|
||||
python_files=(emulated_bluetooth_pb2.py emulated_bluetooth_pb2_grpc.py emulated_bluetooth_packets_pb2.py emulated_bluetooth_packets_pb2_grpc.py emulated_bluetooth_vhci_pb2_grpc.py emulated_bluetooth_vhci_pb2.py)
|
||||
for python_file in "${python_files[@]}"
|
||||
do
|
||||
sed -i '' 's/^import .*_pb2 as/from . &/' $PROTOC_OUT/$python_file
|
||||
done
|
||||
|
||||
stub_files=(emulated_bluetooth_pb2.pyi emulated_bluetooth_packets_pb2.pyi emulated_bluetooth_vhci_pb2.pyi)
|
||||
for source_file in "${python_files[@]}" "${stub_files[@]}"
|
||||
do
|
||||
head -14 $LICENSE_FILE_INPUT > $PROTOC_OUT/${source_file}.lic
|
||||
cat $PROTOC_OUT/$source_file >> $PROTOC_OUT/${source_file}.lic
|
||||
mv $PROTOC_OUT/${source_file}.lic $PROTOC_OUT/$source_file
|
||||
done
|
||||
10
setup.cfg
10
setup.cfg
@@ -28,6 +28,7 @@ packages = bumble, bumble.transport, bumble.profiles, bumble.apps, bumble.apps.l
|
||||
package_dir =
|
||||
bumble = bumble
|
||||
bumble.apps = apps
|
||||
include-package-data = True
|
||||
install_requires =
|
||||
aioconsole >= 0.4.1
|
||||
ansicolors >= 1.1
|
||||
@@ -37,7 +38,7 @@ install_requires =
|
||||
cryptography == 35; platform_system!='Emscripten'
|
||||
grpcio >= 1.46; platform_system!='Emscripten'
|
||||
libusb1 >= 2.0.1; platform_system!='Emscripten'
|
||||
libusb-package == 1.0.26.0; platform_system!='Emscripten'
|
||||
libusb-package == 1.0.26.1; platform_system!='Emscripten'
|
||||
prompt_toolkit >= 3.0.16; platform_system!='Emscripten'
|
||||
protobuf >= 3.12.4
|
||||
pyee >= 8.2.2
|
||||
@@ -60,6 +61,9 @@ console_scripts =
|
||||
bumble-usb-probe = bumble.apps.usb_probe:main
|
||||
bumble-link-relay = bumble.apps.link_relay.link_relay:main
|
||||
|
||||
[options.package_data]
|
||||
* = py.typed, *.pyi
|
||||
|
||||
[options.extras_require]
|
||||
build =
|
||||
build >= 0.7
|
||||
@@ -71,8 +75,12 @@ test =
|
||||
development =
|
||||
black >= 22.10
|
||||
invoke >= 1.7.3
|
||||
mypy >= 0.991
|
||||
nox >= 2022
|
||||
pylint >= 2.15.8
|
||||
types-appdirs >= 1.4.3
|
||||
types-invoke >= 1.7.3
|
||||
types-protobuf >= 4.21.0
|
||||
documentation =
|
||||
mkdocs >= 1.4.0
|
||||
mkdocs-material >= 8.5.6
|
||||
|
||||
31
tasks.py
31
tasks.py
@@ -22,7 +22,7 @@ Invoke tasks
|
||||
import os
|
||||
|
||||
from invoke import task, call, Collection
|
||||
from invoke.exceptions import UnexpectedExit
|
||||
from invoke.exceptions import Exit, UnexpectedExit
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -126,9 +126,9 @@ def lint(ctx, disable='C,R', errors_only=False):
|
||||
try:
|
||||
ctx.run(f"pylint {' '.join(options)} bumble apps examples tasks.py")
|
||||
print("The linter is happy. ✅ 😊 🐝'")
|
||||
except UnexpectedExit:
|
||||
except UnexpectedExit as exc:
|
||||
print("Please check your code against the linter messages. ❌")
|
||||
print(">>> Linter done.")
|
||||
raise Exit(code=1) from exc
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -143,13 +143,31 @@ def format_code(ctx, check=False, diff=False):
|
||||
print(">>> Running the formatter...")
|
||||
try:
|
||||
ctx.run(f"black -S {' '.join(options)} .")
|
||||
except UnexpectedExit:
|
||||
except UnexpectedExit as exc:
|
||||
print("Please run 'invoke project.format' or 'black .' to format the code. ❌")
|
||||
print(">>> formatter done.")
|
||||
raise Exit(code=1) from exc
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@task(pre=[call(format_code, check=True), call(lint, errors_only=True), test])
|
||||
@task
|
||||
def check_types(ctx):
|
||||
checklist = ["apps", "bumble", "examples", "tests", "tasks.py"]
|
||||
try:
|
||||
ctx.run(f"mypy {' '.join(checklist)}")
|
||||
except UnexpectedExit as exc:
|
||||
print("Please check your code against the mypy messages.")
|
||||
raise Exit(code=1) from exc
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@task(
|
||||
pre=[
|
||||
call(format_code, check=True),
|
||||
call(lint, errors_only=True),
|
||||
call(check_types),
|
||||
test,
|
||||
]
|
||||
)
|
||||
def pre_commit(_ctx):
|
||||
print("All good!")
|
||||
|
||||
@@ -157,4 +175,5 @@ def pre_commit(_ctx):
|
||||
# -----------------------------------------------------------------------------
|
||||
project_tasks.add_task(lint)
|
||||
project_tasks.add_task(format_code, name="format")
|
||||
project_tasks.add_task(check_types, name="check-types")
|
||||
project_tasks.add_task(pre_commit)
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from bumble.device import Device
|
||||
from bumble.transport import PacketParser
|
||||
from bumble.transport.common import PacketParser
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user