forked from auracaster/bumble_mirror
Ruff: Add and fix UP rules
This commit is contained in:
@@ -23,7 +23,8 @@ import contextlib
|
|||||||
import dataclasses
|
import dataclasses
|
||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, AsyncGenerator, Coroutine, Optional
|
from collections.abc import AsyncGenerator, Coroutine
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
@@ -112,12 +113,12 @@ class BroadcastScanner(bumble.utils.EventEmitter):
|
|||||||
sync: bumble.device.PeriodicAdvertisingSync
|
sync: bumble.device.PeriodicAdvertisingSync
|
||||||
broadcast_id: int
|
broadcast_id: int
|
||||||
rssi: int = 0
|
rssi: int = 0
|
||||||
public_broadcast_announcement: Optional[pbp.PublicBroadcastAnnouncement] = None
|
public_broadcast_announcement: pbp.PublicBroadcastAnnouncement | None = None
|
||||||
broadcast_audio_announcement: Optional[bap.BroadcastAudioAnnouncement] = None
|
broadcast_audio_announcement: bap.BroadcastAudioAnnouncement | None = None
|
||||||
basic_audio_announcement: Optional[bap.BasicAudioAnnouncement] = None
|
basic_audio_announcement: bap.BasicAudioAnnouncement | None = None
|
||||||
appearance: Optional[core.Appearance] = None
|
appearance: core.Appearance | None = None
|
||||||
biginfo: Optional[bumble.device.BigInfoAdvertisement] = None
|
biginfo: bumble.device.BigInfoAdvertisement | None = None
|
||||||
manufacturer_data: Optional[tuple[str, bytes]] = None
|
manufacturer_data: tuple[str, bytes] | None = None
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -433,7 +434,7 @@ async def create_device(transport: str) -> AsyncGenerator[bumble.device.Device,
|
|||||||
|
|
||||||
|
|
||||||
async def find_broadcast_by_name(
|
async def find_broadcast_by_name(
|
||||||
device: bumble.device.Device, name: Optional[str]
|
device: bumble.device.Device, name: str | None
|
||||||
) -> BroadcastScanner.Broadcast:
|
) -> BroadcastScanner.Broadcast:
|
||||||
result = asyncio.get_running_loop().create_future()
|
result = asyncio.get_running_loop().create_future()
|
||||||
|
|
||||||
@@ -474,8 +475,8 @@ async def run_scan(
|
|||||||
|
|
||||||
|
|
||||||
async def run_assist(
|
async def run_assist(
|
||||||
broadcast_name: Optional[str],
|
broadcast_name: str | None,
|
||||||
source_id: Optional[int],
|
source_id: int | None,
|
||||||
command: str,
|
command: str,
|
||||||
transport: str,
|
transport: str,
|
||||||
address: str,
|
address: str,
|
||||||
@@ -658,7 +659,7 @@ async def run_pair(transport: str, address: str) -> None:
|
|||||||
|
|
||||||
async def run_receive(
|
async def run_receive(
|
||||||
transport: str,
|
transport: str,
|
||||||
broadcast_id: Optional[int],
|
broadcast_id: int | None,
|
||||||
output: str,
|
output: str,
|
||||||
broadcast_code: str | None,
|
broadcast_code: str | None,
|
||||||
sync_timeout: float,
|
sync_timeout: float,
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import logging
|
|||||||
import statistics
|
import statistics
|
||||||
import struct
|
import struct
|
||||||
import time
|
import time
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
@@ -257,8 +256,8 @@ async def pre_power_on(device: Device, classic: bool) -> None:
|
|||||||
|
|
||||||
async def post_power_on(
|
async def post_power_on(
|
||||||
device: Device,
|
device: Device,
|
||||||
le_scan: Optional[tuple[int, int]],
|
le_scan: tuple[int, int] | None,
|
||||||
le_advertise: Optional[int],
|
le_advertise: int | None,
|
||||||
classic_page_scan: bool,
|
classic_page_scan: bool,
|
||||||
classic_inquiry_scan: bool,
|
classic_inquiry_scan: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -1300,7 +1299,7 @@ class IsoClient(StreamedPacketIO):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
self.device = device
|
self.device = device
|
||||||
self.ready = asyncio.Event()
|
self.ready = asyncio.Event()
|
||||||
self.cis_link: Optional[CisLink] = None
|
self.cis_link: CisLink | None = None
|
||||||
|
|
||||||
async def on_connection(
|
async def on_connection(
|
||||||
self, connection: Connection, cis_link: CisLink, sender: bool
|
self, connection: Connection, cis_link: CisLink, sender: bool
|
||||||
@@ -1341,7 +1340,7 @@ class IsoServer(StreamedPacketIO):
|
|||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.device = device
|
self.device = device
|
||||||
self.cis_link: Optional[CisLink] = None
|
self.cis_link: CisLink | None = None
|
||||||
self.ready = asyncio.Event()
|
self.ready = asyncio.Event()
|
||||||
|
|
||||||
logging.info(
|
logging.info(
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from typing import Optional, Union
|
|
||||||
|
|
||||||
import click
|
import click
|
||||||
import humanize
|
import humanize
|
||||||
@@ -126,8 +125,8 @@ def parse_phys(phys):
|
|||||||
# Console App
|
# Console App
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
class ConsoleApp:
|
class ConsoleApp:
|
||||||
connected_peer: Optional[Peer]
|
connected_peer: Peer | None
|
||||||
connection_phy: Optional[ConnectionPHY]
|
connection_phy: ConnectionPHY | None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.known_addresses = set()
|
self.known_addresses = set()
|
||||||
@@ -520,7 +519,7 @@ class ConsoleApp:
|
|||||||
|
|
||||||
self.show_attributes(attributes)
|
self.show_attributes(attributes)
|
||||||
|
|
||||||
def find_remote_characteristic(self, param) -> Optional[CharacteristicProxy]:
|
def find_remote_characteristic(self, param) -> CharacteristicProxy | None:
|
||||||
if not self.connected_peer:
|
if not self.connected_peer:
|
||||||
return None
|
return None
|
||||||
parts = param.split('.')
|
parts = param.split('.')
|
||||||
@@ -542,9 +541,7 @@ class ConsoleApp:
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def find_local_attribute(
|
def find_local_attribute(self, param) -> Characteristic | Descriptor | None:
|
||||||
self, param
|
|
||||||
) -> Optional[Union[Characteristic, Descriptor]]:
|
|
||||||
parts = param.split('.')
|
parts = param.split('.')
|
||||||
if len(parts) == 3:
|
if len(parts) == 3:
|
||||||
service_uuid = UUID(parts[0])
|
service_uuid = UUID(parts[0])
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
import asyncio
|
import asyncio
|
||||||
import time
|
import time
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
@@ -41,7 +40,7 @@ class Loopback:
|
|||||||
self.transport = transport
|
self.transport = transport
|
||||||
self.packet_size = packet_size
|
self.packet_size = packet_size
|
||||||
self.packet_count = packet_count
|
self.packet_count = packet_count
|
||||||
self.connection_handle: Optional[int] = None
|
self.connection_handle: int | None = None
|
||||||
self.connection_event = asyncio.Event()
|
self.connection_event = asyncio.Event()
|
||||||
self.done = asyncio.Event()
|
self.done = asyncio.Event()
|
||||||
self.expected_cid = 0
|
self.expected_cid = 0
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
# Imports
|
# Imports
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Callable, Iterable, Optional
|
from collections.abc import Callable, Iterable
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ async def show_vcs(vcs: VolumeControlServiceProxy) -> None:
|
|||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
async def show_device_info(peer, done: Optional[asyncio.Future]) -> None:
|
async def show_device_info(peer, done: asyncio.Future | None) -> None:
|
||||||
try:
|
try:
|
||||||
# Discover all services
|
# Discover all services
|
||||||
print(color('### Discovering Services and Characteristics', 'magenta'))
|
print(color('### Discovering Services and Characteristics', 'magenta'))
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ def retrieve_config(config: str) -> dict[str, Any]:
|
|||||||
if not config:
|
if not config:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
with open(config, 'r') as f:
|
with open(config) as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional, Union
|
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
@@ -190,7 +189,7 @@ class Player:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
transport: str,
|
transport: str,
|
||||||
device_config: Optional[str],
|
device_config: str | None,
|
||||||
authenticate: bool,
|
authenticate: bool,
|
||||||
encrypt: bool,
|
encrypt: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -198,8 +197,8 @@ class Player:
|
|||||||
self.device_config = device_config
|
self.device_config = device_config
|
||||||
self.authenticate = authenticate
|
self.authenticate = authenticate
|
||||||
self.encrypt = encrypt
|
self.encrypt = encrypt
|
||||||
self.avrcp_protocol: Optional[AvrcpProtocol] = None
|
self.avrcp_protocol: AvrcpProtocol | None = None
|
||||||
self.done: Optional[asyncio.Event]
|
self.done: asyncio.Event | None
|
||||||
|
|
||||||
async def run(self, workload) -> None:
|
async def run(self, workload) -> None:
|
||||||
self.done = asyncio.Event()
|
self.done = asyncio.Event()
|
||||||
@@ -314,7 +313,7 @@ class Player:
|
|||||||
codec_type: int,
|
codec_type: int,
|
||||||
vendor_id: int,
|
vendor_id: int,
|
||||||
codec_id: int,
|
codec_id: int,
|
||||||
packet_source: Union[SbcPacketSource, AacPacketSource, OpusPacketSource],
|
packet_source: SbcPacketSource | AacPacketSource | OpusPacketSource,
|
||||||
codec_capabilities: MediaCodecCapabilities,
|
codec_capabilities: MediaCodecCapabilities,
|
||||||
):
|
):
|
||||||
# Discover all endpoints on the remote device
|
# Discover all endpoints on the remote device
|
||||||
@@ -419,7 +418,7 @@ class Player:
|
|||||||
async def play(
|
async def play(
|
||||||
self,
|
self,
|
||||||
device: Device,
|
device: Device,
|
||||||
address: Optional[str],
|
address: str | None,
|
||||||
audio_format: str,
|
audio_format: str,
|
||||||
audio_file: str,
|
audio_file: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -448,7 +447,7 @@ class Player:
|
|||||||
return input_file.read(byte_count)
|
return input_file.read(byte_count)
|
||||||
|
|
||||||
# Obtain the codec capabilities from the stream
|
# Obtain the codec capabilities from the stream
|
||||||
packet_source: Union[SbcPacketSource, AacPacketSource, OpusPacketSource]
|
packet_source: SbcPacketSource | AacPacketSource | OpusPacketSource
|
||||||
vendor_id = 0
|
vendor_id = 0
|
||||||
codec_id = 0
|
codec_id = 0
|
||||||
if audio_format == "sbc":
|
if audio_format == "sbc":
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
import asyncio
|
import asyncio
|
||||||
import time
|
import time
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
@@ -82,14 +81,14 @@ class ServerBridge:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self, channel: int, uuid: str, trace: bool, tcp_host: str, tcp_port: int
|
self, channel: int, uuid: str, trace: bool, tcp_host: str, tcp_port: int
|
||||||
) -> None:
|
) -> None:
|
||||||
self.device: Optional[Device] = None
|
self.device: Device | None = None
|
||||||
self.channel = channel
|
self.channel = channel
|
||||||
self.uuid = uuid
|
self.uuid = uuid
|
||||||
self.tcp_host = tcp_host
|
self.tcp_host = tcp_host
|
||||||
self.tcp_port = tcp_port
|
self.tcp_port = tcp_port
|
||||||
self.rfcomm_channel: Optional[rfcomm.DLC] = None
|
self.rfcomm_channel: rfcomm.DLC | None = None
|
||||||
self.tcp_tracer: Optional[Tracer]
|
self.tcp_tracer: Tracer | None
|
||||||
self.rfcomm_tracer: Optional[Tracer]
|
self.rfcomm_tracer: Tracer | None
|
||||||
|
|
||||||
if trace:
|
if trace:
|
||||||
self.tcp_tracer = Tracer(color("RFCOMM->TCP", "cyan"))
|
self.tcp_tracer = Tracer(color("RFCOMM->TCP", "cyan"))
|
||||||
@@ -242,14 +241,14 @@ class ClientBridge:
|
|||||||
self.tcp_port = tcp_port
|
self.tcp_port = tcp_port
|
||||||
self.authenticate = authenticate
|
self.authenticate = authenticate
|
||||||
self.encrypt = encrypt
|
self.encrypt = encrypt
|
||||||
self.device: Optional[Device] = None
|
self.device: Device | None = None
|
||||||
self.connection: Optional[Connection] = None
|
self.connection: Connection | None = None
|
||||||
self.rfcomm_client: Optional[rfcomm.Client]
|
self.rfcomm_client: rfcomm.Client | None
|
||||||
self.rfcomm_mux: Optional[rfcomm.Multiplexer]
|
self.rfcomm_mux: rfcomm.Multiplexer | None
|
||||||
self.tcp_connected: bool = False
|
self.tcp_connected: bool = False
|
||||||
|
|
||||||
self.tcp_tracer: Optional[Tracer]
|
self.tcp_tracer: Tracer | None
|
||||||
self.rfcomm_tracer: Optional[Tracer]
|
self.rfcomm_tracer: Tracer | None
|
||||||
|
|
||||||
if trace:
|
if trace:
|
||||||
self.tcp_tracer = Tracer(color("RFCOMM->TCP", "cyan"))
|
self.tcp_tracer = Tracer(color("RFCOMM->TCP", "cyan"))
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import pathlib
|
|||||||
import subprocess
|
import subprocess
|
||||||
import weakref
|
import weakref
|
||||||
from importlib import resources
|
from importlib import resources
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import click
|
import click
|
||||||
@@ -156,7 +155,7 @@ class QueuedOutput(Output):
|
|||||||
|
|
||||||
packets: asyncio.Queue
|
packets: asyncio.Queue
|
||||||
extractor: AudioExtractor
|
extractor: AudioExtractor
|
||||||
packet_pump_task: Optional[asyncio.Task]
|
packet_pump_task: asyncio.Task | None
|
||||||
started: bool
|
started: bool
|
||||||
|
|
||||||
def __init__(self, extractor):
|
def __init__(self, extractor):
|
||||||
@@ -230,8 +229,8 @@ class WebSocketOutput(QueuedOutput):
|
|||||||
class FfplayOutput(QueuedOutput):
|
class FfplayOutput(QueuedOutput):
|
||||||
MAX_QUEUE_SIZE = 32768
|
MAX_QUEUE_SIZE = 32768
|
||||||
|
|
||||||
subprocess: Optional[asyncio.subprocess.Process]
|
subprocess: asyncio.subprocess.Process | None
|
||||||
ffplay_task: Optional[asyncio.Task]
|
ffplay_task: asyncio.Task | None
|
||||||
|
|
||||||
def __init__(self, codec: str) -> None:
|
def __init__(self, codec: str) -> None:
|
||||||
super().__init__(AudioExtractor.create(codec))
|
super().__init__(AudioExtractor.create(codec))
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ import enum
|
|||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
from collections.abc import AsyncGenerator, Awaitable, Callable
|
from collections.abc import AsyncGenerator, Awaitable, Callable
|
||||||
from typing import Union
|
from typing import ClassVar
|
||||||
|
|
||||||
from typing_extensions import ClassVar, Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
from bumble import utils
|
from bumble import utils
|
||||||
from bumble.codecs import AacAudioRtpPacket
|
from bumble.codecs import AacAudioRtpPacket
|
||||||
@@ -266,7 +266,7 @@ class MediaCodecInformation:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def create(
|
def create(
|
||||||
cls, media_codec_type: int, data: bytes
|
cls, media_codec_type: int, data: bytes
|
||||||
) -> Union[MediaCodecInformation, bytes]:
|
) -> MediaCodecInformation | bytes:
|
||||||
if media_codec_type == CodecType.SBC:
|
if media_codec_type == CodecType.SBC:
|
||||||
return SbcMediaCodecInformation.from_bytes(data)
|
return SbcMediaCodecInformation.from_bytes(data)
|
||||||
elif media_codec_type == CodecType.MPEG_2_4_AAC:
|
elif media_codec_type == CodecType.MPEG_2_4_AAC:
|
||||||
@@ -686,7 +686,7 @@ class SbcPacketSource:
|
|||||||
# Prepare for next packets
|
# Prepare for next packets
|
||||||
sequence_number += 1
|
sequence_number += 1
|
||||||
sequence_number &= 0xFFFF
|
sequence_number &= 0xFFFF
|
||||||
sample_count += sum((frame.sample_count for frame in frames))
|
sample_count += sum(frame.sample_count for frame in frames)
|
||||||
frames = [frame]
|
frames = [frame]
|
||||||
frames_size = len(frame.payload)
|
frames_size = len(frame.payload)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
from bumble import core
|
from bumble import core
|
||||||
|
|
||||||
@@ -63,18 +62,18 @@ def tokenize_parameters(buffer: bytes) -> list[bytes]:
|
|||||||
return [bytes(token) for token in tokens if len(token) > 0]
|
return [bytes(token) for token in tokens if len(token) > 0]
|
||||||
|
|
||||||
|
|
||||||
def parse_parameters(buffer: bytes) -> list[Union[bytes, list]]:
|
def parse_parameters(buffer: bytes) -> list[bytes | list]:
|
||||||
"""Parse the parameters using the comma and parenthesis separators.
|
"""Parse the parameters using the comma and parenthesis separators.
|
||||||
Raises AtParsingError in case of invalid input string."""
|
Raises AtParsingError in case of invalid input string."""
|
||||||
|
|
||||||
tokens = tokenize_parameters(buffer)
|
tokens = tokenize_parameters(buffer)
|
||||||
accumulator: list[list] = [[]]
|
accumulator: list[list] = [[]]
|
||||||
current: Union[bytes, list] = bytes()
|
current: bytes | list = b''
|
||||||
|
|
||||||
for token in tokens:
|
for token in tokens:
|
||||||
if token == b',':
|
if token == b',':
|
||||||
accumulator[-1].append(current)
|
accumulator[-1].append(current)
|
||||||
current = bytes()
|
current = b''
|
||||||
elif token == b'(':
|
elif token == b'(':
|
||||||
accumulator.append([])
|
accumulator.append([])
|
||||||
elif token == b')':
|
elif token == b')':
|
||||||
|
|||||||
@@ -29,15 +29,12 @@ import enum
|
|||||||
import functools
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
import struct
|
import struct
|
||||||
|
from collections.abc import Awaitable, Callable
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Awaitable,
|
|
||||||
Callable,
|
|
||||||
ClassVar,
|
ClassVar,
|
||||||
Generic,
|
Generic,
|
||||||
Optional,
|
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Union,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from bumble import hci, utils
|
from bumble import hci, utils
|
||||||
@@ -220,7 +217,7 @@ class ATT_PDU:
|
|||||||
fields: ClassVar[hci.Fields] = ()
|
fields: ClassVar[hci.Fields] = ()
|
||||||
op_code: int = dataclasses.field(init=False)
|
op_code: int = dataclasses.field(init=False)
|
||||||
name: str = dataclasses.field(init=False)
|
name: str = dataclasses.field(init=False)
|
||||||
_payload: Optional[bytes] = dataclasses.field(default=None, init=False)
|
_payload: bytes | None = dataclasses.field(default=None, init=False)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_bytes(cls, pdu: bytes) -> ATT_PDU:
|
def from_bytes(cls, pdu: bytes) -> ATT_PDU:
|
||||||
@@ -760,26 +757,24 @@ class AttributeValue(Generic[_T]):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
read: Union[
|
read: (
|
||||||
Callable[[Connection], _T],
|
Callable[[Connection], _T] | Callable[[Connection], Awaitable[_T]] | None
|
||||||
Callable[[Connection], Awaitable[_T]],
|
) = None,
|
||||||
None,
|
write: (
|
||||||
] = None,
|
Callable[[Connection, _T], None]
|
||||||
write: Union[
|
| Callable[[Connection, _T], Awaitable[None]]
|
||||||
Callable[[Connection, _T], None],
|
| None
|
||||||
Callable[[Connection, _T], Awaitable[None]],
|
) = None,
|
||||||
None,
|
|
||||||
] = None,
|
|
||||||
):
|
):
|
||||||
self._read = read
|
self._read = read
|
||||||
self._write = write
|
self._write = write
|
||||||
|
|
||||||
def read(self, connection: Connection) -> Union[_T, Awaitable[_T]]:
|
def read(self, connection: Connection) -> _T | Awaitable[_T]:
|
||||||
if self._read is None:
|
if self._read is None:
|
||||||
raise InvalidOperationError('AttributeValue has no read function')
|
raise InvalidOperationError('AttributeValue has no read function')
|
||||||
return self._read(connection)
|
return self._read(connection)
|
||||||
|
|
||||||
def write(self, connection: Connection, value: _T) -> Union[Awaitable[None], None]:
|
def write(self, connection: Connection, value: _T) -> Awaitable[None] | None:
|
||||||
if self._write is None:
|
if self._write is None:
|
||||||
raise InvalidOperationError('AttributeValue has no write function')
|
raise InvalidOperationError('AttributeValue has no write function')
|
||||||
return self._write(connection, value)
|
return self._write(connection, value)
|
||||||
@@ -828,13 +823,13 @@ class Attribute(utils.EventEmitter, Generic[_T]):
|
|||||||
EVENT_READ = "read"
|
EVENT_READ = "read"
|
||||||
EVENT_WRITE = "write"
|
EVENT_WRITE = "write"
|
||||||
|
|
||||||
value: Union[AttributeValue[_T], _T, None]
|
value: AttributeValue[_T] | _T | None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
attribute_type: Union[str, bytes, UUID],
|
attribute_type: str | bytes | UUID,
|
||||||
permissions: Union[str, Attribute.Permissions],
|
permissions: str | Attribute.Permissions,
|
||||||
value: Union[AttributeValue[_T], _T, None] = None,
|
value: AttributeValue[_T] | _T | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
utils.EventEmitter.__init__(self)
|
utils.EventEmitter.__init__(self)
|
||||||
self.handle = 0
|
self.handle = 0
|
||||||
@@ -883,7 +878,7 @@ class Attribute(utils.EventEmitter, Generic[_T]):
|
|||||||
error_code=ATT_INSUFFICIENT_AUTHORIZATION_ERROR, att_handle=self.handle
|
error_code=ATT_INSUFFICIENT_AUTHORIZATION_ERROR, att_handle=self.handle
|
||||||
)
|
)
|
||||||
|
|
||||||
value: Union[_T, None]
|
value: _T | None
|
||||||
if isinstance(self.value, AttributeValue):
|
if isinstance(self.value, AttributeValue):
|
||||||
try:
|
try:
|
||||||
read_value = self.value.read(connection)
|
read_value = self.value.read(connection)
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ import logging
|
|||||||
import pathlib
|
import pathlib
|
||||||
import sys
|
import sys
|
||||||
import wave
|
import wave
|
||||||
from typing import TYPE_CHECKING, AsyncGenerator, BinaryIO
|
from collections.abc import AsyncGenerator
|
||||||
|
from typing import TYPE_CHECKING, BinaryIO
|
||||||
|
|
||||||
from bumble.colors import color
|
from bumble.colors import color
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import enum
|
import enum
|
||||||
import struct
|
import struct
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
from bumble import core, utils
|
from bumble import core, utils
|
||||||
|
|
||||||
@@ -166,7 +165,7 @@ class Frame:
|
|||||||
|
|
||||||
def to_bytes(
|
def to_bytes(
|
||||||
self,
|
self,
|
||||||
ctype_or_response: Union[CommandFrame.CommandType, ResponseFrame.ResponseCode],
|
ctype_or_response: CommandFrame.CommandType | ResponseFrame.ResponseCode,
|
||||||
) -> bytes:
|
) -> bytes:
|
||||||
# TODO: support extended subunit types and ids.
|
# TODO: support extended subunit types and ids.
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import logging
|
|||||||
import struct
|
import struct
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from bumble import core, l2cap
|
from bumble import core, l2cap
|
||||||
from bumble.colors import color
|
from bumble.colors import color
|
||||||
@@ -147,7 +146,7 @@ class MessageAssembler:
|
|||||||
class Protocol:
|
class Protocol:
|
||||||
CommandHandler = Callable[[int, bytes], None]
|
CommandHandler = Callable[[int, bytes], None]
|
||||||
command_handlers: dict[int, CommandHandler] # Command handlers, by PID
|
command_handlers: dict[int, CommandHandler] # Command handlers, by PID
|
||||||
ResponseHandler = Callable[[int, Optional[bytes]], None]
|
ResponseHandler = Callable[[int, bytes | None], None]
|
||||||
response_handlers: dict[int, ResponseHandler] # Response handlers, by PID
|
response_handlers: dict[int, ResponseHandler] # Response handlers, by PID
|
||||||
next_transaction_label: int
|
next_transaction_label: int
|
||||||
message_assembler: MessageAssembler
|
message_assembler: MessageAssembler
|
||||||
|
|||||||
110
bumble/avdtp.py
110
bumble/avdtp.py
@@ -22,16 +22,13 @@ import enum
|
|||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
import warnings
|
import warnings
|
||||||
from collections.abc import AsyncGenerator, Awaitable, Iterable
|
from collections.abc import AsyncGenerator, Awaitable, Callable, Iterable
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
Callable,
|
|
||||||
ClassVar,
|
ClassVar,
|
||||||
Optional,
|
|
||||||
SupportsBytes,
|
SupportsBytes,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Union,
|
|
||||||
cast,
|
cast,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -184,7 +181,7 @@ class State(utils.OpenIntEnum):
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
async def find_avdtp_service_with_sdp_client(
|
async def find_avdtp_service_with_sdp_client(
|
||||||
sdp_client: sdp.Client,
|
sdp_client: sdp.Client,
|
||||||
) -> Optional[tuple[int, int]]:
|
) -> tuple[int, int] | None:
|
||||||
'''
|
'''
|
||||||
Find an AVDTP service, using a connected SDP client, and return its version,
|
Find an AVDTP service, using a connected SDP client, and return its version,
|
||||||
or None if none is found
|
or None if none is found
|
||||||
@@ -214,7 +211,7 @@ async def find_avdtp_service_with_sdp_client(
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
async def find_avdtp_service_with_connection(
|
async def find_avdtp_service_with_connection(
|
||||||
connection: device.Connection,
|
connection: device.Connection,
|
||||||
) -> Optional[tuple[int, int]]:
|
) -> tuple[int, int] | None:
|
||||||
'''
|
'''
|
||||||
Find an AVDTP service, for a connection, and return its version,
|
Find an AVDTP service, for a connection, and return its version,
|
||||||
or None if none is found
|
or None if none is found
|
||||||
@@ -239,7 +236,7 @@ class RealtimeClock:
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
class MediaPacketPump:
|
class MediaPacketPump:
|
||||||
pump_task: Optional[asyncio.Task]
|
pump_task: asyncio.Task | None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, packets: AsyncGenerator, clock: RealtimeClock = RealtimeClock()
|
self, packets: AsyncGenerator, clock: RealtimeClock = RealtimeClock()
|
||||||
@@ -296,7 +293,7 @@ class MediaPacketPump:
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
class MessageAssembler:
|
class MessageAssembler:
|
||||||
message: Optional[bytes]
|
message: bytes | None
|
||||||
signal_identifier: SignalIdentifier
|
signal_identifier: SignalIdentifier
|
||||||
|
|
||||||
def __init__(self, callback: Callable[[int, Message], Any]) -> None:
|
def __init__(self, callback: Callable[[int, Message], Any]) -> None:
|
||||||
@@ -470,15 +467,15 @@ class MediaCodecCapabilities(ServiceCapabilities):
|
|||||||
|
|
||||||
media_type: MediaType
|
media_type: MediaType
|
||||||
media_codec_type: a2dp.CodecType
|
media_codec_type: a2dp.CodecType
|
||||||
media_codec_information: Union[bytes, SupportsBytes]
|
media_codec_information: bytes | SupportsBytes
|
||||||
|
|
||||||
# Override init to allow passing service_capabilities_bytes.
|
# Override init to allow passing service_capabilities_bytes.
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
media_type: MediaType,
|
media_type: MediaType,
|
||||||
media_codec_type: a2dp.CodecType,
|
media_codec_type: a2dp.CodecType,
|
||||||
media_codec_information: Union[bytes, SupportsBytes],
|
media_codec_information: bytes | SupportsBytes,
|
||||||
service_capabilities_bytes: Optional[bytes] = None,
|
service_capabilities_bytes: bytes | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.media_type = media_type
|
self.media_type = media_type
|
||||||
self.media_codec_type = media_codec_type
|
self.media_codec_type = media_codec_type
|
||||||
@@ -553,7 +550,7 @@ class Message:
|
|||||||
|
|
||||||
message_type: MessageType
|
message_type: MessageType
|
||||||
signal_identifier: SignalIdentifier
|
signal_identifier: SignalIdentifier
|
||||||
_payload: Optional[bytes] = None
|
_payload: bytes | None = None
|
||||||
fields: ClassVar[hci.Fields] = ()
|
fields: ClassVar[hci.Fields] = ()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -607,7 +604,7 @@ class Message:
|
|||||||
instance.signal_identifier = signal_identifier
|
instance.signal_identifier = signal_identifier
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def to_string(self, details: Union[str, Iterable[str]]) -> str:
|
def to_string(self, details: str | Iterable[str]) -> str:
|
||||||
base = color(
|
base = color(
|
||||||
f'{self.signal_identifier.name}_{self.message_type.name}',
|
f'{self.signal_identifier.name}_{self.message_type.name}',
|
||||||
'yellow',
|
'yellow',
|
||||||
@@ -1266,9 +1263,9 @@ class Protocol(utils.EventEmitter):
|
|||||||
local_endpoints: list[LocalStreamEndPoint]
|
local_endpoints: list[LocalStreamEndPoint]
|
||||||
remote_endpoints: dict[int, DiscoveredStreamEndPoint]
|
remote_endpoints: dict[int, DiscoveredStreamEndPoint]
|
||||||
streams: dict[int, Stream]
|
streams: dict[int, Stream]
|
||||||
transaction_results: list[Optional[asyncio.Future[Message]]]
|
transaction_results: list[asyncio.Future[Message] | None]
|
||||||
channel_connector: Callable[[], Awaitable[l2cap.ClassicChannel]]
|
channel_connector: Callable[[], Awaitable[l2cap.ClassicChannel]]
|
||||||
channel_acceptor: Optional[Stream]
|
channel_acceptor: Stream | None
|
||||||
|
|
||||||
EVENT_OPEN = "open"
|
EVENT_OPEN = "open"
|
||||||
EVENT_CLOSE = "close"
|
EVENT_CLOSE = "close"
|
||||||
@@ -1311,7 +1308,7 @@ class Protocol(utils.EventEmitter):
|
|||||||
l2cap_channel.on(l2cap_channel.EVENT_OPEN, self.on_l2cap_channel_open)
|
l2cap_channel.on(l2cap_channel.EVENT_OPEN, self.on_l2cap_channel_open)
|
||||||
l2cap_channel.on(l2cap_channel.EVENT_CLOSE, self.on_l2cap_channel_close)
|
l2cap_channel.on(l2cap_channel.EVENT_CLOSE, self.on_l2cap_channel_close)
|
||||||
|
|
||||||
def get_local_endpoint_by_seid(self, seid: int) -> Optional[LocalStreamEndPoint]:
|
def get_local_endpoint_by_seid(self, seid: int) -> LocalStreamEndPoint | None:
|
||||||
if 0 < seid <= len(self.local_endpoints):
|
if 0 < seid <= len(self.local_endpoints):
|
||||||
return self.local_endpoints[seid - 1]
|
return self.local_endpoints[seid - 1]
|
||||||
|
|
||||||
@@ -1385,7 +1382,7 @@ class Protocol(utils.EventEmitter):
|
|||||||
|
|
||||||
def find_remote_sink_by_codec(
|
def find_remote_sink_by_codec(
|
||||||
self, media_type: int, codec_type: int, vendor_id: int = 0, codec_id: int = 0
|
self, media_type: int, codec_type: int, vendor_id: int = 0, codec_id: int = 0
|
||||||
) -> Optional[DiscoveredStreamEndPoint]:
|
) -> DiscoveredStreamEndPoint | None:
|
||||||
for endpoint in self.remote_endpoints.values():
|
for endpoint in self.remote_endpoints.values():
|
||||||
if (
|
if (
|
||||||
not endpoint.in_use
|
not endpoint.in_use
|
||||||
@@ -1569,10 +1566,9 @@ class Protocol(utils.EventEmitter):
|
|||||||
|
|
||||||
assert False # Should never reach this
|
assert False # Should never reach this
|
||||||
|
|
||||||
async def get_capabilities(self, seid: int) -> Union[
|
async def get_capabilities(
|
||||||
Get_Capabilities_Response,
|
self, seid: int
|
||||||
Get_All_Capabilities_Response,
|
) -> Get_Capabilities_Response | Get_All_Capabilities_Response:
|
||||||
]:
|
|
||||||
if self.version > (1, 2):
|
if self.version > (1, 2):
|
||||||
return await self.send_command(Get_All_Capabilities_Command(seid))
|
return await self.send_command(Get_All_Capabilities_Command(seid))
|
||||||
|
|
||||||
@@ -1604,7 +1600,7 @@ class Protocol(utils.EventEmitter):
|
|||||||
async def abort(self, seid: int) -> Abort_Response:
|
async def abort(self, seid: int) -> Abort_Response:
|
||||||
return await self.send_command(Abort_Command(seid))
|
return await self.send_command(Abort_Command(seid))
|
||||||
|
|
||||||
def on_discover_command(self, command: Discover_Command) -> Optional[Message]:
|
def on_discover_command(self, command: Discover_Command) -> Message | None:
|
||||||
endpoint_infos = [
|
endpoint_infos = [
|
||||||
EndPointInfo(endpoint.seid, 0, endpoint.media_type, endpoint.tsep)
|
EndPointInfo(endpoint.seid, 0, endpoint.media_type, endpoint.tsep)
|
||||||
for endpoint in self.local_endpoints
|
for endpoint in self.local_endpoints
|
||||||
@@ -1613,7 +1609,7 @@ class Protocol(utils.EventEmitter):
|
|||||||
|
|
||||||
def on_get_capabilities_command(
|
def on_get_capabilities_command(
|
||||||
self, command: Get_Capabilities_Command
|
self, command: Get_Capabilities_Command
|
||||||
) -> Optional[Message]:
|
) -> Message | None:
|
||||||
endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
|
endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
|
||||||
if endpoint is None:
|
if endpoint is None:
|
||||||
return Get_Capabilities_Reject(AVDTP_BAD_ACP_SEID_ERROR)
|
return Get_Capabilities_Reject(AVDTP_BAD_ACP_SEID_ERROR)
|
||||||
@@ -1622,7 +1618,7 @@ class Protocol(utils.EventEmitter):
|
|||||||
|
|
||||||
def on_get_all_capabilities_command(
|
def on_get_all_capabilities_command(
|
||||||
self, command: Get_All_Capabilities_Command
|
self, command: Get_All_Capabilities_Command
|
||||||
) -> Optional[Message]:
|
) -> Message | None:
|
||||||
endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
|
endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
|
||||||
if endpoint is None:
|
if endpoint is None:
|
||||||
return Get_All_Capabilities_Reject(AVDTP_BAD_ACP_SEID_ERROR)
|
return Get_All_Capabilities_Reject(AVDTP_BAD_ACP_SEID_ERROR)
|
||||||
@@ -1631,7 +1627,7 @@ class Protocol(utils.EventEmitter):
|
|||||||
|
|
||||||
def on_set_configuration_command(
|
def on_set_configuration_command(
|
||||||
self, command: Set_Configuration_Command
|
self, command: Set_Configuration_Command
|
||||||
) -> Optional[Message]:
|
) -> Message | None:
|
||||||
endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
|
endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
|
||||||
if endpoint is None:
|
if endpoint is None:
|
||||||
return Set_Configuration_Reject(error_code=AVDTP_BAD_ACP_SEID_ERROR)
|
return Set_Configuration_Reject(error_code=AVDTP_BAD_ACP_SEID_ERROR)
|
||||||
@@ -1649,7 +1645,7 @@ class Protocol(utils.EventEmitter):
|
|||||||
|
|
||||||
def on_get_configuration_command(
|
def on_get_configuration_command(
|
||||||
self, command: Get_Configuration_Command
|
self, command: Get_Configuration_Command
|
||||||
) -> Optional[Message]:
|
) -> Message | None:
|
||||||
endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
|
endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
|
||||||
if endpoint is None:
|
if endpoint is None:
|
||||||
return Get_Configuration_Reject(AVDTP_BAD_ACP_SEID_ERROR)
|
return Get_Configuration_Reject(AVDTP_BAD_ACP_SEID_ERROR)
|
||||||
@@ -1658,7 +1654,7 @@ class Protocol(utils.EventEmitter):
|
|||||||
|
|
||||||
return endpoint.stream.on_get_configuration_command()
|
return endpoint.stream.on_get_configuration_command()
|
||||||
|
|
||||||
def on_reconfigure_command(self, command: Reconfigure_Command) -> Optional[Message]:
|
def on_reconfigure_command(self, command: Reconfigure_Command) -> Message | None:
|
||||||
endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
|
endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
|
||||||
if endpoint is None:
|
if endpoint is None:
|
||||||
return Reconfigure_Reject(error_code=AVDTP_BAD_ACP_SEID_ERROR)
|
return Reconfigure_Reject(error_code=AVDTP_BAD_ACP_SEID_ERROR)
|
||||||
@@ -1668,7 +1664,7 @@ class Protocol(utils.EventEmitter):
|
|||||||
result = endpoint.stream.on_reconfigure_command(command.capabilities)
|
result = endpoint.stream.on_reconfigure_command(command.capabilities)
|
||||||
return result or Reconfigure_Response()
|
return result or Reconfigure_Response()
|
||||||
|
|
||||||
def on_open_command(self, command: Open_Command) -> Optional[Message]:
|
def on_open_command(self, command: Open_Command) -> Message | None:
|
||||||
endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
|
endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
|
||||||
if endpoint is None:
|
if endpoint is None:
|
||||||
return Open_Reject(AVDTP_BAD_ACP_SEID_ERROR)
|
return Open_Reject(AVDTP_BAD_ACP_SEID_ERROR)
|
||||||
@@ -1678,7 +1674,7 @@ class Protocol(utils.EventEmitter):
|
|||||||
result = endpoint.stream.on_open_command()
|
result = endpoint.stream.on_open_command()
|
||||||
return result or Open_Response()
|
return result or Open_Response()
|
||||||
|
|
||||||
def on_start_command(self, command: Start_Command) -> Optional[Message]:
|
def on_start_command(self, command: Start_Command) -> Message | None:
|
||||||
for seid in command.acp_seids:
|
for seid in command.acp_seids:
|
||||||
endpoint = self.get_local_endpoint_by_seid(seid)
|
endpoint = self.get_local_endpoint_by_seid(seid)
|
||||||
if endpoint is None:
|
if endpoint is None:
|
||||||
@@ -1697,7 +1693,7 @@ class Protocol(utils.EventEmitter):
|
|||||||
|
|
||||||
return Start_Response()
|
return Start_Response()
|
||||||
|
|
||||||
def on_suspend_command(self, command: Suspend_Command) -> Optional[Message]:
|
def on_suspend_command(self, command: Suspend_Command) -> Message | None:
|
||||||
for seid in command.acp_seids:
|
for seid in command.acp_seids:
|
||||||
endpoint = self.get_local_endpoint_by_seid(seid)
|
endpoint = self.get_local_endpoint_by_seid(seid)
|
||||||
if endpoint is None:
|
if endpoint is None:
|
||||||
@@ -1716,7 +1712,7 @@ class Protocol(utils.EventEmitter):
|
|||||||
|
|
||||||
return Suspend_Response()
|
return Suspend_Response()
|
||||||
|
|
||||||
def on_close_command(self, command: Close_Command) -> Optional[Message]:
|
def on_close_command(self, command: Close_Command) -> Message | None:
|
||||||
endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
|
endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
|
||||||
if endpoint is None:
|
if endpoint is None:
|
||||||
return Close_Reject(AVDTP_BAD_ACP_SEID_ERROR)
|
return Close_Reject(AVDTP_BAD_ACP_SEID_ERROR)
|
||||||
@@ -1726,7 +1722,7 @@ class Protocol(utils.EventEmitter):
|
|||||||
result = endpoint.stream.on_close_command()
|
result = endpoint.stream.on_close_command()
|
||||||
return result or Close_Response()
|
return result or Close_Response()
|
||||||
|
|
||||||
def on_abort_command(self, command: Abort_Command) -> Optional[Message]:
|
def on_abort_command(self, command: Abort_Command) -> Message | None:
|
||||||
endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
|
endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
|
||||||
if endpoint is None or endpoint.stream is None:
|
if endpoint is None or endpoint.stream is None:
|
||||||
return Abort_Response()
|
return Abort_Response()
|
||||||
@@ -1736,7 +1732,7 @@ class Protocol(utils.EventEmitter):
|
|||||||
|
|
||||||
def on_security_control_command(
|
def on_security_control_command(
|
||||||
self, command: Security_Control_Command
|
self, command: Security_Control_Command
|
||||||
) -> Optional[Message]:
|
) -> Message | None:
|
||||||
endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
|
endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
|
||||||
if endpoint is None:
|
if endpoint is None:
|
||||||
return Security_Control_Reject(AVDTP_BAD_ACP_SEID_ERROR)
|
return Security_Control_Reject(AVDTP_BAD_ACP_SEID_ERROR)
|
||||||
@@ -1744,7 +1740,7 @@ class Protocol(utils.EventEmitter):
|
|||||||
result = endpoint.on_security_control_command(command.data)
|
result = endpoint.on_security_control_command(command.data)
|
||||||
return result or Security_Control_Response()
|
return result or Security_Control_Response()
|
||||||
|
|
||||||
def on_delayreport_command(self, command: DelayReport_Command) -> Optional[Message]:
|
def on_delayreport_command(self, command: DelayReport_Command) -> Message | None:
|
||||||
endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
|
endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
|
||||||
if endpoint is None:
|
if endpoint is None:
|
||||||
return DelayReport_Reject(AVDTP_BAD_ACP_SEID_ERROR)
|
return DelayReport_Reject(AVDTP_BAD_ACP_SEID_ERROR)
|
||||||
@@ -1825,7 +1821,7 @@ class Stream:
|
|||||||
Pair of a local and a remote stream endpoint that can stream from one to the other
|
Pair of a local and a remote stream endpoint that can stream from one to the other
|
||||||
'''
|
'''
|
||||||
|
|
||||||
rtp_channel: Optional[l2cap.ClassicChannel]
|
rtp_channel: l2cap.ClassicChannel | None
|
||||||
|
|
||||||
def change_state(self, state: State) -> None:
|
def change_state(self, state: State) -> None:
|
||||||
logger.debug(f'{self} state change -> {color(state.name, "cyan")}')
|
logger.debug(f'{self} state change -> {color(state.name, "cyan")}')
|
||||||
@@ -1914,7 +1910,7 @@ class Stream:
|
|||||||
|
|
||||||
def on_set_configuration_command(
|
def on_set_configuration_command(
|
||||||
self, configuration: Iterable[ServiceCapabilities]
|
self, configuration: Iterable[ServiceCapabilities]
|
||||||
) -> Optional[Message]:
|
) -> Message | None:
|
||||||
if self.state != State.IDLE:
|
if self.state != State.IDLE:
|
||||||
return Set_Configuration_Reject(error_code=AVDTP_BAD_STATE_ERROR)
|
return Set_Configuration_Reject(error_code=AVDTP_BAD_STATE_ERROR)
|
||||||
|
|
||||||
@@ -1925,7 +1921,7 @@ class Stream:
|
|||||||
self.change_state(State.CONFIGURED)
|
self.change_state(State.CONFIGURED)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def on_get_configuration_command(self) -> Optional[Message]:
|
def on_get_configuration_command(self) -> Message | None:
|
||||||
if self.state not in (
|
if self.state not in (
|
||||||
State.CONFIGURED,
|
State.CONFIGURED,
|
||||||
State.OPEN,
|
State.OPEN,
|
||||||
@@ -1937,7 +1933,7 @@ class Stream:
|
|||||||
|
|
||||||
def on_reconfigure_command(
|
def on_reconfigure_command(
|
||||||
self, configuration: Iterable[ServiceCapabilities]
|
self, configuration: Iterable[ServiceCapabilities]
|
||||||
) -> Optional[Message]:
|
) -> Message | None:
|
||||||
if self.state != State.OPEN:
|
if self.state != State.OPEN:
|
||||||
return Reconfigure_Reject(error_code=AVDTP_BAD_STATE_ERROR)
|
return Reconfigure_Reject(error_code=AVDTP_BAD_STATE_ERROR)
|
||||||
|
|
||||||
@@ -1947,7 +1943,7 @@ class Stream:
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def on_open_command(self) -> Optional[Message]:
|
def on_open_command(self) -> Message | None:
|
||||||
if self.state != State.CONFIGURED:
|
if self.state != State.CONFIGURED:
|
||||||
return Open_Reject(AVDTP_BAD_STATE_ERROR)
|
return Open_Reject(AVDTP_BAD_STATE_ERROR)
|
||||||
|
|
||||||
@@ -1961,7 +1957,7 @@ class Stream:
|
|||||||
self.change_state(State.OPEN)
|
self.change_state(State.OPEN)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def on_start_command(self) -> Optional[Message]:
|
def on_start_command(self) -> Message | None:
|
||||||
if self.state != State.OPEN:
|
if self.state != State.OPEN:
|
||||||
return Open_Reject(AVDTP_BAD_STATE_ERROR)
|
return Open_Reject(AVDTP_BAD_STATE_ERROR)
|
||||||
|
|
||||||
@@ -1977,7 +1973,7 @@ class Stream:
|
|||||||
self.change_state(State.STREAMING)
|
self.change_state(State.STREAMING)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def on_suspend_command(self) -> Optional[Message]:
|
def on_suspend_command(self) -> Message | None:
|
||||||
if self.state != State.STREAMING:
|
if self.state != State.STREAMING:
|
||||||
return Open_Reject(AVDTP_BAD_STATE_ERROR)
|
return Open_Reject(AVDTP_BAD_STATE_ERROR)
|
||||||
|
|
||||||
@@ -1988,7 +1984,7 @@ class Stream:
|
|||||||
self.change_state(State.OPEN)
|
self.change_state(State.OPEN)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def on_close_command(self) -> Optional[Message]:
|
def on_close_command(self) -> Message | None:
|
||||||
if self.state not in (State.OPEN, State.STREAMING):
|
if self.state not in (State.OPEN, State.STREAMING):
|
||||||
return Open_Reject(AVDTP_BAD_STATE_ERROR)
|
return Open_Reject(AVDTP_BAD_STATE_ERROR)
|
||||||
|
|
||||||
@@ -2007,7 +2003,7 @@ class Stream:
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def on_abort_command(self) -> Optional[Message]:
|
def on_abort_command(self) -> Message | None:
|
||||||
if self.rtp_channel is None:
|
if self.rtp_channel is None:
|
||||||
# No need to wait
|
# No need to wait
|
||||||
self.change_state(State.IDLE)
|
self.change_state(State.IDLE)
|
||||||
@@ -2120,7 +2116,7 @@ class DiscoveredStreamEndPoint(StreamEndPoint, StreamEndPointProxy):
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
class LocalStreamEndPoint(StreamEndPoint, utils.EventEmitter):
|
class LocalStreamEndPoint(StreamEndPoint, utils.EventEmitter):
|
||||||
stream: Optional[Stream]
|
stream: Stream | None
|
||||||
|
|
||||||
EVENT_CONFIGURATION = "configuration"
|
EVENT_CONFIGURATION = "configuration"
|
||||||
EVENT_OPEN = "open"
|
EVENT_OPEN = "open"
|
||||||
@@ -2142,7 +2138,7 @@ class LocalStreamEndPoint(StreamEndPoint, utils.EventEmitter):
|
|||||||
media_type: MediaType,
|
media_type: MediaType,
|
||||||
tsep: StreamEndPointType,
|
tsep: StreamEndPointType,
|
||||||
capabilities: Iterable[ServiceCapabilities],
|
capabilities: Iterable[ServiceCapabilities],
|
||||||
configuration: Optional[Iterable[ServiceCapabilities]] = None,
|
configuration: Iterable[ServiceCapabilities] | None = None,
|
||||||
):
|
):
|
||||||
StreamEndPoint.__init__(self, seid, media_type, tsep, 0, capabilities)
|
StreamEndPoint.__init__(self, seid, media_type, tsep, 0, capabilities)
|
||||||
utils.EventEmitter.__init__(self)
|
utils.EventEmitter.__init__(self)
|
||||||
@@ -2161,13 +2157,13 @@ class LocalStreamEndPoint(StreamEndPoint, utils.EventEmitter):
|
|||||||
|
|
||||||
def on_reconfigure_command(
|
def on_reconfigure_command(
|
||||||
self, command: Iterable[ServiceCapabilities]
|
self, command: Iterable[ServiceCapabilities]
|
||||||
) -> Optional[Message]:
|
) -> Message | None:
|
||||||
del command # unused.
|
del command # unused.
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def on_set_configuration_command(
|
def on_set_configuration_command(
|
||||||
self, configuration: Iterable[ServiceCapabilities]
|
self, configuration: Iterable[ServiceCapabilities]
|
||||||
) -> Optional[Message]:
|
) -> Message | None:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
'<<< received configuration: '
|
'<<< received configuration: '
|
||||||
f'{",".join([str(capability) for capability in configuration])}'
|
f'{",".join([str(capability) for capability in configuration])}'
|
||||||
@@ -2176,34 +2172,34 @@ class LocalStreamEndPoint(StreamEndPoint, utils.EventEmitter):
|
|||||||
self.emit(self.EVENT_CONFIGURATION)
|
self.emit(self.EVENT_CONFIGURATION)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def on_get_configuration_command(self) -> Optional[Message]:
|
def on_get_configuration_command(self) -> Message | None:
|
||||||
return Get_Configuration_Response(self.configuration)
|
return Get_Configuration_Response(self.configuration)
|
||||||
|
|
||||||
def on_open_command(self) -> Optional[Message]:
|
def on_open_command(self) -> Message | None:
|
||||||
self.emit(self.EVENT_OPEN)
|
self.emit(self.EVENT_OPEN)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def on_start_command(self) -> Optional[Message]:
|
def on_start_command(self) -> Message | None:
|
||||||
self.emit(self.EVENT_START)
|
self.emit(self.EVENT_START)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def on_suspend_command(self) -> Optional[Message]:
|
def on_suspend_command(self) -> Message | None:
|
||||||
self.emit(self.EVENT_SUSPEND)
|
self.emit(self.EVENT_SUSPEND)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def on_close_command(self) -> Optional[Message]:
|
def on_close_command(self) -> Message | None:
|
||||||
self.emit(self.EVENT_CLOSE)
|
self.emit(self.EVENT_CLOSE)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def on_abort_command(self) -> Optional[Message]:
|
def on_abort_command(self) -> Message | None:
|
||||||
self.emit(self.EVENT_ABORT)
|
self.emit(self.EVENT_ABORT)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def on_delayreport_command(self, delay: int) -> Optional[Message]:
|
def on_delayreport_command(self, delay: int) -> Message | None:
|
||||||
self.emit(self.EVENT_DELAY_REPORT, delay)
|
self.emit(self.EVENT_DELAY_REPORT, delay)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def on_security_control_command(self, data: bytes) -> Optional[Message]:
|
def on_security_control_command(self, data: bytes) -> Message | None:
|
||||||
self.emit(self.EVENT_SECURITY_CONTROL, data)
|
self.emit(self.EVENT_SECURITY_CONTROL, data)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -2255,12 +2251,12 @@ class LocalSource(LocalStreamEndPoint):
|
|||||||
self.emit(self.EVENT_STOP)
|
self.emit(self.EVENT_STOP)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def on_start_command(self) -> Optional[Message]:
|
def on_start_command(self) -> Message | None:
|
||||||
asyncio.create_task(self.start())
|
asyncio.create_task(self.start())
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def on_suspend_command(self) -> Optional[Message]:
|
def on_suspend_command(self) -> Message | None:
|
||||||
asyncio.create_task(self.stop())
|
asyncio.create_task(self.stop())
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import logging
|
|||||||
import struct
|
import struct
|
||||||
from collections.abc import AsyncIterator, Awaitable, Callable, Iterable, Sequence
|
from collections.abc import AsyncIterator, Awaitable, Callable, Iterable, Sequence
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import ClassVar, Optional, SupportsBytes, TypeVar, Union
|
from typing import ClassVar, SupportsBytes, TypeVar
|
||||||
|
|
||||||
from bumble import avc, avctp, core, hci, l2cap, utils
|
from bumble import avc, avctp, core, hci, l2cap, utils
|
||||||
from bumble.colors import color
|
from bumble.colors import color
|
||||||
@@ -196,7 +196,7 @@ def make_controller_service_sdp_records(
|
|||||||
service_record_handle: int,
|
service_record_handle: int,
|
||||||
avctp_version: tuple[int, int] = (1, 4),
|
avctp_version: tuple[int, int] = (1, 4),
|
||||||
avrcp_version: tuple[int, int] = (1, 6),
|
avrcp_version: tuple[int, int] = (1, 6),
|
||||||
supported_features: Union[int, ControllerFeatures] = 1,
|
supported_features: int | ControllerFeatures = 1,
|
||||||
) -> list[ServiceAttribute]:
|
) -> list[ServiceAttribute]:
|
||||||
avctp_version_int = avctp_version[0] << 8 | avctp_version[1]
|
avctp_version_int = avctp_version[0] << 8 | avctp_version[1]
|
||||||
avrcp_version_int = avrcp_version[0] << 8 | avrcp_version[1]
|
avrcp_version_int = avrcp_version[0] << 8 | avrcp_version[1]
|
||||||
@@ -288,7 +288,7 @@ def make_target_service_sdp_records(
|
|||||||
service_record_handle: int,
|
service_record_handle: int,
|
||||||
avctp_version: tuple[int, int] = (1, 4),
|
avctp_version: tuple[int, int] = (1, 4),
|
||||||
avrcp_version: tuple[int, int] = (1, 6),
|
avrcp_version: tuple[int, int] = (1, 6),
|
||||||
supported_features: Union[int, TargetFeatures] = 0x23,
|
supported_features: int | TargetFeatures = 0x23,
|
||||||
) -> list[ServiceAttribute]:
|
) -> list[ServiceAttribute]:
|
||||||
# TODO: support a way to compute the supported features from a feature list
|
# TODO: support a way to compute the supported features from a feature list
|
||||||
avctp_version_int = avctp_version[0] << 8 | avctp_version[1]
|
avctp_version_int = avctp_version[0] << 8 | avctp_version[1]
|
||||||
@@ -478,7 +478,7 @@ class BrowseableItem:
|
|||||||
MEDIA_ELEMENT = 0x03
|
MEDIA_ELEMENT = 0x03
|
||||||
|
|
||||||
item_type: ClassVar[Type]
|
item_type: ClassVar[Type]
|
||||||
_payload: Optional[bytes] = None
|
_payload: bytes | None = None
|
||||||
|
|
||||||
subclasses: ClassVar[dict[Type, type[BrowseableItem]]] = {}
|
subclasses: ClassVar[dict[Type, type[BrowseableItem]]] = {}
|
||||||
fields: ClassVar[hci.Fields] = ()
|
fields: ClassVar[hci.Fields] = ()
|
||||||
@@ -672,7 +672,7 @@ class PduAssembler:
|
|||||||
6.3.1 AVRCP specific AV//C commands
|
6.3.1 AVRCP specific AV//C commands
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pdu_id: Optional[PduId]
|
pdu_id: PduId | None
|
||||||
payload: bytes
|
payload: bytes
|
||||||
|
|
||||||
def __init__(self, callback: Callable[[PduId, bytes], None]) -> None:
|
def __init__(self, callback: Callable[[PduId, bytes], None]) -> None:
|
||||||
@@ -725,7 +725,7 @@ class PduAssembler:
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
class Command:
|
class Command:
|
||||||
pdu_id: ClassVar[PduId]
|
pdu_id: ClassVar[PduId]
|
||||||
_payload: Optional[bytes] = None
|
_payload: bytes | None = None
|
||||||
|
|
||||||
_Command = TypeVar('_Command', bound='Command')
|
_Command = TypeVar('_Command', bound='Command')
|
||||||
subclasses: ClassVar[dict[int, type[Command]]] = {}
|
subclasses: ClassVar[dict[int, type[Command]]] = {}
|
||||||
@@ -1017,7 +1017,7 @@ class AddToNowPlayingCommand(Command):
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
class Response:
|
class Response:
|
||||||
pdu_id: PduId
|
pdu_id: PduId
|
||||||
_payload: Optional[bytes] = None
|
_payload: bytes | None = None
|
||||||
|
|
||||||
fields: ClassVar[hci.Fields] = ()
|
fields: ClassVar[hci.Fields] = ()
|
||||||
subclasses: ClassVar[dict[PduId, type[Response]]] = {}
|
subclasses: ClassVar[dict[PduId, type[Response]]] = {}
|
||||||
@@ -1079,7 +1079,7 @@ class NotImplementedResponse(Response):
|
|||||||
class GetCapabilitiesResponse(Response):
|
class GetCapabilitiesResponse(Response):
|
||||||
pdu_id = PduId.GET_CAPABILITIES
|
pdu_id = PduId.GET_CAPABILITIES
|
||||||
capability_id: GetCapabilitiesCommand.CapabilityId
|
capability_id: GetCapabilitiesCommand.CapabilityId
|
||||||
capabilities: Sequence[Union[SupportsBytes, bytes]]
|
capabilities: Sequence[SupportsBytes | bytes]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_parameters(cls, parameters: bytes) -> Response:
|
def from_parameters(cls, parameters: bytes) -> Response:
|
||||||
@@ -1092,7 +1092,7 @@ class GetCapabilitiesResponse(Response):
|
|||||||
capability_id = GetCapabilitiesCommand.CapabilityId(parameters[0])
|
capability_id = GetCapabilitiesCommand.CapabilityId(parameters[0])
|
||||||
capability_count = parameters[1]
|
capability_count = parameters[1]
|
||||||
|
|
||||||
capabilities: list[Union[SupportsBytes, bytes]]
|
capabilities: list[SupportsBytes | bytes]
|
||||||
if capability_id == GetCapabilitiesCommand.CapabilityId.EVENTS_SUPPORTED:
|
if capability_id == GetCapabilitiesCommand.CapabilityId.EVENTS_SUPPORTED:
|
||||||
capabilities = [EventId(parameters[2 + x]) for x in range(capability_count)]
|
capabilities = [EventId(parameters[2 + x]) for x in range(capability_count)]
|
||||||
else:
|
else:
|
||||||
@@ -1363,7 +1363,7 @@ class AddToNowPlayingResponse(Response):
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
class Event:
|
class Event:
|
||||||
event_id: EventId
|
event_id: EventId
|
||||||
_pdu: Optional[bytes] = None
|
_pdu: bytes | None = None
|
||||||
|
|
||||||
_Event = TypeVar('_Event', bound='Event')
|
_Event = TypeVar('_Event', bound='Event')
|
||||||
subclasses: ClassVar[dict[int, type[Event]]] = {}
|
subclasses: ClassVar[dict[int, type[Event]]] = {}
|
||||||
@@ -1436,13 +1436,13 @@ class PlayerApplicationSettingChangedEvent(Event):
|
|||||||
attribute_id: ApplicationSetting.AttributeId = field(
|
attribute_id: ApplicationSetting.AttributeId = field(
|
||||||
metadata=ApplicationSetting.AttributeId.type_metadata(1)
|
metadata=ApplicationSetting.AttributeId.type_metadata(1)
|
||||||
)
|
)
|
||||||
value_id: Union[
|
value_id: (
|
||||||
ApplicationSetting.EqualizerOnOffStatus,
|
ApplicationSetting.EqualizerOnOffStatus
|
||||||
ApplicationSetting.RepeatModeStatus,
|
| ApplicationSetting.RepeatModeStatus
|
||||||
ApplicationSetting.ShuffleOnOffStatus,
|
| ApplicationSetting.ShuffleOnOffStatus
|
||||||
ApplicationSetting.ScanOnOffStatus,
|
| ApplicationSetting.ScanOnOffStatus
|
||||||
ApplicationSetting.GenericValue,
|
| ApplicationSetting.GenericValue
|
||||||
] = field(metadata=hci.metadata(1))
|
) = field(metadata=hci.metadata(1))
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
super().__post_init__()
|
super().__post_init__()
|
||||||
@@ -1628,17 +1628,17 @@ class Protocol(utils.EventEmitter):
|
|||||||
delegate: Delegate
|
delegate: Delegate
|
||||||
send_transaction_label: int
|
send_transaction_label: int
|
||||||
command_pdu_assembler: PduAssembler
|
command_pdu_assembler: PduAssembler
|
||||||
receive_command_state: Optional[ReceiveCommandState]
|
receive_command_state: ReceiveCommandState | None
|
||||||
response_pdu_assembler: PduAssembler
|
response_pdu_assembler: PduAssembler
|
||||||
receive_response_state: Optional[ReceiveResponseState]
|
receive_response_state: ReceiveResponseState | None
|
||||||
avctp_protocol: Optional[avctp.Protocol]
|
avctp_protocol: avctp.Protocol | None
|
||||||
free_commands: asyncio.Queue
|
free_commands: asyncio.Queue
|
||||||
pending_commands: dict[int, PendingCommand] # Pending commands, by label
|
pending_commands: dict[int, PendingCommand] # Pending commands, by label
|
||||||
notification_listeners: dict[EventId, NotificationListener]
|
notification_listeners: dict[EventId, NotificationListener]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _check_vendor_dependent_frame(
|
def _check_vendor_dependent_frame(
|
||||||
frame: Union[avc.VendorDependentCommandFrame, avc.VendorDependentResponseFrame],
|
frame: avc.VendorDependentCommandFrame | avc.VendorDependentResponseFrame,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
if frame.company_id != AVRCP_BLUETOOTH_SIG_COMPANY_ID:
|
if frame.company_id != AVRCP_BLUETOOTH_SIG_COMPANY_ID:
|
||||||
logger.debug("unsupported company id, ignoring")
|
logger.debug("unsupported company id, ignoring")
|
||||||
@@ -1650,7 +1650,7 @@ class Protocol(utils.EventEmitter):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def __init__(self, delegate: Optional[Delegate] = None) -> None:
|
def __init__(self, delegate: Delegate | None = None) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.delegate = delegate if delegate else Delegate()
|
self.delegate = delegate if delegate else Delegate()
|
||||||
self.command_pdu_assembler = PduAssembler(self._on_command_pdu)
|
self.command_pdu_assembler = PduAssembler(self._on_command_pdu)
|
||||||
@@ -2067,9 +2067,7 @@ class Protocol(utils.EventEmitter):
|
|||||||
# TODO handle other types
|
# TODO handle other types
|
||||||
self.send_not_implemented_response(transaction_label, command)
|
self.send_not_implemented_response(transaction_label, command)
|
||||||
|
|
||||||
def _on_avctp_response(
|
def _on_avctp_response(self, transaction_label: int, payload: bytes | None) -> None:
|
||||||
self, transaction_label: int, payload: Optional[bytes]
|
|
||||||
) -> None:
|
|
||||||
response = avc.ResponseFrame.from_bytes(payload) if payload else None
|
response = avc.ResponseFrame.from_bytes(payload) if payload else None
|
||||||
if not isinstance(response, avc.ResponseFrame):
|
if not isinstance(response, avc.ResponseFrame):
|
||||||
raise core.InvalidPacketError(
|
raise core.InvalidPacketError(
|
||||||
@@ -2176,7 +2174,7 @@ class Protocol(utils.EventEmitter):
|
|||||||
# NOTE: with a small number of supported responses, a manual switch like this
|
# NOTE: with a small number of supported responses, a manual switch like this
|
||||||
# is Ok, but if/when more responses are supported, a lookup mechanism would be
|
# is Ok, but if/when more responses are supported, a lookup mechanism would be
|
||||||
# more appropriate.
|
# more appropriate.
|
||||||
response: Optional[Response] = None
|
response: Response | None = None
|
||||||
if response_code == avc.ResponseFrame.ResponseCode.REJECTED:
|
if response_code == avc.ResponseFrame.ResponseCode.REJECTED:
|
||||||
response = RejectedResponse(pdu_id=pdu_id, status_code=StatusCode(pdu[0]))
|
response = RejectedResponse(pdu_id=pdu_id, status_code=StatusCode(pdu[0]))
|
||||||
elif response_code == avc.ResponseFrame.ResponseCode.NOT_IMPLEMENTED:
|
elif response_code == avc.ResponseFrame.ResponseCode.NOT_IMPLEMENTED:
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import Optional, Union
|
|
||||||
|
|
||||||
|
|
||||||
class ColorError(ValueError):
|
class ColorError(ValueError):
|
||||||
@@ -38,7 +37,7 @@ STYLES = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
ColorSpec = Union[str, int]
|
ColorSpec = str | int
|
||||||
|
|
||||||
|
|
||||||
def _join(*values: ColorSpec) -> str:
|
def _join(*values: ColorSpec) -> str:
|
||||||
@@ -56,14 +55,14 @@ def _color_code(spec: ColorSpec, base: int) -> str:
|
|||||||
elif isinstance(spec, int) and 0 <= spec <= 255:
|
elif isinstance(spec, int) and 0 <= spec <= 255:
|
||||||
return _join(base + 8, 5, spec)
|
return _join(base + 8, 5, spec)
|
||||||
else:
|
else:
|
||||||
raise ColorError('Invalid color spec "%s"' % spec)
|
raise ColorError(f'Invalid color spec "{spec}"')
|
||||||
|
|
||||||
|
|
||||||
def color(
|
def color(
|
||||||
s: str,
|
s: str,
|
||||||
fg: Optional[ColorSpec] = None,
|
fg: ColorSpec | None = None,
|
||||||
bg: Optional[ColorSpec] = None,
|
bg: ColorSpec | None = None,
|
||||||
style: Optional[str] = None,
|
style: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
codes: list[ColorSpec] = []
|
codes: list[ColorSpec] = []
|
||||||
|
|
||||||
@@ -76,10 +75,10 @@ def color(
|
|||||||
if style_part in STYLES:
|
if style_part in STYLES:
|
||||||
codes.append(STYLES.index(style_part))
|
codes.append(STYLES.index(style_part))
|
||||||
else:
|
else:
|
||||||
raise ColorError('Invalid style "%s"' % style_part)
|
raise ColorError(f'Invalid style "{style_part}"')
|
||||||
|
|
||||||
if codes:
|
if codes:
|
||||||
return '\x1b[{0}m{1}\x1b[0m'.format(_join(*codes), s)
|
return f'\x1b[{_join(*codes)}m{s}\x1b[0m'
|
||||||
else:
|
else:
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import itertools
|
|||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
import struct
|
import struct
|
||||||
from typing import TYPE_CHECKING, Any, Optional, Union, cast
|
from typing import TYPE_CHECKING, Any, cast
|
||||||
|
|
||||||
from bumble import hci, link, ll, lmp
|
from bumble import hci, link, ll, lmp
|
||||||
from bumble import link as bumble_link
|
from bumble import link as bumble_link
|
||||||
@@ -52,7 +52,7 @@ class CisLink:
|
|||||||
handle: int
|
handle: int
|
||||||
cis_id: int
|
cis_id: int
|
||||||
cig_id: int
|
cig_id: int
|
||||||
acl_connection: Optional[Connection] = None
|
acl_connection: Connection | None = None
|
||||||
data_paths: set[int] = dataclasses.field(default_factory=set)
|
data_paths: set[int] = dataclasses.field(default_factory=set)
|
||||||
|
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ class LegacyAdvertiser:
|
|||||||
scan_response_data: bytes = b''
|
scan_response_data: bytes = b''
|
||||||
|
|
||||||
enabled: bool = False
|
enabled: bool = False
|
||||||
timer_handle: Optional[asyncio.Handle] = None
|
timer_handle: asyncio.Handle | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def address(self) -> hci.Address:
|
def address(self) -> hci.Address:
|
||||||
@@ -124,12 +124,12 @@ class LegacyAdvertiser:
|
|||||||
class AdvertisingSet:
|
class AdvertisingSet:
|
||||||
controller: Controller
|
controller: Controller
|
||||||
handle: int
|
handle: int
|
||||||
parameters: Optional[hci.HCI_LE_Set_Extended_Advertising_Parameters_Command] = None
|
parameters: hci.HCI_LE_Set_Extended_Advertising_Parameters_Command | None = None
|
||||||
data: bytearray = dataclasses.field(default_factory=bytearray)
|
data: bytearray = dataclasses.field(default_factory=bytearray)
|
||||||
scan_response_data: bytearray = dataclasses.field(default_factory=bytearray)
|
scan_response_data: bytearray = dataclasses.field(default_factory=bytearray)
|
||||||
enabled: bool = False
|
enabled: bool = False
|
||||||
timer_handle: Optional[asyncio.Handle] = None
|
timer_handle: asyncio.Handle | None = None
|
||||||
random_address: Optional[hci.Address] = None
|
random_address: hci.Address | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def address(self) -> hci.Address | None:
|
def address(self) -> hci.Address | None:
|
||||||
@@ -223,7 +223,7 @@ class Connection:
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
class Controller:
|
class Controller:
|
||||||
hci_sink: Optional[TransportSink] = None
|
hci_sink: TransportSink | None = None
|
||||||
|
|
||||||
le_connections: dict[hci.Address, Connection] # LE Connections
|
le_connections: dict[hci.Address, Connection] # LE Connections
|
||||||
classic_connections: dict[hci.Address, Connection] # Connections in BR/EDR
|
classic_connections: dict[hci.Address, Connection] # Connections in BR/EDR
|
||||||
@@ -292,8 +292,8 @@ class Controller:
|
|||||||
sync_flow_control: bool = False
|
sync_flow_control: bool = False
|
||||||
local_name: str = 'Bumble'
|
local_name: str = 'Bumble'
|
||||||
advertising_interval: int = 2000
|
advertising_interval: int = 2000
|
||||||
advertising_data: Optional[bytes] = None
|
advertising_data: bytes | None = None
|
||||||
advertising_timer_handle: Optional[asyncio.Handle] = None
|
advertising_timer_handle: asyncio.Handle | None = None
|
||||||
classic_scan_enable: int = 0
|
classic_scan_enable: int = 0
|
||||||
classic_allow_role_switch: bool = True
|
classic_allow_role_switch: bool = True
|
||||||
pending_le_connection: (
|
pending_le_connection: (
|
||||||
@@ -308,9 +308,9 @@ class Controller:
|
|||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
host_source=None,
|
host_source=None,
|
||||||
host_sink: Optional[TransportSink] = None,
|
host_sink: TransportSink | None = None,
|
||||||
link: Optional[link.LocalLink] = None,
|
link: link.LocalLink | None = None,
|
||||||
public_address: Optional[Union[bytes, str, hci.Address]] = None,
|
public_address: bytes | str | hci.Address | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.link = link or bumble_link.LocalLink()
|
self.link = link or bumble_link.LocalLink()
|
||||||
@@ -351,18 +351,18 @@ class Controller:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def host(self) -> Optional[TransportSink]:
|
def host(self) -> TransportSink | None:
|
||||||
return self.hci_sink
|
return self.hci_sink
|
||||||
|
|
||||||
@host.setter
|
@host.setter
|
||||||
def host(self, host: Optional[TransportSink]) -> None:
|
def host(self, host: TransportSink | None) -> None:
|
||||||
'''
|
'''
|
||||||
Sets the host (sink) for this controller, and set this controller as the
|
Sets the host (sink) for this controller, and set this controller as the
|
||||||
controller (sink) for the host
|
controller (sink) for the host
|
||||||
'''
|
'''
|
||||||
self.set_packet_sink(host)
|
self.set_packet_sink(host)
|
||||||
|
|
||||||
def set_packet_sink(self, sink: Optional[TransportSink]) -> None:
|
def set_packet_sink(self, sink: TransportSink | None) -> None:
|
||||||
'''
|
'''
|
||||||
Method from the Packet Source interface
|
Method from the Packet Source interface
|
||||||
'''
|
'''
|
||||||
@@ -373,7 +373,7 @@ class Controller:
|
|||||||
return self._public_address
|
return self._public_address
|
||||||
|
|
||||||
@public_address.setter
|
@public_address.setter
|
||||||
def public_address(self, address: Union[hci.Address, str]) -> None:
|
def public_address(self, address: hci.Address | str) -> None:
|
||||||
if isinstance(address, str):
|
if isinstance(address, str):
|
||||||
address = hci.Address(address)
|
address = hci.Address(address)
|
||||||
self._public_address = address
|
self._public_address = address
|
||||||
@@ -383,7 +383,7 @@ class Controller:
|
|||||||
return self._random_address
|
return self._random_address
|
||||||
|
|
||||||
@random_address.setter
|
@random_address.setter
|
||||||
def random_address(self, address: Union[hci.Address, str]) -> None:
|
def random_address(self, address: hci.Address | str) -> None:
|
||||||
if isinstance(address, str):
|
if isinstance(address, str):
|
||||||
address = hci.Address(address)
|
address = hci.Address(address)
|
||||||
self._random_address = address
|
self._random_address = address
|
||||||
@@ -415,7 +415,7 @@ class Controller:
|
|||||||
def on_hci_command_packet(self, command: hci.HCI_Command) -> None:
|
def on_hci_command_packet(self, command: hci.HCI_Command) -> None:
|
||||||
handler_name = f'on_{command.name.lower()}'
|
handler_name = f'on_{command.name.lower()}'
|
||||||
handler = getattr(self, handler_name, self.on_hci_command)
|
handler = getattr(self, handler_name, self.on_hci_command)
|
||||||
result: Optional[bytes] = handler(command)
|
result: bytes | None = handler(command)
|
||||||
if isinstance(result, bytes):
|
if isinstance(result, bytes):
|
||||||
self.send_hci_packet(
|
self.send_hci_packet(
|
||||||
hci.HCI_Command_Complete_Event(
|
hci.HCI_Command_Complete_Event(
|
||||||
@@ -472,7 +472,7 @@ class Controller:
|
|||||||
if handle not in current_handles
|
if handle not in current_handles
|
||||||
)
|
)
|
||||||
|
|
||||||
def find_connection_by_handle(self, handle: int) -> Optional[Connection]:
|
def find_connection_by_handle(self, handle: int) -> Connection | None:
|
||||||
for connection in itertools.chain(
|
for connection in itertools.chain(
|
||||||
self.le_connections.values(),
|
self.le_connections.values(),
|
||||||
self.classic_connections.values(),
|
self.classic_connections.values(),
|
||||||
@@ -481,13 +481,13 @@ class Controller:
|
|||||||
return connection
|
return connection
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def find_classic_sco_link_by_handle(self, handle: int) -> Optional[ScoLink]:
|
def find_classic_sco_link_by_handle(self, handle: int) -> ScoLink | None:
|
||||||
for connection in self.sco_links.values():
|
for connection in self.sco_links.values():
|
||||||
if connection.handle == handle:
|
if connection.handle == handle:
|
||||||
return connection
|
return connection
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def find_iso_link_by_handle(self, handle: int) -> Optional[CisLink]:
|
def find_iso_link_by_handle(self, handle: int) -> CisLink | None:
|
||||||
return self.central_cis_links.get(handle) or self.peripheral_cis_links.get(
|
return self.central_cis_links.get(handle) or self.peripheral_cis_links.get(
|
||||||
handle
|
handle
|
||||||
)
|
)
|
||||||
@@ -1130,13 +1130,13 @@ class Controller:
|
|||||||
############################################################
|
############################################################
|
||||||
# HCI handlers
|
# HCI handlers
|
||||||
############################################################
|
############################################################
|
||||||
def on_hci_command(self, command: hci.HCI_Command) -> Optional[bytes]:
|
def on_hci_command(self, command: hci.HCI_Command) -> bytes | None:
|
||||||
logger.warning(color(f'--- Unsupported command {command}', 'red'))
|
logger.warning(color(f'--- Unsupported command {command}', 'red'))
|
||||||
return bytes([hci.HCI_UNKNOWN_HCI_COMMAND_ERROR])
|
return bytes([hci.HCI_UNKNOWN_HCI_COMMAND_ERROR])
|
||||||
|
|
||||||
def on_hci_create_connection_command(
|
def on_hci_create_connection_command(
|
||||||
self, command: hci.HCI_Create_Connection_Command
|
self, command: hci.HCI_Create_Connection_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.1.5 Create Connection command
|
See Bluetooth spec Vol 4, Part E - 7.1.5 Create Connection command
|
||||||
'''
|
'''
|
||||||
@@ -1186,7 +1186,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_disconnect_command(
|
def on_hci_disconnect_command(
|
||||||
self, command: hci.HCI_Disconnect_Command
|
self, command: hci.HCI_Disconnect_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.1.6 Disconnect Command
|
See Bluetooth spec Vol 4, Part E - 7.1.6 Disconnect Command
|
||||||
'''
|
'''
|
||||||
@@ -1256,7 +1256,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_accept_connection_request_command(
|
def on_hci_accept_connection_request_command(
|
||||||
self, command: hci.HCI_Accept_Connection_Request_Command
|
self, command: hci.HCI_Accept_Connection_Request_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.1.8 Accept Connection Request command
|
See Bluetooth spec Vol 4, Part E - 7.1.8 Accept Connection Request command
|
||||||
'''
|
'''
|
||||||
@@ -1314,7 +1314,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_remote_name_request_command(
|
def on_hci_remote_name_request_command(
|
||||||
self, command: hci.HCI_Remote_Name_Request_Command
|
self, command: hci.HCI_Remote_Name_Request_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.1.19 Remote Name Request command
|
See Bluetooth spec Vol 4, Part E - 7.1.19 Remote Name Request command
|
||||||
'''
|
'''
|
||||||
@@ -1332,7 +1332,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_enhanced_setup_synchronous_connection_command(
|
def on_hci_enhanced_setup_synchronous_connection_command(
|
||||||
self, command: hci.HCI_Enhanced_Setup_Synchronous_Connection_Command
|
self, command: hci.HCI_Enhanced_Setup_Synchronous_Connection_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.1.45 Enhanced Setup Synchronous Connection command
|
See Bluetooth spec Vol 4, Part E - 7.1.45 Enhanced Setup Synchronous Connection command
|
||||||
'''
|
'''
|
||||||
@@ -1389,7 +1389,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_enhanced_accept_synchronous_connection_request_command(
|
def on_hci_enhanced_accept_synchronous_connection_request_command(
|
||||||
self, command: hci.HCI_Enhanced_Accept_Synchronous_Connection_Request_Command
|
self, command: hci.HCI_Enhanced_Accept_Synchronous_Connection_Request_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.1.46 Enhanced Accept Synchronous Connection Request command
|
See Bluetooth spec Vol 4, Part E - 7.1.46 Enhanced Accept Synchronous Connection Request command
|
||||||
'''
|
'''
|
||||||
@@ -1427,7 +1427,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_sniff_mode_command(
|
def on_hci_sniff_mode_command(
|
||||||
self, command: hci.HCI_Sniff_Mode_Command
|
self, command: hci.HCI_Sniff_Mode_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.2.2 Sniff Mode command
|
See Bluetooth spec Vol 4, Part E - 7.2.2 Sniff Mode command
|
||||||
'''
|
'''
|
||||||
@@ -1460,7 +1460,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_exit_sniff_mode_command(
|
def on_hci_exit_sniff_mode_command(
|
||||||
self, command: hci.HCI_Exit_Sniff_Mode_Command
|
self, command: hci.HCI_Exit_Sniff_Mode_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.2.3 Exit Sniff Mode command
|
See Bluetooth spec Vol 4, Part E - 7.2.3 Exit Sniff Mode command
|
||||||
'''
|
'''
|
||||||
@@ -1494,7 +1494,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_switch_role_command(
|
def on_hci_switch_role_command(
|
||||||
self, command: hci.HCI_Switch_Role_Command
|
self, command: hci.HCI_Switch_Role_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.2.8 Switch hci.Role command
|
See Bluetooth spec Vol 4, Part E - 7.2.8 Switch hci.Role command
|
||||||
'''
|
'''
|
||||||
@@ -1551,7 +1551,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_set_event_mask_command(
|
def on_hci_set_event_mask_command(
|
||||||
self, command: hci.HCI_Set_Event_Mask_Command
|
self, command: hci.HCI_Set_Event_Mask_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.3.1 Set Event Mask Command
|
See Bluetooth spec Vol 4, Part E - 7.3.1 Set Event Mask Command
|
||||||
'''
|
'''
|
||||||
@@ -1560,7 +1560,7 @@ class Controller:
|
|||||||
)
|
)
|
||||||
return bytes([hci.HCI_SUCCESS])
|
return bytes([hci.HCI_SUCCESS])
|
||||||
|
|
||||||
def on_hci_reset_command(self, _command: hci.HCI_Reset_Command) -> Optional[bytes]:
|
def on_hci_reset_command(self, _command: hci.HCI_Reset_Command) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.3.2 Reset Command
|
See Bluetooth spec Vol 4, Part E - 7.3.2 Reset Command
|
||||||
'''
|
'''
|
||||||
@@ -1569,7 +1569,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_write_local_name_command(
|
def on_hci_write_local_name_command(
|
||||||
self, command: hci.HCI_Write_Local_Name_Command
|
self, command: hci.HCI_Write_Local_Name_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.3.11 Write Local Name Command
|
See Bluetooth spec Vol 4, Part E - 7.3.11 Write Local Name Command
|
||||||
'''
|
'''
|
||||||
@@ -1586,7 +1586,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_read_local_name_command(
|
def on_hci_read_local_name_command(
|
||||||
self, _command: hci.HCI_Read_Local_Name_Command
|
self, _command: hci.HCI_Read_Local_Name_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.3.12 Read Local Name Command
|
See Bluetooth spec Vol 4, Part E - 7.3.12 Read Local Name Command
|
||||||
'''
|
'''
|
||||||
@@ -1598,7 +1598,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_read_class_of_device_command(
|
def on_hci_read_class_of_device_command(
|
||||||
self, _command: hci.HCI_Read_Class_Of_Device_Command
|
self, _command: hci.HCI_Read_Class_Of_Device_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.3.25 Read Class of Device Command
|
See Bluetooth spec Vol 4, Part E - 7.3.25 Read Class of Device Command
|
||||||
'''
|
'''
|
||||||
@@ -1606,7 +1606,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_write_class_of_device_command(
|
def on_hci_write_class_of_device_command(
|
||||||
self, _command: hci.HCI_Write_Class_Of_Device_Command
|
self, _command: hci.HCI_Write_Class_Of_Device_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.3.26 Write Class of Device Command
|
See Bluetooth spec Vol 4, Part E - 7.3.26 Write Class of Device Command
|
||||||
'''
|
'''
|
||||||
@@ -1614,7 +1614,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_read_synchronous_flow_control_enable_command(
|
def on_hci_read_synchronous_flow_control_enable_command(
|
||||||
self, _command: hci.HCI_Read_Synchronous_Flow_Control_Enable_Command
|
self, _command: hci.HCI_Read_Synchronous_Flow_Control_Enable_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.3.36 Read Synchronous Flow Control Enable
|
See Bluetooth spec Vol 4, Part E - 7.3.36 Read Synchronous Flow Control Enable
|
||||||
Command
|
Command
|
||||||
@@ -1627,7 +1627,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_write_synchronous_flow_control_enable_command(
|
def on_hci_write_synchronous_flow_control_enable_command(
|
||||||
self, command: hci.HCI_Write_Synchronous_Flow_Control_Enable_Command
|
self, command: hci.HCI_Write_Synchronous_Flow_Control_Enable_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.3.37 Write Synchronous Flow Control Enable
|
See Bluetooth spec Vol 4, Part E - 7.3.37 Write Synchronous Flow Control Enable
|
||||||
Command
|
Command
|
||||||
@@ -1643,7 +1643,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_set_controller_to_host_flow_control_command(
|
def on_hci_set_controller_to_host_flow_control_command(
|
||||||
self, _command: hci.HCI_Set_Controller_To_Host_Flow_Control_Command
|
self, _command: hci.HCI_Set_Controller_To_Host_Flow_Control_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.3.38 Set Controller To Host Flow Control
|
See Bluetooth spec Vol 4, Part E - 7.3.38 Set Controller To Host Flow Control
|
||||||
Command
|
Command
|
||||||
@@ -1654,7 +1654,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_host_buffer_size_command(
|
def on_hci_host_buffer_size_command(
|
||||||
self, _command: hci.HCI_Host_Buffer_Size_Command
|
self, _command: hci.HCI_Host_Buffer_Size_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.3.39 Host Buffer Size Command
|
See Bluetooth spec Vol 4, Part E - 7.3.39 Host Buffer Size Command
|
||||||
'''
|
'''
|
||||||
@@ -1664,7 +1664,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_write_extended_inquiry_response_command(
|
def on_hci_write_extended_inquiry_response_command(
|
||||||
self, _command: hci.HCI_Write_Extended_Inquiry_Response_Command
|
self, _command: hci.HCI_Write_Extended_Inquiry_Response_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.3.56 Write Extended Inquiry Response
|
See Bluetooth spec Vol 4, Part E - 7.3.56 Write Extended Inquiry Response
|
||||||
Command
|
Command
|
||||||
@@ -1673,7 +1673,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_write_simple_pairing_mode_command(
|
def on_hci_write_simple_pairing_mode_command(
|
||||||
self, _command: hci.HCI_Write_Simple_Pairing_Mode_Command
|
self, _command: hci.HCI_Write_Simple_Pairing_Mode_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.3.59 Write Simple Pairing Mode Command
|
See Bluetooth spec Vol 4, Part E - 7.3.59 Write Simple Pairing Mode Command
|
||||||
'''
|
'''
|
||||||
@@ -1681,7 +1681,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_set_event_mask_page_2_command(
|
def on_hci_set_event_mask_page_2_command(
|
||||||
self, command: hci.HCI_Set_Event_Mask_Page_2_Command
|
self, command: hci.HCI_Set_Event_Mask_Page_2_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.3.69 Set Event Mask Page 2 Command
|
See Bluetooth spec Vol 4, Part E - 7.3.69 Set Event Mask Page 2 Command
|
||||||
'''
|
'''
|
||||||
@@ -1692,7 +1692,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_read_le_host_support_command(
|
def on_hci_read_le_host_support_command(
|
||||||
self, _command: hci.HCI_Read_LE_Host_Support_Command
|
self, _command: hci.HCI_Read_LE_Host_Support_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.3.78 Write LE Host Support Command
|
See Bluetooth spec Vol 4, Part E - 7.3.78 Write LE Host Support Command
|
||||||
'''
|
'''
|
||||||
@@ -1700,7 +1700,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_write_le_host_support_command(
|
def on_hci_write_le_host_support_command(
|
||||||
self, _command: hci.HCI_Write_LE_Host_Support_Command
|
self, _command: hci.HCI_Write_LE_Host_Support_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.3.79 Write LE Host Support Command
|
See Bluetooth spec Vol 4, Part E - 7.3.79 Write LE Host Support Command
|
||||||
'''
|
'''
|
||||||
@@ -1709,7 +1709,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_write_authenticated_payload_timeout_command(
|
def on_hci_write_authenticated_payload_timeout_command(
|
||||||
self, command: hci.HCI_Write_Authenticated_Payload_Timeout_Command
|
self, command: hci.HCI_Write_Authenticated_Payload_Timeout_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.3.94 Write Authenticated Payload Timeout
|
See Bluetooth spec Vol 4, Part E - 7.3.94 Write Authenticated Payload Timeout
|
||||||
Command
|
Command
|
||||||
@@ -1719,7 +1719,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_read_local_version_information_command(
|
def on_hci_read_local_version_information_command(
|
||||||
self, _command: hci.HCI_Read_Local_Version_Information_Command
|
self, _command: hci.HCI_Read_Local_Version_Information_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.4.1 Read Local Version Information Command
|
See Bluetooth spec Vol 4, Part E - 7.4.1 Read Local Version Information Command
|
||||||
'''
|
'''
|
||||||
@@ -1735,7 +1735,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_read_local_supported_commands_command(
|
def on_hci_read_local_supported_commands_command(
|
||||||
self, _command: hci.HCI_Read_Local_Supported_Commands_Command
|
self, _command: hci.HCI_Read_Local_Supported_Commands_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.4.2 Read Local Supported Commands Command
|
See Bluetooth spec Vol 4, Part E - 7.4.2 Read Local Supported Commands Command
|
||||||
'''
|
'''
|
||||||
@@ -1743,7 +1743,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_read_local_supported_features_command(
|
def on_hci_read_local_supported_features_command(
|
||||||
self, _command: hci.HCI_Read_Local_Supported_Features_Command
|
self, _command: hci.HCI_Read_Local_Supported_Features_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.4.3 Read Local Supported Features Command
|
See Bluetooth spec Vol 4, Part E - 7.4.3 Read Local Supported Features Command
|
||||||
'''
|
'''
|
||||||
@@ -1751,7 +1751,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_read_local_extended_features_command(
|
def on_hci_read_local_extended_features_command(
|
||||||
self, command: hci.HCI_Read_Local_Extended_Features_Command
|
self, command: hci.HCI_Read_Local_Extended_Features_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.4.4 Read Local Extended Features Command
|
See Bluetooth spec Vol 4, Part E - 7.4.4 Read Local Extended Features Command
|
||||||
'''
|
'''
|
||||||
@@ -1774,7 +1774,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_read_buffer_size_command(
|
def on_hci_read_buffer_size_command(
|
||||||
self, _command: hci.HCI_Read_Buffer_Size_Command
|
self, _command: hci.HCI_Read_Buffer_Size_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.4.5 Read Buffer Size Command
|
See Bluetooth spec Vol 4, Part E - 7.4.5 Read Buffer Size Command
|
||||||
'''
|
'''
|
||||||
@@ -1789,7 +1789,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_read_bd_addr_command(
|
def on_hci_read_bd_addr_command(
|
||||||
self, _command: hci.HCI_Read_BD_ADDR_Command
|
self, _command: hci.HCI_Read_BD_ADDR_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.4.6 Read BD_ADDR Command
|
See Bluetooth spec Vol 4, Part E - 7.4.6 Read BD_ADDR Command
|
||||||
'''
|
'''
|
||||||
@@ -1802,7 +1802,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_set_default_subrate_command(
|
def on_hci_le_set_default_subrate_command(
|
||||||
self, command: hci.HCI_LE_Set_Default_Subrate_Command
|
self, command: hci.HCI_LE_Set_Default_Subrate_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 6, Part E - 7.8.123 LE Set Event Mask Command
|
See Bluetooth spec Vol 6, Part E - 7.8.123 LE Set Event Mask Command
|
||||||
'''
|
'''
|
||||||
@@ -1818,7 +1818,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_subrate_request_command(
|
def on_hci_le_subrate_request_command(
|
||||||
self, command: hci.HCI_LE_Subrate_Request_Command
|
self, command: hci.HCI_LE_Subrate_Request_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 6, Part E - 7.8.124 LE Subrate Request command
|
See Bluetooth spec Vol 6, Part E - 7.8.124 LE Subrate Request command
|
||||||
'''
|
'''
|
||||||
@@ -1852,7 +1852,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_set_event_mask_command(
|
def on_hci_le_set_event_mask_command(
|
||||||
self, command: hci.HCI_LE_Set_Event_Mask_Command
|
self, command: hci.HCI_LE_Set_Event_Mask_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.1 LE Set Event Mask Command
|
See Bluetooth spec Vol 4, Part E - 7.8.1 LE Set Event Mask Command
|
||||||
'''
|
'''
|
||||||
@@ -1863,7 +1863,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_read_buffer_size_command(
|
def on_hci_le_read_buffer_size_command(
|
||||||
self, _command: hci.HCI_LE_Read_Buffer_Size_Command
|
self, _command: hci.HCI_LE_Read_Buffer_Size_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.2 LE Read Buffer Size Command
|
See Bluetooth spec Vol 4, Part E - 7.8.2 LE Read Buffer Size Command
|
||||||
'''
|
'''
|
||||||
@@ -1876,7 +1876,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_read_buffer_size_v2_command(
|
def on_hci_le_read_buffer_size_v2_command(
|
||||||
self, _command: hci.HCI_LE_Read_Buffer_Size_V2_Command
|
self, _command: hci.HCI_LE_Read_Buffer_Size_V2_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.2 LE Read Buffer Size Command
|
See Bluetooth spec Vol 4, Part E - 7.8.2 LE Read Buffer Size Command
|
||||||
'''
|
'''
|
||||||
@@ -1891,7 +1891,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_read_local_supported_features_command(
|
def on_hci_le_read_local_supported_features_command(
|
||||||
self, _command: hci.HCI_LE_Read_Local_Supported_Features_Command
|
self, _command: hci.HCI_LE_Read_Local_Supported_Features_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.3 LE Read Local Supported Features
|
See Bluetooth spec Vol 4, Part E - 7.8.3 LE Read Local Supported Features
|
||||||
Command
|
Command
|
||||||
@@ -1900,7 +1900,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_set_random_address_command(
|
def on_hci_le_set_random_address_command(
|
||||||
self, command: hci.HCI_LE_Set_Random_Address_Command
|
self, command: hci.HCI_LE_Set_Random_Address_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.4 LE Set Random hci.Address Command
|
See Bluetooth spec Vol 4, Part E - 7.8.4 LE Set Random hci.Address Command
|
||||||
'''
|
'''
|
||||||
@@ -1909,7 +1909,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_set_advertising_parameters_command(
|
def on_hci_le_set_advertising_parameters_command(
|
||||||
self, command: hci.HCI_LE_Set_Advertising_Parameters_Command
|
self, command: hci.HCI_LE_Set_Advertising_Parameters_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.5 LE Set Advertising Parameters Command
|
See Bluetooth spec Vol 4, Part E - 7.8.5 LE Set Advertising Parameters Command
|
||||||
'''
|
'''
|
||||||
@@ -1933,7 +1933,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_read_advertising_physical_channel_tx_power_command(
|
def on_hci_le_read_advertising_physical_channel_tx_power_command(
|
||||||
self, _command: hci.HCI_LE_Read_Advertising_Physical_Channel_Tx_Power_Command
|
self, _command: hci.HCI_LE_Read_Advertising_Physical_Channel_Tx_Power_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.6 LE Read Advertising Physical Channel
|
See Bluetooth spec Vol 4, Part E - 7.8.6 LE Read Advertising Physical Channel
|
||||||
Tx Power Command
|
Tx Power Command
|
||||||
@@ -1942,7 +1942,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_set_advertising_data_command(
|
def on_hci_le_set_advertising_data_command(
|
||||||
self, command: hci.HCI_LE_Set_Advertising_Data_Command
|
self, command: hci.HCI_LE_Set_Advertising_Data_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.7 LE Set Advertising Data Command
|
See Bluetooth spec Vol 4, Part E - 7.8.7 LE Set Advertising Data Command
|
||||||
'''
|
'''
|
||||||
@@ -1952,7 +1952,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_set_scan_response_data_command(
|
def on_hci_le_set_scan_response_data_command(
|
||||||
self, command: hci.HCI_LE_Set_Scan_Response_Data_Command
|
self, command: hci.HCI_LE_Set_Scan_Response_Data_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.8 LE Set Scan Response Data Command
|
See Bluetooth spec Vol 4, Part E - 7.8.8 LE Set Scan Response Data Command
|
||||||
'''
|
'''
|
||||||
@@ -1961,7 +1961,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_set_advertising_enable_command(
|
def on_hci_le_set_advertising_enable_command(
|
||||||
self, command: hci.HCI_LE_Set_Advertising_Enable_Command
|
self, command: hci.HCI_LE_Set_Advertising_Enable_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.9 LE Set Advertising Enable Command
|
See Bluetooth spec Vol 4, Part E - 7.8.9 LE Set Advertising Enable Command
|
||||||
'''
|
'''
|
||||||
@@ -1974,7 +1974,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_set_scan_parameters_command(
|
def on_hci_le_set_scan_parameters_command(
|
||||||
self, command: hci.HCI_LE_Set_Scan_Parameters_Command
|
self, command: hci.HCI_LE_Set_Scan_Parameters_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.10 LE Set Scan Parameters Command
|
See Bluetooth spec Vol 4, Part E - 7.8.10 LE Set Scan Parameters Command
|
||||||
'''
|
'''
|
||||||
@@ -1990,7 +1990,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_set_scan_enable_command(
|
def on_hci_le_set_scan_enable_command(
|
||||||
self, command: hci.HCI_LE_Set_Scan_Enable_Command
|
self, command: hci.HCI_LE_Set_Scan_Enable_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.11 LE Set Scan Enable Command
|
See Bluetooth spec Vol 4, Part E - 7.8.11 LE Set Scan Enable Command
|
||||||
'''
|
'''
|
||||||
@@ -2000,7 +2000,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_create_connection_command(
|
def on_hci_le_create_connection_command(
|
||||||
self, command: hci.HCI_LE_Create_Connection_Command
|
self, command: hci.HCI_LE_Create_Connection_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.12 LE Create Connection Command
|
See Bluetooth spec Vol 4, Part E - 7.8.12 LE Create Connection Command
|
||||||
'''
|
'''
|
||||||
@@ -2035,7 +2035,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_create_connection_cancel_command(
|
def on_hci_le_create_connection_cancel_command(
|
||||||
self, _command: hci.HCI_LE_Create_Connection_Cancel_Command
|
self, _command: hci.HCI_LE_Create_Connection_Cancel_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.13 LE Create Connection Cancel Command
|
See Bluetooth spec Vol 4, Part E - 7.8.13 LE Create Connection Cancel Command
|
||||||
'''
|
'''
|
||||||
@@ -2043,7 +2043,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_extended_create_connection_command(
|
def on_hci_le_extended_create_connection_command(
|
||||||
self, command: hci.HCI_LE_Extended_Create_Connection_Command
|
self, command: hci.HCI_LE_Extended_Create_Connection_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.66 LE Extended Create Connection Command
|
See Bluetooth spec Vol 4, Part E - 7.8.66 LE Extended Create Connection Command
|
||||||
'''
|
'''
|
||||||
@@ -2074,7 +2074,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_read_filter_accept_list_size_command(
|
def on_hci_le_read_filter_accept_list_size_command(
|
||||||
self, _command: hci.HCI_LE_Read_Filter_Accept_List_Size_Command
|
self, _command: hci.HCI_LE_Read_Filter_Accept_List_Size_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.14 LE Read Filter Accept List Size
|
See Bluetooth spec Vol 4, Part E - 7.8.14 LE Read Filter Accept List Size
|
||||||
Command
|
Command
|
||||||
@@ -2083,7 +2083,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_clear_filter_accept_list_command(
|
def on_hci_le_clear_filter_accept_list_command(
|
||||||
self, _command: hci.HCI_LE_Clear_Filter_Accept_List_Command
|
self, _command: hci.HCI_LE_Clear_Filter_Accept_List_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.15 LE Clear Filter Accept List Command
|
See Bluetooth spec Vol 4, Part E - 7.8.15 LE Clear Filter Accept List Command
|
||||||
'''
|
'''
|
||||||
@@ -2091,7 +2091,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_add_device_to_filter_accept_list_command(
|
def on_hci_le_add_device_to_filter_accept_list_command(
|
||||||
self, _command: hci.HCI_LE_Add_Device_To_Filter_Accept_List_Command
|
self, _command: hci.HCI_LE_Add_Device_To_Filter_Accept_List_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.16 LE Add Device To Filter Accept List
|
See Bluetooth spec Vol 4, Part E - 7.8.16 LE Add Device To Filter Accept List
|
||||||
Command
|
Command
|
||||||
@@ -2100,7 +2100,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_remove_device_from_filter_accept_list_command(
|
def on_hci_le_remove_device_from_filter_accept_list_command(
|
||||||
self, _command: hci.HCI_LE_Remove_Device_From_Filter_Accept_List_Command
|
self, _command: hci.HCI_LE_Remove_Device_From_Filter_Accept_List_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.17 LE Remove Device From Filter Accept
|
See Bluetooth spec Vol 4, Part E - 7.8.17 LE Remove Device From Filter Accept
|
||||||
List Command
|
List Command
|
||||||
@@ -2109,7 +2109,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_write_scan_enable_command(
|
def on_hci_write_scan_enable_command(
|
||||||
self, command: hci.HCI_Write_Scan_Enable_Command
|
self, command: hci.HCI_Write_Scan_Enable_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.3.18 Write Scan Enable Command
|
See Bluetooth spec Vol 4, Part E - 7.3.18 Write Scan Enable Command
|
||||||
'''
|
'''
|
||||||
@@ -2118,7 +2118,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_read_remote_features_command(
|
def on_hci_le_read_remote_features_command(
|
||||||
self, command: hci.HCI_LE_Read_Remote_Features_Command
|
self, command: hci.HCI_LE_Read_Remote_Features_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.21 LE Read Remote Features Command
|
See Bluetooth spec Vol 4, Part E - 7.8.21 LE Read Remote Features Command
|
||||||
'''
|
'''
|
||||||
@@ -2154,9 +2154,7 @@ class Controller:
|
|||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def on_hci_le_rand_command(
|
def on_hci_le_rand_command(self, _command: hci.HCI_LE_Rand_Command) -> bytes | None:
|
||||||
self, _command: hci.HCI_LE_Rand_Command
|
|
||||||
) -> Optional[bytes]:
|
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.23 LE Rand Command
|
See Bluetooth spec Vol 4, Part E - 7.8.23 LE Rand Command
|
||||||
'''
|
'''
|
||||||
@@ -2164,7 +2162,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_enable_encryption_command(
|
def on_hci_le_enable_encryption_command(
|
||||||
self, command: hci.HCI_LE_Enable_Encryption_Command
|
self, command: hci.HCI_LE_Enable_Encryption_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.24 LE Enable Encryption Command
|
See Bluetooth spec Vol 4, Part E - 7.8.24 LE Enable Encryption Command
|
||||||
'''
|
'''
|
||||||
@@ -2204,7 +2202,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_read_supported_states_command(
|
def on_hci_le_read_supported_states_command(
|
||||||
self, _command: hci.HCI_LE_Read_Supported_States_Command
|
self, _command: hci.HCI_LE_Read_Supported_States_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.27 LE Read Supported States Command
|
See Bluetooth spec Vol 4, Part E - 7.8.27 LE Read Supported States Command
|
||||||
'''
|
'''
|
||||||
@@ -2212,7 +2210,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_read_suggested_default_data_length_command(
|
def on_hci_le_read_suggested_default_data_length_command(
|
||||||
self, _command: hci.HCI_LE_Read_Suggested_Default_Data_Length_Command
|
self, _command: hci.HCI_LE_Read_Suggested_Default_Data_Length_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.34 LE Read Suggested Default Data Length
|
See Bluetooth spec Vol 4, Part E - 7.8.34 LE Read Suggested Default Data Length
|
||||||
Command
|
Command
|
||||||
@@ -2226,7 +2224,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_write_suggested_default_data_length_command(
|
def on_hci_le_write_suggested_default_data_length_command(
|
||||||
self, command: hci.HCI_LE_Write_Suggested_Default_Data_Length_Command
|
self, command: hci.HCI_LE_Write_Suggested_Default_Data_Length_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.35 LE Write Suggested Default Data Length
|
See Bluetooth spec Vol 4, Part E - 7.8.35 LE Write Suggested Default Data Length
|
||||||
Command
|
Command
|
||||||
@@ -2238,7 +2236,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_read_local_p_256_public_key_command(
|
def on_hci_le_read_local_p_256_public_key_command(
|
||||||
self, _command: hci.HCI_LE_Read_Local_P_256_Public_Key_Command
|
self, _command: hci.HCI_LE_Read_Local_P_256_Public_Key_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.36 LE Read P-256 Public Key Command
|
See Bluetooth spec Vol 4, Part E - 7.8.36 LE Read P-256 Public Key Command
|
||||||
'''
|
'''
|
||||||
@@ -2247,7 +2245,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_add_device_to_resolving_list_command(
|
def on_hci_le_add_device_to_resolving_list_command(
|
||||||
self, _command: hci.HCI_LE_Add_Device_To_Resolving_List_Command
|
self, _command: hci.HCI_LE_Add_Device_To_Resolving_List_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.38 LE Add Device To Resolving List
|
See Bluetooth spec Vol 4, Part E - 7.8.38 LE Add Device To Resolving List
|
||||||
Command
|
Command
|
||||||
@@ -2256,7 +2254,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_clear_resolving_list_command(
|
def on_hci_le_clear_resolving_list_command(
|
||||||
self, _command: hci.HCI_LE_Clear_Resolving_List_Command
|
self, _command: hci.HCI_LE_Clear_Resolving_List_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.40 LE Clear Resolving List Command
|
See Bluetooth spec Vol 4, Part E - 7.8.40 LE Clear Resolving List Command
|
||||||
'''
|
'''
|
||||||
@@ -2264,7 +2262,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_read_resolving_list_size_command(
|
def on_hci_le_read_resolving_list_size_command(
|
||||||
self, _command: hci.HCI_LE_Read_Resolving_List_Size_Command
|
self, _command: hci.HCI_LE_Read_Resolving_List_Size_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.41 LE Read Resolving List Size Command
|
See Bluetooth spec Vol 4, Part E - 7.8.41 LE Read Resolving List Size Command
|
||||||
'''
|
'''
|
||||||
@@ -2272,7 +2270,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_set_address_resolution_enable_command(
|
def on_hci_le_set_address_resolution_enable_command(
|
||||||
self, command: hci.HCI_LE_Set_Address_Resolution_Enable_Command
|
self, command: hci.HCI_LE_Set_Address_Resolution_Enable_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.44 LE Set hci.Address Resolution Enable
|
See Bluetooth spec Vol 4, Part E - 7.8.44 LE Set hci.Address Resolution Enable
|
||||||
Command
|
Command
|
||||||
@@ -2288,7 +2286,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_set_resolvable_private_address_timeout_command(
|
def on_hci_le_set_resolvable_private_address_timeout_command(
|
||||||
self, command: hci.HCI_LE_Set_Resolvable_Private_Address_Timeout_Command
|
self, command: hci.HCI_LE_Set_Resolvable_Private_Address_Timeout_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.45 LE Set Resolvable Private hci.Address
|
See Bluetooth spec Vol 4, Part E - 7.8.45 LE Set Resolvable Private hci.Address
|
||||||
Timeout Command
|
Timeout Command
|
||||||
@@ -2298,7 +2296,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_read_maximum_data_length_command(
|
def on_hci_le_read_maximum_data_length_command(
|
||||||
self, _command: hci.HCI_LE_Read_Maximum_Data_Length_Command
|
self, _command: hci.HCI_LE_Read_Maximum_Data_Length_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.46 LE Read Maximum Data Length Command
|
See Bluetooth spec Vol 4, Part E - 7.8.46 LE Read Maximum Data Length Command
|
||||||
'''
|
'''
|
||||||
@@ -2313,7 +2311,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_read_phy_command(
|
def on_hci_le_read_phy_command(
|
||||||
self, command: hci.HCI_LE_Read_PHY_Command
|
self, command: hci.HCI_LE_Read_PHY_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.47 LE Read PHY Command
|
See Bluetooth spec Vol 4, Part E - 7.8.47 LE Read PHY Command
|
||||||
'''
|
'''
|
||||||
@@ -2327,7 +2325,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_set_default_phy_command(
|
def on_hci_le_set_default_phy_command(
|
||||||
self, command: hci.HCI_LE_Set_Default_PHY_Command
|
self, command: hci.HCI_LE_Set_Default_PHY_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.48 LE Set Default PHY Command
|
See Bluetooth spec Vol 4, Part E - 7.8.48 LE Set Default PHY Command
|
||||||
'''
|
'''
|
||||||
@@ -2338,7 +2336,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_set_advertising_set_random_address_command(
|
def on_hci_le_set_advertising_set_random_address_command(
|
||||||
self, command: hci.HCI_LE_Set_Advertising_Set_Random_Address_Command
|
self, command: hci.HCI_LE_Set_Advertising_Set_Random_Address_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.52 LE Set Advertising Set Random hci.Address
|
See Bluetooth spec Vol 4, Part E - 7.8.52 LE Set Advertising Set Random hci.Address
|
||||||
Command
|
Command
|
||||||
@@ -2353,7 +2351,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_set_extended_advertising_parameters_command(
|
def on_hci_le_set_extended_advertising_parameters_command(
|
||||||
self, command: hci.HCI_LE_Set_Extended_Advertising_Parameters_Command
|
self, command: hci.HCI_LE_Set_Extended_Advertising_Parameters_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.53 LE Set Extended Advertising Parameters
|
See Bluetooth spec Vol 4, Part E - 7.8.53 LE Set Extended Advertising Parameters
|
||||||
Command
|
Command
|
||||||
@@ -2369,7 +2367,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_set_extended_advertising_data_command(
|
def on_hci_le_set_extended_advertising_data_command(
|
||||||
self, command: hci.HCI_LE_Set_Extended_Advertising_Data_Command
|
self, command: hci.HCI_LE_Set_Extended_Advertising_Data_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.54 LE Set Extended Advertising Data
|
See Bluetooth spec Vol 4, Part E - 7.8.54 LE Set Extended Advertising Data
|
||||||
Command
|
Command
|
||||||
@@ -2393,7 +2391,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_set_extended_scan_response_data_command(
|
def on_hci_le_set_extended_scan_response_data_command(
|
||||||
self, command: hci.HCI_LE_Set_Extended_Scan_Response_Data_Command
|
self, command: hci.HCI_LE_Set_Extended_Scan_Response_Data_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.55 LE Set Extended Scan Response Data
|
See Bluetooth spec Vol 4, Part E - 7.8.55 LE Set Extended Scan Response Data
|
||||||
Command
|
Command
|
||||||
@@ -2417,7 +2415,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_set_extended_advertising_enable_command(
|
def on_hci_le_set_extended_advertising_enable_command(
|
||||||
self, command: hci.HCI_LE_Set_Extended_Advertising_Enable_Command
|
self, command: hci.HCI_LE_Set_Extended_Advertising_Enable_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.56 LE Set Extended Advertising Enable
|
See Bluetooth spec Vol 4, Part E - 7.8.56 LE Set Extended Advertising Enable
|
||||||
Command
|
Command
|
||||||
@@ -2438,7 +2436,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_remove_advertising_set_command(
|
def on_hci_le_remove_advertising_set_command(
|
||||||
self, command: hci.HCI_LE_Remove_Advertising_Set_Command
|
self, command: hci.HCI_LE_Remove_Advertising_Set_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.59 LE Remove Advertising Set Command
|
See Bluetooth spec Vol 4, Part E - 7.8.59 LE Remove Advertising Set Command
|
||||||
'''
|
'''
|
||||||
@@ -2449,7 +2447,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_clear_advertising_sets_command(
|
def on_hci_le_clear_advertising_sets_command(
|
||||||
self, _command: hci.HCI_LE_Clear_Advertising_Sets_Command
|
self, _command: hci.HCI_LE_Clear_Advertising_Sets_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.60 LE Clear Advertising Sets Command
|
See Bluetooth spec Vol 4, Part E - 7.8.60 LE Clear Advertising Sets Command
|
||||||
'''
|
'''
|
||||||
@@ -2460,7 +2458,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_read_maximum_advertising_data_length_command(
|
def on_hci_le_read_maximum_advertising_data_length_command(
|
||||||
self, _command: hci.HCI_LE_Read_Maximum_Advertising_Data_Length_Command
|
self, _command: hci.HCI_LE_Read_Maximum_Advertising_Data_Length_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.57 LE Read Maximum Advertising Data
|
See Bluetooth spec Vol 4, Part E - 7.8.57 LE Read Maximum Advertising Data
|
||||||
Length Command
|
Length Command
|
||||||
@@ -2469,7 +2467,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_read_number_of_supported_advertising_sets_command(
|
def on_hci_le_read_number_of_supported_advertising_sets_command(
|
||||||
self, _command: hci.HCI_LE_Read_Number_Of_Supported_Advertising_Sets_Command
|
self, _command: hci.HCI_LE_Read_Number_Of_Supported_Advertising_Sets_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.58 LE Read Number of Supported
|
See Bluetooth spec Vol 4, Part E - 7.8.58 LE Read Number of Supported
|
||||||
Advertising Set Command
|
Advertising Set Command
|
||||||
@@ -2478,7 +2476,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_set_periodic_advertising_parameters_command(
|
def on_hci_le_set_periodic_advertising_parameters_command(
|
||||||
self, _command: hci.HCI_LE_Set_Periodic_Advertising_Parameters_Command
|
self, _command: hci.HCI_LE_Set_Periodic_Advertising_Parameters_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.61 LE Set Periodic Advertising Parameters
|
See Bluetooth spec Vol 4, Part E - 7.8.61 LE Set Periodic Advertising Parameters
|
||||||
Command
|
Command
|
||||||
@@ -2487,7 +2485,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_set_periodic_advertising_data_command(
|
def on_hci_le_set_periodic_advertising_data_command(
|
||||||
self, _command: hci.HCI_LE_Set_Periodic_Advertising_Data_Command
|
self, _command: hci.HCI_LE_Set_Periodic_Advertising_Data_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.62 LE Set Periodic Advertising Data
|
See Bluetooth spec Vol 4, Part E - 7.8.62 LE Set Periodic Advertising Data
|
||||||
Command
|
Command
|
||||||
@@ -2496,7 +2494,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_set_periodic_advertising_enable_command(
|
def on_hci_le_set_periodic_advertising_enable_command(
|
||||||
self, _command: hci.HCI_LE_Set_Periodic_Advertising_Enable_Command
|
self, _command: hci.HCI_LE_Set_Periodic_Advertising_Enable_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.63 LE Set Periodic Advertising Enable
|
See Bluetooth spec Vol 4, Part E - 7.8.63 LE Set Periodic Advertising Enable
|
||||||
Command
|
Command
|
||||||
@@ -2505,7 +2503,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_read_transmit_power_command(
|
def on_hci_le_read_transmit_power_command(
|
||||||
self, _command: hci.HCI_LE_Read_Transmit_Power_Command
|
self, _command: hci.HCI_LE_Read_Transmit_Power_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.74 LE Read Transmit Power Command
|
See Bluetooth spec Vol 4, Part E - 7.8.74 LE Read Transmit Power Command
|
||||||
'''
|
'''
|
||||||
@@ -2513,7 +2511,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_set_cig_parameters_command(
|
def on_hci_le_set_cig_parameters_command(
|
||||||
self, command: hci.HCI_LE_Set_CIG_Parameters_Command
|
self, command: hci.HCI_LE_Set_CIG_Parameters_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.97 LE Set CIG Parameter Command
|
See Bluetooth spec Vol 4, Part E - 7.8.97 LE Set CIG Parameter Command
|
||||||
'''
|
'''
|
||||||
@@ -2539,7 +2537,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_create_cis_command(
|
def on_hci_le_create_cis_command(
|
||||||
self, command: hci.HCI_LE_Create_CIS_Command
|
self, command: hci.HCI_LE_Create_CIS_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.99 LE Create CIS Command
|
See Bluetooth spec Vol 4, Part E - 7.8.99 LE Create CIS Command
|
||||||
'''
|
'''
|
||||||
@@ -2574,7 +2572,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_remove_cig_command(
|
def on_hci_le_remove_cig_command(
|
||||||
self, command: hci.HCI_LE_Remove_CIG_Command
|
self, command: hci.HCI_LE_Remove_CIG_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.100 LE Remove CIG Command
|
See Bluetooth spec Vol 4, Part E - 7.8.100 LE Remove CIG Command
|
||||||
'''
|
'''
|
||||||
@@ -2591,7 +2589,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_accept_cis_request_command(
|
def on_hci_le_accept_cis_request_command(
|
||||||
self, command: hci.HCI_LE_Accept_CIS_Request_Command
|
self, command: hci.HCI_LE_Accept_CIS_Request_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.101 LE Accept CIS Request Command
|
See Bluetooth spec Vol 4, Part E - 7.8.101 LE Accept CIS Request Command
|
||||||
'''
|
'''
|
||||||
@@ -2620,7 +2618,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_setup_iso_data_path_command(
|
def on_hci_le_setup_iso_data_path_command(
|
||||||
self, command: hci.HCI_LE_Setup_ISO_Data_Path_Command
|
self, command: hci.HCI_LE_Setup_ISO_Data_Path_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.109 LE Setup ISO Data Path Command
|
See Bluetooth spec Vol 4, Part E - 7.8.109 LE Setup ISO Data Path Command
|
||||||
'''
|
'''
|
||||||
@@ -2641,7 +2639,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_remove_iso_data_path_command(
|
def on_hci_le_remove_iso_data_path_command(
|
||||||
self, command: hci.HCI_LE_Remove_ISO_Data_Path_Command
|
self, command: hci.HCI_LE_Remove_ISO_Data_Path_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.110 LE Remove ISO Data Path Command
|
See Bluetooth spec Vol 4, Part E - 7.8.110 LE Remove ISO Data Path Command
|
||||||
'''
|
'''
|
||||||
@@ -2667,7 +2665,7 @@ class Controller:
|
|||||||
|
|
||||||
def on_hci_le_set_host_feature_command(
|
def on_hci_le_set_host_feature_command(
|
||||||
self, _command: hci.HCI_LE_Set_Host_Feature_Command
|
self, _command: hci.HCI_LE_Set_Host_Feature_Command
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec Vol 4, Part E - 7.8.115 LE Set Host Feature command
|
See Bluetooth spec Vol 4, Part E - 7.8.115 LE Set Host Feature command
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -20,14 +20,11 @@ from __future__ import annotations
|
|||||||
import dataclasses
|
import dataclasses
|
||||||
import enum
|
import enum
|
||||||
import struct
|
import struct
|
||||||
|
from collections.abc import Iterable
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
ClassVar,
|
ClassVar,
|
||||||
Iterable,
|
|
||||||
Literal,
|
Literal,
|
||||||
Optional,
|
|
||||||
Type,
|
|
||||||
Union,
|
|
||||||
cast,
|
cast,
|
||||||
overload,
|
overload,
|
||||||
)
|
)
|
||||||
@@ -102,7 +99,7 @@ class BaseError(BaseBumbleError):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
error_code: Optional[int],
|
error_code: int | None,
|
||||||
error_namespace: str = '',
|
error_namespace: str = '',
|
||||||
error_name: str = '',
|
error_name: str = '',
|
||||||
details: str = '',
|
details: str = '',
|
||||||
@@ -215,11 +212,9 @@ class UUID:
|
|||||||
UUIDS: list[UUID] = [] # Registry of all instances created
|
UUIDS: list[UUID] = [] # Registry of all instances created
|
||||||
|
|
||||||
uuid_bytes: bytes
|
uuid_bytes: bytes
|
||||||
name: Optional[str]
|
name: str | None
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, uuid_str_or_int: str | int, name: str | None = None) -> None:
|
||||||
self, uuid_str_or_int: Union[str, int], name: Optional[str] = None
|
|
||||||
) -> None:
|
|
||||||
if isinstance(uuid_str_or_int, int):
|
if isinstance(uuid_str_or_int, int):
|
||||||
self.uuid_bytes = struct.pack('<H', uuid_str_or_int)
|
self.uuid_bytes = struct.pack('<H', uuid_str_or_int)
|
||||||
else:
|
else:
|
||||||
@@ -252,7 +247,7 @@ class UUID:
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_bytes(cls, uuid_bytes: bytes, name: Optional[str] = None) -> UUID:
|
def from_bytes(cls, uuid_bytes: bytes, name: str | None = None) -> UUID:
|
||||||
if len(uuid_bytes) in (2, 4, 16):
|
if len(uuid_bytes) in (2, 4, 16):
|
||||||
self = cls.__new__(cls)
|
self = cls.__new__(cls)
|
||||||
self.uuid_bytes = uuid_bytes
|
self.uuid_bytes = uuid_bytes
|
||||||
@@ -263,11 +258,11 @@ class UUID:
|
|||||||
raise InvalidArgumentError('only 2, 4 and 16 bytes are allowed')
|
raise InvalidArgumentError('only 2, 4 and 16 bytes are allowed')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_16_bits(cls, uuid_16: int, name: Optional[str] = None) -> UUID:
|
def from_16_bits(cls, uuid_16: int, name: str | None = None) -> UUID:
|
||||||
return cls.from_bytes(struct.pack('<H', uuid_16), name)
|
return cls.from_bytes(struct.pack('<H', uuid_16), name)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_32_bits(cls, uuid_32: int, name: Optional[str] = None) -> UUID:
|
def from_32_bits(cls, uuid_32: int, name: str | None = None) -> UUID:
|
||||||
return cls.from_bytes(struct.pack('<I', uuid_32), name)
|
return cls.from_bytes(struct.pack('<I', uuid_32), name)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -733,7 +728,7 @@ class ClassOfDevice:
|
|||||||
MajorDeviceClass.HEALTH: HEALTH_MINOR_DEVICE_CLASS_LABELS,
|
MajorDeviceClass.HEALTH: HEALTH_MINOR_DEVICE_CLASS_LABELS,
|
||||||
}
|
}
|
||||||
|
|
||||||
_MINOR_DEVICE_CLASSES: ClassVar[dict[MajorDeviceClass, Type]] = {
|
_MINOR_DEVICE_CLASSES: ClassVar[dict[MajorDeviceClass, type]] = {
|
||||||
MajorDeviceClass.COMPUTER: ComputerMinorDeviceClass,
|
MajorDeviceClass.COMPUTER: ComputerMinorDeviceClass,
|
||||||
MajorDeviceClass.PHONE: PhoneMinorDeviceClass,
|
MajorDeviceClass.PHONE: PhoneMinorDeviceClass,
|
||||||
MajorDeviceClass.LAN_NETWORK_ACCESS_POINT: LanNetworkMinorDeviceClass,
|
MajorDeviceClass.LAN_NETWORK_ACCESS_POINT: LanNetworkMinorDeviceClass,
|
||||||
@@ -748,17 +743,17 @@ class ClassOfDevice:
|
|||||||
|
|
||||||
major_service_classes: MajorServiceClasses
|
major_service_classes: MajorServiceClasses
|
||||||
major_device_class: MajorDeviceClass
|
major_device_class: MajorDeviceClass
|
||||||
minor_device_class: Union[
|
minor_device_class: (
|
||||||
ComputerMinorDeviceClass,
|
ComputerMinorDeviceClass
|
||||||
PhoneMinorDeviceClass,
|
| PhoneMinorDeviceClass
|
||||||
LanNetworkMinorDeviceClass,
|
| LanNetworkMinorDeviceClass
|
||||||
AudioVideoMinorDeviceClass,
|
| AudioVideoMinorDeviceClass
|
||||||
PeripheralMinorDeviceClass,
|
| PeripheralMinorDeviceClass
|
||||||
WearableMinorDeviceClass,
|
| WearableMinorDeviceClass
|
||||||
ToyMinorDeviceClass,
|
| ToyMinorDeviceClass
|
||||||
HealthMinorDeviceClass,
|
| HealthMinorDeviceClass
|
||||||
int,
|
| int
|
||||||
]
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_int(cls, class_of_device: int) -> Self:
|
def from_int(cls, class_of_device: int) -> Self:
|
||||||
@@ -1547,7 +1542,7 @@ class DataType:
|
|||||||
return f"{self.__class__.__name__}({self.value_string()})"
|
return f"{self.__class__.__name__}({self.value_string()})"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_advertising_data(cls, advertising_data: AdvertisingData) -> Optional[Self]:
|
def from_advertising_data(cls, advertising_data: AdvertisingData) -> Self | None:
|
||||||
if (data := advertising_data.get(cls.ad_type, raw=True)) is None:
|
if (data := advertising_data.get(cls.ad_type, raw=True)) is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -1575,16 +1570,16 @@ class DataType:
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Advertising Data
|
# Advertising Data
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
AdvertisingDataObject = Union[
|
AdvertisingDataObject = (
|
||||||
list[UUID],
|
list[UUID]
|
||||||
tuple[UUID, bytes],
|
| tuple[UUID, bytes]
|
||||||
bytes,
|
| bytes
|
||||||
str,
|
| str
|
||||||
int,
|
| int
|
||||||
tuple[int, int],
|
| tuple[int, int]
|
||||||
tuple[int, bytes],
|
| tuple[int, bytes]
|
||||||
Appearance,
|
| Appearance
|
||||||
]
|
)
|
||||||
|
|
||||||
|
|
||||||
class AdvertisingData:
|
class AdvertisingData:
|
||||||
@@ -1721,7 +1716,7 @@ class AdvertisingData:
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
ad_structures: Optional[Iterable[Union[tuple[int, bytes], DataType]]] = None,
|
ad_structures: Iterable[tuple[int, bytes] | DataType] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if ad_structures is None:
|
if ad_structures is None:
|
||||||
ad_structures = []
|
ad_structures = []
|
||||||
@@ -2019,7 +2014,7 @@ class AdvertisingData:
|
|||||||
AdvertisingData.Type.LIST_OF_128_BIT_SERVICE_SOLICITATION_UUIDS,
|
AdvertisingData.Type.LIST_OF_128_BIT_SERVICE_SOLICITATION_UUIDS,
|
||||||
],
|
],
|
||||||
raw: Literal[False] = False,
|
raw: Literal[False] = False,
|
||||||
) -> Optional[list[UUID]]: ...
|
) -> list[UUID] | None: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get(
|
def get(
|
||||||
@@ -2030,7 +2025,7 @@ class AdvertisingData:
|
|||||||
AdvertisingData.Type.SERVICE_DATA_128_BIT_UUID,
|
AdvertisingData.Type.SERVICE_DATA_128_BIT_UUID,
|
||||||
],
|
],
|
||||||
raw: Literal[False] = False,
|
raw: Literal[False] = False,
|
||||||
) -> Optional[tuple[UUID, bytes]]: ...
|
) -> tuple[UUID, bytes] | None: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get(
|
def get(
|
||||||
@@ -2042,7 +2037,7 @@ class AdvertisingData:
|
|||||||
AdvertisingData.Type.BROADCAST_NAME,
|
AdvertisingData.Type.BROADCAST_NAME,
|
||||||
],
|
],
|
||||||
raw: Literal[False] = False,
|
raw: Literal[False] = False,
|
||||||
) -> Optional[Optional[str]]: ...
|
) -> str | None: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get(
|
def get(
|
||||||
@@ -2054,38 +2049,36 @@ class AdvertisingData:
|
|||||||
AdvertisingData.Type.CLASS_OF_DEVICE,
|
AdvertisingData.Type.CLASS_OF_DEVICE,
|
||||||
],
|
],
|
||||||
raw: Literal[False] = False,
|
raw: Literal[False] = False,
|
||||||
) -> Optional[int]: ...
|
) -> int | None: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get(
|
def get(
|
||||||
self,
|
self,
|
||||||
type_id: Literal[AdvertisingData.Type.PERIPHERAL_CONNECTION_INTERVAL_RANGE,],
|
type_id: Literal[AdvertisingData.Type.PERIPHERAL_CONNECTION_INTERVAL_RANGE,],
|
||||||
raw: Literal[False] = False,
|
raw: Literal[False] = False,
|
||||||
) -> Optional[tuple[int, int]]: ...
|
) -> tuple[int, int] | None: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get(
|
def get(
|
||||||
self,
|
self,
|
||||||
type_id: Literal[AdvertisingData.Type.MANUFACTURER_SPECIFIC_DATA,],
|
type_id: Literal[AdvertisingData.Type.MANUFACTURER_SPECIFIC_DATA,],
|
||||||
raw: Literal[False] = False,
|
raw: Literal[False] = False,
|
||||||
) -> Optional[tuple[int, bytes]]: ...
|
) -> tuple[int, bytes] | None: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get(
|
def get(
|
||||||
self,
|
self,
|
||||||
type_id: Literal[AdvertisingData.Type.APPEARANCE,],
|
type_id: Literal[AdvertisingData.Type.APPEARANCE,],
|
||||||
raw: Literal[False] = False,
|
raw: Literal[False] = False,
|
||||||
) -> Optional[Appearance]: ...
|
) -> Appearance | None: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get(self, type_id: int, raw: Literal[True]) -> Optional[bytes]: ...
|
def get(self, type_id: int, raw: Literal[True]) -> bytes | None: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get(
|
def get(self, type_id: int, raw: bool = False) -> AdvertisingDataObject | None: ...
|
||||||
self, type_id: int, raw: bool = False
|
|
||||||
) -> Optional[AdvertisingDataObject]: ...
|
|
||||||
|
|
||||||
def get(self, type_id: int, raw: bool = False) -> Optional[AdvertisingDataObject]:
|
def get(self, type_id: int, raw: bool = False) -> AdvertisingDataObject | None:
|
||||||
'''
|
'''
|
||||||
Get advertising data as a simple AdvertisingDataObject object.
|
Get advertising data as a simple AdvertisingDataObject object.
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import dataclasses
|
|||||||
import functools
|
import functools
|
||||||
import secrets
|
import secrets
|
||||||
import struct
|
import struct
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from bumble import core
|
from bumble import core
|
||||||
|
|
||||||
@@ -309,7 +308,7 @@ class _CMAC:
|
|||||||
self.digest_size = mac_len
|
self.digest_size = mac_len
|
||||||
self._key = key
|
self._key = key
|
||||||
self._block_size = bs = 16
|
self._block_size = bs = 16
|
||||||
self._mac_tag: Optional[bytes] = None
|
self._mac_tag: bytes | None = None
|
||||||
self._update_after_digest = update_after_digest
|
self._update_after_digest = update_after_digest
|
||||||
|
|
||||||
# Section 5.3 of NIST SP 800 38B and Appendix B
|
# Section 5.3 of NIST SP 800 38B and Appendix B
|
||||||
@@ -348,7 +347,7 @@ class _CMAC:
|
|||||||
self._last_ct = zero_block
|
self._last_ct = zero_block
|
||||||
|
|
||||||
# Last block that was encrypted with AES
|
# Last block that was encrypted with AES
|
||||||
self._last_pt: Optional[bytes] = None
|
self._last_pt: bytes | None = None
|
||||||
|
|
||||||
# Counter for total message size
|
# Counter for total message size
|
||||||
self._data_size = 0
|
self._data_size = 0
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ from __future__ import annotations
|
|||||||
import dataclasses
|
import dataclasses
|
||||||
import math
|
import math
|
||||||
import struct
|
import struct
|
||||||
from typing import Any, ClassVar, Sequence
|
from collections.abc import Sequence
|
||||||
|
from typing import Any, ClassVar
|
||||||
|
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Constants
|
# Constants
|
||||||
@@ -167,12 +166,12 @@ class G722Decoder:
|
|||||||
# The initial value in BLOCK 3H
|
# The initial value in BLOCK 3H
|
||||||
self._band[1].det = 8
|
self._band[1].det = 8
|
||||||
|
|
||||||
def decode_frame(self, encoded_data: Union[bytes, bytearray]) -> bytearray:
|
def decode_frame(self, encoded_data: bytes | bytearray) -> bytearray:
|
||||||
result_array = bytearray(len(encoded_data) * 4)
|
result_array = bytearray(len(encoded_data) * 4)
|
||||||
self.g722_decode(result_array, encoded_data)
|
self.g722_decode(result_array, encoded_data)
|
||||||
return result_array
|
return result_array
|
||||||
|
|
||||||
def g722_decode(self, result_array, encoded_data: Union[bytes, bytearray]) -> int:
|
def g722_decode(self, result_array, encoded_data: bytes | bytearray) -> int:
|
||||||
"""Decode the data frame using g722 decoder."""
|
"""Decode the data frame using g722 decoder."""
|
||||||
result_length = 0
|
result_length = 0
|
||||||
|
|
||||||
|
|||||||
204
bumble/device.py
204
bumble/device.py
@@ -25,19 +25,15 @@ import itertools
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import secrets
|
import secrets
|
||||||
from collections.abc import Iterable, Sequence
|
from collections.abc import Awaitable, Callable, Iterable, Sequence
|
||||||
from contextlib import AsyncExitStack, asynccontextmanager, closing
|
from contextlib import AsyncExitStack, asynccontextmanager, closing
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from enum import Enum, IntEnum
|
from enum import Enum, IntEnum
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Any,
|
Any,
|
||||||
Awaitable,
|
|
||||||
Callable,
|
|
||||||
ClassVar,
|
ClassVar,
|
||||||
Optional,
|
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Union,
|
|
||||||
cast,
|
cast,
|
||||||
overload,
|
overload,
|
||||||
)
|
)
|
||||||
@@ -188,7 +184,7 @@ class Advertisement:
|
|||||||
self.data = AdvertisingData.from_bytes(self.data_bytes)
|
self.data = AdvertisingData.from_bytes(self.data_bytes)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_advertising_report(cls, report) -> Optional[Advertisement]:
|
def from_advertising_report(cls, report) -> Advertisement | None:
|
||||||
if isinstance(report, hci.HCI_LE_Advertising_Report_Event.Report):
|
if isinstance(report, hci.HCI_LE_Advertising_Report_Event.Report):
|
||||||
return LegacyAdvertisement.from_advertising_report(report)
|
return LegacyAdvertisement.from_advertising_report(report)
|
||||||
|
|
||||||
@@ -604,11 +600,11 @@ class AdvertisingSet(utils.EventEmitter):
|
|||||||
device: Device
|
device: Device
|
||||||
advertising_handle: int
|
advertising_handle: int
|
||||||
auto_restart: bool
|
auto_restart: bool
|
||||||
random_address: Optional[hci.Address]
|
random_address: hci.Address | None
|
||||||
advertising_parameters: AdvertisingParameters
|
advertising_parameters: AdvertisingParameters
|
||||||
advertising_data: bytes
|
advertising_data: bytes
|
||||||
scan_response_data: bytes
|
scan_response_data: bytes
|
||||||
periodic_advertising_parameters: Optional[PeriodicAdvertisingParameters]
|
periodic_advertising_parameters: PeriodicAdvertisingParameters | None
|
||||||
periodic_advertising_data: bytes
|
periodic_advertising_data: bytes
|
||||||
selected_tx_power: int = 0
|
selected_tx_power: int = 0
|
||||||
enabled: bool = False
|
enabled: bool = False
|
||||||
@@ -855,7 +851,7 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
|
|||||||
TERMINATED = 6
|
TERMINATED = 6
|
||||||
|
|
||||||
_state: State
|
_state: State
|
||||||
sync_handle: Optional[int]
|
sync_handle: int | None
|
||||||
advertiser_address: hci.Address
|
advertiser_address: hci.Address
|
||||||
sid: int
|
sid: int
|
||||||
skip: int
|
skip: int
|
||||||
@@ -1282,7 +1278,7 @@ class Peer:
|
|||||||
return mtu
|
return mtu
|
||||||
|
|
||||||
async def discover_service(
|
async def discover_service(
|
||||||
self, uuid: Union[core.UUID, str]
|
self, uuid: core.UUID | str
|
||||||
) -> list[gatt_client.ServiceProxy]:
|
) -> list[gatt_client.ServiceProxy]:
|
||||||
return await self.gatt_client.discover_service(uuid)
|
return await self.gatt_client.discover_service(uuid)
|
||||||
|
|
||||||
@@ -1298,8 +1294,8 @@ class Peer:
|
|||||||
|
|
||||||
async def discover_characteristics(
|
async def discover_characteristics(
|
||||||
self,
|
self,
|
||||||
uuids: Iterable[Union[core.UUID, str]] = (),
|
uuids: Iterable[core.UUID | str] = (),
|
||||||
service: Optional[gatt_client.ServiceProxy] = None,
|
service: gatt_client.ServiceProxy | None = None,
|
||||||
) -> list[gatt_client.CharacteristicProxy[bytes]]:
|
) -> list[gatt_client.CharacteristicProxy[bytes]]:
|
||||||
return await self.gatt_client.discover_characteristics(
|
return await self.gatt_client.discover_characteristics(
|
||||||
uuids=uuids, service=service
|
uuids=uuids, service=service
|
||||||
@@ -1307,9 +1303,9 @@ class Peer:
|
|||||||
|
|
||||||
async def discover_descriptors(
|
async def discover_descriptors(
|
||||||
self,
|
self,
|
||||||
characteristic: Optional[gatt_client.CharacteristicProxy] = None,
|
characteristic: gatt_client.CharacteristicProxy | None = None,
|
||||||
start_handle: Optional[int] = None,
|
start_handle: int | None = None,
|
||||||
end_handle: Optional[int] = None,
|
end_handle: int | None = None,
|
||||||
):
|
):
|
||||||
return await self.gatt_client.discover_descriptors(
|
return await self.gatt_client.discover_descriptors(
|
||||||
characteristic, start_handle, end_handle
|
characteristic, start_handle, end_handle
|
||||||
@@ -1330,7 +1326,7 @@ class Peer:
|
|||||||
async def subscribe(
|
async def subscribe(
|
||||||
self,
|
self,
|
||||||
characteristic: gatt_client.CharacteristicProxy,
|
characteristic: gatt_client.CharacteristicProxy,
|
||||||
subscriber: Optional[Callable[[bytes], Any]] = None,
|
subscriber: Callable[[bytes], Any] | None = None,
|
||||||
prefer_notify: bool = True,
|
prefer_notify: bool = True,
|
||||||
) -> None:
|
) -> None:
|
||||||
return await self.gatt_client.subscribe(
|
return await self.gatt_client.subscribe(
|
||||||
@@ -1340,25 +1336,23 @@ class Peer:
|
|||||||
async def unsubscribe(
|
async def unsubscribe(
|
||||||
self,
|
self,
|
||||||
characteristic: gatt_client.CharacteristicProxy,
|
characteristic: gatt_client.CharacteristicProxy,
|
||||||
subscriber: Optional[Callable[[bytes], Any]] = None,
|
subscriber: Callable[[bytes], Any] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
return await self.gatt_client.unsubscribe(characteristic, subscriber)
|
return await self.gatt_client.unsubscribe(characteristic, subscriber)
|
||||||
|
|
||||||
async def read_value(
|
async def read_value(self, attribute: int | gatt_client.AttributeProxy) -> bytes:
|
||||||
self, attribute: Union[int, gatt_client.AttributeProxy]
|
|
||||||
) -> bytes:
|
|
||||||
return await self.gatt_client.read_value(attribute)
|
return await self.gatt_client.read_value(attribute)
|
||||||
|
|
||||||
async def write_value(
|
async def write_value(
|
||||||
self,
|
self,
|
||||||
attribute: Union[int, gatt_client.AttributeProxy],
|
attribute: int | gatt_client.AttributeProxy,
|
||||||
value: bytes,
|
value: bytes,
|
||||||
with_response: bool = False,
|
with_response: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
return await self.gatt_client.write_value(attribute, value, with_response)
|
return await self.gatt_client.write_value(attribute, value, with_response)
|
||||||
|
|
||||||
async def read_characteristics_by_uuid(
|
async def read_characteristics_by_uuid(
|
||||||
self, uuid: core.UUID, service: Optional[gatt_client.ServiceProxy] = None
|
self, uuid: core.UUID, service: gatt_client.ServiceProxy | None = None
|
||||||
) -> list[bytes]:
|
) -> list[bytes]:
|
||||||
return await self.gatt_client.read_characteristics_by_uuid(uuid, service)
|
return await self.gatt_client.read_characteristics_by_uuid(uuid, service)
|
||||||
|
|
||||||
@@ -1368,7 +1362,7 @@ class Peer:
|
|||||||
def get_characteristics_by_uuid(
|
def get_characteristics_by_uuid(
|
||||||
self,
|
self,
|
||||||
uuid: core.UUID,
|
uuid: core.UUID,
|
||||||
service: Optional[Union[gatt_client.ServiceProxy, core.UUID]] = None,
|
service: gatt_client.ServiceProxy | core.UUID | None = None,
|
||||||
) -> list[gatt_client.CharacteristicProxy[bytes]]:
|
) -> list[gatt_client.CharacteristicProxy[bytes]]:
|
||||||
if isinstance(service, core.UUID):
|
if isinstance(service, core.UUID):
|
||||||
return list(
|
return list(
|
||||||
@@ -1384,7 +1378,7 @@ class Peer:
|
|||||||
|
|
||||||
def create_service_proxy(
|
def create_service_proxy(
|
||||||
self, proxy_class: type[_PROXY_CLASS]
|
self, proxy_class: type[_PROXY_CLASS]
|
||||||
) -> Optional[_PROXY_CLASS]:
|
) -> _PROXY_CLASS | None:
|
||||||
if proxy := proxy_class.from_client(self.gatt_client):
|
if proxy := proxy_class.from_client(self.gatt_client):
|
||||||
return cast(_PROXY_CLASS, proxy)
|
return cast(_PROXY_CLASS, proxy)
|
||||||
|
|
||||||
@@ -1392,7 +1386,7 @@ class Peer:
|
|||||||
|
|
||||||
async def discover_service_and_create_proxy(
|
async def discover_service_and_create_proxy(
|
||||||
self, proxy_class: type[_PROXY_CLASS]
|
self, proxy_class: type[_PROXY_CLASS]
|
||||||
) -> Optional[_PROXY_CLASS]:
|
) -> _PROXY_CLASS | None:
|
||||||
# Discover the first matching service and its characteristics
|
# Discover the first matching service and its characteristics
|
||||||
services = await self.discover_service(proxy_class.SERVICE_CLASS.UUID)
|
services = await self.discover_service(proxy_class.SERVICE_CLASS.UUID)
|
||||||
if services:
|
if services:
|
||||||
@@ -1401,7 +1395,7 @@ class Peer:
|
|||||||
return self.create_service_proxy(proxy_class)
|
return self.create_service_proxy(proxy_class)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def sustain(self, timeout: Optional[float] = None) -> None:
|
async def sustain(self, timeout: float | None = None) -> None:
|
||||||
await self.connection.sustain(timeout)
|
await self.connection.sustain(timeout)
|
||||||
|
|
||||||
# [Classic only]
|
# [Classic only]
|
||||||
@@ -1444,7 +1438,7 @@ class ScoLink(utils.CompositeEventEmitter):
|
|||||||
acl_connection: Connection
|
acl_connection: Connection
|
||||||
handle: int
|
handle: int
|
||||||
link_type: int
|
link_type: int
|
||||||
sink: Optional[Callable[[hci.HCI_SynchronousDataPacket], Any]] = None
|
sink: Callable[[hci.HCI_SynchronousDataPacket], Any] | None = None
|
||||||
|
|
||||||
EVENT_DISCONNECTION: ClassVar[str] = "disconnection"
|
EVENT_DISCONNECTION: ClassVar[str] = "disconnection"
|
||||||
EVENT_DISCONNECTION_FAILURE: ClassVar[str] = "disconnection_failure"
|
EVENT_DISCONNECTION_FAILURE: ClassVar[str] = "disconnection_failure"
|
||||||
@@ -1627,8 +1621,8 @@ class CisLink(utils.EventEmitter, _IsoLink):
|
|||||||
cis_sync_delay: int = 0 # CIS sync delay, in microseconds
|
cis_sync_delay: int = 0 # CIS sync delay, in microseconds
|
||||||
transport_latency_c_to_p: int = 0 # C->P transport latency, in microseconds
|
transport_latency_c_to_p: int = 0 # C->P transport latency, in microseconds
|
||||||
transport_latency_p_to_c: int = 0 # P->C transport latency, in microseconds
|
transport_latency_p_to_c: int = 0 # P->C transport latency, in microseconds
|
||||||
phy_c_to_p: Optional[hci.Phy] = None
|
phy_c_to_p: hci.Phy | None = None
|
||||||
phy_p_to_c: Optional[hci.Phy] = None
|
phy_p_to_c: hci.Phy | None = None
|
||||||
nse: int = 0
|
nse: int = 0
|
||||||
bn_c_to_p: int = 0
|
bn_c_to_p: int = 0
|
||||||
bn_p_to_c: int = 0
|
bn_p_to_c: int = 0
|
||||||
@@ -1716,11 +1710,11 @@ class Connection(utils.CompositeEventEmitter):
|
|||||||
handle: int
|
handle: int
|
||||||
transport: core.PhysicalTransport
|
transport: core.PhysicalTransport
|
||||||
self_address: hci.Address
|
self_address: hci.Address
|
||||||
self_resolvable_address: Optional[hci.Address]
|
self_resolvable_address: hci.Address | None
|
||||||
peer_address: hci.Address
|
peer_address: hci.Address
|
||||||
peer_name: Optional[str]
|
peer_name: str | None
|
||||||
peer_resolvable_address: Optional[hci.Address]
|
peer_resolvable_address: hci.Address | None
|
||||||
peer_le_features: Optional[hci.LeFeatureMask]
|
peer_le_features: hci.LeFeatureMask | None
|
||||||
role: hci.Role
|
role: hci.Role
|
||||||
parameters: Parameters
|
parameters: Parameters
|
||||||
encryption: int
|
encryption: int
|
||||||
@@ -1728,8 +1722,8 @@ class Connection(utils.CompositeEventEmitter):
|
|||||||
authenticated: bool
|
authenticated: bool
|
||||||
sc: bool
|
sc: bool
|
||||||
gatt_client: gatt_client.Client
|
gatt_client: gatt_client.Client
|
||||||
pairing_peer_io_capability: Optional[int]
|
pairing_peer_io_capability: int | None
|
||||||
pairing_peer_authentication_requirements: Optional[int]
|
pairing_peer_authentication_requirements: int | None
|
||||||
cs_configs: dict[int, ChannelSoundingConfig] # Config ID to Configuration
|
cs_configs: dict[int, ChannelSoundingConfig] # Config ID to Configuration
|
||||||
cs_procedures: dict[int, ChannelSoundingProcedure] # Config ID to Procedures
|
cs_procedures: dict[int, ChannelSoundingProcedure] # Config ID to Procedures
|
||||||
classic_mode: int = hci.HCI_Mode_Change_Event.Mode.ACTIVE
|
classic_mode: int = hci.HCI_Mode_Change_Event.Mode.ACTIVE
|
||||||
@@ -1831,9 +1825,9 @@ class Connection(utils.CompositeEventEmitter):
|
|||||||
handle: int,
|
handle: int,
|
||||||
transport: core.PhysicalTransport,
|
transport: core.PhysicalTransport,
|
||||||
self_address: hci.Address,
|
self_address: hci.Address,
|
||||||
self_resolvable_address: Optional[hci.Address],
|
self_resolvable_address: hci.Address | None,
|
||||||
peer_address: hci.Address,
|
peer_address: hci.Address,
|
||||||
peer_resolvable_address: Optional[hci.Address],
|
peer_resolvable_address: hci.Address | None,
|
||||||
role: hci.Role,
|
role: hci.Role,
|
||||||
parameters: Parameters,
|
parameters: Parameters,
|
||||||
):
|
):
|
||||||
@@ -1896,8 +1890,8 @@ class Connection(utils.CompositeEventEmitter):
|
|||||||
) -> l2cap.LeCreditBasedChannel: ...
|
) -> l2cap.LeCreditBasedChannel: ...
|
||||||
|
|
||||||
async def create_l2cap_channel(
|
async def create_l2cap_channel(
|
||||||
self, spec: Union[l2cap.ClassicChannelSpec, l2cap.LeCreditBasedChannelSpec]
|
self, spec: l2cap.ClassicChannelSpec | l2cap.LeCreditBasedChannelSpec
|
||||||
) -> Union[l2cap.ClassicChannel, l2cap.LeCreditBasedChannel]:
|
) -> l2cap.ClassicChannel | l2cap.LeCreditBasedChannel:
|
||||||
return await self.device.create_l2cap_channel(connection=self, spec=spec)
|
return await self.device.create_l2cap_channel(connection=self, spec=spec)
|
||||||
|
|
||||||
async def disconnect(
|
async def disconnect(
|
||||||
@@ -1921,7 +1915,7 @@ class Connection(utils.CompositeEventEmitter):
|
|||||||
async def switch_role(self, role: hci.Role) -> None:
|
async def switch_role(self, role: hci.Role) -> None:
|
||||||
return await self.device.switch_role(self, role)
|
return await self.device.switch_role(self, role)
|
||||||
|
|
||||||
async def sustain(self, timeout: Optional[float] = None) -> None:
|
async def sustain(self, timeout: float | None = None) -> None:
|
||||||
"""Idles the current task waiting for a disconnect or timeout"""
|
"""Idles the current task waiting for a disconnect or timeout"""
|
||||||
|
|
||||||
abort = asyncio.get_running_loop().create_future()
|
abort = asyncio.get_running_loop().create_future()
|
||||||
@@ -1965,8 +1959,8 @@ class Connection(utils.CompositeEventEmitter):
|
|||||||
|
|
||||||
async def set_phy(
|
async def set_phy(
|
||||||
self,
|
self,
|
||||||
tx_phys: Optional[Iterable[hci.Phy]] = None,
|
tx_phys: Iterable[hci.Phy] | None = None,
|
||||||
rx_phys: Optional[Iterable[hci.Phy]] = None,
|
rx_phys: Iterable[hci.Phy] | None = None,
|
||||||
phy_options: int = 0,
|
phy_options: int = 0,
|
||||||
):
|
):
|
||||||
return await self.device.set_connection_phy(self, tx_phys, rx_phys, phy_options)
|
return await self.device.set_connection_phy(self, tx_phys, rx_phys, phy_options)
|
||||||
@@ -2070,12 +2064,12 @@ class DeviceConfiguration:
|
|||||||
AdvertisingData([data_types.CompleteLocalName(DEVICE_DEFAULT_NAME)])
|
AdvertisingData([data_types.CompleteLocalName(DEVICE_DEFAULT_NAME)])
|
||||||
)
|
)
|
||||||
irk: bytes = bytes(16) # This really must be changed for any level of security
|
irk: bytes = bytes(16) # This really must be changed for any level of security
|
||||||
keystore: Optional[str] = None
|
keystore: str | None = None
|
||||||
address_resolution_offload: bool = False
|
address_resolution_offload: bool = False
|
||||||
address_generation_offload: bool = False
|
address_generation_offload: bool = False
|
||||||
cis_enabled: bool = False
|
cis_enabled: bool = False
|
||||||
channel_sounding_enabled: bool = False
|
channel_sounding_enabled: bool = False
|
||||||
identity_address_type: Optional[int] = None
|
identity_address_type: int | None = None
|
||||||
io_capability: int = pairing.PairingDelegate.IoCapability.NO_OUTPUT_NO_INPUT
|
io_capability: int = pairing.PairingDelegate.IoCapability.NO_OUTPUT_NO_INPUT
|
||||||
gap_service_enabled: bool = True
|
gap_service_enabled: bool = True
|
||||||
gatt_service_enabled: bool = True
|
gatt_service_enabled: bool = True
|
||||||
@@ -2143,7 +2137,7 @@ class DeviceConfiguration:
|
|||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
def load_from_file(self, filename: str) -> None:
|
def load_from_file(self, filename: str) -> None:
|
||||||
with open(filename, 'r', encoding='utf-8') as file:
|
with open(filename, encoding='utf-8') as file:
|
||||||
self.load_from_dict(json.load(file))
|
self.load_from_dict(json.load(file))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -2254,12 +2248,12 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
pending_connections: dict[hci.Address, Connection]
|
pending_connections: dict[hci.Address, Connection]
|
||||||
classic_pending_accepts: dict[
|
classic_pending_accepts: dict[
|
||||||
hci.Address,
|
hci.Address,
|
||||||
list[asyncio.Future[Union[Connection, tuple[hci.Address, int, int]]]],
|
list[asyncio.Future[Connection | tuple[hci.Address, int, int]]],
|
||||||
]
|
]
|
||||||
advertisement_accumulators: dict[hci.Address, AdvertisementDataAccumulator]
|
advertisement_accumulators: dict[hci.Address, AdvertisementDataAccumulator]
|
||||||
periodic_advertising_syncs: list[PeriodicAdvertisingSync]
|
periodic_advertising_syncs: list[PeriodicAdvertisingSync]
|
||||||
config: DeviceConfiguration
|
config: DeviceConfiguration
|
||||||
legacy_advertiser: Optional[LegacyAdvertiser]
|
legacy_advertiser: LegacyAdvertiser | None
|
||||||
sco_links: dict[int, ScoLink]
|
sco_links: dict[int, ScoLink]
|
||||||
cis_links: dict[int, CisLink]
|
cis_links: dict[int, CisLink]
|
||||||
bigs: dict[int, Big]
|
bigs: dict[int, Big]
|
||||||
@@ -2347,10 +2341,10 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name: Optional[str] = None,
|
name: str | None = None,
|
||||||
address: Optional[hci.Address] = None,
|
address: hci.Address | None = None,
|
||||||
config: Optional[DeviceConfiguration] = None,
|
config: DeviceConfiguration | None = None,
|
||||||
host: Optional[Host] = None,
|
host: Host | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
@@ -2407,7 +2401,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
self.le_simultaneous_enabled = config.le_simultaneous_enabled
|
self.le_simultaneous_enabled = config.le_simultaneous_enabled
|
||||||
self.le_privacy_enabled = config.le_privacy_enabled
|
self.le_privacy_enabled = config.le_privacy_enabled
|
||||||
self.le_rpa_timeout = config.le_rpa_timeout
|
self.le_rpa_timeout = config.le_rpa_timeout
|
||||||
self.le_rpa_periodic_update_task: Optional[asyncio.Task] = None
|
self.le_rpa_periodic_update_task: asyncio.Task | None = None
|
||||||
self.le_subrate_enabled = config.le_subrate_enabled
|
self.le_subrate_enabled = config.le_subrate_enabled
|
||||||
self.classic_enabled = config.classic_enabled
|
self.classic_enabled = config.classic_enabled
|
||||||
self.cis_enabled = config.cis_enabled
|
self.cis_enabled = config.cis_enabled
|
||||||
@@ -2431,8 +2425,8 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
# can be initialized from a config object, and for backward compatibility for
|
# can be initialized from a config object, and for backward compatibility for
|
||||||
# client code that may set those values directly before calling
|
# client code that may set those values directly before calling
|
||||||
# start_advertising().
|
# start_advertising().
|
||||||
self.legacy_advertising_set: Optional[AdvertisingSet] = None
|
self.legacy_advertising_set: AdvertisingSet | None = None
|
||||||
self.legacy_advertiser: Optional[LegacyAdvertiser] = None
|
self.legacy_advertiser: LegacyAdvertiser | None = None
|
||||||
self.advertising_data = config.advertising_data
|
self.advertising_data = config.advertising_data
|
||||||
self.scan_response_data = config.scan_response_data
|
self.scan_response_data = config.scan_response_data
|
||||||
self.advertising_interval_min = config.advertising_interval_min
|
self.advertising_interval_min = config.advertising_interval_min
|
||||||
@@ -2550,7 +2544,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
def sdp_service_records(self, service_records):
|
def sdp_service_records(self, service_records):
|
||||||
self.sdp_server.service_records = service_records
|
self.sdp_server.service_records = service_records
|
||||||
|
|
||||||
def lookup_connection(self, connection_handle: int) -> Optional[Connection]:
|
def lookup_connection(self, connection_handle: int) -> Connection | None:
|
||||||
if connection := self.connections.get(connection_handle):
|
if connection := self.connections.get(connection_handle):
|
||||||
return connection
|
return connection
|
||||||
|
|
||||||
@@ -2559,9 +2553,9 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
def find_connection_by_bd_addr(
|
def find_connection_by_bd_addr(
|
||||||
self,
|
self,
|
||||||
bd_addr: hci.Address,
|
bd_addr: hci.Address,
|
||||||
transport: Optional[int] = None,
|
transport: int | None = None,
|
||||||
check_address_type: bool = False,
|
check_address_type: bool = False,
|
||||||
) -> Optional[Connection]:
|
) -> Connection | None:
|
||||||
for connection in self.connections.values():
|
for connection in self.connections.values():
|
||||||
if bytes(connection.peer_address) == bytes(bd_addr):
|
if bytes(connection.peer_address) == bytes(bd_addr):
|
||||||
if (
|
if (
|
||||||
@@ -2576,7 +2570,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
|
|
||||||
def lookup_periodic_advertising_sync(
|
def lookup_periodic_advertising_sync(
|
||||||
self, sync_handle: int
|
self, sync_handle: int
|
||||||
) -> Optional[PeriodicAdvertisingSync]:
|
) -> PeriodicAdvertisingSync | None:
|
||||||
return next(
|
return next(
|
||||||
(
|
(
|
||||||
sync
|
sync
|
||||||
@@ -2614,8 +2608,8 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
async def create_l2cap_channel(
|
async def create_l2cap_channel(
|
||||||
self,
|
self,
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
spec: Union[l2cap.ClassicChannelSpec, l2cap.LeCreditBasedChannelSpec],
|
spec: l2cap.ClassicChannelSpec | l2cap.LeCreditBasedChannelSpec,
|
||||||
) -> Union[l2cap.ClassicChannel, l2cap.LeCreditBasedChannel]:
|
) -> l2cap.ClassicChannel | l2cap.LeCreditBasedChannel:
|
||||||
if isinstance(spec, l2cap.ClassicChannelSpec):
|
if isinstance(spec, l2cap.ClassicChannelSpec):
|
||||||
return await self.l2cap_channel_manager.create_classic_channel(
|
return await self.l2cap_channel_manager.create_classic_channel(
|
||||||
connection=connection, spec=spec
|
connection=connection, spec=spec
|
||||||
@@ -2629,25 +2623,25 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
def create_l2cap_server(
|
def create_l2cap_server(
|
||||||
self,
|
self,
|
||||||
spec: l2cap.ClassicChannelSpec,
|
spec: l2cap.ClassicChannelSpec,
|
||||||
handler: Optional[Callable[[l2cap.ClassicChannel], Any]] = None,
|
handler: Callable[[l2cap.ClassicChannel], Any] | None = None,
|
||||||
) -> l2cap.ClassicChannelServer: ...
|
) -> l2cap.ClassicChannelServer: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def create_l2cap_server(
|
def create_l2cap_server(
|
||||||
self,
|
self,
|
||||||
spec: l2cap.LeCreditBasedChannelSpec,
|
spec: l2cap.LeCreditBasedChannelSpec,
|
||||||
handler: Optional[Callable[[l2cap.LeCreditBasedChannel], Any]] = None,
|
handler: Callable[[l2cap.LeCreditBasedChannel], Any] | None = None,
|
||||||
) -> l2cap.LeCreditBasedChannelServer: ...
|
) -> l2cap.LeCreditBasedChannelServer: ...
|
||||||
|
|
||||||
def create_l2cap_server(
|
def create_l2cap_server(
|
||||||
self,
|
self,
|
||||||
spec: Union[l2cap.ClassicChannelSpec, l2cap.LeCreditBasedChannelSpec],
|
spec: l2cap.ClassicChannelSpec | l2cap.LeCreditBasedChannelSpec,
|
||||||
handler: Union[
|
handler: (
|
||||||
Callable[[l2cap.ClassicChannel], Any],
|
Callable[[l2cap.ClassicChannel], Any]
|
||||||
Callable[[l2cap.LeCreditBasedChannel], Any],
|
| Callable[[l2cap.LeCreditBasedChannel], Any]
|
||||||
None,
|
| None
|
||||||
] = None,
|
) = None,
|
||||||
) -> Union[l2cap.ClassicChannelServer, l2cap.LeCreditBasedChannelServer]:
|
) -> l2cap.ClassicChannelServer | l2cap.LeCreditBasedChannelServer:
|
||||||
if isinstance(spec, l2cap.ClassicChannelSpec):
|
if isinstance(spec, l2cap.ClassicChannelSpec):
|
||||||
return self.l2cap_channel_manager.create_classic_server(
|
return self.l2cap_channel_manager.create_classic_server(
|
||||||
spec=spec,
|
spec=spec,
|
||||||
@@ -2949,13 +2943,13 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
async def start_advertising(
|
async def start_advertising(
|
||||||
self,
|
self,
|
||||||
advertising_type: AdvertisingType = AdvertisingType.UNDIRECTED_CONNECTABLE_SCANNABLE,
|
advertising_type: AdvertisingType = AdvertisingType.UNDIRECTED_CONNECTABLE_SCANNABLE,
|
||||||
target: Optional[hci.Address] = None,
|
target: hci.Address | None = None,
|
||||||
own_address_type: hci.OwnAddressType = hci.OwnAddressType.RANDOM,
|
own_address_type: hci.OwnAddressType = hci.OwnAddressType.RANDOM,
|
||||||
auto_restart: bool = False,
|
auto_restart: bool = False,
|
||||||
advertising_data: Optional[bytes] = None,
|
advertising_data: bytes | None = None,
|
||||||
scan_response_data: Optional[bytes] = None,
|
scan_response_data: bytes | None = None,
|
||||||
advertising_interval_min: Optional[float] = None,
|
advertising_interval_min: float | None = None,
|
||||||
advertising_interval_max: Optional[float] = None,
|
advertising_interval_max: float | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Start legacy advertising.
|
"""Start legacy advertising.
|
||||||
|
|
||||||
@@ -3059,11 +3053,11 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
|
|
||||||
async def create_advertising_set(
|
async def create_advertising_set(
|
||||||
self,
|
self,
|
||||||
advertising_parameters: Optional[AdvertisingParameters] = None,
|
advertising_parameters: AdvertisingParameters | None = None,
|
||||||
random_address: Optional[hci.Address] = None,
|
random_address: hci.Address | None = None,
|
||||||
advertising_data: bytes = b'',
|
advertising_data: bytes = b'',
|
||||||
scan_response_data: bytes = b'',
|
scan_response_data: bytes = b'',
|
||||||
periodic_advertising_parameters: Optional[PeriodicAdvertisingParameters] = None,
|
periodic_advertising_parameters: PeriodicAdvertisingParameters | None = None,
|
||||||
periodic_advertising_data: bytes = b'',
|
periodic_advertising_data: bytes = b'',
|
||||||
auto_start: bool = True,
|
auto_start: bool = True,
|
||||||
auto_restart: bool = False,
|
auto_restart: bool = False,
|
||||||
@@ -3599,13 +3593,13 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
|
|
||||||
async def connect(
|
async def connect(
|
||||||
self,
|
self,
|
||||||
peer_address: Union[hci.Address, str],
|
peer_address: hci.Address | str,
|
||||||
transport: core.PhysicalTransport = PhysicalTransport.LE,
|
transport: core.PhysicalTransport = PhysicalTransport.LE,
|
||||||
connection_parameters_preferences: Optional[
|
connection_parameters_preferences: (
|
||||||
dict[hci.Phy, ConnectionParametersPreferences]
|
dict[hci.Phy, ConnectionParametersPreferences] | None
|
||||||
] = None,
|
) = None,
|
||||||
own_address_type: hci.OwnAddressType = hci.OwnAddressType.RANDOM,
|
own_address_type: hci.OwnAddressType = hci.OwnAddressType.RANDOM,
|
||||||
timeout: Optional[float] = DEVICE_DEFAULT_CONNECT_TIMEOUT,
|
timeout: float | None = DEVICE_DEFAULT_CONNECT_TIMEOUT,
|
||||||
always_resolve: bool = False,
|
always_resolve: bool = False,
|
||||||
) -> Connection:
|
) -> Connection:
|
||||||
'''
|
'''
|
||||||
@@ -3915,9 +3909,9 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
|
|
||||||
async def accept(
|
async def accept(
|
||||||
self,
|
self,
|
||||||
peer_address: Union[hci.Address, str] = hci.Address.ANY,
|
peer_address: hci.Address | str = hci.Address.ANY,
|
||||||
role: hci.Role = hci.Role.PERIPHERAL,
|
role: hci.Role = hci.Role.PERIPHERAL,
|
||||||
timeout: Optional[float] = DEVICE_DEFAULT_CONNECT_TIMEOUT,
|
timeout: float | None = DEVICE_DEFAULT_CONNECT_TIMEOUT,
|
||||||
) -> Connection:
|
) -> Connection:
|
||||||
'''
|
'''
|
||||||
Wait and accept any incoming connection or a connection from `peer_address` when
|
Wait and accept any incoming connection or a connection from `peer_address` when
|
||||||
@@ -4041,7 +4035,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
self.pending_connections.pop(peer_address, None)
|
self.pending_connections.pop(peer_address, None)
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def connect_as_gatt(self, peer_address: Union[hci.Address, str]):
|
async def connect_as_gatt(self, peer_address: hci.Address | str):
|
||||||
async with AsyncExitStack() as stack:
|
async with AsyncExitStack() as stack:
|
||||||
connection = await stack.enter_async_context(
|
connection = await stack.enter_async_context(
|
||||||
await self.connect(peer_address)
|
await self.connect(peer_address)
|
||||||
@@ -4088,7 +4082,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def disconnect(
|
async def disconnect(
|
||||||
self, connection: Union[Connection, ScoLink, CisLink], reason: int
|
self, connection: Connection | ScoLink | CisLink, reason: int
|
||||||
) -> None:
|
) -> None:
|
||||||
# Create a future so that we can wait for the disconnection's result
|
# Create a future so that we can wait for the disconnection's result
|
||||||
pending_disconnection = asyncio.get_running_loop().create_future()
|
pending_disconnection = asyncio.get_running_loop().create_future()
|
||||||
@@ -4227,8 +4221,8 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
async def set_connection_phy(
|
async def set_connection_phy(
|
||||||
self,
|
self,
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
tx_phys: Optional[Iterable[hci.Phy]] = None,
|
tx_phys: Iterable[hci.Phy] | None = None,
|
||||||
rx_phys: Optional[Iterable[hci.Phy]] = None,
|
rx_phys: Iterable[hci.Phy] | None = None,
|
||||||
phy_options: int = 0,
|
phy_options: int = 0,
|
||||||
):
|
):
|
||||||
if not self.host.supports_command(hci.HCI_LE_SET_PHY_COMMAND):
|
if not self.host.supports_command(hci.HCI_LE_SET_PHY_COMMAND):
|
||||||
@@ -4252,8 +4246,8 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
|
|
||||||
async def set_default_phy(
|
async def set_default_phy(
|
||||||
self,
|
self,
|
||||||
tx_phys: Optional[Iterable[hci.Phy]] = None,
|
tx_phys: Iterable[hci.Phy] | None = None,
|
||||||
rx_phys: Optional[Iterable[hci.Phy]] = None,
|
rx_phys: Iterable[hci.Phy] | None = None,
|
||||||
):
|
):
|
||||||
all_phys_bits = (1 if tx_phys is None else 0) | (
|
all_phys_bits = (1 if tx_phys is None else 0) | (
|
||||||
(1 if rx_phys is None else 0) << 1
|
(1 if rx_phys is None else 0) << 1
|
||||||
@@ -4307,7 +4301,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
if local_name == name:
|
if local_name == name:
|
||||||
peer_address.set_result(address)
|
peer_address.set_result(address)
|
||||||
|
|
||||||
listener: Optional[Callable[..., None]] = None
|
listener: Callable[..., None] | None = None
|
||||||
was_scanning = self.scanning
|
was_scanning = self.scanning
|
||||||
was_discovering = self.discovering
|
was_discovering = self.discovering
|
||||||
try:
|
try:
|
||||||
@@ -4421,7 +4415,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
|
|
||||||
async def get_long_term_key(
|
async def get_long_term_key(
|
||||||
self, connection_handle: int, rand: bytes, ediv: int
|
self, connection_handle: int, rand: bytes, ediv: int
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
if (connection := self.lookup_connection(connection_handle)) is None:
|
if (connection := self.lookup_connection(connection_handle)) is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -4445,7 +4439,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
return keys.ltk_peripheral.value
|
return keys.ltk_peripheral.value
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_link_key(self, address: hci.Address) -> Optional[bytes]:
|
async def get_link_key(self, address: hci.Address) -> bytes | None:
|
||||||
if self.keystore is None:
|
if self.keystore is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -4583,7 +4577,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
await connection.cancel_on_disconnection(pending_role_change)
|
await connection.cancel_on_disconnection(pending_role_change)
|
||||||
|
|
||||||
# [Classic only]
|
# [Classic only]
|
||||||
async def request_remote_name(self, remote: Union[hci.Address, Connection]) -> str:
|
async def request_remote_name(self, remote: hci.Address | Connection) -> str:
|
||||||
# Set up event handlers
|
# Set up event handlers
|
||||||
pending_name: asyncio.Future[str] = asyncio.get_running_loop().create_future()
|
pending_name: asyncio.Future[str] = asyncio.get_running_loop().create_future()
|
||||||
|
|
||||||
@@ -5153,7 +5147,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
self,
|
self,
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
attribute: Attribute,
|
attribute: Attribute,
|
||||||
value: Optional[Any] = None,
|
value: Any | None = None,
|
||||||
force: bool = False,
|
force: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -5172,7 +5166,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
await self.gatt_server.notify_subscriber(connection, attribute, value, force)
|
await self.gatt_server.notify_subscriber(connection, attribute, value, force)
|
||||||
|
|
||||||
async def notify_subscribers(
|
async def notify_subscribers(
|
||||||
self, attribute: Attribute, value: Optional[Any] = None, force: bool = False
|
self, attribute: Attribute, value: Any | None = None, force: bool = False
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Send a notification to all the subscribers of an attribute.
|
Send a notification to all the subscribers of an attribute.
|
||||||
@@ -5192,7 +5186,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
self,
|
self,
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
attribute: Attribute,
|
attribute: Attribute,
|
||||||
value: Optional[Any] = None,
|
value: Any | None = None,
|
||||||
force: bool = False,
|
force: bool = False,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -5213,7 +5207,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
await self.gatt_server.indicate_subscriber(connection, attribute, value, force)
|
await self.gatt_server.indicate_subscriber(connection, attribute, value, force)
|
||||||
|
|
||||||
async def indicate_subscribers(
|
async def indicate_subscribers(
|
||||||
self, attribute: Attribute, value: Optional[Any] = None, force: bool = False
|
self, attribute: Attribute, value: Any | None = None, force: bool = False
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Send an indication to all the subscribers of an attribute.
|
Send an indication to all the subscribers of an attribute.
|
||||||
@@ -5437,8 +5431,8 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
self,
|
self,
|
||||||
connection_handle: int,
|
connection_handle: int,
|
||||||
peer_address: hci.Address,
|
peer_address: hci.Address,
|
||||||
self_resolvable_address: Optional[hci.Address],
|
self_resolvable_address: hci.Address | None,
|
||||||
peer_resolvable_address: Optional[hci.Address],
|
peer_resolvable_address: hci.Address | None,
|
||||||
role: hci.Role,
|
role: hci.Role,
|
||||||
connection_interval: int,
|
connection_interval: int,
|
||||||
peripheral_latency: int,
|
peripheral_latency: int,
|
||||||
@@ -5473,7 +5467,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
peer_address = resolved_address
|
peer_address = resolved_address
|
||||||
|
|
||||||
self_address = None
|
self_address = None
|
||||||
own_address_type: Optional[hci.OwnAddressType] = None
|
own_address_type: hci.OwnAddressType | None = None
|
||||||
if role == hci.Role.CENTRAL:
|
if role == hci.Role.CENTRAL:
|
||||||
own_address_type = self.connect_own_address_type
|
own_address_type = self.connect_own_address_type
|
||||||
assert own_address_type is not None
|
assert own_address_type is not None
|
||||||
@@ -5938,7 +5932,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
@host_event_handler
|
@host_event_handler
|
||||||
@try_with_connection_from_address
|
@try_with_connection_from_address
|
||||||
def on_remote_name(
|
def on_remote_name(
|
||||||
self, connection: Optional[Connection], address: hci.Address, remote_name: bytes
|
self, connection: Connection | None, address: hci.Address, remote_name: bytes
|
||||||
):
|
):
|
||||||
# Try to decode the name
|
# Try to decode the name
|
||||||
try:
|
try:
|
||||||
@@ -5957,7 +5951,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
@host_event_handler
|
@host_event_handler
|
||||||
@try_with_connection_from_address
|
@try_with_connection_from_address
|
||||||
def on_remote_name_failure(
|
def on_remote_name_failure(
|
||||||
self, connection: Optional[Connection], address: hci.Address, error: int
|
self, connection: Connection | None, address: hci.Address, error: int
|
||||||
):
|
):
|
||||||
if connection:
|
if connection:
|
||||||
connection.emit(connection.EVENT_REMOTE_NAME_FAILURE, error)
|
connection.emit(connection.EVENT_REMOTE_NAME_FAILURE, error)
|
||||||
@@ -6402,7 +6396,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
@host_event_handler
|
@host_event_handler
|
||||||
@try_with_connection_from_address
|
@try_with_connection_from_address
|
||||||
def on_role_change_failure(
|
def on_role_change_failure(
|
||||||
self, connection: Optional[Connection], address: hci.Address, error: int
|
self, connection: Connection | None, address: hci.Address, error: int
|
||||||
):
|
):
|
||||||
if connection:
|
if connection:
|
||||||
connection.emit(connection.EVENT_ROLE_CHANGE_FAILURE, error)
|
connection.emit(connection.EVENT_ROLE_CHANGE_FAILURE, error)
|
||||||
@@ -6426,7 +6420,7 @@ class Device(utils.CompositeEventEmitter):
|
|||||||
def on_pairing(
|
def on_pairing(
|
||||||
self,
|
self,
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
identity_address: Optional[hci.Address],
|
identity_address: hci.Address | None,
|
||||||
keys: PairingKeys,
|
keys: PairingKeys,
|
||||||
sc: bool,
|
sc: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ from __future__ import annotations
|
|||||||
import logging
|
import logging
|
||||||
import pathlib
|
import pathlib
|
||||||
import platform
|
import platform
|
||||||
from typing import TYPE_CHECKING, Iterable, Optional
|
from collections.abc import Iterable
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from bumble.drivers import intel, rtk
|
from bumble.drivers import intel, rtk
|
||||||
from bumble.drivers.common import Driver
|
from bumble.drivers.common import Driver
|
||||||
@@ -41,7 +42,7 @@ logger = logging.getLogger(__name__)
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Functions
|
# Functions
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
async def get_driver_for_host(host: Host) -> Optional[Driver]:
|
async def get_driver_for_host(host: Host) -> Driver | None:
|
||||||
"""Probe diver classes until one returns a valid instance for a host, or none is
|
"""Probe diver classes until one returns a valid instance for a host, or none is
|
||||||
found.
|
found.
|
||||||
If a "driver" HCI metadata entry is present, only that driver class will be probed.
|
If a "driver" HCI metadata entry is present, only that driver class will be probed.
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import os
|
|||||||
import pathlib
|
import pathlib
|
||||||
import platform
|
import platform
|
||||||
import struct
|
import struct
|
||||||
from typing import TYPE_CHECKING, Any, Optional
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from bumble import core, hci, utils
|
from bumble import core, hci, utils
|
||||||
from bumble.drivers import common
|
from bumble.drivers import common
|
||||||
@@ -353,8 +353,8 @@ class Driver(common.Driver):
|
|||||||
self.reset_complete = asyncio.Event()
|
self.reset_complete = asyncio.Event()
|
||||||
|
|
||||||
# Parse configuration options from the driver name.
|
# Parse configuration options from the driver name.
|
||||||
self.ddc_addon: Optional[bytes] = None
|
self.ddc_addon: bytes | None = None
|
||||||
self.ddc_override: Optional[bytes] = None
|
self.ddc_override: bytes | None = None
|
||||||
driver = host.hci_metadata.get("driver")
|
driver = host.hci_metadata.get("driver")
|
||||||
if driver is not None and driver.startswith("intel/"):
|
if driver is not None and driver.startswith("intel/"):
|
||||||
for key, value in [
|
for key, value in [
|
||||||
@@ -602,7 +602,7 @@ class Driver(common.Driver):
|
|||||||
|
|
||||||
await self.load_ddc_if_any(firmware_base_name)
|
await self.load_ddc_if_any(firmware_base_name)
|
||||||
|
|
||||||
async def load_ddc_if_any(self, firmware_base_name: Optional[str] = None) -> None:
|
async def load_ddc_if_any(self, firmware_base_name: str | None = None) -> None:
|
||||||
"""
|
"""
|
||||||
Check for and load any Device Data Configuration (DDC) blobs.
|
Check for and load any Device Data Configuration (DDC) blobs.
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ import enum
|
|||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
from typing import Iterable, Optional, Sequence, TypeVar, Union
|
from collections.abc import Iterable, Sequence
|
||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
from bumble.att import Attribute, AttributeValue
|
from bumble.att import Attribute, AttributeValue
|
||||||
from bumble.colors import color
|
from bumble.colors import color
|
||||||
@@ -355,7 +356,7 @@ class Service(Attribute):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
uuid: Union[str, UUID],
|
uuid: str | UUID,
|
||||||
characteristics: Iterable[Characteristic],
|
characteristics: Iterable[Characteristic],
|
||||||
primary=True,
|
primary=True,
|
||||||
included_services: Iterable[Service] = (),
|
included_services: Iterable[Service] = (),
|
||||||
@@ -378,7 +379,7 @@ class Service(Attribute):
|
|||||||
self.characteristics = list(characteristics)
|
self.characteristics = list(characteristics)
|
||||||
self.primary = primary
|
self.primary = primary
|
||||||
|
|
||||||
def get_advertising_data(self) -> Optional[bytes]:
|
def get_advertising_data(self) -> bytes | None:
|
||||||
"""
|
"""
|
||||||
Get Service specific advertising data
|
Get Service specific advertising data
|
||||||
Defined by each Service, default value is empty
|
Defined by each Service, default value is empty
|
||||||
@@ -502,10 +503,10 @@ class Characteristic(Attribute[_T]):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
uuid: Union[str, bytes, UUID],
|
uuid: str | bytes | UUID,
|
||||||
properties: Characteristic.Properties,
|
properties: Characteristic.Properties,
|
||||||
permissions: Union[str, Attribute.Permissions],
|
permissions: str | Attribute.Permissions,
|
||||||
value: Union[AttributeValue[_T], _T, None] = None,
|
value: AttributeValue[_T] | _T | None = None,
|
||||||
descriptors: Sequence[Descriptor] = (),
|
descriptors: Sequence[Descriptor] = (),
|
||||||
):
|
):
|
||||||
super().__init__(uuid, permissions, value)
|
super().__init__(uuid, permissions, value)
|
||||||
|
|||||||
@@ -22,7 +22,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import struct
|
import struct
|
||||||
from typing import Any, Callable, Generic, Iterable, Literal, Optional, TypeVar
|
from collections.abc import Callable, Iterable
|
||||||
|
from typing import Any, Generic, Literal, TypeVar
|
||||||
|
|
||||||
from bumble import utils
|
from bumble import utils
|
||||||
from bumble.core import InvalidOperationError
|
from bumble.core import InvalidOperationError
|
||||||
@@ -74,8 +75,8 @@ class DelegatedCharacteristicAdapter(CharacteristicAdapter[_T]):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
characteristic: Characteristic,
|
characteristic: Characteristic,
|
||||||
encode: Optional[Callable[[_T], bytes]] = None,
|
encode: Callable[[_T], bytes] | None = None,
|
||||||
decode: Optional[Callable[[bytes], _T]] = None,
|
decode: Callable[[bytes], _T] | None = None,
|
||||||
):
|
):
|
||||||
super().__init__(characteristic)
|
super().__init__(characteristic)
|
||||||
self.encode = encode
|
self.encode = encode
|
||||||
@@ -101,8 +102,8 @@ class DelegatedCharacteristicProxyAdapter(CharacteristicProxyAdapter[_T]):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
characteristic_proxy: CharacteristicProxy,
|
characteristic_proxy: CharacteristicProxy,
|
||||||
encode: Optional[Callable[[_T], bytes]] = None,
|
encode: Callable[[_T], bytes] | None = None,
|
||||||
decode: Optional[Callable[[bytes], _T]] = None,
|
decode: Callable[[bytes], _T] | None = None,
|
||||||
):
|
):
|
||||||
super().__init__(characteristic_proxy)
|
super().__init__(characteristic_proxy)
|
||||||
self.encode = encode
|
self.encode = encode
|
||||||
|
|||||||
@@ -28,16 +28,13 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
|
from collections.abc import Callable, Iterable
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Any,
|
Any,
|
||||||
Callable,
|
|
||||||
Generic,
|
Generic,
|
||||||
Iterable,
|
|
||||||
Optional,
|
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Union,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from bumble import att, core, utils
|
from bumble import att, core, utils
|
||||||
@@ -192,7 +189,7 @@ class CharacteristicProxy(AttributeProxy[_T]):
|
|||||||
self.descriptors_discovered = False
|
self.descriptors_discovered = False
|
||||||
self.subscribers = {} # Map from subscriber to proxy subscriber
|
self.subscribers = {} # Map from subscriber to proxy subscriber
|
||||||
|
|
||||||
def get_descriptor(self, descriptor_type: UUID) -> Optional[DescriptorProxy]:
|
def get_descriptor(self, descriptor_type: UUID) -> DescriptorProxy | None:
|
||||||
for descriptor in self.descriptors:
|
for descriptor in self.descriptors:
|
||||||
if descriptor.type == descriptor_type:
|
if descriptor.type == descriptor_type:
|
||||||
return descriptor
|
return descriptor
|
||||||
@@ -204,7 +201,7 @@ class CharacteristicProxy(AttributeProxy[_T]):
|
|||||||
|
|
||||||
async def subscribe(
|
async def subscribe(
|
||||||
self,
|
self,
|
||||||
subscriber: Optional[Callable[[_T], Any]] = None,
|
subscriber: Callable[[_T], Any] | None = None,
|
||||||
prefer_notify: bool = True,
|
prefer_notify: bool = True,
|
||||||
) -> None:
|
) -> None:
|
||||||
if subscriber is not None:
|
if subscriber is not None:
|
||||||
@@ -253,7 +250,7 @@ class ProfileServiceProxy:
|
|||||||
SERVICE_CLASS: type[TemplateService]
|
SERVICE_CLASS: type[TemplateService]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_client(cls, client: Client) -> Optional[ProfileServiceProxy]:
|
def from_client(cls, client: Client) -> ProfileServiceProxy | None:
|
||||||
return ServiceProxy.from_client(cls, client, cls.SERVICE_CLASS.UUID)
|
return ServiceProxy.from_client(cls, client, cls.SERVICE_CLASS.UUID)
|
||||||
|
|
||||||
|
|
||||||
@@ -264,13 +261,11 @@ class Client:
|
|||||||
services: list[ServiceProxy]
|
services: list[ServiceProxy]
|
||||||
cached_values: dict[int, tuple[datetime, bytes]]
|
cached_values: dict[int, tuple[datetime, bytes]]
|
||||||
notification_subscribers: dict[
|
notification_subscribers: dict[
|
||||||
int, set[Union[CharacteristicProxy, Callable[[bytes], Any]]]
|
int, set[CharacteristicProxy | Callable[[bytes], Any]]
|
||||||
]
|
]
|
||||||
indication_subscribers: dict[
|
indication_subscribers: dict[int, set[CharacteristicProxy | Callable[[bytes], Any]]]
|
||||||
int, set[Union[CharacteristicProxy, Callable[[bytes], Any]]]
|
pending_response: asyncio.futures.Future[att.ATT_PDU] | None
|
||||||
]
|
pending_request: att.ATT_PDU | None
|
||||||
pending_response: Optional[asyncio.futures.Future[att.ATT_PDU]]
|
|
||||||
pending_request: Optional[att.ATT_PDU]
|
|
||||||
|
|
||||||
def __init__(self, connection: Connection) -> None:
|
def __init__(self, connection: Connection) -> None:
|
||||||
self.connection = connection
|
self.connection = connection
|
||||||
@@ -360,7 +355,7 @@ class Client:
|
|||||||
return [service for service in self.services if service.uuid == uuid]
|
return [service for service in self.services if service.uuid == uuid]
|
||||||
|
|
||||||
def get_characteristics_by_uuid(
|
def get_characteristics_by_uuid(
|
||||||
self, uuid: UUID, service: Optional[ServiceProxy] = None
|
self, uuid: UUID, service: ServiceProxy | None = None
|
||||||
) -> list[CharacteristicProxy[bytes]]:
|
) -> list[CharacteristicProxy[bytes]]:
|
||||||
services = [service] if service else self.services
|
services = [service] if service else self.services
|
||||||
return [
|
return [
|
||||||
@@ -369,13 +364,14 @@ class Client:
|
|||||||
if c.uuid == uuid
|
if c.uuid == uuid
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_attribute_grouping(self, attribute_handle: int) -> Optional[
|
def get_attribute_grouping(
|
||||||
Union[
|
self, attribute_handle: int
|
||||||
ServiceProxy,
|
) -> (
|
||||||
tuple[ServiceProxy, CharacteristicProxy],
|
ServiceProxy
|
||||||
tuple[ServiceProxy, CharacteristicProxy, DescriptorProxy],
|
| tuple[ServiceProxy, CharacteristicProxy]
|
||||||
]
|
| tuple[ServiceProxy, CharacteristicProxy, DescriptorProxy]
|
||||||
]:
|
| None
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Get the attribute(s) associated with an attribute handle
|
Get the attribute(s) associated with an attribute handle
|
||||||
"""
|
"""
|
||||||
@@ -478,7 +474,7 @@ class Client:
|
|||||||
|
|
||||||
return services
|
return services
|
||||||
|
|
||||||
async def discover_service(self, uuid: Union[str, UUID]) -> list[ServiceProxy]:
|
async def discover_service(self, uuid: str | UUID) -> list[ServiceProxy]:
|
||||||
'''
|
'''
|
||||||
See Vol 3, Part G - 4.4.2 Discover Primary Service by Service UUID
|
See Vol 3, Part G - 4.4.2 Discover Primary Service by Service UUID
|
||||||
'''
|
'''
|
||||||
@@ -612,7 +608,7 @@ class Client:
|
|||||||
return included_services
|
return included_services
|
||||||
|
|
||||||
async def discover_characteristics(
|
async def discover_characteristics(
|
||||||
self, uuids, service: Optional[ServiceProxy]
|
self, uuids, service: ServiceProxy | None
|
||||||
) -> list[CharacteristicProxy[bytes]]:
|
) -> list[CharacteristicProxy[bytes]]:
|
||||||
'''
|
'''
|
||||||
See Vol 3, Part G - 4.6.1 Discover All Characteristics of a Service and 4.6.2
|
See Vol 3, Part G - 4.6.1 Discover All Characteristics of a Service and 4.6.2
|
||||||
@@ -699,9 +695,9 @@ class Client:
|
|||||||
|
|
||||||
async def discover_descriptors(
|
async def discover_descriptors(
|
||||||
self,
|
self,
|
||||||
characteristic: Optional[CharacteristicProxy] = None,
|
characteristic: CharacteristicProxy | None = None,
|
||||||
start_handle: Optional[int] = None,
|
start_handle: int | None = None,
|
||||||
end_handle: Optional[int] = None,
|
end_handle: int | None = None,
|
||||||
) -> list[DescriptorProxy]:
|
) -> list[DescriptorProxy]:
|
||||||
'''
|
'''
|
||||||
See Vol 3, Part G - 4.7.1 Discover All Characteristic Descriptors
|
See Vol 3, Part G - 4.7.1 Discover All Characteristic Descriptors
|
||||||
@@ -810,7 +806,7 @@ class Client:
|
|||||||
async def subscribe(
|
async def subscribe(
|
||||||
self,
|
self,
|
||||||
characteristic: CharacteristicProxy,
|
characteristic: CharacteristicProxy,
|
||||||
subscriber: Optional[Callable[[Any], Any]] = None,
|
subscriber: Callable[[Any], Any] | None = None,
|
||||||
prefer_notify: bool = True,
|
prefer_notify: bool = True,
|
||||||
) -> None:
|
) -> None:
|
||||||
# If we haven't already discovered the descriptors for this characteristic,
|
# If we haven't already discovered the descriptors for this characteristic,
|
||||||
@@ -860,7 +856,7 @@ class Client:
|
|||||||
async def unsubscribe(
|
async def unsubscribe(
|
||||||
self,
|
self,
|
||||||
characteristic: CharacteristicProxy,
|
characteristic: CharacteristicProxy,
|
||||||
subscriber: Optional[Callable[[Any], Any]] = None,
|
subscriber: Callable[[Any], Any] | None = None,
|
||||||
force: bool = False,
|
force: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
'''
|
'''
|
||||||
@@ -925,7 +921,7 @@ class Client:
|
|||||||
await self.write_value(cccd, b'\x00\x00', with_response=True)
|
await self.write_value(cccd, b'\x00\x00', with_response=True)
|
||||||
|
|
||||||
async def read_value(
|
async def read_value(
|
||||||
self, attribute: Union[int, AttributeProxy], no_long_read: bool = False
|
self, attribute: int | AttributeProxy, no_long_read: bool = False
|
||||||
) -> bytes:
|
) -> bytes:
|
||||||
'''
|
'''
|
||||||
See Vol 3, Part G - 4.8.1 Read Characteristic Value
|
See Vol 3, Part G - 4.8.1 Read Characteristic Value
|
||||||
@@ -980,7 +976,7 @@ class Client:
|
|||||||
return attribute_value
|
return attribute_value
|
||||||
|
|
||||||
async def read_characteristics_by_uuid(
|
async def read_characteristics_by_uuid(
|
||||||
self, uuid: UUID, service: Optional[ServiceProxy]
|
self, uuid: UUID, service: ServiceProxy | None
|
||||||
) -> list[bytes]:
|
) -> list[bytes]:
|
||||||
'''
|
'''
|
||||||
See Vol 3, Part G - 4.8.2 Read Using Characteristic UUID
|
See Vol 3, Part G - 4.8.2 Read Using Characteristic UUID
|
||||||
@@ -1038,7 +1034,7 @@ class Client:
|
|||||||
|
|
||||||
async def write_value(
|
async def write_value(
|
||||||
self,
|
self,
|
||||||
attribute: Union[int, AttributeProxy],
|
attribute: int | AttributeProxy,
|
||||||
value: bytes,
|
value: bytes,
|
||||||
with_response: bool = False,
|
with_response: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import TYPE_CHECKING, Iterable, Optional, TypeVar
|
from collections.abc import Iterable
|
||||||
|
from typing import TYPE_CHECKING, TypeVar
|
||||||
|
|
||||||
from bumble import att, utils
|
from bumble import att, utils
|
||||||
from bumble.colors import color
|
from bumble.colors import color
|
||||||
@@ -73,7 +74,7 @@ class Server(utils.EventEmitter):
|
|||||||
attributes_by_handle: dict[int, att.Attribute]
|
attributes_by_handle: dict[int, att.Attribute]
|
||||||
subscribers: dict[int, dict[int, bytes]]
|
subscribers: dict[int, dict[int, bytes]]
|
||||||
indication_semaphores: defaultdict[int, asyncio.Semaphore]
|
indication_semaphores: defaultdict[int, asyncio.Semaphore]
|
||||||
pending_confirmations: defaultdict[int, Optional[asyncio.futures.Future]]
|
pending_confirmations: defaultdict[int, asyncio.futures.Future | None]
|
||||||
|
|
||||||
EVENT_CHARACTERISTIC_SUBSCRIPTION = "characteristic_subscription"
|
EVENT_CHARACTERISTIC_SUBSCRIPTION = "characteristic_subscription"
|
||||||
|
|
||||||
@@ -109,7 +110,7 @@ class Server(utils.EventEmitter):
|
|||||||
and (data := attribute.get_advertising_data())
|
and (data := attribute.get_advertising_data())
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_attribute(self, handle: int) -> Optional[att.Attribute]:
|
def get_attribute(self, handle: int) -> att.Attribute | None:
|
||||||
attribute = self.attributes_by_handle.get(handle)
|
attribute = self.attributes_by_handle.get(handle)
|
||||||
if attribute:
|
if attribute:
|
||||||
return attribute
|
return attribute
|
||||||
@@ -126,7 +127,7 @@ class Server(utils.EventEmitter):
|
|||||||
|
|
||||||
def get_attribute_group(
|
def get_attribute_group(
|
||||||
self, handle: int, group_type: type[AttributeGroupType]
|
self, handle: int, group_type: type[AttributeGroupType]
|
||||||
) -> Optional[AttributeGroupType]:
|
) -> AttributeGroupType | None:
|
||||||
return next(
|
return next(
|
||||||
(
|
(
|
||||||
attribute
|
attribute
|
||||||
@@ -137,7 +138,7 @@ class Server(utils.EventEmitter):
|
|||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_service_attribute(self, service_uuid: UUID) -> Optional[Service]:
|
def get_service_attribute(self, service_uuid: UUID) -> Service | None:
|
||||||
return next(
|
return next(
|
||||||
(
|
(
|
||||||
attribute
|
attribute
|
||||||
@@ -151,7 +152,7 @@ class Server(utils.EventEmitter):
|
|||||||
|
|
||||||
def get_characteristic_attributes(
|
def get_characteristic_attributes(
|
||||||
self, service_uuid: UUID, characteristic_uuid: UUID
|
self, service_uuid: UUID, characteristic_uuid: UUID
|
||||||
) -> Optional[tuple[CharacteristicDeclaration, Characteristic]]:
|
) -> tuple[CharacteristicDeclaration, Characteristic] | None:
|
||||||
service_handle = self.get_service_attribute(service_uuid)
|
service_handle = self.get_service_attribute(service_uuid)
|
||||||
if not service_handle:
|
if not service_handle:
|
||||||
return None
|
return None
|
||||||
@@ -176,7 +177,7 @@ class Server(utils.EventEmitter):
|
|||||||
|
|
||||||
def get_descriptor_attribute(
|
def get_descriptor_attribute(
|
||||||
self, service_uuid: UUID, characteristic_uuid: UUID, descriptor_uuid: UUID
|
self, service_uuid: UUID, characteristic_uuid: UUID, descriptor_uuid: UUID
|
||||||
) -> Optional[Descriptor]:
|
) -> Descriptor | None:
|
||||||
characteristics = self.get_characteristic_attributes(
|
characteristics = self.get_characteristic_attributes(
|
||||||
service_uuid, characteristic_uuid
|
service_uuid, characteristic_uuid
|
||||||
)
|
)
|
||||||
@@ -334,7 +335,7 @@ class Server(utils.EventEmitter):
|
|||||||
self,
|
self,
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
attribute: att.Attribute,
|
attribute: att.Attribute,
|
||||||
value: Optional[bytes] = None,
|
value: bytes | None = None,
|
||||||
force: bool = False,
|
force: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
# Check if there's a subscriber
|
# Check if there's a subscriber
|
||||||
@@ -377,7 +378,7 @@ class Server(utils.EventEmitter):
|
|||||||
self,
|
self,
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
attribute: att.Attribute,
|
attribute: att.Attribute,
|
||||||
value: Optional[bytes] = None,
|
value: bytes | None = None,
|
||||||
force: bool = False,
|
force: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
# Check if there's a subscriber
|
# Check if there's a subscriber
|
||||||
@@ -437,7 +438,7 @@ class Server(utils.EventEmitter):
|
|||||||
self,
|
self,
|
||||||
indicate: bool,
|
indicate: bool,
|
||||||
attribute: att.Attribute,
|
attribute: att.Attribute,
|
||||||
value: Optional[bytes] = None,
|
value: bytes | None = None,
|
||||||
force: bool = False,
|
force: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
# Get all the connections for which there's at least one subscription
|
# Get all the connections for which there's at least one subscription
|
||||||
@@ -464,7 +465,7 @@ class Server(utils.EventEmitter):
|
|||||||
async def notify_subscribers(
|
async def notify_subscribers(
|
||||||
self,
|
self,
|
||||||
attribute: att.Attribute,
|
attribute: att.Attribute,
|
||||||
value: Optional[bytes] = None,
|
value: bytes | None = None,
|
||||||
force: bool = False,
|
force: bool = False,
|
||||||
):
|
):
|
||||||
return await self._notify_or_indicate_subscribers(
|
return await self._notify_or_indicate_subscribers(
|
||||||
@@ -474,7 +475,7 @@ class Server(utils.EventEmitter):
|
|||||||
async def indicate_subscribers(
|
async def indicate_subscribers(
|
||||||
self,
|
self,
|
||||||
attribute: att.Attribute,
|
attribute: att.Attribute,
|
||||||
value: Optional[bytes] = None,
|
value: bytes | None = None,
|
||||||
force: bool = False,
|
force: bool = False,
|
||||||
):
|
):
|
||||||
return await self._notify_or_indicate_subscribers(True, attribute, value, force)
|
return await self._notify_or_indicate_subscribers(True, attribute, value, force)
|
||||||
|
|||||||
@@ -24,17 +24,13 @@ import functools
|
|||||||
import logging
|
import logging
|
||||||
import secrets
|
import secrets
|
||||||
import struct
|
import struct
|
||||||
from collections.abc import Sequence
|
from collections.abc import Callable, Iterable, Sequence
|
||||||
from dataclasses import field
|
from dataclasses import field
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
Callable,
|
|
||||||
ClassVar,
|
ClassVar,
|
||||||
Iterable,
|
|
||||||
Literal,
|
Literal,
|
||||||
Optional,
|
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Union,
|
|
||||||
cast,
|
cast,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -106,7 +102,7 @@ def map_class_of_device(class_of_device):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def phy_list_to_bits(phys: Optional[Iterable[Phy]]) -> int:
|
def phy_list_to_bits(phys: Iterable[Phy] | None) -> int:
|
||||||
if phys is None:
|
if phys is None:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@@ -184,8 +180,8 @@ class SpecableFlag(enum.IntFlag):
|
|||||||
# - "v" for variable length bytes with a leading length byte
|
# - "v" for variable length bytes with a leading length byte
|
||||||
# - an integer [1, 4] for 1-byte, 2-byte or 4-byte unsigned little-endian integers
|
# - an integer [1, 4] for 1-byte, 2-byte or 4-byte unsigned little-endian integers
|
||||||
# - an integer [-2, -1] for 1-byte, 2-byte signed little-endian integers
|
# - an integer [-2, -1] for 1-byte, 2-byte signed little-endian integers
|
||||||
FieldSpec = Union[dict[str, Any], Callable[[bytes, int], tuple[int, Any]], str, int]
|
FieldSpec = dict[str, Any] | Callable[[bytes, int], tuple[int, Any]] | str | int
|
||||||
Fields = Sequence[Union[tuple[str, FieldSpec], 'Fields']]
|
Fields = Sequence['tuple[str, FieldSpec] | Fields']
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
@@ -2156,7 +2152,7 @@ class Address:
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
address: Union[bytes, str],
|
address: bytes | str,
|
||||||
address_type: AddressType = RANDOM_DEVICE_ADDRESS,
|
address_type: AddressType = RANDOM_DEVICE_ADDRESS,
|
||||||
) -> None:
|
) -> None:
|
||||||
'''
|
'''
|
||||||
@@ -2421,9 +2417,9 @@ class HCI_Command(HCI_Packet):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
parameters: Optional[bytes] = None,
|
parameters: bytes | None = None,
|
||||||
*,
|
*,
|
||||||
op_code: Optional[int] = None,
|
op_code: int | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> None:
|
) -> None:
|
||||||
# op_code should be set in cls.
|
# op_code should be set in cls.
|
||||||
@@ -5714,7 +5710,7 @@ class HCI_Event(HCI_Packet):
|
|||||||
hci_packet_type = HCI_EVENT_PACKET
|
hci_packet_type = HCI_EVENT_PACKET
|
||||||
event_names: dict[int, str] = {}
|
event_names: dict[int, str] = {}
|
||||||
event_classes: dict[int, type[HCI_Event]] = {}
|
event_classes: dict[int, type[HCI_Event]] = {}
|
||||||
vendor_factories: list[Callable[[bytes], Optional[HCI_Event]]] = []
|
vendor_factories: list[Callable[[bytes], HCI_Event | None]] = []
|
||||||
event_code: int
|
event_code: int
|
||||||
fields: Fields = ()
|
fields: Fields = ()
|
||||||
_parameters: bytes = b''
|
_parameters: bytes = b''
|
||||||
@@ -5775,14 +5771,12 @@ class HCI_Event(HCI_Packet):
|
|||||||
return event_class
|
return event_class
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_vendor_factory(
|
def add_vendor_factory(cls, factory: Callable[[bytes], HCI_Event | None]) -> None:
|
||||||
cls, factory: Callable[[bytes], Optional[HCI_Event]]
|
|
||||||
) -> None:
|
|
||||||
cls.vendor_factories.append(factory)
|
cls.vendor_factories.append(factory)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def remove_vendor_factory(
|
def remove_vendor_factory(
|
||||||
cls, factory: Callable[[bytes], Optional[HCI_Event]]
|
cls, factory: Callable[[bytes], HCI_Event | None]
|
||||||
) -> None:
|
) -> None:
|
||||||
if factory in cls.vendor_factories:
|
if factory in cls.vendor_factories:
|
||||||
cls.vendor_factories.remove(factory)
|
cls.vendor_factories.remove(factory)
|
||||||
@@ -5795,7 +5789,7 @@ class HCI_Event(HCI_Packet):
|
|||||||
if len(parameters) != length:
|
if len(parameters) != length:
|
||||||
raise InvalidPacketError('invalid packet length')
|
raise InvalidPacketError('invalid packet length')
|
||||||
|
|
||||||
subclass: Optional[type[HCI_Event]]
|
subclass: type[HCI_Event] | None
|
||||||
if event_code == HCI_LE_META_EVENT:
|
if event_code == HCI_LE_META_EVENT:
|
||||||
# We do this dispatch here and not in the subclass in order to avoid call
|
# We do this dispatch here and not in the subclass in order to avoid call
|
||||||
# loops
|
# loops
|
||||||
@@ -5833,9 +5827,9 @@ class HCI_Event(HCI_Packet):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
parameters: Optional[bytes] = None,
|
parameters: bytes | None = None,
|
||||||
*,
|
*,
|
||||||
event_code: Optional[int] = None,
|
event_code: int | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
if event_code is not None:
|
if event_code is not None:
|
||||||
@@ -5944,9 +5938,7 @@ class HCI_Extended_Event(HCI_Event):
|
|||||||
cls.subevent_names.update(cls.subevent_map(symbols))
|
cls.subevent_names.update(cls.subevent_map(symbols))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def subclass_from_parameters(
|
def subclass_from_parameters(cls, parameters: bytes) -> HCI_Extended_Event | None:
|
||||||
cls, parameters: bytes
|
|
||||||
) -> Optional[HCI_Extended_Event]:
|
|
||||||
"""
|
"""
|
||||||
Factory method that parses the subevent code, finds a registered subclass,
|
Factory method that parses the subevent code, finds a registered subclass,
|
||||||
and creates an instance if found.
|
and creates an instance if found.
|
||||||
@@ -5966,9 +5958,9 @@ class HCI_Extended_Event(HCI_Event):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
parameters: Optional[bytes] = None,
|
parameters: bytes | None = None,
|
||||||
*,
|
*,
|
||||||
subevent_code: Optional[int] = None,
|
subevent_code: int | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> None:
|
) -> None:
|
||||||
if subevent_code is not None:
|
if subevent_code is not None:
|
||||||
@@ -6964,7 +6956,7 @@ class HCI_Command_Complete_Event(HCI_Event):
|
|||||||
command_opcode: int = field(
|
command_opcode: int = field(
|
||||||
metadata=metadata({'size': 2, 'mapper': HCI_Command.command_name})
|
metadata=metadata({'size': 2, 'mapper': HCI_Command.command_name})
|
||||||
)
|
)
|
||||||
return_parameters: Union[bytes, HCI_Object, int] = field(metadata=metadata("*"))
|
return_parameters: bytes | HCI_Object | int = field(metadata=metadata("*"))
|
||||||
|
|
||||||
def map_return_parameters(self, return_parameters):
|
def map_return_parameters(self, return_parameters):
|
||||||
'''Map simple 'status' return parameters to their named constant form'''
|
'''Map simple 'status' return parameters to their named constant form'''
|
||||||
@@ -7548,20 +7540,20 @@ class HCI_IsoDataPacket(HCI_Packet):
|
|||||||
iso_sdu_fragment: bytes
|
iso_sdu_fragment: bytes
|
||||||
pb_flag: int
|
pb_flag: int
|
||||||
ts_flag: int = 0
|
ts_flag: int = 0
|
||||||
time_stamp: Optional[int] = None
|
time_stamp: int | None = None
|
||||||
packet_sequence_number: Optional[int] = None
|
packet_sequence_number: int | None = None
|
||||||
iso_sdu_length: Optional[int] = None
|
iso_sdu_length: int | None = None
|
||||||
packet_status_flag: Optional[int] = None
|
packet_status_flag: int | None = None
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
self.ts_flag = self.time_stamp is not None
|
self.ts_flag = self.time_stamp is not None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_bytes(packet: bytes) -> HCI_IsoDataPacket:
|
def from_bytes(packet: bytes) -> HCI_IsoDataPacket:
|
||||||
time_stamp: Optional[int] = None
|
time_stamp: int | None = None
|
||||||
packet_sequence_number: Optional[int] = None
|
packet_sequence_number: int | None = None
|
||||||
iso_sdu_length: Optional[int] = None
|
iso_sdu_length: int | None = None
|
||||||
packet_status_flag: Optional[int] = None
|
packet_status_flag: int | None = None
|
||||||
|
|
||||||
pos = 1
|
pos = 1
|
||||||
pdu_info, data_total_length = struct.unpack_from('<HH', packet, pos)
|
pdu_info, data_total_length = struct.unpack_from('<HH', packet, pos)
|
||||||
@@ -7644,7 +7636,7 @@ class HCI_IsoDataPacket(HCI_Packet):
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
class HCI_AclDataPacketAssembler:
|
class HCI_AclDataPacketAssembler:
|
||||||
current_data: Optional[bytes]
|
current_data: bytes | None
|
||||||
|
|
||||||
def __init__(self, callback: Callable[[bytes], Any]) -> None:
|
def __init__(self, callback: Callable[[bytes], Any]) -> None:
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from __future__ import annotations
|
|||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
from collections.abc import Callable, MutableMapping
|
from collections.abc import Callable, MutableMapping
|
||||||
from typing import Any, Optional, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from bumble import avc, avctp, avdtp, avrcp, crypto, rfcomm, sdp
|
from bumble import avc, avctp, avdtp, avrcp, crypto, rfcomm, sdp
|
||||||
from bumble.att import ATT_CID, ATT_PDU
|
from bumble.att import ATT_CID, ATT_PDU
|
||||||
@@ -70,7 +70,7 @@ AVCTP_PID_NAMES = {avrcp.AVRCP_PID: 'AVRCP'}
|
|||||||
class PacketTracer:
|
class PacketTracer:
|
||||||
class AclStream:
|
class AclStream:
|
||||||
psms: MutableMapping[int, int]
|
psms: MutableMapping[int, int]
|
||||||
peer: Optional[PacketTracer.AclStream]
|
peer: PacketTracer.AclStream | None
|
||||||
avdtp_assemblers: MutableMapping[int, avdtp.MessageAssembler]
|
avdtp_assemblers: MutableMapping[int, avdtp.MessageAssembler]
|
||||||
avctp_assemblers: MutableMapping[int, avctp.MessageAssembler]
|
avctp_assemblers: MutableMapping[int, avctp.MessageAssembler]
|
||||||
|
|
||||||
@@ -201,7 +201,7 @@ class PacketTracer:
|
|||||||
self.label = label
|
self.label = label
|
||||||
self.emit_message = emit_message
|
self.emit_message = emit_message
|
||||||
self.acl_streams = {} # ACL streams, by connection handle
|
self.acl_streams = {} # ACL streams, by connection handle
|
||||||
self.packet_timestamp: Optional[datetime.datetime] = None
|
self.packet_timestamp: datetime.datetime | None = None
|
||||||
|
|
||||||
def start_acl_stream(self, connection_handle: int) -> PacketTracer.AclStream:
|
def start_acl_stream(self, connection_handle: int) -> PacketTracer.AclStream:
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -230,7 +230,7 @@ class PacketTracer:
|
|||||||
self.peer.end_acl_stream(connection_handle)
|
self.peer.end_acl_stream(connection_handle)
|
||||||
|
|
||||||
def on_packet(
|
def on_packet(
|
||||||
self, timestamp: Optional[datetime.datetime], packet: HCI_Packet
|
self, timestamp: datetime.datetime | None, packet: HCI_Packet
|
||||||
) -> None:
|
) -> None:
|
||||||
self.packet_timestamp = timestamp
|
self.packet_timestamp = timestamp
|
||||||
self.emit(packet)
|
self.emit(packet)
|
||||||
@@ -262,7 +262,7 @@ class PacketTracer:
|
|||||||
self,
|
self,
|
||||||
packet: HCI_Packet,
|
packet: HCI_Packet,
|
||||||
direction: int = 0,
|
direction: int = 0,
|
||||||
timestamp: Optional[datetime.datetime] = None,
|
timestamp: datetime.datetime | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if direction == 0:
|
if direction == 0:
|
||||||
self.host_to_controller_analyzer.on_packet(timestamp, packet)
|
self.host_to_controller_analyzer.on_packet(timestamp, packet)
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ import enum
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import traceback
|
import traceback
|
||||||
from typing import TYPE_CHECKING, Any, ClassVar, Iterable, Optional, Union
|
from collections.abc import Iterable
|
||||||
|
from typing import TYPE_CHECKING, Any, ClassVar
|
||||||
|
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
@@ -80,7 +81,7 @@ class HfpProtocol:
|
|||||||
|
|
||||||
dlc.sink = self.feed
|
dlc.sink = self.feed
|
||||||
|
|
||||||
def feed(self, data: Union[bytes, str]) -> None:
|
def feed(self, data: bytes | str) -> None:
|
||||||
# Convert the data to a string if needed
|
# Convert the data to a string if needed
|
||||||
if isinstance(data, bytes):
|
if isinstance(data, bytes):
|
||||||
data = data.decode('utf-8')
|
data = data.decode('utf-8')
|
||||||
@@ -324,8 +325,8 @@ class CallInfo:
|
|||||||
status: CallInfoStatus
|
status: CallInfoStatus
|
||||||
mode: CallInfoMode
|
mode: CallInfoMode
|
||||||
multi_party: CallInfoMultiParty
|
multi_party: CallInfoMultiParty
|
||||||
number: Optional[str] = None
|
number: str | None = None
|
||||||
type: Optional[int] = None
|
type: int | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
@@ -353,10 +354,10 @@ class CallLineIdentification:
|
|||||||
|
|
||||||
number: str
|
number: str
|
||||||
type: int
|
type: int
|
||||||
subaddr: Optional[str] = None
|
subaddr: str | None = None
|
||||||
satype: Optional[int] = None
|
satype: int | None = None
|
||||||
alpha: Optional[str] = None
|
alpha: str | None = None
|
||||||
cli_validity: Optional[int] = None
|
cli_validity: int | None = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_from(cls, parameters: list[bytes]) -> Self:
|
def parse_from(cls, parameters: list[bytes]) -> Self:
|
||||||
@@ -584,7 +585,7 @@ class AgIndicatorState:
|
|||||||
indicator: AgIndicator
|
indicator: AgIndicator
|
||||||
supported_values: set[int]
|
supported_values: set[int]
|
||||||
current_status: int
|
current_status: int
|
||||||
index: Optional[int] = None
|
index: int | None = None
|
||||||
enabled: bool = True
|
enabled: bool = True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -728,7 +729,7 @@ class HfProtocol(utils.EventEmitter):
|
|||||||
command_lock: asyncio.Lock
|
command_lock: asyncio.Lock
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
response_queue: asyncio.Queue[AtResponse]
|
response_queue: asyncio.Queue[AtResponse]
|
||||||
unsolicited_queue: asyncio.Queue[Optional[AtResponse]]
|
unsolicited_queue: asyncio.Queue[AtResponse | None]
|
||||||
else:
|
else:
|
||||||
response_queue: asyncio.Queue
|
response_queue: asyncio.Queue
|
||||||
unsolicited_queue: asyncio.Queue
|
unsolicited_queue: asyncio.Queue
|
||||||
@@ -820,7 +821,7 @@ class HfProtocol(utils.EventEmitter):
|
|||||||
cmd: str,
|
cmd: str,
|
||||||
timeout: float = 1.0,
|
timeout: float = 1.0,
|
||||||
response_type: AtResponseType = AtResponseType.NONE,
|
response_type: AtResponseType = AtResponseType.NONE,
|
||||||
) -> Union[None, AtResponse, list[AtResponse]]:
|
) -> None | AtResponse | list[AtResponse]:
|
||||||
"""
|
"""
|
||||||
Sends an AT command and wait for the peer response.
|
Sends an AT command and wait for the peer response.
|
||||||
Wait for the AT responses sent by the peer, to the status code.
|
Wait for the AT responses sent by the peer, to the status code.
|
||||||
@@ -1411,7 +1412,7 @@ class AgProtocol(utils.EventEmitter):
|
|||||||
self.emit(self.EVENT_VOICE_RECOGNITION, VoiceRecognitionState(int(vrec)))
|
self.emit(self.EVENT_VOICE_RECOGNITION, VoiceRecognitionState(int(vrec)))
|
||||||
|
|
||||||
def _on_chld(self, operation_code: bytes) -> None:
|
def _on_chld(self, operation_code: bytes) -> None:
|
||||||
call_index: Optional[int] = None
|
call_index: int | None = None
|
||||||
if len(operation_code) > 1:
|
if len(operation_code) > 1:
|
||||||
call_index = int(operation_code[1:])
|
call_index = int(operation_code[1:])
|
||||||
operation_code = operation_code[:1] + b'x'
|
operation_code = operation_code[:1] + b'x'
|
||||||
@@ -1481,8 +1482,8 @@ class AgProtocol(utils.EventEmitter):
|
|||||||
def _on_cmer(
|
def _on_cmer(
|
||||||
self,
|
self,
|
||||||
mode: bytes,
|
mode: bytes,
|
||||||
keypad: Optional[bytes] = None,
|
keypad: bytes | None = None,
|
||||||
display: Optional[bytes] = None,
|
display: bytes | None = None,
|
||||||
indicator: bytes = b'',
|
indicator: bytes = b'',
|
||||||
) -> None:
|
) -> None:
|
||||||
if (
|
if (
|
||||||
@@ -1844,7 +1845,7 @@ def make_ag_sdp_records(
|
|||||||
|
|
||||||
async def find_hf_sdp_record(
|
async def find_hf_sdp_record(
|
||||||
connection: device.Connection,
|
connection: device.Connection,
|
||||||
) -> Optional[tuple[int, ProfileVersion, HfSdpFeature]]:
|
) -> tuple[int, ProfileVersion, HfSdpFeature] | None:
|
||||||
"""Searches a Hands-Free SDP record from remote device.
|
"""Searches a Hands-Free SDP record from remote device.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -1864,9 +1865,9 @@ async def find_hf_sdp_record(
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
for attribute_lists in search_result:
|
for attribute_lists in search_result:
|
||||||
channel: Optional[int] = None
|
channel: int | None = None
|
||||||
version: Optional[ProfileVersion] = None
|
version: ProfileVersion | None = None
|
||||||
features: Optional[HfSdpFeature] = None
|
features: HfSdpFeature | None = None
|
||||||
for attribute in attribute_lists:
|
for attribute in attribute_lists:
|
||||||
# The layout is [[L2CAP_PROTOCOL], [RFCOMM_PROTOCOL, RFCOMM_CHANNEL]].
|
# The layout is [[L2CAP_PROTOCOL], [RFCOMM_PROTOCOL, RFCOMM_CHANNEL]].
|
||||||
if attribute.id == sdp.SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID:
|
if attribute.id == sdp.SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID:
|
||||||
@@ -1896,7 +1897,7 @@ async def find_hf_sdp_record(
|
|||||||
|
|
||||||
async def find_ag_sdp_record(
|
async def find_ag_sdp_record(
|
||||||
connection: device.Connection,
|
connection: device.Connection,
|
||||||
) -> Optional[tuple[int, ProfileVersion, AgSdpFeature]]:
|
) -> tuple[int, ProfileVersion, AgSdpFeature] | None:
|
||||||
"""Searches an Audio-Gateway SDP record from remote device.
|
"""Searches an Audio-Gateway SDP record from remote device.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -1915,9 +1916,9 @@ async def find_ag_sdp_record(
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
for attribute_lists in search_result:
|
for attribute_lists in search_result:
|
||||||
channel: Optional[int] = None
|
channel: int | None = None
|
||||||
version: Optional[ProfileVersion] = None
|
version: ProfileVersion | None = None
|
||||||
features: Optional[AgSdpFeature] = None
|
features: AgSdpFeature | None = None
|
||||||
for attribute in attribute_lists:
|
for attribute in attribute_lists:
|
||||||
# The layout is [[L2CAP_PROTOCOL], [RFCOMM_PROTOCOL, RFCOMM_CHANNEL]].
|
# The layout is [[L2CAP_PROTOCOL], [RFCOMM_PROTOCOL, RFCOMM_CHANNEL]].
|
||||||
if attribute.id == sdp.SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID:
|
if attribute.id == sdp.SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID:
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import enum
|
|||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Callable, Optional
|
|
||||||
|
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
|
|
||||||
@@ -195,9 +195,9 @@ class SendHandshakeMessage(Message):
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
class HID(ABC, utils.EventEmitter):
|
class HID(ABC, utils.EventEmitter):
|
||||||
l2cap_ctrl_channel: Optional[l2cap.ClassicChannel] = None
|
l2cap_ctrl_channel: l2cap.ClassicChannel | None = None
|
||||||
l2cap_intr_channel: Optional[l2cap.ClassicChannel] = None
|
l2cap_intr_channel: l2cap.ClassicChannel | None = None
|
||||||
connection: Optional[device.Connection] = None
|
connection: device.Connection | None = None
|
||||||
|
|
||||||
EVENT_INTERRUPT_DATA = "interrupt_data"
|
EVENT_INTERRUPT_DATA = "interrupt_data"
|
||||||
EVENT_CONTROL_DATA = "control_data"
|
EVENT_CONTROL_DATA = "control_data"
|
||||||
@@ -212,7 +212,7 @@ class HID(ABC, utils.EventEmitter):
|
|||||||
|
|
||||||
def __init__(self, device: device.Device, role: Role) -> None:
|
def __init__(self, device: device.Device, role: Role) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.remote_device_bd_address: Optional[Address] = None
|
self.remote_device_bd_address: Address | None = None
|
||||||
self.device = device
|
self.device = device
|
||||||
self.role = role
|
self.role = role
|
||||||
|
|
||||||
@@ -353,10 +353,10 @@ class Device(HID):
|
|||||||
data: bytes = b''
|
data: bytes = b''
|
||||||
status: int = 0
|
status: int = 0
|
||||||
|
|
||||||
get_report_cb: Optional[Callable[[int, int, int], GetSetStatus]] = None
|
get_report_cb: Callable[[int, int, int], GetSetStatus] | None = None
|
||||||
set_report_cb: Optional[Callable[[int, int, int, bytes], GetSetStatus]] = None
|
set_report_cb: Callable[[int, int, int, bytes], GetSetStatus] | None = None
|
||||||
get_protocol_cb: Optional[Callable[[], GetSetStatus]] = None
|
get_protocol_cb: Callable[[], GetSetStatus] | None = None
|
||||||
set_protocol_cb: Optional[Callable[[int], GetSetStatus]] = None
|
set_protocol_cb: Callable[[int], GetSetStatus] | None = None
|
||||||
|
|
||||||
def __init__(self, device: device.Device) -> None:
|
def __init__(self, device: device.Device) -> None:
|
||||||
super().__init__(device, HID.Role.DEVICE)
|
super().__init__(device, HID.Role.DEVICE)
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ import collections
|
|||||||
import dataclasses
|
import dataclasses
|
||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Optional, Union, cast
|
from collections.abc import Awaitable, Callable
|
||||||
|
from typing import TYPE_CHECKING, Any, cast
|
||||||
|
|
||||||
from bumble import drivers, hci, utils
|
from bumble import drivers, hci, utils
|
||||||
from bumble.colors import color
|
from bumble.colors import color
|
||||||
@@ -198,7 +199,7 @@ class Connection:
|
|||||||
self.peer_address = peer_address
|
self.peer_address = peer_address
|
||||||
self.assembler = hci.HCI_AclDataPacketAssembler(self.on_acl_pdu)
|
self.assembler = hci.HCI_AclDataPacketAssembler(self.on_acl_pdu)
|
||||||
self.transport = transport
|
self.transport = transport
|
||||||
acl_packet_queue: Optional[DataPacketQueue] = (
|
acl_packet_queue: DataPacketQueue | None = (
|
||||||
host.le_acl_packet_queue
|
host.le_acl_packet_queue
|
||||||
if transport == PhysicalTransport.LE
|
if transport == PhysicalTransport.LE
|
||||||
else host.acl_packet_queue
|
else host.acl_packet_queue
|
||||||
@@ -241,20 +242,18 @@ class Host(utils.EventEmitter):
|
|||||||
bis_links: dict[int, IsoLink]
|
bis_links: dict[int, IsoLink]
|
||||||
sco_links: dict[int, ScoLink]
|
sco_links: dict[int, ScoLink]
|
||||||
bigs: dict[int, set[int]]
|
bigs: dict[int, set[int]]
|
||||||
acl_packet_queue: Optional[DataPacketQueue] = None
|
acl_packet_queue: DataPacketQueue | None = None
|
||||||
le_acl_packet_queue: Optional[DataPacketQueue] = None
|
le_acl_packet_queue: DataPacketQueue | None = None
|
||||||
iso_packet_queue: Optional[DataPacketQueue] = None
|
iso_packet_queue: DataPacketQueue | None = None
|
||||||
hci_sink: Optional[TransportSink] = None
|
hci_sink: TransportSink | None = None
|
||||||
hci_metadata: dict[str, Any]
|
hci_metadata: dict[str, Any]
|
||||||
long_term_key_provider: Optional[
|
long_term_key_provider: Callable[[int, bytes, int], Awaitable[bytes | None]] | None
|
||||||
Callable[[int, bytes, int], Awaitable[Optional[bytes]]]
|
link_key_provider: Callable[[hci.Address], Awaitable[bytes | None]] | None
|
||||||
]
|
|
||||||
link_key_provider: Optional[Callable[[hci.Address], Awaitable[Optional[bytes]]]]
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
controller_source: Optional[TransportSource] = None,
|
controller_source: TransportSource | None = None,
|
||||||
controller_sink: Optional[TransportSink] = None,
|
controller_sink: TransportSink | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
@@ -266,7 +265,7 @@ class Host(utils.EventEmitter):
|
|||||||
self.sco_links = {} # SCO links, by connection handle
|
self.sco_links = {} # SCO links, by connection handle
|
||||||
self.bigs = {} # BIG Handle to BIS Handles
|
self.bigs = {} # BIG Handle to BIS Handles
|
||||||
self.pending_command = None
|
self.pending_command = None
|
||||||
self.pending_response: Optional[asyncio.Future[Any]] = None
|
self.pending_response: asyncio.Future[Any] | None = None
|
||||||
self.number_of_supported_advertising_sets = 0
|
self.number_of_supported_advertising_sets = 0
|
||||||
self.maximum_advertising_data_length = 31
|
self.maximum_advertising_data_length = 31
|
||||||
self.local_version = None
|
self.local_version = None
|
||||||
@@ -279,7 +278,7 @@ class Host(utils.EventEmitter):
|
|||||||
self.long_term_key_provider = None
|
self.long_term_key_provider = None
|
||||||
self.link_key_provider = None
|
self.link_key_provider = None
|
||||||
self.pairing_io_capability_provider = None # Classic only
|
self.pairing_io_capability_provider = None # Classic only
|
||||||
self.snooper: Optional[Snooper] = None
|
self.snooper: Snooper | None = None
|
||||||
|
|
||||||
# Connect to the source and sink if specified
|
# Connect to the source and sink if specified
|
||||||
if controller_source:
|
if controller_source:
|
||||||
@@ -290,9 +289,9 @@ class Host(utils.EventEmitter):
|
|||||||
def find_connection_by_bd_addr(
|
def find_connection_by_bd_addr(
|
||||||
self,
|
self,
|
||||||
bd_addr: hci.Address,
|
bd_addr: hci.Address,
|
||||||
transport: Optional[int] = None,
|
transport: int | None = None,
|
||||||
check_address_type: bool = False,
|
check_address_type: bool = False,
|
||||||
) -> Optional[Connection]:
|
) -> Connection | None:
|
||||||
for connection in self.connections.values():
|
for connection in self.connections.values():
|
||||||
if bytes(connection.peer_address) == bytes(bd_addr):
|
if bytes(connection.peer_address) == bytes(bd_addr):
|
||||||
if (
|
if (
|
||||||
@@ -632,7 +631,7 @@ class Host(utils.EventEmitter):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def controller(self) -> Optional[TransportSink]:
|
def controller(self) -> TransportSink | None:
|
||||||
return self.hci_sink
|
return self.hci_sink
|
||||||
|
|
||||||
@controller.setter
|
@controller.setter
|
||||||
@@ -641,7 +640,7 @@ class Host(utils.EventEmitter):
|
|||||||
if controller:
|
if controller:
|
||||||
self.set_packet_source(controller)
|
self.set_packet_source(controller)
|
||||||
|
|
||||||
def set_packet_sink(self, sink: Optional[TransportSink]) -> None:
|
def set_packet_sink(self, sink: TransportSink | None) -> None:
|
||||||
self.hci_sink = sink
|
self.hci_sink = sink
|
||||||
|
|
||||||
def set_packet_source(self, source: TransportSource) -> None:
|
def set_packet_source(self, source: TransportSource) -> None:
|
||||||
@@ -656,7 +655,7 @@ class Host(utils.EventEmitter):
|
|||||||
self.hci_sink.on_packet(bytes(packet))
|
self.hci_sink.on_packet(bytes(packet))
|
||||||
|
|
||||||
async def send_command(
|
async def send_command(
|
||||||
self, command, check_result=False, response_timeout: Optional[int] = None
|
self, command, check_result=False, response_timeout: int | None = None
|
||||||
):
|
):
|
||||||
# Wait until we can send (only one pending command at a time)
|
# Wait until we can send (only one pending command at a time)
|
||||||
async with self.command_semaphore:
|
async with self.command_semaphore:
|
||||||
@@ -899,7 +898,7 @@ class Host(utils.EventEmitter):
|
|||||||
self.emit('l2cap_pdu', connection.handle, cid, pdu)
|
self.emit('l2cap_pdu', connection.handle, cid, pdu)
|
||||||
|
|
||||||
def on_command_processed(
|
def on_command_processed(
|
||||||
self, event: Union[hci.HCI_Command_Complete_Event, hci.HCI_Command_Status_Event]
|
self, event: hci.HCI_Command_Complete_Event | hci.HCI_Command_Status_Event
|
||||||
):
|
):
|
||||||
if self.pending_response:
|
if self.pending_response:
|
||||||
# Check that it is what we were expecting
|
# Check that it is what we were expecting
|
||||||
@@ -962,11 +961,11 @@ class Host(utils.EventEmitter):
|
|||||||
|
|
||||||
def on_hci_le_connection_complete_event(
|
def on_hci_le_connection_complete_event(
|
||||||
self,
|
self,
|
||||||
event: Union[
|
event: (
|
||||||
hci.HCI_LE_Connection_Complete_Event,
|
hci.HCI_LE_Connection_Complete_Event
|
||||||
hci.HCI_LE_Enhanced_Connection_Complete_Event,
|
| hci.HCI_LE_Enhanced_Connection_Complete_Event
|
||||||
hci.HCI_LE_Enhanced_Connection_Complete_V2_Event,
|
| hci.HCI_LE_Enhanced_Connection_Complete_V2_Event
|
||||||
],
|
),
|
||||||
):
|
):
|
||||||
# Check if this is a cancellation
|
# Check if this is a cancellation
|
||||||
if event.status == hci.HCI_SUCCESS:
|
if event.status == hci.HCI_SUCCESS:
|
||||||
@@ -1011,10 +1010,10 @@ class Host(utils.EventEmitter):
|
|||||||
|
|
||||||
def on_hci_le_enhanced_connection_complete_event(
|
def on_hci_le_enhanced_connection_complete_event(
|
||||||
self,
|
self,
|
||||||
event: Union[
|
event: (
|
||||||
hci.HCI_LE_Enhanced_Connection_Complete_Event,
|
hci.HCI_LE_Enhanced_Connection_Complete_Event
|
||||||
hci.HCI_LE_Enhanced_Connection_Complete_V2_Event,
|
| hci.HCI_LE_Enhanced_Connection_Complete_V2_Event
|
||||||
],
|
),
|
||||||
):
|
):
|
||||||
# Just use the same implementation as for the non-enhanced event for now
|
# Just use the same implementation as for the non-enhanced event for now
|
||||||
self.on_hci_le_connection_complete_event(event)
|
self.on_hci_le_connection_complete_event(event)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import dataclasses
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from typing import TYPE_CHECKING, Any, Optional
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
@@ -51,8 +51,8 @@ class PairingKeys:
|
|||||||
class Key:
|
class Key:
|
||||||
value: bytes
|
value: bytes
|
||||||
authenticated: bool = False
|
authenticated: bool = False
|
||||||
ediv: Optional[int] = None
|
ediv: int | None = None
|
||||||
rand: Optional[bytes] = None
|
rand: bytes | None = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, key_dict: dict[str, Any]) -> PairingKeys.Key:
|
def from_dict(cls, key_dict: dict[str, Any]) -> PairingKeys.Key:
|
||||||
@@ -74,17 +74,17 @@ class PairingKeys:
|
|||||||
|
|
||||||
return key_dict
|
return key_dict
|
||||||
|
|
||||||
address_type: Optional[hci.AddressType] = None
|
address_type: hci.AddressType | None = None
|
||||||
ltk: Optional[Key] = None
|
ltk: Key | None = None
|
||||||
ltk_central: Optional[Key] = None
|
ltk_central: Key | None = None
|
||||||
ltk_peripheral: Optional[Key] = None
|
ltk_peripheral: Key | None = None
|
||||||
irk: Optional[Key] = None
|
irk: Key | None = None
|
||||||
csrk: Optional[Key] = None
|
csrk: Key | None = None
|
||||||
link_key: Optional[Key] = None # Classic
|
link_key: Key | None = None # Classic
|
||||||
link_key_type: Optional[int] = None # Classic
|
link_key_type: int | None = None # Classic
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def key_from_dict(cls, keys_dict: dict[str, Any], key_name: str) -> Optional[Key]:
|
def key_from_dict(cls, keys_dict: dict[str, Any], key_name: str) -> Key | None:
|
||||||
key_dict = keys_dict.get(key_name)
|
key_dict = keys_dict.get(key_name)
|
||||||
if key_dict is None:
|
if key_dict is None:
|
||||||
return None
|
return None
|
||||||
@@ -156,7 +156,7 @@ class KeyStore:
|
|||||||
async def update(self, name: str, keys: PairingKeys) -> None:
|
async def update(self, name: str, keys: PairingKeys) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def get(self, _name: str) -> Optional[PairingKeys]:
|
async def get(self, _name: str) -> PairingKeys | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_all(self) -> list[tuple[str, PairingKeys]]:
|
async def get_all(self) -> list[tuple[str, PairingKeys]]:
|
||||||
@@ -274,7 +274,7 @@ class JsonKeyStore(KeyStore):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_device(
|
def from_device(
|
||||||
cls: type[Self], device: Device, filename: Optional[str] = None
|
cls: type[Self], device: Device, filename: str | None = None
|
||||||
) -> Self:
|
) -> Self:
|
||||||
if not filename:
|
if not filename:
|
||||||
# Extract the filename from the config if there is one
|
# Extract the filename from the config if there is one
|
||||||
@@ -297,7 +297,7 @@ class JsonKeyStore(KeyStore):
|
|||||||
# Try to open the file, without failing. If the file does not exist, it
|
# Try to open the file, without failing. If the file does not exist, it
|
||||||
# will be created upon saving.
|
# will be created upon saving.
|
||||||
try:
|
try:
|
||||||
with open(self.filename, 'r', encoding='utf-8') as json_file:
|
with open(self.filename, encoding='utf-8') as json_file:
|
||||||
db = json.load(json_file)
|
db = json.load(json_file)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
db = {}
|
db = {}
|
||||||
@@ -348,7 +348,7 @@ class JsonKeyStore(KeyStore):
|
|||||||
key_map.clear()
|
key_map.clear()
|
||||||
await self.save(db)
|
await self.save(db)
|
||||||
|
|
||||||
async def get(self, name: str) -> Optional[PairingKeys]:
|
async def get(self, name: str) -> PairingKeys | None:
|
||||||
_, key_map = await self.load()
|
_, key_map = await self.load()
|
||||||
if name not in key_map:
|
if name not in key_map:
|
||||||
return None
|
return None
|
||||||
@@ -370,7 +370,7 @@ class MemoryKeyStore(KeyStore):
|
|||||||
async def update(self, name: str, keys: PairingKeys) -> None:
|
async def update(self, name: str, keys: PairingKeys) -> None:
|
||||||
self.all_keys[name] = keys
|
self.all_keys[name] = keys
|
||||||
|
|
||||||
async def get(self, name: str) -> Optional[PairingKeys]:
|
async def get(self, name: str) -> PairingKeys | None:
|
||||||
return self.all_keys.get(name)
|
return self.all_keys.get(name)
|
||||||
|
|
||||||
async def get_all(self) -> list[tuple[str, PairingKeys]]:
|
async def get_all(self) -> list[tuple[str, PairingKeys]]:
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import logging
|
|||||||
import struct
|
import struct
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from collections.abc import Callable, Iterable, Sequence
|
from collections.abc import Callable, Iterable, Sequence
|
||||||
from typing import TYPE_CHECKING, Any, ClassVar, Optional, SupportsBytes, TypeVar, Union
|
from typing import TYPE_CHECKING, Any, ClassVar, SupportsBytes, TypeVar
|
||||||
|
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ class ClassicChannelSpec:
|
|||||||
fcs_enabled: Whether to enable FCS (Frame Check Sequence).
|
fcs_enabled: Whether to enable FCS (Frame Check Sequence).
|
||||||
'''
|
'''
|
||||||
|
|
||||||
psm: Optional[int] = None
|
psm: int | None = None
|
||||||
mtu: int = L2CAP_DEFAULT_MTU
|
mtu: int = L2CAP_DEFAULT_MTU
|
||||||
mps: int = L2CAP_DEFAULT_MPS
|
mps: int = L2CAP_DEFAULT_MPS
|
||||||
tx_window_size: int = DEFAULT_TX_WINDOW_SIZE
|
tx_window_size: int = DEFAULT_TX_WINDOW_SIZE
|
||||||
@@ -188,7 +188,7 @@ class ClassicChannelSpec:
|
|||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class LeCreditBasedChannelSpec:
|
class LeCreditBasedChannelSpec:
|
||||||
psm: Optional[int] = None
|
psm: int | None = None
|
||||||
mtu: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MTU
|
mtu: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MTU
|
||||||
mps: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS
|
mps: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS
|
||||||
max_credits: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_INITIAL_CREDITS
|
max_credits: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_INITIAL_CREDITS
|
||||||
@@ -372,7 +372,7 @@ class L2CAP_Control_Frame:
|
|||||||
fields: ClassVar[hci.Fields] = ()
|
fields: ClassVar[hci.Fields] = ()
|
||||||
code: int = dataclasses.field(default=0, init=False)
|
code: int = dataclasses.field(default=0, init=False)
|
||||||
name: str = dataclasses.field(default='', init=False)
|
name: str = dataclasses.field(default='', init=False)
|
||||||
_payload: Optional[bytes] = dataclasses.field(default=None, init=False)
|
_payload: bytes | None = dataclasses.field(default=None, init=False)
|
||||||
|
|
||||||
identifier: int
|
identifier: int
|
||||||
|
|
||||||
@@ -910,8 +910,8 @@ class EnhancedRetransmissionProcessor(Processor):
|
|||||||
|
|
||||||
_num_receiver_ready_polls_sent: int = 0
|
_num_receiver_ready_polls_sent: int = 0
|
||||||
_pending_pdus: list[_PendingPdu]
|
_pending_pdus: list[_PendingPdu]
|
||||||
_monitor_handle: Optional[asyncio.TimerHandle] = None
|
_monitor_handle: asyncio.TimerHandle | None = None
|
||||||
_receiver_ready_poll_handle: Optional[asyncio.TimerHandle] = None
|
_receiver_ready_poll_handle: asyncio.TimerHandle | None = None
|
||||||
|
|
||||||
# Timeout, in seconds.
|
# Timeout, in seconds.
|
||||||
monitor_timeout: float
|
monitor_timeout: float
|
||||||
@@ -1109,10 +1109,10 @@ class ClassicChannel(utils.EventEmitter):
|
|||||||
EVENT_OPEN = "open"
|
EVENT_OPEN = "open"
|
||||||
EVENT_CLOSE = "close"
|
EVENT_CLOSE = "close"
|
||||||
|
|
||||||
connection_result: Optional[asyncio.Future[None]]
|
connection_result: asyncio.Future[None] | None
|
||||||
disconnection_result: Optional[asyncio.Future[None]]
|
disconnection_result: asyncio.Future[None] | None
|
||||||
response: Optional[asyncio.Future[bytes]]
|
response: asyncio.Future[bytes] | None
|
||||||
sink: Optional[Callable[[bytes], Any]]
|
sink: Callable[[bytes], Any] | None
|
||||||
state: State
|
state: State
|
||||||
connection: Connection
|
connection: Connection
|
||||||
mtu: int
|
mtu: int
|
||||||
@@ -1159,7 +1159,7 @@ class ClassicChannel(utils.EventEmitter):
|
|||||||
def write(self, sdu: bytes) -> None:
|
def write(self, sdu: bytes) -> None:
|
||||||
self.processor.send_sdu(sdu)
|
self.processor.send_sdu(sdu)
|
||||||
|
|
||||||
def send_pdu(self, pdu: Union[SupportsBytes, bytes]) -> None:
|
def send_pdu(self, pdu: SupportsBytes | bytes) -> None:
|
||||||
if self.state != self.State.OPEN:
|
if self.state != self.State.OPEN:
|
||||||
raise InvalidStateError('channel not open')
|
raise InvalidStateError('channel not open')
|
||||||
self.manager.send_pdu(
|
self.manager.send_pdu(
|
||||||
@@ -1542,13 +1542,13 @@ class LeCreditBasedChannel(utils.EventEmitter):
|
|||||||
CONNECTION_ERROR = 5
|
CONNECTION_ERROR = 5
|
||||||
|
|
||||||
out_queue: deque[bytes]
|
out_queue: deque[bytes]
|
||||||
connection_result: Optional[asyncio.Future[LeCreditBasedChannel]]
|
connection_result: asyncio.Future[LeCreditBasedChannel] | None
|
||||||
disconnection_result: Optional[asyncio.Future[None]]
|
disconnection_result: asyncio.Future[None] | None
|
||||||
in_sdu: Optional[bytes]
|
in_sdu: bytes | None
|
||||||
out_sdu: Optional[bytes]
|
out_sdu: bytes | None
|
||||||
state: State
|
state: State
|
||||||
connection: Connection
|
connection: Connection
|
||||||
sink: Optional[Callable[[bytes], Any]]
|
sink: Callable[[bytes], Any] | None
|
||||||
|
|
||||||
EVENT_OPEN = "open"
|
EVENT_OPEN = "open"
|
||||||
EVENT_CLOSE = "close"
|
EVENT_CLOSE = "close"
|
||||||
@@ -1608,7 +1608,7 @@ class LeCreditBasedChannel(utils.EventEmitter):
|
|||||||
elif new_state == self.State.DISCONNECTED:
|
elif new_state == self.State.DISCONNECTED:
|
||||||
self.emit(self.EVENT_CLOSE)
|
self.emit(self.EVENT_CLOSE)
|
||||||
|
|
||||||
def send_pdu(self, pdu: Union[SupportsBytes, bytes]) -> None:
|
def send_pdu(self, pdu: SupportsBytes | bytes) -> None:
|
||||||
self.manager.send_pdu(self.connection, self.destination_cid, pdu)
|
self.manager.send_pdu(self.connection, self.destination_cid, pdu)
|
||||||
|
|
||||||
def send_control_frame(self, frame: L2CAP_Control_Frame) -> None:
|
def send_control_frame(self, frame: L2CAP_Control_Frame) -> None:
|
||||||
@@ -1913,7 +1913,7 @@ class ClassicChannelServer(utils.EventEmitter):
|
|||||||
self,
|
self,
|
||||||
manager: ChannelManager,
|
manager: ChannelManager,
|
||||||
psm: int,
|
psm: int,
|
||||||
handler: Optional[Callable[[ClassicChannel], Any]],
|
handler: Callable[[ClassicChannel], Any] | None,
|
||||||
spec: ClassicChannelSpec,
|
spec: ClassicChannelSpec,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -1940,7 +1940,7 @@ class LeCreditBasedChannelServer(utils.EventEmitter):
|
|||||||
self,
|
self,
|
||||||
manager: ChannelManager,
|
manager: ChannelManager,
|
||||||
psm: int,
|
psm: int,
|
||||||
handler: Optional[Callable[[LeCreditBasedChannel], Any]],
|
handler: Callable[[LeCreditBasedChannel], Any] | None,
|
||||||
max_credits: int,
|
max_credits: int,
|
||||||
mtu: int,
|
mtu: int,
|
||||||
mps: int,
|
mps: int,
|
||||||
@@ -1966,12 +1966,12 @@ class LeCreditBasedChannelServer(utils.EventEmitter):
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
class ChannelManager:
|
class ChannelManager:
|
||||||
identifiers: dict[int, int]
|
identifiers: dict[int, int]
|
||||||
channels: dict[int, dict[int, Union[ClassicChannel, LeCreditBasedChannel]]]
|
channels: dict[int, dict[int, ClassicChannel | LeCreditBasedChannel]]
|
||||||
servers: dict[int, ClassicChannelServer]
|
servers: dict[int, ClassicChannelServer]
|
||||||
le_coc_channels: dict[int, dict[int, LeCreditBasedChannel]]
|
le_coc_channels: dict[int, dict[int, LeCreditBasedChannel]]
|
||||||
le_coc_servers: dict[int, LeCreditBasedChannelServer]
|
le_coc_servers: dict[int, LeCreditBasedChannelServer]
|
||||||
le_coc_requests: dict[int, L2CAP_LE_Credit_Based_Connection_Request]
|
le_coc_requests: dict[int, L2CAP_LE_Credit_Based_Connection_Request]
|
||||||
fixed_channels: dict[int, Optional[Callable[[int, bytes], Any]]]
|
fixed_channels: dict[int, Callable[[int, bytes], Any] | None]
|
||||||
pending_credit_based_connections: dict[
|
pending_credit_based_connections: dict[
|
||||||
int,
|
int,
|
||||||
dict[
|
dict[
|
||||||
@@ -1982,8 +1982,8 @@ class ChannelManager:
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
_host: Optional[Host]
|
_host: Host | None
|
||||||
connection_parameters_update_response: Optional[asyncio.Future[int]]
|
connection_parameters_update_response: asyncio.Future[int] | None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -2089,7 +2089,7 @@ class ChannelManager:
|
|||||||
def create_classic_server(
|
def create_classic_server(
|
||||||
self,
|
self,
|
||||||
spec: ClassicChannelSpec,
|
spec: ClassicChannelSpec,
|
||||||
handler: Optional[Callable[[ClassicChannel], Any]] = None,
|
handler: Callable[[ClassicChannel], Any] | None = None,
|
||||||
) -> ClassicChannelServer:
|
) -> ClassicChannelServer:
|
||||||
if not spec.psm:
|
if not spec.psm:
|
||||||
# Find a free PSM
|
# Find a free PSM
|
||||||
@@ -2125,7 +2125,7 @@ class ChannelManager:
|
|||||||
def create_le_credit_based_server(
|
def create_le_credit_based_server(
|
||||||
self,
|
self,
|
||||||
spec: LeCreditBasedChannelSpec,
|
spec: LeCreditBasedChannelSpec,
|
||||||
handler: Optional[Callable[[LeCreditBasedChannel], Any]] = None,
|
handler: Callable[[LeCreditBasedChannel], Any] | None = None,
|
||||||
) -> LeCreditBasedChannelServer:
|
) -> LeCreditBasedChannelServer:
|
||||||
if not spec.psm:
|
if not spec.psm:
|
||||||
# Find a free PSM
|
# Find a free PSM
|
||||||
@@ -2175,7 +2175,7 @@ class ChannelManager:
|
|||||||
self,
|
self,
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
cid: int,
|
cid: int,
|
||||||
pdu: Union[SupportsBytes, bytes],
|
pdu: SupportsBytes | bytes,
|
||||||
with_fcs: bool = False,
|
with_fcs: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
pdu_str = pdu.hex() if isinstance(pdu, bytes) else str(pdu)
|
pdu_str = pdu.hex() if isinstance(pdu, bytes) else str(pdu)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import asyncio
|
|||||||
# Imports
|
# Imports
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from bumble import core, hci, ll, lmp
|
from bumble import core, hci, ll, lmp
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ class LocalLink:
|
|||||||
|
|
||||||
def find_classic_controller(
|
def find_classic_controller(
|
||||||
self, address: hci.Address
|
self, address: hci.Address
|
||||||
) -> Optional[controller.Controller]:
|
) -> controller.Controller | None:
|
||||||
for controller in self.controllers:
|
for controller in self.controllers:
|
||||||
if controller.public_address == address:
|
if controller.public_address == address:
|
||||||
return controller
|
return controller
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ from __future__ import annotations
|
|||||||
import enum
|
import enum
|
||||||
import secrets
|
import secrets
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from bumble import hci
|
from bumble import hci
|
||||||
from bumble.core import AdvertisingData, LeRole
|
from bumble.core import AdvertisingData, LeRole
|
||||||
@@ -45,16 +44,16 @@ from bumble.smp import (
|
|||||||
class OobData:
|
class OobData:
|
||||||
"""OOB data that can be sent from one device to another."""
|
"""OOB data that can be sent from one device to another."""
|
||||||
|
|
||||||
address: Optional[hci.Address] = None
|
address: hci.Address | None = None
|
||||||
role: Optional[LeRole] = None
|
role: LeRole | None = None
|
||||||
shared_data: Optional[OobSharedData] = None
|
shared_data: OobSharedData | None = None
|
||||||
legacy_context: Optional[OobLegacyContext] = None
|
legacy_context: OobLegacyContext | None = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_ad(cls, ad: AdvertisingData) -> OobData:
|
def from_ad(cls, ad: AdvertisingData) -> OobData:
|
||||||
instance = cls()
|
instance = cls()
|
||||||
shared_data_c: Optional[bytes] = None
|
shared_data_c: bytes | None = None
|
||||||
shared_data_r: Optional[bytes] = None
|
shared_data_r: bytes | None = None
|
||||||
for ad_type, ad_data in ad.ad_structures:
|
for ad_type, ad_data in ad.ad_structures:
|
||||||
if ad_type == AdvertisingData.LE_BLUETOOTH_DEVICE_ADDRESS:
|
if ad_type == AdvertisingData.LE_BLUETOOTH_DEVICE_ADDRESS:
|
||||||
instance.address = hci.Address(ad_data)
|
instance.address = hci.Address(ad_data)
|
||||||
@@ -181,14 +180,14 @@ class PairingDelegate:
|
|||||||
"""Compare two numbers."""
|
"""Compare two numbers."""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def get_number(self) -> Optional[int]:
|
async def get_number(self) -> int | None:
|
||||||
"""
|
"""
|
||||||
Return an optional number as an answer to a passkey request.
|
Return an optional number as an answer to a passkey request.
|
||||||
Returning `None` will result in a negative reply.
|
Returning `None` will result in a negative reply.
|
||||||
"""
|
"""
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
async def get_string(self, max_length: int) -> Optional[str]:
|
async def get_string(self, max_length: int) -> str | None:
|
||||||
"""
|
"""
|
||||||
Return a string whose utf-8 encoding is up to max_length bytes.
|
Return a string whose utf-8 encoding is up to max_length bytes.
|
||||||
"""
|
"""
|
||||||
@@ -239,18 +238,18 @@ class PairingConfig:
|
|||||||
class OobConfig:
|
class OobConfig:
|
||||||
"""Config for OOB pairing."""
|
"""Config for OOB pairing."""
|
||||||
|
|
||||||
our_context: Optional[OobContext]
|
our_context: OobContext | None
|
||||||
peer_data: Optional[OobSharedData]
|
peer_data: OobSharedData | None
|
||||||
legacy_context: Optional[OobLegacyContext]
|
legacy_context: OobLegacyContext | None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
sc: bool = True,
|
sc: bool = True,
|
||||||
mitm: bool = True,
|
mitm: bool = True,
|
||||||
bonding: bool = True,
|
bonding: bool = True,
|
||||||
delegate: Optional[PairingDelegate] = None,
|
delegate: PairingDelegate | None = None,
|
||||||
identity_address_type: Optional[AddressType] = None,
|
identity_address_type: AddressType | None = None,
|
||||||
oob: Optional[OobConfig] = None,
|
oob: OobConfig | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.sc = sc
|
self.sc = sc
|
||||||
self.mitm = mitm
|
self.mitm = mitm
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ This module implement the Pandora Bluetooth test APIs for the Bumble stack.
|
|||||||
|
|
||||||
__version__ = "0.0.1"
|
__version__ = "0.0.1"
|
||||||
|
|
||||||
from typing import Callable, Optional
|
from collections.abc import Callable
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
import grpc.aio
|
import grpc.aio
|
||||||
@@ -58,7 +58,7 @@ def register_servicer_hook(
|
|||||||
async def serve(
|
async def serve(
|
||||||
bumble: PandoraDevice,
|
bumble: PandoraDevice,
|
||||||
config: Config = Config(),
|
config: Config = Config(),
|
||||||
grpc_server: Optional[grpc.aio.Server] = None,
|
grpc_server: grpc.aio.Server | None = None,
|
||||||
port: int = 0,
|
port: int = 0,
|
||||||
) -> None:
|
) -> None:
|
||||||
# initialize a gRPC server if not provided.
|
# initialize a gRPC server if not provided.
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Optional
|
from typing import Any
|
||||||
|
|
||||||
from bumble import transport
|
from bumble import transport
|
||||||
from bumble.core import (
|
from bumble.core import (
|
||||||
@@ -54,7 +54,7 @@ class PandoraDevice:
|
|||||||
|
|
||||||
# HCI transport name & instance.
|
# HCI transport name & instance.
|
||||||
_hci_name: str
|
_hci_name: str
|
||||||
_hci: Optional[transport.Transport] # type: ignore[name-defined]
|
_hci: transport.Transport | None # type: ignore[name-defined]
|
||||||
|
|
||||||
def __init__(self, config: dict[str, Any]) -> None:
|
def __init__(self, config: dict[str, Any]) -> None:
|
||||||
self.config = config
|
self.config = config
|
||||||
@@ -98,7 +98,7 @@ class PandoraDevice:
|
|||||||
await self.close()
|
await self.close()
|
||||||
await self.open()
|
await self.open()
|
||||||
|
|
||||||
def info(self) -> Optional[dict[str, str]]:
|
def info(self) -> dict[str, str] | None:
|
||||||
return {
|
return {
|
||||||
'public_bd_address': str(self.device.public_address),
|
'public_bd_address': str(self.device.public_address),
|
||||||
'random_address': str(self.device.random_address),
|
'random_address': str(self.device.random_address),
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
from typing import AsyncGenerator, Optional, cast
|
from collections.abc import AsyncGenerator
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
import grpc.aio
|
import grpc.aio
|
||||||
@@ -623,7 +624,7 @@ class HostService(HostServicer):
|
|||||||
self.log.debug('Inquiry')
|
self.log.debug('Inquiry')
|
||||||
|
|
||||||
inquiry_queue: asyncio.Queue[
|
inquiry_queue: asyncio.Queue[
|
||||||
Optional[tuple[Address, int, AdvertisingData, int]]
|
tuple[Address, int, AdvertisingData, int] | None
|
||||||
] = asyncio.Queue()
|
] = asyncio.Queue()
|
||||||
complete_handler = self.device.on(
|
complete_handler = self.device.on(
|
||||||
self.device.EVENT_INQUIRY_COMPLETE, lambda: inquiry_queue.put_nowait(None)
|
self.device.EVENT_INQUIRY_COMPLETE, lambda: inquiry_queue.put_nowait(None)
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
from asyncio import Future
|
from asyncio import Future
|
||||||
from asyncio import Queue as AsyncQueue
|
from asyncio import Queue as AsyncQueue
|
||||||
|
from collections.abc import AsyncGenerator
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import AsyncGenerator, Optional, Union
|
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
from google.protobuf import any_pb2, empty_pb2 # pytype: disable=pyi-error
|
from google.protobuf import any_pb2, empty_pb2 # pytype: disable=pyi-error
|
||||||
@@ -56,7 +56,7 @@ from bumble.l2cap import (
|
|||||||
from bumble.pandora import utils
|
from bumble.pandora import utils
|
||||||
from bumble.pandora.config import Config
|
from bumble.pandora.config import Config
|
||||||
|
|
||||||
L2capChannel = Union[ClassicChannel, LeCreditBasedChannel]
|
L2capChannel = ClassicChannel | LeCreditBasedChannel
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -107,10 +107,8 @@ class L2CAPService(L2CAPServicer):
|
|||||||
oneof = request.WhichOneof('type')
|
oneof = request.WhichOneof('type')
|
||||||
self.log.debug(f'WaitConnection channel request type: {oneof}.')
|
self.log.debug(f'WaitConnection channel request type: {oneof}.')
|
||||||
channel_type = getattr(request, oneof)
|
channel_type = getattr(request, oneof)
|
||||||
spec: Optional[Union[ClassicChannelSpec, LeCreditBasedChannelSpec]] = None
|
spec: ClassicChannelSpec | LeCreditBasedChannelSpec | None = None
|
||||||
l2cap_server: Optional[
|
l2cap_server: ClassicChannelServer | LeCreditBasedChannelServer | None = None
|
||||||
Union[ClassicChannelServer, LeCreditBasedChannelServer]
|
|
||||||
] = None
|
|
||||||
if isinstance(channel_type, CreditBasedChannelRequest):
|
if isinstance(channel_type, CreditBasedChannelRequest):
|
||||||
spec = LeCreditBasedChannelSpec(
|
spec = LeCreditBasedChannelSpec(
|
||||||
psm=channel_type.spsm,
|
psm=channel_type.spsm,
|
||||||
@@ -217,7 +215,7 @@ class L2CAPService(L2CAPServicer):
|
|||||||
oneof = request.WhichOneof('type')
|
oneof = request.WhichOneof('type')
|
||||||
self.log.debug(f'Channel request type: {oneof}.')
|
self.log.debug(f'Channel request type: {oneof}.')
|
||||||
channel_type = getattr(request, oneof)
|
channel_type = getattr(request, oneof)
|
||||||
spec: Optional[Union[ClassicChannelSpec, LeCreditBasedChannelSpec]] = None
|
spec: ClassicChannelSpec | LeCreditBasedChannelSpec | None = None
|
||||||
if isinstance(channel_type, CreditBasedChannelRequest):
|
if isinstance(channel_type, CreditBasedChannelRequest):
|
||||||
spec = LeCreditBasedChannelSpec(
|
spec = LeCreditBasedChannelSpec(
|
||||||
psm=channel_type.spsm,
|
psm=channel_type.spsm,
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
import contextlib
|
import contextlib
|
||||||
import logging
|
import logging
|
||||||
from collections.abc import Awaitable
|
from collections.abc import AsyncGenerator, AsyncIterator, Awaitable, Callable
|
||||||
from typing import Any, AsyncGenerator, AsyncIterator, Callable, Optional, Union
|
from typing import Any
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
from google.protobuf import (
|
from google.protobuf import (
|
||||||
@@ -66,7 +66,7 @@ class PairingDelegate(BasePairingDelegate):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
connection: BumbleConnection,
|
connection: BumbleConnection,
|
||||||
service: "SecurityService",
|
service: SecurityService,
|
||||||
io_capability: BasePairingDelegate.IoCapability = BasePairingDelegate.NO_OUTPUT_NO_INPUT,
|
io_capability: BasePairingDelegate.IoCapability = BasePairingDelegate.NO_OUTPUT_NO_INPUT,
|
||||||
local_initiator_key_distribution: BasePairingDelegate.KeyDistribution = BasePairingDelegate.DEFAULT_KEY_DISTRIBUTION,
|
local_initiator_key_distribution: BasePairingDelegate.KeyDistribution = BasePairingDelegate.DEFAULT_KEY_DISTRIBUTION,
|
||||||
local_responder_key_distribution: BasePairingDelegate.KeyDistribution = BasePairingDelegate.DEFAULT_KEY_DISTRIBUTION,
|
local_responder_key_distribution: BasePairingDelegate.KeyDistribution = BasePairingDelegate.DEFAULT_KEY_DISTRIBUTION,
|
||||||
@@ -132,7 +132,7 @@ class PairingDelegate(BasePairingDelegate):
|
|||||||
assert answer.answer_variant() == 'confirm' and answer.confirm is not None
|
assert answer.answer_variant() == 'confirm' and answer.confirm is not None
|
||||||
return answer.confirm
|
return answer.confirm
|
||||||
|
|
||||||
async def get_number(self) -> Optional[int]:
|
async def get_number(self) -> int | None:
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
f"Pairing event: `passkey_entry_request` (io_capability: {self.io_capability})"
|
f"Pairing event: `passkey_entry_request` (io_capability: {self.io_capability})"
|
||||||
)
|
)
|
||||||
@@ -149,7 +149,7 @@ class PairingDelegate(BasePairingDelegate):
|
|||||||
assert answer.answer_variant() == 'passkey'
|
assert answer.answer_variant() == 'passkey'
|
||||||
return answer.passkey
|
return answer.passkey
|
||||||
|
|
||||||
async def get_string(self, max_length: int) -> Optional[str]:
|
async def get_string(self, max_length: int) -> str | None:
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
f"Pairing event: `pin_code_request` (io_capability: {self.io_capability})"
|
f"Pairing event: `pin_code_request` (io_capability: {self.io_capability})"
|
||||||
)
|
)
|
||||||
@@ -197,8 +197,8 @@ class SecurityService(SecurityServicer):
|
|||||||
self.log = utils.BumbleServerLoggerAdapter(
|
self.log = utils.BumbleServerLoggerAdapter(
|
||||||
logging.getLogger(), {'service_name': 'Security', 'device': device}
|
logging.getLogger(), {'service_name': 'Security', 'device': device}
|
||||||
)
|
)
|
||||||
self.event_queue: Optional[asyncio.Queue[PairingEvent]] = None
|
self.event_queue: asyncio.Queue[PairingEvent] | None = None
|
||||||
self.event_answer: Optional[AsyncIterator[PairingEventAnswer]] = None
|
self.event_answer: AsyncIterator[PairingEventAnswer] | None = None
|
||||||
self.device = device
|
self.device = device
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
@@ -233,7 +233,7 @@ class SecurityService(SecurityServicer):
|
|||||||
if level == LEVEL2:
|
if level == LEVEL2:
|
||||||
return connection.encryption != 0 and connection.authenticated
|
return connection.encryption != 0 and connection.authenticated
|
||||||
|
|
||||||
link_key_type: Optional[int] = None
|
link_key_type: int | None = None
|
||||||
if (keystore := connection.device.keystore) and (
|
if (keystore := connection.device.keystore) and (
|
||||||
keys := await keystore.get(str(connection.peer_address))
|
keys := await keystore.get(str(connection.peer_address))
|
||||||
):
|
):
|
||||||
@@ -412,8 +412,8 @@ class SecurityService(SecurityServicer):
|
|||||||
wait_for_security: asyncio.Future[str] = (
|
wait_for_security: asyncio.Future[str] = (
|
||||||
asyncio.get_running_loop().create_future()
|
asyncio.get_running_loop().create_future()
|
||||||
)
|
)
|
||||||
authenticate_task: Optional[asyncio.Future[None]] = None
|
authenticate_task: asyncio.Future[None] | None = None
|
||||||
pair_task: Optional[asyncio.Future[None]] = None
|
pair_task: asyncio.Future[None] | None = None
|
||||||
|
|
||||||
async def authenticate() -> None:
|
async def authenticate() -> None:
|
||||||
if (encryption := connection.encryption) != 0:
|
if (encryption := connection.encryption) != 0:
|
||||||
@@ -459,7 +459,7 @@ class SecurityService(SecurityServicer):
|
|||||||
if self.need_pairing(connection, level):
|
if self.need_pairing(connection, level):
|
||||||
bumble.utils.AsyncRunner.spawn(connection.pair())
|
bumble.utils.AsyncRunner.spawn(connection.pair())
|
||||||
|
|
||||||
listeners: dict[str, Callable[..., Union[None, Awaitable[None]]]] = {
|
listeners: dict[str, Callable[..., None | Awaitable[None]]] = {
|
||||||
'disconnection': set_failure('connection_died'),
|
'disconnection': set_failure('connection_died'),
|
||||||
'pairing_failure': set_failure('pairing_failure'),
|
'pairing_failure': set_failure('pairing_failure'),
|
||||||
'connection_authentication_failure': set_failure('authentication_failure'),
|
'connection_authentication_failure': set_failure('authentication_failure'),
|
||||||
@@ -502,7 +502,7 @@ class SecurityService(SecurityServicer):
|
|||||||
return WaitSecurityResponse(**kwargs)
|
return WaitSecurityResponse(**kwargs)
|
||||||
|
|
||||||
async def reached_security_level(
|
async def reached_security_level(
|
||||||
self, connection: BumbleConnection, level: Union[SecurityLevel, LESecurityLevel]
|
self, connection: BumbleConnection, level: SecurityLevel | LESecurityLevel
|
||||||
) -> bool:
|
) -> bool:
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
str(
|
str(
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ import contextlib
|
|||||||
import functools
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Generator, MutableMapping, Optional
|
from collections.abc import Generator, MutableMapping
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
from google.protobuf.message import Message # pytype: disable=pyi-error
|
from google.protobuf.message import Message # pytype: disable=pyi-error
|
||||||
@@ -34,7 +35,7 @@ ADDRESS_TYPES: dict[str, AddressType] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def address_from_request(request: Message, field: Optional[str]) -> Address:
|
def address_from_request(request: Message, field: str | None) -> Address:
|
||||||
if field is None:
|
if field is None:
|
||||||
return Address.ANY
|
return Address.ANY
|
||||||
return Address(bytes(reversed(getattr(request, field))), ADDRESS_TYPES[field])
|
return Address(bytes(reversed(getattr(request, field))), ADDRESS_TYPES[field])
|
||||||
@@ -95,8 +96,7 @@ def rpc(func: Any) -> Any:
|
|||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def gen_wrapper(self: Any, request: Any, context: grpc.ServicerContext) -> Any:
|
def gen_wrapper(self: Any, request: Any, context: grpc.ServicerContext) -> Any:
|
||||||
with exception_to_rpc_error(context):
|
with exception_to_rpc_error(context):
|
||||||
for v in func(self, request, context):
|
yield from func(self, request, context)
|
||||||
yield v
|
|
||||||
|
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def wrapper(self: Any, request: Any, context: grpc.ServicerContext) -> Any:
|
def wrapper(self: Any, request: Any, context: grpc.ServicerContext) -> Any:
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ from __future__ import annotations
|
|||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from bumble import utils
|
from bumble import utils
|
||||||
from bumble.att import ATT_Error
|
from bumble.att import ATT_Error
|
||||||
@@ -129,7 +128,7 @@ class AudioInputState:
|
|||||||
mute: Mute = Mute.NOT_MUTED
|
mute: Mute = Mute.NOT_MUTED
|
||||||
gain_mode: GainMode = GainMode.MANUAL
|
gain_mode: GainMode = GainMode.MANUAL
|
||||||
change_counter: int = 0
|
change_counter: int = 0
|
||||||
attribute: Optional[Attribute] = None
|
attribute: Attribute | None = None
|
||||||
|
|
||||||
def __bytes__(self) -> bytes:
|
def __bytes__(self) -> bytes:
|
||||||
return bytes(
|
return bytes(
|
||||||
@@ -316,7 +315,7 @@ class AudioInputDescription:
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
audio_input_description: str = "Bluetooth"
|
audio_input_description: str = "Bluetooth"
|
||||||
attribute: Optional[Attribute] = None
|
attribute: Attribute | None = None
|
||||||
|
|
||||||
def on_read(self, _connection: Connection) -> str:
|
def on_read(self, _connection: Connection) -> str:
|
||||||
return self.audio_input_description
|
return self.audio_input_description
|
||||||
@@ -339,11 +338,11 @@ class AICSService(TemplateService):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
audio_input_state: Optional[AudioInputState] = None,
|
audio_input_state: AudioInputState | None = None,
|
||||||
gain_settings_properties: Optional[GainSettingsProperties] = None,
|
gain_settings_properties: GainSettingsProperties | None = None,
|
||||||
audio_input_type: str = "local",
|
audio_input_type: str = "local",
|
||||||
audio_input_status: Optional[AudioInputStatus] = None,
|
audio_input_status: AudioInputStatus | None = None,
|
||||||
audio_input_description: Optional[AudioInputDescription] = None,
|
audio_input_description: AudioInputDescription | None = None,
|
||||||
):
|
):
|
||||||
self.audio_input_state = (
|
self.audio_input_state = (
|
||||||
AudioInputState() if audio_input_state is None else audio_input_state
|
AudioInputState() if audio_input_state is None else audio_input_state
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import asyncio
|
|||||||
import dataclasses
|
import dataclasses
|
||||||
import enum
|
import enum
|
||||||
import logging
|
import logging
|
||||||
from typing import Iterable, Optional, Union
|
from collections.abc import Iterable
|
||||||
|
|
||||||
from bumble import utils
|
from bumble import utils
|
||||||
from bumble.device import Peer
|
from bumble.device import Peer
|
||||||
@@ -230,7 +230,7 @@ class AmsClient(utils.EventEmitter):
|
|||||||
self.supported_commands = set()
|
self.supported_commands = set()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def for_peer(cls, peer: Peer) -> Optional[AmsClient]:
|
async def for_peer(cls, peer: Peer) -> AmsClient | None:
|
||||||
ams_proxy = await peer.discover_service_and_create_proxy(AmsProxy)
|
ams_proxy = await peer.discover_service_and_create_proxy(AmsProxy)
|
||||||
if ams_proxy is None:
|
if ams_proxy is None:
|
||||||
return None
|
return None
|
||||||
@@ -263,9 +263,7 @@ class AmsClient(utils.EventEmitter):
|
|||||||
async def observe(
|
async def observe(
|
||||||
self,
|
self,
|
||||||
entity: EntityId,
|
entity: EntityId,
|
||||||
attributes: Iterable[
|
attributes: Iterable[PlayerAttributeId | QueueAttributeId | TrackAttributeId],
|
||||||
Union[PlayerAttributeId, QueueAttributeId, TrackAttributeId]
|
|
||||||
],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
await self._ams_proxy.entity_update.write_value(
|
await self._ams_proxy.entity_update.write_value(
|
||||||
bytes([entity] + list(attributes)), with_response=True
|
bytes([entity] + list(attributes)), with_response=True
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import datetime
|
|||||||
import enum
|
import enum
|
||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
from typing import Optional, Sequence, Union
|
from collections.abc import Sequence
|
||||||
|
|
||||||
from bumble import utils
|
from bumble import utils
|
||||||
from bumble.att import ATT_Error
|
from bumble.att import ATT_Error
|
||||||
@@ -116,7 +116,7 @@ class NotificationAttributeId(utils.OpenIntEnum):
|
|||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class NotificationAttribute:
|
class NotificationAttribute:
|
||||||
attribute_id: NotificationAttributeId
|
attribute_id: NotificationAttributeId
|
||||||
value: Union[str, int, datetime.datetime]
|
value: str | int | datetime.datetime
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
@@ -242,10 +242,10 @@ class AncsProxy(ProfileServiceProxy):
|
|||||||
|
|
||||||
|
|
||||||
class AncsClient(utils.EventEmitter):
|
class AncsClient(utils.EventEmitter):
|
||||||
_expected_response_command_id: Optional[CommandId]
|
_expected_response_command_id: CommandId | None
|
||||||
_expected_response_notification_uid: Optional[int]
|
_expected_response_notification_uid: int | None
|
||||||
_expected_response_app_identifier: Optional[str]
|
_expected_response_app_identifier: str | None
|
||||||
_expected_app_identifier: Optional[str]
|
_expected_app_identifier: str | None
|
||||||
_expected_response_tuples: int
|
_expected_response_tuples: int
|
||||||
_response_accumulator: bytes
|
_response_accumulator: bytes
|
||||||
|
|
||||||
@@ -255,12 +255,12 @@ class AncsClient(utils.EventEmitter):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
self._ancs_proxy = ancs_proxy
|
self._ancs_proxy = ancs_proxy
|
||||||
self._command_semaphore = asyncio.Semaphore()
|
self._command_semaphore = asyncio.Semaphore()
|
||||||
self._response: Optional[asyncio.Future] = None
|
self._response: asyncio.Future | None = None
|
||||||
self._reset_response()
|
self._reset_response()
|
||||||
self._started = False
|
self._started = False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def for_peer(cls, peer: Peer) -> Optional[AncsClient]:
|
async def for_peer(cls, peer: Peer) -> AncsClient | None:
|
||||||
ancs_proxy = await peer.discover_service_and_create_proxy(AncsProxy)
|
ancs_proxy = await peer.discover_service_and_create_proxy(AncsProxy)
|
||||||
if ancs_proxy is None:
|
if ancs_proxy is None:
|
||||||
return None
|
return None
|
||||||
@@ -316,7 +316,7 @@ class AncsClient(utils.EventEmitter):
|
|||||||
# Not enough data yet.
|
# Not enough data yet.
|
||||||
return
|
return
|
||||||
|
|
||||||
attributes: list[Union[NotificationAttribute, AppAttribute]] = []
|
attributes: list[NotificationAttribute | AppAttribute] = []
|
||||||
|
|
||||||
if command_id == CommandId.GET_NOTIFICATION_ATTRIBUTES:
|
if command_id == CommandId.GET_NOTIFICATION_ATTRIBUTES:
|
||||||
(notification_uid,) = struct.unpack_from(
|
(notification_uid,) = struct.unpack_from(
|
||||||
@@ -342,7 +342,7 @@ class AncsClient(utils.EventEmitter):
|
|||||||
str_value = attribute_data[3 : 3 + attribute_data_length].decode(
|
str_value = attribute_data[3 : 3 + attribute_data_length].decode(
|
||||||
"utf-8"
|
"utf-8"
|
||||||
)
|
)
|
||||||
value: Union[str, int, datetime.datetime]
|
value: str | int | datetime.datetime
|
||||||
if attribute_id == NotificationAttributeId.MESSAGE_SIZE:
|
if attribute_id == NotificationAttributeId.MESSAGE_SIZE:
|
||||||
value = int(str_value)
|
value = int(str_value)
|
||||||
elif attribute_id == NotificationAttributeId.DATE:
|
elif attribute_id == NotificationAttributeId.DATE:
|
||||||
@@ -415,7 +415,7 @@ class AncsClient(utils.EventEmitter):
|
|||||||
self,
|
self,
|
||||||
notification_uid: int,
|
notification_uid: int,
|
||||||
attributes: Sequence[
|
attributes: Sequence[
|
||||||
Union[NotificationAttributeId, tuple[NotificationAttributeId, int]]
|
NotificationAttributeId | tuple[NotificationAttributeId, int]
|
||||||
],
|
],
|
||||||
) -> list[NotificationAttribute]:
|
) -> list[NotificationAttribute]:
|
||||||
if not self._started:
|
if not self._started:
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import logging
|
|||||||
import struct
|
import struct
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any, Optional, TypeVar, Union
|
from typing import Any, TypeVar
|
||||||
|
|
||||||
from bumble import colors, device, gatt, gatt_client, hci, utils
|
from bumble import colors, device, gatt, gatt_client, hci, utils
|
||||||
from bumble.profiles import le_audio
|
from bumble.profiles import le_audio
|
||||||
@@ -49,7 +49,7 @@ class ASE_Operation:
|
|||||||
classes: dict[int, type[ASE_Operation]] = {}
|
classes: dict[int, type[ASE_Operation]] = {}
|
||||||
op_code: Opcode
|
op_code: Opcode
|
||||||
name: str
|
name: str
|
||||||
fields: Optional[Sequence[Any]] = None
|
fields: Sequence[Any] | None = None
|
||||||
ase_id: Sequence[int]
|
ase_id: Sequence[int]
|
||||||
|
|
||||||
class Opcode(enum.IntEnum):
|
class Opcode(enum.IntEnum):
|
||||||
@@ -278,7 +278,7 @@ class AseStateMachine(gatt.Characteristic):
|
|||||||
|
|
||||||
EVENT_STATE_CHANGE = "state_change"
|
EVENT_STATE_CHANGE = "state_change"
|
||||||
|
|
||||||
cis_link: Optional[device.CisLink] = None
|
cis_link: device.CisLink | None = None
|
||||||
|
|
||||||
# Additional parameters in CODEC_CONFIGURED State
|
# Additional parameters in CODEC_CONFIGURED State
|
||||||
preferred_framing = 0 # Unframed PDU supported
|
preferred_framing = 0 # Unframed PDU supported
|
||||||
@@ -290,7 +290,7 @@ class AseStateMachine(gatt.Characteristic):
|
|||||||
preferred_presentation_delay_min = 0
|
preferred_presentation_delay_min = 0
|
||||||
preferred_presentation_delay_max = 0
|
preferred_presentation_delay_max = 0
|
||||||
codec_id = hci.CodingFormat(hci.CodecID.LC3)
|
codec_id = hci.CodingFormat(hci.CodecID.LC3)
|
||||||
codec_specific_configuration: Union[CodecSpecificConfiguration, bytes] = b''
|
codec_specific_configuration: CodecSpecificConfiguration | bytes = b''
|
||||||
|
|
||||||
# Additional parameters in QOS_CONFIGURED State
|
# Additional parameters in QOS_CONFIGURED State
|
||||||
cig_id = 0
|
cig_id = 0
|
||||||
@@ -610,7 +610,7 @@ class AudioStreamControlService(gatt.TemplateService):
|
|||||||
|
|
||||||
ase_state_machines: dict[int, AseStateMachine]
|
ase_state_machines: dict[int, AseStateMachine]
|
||||||
ase_control_point: gatt.Characteristic[bytes]
|
ase_control_point: gatt.Characteristic[bytes]
|
||||||
_active_client: Optional[device.Connection] = None
|
_active_client: device.Connection | None = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -19,7 +19,8 @@
|
|||||||
import enum
|
import enum
|
||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
from typing import Any, Callable, Optional, Union
|
from collections.abc import Callable
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from bumble import data_types, gatt, gatt_client, l2cap, utils
|
from bumble import data_types, gatt, gatt_client, l2cap, utils
|
||||||
from bumble.core import AdvertisingData
|
from bumble.core import AdvertisingData
|
||||||
@@ -90,20 +91,20 @@ class AshaService(gatt.TemplateService):
|
|||||||
EVENT_DISCONNECTED = "disconnected"
|
EVENT_DISCONNECTED = "disconnected"
|
||||||
EVENT_VOLUME_CHANGED = "volume_changed"
|
EVENT_VOLUME_CHANGED = "volume_changed"
|
||||||
|
|
||||||
audio_sink: Optional[Callable[[bytes], Any]]
|
audio_sink: Callable[[bytes], Any] | None
|
||||||
active_codec: Optional[Codec] = None
|
active_codec: Codec | None = None
|
||||||
audio_type: Optional[AudioType] = None
|
audio_type: AudioType | None = None
|
||||||
volume: Optional[int] = None
|
volume: int | None = None
|
||||||
other_state: Optional[int] = None
|
other_state: int | None = None
|
||||||
connection: Optional[Connection] = None
|
connection: Connection | None = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
capability: int,
|
capability: int,
|
||||||
hisyncid: Union[list[int], bytes],
|
hisyncid: list[int] | bytes,
|
||||||
device: Device,
|
device: Device,
|
||||||
psm: int = 0,
|
psm: int = 0,
|
||||||
audio_sink: Optional[Callable[[bytes], Any]] = None,
|
audio_sink: Callable[[bytes], Any] | None = None,
|
||||||
feature_map: int = FeatureMap.LE_COC_AUDIO_OUTPUT_STREAMING_SUPPORTED,
|
feature_map: int = FeatureMap.LE_COC_AUDIO_OUTPUT_STREAMING_SUPPORTED,
|
||||||
protocol_version: int = 0x01,
|
protocol_version: int = 0x01,
|
||||||
render_delay_milliseconds: int = 0,
|
render_delay_milliseconds: int = 0,
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ from __future__ import annotations
|
|||||||
import dataclasses
|
import dataclasses
|
||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
from typing import ClassVar, Optional, Sequence
|
from collections.abc import Sequence
|
||||||
|
from typing import ClassVar
|
||||||
|
|
||||||
from bumble import core, device, gatt, gatt_adapters, gatt_client, hci, utils
|
from bumble import core, device, gatt, gatt_adapters, gatt_client, hci, utils
|
||||||
|
|
||||||
@@ -351,7 +352,7 @@ class BroadcastAudioScanServiceProxy(gatt_client.ProfileServiceProxy):
|
|||||||
|
|
||||||
broadcast_audio_scan_control_point: gatt_client.CharacteristicProxy[bytes]
|
broadcast_audio_scan_control_point: gatt_client.CharacteristicProxy[bytes]
|
||||||
broadcast_receive_states: list[
|
broadcast_receive_states: list[
|
||||||
gatt_client.CharacteristicProxy[Optional[BroadcastReceiveState]]
|
gatt_client.CharacteristicProxy[BroadcastReceiveState | None]
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, service_proxy: gatt_client.ServiceProxy):
|
def __init__(self, service_proxy: gatt_client.ServiceProxy):
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Imports
|
# Imports
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from bumble.gatt import (
|
from bumble.gatt import (
|
||||||
GATT_BATTERY_LEVEL_CHARACTERISTIC,
|
GATT_BATTERY_LEVEL_CHARACTERISTIC,
|
||||||
@@ -56,7 +55,7 @@ class BatteryService(TemplateService):
|
|||||||
class BatteryServiceProxy(ProfileServiceProxy):
|
class BatteryServiceProxy(ProfileServiceProxy):
|
||||||
SERVICE_CLASS = BatteryService
|
SERVICE_CLASS = BatteryService
|
||||||
|
|
||||||
battery_level: Optional[CharacteristicProxy[int]]
|
battery_level: CharacteristicProxy[int] | None
|
||||||
|
|
||||||
def __init__(self, service_proxy):
|
def __init__(self, service_proxy):
|
||||||
self.service_proxy = service_proxy
|
self.service_proxy = service_proxy
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import enum
|
import enum
|
||||||
import struct
|
import struct
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from bumble import core, crypto, device, gatt, gatt_client
|
from bumble import core, crypto, device, gatt, gatt_client
|
||||||
|
|
||||||
@@ -96,17 +95,17 @@ class CoordinatedSetIdentificationService(gatt.TemplateService):
|
|||||||
|
|
||||||
set_identity_resolving_key: bytes
|
set_identity_resolving_key: bytes
|
||||||
set_identity_resolving_key_characteristic: gatt.Characteristic[bytes]
|
set_identity_resolving_key_characteristic: gatt.Characteristic[bytes]
|
||||||
coordinated_set_size_characteristic: Optional[gatt.Characteristic[bytes]] = None
|
coordinated_set_size_characteristic: gatt.Characteristic[bytes] | None = None
|
||||||
set_member_lock_characteristic: Optional[gatt.Characteristic[bytes]] = None
|
set_member_lock_characteristic: gatt.Characteristic[bytes] | None = None
|
||||||
set_member_rank_characteristic: Optional[gatt.Characteristic[bytes]] = None
|
set_member_rank_characteristic: gatt.Characteristic[bytes] | None = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
set_identity_resolving_key: bytes,
|
set_identity_resolving_key: bytes,
|
||||||
set_identity_resolving_key_type: SirkType,
|
set_identity_resolving_key_type: SirkType,
|
||||||
coordinated_set_size: Optional[int] = None,
|
coordinated_set_size: int | None = None,
|
||||||
set_member_lock: Optional[MemberLock] = None,
|
set_member_lock: MemberLock | None = None,
|
||||||
set_member_rank: Optional[int] = None,
|
set_member_rank: int | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if len(set_identity_resolving_key) != SET_IDENTITY_RESOLVING_KEY_LENGTH:
|
if len(set_identity_resolving_key) != SET_IDENTITY_RESOLVING_KEY_LENGTH:
|
||||||
raise core.InvalidArgumentError(
|
raise core.InvalidArgumentError(
|
||||||
@@ -198,9 +197,9 @@ class CoordinatedSetIdentificationProxy(gatt_client.ProfileServiceProxy):
|
|||||||
SERVICE_CLASS = CoordinatedSetIdentificationService
|
SERVICE_CLASS = CoordinatedSetIdentificationService
|
||||||
|
|
||||||
set_identity_resolving_key: gatt_client.CharacteristicProxy[bytes]
|
set_identity_resolving_key: gatt_client.CharacteristicProxy[bytes]
|
||||||
coordinated_set_size: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
coordinated_set_size: gatt_client.CharacteristicProxy[bytes] | None = None
|
||||||
set_member_lock: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
set_member_lock: gatt_client.CharacteristicProxy[bytes] | None = None
|
||||||
set_member_rank: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
set_member_rank: gatt_client.CharacteristicProxy[bytes] | None = None
|
||||||
|
|
||||||
def __init__(self, service_proxy: gatt_client.ServiceProxy) -> None:
|
def __init__(self, service_proxy: gatt_client.ServiceProxy) -> None:
|
||||||
self.service_proxy = service_proxy
|
self.service_proxy = service_proxy
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
# Imports
|
# Imports
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
import struct
|
import struct
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from bumble.gatt import (
|
from bumble.gatt import (
|
||||||
GATT_DEVICE_INFORMATION_SERVICE,
|
GATT_DEVICE_INFORMATION_SERVICE,
|
||||||
@@ -54,14 +53,14 @@ class DeviceInformationService(TemplateService):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
manufacturer_name: Optional[str] = None,
|
manufacturer_name: str | None = None,
|
||||||
model_number: Optional[str] = None,
|
model_number: str | None = None,
|
||||||
serial_number: Optional[str] = None,
|
serial_number: str | None = None,
|
||||||
hardware_revision: Optional[str] = None,
|
hardware_revision: str | None = None,
|
||||||
firmware_revision: Optional[str] = None,
|
firmware_revision: str | None = None,
|
||||||
software_revision: Optional[str] = None,
|
software_revision: str | None = None,
|
||||||
system_id: Optional[tuple[int, int]] = None, # (OUI, Manufacturer ID)
|
system_id: tuple[int, int] | None = None, # (OUI, Manufacturer ID)
|
||||||
ieee_regulatory_certification_data_list: Optional[bytes] = None,
|
ieee_regulatory_certification_data_list: bytes | None = None,
|
||||||
# TODO: pnp_id
|
# TODO: pnp_id
|
||||||
):
|
):
|
||||||
characteristics: list[Characteristic[bytes]] = [
|
characteristics: list[Characteristic[bytes]] = [
|
||||||
@@ -109,14 +108,14 @@ class DeviceInformationService(TemplateService):
|
|||||||
class DeviceInformationServiceProxy(ProfileServiceProxy):
|
class DeviceInformationServiceProxy(ProfileServiceProxy):
|
||||||
SERVICE_CLASS = DeviceInformationService
|
SERVICE_CLASS = DeviceInformationService
|
||||||
|
|
||||||
manufacturer_name: Optional[CharacteristicProxy[str]]
|
manufacturer_name: CharacteristicProxy[str] | None
|
||||||
model_number: Optional[CharacteristicProxy[str]]
|
model_number: CharacteristicProxy[str] | None
|
||||||
serial_number: Optional[CharacteristicProxy[str]]
|
serial_number: CharacteristicProxy[str] | None
|
||||||
hardware_revision: Optional[CharacteristicProxy[str]]
|
hardware_revision: CharacteristicProxy[str] | None
|
||||||
firmware_revision: Optional[CharacteristicProxy[str]]
|
firmware_revision: CharacteristicProxy[str] | None
|
||||||
software_revision: Optional[CharacteristicProxy[str]]
|
software_revision: CharacteristicProxy[str] | None
|
||||||
system_id: Optional[CharacteristicProxy[tuple[int, int]]]
|
system_id: CharacteristicProxy[tuple[int, int]] | None
|
||||||
ieee_regulatory_certification_data_list: Optional[CharacteristicProxy[bytes]]
|
ieee_regulatory_certification_data_list: CharacteristicProxy[bytes] | None
|
||||||
|
|
||||||
def __init__(self, service_proxy: ServiceProxy):
|
def __init__(self, service_proxy: ServiceProxy):
|
||||||
self.service_proxy = service_proxy
|
self.service_proxy = service_proxy
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
from typing import Optional, Union
|
|
||||||
|
|
||||||
from bumble.core import Appearance
|
from bumble.core import Appearance
|
||||||
from bumble.gatt import (
|
from bumble.gatt import (
|
||||||
@@ -54,7 +53,7 @@ class GenericAccessService(TemplateService):
|
|||||||
appearance_characteristic: Characteristic[bytes]
|
appearance_characteristic: Characteristic[bytes]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, device_name: str, appearance: Union[Appearance, tuple[int, int], int] = 0
|
self, device_name: str, appearance: Appearance | tuple[int, int] | int = 0
|
||||||
):
|
):
|
||||||
if isinstance(appearance, int):
|
if isinstance(appearance, int):
|
||||||
appearance_int = appearance
|
appearance_int = appearance
|
||||||
@@ -88,8 +87,8 @@ class GenericAccessService(TemplateService):
|
|||||||
class GenericAccessServiceProxy(ProfileServiceProxy):
|
class GenericAccessServiceProxy(ProfileServiceProxy):
|
||||||
SERVICE_CLASS = GenericAccessService
|
SERVICE_CLASS = GenericAccessService
|
||||||
|
|
||||||
device_name: Optional[CharacteristicProxy[str]]
|
device_name: CharacteristicProxy[str] | None
|
||||||
appearance: Optional[CharacteristicProxy[Appearance]]
|
appearance: CharacteristicProxy[Appearance] | None
|
||||||
|
|
||||||
def __init__(self, service_proxy: ServiceProxy):
|
def __init__(self, service_proxy: ServiceProxy):
|
||||||
self.service_proxy = service_proxy
|
self.service_proxy = service_proxy
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
import struct
|
import struct
|
||||||
from enum import IntFlag
|
from enum import IntFlag
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from bumble.gatt import (
|
from bumble.gatt import (
|
||||||
GATT_BGR_FEATURES_CHARACTERISTIC,
|
GATT_BGR_FEATURES_CHARACTERISTIC,
|
||||||
@@ -77,18 +76,18 @@ class GamingAudioService(TemplateService):
|
|||||||
UUID = GATT_GAMING_AUDIO_SERVICE
|
UUID = GATT_GAMING_AUDIO_SERVICE
|
||||||
|
|
||||||
gmap_role: Characteristic
|
gmap_role: Characteristic
|
||||||
ugg_features: Optional[Characteristic] = None
|
ugg_features: Characteristic | None = None
|
||||||
ugt_features: Optional[Characteristic] = None
|
ugt_features: Characteristic | None = None
|
||||||
bgs_features: Optional[Characteristic] = None
|
bgs_features: Characteristic | None = None
|
||||||
bgr_features: Optional[Characteristic] = None
|
bgr_features: Characteristic | None = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
gmap_role: GmapRole,
|
gmap_role: GmapRole,
|
||||||
ugg_features: Optional[UggFeatures] = None,
|
ugg_features: UggFeatures | None = None,
|
||||||
ugt_features: Optional[UgtFeatures] = None,
|
ugt_features: UgtFeatures | None = None,
|
||||||
bgs_features: Optional[BgsFeatures] = None,
|
bgs_features: BgsFeatures | None = None,
|
||||||
bgr_features: Optional[BgrFeatures] = None,
|
bgr_features: BgrFeatures | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
characteristics = []
|
characteristics = []
|
||||||
|
|
||||||
@@ -150,10 +149,10 @@ class GamingAudioService(TemplateService):
|
|||||||
class GamingAudioServiceProxy(ProfileServiceProxy):
|
class GamingAudioServiceProxy(ProfileServiceProxy):
|
||||||
SERVICE_CLASS = GamingAudioService
|
SERVICE_CLASS = GamingAudioService
|
||||||
|
|
||||||
ugg_features: Optional[CharacteristicProxy[UggFeatures]] = None
|
ugg_features: CharacteristicProxy[UggFeatures] | None = None
|
||||||
ugt_features: Optional[CharacteristicProxy[UgtFeatures]] = None
|
ugt_features: CharacteristicProxy[UgtFeatures] | None = None
|
||||||
bgs_features: Optional[CharacteristicProxy[BgsFeatures]] = None
|
bgs_features: CharacteristicProxy[BgsFeatures] | None = None
|
||||||
bgr_features: Optional[CharacteristicProxy[BgrFeatures]] = None
|
bgr_features: CharacteristicProxy[BgrFeatures] | None = None
|
||||||
|
|
||||||
def __init__(self, service_proxy: ServiceProxy) -> None:
|
def __init__(self, service_proxy: ServiceProxy) -> None:
|
||||||
self.service_proxy = service_proxy
|
self.service_proxy = service_proxy
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any, Optional, Union
|
from typing import Any
|
||||||
|
|
||||||
from bumble import att, gatt, gatt_adapters, gatt_client, utils
|
from bumble import att, gatt, gatt_adapters, gatt_client, utils
|
||||||
from bumble.core import InvalidArgumentError, InvalidStateError
|
from bumble.core import InvalidArgumentError, InvalidStateError
|
||||||
@@ -145,7 +145,7 @@ class PresetChangedOperation:
|
|||||||
return bytes([self.prev_index]) + bytes(self.preset_record)
|
return bytes([self.prev_index]) + bytes(self.preset_record)
|
||||||
|
|
||||||
change_id: ChangeId
|
change_id: ChangeId
|
||||||
additional_parameters: Union[Generic, int]
|
additional_parameters: Generic | int
|
||||||
|
|
||||||
def to_bytes(self, is_last: bool) -> bytes:
|
def to_bytes(self, is_last: bool) -> bytes:
|
||||||
if isinstance(self.additional_parameters, PresetChangedOperation.Generic):
|
if isinstance(self.additional_parameters, PresetChangedOperation.Generic):
|
||||||
@@ -235,7 +235,7 @@ class HearingAccessService(gatt.TemplateService):
|
|||||||
preset_records: dict[int, PresetRecord] # key is the preset index
|
preset_records: dict[int, PresetRecord] # key is the preset index
|
||||||
read_presets_request_in_progress: bool
|
read_presets_request_in_progress: bool
|
||||||
|
|
||||||
other_server_in_binaural_set: Optional[HearingAccessService] = None
|
other_server_in_binaural_set: HearingAccessService | None = None
|
||||||
|
|
||||||
preset_changed_operations_history_per_device: dict[
|
preset_changed_operations_history_per_device: dict[
|
||||||
Address, list[PresetChangedOperation]
|
Address, list[PresetChangedOperation]
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import struct
|
import struct
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from bumble import core
|
from bumble import core
|
||||||
from bumble.att import ATT_Error
|
from bumble.att import ATT_Error
|
||||||
@@ -207,13 +206,13 @@ class HeartRateService(TemplateService):
|
|||||||
class HeartRateServiceProxy(ProfileServiceProxy):
|
class HeartRateServiceProxy(ProfileServiceProxy):
|
||||||
SERVICE_CLASS = HeartRateService
|
SERVICE_CLASS = HeartRateService
|
||||||
|
|
||||||
heart_rate_measurement: Optional[
|
heart_rate_measurement: (
|
||||||
CharacteristicProxy[HeartRateService.HeartRateMeasurement]
|
CharacteristicProxy[HeartRateService.HeartRateMeasurement] | None
|
||||||
]
|
)
|
||||||
body_sensor_location: Optional[
|
body_sensor_location: (
|
||||||
CharacteristicProxy[HeartRateService.BodySensorLocation]
|
CharacteristicProxy[HeartRateService.BodySensorLocation] | None
|
||||||
]
|
)
|
||||||
heart_rate_control_point: Optional[CharacteristicProxy[int]]
|
heart_rate_control_point: CharacteristicProxy[int] | None
|
||||||
|
|
||||||
def __init__(self, service_proxy):
|
def __init__(self, service_proxy):
|
||||||
self.service_proxy = service_proxy
|
self.service_proxy = service_proxy
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import asyncio
|
|||||||
import dataclasses
|
import dataclasses
|
||||||
import enum
|
import enum
|
||||||
import struct
|
import struct
|
||||||
from typing import TYPE_CHECKING, ClassVar, Optional
|
from typing import TYPE_CHECKING, ClassVar
|
||||||
|
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ class MediaControlService(gatt.TemplateService):
|
|||||||
|
|
||||||
UUID = gatt.GATT_MEDIA_CONTROL_SERVICE
|
UUID = gatt.GATT_MEDIA_CONTROL_SERVICE
|
||||||
|
|
||||||
def __init__(self, media_player_name: Optional[str] = None) -> None:
|
def __init__(self, media_player_name: str | None = None) -> None:
|
||||||
self.track_position = 0
|
self.track_position = 0
|
||||||
|
|
||||||
self.media_player_name_characteristic = gatt.Characteristic(
|
self.media_player_name_characteristic = gatt.Characteristic(
|
||||||
@@ -337,32 +337,32 @@ class MediaControlServiceProxy(
|
|||||||
EVENT_TRACK_DURATION = "track_duration"
|
EVENT_TRACK_DURATION = "track_duration"
|
||||||
EVENT_TRACK_POSITION = "track_position"
|
EVENT_TRACK_POSITION = "track_position"
|
||||||
|
|
||||||
media_player_name: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
media_player_name: gatt_client.CharacteristicProxy[bytes] | None = None
|
||||||
media_player_icon_object_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
media_player_icon_object_id: gatt_client.CharacteristicProxy[bytes] | None = None
|
||||||
media_player_icon_url: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
media_player_icon_url: gatt_client.CharacteristicProxy[bytes] | None = None
|
||||||
track_changed: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
track_changed: gatt_client.CharacteristicProxy[bytes] | None = None
|
||||||
track_title: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
track_title: gatt_client.CharacteristicProxy[bytes] | None = None
|
||||||
track_duration: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
track_duration: gatt_client.CharacteristicProxy[bytes] | None = None
|
||||||
track_position: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
track_position: gatt_client.CharacteristicProxy[bytes] | None = None
|
||||||
playback_speed: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
playback_speed: gatt_client.CharacteristicProxy[bytes] | None = None
|
||||||
seeking_speed: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
seeking_speed: gatt_client.CharacteristicProxy[bytes] | None = None
|
||||||
current_track_segments_object_id: Optional[
|
current_track_segments_object_id: gatt_client.CharacteristicProxy[bytes] | None = (
|
||||||
gatt_client.CharacteristicProxy[bytes]
|
None
|
||||||
] = None
|
)
|
||||||
current_track_object_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
current_track_object_id: gatt_client.CharacteristicProxy[bytes] | None = None
|
||||||
next_track_object_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
next_track_object_id: gatt_client.CharacteristicProxy[bytes] | None = None
|
||||||
parent_group_object_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
parent_group_object_id: gatt_client.CharacteristicProxy[bytes] | None = None
|
||||||
current_group_object_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
current_group_object_id: gatt_client.CharacteristicProxy[bytes] | None = None
|
||||||
playing_order: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
playing_order: gatt_client.CharacteristicProxy[bytes] | None = None
|
||||||
playing_orders_supported: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
playing_orders_supported: gatt_client.CharacteristicProxy[bytes] | None = None
|
||||||
media_state: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
media_state: gatt_client.CharacteristicProxy[bytes] | None = None
|
||||||
media_control_point: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
media_control_point: gatt_client.CharacteristicProxy[bytes] | None = None
|
||||||
media_control_point_opcodes_supported: Optional[
|
media_control_point_opcodes_supported: (
|
||||||
gatt_client.CharacteristicProxy[bytes]
|
gatt_client.CharacteristicProxy[bytes] | None
|
||||||
] = None
|
) = None
|
||||||
search_control_point: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
search_control_point: gatt_client.CharacteristicProxy[bytes] | None = None
|
||||||
search_results_object_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
search_results_object_id: gatt_client.CharacteristicProxy[bytes] | None = None
|
||||||
content_control_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
content_control_id: gatt_client.CharacteristicProxy[bytes] | None = None
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
media_control_point_notifications: asyncio.Queue[bytes]
|
media_control_point_notifications: asyncio.Queue[bytes]
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from __future__ import annotations
|
|||||||
import dataclasses
|
import dataclasses
|
||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
from typing import Optional, Sequence, Union
|
from collections.abc import Sequence
|
||||||
|
|
||||||
from bumble import gatt, gatt_adapters, gatt_client, hci
|
from bumble import gatt, gatt_adapters, gatt_client, hci
|
||||||
from bumble.profiles import le_audio
|
from bumble.profiles import le_audio
|
||||||
@@ -39,7 +39,7 @@ class PacRecord:
|
|||||||
'''Published Audio Capabilities Service, Table 3.2/3.4.'''
|
'''Published Audio Capabilities Service, Table 3.2/3.4.'''
|
||||||
|
|
||||||
coding_format: hci.CodingFormat
|
coding_format: hci.CodingFormat
|
||||||
codec_specific_capabilities: Union[CodecSpecificCapabilities, bytes]
|
codec_specific_capabilities: CodecSpecificCapabilities | bytes
|
||||||
metadata: le_audio.Metadata = dataclasses.field(default_factory=le_audio.Metadata)
|
metadata: le_audio.Metadata = dataclasses.field(default_factory=le_audio.Metadata)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -56,7 +56,7 @@ class PacRecord:
|
|||||||
offset += 1
|
offset += 1
|
||||||
metadata = le_audio.Metadata.from_bytes(data[offset : offset + metadata_size])
|
metadata = le_audio.Metadata.from_bytes(data[offset : offset + metadata_size])
|
||||||
|
|
||||||
codec_specific_capabilities: Union[CodecSpecificCapabilities, bytes]
|
codec_specific_capabilities: CodecSpecificCapabilities | bytes
|
||||||
if coding_format.codec_id == hci.CodecID.VENDOR_SPECIFIC:
|
if coding_format.codec_id == hci.CodecID.VENDOR_SPECIFIC:
|
||||||
codec_specific_capabilities = codec_specific_capabilities_bytes
|
codec_specific_capabilities = codec_specific_capabilities_bytes
|
||||||
else:
|
else:
|
||||||
@@ -101,10 +101,10 @@ class PacRecord:
|
|||||||
class PublishedAudioCapabilitiesService(gatt.TemplateService):
|
class PublishedAudioCapabilitiesService(gatt.TemplateService):
|
||||||
UUID = gatt.GATT_PUBLISHED_AUDIO_CAPABILITIES_SERVICE
|
UUID = gatt.GATT_PUBLISHED_AUDIO_CAPABILITIES_SERVICE
|
||||||
|
|
||||||
sink_pac: Optional[gatt.Characteristic[bytes]]
|
sink_pac: gatt.Characteristic[bytes] | None
|
||||||
sink_audio_locations: Optional[gatt.Characteristic[bytes]]
|
sink_audio_locations: gatt.Characteristic[bytes] | None
|
||||||
source_pac: Optional[gatt.Characteristic[bytes]]
|
source_pac: gatt.Characteristic[bytes] | None
|
||||||
source_audio_locations: Optional[gatt.Characteristic[bytes]]
|
source_audio_locations: gatt.Characteristic[bytes] | None
|
||||||
available_audio_contexts: gatt.Characteristic[bytes]
|
available_audio_contexts: gatt.Characteristic[bytes]
|
||||||
supported_audio_contexts: gatt.Characteristic[bytes]
|
supported_audio_contexts: gatt.Characteristic[bytes]
|
||||||
|
|
||||||
@@ -115,9 +115,9 @@ class PublishedAudioCapabilitiesService(gatt.TemplateService):
|
|||||||
available_source_context: ContextType,
|
available_source_context: ContextType,
|
||||||
available_sink_context: ContextType,
|
available_sink_context: ContextType,
|
||||||
sink_pac: Sequence[PacRecord] = (),
|
sink_pac: Sequence[PacRecord] = (),
|
||||||
sink_audio_locations: Optional[AudioLocation] = None,
|
sink_audio_locations: AudioLocation | None = None,
|
||||||
source_pac: Sequence[PacRecord] = (),
|
source_pac: Sequence[PacRecord] = (),
|
||||||
source_audio_locations: Optional[AudioLocation] = None,
|
source_audio_locations: AudioLocation | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
characteristics = []
|
characteristics = []
|
||||||
|
|
||||||
@@ -183,14 +183,10 @@ class PublishedAudioCapabilitiesService(gatt.TemplateService):
|
|||||||
class PublishedAudioCapabilitiesServiceProxy(gatt_client.ProfileServiceProxy):
|
class PublishedAudioCapabilitiesServiceProxy(gatt_client.ProfileServiceProxy):
|
||||||
SERVICE_CLASS = PublishedAudioCapabilitiesService
|
SERVICE_CLASS = PublishedAudioCapabilitiesService
|
||||||
|
|
||||||
sink_pac: Optional[gatt_client.CharacteristicProxy[list[PacRecord]]] = None
|
sink_pac: gatt_client.CharacteristicProxy[list[PacRecord]] | None = None
|
||||||
sink_audio_locations: Optional[gatt_client.CharacteristicProxy[AudioLocation]] = (
|
sink_audio_locations: gatt_client.CharacteristicProxy[AudioLocation] | None = None
|
||||||
None
|
source_pac: gatt_client.CharacteristicProxy[list[PacRecord]] | None = None
|
||||||
)
|
source_audio_locations: gatt_client.CharacteristicProxy[AudioLocation] | None = None
|
||||||
source_pac: Optional[gatt_client.CharacteristicProxy[list[PacRecord]]] = None
|
|
||||||
source_audio_locations: Optional[gatt_client.CharacteristicProxy[AudioLocation]] = (
|
|
||||||
None
|
|
||||||
)
|
|
||||||
available_audio_contexts: gatt_client.CharacteristicProxy[tuple[ContextType, ...]]
|
available_audio_contexts: gatt_client.CharacteristicProxy[tuple[ContextType, ...]]
|
||||||
supported_audio_contexts: gatt_client.CharacteristicProxy[tuple[ContextType, ...]]
|
supported_audio_contexts: gatt_client.CharacteristicProxy[tuple[ContextType, ...]]
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import enum
|
import enum
|
||||||
from typing import Sequence
|
from collections.abc import Sequence
|
||||||
|
|
||||||
from bumble import att, device, gatt, gatt_adapters, gatt_client
|
from bumble import att, device, gatt, gatt_adapters, gatt_client
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
import struct
|
import struct
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from bumble import utils
|
from bumble import utils
|
||||||
from bumble.att import ATT_Error
|
from bumble.att import ATT_Error
|
||||||
@@ -69,7 +68,7 @@ class ErrorCode(utils.OpenIntEnum):
|
|||||||
class VolumeOffsetState:
|
class VolumeOffsetState:
|
||||||
volume_offset: int = 0
|
volume_offset: int = 0
|
||||||
change_counter: int = 0
|
change_counter: int = 0
|
||||||
attribute: Optional[Characteristic] = None
|
attribute: Characteristic | None = None
|
||||||
|
|
||||||
def __bytes__(self) -> bytes:
|
def __bytes__(self) -> bytes:
|
||||||
return struct.pack('<hB', self.volume_offset, self.change_counter)
|
return struct.pack('<hB', self.volume_offset, self.change_counter)
|
||||||
@@ -93,7 +92,7 @@ class VolumeOffsetState:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class VocsAudioLocation:
|
class VocsAudioLocation:
|
||||||
audio_location: AudioLocation = AudioLocation.NOT_ALLOWED
|
audio_location: AudioLocation = AudioLocation.NOT_ALLOWED
|
||||||
attribute: Optional[Characteristic] = None
|
attribute: Characteristic | None = None
|
||||||
|
|
||||||
def __bytes__(self) -> bytes:
|
def __bytes__(self) -> bytes:
|
||||||
return struct.pack('<I', self.audio_location)
|
return struct.pack('<I', self.audio_location)
|
||||||
@@ -147,7 +146,7 @@ class VolumeOffsetControlPoint:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class AudioOutputDescription:
|
class AudioOutputDescription:
|
||||||
audio_output_description: str = ''
|
audio_output_description: str = ''
|
||||||
attribute: Optional[Characteristic] = None
|
attribute: Characteristic | None = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_bytes(cls, data: bytes):
|
def from_bytes(cls, data: bytes):
|
||||||
@@ -172,9 +171,9 @@ class VolumeOffsetControlService(TemplateService):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
volume_offset_state: Optional[VolumeOffsetState] = None,
|
volume_offset_state: VolumeOffsetState | None = None,
|
||||||
audio_location: Optional[VocsAudioLocation] = None,
|
audio_location: VocsAudioLocation | None = None,
|
||||||
audio_output_description: Optional[AudioOutputDescription] = None,
|
audio_output_description: AudioOutputDescription | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.volume_offset_state = (
|
self.volume_offset_state = (
|
||||||
VolumeOffsetState() if volume_offset_state is None else volume_offset_state
|
VolumeOffsetState() if volume_offset_state is None else volume_offset_state
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ import collections
|
|||||||
import dataclasses
|
import dataclasses
|
||||||
import enum
|
import enum
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Callable, Optional, Union
|
from collections.abc import Callable
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
@@ -119,7 +120,7 @@ RFCOMM_DYNAMIC_CHANNEL_NUMBER_END = 30
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
def make_service_sdp_records(
|
def make_service_sdp_records(
|
||||||
service_record_handle: int, channel: int, uuid: Optional[UUID] = None
|
service_record_handle: int, channel: int, uuid: UUID | None = None
|
||||||
) -> list[sdp.ServiceAttribute]:
|
) -> list[sdp.ServiceAttribute]:
|
||||||
"""
|
"""
|
||||||
Create SDP records for an RFComm service given a channel number and an
|
Create SDP records for an RFComm service given a channel number and an
|
||||||
@@ -186,7 +187,7 @@ async def find_rfcomm_channels(connection: Connection) -> dict[int, list[UUID]]:
|
|||||||
)
|
)
|
||||||
for attribute_lists in search_result:
|
for attribute_lists in search_result:
|
||||||
service_classes: list[UUID] = []
|
service_classes: list[UUID] = []
|
||||||
channel: Optional[int] = None
|
channel: int | None = None
|
||||||
for attribute in attribute_lists:
|
for attribute in attribute_lists:
|
||||||
# The layout is [[L2CAP_PROTOCOL], [RFCOMM_PROTOCOL, RFCOMM_CHANNEL]].
|
# The layout is [[L2CAP_PROTOCOL], [RFCOMM_PROTOCOL, RFCOMM_CHANNEL]].
|
||||||
if attribute.id == sdp.SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID:
|
if attribute.id == sdp.SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID:
|
||||||
@@ -207,7 +208,7 @@ async def find_rfcomm_channels(connection: Connection) -> dict[int, list[UUID]]:
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
async def find_rfcomm_channel_with_uuid(
|
async def find_rfcomm_channel_with_uuid(
|
||||||
connection: Connection, uuid: str | UUID
|
connection: Connection, uuid: str | UUID
|
||||||
) -> Optional[int]:
|
) -> int | None:
|
||||||
"""Searches an RFCOMM channel associated with given UUID from service records.
|
"""Searches an RFCOMM channel associated with given UUID from service records.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -473,15 +474,15 @@ class DLC(utils.EventEmitter):
|
|||||||
self.state = DLC.State.INIT
|
self.state = DLC.State.INIT
|
||||||
self.role = multiplexer.role
|
self.role = multiplexer.role
|
||||||
self.c_r = 1 if self.role == Multiplexer.Role.INITIATOR else 0
|
self.c_r = 1 if self.role == Multiplexer.Role.INITIATOR else 0
|
||||||
self.connection_result: Optional[asyncio.Future] = None
|
self.connection_result: asyncio.Future | None = None
|
||||||
self.disconnection_result: Optional[asyncio.Future] = None
|
self.disconnection_result: asyncio.Future | None = None
|
||||||
self.drained = asyncio.Event()
|
self.drained = asyncio.Event()
|
||||||
self.drained.set()
|
self.drained.set()
|
||||||
# Queued packets when sink is not set.
|
# Queued packets when sink is not set.
|
||||||
self._enqueued_rx_packets: collections.deque[bytes] = collections.deque(
|
self._enqueued_rx_packets: collections.deque[bytes] = collections.deque(
|
||||||
maxlen=DEFAULT_RX_QUEUE_SIZE
|
maxlen=DEFAULT_RX_QUEUE_SIZE
|
||||||
)
|
)
|
||||||
self._sink: Optional[Callable[[bytes], None]] = None
|
self._sink: Callable[[bytes], None] | None = None
|
||||||
|
|
||||||
# Compute the MTU
|
# Compute the MTU
|
||||||
max_overhead = 4 + 1 # header with 2-byte length + fcs
|
max_overhead = 4 + 1 # header with 2-byte length + fcs
|
||||||
@@ -490,11 +491,11 @@ class DLC(utils.EventEmitter):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sink(self) -> Optional[Callable[[bytes], None]]:
|
def sink(self) -> Callable[[bytes], None] | None:
|
||||||
return self._sink
|
return self._sink
|
||||||
|
|
||||||
@sink.setter
|
@sink.setter
|
||||||
def sink(self, sink: Optional[Callable[[bytes], None]]) -> None:
|
def sink(self, sink: Callable[[bytes], None] | None) -> None:
|
||||||
self._sink = sink
|
self._sink = sink
|
||||||
# Dump queued packets to sink
|
# Dump queued packets to sink
|
||||||
if sink:
|
if sink:
|
||||||
@@ -712,7 +713,7 @@ class DLC(utils.EventEmitter):
|
|||||||
self.drained.set()
|
self.drained.set()
|
||||||
|
|
||||||
# Stream protocol
|
# Stream protocol
|
||||||
def write(self, data: Union[bytes, str]) -> None:
|
def write(self, data: bytes | str) -> None:
|
||||||
# We can only send bytes
|
# We can only send bytes
|
||||||
if not isinstance(data, bytes):
|
if not isinstance(data, bytes):
|
||||||
if isinstance(data, str):
|
if isinstance(data, str):
|
||||||
@@ -769,10 +770,10 @@ class Multiplexer(utils.EventEmitter):
|
|||||||
|
|
||||||
EVENT_DLC = "dlc"
|
EVENT_DLC = "dlc"
|
||||||
|
|
||||||
connection_result: Optional[asyncio.Future]
|
connection_result: asyncio.Future | None
|
||||||
disconnection_result: Optional[asyncio.Future]
|
disconnection_result: asyncio.Future | None
|
||||||
open_result: Optional[asyncio.Future]
|
open_result: asyncio.Future | None
|
||||||
acceptor: Optional[Callable[[int], Optional[tuple[int, int]]]]
|
acceptor: Callable[[int], tuple[int, int] | None] | None
|
||||||
dlcs: dict[int, DLC]
|
dlcs: dict[int, DLC]
|
||||||
|
|
||||||
def __init__(self, l2cap_channel: l2cap.ClassicChannel, role: Role) -> None:
|
def __init__(self, l2cap_channel: l2cap.ClassicChannel, role: Role) -> None:
|
||||||
@@ -784,7 +785,7 @@ class Multiplexer(utils.EventEmitter):
|
|||||||
self.connection_result = None
|
self.connection_result = None
|
||||||
self.disconnection_result = None
|
self.disconnection_result = None
|
||||||
self.open_result = None
|
self.open_result = None
|
||||||
self.open_pn: Optional[RFCOMM_MCC_PN] = None
|
self.open_pn: RFCOMM_MCC_PN | None = None
|
||||||
self.open_rx_max_credits = 0
|
self.open_rx_max_credits = 0
|
||||||
self.acceptor = None
|
self.acceptor = None
|
||||||
|
|
||||||
@@ -1031,8 +1032,8 @@ class Multiplexer(utils.EventEmitter):
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
class Client:
|
class Client:
|
||||||
multiplexer: Optional[Multiplexer]
|
multiplexer: Multiplexer | None
|
||||||
l2cap_channel: Optional[l2cap.ClassicChannel]
|
l2cap_channel: l2cap.ClassicChannel | None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, connection: Connection, l2cap_mtu: int = RFCOMM_DEFAULT_L2CAP_MTU
|
self, connection: Connection, l2cap_mtu: int = RFCOMM_DEFAULT_L2CAP_MTU
|
||||||
@@ -1145,7 +1146,7 @@ class Server(utils.EventEmitter):
|
|||||||
# Notify
|
# Notify
|
||||||
self.emit(self.EVENT_START, multiplexer)
|
self.emit(self.EVENT_START, multiplexer)
|
||||||
|
|
||||||
def accept_dlc(self, channel_number: int) -> Optional[tuple[int, int]]:
|
def accept_dlc(self, channel_number: int) -> tuple[int, int] | None:
|
||||||
return self.dlc_configs.get(channel_number)
|
return self.dlc_configs.get(channel_number)
|
||||||
|
|
||||||
def on_dlc(self, dlc: DLC) -> None:
|
def on_dlc(self, dlc: DLC) -> None:
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
from typing import TYPE_CHECKING, Iterable, NewType, Optional, Sequence, Union
|
from collections.abc import Iterable, Sequence
|
||||||
|
from typing import TYPE_CHECKING, NewType
|
||||||
|
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
@@ -497,7 +498,7 @@ class ServiceAttribute:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def find_attribute_in_list(
|
def find_attribute_in_list(
|
||||||
attribute_list: Iterable[ServiceAttribute], attribute_id: int
|
attribute_list: Iterable[ServiceAttribute], attribute_id: int
|
||||||
) -> Optional[DataElement]:
|
) -> DataElement | None:
|
||||||
return next(
|
return next(
|
||||||
(
|
(
|
||||||
attribute.value
|
attribute.value
|
||||||
@@ -778,11 +779,11 @@ class SDP_ServiceSearchAttributeResponse(SDP_PDU):
|
|||||||
class Client:
|
class Client:
|
||||||
def __init__(self, connection: Connection, mtu: int = 0) -> None:
|
def __init__(self, connection: Connection, mtu: int = 0) -> None:
|
||||||
self.connection = connection
|
self.connection = connection
|
||||||
self.channel: Optional[l2cap.ClassicChannel] = None
|
self.channel: l2cap.ClassicChannel | None = None
|
||||||
self.mtu = mtu
|
self.mtu = mtu
|
||||||
self.request_semaphore = asyncio.Semaphore(1)
|
self.request_semaphore = asyncio.Semaphore(1)
|
||||||
self.pending_request: Optional[SDP_PDU] = None
|
self.pending_request: SDP_PDU | None = None
|
||||||
self.pending_response: Optional[asyncio.futures.Future[SDP_PDU]] = None
|
self.pending_response: asyncio.futures.Future[SDP_PDU] | None = None
|
||||||
self.next_transaction_id = 0
|
self.next_transaction_id = 0
|
||||||
|
|
||||||
async def connect(self) -> None:
|
async def connect(self) -> None:
|
||||||
@@ -898,7 +899,7 @@ class Client:
|
|||||||
async def search_attributes(
|
async def search_attributes(
|
||||||
self,
|
self,
|
||||||
uuids: Iterable[core.UUID],
|
uuids: Iterable[core.UUID],
|
||||||
attribute_ids: Iterable[Union[int, tuple[int, int]]],
|
attribute_ids: Iterable[int | tuple[int, int]],
|
||||||
) -> list[list[ServiceAttribute]]:
|
) -> list[list[ServiceAttribute]]:
|
||||||
"""
|
"""
|
||||||
Search for attributes by UUID and attribute IDs.
|
Search for attributes by UUID and attribute IDs.
|
||||||
@@ -970,7 +971,7 @@ class Client:
|
|||||||
async def get_attributes(
|
async def get_attributes(
|
||||||
self,
|
self,
|
||||||
service_record_handle: int,
|
service_record_handle: int,
|
||||||
attribute_ids: Iterable[Union[int, tuple[int, int]]],
|
attribute_ids: Iterable[int | tuple[int, int]],
|
||||||
) -> list[ServiceAttribute]:
|
) -> list[ServiceAttribute]:
|
||||||
"""
|
"""
|
||||||
Get attributes for a service.
|
Get attributes for a service.
|
||||||
@@ -1042,10 +1043,10 @@ class Client:
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
class Server:
|
class Server:
|
||||||
CONTINUATION_STATE = bytes([0x01, 0x00])
|
CONTINUATION_STATE = bytes([0x01, 0x00])
|
||||||
channel: Optional[l2cap.ClassicChannel]
|
channel: l2cap.ClassicChannel | None
|
||||||
Service = NewType('Service', list[ServiceAttribute])
|
Service = NewType('Service', list[ServiceAttribute])
|
||||||
service_records: dict[int, Service]
|
service_records: dict[int, Service]
|
||||||
current_response: Union[None, bytes, tuple[int, list[int]]]
|
current_response: None | bytes | tuple[int, list[int]]
|
||||||
|
|
||||||
def __init__(self, device: Device) -> None:
|
def __init__(self, device: Device) -> None:
|
||||||
self.device = device
|
self.device = device
|
||||||
@@ -1123,7 +1124,7 @@ class Server:
|
|||||||
self,
|
self,
|
||||||
continuation_state: bytes,
|
continuation_state: bytes,
|
||||||
transaction_id: int,
|
transaction_id: int,
|
||||||
) -> Optional[bool]:
|
) -> bool | None:
|
||||||
# Check if this is a valid continuation
|
# Check if this is a valid continuation
|
||||||
if len(continuation_state) > 1:
|
if len(continuation_state) > 1:
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -27,8 +27,9 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
import enum
|
import enum
|
||||||
import logging
|
import logging
|
||||||
|
from collections.abc import Awaitable, Callable
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import TYPE_CHECKING, Awaitable, Callable, ClassVar, Optional, TypeVar, cast
|
from typing import TYPE_CHECKING, ClassVar, TypeVar, cast
|
||||||
|
|
||||||
from bumble import crypto, utils
|
from bumble import crypto, utils
|
||||||
from bumble.colors import color
|
from bumble.colors import color
|
||||||
@@ -204,10 +205,10 @@ class SMP_Command:
|
|||||||
fields: ClassVar[Fields]
|
fields: ClassVar[Fields]
|
||||||
code: int = field(default=0, init=False)
|
code: int = field(default=0, init=False)
|
||||||
name: str = field(default='', init=False)
|
name: str = field(default='', init=False)
|
||||||
_payload: Optional[bytes] = field(default=None, init=False)
|
_payload: bytes | None = field(default=None, init=False)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_bytes(cls, pdu: bytes) -> "SMP_Command":
|
def from_bytes(cls, pdu: bytes) -> SMP_Command:
|
||||||
code = pdu[0]
|
code = pdu[0]
|
||||||
|
|
||||||
subclass = SMP_Command.smp_classes.get(code)
|
subclass = SMP_Command.smp_classes.get(code)
|
||||||
@@ -545,7 +546,7 @@ class OobContext:
|
|||||||
r: bytes
|
r: bytes
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, ecc_key: Optional[crypto.EccKey] = None, r: Optional[bytes] = None
|
self, ecc_key: crypto.EccKey | None = None, r: bytes | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
self.ecc_key = crypto.EccKey.generate() if ecc_key is None else ecc_key
|
self.ecc_key = crypto.EccKey.generate() if ecc_key is None else ecc_key
|
||||||
self.r = crypto.r() if r is None else r
|
self.r = crypto.r() if r is None else r
|
||||||
@@ -561,7 +562,7 @@ class OobLegacyContext:
|
|||||||
|
|
||||||
tk: bytes
|
tk: bytes
|
||||||
|
|
||||||
def __init__(self, tk: Optional[bytes] = None) -> None:
|
def __init__(self, tk: bytes | None = None) -> None:
|
||||||
self.tk = crypto.r() if tk is None else tk
|
self.tk = crypto.r() if tk is None else tk
|
||||||
|
|
||||||
|
|
||||||
@@ -668,31 +669,31 @@ class Session:
|
|||||||
self.stk = None
|
self.stk = None
|
||||||
self.ltk_ediv = 0
|
self.ltk_ediv = 0
|
||||||
self.ltk_rand = bytes(8)
|
self.ltk_rand = bytes(8)
|
||||||
self.link_key: Optional[bytes] = None
|
self.link_key: bytes | None = None
|
||||||
self.maximum_encryption_key_size: int = 0
|
self.maximum_encryption_key_size: int = 0
|
||||||
self.initiator_key_distribution: int = 0
|
self.initiator_key_distribution: int = 0
|
||||||
self.responder_key_distribution: int = 0
|
self.responder_key_distribution: int = 0
|
||||||
self.peer_random_value: Optional[bytes] = None
|
self.peer_random_value: bytes | None = None
|
||||||
self.peer_public_key_x: bytes = bytes(32)
|
self.peer_public_key_x: bytes = bytes(32)
|
||||||
self.peer_public_key_y = bytes(32)
|
self.peer_public_key_y = bytes(32)
|
||||||
self.peer_ltk = None
|
self.peer_ltk = None
|
||||||
self.peer_ediv = None
|
self.peer_ediv = None
|
||||||
self.peer_rand: Optional[bytes] = None
|
self.peer_rand: bytes | None = None
|
||||||
self.peer_identity_resolving_key = None
|
self.peer_identity_resolving_key = None
|
||||||
self.peer_bd_addr: Optional[Address] = None
|
self.peer_bd_addr: Address | None = None
|
||||||
self.peer_signature_key = None
|
self.peer_signature_key = None
|
||||||
self.peer_expected_distributions: list[type[SMP_Command]] = []
|
self.peer_expected_distributions: list[type[SMP_Command]] = []
|
||||||
self.dh_key = b''
|
self.dh_key = b''
|
||||||
self.confirm_value = None
|
self.confirm_value = None
|
||||||
self.passkey: Optional[int] = None
|
self.passkey: int | None = None
|
||||||
self.passkey_ready = asyncio.Event()
|
self.passkey_ready = asyncio.Event()
|
||||||
self.passkey_step = 0
|
self.passkey_step = 0
|
||||||
self.passkey_display = False
|
self.passkey_display = False
|
||||||
self.pairing_method: PairingMethod = PairingMethod.JUST_WORKS
|
self.pairing_method: PairingMethod = PairingMethod.JUST_WORKS
|
||||||
self.pairing_config = pairing_config
|
self.pairing_config = pairing_config
|
||||||
self.wait_before_continuing: Optional[asyncio.Future[None]] = None
|
self.wait_before_continuing: asyncio.Future[None] | None = None
|
||||||
self.completed = False
|
self.completed = False
|
||||||
self.ctkd_task: Optional[Awaitable[None]] = None
|
self.ctkd_task: Awaitable[None] | None = None
|
||||||
|
|
||||||
# Decide if we're the initiator or the responder
|
# Decide if we're the initiator or the responder
|
||||||
self.is_initiator = is_initiator
|
self.is_initiator = is_initiator
|
||||||
@@ -711,7 +712,7 @@ class Session:
|
|||||||
|
|
||||||
# Create a future that can be used to wait for the session to complete
|
# Create a future that can be used to wait for the session to complete
|
||||||
if self.is_initiator:
|
if self.is_initiator:
|
||||||
self.pairing_result: Optional[asyncio.Future[None]] = (
|
self.pairing_result: asyncio.Future[None] | None = (
|
||||||
asyncio.get_running_loop().create_future()
|
asyncio.get_running_loop().create_future()
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -819,7 +820,7 @@ class Session:
|
|||||||
def auth_req(self) -> int:
|
def auth_req(self) -> int:
|
||||||
return smp_auth_req(self.bonding, self.mitm, self.sc, self.keypress, self.ct2)
|
return smp_auth_req(self.bonding, self.mitm, self.sc, self.keypress, self.ct2)
|
||||||
|
|
||||||
def get_long_term_key(self, rand: bytes, ediv: int) -> Optional[bytes]:
|
def get_long_term_key(self, rand: bytes, ediv: int) -> bytes | None:
|
||||||
if not self.sc and not self.completed:
|
if not self.sc and not self.completed:
|
||||||
if rand == self.ltk_rand and ediv == self.ltk_ediv:
|
if rand == self.ltk_rand and ediv == self.ltk_ediv:
|
||||||
return self.stk
|
return self.stk
|
||||||
@@ -930,7 +931,7 @@ class Session:
|
|||||||
self.pairing_config.delegate.display_number(self.passkey, digits=6)
|
self.pairing_config.delegate.display_number(self.passkey, digits=6)
|
||||||
)
|
)
|
||||||
|
|
||||||
def input_passkey(self, next_steps: Optional[Callable[[], None]] = None) -> None:
|
def input_passkey(self, next_steps: Callable[[], None] | None = None) -> None:
|
||||||
# Prompt the user for the passkey displayed on the peer
|
# Prompt the user for the passkey displayed on the peer
|
||||||
def after_input(passkey: int) -> None:
|
def after_input(passkey: int) -> None:
|
||||||
self.passkey = passkey
|
self.passkey = passkey
|
||||||
@@ -947,7 +948,7 @@ class Session:
|
|||||||
self.prompt_user_for_number(after_input)
|
self.prompt_user_for_number(after_input)
|
||||||
|
|
||||||
def display_or_input_passkey(
|
def display_or_input_passkey(
|
||||||
self, next_steps: Optional[Callable[[], None]] = None
|
self, next_steps: Callable[[], None] | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
if self.passkey_display:
|
if self.passkey_display:
|
||||||
|
|
||||||
@@ -1918,7 +1919,7 @@ class Manager(utils.EventEmitter):
|
|||||||
sessions: dict[int, Session]
|
sessions: dict[int, Session]
|
||||||
pairing_config_factory: Callable[[Connection], PairingConfig]
|
pairing_config_factory: Callable[[Connection], PairingConfig]
|
||||||
session_proxy: type[Session]
|
session_proxy: type[Session]
|
||||||
_ecc_key: Optional[crypto.EccKey]
|
_ecc_key: crypto.EccKey | None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -2011,7 +2012,7 @@ class Manager(utils.EventEmitter):
|
|||||||
self.device.on_pairing_start(session.connection)
|
self.device.on_pairing_start(session.connection)
|
||||||
|
|
||||||
async def on_pairing(
|
async def on_pairing(
|
||||||
self, session: Session, identity_address: Optional[Address], keys: PairingKeys
|
self, session: Session, identity_address: Address | None, keys: PairingKeys
|
||||||
) -> None:
|
) -> None:
|
||||||
# Store the keys in the key store
|
# Store the keys in the key store
|
||||||
if self.device.keystore and identity_address is not None:
|
if self.device.keystore and identity_address is not None:
|
||||||
@@ -2030,7 +2031,7 @@ class Manager(utils.EventEmitter):
|
|||||||
|
|
||||||
def get_long_term_key(
|
def get_long_term_key(
|
||||||
self, connection: Connection, rand: bytes, ediv: int
|
self, connection: Connection, rand: bytes, ediv: int
|
||||||
) -> Optional[bytes]:
|
) -> bytes | None:
|
||||||
if session := self.sessions.get(connection.handle):
|
if session := self.sessions.get(connection.handle):
|
||||||
return session.get_long_term_key(rand, ediv)
|
return session.get_long_term_key(rand, ediv)
|
||||||
|
|
||||||
|
|||||||
@@ -16,13 +16,14 @@ import datetime
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
|
from collections.abc import Generator
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Imports
|
# Imports
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from typing import BinaryIO, Generator
|
from typing import BinaryIO
|
||||||
|
|
||||||
from bumble import core
|
from bumble import core
|
||||||
from bumble.hci import HCI_COMMAND_PACKET, HCI_EVENT_PACKET
|
from bumble.hci import HCI_COMMAND_PACKET, HCI_EVENT_PACKET
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from bumble import utils
|
from bumble import utils
|
||||||
from bumble.snoop import create_snooper
|
from bumble.snoop import create_snooper
|
||||||
@@ -111,7 +110,7 @@ async def open_transport(name: str) -> Transport:
|
|||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
async def _open_transport(scheme: str, spec: Optional[str]) -> Transport:
|
async def _open_transport(scheme: str, spec: str | None) -> Transport:
|
||||||
# pylint: disable=import-outside-toplevel
|
# pylint: disable=import-outside-toplevel
|
||||||
# pylint: disable=too-many-return-statements
|
# pylint: disable=too-many-return-statements
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
# Imports
|
# Imports
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional, Union
|
|
||||||
|
|
||||||
import grpc.aio
|
import grpc.aio
|
||||||
|
|
||||||
@@ -44,7 +43,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
async def open_android_emulator_transport(spec: Optional[str]) -> Transport:
|
async def open_android_emulator_transport(spec: str | None) -> Transport:
|
||||||
'''
|
'''
|
||||||
Open a transport connection to an Android emulator via its gRPC interface.
|
Open a transport connection to an Android emulator via its gRPC interface.
|
||||||
The parameter string has this syntax:
|
The parameter string has this syntax:
|
||||||
@@ -89,7 +88,7 @@ async def open_android_emulator_transport(spec: Optional[str]) -> Transport:
|
|||||||
logger.debug('connecting to gRPC server at %s', server_address)
|
logger.debug('connecting to gRPC server at %s', server_address)
|
||||||
channel = grpc.aio.insecure_channel(server_address)
|
channel = grpc.aio.insecure_channel(server_address)
|
||||||
|
|
||||||
service: Union[EmulatedBluetoothServiceStub, VhciForwardingServiceStub]
|
service: EmulatedBluetoothServiceStub | VhciForwardingServiceStub
|
||||||
if mode == 'host':
|
if mode == 'host':
|
||||||
# Connect as a host
|
# Connect as a host
|
||||||
service = EmulatedBluetoothServiceStub(channel)
|
service = EmulatedBluetoothServiceStub(channel)
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import os
|
|||||||
import pathlib
|
import pathlib
|
||||||
import platform
|
import platform
|
||||||
import sys
|
import sys
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import grpc.aio
|
import grpc.aio
|
||||||
|
|
||||||
@@ -66,7 +65,7 @@ DEFAULT_VARIANT = ''
|
|||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
def get_ini_dir() -> Optional[pathlib.Path]:
|
def get_ini_dir() -> pathlib.Path | None:
|
||||||
if sys.platform == 'darwin':
|
if sys.platform == 'darwin':
|
||||||
if tmpdir := os.getenv('TMPDIR', None):
|
if tmpdir := os.getenv('TMPDIR', None):
|
||||||
return pathlib.Path(tmpdir)
|
return pathlib.Path(tmpdir)
|
||||||
@@ -100,7 +99,7 @@ def find_grpc_port(instance_number: int) -> int:
|
|||||||
ini_file = ini_dir / ini_file_name(instance_number)
|
ini_file = ini_dir / ini_file_name(instance_number)
|
||||||
logger.debug(f'Looking for .ini file at {ini_file}')
|
logger.debug(f'Looking for .ini file at {ini_file}')
|
||||||
if ini_file.is_file():
|
if ini_file.is_file():
|
||||||
with open(ini_file, 'r') as ini_file_data:
|
with open(ini_file) as ini_file_data:
|
||||||
for line in ini_file_data.readlines():
|
for line in ini_file_data.readlines():
|
||||||
if '=' in line:
|
if '=' in line:
|
||||||
key, value = line.split('=')
|
key, value = line.split('=')
|
||||||
@@ -146,7 +145,7 @@ def publish_grpc_port(grpc_port: int, instance_number: int) -> bool:
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
async def open_android_netsim_controller_transport(
|
async def open_android_netsim_controller_transport(
|
||||||
server_host: Optional[str], server_port: int, options: dict[str, str]
|
server_host: str | None, server_port: int, options: dict[str, str]
|
||||||
) -> Transport:
|
) -> Transport:
|
||||||
if server_host == '_' or not server_host:
|
if server_host == '_' or not server_host:
|
||||||
server_host = 'localhost'
|
server_host = 'localhost'
|
||||||
@@ -301,9 +300,9 @@ async def open_android_netsim_controller_transport(
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
async def open_android_netsim_host_transport_with_address(
|
async def open_android_netsim_host_transport_with_address(
|
||||||
server_host: Optional[str],
|
server_host: str | None,
|
||||||
server_port: int,
|
server_port: int,
|
||||||
options: Optional[dict[str, str]] = None,
|
options: dict[str, str] | None = None,
|
||||||
):
|
):
|
||||||
if server_host == '_' or not server_host:
|
if server_host == '_' or not server_host:
|
||||||
server_host = 'localhost'
|
server_host = 'localhost'
|
||||||
@@ -328,7 +327,7 @@ async def open_android_netsim_host_transport_with_address(
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
async def open_android_netsim_host_transport_with_channel(
|
async def open_android_netsim_host_transport_with_channel(
|
||||||
channel, options: Optional[dict[str, str]] = None
|
channel, options: dict[str, str] | None = None
|
||||||
):
|
):
|
||||||
# Wrapper for I/O operations
|
# Wrapper for I/O operations
|
||||||
class HciDevice:
|
class HciDevice:
|
||||||
@@ -408,7 +407,7 @@ async def open_android_netsim_host_transport_with_channel(
|
|||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
async def open_android_netsim_transport(spec: Optional[str]) -> Transport:
|
async def open_android_netsim_transport(spec: str | None) -> Transport:
|
||||||
'''
|
'''
|
||||||
Open a transport connection as a client or server, implementing Android's `netsim`
|
Open a transport connection as a client or server, implementing Android's `netsim`
|
||||||
simulator protocol over gRPC.
|
simulator protocol over gRPC.
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import io
|
|||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
from typing import Any, ContextManager, Optional, Protocol
|
from typing import Any, Protocol
|
||||||
|
|
||||||
from bumble import core, hci
|
from bumble import core, hci
|
||||||
from bumble.colors import color
|
from bumble.colors import color
|
||||||
@@ -107,11 +107,11 @@ class PacketParser:
|
|||||||
NEED_LENGTH = 1
|
NEED_LENGTH = 1
|
||||||
NEED_BODY = 2
|
NEED_BODY = 2
|
||||||
|
|
||||||
sink: Optional[TransportSink]
|
sink: TransportSink | None
|
||||||
extended_packet_info: dict[int, tuple[int, int, str]]
|
extended_packet_info: dict[int, tuple[int, int, str]]
|
||||||
packet_info: Optional[tuple[int, int, str]] = None
|
packet_info: tuple[int, int, str] | None = None
|
||||||
|
|
||||||
def __init__(self, sink: Optional[TransportSink] = None) -> None:
|
def __init__(self, sink: TransportSink | None = None) -> None:
|
||||||
self.sink = sink
|
self.sink = sink
|
||||||
self.extended_packet_info = {}
|
self.extended_packet_info = {}
|
||||||
self.reset()
|
self.reset()
|
||||||
@@ -176,7 +176,7 @@ class PacketReader:
|
|||||||
self.source = source
|
self.source = source
|
||||||
self.at_end = False
|
self.at_end = False
|
||||||
|
|
||||||
def next_packet(self) -> Optional[bytes]:
|
def next_packet(self) -> bytes | None:
|
||||||
# Get the packet type
|
# Get the packet type
|
||||||
packet_type = self.source.read(1)
|
packet_type = self.source.read(1)
|
||||||
if len(packet_type) != 1:
|
if len(packet_type) != 1:
|
||||||
@@ -253,7 +253,7 @@ class BaseSource:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
terminated: asyncio.Future[None]
|
terminated: asyncio.Future[None]
|
||||||
sink: Optional[TransportSink]
|
sink: TransportSink | None
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.terminated = asyncio.get_running_loop().create_future()
|
self.terminated = asyncio.get_running_loop().create_future()
|
||||||
@@ -357,7 +357,7 @@ class Transport:
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
class PumpedPacketSource(ParserSource):
|
class PumpedPacketSource(ParserSource):
|
||||||
pump_task: Optional[asyncio.Task[None]]
|
pump_task: asyncio.Task[None] | None
|
||||||
|
|
||||||
def __init__(self, receive) -> None:
|
def __init__(self, receive) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -390,7 +390,7 @@ class PumpedPacketSource(ParserSource):
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
class PumpedPacketSink:
|
class PumpedPacketSink:
|
||||||
pump_task: Optional[asyncio.Task[None]]
|
pump_task: asyncio.Task[None] | None
|
||||||
|
|
||||||
def __init__(self, send: Callable[[bytes], Awaitable[Any]]):
|
def __init__(self, send: Callable[[bytes], Awaitable[Any]]):
|
||||||
self.send_function = send
|
self.send_function = send
|
||||||
@@ -443,7 +443,7 @@ class SnoopingTransport(Transport):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_with(
|
def create_with(
|
||||||
transport: Transport, snooper: ContextManager[Snooper]
|
transport: Transport, snooper: contextlib.AbstractContextManager[Snooper]
|
||||||
) -> SnoopingTransport:
|
) -> SnoopingTransport:
|
||||||
"""
|
"""
|
||||||
Create an instance given a snooper that works as as context manager.
|
Create an instance given a snooper that works as as context manager.
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
# Imports
|
# Imports
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
import asyncio
|
import asyncio
|
||||||
import io
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from bumble.transport.common import StreamPacketSink, StreamPacketSource, Transport
|
from bumble.transport.common import StreamPacketSink, StreamPacketSource, Transport
|
||||||
@@ -36,7 +35,7 @@ async def open_file_transport(spec: str) -> Transport:
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
# Open the file
|
# Open the file
|
||||||
file = io.open(spec, 'r+b', buffering=0)
|
file = open(spec, 'r+b', buffering=0)
|
||||||
|
|
||||||
# Setup reading
|
# Setup reading
|
||||||
read_transport, packet_source = await asyncio.get_running_loop().connect_read_pipe(
|
read_transport, packet_source = await asyncio.get_running_loop().connect_read_pipe(
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
import struct
|
import struct
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from bumble.transport.common import ParserSource, Transport
|
from bumble.transport.common import ParserSource, Transport
|
||||||
|
|
||||||
@@ -33,7 +32,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
async def open_hci_socket_transport(spec: Optional[str]) -> Transport:
|
async def open_hci_socket_transport(spec: str | None) -> Transport:
|
||||||
'''
|
'''
|
||||||
Open an HCI Socket (only available on some platforms).
|
Open an HCI Socket (only available on some platforms).
|
||||||
The parameter string is either empty (to use the first/default Bluetooth adapter)
|
The parameter string is either empty (to use the first/default Bluetooth adapter)
|
||||||
@@ -87,7 +86,7 @@ async def open_hci_socket_transport(spec: Optional[str]) -> Transport:
|
|||||||
)
|
)
|
||||||
!= 0
|
!= 0
|
||||||
):
|
):
|
||||||
raise IOError(ctypes.get_errno(), os.strerror(ctypes.get_errno()))
|
raise OSError(ctypes.get_errno(), os.strerror(ctypes.get_errno()))
|
||||||
|
|
||||||
class HciSocketSource(ParserSource):
|
class HciSocketSource(ParserSource):
|
||||||
def __init__(self, hci_socket):
|
def __init__(self, hci_socket):
|
||||||
|
|||||||
@@ -17,12 +17,10 @@
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
import asyncio
|
import asyncio
|
||||||
import atexit
|
import atexit
|
||||||
import io
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import pty
|
import pty
|
||||||
import tty
|
import tty
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from bumble.transport.common import StreamPacketSink, StreamPacketSource, Transport
|
from bumble.transport.common import StreamPacketSink, StreamPacketSource, Transport
|
||||||
|
|
||||||
@@ -33,7 +31,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
async def open_pty_transport(spec: Optional[str]) -> Transport:
|
async def open_pty_transport(spec: str | None) -> Transport:
|
||||||
'''
|
'''
|
||||||
Open a PTY transport.
|
Open a PTY transport.
|
||||||
The parameter string may be empty, or a path name where a symbolic link
|
The parameter string may be empty, or a path name where a symbolic link
|
||||||
@@ -48,11 +46,11 @@ async def open_pty_transport(spec: Optional[str]) -> Transport:
|
|||||||
tty.setraw(replica)
|
tty.setraw(replica)
|
||||||
|
|
||||||
read_transport, packet_source = await asyncio.get_running_loop().connect_read_pipe(
|
read_transport, packet_source = await asyncio.get_running_loop().connect_read_pipe(
|
||||||
StreamPacketSource, io.open(primary, 'rb', closefd=False)
|
StreamPacketSource, open(primary, 'rb', closefd=False)
|
||||||
)
|
)
|
||||||
|
|
||||||
write_transport, _ = await asyncio.get_running_loop().connect_write_pipe(
|
write_transport, _ = await asyncio.get_running_loop().connect_write_pipe(
|
||||||
asyncio.BaseProtocol, io.open(primary, 'wb', closefd=False)
|
asyncio.BaseProtocol, open(primary, 'wb', closefd=False)
|
||||||
)
|
)
|
||||||
packet_sink = StreamPacketSink(write_transport)
|
packet_sink = StreamPacketSink(write_transport)
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import usb.core
|
import usb.core
|
||||||
import usb.util
|
import usb.util
|
||||||
@@ -389,7 +388,7 @@ def _set_port_status(device: UsbDevice, port: int, on: bool):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _find_device_by_path(sys_path: str) -> Optional[UsbDevice]:
|
def _find_device_by_path(sys_path: str) -> UsbDevice | None:
|
||||||
"""Finds a USB device based on its system path."""
|
"""Finds a USB device based on its system path."""
|
||||||
bus_num, *port_parts = sys_path.split('-')
|
bus_num, *port_parts = sys_path.split('-')
|
||||||
ports = [int(port) for port in port_parts[0].split('.')]
|
ports = [int(port) for port in port_parts[0].split('.')]
|
||||||
@@ -402,7 +401,7 @@ def _find_device_by_path(sys_path: str) -> Optional[UsbDevice]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _find_hub_by_device_path(sys_path: str) -> Optional[UsbDevice]:
|
def _find_hub_by_device_path(sys_path: str) -> UsbDevice | None:
|
||||||
"""Finds the USB hub associated with a specific device path."""
|
"""Finds the USB hub associated with a specific device path."""
|
||||||
hub_sys_path = sys_path.rsplit('.', 1)[0]
|
hub_sys_path = sys_path.rsplit('.', 1)[0]
|
||||||
hub_device = _find_device_by_path(hub_sys_path)
|
hub_device = _find_device_by_path(hub_sys_path)
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import serial_asyncio
|
import serial_asyncio
|
||||||
|
|
||||||
@@ -52,7 +51,7 @@ class SerialPacketSource(StreamPacketSource):
|
|||||||
logger.debug('connection made')
|
logger.debug('connection made')
|
||||||
self._ready.set()
|
self._ready.set()
|
||||||
|
|
||||||
def connection_lost(self, exc: Optional[Exception]) -> None:
|
def connection_lost(self, exc: Exception | None) -> None:
|
||||||
logger.debug('connection lost')
|
logger.debug('connection lost')
|
||||||
self.on_transport_lost()
|
self.on_transport_lost()
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
# Imports
|
# Imports
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from bumble.transport.common import Transport
|
from bumble.transport.common import Transport
|
||||||
from bumble.transport.file import open_file_transport
|
from bumble.transport.file import open_file_transport
|
||||||
@@ -28,7 +27,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
async def open_vhci_transport(spec: Optional[str]) -> Transport:
|
async def open_vhci_transport(spec: str | None) -> Transport:
|
||||||
'''
|
'''
|
||||||
Open a VHCI transport (only available on some platforms).
|
Open a VHCI transport (only available on some platforms).
|
||||||
The parameter string is either empty (to use the default VHCI device
|
The parameter string is either empty (to use the default VHCI device
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
# Imports
|
# Imports
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import websockets.asyncio.server
|
import websockets.asyncio.server
|
||||||
|
|
||||||
@@ -43,8 +42,8 @@ async def open_ws_server_transport(spec: str) -> Transport:
|
|||||||
class WsServerTransport(Transport):
|
class WsServerTransport(Transport):
|
||||||
sink: PumpedPacketSink
|
sink: PumpedPacketSink
|
||||||
source: ParserSource
|
source: ParserSource
|
||||||
connection: Optional[websockets.asyncio.server.ServerConnection]
|
connection: websockets.asyncio.server.ServerConnection | None
|
||||||
server: Optional[websockets.asyncio.server.Server]
|
server: websockets.asyncio.server.Server | None
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
source = ParserSource()
|
source = ParserSource()
|
||||||
|
|||||||
@@ -23,14 +23,11 @@ import enum
|
|||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
import warnings
|
import warnings
|
||||||
|
from collections.abc import Awaitable, Callable
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
Awaitable,
|
|
||||||
Callable,
|
|
||||||
Optional,
|
|
||||||
Protocol,
|
Protocol,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Union,
|
|
||||||
overload,
|
overload,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -169,8 +166,8 @@ class EventWatcher:
|
|||||||
) -> _Handler: ...
|
) -> _Handler: ...
|
||||||
|
|
||||||
def on(
|
def on(
|
||||||
self, emitter: pyee.EventEmitter, event: str, handler: Optional[_Handler] = None
|
self, emitter: pyee.EventEmitter, event: str, handler: _Handler | None = None
|
||||||
) -> Union[_Handler, Callable[[_Handler], _Handler]]:
|
) -> _Handler | Callable[[_Handler], _Handler]:
|
||||||
'''Watch an event until the context is closed.
|
'''Watch an event until the context is closed.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -198,8 +195,8 @@ class EventWatcher:
|
|||||||
) -> _Handler: ...
|
) -> _Handler: ...
|
||||||
|
|
||||||
def once(
|
def once(
|
||||||
self, emitter: pyee.EventEmitter, event: str, handler: Optional[_Handler] = None
|
self, emitter: pyee.EventEmitter, event: str, handler: _Handler | None = None
|
||||||
) -> Union[_Handler, Callable[[_Handler], _Handler]]:
|
) -> _Handler | Callable[[_Handler], _Handler]:
|
||||||
'''Watch an event for once.
|
'''Watch an event for once.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
3
bumble/vendor/android/hci.py
vendored
3
bumble/vendor/android/hci.py
vendored
@@ -18,7 +18,6 @@
|
|||||||
import dataclasses
|
import dataclasses
|
||||||
import struct
|
import struct
|
||||||
from dataclasses import field
|
from dataclasses import field
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from bumble import hci
|
from bumble import hci
|
||||||
|
|
||||||
@@ -209,7 +208,7 @@ class HCI_Android_Vendor_Event(hci.HCI_Extended_Event):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def subclass_from_parameters(
|
def subclass_from_parameters(
|
||||||
cls, parameters: bytes
|
cls, parameters: bytes
|
||||||
) -> Optional[hci.HCI_Extended_Event]:
|
) -> hci.HCI_Extended_Event | None:
|
||||||
subevent_code = parameters[0]
|
subevent_code = parameters[0]
|
||||||
if subevent_code == HCI_BLUETOOTH_QUALITY_REPORT_EVENT:
|
if subevent_code == HCI_BLUETOOTH_QUALITY_REPORT_EVENT:
|
||||||
quality_report_id = parameters[1]
|
quality_report_id = parameters[1]
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class Keyboard:
|
|||||||
def decode_keyboard_report(self, input_report: bytes, report_length: int) -> None:
|
def decode_keyboard_report(self, input_report: bytes, report_length: int) -> None:
|
||||||
if report_length >= 8:
|
if report_length >= 8:
|
||||||
modifier = input_report[1]
|
modifier = input_report[1]
|
||||||
self.report[0] = [int(x) for x in '{0:08b}'.format(modifier)]
|
self.report[0] = [int(x) for x in f'{modifier:08b}']
|
||||||
self.report[0].reverse() # type: ignore
|
self.report[0].reverse() # type: ignore
|
||||||
|
|
||||||
modifier_key = str((modifier & 0x22).to_bytes(1, "big").hex())
|
modifier_key = str((modifier & 0x22).to_bytes(1, "big").hex())
|
||||||
@@ -106,7 +106,7 @@ class Mouse:
|
|||||||
]
|
]
|
||||||
|
|
||||||
def decode_mouse_report(self, input_report: bytes, report_length: int) -> None:
|
def decode_mouse_report(self, input_report: bytes, report_length: int) -> None:
|
||||||
self.report[0] = [int(x) for x in '{0:08b}'.format(input_report[1])]
|
self.report[0] = [int(x) for x in f'{input_report[1]:08b}']
|
||||||
self.report[0].reverse() # type: ignore
|
self.report[0].reverse() # type: ignore
|
||||||
self.report[1] = input_report[2]
|
self.report[1] = input_report[2]
|
||||||
self.report[2] = input_report[3]
|
self.report[2] = input_report[3]
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ async def handle_command_client(
|
|||||||
await ams_client.command(RemoteCommandId[command.upper()])
|
await ams_client.command(RemoteCommandId[command.upper()])
|
||||||
continue
|
continue
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
writer.write(f"ERROR: {error}\n".encode("utf-8"))
|
writer.write(f"ERROR: {error}\n".encode())
|
||||||
|
|
||||||
writer.write(f"unknown command {command}\n".encode("utf-8"))
|
writer.write(f"unknown command {command}\n".encode())
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -91,15 +91,13 @@ async def process_notifications(ancs_client: AncsClient):
|
|||||||
notification.notification_uid, requested_attributes
|
notification.notification_uid, requested_attributes
|
||||||
)
|
)
|
||||||
max_attribute_name_width = max(
|
max_attribute_name_width = max(
|
||||||
(len(attribute.attribute_id.name) for attribute in attributes)
|
len(attribute.attribute_id.name) for attribute in attributes
|
||||||
)
|
)
|
||||||
app_identifier = str(
|
app_identifier = str(
|
||||||
next(
|
next(
|
||||||
(
|
attribute.value
|
||||||
attribute.value
|
for attribute in attributes
|
||||||
for attribute in attributes
|
if attribute.attribute_id == NotificationAttributeId.APP_IDENTIFIER
|
||||||
if attribute.attribute_id == NotificationAttributeId.APP_IDENTIFIER
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if app_identifier not in _cached_app_names:
|
if app_identifier not in _cached_app_names:
|
||||||
@@ -145,9 +143,9 @@ async def handle_command_client(
|
|||||||
notification_uid = int(command_args)
|
notification_uid = int(command_args)
|
||||||
await ancs_client.perform_negative_action(notification_uid)
|
await ancs_client.perform_negative_action(notification_uid)
|
||||||
else:
|
else:
|
||||||
writer.write(f"unknown command {command_name}".encode("utf-8"))
|
writer.write(f"unknown command {command_name}".encode())
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
writer.write(f"ERROR: {error}\n".encode("utf-8"))
|
writer.write(f"ERROR: {error}\n".encode())
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import websockets.asyncio.server
|
import websockets.asyncio.server
|
||||||
|
|
||||||
@@ -29,7 +28,7 @@ from bumble.device import AdvertisingParameters, Device
|
|||||||
from bumble.profiles import asha
|
from bumble.profiles import asha
|
||||||
from bumble.transport import open_transport
|
from bumble.transport import open_transport
|
||||||
|
|
||||||
ws_connection: Optional[websockets.asyncio.server.ServerConnection] = None
|
ws_connection: websockets.asyncio.server.ServerConnection | None = None
|
||||||
g722_decoder = decoder.G722Decoder()
|
g722_decoder = decoder.G722Decoder()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import asyncio
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import websockets.asyncio.server
|
import websockets.asyncio.server
|
||||||
|
|
||||||
@@ -218,7 +217,7 @@ def on_avrcp_start(avrcp_protocol: avrcp.Protocol, websocket_server: WebSocketSe
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
class WebSocketServer:
|
class WebSocketServer:
|
||||||
socket: Optional[websockets.asyncio.server.ServerConnection]
|
socket: websockets.asyncio.server.ServerConnection | None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, avrcp_protocol: avrcp.Protocol, avrcp_delegate: Delegate
|
self, avrcp_protocol: avrcp.Protocol, avrcp_delegate: Delegate
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ async def main() -> None:
|
|||||||
|
|
||||||
if device.host.number_of_supported_advertising_sets >= 1:
|
if device.host.number_of_supported_advertising_sets >= 1:
|
||||||
advertising_data1 = AdvertisingData(
|
advertising_data1 = AdvertisingData(
|
||||||
[(AdvertisingData.COMPLETE_LOCAL_NAME, "Bumble 1".encode("utf-8"))]
|
[(AdvertisingData.COMPLETE_LOCAL_NAME, b"Bumble 1")]
|
||||||
)
|
)
|
||||||
|
|
||||||
set1 = await device.create_advertising_set(
|
set1 = await device.create_advertising_set(
|
||||||
@@ -61,7 +61,7 @@ async def main() -> None:
|
|||||||
print("Selected TX power 1:", set1.selected_tx_power)
|
print("Selected TX power 1:", set1.selected_tx_power)
|
||||||
|
|
||||||
advertising_data2 = AdvertisingData(
|
advertising_data2 = AdvertisingData(
|
||||||
[(AdvertisingData.COMPLETE_LOCAL_NAME, "Bumble 2".encode("utf-8"))]
|
[(AdvertisingData.COMPLETE_LOCAL_NAME, b"Bumble 2")]
|
||||||
)
|
)
|
||||||
|
|
||||||
# pylint: disable=possibly-used-before-assignment
|
# pylint: disable=possibly-used-before-assignment
|
||||||
@@ -78,7 +78,7 @@ async def main() -> None:
|
|||||||
|
|
||||||
if device.host.number_of_supported_advertising_sets >= 3:
|
if device.host.number_of_supported_advertising_sets >= 3:
|
||||||
scan_response_data3 = AdvertisingData(
|
scan_response_data3 = AdvertisingData(
|
||||||
[(AdvertisingData.COMPLETE_LOCAL_NAME, "Bumble 3".encode("utf-8"))]
|
[(AdvertisingData.COMPLETE_LOCAL_NAME, b"Bumble 3")]
|
||||||
)
|
)
|
||||||
|
|
||||||
set3 = await device.create_advertising_set(
|
set3 = await device.create_advertising_set(
|
||||||
|
|||||||
@@ -70,13 +70,13 @@ async def main() -> None:
|
|||||||
descriptor = Descriptor(
|
descriptor = Descriptor(
|
||||||
GATT_CHARACTERISTIC_USER_DESCRIPTION_DESCRIPTOR,
|
GATT_CHARACTERISTIC_USER_DESCRIPTION_DESCRIPTOR,
|
||||||
Descriptor.READABLE,
|
Descriptor.READABLE,
|
||||||
'My Description'.encode(),
|
b'My Description',
|
||||||
)
|
)
|
||||||
manufacturer_name_characteristic = Characteristic[bytes](
|
manufacturer_name_characteristic = Characteristic[bytes](
|
||||||
GATT_MANUFACTURER_NAME_STRING_CHARACTERISTIC,
|
GATT_MANUFACTURER_NAME_STRING_CHARACTERISTIC,
|
||||||
Characteristic.Properties.READ,
|
Characteristic.Properties.READ,
|
||||||
Characteristic.READABLE,
|
Characteristic.READABLE,
|
||||||
"Fitbit".encode(),
|
b"Fitbit",
|
||||||
[descriptor],
|
[descriptor],
|
||||||
)
|
)
|
||||||
device_info_service = Service(
|
device_info_service = Service(
|
||||||
|
|||||||
@@ -93,13 +93,13 @@ async def main() -> None:
|
|||||||
descriptor = Descriptor(
|
descriptor = Descriptor(
|
||||||
GATT_CHARACTERISTIC_USER_DESCRIPTION_DESCRIPTOR,
|
GATT_CHARACTERISTIC_USER_DESCRIPTION_DESCRIPTOR,
|
||||||
Descriptor.READABLE,
|
Descriptor.READABLE,
|
||||||
'My Description'.encode(),
|
b'My Description',
|
||||||
)
|
)
|
||||||
manufacturer_name_characteristic = Characteristic(
|
manufacturer_name_characteristic = Characteristic(
|
||||||
GATT_MANUFACTURER_NAME_STRING_CHARACTERISTIC,
|
GATT_MANUFACTURER_NAME_STRING_CHARACTERISTIC,
|
||||||
Characteristic.Properties.READ,
|
Characteristic.Properties.READ,
|
||||||
Characteristic.READABLE,
|
Characteristic.READABLE,
|
||||||
'Fitbit'.encode(),
|
b'Fitbit',
|
||||||
[descriptor],
|
[descriptor],
|
||||||
)
|
)
|
||||||
device_info_service = Service(
|
device_info_service = Service(
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import functools
|
|||||||
import random
|
import random
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Union
|
from typing import Any
|
||||||
|
|
||||||
import bumble.logging
|
import bumble.logging
|
||||||
from bumble import core, gatt, gatt_adapters, gatt_client, hci, transport
|
from bumble import core, gatt, gatt_adapters, gatt_client, hci, transport
|
||||||
@@ -118,7 +118,7 @@ async def client(device: Device, address: hci.Address) -> None:
|
|||||||
c1 = characteristics[0]
|
c1 = characteristics[0]
|
||||||
c1_value = await c1.read_value()
|
c1_value = await c1.read_value()
|
||||||
print(f"@@@ C1 {c1} value = {c1_value!r} (type={type(c1_value)})")
|
print(f"@@@ C1 {c1} value = {c1_value!r} (type={type(c1_value)})")
|
||||||
await c1.write_value("happy π day".encode("utf-8"))
|
await c1.write_value("happy π day".encode())
|
||||||
await c1.subscribe(functools.partial(on_adapted_characteristic_update, c1))
|
await c1.subscribe(functools.partial(on_adapted_characteristic_update, c1))
|
||||||
|
|
||||||
# Static characteristic with a string value.
|
# Static characteristic with a string value.
|
||||||
@@ -187,7 +187,7 @@ async def client(device: Device, address: hci.Address) -> None:
|
|||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
def dynamic_read(selector: str) -> Union[bytes, str]:
|
def dynamic_read(selector: str) -> bytes | str:
|
||||||
if selector == "bytes":
|
if selector == "bytes":
|
||||||
print("$$$ Returning random bytes")
|
print("$$$ Returning random bytes")
|
||||||
return random.randbytes(7)
|
return random.randbytes(7)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import io
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
from typing import Iterable, Optional
|
from collections.abc import Iterable
|
||||||
|
|
||||||
import websockets.asyncio.server
|
import websockets.asyncio.server
|
||||||
|
|
||||||
@@ -33,9 +33,9 @@ from bumble.transport import open_transport
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
ws: Optional[websockets.asyncio.server.ServerConnection] = None
|
ws: websockets.asyncio.server.ServerConnection | None = None
|
||||||
ag_protocol: Optional[hfp.AgProtocol] = None
|
ag_protocol: hfp.AgProtocol | None = None
|
||||||
source_file: Optional[io.BufferedReader] = None
|
source_file: io.BufferedReader | None = None
|
||||||
|
|
||||||
|
|
||||||
def _default_configuration() -> hfp.AgConfiguration:
|
def _default_configuration() -> hfp.AgConfiguration:
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import contextlib
|
|||||||
import functools
|
import functools
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import websockets.asyncio.server
|
import websockets.asyncio.server
|
||||||
|
|
||||||
@@ -30,8 +29,8 @@ from bumble.device import Connection, Device
|
|||||||
from bumble.hfp import HfProtocol
|
from bumble.hfp import HfProtocol
|
||||||
from bumble.transport import open_transport
|
from bumble.transport import open_transport
|
||||||
|
|
||||||
ws: Optional[websockets.asyncio.server.ServerConnection] = None
|
ws: websockets.asyncio.server.ServerConnection | None = None
|
||||||
hf_protocol: Optional[HfProtocol] = None
|
hf_protocol: HfProtocol | None = None
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import websockets.asyncio.server
|
import websockets.asyncio.server
|
||||||
|
|
||||||
@@ -101,8 +100,8 @@ async def main() -> None:
|
|||||||
)
|
)
|
||||||
device.add_service(AudioStreamControlService(device, sink_ase_id=[1]))
|
device.add_service(AudioStreamControlService(device, sink_ase_id=[1]))
|
||||||
|
|
||||||
ws: Optional[websockets.asyncio.server.ServerConnection] = None
|
ws: websockets.asyncio.server.ServerConnection | None = None
|
||||||
mcp: Optional[MediaControlServiceProxy] = None
|
mcp: MediaControlServiceProxy | None = None
|
||||||
|
|
||||||
advertising_data = bytes(
|
advertising_data = bytes(
|
||||||
AdvertisingData(
|
AdvertisingData(
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import asyncio
|
|||||||
import json
|
import json
|
||||||
import secrets
|
import secrets
|
||||||
import sys
|
import sys
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import websockets.asyncio.server
|
import websockets.asyncio.server
|
||||||
|
|
||||||
@@ -110,7 +109,7 @@ async def main() -> None:
|
|||||||
vcs = VolumeControlService()
|
vcs = VolumeControlService()
|
||||||
device.add_service(vcs)
|
device.add_service(vcs)
|
||||||
|
|
||||||
ws: Optional[websockets.asyncio.server.ServerConnection] = None
|
ws: websockets.asyncio.server.ServerConnection | None = None
|
||||||
|
|
||||||
def on_volume_state_change():
|
def on_volume_state_change():
|
||||||
if ws:
|
if ws:
|
||||||
|
|||||||
@@ -214,5 +214,8 @@ exclude = [
|
|||||||
"bumble/transport/grpc_protobuf"
|
"bumble/transport/grpc_protobuf"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
extend-select = ["UP"]
|
||||||
|
|
||||||
[tool.ruff.format]
|
[tool.ruff.format]
|
||||||
quote-style = "preserve"
|
quote-style = "preserve"
|
||||||
@@ -19,7 +19,7 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
from typing import Awaitable
|
from collections.abc import Awaitable
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
@@ -113,8 +112,8 @@ def _default_ag_sdp_features() -> hfp.AgSdpFeature:
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
async def make_hfp_connections(
|
async def make_hfp_connections(
|
||||||
hf_config: Optional[hfp.HfConfiguration] = None,
|
hf_config: hfp.HfConfiguration | None = None,
|
||||||
ag_config: Optional[hfp.AgConfiguration] = None,
|
ag_config: hfp.AgConfiguration | None = None,
|
||||||
):
|
):
|
||||||
if not hf_config:
|
if not hf_config:
|
||||||
hf_config = _default_hf_configuration()
|
hf_config = _default_hf_configuration()
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ async def test_basic(temporary_file):
|
|||||||
assert foo.ltk is not None
|
assert foo.ltk is not None
|
||||||
assert foo.ltk.value == ltk
|
assert foo.ltk.value == ltk
|
||||||
|
|
||||||
with open(file.name, "r", encoding="utf-8") as json_file:
|
with open(file.name, encoding="utf-8") as json_file:
|
||||||
json_data = json.load(json_file)
|
json_data = json.load(json_file)
|
||||||
assert 'my_namespace' in json_data
|
assert 'my_namespace' in json_data
|
||||||
assert 'foo' in json_data['my_namespace']
|
assert 'foo' in json_data['my_namespace']
|
||||||
@@ -161,7 +161,7 @@ async def test_default_namespace(temporary_file):
|
|||||||
ltk = bytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
|
ltk = bytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
|
||||||
keys.ltk = PairingKeys.Key(ltk)
|
keys.ltk = PairingKeys.Key(ltk)
|
||||||
await keystore.update('foo', keys)
|
await keystore.update('foo', keys)
|
||||||
with open(file.name, "r", encoding="utf-8") as json_file:
|
with open(file.name, encoding="utf-8") as json_file:
|
||||||
json_data = json.load(json_file)
|
json_data = json.load(json_file)
|
||||||
assert '__DEFAULT__' in json_data
|
assert '__DEFAULT__' in json_data
|
||||||
assert 'foo' in json_data['__DEFAULT__']
|
assert 'foo' in json_data['__DEFAULT__']
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import itertools
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import struct
|
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ async def test_update_track_title(gmcs_context):
|
|||||||
|
|
||||||
await gmcs_context.devices[0].notify_subscribers(
|
await gmcs_context.devices[0].notify_subscribers(
|
||||||
gmcs_context.server.track_title_characteristic,
|
gmcs_context.server.track_title_characteristic,
|
||||||
value="My Song".encode(),
|
value=b"My Song",
|
||||||
)
|
)
|
||||||
|
|
||||||
assert (await asyncio.wait_for(state.get(), TIMEOUT)) == "My Song"
|
assert (await asyncio.wait_for(state.get(), TIMEOUT)) == "My Song"
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
# Imports
|
# Imports
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
from typing import Any, Optional
|
from typing import Any
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -294,7 +294,7 @@ def test_link_key_to_ltk(ct2: bool, expected: str, crypto_backend: Any):
|
|||||||
)
|
)
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_send_identity_address_command(
|
async def test_send_identity_address_command(
|
||||||
identity_address_type: Optional[pairing.PairingConfig.AddressType],
|
identity_address_type: pairing.PairingConfig.AddressType | None,
|
||||||
public_address: Address,
|
public_address: Address,
|
||||||
random_address: Address,
|
random_address: Address,
|
||||||
expected_identity_address: Address,
|
expected_identity_address: Address,
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
import asyncio
|
import asyncio
|
||||||
import functools
|
import functools
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
@@ -32,7 +31,7 @@ from bumble.transport.common import AsyncPipeSink
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
class Devices:
|
class Devices:
|
||||||
connections: list[Optional[Connection]]
|
connections: list[Connection | None]
|
||||||
|
|
||||||
def __init__(self, num_devices: int) -> None:
|
def __init__(self, num_devices: int) -> None:
|
||||||
self.connections = [None for _ in range(num_devices)]
|
self.connections = [None for _ in range(num_devices)]
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user