1
votes

I'm trying to make a little driver to communicate with an USB thermometer, using java library Libusb (org.usb4java.LibUsb). I'm testing it on a Raspeberry Pi (3b) with vanilla linux-arm.

My problem is that I don't succeed in transferring a control transfer to the device. I get the error:

org.usb4java.LibUsbException: USB error 9: Control transfer failed: Pipe error

Here is my code:

Main class:

public class usbDriver {

    public static void main(String[] args) {
        Communication2 com = new Communication2();
        try {
            com.trouverDevice();
            com.preparerCom();
            com.testCom();
            com.terminerCom();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

Communication2 class:

public class Communication2 {

/** vendor ID du thermometre */
private static final short VENDOR_ID = 0x1941;

/** product ID du thermometre  */
private static final short PRODUCT_ID = (short) 0x8021;

/** interface active du thermometre  */
private static final byte INTERFACE_ID = 0x0;

/** endpoint sur l'interface active du thermometre  */
private static final byte ENDPOINT_ID = (byte) 0x81;

private Context contexte = null;
private Device device = null;
DeviceHandle handle = null;

private boolean pret;
private boolean detach = false;
private boolean trouve = false;

public Communication2() {
    pret = false;
}

public void trouverDevice() throws SecurityException, UsbException{
    // avec libUsb
    // Create the libusb context
    Context context = new Context();

    // Initialize the libusb context
    int result = LibUsb.init(context);
    if (result < 0)
    {
        throw new LibUsbException("Unable to initialize libusb", result);
    }
    // Read the USB device list
    DeviceList list = new DeviceList();
    result = LibUsb.getDeviceList(context, list);
    if (result < 0)
    {
        throw new LibUsbException("Unable to get device list", result);
    }
    try
    {
        // Iterate over all devices and list them
        for (Device device: list)
        {
            int address = LibUsb.getDeviceAddress(device);
            int busNumber = LibUsb.getBusNumber(device);
            DeviceDescriptor descriptor = new DeviceDescriptor();
            result = LibUsb.getDeviceDescriptor(device, descriptor);
            if (result < 0)
            {
                throw new LibUsbException(
                    "Unable to read device descriptor", result);
            }
            if (descriptor.idVendor() == VENDOR_ID && descriptor.idProduct() == PRODUCT_ID){
                System.out.println("Thermometre Pearl NC-7004 detecté !");
                System.out.println(descriptor.toString());
                this.device = device;
                this.trouve=true;
            } 
        }
    }
    finally
    {
        // Ensure the allocated device list is freed
        //LibUsb.freeDeviceList(list, true);
    }
    // Deinitialize the libusb context  
}

    public boolean preparerCom() throws Exception{

        if (!this.trouve) return false;

        this.contexte = new Context();
        int result = LibUsb.init(contexte);

        // reclamer le handle
        System.out.println("claim device handle");
        this.handle = new DeviceHandle();
        result = LibUsb.open(this.device, handle);
        if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to open USB device", result);

        detach = LibUsb.hasCapability(LibUsb.CAP_SUPPORTS_DETACH_KERNEL_DRIVER);
        detach = true; // pour forcer le claim sur le kernel
        detach = detach && (LibUsb.kernelDriverActive(handle, INTERFACE_ID)==1?true:false);
        System.out.println(LibUsb.hasCapability(LibUsb.CAP_SUPPORTS_DETACH_KERNEL_DRIVER));
        System.out.println((LibUsb.kernelDriverActive(handle, INTERFACE_ID)));
        System.out.println(detach);

        // Detach the kernel driver
        if (detach)
        {
            System.out.println("tentative de detacher le kernel");
            result = LibUsb.detachKernelDriver(handle,  INTERFACE_ID);
            if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to detach kernel driver", result);
        }
        detach = true;  

        System.out.println("claim interface");
        result = LibUsb.claimInterface(handle, INTERFACE_ID);
        if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to claim interface", result);

        this.pret=false;
        return this.pret;
    }


    public void testCom(){
        if (!this.pret) return;

        ByteBuffer buffer = ByteBuffer.allocate(18);


        // LibUsb.fillControlSetup(buffer, (byte)0x80, (byte)0x6,
        //      (short)0x1, (short)0x0, (short)0x1200);

        ByteBuffer buffer2 = ByteBuffer.allocateDirect(18);

        int transfered = LibUsb.controlTransfer(handle,(byte)0x80,(byte)0x6,(short)0x1,(short)0x0,buffer2,2000L);
        if (transfered < 0) throw new LibUsbException("Control transfer failed", transfered);
        System.out.println(transfered + " bytes sent");

        String test;
        String test2;
        if (buffer2.hasArray()) {
            for(int i =0;i<buffer2.array().length;i++){
                System.out.format("%02x",buffer2.array()[i]);
            }


             test=  new String(buffer.array(),
                    buffer.arrayOffset() + buffer.position(),
                    buffer.remaining());
        } else {
            final byte[] b = new byte[buffer.remaining()];
            buffer.duplicate().get(b);
            test =  new String(b);
        }
        System.out.println(test);
    }


    public void terminerCom() throws Exception{
        if (this.pret){
            if (this.detach)
            {
                int result = LibUsb.attachKernelDriver(handle,  INTERFACE_ID);
                if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to re-attach kernel driver", result);
            }

            LibUsb.close(this.handle);
            this.trouve = false;
            this.pret = false;
        }
    }
}

The error occurs when I call LibUsb.ControlTransfer(), trying to transfer a GET_DESCRIPTOR control packet. Here is the complete return of the code with device descriptor info:

> Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 0 Per Interface
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x1941
idProduct 0x8021
bcdDevice 1.00
iManufacturer 0
iProduct 0
iSerial 0
bNumConfigurations 1
claim device handle
false
0
false
claim interface
org.usb4java.LibUsbException: USB error 9: Control transfer failed: Pipe error
at usbDriver.Communication2.testCom(Communication2.java:171)
at usbDriver.usbDriver.main(usbDriver.java:36)
root@raspberrypi:/home/pi/Desktop/execUsbDriver# java -jar usbDriver_executable.jar
java.lang.IllegalArgumentException: handle must not be null
at org.usb4java.LibUsb.controlTransfer(Native Method)
at usbDriver.Communication2.testCom(Communication2.java:170)
at usbDriver.usbDriver.main(usbDriver.java:36) 

I think the pipe is well initialized (Handle seems ok, interface was successfully claimed too). Also the Libusb doc indicates :

LIBUSB_ERROR_PIPE if the control request was not supported by the device

So I guess I'm just misdoing the control request. If you know the code to send a GET_DESCRIPTOR request properly I'd be glad to test it!

5

5 Answers

1
votes

Actually I managed to run the first request on my device ! It was just a problem with the sent requests which were not accurate.

No I want to handle the main goal : retrieve data from the thermometer. With a sniffing tool I saw that i must send a setup packet + 8 bytes of variable through the control pipe. Then on endpoint 0x81 the device should send back 4 * 8 bytes of data in interrupt mode. I tested that with a prototyping tool :

screen of data request

As I undestand it I should do an isochronous transfer. I have tried the following code without success for now.

public void testCom(){
        if (!this.pret) return;

        ByteBuffer buffer = ByteBuffer.allocateDirect(18);
        buffer.rewind();
        // descriptor device
        int transfered = LibUsb.controlTransfer(handle,
                LibUsb.ENDPOINT_IN,LibUsb.REQUEST_GET_DESCRIPTOR,(short)0x0100,(short)0x0000,buffer,2000L);
        if (transfered < 0) throw new LibUsbException("Control transfer failed", transfered);
        System.out.println(transfered + " bytes sent");

        System.out.println("--- DEVICE DESCRIPTOR ---");
        System.out.println(byteBuffer2String(buffer));
        System.out.println();

        buffer = ByteBuffer.allocateDirect(9);
        // descriptor configuration
        transfered = LibUsb.controlTransfer(handle,
                LibUsb.ENDPOINT_IN,LibUsb.REQUEST_GET_DESCRIPTOR,(short)0x0200,(short)0x0000,buffer,2000L);
        if (transfered < 0) throw new LibUsbException("Control transfer failed", transfered);
        System.out.println(transfered + " bytes sent");

        System.out.println("--- CONFIGURATION DESCRIPTOR ---");
        System.out.println(byteBuffer2String(buffer));
        System.out.println();

        buffer = ByteBuffer.allocateDirect(8);
        // descriptor string
        transfered = LibUsb.controlTransfer(handle,
                LibUsb.ENDPOINT_IN,LibUsb.REQUEST_GET_DESCRIPTOR,(short)0x0300,(short)0x0409,buffer,2000L);
        if (transfered < 0) throw new LibUsbException("Control transfer failed", transfered);
        System.out.println(transfered + " bytes sent");

        System.out.println("--- DESCRIPTOR STRING ---");
        System.out.println(byteBuffer2String(buffer));
        System.out.println();

        System.out.println("TRYING TO GET DATA  ...");

        ByteBuffer bufferArgCmd = ByteBuffer.allocateDirect(16);
        /*bufferArgCmd.put((byte)0x21);
        bufferArgCmd.put((byte)0x09);
        bufferArgCmd.put((byte)0x02);
        bufferArgCmd.put((byte)0x00);
        bufferArgCmd.put((byte)0x00);
        bufferArgCmd.put((byte)0x00);
        bufferArgCmd.put((byte)0x08);
        bufferArgCmd.put((byte)0x00);*/

        bufferArgCmd.put((byte)0xA1);
        bufferArgCmd.put((byte)0x00);
        bufferArgCmd.put((byte)0x00);
        bufferArgCmd.put((byte)0x20);
        bufferArgCmd.put((byte)0xA1);
        bufferArgCmd.put((byte)0x00);
        bufferArgCmd.put((byte)0x00);
        bufferArgCmd.put((byte)0x20);
        bufferArgCmd.rewind();

        ByteBuffer bufferResCmd = ByteBuffer.allocateDirect(4);
        bufferResCmd.get();
        bufferResCmd.get();

        ByteBuffer bufferData = ByteBuffer.allocateDirect(32);


        ByteBuffer buffer81 = BufferUtils.allocateByteBuffer(32);
        // buffer81.put(data);
        Transfer transfer = LibUsb.allocTransfer(0);
        LibUsb.fillInterruptTransfer(transfer, handle, ENDPOINT_ID, bufferData,
            receiveData, null, 3000L);
        LibUsb.fillControlSetup(bufferArgCmd,(byte)0x21,(byte)0x09,(short)0x0200,(short)0x0,(short)0x08);
        // LibUsb.fillControlTransfer(transfer, handle, bufferArgCmd,
             //   sendData, null, 2000L);
        System.out.println("doing interrupt transfer to device");
        int result = LibUsb.submitTransfer(transfer);
        if (result != LibUsb.SUCCESS)
        {
            throw new LibUsbException("Unable to submit transfer", result);
        }

        System.out.println("Resultat du transfert : ");
        System.out.println(byteBuffer2String(bufferData));
    }

    // This callback is called after the ADB CONNECT message header is
    // sent and sends the ADB CONNECT message body.
    final TransferCallback receiveData = new TransferCallback()
    {
        @Override
        public void processTransfer(Transfer transfer)
        {
            System.out.println(transfer.actualLength() + " bytes received !!!! ");
            // write(handle, CONNECT_BODY, bodySent);
            // LibUsb.freeTransfer(transfer);
        }
    };

 // This callback is called after the ADB CONNECT message header is
    // sent and sends the ADB CONNECT message body.
    final TransferCallback sendData = new TransferCallback()
    {
        @Override
        public void processTransfer(Transfer transfer)
        {
            System.out.println("send command to device");
            // write(handle, CONNECT_BODY, bodySent);
            // LibUsb.freeTransfer(transfer);
        }
    };

The response is below :

> Thermometre Pearl NC-7004 detecté !
Device Descriptor:
  bLength                 18
  bDescriptorType          1
  bcdUSB                1.10
  bDeviceClass             0 Per Interface
  bDeviceSubClass          0
  bDeviceProtocol          0
  bMaxPacketSize0          8
  idVendor            0x1941
  idProduct           0x8021
  bcdDevice             1.00
  iManufacturer            0
  iProduct                 0
  iSerial                  0
  bNumConfigurations       1

claim device handle
false
0
false
claim interface
18 bytes sent
--- DEVICE DESCRIPTOR ---
1201 1001 0000 0008 4119 2180 0001 0000 0001

9 bytes sent
--- CONFIGURATION DESCRIPTOR ---
0902 2200 0101 0080 32

4 bytes sent
--- DESCRIPTOR STRING ---
0403 0904 0000 0000

tentative de communication de la requete de données ...
doing interrupt transfer to device
Resultat du transfert :
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
java.lang.IllegalStateException: deviceHandlePointer is not initialized
        at org.usb4java.LibUsb.attachKernelDriver(Native Method)
        at usbDriver.Communication2.terminerCom(Communication2.java:313)
        at usbDriver.usbDriver.main(usbDriver.java:37)

Can you give me advice about how to perform this ?

1
votes

Despite a lot of tests still at square one with this asynchronous data transfer. I'm completely sure my request is good. The good news is that handling the LibUsb.handleEventTimeOut the callback function of the interrupt transfer is called ... when its timeout arrives....

This is puzzling because the command used on windows with driverWizard works perfectly fine. Also I think it should be possible to do it with my current configuration on the Raspberry with LibUsb since synchronous transfers with the device ( get descriptor, configuration ...) work fine. Also even if the control callback is never called ( probably since the device sends nothing back on control pipe) it ends with a TIMEOUT with the good command, while it's an error 9 " invalid command" with a slightly different command ... it's a good sign I think, it shows the device react at least a little to the command.

But i'm afraid to be out of idea. Could this be :

  • an incompatibility due to LibUsb/Raspberry that makes asynchronous transfer unhandable ? I don't know if raspberry usb capabilities include asynchronous transfers ( but it seems probable, since without it a lot of device couldn't work with raspberry ! )
  • a problem with the way LibUsb handle asynchronous transfer ? if the device react a little to the command it should write in pipe 0x81 triggering the interrupt transfer completion event, no ? That's exactly what is dne with the pipe listening in DriverWizard even though i don't know how it does exactly.

I'm open ot every ideas right now ! I'll still give some tries next week but if it doesn't work i'll have forget this particular thermometer.
By the way do you know thermometers/hygrometers easily configurable on raspberry and allowing time real (every minutes) measures and data transfer ?

1
votes

Actually, this code does work !

private void baseRequest(){

        LibUsb.clearHalt(handle, (byte)0x0);
        LibUsb.clearHalt(handle, (byte)0x81);

        int result;
        int count = 0;

        bufferData = ByteBuffer.allocateDirect(32);
        Transfer interruptTransfer = LibUsb.allocTransfer(0);
        LibUsb.fillInterruptTransfer(interruptTransfer , handle, ENDPOINT_ID, bufferData,receiveBaseRequest, null, 200L);
        //bufferData.rewind();


        LibUsb.unlockEvents(contexte);
        LibUsb.submitTransfer(interruptTransfer);

        bufferControl= ByteBuffer.allocateDirect(16);
        bufferControl.put((byte)0x00);
        bufferControl.put((byte)0x00);
        bufferControl.put((byte)0x00);
        bufferControl.put((byte)0x00);
        bufferControl.put((byte)0x00);
        bufferControl.put((byte)0x00);
        bufferControl.put((byte)0x00);
        bufferControl.put((byte)0x00);
        // now the data
        bufferControl.put((byte)0xa1);
        bufferControl.put((byte)0x00);
        bufferControl.put((byte)0x00);
        bufferControl.put((byte)0x20);
        bufferControl.put((byte)0xa1);
        bufferControl.put((byte)0x00);
        bufferControl.put((byte)0x00);
        bufferControl.put((byte)0x20);
        bufferControl.rewind();
        Transfer controlTransfer = LibUsb.allocTransfer(0);
        //bufferControl.rewind();
        LibUsb.fillControlSetup(bufferControl,(byte)0x21,(byte)0x09,( short)0x0200,(short)0x0000,(short)0x08);
        LibUsb.fillControlTransfer(controlTransfer, handle, bufferControl,sendBaseRequest, null, 200L);
        //bufferControl.rewind();
        controlTransfer.setEndpoint((byte)0x00);
        display(byteBuffer2String(controlTransfer.buffer()));
        result = LibUsb.submitTransfer(controlTransfer);

        // TODO : voir pour free les transferts
    }

@Dryman : thank you for your advices, I think that my main mistake was to do the fillControlTransfer before the fillControlSetup . Indeed it doesn't work that way the fillControlSetup must be done before the fillControlTransfer ( i noticed that reading the source code for synchronousControlTransfer ). Anyway now i can communicate efficiently with the device, thanks for the advice !

0
votes

I think it should look more like this:

    ByteBuffer bufferControl= ByteBuffer.allocateDirect(16);
    // leaving out 8 byte for setup packet
    bufferControl.put((byte)0x00);
    bufferControl.put((byte)0x00);
    bufferControl.put((byte)0x00);
    bufferControl.put((byte)0x00);
    bufferControl.put((byte)0x00);
    bufferControl.put((byte)0x00);
    bufferControl.put((byte)0x00);
    bufferControl.put((byte)0x00);

    // now the data
    bufferControl.put((byte)0xA1);
    bufferControl.put((byte)0x00);
    bufferControl.put((byte)0x00);
    bufferControl.put((byte)0x20);
    bufferControl.put((byte)0xA1);
    bufferControl.put((byte)0x00);
    bufferControl.put((byte)0x00);
    bufferControl.put((byte)0x20);
    bufferControl.rewind();

    // this is for the data to read
    ByteBuffer bufferData = ByteBuffer.allocateDirect(32);

    Transfer controlTransfer = LibUsb.allocTransfer(0);
    LibUsb.fillControlSetup(bufferControl,(byte)0x21,(byte)0x09, short)0x0200,(short)0x0,(short)0x08);
    LibUsb.fillControlTransfer(controlTransfer, handle, bufferControl, sendData, null, 2000L);
    int result = LibUsb.submitTransfer(controlTransfer);
    if (result != LibUsb.SUCCESS)
    {
        throw new LibUsbException("Unable to submit control transfer", result);
    }

    System.out.println("doing interrupt transfer to device");
    Transfer interruptTransfer = LibUsb.allocTransfer(0);
    LibUsb.fillInterruptTransfer(interruptTransfer , handle, ENDPOINT_ID, bufferData,
        receiveData, null, 3000L);
    int result = LibUsb.submitTransfer(interruptTransfer);
    if (result != LibUsb.SUCCESS)
    {
        throw new LibUsbException("Unable to submit interrupt transfer", result);
    }
    Thread.Sleep(5000);
0
votes

After several tests and variations it still doesn't work ...

It's really annoying that callbacks functions are not called. Really the sendData callback function should be called when the controlTransfer is submitted, no ? but on test it's just never called. Is the control transfer really done ? mystery ...

Also if I get it well there are no really listener in USB. Device can talk only when asked to. This means that once the control transfer is done, i should perform an interrupt transfer on my data endpoint, the 0x81, with a data buffer of 32 bytes but how exactly can i specify the device to send back data with that ? i read about the direction token so is that the first byte of data on interrupt that should have a specific value, indicating the device to effectively write on this buffer and send back data ?