package com.hoho.android.usbserial.driver;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.util.Log;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* USB CDC/ACM serial driver implementation.
*
* @author mike wakerly (opensource@hoho.com)
* @see Universal
* Serial Bus Class Definitions for Communication Devices, v1.1
*/
public class CdcAcmSerialDriver extends CommonUsbSerialDriver {
private final String TAG = CdcAcmSerialDriver.class.getSimpleName();
private UsbInterface mControlInterface;
private UsbInterface mDataInterface;
private UsbEndpoint mControlEndpoint;
private UsbEndpoint mReadEndpoint;
private UsbEndpoint mWriteEndpoint;
private boolean mRts = false;
private boolean mDtr = false;
private static final int USB_RECIP_INTERFACE = 0x01;
private static final int USB_RT_ACM = UsbConstants.USB_TYPE_CLASS | USB_RECIP_INTERFACE;
private static final int SET_LINE_CODING = 0x20; // USB CDC 1.1 section 6.2
private static final int GET_LINE_CODING = 0x21;
private static final int SET_CONTROL_LINE_STATE = 0x22;
private static final int SEND_BREAK = 0x23;
public CdcAcmSerialDriver(UsbDevice device) {
super(device);
}
@Override
public void open() throws IOException {
Log.d(TAG, "device " + mDevice);
mControlInterface = null;
mDataInterface = null;
mWriteEndpoint = null;
mReadEndpoint = null;
// locate all needed interfaces
for(int i = 0; i < mDevice.getInterfaceCount();i++){
UsbInterface iface = mDevice.getInterface(i);
switch(iface.getInterfaceClass()){
case UsbConstants.USB_CLASS_COMM:
mControlInterface = iface;
Log.d(TAG, "control iface=" + iface);
break;
case UsbConstants.USB_CLASS_CDC_DATA:
mDataInterface = iface;
Log.d(TAG, "data iface=" + iface);
break;
default:
Log.d(TAG, "skipping iface=" + iface);
break;
}
}
// failback to the old way
if(mControlInterface == null) {
mControlInterface = mDevice.getInterface(0);
Log.d(TAG, "Failback: Control iface=" + mControlInterface);
}
if (!mConnection.claimInterface(mControlInterface, true)) {
throw new IOException("Could not claim control interface.");
}
mControlEndpoint = mControlInterface.getEndpoint(0);
Log.d(TAG, "Control endpoint: " + mControlEndpoint);
if(mDataInterface == null) {
mDataInterface = mDevice.getInterface(1);
Log.d(TAG, "Failback: data iface=" + mDataInterface);
}
if (!mConnection.claimInterface(mDataInterface, true)) {
throw new IOException("Could not claim data interface.");
}
for(int i = 0; i < mDataInterface.getEndpointCount(); i++) {
UsbEndpoint endpoint = mDataInterface.getEndpoint(i);
switch (endpoint.getDirection()) {
case UsbConstants.USB_DIR_OUT:
mWriteEndpoint = endpoint;
Log.d(TAG, "Write endpoint: " + mWriteEndpoint);
break;
case UsbConstants.USB_DIR_IN:
mReadEndpoint = endpoint;
Log.d(TAG, "Read endpoint: " + mReadEndpoint);
break;
}
}
if(mReadEndpoint == null || mWriteEndpoint == null){
// failback to the old method
mReadEndpoint = mDataInterface.getEndpoint(0);
Log.d(TAG, "Read endpoint direction: " + mReadEndpoint.getDirection());
mWriteEndpoint = mDataInterface.getEndpoint(1);
Log.d(TAG, "Write endpoint direction: " + mWriteEndpoint.getDirection());
}
}
private int sendAcmControlMessage(int request, int value, byte[] buf) {
return mConnection.controlTransfer(
USB_RT_ACM, request, value, 0, buf, buf != null ? buf.length : 0, 5000);
}
@Override
public void close() throws IOException {
mConnection.close();
}
@Override
public int read(byte[] dest, int timeoutMillis) throws IOException {
final int numBytesRead;
synchronized (mReadBufferLock) {
int readAmt = Math.min(dest.length, mReadBuffer.length);
numBytesRead = mConnection.bulkTransfer(mReadEndpoint, mReadBuffer, readAmt,
timeoutMillis);
if (numBytesRead < 0) {
// This sucks: we get -1 on timeout, not 0 as preferred.
// We *should* use UsbRequest, except it has a bug/api oversight
// where there is no way to determine the number of bytes read
// in response :\ -- http://b.android.com/28023
return 0;
}
System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead);
}
return numBytesRead;
}
@Override
public int write(byte[] src, int timeoutMillis) throws IOException {
// TODO(mikey): Nearly identical to FtdiSerial write. Refactor.
int offset = 0;
while (offset < src.length) {
final int writeLength;
final int amtWritten;
synchronized (mWriteBufferLock) {
final byte[] writeBuffer;
writeLength = Math.min(src.length - offset, mWriteBuffer.length);
if (offset == 0) {
writeBuffer = src;
} else {
// bulkTransfer does not support offsets, make a copy.
System.arraycopy(src, offset, mWriteBuffer, 0, writeLength);
writeBuffer = mWriteBuffer;
}
amtWritten = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, writeLength,
timeoutMillis);
}
if (amtWritten <= 0) {
throw new IOException("Error writing " + writeLength
+ " bytes at offset " + offset + " length=" + src.length);
}
//Log.d(TAG, "Wrote amt=" + amtWritten + " attempted=" + writeLength);
offset += amtWritten;
}
return offset;
}
@Override
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) {
byte stopBitsByte;
switch (stopBits) {
case STOPBITS_1: stopBitsByte = 0; break;
case STOPBITS_1_5: stopBitsByte = 1; break;
case STOPBITS_2: stopBitsByte = 2; break;
default: throw new IllegalArgumentException("Bad value for stopBits: " + stopBits);
}
byte parityBitesByte;
switch (parity) {
case PARITY_NONE: parityBitesByte = 0; break;
case PARITY_ODD: parityBitesByte = 1; break;
case PARITY_EVEN: parityBitesByte = 2; break;
case PARITY_MARK: parityBitesByte = 3; break;
case PARITY_SPACE: parityBitesByte = 4; break;
default: throw new IllegalArgumentException("Bad value for parity: " + parity);
}
byte[] msg = {
(byte) ( baudRate & 0xff),
(byte) ((baudRate >> 8 ) & 0xff),
(byte) ((baudRate >> 16) & 0xff),
(byte) ((baudRate >> 24) & 0xff),
stopBitsByte,
parityBitesByte,
(byte) dataBits};
sendAcmControlMessage(SET_LINE_CODING, 0, msg);
}
@Override
public boolean getCD() throws IOException {
return false; // TODO
}
@Override
public boolean getCTS() throws IOException {
return false; // TODO
}
@Override
public boolean getDSR() throws IOException {
return false; // TODO
}
@Override
public boolean getDTR() throws IOException {
return mDtr;
}
@Override
public void setDTR(boolean value) throws IOException {
mDtr = value;
setDtrRts();
}
@Override
public boolean getRI() throws IOException {
return false; // TODO
}
@Override
public boolean getRTS() throws IOException {
return mRts;
}
@Override
public void setRTS(boolean value) throws IOException {
mRts = value;
setDtrRts();
}
private void setDtrRts() {
int value = (mRts ? 0x2 : 0) | (mDtr ? 0x1 : 0);
sendAcmControlMessage(SET_CONTROL_LINE_STATE, value, null);
}
public static Map getSupportedDevices() {
final Map supportedDevices = new LinkedHashMap();
supportedDevices.put(Integer.valueOf(UsbId.VENDOR_ARDUINO),
new int[] {
UsbId.ARDUINO_UNO,
UsbId.ARDUINO_UNO_R3,
UsbId.ARDUINO_MEGA_2560,
UsbId.ARDUINO_MEGA_2560_R3,
UsbId.ARDUINO_SERIAL_ADAPTER,
UsbId.ARDUINO_SERIAL_ADAPTER_R3,
UsbId.ARDUINO_MEGA_ADK,
UsbId.ARDUINO_MEGA_ADK_R3,
UsbId.ARDUINO_LEONARDO,
});
supportedDevices.put(Integer.valueOf(UsbId.VENDOR_VAN_OOIJEN_TECH),
new int[] {
UsbId.VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL,
});
supportedDevices.put(Integer.valueOf(UsbId.VENDOR_ATMEL),
new int[] {
UsbId.ATMEL_LUFA_CDC_DEMO_APP,
});
supportedDevices.put(Integer.valueOf(UsbId.VENDOR_LEAFLABS),
new int[] {
UsbId.LEAFLABS_MAPLE,
});
supportedDevices.put(Integer.valueOf(UsbId.VENDOR_PX4),
new int[] {
UsbId.DEVICE_PX4FMU,
});
supportedDevices.put(Integer.valueOf(UsbId.VENDOR_UBLOX),
new int[] {
UsbId.DEVICE_UBLOX_5,
UsbId.DEVICE_UBLOX_6,
UsbId.DEVICE_UBLOX_7,
UsbId.DEVICE_UBLOX_8,
});
supportedDevices.put(Integer.valueOf(UsbId.VENDOR_OPENPILOT),
new int[] {
UsbId.DEVICE_CC3D,
UsbId.DEVICE_REVOLUTION,
UsbId.DEVICE_SPARKY2,
UsbId.DEVICE_OPLINK,
});
supportedDevices.put(Integer.valueOf(UsbId.VENDOR_ARDUPILOT_CHIBIOS1),
new int[] {
UsbId.DEVICE_ARDUPILOT_CHIBIOS,
});
supportedDevices.put(Integer.valueOf(UsbId.VENDOR_ARDUPILOT_CHIBIOS2),
new int[] {
UsbId.DEVICE_ARDUPILOT_CHIBIOS,
});
supportedDevices.put(Integer.valueOf(UsbId.VENDOR_DRAGONLINK),
new int[] {
UsbId.DEVICE_DRAGONLINK,
});
return supportedDevices;
}
}