forked from auracaster/bumble_mirror
better packet queue logic
This commit is contained in:
@@ -15,10 +15,10 @@
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Imports
|
# Imports
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
from __future__ import annotations
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
import collections
|
|
||||||
import ctypes
|
import ctypes
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
@@ -114,13 +114,17 @@ async def open_usb_transport(spec: str) -> Transport:
|
|||||||
self.device = device
|
self.device = device
|
||||||
self.acl_out = acl_out
|
self.acl_out = acl_out
|
||||||
self.acl_out_transfer = device.getTransfer()
|
self.acl_out_transfer = device.getTransfer()
|
||||||
self.packets = collections.deque() # Queue of packets waiting to be sent
|
self.acl_out_transfer_ready = asyncio.Semaphore(1)
|
||||||
|
self.packets: asyncio.Queue[bytes] = (
|
||||||
|
asyncio.Queue()
|
||||||
|
) # Queue of packets waiting to be sent
|
||||||
self.loop = asyncio.get_running_loop()
|
self.loop = asyncio.get_running_loop()
|
||||||
|
self.queue_task = None
|
||||||
self.cancel_done = self.loop.create_future()
|
self.cancel_done = self.loop.create_future()
|
||||||
self.closed = False
|
self.closed = False
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
pass
|
self.queue_task = asyncio.create_task(self.process_queue())
|
||||||
|
|
||||||
def on_packet(self, packet):
|
def on_packet(self, packet):
|
||||||
# Ignore packets if we're closed
|
# Ignore packets if we're closed
|
||||||
@@ -132,62 +136,64 @@ async def open_usb_transport(spec: str) -> Transport:
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Queue the packet
|
# Queue the packet
|
||||||
self.packets.append(packet)
|
self.packets.put_nowait(packet)
|
||||||
if len(self.packets) == 1:
|
|
||||||
# The queue was previously empty, re-prime the pump
|
|
||||||
self.process_queue()
|
|
||||||
|
|
||||||
def transfer_callback(self, transfer):
|
def transfer_callback(self, transfer):
|
||||||
|
self.acl_out_transfer_ready.release()
|
||||||
status = transfer.getStatus()
|
status = transfer.getStatus()
|
||||||
|
|
||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
if status == usb1.TRANSFER_COMPLETED:
|
if status == usb1.TRANSFER_CANCELLED:
|
||||||
self.loop.call_soon_threadsafe(self.on_packet_sent)
|
|
||||||
elif status == usb1.TRANSFER_CANCELLED:
|
|
||||||
self.loop.call_soon_threadsafe(self.cancel_done.set_result, None)
|
self.loop.call_soon_threadsafe(self.cancel_done.set_result, None)
|
||||||
else:
|
return
|
||||||
|
|
||||||
|
if status != usb1.TRANSFER_COMPLETED:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
color(f'!!! OUT transfer not completed: status={status}', 'red')
|
color(f'!!! OUT transfer not completed: status={status}', 'red')
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_packet_sent(self):
|
async def process_queue(self):
|
||||||
if self.packets:
|
while True:
|
||||||
self.packets.popleft()
|
# Wait for a packet to transfer.
|
||||||
self.process_queue()
|
packet = await self.packets.get()
|
||||||
|
|
||||||
def process_queue(self):
|
# Wait until we can start a transfer.
|
||||||
if len(self.packets) == 0:
|
await self.acl_out_transfer_ready.acquire()
|
||||||
return # Nothing to do
|
|
||||||
|
|
||||||
packet = self.packets[0]
|
# Transfer the packet.
|
||||||
packet_type = packet[0]
|
packet_type = packet[0]
|
||||||
if packet_type == hci.HCI_ACL_DATA_PACKET:
|
if packet_type == hci.HCI_ACL_DATA_PACKET:
|
||||||
self.acl_out_transfer.setBulk(
|
self.acl_out_transfer.setBulk(
|
||||||
self.acl_out, packet[1:], callback=self.transfer_callback
|
self.acl_out, packet[1:], callback=self.transfer_callback
|
||||||
)
|
)
|
||||||
self.acl_out_transfer.submit()
|
self.acl_out_transfer.submit()
|
||||||
elif packet_type == hci.HCI_COMMAND_PACKET:
|
elif packet_type == hci.HCI_COMMAND_PACKET:
|
||||||
self.acl_out_transfer.setControl(
|
self.acl_out_transfer.setControl(
|
||||||
USB_RECIPIENT_DEVICE | USB_REQUEST_TYPE_CLASS,
|
USB_RECIPIENT_DEVICE | USB_REQUEST_TYPE_CLASS,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
packet[1:],
|
packet[1:],
|
||||||
callback=self.transfer_callback,
|
callback=self.transfer_callback,
|
||||||
)
|
)
|
||||||
self.acl_out_transfer.submit()
|
self.acl_out_transfer.submit()
|
||||||
else:
|
else:
|
||||||
logger.warning(color(f'unsupported packet type {packet_type}', 'red'))
|
logger.warning(
|
||||||
|
color(f'unsupported packet type {packet_type}', 'red')
|
||||||
|
)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self.closed = True
|
self.closed = True
|
||||||
|
if self.queue_task:
|
||||||
|
self.queue_task.cancel()
|
||||||
|
|
||||||
async def terminate(self):
|
async def terminate(self):
|
||||||
if not self.closed:
|
if not self.closed:
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
# Empty the packet queue so that we don't send any more data
|
# Empty the packet queue so that we don't send any more data
|
||||||
self.packets.clear()
|
while not self.packets.empty():
|
||||||
|
self.packets.get_nowait()
|
||||||
|
|
||||||
# If we have a transfer in flight, cancel it
|
# If we have a transfer in flight, cancel it
|
||||||
if self.acl_out_transfer.isSubmitted():
|
if self.acl_out_transfer.isSubmitted():
|
||||||
|
|||||||
Reference in New Issue
Block a user