mirror of
				https://github.com/mik3y/usb-serial-for-android
				synced 2025-10-31 02:17:23 +00:00 
			
		
		
		
	Compare commits
	
		
			60 Commits
		
	
	
		
			6ffb666b33
			...
			c608aadc59
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | c608aadc59 | ||
|  | 06faad5622 | ||
|  | 52042f8c3e | ||
|  | 068fe80c07 | ||
|  | e1018ab31c | ||
|  | 026355f61e | ||
|  | 150174573c | ||
|  | 9911e141a7 | ||
|  | 2673407f1d | ||
|  | 8584fe4cb8 | ||
|  | 0b5950c991 | ||
|  | 9f93e192ca | ||
|  | f5380975ce | ||
|  | 0a32c3f9e3 | ||
|  | 88ca3f57c4 | ||
|  | 843792001f | ||
|  | 275590027b | ||
|  | b6e1833270 | ||
|  | b794092c81 | ||
|  | b1362416f0 | ||
|  | 0c0275675f | ||
|  | cab862599d | ||
|  | 2fbceb6cc7 | ||
|  | a4ee5c2158 | ||
|  | 9bc3834eff | ||
|  | 28506a9bf9 | ||
|  | 8b9ad7efdf | ||
|  | 1245293888 | ||
|  | 26a2f9363e | ||
|  | 83646d6955 | ||
|  | 573c7e41ca | ||
|  | 880c0070cb | ||
|  | a1709c3911 | ||
|  | 9c30dc5ffa | ||
|  | b06118b156 | ||
|  | de6d5aa384 | ||
|  | 11ccb5b949 | ||
|  | d585ca8be7 | ||
|  | 2a2463cd12 | ||
|  | 80a555a189 | ||
|  | 34e6d989fd | ||
|  | 35fdeb1e13 | ||
|  | 399d3c9c2f | ||
|  | 54ff9bfa44 | ||
|  | 7aecce7943 | ||
|  | d15f4d52bb | ||
|  | fd8c155ca5 | ||
|  | 88b74d716c | ||
|  | e9a38ca891 | ||
|  | a9c835bcb0 | ||
|  | 9bd1f25773 | ||
|  | 083b9ae7fe | ||
|  | fd551970be | ||
|  | 5db45548ba | ||
|  | 85f64aff96 | ||
|  | 6c648e9f56 | ||
|  | dd1b95b852 | ||
|  | 7ea76f8899 | ||
|  | fbe64fe4be | ||
|  | 8d3326ed66 | 
							
								
								
									
										7
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @ -8,11 +8,10 @@ jobs: | |||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
| 
 | 
 | ||||||
|     steps: |     steps: | ||||||
|     - uses: actions/checkout@v2 |     - uses: actions/checkout@v3 | ||||||
|     - name: set up JDK 1.11 |     - uses: actions/setup-java@v3 | ||||||
|       uses: actions/setup-java@v2 |  | ||||||
|       with: |       with: | ||||||
|         distribution: 'temurin' |         distribution: 'temurin' | ||||||
|         java-version: '11' |         java-version: '17' | ||||||
|     - name: Build with Gradle |     - name: Build with Gradle | ||||||
|       run: ./gradlew assembleDebug |       run: ./gradlew assembleDebug | ||||||
|  | |||||||
							
								
								
									
										5
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -2,3 +2,8 @@ caches | |||||||
| codeStyles | codeStyles | ||||||
| libraries | libraries | ||||||
| workspace.xml | workspace.xml | ||||||
|  | androidTestResultsUserPreferences.xml | ||||||
|  | appInsightsSettings.xml | ||||||
|  | deploymentTargetDropDown.xml | ||||||
|  | deploymentTargetSelector.xml | ||||||
|  | other.xml | ||||||
|  | |||||||
							
								
								
									
										6
									
								
								.idea/AndroidProjectSystem.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.idea/AndroidProjectSystem.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <project version="4"> | ||||||
|  |   <component name="AndroidProjectSystem"> | ||||||
|  |     <option name="providerId" value="com.android.tools.idea.GradleProjectSystem" /> | ||||||
|  |   </component> | ||||||
|  | </project> | ||||||
							
								
								
									
										2
									
								
								.idea/compiler.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								.idea/compiler.xml
									
									
									
										generated
									
									
									
								
							| @ -1,6 +1,6 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <project version="4"> | <project version="4"> | ||||||
|   <component name="CompilerConfiguration"> |   <component name="CompilerConfiguration"> | ||||||
|     <bytecodeTargetLevel target="11" /> |     <bytecodeTargetLevel target="21" /> | ||||||
|   </component> |   </component> | ||||||
| </project> | </project> | ||||||
							
								
								
									
										12
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										12
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
								
							| @ -6,7 +6,7 @@ | |||||||
|     <option name="myDefaultNotNull" value="androidx.annotation.NonNull" /> |     <option name="myDefaultNotNull" value="androidx.annotation.NonNull" /> | ||||||
|     <option name="myNullables"> |     <option name="myNullables"> | ||||||
|       <value> |       <value> | ||||||
|         <list size="15"> |         <list size="18"> | ||||||
|           <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" /> |           <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="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" /> | ||||||
|           <item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" /> |           <item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" /> | ||||||
| @ -22,12 +22,15 @@ | |||||||
|           <item index="12" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.Nullable" /> |           <item index="12" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.Nullable" /> | ||||||
|           <item index="13" class="java.lang.String" itemvalue="io.reactivex.annotations.Nullable" /> |           <item index="13" class="java.lang.String" itemvalue="io.reactivex.annotations.Nullable" /> | ||||||
|           <item index="14" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.Nullable" /> |           <item index="14" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.Nullable" /> | ||||||
|  |           <item index="15" class="java.lang.String" itemvalue="org.jspecify.nullness.Nullable" /> | ||||||
|  |           <item index="16" class="java.lang.String" itemvalue="jakarta.annotation.Nullable" /> | ||||||
|  |           <item index="17" class="java.lang.String" itemvalue="org.jspecify.annotations.Nullable" /> | ||||||
|         </list> |         </list> | ||||||
|       </value> |       </value> | ||||||
|     </option> |     </option> | ||||||
|     <option name="myNotNulls"> |     <option name="myNotNulls"> | ||||||
|       <value> |       <value> | ||||||
|         <list size="14"> |         <list size="17"> | ||||||
|           <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" /> |           <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="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="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" /> | ||||||
| @ -42,11 +45,14 @@ | |||||||
|           <item index="11" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.NonNull" /> |           <item index="11" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.NonNull" /> | ||||||
|           <item index="12" class="java.lang.String" itemvalue="io.reactivex.annotations.NonNull" /> |           <item index="12" class="java.lang.String" itemvalue="io.reactivex.annotations.NonNull" /> | ||||||
|           <item index="13" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.NonNull" /> |           <item index="13" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.NonNull" /> | ||||||
|  |           <item index="14" class="java.lang.String" itemvalue="jakarta.annotation.Nonnull" /> | ||||||
|  |           <item index="15" class="java.lang.String" itemvalue="org.jspecify.nullness.NonNull" /> | ||||||
|  |           <item index="16" class="java.lang.String" itemvalue="org.jspecify.annotations.NonNull" /> | ||||||
|         </list> |         </list> | ||||||
|       </value> |       </value> | ||||||
|     </option> |     </option> | ||||||
|   </component> |   </component> | ||||||
|   <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK"> |   <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" 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"> | ||||||
|  | |||||||
							
								
								
									
										17
									
								
								.idea/runConfigurations.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								.idea/runConfigurations.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <project version="4"> | ||||||
|  |   <component name="RunConfigurationProducerService"> | ||||||
|  |     <option name="ignoredProducers"> | ||||||
|  |       <set> | ||||||
|  |         <option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" /> | ||||||
|  |         <option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" /> | ||||||
|  |         <option value="com.intellij.execution.junit.PatternConfigurationProducer" /> | ||||||
|  |         <option value="com.intellij.execution.junit.TestInClassConfigurationProducer" /> | ||||||
|  |         <option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" /> | ||||||
|  |         <option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" /> | ||||||
|  |         <option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" /> | ||||||
|  |         <option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" /> | ||||||
|  |       </set> | ||||||
|  |     </option> | ||||||
|  |   </component> | ||||||
|  | </project> | ||||||
							
								
								
									
										44
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										44
									
								
								README.md
									
									
									
									
									
								
							| @ -1,6 +1,6 @@ | |||||||
