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)

This commit is contained in:
Kai Morich 2019-02-03 10:22:53 +01:00
parent e527afdf35
commit 0d48ed04e7
10 changed files with 103 additions and 225 deletions

View File

@ -3,7 +3,7 @@
This is a driver library for communication with Arduinos and other USB serial hardware on This is a driver library for communication with Arduinos and other USB serial hardware on
Android, using the Android, using the
[Android USB Host API](http://developer.android.com/guide/topics/connectivity/usb/host.html) [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 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 Java. You get a raw serial port with `read()`, `write()`, and other basic

View File

@ -6,7 +6,7 @@ buildscript {
google() google()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.2.1' classpath 'com.android.tools.build:gradle:3.3.0'
} }
} }

View File

@ -1,6 +1,6 @@
#Sun Nov 11 09:16:07 CET 2018 #Sun Feb 03 09:37:03 CET 2019
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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

View File

@ -5,7 +5,7 @@ android {
buildToolsVersion '28.0.3' buildToolsVersion '28.0.3'
defaultConfig { defaultConfig {
minSdkVersion 14 minSdkVersion 17
targetSdkVersion 28 targetSdkVersion 28
testInstrumentationRunner "android.test.InstrumentationTestRunner" testInstrumentationRunner "android.test.InstrumentationTestRunner"

View File

@ -7,7 +7,7 @@ android {
buildToolsVersion '28.0.3' buildToolsVersion '28.0.3'
defaultConfig { defaultConfig {
minSdkVersion 14 minSdkVersion 17
targetSdkVersion 28 targetSdkVersion 28
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }

View File

@ -27,7 +27,6 @@ import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbRequest; import android.hardware.usb.UsbRequest;
import android.os.Build;
import android.util.Log; import android.util.Log;
import java.io.IOException; import java.io.IOException;
@ -69,7 +68,6 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
class CdcAcmSerialPort extends CommonUsbSerialPort { class CdcAcmSerialPort extends CommonUsbSerialPort {
private final boolean mEnableAsyncReads;
private UsbInterface mControlInterface; private UsbInterface mControlInterface;
private UsbInterface mDataInterface; private UsbInterface mDataInterface;
@ -92,7 +90,6 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
public CdcAcmSerialPort(UsbDevice device, int portNumber) { public CdcAcmSerialPort(UsbDevice device, int portNumber) {
super(device, portNumber); super(device, portNumber);
mEnableAsyncReads = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1);
} }
@Override @Override
@ -118,13 +115,6 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
openInterface(); openInterface();
} }
if (mEnableAsyncReads) {
Log.d(TAG, "Async reads enabled");
} else {
Log.d(TAG, "Async reads disabled.");
}
opened = true; opened = true;
} finally { } finally {
if (!opened) { if (!opened) {
@ -269,51 +259,29 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
@Override @Override
public int read(byte[] dest, int timeoutMillis) throws IOException { public int read(byte[] dest, int timeoutMillis) throws IOException {
if (mEnableAsyncReads) { final UsbRequest request = new UsbRequest();
final UsbRequest request = new UsbRequest(); try {
try {
request.initialize(mConnection, mReadEndpoint); request.initialize(mConnection, mReadEndpoint);
final ByteBuffer buf = ByteBuffer.wrap(dest); final ByteBuffer buf = ByteBuffer.wrap(dest);
if (!request.queue(buf, dest.length)) { if (!request.queue(buf, dest.length)) {
throw new IOException("Error queueing request."); throw new IOException("Error queueing request.");
} }
final UsbRequest response = mConnection.requestWait(); final UsbRequest response = mConnection.requestWait();
if (response == null) { if (response == null) {
throw new IOException("Null response"); throw new IOException("Null response");
} }
final int nread = buf.position(); final int nread = buf.position();
if (nread > 0) { if (nread > 0) {
//Log.d(TAG, HexDump.dumpHexString(dest, 0, Math.min(32, dest.length))); //Log.d(TAG, HexDump.dumpHexString(dest, 0, Math.min(32, dest.length)));
return nread; return nread;
} else { } 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; return 0;
} }
System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead); } finally {
request.close();
} }
return numBytesRead;
} }
@Override @Override

View File

@ -26,7 +26,6 @@ import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbRequest; import android.hardware.usb.UsbRequest;
import android.os.Build;
import android.util.Log; import android.util.Log;
import java.io.IOException; import java.io.IOException;
@ -84,14 +83,12 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
private boolean dtr = false; private boolean dtr = false;
private boolean rts = false; private boolean rts = false;
private final Boolean mEnableAsyncReads;
private UsbEndpoint mReadEndpoint; private UsbEndpoint mReadEndpoint;
private UsbEndpoint mWriteEndpoint; private UsbEndpoint mWriteEndpoint;
private UsbRequest mUsbRequest; private UsbRequest mUsbRequest;
public Ch340SerialPort(UsbDevice device, int portNumber) { public Ch340SerialPort(UsbDevice device, int portNumber) {
super(device, portNumber); super(device, portNumber);
mEnableAsyncReads = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1);
} }
@Override @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(); initialize();
setBaudRate(DEFAULT_BAUD_RATE); setBaudRate(DEFAULT_BAUD_RATE);
@ -153,12 +144,10 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
if (mConnection == null) { if (mConnection == null) {
throw new IOException("Already closed"); throw new IOException("Already closed");
} }
synchronized (mEnableAsyncReads) { synchronized (this) {
if (mUsbRequest != null) if (mUsbRequest != null)
mUsbRequest.cancel(); mUsbRequest.cancel();
} }
// TODO: nothing sended on close, maybe needed?
try { try {
mConnection.close(); mConnection.close();
} finally { } finally {
@ -169,50 +158,32 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
@Override @Override
public int read(byte[] dest, int timeoutMillis) throws IOException { public int read(byte[] dest, int timeoutMillis) throws IOException {
if (mEnableAsyncReads) { final UsbRequest request = new UsbRequest();
final UsbRequest request = new UsbRequest(); try {
try { request.initialize(mConnection, mReadEndpoint);
request.initialize(mConnection, mReadEndpoint); final ByteBuffer buf = ByteBuffer.wrap(dest);
final ByteBuffer buf = ByteBuffer.wrap(dest); if (!request.queue(buf, dest.length)) {
if (!request.queue(buf, dest.length)) { throw new IOException("Error queueing request.");
throw new IOException("Error queueing request."); }
} mUsbRequest = request;
mUsbRequest = request; final UsbRequest response = mConnection.requestWait();
final UsbRequest response = mConnection.requestWait(); synchronized (this) {
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 {
mUsbRequest = null; mUsbRequest = null;
request.close();
} }
} else { if (response == null) {
final int numBytesRead; throw new IOException("Null response");
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;
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();
} }
} }

View File

@ -27,7 +27,6 @@ import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbRequest; import android.hardware.usb.UsbRequest;
import android.os.Build;
import android.util.Log; import android.util.Log;
import java.io.IOException; 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_DTR = 0x0100;
private static final int CONTROL_WRITE_RTS = 0x0200; private static final int CONTROL_WRITE_RTS = 0x0200;
private final Boolean mEnableAsyncReads;
private UsbEndpoint mReadEndpoint; private UsbEndpoint mReadEndpoint;
private UsbEndpoint mWriteEndpoint; private UsbEndpoint mWriteEndpoint;
private UsbRequest mUsbRequest; private UsbRequest mUsbRequest;
@ -118,7 +116,6 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
public Cp21xxSerialPort(UsbDevice device, int portNumber) { public Cp21xxSerialPort(UsbDevice device, int portNumber) {
super(device, portNumber); super(device, portNumber);
mEnableAsyncReads = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1);
} }
@Override @Override
@ -180,13 +177,16 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
if (mConnection == null) { if (mConnection == null) {
throw new IOException("Already closed"); throw new IOException("Already closed");
} }
synchronized (mEnableAsyncReads) { synchronized (this) {
if(mUsbRequest != null) { if(mUsbRequest != null) {
mUsbRequest.cancel(); mUsbRequest.cancel();
} }
} }
try { try {
setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_DISABLE); setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_DISABLE);
} catch (Exception ignored)
{}
try {
mConnection.close(); mConnection.close();
} finally { } finally {
mConnection = null; mConnection = null;
@ -195,50 +195,32 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
@Override @Override
public int read(byte[] dest, int timeoutMillis) throws IOException { public int read(byte[] dest, int timeoutMillis) throws IOException {
if (mEnableAsyncReads) { final UsbRequest request = new UsbRequest();
final UsbRequest request = new UsbRequest(); try {
try { request.initialize(mConnection, mReadEndpoint);
request.initialize(mConnection, mReadEndpoint); final ByteBuffer buf = ByteBuffer.wrap(dest);
final ByteBuffer buf = ByteBuffer.wrap(dest); if (!request.queue(buf, dest.length)) {
if (!request.queue(buf, dest.length)) { throw new IOException("Error queueing request.");
throw new IOException("Error queueing request."); }
} mUsbRequest = request;
mUsbRequest = request; final UsbRequest response = mConnection.requestWait();
final UsbRequest response = mConnection.requestWait(); synchronized (this) {
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 {
mUsbRequest = null; mUsbRequest = null;
request.close();
} }
} else { if (response == null) {
final int numBytesRead; throw new IOException("Null response");
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;
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();
} }
} }

View File

@ -26,7 +26,6 @@ import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbRequest; import android.hardware.usb.UsbRequest;
import android.os.Build;
import android.util.Log; import android.util.Log;
import java.io.IOException; import java.io.IOException;
@ -186,16 +185,8 @@ public class FtdiSerialDriver implements UsbSerialDriver {
private int mIndex = 0; 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) { public FtdiSerialPort(UsbDevice device, int portNumber) {
super(device, portNumber); super(device, portNumber);
mEnableAsyncReads = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1);
} }
@Override @Override
@ -286,46 +277,28 @@ public class FtdiSerialDriver implements UsbSerialDriver {
@Override @Override
public int read(byte[] dest, int timeoutMillis) throws IOException { public int read(byte[] dest, int timeoutMillis) throws IOException {
final UsbEndpoint endpoint = mDevice.getInterface(mPortNumber).getEndpoint(0); final UsbEndpoint endpoint = mDevice.getInterface(mPortNumber).getEndpoint(0);
final UsbRequest request = new UsbRequest();
if (mEnableAsyncReads) { final ByteBuffer buf = ByteBuffer.wrap(dest);
final UsbRequest request = new UsbRequest(); try {
final ByteBuffer buf = ByteBuffer.wrap(dest); request.initialize(mConnection, endpoint);
try { if (!request.queue(buf, dest.length)) {
request.initialize(mConnection, endpoint); throw new IOException("Error queueing request.");
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 int totalBytesRead = buf.position(); final UsbRequest response = mConnection.requestWait();
if (totalBytesRead < MODEM_STATUS_HEADER_LENGTH) { if (response == null) {
throw new IOException("Expected at least " + MODEM_STATUS_HEADER_LENGTH + " bytes"); throw new IOException("Null response");
}
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());
} }
} 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 @Override

View File

@ -33,7 +33,6 @@ import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbRequest; import android.hardware.usb.UsbRequest;
import android.os.Build;
import android.util.Log; import android.util.Log;
import java.io.IOException; import java.io.IOException;
@ -112,7 +111,6 @@ public class ProlificSerialDriver implements UsbSerialDriver {
private int mDeviceType = DEVICE_TYPE_HX; private int mDeviceType = DEVICE_TYPE_HX;
private final boolean mEnableAsyncReads;
private UsbEndpoint mReadEndpoint; private UsbEndpoint mReadEndpoint;
private UsbEndpoint mWriteEndpoint; private UsbEndpoint mWriteEndpoint;
private UsbEndpoint mInterruptEndpoint; private UsbEndpoint mInterruptEndpoint;
@ -130,7 +128,6 @@ public class ProlificSerialDriver implements UsbSerialDriver {
public ProlificSerialPort(UsbDevice device, int portNumber) { public ProlificSerialPort(UsbDevice device, int portNumber) {
super(device, portNumber); super(device, portNumber);
mEnableAsyncReads = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1);
} }
@Override @Override
@ -373,41 +370,28 @@ public class ProlificSerialDriver implements UsbSerialDriver {
@Override @Override
public int read(byte[] dest, int timeoutMillis) throws IOException { public int read(byte[] dest, int timeoutMillis) throws IOException {
if (mEnableAsyncReads) { final UsbRequest request = new UsbRequest();
final UsbRequest request = new UsbRequest(); try {
try { request.initialize(mConnection, mReadEndpoint);
request.initialize(mConnection, mReadEndpoint); final ByteBuffer buf = ByteBuffer.wrap(dest);
final ByteBuffer buf = ByteBuffer.wrap(dest); if (!request.queue(buf, dest.length)) {
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");
}
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();
} }
} else {
synchronized (mReadBufferLock) { final UsbRequest response = mConnection.requestWait();
int readAmt = Math.min(dest.length, mReadBuffer.length); if (response == null) {
int numBytesRead = mConnection.bulkTransfer(mReadEndpoint, mReadBuffer, throw new IOException("Null response");
readAmt, timeoutMillis);
if (numBytesRead < 0) {
return 0;
}
System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead);
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 {
request.close();
} }
} }