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; } }