Merge pull request #674 from zxzxwu/event

Declare emitted events as constants
This commit is contained in:
zxzxwu
2025-04-26 02:45:50 -07:00
committed by GitHub
22 changed files with 535 additions and 278 deletions

View File

@@ -836,6 +836,9 @@ class Attribute(utils.EventEmitter, Generic[_T]):
READ_REQUIRES_AUTHORIZATION = Permissions.READ_REQUIRES_AUTHORIZATION READ_REQUIRES_AUTHORIZATION = Permissions.READ_REQUIRES_AUTHORIZATION
WRITE_REQUIRES_AUTHORIZATION = Permissions.WRITE_REQUIRES_AUTHORIZATION WRITE_REQUIRES_AUTHORIZATION = Permissions.WRITE_REQUIRES_AUTHORIZATION
EVENT_READ = "read"
EVENT_WRITE = "write"
value: Union[AttributeValue[_T], _T, None] value: Union[AttributeValue[_T], _T, None]
def __init__( def __init__(
@@ -906,7 +909,7 @@ class Attribute(utils.EventEmitter, Generic[_T]):
else: else:
value = self.value value = self.value
self.emit('read', connection, b'' if value is None else value) self.emit(self.EVENT_READ, connection, b'' if value is None else value)
return b'' if value is None else self.encode_value(value) return b'' if value is None else self.encode_value(value)
@@ -947,7 +950,7 @@ class Attribute(utils.EventEmitter, Generic[_T]):
else: else:
self.value = decoded_value self.value = decoded_value
self.emit('write', connection, decoded_value) self.emit(self.EVENT_WRITE, connection, decoded_value)
def __repr__(self): def __repr__(self):
if isinstance(self.value, bytes): if isinstance(self.value, bytes):

View File

@@ -166,8 +166,8 @@ class Protocol:
# Register to receive PDUs from the channel # Register to receive PDUs from the channel
l2cap_channel.sink = self.on_pdu l2cap_channel.sink = self.on_pdu
l2cap_channel.on("open", self.on_l2cap_channel_open) l2cap_channel.on(l2cap_channel.EVENT_OPEN, self.on_l2cap_channel_open)
l2cap_channel.on("close", self.on_l2cap_channel_close) l2cap_channel.on(l2cap_channel.EVENT_CLOSE, self.on_l2cap_channel_close)
def on_l2cap_channel_open(self): def on_l2cap_channel_open(self):
logger.debug(color("<<< AVCTP channel open", "magenta")) logger.debug(color("<<< AVCTP channel open", "magenta"))

View File

@@ -896,7 +896,7 @@ class Set_Configuration_Reject(Message):
self.service_category = self.payload[0] self.service_category = self.payload[0]
self.error_code = self.payload[1] self.error_code = self.payload[1]
def __init__(self, service_category, error_code): def __init__(self, error_code: int, service_category: int = 0) -> None:
super().__init__(payload=bytes([service_category, error_code])) super().__init__(payload=bytes([service_category, error_code]))
self.service_category = service_category self.service_category = service_category
self.error_code = error_code self.error_code = error_code
@@ -1132,6 +1132,14 @@ class Security_Control_Command(Message):
See Bluetooth AVDTP spec - 8.17.1 Security Control Command See Bluetooth AVDTP spec - 8.17.1 Security Control Command
''' '''
def init_from_payload(self):
# pylint: disable=attribute-defined-outside-init
self.acp_seid = self.payload[0] >> 2
self.data = self.payload[1:]
def __str__(self) -> str:
return self.to_string([f'ACP_SEID: {self.acp_seid}', f'data: {self.data}'])
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@Message.subclass @Message.subclass
@@ -1200,6 +1208,9 @@ class Protocol(utils.EventEmitter):
transaction_results: List[Optional[asyncio.Future[Message]]] transaction_results: List[Optional[asyncio.Future[Message]]]
channel_connector: Callable[[], Awaitable[l2cap.ClassicChannel]] channel_connector: Callable[[], Awaitable[l2cap.ClassicChannel]]
EVENT_OPEN = "open"
EVENT_CLOSE = "close"
class PacketType(enum.IntEnum): class PacketType(enum.IntEnum):
SINGLE_PACKET = 0 SINGLE_PACKET = 0
START_PACKET = 1 START_PACKET = 1
@@ -1239,8 +1250,8 @@ class Protocol(utils.EventEmitter):
# Register to receive PDUs from the channel # Register to receive PDUs from the channel
l2cap_channel.sink = self.on_pdu l2cap_channel.sink = self.on_pdu
l2cap_channel.on('open', self.on_l2cap_channel_open) l2cap_channel.on(l2cap_channel.EVENT_OPEN, self.on_l2cap_channel_open)
l2cap_channel.on('close', self.on_l2cap_channel_close) l2cap_channel.on(l2cap_channel.EVENT_CLOSE, self.on_l2cap_channel_close)
def get_local_endpoint_by_seid(self, seid: int) -> Optional[LocalStreamEndPoint]: def get_local_endpoint_by_seid(self, seid: int) -> Optional[LocalStreamEndPoint]:
if 0 < seid <= len(self.local_endpoints): if 0 < seid <= len(self.local_endpoints):
@@ -1410,20 +1421,20 @@ class Protocol(utils.EventEmitter):
self.transaction_results[transaction_label] = None self.transaction_results[transaction_label] = None
self.transaction_semaphore.release() self.transaction_semaphore.release()
def on_l2cap_connection(self, channel): def on_l2cap_connection(self, channel: l2cap.ClassicChannel) -> None:
# Forward the channel to the endpoint that's expecting it # Forward the channel to the endpoint that's expecting it
if self.channel_acceptor is None: if self.channel_acceptor is None:
logger.warning(color('!!! l2cap connection with no acceptor', 'red')) logger.warning(color('!!! l2cap connection with no acceptor', 'red'))
return return
self.channel_acceptor.on_l2cap_connection(channel) self.channel_acceptor.on_l2cap_connection(channel)
def on_l2cap_channel_open(self): def on_l2cap_channel_open(self) -> None:
logger.debug(color('<<< L2CAP channel open', 'magenta')) logger.debug(color('<<< L2CAP channel open', 'magenta'))
self.emit('open') self.emit(self.EVENT_OPEN)
def on_l2cap_channel_close(self): def on_l2cap_channel_close(self) -> None:
logger.debug(color('<<< L2CAP channel close', 'magenta')) logger.debug(color('<<< L2CAP channel close', 'magenta'))
self.emit('close') self.emit(self.EVENT_CLOSE)
def send_message(self, transaction_label: int, message: Message) -> None: def send_message(self, transaction_label: int, message: Message) -> None:
logger.debug( logger.debug(
@@ -1541,28 +1552,34 @@ class Protocol(utils.EventEmitter):
async def abort(self, seid: int) -> Abort_Response: async def abort(self, seid: int) -> Abort_Response:
return await self.send_command(Abort_Command(seid)) return await self.send_command(Abort_Command(seid))
def on_discover_command(self, _command): def on_discover_command(self, command: Discover_Command) -> Optional[Message]:
endpoint_infos = [ endpoint_infos = [
EndPointInfo(endpoint.seid, 0, endpoint.media_type, endpoint.tsep) EndPointInfo(endpoint.seid, 0, endpoint.media_type, endpoint.tsep)
for endpoint in self.local_endpoints for endpoint in self.local_endpoints
] ]
return Discover_Response(endpoint_infos) return Discover_Response(endpoint_infos)
def on_get_capabilities_command(self, command): def on_get_capabilities_command(
self, command: Get_Capabilities_Command
) -> Optional[Message]:
endpoint = self.get_local_endpoint_by_seid(command.acp_seid) endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
if endpoint is None: if endpoint is None:
return Get_Capabilities_Reject(AVDTP_BAD_ACP_SEID_ERROR) return Get_Capabilities_Reject(AVDTP_BAD_ACP_SEID_ERROR)
return Get_Capabilities_Response(endpoint.capabilities) return Get_Capabilities_Response(endpoint.capabilities)
def on_get_all_capabilities_command(self, command): def on_get_all_capabilities_command(
self, command: Get_All_Capabilities_Command
) -> Optional[Message]:
endpoint = self.get_local_endpoint_by_seid(command.acp_seid) endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
if endpoint is None: if endpoint is None:
return Get_All_Capabilities_Reject(AVDTP_BAD_ACP_SEID_ERROR) return Get_All_Capabilities_Reject(AVDTP_BAD_ACP_SEID_ERROR)
return Get_All_Capabilities_Response(endpoint.capabilities) return Get_All_Capabilities_Response(endpoint.capabilities)
def on_set_configuration_command(self, command): def on_set_configuration_command(
self, command: Set_Configuration_Command
) -> Optional[Message]:
endpoint = self.get_local_endpoint_by_seid(command.acp_seid) endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
if endpoint is None: if endpoint is None:
return Set_Configuration_Reject(AVDTP_BAD_ACP_SEID_ERROR) return Set_Configuration_Reject(AVDTP_BAD_ACP_SEID_ERROR)
@@ -1578,7 +1595,9 @@ class Protocol(utils.EventEmitter):
result = stream.on_set_configuration_command(command.capabilities) result = stream.on_set_configuration_command(command.capabilities)
return result or Set_Configuration_Response() return result or Set_Configuration_Response()
def on_get_configuration_command(self, command): def on_get_configuration_command(
self, command: Get_Configuration_Command
) -> Optional[Message]:
endpoint = self.get_local_endpoint_by_seid(command.acp_seid) endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
if endpoint is None: if endpoint is None:
return Get_Configuration_Reject(AVDTP_BAD_ACP_SEID_ERROR) return Get_Configuration_Reject(AVDTP_BAD_ACP_SEID_ERROR)
@@ -1587,7 +1606,7 @@ class Protocol(utils.EventEmitter):
return endpoint.stream.on_get_configuration_command() return endpoint.stream.on_get_configuration_command()
def on_reconfigure_command(self, command): def on_reconfigure_command(self, command: Reconfigure_Command) -> Optional[Message]:
endpoint = self.get_local_endpoint_by_seid(command.acp_seid) endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
if endpoint is None: if endpoint is None:
return Reconfigure_Reject(0, AVDTP_BAD_ACP_SEID_ERROR) return Reconfigure_Reject(0, AVDTP_BAD_ACP_SEID_ERROR)
@@ -1597,7 +1616,7 @@ class Protocol(utils.EventEmitter):
result = endpoint.stream.on_reconfigure_command(command.capabilities) result = endpoint.stream.on_reconfigure_command(command.capabilities)
return result or Reconfigure_Response() return result or Reconfigure_Response()
def on_open_command(self, command): def on_open_command(self, command: Open_Command) -> Optional[Message]:
endpoint = self.get_local_endpoint_by_seid(command.acp_seid) endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
if endpoint is None: if endpoint is None:
return Open_Reject(AVDTP_BAD_ACP_SEID_ERROR) return Open_Reject(AVDTP_BAD_ACP_SEID_ERROR)
@@ -1607,25 +1626,26 @@ class Protocol(utils.EventEmitter):
result = endpoint.stream.on_open_command() result = endpoint.stream.on_open_command()
return result or Open_Response() return result or Open_Response()
def on_start_command(self, command): def on_start_command(self, command: Start_Command) -> Optional[Message]:
for seid in command.acp_seids: for seid in command.acp_seids:
endpoint = self.get_local_endpoint_by_seid(seid) endpoint = self.get_local_endpoint_by_seid(seid)
if endpoint is None: if endpoint is None:
return Start_Reject(seid, AVDTP_BAD_ACP_SEID_ERROR) return Start_Reject(seid, AVDTP_BAD_ACP_SEID_ERROR)
if endpoint.stream is None: if endpoint.stream is None:
return Start_Reject(AVDTP_BAD_STATE_ERROR) return Start_Reject(seid, AVDTP_BAD_STATE_ERROR)
# Start all streams # Start all streams
# TODO: deal with partial failures # TODO: deal with partial failures
for seid in command.acp_seids: for seid in command.acp_seids:
endpoint = self.get_local_endpoint_by_seid(seid) endpoint = self.get_local_endpoint_by_seid(seid)
result = endpoint.stream.on_start_command() if not endpoint or not endpoint.stream:
if result is not None: raise InvalidStateError("Should already be checked!")
if (result := endpoint.stream.on_start_command()) is not None:
return result return result
return Start_Response() return Start_Response()
def on_suspend_command(self, command): def on_suspend_command(self, command: Suspend_Command) -> Optional[Message]:
for seid in command.acp_seids: for seid in command.acp_seids:
endpoint = self.get_local_endpoint_by_seid(seid) endpoint = self.get_local_endpoint_by_seid(seid)
if endpoint is None: if endpoint is None:
@@ -1637,13 +1657,14 @@ class Protocol(utils.EventEmitter):
# TODO: deal with partial failures # TODO: deal with partial failures
for seid in command.acp_seids: for seid in command.acp_seids:
endpoint = self.get_local_endpoint_by_seid(seid) endpoint = self.get_local_endpoint_by_seid(seid)
result = endpoint.stream.on_suspend_command() if not endpoint or not endpoint.stream:
if result is not None: raise InvalidStateError("Should already be checked!")
if (result := endpoint.stream.on_suspend_command()) is not None:
return result return result
return Suspend_Response() return Suspend_Response()
def on_close_command(self, command): def on_close_command(self, command: Close_Command) -> Optional[Message]:
endpoint = self.get_local_endpoint_by_seid(command.acp_seid) endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
if endpoint is None: if endpoint is None:
return Close_Reject(AVDTP_BAD_ACP_SEID_ERROR) return Close_Reject(AVDTP_BAD_ACP_SEID_ERROR)
@@ -1653,7 +1674,7 @@ class Protocol(utils.EventEmitter):
result = endpoint.stream.on_close_command() result = endpoint.stream.on_close_command()
return result or Close_Response() return result or Close_Response()
def on_abort_command(self, command): def on_abort_command(self, command: Abort_Command) -> Optional[Message]:
endpoint = self.get_local_endpoint_by_seid(command.acp_seid) endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
if endpoint is None or endpoint.stream is None: if endpoint is None or endpoint.stream is None:
return Abort_Response() return Abort_Response()
@@ -1661,15 +1682,17 @@ class Protocol(utils.EventEmitter):
endpoint.stream.on_abort_command() endpoint.stream.on_abort_command()
return Abort_Response() return Abort_Response()
def on_security_control_command(self, command): def on_security_control_command(
self, command: Security_Control_Command
) -> Optional[Message]:
endpoint = self.get_local_endpoint_by_seid(command.acp_seid) endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
if endpoint is None: if endpoint is None:
return Security_Control_Reject(AVDTP_BAD_ACP_SEID_ERROR) return Security_Control_Reject(AVDTP_BAD_ACP_SEID_ERROR)
result = endpoint.on_security_control_command(command.payload) result = endpoint.on_security_control_command(command.data)
return result or Security_Control_Response() return result or Security_Control_Response()
def on_delayreport_command(self, command): def on_delayreport_command(self, command: DelayReport_Command) -> Optional[Message]:
endpoint = self.get_local_endpoint_by_seid(command.acp_seid) endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
if endpoint is None: if endpoint is None:
return DelayReport_Reject(AVDTP_BAD_ACP_SEID_ERROR) return DelayReport_Reject(AVDTP_BAD_ACP_SEID_ERROR)
@@ -1682,6 +1705,8 @@ class Protocol(utils.EventEmitter):
class Listener(utils.EventEmitter): class Listener(utils.EventEmitter):
servers: Dict[int, Protocol] servers: Dict[int, Protocol]
EVENT_CONNECTION = "connection"
@staticmethod @staticmethod
def create_registrar(device: device.Device): def create_registrar(device: device.Device):
warnings.warn("Please use Listener.for_device()", DeprecationWarning) warnings.warn("Please use Listener.for_device()", DeprecationWarning)
@@ -1716,7 +1741,7 @@ class Listener(utils.EventEmitter):
l2cap_server = device.create_l2cap_server( l2cap_server = device.create_l2cap_server(
spec=l2cap.ClassicChannelSpec(psm=AVDTP_PSM) spec=l2cap.ClassicChannelSpec(psm=AVDTP_PSM)
) )
l2cap_server.on('connection', listener.on_l2cap_connection) l2cap_server.on(l2cap_server.EVENT_CONNECTION, listener.on_l2cap_connection)
return listener return listener
def on_l2cap_connection(self, channel: l2cap.ClassicChannel) -> None: def on_l2cap_connection(self, channel: l2cap.ClassicChannel) -> None:
@@ -1732,14 +1757,14 @@ class Listener(utils.EventEmitter):
logger.debug('setting up new Protocol for the connection') logger.debug('setting up new Protocol for the connection')
server = Protocol(channel, self.version) server = Protocol(channel, self.version)
self.set_server(channel.connection, server) self.set_server(channel.connection, server)
self.emit('connection', server) self.emit(self.EVENT_CONNECTION, server)
def on_channel_close(): def on_channel_close():
logger.debug('removing Protocol for the connection') logger.debug('removing Protocol for the connection')
self.remove_server(channel.connection) self.remove_server(channel.connection)
channel.on('open', on_channel_open) channel.on(channel.EVENT_OPEN, on_channel_open)
channel.on('close', on_channel_close) channel.on(channel.EVENT_CLOSE, on_channel_close)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -1788,6 +1813,7 @@ class Stream:
) )
async def start(self) -> None: async def start(self) -> None:
"""[Source] Start streaming."""
# Auto-open if needed # Auto-open if needed
if self.state == AVDTP_CONFIGURED_STATE: if self.state == AVDTP_CONFIGURED_STATE:
await self.open() await self.open()
@@ -1804,6 +1830,7 @@ class Stream:
self.change_state(AVDTP_STREAMING_STATE) self.change_state(AVDTP_STREAMING_STATE)
async def stop(self) -> None: async def stop(self) -> None:
"""[Source] Stop streaming and transit to OPEN state."""
if self.state != AVDTP_STREAMING_STATE: if self.state != AVDTP_STREAMING_STATE:
raise InvalidStateError('current state is not STREAMING') raise InvalidStateError('current state is not STREAMING')
@@ -1816,6 +1843,7 @@ class Stream:
self.change_state(AVDTP_OPEN_STATE) self.change_state(AVDTP_OPEN_STATE)
async def close(self) -> None: async def close(self) -> None:
"""[Source] Close channel and transit to IDLE state."""
if self.state not in (AVDTP_OPEN_STATE, AVDTP_STREAMING_STATE): if self.state not in (AVDTP_OPEN_STATE, AVDTP_STREAMING_STATE):
raise InvalidStateError('current state is not OPEN or STREAMING') raise InvalidStateError('current state is not OPEN or STREAMING')
@@ -1847,7 +1875,7 @@ class Stream:
self.change_state(AVDTP_CONFIGURED_STATE) self.change_state(AVDTP_CONFIGURED_STATE)
return None return None
def on_get_configuration_command(self, configuration): def on_get_configuration_command(self):
if self.state not in ( if self.state not in (
AVDTP_CONFIGURED_STATE, AVDTP_CONFIGURED_STATE,
AVDTP_OPEN_STATE, AVDTP_OPEN_STATE,
@@ -1855,7 +1883,7 @@ class Stream:
): ):
return Get_Configuration_Reject(AVDTP_BAD_STATE_ERROR) return Get_Configuration_Reject(AVDTP_BAD_STATE_ERROR)
return self.local_endpoint.on_get_configuration_command(configuration) return self.local_endpoint.on_get_configuration_command()
def on_reconfigure_command(self, configuration): def on_reconfigure_command(self, configuration):
if self.state != AVDTP_OPEN_STATE: if self.state != AVDTP_OPEN_STATE:
@@ -1935,20 +1963,20 @@ class Stream:
# Wait for the RTP channel to be closed # Wait for the RTP channel to be closed
self.change_state(AVDTP_ABORTING_STATE) self.change_state(AVDTP_ABORTING_STATE)
def on_l2cap_connection(self, channel): def on_l2cap_connection(self, channel: l2cap.ClassicChannel) -> None:
logger.debug(color('<<< stream channel connected', 'magenta')) logger.debug(color('<<< stream channel connected', 'magenta'))
self.rtp_channel = channel self.rtp_channel = channel
channel.on('open', self.on_l2cap_channel_open) channel.on(channel.EVENT_OPEN, self.on_l2cap_channel_open)
channel.on('close', self.on_l2cap_channel_close) channel.on(channel.EVENT_CLOSE, self.on_l2cap_channel_close)
# We don't need more channels # We don't need more channels
self.protocol.channel_acceptor = None self.protocol.channel_acceptor = None
def on_l2cap_channel_open(self): def on_l2cap_channel_open(self) -> None:
logger.debug(color('<<< stream channel open', 'magenta')) logger.debug(color('<<< stream channel open', 'magenta'))
self.local_endpoint.on_rtp_channel_open() self.local_endpoint.on_rtp_channel_open()
def on_l2cap_channel_close(self): def on_l2cap_channel_close(self) -> None:
logger.debug(color('<<< stream channel closed', 'magenta')) logger.debug(color('<<< stream channel closed', 'magenta'))
self.local_endpoint.on_rtp_channel_close() self.local_endpoint.on_rtp_channel_close()
self.local_endpoint.in_use = 0 self.local_endpoint.in_use = 0
@@ -2065,6 +2093,19 @@ class DiscoveredStreamEndPoint(StreamEndPoint, StreamEndPointProxy):
class LocalStreamEndPoint(StreamEndPoint, utils.EventEmitter): class LocalStreamEndPoint(StreamEndPoint, utils.EventEmitter):
stream: Optional[Stream] stream: Optional[Stream]
EVENT_CONFIGURATION = "configuration"
EVENT_OPEN = "open"
EVENT_START = "start"
EVENT_STOP = "stop"
EVENT_RTP_PACKET = "rtp_packet"
EVENT_SUSPEND = "suspend"
EVENT_CLOSE = "close"
EVENT_ABORT = "abort"
EVENT_DELAY_REPORT = "delay_report"
EVENT_SECURITY_CONTROL = "security_control"
EVENT_RTP_CHANNEL_OPEN = "rtp_channel_open"
EVENT_RTP_CHANNEL_CLOSE = "rtp_channel_close"
def __init__( def __init__(
self, self,
protocol: Protocol, protocol: Protocol,
@@ -2080,52 +2121,65 @@ class LocalStreamEndPoint(StreamEndPoint, utils.EventEmitter):
self.configuration = configuration if configuration is not None else [] self.configuration = configuration if configuration is not None else []
self.stream = None self.stream = None
async def start(self): async def start(self) -> None:
pass """[Source Only] Handles when receiving start command."""
async def stop(self): async def stop(self) -> None:
pass """[Source Only] Handles when receiving stop command."""
async def close(self): async def close(self) -> None:
pass """[Source Only] Handles when receiving close command."""
def on_reconfigure_command(self, command): def on_reconfigure_command(self, command) -> Optional[Message]:
pass return None
def on_set_configuration_command(self, configuration): def on_set_configuration_command(self, configuration) -> Optional[Message]:
logger.debug( logger.debug(
'<<< received configuration: ' '<<< received configuration: '
f'{",".join([str(capability) for capability in configuration])}' f'{",".join([str(capability) for capability in configuration])}'
) )
self.configuration = configuration self.configuration = configuration
self.emit('configuration') self.emit(self.EVENT_CONFIGURATION)
return None
def on_get_configuration_command(self): def on_get_configuration_command(self) -> Optional[Message]:
return Get_Configuration_Response(self.configuration) return Get_Configuration_Response(self.configuration)
def on_open_command(self): def on_open_command(self) -> Optional[Message]:
self.emit('open') self.emit(self.EVENT_OPEN)
return None
def on_start_command(self): def on_start_command(self) -> Optional[Message]:
self.emit('start') self.emit(self.EVENT_START)
return None
def on_suspend_command(self): def on_suspend_command(self) -> Optional[Message]:
self.emit('suspend') self.emit(self.EVENT_SUSPEND)
return None
def on_close_command(self): def on_close_command(self) -> Optional[Message]:
self.emit('close') self.emit(self.EVENT_CLOSE)
return None
def on_abort_command(self): def on_abort_command(self) -> Optional[Message]:
self.emit('abort') self.emit(self.EVENT_ABORT)
return None
def on_delayreport_command(self, delay: int): def on_delayreport_command(self, delay: int) -> Optional[Message]:
self.emit('delay_report', delay) self.emit(self.EVENT_DELAY_REPORT, delay)
return None
def on_rtp_channel_open(self): def on_security_control_command(self, data: bytes) -> Optional[Message]:
self.emit('rtp_channel_open') self.emit(self.EVENT_SECURITY_CONTROL, data)
return None
def on_rtp_channel_close(self): def on_rtp_channel_open(self) -> None:
self.emit('rtp_channel_close') self.emit(self.EVENT_RTP_CHANNEL_OPEN)
return None
def on_rtp_channel_close(self) -> None:
self.emit(self.EVENT_RTP_CHANNEL_CLOSE)
return None
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -2156,13 +2210,13 @@ class LocalSource(LocalStreamEndPoint):
if self.packet_pump and self.stream and self.stream.rtp_channel: if self.packet_pump and self.stream and self.stream.rtp_channel:
return await self.packet_pump.start(self.stream.rtp_channel) return await self.packet_pump.start(self.stream.rtp_channel)
self.emit('start') self.emit(self.EVENT_START)
async def stop(self) -> None: async def stop(self) -> None:
if self.packet_pump: if self.packet_pump:
return await self.packet_pump.stop() return await self.packet_pump.stop()
self.emit('stop') self.emit(self.EVENT_STOP)
def on_start_command(self): def on_start_command(self):
asyncio.create_task(self.start()) asyncio.create_task(self.start())
@@ -2203,4 +2257,4 @@ class LocalSink(LocalStreamEndPoint):
f'{color("<<< RTP Packet:", "green")} ' f'{color("<<< RTP Packet:", "green")} '
f'{rtp_packet} {rtp_packet.payload[:16].hex()}' f'{rtp_packet} {rtp_packet.payload[:16].hex()}'
) )
self.emit('rtp_packet', rtp_packet) self.emit(self.EVENT_RTP_PACKET, rtp_packet)

View File

@@ -996,6 +996,10 @@ class Delegate:
class Protocol(utils.EventEmitter): class Protocol(utils.EventEmitter):
"""AVRCP Controller and Target protocol.""" """AVRCP Controller and Target protocol."""
EVENT_CONNECTION = "connection"
EVENT_START = "start"
EVENT_STOP = "stop"
class PacketType(enum.IntEnum): class PacketType(enum.IntEnum):
SINGLE = 0b00 SINGLE = 0b00
START = 0b01 START = 0b01
@@ -1456,9 +1460,11 @@ class Protocol(utils.EventEmitter):
def _on_avctp_connection(self, l2cap_channel: l2cap.ClassicChannel) -> None: def _on_avctp_connection(self, l2cap_channel: l2cap.ClassicChannel) -> None:
logger.debug("AVCTP connection established") logger.debug("AVCTP connection established")
l2cap_channel.on("open", lambda: self._on_avctp_channel_open(l2cap_channel)) l2cap_channel.on(
l2cap_channel.EVENT_OPEN, lambda: self._on_avctp_channel_open(l2cap_channel)
)
self.emit("connection") self.emit(self.EVENT_CONNECTION)
def _on_avctp_channel_open(self, l2cap_channel: l2cap.ClassicChannel) -> None: def _on_avctp_channel_open(self, l2cap_channel: l2cap.ClassicChannel) -> None:
logger.debug("AVCTP channel open") logger.debug("AVCTP channel open")
@@ -1473,15 +1479,15 @@ class Protocol(utils.EventEmitter):
self.avctp_protocol.register_response_handler( self.avctp_protocol.register_response_handler(
AVRCP_PID, self._on_avctp_response AVRCP_PID, self._on_avctp_response
) )
l2cap_channel.on("close", self._on_avctp_channel_close) l2cap_channel.on(l2cap_channel.EVENT_CLOSE, self._on_avctp_channel_close)
self.emit("start") self.emit(self.EVENT_START)
def _on_avctp_channel_close(self) -> None: def _on_avctp_channel_close(self) -> None:
logger.debug("AVCTP channel closed") logger.debug("AVCTP channel closed")
self.avctp_protocol = None self.avctp_protocol = None
self.emit("stop") self.emit(self.EVENT_STOP)
def _on_avctp_command( def _on_avctp_command(
self, transaction_label: int, command: avc.CommandFrame self, transaction_label: int, command: avc.CommandFrame

View File

@@ -581,6 +581,12 @@ class AdvertisingSet(utils.EventEmitter):
enabled: bool = False enabled: bool = False
periodic_enabled: bool = False periodic_enabled: bool = False
EVENT_START = "start"
EVENT_STOP = "stop"
EVENT_START_PERIODIC = "start_periodic"
EVENT_STOP_PERIODIC = "stop_periodic"
EVENT_TERMINATION = "termination"
def __post_init__(self) -> None: def __post_init__(self) -> None:
super().__init__() super().__init__()
@@ -731,7 +737,7 @@ class AdvertisingSet(utils.EventEmitter):
) )
self.enabled = True self.enabled = True
self.emit('start') self.emit(self.EVENT_START)
async def stop(self) -> None: async def stop(self) -> None:
await self.device.send_command( await self.device.send_command(
@@ -745,7 +751,7 @@ class AdvertisingSet(utils.EventEmitter):
) )
self.enabled = False self.enabled = False
self.emit('stop') self.emit(self.EVENT_STOP)
async def start_periodic(self, include_adi: bool = False) -> None: async def start_periodic(self, include_adi: bool = False) -> None:
if self.periodic_enabled: if self.periodic_enabled:
@@ -759,7 +765,7 @@ class AdvertisingSet(utils.EventEmitter):
) )
self.periodic_enabled = True self.periodic_enabled = True
self.emit('start_periodic') self.emit(self.EVENT_START_PERIODIC)
async def stop_periodic(self) -> None: async def stop_periodic(self) -> None:
if not self.periodic_enabled: if not self.periodic_enabled:
@@ -773,7 +779,7 @@ class AdvertisingSet(utils.EventEmitter):
) )
self.periodic_enabled = False self.periodic_enabled = False
self.emit('stop_periodic') self.emit(self.EVENT_STOP_PERIODIC)
async def remove(self) -> None: async def remove(self) -> None:
await self.device.send_command( await self.device.send_command(
@@ -797,7 +803,7 @@ class AdvertisingSet(utils.EventEmitter):
def on_termination(self, status: int) -> None: def on_termination(self, status: int) -> None:
self.enabled = False self.enabled = False
self.emit('termination', status) self.emit(self.EVENT_TERMINATION, status)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -823,6 +829,14 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
periodic_advertising_interval: int periodic_advertising_interval: int
advertiser_clock_accuracy: int advertiser_clock_accuracy: int
EVENT_STATE_CHANGE = "state_change"
EVENT_ESTABLISHMENT = "establishment"
EVENT_CANCELLATION = "cancellation"
EVENT_ERROR = "error"
EVENT_LOSS = "loss"
EVENT_PERIODIC_ADVERTISEMENT = "periodic_advertisement"
EVENT_BIGINFO_ADVERTISEMENT = "biginfo_advertisement"
def __init__( def __init__(
self, self,
device: Device, device: Device,
@@ -855,7 +869,7 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
def state(self, state: State) -> None: def state(self, state: State) -> None:
logger.debug(f'{self} -> {state.name}') logger.debug(f'{self} -> {state.name}')
self._state = state self._state = state
self.emit('state_change') self.emit(self.EVENT_STATE_CHANGE)
async def establish(self) -> None: async def establish(self) -> None:
if self.state != self.State.INIT: if self.state != self.State.INIT:
@@ -939,7 +953,7 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
self.periodic_advertising_interval = periodic_advertising_interval self.periodic_advertising_interval = periodic_advertising_interval
self.advertiser_clock_accuracy = advertiser_clock_accuracy self.advertiser_clock_accuracy = advertiser_clock_accuracy
self.state = self.State.ESTABLISHED self.state = self.State.ESTABLISHED
self.emit('establishment') self.emit(self.EVENT_ESTABLISHMENT)
return return
# We don't need to keep a reference anymore # We don't need to keep a reference anymore
@@ -948,15 +962,15 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
if status == hci.HCI_OPERATION_CANCELLED_BY_HOST_ERROR: if status == hci.HCI_OPERATION_CANCELLED_BY_HOST_ERROR:
self.state = self.State.CANCELLED self.state = self.State.CANCELLED
self.emit('cancellation') self.emit(self.EVENT_CANCELLATION)
return return
self.state = self.State.ERROR self.state = self.State.ERROR
self.emit('error') self.emit(self.EVENT_ERROR)
def on_loss(self): def on_loss(self):
self.state = self.State.LOST self.state = self.State.LOST
self.emit('loss') self.emit(self.EVENT_LOSS)
def on_periodic_advertising_report(self, report) -> None: def on_periodic_advertising_report(self, report) -> None:
self.data_accumulator += report.data self.data_accumulator += report.data
@@ -967,7 +981,7 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
return return
self.emit( self.emit(
'periodic_advertisement', self.EVENT_PERIODIC_ADVERTISEMENT,
PeriodicAdvertisement( PeriodicAdvertisement(
self.advertiser_address, self.advertiser_address,
self.sid, self.sid,
@@ -984,7 +998,7 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
def on_biginfo_advertising_report(self, report) -> None: def on_biginfo_advertising_report(self, report) -> None:
self.emit( self.emit(
'biginfo_advertisement', self.EVENT_BIGINFO_ADVERTISEMENT,
BIGInfoAdvertisement.from_report(self.advertiser_address, self.sid, report), BIGInfoAdvertisement.from_report(self.advertiser_address, self.sid, report),
) )
@@ -1222,7 +1236,7 @@ class Peer:
async def request_mtu(self, mtu: int) -> int: async def request_mtu(self, mtu: int) -> int:
mtu = await self.gatt_client.request_mtu(mtu) mtu = await self.gatt_client.request_mtu(mtu)
self.connection.emit('connection_att_mtu_update') self.connection.emit(self.connection.EVENT_CONNECTION_ATT_MTU_UPDATE)
return mtu return mtu
async def discover_service( async def discover_service(
@@ -1390,6 +1404,9 @@ class ScoLink(utils.CompositeEventEmitter):
link_type: int link_type: int
sink: Optional[Callable[[hci.HCI_SynchronousDataPacket], Any]] = None sink: Optional[Callable[[hci.HCI_SynchronousDataPacket], Any]] = None
EVENT_DISCONNECTION: ClassVar[str] = "disconnection"
EVENT_DISCONNECTION_FAILURE: ClassVar[str] = "disconnection_failure"
def __post_init__(self) -> None: def __post_init__(self) -> None:
super().__init__() super().__init__()
@@ -1487,6 +1504,11 @@ class CisLink(utils.EventEmitter, _IsoLink):
state: State = State.PENDING state: State = State.PENDING
sink: Callable[[hci.HCI_IsoDataPacket], Any] | None = None sink: Callable[[hci.HCI_IsoDataPacket], Any] | None = None
EVENT_DISCONNECTION: ClassVar[str] = "disconnection"
EVENT_DISCONNECTION_FAILURE: ClassVar[str] = "disconnection_failure"
EVENT_ESTABLISHMENT: ClassVar[str] = "establishment"
EVENT_ESTABLISHMENT_FAILURE: ClassVar[str] = "establishment_failure"
def __post_init__(self) -> None: def __post_init__(self) -> None:
super().__init__() super().__init__()
@@ -1571,6 +1593,40 @@ class Connection(utils.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
EVENT_CONNECTION_ATT_MTU_UPDATE = "connection_att_mtu_update"
EVENT_DISCONNECTION = "disconnection"
EVENT_DISCONNECTION_FAILURE = "disconnection_failure"
EVENT_CONNECTION_AUTHENTICATION = "connection_authentication"
EVENT_CONNECTION_AUTHENTICATION_FAILURE = "connection_authentication_failure"
EVENT_REMOTE_NAME = "remote_name"
EVENT_REMOTE_NAME_FAILURE = "remote_name_failure"
EVENT_CONNECTION_ENCRYPTION_CHANGE = "connection_encryption_change"
EVENT_CONNECTION_ENCRYPTION_FAILURE = "connection_encryption_failure"
EVENT_CONNECTION_ENCRYPTION_KEY_REFRESH = "connection_encryption_key_refresh"
EVENT_CONNECTION_PARAMETERS_UPDATE = "connection_parameters_update"
EVENT_CONNECTION_PARAMETERS_UPDATE_FAILURE = "connection_parameters_update_failure"
EVENT_CONNECTION_PHY_UPDATE = "connection_phy_update"
EVENT_CONNECTION_PHY_UPDATE_FAILURE = "connection_phy_update_failure"
EVENT_CONNECTION_ATT_MTU_UPDATE = "connection_att_mtu_update"
EVENT_CONNECTION_DATA_LENGTH_CHANGE = "connection_data_length_change"
EVENT_CHANNEL_SOUNDING_CAPABILITIES_FAILURE = (
"channel_sounding_capabilities_failure"
)
EVENT_CHANNEL_SOUNDING_CAPABILITIES = "channel_sounding_capabilities"
EVENT_CHANNEL_SOUNDING_CONFIG_FAILURE = "channel_sounding_config_failure"
EVENT_CHANNEL_SOUNDING_CONFIG = "channel_sounding_config"
EVENT_CHANNEL_SOUNDING_CONFIG_REMOVED = "channel_sounding_config_removed"
EVENT_CHANNEL_SOUNDING_PROCEDURE_FAILURE = "channel_sounding_procedure_failure"
EVENT_CHANNEL_SOUNDING_PROCEDURE = "channel_sounding_procedure"
EVENT_ROLE_CHANGE = "role_change"
EVENT_ROLE_CHANGE_FAILURE = "role_change_failure"
EVENT_CLASSIC_PAIRING = "classic_pairing"
EVENT_CLASSIC_PAIRING_FAILURE = "classic_pairing_failure"
EVENT_PAIRING_START = "pairing_start"
EVENT_PAIRING = "pairing"
EVENT_PAIRING_FAILURE = "pairing_failure"
EVENT_SECURITY_REQUEST = "security_request"
@utils.composite_listener @utils.composite_listener
class Listener: class Listener:
def on_disconnection(self, reason): def on_disconnection(self, reason):
@@ -1740,16 +1796,16 @@ class Connection(utils.CompositeEventEmitter):
"""Idles the current task waiting for a disconnect or timeout""" """Idles the current task waiting for a disconnect or timeout"""
abort = asyncio.get_running_loop().create_future() abort = asyncio.get_running_loop().create_future()
self.on('disconnection', abort.set_result) self.on(self.EVENT_DISCONNECTION, abort.set_result)
self.on('disconnection_failure', abort.set_exception) self.on(self.EVENT_DISCONNECTION_FAILURE, abort.set_exception)
try: try:
await asyncio.wait_for( await asyncio.wait_for(
utils.cancel_on_event(self.device, 'flush', abort), timeout utils.cancel_on_event(self.device, 'flush', abort), timeout
) )
finally: finally:
self.remove_listener('disconnection', abort.set_result) self.remove_listener(self.EVENT_DISCONNECTION, abort.set_result)
self.remove_listener('disconnection_failure', abort.set_exception) self.remove_listener(self.EVENT_DISCONNECTION_FAILURE, abort.set_exception)
async def set_data_length(self, tx_octets, tx_time) -> None: async def set_data_length(self, tx_octets, tx_time) -> None:
return await self.device.set_data_length(self, tx_octets, tx_time) return await self.device.set_data_length(self, tx_octets, tx_time)
@@ -2062,6 +2118,26 @@ class Device(utils.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
EVENT_ADVERTISEMENT = "advertisement"
EVENT_PERIODIC_ADVERTISING_SYNC_TRANSFER = "periodic_advertising_sync_transfer"
EVENT_KEY_STORE_UPDATE = "key_store_update"
EVENT_FLUSH = "flush"
EVENT_CONNECTION = "connection"
EVENT_CONNECTION_FAILURE = "connection_failure"
EVENT_SCO_REQUEST = "sco_request"
EVENT_INQUIRY_COMPLETE = "inquiry_complete"
EVENT_REMOTE_NAME = "remote_name"
EVENT_REMOTE_NAME_FAILURE = "remote_name_failure"
EVENT_SCO_CONNECTION = "sco_connection"
EVENT_SCO_CONNECTION_FAILURE = "sco_connection_failure"
EVENT_CIS_REQUEST = "cis_request"
EVENT_CIS_ESTABLISHMENT = "cis_establishment"
EVENT_CIS_ESTABLISHMENT_FAILURE = "cis_establishment_failure"
EVENT_ROLE_CHANGE_FAILURE = "role_change_failure"
EVENT_INQUIRY_RESULT = "inquiry_result"
EVENT_REMOTE_NAME = "remote_name"
EVENT_REMOTE_NAME_FAILURE = "remote_name_failure"
@utils.composite_listener @utils.composite_listener
class Listener: class Listener:
def on_advertisement(self, advertisement): def on_advertisement(self, advertisement):
@@ -3149,7 +3225,7 @@ class Device(utils.CompositeEventEmitter):
accumulator = AdvertisementDataAccumulator(passive=self.scanning_is_passive) accumulator = AdvertisementDataAccumulator(passive=self.scanning_is_passive)
self.advertisement_accumulators[report.address] = accumulator self.advertisement_accumulators[report.address] = accumulator
if advertisement := accumulator.update(report): if advertisement := accumulator.update(report):
self.emit('advertisement', advertisement) self.emit(self.EVENT_ADVERTISEMENT, advertisement)
async def create_periodic_advertising_sync( async def create_periodic_advertising_sync(
self, self,
@@ -3273,7 +3349,7 @@ class Device(utils.CompositeEventEmitter):
periodic_advertising_interval=periodic_advertising_interval, periodic_advertising_interval=periodic_advertising_interval,
advertiser_clock_accuracy=advertiser_clock_accuracy, advertiser_clock_accuracy=advertiser_clock_accuracy,
) )
self.emit('periodic_advertising_sync_transfer', pa_sync, connection) self.emit(self.EVENT_PERIODIC_ADVERTISING_SYNC_TRANSFER, pa_sync, connection)
@host_event_handler @host_event_handler
@with_periodic_advertising_sync_from_handle @with_periodic_advertising_sync_from_handle
@@ -3331,7 +3407,7 @@ class Device(utils.CompositeEventEmitter):
@host_event_handler @host_event_handler
def on_inquiry_result(self, address, class_of_device, data, rssi): def on_inquiry_result(self, address, class_of_device, data, rssi):
self.emit( self.emit(
'inquiry_result', self.EVENT_INQUIRY_RESULT,
address, address,
class_of_device, class_of_device,
AdvertisingData.from_bytes(data), AdvertisingData.from_bytes(data),
@@ -3508,8 +3584,8 @@ class Device(utils.CompositeEventEmitter):
# Create a future so that we can wait for the connection's result # Create a future so that we can wait for the connection's result
pending_connection = asyncio.get_running_loop().create_future() pending_connection = asyncio.get_running_loop().create_future()
self.on('connection', on_connection) self.on(self.EVENT_CONNECTION, on_connection)
self.on('connection_failure', on_connection_failure) self.on(self.EVENT_CONNECTION_FAILURE, on_connection_failure)
try: try:
# Tell the controller to connect # Tell the controller to connect
@@ -3685,8 +3761,8 @@ class Device(utils.CompositeEventEmitter):
except core.ConnectionError as error: except core.ConnectionError as error:
raise core.TimeoutError() from error raise core.TimeoutError() from error
finally: finally:
self.remove_listener('connection', on_connection) self.remove_listener(self.EVENT_CONNECTION, on_connection)
self.remove_listener('connection_failure', on_connection_failure) self.remove_listener(self.EVENT_CONNECTION_FAILURE, on_connection_failure)
if transport == PhysicalTransport.LE: if transport == PhysicalTransport.LE:
self.le_connecting = False self.le_connecting = False
self.connect_own_address_type = None self.connect_own_address_type = None
@@ -3779,8 +3855,8 @@ class Device(utils.CompositeEventEmitter):
): ):
pending_connection.set_exception(error) pending_connection.set_exception(error)
self.on('connection', on_connection) self.on(self.EVENT_CONNECTION, on_connection)
self.on('connection_failure', on_connection_failure) self.on(self.EVENT_CONNECTION_FAILURE, on_connection_failure)
# Save pending connection, with the Peripheral hci.role. # Save pending connection, with the Peripheral hci.role.
# Even if we requested a role switch in the hci.HCI_Accept_Connection_Request # Even if we requested a role switch in the hci.HCI_Accept_Connection_Request
@@ -3802,8 +3878,8 @@ class Device(utils.CompositeEventEmitter):
return await utils.cancel_on_event(self, 'flush', pending_connection) return await utils.cancel_on_event(self, 'flush', pending_connection)
finally: finally:
self.remove_listener('connection', on_connection) self.remove_listener(self.EVENT_CONNECTION, on_connection)
self.remove_listener('connection_failure', on_connection_failure) self.remove_listener(self.EVENT_CONNECTION_FAILURE, on_connection_failure)
self.pending_connections.pop(peer_address, None) self.pending_connections.pop(peer_address, None)
@asynccontextmanager @asynccontextmanager
@@ -3857,8 +3933,10 @@ class Device(utils.CompositeEventEmitter):
) -> None: ) -> None:
# Create a future so that we can wait for the disconnection's result # Create a future so that we can wait for the disconnection's result
pending_disconnection = asyncio.get_running_loop().create_future() pending_disconnection = asyncio.get_running_loop().create_future()
connection.on('disconnection', pending_disconnection.set_result) connection.on(connection.EVENT_DISCONNECTION, pending_disconnection.set_result)
connection.on('disconnection_failure', pending_disconnection.set_exception) connection.on(
connection.EVENT_DISCONNECTION_FAILURE, pending_disconnection.set_exception
)
# Request a disconnection # Request a disconnection
result = await self.send_command( result = await self.send_command(
@@ -3876,10 +3954,11 @@ class Device(utils.CompositeEventEmitter):
return await utils.cancel_on_event(self, '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 connection.EVENT_DISCONNECTION, pending_disconnection.set_result
) )
connection.remove_listener( connection.remove_listener(
'disconnection_failure', pending_disconnection.set_exception connection.EVENT_DISCONNECTION_FAILURE,
pending_disconnection.set_exception,
) )
self.disconnecting = False self.disconnecting = False
@@ -4201,7 +4280,7 @@ class Device(utils.CompositeEventEmitter):
return keys.link_key.value return keys.link_key.value
# [Classic only] # [Classic only]
async def authenticate(self, connection): async def authenticate(self, connection: Connection) -> None:
# Set up event handlers # Set up event handlers
pending_authentication = asyncio.get_running_loop().create_future() pending_authentication = asyncio.get_running_loop().create_future()
@@ -4211,8 +4290,11 @@ class Device(utils.CompositeEventEmitter):
def on_authentication_failure(error_code): def on_authentication_failure(error_code):
pending_authentication.set_exception(hci.HCI_Error(error_code)) pending_authentication.set_exception(hci.HCI_Error(error_code))
connection.on('connection_authentication', on_authentication) connection.on(connection.EVENT_CONNECTION_AUTHENTICATION, on_authentication)
connection.on('connection_authentication_failure', on_authentication_failure) connection.on(
connection.EVENT_CONNECTION_AUTHENTICATION_FAILURE,
on_authentication_failure,
)
# Request the authentication # Request the authentication
try: try:
@@ -4233,9 +4315,12 @@ class Device(utils.CompositeEventEmitter):
connection, 'disconnection', pending_authentication connection, 'disconnection', pending_authentication
) )
finally: finally:
connection.remove_listener('connection_authentication', on_authentication)
connection.remove_listener( connection.remove_listener(
'connection_authentication_failure', on_authentication_failure connection.EVENT_CONNECTION_AUTHENTICATION, on_authentication
)
connection.remove_listener(
connection.EVENT_CONNECTION_AUTHENTICATION_FAILURE,
on_authentication_failure,
) )
async def encrypt(self, connection, enable=True): async def encrypt(self, connection, enable=True):
@@ -4251,8 +4336,12 @@ class Device(utils.CompositeEventEmitter):
def on_encryption_failure(error_code): def on_encryption_failure(error_code):
pending_encryption.set_exception(hci.HCI_Error(error_code)) pending_encryption.set_exception(hci.HCI_Error(error_code))
connection.on('connection_encryption_change', on_encryption_change) connection.on(
connection.on('connection_encryption_failure', on_encryption_failure) connection.EVENT_CONNECTION_ENCRYPTION_CHANGE, on_encryption_change
)
connection.on(
connection.EVENT_CONNECTION_ENCRYPTION_FAILURE, on_encryption_failure
)
# Request the encryption # Request the encryption
try: try:
@@ -4314,10 +4403,10 @@ class Device(utils.CompositeEventEmitter):
await utils.cancel_on_event(connection, '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.EVENT_CONNECTION_ENCRYPTION_CHANGE, on_encryption_change
) )
connection.remove_listener( connection.remove_listener(
'connection_encryption_failure', on_encryption_failure connection.EVENT_CONNECTION_ENCRYPTION_FAILURE, on_encryption_failure
) )
async def update_keys(self, address: str, keys: PairingKeys) -> None: async def update_keys(self, address: str, keys: PairingKeys) -> None:
@@ -4330,7 +4419,7 @@ class Device(utils.CompositeEventEmitter):
except Exception as error: except Exception as error:
logger.warning(f'!!! error while storing keys: {error}') logger.warning(f'!!! error while storing keys: {error}')
else: else:
self.emit('key_store_update') self.emit(self.EVENT_KEY_STORE_UPDATE)
# [Classic only] # [Classic only]
async def switch_role(self, connection: Connection, role: hci.Role): async def switch_role(self, connection: Connection, role: hci.Role):
@@ -4342,8 +4431,8 @@ class Device(utils.CompositeEventEmitter):
def on_role_change_failure(error_code): def on_role_change_failure(error_code):
pending_role_change.set_exception(hci.HCI_Error(error_code)) pending_role_change.set_exception(hci.HCI_Error(error_code))
connection.on('role_change', on_role_change) connection.on(connection.EVENT_ROLE_CHANGE, on_role_change)
connection.on('role_change_failure', on_role_change_failure) connection.on(connection.EVENT_ROLE_CHANGE_FAILURE, on_role_change_failure)
try: try:
result = await self.send_command( result = await self.send_command(
@@ -4359,8 +4448,10 @@ class Device(utils.CompositeEventEmitter):
connection, 'disconnection', pending_role_change connection, 'disconnection', pending_role_change
) )
finally: finally:
connection.remove_listener('role_change', on_role_change) connection.remove_listener(connection.EVENT_ROLE_CHANGE, on_role_change)
connection.remove_listener('role_change_failure', on_role_change_failure) connection.remove_listener(
connection.EVENT_ROLE_CHANGE_FAILURE, on_role_change_failure
)
# [Classic only] # [Classic only]
async def request_remote_name(self, remote: Union[hci.Address, Connection]) -> str: async def request_remote_name(self, remote: Union[hci.Address, Connection]) -> str:
@@ -4372,7 +4463,7 @@ class Device(utils.CompositeEventEmitter):
) )
handler = self.on( handler = self.on(
'remote_name', self.EVENT_REMOTE_NAME,
lambda address, remote_name: ( lambda address, remote_name: (
pending_name.set_result(remote_name) pending_name.set_result(remote_name)
if address == peer_address if address == peer_address
@@ -4380,7 +4471,7 @@ class Device(utils.CompositeEventEmitter):
), ),
) )
failure_handler = self.on( failure_handler = self.on(
'remote_name_failure', self.EVENT_REMOTE_NAME_FAILURE,
lambda address, error_code: ( lambda address, error_code: (
pending_name.set_exception(hci.HCI_Error(error_code)) pending_name.set_exception(hci.HCI_Error(error_code))
if address == peer_address if address == peer_address
@@ -4408,8 +4499,8 @@ class Device(utils.CompositeEventEmitter):
# Wait for the result # Wait for the result
return await utils.cancel_on_event(self, 'flush', pending_name) return await utils.cancel_on_event(self, 'flush', pending_name)
finally: finally:
self.remove_listener('remote_name', handler) self.remove_listener(self.EVENT_REMOTE_NAME, handler)
self.remove_listener('remote_name_failure', failure_handler) self.remove_listener(self.EVENT_REMOTE_NAME_FAILURE, failure_handler)
# [LE only] # [LE only]
@utils.experimental('Only for testing.') @utils.experimental('Only for testing.')
@@ -4500,8 +4591,10 @@ class Device(utils.CompositeEventEmitter):
if pending_future := pending_cis_establishments.get(cis_handle): if pending_future := pending_cis_establishments.get(cis_handle):
pending_future.set_exception(hci.HCI_Error(status)) pending_future.set_exception(hci.HCI_Error(status))
watcher.on(self, 'cis_establishment', on_cis_establishment) watcher.on(self, self.EVENT_CIS_ESTABLISHMENT, on_cis_establishment)
watcher.on(self, 'cis_establishment_failure', on_cis_establishment_failure) watcher.on(
self, self.EVENT_CIS_ESTABLISHMENT_FAILURE, on_cis_establishment_failure
)
await self.send_command( await self.send_command(
hci.HCI_LE_Create_CIS_Command( hci.HCI_LE_Create_CIS_Command(
cis_connection_handle=[p[0] for p in cis_acl_pairs], cis_connection_handle=[p[0] for p in cis_acl_pairs],
@@ -4544,8 +4637,12 @@ class Device(utils.CompositeEventEmitter):
def on_establishment_failure(status: int) -> None: def on_establishment_failure(status: int) -> None:
pending_establishment.set_exception(hci.HCI_Error(status)) pending_establishment.set_exception(hci.HCI_Error(status))
watcher.on(cis_link, 'establishment', on_establishment) watcher.on(cis_link, cis_link.EVENT_ESTABLISHMENT, on_establishment)
watcher.on(cis_link, 'establishment_failure', on_establishment_failure) watcher.on(
cis_link,
cis_link.EVENT_ESTABLISHMENT_FAILURE,
on_establishment_failure,
)
await self.send_command( await self.send_command(
hci.HCI_LE_Accept_CIS_Request_Command(connection_handle=handle), hci.HCI_LE_Accept_CIS_Request_Command(connection_handle=handle),
@@ -4913,9 +5010,9 @@ class Device(utils.CompositeEventEmitter):
@host_event_handler @host_event_handler
def on_flush(self): def on_flush(self):
self.emit('flush') self.emit(self.EVENT_FLUSH)
for _, connection in self.connections.items(): for _, connection in self.connections.items():
connection.emit('disconnection', 0) connection.emit(connection.EVENT_DISCONNECTION, 0)
self.connections = {} self.connections = {}
# [Classic only] # [Classic only]
@@ -5206,7 +5303,7 @@ class Device(utils.CompositeEventEmitter):
lambda _: utils.cancel_on_event(self, 'flush', advertising_set.start()), lambda _: utils.cancel_on_event(self, 'flush', advertising_set.start()),
) )
self.emit('connection', connection) self.emit(self.EVENT_CONNECTION, connection)
@host_event_handler @host_event_handler
def on_connection( def on_connection(
@@ -5244,7 +5341,7 @@ class Device(utils.CompositeEventEmitter):
self.connections[connection_handle] = connection self.connections[connection_handle] = connection
# Emit an event to notify listeners of the new connection # Emit an event to notify listeners of the new connection
self.emit('connection', connection) self.emit(self.EVENT_CONNECTION, connection)
return return
@@ -5319,7 +5416,7 @@ class Device(utils.CompositeEventEmitter):
if role == hci.Role.CENTRAL or not self.supports_le_extended_advertising: if role == hci.Role.CENTRAL or not self.supports_le_extended_advertising:
# We can emit now, we have all the info we need # We can emit now, we have all the info we need
self.emit('connection', connection) self.emit(self.EVENT_CONNECTION, connection)
return return
if role == hci.Role.PERIPHERAL and self.supports_le_extended_advertising: if role == hci.Role.PERIPHERAL and self.supports_le_extended_advertising:
@@ -5353,7 +5450,7 @@ class Device(utils.CompositeEventEmitter):
'hci', 'hci',
hci.HCI_Constant.error_name(error_code), hci.HCI_Constant.error_name(error_code),
) )
self.emit('connection_failure', error) self.emit(self.EVENT_CONNECTION_FAILURE, error)
# FIXME: Explore a delegate-model for BR/EDR wait connection #56. # FIXME: Explore a delegate-model for BR/EDR wait connection #56.
@host_event_handler @host_event_handler
@@ -5368,7 +5465,7 @@ class Device(utils.CompositeEventEmitter):
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
): ):
self.emit('sco_request', connection, link_type) self.emit(self.EVENT_SCO_REQUEST, connection, link_type)
else: else:
logger.error(f'SCO request from a non-connected device {bd_addr}') logger.error(f'SCO request from a non-connected device {bd_addr}')
return return
@@ -5412,14 +5509,14 @@ class Device(utils.CompositeEventEmitter):
f'*** Disconnection: [0x{connection.handle:04X}] ' f'*** Disconnection: [0x{connection.handle:04X}] '
f'{connection.peer_address} as {connection.role_name}, reason={reason}' f'{connection.peer_address} as {connection.role_name}, reason={reason}'
) )
connection.emit('disconnection', reason) connection.emit(connection.EVENT_DISCONNECTION, reason)
# Cleanup subsystems that maintain per-connection state # Cleanup subsystems that maintain per-connection state
self.gatt_server.on_disconnection(connection) self.gatt_server.on_disconnection(connection)
elif sco_link := self.sco_links.pop(connection_handle, None): elif sco_link := self.sco_links.pop(connection_handle, None):
sco_link.emit('disconnection', reason) sco_link.emit(sco_link.EVENT_DISCONNECTION, reason)
elif cis_link := self.cis_links.pop(connection_handle, None): elif cis_link := self.cis_links.pop(connection_handle, None):
cis_link.emit('disconnection', reason) cis_link.emit(cis_link.EVENT_DISCONNECTION, reason)
else: else:
logger.error( logger.error(
f'*** Unknown disconnection handle=0x{connection_handle}, reason={reason} ***' f'*** Unknown disconnection handle=0x{connection_handle}, reason={reason} ***'
@@ -5427,7 +5524,7 @@ class Device(utils.CompositeEventEmitter):
@host_event_handler @host_event_handler
@with_connection_from_handle @with_connection_from_handle
def on_disconnection_failure(self, connection, error_code): def on_disconnection_failure(self, connection: Connection, error_code: int):
logger.debug(f'*** Disconnection failed: {error_code}') logger.debug(f'*** Disconnection failed: {error_code}')
error = core.ConnectionError( error = core.ConnectionError(
error_code, error_code,
@@ -5436,7 +5533,7 @@ class Device(utils.CompositeEventEmitter):
'hci', 'hci',
hci.HCI_Constant.error_name(error_code), hci.HCI_Constant.error_name(error_code),
) )
connection.emit('disconnection_failure', error) connection.emit(connection.EVENT_DISCONNECTION_FAILURE, error)
@host_event_handler @host_event_handler
@utils.AsyncRunner.run_in_task() @utils.AsyncRunner.run_in_task()
@@ -5447,7 +5544,7 @@ class Device(utils.CompositeEventEmitter):
else: else:
self.auto_restart_inquiry = True self.auto_restart_inquiry = True
self.discovering = False self.discovering = False
self.emit('inquiry_complete') self.emit(self.EVENT_INQUIRY_COMPLETE)
@host_event_handler @host_event_handler
@with_connection_from_handle @with_connection_from_handle
@@ -5457,7 +5554,7 @@ class Device(utils.CompositeEventEmitter):
f'{connection.peer_address} as {connection.role_name}' f'{connection.peer_address} as {connection.role_name}'
) )
connection.authenticated = True connection.authenticated = True
connection.emit('connection_authentication') connection.emit(connection.EVENT_CONNECTION_AUTHENTICATION)
@host_event_handler @host_event_handler
@with_connection_from_handle @with_connection_from_handle
@@ -5466,7 +5563,7 @@ class Device(utils.CompositeEventEmitter):
f'*** Connection Authentication Failure: [0x{connection.handle:04X}] ' f'*** Connection Authentication Failure: [0x{connection.handle:04X}] '
f'{connection.peer_address} as {connection.role_name}, error={error}' f'{connection.peer_address} as {connection.role_name}, error={error}'
) )
connection.emit('connection_authentication_failure', error) connection.emit(connection.EVENT_CONNECTION_AUTHENTICATION_FAILURE, error)
# [Classic only] # [Classic only]
@host_event_handler @host_event_handler
@@ -5684,22 +5781,22 @@ class Device(utils.CompositeEventEmitter):
remote_name = remote_name.decode('utf-8') remote_name = remote_name.decode('utf-8')
if connection: if connection:
connection.peer_name = remote_name connection.peer_name = remote_name
connection.emit('remote_name') connection.emit(connection.EVENT_REMOTE_NAME)
self.emit('remote_name', address, remote_name) self.emit(self.EVENT_REMOTE_NAME, address, remote_name)
except UnicodeDecodeError as error: except UnicodeDecodeError as error:
logger.warning('peer name is not valid UTF-8') logger.warning('peer name is not valid UTF-8')
if connection: if connection:
connection.emit('remote_name_failure', error) connection.emit(connection.EVENT_REMOTE_NAME_FAILURE, error)
else: else:
self.emit('remote_name_failure', address, error) self.emit(self.EVENT_REMOTE_NAME_FAILURE, address, error)
# [Classic only] # [Classic only]
@host_event_handler @host_event_handler
@try_with_connection_from_address @try_with_connection_from_address
def on_remote_name_failure(self, connection: Connection, address, error): def on_remote_name_failure(self, connection: Connection, address, error):
if connection: if connection:
connection.emit('remote_name_failure', error) connection.emit(connection.EVENT_REMOTE_NAME_FAILURE, error)
self.emit('remote_name_failure', address, error) self.emit(self.EVENT_REMOTE_NAME_FAILURE, address, error)
# [Classic only] # [Classic only]
@host_event_handler @host_event_handler
@@ -5719,7 +5816,7 @@ class Device(utils.CompositeEventEmitter):
handle=sco_handle, handle=sco_handle,
link_type=link_type, link_type=link_type,
) )
self.emit('sco_connection', sco_link) self.emit(self.EVENT_SCO_CONNECTION, sco_link)
# [Classic only] # [Classic only]
@host_event_handler @host_event_handler
@@ -5729,7 +5826,7 @@ class Device(utils.CompositeEventEmitter):
self, acl_connection: Connection, status: int self, acl_connection: Connection, status: int
) -> None: ) -> None:
logger.debug(f'*** SCO connection failure: {acl_connection.peer_address}***') logger.debug(f'*** SCO connection failure: {acl_connection.peer_address}***')
self.emit('sco_connection_failure') self.emit(self.EVENT_SCO_CONNECTION_FAILURE)
# [Classic only] # [Classic only]
@host_event_handler @host_event_handler
@@ -5766,7 +5863,7 @@ class Device(utils.CompositeEventEmitter):
cig_id=cig_id, cig_id=cig_id,
cis_id=cis_id, cis_id=cis_id,
) )
self.emit('cis_request', acl_connection, cis_handle, cig_id, cis_id) self.emit(self.EVENT_CIS_REQUEST, acl_connection, cis_handle, cig_id, cis_id)
# [LE only] # [LE only]
@host_event_handler @host_event_handler
@@ -5785,8 +5882,8 @@ class Device(utils.CompositeEventEmitter):
f'cis_id=[0x{cis_link.cis_id:02X}] ***' f'cis_id=[0x{cis_link.cis_id:02X}] ***'
) )
cis_link.emit('establishment') cis_link.emit(cis_link.EVENT_ESTABLISHMENT)
self.emit('cis_establishment', cis_link) self.emit(self.EVENT_CIS_ESTABLISHMENT, cis_link)
# [LE only] # [LE only]
@host_event_handler @host_event_handler
@@ -5794,8 +5891,8 @@ class Device(utils.CompositeEventEmitter):
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):
cis_link.emit('establishment_failure', status) cis_link.emit(cis_link.EVENT_ESTABLISHMENT_FAILURE, status)
self.emit('cis_establishment_failure', cis_handle, status) self.emit(self.EVENT_CIS_ESTABLISHMENT_FAILURE, cis_handle, status)
# [LE only] # [LE only]
@host_event_handler @host_event_handler
@@ -5829,7 +5926,7 @@ class Device(utils.CompositeEventEmitter):
): ):
connection.authenticated = True connection.authenticated = True
connection.sc = True connection.sc = True
connection.emit('connection_encryption_change') connection.emit(connection.EVENT_CONNECTION_ENCRYPTION_CHANGE)
@host_event_handler @host_event_handler
@with_connection_from_handle @with_connection_from_handle
@@ -5839,7 +5936,7 @@ class Device(utils.CompositeEventEmitter):
f'{connection.peer_address} as {connection.role_name}, ' f'{connection.peer_address} as {connection.role_name}, '
f'error={error}' f'error={error}'
) )
connection.emit('connection_encryption_failure', error) connection.emit(connection.EVENT_CONNECTION_ENCRYPTION_FAILURE, error)
@host_event_handler @host_event_handler
@with_connection_from_handle @with_connection_from_handle
@@ -5848,7 +5945,7 @@ class Device(utils.CompositeEventEmitter):
f'*** Connection Key Refresh: [0x{connection.handle:04X}] ' f'*** Connection Key Refresh: [0x{connection.handle:04X}] '
f'{connection.peer_address} as {connection.role_name}' f'{connection.peer_address} as {connection.role_name}'
) )
connection.emit('connection_encryption_key_refresh') connection.emit(connection.EVENT_CONNECTION_ENCRYPTION_KEY_REFRESH)
@host_event_handler @host_event_handler
@with_connection_from_handle @with_connection_from_handle
@@ -5859,7 +5956,7 @@ class Device(utils.CompositeEventEmitter):
f'{connection_parameters}' f'{connection_parameters}'
) )
connection.parameters = connection_parameters connection.parameters = connection_parameters
connection.emit('connection_parameters_update') connection.emit(connection.EVENT_CONNECTION_PARAMETERS_UPDATE)
@host_event_handler @host_event_handler
@with_connection_from_handle @with_connection_from_handle
@@ -5869,7 +5966,7 @@ class Device(utils.CompositeEventEmitter):
f'{connection.peer_address} as {connection.role_name}, ' f'{connection.peer_address} as {connection.role_name}, '
f'error={error}' f'error={error}'
) )
connection.emit('connection_parameters_update_failure', error) connection.emit(connection.EVENT_CONNECTION_PARAMETERS_UPDATE_FAILURE, error)
@host_event_handler @host_event_handler
@with_connection_from_handle @with_connection_from_handle
@@ -5879,7 +5976,7 @@ class Device(utils.CompositeEventEmitter):
f'{connection.peer_address} as {connection.role_name}, ' f'{connection.peer_address} as {connection.role_name}, '
f'{phy}' f'{phy}'
) )
connection.emit('connection_phy_update', phy) connection.emit(connection.EVENT_CONNECTION_PHY_UPDATE, phy)
@host_event_handler @host_event_handler
@with_connection_from_handle @with_connection_from_handle
@@ -5889,7 +5986,7 @@ class Device(utils.CompositeEventEmitter):
f'{connection.peer_address} as {connection.role_name}, ' f'{connection.peer_address} as {connection.role_name}, '
f'error={error}' f'error={error}'
) )
connection.emit('connection_phy_update_failure', error) connection.emit(connection.EVENT_CONNECTION_PHY_UPDATE_FAILURE, error)
@host_event_handler @host_event_handler
@with_connection_from_handle @with_connection_from_handle
@@ -5900,7 +5997,7 @@ class Device(utils.CompositeEventEmitter):
f'{att_mtu}' f'{att_mtu}'
) )
connection.att_mtu = att_mtu connection.att_mtu = att_mtu
connection.emit('connection_att_mtu_update') connection.emit(connection.EVENT_CONNECTION_ATT_MTU_UPDATE)
@host_event_handler @host_event_handler
@with_connection_from_handle @with_connection_from_handle
@@ -5917,7 +6014,7 @@ class Device(utils.CompositeEventEmitter):
max_rx_octets, max_rx_octets,
max_rx_time, max_rx_time,
) )
connection.emit('connection_data_length_change') connection.emit(connection.EVENT_CONNECTION_DATA_LENGTH_CHANGE)
@host_event_handler @host_event_handler
def on_cs_remote_supported_capabilities( def on_cs_remote_supported_capabilities(
@@ -5927,7 +6024,9 @@ class Device(utils.CompositeEventEmitter):
return return
if event.status != hci.HCI_SUCCESS: if event.status != hci.HCI_SUCCESS:
connection.emit('channel_sounding_capabilities_failure', event.status) connection.emit(
connection.EVENT_CHANNEL_SOUNDING_CAPABILITIES_FAILURE, event.status
)
return return
capabilities = ChannelSoundingCapabilities( capabilities = ChannelSoundingCapabilities(
@@ -5952,7 +6051,7 @@ class Device(utils.CompositeEventEmitter):
t_sw_time_supported=event.t_sw_time_supported, t_sw_time_supported=event.t_sw_time_supported,
tx_snr_capability=event.tx_snr_capability, tx_snr_capability=event.tx_snr_capability,
) )
connection.emit('channel_sounding_capabilities', capabilities) connection.emit(connection.EVENT_CHANNEL_SOUNDING_CAPABILITIES, capabilities)
@host_event_handler @host_event_handler
def on_cs_config(self, event: hci.HCI_LE_CS_Config_Complete_Event): def on_cs_config(self, event: hci.HCI_LE_CS_Config_Complete_Event):
@@ -5960,7 +6059,9 @@ class Device(utils.CompositeEventEmitter):
return return
if event.status != hci.HCI_SUCCESS: if event.status != hci.HCI_SUCCESS:
connection.emit('channel_sounding_config_failure', event.status) connection.emit(
connection.EVENT_CHANNEL_SOUNDING_CONFIG_FAILURE, event.status
)
return return
if event.action == hci.HCI_LE_CS_Config_Complete_Event.Action.CREATED: if event.action == hci.HCI_LE_CS_Config_Complete_Event.Action.CREATED:
config = ChannelSoundingConfig( config = ChannelSoundingConfig(
@@ -5986,11 +6087,13 @@ class Device(utils.CompositeEventEmitter):
t_pm_time=event.t_pm_time, t_pm_time=event.t_pm_time,
) )
connection.cs_configs[event.config_id] = config connection.cs_configs[event.config_id] = config
connection.emit('channel_sounding_config', config) connection.emit(connection.EVENT_CHANNEL_SOUNDING_CONFIG, config)
elif event.action == hci.HCI_LE_CS_Config_Complete_Event.Action.REMOVED: elif event.action == hci.HCI_LE_CS_Config_Complete_Event.Action.REMOVED:
try: try:
config = connection.cs_configs.pop(event.config_id) config = connection.cs_configs.pop(event.config_id)
connection.emit('channel_sounding_config_removed', config.config_id) connection.emit(
connection.EVENT_CHANNEL_SOUNDING_CONFIG_REMOVED, config.config_id
)
except KeyError: except KeyError:
logger.error('Removing unknown config %d', event.config_id) logger.error('Removing unknown config %d', event.config_id)
@@ -6000,7 +6103,9 @@ class Device(utils.CompositeEventEmitter):
return return
if event.status != hci.HCI_SUCCESS: if event.status != hci.HCI_SUCCESS:
connection.emit('channel_sounding_procedure_failure', event.status) connection.emit(
connection.EVENT_CHANNEL_SOUNDING_PROCEDURE_FAILURE, event.status
)
return return
procedure = ChannelSoundingProcedure( procedure = ChannelSoundingProcedure(
@@ -6017,37 +6122,37 @@ class Device(utils.CompositeEventEmitter):
max_procedure_len=event.max_procedure_len, max_procedure_len=event.max_procedure_len,
) )
connection.cs_procedures[procedure.config_id] = procedure connection.cs_procedures[procedure.config_id] = procedure
connection.emit('channel_sounding_procedure', procedure) connection.emit(connection.EVENT_CHANNEL_SOUNDING_PROCEDURE, procedure)
# [Classic only] # [Classic only]
@host_event_handler @host_event_handler
@with_connection_from_address @with_connection_from_address
def on_role_change(self, connection, new_role): def on_role_change(self, connection, new_role):
connection.role = new_role connection.role = new_role
connection.emit('role_change', new_role) connection.emit(connection.EVENT_ROLE_CHANGE, new_role)
# [Classic only] # [Classic only]
@host_event_handler @host_event_handler
@try_with_connection_from_address @try_with_connection_from_address
def on_role_change_failure(self, connection, address, error): def on_role_change_failure(self, connection, address, error):
if connection: if connection:
connection.emit('role_change_failure', error) connection.emit(connection.EVENT_ROLE_CHANGE_FAILURE, error)
self.emit('role_change_failure', address, error) self.emit(self.EVENT_ROLE_CHANGE_FAILURE, address, error)
# [Classic only] # [Classic only]
@host_event_handler @host_event_handler
@with_connection_from_address @with_connection_from_address
def on_classic_pairing(self, connection: Connection) -> None: def on_classic_pairing(self, connection: Connection) -> None:
connection.emit('classic_pairing') connection.emit(connection.EVENT_CLASSIC_PAIRING)
# [Classic only] # [Classic only]
@host_event_handler @host_event_handler
@with_connection_from_address @with_connection_from_address
def on_classic_pairing_failure(self, connection: Connection, status) -> None: def on_classic_pairing_failure(self, connection: Connection, status) -> None:
connection.emit('classic_pairing_failure', status) connection.emit(connection.EVENT_CLASSIC_PAIRING_FAILURE, status)
def on_pairing_start(self, connection: Connection) -> None: def on_pairing_start(self, connection: Connection) -> None:
connection.emit('pairing_start') connection.emit(connection.EVENT_PAIRING_START)
def on_pairing( def on_pairing(
self, self,
@@ -6061,10 +6166,10 @@ class Device(utils.CompositeEventEmitter):
connection.peer_address = identity_address connection.peer_address = identity_address
connection.sc = sc connection.sc = sc
connection.authenticated = True connection.authenticated = True
connection.emit('pairing', keys) connection.emit(connection.EVENT_PAIRING, keys)
def on_pairing_failure(self, connection: Connection, reason: int) -> None: def on_pairing_failure(self, connection: Connection, reason: int) -> None:
connection.emit('pairing_failure', reason) connection.emit(connection.EVENT_PAIRING_FAILURE, reason)
@with_connection_from_handle @with_connection_from_handle
def on_gatt_pdu(self, connection, pdu): def on_gatt_pdu(self, connection, pdu):

View File

@@ -448,6 +448,8 @@ class Characteristic(Attribute[_T]):
uuid: UUID uuid: UUID
properties: Characteristic.Properties properties: Characteristic.Properties
EVENT_SUBSCRIPTION = "subscription"
class Properties(enum.IntFlag): class Properties(enum.IntFlag):
"""Property flags""" """Property flags"""

View File

@@ -202,6 +202,8 @@ class CharacteristicProxy(AttributeProxy[_T]):
descriptors: List[DescriptorProxy] descriptors: List[DescriptorProxy]
subscribers: Dict[Any, Callable[[_T], Any]] subscribers: Dict[Any, Callable[[_T], Any]]
EVENT_UPDATE = "update"
def __init__( def __init__(
self, self,
client: Client, client: Client,
@@ -308,7 +310,7 @@ class Client:
self.services = [] self.services = []
self.cached_values = {} self.cached_values = {}
connection.on('disconnection', self.on_disconnection) connection.on(connection.EVENT_DISCONNECTION, self.on_disconnection)
def send_gatt_pdu(self, pdu: bytes) -> None: def send_gatt_pdu(self, pdu: bytes) -> None:
self.connection.send_l2cap_pdu(ATT_CID, pdu) self.connection.send_l2cap_pdu(ATT_CID, pdu)
@@ -1142,7 +1144,7 @@ class Client:
if callable(subscriber): if callable(subscriber):
subscriber(notification.attribute_value) subscriber(notification.attribute_value)
else: else:
subscriber.emit('update', notification.attribute_value) subscriber.emit(subscriber.EVENT_UPDATE, notification.attribute_value)
def on_att_handle_value_indication(self, indication): def on_att_handle_value_indication(self, indication):
# Call all subscribers # Call all subscribers
@@ -1157,7 +1159,7 @@ class Client:
if callable(subscriber): if callable(subscriber):
subscriber(indication.attribute_value) subscriber(indication.attribute_value)
else: else:
subscriber.emit('update', indication.attribute_value) subscriber.emit(subscriber.EVENT_UPDATE, indication.attribute_value)
# Confirm that we received the indication # Confirm that we received the indication
self.send_confirmation(ATT_Handle_Value_Confirmation()) self.send_confirmation(ATT_Handle_Value_Confirmation())

View File

@@ -110,6 +110,8 @@ class Server(utils.EventEmitter):
indication_semaphores: defaultdict[int, asyncio.Semaphore] indication_semaphores: defaultdict[int, asyncio.Semaphore]
pending_confirmations: defaultdict[int, Optional[asyncio.futures.Future]] pending_confirmations: defaultdict[int, Optional[asyncio.futures.Future]]
EVENT_CHARACTERISTIC_SUBSCRIPTION = "characteristic_subscription"
def __init__(self, device: Device) -> None: def __init__(self, device: Device) -> None:
super().__init__() super().__init__()
self.device = device self.device = device
@@ -347,10 +349,13 @@ class Server(utils.EventEmitter):
notify_enabled = value[0] & 0x01 != 0 notify_enabled = value[0] & 0x01 != 0
indicate_enabled = value[0] & 0x02 != 0 indicate_enabled = value[0] & 0x02 != 0
characteristic.emit( characteristic.emit(
'subscription', connection, notify_enabled, indicate_enabled characteristic.EVENT_SUBSCRIPTION,
connection,
notify_enabled,
indicate_enabled,
) )
self.emit( self.emit(
'characteristic_subscription', self.EVENT_CHARACTERISTIC_SUBSCRIPTION,
connection, connection,
characteristic, characteristic,
notify_enabled, notify_enabled,

View File

@@ -720,6 +720,14 @@ class HfProtocol(utils.EventEmitter):
vrec: VoiceRecognitionState vrec: VoiceRecognitionState
""" """
EVENT_CODEC_NEGOTIATION = "codec_negotiation"
EVENT_AG_INDICATOR = "ag_indicator"
EVENT_SPEAKER_VOLUME = "speaker_volume"
EVENT_MICROPHONE_VOLUME = "microphone_volume"
EVENT_RING = "ring"
EVENT_CLI_NOTIFICATION = "cli_notification"
EVENT_VOICE_RECOGNITION = "voice_recognition"
class HfLoopTermination(HfpProtocolError): class HfLoopTermination(HfpProtocolError):
"""Termination signal for run() loop.""" """Termination signal for run() loop."""
@@ -777,7 +785,8 @@ class HfProtocol(utils.EventEmitter):
self.dlc.sink = self._read_at self.dlc.sink = self._read_at
# Stop the run() loop when L2CAP is closed. # Stop the run() loop when L2CAP is closed.
self.dlc.multiplexer.l2cap_channel.on( self.dlc.multiplexer.l2cap_channel.on(
'close', lambda: self.unsolicited_queue.put_nowait(None) self.dlc.multiplexer.l2cap_channel.EVENT_CLOSE,
lambda: self.unsolicited_queue.put_nowait(None),
) )
def supports_hf_feature(self, feature: HfFeature) -> bool: def supports_hf_feature(self, feature: HfFeature) -> bool:
@@ -1034,7 +1043,7 @@ class HfProtocol(utils.EventEmitter):
# ID. The HF shall be ready to accept the synchronous connection # ID. The HF shall be ready to accept the synchronous connection
# establishment as soon as it has sent the AT commands AT+BCS=<Codec ID>. # establishment as soon as it has sent the AT commands AT+BCS=<Codec ID>.
self.active_codec = AudioCodec(codec_id) self.active_codec = AudioCodec(codec_id)
self.emit('codec_negotiation', self.active_codec) self.emit(self.EVENT_CODEC_NEGOTIATION, self.active_codec)
logger.info("codec connection setup completed") logger.info("codec connection setup completed")
@@ -1095,7 +1104,7 @@ class HfProtocol(utils.EventEmitter):
# CIEV is in 1-index, while ag_indicators is in 0-index. # CIEV is in 1-index, while ag_indicators is in 0-index.
ag_indicator = self.ag_indicators[index - 1] ag_indicator = self.ag_indicators[index - 1]
ag_indicator.current_status = value ag_indicator.current_status = value
self.emit('ag_indicator', ag_indicator) self.emit(self.EVENT_AG_INDICATOR, ag_indicator)
logger.info(f"AG indicator updated: {ag_indicator.indicator}, {value}") logger.info(f"AG indicator updated: {ag_indicator.indicator}, {value}")
async def handle_unsolicited(self): async def handle_unsolicited(self):
@@ -1110,19 +1119,21 @@ class HfProtocol(utils.EventEmitter):
int(result.parameters[0]), int(result.parameters[1]) int(result.parameters[0]), int(result.parameters[1])
) )
elif result.code == "+VGS": elif result.code == "+VGS":
self.emit('speaker_volume', int(result.parameters[0])) self.emit(self.EVENT_SPEAKER_VOLUME, int(result.parameters[0]))
elif result.code == "+VGM": elif result.code == "+VGM":
self.emit('microphone_volume', int(result.parameters[0])) self.emit(self.EVENT_MICROPHONE_VOLUME, int(result.parameters[0]))
elif result.code == "RING": elif result.code == "RING":
self.emit('ring') self.emit(self.EVENT_RING)
elif result.code == "+CLIP": elif result.code == "+CLIP":
self.emit( self.emit(
'cli_notification', CallLineIdentification.parse_from(result.parameters) self.EVENT_CLI_NOTIFICATION,
CallLineIdentification.parse_from(result.parameters),
) )
elif result.code == "+BVRA": elif result.code == "+BVRA":
# TODO: Support Enhanced Voice Recognition. # TODO: Support Enhanced Voice Recognition.
self.emit( self.emit(
'voice_recognition', VoiceRecognitionState(int(result.parameters[0])) self.EVENT_VOICE_RECOGNITION,
VoiceRecognitionState(int(result.parameters[0])),
) )
else: else:
logging.info(f"unhandled unsolicited response {result.code}") logging.info(f"unhandled unsolicited response {result.code}")
@@ -1179,6 +1190,19 @@ class AgProtocol(utils.EventEmitter):
volume: Int volume: Int
""" """
EVENT_SLC_COMPLETE = "slc_complete"
EVENT_SUPPORTED_AUDIO_CODECS = "supported_audio_codecs"
EVENT_CODEC_NEGOTIATION = "codec_negotiation"
EVENT_VOICE_RECOGNITION = "voice_recognition"
EVENT_CALL_HOLD = "call_hold"
EVENT_HF_INDICATOR = "hf_indicator"
EVENT_CODEC_CONNECTION_REQUEST = "codec_connection_request"
EVENT_ANSWER = "answer"
EVENT_DIAL = "dial"
EVENT_HANG_UP = "hang_up"
EVENT_SPEAKER_VOLUME = "speaker_volume"
EVENT_MICROPHONE_VOLUME = "microphone_volume"
supported_hf_features: int supported_hf_features: int
supported_hf_indicators: Set[HfIndicator] supported_hf_indicators: Set[HfIndicator]
supported_audio_codecs: List[AudioCodec] supported_audio_codecs: List[AudioCodec]
@@ -1371,7 +1395,7 @@ class AgProtocol(utils.EventEmitter):
def _check_remained_slc_commands(self) -> None: def _check_remained_slc_commands(self) -> None:
if not self._remained_slc_setup_features: if not self._remained_slc_setup_features:
self.emit('slc_complete') self.emit(self.EVENT_SLC_COMPLETE)
def _on_brsf(self, hf_features: bytes) -> None: def _on_brsf(self, hf_features: bytes) -> None:
self.supported_hf_features = int(hf_features) self.supported_hf_features = int(hf_features)
@@ -1390,17 +1414,17 @@ class AgProtocol(utils.EventEmitter):
def _on_bac(self, *args) -> None: def _on_bac(self, *args) -> None:
self.supported_audio_codecs = [AudioCodec(int(value)) for value in args] self.supported_audio_codecs = [AudioCodec(int(value)) for value in args]
self.emit('supported_audio_codecs', self.supported_audio_codecs) self.emit(self.EVENT_SUPPORTED_AUDIO_CODECS, self.supported_audio_codecs)
self.send_ok() self.send_ok()
def _on_bcs(self, codec: bytes) -> None: def _on_bcs(self, codec: bytes) -> None:
self.active_codec = AudioCodec(int(codec)) self.active_codec = AudioCodec(int(codec))
self.send_ok() self.send_ok()
self.emit('codec_negotiation', self.active_codec) self.emit(self.EVENT_CODEC_NEGOTIATION, self.active_codec)
def _on_bvra(self, vrec: bytes) -> None: def _on_bvra(self, vrec: bytes) -> None:
self.send_ok() self.send_ok()
self.emit('voice_recognition', VoiceRecognitionState(int(vrec))) self.emit(self.EVENT_VOICE_RECOGNITION, VoiceRecognitionState(int(vrec)))
def _on_chld(self, operation_code: bytes) -> None: def _on_chld(self, operation_code: bytes) -> None:
call_index: Optional[int] = None call_index: Optional[int] = None
@@ -1427,7 +1451,7 @@ class AgProtocol(utils.EventEmitter):
# Real three-way calls have more complicated situations, but this is not a popular issue - let users to handle the remaining :) # Real three-way calls have more complicated situations, but this is not a popular issue - let users to handle the remaining :)
self.send_ok() self.send_ok()
self.emit('call_hold', operation, call_index) self.emit(self.EVENT_CALL_HOLD, operation, call_index)
def _on_chld_test(self) -> None: def _on_chld_test(self) -> None:
if not self.supports_ag_feature(AgFeature.THREE_WAY_CALLING): if not self.supports_ag_feature(AgFeature.THREE_WAY_CALLING):
@@ -1553,7 +1577,7 @@ class AgProtocol(utils.EventEmitter):
return return
self.hf_indicators[index].current_status = int(value_bytes) self.hf_indicators[index].current_status = int(value_bytes)
self.emit('hf_indicator', self.hf_indicators[index]) self.emit(self.EVENT_HF_INDICATOR, self.hf_indicators[index])
self.send_ok() self.send_ok()
def _on_bia(self, *args) -> None: def _on_bia(self, *args) -> None:
@@ -1562,21 +1586,21 @@ class AgProtocol(utils.EventEmitter):
self.send_ok() self.send_ok()
def _on_bcc(self) -> None: def _on_bcc(self) -> None:
self.emit('codec_connection_request') self.emit(self.EVENT_CODEC_CONNECTION_REQUEST)
self.send_ok() self.send_ok()
def _on_a(self) -> None: def _on_a(self) -> None:
"""ATA handler.""" """ATA handler."""
self.emit('answer') self.emit(self.EVENT_ANSWER)
self.send_ok() self.send_ok()
def _on_d(self, number: bytes) -> None: def _on_d(self, number: bytes) -> None:
"""ATD handler.""" """ATD handler."""
self.emit('dial', number.decode()) self.emit(self.EVENT_DIAL, number.decode())
self.send_ok() self.send_ok()
def _on_chup(self) -> None: def _on_chup(self) -> None:
self.emit('hang_up') self.emit(self.EVENT_HANG_UP)
self.send_ok() self.send_ok()
def _on_clcc(self) -> None: def _on_clcc(self) -> None:
@@ -1602,11 +1626,11 @@ class AgProtocol(utils.EventEmitter):
self.send_ok() self.send_ok()
def _on_vgs(self, level: bytes) -> None: def _on_vgs(self, level: bytes) -> None:
self.emit('speaker_volume', int(level)) self.emit(self.EVENT_SPEAKER_VOLUME, int(level))
self.send_ok() self.send_ok()
def _on_vgm(self, level: bytes) -> None: def _on_vgm(self, level: bytes) -> None:
self.emit('microphone_volume', int(level)) self.emit(self.EVENT_MICROPHONE_VOLUME, int(level))
self.send_ok() self.send_ok()

View File

@@ -201,6 +201,13 @@ class HID(ABC, utils.EventEmitter):
l2cap_intr_channel: Optional[l2cap.ClassicChannel] = None l2cap_intr_channel: Optional[l2cap.ClassicChannel] = None
connection: Optional[device.Connection] = None connection: Optional[device.Connection] = None
EVENT_INTERRUPT_DATA = "interrupt_data"
EVENT_CONTROL_DATA = "control_data"
EVENT_SUSPEND = "suspend"
EVENT_EXIT_SUSPEND = "exit_suspend"
EVENT_VIRTUAL_CABLE_UNPLUG = "virtual_cable_unplug"
EVENT_HANDSHAKE = "handshake"
class Role(enum.IntEnum): class Role(enum.IntEnum):
HOST = 0x00 HOST = 0x00
DEVICE = 0x01 DEVICE = 0x01
@@ -215,7 +222,7 @@ class HID(ABC, utils.EventEmitter):
device.register_l2cap_server(HID_CONTROL_PSM, self.on_l2cap_connection) device.register_l2cap_server(HID_CONTROL_PSM, self.on_l2cap_connection)
device.register_l2cap_server(HID_INTERRUPT_PSM, self.on_l2cap_connection) device.register_l2cap_server(HID_INTERRUPT_PSM, self.on_l2cap_connection)
device.on('connection', self.on_device_connection) device.on(device.EVENT_CONNECTION, self.on_device_connection)
async def connect_control_channel(self) -> None: async def connect_control_channel(self) -> None:
# Create a new L2CAP connection - control channel # Create a new L2CAP connection - control channel
@@ -258,15 +265,20 @@ class HID(ABC, utils.EventEmitter):
def on_device_connection(self, connection: device.Connection) -> None: def on_device_connection(self, connection: device.Connection) -> None:
self.connection = connection self.connection = connection
self.remote_device_bd_address = connection.peer_address self.remote_device_bd_address = connection.peer_address
connection.on('disconnection', self.on_device_disconnection) connection.on(connection.EVENT_DISCONNECTION, self.on_device_disconnection)
def on_device_disconnection(self, reason: int) -> None: def on_device_disconnection(self, reason: int) -> None:
self.connection = None self.connection = None
def on_l2cap_connection(self, l2cap_channel: l2cap.ClassicChannel) -> None: def on_l2cap_connection(self, l2cap_channel: l2cap.ClassicChannel) -> None:
logger.debug(f'+++ New L2CAP connection: {l2cap_channel}') logger.debug(f'+++ New L2CAP connection: {l2cap_channel}')
l2cap_channel.on('open', lambda: self.on_l2cap_channel_open(l2cap_channel)) l2cap_channel.on(
l2cap_channel.on('close', lambda: self.on_l2cap_channel_close(l2cap_channel)) l2cap_channel.EVENT_OPEN, lambda: self.on_l2cap_channel_open(l2cap_channel)
)
l2cap_channel.on(
l2cap_channel.EVENT_CLOSE,
lambda: self.on_l2cap_channel_close(l2cap_channel),
)
def on_l2cap_channel_open(self, l2cap_channel: l2cap.ClassicChannel) -> None: def on_l2cap_channel_open(self, l2cap_channel: l2cap.ClassicChannel) -> None:
if l2cap_channel.psm == HID_CONTROL_PSM: if l2cap_channel.psm == HID_CONTROL_PSM:
@@ -290,7 +302,7 @@ class HID(ABC, utils.EventEmitter):
def on_intr_pdu(self, pdu: bytes) -> None: def on_intr_pdu(self, pdu: bytes) -> None:
logger.debug(f'<<< HID INTERRUPT PDU: {pdu.hex()}') logger.debug(f'<<< HID INTERRUPT PDU: {pdu.hex()}')
self.emit("interrupt_data", pdu) self.emit(self.EVENT_INTERRUPT_DATA, pdu)
def send_pdu_on_ctrl(self, msg: bytes) -> None: def send_pdu_on_ctrl(self, msg: bytes) -> None:
assert self.l2cap_ctrl_channel assert self.l2cap_ctrl_channel
@@ -363,17 +375,17 @@ class Device(HID):
self.handle_set_protocol(pdu) self.handle_set_protocol(pdu)
elif message_type == Message.MessageType.DATA: elif message_type == Message.MessageType.DATA:
logger.debug('<<< HID CONTROL DATA') logger.debug('<<< HID CONTROL DATA')
self.emit('control_data', pdu) self.emit(self.EVENT_CONTROL_DATA, pdu)
elif message_type == Message.MessageType.CONTROL: elif message_type == Message.MessageType.CONTROL:
if param == Message.ControlCommand.SUSPEND: if param == Message.ControlCommand.SUSPEND:
logger.debug('<<< HID SUSPEND') logger.debug('<<< HID SUSPEND')
self.emit('suspend') self.emit(self.EVENT_SUSPEND)
elif param == Message.ControlCommand.EXIT_SUSPEND: elif param == Message.ControlCommand.EXIT_SUSPEND:
logger.debug('<<< HID EXIT SUSPEND') logger.debug('<<< HID EXIT SUSPEND')
self.emit('exit_suspend') self.emit(self.EVENT_EXIT_SUSPEND)
elif param == Message.ControlCommand.VIRTUAL_CABLE_UNPLUG: elif param == Message.ControlCommand.VIRTUAL_CABLE_UNPLUG:
logger.debug('<<< HID VIRTUAL CABLE UNPLUG') logger.debug('<<< HID VIRTUAL CABLE UNPLUG')
self.emit('virtual_cable_unplug') self.emit(self.EVENT_VIRTUAL_CABLE_UNPLUG)
else: else:
logger.debug('<<< HID CONTROL OPERATION UNSUPPORTED') logger.debug('<<< HID CONTROL OPERATION UNSUPPORTED')
else: else:
@@ -538,14 +550,14 @@ class Host(HID):
message_type = pdu[0] >> 4 message_type = pdu[0] >> 4
if message_type == Message.MessageType.HANDSHAKE: if message_type == Message.MessageType.HANDSHAKE:
logger.debug(f'<<< HID HANDSHAKE: {Message.Handshake(param).name}') logger.debug(f'<<< HID HANDSHAKE: {Message.Handshake(param).name}')
self.emit('handshake', Message.Handshake(param)) self.emit(self.EVENT_HANDSHAKE, Message.Handshake(param))
elif message_type == Message.MessageType.DATA: elif message_type == Message.MessageType.DATA:
logger.debug('<<< HID CONTROL DATA') logger.debug('<<< HID CONTROL DATA')
self.emit('control_data', pdu) self.emit(self.EVENT_CONTROL_DATA, pdu)
elif message_type == Message.MessageType.CONTROL: elif message_type == Message.MessageType.CONTROL:
if param == Message.ControlCommand.VIRTUAL_CABLE_UNPLUG: if param == Message.ControlCommand.VIRTUAL_CABLE_UNPLUG:
logger.debug('<<< HID VIRTUAL CABLE UNPLUG') logger.debug('<<< HID VIRTUAL CABLE UNPLUG')
self.emit('virtual_cable_unplug') self.emit(self.EVENT_VIRTUAL_CABLE_UNPLUG)
else: else:
logger.debug('<<< HID CONTROL OPERATION UNSUPPORTED') logger.debug('<<< HID CONTROL OPERATION UNSUPPORTED')
else: else:

View File

@@ -744,6 +744,9 @@ class ClassicChannel(utils.EventEmitter):
WAIT_FINAL_RSP = 0x16 WAIT_FINAL_RSP = 0x16
WAIT_CONTROL_IND = 0x17 WAIT_CONTROL_IND = 0x17
EVENT_OPEN = "open"
EVENT_CLOSE = "close"
connection_result: Optional[asyncio.Future[None]] connection_result: Optional[asyncio.Future[None]]
disconnection_result: Optional[asyncio.Future[None]] disconnection_result: Optional[asyncio.Future[None]]
response: Optional[asyncio.Future[bytes]] response: Optional[asyncio.Future[bytes]]
@@ -847,7 +850,7 @@ class ClassicChannel(utils.EventEmitter):
def abort(self) -> None: def abort(self) -> None:
if self.state == self.State.OPEN: if self.state == self.State.OPEN:
self._change_state(self.State.CLOSED) self._change_state(self.State.CLOSED)
self.emit('close') self.emit(self.EVENT_CLOSE)
def send_configure_request(self) -> None: def send_configure_request(self) -> None:
options = L2CAP_Control_Frame.encode_configuration_options( options = L2CAP_Control_Frame.encode_configuration_options(
@@ -940,7 +943,7 @@ class ClassicChannel(utils.EventEmitter):
if self.connection_result: if self.connection_result:
self.connection_result.set_result(None) self.connection_result.set_result(None)
self.connection_result = None self.connection_result = None
self.emit('open') self.emit(self.EVENT_OPEN)
elif self.state == self.State.WAIT_CONFIG_REQ_RSP: elif self.state == self.State.WAIT_CONFIG_REQ_RSP:
self._change_state(self.State.WAIT_CONFIG_RSP) self._change_state(self.State.WAIT_CONFIG_RSP)
@@ -956,7 +959,7 @@ class ClassicChannel(utils.EventEmitter):
if self.connection_result: if self.connection_result:
self.connection_result.set_result(None) self.connection_result.set_result(None)
self.connection_result = None self.connection_result = None
self.emit('open') self.emit(self.EVENT_OPEN)
else: else:
logger.warning(color('invalid state', 'red')) logger.warning(color('invalid state', 'red'))
elif ( elif (
@@ -991,7 +994,7 @@ class ClassicChannel(utils.EventEmitter):
) )
) )
self._change_state(self.State.CLOSED) self._change_state(self.State.CLOSED)
self.emit('close') self.emit(self.EVENT_CLOSE)
self.manager.on_channel_closed(self) self.manager.on_channel_closed(self)
else: else:
logger.warning(color('invalid state', 'red')) logger.warning(color('invalid state', 'red'))
@@ -1012,7 +1015,7 @@ class ClassicChannel(utils.EventEmitter):
if self.disconnection_result: if self.disconnection_result:
self.disconnection_result.set_result(None) self.disconnection_result.set_result(None)
self.disconnection_result = None self.disconnection_result = None
self.emit('close') self.emit(self.EVENT_CLOSE)
self.manager.on_channel_closed(self) self.manager.on_channel_closed(self)
def __str__(self) -> str: def __str__(self) -> str:
@@ -1047,6 +1050,9 @@ class LeCreditBasedChannel(utils.EventEmitter):
connection: Connection connection: Connection
sink: Optional[Callable[[bytes], Any]] sink: Optional[Callable[[bytes], Any]]
EVENT_OPEN = "open"
EVENT_CLOSE = "close"
def __init__( def __init__(
self, self,
manager: ChannelManager, manager: ChannelManager,
@@ -1098,9 +1104,9 @@ class LeCreditBasedChannel(utils.EventEmitter):
self.state = new_state self.state = new_state
if new_state == self.State.CONNECTED: if new_state == self.State.CONNECTED:
self.emit('open') self.emit(self.EVENT_OPEN)
elif new_state == self.State.DISCONNECTED: elif new_state == self.State.DISCONNECTED:
self.emit('close') self.emit(self.EVENT_CLOSE)
def send_pdu(self, pdu: Union[SupportsBytes, bytes]) -> None: def send_pdu(self, pdu: Union[SupportsBytes, bytes]) -> None:
self.manager.send_pdu(self.connection, self.destination_cid, pdu) self.manager.send_pdu(self.connection, self.destination_cid, pdu)
@@ -1381,6 +1387,8 @@ class LeCreditBasedChannel(utils.EventEmitter):
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class ClassicChannelServer(utils.EventEmitter): class ClassicChannelServer(utils.EventEmitter):
EVENT_CONNECTION = "connection"
def __init__( def __init__(
self, self,
manager: ChannelManager, manager: ChannelManager,
@@ -1395,7 +1403,7 @@ class ClassicChannelServer(utils.EventEmitter):
self.mtu = mtu self.mtu = mtu
def on_connection(self, channel: ClassicChannel) -> None: def on_connection(self, channel: ClassicChannel) -> None:
self.emit('connection', channel) self.emit(self.EVENT_CONNECTION, channel)
if self.handler: if self.handler:
self.handler(channel) self.handler(channel)
@@ -1406,6 +1414,8 @@ class ClassicChannelServer(utils.EventEmitter):
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class LeCreditBasedChannelServer(utils.EventEmitter): class LeCreditBasedChannelServer(utils.EventEmitter):
EVENT_CONNECTION = "connection"
def __init__( def __init__(
self, self,
manager: ChannelManager, manager: ChannelManager,
@@ -1424,7 +1434,7 @@ class LeCreditBasedChannelServer(utils.EventEmitter):
self.mps = mps self.mps = mps
def on_connection(self, channel: LeCreditBasedChannel) -> None: def on_connection(self, channel: LeCreditBasedChannel) -> None:
self.emit('connection', channel) self.emit(self.EVENT_CONNECTION, channel)
if self.handler: if self.handler:
self.handler(channel) self.handler(channel)

View File

@@ -296,12 +296,12 @@ class HostService(HostServicer):
def on_disconnection(_: None) -> None: def on_disconnection(_: None) -> None:
disconnection_future.set_result(None) disconnection_future.set_result(None)
connection.on('disconnection', on_disconnection) connection.on(connection.EVENT_DISCONNECTION, on_disconnection)
try: try:
await disconnection_future await disconnection_future
self.log.debug("Disconnected") self.log.debug("Disconnected")
finally: finally:
connection.remove_listener('disconnection', on_disconnection) # type: ignore connection.remove_listener(connection.EVENT_DISCONNECTION, on_disconnection) # type: ignore
return empty_pb2.Empty() return empty_pb2.Empty()
@@ -383,7 +383,7 @@ class HostService(HostServicer):
): ):
connections.put_nowait(connection) connections.put_nowait(connection)
self.device.on('connection', on_connection) self.device.on(self.device.EVENT_CONNECTION, on_connection)
try: try:
# Advertise until RPC is canceled # Advertise until RPC is canceled
@@ -501,7 +501,7 @@ class HostService(HostServicer):
): ):
connections.put_nowait(connection) connections.put_nowait(connection)
self.device.on('connection', on_connection) self.device.on(self.device.EVENT_CONNECTION, on_connection)
try: try:
while True: while True:
@@ -531,7 +531,7 @@ class HostService(HostServicer):
await asyncio.sleep(1) await asyncio.sleep(1)
finally: finally:
if request.connectable: if request.connectable:
self.device.remove_listener('connection', on_connection) # type: ignore self.device.remove_listener(self.device.EVENT_CONNECTION, on_connection) # type: ignore
try: try:
self.log.debug('Stop advertising') self.log.debug('Stop advertising')
@@ -557,7 +557,7 @@ class HostService(HostServicer):
scanning_phys = [int(Phy.LE_1M), int(Phy.LE_CODED)] scanning_phys = [int(Phy.LE_1M), int(Phy.LE_CODED)]
scan_queue: asyncio.Queue[Advertisement] = asyncio.Queue() scan_queue: asyncio.Queue[Advertisement] = asyncio.Queue()
handler = self.device.on('advertisement', scan_queue.put_nowait) handler = self.device.on(self.device.EVENT_ADVERTISEMENT, scan_queue.put_nowait)
await self.device.start_scanning( await self.device.start_scanning(
legacy=request.legacy, legacy=request.legacy,
active=not request.passive, active=not request.passive,
@@ -602,7 +602,7 @@ class HostService(HostServicer):
yield sr yield sr
finally: finally:
self.device.remove_listener('advertisement', handler) # type: ignore self.device.remove_listener(self.device.EVENT_ADVERTISEMENT, handler) # type: ignore
try: try:
self.log.debug('Stop scanning') self.log.debug('Stop scanning')
await bumble.utils.cancel_on_event( await bumble.utils.cancel_on_event(
@@ -621,10 +621,10 @@ class HostService(HostServicer):
Optional[Tuple[Address, int, AdvertisingData, int]] Optional[Tuple[Address, int, AdvertisingData, int]]
] = asyncio.Queue() ] = asyncio.Queue()
complete_handler = self.device.on( complete_handler = self.device.on(
'inquiry_complete', lambda: inquiry_queue.put_nowait(None) self.device.EVENT_INQUIRY_COMPLETE, lambda: inquiry_queue.put_nowait(None)
) )
result_handler = self.device.on( # type: ignore result_handler = self.device.on( # type: ignore
'inquiry_result', self.device.EVENT_INQUIRY_RESULT,
lambda address, class_of_device, eir_data, rssi: inquiry_queue.put_nowait( # type: ignore lambda address, class_of_device, eir_data, rssi: inquiry_queue.put_nowait( # type: ignore
(address, class_of_device, eir_data, rssi) # type: ignore (address, class_of_device, eir_data, rssi) # type: ignore
), ),
@@ -643,8 +643,8 @@ class HostService(HostServicer):
) )
finally: finally:
self.device.remove_listener('inquiry_complete', complete_handler) # type: ignore self.device.remove_listener(self.device.EVENT_INQUIRY_COMPLETE, complete_handler) # type: ignore
self.device.remove_listener('inquiry_result', result_handler) # type: ignore self.device.remove_listener(self.device.EVENT_INQUIRY_RESULT, result_handler) # type: ignore
try: try:
self.log.debug('Stop inquiry') self.log.debug('Stop inquiry')
await bumble.utils.cancel_on_event( await bumble.utils.cancel_on_event(

View File

@@ -83,7 +83,7 @@ class L2CAPService(L2CAPServicer):
close_future.set_result(None) close_future.set_result(None)
l2cap_channel.sink = on_channel_sdu l2cap_channel.sink = on_channel_sdu
l2cap_channel.on('close', on_close) l2cap_channel.on(l2cap_channel.EVENT_CLOSE, on_close)
return ChannelContext(close_future, sdu_queue) return ChannelContext(close_future, sdu_queue)
@@ -151,7 +151,7 @@ class L2CAPService(L2CAPServicer):
spec=spec, handler=on_l2cap_channel spec=spec, handler=on_l2cap_channel
) )
else: else:
l2cap_server.on('connection', on_l2cap_channel) l2cap_server.on(l2cap_server.EVENT_CONNECTION, on_l2cap_channel)
try: try:
self.log.debug('Waiting for a channel connection.') self.log.debug('Waiting for a channel connection.')

View File

@@ -302,15 +302,15 @@ class SecurityService(SecurityServicer):
with contextlib.closing(bumble.utils.EventWatcher()) as watcher: with contextlib.closing(bumble.utils.EventWatcher()) as watcher:
@watcher.on(connection, 'pairing') @watcher.on(connection, connection.EVENT_PAIRING)
def on_pairing(*_: Any) -> None: def on_pairing(*_: Any) -> None:
security_result.set_result('success') security_result.set_result('success')
@watcher.on(connection, 'pairing_failure') @watcher.on(connection, connection.EVENT_PAIRING_FAILURE)
def on_pairing_failure(*_: Any) -> None: def on_pairing_failure(*_: Any) -> None:
security_result.set_result('pairing_failure') security_result.set_result('pairing_failure')
@watcher.on(connection, 'disconnection') @watcher.on(connection, connection.EVENT_DISCONNECTION)
def on_disconnection(*_: Any) -> None: def on_disconnection(*_: Any) -> None:
security_result.set_result('connection_died') security_result.set_result('connection_died')

View File

@@ -250,6 +250,8 @@ class AncsClient(utils.EventEmitter):
_expected_response_tuples: int _expected_response_tuples: int
_response_accumulator: bytes _response_accumulator: bytes
EVENT_NOTIFICATION = "notification"
def __init__(self, ancs_proxy: AncsProxy) -> None: def __init__(self, ancs_proxy: AncsProxy) -> None:
super().__init__() super().__init__()
self._ancs_proxy = ancs_proxy self._ancs_proxy = ancs_proxy
@@ -284,7 +286,7 @@ class AncsClient(utils.EventEmitter):
def _on_notification(self, notification: Notification) -> None: def _on_notification(self, notification: Notification) -> None:
logger.debug(f"ANCS NOTIFICATION: {notification}") logger.debug(f"ANCS NOTIFICATION: {notification}")
self.emit("notification", notification) self.emit(self.EVENT_NOTIFICATION, notification)
def _on_data(self, data: bytes) -> None: def _on_data(self, data: bytes) -> None:
logger.debug(f"ANCS DATA: {data.hex()}") logger.debug(f"ANCS DATA: {data.hex()}")

View File

@@ -276,6 +276,8 @@ class AseStateMachine(gatt.Characteristic):
DISABLING = 0x05 DISABLING = 0x05
RELEASING = 0x06 RELEASING = 0x06
EVENT_STATE_CHANGE = "state_change"
cis_link: Optional[device.CisLink] = None cis_link: Optional[device.CisLink] = None
# Additional parameters in CODEC_CONFIGURED State # Additional parameters in CODEC_CONFIGURED State
@@ -329,8 +331,12 @@ class AseStateMachine(gatt.Characteristic):
value=gatt.CharacteristicValue(read=self.on_read), value=gatt.CharacteristicValue(read=self.on_read),
) )
self.service.device.on('cis_request', self.on_cis_request) self.service.device.on(
self.service.device.on('cis_establishment', self.on_cis_establishment) self.service.device.EVENT_CIS_REQUEST, self.on_cis_request
)
self.service.device.on(
self.service.device.EVENT_CIS_ESTABLISHMENT, self.on_cis_establishment
)
def on_cis_request( def on_cis_request(
self, self,
@@ -356,7 +362,7 @@ class AseStateMachine(gatt.Characteristic):
and cis_link.cis_id == self.cis_id and cis_link.cis_id == self.cis_id
and self.state == self.State.ENABLING and self.state == self.State.ENABLING
): ):
cis_link.on('disconnection', self.on_cis_disconnection) cis_link.on(cis_link.EVENT_DISCONNECTION, self.on_cis_disconnection)
async def post_cis_established(): async def post_cis_established():
await cis_link.setup_data_path(direction=self.role) await cis_link.setup_data_path(direction=self.role)
@@ -525,7 +531,7 @@ class AseStateMachine(gatt.Characteristic):
def state(self, new_state: State) -> None: def state(self, new_state: State) -> None:
logger.debug(f'{self} state change -> {colors.color(new_state.name, "cyan")}') logger.debug(f'{self} state change -> {colors.color(new_state.name, "cyan")}')
self._state = new_state self._state = new_state
self.emit('state_change') self.emit(self.EVENT_STATE_CHANGE)
@property @property
def value(self): def value(self):

View File

@@ -88,6 +88,11 @@ class AudioStatus(utils.OpenIntEnum):
class AshaService(gatt.TemplateService): class AshaService(gatt.TemplateService):
UUID = gatt.GATT_ASHA_SERVICE UUID = gatt.GATT_ASHA_SERVICE
EVENT_STARTED = "started"
EVENT_STOPPED = "stopped"
EVENT_DISCONNECTED = "disconnected"
EVENT_VOLUME_CHANGED = "volume_changed"
audio_sink: Optional[Callable[[bytes], Any]] audio_sink: Optional[Callable[[bytes], Any]]
active_codec: Optional[Codec] = None active_codec: Optional[Codec] = None
audio_type: Optional[AudioType] = None audio_type: Optional[AudioType] = None
@@ -211,14 +216,14 @@ class AshaService(gatt.TemplateService):
f'volume={self.volume}, ' f'volume={self.volume}, '
f'other_state={self.other_state}' f'other_state={self.other_state}'
) )
self.emit('started') self.emit(self.EVENT_STARTED)
elif opcode == OpCode.STOP: elif opcode == OpCode.STOP:
_logger.debug('### STOP') _logger.debug('### STOP')
self.active_codec = None self.active_codec = None
self.audio_type = None self.audio_type = None
self.volume = None self.volume = None
self.other_state = None self.other_state = None
self.emit('stopped') self.emit(self.EVENT_STOPPED)
elif opcode == OpCode.STATUS: elif opcode == OpCode.STATUS:
_logger.debug('### STATUS: %s', PeripheralStatus(value[1]).name) _logger.debug('### STATUS: %s', PeripheralStatus(value[1]).name)
@@ -231,7 +236,7 @@ class AshaService(gatt.TemplateService):
self.audio_type = None self.audio_type = None
self.volume = None self.volume = None
self.other_state = None self.other_state = None
self.emit('disconnected') self.emit(self.EVENT_DISCONNECTED)
connection.once('disconnection', on_disconnection) connection.once('disconnection', on_disconnection)
@@ -245,7 +250,7 @@ class AshaService(gatt.TemplateService):
def _on_volume_write(self, connection: Optional[Connection], value: bytes) -> None: def _on_volume_write(self, connection: Optional[Connection], value: bytes) -> None:
_logger.debug(f'--- VOLUME Write:{value[0]}') _logger.debug(f'--- VOLUME Write:{value[0]}')
self.volume = value[0] self.volume = value[0]
self.emit('volume_changed') self.emit(self.EVENT_VOLUME_CHANGED)
# Register an L2CAP CoC server # Register an L2CAP CoC server
def _on_connection(self, channel: l2cap.LeCreditBasedChannel) -> None: def _on_connection(self, channel: l2cap.LeCreditBasedChannel) -> None:

View File

@@ -266,13 +266,13 @@ class HearingAccessService(gatt.TemplateService):
# associate the lowest index as the current active preset at startup # associate the lowest index as the current active preset at startup
self.active_preset_index = sorted(self.preset_records.keys())[0] self.active_preset_index = sorted(self.preset_records.keys())[0]
@device.on('connection') # type: ignore @device.on(device.EVENT_CONNECTION)
def on_connection(connection: Connection) -> None: def on_connection(connection: Connection) -> None:
@connection.on('disconnection') # type: ignore @connection.on(connection.EVENT_DISCONNECTION)
def on_disconnection(_reason) -> None: def on_disconnection(_reason) -> None:
self.currently_connected_clients.remove(connection) self.currently_connected_clients.remove(connection)
@connection.on('pairing') # type: ignore @connection.on(connection.EVENT_PAIRING)
def on_pairing(*_: Any) -> None: def on_pairing(*_: Any) -> None:
self.on_incoming_paired_connection(connection) self.on_incoming_paired_connection(connection)

View File

@@ -338,6 +338,12 @@ class MediaControlServiceProxy(
'content_control_id': gatt.GATT_CONTENT_CONTROL_ID_CHARACTERISTIC, 'content_control_id': gatt.GATT_CONTENT_CONTROL_ID_CHARACTERISTIC,
} }
EVENT_MEDIA_STATE = "media_state"
EVENT_TRACK_CHANGED = "track_changed"
EVENT_TRACK_TITLE = "track_title"
EVENT_TRACK_DURATION = "track_duration"
EVENT_TRACK_POSITION = "track_position"
media_player_name: Optional[gatt_client.CharacteristicProxy[bytes]] = None media_player_name: Optional[gatt_client.CharacteristicProxy[bytes]] = None
media_player_icon_object_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None media_player_icon_object_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None
media_player_icon_url: Optional[gatt_client.CharacteristicProxy[bytes]] = None media_player_icon_url: Optional[gatt_client.CharacteristicProxy[bytes]] = None
@@ -432,20 +438,20 @@ class MediaControlServiceProxy(
self.media_control_point_notifications.put_nowait(data) self.media_control_point_notifications.put_nowait(data)
def _on_media_state(self, data: bytes) -> None: def _on_media_state(self, data: bytes) -> None:
self.emit('media_state', MediaState(data[0])) self.emit(self.EVENT_MEDIA_STATE, MediaState(data[0]))
def _on_track_changed(self, data: bytes) -> None: def _on_track_changed(self, data: bytes) -> None:
del data del data
self.emit('track_changed') self.emit(self.EVENT_TRACK_CHANGED)
def _on_track_title(self, data: bytes) -> None: def _on_track_title(self, data: bytes) -> None:
self.emit('track_title', data.decode("utf-8")) self.emit(self.EVENT_TRACK_TITLE, data.decode("utf-8"))
def _on_track_duration(self, data: bytes) -> None: def _on_track_duration(self, data: bytes) -> None:
self.emit('track_duration', struct.unpack_from('<i', data)[0]) self.emit(self.EVENT_TRACK_DURATION, struct.unpack_from('<i', data)[0])
def _on_track_position(self, data: bytes) -> None: def _on_track_position(self, data: bytes) -> None:
self.emit('track_position', struct.unpack_from('<i', data)[0]) self.emit(self.EVENT_TRACK_POSITION, struct.unpack_from('<i', data)[0])
class GenericMediaControlServiceProxy(MediaControlServiceProxy): class GenericMediaControlServiceProxy(MediaControlServiceProxy):

View File

@@ -91,6 +91,8 @@ class VolumeState:
class VolumeControlService(gatt.TemplateService): class VolumeControlService(gatt.TemplateService):
UUID = gatt.GATT_VOLUME_CONTROL_SERVICE UUID = gatt.GATT_VOLUME_CONTROL_SERVICE
EVENT_VOLUME_STATE_CHANGE = "volume_state_change"
volume_state: gatt.Characteristic[bytes] volume_state: gatt.Characteristic[bytes]
volume_control_point: gatt.Characteristic[bytes] volume_control_point: gatt.Characteristic[bytes]
volume_flags: gatt.Characteristic[bytes] volume_flags: gatt.Characteristic[bytes]
@@ -166,7 +168,7 @@ class VolumeControlService(gatt.TemplateService):
'disconnection', 'disconnection',
connection.device.notify_subscribers(attribute=self.volume_state), connection.device.notify_subscribers(attribute=self.volume_state),
) )
self.emit('volume_state_change') self.emit(self.EVENT_VOLUME_STATE_CHANGE)
def _on_relative_volume_down(self) -> bool: def _on_relative_volume_down(self) -> bool:
old_volume = self.volume_setting old_volume = self.volume_setting

View File

@@ -442,6 +442,9 @@ class RFCOMM_MCC_MSC:
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class DLC(utils.EventEmitter): class DLC(utils.EventEmitter):
EVENT_OPEN = "open"
EVENT_CLOSE = "close"
class State(enum.IntEnum): class State(enum.IntEnum):
INIT = 0x00 INIT = 0x00
CONNECTING = 0x01 CONNECTING = 0x01
@@ -529,7 +532,7 @@ class DLC(utils.EventEmitter):
self.send_frame(RFCOMM_Frame.uih(c_r=self.c_r, dlci=0, information=mcc)) self.send_frame(RFCOMM_Frame.uih(c_r=self.c_r, dlci=0, information=mcc))
self.change_state(DLC.State.CONNECTED) self.change_state(DLC.State.CONNECTED)
self.emit('open') self.emit(self.EVENT_OPEN)
def on_ua_frame(self, _frame: RFCOMM_Frame) -> None: def on_ua_frame(self, _frame: RFCOMM_Frame) -> None:
if self.state == DLC.State.CONNECTING: if self.state == DLC.State.CONNECTING:
@@ -550,7 +553,7 @@ class DLC(utils.EventEmitter):
self.disconnection_result.set_result(None) self.disconnection_result.set_result(None)
self.disconnection_result = None self.disconnection_result = None
self.multiplexer.on_dlc_disconnection(self) self.multiplexer.on_dlc_disconnection(self)
self.emit('close') self.emit(self.EVENT_CLOSE)
else: else:
logger.warning( logger.warning(
color( color(
@@ -733,7 +736,7 @@ class DLC(utils.EventEmitter):
self.disconnection_result.cancel() self.disconnection_result.cancel()
self.disconnection_result = None self.disconnection_result = None
self.change_state(DLC.State.RESET) self.change_state(DLC.State.RESET)
self.emit('close') self.emit(self.EVENT_CLOSE)
def __str__(self) -> str: def __str__(self) -> str:
return ( return (
@@ -763,6 +766,8 @@ class Multiplexer(utils.EventEmitter):
DISCONNECTED = 0x05 DISCONNECTED = 0x05
RESET = 0x06 RESET = 0x06
EVENT_DLC = "dlc"
connection_result: Optional[asyncio.Future] connection_result: Optional[asyncio.Future]
disconnection_result: Optional[asyncio.Future] disconnection_result: Optional[asyncio.Future]
open_result: Optional[asyncio.Future] open_result: Optional[asyncio.Future]
@@ -785,7 +790,7 @@ class Multiplexer(utils.EventEmitter):
# Become a sink for the L2CAP channel # Become a sink for the L2CAP channel
l2cap_channel.sink = self.on_pdu l2cap_channel.sink = self.on_pdu
l2cap_channel.on('close', self.on_l2cap_channel_close) l2cap_channel.on(l2cap_channel.EVENT_CLOSE, self.on_l2cap_channel_close)
def change_state(self, new_state: State) -> None: def change_state(self, new_state: State) -> None:
logger.debug(f'{self} state change -> {color(new_state.name, "cyan")}') logger.debug(f'{self} state change -> {color(new_state.name, "cyan")}')
@@ -901,7 +906,7 @@ class Multiplexer(utils.EventEmitter):
self.dlcs[pn.dlci] = dlc self.dlcs[pn.dlci] = dlc
# Re-emit the handshake completion event # Re-emit the handshake completion event
dlc.on('open', lambda: self.emit('dlc', dlc)) dlc.on(dlc.EVENT_OPEN, lambda: self.emit(self.EVENT_DLC, dlc))
# Respond to complete the handshake # Respond to complete the handshake
dlc.accept() dlc.accept()
@@ -1076,6 +1081,8 @@ class Client:
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class Server(utils.EventEmitter): class Server(utils.EventEmitter):
EVENT_START = "start"
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:
@@ -1122,7 +1129,9 @@ class Server(utils.EventEmitter):
def on_connection(self, l2cap_channel: l2cap.ClassicChannel) -> None: def on_connection(self, l2cap_channel: l2cap.ClassicChannel) -> None:
logger.debug(f'+++ new L2CAP connection: {l2cap_channel}') logger.debug(f'+++ new L2CAP connection: {l2cap_channel}')
l2cap_channel.on('open', lambda: self.on_l2cap_channel_open(l2cap_channel)) l2cap_channel.on(
l2cap_channel.EVENT_OPEN, lambda: self.on_l2cap_channel_open(l2cap_channel)
)
def on_l2cap_channel_open(self, l2cap_channel: l2cap.ClassicChannel) -> None: def on_l2cap_channel_open(self, l2cap_channel: l2cap.ClassicChannel) -> None:
logger.debug(f'$$$ L2CAP channel open: {l2cap_channel}') logger.debug(f'$$$ L2CAP channel open: {l2cap_channel}')
@@ -1130,10 +1139,10 @@ class Server(utils.EventEmitter):
# Create a new multiplexer for the channel # Create a new multiplexer for the channel
multiplexer = Multiplexer(l2cap_channel, Multiplexer.Role.RESPONDER) multiplexer = Multiplexer(l2cap_channel, Multiplexer.Role.RESPONDER)
multiplexer.acceptor = self.accept_dlc multiplexer.acceptor = self.accept_dlc
multiplexer.on('dlc', self.on_dlc) multiplexer.on(multiplexer.EVENT_DLC, self.on_dlc)
# Notify # Notify
self.emit('start', multiplexer) self.emit(self.EVENT_START, multiplexer)
def accept_dlc(self, channel_number: int) -> Optional[Tuple[int, int]]: def accept_dlc(self, channel_number: int) -> Optional[Tuple[int, int]]:
return self.dlc_configs.get(channel_number) return self.dlc_configs.get(channel_number)

View File

@@ -724,12 +724,13 @@ class Session:
self.is_responder = not self.is_initiator self.is_responder = not self.is_initiator
# Listen for connection events # Listen for connection events
connection.on('disconnection', self.on_disconnection) connection.on(connection.EVENT_DISCONNECTION, self.on_disconnection)
connection.on( connection.on(
'connection_encryption_change', self.on_connection_encryption_change connection.EVENT_CONNECTION_ENCRYPTION_CHANGE,
self.on_connection_encryption_change,
) )
connection.on( connection.on(
'connection_encryption_key_refresh', connection.EVENT_CONNECTION_ENCRYPTION_KEY_REFRESH,
self.on_connection_encryption_key_refresh, self.on_connection_encryption_key_refresh,
) )
@@ -1310,12 +1311,15 @@ class Session:
) )
def on_disconnection(self, _: int) -> None: def on_disconnection(self, _: int) -> None:
self.connection.remove_listener('disconnection', self.on_disconnection)
self.connection.remove_listener( self.connection.remove_listener(
'connection_encryption_change', self.on_connection_encryption_change self.connection.EVENT_DISCONNECTION, self.on_disconnection
) )
self.connection.remove_listener( self.connection.remove_listener(
'connection_encryption_key_refresh', self.connection.EVENT_CONNECTION_ENCRYPTION_CHANGE,
self.on_connection_encryption_change,
)
self.connection.remove_listener(
self.connection.EVENT_CONNECTION_ENCRYPTION_KEY_REFRESH,
self.on_connection_encryption_key_refresh, self.on_connection_encryption_key_refresh,
) )
self.manager.on_session_end(self) self.manager.on_session_end(self)
@@ -1962,7 +1966,7 @@ class Manager(utils.EventEmitter):
def on_smp_security_request_command( def on_smp_security_request_command(
self, connection: Connection, request: SMP_Security_Request_Command self, connection: Connection, request: SMP_Security_Request_Command
) -> None: ) -> None:
connection.emit('security_request', request.auth_req) connection.emit(connection.EVENT_SECURITY_REQUEST, request.auth_req)
def on_smp_pdu(self, connection: Connection, pdu: bytes) -> None: def on_smp_pdu(self, connection: Connection, pdu: bytes) -> None:
# Parse the L2CAP payload into an SMP Command object # Parse the L2CAP payload into an SMP Command object