16

I'm working on a project that utilizes the USB Host capabilities in Android 3.2. I'm suffering from a deplorable lack of knowledge and talent regarding USB/Serial communication in general. I'm also unable to find any good example code for what I need to do.

I need to read from a USB Communication Device.
Ex: When I connect via Putty (on my PC) I enter:

>GO

And the device starts spewing out data for me. Pitch/Roll/Temp/Checksum.

Ex:

$R1.217P-0.986T26.3*60
$R1.217P-0.986T26.3*60
$R1.217P-0.987T26.3*61
$R1.217P-0.986T26.3*60
$R1.217P-0.985T26.3*63

I can send the initial 'GO' command from the Android device at which time I receive an echo of 'GO'.

Then nothing else on any subsequent reads.

How can I: 1) Send the 'go' command. 2) Read in the stream of data that results.

The USB device I'm working with has the following interfaces (endpoints).

Device Class: Communication Device (0x2)

Interfaces:

Interface #0 Class: Communication Device (0x2) Endpoint #0 Direction: Inbound (0x80) Type: Intrrupt (0x3) Poll Interval: 255 Max Packet Size: 32 Attributes: 000000011

Interface #1 Class: Communication Device Class (CDC) (0xa) Endpoint #0 Address: 129 Number: 1 Direction: Inbound (0x80) Type: Bulk (0x2) Poll Interval (0) Max Packet Size: 32 Attributes: 000000010

Endpoint #1 Address: 2 Number: 2 Direction: Outbound (0x0) Type: Bulk (0x2) Poll Interval (0) Max Packet Size: 32 Attributes: 000000010

I'm able to deal with permission, connect to the device, find the correct interface and assign the endpoints. I'm just having trouble figuring out which technique to use to send the initial command read the ensuing data. I'm tried different combinations of bulkTransfer and controlTransfer with no luck.

Thanks.

I'm using interface#1 as seen below:

public AcmDevice(UsbDeviceConnection usbDeviceConnection, UsbInterface usbInterface) {
    Preconditions.checkState(usbDeviceConnection.claimInterface(usbInterface, true));
    this.usbDeviceConnection = usbDeviceConnection;

    UsbEndpoint epOut = null;
    UsbEndpoint epIn = null;
    // look for our bulk endpoints
    for (int i = 0; i < usbInterface.getEndpointCount(); i++) {
      UsbEndpoint ep = usbInterface.getEndpoint(i);
     Log.d(TAG, "EP " + i + ": " + ep.getType());
      if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
        if (ep.getDirection() == UsbConstants.USB_DIR_OUT) {
          epOut = ep;

        } else if (ep.getDirection() == UsbConstants.USB_DIR_IN) {
          epIn = ep;
        }

      }
    }
    if (epOut == null || epIn == null) {
      throw new IllegalArgumentException("Not all endpoints found.");
    }

    AcmReader acmReader = new AcmReader(usbDeviceConnection, epIn);
    AcmWriter acmWriter = new AcmWriter(usbDeviceConnection, epOut);
    reader = new BufferedReader(acmReader);
    writer = new BufferedWriter(acmWriter);
  }
K.R.
  • 477
  • 1
  • 4
  • 13
  • for those of us struggling out there, how did you choose Interface 1, and for what purpose would Interface 0, the interrupt interface, be used for? Thanks! – Rachael Oct 05 '15 at 19:00

1 Answers1

13

I hate to answer my own question but... I got it figured out. I was just mixing up my reads and writes. Additionally the device didn't like the '\n' I was using at the end of my commands. It appears to get along with '\r' much better.

