-
Notifications
You must be signed in to change notification settings - Fork 11
Java Does USB By Examples
This page presents several small examples showing how to use the Java Does USB library to interact with USB devices.
Most examples are designed to work with the USB loopback test device.
Usb.getDevices()
returns a list of all USB devices connected to the system.
for (var device : Usb.getDevices()) {
System.out.println(device);
}
Video cameras can be recognized because they have a USB interface implementing the Video class and Video Control subclass.
Since each interface can have multiple alternate settings, the class code and subclass code are found on the UsbAlternateSetting
instance. It's sufficient to check the current one.
public static void main(String[] args) {
for (var device : Usb.findDevices(Main::isVideoDevice)) {
System.out.println(device);
}
}
static boolean isVideoDevice(UsbDevice device) {
for (var intf : device.getInterfaces()) {
var alternate = intf.getCurrentAlternate();
if (alternate.getClassCode() == CC_VIDEO && alternate.getSubclassCode() == SC_VIDEOCONTROL) {
return true;
}
}
return false;
}
The vendor ID and product ID (0xcafe
and 0xceaf
in this example) uniquely identify a particular type of USB device, i.e. all devices of the same type have the same features and use the same communication protocol (on top of USB).
var testDevice = Usb.findDevice(0xcafe, 0xceaf);
if (testDevice.isPresent()) {
System.out.println(testDevice.get());
} else {
System.out.println("Device not found");
}
A separate callback con be registered to be notified when a device is connected or disconnected.
The callback is consumer taking a UsbDevice
as its argument. The consumer is called from a background thread.
public static void main(String[] args) throws IOException {
Usb.setOnDeviceConnected(device -> System.out.println("Device connected: " + device));
Usb.setOnDeviceDisconnected(device -> System.out.println("Device disconnected: " + device));
System.out.println("Press ENTER to exit");
System.in.read();
}
If multiple devices of the same type are used, the triple of vendor ID, product ID and serial number can be used to uniquely identify a device. This can be useful to save settings related to a particular device.
public static void main(String[] args) {
var rememberedDeviceId = new UsbDeviceId(0xcafe, 0xceaf, "35A737883336");
for (var device : Usb.findDevices(Main::isTestDevice)) {
System.out.println(device);
if (rememberedDeviceId.matches(device)) {
System.out.println("Remembered device");
} else {
System.out.println("New device");
}
}
}
static boolean isTestDevice(UsbDevice device) {
return device.getVendorId() == 0xcafe && device.getProductId() == 0xceaf;
}
record UsbDeviceId(int vendorId, int productId, String serialNumber) {
boolean matches(UsbDevice device) {
return device.getVendorId() == vendorId && device.getProductId() == productId
&& serialNumber.equals(device.getSerialNumber());
}
}
To send a control request, the device is opened first. controlTrasnferOut
takes a UsbControlTransfer
as its first argument, consisting of request type, request, value, index and data. The purpose of these values depends on the USB device and is usually provided by the device manufacturer. For devices implementing an official USB class, it is documented in the USB class specification.
For the test device, the possible values and their functionality are documented in GitHub at USB loopback test device. Note that the UsbControlTransfer
instance uses separate fields for request type and recipient while they are combined in bmRequestType
field in the USB specification.
var testDevice = Usb.findDevice(0xcafe, 0xceaf).orElseThrow();
testDevice.open();
var transfer = new UsbControlTransfer(UsbRequestType.VENDOR, UsbRecipient.INTERFACE, 0x01, 1234, 1);
testDevice.controlTransferOut(transfer, null);
testDevice.close();
Interrupt transfers are small packets sent to a USB endpoint with transfer type interrupt. The test device can receive interrupt transfers on endpoint 3. In order to use, the device must be opened and interface 0 must be claimed.
var testDevice = Usb.findDevice(0xcafe, 0xceaf).orElseThrow();
testDevice.open();
testDevice.claimInterface(0);
testDevice.transferOut(3, new byte[]{(byte) 0x56, (byte) 0x78, (byte) 0x9a, (byte) 0xbc});
testDevice.close();
Note: The test device will echo interrupt packet on another interrupt endpoint. If the echoed data is not read, the device will not accept new data. So the test device should be reset or unplugged after this test.
Interrupt transfers are usually used by a USB device for notification about events happening a unpredictable time. So receiving an interrupt transfer consists mainly of waiting, and the host will likely wait for it in a separate thread.
The test device will echo each packet sent to OUT endpoint 3 on IN endpoint 3 twice. In order to use the endpoints, the device must be opened and interface 0 must be claimed.
var testDevice = Usb.findDevice(0xcafe, 0xceaf).orElseThrow();
testDevice.open();
testDevice.claimInterface(0);
testDevice.transferOut(3, new byte[]{(byte) 0x56, (byte) 0x78, (byte) 0x9a, (byte) 0xbc});
var data = testDevice.transferIn(3);
System.out.println("Received " + data.length + " bytes");
data = testDevice.transferIn(3);
System.out.println("Received " + data.length + " bytes");
testDevice.close();
Data can be sent to endpoints of transfer type bulk in the same way as to endpoints of transfer type interrupt. However, it is more convenient to use an OutputStream
.
The test device accepts bulk transfers on endpoint 1. In order to use, the device must be opened and interface 0 must be claimed.
var testDevice = Usb.findDevice(0xcafe, 0xceaf).orElseThrow();
testDevice.open();
testDevice.claimInterface(0);
try (var writer = new OutputStreamWriter(testDevice.openOutputStream(1))) {
writer.write("Hello, world!\n");
}
testDevice.close();
Note: The test device will echo the data on another endpoint. If the echoed data is not read, a small buffer will fill up and the device will not accept new data. So the test device should be reset or unplugged after this test.
Data can be received from endpoints of transfer type bulk in the same way as from endpoints of transfer type interrupt. However, it is more convenient to use an InputStream
.
The test device loops back all data sent to OUT endpoint 1 to IN endpoint 2. For small amounts of data, it is possible to send it and then receive it in the same thread. For bigger amounts, two separate threads are needed.
Bulk endpoints are a stream based protocol as opposed to a message based protocol. There is no concept of a message boundary. Both the host and the device will merge and split the stream of data into packets as they see fit. If message boundaries are important, the protocol on top of USB must define them. The below code uses the new line character as a message boundary.
In order to use endpoints 1 and 2, the device must be opened and interface 0 must be claimed.
var testDevice = Usb.findDevice(0xcafe, 0xceaf).orElseThrow();
testDevice.open();
testDevice.claimInterface(0);
// send text to the device
try (var writer = new OutputStreamWriter(testDevice.openOutputStream(1))) {
writer.write("Welcome to Java Does USB\n");
writer.write("Have a nice day.\n");
}
// receive echoed text
try (var reader = new BufferedReader(new InputStreamReader(testDevice.openInputStream(2)))) {
System.out.println(reader.readLine());
System.out.println(reader.readLine());
}
testDevice.close();