diff --git a/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/DeviceTest.java b/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/DeviceTest.java index 049f409..377b6dc 100644 --- a/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/DeviceTest.java +++ b/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/DeviceTest.java @@ -229,7 +229,7 @@ public class DeviceTest implements SerialInputOutputManager.Listener { private byte[] telnetRead(int expectedLength) throws Exception { long end = System.currentTimeMillis() + TELNET_READ_WAIT; - ByteBuffer buf = ByteBuffer.allocate(4096); + ByteBuffer buf = ByteBuffer.allocate(8192); while(System.currentTimeMillis() < end) { if(telnetReadStream.available() > 0) { buf.put((byte) telnetReadStream.read()); @@ -498,23 +498,21 @@ public class DeviceTest implements SerialInputOutputManager.Listener { doReadWrite(""); usbClose(); + usbSerialPort = usbSerialDriver.getPorts().get(test_device_port); try { usbSerialPort.close(); fail("already closed expected"); } catch (IOException ignored) { - } catch (NullPointerException ignored) { } try { usbWrite(new byte[]{0x00}); fail("write error expected"); } catch (IOException ignored) { - } catch (NullPointerException ignored) { } try { usbRead(1); - //fail("read error expected"); + fail("read error expected"); } catch (IOException ignored) { - } catch (NullPointerException ignored) { } try { usbParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE); @@ -522,11 +520,22 @@ public class DeviceTest implements SerialInputOutputManager.Listener { } catch (IOException ignored) { } catch (NullPointerException ignored) { } + usbSerialPort = null; usbOpen(true); telnetParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE); usbParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE); doReadWrite(""); + + // close before iomanager + assertEquals(SerialInputOutputManager.State.RUNNING, usbIoManager.getState()); + usbSerialPort.close(); + for (int i = 0; i < 1000; i++) { + if (usbIoManager.getState() == SerialInputOutputManager.State.STOPPED) + break; + Thread.sleep(1); + } + assertEquals(SerialInputOutputManager.State.STOPPED, usbIoManager.getState()); } @Test @@ -684,21 +693,21 @@ public class DeviceTest implements SerialInputOutputManager.Listener { usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); telnetParameters(19200, 7, 1, UsbSerialPort.PARITY_NONE); telnetWrite(new byte[] {0x00}); - Thread.sleep(1); // one bit is 0.05 milliseconds long, wait >> stop bit + Thread.sleep(10); // one bit is 0.05 milliseconds long, wait >> stop bit telnetWrite(new byte[] {(byte)0xff}); data = usbRead(2); assertThat("19200/7N1", data, equalTo(new byte[] {(byte)0x80, (byte)0xff})); telnetParameters(19200, 6, 1, UsbSerialPort.PARITY_NONE); telnetWrite(new byte[] {0x00}); - Thread.sleep(1); + Thread.sleep(10); telnetWrite(new byte[] {(byte)0xff}); data = usbRead(2); assertThat("19000/6N1", data, equalTo(new byte[] {(byte)0xc0, (byte)0xff})); telnetParameters(19200, 5, 1, UsbSerialPort.PARITY_NONE); telnetWrite(new byte[] {0x00}); - Thread.sleep(1); + Thread.sleep(10); telnetWrite(new byte[] {(byte)0xff}); data = usbRead(2); assertThat("19000/5N1", data, equalTo(new byte[] {(byte)0xe0, (byte)0xff})); @@ -708,7 +717,7 @@ public class DeviceTest implements SerialInputOutputManager.Listener { telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); usbParameters(19200, 7, 1, UsbSerialPort.PARITY_NONE); usbWrite(new byte[]{0x00}); - Thread.sleep(1); + Thread.sleep(10); usbWrite(new byte[]{(byte) 0xff}); data = telnetRead(2); assertThat("19000/7N1", data, equalTo(new byte[]{(byte) 0x80, (byte) 0xff})); @@ -719,7 +728,7 @@ public class DeviceTest implements SerialInputOutputManager.Listener { try { usbParameters(19200, 6, 1, UsbSerialPort.PARITY_NONE); usbWrite(new byte[]{0x00}); - Thread.sleep(1); + Thread.sleep(10); usbWrite(new byte[]{(byte) 0xff}); data = telnetRead(2); assertThat("19000/6N1", data, equalTo(new byte[]{(byte) 0xc0, (byte) 0xff})); @@ -730,7 +739,7 @@ public class DeviceTest implements SerialInputOutputManager.Listener { try { usbParameters(19200, 5, 1, UsbSerialPort.PARITY_NONE); usbWrite(new byte[] {0x00}); - Thread.sleep(1); + Thread.sleep(5); usbWrite(new byte[] {(byte)0xff}); data = telnetRead(2); assertThat("19000/5N1", data, equalTo(new byte[] {(byte)0xe0, (byte)0xff})); @@ -1217,5 +1226,109 @@ public class DeviceTest implements SerialInputOutputManager.Listener { assertTrue("not closed in time", i<100); Thread.sleep(1); } - } + } + + @Test + public void wrongDriver() throws Exception { + + UsbDeviceConnection wrongDeviceConnection; + UsbSerialDriver wrongSerialDriver; + UsbSerialPort wrongSerialPort; + + if(!(usbSerialDriver instanceof CdcAcmSerialDriver)) { + wrongDeviceConnection = usbManager.openDevice(usbSerialDriver.getDevice()); + wrongSerialDriver = new CdcAcmSerialDriver(usbSerialDriver.getDevice()); + wrongSerialPort = wrongSerialDriver.getPorts().get(0); + try { + wrongSerialPort.open(wrongDeviceConnection); + wrongSerialPort.setParameters(115200, UsbSerialPort.DATABITS_8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE); // ch340 fails here + wrongSerialPort.write(new byte[]{1}, 1000); // pl2302 does not fail, but sends with wrong baud rate + if(!(usbSerialDriver instanceof ProlificSerialDriver)) + fail("error expected"); + } catch (IOException ignored) { + } + try { + if(usbSerialDriver instanceof ProlificSerialDriver) { + assertNotEquals(new byte[]{1}, telnetRead()); + } + wrongSerialPort.close(); + if(!(usbSerialDriver instanceof Ch34xSerialDriver | + usbSerialDriver instanceof ProlificSerialDriver)) + fail("error expected"); + } catch (IOException ignored) { + } + } + if(!(usbSerialDriver instanceof Ch34xSerialDriver)) { + wrongDeviceConnection = usbManager.openDevice(usbSerialDriver.getDevice()); + wrongSerialDriver = new Ch34xSerialDriver(usbSerialDriver.getDevice()); + wrongSerialPort = wrongSerialDriver.getPorts().get(0); + try { + wrongSerialPort.open(wrongDeviceConnection); + fail("error expected"); + } catch (IOException ignored) { + } + try { + wrongSerialPort.close(); + fail("error expected"); + } catch (IOException ignored) { + } + } + // FTDI only recovers from Cp21xx control commands with power toggle, so skip this combination! + if(!(usbSerialDriver instanceof Cp21xxSerialDriver | usbSerialDriver instanceof FtdiSerialDriver)) { + wrongDeviceConnection = usbManager.openDevice(usbSerialDriver.getDevice()); + wrongSerialDriver = new Cp21xxSerialDriver(usbSerialDriver.getDevice()); + wrongSerialPort = wrongSerialDriver.getPorts().get(0); + try { + wrongSerialPort.open(wrongDeviceConnection); + //if(usbSerialDriver instanceof FtdiSerialDriver) + // wrongSerialPort.setParameters(115200, UsbSerialPort.DATABITS_8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE); // ch340 fails here + fail("error expected"); + } catch (IOException ignored) { + } + try { + wrongSerialPort.close(); + //if(!(usbSerialDriver instanceof FtdiSerialDriver)) + // fail("error expected"); + } catch (IOException ignored) { + } + } + if(!(usbSerialDriver instanceof FtdiSerialDriver)) { + wrongDeviceConnection = usbManager.openDevice(usbSerialDriver.getDevice()); + wrongSerialDriver = new FtdiSerialDriver(usbSerialDriver.getDevice()); + wrongSerialPort = wrongSerialDriver.getPorts().get(0); + try { + wrongSerialPort.open(wrongDeviceConnection); + if(usbSerialDriver instanceof Cp21xxSerialDriver) + wrongSerialPort.setParameters(115200, UsbSerialPort.DATABITS_8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE); // ch340 fails here + fail("error expected"); + } catch (IOException ignored) { + } + try { + wrongSerialPort.close(); + if(!(usbSerialDriver instanceof Cp21xxSerialDriver)) + fail("error expected"); + } catch (IOException ignored) { + } + } + if(!(usbSerialDriver instanceof ProlificSerialDriver)) { + wrongDeviceConnection = usbManager.openDevice(usbSerialDriver.getDevice()); + wrongSerialDriver = new ProlificSerialDriver(usbSerialDriver.getDevice()); + wrongSerialPort = wrongSerialDriver.getPorts().get(0); + try { + wrongSerialPort.open(wrongDeviceConnection); + fail("error expected"); + } catch (IOException ignored) { + } + try { + wrongSerialPort.close(); + fail("error expected"); + } catch (IOException ignored) { + } + } + // test that device recovers from wrong commands + usbOpen(true); + telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); + usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); + doReadWrite(""); + } } diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java index 773f355..6d17453 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java @@ -1,454 +1,455 @@ -/* Copyright 2011-2013 Google Inc. - * Copyright 2013 mike wakerly - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * 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.hardware.usb.UsbRequest; -import android.util.Log; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.Collections; -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 UsbSerialPort mPort; - private UsbRequest mUsbRequest; - - public CdcAcmSerialDriver(UsbDevice device) { - mDevice = device; - mPort = new CdcAcmSerialPort(device, 0); - } - - @Override - public UsbDevice getDevice() { - return mDevice; - } - - @Override - public List getPorts() { - return Collections.singletonList(mPort); - } - - class CdcAcmSerialPort extends CommonUsbSerialPort { - - private UsbInterface mControlInterface; - private UsbInterface mDataInterface; - - private UsbEndpoint mControlEndpoint; - private UsbEndpoint mReadEndpoint; - private UsbEndpoint mWriteEndpoint; - - 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 - public void open(UsbDeviceConnection connection) throws IOException { - if (mConnection != null) { - throw new IOException("Already open"); - } - - mConnection = connection; - boolean opened = false; - try { - - if (1 == mDevice.getInterfaceCount()) { - Log.d(TAG,"device might be castrated ACM device, trying single interface logic"); - openSingleInterface(); - } else { - Log.d(TAG,"trying default interface logic"); - openInterface(); - } - - opened = true; - } finally { - if (!opened) { - mConnection = null; - // just to be on the save side - mControlEndpoint = null; - mReadEndpoint = null; - mWriteEndpoint = null; - } - } - } - - 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); - Log.d(TAG, "Control iface=" + mControlInterface); - - mDataInterface = mDevice.getInterface(0); - Log.d(TAG, "data iface=" + mDataInterface); - - if (!mConnection.claimInterface(mControlInterface, true)) { - throw new IOException("Could not claim shared control/data interface."); - } - - int endCount = mControlInterface.getEndpointCount(); - - if (endCount < 3) { - Log.d(TAG,"not enough endpoints - need 3. count=" + mControlInterface.getEndpointCount()); - throw new IOException("Insufficient number of endpoints(" + mControlInterface.getEndpointCount() + ")"); - } - - // Analyse endpoints for their properties - mControlEndpoint = null; - mReadEndpoint = null; - mWriteEndpoint = null; - for (int i = 0; i < endCount; ++i) { - UsbEndpoint ep = mControlInterface.getEndpoint(i); - if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && - (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_INT)) { - Log.d(TAG,"Found controlling endpoint"); - mControlEndpoint = ep; - } else if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && - (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { - Log.d(TAG,"Found reading endpoint"); - mReadEndpoint = ep; - } else if ((ep.getDirection() == UsbConstants.USB_DIR_OUT) && - (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { - Log.d(TAG,"Found writing endpoint"); - mWriteEndpoint = ep; - } - - - if ((mControlEndpoint != null) && - (mReadEndpoint != null) && - (mWriteEndpoint != null)) { - Log.d(TAG,"Found all required endpoints"); - break; - } - } - - if ((mControlEndpoint == null) || - (mReadEndpoint == null) || - (mWriteEndpoint == null)) { - Log.d(TAG,"Could not establish all endpoints"); - throw new IOException("Could not establish all endpoints"); - } - } - - private void openInterface() throws IOException { - Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount()); - - mControlInterface = null; - mDataInterface = null; - for (int i = 0; i < mDevice.getInterfaceCount(); i++) { - UsbInterface usbInterface = mDevice.getInterface(i); - if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM) { - mControlIndex = i; - mControlInterface = usbInterface; - } - if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) { - mDataInterface = usbInterface; - } - } - - 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."); - } - - mReadEndpoint = null; - mWriteEndpoint = null; - 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; - } - if (mReadEndpoint == null || mWriteEndpoint == null) { - throw new IOException("Could not get read&write endpoints."); - } - } - - 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 - public void close() throws IOException { - if (mConnection == null) { - throw new IOException("Already closed"); - } - synchronized (this) { - if (mUsbRequest != null) - mUsbRequest.cancel(); - } - try { - mConnection.releaseInterface(mControlInterface); - mConnection.releaseInterface(mDataInterface); - } catch(Exception ignored) {} - try { - mConnection.close(); - } finally { - mConnection = null; - } - } - - @Override - public int read(byte[] dest, int timeoutMillis) throws IOException { - final UsbRequest request = new UsbRequest(); - try { - if(mConnection == null) - throw new IOException("Connection closed"); - request.initialize(mConnection, mReadEndpoint); - final ByteBuffer buf = ByteBuffer.wrap(dest); - if (!request.queue(buf, dest.length)) { - throw new IOException("Error queueing request."); - } - mUsbRequest = request; - final UsbRequest response = mConnection.requestWait(); - synchronized (this) { - mUsbRequest = null; - } - if (response == null) { - throw new IOException("Null response"); - } - - final int nread = buf.position(); - if (nread > 0) { - //Log.d(TAG, HexDump.dumpHexString(dest, 0, Math.min(32, dest.length))); - return nread; - } else { - return 0; - } - } finally { - mUsbRequest = null; - request.close(); - } - } - - @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) throws IOException { - 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() throws IOException { - 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(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; - } - -} +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * 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.hardware.usb.UsbRequest; +import android.util.Log; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Collections; +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 UsbSerialPort mPort; + private UsbRequest mUsbRequest; + + public CdcAcmSerialDriver(UsbDevice device) { + mDevice = device; + mPort = new CdcAcmSerialPort(device, 0); + } + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + @Override + public List getPorts() { + return Collections.singletonList(mPort); + } + + class CdcAcmSerialPort extends CommonUsbSerialPort { + + private UsbInterface mControlInterface; + private UsbInterface mDataInterface; + + private UsbEndpoint mControlEndpoint; + private UsbEndpoint mReadEndpoint; + private UsbEndpoint mWriteEndpoint; + + 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 + public void open(UsbDeviceConnection connection) throws IOException { + if (mConnection != null) { + throw new IOException("Already open"); + } + + mConnection = connection; + boolean opened = false; + try { + if (1 == mDevice.getInterfaceCount()) { + Log.d(TAG,"device might be castrated ACM device, trying single interface logic"); + openSingleInterface(); + } else { + Log.d(TAG,"trying default interface logic"); + openInterface(); + } + opened = true; + } finally { + if (!opened) { + close(); + } + } + } + + 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); + Log.d(TAG, "Control iface=" + mControlInterface); + + mDataInterface = mDevice.getInterface(0); + Log.d(TAG, "data iface=" + mDataInterface); + + if (!mConnection.claimInterface(mControlInterface, true)) { + throw new IOException("Could not claim shared control/data interface."); + } + + int endCount = mControlInterface.getEndpointCount(); + + if (endCount < 3) { + Log.d(TAG,"not enough endpoints - need 3. count=" + mControlInterface.getEndpointCount()); + throw new IOException("Insufficient number of endpoints(" + mControlInterface.getEndpointCount() + ")"); + } + + // Analyse endpoints for their properties + mControlEndpoint = null; + mReadEndpoint = null; + mWriteEndpoint = null; + for (int i = 0; i < endCount; ++i) { + UsbEndpoint ep = mControlInterface.getEndpoint(i); + if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && + (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_INT)) { + Log.d(TAG,"Found controlling endpoint"); + mControlEndpoint = ep; + } else if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && + (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { + Log.d(TAG,"Found reading endpoint"); + mReadEndpoint = ep; + } else if ((ep.getDirection() == UsbConstants.USB_DIR_OUT) && + (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { + Log.d(TAG,"Found writing endpoint"); + mWriteEndpoint = ep; + } + + + if ((mControlEndpoint != null) && + (mReadEndpoint != null) && + (mWriteEndpoint != null)) { + Log.d(TAG,"Found all required endpoints"); + break; + } + } + + if ((mControlEndpoint == null) || + (mReadEndpoint == null) || + (mWriteEndpoint == null)) { + Log.d(TAG,"Could not establish all endpoints"); + throw new IOException("Could not establish all endpoints"); + } + } + + private void openInterface() throws IOException { + Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount()); + + mControlInterface = null; + mDataInterface = null; + for (int i = 0; i < mDevice.getInterfaceCount(); i++) { + UsbInterface usbInterface = mDevice.getInterface(i); + if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM) { + mControlIndex = i; + mControlInterface = usbInterface; + } + if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) { + mDataInterface = usbInterface; + } + } + + 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."); + } + + mReadEndpoint = null; + mWriteEndpoint = null; + 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; + } + if (mReadEndpoint == null || mWriteEndpoint == null) { + throw new IOException("Could not get read&write endpoints."); + } + } + + 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 + public void close() throws IOException { + if (mConnection == null) { + throw new IOException("Already closed"); + } + synchronized (this) { + if (mUsbRequest != null) + mUsbRequest.cancel(); + } + mControlEndpoint = null; + mReadEndpoint = null; + mWriteEndpoint = null; + try { + mConnection.releaseInterface(mControlInterface); + mConnection.releaseInterface(mDataInterface); + } catch(Exception ignored) {} + try { + mConnection.close(); + } finally { + mConnection = null; + } + } + + @Override + public int read(byte[] dest, int timeoutMillis) throws IOException { + if(mConnection == null) { + throw new IOException("Connection closed"); + } + final UsbRequest request = new UsbRequest(); + try { + request.initialize(mConnection, mReadEndpoint); + final ByteBuffer buf = ByteBuffer.wrap(dest); + if (!request.queue(buf, dest.length)) { + throw new IOException("Error queueing request."); + } + mUsbRequest = request; + final UsbRequest response = mConnection.requestWait(); + synchronized (this) { + mUsbRequest = null; + } + if (response == null) { + throw new IOException("Null response"); + } + + final int nread = buf.position(); + if (nread > 0) { + //Log.d(TAG, HexDump.dumpHexString(dest, 0, Math.min(32, dest.length))); + return nread; + } else { + return 0; + } + } finally { + mUsbRequest = null; + request.close(); + } + } + + @Override + public int write(byte[] src, int timeoutMillis) throws IOException { + // TODO(mikey): Nearly identical to FtdiSerial write. Refactor. + int offset = 0; + + if(mConnection == null) { + throw new IOException("Connection closed"); + } + 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) throws IOException { + 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() throws IOException { + 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(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; + } + +} diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Ch34xSerialDriver.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Ch34xSerialDriver.java index 7b36171..0c7e16e 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Ch34xSerialDriver.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Ch34xSerialDriver.java @@ -130,11 +130,7 @@ public class Ch34xSerialDriver implements UsbSerialDriver { opened = true; } finally { if (!opened) { - try { - close(); - } catch (IOException e) { - // Ignore IOExceptions during close() - } + close(); } } } @@ -162,10 +158,11 @@ public class Ch34xSerialDriver implements UsbSerialDriver { @Override public int read(byte[] dest, int timeoutMillis) throws IOException { + if(mConnection == null) { + throw new IOException("Connection closed"); + } final UsbRequest request = new UsbRequest(); try { - if(mConnection == null) - throw new IOException("Connection closed"); request.initialize(mConnection, mReadEndpoint); final ByteBuffer buf = ByteBuffer.wrap(dest); if (!request.queue(buf, dest.length)) { @@ -197,6 +194,9 @@ public class Ch34xSerialDriver implements UsbSerialDriver { public int write(byte[] src, int timeoutMillis) throws IOException { int offset = 0; + if(mConnection == null) { + throw new IOException("Connection closed"); + } while (offset < src.length) { final int writeLength; final int amtWritten; diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Cp21xxSerialDriver.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Cp21xxSerialDriver.java index 11d9823..ae572f7 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Cp21xxSerialDriver.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Cp21xxSerialDriver.java @@ -123,9 +123,13 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { return Cp21xxSerialDriver.this; } - private int setConfigSingle(int request, int value) { - return mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, value, + private int setConfigSingle(int request, int value) throws IOException { + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, value, mPortNumber, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Setting baudrate failed: result=" + result); + } + return result; } @Override @@ -163,11 +167,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { opened = true; } finally { if (!opened) { - try { - close(); - } catch (IOException e) { - // Ignore IOExceptions during close() - } + close(); } } } @@ -197,10 +197,11 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { @Override public int read(byte[] dest, int timeoutMillis) throws IOException { + if(mConnection == null) { + throw new IOException("Connection closed"); + } final UsbRequest request = new UsbRequest(); try { - if(mConnection == null) - throw new IOException("Connection closed"); request.initialize(mConnection, mReadEndpoint); final ByteBuffer buf = ByteBuffer.wrap(dest); if (!request.queue(buf, dest.length)) { @@ -232,6 +233,9 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { public int write(byte[] src, int timeoutMillis) throws IOException { int offset = 0; + if(mConnection == null) { + throw new IOException("Connection closed"); + } while (offset < src.length) { final int writeLength; final int amtWritten; diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/FtdiSerialDriver.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/FtdiSerialDriver.java index 8dbdfd4..caffdb9 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/FtdiSerialDriver.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/FtdiSerialDriver.java @@ -257,7 +257,6 @@ public class FtdiSerialDriver implements UsbSerialDriver { } finally { if (!opened) { close(); - mConnection = null; } } } @@ -279,12 +278,13 @@ public class FtdiSerialDriver implements UsbSerialDriver { @Override public int read(byte[] dest, int timeoutMillis) throws IOException { + if(mConnection == null) { + throw new IOException("Connection closed"); + } final UsbEndpoint endpoint = mDevice.getInterface(mPortNumber).getEndpoint(0); final UsbRequest request = new UsbRequest(); final ByteBuffer buf = ByteBuffer.wrap(dest); try { - if(mConnection == null) - throw new IOException("Connection closed"); request.initialize(mConnection, endpoint); if (!request.queue(buf, dest.length)) { throw new IOException("Error queueing request."); @@ -308,6 +308,9 @@ public class FtdiSerialDriver implements UsbSerialDriver { @Override public int write(byte[] src, int timeoutMillis) throws IOException { + if(mConnection == null) { + throw new IOException("Connection closed"); + } final UsbEndpoint endpoint = mDevice.getInterface(mPortNumber).getEndpoint(1); int offset = 0; diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ProbeTable.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ProbeTable.java index db08019..4837366 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ProbeTable.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ProbeTable.java @@ -65,25 +65,19 @@ public class ProbeTable { try { method = driverClass.getMethod("getSupportedDevices"); - } catch (SecurityException e) { - throw new RuntimeException(e); - } catch (NoSuchMethodException e) { + } catch (SecurityException | NoSuchMethodException e) { throw new RuntimeException(e); } final Map devices; try { devices = (Map) method.invoke(null); - } catch (IllegalArgumentException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } catch (InvocationTargetException e) { + } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } for (Map.Entry entry : devices.entrySet()) { - final int vendorId = entry.getKey().intValue(); + final int vendorId = entry.getKey(); for (int productId : entry.getValue()) { addProduct(vendorId, productId, driverClass); } diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ProlificSerialDriver.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ProlificSerialDriver.java index d4928fc..fdbb044 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ProlificSerialDriver.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ProlificSerialDriver.java @@ -336,8 +336,7 @@ public class ProlificSerialDriver implements UsbSerialDriver { opened = true; } finally { if (!opened) { - mConnection = null; - connection.releaseInterface(usbInterface); + close(); } } } @@ -373,10 +372,11 @@ public class ProlificSerialDriver implements UsbSerialDriver { @Override public int read(byte[] dest, int timeoutMillis) throws IOException { + if(mConnection == null) { + throw new IOException("Connection closed"); + } final UsbRequest request = new UsbRequest(); try { - if(mConnection == null) - throw new IOException("Connection closed"); request.initialize(mConnection, mReadEndpoint); final ByteBuffer buf = ByteBuffer.wrap(dest); if (!request.queue(buf, dest.length)) { @@ -404,6 +404,9 @@ public class ProlificSerialDriver implements UsbSerialDriver { public int write(byte[] src, int timeoutMillis) throws IOException { int offset = 0; + if(mConnection == null) { + throw new IOException("Connection closed"); + } while (offset < src.length) { final int writeLength; final int amtWritten; diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbSerialProber.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbSerialProber.java index 333af65..72d3534 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbSerialProber.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbSerialProber.java @@ -95,15 +95,8 @@ public class UsbSerialProber { final Constructor ctor = driverClass.getConstructor(UsbDevice.class); driver = ctor.newInstance(usbDevice); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } catch (IllegalArgumentException e) { - throw new RuntimeException(e); - } catch (InstantiationException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } catch (InvocationTargetException e) { + } catch (NoSuchMethodException | IllegalArgumentException | InstantiationException | + IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } return driver;