mirror of
				https://github.com/mik3y/usb-serial-for-android
				synced 2025-10-31 02:17:23 +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