mirror of
				https://github.com/mik3y/usb-serial-for-android
				synced 2025-10-31 02:17:23 +00:00 
			
		
		
		
	Merge pull request #212 from kai-morich/multiport
support ft_232h, cp210_ multiport devices
This commit is contained in:
		
						commit
						f54dd65624
					
				
							
								
								
									
										4
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | |||||||
|  | caches | ||||||
|  | codeStyles | ||||||
|  | libraries | ||||||
|  | workspace.xml | ||||||
							
								
								
									
										1
									
								
								.idea/.name
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								.idea/.name
									
									
									
										generated
									
									
									
								
							| @ -1 +0,0 @@ | |||||||
| usb-serial-for-android |  | ||||||
							
								
								
									
										21
									
								
								.idea/compiler.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										21
									
								
								.idea/compiler.xml
									
									
									
										generated
									
									
									
								
							| @ -1,21 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <project version="4"> |  | ||||||
|   <component name="CompilerConfiguration"> |  | ||||||
|     <resourceExtensions /> |  | ||||||
|     <wildcardResourcePatterns> |  | ||||||
|       <entry name="!?*.java" /> |  | ||||||
|       <entry name="!?*.form" /> |  | ||||||
|       <entry name="!?*.class" /> |  | ||||||
|       <entry name="!?*.groovy" /> |  | ||||||
|       <entry name="!?*.scala" /> |  | ||||||
|       <entry name="!?*.flex" /> |  | ||||||
|       <entry name="!?*.kt" /> |  | ||||||
|       <entry name="!?*.clj" /> |  | ||||||
|     </wildcardResourcePatterns> |  | ||||||
|     <annotationProcessing> |  | ||||||
|       <profile default="true" name="Default" enabled="false"> |  | ||||||
|         <processorPath useClasspath="true" /> |  | ||||||
|       </profile> |  | ||||||
|     </annotationProcessing> |  | ||||||
|   </component> |  | ||||||
| </project> |  | ||||||
							
								
								
									
										3
									
								
								.idea/copyright/profiles_settings.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								.idea/copyright/profiles_settings.xml
									
									
									
										generated
									
									
									
								
							| @ -1,3 +0,0 @@ | |||||||
| <component name="CopyrightManager"> |  | ||||||
|   <settings default="" /> |  | ||||||
| </component> |  | ||||||
							
								
								
									
										54
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										54
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
								
							| @ -1,38 +1,34 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <project version="4"> | <project version="4"> | ||||||
|   <component name="EntryPointsManager"> |   <component name="NullableNotNullManager"> | ||||||
|     <entry_points version="2.0" /> |     <option name="myDefaultNullable" value="android.support.annotation.Nullable" /> | ||||||
|  |     <option name="myDefaultNotNull" value="android.support.annotation.NonNull" /> | ||||||
|  |     <option name="myNullables"> | ||||||
|  |       <value> | ||||||
|  |         <list size="5"> | ||||||
|  |           <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" /> | ||||||
|  |           <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" /> | ||||||
|  |           <item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" /> | ||||||
|  |           <item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" /> | ||||||
|  |           <item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" /> | ||||||
|  |         </list> | ||||||
|  |       </value> | ||||||
|  |     </option> | ||||||
|  |     <option name="myNotNulls"> | ||||||
|  |       <value> | ||||||
|  |         <list size="4"> | ||||||
|  |           <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" /> | ||||||
|  |           <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" /> | ||||||
|  |           <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" /> | ||||||
|  |           <item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" /> | ||||||
|  |         </list> | ||||||
|  |       </value> | ||||||
|  |     </option> | ||||||
|   </component> |   </component> | ||||||
|   <component name="ProjectLevelVcsManager" settingsEditedManually="false"> |   <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK"> | ||||||
|     <OptionsSetting value="true" id="Add" /> |  | ||||||
|     <OptionsSetting value="true" id="Remove" /> |  | ||||||
|     <OptionsSetting value="true" id="Checkout" /> |  | ||||||
|     <OptionsSetting value="true" id="Update" /> |  | ||||||
|     <OptionsSetting value="true" id="Status" /> |  | ||||||
|     <OptionsSetting value="true" id="Edit" /> |  | ||||||
|     <ConfirmationsSetting value="0" id="Add" /> |  | ||||||
|     <ConfirmationsSetting value="0" id="Remove" /> |  | ||||||
|   </component> |  | ||||||
|   <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK"> |  | ||||||
|     <output url="file://$PROJECT_DIR$/build/classes" /> |     <output url="file://$PROJECT_DIR$/build/classes" /> | ||||||
|   </component> |   </component> | ||||||
|   <component name="ProjectType"> |   <component name="ProjectType"> | ||||||
|     <option name="id" value="Android" /> |     <option name="id" value="Android" /> | ||||||
|   </component> |   </component> | ||||||
|   <component name="masterDetails"> |  | ||||||
|     <states> |  | ||||||
|       <state key="ProjectJDKs.UI"> |  | ||||||
|         <settings> |  | ||||||
|           <last-edited>1.8</last-edited> |  | ||||||
|           <splitter-proportions> |  | ||||||
|             <option name="proportions"> |  | ||||||
|               <list> |  | ||||||
|                 <option value="0.2" /> |  | ||||||
|               </list> |  | ||||||
|             </option> |  | ||||||
|           </splitter-proportions> |  | ||||||
|         </settings> |  | ||||||
|       </state> |  | ||||||
|     </states> |  | ||||||
|   </component> |  | ||||||
| </project> | </project> | ||||||
							
								
								
									
										12
									
								
								.idea/runConfigurations.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								.idea/runConfigurations.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <project version="4"> | ||||||
