All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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