| [](https://github.com/mik3y/usb-serial-for-android/actions) | [](https://github.com/mik3y/usb-serial-for-android/actions) | ||||||
| [](https://jitpack.io/#mik3y/usb-serial-for-android) | [](https://jitpack.io/#mik3y/usb-serial-for-android) | ||||||
| [](https://www.codacy.com/gh/kai-morich/usb-serial-for-android-mik3y/dashboard?utm_source=github.com&utm_medium=referral&utm_content=kai-morich/usb-serial-for-android-mik3y&utm_campaign=Badge_Grade) | [](https://app.codacy.com/gh/kai-morich/usb-serial-for-android-mik3y/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) | ||||||
| [](https://codecov.io/gh/mik3y/usb-serial-for-android) | [](https://codecov.io/gh/mik3y/usb-serial-for-android) | ||||||
| 
 | 
 | ||||||
| # usb-serial-for-android | # usb-serial-for-android | ||||||
| @ -37,10 +37,15 @@ dependencyResolutionManagement { | |||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | If using gradle kotlin  use line | ||||||
|  | ```gradle.kts | ||||||
|  |         maven(url = "https://jitpack.io") | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| Add library to dependencies | Add library to dependencies | ||||||
| ```gradle | ```gradle | ||||||
| dependencies { | dependencies { | ||||||
|     implementation 'com.github.mik3y:usb-serial-for-android:3.4.6' |     implementation 'com.github.mik3y:usb-serial-for-android:3.9.0' | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| @ -110,9 +115,10 @@ For a simple example, see | |||||||
| [UsbSerialExamples](https://github.com/mik3y/usb-serial-for-android/blob/master/usbSerialExamples) | [UsbSerialExamples](https://github.com/mik3y/usb-serial-for-android/blob/master/usbSerialExamples) | ||||||
| folder in this project. | folder in this project. | ||||||
| 
 | 
 | ||||||
| For a more complete example with background service to stay connected while | See separate github project [SimpleUsbTerminal](https://github.com/kai-morich/SimpleUsbTerminal)  | ||||||
| the app is not visible or rotating, see separate github project  | for a more complete example with: | ||||||
| [SimpleUsbTerminal](https://github.com/kai-morich/SimpleUsbTerminal). | * Background service to stay connected while the app is not visible or rotating | ||||||
|  | * Flow control  | ||||||
| 
 | 
 | ||||||
| ## Probing for Unrecognized Devices | ## Probing for Unrecognized Devices | ||||||
| 
 | 
 | ||||||
| @ -124,22 +130,23 @@ new device or for one using a custom VID/PID pair. | |||||||
| UsbSerialProber is a class to help you find and instantiate compatible | UsbSerialProber is a class to help you find and instantiate compatible | ||||||
| UsbSerialDrivers from the tree of connected UsbDevices.  Normally, you will use | UsbSerialDrivers from the tree of connected UsbDevices.  Normally, you will use | ||||||
| the default prober returned by ``UsbSerialProber.getDefaultProber()``, which | the default prober returned by ``UsbSerialProber.getDefaultProber()``, which | ||||||
| uses the built-in list of well-known VIDs and PIDs that are supported by our | uses USB interface types and the built-in list of well-known VIDs and PIDs that | ||||||
| drivers. | are supported by our drivers. | ||||||
| 
 | 
 | ||||||
| To use your own set of rules, create and use a custom prober: | To use your own set of rules, create and use a custom prober: | ||||||
| 
 | 
 | ||||||
| ```java | ```java | ||||||
| // Probe for our custom CDC devices, which use VID 0x1234 | // Probe for our custom FTDI device, which use VID 0x1234 and PID 0x0001 and 0x0002. | ||||||
| // and PIDS 0x0001 and 0x0002. |  | ||||||
| ProbeTable customTable = new ProbeTable(); | ProbeTable customTable = new ProbeTable(); | ||||||
| customTable.addProduct(0x1234, 0x0001, CdcAcmSerialDriver.class); | customTable.addProduct(0x1234, 0x0001, FtdiSerialDriver.class); | ||||||
| customTable.addProduct(0x1234, 0x0002, CdcAcmSerialDriver.class); | customTable.addProduct(0x1234, 0x0002, FtdiSerialDriver.class); | ||||||
| 
 | 
 | ||||||
| UsbSerialProber prober = new UsbSerialProber(customTable); | UsbSerialProber prober = new UsbSerialProber(customTable); | ||||||
| List<UsbSerialDriver> drivers = prober.findAllDrivers(usbManager); | List<UsbSerialDriver> drivers = prober.findAllDrivers(usbManager); | ||||||
| // ... | // ... | ||||||
| ``` | ``` | ||||||
|  | *Note*: as of v3.5.0 this library detects CDC/ACM devices by USB interface types instead of fixed VID+PID, | ||||||
|  | so custom probers are typically not required any more for CDC/ACM devices. | ||||||
| 
 | 
 | ||||||
| Of course, nothing requires you to use UsbSerialProber at all: you can | Of course, nothing requires you to use UsbSerialProber at all: you can | ||||||
| instantiate driver classes directly if you know what you're doing; just supply | instantiate driver classes directly if you know what you're doing; just supply | ||||||
| @ -147,16 +154,21 @@ a compatible UsbDevice. | |||||||
| 
 | 
 | ||||||
| ## Compatible Devices | ## Compatible Devices | ||||||
| 
 | 
 | ||||||
| This library supports USB to serial converter chips: | This library supports USB to serial converter chips with specific drivers | ||||||
| * FTDI FT232R, FT232H, FT2232H, FT4232H, FT230X, FT231X, FT234XD | * FTDI FT232R, FT232H, FT2232H, FT4232H, FT230X, FT231X, FT234XD | ||||||
| * Prolific PL2303 | * Prolific PL2303 | ||||||
| * Silabs CP2102 and all other CP210x | * Silabs CP2102, CP210* | ||||||
| * Qinheng CH340, CH341A, CH9102 | * Qinheng CH340, CH341A | ||||||
| 
 | 
 | ||||||
| and devices implementing the CDC/ACM protocol like | some other device specific drivers | ||||||
|  | * GsmModem devices, e.g. for Unisoc based Fibocom GSM modems | ||||||
|  | * Chrome OS CCD (Closed Case Debugging) | ||||||
|  | 
 | ||||||
|  | and devices implementing the generic CDC/ACM protocol like | ||||||
|  | * Qinheng CH9102 | ||||||
|  | * Microchip MCP2221 | ||||||
| * Arduino using ATmega32U4 | * Arduino using ATmega32U4 | ||||||
| * Digispark using V-USB software USB | * Digispark using V-USB software USB | ||||||
| * BBC micro:bit using ARM mbed DAPLink firmware |  | ||||||
| * ... | * ... | ||||||
| 
 | 
 | ||||||
| ## Help & Discussion | ## Help & Discussion | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ buildscript { | |||||||
|         google() |         google() | ||||||
|     } |     } | ||||||
|     dependencies { |     dependencies { | ||||||
|         classpath 'com.android.tools.build:gradle:7.0.4' |         classpath 'com.android.tools.build:gradle:8.9.1' | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										5
									
								
								codecov.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								codecov.yml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | |||||||
|  | codecov: | ||||||
|  |   max_report_age: off | ||||||
|  |   require_ci_to_pass: no | ||||||
|  |   notify: | ||||||
|  |     wait_for_ci: no | ||||||
| @ -1,2 +1,3 @@ | |||||||
| android.enableJetifier=true | android.enableJetifier=true | ||||||
| android.useAndroidX=true | android.useAndroidX=true | ||||||
|  | android.defaults.buildfeatures.buildconfig=true | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							| @ -3,4 +3,4 @@ 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-7.0.2-all.zip | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| jdk: | jdk: | ||||||
|   - openjdk11 |   - openjdk17 | ||||||
| install: | install: | ||||||
|   - ./gradlew :usbSerialForAndroid:publishToMavenLocal |   - ./gradlew :usbSerialForAndroid:publishToMavenLocal | ||||||
|  | |||||||
							
								
								
									
										6
									
								
								test/pi_pico/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								test/pi_pico/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | |||||||
|  | 
 | ||||||
|  | # `tinyusb_dev_cdc_dual_ports.uf2` | ||||||
|  | 
 | ||||||
|  | compiled from `C:/Program Files/Raspberry Pi/Pico SDK v1.5.1/pico-sdk/lib/tinyusb/examples/device/cdc_dual_ports` | ||||||
|  | to `C:/Users/` _user_`/Documents/Pico-v1.5.1/pico-examples/build/usb/device/tinyusb_device_examples/cdc_dual_ports/tinyusb_dev_cdc_dual_ports.uf2` | ||||||
|  | 
 | ||||||
							
								
								
									
										
											BIN
										
									
								
								test/pi_pico/tinyusb_dev_cdc_dual_ports.uf2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/pi_pico/tinyusb_dev_cdc_dual_ports.uf2
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @ -3,7 +3,7 @@ plugins { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| android { | android { | ||||||
|     compileSdkVersion 32 |     compileSdkVersion 35 | ||||||
| 
 | 
 | ||||||
|     compileOptions { |     compileOptions { | ||||||
|         sourceCompatibility JavaVersion.VERSION_1_8 |         sourceCompatibility JavaVersion.VERSION_1_8 | ||||||
| @ -12,7 +12,7 @@ android { | |||||||
| 
 | 
 | ||||||
|     defaultConfig { |     defaultConfig { | ||||||
|         minSdkVersion 17 |         minSdkVersion 17 | ||||||
|         targetSdkVersion 32 |         targetSdkVersion 35 | ||||||
|         vectorDrawables.useSupportLibrary = true |         vectorDrawables.useSupportLibrary = true | ||||||
| 
 | 
 | ||||||
|         missingDimensionStrategy 'device', 'anyDevice' |         missingDimensionStrategy 'device', 'anyDevice' | ||||||
| @ -23,10 +23,11 @@ android { | |||||||
|             minifyEnabled true |             minifyEnabled true | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     namespace 'com.hoho.android.usbserial.examples' | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| dependencies { | dependencies { | ||||||
|     implementation project(':usbSerialForAndroid') |     implementation project(':usbSerialForAndroid') | ||||||
|     implementation 'androidx.appcompat:appcompat:1.4.1' |     implementation 'androidx.appcompat:appcompat:1.6.1' // later versions have minsdk 21 | ||||||
|     implementation 'com.google.android.material:material:1.5.0' |     implementation 'com.google.android.material:material:1.11.0' // later versions have minsdk 19 | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,7 +1,6 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|     xmlns:tools="http://schemas.android.com/tools" |     xmlns:tools="http://schemas.android.com/tools"> | ||||||
|     package="com.hoho.android.usbserial.examples"> |  | ||||||
| 
 | 
 | ||||||
|     <!-- mipmap/ic_launcher created with Android Studio -> New -> Image Asset using @color/colorPrimary and USB clip art --> |     <!-- mipmap/ic_launcher created with Android Studio -> New -> Image Asset using @color/colorPrimary and USB clip art --> | ||||||
|     <application |     <application | ||||||
| @ -15,7 +14,6 @@ | |||||||
|              when the settings activity is currently shown --> |              when the settings activity is currently shown --> | ||||||
|         <activity |         <activity | ||||||
|             android:name=".MainActivity" |             android:name=".MainActivity" | ||||||
|             android:label="@string/app_name" |  | ||||||
|             android:launchMode="singleTask" |             android:launchMode="singleTask" | ||||||
|             android:windowSoftInputMode="stateHidden|adjustResize" |             android:windowSoftInputMode="stateHidden|adjustResize" | ||||||
|             android:exported="true"> |             android:exported="true"> | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| package com.hoho.android.usbserial.examples; | package com.hoho.android.usbserial.examples; | ||||||
| 
 | 
 | ||||||
| import com.hoho.android.usbserial.driver.CdcAcmSerialDriver; | import com.hoho.android.usbserial.driver.FtdiSerialDriver; | ||||||
| import com.hoho.android.usbserial.driver.ProbeTable; | import com.hoho.android.usbserial.driver.ProbeTable; | ||||||
| import com.hoho.android.usbserial.driver.UsbSerialProber; | import com.hoho.android.usbserial.driver.UsbSerialProber; | ||||||
| 
 | 
 | ||||||
| @ -14,7 +14,8 @@ class CustomProber { | |||||||
| 
 | 
 | ||||||
|     static UsbSerialProber getCustomProber() { |     static UsbSerialProber getCustomProber() { | ||||||
|         ProbeTable customTable = new ProbeTable(); |         ProbeTable customTable = new ProbeTable(); | ||||||
|         customTable.addProduct(0x16d0, 0x087e, CdcAcmSerialDriver.class); // e.g. Digispark CDC |         customTable.addProduct(0x1234, 0x0001, FtdiSerialDriver.class); // e.g. device with custom VID+PID | ||||||
|  |         customTable.addProduct(0x1234, 0x0002, FtdiSerialDriver.class); // e.g. device with custom VID+PID | ||||||
|         return new UsbSerialProber(customTable); |         return new UsbSerialProber(customTable); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -28,6 +28,7 @@ import android.widget.ToggleButton; | |||||||
| 
 | 
 | ||||||
| import androidx.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import androidx.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
|  | import androidx.core.content.ContextCompat; | ||||||
| import androidx.fragment.app.Fragment; | import androidx.fragment.app.Fragment; | ||||||
| 
 | 
 | ||||||
| import com.hoho.android.usbserial.driver.UsbSerialDriver; | import com.hoho.android.usbserial.driver.UsbSerialDriver; | ||||||
| @ -89,12 +90,22 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag | |||||||
|         withIoManager = getArguments().getBoolean("withIoManager"); |         withIoManager = getArguments().getBoolean("withIoManager"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Override | ||||||
|  |     public void onStart() { | ||||||
|  |         super.onStart(); | ||||||
|  |         ContextCompat.registerReceiver(getActivity(), broadcastReceiver, new IntentFilter(INTENT_ACTION_GRANT_USB), ContextCompat.RECEIVER_NOT_EXPORTED); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void onStop() { | ||||||
|  |         getActivity().unregisterReceiver(broadcastReceiver); | ||||||
|  |         super.onStop(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void onResume() { |     public void onResume() { | ||||||
|         super.onResume(); |         super.onResume(); | ||||||
|         getActivity().registerReceiver(broadcastReceiver, new IntentFilter(INTENT_ACTION_GRANT_USB)); |         if(!connected && (usbPermission == UsbPermission.Unknown || usbPermission == UsbPermission.Granted)) | ||||||
| 
 |  | ||||||
|         if(usbPermission == UsbPermission.Unknown || usbPermission == UsbPermission.Granted) |  | ||||||
|             mainLooper.post(this::connect); |             mainLooper.post(this::connect); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -104,7 +115,6 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag | |||||||
|             status("disconnected"); |             status("disconnected"); | ||||||
|             disconnect(); |             disconnect(); | ||||||
|         } |         } | ||||||
|         getActivity().unregisterReceiver(broadcastReceiver); |  | ||||||
|         super.onPause(); |         super.onPause(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -204,7 +214,7 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag | |||||||
|             status("connection failed: no driver for device"); |             status("connection failed: no driver for device"); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         if(driver.getPorts().size() < portNum) { |         if(portNum >= driver.getPorts().size()) { | ||||||
|             status("connection failed: not enough ports at device"); |             status("connection failed: not enough ports at device"); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| @ -212,8 +222,10 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag | |||||||
|         UsbDeviceConnection usbConnection = usbManager.openDevice(driver.getDevice()); |         UsbDeviceConnection usbConnection = usbManager.openDevice(driver.getDevice()); | ||||||
|         if(usbConnection == null && usbPermission == UsbPermission.Unknown && !usbManager.hasPermission(driver.getDevice())) { |         if(usbConnection == null && usbPermission == UsbPermission.Unknown && !usbManager.hasPermission(driver.getDevice())) { | ||||||
|             usbPermission = UsbPermission.Requested; |             usbPermission = UsbPermission.Requested; | ||||||
|             int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0; |             int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_MUTABLE : 0; | ||||||
|             PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(getActivity(), 0, new Intent(INTENT_ACTION_GRANT_USB), flags); |             Intent intent = new Intent(INTENT_ACTION_GRANT_USB); | ||||||
|  |             intent.setPackage(getActivity().getPackageName()); | ||||||
|  |             PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(getActivity(), 0, intent, flags); | ||||||
|             usbManager.requestPermission(driver.getDevice(), usbPermissionIntent); |             usbManager.requestPermission(driver.getDevice(), usbPermissionIntent); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| @ -227,7 +239,11 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag | |||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             usbSerialPort.open(usbConnection); |             usbSerialPort.open(usbConnection); | ||||||
|             usbSerialPort.setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE); |             try{ | ||||||
|  |                 usbSerialPort.setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE); | ||||||
|  |             }catch (UnsupportedOperationException e){ | ||||||
|  |                 status("unsupport setparameters"); | ||||||
|  |             } | ||||||
|             if(withIoManager) { |             if(withIoManager) { | ||||||
|                 usbIoManager = new SerialInputOutputManager(usbSerialPort, this); |                 usbIoManager = new SerialInputOutputManager(usbSerialPort, this); | ||||||
|                 usbIoManager.start(); |                 usbIoManager.start(); | ||||||
| @ -351,7 +367,7 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag | |||||||
|                 cdBtn.setChecked(controlLines.contains(UsbSerialPort.ControlLine.CD)); |                 cdBtn.setChecked(controlLines.contains(UsbSerialPort.ControlLine.CD)); | ||||||
|                 riBtn.setChecked(controlLines.contains(UsbSerialPort.ControlLine.RI)); |                 riBtn.setChecked(controlLines.contains(UsbSerialPort.ControlLine.RI)); | ||||||
|                 mainLooper.postDelayed(runnable, refreshInterval); |                 mainLooper.postDelayed(runnable, refreshInterval); | ||||||
|             } catch (IOException e) { |             } catch (Exception e) { | ||||||
|                 status("getControlLines() failed: " + e.getMessage() + " -> stopped control line refresh"); |                 status("getControlLines() failed: " + e.getMessage() + " -> stopped control line refresh"); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @ -368,8 +384,15 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag | |||||||
|                 if (!controlLines.contains(UsbSerialPort.ControlLine.CD))   cdBtn.setVisibility(View.INVISIBLE); |                 if (!controlLines.contains(UsbSerialPort.ControlLine.CD))   cdBtn.setVisibility(View.INVISIBLE); | ||||||
|                 if (!controlLines.contains(UsbSerialPort.ControlLine.RI))   riBtn.setVisibility(View.INVISIBLE); |                 if (!controlLines.contains(UsbSerialPort.ControlLine.RI))   riBtn.setVisibility(View.INVISIBLE); | ||||||
|                 run(); |                 run(); | ||||||
|             } catch (IOException e) { |             } catch (Exception e) { | ||||||
|                 Toast.makeText(getActivity(), "getSupportedControlLines() failed: " + e.getMessage(), Toast.LENGTH_SHORT).show(); |                 Toast.makeText(getActivity(), "getSupportedControlLines() failed: " + e.getMessage(), Toast.LENGTH_SHORT).show(); | ||||||
|  |                 rtsBtn.setVisibility(View.INVISIBLE); | ||||||
|  |                 ctsBtn.setVisibility(View.INVISIBLE); | ||||||
|  |                 dtrBtn.setVisibility(View.INVISIBLE); | ||||||
|  |                 dsrBtn.setVisibility(View.INVISIBLE); | ||||||
|  |                 cdBtn.setVisibility(View.INVISIBLE); | ||||||
|  |                 cdBtn.setVisibility(View.INVISIBLE); | ||||||
|  |                 riBtn.setVisibility(View.INVISIBLE); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -4,16 +4,16 @@ plugins { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| android { | android { | ||||||
|     compileSdkVersion 32 |     compileSdkVersion 35 | ||||||
| 
 | 
 | ||||||
|     defaultConfig { |     defaultConfig { | ||||||
|         minSdkVersion 17 |         minSdkVersion 17 | ||||||
|         targetSdkVersion 32 |         targetSdkVersion 35 | ||||||
|         consumerProguardFiles 'proguard-rules.pro' |         consumerProguardFiles 'proguard-rules.pro' | ||||||
|          |          | ||||||
|         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" |         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | ||||||
|         testInstrumentationRunnerArguments = [                    // Raspi   Windows   LinuxVM   ... |         testInstrumentationRunnerArguments = [                    // Raspi   Windows   LinuxVM   ... | ||||||
|                 'rfc2217_server_host': '192.168.0.186', |                 'rfc2217_server_host': '192.168.0.78', | ||||||
|                 'rfc2217_server_nonstandard_baudrates': 'true',   // true    false     false |                 'rfc2217_server_nonstandard_baudrates': 'true',   // true    false     false | ||||||
|         ] |         ] | ||||||
|     } |     } | ||||||
| @ -21,16 +21,24 @@ android { | |||||||
|         sourceCompatibility JavaVersion.VERSION_1_8 |         sourceCompatibility JavaVersion.VERSION_1_8 | ||||||
|         targetCompatibility JavaVersion.VERSION_1_8 |         targetCompatibility JavaVersion.VERSION_1_8 | ||||||
|     } |     } | ||||||
|  |     namespace 'com.hoho.android.usbserial' | ||||||
|  |     publishing { | ||||||
|  |         // if coverage is enabled, change 'release' to 'anyDeviceRelease' or comment out publishing rule | ||||||
|  |         singleVariant('release') { | ||||||
|  |             withSourcesJar() | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| dependencies { | dependencies { | ||||||
|     implementation "androidx.annotation:annotation:1.3.0" |     implementation "androidx.annotation:annotation:1.9.1" | ||||||
|     implementation 'androidx.test:core:1.4.0' |  | ||||||
|     testImplementation 'junit:junit:4.13.2' |     testImplementation 'junit:junit:4.13.2' | ||||||
|     testImplementation 'org.mockito:mockito-core:1.10.19' |     testImplementation 'org.mockito:mockito-core:5.15.2' | ||||||
|     androidTestImplementation 'androidx.test:runner:1.4.0' |     androidTestImplementation 'androidx.appcompat:appcompat:1.6.1' // later versions have minsdk 21 | ||||||
|     androidTestImplementation 'commons-net:commons-net:3.8.0' |     androidTestImplementation 'androidx.test:core:1.5.0' // later versions have minsdk 19 | ||||||
|     androidTestImplementation 'org.apache.commons:commons-lang3:3.11' |     androidTestImplementation 'androidx.test:runner:1.5.2' // later versions have minsdk 19 | ||||||
|  |     androidTestImplementation 'commons-net:commons-net:3.9.0' // later versions fail on old Android devices with missing java.time.Duration class | ||||||
|  |     androidTestImplementation 'org.apache.commons:commons-lang3:3.14.0' | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // gradle task: publishToMavenLocal | // gradle task: publishToMavenLocal | ||||||
| @ -38,21 +46,15 @@ project.afterEvaluate { | |||||||
|     publishing { |     publishing { | ||||||
|         publications { |         publications { | ||||||
|             release(MavenPublication) { |             release(MavenPublication) { | ||||||
|                 from components.release // change to anyDeviceRelease if coverage is enabled |                 from components.release | ||||||
|                 artifact androidSourcesJar |  | ||||||
| 
 | 
 | ||||||
|                 // values used for local maven repo, jitpack uses github release: |                 // values used for local maven repo, jitpack uses github release: | ||||||
|                 groupId 'com.github.mik3y' |                 groupId 'com.github.mik3y' | ||||||
|                 artifactId 'usb-serial-for-android' |                 artifactId 'usb-serial-for-android' | ||||||
|                 version 'v3.5.0beta' |                 version '3.8.0beta' | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| task androidSourcesJar(type: Jar) { |  | ||||||
|     classifier 'sources' |  | ||||||
|     from android.sourceSets.main.java.srcDirs |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| //apply from: 'coverage.gradle' | //apply from: 'coverage.gradle' | ||||||
|  | |||||||
| @ -2,13 +2,13 @@ | |||||||
| apply plugin: 'jacoco' | apply plugin: 'jacoco' | ||||||
| 
 | 
 | ||||||
| android { | android { | ||||||
|     flavorDimensions 'device' |     flavorDimensions += 'device' | ||||||
|     productFlavors { |     productFlavors { | ||||||
|         anyDevice { |         anyDevice { | ||||||
|             // Used as fallback in usbSerialExample/build.gradle -> missingDimensionStrategy, but not for coverage report |             // Used as fallback in usbSerialExample/build.gradle -> missingDimensionStrategy, but not for coverage report | ||||||
|             dimension 'device' |             dimension 'device' | ||||||
|         } |         } | ||||||
|         arduino { |         mcp2221 { | ||||||
|             dimension 'device' |             dimension 'device' | ||||||
|             testInstrumentationRunnerArguments = ['test_device_driver': 'CdcAcm'] |             testInstrumentationRunnerArguments = ['test_device_driver': 'CdcAcm'] | ||||||
|         } |         } | ||||||
| @ -18,7 +18,7 @@ android { | |||||||
|         } |         } | ||||||
|         cp2102 { // and cp2105 first port |         cp2102 { // and cp2105 first port | ||||||
|             dimension 'device' |             dimension 'device' | ||||||
|             testInstrumentationRunnerArguments = ['test_device_driver': 'Cp21xx'] |             testInstrumentationRunnerArguments = ['test_device_driver': 'Cp21xx', 'test_device_port': '0'] | ||||||
|         } |         } | ||||||
|         cp2105 { // second port |         cp2105 { // second port | ||||||
|             dimension 'device' |             dimension 'device' | ||||||
| @ -26,7 +26,7 @@ android { | |||||||
|         } |         } | ||||||
|         ft232 { // and ft2232 first port |         ft232 { // and ft2232 first port | ||||||
|             dimension 'device' |             dimension 'device' | ||||||
|             testInstrumentationRunnerArguments = ['test_device_driver': 'Ftdi'] |             testInstrumentationRunnerArguments = ['test_device_driver': 'Ftdi', 'test_device_port': '0'] | ||||||
|         } |         } | ||||||
|         ft2232 { // second port |         ft2232 { // second port | ||||||
|             dimension 'device' |             dimension 'device' | ||||||
| @ -48,7 +48,8 @@ android { | |||||||
| 
 | 
 | ||||||
|     buildTypes { |     buildTypes { | ||||||
|         debug { |         debug { | ||||||
|             testCoverageEnabled true // disable for testAnyDeviceDebugUnitTest |             enableUnitTestCoverage true | ||||||
|  |             enableAndroidTestCoverage true | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -60,20 +61,18 @@ project.gradle.taskGraph.whenReady { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| task jacocoTestReport(type: JacocoReport , | task jacocoTestReport(type: JacocoReport, dependsOn: ['compileAnyDeviceDebugSources' | ||||||
|         dependsOn: ['compileAnyDeviceDebugSources' /*'testAnyDeviceDebugUnitTest' , 'create<device>DebugCoverageReport'*/]) { |                                                   /*, 'testAnyDeviceDebugUnitTest' */ | ||||||
|     reports { |                                                   /*, 'create<device>DebugCoverageReport' */]) { | ||||||
|         xml.enabled = true |  | ||||||
|         html.enabled = true |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*'] |     def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*'] | ||||||
|     def debugTree = fileTree(dir: "$project.buildDir/intermediates/javac/anyDeviceDebug", excludes: fileFilter) |     def debugTree = fileTree(dir: "$project.buildDir/intermediates/javac/anyDeviceDebug", excludes: fileFilter) | ||||||
|     def mainSrc = "$project.projectDir/src/main/java" |     def mainSrc = "$project.projectDir/src/main/java" | ||||||
| 
 | 
 | ||||||
|  |     reports.xml.required = true | ||||||
|     sourceDirectories.from files([mainSrc]) |     sourceDirectories.from files([mainSrc]) | ||||||
|     classDirectories.from files([debugTree]) |     classDirectories.from files([debugTree]) | ||||||
|     executionData.from fileTree(dir: project.buildDir, includes: [ |     executionData.from fileTree(dir: project.buildDir, includes: [ | ||||||
|             'jacoco/testAnyDeviceDebugUnitTest.exec', 'outputs/code_coverage/*AndroidTest/connected/*.ec' |             'outputs/unit_test_code_coverage/anyDeviceDebugUnitTest/testAnyDeviceDebugUnitTest.exec', | ||||||
|  |             'outputs/code_coverage/*DebugAndroidTest/connected/*/coverage.ec' | ||||||
|     ]) |     ]) | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|       package="com.hoho.android.usbserial" |  | ||||||
|       android:versionCode="1" |       android:versionCode="1" | ||||||
|       android:versionName="1.0"> |       android:versionName="1.0"> | ||||||
|     <uses-permission android:name="android.permission.INTERNET"/> |     <uses-permission android:name="android.permission.INTERNET"/> | ||||||
|  | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -18,6 +18,7 @@ import com.hoho.android.usbserial.driver.CommonUsbSerialPort; | |||||||
| import com.hoho.android.usbserial.driver.Cp21xxSerialDriver; | import com.hoho.android.usbserial.driver.Cp21xxSerialDriver; | ||||||
| import com.hoho.android.usbserial.driver.FtdiSerialDriver; | import com.hoho.android.usbserial.driver.FtdiSerialDriver; | ||||||
| import com.hoho.android.usbserial.driver.ProlificSerialDriver; | import com.hoho.android.usbserial.driver.ProlificSerialDriver; | ||||||
|  | import com.hoho.android.usbserial.driver.ProlificSerialPortWrapper; | ||||||
| import com.hoho.android.usbserial.driver.UsbId; | import com.hoho.android.usbserial.driver.UsbId; | ||||||
| import com.hoho.android.usbserial.driver.UsbSerialDriver; | import com.hoho.android.usbserial.driver.UsbSerialDriver; | ||||||
| import com.hoho.android.usbserial.driver.UsbSerialPort; | import com.hoho.android.usbserial.driver.UsbSerialPort; | ||||||
| @ -32,6 +33,8 @@ import java.util.concurrent.Callable; | |||||||
| import static org.junit.Assert.assertEquals; | import static org.junit.Assert.assertEquals; | ||||||
| import static org.junit.Assert.assertTrue; | import static org.junit.Assert.assertTrue; | ||||||
| 
 | 
 | ||||||
|  | import androidx.core.content.ContextCompat; | ||||||
|  | 
 | ||||||
| public class UsbWrapper implements SerialInputOutputManager.Listener { | public class UsbWrapper implements SerialInputOutputManager.Listener { | ||||||
| 
 | 
 | ||||||
|     public final static int     USB_READ_WAIT = 500; |     public final static int     USB_READ_WAIT = 500; | ||||||
| @ -56,11 +59,13 @@ public class UsbWrapper implements SerialInputOutputManager.Listener { | |||||||
| 
 | 
 | ||||||
|     // device properties |     // device properties | ||||||
|     public boolean isCp21xxRestrictedPort; // second port of Cp2105 has limited dataBits, stopBits, parity |     public boolean isCp21xxRestrictedPort; // second port of Cp2105 has limited dataBits, stopBits, parity | ||||||
|  |     public boolean outputLinesSupported; | ||||||
|     public boolean inputLinesSupported; |     public boolean inputLinesSupported; | ||||||
|     public boolean inputLinesConnected; |     public boolean inputLinesConnected; | ||||||
|     public boolean inputLinesOnlyRtsCts; |     public boolean inputLinesOnlyRtsCts; | ||||||
|     public int writePacketSize = -1; |     public int writePacketSize = -1; | ||||||
|     public int writeBufferSize = -1; |     public int writeBufferSize = -1; | ||||||
|  |     public int readBufferSize = -1; | ||||||
| 
 | 
 | ||||||
|     public UsbWrapper(Context context, UsbSerialDriver serialDriver, int devicePort) { |     public UsbWrapper(Context context, UsbSerialDriver serialDriver, int devicePort) { | ||||||
|         this.context = context; |         this.context = context; | ||||||
| @ -84,10 +89,12 @@ public class UsbWrapper implements SerialInputOutputManager.Listener { | |||||||
|                     granted[0] = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false); |                     granted[0] = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false); | ||||||
|                 } |                 } | ||||||
|             }; |             }; | ||||||
|             int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0; |             int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_MUTABLE : 0; | ||||||
|             PendingIntent permissionIntent = PendingIntent.getBroadcast(context, 0, new Intent("com.android.example.USB_PERMISSION"), flags); |             Intent intent = new Intent("com.android.example.USB_PERMISSION"); | ||||||
|  |             intent.setPackage(context.getPackageName()); | ||||||
|  |             PendingIntent permissionIntent = PendingIntent.getBroadcast(context, 0, intent, flags); | ||||||
|             IntentFilter filter = new IntentFilter("com.android.example.USB_PERMISSION"); |             IntentFilter filter = new IntentFilter("com.android.example.USB_PERMISSION"); | ||||||
|             context.registerReceiver(usbReceiver, filter); |             ContextCompat.registerReceiver(context, usbReceiver, filter, Context.RECEIVER_NOT_EXPORTED); | ||||||
|             usbManager.requestPermission(serialDriver.getDevice(), permissionIntent); |             usbManager.requestPermission(serialDriver.getDevice(), permissionIntent); | ||||||
|             for(int i=0; i<5000; i++) { |             for(int i=0; i<5000; i++) { | ||||||
|                 if(granted[0] != null) break; |                 if(granted[0] != null) break; | ||||||
| @ -99,9 +106,10 @@ public class UsbWrapper implements SerialInputOutputManager.Listener { | |||||||
| 
 | 
 | ||||||
|         // extract some device properties: |         // extract some device properties: | ||||||
|         isCp21xxRestrictedPort = serialDriver instanceof Cp21xxSerialDriver && serialDriver.getPorts().size()==2 && serialPort.getPortNumber() == 1; |         isCp21xxRestrictedPort = serialDriver instanceof Cp21xxSerialDriver && serialDriver.getPorts().size()==2 && serialPort.getPortNumber() == 1; | ||||||
|         // output lines are supported by all drivers |         // output lines are supported by all common drivers | ||||||
|         // input lines are supported by all drivers except CDC |         // input lines are supported by all common drivers except CDC | ||||||
|         if (serialDriver instanceof FtdiSerialDriver) { |         if (serialDriver instanceof FtdiSerialDriver) { | ||||||
|  |             outputLinesSupported = true; | ||||||
|             inputLinesSupported = true; |             inputLinesSupported = true; | ||||||
|             if(serialDriver.getDevice().getProductId() == UsbId.FTDI_FT2232H) |             if(serialDriver.getDevice().getProductId() == UsbId.FTDI_FT2232H) | ||||||
|                 inputLinesConnected = true; // I only have 74LS138 connected at FT2232, not at FT232 |                 inputLinesConnected = true; // I only have 74LS138 connected at FT2232, not at FT232 | ||||||
| @ -110,16 +118,21 @@ public class UsbWrapper implements SerialInputOutputManager.Listener { | |||||||
|                 inputLinesOnlyRtsCts = true; // I only test with FT230X that has only these 2 control lines. DTR is silently ignored |                 inputLinesOnlyRtsCts = true; // I only test with FT230X that has only these 2 control lines. DTR is silently ignored | ||||||
|             } |             } | ||||||
|         } else if (serialDriver instanceof Cp21xxSerialDriver) { |         } else if (serialDriver instanceof Cp21xxSerialDriver) { | ||||||
|  |             outputLinesSupported = true; | ||||||
|             inputLinesSupported = true; |             inputLinesSupported = true; | ||||||
|             if(serialDriver.getPorts().size() == 1) |             if(serialDriver.getPorts().size() == 1) | ||||||
|                 inputLinesConnected = true; // I only have 74LS138 connected at CP2102, not at CP2105 |                 inputLinesConnected = true; // I only have 74LS138 connected at CP2102, not at CP2105 | ||||||
|         } else if (serialDriver instanceof ProlificSerialDriver) { |         } else if (serialDriver instanceof ProlificSerialDriver) { | ||||||
|  |             outputLinesSupported = true; | ||||||
|             inputLinesSupported = true; |             inputLinesSupported = true; | ||||||
|             inputLinesConnected = true; |             inputLinesConnected = true; | ||||||
|         } else if (serialDriver instanceof Ch34xSerialDriver) { |         } else if (serialDriver instanceof Ch34xSerialDriver) { | ||||||
|  |             outputLinesSupported = true; | ||||||
|             inputLinesSupported = true; |             inputLinesSupported = true; | ||||||
|             if(serialDriver.getDevice().getProductId() == UsbId.QINHENG_CH340) |             if(serialDriver.getDevice().getProductId() == UsbId.QINHENG_CH340) | ||||||
|                 inputLinesConnected = true;  // I only have 74LS138 connected at CH340, not connected at CH341A |                 inputLinesConnected = true;  // I only have 74LS138 connected at CH340, not connected at CH341A | ||||||
|  |         } else if (serialDriver instanceof CdcAcmSerialDriver) { | ||||||
|  |             outputLinesSupported = true; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (serialDriver instanceof Cp21xxSerialDriver) { |         if (serialDriver instanceof Cp21xxSerialDriver) { | ||||||
| @ -136,10 +149,18 @@ public class UsbWrapper implements SerialInputOutputManager.Listener { | |||||||
|                 case 2: writePacketSize = 512; writeBufferSize = 4096; break; |                 case 2: writePacketSize = 512; writeBufferSize = 4096; break; | ||||||
|                 case 4: writePacketSize = 512; writeBufferSize = 2048; break; |                 case 4: writePacketSize = 512; writeBufferSize = 2048; break; | ||||||
|             } |             } | ||||||
|  |             if(serialDriver.getDevice().getProductId() == UsbId.FTDI_FT231X) | ||||||
|  |                 writeBufferSize = 512; | ||||||
|         } else if (serialDriver instanceof CdcAcmSerialDriver) { |         } else if (serialDriver instanceof CdcAcmSerialDriver) { | ||||||
|             writePacketSize = 64; writeBufferSize = 128; |             writePacketSize = 16; writeBufferSize = 32; // MCP2221 values, other devices might be different | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         readBufferSize = writeBufferSize; | ||||||
|  |         if (serialDriver instanceof Cp21xxSerialDriver && serialDriver.getPorts().size() == 2) { | ||||||
|  |             readBufferSize = 256; | ||||||
|  |         } else if (serialDriver instanceof FtdiSerialDriver && serialDriver.getPorts().size() == 1 && serialDriver.getDevice().getProductId() != UsbId.FTDI_FT231X) { | ||||||
|  |             readBufferSize = 256; | ||||||
|  |         } // PL2303 HXN checked in open() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void tearDown() { |     public void tearDown() { | ||||||
| @ -168,6 +189,8 @@ public class UsbWrapper implements SerialInputOutputManager.Listener { | |||||||
|                 if(!flags.contains(OpenCloseFlags.NO_CONTROL_LINE_INIT)) { |                 if(!flags.contains(OpenCloseFlags.NO_CONTROL_LINE_INIT)) { | ||||||
|                     serialPort.setDTR(false); |                     serialPort.setDTR(false); | ||||||
|                     serialPort.setRTS(false); |                     serialPort.setRTS(false); | ||||||
|  |                     if (serialPort.getFlowControl() != UsbSerialPort.FlowControl.NONE) | ||||||
|  |                         serialPort.setFlowControl(UsbSerialPort.FlowControl.NONE); | ||||||
|                 } |                 } | ||||||
|             } catch (Exception ignored) { |             } catch (Exception ignored) { | ||||||
|             } |             } | ||||||
| @ -217,6 +240,10 @@ public class UsbWrapper implements SerialInputOutputManager.Listener { | |||||||
|             readBuffer.clear(); |             readBuffer.clear(); | ||||||
|         } |         } | ||||||
|         readError = null; |         readError = null; | ||||||
|  | 
 | ||||||
|  |         if (serialDriver instanceof ProlificSerialDriver && ProlificSerialPortWrapper.isDeviceTypeHxn(serialPort)) { | ||||||
|  |             readBufferSize = 768; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void waitForIoManagerStarted() throws IOException { |     public void waitForIoManagerStarted() throws IOException { | ||||||
| @ -231,12 +258,15 @@ public class UsbWrapper implements SerialInputOutputManager.Listener { | |||||||
|         throw new IOException("IoManager not started"); |         throw new IOException("IoManager not started"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public boolean hasIoManagerThread() { |     public boolean hasIoManagerThreads() { | ||||||
|  |         int c = 0; | ||||||
|         for (Thread thread : Thread.getAllStackTraces().keySet()) { |         for (Thread thread : Thread.getAllStackTraces().keySet()) { | ||||||
|             if (thread.getName().equals(SerialInputOutputManager.class.getSimpleName())) |             if (thread.getName().equals(SerialInputOutputManager.class.getSimpleName() + "_read")) | ||||||
|                 return true; |                 c += 1; | ||||||
|  |             if (thread.getName().equals(SerialInputOutputManager.class.getSimpleName() + "_write")) | ||||||
|  |                 c += 1; | ||||||
|         } |         } | ||||||
|         return false; |         return c == 2; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // wait full time |     // wait full time | ||||||
|  | |||||||
| @ -1,4 +1,3 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | <manifest xmlns:android="http://schemas.android.com/apk/res/android"> | ||||||
|       package="com.hoho.android.usbserial"> |  | ||||||
| </manifest> | </manifest> | ||||||
|  | |||||||
| @ -8,11 +8,13 @@ package com.hoho.android.usbserial.driver; | |||||||
| 
 | 
 | ||||||
| import android.hardware.usb.UsbConstants; | import android.hardware.usb.UsbConstants; | ||||||
| import android.hardware.usb.UsbDevice; | import android.hardware.usb.UsbDevice; | ||||||
| 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.util.Log; | import android.util.Log; | ||||||
| 
 | 
 | ||||||
|  | import com.hoho.android.usbserial.util.HexDump; | ||||||
|  | import com.hoho.android.usbserial.util.UsbUtils; | ||||||
|  | 
 | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.EnumSet; | import java.util.EnumSet; | ||||||
| @ -30,6 +32,8 @@ import java.util.Map; | |||||||
|  */ |  */ | ||||||
| public class CdcAcmSerialDriver implements UsbSerialDriver { | public class CdcAcmSerialDriver implements UsbSerialDriver { | ||||||
| 
 | 
 | ||||||
|  |     public static final int USB_SUBCLASS_ACM = 2; | ||||||
|  | 
 | ||||||
|     private final String TAG = CdcAcmSerialDriver.class.getSimpleName(); |     private final String TAG = CdcAcmSerialDriver.class.getSimpleName(); | ||||||
| 
 | 
 | ||||||
|     private final UsbDevice mDevice; |     private final UsbDevice mDevice; | ||||||
| @ -38,23 +42,33 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { | |||||||
|     public CdcAcmSerialDriver(UsbDevice device) { |     public CdcAcmSerialDriver(UsbDevice device) { | ||||||
|         mDevice = device; |         mDevice = device; | ||||||
|         mPorts = new ArrayList<>(); |         mPorts = new ArrayList<>(); | ||||||
| 
 |         int ports = countPorts(device); | ||||||
|         int controlInterfaceCount = 0; |         for (int port = 0; port < ports; port++) { | ||||||
|         int dataInterfaceCount = 0; |  | ||||||
|         for( int i = 0; i < device.getInterfaceCount(); i++) { |  | ||||||
|             if(device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_COMM) |  | ||||||
|                 controlInterfaceCount++; |  | ||||||
|             if(device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) |  | ||||||
|                 dataInterfaceCount++; |  | ||||||
|         } |  | ||||||
|         for( int port = 0; port < Math.min(controlInterfaceCount, dataInterfaceCount); port++) { |  | ||||||
|             mPorts.add(new CdcAcmSerialPort(mDevice, port)); |             mPorts.add(new CdcAcmSerialPort(mDevice, port)); | ||||||
|         } |         } | ||||||
|         if(mPorts.size() == 0) { |         if (mPorts.size() == 0) { | ||||||
|             mPorts.add(new CdcAcmSerialPort(mDevice, -1)); |             mPorts.add(new CdcAcmSerialPort(mDevice, -1)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @SuppressWarnings({"unused"}) | ||||||
|  |     public static boolean probe(UsbDevice device) { | ||||||
|  |         return countPorts(device) > 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static int countPorts(UsbDevice device) { | ||||||
|  |         int controlInterfaceCount = 0; | ||||||
|  |         int dataInterfaceCount = 0; | ||||||
|  |         for (int i = 0; i < device.getInterfaceCount(); i++) { | ||||||
|  |             if (device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_COMM && | ||||||
|  |                     device.getInterface(i).getInterfaceSubclass() == USB_SUBCLASS_ACM) | ||||||
|  |                 controlInterfaceCount++; | ||||||
|  |             if (device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) | ||||||
|  |                 dataInterfaceCount++; | ||||||
|  |         } | ||||||
|  |         return Math.min(controlInterfaceCount, dataInterfaceCount); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public UsbDevice getDevice() { |     public UsbDevice getDevice() { | ||||||
|         return mDevice; |         return mDevice; | ||||||
| @ -95,7 +109,11 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         @Override |         @Override | ||||||
|         protected void openInt(UsbDeviceConnection connection) throws IOException { |         protected void openInt() throws IOException { | ||||||
|  |             Log.d(TAG, "interfaces:"); | ||||||
|  |             for (int i = 0; i < mDevice.getInterfaceCount(); i++) { | ||||||
|  |                 Log.d(TAG, mDevice.getInterface(i).toString()); | ||||||
|  |             } | ||||||
|             if (mPortNumber == -1) { |             if (mPortNumber == -1) { | ||||||
|                 Log.d(TAG,"device might be castrated ACM device, trying single interface logic"); |                 Log.d(TAG,"device might be castrated ACM device, trying single interface logic"); | ||||||
|                 openSingleInterface(); |                 openSingleInterface(); | ||||||
| @ -131,38 +149,57 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         private void openInterface() throws IOException { |         private void openInterface() throws IOException { | ||||||
|             Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount()); |  | ||||||
| 
 | 
 | ||||||
|             int controlInterfaceCount = 0; |  | ||||||
|             int dataInterfaceCount = 0; |  | ||||||
|             mControlInterface = null; |             mControlInterface = null; | ||||||
|             mDataInterface = null; |             mDataInterface = null; | ||||||
|             for (int i = 0; i < mDevice.getInterfaceCount(); i++) { |             int j = getInterfaceIdFromDescriptors(); | ||||||
|                 UsbInterface usbInterface = mDevice.getInterface(i); |             Log.d(TAG, "interface count=" + mDevice.getInterfaceCount() + ", IAD=" + j); | ||||||
|                 if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM) { |             if (j >= 0) { | ||||||
|                     if(controlInterfaceCount == mPortNumber) { |                 for (int i = 0; i < mDevice.getInterfaceCount(); i++) { | ||||||
|                         mControlIndex = i; |                     UsbInterface usbInterface = mDevice.getInterface(i); | ||||||
|                         mControlInterface = usbInterface; |                     if (usbInterface.getId() == j || usbInterface.getId() == j+1) { | ||||||
|  |                         if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM && | ||||||
|  |                                 usbInterface.getInterfaceSubclass() == USB_SUBCLASS_ACM) { | ||||||
|  |                             mControlIndex = usbInterface.getId(); | ||||||
|  |                             mControlInterface = usbInterface; | ||||||
|  |                         } | ||||||
|  |                         if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) { | ||||||
|  |                             mDataInterface = usbInterface; | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|                     controlInterfaceCount++; |  | ||||||
|                 } |                 } | ||||||
|                 if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) { |             } | ||||||
|                     if(dataInterfaceCount == mPortNumber) { |             if (mControlInterface == null || mDataInterface == null) { | ||||||
|                         mDataInterface = usbInterface; |                 Log.d(TAG, "no IAD fallback"); | ||||||
|  |                 int controlInterfaceCount = 0; | ||||||
|  |                 int dataInterfaceCount = 0; | ||||||
|  |                 for (int i = 0; i < mDevice.getInterfaceCount(); i++) { | ||||||
|  |                     UsbInterface usbInterface = mDevice.getInterface(i); | ||||||
|  |                     if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM && | ||||||
|  |                             usbInterface.getInterfaceSubclass() == USB_SUBCLASS_ACM) { | ||||||
|  |                         if (controlInterfaceCount == mPortNumber) { | ||||||
|  |                             mControlIndex = usbInterface.getId(); | ||||||
|  |                             mControlInterface = usbInterface; | ||||||
|  |                         } | ||||||
|  |                         controlInterfaceCount++; | ||||||
|  |                     } | ||||||
|  |                     if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) { | ||||||
|  |                         if (dataInterfaceCount == mPortNumber) { | ||||||
|  |                             mDataInterface = usbInterface; | ||||||
|  |                         } | ||||||
|  |                         dataInterfaceCount++; | ||||||
|                     } |                     } | ||||||
|                     dataInterfaceCount++; |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if(mControlInterface == null) { |             if(mControlInterface == null) { | ||||||
|                 throw new IOException("No control interface"); |                 throw new IOException("No control interface"); | ||||||
|             } |             } | ||||||
|             Log.d(TAG, "Control iface=" + mControlInterface); |             Log.d(TAG, "Control interface id " + mControlInterface.getId()); | ||||||
| 
 | 
 | ||||||
|             if (!mConnection.claimInterface(mControlInterface, true)) { |             if (!mConnection.claimInterface(mControlInterface, true)) { | ||||||
|                 throw new IOException("Could not claim control interface"); |                 throw new IOException("Could not claim control interface"); | ||||||
|             } |             } | ||||||
| 
 |  | ||||||
|             mControlEndpoint = mControlInterface.getEndpoint(0); |             mControlEndpoint = mControlInterface.getEndpoint(0); | ||||||
|             if (mControlEndpoint.getDirection() != UsbConstants.USB_DIR_IN || mControlEndpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_INT) { |             if (mControlEndpoint.getDirection() != UsbConstants.USB_DIR_IN || mControlEndpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_INT) { | ||||||
|                 throw new IOException("Invalid control endpoint"); |                 throw new IOException("Invalid control endpoint"); | ||||||
| @ -171,12 +208,10 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { | |||||||
|             if(mDataInterface == null) { |             if(mDataInterface == null) { | ||||||
|                 throw new IOException("No data interface"); |                 throw new IOException("No data interface"); | ||||||
|             } |             } | ||||||
|             Log.d(TAG, "data iface=" + mDataInterface); |             Log.d(TAG, "data interface id " + mDataInterface.getId()); | ||||||
| 
 |  | ||||||
|             if (!mConnection.claimInterface(mDataInterface, true)) { |             if (!mConnection.claimInterface(mDataInterface, true)) { | ||||||
|                 throw new IOException("Could not claim data interface"); |                 throw new IOException("Could not claim data interface"); | ||||||
|             } |             } | ||||||
| 
 |  | ||||||
|             for (int i = 0; i < mDataInterface.getEndpointCount(); i++) { |             for (int i = 0; i < mDataInterface.getEndpointCount(); i++) { | ||||||
|                 UsbEndpoint ep = mDataInterface.getEndpoint(i); |                 UsbEndpoint ep = mDataInterface.getEndpoint(i); | ||||||
|                 if (ep.getDirection() == UsbConstants.USB_DIR_IN && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) |                 if (ep.getDirection() == UsbConstants.USB_DIR_IN && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) | ||||||
| @ -186,6 +221,36 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         private int getInterfaceIdFromDescriptors() { | ||||||
|  |             ArrayList<byte[]> descriptors = UsbUtils.getDescriptors(mConnection); | ||||||
|  |             Log.d(TAG, "USB descriptor:"); | ||||||
|  |             for(byte[] descriptor : descriptors) | ||||||
|  |                 Log.d(TAG, HexDump.toHexString(descriptor)); | ||||||
|  | 
 | ||||||
|  |             if (descriptors.size() > 0 && | ||||||
|  |                     descriptors.get(0).length == 18 && | ||||||
|  |                     descriptors.get(0)[1] == 1 && // bDescriptorType | ||||||
|  |                     descriptors.get(0)[4] == (byte)(UsbConstants.USB_CLASS_MISC) && //bDeviceClass | ||||||
|  |                     descriptors.get(0)[5] == 2 && // bDeviceSubClass | ||||||
|  |                     descriptors.get(0)[6] == 1) { // bDeviceProtocol | ||||||
|  |                 // is IAD device, see https://www.usb.org/sites/default/files/iadclasscode_r10.pdf | ||||||
|  |                 int port = -1; | ||||||
|  |                 for (int d = 1; d < descriptors.size(); d++) { | ||||||
|  |                     if (descriptors.get(d).length == 8 && | ||||||
|  |                             descriptors.get(d)[1] == 0x0b && // bDescriptorType == IAD | ||||||
|  |                             descriptors.get(d)[4] == UsbConstants.USB_CLASS_COMM && // bFunctionClass == CDC | ||||||
|  |                             descriptors.get(d)[5] == USB_SUBCLASS_ACM) { // bFunctionSubClass == ACM | ||||||
|  |                         port++; | ||||||
|  |                         if (port == mPortNumber && | ||||||
|  |                                 descriptors.get(d)[3] == 2) { // bInterfaceCount | ||||||
|  |                             return descriptors.get(d)[2]; // bFirstInterface | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return -1; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         private int sendAcmControlMessage(int request, int value, byte[] buf) throws IOException { |         private int sendAcmControlMessage(int request, int value, byte[] buf) throws IOException { | ||||||
|             int len = mConnection.controlTransfer( |             int len = mConnection.controlTransfer( | ||||||
|                     USB_RT_ACM, request, value, mControlIndex, buf, buf != null ? buf.length : 0, 5000); |                     USB_RT_ACM, request, value, mControlIndex, buf, buf != null ? buf.length : 0, 5000); | ||||||
| @ -286,51 +351,9 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { | |||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @SuppressWarnings({"unused"}) | ||||||
|     public static Map<Integer, int[]> getSupportedDevices() { |     public static Map<Integer, int[]> getSupportedDevices() { | ||||||
|         final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>(); |         return new LinkedHashMap<>(); | ||||||
|         supportedDevices.put(UsbId.VENDOR_ARDUINO, |  | ||||||
|                 new int[] { |  | ||||||
|                         UsbId.ARDUINO_UNO, |  | ||||||
|                         UsbId.ARDUINO_UNO_R3, |  | ||||||
|                         UsbId.ARDUINO_MEGA_2560, |  | ||||||
|                         UsbId.ARDUINO_MEGA_2560_R3, |  | ||||||
|                         UsbId.ARDUINO_SERIAL_ADAPTER, |  | ||||||
|                         UsbId.ARDUINO_SERIAL_ADAPTER_R3, |  | ||||||
|                         UsbId.ARDUINO_MEGA_ADK, |  | ||||||
|                         UsbId.ARDUINO_MEGA_ADK_R3, |  | ||||||
|                         UsbId.ARDUINO_LEONARDO, |  | ||||||
|                         UsbId.ARDUINO_MICRO, |  | ||||||
|                 }); |  | ||||||
|         supportedDevices.put(UsbId.VENDOR_VAN_OOIJEN_TECH, |  | ||||||
|                 new int[] { |  | ||||||
|                         UsbId.VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL, |  | ||||||
|                 }); |  | ||||||
|         supportedDevices.put(UsbId.VENDOR_ATMEL, |  | ||||||
|                 new int[] { |  | ||||||
|                         UsbId.ATMEL_LUFA_CDC_DEMO_APP, |  | ||||||
|                 }); |  | ||||||
|         supportedDevices.put(UsbId.VENDOR_LEAFLABS, |  | ||||||
|                 new int[] { |  | ||||||
|                         UsbId.LEAFLABS_MAPLE, |  | ||||||
|                 }); |  | ||||||
|         supportedDevices.put(UsbId.VENDOR_ARM, |  | ||||||
|                 new int[] { |  | ||||||
|                         UsbId.ARM_MBED, |  | ||||||
|                 }); |  | ||||||
|         supportedDevices.put(UsbId.VENDOR_ST, |  | ||||||
|                 new int[] { |  | ||||||
|                         UsbId.ST_CDC, |  | ||||||
|                 }); |  | ||||||
|         supportedDevices.put(UsbId.VENDOR_RASPBERRY_PI, |  | ||||||
|                 new int[] { |  | ||||||
|                         UsbId.RASPBERRY_PI_PICO_MICROPYTHON, |  | ||||||
|                         UsbId.RASPBERRY_PI_PICO_SDK, |  | ||||||
|                 }); |  | ||||||
|         supportedDevices.put(UsbId.VENDOR_QINHENG, |  | ||||||
|                 new int[] { |  | ||||||
|                         UsbId.QINHENG_CH9102F, |  | ||||||
|                 }); |  | ||||||
|         return supportedDevices; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -7,7 +7,6 @@ package com.hoho.android.usbserial.driver; | |||||||
| 
 | 
 | ||||||
| import android.hardware.usb.UsbConstants; | import android.hardware.usb.UsbConstants; | ||||||
| import android.hardware.usb.UsbDevice; | import android.hardware.usb.UsbDevice; | ||||||
| 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.util.Log; | import android.util.Log; | ||||||
| @ -23,365 +22,366 @@ import java.util.Map; | |||||||
| 
 | 
 | ||||||
| public class Ch34xSerialDriver implements UsbSerialDriver { | public class Ch34xSerialDriver implements UsbSerialDriver { | ||||||
| 
 | 
 | ||||||
| 	private static final String TAG = Ch34xSerialDriver.class.getSimpleName(); |     private static final String TAG = Ch34xSerialDriver.class.getSimpleName(); | ||||||
| 
 | 
 | ||||||
| 	private final UsbDevice mDevice; |     private final UsbDevice mDevice; | ||||||
| 	private final UsbSerialPort mPort; |     private final UsbSerialPort mPort; | ||||||
| 
 | 
 | ||||||
| 	private static final int LCR_ENABLE_RX   = 0x80; |     private static final int LCR_ENABLE_RX   = 0x80; | ||||||
| 	private static final int LCR_ENABLE_TX   = 0x40; |     private static final int LCR_ENABLE_TX   = 0x40; | ||||||
| 	private static final int LCR_MARK_SPACE  = 0x20; |     private static final int LCR_MARK_SPACE  = 0x20; | ||||||
| 	private static final int LCR_PAR_EVEN    = 0x10; |     private static final int LCR_PAR_EVEN    = 0x10; | ||||||
| 	private static final int LCR_ENABLE_PAR  = 0x08; |     private static final int LCR_ENABLE_PAR  = 0x08; | ||||||
| 	private static final int LCR_STOP_BITS_2 = 0x04; |     private static final int LCR_STOP_BITS_2 = 0x04; | ||||||
| 	private static final int LCR_CS8         = 0x03; |     private static final int LCR_CS8         = 0x03; | ||||||
| 	private static final int LCR_CS7         = 0x02; |     private static final int LCR_CS7         = 0x02; | ||||||
| 	private static final int LCR_CS6         = 0x01; |     private static final int LCR_CS6         = 0x01; | ||||||
| 	private static final int LCR_CS5         = 0x00; |     private static final int LCR_CS5         = 0x00; | ||||||
| 
 | 
 | ||||||
| 	private static final int GCL_CTS = 0x01; |     private static final int GCL_CTS = 0x01; | ||||||
| 	private static final int GCL_DSR = 0x02; |     private static final int GCL_DSR = 0x02; | ||||||
| 	private static final int GCL_RI  = 0x04; |     private static final int GCL_RI  = 0x04; | ||||||
| 	private static final int GCL_CD  = 0x08; |     private static final int GCL_CD  = 0x08; | ||||||
| 	private static final int SCL_DTR = 0x20; |     private static final int SCL_DTR = 0x20; | ||||||
| 	private static final int SCL_RTS = 0x40; |     private static final int SCL_RTS = 0x40; | ||||||
| 
 | 
 | ||||||
| 	public Ch34xSerialDriver(UsbDevice device) { |     public Ch34xSerialDriver(UsbDevice device) { | ||||||
| 		mDevice = device; |         mDevice = device; | ||||||
| 		mPort = new Ch340SerialPort(mDevice, 0); |         mPort = new Ch340SerialPort(mDevice, 0); | ||||||
| 	} |     } | ||||||
| 
 | 
 | ||||||
| 	@Override |     @Override | ||||||
| 	public UsbDevice getDevice() { |     public UsbDevice getDevice() { | ||||||
| 		return mDevice; |         return mDevice; | ||||||
| 	} |     } | ||||||
| 
 | 
 | ||||||
| 	@Override |     @Override | ||||||
| 	public List<UsbSerialPort> getPorts() { |     public List<UsbSerialPort> getPorts() { | ||||||
| 		return Collections.singletonList(mPort); |         return Collections.singletonList(mPort); | ||||||
| 	} |     } | ||||||
| 
 | 
 | ||||||
| 	public class Ch340SerialPort extends CommonUsbSerialPort { |     public class Ch340SerialPort extends CommonUsbSerialPort { | ||||||
| 
 | 
 | ||||||
| 		private static final int USB_TIMEOUT_MILLIS = 5000; |         private static final int USB_TIMEOUT_MILLIS = 5000; | ||||||
| 
 | 
 | ||||||
| 		private final int DEFAULT_BAUD_RATE = 9600; |         private final int DEFAULT_BAUD_RATE = 9600; | ||||||
| 
 | 
 | ||||||
| 		private boolean dtr = false; |         private boolean dtr = false; | ||||||
| 		private boolean rts = false; |         private boolean rts = false; | ||||||
| 
 | 
 | ||||||
| 		public Ch340SerialPort(UsbDevice device, int portNumber) { |         public Ch340SerialPort(UsbDevice device, int portNumber) { | ||||||
| 			super(device, portNumber); |             super(device, portNumber); | ||||||
| 		} |         } | ||||||
| 
 | 
 | ||||||
| 		@Override |         @Override | ||||||
| 		public UsbSerialDriver getDriver() { |         public UsbSerialDriver getDriver() { | ||||||
| 			return Ch34xSerialDriver.this; |             return Ch34xSerialDriver.this; | ||||||
| 		} |         } | ||||||
| 
 | 
 | ||||||
| 		@Override |         @Override | ||||||
| 		protected void openInt(UsbDeviceConnection connection) throws IOException { |         protected void openInt() throws IOException { | ||||||
| 			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)) { | ||||||
| 					throw new IOException("Could not claim data interface"); |                     throw new IOException("Could not claim data interface"); | ||||||
| 				} |                 } | ||||||
| 			} |             } | ||||||
| 
 | 
 | ||||||
| 			UsbInterface dataIface = mDevice.getInterface(mDevice.getInterfaceCount() - 1); |             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) { | ||||||
| 					if (ep.getDirection() == UsbConstants.USB_DIR_IN) { |                     if (ep.getDirection() == UsbConstants.USB_DIR_IN) { | ||||||
| 						mReadEndpoint = ep; |                         mReadEndpoint = ep; | ||||||
| 					} else { |                     } else { | ||||||
| 						mWriteEndpoint = ep; |                         mWriteEndpoint = ep; | ||||||
| 					} |                     } | ||||||
| 				} |                 } | ||||||
| 			} |             } | ||||||
| 
 | 
 | ||||||
| 			initialize(); |             initialize(); | ||||||
| 			setBaudRate(DEFAULT_BAUD_RATE); |             setBaudRate(DEFAULT_BAUD_RATE); | ||||||
| 		} |         } | ||||||
| 
 | 
 | ||||||
| 		@Override |         @Override | ||||||
| 		protected void closeInt() { |         protected void closeInt() { | ||||||
| 			try { |             try { | ||||||
| 				for (int i = 0; i < mDevice.getInterfaceCount(); i++) |                 for (int i = 0; i < mDevice.getInterfaceCount(); i++) | ||||||
| 					mConnection.releaseInterface(mDevice.getInterface(i)); |                     mConnection.releaseInterface(mDevice.getInterface(i)); | ||||||
| 			} catch(Exception ignored) {} |             } catch(Exception ignored) {} | ||||||
| 		} |         } | ||||||
| 
 | 
 | ||||||
| 		private int controlOut(int request, int value, int index) { |         private int controlOut(int request, int value, int index) { | ||||||
| 			final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT; |             final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT; | ||||||
| 			return mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, |             return mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, | ||||||
| 					value, index, null, 0, USB_TIMEOUT_MILLIS); |                     value, index, null, 0, USB_TIMEOUT_MILLIS); | ||||||
| 		} |         } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 		private int controlIn(int request, int value, int index, byte[] buffer) { |         private int controlIn(int request, int value, int index, byte[] buffer) { | ||||||
| 			final int REQTYPE_DEVICE_TO_HOST = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_IN; |             final int REQTYPE_DEVICE_TO_HOST = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_IN; | ||||||
| 			return mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, request, |             return mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, request, | ||||||
| 					value, index, buffer, buffer.length, USB_TIMEOUT_MILLIS); |                     value, index, buffer, buffer.length, USB_TIMEOUT_MILLIS); | ||||||
| 		} |         } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 		private void checkState(String msg, int request, int value, int[] expected) throws IOException { |         private void checkState(String msg, int request, int value, int[] expected) throws IOException { | ||||||
| 			byte[] buffer = new byte[expected.length]; |             byte[] buffer = new byte[expected.length]; | ||||||
| 			int ret = controlIn(request, value, 0, buffer); |             int ret = controlIn(request, value, 0, buffer); | ||||||
| 
 | 
 | ||||||
| 			if (ret < 0) { |             if (ret < 0) { | ||||||
| 				throw new IOException("Failed send cmd [" + msg + "]"); |                 throw new IOException("Failed send cmd [" + msg + "]"); | ||||||
| 			} |             } | ||||||
| 
 | 
 | ||||||
| 			if (ret != expected.length) { |             if (ret != expected.length) { | ||||||
| 				throw new IOException("Expected " + expected.length + " bytes, but get " + ret + " [" + msg + "]"); |                 throw new IOException("Expected " + expected.length + " bytes, but get " + ret + " [" + msg + "]"); | ||||||
| 			} |             } | ||||||
| 
 | 
 | ||||||
| 			for (int i = 0; i < expected.length; i++) { |             for (int i = 0; i < expected.length; i++) { | ||||||
| 				if (expected[i] == -1) { |                 if (expected[i] == -1) { | ||||||
| 					continue; |                     continue; | ||||||
| 				} |                 } | ||||||
| 
 | 
 | ||||||
| 				int current = buffer[i] & 0xff; |                 int current = buffer[i] & 0xff; | ||||||
| 				if (expected[i] != current) { |                 if (expected[i] != current) { | ||||||
| 					throw new IOException("Expected 0x" + Integer.toHexString(expected[i]) + " byte, but get 0x" + Integer.toHexString(current) + " [" + msg + "]"); |                     throw new IOException("Expected 0x" + Integer.toHexString(expected[i]) + " byte, but get 0x" + Integer.toHexString(current) + " [" + msg + "]"); | ||||||
| 				} |                 } | ||||||
| 			} |             } | ||||||
| 		} |         } | ||||||
| 
 | 
 | ||||||
| 		private void setControlLines() throws IOException { |         private void setControlLines() throws IOException { | ||||||
| 			if (controlOut(0xa4, ~((dtr ? SCL_DTR : 0) | (rts ? SCL_RTS : 0)), 0) < 0) { |             if (controlOut(0xa4, ~((dtr ? SCL_DTR : 0) | (rts ? SCL_RTS : 0)), 0) < 0) { | ||||||
| 				throw new IOException("Failed to set control lines"); |                 throw new IOException("Failed to set control lines"); | ||||||
| 			} |             } | ||||||
| 		} |         } | ||||||
| 
 | 
 | ||||||
| 		private byte getStatus() throws IOException { |         private byte getStatus() throws IOException { | ||||||
| 			byte[] buffer = new byte[2]; |             byte[] buffer = new byte[2]; | ||||||
| 			int ret = controlIn(0x95, 0x0706, 0, buffer); |             int ret = controlIn(0x95, 0x0706, 0, buffer); | ||||||
| 			if (ret < 0) |             if (ret < 0) | ||||||
| 				throw new IOException("Error getting control lines"); |                 throw new IOException("Error getting control lines"); | ||||||
| 			return buffer[0]; |             return buffer[0]; | ||||||
| 		} |         } | ||||||
| 
 | 
 | ||||||
| 		private void initialize() throws IOException { |         private void initialize() throws IOException { | ||||||
| 			checkState("init #1", 0x5f, 0, new int[]{-1 /* 0x27, 0x30 */, 0x00}); |             checkState("init #1", 0x5f, 0, new int[]{-1 /* 0x27, 0x30 */, 0x00}); | ||||||
| 
 | 
 | ||||||
| 			if (controlOut(0xa1, 0, 0) < 0) { |             if (controlOut(0xa1, 0, 0) < 0) { | ||||||
| 				throw new IOException("Init failed: #2"); |                 throw new IOException("Init failed: #2"); | ||||||
| 			} |             } | ||||||
| 
 | 
 | ||||||
| 			setBaudRate(DEFAULT_BAUD_RATE); |             setBaudRate(DEFAULT_BAUD_RATE); | ||||||
| 
 | 
 | ||||||
| 			checkState("init #4", 0x95, 0x2518, new int[]{-1 /* 0x56, c3*/, 0x00}); |             checkState("init #4", 0x95, 0x2518, new int[]{-1 /* 0x56, c3*/, 0x00}); | ||||||
| 
 | 
 | ||||||
| 			if (controlOut(0x9a, 0x2518, LCR_ENABLE_RX | LCR_ENABLE_TX | LCR_CS8) < 0) { |             if (controlOut(0x9a, 0x2518, LCR_ENABLE_RX | LCR_ENABLE_TX | LCR_CS8) < 0) { | ||||||
| 				throw new IOException("Init failed: #5"); |                 throw new IOException("Init failed: #5"); | ||||||
| 			} |             } | ||||||
| 
 | 
 | ||||||
| 			checkState("init #6", 0x95, 0x0706, new int[]{-1/*0xf?*/, -1/*0xec,0xee*/}); |             checkState("init #6", 0x95, 0x0706, new int[]{-1/*0xf?*/, -1/*0xec,0xee*/}); | ||||||
| 
 | 
 | ||||||
| 			if (controlOut(0xa1, 0x501f, 0xd90a) < 0) { |             if (controlOut(0xa1, 0x501f, 0xd90a) < 0) { | ||||||
| 				throw new IOException("Init failed: #7"); |                 throw new IOException("Init failed: #7"); | ||||||
| 			} |             } | ||||||
| 
 | 
 | ||||||
| 			setBaudRate(DEFAULT_BAUD_RATE); |             setBaudRate(DEFAULT_BAUD_RATE); | ||||||
| 
 | 
 | ||||||
| 			setControlLines(); |             setControlLines(); | ||||||
| 
 | 
 | ||||||
| 			checkState("init #10", 0x95, 0x0706, new int[]{-1/* 0x9f, 0xff*/, -1/*0xec,0xee*/}); |             checkState("init #10", 0x95, 0x0706, new int[]{-1/* 0x9f, 0xff*/, -1/*0xec,0xee*/}); | ||||||
| 		} |         } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 		private void setBaudRate(int baudRate) throws IOException { |         private void setBaudRate(int baudRate) throws IOException { | ||||||
| 			long factor; |             long factor; | ||||||
| 			long divisor; |             long divisor; | ||||||
| 
 | 
 | ||||||
| 			if (baudRate == 921600) { |             if (baudRate == 921600) { | ||||||
| 				divisor = 7; |                 divisor = 7; | ||||||
| 				factor = 0xf300; |                 factor = 0xf300; | ||||||
| 			} else { |             } else { | ||||||
| 				final long BAUDBASE_FACTOR = 1532620800; |                 final long BAUDBASE_FACTOR = 1532620800; | ||||||
| 				final int BAUDBASE_DIVMAX = 3; |                 final int BAUDBASE_DIVMAX = 3; | ||||||
| 
 | 
 | ||||||
| 				if(BuildConfig.DEBUG && (baudRate & (3<<29)) == (1<<29)) |                 if(BuildConfig.DEBUG && (baudRate & (3<<29)) == (1<<29)) | ||||||
| 					baudRate &= ~(1<<29); // for testing purpose bypass dedicated baud rate handling |                     baudRate &= ~(1<<29); // for testing purpose bypass dedicated baud rate handling | ||||||
| 				factor = BAUDBASE_FACTOR / baudRate; |                 factor = BAUDBASE_FACTOR / baudRate; | ||||||
| 				divisor = BAUDBASE_DIVMAX; |                 divisor = BAUDBASE_DIVMAX; | ||||||
| 				while ((factor > 0xfff0) && divisor > 0) { |                 while ((factor > 0xfff0) && divisor > 0) { | ||||||
| 					factor >>= 3; |                     factor >>= 3; | ||||||
| 					divisor--; |                     divisor--; | ||||||
| 				} |                 } | ||||||
| 				if (factor > 0xfff0) { |                 if (factor > 0xfff0) { | ||||||
| 					throw new UnsupportedOperationException("Unsupported baud rate: " + baudRate); |                     throw new UnsupportedOperationException("Unsupported baud rate: " + baudRate); | ||||||
| 				} |                 } | ||||||
| 				factor = 0x10000 - factor; |                 factor = 0x10000 - factor; | ||||||
| 			} |             } | ||||||
| 
 | 
 | ||||||
| 			divisor |= 0x0080; // else ch341a waits until buffer full |             divisor |= 0x0080; // else ch341a waits until buffer full | ||||||
| 			int val1 = (int) ((factor & 0xff00) | divisor); |             int val1 = (int) ((factor & 0xff00) | divisor); | ||||||
| 			int val2 = (int) (factor & 0xff); |             int val2 = (int) (factor & 0xff); | ||||||
| 			Log.d(TAG, String.format("baud rate=%d, 0x1312=0x%04x, 0x0f2c=0x%04x", baudRate, val1, val2)); |             Log.d(TAG, String.format("baud rate=%d, 0x1312=0x%04x, 0x0f2c=0x%04x", baudRate, val1, val2)); | ||||||
| 			int ret = controlOut(0x9a, 0x1312, val1); |             int ret = controlOut(0x9a, 0x1312, val1); | ||||||
| 			if (ret < 0) { |             if (ret < 0) { | ||||||
| 				throw new IOException("Error setting baud rate: #1)"); |                 throw new IOException("Error setting baud rate: #1)"); | ||||||
| 			} |             } | ||||||
| 			ret = controlOut(0x9a, 0x0f2c, val2); |             ret = controlOut(0x9a, 0x0f2c, val2); | ||||||
| 			if (ret < 0) { |             if (ret < 0) { | ||||||
| 				throw new IOException("Error setting baud rate: #2"); |                 throw new IOException("Error setting baud rate: #2"); | ||||||
| 			} |             } | ||||||
| 		} |         } | ||||||
| 
 | 
 | ||||||
| 		@Override |         @Override | ||||||
| 		public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException { |         public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException { | ||||||
| 			if(baudRate <= 0) { |             if(baudRate <= 0) { | ||||||
| 				throw new IllegalArgumentException("Invalid baud rate: " + baudRate); |                 throw new IllegalArgumentException("Invalid baud rate: " + baudRate); | ||||||
| 			} |             } | ||||||
| 			setBaudRate(baudRate); |             setBaudRate(baudRate); | ||||||
| 
 | 
 | ||||||
| 			int lcr = LCR_ENABLE_RX | LCR_ENABLE_TX; |             int lcr = LCR_ENABLE_RX | LCR_ENABLE_TX; | ||||||
| 
 | 
 | ||||||
| 			switch (dataBits) { |             switch (dataBits) { | ||||||
| 				case DATABITS_5: |                 case DATABITS_5: | ||||||
| 					lcr |= LCR_CS5; |                     lcr |= LCR_CS5; | ||||||
| 					break; |                     break; | ||||||
| 				case DATABITS_6: |                 case DATABITS_6: | ||||||
| 					lcr |= LCR_CS6; |                     lcr |= LCR_CS6; | ||||||
| 					break; |                     break; | ||||||
| 				case DATABITS_7: |                 case DATABITS_7: | ||||||
| 					lcr |= LCR_CS7; |                     lcr |= LCR_CS7; | ||||||
| 					break; |                     break; | ||||||
| 				case DATABITS_8: |                 case DATABITS_8: | ||||||
| 					lcr |= LCR_CS8; |                     lcr |= LCR_CS8; | ||||||
| 					break; |                     break; | ||||||
| 				default: |                 default: | ||||||
| 					throw new IllegalArgumentException("Invalid data bits: " + dataBits); |                     throw new IllegalArgumentException("Invalid data bits: " + dataBits); | ||||||
| 			} |             } | ||||||
| 
 | 
 | ||||||
| 			switch (parity) { |             switch (parity) { | ||||||
| 				case PARITY_NONE: |                 case PARITY_NONE: | ||||||
| 					break; |                     break; | ||||||
| 				case PARITY_ODD: |                 case PARITY_ODD: | ||||||
| 					lcr |= LCR_ENABLE_PAR; |                     lcr |= LCR_ENABLE_PAR; | ||||||
| 					break; |                     break; | ||||||
| 				case PARITY_EVEN: |                 case PARITY_EVEN: | ||||||
| 					lcr |= LCR_ENABLE_PAR | LCR_PAR_EVEN; |                     lcr |= LCR_ENABLE_PAR | LCR_PAR_EVEN; | ||||||
| 					break; |                     break; | ||||||
| 				case PARITY_MARK: |                 case PARITY_MARK: | ||||||
| 					lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE; |                     lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE; | ||||||
| 					break; |                     break; | ||||||
| 				case PARITY_SPACE: |                 case PARITY_SPACE: | ||||||
| 					lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE | LCR_PAR_EVEN; |                     lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE | LCR_PAR_EVEN; | ||||||
| 					break; |                     break; | ||||||
| 				default: |                 default: | ||||||
| 					throw new IllegalArgumentException("Invalid parity: " + parity); |                     throw new IllegalArgumentException("Invalid parity: " + parity); | ||||||
| 			} |             } | ||||||
| 
 | 
 | ||||||
| 			switch (stopBits) { |             switch (stopBits) { | ||||||
| 				case STOPBITS_1: |                 case STOPBITS_1: | ||||||
| 					break; |                     break; | ||||||
| 				case STOPBITS_1_5: |                 case STOPBITS_1_5: | ||||||
| 					throw new UnsupportedOperationException("Unsupported stop bits: 1.5"); |                     throw new UnsupportedOperationException("Unsupported stop bits: 1.5"); | ||||||
| 				case STOPBITS_2: |                 case STOPBITS_2: | ||||||
| 					lcr |= LCR_STOP_BITS_2; |                     lcr |= LCR_STOP_BITS_2; | ||||||
| 					break; |                     break; | ||||||
| 				default: |                 default: | ||||||
| 					throw new IllegalArgumentException("Invalid stop bits: " + stopBits); |                     throw new IllegalArgumentException("Invalid stop bits: " + stopBits); | ||||||
| 			} |             } | ||||||
| 
 | 
 | ||||||
| 			int ret = controlOut(0x9a, 0x2518, lcr); |             int ret = controlOut(0x9a, 0x2518, lcr); | ||||||
| 			if (ret < 0) { |             if (ret < 0) { | ||||||
| 				throw new IOException("Error setting control byte"); |                 throw new IOException("Error setting control byte"); | ||||||
| 			} |             } | ||||||
| 		} |         } | ||||||
| 
 | 
 | ||||||
| 		@Override |         @Override | ||||||
| 		public boolean getCD() throws IOException { |         public boolean getCD() throws IOException { | ||||||
| 			return (getStatus() & GCL_CD) == 0; |             return (getStatus() & GCL_CD) == 0; | ||||||
| 		} |         } | ||||||
| 
 | 
 | ||||||
| 		@Override |         @Override | ||||||
| 		public boolean getCTS() throws IOException { |         public boolean getCTS() throws IOException { | ||||||
| 			return (getStatus() & GCL_CTS) == 0; |             return (getStatus() & GCL_CTS) == 0; | ||||||
| 		} |         } | ||||||
| 
 | 
 | ||||||
| 		@Override |         @Override | ||||||
| 		public boolean getDSR() throws IOException { |         public boolean getDSR() throws IOException { | ||||||
| 			return (getStatus() & GCL_DSR) == 0; |             return (getStatus() & GCL_DSR) == 0; | ||||||
| 		} |         } | ||||||
| 
 | 
 | ||||||
| 		@Override |         @Override | ||||||
| 		public boolean getDTR() throws IOException { |         public boolean getDTR() throws IOException { | ||||||
| 			return dtr; |             return dtr; | ||||||
| 		} |         } | ||||||
| 
 | 
 | ||||||
| 		@Override |         @Override | ||||||
| 		public void setDTR(boolean value) throws IOException { |         public void setDTR(boolean value) throws IOException { | ||||||
| 			dtr = value; |             dtr = value; | ||||||
| 			setControlLines(); |             setControlLines(); | ||||||
| 		} |         } | ||||||
| 
 | 
 | ||||||
| 		@Override |         @Override | ||||||
| 		public boolean getRI() throws IOException { |         public boolean getRI() throws IOException { | ||||||
| 			return (getStatus() & GCL_RI) == 0; |             return (getStatus() & GCL_RI) == 0; | ||||||
| 		} |         } | ||||||
| 
 | 
 | ||||||
| 		@Override |         @Override | ||||||
| 		public boolean getRTS() throws IOException { |         public boolean getRTS() throws IOException { | ||||||
| 			return rts; |             return rts; | ||||||
| 		} |         } | ||||||
| 
 | 
 | ||||||
| 		@Override |         @Override | ||||||
| 		public void setRTS(boolean value) throws IOException { |         public void setRTS(boolean value) throws IOException { | ||||||
| 			rts = value; |             rts = value; | ||||||
| 			setControlLines(); |             setControlLines(); | ||||||
| 		} |         } | ||||||
| 
 | 
 | ||||||
| 		@Override |         @Override | ||||||
| 		public EnumSet<ControlLine> getControlLines() throws IOException { |         public EnumSet<ControlLine> getControlLines() throws IOException { | ||||||
| 			int status = getStatus(); |             int status = getStatus(); | ||||||
| 			EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class); |             EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class); | ||||||
| 			if(rts) set.add(ControlLine.RTS); |             if(rts) set.add(ControlLine.RTS); | ||||||
| 			if((status & GCL_CTS) == 0) set.add(ControlLine.CTS); |             if((status & GCL_CTS) == 0) set.add(ControlLine.CTS); | ||||||
| 			if(dtr) set.add(ControlLine.DTR); |             if(dtr) set.add(ControlLine.DTR); | ||||||
| 			if((status & GCL_DSR) == 0) set.add(ControlLine.DSR); |             if((status & GCL_DSR) == 0) set.add(ControlLine.DSR); | ||||||
| 			if((status & GCL_CD) == 0) set.add(ControlLine.CD); |             if((status & GCL_CD) == 0) set.add(ControlLine.CD); | ||||||
| 			if((status & GCL_RI) == 0) set.add(ControlLine.RI); |             if((status & GCL_RI) == 0) set.add(ControlLine.RI); | ||||||
| 			return set; |             return set; | ||||||
| 		} |         } | ||||||
| 
 | 
 | ||||||
| 		@Override |         @Override | ||||||
| 		public EnumSet<ControlLine> getSupportedControlLines() throws IOException { |         public EnumSet<ControlLine> getSupportedControlLines() throws IOException { | ||||||
| 			return EnumSet.allOf(ControlLine.class); |             return EnumSet.allOf(ControlLine.class); | ||||||
| 		} |         } | ||||||
| 
 | 
 | ||||||
| 		@Override |         @Override | ||||||
| 		public void setBreak(boolean value) throws IOException { |         public void setBreak(boolean value) throws IOException { | ||||||
| 			byte[] req = new byte[2]; |             byte[] req = new byte[2]; | ||||||
| 			if(controlIn(0x95, 0x1805, 0, req) < 0) { |             if(controlIn(0x95, 0x1805, 0, req) < 0) { | ||||||
| 				throw new IOException("Error getting BREAK condition"); |                 throw new IOException("Error getting BREAK condition"); | ||||||
| 			} |             } | ||||||
| 			if(value) { |             if(value) { | ||||||
| 				req[0] &= ~1; |                 req[0] &= ~1; | ||||||
| 				req[1] &= ~0x40; |                 req[1] &= ~0x40; | ||||||
| 			} else { |             } else { | ||||||
| 				req[0] |= 1; |                 req[0] |= 1; | ||||||
| 				req[1] |= 0x40; |                 req[1] |= 0x40; | ||||||
| 			} |             } | ||||||
| 			int val = (req[1] & 0xff) << 8 | (req[0] & 0xff); |             int val = (req[1] & 0xff) << 8 | (req[0] & 0xff); | ||||||
| 			if(controlOut(0x9a, 0x1805, val) < 0) { |             if(controlOut(0x9a, 0x1805, val) < 0) { | ||||||
| 				throw new IOException("Error setting BREAK condition"); |                 throw new IOException("Error setting BREAK condition"); | ||||||
| 			} |             } | ||||||
| 		} |         } | ||||||
| 	} |     } | ||||||
| 
 | 
 | ||||||
| 	public static Map<Integer, int[]> getSupportedDevices() { |     @SuppressWarnings({"unused"}) | ||||||
| 		final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>(); |     public static Map<Integer, int[]> getSupportedDevices() { | ||||||
| 		supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{ |         final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>(); | ||||||
| 				UsbId.QINHENG_CH340, |         supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{ | ||||||
| 				UsbId.QINHENG_CH341A, |                 UsbId.QINHENG_CH340, | ||||||
| 		}); |                 UsbId.QINHENG_CH341A, | ||||||
| 		return supportedDevices; |         }); | ||||||
| 	} |         return supportedDevices; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| @ -0,0 +1,91 @@ | |||||||
|  | package com.hoho.android.usbserial.driver; | ||||||
|  | 
 | ||||||
|  | import android.hardware.usb.UsbConstants; | ||||||
|  | import android.hardware.usb.UsbDevice; | ||||||
|  | import android.hardware.usb.UsbEndpoint; | ||||||
|  | import android.hardware.usb.UsbInterface; | ||||||
|  | import android.util.Log; | ||||||
|  | 
 | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.util.Collections; | ||||||
|  | import java.util.EnumSet; | ||||||
|  | import java.util.LinkedHashMap; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.ArrayList; | ||||||
|  | 
 | ||||||
|  | public class ChromeCcdSerialDriver implements UsbSerialDriver{ | ||||||
|  | 
 | ||||||
|  |     private final String TAG = ChromeCcdSerialDriver.class.getSimpleName(); | ||||||
|  | 
 | ||||||
|  |     private final UsbDevice mDevice; | ||||||
|  |     private final List<UsbSerialPort> mPorts; | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public UsbDevice getDevice() { | ||||||
|  |         return mDevice; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public List<UsbSerialPort> getPorts() { | ||||||
|  |         return mPorts; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public ChromeCcdSerialDriver(UsbDevice mDevice) { | ||||||
|  |         this.mDevice = mDevice; | ||||||
|  |         mPorts = new ArrayList<UsbSerialPort>(); | ||||||
|  |         for (int i = 0; i < 3; i++) | ||||||
|  |             mPorts.add(new ChromeCcdSerialPort(mDevice, i)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public class ChromeCcdSerialPort extends CommonUsbSerialPort { | ||||||
|  |         private UsbInterface mDataInterface; | ||||||
|  | 
 | ||||||
|  |         public ChromeCcdSerialPort(UsbDevice device, int portNumber) { | ||||||
|  |             super(device, portNumber); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         protected void openInt() throws IOException { | ||||||
|  |             Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount()); | ||||||
|  |             mDataInterface = mDevice.getInterface(mPortNumber); | ||||||
|  |             if (!mConnection.claimInterface(mDataInterface, true)) { | ||||||
|  |                 throw new IOException("Could not claim shared control/data interface"); | ||||||
|  |             } | ||||||
|  |             Log.d(TAG, "endpoint count=" + mDataInterface.getEndpointCount()); | ||||||
|  |             for (int i = 0; i < mDataInterface.getEndpointCount(); ++i) { | ||||||
|  |                 UsbEndpoint ep = mDataInterface.getEndpoint(i); | ||||||
|  |                 if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { | ||||||
|  |                     mReadEndpoint = ep; | ||||||
|  |                 } else if ((ep.getDirection() == UsbConstants.USB_DIR_OUT) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { | ||||||
|  |                     mWriteEndpoint = ep; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         protected void closeInt() { | ||||||
|  |             try { | ||||||
|  |                 mConnection.releaseInterface(mDataInterface); | ||||||
|  |             } catch(Exception ignored) {} | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public UsbSerialDriver getDriver() { | ||||||
|  |             return ChromeCcdSerialDriver.this; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { | ||||||
|  |             throw new UnsupportedOperationException(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static Map<Integer, int[]> getSupportedDevices() { | ||||||
|  |         final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>(); | ||||||
|  |         supportedDevices.put(UsbId.VENDOR_GOOGLE, new int[]{ | ||||||
|  |                 UsbId.GOOGLE_CR50, | ||||||
|  |         }); | ||||||
|  |         return supportedDevices; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -10,6 +10,7 @@ import android.hardware.usb.UsbDevice; | |||||||
| import android.hardware.usb.UsbDeviceConnection; | import android.hardware.usb.UsbDeviceConnection; | ||||||
| import android.hardware.usb.UsbEndpoint; | import android.hardware.usb.UsbEndpoint; | ||||||
| import android.hardware.usb.UsbRequest; | import android.hardware.usb.UsbRequest; | ||||||
|  | import android.os.Build; | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
| 
 | 
 | ||||||
| import com.hoho.android.usbserial.util.MonotonicClock; | import com.hoho.android.usbserial.util.MonotonicClock; | ||||||
| @ -28,16 +29,17 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | |||||||
|     public static boolean DEBUG = false; |     public static boolean DEBUG = false; | ||||||
| 
 | 
 | ||||||
|     private static final String TAG = CommonUsbSerialPort.class.getSimpleName(); |     private static final String TAG = CommonUsbSerialPort.class.getSimpleName(); | ||||||
|     private static final int MAX_READ_SIZE = 16 * 1024; // = old bulkTransfer limit |     private static final int MAX_READ_SIZE = 16 * 1024; // = old bulkTransfer limit prior to Android 9 | ||||||
| 
 | 
 | ||||||
|     protected final UsbDevice mDevice; |     protected final UsbDevice mDevice; | ||||||
|     protected final int mPortNumber; |     protected final int mPortNumber; | ||||||
| 
 | 
 | ||||||
|     // non-null when open() |     // non-null when open() | ||||||
|     protected UsbDeviceConnection mConnection = null; |     protected UsbDeviceConnection mConnection; | ||||||
|     protected UsbEndpoint mReadEndpoint; |     protected UsbEndpoint mReadEndpoint; | ||||||
|     protected UsbEndpoint mWriteEndpoint; |     protected UsbEndpoint mWriteEndpoint; | ||||||
|     protected UsbRequest mUsbRequest; |     protected UsbRequest mUsbRequest; | ||||||
|  |     protected FlowControl mFlowControl = FlowControl.NONE; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Internal write buffer. |      * Internal write buffer. | ||||||
| @ -117,32 +119,36 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | |||||||
|             throw new IllegalArgumentException("Connection is null"); |             throw new IllegalArgumentException("Connection is null"); | ||||||
|         } |         } | ||||||
|         mConnection = connection; |         mConnection = connection; | ||||||
|  |         boolean ok = false; | ||||||
|         try { |         try { | ||||||
|             openInt(connection); |             openInt(); | ||||||
|             if (mReadEndpoint == null || mWriteEndpoint == null) { |             if (mReadEndpoint == null || mWriteEndpoint == null) { | ||||||
|                 throw new IOException("Could not get read & write endpoints"); |                 throw new IOException("Could not get read & write endpoints"); | ||||||
|             } |             } | ||||||
|             mUsbRequest = new UsbRequest(); |             mUsbRequest = new UsbRequest(); | ||||||
|             mUsbRequest.initialize(mConnection, mReadEndpoint); |             mUsbRequest.initialize(mConnection, mReadEndpoint); | ||||||
|         } catch(Exception e) { |             ok = true; | ||||||
|             try { |         } finally { | ||||||
|                 close(); |             if (!ok) { | ||||||
|             } catch(Exception ignored) {} |                 try { | ||||||
|             throw e; |                     close(); | ||||||
|  |                 } catch (Exception ignored) {} | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected abstract void openInt(UsbDeviceConnection connection) throws IOException; |     protected abstract void openInt() throws IOException; | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void close() throws IOException { |     public void close() throws IOException { | ||||||
|         if (mConnection == null) { |         if (mConnection == null) { | ||||||
|             throw new IOException("Already closed"); |             throw new IOException("Already closed"); | ||||||
|         } |         } | ||||||
|         try { |         UsbRequest usbRequest = mUsbRequest; | ||||||
|             mUsbRequest.cancel(); |  | ||||||
|         } catch(Exception ignored) {} |  | ||||||
|         mUsbRequest = null; |         mUsbRequest = null; | ||||||
|  |         try { | ||||||
|  |             usbRequest.cancel(); | ||||||
|  |         } catch(Exception ignored) {} | ||||||
|         try { |         try { | ||||||
|             closeInt(); |             closeInt(); | ||||||
|         } catch(Exception ignored) {} |         } catch(Exception ignored) {} | ||||||
| @ -157,25 +163,40 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | |||||||
|     /** |     /** | ||||||
|      * use simple USB request supported by all devices to test if connection is still valid |      * use simple USB request supported by all devices to test if connection is still valid | ||||||
|      */ |      */ | ||||||
|     protected void testConnection() throws IOException { |     protected void testConnection(boolean full) throws IOException { | ||||||
|  |         testConnection(full, "USB get_status request failed"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected void testConnection(boolean full, String msg) throws IOException { | ||||||
|  |         if(mUsbRequest == null) { | ||||||
|  |             throw new IOException("Connection closed"); | ||||||
|  |         } | ||||||
|  |         if(!full) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|         byte[] buf = new byte[2]; |         byte[] buf = new byte[2]; | ||||||
|         int len = mConnection.controlTransfer(0x80 /*DEVICE*/, 0 /*GET_STATUS*/, 0, 0, buf, buf.length, 200); |         int len = mConnection.controlTransfer(0x80 /*DEVICE*/, 0 /*GET_STATUS*/, 0, 0, buf, buf.length, 200); | ||||||
|         if(len < 0) |         if(len < 0) | ||||||
|             throw new IOException("USB get_status request failed"); |             throw new IOException(msg); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public int read(final byte[] dest, final int timeout) throws IOException { |     public int read(final byte[] dest, final int timeout) throws IOException { | ||||||
|         return read(dest, timeout, true); |         if(dest.length == 0) { | ||||||
|  |             throw new IllegalArgumentException("Read buffer too small"); | ||||||
|  |         } | ||||||
|  |         return read(dest, dest.length, timeout); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected int read(final byte[] dest, final int timeout, boolean testConnection) throws IOException { |     @Override | ||||||
|         if(mConnection == null) { |     public int read(final byte[] dest, final int length, final int timeout) throws IOException {return read(dest, length, timeout, true);} | ||||||
|             throw new IOException("Connection closed"); | 
 | ||||||
|         } |     protected int read(final byte[] dest, int length, final int timeout, boolean testConnection) throws IOException { | ||||||
|         if(dest.length <= 0) { |         testConnection(false); | ||||||
|             throw new IllegalArgumentException("Read buffer to small"); |         if(length <= 0) { | ||||||
|  |             throw new IllegalArgumentException("Read length too small"); | ||||||
|         } |         } | ||||||
|  |         length = Math.min(length, dest.length); | ||||||
|         final int nread; |         final int nread; | ||||||
|         if (timeout != 0) { |         if (timeout != 0) { | ||||||
|             // bulkTransfer will cause data loss with short timeout + high baud rates + continuous transfer |             // bulkTransfer will cause data loss with short timeout + high baud rates + continuous transfer | ||||||
| @ -187,16 +208,16 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | |||||||
|             //     /system/lib64/libandroid_runtime.so (android_hardware_UsbDeviceConnection_request_wait(_JNIEnv*, _jobject*, long)+84) |             //     /system/lib64/libandroid_runtime.so (android_hardware_UsbDeviceConnection_request_wait(_JNIEnv*, _jobject*, long)+84) | ||||||
|             // data loss / crashes were observed with timeout up to 200 msec |             // data loss / crashes were observed with timeout up to 200 msec | ||||||
|             long endTime = testConnection ? MonotonicClock.millis() + timeout : 0; |             long endTime = testConnection ? MonotonicClock.millis() + timeout : 0; | ||||||
|             int readMax = Math.min(dest.length, MAX_READ_SIZE); |             int readMax = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) ? length : Math.min(length, MAX_READ_SIZE); | ||||||
|             nread = mConnection.bulkTransfer(mReadEndpoint, dest, readMax, timeout); |             nread = mConnection.bulkTransfer(mReadEndpoint, dest, readMax, timeout); | ||||||
|             // Android error propagation is improvable: |             // Android error propagation is improvable: | ||||||
|             //  nread == -1 can be: timeout, connection lost, buffer to small, ??? |             //  nread == -1 can be: timeout, connection lost, buffer to small, ??? | ||||||
|             if(nread == -1 && testConnection && MonotonicClock.millis() < endTime) |             if(nread == -1 && testConnection) | ||||||
|                 testConnection(); |                 testConnection(MonotonicClock.millis() < endTime); | ||||||
| 
 | 
 | ||||||
|         } else { |         } else { | ||||||
|             final ByteBuffer buf = ByteBuffer.wrap(dest); |             final ByteBuffer buf = ByteBuffer.wrap(dest, 0, length); | ||||||
|             if (!mUsbRequest.queue(buf, dest.length)) { |             if (!mUsbRequest.queue(buf, length)) { | ||||||
|                 throw new IOException("Queueing USB request failed"); |                 throw new IOException("Queueing USB request failed"); | ||||||
|             } |             } | ||||||
|             final UsbRequest response = mConnection.requestWait(); |             final UsbRequest response = mConnection.requestWait(); | ||||||
| @ -207,21 +228,23 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | |||||||
|             // Android error propagation is improvable: |             // Android error propagation is improvable: | ||||||
|             //   response != null & nread == 0 can be: connection lost, buffer to small, ??? |             //   response != null & nread == 0 can be: connection lost, buffer to small, ??? | ||||||
|             if(nread == 0) { |             if(nread == 0) { | ||||||
|                 testConnection(); |                 testConnection(true); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         return Math.max(nread, 0); |         return Math.max(nread, 0); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void write(final byte[] src, final int timeout) throws IOException { |     public void write(byte[] src, int timeout) throws IOException {write(src, src.length, timeout);} | ||||||
|         int offset = 0; |  | ||||||
|         final long endTime = (timeout == 0) ? 0 : (MonotonicClock.millis() + timeout); |  | ||||||
| 
 | 
 | ||||||
|         if(mConnection == null) { |     @Override | ||||||
|             throw new IOException("Connection closed"); |     public void write(final byte[] src, int length, final int timeout) throws IOException { | ||||||
|         } |         int offset = 0; | ||||||
|         while (offset < src.length) { |         long startTime = MonotonicClock.millis(); | ||||||
|  |         length = Math.min(length, src.length); | ||||||
|  | 
 | ||||||
|  |         testConnection(false); | ||||||
|  |         while (offset < length) { | ||||||
|             int requestTimeout; |             int requestTimeout; | ||||||
|             final int requestLength; |             final int requestLength; | ||||||
|             final int actualLength; |             final int actualLength; | ||||||
| @ -232,7 +255,7 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | |||||||
|                 if (mWriteBuffer == null) { |                 if (mWriteBuffer == null) { | ||||||
|                     mWriteBuffer = new byte[mWriteEndpoint.getMaxPacketSize()]; |                     mWriteBuffer = new byte[mWriteEndpoint.getMaxPacketSize()]; | ||||||
|                 } |                 } | ||||||
|                 requestLength = Math.min(src.length - offset, mWriteBuffer.length); |                 requestLength = Math.min(length - offset, mWriteBuffer.length); | ||||||
|                 if (offset == 0) { |                 if (offset == 0) { | ||||||
|                     writeBuffer = src; |                     writeBuffer = src; | ||||||
|                 } else { |                 } else { | ||||||
| @ -243,7 +266,7 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | |||||||
|                 if (timeout == 0 || offset == 0) { |                 if (timeout == 0 || offset == 0) { | ||||||
|                     requestTimeout = timeout; |                     requestTimeout = timeout; | ||||||
|                 } else { |                 } else { | ||||||
|                     requestTimeout = (int)(endTime - MonotonicClock.millis()); |                     requestTimeout = (int)(startTime + timeout - MonotonicClock.millis()); | ||||||
|                     if(requestTimeout == 0) |                     if(requestTimeout == 0) | ||||||
|                         requestTimeout = -1; |                         requestTimeout = -1; | ||||||
|                 } |                 } | ||||||
| @ -253,16 +276,19 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | |||||||
|                     actualLength = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, requestLength, requestTimeout); |                     actualLength = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, requestLength, requestTimeout); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             long elapsed = MonotonicClock.millis() - startTime; | ||||||
|             if (DEBUG) { |             if (DEBUG) { | ||||||
|                 Log.d(TAG, "Wrote " + actualLength + "/" + requestLength + " offset " + offset + "/" + src.length + " timeout " + requestTimeout); |                 Log.d(TAG, "Wrote " + actualLength + "/" + requestLength + " offset " + offset + "/" + length + " time " + elapsed + "/" + requestTimeout); | ||||||
|             } |             } | ||||||
|             if (actualLength <= 0) { |             if (actualLength <= 0) { | ||||||
|                 if (timeout != 0 && MonotonicClock.millis() >= endTime) { |                 String msg = "Error writing " + requestLength + " bytes at offset " + offset + " of total " + src.length + " after " + elapsed + "msec, rc=" + actualLength; | ||||||
|                     SerialTimeoutException ex = new SerialTimeoutException("Error writing " + requestLength + " bytes at offset " + offset + " of total " + src.length + ", rc=" + actualLength); |                 if (timeout != 0) { | ||||||
|                     ex.bytesTransferred = offset; |                     // could be buffer full because: writing to fast, stopped by flow control | ||||||
|                     throw ex; |                     testConnection(elapsed < timeout, msg); | ||||||
|  |                     throw new SerialTimeoutException(msg, offset); | ||||||
|                 } else { |                 } else { | ||||||
|                     throw new IOException("Error writing " + requestLength + " bytes at offset " + offset + " of total " + src.length); |                     throw new IOException(msg); | ||||||
|  | 
 | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             offset += actualLength; |             offset += actualLength; | ||||||
| @ -271,7 +297,7 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | |||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public boolean isOpen() { |     public boolean isOpen() { | ||||||
|         return mConnection != null; |         return mUsbRequest != null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
| @ -302,16 +328,29 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | |||||||
|     public void setRTS(boolean value) throws IOException { throw new UnsupportedOperationException(); } |     public void setRTS(boolean value) throws IOException { throw new UnsupportedOperationException(); } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public abstract EnumSet<ControlLine> getControlLines() throws IOException; |     public EnumSet<ControlLine> getControlLines() throws IOException { throw new UnsupportedOperationException(); } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public abstract EnumSet<ControlLine> getSupportedControlLines() throws IOException; |     public EnumSet<ControlLine> getSupportedControlLines() throws IOException { return EnumSet.noneOf(ControlLine.class); } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { |     public void setFlowControl(FlowControl flowcontrol) throws IOException { | ||||||
|         throw new UnsupportedOperationException(); |         if (flowcontrol != FlowControl.NONE) | ||||||
|  |             throw new UnsupportedOperationException(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Override | ||||||
|  |     public FlowControl getFlowControl() { return mFlowControl; } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public EnumSet<FlowControl> getSupportedFlowControl() { return EnumSet.of(FlowControl.NONE); } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public boolean getXON() throws IOException { throw new UnsupportedOperationException(); } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { throw new UnsupportedOperationException(); } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void setBreak(boolean value) throws IOException { throw new UnsupportedOperationException(); } |     public void setBreak(boolean value) throws IOException { throw new UnsupportedOperationException(); } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -8,7 +8,6 @@ package com.hoho.android.usbserial.driver; | |||||||
| 
 | 
 | ||||||
| import android.hardware.usb.UsbConstants; | import android.hardware.usb.UsbConstants; | ||||||
| import android.hardware.usb.UsbDevice; | import android.hardware.usb.UsbDevice; | ||||||
| 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; | ||||||
| 
 | 
 | ||||||
| @ -61,9 +60,14 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | |||||||
|         private static final int SILABSER_SET_LINE_CTL_REQUEST_CODE = 0x03; |         private static final int SILABSER_SET_LINE_CTL_REQUEST_CODE = 0x03; | ||||||
|         private static final int SILABSER_SET_BREAK_REQUEST_CODE = 0x05; |         private static final int SILABSER_SET_BREAK_REQUEST_CODE = 0x05; | ||||||
|         private static final int SILABSER_SET_MHS_REQUEST_CODE = 0x07; |         private static final int SILABSER_SET_MHS_REQUEST_CODE = 0x07; | ||||||
|         private static final int SILABSER_SET_BAUDRATE = 0x1E; |  | ||||||
|         private static final int SILABSER_FLUSH_REQUEST_CODE = 0x12; |  | ||||||
|         private static final int SILABSER_GET_MDMSTS_REQUEST_CODE = 0x08; |         private static final int SILABSER_GET_MDMSTS_REQUEST_CODE = 0x08; | ||||||
|  |         private static final int SILABSER_SET_XON_REQUEST_CODE = 0x09; | ||||||
|  |         private static final int SILABSER_SET_XOFF_REQUEST_CODE = 0x0A; | ||||||
|  |         private static final int SILABSER_GET_COMM_STATUS_REQUEST_CODE = 0x10; | ||||||
|  |         private static final int SILABSER_FLUSH_REQUEST_CODE = 0x12; | ||||||
|  |         private static final int SILABSER_SET_FLOW_REQUEST_CODE = 0x13; | ||||||
|  |         private static final int SILABSER_SET_CHARS_REQUEST_CODE = 0x19; | ||||||
|  |         private static final int SILABSER_SET_BAUDRATE_REQUEST_CODE = 0x1E; | ||||||
| 
 | 
 | ||||||
|         private static final int FLUSH_READ_CODE = 0x0a; |         private static final int FLUSH_READ_CODE = 0x0a; | ||||||
|         private static final int FLUSH_WRITE_CODE = 0x05; |         private static final int FLUSH_WRITE_CODE = 0x05; | ||||||
| @ -85,6 +89,8 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | |||||||
|         /* |         /* | ||||||
|         * SILABSER_GET_MDMSTS_REQUEST_CODE |         * SILABSER_GET_MDMSTS_REQUEST_CODE | ||||||
|          */ |          */ | ||||||
|  |         private static final int STATUS_DTR = 0x01; | ||||||
|  |         private static final int STATUS_RTS = 0x02; | ||||||
|         private static final int STATUS_CTS = 0x10; |         private static final int STATUS_CTS = 0x10; | ||||||
|         private static final int STATUS_DSR = 0x20; |         private static final int STATUS_DSR = 0x20; | ||||||
|         private static final int STATUS_RI = 0x40; |         private static final int STATUS_RI = 0x40; | ||||||
| @ -119,14 +125,14 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | |||||||
|             byte[] buffer = new byte[1]; |             byte[] buffer = new byte[1]; | ||||||
|             int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, SILABSER_GET_MDMSTS_REQUEST_CODE, 0, |             int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, SILABSER_GET_MDMSTS_REQUEST_CODE, 0, | ||||||
|                     mPortNumber, buffer, buffer.length, USB_WRITE_TIMEOUT_MILLIS); |                     mPortNumber, buffer, buffer.length, USB_WRITE_TIMEOUT_MILLIS); | ||||||
|             if (result != 1) { |             if (result != buffer.length) { | ||||||
|                 throw new IOException("Control transfer failed: " + SILABSER_GET_MDMSTS_REQUEST_CODE + " / " + 0 + " -> " + result); |                 throw new IOException("Control transfer failed: " + SILABSER_GET_MDMSTS_REQUEST_CODE + " / " + 0 + " -> " + result); | ||||||
|             } |             } | ||||||
|             return buffer[0]; |             return buffer[0]; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         @Override |         @Override | ||||||
|         protected void openInt(UsbDeviceConnection connection) throws IOException { |         protected void openInt() throws IOException { | ||||||
|             mIsRestrictedPort = mDevice.getInterfaceCount() == 2 && mPortNumber == 1; |             mIsRestrictedPort = mDevice.getInterfaceCount() == 2 && mPortNumber == 1; | ||||||
|             if(mPortNumber >= mDevice.getInterfaceCount()) { |             if(mPortNumber >= mDevice.getInterfaceCount()) { | ||||||
|                 throw new IOException("Unknown port number"); |                 throw new IOException("Unknown port number"); | ||||||
| @ -148,6 +154,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | |||||||
| 
 | 
 | ||||||
|             setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_ENABLE); |             setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_ENABLE); | ||||||
|             setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, (dtr ? DTR_ENABLE : DTR_DISABLE) | (rts ? RTS_ENABLE : RTS_DISABLE)); |             setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, (dtr ? DTR_ENABLE : DTR_DISABLE) | (rts ? RTS_ENABLE : RTS_DISABLE)); | ||||||
|  |             setFlowControl(mFlowControl); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         @Override |         @Override | ||||||
| @ -167,7 +174,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | |||||||
|                     (byte) ((baudRate >> 16) & 0xff), |                     (byte) ((baudRate >> 16) & 0xff), | ||||||
|                     (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_REQUEST_CODE, | ||||||
|                     0, mPortNumber, 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"); | ||||||
| @ -290,9 +297,11 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | |||||||
|         public EnumSet<ControlLine> getControlLines() throws IOException { |         public EnumSet<ControlLine> getControlLines() throws IOException { | ||||||
|             byte status = getStatus(); |             byte status = getStatus(); | ||||||
|             EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class); |             EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class); | ||||||
|             if(rts) set.add(ControlLine.RTS); |             //if(rts) set.add(ControlLine.RTS);                      // configured value | ||||||
|  |             if((status & STATUS_RTS) != 0) set.add(ControlLine.RTS); // actual value | ||||||
|             if((status & STATUS_CTS) != 0) set.add(ControlLine.CTS); |             if((status & STATUS_CTS) != 0) set.add(ControlLine.CTS); | ||||||
|             if(dtr) set.add(ControlLine.DTR); |             //if(dtr) set.add(ControlLine.DTR);                      // configured value | ||||||
|  |             if((status & STATUS_DTR) != 0) set.add(ControlLine.DTR); // actual value | ||||||
|             if((status & STATUS_DSR) != 0) set.add(ControlLine.DSR); |             if((status & STATUS_DSR) != 0) set.add(ControlLine.DSR); | ||||||
|             if((status & STATUS_CD) != 0) set.add(ControlLine.CD); |             if((status & STATUS_CD) != 0) set.add(ControlLine.CD); | ||||||
|             if((status & STATUS_RI) != 0) set.add(ControlLine.RI); |             if((status & STATUS_RI) != 0) set.add(ControlLine.RI); | ||||||
| @ -304,6 +313,73 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | |||||||
|             return EnumSet.allOf(ControlLine.class); |             return EnumSet.allOf(ControlLine.class); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         @Override | ||||||
|  |         public boolean getXON() throws IOException { | ||||||
|  |             byte[] buffer = new byte[0x13]; | ||||||
|  |             int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, SILABSER_GET_COMM_STATUS_REQUEST_CODE, 0, | ||||||
|  |                     mPortNumber, buffer, buffer.length, USB_WRITE_TIMEOUT_MILLIS); | ||||||
|  |             if (result != buffer.length) { | ||||||
|  |                 throw new IOException("Control transfer failed: " + SILABSER_GET_COMM_STATUS_REQUEST_CODE + " -> " + result); | ||||||
|  |             } | ||||||
|  |             return (buffer[4] & 8) == 0; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * emulate external XON/OFF | ||||||
|  |          * @throws IOException | ||||||
|  |          */ | ||||||
|  |         public void setXON(boolean value) throws IOException { | ||||||
|  |             setConfigSingle(value ? SILABSER_SET_XON_REQUEST_CODE : SILABSER_SET_XOFF_REQUEST_CODE, 0); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public void setFlowControl(FlowControl flowControl) throws IOException { | ||||||
|  |             byte[] data = new byte[16]; | ||||||
|  |             if(flowControl == FlowControl.RTS_CTS) { | ||||||
|  |                 data[4] |=  0b1000_0000; // RTS | ||||||
|  |                 data[0] |=  0b0000_1000; // CTS | ||||||
|  |             } else { | ||||||
|  |                 if(rts) | ||||||
|  |                     data[4] |= 0b0100_0000; | ||||||
|  |             } | ||||||
|  |             if(flowControl == FlowControl.DTR_DSR) { | ||||||
|  |                 data[0] |= 0b0000_0010; // DTR | ||||||
|  |                 data[0] |= 0b0001_0000; // DSR | ||||||
|  |             } else { | ||||||
|  |                 if(dtr) | ||||||
|  |                     data[0] |= 0b0000_0001; | ||||||
|  |             } | ||||||
|  |             if(flowControl == FlowControl.XON_XOFF) { | ||||||
|  |                 byte[] chars = new byte[]{0, 0, 0, 0, CHAR_XON, CHAR_XOFF}; | ||||||
|  |                 int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_CHARS_REQUEST_CODE, | ||||||
|  |                         0, mPortNumber, chars, chars.length, USB_WRITE_TIMEOUT_MILLIS); | ||||||
|  |                 if (ret != chars.length) { | ||||||
|  |                     throw new IOException("Error setting XON/XOFF chars"); | ||||||
|  |                 } | ||||||
|  |                 data[4] |= 0b0000_0011; | ||||||
|  |                 data[7] |= 0b1000_0000; | ||||||
|  |                 data[8] = (byte)128; | ||||||
|  |                 data[12] = (byte)128; | ||||||
|  |             } | ||||||
|  |             if(flowControl == FlowControl.XON_XOFF_INLINE) { | ||||||
|  |                 throw new UnsupportedOperationException(); | ||||||
|  |             } | ||||||
|  |             int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_FLOW_REQUEST_CODE, | ||||||
|  |                     0, mPortNumber, data, data.length, USB_WRITE_TIMEOUT_MILLIS); | ||||||
|  |             if (ret != data.length) { | ||||||
|  |                 throw new IOException("Error setting flow control"); | ||||||
|  |             } | ||||||
|  |             if(flowControl == FlowControl.XON_XOFF) { | ||||||
|  |                 setXON(true); | ||||||
|  |             } | ||||||
|  |             mFlowControl = flowControl; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public EnumSet<FlowControl> getSupportedFlowControl() { | ||||||
|  |             return EnumSet.of(FlowControl.NONE, FlowControl.RTS_CTS, FlowControl.DTR_DSR, FlowControl.XON_XOFF); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         @Override |         @Override | ||||||
|         // note: only working on some devices, on other devices ignored w/o error |         // note: only working on some devices, on other devices ignored w/o error | ||||||
|         public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { |         public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { | ||||||
| @ -321,6 +397,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @SuppressWarnings({"unused"}) | ||||||
|     public static Map<Integer, int[]> getSupportedDevices() { |     public static Map<Integer, int[]> getSupportedDevices() { | ||||||
|         final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>(); |         final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>(); | ||||||
|         supportedDevices.put(UsbId.VENDOR_SILABS, |         supportedDevices.put(UsbId.VENDOR_SILABS, | ||||||
|  | |||||||
| @ -9,7 +9,6 @@ package com.hoho.android.usbserial.driver; | |||||||
| 
 | 
 | ||||||
| import android.hardware.usb.UsbConstants; | import android.hardware.usb.UsbConstants; | ||||||
| import android.hardware.usb.UsbDevice; | import android.hardware.usb.UsbDevice; | ||||||
| import android.hardware.usb.UsbDeviceConnection; |  | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
| 
 | 
 | ||||||
| import com.hoho.android.usbserial.util.MonotonicClock; | import com.hoho.android.usbserial.util.MonotonicClock; | ||||||
| @ -65,6 +64,7 @@ public class FtdiSerialDriver implements UsbSerialDriver { | |||||||
| 
 | 
 | ||||||
|         private static final int RESET_REQUEST = 0; |         private static final int RESET_REQUEST = 0; | ||||||
|         private static final int MODEM_CONTROL_REQUEST = 1; |         private static final int MODEM_CONTROL_REQUEST = 1; | ||||||
|  |         private static final int SET_FLOW_CONTROL_REQUEST = 2; | ||||||
|         private static final int SET_BAUD_RATE_REQUEST = 3; |         private static final int SET_BAUD_RATE_REQUEST = 3; | ||||||
|         private static final int SET_DATA_REQUEST = 4; |         private static final int SET_DATA_REQUEST = 4; | ||||||
|         private static final int GET_MODEM_STATUS_REQUEST = 5; |         private static final int GET_MODEM_STATUS_REQUEST = 5; | ||||||
| @ -99,8 +99,8 @@ public class FtdiSerialDriver implements UsbSerialDriver { | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         @Override |         @Override | ||||||
|         protected void openInt(UsbDeviceConnection connection) throws IOException { |         protected void openInt() throws IOException { | ||||||
|             if (!connection.claimInterface(mDevice.getInterface(mPortNumber), true)) { |             if (!mConnection.claimInterface(mDevice.getInterface(mPortNumber), true)) { | ||||||
|                 throw new IOException("Could not claim interface " + mPortNumber); |                 throw new IOException("Could not claim interface " + mPortNumber); | ||||||
|             } |             } | ||||||
|             if (mDevice.getInterface(mPortNumber).getEndpointCount() < 2) { |             if (mDevice.getInterface(mPortNumber).getEndpointCount() < 2) { | ||||||
| @ -121,9 +121,10 @@ public class FtdiSerialDriver implements UsbSerialDriver { | |||||||
|             if (result != 0) { |             if (result != 0) { | ||||||
|                 throw new IOException("Init RTS,DTR failed: result=" + result); |                 throw new IOException("Init RTS,DTR failed: result=" + result); | ||||||
|             } |             } | ||||||
|  |             setFlowControl(mFlowControl); | ||||||
| 
 | 
 | ||||||
|             // mDevice.getVersion() would require API 23 |             // mDevice.getVersion() would require API 23 | ||||||
|             byte[] rawDescriptors = connection.getRawDescriptors(); |             byte[] rawDescriptors = mConnection.getRawDescriptors(); | ||||||
|             if(rawDescriptors == null || rawDescriptors.length < 14) { |             if(rawDescriptors == null || rawDescriptors.length < 14) { | ||||||
|                 throw new IOException("Could not get device descriptors"); |                 throw new IOException("Could not get device descriptors"); | ||||||
|             } |             } | ||||||
| @ -140,24 +141,37 @@ public class FtdiSerialDriver implements UsbSerialDriver { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         @Override |         @Override | ||||||
|         public int read(final byte[] dest, final int timeout) throws IOException { |         public int read(final byte[] dest, final int timeout) throws IOException | ||||||
|  |         { | ||||||
|             if(dest.length <= READ_HEADER_LENGTH) { |             if(dest.length <= READ_HEADER_LENGTH) { | ||||||
|                 throw new IllegalArgumentException("Read buffer to small"); |                 throw new IllegalArgumentException("Read buffer too small"); | ||||||
|                 // could allocate larger buffer, including space for 2 header bytes, but this would |                 // could allocate larger buffer, including space for 2 header bytes, but this would | ||||||
|                 // result in buffers not being 64 byte aligned any more, causing data loss at continuous |                 // result in buffers not being 64 byte aligned any more, causing data loss at continuous | ||||||
|                 // data transfer at high baud rates when buffers are fully filled. |                 // data transfer at high baud rates when buffers are fully filled. | ||||||
|             } |             } | ||||||
|  |             return read(dest, dest.length, timeout); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public int read(final byte[] dest, int length, final int timeout) throws IOException { | ||||||
|  |             if(length <= READ_HEADER_LENGTH) { | ||||||
|  |                 throw new IllegalArgumentException("Read length too small"); | ||||||
|  |                 // could allocate larger buffer, including space for 2 header bytes, but this would | ||||||
|  |                 // result in buffers not being 64 byte aligned any more, causing data loss at continuous | ||||||
|  |                 // data transfer at high baud rates when buffers are fully filled. | ||||||
|  |             } | ||||||
|  |             length = Math.min(length, dest.length); | ||||||
|             int nread; |             int nread; | ||||||
|             if (timeout != 0) { |             if (timeout != 0) { | ||||||
|                 long endTime = MonotonicClock.millis() + timeout; |                 long endTime = MonotonicClock.millis() + timeout; | ||||||
|                 do { |                 do { | ||||||
|                     nread = super.read(dest, Math.max(1, (int)(endTime - MonotonicClock.millis())), false); |                     nread = super.read(dest, length, Math.max(1, (int)(endTime - MonotonicClock.millis())), false); | ||||||
|                 } while (nread == READ_HEADER_LENGTH && MonotonicClock.millis() < endTime); |                 } while (nread == READ_HEADER_LENGTH && MonotonicClock.millis() < endTime); | ||||||
|                 if(nread <= 0 && MonotonicClock.millis() < endTime) |                 if(nread <= 0) | ||||||
|                     testConnection(); |                     testConnection(MonotonicClock.millis() < endTime); | ||||||
|             } else { |             } else { | ||||||
|                 do { |                 do { | ||||||
|                     nread = super.read(dest, timeout, false); |                     nread = super.read(dest, length, timeout); | ||||||
|                 } while (nread == READ_HEADER_LENGTH); |                 } while (nread == READ_HEADER_LENGTH); | ||||||
|             } |             } | ||||||
|             return readFilter(dest, nread); |             return readFilter(dest, nread); | ||||||
| @ -291,7 +305,7 @@ public class FtdiSerialDriver implements UsbSerialDriver { | |||||||
|             byte[] data = new byte[2]; |             byte[] data = new byte[2]; | ||||||
|             int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_MODEM_STATUS_REQUEST, |             int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_MODEM_STATUS_REQUEST, | ||||||
|                     0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS); |                     0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS); | ||||||
|             if (result != 2) { |             if (result != data.length) { | ||||||
|                 throw new IOException("Get modem status failed: result=" + result); |                 throw new IOException("Get modem status failed: result=" + result); | ||||||
|             } |             } | ||||||
|             return data[0]; |             return data[0]; | ||||||
| @ -365,6 +379,38 @@ public class FtdiSerialDriver implements UsbSerialDriver { | |||||||
|             return EnumSet.allOf(ControlLine.class); |             return EnumSet.allOf(ControlLine.class); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         @Override | ||||||
|  |         public void setFlowControl(FlowControl flowControl) throws IOException { | ||||||
|  |             int value = 0; | ||||||
|  |             int index = mPortNumber+1; | ||||||
|  |             switch (flowControl) { | ||||||
|  |                 case NONE: | ||||||
|  |                     break; | ||||||
|  |                 case RTS_CTS: | ||||||
|  |                     index |= 0x100; | ||||||
|  |                     break; | ||||||
|  |                 case DTR_DSR: | ||||||
|  |                     index |= 0x200; | ||||||
|  |                     break; | ||||||
|  |                 case XON_XOFF_INLINE: | ||||||
|  |                     value = CHAR_XON + (CHAR_XOFF << 8); | ||||||
|  |                     index |= 0x400; | ||||||
|  |                     break; | ||||||
|  |                 default: | ||||||
|  |                     throw new UnsupportedOperationException(); | ||||||
|  |             } | ||||||
|  |             int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_FLOW_CONTROL_REQUEST, | ||||||
|  |                     value, index, null, 0, USB_WRITE_TIMEOUT_MILLIS); | ||||||
|  |             if (result != 0) | ||||||
|  |                 throw new IOException("Set flow control failed: result=" + result); | ||||||
|  |             mFlowControl = flowControl; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public EnumSet<FlowControl> getSupportedFlowControl() { | ||||||
|  |             return EnumSet.of(FlowControl.NONE, FlowControl.RTS_CTS, FlowControl.DTR_DSR, FlowControl.XON_XOFF_INLINE); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         @Override |         @Override | ||||||
|         public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { |         public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { | ||||||
|             if (purgeWriteBuffers) { |             if (purgeWriteBuffers) { | ||||||
| @ -407,7 +453,7 @@ public class FtdiSerialDriver implements UsbSerialDriver { | |||||||
|             byte[] data = new byte[1]; |             byte[] data = new byte[1]; | ||||||
|             int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_LATENCY_TIMER_REQUEST, |             int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_LATENCY_TIMER_REQUEST, | ||||||
|                     0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS); |                     0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS); | ||||||
|             if (result != 1) { |             if (result != data.length) { | ||||||
|                 throw new IOException("Get latency timer failed: result=" + result); |                 throw new IOException("Get latency timer failed: result=" + result); | ||||||
|             } |             } | ||||||
|             return data[0]; |             return data[0]; | ||||||
| @ -415,6 +461,7 @@ public class FtdiSerialDriver implements UsbSerialDriver { | |||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @SuppressWarnings({"unused"}) | ||||||
|     public static Map<Integer, int[]> getSupportedDevices() { |     public static Map<Integer, int[]> getSupportedDevices() { | ||||||
|         final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>(); |         final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>(); | ||||||
|         supportedDevices.put(UsbId.VENDOR_FTDI, |         supportedDevices.put(UsbId.VENDOR_FTDI, | ||||||
|  | |||||||
| @ -0,0 +1,102 @@ | |||||||
|  | package com.hoho.android.usbserial.driver; | ||||||
|  | 
 | ||||||
|  | import android.hardware.usb.UsbConstants; | ||||||
|  | import android.hardware.usb.UsbDevice; | ||||||
|  | import android.hardware.usb.UsbEndpoint; | ||||||
|  | import android.hardware.usb.UsbInterface; | ||||||
|  | import android.util.Log; | ||||||
|  | 
 | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.util.Collections; | ||||||
|  | import java.util.EnumSet; | ||||||
|  | import java.util.LinkedHashMap; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Map; | ||||||
|  | 
 | ||||||
|  | public class GsmModemSerialDriver implements UsbSerialDriver{ | ||||||
|  | 
 | ||||||
|  |     private final String TAG = GsmModemSerialDriver.class.getSimpleName(); | ||||||
|  | 
 | ||||||
|  |     private final UsbDevice mDevice; | ||||||
|  |     private final UsbSerialPort mPort; | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public UsbDevice getDevice() { | ||||||
|  |         return mDevice; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public List<UsbSerialPort> getPorts() { | ||||||
|  |         return Collections.singletonList(mPort); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public GsmModemSerialDriver(UsbDevice mDevice) { | ||||||
|  |         this.mDevice = mDevice; | ||||||
|  |         mPort = new GsmModemSerialPort(mDevice, 0); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public class GsmModemSerialPort extends CommonUsbSerialPort { | ||||||
|  | 
 | ||||||
|  |         private UsbInterface mDataInterface; | ||||||
|  | 
 | ||||||
|  |         public GsmModemSerialPort(UsbDevice device, int portNumber) { | ||||||
|  |             super(device, portNumber); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         protected void openInt() throws IOException { | ||||||
|  |             Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount()); | ||||||
|  |             mDataInterface = mDevice.getInterface(0); | ||||||
|  |             if (!mConnection.claimInterface(mDataInterface, true)) { | ||||||
|  |                 throw new IOException("Could not claim shared control/data interface"); | ||||||
|  |             } | ||||||
|  |             Log.d(TAG, "endpoint count=" + mDataInterface.getEndpointCount()); | ||||||
|  |             for (int i = 0; i < mDataInterface.getEndpointCount(); ++i) { | ||||||
|  |                 UsbEndpoint ep = mDataInterface.getEndpoint(i); | ||||||
|  |                 if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { | ||||||
|  |                     mReadEndpoint = ep; | ||||||
|  |                 } else if ((ep.getDirection() == UsbConstants.USB_DIR_OUT) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { | ||||||
|  |                     mWriteEndpoint = ep; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             initGsmModem(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         protected void closeInt() { | ||||||
|  |             try { | ||||||
|  |                 mConnection.releaseInterface(mDataInterface); | ||||||
|  |             } catch(Exception ignored) {} | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private int initGsmModem() throws IOException { | ||||||
|  |             int len = mConnection.controlTransfer( | ||||||
|  |                     0x21, 0x22, 0x01, 0, null, 0, 5000); | ||||||
|  |             if(len < 0) { | ||||||
|  |                 throw new IOException("init failed"); | ||||||
|  |             } | ||||||
|  |             return len; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public UsbSerialDriver getDriver() { | ||||||
|  |             return GsmModemSerialDriver.this; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { | ||||||
|  |             throw new UnsupportedOperationException(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static Map<Integer, int[]> getSupportedDevices() { | ||||||
|  |         final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>(); | ||||||
|  |         supportedDevices.put(UsbId.VENDOR_UNISOC, new int[]{ | ||||||
|  |                 UsbId.FIBOCOM_L610, | ||||||
|  |                 UsbId.FIBOCOM_L612, | ||||||
|  |         }); | ||||||
|  |         return supportedDevices; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -6,6 +6,7 @@ | |||||||
| 
 | 
 | ||||||
| package com.hoho.android.usbserial.driver; | package com.hoho.android.usbserial.driver; | ||||||
| 
 | 
 | ||||||
|  | import android.hardware.usb.UsbDevice; | ||||||
| import android.util.Pair; | import android.util.Pair; | ||||||
| 
 | 
 | ||||||
| import java.lang.reflect.InvocationTargetException; | import java.lang.reflect.InvocationTargetException; | ||||||
| @ -14,14 +15,14 @@ import java.util.LinkedHashMap; | |||||||
| import java.util.Map; | import java.util.Map; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Maps (vendor id, product id) pairs to the corresponding serial driver. |  * Maps (vendor id, product id) pairs to the corresponding serial driver, | ||||||
|  * |  * or invoke 'probe' method to check actual USB devices for matching interfaces. | ||||||
|  * @author mike wakerly (opensource@hoho.com) |  | ||||||
|  */ |  */ | ||||||
| public class ProbeTable { | public class ProbeTable { | ||||||
| 
 | 
 | ||||||
|     private final Map<Pair<Integer, Integer>, Class<? extends UsbSerialDriver>> mProbeTable = |     private final Map<Pair<Integer, Integer>, Class<? extends UsbSerialDriver>> mVidPidProbeTable = | ||||||
|             new LinkedHashMap<>(); |             new LinkedHashMap<>(); | ||||||
|  |     private final Map<Method, Class<? extends UsbSerialDriver>> mMethodProbeTable = new LinkedHashMap<>(); | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Adds or updates a (vendor, product) pair in the table. |      * Adds or updates a (vendor, product) pair in the table. | ||||||
| @ -33,7 +34,7 @@ public class ProbeTable { | |||||||
|      */ |      */ | ||||||
|     public ProbeTable addProduct(int vendorId, int productId, |     public ProbeTable addProduct(int vendorId, int productId, | ||||||
|             Class<? extends UsbSerialDriver> driverClass) { |             Class<? extends UsbSerialDriver> driverClass) { | ||||||
|         mProbeTable.put(Pair.create(vendorId, productId), driverClass); |         mVidPidProbeTable.put(Pair.create(vendorId, productId), driverClass); | ||||||
|         return this; |         return this; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -41,12 +42,11 @@ public class ProbeTable { | |||||||
|      * Internal method to add all supported products from |      * Internal method to add all supported products from | ||||||
|      * {@code getSupportedProducts} static method. |      * {@code getSupportedProducts} static method. | ||||||
|      * |      * | ||||||
|      * @param driverClass |      * @param driverClass to be added | ||||||
|      * @return |  | ||||||
|      */ |      */ | ||||||
|     @SuppressWarnings("unchecked") |     @SuppressWarnings("unchecked") | ||||||
|     ProbeTable addDriver(Class<? extends UsbSerialDriver> driverClass) { |     void addDriver(Class<? extends UsbSerialDriver> driverClass) { | ||||||
|         final Method method; |         Method method; | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             method = driverClass.getMethod("getSupportedDevices"); |             method = driverClass.getMethod("getSupportedDevices"); | ||||||
| @ -68,20 +68,35 @@ public class ProbeTable { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return this; |         try { | ||||||
|  |             method = driverClass.getMethod("probe", UsbDevice.class); | ||||||
|  |             mMethodProbeTable.put(method, driverClass); | ||||||
|  |         } catch (SecurityException | NoSuchMethodException ignored) { | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Returns the driver for the given (vendor, product) pair, or {@code null} |      * Returns the driver for the given USB device, or {@code null} if no match. | ||||||
|      * if no match. |  | ||||||
|      * |      * | ||||||
|      * @param vendorId the USB vendor id |      * @param usbDevice the USB device to be probed | ||||||
|      * @param productId the USB product id |  | ||||||
|      * @return the driver class matching this pair, or {@code null} |      * @return the driver class matching this pair, or {@code null} | ||||||
|      */ |      */ | ||||||
|     public Class<? extends UsbSerialDriver> findDriver(int vendorId, int productId) { |     public Class<? extends UsbSerialDriver> findDriver(final UsbDevice usbDevice) { | ||||||
|         final Pair<Integer, Integer> pair = Pair.create(vendorId, productId); |         final Pair<Integer, Integer> pair = Pair.create(usbDevice.getVendorId(), usbDevice.getProductId()); | ||||||
|         return mProbeTable.get(pair); |         Class<? extends UsbSerialDriver> driverClass = mVidPidProbeTable.get(pair); | ||||||
|  |         if (driverClass != null) | ||||||
|  |             return driverClass; | ||||||
|  |         for (Map.Entry<Method, Class<? extends UsbSerialDriver>> entry : mMethodProbeTable.entrySet()) { | ||||||
|  |             try { | ||||||
|  |                 Method method = entry.getKey(); | ||||||
|  |                 Object o = method.invoke(null, usbDevice); | ||||||
|  |                 if((boolean)o) | ||||||
|  |                     return entry.getValue(); | ||||||
|  |             } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { | ||||||
|  |                 throw new RuntimeException(e); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -11,7 +11,6 @@ package com.hoho.android.usbserial.driver; | |||||||
| 
 | 
 | ||||||
| import android.hardware.usb.UsbConstants; | import android.hardware.usb.UsbConstants; | ||||||
| import android.hardware.usb.UsbDevice; | import android.hardware.usb.UsbDevice; | ||||||
| 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.util.Log; | import android.util.Log; | ||||||
| @ -123,7 +122,7 @@ public class ProlificSerialDriver implements UsbSerialDriver { | |||||||
|         private volatile Thread mReadStatusThread = null; |         private volatile Thread mReadStatusThread = null; | ||||||
|         private final Object mReadStatusThreadLock = new Object(); |         private final Object mReadStatusThreadLock = new Object(); | ||||||
|         private boolean mStopReadStatusThread = false; |         private boolean mStopReadStatusThread = false; | ||||||
|         private IOException mReadStatusException = null; |         private Exception mReadStatusException = null; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         public ProlificSerialPort(UsbDevice device, int portNumber) { |         public ProlificSerialPort(UsbDevice device, int portNumber) { | ||||||
| @ -202,12 +201,12 @@ public class ProlificSerialDriver implements UsbSerialDriver { | |||||||
| 
 | 
 | ||||||
|         private void readStatusThreadFunction() { |         private void readStatusThreadFunction() { | ||||||
|             try { |             try { | ||||||
|  |                 byte[] buffer = new byte[STATUS_BUFFER_SIZE]; | ||||||
|                 while (!mStopReadStatusThread) { |                 while (!mStopReadStatusThread) { | ||||||
|                     byte[] buffer = new byte[STATUS_BUFFER_SIZE]; |  | ||||||
|                     long endTime = MonotonicClock.millis() + 500; |                     long endTime = MonotonicClock.millis() + 500; | ||||||
|                     int readBytesCount = mConnection.bulkTransfer(mInterruptEndpoint, buffer, STATUS_BUFFER_SIZE, 500); |                     int readBytesCount = mConnection.bulkTransfer(mInterruptEndpoint, buffer, STATUS_BUFFER_SIZE, 500); | ||||||
|                     if(readBytesCount == -1 && MonotonicClock.millis() < endTime) |                     if(readBytesCount == -1) | ||||||
|                         testConnection(); |                         testConnection(MonotonicClock.millis() < endTime); | ||||||
|                     if (readBytesCount > 0) { |                     if (readBytesCount > 0) { | ||||||
|                         if (readBytesCount != STATUS_BUFFER_SIZE) { |                         if (readBytesCount != STATUS_BUFFER_SIZE) { | ||||||
|                             throw new IOException("Invalid status notification, expected " + STATUS_BUFFER_SIZE + " bytes, got " + readBytesCount); |                             throw new IOException("Invalid status notification, expected " + STATUS_BUFFER_SIZE + " bytes, got " + readBytesCount); | ||||||
| @ -218,8 +217,9 @@ public class ProlificSerialDriver implements UsbSerialDriver { | |||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } catch (IOException e) { |             } catch (Exception e) { | ||||||
|                 mReadStatusException = e; |                 if (isOpen()) | ||||||
|  |                     mReadStatusException = e; | ||||||
|             } |             } | ||||||
|             //Log.d(TAG, "end control line status thread " + mStopReadStatusThread + " " + (mReadStatusException == null ? "-" : mReadStatusException.getMessage())); |             //Log.d(TAG, "end control line status thread " + mStopReadStatusThread + " " + (mReadStatusException == null ? "-" : mReadStatusException.getMessage())); | ||||||
|         } |         } | ||||||
| @ -250,8 +250,8 @@ public class ProlificSerialDriver implements UsbSerialDriver { | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             /* throw and clear an exception which occured in the status read thread */ |             /* throw and clear an exception which occurred in the status read thread */ | ||||||
|             IOException readStatusException = mReadStatusException; |             Exception readStatusException = mReadStatusException; | ||||||
|             if (mReadStatusException != null) { |             if (mReadStatusException != null) { | ||||||
|                 mReadStatusException = null; |                 mReadStatusException = null; | ||||||
|                 throw new IOException(readStatusException); |                 throw new IOException(readStatusException); | ||||||
| @ -265,10 +265,10 @@ public class ProlificSerialDriver implements UsbSerialDriver { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         @Override |         @Override | ||||||
|         public void openInt(UsbDeviceConnection connection) throws IOException { |         public void openInt() throws IOException { | ||||||
|             UsbInterface usbInterface = mDevice.getInterface(0); |             UsbInterface usbInterface = mDevice.getInterface(0); | ||||||
| 
 | 
 | ||||||
|             if (!connection.claimInterface(usbInterface, true)) { |             if (!mConnection.claimInterface(usbInterface, true)) { | ||||||
|                 throw new IOException("Error claiming Prolific interface 0"); |                 throw new IOException("Error claiming Prolific interface 0"); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
| @ -290,7 +290,7 @@ public class ProlificSerialDriver implements UsbSerialDriver { | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             byte[] rawDescriptors = connection.getRawDescriptors(); |             byte[] rawDescriptors = mConnection.getRawDescriptors(); | ||||||
|             if(rawDescriptors == null || rawDescriptors.length < 14) { |             if(rawDescriptors == null || rawDescriptors.length < 14) { | ||||||
|                 throw new IOException("Could not get device descriptors"); |                 throw new IOException("Could not get device descriptors"); | ||||||
|             } |             } | ||||||
| @ -315,6 +315,7 @@ public class ProlificSerialDriver implements UsbSerialDriver { | |||||||
|             resetDevice(); |             resetDevice(); | ||||||
|             doBlackMagic(); |             doBlackMagic(); | ||||||
|             setControlLines(mControlLinesValue); |             setControlLines(mControlLinesValue); | ||||||
|  |             setFlowControl(mFlowControl); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         @Override |         @Override | ||||||
| @ -526,7 +527,6 @@ public class ProlificSerialDriver implements UsbSerialDriver { | |||||||
|             setControlLines(newControlLinesValue); |             setControlLines(newControlLinesValue); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|         @Override |         @Override | ||||||
|         public EnumSet<ControlLine> getControlLines() throws IOException { |         public EnumSet<ControlLine> getControlLines() throws IOException { | ||||||
|             int status = getStatus(); |             int status = getStatus(); | ||||||
| @ -545,6 +545,39 @@ public class ProlificSerialDriver implements UsbSerialDriver { | |||||||
|             return EnumSet.allOf(ControlLine.class); |             return EnumSet.allOf(ControlLine.class); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         @Override | ||||||
|  |         public void setFlowControl(FlowControl flowControl) throws IOException { | ||||||
|  |             // vendorOut values from https://www.mail-archive.com/linux-usb@vger.kernel.org/msg110968.html | ||||||
|  |             switch (flowControl) { | ||||||
|  |                 case NONE: | ||||||
|  |                     if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) | ||||||
|  |                         vendorOut(0x0a, 0xff, null); | ||||||
|  |                     else | ||||||
|  |                         vendorOut(0, 0, null); | ||||||
|  |                     break; | ||||||
|  |                 case RTS_CTS: | ||||||
|  |                     if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) | ||||||
|  |                         vendorOut(0x0a, 0xfa, null); | ||||||
|  |                     else | ||||||
|  |                         vendorOut(0, 0x61, null); | ||||||
|  |                     break; | ||||||
|  |                 case XON_XOFF_INLINE: | ||||||
|  |                     if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) | ||||||
|  |                         vendorOut(0x0a, 0xee, null); | ||||||
|  |                     else | ||||||
|  |                         vendorOut(0, 0xc1, null); | ||||||
|  |                     break; | ||||||
|  |                 default: | ||||||
|  |                     throw new UnsupportedOperationException(); | ||||||
|  |             } | ||||||
|  |             mFlowControl = flowControl; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public EnumSet<FlowControl> getSupportedFlowControl() { | ||||||
|  |             return EnumSet.of(FlowControl.NONE, FlowControl.RTS_CTS, FlowControl.XON_XOFF_INLINE); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         @Override |         @Override | ||||||
|         public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { |         public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { | ||||||
|             if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) { |             if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) { | ||||||
| @ -567,6 +600,7 @@ public class ProlificSerialDriver implements UsbSerialDriver { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @SuppressWarnings({"unused"}) | ||||||
|     public static Map<Integer, int[]> getSupportedDevices() { |     public static Map<Integer, int[]> getSupportedDevices() { | ||||||
|         final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>(); |         final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>(); | ||||||
|         supportedDevices.put(UsbId.VENDOR_PROLIFIC, |         supportedDevices.put(UsbId.VENDOR_PROLIFIC, | ||||||
|  | |||||||
| @ -9,7 +9,8 @@ import java.io.InterruptedIOException; | |||||||
|  * {@see InterruptedIOException#bytesTransferred} may contain bytes transferred |  * {@see InterruptedIOException#bytesTransferred} may contain bytes transferred | ||||||
|  */ |  */ | ||||||
| public class SerialTimeoutException extends InterruptedIOException { | public class SerialTimeoutException extends InterruptedIOException { | ||||||
|     public SerialTimeoutException(String s) { |     public SerialTimeoutException(String s, int bytesTransferred) { | ||||||
|         super(s); |         super(s); | ||||||
|  |         this.bytesTransferred = bytesTransferred; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -23,27 +23,6 @@ public final class UsbId { | |||||||
|     public static final int FTDI_FT232H = 0x6014; |     public static final int FTDI_FT232H = 0x6014; | ||||||
|     public static final int FTDI_FT231X = 0x6015; // same ID for FT230X, FT231X, FT234XD |     public static final int FTDI_FT231X = 0x6015; // same ID for FT230X, FT231X, FT234XD | ||||||
| 
 | 
 | ||||||
|     public static final int VENDOR_ATMEL = 0x03EB; |  | ||||||
|     public static final int ATMEL_LUFA_CDC_DEMO_APP = 0x2044; |  | ||||||
| 
 |  | ||||||
|     public static final int VENDOR_ARDUINO = 0x2341; |  | ||||||
|     public static final int ARDUINO_UNO = 0x0001; |  | ||||||
|     public static final int ARDUINO_MEGA_2560 = 0x0010; |  | ||||||
|     public static final int ARDUINO_SERIAL_ADAPTER = 0x003b; |  | ||||||
|     public static final int ARDUINO_MEGA_ADK = 0x003f; |  | ||||||
|     public static final int ARDUINO_MEGA_2560_R3 = 0x0042; |  | ||||||
|     public static final int ARDUINO_UNO_R3 = 0x0043; |  | ||||||
|     public static final int ARDUINO_MEGA_ADK_R3 = 0x0044; |  | ||||||
|     public static final int ARDUINO_SERIAL_ADAPTER_R3 = 0x0044; |  | ||||||
|     public static final int ARDUINO_LEONARDO = 0x8036; |  | ||||||
|     public static final int ARDUINO_MICRO = 0x8037; |  | ||||||
| 
 |  | ||||||
|     public static final int VENDOR_VAN_OOIJEN_TECH = 0x16c0; |  | ||||||
|     public static final int VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL = 0x0483; |  | ||||||
| 
 |  | ||||||
|     public static final int VENDOR_LEAFLABS = 0x1eaf; |  | ||||||
|     public static final int LEAFLABS_MAPLE = 0x0004; |  | ||||||
| 
 |  | ||||||
|     public static final int VENDOR_SILABS = 0x10c4; |     public static final int VENDOR_SILABS = 0x10c4; | ||||||
|     public static final int SILABS_CP2102 = 0xea60; // same ID for CP2101, CP2103, CP2104, CP2109 |     public static final int SILABS_CP2102 = 0xea60; // same ID for CP2101, CP2103, CP2104, CP2109 | ||||||
|     public static final int SILABS_CP2105 = 0xea70; |     public static final int SILABS_CP2105 = 0xea70; | ||||||
| @ -58,21 +37,17 @@ public final class UsbId { | |||||||
|     public static final int PROLIFIC_PL2303GE = 0x23e3; // " |     public static final int PROLIFIC_PL2303GE = 0x23e3; // " | ||||||
|     public static final int PROLIFIC_PL2303GS = 0x23f3; // " |     public static final int PROLIFIC_PL2303GS = 0x23f3; // " | ||||||
| 
 | 
 | ||||||
|  |     public static final int VENDOR_GOOGLE = 0x18d1; | ||||||
|  |     public static final int GOOGLE_CR50 = 0x5014; | ||||||
|  | 
 | ||||||
|     public static final int VENDOR_QINHENG = 0x1a86; |     public static final int VENDOR_QINHENG = 0x1a86; | ||||||
|     public static final int QINHENG_CH340 = 0x7523; |     public static final int QINHENG_CH340 = 0x7523; | ||||||
|     public static final int QINHENG_CH341A = 0x5523; |     public static final int QINHENG_CH341A = 0x5523; | ||||||
|     public static final int QINHENG_CH9102F = 0x55D4; |  | ||||||
| 
 | 
 | ||||||
|     // at www.linux-usb.org/usb.ids listed for NXP/LPC1768, but all processors supported by ARM mbed DAPLink firmware report these ids |     public static final int VENDOR_UNISOC = 0x1782; | ||||||
|     public static final int VENDOR_ARM = 0x0d28; |     public static final int FIBOCOM_L610 = 0x4D10; | ||||||
|     public static final int ARM_MBED = 0x0204; |     public static final int FIBOCOM_L612 = 0x4D12; | ||||||
| 
 | 
 | ||||||
|     public static final int VENDOR_ST = 0x0483; |  | ||||||
|     public static final int ST_CDC = 0x5740; |  | ||||||
| 
 |  | ||||||
|     public static final int VENDOR_RASPBERRY_PI = 0x2e8a; |  | ||||||
|     public static final int RASPBERRY_PI_PICO_MICROPYTHON = 0x0005; |  | ||||||
|     public static final int RASPBERRY_PI_PICO_SDK = 0x000a; |  | ||||||
| 
 | 
 | ||||||
|     private UsbId() { |     private UsbId() { | ||||||
|         throw new IllegalAccessError("Non-instantiable class"); |         throw new IllegalAccessError("Non-instantiable class"); | ||||||
|  | |||||||
| @ -10,12 +10,17 @@ import android.hardware.usb.UsbDevice; | |||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| /** |  | ||||||
|  * |  | ||||||
|  * @author mike wakerly (opensource@hoho.com) |  | ||||||
|  */ |  | ||||||
| public interface UsbSerialDriver { | public interface UsbSerialDriver { | ||||||
| 
 | 
 | ||||||
|  |     /* | ||||||
|  |      * Additional interface properties. Invoked thru reflection. | ||||||
|  |      * | ||||||
|  |         UsbSerialDriver(UsbDevice device);                  // constructor with device | ||||||
|  |         static Map<Integer, int[]> getSupportedDevices(); | ||||||
|  |         static boolean probe(UsbDevice device);             // optional | ||||||
|  |      */ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Returns the raw {@link UsbDevice} backing this port. |      * Returns the raw {@link UsbDevice} backing this port. | ||||||
|      * |      * | ||||||
|  | |||||||
| @ -58,7 +58,16 @@ public interface UsbSerialPort extends Closeable { | |||||||
|     int STOPBITS_2 = 2; |     int STOPBITS_2 = 2; | ||||||
| 
 | 
 | ||||||
|     /** Values for get[Supported]ControlLines() */ |     /** Values for get[Supported]ControlLines() */ | ||||||
|     enum ControlLine { RTS, CTS,  DTR, DSR,  CD, RI } |     enum ControlLine { RTS, CTS, DTR, DSR, CD, RI } | ||||||
|  | 
 | ||||||
|  |     /** Values for (set|get|getSupported)FlowControl() */ | ||||||
|  |     enum FlowControl { NONE, RTS_CTS, DTR_DSR, XON_XOFF, XON_XOFF_INLINE } | ||||||
|  | 
 | ||||||
|  |     /** XON character used with flow control XON/XOFF */ | ||||||
|  |     char CHAR_XON = 17; | ||||||
|  |     /** XOFF character used with flow control XON/XOFF */ | ||||||
|  |     char CHAR_XOFF = 19; | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Returns the driver used by this port. |      * Returns the driver used by this port. | ||||||
| @ -122,6 +131,17 @@ public interface UsbSerialPort extends Closeable { | |||||||
|      */ |      */ | ||||||
|     int read(final byte[] dest, final int timeout) throws IOException; |     int read(final byte[] dest, final int timeout) throws IOException; | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Reads bytes with specified length into the destination buffer. | ||||||
|  |      * | ||||||
|  |      * @param dest the destination byte buffer | ||||||
|  |      * @param length the maximum length of the data to read | ||||||
|  |      * @param timeout the timeout for reading in milliseconds, 0 is infinite | ||||||
|  |      * @return the actual number of bytes read | ||||||
|  |      * @throws IOException if an error occurred during reading | ||||||
|  |      */ | ||||||
|  |     int read(final byte[] dest, int length, final int timeout) throws IOException; | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Writes as many bytes as possible from the source buffer. |      * Writes as many bytes as possible from the source buffer. | ||||||
|      * |      * | ||||||
| @ -133,6 +153,18 @@ public interface UsbSerialPort extends Closeable { | |||||||
|      */ |      */ | ||||||
|     void write(final byte[] src, final int timeout) throws IOException; |     void write(final byte[] src, final int timeout) throws IOException; | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Writes bytes with specified length from the source buffer. | ||||||
|  |      * | ||||||
|  |      * @param src the source byte buffer | ||||||
|  |      * @param length the length of the data to write | ||||||
|  |      * @param timeout the timeout for writing in milliseconds, 0 is infinite | ||||||
|  |      * @throws SerialTimeoutException if timeout reached before sending all data. | ||||||
|  |      *                                ex.bytesTransferred may contain bytes transferred | ||||||
|  |      * @throws IOException if an error occurred during writing | ||||||
|  |      */ | ||||||
|  |     void write(final byte[] src, int length, final int timeout) throws IOException; | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Sets various serial port parameters. |      * Sets various serial port parameters. | ||||||
|      * |      * | ||||||
| @ -143,7 +175,7 @@ public interface UsbSerialPort extends Closeable { | |||||||
|      * @param parity one of {@link #PARITY_NONE}, {@link #PARITY_ODD}, |      * @param parity one of {@link #PARITY_NONE}, {@link #PARITY_ODD}, | ||||||
|      *               {@link #PARITY_EVEN}, {@link #PARITY_MARK}, or {@link #PARITY_SPACE}. |      *               {@link #PARITY_EVEN}, {@link #PARITY_MARK}, or {@link #PARITY_SPACE}. | ||||||
|      * @throws IOException on error setting the port parameters |      * @throws IOException on error setting the port parameters | ||||||
|      * @throws UnsupportedOperationException if values are not supported by a specific device |      * @throws UnsupportedOperationException if not supported or values are not supported by a specific device | ||||||
|      */ |      */ | ||||||
|     void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException; |     void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException; | ||||||
| 
 | 
 | ||||||
| @ -225,6 +257,7 @@ public interface UsbSerialPort extends Closeable { | |||||||
|      * |      * | ||||||
|      * @return EnumSet.contains(...) is {@code true} if set, else {@code false} |      * @return EnumSet.contains(...) is {@code true} if set, else {@code false} | ||||||
|      * @throws IOException if an error occurred during reading |      * @throws IOException if an error occurred during reading | ||||||
|  |      * @throws UnsupportedOperationException if not supported | ||||||
|      */ |      */ | ||||||
|     EnumSet<ControlLine> getControlLines() throws IOException; |     EnumSet<ControlLine> getControlLines() throws IOException; | ||||||
| 
 | 
 | ||||||
| @ -236,6 +269,36 @@ public interface UsbSerialPort extends Closeable { | |||||||
|      */ |      */ | ||||||
|     EnumSet<ControlLine> getSupportedControlLines() throws IOException; |     EnumSet<ControlLine> getSupportedControlLines() throws IOException; | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Set flow control mode, if supported | ||||||
|  |      * @param flowControl @FlowControl | ||||||
|  |      * @throws IOException if an error occurred during writing | ||||||
|  |      * @throws UnsupportedOperationException if not supported | ||||||
|  |      */ | ||||||
|  |     void setFlowControl(FlowControl flowControl) throws IOException; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get flow control mode. | ||||||
|  |      * @return FlowControl | ||||||
|  |      */ | ||||||
|  |     FlowControl getFlowControl(); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get supported flow control modes | ||||||
|  |      * @return EnumSet.contains(...) is {@code true} if supported, else {@code false} | ||||||
|  |      */ | ||||||
|  |     EnumSet<FlowControl> getSupportedFlowControl(); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * If flow control = XON_XOFF, indicates that send is enabled by XON. | ||||||
|  |      * Devices supporting flow control = XON_XOFF_INLINE return CHAR_XON/CHAR_XOFF in read() data. | ||||||
|  |      * | ||||||
|  |      * @return the current state | ||||||
|  |      * @throws IOException if an error occurred during reading | ||||||
|  |      * @throws UnsupportedOperationException if not supported | ||||||
|  |      */ | ||||||
|  |     boolean getXON() throws IOException; | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Purge non-transmitted output data and / or non-read input data. |      * Purge non-transmitted output data and / or non-read input data. | ||||||
|      * |      * | ||||||
|  | |||||||
| @ -37,6 +37,8 @@ public class UsbSerialProber { | |||||||
|         probeTable.addDriver(FtdiSerialDriver.class); |         probeTable.addDriver(FtdiSerialDriver.class); | ||||||
|         probeTable.addDriver(ProlificSerialDriver.class); |         probeTable.addDriver(ProlificSerialDriver.class); | ||||||
|         probeTable.addDriver(Ch34xSerialDriver.class); |         probeTable.addDriver(Ch34xSerialDriver.class); | ||||||
|  |         probeTable.addDriver(GsmModemSerialDriver.class); | ||||||
|  |         probeTable.addDriver(ChromeCcdSerialDriver.class); | ||||||
|         return probeTable; |         return probeTable; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -69,11 +71,7 @@ public class UsbSerialProber { | |||||||
|      *         {@code null} if none available. |      *         {@code null} if none available. | ||||||
|      */ |      */ | ||||||
|     public UsbSerialDriver probeDevice(final UsbDevice usbDevice) { |     public UsbSerialDriver probeDevice(final UsbDevice usbDevice) { | ||||||
|         final int vendorId = usbDevice.getVendorId(); |         final Class<? extends UsbSerialDriver> driverClass = mProbeTable.findDriver(usbDevice); | ||||||
|         final int productId = usbDevice.getProductId(); |  | ||||||
| 
 |  | ||||||
|         final Class<? extends UsbSerialDriver> driverClass = |  | ||||||
|                 mProbeTable.findDriver(vendorId, productId); |  | ||||||
|         if (driverClass != null) { |         if (driverClass != null) { | ||||||
|             final UsbSerialDriver driver; |             final UsbSerialDriver driver; | ||||||
|             try { |             try { | ||||||
|  | |||||||
| @ -19,14 +19,17 @@ package com.hoho.android.usbserial.util; | |||||||
| import java.security.InvalidParameterException; | import java.security.InvalidParameterException; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Clone of Android's HexDump class, for use in debugging. Cosmetic changes |  * Clone of Android's /core/java/com/android/internal/util/HexDump class, for use in debugging. | ||||||
|  * only. |  * Changes: space separated hex strings | ||||||
|  */ |  */ | ||||||
| public class HexDump { | public class HexDump { | ||||||
|     private final static char[] HEX_DIGITS = { |     private final static char[] HEX_DIGITS = { | ||||||
|             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' |             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|  |     private HexDump() { | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public static String dumpHexString(byte[] array) { |     public static String dumpHexString(byte[] array) { | ||||||
|         return dumpHexString(array, 0, array.length); |         return dumpHexString(array, 0, array.length); | ||||||
|     } |     } | ||||||
| @ -82,10 +85,12 @@ public class HexDump { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static String toHexString(byte[] array, int offset, int length) { |     public static String toHexString(byte[] array, int offset, int length) { | ||||||
|         char[] buf = new char[length * 2]; |         char[] buf = new char[length > 0 ? length * 3 - 1 : 0]; | ||||||
| 
 | 
 | ||||||
|         int bufIndex = 0; |         int bufIndex = 0; | ||||||
|         for (int i = offset; i < offset + length; i++) { |         for (int i = offset; i < offset + length; i++) { | ||||||
|  |             if (i > offset) | ||||||
|  |                 buf[bufIndex++] = ' '; | ||||||
|             byte b = array[i]; |             byte b = array[i]; | ||||||
|             buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F]; |             buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F]; | ||||||
|             buf[bufIndex++] = HEX_DIGITS[b & 0x0F]; |             buf[bufIndex++] = HEX_DIGITS[b & 0x0F]; | ||||||
| @ -139,13 +144,13 @@ public class HexDump { | |||||||
|         throw new InvalidParameterException("Invalid hex char '" + c + "'"); |         throw new InvalidParameterException("Invalid hex char '" + c + "'"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** accepts any separator, e.g. space or newline */ | ||||||
|     public static byte[] hexStringToByteArray(String hexString) { |     public static byte[] hexStringToByteArray(String hexString) { | ||||||
|         int length = hexString.length(); |         int length = hexString.length(); | ||||||
|         byte[] buffer = new byte[length / 2]; |         byte[] buffer = new byte[(length + 1) / 3]; | ||||||
| 
 | 
 | ||||||
|         for (int i = 0; i < length; i += 2) { |         for (int i = 0; i < length; i += 3) { | ||||||
|             buffer[i / 2] = (byte) ((toByte(hexString.charAt(i)) << 4) | toByte(hexString |             buffer[i / 3] = (byte) ((toByte(hexString.charAt(i)) << 4) | toByte(hexString.charAt(i + 1))); | ||||||
|                     .charAt(i + 1))); |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return buffer; |         return buffer; | ||||||
| @ -13,16 +13,19 @@ import com.hoho.android.usbserial.driver.UsbSerialPort; | |||||||
| 
 | 
 | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.nio.ByteBuffer; | import java.nio.ByteBuffer; | ||||||
|  | import java.util.concurrent.CountDownLatch; | ||||||
|  | import java.util.concurrent.atomic.AtomicReference; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Utility class which services a {@link UsbSerialPort} in its {@link #run()} method. |  * Utility class which services a {@link UsbSerialPort} in its {@link #runWrite()} ()} and {@link #runRead()} ()} ()} methods. | ||||||
|  * |  * | ||||||
|  * @author mike wakerly (opensource@hoho.com) |  * @author mike wakerly (opensource@hoho.com) | ||||||
|  */ |  */ | ||||||
| public class SerialInputOutputManager implements Runnable { | public class SerialInputOutputManager { | ||||||
| 
 | 
 | ||||||
|     public enum State { |     public enum State { | ||||||
|         STOPPED, |         STOPPED, | ||||||
|  |         STARTING, | ||||||
|         RUNNING, |         RUNNING, | ||||||
|         STOPPING |         STOPPING | ||||||
|     } |     } | ||||||
| @ -32,9 +35,6 @@ public class SerialInputOutputManager implements Runnable { | |||||||
|     private static final String TAG = SerialInputOutputManager.class.getSimpleName(); |     private static final String TAG = SerialInputOutputManager.class.getSimpleName(); | ||||||
|     private static final int BUFSIZ = 4096; |     private static final int BUFSIZ = 4096; | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * default read timeout is infinite, to avoid data loss with bulkTransfer API |  | ||||||
|      */ |  | ||||||
|     private int mReadTimeout = 0; |     private int mReadTimeout = 0; | ||||||
|     private int mWriteTimeout = 0; |     private int mWriteTimeout = 0; | ||||||
| 
 | 
 | ||||||
| @ -45,7 +45,8 @@ public class SerialInputOutputManager implements Runnable { | |||||||
|     private ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ); |     private ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ); | ||||||
| 
 | 
 | ||||||
|     private int mThreadPriority = Process.THREAD_PRIORITY_URGENT_AUDIO; |     private int mThreadPriority = Process.THREAD_PRIORITY_URGENT_AUDIO; | ||||||
|     private State mState = State.STOPPED; // Synchronized by 'this' |     private final AtomicReference<State> mState = new AtomicReference<>(State.STOPPED); | ||||||
|  |     private CountDownLatch mStartuplatch = new CountDownLatch(2); | ||||||
|     private Listener mListener; // Synchronized by 'this' |     private Listener mListener; // Synchronized by 'this' | ||||||
|     private final UsbSerialPort mSerialPort; |     private final UsbSerialPort mSerialPort; | ||||||
| 
 | 
 | ||||||
| @ -56,7 +57,7 @@ public class SerialInputOutputManager implements Runnable { | |||||||
|         void onNewData(byte[] data); |         void onNewData(byte[] data); | ||||||
| 
 | 
 | ||||||
|         /** |         /** | ||||||
|          * Called when {@link SerialInputOutputManager#run()} aborts due to an error. |          * Called when {@link SerialInputOutputManager#runRead()} ()} or {@link SerialInputOutputManager#runWrite()} ()} ()} aborts due to an error. | ||||||
|          */ |          */ | ||||||
|         void onRunError(Exception e); |         void onRunError(Exception e); | ||||||
|     } |     } | ||||||
| @ -86,8 +87,9 @@ public class SerialInputOutputManager implements Runnable { | |||||||
|      * @param threadPriority  see {@link Process#setThreadPriority(int)} |      * @param threadPriority  see {@link Process#setThreadPriority(int)} | ||||||
|      * */ |      * */ | ||||||
|     public void setThreadPriority(int threadPriority) { |     public void setThreadPriority(int threadPriority) { | ||||||
|         if (mState != State.STOPPED) |         if (!mState.compareAndSet(State.STOPPED, State.STOPPED)) { | ||||||
|             throw new IllegalStateException("threadPriority only configurable before SerialInputOutputManager is started"); |             throw new IllegalStateException("threadPriority only configurable before SerialInputOutputManager is started"); | ||||||
|  |         } | ||||||
|         mThreadPriority = threadPriority; |         mThreadPriority = threadPriority; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -96,7 +98,7 @@ public class SerialInputOutputManager implements Runnable { | |||||||
|      */ |      */ | ||||||
|     public void setReadTimeout(int timeout) { |     public void setReadTimeout(int timeout) { | ||||||
|         // when set if already running, read already blocks and the new value will not become effective now |         // when set if already running, read already blocks and the new value will not become effective now | ||||||
|         if(mReadTimeout == 0 && timeout != 0 && mState != State.STOPPED) |         if(mReadTimeout == 0 && timeout != 0 && mState.get() != State.STOPPED) | ||||||
|             throw new IllegalStateException("readTimeout only configurable before SerialInputOutputManager is started"); |             throw new IllegalStateException("readTimeout only configurable before SerialInputOutputManager is started"); | ||||||
|         mReadTimeout = timeout; |         mReadTimeout = timeout; | ||||||
|     } |     } | ||||||
| @ -144,79 +146,152 @@ public class SerialInputOutputManager implements Runnable { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * when using writeAsync, it is recommended to use readTimeout != 0, |      * write data asynchronously | ||||||
|      * else the write will be delayed until read data is available |  | ||||||
|      */ |      */ | ||||||
|     public void writeAsync(byte[] data) { |     public void writeAsync(byte[] data) { | ||||||
|         synchronized (mWriteBufferLock) { |         synchronized (mWriteBufferLock) { | ||||||
|             mWriteBuffer.put(data); |             mWriteBuffer.put(data); | ||||||
|  |             mWriteBufferLock.notifyAll(); // Notify waiting threads | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * start SerialInputOutputManager in separate thread |      * start SerialInputOutputManager in separate threads | ||||||
|      */ |      */ | ||||||
|     public void start() { |     public void start() { | ||||||
|         if(mState != State.STOPPED) |         if(mState.compareAndSet(State.STOPPED, State.STARTING)) { | ||||||
|  |             mStartuplatch = new CountDownLatch(2); | ||||||
|  |             new Thread(this::runRead, this.getClass().getSimpleName() + "_read").start(); | ||||||
|  |             new Thread(this::runWrite, this.getClass().getSimpleName() + "_write").start(); | ||||||
|  |             try { | ||||||
|  |                 mStartuplatch.await(); | ||||||
|  |                 mState.set(State.RUNNING); | ||||||
|  |             } catch (InterruptedException e) { | ||||||
|  |                 Thread.currentThread().interrupt(); | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|             throw new IllegalStateException("already started"); |             throw new IllegalStateException("already started"); | ||||||
|         new Thread(this, this.getClass().getSimpleName()).start(); |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * stop SerialInputOutputManager thread |      * stop SerialInputOutputManager threads | ||||||
|      * |      * | ||||||
|      * when using readTimeout == 0 (default), additionally use usbSerialPort.close() to |      * when using readTimeout == 0 (default), additionally use usbSerialPort.close() to | ||||||
|      * interrupt blocking read |      * interrupt blocking read | ||||||
|      */ |      */ | ||||||
|     public synchronized void stop() { |     public void stop() { | ||||||
|         if (getState() == State.RUNNING) { |         if(mState.compareAndSet(State.RUNNING, State.STOPPING)) { | ||||||
|  |             synchronized (mWriteBufferLock) { | ||||||
|  |                 mWriteBufferLock.notifyAll(); // wake up write thread to check the stop condition | ||||||
|  |             } | ||||||
|             Log.i(TAG, "Stop requested"); |             Log.i(TAG, "Stop requested"); | ||||||
|             mState = State.STOPPING; |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public synchronized State getState() { |     public State getState() { | ||||||
|         return mState; |         return mState.get(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Continuously services the read and write buffers until {@link #stop()} is |      * @return true if the thread is still running | ||||||
|      * called, or until a driver exception is raised. |  | ||||||
|      */ |      */ | ||||||
|     @Override |     private boolean isStillRunning() { | ||||||
|     public void run() { |         State state = mState.get(); | ||||||
|         synchronized (this) { |         return ((state == State.RUNNING) || (state == State.STARTING)) | ||||||
|             if (getState() != State.STOPPED) { |             && !Thread.currentThread().isInterrupted(); | ||||||
|                 throw new IllegalStateException("Already running"); |     } | ||||||
|             } | 
 | ||||||
|             mState = State.RUNNING; |     /** | ||||||
|         } |      * Notify listener of an error | ||||||
|         Log.i(TAG, "Running ..."); |      * | ||||||
|         try { |      * @param e the exception | ||||||
|             if(mThreadPriority != Process.THREAD_PRIORITY_DEFAULT) |      */ | ||||||
|                 Process.setThreadPriority(mThreadPriority); |     private void notifyErrorListener(Throwable e) { | ||||||
|             while (true) { |         Listener listener = getListener(); | ||||||
|                 if (getState() != State.RUNNING) { |         if (listener != null) { | ||||||
|                     Log.i(TAG, "Stopping mState=" + getState()); |             try { | ||||||
|                     break; |                 listener.onRunError(e instanceof Exception ? (Exception) e : new Exception(e)); | ||||||
|                 } |             } catch (Throwable t) { | ||||||
|                 step(); |                 Log.w(TAG, "Exception in onRunError: " + t.getMessage(), t); | ||||||
|             } |  | ||||||
|         } catch (Exception e) { |  | ||||||
|             Log.w(TAG, "Run ending due to exception: " + e.getMessage(), e); |  | ||||||
|             final Listener listener = getListener(); |  | ||||||
|             if (listener != null) { |  | ||||||
|               listener.onRunError(e); |  | ||||||
|             } |  | ||||||
|         } finally { |  | ||||||
|             synchronized (this) { |  | ||||||
|                 mState = State.STOPPED; |  | ||||||
|                 Log.i(TAG, "Stopped"); |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void step() throws IOException { |     /** | ||||||
|  |      * Set the thread priority | ||||||
|  |      */ | ||||||
|  |     private void setThreadPriority() { | ||||||
|  |         if (mThreadPriority != Process.THREAD_PRIORITY_DEFAULT) { | ||||||
|  |             Process.setThreadPriority(mThreadPriority); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Continuously services the read buffers until {@link #stop()} is called, or until a driver exception is | ||||||
|  |      * raised. | ||||||
|  |      */ | ||||||
|  |     void runRead() { | ||||||
|  |         Log.i(TAG, "runRead running ..."); | ||||||
|  |         try { | ||||||
|  |             setThreadPriority(); | ||||||
|  |             mStartuplatch.countDown(); | ||||||
|  |             do { | ||||||
|  |                 stepRead(); | ||||||
|  |             } while (isStillRunning()); | ||||||
|  |             Log.i(TAG, "runRead: Stopping mState=" + getState()); | ||||||
|  |         } catch (Throwable e) { | ||||||
|  |             if (Thread.currentThread().isInterrupted()) { | ||||||
|  |                 Log.w(TAG, "runRead: interrupted"); | ||||||
|  |             } else if(mSerialPort.isOpen()) { | ||||||
|  |                 Log.w(TAG, "runRead ending due to exception: " + e.getMessage(), e); | ||||||
|  |             } else { | ||||||
|  |                 Log.i(TAG, "runRead: Socket closed"); | ||||||
|  |             } | ||||||
|  |             notifyErrorListener(e); | ||||||
|  |         } finally { | ||||||
|  |             if (mState.compareAndSet(State.RUNNING, State.STOPPING)) { | ||||||
|  |                 synchronized (mWriteBufferLock) { | ||||||
|  |                     mWriteBufferLock.notifyAll(); // wake up write thread to check the stop condition | ||||||
|  |                 } | ||||||
|  |             } else if (mState.compareAndSet(State.STOPPING, State.STOPPED)) { | ||||||
|  |                 Log.i(TAG, "runRead: Stopped mState=" + getState()); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Continuously services the write buffers until {@link #stop()} is called, or until a driver exception is | ||||||
|  |      * raised. | ||||||
|  |      */ | ||||||
|  |     void runWrite() { | ||||||
|  |         Log.i(TAG, "runWrite running ..."); | ||||||
|  |         try { | ||||||
|  |             setThreadPriority(); | ||||||
|  |             mStartuplatch.countDown(); | ||||||
|  |             do { | ||||||
|  |                 stepWrite(); | ||||||
|  |             } while (isStillRunning()); | ||||||
|  |             Log.i(TAG, "runWrite: Stopping mState=" + getState()); | ||||||
|  |         } catch (Throwable e) { | ||||||
|  |             if (Thread.currentThread().isInterrupted()) { | ||||||
|  |                 Log.w(TAG, "runWrite: interrupted"); | ||||||
|  |             } else if(mSerialPort.isOpen()) { | ||||||
|  |                 Log.w(TAG, "runWrite ending due to exception: " + e.getMessage(), e); | ||||||
|  |             } else { | ||||||
|  |                 Log.i(TAG, "runWrite: Socket closed"); | ||||||
|  |             } | ||||||
|  |             notifyErrorListener(e); | ||||||
|  |         } finally { | ||||||
|  |             if (!mState.compareAndSet(State.RUNNING, State.STOPPING)) { | ||||||
|  |                 if (mState.compareAndSet(State.STOPPING, State.STOPPED)) { | ||||||
|  |                     Log.i(TAG, "runWrite: Stopped mState=" + getState()); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void stepRead() throws IOException { | ||||||
|         // Handle incoming data. |         // Handle incoming data. | ||||||
|         byte[] buffer; |         byte[] buffer; | ||||||
|         synchronized (mReadBufferLock) { |         synchronized (mReadBufferLock) { | ||||||
| @ -234,21 +309,26 @@ public class SerialInputOutputManager implements Runnable { | |||||||
|                 listener.onNewData(data); |                 listener.onNewData(data); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|  |     private void stepWrite() throws IOException, InterruptedException { | ||||||
|         // Handle outgoing data. |         // Handle outgoing data. | ||||||
|         buffer = null; |         byte[] buffer = null; | ||||||
|         synchronized (mWriteBufferLock) { |         synchronized (mWriteBufferLock) { | ||||||
|             len = mWriteBuffer.position(); |             int len = mWriteBuffer.position(); | ||||||
|             if (len > 0) { |             if (len > 0) { | ||||||
|                 buffer = new byte[len]; |                 buffer = new byte[len]; | ||||||
|                 mWriteBuffer.rewind(); |                 mWriteBuffer.rewind(); | ||||||
|                 mWriteBuffer.get(buffer, 0, len); |                 mWriteBuffer.get(buffer, 0, len); | ||||||
|                 mWriteBuffer.clear(); |                 mWriteBuffer.clear(); | ||||||
|  |                 mWriteBufferLock.notifyAll(); // Notify writeAsync that there is space in the buffer | ||||||
|  |             } else { | ||||||
|  |                 mWriteBufferLock.wait(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         if (buffer != null) { |         if (buffer != null) { | ||||||
|             if (DEBUG) { |             if (DEBUG) { | ||||||
|                 Log.d(TAG, "Writing data len=" + len); |                 Log.d(TAG, "Writing data len=" + buffer.length); | ||||||
|             } |             } | ||||||
|             mSerialPort.write(buffer, mWriteTimeout); |             mSerialPort.write(buffer, mWriteTimeout); | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -0,0 +1,33 @@ | |||||||
|  | package com.hoho.android.usbserial.util; | ||||||
|  | 
 | ||||||
|  | import android.hardware.usb.UsbDeviceConnection; | ||||||
|  | 
 | ||||||
|  | import java.util.ArrayList; | ||||||
|  | 
 | ||||||
|  | public class UsbUtils { | ||||||
|  | 
 | ||||||
|  |     private UsbUtils() { | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static  ArrayList<byte[]> getDescriptors(UsbDeviceConnection connection) { | ||||||
|  |         ArrayList<byte[]> descriptors = new ArrayList<>(); | ||||||
|  |         byte[] rawDescriptors = connection.getRawDescriptors(); | ||||||
|  |         if (rawDescriptors != null) { | ||||||
|  |             int pos = 0; | ||||||
|  |             while (pos < rawDescriptors.length) { | ||||||
|  |                 int len = rawDescriptors[pos] & 0xFF; | ||||||
|  |                 if (len == 0) | ||||||
|  |                     break; | ||||||
|  |                 if (pos + len > rawDescriptors.length) | ||||||
|  |                     len = rawDescriptors.length - pos; | ||||||
|  |                 byte[] descriptor = new byte[len]; | ||||||
|  |                 System.arraycopy(rawDescriptors, pos, descriptor, 0, len); | ||||||
|  |                 descriptors.add(descriptor); | ||||||
|  |                 pos += len; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return descriptors; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -0,0 +1,47 @@ | |||||||
|  | package com.hoho.android.usbserial.util; | ||||||
|  | 
 | ||||||
|  | import com.hoho.android.usbserial.driver.UsbSerialPort; | ||||||
|  | 
 | ||||||
|  | import java.io.IOException; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Some devices return XON and XOFF characters inline in read() data. | ||||||
|  |  * Other devices return XON / XOFF condition thru getXOFF() method. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | public class XonXoffFilter { | ||||||
|  |     private boolean xon = true; | ||||||
|  | 
 | ||||||
|  |     public XonXoffFilter() { | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean getXON()  { | ||||||
|  |         return xon; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Filter XON/XOFF from read() data and remember | ||||||
|  |      * | ||||||
|  |      * @param data unfiltered data | ||||||
|  |      * @return filtered data | ||||||
|  |      */ | ||||||
|  |     public byte[] filter(byte[] data) { | ||||||
|  |         int found = 0; | ||||||
|  |         for (int i=0; i<data.length; i++) { | ||||||
|  |             if (data[i] == UsbSerialPort.CHAR_XON || data[i] == UsbSerialPort.CHAR_XOFF) | ||||||
|  |                 found++; | ||||||
|  |         } | ||||||
|  |         if(found == 0) | ||||||
|  |             return data; | ||||||
|  |         byte[] filtered = new byte[data.length - found]; | ||||||
|  |         for (int i=0, j=0; i<data.length; i++) { | ||||||
|  |             if (data[i] == UsbSerialPort.CHAR_XON) | ||||||
|  |                 xon = true; | ||||||
|  |             else if(data[i] == UsbSerialPort.CHAR_XOFF) | ||||||
|  |                 xon = false; | ||||||
|  |             else | ||||||
|  |                 filtered[j++] = data[i]; | ||||||
|  |         } | ||||||
|  |         return filtered; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										29
									
								
								usbSerialForAndroid/src/test/java/android/util/Log.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								usbSerialForAndroid/src/test/java/android/util/Log.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | |||||||
|  | // without this class Mockito complains about non-mocked Log methods | ||||||
|  | package android.util; | ||||||
|  | 
 | ||||||
|  | public class Log { | ||||||
|  |     public static int d(String tag, String msg) { | ||||||
|  |         System.out.println("DEBUG: " + tag + ": " + msg); | ||||||
|  |         return 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static int i(String tag, String msg) { | ||||||
|  |         System.out.println("INFO: " + tag + ": " + msg); | ||||||
|  |         return 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static int w(String tag, String msg) { | ||||||
|  |         System.out.println("WARN: " + tag + ": " + msg); | ||||||
|  |         return 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static int w(String tag, String msg, Throwable tr) { | ||||||
|  |         System.out.println("WARN: " + tag + ": " + msg + " / " + tr.getMessage()); | ||||||
|  |         return 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static int e(String tag, String msg) { | ||||||
|  |         System.out.println("ERROR: " + tag + ": " + msg); | ||||||
|  |         return 0; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										85
									
								
								usbSerialForAndroid/src/test/java/android/util/Pair.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								usbSerialForAndroid/src/test/java/android/util/Pair.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,85 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (C) 2009 The Android Open Source Project | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *      http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package android.util; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | import androidx.annotation.Nullable; | ||||||
|  | 
 | ||||||
|  | import java.util.Objects; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Container to ease passing around a tuple of two objects. This object provides a sensible | ||||||
|  |  * implementation of equals(), returning true if equals() is true on each of the contained | ||||||
|  |  * objects. | ||||||
|  |  */ | ||||||
|  | public class Pair<F, S> { | ||||||
|  |     public final F first; | ||||||
|  |     public final S second; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Constructor for a Pair. | ||||||
|  |      * | ||||||
|  |      * @param first the first object in the Pair | ||||||
|  |      * @param second the second object in the pair | ||||||
|  |      */ | ||||||
|  |     public Pair(F first, S second) { | ||||||
|  |         this.first = first; | ||||||
|  |         this.second = second; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Checks the two objects for equality by delegating to their respective | ||||||
|  |      * {@link Object#equals(Object)} methods. | ||||||
|  |      * | ||||||
|  |      * @param o the {@link Pair} to which this one is to be checked for equality | ||||||
|  |      * @return true if the underlying objects of the Pair are both considered | ||||||
|  |      *         equal | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public boolean equals(@Nullable Object o) { | ||||||
|  |         if (!(o instanceof Pair)) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         Pair<?, ?> p = (Pair<?, ?>) o; | ||||||
|  |         return Objects.equals(p.first, first) && Objects.equals(p.second, second); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Compute a hash code using the hash codes of the underlying objects | ||||||
|  |      * | ||||||
|  |      * @return a hashcode of the Pair | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public int hashCode() { | ||||||
|  |         return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public String toString() { | ||||||
|  |         return "Pair{" + String.valueOf(first) + " " + String.valueOf(second) + "}"; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Convenience method for creating an appropriately typed pair. | ||||||
|  |      * @param a the first object in the Pair | ||||||
|  |      * @param b the second object in the pair | ||||||
|  |      * @return a Pair that is templatized with the types of a and b | ||||||
|  |      */ | ||||||
|  |     public static <A, B> Pair <A, B> create(A a, B b) { | ||||||
|  |         return new Pair<A, B>(a, b); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,546 @@ | |||||||
|  | package com.hoho.android.usbserial.driver; | ||||||
|  | 
 | ||||||
|  | import static com.hoho.android.usbserial.driver.CdcAcmSerialDriver.USB_SUBCLASS_ACM; | ||||||
|  | import static org.junit.Assert.assertEquals; | ||||||
|  | import static org.junit.Assert.assertNull; | ||||||
|  | import static org.junit.Assert.assertThrows; | ||||||
|  | import static org.mockito.Mockito.clearInvocations; | ||||||
|  | import static org.mockito.Mockito.mock; | ||||||
|  | import static org.mockito.Mockito.times; | ||||||
|  | import static org.mockito.Mockito.verify; | ||||||
|  | import static org.mockito.Mockito.when; | ||||||
|  | 
 | ||||||
|  | import android.hardware.usb.UsbConstants; | ||||||
|  | import android.hardware.usb.UsbDevice; | ||||||
|  | import android.hardware.usb.UsbDeviceConnection; | ||||||
|  | import android.hardware.usb.UsbEndpoint; | ||||||
|  | import android.hardware.usb.UsbInterface; | ||||||
|  | 
 | ||||||
|  | import com.hoho.android.usbserial.util.HexDump; | ||||||
|  | 
 | ||||||
|  | import org.junit.Test; | ||||||
|  | 
 | ||||||
|  | import java.io.IOException; | ||||||
|  | 
 | ||||||
|  | public class CdcAcmSerialDriverTest { | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void standardDevice() throws Exception { | ||||||
|  |         UsbDeviceConnection usbDeviceConnection = mock(UsbDeviceConnection.class); | ||||||
|  |         UsbDevice usbDevice = mock(UsbDevice.class); | ||||||
|  |         UsbInterface controlInterface = mock(UsbInterface.class); | ||||||
|  |         UsbInterface dataInterface = mock(UsbInterface.class); | ||||||
|  |         UsbEndpoint controlEndpoint = mock(UsbEndpoint.class); | ||||||
|  |         UsbEndpoint readEndpoint = mock(UsbEndpoint.class); | ||||||
|  |         UsbEndpoint writeEndpoint = mock(UsbEndpoint.class); | ||||||
|  | 
 | ||||||
|  |         /* | ||||||
|  |          * digispark - no IAD | ||||||
|  |          *   UsbInterface[mId=0,mAlternateSetting=0,mName=null,mClass=2,mSubclass=2,mProtocol=1,mEndpoints=[ | ||||||
|  |          *     UsbEndpoint[mAddress=131,mAttributes=3,mMaxPacketSize=8,mInterval=255]] | ||||||
|  |          *   UsbInterface[mId=1,mAlternateSetting=0,mName=null,mClass=10,mSubclass=0,mProtocol=0,mEndpoints=[ | ||||||
|  |          *     UsbEndpoint[mAddress=1,mAttributes=2,mMaxPacketSize=8,mInterval=0] | ||||||
|  |          *     UsbEndpoint[mAddress=129,mAttributes=2,mMaxPacketSize=8,mInterval=0]] | ||||||
|  |          */ | ||||||
|  |         when(usbDeviceConnection.getRawDescriptors()).thenReturn(HexDump.hexStringToByteArray( | ||||||
|  |                 "12 01 10 01 02 00 00 08 D0 16 7E 08 00 01 01 02 00 01\n" + | ||||||
|  |                 "09 02 43 00 02 01 00 80 32\n" + | ||||||
|  |                 "09 04 00 00 01 02 02 01 00\n" + | ||||||
|  |                 "05 24 00 10 01\n" + | ||||||
|  |                 "04 24 02 02\n" + | ||||||
|  |                 "05 24 06 00 01\n" + | ||||||
|  |                 "05 24 01 03 01\n" + | ||||||
|  |                 "07 05 83 03 08 00 FF\n" + | ||||||
|  |                 "09 04 01 00 02 0A 00 00 00\n" + | ||||||
|  |                 "07 05 01 02 08 00 00\n" + | ||||||
|  |                 "07 05 81 02 08 00 00")); | ||||||
|  |         when(usbDeviceConnection.claimInterface(controlInterface,true)).thenReturn(true); | ||||||
|  |         when(usbDeviceConnection.claimInterface(dataInterface,true)).thenReturn(true); | ||||||
|  |         when(usbDevice.getInterfaceCount()).thenReturn(2); | ||||||
|  |         when(usbDevice.getInterface(0)).thenReturn(controlInterface); | ||||||
|  |         when(usbDevice.getInterface(1)).thenReturn(dataInterface); | ||||||
|  |         when(controlInterface.getId()).thenReturn(0); | ||||||
|  |         when(controlInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_COMM); | ||||||
|  |         when(controlInterface.getInterfaceSubclass()).thenReturn(USB_SUBCLASS_ACM); | ||||||
|  |         when(controlInterface.getEndpointCount()).thenReturn(1); | ||||||
|  |         when(controlInterface.getEndpoint(0)).thenReturn(controlEndpoint); | ||||||
|  |         when(dataInterface.getId()).thenReturn(1); | ||||||
|  |         when(dataInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_CDC_DATA); | ||||||
|  |         when(dataInterface.getEndpointCount()).thenReturn(2); | ||||||
|  |         when(dataInterface.getEndpoint(0)).thenReturn(writeEndpoint); | ||||||
|  |         when(dataInterface.getEndpoint(1)).thenReturn(readEndpoint); | ||||||
|  |         when(controlEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||||
|  |         when(controlEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_INT); | ||||||
|  |         when(readEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||||
|  |         when(readEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||||
|  |         when(writeEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_OUT); | ||||||
|  |         when(writeEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||||
|  | 
 | ||||||
|  |         CdcAcmSerialDriver driver = new CdcAcmSerialDriver(usbDevice); | ||||||
|  |         CdcAcmSerialDriver.CdcAcmSerialPort port = (CdcAcmSerialDriver.CdcAcmSerialPort) driver.getPorts().get(0); | ||||||
|  |         port.mConnection = usbDeviceConnection; | ||||||
|  |         port.openInt(); | ||||||
|  |         assertEquals(readEndpoint, port.mReadEndpoint); | ||||||
|  |         assertEquals(writeEndpoint, port.mWriteEndpoint); | ||||||
|  | 
 | ||||||
|  |         ProbeTable probeTable = UsbSerialProber.getDefaultProbeTable(); | ||||||
|  |         Class<? extends UsbSerialDriver> probeDriver = probeTable.findDriver(usbDevice); | ||||||
|  |         assertEquals(driver.getClass(), probeDriver); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void singleInterfaceDevice() throws Exception { | ||||||
|  |         UsbDeviceConnection usbDeviceConnection = mock(UsbDeviceConnection.class); | ||||||
|  |         UsbDevice usbDevice = mock(UsbDevice.class); | ||||||
|  |         UsbInterface usbInterface = mock(UsbInterface.class); | ||||||
|  |         UsbEndpoint controlEndpoint = mock(UsbEndpoint.class); | ||||||
|  |         UsbEndpoint readEndpoint = mock(UsbEndpoint.class); | ||||||
|  |         UsbEndpoint writeEndpoint = mock(UsbEndpoint.class); | ||||||
|  | 
 | ||||||
|  |         when(usbDeviceConnection.claimInterface(usbInterface,true)).thenReturn(true); | ||||||
|  |         when(usbDevice.getInterfaceCount()).thenReturn(1); | ||||||
|  |         when(usbDevice.getInterface(0)).thenReturn(usbInterface); | ||||||
|  |         when(usbInterface.getEndpointCount()).thenReturn(3); | ||||||
|  |         when(usbInterface.getEndpoint(0)).thenReturn(controlEndpoint); | ||||||
|  |         when(usbInterface.getEndpoint(1)).thenReturn(readEndpoint); | ||||||
|  |         when(usbInterface.getEndpoint(2)).thenReturn(writeEndpoint); | ||||||
|  |         when(controlEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||||
|  |         when(controlEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_INT); | ||||||
|  |         when(readEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||||
|  |         when(readEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||||
|  |         when(writeEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_OUT); | ||||||
|  |         when(writeEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||||
|  | 
 | ||||||
|  |         CdcAcmSerialDriver driver = new CdcAcmSerialDriver(usbDevice); | ||||||
|  |         CdcAcmSerialDriver.CdcAcmSerialPort port = (CdcAcmSerialDriver.CdcAcmSerialPort) driver.getPorts().get(0); | ||||||
|  |         port.mConnection = usbDeviceConnection; | ||||||
|  |         port.openInt(); | ||||||
|  |         assertEquals(readEndpoint, port.mReadEndpoint); | ||||||
|  |         assertEquals(writeEndpoint, port.mWriteEndpoint); | ||||||
|  | 
 | ||||||
|  |         ProbeTable probeTable = UsbSerialProber.getDefaultProbeTable(); | ||||||
|  |         Class<? extends UsbSerialDriver> probeDriver = probeTable.findDriver(usbDevice); | ||||||
|  |         assertNull(probeDriver); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void multiPortDevice() throws Exception { | ||||||
|  |         int n = 2; | ||||||
|  |         UsbDeviceConnection usbDeviceConnection = mock(UsbDeviceConnection.class); | ||||||
|  |         UsbDevice usbDevice = mock(UsbDevice.class); | ||||||
|  |         UsbInterface[] controlInterfaces = new UsbInterface[n]; | ||||||
|  |         UsbInterface[] dataInterfaces = new UsbInterface[n]; | ||||||
|  |         UsbEndpoint[] controlEndpoints = new UsbEndpoint[n]; | ||||||
|  |         UsbEndpoint[] readEndpoints = new UsbEndpoint[n]; | ||||||
|  |         UsbEndpoint[] writeEndpoints = new UsbEndpoint[n]; | ||||||
|  | 
 | ||||||
|  |         /* | ||||||
|  |          * pi zero - dual port | ||||||
|  |          *   UsbInterface[mId=0,mAlternateSetting=0,mName=TinyUSB CDC,mClass=2,mSubclass=2,mProtocol=0,mEndpoints=[ | ||||||
|  |          *     UsbEndpoint[mAddress=129,mAttributes=3,mMaxPacketSize=8,mInterval=16]] | ||||||
|  |          *   UsbInterface[mId=1,mAlternateSetting=0,mName=null,mClass=10,mSubclass=0,mProtocol=0,mEndpoints=[ | ||||||
|  |          *     UsbEndpoint[mAddress=2,mAttributes=2,mMaxPacketSize=64,mInterval=0] | ||||||
|  |          *     UsbEndpoint[mAddress=130,mAttributes=2,mMaxPacketSize=64,mInterval=0]] | ||||||
|  |          *   UsbInterface[mId=2,mAlternateSetting=0,mName=TinyUSB CDC,mClass=2,mSubclass=2,mProtocol=0,mEndpoints=[ | ||||||
|  |          *     UsbEndpoint[mAddress=131,mAttributes=3,mMaxPacketSize=8,mInterval=16]] | ||||||
|  |          *   UsbInterface[mId=3,mAlternateSetting=0,mName=null,mClass=10,mSubclass=0,mProtocol=0,mEndpoints=[ | ||||||
|  |          *     UsbEndpoint[mAddress=4,mAttributes=2,mMaxPacketSize=64,mInterval=0] | ||||||
|  |          *      UsbEndpoint[mAddress=132,mAttributes=2,mMaxPacketSize=64,mInterval=0]] | ||||||
|  |          */ | ||||||
|  |         when(usbDeviceConnection.getRawDescriptors()).thenReturn(HexDump.hexStringToByteArray( | ||||||
|  |                 "12 01 00 02 EF 02 01 40 FE CA 02 40 00 01 01 02 03 01\n" + | ||||||
|  |                 "09 02 8D 00 04 01 00 80 32\n" + | ||||||
|  |                 "08 0B 00 02 02 02 00 00\n" + | ||||||
|  |                 "09 04 00 00 01 02 02 00 04\n" + | ||||||
|  |                 "05 24 00 20 01\n" + | ||||||
|  |                 "05 24 01 00 01\n" + | ||||||
|  |                 "04 24 02 02\n" + | ||||||
|  |                 "05 24 06 00 01\n" + | ||||||
|  |                 "07 05 81 03 08 00 10\n" + | ||||||
|  |                 "09 04 01 00 02 0A 00 00 00\n" + | ||||||
|  |                 "07 05 02 02 40 00 00\n" + | ||||||
|  |                 "07 05 82 02 40 00 00\n" + | ||||||
|  |                 "08 0B 02 02 02 02 00 00\n" + | ||||||
|  |                 "09 04 02 00 01 02 02 00 04\n" + | ||||||
|  |                 "05 24 00 20 01\n" + | ||||||
|  |                 "05 24 01 00 03\n" + | ||||||
|  |                 "04 24 02 02\n" + | ||||||
|  |                 "05 24 06 02 03\n" + | ||||||
|  |                 "07 05 83 03 08 00 10\n" + | ||||||
|  |                 "09 04 03 00 02 0A 00 00 00\n" + | ||||||
|  |                 "07 05 04 02 40 00 00\n" + | ||||||
|  |                 "07 05 84 02 40 00 00\n")); | ||||||
|  |         when(usbDevice.getInterfaceCount()).thenReturn(2*n); | ||||||
|  |         for(int i=0; i<n; i++) { | ||||||
|  |             controlInterfaces[i] = mock(UsbInterface.class); | ||||||
|  |             dataInterfaces[i] = mock(UsbInterface.class); | ||||||
|  |             controlEndpoints[i] = mock(UsbEndpoint.class); | ||||||
|  |             readEndpoints[i] = mock(UsbEndpoint.class); | ||||||
|  |             writeEndpoints[i] = mock(UsbEndpoint.class); | ||||||
|  |             when(usbDeviceConnection.claimInterface(controlInterfaces[i], true)).thenReturn(true); | ||||||
|  |             when(usbDeviceConnection.claimInterface(dataInterfaces[i], true)).thenReturn(true); | ||||||
|  |             when(usbDevice.getInterface(2*i  )).thenReturn(controlInterfaces[i]); | ||||||
|  |             when(usbDevice.getInterface(2*i+1)).thenReturn(dataInterfaces[i]); | ||||||
|  |             when(controlInterfaces[i].getId()).thenReturn(2*i); | ||||||
|  |             when(controlInterfaces[i].getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_COMM); | ||||||
|  |             when(controlInterfaces[i].getInterfaceSubclass()).thenReturn(USB_SUBCLASS_ACM); | ||||||
|  |             when(controlInterfaces[i].getEndpointCount()).thenReturn(1); | ||||||
|  |             when(controlInterfaces[i].getEndpoint(0)).thenReturn(controlEndpoints[i]); | ||||||
|  |             when(dataInterfaces[i].getId()).thenReturn(2*i+1); | ||||||
|  |             when(dataInterfaces[i].getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_CDC_DATA); | ||||||
|  |             when(dataInterfaces[i].getEndpointCount()).thenReturn(2); | ||||||
|  |             when(dataInterfaces[i].getEndpoint(0)).thenReturn(writeEndpoints[i]); | ||||||
|  |             when(dataInterfaces[i].getEndpoint(1)).thenReturn(readEndpoints[i]); | ||||||
|  |             when(controlEndpoints[i].getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||||
|  |             when(controlEndpoints[i].getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_INT); | ||||||
|  |             when(readEndpoints[i].getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||||
|  |             when(readEndpoints[i].getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||||
|  |             when(writeEndpoints[i].getDirection()).thenReturn(UsbConstants.USB_DIR_OUT); | ||||||
|  |             when(writeEndpoints[i].getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||||
|  |         } | ||||||
|  |         int i = 1; | ||||||
|  |         CdcAcmSerialDriver driver = new CdcAcmSerialDriver(usbDevice); | ||||||
|  |         CdcAcmSerialDriver.CdcAcmSerialPort port = (CdcAcmSerialDriver.CdcAcmSerialPort) driver.getPorts().get(i); | ||||||
|  |         port.mConnection = usbDeviceConnection; | ||||||
|  |         // reset invocations from countPorts() | ||||||
|  |         clearInvocations(controlInterfaces[0]); | ||||||
|  |         clearInvocations(controlInterfaces[1]); | ||||||
|  | 
 | ||||||
|  |         port.openInt(); | ||||||
|  |         assertEquals(readEndpoints[i], port.mReadEndpoint); | ||||||
|  |         assertEquals(writeEndpoints[i], port.mWriteEndpoint); | ||||||
|  |         verify(controlInterfaces[0], times(0)).getInterfaceClass(); // not openInterface with 'no IAD fallback' | ||||||
|  |         verify(controlInterfaces[1], times(2)).getInterfaceClass(); // openInterface with IAD | ||||||
|  |         port.closeInt(); | ||||||
|  |         clearInvocations(controlInterfaces[0]); | ||||||
|  |         clearInvocations(controlInterfaces[1]); | ||||||
|  | 
 | ||||||
|  |         when(usbDeviceConnection.getRawDescriptors()).thenReturn(null); | ||||||
|  |         port.openInt(); | ||||||
|  |         verify(controlInterfaces[0], times(2)).getInterfaceClass(); // openInterface with 'no IAD fallback' | ||||||
|  |         verify(controlInterfaces[1], times(2)).getInterfaceClass(); // openInterface with 'no IAD fallback' | ||||||
|  |         port.closeInt(); | ||||||
|  |         clearInvocations(controlInterfaces[0]); | ||||||
|  |         clearInvocations(controlInterfaces[1]); | ||||||
|  | 
 | ||||||
|  |         when(usbDeviceConnection.getRawDescriptors()).thenReturn(HexDump.hexStringToByteArray("01 02 02 82 02")); // truncated descriptor | ||||||
|  |         port.openInt(); | ||||||
|  |         verify(controlInterfaces[0], times(2)).getInterfaceClass(); // openInterface with 'no IAD fallback' | ||||||
|  |         verify(controlInterfaces[1], times(2)).getInterfaceClass(); // openInterface with 'no IAD fallback' | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void compositeDevice() throws Exception { | ||||||
|  |         UsbDeviceConnection usbDeviceConnection = mock(UsbDeviceConnection.class); | ||||||
|  |         UsbDevice usbDevice = mock(UsbDevice.class); | ||||||
|  |         UsbInterface massStorageInterface = mock(UsbInterface.class); | ||||||
|  |         UsbInterface controlInterface = mock(UsbInterface.class); | ||||||
|  |         UsbInterface dataInterface = mock(UsbInterface.class); | ||||||
|  |         UsbInterface hidInterface = mock(UsbInterface.class); | ||||||
|  |         UsbInterface vendorInterface = mock(UsbInterface.class); | ||||||
|  |         UsbEndpoint controlEndpoint = mock(UsbEndpoint.class); | ||||||
|  |         UsbEndpoint readEndpoint = mock(UsbEndpoint.class); | ||||||
|  |         UsbEndpoint writeEndpoint = mock(UsbEndpoint.class); | ||||||
|  | 
 | ||||||
|  |         /* | ||||||
|  |          * BBC micro:bit | ||||||
|  |          *   UsbInterface[mId=0,mAlternateSetting=0,mName=USB_MSC,mClass=8,mSubclass=6,mProtocol=80,mEndpoints=[ | ||||||
|  |          *     UsbEndpoint[mAddress=130,mAttributes=2,mMaxPacketSize=64,mInterval=0] | ||||||
|  |          *     UsbEndpoint[mAddress=2,mAttributes=2,mMaxPacketSize=64,mInterval=0]] | ||||||
|  |          *   UsbInterface[mId=1,mAlternateSetting=0,mName=mbed Serial Port,mClass=2,mSubclass=2,mProtocol=1,mEndpoints=[ | ||||||
|  |          *     UsbEndpoint[mAddress=131,mAttributes=3,mMaxPacketSize=16,mInterval=32]] | ||||||
|  |          *   UsbInterface[mId=2,mAlternateSetting=0,mName=mbed Serial Port,mClass=10,mSubclass=0,mProtocol=0,mEndpoints=[ | ||||||
|  |          *     UsbEndpoint[mAddress=4,mAttributes=2,mMaxPacketSize=64,mInterval=0] | ||||||
|  |          *     UsbEndpoint[mAddress=132,mAttributes=2,mMaxPacketSize=64,mInterval=0]] | ||||||
|  |          *   UsbInterface[mId=3,mAlternateSetting=0,mName=CMSIS-DAP,mClass=3,mSubclass=0,mProtocol=0,mEndpoints=[ | ||||||
|  |          *     UsbEndpoint[mAddress=129,mAttributes=3,mMaxPacketSize=64,mInterval=1] | ||||||
|  |          *     UsbEndpoint[mAddress=1,mAttributes=3,mMaxPacketSize=64,mInterval=1]] | ||||||
|  |          *   UsbInterface[mId=4,mAlternateSetting=0,mName=WebUSB: CMSIS-DAP,mClass=255,mSubclass=3,mProtocol=0,mEndpoints=[] | ||||||
|  |          */ | ||||||
|  |         when(usbDeviceConnection.getRawDescriptors()).thenReturn(HexDump.hexStringToByteArray( | ||||||
|  |                 "12 01 10 02 EF 02 01 40 28 0D 04 02 00 10 01 02 03 01\n" + | ||||||
|  |                 "09 02 8B 00 05 01 00 80 FA\n" + | ||||||
|  |                 "09 04 00 00 02 08 06 50 08\n" + | ||||||
|  |                 "07 05 82 02 40 00 00\n" + | ||||||
|  |                 "07 05 02 02 40 00 00\n" + | ||||||
|  |                 "08 0B 01 02 02 02 01 04\n" + | ||||||
|  |                 "09 04 01 00 01 02 02 01 04\n" + | ||||||
|  |                 "05 24 00 10 01\n" + | ||||||
|  |                 "05 24 01 03 02\n" + | ||||||
|  |                 "04 24 02 06\n" + | ||||||
|  |                 "05 24 06 01 02\n" + | ||||||
|  |                 "07 05 83 03 10 00 20\n" + | ||||||
|  |                 "09 04 02 00 02 0A 00 00 05\n" + | ||||||
|  |                 "07 05 04 02 40 00 00\n" + | ||||||
|  |                 "07 05 84 02 40 00 00\n" + | ||||||
|  |                 "09 04 03 00 02 03 00 00 06\n" + | ||||||
|  |                 "09 21 00 01 00 01 22 21 00\n" + | ||||||
|  |                 "07 05 81 03 40 00 01\n" + | ||||||
|  |                 "07 05 01 03 40 00 01\n" + | ||||||
|  |                 "09 04 04 00 00 FF 03 00 07")); | ||||||
|  |         when(usbDeviceConnection.claimInterface(controlInterface,true)).thenReturn(true); | ||||||
|  |         when(usbDeviceConnection.claimInterface(dataInterface,true)).thenReturn(true); | ||||||
|  |         when(usbDevice.getInterfaceCount()).thenReturn(5); | ||||||
|  |         when(usbDevice.getInterface(0)).thenReturn(massStorageInterface); | ||||||
|  |         when(usbDevice.getInterface(1)).thenReturn(controlInterface); | ||||||
|  |         when(usbDevice.getInterface(2)).thenReturn(dataInterface); | ||||||
|  |         when(usbDevice.getInterface(3)).thenReturn(hidInterface); | ||||||
|  |         when(usbDevice.getInterface(4)).thenReturn(vendorInterface); | ||||||
|  |         when(massStorageInterface.getId()).thenReturn(0); | ||||||
|  |         when(massStorageInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_MASS_STORAGE); | ||||||
|  |         when(controlInterface.getId()).thenReturn(1); | ||||||
|  |         when(controlInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_COMM); | ||||||
|  |         when(controlInterface.getInterfaceSubclass()).thenReturn(USB_SUBCLASS_ACM); | ||||||
|  |         when(dataInterface.getId()).thenReturn(2); | ||||||
|  |         when(dataInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_CDC_DATA); | ||||||
|  |         when(hidInterface.getId()).thenReturn(3); | ||||||
|  |         when(hidInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_HID); | ||||||
|  |         when(vendorInterface.getId()).thenReturn(4); | ||||||
|  |         when(vendorInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_VENDOR_SPEC); | ||||||
|  | 
 | ||||||
|  |         when(controlInterface.getEndpointCount()).thenReturn(1); | ||||||
|  |         when(controlInterface.getEndpoint(0)).thenReturn(controlEndpoint); | ||||||
|  |         when(dataInterface.getEndpointCount()).thenReturn(2); | ||||||
|  |         when(dataInterface.getEndpoint(0)).thenReturn(writeEndpoint); | ||||||
|  |         when(dataInterface.getEndpoint(1)).thenReturn(readEndpoint); | ||||||
|  |         when(controlEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||||
|  |         when(controlEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_INT); | ||||||
|  |         when(readEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||||
|  |         when(readEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||||
|  |         when(writeEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_OUT); | ||||||
|  |         when(writeEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||||
|  | 
 | ||||||
|  |         CdcAcmSerialDriver driver = new CdcAcmSerialDriver(usbDevice); | ||||||
|  |         CdcAcmSerialDriver.CdcAcmSerialPort port = (CdcAcmSerialDriver.CdcAcmSerialPort) driver.getPorts().get(0); | ||||||
|  |         port.mConnection = usbDeviceConnection; | ||||||
|  |         port.openInt(); | ||||||
|  |         assertEquals(readEndpoint, port.mReadEndpoint); | ||||||
|  |         assertEquals(writeEndpoint, port.mWriteEndpoint); | ||||||
|  | 
 | ||||||
|  |         ProbeTable probeTable = UsbSerialProber.getDefaultProbeTable(); | ||||||
|  |         Class<? extends UsbSerialDriver> probeDriver = probeTable.findDriver(usbDevice); | ||||||
|  |         assertEquals(driver.getClass(), probeDriver); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void compositeRndisDevice() throws Exception { | ||||||
|  |         UsbDeviceConnection usbDeviceConnection = mock(UsbDeviceConnection.class); | ||||||
|  |         UsbDevice usbDevice = mock(UsbDevice.class); | ||||||
|  |         UsbInterface rndisControlInterface = mock(UsbInterface.class); | ||||||
|  |         UsbInterface rndisDataInterface = mock(UsbInterface.class); | ||||||
|  |         UsbInterface controlInterface = mock(UsbInterface.class); | ||||||
|  |         UsbInterface dataInterface = mock(UsbInterface.class); | ||||||
|  |         UsbEndpoint controlEndpoint = mock(UsbEndpoint.class); | ||||||
|  |         UsbEndpoint readEndpoint = mock(UsbEndpoint.class); | ||||||
|  |         UsbEndpoint writeEndpoint = mock(UsbEndpoint.class); | ||||||
|  | 
 | ||||||
|  |         // has multiple USB_CLASS_CDC_DATA interfaces => get correct with IAD | ||||||
|  |         when(usbDeviceConnection.getRawDescriptors()).thenReturn(HexDump.hexStringToByteArray( | ||||||
|  |                 "12 01 00 02 EF 02 01 40 FE CA 02 40 00 01 01 02 03 01\n" + | ||||||
|  |                 "09 02 8D 00 04 01 00 80 32\n" + | ||||||
|  |                 "08 0B 00 02 E0 01 03 00\n" + | ||||||
|  |                 "09 04 00 00 01 E0 01 03 04\n" + | ||||||
|  |                 "05 24 00 10 01\n" + | ||||||
|  |                 "05 24 01 00 01\n" + | ||||||
|  |                 "04 24 02 00\n" + | ||||||
|  |                 "05 24 06 00 01\n" + | ||||||
|  |                 "07 05 81 03 08 00 01\n" + | ||||||
|  |                 "09 04 01 00 02 0A 00 00 00\n" + | ||||||
|  |                 "07 05 82 02 40 00 00\n" + | ||||||
|  |                 "07 05 02 02 40 00 00\n" + | ||||||
|  |                 "08 0B 02 02 02 02 00 00\n" + | ||||||
|  |                 "09 04 02 00 01 02 02 00 04\n" + | ||||||
|  |                 "05 24 00 20 01\n" + | ||||||
|  |                 "05 24 01 00 03\n" + | ||||||
|  |                 "04 24 02 02\n" + | ||||||
|  |                 "05 24 06 02 03\n" + | ||||||
|  |                 "07 05 83 03 08 00 10\n" + | ||||||
|  |                 "09 04 03 00 02 0A 00 00 00\n" + | ||||||
|  |                 "07 05 04 02 40 00 00\n" + | ||||||
|  |                 "07 05 84 02 40 00 00")); | ||||||
|  |         when(usbDeviceConnection.claimInterface(controlInterface,true)).thenReturn(true); | ||||||
|  |         when(usbDeviceConnection.claimInterface(dataInterface,true)).thenReturn(true); | ||||||
|  |         when(usbDevice.getInterfaceCount()).thenReturn(4); | ||||||
|  |         when(usbDevice.getInterface(0)).thenReturn(rndisControlInterface); | ||||||
|  |         when(usbDevice.getInterface(1)).thenReturn(rndisDataInterface); | ||||||
|  |         when(usbDevice.getInterface(2)).thenReturn(controlInterface); | ||||||
|  |         when(usbDevice.getInterface(3)).thenReturn(dataInterface); | ||||||
|  |         when(rndisControlInterface.getId()).thenReturn(0); | ||||||
|  |         when(rndisControlInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_WIRELESS_CONTROLLER); | ||||||
|  |         when(rndisControlInterface.getInterfaceSubclass()).thenReturn(1); | ||||||
|  |         when(rndisControlInterface.getInterfaceProtocol()).thenReturn(3); | ||||||
|  |         when(rndisDataInterface.getId()).thenReturn(1); | ||||||
|  |         when(rndisDataInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_CDC_DATA); | ||||||
|  |         when(controlInterface.getId()).thenReturn(2); | ||||||
|  |         when(controlInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_COMM); | ||||||
|  |         when(controlInterface.getInterfaceSubclass()).thenReturn(USB_SUBCLASS_ACM); | ||||||
|  |         when(dataInterface.getId()).thenReturn(3); | ||||||
|  |         when(dataInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_CDC_DATA); | ||||||
|  | 
 | ||||||
|  |         when(controlInterface.getEndpointCount()).thenReturn(1); | ||||||
|  |         when(controlInterface.getEndpoint(0)).thenReturn(controlEndpoint); | ||||||
|  |         when(dataInterface.getEndpointCount()).thenReturn(2); | ||||||
|  |         when(dataInterface.getEndpoint(0)).thenReturn(writeEndpoint); | ||||||
|  |         when(dataInterface.getEndpoint(1)).thenReturn(readEndpoint); | ||||||
|  |         when(controlEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||||
|  |         when(controlEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_INT); | ||||||
|  |         when(readEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||||
|  |         when(readEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||||
|  |         when(writeEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_OUT); | ||||||
|  |         when(writeEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||||
|  | 
 | ||||||
|  |         CdcAcmSerialDriver driver = new CdcAcmSerialDriver(usbDevice); | ||||||
|  |         CdcAcmSerialDriver.CdcAcmSerialPort port = (CdcAcmSerialDriver.CdcAcmSerialPort) driver.getPorts().get(0); | ||||||
|  |         port.mConnection = usbDeviceConnection; | ||||||
|  |         port.openInt(); | ||||||
|  |         assertEquals(readEndpoint, port.mReadEndpoint); | ||||||
|  |         assertEquals(writeEndpoint, port.mWriteEndpoint); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void compositeAlternateSettingDevice() throws Exception { | ||||||
|  |         UsbDeviceConnection usbDeviceConnection = mock(UsbDeviceConnection.class); | ||||||
|  |         UsbDevice usbDevice = mock(UsbDevice.class); | ||||||
|  |         UsbInterface ethernetControlInterface = mock(UsbInterface.class); | ||||||
|  |         UsbInterface ethernetDummyInterface = mock(UsbInterface.class); | ||||||
|  |         UsbInterface ethernetDataInterface = mock(UsbInterface.class); | ||||||
|  |         UsbInterface controlInterface = mock(UsbInterface.class); | ||||||
|  |         UsbInterface dataInterface = mock(UsbInterface.class); | ||||||
|  |         UsbEndpoint controlEndpoint = mock(UsbEndpoint.class); | ||||||
|  |         UsbEndpoint readEndpoint = mock(UsbEndpoint.class); | ||||||
|  |         UsbEndpoint writeEndpoint = mock(UsbEndpoint.class); | ||||||
|  | 
 | ||||||
|  |         // has multiple USB_CLASS_CDC_DATA interfaces => get correct with IAD | ||||||
|  |         when(usbDeviceConnection.getRawDescriptors()).thenReturn(HexDump.hexStringToByteArray( | ||||||
|  |                 "12 01 00 02 EF 02 01 40 FE CA 02 40 00 01 01 02 03 01\n" + | ||||||
|  |                 "09 02 9A 00 04 01 00 80 32\n" + | ||||||
|  |                 "08 0B 00 02 02 06 00 00\n" + | ||||||
|  |                 "09 04 00 00 01 02 06 00 04\n" + | ||||||
|  |                 "05 24 00 20 01\n" + | ||||||
|  |                 "05 24 06 00 01\n" + | ||||||
|  |                 "0D 24 0F 04 00 00 00 00 DC 05 00 00 00\n" + | ||||||
|  |                 "07 05 81 03 08 00 01\n" + | ||||||
|  |                 "09 04 01 00 00 0A 00 00 00\n" + | ||||||
|  |                 "09 04 01 01 02 0A 00 00 00\n" + | ||||||
|  |                 "07 05 82 02 40 00 00\n" + | ||||||
|  |                 "07 05 02 02 40 00 00\n" + | ||||||
|  |                 "08 0B 02 02 02 02 00 00\n" + | ||||||
|  |                 "09 04 02 00 01 02 02 00 04\n" + | ||||||
|  |                 "05 24 00 20 01\n" + | ||||||
|  |                 "05 24 01 00 03\n" + | ||||||
|  |                 "04 24 02 02\n" + | ||||||
|  |                 "05 24 06 02 03\n" + | ||||||
|  |                 "07 05 83 03 08 00 10\n" + | ||||||
|  |                 "09 04 03 00 02 0A 00 00 00\n" + | ||||||
|  |                 "07 05 04 02 40 00 00\n" + | ||||||
|  |                 "07 05 84 02 40 00 00")); | ||||||
|  |         when(usbDeviceConnection.claimInterface(controlInterface,true)).thenReturn(true); | ||||||
|  |         when(usbDeviceConnection.claimInterface(dataInterface,true)).thenReturn(true); | ||||||
|  |         when(usbDevice.getInterfaceCount()).thenReturn(5); | ||||||
|  |         when(usbDevice.getInterface(0)).thenReturn(ethernetControlInterface); | ||||||
|  |         when(usbDevice.getInterface(1)).thenReturn(ethernetDummyInterface); | ||||||
|  |         when(usbDevice.getInterface(2)).thenReturn(ethernetDataInterface); | ||||||
|  |         when(usbDevice.getInterface(3)).thenReturn(controlInterface); | ||||||
|  |         when(usbDevice.getInterface(4)).thenReturn(dataInterface); | ||||||
|  |         when(ethernetControlInterface.getId()).thenReturn(0); | ||||||
|  |         when(ethernetControlInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_COMM); | ||||||
|  |         when(ethernetControlInterface.getInterfaceSubclass()).thenReturn(6); | ||||||
|  |         when(ethernetDummyInterface.getId()).thenReturn(1); | ||||||
|  |         when(ethernetDummyInterface.getAlternateSetting()).thenReturn(0); | ||||||
|  |         when(ethernetDummyInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_CDC_DATA); | ||||||
|  |         when(ethernetDataInterface.getId()).thenReturn(1); | ||||||
|  |         when(ethernetDataInterface.getAlternateSetting()).thenReturn(1); | ||||||
|  |         when(ethernetDataInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_CDC_DATA); | ||||||
|  |         when(controlInterface.getId()).thenReturn(2); | ||||||
|  |         when(controlInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_COMM); | ||||||
|  |         when(controlInterface.getInterfaceSubclass()).thenReturn(USB_SUBCLASS_ACM); | ||||||
|  |         when(dataInterface.getId()).thenReturn(3); | ||||||
|  |         when(dataInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_CDC_DATA); | ||||||
|  | 
 | ||||||
|  |         when(controlInterface.getEndpointCount()).thenReturn(1); | ||||||
|  |         when(controlInterface.getEndpoint(0)).thenReturn(controlEndpoint); | ||||||
|  |         when(dataInterface.getEndpointCount()).thenReturn(2); | ||||||
|  |         when(dataInterface.getEndpoint(0)).thenReturn(writeEndpoint); | ||||||
|  |         when(dataInterface.getEndpoint(1)).thenReturn(readEndpoint); | ||||||
|  |         when(controlEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||||
|  |         when(controlEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_INT); | ||||||
|  |         when(readEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||||
|  |         when(readEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||||
|  |         when(writeEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_OUT); | ||||||
|  |         when(writeEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||||
|  | 
 | ||||||
|  |         CdcAcmSerialDriver driver = new CdcAcmSerialDriver(usbDevice); | ||||||
|  |         CdcAcmSerialDriver.CdcAcmSerialPort port = (CdcAcmSerialDriver.CdcAcmSerialPort) driver.getPorts().get(0); | ||||||
|  |         port.mConnection = usbDeviceConnection; | ||||||
|  |         port.openInt(); | ||||||
|  |         assertEquals(readEndpoint, port.mReadEndpoint); | ||||||
|  |         assertEquals(writeEndpoint, port.mWriteEndpoint); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void invalidStandardDevice() throws Exception { | ||||||
|  |         UsbDeviceConnection usbDeviceConnection = mock(UsbDeviceConnection.class); | ||||||
|  |         UsbDevice usbDevice = mock(UsbDevice.class); | ||||||
|  |         UsbInterface controlInterface = mock(UsbInterface.class); | ||||||
|  |         UsbInterface dataInterface = mock(UsbInterface.class); | ||||||
|  |         UsbEndpoint controlEndpoint = mock(UsbEndpoint.class); | ||||||
|  |         UsbEndpoint readEndpoint = mock(UsbEndpoint.class); | ||||||
|  |         UsbEndpoint writeEndpoint = mock(UsbEndpoint.class); | ||||||
|  | 
 | ||||||
|  |         when(usbDeviceConnection.claimInterface(controlInterface,true)).thenReturn(true); | ||||||
|  |         when(usbDeviceConnection.claimInterface(dataInterface,true)).thenReturn(true); | ||||||
|  |         when(usbDevice.getInterfaceCount()).thenReturn(2); | ||||||
|  |         when(usbDevice.getInterface(0)).thenReturn(controlInterface); | ||||||
|  |         when(usbDevice.getInterface(1)).thenReturn(dataInterface); | ||||||
|  |         when(controlInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_COMM); | ||||||
|  |         when(controlInterface.getInterfaceSubclass()).thenReturn(USB_SUBCLASS_ACM); | ||||||
|  |         when(controlInterface.getEndpointCount()).thenReturn(1); | ||||||
|  |         when(controlInterface.getEndpoint(0)).thenReturn(controlEndpoint); | ||||||
|  |         when(dataInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_CDC_DATA); | ||||||
|  |         when(dataInterface.getEndpointCount()).thenReturn(2); | ||||||
|  |         when(dataInterface.getEndpoint(0)).thenReturn(writeEndpoint); | ||||||
|  |         when(dataInterface.getEndpoint(1)).thenReturn(readEndpoint); | ||||||
|  |         //when(controlEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||||
|  |         //when(controlEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_INT); | ||||||
|  |         when(readEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||||
|  |         when(readEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||||
|  |         when(writeEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_OUT); | ||||||
|  |         when(writeEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||||
|  | 
 | ||||||
|  |         CdcAcmSerialDriver driver = new CdcAcmSerialDriver(usbDevice); | ||||||
|  |         CdcAcmSerialDriver.CdcAcmSerialPort port = (CdcAcmSerialDriver.CdcAcmSerialPort) driver.getPorts().get(0); | ||||||
|  |         port.mConnection = usbDeviceConnection; | ||||||
|  |         assertThrows(IOException.class, port::openInt); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void invalidSingleInterfaceDevice() throws Exception { | ||||||
|  |         UsbDeviceConnection usbDeviceConnection = mock(UsbDeviceConnection.class); | ||||||
|  |         UsbDevice usbDevice = mock(UsbDevice.class); | ||||||
|  |         UsbInterface usbInterface = mock(UsbInterface.class); | ||||||
|  |         UsbEndpoint controlEndpoint = mock(UsbEndpoint.class); | ||||||
|  |         UsbEndpoint readEndpoint = mock(UsbEndpoint.class); | ||||||
|  |         //UsbEndpoint writeEndpoint = mock(UsbEndpoint.class); | ||||||
|  | 
 | ||||||
|  |         when(usbDeviceConnection.claimInterface(usbInterface,true)).thenReturn(true); | ||||||
|  |         when(usbDevice.getInterfaceCount()).thenReturn(1); | ||||||
|  |         when(usbDevice.getInterface(0)).thenReturn(usbInterface); | ||||||
|  |         when(usbInterface.getEndpointCount()).thenReturn(2); | ||||||
|  |         when(usbInterface.getEndpoint(0)).thenReturn(controlEndpoint); | ||||||
|  |         when(usbInterface.getEndpoint(1)).thenReturn(readEndpoint); | ||||||
|  |         //when(usbInterface.getEndpoint(2)).thenReturn(writeEndpoint); | ||||||
|  |         when(controlEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||||
|  |         when(controlEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_INT); | ||||||
|  |         when(readEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||||
|  |         when(readEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||||
|  |         //when(writeEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_OUT); | ||||||
|  |         //when(writeEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||||
|  | 
 | ||||||
|  |         CdcAcmSerialDriver driver = new CdcAcmSerialDriver(usbDevice); | ||||||
|  |         CdcAcmSerialDriver.CdcAcmSerialPort port = (CdcAcmSerialDriver.CdcAcmSerialPort) driver.getPorts().get(0); | ||||||
|  |         port.mConnection = usbDeviceConnection; | ||||||
|  |         port.openInt(); | ||||||
|  |         assertNull(port.mWriteEndpoint); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -0,0 +1,44 @@ | |||||||
|  | package com.hoho.android.usbserial.util; | ||||||
|  | 
 | ||||||
|  | import static org.hamcrest.CoreMatchers.equalTo; | ||||||
|  | import static org.hamcrest.MatcherAssert.assertThat; | ||||||
|  | import static org.junit.Assert.assertEquals; | ||||||
|  | import static org.junit.Assert.assertThrows; | ||||||
|  | 
 | ||||||
|  | import org.junit.Test; | ||||||
|  | 
 | ||||||
|  | import java.security.InvalidParameterException; | ||||||
|  | 
 | ||||||
|  | public class HexDumpText { | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void toByteArray() throws Exception { | ||||||
|  |         assertThat(HexDump.toByteArray((byte)0x4a), equalTo(new byte[]{ 0x4A})); | ||||||
|  |         assertThat(HexDump.toByteArray((short)0x4a5b), equalTo(new byte[]{ 0x4A, 0x5B})); | ||||||
|  |         assertThat(HexDump.toByteArray((int)0x4a5b6c7d), equalTo(new byte[]{ 0x4A, 0x5B, 0x6C, 0x7D})); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void toHexString() throws Exception { | ||||||
|  |         assertEquals("4A", HexDump.toHexString((byte)0x4a)); | ||||||
|  |         assertEquals("4A 5B", HexDump.toHexString((short)0x4a5b)); | ||||||
|  |         assertEquals("4A 5B 6C 7D", HexDump.toHexString((int)0x4a5b6c7d)); | ||||||
|  |         assertEquals("4A 5B 6C 7D", HexDump.toHexString(new byte[]{ 0x4A, 0x5B, 0x6C, 0x7D})); | ||||||
|  |         assertEquals("5B 6C", HexDump.toHexString(new byte[]{ 0x4A, 0x5B, 0x6C, 0x7D}, 1, 2)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void dumpHexString() throws Exception { | ||||||
|  |         assertEquals("10 31 32 33 34 35 36 37 .1234567\n18 39                   .9", HexDump.dumpHexString(new byte[]{ 0x10, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x18, 0x39})); | ||||||
|  |         assertEquals("31 32                   12", HexDump.dumpHexString(new byte[]{ 0x30, 0x31, 0x32, 0x33}, 1, 2)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void toByte() throws Exception { | ||||||
|  |         assertThat(HexDump.hexStringToByteArray("4a 5B-6c\n7d"), equalTo(new byte[]{ 0x4A, 0x5B, 0x6C, 0x7D})); | ||||||
|  |         assertThrows(InvalidParameterException.class, () -> HexDump.hexStringToByteArray("3 ")); | ||||||
|  |         assertThrows(InvalidParameterException.class, () -> HexDump.hexStringToByteArray("3z")); | ||||||
|  |         assertThrows(InvalidParameterException.class, () -> HexDump.hexStringToByteArray("3Z")); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -0,0 +1,55 @@ | |||||||
|  | package com.hoho.android.usbserial.util; | ||||||
|  | 
 | ||||||
|  | import static org.junit.Assert.assertEquals; | ||||||
|  | import static org.mockito.Mockito.mock; | ||||||
|  | import static org.mockito.Mockito.when; | ||||||
|  | 
 | ||||||
|  | import android.hardware.usb.UsbEndpoint; | ||||||
|  | import android.os.Process; | ||||||
|  | 
 | ||||||
|  | import com.hoho.android.usbserial.driver.CommonUsbSerialPort; | ||||||
|  | 
 | ||||||
|  | import org.junit.Test; | ||||||
|  | 
 | ||||||
|  | public class SerialInputOutputManagerTest { | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     // catch all Throwables in onNewData() and onRunError() | ||||||
|  |     @Test | ||||||
|  |     public void throwable() throws Exception { | ||||||
|  | 
 | ||||||
|  |         class ExceptionListener implements SerialInputOutputManager.Listener { | ||||||
|  |             public Exception e; | ||||||
|  |             @Override public void onNewData(byte[] data) { throw new RuntimeException("exception1"); } | ||||||
|  |             @Override public void onRunError(Exception e) { this.e = e; throw new RuntimeException("exception2"); } | ||||||
|  |         } | ||||||
|  |         class ErrorListener implements SerialInputOutputManager.Listener { | ||||||
|  |             public Exception e; | ||||||
|  |             @Override public void onNewData(byte[] data) { throw new UnknownError("error1"); } | ||||||
|  |             @Override public void onRunError(Exception e) { this.e = e; throw new UnknownError("error2");} | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         UsbEndpoint readEndpoint = mock(UsbEndpoint.class); | ||||||
|  |         when(readEndpoint.getMaxPacketSize()).thenReturn(16); | ||||||
|  |         CommonUsbSerialPort port = mock(CommonUsbSerialPort.class); | ||||||
|  |         when(port.getReadEndpoint()).thenReturn(readEndpoint); | ||||||
|  |         when(port.read(new byte[16], 0)).thenReturn(1); | ||||||
|  |         when(port.isOpen()).thenReturn(true); | ||||||
|  |         SerialInputOutputManager manager = new SerialInputOutputManager(port); | ||||||
|  |         manager.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); | ||||||
|  | 
 | ||||||
|  |         ExceptionListener exceptionListener = new ExceptionListener(); | ||||||
|  |         manager.setListener(exceptionListener); | ||||||
|  |         manager.runRead(); | ||||||
|  |         assertEquals(RuntimeException.class, exceptionListener.e.getClass()); | ||||||
|  |         assertEquals("exception1", exceptionListener.e.getMessage()); | ||||||
|  | 
 | ||||||
|  |         ErrorListener errorListener = new ErrorListener(); | ||||||
|  |         manager.setListener(errorListener); | ||||||
|  |         manager.runRead(); | ||||||
|  |         assertEquals(Exception.class, errorListener.e.getClass()); | ||||||
|  |         assertEquals("java.lang.UnknownError: error1", errorListener.e.getMessage()); | ||||||
|  |         assertEquals(UnknownError.class, errorListener.e.getCause().getClass()); | ||||||
|  |         assertEquals("error1", errorListener.e.getCause().getMessage()); | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user