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

The overall class structure is shown in the diagram below. The Usb class creates a singleton instance of UsbDeviceRegistry. The device registry implementations are responsible for enumerating the USB devices, monitoring connected and disconnected devices and creating new UsbDevice instances.

For each supported operating system, a subclass of UsbDeviceRegistry exists. They contain operating system specific code and can only be created on the appropriate operating system as they bind to native functions. The UsbDeviceRegistry class contains the code common to the three registry implementations.

The device registry instance creates instances of UsbDevice. They will create instances of operating system specific class of the same operating system. UsbDeviceImpl contains the code common to the three USB device implementations. UsbDevice is the public interface exposed to the library users.

Class Diagram

UsbDeviceRegistry

The single UsbDeviceRegistry instance manages a global list of all connected USB devices. It initially enumerates all connected devices. It then monitors devices being connected and disconnected and updates the list accordingly.

All the work is done in a background thread. After the initial enumeration, the background thread is mainly waiting for the next event, triggered by connecting or disconnecting a USB device. The waiting mechanism is operating system specific. The background thread also calls the registered notification handlers.

During the initial enumeration, the triggering code has to wait until the background process has started and completed the enumeration. To synchronize these activities, a Condition is used.

Most of the registry code is in operating system specific subclasses as it mainly uses native APIs, and they considerably differ between operating systems.

UsbDevice

The implementations of UsbDevice are mostly operating system specific as well. Most code can be found in the specific subclasses. The intermediate class UsbDeviceImpl contains the common code, mainly related to descriptive information about the devices and its interfaces and endpoints.

Asynchronous Transfers

At the core, all transfers from and to endpoints are executed asynchronously. The EndpointInputStream and EndpointOutputStream returned by UsbDevice.openInputStream() and UsbDevice.openOutputStream() use multiple concurrent asynchronous transfers to achieve a high throughput. Blocking methods like UsbDevice.transferIn() and UsbDevice.transferOut() are blocking wrappers around the asynchronous transfers. Currently, no API is offered to use the asynchronous functions directly.

For the implementation of asynchronous transfers, a background task is used to handle the IO completion. Each operating system has a separate implementation without shared code and without a common superclass.

Class Diagram AsyncTask

When an asynchronous operation completes, a completion handler provided as part of the submitted request will be called. It is called from the background thread handling the transfer completion. It is up to the client to ensure that the completion handler only does minimal work as it would otherwise delay other asynchronous operations. Furthermore, it will need to handle it in multi-threading safe way. In EndpointInputStream and EndpointOutputStream, the completion handler just adds the completed transfer to a BlockingArrayQueue instance.