org.bidib.jbidibc.usbhid.UsbDeviceConnection Maven / Gradle / Ivy
package org.bidib.jbidibc.usbhid;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import org.bidib.jbidibc.messages.utils.ByteUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usb4java.BufferUtils;
import org.usb4java.ConfigDescriptor;
import org.usb4java.Device;
import org.usb4java.DeviceHandle;
import org.usb4java.LibUsb;
import org.usb4java.LibUsbException;
public class UsbDeviceConnection implements AutoCloseable {
private static final Logger LOGGER = LoggerFactory.getLogger(UsbDeviceConnection.class);
/** The interface number. */
private static final byte DEFAULT_INTERFACE = 0;
private static final int DEFAULT_CONFIGURATION = 0x01;
private final UsbDevice usbDevice;
private boolean detachKernelDriver;
public UsbDeviceConnection(UsbDevice usbDevice) {
this.usbDevice = usbDevice;
}
public void open() {
LOGGER.info("Open the device.");
// this.usbDevice.open();
int result = -1;
int vendorId = this.usbDevice.getVendorId();
int productId = this.usbDevice.getProductId();
final DeviceHandle handle = LibUsb.openDeviceWithVidPid(null, (short) vendorId, (short) productId);
if (handle == null) {
throw new RuntimeException("Unable to open USB device");
}
this.usbDevice.setHandle(handle);
final Device device = LibUsb.getDevice(handle);
LOGGER.info("Get device passed, device: {}", device);
LOGGER
.info("Found [{}:{}] as device [{}] on USB bus [{}]\n", ByteUtils.intToHex(vendorId),
ByteUtils.intToHex(productId), LibUsb.getDeviceAddress(device), LibUsb.getBusNumber(device));
detachKernelDriver = LibUsb.kernelDriverActive(handle, DEFAULT_INTERFACE) > 0;
if (detachKernelDriver) {
int ret = LibUsb.detachKernelDriver(handle, DEFAULT_INTERFACE);
if (ret > 0) {
LOGGER.warn("Failed to detach kernel driver: {}", ret);
throw new LibUsbException("Unable to open USB device", ret);
}
else {
LOGGER.info("Detached kernel driver.");
}
}
else {
LOGGER.info("No kernel driver active.");
}
IntBuffer currentConfig = IntBuffer.allocate(1);
result = LibUsb.getConfiguration(handle, currentConfig);
if (result != LibUsb.SUCCESS) {
LOGGER.warn("Failed to get current device configuration: {}", result);
// return NULL;
}
LOGGER.info("Current configuration: {}", currentConfig.get(0));
// TODO check if required
if (currentConfig.get(0) != DEFAULT_CONFIGURATION) {
result = LibUsb.setConfiguration(handle, DEFAULT_CONFIGURATION);
if (result != LibUsb.SUCCESS) {
LOGGER.warn("Failed to set device configuration to {}: {}", DEFAULT_CONFIGURATION, result);
}
}
// Claim the ADB interface
result = LibUsb.claimInterface(handle, DEFAULT_INTERFACE);
if (result != LibUsb.SUCCESS) {
throw new LibUsbException("Unable to claim interface", result);
}
LOGGER.info("Claim the default interface passed.");
ByteBuffer ch341DescriptorBuffer = ByteBuffer.allocateDirect(0x12);
result = LibUsb.getDescriptor(handle, LibUsb.DT_DEVICE, (byte) 0x00, ch341DescriptorBuffer);
if (result < LibUsb.SUCCESS) {
// throw new LibUsbException("Unable to claim interface", result);
LOGGER.warn("Unable to get the device descriptor, result: {}", result);
}
else {
LOGGER
.info(String
.format("Device reported its revision [%d.%02d]", ch341DescriptorBuffer.get(13),
ch341DescriptorBuffer.get(12)));
// TODO fix this
byte[] temp = new byte[0x12];
ch341DescriptorBuffer.get(temp);
LOGGER.info("Device descriptor: {}", ByteUtils.bytesToHex(temp));
}
ConfigDescriptor descriptor = null;
try {
// Use device handle here
descriptor = new ConfigDescriptor();
result = LibUsb.getActiveConfigDescriptor(device, descriptor);
if (result != LibUsb.SUCCESS) {
throw new LibUsbException("Unable to get the config descriptor", result);
}
LOGGER.info("Current config descriptor: {}", descriptor);
}
finally {
LibUsb.freeConfigDescriptor(descriptor);
}
}
public boolean claimInterface(UsbInterface usbDeviceInterface, boolean force) {
DeviceHandle handle = this.usbDevice.getHandle();
// Claim the ADB interface
int result = LibUsb.claimInterface(handle, usbDeviceInterface.getId());
if (result != LibUsb.SUCCESS) {
// throw new LibUsbException("Unable to claim interface", result);
LOGGER.warn("Unable to claim interface");
return false;
}
LOGGER.info("Claim the interface passed, id: {}", usbDeviceInterface.getId());
return true;
}
public void releaseInterface(UsbInterface usbDeviceInterface) {
DeviceHandle handle = this.usbDevice.getHandle();
// Claim the ADB interface
int result = LibUsb.releaseInterface(handle, usbDeviceInterface.getId());
if (result != LibUsb.SUCCESS) {
// throw new LibUsbException("Unable to release interface", result);
LOGGER.warn("Unable to release interface");
return;
}
LOGGER.info("Release the default interface passed.");
}
/**
* Performs a control transaction on endpoint zero for this device. The direction of the transfer is determined by
* the request type. If requestType & {@link UsbConstants#USB_ENDPOINT_DIR_MASK} is
* {@link UsbConstants#USB_DIR_OUT}, then the transfer is a write, and if it is {@link UsbConstants#USB_DIR_IN},
* then the transfer is a read.
*
* This method transfers data starting from index 0 in the buffer. To specify a different offset, use
* {@link #controlTransfer(int, int, int, int, byte[], int, int, int)}.
*
*
* @param requestType
* request type for this transaction
* @param request
* request ID for this transaction
* @param value
* value field for this transaction
* @param index
* index field for this transaction
* @param buffer
* buffer for data portion of transaction, or null if no data needs to be sent or received
* @param length
* the length of the data to send or receive
* @param usbTimeoutMillis
* in milliseconds
* @return length of data transferred (or zero) for success, or negative value for failure
*/
public int controlTransfer(
int requestType, int request, int value, int index, byte[] buffer, int length, int usbTimeoutMillis) {
return controlTransfer(requestType, request, value, index, buffer, 0, length, usbTimeoutMillis);
}
/**
* Performs a control transaction on endpoint zero for this device. The direction of the transfer is determined by
* the request type. If requestType & {@link UsbConstants#USB_ENDPOINT_DIR_MASK} is
* {@link UsbConstants#USB_DIR_OUT}, then the transfer is a write, and if it is {@link UsbConstants#USB_DIR_IN},
* then the transfer is a read.
*
* @param requestType
* request type for this transaction
* @param request
* request ID for this transaction
* @param value
* value field for this transaction
* @param index
* index field for this transaction
* @param buffer
* buffer for data portion of transaction, or null if no data needs to be sent or received
* @param offset
* the index of the first byte in the buffer to send or receive
* @param length
* the length of the data to send or receive
* @param usbTimeoutMillis
* in milliseconds
* @return length of data transferred (or zero) for success, or negative value for failure
*/
public int controlTransfer(
int requestType, int request, int value, int index, byte[] buffer, int offset, int length,
int usbTimeoutMillis) {
checkBounds(buffer, offset, length);
DeviceHandle handle = this.usbDevice.getHandle();
final ByteBuffer data = BufferUtils.allocateByteBuffer(length).order(ByteOrder.LITTLE_ENDIAN);
if ((requestType & UsbConstants.USB_ENDPOINT_DIR_MASK) == UsbConstants.USB_DIR_IN) {
// read
}
else {
data.put(buffer, offset, length);
data.rewind();
}
// final ByteBuffer data = ByteBuffer.wrap(buffer, offset, length);
LOGGER
.info("Current requestType: {}, request: {}, index: {}, value: {}", ByteUtils.int16ToHex(requestType),
ByteUtils.int16ToHex(request), ByteUtils.int16ToHex(index), ByteUtils.int16ToHex(value));
int transferredLength =
LibUsb
.controlTransfer(handle, ByteUtils.getLowByte(requestType), ByteUtils.getLowByte(request),
(short) value, (short) index, data, usbTimeoutMillis);
LOGGER.info("controlTransfer, Transferred data length: {}", transferredLength);
if (transferredLength > LibUsb.ERROR_IO) {
data.get(buffer, offset, transferredLength);
}
else {
throw new LibUsbException("Control transfer failed.", transferredLength);
}
return transferredLength;
}
/**
* Performs a bulk transaction on the given endpoint. The direction of the transfer is determined by the direction
* of the endpoint.
*
* This method transfers data starting from index 0 in the buffer. To specify a different offset, use
* {@link #bulkTransfer(UsbEndpoint, byte[], int, int, int)}.
*
*
* @param endpoint
* the endpoint for this transaction
* @param buffer
* buffer for data to send or receive; can be {@code null} to wait for next transaction without reading
* data
* @param length
* the length of the data to send or receive.
* @param timeout
* in milliseconds, 0 is infinite
* @return length of data transferred (or zero) for success, or negative value for failure
* @throws IOException
*/
public int bulkTransfer(UsbEndpoint usbReadEndpoint, byte[] buffer, int length, int timeout) throws IOException {
LOGGER
.info("bulkTransfer, endpointId: {}, timeout: {}, length: {}, data: {}",
ByteUtils.intToHex(usbReadEndpoint.getAddress()), timeout, length,
ByteUtils.bytesToHex(buffer, length));
return bulkTransfer(usbReadEndpoint, buffer, 0, length, timeout);
}
/**
* Performs a bulk transaction on the given endpoint. The direction of the transfer is determined by the direction
* of the endpoint.
*
* @param endpoint
* the endpoint for this transaction
* @param buffer
* buffer for data to send or receive
* @param offset
* the index of the first byte in the buffer to send or receive
* @param length
* the length of the data to send or receive. Before {@value Build.VERSION_CODES#P}, a value larger than
* 16384 bytes would be truncated down to 16384. In API {@value Build.VERSION_CODES#P} and after, any
* value of length is valid.
* @param timeout
* in milliseconds, 0 is infinite
* @return length of data transferred (or zero) for success, or negative value for failure
* @throws IOException
*/
public int bulkTransfer(UsbEndpoint endpoint, byte[] buffer, int offset, int length, int timeout)
throws IOException {
checkBounds(buffer, offset, length);
DeviceHandle handle = this.usbDevice.getHandle();
final ByteBuffer data = BufferUtils.allocateByteBuffer(length); // .order(ByteOrder.LITTLE_ENDIAN);
data.put(buffer, offset, length);
data.rewind();
final IntBuffer transferred = IntBuffer.allocate(1);
int result =
LibUsb.bulkTransfer(handle, ByteUtils.getLowByte(endpoint.getAddress()), data, transferred, timeout);
if (result == LibUsb.ERROR_TIMEOUT) {
throw new IOException("Bulk transfer failed.");
}
int transferredLen = transferred.get();
if (result != LibUsb.SUCCESS) {
throw new LibUsbException("Bulk transfer failed.", transferredLen);
}
LOGGER.info("Bulk transfer passed, {} bytes sent", transferredLen);
return transferredLen;
}
public int interruptTransfer(UsbEndpoint usbReadEndpoint, byte[] buffer, int length, int timeout)
throws IOException {
LOGGER
.info("interruptTransfer, endpointId: {}, timeout: {}, length: {}, data: {}",
ByteUtils.intToHex(usbReadEndpoint.getAddress()), timeout, length,
ByteUtils.bytesToHex(buffer, length));
return interruptTransfer(usbReadEndpoint, buffer, 0, length, timeout);
}
public int interruptTransfer(UsbEndpoint endpoint, byte[] buffer, int offset, int length, int timeout)
throws IOException {
checkBounds(buffer, offset, length);
DeviceHandle handle = this.usbDevice.getHandle();
final ByteBuffer data = BufferUtils.allocateByteBuffer(length); // .order(ByteOrder.LITTLE_ENDIAN);
data.put(buffer, offset, length);
data.rewind();
final IntBuffer transferred = IntBuffer.allocate(1);
int result =
LibUsb.interruptTransfer(handle, ByteUtils.getLowByte(endpoint.getAddress()), data, transferred, timeout);
if (result == LibUsb.ERROR_TIMEOUT) {
throw new IOException("Interrupt transfer failed.");
}
int transferredLen = transferred.get();
if (result != LibUsb.SUCCESS) {
throw new LibUsbException("Interrupt transfer failed.", transferredLen);
}
LOGGER.info("Interrupt transfer passed, {} bytes sent", transferredLen);
return transferredLen;
}
@Override
public void close() {
LOGGER.info("Close the UsbDeviceConnection, current usbDevice: {}", usbDevice);
if (usbDevice != null) {
try {
final DeviceHandle handle = usbDevice.getHandle();
if (handle == null) {
return;
}
int result = -1;
LOGGER.info("Release the interface.");
try {
result = LibUsb.releaseInterface(handle, DEFAULT_INTERFACE);
if (result != LibUsb.SUCCESS) {
// throw new LibUsbException("Unable to release interface", result);
LOGGER.warn("Unable to release interface: {}", result);
}
// Attach the kernel driver again if needed
if (detachKernelDriver) {
LOGGER.info("Re-attach the kernel driver.");
result = LibUsb.attachKernelDriver(handle, DEFAULT_INTERFACE);
if (result != LibUsb.SUCCESS) {
LOGGER.warn("Unable to re-attach kernel driver: {}", result);
// throw new LibUsbException("Unable to re-attach kernel driver", result);
}
// reset the flag
detachKernelDriver = false;
}
}
finally {
LibUsb.close(handle);
// reset the handle
usbDevice.setHandle(null);
// device = null;
}
}
catch (Exception ex) {
LOGGER.warn("Close usbDevice failed.", ex);
}
}
}
private static void checkBounds(byte[] buffer, int start, int length) {
final int bufferLength = (buffer != null ? buffer.length : 0);
if (length < 0 || start < 0 || start + length > bufferLength) {
throw new IllegalArgumentException("Buffer start or length out of bounds.");
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy