USBTransferThread

August 29, 2026 · View on GitHub

High performance data exchange via USB for Java, using a thread and the usb4java library.

This is the SensorsINI continuation of Luca Longinotti / iniVation USBTransferThread (BSD-2-Clause). Last upstream tag: 0.9.8 (December 2020). SensorsINI 0.9.9 catches submit failures in run() so they are not uncaught.

jAER uses a frozen jar from this project (jars/USBTransferThread-*.jar). Keep the li.longi.USBTransferThread package name so existing AEReaders keep compiling.

Purpose of this mirror

iniVation stopped at 0.9.8. jAER could not patch the jar, so AEReaders wrapped start/stop around it. The main reason for this GitHub continuation is those uncaught IllegalStateExceptions from failed LibUsb.submitTransfer (missing endpoint after hotplug, gone device, stall, I/O). They killed USBTransferThread without reaching the shutdown callback, so the viewer could show LIVE with a dead reader. 0.9.9 catches that in run(), records getStartFailure(), and exits so join() returns.

Build

Requires JDK 25 (same as jAER javac target 25) and Maven 3.9+.

mvn -DskipTests package

The jar is target/USBTransferThread-0.9.9.jar. Drop that file into jAER jars/ and point NetBeans file.reference.USBTransferThread-*.jar at it. mvn deploy is not required for jAER.

Usage

This library does not replace usb4java. usb4java already does device open, control transfers, and both sync and async I/O. The aim here is high-throughput streaming on bulk and interrupt endpoints: keep several transfers in flight (overlapped / pipelined async I/O) and run libusb’s event loop on a dedicated max-priority thread, so processing and the rest of the JVM do not stall the USB pipe.

usb4java still owns open/claim. USBTransferThread owns the streaming loop: allocate a pool of Transfers, submit them, handleEvents, resubmit after processTransfer, set the IN buffer limit to the bytes received, and cancel/free on interrupt(). A blocking LibUsb.bulkTransfer on the viewer thread cannot do that.

Open the device with usb4java (LibUsb.init, open handle, claim the interface). Implement RestrictedTransferCallback:

  • prepareTransfer — fill the buffer for OUT transfers (no-op for IN).
  • processTransfer — read status(), actualLength(), and buffer() after each completion. Successful IN transfers have the buffer limit set to the bytes received.

Construct USBTransferThread with the DeviceHandle, endpoint address, transfer type (LibUsb.TRANSFER_TYPE_BULK or TRANSFER_TYPE_INTERRUPT), and the callback. Optional arguments: buffer count and size (defaults 16 × 8192 bytes; pass 0 for a default), then optional setup / shutdown / exceptional-shutdown Runnables and a libusb Context. Call start(). If submit fails (LIBUSB_ERROR_NOT_FOUND and similar), the thread exits after deallocateTransfers; join() returns and getStartFailure() holds the IllegalStateException. Change pool size later with setBufferNumber / setBufferSize. Stop with interrupt() then join().

See src/test/java/li/longi/USBTransferThread/USBTransferThreadTest.java for a full IN bulk example. jAER AEReaders follow the same pattern.