Skip to content
Manuel Bl edited this page Feb 25, 2024 · 5 revisions

The API design was heavily influenced by the WebUSB Javascript API proposed by Google and implemented in Chrome.

Usb as the entry point

The single entry point is the class Usb. It lists all connected device, gets a specific device and provides notifications about connected and disconnected classes. The direct or indirect result of all methods is a UsbDevice instance.

All methods of Usb are static.

for (var device : Usb.getAllDevices()) {
    System.out.println(device);
}
Usb.setOnDeviceConnected((device) -> System.out.println("Connected: " + device.toString()));

UsbDevice for all USB operations

UsbDevice instances provide all the methods to communicate with a USB device.

Even though there are separate classes for USB interfaces and USB endpoints, they are only used to query information about them. The methods for claiming and releasing interfaces as well as for communication with endpoints are part of the UsbDevice class.

device.open();
device.claimInterface(0);

device.transferOut(1, data);

var newData = device.transferIn(2);

device.close();

This approach simplifies working with USB devices.

Any Usb method will always return the same UsbDevice instance for a connected USB device. This applies as long as the device remains connected. If it is unplugged and plugged in again, a new instance will be created and the old one – if held on to despite the device being disconnected – will no longer work.

Lifecycle of UsbDevice instances

Instances of UsbDevice are created when a USB device is plugged in, and all related resources are released when the device is disconnected. This background work is not started until the Usb class is accessed for the first time. At that time, instances for the already plugged in USB devices are created.

Thus, the lifecycle is managed automatically in the background, independent of the code using this library. For this reason, UsbDevice does not implement Closeable.

If code holds on to a UsbDevice instance, it can continue to access the descriptive information even though all operating system resource have been released. The instance will eventually be claimed by the garbage collector when the last reference to it is gone.

In order to communicate with the USB device, it must be opened, and when it is no longer used, it must be closed. This can be done multiple times with the same UsbDevice instance. The device does not need to be opened in order to get descriptive information such as the USB interfaces and their details, the endpoints and their details.

Before communicating with a specific endpoint, the endpoint's interface must be claimed. After use, it should be released. Closing the device will release all interfaces.

So the typical order is:

device.open();
device.claimInterface(0);

// communicate with endpoints...

device.releaseInterface(0); // optional
device.close();

The effect of opening the device and claiming an interface differs slightly between operating systems. One or the other will establish exclusive access to the USB device or to the USB interface and prevent other applications from opening the same device and/or claiming the same interface.

Endpoint number vs endpoint address

The USB standard uses a single byte to refer to an endpoint. The most significant bit of the byte indicates the direction of the communication:

Direction Official Term Bit (MSB)
Host to device OUT Not set
Device to host IN Set

This byte with the direction bit is usually called an endpoint address, while the endpoint number is the same number without the direction bit.

Endpoint Number Direction Endpoint Address
1 OUT 0x01
1 IN 0x81
2 OUT 0x02
2 IN 0x82

Endpoint 0 is always the device's control endpoint. The endpoint address is always 0x00, even for control requests with DATA IN phase.

A pair of endpoints with the same endpoint number but different direction do not need to be related in a way. They can have different transfer type and serve two entirely different purposes.

This library always uses the endpoint number and a direction, and not the endpoint address. The direction is usually implicitly given by the method, e.g. transferIn(int), sometimes as an argument, e.g. clearHalt(UsbDirection,int)

Integer data types

The public interface of this library uses the type int for all integer types, even if the valid range for a particular method or data structure would fit into an 8-bit byte or a 16-bit short.

This choice simplifies the use of the library, in particular since the 8-bit and 16-bit values in the USB standard are always unsigned types while byte and short in Java are signed types.

Using int, the library takes care of the required conversion from and to byte and short. In particular converting from byte or short to int is tricky if the value is supposed to be unsigned.