diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..4a443ac
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,4 @@
+caches
+codeStyles
+libraries
+workspace.xml
diff --git a/.idea/.name b/.idea/.name
deleted file mode 100644
index 88cdcce..0000000
--- a/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-usb-serial-for-android
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
deleted file mode 100644
index 1f2af51..0000000
--- a/.idea/compiler.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
deleted file mode 100644
index e7bedf3..0000000
--- a/.idea/copyright/profiles_settings.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index aaaf922..c0f68ed 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,38 +1,34 @@
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
- 1.8
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..7f68460
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 0c42278..df159ef 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,15 +2,17 @@
buildscript {
repositories {
- mavenCentral()
+ jcenter()
+ google()
}
dependencies {
- classpath 'com.android.tools.build:gradle:1.2.3'
+ classpath 'com.android.tools.build:gradle:3.1.2'
}
}
allprojects {
repositories {
- mavenCentral()
+ jcenter()
+ google()
}
}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 8c0fb64..13372ae 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index f3d36d0..eb10a04 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Tue Jun 23 00:11:28 EDT 2015
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
+#Tue Mar 27 21:28:01 CEST 2018
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
diff --git a/test/arduino_leonardo_bridge/arduino_leonardo_bridge.ino b/test/arduino_leonardo_bridge/arduino_leonardo_bridge.ino
new file mode 100644
index 0000000..e7071d9
--- /dev/null
+++ b/test/arduino_leonardo_bridge/arduino_leonardo_bridge.ino
@@ -0,0 +1,64 @@
+/*
+ bridge USB-serial to hardware-serial
+
+ for Arduinos based on ATmega32u4 (Leonardo and compatible Pro Micro, Micro)
+ hardware serial is configured with baud-rate, databits, stopbits, parity as send over USB
+
+ see https://github.com/arduino/Arduino/tree/master/hardware/arduino/avr/cores/arduino
+ -> CDC.cpp|HardwareSerial.cpp for serial implementation details
+
+ this sketch is mainly for demonstration / test of CDC communication
+ performance as real usb-serial bridge would be inacceptable as each byte is send in separate USB packet
+*/
+
+uint32_t baud = 9600;
+uint8_t databits = 8;
+uint8_t stopbits = 1;
+uint8_t parity = 0;
+
+void setup() {
+ Serial.begin(baud); // USB
+ Serial1.begin(baud, SERIAL_8N1);
+}
+
+void loop() {
+ // show USB connected state
+ if (Serial) TXLED1;
+ else TXLED0;
+
+ // configure hardware serial
+ if (Serial.baud() != baud ||
+ Serial.numbits() != databits ||
+ Serial.stopbits() != stopbits ||
+ Serial.paritytype() != parity) {
+ baud = Serial.baud();
+ databits = Serial.numbits();
+ stopbits = Serial.stopbits();
+ parity = Serial.paritytype();
+ uint8_t config = 0; // ucsrc register
+ switch (databits) {
+ case 5: break;
+ case 6: config |= 2; break;
+ case 7: config |= 4; break;
+ case 8: config |= 6; break;
+ default: config |= 6;
+ }
+ switch (stopbits) {
+ case 2: config |= 8;
+ // 1.5 stopbits not supported
+ }
+ switch (parity) {
+ case 1: config |= 0x30; break; // odd
+ case 2: config |= 0x20; break; // even
+ // mark, space not supported
+ }
+ Serial1.end();
+ Serial1.begin(baud, config);
+ }
+
+ // bridge
+ if (Serial.available() > 0)
+ Serial1.write(Serial.read());
+ if (Serial1.available() > 0)
+ Serial.write(Serial1.read());
+}
diff --git a/test/rfc2217_server.diff b/test/rfc2217_server.diff
new file mode 100644
index 0000000..a5d09a2
--- /dev/null
+++ b/test/rfc2217_server.diff
@@ -0,0 +1,42 @@
+*** /n/archiv/python/rfc2217_server.py 2018-03-10 09:02:07.613771600 +0100
+--- rfc2217_server.py 2018-03-09 20:57:44.933717100 +0100
+***************
+*** 26,31 ****
+--- 26,32 ----
+ self,
+ logger=logging.getLogger('rfc2217.server') if debug else None)
+ self.log = logging.getLogger('redirector')
++ self.dlog = logging.getLogger('data')
+
+ def statusline_poller(self):
+ self.log.debug('status line poll thread started')
+***************
+*** 55,60 ****
+--- 56,62 ----
+ try:
+ data = self.serial.read(self.serial.in_waiting or 1)
+ if data:
++ self.dlog.debug("serial read: "+data.encode('hex'))
+ # escape outgoing data when needed (Telnet IAC (0xff) character)
+ self.write(b''.join(self.rfc2217.escape(data)))
+ except socket.error as msg:
+***************
+*** 76,81 ****
+--- 78,84 ----
+ data = self.socket.recv(1024)
+ if not data:
+ break
++ self.dlog.debug("socket read: "+data.encode('hex'))
+ self.serial.write(b''.join(self.rfc2217.filter(data)))
+ except socket.error as msg:
+ self.log.error('{}'.format(msg))
+***************
+*** 132,137 ****
+--- 135,141 ----
+ logging.basicConfig(level=logging.INFO)
+ #~ logging.getLogger('root').setLevel(logging.INFO)
+ logging.getLogger('rfc2217').setLevel(level)
++ logging.getLogger('data').setLevel(level)
+
+ # connect to serial port
+ ser = serial.serial_for_url(args.SERIALPORT, do_not_open=True)
diff --git a/arduino/serial_test.ino b/test/serial_test/serial_test.ino
similarity index 100%
rename from arduino/serial_test.ino
rename to test/serial_test/serial_test.ino
diff --git a/usbSerialExamples/build.gradle b/usbSerialExamples/build.gradle
index 2da9b1c..cde7b10 100644
--- a/usbSerialExamples/build.gradle
+++ b/usbSerialExamples/build.gradle
@@ -1,14 +1,13 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 22
- buildToolsVersion "22.0.1"
+ compileSdkVersion 27
+ buildToolsVersion '27.0.3'
defaultConfig {
minSdkVersion 14
- targetSdkVersion 22
+ targetSdkVersion 27
- testApplicationId "com.hoho.android.usbserial.examples"
testInstrumentationRunner "android.test.InstrumentationTestRunner"
}
@@ -20,5 +19,5 @@ android {
}
dependencies {
- compile project(':usbSerialForAndroid')
+ implementation project(':usbSerialForAndroid')
}
diff --git a/usbSerialForAndroid/build.gradle b/usbSerialForAndroid/build.gradle
index c3d835c..d335842 100644
--- a/usbSerialForAndroid/build.gradle
+++ b/usbSerialForAndroid/build.gradle
@@ -3,12 +3,13 @@ apply plugin: 'maven'
apply plugin: 'signing'
android {
- compileSdkVersion 19
- buildToolsVersion "19.1"
+ compileSdkVersion 27
+ buildToolsVersion '27.0.3'
defaultConfig {
- minSdkVersion 12
- targetSdkVersion 19
+ minSdkVersion 14
+ targetSdkVersion 27
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
@@ -19,6 +20,14 @@ android {
}
}
+dependencies {
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'com.android.support:support-annotations:27.1.1'
+ androidTestImplementation 'com.android.support.test:runner:1.0.2'
+ androidTestImplementation 'commons-net:commons-net:3.6'
+ androidTestImplementation 'org.apache.commons:commons-lang3:3.7'
+}
+
group = "com.hoho.android"
version = "0.2.0-SNAPSHOT"
diff --git a/usbSerialForAndroid/src/androidTest/AndroidManifest.xml b/usbSerialForAndroid/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..51ad082
--- /dev/null
+++ b/usbSerialForAndroid/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/DeviceTest.java b/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/DeviceTest.java
new file mode 100644
index 0000000..e20ab5e
--- /dev/null
+++ b/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/DeviceTest.java
@@ -0,0 +1,972 @@
+/*
+ * test setup
+ * - android device with ADB over Wi-Fi
+ * - to set up ADB over Wi-Fi with custom roms you typically can do it from: Android settings -> Developer options
+ * - for other devices you first have to manually connect over USB and enable Wi-Fi as shown here:
+ * https://developer.android.com/studio/command-line/adb.html
+ * - windows/linux machine running rfc2217_server.py
+ * python + pyserial + https://github.com/pyserial/pyserial/blob/master/examples/rfc2217_server.py
+ * for developing this test it was essential to see all data (see test/rfc2217_server.diff, run python script with '-v -v' option)
+ * - all suppported usb <-> serial converter
+ * as CDC test device use an arduino leonardo / pro mini programmed with arduino_leonardo_bridge.ino
+ *
+ * restrictions
+ * - as real hardware is used, timing might need tuning. see:
+ * - Thread.sleep(...)
+ * - obj.wait(...)
+ * - some tests fail sporadically. typical workarounds are:
+ * - reconnect device
+ * - run test individually
+ * - increase sleep?
+ * - missing functionality on certain devices, see:
+ * - if(rfc2217_server_nonstandard_baudrates)
+ * - if(usbSerialDriver instanceof ...)
+ *
+ */
+package com.hoho.android.usbserial;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+import android.hardware.usb.UsbManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+import android.os.Process;
+
+import com.hoho.android.usbserial.driver.CdcAcmSerialDriver;
+import com.hoho.android.usbserial.driver.Ch34xSerialDriver;
+import com.hoho.android.usbserial.driver.Cp21xxSerialDriver;
+import com.hoho.android.usbserial.driver.FtdiSerialDriver;
+import com.hoho.android.usbserial.driver.ProbeTable;
+import com.hoho.android.usbserial.driver.ProlificSerialDriver;
+import com.hoho.android.usbserial.driver.UsbSerialDriver;
+import com.hoho.android.usbserial.driver.UsbSerialPort;
+import com.hoho.android.usbserial.driver.UsbSerialProber;
+import com.hoho.android.usbserial.util.SerialInputOutputManager;
+
+import org.apache.commons.net.telnet.InvalidTelnetOptionException;
+import org.apache.commons.net.telnet.TelnetClient;
+import org.apache.commons.net.telnet.TelnetCommand;
+import org.apache.commons.net.telnet.TelnetOptionHandler;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.Deque;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.Executors;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest implements SerialInputOutputManager.Listener {
+
+ // configuration:
+ private final static String rfc2217_server_host = "192.168.0.100";
+ private final static int rfc2217_server_port = 2217;
+ 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 test_device_port = 0;
+
+ private final static int TELNET_READ_WAIT = 500;
+ private final static int USB_READ_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 byte RFC2217_COM_PORT_OPTION = 0x2c;
+ private final static byte RFC2217_SET_BAUDRATE = 1;
+ private final static byte RFC2217_SET_DATASIZE = 2;
+ private final static byte RFC2217_SET_PARITY = 3;
+ private final static byte RFC2217_SET_STOPSIZE = 4;
+
+ private Context context;
+ private UsbSerialDriver usbSerialDriver;
+ private UsbDeviceConnection usbDeviceConnection;
+ private UsbSerialPort usbSerialPort;
+ private SerialInputOutputManager usbIoManager;
+ private final Deque usbReadBuffer = new LinkedList<>();
+ private boolean usbReadBlock = false;
+ private long usbReadTime = 0;
+
+ private static TelnetClient telnetClient;
+ private static InputStream telnetReadStream;
+ private static OutputStream telnetWriteStream;
+ private static Integer[] telnetComPortOptionCounter = {0};
+ private int telnetWriteDelay = 0;
+ private boolean isCp21xxRestrictedPort = false; // second port of Cp2105 has limited dataBits, stopBits, parity
+
+ @BeforeClass
+ public static void setUpFixture() throws Exception {
+ telnetClient = null;
+ // postpone fixture setup to first test, because exceptions are not reported for @BeforeClass
+ // and test terminates with missleading 'Empty test suite'
+ }
+
+ public static void setUpFixtureInt() throws Exception {
+ if(telnetClient != null)
+ return;
+ telnetClient = new TelnetClient();
+ telnetClient.addOptionHandler(new TelnetOptionHandler(RFC2217_COM_PORT_OPTION, false, false, false, false) {
+ @Override
+ public int[] answerSubnegotiation(int[] suboptionData, int suboptionLength) {
+ telnetComPortOptionCounter[0] += 1;
+ return super.answerSubnegotiation(suboptionData, suboptionLength);
+ }
+ });
+
+ telnetClient.setConnectTimeout(2000);
+ telnetClient.connect(rfc2217_server_host, rfc2217_server_port);
+ telnetClient.setTcpNoDelay(true);
+ telnetWriteStream = telnetClient.getOutputStream();
+ telnetReadStream = telnetClient.getInputStream();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ setUpFixtureInt();
+ 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;
+ telnetWriteDelay = 0;
+
+ context = InstrumentationRegistry.getContext();
+ final UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
+ List availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(usbManager);
+ assertEquals("no usb device found", 1, availableDrivers.size());
+ usbSerialDriver = availableDrivers.get(0);
+ assertTrue( usbSerialDriver.getPorts().size() > test_device_port);
+ usbSerialPort = usbSerialDriver.getPorts().get(test_device_port);
+ Log.i(TAG, "Using USB device "+ usbSerialDriver.getClass().getSimpleName());
+ isCp21xxRestrictedPort = usbSerialDriver instanceof Cp21xxSerialDriver && usbSerialDriver.getPorts().size()==2 && test_device_port == 1;
+
+ if (!usbManager.hasPermission(usbSerialPort.getDriver().getDevice())) {
+ final Boolean[] granted = {Boolean.FALSE};
+ BroadcastReceiver usbReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ granted[0] = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
+ synchronized (granted) {
+ granted.notify();
+ }
+ }
+ };
+ PendingIntent permissionIntent = PendingIntent.getBroadcast(context, 0, new Intent("com.android.example.USB_PERMISSION"), 0);
+ IntentFilter filter = new IntentFilter("com.android.example.USB_PERMISSION");
+ context.registerReceiver(usbReceiver, filter);
+ usbManager.requestPermission(usbSerialDriver.getDevice(), permissionIntent);
+ synchronized (granted) {
+ granted.wait(5000);
+ }
+ assertTrue("USB permission dialog not confirmed", granted[0]);
+ }
+ usbDeviceConnection = usbManager.openDevice(usbSerialDriver.getDevice());
+ usbSerialPort.open(usbDeviceConnection);
+ usbSerialPort.setDTR(true);
+ usbSerialPort.setRTS(true);
+ 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);
+
+ synchronized (usbReadBuffer) {
+ usbReadBuffer.clear();
+ }
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ try {
+ usbRead(0);
+ } catch (Exception ignored) {}
+ try {
+ telnetRead(0);
+ } catch (Exception ignored) {}
+
+ try {
+ usbIoManager.setListener(null);
+ usbIoManager.stop();
+ } catch (Exception ignored) {}
+ try {
+ usbSerialPort.setDTR(false);
+ usbSerialPort.setRTS(false);
+ usbSerialPort.close();
+ } catch (Exception ignored) {}
+ try {
+ usbDeviceConnection.close();
+ } catch (Exception ignored) {}
+ usbIoManager = null;
+ usbSerialPort = null;
+ usbDeviceConnection = null;
+ usbSerialDriver = null;
+ }
+
+ @AfterClass
+ public static void tearDownFixture() throws Exception {
+ try {
+ telnetClient.disconnect();
+ } catch (Exception ignored) {}
+ telnetReadStream = null;
+ telnetWriteStream = null;
+ telnetClient = null;
+ }
+
+ // wait full time
+ private byte[] telnetRead() throws Exception {
+ return telnetRead(-1);
+ }
+
+ private byte[] telnetRead(int expectedLength) throws Exception {
+ long end = System.currentTimeMillis() + TELNET_READ_WAIT;
+ ByteBuffer buf = ByteBuffer.allocate(4096);
+ while(System.currentTimeMillis() < end) {
+ if(telnetReadStream.available() > 0) {
+ buf.put((byte) telnetReadStream.read());
+ } else {
+ if (expectedLength >= 0 && buf.position() >= expectedLength)
+ break;
+ Thread.sleep(1);
+ }
+ }
+ byte[] data = new byte[buf.position()];
+ buf.flip();
+ buf.get(data);
+ return data;
+ }
+
+ private void telnetWrite(byte[] data) throws Exception{
+ if(telnetWriteDelay != 0) {
+ for(byte b : data) {
+ telnetWriteStream.write(b);
+ telnetWriteStream.flush();
+ Thread.sleep(telnetWriteDelay);
+ }
+ } else {
+ telnetWriteStream.write(data);
+ telnetWriteStream.flush();
+ }
+ }
+
+ // wait full time
+ private byte[] usbRead() throws Exception {
+ return usbRead(-1);
+ }
+
+ private byte[] usbRead(int expectedLength) throws Exception {
+ long end = System.currentTimeMillis() + USB_READ_WAIT;
+ ByteBuffer buf = ByteBuffer.allocate(8192);
+ if(usbIoManager != null) {
+ while (System.currentTimeMillis() < end) {
+ synchronized (usbReadBuffer) {
+ while(usbReadBuffer.peek() != null)
+ buf.put(usbReadBuffer.remove());
+ }
+ if (expectedLength >= 0 && buf.position() >= expectedLength)
+ break;
+ Thread.sleep(1);
+ }
+
+ } else {
+ byte[] b1 = new byte[256];
+ while (System.currentTimeMillis() < end) {
+ int len = usbSerialPort.read(b1, USB_READ_WAIT / 10);
+ if (len > 0) {
+ buf.put(b1, 0, len);
+ } else {
+ if (expectedLength >= 0 && buf.position() >= expectedLength)
+ break;
+ Thread.sleep(1);
+ }
+ }
+ }
+ byte[] data = new byte[buf.position()];
+ buf.flip();
+ buf.get(data);
+ return data;
+ }
+
+ private void usbWrite(byte[] data) throws IOException {
+ usbSerialPort.write(data, USB_WRITE_WAIT);
+ }
+
+ private void usbParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException, InterruptedException {
+ usbSerialPort.setParameters(baudRate, dataBits, stopBits, parity);
+ if(usbSerialDriver instanceof CdcAcmSerialDriver)
+ Thread.sleep(10); // arduino_leonardeo_bridge.ini needs some time
+ else
+ Thread.sleep(1);
+ }
+
+ private void telnetParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException, InterruptedException, InvalidTelnetOptionException {
+ telnetComPortOptionCounter[0] = 0;
+
+ telnetClient.sendCommand((byte)TelnetCommand.SB);
+ telnetWriteStream.write(new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_BAUDRATE, (byte)(baudRate>>24), (byte)(baudRate>>16), (byte)(baudRate>>8), (byte)baudRate});
+ telnetClient.sendCommand((byte)TelnetCommand.SE);
+
+ telnetClient.sendCommand((byte)TelnetCommand.SB);
+ telnetWriteStream.write(new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_DATASIZE, (byte)dataBits});
+ telnetClient.sendCommand((byte)TelnetCommand.SE);
+
+ telnetClient.sendCommand((byte)TelnetCommand.SB);
+ telnetWriteStream.write(new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_STOPSIZE, (byte)stopBits});
+ telnetClient.sendCommand((byte)TelnetCommand.SE);
+
+ telnetClient.sendCommand((byte)TelnetCommand.SB);
+ telnetWriteStream.write(new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_PARITY, (byte)(parity+1)});
+ telnetClient.sendCommand((byte)TelnetCommand.SE);
+
+ // windows does not like nonstandard baudrates. rfc2217_server.py terminates w/o response
+ for(int i=0; i<2000; i++) {
+ if(telnetComPortOptionCounter[0] == 4) break;
+ Thread.sleep(1);
+ }
+ assertEquals("telnet connection lost", 4, telnetComPortOptionCounter[0].intValue());
+ }
+
+ @Override
+ 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)
+ try {
+ Thread.sleep(1);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ synchronized (usbReadBuffer) {
+ usbReadBuffer.add(data);
+ }
+ }
+
+ @Override
+ public void onRunError(Exception e) {
+ 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
+ public void baudRate() throws Exception {
+ byte[] data;
+
+ if (false) { // default baud rate
+ // CP2102: only works if first connection after attaching device
+ // PL2303, FTDI: it's not 9600
+ telnetParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE);
+
+ telnetWrite("net2usb".getBytes());
+ data = usbRead(7);
+ assertThat(data, equalTo("net2usb".getBytes())); // includes array content in output
+ //assertArrayEquals("net2usb".getBytes(), data); // only includes array length in output
+ usbWrite("usb2net".getBytes());
+ data = telnetRead(7);
+ assertThat(data, equalTo("usb2net".getBytes()));
+ }
+
+ // invalid values
+ try {
+ usbParameters(-1, 8, 1, UsbSerialPort.PARITY_NONE);
+ if (usbSerialDriver instanceof Ch34xSerialDriver)
+ ; // todo: add range check in driver
+ else if (usbSerialDriver instanceof FtdiSerialDriver)
+ ; // todo: add range check in driver
+ else if (usbSerialDriver instanceof ProlificSerialDriver)
+ ; // todo: add range check in driver
+ else if (usbSerialDriver instanceof Cp21xxSerialDriver)
+ ; // todo: add range check in driver
+ else if (usbSerialDriver instanceof CdcAcmSerialDriver)
+ ; // todo: add range check in driver
+ else
+ fail("invalid baudrate 0");
+ } catch (java.io.IOException e) { // cp2105 second port
+ } catch (java.lang.IllegalArgumentException e) {
+ }
+ try {
+ usbParameters(0, 8, 1, UsbSerialPort.PARITY_NONE);
+ if (usbSerialDriver instanceof ProlificSerialDriver)
+ ; // todo: add range check in driver
+ else if (usbSerialDriver instanceof Cp21xxSerialDriver)
+ ; // todo: add range check in driver
+ else if (usbSerialDriver instanceof CdcAcmSerialDriver)
+ ; // todo: add range check in driver
+ else
+ fail("invalid baudrate 0");
+ } catch (java.lang.ArithmeticException e) { // ch340
+ } catch (java.io.IOException e) { // cp2105 second port
+ } catch (java.lang.IllegalArgumentException e) {
+ }
+ try {
+ usbParameters(1, 8, 1, UsbSerialPort.PARITY_NONE);
+ if (usbSerialDriver instanceof FtdiSerialDriver)
+ ;
+ else if (usbSerialDriver instanceof ProlificSerialDriver)
+ ;
+ else if (usbSerialDriver instanceof Cp21xxSerialDriver)
+ ;
+ else if (usbSerialDriver instanceof CdcAcmSerialDriver)
+ ;
+ else
+ fail("invalid baudrate 0");
+ } catch (java.io.IOException e) { // ch340
+ } catch (java.lang.IllegalArgumentException e) {
+ }
+ try {
+ usbParameters(2<<31, 8, 1, UsbSerialPort.PARITY_NONE);
+ if (usbSerialDriver instanceof ProlificSerialDriver)
+ ;
+ else if (usbSerialDriver instanceof Cp21xxSerialDriver)
+ ;
+ else if (usbSerialDriver instanceof CdcAcmSerialDriver)
+ ;
+ else
+ fail("invalid baudrate 2^31");
+ } catch (java.lang.ArithmeticException e) { // ch340
+ } catch (java.io.IOException e) { // cp2105 second port
+ } catch (java.lang.IllegalArgumentException e) {
+ }
+
+ for(int baudRate : new int[] {300, 2400, 19200, 42000, 115200} ) {
+ if(baudRate == 42000 && !rfc2217_server_nonstandard_baudrates)
+ continue; // rfc2217_server.py would terminate
+ if(baudRate == 300 && isCp21xxRestrictedPort) {
+ try {
+ usbParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE);
+ assertTrue(false);
+ } catch (java.io.IOException e) {
+ }
+ continue;
+ }
+ telnetParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE);
+ usbParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE);
+
+ telnetWrite("net2usb".getBytes());
+ data = usbRead(7);
+ assertThat(String.valueOf(baudRate)+"/8N1", data, equalTo("net2usb".getBytes()));
+ usbWrite("usb2net".getBytes());
+ data = telnetRead(7);
+ assertThat(String.valueOf(baudRate)+"/8N1", data, equalTo("usb2net".getBytes()));
+ }
+ { // non matching baud rate
+ telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
+ usbParameters(2400, 8, 1, UsbSerialPort.PARITY_NONE);
+
+ telnetWrite("net2usb".getBytes());
+ data = usbRead();
+ assertNotEquals(7, data.length);
+ usbWrite("usb2net".getBytes());
+ data = telnetRead();
+ assertNotEquals(7, data.length);
+ }
+ }
+
+ @Test
+ public void dataBits() throws Exception {
+ byte[] data;
+
+ for(int i: new int[] {0, 4, 9}) {
+ try {
+ usbParameters(19200, i, 1, UsbSerialPort.PARITY_NONE);
+ if (usbSerialDriver instanceof ProlificSerialDriver)
+ ; // todo: add range check in driver
+ else if (usbSerialDriver instanceof CdcAcmSerialDriver)
+ ; // todo: add range check in driver
+ else
+ fail("invalid databits "+i);
+ } catch (java.lang.IllegalArgumentException e) {
+ }
+ }
+
+ // telnet -> usb
+ 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
+ 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);
+ 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);
+ telnetWrite(new byte[] {(byte)0xff});
+ data = usbRead(2);
+ assertThat("19000/5N1", data, equalTo(new byte[] {(byte)0xe0, (byte)0xff}));
+
+ // usb -> telnet
+ try {
+ telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
+ usbParameters(19200, 7, 1, UsbSerialPort.PARITY_NONE);
+ usbWrite(new byte[]{0x00});
+ Thread.sleep(1);
+ usbWrite(new byte[]{(byte) 0xff});
+ data = telnetRead(2);
+ assertThat("19000/7N1", data, equalTo(new byte[]{(byte) 0x80, (byte) 0xff}));
+ } catch (java.lang.IllegalArgumentException e) {
+ if(!isCp21xxRestrictedPort)
+ throw e;
+ }
+ try {
+ usbParameters(19200, 6, 1, UsbSerialPort.PARITY_NONE);
+ usbWrite(new byte[]{0x00});
+ Thread.sleep(1);
+ usbWrite(new byte[]{(byte) 0xff});
+ data = telnetRead(2);
+ assertThat("19000/6N1", data, equalTo(new byte[]{(byte) 0xc0, (byte) 0xff}));
+ } catch (java.lang.IllegalArgumentException e) {
+ if (!(isCp21xxRestrictedPort || usbSerialDriver instanceof FtdiSerialDriver))
+ throw e;
+ }
+ try {
+ usbParameters(19200, 5, 1, UsbSerialPort.PARITY_NONE);
+ usbWrite(new byte[] {0x00});
+ Thread.sleep(1);
+ usbWrite(new byte[] {(byte)0xff});
+ data = telnetRead(2);
+ assertThat("19000/5N1", data, equalTo(new byte[] {(byte)0xe0, (byte)0xff}));
+ } catch (java.lang.IllegalArgumentException e) {
+ if (!(isCp21xxRestrictedPort || usbSerialDriver instanceof FtdiSerialDriver))
+ throw e;
+ }
+ }
+
+ @Test
+ public void parity() throws Exception {
+ byte[] _8n1 = {(byte)0x00, (byte)0x01, (byte)0xfe, (byte)0xff};
+ byte[] _7n1 = {(byte)0x00, (byte)0x01, (byte)0x7e, (byte)0x7f};
+ byte[] _7o1 = {(byte)0x80, (byte)0x01, (byte)0xfe, (byte)0x7f};
+ byte[] _7e1 = {(byte)0x00, (byte)0x81, (byte)0x7e, (byte)0xff};
+ byte[] _7m1 = {(byte)0x80, (byte)0x81, (byte)0xfe, (byte)0xff};
+ byte[] _7s1 = {(byte)0x00, (byte)0x01, (byte)0x7e, (byte)0x7f};
+ byte[] data;
+
+ for(int i: new int[] {-1, 5}) {
+ try {
+ usbParameters(19200, 8, 1, i);
+ fail("invalid parity "+i);
+ } catch (java.lang.IllegalArgumentException e) {
+ }
+ }
+ if(isCp21xxRestrictedPort) {
+ usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
+ usbParameters(19200, 8, 1, UsbSerialPort.PARITY_EVEN);
+ usbParameters(19200, 8, 1, UsbSerialPort.PARITY_ODD);
+ try {
+ usbParameters(19200, 8, 1, UsbSerialPort.PARITY_MARK);
+ usbParameters(19200, 8, 1, UsbSerialPort.PARITY_SPACE);
+ } catch (java.lang.IllegalArgumentException e) {
+ }
+ return;
+ // test below not possible as it requires unsupported 7 dataBits
+ }
+
+ // usb -> telnet
+ telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
+ usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
+ usbWrite(_8n1);
+ data = telnetRead(4);
+ assertThat("19200/8N1", data, equalTo(_8n1));
+
+ usbParameters(19200, 7, 1, UsbSerialPort.PARITY_ODD);
+ usbWrite(_8n1);
+ data = telnetRead(4);
+ assertThat("19200/7O1", data, equalTo(_7o1));
+
+ usbParameters(19200, 7, 1, UsbSerialPort.PARITY_EVEN);
+ usbWrite(_8n1);
+ data = telnetRead(4);
+ assertThat("19200/7E1", data, equalTo(_7e1));
+
+ if (usbSerialDriver instanceof CdcAcmSerialDriver) {
+ // not supported by arduino_leonardo_bridge.ino, other devices might support it
+ } else if (rfc2217_server_parity_mark_space) {
+ usbParameters(19200, 7, 1, UsbSerialPort.PARITY_MARK);
+ usbWrite(_8n1);
+ data = telnetRead(4);
+ assertThat("19200/7M1", data, equalTo(_7m1));
+
+ usbParameters(19200, 7, 1, UsbSerialPort.PARITY_SPACE);
+ usbWrite(_8n1);
+ data = telnetRead(4);
+ assertThat("19200/7S1", data, equalTo(_7s1));
+ }
+
+ // telnet -> usb
+ usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
+ telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
+ telnetWrite(_8n1);
+ data = usbRead(4);
+ assertThat("19200/8N1", data, equalTo(_8n1));
+
+ telnetParameters(19200, 7, 1, UsbSerialPort.PARITY_ODD);
+ telnetWrite(_8n1);
+ data = usbRead(4);
+ assertThat("19200/7O1", data, equalTo(_7o1));
+
+ telnetParameters(19200, 7, 1, UsbSerialPort.PARITY_EVEN);
+ telnetWrite(_8n1);
+ data = usbRead(4);
+ assertThat("19200/7E1", data, equalTo(_7e1));
+
+ if (usbSerialDriver instanceof CdcAcmSerialDriver) {
+ // not supported by arduino_leonardo_bridge.ino, other devices might support it
+ } else {
+ if (rfc2217_server_parity_mark_space) {
+ telnetParameters(19200, 7, 1, UsbSerialPort.PARITY_MARK);
+ telnetWrite(_8n1);
+ 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));
+ }
+ usbParameters(19200, 7, 1, UsbSerialPort.PARITY_ODD);
+ telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
+ telnetWrite(_8n1);
+ data = usbRead(4);
+ assertThat("19200/8N1", data, equalTo(_7n1)); // read is resilient against errors
+ }
+ }
+
+ @Test
+ public void stopBits() throws Exception {
+ byte[] data;
+
+ for (int i : new int[]{0, 4}) {
+ try {
+ usbParameters(19200, 8, i, UsbSerialPort.PARITY_NONE);
+ fail("invalid stopbits " + i);
+ } catch (java.lang.IllegalArgumentException e) {
+ }
+ }
+
+ if (usbSerialDriver instanceof CdcAcmSerialDriver) {
+ // software based bridge in arduino_leonardo_bridge.ino is to slow, other devices might support it
+ } else {
+ // shift stopbits into next byte, by using different databits
+ // a - start bit (0)
+ // o - stop bit (1)
+ // d - data bit
+
+ // out 8N2: addddddd doaddddddddo
+ // 1000001 0 10001111
+ // in 6N1: addddddo addddddo
+ // 100000 101000
+ usbParameters(19200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);
+ telnetParameters(19200, 6, 1, UsbSerialPort.PARITY_NONE);
+ usbWrite(new byte[]{(byte)0x41, (byte)0xf1});
+ data = telnetRead(2);
+ assertThat("19200/8N1", data, equalTo(new byte[]{1, 5}));
+
+ // out 8N2: addddddd dooaddddddddoo
+ // 1000001 0 10011111
+ // in 6N1: addddddo addddddo
+ // 100000 110100
+ try {
+ usbParameters(19200, 8, UsbSerialPort.STOPBITS_2, UsbSerialPort.PARITY_NONE);
+ telnetParameters(19200, 6, 1, UsbSerialPort.PARITY_NONE);
+ usbWrite(new byte[]{(byte) 0x41, (byte) 0xf9});
+ data = telnetRead(2);
+ assertThat("19200/8N1", data, equalTo(new byte[]{1, 11}));
+ } catch(java.lang.IllegalArgumentException e) {
+ if(!isCp21xxRestrictedPort)
+ throw e;
+ }
+ // todo: could create similar test for 1.5 stopbits, by reading at double speed
+ // but only some devices support 1.5 stopbits and it is basically not used any more
+ }
+ }
+
+
+ @Test
+ public void probeTable() throws Exception {
+ class DummyDriver implements UsbSerialDriver {
+ @Override
+ public UsbDevice getDevice() { return null; }
+ @Override
+ public List getPorts() { return null; }
+ }
+ List availableDrivers;
+ ProbeTable probeTable = new ProbeTable();
+ UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
+ availableDrivers = new UsbSerialProber(probeTable).findAllDrivers(usbManager);
+ assertEquals(0, availableDrivers.size());
+
+ probeTable.addProduct(0, 0, DummyDriver.class);
+ availableDrivers = new UsbSerialProber(probeTable).findAllDrivers(usbManager);
+ assertEquals(0, availableDrivers.size());
+
+ probeTable.addProduct(usbSerialDriver.getDevice().getVendorId(), usbSerialDriver.getDevice().getProductId(), usbSerialDriver.getClass());
+ availableDrivers = new UsbSerialProber(probeTable).findAllDrivers(usbManager);
+ assertEquals(1, availableDrivers.size());
+ assertEquals(true, availableDrivers.get(0).getClass() == usbSerialDriver.getClass());
+ }
+
+ @Test
+ // data loss es expected, if data is not consumed fast enough
+ public void readBuffer() throws Exception {
+ if(usbSerialDriver instanceof CdcAcmSerialDriver)
+ telnetWriteDelay = 10; // arduino_leonardo_bridge.ino sends each byte in own USB packet, which is horribly slow
+ usbParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
+ telnetParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
+
+ StringBuilder expected = new StringBuilder();
+ StringBuilder data = new StringBuilder();
+ final int maxWait = 2000;
+ int bufferSize = 0;
+ for(bufferSize = 8; bufferSize < (2<<15); bufferSize *= 2) {
+ int linenr = 0;
+ String line;
+ expected.setLength(0);
+ data.setLength(0);
+
+ Log.i(TAG, "bufferSize " + bufferSize);
+ usbReadBlock = true;
+ for (linenr = 0; linenr < bufferSize/8; linenr++) {
+ line = String.format("%07d,", linenr);
+ telnetWrite(line.getBytes());
+ expected.append(line);
+ }
+ usbReadBlock = false;
+
+ // slowly write new data, until old data is comletely read from buffer and new data is received again
+ boolean found = false;
+ for (; linenr < bufferSize/8 + maxWait/10 && !found; linenr++) {
+ line = String.format("%07d,", linenr);
+ telnetWrite(line.getBytes());
+ Thread.sleep(10);
+ expected.append(line);
+ data.append(new String(usbRead(0)));
+ found = data.toString().endsWith(line);
+ }
+ if(!found) {
+ // use waiting read to clear input queue, else next test would see unexpected data
+ byte[] rest = null;
+ while(rest==null || rest.length>0)
+ rest = usbRead(-1);
+ fail("end not found");
+ }
+ if (data.length() != expected.length())
+ break;
+ }
+ int pos = indexOfDifference(data, expected);
+ Log.i(TAG, "bufferSize " + bufferSize + ", first difference at " + pos);
+ // actual values have large variance for same device, e.g.
+ // bufferSize 4096, first difference at 164
+ // bufferSize 64, first difference at 57
+ assertTrue(bufferSize > 16);
+ 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
+ public void readSpeed() throws Exception {
+ // see logcat for performance results
+ //
+ // CDC arduino_leonardo_bridge.ini has transfer speed ~ 100 byte/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);
+
+ // fails more likely with larger or unlimited (-1) write ahead
+ int writeSeconds = 5;
+ int writeAhead = 5*baudrate/10; // write ahead for another 5 second read
+ if(usbSerialDriver instanceof CdcAcmSerialDriver)
+ writeAhead = 50;
+
+ int linenr = 0;
+ String line="";
+ StringBuilder data = new StringBuilder();
+ StringBuilder expected = new StringBuilder();
+ int dlen = 0, elen = 0;
+ Log.i(TAG, "readSpeed: 'read' should be near "+baudrate/10);
+ long begin = System.currentTimeMillis();
+ long next = System.currentTimeMillis();
+ for(int seconds=1; seconds <= writeSeconds; seconds++) {
+ next += 1000;
+ while (System.currentTimeMillis() < next) {
+ if((writeAhead < 0) || (expected.length() < data.length() + writeAhead)) {
+ line = String.format("%07d,", linenr++);
+ telnetWrite(line.getBytes());
+ expected.append(line);
+ } else {
+ Thread.sleep(0, 100000);
+ }
+ data.append(new String(usbRead(0)));
+ }
+ Log.i(TAG, "readSpeed: t="+(next-begin)+", read="+(data.length()-dlen)+", write="+(expected.length()-elen));
+ dlen = data.length();
+ elen = expected.length();
+ }
+ boolean found = false;
+ 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)));
+ found = data.toString().endsWith(line);
+ Thread.sleep(1);
+ }
+ //next = System.currentTimeMillis();
+ //Log.i(TAG, "readSpeed: t="+(next-begin)+", read="+(data.length()-dlen));
+
+ int errcnt = 0;
+ int errlen = 0;
+ int datapos = indexOfDifference(data, expected);
+ int expectedpos = datapos;
+ while(datapos != -1) {
+ 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
+ public void writeSpeed() throws Exception {
+ // see logcat for performance results
+ //
+ // CDC arduino_leonardo_bridge.ini has transfer speed ~ 100 byte/sec
+ // all other devices can get near physical limit:
+ // longlines=true:, speed is near physical limit at 11.5k
+ // longlines=false: speed is 3-4k for all devices, as more USB packets are required
+ usbParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
+ telnetParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
+ boolean longlines = !(usbSerialDriver instanceof CdcAcmSerialDriver);
+
+ int linenr = 0;
+ String line="";
+ StringBuilder data = new StringBuilder();
+ StringBuilder expected = new StringBuilder();
+ int dlen = 0, elen = 0;
+ Log.i(TAG, "writeSpeed: 'write' should be near "+115200/10);
+ long begin = System.currentTimeMillis();
+ long next = System.currentTimeMillis();
+ for(int seconds=1; seconds<=5; seconds++) {
+ next += 1000;
+ while (System.currentTimeMillis() < next) {
+ if(longlines)
+ line = String.format("%060d,", linenr++);
+ else
+ line = String.format("%07d,", linenr++);
+ usbWrite(line.getBytes());
+ expected.append(line);
+ data.append(new String(telnetRead(0)));
+ }
+ Log.i(TAG, "writeSpeed: t="+(next-begin)+", write="+(expected.length()-elen)+", read="+(data.length()-dlen));
+ dlen = data.length();
+ elen = expected.length();
+ }
+ boolean found = false;
+ for (linenr=0; linenr < 2000 && !found; linenr++) {
+ data.append(new String(telnetRead(0)));
+ Thread.sleep(1);
+ found = data.toString().endsWith(line);
+ }
+ next = System.currentTimeMillis();
+ Log.i(TAG, "writeSpeed: t="+(next-begin)+", read="+(data.length()-dlen));
+ assertTrue(found);
+ int pos = indexOfDifference(data, expected);
+ if(pos!=-1) {
+
+ Log.i(TAG, "writeSpeed: first difference at " + pos);
+ 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()));
+ assertThat(datasub, equalTo(expectedsub));
+ }
+ }
+
+}
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 59be14f..48b26fc 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
@@ -84,9 +84,10 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
private boolean dtr = false;
private boolean rts = false;
- private final boolean mEnableAsyncReads;
+ private final Boolean mEnableAsyncReads;
private UsbEndpoint mReadEndpoint;
private UsbEndpoint mWriteEndpoint;
+ private UsbRequest mUsbRequest;
public Ch340SerialPort(UsbDevice device, int portNumber) {
super(device, portNumber);
@@ -109,10 +110,8 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
try {
for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
UsbInterface usbIface = mDevice.getInterface(i);
- if (mConnection.claimInterface(usbIface, true)) {
- Log.d(TAG, "claimInterface " + i + " SUCCESS");
- } else {
- Log.d(TAG, "claimInterface " + i + " FAIL");
+ if (!mConnection.claimInterface(usbIface, true)) {
+ throw new IOException("Could not claim data interface.");
}
}
@@ -154,7 +153,10 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
if (mConnection == null) {
throw new IOException("Already closed");
}
-
+ synchronized (mEnableAsyncReads) {
+ if (mUsbRequest != null)
+ mUsbRequest.cancel();
+ }
// TODO: nothing sended on close, maybe needed?
try {
@@ -175,8 +177,11 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
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");
}
@@ -189,25 +194,26 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
return 0;
}
} finally {
+ mUsbRequest = null;
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
- return 0;
+ } 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);
}
- System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead);
+ return numBytesRead;
}
- return numBytesRead;
}
@Override
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 b5956a8..2119130 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
@@ -26,10 +26,13 @@ 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.os.Build;
import android.util.Log;
import java.io.IOException;
-import java.util.Collections;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -39,11 +42,14 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
private static final String TAG = Cp21xxSerialDriver.class.getSimpleName();
private final UsbDevice mDevice;
- private final UsbSerialPort mPort;
+ private final List mPorts;
public Cp21xxSerialDriver(UsbDevice device) {
mDevice = device;
- mPort = new Cp21xxSerialPort(mDevice, 0);
+ mPorts = new ArrayList<>();
+ for( int port = 0; port < device.getInterfaceCount(); port++) {
+ mPorts.add(new Cp21xxSerialPort(mDevice, port));
+ }
}
@Override
@@ -53,7 +59,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
@Override
public List getPorts() {
- return Collections.singletonList(mPort);
+ return mPorts;
}
public class Cp21xxSerialPort extends CommonUsbSerialPort {
@@ -101,11 +107,18 @@ 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;
+
+ // second port of Cp2105 has limited baudRate, dataBits, stopBits, parity
+ // unsupported baudrate returns error at controlTransfer(), other parameters are silently ignored
+ private boolean mIsRestrictedPort;
public Cp21xxSerialPort(UsbDevice device, int portNumber) {
super(device, portNumber);
+ mEnableAsyncReads = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1);
}
@Override
@@ -115,7 +128,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
private int setConfigSingle(int request, int value) {
return mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, value,
- 0, null, 0, USB_WRITE_TIMEOUT_MILLIS);
+ mPortNumber, null, 0, USB_WRITE_TIMEOUT_MILLIS);
}
@Override
@@ -126,17 +139,15 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
mConnection = connection;
boolean opened = false;
+ mIsRestrictedPort = mDevice.getInterfaceCount() == 2 && mPortNumber == 1;
try {
- for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
- UsbInterface usbIface = mDevice.getInterface(i);
- if (mConnection.claimInterface(usbIface, true)) {
- Log.d(TAG, "claimInterface " + i + " SUCCESS");
- } else {
- Log.d(TAG, "claimInterface " + i + " FAIL");
- }
+ if(mPortNumber >= mDevice.getInterfaceCount()) {
+ throw new IOException("Unknown port number");
+ }
+ UsbInterface dataIface = mDevice.getInterface(mPortNumber);
+ if (!mConnection.claimInterface(dataIface, true)) {
+ throw new IOException("Could not claim interface " + mPortNumber);
}
-
- UsbInterface dataIface = mDevice.getInterface(mDevice.getInterfaceCount() - 1);
for (int i = 0; i < dataIface.getEndpointCount(); i++) {
UsbEndpoint ep = dataIface.getEndpoint(i);
if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
@@ -169,6 +180,11 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
if (mConnection == null) {
throw new IOException("Already closed");
}
+ synchronized (mEnableAsyncReads) {
+ if(mUsbRequest != null) {
+ mUsbRequest.cancel();
+ }
+ }
try {
setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_DISABLE);
mConnection.close();
@@ -179,21 +195,51 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
@Override
public int read(byte[] dest, int timeoutMillis) throws IOException {
- 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;
+ 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 {
+ mUsbRequest = null;
+ 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
@@ -238,7 +284,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
(byte) ((baudRate >> 24) & 0xff)
};
int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_BAUDRATE,
- 0, 0, data, 4, USB_WRITE_TIMEOUT_MILLIS);
+ 0, mPortNumber, data, 4, USB_WRITE_TIMEOUT_MILLIS);
if (ret < 0) {
throw new IOException("Error setting baud rate.");
}
@@ -252,12 +298,18 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
int configDataBits = 0;
switch (dataBits) {
case DATABITS_5:
+ if(mIsRestrictedPort)
+ throw new IllegalArgumentException("Unsupported dataBits value: " + dataBits);
configDataBits |= 0x0500;
break;
case DATABITS_6:
+ if(mIsRestrictedPort)
+ throw new IllegalArgumentException("Unsupported dataBits value: " + dataBits);
configDataBits |= 0x0600;
break;
case DATABITS_7:
+ if(mIsRestrictedPort)
+ throw new IllegalArgumentException("Unsupported dataBits value: " + dataBits);
configDataBits |= 0x0700;
break;
case DATABITS_8:
@@ -277,9 +329,13 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
configDataBits |= 0x0020;
break;
case PARITY_MARK:
+ if(mIsRestrictedPort)
+ throw new IllegalArgumentException("Unsupported parity value: mark");
configDataBits |= 0x0030;
break;
case PARITY_SPACE:
+ if(mIsRestrictedPort)
+ throw new IllegalArgumentException("Unsupported parity value: space");
configDataBits |= 0x0040;
break;
default:
@@ -292,6 +348,8 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
case STOPBITS_1_5:
throw new IllegalArgumentException("Unsupported stopBits value: 1.5");
case STOPBITS_2:
+ if(mIsRestrictedPort)
+ throw new IllegalArgumentException("Unsupported stopBits value: 2");
configDataBits |= 2;
break;
default:
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 56b98b8..757c567 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
@@ -29,11 +29,9 @@ import android.hardware.usb.UsbRequest;
import android.os.Build;
import android.util.Log;
-import com.hoho.android.usbserial.util.HexDump;
-
import java.io.IOException;
import java.nio.ByteBuffer;
-import java.util.Collections;
+import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -73,6 +71,8 @@ import java.util.Map;
* Supported and tested devices:
*
* - {@value DeviceType#TYPE_R}
+ * - {@value DeviceType#TYPE_2232H}
+ * - {@value DeviceType#TYPE_4232H}
*
*
*
@@ -80,8 +80,6 @@ import java.util.Map;
* feedback or patches):
*
* - {@value DeviceType#TYPE_2232C}
- * - {@value DeviceType#TYPE_2232H}
- * - {@value DeviceType#TYPE_4232H}
* - {@value DeviceType#TYPE_AM}
* - {@value DeviceType#TYPE_BM}
*
@@ -96,7 +94,7 @@ import java.util.Map;
public class FtdiSerialDriver implements UsbSerialDriver {
private final UsbDevice mDevice;
- private final UsbSerialPort mPort;
+ private final List mPorts;
/**
* FTDI chip types.
@@ -107,8 +105,12 @@ public class FtdiSerialDriver implements UsbSerialDriver {
public FtdiSerialDriver(UsbDevice device) {
mDevice = device;
- mPort = new FtdiSerialPort(mDevice, 0);
+ mPorts = new ArrayList<>();
+ for( int port = 0; port < device.getInterfaceCount(); port++) {
+ mPorts.add(new FtdiSerialPort(mDevice, port));
+ }
}
+
@Override
public UsbDevice getDevice() {
return mDevice;
@@ -116,7 +118,7 @@ public class FtdiSerialDriver implements UsbSerialDriver {
@Override
public List getPorts() {
- return Collections.singletonList(mPort);
+ return mPorts;
}
private class FtdiSerialPort extends CommonUsbSerialPort {
@@ -182,9 +184,7 @@ public class FtdiSerialDriver implements UsbSerialDriver {
private DeviceType mType;
- private int mInterface = 0; /* INTERFACE_ANY */
-
- private int mMaxPacketSize = 64; // TODO(mikey): detect
+ private int mIndex = 0;
/**
* Due to http://b.android.com/28023 , we cannot use UsbRequest async reads
@@ -230,14 +230,21 @@ public class FtdiSerialDriver implements UsbSerialDriver {
}
public void reset() throws IOException {
+ // TODO(mikey): autodetect.
+ mType = DeviceType.TYPE_R;
+ if(mDevice.getInterfaceCount() > 1) {
+ mIndex = mPortNumber + 1;
+ if (mDevice.getInterfaceCount() == 2)
+ mType = DeviceType.TYPE_2232H;
+ if (mDevice.getInterfaceCount() == 4)
+ mType = DeviceType.TYPE_4232H;
+ }
+
int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE, SIO_RESET_REQUEST,
- SIO_RESET_SIO, 0 /* index */, null, 0, USB_WRITE_TIMEOUT_MILLIS);
+ SIO_RESET_SIO, mIndex, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Reset failed: result=" + result);
}
-
- // TODO(mikey): autodetect.
- mType = DeviceType.TYPE_R;
}
@Override
@@ -249,12 +256,10 @@ public class FtdiSerialDriver implements UsbSerialDriver {
boolean opened = false;
try {
- for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
- if (connection.claimInterface(mDevice.getInterface(i), true)) {
- Log.d(TAG, "claimInterface " + i + " SUCCESS");
- } else {
- throw new IOException("Error claiming interface " + i);
- }
+ if (connection.claimInterface(mDevice.getInterface(mPortNumber), true)) {
+ Log.d(TAG, "claimInterface " + mPortNumber + " SUCCESS");
+ } else {
+ throw new IOException("Error claiming interface " + mPortNumber);
}
reset();
opened = true;
@@ -280,7 +285,7 @@ public class FtdiSerialDriver implements UsbSerialDriver {
@Override
public int read(byte[] dest, int timeoutMillis) throws IOException {
- final UsbEndpoint endpoint = mDevice.getInterface(0).getEndpoint(0);
+ final UsbEndpoint endpoint = mDevice.getInterface(mPortNumber).getEndpoint(0);
if (mEnableAsyncReads) {
final UsbRequest request = new UsbRequest();
@@ -325,7 +330,7 @@ public class FtdiSerialDriver implements UsbSerialDriver {
@Override
public int write(byte[] src, int timeoutMillis) throws IOException {
- final UsbEndpoint endpoint = mDevice.getInterface(0).getEndpoint(1);
+ final UsbEndpoint endpoint = mDevice.getInterface(mPortNumber).getEndpoint(1);
int offset = 0;
while (offset < src.length) {
@@ -425,7 +430,7 @@ public class FtdiSerialDriver implements UsbSerialDriver {
}
int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE,
- SIO_SET_DATA_REQUEST, config, 0 /* index */,
+ SIO_SET_DATA_REQUEST, config, mIndex,
null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Setting parameters failed: result=" + result);
@@ -505,9 +510,8 @@ public class FtdiSerialDriver implements UsbSerialDriver {
long index;
if (mType == DeviceType.TYPE_2232C || mType == DeviceType.TYPE_2232H
|| mType == DeviceType.TYPE_4232H) {
- index = (encodedDivisor >> 8) & 0xffff;
- index &= 0xFF00;
- index |= 0 /* TODO mIndex */;
+ index = (encodedDivisor >> 8) & 0xff00;
+ index |= mIndex;
} else {
index = (encodedDivisor >> 16) & 0xffff;
}
@@ -560,7 +564,7 @@ public class FtdiSerialDriver implements UsbSerialDriver {
public boolean purgeHwBuffers(boolean purgeReadBuffers, boolean purgeWriteBuffers) throws IOException {
if (purgeReadBuffers) {
int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE, SIO_RESET_REQUEST,
- SIO_RESET_PURGE_RX, 0 /* index */, null, 0, USB_WRITE_TIMEOUT_MILLIS);
+ SIO_RESET_PURGE_RX, mIndex, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Flushing RX failed: result=" + result);
}
@@ -568,7 +572,7 @@ public class FtdiSerialDriver implements UsbSerialDriver {
if (purgeWriteBuffers) {
int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE, SIO_RESET_REQUEST,
- SIO_RESET_PURGE_TX, 0 /* index */, null, 0, USB_WRITE_TIMEOUT_MILLIS);
+ SIO_RESET_PURGE_TX, mIndex, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Flushing RX failed: result=" + result);
}
@@ -582,6 +586,9 @@ public class FtdiSerialDriver implements UsbSerialDriver {
supportedDevices.put(Integer.valueOf(UsbId.VENDOR_FTDI),
new int[] {
UsbId.FTDI_FT232R,
+ UsbId.FTDI_FT232H,
+ UsbId.FTDI_FT2232H,
+ UsbId.FTDI_FT4232H,
UsbId.FTDI_FT231X,
});
return supportedDevices;
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 01d3a85..550350c 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
@@ -32,10 +32,13 @@ 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.os.Build;
import android.util.Log;
import java.io.IOException;
import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
@@ -109,6 +112,7 @@ public class ProlificSerialDriver implements UsbSerialDriver {
private int mDeviceType = DEVICE_TYPE_HX;
+ private final boolean mEnableAsyncReads;
private UsbEndpoint mReadEndpoint;
private UsbEndpoint mWriteEndpoint;
private UsbEndpoint mInterruptEndpoint;
@@ -126,6 +130,7 @@ 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
@@ -368,15 +373,41 @@ public class ProlificSerialDriver implements UsbSerialDriver {
@Override
public int read(byte[] dest, int timeoutMillis) throws IOException {
- synchronized (mReadBufferLock) {
- int readAmt = Math.min(dest.length, mReadBuffer.length);
- int numBytesRead = mConnection.bulkTransfer(mReadEndpoint, mReadBuffer,
- readAmt, timeoutMillis);
- if (numBytesRead < 0) {
- return 0;
+ 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();
+ }
+ } 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;
}
}
diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbId.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbId.java
index fda7d1f..7bba4db 100644
--- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbId.java
+++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbId.java
@@ -33,6 +33,9 @@ public final class UsbId {
public static final int VENDOR_FTDI = 0x0403;
public static final int FTDI_FT232R = 0x6001;
+ public static final int FTDI_FT2232H = 0x6010;
+ public static final int FTDI_FT4232H = 0x6011;
+ public static final int FTDI_FT232H = 0x6014;
public static final int FTDI_FT231X = 0x6015;
public static final int VENDOR_ATMEL = 0x03EB;
diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/util/SerialInputOutputManager.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/util/SerialInputOutputManager.java
index 51c5655..78bcd0b 100644
--- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/util/SerialInputOutputManager.java
+++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/util/SerialInputOutputManager.java
@@ -120,7 +120,6 @@ public class SerialInputOutputManager implements Runnable {
* called, or until a driver exception is raised.
*
* NOTE(mikey): Uses inefficient read/write-with-timeout.
- * TODO(mikey): Read asynchronously with {@link UsbRequest#queue(ByteBuffer, int)}
*/
@Override
public void run() {