I ended up using android's bulkTransfer for reads and writes. My writes looked like this.

    try {
            device.getWriter().write(command + "\r");
            device.getWriter().flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

And my overridden write method for my BufferedWriter:

@Override

  public void write(char[] buf, int offset, int count) throws IOException {
    byte[] buffer = new String(buf, offset, count).getBytes(Charset.forName("US-ASCII"));
    int byteCount = connection.bulkTransfer(endpoint, buffer, buffer.length, TIMEOUT);
  }

The reads were similar:

    char[] buffer = new char[BUF_SIZE];
    try {
        BufferedReader reader = device.getReader();

        int readBytes = reader.read(buffer);
        Log.d(TAG, "BYTES READ: " + readBytes);

    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    String strBuf = new String(buffer).trim();
    if (DEBUG) {
        Log.d(TAG, "Read: " + strBuf);
    }

And:

@Override
  public int read(char[] buf, int offset, int count) throws IOException {
    byte[] buffer = new byte[count];
    int byteCount = connection.bulkTransfer(endpoint, buffer, buffer.length, TIMEOUT);

    if (byteCount < 0) {
      throw new IOException();
    }
    char[] charBuffer = new String(buffer, Charset.forName("US-ASCII")).toCharArray();
    System.arraycopy(charBuffer, 0, buf, offset, byteCount);
    return byteCount;
  }

This was all just kicked off in a thread like so:

new Thread() {
    @Override
public void run() {

    String command = "go";
    write(command);

    while (true) {
        String coords = read(); 
        }
}
}.start();

Obviously this is just the comm stuff and I'll need to now do something with it (put it in a Service that can report back to a top level UI Activity using a handler). But this part of it is figured out.

A huge thank you to the folks who are working on rosjava (http://code.google.com/p/rosjava/)... They have put together a lot of great projects and their code was very helpful.

Adding my device class to help clarify things.

import com.google.common.base.Preconditions;

import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.util.Log;

import java.io.BufferedReader;
import java.io.BufferedWriter;

/* This class represents a USB device that supports the adb protocol. */
public class BKDevice {

// private static final int TIMEOUT = 3000;

private final UsbDeviceConnection usbDeviceConnection;
private final BufferedReader reader;
private final BufferedWriter writer;
public static final String TAG = "AcmDevice";

public BKDevice(UsbDeviceConnection usbDeviceConnection,
        UsbInterface usbInterface) {
    Preconditions.checkState(usbDeviceConnection.claimInterface(
            usbInterface, true));
    this.usbDeviceConnection = usbDeviceConnection;

    UsbEndpoint epOut = null;
    UsbEndpoint epIn = null;
    // look for our bulk endpoints
    for (int i = 0; i < usbInterface.getEndpointCount(); i++) {
        UsbEndpoint ep = usbInterface.getEndpoint(i);
        Log.d(TAG, "EP " + i + ": " + ep.getType());

        if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
            if (ep.getDirection() == UsbConstants.USB_DIR_OUT) {
                epOut = ep;

            } else if (ep.getDirection() == UsbConstants.USB_DIR_IN) {
                epIn = ep;

            }
        }
    }
    if (epOut == null || epIn == null) {
        throw new IllegalArgumentException("Not all endpoints found.");
    }

    BKReader acmReader = new BKReader(usbDeviceConnection, epIn);
    BKWriter acmWriter = new BKWriter(usbDeviceConnection, epOut);

    reader = new BufferedReader(acmReader);
    writer = new BufferedWriter(acmWriter);
}

public BufferedReader getReader() {
    return reader;
}

public BufferedWriter getWriter() {
    return writer;
}
}

Adding BKReader code:

import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.util.Log;

import java.io.IOException;
import java.io.Reader;
import java.nio.charset.Charset;

public class BKReader extends Reader {

    private static final int TIMEOUT = 1000;

    private final UsbDeviceConnection connection;
    private final UsbEndpoint endpoint;

    public BKReader(UsbDeviceConnection connection, UsbEndpoint endpoint) {
        this.connection = connection;
        this.endpoint = endpoint;
    }

    @Override
    public int read(char[] buf, int offset, int count) throws IOException {
        byte[] buffer = new byte[count];
        int byteCount = connection.bulkTransfer(endpoint, buffer, buffer.length, TIMEOUT);

        if (byteCount < 0) {
          throw new IOException();
        }
        char[] charBuffer = new String(buffer, Charset.forName("US-ASCII")).toCharArray();
        System.arraycopy(charBuffer, 0, buf, offset, byteCount);
        return byteCount;
    }

    @Override
    public void close() throws IOException {
    }

}
K.R.
  • 477
  • 1
  • 4
  • 13
  • how did u get device.getwriter()? and what did flush() method will do? – Sathish Jan 09 '14 at 07:51
  • My device class contained a BufferedWriter. getWriter returned that object. So, flush() simply flushes the stream. To help out, I'll paste my class code above. – K.R. Jan 11 '14 at 00:12
  • what is BKReader ? Seems like a class you wrote. Can you show some code for that too ? – P_Rein Aug 18 '14 at 06:51
  • @P_Rein, I added the BK Reader code to the end of the original solution. – K.R. Aug 18 '14 at 13:24
  • @K.R. Did you have experience copying android file to usb drive programmatically? – support_ms Feb 18 '15 at 09:51
  • @support_ms, no I didn't. Sorry. – K.R. Feb 19 '15 at 02:48
  • @K.R. you should go ahead and accept this as the answer. It is a very helpful solution and one of only a few USB Host questions with an answer and code example. Thanks :) – Rachael Oct 05 '15 at 17:08
  • @K.R. This has helped me so much, thank you. Question: If you wanted to use an Interrupt endpoint interface in addition to BulkTransfer, would you create an InterruptDevice class object and use it in tandem with BulkTransfer, and if so what communication is done on that endpoint? Additionally, what steps need to be taken to properly close communication over the endpoints/device? Thanks again! – Rachael Oct 13 '15 at 17:42
  • @Rachael I'm so sorry I missed your question. TBH, I'm not sure :-) It's been ages since I was wrestling with this problem. I'll try and dig up the original source and see if I can help out.... let me know if you find an answer in the meantime. – K.R. Nov 04 '15 at 00:45
  • @K.R. After much ado, I have *finally* made my own libraries work. I'm still testing and refining, but I plan to release the code on github. A lot of the stuff on there was too abstracted or the source code wasn't revealed. Buffering the data and querying to create a non-blocking stream was the biggest challenge, as the Android documentation wasn't detailed enough for me. It's also so dependent upon the device, whether it requires control transfer, or builk transfer, wait times between sending commands, and the buffering method used for reading a stream off the device. – Rachael Nov 04 '15 at 17:54