Make all event emitters async

* Also remove AbortableEventEmitter
This commit is contained in:
Josh Wu
2025-04-12 22:53:32 +08:00
parent 6cecc16519
commit 55801bc2ca
32 changed files with 432 additions and 395 deletions
+3 -5
View File
@@ -18,7 +18,6 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
import asyncio.subprocess
import collections import collections
import contextlib import contextlib
import dataclasses import dataclasses
@@ -36,7 +35,6 @@ from typing import (
) )
import click import click
import pyee
try: try:
import lc3 # type: ignore # pylint: disable=E0401 import lc3 # type: ignore # pylint: disable=E0401
@@ -121,9 +119,9 @@ def broadcast_code_bytes(broadcast_code: str) -> bytes:
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Scan For Broadcasts # Scan For Broadcasts
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class BroadcastScanner(pyee.EventEmitter): class BroadcastScanner(bumble.utils.EventEmitter):
@dataclasses.dataclass @dataclasses.dataclass
class Broadcast(pyee.EventEmitter): class Broadcast(bumble.utils.EventEmitter):
name: str | None name: str | None
sync: bumble.device.PeriodicAdvertisingSync sync: bumble.device.PeriodicAdvertisingSync
broadcast_id: int broadcast_id: int
@@ -376,7 +374,7 @@ class BroadcastScanner(pyee.EventEmitter):
self.emit('broadcast_loss', broadcast) self.emit('broadcast_loss', broadcast)
class PrintingBroadcastScanner(pyee.EventEmitter): class PrintingBroadcastScanner(bumble.utils.EventEmitter):
def __init__( def __init__(
self, device: bumble.device.Device, filter_duplicates: bool, sync_timeout: float self, device: bumble.device.Device, filter_duplicates: bool, sync_timeout: float
) -> None: ) -> None:
+6 -2
View File
@@ -37,6 +37,7 @@ import click
import aiohttp.web import aiohttp.web
import bumble import bumble
from bumble import utils
from bumble.core import AdvertisingData from bumble.core import AdvertisingData
from bumble.colors import color from bumble.colors import color
from bumble.device import Device, DeviceConfiguration, AdvertisingParameters, CisLink from bumble.device import Device, DeviceConfiguration, AdvertisingParameters, CisLink
@@ -359,7 +360,9 @@ class Speaker:
pcm = decoder.decode( pcm = decoder.decode(
pdu.iso_sdu_fragment, bit_depth=DEFAULT_PCM_BYTES_PER_SAMPLE * 8 pdu.iso_sdu_fragment, bit_depth=DEFAULT_PCM_BYTES_PER_SAMPLE * 8
) )
self.device.abort_on('disconnection', self.ui_server.send_audio(pcm)) utils.cancel_on_event(
self.device, 'disconnection', self.ui_server.send_audio(pcm)
)
def on_ase_state_change(ase: ascs.AseStateMachine) -> None: def on_ase_state_change(ase: ascs.AseStateMachine) -> None:
codec_config = ase.codec_specific_configuration codec_config = ase.codec_specific_configuration
@@ -373,7 +376,8 @@ class Speaker:
or codec_config.codec_frames_per_sdu is None or codec_config.codec_frames_per_sdu is None
): ):
return return
ase.cis_link.abort_on( utils.cancel_on_event(
ase.cis_link,
'disconnection', 'disconnection',
lc3_source_task( lc3_source_task(
filename=self.lc3_input_file_path, filename=self.lc3_input_file_path,
+2 -3
View File
@@ -41,7 +41,6 @@ from typing import (
TYPE_CHECKING, TYPE_CHECKING,
) )
from pyee import EventEmitter
from bumble import utils from bumble import utils
from bumble.core import UUID, name_or_number, InvalidOperationError, ProtocolError from bumble.core import UUID, name_or_number, InvalidOperationError, ProtocolError
@@ -798,7 +797,7 @@ class AttributeValue(Generic[_T]):
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class Attribute(EventEmitter, Generic[_T]): class Attribute(utils.EventEmitter, Generic[_T]):
class Permissions(enum.IntFlag): class Permissions(enum.IntFlag):
READABLE = 0x01 READABLE = 0x01
WRITEABLE = 0x02 WRITEABLE = 0x02
@@ -845,7 +844,7 @@ class Attribute(EventEmitter, Generic[_T]):
permissions: Union[str, Attribute.Permissions], permissions: Union[str, Attribute.Permissions],
value: Union[AttributeValue[_T], _T, None] = None, value: Union[AttributeValue[_T], _T, None] = None,
) -> None: ) -> None:
EventEmitter.__init__(self) utils.EventEmitter.__init__(self)
self.handle = 0 self.handle = 0
self.end_group_handle = 0 self.end_group_handle = 0
if isinstance(permissions, str): if isinstance(permissions, str):
+5 -5
View File
@@ -21,7 +21,7 @@ import struct
from typing import Dict, Type, Union, Tuple from typing import Dict, Type, Union, Tuple
from bumble import core from bumble import core
from bumble.utils import OpenIntEnum from bumble import utils
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -43,7 +43,7 @@ class Frame:
EXTENDED = 0x1E EXTENDED = 0x1E
UNIT = 0x1F UNIT = 0x1F
class OperationCode(OpenIntEnum): class OperationCode(utils.OpenIntEnum):
# 0x00 - 0x0F: Unit and subunit commands # 0x00 - 0x0F: Unit and subunit commands
VENDOR_DEPENDENT = 0x00 VENDOR_DEPENDENT = 0x00
RESERVE = 0x01 RESERVE = 0x01
@@ -204,7 +204,7 @@ class Frame:
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class CommandFrame(Frame): class CommandFrame(Frame):
class CommandType(OpenIntEnum): class CommandType(utils.OpenIntEnum):
# AV/C Digital Interface Command Set General Specification Version 4.1 # AV/C Digital Interface Command Set General Specification Version 4.1
# Table 7.1 # Table 7.1
CONTROL = 0x00 CONTROL = 0x00
@@ -240,7 +240,7 @@ class CommandFrame(Frame):
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class ResponseFrame(Frame): class ResponseFrame(Frame):
class ResponseCode(OpenIntEnum): class ResponseCode(utils.OpenIntEnum):
# AV/C Digital Interface Command Set General Specification Version 4.1 # AV/C Digital Interface Command Set General Specification Version 4.1
# Table 7.2 # Table 7.2
NOT_IMPLEMENTED = 0x08 NOT_IMPLEMENTED = 0x08
@@ -368,7 +368,7 @@ class PassThroughFrame:
PRESSED = 0 PRESSED = 0
RELEASED = 1 RELEASED = 1
class OperationId(OpenIntEnum): class OperationId(utils.OpenIntEnum):
SELECT = 0x00 SELECT = 0x00
UP = 0x01 UP = 0x01
DOWN = 0x01 DOWN = 0x01
+5 -6
View File
@@ -37,7 +37,6 @@ from typing import (
cast, cast,
) )
from pyee import EventEmitter
from bumble.core import ( from bumble.core import (
BT_ADVANCED_AUDIO_DISTRIBUTION_SERVICE, BT_ADVANCED_AUDIO_DISTRIBUTION_SERVICE,
@@ -57,7 +56,7 @@ from bumble.a2dp import (
VendorSpecificMediaCodecInformation, VendorSpecificMediaCodecInformation,
) )
from bumble.rtp import MediaPacket from bumble.rtp import MediaPacket
from bumble import sdp, device, l2cap from bumble import sdp, device, l2cap, utils
from bumble.colors import color from bumble.colors import color
@@ -1194,7 +1193,7 @@ class DelayReport_Reject(Simple_Reject):
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class Protocol(EventEmitter): 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]
@@ -1680,7 +1679,7 @@ class Protocol(EventEmitter):
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class Listener(EventEmitter): class Listener(utils.EventEmitter):
servers: Dict[int, Protocol] servers: Dict[int, Protocol]
@staticmethod @staticmethod
@@ -2063,7 +2062,7 @@ class DiscoveredStreamEndPoint(StreamEndPoint, StreamEndPointProxy):
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class LocalStreamEndPoint(StreamEndPoint, EventEmitter): class LocalStreamEndPoint(StreamEndPoint, utils.EventEmitter):
stream: Optional[Stream] stream: Optional[Stream]
def __init__( def __init__(
@@ -2076,7 +2075,7 @@ class LocalStreamEndPoint(StreamEndPoint, EventEmitter):
configuration: Optional[Iterable[ServiceCapabilities]] = None, configuration: Optional[Iterable[ServiceCapabilities]] = None,
): ):
StreamEndPoint.__init__(self, seid, media_type, tsep, 0, capabilities) StreamEndPoint.__init__(self, seid, media_type, tsep, 0, capabilities)
EventEmitter.__init__(self) utils.EventEmitter.__init__(self)
self.protocol = protocol self.protocol = protocol
self.configuration = configuration if configuration is not None else [] self.configuration = configuration if configuration is not None else []
self.stream = None self.stream = None
+18 -19
View File
@@ -38,7 +38,6 @@ from typing import (
Union, Union,
) )
import pyee
from bumble.colors import color from bumble.colors import color
from bumble.device import Device, Connection from bumble.device import Device, Connection
@@ -53,7 +52,7 @@ from bumble.sdp import (
DataElement, DataElement,
ServiceAttribute, ServiceAttribute,
) )
from bumble.utils import AsyncRunner, OpenIntEnum from bumble import utils
from bumble.core import ( from bumble.core import (
InvalidArgumentError, InvalidArgumentError,
ProtocolError, ProtocolError,
@@ -307,7 +306,7 @@ class Command:
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class GetCapabilitiesCommand(Command): class GetCapabilitiesCommand(Command):
class CapabilityId(OpenIntEnum): class CapabilityId(utils.OpenIntEnum):
COMPANY_ID = 0x02 COMPANY_ID = 0x02
EVENTS_SUPPORTED = 0x03 EVENTS_SUPPORTED = 0x03
@@ -637,7 +636,7 @@ class RegisterNotificationResponse(Response):
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class EventId(OpenIntEnum): class EventId(utils.OpenIntEnum):
PLAYBACK_STATUS_CHANGED = 0x01 PLAYBACK_STATUS_CHANGED = 0x01
TRACK_CHANGED = 0x02 TRACK_CHANGED = 0x02
TRACK_REACHED_END = 0x03 TRACK_REACHED_END = 0x03
@@ -657,12 +656,12 @@ class EventId(OpenIntEnum):
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class CharacterSetId(OpenIntEnum): class CharacterSetId(utils.OpenIntEnum):
UTF_8 = 0x06 UTF_8 = 0x06
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class MediaAttributeId(OpenIntEnum): class MediaAttributeId(utils.OpenIntEnum):
TITLE = 0x01 TITLE = 0x01
ARTIST_NAME = 0x02 ARTIST_NAME = 0x02
ALBUM_NAME = 0x03 ALBUM_NAME = 0x03
@@ -682,7 +681,7 @@ class MediaAttribute:
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class PlayStatus(OpenIntEnum): class PlayStatus(utils.OpenIntEnum):
STOPPED = 0x00 STOPPED = 0x00
PLAYING = 0x01 PLAYING = 0x01
PAUSED = 0x02 PAUSED = 0x02
@@ -701,33 +700,33 @@ class SongAndPlayStatus:
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class ApplicationSetting: class ApplicationSetting:
class AttributeId(OpenIntEnum): class AttributeId(utils.OpenIntEnum):
EQUALIZER_ON_OFF = 0x01 EQUALIZER_ON_OFF = 0x01
REPEAT_MODE = 0x02 REPEAT_MODE = 0x02
SHUFFLE_ON_OFF = 0x03 SHUFFLE_ON_OFF = 0x03
SCAN_ON_OFF = 0x04 SCAN_ON_OFF = 0x04
class EqualizerOnOffStatus(OpenIntEnum): class EqualizerOnOffStatus(utils.OpenIntEnum):
OFF = 0x01 OFF = 0x01
ON = 0x02 ON = 0x02
class RepeatModeStatus(OpenIntEnum): class RepeatModeStatus(utils.OpenIntEnum):
OFF = 0x01 OFF = 0x01
SINGLE_TRACK_REPEAT = 0x02 SINGLE_TRACK_REPEAT = 0x02
ALL_TRACK_REPEAT = 0x03 ALL_TRACK_REPEAT = 0x03
GROUP_REPEAT = 0x04 GROUP_REPEAT = 0x04
class ShuffleOnOffStatus(OpenIntEnum): class ShuffleOnOffStatus(utils.OpenIntEnum):
OFF = 0x01 OFF = 0x01
ALL_TRACKS_SHUFFLE = 0x02 ALL_TRACKS_SHUFFLE = 0x02
GROUP_SHUFFLE = 0x03 GROUP_SHUFFLE = 0x03
class ScanOnOffStatus(OpenIntEnum): class ScanOnOffStatus(utils.OpenIntEnum):
OFF = 0x01 OFF = 0x01
ALL_TRACKS_SCAN = 0x02 ALL_TRACKS_SCAN = 0x02
GROUP_SCAN = 0x03 GROUP_SCAN = 0x03
class GenericValue(OpenIntEnum): class GenericValue(utils.OpenIntEnum):
pass pass
@@ -816,7 +815,7 @@ class PlayerApplicationSettingChangedEvent(Event):
@dataclass @dataclass
class Setting: class Setting:
attribute_id: ApplicationSetting.AttributeId attribute_id: ApplicationSetting.AttributeId
value_id: OpenIntEnum value_id: utils.OpenIntEnum
player_application_settings: List[Setting] player_application_settings: List[Setting]
@@ -824,7 +823,7 @@ class PlayerApplicationSettingChangedEvent(Event):
def from_bytes(cls, pdu: bytes) -> PlayerApplicationSettingChangedEvent: def from_bytes(cls, pdu: bytes) -> PlayerApplicationSettingChangedEvent:
def setting(attribute_id_int: int, value_id_int: int): def setting(attribute_id_int: int, value_id_int: int):
attribute_id = ApplicationSetting.AttributeId(attribute_id_int) attribute_id = ApplicationSetting.AttributeId(attribute_id_int)
value_id: OpenIntEnum value_id: utils.OpenIntEnum
if attribute_id == ApplicationSetting.AttributeId.EQUALIZER_ON_OFF: if attribute_id == ApplicationSetting.AttributeId.EQUALIZER_ON_OFF:
value_id = ApplicationSetting.EqualizerOnOffStatus(value_id_int) value_id = ApplicationSetting.EqualizerOnOffStatus(value_id_int)
elif attribute_id == ApplicationSetting.AttributeId.REPEAT_MODE: elif attribute_id == ApplicationSetting.AttributeId.REPEAT_MODE:
@@ -994,7 +993,7 @@ class Delegate:
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class Protocol(pyee.EventEmitter): class Protocol(utils.EventEmitter):
"""AVRCP Controller and Target protocol.""" """AVRCP Controller and Target protocol."""
class PacketType(enum.IntEnum): class PacketType(enum.IntEnum):
@@ -1003,7 +1002,7 @@ class Protocol(pyee.EventEmitter):
CONTINUE = 0b10 CONTINUE = 0b10
END = 0b11 END = 0b11
class PduId(OpenIntEnum): class PduId(utils.OpenIntEnum):
GET_CAPABILITIES = 0x10 GET_CAPABILITIES = 0x10
LIST_PLAYER_APPLICATION_SETTING_ATTRIBUTES = 0x11 LIST_PLAYER_APPLICATION_SETTING_ATTRIBUTES = 0x11
LIST_PLAYER_APPLICATION_SETTING_VALUES = 0x12 LIST_PLAYER_APPLICATION_SETTING_VALUES = 0x12
@@ -1024,7 +1023,7 @@ class Protocol(pyee.EventEmitter):
GET_FOLDER_ITEMS = 0x71 GET_FOLDER_ITEMS = 0x71
GET_TOTAL_NUMBER_OF_ITEMS = 0x75 GET_TOTAL_NUMBER_OF_ITEMS = 0x75
class StatusCode(OpenIntEnum): class StatusCode(utils.OpenIntEnum):
INVALID_COMMAND = 0x00 INVALID_COMMAND = 0x00
INVALID_PARAMETER = 0x01 INVALID_PARAMETER = 0x01
PARAMETER_CONTENT_ERROR = 0x02 PARAMETER_CONTENT_ERROR = 0x02
@@ -1466,7 +1465,7 @@ class Protocol(pyee.EventEmitter):
if self.avctp_protocol is not None: if self.avctp_protocol is not None:
# TODO: find a better strategy instead of just closing # TODO: find a better strategy instead of just closing
logger.warning("AVCTP protocol already active, closing connection") logger.warning("AVCTP protocol already active, closing connection")
AsyncRunner.spawn(l2cap_channel.disconnect()) utils.AsyncRunner.spawn(l2cap_channel.disconnect())
return return
self.avctp_protocol = avctp.Protocol(l2cap_channel) self.avctp_protocol = avctp.Protocol(l2cap_channel)
+56 -56
View File
@@ -23,7 +23,7 @@ from typing import cast, overload, Literal, Union, Optional
from typing_extensions import Self from typing_extensions import Self
from bumble.company_ids import COMPANY_IDENTIFIERS from bumble.company_ids import COMPANY_IDENTIFIERS
from bumble.utils import OpenIntEnum from bumble import utils
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -730,7 +730,7 @@ class DeviceClass:
# Appearance # Appearance
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class Appearance: class Appearance:
class Category(OpenIntEnum): class Category(utils.OpenIntEnum):
UNKNOWN = 0x0000 UNKNOWN = 0x0000
PHONE = 0x0001 PHONE = 0x0001
COMPUTER = 0x0002 COMPUTER = 0x0002
@@ -784,13 +784,13 @@ class Appearance:
SPIROMETER = 0x0037 SPIROMETER = 0x0037
OUTDOOR_SPORTS_ACTIVITY = 0x0051 OUTDOOR_SPORTS_ACTIVITY = 0x0051
class UnknownSubcategory(OpenIntEnum): class UnknownSubcategory(utils.OpenIntEnum):
GENERIC_UNKNOWN = 0x00 GENERIC_UNKNOWN = 0x00
class PhoneSubcategory(OpenIntEnum): class PhoneSubcategory(utils.OpenIntEnum):
GENERIC_PHONE = 0x00 GENERIC_PHONE = 0x00
class ComputerSubcategory(OpenIntEnum): class ComputerSubcategory(utils.OpenIntEnum):
GENERIC_COMPUTER = 0x00 GENERIC_COMPUTER = 0x00
DESKTOP_WORKSTATION = 0x01 DESKTOP_WORKSTATION = 0x01
SERVER_CLASS_COMPUTER = 0x02 SERVER_CLASS_COMPUTER = 0x02
@@ -808,49 +808,49 @@ class Appearance:
MINI_PC = 0x0E MINI_PC = 0x0E
STICK_PC = 0x0F STICK_PC = 0x0F
class WatchSubcategory(OpenIntEnum): class WatchSubcategory(utils.OpenIntEnum):
GENENERIC_WATCH = 0x00 GENENERIC_WATCH = 0x00
SPORTS_WATCH = 0x01 SPORTS_WATCH = 0x01
SMARTWATCH = 0x02 SMARTWATCH = 0x02
class ClockSubcategory(OpenIntEnum): class ClockSubcategory(utils.OpenIntEnum):
GENERIC_CLOCK = 0x00 GENERIC_CLOCK = 0x00
class DisplaySubcategory(OpenIntEnum): class DisplaySubcategory(utils.OpenIntEnum):
GENERIC_DISPLAY = 0x00 GENERIC_DISPLAY = 0x00
class RemoteControlSubcategory(OpenIntEnum): class RemoteControlSubcategory(utils.OpenIntEnum):
GENERIC_REMOTE_CONTROL = 0x00 GENERIC_REMOTE_CONTROL = 0x00
class EyeglassesSubcategory(OpenIntEnum): class EyeglassesSubcategory(utils.OpenIntEnum):
GENERIC_EYEGLASSES = 0x00 GENERIC_EYEGLASSES = 0x00
class TagSubcategory(OpenIntEnum): class TagSubcategory(utils.OpenIntEnum):
GENERIC_TAG = 0x00 GENERIC_TAG = 0x00
class KeyringSubcategory(OpenIntEnum): class KeyringSubcategory(utils.OpenIntEnum):
GENERIC_KEYRING = 0x00 GENERIC_KEYRING = 0x00
class MediaPlayerSubcategory(OpenIntEnum): class MediaPlayerSubcategory(utils.OpenIntEnum):
GENERIC_MEDIA_PLAYER = 0x00 GENERIC_MEDIA_PLAYER = 0x00
class BarcodeScannerSubcategory(OpenIntEnum): class BarcodeScannerSubcategory(utils.OpenIntEnum):
GENERIC_BARCODE_SCANNER = 0x00 GENERIC_BARCODE_SCANNER = 0x00
class ThermometerSubcategory(OpenIntEnum): class ThermometerSubcategory(utils.OpenIntEnum):
GENERIC_THERMOMETER = 0x00 GENERIC_THERMOMETER = 0x00
EAR_THERMOMETER = 0x01 EAR_THERMOMETER = 0x01
class HeartRateSensorSubcategory(OpenIntEnum): class HeartRateSensorSubcategory(utils.OpenIntEnum):
GENERIC_HEART_RATE_SENSOR = 0x00 GENERIC_HEART_RATE_SENSOR = 0x00
HEART_RATE_BELT = 0x01 HEART_RATE_BELT = 0x01
class BloodPressureSubcategory(OpenIntEnum): class BloodPressureSubcategory(utils.OpenIntEnum):
GENERIC_BLOOD_PRESSURE = 0x00 GENERIC_BLOOD_PRESSURE = 0x00
ARM_BLOOD_PRESSURE = 0x01 ARM_BLOOD_PRESSURE = 0x01
WRIST_BLOOD_PRESSURE = 0x02 WRIST_BLOOD_PRESSURE = 0x02
class HumanInterfaceDeviceSubcategory(OpenIntEnum): class HumanInterfaceDeviceSubcategory(utils.OpenIntEnum):
GENERIC_HUMAN_INTERFACE_DEVICE = 0x00 GENERIC_HUMAN_INTERFACE_DEVICE = 0x00
KEYBOARD = 0x01 KEYBOARD = 0x01
MOUSE = 0x02 MOUSE = 0x02
@@ -863,16 +863,16 @@ class Appearance:
TOUCHPAD = 0x09 TOUCHPAD = 0x09
PRESENTATION_REMOTE = 0x0A PRESENTATION_REMOTE = 0x0A
class GlucoseMeterSubcategory(OpenIntEnum): class GlucoseMeterSubcategory(utils.OpenIntEnum):
GENERIC_GLUCOSE_METER = 0x00 GENERIC_GLUCOSE_METER = 0x00
class RunningWalkingSensorSubcategory(OpenIntEnum): class RunningWalkingSensorSubcategory(utils.OpenIntEnum):
GENERIC_RUNNING_WALKING_SENSOR = 0x00 GENERIC_RUNNING_WALKING_SENSOR = 0x00
IN_SHOE_RUNNING_WALKING_SENSOR = 0x01 IN_SHOE_RUNNING_WALKING_SENSOR = 0x01
ON_SHOW_RUNNING_WALKING_SENSOR = 0x02 ON_SHOW_RUNNING_WALKING_SENSOR = 0x02
ON_HIP_RUNNING_WALKING_SENSOR = 0x03 ON_HIP_RUNNING_WALKING_SENSOR = 0x03
class CyclingSubcategory(OpenIntEnum): class CyclingSubcategory(utils.OpenIntEnum):
GENERIC_CYCLING = 0x00 GENERIC_CYCLING = 0x00
CYCLING_COMPUTER = 0x01 CYCLING_COMPUTER = 0x01
SPEED_SENSOR = 0x02 SPEED_SENSOR = 0x02
@@ -880,7 +880,7 @@ class Appearance:
POWER_SENSOR = 0x04 POWER_SENSOR = 0x04
SPEED_AND_CADENCE_SENSOR = 0x05 SPEED_AND_CADENCE_SENSOR = 0x05
class ControlDeviceSubcategory(OpenIntEnum): class ControlDeviceSubcategory(utils.OpenIntEnum):
GENERIC_CONTROL_DEVICE = 0x00 GENERIC_CONTROL_DEVICE = 0x00
SWITCH = 0x01 SWITCH = 0x01
MULTI_SWITCH = 0x02 MULTI_SWITCH = 0x02
@@ -895,13 +895,13 @@ class Appearance:
ENERGY_HARVESTING_SWITCH = 0x0B ENERGY_HARVESTING_SWITCH = 0x0B
PUSH_BUTTON = 0x0C PUSH_BUTTON = 0x0C
class NetworkDeviceSubcategory(OpenIntEnum): class NetworkDeviceSubcategory(utils.OpenIntEnum):
GENERIC_NETWORK_DEVICE = 0x00 GENERIC_NETWORK_DEVICE = 0x00
ACCESS_POINT = 0x01 ACCESS_POINT = 0x01
MESH_DEVICE = 0x02 MESH_DEVICE = 0x02
MESH_NETWORK_PROXY = 0x03 MESH_NETWORK_PROXY = 0x03
class SensorSubcategory(OpenIntEnum): class SensorSubcategory(utils.OpenIntEnum):
GENERIC_SENSOR = 0x00 GENERIC_SENSOR = 0x00
MOTION_SENSOR = 0x01 MOTION_SENSOR = 0x01
AIR_QUALITY_SENSOR = 0x02 AIR_QUALITY_SENSOR = 0x02
@@ -929,7 +929,7 @@ class Appearance:
FLAME_DETECTOR = 0x18 FLAME_DETECTOR = 0x18
VEHICLE_TIRE_PRESSURE_SENSOR = 0x19 VEHICLE_TIRE_PRESSURE_SENSOR = 0x19
class LightFixturesSubcategory(OpenIntEnum): class LightFixturesSubcategory(utils.OpenIntEnum):
GENERIC_LIGHT_FIXTURES = 0x00 GENERIC_LIGHT_FIXTURES = 0x00
WALL_LIGHT = 0x01 WALL_LIGHT = 0x01
CEILING_LIGHT = 0x02 CEILING_LIGHT = 0x02
@@ -957,7 +957,7 @@ class Appearance:
LOW_BAY_LIGHT = 0x18 LOW_BAY_LIGHT = 0x18
HIGH_BAY_LIGHT = 0x19 HIGH_BAY_LIGHT = 0x19
class FanSubcategory(OpenIntEnum): class FanSubcategory(utils.OpenIntEnum):
GENERIC_FAN = 0x00 GENERIC_FAN = 0x00
CEILING_FAN = 0x01 CEILING_FAN = 0x01
AXIAL_FAN = 0x02 AXIAL_FAN = 0x02
@@ -966,7 +966,7 @@ class Appearance:
DESK_FAN = 0x05 DESK_FAN = 0x05
WALL_FAN = 0x06 WALL_FAN = 0x06
class HvacSubcategory(OpenIntEnum): class HvacSubcategory(utils.OpenIntEnum):
GENERIC_HVAC = 0x00 GENERIC_HVAC = 0x00
THERMOSTAT = 0x01 THERMOSTAT = 0x01
HUMIDIFIER = 0x02 HUMIDIFIER = 0x02
@@ -980,13 +980,13 @@ class Appearance:
FAN_HEATER = 0x0A FAN_HEATER = 0x0A
AIR_CURTAIN = 0x0B AIR_CURTAIN = 0x0B
class AirConditioningSubcategory(OpenIntEnum): class AirConditioningSubcategory(utils.OpenIntEnum):
GENERIC_AIR_CONDITIONING = 0x00 GENERIC_AIR_CONDITIONING = 0x00
class HumidifierSubcategory(OpenIntEnum): class HumidifierSubcategory(utils.OpenIntEnum):
GENERIC_HUMIDIFIER = 0x00 GENERIC_HUMIDIFIER = 0x00
class HeatingSubcategory(OpenIntEnum): class HeatingSubcategory(utils.OpenIntEnum):
GENERIC_HEATING = 0x00 GENERIC_HEATING = 0x00
RADIATOR = 0x01 RADIATOR = 0x01
BOILER = 0x02 BOILER = 0x02
@@ -996,7 +996,7 @@ class Appearance:
FAN_HEATER = 0x06 FAN_HEATER = 0x06
AIR_CURTAIN = 0x07 AIR_CURTAIN = 0x07
class AccessControlSubcategory(OpenIntEnum): class AccessControlSubcategory(utils.OpenIntEnum):
GENERIC_ACCESS_CONTROL = 0x00 GENERIC_ACCESS_CONTROL = 0x00
ACCESS_DOOR = 0x01 ACCESS_DOOR = 0x01
GARAGE_DOOR = 0x02 GARAGE_DOOR = 0x02
@@ -1008,7 +1008,7 @@ class Appearance:
DOOR_LOCK = 0x08 DOOR_LOCK = 0x08
LOCKER = 0x09 LOCKER = 0x09
class MotorizedDeviceSubcategory(OpenIntEnum): class MotorizedDeviceSubcategory(utils.OpenIntEnum):
GENERIC_MOTORIZED_DEVICE = 0x00 GENERIC_MOTORIZED_DEVICE = 0x00
MOTORIZED_GATE = 0x01 MOTORIZED_GATE = 0x01
AWNING = 0x02 AWNING = 0x02
@@ -1016,7 +1016,7 @@ class Appearance:
CURTAINS = 0x04 CURTAINS = 0x04
SCREEN = 0x05 SCREEN = 0x05
class PowerDeviceSubcategory(OpenIntEnum): class PowerDeviceSubcategory(utils.OpenIntEnum):
GENERIC_POWER_DEVICE = 0x00 GENERIC_POWER_DEVICE = 0x00
POWER_OUTLET = 0x01 POWER_OUTLET = 0x01
POWER_STRIP = 0x02 POWER_STRIP = 0x02
@@ -1028,7 +1028,7 @@ class Appearance:
CHARGE_CASE = 0x08 CHARGE_CASE = 0x08
POWER_BANK = 0x09 POWER_BANK = 0x09
class LightSourceSubcategory(OpenIntEnum): class LightSourceSubcategory(utils.OpenIntEnum):
GENERIC_LIGHT_SOURCE = 0x00 GENERIC_LIGHT_SOURCE = 0x00
INCANDESCENT_LIGHT_BULB = 0x01 INCANDESCENT_LIGHT_BULB = 0x01
LED_LAMP = 0x02 LED_LAMP = 0x02
@@ -1039,7 +1039,7 @@ class Appearance:
LOW_VOLTAGE_HALOGEN = 0x07 LOW_VOLTAGE_HALOGEN = 0x07
ORGANIC_LIGHT_EMITTING_DIODE = 0x08 ORGANIC_LIGHT_EMITTING_DIODE = 0x08
class WindowCoveringSubcategory(OpenIntEnum): class WindowCoveringSubcategory(utils.OpenIntEnum):
GENERIC_WINDOW_COVERING = 0x00 GENERIC_WINDOW_COVERING = 0x00
WINDOW_SHADES = 0x01 WINDOW_SHADES = 0x01
WINDOW_BLINDS = 0x02 WINDOW_BLINDS = 0x02
@@ -1048,7 +1048,7 @@ class Appearance:
EXTERIOR_SHUTTER = 0x05 EXTERIOR_SHUTTER = 0x05
EXTERIOR_SCREEN = 0x06 EXTERIOR_SCREEN = 0x06
class AudioSinkSubcategory(OpenIntEnum): class AudioSinkSubcategory(utils.OpenIntEnum):
GENERIC_AUDIO_SINK = 0x00 GENERIC_AUDIO_SINK = 0x00
STANDALONE_SPEAKER = 0x01 STANDALONE_SPEAKER = 0x01
SOUNDBAR = 0x02 SOUNDBAR = 0x02
@@ -1056,7 +1056,7 @@ class Appearance:
STANDMOUNTED_SPEAKER = 0x04 STANDMOUNTED_SPEAKER = 0x04
SPEAKERPHONE = 0x05 SPEAKERPHONE = 0x05
class AudioSourceSubcategory(OpenIntEnum): class AudioSourceSubcategory(utils.OpenIntEnum):
GENERIC_AUDIO_SOURCE = 0x00 GENERIC_AUDIO_SOURCE = 0x00
MICROPHONE = 0x01 MICROPHONE = 0x01
ALARM = 0x02 ALARM = 0x02
@@ -1068,7 +1068,7 @@ class Appearance:
BROADCASTING_ROOM = 0x08 BROADCASTING_ROOM = 0x08
AUDITORIUM = 0x09 AUDITORIUM = 0x09
class MotorizedVehicleSubcategory(OpenIntEnum): class MotorizedVehicleSubcategory(utils.OpenIntEnum):
GENERIC_MOTORIZED_VEHICLE = 0x00 GENERIC_MOTORIZED_VEHICLE = 0x00
CAR = 0x01 CAR = 0x01
LARGE_GOODS_VEHICLE = 0x02 LARGE_GOODS_VEHICLE = 0x02
@@ -1086,7 +1086,7 @@ class Appearance:
CAMPER_CARAVAN = 0x0E CAMPER_CARAVAN = 0x0E
RECREATIONAL_VEHICLE_MOTOR_HOME = 0x0F RECREATIONAL_VEHICLE_MOTOR_HOME = 0x0F
class DomesticApplianceSubcategory(OpenIntEnum): class DomesticApplianceSubcategory(utils.OpenIntEnum):
GENERIC_DOMESTIC_APPLIANCE = 0x00 GENERIC_DOMESTIC_APPLIANCE = 0x00
REFRIGERATOR = 0x01 REFRIGERATOR = 0x01
FREEZER = 0x02 FREEZER = 0x02
@@ -1104,21 +1104,21 @@ class Appearance:
RICE_COOKER = 0x0E RICE_COOKER = 0x0E
CLOTHES_STEAMER = 0x0F CLOTHES_STEAMER = 0x0F
class WearableAudioDeviceSubcategory(OpenIntEnum): class WearableAudioDeviceSubcategory(utils.OpenIntEnum):
GENERIC_WEARABLE_AUDIO_DEVICE = 0x00 GENERIC_WEARABLE_AUDIO_DEVICE = 0x00
EARBUD = 0x01 EARBUD = 0x01
HEADSET = 0x02 HEADSET = 0x02
HEADPHONES = 0x03 HEADPHONES = 0x03
NECK_BAND = 0x04 NECK_BAND = 0x04
class AircraftSubcategory(OpenIntEnum): class AircraftSubcategory(utils.OpenIntEnum):
GENERIC_AIRCRAFT = 0x00 GENERIC_AIRCRAFT = 0x00
LIGHT_AIRCRAFT = 0x01 LIGHT_AIRCRAFT = 0x01
MICROLIGHT = 0x02 MICROLIGHT = 0x02
PARAGLIDER = 0x03 PARAGLIDER = 0x03
LARGE_PASSENGER_AIRCRAFT = 0x04 LARGE_PASSENGER_AIRCRAFT = 0x04
class AvEquipmentSubcategory(OpenIntEnum): class AvEquipmentSubcategory(utils.OpenIntEnum):
GENERIC_AV_EQUIPMENT = 0x00 GENERIC_AV_EQUIPMENT = 0x00
AMPLIFIER = 0x01 AMPLIFIER = 0x01
RECEIVER = 0x02 RECEIVER = 0x02
@@ -1131,65 +1131,65 @@ class Appearance:
OPTICAL_DISC_PLAYER = 0x09 OPTICAL_DISC_PLAYER = 0x09
SET_TOP_BOX = 0x0A SET_TOP_BOX = 0x0A
class DisplayEquipmentSubcategory(OpenIntEnum): class DisplayEquipmentSubcategory(utils.OpenIntEnum):
GENERIC_DISPLAY_EQUIPMENT = 0x00 GENERIC_DISPLAY_EQUIPMENT = 0x00
TELEVISION = 0x01 TELEVISION = 0x01
MONITOR = 0x02 MONITOR = 0x02
PROJECTOR = 0x03 PROJECTOR = 0x03
class HearingAidSubcategory(OpenIntEnum): class HearingAidSubcategory(utils.OpenIntEnum):
GENERIC_HEARING_AID = 0x00 GENERIC_HEARING_AID = 0x00
IN_EAR_HEARING_AID = 0x01 IN_EAR_HEARING_AID = 0x01
BEHIND_EAR_HEARING_AID = 0x02 BEHIND_EAR_HEARING_AID = 0x02
COCHLEAR_IMPLANT = 0x03 COCHLEAR_IMPLANT = 0x03
class GamingSubcategory(OpenIntEnum): class GamingSubcategory(utils.OpenIntEnum):
GENERIC_GAMING = 0x00 GENERIC_GAMING = 0x00
HOME_VIDEO_GAME_CONSOLE = 0x01 HOME_VIDEO_GAME_CONSOLE = 0x01
PORTABLE_HANDHELD_CONSOLE = 0x02 PORTABLE_HANDHELD_CONSOLE = 0x02
class SignageSubcategory(OpenIntEnum): class SignageSubcategory(utils.OpenIntEnum):
GENERIC_SIGNAGE = 0x00 GENERIC_SIGNAGE = 0x00
DIGITAL_SIGNAGE = 0x01 DIGITAL_SIGNAGE = 0x01
ELECTRONIC_LABEL = 0x02 ELECTRONIC_LABEL = 0x02
class PulseOximeterSubcategory(OpenIntEnum): class PulseOximeterSubcategory(utils.OpenIntEnum):
GENERIC_PULSE_OXIMETER = 0x00 GENERIC_PULSE_OXIMETER = 0x00
FINGERTIP_PULSE_OXIMETER = 0x01 FINGERTIP_PULSE_OXIMETER = 0x01
WRIST_WORN_PULSE_OXIMETER = 0x02 WRIST_WORN_PULSE_OXIMETER = 0x02
class WeightScaleSubcategory(OpenIntEnum): class WeightScaleSubcategory(utils.OpenIntEnum):
GENERIC_WEIGHT_SCALE = 0x00 GENERIC_WEIGHT_SCALE = 0x00
class PersonalMobilityDeviceSubcategory(OpenIntEnum): class PersonalMobilityDeviceSubcategory(utils.OpenIntEnum):
GENERIC_PERSONAL_MOBILITY_DEVICE = 0x00 GENERIC_PERSONAL_MOBILITY_DEVICE = 0x00
POWERED_WHEELCHAIR = 0x01 POWERED_WHEELCHAIR = 0x01
MOBILITY_SCOOTER = 0x02 MOBILITY_SCOOTER = 0x02
class ContinuousGlucoseMonitorSubcategory(OpenIntEnum): class ContinuousGlucoseMonitorSubcategory(utils.OpenIntEnum):
GENERIC_CONTINUOUS_GLUCOSE_MONITOR = 0x00 GENERIC_CONTINUOUS_GLUCOSE_MONITOR = 0x00
class InsulinPumpSubcategory(OpenIntEnum): class InsulinPumpSubcategory(utils.OpenIntEnum):
GENERIC_INSULIN_PUMP = 0x00 GENERIC_INSULIN_PUMP = 0x00
INSULIN_PUMP_DURABLE_PUMP = 0x01 INSULIN_PUMP_DURABLE_PUMP = 0x01
INSULIN_PUMP_PATCH_PUMP = 0x02 INSULIN_PUMP_PATCH_PUMP = 0x02
INSULIN_PEN = 0x03 INSULIN_PEN = 0x03
class MedicationDeliverySubcategory(OpenIntEnum): class MedicationDeliverySubcategory(utils.OpenIntEnum):
GENERIC_MEDICATION_DELIVERY = 0x00 GENERIC_MEDICATION_DELIVERY = 0x00
class SpirometerSubcategory(OpenIntEnum): class SpirometerSubcategory(utils.OpenIntEnum):
GENERIC_SPIROMETER = 0x00 GENERIC_SPIROMETER = 0x00
HANDHELD_SPIROMETER = 0x01 HANDHELD_SPIROMETER = 0x01
class OutdoorSportsActivitySubcategory(OpenIntEnum): class OutdoorSportsActivitySubcategory(utils.OpenIntEnum):
GENERIC_OUTDOOR_SPORTS_ACTIVITY = 0x00 GENERIC_OUTDOOR_SPORTS_ACTIVITY = 0x00
LOCATION_DISPLAY = 0x01 LOCATION_DISPLAY = 0x01
LOCATION_AND_NAVIGATION_DISPLAY = 0x02 LOCATION_AND_NAVIGATION_DISPLAY = 0x02
LOCATION_POD = 0x03 LOCATION_POD = 0x03
LOCATION_AND_NAVIGATION_POD = 0x04 LOCATION_AND_NAVIGATION_POD = 0x04
class _OpenSubcategory(OpenIntEnum): class _OpenSubcategory(utils.OpenIntEnum):
GENERIC = 0x00 GENERIC = 0x00
SUBCATEGORY_CLASSES = { SUBCATEGORY_CLASSES = {
@@ -1296,7 +1296,7 @@ class AdvertisingData:
# fmt: off # fmt: off
# pylint: disable=line-too-long # pylint: disable=line-too-long
class Type(OpenIntEnum): class Type(utils.OpenIntEnum):
FLAGS = 0x01 FLAGS = 0x01
INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS = 0x02 INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS = 0x02
COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS = 0x03 COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS = 0x03
+85 -82
View File
@@ -49,7 +49,6 @@ from typing import (
) )
from typing_extensions import Self from typing_extensions import Self
from pyee import EventEmitter
from bumble.colors import color from bumble.colors import color
from bumble.att import ATT_CID, ATT_DEFAULT_MTU, ATT_PDU from bumble.att import ATT_CID, ATT_DEFAULT_MTU, ATT_PDU
@@ -71,15 +70,7 @@ from bumble.core import (
OutOfResourcesError, OutOfResourcesError,
UnreachableError, UnreachableError,
) )
from bumble.utils import ( from bumble import utils
AsyncRunner,
CompositeEventEmitter,
EventWatcher,
setup_event_forwarding,
composite_listener,
deprecated,
experimental,
)
from bumble.keys import ( from bumble.keys import (
KeyStore, KeyStore,
PairingKeys, PairingKeys,
@@ -576,7 +567,7 @@ class PeriodicAdvertisingParameters:
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@dataclass @dataclass
class AdvertisingSet(EventEmitter): class AdvertisingSet(utils.EventEmitter):
device: Device device: Device
advertising_handle: int advertising_handle: int
auto_restart: bool auto_restart: bool
@@ -810,7 +801,7 @@ class AdvertisingSet(EventEmitter):
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class PeriodicAdvertisingSync(EventEmitter): class PeriodicAdvertisingSync(utils.EventEmitter):
class State(Enum): class State(Enum):
INIT = 0 INIT = 0
PENDING = 1 PENDING = 1
@@ -939,7 +930,7 @@ class PeriodicAdvertisingSync(EventEmitter):
"received established event for cancelled sync, will terminate" "received established event for cancelled sync, will terminate"
) )
self.state = self.State.ESTABLISHED self.state = self.State.ESTABLISHED
AsyncRunner.spawn(self.terminate()) utils.AsyncRunner.spawn(self.terminate())
return return
if status == hci.HCI_SUCCESS: if status == hci.HCI_SUCCESS:
@@ -1025,7 +1016,7 @@ class BigParameters:
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@dataclass @dataclass
class Big(EventEmitter): class Big(utils.EventEmitter):
class State(IntEnum): class State(IntEnum):
PENDING = 0 PENDING = 0
ACTIVE = 1 ACTIVE = 1
@@ -1065,7 +1056,7 @@ class Big(EventEmitter):
logger.error('BIG %d is not active.', self.big_handle) logger.error('BIG %d is not active.', self.big_handle)
return return
with closing(EventWatcher()) as watcher: with closing(utils.EventWatcher()) as watcher:
terminated = asyncio.Event() terminated = asyncio.Event()
watcher.once(self, Big.Event.TERMINATION, lambda _: terminated.set()) watcher.once(self, Big.Event.TERMINATION, lambda _: terminated.set())
await self.device.send_command( await self.device.send_command(
@@ -1088,7 +1079,7 @@ class BigSyncParameters:
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@dataclass @dataclass
class BigSync(EventEmitter): class BigSync(utils.EventEmitter):
class State(IntEnum): class State(IntEnum):
PENDING = 0 PENDING = 0
ACTIVE = 1 ACTIVE = 1
@@ -1123,7 +1114,7 @@ class BigSync(EventEmitter):
logger.error('BIG Sync %d is not active.', self.big_handle) logger.error('BIG Sync %d is not active.', self.big_handle)
return return
with closing(EventWatcher()) as watcher: with closing(utils.EventWatcher()) as watcher:
terminated = asyncio.Event() terminated = asyncio.Event()
watcher.once(self, BigSync.Event.TERMINATION, lambda _: terminated.set()) watcher.once(self, BigSync.Event.TERMINATION, lambda _: terminated.set())
await self.device.send_command( await self.device.send_command(
@@ -1392,7 +1383,7 @@ ConnectionParametersPreferences.default = ConnectionParametersPreferences()
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@dataclass @dataclass
class ScoLink(CompositeEventEmitter): class ScoLink(utils.CompositeEventEmitter):
device: Device device: Device
acl_connection: Connection acl_connection: Connection
handle: int handle: int
@@ -1483,7 +1474,7 @@ class _IsoLink:
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@dataclass @dataclass
class CisLink(CompositeEventEmitter, _IsoLink): class CisLink(utils.EventEmitter, _IsoLink):
class State(IntEnum): class State(IntEnum):
PENDING = 0 PENDING = 0
ESTABLISHED = 1 ESTABLISHED = 1
@@ -1560,7 +1551,7 @@ class IsoPacketStream:
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class Connection(CompositeEventEmitter): class Connection(utils.CompositeEventEmitter):
device: Device device: Device
handle: int handle: int
transport: core.PhysicalTransport transport: core.PhysicalTransport
@@ -1580,7 +1571,7 @@ class Connection(CompositeEventEmitter):
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
@composite_listener @utils.composite_listener
class Listener: class Listener:
def on_disconnection(self, reason): def on_disconnection(self, reason):
pass pass
@@ -1699,7 +1690,7 @@ class Connection(CompositeEventEmitter):
def send_l2cap_pdu(self, cid: int, pdu: bytes) -> None: def send_l2cap_pdu(self, cid: int, pdu: bytes) -> None:
self.device.send_l2cap_pdu(self.handle, cid, pdu) self.device.send_l2cap_pdu(self.handle, cid, pdu)
@deprecated("Please use create_l2cap_channel()") @utils.deprecated("Please use create_l2cap_channel()")
async def open_l2cap_channel( async def open_l2cap_channel(
self, self,
psm, psm,
@@ -1753,7 +1744,9 @@ class Connection(CompositeEventEmitter):
self.on('disconnection_failure', abort.set_exception) self.on('disconnection_failure', abort.set_exception)
try: try:
await asyncio.wait_for(self.device.abort_on('flush', abort), timeout) await asyncio.wait_for(
utils.cancel_on_event(self.device, 'flush', abort), timeout
)
finally: finally:
self.remove_listener('disconnection', abort.set_result) self.remove_listener('disconnection', abort.set_result)
self.remove_listener('disconnection_failure', abort.set_exception) self.remove_listener('disconnection_failure', abort.set_exception)
@@ -2037,7 +2030,7 @@ device_host_event_handlers: list[str] = []
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class Device(CompositeEventEmitter): class Device(utils.CompositeEventEmitter):
# Incomplete list of fields. # Incomplete list of fields.
random_address: hci.Address # Random private address that may change periodically random_address: hci.Address # Random private address that may change periodically
public_address: ( public_address: (
@@ -2069,7 +2062,7 @@ class Device(CompositeEventEmitter):
_pending_cis: Dict[int, tuple[int, int]] _pending_cis: Dict[int, tuple[int, int]]
gatt_service: gatt_service.GenericAttributeProfileService | None = None gatt_service: gatt_service.GenericAttributeProfileService | None = None
@composite_listener @utils.composite_listener
class Listener: class Listener:
def on_advertisement(self, advertisement): def on_advertisement(self, advertisement):
pass pass
@@ -2290,7 +2283,9 @@ class Device(CompositeEventEmitter):
self.l2cap_channel_manager.register_fixed_channel(ATT_CID, self.on_gatt_pdu) self.l2cap_channel_manager.register_fixed_channel(ATT_CID, self.on_gatt_pdu)
# Forward some events # Forward some events
setup_event_forwarding(self.gatt_server, self, 'characteristic_subscription') utils.setup_event_forwarding(
self.gatt_server, self, 'characteristic_subscription'
)
# Set the initial host # Set the initial host
if host: if host:
@@ -2379,11 +2374,11 @@ class Device(CompositeEventEmitter):
None, None,
) )
@deprecated("Please use create_l2cap_server()") @utils.deprecated("Please use create_l2cap_server()")
def register_l2cap_server(self, psm, server) -> int: def register_l2cap_server(self, psm, server) -> int:
return self.l2cap_channel_manager.register_server(psm, server) return self.l2cap_channel_manager.register_server(psm, server)
@deprecated("Please use create_l2cap_server()") @utils.deprecated("Please use create_l2cap_server()")
def register_l2cap_channel_server( def register_l2cap_channel_server(
self, self,
psm, psm,
@@ -2396,7 +2391,7 @@ class Device(CompositeEventEmitter):
psm, server, max_credits, mtu, mps psm, server, max_credits, mtu, mps
) )
@deprecated("Please use create_l2cap_channel()") @utils.deprecated("Please use create_l2cap_channel()")
async def open_l2cap_channel( async def open_l2cap_channel(
self, self,
connection, connection,
@@ -3237,7 +3232,7 @@ class Device(CompositeEventEmitter):
advertiser_clock_accuracy, advertiser_clock_accuracy,
) )
AsyncRunner.spawn(self._update_periodic_advertising_syncs()) utils.AsyncRunner.spawn(self._update_periodic_advertising_syncs())
return return
@@ -3667,7 +3662,7 @@ class Device(CompositeEventEmitter):
self.le_connecting = True self.le_connecting = True
if timeout is None: if timeout is None:
return await self.abort_on('flush', pending_connection) return await utils.cancel_on_event(self, 'flush', pending_connection)
try: try:
return await asyncio.wait_for( return await asyncio.wait_for(
@@ -3684,7 +3679,9 @@ class Device(CompositeEventEmitter):
) )
try: try:
return await self.abort_on('flush', pending_connection) return await utils.cancel_on_event(
self, 'flush', pending_connection
)
except core.ConnectionError as error: except core.ConnectionError as error:
raise core.TimeoutError() from error raise core.TimeoutError() from error
finally: finally:
@@ -3740,7 +3737,7 @@ class Device(CompositeEventEmitter):
try: try:
# Wait for a request or a completed connection # Wait for a request or a completed connection
pending_request = self.abort_on('flush', pending_request_fut) pending_request = utils.cancel_on_event(self, 'flush', pending_request_fut)
result = await ( result = await (
asyncio.wait_for(pending_request, timeout) asyncio.wait_for(pending_request, timeout)
if timeout if timeout
@@ -3802,7 +3799,7 @@ class Device(CompositeEventEmitter):
) )
# Wait for connection complete # Wait for connection complete
return await self.abort_on('flush', pending_connection) return await utils.cancel_on_event(self, 'flush', pending_connection)
finally: finally:
self.remove_listener('connection', on_connection) self.remove_listener('connection', on_connection)
@@ -3876,7 +3873,7 @@ class Device(CompositeEventEmitter):
# Wait for the disconnection process to complete # Wait for the disconnection process to complete
self.disconnecting = True self.disconnecting = True
return await self.abort_on('flush', pending_disconnection) return await utils.cancel_on_event(self, 'flush', pending_disconnection)
finally: finally:
connection.remove_listener( connection.remove_listener(
'disconnection', pending_disconnection.set_result 'disconnection', pending_disconnection.set_result
@@ -4075,7 +4072,7 @@ class Device(CompositeEventEmitter):
else: else:
return None return None
return await self.abort_on('flush', peer_address) return await utils.cancel_on_event(self, 'flush', peer_address)
finally: finally:
if listener is not None: if listener is not None:
self.remove_listener(event_name, listener) self.remove_listener(event_name, listener)
@@ -4125,7 +4122,7 @@ class Device(CompositeEventEmitter):
if not self.scanning: if not self.scanning:
await self.start_scanning(filter_duplicates=True) await self.start_scanning(filter_duplicates=True)
return await self.abort_on('flush', peer_address) return await utils.cancel_on_event(self, 'flush', peer_address)
finally: finally:
if listener is not None: if listener is not None:
self.remove_listener(event_name, listener) self.remove_listener(event_name, listener)
@@ -4229,7 +4226,9 @@ class Device(CompositeEventEmitter):
raise hci.HCI_StatusError(result) raise hci.HCI_StatusError(result)
# Wait for the authentication to complete # Wait for the authentication to complete
await connection.abort_on('disconnection', pending_authentication) await utils.cancel_on_event(
connection, 'disconnection', pending_authentication
)
finally: finally:
connection.remove_listener('connection_authentication', on_authentication) connection.remove_listener('connection_authentication', on_authentication)
connection.remove_listener( connection.remove_listener(
@@ -4309,7 +4308,7 @@ class Device(CompositeEventEmitter):
raise hci.HCI_StatusError(result) raise hci.HCI_StatusError(result)
# Wait for the result # Wait for the result
await connection.abort_on('disconnection', pending_encryption) await utils.cancel_on_event(connection, 'disconnection', pending_encryption)
finally: finally:
connection.remove_listener( connection.remove_listener(
'connection_encryption_change', on_encryption_change 'connection_encryption_change', on_encryption_change
@@ -4353,7 +4352,9 @@ class Device(CompositeEventEmitter):
f'{hci.HCI_Constant.error_name(result.status)}' f'{hci.HCI_Constant.error_name(result.status)}'
) )
raise hci.HCI_StatusError(result) raise hci.HCI_StatusError(result)
await connection.abort_on('disconnection', pending_role_change) await utils.cancel_on_event(
connection, 'disconnection', pending_role_change
)
finally: finally:
connection.remove_listener('role_change', on_role_change) connection.remove_listener('role_change', on_role_change)
connection.remove_listener('role_change_failure', on_role_change_failure) connection.remove_listener('role_change_failure', on_role_change_failure)
@@ -4402,13 +4403,13 @@ class Device(CompositeEventEmitter):
raise hci.HCI_StatusError(result) raise hci.HCI_StatusError(result)
# Wait for the result # Wait for the result
return await self.abort_on('flush', pending_name) return await utils.cancel_on_event(self, 'flush', pending_name)
finally: finally:
self.remove_listener('remote_name', handler) self.remove_listener('remote_name', handler)
self.remove_listener('remote_name_failure', failure_handler) self.remove_listener('remote_name_failure', failure_handler)
# [LE only] # [LE only]
@experimental('Only for testing.') @utils.experimental('Only for testing.')
async def setup_cig( async def setup_cig(
self, self,
cig_id: int, cig_id: int,
@@ -4466,7 +4467,7 @@ class Device(CompositeEventEmitter):
return cis_handles return cis_handles
# [LE only] # [LE only]
@experimental('Only for testing.') @utils.experimental('Only for testing.')
async def create_cis( async def create_cis(
self, cis_acl_pairs: Sequence[tuple[int, int]] self, cis_acl_pairs: Sequence[tuple[int, int]]
) -> list[CisLink]: ) -> list[CisLink]:
@@ -4482,7 +4483,7 @@ class Device(CompositeEventEmitter):
cig_id=cig_id, cig_id=cig_id,
) )
with closing(EventWatcher()) as watcher: with closing(utils.EventWatcher()) as watcher:
pending_cis_establishments = { pending_cis_establishments = {
cis_handle: asyncio.get_running_loop().create_future() cis_handle: asyncio.get_running_loop().create_future()
for cis_handle, _ in cis_acl_pairs for cis_handle, _ in cis_acl_pairs
@@ -4509,7 +4510,7 @@ class Device(CompositeEventEmitter):
return await asyncio.gather(*pending_cis_establishments.values()) return await asyncio.gather(*pending_cis_establishments.values())
# [LE only] # [LE only]
@experimental('Only for testing.') @utils.experimental('Only for testing.')
async def accept_cis_request(self, handle: int) -> CisLink: async def accept_cis_request(self, handle: int) -> CisLink:
"""[LE Only] Accepts an incoming CIS request. """[LE Only] Accepts an incoming CIS request.
@@ -4531,7 +4532,7 @@ class Device(CompositeEventEmitter):
if cis_link.state == CisLink.State.ESTABLISHED: if cis_link.state == CisLink.State.ESTABLISHED:
return cis_link return cis_link
with closing(EventWatcher()) as watcher: with closing(utils.EventWatcher()) as watcher:
pending_establishment = asyncio.get_running_loop().create_future() pending_establishment = asyncio.get_running_loop().create_future()
def on_establishment() -> None: def on_establishment() -> None:
@@ -4555,7 +4556,7 @@ class Device(CompositeEventEmitter):
raise UnreachableError() raise UnreachableError()
# [LE only] # [LE only]
@experimental('Only for testing.') @utils.experimental('Only for testing.')
async def reject_cis_request( async def reject_cis_request(
self, self,
handle: int, handle: int,
@@ -4569,14 +4570,14 @@ class Device(CompositeEventEmitter):
) )
# [LE only] # [LE only]
@experimental('Only for testing.') @utils.experimental('Only for testing.')
async def create_big( async def create_big(
self, advertising_set: AdvertisingSet, parameters: BigParameters self, advertising_set: AdvertisingSet, parameters: BigParameters
) -> Big: ) -> Big:
if (big_handle := self.next_big_handle()) is None: if (big_handle := self.next_big_handle()) is None:
raise core.OutOfResourcesError("All valid BIG handles already in use") raise core.OutOfResourcesError("All valid BIG handles already in use")
with closing(EventWatcher()) as watcher: with closing(utils.EventWatcher()) as watcher:
big = Big( big = Big(
big_handle=big_handle, big_handle=big_handle,
parameters=parameters, parameters=parameters,
@@ -4619,7 +4620,7 @@ class Device(CompositeEventEmitter):
return big return big
# [LE only] # [LE only]
@experimental('Only for testing.') @utils.experimental('Only for testing.')
async def create_big_sync( async def create_big_sync(
self, pa_sync: PeriodicAdvertisingSync, parameters: BigSyncParameters self, pa_sync: PeriodicAdvertisingSync, parameters: BigSyncParameters
) -> BigSync: ) -> BigSync:
@@ -4629,7 +4630,7 @@ class Device(CompositeEventEmitter):
if (pa_sync_handle := pa_sync.sync_handle) is None: if (pa_sync_handle := pa_sync.sync_handle) is None:
raise core.InvalidStateError("PA Sync is not established") raise core.InvalidStateError("PA Sync is not established")
with closing(EventWatcher()) as watcher: with closing(utils.EventWatcher()) as watcher:
big_sync = BigSync( big_sync = BigSync(
big_handle=big_handle, big_handle=big_handle,
parameters=parameters, parameters=parameters,
@@ -4677,7 +4678,7 @@ class Device(CompositeEventEmitter):
Returns: Returns:
LE features supported by the remote device. LE features supported by the remote device.
""" """
with closing(EventWatcher()) as watcher: with closing(utils.EventWatcher()) as watcher:
read_feature_future: asyncio.Future[hci.LeFeatureMask] = ( read_feature_future: asyncio.Future[hci.LeFeatureMask] = (
asyncio.get_running_loop().create_future() asyncio.get_running_loop().create_future()
) )
@@ -4700,7 +4701,7 @@ class Device(CompositeEventEmitter):
) )
return await read_feature_future return await read_feature_future
@experimental('Only for testing.') @utils.experimental('Only for testing.')
async def get_remote_cs_capabilities( async def get_remote_cs_capabilities(
self, connection: Connection self, connection: Connection
) -> ChannelSoundingCapabilities: ) -> ChannelSoundingCapabilities:
@@ -4708,7 +4709,7 @@ class Device(CompositeEventEmitter):
asyncio.get_running_loop().create_future() asyncio.get_running_loop().create_future()
) )
with closing(EventWatcher()) as watcher: with closing(utils.EventWatcher()) as watcher:
watcher.once( watcher.once(
connection, 'channel_sounding_capabilities', complete_future.set_result connection, 'channel_sounding_capabilities', complete_future.set_result
) )
@@ -4725,7 +4726,7 @@ class Device(CompositeEventEmitter):
) )
return await complete_future return await complete_future
@experimental('Only for testing.') @utils.experimental('Only for testing.')
async def set_default_cs_settings( async def set_default_cs_settings(
self, self,
connection: Connection, connection: Connection,
@@ -4745,7 +4746,7 @@ class Device(CompositeEventEmitter):
check_result=True, check_result=True,
) )
@experimental('Only for testing.') @utils.experimental('Only for testing.')
async def create_cs_config( async def create_cs_config(
self, self,
connection: Connection, connection: Connection,
@@ -4782,7 +4783,7 @@ class Device(CompositeEventEmitter):
if config_id is None: if config_id is None:
raise OutOfResourcesError("No available config ID on this connection!") raise OutOfResourcesError("No available config ID on this connection!")
with closing(EventWatcher()) as watcher: with closing(utils.EventWatcher()) as watcher:
watcher.once( watcher.once(
connection, 'channel_sounding_config', complete_future.set_result connection, 'channel_sounding_config', complete_future.set_result
) )
@@ -4816,12 +4817,12 @@ class Device(CompositeEventEmitter):
) )
return await complete_future return await complete_future
@experimental('Only for testing.') @utils.experimental('Only for testing.')
async def enable_cs_security(self, connection: Connection) -> None: async def enable_cs_security(self, connection: Connection) -> None:
complete_future: asyncio.Future[None] = ( complete_future: asyncio.Future[None] = (
asyncio.get_running_loop().create_future() asyncio.get_running_loop().create_future()
) )
with closing(EventWatcher()) as watcher: with closing(utils.EventWatcher()) as watcher:
def on_event(event: hci.HCI_LE_CS_Security_Enable_Complete_Event) -> None: def on_event(event: hci.HCI_LE_CS_Security_Enable_Complete_Event) -> None:
if event.connection_handle != connection.handle: if event.connection_handle != connection.handle:
@@ -4840,7 +4841,7 @@ class Device(CompositeEventEmitter):
) )
return await complete_future return await complete_future
@experimental('Only for testing.') @utils.experimental('Only for testing.')
async def set_cs_procedure_parameters( async def set_cs_procedure_parameters(
self, self,
connection: Connection, connection: Connection,
@@ -4878,7 +4879,7 @@ class Device(CompositeEventEmitter):
check_result=True, check_result=True,
) )
@experimental('Only for testing.') @utils.experimental('Only for testing.')
async def enable_cs_procedure( async def enable_cs_procedure(
self, self,
connection: Connection, connection: Connection,
@@ -4888,7 +4889,7 @@ class Device(CompositeEventEmitter):
complete_future: asyncio.Future[ChannelSoundingProcedure] = ( complete_future: asyncio.Future[ChannelSoundingProcedure] = (
asyncio.get_running_loop().create_future() asyncio.get_running_loop().create_future()
) )
with closing(EventWatcher()) as watcher: with closing(utils.EventWatcher()) as watcher:
watcher.once( watcher.once(
connection, 'channel_sounding_procedure', complete_future.set_result connection, 'channel_sounding_procedure', complete_future.set_result
) )
@@ -4928,7 +4929,9 @@ class Device(CompositeEventEmitter):
value=link_key, authenticated=authenticated value=link_key, authenticated=authenticated
) )
self.abort_on('flush', self.update_keys(str(bd_addr), pairing_keys)) utils.cancel_on_event(
self, 'flush', self.update_keys(str(bd_addr), pairing_keys)
)
if connection := self.find_connection_by_bd_addr( if connection := self.find_connection_by_bd_addr(
bd_addr, transport=PhysicalTransport.BR_EDR bd_addr, transport=PhysicalTransport.BR_EDR
@@ -5197,7 +5200,7 @@ class Device(CompositeEventEmitter):
if advertising_set.auto_restart: if advertising_set.auto_restart:
connection.once( connection.once(
'disconnection', 'disconnection',
lambda _: self.abort_on('flush', advertising_set.start()), lambda _: utils.cancel_on_event(self, 'flush', advertising_set.start()),
) )
self.emit('connection', connection) self.emit('connection', connection)
@@ -5306,7 +5309,7 @@ class Device(CompositeEventEmitter):
advertiser = self.legacy_advertiser advertiser = self.legacy_advertiser
connection.once( connection.once(
'disconnection', 'disconnection',
lambda _: self.abort_on('flush', advertiser.start()), lambda _: utils.cancel_on_event(self, 'flush', advertiser.start()),
) )
else: else:
self.legacy_advertiser = None self.legacy_advertiser = None
@@ -5433,7 +5436,7 @@ class Device(CompositeEventEmitter):
connection.emit('disconnection_failure', error) connection.emit('disconnection_failure', error)
@host_event_handler @host_event_handler
@AsyncRunner.run_in_task() @utils.AsyncRunner.run_in_task()
async def on_inquiry_complete(self): async def on_inquiry_complete(self):
if self.auto_restart_inquiry: if self.auto_restart_inquiry:
# Inquire again # Inquire again
@@ -5565,7 +5568,7 @@ class Device(CompositeEventEmitter):
async def reply() -> None: async def reply() -> None:
try: try:
if await connection.abort_on('disconnection', method()): if await utils.cancel_on_event(connection, 'disconnection', method()):
await self.host.send_command( await self.host.send_command(
hci.HCI_User_Confirmation_Request_Reply_Command( hci.HCI_User_Confirmation_Request_Reply_Command(
bd_addr=connection.peer_address bd_addr=connection.peer_address
@@ -5581,7 +5584,7 @@ class Device(CompositeEventEmitter):
) )
) )
AsyncRunner.spawn(reply()) utils.AsyncRunner.spawn(reply())
# [Classic only] # [Classic only]
@host_event_handler @host_event_handler
@@ -5592,8 +5595,8 @@ class Device(CompositeEventEmitter):
async def reply() -> None: async def reply() -> None:
try: try:
number = await connection.abort_on( number = await utils.cancel_on_event(
'disconnection', pairing_config.delegate.get_number() connection, 'disconnection', pairing_config.delegate.get_number()
) )
if number is not None: if number is not None:
await self.host.send_command( await self.host.send_command(
@@ -5611,7 +5614,7 @@ class Device(CompositeEventEmitter):
) )
) )
AsyncRunner.spawn(reply()) utils.AsyncRunner.spawn(reply())
# [Classic only] # [Classic only]
@host_event_handler @host_event_handler
@@ -5626,8 +5629,8 @@ class Device(CompositeEventEmitter):
if io_capability == hci.HCI_KEYBOARD_ONLY_IO_CAPABILITY: if io_capability == hci.HCI_KEYBOARD_ONLY_IO_CAPABILITY:
# Ask the user to enter a string # Ask the user to enter a string
async def get_pin_code(): async def get_pin_code():
pin_code = await connection.abort_on( pin_code = await utils.cancel_on_event(
'disconnection', pairing_config.delegate.get_string(16) connection, 'disconnection', pairing_config.delegate.get_string(16)
) )
if pin_code is not None: if pin_code is not None:
@@ -5665,8 +5668,8 @@ class Device(CompositeEventEmitter):
pairing_config = self.pairing_config_factory(connection) pairing_config = self.pairing_config_factory(connection)
# Show the passkey to the user # Show the passkey to the user
connection.abort_on( utils.cancel_on_event(
'disconnection', pairing_config.delegate.display_number(passkey) connection, 'disconnection', pairing_config.delegate.display_number(passkey)
) )
# [Classic only] # [Classic only]
@@ -5698,7 +5701,7 @@ class Device(CompositeEventEmitter):
# [Classic only] # [Classic only]
@host_event_handler @host_event_handler
@with_connection_from_address @with_connection_from_address
@experimental('Only for testing.') @utils.experimental('Only for testing.')
def on_sco_connection( def on_sco_connection(
self, acl_connection: Connection, sco_handle: int, link_type: int self, acl_connection: Connection, sco_handle: int, link_type: int
) -> None: ) -> None:
@@ -5718,7 +5721,7 @@ class Device(CompositeEventEmitter):
# [Classic only] # [Classic only]
@host_event_handler @host_event_handler
@with_connection_from_address @with_connection_from_address
@experimental('Only for testing.') @utils.experimental('Only for testing.')
def on_sco_connection_failure( def on_sco_connection_failure(
self, acl_connection: Connection, status: int self, acl_connection: Connection, status: int
) -> None: ) -> None:
@@ -5727,7 +5730,7 @@ class Device(CompositeEventEmitter):
# [Classic only] # [Classic only]
@host_event_handler @host_event_handler
@experimental('Only for testing') @utils.experimental('Only for testing')
def on_sco_packet( def on_sco_packet(
self, sco_handle: int, packet: hci.HCI_SynchronousDataPacket self, sco_handle: int, packet: hci.HCI_SynchronousDataPacket
) -> None: ) -> None:
@@ -5737,7 +5740,7 @@ class Device(CompositeEventEmitter):
# [LE only] # [LE only]
@host_event_handler @host_event_handler
@with_connection_from_handle @with_connection_from_handle
@experimental('Only for testing') @utils.experimental('Only for testing')
def on_cis_request( def on_cis_request(
self, self,
acl_connection: Connection, acl_connection: Connection,
@@ -5764,7 +5767,7 @@ class Device(CompositeEventEmitter):
# [LE only] # [LE only]
@host_event_handler @host_event_handler
@experimental('Only for testing') @utils.experimental('Only for testing')
def on_cis_establishment(self, cis_handle: int) -> None: def on_cis_establishment(self, cis_handle: int) -> None:
cis_link = self.cis_links[cis_handle] cis_link = self.cis_links[cis_handle]
cis_link.state = CisLink.State.ESTABLISHED cis_link.state = CisLink.State.ESTABLISHED
@@ -5784,7 +5787,7 @@ class Device(CompositeEventEmitter):
# [LE only] # [LE only]
@host_event_handler @host_event_handler
@experimental('Only for testing') @utils.experimental('Only for testing')
def on_cis_establishment_failure(self, cis_handle: int, status: int) -> None: def on_cis_establishment_failure(self, cis_handle: int, status: int) -> None:
logger.debug(f'*** CIS Establishment Failure: cis=[0x{cis_handle:04X}] ***') logger.debug(f'*** CIS Establishment Failure: cis=[0x{cis_handle:04X}] ***')
if cis_link := self.cis_links.pop(cis_handle): if cis_link := self.cis_links.pop(cis_handle):
@@ -5793,7 +5796,7 @@ class Device(CompositeEventEmitter):
# [LE only] # [LE only]
@host_event_handler @host_event_handler
@experimental('Only for testing') @utils.experimental('Only for testing')
def on_iso_packet(self, handle: int, packet: hci.HCI_IsoDataPacket) -> None: def on_iso_packet(self, handle: int, packet: hci.HCI_IsoDataPacket) -> None:
if (cis_link := self.cis_links.get(handle)) and cis_link.sink: if (cis_link := self.cis_links.get(handle)) and cis_link.sink:
cis_link.sink(packet) cis_link.sink(packet)
+3 -3
View File
@@ -35,15 +35,15 @@ from typing import (
from bumble.core import InvalidOperationError from bumble.core import InvalidOperationError
from bumble.gatt import Characteristic from bumble.gatt import Characteristic
from bumble.gatt_client import CharacteristicProxy from bumble.gatt_client import CharacteristicProxy
from bumble.utils import ByteSerializable, IntConvertible from bumble import utils
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Typing # Typing
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
_T = TypeVar('_T') _T = TypeVar('_T')
_T2 = TypeVar('_T2', bound=ByteSerializable) _T2 = TypeVar('_T2', bound=utils.ByteSerializable)
_T3 = TypeVar('_T3', bound=IntConvertible) _T3 = TypeVar('_T3', bound=utils.IntConvertible)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
+3 -3
View File
@@ -44,7 +44,6 @@ from typing import (
TYPE_CHECKING, TYPE_CHECKING,
) )
from pyee import EventEmitter
from bumble.colors import color from bumble.colors import color
from bumble.hci import HCI_Constant from bumble.hci import HCI_Constant
@@ -69,6 +68,7 @@ from bumble.att import (
ATT_Write_Request, ATT_Write_Request,
ATT_Error, ATT_Error,
) )
from bumble import utils
from bumble import core from bumble import core
from bumble.core import UUID, InvalidStateError from bumble.core import UUID, InvalidStateError
from bumble.gatt import ( from bumble.gatt import (
@@ -117,11 +117,11 @@ def show_services(services: Iterable[ServiceProxy]) -> None:
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Proxies # Proxies
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class AttributeProxy(EventEmitter, Generic[_T]): class AttributeProxy(utils.EventEmitter, Generic[_T]):
def __init__( def __init__(
self, client: Client, handle: int, end_group_handle: int, attribute_type: UUID self, client: Client, handle: int, end_group_handle: int, attribute_type: UUID
) -> None: ) -> None:
EventEmitter.__init__(self) utils.EventEmitter.__init__(self)
self.client = client self.client = client
self.handle = handle self.handle = handle
self.end_group_handle = end_group_handle self.end_group_handle = end_group_handle
+9 -10
View File
@@ -38,7 +38,6 @@ from typing import (
Type, Type,
TYPE_CHECKING, TYPE_CHECKING,
) )
from pyee import EventEmitter
from bumble.colors import color from bumble.colors import color
from bumble.core import UUID from bumble.core import UUID
@@ -83,7 +82,7 @@ from bumble.gatt import (
Descriptor, Descriptor,
Service, Service,
) )
from bumble.utils import AsyncRunner from bumble import utils
if TYPE_CHECKING: if TYPE_CHECKING:
from bumble.device import Device, Connection from bumble.device import Device, Connection
@@ -103,7 +102,7 @@ GATT_SERVER_DEFAULT_MAX_MTU = 517
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# GATT Server # GATT Server
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class Server(EventEmitter): class Server(utils.EventEmitter):
attributes: List[Attribute] attributes: List[Attribute]
services: List[Service] services: List[Service]
attributes_by_handle: Dict[int, Attribute] attributes_by_handle: Dict[int, Attribute]
@@ -662,7 +661,7 @@ class Server(EventEmitter):
self.send_response(connection, response) self.send_response(connection, response)
@AsyncRunner.run_in_task() @utils.AsyncRunner.run_in_task()
async def on_att_find_by_type_value_request(self, connection, request): async def on_att_find_by_type_value_request(self, connection, request):
''' '''
See Bluetooth spec Vol 3, Part F - 3.4.3.3 Find By Type Value Request See Bluetooth spec Vol 3, Part F - 3.4.3.3 Find By Type Value Request
@@ -715,7 +714,7 @@ class Server(EventEmitter):
self.send_response(connection, response) self.send_response(connection, response)
@AsyncRunner.run_in_task() @utils.AsyncRunner.run_in_task()
async def on_att_read_by_type_request(self, connection, request): async def on_att_read_by_type_request(self, connection, request):
''' '''
See Bluetooth spec Vol 3, Part F - 3.4.4.1 Read By Type Request See Bluetooth spec Vol 3, Part F - 3.4.4.1 Read By Type Request
@@ -781,7 +780,7 @@ class Server(EventEmitter):
self.send_response(connection, response) self.send_response(connection, response)
@AsyncRunner.run_in_task() @utils.AsyncRunner.run_in_task()
async def on_att_read_request(self, connection, request): async def on_att_read_request(self, connection, request):
''' '''
See Bluetooth spec Vol 3, Part F - 3.4.4.3 Read Request See Bluetooth spec Vol 3, Part F - 3.4.4.3 Read Request
@@ -807,7 +806,7 @@ class Server(EventEmitter):
) )
self.send_response(connection, response) self.send_response(connection, response)
@AsyncRunner.run_in_task() @utils.AsyncRunner.run_in_task()
async def on_att_read_blob_request(self, connection, request): async def on_att_read_blob_request(self, connection, request):
''' '''
See Bluetooth spec Vol 3, Part F - 3.4.4.5 Read Blob Request See Bluetooth spec Vol 3, Part F - 3.4.4.5 Read Blob Request
@@ -852,7 +851,7 @@ class Server(EventEmitter):
) )
self.send_response(connection, response) self.send_response(connection, response)
@AsyncRunner.run_in_task() @utils.AsyncRunner.run_in_task()
async def on_att_read_by_group_type_request(self, connection, request): async def on_att_read_by_group_type_request(self, connection, request):
''' '''
See Bluetooth spec Vol 3, Part F - 3.4.4.9 Read by Group Type Request See Bluetooth spec Vol 3, Part F - 3.4.4.9 Read by Group Type Request
@@ -920,7 +919,7 @@ class Server(EventEmitter):
self.send_response(connection, response) self.send_response(connection, response)
@AsyncRunner.run_in_task() @utils.AsyncRunner.run_in_task()
async def on_att_write_request(self, connection, request): async def on_att_write_request(self, connection, request):
''' '''
See Bluetooth spec Vol 3, Part F - 3.4.5.1 Write Request See Bluetooth spec Vol 3, Part F - 3.4.5.1 Write Request
@@ -967,7 +966,7 @@ class Server(EventEmitter):
response = ATT_Write_Response() response = ATT_Write_Response()
self.send_response(connection, response) self.send_response(connection, response)
@AsyncRunner.run_in_task() @utils.AsyncRunner.run_in_task()
async def on_att_write_command(self, connection, request): async def on_att_write_command(self, connection, request):
''' '''
See Bluetooth spec Vol 3, Part F - 3.4.5.3 Write Command See Bluetooth spec Vol 3, Part F - 3.4.5.3 Write Command
+18 -18
View File
@@ -40,7 +40,7 @@ from bumble.core import (
name_or_number, name_or_number,
padded_bytes, padded_bytes,
) )
from bumble.utils import OpenIntEnum from bumble import utils
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -739,7 +739,7 @@ class PhyBit(enum.IntFlag):
LE_CODED = 1 << HCI_LE_CODED_PHY_BIT LE_CODED = 1 << HCI_LE_CODED_PHY_BIT
class CsRole(OpenIntEnum): class CsRole(utils.OpenIntEnum):
INITIATOR = 0x00 INITIATOR = 0x00
REFLECTOR = 0x01 REFLECTOR = 0x01
@@ -749,7 +749,7 @@ class CsRoleMask(enum.IntFlag):
REFLECTOR = 0x02 REFLECTOR = 0x02
class CsSyncPhy(OpenIntEnum): class CsSyncPhy(utils.OpenIntEnum):
LE_1M = 1 LE_1M = 1
LE_2M = 2 LE_2M = 2
LE_2M_2BT = 3 LE_2M_2BT = 3
@@ -760,7 +760,7 @@ class CsSyncPhySupported(enum.IntFlag):
LE_2M_2BT = 0x02 LE_2M_2BT = 0x02
class RttType(OpenIntEnum): class RttType(utils.OpenIntEnum):
AA_ONLY = 0x00 AA_ONLY = 0x00
SOUNDING_SEQUENCE_32_BIT = 0x01 SOUNDING_SEQUENCE_32_BIT = 0x01
SOUNDING_SEQUENCE_96_BIT = 0x02 SOUNDING_SEQUENCE_96_BIT = 0x02
@@ -770,7 +770,7 @@ class RttType(OpenIntEnum):
RANDOM_SEQUENCE_128_BIT = 0x06 RANDOM_SEQUENCE_128_BIT = 0x06
class CsSnr(OpenIntEnum): class CsSnr(utils.OpenIntEnum):
SNR_18_DB = 0x00 SNR_18_DB = 0x00
SNR_21_DB = 0x01 SNR_21_DB = 0x01
SNR_24_DB = 0x02 SNR_24_DB = 0x02
@@ -779,20 +779,20 @@ class CsSnr(OpenIntEnum):
NOT_APPLIED = 0xFF NOT_APPLIED = 0xFF
class CsDoneStatus(OpenIntEnum): class CsDoneStatus(utils.OpenIntEnum):
ALL_RESULTS_COMPLETED = 0x00 ALL_RESULTS_COMPLETED = 0x00
PARTIAL = 0x01 PARTIAL = 0x01
ABORTED = 0x0F ABORTED = 0x0F
class CsProcedureAbortReason(OpenIntEnum): class CsProcedureAbortReason(utils.OpenIntEnum):
NO_ABORT = 0x00 NO_ABORT = 0x00
LOCAL_HOST_OR_REMOTE_REQUEST = 0x01 LOCAL_HOST_OR_REMOTE_REQUEST = 0x01
CHANNEL_MAP_UPDATE_INSTANT_PASSED = 0x02 CHANNEL_MAP_UPDATE_INSTANT_PASSED = 0x02
UNSPECIFIED = 0x0F UNSPECIFIED = 0x0F
class CsSubeventAbortReason(OpenIntEnum): class CsSubeventAbortReason(utils.OpenIntEnum):
NO_ABORT = 0x00 NO_ABORT = 0x00
LOCAL_HOST_OR_REMOTE_REQUEST = 0x01 LOCAL_HOST_OR_REMOTE_REQUEST = 0x01
NO_CS_SYNC_RECEIVED = 0x02 NO_CS_SYNC_RECEIVED = 0x02
@@ -890,7 +890,7 @@ HCI_LINK_TYPE_NAMES = {
} }
# Address types # Address types
class AddressType(OpenIntEnum): class AddressType(utils.OpenIntEnum):
PUBLIC_DEVICE = 0x00 PUBLIC_DEVICE = 0x00
RANDOM_DEVICE = 0x01 RANDOM_DEVICE = 0x01
PUBLIC_IDENTITY = 0x02 PUBLIC_IDENTITY = 0x02
@@ -1239,7 +1239,7 @@ HCI_SUPPORTED_COMMANDS_MASKS = {
# LE Supported Features # LE Supported Features
# See Bluetooth spec @ Vol 6, Part B, 4.6 FEATURE SUPPORT # See Bluetooth spec @ Vol 6, Part B, 4.6 FEATURE SUPPORT
class LeFeature(OpenIntEnum): class LeFeature(utils.OpenIntEnum):
LE_ENCRYPTION = 0 LE_ENCRYPTION = 0
CONNECTION_PARAMETERS_REQUEST_PROCEDURE = 1 CONNECTION_PARAMETERS_REQUEST_PROCEDURE = 1
EXTENDED_REJECT_INDICATION = 2 EXTENDED_REJECT_INDICATION = 2
@@ -1537,7 +1537,7 @@ RTT_TYPE_SPEC = {'size': 1, 'mapper': lambda x: RttType(x).name}
CS_SNR_SPEC = {'size': 1, 'mapper': lambda x: CsSnr(x).name} CS_SNR_SPEC = {'size': 1, 'mapper': lambda x: CsSnr(x).name}
class CodecID(OpenIntEnum): class CodecID(utils.OpenIntEnum):
# fmt: off # fmt: off
U_LOG = 0x00 U_LOG = 0x00
A_LOG = 0x01 A_LOG = 0x01
@@ -5370,11 +5370,11 @@ class HCI_LE_CS_Create_Config_Command(HCI_Command):
See Bluetooth spec @ 7.8.137 LE CS Create Config command See Bluetooth spec @ 7.8.137 LE CS Create Config command
''' '''
class ChannelSelectionType(OpenIntEnum): class ChannelSelectionType(utils.OpenIntEnum):
ALGO_3B = 0 ALGO_3B = 0
ALGO_3C = 1 ALGO_3C = 1
class Ch3cShape(OpenIntEnum): class Ch3cShape(utils.OpenIntEnum):
HAT = 0x00 HAT = 0x00
X = 0x01 X = 0x01
@@ -6203,13 +6203,13 @@ class HCI_LE_Periodic_Advertising_Report_Event(HCI_LE_Meta_Event):
TX_POWER_INFORMATION_NOT_AVAILABLE = 0x7F TX_POWER_INFORMATION_NOT_AVAILABLE = 0x7F
RSSI_NOT_AVAILABLE = 0x7F RSSI_NOT_AVAILABLE = 0x7F
class CteType(OpenIntEnum): class CteType(utils.OpenIntEnum):
AOA_CONSTANT_TONE_EXTENSION = 0x00 AOA_CONSTANT_TONE_EXTENSION = 0x00
AOD_CONSTANT_TONE_EXTENSION_1US = 0x01 AOD_CONSTANT_TONE_EXTENSION_1US = 0x01
AOD_CONSTANT_TONE_EXTENSION_2US = 0x02 AOD_CONSTANT_TONE_EXTENSION_2US = 0x02
NO_CONSTANT_TONE_EXTENSION = 0xFF NO_CONSTANT_TONE_EXTENSION = 0xFF
class DataStatus(OpenIntEnum): class DataStatus(utils.OpenIntEnum):
DATA_COMPLETE = 0x00 DATA_COMPLETE = 0x00
DATA_INCOMPLETE_MORE_TO_COME = 0x01 DATA_INCOMPLETE_MORE_TO_COME = 0x01
DATA_INCOMPLETE_TRUNCATED_NO_MORE_TO_COME = 0x02 DATA_INCOMPLETE_TRUNCATED_NO_MORE_TO_COME = 0x02
@@ -6590,7 +6590,7 @@ class HCI_LE_CS_Config_Complete_Event(HCI_LE_Meta_Event):
See Bluetooth spec @ 7.7.65.42 LE CS Config Complete event See Bluetooth spec @ 7.7.65.42 LE CS Config Complete event
''' '''
class Action(OpenIntEnum): class Action(utils.OpenIntEnum):
REMOVED = 0 REMOVED = 0
CREATED = 1 CREATED = 1
@@ -6642,7 +6642,7 @@ class HCI_LE_CS_Procedure_Enable_Complete_Event(HCI_LE_Meta_Event):
See Bluetooth spec @ 7.7.65.43 LE CS Procedure Enable Complete event See Bluetooth spec @ 7.7.65.43 LE CS Procedure Enable Complete event
''' '''
class State(OpenIntEnum): class State(utils.OpenIntEnum):
DISABLED = 0 DISABLED = 0
ENABLED = 1 ENABLED = 1
@@ -6984,7 +6984,7 @@ class HCI_QOS_Setup_Complete_Event(HCI_Event):
See Bluetooth spec @ 7.7.13 QoS Setup Complete Event See Bluetooth spec @ 7.7.13 QoS Setup Complete Event
''' '''
class ServiceType(OpenIntEnum): class ServiceType(utils.OpenIntEnum):
NO_TRAFFIC_AVAILABLE = 0x00 NO_TRAFFIC_AVAILABLE = 0x00
BEST_EFFORT_AVAILABLE = 0x01 BEST_EFFORT_AVAILABLE = 0x01
GUARANTEED_AVAILABLE = 0x02 GUARANTEED_AVAILABLE = 0x02
+3 -3
View File
@@ -24,7 +24,6 @@ import asyncio
import dataclasses import dataclasses
import enum import enum
import traceback import traceback
import pyee
import re import re
from typing import ( from typing import (
Dict, Dict,
@@ -45,6 +44,7 @@ from bumble import at
from bumble import device from bumble import device
from bumble import rfcomm from bumble import rfcomm
from bumble import sdp from bumble import sdp
from bumble import utils
from bumble.colors import color from bumble.colors import color
from bumble.core import ( from bumble.core import (
ProtocolError, ProtocolError,
@@ -690,7 +690,7 @@ class HfIndicatorState:
current_status: int = 0 current_status: int = 0
class HfProtocol(pyee.EventEmitter): class HfProtocol(utils.EventEmitter):
""" """
Implementation for the Hands-Free side of the Hands-Free profile. Implementation for the Hands-Free side of the Hands-Free profile.
@@ -1146,7 +1146,7 @@ class HfProtocol(pyee.EventEmitter):
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
class AgProtocol(pyee.EventEmitter): class AgProtocol(utils.EventEmitter):
""" """
Implementation for the Audio-Gateway side of the Hands-Free profile. Implementation for the Audio-Gateway side of the Hands-Free profile.
+4 -3
View File
@@ -22,11 +22,12 @@ import enum
import struct import struct
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from pyee import EventEmitter
from typing import Optional, Callable from typing import Optional, Callable
from typing_extensions import override from typing_extensions import override
from bumble import l2cap, device from bumble import l2cap
from bumble import device
from bumble import utils
from bumble.core import InvalidStateError, ProtocolError from bumble.core import InvalidStateError, ProtocolError
from bumble.hci import Address from bumble.hci import Address
@@ -195,7 +196,7 @@ class SendHandshakeMessage(Message):
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class HID(ABC, EventEmitter): class HID(ABC, utils.EventEmitter):
l2cap_ctrl_channel: Optional[l2cap.ClassicChannel] = None l2cap_ctrl_channel: Optional[l2cap.ClassicChannel] = None
l2cap_intr_channel: Optional[l2cap.ClassicChannel] = None l2cap_intr_channel: Optional[l2cap.ClassicChannel] = None
connection: Optional[device.Connection] = None connection: Optional[device.Connection] = None
+7 -6
View File
@@ -34,7 +34,6 @@ from typing import (
TYPE_CHECKING, TYPE_CHECKING,
) )
import pyee
from bumble.colors import color from bumble.colors import color
from bumble.l2cap import L2CAP_PDU from bumble.l2cap import L2CAP_PDU
@@ -47,7 +46,7 @@ from bumble.core import (
ConnectionPHY, ConnectionPHY,
ConnectionParameters, ConnectionParameters,
) )
from bumble.utils import AbortableEventEmitter from bumble import utils
from bumble.transport.common import TransportLostError from bumble.transport.common import TransportLostError
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -61,7 +60,7 @@ logger = logging.getLogger(__name__)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class DataPacketQueue(pyee.EventEmitter): class DataPacketQueue(utils.EventEmitter):
""" """
Flow-control queue for host->controller data packets (ACL, ISO). Flow-control queue for host->controller data packets (ACL, ISO).
@@ -234,7 +233,7 @@ class IsoLink:
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class Host(AbortableEventEmitter): class Host(utils.EventEmitter):
connections: Dict[int, Connection] connections: Dict[int, Connection]
cis_links: Dict[int, IsoLink] cis_links: Dict[int, IsoLink]
bis_links: Dict[int, IsoLink] bis_links: Dict[int, IsoLink]
@@ -1289,7 +1288,8 @@ class Host(AbortableEventEmitter):
logger.debug('no long term key provider') logger.debug('no long term key provider')
long_term_key = None long_term_key = None
else: else:
long_term_key = await self.abort_on( long_term_key = await utils.cancel_on_event(
self,
'flush', 'flush',
# pylint: disable-next=not-callable # pylint: disable-next=not-callable
self.long_term_key_provider( self.long_term_key_provider(
@@ -1447,7 +1447,8 @@ class Host(AbortableEventEmitter):
logger.debug('no link key provider') logger.debug('no link key provider')
link_key = None link_key = None
else: else:
link_key = await self.abort_on( link_key = await utils.cancel_on_event(
self,
'flush', 'flush',
# pylint: disable-next=not-callable # pylint: disable-next=not-callable
self.link_key_provider(event.bd_addr), self.link_key_provider(event.bd_addr),
+13 -14
View File
@@ -23,7 +23,6 @@ import logging
import struct import struct
from collections import deque from collections import deque
from pyee import EventEmitter
from typing import ( from typing import (
Dict, Dict,
Type, Type,
@@ -39,7 +38,7 @@ from typing import (
TYPE_CHECKING, TYPE_CHECKING,
) )
from bumble.utils import deprecated from bumble import utils
from bumble.colors import color from bumble.colors import color
from bumble.core import ( from bumble.core import (
InvalidStateError, InvalidStateError,
@@ -720,7 +719,7 @@ class L2CAP_LE_Flow_Control_Credit(L2CAP_Control_Frame):
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class ClassicChannel(EventEmitter): class ClassicChannel(utils.EventEmitter):
class State(enum.IntEnum): class State(enum.IntEnum):
# States # States
CLOSED = 0x00 CLOSED = 0x00
@@ -821,8 +820,8 @@ class ClassicChannel(EventEmitter):
# Wait for the connection to succeed or fail # Wait for the connection to succeed or fail
try: try:
return await self.connection.abort_on( return await utils.cancel_on_event(
'disconnection', self.connection_result self.connection, 'disconnection', self.connection_result
) )
finally: finally:
self.connection_result = None self.connection_result = None
@@ -1026,7 +1025,7 @@ class ClassicChannel(EventEmitter):
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class LeCreditBasedChannel(EventEmitter): class LeCreditBasedChannel(utils.EventEmitter):
""" """
LE Credit-based Connection Oriented Channel LE Credit-based Connection Oriented Channel
""" """
@@ -1381,7 +1380,7 @@ class LeCreditBasedChannel(EventEmitter):
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class ClassicChannelServer(EventEmitter): class ClassicChannelServer(utils.EventEmitter):
def __init__( def __init__(
self, self,
manager: ChannelManager, manager: ChannelManager,
@@ -1406,7 +1405,7 @@ class ClassicChannelServer(EventEmitter):
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class LeCreditBasedChannelServer(EventEmitter): class LeCreditBasedChannelServer(utils.EventEmitter):
def __init__( def __init__(
self, self,
manager: ChannelManager, manager: ChannelManager,
@@ -1536,7 +1535,7 @@ class ChannelManager:
if cid in self.fixed_channels: if cid in self.fixed_channels:
del self.fixed_channels[cid] del self.fixed_channels[cid]
@deprecated("Please use create_classic_server") @utils.deprecated("Please use create_classic_server")
def register_server( def register_server(
self, self,
psm: int, psm: int,
@@ -1582,7 +1581,7 @@ class ChannelManager:
return self.servers[spec.psm] return self.servers[spec.psm]
@deprecated("Please use create_le_credit_based_server()") @utils.deprecated("Please use create_le_credit_based_server()")
def register_le_coc_server( def register_le_coc_server(
self, self,
psm: int, psm: int,
@@ -2126,7 +2125,7 @@ class ChannelManager:
if channel.source_cid in connection_channels: if channel.source_cid in connection_channels:
del connection_channels[channel.source_cid] del connection_channels[channel.source_cid]
@deprecated("Please use create_le_credit_based_channel()") @utils.deprecated("Please use create_le_credit_based_channel()")
async def open_le_coc( async def open_le_coc(
self, connection: Connection, psm: int, max_credits: int, mtu: int, mps: int self, connection: Connection, psm: int, max_credits: int, mtu: int, mps: int
) -> LeCreditBasedChannel: ) -> LeCreditBasedChannel:
@@ -2183,7 +2182,7 @@ class ChannelManager:
return channel return channel
@deprecated("Please use create_classic_channel()") @utils.deprecated("Please use create_classic_channel()")
async def connect(self, connection: Connection, psm: int) -> ClassicChannel: async def connect(self, connection: Connection, psm: int) -> ClassicChannel:
return await self.create_classic_channel( return await self.create_classic_channel(
connection=connection, spec=ClassicChannelSpec(psm=psm) connection=connection, spec=ClassicChannelSpec(psm=psm)
@@ -2233,12 +2232,12 @@ class ChannelManager:
class Channel(ClassicChannel): class Channel(ClassicChannel):
@deprecated("Please use ClassicChannel") @utils.deprecated("Please use ClassicChannel")
def __init__(self, *args, **kwargs) -> None: def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
class LeConnectionOrientedChannel(LeCreditBasedChannel): class LeConnectionOrientedChannel(LeCreditBasedChannel):
@deprecated("Please use LeCreditBasedChannel") @utils.deprecated("Please use LeCreditBasedChannel")
def __init__(self, *args, **kwargs) -> None: def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
+10 -3
View File
@@ -20,6 +20,7 @@ import grpc.aio
import logging import logging
import struct import struct
import bumble.utils
from bumble.pandora import utils from bumble.pandora import utils
from bumble.pandora.config import Config from bumble.pandora.config import Config
from bumble.core import ( from bumble.core import (
@@ -534,7 +535,9 @@ class HostService(HostServicer):
try: try:
self.log.debug('Stop advertising') self.log.debug('Stop advertising')
await self.device.abort_on('flush', self.device.stop_advertising()) await bumble.utils.cancel_on_event(
self.device, 'flush', self.device.stop_advertising()
)
except: except:
pass pass
@@ -602,7 +605,9 @@ class HostService(HostServicer):
self.device.remove_listener('advertisement', handler) # type: ignore self.device.remove_listener('advertisement', handler) # type: ignore
try: try:
self.log.debug('Stop scanning') self.log.debug('Stop scanning')
await self.device.abort_on('flush', self.device.stop_scanning()) await bumble.utils.cancel_on_event(
self.device, 'flush', self.device.stop_scanning()
)
except: except:
pass pass
@@ -642,7 +647,9 @@ class HostService(HostServicer):
self.device.remove_listener('inquiry_result', result_handler) # type: ignore self.device.remove_listener('inquiry_result', result_handler) # type: ignore
try: try:
self.log.debug('Stop inquiry') self.log.debug('Stop inquiry')
await self.device.abort_on('flush', self.device.stop_discovery()) await bumble.utils.cancel_on_event(
self.device, 'flush', self.device.stop_discovery()
)
except: except:
pass pass
+3 -3
View File
@@ -25,9 +25,9 @@ from bumble.core import (
PhysicalTransport, PhysicalTransport,
ProtocolError, ProtocolError,
) )
import bumble.utils
from bumble.device import Connection as BumbleConnection, Device from bumble.device import Connection as BumbleConnection, Device
from bumble.hci import HCI_Error, Role from bumble.hci import HCI_Error, Role
from bumble.utils import EventWatcher
from bumble.pairing import PairingConfig, PairingDelegate as BasePairingDelegate from bumble.pairing import PairingConfig, PairingDelegate as BasePairingDelegate
from google.protobuf import any_pb2 # pytype: disable=pyi-error from google.protobuf import any_pb2 # pytype: disable=pyi-error
from google.protobuf import empty_pb2 # pytype: disable=pyi-error from google.protobuf import empty_pb2 # pytype: disable=pyi-error
@@ -300,7 +300,7 @@ class SecurityService(SecurityServicer):
security_result = asyncio.get_running_loop().create_future() security_result = asyncio.get_running_loop().create_future()
with contextlib.closing(EventWatcher()) as watcher: with contextlib.closing(bumble.utils.EventWatcher()) as watcher:
@watcher.on(connection, 'pairing') @watcher.on(connection, 'pairing')
def on_pairing(*_: Any) -> None: def on_pairing(*_: Any) -> None:
@@ -449,7 +449,7 @@ class SecurityService(SecurityServicer):
'security_request': pair, 'security_request': pair,
} }
with contextlib.closing(EventWatcher()) as watcher: with contextlib.closing(bumble.utils.EventWatcher()) as watcher:
# register event handlers # register event handlers
for event, listener in listeners.items(): for event, listener in listeners.items():
watcher.on(connection, event, listener) watcher.on(connection, event, listener)
+6 -6
View File
@@ -48,7 +48,7 @@ from bumble.gatt_adapters import (
UTF8CharacteristicProxyAdapter, UTF8CharacteristicProxyAdapter,
) )
from bumble.gatt_client import ProfileServiceProxy, ServiceProxy from bumble.gatt_client import ProfileServiceProxy, ServiceProxy
from bumble.utils import OpenIntEnum from bumble import utils
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Logging # Logging
@@ -64,7 +64,7 @@ GAIN_SETTINGS_MIN_VALUE = 0
GAIN_SETTINGS_MAX_VALUE = 255 GAIN_SETTINGS_MAX_VALUE = 255
class ErrorCode(OpenIntEnum): class ErrorCode(utils.OpenIntEnum):
''' '''
Cf. 1.6 Application error codes Cf. 1.6 Application error codes
''' '''
@@ -76,7 +76,7 @@ class ErrorCode(OpenIntEnum):
GAIN_MODE_CHANGE_NOT_ALLOWED = 0x84 GAIN_MODE_CHANGE_NOT_ALLOWED = 0x84
class Mute(OpenIntEnum): class Mute(utils.OpenIntEnum):
''' '''
Cf. 2.2.1.2 Mute Field Cf. 2.2.1.2 Mute Field
''' '''
@@ -86,7 +86,7 @@ class Mute(OpenIntEnum):
DISABLED = 0x02 DISABLED = 0x02
class GainMode(OpenIntEnum): class GainMode(utils.OpenIntEnum):
''' '''
Cf. 2.2.1.3 Gain Mode Cf. 2.2.1.3 Gain Mode
''' '''
@@ -97,7 +97,7 @@ class GainMode(OpenIntEnum):
AUTOMATIC = 0x03 AUTOMATIC = 0x03
class AudioInputStatus(OpenIntEnum): class AudioInputStatus(utils.OpenIntEnum):
''' '''
Cf. 3.4 Audio Input Status Cf. 3.4 Audio Input Status
''' '''
@@ -106,7 +106,7 @@ class AudioInputStatus(OpenIntEnum):
ACTIVE = 0x01 ACTIVE = 0x01
class AudioInputControlPointOpCode(OpenIntEnum): class AudioInputControlPointOpCode(utils.OpenIntEnum):
''' '''
Cf. 3.5.1 Audio Input Control Point procedure requirements Cf. 3.5.1 Audio Input Control Point procedure requirements
''' '''
+9 -10
View File
@@ -28,7 +28,6 @@ import logging
import struct import struct
from typing import Optional, Sequence, Union from typing import Optional, Sequence, Union
from pyee import EventEmitter
from bumble.att import ATT_Error from bumble.att import ATT_Error
from bumble.device import Peer from bumble.device import Peer
@@ -42,7 +41,7 @@ from bumble.gatt import (
) )
from bumble.gatt_client import CharacteristicProxy, ProfileServiceProxy, ServiceProxy from bumble.gatt_client import CharacteristicProxy, ProfileServiceProxy, ServiceProxy
from bumble.gatt_adapters import SerializableCharacteristicProxyAdapter from bumble.gatt_adapters import SerializableCharacteristicProxyAdapter
from bumble.utils import OpenIntEnum from bumble import utils
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -60,16 +59,16 @@ logger = logging.getLogger(__name__)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Protocol # Protocol
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class ActionId(OpenIntEnum): class ActionId(utils.OpenIntEnum):
POSITIVE = 0 POSITIVE = 0
NEGATIVE = 1 NEGATIVE = 1
class AppAttributeId(OpenIntEnum): class AppAttributeId(utils.OpenIntEnum):
DISPLAY_NAME = 0 DISPLAY_NAME = 0
class CategoryId(OpenIntEnum): class CategoryId(utils.OpenIntEnum):
OTHER = 0 OTHER = 0
INCOMING_CALL = 1 INCOMING_CALL = 1
MISSED_CALL = 2 MISSED_CALL = 2
@@ -84,13 +83,13 @@ class CategoryId(OpenIntEnum):
ENTERTAINMENT = 11 ENTERTAINMENT = 11
class CommandId(OpenIntEnum): class CommandId(utils.OpenIntEnum):
GET_NOTIFICATION_ATTRIBUTES = 0 GET_NOTIFICATION_ATTRIBUTES = 0
GET_APP_ATTRIBUTES = 1 GET_APP_ATTRIBUTES = 1
PERFORM_NOTIFICATION_ACTION = 2 PERFORM_NOTIFICATION_ACTION = 2
class EventId(OpenIntEnum): class EventId(utils.OpenIntEnum):
NOTIFICATION_ADDED = 0 NOTIFICATION_ADDED = 0
NOTIFICATION_MODIFIED = 1 NOTIFICATION_MODIFIED = 1
NOTIFICATION_REMOVED = 2 NOTIFICATION_REMOVED = 2
@@ -104,7 +103,7 @@ class EventFlags(enum.IntFlag):
NEGATIVE_ACTION = 1 << 4 NEGATIVE_ACTION = 1 << 4
class NotificationAttributeId(OpenIntEnum): class NotificationAttributeId(utils.OpenIntEnum):
APP_IDENTIFIER = 0 APP_IDENTIFIER = 0
TITLE = 1 TITLE = 1
SUBTITLE = 2 SUBTITLE = 2
@@ -156,7 +155,7 @@ class Notification:
) )
class ErrorCode(OpenIntEnum): class ErrorCode(utils.OpenIntEnum):
UNKNOWN_COMMAND = 0xA0 UNKNOWN_COMMAND = 0xA0
INVALID_COMMAND = 0xA1 INVALID_COMMAND = 0xA1
INVALID_PARAMETER = 0xA2 INVALID_PARAMETER = 0xA2
@@ -243,7 +242,7 @@ class AncsProxy(ProfileServiceProxy):
) )
class AncsClient(EventEmitter): class AncsClient(utils.EventEmitter):
_expected_response_command_id: Optional[CommandId] _expected_response_command_id: Optional[CommandId]
_expected_response_notification_uid: Optional[int] _expected_response_notification_uid: Optional[int]
_expected_response_app_identifier: Optional[str] _expected_response_app_identifier: Optional[str]
+13 -6
View File
@@ -23,6 +23,7 @@ import logging
import struct import struct
from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, Union from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, Union
from bumble import utils
from bumble import colors from bumble import colors
from bumble.profiles.bap import CodecSpecificConfiguration from bumble.profiles.bap import CodecSpecificConfiguration
from bumble.profiles import le_audio from bumble.profiles import le_audio
@@ -343,8 +344,10 @@ class AseStateMachine(gatt.Characteristic):
and cis_id == self.cis_id and cis_id == self.cis_id
and self.state == self.State.ENABLING and self.state == self.State.ENABLING
): ):
acl_connection.abort_on( utils.cancel_on_event(
'flush', self.service.device.accept_cis_request(cis_handle) acl_connection,
'flush',
self.service.device.accept_cis_request(cis_handle),
) )
def on_cis_establishment(self, cis_link: device.CisLink) -> None: def on_cis_establishment(self, cis_link: device.CisLink) -> None:
@@ -361,7 +364,9 @@ class AseStateMachine(gatt.Characteristic):
self.state = self.State.STREAMING self.state = self.State.STREAMING
await self.service.device.notify_subscribers(self, self.value) await self.service.device.notify_subscribers(self, self.value)
cis_link.acl_connection.abort_on('flush', post_cis_established()) utils.cancel_on_event(
cis_link.acl_connection, 'flush', post_cis_established()
)
self.cis_link = cis_link self.cis_link = cis_link
def on_cis_disconnection(self, _reason) -> None: def on_cis_disconnection(self, _reason) -> None:
@@ -509,7 +514,7 @@ class AseStateMachine(gatt.Characteristic):
self.state = self.State.IDLE self.state = self.State.IDLE
await self.service.device.notify_subscribers(self, self.value) await self.service.device.notify_subscribers(self, self.value)
self.service.device.abort_on('flush', remove_cis_async()) utils.cancel_on_event(self.service.device, 'flush', remove_cis_async())
return (AseResponseCode.SUCCESS, AseReasonCode.NONE) return (AseResponseCode.SUCCESS, AseReasonCode.NONE)
@property @property
@@ -691,7 +696,8 @@ class AudioStreamControlService(gatt.TemplateService):
control_point_notification = bytes( control_point_notification = bytes(
[operation.op_code, len(responses)] [operation.op_code, len(responses)]
) + b''.join(map(bytes, responses)) ) + b''.join(map(bytes, responses))
self.device.abort_on( utils.cancel_on_event(
self.device,
'flush', 'flush',
self.device.notify_subscribers( self.device.notify_subscribers(
self.ase_control_point, control_point_notification self.ase_control_point, control_point_notification
@@ -700,7 +706,8 @@ class AudioStreamControlService(gatt.TemplateService):
for ase_id, *_ in responses: for ase_id, *_ in responses:
if ase := self.ase_state_machines.get(ase_id): if ase := self.ase_state_machines.get(ase_id):
self.device.abort_on( utils.cancel_on_event(
self.device,
'flush', 'flush',
self.device.notify_subscribers(ase, ase.value), self.device.notify_subscribers(ase, ase.value),
) )
+13 -13
View File
@@ -25,14 +25,14 @@ from typing import Any, Dict, List, Optional, Set, Union
from bumble import att, gatt, gatt_adapters, gatt_client from bumble import att, gatt, gatt_adapters, gatt_client
from bumble.core import InvalidArgumentError, InvalidStateError from bumble.core import InvalidArgumentError, InvalidStateError
from bumble.device import Device, Connection from bumble.device import Device, Connection
from bumble.utils import AsyncRunner, OpenIntEnum from bumble import utils
from bumble.hci import Address from bumble.hci import Address
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Constants # Constants
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class ErrorCode(OpenIntEnum): class ErrorCode(utils.OpenIntEnum):
'''See Hearing Access Service 2.4. Attribute Profile error codes.''' '''See Hearing Access Service 2.4. Attribute Profile error codes.'''
INVALID_OPCODE = 0x80 INVALID_OPCODE = 0x80
@@ -42,7 +42,7 @@ class ErrorCode(OpenIntEnum):
INVALID_PARAMETERS_LENGTH = 0x84 INVALID_PARAMETERS_LENGTH = 0x84
class HearingAidType(OpenIntEnum): class HearingAidType(utils.OpenIntEnum):
'''See Hearing Access Service 3.1. Hearing Aid Features.''' '''See Hearing Access Service 3.1. Hearing Aid Features.'''
BINAURAL_HEARING_AID = 0b00 BINAURAL_HEARING_AID = 0b00
@@ -50,35 +50,35 @@ class HearingAidType(OpenIntEnum):
BANDED_HEARING_AID = 0b10 BANDED_HEARING_AID = 0b10
class PresetSynchronizationSupport(OpenIntEnum): class PresetSynchronizationSupport(utils.OpenIntEnum):
'''See Hearing Access Service 3.1. Hearing Aid Features.''' '''See Hearing Access Service 3.1. Hearing Aid Features.'''
PRESET_SYNCHRONIZATION_IS_NOT_SUPPORTED = 0b0 PRESET_SYNCHRONIZATION_IS_NOT_SUPPORTED = 0b0
PRESET_SYNCHRONIZATION_IS_SUPPORTED = 0b1 PRESET_SYNCHRONIZATION_IS_SUPPORTED = 0b1
class IndependentPresets(OpenIntEnum): class IndependentPresets(utils.OpenIntEnum):
'''See Hearing Access Service 3.1. Hearing Aid Features.''' '''See Hearing Access Service 3.1. Hearing Aid Features.'''
IDENTICAL_PRESET_RECORD = 0b0 IDENTICAL_PRESET_RECORD = 0b0
DIFFERENT_PRESET_RECORD = 0b1 DIFFERENT_PRESET_RECORD = 0b1
class DynamicPresets(OpenIntEnum): class DynamicPresets(utils.OpenIntEnum):
'''See Hearing Access Service 3.1. Hearing Aid Features.''' '''See Hearing Access Service 3.1. Hearing Aid Features.'''
PRESET_RECORDS_DOES_NOT_CHANGE = 0b0 PRESET_RECORDS_DOES_NOT_CHANGE = 0b0
PRESET_RECORDS_MAY_CHANGE = 0b1 PRESET_RECORDS_MAY_CHANGE = 0b1
class WritablePresetsSupport(OpenIntEnum): class WritablePresetsSupport(utils.OpenIntEnum):
'''See Hearing Access Service 3.1. Hearing Aid Features.''' '''See Hearing Access Service 3.1. Hearing Aid Features.'''
WRITABLE_PRESET_RECORDS_NOT_SUPPORTED = 0b0 WRITABLE_PRESET_RECORDS_NOT_SUPPORTED = 0b0
WRITABLE_PRESET_RECORDS_SUPPORTED = 0b1 WRITABLE_PRESET_RECORDS_SUPPORTED = 0b1
class HearingAidPresetControlPointOpcode(OpenIntEnum): class HearingAidPresetControlPointOpcode(utils.OpenIntEnum):
'''See Hearing Access Service 3.3.1 Hearing Aid Preset Control Point operation requirements.''' '''See Hearing Access Service 3.3.1 Hearing Aid Preset Control Point operation requirements.'''
# fmt: off # fmt: off
@@ -130,7 +130,7 @@ def HearingAidFeatures_from_bytes(data: int) -> HearingAidFeatures:
class PresetChangedOperation: class PresetChangedOperation:
'''See Hearing Access Service 3.2.2.2. Preset Changed operation.''' '''See Hearing Access Service 3.2.2.2. Preset Changed operation.'''
class ChangeId(OpenIntEnum): class ChangeId(utils.OpenIntEnum):
# fmt: off # fmt: off
GENERIC_UPDATE = 0x00 GENERIC_UPDATE = 0x00
PRESET_RECORD_DELETED = 0x01 PRESET_RECORD_DELETED = 0x01
@@ -190,11 +190,11 @@ class PresetRecord:
@dataclass @dataclass
class Property: class Property:
class Writable(OpenIntEnum): class Writable(utils.OpenIntEnum):
CANNOT_BE_WRITTEN = 0b0 CANNOT_BE_WRITTEN = 0b0
CAN_BE_WRITTEN = 0b1 CAN_BE_WRITTEN = 0b1
class IsAvailable(OpenIntEnum): class IsAvailable(utils.OpenIntEnum):
IS_UNAVAILABLE = 0b0 IS_UNAVAILABLE = 0b0
IS_AVAILABLE = 0b1 IS_AVAILABLE = 0b1
@@ -333,7 +333,7 @@ class HearingAccessService(gatt.TemplateService):
# Update the active preset index if needed # Update the active preset index if needed
await self.notify_active_preset_for_connection(connection) await self.notify_active_preset_for_connection(connection)
connection.abort_on('disconnection', on_connection_async()) utils.cancel_on_event(connection, 'disconnection', on_connection_async())
def _on_read_active_preset_index( def _on_read_active_preset_index(
self, __connection__: Optional[Connection] self, __connection__: Optional[Connection]
@@ -382,7 +382,7 @@ class HearingAccessService(gatt.TemplateService):
if len(presets) == 0: if len(presets) == 0:
raise att.ATT_Error(att.ErrorCode.OUT_OF_RANGE) raise att.ATT_Error(att.ErrorCode.OUT_OF_RANGE)
AsyncRunner.spawn(self._read_preset_response(connection, presets)) utils.AsyncRunner.spawn(self._read_preset_response(connection, presets))
async def _read_preset_response( async def _read_preset_response(
self, connection: Connection, presets: List[PresetRecord] self, connection: Connection, presets: List[PresetRecord]
+3 -1
View File
@@ -23,6 +23,7 @@ import enum
from typing import Optional, Sequence from typing import Optional, Sequence
from bumble import att from bumble import att
from bumble import utils
from bumble import device from bumble import device
from bumble import gatt from bumble import gatt
from bumble import gatt_adapters from bumble import gatt_adapters
@@ -160,7 +161,8 @@ class VolumeControlService(gatt.TemplateService):
handler = getattr(self, '_on_' + opcode.name.lower()) handler = getattr(self, '_on_' + opcode.name.lower())
if handler(*value[2:]): if handler(*value[2:]):
self.change_counter = (self.change_counter + 1) % 256 self.change_counter = (self.change_counter + 1) % 256
connection.abort_on( utils.cancel_on_event(
connection,
'disconnection', 'disconnection',
connection.device.notify_subscribers(attribute=self.volume_state), connection.device.notify_subscribers(attribute=self.volume_state),
) )
+3 -3
View File
@@ -38,7 +38,7 @@ from bumble.gatt_adapters import (
UTF8CharacteristicProxyAdapter, UTF8CharacteristicProxyAdapter,
) )
from bumble.gatt_client import ProfileServiceProxy, ServiceProxy from bumble.gatt_client import ProfileServiceProxy, ServiceProxy
from bumble.utils import OpenIntEnum from bumble import utils
from bumble.profiles.bap import AudioLocation from bumble.profiles.bap import AudioLocation
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -50,11 +50,11 @@ MAX_VOLUME_OFFSET = 255
CHANGE_COUNTER_MAX_VALUE = 0xFF CHANGE_COUNTER_MAX_VALUE = 0xFF
class SetVolumeOffsetOpCode(OpenIntEnum): class SetVolumeOffsetOpCode(utils.OpenIntEnum):
SET_VOLUME_OFFSET = 0x01 SET_VOLUME_OFFSET = 0x01
class ErrorCode(OpenIntEnum): class ErrorCode(utils.OpenIntEnum):
""" """
See Volume Offset Control Service 1.6. Application error codes. See Volume Offset Control Service 1.6. Application error codes.
""" """
+4 -4
View File
@@ -25,11 +25,11 @@ import enum
from typing import Callable, Dict, List, Optional, Tuple, Union, TYPE_CHECKING from typing import Callable, Dict, List, Optional, Tuple, Union, TYPE_CHECKING
from typing_extensions import Self from typing_extensions import Self
from pyee import EventEmitter
from bumble import core from bumble import core
from bumble import l2cap from bumble import l2cap
from bumble import sdp from bumble import sdp
from bumble import utils
from bumble.colors import color from bumble.colors import color
from bumble.core import ( from bumble.core import (
UUID, UUID,
@@ -441,7 +441,7 @@ class RFCOMM_MCC_MSC:
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class DLC(EventEmitter): class DLC(utils.EventEmitter):
class State(enum.IntEnum): class State(enum.IntEnum):
INIT = 0x00 INIT = 0x00
CONNECTING = 0x01 CONNECTING = 0x01
@@ -749,7 +749,7 @@ class DLC(EventEmitter):
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class Multiplexer(EventEmitter): class Multiplexer(utils.EventEmitter):
class Role(enum.IntEnum): class Role(enum.IntEnum):
INITIATOR = 0x00 INITIATOR = 0x00
RESPONDER = 0x01 RESPONDER = 0x01
@@ -1075,7 +1075,7 @@ class Client:
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class Server(EventEmitter): class Server(utils.EventEmitter):
def __init__( def __init__(
self, device: Device, l2cap_mtu: int = RFCOMM_DEFAULT_L2CAP_MTU self, device: Device, l2cap_mtu: int = RFCOMM_DEFAULT_L2CAP_MTU
) -> None: ) -> None:
+21 -16
View File
@@ -41,7 +41,6 @@ from typing import (
cast, cast,
) )
from pyee import EventEmitter
from bumble.colors import color from bumble.colors import color
from bumble.hci import ( from bumble.hci import (
@@ -60,6 +59,7 @@ from bumble.core import (
) )
from bumble.keys import PairingKeys from bumble.keys import PairingKeys
from bumble import crypto from bumble import crypto
from bumble import utils
if TYPE_CHECKING: if TYPE_CHECKING:
from bumble.device import Connection, Device from bumble.device import Connection, Device
@@ -899,7 +899,7 @@ class Session:
self.send_pairing_failed(SMP_CONFIRM_VALUE_FAILED_ERROR) self.send_pairing_failed(SMP_CONFIRM_VALUE_FAILED_ERROR)
self.connection.abort_on('disconnection', prompt()) utils.cancel_on_event(self.connection, 'disconnection', prompt())
def prompt_user_for_numeric_comparison( def prompt_user_for_numeric_comparison(
self, code: int, next_steps: Callable[[], None] self, code: int, next_steps: Callable[[], None]
@@ -918,7 +918,7 @@ class Session:
self.send_pairing_failed(SMP_CONFIRM_VALUE_FAILED_ERROR) self.send_pairing_failed(SMP_CONFIRM_VALUE_FAILED_ERROR)
self.connection.abort_on('disconnection', prompt()) utils.cancel_on_event(self.connection, 'disconnection', prompt())
def prompt_user_for_number(self, next_steps: Callable[[int], None]) -> None: def prompt_user_for_number(self, next_steps: Callable[[int], None]) -> None:
async def prompt() -> None: async def prompt() -> None:
@@ -935,7 +935,7 @@ class Session:
logger.warning(f'exception while prompting: {error}') logger.warning(f'exception while prompting: {error}')
self.send_pairing_failed(SMP_PASSKEY_ENTRY_FAILED_ERROR) self.send_pairing_failed(SMP_PASSKEY_ENTRY_FAILED_ERROR)
self.connection.abort_on('disconnection', prompt()) utils.cancel_on_event(self.connection, 'disconnection', prompt())
def display_passkey(self) -> None: def display_passkey(self) -> None:
# Generate random Passkey/PIN code # Generate random Passkey/PIN code
@@ -950,7 +950,8 @@ class Session:
logger.debug(f'TK from passkey = {self.tk.hex()}') logger.debug(f'TK from passkey = {self.tk.hex()}')
try: try:
self.connection.abort_on( utils.cancel_on_event(
self.connection,
'disconnection', 'disconnection',
self.pairing_config.delegate.display_number(self.passkey, digits=6), self.pairing_config.delegate.display_number(self.passkey, digits=6),
) )
@@ -1049,7 +1050,7 @@ class Session:
) )
# Perform the next steps asynchronously in case we need to wait for input # Perform the next steps asynchronously in case we need to wait for input
self.connection.abort_on('disconnection', next_steps()) utils.cancel_on_event(self.connection, 'disconnection', next_steps())
else: else:
confirm_value = crypto.c1( confirm_value = crypto.c1(
self.tk, self.tk,
@@ -1172,8 +1173,8 @@ class Session:
self.connection.transport == PhysicalTransport.BR_EDR self.connection.transport == PhysicalTransport.BR_EDR
and self.initiator_key_distribution & SMP_ENC_KEY_DISTRIBUTION_FLAG and self.initiator_key_distribution & SMP_ENC_KEY_DISTRIBUTION_FLAG
): ):
self.ctkd_task = self.connection.abort_on( self.ctkd_task = utils.cancel_on_event(
'disconnection', self.get_link_key_and_derive_ltk() self.connection, 'disconnection', self.get_link_key_and_derive_ltk()
) )
elif not self.sc: elif not self.sc:
# Distribute the LTK, EDIV and RAND # Distribute the LTK, EDIV and RAND
@@ -1211,8 +1212,8 @@ class Session:
self.connection.transport == PhysicalTransport.BR_EDR self.connection.transport == PhysicalTransport.BR_EDR
and self.responder_key_distribution & SMP_ENC_KEY_DISTRIBUTION_FLAG and self.responder_key_distribution & SMP_ENC_KEY_DISTRIBUTION_FLAG
): ):
self.ctkd_task = self.connection.abort_on( self.ctkd_task = utils.cancel_on_event(
'disconnection', self.get_link_key_and_derive_ltk() self.connection, 'disconnection', self.get_link_key_and_derive_ltk()
) )
# Distribute the LTK, EDIV and RAND # Distribute the LTK, EDIV and RAND
elif not self.sc: elif not self.sc:
@@ -1304,7 +1305,9 @@ class Session:
# Wait for the pairing process to finish # Wait for the pairing process to finish
assert self.pairing_result assert self.pairing_result
await self.connection.abort_on('disconnection', self.pairing_result) await utils.cancel_on_event(
self.connection, 'disconnection', self.pairing_result
)
def on_disconnection(self, _: int) -> None: def on_disconnection(self, _: int) -> None:
self.connection.remove_listener('disconnection', self.on_disconnection) self.connection.remove_listener('disconnection', self.on_disconnection)
@@ -1322,7 +1325,7 @@ class Session:
if self.is_initiator: if self.is_initiator:
self.distribute_keys() self.distribute_keys()
self.connection.abort_on('disconnection', self.on_pairing()) utils.cancel_on_event(self.connection, 'disconnection', self.on_pairing())
def on_connection_encryption_change(self) -> None: def on_connection_encryption_change(self) -> None:
if self.connection.is_encrypted and not self.completed: if self.connection.is_encrypted and not self.completed:
@@ -1431,8 +1434,10 @@ class Session:
def on_smp_pairing_request_command( def on_smp_pairing_request_command(
self, command: SMP_Pairing_Request_Command self, command: SMP_Pairing_Request_Command
) -> None: ) -> None:
self.connection.abort_on( utils.cancel_on_event(
'disconnection', self.on_smp_pairing_request_command_async(command) self.connection,
'disconnection',
self.on_smp_pairing_request_command_async(command),
) )
async def on_smp_pairing_request_command_async( async def on_smp_pairing_request_command_async(
@@ -1877,7 +1882,7 @@ class Session:
self.wait_before_continuing = None self.wait_before_continuing = None
self.send_pairing_dhkey_check_command() self.send_pairing_dhkey_check_command()
self.connection.abort_on('disconnection', next_steps()) utils.cancel_on_event(self.connection, 'disconnection', next_steps())
else: else:
self.send_pairing_dhkey_check_command() self.send_pairing_dhkey_check_command()
else: else:
@@ -1921,7 +1926,7 @@ class Session:
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class Manager(EventEmitter): class Manager(utils.EventEmitter):
''' '''
Implements the Initiator and Responder roles of the Security Manager Protocol Implements the Initiator and Responder roles of the Security Manager Protocol
''' '''
+91 -78
View File
@@ -38,7 +38,8 @@ from typing import (
) )
from typing_extensions import Self from typing_extensions import Self
from pyee import EventEmitter import pyee
import pyee.asyncio
from bumble.colors import color from bumble.colors import color
@@ -56,6 +57,48 @@ def setup_event_forwarding(emitter, forwarder, event_name):
emitter.on(event_name, emit) emitter.on(event_name, emit)
# -----------------------------------------------------------------------------
def wrap_async(function):
"""
Wraps the provided function in an async function.
"""
return functools.partial(async_call, function)
# -----------------------------------------------------------------------------
def deprecated(msg: str):
"""
Throw deprecation warning before execution.
"""
def wrapper(function):
@functools.wraps(function)
def inner(*args, **kwargs):
warnings.warn(msg, DeprecationWarning, stacklevel=2)
return function(*args, **kwargs)
return inner
return wrapper
# -----------------------------------------------------------------------------
def experimental(msg: str):
"""
Throws a future warning before execution.
"""
def wrapper(function):
@functools.wraps(function)
def inner(*args, **kwargs):
warnings.warn(msg, FutureWarning, stacklevel=2)
return function(*args, **kwargs)
return inner
return wrapper
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
def composite_listener(cls): def composite_listener(cls):
""" """
@@ -113,21 +156,23 @@ class EventWatcher:
``` ```
''' '''
handlers: List[Tuple[EventEmitter, str, Callable[..., Any]]] handlers: List[Tuple[pyee.EventEmitter, str, Callable[..., Any]]]
def __init__(self) -> None: def __init__(self) -> None:
self.handlers = [] self.handlers = []
@overload @overload
def on( def on(
self, emitter: EventEmitter, event: str self, emitter: pyee.EventEmitter, event: str
) -> Callable[[_Handler], _Handler]: ... ) -> Callable[[_Handler], _Handler]: ...
@overload @overload
def on(self, emitter: EventEmitter, event: str, handler: _Handler) -> _Handler: ... def on(
self, emitter: pyee.EventEmitter, event: str, handler: _Handler
) -> _Handler: ...
def on( def on(
self, emitter: EventEmitter, event: str, handler: Optional[_Handler] = None self, emitter: pyee.EventEmitter, event: str, handler: Optional[_Handler] = None
) -> Union[_Handler, Callable[[_Handler], _Handler]]: ) -> Union[_Handler, Callable[[_Handler], _Handler]]:
'''Watch an event until the context is closed. '''Watch an event until the context is closed.
@@ -147,16 +192,16 @@ class EventWatcher:
@overload @overload
def once( def once(
self, emitter: EventEmitter, event: str self, emitter: pyee.EventEmitter, event: str
) -> Callable[[_Handler], _Handler]: ... ) -> Callable[[_Handler], _Handler]: ...
@overload @overload
def once( def once(
self, emitter: EventEmitter, event: str, handler: _Handler self, emitter: pyee.EventEmitter, event: str, handler: _Handler
) -> _Handler: ... ) -> _Handler: ...
def once( def once(
self, emitter: EventEmitter, event: str, handler: Optional[_Handler] = None self, emitter: pyee.EventEmitter, event: str, handler: Optional[_Handler] = None
) -> Union[_Handler, Callable[[_Handler], _Handler]]: ) -> Union[_Handler, Callable[[_Handler], _Handler]]:
'''Watch an event for once. '''Watch an event for once.
@@ -184,38 +229,48 @@ class EventWatcher:
_T = TypeVar('_T') _T = TypeVar('_T')
class AbortableEventEmitter(EventEmitter): def cancel_on_event(
def abort_on(self, event: str, awaitable: Awaitable[_T]) -> Awaitable[_T]: emitter: pyee.EventEmitter, event: str, awaitable: Awaitable[_T]
""" ) -> Awaitable[_T]:
Set a coroutine or future to abort when an event occur. """Set a coroutine or future to cancel when an event occur."""
""" future = asyncio.ensure_future(awaitable)
future = asyncio.ensure_future(awaitable) if future.done():
if future.done():
return future
def on_event(*_):
if future.done():
return
msg = f'abort: {event} event occurred.'
if isinstance(future, asyncio.Task):
# python < 3.9 does not support passing a message on `Task.cancel`
if sys.version_info < (3, 9, 0):
future.cancel()
else:
future.cancel(msg)
else:
future.set_exception(asyncio.CancelledError(msg))
def on_done(_):
self.remove_listener(event, on_event)
self.on(event, on_event)
future.add_done_callback(on_done)
return future return future
def on_event(*args, **kwargs) -> None:
del args, kwargs
if future.done():
return
msg = f'abort: {event} event occurred.'
if isinstance(future, asyncio.Task):
# python < 3.9 does not support passing a message on `Task.cancel`
if sys.version_info < (3, 9, 0):
future.cancel()
else:
future.cancel(msg)
else:
future.set_exception(asyncio.CancelledError(msg))
def on_done(_):
emitter.remove_listener(event, on_event)
emitter.on(event, on_event)
future.add_done_callback(on_done)
return future
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class CompositeEventEmitter(AbortableEventEmitter): class EventEmitter(pyee.asyncio.AsyncIOEventEmitter):
"""A Base EventEmitter for Bumble."""
@deprecated("Use `cancel_on_event` instead.")
def abort_on(self, event: str, awaitable: Awaitable[_T]) -> Awaitable[_T]:
"""Set a coroutine or future to abort when an event occur."""
return cancel_on_event(self, event, awaitable)
# -----------------------------------------------------------------------------
class CompositeEventEmitter(EventEmitter):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self._listener = None self._listener = None
@@ -430,48 +485,6 @@ async def async_call(function, *args, **kwargs):
return function(*args, **kwargs) return function(*args, **kwargs)
# -----------------------------------------------------------------------------
def wrap_async(function):
"""
Wraps the provided function in an async function.
"""
return functools.partial(async_call, function)
# -----------------------------------------------------------------------------
def deprecated(msg: str):
"""
Throw deprecation warning before execution.
"""
def wrapper(function):
@functools.wraps(function)
def inner(*args, **kwargs):
warnings.warn(msg, DeprecationWarning, stacklevel=2)
return function(*args, **kwargs)
return inner
return wrapper
# -----------------------------------------------------------------------------
def experimental(msg: str):
"""
Throws a future warning before execution.
"""
def wrapper(function):
@functools.wraps(function)
def inner(*args, **kwargs):
warnings.warn(msg, FutureWarning, stacklevel=2)
return function(*args, **kwargs)
return inner
return wrapper
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class OpenIntEnum(enum.IntEnum): class OpenIntEnum(enum.IntEnum):
""" """
+5 -7
View File
@@ -19,12 +19,8 @@ import asyncio
import logging import logging
import sys import sys
import os import os
from bumble.device import ( from bumble import utils
Device, from bumble.device import Device, Connection
Connection,
AdvertisingParameters,
AdvertisingEventProperties,
)
from bumble.hci import ( from bumble.hci import (
OwnAddressType, OwnAddressType,
) )
@@ -79,7 +75,9 @@ async def main() -> None:
def on_cis_request( def on_cis_request(
connection: Connection, cis_handle: int, _cig_id: int, _cis_id: int connection: Connection, cis_handle: int, _cig_id: int, _cis_id: int
): ):
connection.abort_on('disconnection', devices[0].accept_cis_request(cis_handle)) utils.cancel_on_event(
connection, 'disconnection', devices[0].accept_cis_request(cis_handle)
)
devices[0].on('cis_request', on_cis_request) devices[0].on('cis_request', on_cis_request)
+3 -1
View File
@@ -25,6 +25,7 @@ import websockets
import functools import functools
from typing import Optional from typing import Optional
from bumble import utils
from bumble import rfcomm from bumble import rfcomm
from bumble import hci from bumble import hci
from bumble.device import Device, Connection from bumble.device import Device, Connection
@@ -60,7 +61,8 @@ def on_dlc(dlc: rfcomm.DLC, configuration: hfp.HfConfiguration):
else: else:
raise RuntimeError("unknown active codec") raise RuntimeError("unknown active codec")
connection.abort_on( utils.cancel_on_event(
connection,
'disconnection', 'disconnection',
connection.device.send_command( connection.device.send_command(
hci.HCI_Enhanced_Accept_Synchronous_Connection_Request_Command( hci.HCI_Enhanced_Accept_Synchronous_Connection_Request_Command(
+2 -1
View File
@@ -22,6 +22,7 @@ import os
import websockets import websockets
import json import json
from bumble import utils
from bumble.core import AdvertisingData from bumble.core import AdvertisingData
from bumble.device import ( from bumble.device import (
Device, Device,
@@ -169,7 +170,7 @@ async def main() -> None:
mcp.on('track_position', on_track_position) mcp.on('track_position', on_track_position)
await mcp.subscribe_characteristics() await mcp.subscribe_characteristics()
connection.abort_on('disconnection', on_connection_async()) utils.cancel_on_event(connection, 'disconnection', on_connection_async())
device.on('connection', on_connection) device.on('connection', on_connection)
+4 -3
View File
@@ -49,6 +49,7 @@ from bumble.hci import (
HCI_Error, HCI_Error,
HCI_Packet, HCI_Packet,
) )
from bumble import utils
from bumble import gatt from bumble import gatt
from .test_utils import TwoDevices, async_barrier from .test_utils import TwoDevices, async_barrier
@@ -251,7 +252,7 @@ async def test_device_connect_parallel():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_flush(): async def test_flush():
d0 = Device(host=Host(None, None)) d0 = Device(host=Host(None, None))
task = d0.abort_on('flush', asyncio.sleep(10000)) task = utils.cancel_on_event(d0, 'flush', asyncio.sleep(10000))
await d0.host.flush() await d0.host.flush()
try: try:
await task await task
@@ -482,8 +483,8 @@ async def test_cis():
_cig_id: int, _cig_id: int,
_cis_id: int, _cis_id: int,
): ):
acl_connection.abort_on( utils.cancel_on_event(
'disconnection', devices[1].accept_cis_request(cis_handle) acl_connection, 'disconnection', devices[1].accept_cis_request(cis_handle)
) )
peripheral_cis_futures[cis_handle] = asyncio.get_running_loop().create_future() peripheral_cis_futures[cis_handle] = asyncio.get_running_loop().create_future()
+2 -2
View File
@@ -15,14 +15,14 @@
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Imports # Imports
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
import pyee
from bumble import utils
from bumble.device import Device from bumble.device import Device
from bumble.hci import HCI_Reset_Command from bumble.hci import HCI_Reset_Command
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class Scanner(pyee.EventEmitter): class Scanner(utils.EventEmitter):
""" """
Scanner web app Scanner web app