/* Copyright 2011-2013 Google Inc. * Copyright 2013 mike wakerly * * Project home page: https://github.com/mik3y/usb-serial-for-android */ 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.ArrayList; import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.List; 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 implements UsbSerialDriver { private final String TAG = CdcAcmSerialDriver.class.getSimpleName(); private final UsbDevice mDevice; private final List mPorts; public CdcAcmSerialDriver(UsbDevice device) { mDevice = device; mPorts = new ArrayList<>(); int controlInterfaceCount = 0; int dataInterfaceCount = 0; for( int i = 0; i < device.getInterfaceCount(); i++) { if(device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_COMM) controlInterfaceCount++; if(device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) dataInterfaceCount++; } for( int port = 0; port < Math.min(controlInterfaceCount, dataInterfaceCount); port++) { mPorts.add(new CdcAcmSerialPort(mDevice, port)); } if(mPorts.size() == 0) { mPorts.add(new CdcAcmSerialPort(mDevice, -1)); } } @Override public UsbDevice getDevice() { return mDevice; } @Override public List getPorts() { return mPorts; } public class CdcAcmSerialPort extends CommonUsbSerialPort { private UsbInterface mControlInterface; private UsbInterface mDataInterface; private UsbEndpoint mControlEndpoint; private int mControlIndex; 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 CdcAcmSerialPort(UsbDevice device, int portNumber) { super(device, portNumber); } @Override public UsbSerialDriver getDriver() { return CdcAcmSerialDriver.this; } @Override protected void openInt(UsbDeviceConnection connection) throws IOException { if (mPortNumber == -1) { Log.d(TAG,"device might be castrated ACM device, trying single interface logic"); openSingleInterface(); } else { Log.d(TAG,"trying default interface logic"); openInterface(); } } private void openSingleInterface() throws IOException { // the following code is inspired by the cdc-acm driver in the linux kernel mControlIndex = 0; mControlInterface = mDevice.getInterface(0); mDataInterface = mDevice.getInterface(0); if (!mConnection.claimInterface(mControlInterface, true)) { throw new IOException("Could not claim shared control/data interface"); } for (int i = 0; i < mControlInterface.getEndpointCount(); ++i) { UsbEndpoint ep = mControlInterface.getEndpoint(i); if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_INT)) { mControlEndpoint = ep; } else if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { mReadEndpoint = ep; } else if ((ep.getDirection() == UsbConstants.USB_DIR_OUT) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { mWriteEndpoint = ep; } } if (mControlEndpoint == null) { throw new IOException("No control endpoint"); } } private void openInterface() throws IOException { Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount()); int controlInterfaceCount = 0; int dataInterfaceCount = 0; mControlInterface = null; mDataInterface = null; for (int i = 0; i < mDevice.getInterfaceCount(); i++) { UsbInterface usbInterface = mDevice.getInterface(i); if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM) { if(controlInterfaceCount == mPortNumber) { mControlIndex = i; mControlInterface = usbInterface; } controlInterfaceCount++; } if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) { if(dataInterfaceCount == mPortNumber) { mDataInterface = usbInterface; } dataInterfaceCount++; } } if(mControlInterface == null) { throw new IOException("No control interface"); } Log.d(TAG, "Control iface=" + mControlInterface); if (!mConnection.claimInterface(mControlInterface, true)) { throw new IOException("Could not claim control interface"); } mControlEndpoint = mControlInterface.getEndpoint(0); if (mControlEndpoint.getDirection() != UsbConstants.USB_DIR_IN || mControlEndpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_INT) { throw new IOException("Invalid control endpoint"); } if(mDataInterface == null) { throw new IOException("No data interface"); } Log.d(TAG, "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 ep = mDataInterface.getEndpoint(i); if (ep.getDirection() == UsbConstants.USB_DIR_IN && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) mReadEndpoint = ep; if (ep.getDirection() == UsbConstants.USB_DIR_OUT && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) mWriteEndpoint = ep; } } private int sendAcmControlMessage(int request, int value, byte[] buf) throws IOException { int len = mConnection.controlTransfer( USB_RT_ACM, request, value, mControlIndex, buf, buf != null ? buf.length : 0, 5000); if(len < 0) { throw new IOException("controlTransfer failed"); } return len; } @Override protected void closeInt() { try { mConnection.releaseInterface(mControlInterface); mConnection.releaseInterface(mDataInterface); } catch(Exception ignored) {} } @Override public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { if(baudRate <= 0) { throw new IllegalArgumentException("Invalid baud rate: " + baudRate); } if(dataBits < DATABITS_5 || dataBits > DATABITS_8) { throw new IllegalArgumentException("Invalid data bits: " + dataBits); } 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("Invalid stop bits: " + 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("Invalid 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() throws IOException { int value = (mRts ? 0x2 : 0) | (mDtr ? 0x1 : 0); sendAcmControlMessage(SET_CONTROL_LINE_STATE, value, null); } @Override public EnumSet getControlLines() throws IOException { EnumSet set = EnumSet.noneOf(ControlLine.class); if(mRts) set.add(ControlLine.RTS); if(mDtr) set.add(ControlLine.DTR); return set; } @Override public EnumSet getSupportedControlLines() throws IOException { return EnumSet.of(ControlLine.RTS, ControlLine.DTR); } } public static Map getSupportedDevices() { final Map supportedDevices = new LinkedHashMap(); supportedDevices.put(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, UsbId.ARDUINO_MICRO, }); supportedDevices.put(UsbId.VENDOR_VAN_OOIJEN_TECH, new int[] { UsbId.VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL, }); supportedDevices.put(UsbId.VENDOR_ATMEL, new int[] { UsbId.ATMEL_LUFA_CDC_DEMO_APP, }); supportedDevices.put(UsbId.VENDOR_LEAFLABS, new int[] { UsbId.LEAFLABS_MAPLE, }); supportedDevices.put(UsbId.VENDOR_ARM, new int[] { UsbId.ARM_MBED, }); return supportedDevices; } }