Skip to content
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

Implement asynchronous transfer functions #63

Open
walac opened this issue Sep 23, 2014 · 25 comments
Open

Implement asynchronous transfer functions #63

walac opened this issue Sep 23, 2014 · 25 comments

Comments

@walac
Copy link
Member

walac commented Sep 23, 2014

Implement asynchronous versions of the transfers functions.

@jrast
Copy link

jrast commented Feb 5, 2018

This is a very old issue, any updates on the state of this feature?

@Armavica
Copy link

I would like to help make this happen. Where do we start?

@SimplicityGuy
Copy link
Contributor

SimplicityGuy commented Sep 12, 2018 via email

@Armavica
Copy link

Armavica commented Sep 14, 2018

Ok. Just to check my understanding of what exactly we want to do, the end goal of this would be to implement the asyncio transfers/protocols for USB? For this, I need to

  1. Write the FFI allowing to call the async libusb functions from Python;
  2. Connect this to asyncio.

Is that correct?

@mrnuke
Copy link

mrnuke commented Apr 8, 2020

Hi, I'm from the future, in the year 2020. I believe I have a compelling use case for this. I have a device here (Agilent 82347A) that needs transfers submitted in a very specific way:

  1. Submit a BULK IN transfer
  2. Send a BULK OUT transfer with a commant to read data
  3. Receive data via a BULK IN endpoint

With the available API, the best we can do is:

  1. Submit and finish BULK OUT transfer
  2. Submit and receive BULK IN transfer

What can happen in this case is, the device processes the command in step (1), sees no active IN request, and discards the data. When the IN request arrives, there is no data to send back, and the transfer times out.

@jonasmalacofilho
Copy link
Member

Hi @mrnuke,

Do you think you can help me with this?

You could start by adding the additonal libusb async functions to the libusb1 backend.


I still don't have a complete picture of how that should be exposed on the higher level and backend-agnostic PyUSB layer. Also, @Armavica mentioned implementing Python's asyncio, which might indeed be a good idea.

Anyway, that can wait until we have the C FFI ready, and you could probably use it directly in pygpib until we figure out and implement the higher-level APIs.

@mrnuke
Copy link

mrnuke commented Apr 8, 2020

I was just looking into calling libusb_submit_transfer on top of pyusb, albeit in a hackier manner.

@mrnuke
Copy link

mrnuke commented Apr 9, 2020

HI @jonasmalacofilho ,

I tried to implement libusb_fill_bulk_transfer() here
mrnuke@ecbb0ad .
That's easy. What I'm having difficulty with is how to wait for a usb transfer to complete. We need to run libusb_event_loop() while also waiting for transfer(s). I have no clue how that works with asyncio.

Do you have any ideas?

A very stupid example is here

@mrnuke
Copy link

mrnuke commented Apr 10, 2020

I think the following async example is what I'd like to see:

async def do_usb_stuff():
	dev = usb.core.find(...)

	transfer1 = dev.submit_read(...)
	transfer2 = dev.submit_write(...)

	await transfer2.result()
	await transfer1.result()

To that extent, I have tried something like mrnuke@02521a0

Am I on the right path here?

@joernheissler
Copy link

I've got some code that I'd like to be async. Would be great if pyusb could be used with different event loops (asyncio, trio, curio, anyio, maybe more).

Code currently looks like

device.write(0x01, msg, timeout)
device.read(0x81, 0xffff, timeout)

I haven't looked much into it yet. But I think it should look like this:

await device.awrite(0x01, msg, timeout)
await device.aread(0x81, 0xffff, timeout)

Or from a higher level view:

await foo.transceive(msg)

While an operation is blocking/waiting, other stuff can happen. Event loops need some kind of event, e.g. a readable file descriptor.

In strace on a recent Linux system, a long running request currently looks like:

16:39:18.863508 openat(AT_FDCWD, "/dev/bus/usb/001/055", O_RDWR|O_CLOEXEC) = 9
...
16:39:18.926996 ioctl(9, USBDEVFS_SUBMITURB, 0xe905d0) = 0
16:39:18.927120 poll([{fd=6, events=POLLIN}, {fd=8, events=POLLIN}, {fd=9, events=POLLOUT}], 3, 60000) = 1 ([{fd=9, revents=POLLOUT}])
16:39:48.549257 ioctl(9, USBDEVFS_REAPURBNDELAY, 0x7fff4fe61eb0) = 0

