1
0
mirror of https://github.com/mik3y/usb-serial-for-android synced 2025-07-06 14:16:27 +00:00

Compare commits

...

94 Commits

Author SHA1 Message Date
Kai Morich
c608aadc59 target sdk 35 2025-04-18 08:37:24 +02:00
Kai Morich
06faad5622 CDC device tests with MCP2221 2025-04-18 08:18:33 +02:00
Kai Morich
52042f8c3e fix off-by-one error in example app (#623) 2025-04-13 07:33:46 +02:00
kai-morich
068fe80c07
Update README.md 2025-03-10 18:30:37 +01:00
Kai Morich
e1018ab31c gradle update 2025-03-09 09:32:14 +01:00
Kai Morich
026355f61e terminate SerialInputOutputManager write thread if read thread terminates (e.g. port closed) 2025-02-15 18:02:15 +01:00
Kai Morich
150174573c gradle + library update 2025-01-29 08:39:26 +01:00
Dmitry Kaukov
9911e141a7
01. Refactored SerialInputOutputManager (#615)
Used separate threads for reading and writing, enhancing concurrency and performance.

Note: before was possible to start `SerialInputOutputManager` with `Executors.newSingleThreadExecutor().submit(ioManager)`. Now you have to use `ioManager.start()`
2025-01-28 21:26:09 +01:00
Holden
2673407f1d
UsbSerialPort Documentation Fixes (#608) 2024-11-09 10:42:55 +01:00
Holden
8584fe4cb8
Allow Unlimited Read Size for Android 9+ (#609) 2024-11-09 09:56:13 +01:00
kai-morich
0b5950c991
catch all Throwables from SerialInputOutputManager.Listener methods (#601) (#606)
to avoid breaking Interface changes, Error from onNewData() is wrapped into Exception when calling onRunError()
2024-10-28 21:12:22 +01:00
kai-morich
9f93e192ca
v3.8.1 2024-10-18 10:25:35 +02:00
Kai Morich
f5380975ce values supported by default setFlowControl() consistent with getSupportedFlowControl() 2024-07-12 09:29:08 +02:00
kai-morich
0a32c3f9e3
v3.8.0 2024-07-05 22:07:44 +02:00
Kai Morich
88ca3f57c4 flowcontrol for ftdi, pl2303, cp210x 2024-07-05 21:18:37 +02:00
Kai Morich
843792001f replace catch+throw with finally !ok, to get rid of UnhandledException shown as error
method declared as throwing only IOException, but unchecked exceptions can always happen
2024-07-05 18:59:54 +02:00
kai-morich
275590027b
Update README.md 2024-06-02 15:09:07 +02:00
Kai Morich
b6e1833270 test coverage 2024-06-02 14:52:39 +02:00
Kai Morich
b794092c81 improved error handling for read() with concurrent close() (#569)
reworked previous solution from change 8b9ad7ef / v3.7.1 because closeInt() was not working any more
2024-06-02 10:16:56 +02:00
Kai Morich
b1362416f0 gradle + library update 2024-06-01 14:46:38 +02:00
Kai Morich
0c0275675f SerialInputOutputManager.writeAsync(): handle SerialTimeoutException 2024-06-01 14:46:38 +02:00
Kai Morich
cab862599d write(): throw SerialTimeoutException if connection still valid 2024-06-01 14:46:38 +02:00
Holden
2fbceb6cc7
Fix ControlLine enum spacing (#577) 2024-06-01 10:07:38 +02:00
kai-morich
a4ee5c2158
Update README.md 2024-05-13 22:19:37 +02:00
Kai Morich
9bc3834eff handle uncaught NPE causing App termination in prolific driver controlline background thread 2024-05-13 22:07:27 +02:00
Kai Morich
28506a9bf9 assert warning cleanup 2024-05-03 08:47:22 +02:00
Kai Morich
8b9ad7efdf improved error handling for read() with concurrent close() (#569)
- isOpen() returns false during concurrent close()
- less tracing in SerialInputOutputManager
2024-04-25 18:24:28 +02:00
Kai Morich
1245293888 harmonize controlTransfer() result comparison 2024-02-18 13:34:08 +01:00
Kai Morich
26a2f9363e target sdk 34
Pending intent and broadcast receiver changed according to sdk 34 release notes.
Permisssion dialog now shown while fragment is paused.
2024-02-18 13:30:50 +01:00
Kai Morich
83646d6955 gradle 8.2 2024-02-18 12:19:46 +01:00
Self Not Found
573c7e41ca
Add read() and write() with length argument (#544)
To reduce array copy
2023-11-08 21:12:30 +01:00
kai-morich
880c0070cb
3.7.0 in README 2023-10-15 17:07:10 +02:00
kai-morich
a1709c3911
mention gradle kotlin DSL (#537) 2023-10-15 17:06:14 +02:00
kai-morich
9c30dc5ffa
update build workflow versions 2023-10-02 19:12:33 +02:00
kai-morich
b06118b156 consolidate get[Supported]ControlLines 2023-10-02 19:05:55 +02:00
kai-morich
de6d5aa384 replace tab with spaces 2023-10-02 15:52:25 +02:00
kai-morich
11ccb5b949 add missing ChromeCcd setParameters 2023-10-02 08:36:50 +02:00
kai-morich
d585ca8be7
add ChromeCcd to readme 2023-10-02 08:29:22 +02:00
Vladimir Serbinenko
2a2463cd12
Add support for Cr50 (Chromebook CCD) (#540) 2023-10-02 08:19:57 +02:00
kai-morich
80a555a189
v3.6.0 in readme 2023-09-06 07:54:30 +02:00
kai-morich
34e6d989fd
fix codacy badge in README.md 2023-08-25 08:51:25 +02:00
kai-morich
35fdeb1e13 improved exception type also for read with timeout 2023-08-24 19:51:47 +02:00
ExPl0siF
399d3c9c2f
Added error management inside read function to get more appropriate exception (#529) 2023-08-24 18:49:29 +02:00
kai-morich
54ff9bfa44 composite CDC devices: get correct ACM data interface from IAD (#499) 2023-08-23 07:55:31 +02:00
kai-morich
7aecce7943 util/HexDump with space separated hex strings 2023-07-31 08:23:35 +02:00
kai-morich
d15f4d52bb move util/HexDump class from example to library 2023-07-31 08:23:35 +02:00
kai-morich
fd8c155ca5
Merge pull request #521 from elicec/master
add gsm modem usb driver
2023-07-31 08:19:24 +02:00
elicec
88b74d716c add GSM Modem usb device driver 2023-07-31 08:57:34 +08:00
kai-morich
e9a38ca891 skip non ACM subclasses for CDC composite devices 2023-07-24 19:12:18 +02:00
kai-morich
a9c835bcb0 gradle 8.0 2023-07-04 20:52:08 +02:00
kai-morich
9bd1f25773 version update in README 2023-03-15 07:47:48 +01:00
kai-morich
083b9ae7fe use correct control index for composite CDC devices with non-consecutive interface IDs (#477) 2023-03-15 07:47:27 +01:00
kai-morich
fd551970be no code changes, just use normal line breaks 2023-03-15 07:47:08 +01:00
kai-morich
5db45548ba probe CDC devices by USB interface types instead of fixed VID+PID
- no more custom prober required for standard CDC devices
- legacy (singleInterface) CDC devices still have to be added by VID+PID
- for autostart VID+PID still have to be added to device_filter.xml
2023-03-11 19:12:42 +01:00
kai-morich
85f64aff96 skip RNDIS related data interfaces in composite CDC devices (#469) 2023-03-11 17:42:13 +01:00
kai-morich
6c648e9f56 have to use MUTABLE to get GRANTED flag at intent extras 2023-03-11 17:42:12 +01:00
kai-morich
dd1b95b852 target sdk 33 2023-03-11 17:42:12 +01:00
kai-morich
7ea76f8899 cdcacm unit test 2023-03-11 17:42:12 +01:00
kai-morich
fbe64fe4be gradle update, coverage working again 2023-03-11 17:42:12 +01:00
kai-morich
8d3326ed66 remove redundant parameter 2023-03-11 17:42:12 +01:00
kai-morich
6ffb666b33
Merge pull request #464 from chobitsfan/patch-2
update adding repository
2023-01-12 17:25:01 +01:00
kai-morich
e3085eacce
clarified condition for alternative repo setting 2023-01-12 17:21:32 +01:00
Chobits Tai
589b8a0fbf
update adding repository 2023-01-04 14:10:54 +08:00
kai-morich
5bb8db02d5
Update README.md
mention Qinheng CH9102
2022-07-22 08:12:37 +02:00
kai-morich
963ae1d243
version update 2022-07-22 08:03:51 +02:00
kai-morich
ab27c19dc3 sdk 31 fixes: pending intent mutability 2022-07-21 21:59:36 +02:00
kai-morich
1091b4d88b improve PL2303 device type detection (#439) 2022-07-21 12:56:59 +02:00
kai-morich
b0e956c5b3
Merge pull request #440 from majbthrd/addCH9102F
added VID/PID for Qinheng CH9102F
2022-07-21 07:58:56 +02:00
Peter Lawrence
82aeccbf1c added VID/PID for Qinheng CH9102F 2022-07-20 17:21:00 -05:00
kai-morich
f8e76c9b3b
updated version in dependency example 2022-07-17 16:05:02 +02:00
kai-morich
1d4e0128c0 added VID/PID for Raspberry Pi Pico SDK 2022-07-05 07:29:25 +02:00
kai-morich
f997a8b68a log prolific device type 2022-05-18 08:27:27 +02:00
kai-morich
cf9bada887 use optimal write buffer size by default + revert gradle update
write buffer: SerialTimeoutException from write() has valid ex.bytesTransferred
gradle 7.1.x creates empty coverage results
2022-04-26 21:40:49 +02:00
kai-morich
b853ac773c test concurrent access on multi-port devices 2022-04-19 22:17:23 +02:00
kai-morich
1f35587739 target-sdk + dependency update 2022-04-19 20:55:34 +02:00
kai-morich
dea836d8ce
Merge pull request #411 from Glass-Imaging/rp2_support
Add support for Raspberry Pi Pico
2022-02-13 13:09:45 +01:00
Doug MacEwen
a2fa5f010a Specify Support is only for Micropython 2022-02-11 11:32:51 -08:00
Doug MacEwen
49ee2d3c8e Add support for Raspberry Pi Pico 2022-02-08 16:08:49 -08:00
kai-morich
896b242be0
switch openjdk distribution for build action 2021-10-06 08:29:15 +02:00
kai-morich
bdfb7d5f6c reordered public members 2021-09-26 08:09:39 +02:00
kai-morich
76f9198c02 more configurable debug log, disabled by default (#389) 2021-09-26 08:02:20 +02:00
kai-morich
d319879386 jitpack with gradle 7 2021-09-21 20:38:32 +02:00
kai-morich
70d4f41268
Create jitpack.yml 2021-09-20 21:23:00 +02:00
kai-morich
a7e88827f0
Update README.md 2021-09-20 21:10:53 +02:00
kai-morich
12095f6b94 coverage for PL2303 variants 2021-08-17 22:51:26 +02:00
kai-morich
1661535d6b
Update build.yml 2021-08-17 17:56:52 +02:00
kai-morich
21cf775281 fix PL2303G product IDs (#383) 2021-08-17 17:29:49 +02:00
kai-morich
cd83951bd1
version update 2021-08-05 17:09:05 +02:00
kai-morich
18e300efa3 add dedicated handling for Ch34x baud rate 921600 2021-07-28 17:49:35 +02:00
kai-morich
76f0260a55
Update version 2021-07-01 18:33:07 +02:00
kai-morich
4e0a6d6d2d
Merge pull request #374 from mik3y/ft2232c
restore FT2232C support
2021-07-01 11:15:34 +02:00
kai-morich
7ffbc73919 restore FT2232C support 2021-07-01 07:45:24 +02:00
kai-morich
c73c38ca82
Merge pull request #366 from ti777777/main
update readme
2021-05-23 15:49:07 +02:00
ti777777
25b5f28a8d update readme 2021-05-23 17:41:33 +08:00
54 changed files with 3253 additions and 1084 deletions

View File

@ -8,10 +8,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v3
- name: set up JDK 1.8 - uses: actions/setup-java@v3
uses: actions/setup-java@v1
with: with:
java-version: 1.8 distribution: 'temurin'
java-version: '17'
- name: Build with Gradle - name: Build with Gradle
run: ./gradlew assembleDebug run: ./gradlew assembleDebug

5
.idea/.gitignore generated vendored
View File

@ -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
View 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
View File

@ -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>

13
.idea/misc.xml generated
View File

@ -1,11 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="NullableNotNullManager"> <component name="NullableNotNullManager">
<option name="myDefaultNullable" value="androidx.annotation.Nullable" /> <option name="myDefaultNullable" value="androidx.annotation.Nullable" />
<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" />
@ -21,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" />
@ -41,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">

10
.idea/modules.xml generated
View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/modules/usb-serial-for-android.iml" filepath="$PROJECT_DIR$/.idea/modules/usb-serial-for-android.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/usbSerialExamples/usb-serial-for-android.usbSerialExamples.iml" filepath="$PROJECT_DIR$/.idea/modules/usbSerialExamples/usb-serial-for-android.usbSerialExamples.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/usbSerialForAndroid/usb-serial-for-android.usbSerialForAndroid.iml" filepath="$PROJECT_DIR$/.idea/modules/usbSerialForAndroid/usb-serial-for-android.usbSerialForAndroid.iml" />
</modules>
</component>
</project>

View File

@ -3,10 +3,14 @@
<component name="RunConfigurationProducerService"> <component name="RunConfigurationProducerService">
<option name="ignoredProducers"> <option name="ignoredProducers">
<set> <set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" /> <option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" /> <option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" /> <option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" /> <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> </set>
</option> </option>
</component> </component>

View File

@ -1,6 +1,6 @@
[![Actions Status](https://github.com/mik3y/usb-serial-for-android/workflows/build/badge.svg)](https://github.com/mik3y/usb-serial-for-android/actions) [![Actions Status](https://github.com/mik3y/usb-serial-for-android/workflows/build/badge.svg)](https://github.com/mik3y/usb-serial-for-android/actions)
[![Jitpack](https://jitpack.io/v/mik3y/usb-serial-for-android.svg)](https://jitpack.io/#mik3y/usb-serial-for-android) [![Jitpack](https://jitpack.io/v/mik3y/usb-serial-for-android.svg)](https://jitpack.io/#mik3y/usb-serial-for-android)
[![Codacy](https://app.codacy.com/project/badge/Grade/ef799bba8a7343818af0a90eba3ecb46)](https://www.codacy.com/gh/kai-morich/usb-serial-for-android-mik3y/dashboard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=kai-morich/usb-serial-for-android-mik3y&amp;utm_campaign=Badge_Grade) [![Codacy](https://app.codacy.com/project/badge/Grade/ef799bba8a7343818af0a90eba3ecb46)](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)
[![codecov](https://codecov.io/gh/mik3y/usb-serial-for-android/branch/master/graph/badge.svg)](https://codecov.io/gh/mik3y/usb-serial-for-android) [![codecov](https://codecov.io/gh/mik3y/usb-serial-for-android/branch/master/graph/badge.svg)](https://codecov.io/gh/mik3y/usb-serial-for-android)
# usb-serial-for-android # usb-serial-for-android
@ -26,10 +26,26 @@ allprojects {
} }
} }
``` ```
Starting with gradle 6.8 you can alternatively add jitpack.io repository to your settings.gradle:
```gradle
dependencyResolutionManagement {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
```
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.3.1' implementation 'com.github.mik3y:usb-serial-for-android:3.9.0'
} }
``` ```
@ -99,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
@ -113,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
@ -136,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 * 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

View File

@ -6,7 +6,7 @@ buildscript {
google() google()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.2.0' classpath 'com.android.tools.build:gradle:8.9.1'
} }
} }

5
codecov.yml Normal file
View File

@ -0,0 +1,5 @@
codecov:
max_report_age: off
require_ci_to_pass: no
notify:
wait_for_ci: no

View File

@ -1,2 +1,3 @@
#android.enableJetifier=true android.enableJetifier=true
android.useAndroidX=true android.useAndroidX=true
android.defaults.buildfeatures.buildconfig=true

View File

@ -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-6.7.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip

4
jitpack.yml Normal file
View File

@ -0,0 +1,4 @@
jdk:
- openjdk17
install:
- ./gradlew :usbSerialForAndroid:publishToMavenLocal

6
test/pi_pico/README.md Normal file
View 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`

Binary file not shown.

View File

@ -1,8 +1,9 @@
apply plugin: 'com.android.application' plugins {
id 'com.android.application'
}
android { android {
compileSdkVersion 30 compileSdkVersion 35
buildToolsVersion '30.0.3'
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
@ -11,10 +12,9 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 17 minSdkVersion 17
targetSdkVersion 30 targetSdkVersion 35
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
testInstrumentationRunner "android.test.InstrumentationTestRunner"
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.2.0' implementation 'androidx.appcompat:appcompat:1.6.1' // later versions have minsdk 21
implementation 'com.google.android.material:material:1.3.0' implementation 'com.google.android.material:material:1.11.0' // later versions have minsdk 19
} }

View File

@ -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,9 +14,9 @@
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">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />

View File

@ -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);
} }

View File

@ -8,6 +8,7 @@ import android.content.IntentFilter;
import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager; import android.hardware.usb.UsbManager;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
@ -27,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;
@ -88,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);
} }
@ -103,7 +115,6 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
status("disconnected"); status("disconnected");
disconnect(); disconnect();
} }
getActivity().unregisterReceiver(broadcastReceiver);
super.onPause(); super.onPause();
} }
@ -203,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;
} }
@ -211,7 +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;
PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(getActivity(), 0, new Intent(INTENT_ACTION_GRANT_USB), 0); int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_MUTABLE : 0;
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;
} }
@ -225,7 +239,11 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
try { try {
usbSerialPort.open(usbConnection); usbSerialPort.open(usbConnection);
try{
usbSerialPort.setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE); 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();
@ -349,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");
} }
} }
@ -366,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);
} }
} }

View File

@ -32,4 +32,7 @@
<usb-device vendor-id="7855" product-id="4" /> <!-- 0x1eaf / 0x0004: Leaflabs Maple --> <usb-device vendor-id="7855" product-id="4" /> <!-- 0x1eaf / 0x0004: Leaflabs Maple -->
<usb-device vendor-id="3368" product-id="516" /> <!-- 0x0d28 / 0x0204: ARM mbed --> <usb-device vendor-id="3368" product-id="516" /> <!-- 0x0d28 / 0x0204: ARM mbed -->
<usb-device vendor-id="1155" product-id="22336" /><!-- 0x0483 / 0x5740: ST CDC --> <usb-device vendor-id="1155" product-id="22336" /><!-- 0x0483 / 0x5740: ST CDC -->
<usb-device vendor-id="11914" product-id="5" /> <!-- 0x2E8A / 0x0005: Raspberry Pi Pico Micropython -->
<usb-device vendor-id="11914" product-id="10" /> <!-- 0x2E8A / 0x000A: Raspberry Pi Pico SDK -->
<usb-device vendor-id="6790" product-id="21972" /><!-- 0x1A86 / 0x55D4: Qinheng CH9102F -->
</resources> </resources>

View File

@ -1,17 +1,19 @@
apply plugin: 'com.android.library' plugins {
id 'com.android.library'
id 'maven-publish'
}
android { android {
compileSdkVersion 30 compileSdkVersion 35
buildToolsVersion '30.0.3'
defaultConfig { defaultConfig {
minSdkVersion 17 minSdkVersion 17
targetSdkVersion 30 targetSdkVersion 35
consumerProguardFiles 'proguard-rules.pro' consumerProguardFiles 'proguard-rules.pro'
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments = [ // Raspi Windows LinuxVM ... testInstrumentationRunnerArguments = [ // Raspi Windows LinuxVM ...
'rfc2217_server_host': '192.168.0.100', 'rfc2217_server_host': '192.168.0.78',
'rfc2217_server_nonstandard_baudrates': 'true', // true false false 'rfc2217_server_nonstandard_baudrates': 'true', // true false false
] ]
} }
@ -19,17 +21,40 @@ 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.2.0" implementation "androidx.annotation:annotation:1.9.1"
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 'com.android.support.test:runner:1.0.2' 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'
} }
//apply from: 'publishToMavenLocal.gradle' // gradle task: publishToMavenLocal
project.afterEvaluate {
publishing {
publications {
release(MavenPublication) {
from components.release
// values used for local maven repo, jitpack uses github release:
groupId 'com.github.mik3y'
artifactId 'usb-serial-for-android'
version '3.8.0beta'
}
}
}
}
//apply from: 'coverage.gradle' //apply from: 'coverage.gradle'

View File

@ -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,13 +26,21 @@ 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'
testInstrumentationRunnerArguments = ['test_device_driver': 'Ftdi', 'test_device_port': '1'] testInstrumentationRunnerArguments = ['test_device_driver': 'Ftdi', 'test_device_port': '1']
} }
pl2302 { pl2303 {
dimension 'device'
testInstrumentationRunnerArguments = ['test_device_driver': 'Prolific']
}
pl2303t {
dimension 'device'
testInstrumentationRunnerArguments = ['test_device_driver': 'Prolific']
}
pl2303g {
dimension 'device' dimension 'device'
testInstrumentationRunnerArguments = ['test_device_driver': 'Prolific'] testInstrumentationRunnerArguments = ['test_device_driver': 'Prolific']
} }
@ -40,7 +48,8 @@ android {
buildTypes { buildTypes {
debug { debug {
testCoverageEnabled true // disable for testAnyDeviceDebugUnitTest enableUnitTestCoverage true
enableAndroidTestCoverage true
} }
} }
} }
@ -52,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'
]) ])
} }

View File

@ -1,20 +0,0 @@
apply plugin: 'maven-publish'
publishing {
publications {
maven(MavenPublication) {
groupId 'com.github.mik3y'
artifactId 'usb-serial-for-android'
version '3.0.0beta'
afterEvaluate {
artifact androidSourcesJar
artifact bundleReleaseAar
}
}
}
}
task androidSourcesJar(type: Jar) {
classifier 'sources'
from android.sourceSets.main.java.srcDirs
}

View File

@ -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"/>

View File

@ -7,13 +7,15 @@ package com.hoho.android.usbserial;
import android.content.Context; import android.content.Context;
import android.hardware.usb.UsbManager; import android.hardware.usb.UsbManager;
import android.support.test.InstrumentationRegistry; import androidx.test.core.app.ApplicationProvider;
import android.support.test.runner.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import android.util.Log; import android.util.Log;
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;
import com.hoho.android.usbserial.driver.UsbSerialProber; import com.hoho.android.usbserial.driver.UsbSerialProber;
import com.hoho.android.usbserial.util.TestBuffer;
import com.hoho.android.usbserial.util.UsbWrapper; import com.hoho.android.usbserial.util.UsbWrapper;
import org.junit.Before; import org.junit.Before;
@ -24,12 +26,14 @@ import org.junit.rules.TestWatcher;
import org.junit.runner.Description; import org.junit.runner.Description;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import java.io.IOException;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue; import static org.junit.Assume.assumeTrue;
@ -55,7 +59,7 @@ public class CrossoverTest {
assumeTrue("ignore test for device specific coverage report", assumeTrue("ignore test for device specific coverage report",
InstrumentationRegistry.getArguments().getString("test_device_driver") == null); InstrumentationRegistry.getArguments().getString("test_device_driver") == null);
context = InstrumentationRegistry.getContext(); context = ApplicationProvider.getApplicationContext();
usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE); usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
List<UsbSerialDriver> availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(usbManager); List<UsbSerialDriver> availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(usbManager);
assertNotEquals("no USB device found", 0, availableDrivers.size()); assertNotEquals("no USB device found", 0, availableDrivers.size());
@ -160,4 +164,53 @@ public class CrossoverTest {
usb2.close(); usb2.close();
} }
@Test
public void concurrent() throws Exception {
// 115200 baud ~= 11kB/sec => ~1.5 second test duration with 16kB tbuf
// concurrent (+ blocking) write calls as tbuf larger than any buffer size returned by UsbWrapper.getWriteSizes()
// concurrent read calls in IoManager threads
TestBuffer tbuf1 = new TestBuffer(16*1024);
TestBuffer tbuf2 = new TestBuffer(16*1024);
class WriteRunnable implements Runnable {
public WriteRunnable(int port) { this.port = port; }
private final int port;
Exception exc;
@Override
public void run() {
byte[] buf = new byte[1024];
try {
for(int i=0; i<tbuf1.buf.length / 1024; i++) {
System.arraycopy(tbuf1.buf, i*1024, buf, 0, 1024);
if (port == 1)
usb1.write(buf);
else
usb2.write(buf);
}
} catch (IOException exc) {
this.exc = exc;
}
}
}
usb1.open();
usb2.open();
usb1.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
usb2.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
WriteRunnable wr1 = new WriteRunnable(1), wr2 = new WriteRunnable(2);
Thread wt1 = new Thread(wr1), wt2 = new Thread(wr2);
boolean done1 = false, done2 = false;
wt1.start();
Thread.sleep(50);
wt2.start();
while(!done1 && !done2) {
if(!done1)
done1 = tbuf1.testRead(usb1.read(-1));
if(!done2)
done2 = tbuf2.testRead(usb2.read(-1));
}
wt1.join(); wt2.join();
assertNull(wr1.exc);
assertNull(wr2.exc);
}
} }

View File

@ -0,0 +1,8 @@
package com.hoho.android.usbserial.driver;
public class CommonUsbSerialPortWrapper {
public static byte[] getWriteBuffer(UsbSerialPort serialPort) {
CommonUsbSerialPort commonSerialPort = (CommonUsbSerialPort) serialPort;
return commonSerialPort.mWriteBuffer;
}
}

View File

@ -1,6 +1,6 @@
package com.hoho.android.usbserial.driver; package com.hoho.android.usbserial.driver;
public class ProlificWrapper { public class ProlificSerialPortWrapper {
public static boolean isDeviceTypeT(UsbSerialPort serialPort) { public static boolean isDeviceTypeT(UsbSerialPort serialPort) {
ProlificSerialDriver.ProlificSerialPort prolificSerialPort = (ProlificSerialDriver.ProlificSerialPort) serialPort; ProlificSerialDriver.ProlificSerialPort prolificSerialPort = (ProlificSerialDriver.ProlificSerialPort) serialPort;
return prolificSerialPort.mDeviceType == ProlificSerialDriver.DeviceType.DEVICE_TYPE_T; return prolificSerialPort.mDeviceType == ProlificSerialDriver.DeviceType.DEVICE_TYPE_T;

View File

@ -0,0 +1,32 @@
package com.hoho.android.usbserial.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
public class TestBuffer {
public final byte[] buf;
public int len;
public TestBuffer(int length) {
len = 0;
buf = new byte[length];
int i = 0;
int j = 0;
for (j = 0; j < length / 16; j++)
for (int k = 0; k < 16; k++)
buf[i++] = (byte) j;
while (i < length)
buf[i++] = (byte) j;
}
public boolean testRead(byte[] data) {
assertNotEquals(0, data.length);
assertTrue("got " + (len + data.length) + " bytes", (len + data.length) <= buf.length);
for (int j = 0; j < data.length; j++)
assertEquals("at pos " + (len + j), (byte) ((len + j) / 16), data[j]);
len += data.length;
//Log.d(TAG, "read " + len);
return len == buf.length;
}
}

View File

@ -9,9 +9,17 @@ import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager; import android.hardware.usb.UsbManager;
import android.media.RingtoneManager; import android.media.RingtoneManager;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.util.Log; import android.util.Log;
import com.hoho.android.usbserial.driver.CdcAcmSerialDriver; import com.hoho.android.usbserial.driver.CdcAcmSerialDriver;
import com.hoho.android.usbserial.driver.Ch34xSerialDriver;
import com.hoho.android.usbserial.driver.CommonUsbSerialPort;
import com.hoho.android.usbserial.driver.Cp21xxSerialDriver;
import com.hoho.android.usbserial.driver.FtdiSerialDriver;
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.UsbSerialDriver; import com.hoho.android.usbserial.driver.UsbSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialPort; import com.hoho.android.usbserial.driver.UsbSerialPort;
@ -25,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;
@ -47,12 +57,22 @@ public class UsbWrapper implements SerialInputOutputManager.Listener {
public boolean readBlock = false; public boolean readBlock = false;
long readTime = 0; long readTime = 0;
// device properties
public boolean isCp21xxRestrictedPort; // second port of Cp2105 has limited dataBits, stopBits, parity
public boolean outputLinesSupported;
public boolean inputLinesSupported;
public boolean inputLinesConnected;
public boolean inputLinesOnlyRtsCts;
public int writePacketSize = -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;
this.serialDriver = serialDriver; this.serialDriver = serialDriver;
this.devicePort = devicePort; this.devicePort = devicePort;
serialPort = serialDriver.getPorts().get(devicePort); serialPort = serialDriver.getPorts().get(devicePort);
CommonUsbSerialPort.DEBUG = true;
} }
public void setUp() throws Exception { public void setUp() throws Exception {
@ -69,17 +89,78 @@ public class UsbWrapper implements SerialInputOutputManager.Listener {
granted[0] = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false); granted[0] = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
} }
}; };
PendingIntent permissionIntent = PendingIntent.getBroadcast(context, 0, new Intent("com.android.example.USB_PERMISSION"), 0); int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_MUTABLE : 0;
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;
Thread.sleep(1); Thread.sleep(1);
} }
Log.d(TAG,"USB permission "+granted[0]); Log.d(TAG,"USB permission "+granted[0]);
assertTrue("USB permission dialog not confirmed", granted[0]==null?false:granted[0]); assertTrue("USB permission dialog not confirmed", granted[0] != null && granted[0]);
} }
// extract some device properties:
isCp21xxRestrictedPort = serialDriver instanceof Cp21xxSerialDriver && serialDriver.getPorts().size()==2 && serialPort.getPortNumber() == 1;
// output lines are supported by all common drivers
// input lines are supported by all common drivers except CDC
if (serialDriver instanceof FtdiSerialDriver) {
outputLinesSupported = true;
inputLinesSupported = true;
if(serialDriver.getDevice().getProductId() == UsbId.FTDI_FT2232H)
inputLinesConnected = true; // I only have 74LS138 connected at FT2232, not at FT232
if(serialDriver.getDevice().getProductId() == UsbId.FTDI_FT231X) {
inputLinesConnected = true;
inputLinesOnlyRtsCts = true; // I only test with FT230X that has only these 2 control lines. DTR is silently ignored
}
} else if (serialDriver instanceof Cp21xxSerialDriver) {
outputLinesSupported = true;
inputLinesSupported = true;
if(serialDriver.getPorts().size() == 1)
inputLinesConnected = true; // I only have 74LS138 connected at CP2102, not at CP2105
} else if (serialDriver instanceof ProlificSerialDriver) {
outputLinesSupported = true;
inputLinesSupported = true;
inputLinesConnected = true;
} else if (serialDriver instanceof Ch34xSerialDriver) {
outputLinesSupported = true;
inputLinesSupported = true;
if(serialDriver.getDevice().getProductId() == UsbId.QINHENG_CH340)
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.getPorts().size() == 1) { writePacketSize = 64; writeBufferSize = 576; }
else if (serialPort.getPortNumber() == 0) { writePacketSize = 64; writeBufferSize = 320; }
else { writePacketSize = 32; writeBufferSize = 128; }; //, 160}; // write buffer size detection is unreliable
} else if (serialDriver instanceof Ch34xSerialDriver) {
writePacketSize = 32; writeBufferSize = 64;
} else if (serialDriver instanceof ProlificSerialDriver) {
writePacketSize = 64; writeBufferSize = 256;
} else if (serialDriver instanceof FtdiSerialDriver) {
switch (serialDriver.getPorts().size()) {
case 1: writePacketSize = 64; writeBufferSize = 128; break;
case 2: writePacketSize = 512; writeBufferSize = 4096; break;
case 4: writePacketSize = 512; writeBufferSize = 2048; break;
}
if(serialDriver.getDevice().getProductId() == UsbId.FTDI_FT231X)
writeBufferSize = 512;
} else if (serialDriver instanceof CdcAcmSerialDriver) {
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() {
@ -108,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) {
} }
@ -157,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 {
@ -171,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
@ -244,15 +334,40 @@ public class UsbWrapper implements SerialInputOutputManager.Listener {
} }
} }
// return [write packet size, write buffer size(s)]
public int[] getWriteSizes() {
if (serialDriver instanceof Cp21xxSerialDriver) {
if (serialDriver.getPorts().size() == 1) return new int[]{64, 576};
else if (serialPort.getPortNumber() == 0) return new int[]{64, 320};
else return new int[]{32, 128, 160}; // write buffer size detection is unreliable
} else if (serialDriver instanceof Ch34xSerialDriver) {
return new int[]{32, 64};
} else if (serialDriver instanceof ProlificSerialDriver) {
return new int[]{64, 256};
} else if (serialDriver instanceof FtdiSerialDriver) {
switch (serialDriver.getPorts().size()) {
case 1: return new int[]{64, 128};
case 2: return new int[]{512, 4096};
case 4: return new int[]{512, 2048};
default: return null;
}
} else if (serialDriver instanceof CdcAcmSerialDriver) {
return new int[]{64, 128};
} else {
return null;
}
}
@Override @Override
public void onNewData(byte[] data) { public void onNewData(byte[] data) {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
if(readTime == 0) if(readTime == 0)
readTime = now; readTime = now;
if(data.length > 64) { if(data.length > 64) {
Log.d(TAG, "usb read: time+=" + String.format("%-3d",now- readTime) + " len=" + String.format("%-4d",data.length) + " data=" + new String(data, 0, 32) + "..." + new String(data, data.length-32, 32)); Log.d(TAG, "usb " + devicePort + " read: time+=" + String.format("%-3d",now- readTime) + " len=" + String.format("%-4d",data.length) + " data=" + new String(data, 0, 32) + "..." + new String(data, data.length-32, 32));
} else { } else {
Log.d(TAG, "usb read: time+=" + String.format("%-3d",now- readTime) + " len=" + String.format("%-4d",data.length) + " data=" + new String(data)); Log.d(TAG, "usb " + devicePort + " read: time+=" + String.format("%-3d",now- readTime) + " len=" + String.format("%-4d",data.length) + " data=" + new String(data));
} }
readTime = now; readTime = now;

View File

@ -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>

View File

@ -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,16 +42,8 @@ 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) {
@ -55,6 +51,24 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
} }
} }
@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,17 +149,36 @@ 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;
int j = getInterfaceIdFromDescriptors();
Log.d(TAG, "interface count=" + mDevice.getInterfaceCount() + ", IAD=" + j);
if (j >= 0) {
for (int i = 0; i < mDevice.getInterfaceCount(); i++) { for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
UsbInterface usbInterface = mDevice.getInterface(i); UsbInterface usbInterface = mDevice.getInterface(i);
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM) { 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;
}
}
}
}
if (mControlInterface == null || mDataInterface == null) {
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) { if (controlInterfaceCount == mPortNumber) {
mControlIndex = i; mControlIndex = usbInterface.getId();
mControlInterface = usbInterface; mControlInterface = usbInterface;
} }
controlInterfaceCount++; controlInterfaceCount++;
@ -153,16 +190,16 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
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,42 +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,
});
return supportedDevices;
} }
} }

View File

@ -7,9 +7,11 @@ 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 com.hoho.android.usbserial.BuildConfig;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
@ -77,7 +79,7 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
} }
@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)) {
@ -191,29 +193,39 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
private void setBaudRate(int baudRate) throws IOException { private void setBaudRate(int baudRate) throws IOException {
final long CH341_BAUDBASE_FACTOR = 1532620800; long factor;
final int CH341_BAUDBASE_DIVMAX = 3; long divisor;
long factor = CH341_BAUDBASE_FACTOR / baudRate; if (baudRate == 921600) {
int divisor = CH341_BAUDBASE_DIVMAX; divisor = 7;
factor = 0xf300;
} else {
final long BAUDBASE_FACTOR = 1532620800;
final int BAUDBASE_DIVMAX = 3;
if(BuildConfig.DEBUG && (baudRate & (3<<29)) == (1<<29))
baudRate &= ~(1<<29); // for testing purpose bypass dedicated baud rate handling
factor = BAUDBASE_FACTOR / baudRate;
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 ret = controlOut(0x9a, 0x1312, (int) ((factor & 0xff00) | divisor)); int val1 = (int) ((factor & 0xff00) | divisor);
int val2 = (int) (factor & 0xff);
Log.d(TAG, String.format("baud rate=%d, 0x1312=0x%04x, 0x0f2c=0x%04x", baudRate, val1, val2));
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, (int) (factor & 0xff));
if (ret < 0) { if (ret < 0) {
throw new IOException("Error setting baud rate: #2"); throw new IOException("Error setting baud rate: #2");
} }
@ -362,6 +374,7 @@ public class Ch34xSerialDriver 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_QINHENG, new int[]{ supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{

View File

@ -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;
}
}

View File

@ -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;
@ -25,28 +26,33 @@ import java.util.EnumSet;
*/ */
public abstract class CommonUsbSerialPort implements UsbSerialPort { public abstract class CommonUsbSerialPort implements UsbSerialPort {
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 DEFAULT_WRITE_BUFFER_SIZE = 16 * 1024; private static final int MAX_READ_SIZE = 16 * 1024; // = old bulkTransfer limit prior to Android 9
private static final int MAX_READ_SIZE = 16 * 1024; // = old bulkTransfer limit
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;
protected final Object mWriteBufferLock = new Object(); /**
/** Internal write buffer. Guarded by {@link #mWriteBufferLock}. */ * Internal write buffer.
* Guarded by {@link #mWriteBufferLock}.
* Default length = mReadEndpoint.getMaxPacketSize()
**/
protected byte[] mWriteBuffer; protected byte[] mWriteBuffer;
protected final Object mWriteBufferLock = new Object();
public CommonUsbSerialPort(UsbDevice device, int portNumber) { public CommonUsbSerialPort(UsbDevice device, int portNumber) {
mDevice = device; mDevice = device;
mPortNumber = portNumber; mPortNumber = portNumber;
mWriteBuffer = new byte[DEFAULT_WRITE_BUFFER_SIZE];
} }
@Override @Override
@ -85,11 +91,19 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort {
* Sets the size of the internal buffer used to exchange data with the USB * Sets the size of the internal buffer used to exchange data with the USB
* stack for write operations. Most users should not need to change this. * stack for write operations. Most users should not need to change this.
* *
* @param bufferSize the size in bytes * @param bufferSize the size in bytes, <= 0 resets original size
*/ */
public final void setWriteBufferSize(int bufferSize) { public final void setWriteBufferSize(int bufferSize) {
synchronized (mWriteBufferLock) { synchronized (mWriteBufferLock) {
if (bufferSize == mWriteBuffer.length) { if (bufferSize <= 0) {
if (mWriteEndpoint != null) {
bufferSize = mWriteEndpoint.getMaxPacketSize();
} else {
mWriteBuffer = null;
return;
}
}
if (mWriteBuffer != null && bufferSize == mWriteBuffer.length) {
return; return;
} }
mWriteBuffer = new byte[bufferSize]; mWriteBuffer = new byte[bufferSize];
@ -105,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;
} finally {
if (!ok) {
try { try {
close(); close();
} catch (Exception ignored) {} } catch (Exception ignored) {}
throw e; }
} }
} }
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) {}
@ -145,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
@ -175,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();
@ -195,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;
@ -217,7 +252,10 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort {
synchronized (mWriteBufferLock) { synchronized (mWriteBufferLock) {
final byte[] writeBuffer; final byte[] writeBuffer;
requestLength = Math.min(src.length - offset, mWriteBuffer.length); if (mWriteBuffer == null) {
mWriteBuffer = new byte[mWriteEndpoint.getMaxPacketSize()];
}
requestLength = Math.min(length - offset, mWriteBuffer.length);
if (offset == 0) { if (offset == 0) {
writeBuffer = src; writeBuffer = src;
} else { } else {
@ -228,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;
} }
@ -238,14 +276,19 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort {
actualLength = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, requestLength, requestTimeout); actualLength = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, requestLength, requestTimeout);
} }
} }
Log.d(TAG, "Wrote " + actualLength + "/" + requestLength + " offset " + offset + "/" + src.length + " timeout " + requestTimeout); long elapsed = MonotonicClock.millis() - startTime;
if (DEBUG) {
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;
@ -254,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
@ -285,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 {
if (flowcontrol != FlowControl.NONE)
throw new UnsupportedOperationException(); 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(); }

View File

@ -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,

View File

@ -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,14 +121,16 @@ 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");
} }
int deviceType = rawDescriptors[13]; int deviceType = rawDescriptors[13];
baudRateWithPort = deviceType == 7 || deviceType == 8 || deviceType == 9; // ...H devices baudRateWithPort = deviceType == 7 || deviceType == 8 || deviceType == 9 // ...H devices
|| mDevice.getInterfaceCount() > 1; // FT2232C
} }
@Override @Override
@ -139,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);
@ -290,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];
@ -364,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) {
@ -406,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];
@ -414,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,

View File

@ -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;
}
}

View File

@ -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;
} }
} }

View File

@ -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) {
@ -139,7 +138,7 @@ public class ProlificSerialDriver implements UsbSerialDriver {
byte[] buffer = new byte[length]; byte[] buffer = new byte[length];
int result = mConnection.controlTransfer(requestType, request, value, index, buffer, length, USB_READ_TIMEOUT_MILLIS); int result = mConnection.controlTransfer(requestType, request, value, index, buffer, length, USB_READ_TIMEOUT_MILLIS);
if (result != length) { if (result != length) {
throw new IOException(String.format("ControlTransfer 0x%x failed: %d",value, result)); throw new IOException(String.format("ControlTransfer %s 0x%x failed: %d",mDeviceType.name(), value, result));
} }
return buffer; return buffer;
} }
@ -148,7 +147,7 @@ public class ProlificSerialDriver implements UsbSerialDriver {
int length = (data == null) ? 0 : data.length; int length = (data == null) ? 0 : data.length;
int result = mConnection.controlTransfer(requestType, request, value, index, data, length, USB_WRITE_TIMEOUT_MILLIS); int result = mConnection.controlTransfer(requestType, request, value, index, data, length, USB_WRITE_TIMEOUT_MILLIS);
if (result != length) { if (result != length) {
throw new IOException( String.format("ControlTransfer 0x%x failed: %d", value, result)); throw new IOException( String.format("ControlTransfer %s 0x%x failed: %d", mDeviceType.name(), value, result));
} }
} }
@ -202,12 +201,12 @@ public class ProlificSerialDriver implements UsbSerialDriver {
private void readStatusThreadFunction() { private void readStatusThreadFunction() {
try { try {
while (!mStopReadStatusThread) {
byte[] buffer = new byte[STATUS_BUFFER_SIZE]; byte[] buffer = new byte[STATUS_BUFFER_SIZE];
while (!mStopReadStatusThread) {
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,7 +217,8 @@ public class ProlificSerialDriver implements UsbSerialDriver {
} }
} }
} }
} catch (IOException e) { } catch (Exception e) {
if (isOpen())
mReadStatusException = e; 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");
} }
@ -299,18 +299,23 @@ public class ProlificSerialDriver implements UsbSerialDriver {
byte maxPacketSize0 = rawDescriptors[7]; byte maxPacketSize0 = rawDescriptors[7];
if (mDevice.getDeviceClass() == 0x02 || maxPacketSize0 != 64) { if (mDevice.getDeviceClass() == 0x02 || maxPacketSize0 != 64) {
mDeviceType = DeviceType.DEVICE_TYPE_01; mDeviceType = DeviceType.DEVICE_TYPE_01;
} else if(deviceVersion == 0x300 && usbVersion == 0x200) { } else if(usbVersion == 0x200) {
if(deviceVersion == 0x300 && testHxStatus()) {
mDeviceType = DeviceType.DEVICE_TYPE_T; // TA mDeviceType = DeviceType.DEVICE_TYPE_T; // TA
} else if(deviceVersion == 0x500) { } else if(deviceVersion == 0x500 && testHxStatus()) {
mDeviceType = DeviceType.DEVICE_TYPE_T; // TB mDeviceType = DeviceType.DEVICE_TYPE_T; // TB
} else if(usbVersion == 0x200 && !testHxStatus()) { } else {
mDeviceType = DeviceType.DEVICE_TYPE_HXN; mDeviceType = DeviceType.DEVICE_TYPE_HXN;
}
} else { } else {
mDeviceType = DeviceType.DEVICE_TYPE_HX; mDeviceType = DeviceType.DEVICE_TYPE_HX;
} }
Log.d(TAG, String.format("usbVersion=%x, deviceVersion=%x, deviceClass=%d, packetSize=%d => deviceType=%s",
usbVersion, deviceVersion, mDevice.getDeviceClass(), maxPacketSize0, mDeviceType.name()));
resetDevice(); resetDevice();
doBlackMagic(); doBlackMagic();
setControlLines(mControlLinesValue); setControlLines(mControlLinesValue);
setFlowControl(mFlowControl);
} }
@Override @Override
@ -522,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();
@ -541,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) {
@ -563,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,

View File

@ -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;
} }
} }

View File

@ -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;
@ -53,21 +32,22 @@ public final class UsbId {
public static final int PROLIFIC_PL2303 = 0x2303; // device type 01, T, HX public static final int PROLIFIC_PL2303 = 0x2303; // device type 01, T, HX
public static final int PROLIFIC_PL2303GC = 0x23a3; // device type HXN public static final int PROLIFIC_PL2303GC = 0x23a3; // device type HXN
public static final int PROLIFIC_PL2303GB = 0x23b3; // " public static final int PROLIFIC_PL2303GB = 0x23b3; // "
public static final int PROLIFIC_PL2303GT = 0x23cd; // " public static final int PROLIFIC_PL2303GT = 0x23c3; // "
public static final int PROLIFIC_PL2303GL = 0x23e3; // " public static final int PROLIFIC_PL2303GL = 0x23d3; // "
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;
// 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;
private UsbId() { private UsbId() {
throw new IllegalAccessError("Non-instantiable class"); throw new IllegalAccessError("Non-instantiable class");

View File

@ -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.
* *

View File

@ -60,6 +60,15 @@ public interface UsbSerialPort extends Closeable {
/** 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.
* *

View File

@ -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 {

View File

@ -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;

View File

@ -13,21 +13,28 @@ 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 {
STOPPED,
STARTING,
RUNNING,
STOPPING
}
public static boolean DEBUG = false;
private static final String TAG = SerialInputOutputManager.class.getSimpleName(); private static final String TAG = SerialInputOutputManager.class.getSimpleName();
public static boolean DEBUG = false;
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;
@ -37,14 +44,9 @@ public class SerialInputOutputManager implements Runnable {
private ByteBuffer mReadBuffer; // default size = getReadEndpoint().getMaxPacketSize() private ByteBuffer mReadBuffer; // default size = getReadEndpoint().getMaxPacketSize()
private ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ); private ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ);
public enum State {
STOPPED,
RUNNING,
STOPPING
}
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;
@ -55,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);
} }
@ -85,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;
} }
@ -95,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;
} }
@ -143,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;
} /**
Log.i(TAG, "Running ..."); * Notify listener of an error
try { *
if(mThreadPriority != Process.THREAD_PRIORITY_DEFAULT) * @param e the exception
Process.setThreadPriority(mThreadPriority); */
while (true) { private void notifyErrorListener(Throwable e) {
if (getState() != State.RUNNING) { Listener listener = getListener();
Log.i(TAG, "Stopping mState=" + getState());
break;
}
step();
}
} catch (Exception e) {
Log.w(TAG, "Run ending due to exception: " + e.getMessage(), e);
final Listener listener = getListener();
if (listener != null) { if (listener != null) {
listener.onRunError(e); try {
} listener.onRunError(e instanceof Exception ? (Exception) e : new Exception(e));
} finally { } catch (Throwable t) {
synchronized (this) { Log.w(TAG, "Exception in onRunError: " + t.getMessage(), t);
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) {
@ -223,7 +299,9 @@ public class SerialInputOutputManager implements Runnable {
} }
int len = mSerialPort.read(buffer, mReadTimeout); int len = mSerialPort.read(buffer, mReadTimeout);
if (len > 0) { if (len > 0) {
if (DEBUG) Log.d(TAG, "Read data len=" + len); if (DEBUG) {
Log.d(TAG, "Read data len=" + len);
}
final Listener listener = getListener(); final Listener listener = getListener();
if (listener != null) { if (listener != null) {
final byte[] data = new byte[len]; final byte[] data = new byte[len];
@ -231,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);
} }

View File

@ -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;
}
}

View File

@ -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;
}
}

View 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;
}
}

View 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);
}
}

View File

@ -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);
}
}

View File

@ -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"));
}
}

View File

@ -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());
}
}