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