I.e. when I understand it correctly, the usb file descriptor is polled and it will become writable (why not readable?!) when the request is finished.
This can be used to create a proper async interface in python.

I'll have a look at pyusb and libusb so see if those libs can be extended or if it's a pointless endeavour without a rewrite.

@joernheissler
Copy link

joernheissler commented Dec 2, 2020

Looks like libusb got the required functions:

There appears to be another python package which already includes the required bindings: https://github.com/karpierz/libusb
But it doesn't provide any higher level interface :-(

@mcuee
Copy link
Member

mcuee commented Feb 14, 2021

https://github.com/vpelletier/python-libusb1
This is another libusb bindings which provide async API.

@mcuee mcuee pinned this issue Jun 15, 2021
@mcuee mcuee added this to the pyusb 1.3.0 milestone Jun 15, 2021
@mcuee
Copy link
Member

mcuee commented Aug 2, 2021

Actually isochronous transfer is asynchronous in nature and the codes for isoc transfer are already inside pyusb libusb-1.0 backend.

So indeed the first step is to have the low level async I/O function for the other types of transfer.

As for high level API, I am not so sure.

@mcuee
Copy link
Member

mcuee commented Aug 2, 2021

Reference: #303 is a WIP implementation.

@jonasmalacofilho
Copy link
Member

@mcuee I don't see why this issue needs to be pinned. While we may want to work on this soon, my opinion is that issues should only be pinned for the benefit of users, and we don't have that many of them hitting this limitation.

@mcuee mcuee unpinned this issue Aug 24, 2021
@mcuee
Copy link
Member

mcuee commented Aug 24, 2021

Okay, un-pin it.

@rickwierenga
Copy link

Is there any update on this?

@mcuee
Copy link
Member

mcuee commented Jan 19, 2023

@rickwierenga

Maybe you want to give PR #303 a try to see if it works for you or not. It is only for the libusb-1.0 backend which is anyway the way to go for async transfer.

@rickwierenga
Copy link

I will!

@rickwierenga
Copy link

rickwierenga commented Jan 22, 2023

It worked perfectly in my test on an M1 Mac.

One comment is that I think a better API would be to directly return a future, so the dev.submit_read and dev.submit_write can be awaited directly, as the example by @joernheissler above:

await device.awrite(0x01, msg, timeout)
await device.aread(0x81, 0xffff, timeout)

This should be easy to implement by calling .result() on the fn call in the high level submit_read and submit_write methods. https://github.com/mrnuke/pyusb/blob/asyncio-usb/usb/core.py#L1048

As a reference, I think the websockets library has a really good api: https://websockets.readthedocs.io/en/5.0/intro.html#basic-example

@jonasmalacofilho
Copy link
Member

jonasmalacofilho commented Jan 22, 2023

@rickwierenga, thanks for the feedback! And I agree that the API you and joernheissler propose is more ergonomic, barring any unforeseen reason why the extra indirection is really needed.

I'll try to review and make any necessary adjustments to #303.

(@mcuee please remind me in one month if I still haven't gotten to it).

@adamcalhoon
Copy link

Have a reminder from me instead @jonasmalacofilho 😁 I have no skin in this game, but stumbled on this issue while trying to figure out in pyusb had async support.

@mcuee
Copy link
Member

mcuee commented May 20, 2023

Ah, I missed this one and forgot to remind @jonasmalacofilho.

@mcuee
Copy link
Member

mcuee commented May 20, 2023

BTW, I was actutally testing a few async feature enhancement for libusb language bindings recently.

There are also quite some discussions on hidapi async feature and language bindings like NodeJS and Rust.

@jonasmalacofilho
Copy link
Member

Thanks for the reminder, both of you. Unfortunately I was only able to look into #303 for a short while.

IIRC, I found some additional issues in how the libusb async API was being used, but I didn't get to point of actually writing the problems down, or how to solve them (otherwise I'd left comments on #303).

This issue is still (fairly high) on my to-do list, but it's unlikely I'll get to it for a couple of moths still (at least).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants