From 0d48ed04e7cf3ff83c8089d4dedf61f1ba7277be Mon Sep 17 00:00:00 2001 From: Kai Morich Date: Sun, 3 Feb 2019 10:22:53 +0100 Subject: [PATCH] Always use async read, as bulkTransfer can cause data loss. Increase API version to 17 because async read only works reliably since Android 4.2 (http://b.android.com/28023) --- README.md | 2 +- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 +- usbSerialExamples/build.gradle | 2 +- usbSerialForAndroid/build.gradle | 2 +- .../usbserial/driver/CdcAcmSerialDriver.java | 48 ++---------- .../usbserial/driver/Ch34xSerialDriver.java | 77 ++++++------------- .../usbserial/driver/Cp21xxSerialDriver.java | 72 +++++++---------- .../usbserial/driver/FtdiSerialDriver.java | 63 +++++---------- .../driver/ProlificSerialDriver.java | 56 +++++--------- 10 files changed, 103 insertions(+), 225 deletions(-) diff --git a/README.md b/README.md index 50a9551..e2fb7b2 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This is a driver library for communication with Arduinos and other USB serial hardware on Android, using the [Android USB Host API](http://developer.android.com/guide/topics/connectivity/usb/host.html) -available on Android 3.1+. +available since Android 3.1 and asynchronous interrupt transfer working reliably since Android 4.2 No root access, ADK, or special kernel drivers are required; all drivers are implemented in Java. You get a raw serial port with `read()`, `write()`, and other basic diff --git a/build.gradle b/build.gradle index f400f0c..f055c90 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.android.tools.build:gradle:3.3.0' } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 93a408c..b2a4404 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Nov 11 09:16:07 CET 2018 +#Sun Feb 03 09:37:03 CET 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip diff --git a/usbSerialExamples/build.gradle b/usbSerialExamples/build.gradle index fbcfcfa..b45091c 100644 --- a/usbSerialExamples/build.gradle +++ b/usbSerialExamples/build.gradle @@ -5,7 +5,7 @@ android { buildToolsVersion '28.0.3' defaultConfig { - minSdkVersion 14 + minSdkVersion 17 targetSdkVersion 28 testInstrumentationRunner "android.test.InstrumentationTestRunner" diff --git a/usbSerialForAndroid/build.gradle b/usbSerialForAndroid/build.gradle index 0e66b22..bbb2b57 100644 --- a/usbSerialForAndroid/build.gradle +++ b/usbSerialForAndroid/build.gradle @@ -7,7 +7,7 @@ android { buildToolsVersion '28.0.3' defaultConfig { - minSdkVersion 14 + minSdkVersion 17 targetSdkVersion 28 testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } 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 3f4b96d..a3d9a76 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 @@ -27,7 +27,6 @@ import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbRequest; -import android.os.Build; import android.util.Log; import java.io.IOException; @@ -69,7 +68,6 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { class CdcAcmSerialPort extends CommonUsbSerialPort { - private final boolean mEnableAsyncReads; private UsbInterface mControlInterface; private UsbInterface mDataInterface; @@ -92,7 +90,6 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { public CdcAcmSerialPort(UsbDevice device, int portNumber) { super(device, portNumber); - mEnableAsyncReads = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1); } @Override @@ -118,13 +115,6 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { openInterface(); } - if (mEnableAsyncReads) { - Log.d(TAG, "Async reads enabled"); - } else { - Log.d(TAG, "Async reads disabled."); - } - - opened = true; } finally { if (!opened) { @@ -269,51 +259,29 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { @Override public int read(byte[] dest, int timeoutMillis) throws IOException { - if (mEnableAsyncReads) { - final UsbRequest request = new UsbRequest(); - try { + 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."); + throw new IOException("Error queueing request."); } final UsbRequest response = mConnection.requestWait(); if (response == null) { - throw new IOException("Null response"); + 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; + //Log.d(TAG, HexDump.dumpHexString(dest, 0, Math.min(32, dest.length))); + return nread; } else { - return 0; - } - } finally { - request.close(); - } - } - - 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 - if (timeoutMillis == Integer.MAX_VALUE) { - // Hack: Special case "~infinite timeout" as an error. - return -1; - } return 0; } - System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead); + } finally { + request.close(); } - return numBytesRead; } @Override 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 48b26fc..0b0ae97 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 @@ -26,7 +26,6 @@ import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbRequest; -import android.os.Build; import android.util.Log; import java.io.IOException; @@ -84,14 +83,12 @@ public class Ch34xSerialDriver implements UsbSerialDriver { private boolean dtr = false; private boolean rts = false; - private final Boolean mEnableAsyncReads; private UsbEndpoint mReadEndpoint; private UsbEndpoint mWriteEndpoint; private UsbRequest mUsbRequest; public Ch340SerialPort(UsbDevice device, int portNumber) { super(device, portNumber); - mEnableAsyncReads = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1); } @Override @@ -127,12 +124,6 @@ public class Ch34xSerialDriver implements UsbSerialDriver { } } - if (mEnableAsyncReads) { - Log.d(TAG, "Async reads enabled"); - } else { - Log.d(TAG, "Async reads disabled."); - } - initialize(); setBaudRate(DEFAULT_BAUD_RATE); @@ -153,12 +144,10 @@ public class Ch34xSerialDriver implements UsbSerialDriver { if (mConnection == null) { throw new IOException("Already closed"); } - synchronized (mEnableAsyncReads) { + synchronized (this) { if (mUsbRequest != null) mUsbRequest.cancel(); } - // TODO: nothing sended on close, maybe needed? - try { mConnection.close(); } finally { @@ -169,50 +158,32 @@ public class Ch34xSerialDriver implements UsbSerialDriver { @Override public int read(byte[] dest, int timeoutMillis) throws IOException { - if (mEnableAsyncReads) { - 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 (mEnableAsyncReads) { - 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 { + 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; - request.close(); } - } else { - 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); + if (response == null) { + throw new IOException("Null response"); } - return numBytesRead; + + 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(); } } 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 2119130..4bc07ee 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 @@ -27,7 +27,6 @@ import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbRequest; -import android.os.Build; import android.util.Log; import java.io.IOException; @@ -107,7 +106,6 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { private static final int CONTROL_WRITE_DTR = 0x0100; private static final int CONTROL_WRITE_RTS = 0x0200; - private final Boolean mEnableAsyncReads; private UsbEndpoint mReadEndpoint; private UsbEndpoint mWriteEndpoint; private UsbRequest mUsbRequest; @@ -118,7 +116,6 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { public Cp21xxSerialPort(UsbDevice device, int portNumber) { super(device, portNumber); - mEnableAsyncReads = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1); } @Override @@ -180,13 +177,16 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { if (mConnection == null) { throw new IOException("Already closed"); } - synchronized (mEnableAsyncReads) { + synchronized (this) { if(mUsbRequest != null) { mUsbRequest.cancel(); } } try { setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_DISABLE); + } catch (Exception ignored) + {} + try { mConnection.close(); } finally { mConnection = null; @@ -195,50 +195,32 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { @Override public int read(byte[] dest, int timeoutMillis) throws IOException { - if (mEnableAsyncReads) { - 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 (mEnableAsyncReads) { - 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 { + 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; - request.close(); } - } else { - 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); + if (response == null) { + throw new IOException("Null response"); } - return numBytesRead; + + 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(); } } 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 757c567..ccdc25e 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 @@ -26,7 +26,6 @@ import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbRequest; -import android.os.Build; import android.util.Log; import java.io.IOException; @@ -186,16 +185,8 @@ public class FtdiSerialDriver implements UsbSerialDriver { private int mIndex = 0; - /** - * Due to http://b.android.com/28023 , we cannot use UsbRequest async reads - * since it gives no indication of number of bytes read. Set this to - * {@code true} on platforms where it is fixed. - */ - private final boolean mEnableAsyncReads; - public FtdiSerialPort(UsbDevice device, int portNumber) { super(device, portNumber); - mEnableAsyncReads = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1); } @Override @@ -286,46 +277,28 @@ public class FtdiSerialDriver implements UsbSerialDriver { @Override public int read(byte[] dest, int timeoutMillis) throws IOException { final UsbEndpoint endpoint = mDevice.getInterface(mPortNumber).getEndpoint(0); - - if (mEnableAsyncReads) { - final UsbRequest request = new UsbRequest(); - final ByteBuffer buf = ByteBuffer.wrap(dest); - try { - request.initialize(mConnection, endpoint); - if (!request.queue(buf, dest.length)) { - throw new IOException("Error queueing request."); - } - - final UsbRequest response = mConnection.requestWait(); - if (response == null) { - throw new IOException("Null response"); - } - } finally { - request.close(); + final UsbRequest request = new UsbRequest(); + final ByteBuffer buf = ByteBuffer.wrap(dest); + try { + request.initialize(mConnection, endpoint); + if (!request.queue(buf, dest.length)) { + throw new IOException("Error queueing request."); } - final int totalBytesRead = buf.position(); - if (totalBytesRead < MODEM_STATUS_HEADER_LENGTH) { - throw new IOException("Expected at least " + MODEM_STATUS_HEADER_LENGTH + " bytes"); - } - - return filterStatusBytes(dest, dest, totalBytesRead, endpoint.getMaxPacketSize()); - - } else { - final int totalBytesRead; - - synchronized (mReadBufferLock) { - final int readAmt = Math.min(dest.length, mReadBuffer.length); - totalBytesRead = mConnection.bulkTransfer(endpoint, mReadBuffer, - readAmt, timeoutMillis); - - if (totalBytesRead < MODEM_STATUS_HEADER_LENGTH) { - throw new IOException("Expected at least " + MODEM_STATUS_HEADER_LENGTH + " bytes"); - } - - return filterStatusBytes(mReadBuffer, dest, totalBytesRead, endpoint.getMaxPacketSize()); + final UsbRequest response = mConnection.requestWait(); + if (response == null) { + throw new IOException("Null response"); } + } finally { + request.close(); } + + final int totalBytesRead = buf.position(); + if (totalBytesRead < MODEM_STATUS_HEADER_LENGTH) { + throw new IOException("Expected at least " + MODEM_STATUS_HEADER_LENGTH + " bytes"); + } + + return filterStatusBytes(dest, dest, totalBytesRead, endpoint.getMaxPacketSize()); } @Override 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 550350c..c3c8b23 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 @@ -33,7 +33,6 @@ import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbRequest; -import android.os.Build; import android.util.Log; import java.io.IOException; @@ -112,7 +111,6 @@ public class ProlificSerialDriver implements UsbSerialDriver { private int mDeviceType = DEVICE_TYPE_HX; - private final boolean mEnableAsyncReads; private UsbEndpoint mReadEndpoint; private UsbEndpoint mWriteEndpoint; private UsbEndpoint mInterruptEndpoint; @@ -130,7 +128,6 @@ public class ProlificSerialDriver implements UsbSerialDriver { public ProlificSerialPort(UsbDevice device, int portNumber) { super(device, portNumber); - mEnableAsyncReads = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1); } @Override @@ -373,41 +370,28 @@ public class ProlificSerialDriver implements UsbSerialDriver { @Override public int read(byte[] dest, int timeoutMillis) throws IOException { - if (mEnableAsyncReads) { - 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."); - } - - final UsbRequest response = mConnection.requestWait(); - 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 { - request.close(); + 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."); } - } else { - synchronized (mReadBufferLock) { - int readAmt = Math.min(dest.length, mReadBuffer.length); - int numBytesRead = mConnection.bulkTransfer(mReadEndpoint, mReadBuffer, - readAmt, timeoutMillis); - if (numBytesRead < 0) { - return 0; - } - System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead); - return numBytesRead; + + final UsbRequest response = mConnection.requestWait(); + 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 { + request.close(); } }