Declare emitted events as constants

This commit is contained in:
Josh Wu
2025-04-22 20:14:46 +08:00
parent 1bd83273e8
commit 22d6a7bf05
22 changed files with 535 additions and 278 deletions

View File

@@ -896,7 +896,7 @@ class Set_Configuration_Reject(Message):
self.service_category = self.payload[0]
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]))
self.service_category = service_category
self.error_code = error_code
@@ -1132,6 +1132,14 @@ class Security_Control_Command(Message):
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
@@ -1200,6 +1208,9 @@ class Protocol(utils.EventEmitter):
transaction_results: List[Optional[asyncio.Future[Message]]]
channel_connector: Callable[[], Awaitable[l2cap.ClassicChannel]]
EVENT_OPEN = "open"
EVENT_CLOSE = "close"
class PacketType(enum.IntEnum):
SINGLE_PACKET = 0
START_PACKET = 1
@@ -1239,8 +1250,8 @@ class Protocol(utils.EventEmitter):
# Register to receive PDUs from the channel
l2cap_channel.sink = self.on_pdu
l2cap_channel.on('open', self.on_l2cap_channel_open)
l2cap_channel.on('close', self.on_l2cap_channel_close)
l2cap_channel.on(l2cap_channel.EVENT_OPEN, self.on_l2cap_channel_open)
l2cap_channel.on(l2cap_channel.EVENT_CLOSE, self.on_l2cap_channel_close)
def get_local_endpoint_by_seid(self, seid: int) -> Optional[LocalStreamEndPoint]:
if 0 < seid <= len(self.local_endpoints):
@@ -1410,20 +1421,20 @@ class Protocol(utils.EventEmitter):
self.transaction_results[transaction_label] = None
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
if self.channel_acceptor is None:
logger.warning(color('!!! l2cap connection with no acceptor', 'red'))
return
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'))
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'))
self.emit('close')
self.emit(self.EVENT_CLOSE)
def send_message(self, transaction_label: int, message: Message) -> None:
logger.debug(
@@ -1541,28 +1552,34 @@ class Protocol(utils.EventEmitter):
async def abort(self, seid: int) -> Abort_Response:
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 = [
EndPointInfo(endpoint.seid, 0, endpoint.media_type, endpoint.tsep)
for endpoint in self.local_endpoints
]
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)
if endpoint is None:
return Get_Capabilities_Reject(AVDTP_BAD_ACP_SEID_ERROR)
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)
if endpoint is None:
return Get_All_Capabilities_Reject(AVDTP_BAD_ACP_SEID_ERROR)
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)
if endpoint is None:
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)
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)
if endpoint is None:
return Get_Configuration_Reject(AVDTP_BAD_ACP_SEID_ERROR)
@@ -1587,7 +1606,7 @@ class Protocol(utils.EventEmitter):
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)
if endpoint is None:
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)
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)
if endpoint is None:
return Open_Reject(AVDTP_BAD_ACP_SEID_ERROR)
@@ -1607,25 +1626,26 @@ class Protocol(utils.EventEmitter):
result = endpoint.stream.on_open_command()
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:
endpoint = self.get_local_endpoint_by_seid(seid)
if endpoint is None:
return Start_Reject(seid, AVDTP_BAD_ACP_SEID_ERROR)
if endpoint.stream is None:
return Start_Reject(AVDTP_BAD_STATE_ERROR)
return Start_Reject(seid, AVDTP_BAD_STATE_ERROR)
# Start all streams
# TODO: deal with partial failures
for seid in command.acp_seids:
endpoint = self.get_local_endpoint_by_seid(seid)
result = endpoint.stream.on_start_command()
if result is not None:
if not endpoint or not endpoint.stream:
raise InvalidStateError("Should already be checked!")
if (result := endpoint.stream.on_start_command()) is not None:
return result
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:
endpoint = self.get_local_endpoint_by_seid(seid)
if endpoint is None:
@@ -1637,13 +1657,14 @@ class Protocol(utils.EventEmitter):
# TODO: deal with partial failures
for seid in command.acp_seids:
endpoint = self.get_local_endpoint_by_seid(seid)
result = endpoint.stream.on_suspend_command()
if result is not None:
if not endpoint or not endpoint.stream:
raise InvalidStateError("Should already be checked!")
if (result := endpoint.stream.on_suspend_command()) is not None:
return result
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)
if endpoint is None:
return Close_Reject(AVDTP_BAD_ACP_SEID_ERROR)
@@ -1653,7 +1674,7 @@ class Protocol(utils.EventEmitter):
result = endpoint.stream.on_close_command()
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)
if endpoint is None or endpoint.stream is None:
return Abort_Response()
@@ -1661,15 +1682,17 @@ class Protocol(utils.EventEmitter):
endpoint.stream.on_abort_command()
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)
if endpoint is None:
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()
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)
if endpoint is None:
return DelayReport_Reject(AVDTP_BAD_ACP_SEID_ERROR)
@@ -1682,6 +1705,8 @@ class Protocol(utils.EventEmitter):
class Listener(utils.EventEmitter):
servers: Dict[int, Protocol]
EVENT_CONNECTION = "connection"
@staticmethod
def create_registrar(device: device.Device):
warnings.warn("Please use Listener.for_device()", DeprecationWarning)
@@ -1716,7 +1741,7 @@ class Listener(utils.EventEmitter):
l2cap_server = device.create_l2cap_server(
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
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')
server = Protocol(channel, self.version)
self.set_server(channel.connection, server)
self.emit('connection', server)
self.emit(self.EVENT_CONNECTION, server)
def on_channel_close():
logger.debug('removing Protocol for the connection')
self.remove_server(channel.connection)
channel.on('open', on_channel_open)
channel.on('close', on_channel_close)
channel.on(channel.EVENT_OPEN, on_channel_open)
channel.on(channel.EVENT_CLOSE, on_channel_close)
# -----------------------------------------------------------------------------
@@ -1788,6 +1813,7 @@ class Stream:
)
async def start(self) -> None:
"""[Source] Start streaming."""
# Auto-open if needed
if self.state == AVDTP_CONFIGURED_STATE:
await self.open()
@@ -1804,6 +1830,7 @@ class Stream:
self.change_state(AVDTP_STREAMING_STATE)
async def stop(self) -> None:
"""[Source] Stop streaming and transit to OPEN state."""
if self.state != AVDTP_STREAMING_STATE:
raise InvalidStateError('current state is not STREAMING')
@@ -1816,6 +1843,7 @@ class Stream:
self.change_state(AVDTP_OPEN_STATE)
async def close(self) -> None:
"""[Source] Close channel and transit to IDLE state."""
if self.state not in (AVDTP_OPEN_STATE, AVDTP_STREAMING_STATE):
raise InvalidStateError('current state is not OPEN or STREAMING')
@@ -1847,7 +1875,7 @@ class Stream:
self.change_state(AVDTP_CONFIGURED_STATE)
return None
def on_get_configuration_command(self, configuration):
def on_get_configuration_command(self):
if self.state not in (
AVDTP_CONFIGURED_STATE,
AVDTP_OPEN_STATE,
@@ -1855,7 +1883,7 @@ class Stream:
):
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):
if self.state != AVDTP_OPEN_STATE:
@@ -1935,20 +1963,20 @@ class Stream:
# Wait for the RTP channel to be closed
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'))
self.rtp_channel = channel
channel.on('open', self.on_l2cap_channel_open)
channel.on('close', self.on_l2cap_channel_close)
channel.on(channel.EVENT_OPEN, self.on_l2cap_channel_open)
channel.on(channel.EVENT_CLOSE, self.on_l2cap_channel_close)
# We don't need more channels
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'))
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'))
self.local_endpoint.on_rtp_channel_close()
self.local_endpoint.in_use = 0
@@ -2065,6 +2093,19 @@ class DiscoveredStreamEndPoint(StreamEndPoint, StreamEndPointProxy):
class LocalStreamEndPoint(StreamEndPoint, utils.EventEmitter):
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__(
self,
protocol: Protocol,
@@ -2080,52 +2121,65 @@ class LocalStreamEndPoint(StreamEndPoint, utils.EventEmitter):
self.configuration = configuration if configuration is not None else []
self.stream = None
async def start(self):
pass
async def start(self) -> None:
"""[Source Only] Handles when receiving start command."""
async def stop(self):
pass
async def stop(self) -> None:
"""[Source Only] Handles when receiving stop command."""
async def close(self):
pass
async def close(self) -> None:
"""[Source Only] Handles when receiving close command."""
def on_reconfigure_command(self, command):
pass
def on_reconfigure_command(self, command) -> Optional[Message]:
return None
def on_set_configuration_command(self, configuration):
def on_set_configuration_command(self, configuration) -> Optional[Message]:
logger.debug(
'<<< received configuration: '
f'{",".join([str(capability) for capability in 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)
def on_open_command(self):
self.emit('open')
def on_open_command(self) -> Optional[Message]:
self.emit(self.EVENT_OPEN)
return None
def on_start_command(self):
self.emit('start')
def on_start_command(self) -> Optional[Message]:
self.emit(self.EVENT_START)
return None
def on_suspend_command(self):
self.emit('suspend')
def on_suspend_command(self) -> Optional[Message]:
self.emit(self.EVENT_SUSPEND)
return None
def on_close_command(self):
self.emit('close')
def on_close_command(self) -> Optional[Message]:
self.emit(self.EVENT_CLOSE)
return None
def on_abort_command(self):
self.emit('abort')
def on_abort_command(self) -> Optional[Message]:
self.emit(self.EVENT_ABORT)
return None
def on_delayreport_command(self, delay: int):
self.emit('delay_report', delay)
def on_delayreport_command(self, delay: int) -> Optional[Message]:
self.emit(self.EVENT_DELAY_REPORT, delay)
return None
def on_rtp_channel_open(self):
self.emit('rtp_channel_open')
def on_security_control_command(self, data: bytes) -> Optional[Message]:
self.emit(self.EVENT_SECURITY_CONTROL, data)
return None
def on_rtp_channel_close(self):
self.emit('rtp_channel_close')
def on_rtp_channel_open(self) -> None:
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:
return await self.packet_pump.start(self.stream.rtp_channel)
self.emit('start')
self.emit(self.EVENT_START)
async def stop(self) -> None:
if self.packet_pump:
return await self.packet_pump.stop()
self.emit('stop')
self.emit(self.EVENT_STOP)
def on_start_command(self):
asyncio.create_task(self.start())
@@ -2203,4 +2257,4 @@ class LocalSink(LocalStreamEndPoint):
f'{color("<<< RTP Packet:", "green")} '
f'{rtp_packet} {rtp_packet.payload[:16].hex()}'
)
self.emit('rtp_packet', rtp_packet)
self.emit(self.EVENT_RTP_PACKET, rtp_packet)