Improve documentation

This commit is contained in:
Klaus Reimer 2014-03-18 22:21:32 +01:00
parent c84c5fad92
commit 6a5da7a004
6 changed files with 562 additions and 213 deletions

View File

@ -1,206 +0,0 @@
-----------------------------------------------------------------------------
Quick start
-----------------------------------------------------------------------------
Quick start
* Choose an API
usb4java provides two different APIs you can work with:
* The low-level (libusb) API
* The high-level (javax.usb) API
[]
The high-level API simply implements the
{{{http://javax-usb.sourceforge.net/}javax.usb (JSR80) API}}. One advantage
of this API is that it is implementation-independent. So it is easy to
switch to a different javax.usb implementation later without changing your
code. Another advantage is that this API is object oriented and is much
easier to use for Java developers. The disadvantage is that the javax.usb
project is pretty old and currently looks inactive, maybe even dead.
The low-level API closely follows the C API of the
{{{http://libusb.info/}libusb}} project. This API has the advantage that it
provides the same functionality as libusb does. And if you know the C API of
libusb then you will most likely feel right at home when using this API
with usb4java. The disadvantage is that you will have a hard time changing
your code when you later switch to a different Java USB library.
* The low-level (libusb) API
** API design
The low-level API of usb4java closely follows the C API of the
{{{http://libusb.info/}libusb}} project. All global functions and
constants of <libusb> are defined as static members of the class
{{{./apidocs/org/usb4java/LibUsb.html}org.usb4java.LibUsb}}.
All structures of <libusb> are defined in separate classes which are named
similar to the original struct names but without underscores, with upper
case names and with the <libusb> prefix removed. For example the struct
<libusb_device_handle> is defined in the class
{{{./apidocs/org/usb4java/DeviceHandle.html}DeviceHandle}}. Struct
members are represented by static methods in the corresponding class.
The following notable differences exists between the <libusb 1.0 API> and
the <usb4java> API:
* <interface> in the configuration descriptor is named <iface> because
<interface> is a reserved word in Java.
* <MaxPower> in the configuration descriptor is named <bMaxPower> to
be compatible to the USB specification and because method names starting
with upper-case letters are quite unusual in Java.
* Whenever libusb expects a byte pointer and a length you have to use
a direct Java NIO ByteBuffer instead.
* Methods which are returning a string through a byte buffer which was
passed as argument have additional simplified overloaded method
equivalents which are returning a Java String directly.
[]
** Initialization/deinitialization
Before using any usb4java functionality you must initialize libusb:
----
final Context context = new Context();
int result = LibUsb.init(context);
if (result < 0) throw new RuntimeException("Unable to initialize libusb. Result=" + result);
----
Specifiying a context is optional. If your application only needs a single
libusb context then you can specify <null> as context.
Before your application terminates you should deinitialize libusb:
----
LibUsb.exit(context);
----
** Find your device
Your program most likely wants to communicate with a specific device so first
of all you have to find it. You have to get a list of all connected USB
devices and then check the vendor/product ids. Here is a method which can
be used for this purpose:
----
public Device findDevice(short vendorId, short productId)
{
// Read the USB device list
DeviceList list = new DeviceList();
int result = LibUsb.getDeviceList(null, list);
if (result < 0) throw new LibUsbException("Unable to get device list", result);
try
{
// Iterate over all devices and scan for the right one
for (Device device: list)
{
DeviceDescriptor descriptor = new DeviceDescriptor();
result = LibUsb.getDeviceDescriptor(device, descriptor);
if (result < 0) throw new LibUsbException("Unable to read device descriptor", result);
if (descriptor.idVendor() == vendorId && descriptor.idProduct() == productId) return device;
}
}
finally
{
// Ensure the allocated device list is freed
LibUsb.freeDeviceList(list, true);
}
// Device not found
return null;
}
----
In your application it might be a little bit more complicated. Maybe you
have more than one device of the same type so you may need a list of devices.
Or you have to identify your device by the product or vendor string
descriptor instead of just checking the ID (In case you are using a
shared vendor/product ID). But this example should bring you on the right
track.
** Device handles
For the real USB communication you must open a new device handle and you
must close it again when you are finished communicating with the device.
Example:
----
DeviceHandle handle = new DeviceHandle();
int result = LibUsb.open(device, handle);
if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to open USB device", result);
try
{
// Use device handle here
}
finally
{
LibUsb.close(handle);
}
----
** Interfaces
Usually you are communicating with an interface provided by the USB device and
you have to claim this interface before using it and you have to release it
when you are finished. Example:
----
int result = LibUsb.claimInterface(handle, interfaceNumber);
if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to claim interface", result);
try
{
// Use interface here
}
finally
{
result = LibUsb.releaseInterface(handle, interfaceNumber);
if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to release interface", result);
}
----
** Communication
For the actual USB communication you usually have to create a direct
byte buffer for the data to send or receive. Here is an example which
sends 8 bytes to a claimed interface unsing a control transfer:
----
ByteBuffer buffer = ByteBuffer.allocateDirect(8);
buffer.put(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 });
int transfered = LibUsb.controlTransfer(handle,
(byte) (LibUsb.REQUEST_TYPE_CLASS | LibUsb.RECIPIENT_INTERFACE),
(byte) 0x09, (short) 2, (short) 1, buffer, 2500);
if (transfered < 0)
throw new LibUsbException("Control transfer failed", transfered);
if (transfered != message.length)
throw new RuntimeException("Not all data was sent to device");
----
You may also want to use
{{{./apidocs/org/usb4java/LibUsb.html##bulkTransfer(org.usb4java.DeviceHandle, byte, java.nio.ByteBuffer, java.nio.IntBuffer, long)}bulkTransfer}} or
{{{./apidocs/org/usb4java/LibUsb.html##interruptTransfer(org.usb4java.DeviceHandle, byte, java.nio.ByteBuffer, java.nio.IntBuffer, long)}interruptTransfer}}
instead of
{{{./apidocs/org/usb4java/LibUsb.html##controlTransfer(org.usb4java.DeviceHandle, byte, byte, short, short, java.nio.ByteBuffer, long)}controlTransfer}}
The parameters needed for the transfer calls are completely
device dependent so you have to check the device documentation for details.
* See also
* {{{./apidocs/org/usb4java/package-summary.html}API documentation of usb4java}}
* {{{http://javax-usb.sourceforge.net/jdoc/}javax.usb (JSR80) API documentation}}
* {{{http://libusb.sourceforge.net/api-1.0/}API documentation of libusb}}
[]

View File

@ -0,0 +1,37 @@
-----------------------------------------------------------------------------
Quick start
-----------------------------------------------------------------------------
Quick start
* Choose an API
usb4java provides two different APIs you can choose from:
* {{{./libusb.html}The low-level (libusb) API}}
* {{{./javax-usb.html}The high-level (javax-usb) API}}
[]
The low-level API closely follows the C API of the
{{{http://libusb.info/}libusb}} project. This API has the advantage that it
provides the same functionality as libusb does. And if you know the C API of
libusb then you will most likely feel right at home when using this API
with usb4java. It is also easy to convert existing C libusb programs into
Java. The disadvantage is that you will have a hard time changing
your code when you later switch to a different Java USB library. And as a
pure Java developer you may dislike the API because it is too low-level (For
example most methods return error codes instead of throwing exceptions).
The high-level API simply implements the
{{{http://javax-usb.sourceforge.net/}javax-usb (JSR80) API}}. One advantage
of this API is that it is implementation-independent. So it is easy to
switch to a different javax-usb implementation later without changing your
code. Another advantage is that this API is object oriented and is much
easier to use for Java developers. The disadvantage is that the javax-usb
specification is pretty old and may lack support for some newer USB
techniques provided by the low-level API.

View File

@ -0,0 +1,188 @@
-----------------------------------------------------------------------------
High-level (javax-usb) API
-----------------------------------------------------------------------------
High-level (javax-usb) API
The high-level API implements the
{{{http://javax-usb.sourceforge.net/}javax-usb (JSR-80)}} standard. This API
is object-oriented, event-driven and uses exceptions for error-handling
instead of negative return values like the low-level API. Another advantage
is that you may switch to a different <javax-usb> implementation later
without changing your code. For example instead of using <usb4java> you may
try out the reference implementation for Linux and Windows.
* Configuration
To use the <usb4java> implementation you have to create a file named
<{{{./configuration.html}javax.usb.properties}}> in the root of your class
path with the following content:
+-----------------------------------------------------------------------------+
javax.usb.services = org.usb4java.javax.Services
+-----------------------------------------------------------------------------+
* Finding USB devices
USB devices are managed in a tree. The root of this tree is a virtual
USB hub to which all physical root hubs are connected. More hubs can be
connected to these root hubs and any hub can have a number of connected
USB devices.
Often you need to search for a specific device before working with it. Here
is an example how to scan the device tree for the first device with a
specific vendor and product id. It can be easily expanded to check for
specific device classes or whatever:
+-----------------------------------------------------------------------------+
public UsbDevice findDevice(UsbHub hub, short vendorId, short productId)
{
for (UsbDevice device : (List<UsbDevice>) hub.getAttachedUsbDevices())
{
UsbDeviceDescriptor desc = device.getUsbDeviceDescriptor();
if (desc.idVendor() == vendorId && desc.idProduct() == productId) return device;
if (device.isUsbHub())
{
device = findDevice((UsbHub) device, vendorId, productId);
if (device != null) return device;
}
}
return null;
}
+-----------------------------------------------------------------------------+
* Control requests
This example reads the current configuration number from a device by
using a control request:
+-----------------------------------------------------------------------------+
UsbControlIrp irp = device.createUsbControlIrp(
(byte) (UsbConst.REQUESTTYPE_DIRECTION_IN
| UsbConst.REQUESTTYPE_TYPE_STANDARD
| UsbConst.REQUESTTYPE_RECIPIENT_DEVICE),
UsbConst.REQUEST_GET_CONFIGURATION,
(short) 0,
(short) 0
);
irp.setData(new byte[1]);
device.syncSubmit(irp);
System.out.println(irp.getData()[0]);
+-----------------------------------------------------------------------------+
* Interfaces
When you want to communicate with an interface or with endpoints of this
interface then you have to claim it before using it and you have to
release it when you are finished. Example:
----
UsbConfiguration configuration = device.getActiveUsbConfiguration();
UsbInterface iface = configuration.getUsbInterface((byte) 1);
iface.claim();
try
{
... Communicate with the interface or endpoints ...
}
finally
{
iface.release();
}
----
It is possible that the interface you want to communicate with is already
used by a kernel driver. In this case you can try to force the claiming by
passing an interface policy to the <<<claim>>> method:
----
iface.claim(new UsbInterfacePolicy()
{
@Override
public boolean forceClaim(UsbInterface usbInterface)
{
return true;
}
});
----
Please note that interface policies are just a hint for the underlying USB
implementation. In case of <usb4java> the policy will be ignored on Windows
because <libusb> doesn't support detaching drivers on Windows.
* Synchronous I/O
This example sends 8 bytes to endpoint 0x03:
----
UsbEndpoint endpoint = iface.getUsbEndpoint(0x03);
UsbPipe pipe = endpoint.getUsbPipe();
pipe.open();
try
{
int sent = pipe.syncSubmit(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 });
System.out.println(sent + " bytes sent");
}
finally
{
pipe.close();
}
----
This example reads 8 bytes from endpoint 0x83:
----
UsbEndpoint endpoint = iface.getUsbEndpoint((byte) 0x83);
UsbPipe pipe = endpoint.getUsbPipe();
pipe.open();
try
{
byte[] data = new byte[8];
int received = pipe.syncSubmit(data);
System.out.println(received + " bytes received");
}
finally
{
pipe.close();
}
----
* Asynchronous I/O
Asynchronous I/O works pretty much the same as synchronous I/O. You just
use the <<<asyncSubmit>>> methods instead of the <<<syncSubmit>>> methods.
While <<<syncSubmit>>> blocks until the request is complete
<<<asyncSubmit>>> does not block and return immediately. To
receive the response you have to add a listener to the pipe like this:
----
pipe.addUsbPipeListener(new UsbPipeListener()
{
@Override
public void errorEventOccurred(UsbPipeErrorEvent event)
{
UsbException error = event.getUsbException();
... Handle error ...
}
@Override
public void dataEventOccurred(UsbPipeDataEvent event)
{
byte[] data = event.getData();
... Process received data ...
}
});
----
* See also
* {{{../usb4java-javax/apidocs/index.html}API documentation of usb4java-javax}}
* {{{https://github.com/usb4java/usb4java-javax-examples/}usb4java-javax examples}}
* {{{http://javax-usb.sourceforge.net/}javax-usb website}}
[]

View File

@ -0,0 +1,327 @@
-----------------------------------------------------------------------------
Low-level (libusb) API
-----------------------------------------------------------------------------
Low-level (libusb) API
* API design
The low-level API of usb4java closely follows the C API of the
{{{http://libusb.info/}libusb}} project. All global functions and
constants of <libusb> are defined as static members of the class
{{{../apidocs/org/usb4java/LibUsb.html}org.usb4java.LibUsb}}.
All structures of <libusb> are defined in separate classes which are named
similar to the original struct names but without underscores, with camel-case
names and with the <libusb> prefix removed. For example the struct
<libusb_device_handle> is defined in the class
{{{../apidocs/org/usb4java/DeviceHandle.html}DeviceHandle}}. Struct
members are represented by static methods in the corresponding class.
The following notable differences exists between the <libusb 1.0 API> and
the <usb4java> API:
* <interface> in the configuration descriptor is named <iface> because
<interface> is a reserved word in Java.
* <MaxPower> in the configuration descriptor is named <bMaxPower> to
be compatible to the USB specification and because method names starting
with upper-case letters are quite unusual in Java.
* Whenever libusb expects a byte pointer and a length you have to use
a direct Java NIO ByteBuffer instead.
* Methods which are returning a string through a byte buffer which was
passed as argument have additional simplified overloaded method
equivalents which are returning a Java String directly.
[]
* Initialization/deinitialization
Before using any usb4java functionality you must initialize libusb:
----
Context context = new Context();
int result = LibUsb.init(context);
if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to initialize libusb.", result);
----
Specifiying a context is optional. If your application only needs a single
libusb context then you can specify <null> as context.
Before your application terminates you should deinitialize libusb:
----
LibUsb.exit(context);
----
Related libusb documentation:
* {{{http://libusb.sourceforge.net/api-1.0/group__lib.html}Library initialization/deinitialization}}
[]
* Finding USB devices
Your program most likely wants to communicate with a specific device so first
of all you have to find it. You have to get a list of all connected USB
devices and then check the vendor/product ids. Here is a method which can
be used for this purpose:
----
public Device findDevice(short vendorId, short productId)
{
// Read the USB device list
DeviceList list = new DeviceList();
int result = LibUsb.getDeviceList(null, list);
if (result < 0) throw new LibUsbException("Unable to get device list", result);
try
{
// Iterate over all devices and scan for the right one
for (Device device: list)
{
DeviceDescriptor descriptor = new DeviceDescriptor();
result = LibUsb.getDeviceDescriptor(device, descriptor);
if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to read device descriptor", result);
if (descriptor.idVendor() == vendorId && descriptor.idProduct() == productId) return device;
}
}
finally
{
// Ensure the allocated device list is freed
LibUsb.freeDeviceList(list, true);
}
// Device not found
return null;
}
----
In your application it might be a little bit more complicated. Maybe you
have more than one device of the same type so you may need a list of devices.
Or you have to identify your device by the product or vendor string
descriptor instead of just checking the ID (In case you are using a
shared vendor/product ID). But this example should bring you on the right
track.
Related libusb documentation:
* {{{http://libusb.sourceforge.net/api-1.0/group__dev.html}Device handling and enumeration}}
[]
* Device handles
For the real USB communication you must open a new device handle and you
must close it again when you are finished communicating with the device.
Example:
----
DeviceHandle handle = new DeviceHandle();
int result = LibUsb.open(device, handle);
if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to open USB device", result);
try
{
// Use device handle here
}
finally
{
LibUsb.close(handle);
}
----
* Interfaces
When you want to communicate with an interface or with endpoints of this
interface then you have to claim it before using it and you have to
release it when you are finished. Example:
----
int result = LibUsb.claimInterface(handle, interfaceNumber);
if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to claim interface", result);
try
{
// Use interface here
}
finally
{
result = LibUsb.releaseInterface(handle, interfaceNumber);
if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to release interface", result);
}
----
It is possible that the interface you want to communicate with is already
used by a kernel driver. In this case you have to detach the kernel driver
from the interface before claiming it. Example:
----
// Check if kernel driver must be detached
boolean detach = LibUsb.hasCapability(LibUsb.CAP_SUPPORTS_DETACH_KERNEL_DRIVER)
&& LibUsb.kernelDriverActive(handle, interfaceNumber);
// Detach the kernel driver
if (detach)
{
int result = LibUsb.detachKernelDriver(handle, interfaceNumber);
if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to detach kernel driver", result);
}
// Communicate with the device
...
// Attach the kernel driver again if needed
if (detach)
{
int result = LibUsb.attachKernelDriver(handle, interfaceNumber);
if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to re-attach kernel driver", result);
}
----
Please note that detaching kernel drivers is not supported on Windows.
* Synchronous I/O
For the actual USB communication you usually have to create a direct
byte buffer for the data to send or receive.
This examples sends 8 bytes to a claimed interface using a control transfer:
----
ByteBuffer buffer = ByteBuffer.allocateDirect(8);
buffer.put(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 });
int transfered = LibUsb.controlTransfer(handle,
(byte) (LibUsb.REQUEST_TYPE_CLASS | LibUsb.RECIPIENT_INTERFACE),
(byte) 0x09, (short) 2, (short) 1, buffer, timeout);
if (transfered < 0) throw new LibUsbException("Control transfer failed", transfered);
System.out.println(transfered + " bytes sent");
----
This example sends 8 bytes to endpoint 0x03 of the claimed interface using a
bulk transfer:
----
ByteBuffer buffer = ByteBuffer.allocateDirect(8);
buffer.put(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 });
IntBuffer transfered = IntBuffer.allocate(1);
int result = LibUsb.bulkTransfer(handle, 0x03, buffer, transfered, timeout);
if (result != LibUsb.SUCCESS) throw new LibUsbException("Control transfer failed", transfered);
System.out.println(transfered.get() + " bytes sent");
----
Related libusb documentation:
* {{{http://libusb.sourceforge.net/api-1.0/group__syncio.html}Synchronous device I/O}}
[]
* Asynchronous I/O
Asynchronous I/O is a little bit more complex than synchronous I/O. That's
because libusb doesn't start its own thread to
handle the actual background tasks. Instead you have to create you own
worker thread like this:
----
class EventHandlingThread extends Thread
{
/** If thread should abort. */
private volatile boolean abort;
/**
* Aborts the event handling thread.
*/
public void abort()
{
this.abort = true;
}
@Override
public void run()
{
while (!this.abort)
{
int result = LibUsb.handleEventsTimeout(null, 250000);
if (result != LibUsb.SUCCESS)
throw new LibUsbException("Unable to handle events", result);
}
}
}
----
This simple thread implementation doesn't use a specific libusb context so
it specified <<<null>>> as context. If you need contexts then you may want
to pass it to the thread somehow.
The thread must be started after you have initialized libusb:
----
EventHandlingThread thread = new EventHandlingThread();
thread.start();
----
And it must be stopped before deinitializing libusb:
----
thread.abort();
thread.join();
----
So now with this thread running in the background you can use the
asynchronous functions of libusb. If you don't like this thread and your
program already has some kind of application loop then you can also simply
call <<<LibUsb.handleEventsTimeout(null, 0)>>> inside the loop. This call
returns immediately if there are no events to process.
An actual asynchronous transfer is submitted like this (In this case
an outgoing bulk transfer to endpoint <0x03>):
----
ByteBuffer buffer = BufferUtils.allocateByteBuffer(8);
buffer.put(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 });
Transfer transfer = LibUsb.allocTransfer();
LibUsb.fillBulkTransfer(transfer, handle, 0x03, buffer, callback, null, timeout);
int result = LibUsb.submitTransfer(transfer);
if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to submit transfer", result);
----
The <<<callback>>> is an object implementing the
{{{../apidocs/org/usb4java/TransferCallback.html}TransferCallback}} interface.
Here is an example of such a callback:
----
TransferCallback callback = new TransferCallback()
{
@Override
public void processTransfer(Transfer transfer)
{
System.out.println(transfer.actualLength() + " bytes sent");
LibUsb.freeTransfer(transfer);
}
};
----
Related libusb documentation:
* {{{http://libusb.sourceforge.net/api-1.0/group__asyncio.html}Asynchronous device I/O}}
* {{{http://libusb.sourceforge.net/api-1.0/mtasync.html}Multi-threaded applications and asynchronous I/O}}
* {{{http://libusb.sourceforge.net/api-1.0/io.html}Synchronous and asynchronous device I/O}}
* {{{http://libusb.sourceforge.net/api-1.0/group__poll.html}Polling and timing}}
[]
* See also
* {{{../apidocs/org/usb4java/package-summary.html}API documentation of usb4java}}
* {{{https://github.com/usb4java/usb4java-examples/}usb4java examples}}
* {{{http://libusb.sourceforge.net/api-1.0/}API documentation of libusb}}
[]

View File

@ -31,7 +31,10 @@
<menu name="usb4java">
<item name="About" href="./index.html" />
<item name="Quick start" href="./quickstart.html" />
<item name="Quick start" href="./quickstart/index.html" collapse="true">
<item name="Low-level (libusb) API" href="./quickstart/libusb.html" />
<item name="High-level (javax-usb) API" href="./quickstart/javax-usb.html" />
</item>
<item name="FAQ" href="./faq.html" />
<item name="Configuration" href="./configuration.html" />
<item name="Native libs" href="./nativelibs.html" />

View File

@ -10,7 +10,7 @@
the native <a href="http://libusb.info/">libusb 1.0</a> library
and uses Java NIO buffers for data exchange between libusb and Java.
usb4java also supports the
<a href="http://javax-usb.sourceforge.net/">javax.usb standard (JSR-80)</a>
<a href="http://javax-usb.sourceforge.net/">javax-usb standard (JSR-80)</a>
through the <a href="usb4java-javax/">usb4java-javax extension</a>.
</p>
<p>
@ -30,7 +30,7 @@
<strong><a href="${artifactBaseUrl}.zip">${project.artifactId}-${project.version}.zip</a></strong><br />
</li>
<li>
javax.usb extension:<br />
javax-usb extension:<br />
<strong><a href="${artifactBaseUrl}.tar.bz2">${project.artifactId}-javax-${usb4javaJavaxVersion}.tar.bz2</a></strong><br />
<strong><a href="${artifactBaseUrl}.zip">${project.artifactId}-javax-${usb4javaJavaxVersion}.zip</a></strong><br />
</li>
@ -52,7 +52,7 @@
&lt;/repository&gt;
&lt;/repositories&gt;
&lt;-- For using just usb4java without javax.usb --&gt;
&lt;-- For using just usb4java without javax-usb --&gt;
&lt;dependencies&gt;
&lt;dependency&gt;
&lt;groupId&gt;${project.groupId}&lt;/groupId&gt;
@ -61,7 +61,7 @@
&lt;/dependency&gt;
&lt;/dependencies&gt;
&lt;-- For using usb4java with javax.usb --&gt;
&lt;-- For using usb4java with javax-usb --&gt;
&lt;dependencies&gt;
&lt;dependency&gt;
&lt;groupId&gt;${project.groupId}&lt;/groupId&gt;
@ -89,11 +89,11 @@
<section name="Getting started">
<p>
Read the <a href="quickstart.html">quick start guide</a> and the
Read the <a href="quickstart/index.html">quick start guide</a> and the
<a href="faq.html">FAQ</a>. There are also some
<a href="https://github.com/usb4java/usb4java-examples/">low-level (libusb) examples</a>
and
<a href="https://github.com/usb4java/usb4java-javax-examples/">high-level (javax.usb) examples</a>
<a href="https://github.com/usb4java/usb4java-javax-examples/">high-level (javax-usb) examples</a>
available.
</p>
</section>