|  |   <component name="RunConfigurationProducerService"> | ||||||
|  |     <option name="ignoredProducers"> | ||||||
|  |       <set> | ||||||
|  |         <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" /> | ||||||
|  |         <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" /> | ||||||
|  |         <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" /> | ||||||
|  |       </set> | ||||||
|  |     </option> | ||||||
|  |   </component> | ||||||
|  | </project> | ||||||
| @ -2,15 +2,17 @@ | |||||||
| 
 | 
 | ||||||
| buildscript { | buildscript { | ||||||
|     repositories { |     repositories { | ||||||
|         mavenCentral() |         jcenter() | ||||||
|  |         google() | ||||||
|     } |     } | ||||||
|     dependencies { |     dependencies { | ||||||
|         classpath 'com.android.tools.build:gradle:1.2.3' |         classpath 'com.android.tools.build:gradle:3.1.2' | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| allprojects { | allprojects { | ||||||
|     repositories { |     repositories { | ||||||
|         mavenCentral() |         jcenter() | ||||||
|  |         google() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										
											BIN
										
									
								
								gradle/wrapper/gradle-wrapper.jar
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								gradle/wrapper/gradle-wrapper.jar
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										4
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							| @ -1,6 +1,6 @@ | |||||||
| #Tue Jun 23 00:11:28 EDT 2015 | #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-2.2.1-all.zip | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip | ||||||
|  | |||||||
							
								
								
									
										64
									
								
								test/arduino_leonardo_bridge/arduino_leonardo_bridge.ino
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								test/arduino_leonardo_bridge/arduino_leonardo_bridge.ino
									
									
									
									
									
										Normal file
									
								
							| @ -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()); | ||||||
|  | } | ||||||
							
								
								
									
										42
									
								
								test/rfc2217_server.diff
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								test/rfc2217_server.diff
									
									
									
									
									
										Normal file
									
								
							| @ -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) | ||||||
| @ -1,14 +1,13 @@ | |||||||
| apply plugin: 'com.android.application' | apply plugin: 'com.android.application' | ||||||
| 
 | 
 | ||||||
| android { | android { | ||||||
|     compileSdkVersion 22 |     compileSdkVersion 27 | ||||||
|     buildToolsVersion "22.0.1" |     buildToolsVersion '27.0.3' | ||||||
| 
 | 
 | ||||||
|     defaultConfig { |     defaultConfig { | ||||||
|         minSdkVersion 14 |         minSdkVersion 14 | ||||||
|         targetSdkVersion 22 |         targetSdkVersion 27 | ||||||
| 
 | 
 | ||||||
|         testApplicationId "com.hoho.android.usbserial.examples" |  | ||||||
|         testInstrumentationRunner "android.test.InstrumentationTestRunner" |         testInstrumentationRunner "android.test.InstrumentationTestRunner" | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -20,5 +19,5 @@ android { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| dependencies { | dependencies { | ||||||
|     compile project(':usbSerialForAndroid') |     implementation project(':usbSerialForAndroid') | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,12 +3,13 @@ apply plugin: 'maven' | |||||||
| apply plugin: 'signing' | apply plugin: 'signing' | ||||||
| 
 | 
 | ||||||
| android { | android { | ||||||
|     compileSdkVersion 19 |     compileSdkVersion 27 | ||||||
|     buildToolsVersion "19.1" |     buildToolsVersion '27.0.3' | ||||||
| 
 | 
 | ||||||
|     defaultConfig { |     defaultConfig { | ||||||
|         minSdkVersion 12 |         minSdkVersion 14 | ||||||
|         targetSdkVersion 19 |         targetSdkVersion 27 | ||||||
|  |         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     buildTypes { |     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" | group = "com.hoho.android" | ||||||
| version = "0.2.0-SNAPSHOT" | version = "0.2.0-SNAPSHOT" | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										7
									
								
								usbSerialForAndroid/src/androidTest/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								usbSerialForAndroid/src/androidTest/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |       package="com.hoho.android.usbserial" | ||||||
|  |       android:versionCode="1" | ||||||
|  |       android:versionName="1.0"> | ||||||
|  |     <uses-permission android:name="android.permission.INTERNET"/> | ||||||
|  | </manifest> | ||||||
| @ -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<byte[]> 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<UsbSerialDriver> 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<UsbSerialPort> getPorts() { return null; } | ||||||
|  |         } | ||||||
|  |         List<UsbSerialDriver> 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)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -84,9 +84,10 @@ public class Ch34xSerialDriver implements UsbSerialDriver { | |||||||
| 		private boolean dtr = false; | 		private boolean dtr = false; | ||||||
| 		private boolean rts = false; | 		private boolean rts = false; | ||||||
| 
 | 
 | ||||||
| 		private final boolean mEnableAsyncReads; | 		private final Boolean mEnableAsyncReads; | ||||||
| 		private UsbEndpoint mReadEndpoint; | 		private UsbEndpoint mReadEndpoint; | ||||||
| 		private UsbEndpoint mWriteEndpoint; | 		private UsbEndpoint mWriteEndpoint; | ||||||
|  | 		private UsbRequest mUsbRequest; | ||||||
| 
 | 
 | ||||||
| 		public Ch340SerialPort(UsbDevice device, int portNumber) { | 		public Ch340SerialPort(UsbDevice device, int portNumber) { | ||||||
| 			super(device, portNumber); | 			super(device, portNumber); | ||||||
| @ -109,10 +110,8 @@ public class Ch34xSerialDriver implements UsbSerialDriver { | |||||||
| 			try { | 			try { | ||||||
| 				for (int i = 0; i < mDevice.getInterfaceCount(); i++) { | 				for (int i = 0; i < mDevice.getInterfaceCount(); i++) { | ||||||
| 					UsbInterface usbIface = mDevice.getInterface(i); | 					UsbInterface usbIface = mDevice.getInterface(i); | ||||||
| 					if (mConnection.claimInterface(usbIface, true)) { | 					if (!mConnection.claimInterface(usbIface, true)) { | ||||||
| 						Log.d(TAG, "claimInterface " + i + " SUCCESS"); | 						throw new IOException("Could not claim data interface."); | ||||||
| 					} else { |  | ||||||
| 						Log.d(TAG, "claimInterface " + i + " FAIL"); |  | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| @ -154,7 +153,10 @@ public class Ch34xSerialDriver implements UsbSerialDriver { | |||||||
| 			if (mConnection == null) { | 			if (mConnection == null) { | ||||||
| 				throw new IOException("Already closed"); | 				throw new IOException("Already closed"); | ||||||
| 			} | 			} | ||||||
| 
 | 			synchronized (mEnableAsyncReads) { | ||||||
|  | 				if (mUsbRequest != null) | ||||||
|  | 					mUsbRequest.cancel(); | ||||||
|  | 			} | ||||||
| 			// TODO: nothing sended on close, maybe needed? | 			// TODO: nothing sended on close, maybe needed? | ||||||
| 
 | 
 | ||||||
| 			try { | 			try { | ||||||
| @ -175,8 +177,11 @@ public class Ch34xSerialDriver implements UsbSerialDriver { | |||||||
| 					if (!request.queue(buf, dest.length)) { | 					if (!request.queue(buf, dest.length)) { | ||||||
| 						throw new IOException("Error queueing request."); | 						throw new IOException("Error queueing request."); | ||||||
| 					} | 					} | ||||||
| 
 | 					mUsbRequest = request; | ||||||
| 					final UsbRequest response = mConnection.requestWait(); | 					final UsbRequest response = mConnection.requestWait(); | ||||||
|  | 					synchronized (mEnableAsyncReads) { | ||||||
|  | 						mUsbRequest = null; | ||||||
|  | 					} | ||||||
| 					if (response == null) { | 					if (response == null) { | ||||||
| 						throw new IOException("Null response"); | 						throw new IOException("Null response"); | ||||||
| 					} | 					} | ||||||
| @ -189,25 +194,26 @@ public class Ch34xSerialDriver implements UsbSerialDriver { | |||||||
| 						return 0; | 						return 0; | ||||||
| 					} | 					} | ||||||
| 				} finally { | 				} finally { | ||||||
|  | 					mUsbRequest = null; | ||||||
| 					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,10 +26,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.util.Collections; | import java.nio.ByteBuffer; | ||||||
|  | import java.util.ArrayList; | ||||||
| import java.util.LinkedHashMap; | import java.util.LinkedHashMap; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
| @ -39,11 +42,14 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | |||||||
|     private static final String TAG = Cp21xxSerialDriver.class.getSimpleName(); |     private static final String TAG = Cp21xxSerialDriver.class.getSimpleName(); | ||||||
| 
 | 
 | ||||||
|     private final UsbDevice mDevice; |     private final UsbDevice mDevice; | ||||||
|     private final UsbSerialPort mPort; |     private final List<UsbSerialPort> mPorts; | ||||||
| 
 | 
 | ||||||
|     public Cp21xxSerialDriver(UsbDevice device) { |     public Cp21xxSerialDriver(UsbDevice device) { | ||||||
|         mDevice = 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 |     @Override | ||||||
| @ -53,7 +59,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | |||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public List<UsbSerialPort> getPorts() { |     public List<UsbSerialPort> getPorts() { | ||||||
|         return Collections.singletonList(mPort); |         return mPorts; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public class Cp21xxSerialPort extends CommonUsbSerialPort { |     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_DTR = 0x0100; | ||||||
|         private static final int CONTROL_WRITE_RTS = 0x0200; |         private static final int CONTROL_WRITE_RTS = 0x0200; | ||||||
| 
 | 
 | ||||||
|  |         private final Boolean mEnableAsyncReads; | ||||||
|         private UsbEndpoint mReadEndpoint; |         private UsbEndpoint mReadEndpoint; | ||||||
|         private UsbEndpoint mWriteEndpoint; |         private UsbEndpoint mWriteEndpoint; | ||||||
|  |         private UsbRequest mUsbRequest; | ||||||
|  | 
 | ||||||
|  |         // 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) { |         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 | ||||||
| @ -115,7 +128,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | |||||||
| 
 | 
 | ||||||
|         private int setConfigSingle(int request, int value) { |         private int setConfigSingle(int request, int value) { | ||||||
|             return mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, 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 |         @Override | ||||||
| @ -126,17 +139,15 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | |||||||
| 
 | 
 | ||||||
|             mConnection = connection; |             mConnection = connection; | ||||||
|             boolean opened = false; |             boolean opened = false; | ||||||
|  |             mIsRestrictedPort = mDevice.getInterfaceCount() == 2 && mPortNumber == 1; | ||||||
|             try { |             try { | ||||||
|                 for (int i = 0; i < mDevice.getInterfaceCount(); i++) { |                 if(mPortNumber >= mDevice.getInterfaceCount()) { | ||||||
|                     UsbInterface usbIface = mDevice.getInterface(i); |                     throw new IOException("Unknown port number"); | ||||||
|                     if (mConnection.claimInterface(usbIface, true)) { |                 } | ||||||
|                         Log.d(TAG, "claimInterface " + i + " SUCCESS"); |                 UsbInterface dataIface = mDevice.getInterface(mPortNumber); | ||||||
|                     } else { |                 if (!mConnection.claimInterface(dataIface, true)) { | ||||||
|                         Log.d(TAG, "claimInterface " + i + " FAIL"); |                     throw new IOException("Could not claim interface " + mPortNumber); | ||||||
|                     } |  | ||||||
|                 } |                 } | ||||||
| 
 |  | ||||||
|                 UsbInterface dataIface = mDevice.getInterface(mDevice.getInterfaceCount() - 1); |  | ||||||
|                 for (int i = 0; i < dataIface.getEndpointCount(); i++) { |                 for (int i = 0; i < dataIface.getEndpointCount(); i++) { | ||||||
|                     UsbEndpoint ep = dataIface.getEndpoint(i); |                     UsbEndpoint ep = dataIface.getEndpoint(i); | ||||||
|                     if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { |                     if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { | ||||||
| @ -169,6 +180,11 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | |||||||
|             if (mConnection == null) { |             if (mConnection == null) { | ||||||
|                 throw new IOException("Already closed"); |                 throw new IOException("Already closed"); | ||||||
|             } |             } | ||||||
|  |             synchronized (mEnableAsyncReads) { | ||||||
|  |                 if(mUsbRequest != null) { | ||||||
|  |                     mUsbRequest.cancel(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|             try { |             try { | ||||||
|                 setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_DISABLE); |                 setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_DISABLE); | ||||||
|                 mConnection.close(); |                 mConnection.close(); | ||||||
| @ -179,21 +195,51 @@ 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 |                     mUsbRequest = request; | ||||||
|                     // in response :\ -- http://b.android.com/28023 |                     final UsbRequest response = mConnection.requestWait(); | ||||||
|                     return 0; |                     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 |         @Override | ||||||
| @ -238,7 +284,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | |||||||
|                     (byte) ((baudRate >> 24) & 0xff) |                     (byte) ((baudRate >> 24) & 0xff) | ||||||
|             }; |             }; | ||||||
|             int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_BAUDRATE, |             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) { |             if (ret < 0) { | ||||||
|                 throw new IOException("Error setting baud rate."); |                 throw new IOException("Error setting baud rate."); | ||||||
|             } |             } | ||||||
| @ -252,12 +298,18 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | |||||||
|             int configDataBits = 0; |             int configDataBits = 0; | ||||||
|             switch (dataBits) { |             switch (dataBits) { | ||||||
|                 case DATABITS_5: |                 case DATABITS_5: | ||||||
|  |                     if(mIsRestrictedPort) | ||||||
|  |                         throw new IllegalArgumentException("Unsupported dataBits value: " + dataBits); | ||||||
|                     configDataBits |= 0x0500; |                     configDataBits |= 0x0500; | ||||||
|                     break; |                     break; | ||||||
|                 case DATABITS_6: |                 case DATABITS_6: | ||||||
|  |                     if(mIsRestrictedPort) | ||||||
|  |                         throw new IllegalArgumentException("Unsupported dataBits value: " + dataBits); | ||||||
|                     configDataBits |= 0x0600; |                     configDataBits |= 0x0600; | ||||||
|                     break; |                     break; | ||||||
|                 case DATABITS_7: |                 case DATABITS_7: | ||||||
|  |                     if(mIsRestrictedPort) | ||||||
|  |                         throw new IllegalArgumentException("Unsupported dataBits value: " + dataBits); | ||||||
|                     configDataBits |= 0x0700; |                     configDataBits |= 0x0700; | ||||||
|                     break; |                     break; | ||||||
|                 case DATABITS_8: |                 case DATABITS_8: | ||||||
| @ -277,9 +329,13 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | |||||||
|                     configDataBits |= 0x0020; |                     configDataBits |= 0x0020; | ||||||
|                     break; |                     break; | ||||||
|                 case PARITY_MARK: |                 case PARITY_MARK: | ||||||
|  |                     if(mIsRestrictedPort) | ||||||
|  |                         throw new IllegalArgumentException("Unsupported parity value: mark"); | ||||||
|                     configDataBits |= 0x0030; |                     configDataBits |= 0x0030; | ||||||
|                     break; |                     break; | ||||||
|                 case PARITY_SPACE: |                 case PARITY_SPACE: | ||||||
|  |                     if(mIsRestrictedPort) | ||||||
|  |                         throw new IllegalArgumentException("Unsupported parity value: space"); | ||||||
|                     configDataBits |= 0x0040; |                     configDataBits |= 0x0040; | ||||||
|                     break; |                     break; | ||||||
|                 default: |                 default: | ||||||
| @ -292,6 +348,8 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | |||||||
|                 case STOPBITS_1_5: |                 case STOPBITS_1_5: | ||||||
|                     throw new IllegalArgumentException("Unsupported stopBits value: 1.5"); |                     throw new IllegalArgumentException("Unsupported stopBits value: 1.5"); | ||||||
|                 case STOPBITS_2: |                 case STOPBITS_2: | ||||||
|  |                     if(mIsRestrictedPort) | ||||||
|  |                         throw new IllegalArgumentException("Unsupported stopBits value: 2"); | ||||||
|                     configDataBits |= 2; |                     configDataBits |= 2; | ||||||
|                     break; |                     break; | ||||||
|                 default: |                 default: | ||||||
|  | |||||||
| @ -29,11 +29,9 @@ import android.hardware.usb.UsbRequest; | |||||||
| import android.os.Build; | import android.os.Build; | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
| 
 | 
 | ||||||
| import com.hoho.android.usbserial.util.HexDump; |  | ||||||
| 
 |  | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.nio.ByteBuffer; | import java.nio.ByteBuffer; | ||||||
| import java.util.Collections; | import java.util.ArrayList; | ||||||
| import java.util.LinkedHashMap; | import java.util.LinkedHashMap; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
| @ -73,6 +71,8 @@ import java.util.Map; | |||||||
|  * Supported and tested devices: |  * Supported and tested devices: | ||||||
|  * <ul> |  * <ul> | ||||||
|  * <li>{@value DeviceType#TYPE_R}</li> |  * <li>{@value DeviceType#TYPE_R}</li> | ||||||
|  |  * <li>{@value DeviceType#TYPE_2232H}</li> | ||||||
|  |  * <li>{@value DeviceType#TYPE_4232H}</li> | ||||||
|  * </ul> |  * </ul> | ||||||
|  * </p> |  * </p> | ||||||
|  * <p> |  * <p> | ||||||
| @ -80,8 +80,6 @@ import java.util.Map; | |||||||
|  * feedback or patches): |  * feedback or patches): | ||||||
|  * <ul> |  * <ul> | ||||||
|  * <li>{@value DeviceType#TYPE_2232C}</li> |  * <li>{@value DeviceType#TYPE_2232C}</li> | ||||||
|  * <li>{@value DeviceType#TYPE_2232H}</li> |  | ||||||
|  * <li>{@value DeviceType#TYPE_4232H}</li> |  | ||||||
|  * <li>{@value DeviceType#TYPE_AM}</li> |  * <li>{@value DeviceType#TYPE_AM}</li> | ||||||
|  * <li>{@value DeviceType#TYPE_BM}</li> |  * <li>{@value DeviceType#TYPE_BM}</li> | ||||||
|  * </ul> |  * </ul> | ||||||
| @ -96,7 +94,7 @@ import java.util.Map; | |||||||
| public class FtdiSerialDriver implements UsbSerialDriver { | public class FtdiSerialDriver implements UsbSerialDriver { | ||||||
| 
 | 
 | ||||||
|     private final UsbDevice mDevice; |     private final UsbDevice mDevice; | ||||||
|     private final UsbSerialPort mPort; |     private final List<UsbSerialPort> mPorts; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * FTDI chip types. |      * FTDI chip types. | ||||||
| @ -107,8 +105,12 @@ public class FtdiSerialDriver implements UsbSerialDriver { | |||||||
| 
 | 
 | ||||||
|     public FtdiSerialDriver(UsbDevice device) { |     public FtdiSerialDriver(UsbDevice device) { | ||||||
|         mDevice = 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 |     @Override | ||||||
|     public UsbDevice getDevice() { |     public UsbDevice getDevice() { | ||||||
|         return mDevice; |         return mDevice; | ||||||
| @ -116,7 +118,7 @@ public class FtdiSerialDriver implements UsbSerialDriver { | |||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public List<UsbSerialPort> getPorts() { |     public List<UsbSerialPort> getPorts() { | ||||||
|         return Collections.singletonList(mPort); |         return mPorts; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private class FtdiSerialPort extends CommonUsbSerialPort { |     private class FtdiSerialPort extends CommonUsbSerialPort { | ||||||
| @ -182,9 +184,7 @@ public class FtdiSerialDriver implements UsbSerialDriver { | |||||||
| 
 | 
 | ||||||
|         private DeviceType mType; |         private DeviceType mType; | ||||||
| 
 | 
 | ||||||
|         private int mInterface = 0; /* INTERFACE_ANY */ |         private int mIndex = 0; | ||||||
| 
 |  | ||||||
|         private int mMaxPacketSize = 64; // TODO(mikey): detect |  | ||||||
| 
 | 
 | ||||||
|         /** |         /** | ||||||
|          * Due to http://b.android.com/28023 , we cannot use UsbRequest async reads |          * 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 { |         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, |             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) { |             if (result != 0) { | ||||||
|                 throw new IOException("Reset failed: result=" + result); |                 throw new IOException("Reset failed: result=" + result); | ||||||
|             } |             } | ||||||
| 
 |  | ||||||
|             // TODO(mikey): autodetect. |  | ||||||
|             mType = DeviceType.TYPE_R; |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         @Override |         @Override | ||||||
| @ -249,12 +256,10 @@ public class FtdiSerialDriver implements UsbSerialDriver { | |||||||
| 
 | 
 | ||||||
|             boolean opened = false; |             boolean opened = false; | ||||||
|             try { |             try { | ||||||
|                 for (int i = 0; i < mDevice.getInterfaceCount(); i++) { |                 if (connection.claimInterface(mDevice.getInterface(mPortNumber), true)) { | ||||||
|                     if (connection.claimInterface(mDevice.getInterface(i), true)) { |                     Log.d(TAG, "claimInterface " + mPortNumber + " SUCCESS"); | ||||||
|                         Log.d(TAG, "claimInterface " + i + " SUCCESS"); |                 } else { | ||||||
|                     } else { |                     throw new IOException("Error claiming interface " + mPortNumber); | ||||||
|                         throw new IOException("Error claiming interface " + i); |  | ||||||
|                     } |  | ||||||
|                 } |                 } | ||||||
|                 reset(); |                 reset(); | ||||||
|                 opened = true; |                 opened = true; | ||||||
| @ -280,7 +285,7 @@ public class FtdiSerialDriver implements UsbSerialDriver { | |||||||
| 
 | 
 | ||||||
|         @Override |         @Override | ||||||
|         public int read(byte[] dest, int timeoutMillis) throws IOException { |         public int read(byte[] dest, int timeoutMillis) throws IOException { | ||||||
|             final UsbEndpoint endpoint = mDevice.getInterface(0).getEndpoint(0); |             final UsbEndpoint endpoint = mDevice.getInterface(mPortNumber).getEndpoint(0); | ||||||
| 
 | 
 | ||||||
|             if (mEnableAsyncReads) { |             if (mEnableAsyncReads) { | ||||||
|                 final UsbRequest request = new UsbRequest(); |                 final UsbRequest request = new UsbRequest(); | ||||||
| @ -325,7 +330,7 @@ public class FtdiSerialDriver implements UsbSerialDriver { | |||||||
| 
 | 
 | ||||||
|         @Override |         @Override | ||||||
|         public int write(byte[] src, int timeoutMillis) throws IOException { |         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; |             int offset = 0; | ||||||
| 
 | 
 | ||||||
|             while (offset < src.length) { |             while (offset < src.length) { | ||||||
| @ -425,7 +430,7 @@ public class FtdiSerialDriver implements UsbSerialDriver { | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE, |             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); |                     null, 0, USB_WRITE_TIMEOUT_MILLIS); | ||||||
|             if (result != 0) { |             if (result != 0) { | ||||||
|                 throw new IOException("Setting parameters failed: result=" + result); |                 throw new IOException("Setting parameters failed: result=" + result); | ||||||
| @ -505,9 +510,8 @@ public class FtdiSerialDriver implements UsbSerialDriver { | |||||||
|             long index; |             long index; | ||||||
|             if (mType == DeviceType.TYPE_2232C || mType == DeviceType.TYPE_2232H |             if (mType == DeviceType.TYPE_2232C || mType == DeviceType.TYPE_2232H | ||||||
|                     || mType == DeviceType.TYPE_4232H) { |                     || mType == DeviceType.TYPE_4232H) { | ||||||
|                 index = (encodedDivisor >> 8) & 0xffff; |                 index = (encodedDivisor >> 8) & 0xff00; | ||||||
|                 index &= 0xFF00; |                 index |= mIndex; | ||||||
|                 index |= 0 /* TODO mIndex */; |  | ||||||
|             } else { |             } else { | ||||||
|                 index = (encodedDivisor >> 16) & 0xffff; |                 index = (encodedDivisor >> 16) & 0xffff; | ||||||
|             } |             } | ||||||
| @ -560,7 +564,7 @@ public class FtdiSerialDriver implements UsbSerialDriver { | |||||||
|         public boolean purgeHwBuffers(boolean purgeReadBuffers, boolean purgeWriteBuffers) throws IOException { |         public boolean purgeHwBuffers(boolean purgeReadBuffers, boolean purgeWriteBuffers) throws IOException { | ||||||
|             if (purgeReadBuffers) { |             if (purgeReadBuffers) { | ||||||
|                 int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE, SIO_RESET_REQUEST, |                 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) { |                 if (result != 0) { | ||||||
|                     throw new IOException("Flushing RX failed: result=" + result); |                     throw new IOException("Flushing RX failed: result=" + result); | ||||||
|                 } |                 } | ||||||
| @ -568,7 +572,7 @@ public class FtdiSerialDriver implements UsbSerialDriver { | |||||||
| 
 | 
 | ||||||
|             if (purgeWriteBuffers) { |             if (purgeWriteBuffers) { | ||||||
|                 int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE, SIO_RESET_REQUEST, |                 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) { |                 if (result != 0) { | ||||||
|                     throw new IOException("Flushing RX failed: result=" + result); |                     throw new IOException("Flushing RX failed: result=" + result); | ||||||
|                 } |                 } | ||||||
| @ -582,6 +586,9 @@ public class FtdiSerialDriver implements UsbSerialDriver { | |||||||
|         supportedDevices.put(Integer.valueOf(UsbId.VENDOR_FTDI), |         supportedDevices.put(Integer.valueOf(UsbId.VENDOR_FTDI), | ||||||
|                 new int[] { |                 new int[] { | ||||||
|                     UsbId.FTDI_FT232R, |                     UsbId.FTDI_FT232R, | ||||||
|  |                     UsbId.FTDI_FT232H, | ||||||
|  |                     UsbId.FTDI_FT2232H, | ||||||
|  |                     UsbId.FTDI_FT4232H, | ||||||
|                     UsbId.FTDI_FT231X, |                     UsbId.FTDI_FT231X, | ||||||
|                 }); |                 }); | ||||||
|         return supportedDevices; |         return supportedDevices; | ||||||
|  | |||||||
| @ -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; |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -33,6 +33,9 @@ public final class UsbId { | |||||||
| 
 | 
 | ||||||
|     public static final int VENDOR_FTDI = 0x0403; |     public static final int VENDOR_FTDI = 0x0403; | ||||||
|     public static final int FTDI_FT232R = 0x6001; |     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 FTDI_FT231X = 0x6015; | ||||||
| 
 | 
 | ||||||
|     public static final int VENDOR_ATMEL = 0x03EB; |     public static final int VENDOR_ATMEL = 0x03EB; | ||||||
|  | |||||||
| @ -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