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

Compare commits

...

139 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
kai-morich
4f2d6c73a4
list all supported FTDI devices 2020-08-24 17:32:03 +02:00
kai-morich
698f590d58 restored UsbId.FTDI_FT231X
same ID for FT230X, FT231X, FT234XD
tested with FT230X
2020-08-23 20:44:34 +02:00
kai-morich
f36756dc86
Update CHANGELOG.txt 2020-08-20 07:49:50 +02:00
kai-morich
73d669c4dc remove FT231X also from device_filter.xml 2020-08-01 12:24:54 +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
62 changed files with 4584 additions and 1403 deletions

View File

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

2
.gitignore vendored
View File

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

5
.idea/.gitignore generated vendored
View File

@ -2,3 +2,8 @@ caches
codeStyles codeStyles
libraries libraries
workspace.xml workspace.xml
androidTestResultsUserPreferences.xml
appInsightsSettings.xml
deploymentTargetDropDown.xml
deploymentTargetSelector.xml
other.xml

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

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

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"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="NullableNotNullManager"> <component name="NullableNotNullManager">
<option name="myDefaultNullable" value="android.support.annotation.Nullable" /> <option name="myDefaultNullable" value="androidx.annotation.Nullable" />
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" /> <option name="myDefaultNotNull" value="androidx.annotation.NonNull" />
<option name="myNullables"> <option name="myNullables">
<value> <value>
<list size="12"> <list size="18">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" /> <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" /> <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" /> <item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
@ -18,12 +19,18 @@
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" /> <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="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="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> </list>
</value> </value>
</option> </option>
<option name="myNotNulls"> <option name="myNotNulls">
<value> <value>
<list size="11"> <list size="17">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" /> <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" /> <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" /> <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
@ -35,11 +42,17 @@
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" /> <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="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="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> </list>
</value> </value>
</option> </option>
</component> </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" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

10
.idea/modules.xml generated
View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/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"> <component name="RunConfigurationProducerService">
<option name="ignoredProducers"> <option name="ignoredProducers">
<set> <set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" /> <option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" /> <option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" /> <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> </set>
</option> </option>
</component> </component>

View File

@ -1,4 +1,7 @@
Current Version (in development) For recent changelog look here:
* https://github.com/mik3y/usb-serial-for-android/releases
v0.2.0 (unreleased)
* Gradle, Android Studio support. * Gradle, Android Studio support.
* New driver: CP2102 (thanks Ducky). * New driver: CP2102 (thanks Ducky).
* New prober support: LUFA Virtual Serial, Leaflabs Maple, Teensyduino. * New prober support: LUFA Virtual Serial, Leaflabs Maple, Teensyduino.

View File

@ -1,6 +1,6 @@
[![Actions Status](https://github.com/mik3y/usb-serial-for-android/workflows/build/badge.svg)](https://github.com/mik3y/usb-serial-for-android/actions) [![Actions Status](https://github.com/mik3y/usb-serial-for-android/workflows/build/badge.svg)](https://github.com/mik3y/usb-serial-for-android/actions)
[![Jitpack](https://jitpack.io/v/mik3y/usb-serial-for-android.svg)](https://jitpack.io/#mik3y/usb-serial-for-android) [![Jitpack](https://jitpack.io/v/mik3y/usb-serial-for-android.svg)](https://jitpack.io/#mik3y/usb-serial-for-android)
[![Codacy](https://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) [![codecov](https://codecov.io/gh/mik3y/usb-serial-for-android/branch/master/graph/badge.svg)](https://codecov.io/gh/mik3y/usb-serial-for-android)
# usb-serial-for-android # usb-serial-for-android
@ -11,8 +11,7 @@ Android, using the
available since Android 3.1 and working reliably since Android 4.2. 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 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 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.
functions for use with your own protocols.
## Quick Start ## 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 Add library to dependencies
```gradle ```gradle
dependencies { 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: or direct write + event driven read:
```java ```java
usbIoManager = new SerialInputOutputManager(usbSerialPort, this); usbIoManager = new SerialInputOutputManager(usbSerialPort, this);
Executors.newSingleThreadExecutor().submit(usbIoManager); usbIoManager.start();
... ...
port.write("hello".getBytes(), WRITE_WAIT_MILLIS); 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) [UsbSerialExamples](https://github.com/mik3y/usb-serial-for-android/blob/master/usbSerialExamples)
folder in this project. folder in this project.
For a more complete example with background service to stay connected while See separate github project [SimpleUsbTerminal](https://github.com/kai-morich/SimpleUsbTerminal)
the app is not visible or rotating, see separate github project for a more complete example with:
[SimpleUsbTerminal](https://github.com/kai-morich/SimpleUsbTerminal). * Background service to stay connected while the app is not visible or rotating
* Flow control
## Probing for Unrecognized Devices ## Probing for Unrecognized Devices
@ -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 UsbSerialProber is a class to help you find and instantiate compatible
UsbSerialDrivers from the tree of connected UsbDevices. Normally, you will use UsbSerialDrivers from the tree of connected UsbDevices. Normally, you will use
the default prober returned by ``UsbSerialProber.getDefaultProber()``, which the default prober returned by ``UsbSerialProber.getDefaultProber()``, which
uses the built-in list of well-known VIDs and PIDs that are supported by our uses USB interface types and the built-in list of well-known VIDs and PIDs that
drivers. are supported by our drivers.
To use your own set of rules, create and use a custom prober: To use your own set of rules, create and use a custom prober:
```java ```java
// Probe for our custom CDC devices, which use VID 0x1234 // Probe for our custom FTDI device, which use VID 0x1234 and PID 0x0001 and 0x0002.
// and PIDS 0x0001 and 0x0002.
ProbeTable customTable = new ProbeTable(); ProbeTable customTable = new ProbeTable();
customTable.addProduct(0x1234, 0x0001, CdcAcmSerialDriver.class); customTable.addProduct(0x1234, 0x0001, FtdiSerialDriver.class);
customTable.addProduct(0x1234, 0x0002, CdcAcmSerialDriver.class); customTable.addProduct(0x1234, 0x0002, FtdiSerialDriver.class);
UsbSerialProber prober = new UsbSerialProber(customTable); UsbSerialProber prober = new UsbSerialProber(customTable);
List<UsbSerialDriver> drivers = prober.findAllDrivers(usbManager); List<UsbSerialDriver> drivers = prober.findAllDrivers(usbManager);
// ... // ...
``` ```
*Note*: as of v3.5.0 this library detects CDC/ACM devices by USB interface types instead of fixed VID+PID,
so custom probers are typically not required any more for CDC/ACM devices.
Of course, nothing requires you to use UsbSerialProber at all: you can Of course, nothing requires you to use UsbSerialProber at all: you can
instantiate driver classes directly if you know what you're doing; just supply instantiate driver classes directly if you know what you're doing; just supply
@ -137,23 +154,26 @@ a compatible UsbDevice.
## Compatible Devices ## Compatible Devices
This library supports USB to serial converter chips: This library supports USB to serial converter chips with specific drivers
* FTDI FT232, FT2232, ... * FTDI FT232R, FT232H, FT2232H, FT4232H, FT230X, FT231X, FT234XD
* Prolific PL2303 * Prolific PL2303
* Silabs CP2102, CP2105, ... * Silabs CP2102, CP210*
* Qinheng CH340, CH341A * Qinheng CH340, CH341A
and devices implementing the CDC/ACM protocol like some other device specific drivers
* GsmModem devices, e.g. for Unisoc based Fibocom GSM modems
* Chrome OS CCD (Closed Case Debugging)
and devices implementing the generic CDC/ACM protocol like
* Qinheng CH9102
* Microchip MCP2221
* Arduino using ATmega32U4 * Arduino using ATmega32U4
* Digispark using V-USB software USB * Digispark using V-USB software USB
* BBC micro:bit using ARM mbed DAPLink firmware
* ... * ...
## Help & Discussion ## Help & Discussion
For common problems, see the For common problems, see the [FAQ](https://github.com/mik3y/usb-serial-for-android/wiki/FAQ) wiki page.
[Troubleshooting](https://github.com/mik3y/usb-serial-for-android/wiki/Troubleshooting)
wiki page.
Are you using the library? Add your project to 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). [ProjectsUsingUsbSerialForAndroid](https://github.com/mik3y/usb-serial-for-android/wiki/Projects-Using-usb-serial-for-android).

View File

@ -2,17 +2,17 @@
buildscript { buildscript {
repositories { repositories {
jcenter() mavenCentral()
google() google()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.0.1' classpath 'com.android.tools.build:gradle:8.9.1'
} }
} }
allprojects { allprojects {
repositories { repositories {
jcenter() mavenCentral()
google() 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.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 distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.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 { android {
compileSdkVersion 29 compileSdkVersion 35
buildToolsVersion '29.0.3'
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
@ -11,10 +12,9 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 17 minSdkVersion 17
targetSdkVersion 28 targetSdkVersion 35
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
testInstrumentationRunner "android.test.InstrumentationTestRunner"
missingDimensionStrategy 'device', 'anyDevice' missingDimensionStrategy 'device', 'anyDevice'
} }
@ -23,10 +23,11 @@ android {
minifyEnabled true minifyEnabled true
} }
} }
namespace 'com.hoho.android.usbserial.examples'
} }
dependencies { dependencies {
implementation project(':usbSerialForAndroid') implementation project(':usbSerialForAndroid')
implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.appcompat:appcompat:1.6.1' // later versions have minsdk 21
implementation 'com.google.android.material:material:1.1.0' implementation 'com.google.android.material:material:1.11.0' // later versions have minsdk 19
} }

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools">
package="com.hoho.android.usbserial.examples">
<!-- mipmap/ic_launcher created with Android Studio -> New -> Image Asset using @color/colorPrimary and USB clip art --> <!-- mipmap/ic_launcher created with Android Studio -> New -> Image Asset using @color/colorPrimary and USB clip art -->
<application <application
@ -15,9 +14,9 @@
when the settings activity is currently shown --> when the settings activity is currently shown -->
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:label="@string/app_name"
android:launchMode="singleTask" android:launchMode="singleTask"
android:windowSoftInputMode="stateHidden|adjustResize"> android:windowSoftInputMode="stateHidden|adjustResize"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />

View File

@ -1,6 +1,6 @@
package com.hoho.android.usbserial.examples; package com.hoho.android.usbserial.examples;
import com.hoho.android.usbserial.driver.CdcAcmSerialDriver; import com.hoho.android.usbserial.driver.FtdiSerialDriver;
import com.hoho.android.usbserial.driver.ProbeTable; import com.hoho.android.usbserial.driver.ProbeTable;
import com.hoho.android.usbserial.driver.UsbSerialProber; import com.hoho.android.usbserial.driver.UsbSerialProber;
@ -14,7 +14,8 @@ class CustomProber {
static UsbSerialProber getCustomProber() { static UsbSerialProber getCustomProber() {
ProbeTable customTable = new ProbeTable(); ProbeTable customTable = new ProbeTable();
customTable.addProduct(0x16d0, 0x087e, CdcAcmSerialDriver.class); // e.g. Digispark CDC customTable.addProduct(0x1234, 0x0001, FtdiSerialDriver.class); // e.g. device with custom VID+PID
customTable.addProduct(0x1234, 0x0002, FtdiSerialDriver.class); // e.g. device with custom VID+PID
return new UsbSerialProber(customTable); return new UsbSerialProber(customTable);
} }

View File

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

View File

@ -34,7 +34,7 @@ public class MainActivity extends AppCompatActivity implements FragmentManager.O
@Override @Override
protected void onNewIntent(Intent intent) { 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"); TerminalFragment terminal = (TerminalFragment)getSupportFragmentManager().findFragmentByTag("terminal");
if (terminal != null) if (terminal != null)
terminal.status("USB device detected"); terminal.status("USB device detected");

View File

@ -8,6 +8,7 @@ import android.content.IntentFilter;
import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager; import android.hardware.usb.UsbManager;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
@ -27,6 +28,7 @@ import android.widget.ToggleButton;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import com.hoho.android.usbserial.driver.UsbSerialDriver; import com.hoho.android.usbserial.driver.UsbSerialDriver;
@ -38,11 +40,10 @@ import com.hoho.android.usbserial.util.SerialInputOutputManager;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.concurrent.Executors;
public class TerminalFragment extends Fragment implements SerialInputOutputManager.Listener { 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 String INTENT_ACTION_GRANT_USB = BuildConfig.APPLICATION_ID + ".GRANT_USB";
private static final int WRITE_WAIT_MILLIS = 2000; 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 int deviceId, portNum, baudRate;
private boolean withIoManager; private boolean withIoManager;
private BroadcastReceiver broadcastReceiver; private final BroadcastReceiver broadcastReceiver;
private Handler mainLooper; private final Handler mainLooper;
private TextView receiveText; private TextView receiveText;
private ControlLines controlLines; private ControlLines controlLines;
@ -65,7 +66,7 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
broadcastReceiver = new BroadcastReceiver() { broadcastReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { 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 = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)
? UsbPermission.Granted : UsbPermission.Denied; ? UsbPermission.Granted : UsbPermission.Denied;
connect(); connect();
@ -89,12 +90,22 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
withIoManager = getArguments().getBoolean("withIoManager"); withIoManager = getArguments().getBoolean("withIoManager");
} }
@Override
public void onStart() {
super.onStart();
ContextCompat.registerReceiver(getActivity(), broadcastReceiver, new IntentFilter(INTENT_ACTION_GRANT_USB), ContextCompat.RECEIVER_NOT_EXPORTED);
}
@Override
public void onStop() {
getActivity().unregisterReceiver(broadcastReceiver);
super.onStop();
}
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
getActivity().registerReceiver(broadcastReceiver, new IntentFilter(INTENT_ACTION_GRANT_USB)); if(!connected && (usbPermission == UsbPermission.Unknown || usbPermission == UsbPermission.Granted))
if(usbPermission == UsbPermission.Unknown || usbPermission == UsbPermission.Granted)
mainLooper.post(this::connect); mainLooper.post(this::connect);
} }
@ -104,7 +115,6 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
status("disconnected"); status("disconnected");
disconnect(); disconnect();
} }
getActivity().unregisterReceiver(broadcastReceiver);
super.onPause(); super.onPause();
} }
@ -141,6 +151,25 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
if (id == R.id.clear) { if (id == R.id.clear) {
receiveText.setText(""); receiveText.setText("");
return true; 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 { } else {
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
@ -185,7 +214,7 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
status("connection failed: no driver for device"); status("connection failed: no driver for device");
return; return;
} }
if(driver.getPorts().size() < portNum) { if(portNum >= driver.getPorts().size()) {
status("connection failed: not enough ports at device"); status("connection failed: not enough ports at device");
return; return;
} }
@ -193,7 +222,10 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
UsbDeviceConnection usbConnection = usbManager.openDevice(driver.getDevice()); UsbDeviceConnection usbConnection = usbManager.openDevice(driver.getDevice());
if(usbConnection == null && usbPermission == UsbPermission.Unknown && !usbManager.hasPermission(driver.getDevice())) { if(usbConnection == null && usbPermission == UsbPermission.Unknown && !usbManager.hasPermission(driver.getDevice())) {
usbPermission = UsbPermission.Requested; usbPermission = UsbPermission.Requested;
PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(getActivity(), 0, new Intent(INTENT_ACTION_GRANT_USB), 0); int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_MUTABLE : 0;
Intent intent = new Intent(INTENT_ACTION_GRANT_USB);
intent.setPackage(getActivity().getPackageName());
PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(getActivity(), 0, intent, flags);
usbManager.requestPermission(driver.getDevice(), usbPermissionIntent); usbManager.requestPermission(driver.getDevice(), usbPermissionIntent);
return; return;
} }
@ -207,10 +239,14 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
try { try {
usbSerialPort.open(usbConnection); 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) { if(withIoManager) {
usbIoManager = new SerialInputOutputManager(usbSerialPort, this); usbIoManager = new SerialInputOutputManager(usbSerialPort, this);
Executors.newSingleThreadExecutor().submit(usbIoManager); usbIoManager.start();
} }
status("connected"); status("connected");
connected = true; connected = true;
@ -224,8 +260,10 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
private void disconnect() { private void disconnect() {
connected = false; connected = false;
controlLines.stop(); controlLines.stop();
if(usbIoManager != null) if(usbIoManager != null) {
usbIoManager.setListener(null);
usbIoManager.stop(); usbIoManager.stop();
}
usbIoManager = null; usbIoManager = null;
try { try {
usbSerialPort.close(); usbSerialPort.close();
@ -239,10 +277,10 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
return; return;
} }
try { try {
byte[] data = (str + '\n').getBytes(); byte[] data = (str + '\n').getBytes();
SpannableStringBuilder spn = new SpannableStringBuilder(); SpannableStringBuilder spn = new SpannableStringBuilder();
spn.append("send " + data.length + " bytes\n"); 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); spn.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorSendText)), 0, spn.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
receiveText.append(spn); receiveText.append(spn);
usbSerialPort.write(data, WRITE_WAIT_MILLIS); usbSerialPort.write(data, WRITE_WAIT_MILLIS);
@ -272,7 +310,7 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
SpannableStringBuilder spn = new SpannableStringBuilder(); SpannableStringBuilder spn = new SpannableStringBuilder();
spn.append("receive " + data.length + " bytes\n"); spn.append("receive " + data.length + " bytes\n");
if(data.length > 0) if(data.length > 0)
spn.append(HexDump.dumpHexString(data)+"\n"); spn.append(HexDump.dumpHexString(data)).append("\n");
receiveText.append(spn); receiveText.append(spn);
} }
@ -285,8 +323,8 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag
class ControlLines { class ControlLines {
private static final int refreshInterval = 200; // msec private static final int refreshInterval = 200; // msec
private Runnable runnable; private final Runnable runnable;
private ToggleButton rtsBtn, ctsBtn, dtrBtn, dsrBtn, cdBtn, riBtn; private final ToggleButton rtsBtn, ctsBtn, dtrBtn, dsrBtn, cdBtn, riBtn;
ControlLines(View view) { 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 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)); cdBtn.setChecked(controlLines.contains(UsbSerialPort.ControlLine.CD));
riBtn.setChecked(controlLines.contains(UsbSerialPort.ControlLine.RI)); riBtn.setChecked(controlLines.contains(UsbSerialPort.ControlLine.RI));
mainLooper.postDelayed(runnable, refreshInterval); mainLooper.postDelayed(runnable, refreshInterval);
} catch (IOException e) { } catch (Exception e) {
status("getControlLines() failed: " + e.getMessage() + " -> stopped control line refresh"); status("getControlLines() failed: " + e.getMessage() + " -> stopped control line refresh");
} }
} }
@ -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.CD)) cdBtn.setVisibility(View.INVISIBLE);
if (!controlLines.contains(UsbSerialPort.ControlLine.RI)) riBtn.setVisibility(View.INVISIBLE); if (!controlLines.contains(UsbSerialPort.ControlLine.RI)) riBtn.setVisibility(View.INVISIBLE);
run(); run();
} catch (IOException e) { } catch (Exception e) {
Toast.makeText(getActivity(), "getSupportedControlLines() failed: " + e.getMessage(), Toast.LENGTH_SHORT).show(); Toast.makeText(getActivity(), "getSupportedControlLines() failed: " + e.getMessage(), Toast.LENGTH_SHORT).show();
rtsBtn.setVisibility(View.INVISIBLE);
ctsBtn.setVisibility(View.INVISIBLE);
dtrBtn.setVisibility(View.INVISIBLE);
dsrBtn.setVisibility(View.INVISIBLE);
cdBtn.setVisibility(View.INVISIBLE);
cdBtn.setVisibility(View.INVISIBLE);
riBtn.setVisibility(View.INVISIBLE);
} }
} }

View File

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

View File

@ -5,16 +5,21 @@
<usb-device vendor-id="1027" product-id="24592" /> <!-- 0x6010: FT2232H --> <usb-device vendor-id="1027" product-id="24592" /> <!-- 0x6010: FT2232H -->
<usb-device vendor-id="1027" product-id="24593" /> <!-- 0x6011: FT4232H --> <usb-device vendor-id="1027" product-id="24593" /> <!-- 0x6011: FT4232H -->
<usb-device vendor-id="1027" product-id="24596" /> <!-- 0x6014: FT232H --> <usb-device vendor-id="1027" product-id="24596" /> <!-- 0x6014: FT232H -->
<usb-device vendor-id="1027" product-id="24597" /> <!-- 0x6015: FT231X --> <usb-device vendor-id="1027" product-id="24597" /> <!-- 0x6015: FT230X, FT231X, FT234XD -->
<!-- 0x10C4 / 0xEA??: Silabs CP210x --> <!-- 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="60016" /> <!-- 0xea70: CP2105 -->
<usb-device vendor-id="4292" product-id="60017" /> <!-- 0xea71: CP2108 --> <usb-device vendor-id="4292" product-id="60017" /> <!-- 0xea71: CP2108 -->
<usb-device vendor-id="4292" product-id="60032" /> <!-- 0xea80: CP2110 -->
<!-- 0x067B / 0x2303: Prolific PL2303 --> <!-- 0x067B / 0x23?3: Prolific PL2303x -->
<usb-device vendor-id="1659" product-id="8963" /> <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 --> <!-- 0x1a86 / 0x?523: Qinheng CH34x -->
<usb-device vendor-id="6790" product-id="21795" /> <!-- 0x5523: CH341A --> <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="1003" product-id="8260" /> <!-- 0x03EB / 0x2044: Atmel Lufa -->
<usb-device vendor-id="7855" product-id="4" /> <!-- 0x1eaf / 0x0004: Leaflabs Maple --> <usb-device vendor-id="7855" product-id="4" /> <!-- 0x1eaf / 0x0004: Leaflabs Maple -->
<usb-device vendor-id="3368" product-id="516" /> <!-- 0x0d28 / 0x0204: ARM mbed --> <usb-device vendor-id="3368" product-id="516" /> <!-- 0x0d28 / 0x0204: ARM mbed -->
<usb-device vendor-id="1155" product-id="22336" /><!-- 0x0483 / 0x5740: ST CDC -->
<usb-device vendor-id="11914" product-id="5" /> <!-- 0x2E8A / 0x0005: Raspberry Pi Pico Micropython -->
<usb-device vendor-id="11914" product-id="10" /> <!-- 0x2E8A / 0x000A: Raspberry Pi Pico SDK -->
<usb-device vendor-id="6790" product-id="21972" /><!-- 0x1A86 / 0x55D4: Qinheng CH9102F -->
</resources> </resources>

View File

@ -1,17 +1,19 @@
apply plugin: 'com.android.library' plugins {
id 'com.android.library'
id 'maven-publish'
}
android { android {
compileSdkVersion 29 compileSdkVersion 35
buildToolsVersion '29.0.3'
defaultConfig { defaultConfig {
minSdkVersion 17 minSdkVersion 17
targetSdkVersion 28 targetSdkVersion 35
consumerProguardFiles 'proguard-rules.pro' consumerProguardFiles 'proguard-rules.pro'
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments = [ // Raspi Windows LinuxVM ... testInstrumentationRunnerArguments = [ // Raspi Windows LinuxVM ...
'rfc2217_server_host': '192.168.0.100', 'rfc2217_server_host': '192.168.0.78',
'rfc2217_server_nonstandard_baudrates': 'true', // true false false 'rfc2217_server_nonstandard_baudrates': 'true', // true false false
] ]
} }
@ -19,16 +21,40 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
namespace 'com.hoho.android.usbserial'
publishing {
// if coverage is enabled, change 'release' to 'anyDeviceRelease' or comment out publishing rule
singleVariant('release') {
withSourcesJar()
}
}
} }
dependencies { dependencies {
testImplementation 'junit:junit:4.13' implementation "androidx.annotation:annotation:1.9.1"
androidTestImplementation 'com.android.support:support-annotations:28.0.0' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'com.android.support.test:runner:1.0.2' testImplementation 'org.mockito:mockito-core:5.15.2'
androidTestImplementation 'commons-net:commons-net:3.6' androidTestImplementation 'androidx.appcompat:appcompat:1.6.1' // later versions have minsdk 21
androidTestImplementation 'org.apache.commons:commons-lang3:3.8.1' // starting with 3.9 requires min-api 26 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' //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' apply plugin: 'jacoco'
android { android {
flavorDimensions 'device' flavorDimensions += 'device'
productFlavors { productFlavors {
anyDevice { anyDevice {
// Used as fallback in usbSerialExample/build.gradle -> missingDimensionStrategy, but not for coverage report // Used as fallback in usbSerialExample/build.gradle -> missingDimensionStrategy, but not for coverage report
dimension 'device' dimension 'device'
} }
arduino { mcp2221 {
dimension 'device' dimension 'device'
testInstrumentationRunnerArguments = ['test_device_driver': 'CdcAcm'] testInstrumentationRunnerArguments = ['test_device_driver': 'CdcAcm']
} }
@ -17,7 +18,7 @@ android {
} }
cp2102 { // and cp2105 first port cp2102 { // and cp2105 first port
dimension 'device' dimension 'device'
testInstrumentationRunnerArguments = ['test_device_driver': 'Cp21xx'] testInstrumentationRunnerArguments = ['test_device_driver': 'Cp21xx', 'test_device_port': '0']
} }
cp2105 { // second port cp2105 { // second port
dimension 'device' dimension 'device'
@ -25,13 +26,21 @@ android {
} }
ft232 { // and ft2232 first port ft232 { // and ft2232 first port
dimension 'device' dimension 'device'
testInstrumentationRunnerArguments = ['test_device_driver': 'Ftdi'] testInstrumentationRunnerArguments = ['test_device_driver': 'Ftdi', 'test_device_port': '0']
} }
ft2232 { // second port ft2232 { // second port
dimension 'device' dimension 'device'
testInstrumentationRunnerArguments = ['test_device_driver': 'Ftdi', 'test_device_port': '1'] testInstrumentationRunnerArguments = ['test_device_driver': 'Ftdi', 'test_device_port': '1']
} }
pl2302 { pl2303 {
dimension 'device'
testInstrumentationRunnerArguments = ['test_device_driver': 'Prolific']
}
pl2303t {
dimension 'device'
testInstrumentationRunnerArguments = ['test_device_driver': 'Prolific']
}
pl2303g {
dimension 'device' dimension 'device'
testInstrumentationRunnerArguments = ['test_device_driver': 'Prolific'] testInstrumentationRunnerArguments = ['test_device_driver': 'Prolific']
} }
@ -39,13 +48,31 @@ android {
buildTypes { buildTypes {
debug { debug {
testCoverageEnabled true enableUnitTestCoverage true
enableAndroidTestCoverage true
} }
} }
} }
// create report even if tests fail // create report even if tests fail
project.gradle.taskGraph.whenReady { project.gradle.taskGraph.whenReady {
-> project.tasks.findAll { it.name =~ /connected.+AndroidTest/ }.each { -> project.tasks.findAll { it.name =~ /connected.+AndroidTest/ }.each {
it.ignoreFailures = true 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"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hoho.android.usbserial"
android:versionCode="1" android:versionCode="1"
android:versionName="1.0"> android:versionName="1.0">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>

View File

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

View File

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

View File

@ -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; 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.InvalidTelnetOptionException;
import org.apache.commons.net.telnet.TelnetClient; import org.apache.commons.net.telnet.TelnetClient;
import org.apache.commons.net.telnet.TelnetCommand; import org.apache.commons.net.telnet.TelnetCommand;
@ -9,6 +11,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@ -31,7 +34,7 @@ public class TelnetWrapper {
private TelnetClient telnetClient; private TelnetClient telnetClient;
private InputStream readStream; private InputStream readStream;
private OutputStream writeStream; private OutputStream writeStream;
private Integer[] comPortOptionCounter = {0}; private ArrayList<int[]> commandResponse = new ArrayList<>();
public int writeDelay = 0; public int writeDelay = 0;
public TelnetWrapper(String host, int port) { 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) { telnetClient.addOptionHandler(new TelnetOptionHandler(RFC2217_COM_PORT_OPTION, false, false, false, false) {
@Override @Override
public int[] answerSubnegotiation(int[] suboptionData, int suboptionLength) { 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); return super.answerSubnegotiation(suboptionData, suboptionLength);
} }
}); });
@ -59,18 +64,25 @@ public class TelnetWrapper {
readStream = telnetClient.getInputStream(); 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 { public void setUp() throws Exception {
setUpFixtureInt(); setUpFixtureInt();
telnetClient.sendAYT(1000); // not correctly handled by rfc2217_server.py, but WARNING output "ignoring Telnet command: '\xf6'" is a nice separator between tests telnetClient.sendAYT(1000); // not correctly handled by rfc2217_server.py, but WARNING output "ignoring Telnet command: '\xf6'" is a nice separator between tests
comPortOptionCounter[0] = 0; doCommand("purge-data", new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_PURGE_DATA, 3});
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());
writeDelay = 0; writeDelay = 0;
} }
@ -92,11 +104,15 @@ public class TelnetWrapper {
// wait full time // wait full time
public byte[] read() throws Exception { public byte[] read() throws Exception {
return read(-1); return read(-1, -1);
} }
public byte[] read(int expectedLength) throws Exception { 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); ByteBuffer buf = ByteBuffer.allocate(65536);
while(System.currentTimeMillis() < end) { while(System.currentTimeMillis() < end) {
if(readStream.available() > 0) { 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 { public void setParameters(int baudRate, int dataBits, int stopBits, @UsbSerialPort.Parity int parity) throws IOException, InterruptedException, InvalidTelnetOptionException {
comPortOptionCounter[0] = 0; 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});
telnetClient.sendCommand((byte) TelnetCommand.SB); doCommand("set-stopsize", new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_STOPSIZE, (byte)stopBits});
writeStream.write(new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_BAUDRATE, (byte)(baudRate>>24), (byte)(baudRate>>16), (byte)(baudRate>>8), (byte)baudRate}); doCommand("set-parity", new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_PARITY, (byte)(parity+1)});
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());
} }
} }

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.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager; import android.hardware.usb.UsbManager;
import android.os.Process; import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.util.Log; import android.util.Log;
import com.hoho.android.usbserial.driver.CdcAcmSerialDriver; import com.hoho.android.usbserial.driver.CdcAcmSerialDriver;
import com.hoho.android.usbserial.driver.Ch34xSerialDriver;
import com.hoho.android.usbserial.driver.CommonUsbSerialPort;
import com.hoho.android.usbserial.driver.Cp21xxSerialDriver;
import com.hoho.android.usbserial.driver.FtdiSerialDriver;
import com.hoho.android.usbserial.driver.ProlificSerialDriver;
import com.hoho.android.usbserial.driver.ProlificSerialPortWrapper;
import com.hoho.android.usbserial.driver.UsbId;
import com.hoho.android.usbserial.driver.UsbSerialDriver; import com.hoho.android.usbserial.driver.UsbSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialPort; import com.hoho.android.usbserial.driver.UsbSerialPort;
@ -21,24 +29,19 @@ import java.util.Deque;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import static org.junit.Assert.assertEquals; 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.assertTrue;
import static org.junit.Assert.fail;
import androidx.core.content.ContextCompat;
public class UsbWrapper implements SerialInputOutputManager.Listener { public class UsbWrapper implements SerialInputOutputManager.Listener {
private final static int USB_READ_WAIT = 500; public final static int USB_READ_WAIT = 500;
private final static int USB_WRITE_WAIT = 500; public final static int USB_WRITE_WAIT = 500;
private final static Integer SERIAL_INPUT_OUTPUT_MANAGER_THREAD_PRIORITY = Process.THREAD_PRIORITY_URGENT_AUDIO;
private static final String TAG = UsbWrapper.class.getSimpleName(); 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 // constructor
final Context context; final Context context;
@ -54,17 +57,30 @@ public class UsbWrapper implements SerialInputOutputManager.Listener {
public boolean readBlock = false; public boolean readBlock = false;
long readTime = 0; long readTime = 0;
// device properties
public boolean isCp21xxRestrictedPort; // second port of Cp2105 has limited dataBits, stopBits, parity
public boolean outputLinesSupported;
public boolean inputLinesSupported;
public boolean inputLinesConnected;
public boolean inputLinesOnlyRtsCts;
public int writePacketSize = -1;
public int writeBufferSize = -1;
public int readBufferSize = -1;
public UsbWrapper(Context context, UsbSerialDriver serialDriver, int devicePort) { public UsbWrapper(Context context, UsbSerialDriver serialDriver, int devicePort) {
this.context = context; this.context = context;
this.serialDriver = serialDriver; this.serialDriver = serialDriver;
this.devicePort = devicePort; this.devicePort = devicePort;
serialPort = serialDriver.getPorts().get(devicePort); serialPort = serialDriver.getPorts().get(devicePort);
CommonUsbSerialPort.DEBUG = true;
} }
public void setUp() throws Exception { public void setUp() throws Exception {
UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE); UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
if (!usbManager.hasPermission(serialDriver.getDevice())) { if (!usbManager.hasPermission(serialDriver.getDevice())) {
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
RingtoneManager.getRingtone(context, notification).play();
Log.d(TAG,"USB permission ..."); Log.d(TAG,"USB permission ...");
final Boolean[] granted = {null}; final Boolean[] granted = {null};
BroadcastReceiver usbReceiver = new BroadcastReceiver() { BroadcastReceiver usbReceiver = new BroadcastReceiver() {
@ -73,17 +89,78 @@ public class UsbWrapper implements SerialInputOutputManager.Listener {
granted[0] = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false); granted[0] = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
} }
}; };
PendingIntent permissionIntent = PendingIntent.getBroadcast(context, 0, new Intent("com.android.example.USB_PERMISSION"), 0); int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_MUTABLE : 0;
Intent intent = new Intent("com.android.example.USB_PERMISSION");
intent.setPackage(context.getPackageName());
PendingIntent permissionIntent = PendingIntent.getBroadcast(context, 0, intent, flags);
IntentFilter filter = new IntentFilter("com.android.example.USB_PERMISSION"); IntentFilter filter = new IntentFilter("com.android.example.USB_PERMISSION");
context.registerReceiver(usbReceiver, filter); ContextCompat.registerReceiver(context, usbReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
usbManager.requestPermission(serialDriver.getDevice(), permissionIntent); usbManager.requestPermission(serialDriver.getDevice(), permissionIntent);
for(int i=0; i<5000; i++) { for(int i=0; i<5000; i++) {
if(granted[0] != null) break; if(granted[0] != null) break;
Thread.sleep(1); Thread.sleep(1);
} }
Log.d(TAG,"USB permission "+granted[0]); Log.d(TAG,"USB permission "+granted[0]);
assertTrue("USB permission dialog not confirmed", granted[0]==null?false:granted[0]); assertTrue("USB permission dialog not confirmed", granted[0] != null && granted[0]);
} }
// extract some device properties:
isCp21xxRestrictedPort = serialDriver instanceof Cp21xxSerialDriver && serialDriver.getPorts().size()==2 && serialPort.getPortNumber() == 1;
// output lines are supported by all common drivers
// input lines are supported by all common drivers except CDC
if (serialDriver instanceof FtdiSerialDriver) {
outputLinesSupported = true;
inputLinesSupported = true;
if(serialDriver.getDevice().getProductId() == UsbId.FTDI_FT2232H)
inputLinesConnected = true; // I only have 74LS138 connected at FT2232, not at FT232
if(serialDriver.getDevice().getProductId() == UsbId.FTDI_FT231X) {
inputLinesConnected = true;
inputLinesOnlyRtsCts = true; // I only test with FT230X that has only these 2 control lines. DTR is silently ignored
}
} else if (serialDriver instanceof Cp21xxSerialDriver) {
outputLinesSupported = true;
inputLinesSupported = true;
if(serialDriver.getPorts().size() == 1)
inputLinesConnected = true; // I only have 74LS138 connected at CP2102, not at CP2105
} else if (serialDriver instanceof ProlificSerialDriver) {
outputLinesSupported = true;
inputLinesSupported = true;
inputLinesConnected = true;
} else if (serialDriver instanceof Ch34xSerialDriver) {
outputLinesSupported = true;
inputLinesSupported = true;
if(serialDriver.getDevice().getProductId() == UsbId.QINHENG_CH340)
inputLinesConnected = true; // I only have 74LS138 connected at CH340, not connected at CH341A
} else if (serialDriver instanceof CdcAcmSerialDriver) {
outputLinesSupported = true;
}
if (serialDriver instanceof Cp21xxSerialDriver) {
if (serialDriver.getPorts().size() == 1) { writePacketSize = 64; writeBufferSize = 576; }
else if (serialPort.getPortNumber() == 0) { writePacketSize = 64; writeBufferSize = 320; }
else { writePacketSize = 32; writeBufferSize = 128; }; //, 160}; // write buffer size detection is unreliable
} else if (serialDriver instanceof Ch34xSerialDriver) {
writePacketSize = 32; writeBufferSize = 64;
} else if (serialDriver instanceof ProlificSerialDriver) {
writePacketSize = 64; writeBufferSize = 256;
} else if (serialDriver instanceof FtdiSerialDriver) {
switch (serialDriver.getPorts().size()) {
case 1: writePacketSize = 64; writeBufferSize = 128; break;
case 2: writePacketSize = 512; writeBufferSize = 4096; break;
case 4: writePacketSize = 512; writeBufferSize = 2048; break;
}
if(serialDriver.getDevice().getProductId() == UsbId.FTDI_FT231X)
writeBufferSize = 512;
} else if (serialDriver instanceof CdcAcmSerialDriver) {
writePacketSize = 16; writeBufferSize = 32; // MCP2221 values, other devices might be different
}
readBufferSize = writeBufferSize;
if (serialDriver instanceof Cp21xxSerialDriver && serialDriver.getPorts().size() == 2) {
readBufferSize = 256;
} else if (serialDriver instanceof FtdiSerialDriver && serialDriver.getPorts().size() == 1 && serialDriver.getDevice().getProductId() != UsbId.FTDI_FT231X) {
readBufferSize = 256;
} // PL2303 HXN checked in open()
} }
public void tearDown() { public void tearDown() {
@ -112,6 +189,8 @@ public class UsbWrapper implements SerialInputOutputManager.Listener {
if(!flags.contains(OpenCloseFlags.NO_CONTROL_LINE_INIT)) { if(!flags.contains(OpenCloseFlags.NO_CONTROL_LINE_INIT)) {
serialPort.setDTR(false); serialPort.setDTR(false);
serialPort.setRTS(false); serialPort.setRTS(false);
if (serialPort.getFlowControl() != UsbSerialPort.FlowControl.NONE)
serialPort.setFlowControl(UsbSerialPort.FlowControl.NONE);
} }
} catch (Exception ignored) { } catch (Exception ignored) {
} }
@ -139,14 +218,10 @@ public class UsbWrapper implements SerialInputOutputManager.Listener {
} }
public void open() throws Exception { public void open() throws Exception {
open(EnumSet.noneOf(OpenCloseFlags.class), 0); open(EnumSet.noneOf(OpenCloseFlags.class));
} }
public void open(EnumSet<OpenCloseFlags> flags) throws Exception { 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)) { if(!flags.contains(OpenCloseFlags.NO_DEVICE_CONNECTION)) {
UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE); UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
deviceConnection = usbManager.openDevice(serialDriver.getDevice()); deviceConnection = usbManager.openDevice(serialDriver.getDevice());
@ -157,36 +232,62 @@ public class UsbWrapper implements SerialInputOutputManager.Listener {
serialPort.setRTS(true); serialPort.setRTS(true);
} }
if(!flags.contains(OpenCloseFlags.NO_IOMANAGER_THREAD)) { if(!flags.contains(OpenCloseFlags.NO_IOMANAGER_THREAD)) {
ioManager = new SerialInputOutputManager(serialPort, this) { ioManager = new SerialInputOutputManager(serialPort, this);
@Override if(!flags.contains(OpenCloseFlags.NO_IOMANAGER_START))
public void run() { ioManager.start();
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);
} }
synchronized (readBuffer) { synchronized (readBuffer) {
readBuffer.clear(); readBuffer.clear();
} }
readError = null; 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 // wait full time
public byte[] read() throws Exception { public byte[] read() throws Exception {
return read(-1); return read(-1, -1, -1);
} }
public byte[] read(int expectedLength) throws Exception { 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); ByteBuffer buf = ByteBuffer.allocate(16*1024);
if(ioManager != null) { if(ioManager != null) {
while (System.currentTimeMillis() < end) { while (System.currentTimeMillis() < end) {
if(readError != null) if(readError != null)
throw readError; throw new IOException(readError);
synchronized (readBuffer) { synchronized (readBuffer) {
while(readBuffer.peek() != null) while(readBuffer.peek() != null)
buf.put(readBuffer.remove()); buf.put(readBuffer.remove());
@ -197,7 +298,7 @@ public class UsbWrapper implements SerialInputOutputManager.Listener {
} }
} else { } else {
byte[] b1 = new byte[256]; byte[] b1 = new byte[readBufferSize > 0 ? readBufferSize : 256];
while (System.currentTimeMillis() < end) { while (System.currentTimeMillis() < end) {
int len = serialPort.read(b1, USB_READ_WAIT); int len = serialPort.read(b1, USB_READ_WAIT);
if (len > 0) if (len > 0)
@ -216,7 +317,7 @@ public class UsbWrapper implements SerialInputOutputManager.Listener {
serialPort.write(data, USB_WRITE_WAIT); 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); serialPort.setParameters(baudRate, dataBits, stopBits, parity);
if(serialDriver instanceof CdcAcmSerialDriver) if(serialDriver instanceof CdcAcmSerialDriver)
Thread.sleep(10); // arduino_leonardeo_bridge.ini needs some time 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 @Override
public void onNewData(byte[] data) { public void onNewData(byte[] data) {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
if(readTime == 0) if(readTime == 0)
readTime = now; readTime = now;
if(data.length > 64) { if(data.length > 64) {
Log.d(TAG, "usb read: time+=" + String.format("%-3d",now- readTime) + " len=" + String.format("%-4d",data.length) + " data=" + new String(data, 0, 32) + "..." + new String(data, data.length-32, 32)); Log.d(TAG, "usb " + devicePort + " read: time+=" + String.format("%-3d",now- readTime) + " len=" + String.format("%-4d",data.length) + " data=" + new String(data, 0, 32) + "..." + new String(data, data.length-32, 32));
} else { } else {
Log.d(TAG, "usb read: time+=" + String.format("%-3d",now- readTime) + " len=" + String.format("%-4d",data.length) + " data=" + new String(data)); Log.d(TAG, "usb " + devicePort + " read: time+=" + String.format("%-3d",now- readTime) + " len=" + String.format("%-4d",data.length) + " data=" + new String(data));
} }
readTime = now; readTime = now;

View File

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

View File

@ -8,11 +8,13 @@ package com.hoho.android.usbserial.driver;
import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbInterface;
import android.util.Log; import android.util.Log;
import com.hoho.android.usbserial.util.HexDump;
import com.hoho.android.usbserial.util.UsbUtils;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet; import java.util.EnumSet;
@ -30,6 +32,8 @@ import java.util.Map;
*/ */
public class CdcAcmSerialDriver implements UsbSerialDriver { public class CdcAcmSerialDriver implements UsbSerialDriver {
public static final int USB_SUBCLASS_ACM = 2;
private final String TAG = CdcAcmSerialDriver.class.getSimpleName(); private final String TAG = CdcAcmSerialDriver.class.getSimpleName();
private final UsbDevice mDevice; private final UsbDevice mDevice;
@ -38,23 +42,33 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
public CdcAcmSerialDriver(UsbDevice device) { public CdcAcmSerialDriver(UsbDevice device) {
mDevice = device; mDevice = device;
mPorts = new ArrayList<>(); mPorts = new ArrayList<>();
int ports = countPorts(device);
int controlInterfaceCount = 0; for (int port = 0; port < ports; port++) {
int dataInterfaceCount = 0;
for( int i = 0; i < device.getInterfaceCount(); i++) {
if(device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_COMM)
controlInterfaceCount++;
if(device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA)
dataInterfaceCount++;
}
for( int port = 0; port < Math.min(controlInterfaceCount, dataInterfaceCount); port++) {
mPorts.add(new CdcAcmSerialPort(mDevice, port)); mPorts.add(new CdcAcmSerialPort(mDevice, port));
} }
if(mPorts.size() == 0) { if (mPorts.size() == 0) {
mPorts.add(new CdcAcmSerialPort(mDevice, -1)); mPorts.add(new CdcAcmSerialPort(mDevice, -1));
} }
} }
@SuppressWarnings({"unused"})
public static boolean probe(UsbDevice device) {
return countPorts(device) > 0;
}
private static int countPorts(UsbDevice device) {
int controlInterfaceCount = 0;
int dataInterfaceCount = 0;
for (int i = 0; i < device.getInterfaceCount(); i++) {
if (device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_COMM &&
device.getInterface(i).getInterfaceSubclass() == USB_SUBCLASS_ACM)
controlInterfaceCount++;
if (device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA)
dataInterfaceCount++;
}
return Math.min(controlInterfaceCount, dataInterfaceCount);
}
@Override @Override
public UsbDevice getDevice() { public UsbDevice getDevice() {
return mDevice; return mDevice;
@ -95,7 +109,11 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
} }
@Override @Override
protected void openInt(UsbDeviceConnection connection) throws IOException { protected void openInt() throws IOException {
Log.d(TAG, "interfaces:");
for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
Log.d(TAG, mDevice.getInterface(i).toString());
}
if (mPortNumber == -1) { if (mPortNumber == -1) {
Log.d(TAG,"device might be castrated ACM device, trying single interface logic"); Log.d(TAG,"device might be castrated ACM device, trying single interface logic");
openSingleInterface(); openSingleInterface();
@ -131,38 +149,57 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
} }
private void openInterface() throws IOException { private void openInterface() throws IOException {
Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount());
int controlInterfaceCount = 0;
int dataInterfaceCount = 0;
mControlInterface = null; mControlInterface = null;
mDataInterface = null; mDataInterface = null;
for (int i = 0; i < mDevice.getInterfaceCount(); i++) { int j = getInterfaceIdFromDescriptors();
UsbInterface usbInterface = mDevice.getInterface(i); Log.d(TAG, "interface count=" + mDevice.getInterfaceCount() + ", IAD=" + j);
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM) { if (j >= 0) {
if(controlInterfaceCount == mPortNumber) { for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
mControlIndex = i; UsbInterface usbInterface = mDevice.getInterface(i);
mControlInterface = usbInterface; if (usbInterface.getId() == j || usbInterface.getId() == j+1) {
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM &&
usbInterface.getInterfaceSubclass() == USB_SUBCLASS_ACM) {
mControlIndex = usbInterface.getId();
mControlInterface = usbInterface;
}
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) {
mDataInterface = usbInterface;
}
} }
controlInterfaceCount++;
} }
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) { }
if(dataInterfaceCount == mPortNumber) { if (mControlInterface == null || mDataInterface == null) {
mDataInterface = usbInterface; Log.d(TAG, "no IAD fallback");
int controlInterfaceCount = 0;
int dataInterfaceCount = 0;
for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
UsbInterface usbInterface = mDevice.getInterface(i);
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM &&
usbInterface.getInterfaceSubclass() == USB_SUBCLASS_ACM) {
if (controlInterfaceCount == mPortNumber) {
mControlIndex = usbInterface.getId();
mControlInterface = usbInterface;
}
controlInterfaceCount++;
}
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) {
if (dataInterfaceCount == mPortNumber) {
mDataInterface = usbInterface;
}
dataInterfaceCount++;
} }
dataInterfaceCount++;
} }
} }
if(mControlInterface == null) { if(mControlInterface == null) {
throw new IOException("No control interface"); throw new IOException("No control interface");
} }
Log.d(TAG, "Control iface=" + mControlInterface); Log.d(TAG, "Control interface id " + mControlInterface.getId());
if (!mConnection.claimInterface(mControlInterface, true)) { if (!mConnection.claimInterface(mControlInterface, true)) {
throw new IOException("Could not claim control interface"); throw new IOException("Could not claim control interface");
} }
mControlEndpoint = mControlInterface.getEndpoint(0); mControlEndpoint = mControlInterface.getEndpoint(0);
if (mControlEndpoint.getDirection() != UsbConstants.USB_DIR_IN || mControlEndpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_INT) { if (mControlEndpoint.getDirection() != UsbConstants.USB_DIR_IN || mControlEndpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_INT) {
throw new IOException("Invalid control endpoint"); throw new IOException("Invalid control endpoint");
@ -171,12 +208,10 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
if(mDataInterface == null) { if(mDataInterface == null) {
throw new IOException("No data interface"); throw new IOException("No data interface");
} }
Log.d(TAG, "data iface=" + mDataInterface); Log.d(TAG, "data interface id " + mDataInterface.getId());
if (!mConnection.claimInterface(mDataInterface, true)) { if (!mConnection.claimInterface(mDataInterface, true)) {
throw new IOException("Could not claim data interface"); throw new IOException("Could not claim data interface");
} }
for (int i = 0; i < mDataInterface.getEndpointCount(); i++) { for (int i = 0; i < mDataInterface.getEndpointCount(); i++) {
UsbEndpoint ep = mDataInterface.getEndpoint(i); UsbEndpoint ep = mDataInterface.getEndpoint(i);
if (ep.getDirection() == UsbConstants.USB_DIR_IN && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) if (ep.getDirection() == UsbConstants.USB_DIR_IN && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)
@ -186,6 +221,36 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
} }
} }
private int getInterfaceIdFromDescriptors() {
ArrayList<byte[]> descriptors = UsbUtils.getDescriptors(mConnection);
Log.d(TAG, "USB descriptor:");
for(byte[] descriptor : descriptors)
Log.d(TAG, HexDump.toHexString(descriptor));
if (descriptors.size() > 0 &&
descriptors.get(0).length == 18 &&
descriptors.get(0)[1] == 1 && // bDescriptorType
descriptors.get(0)[4] == (byte)(UsbConstants.USB_CLASS_MISC) && //bDeviceClass
descriptors.get(0)[5] == 2 && // bDeviceSubClass
descriptors.get(0)[6] == 1) { // bDeviceProtocol
// is IAD device, see https://www.usb.org/sites/default/files/iadclasscode_r10.pdf
int port = -1;
for (int d = 1; d < descriptors.size(); d++) {
if (descriptors.get(d).length == 8 &&
descriptors.get(d)[1] == 0x0b && // bDescriptorType == IAD
descriptors.get(d)[4] == UsbConstants.USB_CLASS_COMM && // bFunctionClass == CDC
descriptors.get(d)[5] == USB_SUBCLASS_ACM) { // bFunctionSubClass == ACM
port++;
if (port == mPortNumber &&
descriptors.get(d)[3] == 2) { // bInterfaceCount
return descriptors.get(d)[2]; // bFirstInterface
}
}
}
}
return -1;
}
private int sendAcmControlMessage(int request, int value, byte[] buf) throws IOException { private int sendAcmControlMessage(int request, int value, byte[] buf) throws IOException {
int len = mConnection.controlTransfer( int len = mConnection.controlTransfer(
USB_RT_ACM, request, value, mControlIndex, buf, buf != null ? buf.length : 0, 5000); USB_RT_ACM, request, value, mControlIndex, buf, buf != null ? buf.length : 0, 5000);
@ -204,7 +269,7 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
} }
@Override @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) { if(baudRate <= 0) {
throw new IllegalArgumentException("Invalid baud rate: " + baudRate); throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
} }
@ -278,40 +343,17 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
public EnumSet<ControlLine> getSupportedControlLines() throws IOException { public EnumSet<ControlLine> getSupportedControlLines() throws IOException {
return EnumSet.of(ControlLine.RTS, ControlLine.DTR); 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() { public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<Integer, int[]>(); return new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_ARDUINO,
new int[] {
UsbId.ARDUINO_UNO,
UsbId.ARDUINO_UNO_R3,
UsbId.ARDUINO_MEGA_2560,
UsbId.ARDUINO_MEGA_2560_R3,
UsbId.ARDUINO_SERIAL_ADAPTER,
UsbId.ARDUINO_SERIAL_ADAPTER_R3,
UsbId.ARDUINO_MEGA_ADK,
UsbId.ARDUINO_MEGA_ADK_R3,
UsbId.ARDUINO_LEONARDO,
UsbId.ARDUINO_MICRO,
});
supportedDevices.put(UsbId.VENDOR_VAN_OOIJEN_TECH,
new int[] {
UsbId.VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL,
});
supportedDevices.put(UsbId.VENDOR_ATMEL,
new int[] {
UsbId.ATMEL_LUFA_CDC_DEMO_APP,
});
supportedDevices.put(UsbId.VENDOR_LEAFLABS,
new int[] {
UsbId.LEAFLABS_MAPLE,
});
supportedDevices.put(UsbId.VENDOR_ARM,
new int[] {
UsbId.ARM_MBED,
});
return supportedDevices;
} }
} }

View File

@ -7,9 +7,11 @@ package com.hoho.android.usbserial.driver;
import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbInterface;
import android.util.Log;
import com.hoho.android.usbserial.BuildConfig;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
@ -20,336 +22,366 @@ import java.util.Map;
public class Ch34xSerialDriver implements UsbSerialDriver { public class Ch34xSerialDriver implements UsbSerialDriver {
private static final String TAG = Ch34xSerialDriver.class.getSimpleName(); private static final String TAG = Ch34xSerialDriver.class.getSimpleName();
private final UsbDevice mDevice; private final UsbDevice mDevice;
private final UsbSerialPort mPort; private final UsbSerialPort mPort;
private static final int LCR_ENABLE_RX = 0x80; private static final int LCR_ENABLE_RX = 0x80;
private static final int LCR_ENABLE_TX = 0x40; private static final int LCR_ENABLE_TX = 0x40;
private static final int LCR_MARK_SPACE = 0x20; private static final int LCR_MARK_SPACE = 0x20;
private static final int LCR_PAR_EVEN = 0x10; private static final int LCR_PAR_EVEN = 0x10;
private static final int LCR_ENABLE_PAR = 0x08; private static final int LCR_ENABLE_PAR = 0x08;
private static final int LCR_STOP_BITS_2 = 0x04; private static final int LCR_STOP_BITS_2 = 0x04;
private static final int LCR_CS8 = 0x03; private static final int LCR_CS8 = 0x03;
private static final int LCR_CS7 = 0x02; private static final int LCR_CS7 = 0x02;
private static final int LCR_CS6 = 0x01; private static final int LCR_CS6 = 0x01;
private static final int LCR_CS5 = 0x00; private static final int LCR_CS5 = 0x00;
private static final int GCL_CTS = 0x01; private static final int GCL_CTS = 0x01;
private static final int GCL_DSR = 0x02; private static final int GCL_DSR = 0x02;
private static final int GCL_RI = 0x04; private static final int GCL_RI = 0x04;
private static final int GCL_CD = 0x08; private static final int GCL_CD = 0x08;
private static final int SCL_DTR = 0x20; private static final int SCL_DTR = 0x20;
private static final int SCL_RTS = 0x40; private static final int SCL_RTS = 0x40;
public Ch34xSerialDriver(UsbDevice device) { public Ch34xSerialDriver(UsbDevice device) {
mDevice = device; mDevice = device;
mPort = new Ch340SerialPort(mDevice, 0); mPort = new Ch340SerialPort(mDevice, 0);
} }
@Override @Override
public UsbDevice getDevice() { public UsbDevice getDevice() {
return mDevice; return mDevice;
} }
@Override @Override
public List<UsbSerialPort> getPorts() { public List<UsbSerialPort> getPorts() {
return Collections.singletonList(mPort); return Collections.singletonList(mPort);
} }
public class Ch340SerialPort extends CommonUsbSerialPort { public class Ch340SerialPort extends CommonUsbSerialPort {
private static final int USB_TIMEOUT_MILLIS = 5000; private static final int USB_TIMEOUT_MILLIS = 5000;
private final int DEFAULT_BAUD_RATE = 9600; private final int DEFAULT_BAUD_RATE = 9600;
private boolean dtr = false; private boolean dtr = false;
private boolean rts = false; private boolean rts = false;
public Ch340SerialPort(UsbDevice device, int portNumber) { public Ch340SerialPort(UsbDevice device, int portNumber) {
super(device, portNumber); super(device, portNumber);
} }
@Override @Override
public UsbSerialDriver getDriver() { public UsbSerialDriver getDriver() {
return Ch34xSerialDriver.this; return Ch34xSerialDriver.this;
} }
@Override @Override
protected void openInt(UsbDeviceConnection connection) throws IOException { protected void openInt() throws IOException {
for (int i = 0; i < mDevice.getInterfaceCount(); i++) { for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
UsbInterface usbIface = mDevice.getInterface(i); UsbInterface usbIface = mDevice.getInterface(i);
if (!mConnection.claimInterface(usbIface, true)) { if (!mConnection.claimInterface(usbIface, true)) {
throw new IOException("Could not claim data interface"); throw new IOException("Could not claim data interface");
} }
} }
UsbInterface dataIface = mDevice.getInterface(mDevice.getInterfaceCount() - 1); UsbInterface dataIface = mDevice.getInterface(mDevice.getInterfaceCount() - 1);
for (int i = 0; i < dataIface.getEndpointCount(); i++) { for (int i = 0; i < dataIface.getEndpointCount(); i++) {
UsbEndpoint ep = dataIface.getEndpoint(i); UsbEndpoint ep = dataIface.getEndpoint(i);
if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
if (ep.getDirection() == UsbConstants.USB_DIR_IN) { if (ep.getDirection() == UsbConstants.USB_DIR_IN) {
mReadEndpoint = ep; mReadEndpoint = ep;
} else { } else {
mWriteEndpoint = ep; mWriteEndpoint = ep;
} }
} }
} }
initialize(); initialize();
setBaudRate(DEFAULT_BAUD_RATE); setBaudRate(DEFAULT_BAUD_RATE);
} }
@Override @Override
protected void closeInt() { protected void closeInt() {
try { try {
for (int i = 0; i < mDevice.getInterfaceCount(); i++) for (int i = 0; i < mDevice.getInterfaceCount(); i++)
mConnection.releaseInterface(mDevice.getInterface(i)); mConnection.releaseInterface(mDevice.getInterface(i));
} catch(Exception ignored) {} } catch(Exception ignored) {}
} }
private int controlOut(int request, int value, int index) { private int controlOut(int request, int value, int index) {
final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT; final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT;
return mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, return mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request,
value, index, null, 0, USB_TIMEOUT_MILLIS); value, index, null, 0, USB_TIMEOUT_MILLIS);
} }
private int controlIn(int request, int value, int index, byte[] buffer) { private int controlIn(int request, int value, int index, byte[] buffer) {
final int REQTYPE_DEVICE_TO_HOST = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_IN; final int REQTYPE_DEVICE_TO_HOST = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_IN;
return mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, request, return mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, request,
value, index, buffer, buffer.length, USB_TIMEOUT_MILLIS); value, index, buffer, buffer.length, USB_TIMEOUT_MILLIS);
} }
private void checkState(String msg, int request, int value, int[] expected) throws IOException { private void checkState(String msg, int request, int value, int[] expected) throws IOException {
byte[] buffer = new byte[expected.length]; byte[] buffer = new byte[expected.length];
int ret = controlIn(request, value, 0, buffer); int ret = controlIn(request, value, 0, buffer);
if (ret < 0) { if (ret < 0) {
throw new IOException("Failed send cmd [" + msg + "]"); throw new IOException("Failed send cmd [" + msg + "]");
} }
if (ret != expected.length) { if (ret != expected.length) {
throw new IOException("Expected " + expected.length + " bytes, but get " + ret + " [" + msg + "]"); throw new IOException("Expected " + expected.length + " bytes, but get " + ret + " [" + msg + "]");
} }
for (int i = 0; i < expected.length; i++) { for (int i = 0; i < expected.length; i++) {
if (expected[i] == -1) { if (expected[i] == -1) {
continue; continue;
} }
int current = buffer[i] & 0xff; int current = buffer[i] & 0xff;
if (expected[i] != current) { if (expected[i] != current) {
throw new IOException("Expected 0x" + Integer.toHexString(expected[i]) + " byte, but get 0x" + Integer.toHexString(current) + " [" + msg + "]"); throw new IOException("Expected 0x" + Integer.toHexString(expected[i]) + " byte, but get 0x" + Integer.toHexString(current) + " [" + msg + "]");
} }
} }
} }
private void setControlLines() throws IOException { private void setControlLines() throws IOException {
if (controlOut(0xa4, ~((dtr ? SCL_DTR : 0) | (rts ? SCL_RTS : 0)), 0) < 0) { if (controlOut(0xa4, ~((dtr ? SCL_DTR : 0) | (rts ? SCL_RTS : 0)), 0) < 0) {
throw new IOException("Failed to set control lines"); throw new IOException("Failed to set control lines");
} }
} }
private byte getStatus() throws IOException { private byte getStatus() throws IOException {
byte[] buffer = new byte[2]; byte[] buffer = new byte[2];
int ret = controlIn(0x95, 0x0706, 0, buffer); int ret = controlIn(0x95, 0x0706, 0, buffer);
if (ret < 0) if (ret < 0)
throw new IOException("Error getting control lines"); throw new IOException("Error getting control lines");
return buffer[0]; return buffer[0];
} }
private void initialize() throws IOException { private void initialize() throws IOException {
checkState("init #1", 0x5f, 0, new int[]{-1 /* 0x27, 0x30 */, 0x00}); checkState("init #1", 0x5f, 0, new int[]{-1 /* 0x27, 0x30 */, 0x00});
if (controlOut(0xa1, 0, 0) < 0) { if (controlOut(0xa1, 0, 0) < 0) {
throw new IOException("Init failed: #2"); throw new IOException("Init failed: #2");
} }
setBaudRate(DEFAULT_BAUD_RATE); setBaudRate(DEFAULT_BAUD_RATE);
checkState("init #4", 0x95, 0x2518, new int[]{-1 /* 0x56, c3*/, 0x00}); checkState("init #4", 0x95, 0x2518, new int[]{-1 /* 0x56, c3*/, 0x00});
if (controlOut(0x9a, 0x2518, LCR_ENABLE_RX | LCR_ENABLE_TX | LCR_CS8) < 0) { if (controlOut(0x9a, 0x2518, LCR_ENABLE_RX | LCR_ENABLE_TX | LCR_CS8) < 0) {
throw new IOException("Init failed: #5"); throw new IOException("Init failed: #5");
} }
checkState("init #6", 0x95, 0x0706, new int[]{-1/*0xf?*/, -1/*0xec,0xee*/}); checkState("init #6", 0x95, 0x0706, new int[]{-1/*0xf?*/, -1/*0xec,0xee*/});
if (controlOut(0xa1, 0x501f, 0xd90a) < 0) { if (controlOut(0xa1, 0x501f, 0xd90a) < 0) {
throw new IOException("Init failed: #7"); throw new IOException("Init failed: #7");
} }
setBaudRate(DEFAULT_BAUD_RATE); setBaudRate(DEFAULT_BAUD_RATE);
setControlLines(); setControlLines();
checkState("init #10", 0x95, 0x0706, new int[]{-1/* 0x9f, 0xff*/, -1/*0xec,0xee*/}); checkState("init #10", 0x95, 0x0706, new int[]{-1/* 0x9f, 0xff*/, -1/*0xec,0xee*/});
} }
private void setBaudRate(int baudRate) throws IOException { private void setBaudRate(int baudRate) throws IOException {
final long CH341_BAUDBASE_FACTOR = 1532620800; long factor;
final int CH341_BAUDBASE_DIVMAX = 3; long divisor;
long factor = CH341_BAUDBASE_FACTOR / baudRate; if (baudRate == 921600) {
int divisor = CH341_BAUDBASE_DIVMAX; divisor = 7;
factor = 0xf300;
} else {
final long BAUDBASE_FACTOR = 1532620800;
final int BAUDBASE_DIVMAX = 3;
while ((factor > 0xfff0) && divisor > 0) { if(BuildConfig.DEBUG && (baudRate & (3<<29)) == (1<<29))
factor >>= 3; baudRate &= ~(1<<29); // for testing purpose bypass dedicated baud rate handling
divisor--; 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) { divisor |= 0x0080; // else ch341a waits until buffer full
throw new UnsupportedOperationException("Unsupported baud rate: " + baudRate); 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; @Override
divisor |= 0x0080; // else ch341a waits until buffer full public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException {
int ret = controlOut(0x9a, 0x1312, (int) ((factor & 0xff00) | divisor)); if(baudRate <= 0) {
if (ret < 0) { throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
throw new IOException("Error setting baud rate: #1)"); }
} setBaudRate(baudRate);
ret = controlOut(0x9a, 0x0f2c, (int) (factor & 0xff)); int lcr = LCR_ENABLE_RX | LCR_ENABLE_TX;
if (ret < 0) {
throw new IOException("Error setting baud rate: #2");
}
}
@Override switch (dataBits) {
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { case DATABITS_5:
if(baudRate <= 0) { lcr |= LCR_CS5;
throw new IllegalArgumentException("Invalid baud rate: " + baudRate); break;
} case DATABITS_6:
setBaudRate(baudRate); 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) { switch (stopBits) {
case DATABITS_5: case STOPBITS_1:
lcr |= LCR_CS5; break;
break; case STOPBITS_1_5:
case DATABITS_6: throw new UnsupportedOperationException("Unsupported stop bits: 1.5");
lcr |= LCR_CS6; case STOPBITS_2:
break; lcr |= LCR_STOP_BITS_2;
case DATABITS_7: break;
lcr |= LCR_CS7; default:
break; throw new IllegalArgumentException("Invalid stop bits: " + stopBits);
case DATABITS_8: }
lcr |= LCR_CS8;
break;
default:
throw new IllegalArgumentException("Invalid data bits: " + dataBits);
}
switch (parity) { int ret = controlOut(0x9a, 0x2518, lcr);
case PARITY_NONE: if (ret < 0) {
break; throw new IOException("Error setting control byte");
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 (stopBits) { @Override
case STOPBITS_1: public boolean getCD() throws IOException {
break; return (getStatus() & GCL_CD) == 0;
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);
}
int ret = controlOut(0x9a, 0x2518, lcr); @Override
if (ret < 0) { public boolean getCTS() throws IOException {
throw new IOException("Error setting control byte"); return (getStatus() & GCL_CTS) == 0;
} }
}
@Override @Override
public boolean getCD() throws IOException { public boolean getDSR() throws IOException {
return (getStatus() & GCL_CD) == 0; return (getStatus() & GCL_DSR) == 0;
} }
@Override @Override
public boolean getCTS() throws IOException { public boolean getDTR() throws IOException {
return (getStatus() & GCL_CTS) == 0; return dtr;
} }
@Override @Override
public boolean getDSR() throws IOException { public void setDTR(boolean value) throws IOException {
return (getStatus() & GCL_DSR) == 0; dtr = value;
} setControlLines();
}
@Override @Override
public boolean getDTR() throws IOException { public boolean getRI() throws IOException {
return dtr; return (getStatus() & GCL_RI) == 0;
} }
@Override @Override
public void setDTR(boolean value) throws IOException { public boolean getRTS() throws IOException {
dtr = value; return rts;
setControlLines(); }
}
@Override @Override
public boolean getRI() throws IOException { public void setRTS(boolean value) throws IOException {
return (getStatus() & GCL_RI) == 0; rts = value;
} setControlLines();
}
@Override @Override
public boolean getRTS() throws IOException { public EnumSet<ControlLine> getControlLines() throws IOException {
return rts; 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 @Override
public void setRTS(boolean value) throws IOException { public EnumSet<ControlLine> getSupportedControlLines() throws IOException {
rts = value; return EnumSet.allOf(ControlLine.class);
setControlLines(); }
}
@Override @Override
public EnumSet<ControlLine> getControlLines() throws IOException { public void setBreak(boolean value) throws IOException {
int status = getStatus(); byte[] req = new byte[2];
EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class); if(controlIn(0x95, 0x1805, 0, req) < 0) {
if(rts) set.add(ControlLine.RTS); throw new IOException("Error getting BREAK condition");
if((status & GCL_CTS) == 0) set.add(ControlLine.CTS); }
if(dtr) set.add(ControlLine.DTR); if(value) {
if((status & GCL_DSR) == 0) set.add(ControlLine.DSR); req[0] &= ~1;
if((status & GCL_CD) == 0) set.add(ControlLine.CD); req[1] &= ~0x40;
if((status & GCL_RI) == 0) set.add(ControlLine.RI); } else {
return set; 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 @SuppressWarnings({"unused"})
public EnumSet<ControlLine> getSupportedControlLines() throws IOException { public static Map<Integer, int[]> getSupportedDevices() {
return EnumSet.allOf(ControlLine.class); final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
} supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{
} UsbId.QINHENG_CH340,
UsbId.QINHENG_CH341A,
public static Map<Integer, int[]> getSupportedDevices() { });
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<Integer, int[]>(); return supportedDevices;
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.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbRequest; import android.hardware.usb.UsbRequest;
import android.os.Build;
import android.util.Log; import android.util.Log;
import com.hoho.android.usbserial.util.MonotonicClock;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.EnumSet; import java.util.EnumSet;
@ -23,28 +26,33 @@ import java.util.EnumSet;
*/ */
public abstract class CommonUsbSerialPort implements UsbSerialPort { public abstract class CommonUsbSerialPort implements UsbSerialPort {
public static boolean DEBUG = false;
private static final String TAG = CommonUsbSerialPort.class.getSimpleName(); private static final String TAG = CommonUsbSerialPort.class.getSimpleName();
private static final int DEFAULT_WRITE_BUFFER_SIZE = 16 * 1024; private static final int MAX_READ_SIZE = 16 * 1024; // = old bulkTransfer limit prior to Android 9
private static final int MAX_READ_SIZE = 16 * 1024; // = old bulkTransfer limit
protected final UsbDevice mDevice; protected final UsbDevice mDevice;
protected final int mPortNumber; protected final int mPortNumber;
// non-null when open() // non-null when open()
protected UsbDeviceConnection mConnection = null; protected UsbDeviceConnection mConnection;
protected UsbEndpoint mReadEndpoint; protected UsbEndpoint mReadEndpoint;
protected UsbEndpoint mWriteEndpoint; protected UsbEndpoint mWriteEndpoint;
protected UsbRequest mUsbRequest; protected UsbRequest mUsbRequest;
protected FlowControl mFlowControl = FlowControl.NONE;
protected final Object mWriteBufferLock = new Object(); /**
/** Internal write buffer. Guarded by {@link #mWriteBufferLock}. */ * Internal write buffer.
* Guarded by {@link #mWriteBufferLock}.
* Default length = mReadEndpoint.getMaxPacketSize()
**/
protected byte[] mWriteBuffer; protected byte[] mWriteBuffer;
protected final Object mWriteBufferLock = new Object();
public CommonUsbSerialPort(UsbDevice device, int portNumber) { public CommonUsbSerialPort(UsbDevice device, int portNumber) {
mDevice = device; mDevice = device;
mPortNumber = portNumber; mPortNumber = portNumber;
mWriteBuffer = new byte[DEFAULT_WRITE_BUFFER_SIZE];
} }
@Override @Override
@ -63,7 +71,13 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort {
public int getPortNumber() { public int getPortNumber() {
return mPortNumber; return mPortNumber;
} }
@Override
public UsbEndpoint getWriteEndpoint() { return mWriteEndpoint; }
@Override
public UsbEndpoint getReadEndpoint() { return mReadEndpoint; }
/** /**
* Returns the device serial number * Returns the device serial number
* @return 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 * Sets the size of the internal buffer used to exchange data with the USB
* stack for write operations. Most users should not need to change this. * stack for write operations. Most users should not need to change this.
* *
* @param bufferSize the size in bytes * @param bufferSize the size in bytes, <= 0 resets original size
*/ */
public final void setWriteBufferSize(int bufferSize) { public final void setWriteBufferSize(int bufferSize) {
synchronized (mWriteBufferLock) { synchronized (mWriteBufferLock) {
if (bufferSize == mWriteBuffer.length) { if (bufferSize <= 0) {
if (mWriteEndpoint != null) {
bufferSize = mWriteEndpoint.getMaxPacketSize();
} else {
mWriteBuffer = null;
return;
}
}
if (mWriteBuffer != null && bufferSize == mWriteBuffer.length) {
return; return;
} }
mWriteBuffer = new byte[bufferSize]; mWriteBuffer = new byte[bufferSize];
@ -93,31 +115,40 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort {
if (mConnection != null) { if (mConnection != null) {
throw new IOException("Already open"); throw new IOException("Already open");
} }
if(connection == null) {
throw new IllegalArgumentException("Connection is null");
}
mConnection = connection; mConnection = connection;
boolean ok = false;
try { try {
openInt(connection); openInt();
if (mReadEndpoint == null || mWriteEndpoint == null) { if (mReadEndpoint == null || mWriteEndpoint == null) {
throw new IOException("Could not get read & write endpoints"); throw new IOException("Could not get read & write endpoints");
} }
mUsbRequest = new UsbRequest(); mUsbRequest = new UsbRequest();
mUsbRequest.initialize(mConnection, mReadEndpoint); mUsbRequest.initialize(mConnection, mReadEndpoint);
} catch(Exception e) { ok = true;
close(); } finally {
throw e; if (!ok) {
try {
close();
} catch (Exception ignored) {}
}
} }
} }
protected abstract void openInt(UsbDeviceConnection connection) throws IOException; protected abstract void openInt() throws IOException;
@Override @Override
public void close() throws IOException { public void close() throws IOException {
if (mConnection == null) { if (mConnection == null) {
throw new IOException("Already closed"); throw new IOException("Already closed");
} }
try { UsbRequest usbRequest = mUsbRequest;
mUsbRequest.cancel();
} catch(Exception ignored) {}
mUsbRequest = null; mUsbRequest = null;
try {
usbRequest.cancel();
} catch(Exception ignored) {}
try { try {
closeInt(); closeInt();
} catch(Exception ignored) {} } catch(Exception ignored) {}
@ -129,11 +160,43 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort {
protected abstract void closeInt(); protected abstract void closeInt();
@Override /**
public int read(final byte[] dest, final int timeout) throws IOException { * use simple USB request supported by all devices to test if connection is still valid
if(mConnection == null) { */
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"); 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; final int nread;
if (timeout != 0) { if (timeout != 0) {
// bulkTransfer will cause data loss with short timeout + high baud rates + continuous transfer // bulkTransfer will cause data loss with short timeout + high baud rates + continuous transfer
@ -144,12 +207,17 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort {
// /system/lib64/libusbhost.so (usb_request_wait+192) // /system/lib64/libusbhost.so (usb_request_wait+192)
// /system/lib64/libandroid_runtime.so (android_hardware_UsbDeviceConnection_request_wait(_JNIEnv*, _jobject*, long)+84) // /system/lib64/libandroid_runtime.so (android_hardware_UsbDeviceConnection_request_wait(_JNIEnv*, _jobject*, long)+84)
// data loss / crashes were observed with timeout up to 200 msec // data loss / crashes were observed with timeout up to 200 msec
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); 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 { } else {
final ByteBuffer buf = ByteBuffer.wrap(dest); final ByteBuffer buf = ByteBuffer.wrap(dest, 0, length);
if (!mUsbRequest.queue(buf, dest.length)) { if (!mUsbRequest.queue(buf, length)) {
throw new IOException("Queueing USB request failed"); throw new IOException("Queueing USB request failed");
} }
final UsbRequest response = mConnection.requestWait(); final UsbRequest response = mConnection.requestWait();
@ -157,59 +225,83 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort {
throw new IOException("Waiting for USB request failed"); throw new IOException("Waiting for USB request failed");
} }
nread = buf.position(); 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 Math.max(nread, 0);
return readFilter(dest, nread);
} else {
return 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 @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; int offset = 0;
long startTime = MonotonicClock.millis();
length = Math.min(length, src.length);
if(mConnection == null) { testConnection(false);
throw new IOException("Connection closed"); while (offset < length) {
} int requestTimeout;
while (offset < src.length) { final int requestLength;
final int writeLength; final int actualLength;
final int amtWritten;
synchronized (mWriteBufferLock) { synchronized (mWriteBufferLock) {
final byte[] writeBuffer; 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) { if (offset == 0) {
writeBuffer = src; writeBuffer = src;
} else { } else {
// bulkTransfer does not support offsets, make a copy. // bulkTransfer does not support offsets, make a copy.
System.arraycopy(src, offset, mWriteBuffer, 0, writeLength); System.arraycopy(src, offset, mWriteBuffer, 0, requestLength);
writeBuffer = mWriteBuffer; writeBuffer = mWriteBuffer;
} }
if (timeout == 0 || offset == 0) {
amtWritten = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, writeLength, timeout); 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) { long elapsed = MonotonicClock.millis() - startTime;
throw new IOException("Error writing " + writeLength if (DEBUG) {
+ " bytes at offset " + offset + " length=" + src.length); 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 @Override
public boolean isOpen() { public boolean isOpen() {
return mConnection != null; return mUsbRequest != null;
} }
@Override @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 @Override
public boolean getCD() throws IOException { throw new UnsupportedOperationException(); } 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(); } public void setRTS(boolean value) throws IOException { throw new UnsupportedOperationException(); }
@Override @Override
public abstract EnumSet<ControlLine> getControlLines() throws IOException; public EnumSet<ControlLine> getControlLines() throws IOException { throw new UnsupportedOperationException(); }
@Override @Override
public abstract EnumSet<ControlLine> getSupportedControlLines() throws IOException; public EnumSet<ControlLine> getSupportedControlLines() throws IOException { return EnumSet.noneOf(ControlLine.class); }
@Override @Override
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { public void setFlowControl(FlowControl flowcontrol) throws IOException {
throw new UnsupportedOperationException(); if (flowcontrol != FlowControl.NONE)
throw new UnsupportedOperationException();
} }
@Override
public FlowControl getFlowControl() { return mFlowControl; }
@Override
public EnumSet<FlowControl> getSupportedFlowControl() { return EnumSet.of(FlowControl.NONE); }
@Override
public boolean getXON() throws IOException { throw new UnsupportedOperationException(); }
@Override
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { throw new UnsupportedOperationException(); }
@Override
public void setBreak(boolean value) throws IOException { throw new UnsupportedOperationException(); }
} }

View File

@ -8,7 +8,6 @@ package com.hoho.android.usbserial.driver;
import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbInterface;
@ -59,10 +58,16 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
*/ */
private static final int SILABSER_IFC_ENABLE_REQUEST_CODE = 0x00; 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_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_MHS_REQUEST_CODE = 0x07;
private static final int SILABSER_SET_BAUDRATE = 0x1E;
private static final int SILABSER_FLUSH_REQUEST_CODE = 0x12;
private static final int SILABSER_GET_MDMSTS_REQUEST_CODE = 0x08; private static final int SILABSER_GET_MDMSTS_REQUEST_CODE = 0x08;
private static final int SILABSER_SET_XON_REQUEST_CODE = 0x09;
private static final int SILABSER_SET_XOFF_REQUEST_CODE = 0x0A;
private static final int SILABSER_GET_COMM_STATUS_REQUEST_CODE = 0x10;
private static final int SILABSER_FLUSH_REQUEST_CODE = 0x12;
private static final int SILABSER_SET_FLOW_REQUEST_CODE = 0x13;
private static final int SILABSER_SET_CHARS_REQUEST_CODE = 0x19;
private static final int SILABSER_SET_BAUDRATE_REQUEST_CODE = 0x1E;
private static final int FLUSH_READ_CODE = 0x0a; private static final int FLUSH_READ_CODE = 0x0a;
private static final int FLUSH_WRITE_CODE = 0x05; private static final int FLUSH_WRITE_CODE = 0x05;
@ -84,6 +89,8 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
/* /*
* SILABSER_GET_MDMSTS_REQUEST_CODE * SILABSER_GET_MDMSTS_REQUEST_CODE
*/ */
private static final int STATUS_DTR = 0x01;
private static final int STATUS_RTS = 0x02;
private static final int STATUS_CTS = 0x10; private static final int STATUS_CTS = 0x10;
private static final int STATUS_DSR = 0x20; private static final int STATUS_DSR = 0x20;
private static final int STATUS_RI = 0x40; private static final int STATUS_RI = 0x40;
@ -118,14 +125,14 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
byte[] buffer = new byte[1]; byte[] buffer = new byte[1];
int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, SILABSER_GET_MDMSTS_REQUEST_CODE, 0, int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, SILABSER_GET_MDMSTS_REQUEST_CODE, 0,
mPortNumber, buffer, buffer.length, USB_WRITE_TIMEOUT_MILLIS); mPortNumber, buffer, buffer.length, USB_WRITE_TIMEOUT_MILLIS);
if (result != 1) { if (result != buffer.length) {
throw new IOException("Control transfer failed: " + SILABSER_GET_MDMSTS_REQUEST_CODE + " / " + 0 + " -> " + result); throw new IOException("Control transfer failed: " + SILABSER_GET_MDMSTS_REQUEST_CODE + " / " + 0 + " -> " + result);
} }
return buffer[0]; return buffer[0];
} }
@Override @Override
protected void openInt(UsbDeviceConnection connection) throws IOException { protected void openInt() throws IOException {
mIsRestrictedPort = mDevice.getInterfaceCount() == 2 && mPortNumber == 1; mIsRestrictedPort = mDevice.getInterfaceCount() == 2 && mPortNumber == 1;
if(mPortNumber >= mDevice.getInterfaceCount()) { if(mPortNumber >= mDevice.getInterfaceCount()) {
throw new IOException("Unknown port number"); throw new IOException("Unknown port number");
@ -147,6 +154,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_ENABLE); setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_ENABLE);
setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, (dtr ? DTR_ENABLE : DTR_DISABLE) | (rts ? RTS_ENABLE : RTS_DISABLE)); setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, (dtr ? DTR_ENABLE : DTR_DISABLE) | (rts ? RTS_ENABLE : RTS_DISABLE));
setFlowControl(mFlowControl);
} }
@Override @Override
@ -166,7 +174,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
(byte) ((baudRate >> 16) & 0xff), (byte) ((baudRate >> 16) & 0xff),
(byte) ((baudRate >> 24) & 0xff) (byte) ((baudRate >> 24) & 0xff)
}; };
int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_BAUDRATE, int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_BAUDRATE_REQUEST_CODE,
0, mPortNumber, data, 4, USB_WRITE_TIMEOUT_MILLIS); 0, mPortNumber, data, 4, USB_WRITE_TIMEOUT_MILLIS);
if (ret < 0) { if (ret < 0) {
throw new IOException("Error setting baud rate"); throw new IOException("Error setting baud rate");
@ -174,7 +182,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
} }
@Override @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) { if(baudRate <= 0) {
throw new IllegalArgumentException("Invalid baud rate: " + baudRate); throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
} }
@ -289,9 +297,11 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
public EnumSet<ControlLine> getControlLines() throws IOException { public EnumSet<ControlLine> getControlLines() throws IOException {
byte status = getStatus(); byte status = getStatus();
EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class); EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class);
if(rts) set.add(ControlLine.RTS); //if(rts) set.add(ControlLine.RTS); // configured value
if((status & STATUS_RTS) != 0) set.add(ControlLine.RTS); // actual value
if((status & STATUS_CTS) != 0) set.add(ControlLine.CTS); if((status & STATUS_CTS) != 0) set.add(ControlLine.CTS);
if(dtr) set.add(ControlLine.DTR); //if(dtr) set.add(ControlLine.DTR); // configured value
if((status & STATUS_DTR) != 0) set.add(ControlLine.DTR); // actual value
if((status & STATUS_DSR) != 0) set.add(ControlLine.DSR); if((status & STATUS_DSR) != 0) set.add(ControlLine.DSR);
if((status & STATUS_CD) != 0) set.add(ControlLine.CD); if((status & STATUS_CD) != 0) set.add(ControlLine.CD);
if((status & STATUS_RI) != 0) set.add(ControlLine.RI); if((status & STATUS_RI) != 0) set.add(ControlLine.RI);
@ -303,6 +313,73 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
return EnumSet.allOf(ControlLine.class); return EnumSet.allOf(ControlLine.class);
} }
@Override
public boolean getXON() throws IOException {
byte[] buffer = new byte[0x13];
int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, SILABSER_GET_COMM_STATUS_REQUEST_CODE, 0,
mPortNumber, buffer, buffer.length, USB_WRITE_TIMEOUT_MILLIS);
if (result != buffer.length) {
throw new IOException("Control transfer failed: " + SILABSER_GET_COMM_STATUS_REQUEST_CODE + " -> " + result);
}
return (buffer[4] & 8) == 0;
}
/**
* emulate external XON/OFF
* @throws IOException
*/
public void setXON(boolean value) throws IOException {
setConfigSingle(value ? SILABSER_SET_XON_REQUEST_CODE : SILABSER_SET_XOFF_REQUEST_CODE, 0);
}
@Override
public void setFlowControl(FlowControl flowControl) throws IOException {
byte[] data = new byte[16];
if(flowControl == FlowControl.RTS_CTS) {
data[4] |= 0b1000_0000; // RTS
data[0] |= 0b0000_1000; // CTS
} else {
if(rts)
data[4] |= 0b0100_0000;
}
if(flowControl == FlowControl.DTR_DSR) {
data[0] |= 0b0000_0010; // DTR
data[0] |= 0b0001_0000; // DSR
} else {
if(dtr)
data[0] |= 0b0000_0001;
}
if(flowControl == FlowControl.XON_XOFF) {
byte[] chars = new byte[]{0, 0, 0, 0, CHAR_XON, CHAR_XOFF};
int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_CHARS_REQUEST_CODE,
0, mPortNumber, chars, chars.length, USB_WRITE_TIMEOUT_MILLIS);
if (ret != chars.length) {
throw new IOException("Error setting XON/XOFF chars");
}
data[4] |= 0b0000_0011;
data[7] |= 0b1000_0000;
data[8] = (byte)128;
data[12] = (byte)128;
}
if(flowControl == FlowControl.XON_XOFF_INLINE) {
throw new UnsupportedOperationException();
}
int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_FLOW_REQUEST_CODE,
0, mPortNumber, data, data.length, USB_WRITE_TIMEOUT_MILLIS);
if (ret != data.length) {
throw new IOException("Error setting flow control");
}
if(flowControl == FlowControl.XON_XOFF) {
setXON(true);
}
mFlowControl = flowControl;
}
@Override
public EnumSet<FlowControl> getSupportedFlowControl() {
return EnumSet.of(FlowControl.NONE, FlowControl.RTS_CTS, FlowControl.DTR_DSR, FlowControl.XON_XOFF);
}
@Override @Override
// note: only working on some devices, on other devices ignored w/o error // note: only working on some devices, on other devices ignored w/o error
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {
@ -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() { 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, supportedDevices.put(UsbId.VENDOR_SILABS,
new int[] { new int[] {
UsbId.SILABS_CP2102, UsbId.SILABS_CP2102, // same ID for CP2101, CP2103, CP2104, CP2109
UsbId.SILABS_CP2105, UsbId.SILABS_CP2105,
UsbId.SILABS_CP2108, UsbId.SILABS_CP2108,
UsbId.SILABS_CP2110
}); });
return supportedDevices; return supportedDevices;
} }

View File

@ -9,9 +9,10 @@ package com.hoho.android.usbserial.driver;
import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.util.Log; import android.util.Log;
import com.hoho.android.usbserial.util.MonotonicClock;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet; import java.util.EnumSet;
@ -63,6 +64,7 @@ public class FtdiSerialDriver implements UsbSerialDriver {
private static final int RESET_REQUEST = 0; private static final int RESET_REQUEST = 0;
private static final int MODEM_CONTROL_REQUEST = 1; private static final int MODEM_CONTROL_REQUEST = 1;
private static final int SET_FLOW_CONTROL_REQUEST = 2;
private static final int SET_BAUD_RATE_REQUEST = 3; private static final int SET_BAUD_RATE_REQUEST = 3;
private static final int SET_DATA_REQUEST = 4; private static final int SET_DATA_REQUEST = 4;
private static final int GET_MODEM_STATUS_REQUEST = 5; private static final int GET_MODEM_STATUS_REQUEST = 5;
@ -84,6 +86,7 @@ public class FtdiSerialDriver implements UsbSerialDriver {
private boolean baudRateWithPort = false; private boolean baudRateWithPort = false;
private boolean dtr = false; private boolean dtr = false;
private boolean rts = false; private boolean rts = false;
private int breakConfig = 0;
public FtdiSerialPort(UsbDevice device, int portNumber) { public FtdiSerialPort(UsbDevice device, int portNumber) {
super(device, portNumber); super(device, portNumber);
@ -96,8 +99,8 @@ public class FtdiSerialDriver implements UsbSerialDriver {
@Override @Override
protected void openInt(UsbDeviceConnection connection) throws IOException { protected void openInt() throws IOException {
if (!connection.claimInterface(mDevice.getInterface(mPortNumber), true)) { if (!mConnection.claimInterface(mDevice.getInterface(mPortNumber), true)) {
throw new IOException("Could not claim interface " + mPortNumber); throw new IOException("Could not claim interface " + mPortNumber);
} }
if (mDevice.getInterface(mPortNumber).getEndpointCount() < 2) { if (mDevice.getInterface(mPortNumber).getEndpointCount() < 2) {
@ -118,14 +121,16 @@ public class FtdiSerialDriver implements UsbSerialDriver {
if (result != 0) { if (result != 0) {
throw new IOException("Init RTS,DTR failed: result=" + result); throw new IOException("Init RTS,DTR failed: result=" + result);
} }
setFlowControl(mFlowControl);
// mDevice.getVersion() would require API 23 // mDevice.getVersion() would require API 23
byte[] rawDescriptors = connection.getRawDescriptors(); byte[] rawDescriptors = mConnection.getRawDescriptors();
if(rawDescriptors == null || rawDescriptors.length < 14) { if(rawDescriptors == null || rawDescriptors.length < 14) {
throw new IOException("Could not get device descriptors"); throw new IOException("Could not get device descriptors");
} }
int deviceType = rawDescriptors[13]; int deviceType = rawDescriptors[13];
baudRateWithPort = deviceType == 7 || deviceType == 8 || deviceType == 9; // ...H devices baudRateWithPort = deviceType == 7 || deviceType == 8 || deviceType == 9 // ...H devices
|| mDevice.getInterfaceCount() > 1; // FT2232C
} }
@Override @Override
@ -136,6 +141,42 @@ public class FtdiSerialDriver implements UsbSerialDriver {
} }
@Override @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 { protected int readFilter(byte[] buffer, int totalBytesRead) throws IOException {
final int maxPacketSize = mReadEndpoint.getMaxPacketSize(); final int maxPacketSize = mReadEndpoint.getMaxPacketSize();
int destPos = 0; int destPos = 0;
@ -146,13 +187,14 @@ public class FtdiSerialDriver implements UsbSerialDriver {
System.arraycopy(buffer, srcPos + READ_HEADER_LENGTH, buffer, destPos, length); System.arraycopy(buffer, srcPos + READ_HEADER_LENGTH, buffer, destPos, length);
destPos += length; destPos += length;
} }
//Log.d(TAG, "read filter " + totalBytesRead + " -> " + destPos);
return destPos; return destPos;
} }
private void setBaudrate(int baudRate) throws IOException { private void setBaudrate(int baudRate) throws IOException {
int divisor, subdivisor, effectiveBaudRate; int divisor, subdivisor, effectiveBaudRate;
if (baudRate > 3500000) { if (baudRate > 3500000) {
throw new IOException("Baud rate to high"); throw new UnsupportedOperationException("Baud rate to high");
} else if(baudRate >= 2500000) { } else if(baudRate >= 2500000) {
divisor = 0; divisor = 0;
subdivisor = 0; subdivisor = 0;
@ -167,13 +209,13 @@ public class FtdiSerialDriver implements UsbSerialDriver {
subdivisor = divisor & 0x07; subdivisor = divisor & 0x07;
divisor >>= 3; divisor >>= 3;
if (divisor > 0x3fff) // exceeds bit 13 at 183 baud 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 = (24000000 << 1) / ((divisor << 3) + subdivisor);
effectiveBaudRate = (effectiveBaudRate +1) >> 1; effectiveBaudRate = (effectiveBaudRate +1) >> 1;
} }
double baudRateError = Math.abs(1.0 - (effectiveBaudRate / (double)baudRate)); double baudRateError = Math.abs(1.0 - (effectiveBaudRate / (double)baudRate));
if(baudRateError >= 0.031) // can happen only > 1.5Mbaud 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 value = divisor;
int index = 0; int index = 0;
switch(subdivisor) { switch(subdivisor) {
@ -201,7 +243,7 @@ public class FtdiSerialDriver implements UsbSerialDriver {
} }
@Override @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) { if(baudRate <= 0) {
throw new IllegalArgumentException("Invalid baud rate: " + baudRate); throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
} }
@ -256,13 +298,14 @@ public class FtdiSerialDriver implements UsbSerialDriver {
if (result != 0) { if (result != 0) {
throw new IOException("Setting parameters failed: result=" + result); throw new IOException("Setting parameters failed: result=" + result);
} }
breakConfig = config;
} }
private int getStatus() throws IOException { private int getStatus() throws IOException {
byte[] data = new byte[2]; byte[] data = new byte[2];
int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_MODEM_STATUS_REQUEST, int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_MODEM_STATUS_REQUEST,
0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS); 0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS);
if (result != 2) { if (result != data.length) {
throw new IOException("Get modem status failed: result=" + result); throw new IOException("Get modem status failed: result=" + result);
} }
return data[0]; return data[0];
@ -336,13 +379,45 @@ public class FtdiSerialDriver implements UsbSerialDriver {
return EnumSet.allOf(ControlLine.class); return EnumSet.allOf(ControlLine.class);
} }
@Override
public void setFlowControl(FlowControl flowControl) throws IOException {
int value = 0;
int index = mPortNumber+1;
switch (flowControl) {
case NONE:
break;
case RTS_CTS:
index |= 0x100;
break;
case DTR_DSR:
index |= 0x200;
break;
case XON_XOFF_INLINE:
value = CHAR_XON + (CHAR_XOFF << 8);
index |= 0x400;
break;
default:
throw new UnsupportedOperationException();
}
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_FLOW_CONTROL_REQUEST,
value, index, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0)
throw new IOException("Set flow control failed: result=" + result);
mFlowControl = flowControl;
}
@Override
public EnumSet<FlowControl> getSupportedFlowControl() {
return EnumSet.of(FlowControl.NONE, FlowControl.RTS_CTS, FlowControl.DTR_DSR, FlowControl.XON_XOFF_INLINE);
}
@Override @Override
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {
if (purgeWriteBuffers) { if (purgeWriteBuffers) {
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST, int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST,
RESET_PURGE_RX, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); RESET_PURGE_RX, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) { 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, int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST,
RESET_PURGE_TX, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); RESET_PURGE_TX, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) { 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 { public void setLatencyTimer(int latencyTime) throws IOException {
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_LATENCY_TIMER_REQUEST, int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_LATENCY_TIMER_REQUEST,
latencyTime, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); latencyTime, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
@ -367,7 +453,7 @@ public class FtdiSerialDriver implements UsbSerialDriver {
byte[] data = new byte[1]; byte[] data = new byte[1];
int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_LATENCY_TIMER_REQUEST, int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_LATENCY_TIMER_REQUEST,
0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS); 0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS);
if (result != 1) { if (result != data.length) {
throw new IOException("Get latency timer failed: result=" + result); throw new IOException("Get latency timer failed: result=" + result);
} }
return data[0]; return data[0];
@ -375,14 +461,16 @@ public class FtdiSerialDriver implements UsbSerialDriver {
} }
@SuppressWarnings({"unused"})
public static Map<Integer, int[]> getSupportedDevices() { public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<Integer, int[]>(); final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_FTDI, supportedDevices.put(UsbId.VENDOR_FTDI,
new int[] { new int[] {
UsbId.FTDI_FT232R, UsbId.FTDI_FT232R,
UsbId.FTDI_FT232H, UsbId.FTDI_FT232H,
UsbId.FTDI_FT2232H, UsbId.FTDI_FT2232H,
UsbId.FTDI_FT4232H, UsbId.FTDI_FT4232H,
UsbId.FTDI_FT231X, // same ID for FT230X, FT231X, FT234XD
}); });
return supportedDevices; return supportedDevices;
} }

View File

@ -0,0 +1,102 @@
package com.hoho.android.usbserial.driver;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.util.Log;
import java.io.IOException;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class GsmModemSerialDriver implements UsbSerialDriver{
private final String TAG = GsmModemSerialDriver.class.getSimpleName();
private final UsbDevice mDevice;
private final UsbSerialPort mPort;
@Override
public UsbDevice getDevice() {
return mDevice;
}
@Override
public List<UsbSerialPort> getPorts() {
return Collections.singletonList(mPort);
}
public GsmModemSerialDriver(UsbDevice mDevice) {
this.mDevice = mDevice;
mPort = new GsmModemSerialPort(mDevice, 0);
}
public class GsmModemSerialPort extends CommonUsbSerialPort {
private UsbInterface mDataInterface;
public GsmModemSerialPort(UsbDevice device, int portNumber) {
super(device, portNumber);
}
@Override
protected void openInt() throws IOException {
Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount());
mDataInterface = mDevice.getInterface(0);
if (!mConnection.claimInterface(mDataInterface, true)) {
throw new IOException("Could not claim shared control/data interface");
}
Log.d(TAG, "endpoint count=" + mDataInterface.getEndpointCount());
for (int i = 0; i < mDataInterface.getEndpointCount(); ++i) {
UsbEndpoint ep = mDataInterface.getEndpoint(i);
if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) {
mReadEndpoint = ep;
} else if ((ep.getDirection() == UsbConstants.USB_DIR_OUT) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) {
mWriteEndpoint = ep;
}
}
initGsmModem();
}
@Override
protected void closeInt() {
try {
mConnection.releaseInterface(mDataInterface);
} catch(Exception ignored) {}
}
private int initGsmModem() throws IOException {
int len = mConnection.controlTransfer(
0x21, 0x22, 0x01, 0, null, 0, 5000);
if(len < 0) {
throw new IOException("init failed");
}
return len;
}
@Override
public UsbSerialDriver getDriver() {
return GsmModemSerialDriver.this;
}
@Override
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException {
throw new UnsupportedOperationException();
}
}
public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_UNISOC, new int[]{
UsbId.FIBOCOM_L610,
UsbId.FIBOCOM_L612,
});
return supportedDevices;
}
}

View File

@ -6,6 +6,7 @@
package com.hoho.android.usbserial.driver; package com.hoho.android.usbserial.driver;
import android.hardware.usb.UsbDevice;
import android.util.Pair; import android.util.Pair;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
@ -14,14 +15,14 @@ import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
/** /**
* Maps (vendor id, product id) pairs to the corresponding serial driver. * Maps (vendor id, product id) pairs to the corresponding serial driver,
* * or invoke 'probe' method to check actual USB devices for matching interfaces.
* @author mike wakerly (opensource@hoho.com)
*/ */
public class ProbeTable { public class ProbeTable {
private final Map<Pair<Integer, Integer>, Class<? extends UsbSerialDriver>> mProbeTable = private final Map<Pair<Integer, Integer>, Class<? extends UsbSerialDriver>> mVidPidProbeTable =
new LinkedHashMap<Pair<Integer,Integer>, Class<? extends UsbSerialDriver>>(); new LinkedHashMap<>();
private final Map<Method, Class<? extends UsbSerialDriver>> mMethodProbeTable = new LinkedHashMap<>();
/** /**
* Adds or updates a (vendor, product) pair in the table. * Adds or updates a (vendor, product) pair in the table.
@ -33,7 +34,7 @@ public class ProbeTable {
*/ */
public ProbeTable addProduct(int vendorId, int productId, public ProbeTable addProduct(int vendorId, int productId,
Class<? extends UsbSerialDriver> driverClass) { Class<? extends UsbSerialDriver> driverClass) {
mProbeTable.put(Pair.create(vendorId, productId), driverClass); mVidPidProbeTable.put(Pair.create(vendorId, productId), driverClass);
return this; return this;
} }
@ -41,12 +42,11 @@ public class ProbeTable {
* Internal method to add all supported products from * Internal method to add all supported products from
* {@code getSupportedProducts} static method. * {@code getSupportedProducts} static method.
* *
* @param driverClass * @param driverClass to be added
* @return
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
ProbeTable addDriver(Class<? extends UsbSerialDriver> driverClass) { void addDriver(Class<? extends UsbSerialDriver> driverClass) {
final Method method; Method method;
try { try {
method = driverClass.getMethod("getSupportedDevices"); method = driverClass.getMethod("getSupportedDevices");
@ -68,20 +68,35 @@ public class ProbeTable {
} }
} }
return this; try {
method = driverClass.getMethod("probe", UsbDevice.class);
mMethodProbeTable.put(method, driverClass);
} catch (SecurityException | NoSuchMethodException ignored) {
}
} }
/** /**
* Returns the driver for the given (vendor, product) pair, or {@code null} * Returns the driver for the given USB device, or {@code null} if no match.
* if no match.
* *
* @param vendorId the USB vendor id * @param usbDevice the USB device to be probed
* @param productId the USB product id
* @return the driver class matching this pair, or {@code null} * @return the driver class matching this pair, or {@code null}
*/ */
public Class<? extends UsbSerialDriver> findDriver(int vendorId, int productId) { public Class<? extends UsbSerialDriver> findDriver(final UsbDevice usbDevice) {
final Pair<Integer, Integer> pair = Pair.create(vendorId, productId); final Pair<Integer, Integer> pair = Pair.create(usbDevice.getVendorId(), usbDevice.getProductId());
return mProbeTable.get(pair); Class<? extends UsbSerialDriver> driverClass = mVidPidProbeTable.get(pair);
if (driverClass != null)
return driverClass;
for (Map.Entry<Method, Class<? extends UsbSerialDriver>> entry : mMethodProbeTable.entrySet()) {
try {
Method method = entry.getKey();
Object o = method.invoke(null, usbDevice);
if((boolean)o)
return entry.getValue();
} catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
return null;
} }
} }

View File

@ -11,13 +11,14 @@ package com.hoho.android.usbserial.driver;
import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbInterface;
import android.util.Log; import android.util.Log;
import com.hoho.android.usbserial.BuildConfig;
import com.hoho.android.usbserial.util.MonotonicClock;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -28,6 +29,13 @@ public class ProlificSerialDriver implements UsbSerialDriver {
private final String TAG = ProlificSerialDriver.class.getSimpleName(); 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 UsbDevice mDevice;
private final UsbSerialPort mPort; 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 USB_RECIP_INTERFACE = 0x01;
private static final int PROLIFIC_VENDOR_READ_REQUEST = 0x01; private static final int VENDOR_READ_REQUEST = 0x01;
private static final int PROLIFIC_VENDOR_WRITE_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 private static final int VENDOR_OUT_REQTYPE = UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR;
| 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 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 WRITE_ENDPOINT = 0x02; private static final int WRITE_ENDPOINT = 0x02;
private static final int READ_ENDPOINT = 0x83; private static final int READ_ENDPOINT = 0x83;
private static final int INTERRUPT_ENDPOINT = 0x81; 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 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; /* RESET_HXN_REQUEST */
private static final int SET_CONTROL_REQUEST = 0x22; 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_DTR = 0x01;
private static final int CONTROL_RTS = 0x02; 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_CD = 0x01;
private static final int STATUS_FLAG_DSR = 0x02; private static final int STATUS_FLAG_DSR = 0x02;
private static final int STATUS_FLAG_RI = 0x08; 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_BUFFER_SIZE = 10;
private static final int STATUS_BYTE_IDX = 8; private static final int STATUS_BYTE_IDX = 8;
private static final int DEVICE_TYPE_HX = 0; protected DeviceType mDeviceType = DeviceType.DEVICE_TYPE_HX;
private static final int DEVICE_TYPE_0 = 1;
private static final int DEVICE_TYPE_1 = 2;
private int mDeviceType = DEVICE_TYPE_HX;
private UsbEndpoint mInterruptEndpoint; private UsbEndpoint mInterruptEndpoint;
private int mControlLinesValue = 0; private int mControlLinesValue = 0;
private int mBaudRate = -1, mDataBits = -1, mStopBits = -1, mParity = -1; private int mBaudRate = -1, mDataBits = -1, mStopBits = -1, mParity = -1;
private int mStatus = 0; private int mStatus = 0;
private volatile Thread mReadStatusThread = null; private volatile Thread mReadStatusThread = null;
private final Object mReadStatusThreadLock = new Object(); private final Object mReadStatusThreadLock = new Object();
boolean mStopReadStatusThread = false; private boolean mStopReadStatusThread = false;
private IOException mReadStatusException = null; private Exception mReadStatusException = null;
public ProlificSerialPort(UsbDevice device, int portNumber) { public ProlificSerialPort(UsbDevice device, int portNumber) {
@ -114,54 +134,53 @@ public class ProlificSerialDriver implements UsbSerialDriver {
return ProlificSerialDriver.this; return ProlificSerialDriver.this;
} }
private final byte[] inControlTransfer(int requestType, int request, private byte[] inControlTransfer(int requestType, int request, int value, int index, int length) throws IOException {
int value, int index, int length) throws IOException {
byte[] buffer = new byte[length]; byte[] buffer = new byte[length];
int result = mConnection.controlTransfer(requestType, request, value, int result = mConnection.controlTransfer(requestType, request, value, index, buffer, length, USB_READ_TIMEOUT_MILLIS);
index, buffer, length, USB_READ_TIMEOUT_MILLIS);
if (result != length) { if (result != length) {
throw new IOException( throw new IOException(String.format("ControlTransfer %s 0x%x failed: %d",mDeviceType.name(), value, result));
String.format("ControlTransfer with value 0x%x failed: %d",
value, result));
} }
return buffer; return buffer;
} }
private final void outControlTransfer(int requestType, int request, private void outControlTransfer(int requestType, int request, int value, int index, byte[] data) throws IOException {
int value, int index, byte[] data) throws IOException {
int length = (data == null) ? 0 : data.length; int length = (data == null) ? 0 : data.length;
int result = mConnection.controlTransfer(requestType, request, value, int result = mConnection.controlTransfer(requestType, request, value, index, data, length, USB_WRITE_TIMEOUT_MILLIS);
index, data, length, USB_WRITE_TIMEOUT_MILLIS);
if (result != length) { if (result != length) {
throw new IOException( throw new IOException( String.format("ControlTransfer %s 0x%x failed: %d", mDeviceType.name(), value, result));
String.format("ControlTransfer with value 0x%x failed: %d",
value, result));
} }
} }
private final byte[] vendorIn(int value, int index, int length) private byte[] vendorIn(int value, int index, int length) throws IOException {
throws IOException { int request = (mDeviceType == DeviceType.DEVICE_TYPE_HXN) ? VENDOR_READ_HXN_REQUEST : VENDOR_READ_REQUEST;
return inControlTransfer(PROLIFIC_VENDOR_IN_REQTYPE, return inControlTransfer(VENDOR_IN_REQTYPE, request, value, index, length);
PROLIFIC_VENDOR_READ_REQUEST, value, index, length);
} }
private final void vendorOut(int value, int index, byte[] data) private void vendorOut(int value, int index, byte[] data) throws IOException {
throws IOException { int request = (mDeviceType == DeviceType.DEVICE_TYPE_HXN) ? VENDOR_WRITE_HXN_REQUEST : VENDOR_WRITE_REQUEST;
outControlTransfer(PROLIFIC_VENDOR_OUT_REQTYPE, outControlTransfer(VENDOR_OUT_REQTYPE, request, value, index, data);
PROLIFIC_VENDOR_WRITE_REQUEST, value, index, data);
} }
private void resetDevice() throws IOException { private void resetDevice() throws IOException {
purgeHwBuffers(true, true); purgeHwBuffers(true, true);
} }
private final void ctrlOut(int request, int value, int index, byte[] data) private void ctrlOut(int request, int value, int index, byte[] data) throws IOException {
throws IOException { outControlTransfer(CTRL_OUT_REQTYPE, request, value, index, data);
outControlTransfer(PROLIFIC_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 { private void doBlackMagic() throws IOException {
if (mDeviceType == DeviceType.DEVICE_TYPE_HXN)
return;
vendorIn(0x8484, 0, 1); vendorIn(0x8484, 0, 1);
vendorOut(0x0404, 0, null); vendorOut(0x0404, 0, null);
vendorIn(0x8484, 0, 1); vendorIn(0x8484, 0, 1);
@ -172,7 +191,7 @@ public class ProlificSerialDriver implements UsbSerialDriver {
vendorIn(0x8383, 0, 1); vendorIn(0x8383, 0, 1);
vendorOut(0, 1, null); vendorOut(0, 1, null);
vendorOut(1, 0, 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 { private void setControlLines(int newControlLinesValue) throws IOException {
@ -180,76 +199,76 @@ public class ProlificSerialDriver implements UsbSerialDriver {
mControlLinesValue = newControlLinesValue; mControlLinesValue = newControlLinesValue;
} }
private final void readStatusThreadFunction() { private void readStatusThreadFunction() {
try { try {
byte[] buffer = new byte[STATUS_BUFFER_SIZE];
while (!mStopReadStatusThread) { while (!mStopReadStatusThread) {
byte[] buffer = new byte[STATUS_BUFFER_SIZE]; long endTime = MonotonicClock.millis() + 500;
int readBytesCount = mConnection.bulkTransfer(mInterruptEndpoint, int readBytesCount = mConnection.bulkTransfer(mInterruptEndpoint, buffer, STATUS_BUFFER_SIZE, 500);
buffer, if(readBytesCount == -1)
STATUS_BUFFER_SIZE, testConnection(MonotonicClock.millis() < endTime);
500);
if (readBytesCount > 0) { if (readBytesCount > 0) {
if (readBytesCount == STATUS_BUFFER_SIZE) { if (readBytesCount != STATUS_BUFFER_SIZE) {
mStatus = buffer[STATUS_BYTE_IDX] & 0xff; 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 { } else {
throw new IOException( mStatus = buffer[STATUS_BYTE_IDX] & 0xff;
String.format("Invalid CTS / DSR / CD / RI status buffer received, expected %d bytes, but received %d",
STATUS_BUFFER_SIZE,
readBytesCount));
} }
} }
} }
} catch (IOException e) { } catch (Exception e) {
mReadStatusException = e; if (isOpen())
mReadStatusException = e;
} }
//Log.d(TAG, "end control line status thread " + mStopReadStatusThread + " " + (mReadStatusException == null ? "-" : mReadStatusException.getMessage()));
} }
private final int getStatus() throws IOException { private int getStatus() throws IOException {
if ((mReadStatusThread == null) && (mReadStatusException == null)) { if ((mReadStatusThread == null) && (mReadStatusException == null)) {
synchronized (mReadStatusThreadLock) { synchronized (mReadStatusThreadLock) {
if (mReadStatusThread == null) { if (mReadStatusThread == null) {
byte[] buffer = new byte[STATUS_BUFFER_SIZE]; mStatus = 0;
int readBytes = mConnection.bulkTransfer(mInterruptEndpoint, if(mDeviceType == DeviceType.DEVICE_TYPE_HXN) {
buffer, byte[] data = vendorIn(GET_CONTROL_HXN_REQUEST, 0, 1);
STATUS_BUFFER_SIZE, if ((data[0] & GET_CONTROL_HXN_FLAG_CTS) == 0) mStatus |= STATUS_FLAG_CTS;
100); if ((data[0] & GET_CONTROL_HXN_FLAG_DSR) == 0) mStatus |= STATUS_FLAG_DSR;
if (readBytes != STATUS_BUFFER_SIZE) { if ((data[0] & GET_CONTROL_HXN_FLAG_CD) == 0) mStatus |= STATUS_FLAG_CD;
Log.w(TAG, "Could not read initial CTS / DSR / CD / RI status"); if ((data[0] & GET_CONTROL_HXN_FLAG_RI) == 0) mStatus |= STATUS_FLAG_RI;
} else { } 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;
} }
//Log.d(TAG, "start control line status thread " + mStatus);
mReadStatusThread = new Thread(new Runnable() { mReadStatusThread = new Thread(this::readStatusThreadFunction);
@Override
public void run() {
readStatusThreadFunction();
}
});
mReadStatusThread.setDaemon(true); mReadStatusThread.setDaemon(true);
mReadStatusThread.start(); mReadStatusThread.start();
} }
} }
} }
/* throw and clear an exception which occured in the status read thread */ /* throw and clear an exception which occurred in the status read thread */
IOException readStatusException = mReadStatusException; Exception readStatusException = mReadStatusException;
if (mReadStatusException != null) { if (mReadStatusException != null) {
mReadStatusException = null; mReadStatusException = null;
throw readStatusException; throw new IOException(readStatusException);
} }
return mStatus; return mStatus;
} }
private final boolean testStatusFlag(int flag) throws IOException { private boolean testStatusFlag(int flag) throws IOException {
return ((getStatus() & flag) == flag); return ((getStatus() & flag) == flag);
} }
@Override @Override
public void openInt(UsbDeviceConnection connection) throws IOException { public void openInt() throws IOException {
UsbInterface usbInterface = mDevice.getInterface(0); UsbInterface usbInterface = mDevice.getInterface(0);
if (!connection.claimInterface(usbInterface, true)) { if (!mConnection.claimInterface(usbInterface, true)) {
throw new IOException("Error claiming Prolific interface 0"); throw new IOException("Error claiming Prolific interface 0");
} }
@ -271,51 +290,48 @@ public class ProlificSerialDriver implements UsbSerialDriver {
} }
} }
if (mDevice.getDeviceClass() == 0x02) { byte[] rawDescriptors = mConnection.getRawDescriptors();
mDeviceType = DEVICE_TYPE_0; if(rawDescriptors == null || rawDescriptors.length < 14) {
} else { throw new IOException("Could not get device descriptors");
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);
}
} }
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(); resetDevice();
doBlackMagic(); doBlackMagic();
setControlLines(mControlLinesValue);
setFlowControl(mFlowControl);
} }
@Override @Override
public void closeInt() { public void closeInt() {
try { try {
mStopReadStatusThread = true;
synchronized (mReadStatusThreadLock) { synchronized (mReadStatusThreadLock) {
if (mReadStatusThread != null) { if (mReadStatusThread != null) {
try { try {
mStopReadStatusThread = true;
mReadStatusThread.join(); mReadStatusThread.join();
} catch (Exception e) { } catch (Exception e) {
Log.w(TAG, "An error occured while waiting for status read thread", e); Log.w(TAG, "An error occured while waiting for status read thread", e);
} }
mStopReadStatusThread = false;
mReadStatusThread = null;
mReadStatusException = null;
} }
} }
resetDevice(); resetDevice();
@ -325,8 +341,79 @@ public class ProlificSerialDriver implements UsbSerialDriver {
} catch(Exception ignored) {} } 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 @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) if ((mBaudRate == baudRate) && (mDataBits == dataBits)
&& (mStopBits == stopBits) && (mParity == parity)) { && (mStopBits == stopBits) && (mParity == parity)) {
// Make sure no action is performed if there is nothing to change // 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]; byte[] lineRequestData = new byte[7];
if(baudRate <= 0) {
throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
}
lineRequestData[0] = (byte) (baudRate & 0xff); lineRequestData[0] = (byte) (baudRate & 0xff);
lineRequestData[1] = (byte) ((baudRate >> 8) & 0xff); lineRequestData[1] = (byte) ((baudRate >> 8) & 0xff);
lineRequestData[2] = (byte) ((baudRate >> 16) & 0xff); lineRequestData[2] = (byte) ((baudRate >> 16) & 0xff);
@ -444,7 +527,6 @@ public class ProlificSerialDriver implements UsbSerialDriver {
setControlLines(newControlLinesValue); setControlLines(newControlLinesValue);
} }
@Override @Override
public EnumSet<ControlLine> getControlLines() throws IOException { public EnumSet<ControlLine> getControlLines() throws IOException {
int status = getStatus(); int status = getStatus();
@ -464,21 +546,73 @@ public class ProlificSerialDriver implements UsbSerialDriver {
} }
@Override @Override
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { public void setFlowControl(FlowControl flowControl) throws IOException {
if (purgeWriteBuffers) { // vendorOut values from https://www.mail-archive.com/linux-usb@vger.kernel.org/msg110968.html
vendorOut(FLUSH_RX_REQUEST, 0, null); 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) { @Override
vendorOut(FLUSH_TX_REQUEST, 0, null); 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() { 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, 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; 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

@ -21,44 +21,33 @@ public final class UsbId {
public static final int FTDI_FT2232H = 0x6010; public static final int FTDI_FT2232H = 0x6010;
public static final int FTDI_FT4232H = 0x6011; public static final int FTDI_FT4232H = 0x6011;
public static final int FTDI_FT232H = 0x6014; public static final int FTDI_FT232H = 0x6014;
public static final int FTDI_FT231X = 0x6015; // same ID for FT230X, FT231X, FT234XD
public static final int VENDOR_ATMEL = 0x03EB;
public static final int ATMEL_LUFA_CDC_DEMO_APP = 0x2044;
public static final int VENDOR_ARDUINO = 0x2341;
public static final int ARDUINO_UNO = 0x0001;
public static final int ARDUINO_MEGA_2560 = 0x0010;
public static final int ARDUINO_SERIAL_ADAPTER = 0x003b;
public static final int ARDUINO_MEGA_ADK = 0x003f;
public static final int ARDUINO_MEGA_2560_R3 = 0x0042;
public static final int ARDUINO_UNO_R3 = 0x0043;
public static final int ARDUINO_MEGA_ADK_R3 = 0x0044;
public static final int ARDUINO_SERIAL_ADAPTER_R3 = 0x0044;
public static final int ARDUINO_LEONARDO = 0x8036;
public static final int ARDUINO_MICRO = 0x8037;
public static final int VENDOR_VAN_OOIJEN_TECH = 0x16c0;
public static final int VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL = 0x0483;
public static final int VENDOR_LEAFLABS = 0x1eaf;
public static final int LEAFLABS_MAPLE = 0x0004;
public static final int VENDOR_SILABS = 0x10c4; public static final int VENDOR_SILABS = 0x10c4;
public static final int SILABS_CP2102 = 0xea60; public static final int SILABS_CP2102 = 0xea60; // same ID for CP2101, CP2103, CP2104, CP2109
public static final int SILABS_CP2105 = 0xea70; public static final int SILABS_CP2105 = 0xea70;
public static final int SILABS_CP2108 = 0xea71; 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 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 VENDOR_QINHENG = 0x1a86;
public static final int QINHENG_CH340 = 0x7523; public static final int QINHENG_CH340 = 0x7523;
public static final int QINHENG_CH341A = 0x5523; public static final int QINHENG_CH341A = 0x5523;
// at www.linux-usb.org/usb.ids listed for NXP/LPC1768, but all processors supported by ARM mbed DAPLink firmware report these ids public static final int VENDOR_UNISOC = 0x1782;
public static final int VENDOR_ARM = 0x0d28; public static final int FIBOCOM_L610 = 0x4D10;
public static final int ARM_MBED = 0x0204; public static final int FIBOCOM_L612 = 0x4D12;
private UsbId() { private UsbId() {
throw new IllegalAccessError("Non-instantiable class"); throw new IllegalAccessError("Non-instantiable class");

View File

@ -10,18 +10,23 @@ import android.hardware.usb.UsbDevice;
import java.util.List; import java.util.List;
/**
*
* @author mike wakerly (opensource@hoho.com)
*/
public interface UsbSerialDriver { public interface UsbSerialDriver {
/*
* Additional interface properties. Invoked thru reflection.
*
UsbSerialDriver(UsbDevice device); // constructor with device
static Map<Integer, int[]> getSupportedDevices();
static boolean probe(UsbDevice device); // optional
*/
/** /**
* Returns the raw {@link UsbDevice} backing this port. * Returns the raw {@link UsbDevice} backing this port.
* *
* @return the device * @return the device
*/ */
public UsbDevice getDevice(); UsbDevice getDevice();
/** /**
* Returns all available ports for this device. This list must have at least * Returns all available ports for this device. This list must have at least
@ -29,5 +34,5 @@ public interface UsbSerialDriver {
* *
* @return the ports * @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.UsbDevice;
import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbManager; import android.hardware.usb.UsbManager;
import androidx.annotation.IntDef;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.EnumSet; import java.util.EnumSet;
/** /**
@ -22,81 +27,82 @@ import java.util.EnumSet;
public interface UsbSerialPort extends Closeable { public interface UsbSerialPort extends Closeable {
/** 5 data bits. */ /** 5 data bits. */
public static final int DATABITS_5 = 5; int DATABITS_5 = 5;
/** 6 data bits. */ /** 6 data bits. */
public static final int DATABITS_6 = 6; int DATABITS_6 = 6;
/** 7 data bits. */ /** 7 data bits. */
public static final int DATABITS_7 = 7; int DATABITS_7 = 7;
/** 8 data bits. */ /** 8 data bits. */
public static final int DATABITS_8 = 8; 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;
/** Values for setParameters(..., parity) */
@Retention(RetentionPolicy.SOURCE)
@IntDef({PARITY_NONE, PARITY_ODD, PARITY_EVEN, PARITY_MARK, PARITY_SPACE})
@interface Parity {}
/** No parity. */ /** No parity. */
public static final int PARITY_NONE = 0; int PARITY_NONE = 0;
/** Odd parity. */ /** Odd parity. */
public static final int PARITY_ODD = 1; int PARITY_ODD = 1;
/** Even parity. */ /** Even parity. */
public static final int PARITY_EVEN = 2; int PARITY_EVEN = 2;
/** Mark parity. */ /** Mark parity. */
public static final int PARITY_MARK = 3; int PARITY_MARK = 3;
/** Space parity. */ /** Space parity. */
public static final int PARITY_SPACE = 4; int PARITY_SPACE = 4;
/** 1 stop bit. */ /** 1 stop bit. */
public static final int STOPBITS_1 = 1; int STOPBITS_1 = 1;
/** 1.5 stop bits. */ /** 1.5 stop bits. */
public static final int STOPBITS_1_5 = 3; int STOPBITS_1_5 = 3;
/** 2 stop bits. */ /** 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. * Returns the driver used by this port.
*/ */
public UsbSerialDriver getDriver(); UsbSerialDriver getDriver();
/** /**
* Returns the currently-bound USB device. * Returns the currently-bound USB device.
*/ */
public UsbDevice getDevice(); UsbDevice getDevice();
/** /**
* Port number within driver. * 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}. * The serial number of the underlying UsbDeviceConnection, or {@code null}.
* *
* @return value from {@link UsbDeviceConnection#getSerial()} * @return value from {@link UsbDeviceConnection#getSerial()}
* @throws SecurityException starting with target SDK 29 (Android 10) if permission for USB device is not granted * @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 * 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)} * {@link UsbManager#openDevice(android.hardware.usb.UsbDevice)}
* @throws IOException on error opening or initializing the port. * @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} * Closes the port and {@link UsbDeviceConnection}
* *
* @throws IOException on error closing the port. * @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. * 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 * @return the actual number of bytes read
* @throws IOException if an error occurred during reading * @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. * Writes as many bytes as possible from the source buffer.
* *
* @param src the source byte buffer * @param src the source byte buffer
* @param timeout the timeout for writing in milliseconds, 0 is infinite * @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 * @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. * Sets various serial port parameters.
@ -145,9 +175,9 @@ public interface UsbSerialPort extends Closeable {
* @param parity one of {@link #PARITY_NONE}, {@link #PARITY_ODD}, * @param parity one of {@link #PARITY_NONE}, {@link #PARITY_ODD},
* {@link #PARITY_EVEN}, {@link #PARITY_MARK}, or {@link #PARITY_SPACE}. * {@link #PARITY_EVEN}, {@link #PARITY_MARK}, or {@link #PARITY_SPACE}.
* @throws IOException on error setting the port parameters * @throws IOException on error setting the port parameters
* @throws UnsupportedOperationException if values are not supported by a specific device * @throws UnsupportedOperationException if not supported or values are not supported by a specific device
*/ */
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. * 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 IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported * @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. * 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 IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported * @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. * 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 IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported * @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. * 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 IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported * @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. * 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 IOException if an error occurred during writing
* @throws UnsupportedOperationException if not supported * @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. * 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 IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported * @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. * 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 IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported * @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. * 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 IOException if an error occurred during writing
* @throws UnsupportedOperationException if not supported * @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. * 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} * @return EnumSet.contains(...) is {@code true} if set, else {@code false}
* @throws IOException if an error occurred during reading * @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported
*/ */
public EnumSet<ControlLine> getControlLines() throws IOException; EnumSet<ControlLine> getControlLines() throws IOException;
/** /**
* Gets all control line supported flags. * 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} * @return EnumSet.contains(...) is {@code true} if supported, else {@code false}
* @throws IOException if an error occurred during reading * @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. * 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 IOException if an error occurred during flush
* @throws UnsupportedOperationException if not supported * @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. * 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(FtdiSerialDriver.class);
probeTable.addDriver(ProlificSerialDriver.class); probeTable.addDriver(ProlificSerialDriver.class);
probeTable.addDriver(Ch34xSerialDriver.class); probeTable.addDriver(Ch34xSerialDriver.class);
probeTable.addDriver(GsmModemSerialDriver.class);
probeTable.addDriver(ChromeCcdSerialDriver.class);
return probeTable; return probeTable;
} }
@ -46,11 +48,11 @@ public class UsbSerialProber {
* not require permission from the Android USB system, since it does not * not require permission from the Android USB system, since it does not
* open any of the devices. * open any of the devices.
* *
* @param usbManager * @param usbManager usb manager
* @return a list, possibly empty, of all compatible drivers * @return a list, possibly empty, of all compatible drivers
*/ */
public List<UsbSerialDriver> findAllDrivers(final UsbManager usbManager) { 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()) { for (final UsbDevice usbDevice : usbManager.getDeviceList().values()) {
final UsbSerialDriver driver = probeDevice(usbDevice); final UsbSerialDriver driver = probeDevice(usbDevice);
@ -69,11 +71,7 @@ public class UsbSerialProber {
* {@code null} if none available. * {@code null} if none available.
*/ */
public UsbSerialDriver probeDevice(final UsbDevice usbDevice) { public UsbSerialDriver probeDevice(final UsbDevice usbDevice) {
final int vendorId = usbDevice.getVendorId(); final Class<? extends UsbSerialDriver> driverClass = mProbeTable.findDriver(usbDevice);
final int productId = usbDevice.getProductId();
final Class<? extends UsbSerialDriver> driverClass =
mProbeTable.findDriver(vendorId, productId);
if (driverClass != null) { if (driverClass != null) {
final UsbSerialDriver driver; final UsbSerialDriver driver;
try { try {

View File

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

View File

@ -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; package com.hoho.android.usbserial.util;
import android.os.Process;
import android.util.Log; import android.util.Log;
import com.hoho.android.usbserial.driver.UsbSerialPort; import com.hoho.android.usbserial.driver.UsbSerialPort;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
/** /**
* Utility class which services a {@link UsbSerialPort} in its {@link #run()} method. * Utility class which services a {@link UsbSerialPort} in its {@link #runWrite()} ()} and {@link #runRead()} ()} ()} methods.
* *
* @author mike wakerly (opensource@hoho.com) * @author mike wakerly (opensource@hoho.com)
*/ */
public class SerialInputOutputManager implements Runnable { public class SerialInputOutputManager {
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 enum State { public enum State {
STOPPED, STOPPED,
STARTING,
RUNNING, RUNNING,
STOPPING 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 Listener mListener; // Synchronized by 'this'
private final UsbSerialPort mSerialPort; private final UsbSerialPort mSerialPort;
@ -47,21 +54,23 @@ public class SerialInputOutputManager implements Runnable {
/** /**
* Called when new incoming data is available. * 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) { public SerialInputOutputManager(UsbSerialPort serialPort) {
mSerialPort = serialPort; mSerialPort = serialPort;
mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize());
} }
public SerialInputOutputManager(UsbSerialPort serialPort, Listener listener) { public SerialInputOutputManager(UsbSerialPort serialPort, Listener listener) {
mSerialPort = serialPort; mSerialPort = serialPort;
mListener = listener; mListener = listener;
mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize());
} }
public synchronized void setListener(Listener listener) { public synchronized void setListener(Listener listener) {
@ -72,10 +81,25 @@ public class SerialInputOutputManager implements Runnable {
return mListener; 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) { public void setReadTimeout(int timeout) {
// when set if already running, read already blocks and the new value will not become effective now // when set if already running, read already blocks and the new value will not become effective now
if(mReadTimeout == 0 && timeout != 0 && mState != State.STOPPED) if(mReadTimeout == 0 && timeout != 0 && mState.get() != State.STOPPED)
throw new IllegalStateException("Set readTimeout before SerialInputOutputManager is started"); throw new IllegalStateException("readTimeout only configurable before SerialInputOutputManager is started");
mReadTimeout = timeout; mReadTimeout = timeout;
} }
@ -91,93 +115,222 @@ public class SerialInputOutputManager implements Runnable {
return mWriteTimeout; return mWriteTimeout;
} }
/* /**
* when writeAsync is used, it is recommended to use readTimeout != 0, * read/write buffer size
* else the write will be delayed until read data is available
*/ */
public void writeAsync(byte[] data) { public void setReadBufferSize(int bufferSize) {
synchronized (mWriteBuffer) { if (getReadBufferSize() == bufferSize)
mWriteBuffer.put(data); return;
synchronized (mReadBufferLock) {
mReadBuffer = ByteBuffer.allocate(bufferSize);
} }
} }
public synchronized void stop() { public int getReadBufferSize() {
if (getState() == State.RUNNING) { return mReadBuffer.capacity();
Log.i(TAG, "Stop requested"); }
mState = State.STOPPING;
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() { public int getWriteBufferSize() {
return mState; return mWriteBuffer.capacity();
} }
/** /**
* Continuously services the read and write buffers until {@link #stop()} is * write data asynchronously
* called, or until a driver exception is raised.
*/ */
@Override public void writeAsync(byte[] data) {
public void run() { synchronized (mWriteBufferLock) {
synchronized (this) { mWriteBuffer.put(data);
if (getState() != State.STOPPED) { mWriteBufferLock.notifyAll(); // Notify waiting threads
throw new IllegalStateException("Already running");
}
mState = State.RUNNING;
} }
}
Log.i(TAG, "Running ..."); /**
try { * start SerialInputOutputManager in separate threads
while (true) { */
if (getState() != State.RUNNING) { public void start() {
Log.i(TAG, "Stopping mState=" + getState()); if(mState.compareAndSet(State.STOPPED, State.STARTING)) {
break; mStartuplatch = new CountDownLatch(2);
} new Thread(this::runRead, this.getClass().getSimpleName() + "_read").start();
step(); 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) { } else {
Log.w(TAG, "Run ending due to exception: " + e.getMessage(), e); throw new IllegalStateException("already started");
final Listener listener = getListener(); }
if (listener != null) { }
listener.onRunError(e);
/**
* 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 { Log.i(TAG, "Stop requested");
synchronized (this) { }
mState = State.STOPPED; }
Log.i(TAG, "Stopped");
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. // 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 (len > 0) {
if (DEBUG) Log.d(TAG, "Read data len=" + len); if (DEBUG) {
Log.d(TAG, "Read data len=" + len);
}
final Listener listener = getListener(); final Listener listener = getListener();
if (listener != null) { if (listener != null) {
final byte[] data = new byte[len]; final byte[] data = new byte[len];
mReadBuffer.get(data, 0, len); System.arraycopy(buffer, 0, data, 0, len);
listener.onNewData(data); listener.onNewData(data);
} }
mReadBuffer.clear();
} }
}
private void stepWrite() throws IOException, InterruptedException {
// Handle outgoing data. // Handle outgoing data.
byte[] outBuff = null; byte[] buffer = null;
synchronized (mWriteBuffer) { synchronized (mWriteBufferLock) {
len = mWriteBuffer.position(); int len = mWriteBuffer.position();
if (len > 0) { if (len > 0) {
outBuff = new byte[len]; buffer = new byte[len];
mWriteBuffer.rewind(); mWriteBuffer.rewind();
mWriteBuffer.get(outBuff, 0, len); mWriteBuffer.get(buffer, 0, len);
mWriteBuffer.clear(); mWriteBuffer.clear();
mWriteBufferLock.notifyAll(); // Notify writeAsync that there is space in the buffer
} else {
mWriteBufferLock.wait();
} }
} }
if (outBuff != null) { if (buffer != null) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "Writing data len=" + len); Log.d(TAG, "Writing data len=" + buffer.length);
} }
mSerialPort.write(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());
}
}