1
0
mirror of https://github.com/mik3y/usb-serial-for-android synced 2025-09-09 17:07:38 +00:00

Compare commits

...

135 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
kai-morich
c82cd284ae support PL2303GC/GB/GT/GL/GE/GS
see https://lore.kernel.org/linux-usb/20190213123000.4656-1-charlesyeh522@gmail.com/
2021-05-13 20:55:15 +02:00
kai-morich
2f23bdfb6d custom baud rates for PL2303TA/TB
see https://lore.kernel.org/r/3aee5708-7961-f464-8c5f-6685d96920d6@IEEE.org
2021-05-11 17:30:09 +02:00
kai-morich
22a685e738 target-sdk-version update from 29 to 30 2021-05-09 08:50:29 +02:00
kai-morich
38527730cd
Merge pull request #289 from rusefi/st_cdc
ST CDC
2021-05-08 19:22:38 +02:00
kai-morich
73ef6c5b53
renamed Troubleshooting wiki page to FAQ 2021-04-26 08:04:00 +02:00
kai-morich
5f94a47b63 read w/o timeout now only throws exception on connection lost
partly revert f4166f34, as there might be unkown reasons for empty response
2021-04-20 22:53:53 +02:00
kai-morich
128d1a00b1 new SerialInputOutputManager.start() method
Previously recommended start action `Executors.newSingleThreadExecutor().submit(ioManager)` did not shutdown the Executor, which caused a thread leak. It's still possible to use old style start, as SerialInputOutputManager continues to implement Runnable interface.
2021-04-16 21:55:22 +02:00
kai-morich
848d4e7713 SerialInputOutputManager: use optimal read buffer size to reduce latency for FTDI and CH34x 2021-04-04 20:55:41 +02:00
kai-morich
c917ac5c83 fixed example app crash 2021-04-02 20:36:12 +02:00
kai-morich
f1d73c04dc fixed some warnings 2021-04-02 20:28:41 +02:00
kai-morich
b6e9dbe40f generate unit test coverage xml for codecov upload 2021-03-26 20:25:59 +01:00
kai-morich
f4166f34a0 read w/o timeout now throws exception on connection lost or buffer to small
SerialInputOutputManager already returned connection lost exception, as the next read failed
2021-03-26 18:11:23 +01:00
kai-morich
2d4d2f78a5
Merge pull request #351 from lambdapioneer/master
Use monotonic clock for timeouts
2021-03-18 08:11:26 +01:00
Daniel Hugenroth
b8c3057967 Use monotonic clock for timeouts 2021-03-16 22:39:00 +00:00
kai-morich
c06ccf70bc really set thread priority in SerialInputOutputManager (#349) 2021-02-27 13:59:30 +01:00
kai-morich
cbed086279 fix write timeout calculation 2021-02-14 14:16:25 +01:00
kai-morich
4ffcc8d0fb simplify write timeout handling 2021-02-13 21:07:21 +01:00
kai-morich
f60414f8ec improve write timeout handling
Return type of write() method changed to void. The return value was redundant before, as it always was the request length or an exception was thrown.

If timeout is reached, write() now throws a SerialTimeoutException with ex.bytesTransferred filled with known transferred bytes.

Added CommonUsbSerialPort.getReadEndpoint() and .getWriteEndpoint() to assist in setting the optimal write buffer size with port.setWriteBufferSize(port.getWriteEndpoint().getMaxPacketSize()).

By default the write buffer size is > MaxPacketSize and the Linux kernel splits writes in chunks. When the timeout occurs, it's unknown how many chunks have already been transferred and the exception typically stores 0. With optimal write buffer size, this value is known and stored in SerialTimeoutException, but due to more kernel round trips write() might take slightly longer().
2021-02-07 16:37:01 +01:00
kai-morich
85d0348844 improve error quality + test for PR #339 2021-01-31 19:58:59 +01:00
kai-morich
fc610a9764 IntDef Parity for better warnings
but no @Intdef for databits, stopbits as these are frequently used with numbers instead of constants
remove redundant modifiers
2021-01-16 23:21:10 +01:00
kai-morich
5519182256
Merge pull request #339 from ybs-github/master
catch exception thrown by `close()` inside `open()`
2021-01-10 10:04:27 +01:00
kai-morich
a807ea91f0
Merge pull request #333 from IljaK/patch-1
Debug mode disable
2020-12-16 20:01:48 +01:00
Ilja
911cf96ba0
Debug mode disable
Ability to disable DEBUG Logging for in/out bytes.
2020-12-16 14:24:43 +02:00
Yehezkiel Syamsuhadi
ebc8d791fc catch exception thrown by close() 2020-12-14 10:23:29 +11:00
kai-morich
6b7d358f1f
move codacy project 2020-12-12 11:40:16 +01:00
kai-morich
2d3f5e73ab
Merge pull request #330 from Sharabaddin/master
dependencies example with current version instead of `Tag` that has to be replaced by each user
2020-12-10 20:45:35 +01:00
Sharabaddin
6ff679d989
ez for start
and fix potential problems
2020-12-10 14:03:45 +02:00
kai-morich
69330e9168
link feature matrix 2020-10-17 12:31:10 +02:00
kai-morich
115fb407b4 coverage fix, gradle update 2020-10-14 20:36:49 +02:00
kai-morich
768f716600 new setBreak() method 2020-10-14 20:36:49 +02:00
kai-morich
1e75f91467 slightly more coverage, local coverage report, dependency update 2020-10-12 21:28:50 +02:00
kai-morich
08a93ec530 PL2303 fix initial input control line values 2020-10-07 21:40:07 +02:00
kai-morich
732e138630 PL2303(HX) support non-standard baud rates 2020-09-28 21:12:50 +02:00
kai-morich
1adf2a9b98 PL2303 throw error on unsupported baud rates
instead of silently falling back to 9600 baud
2020-09-27 09:03:37 +02:00
kai-morich
d63a24762d mention other CP210x devices, remove CP2110 which is a HID device 2020-09-22 07:52:18 +02:00
kai-morich
26999e3626 read with timeout now throws error on connection lost, e.g. device disconnected
and similar connection lost detection for prolific input control lines
2020-09-12 21:17:52 +02:00
kai-morich
c53c3ed0ae check read buffer size 2020-09-06 09:48:10 +02:00
kai-morich
6f4cd0313c FTDI read() now waits until timeout
previously returned after periodic FTDI status response (default 16 msec)
2020-09-05 12:00:37 +02:00
kai-morich
80e8eb8a60 iomanager with configurable threadpriority and higher default to prevent data loss 2020-08-31 22:40:28 +02:00
kai-morich
f443d1f012 iomanager with configurable buffer size 2020-08-31 22:40:28 +02:00
rusefi
aee7fc1b9d ST CDC
See https://www.the-sz.com/products/usbid/index.php?v=0483&p=&n=
2020-06-24 20:38:52 -04:00
61 changed files with 4569 additions and 1407 deletions

View File

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

2
.gitignore vendored
View File

@ -40,4 +40,4 @@ build/
local.properties
*.DS_Store
proguard/
jacoco.exec

5
.idea/.gitignore generated vendored
View File

@ -2,3 +2,8 @@ caches
codeStyles
libraries
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>

6
.idea/compiler.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>

23
.idea/misc.xml generated
View File

@ -1,11 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
<option name="myDefaultNullable" value="androidx.annotation.Nullable" />
<option name="myDefaultNotNull" value="androidx.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="12">
<list size="18">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
@ -18,12 +19,18 @@
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
<item index="10" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
<item index="11" class="java.lang.String" itemvalue="com.android.annotations.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="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>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="11">
<list size="17">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
@ -35,11 +42,17 @@
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
<item index="10" class="java.lang.String" itemvalue="com.android.annotations.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="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>
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" 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" />
</component>
<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$/usb-serial-for-android.iml" filepath="$PROJECT_DIR$/usb-serial-for-android.iml" group="usb-serial-for-android" />
<module fileurl="file://$PROJECT_DIR$/usbSerialExamples/usbSerialExamples.iml" filepath="$PROJECT_DIR$/usbSerialExamples/usbSerialExamples.iml" group="usb-serial-for-android/usbSerialExamples" />
<module fileurl="file://$PROJECT_DIR$/usbSerialForAndroid/usbSerialForAndroid.iml" filepath="$PROJECT_DIR$/usbSerialForAndroid/usbSerialForAndroid.iml" group="usb-serial-for-android/usbSerialForAndroid" />
</modules>
</component>
</project>

View File

@ -3,9 +3,14 @@
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>

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)
[![Jitpack](https://jitpack.io/v/mik3y/usb-serial-for-android.svg)](https://jitpack.io/#mik3y/usb-serial-for-android)
[![Codacy](https://api.codacy.com/project/badge/Grade/4d528e82e35d42d49f659e9b93a9c77d)](https://www.codacy.com/manual/kai-morich/usb-serial-for-android-mik3y?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=mik3y/usb-serial-for-android&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)
# usb-serial-for-android
@ -11,8 +11,7 @@ Android, using the
available since Android 3.1 and working reliably since Android 4.2.
No root access, ADK, or special kernel drivers are required; all drivers are implemented in
Java. You get a raw serial port with `read()`, `write()`, and other basic
functions for use with your own protocols.
Java. You get a raw serial port with `read()`, `write()`, and [other functions](https://github.com/mik3y/usb-serial-for-android/wiki/FAQ#Feature_Matrix) for use with your own protocols.
## Quick Start
@ -27,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
```gradle
dependencies {
implementation 'com.github.mik3y:usb-serial-for-android:Tag'
implementation 'com.github.mik3y:usb-serial-for-android:3.9.0'
}
```
@ -82,7 +97,7 @@ then use direct read/write
or direct write + event driven read:
```java
usbIoManager = new SerialInputOutputManager(usbSerialPort, this);
Executors.newSingleThreadExecutor().submit(usbIoManager);
usbIoManager.start();
...
port.write("hello".getBytes(), WRITE_WAIT_MILLIS);
@ -100,9 +115,10 @@ For a simple example, see
[UsbSerialExamples](https://github.com/mik3y/usb-serial-for-android/blob/master/usbSerialExamples)
folder in this project.
For a more complete example with background service to stay connected while
the app is not visible or rotating, see separate github project
[SimpleUsbTerminal](https://github.com/kai-morich/SimpleUsbTerminal).
See separate github project [SimpleUsbTerminal](https://github.com/kai-morich/SimpleUsbTerminal)
for a more complete example with:
* Background service to stay connected while the app is not visible or rotating
* Flow control
## Probing for Unrecognized Devices
@ -114,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
UsbSerialDrivers from the tree of connected UsbDevices. Normally, you will use
the default prober returned by ``UsbSerialProber.getDefaultProber()``, which
uses the built-in list of well-known VIDs and PIDs that are supported by our
drivers.
uses USB interface types and the built-in list of well-known VIDs and PIDs that
are supported by our drivers.
To use your own set of rules, create and use a custom prober:
```java
// Probe for our custom CDC devices, which use VID 0x1234
// and PIDS 0x0001 and 0x0002.
// Probe for our custom FTDI device, which use VID 0x1234 and PID 0x0001 and 0x0002.
ProbeTable customTable = new ProbeTable();
customTable.addProduct(0x1234, 0x0001, CdcAcmSerialDriver.class);
customTable.addProduct(0x1234, 0x0002, CdcAcmSerialDriver.class);
customTable.addProduct(0x1234, 0x0001, FtdiSerialDriver.class);
customTable.addProduct(0x1234, 0x0002, FtdiSerialDriver.class);
UsbSerialProber prober = new UsbSerialProber(customTable);
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
instantiate driver classes directly if you know what you're doing; just supply
@ -137,23 +154,26 @@ a compatible UsbDevice.
## 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
* Prolific PL2303
* Silabs CP2102, CP2105, ...
* Silabs CP2102, CP210*
* 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
* Digispark using V-USB software USB
* BBC micro:bit using ARM mbed DAPLink firmware
* ...
## Help & Discussion
For common problems, see the
[Troubleshooting](https://github.com/mik3y/usb-serial-for-android/wiki/Troubleshooting)
wiki page.
For common problems, see the [FAQ](https://github.com/mik3y/usb-serial-for-android/wiki/FAQ) wiki page.
Are you using the library? Add your project to
[ProjectsUsingUsbSerialForAndroid](https://github.com/mik3y/usb-serial-for-android/wiki/Projects-Using-usb-serial-for-android).

View File

@ -2,17 +2,17 @@
buildscript {
repositories {
jcenter()
mavenCentral()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.1'
classpath 'com.android.tools.build:gradle:8.9.1'
}
}
allprojects {
repositories {
jcenter()
mavenCentral()
google()
}
}

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.defaults.buildfeatures.buildconfig=true

View File

@ -1,6 +1,6 @@
#Wed Jun 10 08:41:47 CEST 2020
#Tue Oct 13 21:20:09 CEST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.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 {
compileSdkVersion 29
buildToolsVersion '29.0.3'
compileSdkVersion 35
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
@ -11,10 +12,9 @@ android {
defaultConfig {
minSdkVersion 17
targetSdkVersion 28
targetSdkVersion 35
vectorDrawables.useSupportLibrary = true
testInstrumentationRunner "android.test.InstrumentationTestRunner"
missingDimensionStrategy 'device', 'anyDevice'
}
@ -23,10 +23,11 @@ android {
minifyEnabled true
}
}
namespace 'com.hoho.android.usbserial.examples'
}
dependencies {
implementation project(':usbSerialForAndroid')
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.appcompat:appcompat:1.6.1' // later versions have minsdk 21
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"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.hoho.android.usbserial.examples">
xmlns:tools="http://schemas.android.com/tools">
<!-- mipmap/ic_launcher created with Android Studio -> New -> Image Asset using @color/colorPrimary and USB clip art -->
<application
@ -15,9 +14,9 @@
when the settings activity is currently shown -->
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:windowSoftInputMode="stateHidden|adjustResize">
android:windowSoftInputMode="stateHidden|adjustResize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

View File

@ -1,6 +1,6 @@
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.UsbSerialProber;
@ -14,7 +14,8 @@ class CustomProber {
static UsbSerialProber getCustomProber() {
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);
}

View File

@ -5,6 +5,8 @@ import android.content.Context;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.ListFragment;
import android.view.Menu;
@ -25,7 +27,7 @@ import java.util.Locale;
public class DevicesFragment extends ListFragment {
class ListItem {
static class ListItem {
UsbDevice device;
int port;
UsbSerialDriver driver;
@ -37,7 +39,7 @@ public class DevicesFragment extends ListFragment {
}
}
private ArrayList<ListItem> listItems = new ArrayList<>();
private final ArrayList<ListItem> listItems = new ArrayList<>();
private ArrayAdapter<ListItem> listAdapter;
private int baudRate = 19200;
private boolean withIoManager = true;
@ -47,8 +49,9 @@ public class DevicesFragment extends ListFragment {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
listAdapter = new ArrayAdapter<ListItem>(getActivity(), 0, listItems) {
@NonNull
@Override
public View getView(int position, View view, ViewGroup parent) {
public View getView(int position, View view, @NonNull ViewGroup parent) {
ListItem item = listItems.get(position);
if (view == null)
view = getActivity().getLayoutInflater().inflate(R.layout.device_list_item, parent, false);
@ -78,7 +81,7 @@ public class DevicesFragment extends ListFragment {
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_devices, menu);
}
@ -142,7 +145,7 @@ public class DevicesFragment extends ListFragment {
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
public void onListItemClick(@NonNull ListView l, @NonNull View v, int position, long id) {
ListItem item = listItems.get(position-1);
if(item.driver == null) {
Toast.makeText(getActivity(), "no driver", Toast.LENGTH_SHORT).show();

View File

@ -34,7 +34,7 @@ public class MainActivity extends AppCompatActivity implements FragmentManager.O
@Override
protected void onNewIntent(Intent intent) {
if(intent.getAction().equals("android.hardware.usb.action.USB_DEVICE_ATTACHED")) {
if("android.hardware.usb.action.USB_DEVICE_ATTACHED".equals(intent.getAction())) {
TerminalFragment terminal = (TerminalFragment)getSupportFragmentManager().findFragmentByTag("terminal");
if (terminal != null)
terminal.status("USB device detected");

View File

@ -8,6 +8,7 @@ import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@ -27,6 +28,7 @@ import android.widget.ToggleButton;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import com.hoho.android.usbserial.driver.UsbSerialDriver;
@ -38,11 +40,10 @@ import com.hoho.android.usbserial.util.SerialInputOutputManager;
import java.io.IOException;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.concurrent.Executors;
public class TerminalFragment extends Fragment implements SerialInputOutputManager.Listener {
private enum UsbPermission { Unknown, Requested, Granted, Denied };
private enum UsbPermission { Unknown, Requested, Granted, Denied }
private static final String INTENT_ACTION_GRANT_USB = BuildConfig.APPLICATION_ID + ".GRANT_USB";
private static final int WRITE_WAIT_MILLIS = 2000;
@ -51,8 +52,8 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
private int deviceId, portNum, baudRate;
private boolean withIoManager;
private BroadcastReceiver broadcastReceiver;
private Handler mainLooper;
private final BroadcastReceiver broadcastReceiver;
private final Handler mainLooper;
private TextView receiveText;
private ControlLines controlLines;
@ -65,7 +66,7 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals(INTENT_ACTION_GRANT_USB)) {
if(INTENT_ACTION_GRANT_USB.equals(intent.getAction())) {
usbPermission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)
? UsbPermission.Granted : UsbPermission.Denied;
connect();
@ -89,12 +90,22 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
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
public void onResume() {
super.onResume();
getActivity().registerReceiver(broadcastReceiver, new IntentFilter(INTENT_ACTION_GRANT_USB));
if(usbPermission == UsbPermission.Unknown || usbPermission == UsbPermission.Granted)
if(!connected && (usbPermission == UsbPermission.Unknown || usbPermission == UsbPermission.Granted))
mainLooper.post(this::connect);
}
@ -104,7 +115,6 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
status("disconnected");
disconnect();
}
getActivity().unregisterReceiver(broadcastReceiver);
super.onPause();
}
@ -141,6 +151,25 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
if (id == R.id.clear) {
receiveText.setText("");
return true;
} else if( id == R.id.send_break) {
if(!connected) {
Toast.makeText(getActivity(), "not connected", Toast.LENGTH_SHORT).show();
} else {
try {
usbSerialPort.setBreak(true);
Thread.sleep(100); // should show progress bar instead of blocking UI thread
usbSerialPort.setBreak(false);
SpannableStringBuilder spn = new SpannableStringBuilder();
spn.append("send <break>\n");
spn.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorSendText)), 0, spn.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
receiveText.append(spn);
} catch(UnsupportedOperationException ignored) {
Toast.makeText(getActivity(), "BREAK not supported", Toast.LENGTH_SHORT).show();
} catch(Exception e) {
Toast.makeText(getActivity(), "BREAK failed: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
return true;
} else {
return super.onOptionsItemSelected(item);
}
@ -185,7 +214,7 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
status("connection failed: no driver for device");
return;
}
if(driver.getPorts().size() < portNum) {
if(portNum >= driver.getPorts().size()) {
status("connection failed: not enough ports at device");
return;
}
@ -193,7 +222,10 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
UsbDeviceConnection usbConnection = usbManager.openDevice(driver.getDevice());
if(usbConnection == null && usbPermission == UsbPermission.Unknown && !usbManager.hasPermission(driver.getDevice())) {
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);
return;
}
@ -207,10 +239,14 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
try {
usbSerialPort.open(usbConnection);
usbSerialPort.setParameters(baudRate, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);
try{
usbSerialPort.setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE);
}catch (UnsupportedOperationException e){
status("unsupport setparameters");
}
if(withIoManager) {
usbIoManager = new SerialInputOutputManager(usbSerialPort, this);
Executors.newSingleThreadExecutor().submit(usbIoManager);
usbIoManager.start();
}
status("connected");
connected = true;
@ -224,8 +260,10 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
private void disconnect() {
connected = false;
controlLines.stop();
if(usbIoManager != null)
if(usbIoManager != null) {
usbIoManager.setListener(null);
usbIoManager.stop();
}
usbIoManager = null;
try {
usbSerialPort.close();
@ -239,10 +277,10 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
return;
}
try {
byte[] data = (str + '\n').getBytes();
byte[] data = (str + '\n').getBytes();
SpannableStringBuilder spn = new SpannableStringBuilder();
spn.append("send " + data.length + " bytes\n");
spn.append(HexDump.dumpHexString(data)+"\n");
spn.append(HexDump.dumpHexString(data)).append("\n");
spn.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorSendText)), 0, spn.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
receiveText.append(spn);
usbSerialPort.write(data, WRITE_WAIT_MILLIS);
@ -272,7 +310,7 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
SpannableStringBuilder spn = new SpannableStringBuilder();
spn.append("receive " + data.length + " bytes\n");
if(data.length > 0)
spn.append(HexDump.dumpHexString(data)+"\n");
spn.append(HexDump.dumpHexString(data)).append("\n");
receiveText.append(spn);
}
@ -285,8 +323,8 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
class ControlLines {
private static final int refreshInterval = 200; // msec
private Runnable runnable;
private ToggleButton rtsBtn, ctsBtn, dtrBtn, dsrBtn, cdBtn, riBtn;
private final Runnable runnable;
private final ToggleButton rtsBtn, ctsBtn, dtrBtn, dsrBtn, cdBtn, riBtn;
ControlLines(View view) {
runnable = this::run; // w/o explicit Runnable, a new lambda would be created on each postDelayed, which would not be found again by removeCallbacks
@ -329,7 +367,7 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
cdBtn.setChecked(controlLines.contains(UsbSerialPort.ControlLine.CD));
riBtn.setChecked(controlLines.contains(UsbSerialPort.ControlLine.RI));
mainLooper.postDelayed(runnable, refreshInterval);
} catch (IOException e) {
} catch (Exception e) {
status("getControlLines() failed: " + e.getMessage() + " -> stopped control line refresh");
}
}
@ -346,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.RI)) riBtn.setVisibility(View.INVISIBLE);
run();
} catch (IOException e) {
} catch (Exception e) {
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

@ -6,4 +6,8 @@
android:icon="@drawable/ic_delete_white_24dp"
android:title="Clear"
app:showAsAction="always" />
<item
android:id="@+id/send_break"
android:title="Send BREAK"
app:showAsAction="never" />
</menu>

View File

@ -8,13 +8,18 @@
<usb-device vendor-id="1027" product-id="24597" /> <!-- 0x6015: FT230X, FT231X, FT234XD -->
<!-- 0x10C4 / 0xEA??: Silabs CP210x -->
<usb-device vendor-id="4292" product-id="60000" /> <!-- 0xea60: CP2102 -->
<usb-device vendor-id="4292" product-id="60000" /> <!-- 0xea60: CP2102 and other CP210x single port devices -->
<usb-device vendor-id="4292" product-id="60016" /> <!-- 0xea70: CP2105 -->
<usb-device vendor-id="4292" product-id="60017" /> <!-- 0xea71: CP2108 -->
<usb-device vendor-id="4292" product-id="60032" /> <!-- 0xea80: CP2110 -->
<!-- 0x067B / 0x2303: Prolific PL2303 -->
<usb-device vendor-id="1659" product-id="8963" />
<!-- 0x067B / 0x23?3: Prolific PL2303x -->
<usb-device vendor-id="1659" product-id="8963" /> <!-- 0x2303: PL2303HX, HXD, TA, ... -->
<usb-device vendor-id="1659" product-id="9123" /> <!-- 0x23a3: PL2303GC -->
<usb-device vendor-id="1659" product-id="9139" /> <!-- 0x23b3: PL2303GB -->
<usb-device vendor-id="1659" product-id="9155" /> <!-- 0x23c3: PL2303GT -->
<usb-device vendor-id="1659" product-id="9171" /> <!-- 0x23d3: PL2303GL -->
<usb-device vendor-id="1659" product-id="9187" /> <!-- 0x23e3: PL2303GE -->
<usb-device vendor-id="1659" product-id="9203" /> <!-- 0x23f3: PL2303GS -->
<!-- 0x1a86 / 0x?523: Qinheng CH34x -->
<usb-device vendor-id="6790" product-id="21795" /> <!-- 0x5523: CH341A -->
@ -26,4 +31,8 @@
<usb-device vendor-id="1003" product-id="8260" /> <!-- 0x03EB / 0x2044: Atmel Lufa -->
<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="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>

View File

@ -1,17 +1,19 @@
apply plugin: 'com.android.library'
plugins {
id 'com.android.library'
id 'maven-publish'
}
android {
compileSdkVersion 29
buildToolsVersion '29.0.3'
compileSdkVersion 35
defaultConfig {
minSdkVersion 17
targetSdkVersion 28
targetSdkVersion 35
consumerProguardFiles 'proguard-rules.pro'
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
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
]
}
@ -19,16 +21,40 @@ android {
sourceCompatibility 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 {
testImplementation 'junit:junit:4.13'
androidTestImplementation 'com.android.support:support-annotations:28.0.0'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'commons-net:commons-net:3.6'
androidTestImplementation 'org.apache.commons:commons-lang3:3.8.1' // starting with 3.9 requires min-api 26
implementation "androidx.annotation:annotation:1.9.1"
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:5.15.2'
androidTestImplementation 'androidx.appcompat:appcompat:1.6.1' // later versions have minsdk 21
androidTestImplementation 'androidx.test:core:1.5.0' // later versions have minsdk 19
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'

View File

@ -1,13 +1,14 @@
// see https://github.com/mik3y/usb-serial-for-android/wiki/Device-Tests-&-Coverage-Report for instructions
apply plugin: 'jacoco'
android {
flavorDimensions 'device'
flavorDimensions += 'device'
productFlavors {
anyDevice {
// Used as fallback in usbSerialExample/build.gradle -> missingDimensionStrategy, but not for coverage report
dimension 'device'
}
arduino {
mcp2221 {
dimension 'device'
testInstrumentationRunnerArguments = ['test_device_driver': 'CdcAcm']
}
@ -17,7 +18,7 @@ android {
}
cp2102 { // and cp2105 first port
dimension 'device'
testInstrumentationRunnerArguments = ['test_device_driver': 'Cp21xx']
testInstrumentationRunnerArguments = ['test_device_driver': 'Cp21xx', 'test_device_port': '0']
}
cp2105 { // second port
dimension 'device'
@ -25,13 +26,21 @@ android {
}
ft232 { // and ft2232 first port
dimension 'device'
testInstrumentationRunnerArguments = ['test_device_driver': 'Ftdi']
testInstrumentationRunnerArguments = ['test_device_driver': 'Ftdi', 'test_device_port': '0']
}
ft2232 { // second port
dimension 'device'
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'
testInstrumentationRunnerArguments = ['test_device_driver': 'Prolific']
}
@ -39,13 +48,31 @@ android {
buildTypes {
debug {
testCoverageEnabled true
enableUnitTestCoverage true
enableAndroidTestCoverage true
}
}
}
// create report even if tests fail
project.gradle.taskGraph.whenReady {
-> project.tasks.findAll { it.name =~ /connected.+AndroidTest/ }.each {
it.ignoreFailures = true
}
}
task jacocoTestReport(type: JacocoReport, dependsOn: ['compileAnyDeviceDebugSources'
/*, 'testAnyDeviceDebugUnitTest' */
/*, 'create<device>DebugCoverageReport' */]) {
def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*']
def debugTree = fileTree(dir: "$project.buildDir/intermediates/javac/anyDeviceDebug", excludes: fileFilter)
def mainSrc = "$project.projectDir/src/main/java"
reports.xml.required = true
sourceDirectories.from files([mainSrc])
classDirectories.from files([debugTree])
executionData.from fileTree(dir: project.buildDir, includes: [
'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"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hoho.android.usbserial"
android:versionCode="1"
android:versionName="1.0">
<uses-permission android:name="android.permission.INTERNET"/>

View File

@ -7,13 +7,15 @@ package com.hoho.android.usbserial;
import android.content.Context;
import android.hardware.usb.UsbManager;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import android.util.Log;
import com.hoho.android.usbserial.driver.UsbSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialPort;
import com.hoho.android.usbserial.driver.UsbSerialProber;
import com.hoho.android.usbserial.util.TestBuffer;
import com.hoho.android.usbserial.util.UsbWrapper;
import org.junit.Before;
@ -24,12 +26,14 @@ import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.runner.RunWith;
import java.io.IOException;
import java.util.EnumSet;
import java.util.List;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
@ -55,19 +59,19 @@ public class CrossoverTest {
assumeTrue("ignore test for device specific coverage report",
InstrumentationRegistry.getArguments().getString("test_device_driver") == null);
context = InstrumentationRegistry.getContext();
context = ApplicationProvider.getApplicationContext();
usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
List<UsbSerialDriver> availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(usbManager);
assertNotEquals("no USB device found", 0, availableDrivers.size());
if (availableDrivers.size() == 0) {
fail("no USB device found");
} else if (availableDrivers.size() == 1) {
assertEquals(2, availableDrivers.get(0).getPorts().size());
assertEquals("expected device with 2 ports.", 2, availableDrivers.get(0).getPorts().size());
usb1 = new UsbWrapper(context, availableDrivers.get(0), 0);
usb2 = new UsbWrapper(context, availableDrivers.get(0), 1);
} else {
assertEquals(1, availableDrivers.get(0).getPorts().size());
assertEquals(1, availableDrivers.get(1).getPorts().size());
assertEquals("expected 2 devices with 1 port.", 1, availableDrivers.get(0).getPorts().size());
assertEquals("expected 2 devices with 1 port.", 1, availableDrivers.get(1).getPorts().size());
usb1 = new UsbWrapper(context, availableDrivers.get(0), 0);
usb2 = new UsbWrapper(context, availableDrivers.get(1), 0);
}
@ -160,4 +164,53 @@ public class CrossoverTest {
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

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

View File

@ -1,5 +1,7 @@
package com.hoho.android.usbserial.util;
import com.hoho.android.usbserial.driver.UsbSerialPort;
import org.apache.commons.net.telnet.InvalidTelnetOptionException;
import org.apache.commons.net.telnet.TelnetClient;
import org.apache.commons.net.telnet.TelnetCommand;
@ -9,6 +11,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import static org.junit.Assert.assertEquals;
@ -31,7 +34,7 @@ public class TelnetWrapper {
private TelnetClient telnetClient;
private InputStream readStream;
private OutputStream writeStream;
private Integer[] comPortOptionCounter = {0};
private ArrayList<int[]> commandResponse = new ArrayList<>();
public int writeDelay = 0;
public TelnetWrapper(String host, int port) {
@ -47,7 +50,9 @@ public class TelnetWrapper {
telnetClient.addOptionHandler(new TelnetOptionHandler(RFC2217_COM_PORT_OPTION, false, false, false, false) {
@Override
public int[] answerSubnegotiation(int[] suboptionData, int suboptionLength) {
comPortOptionCounter[0] += 1;
int[] data = new int[suboptionLength];
System.arraycopy(suboptionData, 0, data, 0, suboptionLength);
commandResponse.add(data);
return super.answerSubnegotiation(suboptionData, suboptionLength);
}
});
@ -59,18 +64,25 @@ public class TelnetWrapper {
readStream = telnetClient.getInputStream();
}
private int[] doCommand(String name, byte[] command) throws IOException, InterruptedException {
commandResponse.clear();
telnetClient.sendCommand((byte) TelnetCommand.SB);
writeStream.write(command);
telnetClient.sendCommand((byte)TelnetCommand.SE);
for(int i=0; i<TELNET_COMMAND_WAIT; i++) {
if(commandResponse.size() > 0) break;
Thread.sleep(1);
}
assertEquals("RFC2217 " + name+ " w/o response.", 1, commandResponse.size());
//Log.d(TAG, name + " -> " + Arrays.toString(commandResponse.get(0)));
return commandResponse.get(0);
}
public void setUp() throws Exception {
setUpFixtureInt();
telnetClient.sendAYT(1000); // not correctly handled by rfc2217_server.py, but WARNING output "ignoring Telnet command: '\xf6'" is a nice separator between tests
comPortOptionCounter[0] = 0;
telnetClient.sendCommand((byte)TelnetCommand.SB);
writeStream.write(new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_PURGE_DATA, 3});
telnetClient.sendCommand((byte)TelnetCommand.SE);
for(int i=0; i<TELNET_COMMAND_WAIT; i++) {
if(comPortOptionCounter[0] == 1) break;
Thread.sleep(1);
}
assertEquals("telnet connection lost", 1, comPortOptionCounter[0].intValue());
doCommand("purge-data", new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_PURGE_DATA, 3});
writeDelay = 0;
}
@ -92,11 +104,15 @@ public class TelnetWrapper {
// wait full time
public byte[] read() throws Exception {
return read(-1);
return read(-1, -1);
}
public byte[] read(int expectedLength) throws Exception {
long end = System.currentTimeMillis() + TELNET_READ_WAIT;
return read(expectedLength, -1);
}
public byte[] read(int expectedLength, int readWait) throws Exception {
if(readWait == -1)
readWait = TELNET_READ_WAIT;
long end = System.currentTimeMillis() + readWait;
ByteBuffer buf = ByteBuffer.allocate(65536);
while(System.currentTimeMillis() < end) {
if(readStream.available() > 0) {
@ -126,30 +142,11 @@ public class TelnetWrapper {
}
}
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException, InterruptedException, InvalidTelnetOptionException {
comPortOptionCounter[0] = 0;
telnetClient.sendCommand((byte) TelnetCommand.SB);
writeStream.write(new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_BAUDRATE, (byte)(baudRate>>24), (byte)(baudRate>>16), (byte)(baudRate>>8), (byte)baudRate});
telnetClient.sendCommand((byte)TelnetCommand.SE);
telnetClient.sendCommand((byte)TelnetCommand.SB);
writeStream.write(new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_DATASIZE, (byte)dataBits});
telnetClient.sendCommand((byte)TelnetCommand.SE);
telnetClient.sendCommand((byte)TelnetCommand.SB);
writeStream.write(new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_STOPSIZE, (byte)stopBits});
telnetClient.sendCommand((byte)TelnetCommand.SE);
telnetClient.sendCommand((byte)TelnetCommand.SB);
writeStream.write(new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_PARITY, (byte)(parity+1)});
telnetClient.sendCommand((byte)TelnetCommand.SE);
// windows does not like nonstandard baudrates. rfc2217_server.py terminates w/o response
for(int i=0; i<TELNET_COMMAND_WAIT; i++) {
if(comPortOptionCounter[0] == 4) break;
Thread.sleep(1);
}
assertEquals("telnet connection lost", 4, comPortOptionCounter[0].intValue());
public void setParameters(int baudRate, int dataBits, int stopBits, @UsbSerialPort.Parity int parity) throws IOException, InterruptedException, InvalidTelnetOptionException {
doCommand("set-baudrate", new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_BAUDRATE, (byte)(baudRate>>24), (byte)(baudRate>>16), (byte)(baudRate>>8), (byte)baudRate});
doCommand("set-datasize", new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_DATASIZE, (byte)dataBits});
doCommand("set-stopsize", new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_STOPSIZE, (byte)stopBits});
doCommand("set-parity", new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_PARITY, (byte)(parity+1)});
}
}

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

@ -5,13 +5,21 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.os.Process;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
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.UsbSerialPort;
@ -21,24 +29,19 @@ import java.util.Deque;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import androidx.core.content.ContextCompat;
public class UsbWrapper implements SerialInputOutputManager.Listener {
private final static int USB_READ_WAIT = 500;
private final static int USB_WRITE_WAIT = 500;
private final static Integer SERIAL_INPUT_OUTPUT_MANAGER_THREAD_PRIORITY = Process.THREAD_PRIORITY_URGENT_AUDIO;
public final static int USB_READ_WAIT = 500;
public final static int USB_WRITE_WAIT = 500;
private static final String TAG = UsbWrapper.class.getSimpleName();
public enum OpenCloseFlags { NO_IOMANAGER_THREAD, NO_CONTROL_LINE_INIT, NO_DEVICE_CONNECTION };
public enum OpenCloseFlags { NO_IOMANAGER_THREAD, NO_IOMANAGER_START, NO_CONTROL_LINE_INIT, NO_DEVICE_CONNECTION };
// constructor
final Context context;
@ -54,17 +57,30 @@ public class UsbWrapper implements SerialInputOutputManager.Listener {
public boolean readBlock = false;
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) {
this.context = context;
this.serialDriver = serialDriver;
this.devicePort = devicePort;
serialPort = serialDriver.getPorts().get(devicePort);
CommonUsbSerialPort.DEBUG = true;
}
public void setUp() throws Exception {
UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
if (!usbManager.hasPermission(serialDriver.getDevice())) {
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
RingtoneManager.getRingtone(context, notification).play();
Log.d(TAG,"USB permission ...");
final Boolean[] granted = {null};
BroadcastReceiver usbReceiver = new BroadcastReceiver() {
@ -73,17 +89,78 @@ public class UsbWrapper implements SerialInputOutputManager.Listener {
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");
context.registerReceiver(usbReceiver, filter);
ContextCompat.registerReceiver(context, usbReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
usbManager.requestPermission(serialDriver.getDevice(), permissionIntent);
for(int i=0; i<5000; i++) {
if(granted[0] != null) break;
Thread.sleep(1);
}
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() {
@ -112,6 +189,8 @@ public class UsbWrapper implements SerialInputOutputManager.Listener {
if(!flags.contains(OpenCloseFlags.NO_CONTROL_LINE_INIT)) {
serialPort.setDTR(false);
serialPort.setRTS(false);
if (serialPort.getFlowControl() != UsbSerialPort.FlowControl.NONE)
serialPort.setFlowControl(UsbSerialPort.FlowControl.NONE);
}
} catch (Exception ignored) {
}
@ -139,14 +218,10 @@ public class UsbWrapper implements SerialInputOutputManager.Listener {
}
public void open() throws Exception {
open(EnumSet.noneOf(OpenCloseFlags.class), 0);
open(EnumSet.noneOf(OpenCloseFlags.class));
}
public void open(EnumSet<OpenCloseFlags> flags) throws Exception {
open(flags, 0);
}
public void open(EnumSet<OpenCloseFlags> flags, int ioManagerTimeout) throws Exception {
if(!flags.contains(OpenCloseFlags.NO_DEVICE_CONNECTION)) {
UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
deviceConnection = usbManager.openDevice(serialDriver.getDevice());
@ -157,36 +232,62 @@ public class UsbWrapper implements SerialInputOutputManager.Listener {
serialPort.setRTS(true);
}
if(!flags.contains(OpenCloseFlags.NO_IOMANAGER_THREAD)) {
ioManager = new SerialInputOutputManager(serialPort, this) {
@Override
public void run() {
if (SERIAL_INPUT_OUTPUT_MANAGER_THREAD_PRIORITY != null)
Process.setThreadPriority(SERIAL_INPUT_OUTPUT_MANAGER_THREAD_PRIORITY);
super.run();
}
};
ioManager.setReadTimeout(ioManagerTimeout);
ioManager.setWriteTimeout(ioManagerTimeout);
Executors.newSingleThreadExecutor().submit(ioManager);
ioManager = new SerialInputOutputManager(serialPort, this);
if(!flags.contains(OpenCloseFlags.NO_IOMANAGER_START))
ioManager.start();
}
synchronized (readBuffer) {
readBuffer.clear();
}
readError = null;
if (serialDriver instanceof ProlificSerialDriver && ProlificSerialPortWrapper.isDeviceTypeHxn(serialPort)) {
readBufferSize = 768;
}
}
public void waitForIoManagerStarted() throws IOException {
for (int i = 0; i < 100; i++) {
if (SerialInputOutputManager.State.STOPPED != ioManager.getState()) return;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
throw new IOException("IoManager not started");
}
public boolean hasIoManagerThreads() {
int c = 0;
for (Thread thread : Thread.getAllStackTraces().keySet()) {
if (thread.getName().equals(SerialInputOutputManager.class.getSimpleName() + "_read"))
c += 1;
if (thread.getName().equals(SerialInputOutputManager.class.getSimpleName() + "_write"))
c += 1;
}
return c == 2;
}
// wait full time
public byte[] read() throws Exception {
return read(-1);
return read(-1, -1, -1);
}
public byte[] read(int expectedLength) throws Exception {
long end = System.currentTimeMillis() + USB_READ_WAIT;
return read(expectedLength, -1, -1);
}
public byte[] read(int expectedLength, int readBufferSize) throws Exception {
return read(expectedLength, readBufferSize, -1);
}
public byte[] read(int expectedLength, int readBufferSize, int readWait) throws Exception {
if(readWait == -1)
readWait = USB_READ_WAIT;
long end = System.currentTimeMillis() + readWait;
ByteBuffer buf = ByteBuffer.allocate(16*1024);
if(ioManager != null) {
while (System.currentTimeMillis() < end) {
if(readError != null)
throw readError;
throw new IOException(readError);
synchronized (readBuffer) {
while(readBuffer.peek() != null)
buf.put(readBuffer.remove());
@ -197,7 +298,7 @@ public class UsbWrapper implements SerialInputOutputManager.Listener {
}
} else {
byte[] b1 = new byte[256];
byte[] b1 = new byte[readBufferSize > 0 ? readBufferSize : 256];
while (System.currentTimeMillis() < end) {
int len = serialPort.read(b1, USB_READ_WAIT);
if (len > 0)
@ -216,7 +317,7 @@ public class UsbWrapper implements SerialInputOutputManager.Listener {
serialPort.write(data, USB_WRITE_WAIT);
}
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException, InterruptedException {
public void setParameters(int baudRate, int dataBits, int stopBits, @UsbSerialPort.Parity int parity) throws IOException, InterruptedException {
serialPort.setParameters(baudRate, dataBits, stopBits, parity);
if(serialDriver instanceof CdcAcmSerialDriver)
Thread.sleep(10); // arduino_leonardeo_bridge.ini needs some time
@ -233,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
public void onNewData(byte[] data) {
long now = System.currentTimeMillis();
if(readTime == 0)
readTime = now;
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 {
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;

View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hoho.android.usbserial">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@ -8,11 +8,13 @@ package com.hoho.android.usbserial.driver;
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 android.util.Log;
import com.hoho.android.usbserial.util.HexDump;
import com.hoho.android.usbserial.util.UsbUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumSet;
@ -30,6 +32,8 @@ import java.util.Map;
*/
public class CdcAcmSerialDriver implements UsbSerialDriver {
public static final int USB_SUBCLASS_ACM = 2;
private final String TAG = CdcAcmSerialDriver.class.getSimpleName();
private final UsbDevice mDevice;
@ -38,23 +42,33 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
public CdcAcmSerialDriver(UsbDevice device) {
mDevice = device;
mPorts = new ArrayList<>();
int controlInterfaceCount = 0;
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++) {
int ports = countPorts(device);
for (int port = 0; port < ports; port++) {
mPorts.add(new CdcAcmSerialPort(mDevice, port));
}
if(mPorts.size() == 0) {
if (mPorts.size() == 0) {
mPorts.add(new CdcAcmSerialPort(mDevice, -1));
}
}
@SuppressWarnings({"unused"})
public static boolean probe(UsbDevice device) {
return countPorts(device) > 0;
}
private static int countPorts(UsbDevice device) {
int controlInterfaceCount = 0;
int dataInterfaceCount = 0;
for (int i = 0; i < device.getInterfaceCount(); i++) {
if (device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_COMM &&
device.getInterface(i).getInterfaceSubclass() == USB_SUBCLASS_ACM)
controlInterfaceCount++;
if (device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA)
dataInterfaceCount++;
}
return Math.min(controlInterfaceCount, dataInterfaceCount);
}
@Override
public UsbDevice getDevice() {
return mDevice;
@ -95,7 +109,11 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
}
@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) {
Log.d(TAG,"device might be castrated ACM device, trying single interface logic");
openSingleInterface();
@ -131,38 +149,57 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
}
private void openInterface() throws IOException {
Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount());
int controlInterfaceCount = 0;
int dataInterfaceCount = 0;
mControlInterface = null;
mDataInterface = null;
for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
UsbInterface usbInterface = mDevice.getInterface(i);
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM) {
if(controlInterfaceCount == mPortNumber) {
mControlIndex = i;
mControlInterface = usbInterface;
int j = getInterfaceIdFromDescriptors();
Log.d(TAG, "interface count=" + mDevice.getInterfaceCount() + ", IAD=" + j);
if (j >= 0) {
for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
UsbInterface usbInterface = mDevice.getInterface(i);
if (usbInterface.getId() == j || usbInterface.getId() == j+1) {
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM &&
usbInterface.getInterfaceSubclass() == USB_SUBCLASS_ACM) {
mControlIndex = usbInterface.getId();
mControlInterface = usbInterface;
}
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) {
mDataInterface = usbInterface;
}
}
controlInterfaceCount++;
}
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) {
if(dataInterfaceCount == mPortNumber) {
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) {
mControlIndex = usbInterface.getId();
mControlInterface = usbInterface;
}
controlInterfaceCount++;
}
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) {
if (dataInterfaceCount == mPortNumber) {
mDataInterface = usbInterface;
}
dataInterfaceCount++;
}
dataInterfaceCount++;
}
}
if(mControlInterface == null) {
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)) {
throw new IOException("Could not claim control interface");
}
mControlEndpoint = mControlInterface.getEndpoint(0);
if (mControlEndpoint.getDirection() != UsbConstants.USB_DIR_IN || mControlEndpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_INT) {
throw new IOException("Invalid control endpoint");
@ -171,12 +208,10 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
if(mDataInterface == null) {
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)) {
throw new IOException("Could not claim data interface");
}
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)
@ -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 {
int len = mConnection.controlTransfer(
USB_RT_ACM, request, value, mControlIndex, buf, buf != null ? buf.length : 0, 5000);
@ -204,7 +269,7 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
}
@Override
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException {
public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException {
if(baudRate <= 0) {
throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
}
@ -278,40 +343,17 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
public EnumSet<ControlLine> getSupportedControlLines() throws IOException {
return EnumSet.of(ControlLine.RTS, ControlLine.DTR);
}
@Override
public void setBreak(boolean value) throws IOException {
sendAcmControlMessage(SEND_BREAK, value ? 0xffff : 0, null);
}
}
@SuppressWarnings({"unused"})
public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<Integer, int[]>();
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,
});
return supportedDevices;
return new LinkedHashMap<>();
}
}

View File

@ -7,9 +7,11 @@ package com.hoho.android.usbserial.driver;
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 android.util.Log;
import com.hoho.android.usbserial.BuildConfig;
import java.io.IOException;
import java.util.Collections;
@ -20,336 +22,366 @@ import java.util.Map;
public class Ch34xSerialDriver implements UsbSerialDriver {
private static final String TAG = Ch34xSerialDriver.class.getSimpleName();
private static final String TAG = Ch34xSerialDriver.class.getSimpleName();
private final UsbDevice mDevice;
private final UsbSerialPort mPort;
private final UsbDevice mDevice;
private final UsbSerialPort mPort;
private static final int LCR_ENABLE_RX = 0x80;
private static final int LCR_ENABLE_TX = 0x40;
private static final int LCR_MARK_SPACE = 0x20;
private static final int LCR_PAR_EVEN = 0x10;
private static final int LCR_ENABLE_PAR = 0x08;
private static final int LCR_STOP_BITS_2 = 0x04;
private static final int LCR_CS8 = 0x03;
private static final int LCR_CS7 = 0x02;
private static final int LCR_CS6 = 0x01;
private static final int LCR_CS5 = 0x00;
private static final int LCR_ENABLE_RX = 0x80;
private static final int LCR_ENABLE_TX = 0x40;
private static final int LCR_MARK_SPACE = 0x20;
private static final int LCR_PAR_EVEN = 0x10;
private static final int LCR_ENABLE_PAR = 0x08;
private static final int LCR_STOP_BITS_2 = 0x04;
private static final int LCR_CS8 = 0x03;
private static final int LCR_CS7 = 0x02;
private static final int LCR_CS6 = 0x01;
private static final int LCR_CS5 = 0x00;
private static final int GCL_CTS = 0x01;
private static final int GCL_DSR = 0x02;
private static final int GCL_RI = 0x04;
private static final int GCL_CD = 0x08;
private static final int SCL_DTR = 0x20;
private static final int SCL_RTS = 0x40;
private static final int GCL_CTS = 0x01;
private static final int GCL_DSR = 0x02;
private static final int GCL_RI = 0x04;
private static final int GCL_CD = 0x08;
private static final int SCL_DTR = 0x20;
private static final int SCL_RTS = 0x40;
public Ch34xSerialDriver(UsbDevice device) {
mDevice = device;
mPort = new Ch340SerialPort(mDevice, 0);
}
public Ch34xSerialDriver(UsbDevice device) {
mDevice = device;
mPort = new Ch340SerialPort(mDevice, 0);
}
@Override
public UsbDevice getDevice() {
return mDevice;
}
@Override
public UsbDevice getDevice() {
return mDevice;
}
@Override
public List<UsbSerialPort> getPorts() {
return Collections.singletonList(mPort);
}
@Override
public List<UsbSerialPort> getPorts() {
return Collections.singletonList(mPort);
}
public class Ch340SerialPort extends CommonUsbSerialPort {
public class Ch340SerialPort extends CommonUsbSerialPort {
private static final int USB_TIMEOUT_MILLIS = 5000;
private static final int USB_TIMEOUT_MILLIS = 5000;
private final int DEFAULT_BAUD_RATE = 9600;
private final int DEFAULT_BAUD_RATE = 9600;
private boolean dtr = false;
private boolean rts = false;
private boolean dtr = false;
private boolean rts = false;
public Ch340SerialPort(UsbDevice device, int portNumber) {
super(device, portNumber);
}
public Ch340SerialPort(UsbDevice device, int portNumber) {
super(device, portNumber);
}
@Override
public UsbSerialDriver getDriver() {
return Ch34xSerialDriver.this;
}
@Override
public UsbSerialDriver getDriver() {
return Ch34xSerialDriver.this;
}
@Override
protected void openInt(UsbDeviceConnection connection) throws IOException {
for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
UsbInterface usbIface = mDevice.getInterface(i);
if (!mConnection.claimInterface(usbIface, true)) {
throw new IOException("Could not claim data interface");
}
}
@Override
protected void openInt() throws IOException {
for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
UsbInterface usbIface = mDevice.getInterface(i);
if (!mConnection.claimInterface(usbIface, true)) {
throw new IOException("Could not claim data interface");
}
}
UsbInterface dataIface = mDevice.getInterface(mDevice.getInterfaceCount() - 1);
for (int i = 0; i < dataIface.getEndpointCount(); i++) {
UsbEndpoint ep = dataIface.getEndpoint(i);
if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
if (ep.getDirection() == UsbConstants.USB_DIR_IN) {
mReadEndpoint = ep;
} else {
mWriteEndpoint = ep;
}
}
}
UsbInterface dataIface = mDevice.getInterface(mDevice.getInterfaceCount() - 1);
for (int i = 0; i < dataIface.getEndpointCount(); i++) {
UsbEndpoint ep = dataIface.getEndpoint(i);
if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
if (ep.getDirection() == UsbConstants.USB_DIR_IN) {
mReadEndpoint = ep;
} else {
mWriteEndpoint = ep;
}
}
}
initialize();
setBaudRate(DEFAULT_BAUD_RATE);
}
initialize();
setBaudRate(DEFAULT_BAUD_RATE);
}
@Override
protected void closeInt() {
try {
for (int i = 0; i < mDevice.getInterfaceCount(); i++)
mConnection.releaseInterface(mDevice.getInterface(i));
} catch(Exception ignored) {}
}
@Override
protected void closeInt() {
try {
for (int i = 0; i < mDevice.getInterfaceCount(); i++)
mConnection.releaseInterface(mDevice.getInterface(i));
} catch(Exception ignored) {}
}
private int controlOut(int request, int value, int index) {
final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT;
return mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request,
value, index, null, 0, USB_TIMEOUT_MILLIS);
}
private int controlOut(int request, int value, int index) {
final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT;
return mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request,
value, index, null, 0, USB_TIMEOUT_MILLIS);
}
private int controlIn(int request, int value, int index, byte[] buffer) {
final int REQTYPE_DEVICE_TO_HOST = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_IN;
return mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, request,
value, index, buffer, buffer.length, USB_TIMEOUT_MILLIS);
}
private int controlIn(int request, int value, int index, byte[] buffer) {
final int REQTYPE_DEVICE_TO_HOST = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_IN;
return mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, request,
value, index, buffer, buffer.length, USB_TIMEOUT_MILLIS);
}
private void checkState(String msg, int request, int value, int[] expected) throws IOException {
byte[] buffer = new byte[expected.length];
int ret = controlIn(request, value, 0, buffer);
private void checkState(String msg, int request, int value, int[] expected) throws IOException {
byte[] buffer = new byte[expected.length];
int ret = controlIn(request, value, 0, buffer);
if (ret < 0) {
throw new IOException("Failed send cmd [" + msg + "]");
}
if (ret < 0) {
throw new IOException("Failed send cmd [" + msg + "]");
}
if (ret != expected.length) {
throw new IOException("Expected " + expected.length + " bytes, but get " + ret + " [" + msg + "]");
}
if (ret != expected.length) {
throw new IOException("Expected " + expected.length + " bytes, but get " + ret + " [" + msg + "]");
}
for (int i = 0; i < expected.length; i++) {
if (expected[i] == -1) {
continue;
}
for (int i = 0; i < expected.length; i++) {
if (expected[i] == -1) {
continue;
}
int current = buffer[i] & 0xff;
if (expected[i] != current) {
throw new IOException("Expected 0x" + Integer.toHexString(expected[i]) + " byte, but get 0x" + Integer.toHexString(current) + " [" + msg + "]");
}
}
}
int current = buffer[i] & 0xff;
if (expected[i] != current) {
throw new IOException("Expected 0x" + Integer.toHexString(expected[i]) + " byte, but get 0x" + Integer.toHexString(current) + " [" + msg + "]");
}
}
}
private void setControlLines() throws IOException {
if (controlOut(0xa4, ~((dtr ? SCL_DTR : 0) | (rts ? SCL_RTS : 0)), 0) < 0) {
throw new IOException("Failed to set control lines");
}
}
private void setControlLines() throws IOException {
if (controlOut(0xa4, ~((dtr ? SCL_DTR : 0) | (rts ? SCL_RTS : 0)), 0) < 0) {
throw new IOException("Failed to set control lines");
}
}
private byte getStatus() throws IOException {
byte[] buffer = new byte[2];
int ret = controlIn(0x95, 0x0706, 0, buffer);
if (ret < 0)
throw new IOException("Error getting control lines");
return buffer[0];
}
private byte getStatus() throws IOException {
byte[] buffer = new byte[2];
int ret = controlIn(0x95, 0x0706, 0, buffer);
if (ret < 0)
throw new IOException("Error getting control lines");
return buffer[0];
}
private void initialize() throws IOException {
checkState("init #1", 0x5f, 0, new int[]{-1 /* 0x27, 0x30 */, 0x00});
private void initialize() throws IOException {
checkState("init #1", 0x5f, 0, new int[]{-1 /* 0x27, 0x30 */, 0x00});
if (controlOut(0xa1, 0, 0) < 0) {
throw new IOException("Init failed: #2");
}
if (controlOut(0xa1, 0, 0) < 0) {
throw new IOException("Init failed: #2");
}
setBaudRate(DEFAULT_BAUD_RATE);
setBaudRate(DEFAULT_BAUD_RATE);
checkState("init #4", 0x95, 0x2518, new int[]{-1 /* 0x56, c3*/, 0x00});
checkState("init #4", 0x95, 0x2518, new int[]{-1 /* 0x56, c3*/, 0x00});
if (controlOut(0x9a, 0x2518, LCR_ENABLE_RX | LCR_ENABLE_TX | LCR_CS8) < 0) {
throw new IOException("Init failed: #5");
}
if (controlOut(0x9a, 0x2518, LCR_ENABLE_RX | LCR_ENABLE_TX | LCR_CS8) < 0) {
throw new IOException("Init failed: #5");
}
checkState("init #6", 0x95, 0x0706, new int[]{-1/*0xf?*/, -1/*0xec,0xee*/});
checkState("init #6", 0x95, 0x0706, new int[]{-1/*0xf?*/, -1/*0xec,0xee*/});
if (controlOut(0xa1, 0x501f, 0xd90a) < 0) {
throw new IOException("Init failed: #7");
}
if (controlOut(0xa1, 0x501f, 0xd90a) < 0) {
throw new IOException("Init failed: #7");
}
setBaudRate(DEFAULT_BAUD_RATE);
setBaudRate(DEFAULT_BAUD_RATE);
setControlLines();
setControlLines();
checkState("init #10", 0x95, 0x0706, new int[]{-1/* 0x9f, 0xff*/, -1/*0xec,0xee*/});
}
checkState("init #10", 0x95, 0x0706, new int[]{-1/* 0x9f, 0xff*/, -1/*0xec,0xee*/});
}
private void setBaudRate(int baudRate) throws IOException {
final long CH341_BAUDBASE_FACTOR = 1532620800;
final int CH341_BAUDBASE_DIVMAX = 3;
private void setBaudRate(int baudRate) throws IOException {
long factor;
long divisor;
long factor = CH341_BAUDBASE_FACTOR / baudRate;
int divisor = CH341_BAUDBASE_DIVMAX;
if (baudRate == 921600) {
divisor = 7;
factor = 0xf300;
} else {
final long BAUDBASE_FACTOR = 1532620800;
final int BAUDBASE_DIVMAX = 3;
while ((factor > 0xfff0) && divisor > 0) {
factor >>= 3;
divisor--;
}
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) {
factor >>= 3;
divisor--;
}
if (factor > 0xfff0) {
throw new UnsupportedOperationException("Unsupported baud rate: " + baudRate);
}
factor = 0x10000 - factor;
}
if (factor > 0xfff0) {
throw new UnsupportedOperationException("Unsupported baud rate: " + baudRate);
}
divisor |= 0x0080; // else ch341a waits until buffer full
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) {
throw new IOException("Error setting baud rate: #1)");
}
ret = controlOut(0x9a, 0x0f2c, val2);
if (ret < 0) {
throw new IOException("Error setting baud rate: #2");
}
}
factor = 0x10000 - factor;
divisor |= 0x0080; // else ch341a waits until buffer full
int ret = controlOut(0x9a, 0x1312, (int) ((factor & 0xff00) | divisor));
if (ret < 0) {
throw new IOException("Error setting baud rate: #1)");
}
@Override
public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException {
if(baudRate <= 0) {
throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
}
setBaudRate(baudRate);
ret = controlOut(0x9a, 0x0f2c, (int) (factor & 0xff));
if (ret < 0) {
throw new IOException("Error setting baud rate: #2");
}
}
int lcr = LCR_ENABLE_RX | LCR_ENABLE_TX;
@Override
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException {
if(baudRate <= 0) {
throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
}
setBaudRate(baudRate);
switch (dataBits) {
case DATABITS_5:
lcr |= LCR_CS5;
break;
case DATABITS_6:
lcr |= LCR_CS6;
break;
case DATABITS_7:
lcr |= LCR_CS7;
break;
case DATABITS_8:
lcr |= LCR_CS8;
break;
default:
throw new IllegalArgumentException("Invalid data bits: " + dataBits);
}
int lcr = LCR_ENABLE_RX | LCR_ENABLE_TX;
switch (parity) {
case PARITY_NONE:
break;
case PARITY_ODD:
lcr |= LCR_ENABLE_PAR;
break;
case PARITY_EVEN:
lcr |= LCR_ENABLE_PAR | LCR_PAR_EVEN;
break;
case PARITY_MARK:
lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE;
break;
case PARITY_SPACE:
lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE | LCR_PAR_EVEN;
break;
default:
throw new IllegalArgumentException("Invalid parity: " + parity);
}
switch (dataBits) {
case DATABITS_5:
lcr |= LCR_CS5;
break;
case DATABITS_6:
lcr |= LCR_CS6;
break;
case DATABITS_7:
lcr |= LCR_CS7;
break;
case DATABITS_8:
lcr |= LCR_CS8;
break;
default:
throw new IllegalArgumentException("Invalid data bits: " + dataBits);
}
switch (stopBits) {
case STOPBITS_1:
break;
case STOPBITS_1_5:
throw new UnsupportedOperationException("Unsupported stop bits: 1.5");
case STOPBITS_2:
lcr |= LCR_STOP_BITS_2;
break;
default:
throw new IllegalArgumentException("Invalid stop bits: " + stopBits);
}
switch (parity) {
case PARITY_NONE:
break;
case PARITY_ODD:
lcr |= LCR_ENABLE_PAR;
break;
case PARITY_EVEN:
lcr |= LCR_ENABLE_PAR | LCR_PAR_EVEN;
break;
case PARITY_MARK:
lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE;
break;
case PARITY_SPACE:
lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE | LCR_PAR_EVEN;
break;
default:
throw new IllegalArgumentException("Invalid parity: " + parity);
}
int ret = controlOut(0x9a, 0x2518, lcr);
if (ret < 0) {
throw new IOException("Error setting control byte");
}
}
switch (stopBits) {
case STOPBITS_1:
break;
case STOPBITS_1_5:
throw new UnsupportedOperationException("Unsupported stop bits: 1.5");
case STOPBITS_2:
lcr |= LCR_STOP_BITS_2;
break;
default:
throw new IllegalArgumentException("Invalid stop bits: " + stopBits);
}
@Override
public boolean getCD() throws IOException {
return (getStatus() & GCL_CD) == 0;
}
int ret = controlOut(0x9a, 0x2518, lcr);
if (ret < 0) {
throw new IOException("Error setting control byte");
}
}
@Override
public boolean getCTS() throws IOException {
return (getStatus() & GCL_CTS) == 0;
}
@Override
public boolean getCD() throws IOException {
return (getStatus() & GCL_CD) == 0;
}
@Override
public boolean getDSR() throws IOException {
return (getStatus() & GCL_DSR) == 0;
}
@Override
public boolean getCTS() throws IOException {
return (getStatus() & GCL_CTS) == 0;
}
@Override
public boolean getDTR() throws IOException {
return dtr;
}
@Override
public boolean getDSR() throws IOException {
return (getStatus() & GCL_DSR) == 0;
}
@Override
public void setDTR(boolean value) throws IOException {
dtr = value;
setControlLines();
}
@Override
public boolean getDTR() throws IOException {
return dtr;
}
@Override
public boolean getRI() throws IOException {
return (getStatus() & GCL_RI) == 0;
}
@Override
public void setDTR(boolean value) throws IOException {
dtr = value;
setControlLines();
}
@Override
public boolean getRTS() throws IOException {
return rts;
}
@Override
public boolean getRI() throws IOException {
return (getStatus() & GCL_RI) == 0;
}
@Override
public void setRTS(boolean value) throws IOException {
rts = value;
setControlLines();
}
@Override
public boolean getRTS() throws IOException {
return rts;
}
@Override
public EnumSet<ControlLine> getControlLines() throws IOException {
int status = getStatus();
EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class);
if(rts) set.add(ControlLine.RTS);
if((status & GCL_CTS) == 0) set.add(ControlLine.CTS);
if(dtr) set.add(ControlLine.DTR);
if((status & GCL_DSR) == 0) set.add(ControlLine.DSR);
if((status & GCL_CD) == 0) set.add(ControlLine.CD);
if((status & GCL_RI) == 0) set.add(ControlLine.RI);
return set;
}
@Override
public void setRTS(boolean value) throws IOException {
rts = value;
setControlLines();
}
@Override
public EnumSet<ControlLine> getSupportedControlLines() throws IOException {
return EnumSet.allOf(ControlLine.class);
}
@Override
public EnumSet<ControlLine> getControlLines() throws IOException {
int status = getStatus();
EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class);
if(rts) set.add(ControlLine.RTS);
if((status & GCL_CTS) == 0) set.add(ControlLine.CTS);
if(dtr) set.add(ControlLine.DTR);
if((status & GCL_DSR) == 0) set.add(ControlLine.DSR);
if((status & GCL_CD) == 0) set.add(ControlLine.CD);
if((status & GCL_RI) == 0) set.add(ControlLine.RI);
return set;
}
@Override
public void setBreak(boolean value) throws IOException {
byte[] req = new byte[2];
if(controlIn(0x95, 0x1805, 0, req) < 0) {
throw new IOException("Error getting BREAK condition");
}
if(value) {
req[0] &= ~1;
req[1] &= ~0x40;
} else {
req[0] |= 1;
req[1] |= 0x40;
}
int val = (req[1] & 0xff) << 8 | (req[0] & 0xff);
if(controlOut(0x9a, 0x1805, val) < 0) {
throw new IOException("Error setting BREAK condition");
}
}
}
@Override
public EnumSet<ControlLine> getSupportedControlLines() throws IOException {
return EnumSet.allOf(ControlLine.class);
}
}
public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<Integer, int[]>();
supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{
UsbId.QINHENG_CH340,
UsbId.QINHENG_CH341A,
});
return supportedDevices;
}
@SuppressWarnings({"unused"})
public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{
UsbId.QINHENG_CH340,
UsbId.QINHENG_CH341A,
});
return supportedDevices;
}
}

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,8 +10,11 @@ import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbRequest;
import android.os.Build;
import android.util.Log;
import com.hoho.android.usbserial.util.MonotonicClock;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.EnumSet;
@ -23,28 +26,33 @@ import java.util.EnumSet;
*/
public abstract class CommonUsbSerialPort implements UsbSerialPort {
public static boolean DEBUG = false;
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
private static final int MAX_READ_SIZE = 16 * 1024; // = old bulkTransfer limit prior to Android 9
protected final UsbDevice mDevice;
protected final int mPortNumber;
// non-null when open()
protected UsbDeviceConnection mConnection = null;
protected UsbDeviceConnection mConnection;
protected UsbEndpoint mReadEndpoint;
protected UsbEndpoint mWriteEndpoint;
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 final Object mWriteBufferLock = new Object();
public CommonUsbSerialPort(UsbDevice device, int portNumber) {
mDevice = device;
mPortNumber = portNumber;
mWriteBuffer = new byte[DEFAULT_WRITE_BUFFER_SIZE];
}
@Override
@ -63,7 +71,13 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort {
public int getPortNumber() {
return mPortNumber;
}
@Override
public UsbEndpoint getWriteEndpoint() { return mWriteEndpoint; }
@Override
public UsbEndpoint getReadEndpoint() { return mReadEndpoint; }
/**
* Returns the device serial number
* @return serial number
@ -77,11 +91,19 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort {
* 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.
*
* @param bufferSize the size in bytes
* @param bufferSize the size in bytes, <= 0 resets original size
*/
public final void setWriteBufferSize(int bufferSize) {
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;
}
mWriteBuffer = new byte[bufferSize];
@ -93,31 +115,40 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort {
if (mConnection != null) {
throw new IOException("Already open");
}
if(connection == null) {
throw new IllegalArgumentException("Connection is null");
}
mConnection = connection;
boolean ok = false;
try {
openInt(connection);
openInt();
if (mReadEndpoint == null || mWriteEndpoint == null) {
throw new IOException("Could not get read & write endpoints");
}
mUsbRequest = new UsbRequest();
mUsbRequest.initialize(mConnection, mReadEndpoint);
} catch(Exception e) {
close();
throw e;
ok = true;
} finally {
if (!ok) {
try {
close();
} catch (Exception ignored) {}
}
}
}
protected abstract void openInt(UsbDeviceConnection connection) throws IOException;
protected abstract void openInt() throws IOException;
@Override
public void close() throws IOException {
if (mConnection == null) {
throw new IOException("Already closed");
}
try {
mUsbRequest.cancel();
} catch(Exception ignored) {}
UsbRequest usbRequest = mUsbRequest;
mUsbRequest = null;
try {
usbRequest.cancel();
} catch(Exception ignored) {}
try {
closeInt();
} catch(Exception ignored) {}
@ -129,11 +160,43 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort {
protected abstract void closeInt();
@Override
public int read(final byte[] dest, final int timeout) throws IOException {
if(mConnection == null) {
/**
* use simple USB request supported by all devices to test if connection is still valid
*/
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];
int len = mConnection.controlTransfer(0x80 /*DEVICE*/, 0 /*GET_STATUS*/, 0, 0, buf, buf.length, 200);
if(len < 0)
throw new IOException(msg);
}
@Override
public int read(final byte[] dest, final int timeout) throws IOException {
if(dest.length == 0) {
throw new IllegalArgumentException("Read buffer too small");
}
return read(dest, dest.length, timeout);
}
@Override
public int read(final byte[] dest, final int length, final int timeout) throws IOException {return read(dest, length, timeout, true);}
protected int read(final byte[] dest, int length, final int timeout, boolean testConnection) throws IOException {
testConnection(false);
if(length <= 0) {
throw new IllegalArgumentException("Read length too small");
}
length = Math.min(length, dest.length);
final int nread;
if (timeout != 0) {
// bulkTransfer will cause data loss with short timeout + high baud rates + continuous transfer
@ -144,12 +207,17 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort {
// /system/lib64/libusbhost.so (usb_request_wait+192)
// /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
int readMax = Math.min(dest.length, MAX_READ_SIZE);
long endTime = testConnection ? MonotonicClock.millis() + timeout : 0;
int readMax = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) ? length : Math.min(length, MAX_READ_SIZE);
nread = mConnection.bulkTransfer(mReadEndpoint, dest, readMax, timeout);
// Android error propagation is improvable:
// nread == -1 can be: timeout, connection lost, buffer to small, ???
if(nread == -1 && testConnection)
testConnection(MonotonicClock.millis() < endTime);
} else {
final ByteBuffer buf = ByteBuffer.wrap(dest);
if (!mUsbRequest.queue(buf, dest.length)) {
final ByteBuffer buf = ByteBuffer.wrap(dest, 0, length);
if (!mUsbRequest.queue(buf, length)) {
throw new IOException("Queueing USB request failed");
}
final UsbRequest response = mConnection.requestWait();
@ -157,59 +225,83 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort {
throw new IOException("Waiting for USB request failed");
}
nread = buf.position();
// Android error propagation is improvable:
// response != null & nread == 0 can be: connection lost, buffer to small, ???
if(nread == 0) {
testConnection(true);
}
}
if (nread > 0) {
return readFilter(dest, nread);
} else {
return 0;
}
return Math.max(nread, 0);
}
protected int readFilter(final byte[] buffer, int len) throws IOException { return len; }
@Override
public void write(byte[] src, int timeout) throws IOException {write(src, src.length, timeout);}
@Override
public int write(final byte[] src, final int timeout) throws IOException {
public void write(final byte[] src, int length, final int timeout) throws IOException {
int offset = 0;
long startTime = MonotonicClock.millis();
length = Math.min(length, src.length);
if(mConnection == null) {
throw new IOException("Connection closed");
}
while (offset < src.length) {
final int writeLength;
final int amtWritten;
testConnection(false);
while (offset < length) {
int requestTimeout;
final int requestLength;
final int actualLength;
synchronized (mWriteBufferLock) {
final byte[] writeBuffer;
writeLength = 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) {
writeBuffer = src;
} else {
// bulkTransfer does not support offsets, make a copy.
System.arraycopy(src, offset, mWriteBuffer, 0, writeLength);
System.arraycopy(src, offset, mWriteBuffer, 0, requestLength);
writeBuffer = mWriteBuffer;
}
amtWritten = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, writeLength, timeout);
if (timeout == 0 || offset == 0) {
requestTimeout = timeout;
} else {
requestTimeout = (int)(startTime + timeout - MonotonicClock.millis());
if(requestTimeout == 0)
requestTimeout = -1;
}
if (requestTimeout < 0) {
actualLength = -2;
} else {
actualLength = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, requestLength, requestTimeout);
}
}
if (amtWritten <= 0) {
throw new IOException("Error writing " + writeLength
+ " bytes at offset " + offset + " length=" + src.length);
long elapsed = MonotonicClock.millis() - startTime;
if (DEBUG) {
Log.d(TAG, "Wrote " + actualLength + "/" + requestLength + " offset " + offset + "/" + length + " time " + elapsed + "/" + requestTimeout);
}
if (actualLength <= 0) {
String msg = "Error writing " + requestLength + " bytes at offset " + offset + " of total " + src.length + " after " + elapsed + "msec, rc=" + actualLength;
if (timeout != 0) {
// could be buffer full because: writing to fast, stopped by flow control
testConnection(elapsed < timeout, msg);
throw new SerialTimeoutException(msg, offset);
} else {
throw new IOException(msg);
Log.d(TAG, "Wrote amt=" + amtWritten + " attempted=" + writeLength);
offset += amtWritten;
}
}
offset += actualLength;
}
return offset;
}
@Override
public boolean isOpen() {
return mConnection != null;
return mUsbRequest != null;
}
@Override
public abstract void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException;
public abstract void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException;
@Override
public boolean getCD() throws IOException { throw new UnsupportedOperationException(); }
@ -236,14 +328,30 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort {
public void setRTS(boolean value) throws IOException { throw new UnsupportedOperationException(); }
@Override
public abstract EnumSet<ControlLine> getControlLines() throws IOException;
public EnumSet<ControlLine> getControlLines() throws IOException { throw new UnsupportedOperationException(); }
@Override
public abstract EnumSet<ControlLine> getSupportedControlLines() throws IOException;
public EnumSet<ControlLine> getSupportedControlLines() throws IOException { return EnumSet.noneOf(ControlLine.class); }
@Override
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {
throw new UnsupportedOperationException();
public void setFlowControl(FlowControl flowcontrol) throws IOException {
if (flowcontrol != FlowControl.NONE)
throw new UnsupportedOperationException();
}
@Override
public FlowControl getFlowControl() { return mFlowControl; }
@Override
public EnumSet<FlowControl> getSupportedFlowControl() { return EnumSet.of(FlowControl.NONE); }
@Override
public boolean getXON() throws IOException { throw new UnsupportedOperationException(); }
@Override
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { throw new UnsupportedOperationException(); }
@Override
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.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
@ -59,10 +58,16 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
*/
private static final int SILABSER_IFC_ENABLE_REQUEST_CODE = 0x00;
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_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_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_WRITE_CODE = 0x05;
@ -84,6 +89,8 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
/*
* 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_DSR = 0x20;
private static final int STATUS_RI = 0x40;
@ -118,14 +125,14 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
byte[] buffer = new byte[1];
int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, SILABSER_GET_MDMSTS_REQUEST_CODE, 0,
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);
}
return buffer[0];
}
@Override
protected void openInt(UsbDeviceConnection connection) throws IOException {
protected void openInt() throws IOException {
mIsRestrictedPort = mDevice.getInterfaceCount() == 2 && mPortNumber == 1;
if(mPortNumber >= mDevice.getInterfaceCount()) {
throw new IOException("Unknown port number");
@ -147,6 +154,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_ENABLE);
setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, (dtr ? DTR_ENABLE : DTR_DISABLE) | (rts ? RTS_ENABLE : RTS_DISABLE));
setFlowControl(mFlowControl);
}
@Override
@ -166,7 +174,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
(byte) ((baudRate >> 16) & 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);
if (ret < 0) {
throw new IOException("Error setting baud rate");
@ -174,7 +182,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
}
@Override
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException {
public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException {
if(baudRate <= 0) {
throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
}
@ -289,9 +297,11 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
public EnumSet<ControlLine> getControlLines() throws IOException {
byte status = getStatus();
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(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_CD) != 0) set.add(ControlLine.CD);
if((status & STATUS_RI) != 0) set.add(ControlLine.RI);
@ -303,6 +313,73 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
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
// note: only working on some devices, on other devices ignored w/o error
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {
@ -314,16 +391,20 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
}
}
@Override
public void setBreak(boolean value) throws IOException {
setConfigSingle(SILABSER_SET_BREAK_REQUEST_CODE, value ? 1 : 0);
}
}
@SuppressWarnings({"unused"})
public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<Integer, int[]>();
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_SILABS,
new int[] {
UsbId.SILABS_CP2102,
UsbId.SILABS_CP2102, // same ID for CP2101, CP2103, CP2104, CP2109
UsbId.SILABS_CP2105,
UsbId.SILABS_CP2108,
UsbId.SILABS_CP2110
});
return supportedDevices;
}

View File

@ -9,9 +9,10 @@ package com.hoho.android.usbserial.driver;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.util.Log;
import com.hoho.android.usbserial.util.MonotonicClock;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumSet;
@ -63,6 +64,7 @@ public class FtdiSerialDriver implements UsbSerialDriver {
private static final int RESET_REQUEST = 0;
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_DATA_REQUEST = 4;
private static final int GET_MODEM_STATUS_REQUEST = 5;
@ -84,6 +86,7 @@ public class FtdiSerialDriver implements UsbSerialDriver {
private boolean baudRateWithPort = false;
private boolean dtr = false;
private boolean rts = false;
private int breakConfig = 0;
public FtdiSerialPort(UsbDevice device, int portNumber) {
super(device, portNumber);
@ -96,8 +99,8 @@ public class FtdiSerialDriver implements UsbSerialDriver {
@Override
protected void openInt(UsbDeviceConnection connection) throws IOException {
if (!connection.claimInterface(mDevice.getInterface(mPortNumber), true)) {
protected void openInt() throws IOException {
if (!mConnection.claimInterface(mDevice.getInterface(mPortNumber), true)) {
throw new IOException("Could not claim interface " + mPortNumber);
}
if (mDevice.getInterface(mPortNumber).getEndpointCount() < 2) {
@ -118,14 +121,16 @@ public class FtdiSerialDriver implements UsbSerialDriver {
if (result != 0) {
throw new IOException("Init RTS,DTR failed: result=" + result);
}
setFlowControl(mFlowControl);
// mDevice.getVersion() would require API 23
byte[] rawDescriptors = connection.getRawDescriptors();
byte[] rawDescriptors = mConnection.getRawDescriptors();
if(rawDescriptors == null || rawDescriptors.length < 14) {
throw new IOException("Could not get device descriptors");
}
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
@ -136,6 +141,42 @@ public class FtdiSerialDriver implements UsbSerialDriver {
}
@Override
public int read(final byte[] dest, final int timeout) throws IOException
{
if(dest.length <= READ_HEADER_LENGTH) {
throw new IllegalArgumentException("Read buffer 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.
}
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;
if (timeout != 0) {
long endTime = MonotonicClock.millis() + timeout;
do {
nread = super.read(dest, length, Math.max(1, (int)(endTime - MonotonicClock.millis())), false);
} while (nread == READ_HEADER_LENGTH && MonotonicClock.millis() < endTime);
if(nread <= 0)
testConnection(MonotonicClock.millis() < endTime);
} else {
do {
nread = super.read(dest, length, timeout);
} while (nread == READ_HEADER_LENGTH);
}
return readFilter(dest, nread);
}
protected int readFilter(byte[] buffer, int totalBytesRead) throws IOException {
final int maxPacketSize = mReadEndpoint.getMaxPacketSize();
int destPos = 0;
@ -146,13 +187,14 @@ public class FtdiSerialDriver implements UsbSerialDriver {
System.arraycopy(buffer, srcPos + READ_HEADER_LENGTH, buffer, destPos, length);
destPos += length;
}
//Log.d(TAG, "read filter " + totalBytesRead + " -> " + destPos);
return destPos;
}
private void setBaudrate(int baudRate) throws IOException {
int divisor, subdivisor, effectiveBaudRate;
if (baudRate > 3500000) {
throw new IOException("Baud rate to high");
throw new UnsupportedOperationException("Baud rate to high");
} else if(baudRate >= 2500000) {
divisor = 0;
subdivisor = 0;
@ -167,13 +209,13 @@ public class FtdiSerialDriver implements UsbSerialDriver {
subdivisor = divisor & 0x07;
divisor >>= 3;
if (divisor > 0x3fff) // exceeds bit 13 at 183 baud
throw new IOException("Baud rate to low");
throw new UnsupportedOperationException("Baud rate to low");
effectiveBaudRate = (24000000 << 1) / ((divisor << 3) + subdivisor);
effectiveBaudRate = (effectiveBaudRate +1) >> 1;
}
double baudRateError = Math.abs(1.0 - (effectiveBaudRate / (double)baudRate));
if(baudRateError >= 0.031) // can happen only > 1.5Mbaud
throw new IOException(String.format("baud rate deviation %.1f%% is higher than allowed 3%%", baudRateError*100));
throw new UnsupportedOperationException(String.format("Baud rate deviation %.1f%% is higher than allowed 3%%", baudRateError*100));
int value = divisor;
int index = 0;
switch(subdivisor) {
@ -201,7 +243,7 @@ public class FtdiSerialDriver implements UsbSerialDriver {
}
@Override
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException {
public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException {
if(baudRate <= 0) {
throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
}
@ -256,13 +298,14 @@ public class FtdiSerialDriver implements UsbSerialDriver {
if (result != 0) {
throw new IOException("Setting parameters failed: result=" + result);
}
breakConfig = config;
}
private int getStatus() throws IOException {
byte[] data = new byte[2];
int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_MODEM_STATUS_REQUEST,
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);
}
return data[0];
@ -336,13 +379,45 @@ public class FtdiSerialDriver implements UsbSerialDriver {
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
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {
if (purgeWriteBuffers) {
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST,
RESET_PURGE_RX, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("purge write buffer failed: result=" + result);
throw new IOException("Purge write buffer failed: result=" + result);
}
}
@ -350,11 +425,22 @@ public class FtdiSerialDriver implements UsbSerialDriver {
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST,
RESET_PURGE_TX, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("purge read buffer failed: result=" + result);
throw new IOException("Purge read buffer failed: result=" + result);
}
}
}
@Override
public void setBreak(boolean value) throws IOException {
int config = breakConfig;
if(value) config |= 0x4000;
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_DATA_REQUEST,
config, mPortNumber+1,null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Setting BREAK failed: result=" + result);
}
}
public void setLatencyTimer(int latencyTime) throws IOException {
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_LATENCY_TIMER_REQUEST,
latencyTime, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
@ -367,7 +453,7 @@ public class FtdiSerialDriver implements UsbSerialDriver {
byte[] data = new byte[1];
int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_LATENCY_TIMER_REQUEST,
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);
}
return data[0];
@ -375,8 +461,9 @@ public class FtdiSerialDriver implements UsbSerialDriver {
}
@SuppressWarnings({"unused"})
public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<Integer, int[]>();
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_FTDI,
new int[] {
UsbId.FTDI_FT232R,

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;
import android.hardware.usb.UsbDevice;
import android.util.Pair;
import java.lang.reflect.InvocationTargetException;
@ -14,14 +15,14 @@ import java.util.LinkedHashMap;
import java.util.Map;
/**
* Maps (vendor id, product id) pairs to the corresponding serial driver.
*
* @author mike wakerly (opensource@hoho.com)
* Maps (vendor id, product id) pairs to the corresponding serial driver,
* or invoke 'probe' method to check actual USB devices for matching interfaces.
*/
public class ProbeTable {
private final Map<Pair<Integer, Integer>, Class<? extends UsbSerialDriver>> mProbeTable =
new LinkedHashMap<Pair<Integer,Integer>, Class<? extends UsbSerialDriver>>();
private final Map<Pair<Integer, Integer>, Class<? extends UsbSerialDriver>> mVidPidProbeTable =
new LinkedHashMap<>();
private final Map<Method, Class<? extends UsbSerialDriver>> mMethodProbeTable = new LinkedHashMap<>();
/**
* Adds or updates a (vendor, product) pair in the table.
@ -33,7 +34,7 @@ public class ProbeTable {
*/
public ProbeTable addProduct(int vendorId, int productId,
Class<? extends UsbSerialDriver> driverClass) {
mProbeTable.put(Pair.create(vendorId, productId), driverClass);
mVidPidProbeTable.put(Pair.create(vendorId, productId), driverClass);
return this;
}
@ -41,12 +42,11 @@ public class ProbeTable {
* Internal method to add all supported products from
* {@code getSupportedProducts} static method.
*
* @param driverClass
* @return
* @param driverClass to be added
*/
@SuppressWarnings("unchecked")
ProbeTable addDriver(Class<? extends UsbSerialDriver> driverClass) {
final Method method;
void addDriver(Class<? extends UsbSerialDriver> driverClass) {
Method method;
try {
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}
* if no match.
* Returns the driver for the given USB device, or {@code null} if no match.
*
* @param vendorId the USB vendor id
* @param productId the USB product id
* @param usbDevice the USB device to be probed
* @return the driver class matching this pair, or {@code null}
*/
public Class<? extends UsbSerialDriver> findDriver(int vendorId, int productId) {
final Pair<Integer, Integer> pair = Pair.create(vendorId, productId);
return mProbeTable.get(pair);
public Class<? extends UsbSerialDriver> findDriver(final UsbDevice usbDevice) {
final Pair<Integer, Integer> pair = Pair.create(usbDevice.getVendorId(), usbDevice.getProductId());
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,13 +11,14 @@ package com.hoho.android.usbserial.driver;
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 android.util.Log;
import com.hoho.android.usbserial.BuildConfig;
import com.hoho.android.usbserial.util.MonotonicClock;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedHashMap;
@ -28,6 +29,13 @@ public class ProlificSerialDriver implements UsbSerialDriver {
private final String TAG = ProlificSerialDriver.class.getSimpleName();
private final static int[] standardBaudRates = {
75, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600, 14400, 19200,
28800, 38400, 57600, 115200, 128000, 134400, 161280, 201600, 230400, 268800,
403200, 460800, 614400, 806400, 921600, 1228800, 2457600, 3000000, 6000000
};
protected enum DeviceType { DEVICE_TYPE_01, DEVICE_TYPE_T, DEVICE_TYPE_HX, DEVICE_TYPE_HXN }
private final UsbDevice mDevice;
private final UsbSerialPort mPort;
@ -53,31 +61,50 @@ public class ProlificSerialDriver implements UsbSerialDriver {
private static final int USB_RECIP_INTERFACE = 0x01;
private static final int PROLIFIC_VENDOR_READ_REQUEST = 0x01;
private static final int PROLIFIC_VENDOR_WRITE_REQUEST = 0x01;
private static final int VENDOR_READ_REQUEST = 0x01;
private static final int VENDOR_WRITE_REQUEST = 0x01;
private static final int VENDOR_READ_HXN_REQUEST = 0x81;
private static final int VENDOR_WRITE_HXN_REQUEST = 0x80;
private static final int PROLIFIC_VENDOR_OUT_REQTYPE = UsbConstants.USB_DIR_OUT
| UsbConstants.USB_TYPE_VENDOR;
private static final int PROLIFIC_VENDOR_IN_REQTYPE = UsbConstants.USB_DIR_IN
| UsbConstants.USB_TYPE_VENDOR;
private static final int PROLIFIC_CTRL_OUT_REQTYPE = UsbConstants.USB_DIR_OUT
| UsbConstants.USB_TYPE_CLASS | USB_RECIP_INTERFACE;
private static final int VENDOR_OUT_REQTYPE = UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR;
private static final int VENDOR_IN_REQTYPE = UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_VENDOR;
private static final int CTRL_OUT_REQTYPE = UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_CLASS | USB_RECIP_INTERFACE;
private static final int WRITE_ENDPOINT = 0x02;
private static final int READ_ENDPOINT = 0x83;
private static final int INTERRUPT_ENDPOINT = 0x81;
private static final int FLUSH_RX_REQUEST = 0x08; // RX @ Prolific device = write @ usb-serial-for-android library
private static final int RESET_HXN_REQUEST = 0x07;
private static final int FLUSH_RX_REQUEST = 0x08;
private static final int FLUSH_TX_REQUEST = 0x09;
private static final int SET_LINE_REQUEST = 0x20; // same as CDC SET_LINE_CODING
private static final int SET_CONTROL_REQUEST = 0x22; // same as CDC SET_CONTROL_LINE_STATE
private static final int SEND_BREAK_REQUEST = 0x23; // same as CDC SEND_BREAK
private static final int GET_CONTROL_HXN_REQUEST = 0x80;
private static final int GET_CONTROL_REQUEST = 0x87;
private static final int STATUS_NOTIFICATION = 0xa1; // similar to CDC SERIAL_STATE but different length
private static final int SET_LINE_REQUEST = 0x20;
private static final int SET_CONTROL_REQUEST = 0x22;
/* RESET_HXN_REQUEST */
private static final int RESET_HXN_RX_PIPE = 1;
private static final int RESET_HXN_TX_PIPE = 2;
/* SET_CONTROL_REQUEST */
private static final int CONTROL_DTR = 0x01;
private static final int CONTROL_RTS = 0x02;
/* GET_CONTROL_REQUEST */
private static final int GET_CONTROL_FLAG_CD = 0x02;
private static final int GET_CONTROL_FLAG_DSR = 0x04;
private static final int GET_CONTROL_FLAG_RI = 0x01;
private static final int GET_CONTROL_FLAG_CTS = 0x08;
/* GET_CONTROL_HXN_REQUEST */
private static final int GET_CONTROL_HXN_FLAG_CD = 0x40;
private static final int GET_CONTROL_HXN_FLAG_DSR = 0x20;
private static final int GET_CONTROL_HXN_FLAG_RI = 0x80;
private static final int GET_CONTROL_HXN_FLAG_CTS = 0x08;
/* interrupt endpoint read */
private static final int STATUS_FLAG_CD = 0x01;
private static final int STATUS_FLAG_DSR = 0x02;
private static final int STATUS_FLAG_RI = 0x08;
@ -86,23 +113,16 @@ public class ProlificSerialDriver implements UsbSerialDriver {
private static final int STATUS_BUFFER_SIZE = 10;
private static final int STATUS_BYTE_IDX = 8;
private static final int DEVICE_TYPE_HX = 0;
private static final int DEVICE_TYPE_0 = 1;
private static final int DEVICE_TYPE_1 = 2;
private int mDeviceType = DEVICE_TYPE_HX;
protected DeviceType mDeviceType = DeviceType.DEVICE_TYPE_HX;
private UsbEndpoint mInterruptEndpoint;
private int mControlLinesValue = 0;
private int mBaudRate = -1, mDataBits = -1, mStopBits = -1, mParity = -1;
private int mStatus = 0;
private volatile Thread mReadStatusThread = null;
private final Object mReadStatusThreadLock = new Object();
boolean mStopReadStatusThread = false;
private IOException mReadStatusException = null;
private boolean mStopReadStatusThread = false;
private Exception mReadStatusException = null;
public ProlificSerialPort(UsbDevice device, int portNumber) {
@ -114,54 +134,53 @@ public class ProlificSerialDriver implements UsbSerialDriver {
return ProlificSerialDriver.this;
}
private final byte[] inControlTransfer(int requestType, int request,
int value, int index, int length) throws IOException {
private byte[] inControlTransfer(int requestType, int request, int value, int index, int length) throws IOException {
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) {
throw new IOException(
String.format("ControlTransfer with value 0x%x failed: %d",
value, result));
throw new IOException(String.format("ControlTransfer %s 0x%x failed: %d",mDeviceType.name(), value, result));
}
return buffer;
}
private final void outControlTransfer(int requestType, int request,
int value, int index, byte[] data) throws IOException {
private void outControlTransfer(int requestType, int request, int value, int index, byte[] data) throws IOException {
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) {
throw new IOException(
String.format("ControlTransfer with value 0x%x failed: %d",
value, result));
throw new IOException( String.format("ControlTransfer %s 0x%x failed: %d", mDeviceType.name(), value, result));
}
}
private final byte[] vendorIn(int value, int index, int length)
throws IOException {
return inControlTransfer(PROLIFIC_VENDOR_IN_REQTYPE,
PROLIFIC_VENDOR_READ_REQUEST, value, index, length);
private byte[] vendorIn(int value, int index, int length) throws IOException {
int request = (mDeviceType == DeviceType.DEVICE_TYPE_HXN) ? VENDOR_READ_HXN_REQUEST : VENDOR_READ_REQUEST;
return inControlTransfer(VENDOR_IN_REQTYPE, request, value, index, length);
}
private final void vendorOut(int value, int index, byte[] data)
throws IOException {
outControlTransfer(PROLIFIC_VENDOR_OUT_REQTYPE,
PROLIFIC_VENDOR_WRITE_REQUEST, value, index, data);
private void vendorOut(int value, int index, byte[] data) throws IOException {
int request = (mDeviceType == DeviceType.DEVICE_TYPE_HXN) ? VENDOR_WRITE_HXN_REQUEST : VENDOR_WRITE_REQUEST;
outControlTransfer(VENDOR_OUT_REQTYPE, request, value, index, data);
}
private void resetDevice() throws IOException {
purgeHwBuffers(true, true);
}
private final void ctrlOut(int request, int value, int index, byte[] data)
throws IOException {
outControlTransfer(PROLIFIC_CTRL_OUT_REQTYPE, request, value, index,
data);
private void ctrlOut(int request, int value, int index, byte[] data) throws IOException {
outControlTransfer(CTRL_OUT_REQTYPE, request, value, index, data);
}
private boolean testHxStatus() {
try {
inControlTransfer(VENDOR_IN_REQTYPE, VENDOR_READ_REQUEST, 0x8080, 0, 1);
return true;
} catch(IOException ignored) {
return false;
}
}
private void doBlackMagic() throws IOException {
if (mDeviceType == DeviceType.DEVICE_TYPE_HXN)
return;
vendorIn(0x8484, 0, 1);
vendorOut(0x0404, 0, null);
vendorIn(0x8484, 0, 1);
@ -172,7 +191,7 @@ public class ProlificSerialDriver implements UsbSerialDriver {
vendorIn(0x8383, 0, 1);
vendorOut(0, 1, null);
vendorOut(1, 0, null);
vendorOut(2, (mDeviceType == DEVICE_TYPE_HX) ? 0x44 : 0x24, null);
vendorOut(2, (mDeviceType == DeviceType.DEVICE_TYPE_01) ? 0x24 : 0x44, null);
}
private void setControlLines(int newControlLinesValue) throws IOException {
@ -180,76 +199,76 @@ public class ProlificSerialDriver implements UsbSerialDriver {
mControlLinesValue = newControlLinesValue;
}
private final void readStatusThreadFunction() {
private void readStatusThreadFunction() {
try {
byte[] buffer = new byte[STATUS_BUFFER_SIZE];
while (!mStopReadStatusThread) {
byte[] buffer = new byte[STATUS_BUFFER_SIZE];
int readBytesCount = mConnection.bulkTransfer(mInterruptEndpoint,
buffer,
STATUS_BUFFER_SIZE,
500);
long endTime = MonotonicClock.millis() + 500;
int readBytesCount = mConnection.bulkTransfer(mInterruptEndpoint, buffer, STATUS_BUFFER_SIZE, 500);
if(readBytesCount == -1)
testConnection(MonotonicClock.millis() < endTime);
if (readBytesCount > 0) {
if (readBytesCount == STATUS_BUFFER_SIZE) {
mStatus = buffer[STATUS_BYTE_IDX] & 0xff;
if (readBytesCount != STATUS_BUFFER_SIZE) {
throw new IOException("Invalid status notification, expected " + STATUS_BUFFER_SIZE + " bytes, got " + readBytesCount);
} else if(buffer[0] != (byte)STATUS_NOTIFICATION ) {
throw new IOException("Invalid status notification, expected " + STATUS_NOTIFICATION + " request, got " + buffer[0]);
} else {
throw new IOException(
String.format("Invalid CTS / DSR / CD / RI status buffer received, expected %d bytes, but received %d",
STATUS_BUFFER_SIZE,
readBytesCount));
mStatus = buffer[STATUS_BYTE_IDX] & 0xff;
}
}
}
} catch (IOException e) {
mReadStatusException = e;
} catch (Exception e) {
if (isOpen())
mReadStatusException = e;
}
//Log.d(TAG, "end control line status thread " + mStopReadStatusThread + " " + (mReadStatusException == null ? "-" : mReadStatusException.getMessage()));
}
private final int getStatus() throws IOException {
private int getStatus() throws IOException {
if ((mReadStatusThread == null) && (mReadStatusException == null)) {
synchronized (mReadStatusThreadLock) {
if (mReadStatusThread == null) {
byte[] buffer = new byte[STATUS_BUFFER_SIZE];
int readBytes = mConnection.bulkTransfer(mInterruptEndpoint,
buffer,
STATUS_BUFFER_SIZE,
100);
if (readBytes != STATUS_BUFFER_SIZE) {
Log.w(TAG, "Could not read initial CTS / DSR / CD / RI status");
mStatus = 0;
if(mDeviceType == DeviceType.DEVICE_TYPE_HXN) {
byte[] data = vendorIn(GET_CONTROL_HXN_REQUEST, 0, 1);
if ((data[0] & GET_CONTROL_HXN_FLAG_CTS) == 0) mStatus |= STATUS_FLAG_CTS;
if ((data[0] & GET_CONTROL_HXN_FLAG_DSR) == 0) mStatus |= STATUS_FLAG_DSR;
if ((data[0] & GET_CONTROL_HXN_FLAG_CD) == 0) mStatus |= STATUS_FLAG_CD;
if ((data[0] & GET_CONTROL_HXN_FLAG_RI) == 0) mStatus |= STATUS_FLAG_RI;
} else {
mStatus = buffer[STATUS_BYTE_IDX] & 0xff;
byte[] data = vendorIn(GET_CONTROL_REQUEST, 0, 1);
if ((data[0] & GET_CONTROL_FLAG_CTS) == 0) mStatus |= STATUS_FLAG_CTS;
if ((data[0] & GET_CONTROL_FLAG_DSR) == 0) mStatus |= STATUS_FLAG_DSR;
if ((data[0] & GET_CONTROL_FLAG_CD) == 0) mStatus |= STATUS_FLAG_CD;
if ((data[0] & GET_CONTROL_FLAG_RI) == 0) mStatus |= STATUS_FLAG_RI;
}
mReadStatusThread = new Thread(new Runnable() {
@Override
public void run() {
readStatusThreadFunction();
}
});
//Log.d(TAG, "start control line status thread " + mStatus);
mReadStatusThread = new Thread(this::readStatusThreadFunction);
mReadStatusThread.setDaemon(true);
mReadStatusThread.start();
}
}
}
/* throw and clear an exception which occured in the status read thread */
IOException readStatusException = mReadStatusException;
/* throw and clear an exception which occurred in the status read thread */
Exception readStatusException = mReadStatusException;
if (mReadStatusException != null) {
mReadStatusException = null;
throw readStatusException;
throw new IOException(readStatusException);
}
return mStatus;
}
private final boolean testStatusFlag(int flag) throws IOException {
private boolean testStatusFlag(int flag) throws IOException {
return ((getStatus() & flag) == flag);
}
@Override
public void openInt(UsbDeviceConnection connection) throws IOException {
public void openInt() throws IOException {
UsbInterface usbInterface = mDevice.getInterface(0);
if (!connection.claimInterface(usbInterface, true)) {
if (!mConnection.claimInterface(usbInterface, true)) {
throw new IOException("Error claiming Prolific interface 0");
}
@ -271,51 +290,48 @@ public class ProlificSerialDriver implements UsbSerialDriver {
}
}
if (mDevice.getDeviceClass() == 0x02) {
mDeviceType = DEVICE_TYPE_0;
} else {
try {
Method getRawDescriptorsMethod
= mConnection.getClass().getMethod("getRawDescriptors");
byte[] rawDescriptors
= (byte[]) getRawDescriptorsMethod.invoke(mConnection);
byte maxPacketSize0 = rawDescriptors[7];
if (maxPacketSize0 == 64) {
mDeviceType = DEVICE_TYPE_HX;
} else if ((mDevice.getDeviceClass() == 0x00)
|| (mDevice.getDeviceClass() == 0xff)) {
mDeviceType = DEVICE_TYPE_1;
} else {
Log.w(TAG, "Could not detect PL2303 subtype, "
+ "Assuming that it is a HX device");
mDeviceType = DEVICE_TYPE_HX;
}
} catch (NoSuchMethodException e) {
Log.w(TAG, "Method UsbDeviceConnection.getRawDescriptors, "
+ "required for PL2303 subtype detection, not "
+ "available! Assuming that it is a HX device");
mDeviceType = DEVICE_TYPE_HX;
} catch (Exception e) {
Log.e(TAG, "An unexpected exception occured while trying "
+ "to detect PL2303 subtype", e);
}
byte[] rawDescriptors = mConnection.getRawDescriptors();
if(rawDescriptors == null || rawDescriptors.length < 14) {
throw new IOException("Could not get device descriptors");
}
setControlLines(mControlLinesValue);
int usbVersion = (rawDescriptors[3] << 8) + rawDescriptors[2];
int deviceVersion = (rawDescriptors[13] << 8) + rawDescriptors[12];
byte maxPacketSize0 = rawDescriptors[7];
if (mDevice.getDeviceClass() == 0x02 || maxPacketSize0 != 64) {
mDeviceType = DeviceType.DEVICE_TYPE_01;
} else if(usbVersion == 0x200) {
if(deviceVersion == 0x300 && testHxStatus()) {
mDeviceType = DeviceType.DEVICE_TYPE_T; // TA
} else if(deviceVersion == 0x500 && testHxStatus()) {
mDeviceType = DeviceType.DEVICE_TYPE_T; // TB
} else {
mDeviceType = DeviceType.DEVICE_TYPE_HXN;
}
} else {
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();
doBlackMagic();
setControlLines(mControlLinesValue);
setFlowControl(mFlowControl);
}
@Override
public void closeInt() {
try {
mStopReadStatusThread = true;
synchronized (mReadStatusThreadLock) {
if (mReadStatusThread != null) {
try {
mStopReadStatusThread = true;
mReadStatusThread.join();
} catch (Exception e) {
Log.w(TAG, "An error occured while waiting for status read thread", e);
}
mStopReadStatusThread = false;
mReadStatusThread = null;
mReadStatusException = null;
}
}
resetDevice();
@ -325,8 +341,79 @@ public class ProlificSerialDriver implements UsbSerialDriver {
} catch(Exception ignored) {}
}
private int filterBaudRate(int baudRate) {
if(BuildConfig.DEBUG && (baudRate & (3<<29)) == (1<<29)) {
return baudRate & ~(1<<29); // for testing purposes accept without further checks
}
if (baudRate <= 0) {
throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
}
if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) {
return baudRate;
}
for(int br : standardBaudRates) {
if (br == baudRate) {
return baudRate;
}
}
/*
* Formula taken from Linux + FreeBSD.
*
* For TA+TB devices
* baudrate = baseline / (mantissa * 2^exponent)
* where
* mantissa = buf[10:0]
* exponent = buf[15:13 16]
*
* For other devices
* baudrate = baseline / (mantissa * 4^exponent)
* where
* mantissa = buf[8:0]
* exponent = buf[11:9]
*
*/
int baseline, mantissa, exponent, buf, effectiveBaudRate;
baseline = 12000000 * 32;
mantissa = baseline / baudRate;
if (mantissa == 0) { // > unrealistic 384 MBaud
throw new UnsupportedOperationException("Baud rate to high");
}
exponent = 0;
if (mDeviceType == DeviceType.DEVICE_TYPE_T) {
while (mantissa >= 2048) {
if (exponent < 15) {
mantissa >>= 1; /* divide by 2 */
exponent++;
} else { // < 7 baud
throw new UnsupportedOperationException("Baud rate to low");
}
}
buf = mantissa + ((exponent & ~1) << 12) + ((exponent & 1) << 16) + (1 << 31);
effectiveBaudRate = (baseline / mantissa) >> exponent;
} else {
while (mantissa >= 512) {
if (exponent < 7) {
mantissa >>= 2; /* divide by 4 */
exponent++;
} else { // < 45.8 baud
throw new UnsupportedOperationException("Baud rate to low");
}
}
buf = mantissa + (exponent << 9) + (1 << 31);
effectiveBaudRate = (baseline / mantissa) >> (exponent << 1);
}
double baudRateError = Math.abs(1.0 - (effectiveBaudRate / (double)baudRate));
if(baudRateError >= 0.031) // > unrealistic 11.6 Mbaud
throw new UnsupportedOperationException(String.format("Baud rate deviation %.1f%% is higher than allowed 3%%", baudRateError*100));
Log.d(TAG, String.format("baud rate=%d, effective=%d, error=%.1f%%, value=0x%08x, mantissa=%d, exponent=%d",
baudRate, effectiveBaudRate, baudRateError*100, buf, mantissa, exponent));
return buf;
}
@Override
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException {
public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException {
baudRate = filterBaudRate(baudRate);
if ((mBaudRate == baudRate) && (mDataBits == dataBits)
&& (mStopBits == stopBits) && (mParity == parity)) {
// Make sure no action is performed if there is nothing to change
@ -334,10 +421,6 @@ public class ProlificSerialDriver implements UsbSerialDriver {
}
byte[] lineRequestData = new byte[7];
if(baudRate <= 0) {
throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
}
lineRequestData[0] = (byte) (baudRate & 0xff);
lineRequestData[1] = (byte) ((baudRate >> 8) & 0xff);
lineRequestData[2] = (byte) ((baudRate >> 16) & 0xff);
@ -444,7 +527,6 @@ public class ProlificSerialDriver implements UsbSerialDriver {
setControlLines(newControlLinesValue);
}
@Override
public EnumSet<ControlLine> getControlLines() throws IOException {
int status = getStatus();
@ -464,21 +546,73 @@ public class ProlificSerialDriver implements UsbSerialDriver {
}
@Override
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {
if (purgeWriteBuffers) {
vendorOut(FLUSH_RX_REQUEST, 0, null);
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;
}
if (purgeReadBuffers) {
vendorOut(FLUSH_TX_REQUEST, 0, null);
@Override
public EnumSet<FlowControl> getSupportedFlowControl() {
return EnumSet.of(FlowControl.NONE, FlowControl.RTS_CTS, FlowControl.XON_XOFF_INLINE);
}
@Override
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {
if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) {
int index = 0;
if(purgeWriteBuffers) index |= RESET_HXN_RX_PIPE;
if(purgeReadBuffers) index |= RESET_HXN_TX_PIPE;
if(index != 0)
vendorOut(RESET_HXN_REQUEST, index, null);
} else {
if (purgeWriteBuffers)
vendorOut(FLUSH_RX_REQUEST, 0, null);
if (purgeReadBuffers)
vendorOut(FLUSH_TX_REQUEST, 0, null);
}
}
@Override
public void setBreak(boolean value) throws IOException {
ctrlOut(SEND_BREAK_REQUEST, value ? 0xffff : 0, 0, null);
}
}
@SuppressWarnings({"unused"})
public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<Integer, int[]>();
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_PROLIFIC,
new int[] { UsbId.PROLIFIC_PL2303, });
new int[] {
UsbId.PROLIFIC_PL2303,
UsbId.PROLIFIC_PL2303GC,
UsbId.PROLIFIC_PL2303GB,
UsbId.PROLIFIC_PL2303GT,
UsbId.PROLIFIC_PL2303GL,
UsbId.PROLIFIC_PL2303GE,
UsbId.PROLIFIC_PL2303GS,
});
return supportedDevices;
}
}

View File

@ -0,0 +1,16 @@
package com.hoho.android.usbserial.driver;
import java.io.InterruptedIOException;
/**
* Signals that a timeout has occurred on serial write.
* Similar to SocketTimeoutException.
*
* {@see InterruptedIOException#bytesTransferred} may contain bytes transferred
*/
public class SerialTimeoutException extends InterruptedIOException {
public SerialTimeoutException(String s, int bytesTransferred) {
super(s);
this.bytesTransferred = bytesTransferred;
}
}

View File

@ -23,43 +23,31 @@ public final class UsbId {
public static final int FTDI_FT232H = 0x6014;
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 SILABS_CP2102 = 0xea60;
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_CP2108 = 0xea71;
public static final int SILABS_CP2110 = 0xea80;
public static final int VENDOR_PROLIFIC = 0x067b;
public static final int PROLIFIC_PL2303 = 0x2303;
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_PL2303GB = 0x23b3; // "
public static final int PROLIFIC_PL2303GT = 0x23c3; // "
public static final int PROLIFIC_PL2303GL = 0x23d3; // "
public static final int PROLIFIC_PL2303GE = 0x23e3; // "
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 QINHENG_CH340 = 0x7523;
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_ARM = 0x0d28;
public static final int ARM_MBED = 0x0204;
public static final int VENDOR_UNISOC = 0x1782;
public static final int FIBOCOM_L610 = 0x4D10;
public static final int FIBOCOM_L612 = 0x4D12;
private UsbId() {
throw new IllegalAccessError("Non-instantiable class");

View File

@ -10,18 +10,23 @@ import android.hardware.usb.UsbDevice;
import java.util.List;
/**
*
* @author mike wakerly (opensource@hoho.com)
*/
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.
*
* @return the device
*/
public UsbDevice getDevice();
UsbDevice getDevice();
/**
* Returns all available ports for this device. This list must have at least
@ -29,5 +34,5 @@ public interface UsbSerialDriver {
*
* @return the ports
*/
public List<UsbSerialPort> getPorts();
List<UsbSerialPort> getPorts();
}

View File

@ -8,10 +8,15 @@ package com.hoho.android.usbserial.driver;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbManager;
import androidx.annotation.IntDef;
import java.io.Closeable;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.EnumSet;
/**
@ -22,81 +27,82 @@ import java.util.EnumSet;
public interface UsbSerialPort extends Closeable {
/** 5 data bits. */
public static final int DATABITS_5 = 5;
int DATABITS_5 = 5;
/** 6 data bits. */
public static final int DATABITS_6 = 6;
int DATABITS_6 = 6;
/** 7 data bits. */
public static final int DATABITS_7 = 7;
int DATABITS_7 = 7;
/** 8 data bits. */
public static final int DATABITS_8 = 8;
/** No flow control. */
public static final int FLOWCONTROL_NONE = 0;
/** RTS/CTS input flow control. */
public static final int FLOWCONTROL_RTSCTS_IN = 1;
/** RTS/CTS output flow control. */
public static final int FLOWCONTROL_RTSCTS_OUT = 2;
/** XON/XOFF input flow control. */
public static final int FLOWCONTROL_XONXOFF_IN = 4;
/** XON/XOFF output flow control. */
public static final int FLOWCONTROL_XONXOFF_OUT = 8;
int DATABITS_8 = 8;
/** Values for setParameters(..., parity) */
@Retention(RetentionPolicy.SOURCE)
@IntDef({PARITY_NONE, PARITY_ODD, PARITY_EVEN, PARITY_MARK, PARITY_SPACE})
@interface Parity {}
/** No parity. */
public static final int PARITY_NONE = 0;
int PARITY_NONE = 0;
/** Odd parity. */
public static final int PARITY_ODD = 1;
int PARITY_ODD = 1;
/** Even parity. */
public static final int PARITY_EVEN = 2;
int PARITY_EVEN = 2;
/** Mark parity. */
public static final int PARITY_MARK = 3;
int PARITY_MARK = 3;
/** Space parity. */
public static final int PARITY_SPACE = 4;
int PARITY_SPACE = 4;
/** 1 stop bit. */
public static final int STOPBITS_1 = 1;
int STOPBITS_1 = 1;
/** 1.5 stop bits. */
public static final int STOPBITS_1_5 = 3;
int STOPBITS_1_5 = 3;
/** 2 stop bits. */
public static final int STOPBITS_2 = 2;
int STOPBITS_2 = 2;
/** Values for get[Supported]ControlLines() */
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;
/** values for get[Supported]ControlLines() */
public enum ControlLine { RTS, CTS, DTR, DSR, CD, RI };
/**
* Returns the driver used by this port.
*/
public UsbSerialDriver getDriver();
UsbSerialDriver getDriver();
/**
* Returns the currently-bound USB device.
*/
public UsbDevice getDevice();
UsbDevice getDevice();
/**
* Port number within driver.
*/
public int getPortNumber();
int getPortNumber();
/**
* Returns the write endpoint.
* @return write endpoint
*/
UsbEndpoint getWriteEndpoint();
/**
* Returns the read endpoint.
* @return read endpoint
*/
UsbEndpoint getReadEndpoint();
/**
* The serial number of the underlying UsbDeviceConnection, or {@code null}.
*
* @return value from {@link UsbDeviceConnection#getSerial()}
* @throws SecurityException starting with target SDK 29 (Android 10) if permission for USB device is not granted
*/
public String getSerial();
String getSerial();
/**
* Opens and initializes the port. Upon success, caller must ensure that
@ -106,14 +112,14 @@ public interface UsbSerialPort extends Closeable {
* {@link UsbManager#openDevice(android.hardware.usb.UsbDevice)}
* @throws IOException on error opening or initializing the port.
*/
public void open(UsbDeviceConnection connection) throws IOException;
void open(UsbDeviceConnection connection) throws IOException;
/**
* Closes the port and {@link UsbDeviceConnection}
*
* @throws IOException on error closing the port.
*/
public void close() throws IOException;
void close() throws IOException;
/**
* Reads as many bytes as possible into the destination buffer.
@ -123,17 +129,41 @@ public interface UsbSerialPort extends Closeable {
* @return the actual number of bytes read
* @throws IOException if an error occurred during reading
*/
public 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.
*
* @param src the source byte buffer
* @param timeout the timeout for writing in milliseconds, 0 is infinite
* @return the actual number of bytes written
* @throws SerialTimeoutException if timeout reached before sending all data.
* ex.bytesTransferred may contain bytes transferred
* @throws IOException if an error occurred during writing
*/
public int 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.
@ -145,9 +175,9 @@ public interface UsbSerialPort extends Closeable {
* @param parity one of {@link #PARITY_NONE}, {@link #PARITY_ODD},
* {@link #PARITY_EVEN}, {@link #PARITY_MARK}, or {@link #PARITY_SPACE}.
* @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
*/
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException;
void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException;
/**
* Gets the CD (Carrier Detect) bit from the underlying UART.
@ -156,7 +186,7 @@ public interface UsbSerialPort extends Closeable {
* @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported
*/
public boolean getCD() throws IOException;
boolean getCD() throws IOException;
/**
* Gets the CTS (Clear To Send) bit from the underlying UART.
@ -165,7 +195,7 @@ public interface UsbSerialPort extends Closeable {
* @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported
*/
public boolean getCTS() throws IOException;
boolean getCTS() throws IOException;
/**
* Gets the DSR (Data Set Ready) bit from the underlying UART.
@ -174,7 +204,7 @@ public interface UsbSerialPort extends Closeable {
* @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported
*/
public boolean getDSR() throws IOException;
boolean getDSR() throws IOException;
/**
* Gets the DTR (Data Terminal Ready) bit from the underlying UART.
@ -183,7 +213,7 @@ public interface UsbSerialPort extends Closeable {
* @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported
*/
public boolean getDTR() throws IOException;
boolean getDTR() throws IOException;
/**
* Sets the DTR (Data Terminal Ready) bit on the underlying UART, if supported.
@ -192,7 +222,7 @@ public interface UsbSerialPort extends Closeable {
* @throws IOException if an error occurred during writing
* @throws UnsupportedOperationException if not supported
*/
public void setDTR(boolean value) throws IOException;
void setDTR(boolean value) throws IOException;
/**
* Gets the RI (Ring Indicator) bit from the underlying UART.
@ -201,7 +231,7 @@ public interface UsbSerialPort extends Closeable {
* @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported
*/
public boolean getRI() throws IOException;
boolean getRI() throws IOException;
/**
* Gets the RTS (Request To Send) bit from the underlying UART.
@ -210,7 +240,7 @@ public interface UsbSerialPort extends Closeable {
* @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported
*/
public boolean getRTS() throws IOException;
boolean getRTS() throws IOException;
/**
* Sets the RTS (Request To Send) bit on the underlying UART, if supported.
@ -219,7 +249,7 @@ public interface UsbSerialPort extends Closeable {
* @throws IOException if an error occurred during writing
* @throws UnsupportedOperationException if not supported
*/
public void setRTS(boolean value) throws IOException;
void setRTS(boolean value) throws IOException;
/**
* Gets all control line values from the underlying UART, if supported.
@ -227,8 +257,9 @@ public interface UsbSerialPort extends Closeable {
*
* @return EnumSet.contains(...) is {@code true} if set, else {@code false}
* @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported
*/
public EnumSet<ControlLine> getControlLines() throws IOException;
EnumSet<ControlLine> getControlLines() throws IOException;
/**
* Gets all control line supported flags.
@ -236,7 +267,37 @@ public interface UsbSerialPort extends Closeable {
* @return EnumSet.contains(...) is {@code true} if supported, else {@code false}
* @throws IOException if an error occurred during reading
*/
public 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.
@ -246,11 +307,18 @@ public interface UsbSerialPort extends Closeable {
* @throws IOException if an error occurred during flush
* @throws UnsupportedOperationException if not supported
*/
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException;
void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException;
/**
* send BREAK condition.
*
* @param value set/reset
*/
void setBreak(boolean value) throws IOException;
/**
* Returns the current state of the connection.
*/
public boolean isOpen();
boolean isOpen();
}

View File

@ -37,6 +37,8 @@ public class UsbSerialProber {
probeTable.addDriver(FtdiSerialDriver.class);
probeTable.addDriver(ProlificSerialDriver.class);
probeTable.addDriver(Ch34xSerialDriver.class);
probeTable.addDriver(GsmModemSerialDriver.class);
probeTable.addDriver(ChromeCcdSerialDriver.class);
return probeTable;
}
@ -46,11 +48,11 @@ public class UsbSerialProber {
* not require permission from the Android USB system, since it does not
* open any of the devices.
*
* @param usbManager
* @param usbManager usb manager
* @return a list, possibly empty, of all compatible drivers
*/
public List<UsbSerialDriver> findAllDrivers(final UsbManager usbManager) {
final List<UsbSerialDriver> result = new ArrayList<UsbSerialDriver>();
final List<UsbSerialDriver> result = new ArrayList<>();
for (final UsbDevice usbDevice : usbManager.getDeviceList().values()) {
final UsbSerialDriver driver = probeDevice(usbDevice);
@ -69,11 +71,7 @@ public class UsbSerialProber {
* {@code null} if none available.
*/
public UsbSerialDriver probeDevice(final UsbDevice usbDevice) {
final int vendorId = usbDevice.getVendorId();
final int productId = usbDevice.getProductId();
final Class<? extends UsbSerialDriver> driverClass =
mProbeTable.findDriver(vendorId, productId);
final Class<? extends UsbSerialDriver> driverClass = mProbeTable.findDriver(usbDevice);
if (driverClass != null) {
final UsbSerialDriver driver;
try {

View File

@ -19,14 +19,17 @@ package com.hoho.android.usbserial.util;
import java.security.InvalidParameterException;
/**
* Clone of Android's HexDump class, for use in debugging. Cosmetic changes
* only.
* Clone of Android's /core/java/com/android/internal/util/HexDump class, for use in debugging.
* Changes: space separated hex strings
*/
public class HexDump {
private final static char[] HEX_DIGITS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
private HexDump() {
}
public static String dumpHexString(byte[] array) {
return dumpHexString(array, 0, array.length);
}
@ -82,10 +85,12 @@ public class HexDump {
}
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;
for (int i = offset; i < offset + length; i++) {
if (i > offset)
buf[bufIndex++] = ' ';
byte b = array[i];
buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F];
buf[bufIndex++] = HEX_DIGITS[b & 0x0F];
@ -139,13 +144,13 @@ public class HexDump {
throw new InvalidParameterException("Invalid hex char '" + c + "'");
}
/** accepts any separator, e.g. space or newline */
public static byte[] hexStringToByteArray(String hexString) {
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) {
buffer[i / 2] = (byte) ((toByte(hexString.charAt(i)) << 4) | toByte(hexString
.charAt(i + 1)));
for (int i = 0; i < length; i += 3) {
buffer[i / 3] = (byte) ((toByte(hexString.charAt(i)) << 4) | toByte(hexString.charAt(i + 1)));
}
return buffer;

View File

@ -0,0 +1,14 @@
package com.hoho.android.usbserial.util;
public final class MonotonicClock {
private static final long NS_PER_MS = 1_000_000;
private MonotonicClock() {
}
public static long millis() {
return System.nanoTime() / NS_PER_MS;
}
}

View File

@ -6,40 +6,47 @@
package com.hoho.android.usbserial.util;
import android.os.Process;
import android.util.Log;
import com.hoho.android.usbserial.driver.UsbSerialPort;
import java.io.IOException;
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)
*/
public class SerialInputOutputManager implements Runnable {
private static final String TAG = SerialInputOutputManager.class.getSimpleName();
private static final boolean DEBUG = true;
private static final int BUFSIZ = 4096;
/**
* default read timeout is infinite, to avoid data loss with bulkTransfer API
*/
private int mReadTimeout = 0;
private int mWriteTimeout = 0;
private final ByteBuffer mReadBuffer = ByteBuffer.allocate(BUFSIZ);
private final ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ); // Synchronized by 'mWriteBuffer'
public class SerialInputOutputManager {
public enum State {
STOPPED,
STARTING,
RUNNING,
STOPPING
}
private State mState = State.STOPPED; // Synchronized by 'this'
public static boolean DEBUG = false;
private static final String TAG = SerialInputOutputManager.class.getSimpleName();
private static final int BUFSIZ = 4096;
private int mReadTimeout = 0;
private int mWriteTimeout = 0;
private final Object mReadBufferLock = new Object();
private final Object mWriteBufferLock = new Object();
private ByteBuffer mReadBuffer; // default size = getReadEndpoint().getMaxPacketSize()
private ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ);
private int mThreadPriority = Process.THREAD_PRIORITY_URGENT_AUDIO;
private final AtomicReference<State> mState = new AtomicReference<>(State.STOPPED);
private CountDownLatch mStartuplatch = new CountDownLatch(2);
private Listener mListener; // Synchronized by 'this'
private final UsbSerialPort mSerialPort;
@ -47,21 +54,23 @@ public class SerialInputOutputManager implements Runnable {
/**
* Called when new incoming data is available.
*/
public 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.
*/
public void onRunError(Exception e);
void onRunError(Exception e);
}
public SerialInputOutputManager(UsbSerialPort serialPort) {
mSerialPort = serialPort;
mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize());
}
public SerialInputOutputManager(UsbSerialPort serialPort, Listener listener) {
mSerialPort = serialPort;
mListener = listener;
mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize());
}
public synchronized void setListener(Listener listener) {
@ -72,10 +81,25 @@ public class SerialInputOutputManager implements Runnable {
return mListener;
}
/**
* setThreadPriority. By default a higher priority than UI thread is used to prevent data loss
*
* @param threadPriority see {@link Process#setThreadPriority(int)}
* */
public void setThreadPriority(int threadPriority) {
if (!mState.compareAndSet(State.STOPPED, State.STOPPED)) {
throw new IllegalStateException("threadPriority only configurable before SerialInputOutputManager is started");
}
mThreadPriority = threadPriority;
}
/**
* read/write timeout
*/
public void setReadTimeout(int timeout) {
// 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)
throw new IllegalStateException("Set readTimeout before SerialInputOutputManager is started");
if(mReadTimeout == 0 && timeout != 0 && mState.get() != State.STOPPED)
throw new IllegalStateException("readTimeout only configurable before SerialInputOutputManager is started");
mReadTimeout = timeout;
}
@ -91,93 +115,222 @@ public class SerialInputOutputManager implements Runnable {
return mWriteTimeout;
}
/*
* when writeAsync is used, it is recommended to use readTimeout != 0,
* else the write will be delayed until read data is available
/**
* read/write buffer size
*/
public void writeAsync(byte[] data) {
synchronized (mWriteBuffer) {
mWriteBuffer.put(data);
public void setReadBufferSize(int bufferSize) {
if (getReadBufferSize() == bufferSize)
return;
synchronized (mReadBufferLock) {
mReadBuffer = ByteBuffer.allocate(bufferSize);
}
}
public synchronized void stop() {
if (getState() == State.RUNNING) {
Log.i(TAG, "Stop requested");
mState = State.STOPPING;
public int getReadBufferSize() {
return mReadBuffer.capacity();
}
public void setWriteBufferSize(int bufferSize) {
if(getWriteBufferSize() == bufferSize)
return;
synchronized (mWriteBufferLock) {
ByteBuffer newWriteBuffer = ByteBuffer.allocate(bufferSize);
if(mWriteBuffer.position() > 0)
newWriteBuffer.put(mWriteBuffer.array(), 0, mWriteBuffer.position());
mWriteBuffer = newWriteBuffer;
}
}
public synchronized State getState() {
return mState;
public int getWriteBufferSize() {
return mWriteBuffer.capacity();
}
/**
* Continuously services the read and write buffers until {@link #stop()} is
* called, or until a driver exception is raised.
* write data asynchronously
*/
@Override
public void run() {
synchronized (this) {
if (getState() != State.STOPPED) {
throw new IllegalStateException("Already running");
}
mState = State.RUNNING;
public void writeAsync(byte[] data) {
synchronized (mWriteBufferLock) {
mWriteBuffer.put(data);
mWriteBufferLock.notifyAll(); // Notify waiting threads
}
}
Log.i(TAG, "Running ...");
try {
while (true) {
if (getState() != State.RUNNING) {
Log.i(TAG, "Stopping mState=" + getState());
break;
}
step();
/**
* start SerialInputOutputManager in separate threads
*/
public void start() {
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();
}
} catch (Exception e) {
Log.w(TAG, "Run ending due to exception: " + e.getMessage(), e);
final Listener listener = getListener();
if (listener != null) {
listener.onRunError(e);
} else {
throw new IllegalStateException("already started");
}
}
/**
* stop SerialInputOutputManager threads
*
* when using readTimeout == 0 (default), additionally use usbSerialPort.close() to
* interrupt blocking read
*/
public void stop() {
if(mState.compareAndSet(State.RUNNING, State.STOPPING)) {
synchronized (mWriteBufferLock) {
mWriteBufferLock.notifyAll(); // wake up write thread to check the stop condition
}
} finally {
synchronized (this) {
mState = State.STOPPED;
Log.i(TAG, "Stopped");
Log.i(TAG, "Stop requested");
}
}
public State getState() {
return mState.get();
}
/**
* @return true if the thread is still running
*/
private boolean isStillRunning() {
State state = mState.get();
return ((state == State.RUNNING) || (state == State.STARTING))
&& !Thread.currentThread().isInterrupted();
}
/**
* Notify listener of an error
*
* @param e the exception
*/
private void notifyErrorListener(Throwable e) {
Listener listener = getListener();
if (listener != null) {
try {
listener.onRunError(e instanceof Exception ? (Exception) e : new Exception(e));
} catch (Throwable t) {
Log.w(TAG, "Exception in onRunError: " + t.getMessage(), t);
}
}
}
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.
int len = mSerialPort.read(mReadBuffer.array(), mReadTimeout);
byte[] buffer;
synchronized (mReadBufferLock) {
buffer = mReadBuffer.array();
}
int len = mSerialPort.read(buffer, mReadTimeout);
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();
if (listener != null) {
final byte[] data = new byte[len];
mReadBuffer.get(data, 0, len);
System.arraycopy(buffer, 0, data, 0, len);
listener.onNewData(data);
}
mReadBuffer.clear();
}
}
private void stepWrite() throws IOException, InterruptedException {
// Handle outgoing data.
byte[] outBuff = null;
synchronized (mWriteBuffer) {
len = mWriteBuffer.position();
byte[] buffer = null;
synchronized (mWriteBufferLock) {
int len = mWriteBuffer.position();
if (len > 0) {
outBuff = new byte[len];
buffer = new byte[len];
mWriteBuffer.rewind();
mWriteBuffer.get(outBuff, 0, len);
mWriteBuffer.get(buffer, 0, len);
mWriteBuffer.clear();
mWriteBufferLock.notifyAll(); // Notify writeAsync that there is space in the buffer
} else {
mWriteBufferLock.wait();
}
}
if (outBuff != null) {
if (buffer != null) {
if (DEBUG) {
Log.d(TAG, "Writing data len=" + len);
Log.d(TAG, "Writing data len=" + buffer.length);
}
mSerialPort.write(outBuff, 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,89 @@
package com.hoho.android.usbserial.driver;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbEndpoint;
import org.junit.Test;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class FtdiSerialDriverTest {
private final UsbDevice usbDevice = mock(UsbDevice.class);
private final UsbEndpoint readEndpoint = mock(UsbEndpoint.class);
private void initBuf(byte[] buf) {
for(int i=0; i<buf.length; i++)
buf[i] = (byte) i;
}
private boolean testBuf(byte[] buf, int len) {
byte j = 2;
for(int i=0; i<len; i++) {
if(buf[i]!=j)
return false;
j++;
if(j % 64 == 0)
j+=2;
}
return true;
}
@Test
public void readFilter() throws Exception {
byte[] buf = new byte[2048];
int len;
when(usbDevice.getInterfaceCount()).thenReturn(1);
when(readEndpoint.getMaxPacketSize()).thenReturn(64);
FtdiSerialDriver driver = new FtdiSerialDriver(usbDevice);
FtdiSerialDriver.FtdiSerialPort port = (FtdiSerialDriver.FtdiSerialPort) driver.getPorts().get(0);
port.mReadEndpoint = readEndpoint;
len = port.readFilter(buf, 0);
assertEquals(len, 0);
assertThrows(IOException.class, () -> port.readFilter(buf, 1));
initBuf(buf);
len = port.readFilter(buf, 2);
assertEquals(len, 0);
initBuf(buf);
len = port.readFilter(buf, 3);
assertEquals(len, 1);
assertTrue(testBuf(buf, len));
initBuf(buf);
len = port.readFilter(buf, 4);
assertEquals(len, 2);
assertTrue(testBuf(buf, len));
initBuf(buf);
len = port.readFilter(buf, 64);
assertEquals(len, 62);
assertTrue(testBuf(buf, len));
assertThrows(IOException.class, () -> port.readFilter(buf, 65));
initBuf(buf);
len = port.readFilter(buf, 66);
assertEquals(len, 62);
assertTrue(testBuf(buf, len));
initBuf(buf);
len = port.readFilter(buf, 68);
assertEquals(len, 64);
assertTrue(testBuf(buf, len));
initBuf(buf);
len = port.readFilter(buf, 16*64+11);
assertEquals(len, 16*62+9);
assertTrue(testBuf(buf, len));
}
}

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