mirror of
https://github.com/mik3y/usb-serial-for-android
synced 2025-06-07 16:06:10 +00:00
implement async read for all devices
This commit is contained in:
parent
adb22f718e
commit
0ea5b282b7
4
.idea/.gitignore
generated
vendored
Normal file
4
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
caches
|
||||||
|
codeStyles
|
||||||
|
libraries
|
||||||
|
workspace.xml
|
@ -6,7 +6,7 @@ buildscript {
|
|||||||
google()
|
google()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.0.1'
|
classpath 'com.android.tools.build:gradle:3.1.1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
|||||||
#Thu Oct 26 20:06:55 CEST 2017
|
#Tue Mar 27 21:28:01 CEST 2018
|
||||||
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.1-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
|
||||||
|
@ -2,7 +2,7 @@ apply plugin: 'com.android.application'
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 26
|
compileSdkVersion 26
|
||||||
buildToolsVersion "26.0.2"
|
buildToolsVersion '27.0.3'
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 14
|
minSdkVersion 14
|
||||||
|
@ -4,7 +4,7 @@ apply plugin: 'signing'
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 26
|
compileSdkVersion 26
|
||||||
buildToolsVersion "26.0.2"
|
buildToolsVersion '27.0.3'
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 14
|
minSdkVersion 14
|
||||||
@ -22,7 +22,7 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'com.android.support:support-annotations:27.1.0'
|
androidTestImplementation 'com.android.support:support-annotations:27.1.1'
|
||||||
androidTestImplementation 'com.android.support.test:runner:1.0.1'
|
androidTestImplementation 'com.android.support.test:runner:1.0.1'
|
||||||
androidTestImplementation 'commons-net:commons-net:3.6'
|
androidTestImplementation 'commons-net:commons-net:3.6'
|
||||||
androidTestImplementation 'org.apache.commons:commons-lang3:3.7'
|
androidTestImplementation 'org.apache.commons:commons-lang3:3.7'
|
||||||
|
@ -36,6 +36,7 @@ import android.hardware.usb.UsbManager;
|
|||||||
import android.support.test.InstrumentationRegistry;
|
import android.support.test.InstrumentationRegistry;
|
||||||
import android.support.test.runner.AndroidJUnit4;
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.os.Process;
|
||||||
|
|
||||||
import com.hoho.android.usbserial.driver.CdcAcmSerialDriver;
|
import com.hoho.android.usbserial.driver.CdcAcmSerialDriver;
|
||||||
import com.hoho.android.usbserial.driver.Ch34xSerialDriver;
|
import com.hoho.android.usbserial.driver.Ch34xSerialDriver;
|
||||||
@ -48,7 +49,6 @@ import com.hoho.android.usbserial.driver.UsbSerialPort;
|
|||||||
import com.hoho.android.usbserial.driver.UsbSerialProber;
|
import com.hoho.android.usbserial.driver.UsbSerialProber;
|
||||||
import com.hoho.android.usbserial.util.SerialInputOutputManager;
|
import com.hoho.android.usbserial.util.SerialInputOutputManager;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.commons.net.telnet.InvalidTelnetOptionException;
|
import org.apache.commons.net.telnet.InvalidTelnetOptionException;
|
||||||
import org.apache.commons.net.telnet.TelnetClient;
|
import org.apache.commons.net.telnet.TelnetClient;
|
||||||
import org.apache.commons.net.telnet.TelnetCommand;
|
import org.apache.commons.net.telnet.TelnetCommand;
|
||||||
@ -79,13 +79,15 @@ import static org.junit.Assert.fail;
|
|||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class DeviceTest implements SerialInputOutputManager.Listener {
|
public class DeviceTest implements SerialInputOutputManager.Listener {
|
||||||
|
|
||||||
private final static String rfc2217_server_host = "192.168.0.171";
|
private final static String rfc2217_server_host = "192.168.0.100";
|
||||||
private final static int rfc2217_server_port = 2217;
|
private final static int rfc2217_server_port = 2217;
|
||||||
private final static boolean rfc2217_server_nonstandard_baudrates = false; // false on Windows
|
private final static boolean rfc2217_server_nonstandard_baudrates = false; // false on Windows, Raspi
|
||||||
|
private final static boolean rfc2217_server_parity_mark_space = false; // false on Raspi
|
||||||
|
|
||||||
private final static int TELNET_READ_WAIT = 500;
|
private final static int TELNET_READ_WAIT = 500;
|
||||||
private final static int USB_READ_WAIT = 500;
|
private final static int USB_READ_WAIT = 500;
|
||||||
private final static int USB_WRITE_WAIT = 500;
|
private final static int USB_WRITE_WAIT = 500;
|
||||||
|
private final static Integer SERIAL_INPUT_OUTPUT_MANAGER_THREAD_PRIORITY = Process.THREAD_PRIORITY_URGENT_AUDIO;
|
||||||
|
|
||||||
private final static String TAG = "DeviceTest";
|
private final static String TAG = "DeviceTest";
|
||||||
private final static byte RFC2217_COM_PORT_OPTION = 0x2c;
|
private final static byte RFC2217_COM_PORT_OPTION = 0x2c;
|
||||||
@ -101,6 +103,7 @@ public class DeviceTest implements SerialInputOutputManager.Listener {
|
|||||||
private SerialInputOutputManager usbIoManager;
|
private SerialInputOutputManager usbIoManager;
|
||||||
private final Deque<byte[]> usbReadBuffer = new LinkedList<>();
|
private final Deque<byte[]> usbReadBuffer = new LinkedList<>();
|
||||||
private boolean usbReadBlock = false;
|
private boolean usbReadBlock = false;
|
||||||
|
private long usbReadTime = 0;
|
||||||
|
|
||||||
private static TelnetClient telnetClient;
|
private static TelnetClient telnetClient;
|
||||||
private static InputStream telnetReadStream;
|
private static InputStream telnetReadStream;
|
||||||
@ -137,7 +140,7 @@ public class DeviceTest implements SerialInputOutputManager.Listener {
|
|||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
setUpFixtureInt();
|
setUpFixtureInt();
|
||||||
telnetClient.sendAYT(1000); // not corrctly handled by rfc2217_server.py, but WARNING output "ignoring Telnet command: '\xf6'" is a nice separator between tests
|
telnetClient.sendAYT(1000); // not correctly handled by rfc2217_server.py, but WARNING output "ignoring Telnet command: '\xf6'" is a nice separator between tests
|
||||||
telnetComPortOptionCounter[0] = 0;
|
telnetComPortOptionCounter[0] = 0;
|
||||||
telnetWriteDelay = 0;
|
telnetWriteDelay = 0;
|
||||||
|
|
||||||
@ -175,7 +178,15 @@ public class DeviceTest implements SerialInputOutputManager.Listener {
|
|||||||
usbSerialPort.open(usbDeviceConnection);
|
usbSerialPort.open(usbDeviceConnection);
|
||||||
usbSerialPort.setDTR(true);
|
usbSerialPort.setDTR(true);
|
||||||
usbSerialPort.setRTS(true);
|
usbSerialPort.setRTS(true);
|
||||||
usbIoManager = new SerialInputOutputManager(usbSerialPort, this);
|
usbIoManager = new SerialInputOutputManager(usbSerialPort, this) {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if(SERIAL_INPUT_OUTPUT_MANAGER_THREAD_PRIORITY != null)
|
||||||
|
Process.setThreadPriority(SERIAL_INPUT_OUTPUT_MANAGER_THREAD_PRIORITY);
|
||||||
|
super.run();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Executors.newSingleThreadExecutor().submit(usbIoManager);
|
Executors.newSingleThreadExecutor().submit(usbIoManager);
|
||||||
|
|
||||||
synchronized (usbReadBuffer) {
|
synchronized (usbReadBuffer) {
|
||||||
@ -263,7 +274,7 @@ public class DeviceTest implements SerialInputOutputManager.Listener {
|
|||||||
|
|
||||||
private byte[] usbRead(int expectedLength) throws Exception {
|
private byte[] usbRead(int expectedLength) throws Exception {
|
||||||
long end = System.currentTimeMillis() + USB_READ_WAIT;
|
long end = System.currentTimeMillis() + USB_READ_WAIT;
|
||||||
ByteBuffer buf = ByteBuffer.allocate(4096);
|
ByteBuffer buf = ByteBuffer.allocate(8192);
|
||||||
if(usbIoManager != null) {
|
if(usbIoManager != null) {
|
||||||
while (System.currentTimeMillis() < end) {
|
while (System.currentTimeMillis() < end) {
|
||||||
synchronized (usbReadBuffer) {
|
synchronized (usbReadBuffer) {
|
||||||
@ -335,6 +346,16 @@ public class DeviceTest implements SerialInputOutputManager.Listener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNewData(byte[] data) {
|
public void onNewData(byte[] data) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if(usbReadTime == 0)
|
||||||
|
usbReadTime = now;
|
||||||
|
if(data.length > 64) {
|
||||||
|
Log.d(TAG, "usb read: time+=" + String.format("%-3d",now-usbReadTime) + " len=" + String.format("%-4d",data.length) + " data=" + new String(data, 0, 32) + "..." + new String(data, data.length-32, 32));
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "usb read: time+=" + String.format("%-3d",now-usbReadTime) + " len=" + String.format("%-4d",data.length) + " data=" + new String(data));
|
||||||
|
}
|
||||||
|
usbReadTime = now;
|
||||||
|
|
||||||
while(usbReadBlock)
|
while(usbReadBlock)
|
||||||
try {
|
try {
|
||||||
Thread.sleep(1);
|
Thread.sleep(1);
|
||||||
@ -351,6 +372,32 @@ public class DeviceTest implements SerialInputOutputManager.Listener {
|
|||||||
assertTrue("usb connection lost", false);
|
assertTrue("usb connection lost", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clone of org.apache.commons.lang3.StringUtils.indexOfDifference + optional startpos
|
||||||
|
private static int indexOfDifference(final CharSequence cs1, final CharSequence cs2) {
|
||||||
|
return indexOfDifference(cs1, cs2, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int indexOfDifference(final CharSequence cs1, final CharSequence cs2, int cs1startpos, int cs2startpos) {
|
||||||
|
if (cs1 == cs2) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (cs1 == null || cs2 == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(cs1startpos < 0 || cs2startpos < 0)
|
||||||
|
return -1;
|
||||||
|
int i, j;
|
||||||
|
for (i = cs1startpos, j = cs2startpos; i < cs1.length() && j < cs2.length(); ++i, ++j) {
|
||||||
|
if (cs1.charAt(i) != cs2.charAt(j)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (j < cs2.length() || i < cs1.length()) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void baudRate() throws Exception {
|
public void baudRate() throws Exception {
|
||||||
@ -565,7 +612,7 @@ public class DeviceTest implements SerialInputOutputManager.Listener {
|
|||||||
|
|
||||||
if (usbSerialDriver instanceof CdcAcmSerialDriver) {
|
if (usbSerialDriver instanceof CdcAcmSerialDriver) {
|
||||||
// not supported by arduino_leonardo_bridge.ino, other devices might support it
|
// not supported by arduino_leonardo_bridge.ino, other devices might support it
|
||||||
} else {
|
} else if (rfc2217_server_parity_mark_space) {
|
||||||
usbParameters(19200, 7, 1, UsbSerialPort.PARITY_MARK);
|
usbParameters(19200, 7, 1, UsbSerialPort.PARITY_MARK);
|
||||||
usbWrite(_8n1);
|
usbWrite(_8n1);
|
||||||
data = telnetRead(4);
|
data = telnetRead(4);
|
||||||
@ -597,16 +644,17 @@ public class DeviceTest implements SerialInputOutputManager.Listener {
|
|||||||
if (usbSerialDriver instanceof CdcAcmSerialDriver) {
|
if (usbSerialDriver instanceof CdcAcmSerialDriver) {
|
||||||
// not supported by arduino_leonardo_bridge.ino, other devices might support it
|
// not supported by arduino_leonardo_bridge.ino, other devices might support it
|
||||||
} else {
|
} else {
|
||||||
telnetParameters(19200, 7, 1, UsbSerialPort.PARITY_MARK);
|
if (rfc2217_server_parity_mark_space) {
|
||||||
telnetWrite(_8n1);
|
telnetParameters(19200, 7, 1, UsbSerialPort.PARITY_MARK);
|
||||||
data = usbRead(4);
|
telnetWrite(_8n1);
|
||||||
assertThat("19200/7M1", data, equalTo(_7m1));
|
data = usbRead(4);
|
||||||
|
assertThat("19200/7M1", data, equalTo(_7m1));
|
||||||
telnetParameters(19200, 7, 1, UsbSerialPort.PARITY_SPACE);
|
|
||||||
telnetWrite(_8n1);
|
|
||||||
data = usbRead(4);
|
|
||||||
assertThat("19200/7S1", data, equalTo(_7s1));
|
|
||||||
|
|
||||||
|
telnetParameters(19200, 7, 1, UsbSerialPort.PARITY_SPACE);
|
||||||
|
telnetWrite(_8n1);
|
||||||
|
data = usbRead(4);
|
||||||
|
assertThat("19200/7S1", data, equalTo(_7s1));
|
||||||
|
}
|
||||||
usbParameters(19200, 7, 1, UsbSerialPort.PARITY_ODD);
|
usbParameters(19200, 7, 1, UsbSerialPort.PARITY_ODD);
|
||||||
telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
|
telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
|
||||||
telnetWrite(_8n1);
|
telnetWrite(_8n1);
|
||||||
@ -635,23 +683,23 @@ public class DeviceTest implements SerialInputOutputManager.Listener {
|
|||||||
// o - stop bit (1)
|
// o - stop bit (1)
|
||||||
// d - data bit
|
// d - data bit
|
||||||
|
|
||||||
// out 8N2: addddddd doadddddddoo
|
// out 8N2: addddddd doaddddddddo
|
||||||
// 1000001 0 1
|
// 1000001 0 10001111
|
||||||
// in 6N1: addddddo adddddo
|
// in 6N1: addddddo addddddo
|
||||||
// 100000 101
|
// 100000 101000
|
||||||
usbParameters(19200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);
|
usbParameters(19200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);
|
||||||
telnetParameters(19200, 6, 1, UsbSerialPort.PARITY_NONE);
|
telnetParameters(19200, 6, 1, UsbSerialPort.PARITY_NONE);
|
||||||
usbWrite(new byte[]{65, 1});
|
usbWrite(new byte[]{(byte)0x41, (byte)0xf1});
|
||||||
data = telnetRead(2);
|
data = telnetRead(2);
|
||||||
assertThat("19200/8N1", data, equalTo(new byte[]{1, 5}));
|
assertThat("19200/8N1", data, equalTo(new byte[]{1, 5}));
|
||||||
|
|
||||||
// out 8N2: addddddd dooadddddddoo
|
// out 8N2: addddddd dooaddddddddoo
|
||||||
// 1000001 0 1
|
// 1000001 0 10011111
|
||||||
// in 6N1: addddddo addddddo
|
// in 6N1: addddddo addddddo
|
||||||
// 100000 1101
|
// 100000 110100
|
||||||
usbParameters(19200, 8, UsbSerialPort.STOPBITS_2, UsbSerialPort.PARITY_NONE);
|
usbParameters(19200, 8, UsbSerialPort.STOPBITS_2, UsbSerialPort.PARITY_NONE);
|
||||||
telnetParameters(19200, 6, 1, UsbSerialPort.PARITY_NONE);
|
telnetParameters(19200, 6, 1, UsbSerialPort.PARITY_NONE);
|
||||||
usbWrite(new byte[]{65, 1});
|
usbWrite(new byte[]{(byte)0x41, (byte)0xf9});
|
||||||
data = telnetRead(2);
|
data = telnetRead(2);
|
||||||
assertThat("19200/8N1", data, equalTo(new byte[]{1, 11}));
|
assertThat("19200/8N1", data, equalTo(new byte[]{1, 11}));
|
||||||
// todo: could create similar test for 1.5 stopbits, by reading at double speed
|
// todo: could create similar test for 1.5 stopbits, by reading at double speed
|
||||||
@ -705,7 +753,7 @@ public class DeviceTest implements SerialInputOutputManager.Listener {
|
|||||||
Log.i(TAG, "bufferSize " + bufferSize);
|
Log.i(TAG, "bufferSize " + bufferSize);
|
||||||
usbReadBlock = true;
|
usbReadBlock = true;
|
||||||
for (linenr = 0; linenr < bufferSize/8; linenr++) {
|
for (linenr = 0; linenr < bufferSize/8; linenr++) {
|
||||||
line = String.format("%06d\r\n", linenr);
|
line = String.format("%07d,", linenr);
|
||||||
telnetWrite(line.getBytes());
|
telnetWrite(line.getBytes());
|
||||||
expected.append(line);
|
expected.append(line);
|
||||||
}
|
}
|
||||||
@ -714,7 +762,7 @@ public class DeviceTest implements SerialInputOutputManager.Listener {
|
|||||||
// slowly write new data, until old data is comletely read from buffer and new data is received again
|
// slowly write new data, until old data is comletely read from buffer and new data is received again
|
||||||
boolean found = false;
|
boolean found = false;
|
||||||
for (; linenr < bufferSize/8 + maxWait/10 && !found; linenr++) {
|
for (; linenr < bufferSize/8 + maxWait/10 && !found; linenr++) {
|
||||||
line = String.format("%06d\r\n", linenr);
|
line = String.format("%07d,", linenr);
|
||||||
telnetWrite(line.getBytes());
|
telnetWrite(line.getBytes());
|
||||||
Thread.sleep(10);
|
Thread.sleep(10);
|
||||||
expected.append(line);
|
expected.append(line);
|
||||||
@ -731,7 +779,7 @@ public class DeviceTest implements SerialInputOutputManager.Listener {
|
|||||||
if (data.length() != expected.length())
|
if (data.length() != expected.length())
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
int pos = StringUtils.indexOfDifference(data, expected);
|
int pos = indexOfDifference(data, expected);
|
||||||
Log.i(TAG, "bufferSize " + bufferSize + ", first difference at " + pos);
|
Log.i(TAG, "bufferSize " + bufferSize + ", first difference at " + pos);
|
||||||
// actual values have large variance for same device, e.g.
|
// actual values have large variance for same device, e.g.
|
||||||
// bufferSize 4096, first difference at 164
|
// bufferSize 4096, first difference at 164
|
||||||
@ -740,21 +788,30 @@ public class DeviceTest implements SerialInputOutputManager.Listener {
|
|||||||
assertTrue(data.length() != expected.length());
|
assertTrue(data.length() != expected.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// this test can fail sporadically!
|
||||||
|
//
|
||||||
|
// Android is not a real time OS, so there is no guarantee that the usb thread is scheduled, or it might be blocked by Java garbage collection.
|
||||||
|
// The SerialInputOutputManager uses a buffer size of 4Kb. Reading of these blocks happen behind UsbRequest.queue / UsbDeviceConnection.requestWait
|
||||||
|
// The dump of data and error positions in logcat show, that data is lost somewhere in the UsbRequest handling,
|
||||||
|
// very likely when the individual 64 byte USB packets are not read fast enough, and the serial converter chip has to discard bytes.
|
||||||
|
//
|
||||||
|
// On some days SERIAL_INPUT_OUTPUT_MANAGER_THREAD_PRIORITY=THREAD_PRIORITY_URGENT_AUDIO reduced errors by factor 10, on other days it had no effect at all!
|
||||||
|
//
|
||||||
@Test
|
@Test
|
||||||
// see logcat for performance results
|
|
||||||
public void readSpeed() throws Exception {
|
public void readSpeed() throws Exception {
|
||||||
|
// see logcat for performance results
|
||||||
|
//
|
||||||
// CDC arduino_leonardo_bridge.ini has transfer speed ~ 100 byte/sec
|
// CDC arduino_leonardo_bridge.ini has transfer speed ~ 100 byte/sec
|
||||||
// all other devices are near physical limit with ~ 10-12k/sec
|
// all other devices are near physical limit with ~ 10-12k/sec
|
||||||
|
int baudrate = 115200;
|
||||||
|
usbParameters(baudrate, 8, 1, UsbSerialPort.PARITY_NONE);
|
||||||
|
telnetParameters(baudrate, 8, 1, UsbSerialPort.PARITY_NONE);
|
||||||
|
|
||||||
// CH340 w/o asyncReads (bulkTransfer) is much slower and fails reproducibly here
|
// fails more likely with larger or unlimited (-1) write ahead
|
||||||
// FTDI w/o asyncReads (bulkTransfer) does not continue to read after ~2k
|
int writeSeconds = 5;
|
||||||
// CP2102 and PL2303 do not have data loss issues with bulkTransfer
|
int writeAhead = 5*baudrate/10; // write ahead for another 5 second read
|
||||||
usbParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
|
|
||||||
telnetParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
|
|
||||||
|
|
||||||
// limited write ahead to avoid buffer overrun
|
|
||||||
// with unlimited write ahead all devices fail sporadically. is it windows/device/usb-buffer overrun?
|
|
||||||
int writeAhead = 2000;
|
|
||||||
if(usbSerialDriver instanceof CdcAcmSerialDriver)
|
if(usbSerialDriver instanceof CdcAcmSerialDriver)
|
||||||
writeAhead = 50;
|
writeAhead = 50;
|
||||||
|
|
||||||
@ -763,14 +820,14 @@ public class DeviceTest implements SerialInputOutputManager.Listener {
|
|||||||
StringBuilder data = new StringBuilder();
|
StringBuilder data = new StringBuilder();
|
||||||
StringBuilder expected = new StringBuilder();
|
StringBuilder expected = new StringBuilder();
|
||||||
int dlen = 0, elen = 0;
|
int dlen = 0, elen = 0;
|
||||||
Log.i(TAG, "readSpeed: 'in' should be near "+115200/10);
|
Log.i(TAG, "readSpeed: 'read' should be near "+baudrate/10);
|
||||||
long begin = System.currentTimeMillis();
|
long begin = System.currentTimeMillis();
|
||||||
long next = System.currentTimeMillis();
|
long next = System.currentTimeMillis();
|
||||||
for(int seconds=1; seconds<=5; seconds++) {
|
for(int seconds=1; seconds <= writeSeconds; seconds++) {
|
||||||
next += 1000;
|
next += 1000;
|
||||||
while (System.currentTimeMillis() < next) {
|
while (System.currentTimeMillis() < next) {
|
||||||
if(expected.length() < data.length() + writeAhead) {
|
if((writeAhead < 0) || (expected.length() < data.length() + writeAhead)) {
|
||||||
line = String.format("%06d\r\n", linenr++);
|
line = String.format("%07d,", linenr++);
|
||||||
telnetWrite(line.getBytes());
|
telnetWrite(line.getBytes());
|
||||||
expected.append(line);
|
expected.append(line);
|
||||||
} else {
|
} else {
|
||||||
@ -778,31 +835,55 @@ public class DeviceTest implements SerialInputOutputManager.Listener {
|
|||||||
}
|
}
|
||||||
data.append(new String(usbRead(0)));
|
data.append(new String(usbRead(0)));
|
||||||
}
|
}
|
||||||
Log.i(TAG, "readSpeed: t="+(next-begin)+", in="+(data.length()-dlen)+", out="+(expected.length()-elen));
|
Log.i(TAG, "readSpeed: t="+(next-begin)+", read="+(data.length()-dlen)+", write="+(expected.length()-elen));
|
||||||
dlen = data.length();
|
dlen = data.length();
|
||||||
elen = expected.length();
|
elen = expected.length();
|
||||||
}
|
}
|
||||||
boolean found = false;
|
boolean found = false;
|
||||||
for (linenr=0; linenr < 2000 && !found; linenr++) {
|
long maxwait = Math.max(1000, (expected.length() - data.length()) * 20000L / baudrate );
|
||||||
|
next = System.currentTimeMillis() + maxwait;
|
||||||
|
Log.d(TAG, "readSpeed: rest wait time " + maxwait + " for " + (expected.length() - data.length()) + " byte");
|
||||||
|
while(!found && System.currentTimeMillis() < next) {
|
||||||
data.append(new String(usbRead(0)));
|
data.append(new String(usbRead(0)));
|
||||||
Thread.sleep(1);
|
|
||||||
found = data.toString().endsWith(line);
|
found = data.toString().endsWith(line);
|
||||||
|
Thread.sleep(1);
|
||||||
}
|
}
|
||||||
next = System.currentTimeMillis();
|
//next = System.currentTimeMillis();
|
||||||
//Log.i(TAG, "readSpeed: t="+(next-begin)+", in="+(data.length()-dlen));
|
//Log.i(TAG, "readSpeed: t="+(next-begin)+", read="+(data.length()-dlen));
|
||||||
assertTrue(found);
|
|
||||||
int pos = StringUtils.indexOfDifference(data, expected);
|
int errcnt = 0;
|
||||||
if(pos!=-1) {
|
int errlen = 0;
|
||||||
Log.i(TAG, "readSpeed: first difference at " + pos);
|
int datapos = indexOfDifference(data, expected);
|
||||||
String datasub = data.substring(Math.max(pos - 20, 0), Math.min(pos + 20, data.length()));
|
int expectedpos = datapos;
|
||||||
String expectedsub = expected.substring(Math.max(pos - 20, 0), Math.min(pos + 20, expected.length()));
|
while(datapos != -1) {
|
||||||
assertThat(datasub, equalTo(expectedsub));
|
errcnt += 1;
|
||||||
|
int nextexpectedpos = -1;
|
||||||
|
int nextdatapos = datapos + 2;
|
||||||
|
int len = -1;
|
||||||
|
if(nextdatapos + 10 < data.length()) { // try to sync data+expected, assuming that data is lost, but not corrupted
|
||||||
|
String nextsub = data.substring(nextdatapos, nextdatapos + 10);
|
||||||
|
nextexpectedpos = expected.indexOf(nextsub, expectedpos);
|
||||||
|
if(nextexpectedpos >= 0) {
|
||||||
|
len = nextexpectedpos - expectedpos - 2;
|
||||||
|
errlen += len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.i(TAG, "readSpeed: difference at " + datapos + " len " + len );
|
||||||
|
Log.d(TAG, "readSpeed: got " + data.substring(Math.max(datapos - 20, 0), Math.min(datapos + 20, data.length())));
|
||||||
|
Log.d(TAG, "readSpeed: expected " + expected.substring(Math.max(expectedpos - 20, 0), Math.min(expectedpos + 20, expected.length())));
|
||||||
|
datapos = indexOfDifference(data, expected, nextdatapos, nextexpectedpos);
|
||||||
|
expectedpos = nextexpectedpos + (datapos - nextdatapos);
|
||||||
}
|
}
|
||||||
|
if(errcnt != 0)
|
||||||
|
Log.i(TAG, "readSpeed: got " + errcnt + " errors, total len " + errlen+ ", avg. len " + errlen/errcnt);
|
||||||
|
assertTrue("end not found", found);
|
||||||
|
assertEquals("no errors", 0, errcnt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
// see logcat for performance results
|
|
||||||
public void writeSpeed() throws Exception {
|
public void writeSpeed() throws Exception {
|
||||||
|
// see logcat for performance results
|
||||||
|
//
|
||||||
// CDC arduino_leonardo_bridge.ini has transfer speed ~ 100 byte/sec
|
// CDC arduino_leonardo_bridge.ini has transfer speed ~ 100 byte/sec
|
||||||
// all other devices can get near physical limit:
|
// all other devices can get near physical limit:
|
||||||
// longlines=true:, speed is near physical limit at 11.5k
|
// longlines=true:, speed is near physical limit at 11.5k
|
||||||
@ -816,21 +897,21 @@ public class DeviceTest implements SerialInputOutputManager.Listener {
|
|||||||
StringBuilder data = new StringBuilder();
|
StringBuilder data = new StringBuilder();
|
||||||
StringBuilder expected = new StringBuilder();
|
StringBuilder expected = new StringBuilder();
|
||||||
int dlen = 0, elen = 0;
|
int dlen = 0, elen = 0;
|
||||||
Log.i(TAG, "writeSpeed: 'out' should be near "+115200/10);
|
Log.i(TAG, "writeSpeed: 'write' should be near "+115200/10);
|
||||||
long begin = System.currentTimeMillis();
|
long begin = System.currentTimeMillis();
|
||||||
long next = System.currentTimeMillis();
|
long next = System.currentTimeMillis();
|
||||||
for(int seconds=1; seconds<=5; seconds++) {
|
for(int seconds=1; seconds<=5; seconds++) {
|
||||||
next += 1000;
|
next += 1000;
|
||||||
while (System.currentTimeMillis() < next) {
|
while (System.currentTimeMillis() < next) {
|
||||||
if(longlines)
|
if(longlines)
|
||||||
line = String.format("%060d\r\n", linenr++);
|
line = String.format("%060d,", linenr++);
|
||||||
else
|
else
|
||||||
line = String.format("%06d\r\n", linenr++);
|
line = String.format("%07d,", linenr++);
|
||||||
usbWrite(line.getBytes());
|
usbWrite(line.getBytes());
|
||||||
expected.append(line);
|
expected.append(line);
|
||||||
data.append(new String(telnetRead(0)));
|
data.append(new String(telnetRead(0)));
|
||||||
}
|
}
|
||||||
Log.i(TAG, "writeSpeed: t="+(next-begin)+", out="+(expected.length()-elen)+", in="+(data.length()-dlen));
|
Log.i(TAG, "writeSpeed: t="+(next-begin)+", write="+(expected.length()-elen)+", read="+(data.length()-dlen));
|
||||||
dlen = data.length();
|
dlen = data.length();
|
||||||
elen = expected.length();
|
elen = expected.length();
|
||||||
}
|
}
|
||||||
@ -841,10 +922,11 @@ public class DeviceTest implements SerialInputOutputManager.Listener {
|
|||||||
found = data.toString().endsWith(line);
|
found = data.toString().endsWith(line);
|
||||||
}
|
}
|
||||||
next = System.currentTimeMillis();
|
next = System.currentTimeMillis();
|
||||||
Log.i(TAG, "writeSpeed: t="+(next-begin)+", in="+(data.length()-dlen));
|
Log.i(TAG, "writeSpeed: t="+(next-begin)+", read="+(data.length()-dlen));
|
||||||
assertTrue(found);
|
assertTrue(found);
|
||||||
int pos = StringUtils.indexOfDifference(data, expected);
|
int pos = indexOfDifference(data, expected);
|
||||||
if(pos!=-1) {
|
if(pos!=-1) {
|
||||||
|
|
||||||
Log.i(TAG, "writeSpeed: first difference at " + pos);
|
Log.i(TAG, "writeSpeed: first difference at " + pos);
|
||||||
String datasub = data.substring(Math.max(pos - 20, 0), Math.min(pos + 20, data.length()));
|
String datasub = data.substring(Math.max(pos - 20, 0), Math.min(pos + 20, data.length()));
|
||||||
String expectedsub = expected.substring(Math.max(pos - 20, 0), Math.min(pos + 20, expected.length()));
|
String expectedsub = expected.substring(Math.max(pos - 20, 0), Math.min(pos + 20, expected.length()));
|
||||||
|
@ -191,23 +191,23 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
|
|||||||
} finally {
|
} finally {
|
||||||
request.close();
|
request.close();
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
final int numBytesRead;
|
||||||
final int numBytesRead;
|
synchronized (mReadBufferLock) {
|
||||||
synchronized (mReadBufferLock) {
|
int readAmt = Math.min(dest.length, mReadBuffer.length);
|
||||||
int readAmt = Math.min(dest.length, mReadBuffer.length);
|
numBytesRead = mConnection.bulkTransfer(mReadEndpoint, mReadBuffer, readAmt,
|
||||||
numBytesRead = mConnection.bulkTransfer(mReadEndpoint, mReadBuffer, readAmt,
|
timeoutMillis);
|
||||||
timeoutMillis);
|
if (numBytesRead < 0) {
|
||||||
if (numBytesRead < 0) {
|
// This sucks: we get -1 on timeout, not 0 as preferred.
|
||||||
// This sucks: we get -1 on timeout, not 0 as preferred.
|
// We *should* use UsbRequest, except it has a bug/api oversight
|
||||||
// We *should* use UsbRequest, except it has a bug/api oversight
|
// where there is no way to determine the number of bytes read
|
||||||
// where there is no way to determine the number of bytes read
|
// in response :\ -- http://b.android.com/28023
|
||||||
// in response :\ -- http://b.android.com/28023
|
return 0;
|
||||||
return 0;
|
}
|
||||||
|
System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead);
|
||||||
}
|
}
|
||||||
System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead);
|
return numBytesRead;
|
||||||
}
|
}
|
||||||
return numBytesRead;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -26,9 +26,12 @@ 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.UsbInterface;
|
import android.hardware.usb.UsbInterface;
|
||||||
|
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;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -101,11 +104,13 @@ 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;
|
||||||
|
|
||||||
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
|
||||||
@ -179,21 +184,47 @@ 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 {
|
||||||
final int numBytesRead;
|
if (mEnableAsyncReads) {
|
||||||
synchronized (mReadBufferLock) {
|
final UsbRequest request = new UsbRequest();
|
||||||
int readAmt = Math.min(dest.length, mReadBuffer.length);
|
try {
|
||||||
numBytesRead = mConnection.bulkTransfer(mReadEndpoint, mReadBuffer, readAmt,
|
request.initialize(mConnection, mReadEndpoint);
|
||||||
timeoutMillis);
|
final ByteBuffer buf = ByteBuffer.wrap(dest);
|
||||||
if (numBytesRead < 0) {
|
if (!request.queue(buf, dest.length)) {
|
||||||
// This sucks: we get -1 on timeout, not 0 as preferred.
|
throw new IOException("Error queueing request.");
|
||||||
// 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
|
final UsbRequest response = mConnection.requestWait();
|
||||||
return 0;
|
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();
|
||||||
}
|
}
|
||||||
System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead);
|
} 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);
|
||||||
|
}
|
||||||
|
return numBytesRead;
|
||||||
}
|
}
|
||||||
return numBytesRead;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -32,10 +32,13 @@ 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.UsbInterface;
|
import android.hardware.usb.UsbInterface;
|
||||||
|
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;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -109,6 +112,7 @@ 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;
|
||||||
@ -126,6 +130,7 @@ 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
|
||||||
@ -368,15 +373,41 @@ 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 {
|
||||||
synchronized (mReadBufferLock) {
|
if (mEnableAsyncReads) {
|
||||||
int readAmt = Math.min(dest.length, mReadBuffer.length);
|
final UsbRequest request = new UsbRequest();
|
||||||
int numBytesRead = mConnection.bulkTransfer(mReadEndpoint, mReadBuffer,
|
try {
|
||||||
readAmt, timeoutMillis);
|
request.initialize(mConnection, mReadEndpoint);
|
||||||
if (numBytesRead < 0) {
|
final ByteBuffer buf = ByteBuffer.wrap(dest);
|
||||||
return 0;
|
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();
|
||||||
|
}
|
||||||
|
} 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;
|
||||||
}
|
}
|
||||||
System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead);
|
|
||||||
return numBytesRead;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +120,6 @@ public class SerialInputOutputManager implements Runnable {
|
|||||||
* called, or until a driver exception is raised.
|
* called, or until a driver exception is raised.
|
||||||
*
|
*
|
||||||
* NOTE(mikey): Uses inefficient read/write-with-timeout.
|
* NOTE(mikey): Uses inefficient read/write-with-timeout.
|
||||||
* TODO(mikey): Read asynchronously with {@link UsbRequest#queue(ByteBuffer, int)}
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user