-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Blocking reads() from usb.core.Device #9226
Comments
thanks for submitting this, @todbot I'm trying to add button presses for the ESP32-S3 reverse TFT Feather's D0, D1, D2 buttons for things like UI and to send MIDI panic, while using the USB MIDI Host function on the attached MAX3421e Feather Wing |
@jedgarpark Are you using |
The problem is that code flow entirely blocks on .read(). If there’s no incoming data, the call to |
@dhalbert I was using debouncer, but didn't try keypad. I tried doing it with asyncio as well, but still was being blocked until a MIDI message came through. |
Debouncer will not try to read the switch until its async task runs. keypad will read the switch transition immediately and save it in the queue, but to read the queue, you still have to wait and not be blocked. |
@dhalbert ok, i've set it to use keypad. it now can show me a queued button press occurred the next time i play a MIDI note. not useable for how i'm planning to use buttons in this MIDI monitor project, but good to know keypad can queue up presses |
The Here's an example that should print out "doing UI things" every 0.1 seconds but blocks on import time
import board
import usb.core
import usb_host
import adafruit_usb_host_midi
usb_host.Port(board.MOSI, board.MISO) # GP3,4 on QTPy RP2040; GP16,17 on RP2040 USB Host Feather
print("Looking for midi device")
raw_midi = None
while raw_midi is None:
for device in usb.core.find(find_all=True):
try:
raw_midi = adafruit_usb_host_midi.MIDI(device)
print("Found vid/pid %04x/%04x" % (device.idVendor, device.idProduct),
device.manufacturer, device.product)
except ValueError:
continue
time.sleep(0.1)
last_time = 0
while True:
midi_bytes = raw_midi.read(1) # this blocks instead of returning 0 or None
if midi_bytes:
print(time.monotonic(), "midi:",["0x%02x" % b for b in midi_bytes])
if time.monotonic() - last_time > 0.1:
last_time = time.monotonic()
print(last_time, "doing UI things...") |
The read method is documented as having a timeout argument in milliseconds. Did y'all give that a try? https://docs.circuitpython.org/en/latest/shared-bindings/usb/core/index.html#usb.core.Device.read |
oh I see that the midi library is an intermediate here and probably can't provide the timeout argument. well, that complicates things. |
Also from the first post:
|
At least it looks like the timeout error should be possible to distinguish, it is of type USBTimeoutError, while all other errors are of type USBError. Both derive from OSError. It's true they don't have any relevant attributes, but an except clause should be able to catch them as different things, by having the except clause for the more specific USBTimeoutError first. |
I do agree it should just maybe just return instead of raising an error. |
fbofw I think this module strives for compatibility with pyusb |
Unfortunately, adding a try/except for Full gist here: https://gist.github.com/todbot/07a2ef16ec9ee83eb8980f8a128d660b |
Ah, I see. |
This may help with diagnosing adafruit#9226: it will tell us *which* site is unexpectedly failing after a first timed out transfer (which correctly raises USBTimeoutError)
The build in #9240 may be helpful for further diagnosis. I tried to mark each site in the source that might be throwing the USBError exception with a different string. so, what string is shown with the 2nd exception that "should have been" USBTimeoutError? |
Thanks. I tried the artifact from PR #9240 and the result is |
I think we'll need @hathach to look into this because we try to abort the pending transfer when we reach the timeout: circuitpython/shared-module/usb/core/Device.c Lines 201 to 204 in ca2a24e
This should allow the subsequent call to work ok. It may be weird because our transaction is being NAKed by the device each SOF. The USBTimeoutError is pyusb behavior it does here: |
ah, abort xfer isn't implemented on max3421 just yet, though it should work with pio-usb. Let me try to reproduce and double check with the rp2040 first. |
I believe the RP2040 PIO host does implement abort xfer. At least the above CircuitPython code works as expected on rp2040 and doesn’t throw the error. |
@todbot sorry, I misread your above statement with rp2040 as ""it does not work with pio-usb as well". PR hathach/tinyusb#2646 implements abort xfer for max3421e. It should work, please try it out to see if that work for you |
Hi @hathach, thanks for the patch! It fixes the issue so now To test, I make a local build of CircuitPython for I guess when CircuitPython updates TinyUSB next, this issue can be closed. |
One thing I can notice now though with the |
What happens with shorter timeouts? |
Any timeout less than 100ms results in a 100ms timeout. |
Does it work correctly on RP2-PIO USB host? The timeout code is the same (but background tasks or timekeeping could be messing us up.) |
Ignore my reported 100ms timeout error. The error was in the todbot (me), not the code. Both the RP2 PIO USB Host ( btw, when specifying the minimum read timeout (1 millisecond), PIO USB does take 1ms, but with the max3421e it takes 2-3 milliseconds on average. Not deal-breaking, but something I wanted to note. |
glad that works out, feel free to pin me whenever you need my help |
CircuitPython version
Code/REPL
Behavior
Code blocks on read(), only printing "this should print 10 times a second" when a byte is read.
Description
The code demonstrates how
device.read()
blocks infinitely, which is different from established behavior.The expected semantics of
device.read()
when using eitherbusio.UART
orusb_midi.PortIn
is that the read() does not noticably block if a smalltimeout
is given.(a timeout given explicitly in the constructor of
busio.UART
, a common practice, and implicitly in howusb_midi
works)But for the
usb.core.Device
that is returned and used inadafruit_usb_host_midi
, thedevice.read()
blocks forever. Usingasyncio
orselect
does not help with this.There is a non-standard
timeout
argument to theusb.core.Device.read()
and
usb.core.Device.write()
methods. However, when specifying a timeout on.read()
(by modifyingadafruit_usb_host_midi.MIDI.read())
, the .read() throwsan unattributed
usb.core.USBError
, instead of the expected return value of None or 0.Since the exception is unattributed, there's no wayt to distinguish a read timeout
from a device unplug or other USB error.
Additional information
Tested on USB Host Feather Wing and ESP32-S2 TFT Feather and ESP32-S3 Reverse TFT Feather.
Discovered by @jedgarpark so tagging him here too.
The text was updated successfully, but these errors were encountered: