1
0
mirror of https://github.com/mik3y/usb-serial-for-android synced 2025-06-29 10:46:28 +00:00

Compare commits

...

194 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
kai-morich
57d10a08dd
Merge pull request #297 from mik3y/v3
ftdi rewrite, MIT license, ...
2020-08-01 10:21:54 +02:00
kai-morich
a664082f23 throw UnsupportedOperationException instead of returning false 2020-08-01 10:06:51 +02:00
kai-morich
954295456c rebase new FTDI baudrate test 2020-07-30 17:52:18 +02:00
kai-morich
2d13b90f59 move from LGPL to MIT license (#244)
moving away from LGPL possible, as the FTDI driver is rewritten and not based any more on LGPL code from libftdi.
2020-07-29 11:03:26 +02:00
kai-morich
e496195bd9 rewrite ftdi driver without LGPL code from libftdi 2020-07-27 17:39:01 +02:00
kai-morich
e0ed25b85f CH341A readme update 2020-07-27 17:38:16 +02:00
kai-morich
963729924b mention close of UsbDeviceConnection in javadoc comment 2020-07-21 08:20:22 +02:00
kai-morich
ce73857825 slightly more tests 2020-07-19 19:47:13 +02:00
kai-morich
a2f0097092 improve control line example 2020-07-19 19:16:05 +02:00
kai-morich
8eaf3f5c5f tests UsbDeviceConnection close behavior
and extract test utilities
2020-07-18 20:48:27 +02:00
kai-morich
a1e58b9843 implement CP21xx input control lines
open() CP21xx without RTS, DTR set
2020-07-04 15:31:58 +02:00
kai-morich
7423fd9d79 new getControlLines() and getSupportedControLines() methods
getControlLines() requires less USB calls than calling getRTS() + ... + getRI() individually.
getSupportedControlLines() tells you, which control lines are supported by a driver. Previously you had to check the driver implementation.
2020-06-30 18:10:02 +02:00
kai-morich
13df128226 implement CH34x input control lines 2020-06-27 11:53:12 +02:00
kai-morich
06d1041738 added CH341A support 2020-06-27 08:46:23 +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
kai-morich
3e7fd9a748 add vectorDrawables.useSupportLibrary=true to sample app 2020-06-11 13:15:00 +02:00
kai-morich
735fa3d70f make all SerialPort classes public
and test FtdiSerialPort methods
2020-06-10 19:09:34 +02:00
kai-morich
33149b66fd
Merge pull request #281 from bensadiku/master
Expose a isOpen method to check on the current state of the connection
2020-05-19 21:10:08 +02:00
Behxhet Sadiku
37324c6b0f Expose isOpen on CommonUsbSerialPort 2020-05-19 20:50:42 +02:00
kai-morich
ca78840144 example app with event-based or direct read 2020-04-08 22:21:26 +02:00
kai-morich
2354f93354 modernize example app 2020-03-29 16:56:26 +02:00
kai-morich
cffe54e15c test control lines 2020-03-21 18:01:05 +01:00
Kai Morich
5096d6940c relax CH340 initialization
controlIn(0x95, 0x0706) result includes control line values
2020-03-08 16:06:44 +01:00
kai-morich
2cad1fd5a2 fix CP2102N initialization
do not send SET_BAUDDIV command. It fails on CP2102N and CP2102/5 work without
2020-03-01 13:54:35 +01:00
kai-morich
17c3d40605 align device_filter.xml with devices recognized by DefaultProber 2020-02-29 19:15:14 +01:00
kai-morich
2a77ebf8b9 fix ch340 initialization 2020-02-03 20:36:56 +01:00
kai-morich
bbed92eafb support multi-port CDC devices 2020-01-10 08:02:56 +01:00
kai-morich
ce97a3408b reuse UsbRequest
less LogCat output
2019-12-13 21:13:48 +01:00
kai-morich
7b578918b0 support FTDI control lines for multi-port devices 2019-11-23 13:24:28 +01:00
uholeschak
6b32e25e9c Fixed modem status
Signed-off-by: kai-morich <mail@kai-morich.de>
2019-11-23 13:23:10 +01:00
uholeschak
21e96594d2 Added missing functions: - latency timer - modem lines
Signed-off-by: kai-morich <mail@kai-morich.de>
2019-11-23 13:22:42 +01:00
kai-morich
a954db1b94 readme with sync + async read 2019-11-16 00:09:41 +01:00
kai-morich
6d3ed12ca8
Create build.yml 2019-11-15 23:57:39 +01:00
kai-morich
e2e9df8463 reimplement read timeout 2019-11-15 21:45:22 +01:00
kai-morich
669ab48e0f resolve merge issue 2019-11-15 07:51:03 +01:00
kai-morich
8c559ef892
Merge pull request #240 from tva-TIS/master
DTR and RTS support for CP21xx drivers
2019-11-14 19:40:45 +01:00
Tim Vahlbrock
89ad5be9c3 Improved DTR and RTS support. DTR and RTS can now be queried after transmission. Added usage of existing methods and new constants. 2019-11-14 14:44:39 +01:00
Tim Vahlbrock
92b16a8c24 Merge branch 'master' of https://github.com/mik3y/usb-serial-for-android 2019-11-14 14:15:39 +01:00
kai-morich
fd2055791a added proguard rules
now they are part of the .aar library, before you had to add them to each release app
2019-11-13 18:49:27 +01:00
kai-morich
24187b3af6 refactor duplicated code in close method 2019-11-09 22:48:00 +01:00
kai-morich
5767298636 refactor duplicated read/write methods 2019-11-09 20:26:50 +01:00
kai-morich
e1b62cf675 write + purge tests, remove unused read buffer code 2019-11-03 19:34:14 +01:00
kai-morich
5c6748e1b8 improve setParameter() error handling
harmonize exception messages, more UI friendly messages
distinguish IllegalArgumentException and UnsupportedOperationException
2019-11-02 13:49:08 +01:00
kai-morich
18b5b6e648 unify open() error handling, more tests, minor cleanup 2019-11-02 13:09:15 +01:00
kai-morich
6869eff88a revert previous usbRequest.cancel() removal
Combine usbRequest.cancel() and releaseInterface to interrupt read() and terminate SerialInputOutputManager.
UsbRequest.cancel() immediately interrupts read() on newer Android versions.
With releaseInterface() only, some hickup are observed on fast reconnect.
Keep releaseInterface() as only this interrupts read() on older Androids.
2019-11-02 12:46:45 +01:00
Tim Vahlbrock
7eaea45068 Added DTR and RTS support for cp21xx driver 2019-10-28 13:20:34 +01:00
TVa[TIS]
e20b3cc913
Merge pull request #1 from mik3y/master
Update from upstream
2019-10-28 13:16:12 +01:00
kai-morich
9ea936b14a improve close handling
Use releaseInterface to interrupt read() and terminate SerialInputOutputManager. Previously some drivers used usbRequest.cancel() but this does not interrupt read() on older Android.

Added connection check to read(). Before Android 8.0 request.initialize() did not check usbConnection, which can lead to native crash if NULL
2019-10-27 21:32:38 +01:00
Kai Morich
b3631dff58 README with SerialInputOutputManager for read() and port for write()
test the usually not used read/write variants
2019-10-26 21:32:28 +02:00
Kai Morich
800381e370 CdcAcm driver: cancel read() on close() 2019-10-26 20:37:06 +02:00
Kai Morich
fac8c9f340 test nonstandard baud rates 2019-10-26 15:40:40 +02:00
Kai Morich
f7399c2aad more coverage tests
purgeHwBuffers: adjust parameter names to match read/write methods and actual behavior
2019-10-25 22:23:00 +02:00
Kai Morich
37059b1a27 reduce non covered code
move HexDump class from library to example
remove unused UsbSerialRuntimeException
2019-10-21 21:04:58 +02:00
Kai Morich
ac1fe40793 manage USB permission intent 2019-10-21 20:56:13 +02:00
kai-morich
6a50595274
Merge pull request #231 from mik3y/coverage
show code coverage
2019-10-20 22:52:58 +02:00
Kai Morich
54a3db115f show code coverage 2019-10-20 22:41:55 +02:00
99 changed files with 10120 additions and 4047 deletions

17
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,17 @@
name: build
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
- name: Build with Gradle
run: ./gradlew assembleDebug

4
.gitignore vendored
View File

@ -21,6 +21,7 @@ build/
.idea/workspace.xml .idea/workspace.xml
.idea/tasks.xml .idea/tasks.xml
.idea/dictionaries .idea/dictionaries
.idea/markdown-navigator*.xml
# Sensitive or high-churn files: # Sensitive or high-churn files:
.idea/dataSources.ids .idea/dataSources.ids
@ -32,10 +33,11 @@ build/
# Gradle: # Gradle:
.idea/gradle.xml .idea/gradle.xml
.idea/libraries .idea/libraries
.idea/jarRepositories.xml
# Eclipse/Android/Misc # Eclipse/Android/Misc
.metadata/ .metadata/
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>

33
.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="7"> <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" />
@ -13,23 +14,45 @@
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" /> <item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" /> <item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
<item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" /> <item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
<item index="7" class="java.lang.String" itemvalue="android.annotation.Nullable" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
<item index="10" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
<item index="11" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
<item index="12" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.Nullable" />
<item index="13" class="java.lang.String" itemvalue="io.reactivex.annotations.Nullable" />
<item index="14" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.Nullable" />
<item index="15" class="java.lang.String" itemvalue="org.jspecify.nullness.Nullable" />
<item index="16" class="java.lang.String" itemvalue="jakarta.annotation.Nullable" />
<item index="17" class="java.lang.String" itemvalue="org.jspecify.annotations.Nullable" />
</list> </list>
</value> </value>
</option> </option>
<option name="myNotNulls"> <option name="myNotNulls">
<value> <value>
<list size="6"> <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" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" /> <item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
<item index="4" class="java.lang.String" itemvalue="androidx.annotation.NonNull" /> <item index="4" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" /> <item index="5" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
<item index="6" class="java.lang.String" itemvalue="android.annotation.NonNull" />
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
<item index="10" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
<item index="11" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.NonNull" />
<item index="12" class="java.lang.String" itemvalue="io.reactivex.annotations.NonNull" />
<item index="13" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.NonNull" />
<item index="14" class="java.lang.String" itemvalue="jakarta.annotation.Nonnull" />
<item index="15" class="java.lang.String" itemvalue="org.jspecify.nullness.NonNull" />
<item index="16" class="java.lang.String" itemvalue="org.jspecify.annotations.NonNull" />
</list> </list>
</value> </value>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" 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" />
<module fileurl="file://$PROJECT_DIR$/usbSerialExamples/usbSerialExamples.iml" filepath="$PROJECT_DIR$/usbSerialExamples/usbSerialExamples.iml" />
<module fileurl="file://$PROJECT_DIR$/usbSerialForAndroid/usbSerialForAndroid.iml" filepath="$PROJECT_DIR$/usbSerialForAndroid/usbSerialForAndroid.iml" />
</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,456 +1,22 @@
GNU LESSER GENERAL PUBLIC LICENSE MIT License
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc. Copyright (c) 2011-2013 Google Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Copyright (c) 2013 Mike Wakerly
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts Permission is hereby granted, free of charge, to any person obtaining a copy
as the successor of the GNU Library Public License, version 2, hence of this software and associated documentation files (the "Software"), to deal
the version number 2.1.] in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Preamble The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The licenses for most software are designed to take away your THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
freedom to share and change it. By contrast, the GNU General Public IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
Licenses are intended to guarantee your freedom to share and change FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
free software--to make sure the software is free for all its users. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
This license, the Lesser General Public License, applies to some OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
specially designated software packages--typically libraries--of the SOFTWARE.
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.

129
README.md
View File

@ -1,5 +1,7 @@
[![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)
# usb-serial-for-android # usb-serial-for-android
@ -9,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
@ -25,16 +26,32 @@ allprojects {
} }
} }
``` ```
Add library to dependencies
Starting with gradle 6.8 you can alternatively add jitpack.io repository to your settings.gradle:
```gradle ```gradle
dependencies { dependencyResolutionManagement {
implementation 'com.github.mik3y:usb-serial-for-android:Tag' repositories {
...
maven { url 'https://jitpack.io' }
}
} }
``` ```
**2.** Copy [device_filter.xml](https://github.com/mik3y/usb-serial-for-android/blob/master/usbSerialExamples/src/main/res/xml/device_filter.xml) to your project's `res/xml/` directory. If using gradle kotlin use line
```gradle.kts
maven(url = "https://jitpack.io")
```
**3.** Configure your `AndroidManifest.xml` to notify your app when a device is attached (see [Android USB Host documentation](http://developer.android.com/guide/topics/connectivity/usb/host.html#discovering-d) for help). Add library to dependencies
```gradle
dependencies {
implementation 'com.github.mik3y:usb-serial-for-android:3.9.0'
}
```
**2.** If the app should be notified when a device is attached, add
[device_filter.xml](https://github.com/mik3y/usb-serial-for-android/blob/master/usbSerialExamples/src/main/res/xml/device_filter.xml)
to your project's `res/xml/` directory and configure in your `AndroidManifest.xml`.
```xml ```xml
<activity <activity
@ -49,8 +66,9 @@ dependencies {
</activity> </activity>
``` ```
**4.** Use it! Example code snippet: **3.** Use it! Example code snippet:
open device:
```java ```java
// Find all available drivers from attached devices. // Find all available drivers from attached devices.
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE); UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
@ -63,35 +81,44 @@ if (availableDrivers.isEmpty()) {
UsbSerialDriver driver = availableDrivers.get(0); UsbSerialDriver driver = availableDrivers.get(0);
UsbDeviceConnection connection = manager.openDevice(driver.getDevice()); UsbDeviceConnection connection = manager.openDevice(driver.getDevice());
if (connection == null) { if (connection == null) {
// You probably need to call UsbManager.requestPermission(driver.getDevice(), ..) // add UsbManager.requestPermission(driver.getDevice(), ..) handling here
return; return;
} }
// Read some data! Most have just one port (port 0). UsbSerialPort port = driver.getPorts().get(0); // Most devices have just one port (port 0)
UsbSerialPort port = driver.getPorts().get(0);
try {
port.open(connection); port.open(connection);
port.setParameters(115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE); port.setParameters(115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);
```
then use direct read/write
```java
port.write(request, WRITE_WAIT_MILLIS);
len = port.read(response, READ_WAIT_MILLIS);
```
or direct write + event driven read:
```java
usbIoManager = new SerialInputOutputManager(usbSerialPort, this);
usbIoManager.start();
...
port.write("hello".getBytes(), WRITE_WAIT_MILLIS);
byte buffer[] = new byte[16]; @Override
int numBytesRead = port.read(buffer, 1000); public void onNewData(byte[] data) {
Log.d(TAG, "Read " + numBytesRead + " bytes."); runOnUiThread(() -> { textView.append(new String(data)); });
} catch (IOException e) {
// Deal with error.
} finally {
port.close();
} }
``` ```
and finally:
```java
port.close();
```
For a simple example, see the For a simple example, see
[UsbSerialExamples project](https://github.com/mik3y/usb-serial-for-android/blob/master/usbSerialExamples) [UsbSerialExamples](https://github.com/mik3y/usb-serial-for-android/blob/master/usbSerialExamples)
in git, which is a simple application for reading and showing serial data. folder in this project.
For a more complete example, see separate github project See separate github project [SimpleUsbTerminal](https://github.com/kai-morich/SimpleUsbTerminal)
[SimpleUsbTerminal](https://github.com/kai-morich/SimpleUsbTerminal) for a more complete example with:
* Background service to stay connected while the app is not visible or rotating
A [simple Arduino application](https://github.com/mik3y/usb-serial-for-android/blob/master/arduino) * Flow control
is also available which can be used for testing.
## Probing for Unrecognized Devices ## Probing for Unrecognized Devices
@ -103,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
@ -126,35 +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 * 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
* ... * ...
## Author, License, and Copyright
usb-serial-for-android is written and maintained by *mike wakerly* and *kai morich*
This library is licensed under *LGPL Version 2.1*. Please see LICENSE.txt for the
complete license.
Copyright 2011-2012, Google Inc. All Rights Reserved.
Portions of this library are based on [libftdi](http://www.intra2net.com/en/developer/libftdi).
Please see FtdiSerialDriver.java for more information.
## 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:3.5.0' 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

3
gradle.properties Normal file
View File

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

View File

@ -1,6 +1,6 @@
#Sun Oct 06 09:46:24 CEST 2019 #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-5.4.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

View File

@ -0,0 +1,303 @@
/* Copyright (c) 2011, Peter Barrett
**
** Permission to use, copy, modify, and/or distribute this software for
** any purpose with or without fee is hereby granted, provided that the
** above copyright notice and this permission notice appear in all copies.
**
** THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
** WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
** WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
** BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
** OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
** WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
** ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
** SOFTWARE.
*/
#include "USBCore.h" // kai:added
#include "USBAPI.h"
#include <avr/wdt.h>
#include <util/atomic.h>
#if defined(USBCON)
typedef struct
{
u32 dwDTERate;
u8 bCharFormat;
u8 bParityType;
u8 bDataBits;
u8 lineState;
} LineInfo;
static volatile LineInfo _usbLineInfo = { 57600, 0x00, 0x00, 0x00, 0x00 };
static volatile int32_t breakValue = -1;
static u8 wdtcsr_save;
#define WEAK __attribute__ ((weak))
extern const CDCDescriptor _cdcInterface PROGMEM;
const CDCDescriptor _cdcInterface =
{
D_IAD(0,2,CDC_COMMUNICATION_INTERFACE_CLASS,CDC_ABSTRACT_CONTROL_MODEL,1),
// CDC communication interface
D_INTERFACE(CDC_ACM_INTERFACE,3,CDC_COMMUNICATION_INTERFACE_CLASS,CDC_ABSTRACT_CONTROL_MODEL,0), // kai
D_CDCCS(CDC_HEADER,0x10,0x01), // Header (1.10 bcd)
D_CDCCS(CDC_CALL_MANAGEMENT,1,1), // Device handles call management (not)
D_CDCCS4(CDC_ABSTRACT_CONTROL_MANAGEMENT,6), // SET_LINE_CODING, GET_LINE_CODING, SET_CONTROL_LINE_STATE supported
D_CDCCS(CDC_UNION,CDC_ACM_INTERFACE,CDC_DATA_INTERFACE), // Communication interface is master, data interface is slave 0
D_ENDPOINT(USB_ENDPOINT_IN (CDC_ENDPOINT_ACM),USB_ENDPOINT_TYPE_INTERRUPT,0x10,0x40),
// CDC data interface
//D_INTERFACE(CDC_DATA_INTERFACE,2,CDC_DATA_INTERFACE_CLASS,0,0), // kai:removed
D_ENDPOINT(USB_ENDPOINT_OUT(CDC_ENDPOINT_OUT),USB_ENDPOINT_TYPE_BULK,USB_EP_SIZE,0),
D_ENDPOINT(USB_ENDPOINT_IN (CDC_ENDPOINT_IN ),USB_ENDPOINT_TYPE_BULK,USB_EP_SIZE,0)
};
bool isLUFAbootloader()
{
return pgm_read_word(FLASHEND - 1) == NEW_LUFA_SIGNATURE;
}
int CDC_GetInterface(u8* interfaceNum)
{
interfaceNum[0] += 1; // kai
return USB_SendControl(TRANSFER_PGM,&_cdcInterface,sizeof(_cdcInterface));
}
bool CDC_Setup(USBSetup& setup)
{
u8 r = setup.bRequest;
u8 requestType = setup.bmRequestType;
if (REQUEST_DEVICETOHOST_CLASS_INTERFACE == requestType)
{
if (CDC_GET_LINE_CODING == r)
{
USB_SendControl(0,(void*)&_usbLineInfo,7);
return true;
}
}
if (REQUEST_HOSTTODEVICE_CLASS_INTERFACE == requestType)
{
if (CDC_SEND_BREAK == r)
{
breakValue = ((uint16_t)setup.wValueH << 8) | setup.wValueL;
}
if (CDC_SET_LINE_CODING == r)
{
USB_RecvControl((void*)&_usbLineInfo,7);
}
if (CDC_SET_CONTROL_LINE_STATE == r)
{
_usbLineInfo.lineState = setup.wValueL;
// auto-reset into the bootloader is triggered when the port, already
// open at 1200 bps, is closed. this is the signal to start the watchdog
// with a relatively long period so it can finish housekeeping tasks
// like servicing endpoints before the sketch ends
uint16_t magic_key_pos = MAGIC_KEY_POS;
// If we don't use the new RAMEND directly, check manually if we have a newer bootloader.
// This is used to keep compatible with the old leonardo bootloaders.
// You are still able to set the magic key position manually to RAMEND-1 to save a few bytes for this check.
#if MAGIC_KEY_POS != (RAMEND-1)
// For future boards save the key in the inproblematic RAMEND
// Which is reserved for the main() return value (which will never return)
if (isLUFAbootloader()) {
// horray, we got a new bootloader!
magic_key_pos = (RAMEND-1);
}
#endif
// We check DTR state to determine if host port is open (bit 0 of lineState).
if (1200 == _usbLineInfo.dwDTERate && (_usbLineInfo.lineState & 0x01) == 0)
{
#if MAGIC_KEY_POS != (RAMEND-1)
// Backup ram value if its not a newer bootloader and it hasn't already been saved.
// This should avoid memory corruption at least a bit, not fully
if (magic_key_pos != (RAMEND-1) && *(uint16_t *)magic_key_pos != MAGIC_KEY) {
*(uint16_t *)(RAMEND-1) = *(uint16_t *)magic_key_pos;
}
#endif
// Store boot key
*(uint16_t *)magic_key_pos = MAGIC_KEY;
// Save the watchdog state in case the reset is aborted.
wdtcsr_save = WDTCSR;
wdt_enable(WDTO_120MS);
}
else if (*(uint16_t *)magic_key_pos == MAGIC_KEY)
{
// Most OSs do some intermediate steps when configuring ports and DTR can
// twiggle more than once before stabilizing.
// To avoid spurious resets we set the watchdog to 120ms and eventually
// cancel if DTR goes back high.
// Cancellation is only done if an auto-reset was started, which is
// indicated by the magic key having been set.
wdt_reset();
// Restore the watchdog state in case the sketch was using it.
WDTCSR |= (1<<WDCE) | (1<<WDE);
WDTCSR = wdtcsr_save;
#if MAGIC_KEY_POS != (RAMEND-1)
// Restore backed up (old bootloader) magic key data
if (magic_key_pos != (RAMEND-1)) {
*(uint16_t *)magic_key_pos = *(uint16_t *)(RAMEND-1);
} else
#endif
{
// Clean up RAMEND key
*(uint16_t *)magic_key_pos = 0x0000;
}
}
}
return true;
}
return false;
}
void Serial_::begin(unsigned long /* baud_count */)
{
peek_buffer = -1;
}
void Serial_::begin(unsigned long /* baud_count */, byte /* config */)
{
peek_buffer = -1;
}
void Serial_::end(void)
{
}
int Serial_::available(void)
{
if (peek_buffer >= 0) {
return 1 + USB_Available(CDC_RX);
}
return USB_Available(CDC_RX);
}
int Serial_::peek(void)
{
if (peek_buffer < 0)
peek_buffer = USB_Recv(CDC_RX);
return peek_buffer;
}
int Serial_::read(void)
{
if (peek_buffer >= 0) {
int c = peek_buffer;
peek_buffer = -1;
return c;
}
return USB_Recv(CDC_RX);
}
int Serial_::availableForWrite(void)
{
return USB_SendSpace(CDC_TX);
}
void Serial_::flush(void)
{
USB_Flush(CDC_TX);
}
size_t Serial_::write(uint8_t c)
{
return write(&c, 1);
}
size_t Serial_::write(const uint8_t *buffer, size_t size)
{
/* only try to send bytes if the high-level CDC connection itself
is open (not just the pipe) - the OS should set lineState when the port
is opened and clear lineState when the port is closed.
bytes sent before the user opens the connection or after
the connection is closed are lost - just like with a UART. */
// TODO - ZE - check behavior on different OSes and test what happens if an
// open connection isn't broken cleanly (cable is yanked out, host dies
// or locks up, or host virtual serial port hangs)
if (_usbLineInfo.lineState > 0) {
int r = USB_Send(CDC_TX,buffer,size);
if (r > 0) {
return r;
} else {
setWriteError();
return 0;
}
}
setWriteError();
return 0;
}
// This operator is a convenient way for a sketch to check whether the
// port has actually been configured and opened by the host (as opposed
// to just being connected to the host). It can be used, for example, in
// setup() before printing to ensure that an application on the host is
// actually ready to receive and display the data.
// We add a short delay before returning to fix a bug observed by Federico
// where the port is configured (lineState != 0) but not quite opened.
Serial_::operator bool() {
bool result = false;
if (_usbLineInfo.lineState > 0)
result = true;
delay(10);
return result;
}
unsigned long Serial_::baud() {
// Disable interrupts while reading a multi-byte value
uint32_t baudrate;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
baudrate = _usbLineInfo.dwDTERate;
}
return baudrate;
}
uint8_t Serial_::stopbits() {
return _usbLineInfo.bCharFormat;
}
uint8_t Serial_::paritytype() {
return _usbLineInfo.bParityType;
}
uint8_t Serial_::numbits() {
return _usbLineInfo.bDataBits;
}
bool Serial_::dtr() {
return _usbLineInfo.lineState & 0x1;
}
bool Serial_::rts() {
return _usbLineInfo.lineState & 0x2;
}
int32_t Serial_::readBreak() {
int32_t ret;
// Disable IRQs while reading and clearing breakValue to make
// sure we don't overwrite a value just set by the ISR.
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
ret = breakValue;
breakValue = -1;
}
return ret;
}
Serial_ Serial;
#endif /* if defined(USBCON) */

View File

@ -0,0 +1,8 @@
## castrated CDC test (single interface with 3 endpoints)
As mentioned [here](https://arduino.stackexchange.com/a/31695/62145), Arduino functions can be _overwritten_ by copying complete files into the own project.
This is used to create a castrated CDC device with a single interface containing 3 endpoints.
The modifications have been done against Arduino 1.8.10, for changes see comments containing `kai`.

View File

@ -0,0 +1,301 @@
// Copyright (c) 2010, Peter Barrett
/*
** Permission to use, copy, modify, and/or distribute this software for
** any purpose with or without fee is hereby granted, provided that the
** above copyright notice and this permission notice appear in all copies.
**
** THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
** WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
** WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
** BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
** OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
** WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
** ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
** SOFTWARE.
*/
#ifndef __USBCORE_H__
#define __USBCORE_H__
#include "USBAPI.h"
// Standard requests
#define GET_STATUS 0
#define CLEAR_FEATURE 1
#define SET_FEATURE 3
#define SET_ADDRESS 5
#define GET_DESCRIPTOR 6
#define SET_DESCRIPTOR 7
#define GET_CONFIGURATION 8
#define SET_CONFIGURATION 9
#define GET_INTERFACE 10
#define SET_INTERFACE 11
// bmRequestType
#define REQUEST_HOSTTODEVICE 0x00
#define REQUEST_DEVICETOHOST 0x80
#define REQUEST_DIRECTION 0x80
#define REQUEST_STANDARD 0x00
#define REQUEST_CLASS 0x20
#define REQUEST_VENDOR 0x40
#define REQUEST_TYPE 0x60
#define REQUEST_DEVICE 0x00
#define REQUEST_INTERFACE 0x01
#define REQUEST_ENDPOINT 0x02
#define REQUEST_OTHER 0x03
#define REQUEST_RECIPIENT 0x03
#define REQUEST_DEVICETOHOST_CLASS_INTERFACE (REQUEST_DEVICETOHOST | REQUEST_CLASS | REQUEST_INTERFACE)
#define REQUEST_HOSTTODEVICE_CLASS_INTERFACE (REQUEST_HOSTTODEVICE | REQUEST_CLASS | REQUEST_INTERFACE)
#define REQUEST_DEVICETOHOST_STANDARD_INTERFACE (REQUEST_DEVICETOHOST | REQUEST_STANDARD | REQUEST_INTERFACE)
// Class requests
#define CDC_SET_LINE_CODING 0x20
#define CDC_GET_LINE_CODING 0x21
#define CDC_SET_CONTROL_LINE_STATE 0x22
#define CDC_SEND_BREAK 0x23
#define MSC_RESET 0xFF
#define MSC_GET_MAX_LUN 0xFE
// Descriptors
#define USB_DEVICE_DESC_SIZE 18
#define USB_CONFIGUARTION_DESC_SIZE 9
#define USB_INTERFACE_DESC_SIZE 9
#define USB_ENDPOINT_DESC_SIZE 7
#define USB_DEVICE_DESCRIPTOR_TYPE 1
#define USB_CONFIGURATION_DESCRIPTOR_TYPE 2
#define USB_STRING_DESCRIPTOR_TYPE 3
#define USB_INTERFACE_DESCRIPTOR_TYPE 4
#define USB_ENDPOINT_DESCRIPTOR_TYPE 5
// usb_20.pdf Table 9.6 Standard Feature Selectors
#define DEVICE_REMOTE_WAKEUP 1
#define ENDPOINT_HALT 2
#define TEST_MODE 3
// usb_20.pdf Figure 9-4. Information Returned by a GetStatus() Request to a Device
#define FEATURE_SELFPOWERED_ENABLED (1 << 0)
#define FEATURE_REMOTE_WAKEUP_ENABLED (1 << 1)
#define USB_DEVICE_CLASS_COMMUNICATIONS 0x02
#define USB_DEVICE_CLASS_HUMAN_INTERFACE 0x03
#define USB_DEVICE_CLASS_STORAGE 0x08
#define USB_DEVICE_CLASS_VENDOR_SPECIFIC 0xFF
#define USB_CONFIG_POWERED_MASK 0x40
#define USB_CONFIG_BUS_POWERED 0x80
#define USB_CONFIG_SELF_POWERED 0xC0
#define USB_CONFIG_REMOTE_WAKEUP 0x20
// bMaxPower in Configuration Descriptor
#define USB_CONFIG_POWER_MA(mA) ((mA)/2)
// bEndpointAddress in Endpoint Descriptor
#define USB_ENDPOINT_DIRECTION_MASK 0x80
#define USB_ENDPOINT_OUT(addr) (lowByte((addr) | 0x00))
#define USB_ENDPOINT_IN(addr) (lowByte((addr) | 0x80))
#define USB_ENDPOINT_TYPE_MASK 0x03
#define USB_ENDPOINT_TYPE_CONTROL 0x00
#define USB_ENDPOINT_TYPE_ISOCHRONOUS 0x01
#define USB_ENDPOINT_TYPE_BULK 0x02
#define USB_ENDPOINT_TYPE_INTERRUPT 0x03
#define TOBYTES(x) ((x) & 0xFF),(((x) >> 8) & 0xFF)
#define CDC_V1_10 0x0110
#define CDC_COMMUNICATION_INTERFACE_CLASS 0x02
#define CDC_CALL_MANAGEMENT 0x01
#define CDC_ABSTRACT_CONTROL_MODEL 0x02
#define CDC_HEADER 0x00
#define CDC_ABSTRACT_CONTROL_MANAGEMENT 0x02
#define CDC_UNION 0x06
#define CDC_CS_INTERFACE 0x24
#define CDC_CS_ENDPOINT 0x25
#define CDC_DATA_INTERFACE_CLASS 0x0A
#define MSC_SUBCLASS_SCSI 0x06
#define MSC_PROTOCOL_BULK_ONLY 0x50
#ifndef USB_VERSION
#define USB_VERSION 0x200
#endif
// Device
typedef struct {
u8 len; // 18
u8 dtype; // 1 USB_DEVICE_DESCRIPTOR_TYPE
u16 usbVersion; // 0x200 or 0x210
u8 deviceClass;
u8 deviceSubClass;
u8 deviceProtocol;
u8 packetSize0; // Packet 0
u16 idVendor;
u16 idProduct;
u16 deviceVersion; // 0x100
u8 iManufacturer;
u8 iProduct;
u8 iSerialNumber;
u8 bNumConfigurations;
} DeviceDescriptor;
// Config
typedef struct {
u8 len; // 9
u8 dtype; // 2
u16 clen; // total length
u8 numInterfaces;
u8 config;
u8 iconfig;
u8 attributes;
u8 maxPower;
} ConfigDescriptor;
// String
// Interface
typedef struct
{
u8 len; // 9
u8 dtype; // 4
u8 number;
u8 alternate;
u8 numEndpoints;
u8 interfaceClass;
u8 interfaceSubClass;
u8 protocol;
u8 iInterface;
} InterfaceDescriptor;
// Endpoint
typedef struct
{
u8 len; // 7
u8 dtype; // 5
u8 addr;
u8 attr;
u16 packetSize;
u8 interval;
} EndpointDescriptor;
// Interface Association Descriptor
// Used to bind 2 interfaces together in CDC compostite device
typedef struct
{
u8 len; // 8
u8 dtype; // 11
u8 firstInterface;
u8 interfaceCount;
u8 functionClass;
u8 funtionSubClass;
u8 functionProtocol;
u8 iInterface;
} IADDescriptor;
// CDC CS interface descriptor
typedef struct
{
u8 len; // 5
u8 dtype; // 0x24
u8 subtype;
u8 d0;
u8 d1;
} CDCCSInterfaceDescriptor;
typedef struct
{
u8 len; // 4
u8 dtype; // 0x24
u8 subtype;
u8 d0;
} CDCCSInterfaceDescriptor4;
typedef struct
{
u8 len;
u8 dtype; // 0x24
u8 subtype; // 1
u8 bmCapabilities;
u8 bDataInterface;
} CMFunctionalDescriptor;
typedef struct
{
u8 len;
u8 dtype; // 0x24
u8 subtype; // 1
u8 bmCapabilities;
} ACMFunctionalDescriptor;
typedef struct
{
// IAD
IADDescriptor iad; // Only needed on compound device
// Control
InterfaceDescriptor cif; //
CDCCSInterfaceDescriptor header;
CMFunctionalDescriptor callManagement; // Call Management
ACMFunctionalDescriptor controlManagement; // ACM
CDCCSInterfaceDescriptor functionalDescriptor; // CDC_UNION
EndpointDescriptor cifin;
// Data
//InterfaceDescriptor dif; // kai:removed
EndpointDescriptor in;
EndpointDescriptor out;
} CDCDescriptor;
typedef struct
{
InterfaceDescriptor msc;
EndpointDescriptor in;
EndpointDescriptor out;
} MSCDescriptor;
#define D_DEVICE(_class,_subClass,_proto,_packetSize0,_vid,_pid,_version,_im,_ip,_is,_configs) \
{ 18, 1, USB_VERSION, _class,_subClass,_proto,_packetSize0,_vid,_pid,_version,_im,_ip,_is,_configs }
#define D_CONFIG(_totalLength,_interfaces) \
{ 9, 2, _totalLength,_interfaces, 1, 0, USB_CONFIG_BUS_POWERED | USB_CONFIG_REMOTE_WAKEUP, USB_CONFIG_POWER_MA(500) }
#define D_INTERFACE(_n,_numEndpoints,_class,_subClass,_protocol) \
{ 9, 4, _n, 0, _numEndpoints, _class,_subClass, _protocol, 0 }
#define D_ENDPOINT(_addr,_attr,_packetSize, _interval) \
{ 7, 5, _addr,_attr,_packetSize, _interval }
#define D_IAD(_firstInterface, _count, _class, _subClass, _protocol) \
{ 8, 11, _firstInterface, _count, _class, _subClass, _protocol, 0 }
#define D_CDCCS(_subtype,_d0,_d1) { 5, 0x24, _subtype, _d0, _d1 }
#define D_CDCCS4(_subtype,_d0) { 4, 0x24, _subtype, _d0 }
// Bootloader related fields
// Old Caterina bootloader places the MAGIC key into unsafe RAM locations (it can be rewritten
// by the running sketch before to actual reboot).
// Newer bootloaders, recognizable by the LUFA "signature" at the end of the flash, can handle both
// the usafe and the safe location.
#ifndef MAGIC_KEY
#define MAGIC_KEY 0x7777
#endif
#ifndef MAGIC_KEY_POS
#define MAGIC_KEY_POS 0x0800
#endif
#ifndef NEW_LUFA_SIGNATURE
#define NEW_LUFA_SIGNATURE 0xDCFB
#endif
#endif

View File

@ -0,0 +1,64 @@
/*
bridge USB-serial to hardware-serial
for Arduinos based on ATmega32u4 (Leonardo and compatible Pro Micro, Micro)
hardware serial is configured with baud-rate, databits, stopbits, parity as send over USB
see https://github.com/arduino/Arduino/tree/master/hardware/arduino/avr/cores/arduino
-> CDC.cpp|HardwareSerial.cpp for serial implementation details
this sketch is mainly for demonstration / test of CDC communication
performance as real usb-serial bridge would be inacceptable as each byte is send in separate USB packet
*/
uint32_t baud = 9600;
uint8_t databits = 8;
uint8_t stopbits = 1;
uint8_t parity = 0;
void setup() {
Serial.begin(baud); // USB
Serial1.begin(baud, SERIAL_8N1);
}
void loop() {
// show USB connected state
if (Serial) TXLED1;
else TXLED0;
// configure hardware serial
if (Serial.baud() != baud ||
Serial.numbits() != databits ||
Serial.stopbits() != stopbits ||
Serial.paritytype() != parity) {
baud = Serial.baud();
databits = Serial.numbits();
stopbits = Serial.stopbits();
parity = Serial.paritytype();
uint8_t config = 0; // ucsrc register
switch (databits) {
case 5: break;
case 6: config |= 2; break;
case 7: config |= 4; break;
case 8: config |= 6; break;
default: config |= 6;
}
switch (stopbits) {
case 2: config |= 8;
// 1.5 stopbits not supported
}
switch (parity) {
case 1: config |= 0x30; break; // odd
case 2: config |= 0x20; break; // even
// mark, space not supported
}
Serial1.end();
Serial1.begin(baud, config);
}
// bridge
if (Serial.available() > 0)
Serial1.write(Serial.read());
if (Serial1.available() > 0)
Serial.write(Serial1.read());
}

View File

@ -0,0 +1,320 @@
/* Copyright (c) 2011, Peter Barrett
**
** Permission to use, copy, modify, and/or distribute this software for
** any purpose with or without fee is hereby granted, provided that the
** above copyright notice and this permission notice appear in all copies.
**
** THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
** WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
** WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
** BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
** OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
** WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
** ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
** SOFTWARE.
*/
#include "USBCore.h" // kai:added
#include "USBAPI.h"
#include "USBDesc.h" // kai:added
#include <avr/wdt.h>
#include <util/atomic.h>
#if defined(USBCON)
typedef struct
{
u32 dwDTERate;
u8 bCharFormat;
u8 bParityType;
u8 bDataBits;
u8 lineState;
} LineInfo;
static volatile LineInfo _usbLineInfo = { 57600, 0x00, 0x00, 0x00, 0x00 };
static volatile int32_t breakValue = -1;
static u8 wdtcsr_save;
#define WEAK __attribute__ ((weak))
extern const CDCDescriptor _cdcInterface PROGMEM;
const CDCDescriptor _cdcInterface =
{
D_IAD(0,2,CDC_COMMUNICATION_INTERFACE_CLASS,CDC_ABSTRACT_CONTROL_MODEL,1),
// CDC communication interface
D_INTERFACE(CDC_ACM_INTERFACE1,1,CDC_COMMUNICATION_INTERFACE_CLASS,CDC_ABSTRACT_CONTROL_MODEL,0),
D_CDCCS(CDC_HEADER,0x10,0x01), // Header (1.10 bcd)
D_CDCCS(CDC_CALL_MANAGEMENT,1,1), // Device handles call management (not)
D_CDCCS4(CDC_ABSTRACT_CONTROL_MANAGEMENT,6), // SET_LINE_CODING, GET_LINE_CODING, SET_CONTROL_LINE_STATE supported
D_CDCCS(CDC_UNION,CDC_ACM_INTERFACE1,CDC_DATA_INTERFACE1), // Communication interface is master, data interface is slave 0
D_ENDPOINT(USB_ENDPOINT_IN (CDC_ENDPOINT_ACM1),USB_ENDPOINT_TYPE_INTERRUPT,0x10,0x40),
// CDC data interface
D_INTERFACE(CDC_DATA_INTERFACE1,2,CDC_DATA_INTERFACE_CLASS,0,0),
D_ENDPOINT(USB_ENDPOINT_OUT(CDC_ENDPOINT_OUT1),USB_ENDPOINT_TYPE_BULK,USB_EP_SIZE,0),
D_ENDPOINT(USB_ENDPOINT_IN (CDC_ENDPOINT_IN1 ),USB_ENDPOINT_TYPE_BULK,USB_EP_SIZE,0)
, // kai: added
D_IAD(2,2,CDC_COMMUNICATION_INTERFACE_CLASS,CDC_ABSTRACT_CONTROL_MODEL,1),
// CDC communication interface
D_INTERFACE(CDC_ACM_INTERFACE2,1,CDC_COMMUNICATION_INTERFACE_CLASS,CDC_ABSTRACT_CONTROL_MODEL,0),
D_CDCCS(CDC_HEADER,0x10,0x01), // Header (1.10 bcd)
D_CDCCS(CDC_CALL_MANAGEMENT,1,1), // Device handles call management (not)
D_CDCCS4(CDC_ABSTRACT_CONTROL_MANAGEMENT,6), // SET_LINE_CODING, GET_LINE_CODING, SET_CONTROL_LINE_STATE supported
D_CDCCS(CDC_UNION,CDC_ACM_INTERFACE2,CDC_DATA_INTERFACE2), // Communication interface is master, data interface is slave 0
D_ENDPOINT(USB_ENDPOINT_IN (CDC_ENDPOINT_ACM2),USB_ENDPOINT_TYPE_INTERRUPT,0x10,0x40),
// CDC data interface
D_INTERFACE(CDC_DATA_INTERFACE2,2,CDC_DATA_INTERFACE_CLASS,0,0),
D_ENDPOINT(USB_ENDPOINT_OUT(CDC_ENDPOINT_OUT2),USB_ENDPOINT_TYPE_BULK,USB_EP_SIZE,0),
D_ENDPOINT(USB_ENDPOINT_IN (CDC_ENDPOINT_IN2),USB_ENDPOINT_TYPE_BULK,USB_EP_SIZE,0)
};
bool isLUFAbootloader()
{
return pgm_read_word(FLASHEND - 1) == NEW_LUFA_SIGNATURE;
}
int CDC_GetInterface(u8* interfaceNum)
{
interfaceNum[0] += 4; // kai
return USB_SendControl(TRANSFER_PGM,&_cdcInterface,sizeof(_cdcInterface));
}
bool CDC_Setup(USBSetup& setup)
{
u8 r = setup.bRequest;
u8 requestType = setup.bmRequestType;
if (REQUEST_DEVICETOHOST_CLASS_INTERFACE == requestType)
{
if (CDC_GET_LINE_CODING == r)
{
USB_SendControl(0,(void*)&_usbLineInfo,7);
return true;
}
}
if (REQUEST_HOSTTODEVICE_CLASS_INTERFACE == requestType)
{
if (CDC_SEND_BREAK == r)
{
breakValue = ((uint16_t)setup.wValueH << 8) | setup.wValueL;
}
if (CDC_SET_LINE_CODING == r)
{
USB_RecvControl((void*)&_usbLineInfo,7);
}
if (CDC_SET_CONTROL_LINE_STATE == r)
{
_usbLineInfo.lineState = setup.wValueL;
// auto-reset into the bootloader is triggered when the port, already
// open at 1200 bps, is closed. this is the signal to start the watchdog
// with a relatively long period so it can finish housekeeping tasks
// like servicing endpoints before the sketch ends
uint16_t magic_key_pos = MAGIC_KEY_POS;
// If we don't use the new RAMEND directly, check manually if we have a newer bootloader.
// This is used to keep compatible with the old leonardo bootloaders.
// You are still able to set the magic key position manually to RAMEND-1 to save a few bytes for this check.
#if MAGIC_KEY_POS != (RAMEND-1)
// For future boards save the key in the inproblematic RAMEND
// Which is reserved for the main() return value (which will never return)
if (isLUFAbootloader()) {
// horray, we got a new bootloader!
magic_key_pos = (RAMEND-1);
}
#endif
// We check DTR state to determine if host port is open (bit 0 of lineState).
if (1200 == _usbLineInfo.dwDTERate && (_usbLineInfo.lineState & 0x01) == 0)
{
#if MAGIC_KEY_POS != (RAMEND-1)
// Backup ram value if its not a newer bootloader and it hasn't already been saved.
// This should avoid memory corruption at least a bit, not fully
if (magic_key_pos != (RAMEND-1) && *(uint16_t *)magic_key_pos != MAGIC_KEY) {
*(uint16_t *)(RAMEND-1) = *(uint16_t *)magic_key_pos;
}
#endif
// Store boot key
*(uint16_t *)magic_key_pos = MAGIC_KEY;
// Save the watchdog state in case the reset is aborted.
wdtcsr_save = WDTCSR;
wdt_enable(WDTO_120MS);
}
else if (*(uint16_t *)magic_key_pos == MAGIC_KEY)
{
// Most OSs do some intermediate steps when configuring ports and DTR can
// twiggle more than once before stabilizing.
// To avoid spurious resets we set the watchdog to 120ms and eventually
// cancel if DTR goes back high.
// Cancellation is only done if an auto-reset was started, which is
// indicated by the magic key having been set.
wdt_reset();
// Restore the watchdog state in case the sketch was using it.
WDTCSR |= (1<<WDCE) | (1<<WDE);
WDTCSR = wdtcsr_save;
#if MAGIC_KEY_POS != (RAMEND-1)
// Restore backed up (old bootloader) magic key data
if (magic_key_pos != (RAMEND-1)) {
*(uint16_t *)magic_key_pos = *(uint16_t *)(RAMEND-1);
} else
#endif
{
// Clean up RAMEND key
*(uint16_t *)magic_key_pos = 0x0000;
}
}
}
return true;
}
return false;
}
void Serial_::begin(unsigned long /* baud_count */)
{
peek_buffer = -1;
}
void Serial_::begin(unsigned long /* baud_count */, byte /* config */)
{
peek_buffer = -1;
}
void Serial_::end(void)
{
}
int Serial_::available(void)
{
if (peek_buffer >= 0) {
return 1 + USB_Available(CDC_RX);
}
return USB_Available(CDC_RX);
}
int Serial_::peek(void)
{
if (peek_buffer < 0)
peek_buffer = USB_Recv(CDC_RX);
return peek_buffer;
}
int Serial_::read(void)
{
if (peek_buffer >= 0) {
int c = peek_buffer;
peek_buffer = -1;
return c;
}
return USB_Recv(CDC_RX);
}
int Serial_::availableForWrite(void)
{
return USB_SendSpace(CDC_TX);
}
void Serial_::flush(void)
{
USB_Flush(CDC_TX);
}
size_t Serial_::write(uint8_t c)
{
return write(&c, 1);
}
size_t Serial_::write(const uint8_t *buffer, size_t size)
{
/* only try to send bytes if the high-level CDC connection itself
is open (not just the pipe) - the OS should set lineState when the port
is opened and clear lineState when the port is closed.
bytes sent before the user opens the connection or after
the connection is closed are lost - just like with a UART. */
// TODO - ZE - check behavior on different OSes and test what happens if an
// open connection isn't broken cleanly (cable is yanked out, host dies
// or locks up, or host virtual serial port hangs)
if (_usbLineInfo.lineState > 0) {
int r = USB_Send(CDC_TX,buffer,size);
if (r > 0) {
return r;
} else {
setWriteError();
return 0;
}
}
setWriteError();
return 0;
}
// This operator is a convenient way for a sketch to check whether the
// port has actually been configured and opened by the host (as opposed
// to just being connected to the host). It can be used, for example, in
// setup() before printing to ensure that an application on the host is
// actually ready to receive and display the data.
// We add a short delay before returning to fix a bug observed by Federico
// where the port is configured (lineState != 0) but not quite opened.
Serial_::operator bool() {
bool result = false;
if (_usbLineInfo.lineState > 0)
result = true;
delay(10);
return result;
}
unsigned long Serial_::baud() {
// Disable interrupts while reading a multi-byte value
uint32_t baudrate;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
baudrate = _usbLineInfo.dwDTERate;
}
return baudrate;
}
uint8_t Serial_::stopbits() {
return _usbLineInfo.bCharFormat;
}
uint8_t Serial_::paritytype() {
return _usbLineInfo.bParityType;
}
uint8_t Serial_::numbits() {
return _usbLineInfo.bDataBits;
}
bool Serial_::dtr() {
return _usbLineInfo.lineState & 0x1;
}
bool Serial_::rts() {
return _usbLineInfo.lineState & 0x2;
}
int32_t Serial_::readBreak() {
int32_t ret;
// Disable IRQs while reading and clearing breakValue to make
// sure we don't overwrite a value just set by the ISR.
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
ret = breakValue;
breakValue = -1;
}
return ret;
}
Serial_ Serial;
#endif /* if defined(USBCON) */

View File

@ -0,0 +1,8 @@
## multiple CDC interface test
As mentioned [here](https://arduino.stackexchange.com/a/31695/62145), Arduino functions can be _overwritten_ by copying complete files into the own project.
This is used to create a device with 2 CDC interfaces. For simplicity only one of both is functional (configurable in USBDesc.h)
The modifications have been done against Arduino 1.8.10, for changes see comments containing `kai`.

View File

@ -0,0 +1,870 @@
/* Copyright (c) 2010, Peter Barrett
** Sleep/Wakeup support added by Michael Dreher
**
** Permission to use, copy, modify, and/or distribute this software for
** any purpose with or without fee is hereby granted, provided that the
** above copyright notice and this permission notice appear in all copies.
**
** THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
** WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
** WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
** BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
** OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
** WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
** ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
** SOFTWARE.
*/
#include "USBAPI.h"
#include "USBDesc.h" // kai:added
#include "PluggableUSB.h"
#include <stdlib.h>
#if defined(USBCON)
/** Pulse generation counters to keep track of the number of milliseconds remaining for each pulse type */
#define TX_RX_LED_PULSE_MS 100
volatile u8 TxLEDPulse; /**< Milliseconds remaining for data Tx LED pulse */
volatile u8 RxLEDPulse; /**< Milliseconds remaining for data Rx LED pulse */
//==================================================================
//==================================================================
extern const u16 STRING_LANGUAGE[] PROGMEM;
extern const u8 STRING_PRODUCT[] PROGMEM;
extern const u8 STRING_MANUFACTURER[] PROGMEM;
extern const DeviceDescriptor USB_DeviceDescriptorIAD PROGMEM;
const u16 STRING_LANGUAGE[2] = {
(3<<8) | (2+2),
0x0409 // English
};
#ifndef USB_PRODUCT
// If no product is provided, use USB IO Board
#define USB_PRODUCT "USB IO Board"
#endif
const u8 STRING_PRODUCT[] PROGMEM = USB_PRODUCT;
#if USB_VID == 0x2341
# if defined(USB_MANUFACTURER)
# undef USB_MANUFACTURER
# endif
# define USB_MANUFACTURER "Arduino LLC"
#elif USB_VID == 0x1b4f
# if defined(USB_MANUFACTURER)
# undef USB_MANUFACTURER
# endif
# define USB_MANUFACTURER "SparkFun"
#elif !defined(USB_MANUFACTURER)
// Fall through to unknown if no manufacturer name was provided in a macro
# define USB_MANUFACTURER "Unknown"
#endif
const u8 STRING_MANUFACTURER[] PROGMEM = USB_MANUFACTURER;
#define DEVICE_CLASS 0x02
// DEVICE DESCRIPTOR
const DeviceDescriptor USB_DeviceDescriptorIAD =
D_DEVICE(0xEF,0x02,0x01,64,USB_VID,USB_PID,0x100,IMANUFACTURER,IPRODUCT,ISERIAL,1);
//==================================================================
//==================================================================
volatile u8 _usbConfiguration = 0;
volatile u8 _usbCurrentStatus = 0; // meaning of bits see usb_20.pdf, Figure 9-4. Information Returned by a GetStatus() Request to a Device
volatile u8 _usbSuspendState = 0; // copy of UDINT to check SUSPI and WAKEUPI bits
static inline void WaitIN(void)
{
while (!(UEINTX & (1<<TXINI)))
;
}
static inline void ClearIN(void)
{
UEINTX = ~(1<<TXINI);
}
static inline void WaitOUT(void)
{
while (!(UEINTX & (1<<RXOUTI)))
;
}
static inline u8 WaitForINOrOUT()
{
while (!(UEINTX & ((1<<TXINI)|(1<<RXOUTI))))
;
return (UEINTX & (1<<RXOUTI)) == 0;
}
static inline void ClearOUT(void)
{
UEINTX = ~(1<<RXOUTI);
}
static inline void Recv(volatile u8* data, u8 count)
{
while (count--)
*data++ = UEDATX;
RXLED1; // light the RX LED
RxLEDPulse = TX_RX_LED_PULSE_MS;
}
static inline u8 Recv8()
{
RXLED1; // light the RX LED
RxLEDPulse = TX_RX_LED_PULSE_MS;
return UEDATX;
}
static inline void Send8(u8 d)
{
UEDATX = d;
}
static inline void SetEP(u8 ep)
{
UENUM = ep;
}
static inline u8 FifoByteCount()
{
return UEBCLX;
}
static inline u8 ReceivedSetupInt()
{
return UEINTX & (1<<RXSTPI);
}
static inline void ClearSetupInt()
{
UEINTX = ~((1<<RXSTPI) | (1<<RXOUTI) | (1<<TXINI));
}
static inline void Stall()
{
UECONX = (1<<STALLRQ) | (1<<EPEN);
}
static inline u8 ReadWriteAllowed()
{
return UEINTX & (1<<RWAL);
}
static inline u8 Stalled()
{
return UEINTX & (1<<STALLEDI);
}
static inline u8 FifoFree()
{
return UEINTX & (1<<FIFOCON);
}
static inline void ReleaseRX()
{
UEINTX = 0x6B; // FIFOCON=0 NAKINI=1 RWAL=1 NAKOUTI=0 RXSTPI=1 RXOUTI=0 STALLEDI=1 TXINI=1
}
static inline void ReleaseTX()
{
UEINTX = 0x3A; // FIFOCON=0 NAKINI=0 RWAL=1 NAKOUTI=1 RXSTPI=1 RXOUTI=0 STALLEDI=1 TXINI=0
}
static inline u8 FrameNumber()
{
return UDFNUML;
}
//==================================================================
//==================================================================
u8 USBGetConfiguration(void)
{
return _usbConfiguration;
}
#define USB_RECV_TIMEOUT
class LockEP
{
u8 _sreg;
public:
LockEP(u8 ep) : _sreg(SREG)
{
cli();
SetEP(ep & 7);
}
~LockEP()
{
SREG = _sreg;
}
};
// Number of bytes, assumes a rx endpoint
u8 USB_Available(u8 ep)
{
LockEP lock(ep);
return FifoByteCount();
}
// Non Blocking receive
// Return number of bytes read
int USB_Recv(u8 ep, void* d, int len)
{
if (!_usbConfiguration || len < 0)
return -1;
LockEP lock(ep);
u8 n = FifoByteCount();
len = min(n,len);
n = len;
u8* dst = (u8*)d;
while (n--)
*dst++ = Recv8();
if (len && !FifoByteCount()) // release empty buffer
ReleaseRX();
return len;
}
// Recv 1 byte if ready
int USB_Recv(u8 ep)
{
u8 c;
if (USB_Recv(ep,&c,1) != 1)
return -1;
return c;
}
// Space in send EP
u8 USB_SendSpace(u8 ep)
{
LockEP lock(ep);
if (!ReadWriteAllowed())
return 0;
return USB_EP_SIZE - FifoByteCount();
}
// Blocking Send of data to an endpoint
int USB_Send(u8 ep, const void* d, int len)
{
if (!_usbConfiguration)
return -1;
if (_usbSuspendState & (1<<SUSPI)) {
//send a remote wakeup
UDCON |= (1 << RMWKUP);
}
int r = len;
const u8* data = (const u8*)d;
u8 timeout = 250; // 250ms timeout on send? TODO
bool sendZlp = false;
while (len || sendZlp)
{
u8 n = USB_SendSpace(ep);
if (n == 0)
{
if (!(--timeout))
return -1;
delay(1);
continue;
}
if (n > len) {
n = len;
}
{
LockEP lock(ep);
// Frame may have been released by the SOF interrupt handler
if (!ReadWriteAllowed())
continue;
len -= n;
if (ep & TRANSFER_ZERO)
{
while (n--)
Send8(0);
}
else if (ep & TRANSFER_PGM)
{
while (n--)
Send8(pgm_read_byte(data++));
}
else
{
while (n--)
Send8(*data++);
}
if (sendZlp) {
ReleaseTX();
sendZlp = false;
} else if (!ReadWriteAllowed()) { // ...release if buffer is full...
ReleaseTX();
if (len == 0) sendZlp = true;
} else if ((len == 0) && (ep & TRANSFER_RELEASE)) { // ...or if forced with TRANSFER_RELEASE
// XXX: TRANSFER_RELEASE is never used can be removed?
ReleaseTX();
}
}
}
TXLED1; // light the TX LED
TxLEDPulse = TX_RX_LED_PULSE_MS;
return r;
}
u8 _initEndpoints[USB_ENDPOINTS] =
{
0, // Control Endpoint
EP_TYPE_INTERRUPT_IN, // CDC_ENDPOINT_ACM
EP_TYPE_BULK_OUT, // CDC_ENDPOINT_OUT
EP_TYPE_BULK_IN, // CDC_ENDPOINT_IN
// kai:added
EP_TYPE_INTERRUPT_IN, // CDC_ENDPOINT_ACM
EP_TYPE_BULK_OUT, // CDC_ENDPOINT_OUT
EP_TYPE_BULK_IN, // CDC_ENDPOINT_IN
// Following endpoints are automatically initialized to 0
};
#define EP_SINGLE_64 0x32 // EP0
#define EP_DOUBLE_64 0x36 // Other endpoints
#define EP_SINGLE_16 0x12
static
void InitEP(u8 index, u8 type, u8 size)
{
UENUM = index;
UECONX = (1<<EPEN);
UECFG0X = type;
UECFG1X = size;
}
static
void InitEndpoints()
{
for (u8 i = 1; i < sizeof(_initEndpoints) && _initEndpoints[i] != 0; i++)
{
UENUM = i;
UECONX = (1<<EPEN);
UECFG0X = _initEndpoints[i];
#if USB_EP_SIZE == 16
UECFG1X = EP_SINGLE_16;
#elif USB_EP_SIZE == 64
UECFG1X = EP_DOUBLE_64;
#else
#error Unsupported value for USB_EP_SIZE
#endif
}
UERST = 0x7E; // And reset them
UERST = 0;
}
// Handle CLASS_INTERFACE requests
static
bool ClassInterfaceRequest(USBSetup& setup)
{
u8 i = setup.wIndex;
if (CDC_CLASS_INTERFACE == i) // kai
return CDC_Setup(setup);
#ifdef PLUGGABLE_USB_ENABLED
return PluggableUSB().setup(setup);
#endif
return false;
}
static int _cmark;
static int _cend;
void InitControl(int end)
{
SetEP(0);
_cmark = 0;
_cend = end;
}
static
bool SendControl(u8 d)
{
if (_cmark < _cend)
{
if (!WaitForINOrOUT())
return false;
Send8(d);
if (!((_cmark + 1) & 0x3F))
ClearIN(); // Fifo is full, release this packet
}
_cmark++;
return true;
}
// Clipped by _cmark/_cend
int USB_SendControl(u8 flags, const void* d, int len)
{
int sent = len;
const u8* data = (const u8*)d;
bool pgm = flags & TRANSFER_PGM;
while (len--)
{
u8 c = pgm ? pgm_read_byte(data++) : *data++;
if (!SendControl(c))
return -1;
}
return sent;
}
// Send a USB descriptor string. The string is stored in PROGMEM as a
// plain ASCII string but is sent out as UTF-16 with the correct 2-byte
// prefix
static bool USB_SendStringDescriptor(const u8*string_P, u8 string_len, uint8_t flags) {
SendControl(2 + string_len * 2);
SendControl(3);
bool pgm = flags & TRANSFER_PGM;
for(u8 i = 0; i < string_len; i++) {
bool r = SendControl(pgm ? pgm_read_byte(&string_P[i]) : string_P[i]);
r &= SendControl(0); // high byte
if(!r) {
return false;
}
}
return true;
}
// Does not timeout or cross fifo boundaries
int USB_RecvControl(void* d, int len)
{
auto length = len;
while(length)
{
// Dont receive more than the USB Control EP has to offer
// Use fixed 64 because control EP always have 64 bytes even on 16u2.
auto recvLength = length;
if(recvLength > 64){
recvLength = 64;
}
// Write data to fit to the end (not the beginning) of the array
WaitOUT();
Recv((u8*)d + len - length, recvLength);
ClearOUT();
length -= recvLength;
}
return len;
}
static u8 SendInterfaces()
{
u8 interfaces = 0;
CDC_GetInterface(&interfaces);
#ifdef PLUGGABLE_USB_ENABLED
PluggableUSB().getInterface(&interfaces);
#endif
return interfaces;
}
// Construct a dynamic configuration descriptor
// This really needs dynamic endpoint allocation etc
// TODO
static
bool SendConfiguration(int maxlen)
{
// Count and measure interfaces
InitControl(0);
u8 interfaces = SendInterfaces();
ConfigDescriptor config = D_CONFIG(_cmark + sizeof(ConfigDescriptor),interfaces);
// Now send them
InitControl(maxlen);
USB_SendControl(0,&config,sizeof(ConfigDescriptor));
SendInterfaces();
return true;
}
static
bool SendDescriptor(USBSetup& setup)
{
int ret;
u8 t = setup.wValueH;
if (USB_CONFIGURATION_DESCRIPTOR_TYPE == t)
return SendConfiguration(setup.wLength);
InitControl(setup.wLength);
#ifdef PLUGGABLE_USB_ENABLED
ret = PluggableUSB().getDescriptor(setup);
if (ret != 0) {
return (ret > 0 ? true : false);
}
#endif
const u8* desc_addr = 0;
if (USB_DEVICE_DESCRIPTOR_TYPE == t)
{
desc_addr = (const u8*)&USB_DeviceDescriptorIAD;
}
else if (USB_STRING_DESCRIPTOR_TYPE == t)
{
if (setup.wValueL == 0) {
desc_addr = (const u8*)&STRING_LANGUAGE;
}
else if (setup.wValueL == IPRODUCT) {
return USB_SendStringDescriptor(STRING_PRODUCT, strlen(USB_PRODUCT), TRANSFER_PGM);
}
else if (setup.wValueL == IMANUFACTURER) {
return USB_SendStringDescriptor(STRING_MANUFACTURER, strlen(USB_MANUFACTURER), TRANSFER_PGM);
}
else if (setup.wValueL == ISERIAL) {
#ifdef PLUGGABLE_USB_ENABLED
char name[ISERIAL_MAX_LEN];
PluggableUSB().getShortName(name);
return USB_SendStringDescriptor((uint8_t*)name, strlen(name), 0);
#endif
}
else
return false;
}
if (desc_addr == 0)
return false;
u8 desc_length = pgm_read_byte(desc_addr);
USB_SendControl(TRANSFER_PGM,desc_addr,desc_length);
return true;
}
// Endpoint 0 interrupt
ISR(USB_COM_vect)
{
SetEP(0);
if (!ReceivedSetupInt())
return;
USBSetup setup;
Recv((u8*)&setup,8);
ClearSetupInt();
u8 requestType = setup.bmRequestType;
if (requestType & REQUEST_DEVICETOHOST)
WaitIN();
else
ClearIN();
bool ok = true;
if (REQUEST_STANDARD == (requestType & REQUEST_TYPE))
{
// Standard Requests
u8 r = setup.bRequest;
u16 wValue = setup.wValueL | (setup.wValueH << 8);
if (GET_STATUS == r)
{
if (requestType == (REQUEST_DEVICETOHOST | REQUEST_STANDARD | REQUEST_DEVICE))
{
Send8(_usbCurrentStatus);
Send8(0);
}
else
{
// TODO: handle the HALT state of an endpoint here
// see "Figure 9-6. Information Returned by a GetStatus() Request to an Endpoint" in usb_20.pdf for more information
Send8(0);
Send8(0);
}
}
else if (CLEAR_FEATURE == r)
{
if((requestType == (REQUEST_HOSTTODEVICE | REQUEST_STANDARD | REQUEST_DEVICE))
&& (wValue == DEVICE_REMOTE_WAKEUP))
{
_usbCurrentStatus &= ~FEATURE_REMOTE_WAKEUP_ENABLED;
}
}
else if (SET_FEATURE == r)
{
if((requestType == (REQUEST_HOSTTODEVICE | REQUEST_STANDARD | REQUEST_DEVICE))
&& (wValue == DEVICE_REMOTE_WAKEUP))
{
_usbCurrentStatus |= FEATURE_REMOTE_WAKEUP_ENABLED;
}
}
else if (SET_ADDRESS == r)
{
WaitIN();
UDADDR = setup.wValueL | (1<<ADDEN);
}
else if (GET_DESCRIPTOR == r)
{
ok = SendDescriptor(setup);
}
else if (SET_DESCRIPTOR == r)
{
ok = false;
}
else if (GET_CONFIGURATION == r)
{
Send8(1);
}
else if (SET_CONFIGURATION == r)
{
if (REQUEST_DEVICE == (requestType & REQUEST_RECIPIENT))
{
InitEndpoints();
_usbConfiguration = setup.wValueL;
} else
ok = false;
}
else if (GET_INTERFACE == r)
{
}
else if (SET_INTERFACE == r)
{
}
}
else
{
InitControl(setup.wLength); // Max length of transfer
ok = ClassInterfaceRequest(setup);
}
if (ok)
ClearIN();
else
{
Stall();
}
}
void USB_Flush(u8 ep)
{
SetEP(ep);
if (FifoByteCount())
ReleaseTX();
}
static inline void USB_ClockDisable()
{
#if defined(OTGPADE)
USBCON = (USBCON & ~(1<<OTGPADE)) | (1<<FRZCLK); // freeze clock and disable VBUS Pad
#else // u2 Series
USBCON = (1 << FRZCLK); // freeze clock
#endif
PLLCSR &= ~(1<<PLLE); // stop PLL
}
static inline void USB_ClockEnable()
{
#if defined(UHWCON)
UHWCON |= (1<<UVREGE); // power internal reg
#endif
USBCON = (1<<USBE) | (1<<FRZCLK); // clock frozen, usb enabled
// ATmega32U4
#if defined(PINDIV)
#if F_CPU == 16000000UL
PLLCSR |= (1<<PINDIV); // Need 16 MHz xtal
#elif F_CPU == 8000000UL
PLLCSR &= ~(1<<PINDIV); // Need 8 MHz xtal
#else
#error "Clock rate of F_CPU not supported"
#endif
#elif defined(__AVR_AT90USB82__) || defined(__AVR_AT90USB162__) || defined(__AVR_ATmega32U2__) || defined(__AVR_ATmega16U2__) || defined(__AVR_ATmega8U2__)
// for the u2 Series the datasheet is confusing. On page 40 its called PINDIV and on page 290 its called PLLP0
#if F_CPU == 16000000UL
// Need 16 MHz xtal
PLLCSR |= (1 << PLLP0);
#elif F_CPU == 8000000UL
// Need 8 MHz xtal
PLLCSR &= ~(1 << PLLP0);
#endif
// AT90USB646, AT90USB647, AT90USB1286, AT90USB1287
#elif defined(PLLP2)
#if F_CPU == 16000000UL
#if defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB1287__)
// For Atmel AT90USB128x only. Do not use with Atmel AT90USB64x.
PLLCSR = (PLLCSR & ~(1<<PLLP1)) | ((1<<PLLP2) | (1<<PLLP0)); // Need 16 MHz xtal
#elif defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB647__)
// For AT90USB64x only. Do not use with AT90USB128x.
PLLCSR = (PLLCSR & ~(1<<PLLP0)) | ((1<<PLLP2) | (1<<PLLP1)); // Need 16 MHz xtal
#else
#error "USB Chip not supported, please defined method of USB PLL initialization"
#endif
#elif F_CPU == 8000000UL
// for Atmel AT90USB128x and AT90USB64x
PLLCSR = (PLLCSR & ~(1<<PLLP2)) | ((1<<PLLP1) | (1<<PLLP0)); // Need 8 MHz xtal
#else
#error "Clock rate of F_CPU not supported"
#endif
#else
#error "USB Chip not supported, please defined method of USB PLL initialization"
#endif
PLLCSR |= (1<<PLLE);
while (!(PLLCSR & (1<<PLOCK))) // wait for lock pll
{
}
// Some tests on specific versions of macosx (10.7.3), reported some
// strange behaviors when the board is reset using the serial
// port touch at 1200 bps. This delay fixes this behavior.
delay(1);
#if defined(OTGPADE)
USBCON = (USBCON & ~(1<<FRZCLK)) | (1<<OTGPADE); // start USB clock, enable VBUS Pad
#else
USBCON &= ~(1 << FRZCLK); // start USB clock
#endif
#if defined(RSTCPU)
#if defined(LSM)
UDCON &= ~((1<<RSTCPU) | (1<<LSM) | (1<<RMWKUP) | (1<<DETACH)); // enable attach resistor, set full speed mode
#else // u2 Series
UDCON &= ~((1 << RSTCPU) | (1 << RMWKUP) | (1 << DETACH)); // enable attach resistor, set full speed mode
#endif
#else
// AT90USB64x and AT90USB128x don't have RSTCPU
UDCON &= ~((1<<LSM) | (1<<RMWKUP) | (1<<DETACH)); // enable attach resistor, set full speed mode
#endif
}
// General interrupt
ISR(USB_GEN_vect)
{
u8 udint = UDINT;
UDINT &= ~((1<<EORSTI) | (1<<SOFI)); // clear the IRQ flags for the IRQs which are handled here, except WAKEUPI and SUSPI (see below)
// End of Reset
if (udint & (1<<EORSTI))
{
InitEP(0,EP_TYPE_CONTROL,EP_SINGLE_64); // init ep0
_usbConfiguration = 0; // not configured yet
UEIENX = 1 << RXSTPE; // Enable interrupts for ep0
}
// Start of Frame - happens every millisecond so we use it for TX and RX LED one-shot timing, too
if (udint & (1<<SOFI))
{
USB_Flush(CDC_TX); // Send a tx frame if found
// check whether the one-shot period has elapsed. if so, turn off the LED
if (TxLEDPulse && !(--TxLEDPulse))
TXLED0;
if (RxLEDPulse && !(--RxLEDPulse))
RXLED0;
}
// the WAKEUPI interrupt is triggered as soon as there are non-idle patterns on the data
// lines. Thus, the WAKEUPI interrupt can occur even if the controller is not in the "suspend" mode.
// Therefore the we enable it only when USB is suspended
if (udint & (1<<WAKEUPI))
{
UDIEN = (UDIEN & ~(1<<WAKEUPE)) | (1<<SUSPE); // Disable interrupts for WAKEUP and enable interrupts for SUSPEND
//TODO
// WAKEUPI shall be cleared by software (USB clock inputs must be enabled before).
//USB_ClockEnable();
UDINT &= ~(1<<WAKEUPI);
_usbSuspendState = (_usbSuspendState & ~(1<<SUSPI)) | (1<<WAKEUPI);
}
else if (udint & (1<<SUSPI)) // only one of the WAKEUPI / SUSPI bits can be active at time
{
UDIEN = (UDIEN & ~(1<<SUSPE)) | (1<<WAKEUPE); // Disable interrupts for SUSPEND and enable interrupts for WAKEUP
//TODO
//USB_ClockDisable();
UDINT &= ~((1<<WAKEUPI) | (1<<SUSPI)); // clear any already pending WAKEUP IRQs and the SUSPI request
_usbSuspendState = (_usbSuspendState & ~(1<<WAKEUPI)) | (1<<SUSPI);
}
}
// VBUS or counting frames
// Any frame counting?
u8 USBConnected()
{
u8 f = UDFNUML;
delay(3);
return f != UDFNUML;
}
//=======================================================================
//=======================================================================
USBDevice_ USBDevice;
USBDevice_::USBDevice_()
{
}
void USBDevice_::attach()
{
_usbConfiguration = 0;
_usbCurrentStatus = 0;
_usbSuspendState = 0;
USB_ClockEnable();
UDINT &= ~((1<<WAKEUPI) | (1<<SUSPI)); // clear already pending WAKEUP / SUSPEND requests
UDIEN = (1<<EORSTE) | (1<<SOFE) | (1<<SUSPE); // Enable interrupts for EOR (End of Reset), SOF (start of frame) and SUSPEND
TX_RX_LED_INIT;
}
void USBDevice_::detach()
{
}
// Check for interrupts
// TODO: VBUS detection
bool USBDevice_::configured()
{
return _usbConfiguration;
}
void USBDevice_::poll()
{
}
bool USBDevice_::wakeupHost()
{
// clear any previous wakeup request which might have been set but could be processed at that time
// e.g. because the host was not suspended at that time
UDCON &= ~(1 << RMWKUP);
if(!(UDCON & (1 << RMWKUP))
&& (_usbSuspendState & (1<<SUSPI))
&& (_usbCurrentStatus & FEATURE_REMOTE_WAKEUP_ENABLED))
{
// This short version will only work, when the device has not been suspended. Currently the
// Arduino core doesn't handle SUSPEND at all, so this is ok.
USB_ClockEnable();
UDCON |= (1 << RMWKUP); // send the wakeup request
return true;
}
return false;
}
bool USBDevice_::isSuspended()
{
return (_usbSuspendState & (1 << SUSPI));
}
#endif /* if defined(USBCON) */

View File

@ -0,0 +1,317 @@
// Copyright (c) 2010, Peter Barrett
/*
** Permission to use, copy, modify, and/or distribute this software for
** any purpose with or without fee is hereby granted, provided that the
** above copyright notice and this permission notice appear in all copies.
**
** THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
** WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
** WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
** BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
** OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
** WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
** ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
** SOFTWARE.
*/
#ifndef __USBCORE_H__
#define __USBCORE_H__
#include "USBAPI.h"
// Standard requests
#define GET_STATUS 0
#define CLEAR_FEATURE 1
#define SET_FEATURE 3
#define SET_ADDRESS 5
#define GET_DESCRIPTOR 6
#define SET_DESCRIPTOR 7
#define GET_CONFIGURATION 8
#define SET_CONFIGURATION 9
#define GET_INTERFACE 10
#define SET_INTERFACE 11
// bmRequestType
#define REQUEST_HOSTTODEVICE 0x00
#define REQUEST_DEVICETOHOST 0x80
#define REQUEST_DIRECTION 0x80
#define REQUEST_STANDARD 0x00
#define REQUEST_CLASS 0x20
#define REQUEST_VENDOR 0x40
#define REQUEST_TYPE 0x60
#define REQUEST_DEVICE 0x00
#define REQUEST_INTERFACE 0x01
#define REQUEST_ENDPOINT 0x02
#define REQUEST_OTHER 0x03
#define REQUEST_RECIPIENT 0x03
#define REQUEST_DEVICETOHOST_CLASS_INTERFACE (REQUEST_DEVICETOHOST | REQUEST_CLASS | REQUEST_INTERFACE)
#define REQUEST_HOSTTODEVICE_CLASS_INTERFACE (REQUEST_HOSTTODEVICE | REQUEST_CLASS | REQUEST_INTERFACE)
#define REQUEST_DEVICETOHOST_STANDARD_INTERFACE (REQUEST_DEVICETOHOST | REQUEST_STANDARD | REQUEST_INTERFACE)
// Class requests
#define CDC_SET_LINE_CODING 0x20
#define CDC_GET_LINE_CODING 0x21
#define CDC_SET_CONTROL_LINE_STATE 0x22
#define CDC_SEND_BREAK 0x23
#define MSC_RESET 0xFF
#define MSC_GET_MAX_LUN 0xFE
// Descriptors
#define USB_DEVICE_DESC_SIZE 18
#define USB_CONFIGUARTION_DESC_SIZE 9
#define USB_INTERFACE_DESC_SIZE 9
#define USB_ENDPOINT_DESC_SIZE 7
#define USB_DEVICE_DESCRIPTOR_TYPE 1
#define USB_CONFIGURATION_DESCRIPTOR_TYPE 2
#define USB_STRING_DESCRIPTOR_TYPE 3
#define USB_INTERFACE_DESCRIPTOR_TYPE 4
#define USB_ENDPOINT_DESCRIPTOR_TYPE 5
// usb_20.pdf Table 9.6 Standard Feature Selectors
#define DEVICE_REMOTE_WAKEUP 1
#define ENDPOINT_HALT 2
#define TEST_MODE 3
// usb_20.pdf Figure 9-4. Information Returned by a GetStatus() Request to a Device
#define FEATURE_SELFPOWERED_ENABLED (1 << 0)
#define FEATURE_REMOTE_WAKEUP_ENABLED (1 << 1)
#define USB_DEVICE_CLASS_COMMUNICATIONS 0x02
#define USB_DEVICE_CLASS_HUMAN_INTERFACE 0x03
#define USB_DEVICE_CLASS_STORAGE 0x08
#define USB_DEVICE_CLASS_VENDOR_SPECIFIC 0xFF
#define USB_CONFIG_POWERED_MASK 0x40
#define USB_CONFIG_BUS_POWERED 0x80
#define USB_CONFIG_SELF_POWERED 0xC0
#define USB_CONFIG_REMOTE_WAKEUP 0x20
// bMaxPower in Configuration Descriptor
#define USB_CONFIG_POWER_MA(mA) ((mA)/2)
// bEndpointAddress in Endpoint Descriptor
#define USB_ENDPOINT_DIRECTION_MASK 0x80
#define USB_ENDPOINT_OUT(addr) (lowByte((addr) | 0x00))
#define USB_ENDPOINT_IN(addr) (lowByte((addr) | 0x80))
#define USB_ENDPOINT_TYPE_MASK 0x03
#define USB_ENDPOINT_TYPE_CONTROL 0x00
#define USB_ENDPOINT_TYPE_ISOCHRONOUS 0x01
#define USB_ENDPOINT_TYPE_BULK 0x02
#define USB_ENDPOINT_TYPE_INTERRUPT 0x03
#define TOBYTES(x) ((x) & 0xFF),(((x) >> 8) & 0xFF)
#define CDC_V1_10 0x0110
#define CDC_COMMUNICATION_INTERFACE_CLASS 0x02
#define CDC_CALL_MANAGEMENT 0x01
#define CDC_ABSTRACT_CONTROL_MODEL 0x02
#define CDC_HEADER 0x00
#define CDC_ABSTRACT_CONTROL_MANAGEMENT 0x02
#define CDC_UNION 0x06
#define CDC_CS_INTERFACE 0x24
#define CDC_CS_ENDPOINT 0x25
#define CDC_DATA_INTERFACE_CLASS 0x0A
#define MSC_SUBCLASS_SCSI 0x06
#define MSC_PROTOCOL_BULK_ONLY 0x50
#ifndef USB_VERSION
#define USB_VERSION 0x200
#endif
// Device
typedef struct {
u8 len; // 18
u8 dtype; // 1 USB_DEVICE_DESCRIPTOR_TYPE
u16 usbVersion; // 0x200 or 0x210
u8 deviceClass;
u8 deviceSubClass;
u8 deviceProtocol;
u8 packetSize0; // Packet 0
u16 idVendor;
u16 idProduct;
u16 deviceVersion; // 0x100
u8 iManufacturer;
u8 iProduct;
u8 iSerialNumber;
u8 bNumConfigurations;
} DeviceDescriptor;
// Config
typedef struct {
u8 len; // 9
u8 dtype; // 2
u16 clen; // total length
u8 numInterfaces;
u8 config;
u8 iconfig;
u8 attributes;
u8 maxPower;
} ConfigDescriptor;
// String
// Interface
typedef struct
{
u8 len; // 9
u8 dtype; // 4
u8 number;
u8 alternate;
u8 numEndpoints;
u8 interfaceClass;
u8 interfaceSubClass;
u8 protocol;
u8 iInterface;
} InterfaceDescriptor;
// Endpoint
typedef struct
{
u8 len; // 7
u8 dtype; // 5
u8 addr;
u8 attr;
u16 packetSize;
u8 interval;
} EndpointDescriptor;
// Interface Association Descriptor
// Used to bind 2 interfaces together in CDC compostite device
typedef struct
{
u8 len; // 8
u8 dtype; // 11
u8 firstInterface;
u8 interfaceCount;
u8 functionClass;
u8 funtionSubClass;
u8 functionProtocol;
u8 iInterface;
} IADDescriptor;
// CDC CS interface descriptor
typedef struct
{
u8 len; // 5
u8 dtype; // 0x24
u8 subtype;
u8 d0;
u8 d1;
} CDCCSInterfaceDescriptor;
typedef struct
{
u8 len; // 4
u8 dtype; // 0x24
u8 subtype;
u8 d0;
} CDCCSInterfaceDescriptor4;
typedef struct
{
u8 len;
u8 dtype; // 0x24
u8 subtype; // 1
u8 bmCapabilities;
u8 bDataInterface;
} CMFunctionalDescriptor;
typedef struct
{
u8 len;
u8 dtype; // 0x24
u8 subtype; // 1
u8 bmCapabilities;
} ACMFunctionalDescriptor;
typedef struct
{
// IAD
IADDescriptor iad; // Only needed on compound device
// Control
InterfaceDescriptor cif; //
CDCCSInterfaceDescriptor header;
CMFunctionalDescriptor callManagement; // Call Management
ACMFunctionalDescriptor controlManagement; // ACM
CDCCSInterfaceDescriptor functionalDescriptor; // CDC_UNION
EndpointDescriptor cifin;
// Data
InterfaceDescriptor dif;
EndpointDescriptor in;
EndpointDescriptor out;
// kai:added
IADDescriptor iad2; // Only needed on compound device
// Control
InterfaceDescriptor cif2; //
CDCCSInterfaceDescriptor header2;
CMFunctionalDescriptor callManagement2; // Call Management
ACMFunctionalDescriptor controlManagement2; // ACM
CDCCSInterfaceDescriptor functionalDescriptor2; // CDC_UNION
EndpointDescriptor cifin2;
// Data
InterfaceDescriptor dif2;
EndpointDescriptor in2;
EndpointDescriptor out2;
} CDCDescriptor;
typedef struct
{
InterfaceDescriptor msc;
EndpointDescriptor in;
EndpointDescriptor out;
} MSCDescriptor;
#define D_DEVICE(_class,_subClass,_proto,_packetSize0,_vid,_pid,_version,_im,_ip,_is,_configs) \
{ 18, 1, USB_VERSION, _class,_subClass,_proto,_packetSize0,_vid,_pid,_version,_im,_ip,_is,_configs }
#define D_CONFIG(_totalLength,_interfaces) \
{ 9, 2, _totalLength,_interfaces, 1, 0, USB_CONFIG_BUS_POWERED | USB_CONFIG_REMOTE_WAKEUP, USB_CONFIG_POWER_MA(500) }
#define D_INTERFACE(_n,_numEndpoints,_class,_subClass,_protocol) \
{ 9, 4, _n, 0, _numEndpoints, _class,_subClass, _protocol, 0 }
#define D_ENDPOINT(_addr,_attr,_packetSize, _interval) \
{ 7, 5, _addr,_attr,_packetSize, _interval }
#define D_IAD(_firstInterface, _count, _class, _subClass, _protocol) \
{ 8, 11, _firstInterface, _count, _class, _subClass, _protocol, 0 }
#define D_CDCCS(_subtype,_d0,_d1) { 5, 0x24, _subtype, _d0, _d1 }
#define D_CDCCS4(_subtype,_d0) { 4, 0x24, _subtype, _d0 }
// Bootloader related fields
// Old Caterina bootloader places the MAGIC key into unsafe RAM locations (it can be rewritten
// by the running sketch before to actual reboot).
// Newer bootloaders, recognizable by the LUFA "signature" at the end of the flash, can handle both
// the usafe and the safe location.
#ifndef MAGIC_KEY
#define MAGIC_KEY 0x7777
#endif
#ifndef MAGIC_KEY_POS
#define MAGIC_KEY_POS 0x0800
#endif
#ifndef NEW_LUFA_SIGNATURE
#define NEW_LUFA_SIGNATURE 0xDCFB
#endif
#endif

View File

@ -0,0 +1,77 @@
/*
Copyright (c) 2011, Peter Barrett
Copyright (c) 2015, Arduino LLC
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
SOFTWARE.
*/
#define PLUGGABLE_USB_ENABLED
#if defined(EPRST6)
#define USB_ENDPOINTS 7 // AtMegaxxU4
#else
#define USB_ENDPOINTS 5 // AtMegaxxU2
#endif
#define ISERIAL_MAX_LEN 20
#define CDC_INTERFACE_COUNT 2
#define CDC_ENPOINT_COUNT 3
// kai:begin
#undef CDC_ACM_INTERFACE
#undef CDC_DATA_INTERFACE
#undef CDC_FIRST_ENDPOINT
#undef CDC_ENDPOINT_ACM
#undef CDC_ENDPOINT_OUT
#undef CDC_ENDPOINT_IN
#undef CDC_RX
#undef CDC_TX
#define CDC_ACM_INTERFACE1 0 // CDC ACM
#define CDC_DATA_INTERFACE1 1 // CDC Data
#define CDC_FIRST_ENDPOINT1 1
#define CDC_ENDPOINT_ACM1 (CDC_FIRST_ENDPOINT1) // CDC First
#define CDC_ENDPOINT_OUT1 (CDC_FIRST_ENDPOINT1+1)
#define CDC_ENDPOINT_IN1 (CDC_FIRST_ENDPOINT1+2)
#define CDC_ACM_INTERFACE2 2 // CDC ACM
#define CDC_DATA_INTERFACE2 3 // CDC Data
#define CDC_FIRST_ENDPOINT2 4
#define CDC_ENDPOINT_ACM2 (CDC_FIRST_ENDPOINT2) // CDC First
#define CDC_ENDPOINT_OUT2 (CDC_FIRST_ENDPOINT2+1)
#define CDC_ENDPOINT_IN2 (CDC_FIRST_ENDPOINT2+2)
// only one of both interfaces is functional:
#define ACTIVE_INTERFACE 1
#if ACTIVE_INTERFACE == 1
# define CDC_CLASS_INTERFACE CDC_ACM_INTERFACE1
# define CDC_RX CDC_ENDPOINT_OUT1
# define CDC_TX CDC_ENDPOINT_IN1
#else
# define CDC_CLASS_INTERFACE CDC_ACM_INTERFACE2
# define CDC_RX CDC_ENDPOINT_OUT2
# define CDC_TX CDC_ENDPOINT_IN2
#endif
#undef USB_VID
#define USB_VID 0x2342
// kai:end
#define INTERFACE_COUNT (MSC_INTERFACE + MSC_INTERFACE_COUNT)
#define IMANUFACTURER 1
#define IPRODUCT 2
#define ISERIAL 3

View File

@ -0,0 +1,64 @@
/*
bridge USB-serial to hardware-serial
for Arduinos based on ATmega32u4 (Leonardo and compatible Pro Micro, Micro)
hardware serial is configured with baud-rate, databits, stopbits, parity as send over USB
see https://github.com/arduino/Arduino/tree/master/hardware/arduino/avr/cores/arduino
-> CDC.cpp|HardwareSerial.cpp for serial implementation details
this sketch is mainly for demonstration / test of CDC communication
performance as real usb-serial bridge would be inacceptable as each byte is send in separate USB packet
*/
uint32_t baud = 9600;
uint8_t databits = 8;
uint8_t stopbits = 1;
uint8_t parity = 0;
void setup() {
Serial.begin(baud); // USB
Serial1.begin(baud, SERIAL_8N1);
}
void loop() {
// show USB connected state
if (Serial) TXLED1;
else TXLED0;
// configure hardware serial
if (Serial.baud() != baud ||
Serial.numbits() != databits ||
Serial.stopbits() != stopbits ||
Serial.paritytype() != parity) {
baud = Serial.baud();
databits = Serial.numbits();
stopbits = Serial.stopbits();
parity = Serial.paritytype();
uint8_t config = 0; // ucsrc register
switch (databits) {
case 5: break;
case 6: config |= 2; break;
case 7: config |= 4; break;
case 8: config |= 6; break;
default: config |= 6;
}
switch (stopbits) {
case 2: config |= 8;
// 1.5 stopbits not supported
}
switch (parity) {
case 1: config |= 0x30; break; // odd
case 2: config |= 0x20; break; // even
// mark, space not supported
}
Serial1.end();
Serial1.begin(baud, config);
}
// bridge
if (Serial.available() > 0)
Serial1.write(Serial.read());
if (Serial1.available() > 0)
Serial.write(Serial1.read());
}

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,43 +0,0 @@
/* Copyright 2012 Google Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
* Project home page: http://code.google.com/p/usb-serial-for-android/
*/
// Sample Arduino sketch for use with usb-serial-for-android.
// Prints an ever-increasing counter, and writes back anything
// it receives.
static int counter = 0;
void setup() {
Serial.begin(115200);
}
void loop() {
Serial.print("Tick #");
Serial.print(counter++, DEC);
Serial.print("\n");
if (Serial.peek() != -1) {
Serial.print("Read: ");
do {
Serial.print((char) Serial.read());
} while (Serial.peek() != -1);
Serial.print("\n");
}
delay(1000);
}

View File

@ -1,14 +1,21 @@
apply plugin: 'com.android.application' plugins {
id 'com.android.application'
}
android { android {
compileSdkVersion 28 compileSdkVersion 35
buildToolsVersion '28.0.3'
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig { defaultConfig {
minSdkVersion 17 minSdkVersion 17
targetSdkVersion 28 targetSdkVersion 35
vectorDrawables.useSupportLibrary = true
testInstrumentationRunner "android.test.InstrumentationTestRunner" missingDimensionStrategy 'device', 'anyDevice'
} }
buildTypes { buildTypes {
@ -16,8 +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.6.1' // later versions have minsdk 21
implementation 'com.google.android.material:material:1.11.0' // later versions have minsdk 19
} }

View File

@ -1,37 +1,32 @@
<?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.examples" xmlns:tools="http://schemas.android.com/tools">
android:versionCode="1"
android:versionName="1.0" >
<uses-feature android:name="android.hardware.usb.host" />
<!-- mipmap/ic_launcher created with Android Studio -> New -> Image Asset using @color/colorPrimary and USB clip art -->
<application <application
android:icon="@drawable/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" >
<activity
android:name="com.hoho.android.usbserial.examples.DeviceListActivity"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen" > android:theme="@style/AppTheme"
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
<!-- for this simple app launchMode=singleTask and singleTop have same effect.
If you would start another activity in the app, e.g. Android Settings
then you should use singleTask, else a new MainActivity would be started
when the settings activity is currently shown -->
<activity
android:name=".MainActivity"
android:launchMode="singleTask"
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.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /> <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter> </intent-filter>
<meta-data <meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" /> android:resource="@xml/device_filter" />
</activity> </activity>
<activity
android:name="com.hoho.android.usbserial.examples.SerialConsoleActivity"
android:label="@string/app_name"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen" >
</activity>
</application> </application>
</manifest> </manifest>

View File

@ -0,0 +1,22 @@
package com.hoho.android.usbserial.examples;
import com.hoho.android.usbserial.driver.FtdiSerialDriver;
import com.hoho.android.usbserial.driver.ProbeTable;
import com.hoho.android.usbserial.driver.UsbSerialProber;
/**
* add devices here, that are not known to DefaultProber
*
* if the App should auto start for these devices, also
* add IDs to app/src/main/res/xml/device_filter.xml
*/
class CustomProber {
static UsbSerialProber getCustomProber() {
ProbeTable customTable = new ProbeTable();
customTable.addProduct(0x1234, 0x0001, FtdiSerialDriver.class); // e.g. device with custom VID+PID
customTable.addProduct(0x1234, 0x0002, FtdiSerialDriver.class); // e.g. device with custom VID+PID
return new UsbSerialProber(customTable);
}
}

View File

@ -1,206 +0,0 @@
/* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
* Project home page: https://github.com/mik3y/usb-serial-for-android
*/
package com.hoho.android.usbserial.examples;
import android.app.Activity;
import android.content.Context;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.TwoLineListItem;
import com.hoho.android.usbserial.driver.UsbSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialPort;
import com.hoho.android.usbserial.driver.UsbSerialProber;
import com.hoho.android.usbserial.util.HexDump;
import java.util.ArrayList;
import java.util.List;
/**
* Shows a {@link ListView} of available USB devices.
*
* @author mike wakerly (opensource@hoho.com)
*/
public class DeviceListActivity extends Activity {
private final String TAG = DeviceListActivity.class.getSimpleName();
private UsbManager mUsbManager;
private ListView mListView;
private TextView mProgressBarTitle;
private ProgressBar mProgressBar;
private static final int MESSAGE_REFRESH = 101;
private static final long REFRESH_TIMEOUT_MILLIS = 5000;
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_REFRESH:
refreshDeviceList();
mHandler.sendEmptyMessageDelayed(MESSAGE_REFRESH, REFRESH_TIMEOUT_MILLIS);
break;
default:
super.handleMessage(msg);
break;
}
}
};
private List<UsbSerialPort> mEntries = new ArrayList<UsbSerialPort>();
private ArrayAdapter<UsbSerialPort> mAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
mListView = (ListView) findViewById(R.id.deviceList);
mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
mProgressBarTitle = (TextView) findViewById(R.id.progressBarTitle);
mAdapter = new ArrayAdapter<UsbSerialPort>(this,
android.R.layout.simple_expandable_list_item_2, mEntries) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final TwoLineListItem row;
if (convertView == null){
final LayoutInflater inflater =
(LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = (TwoLineListItem) inflater.inflate(android.R.layout.simple_list_item_2, null);
} else {
row = (TwoLineListItem) convertView;
}
final UsbSerialPort port = mEntries.get(position);
final UsbSerialDriver driver = port.getDriver();
final UsbDevice device = driver.getDevice();
final String title = String.format("Vendor %s Product %s",
HexDump.toHexString((short) device.getVendorId()),
HexDump.toHexString((short) device.getProductId()));
row.getText1().setText(title);
final String subtitle = driver.getClass().getSimpleName();
row.getText2().setText(subtitle);
return row;
}
};
mListView.setAdapter(mAdapter);
mListView.setOnItemClickListener(new ListView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Log.d(TAG, "Pressed item " + position);
if (position >= mEntries.size()) {
Log.w(TAG, "Illegal position.");
return;
}
final UsbSerialPort port = mEntries.get(position);
showConsoleActivity(port);
}
});
}
@Override
protected void onResume() {
super.onResume();
mHandler.sendEmptyMessage(MESSAGE_REFRESH);
}
@Override
protected void onPause() {
super.onPause();
mHandler.removeMessages(MESSAGE_REFRESH);
}
private void refreshDeviceList() {
showProgressBar();
new AsyncTask<Void, Void, List<UsbSerialPort>>() {
@Override
protected List<UsbSerialPort> doInBackground(Void... params) {
Log.d(TAG, "Refreshing device list ...");
SystemClock.sleep(1000);
final List<UsbSerialDriver> drivers =
UsbSerialProber.getDefaultProber().findAllDrivers(mUsbManager);
final List<UsbSerialPort> result = new ArrayList<UsbSerialPort>();
for (final UsbSerialDriver driver : drivers) {
final List<UsbSerialPort> ports = driver.getPorts();
Log.d(TAG, String.format("+ %s: %s port%s",
driver, Integer.valueOf(ports.size()), ports.size() == 1 ? "" : "s"));
result.addAll(ports);
}
return result;
}
@Override
protected void onPostExecute(List<UsbSerialPort> result) {
mEntries.clear();
mEntries.addAll(result);
mAdapter.notifyDataSetChanged();
mProgressBarTitle.setText(
String.format("%s device(s) found",Integer.valueOf(mEntries.size())));
hideProgressBar();
Log.d(TAG, "Done refreshing, " + mEntries.size() + " entries found.");
}
}.execute((Void) null);
}
private void showProgressBar() {
mProgressBar.setVisibility(View.VISIBLE);
mProgressBarTitle.setText(R.string.refreshing);
}
private void hideProgressBar() {
mProgressBar.setVisibility(View.INVISIBLE);
}
private void showConsoleActivity(UsbSerialPort port) {
SerialConsoleActivity.show(this, port);
}
}

View File

@ -0,0 +1,164 @@
package com.hoho.android.usbserial.examples;
import android.app.AlertDialog;
import android.content.Context;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.ListFragment;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.hoho.android.usbserial.driver.UsbSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialProber;
import java.util.ArrayList;
import java.util.Locale;
public class DevicesFragment extends ListFragment {
static class ListItem {
UsbDevice device;
int port;
UsbSerialDriver driver;
ListItem(UsbDevice device, int port, UsbSerialDriver driver) {
this.device = device;
this.port = port;
this.driver = driver;
}
}
private final ArrayList<ListItem> listItems = new ArrayList<>();
private ArrayAdapter<ListItem> listAdapter;
private int baudRate = 19200;
private boolean withIoManager = true;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
listAdapter = new ArrayAdapter<ListItem>(getActivity(), 0, listItems) {
@NonNull
@Override
public View getView(int position, View view, @NonNull ViewGroup parent) {
ListItem item = listItems.get(position);
if (view == null)
view = getActivity().getLayoutInflater().inflate(R.layout.device_list_item, parent, false);
TextView text1 = view.findViewById(R.id.text1);
TextView text2 = view.findViewById(R.id.text2);
if(item.driver == null)
text1.setText("<no driver>");
else if(item.driver.getPorts().size() == 1)
text1.setText(item.driver.getClass().getSimpleName().replace("SerialDriver",""));
else
text1.setText(item.driver.getClass().getSimpleName().replace("SerialDriver","")+", Port "+item.port);
text2.setText(String.format(Locale.US, "Vendor %04X, Product %04X", item.device.getVendorId(), item.device.getProductId()));
return view;
}
};
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setListAdapter(null);
View header = getActivity().getLayoutInflater().inflate(R.layout.device_list_header, null, false);
getListView().addHeaderView(header, null, false);
setEmptyText("<no USB devices found>");
((TextView) getListView().getEmptyView()).setTextSize(18);
setListAdapter(listAdapter);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_devices, menu);
}
@Override
public void onResume() {
super.onResume();
refresh();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if(id == R.id.refresh) {
refresh();
return true;
} else if (id ==R.id.baud_rate) {
final String[] values = getResources().getStringArray(R.array.baud_rates);
int pos = java.util.Arrays.asList(values).indexOf(String.valueOf(baudRate));
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Baud rate");
builder.setSingleChoiceItems(values, pos, (dialog, which) -> {
baudRate = Integer.parseInt(values[which]);
dialog.dismiss();
});
builder.create().show();
return true;
} else if (id ==R.id.read_mode) {
final String[] values = getResources().getStringArray(R.array.read_modes);
int pos = withIoManager ? 0 : 1; // read_modes[0]=event/io-manager, read_modes[1]=direct
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Read mode");
builder.setSingleChoiceItems(values, pos, (dialog, which) -> {
withIoManager = (which == 0);
dialog.dismiss();
});
builder.create().show();
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
void refresh() {
UsbManager usbManager = (UsbManager) getActivity().getSystemService(Context.USB_SERVICE);
UsbSerialProber usbDefaultProber = UsbSerialProber.getDefaultProber();
UsbSerialProber usbCustomProber = CustomProber.getCustomProber();
listItems.clear();
for(UsbDevice device : usbManager.getDeviceList().values()) {
UsbSerialDriver driver = usbDefaultProber.probeDevice(device);
if(driver == null) {
driver = usbCustomProber.probeDevice(device);
}
if(driver != null) {
for(int port = 0; port < driver.getPorts().size(); port++)
listItems.add(new ListItem(device, port, driver));
} else {
listItems.add(new ListItem(device, 0, null));
}
}
listAdapter.notifyDataSetChanged();
}
@Override
public void onListItemClick(@NonNull ListView l, @NonNull View v, int position, long id) {
ListItem item = listItems.get(position-1);
if(item.driver == null) {
Toast.makeText(getActivity(), "no driver", Toast.LENGTH_SHORT).show();
} else {
Bundle args = new Bundle();
args.putInt("device", item.device.getDeviceId());
args.putInt("port", item.port);
args.putInt("baud", baudRate);
args.putBoolean("withIoManager", withIoManager);
Fragment fragment = new TerminalFragment();
fragment.setArguments(args);
getFragmentManager().beginTransaction().replace(R.id.fragment, fragment, "terminal").addToBackStack(null).commit();
}
}
}

View File

@ -0,0 +1,45 @@
package com.hoho.android.usbserial.examples;
import android.content.Intent;
import android.os.Bundle;
import androidx.fragment.app.FragmentManager;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
public class MainActivity extends AppCompatActivity implements FragmentManager.OnBackStackChangedListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportFragmentManager().addOnBackStackChangedListener(this);
if (savedInstanceState == null)
getSupportFragmentManager().beginTransaction().add(R.id.fragment, new DevicesFragment(), "devices").commit();
else
onBackStackChanged();
}
@Override
public void onBackStackChanged() {
getSupportActionBar().setDisplayHomeAsUpEnabled(getSupportFragmentManager().getBackStackEntryCount()>0);
}
@Override
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
}
@Override
protected void onNewIntent(Intent intent) {
if("android.hardware.usb.action.USB_DEVICE_ATTACHED".equals(intent.getAction())) {
TerminalFragment terminal = (TerminalFragment)getSupportFragmentManager().findFragmentByTag("terminal");
if (terminal != null)
terminal.status("USB device detected");
}
super.onNewIntent(intent);
}
}

View File

@ -1,230 +0,0 @@
/* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
* Project home page: https://github.com/mik3y/usb-serial-for-android
*/
package com.hoho.android.usbserial.examples;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ScrollView;
import android.widget.TextView;
import com.hoho.android.usbserial.driver.UsbSerialPort;
import com.hoho.android.usbserial.util.HexDump;
import com.hoho.android.usbserial.util.SerialInputOutputManager;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Monitors a single {@link UsbSerialPort} instance, showing all data
* received.
*
* @author mike wakerly (opensource@hoho.com)
*/
public class SerialConsoleActivity extends Activity {
private final String TAG = SerialConsoleActivity.class.getSimpleName();
/**
* Driver instance, passed in statically via
* {@link #show(Context, UsbSerialPort)}.
*
* <p/>
* This is a devious hack; it'd be cleaner to re-create the driver using
* arguments passed in with the {@link #startActivity(Intent)} intent. We
* can get away with it because both activities will run in the same
* process, and this is a simple demo.
*/
private static UsbSerialPort sPort = null;
private TextView mTitleTextView;
private TextView mDumpTextView;
private ScrollView mScrollView;
private CheckBox chkDTR;
private CheckBox chkRTS;
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
private SerialInputOutputManager mSerialIoManager;
private final SerialInputOutputManager.Listener mListener =
new SerialInputOutputManager.Listener() {
@Override
public void onRunError(Exception e) {
Log.d(TAG, "Runner stopped.");
}
@Override
public void onNewData(final byte[] data) {
SerialConsoleActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
SerialConsoleActivity.this.updateReceivedData(data);
}
});
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.serial_console);
mTitleTextView = (TextView) findViewById(R.id.demoTitle);
mDumpTextView = (TextView) findViewById(R.id.consoleText);
mScrollView = (ScrollView) findViewById(R.id.demoScroller);
chkDTR = (CheckBox) findViewById(R.id.checkBoxDTR);
chkRTS = (CheckBox) findViewById(R.id.checkBoxRTS);
chkDTR.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
try {
sPort.setDTR(isChecked);
}catch (IOException x){}
}
});
chkRTS.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
try {
sPort.setRTS(isChecked);
}catch (IOException x){}
}
});
}
@Override
protected void onPause() {
super.onPause();
stopIoManager();
if (sPort != null) {
try {
sPort.close();
} catch (IOException e) {
// Ignore.
}
sPort = null;
}
finish();
}
void showStatus(TextView theTextView, String theLabel, boolean theValue){
String msg = theLabel + ": " + (theValue ? "enabled" : "disabled") + "\n";
theTextView.append(msg);
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "Resumed, port=" + sPort);
if (sPort == null) {
mTitleTextView.setText("No serial device.");
} else {
final UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
UsbDeviceConnection connection = usbManager.openDevice(sPort.getDriver().getDevice());
if (connection == null) {
mTitleTextView.setText("Opening device failed");
return;
}
try {
sPort.open(connection);
sPort.setParameters(115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);
showStatus(mDumpTextView, "CD - Carrier Detect", sPort.getCD());
showStatus(mDumpTextView, "CTS - Clear To Send", sPort.getCTS());
showStatus(mDumpTextView, "DSR - Data Set Ready", sPort.getDSR());
showStatus(mDumpTextView, "DTR - Data Terminal Ready", sPort.getDTR());
showStatus(mDumpTextView, "DSR - Data Set Ready", sPort.getDSR());
showStatus(mDumpTextView, "RI - Ring Indicator", sPort.getRI());
showStatus(mDumpTextView, "RTS - Request To Send", sPort.getRTS());
} catch (IOException e) {
Log.e(TAG, "Error setting up device: " + e.getMessage(), e);
mTitleTextView.setText("Error opening device: " + e.getMessage());
try {
sPort.close();
} catch (IOException e2) {
// Ignore.
}
sPort = null;
return;
}
mTitleTextView.setText("Serial device: " + sPort.getClass().getSimpleName());
}
onDeviceStateChange();
}
private void stopIoManager() {
if (mSerialIoManager != null) {
Log.i(TAG, "Stopping io manager ..");
mSerialIoManager.stop();
mSerialIoManager = null;
}
}
private void startIoManager() {
if (sPort != null) {
Log.i(TAG, "Starting io manager ..");
mSerialIoManager = new SerialInputOutputManager(sPort, mListener);
mExecutor.submit(mSerialIoManager);
}
}
private void onDeviceStateChange() {
stopIoManager();
startIoManager();
}
private void updateReceivedData(byte[] data) {
final String message = "Read " + data.length + " bytes: \n"
+ HexDump.dumpHexString(data) + "\n\n";
mDumpTextView.append(message);
mScrollView.smoothScrollTo(0, mDumpTextView.getBottom());
}
/**
* Starts the activity, using the supplied driver instance.
*
* @param context
* @param driver
*/
static void show(Context context, UsbSerialPort port) {
sPort = port;
final Intent intent = new Intent(context, SerialConsoleActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NO_HISTORY);
context.startActivity(intent);
}
}

View File

@ -0,0 +1,409 @@
package com.hoho.android.usbserial.examples;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.method.ScrollingMovementMethod;
import android.text.style.ForegroundColorSpan;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import com.hoho.android.usbserial.driver.UsbSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialPort;
import com.hoho.android.usbserial.driver.UsbSerialProber;
import com.hoho.android.usbserial.util.HexDump;
import com.hoho.android.usbserial.util.SerialInputOutputManager;
import java.io.IOException;
import java.util.Arrays;
import java.util.EnumSet;
public class TerminalFragment extends Fragment implements SerialInputOutputManager.Listener {
private enum UsbPermission { Unknown, Requested, Granted, Denied }
private static final String INTENT_ACTION_GRANT_USB = BuildConfig.APPLICATION_ID + ".GRANT_USB";
private static final int WRITE_WAIT_MILLIS = 2000;
private static final int READ_WAIT_MILLIS = 2000;
private int deviceId, portNum, baudRate;
private boolean withIoManager;
private final BroadcastReceiver broadcastReceiver;
private final Handler mainLooper;
private TextView receiveText;
private ControlLines controlLines;
private SerialInputOutputManager usbIoManager;
private UsbSerialPort usbSerialPort;
private UsbPermission usbPermission = UsbPermission.Unknown;
private boolean connected = false;
public TerminalFragment() {
broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if(INTENT_ACTION_GRANT_USB.equals(intent.getAction())) {
usbPermission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)
? UsbPermission.Granted : UsbPermission.Denied;
connect();
}
}
};
mainLooper = new Handler(Looper.getMainLooper());
}
/*
* Lifecycle
*/
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
setRetainInstance(true);
deviceId = getArguments().getInt("device");
portNum = getArguments().getInt("port");
baudRate = getArguments().getInt("baud");
withIoManager = getArguments().getBoolean("withIoManager");
}
@Override
public void onStart() {
super.onStart();
ContextCompat.registerReceiver(getActivity(), broadcastReceiver, new IntentFilter(INTENT_ACTION_GRANT_USB), ContextCompat.RECEIVER_NOT_EXPORTED);
}
@Override
public void onStop() {
getActivity().unregisterReceiver(broadcastReceiver);
super.onStop();
}
@Override
public void onResume() {
super.onResume();
if(!connected && (usbPermission == UsbPermission.Unknown || usbPermission == UsbPermission.Granted))
mainLooper.post(this::connect);
}
@Override
public void onPause() {
if(connected) {
status("disconnected");
disconnect();
}
super.onPause();
}
/*
* UI
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_terminal, container, false);
receiveText = view.findViewById(R.id.receive_text); // TextView performance decreases with number of spans
receiveText.setTextColor(getResources().getColor(R.color.colorRecieveText)); // set as default color to reduce number of spans
receiveText.setMovementMethod(ScrollingMovementMethod.getInstance());
TextView sendText = view.findViewById(R.id.send_text);
View sendBtn = view.findViewById(R.id.send_btn);
sendBtn.setOnClickListener(v -> send(sendText.getText().toString()));
View receiveBtn = view.findViewById(R.id.receive_btn);
controlLines = new ControlLines(view);
if(withIoManager) {
receiveBtn.setVisibility(View.GONE);
} else {
receiveBtn.setOnClickListener(v -> read());
}
return view;
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_terminal, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.clear) {
receiveText.setText("");
return true;
} else if( id == R.id.send_break) {
if(!connected) {
Toast.makeText(getActivity(), "not connected", Toast.LENGTH_SHORT).show();
} else {
try {
usbSerialPort.setBreak(true);
Thread.sleep(100); // should show progress bar instead of blocking UI thread
usbSerialPort.setBreak(false);
SpannableStringBuilder spn = new SpannableStringBuilder();
spn.append("send <break>\n");
spn.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorSendText)), 0, spn.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
receiveText.append(spn);
} catch(UnsupportedOperationException ignored) {
Toast.makeText(getActivity(), "BREAK not supported", Toast.LENGTH_SHORT).show();
} catch(Exception e) {
Toast.makeText(getActivity(), "BREAK failed: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
/*
* Serial
*/
@Override
public void onNewData(byte[] data) {
mainLooper.post(() -> {
receive(data);
});
}
@Override
public void onRunError(Exception e) {
mainLooper.post(() -> {
status("connection lost: " + e.getMessage());
disconnect();
});
}
/*
* Serial + UI
*/
private void connect() {
UsbDevice device = null;
UsbManager usbManager = (UsbManager) getActivity().getSystemService(Context.USB_SERVICE);
for(UsbDevice v : usbManager.getDeviceList().values())
if(v.getDeviceId() == deviceId)
device = v;
if(device == null) {
status("connection failed: device not found");
return;
}
UsbSerialDriver driver = UsbSerialProber.getDefaultProber().probeDevice(device);
if(driver == null) {
driver = CustomProber.getCustomProber().probeDevice(device);
}
if(driver == null) {
status("connection failed: no driver for device");
return;
}
if(portNum >= driver.getPorts().size()) {
status("connection failed: not enough ports at device");
return;
}
usbSerialPort = driver.getPorts().get(portNum);
UsbDeviceConnection usbConnection = usbManager.openDevice(driver.getDevice());
if(usbConnection == null && usbPermission == UsbPermission.Unknown && !usbManager.hasPermission(driver.getDevice())) {
usbPermission = UsbPermission.Requested;
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_MUTABLE : 0;
Intent intent = new Intent(INTENT_ACTION_GRANT_USB);
intent.setPackage(getActivity().getPackageName());
PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(getActivity(), 0, intent, flags);
usbManager.requestPermission(driver.getDevice(), usbPermissionIntent);
return;
}
if(usbConnection == null) {
if (!usbManager.hasPermission(driver.getDevice()))
status("connection failed: permission denied");
else
status("connection failed: open failed");
return;
}
try {
usbSerialPort.open(usbConnection);
try{
usbSerialPort.setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE);
}catch (UnsupportedOperationException e){
status("unsupport setparameters");
}
if(withIoManager) {
usbIoManager = new SerialInputOutputManager(usbSerialPort, this);
usbIoManager.start();
}
status("connected");
connected = true;
controlLines.start();
} catch (Exception e) {
status("connection failed: " + e.getMessage());
disconnect();
}
}
private void disconnect() {
connected = false;
controlLines.stop();
if(usbIoManager != null) {
usbIoManager.setListener(null);
usbIoManager.stop();
}
usbIoManager = null;
try {
usbSerialPort.close();
} catch (IOException ignored) {}
usbSerialPort = null;
}
private void send(String str) {
if(!connected) {
Toast.makeText(getActivity(), "not connected", Toast.LENGTH_SHORT).show();
return;
}
try {
byte[] data = (str + '\n').getBytes();
SpannableStringBuilder spn = new SpannableStringBuilder();
spn.append("send " + data.length + " bytes\n");
spn.append(HexDump.dumpHexString(data)).append("\n");
spn.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorSendText)), 0, spn.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
receiveText.append(spn);
usbSerialPort.write(data, WRITE_WAIT_MILLIS);
} catch (Exception e) {
onRunError(e);
}
}
private void read() {
if(!connected) {
Toast.makeText(getActivity(), "not connected", Toast.LENGTH_SHORT).show();
return;
}
try {
byte[] buffer = new byte[8192];
int len = usbSerialPort.read(buffer, READ_WAIT_MILLIS);
receive(Arrays.copyOf(buffer, len));
} catch (IOException e) {
// when using read with timeout, USB bulkTransfer returns -1 on timeout _and_ errors
// like connection loss, so there is typically no exception thrown here on error
status("connection lost: " + e.getMessage());
disconnect();
}
}
private void receive(byte[] data) {
SpannableStringBuilder spn = new SpannableStringBuilder();
spn.append("receive " + data.length + " bytes\n");
if(data.length > 0)
spn.append(HexDump.dumpHexString(data)).append("\n");
receiveText.append(spn);
}
void status(String str) {
SpannableStringBuilder spn = new SpannableStringBuilder(str+'\n');
spn.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorStatusText)), 0, spn.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
receiveText.append(spn);
}
class ControlLines {
private static final int refreshInterval = 200; // msec
private final Runnable runnable;
private final ToggleButton rtsBtn, ctsBtn, dtrBtn, dsrBtn, cdBtn, riBtn;
ControlLines(View view) {
runnable = this::run; // w/o explicit Runnable, a new lambda would be created on each postDelayed, which would not be found again by removeCallbacks
rtsBtn = view.findViewById(R.id.controlLineRts);
ctsBtn = view.findViewById(R.id.controlLineCts);
dtrBtn = view.findViewById(R.id.controlLineDtr);
dsrBtn = view.findViewById(R.id.controlLineDsr);
cdBtn = view.findViewById(R.id.controlLineCd);
riBtn = view.findViewById(R.id.controlLineRi);
rtsBtn.setOnClickListener(this::toggle);
dtrBtn.setOnClickListener(this::toggle);
}
private void toggle(View v) {
ToggleButton btn = (ToggleButton) v;
if (!connected) {
btn.setChecked(!btn.isChecked());
Toast.makeText(getActivity(), "not connected", Toast.LENGTH_SHORT).show();
return;
}
String ctrl = "";
try {
if (btn.equals(rtsBtn)) { ctrl = "RTS"; usbSerialPort.setRTS(btn.isChecked()); }
if (btn.equals(dtrBtn)) { ctrl = "DTR"; usbSerialPort.setDTR(btn.isChecked()); }
} catch (IOException e) {
status("set" + ctrl + "() failed: " + e.getMessage());
}
}
private void run() {
if (!connected)
return;
try {
EnumSet<UsbSerialPort.ControlLine> controlLines = usbSerialPort.getControlLines();
rtsBtn.setChecked(controlLines.contains(UsbSerialPort.ControlLine.RTS));
ctsBtn.setChecked(controlLines.contains(UsbSerialPort.ControlLine.CTS));
dtrBtn.setChecked(controlLines.contains(UsbSerialPort.ControlLine.DTR));
dsrBtn.setChecked(controlLines.contains(UsbSerialPort.ControlLine.DSR));
cdBtn.setChecked(controlLines.contains(UsbSerialPort.ControlLine.CD));
riBtn.setChecked(controlLines.contains(UsbSerialPort.ControlLine.RI));
mainLooper.postDelayed(runnable, refreshInterval);
} catch (Exception e) {
status("getControlLines() failed: " + e.getMessage() + " -> stopped control line refresh");
}
}
void start() {
if (!connected)
return;
try {
EnumSet<UsbSerialPort.ControlLine> controlLines = usbSerialPort.getSupportedControlLines();
if (!controlLines.contains(UsbSerialPort.ControlLine.RTS)) rtsBtn.setVisibility(View.INVISIBLE);
if (!controlLines.contains(UsbSerialPort.ControlLine.CTS)) ctsBtn.setVisibility(View.INVISIBLE);
if (!controlLines.contains(UsbSerialPort.ControlLine.DTR)) dtrBtn.setVisibility(View.INVISIBLE);
if (!controlLines.contains(UsbSerialPort.ControlLine.DSR)) dsrBtn.setVisibility(View.INVISIBLE);
if (!controlLines.contains(UsbSerialPort.ControlLine.CD)) cdBtn.setVisibility(View.INVISIBLE);
if (!controlLines.contains(UsbSerialPort.ControlLine.RI)) riBtn.setVisibility(View.INVISIBLE);
run();
} catch (Exception e) {
Toast.makeText(getActivity(), "getSupportedControlLines() failed: " + e.getMessage(), Toast.LENGTH_SHORT).show();
rtsBtn.setVisibility(View.INVISIBLE);
ctsBtn.setVisibility(View.INVISIBLE);
dtrBtn.setVisibility(View.INVISIBLE);
dsrBtn.setVisibility(View.INVISIBLE);
cdBtn.setVisibility(View.INVISIBLE);
cdBtn.setVisibility(View.INVISIBLE);
riBtn.setVisibility(View.INVISIBLE);
}
}
void stop() {
mainLooper.removeCallbacks(runnable);
rtsBtn.setChecked(false);
ctsBtn.setChecked(false);
dtrBtn.setChecked(false);
dsrBtn.setChecked(false);
cdBtn.setChecked(false);
riBtn.setChecked(false);
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
</vector>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary" />
</com.google.android.material.appbar.AppBarLayout>
<RelativeLayout
android:id="@+id/fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/listDivider"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp"
android:gravity="center"
android:text="@string/devices"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
</LinearLayout>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/text1"
android:layout_marginTop="12dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<TextView
android:id="@+id/text2"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="12dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</LinearLayout>

View File

@ -0,0 +1,128 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ToggleButton
android:id="@+id/controlLineRts"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="48sp"
android:textOff="RTS"
android:textOn="RTS" />
<ToggleButton
android:id="@+id/controlLineCts"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="48sp"
android:clickable="false"
android:textColor="@android:color/secondary_text_dark"
android:textOff="CTS"
android:textOn="CTS" />
<View
android:layout_height="match_parent"
android:layout_width="6dp" />
<ToggleButton
android:id="@+id/controlLineDtr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="48sp"
android:textOff="DTR"
android:textOn="DTR" />
<ToggleButton
android:id="@+id/controlLineDsr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:minWidth="48sp"
android:textColor="@android:color/secondary_text_dark"
android:textOff="DSR"
android:textOn="DSR" />
<View
android:layout_height="match_parent"
android:layout_width="6dp" />
<ToggleButton
android:id="@+id/controlLineCd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:minWidth="48sp"
android:textColor="@android:color/secondary_text_dark"
android:textOff="CD"
android:textOn="CD" />
<ToggleButton
android:id="@+id/controlLineRi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="48sp"
android:clickable="false"
android:textColor="@android:color/secondary_text_dark"
android:textOff="RI"
android:textOn="RI" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:background="?android:attr/listDivider"
android:layout_height="2dp" />
<TextView
android:id="@+id/receive_text"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:freezesText="true"
android:gravity="bottom"
android:scrollbars="vertical"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:typeface="monospace" />
<Button
android:id="@+id/receive_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Read" />
<View
android:layout_width="match_parent"
android:background="?android:attr/listDivider"
android:layout_height="2dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/send_text"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:inputType="text|textNoSuggestions"
android:singleLine="true" />
<ImageButton
android:id="@+id/send_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_send_white_24dp" />
</LinearLayout>
</LinearLayout>

View File

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="@+id/demoTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:text="@string/app_title"
android:textSize="24sp"
android:textStyle="bold" />
<TextView
android:id="@+id/progressBarTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/demoTitle"
android:layout_centerHorizontal="true"
android:text="@string/refreshing"
android:padding="8dp"
android:textSize="18sp" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/progressBarTitle"
android:layout_centerHorizontal="true"
android:padding="8dp"
style="@android:style/Widget.Holo.ProgressBar.Horizontal"
android:indeterminate="true" />
<View
android:id="@+id/separator"
android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_below="@+id/progressBar"
android:layout_centerHorizontal="true"
android:background="#eeeeee" />
<ListView
android:id="@+id/deviceList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/separator" />
</RelativeLayout>

View File

@ -1,54 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="@+id/demoTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:text="@string/app_title"
android:textSize="24sp"
android:textStyle="bold" />
<View
android:id="@+id/separator"
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="#eeeeee" />
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/textBtnDTR"
android:id="@+id/checkBoxDTR" />
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/textBtnRTS"
android:id="@+id/checkBoxRTS" />
<View
android:id="@+id/separator2"
android:layout_width="match_parent"
android:layout_below="@+id/demoTitle"
android:layout_height="1dip"
android:background="#eeeeee" />
<ScrollView
android:id="@+id/demoScroller"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/consoleText"
android:textIsSelectable="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:typeface="monospace" />
</ScrollView>
</LinearLayout>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/refresh"
android:title="Refresh devices" />
<item
android:id="@+id/baud_rate"
android:title="Baud rate" />
<item
android:id="@+id/read_mode"
android:title="Read mode" />
</menu>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/clear"
android:icon="@drawable/ic_delete_white_24dp"
android:title="Clear"
app:showAsAction="always" />
<item
android:id="@+id/send_break"
android:title="Send BREAK"
app:showAsAction="never" />
</menu>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 895 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="baud_rates">
<item>2400</item>
<item>9600</item>
<item>19200</item>
<item>57600</item>
<item>115200</item>
</string-array>
<string-array name="read_modes">
<item>event: SerialInputOutputManager.onNewData()</item>
<item>direct: UsbSerialPort.read()</item>
</string-array>
</resources>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#949C29</color>
<color name="colorPrimaryDark">#61671B</color>
<color name="colorAccent">#D8E33B</color>
<color name="colorRecieveText">#00FF00</color>
<color name="colorSendText">#82CAFF</color>
<color name="colorStatusText">#FFDB58</color>
</resources>

View File

@ -1,10 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_title">USB Serial For Android Example</string>
<string name="app_title">USB Serial Example</string> <string name="app_name">USB Serial Example</string>
<string name="app_name">Serial Example</string> <string name="devices">USB Devices</string>
<string name="refreshing">Refreshing...</string>
<string name="textBtnRTS">RTS - Request To Send</string>
<string name="textBtnDTR">DTR - Data Terminal Ready</string>
</resources> </resources>

View File

@ -0,0 +1,7 @@
<resources>
<style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>

View File

@ -1,23 +1,38 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- 0x0403 / 0x6001: FTDI FT232R UART --> <!-- 0x0403 / 0x60??: FTDI -->
<usb-device vendor-id="1027" product-id="24577" /> <usb-device vendor-id="1027" product-id="24577" /> <!-- 0x6001: FT232R -->
<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="24596" /> <!-- 0x6014: FT232H -->
<usb-device vendor-id="1027" product-id="24597" /> <!-- 0x6015: FT230X, FT231X, FT234XD -->
<!-- 0x0403 / 0x6015: FTDI FT231X --> <!-- 0x10C4 / 0xEA??: Silabs CP210x -->
<usb-device vendor-id="1027" product-id="24597" /> <usb-device vendor-id="4292" product-id="60000" /> <!-- 0xea60: CP2102 and other CP210x single port devices -->
<usb-device vendor-id="4292" product-id="60016" /> <!-- 0xea70: CP2105 -->
<usb-device vendor-id="4292" product-id="60017" /> <!-- 0xea71: CP2108 -->
<!-- 0x2341 / Arduino --> <!-- 0x067B / 0x23?3: Prolific PL2303x -->
<usb-device vendor-id="9025" /> <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 -->
<!-- 0x16C0 / 0x0483: Teensyduino --> <!-- 0x1a86 / 0x?523: Qinheng CH34x -->
<usb-device vendor-id="5824" product-id="1155" /> <usb-device vendor-id="6790" product-id="21795" /> <!-- 0x5523: CH341A -->
<usb-device vendor-id="6790" product-id="29987" /> <!-- 0x7523: CH340 -->
<!-- 0x10C4 / 0xEA60: CP210x UART Bridge --> <!-- CDC driver -->
<usb-device vendor-id="4292" product-id="60000" /> <usb-device vendor-id="9025" /> <!-- 0x2341 / ......: Arduino -->
<usb-device vendor-id="5824" product-id="1155" /> <!-- 0x16C0 / 0x0483: Teensyduino -->
<!-- 0x067B / 0x2303: Prolific PL2303 --> <usb-device vendor-id="1003" product-id="8260" /> <!-- 0x03EB / 0x2044: Atmel Lufa -->
<usb-device vendor-id="1659" product-id="8963" /> <usb-device vendor-id="7855" product-id="4" /> <!-- 0x1eaf / 0x0004: Leaflabs Maple -->
<usb-device vendor-id="3368" product-id="516" /> <!-- 0x0d28 / 0x0204: ARM mbed -->
<!-- 0x1a86 / 0x7523: Qinheng CH340 --> <usb-device vendor-id="1155" product-id="22336" /><!-- 0x0483 / 0x5740: ST CDC -->
<usb-device vendor-id="6790" product-id="29987" /> <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,32 +1,60 @@
apply plugin: 'com.android.library' plugins {
id 'com.android.library'
id 'maven-publish'
}
android { android {
compileSdkVersion 28 compileSdkVersion 35
buildToolsVersion '28.0.3'
defaultConfig { defaultConfig {
minSdkVersion 17 minSdkVersion 17
targetSdkVersion 28 targetSdkVersion 35
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" consumerProguardFiles 'proguard-rules.pro'
}
buildTypes { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
release { testInstrumentationRunnerArguments = [ // Raspi Windows LinuxVM ...
minifyEnabled false 'rfc2217_server_host': '192.168.0.78',
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 'rfc2217_server_nonstandard_baudrates': 'true', // true false false
]
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
namespace 'com.hoho.android.usbserial'
publishing {
// if coverage is enabled, change 'release' to 'anyDeviceRelease' or comment out publishing rule
singleVariant('release') {
withSourcesJar()
} }
} }
} }
dependencies { dependencies {
testImplementation 'junit:junit:4.12' 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' 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'
} }
// to build a .jar file use gradle task 'createFullJarRelease' // gradle task: publishToMavenLocal
project.afterEvaluate {
publishing {
publications {
release(MavenPublication) {
from components.release
// to deploy into local maven repository use gradle task 'publishToMavenLocal' and enable: // values used for local maven repo, jitpack uses github release:
//apply from: 'publishToMavenLocal.gradle' groupId 'com.github.mik3y'
artifactId 'usb-serial-for-android'
version '3.8.0beta'
}
}
}
}
//apply from: 'coverage.gradle'

View File

@ -0,0 +1,78 @@
// see https://github.com/mik3y/usb-serial-for-android/wiki/Device-Tests-&-Coverage-Report for instructions
apply plugin: 'jacoco'
android {
flavorDimensions += 'device'
productFlavors {
anyDevice {
// Used as fallback in usbSerialExample/build.gradle -> missingDimensionStrategy, but not for coverage report
dimension 'device'
}
mcp2221 {
dimension 'device'
testInstrumentationRunnerArguments = ['test_device_driver': 'CdcAcm']
}
ch340 {
dimension 'device'
testInstrumentationRunnerArguments = ['test_device_driver': 'Ch34x']
}
cp2102 { // and cp2105 first port
dimension 'device'
testInstrumentationRunnerArguments = ['test_device_driver': 'Cp21xx', 'test_device_port': '0']
}
cp2105 { // second port
dimension 'device'
testInstrumentationRunnerArguments = ['test_device_driver': 'Cp21xx', 'test_device_port': '1']
}
ft232 { // and ft2232 first port
dimension 'device'
testInstrumentationRunnerArguments = ['test_device_driver': 'Ftdi', 'test_device_port': '0']
}
ft2232 { // second port
dimension 'device'
testInstrumentationRunnerArguments = ['test_device_driver': 'Ftdi', 'test_device_port': '1']
}
pl2303 {
dimension 'device'
testInstrumentationRunnerArguments = ['test_device_driver': 'Prolific']
}
pl2303t {
dimension 'device'
testInstrumentationRunnerArguments = ['test_device_driver': 'Prolific']
}
pl2303g {
dimension 'device'
testInstrumentationRunnerArguments = ['test_device_driver': 'Prolific']
}
}
buildTypes {
debug {
enableUnitTestCoverage true
enableAndroidTestCoverage true
}
}
}
// create report even if tests fail
project.gradle.taskGraph.whenReady {
-> project.tasks.findAll { it.name =~ /connected.+AndroidTest/ }.each {
it.ignoreFailures = true
}
}
task jacocoTestReport(type: JacocoReport, dependsOn: ['compileAnyDeviceDebugSources'
/*, 'testAnyDeviceDebugUnitTest' */
/*, 'create<device>DebugCoverageReport' */]) {
def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*']
def debugTree = fileTree(dir: "$project.buildDir/intermediates/javac/anyDeviceDebug", excludes: fileFilter)
def mainSrc = "$project.projectDir/src/main/java"
reports.xml.required = true
sourceDirectories.from files([mainSrc])
classDirectories.from files([debugTree])
executionData.from fileTree(dir: project.buildDir, includes: [
'outputs/unit_test_code_coverage/anyDeviceDebugUnitTest/testAnyDeviceDebugUnitTest.exec',
'outputs/code_coverage/*DebugAndroidTest/connected/*/coverage.ec'
])
}

View File

@ -0,0 +1 @@
-keep class com.hoho.android.usbserial.driver.* { *; }

View File

@ -1,20 +0,0 @@
apply plugin: 'maven-publish'
publishing {
publications {
maven(MavenPublication) {
groupId 'com.github.mik3y'
artifactId 'usb-serial-for-android'
version '1.x.0'
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

@ -0,0 +1,216 @@
/*
* test multiple devices or multiple ports on same device
*
* TxD and RxD have to be cross connected
*/
package com.hoho.android.usbserial;
import android.content.Context;
import android.hardware.usb.UsbManager;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import android.util.Log;
import com.hoho.android.usbserial.driver.UsbSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialPort;
import com.hoho.android.usbserial.driver.UsbSerialProber;
import com.hoho.android.usbserial.util.TestBuffer;
import com.hoho.android.usbserial.util.UsbWrapper;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.runner.RunWith;
import java.io.IOException;
import java.util.EnumSet;
import java.util.List;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
@RunWith(AndroidJUnit4.class)
public class CrossoverTest {
private final static String TAG = CrossoverTest.class.getSimpleName();
private Context context;
private UsbManager usbManager;
private UsbWrapper usb1, usb2;
@Rule
public TestRule watcher = new TestWatcher() {
protected void starting(Description description) {
Log.i(TAG, "===== starting test: " + description.getMethodName()+ " =====");
}
};
@Before
public void setUp() throws Exception {
assumeTrue("ignore test for device specific coverage report",
InstrumentationRegistry.getArguments().getString("test_device_driver") == null);
context = ApplicationProvider.getApplicationContext();
usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
List<UsbSerialDriver> availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(usbManager);
assertNotEquals("no USB device found", 0, availableDrivers.size());
if (availableDrivers.size() == 0) {
fail("no USB device found");
} else if (availableDrivers.size() == 1) {
assertEquals("expected device with 2 ports.", 2, availableDrivers.get(0).getPorts().size());
usb1 = new UsbWrapper(context, availableDrivers.get(0), 0);
usb2 = new UsbWrapper(context, availableDrivers.get(0), 1);
} else {
assertEquals("expected 2 devices with 1 port.", 1, availableDrivers.get(0).getPorts().size());
assertEquals("expected 2 devices with 1 port.", 1, availableDrivers.get(1).getPorts().size());
usb1 = new UsbWrapper(context, availableDrivers.get(0), 0);
usb2 = new UsbWrapper(context, availableDrivers.get(1), 0);
}
usb1.setUp();
usb2.setUp();
}
@Test
public void reopen() throws Exception {
byte[] buf;
usb1.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_THREAD));
usb2.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_THREAD));
usb1.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usb2.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usb1.write("x".getBytes());
buf = usb2.read(1);
assertThat(buf, equalTo("x".getBytes()));
usb2.write("y".getBytes());
buf = usb1.read(1);
assertThat(buf, equalTo("y".getBytes()));
usb2.close(); // does not affect usb1 with individual UsbDeviceConnection on same device
usb2.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_THREAD));
usb2.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usb1.write("x".getBytes());
buf = usb2.read(1);
assertThat(buf, equalTo("x".getBytes()));
usb2.write("y".getBytes());
buf = usb1.read(1);
assertThat(buf, equalTo("y".getBytes()));
usb1.close();
usb2.close();
}
@Test
public void ioManager() throws Exception {
byte[] buf;
// each SerialInputOutputManager thread runs in it's own SingleThreadExecutor
usb1.open();
usb2.open();
usb1.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usb2.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usb1.write("x".getBytes());
buf = usb2.read(1);
assertThat(buf, equalTo("x".getBytes()));
usb2.write("y".getBytes());
buf = usb1.read(1);
assertThat(buf, equalTo("y".getBytes()));
usb1.close();
usb2.close();
}
@Test
public void baudRate() throws Exception {
byte[] buf;
usb1.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_THREAD));
usb2.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_THREAD));
usb1.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usb2.setParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE);
// a - start bit (0)
// o - stop bit (1)
// 0/1 - data bit
// out 19k2: a00011001o
// in 9k6: a 0 1 0 1 1 1 1 1 o
usb1.write(new byte[]{(byte)0x98});
buf = usb2.read(1);
assertThat(buf, equalTo(new byte[]{(byte)0xfa}));
// out 9k6: a 1 0 1 1 1 1 1 1 o
// in 19k2: a01100111o
usb2.write(new byte[]{(byte)0xfd});
buf = usb1.read(1);
assertThat(buf, equalTo(new byte[]{(byte)0xe6}));
usb1.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

@ -0,0 +1,152 @@
package com.hoho.android.usbserial.util;
import com.hoho.android.usbserial.driver.UsbSerialPort;
import org.apache.commons.net.telnet.InvalidTelnetOptionException;
import org.apache.commons.net.telnet.TelnetClient;
import org.apache.commons.net.telnet.TelnetCommand;
import org.apache.commons.net.telnet.TelnetOptionHandler;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import static org.junit.Assert.assertEquals;
public class TelnetWrapper {
private final static String TAG = TelnetWrapper.class.getSimpleName();
private final static int TELNET_READ_WAIT = 500;
private final static int TELNET_COMMAND_WAIT = 2000;
private final static byte RFC2217_COM_PORT_OPTION = 0x2c;
private final static byte RFC2217_SET_BAUDRATE = 1;
private final static byte RFC2217_SET_DATASIZE = 2;
private final static byte RFC2217_SET_PARITY = 3;
private final static byte RFC2217_SET_STOPSIZE = 4;
private final static byte RFC2217_PURGE_DATA = 12;
private final String host;
private final int port;
private TelnetClient telnetClient;
private InputStream readStream;
private OutputStream writeStream;
private ArrayList<int[]> commandResponse = new ArrayList<>();
public int writeDelay = 0;
public TelnetWrapper(String host, int port) {
this.host = host;
this.port = port;
telnetClient = null;
}
private void setUpFixtureInt() throws Exception {
if(telnetClient != null)
return;
telnetClient = new TelnetClient();
telnetClient.addOptionHandler(new TelnetOptionHandler(RFC2217_COM_PORT_OPTION, false, false, false, false) {
@Override
public int[] answerSubnegotiation(int[] suboptionData, int suboptionLength) {
int[] data = new int[suboptionLength];
System.arraycopy(suboptionData, 0, data, 0, suboptionLength);
commandResponse.add(data);
return super.answerSubnegotiation(suboptionData, suboptionLength);
}
});
telnetClient.setConnectTimeout(2000);
telnetClient.connect(host, port);
telnetClient.setTcpNoDelay(true);
writeStream = telnetClient.getOutputStream();
readStream = telnetClient.getInputStream();
}
private int[] doCommand(String name, byte[] command) throws IOException, InterruptedException {
commandResponse.clear();
telnetClient.sendCommand((byte) TelnetCommand.SB);
writeStream.write(command);
telnetClient.sendCommand((byte)TelnetCommand.SE);
for(int i=0; i<TELNET_COMMAND_WAIT; i++) {
if(commandResponse.size() > 0) break;
Thread.sleep(1);
}
assertEquals("RFC2217 " + name+ " w/o response.", 1, commandResponse.size());
//Log.d(TAG, name + " -> " + Arrays.toString(commandResponse.get(0)));
return commandResponse.get(0);
}
public void setUp() throws Exception {
setUpFixtureInt();
telnetClient.sendAYT(1000); // not correctly handled by rfc2217_server.py, but WARNING output "ignoring Telnet command: '\xf6'" is a nice separator between tests
doCommand("purge-data", new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_PURGE_DATA, 3});
writeDelay = 0;
}
public void tearDown() {
try {
read(0);
} catch (Exception ignored) {
}
}
public void tearDownFixture() throws Exception {
try {
telnetClient.disconnect();
} catch (Exception ignored) {}
readStream = null;
writeStream = null;
telnetClient = null;
}
// wait full time
public byte[] read() throws Exception {
return read(-1, -1);
}
public byte[] read(int expectedLength) throws Exception {
return read(expectedLength, -1);
}
public byte[] read(int expectedLength, int readWait) throws Exception {
if(readWait == -1)
readWait = TELNET_READ_WAIT;
long end = System.currentTimeMillis() + readWait;
ByteBuffer buf = ByteBuffer.allocate(65536);
while(System.currentTimeMillis() < end) {
if(readStream.available() > 0) {
buf.put((byte) readStream.read());
} else {
if (expectedLength >= 0 && buf.position() >= expectedLength)
break;
Thread.sleep(1);
}
}
byte[] data = new byte[buf.position()];
buf.flip();
buf.get(data);
return data;
}
public void write(byte[] data) throws Exception{
if(writeDelay != 0) {
for(byte b : data) {
writeStream.write(b);
writeStream.flush();
Thread.sleep(writeDelay);
}
} else {
writeStream.write(data);
writeStream.flush();
}
}
public void setParameters(int baudRate, int dataBits, int stopBits, @UsbSerialPort.Parity int parity) throws IOException, InterruptedException, InvalidTelnetOptionException {
doCommand("set-baudrate", new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_BAUDRATE, (byte)(baudRate>>24), (byte)(baudRate>>16), (byte)(baudRate>>8), (byte)baudRate});
doCommand("set-datasize", new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_DATASIZE, (byte)dataBits});
doCommand("set-stopsize", new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_STOPSIZE, (byte)stopBits});
doCommand("set-parity", new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_PARITY, (byte)(parity+1)});
}
}

View File

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

View File

@ -0,0 +1,390 @@
package com.hoho.android.usbserial.util;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import com.hoho.android.usbserial.driver.CdcAcmSerialDriver;
import com.hoho.android.usbserial.driver.Ch34xSerialDriver;
import com.hoho.android.usbserial.driver.CommonUsbSerialPort;
import com.hoho.android.usbserial.driver.Cp21xxSerialDriver;
import com.hoho.android.usbserial.driver.FtdiSerialDriver;
import com.hoho.android.usbserial.driver.ProlificSerialDriver;
import com.hoho.android.usbserial.driver.ProlificSerialPortWrapper;
import com.hoho.android.usbserial.driver.UsbId;
import com.hoho.android.usbserial.driver.UsbSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialPort;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Deque;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.concurrent.Callable;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import androidx.core.content.ContextCompat;
public class UsbWrapper implements SerialInputOutputManager.Listener {
public final static int USB_READ_WAIT = 500;
public final static int USB_WRITE_WAIT = 500;
private static final String TAG = UsbWrapper.class.getSimpleName();
public enum OpenCloseFlags { NO_IOMANAGER_THREAD, NO_IOMANAGER_START, NO_CONTROL_LINE_INIT, NO_DEVICE_CONNECTION };
// constructor
final Context context;
public final UsbSerialDriver serialDriver;
public final int devicePort;
public final UsbSerialPort serialPort;
// open
public UsbDeviceConnection deviceConnection;
public SerialInputOutputManager ioManager;
// read
final Deque<byte[]> readBuffer = new LinkedList<>();
Exception readError;
public boolean readBlock = false;
long readTime = 0;
// device properties
public boolean isCp21xxRestrictedPort; // second port of Cp2105 has limited dataBits, stopBits, parity
public boolean outputLinesSupported;
public boolean inputLinesSupported;
public boolean inputLinesConnected;
public boolean inputLinesOnlyRtsCts;
public int writePacketSize = -1;
public int writeBufferSize = -1;
public int readBufferSize = -1;
public UsbWrapper(Context context, UsbSerialDriver serialDriver, int devicePort) {
this.context = context;
this.serialDriver = serialDriver;
this.devicePort = devicePort;
serialPort = serialDriver.getPorts().get(devicePort);
CommonUsbSerialPort.DEBUG = true;
}
public void setUp() throws Exception {
UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
if (!usbManager.hasPermission(serialDriver.getDevice())) {
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
RingtoneManager.getRingtone(context, notification).play();
Log.d(TAG,"USB permission ...");
final Boolean[] granted = {null};
BroadcastReceiver usbReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
granted[0] = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
}
};
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");
ContextCompat.registerReceiver(context, usbReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
usbManager.requestPermission(serialDriver.getDevice(), permissionIntent);
for(int i=0; i<5000; i++) {
if(granted[0] != null) break;
Thread.sleep(1);
}
Log.d(TAG,"USB permission "+granted[0]);
assertTrue("USB permission dialog not confirmed", granted[0] != null && 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() {
try {
if (ioManager != null)
read(0);
else
serialPort.purgeHwBuffers(true, true);
} catch (Exception ignored) {
}
close();
//usb.serialDriver = null;
}
public void close() {
close(EnumSet.noneOf(OpenCloseFlags.class));
}
public void close(EnumSet<OpenCloseFlags> flags) {
if (ioManager != null) {
ioManager.setListener(null);
ioManager.stop();
}
if (serialPort != null) {
try {
if(!flags.contains(OpenCloseFlags.NO_CONTROL_LINE_INIT)) {
serialPort.setDTR(false);
serialPort.setRTS(false);
if (serialPort.getFlowControl() != UsbSerialPort.FlowControl.NONE)
serialPort.setFlowControl(UsbSerialPort.FlowControl.NONE);
}
} catch (Exception ignored) {
}
try {
serialPort.close();
} catch (Exception ignored) {
}
//usbSerialPort = null;
}
if(!flags.contains(OpenCloseFlags.NO_DEVICE_CONNECTION)) {
deviceConnection = null; // closed in usbSerialPort.close()
}
if(ioManager != null) {
for (int i = 0; i < 2000; i++) {
if (SerialInputOutputManager.State.STOPPED == ioManager.getState()) break;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
assertEquals(SerialInputOutputManager.State.STOPPED, ioManager.getState());
ioManager = null;
}
}
public void open() throws Exception {
open(EnumSet.noneOf(OpenCloseFlags.class));
}
public void open(EnumSet<OpenCloseFlags> flags) throws Exception {
if(!flags.contains(OpenCloseFlags.NO_DEVICE_CONNECTION)) {
UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
deviceConnection = usbManager.openDevice(serialDriver.getDevice());
}
serialPort.open(deviceConnection);
if(!flags.contains(OpenCloseFlags.NO_CONTROL_LINE_INIT)) {
serialPort.setDTR(true);
serialPort.setRTS(true);
}
if(!flags.contains(OpenCloseFlags.NO_IOMANAGER_THREAD)) {
ioManager = new SerialInputOutputManager(serialPort, this);
if(!flags.contains(OpenCloseFlags.NO_IOMANAGER_START))
ioManager.start();
}
synchronized (readBuffer) {
readBuffer.clear();
}
readError = null;
if (serialDriver instanceof ProlificSerialDriver && ProlificSerialPortWrapper.isDeviceTypeHxn(serialPort)) {
readBufferSize = 768;
}
}
public void waitForIoManagerStarted() throws IOException {
for (int i = 0; i < 100; i++) {
if (SerialInputOutputManager.State.STOPPED != ioManager.getState()) return;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
throw new IOException("IoManager not started");
}
public boolean hasIoManagerThreads() {
int c = 0;
for (Thread thread : Thread.getAllStackTraces().keySet()) {
if (thread.getName().equals(SerialInputOutputManager.class.getSimpleName() + "_read"))
c += 1;
if (thread.getName().equals(SerialInputOutputManager.class.getSimpleName() + "_write"))
c += 1;
}
return c == 2;
}
// wait full time
public byte[] read() throws Exception {
return read(-1, -1, -1);
}
public byte[] read(int expectedLength) throws Exception {
return read(expectedLength, -1, -1);
}
public byte[] read(int expectedLength, int readBufferSize) throws Exception {
return read(expectedLength, readBufferSize, -1);
}
public byte[] read(int expectedLength, int readBufferSize, int readWait) throws Exception {
if(readWait == -1)
readWait = USB_READ_WAIT;
long end = System.currentTimeMillis() + readWait;
ByteBuffer buf = ByteBuffer.allocate(16*1024);
if(ioManager != null) {
while (System.currentTimeMillis() < end) {
if(readError != null)
throw new IOException(readError);
synchronized (readBuffer) {
while(readBuffer.peek() != null)
buf.put(readBuffer.remove());
}
if (expectedLength >= 0 && buf.position() >= expectedLength)
break;
Thread.sleep(1);
}
} else {
byte[] b1 = new byte[readBufferSize > 0 ? readBufferSize : 256];
while (System.currentTimeMillis() < end) {
int len = serialPort.read(b1, USB_READ_WAIT);
if (len > 0)
buf.put(b1, 0, len);
if (expectedLength >= 0 && buf.position() >= expectedLength)
break;
}
}
byte[] data = new byte[buf.position()];
buf.flip();
buf.get(data);
return data;
}
public void write(byte[] data) throws IOException {
serialPort.write(data, USB_WRITE_WAIT);
}
public void setParameters(int baudRate, int dataBits, int stopBits, @UsbSerialPort.Parity int parity) throws IOException, InterruptedException {
serialPort.setParameters(baudRate, dataBits, stopBits, parity);
if(serialDriver instanceof CdcAcmSerialDriver)
Thread.sleep(10); // arduino_leonardeo_bridge.ini needs some time
else
Thread.sleep(1);
}
/* return TRUE/FALSE/null instead of true/false/<throw UnsupportedOperationException> */
public Boolean getControlLine(Callable<?> callable) throws Exception {
try {
return (Boolean)callable.call();
} catch (UnsupportedOperationException t) {
return null;
}
}
// return [write packet size, write buffer size(s)]
public int[] getWriteSizes() {
if (serialDriver instanceof Cp21xxSerialDriver) {
if (serialDriver.getPorts().size() == 1) return new int[]{64, 576};
else if (serialPort.getPortNumber() == 0) return new int[]{64, 320};
else return new int[]{32, 128, 160}; // write buffer size detection is unreliable
} else if (serialDriver instanceof Ch34xSerialDriver) {
return new int[]{32, 64};
} else if (serialDriver instanceof ProlificSerialDriver) {
return new int[]{64, 256};
} else if (serialDriver instanceof FtdiSerialDriver) {
switch (serialDriver.getPorts().size()) {
case 1: return new int[]{64, 128};
case 2: return new int[]{512, 4096};
case 4: return new int[]{512, 2048};
default: return null;
}
} else if (serialDriver instanceof CdcAcmSerialDriver) {
return new int[]{64, 128};
} else {
return null;
}
}
@Override
public void onNewData(byte[] data) {
long now = System.currentTimeMillis();
if(readTime == 0)
readTime = now;
if(data.length > 64) {
Log.d(TAG, "usb " + devicePort + " read: time+=" + String.format("%-3d",now- readTime) + " len=" + String.format("%-4d",data.length) + " data=" + new String(data, 0, 32) + "..." + new String(data, data.length-32, 32));
} else {
Log.d(TAG, "usb " + devicePort + " read: time+=" + String.format("%-3d",now- readTime) + " len=" + String.format("%-4d",data.length) + " data=" + new String(data));
}
readTime = now;
while(readBlock)
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (readBuffer) {
readBuffer.add(data);
}
}
@Override
public void onRunError(Exception e) {
readError = e;
}
}

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

@ -1,21 +1,6 @@
/* Copyright 2011-2013 Google Inc. /* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com> * Copyright 2013 mike wakerly <opensource@hoho.com>
* *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
* Project home page: https://github.com/mik3y/usb-serial-for-android * Project home page: https://github.com/mik3y/usb-serial-for-android
*/ */
@ -23,15 +8,16 @@ 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.hardware.usb.UsbRequest;
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.nio.ByteBuffer; import java.util.ArrayList;
import java.util.Collections; import java.util.EnumSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -46,14 +32,41 @@ 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;
private final UsbSerialPort mPort; private final List<UsbSerialPort> mPorts;
public CdcAcmSerialDriver(UsbDevice device) { public CdcAcmSerialDriver(UsbDevice device) {
mDevice = device; mDevice = device;
mPort = new CdcAcmSerialPort(device, 0); mPorts = new ArrayList<>();
int ports = countPorts(device);
for (int port = 0; port < ports; port++) {
mPorts.add(new CdcAcmSerialPort(mDevice, port));
}
if (mPorts.size() == 0) {
mPorts.add(new CdcAcmSerialPort(mDevice, -1));
}
}
@SuppressWarnings({"unused"})
public static boolean probe(UsbDevice device) {
return countPorts(device) > 0;
}
private static int countPorts(UsbDevice device) {
int controlInterfaceCount = 0;
int dataInterfaceCount = 0;
for (int i = 0; i < device.getInterfaceCount(); i++) {
if (device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_COMM &&
device.getInterface(i).getInterfaceSubclass() == USB_SUBCLASS_ACM)
controlInterfaceCount++;
if (device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA)
dataInterfaceCount++;
}
return Math.min(controlInterfaceCount, dataInterfaceCount);
} }
@Override @Override
@ -63,17 +76,15 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
@Override @Override
public List<UsbSerialPort> getPorts() { public List<UsbSerialPort> getPorts() {
return Collections.singletonList(mPort); return mPorts;
} }
class CdcAcmSerialPort extends CommonUsbSerialPort { public class CdcAcmSerialPort extends CommonUsbSerialPort {
private UsbInterface mControlInterface; private UsbInterface mControlInterface;
private UsbInterface mDataInterface; private UsbInterface mDataInterface;
private UsbEndpoint mControlEndpoint; private UsbEndpoint mControlEndpoint;
private UsbEndpoint mReadEndpoint;
private UsbEndpoint mWriteEndpoint;
private int mControlIndex; private int mControlIndex;
@ -98,135 +109,109 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
} }
@Override @Override
public void open(UsbDeviceConnection connection) throws IOException { protected void openInt() throws IOException {
if (mConnection != null) { Log.d(TAG, "interfaces:");
throw new IOException("Already open"); for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
Log.d(TAG, mDevice.getInterface(i).toString());
} }
if (mPortNumber == -1) {
mConnection = connection;
boolean opened = false;
try {
if (1 == mDevice.getInterfaceCount()) {
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();
} else { } else {
Log.d(TAG,"trying default interface logic"); Log.d(TAG,"trying default interface logic");
openInterface(); openInterface();
} }
opened = true;
} finally {
if (!opened) {
mConnection = null;
// just to be on the save side
mControlEndpoint = null;
mReadEndpoint = null;
mWriteEndpoint = null;
}
}
} }
private void openSingleInterface() throws IOException { private void openSingleInterface() throws IOException {
// the following code is inspired by the cdc-acm driver // the following code is inspired by the cdc-acm driver in the linux kernel
// in the linux kernel
mControlIndex = 0; mControlIndex = 0;
mControlInterface = mDevice.getInterface(0); mControlInterface = mDevice.getInterface(0);
Log.d(TAG, "Control iface=" + mControlInterface);
mDataInterface = mDevice.getInterface(0); mDataInterface = mDevice.getInterface(0);
Log.d(TAG, "data iface=" + mDataInterface);
if (!mConnection.claimInterface(mControlInterface, true)) { if (!mConnection.claimInterface(mControlInterface, true)) {
throw new IOException("Could not claim shared control/data interface."); throw new IOException("Could not claim shared control/data interface");
} }
int endCount = mControlInterface.getEndpointCount(); for (int i = 0; i < mControlInterface.getEndpointCount(); ++i) {
if (endCount < 3) {
Log.d(TAG,"not enough endpoints - need 3. count=" + mControlInterface.getEndpointCount());
throw new IOException("Insufficient number of endpoints(" + mControlInterface.getEndpointCount() + ")");
}
// Analyse endpoints for their properties
mControlEndpoint = null;
mReadEndpoint = null;
mWriteEndpoint = null;
for (int i = 0; i < endCount; ++i) {
UsbEndpoint ep = mControlInterface.getEndpoint(i); UsbEndpoint ep = mControlInterface.getEndpoint(i);
if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_INT)) {
(ep.getType() == UsbConstants.USB_ENDPOINT_XFER_INT)) {
Log.d(TAG,"Found controlling endpoint");
mControlEndpoint = ep; mControlEndpoint = ep;
} else if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && } else if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) {
(ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) {
Log.d(TAG,"Found reading endpoint");
mReadEndpoint = ep; mReadEndpoint = ep;
} else if ((ep.getDirection() == UsbConstants.USB_DIR_OUT) && } else if ((ep.getDirection() == UsbConstants.USB_DIR_OUT) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) {
(ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) {
Log.d(TAG,"Found writing endpoint");
mWriteEndpoint = ep; mWriteEndpoint = ep;
} }
if ((mControlEndpoint != null) &&
(mReadEndpoint != null) &&
(mWriteEndpoint != null)) {
Log.d(TAG,"Found all required endpoints");
break;
} }
} if (mControlEndpoint == null) {
throw new IOException("No control endpoint");
if ((mControlEndpoint == null) ||
(mReadEndpoint == null) ||
(mWriteEndpoint == null)) {
Log.d(TAG,"Could not establish all endpoints");
throw new IOException("Could not establish all endpoints");
} }
} }
private void openInterface() throws IOException { private void openInterface() throws IOException {
Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount());
mControlInterface = null; mControlInterface = null;
mDataInterface = null; mDataInterface = null;
int j = getInterfaceIdFromDescriptors();
Log.d(TAG, "interface count=" + mDevice.getInterfaceCount() + ", IAD=" + j);
if (j >= 0) {
for (int i = 0; i < mDevice.getInterfaceCount(); i++) { for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
UsbInterface usbInterface = mDevice.getInterface(i); UsbInterface usbInterface = mDevice.getInterface(i);
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM) { if (usbInterface.getId() == j || usbInterface.getId() == j+1) {
mControlIndex = i; if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM &&
usbInterface.getInterfaceSubclass() == USB_SUBCLASS_ACM) {
mControlIndex = usbInterface.getId();
mControlInterface = usbInterface; mControlInterface = usbInterface;
} }
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) { if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) {
mDataInterface = usbInterface; mDataInterface = usbInterface;
} }
} }
}
}
if (mControlInterface == null || mDataInterface == null) {
Log.d(TAG, "no IAD fallback");
int controlInterfaceCount = 0;
int dataInterfaceCount = 0;
for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
UsbInterface usbInterface = mDevice.getInterface(i);
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM &&
usbInterface.getInterfaceSubclass() == USB_SUBCLASS_ACM) {
if (controlInterfaceCount == mPortNumber) {
mControlIndex = usbInterface.getId();
mControlInterface = usbInterface;
}
controlInterfaceCount++;
}
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) {
if (dataInterfaceCount == mPortNumber) {
mDataInterface = usbInterface;
}
dataInterfaceCount++;
}
}
}
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");
} }
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");
} }
mReadEndpoint = null;
mWriteEndpoint = null;
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)
@ -234,99 +219,69 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
if (ep.getDirection() == UsbConstants.USB_DIR_OUT && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) if (ep.getDirection() == UsbConstants.USB_DIR_OUT && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)
mWriteEndpoint = ep; mWriteEndpoint = ep;
} }
if (mReadEndpoint == null || mWriteEndpoint == null) {
throw new IOException("Could not get read&write endpoints.");
} }
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);
if(len < 0) { if(len < 0) {
throw new IOException("controlTransfer failed."); throw new IOException("controlTransfer failed");
} }
return len; return len;
} }
@Override @Override
public void close() throws IOException { protected void closeInt() {
if (mConnection == null) {
throw new IOException("Already closed");
}
mConnection.close();
mConnection = null;
}
@Override
public int read(byte[] dest, int timeoutMillis) throws IOException {
final UsbRequest request = new UsbRequest();
try { try {
request.initialize(mConnection, mReadEndpoint); mConnection.releaseInterface(mControlInterface);
final ByteBuffer buf = ByteBuffer.wrap(dest); mConnection.releaseInterface(mDataInterface);
if (!request.queue(buf, dest.length)) { } catch(Exception ignored) {}
throw new IOException("Error queueing request.");
}
final UsbRequest response = mConnection.requestWait();
if (response == null) {
throw new IOException("Null response");
}
final int nread = buf.position();
if (nread > 0) {
//Log.d(TAG, HexDump.dumpHexString(dest, 0, Math.min(32, dest.length)));
return nread;
} else {
return 0;
}
} finally {
request.close();
}
} }
@Override @Override
public int write(byte[] src, int timeoutMillis) throws IOException { public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException {
// TODO(mikey): Nearly identical to FtdiSerial write. Refactor. if(baudRate <= 0) {
int offset = 0; throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
while (offset < src.length) {
final int writeLength;
final int amtWritten;
synchronized (mWriteBufferLock) {
final byte[] writeBuffer;
writeLength = Math.min(src.length - offset, mWriteBuffer.length);
if (offset == 0) {
writeBuffer = src;
} else {
// bulkTransfer does not support offsets, make a copy.
System.arraycopy(src, offset, mWriteBuffer, 0, writeLength);
writeBuffer = mWriteBuffer;
} }
if(dataBits < DATABITS_5 || dataBits > DATABITS_8) {
amtWritten = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, writeLength, throw new IllegalArgumentException("Invalid data bits: " + dataBits);
timeoutMillis);
} }
if (amtWritten <= 0) {
throw new IOException("Error writing " + writeLength
+ " bytes at offset " + offset + " length=" + src.length);
}
Log.d(TAG, "Wrote amt=" + amtWritten + " attempted=" + writeLength);
offset += amtWritten;
}
return offset;
}
@Override
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException {
byte stopBitsByte; byte stopBitsByte;
switch (stopBits) { switch (stopBits) {
case STOPBITS_1: stopBitsByte = 0; break; case STOPBITS_1: stopBitsByte = 0; break;
case STOPBITS_1_5: stopBitsByte = 1; break; case STOPBITS_1_5: stopBitsByte = 1; break;
case STOPBITS_2: stopBitsByte = 2; break; case STOPBITS_2: stopBitsByte = 2; break;
default: throw new IllegalArgumentException("Bad value for stopBits: " + stopBits); default: throw new IllegalArgumentException("Invalid stop bits: " + stopBits);
} }
byte parityBitesByte; byte parityBitesByte;
@ -336,9 +291,8 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
case PARITY_EVEN: parityBitesByte = 2; break; case PARITY_EVEN: parityBitesByte = 2; break;
case PARITY_MARK: parityBitesByte = 3; break; case PARITY_MARK: parityBitesByte = 3; break;
case PARITY_SPACE: parityBitesByte = 4; break; case PARITY_SPACE: parityBitesByte = 4; break;
default: throw new IllegalArgumentException("Bad value for parity: " + parity); default: throw new IllegalArgumentException("Invalid parity: " + parity);
} }
byte[] msg = { byte[] msg = {
(byte) ( baudRate & 0xff), (byte) ( baudRate & 0xff),
(byte) ((baudRate >> 8 ) & 0xff), (byte) ((baudRate >> 8 ) & 0xff),
@ -350,21 +304,6 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
sendAcmControlMessage(SET_LINE_CODING, 0, msg); sendAcmControlMessage(SET_LINE_CODING, 0, msg);
} }
@Override
public boolean getCD() throws IOException {
return false; // TODO
}
@Override
public boolean getCTS() throws IOException {
return false; // TODO
}
@Override
public boolean getDSR() throws IOException {
return false; // TODO
}
@Override @Override
public boolean getDTR() throws IOException { public boolean getDTR() throws IOException {
return mDtr; return mDtr;
@ -376,11 +315,6 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
setDtrRts(); setDtrRts();
} }
@Override
public boolean getRI() throws IOException {
return false; // TODO
}
@Override @Override
public boolean getRTS() throws IOException { public boolean getRTS() throws IOException {
return mRts; return mRts;
@ -397,40 +331,29 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
sendAcmControlMessage(SET_CONTROL_LINE_STATE, value, null); sendAcmControlMessage(SET_CONTROL_LINE_STATE, value, null);
} }
@Override
public EnumSet<ControlLine> getControlLines() throws IOException {
EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class);
if(mRts) set.add(ControlLine.RTS);
if(mDtr) set.add(ControlLine.DTR);
return set;
} }
@Override
public EnumSet<ControlLine> getSupportedControlLines() throws IOException {
return EnumSet.of(ControlLine.RTS, ControlLine.DTR);
}
@Override
public void setBreak(boolean value) throws IOException {
sendAcmControlMessage(SEND_BREAK, value ? 0xffff : 0, null);
}
}
@SuppressWarnings({"unused"})
public static Map<Integer, int[]> getSupportedDevices() { public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<Integer, int[]>(); return new LinkedHashMap<>();
supportedDevices.put(Integer.valueOf(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(Integer.valueOf(UsbId.VENDOR_VAN_OOIJEN_TECH),
new int[] {
UsbId.VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL,
});
supportedDevices.put(Integer.valueOf(UsbId.VENDOR_ATMEL),
new int[] {
UsbId.ATMEL_LUFA_CDC_DEMO_APP,
});
supportedDevices.put(Integer.valueOf(UsbId.VENDOR_LEAFLABS),
new int[] {
UsbId.LEAFLABS_MAPLE,
});
supportedDevices.put(Integer.valueOf(UsbId.VENDOR_ARM),
new int[] {
UsbId.ARM_MBED,
});
return supportedDevices;
} }
} }

View File

@ -1,19 +1,4 @@
/* Copyright 2014 Andreas Butti /* Copyright 2014 Andreas Butti
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
* *
* Project home page: https://github.com/mik3y/usb-serial-for-android * Project home page: https://github.com/mik3y/usb-serial-for-android
*/ */
@ -22,25 +7,19 @@ 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.hardware.usb.UsbRequest;
import android.util.Log; import android.util.Log;
import com.hoho.android.usbserial.BuildConfig;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
/**
* Driver for CH340, maybe also working with CH341, but not tested
* See http://wch-ic.com/product/usb/ch340.asp
*
* @author Andreas Butti
*/
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();
@ -59,6 +38,13 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
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_DSR = 0x02;
private static final int GCL_RI = 0x04;
private static final int GCL_CD = 0x08;
private static final int SCL_DTR = 0x20;
private static final int SCL_RTS = 0x40;
public Ch34xSerialDriver(UsbDevice device) { public Ch34xSerialDriver(UsbDevice device) {
mDevice = device; mDevice = device;
mPort = new Ch340SerialPort(mDevice, 0); mPort = new Ch340SerialPort(mDevice, 0);
@ -83,10 +69,6 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
private boolean dtr = false; private boolean dtr = false;
private boolean rts = false; private boolean rts = false;
private UsbEndpoint mReadEndpoint;
private UsbEndpoint mWriteEndpoint;
private UsbRequest mUsbRequest;
public Ch340SerialPort(UsbDevice device, int portNumber) { public Ch340SerialPort(UsbDevice device, int portNumber) {
super(device, portNumber); super(device, portNumber);
} }
@ -97,18 +79,11 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
} }
@Override @Override
public void open(UsbDeviceConnection connection) throws IOException { protected void openInt() throws IOException {
if (mConnection != null) {
throw new IOException("Already opened.");
}
mConnection = connection;
boolean opened = false;
try {
for (int i = 0; i < mDevice.getInterfaceCount(); i++) { for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
UsbInterface usbIface = mDevice.getInterface(i); UsbInterface usbIface = mDevice.getInterface(i);
if (!mConnection.claimInterface(usbIface, true)) { if (!mConnection.claimInterface(usbIface, true)) {
throw new IOException("Could not claim data interface."); throw new IOException("Could not claim data interface");
} }
} }
@ -126,111 +101,26 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
initialize(); initialize();
setBaudRate(DEFAULT_BAUD_RATE); setBaudRate(DEFAULT_BAUD_RATE);
opened = true;
} finally {
if (!opened) {
try {
close();
} catch (IOException e) {
// Ignore IOExceptions during close()
}
}
}
} }
@Override @Override
public void close() throws IOException { protected void closeInt() {
if (mConnection == null) {
throw new IOException("Already closed");
}
synchronized (this) {
if (mUsbRequest != null)
mUsbRequest.cancel();
}
try { try {
mConnection.close(); for (int i = 0; i < mDevice.getInterfaceCount(); i++)
} finally { mConnection.releaseInterface(mDevice.getInterface(i));
mConnection = null; } catch(Exception ignored) {}
}
}
@Override
public int read(byte[] dest, int timeoutMillis) throws IOException {
final UsbRequest request = new UsbRequest();
try {
request.initialize(mConnection, mReadEndpoint);
final ByteBuffer buf = ByteBuffer.wrap(dest);
if (!request.queue(buf, dest.length)) {
throw new IOException("Error queueing request.");
}
mUsbRequest = request;
final UsbRequest response = mConnection.requestWait();
synchronized (this) {
mUsbRequest = null;
}
if (response == null) {
throw new IOException("Null response");
}
final int nread = buf.position();
if (nread > 0) {
//Log.d(TAG, HexDump.dumpHexString(dest, 0, Math.min(32, dest.length)));
return nread;
} else {
return 0;
}
} finally {
mUsbRequest = null;
request.close();
}
}
@Override
public int write(byte[] src, int timeoutMillis) throws IOException {
int offset = 0;
while (offset < src.length) {
final int writeLength;
final int amtWritten;
synchronized (mWriteBufferLock) {
final byte[] writeBuffer;
writeLength = Math.min(src.length - offset, mWriteBuffer.length);
if (offset == 0) {
writeBuffer = src;
} else {
// bulkTransfer does not support offsets, make a copy.
System.arraycopy(src, offset, mWriteBuffer, 0, writeLength);
writeBuffer = mWriteBuffer;
}
amtWritten = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, writeLength,
timeoutMillis);
}
if (amtWritten <= 0) {
throw new IOException("Error writing " + writeLength
+ " bytes at offset " + offset + " length=" + src.length);
}
Log.d(TAG, "Wrote amt=" + amtWritten + " attempted=" + writeLength);
offset += amtWritten;
}
return offset;
} }
private int controlOut(int request, int value, int index) { private int controlOut(int request, int value, int index) {
final int REQTYPE_HOST_TO_DEVICE = 0x41; 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_HOST_TO_DEVICE = 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_HOST_TO_DEVICE, 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);
} }
@ -240,7 +130,7 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
int ret = controlIn(request, value, 0, buffer); int ret = controlIn(request, value, 0, buffer);
if (ret < 0) { if (ret < 0) {
throw new IOException("Faild send cmd [" + msg + "]"); throw new IOException("Failed send cmd [" + msg + "]");
} }
if (ret != expected.length) { if (ret != expected.length) {
@ -254,22 +144,30 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
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]) + " bytes, 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 writeHandshakeByte() throws IOException { private void setControlLines() throws IOException {
if (controlOut(0xa4, ~((dtr ? 1 << 5 : 0) | (rts ? 1 << 6 : 0)), 0) < 0) { if (controlOut(0xa4, ~((dtr ? SCL_DTR : 0) | (rts ? SCL_RTS : 0)), 0) < 0) {
throw new IOException("Faild to set handshake byte"); throw new IOException("Failed to set control lines");
} }
} }
private byte getStatus() throws IOException {
byte[] buffer = new byte[2];
int ret = controlIn(0x95, 0x0706, 0, buffer);
if (ret < 0)
throw new IOException("Error getting control lines");
return buffer[0];
}
private 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);
@ -277,55 +175,67 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
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[]{0xff, 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);
writeHandshakeByte(); setControlLines();
checkState("init #10", 0x95, 0x0706, new int[]{-1/* 0x9f, 0xff*/, 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;
if(BuildConfig.DEBUG && (baudRate & (3<<29)) == (1<<29))
baudRate &= ~(1<<29); // for testing purpose bypass dedicated baud rate handling
factor = BAUDBASE_FACTOR / baudRate;
divisor = BAUDBASE_DIVMAX;
while ((factor > 0xfff0) && divisor > 0) { while ((factor > 0xfff0) && divisor > 0) {
factor >>= 3; factor >>= 3;
divisor--; divisor--;
} }
if (factor > 0xfff0) { if (factor > 0xfff0) {
throw new IOException("Baudrate " + baudRate + " not supported"); throw new UnsupportedOperationException("Unsupported baud rate: " + baudRate);
} }
factor = 0x10000 - factor; factor = 0x10000 - factor;
int ret = controlOut(0x9a, 0x1312, (int) ((factor & 0xff00) | divisor));
if (ret < 0) {
throw new IOException("Error setting baud rate. #1)");
} }
ret = controlOut(0x9a, 0x0f2c, (int) (factor & 0xff)); divisor |= 0x0080; // else ch341a waits until buffer full
int val1 = (int) ((factor & 0xff00) | divisor);
int val2 = (int) (factor & 0xff);
Log.d(TAG, String.format("baud rate=%d, 0x1312=0x%04x, 0x0f2c=0x%04x", baudRate, val1, val2));
int ret = controlOut(0x9a, 0x1312, val1);
if (ret < 0) { if (ret < 0) {
throw new IOException("Error setting baud rate. #2"); throw new IOException("Error setting baud rate: #1)");
}
ret = controlOut(0x9a, 0x0f2c, val2);
if (ret < 0) {
throw new IOException("Error setting baud rate: #2");
} }
} }
@Override @Override
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException {
throws IOException { if(baudRate <= 0) {
throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
}
setBaudRate(baudRate); setBaudRate(baudRate);
int lcr = LCR_ENABLE_RX | LCR_ENABLE_TX; int lcr = LCR_ENABLE_RX | LCR_ENABLE_TX;
@ -344,7 +254,7 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
lcr |= LCR_CS8; lcr |= LCR_CS8;
break; break;
default: default:
throw new IllegalArgumentException("Unknown dataBits value: " + dataBits); throw new IllegalArgumentException("Invalid data bits: " + dataBits);
} }
switch (parity) { switch (parity) {
@ -363,19 +273,19 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE | LCR_PAR_EVEN; lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE | LCR_PAR_EVEN;
break; break;
default: default:
throw new IllegalArgumentException("Unknown parity value: " + parity); throw new IllegalArgumentException("Invalid parity: " + parity);
} }
switch (stopBits) { switch (stopBits) {
case STOPBITS_1: case STOPBITS_1:
break; break;
case STOPBITS_1_5: case STOPBITS_1_5:
throw new IllegalArgumentException("Unsupported stopBits value: 1.5"); throw new UnsupportedOperationException("Unsupported stop bits: 1.5");
case STOPBITS_2: case STOPBITS_2:
lcr |= LCR_STOP_BITS_2; lcr |= LCR_STOP_BITS_2;
break; break;
default: default:
throw new IllegalArgumentException("Unknown stopBits value: " + stopBits); throw new IllegalArgumentException("Invalid stop bits: " + stopBits);
} }
int ret = controlOut(0x9a, 0x2518, lcr); int ret = controlOut(0x9a, 0x2518, lcr);
@ -386,17 +296,17 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
@Override @Override
public boolean getCD() throws IOException { public boolean getCD() throws IOException {
return false; return (getStatus() & GCL_CD) == 0;
} }
@Override @Override
public boolean getCTS() throws IOException { public boolean getCTS() throws IOException {
return false; return (getStatus() & GCL_CTS) == 0;
} }
@Override @Override
public boolean getDSR() throws IOException { public boolean getDSR() throws IOException {
return false; return (getStatus() & GCL_DSR) == 0;
} }
@Override @Override
@ -407,12 +317,12 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
@Override @Override
public void setDTR(boolean value) throws IOException { public void setDTR(boolean value) throws IOException {
dtr = value; dtr = value;
writeHandshakeByte(); setControlLines();
} }
@Override @Override
public boolean getRI() throws IOException { public boolean getRI() throws IOException {
return false; return (getStatus() & GCL_RI) == 0;
} }
@Override @Override
@ -423,20 +333,53 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
@Override @Override
public void setRTS(boolean value) throws IOException { public void setRTS(boolean value) throws IOException {
rts = value; rts = value;
writeHandshakeByte(); setControlLines();
} }
@Override @Override
public boolean purgeHwBuffers(boolean purgeReadBuffers, boolean purgeWriteBuffers) throws IOException { public EnumSet<ControlLine> getControlLines() throws IOException {
return true; int status = getStatus();
EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class);
if(rts) set.add(ControlLine.RTS);
if((status & GCL_CTS) == 0) set.add(ControlLine.CTS);
if(dtr) set.add(ControlLine.DTR);
if((status & GCL_DSR) == 0) set.add(ControlLine.DSR);
if((status & GCL_CD) == 0) set.add(ControlLine.CD);
if((status & GCL_RI) == 0) set.add(ControlLine.RI);
return set;
} }
@Override
public EnumSet<ControlLine> getSupportedControlLines() throws IOException {
return EnumSet.allOf(ControlLine.class);
} }
@Override
public void setBreak(boolean value) throws IOException {
byte[] req = new byte[2];
if(controlIn(0x95, 0x1805, 0, req) < 0) {
throw new IOException("Error getting BREAK condition");
}
if(value) {
req[0] &= ~1;
req[1] &= ~0x40;
} else {
req[0] |= 1;
req[1] |= 0x40;
}
int val = (req[1] & 0xff) << 8 | (req[0] & 0xff);
if(controlOut(0x9a, 0x1805, val) < 0) {
throw new IOException("Error setting BREAK condition");
}
}
}
@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_QINHENG, new int[]{ supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{
UsbId.QINHENG_HL340 UsbId.QINHENG_CH340,
UsbId.QINHENG_CH341A,
}); });
return supportedDevices; 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

@ -1,21 +1,6 @@
/* Copyright 2011-2013 Google Inc. /* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com> * Copyright 2013 mike wakerly <opensource@hoho.com>
* *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
* Project home page: https://github.com/mik3y/usb-serial-for-android * Project home page: https://github.com/mik3y/usb-serial-for-android
*/ */
@ -23,40 +8,51 @@ 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.UsbRequest;
import android.os.Build;
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.util.EnumSet;
/** /**
* A base class shared by several driver implementations. * A base class shared by several driver implementations.
* *
* @author mike wakerly (opensource@hoho.com) * @author mike wakerly (opensource@hoho.com)
*/ */
abstract class CommonUsbSerialPort implements UsbSerialPort { public abstract class CommonUsbSerialPort implements UsbSerialPort {
public static final int DEFAULT_READ_BUFFER_SIZE = 16 * 1024; public static boolean DEBUG = false;
public static final int DEFAULT_WRITE_BUFFER_SIZE = 16 * 1024;
private static final String TAG = CommonUsbSerialPort.class.getSimpleName();
private static final int MAX_READ_SIZE = 16 * 1024; // = old bulkTransfer limit prior to Android 9
protected final UsbDevice mDevice; protected final UsbDevice mDevice;
protected final int mPortNumber; protected final int mPortNumber;
// non-null when open() // non-null when open()
protected UsbDeviceConnection mConnection = null; protected UsbDeviceConnection mConnection;
protected UsbEndpoint mReadEndpoint;
protected UsbEndpoint mWriteEndpoint;
protected UsbRequest mUsbRequest;
protected FlowControl mFlowControl = FlowControl.NONE;
protected final Object mReadBufferLock = new Object(); /**
* Internal write buffer.
* Guarded by {@link #mWriteBufferLock}.
* Default length = mReadEndpoint.getMaxPacketSize()
**/
protected byte[] mWriteBuffer;
protected final Object mWriteBufferLock = new Object(); protected final Object mWriteBufferLock = new Object();
/** Internal read buffer. Guarded by {@link #mReadBufferLock}. */
protected byte[] mReadBuffer;
/** Internal write buffer. Guarded by {@link #mWriteBufferLock}. */
protected byte[] mWriteBuffer;
public CommonUsbSerialPort(UsbDevice device, int portNumber) { public CommonUsbSerialPort(UsbDevice device, int portNumber) {
mDevice = device; mDevice = device;
mPortNumber = portNumber; mPortNumber = portNumber;
mReadBuffer = new byte[DEFAULT_READ_BUFFER_SIZE];
mWriteBuffer = new byte[DEFAULT_WRITE_BUFFER_SIZE];
} }
@Override @Override
@ -66,12 +62,8 @@ abstract class CommonUsbSerialPort implements UsbSerialPort {
mDevice.getDeviceId(), mPortNumber); mDevice.getDeviceId(), mPortNumber);
} }
/** @Override
* Returns the currently-bound USB device. public UsbDevice getDevice() {
*
* @return the device
*/
public final UsbDevice getDevice() {
return mDevice; return mDevice;
} }
@ -80,6 +72,12 @@ abstract class CommonUsbSerialPort implements UsbSerialPort {
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
@ -89,30 +87,23 @@ abstract class CommonUsbSerialPort implements UsbSerialPort {
return mConnection.getSerial(); return mConnection.getSerial();
} }
/**
* Sets the size of the internal buffer used to exchange data with the USB
* stack for read operations. Most users should not need to change this.
*
* @param bufferSize the size in bytes
*/
public final void setReadBufferSize(int bufferSize) {
synchronized (mReadBufferLock) {
if (bufferSize == mReadBuffer.length) {
return;
}
mReadBuffer = new byte[bufferSize];
}
}
/** /**
* 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];
@ -120,48 +111,247 @@ abstract class CommonUsbSerialPort implements UsbSerialPort {
} }
@Override @Override
public abstract void open(UsbDeviceConnection connection) throws IOException; public void open(UsbDeviceConnection connection) throws IOException {
if (mConnection != null) {
@Override throw new IOException("Already open");
public abstract void close() throws IOException; }
if(connection == null) {
@Override throw new IllegalArgumentException("Connection is null");
public abstract int read(final byte[] dest, final int timeoutMillis) throws IOException; }
mConnection = connection;
@Override boolean ok = false;
public abstract int write(final byte[] src, final int timeoutMillis) throws IOException; try {
openInt();
@Override if (mReadEndpoint == null || mWriteEndpoint == null) {
public abstract void setParameters( throw new IOException("Could not get read & write endpoints");
int baudRate, int dataBits, int stopBits, int parity) throws IOException; }
mUsbRequest = new UsbRequest();
@Override mUsbRequest.initialize(mConnection, mReadEndpoint);
public abstract boolean getCD() throws IOException; ok = true;
} finally {
@Override if (!ok) {
public abstract boolean getCTS() throws IOException; try {
close();
@Override } catch (Exception ignored) {}
public abstract boolean getDSR() throws IOException; }
}
@Override
public abstract boolean getDTR() throws IOException;
@Override
public abstract void setDTR(boolean value) throws IOException;
@Override
public abstract boolean getRI() throws IOException;
@Override
public abstract boolean getRTS() throws IOException;
@Override
public abstract void setRTS(boolean value) throws IOException;
@Override
public boolean purgeHwBuffers(boolean flushReadBuffers, boolean flushWriteBuffers) throws IOException {
return !flushReadBuffers && !flushWriteBuffers;
} }
protected abstract void openInt() throws IOException;
@Override
public void close() throws IOException {
if (mConnection == null) {
throw new IOException("Already closed");
}
UsbRequest usbRequest = mUsbRequest;
mUsbRequest = null;
try {
usbRequest.cancel();
} catch(Exception ignored) {}
try {
closeInt();
} catch(Exception ignored) {}
try {
mConnection.close();
} catch(Exception ignored) {}
mConnection = null;
}
protected abstract void closeInt();
/**
* use simple USB request supported by all devices to test if connection is still valid
*/
protected void testConnection(boolean full) throws IOException {
testConnection(full, "USB get_status request failed");
}
protected void testConnection(boolean full, String msg) throws IOException {
if(mUsbRequest == null) {
throw new IOException("Connection closed");
}
if(!full) {
return;
}
byte[] buf = new byte[2];
int len = mConnection.controlTransfer(0x80 /*DEVICE*/, 0 /*GET_STATUS*/, 0, 0, buf, buf.length, 200);
if(len < 0)
throw new IOException(msg);
}
@Override
public int read(final byte[] dest, final int timeout) throws IOException {
if(dest.length == 0) {
throw new IllegalArgumentException("Read buffer too small");
}
return read(dest, dest.length, timeout);
}
@Override
public int read(final byte[] dest, final int length, final int timeout) throws IOException {return read(dest, length, timeout, true);}
protected int read(final byte[] dest, int length, final int timeout, boolean testConnection) throws IOException {
testConnection(false);
if(length <= 0) {
throw new IllegalArgumentException("Read length too small");
}
length = Math.min(length, dest.length);
final int nread;
if (timeout != 0) {
// bulkTransfer will cause data loss with short timeout + high baud rates + continuous transfer
// https://stackoverflow.com/questions/9108548/android-usb-host-bulktransfer-is-losing-data
// but mConnection.requestWait(timeout) available since Android 8.0 es even worse,
// as it crashes with short timeout, e.g.
// A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x276a in tid 29846 (pool-2-thread-1), pid 29618 (.usbserial.test)
// /system/lib64/libusbhost.so (usb_request_wait+192)
// /system/lib64/libandroid_runtime.so (android_hardware_UsbDeviceConnection_request_wait(_JNIEnv*, _jobject*, long)+84)
// data loss / crashes were observed with timeout up to 200 msec
long endTime = testConnection ? MonotonicClock.millis() + timeout : 0;
int readMax = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) ? length : Math.min(length, MAX_READ_SIZE);
nread = mConnection.bulkTransfer(mReadEndpoint, dest, readMax, timeout);
// Android error propagation is improvable:
// nread == -1 can be: timeout, connection lost, buffer to small, ???
if(nread == -1 && testConnection)
testConnection(MonotonicClock.millis() < endTime);
} else {
final ByteBuffer buf = ByteBuffer.wrap(dest, 0, length);
if (!mUsbRequest.queue(buf, length)) {
throw new IOException("Queueing USB request failed");
}
final UsbRequest response = mConnection.requestWait();
if (response == null) {
throw new IOException("Waiting for USB request failed");
}
nread = buf.position();
// Android error propagation is improvable:
// response != null & nread == 0 can be: connection lost, buffer to small, ???
if(nread == 0) {
testConnection(true);
}
}
return Math.max(nread, 0);
}
@Override
public void write(byte[] src, int timeout) throws IOException {write(src, src.length, timeout);}
@Override
public void write(final byte[] src, int length, final int timeout) throws IOException {
int offset = 0;
long startTime = MonotonicClock.millis();
length = Math.min(length, src.length);
testConnection(false);
while (offset < length) {
int requestTimeout;
final int requestLength;
final int actualLength;
synchronized (mWriteBufferLock) {
final byte[] writeBuffer;
if (mWriteBuffer == null) {
mWriteBuffer = new byte[mWriteEndpoint.getMaxPacketSize()];
}
requestLength = Math.min(length - offset, mWriteBuffer.length);
if (offset == 0) {
writeBuffer = src;
} else {
// bulkTransfer does not support offsets, make a copy.
System.arraycopy(src, offset, mWriteBuffer, 0, requestLength);
writeBuffer = mWriteBuffer;
}
if (timeout == 0 || offset == 0) {
requestTimeout = timeout;
} else {
requestTimeout = (int)(startTime + timeout - MonotonicClock.millis());
if(requestTimeout == 0)
requestTimeout = -1;
}
if (requestTimeout < 0) {
actualLength = -2;
} else {
actualLength = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, requestLength, requestTimeout);
}
}
long elapsed = MonotonicClock.millis() - startTime;
if (DEBUG) {
Log.d(TAG, "Wrote " + actualLength + "/" + requestLength + " offset " + offset + "/" + length + " time " + elapsed + "/" + requestTimeout);
}
if (actualLength <= 0) {
String msg = "Error writing " + requestLength + " bytes at offset " + offset + " of total " + src.length + " after " + elapsed + "msec, rc=" + actualLength;
if (timeout != 0) {
// could be buffer full because: writing to fast, stopped by flow control
testConnection(elapsed < timeout, msg);
throw new SerialTimeoutException(msg, offset);
} else {
throw new IOException(msg);
}
}
offset += actualLength;
}
}
@Override
public boolean isOpen() {
return mUsbRequest != null;
}
@Override
public abstract void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException;
@Override
public boolean getCD() throws IOException { throw new UnsupportedOperationException(); }
@Override
public boolean getCTS() throws IOException { throw new UnsupportedOperationException(); }
@Override
public boolean getDSR() throws IOException { throw new UnsupportedOperationException(); }
@Override
public boolean getDTR() throws IOException { throw new UnsupportedOperationException(); }
@Override
public void setDTR(boolean value) throws IOException { throw new UnsupportedOperationException(); }
@Override
public boolean getRI() throws IOException { throw new UnsupportedOperationException(); }
@Override
public boolean getRTS() throws IOException { throw new UnsupportedOperationException(); }
@Override
public void setRTS(boolean value) throws IOException { throw new UnsupportedOperationException(); }
@Override
public EnumSet<ControlLine> getControlLines() throws IOException { throw new UnsupportedOperationException(); }
@Override
public EnumSet<ControlLine> getSupportedControlLines() throws IOException { return EnumSet.noneOf(ControlLine.class); }
@Override
public void setFlowControl(FlowControl flowcontrol) throws IOException {
if (flowcontrol != FlowControl.NONE)
throw new UnsupportedOperationException();
}
@Override
public FlowControl getFlowControl() { return mFlowControl; }
@Override
public EnumSet<FlowControl> getSupportedFlowControl() { return EnumSet.of(FlowControl.NONE); }
@Override
public boolean getXON() throws IOException { throw new UnsupportedOperationException(); }
@Override
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { throw new UnsupportedOperationException(); }
@Override
public void setBreak(boolean value) throws IOException { throw new UnsupportedOperationException(); }
} }

View File

@ -1,21 +1,6 @@
/* Copyright 2011-2013 Google Inc. /* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com> * Copyright 2013 mike wakerly <opensource@hoho.com>
* *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
* Project home page: https://github.com/mik3y/usb-serial-for-android * Project home page: https://github.com/mik3y/usb-serial-for-android
*/ */
@ -23,15 +8,12 @@ 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.hardware.usb.UsbRequest;
import android.util.Log;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -63,24 +45,29 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
public class Cp21xxSerialPort extends CommonUsbSerialPort { public class Cp21xxSerialPort extends CommonUsbSerialPort {
private static final int DEFAULT_BAUD_RATE = 9600;
private static final int USB_WRITE_TIMEOUT_MILLIS = 5000; private static final int USB_WRITE_TIMEOUT_MILLIS = 5000;
/* /*
* Configuration Request Types * Configuration Request Types
*/ */
private static final int REQTYPE_HOST_TO_DEVICE = 0x41; private static final int REQTYPE_HOST_TO_DEVICE = 0x41;
private static final int REQTYPE_DEVICE_TO_HOST = 0xc1;
/* /*
* Configuration Request Codes * Configuration Request Codes
*/ */
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_BAUDDIV_REQUEST_CODE = 0x01;
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_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_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;
@ -91,24 +78,27 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
private static final int UART_ENABLE = 0x0001; private static final int UART_ENABLE = 0x0001;
private static final int UART_DISABLE = 0x0000; private static final int UART_DISABLE = 0x0000;
/*
* SILABSER_SET_BAUDDIV_REQUEST_CODE
*/
private static final int BAUD_RATE_GEN_FREQ = 0x384000;
/* /*
* SILABSER_SET_MHS_REQUEST_CODE * SILABSER_SET_MHS_REQUEST_CODE
*/ */
private static final int MCR_DTR = 0x0001; private static final int DTR_ENABLE = 0x101;
private static final int MCR_RTS = 0x0002; private static final int DTR_DISABLE = 0x100;
private static final int MCR_ALL = 0x0003; private static final int RTS_ENABLE = 0x202;
private static final int RTS_DISABLE = 0x200;
private static final int CONTROL_WRITE_DTR = 0x0100; /*
private static final int CONTROL_WRITE_RTS = 0x0200; * SILABSER_GET_MDMSTS_REQUEST_CODE
*/
private static final int STATUS_DTR = 0x01;
private static final int STATUS_RTS = 0x02;
private static final int STATUS_CTS = 0x10;
private static final int STATUS_DSR = 0x20;
private static final int STATUS_RI = 0x40;
private static final int STATUS_CD = 0x80;
private UsbEndpoint mReadEndpoint;
private UsbEndpoint mWriteEndpoint; private boolean dtr = false;
private UsbRequest mUsbRequest; private boolean rts = false;
// second port of Cp2105 has limited baudRate, dataBits, stopBits, parity // second port of Cp2105 has limited baudRate, dataBits, stopBits, parity
// unsupported baudrate returns error at controlTransfer(), other parameters are silently ignored // unsupported baudrate returns error at controlTransfer(), other parameters are silently ignored
@ -123,21 +113,27 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
return Cp21xxSerialDriver.this; return Cp21xxSerialDriver.this;
} }
private int setConfigSingle(int request, int value) { private void setConfigSingle(int request, int value) throws IOException {
return mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, value, int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, value,
mPortNumber, null, 0, USB_WRITE_TIMEOUT_MILLIS); mPortNumber, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Control transfer failed: " + request + " / " + value + " -> " + result);
}
}
private byte getStatus() throws IOException {
byte[] buffer = new byte[1];
int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, SILABSER_GET_MDMSTS_REQUEST_CODE, 0,
mPortNumber, buffer, buffer.length, USB_WRITE_TIMEOUT_MILLIS);
if (result != buffer.length) {
throw new IOException("Control transfer failed: " + SILABSER_GET_MDMSTS_REQUEST_CODE + " / " + 0 + " -> " + result);
}
return buffer[0];
} }
@Override @Override
public void open(UsbDeviceConnection connection) throws IOException { protected void openInt() throws IOException {
if (mConnection != null) {
throw new IOException("Already opened.");
}
mConnection = connection;
boolean opened = false;
mIsRestrictedPort = mDevice.getInterfaceCount() == 2 && mPortNumber == 1; mIsRestrictedPort = mDevice.getInterfaceCount() == 2 && mPortNumber == 1;
try {
if(mPortNumber >= mDevice.getInterfaceCount()) { if(mPortNumber >= mDevice.getInterfaceCount()) {
throw new IOException("Unknown port number"); throw new IOException("Unknown port number");
} }
@ -157,105 +153,18 @@ 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, MCR_ALL | CONTROL_WRITE_DTR | CONTROL_WRITE_RTS); setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, (dtr ? DTR_ENABLE : DTR_DISABLE) | (rts ? RTS_ENABLE : RTS_DISABLE));
setConfigSingle(SILABSER_SET_BAUDDIV_REQUEST_CODE, BAUD_RATE_GEN_FREQ / DEFAULT_BAUD_RATE); setFlowControl(mFlowControl);
// setParameters(DEFAULT_BAUD_RATE, DEFAULT_DATA_BITS, DEFAULT_STOP_BITS, DEFAULT_PARITY);
opened = true;
} finally {
if (!opened) {
try {
close();
} catch (IOException e) {
// Ignore IOExceptions during close()
}
}
}
} }
@Override @Override
public void close() throws IOException { protected void closeInt() {
if (mConnection == null) {
throw new IOException("Already closed");
}
synchronized (this) {
if(mUsbRequest != null) {
mUsbRequest.cancel();
}
}
try { try {
setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_DISABLE); setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_DISABLE);
} catch (Exception ignored) } catch (Exception ignored) {}
{}
try { try {
mConnection.close(); mConnection.releaseInterface(mDevice.getInterface(mPortNumber));
} finally { } catch(Exception ignored) {}
mConnection = null;
}
}
@Override
public int read(byte[] dest, int timeoutMillis) throws IOException {
final UsbRequest request = new UsbRequest();
try {
request.initialize(mConnection, mReadEndpoint);
final ByteBuffer buf = ByteBuffer.wrap(dest);
if (!request.queue(buf, dest.length)) {
throw new IOException("Error queueing request.");
}
mUsbRequest = request;
final UsbRequest response = mConnection.requestWait();
synchronized (this) {
mUsbRequest = null;
}
if (response == null) {
throw new IOException("Null response");
}
final int nread = buf.position();
if (nread > 0) {
//Log.d(TAG, HexDump.dumpHexString(dest, 0, Math.min(32, dest.length)));
return nread;
} else {
return 0;
}
} finally {
mUsbRequest = null;
request.close();
}
}
@Override
public int write(byte[] src, int timeoutMillis) throws IOException {
int offset = 0;
while (offset < src.length) {
final int writeLength;
final int amtWritten;
synchronized (mWriteBufferLock) {
final byte[] writeBuffer;
writeLength = Math.min(src.length - offset, mWriteBuffer.length);
if (offset == 0) {
writeBuffer = src;
} else {
// bulkTransfer does not support offsets, make a copy.
System.arraycopy(src, offset, mWriteBuffer, 0, writeLength);
writeBuffer = mWriteBuffer;
}
amtWritten = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, writeLength,
timeoutMillis);
}
if (amtWritten <= 0) {
throw new IOException("Error writing " + writeLength
+ " bytes at offset " + offset + " length=" + src.length);
}
Log.d(TAG, "Wrote amt=" + amtWritten + " attempted=" + writeLength);
offset += amtWritten;
}
return offset;
} }
private void setBaudRate(int baudRate) throws IOException { private void setBaudRate(int baudRate) throws IOException {
@ -265,40 +174,42 @@ 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");
} }
} }
@Override @Override
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException {
throws IOException { if(baudRate <= 0) {
throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
}
setBaudRate(baudRate); setBaudRate(baudRate);
int configDataBits = 0; int configDataBits = 0;
switch (dataBits) { switch (dataBits) {
case DATABITS_5: case DATABITS_5:
if(mIsRestrictedPort) if(mIsRestrictedPort)
throw new IllegalArgumentException("Unsupported dataBits value: " + dataBits); throw new UnsupportedOperationException("Unsupported data bits: " + dataBits);
configDataBits |= 0x0500; configDataBits |= 0x0500;
break; break;
case DATABITS_6: case DATABITS_6:
if(mIsRestrictedPort) if(mIsRestrictedPort)
throw new IllegalArgumentException("Unsupported dataBits value: " + dataBits); throw new UnsupportedOperationException("Unsupported data bits: " + dataBits);
configDataBits |= 0x0600; configDataBits |= 0x0600;
break; break;
case DATABITS_7: case DATABITS_7:
if(mIsRestrictedPort) if(mIsRestrictedPort)
throw new IllegalArgumentException("Unsupported dataBits value: " + dataBits); throw new UnsupportedOperationException("Unsupported data bits: " + dataBits);
configDataBits |= 0x0700; configDataBits |= 0x0700;
break; break;
case DATABITS_8: case DATABITS_8:
configDataBits |= 0x0800; configDataBits |= 0x0800;
break; break;
default: default:
throw new IllegalArgumentException("Unknown dataBits value: " + dataBits); throw new IllegalArgumentException("Invalid data bits: " + dataBits);
} }
switch (parity) { switch (parity) {
@ -312,95 +223,188 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
break; break;
case PARITY_MARK: case PARITY_MARK:
if(mIsRestrictedPort) if(mIsRestrictedPort)
throw new IllegalArgumentException("Unsupported parity value: mark"); throw new UnsupportedOperationException("Unsupported parity: mark");
configDataBits |= 0x0030; configDataBits |= 0x0030;
break; break;
case PARITY_SPACE: case PARITY_SPACE:
if(mIsRestrictedPort) if(mIsRestrictedPort)
throw new IllegalArgumentException("Unsupported parity value: space"); throw new UnsupportedOperationException("Unsupported parity: space");
configDataBits |= 0x0040; configDataBits |= 0x0040;
break; break;
default: default:
throw new IllegalArgumentException("Unknown parity value: " + parity); throw new IllegalArgumentException("Invalid parity: " + parity);
} }
switch (stopBits) { switch (stopBits) {
case STOPBITS_1: case STOPBITS_1:
break; break;
case STOPBITS_1_5: case STOPBITS_1_5:
throw new IllegalArgumentException("Unsupported stopBits value: 1.5"); throw new UnsupportedOperationException("Unsupported stop bits: 1.5");
case STOPBITS_2: case STOPBITS_2:
if(mIsRestrictedPort) if(mIsRestrictedPort)
throw new IllegalArgumentException("Unsupported stopBits value: 2"); throw new UnsupportedOperationException("Unsupported stop bits: 2");
configDataBits |= 2; configDataBits |= 2;
break; break;
default: default:
throw new IllegalArgumentException("Unknown stopBits value: " + stopBits); throw new IllegalArgumentException("Invalid stop bits: " + stopBits);
} }
setConfigSingle(SILABSER_SET_LINE_CTL_REQUEST_CODE, configDataBits); setConfigSingle(SILABSER_SET_LINE_CTL_REQUEST_CODE, configDataBits);
} }
@Override @Override
public boolean getCD() throws IOException { public boolean getCD() throws IOException {
return false; return (getStatus() & STATUS_CD) != 0;
} }
@Override @Override
public boolean getCTS() throws IOException { public boolean getCTS() throws IOException {
return false; return (getStatus() & STATUS_CTS) != 0;
} }
@Override @Override
public boolean getDSR() throws IOException { public boolean getDSR() throws IOException {
return false; return (getStatus() & STATUS_DSR) != 0;
} }
@Override @Override
public boolean getDTR() throws IOException { public boolean getDTR() throws IOException {
return true; return dtr;
} }
@Override @Override
public void setDTR(boolean value) throws IOException { public void setDTR(boolean value) throws IOException {
dtr = value;
setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, dtr ? DTR_ENABLE : DTR_DISABLE);
} }
@Override @Override
public boolean getRI() throws IOException { public boolean getRI() throws IOException {
return false; return (getStatus() & STATUS_RI) != 0;
} }
@Override @Override
public boolean getRTS() throws IOException { public boolean getRTS() throws IOException {
return true; return rts;
} }
@Override @Override
public void setRTS(boolean value) throws IOException { public void setRTS(boolean value) throws IOException {
rts = value;
setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, rts ? RTS_ENABLE : RTS_DISABLE);
} }
@Override @Override
public boolean purgeHwBuffers(boolean purgeReadBuffers, public EnumSet<ControlLine> getControlLines() throws IOException {
boolean purgeWriteBuffers) throws IOException { byte status = getStatus();
EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class);
//if(rts) set.add(ControlLine.RTS); // configured value
if((status & STATUS_RTS) != 0) set.add(ControlLine.RTS); // actual value
if((status & STATUS_CTS) != 0) set.add(ControlLine.CTS);
//if(dtr) set.add(ControlLine.DTR); // configured value
if((status & STATUS_DTR) != 0) set.add(ControlLine.DTR); // actual value
if((status & STATUS_DSR) != 0) set.add(ControlLine.DSR);
if((status & STATUS_CD) != 0) set.add(ControlLine.CD);
if((status & STATUS_RI) != 0) set.add(ControlLine.RI);
return set;
}
@Override
public EnumSet<ControlLine> getSupportedControlLines() throws IOException {
return EnumSet.allOf(ControlLine.class);
}
@Override
public boolean getXON() throws IOException {
byte[] buffer = new byte[0x13];
int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, SILABSER_GET_COMM_STATUS_REQUEST_CODE, 0,
mPortNumber, buffer, buffer.length, USB_WRITE_TIMEOUT_MILLIS);
if (result != buffer.length) {
throw new IOException("Control transfer failed: " + SILABSER_GET_COMM_STATUS_REQUEST_CODE + " -> " + result);
}
return (buffer[4] & 8) == 0;
}
/**
* emulate external XON/OFF
* @throws IOException
*/
public void setXON(boolean value) throws IOException {
setConfigSingle(value ? SILABSER_SET_XON_REQUEST_CODE : SILABSER_SET_XOFF_REQUEST_CODE, 0);
}
@Override
public void setFlowControl(FlowControl flowControl) throws IOException {
byte[] data = new byte[16];
if(flowControl == FlowControl.RTS_CTS) {
data[4] |= 0b1000_0000; // RTS
data[0] |= 0b0000_1000; // CTS
} else {
if(rts)
data[4] |= 0b0100_0000;
}
if(flowControl == FlowControl.DTR_DSR) {
data[0] |= 0b0000_0010; // DTR
data[0] |= 0b0001_0000; // DSR
} else {
if(dtr)
data[0] |= 0b0000_0001;
}
if(flowControl == FlowControl.XON_XOFF) {
byte[] chars = new byte[]{0, 0, 0, 0, CHAR_XON, CHAR_XOFF};
int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_CHARS_REQUEST_CODE,
0, mPortNumber, chars, chars.length, USB_WRITE_TIMEOUT_MILLIS);
if (ret != chars.length) {
throw new IOException("Error setting XON/XOFF chars");
}
data[4] |= 0b0000_0011;
data[7] |= 0b1000_0000;
data[8] = (byte)128;
data[12] = (byte)128;
}
if(flowControl == FlowControl.XON_XOFF_INLINE) {
throw new UnsupportedOperationException();
}
int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_FLOW_REQUEST_CODE,
0, mPortNumber, data, data.length, USB_WRITE_TIMEOUT_MILLIS);
if (ret != data.length) {
throw new IOException("Error setting flow control");
}
if(flowControl == FlowControl.XON_XOFF) {
setXON(true);
}
mFlowControl = flowControl;
}
@Override
public EnumSet<FlowControl> getSupportedFlowControl() {
return EnumSet.of(FlowControl.NONE, FlowControl.RTS_CTS, FlowControl.DTR_DSR, FlowControl.XON_XOFF);
}
@Override
// note: only working on some devices, on other devices ignored w/o error
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {
int value = (purgeReadBuffers ? FLUSH_READ_CODE : 0) int value = (purgeReadBuffers ? FLUSH_READ_CODE : 0)
| (purgeWriteBuffers ? FLUSH_WRITE_CODE : 0); | (purgeWriteBuffers ? FLUSH_WRITE_CODE : 0);
if (value != 0) { if (value != 0) {
setConfigSingle(SILABSER_FLUSH_REQUEST_CODE, value); setConfigSingle(SILABSER_FLUSH_REQUEST_CODE, value);
} }
return true;
} }
@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(Integer.valueOf(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

@ -1,20 +1,6 @@
/* Copyright 2011-2013 Google Inc. /* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com> * Copyright 2013 mike wakerly <opensource@hoho.com>
* * Copyright 2020 kai morich <mail@kai-morich.de>
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
* *
* Project home page: https://github.com/mik3y/usb-serial-for-android * Project home page: https://github.com/mik3y/usb-serial-for-android
*/ */
@ -23,85 +9,33 @@ 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.UsbRequest;
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.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
/** /*
* A {@link CommonUsbSerialPort} implementation for a variety of FTDI devices * driver is implemented from various information scattered over FTDI documentation
* <p>
* This driver is based on <a
* href="http://www.intra2net.com/en/developer/libftdi">libftdi</a>, and is
* copyright and subject to the following terms:
* *
* <pre> * baud rate calculation https://www.ftdichip.com/Support/Documents/AppNotes/AN232B-05_BaudRates.pdf
* Copyright (C) 2003 by Intra2net AG * control bits https://www.ftdichip.com/Firmware/Precompiled/UM_VinculumFirmware_V205.pdf
* device type https://www.ftdichip.com/Support/Documents/AppNotes/AN_233_Java_D2XX_for_Android_API_User_Manual.pdf -> bvdDevice
* *
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation;
*
* opensource@intra2net.com
* http://www.intra2net.com/en/developer/libftdi
* </pre>
*
* </p>
* <p>
* Some FTDI devices have not been tested; see later listing of supported and
* unsupported devices. Devices listed as "supported" support the following
* features:
* <ul>
* <li>Read and write of serial data (see
* {@link CommonUsbSerialPort#read(byte[], int)} and
* {@link CommonUsbSerialPort#write(byte[], int)}.</li>
* <li>Setting serial line parameters (see
* {@link CommonUsbSerialPort#setParameters(int, int, int, int)}.</li>
* </ul>
* </p>
* <p>
* Supported and tested devices:
* <ul>
* <li>{@value DeviceType#TYPE_R}</li>
* <li>{@value DeviceType#TYPE_2232H}</li>
* <li>{@value DeviceType#TYPE_4232H}</li>
* </ul>
* </p>
* <p>
* Unsupported but possibly working devices (please contact the author with
* feedback or patches):
* <ul>
* <li>{@value DeviceType#TYPE_2232C}</li>
* <li>{@value DeviceType#TYPE_AM}</li>
* <li>{@value DeviceType#TYPE_BM}</li>
* </ul>
* </p>
*
* @author mike wakerly (opensource@hoho.com)
* @see <a href="https://github.com/mik3y/usb-serial-for-android">USB Serial
* for Android project page</a>
* @see <a href="http://www.ftdichip.com/">FTDI Homepage</a>
* @see <a href="http://www.intra2net.com/en/developer/libftdi">libftdi</a>
*/ */
public class FtdiSerialDriver implements UsbSerialDriver { public class FtdiSerialDriver implements UsbSerialDriver {
private static final String TAG = FtdiSerialPort.class.getSimpleName();
private final UsbDevice mDevice; private final UsbDevice mDevice;
private final List<UsbSerialPort> mPorts; private final List<UsbSerialPort> mPorts;
/**
* FTDI chip types.
*/
private static enum DeviceType {
TYPE_BM, TYPE_AM, TYPE_2232C, TYPE_R, TYPE_2232H, TYPE_4232H;
}
public FtdiSerialDriver(UsbDevice device) { public FtdiSerialDriver(UsbDevice device) {
mDevice = device; mDevice = device;
mPorts = new ArrayList<>(); mPorts = new ArrayList<>();
@ -120,70 +54,39 @@ public class FtdiSerialDriver implements UsbSerialDriver {
return mPorts; return mPorts;
} }
private class FtdiSerialPort extends CommonUsbSerialPort { public class FtdiSerialPort extends CommonUsbSerialPort {
public static final int USB_TYPE_STANDARD = 0x00 << 5; private static final int USB_WRITE_TIMEOUT_MILLIS = 5000;
public static final int USB_TYPE_CLASS = 0x00 << 5; private static final int READ_HEADER_LENGTH = 2; // contains MODEM_STATUS
public static final int USB_TYPE_VENDOR = 0x00 << 5;
public static final int USB_TYPE_RESERVED = 0x00 << 5;
public static final int USB_RECIP_DEVICE = 0x00; private static final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT;
public static final int USB_RECIP_INTERFACE = 0x01; private static final int REQTYPE_DEVICE_TO_HOST = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_IN;
public static final int USB_RECIP_ENDPOINT = 0x02;
public static final int USB_RECIP_OTHER = 0x03;
public static final int USB_ENDPOINT_IN = 0x80; private static final int RESET_REQUEST = 0;
public static final int USB_ENDPOINT_OUT = 0x00; private static final int MODEM_CONTROL_REQUEST = 1;
private static final int SET_FLOW_CONTROL_REQUEST = 2;
private static final int SET_BAUD_RATE_REQUEST = 3;
private static final int SET_DATA_REQUEST = 4;
private static final int GET_MODEM_STATUS_REQUEST = 5;
private static final int SET_LATENCY_TIMER_REQUEST = 9;
private static final int GET_LATENCY_TIMER_REQUEST = 10;
public static final int USB_WRITE_TIMEOUT_MILLIS = 5000; private static final int MODEM_CONTROL_DTR_ENABLE = 0x0101;
public static final int USB_READ_TIMEOUT_MILLIS = 5000; private static final int MODEM_CONTROL_DTR_DISABLE = 0x0100;
private static final int MODEM_CONTROL_RTS_ENABLE = 0x0202;
private static final int MODEM_CONTROL_RTS_DISABLE = 0x0200;
private static final int MODEM_STATUS_CTS = 0x10;
private static final int MODEM_STATUS_DSR = 0x20;
private static final int MODEM_STATUS_RI = 0x40;
private static final int MODEM_STATUS_CD = 0x80;
private static final int RESET_ALL = 0;
private static final int RESET_PURGE_RX = 1;
private static final int RESET_PURGE_TX = 2;
// From ftdi.h private boolean baudRateWithPort = false;
/** private boolean dtr = false;
* Reset the port. private boolean rts = false;
*/ private int breakConfig = 0;
private static final int SIO_RESET_REQUEST = 0;
/**
* Set the modem control register.
*/
private static final int SIO_MODEM_CTRL_REQUEST = 1;
/**
* Set flow control register.
*/
private static final int SIO_SET_FLOW_CTRL_REQUEST = 2;
/**
* Set baud rate.
*/
private static final int SIO_SET_BAUD_RATE_REQUEST = 3;
/**
* Set the data characteristics of the port.
*/
private static final int SIO_SET_DATA_REQUEST = 4;
private static final int SIO_RESET_SIO = 0;
private static final int SIO_RESET_PURGE_RX = 1;
private static final int SIO_RESET_PURGE_TX = 2;
public static final int FTDI_DEVICE_OUT_REQTYPE =
UsbConstants.USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_OUT;
public static final int FTDI_DEVICE_IN_REQTYPE =
UsbConstants.USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN;
/**
* Length of the modem status header, transmitted with every read.
*/
private static final int MODEM_STATUS_HEADER_LENGTH = 2;
private final String TAG = FtdiSerialDriver.class.getSimpleName();
private DeviceType mType;
private int mIndex = 0;
public FtdiSerialPort(UsbDevice device, int portNumber) { public FtdiSerialPort(UsbDevice device, int portNumber) {
super(device, portNumber); super(device, portNumber);
@ -194,375 +97,380 @@ public class FtdiSerialDriver implements UsbSerialDriver {
return FtdiSerialDriver.this; return FtdiSerialDriver.this;
} }
/**
* Filter FTDI status bytes from buffer
* @param src The source buffer (which contains status bytes)
* @param dest The destination buffer to write the status bytes into (can be src)
* @param totalBytesRead Number of bytes read to src
* @param maxPacketSize The USB endpoint max packet size
* @return The number of payload bytes
*/
private final int filterStatusBytes(byte[] src, byte[] dest, int totalBytesRead, int maxPacketSize) {
final int packetsCount = (totalBytesRead + maxPacketSize -1 )/ maxPacketSize;
for (int packetIdx = 0; packetIdx < packetsCount; ++packetIdx) {
final int count = (packetIdx == (packetsCount - 1))
? totalBytesRead - packetIdx * maxPacketSize - MODEM_STATUS_HEADER_LENGTH
: maxPacketSize - MODEM_STATUS_HEADER_LENGTH;
if (count > 0) {
System.arraycopy(src,
packetIdx * maxPacketSize + MODEM_STATUS_HEADER_LENGTH,
dest,
packetIdx * (maxPacketSize - MODEM_STATUS_HEADER_LENGTH),
count);
}
}
return totalBytesRead - (packetsCount * 2); @Override
protected void openInt() throws IOException {
if (!mConnection.claimInterface(mDevice.getInterface(mPortNumber), true)) {
throw new IOException("Could not claim interface " + mPortNumber);
} }
if (mDevice.getInterface(mPortNumber).getEndpointCount() < 2) {
public void reset() throws IOException { throw new IOException("Not enough endpoints");
// TODO(mikey): autodetect.
mType = DeviceType.TYPE_R;
if(mDevice.getInterfaceCount() > 1) {
mIndex = mPortNumber + 1;
if (mDevice.getInterfaceCount() == 2)
mType = DeviceType.TYPE_2232H;
if (mDevice.getInterfaceCount() == 4)
mType = DeviceType.TYPE_4232H;
} }
mReadEndpoint = mDevice.getInterface(mPortNumber).getEndpoint(0);
mWriteEndpoint = mDevice.getInterface(mPortNumber).getEndpoint(1);
int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE, SIO_RESET_REQUEST, int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST,
SIO_RESET_SIO, mIndex, null, 0, USB_WRITE_TIMEOUT_MILLIS); RESET_ALL, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) { if (result != 0) {
throw new IOException("Reset failed: result=" + result); throw new IOException("Reset failed: result=" + result);
} }
result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST,
(dtr ? MODEM_CONTROL_DTR_ENABLE : MODEM_CONTROL_DTR_DISABLE) |
(rts ? MODEM_CONTROL_RTS_ENABLE : MODEM_CONTROL_RTS_DISABLE),
mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Init RTS,DTR failed: result=" + result);
}
setFlowControl(mFlowControl);
// mDevice.getVersion() would require API 23
byte[] rawDescriptors = mConnection.getRawDescriptors();
if(rawDescriptors == null || rawDescriptors.length < 14) {
throw new IOException("Could not get device descriptors");
}
int deviceType = rawDescriptors[13];
baudRateWithPort = deviceType == 7 || deviceType == 8 || deviceType == 9 // ...H devices
|| mDevice.getInterfaceCount() > 1; // FT2232C
} }
@Override @Override
public void open(UsbDeviceConnection connection) throws IOException { protected void closeInt() {
if (mConnection != null) {
throw new IOException("Already open");
}
mConnection = connection;
boolean opened = false;
try { try {
if (connection.claimInterface(mDevice.getInterface(mPortNumber), true)) { mConnection.releaseInterface(mDevice.getInterface(mPortNumber));
Log.d(TAG, "claimInterface " + mPortNumber + " SUCCESS"); } catch(Exception ignored) {}
}
@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 { } else {
throw new IOException("Error claiming interface " + mPortNumber); do {
} nread = super.read(dest, length, timeout);
reset(); } while (nread == READ_HEADER_LENGTH);
opened = true;
} finally {
if (!opened) {
close();
mConnection = null;
}
} }
return readFilter(dest, nread);
} }
@Override protected int readFilter(byte[] buffer, int totalBytesRead) throws IOException {
public void close() throws IOException { final int maxPacketSize = mReadEndpoint.getMaxPacketSize();
if (mConnection == null) { int destPos = 0;
throw new IOException("Already closed"); for(int srcPos = 0; srcPos < totalBytesRead; srcPos += maxPacketSize) {
} int length = Math.min(srcPos + maxPacketSize, totalBytesRead) - (srcPos + READ_HEADER_LENGTH);
try { if (length < 0)
mConnection.close(); throw new IOException("Expected at least " + READ_HEADER_LENGTH + " bytes");
} finally { System.arraycopy(buffer, srcPos + READ_HEADER_LENGTH, buffer, destPos, length);
mConnection = null; destPos += length;
} }
//Log.d(TAG, "read filter " + totalBytesRead + " -> " + destPos);
return destPos;
} }
@Override private void setBaudrate(int baudRate) throws IOException {
public int read(byte[] dest, int timeoutMillis) throws IOException { int divisor, subdivisor, effectiveBaudRate;
final UsbEndpoint endpoint = mDevice.getInterface(mPortNumber).getEndpoint(0); if (baudRate > 3500000) {
final UsbRequest request = new UsbRequest(); throw new UnsupportedOperationException("Baud rate to high");
final ByteBuffer buf = ByteBuffer.wrap(dest); } else if(baudRate >= 2500000) {
try { divisor = 0;
request.initialize(mConnection, endpoint); subdivisor = 0;
if (!request.queue(buf, dest.length)) { effectiveBaudRate = 3000000;
throw new IOException("Error queueing request."); } else if(baudRate >= 1750000) {
} divisor = 1;
subdivisor = 0;
final UsbRequest response = mConnection.requestWait(); effectiveBaudRate = 2000000;
if (response == null) {
throw new IOException("Null response");
}
} finally {
request.close();
}
final int totalBytesRead = buf.position();
if (totalBytesRead < MODEM_STATUS_HEADER_LENGTH) {
throw new IOException("Expected at least " + MODEM_STATUS_HEADER_LENGTH + " bytes");
}
return filterStatusBytes(dest, dest, totalBytesRead, endpoint.getMaxPacketSize());
}
@Override
public int write(byte[] src, int timeoutMillis) throws IOException {
final UsbEndpoint endpoint = mDevice.getInterface(mPortNumber).getEndpoint(1);
int offset = 0;
while (offset < src.length) {
final int writeLength;
final int amtWritten;
synchronized (mWriteBufferLock) {
final byte[] writeBuffer;
writeLength = Math.min(src.length - offset, mWriteBuffer.length);
if (offset == 0) {
writeBuffer = src;
} else { } else {
// bulkTransfer does not support offsets, make a copy. divisor = (24000000 << 1) / baudRate;
System.arraycopy(src, offset, mWriteBuffer, 0, writeLength); divisor = (divisor + 1) >> 1; // round
writeBuffer = mWriteBuffer; subdivisor = divisor & 0x07;
divisor >>= 3;
if (divisor > 0x3fff) // exceeds bit 13 at 183 baud
throw new UnsupportedOperationException("Baud rate to low");
effectiveBaudRate = (24000000 << 1) / ((divisor << 3) + subdivisor);
effectiveBaudRate = (effectiveBaudRate +1) >> 1;
} }
double baudRateError = Math.abs(1.0 - (effectiveBaudRate / (double)baudRate));
if(baudRateError >= 0.031) // can happen only > 1.5Mbaud
throw new UnsupportedOperationException(String.format("Baud rate deviation %.1f%% is higher than allowed 3%%", baudRateError*100));
int value = divisor;
int index = 0;
switch(subdivisor) {
case 0: break; // 16,15,14 = 000 - sub-integer divisor = 0
case 4: value |= 0x4000; break; // 16,15,14 = 001 - sub-integer divisor = 0.5
case 2: value |= 0x8000; break; // 16,15,14 = 010 - sub-integer divisor = 0.25
case 1: value |= 0xc000; break; // 16,15,14 = 011 - sub-integer divisor = 0.125
case 3: value |= 0x0000; index |= 1; break; // 16,15,14 = 100 - sub-integer divisor = 0.375
case 5: value |= 0x4000; index |= 1; break; // 16,15,14 = 101 - sub-integer divisor = 0.625
case 6: value |= 0x8000; index |= 1; break; // 16,15,14 = 110 - sub-integer divisor = 0.75
case 7: value |= 0xc000; index |= 1; break; // 16,15,14 = 111 - sub-integer divisor = 0.875
}
if(baudRateWithPort) {
index <<= 8;
index |= mPortNumber+1;
}
Log.d(TAG, String.format("baud rate=%d, effective=%d, error=%.1f%%, value=0x%04x, index=0x%04x, divisor=%d, subdivisor=%d",
baudRate, effectiveBaudRate, baudRateError*100, value, index, divisor, subdivisor));
amtWritten = mConnection.bulkTransfer(endpoint, writeBuffer, writeLength, int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_BAUD_RATE_REQUEST,
timeoutMillis); value, index, null, 0, USB_WRITE_TIMEOUT_MILLIS);
}
if (amtWritten <= 0) {
throw new IOException("Error writing " + writeLength
+ " bytes at offset " + offset + " length=" + src.length);
}
Log.d(TAG, "Wrote amtWritten=" + amtWritten + " attempted=" + writeLength);
offset += amtWritten;
}
return offset;
}
private int setBaudRate(int baudRate) throws IOException {
long[] vals = convertBaudrate(baudRate);
long actualBaudrate = vals[0];
long index = vals[1];
long value = vals[2];
int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE,
SIO_SET_BAUD_RATE_REQUEST, (int) value, (int) index,
null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) { if (result != 0) {
throw new IOException("Setting baudrate failed: result=" + result); throw new IOException("Setting baudrate failed: result=" + result);
} }
return (int) actualBaudrate;
} }
@Override @Override
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException {
throws IOException { if(baudRate <= 0) {
setBaudRate(baudRate); throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
}
setBaudrate(baudRate);
int config = 0; int config = 0;
switch (dataBits) { switch (dataBits) {
case DATABITS_5: case DATABITS_5:
case DATABITS_6: case DATABITS_6:
throw new IllegalArgumentException("Unsupported dataBits value: " + dataBits); throw new UnsupportedOperationException("Unsupported data bits: " + dataBits);
case DATABITS_7: case DATABITS_7:
case DATABITS_8: case DATABITS_8:
config |= dataBits; config |= dataBits;
break; break;
default: default:
throw new IllegalArgumentException("Unknown dataBits value: " + dataBits); throw new IllegalArgumentException("Invalid data bits: " + dataBits);
} }
switch (parity) { switch (parity) {
case PARITY_NONE: case PARITY_NONE:
config |= (0x00 << 8);
break; break;
case PARITY_ODD: case PARITY_ODD:
config |= (0x01 << 8); config |= 0x100;
break; break;
case PARITY_EVEN: case PARITY_EVEN:
config |= (0x02 << 8); config |= 0x200;
break; break;
case PARITY_MARK: case PARITY_MARK:
config |= (0x03 << 8); config |= 0x300;
break; break;
case PARITY_SPACE: case PARITY_SPACE:
config |= (0x04 << 8); config |= 0x400;
break; break;
default: default:
throw new IllegalArgumentException("Unknown parity value: " + parity); throw new IllegalArgumentException("Invalid parity: " + parity);
} }
switch (stopBits) { switch (stopBits) {
case STOPBITS_1: case STOPBITS_1:
config |= (0x00 << 11);
break; break;
case STOPBITS_1_5: case STOPBITS_1_5:
throw new IllegalArgumentException("Unsupported stopBits value: 1.5"); throw new UnsupportedOperationException("Unsupported stop bits: 1.5");
case STOPBITS_2: case STOPBITS_2:
config |= (0x02 << 11); config |= 0x1000;
break; break;
default: default:
throw new IllegalArgumentException("Unknown stopBits value: " + stopBits); throw new IllegalArgumentException("Invalid stop bits: " + stopBits);
} }
int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE, int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_DATA_REQUEST,
SIO_SET_DATA_REQUEST, config, mIndex, config, mPortNumber+1,null, 0, USB_WRITE_TIMEOUT_MILLIS);
null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) { if (result != 0) {
throw new IOException("Setting parameters failed: result=" + result); throw new IOException("Setting parameters failed: result=" + result);
} }
breakConfig = config;
} }
private long[] convertBaudrate(int baudrate) { private int getStatus() throws IOException {
// TODO(mikey): Braindead transcription of libfti method. Clean up, byte[] data = new byte[2];
// using more idiomatic Java where possible. int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_MODEM_STATUS_REQUEST,
int divisor = 24000000 / baudrate; 0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS);
int bestDivisor = 0; if (result != data.length) {
int bestBaud = 0; throw new IOException("Get modem status failed: result=" + result);
int bestBaudDiff = 0;
int fracCode[] = {
0, 3, 2, 4, 1, 5, 6, 7
};
for (int i = 0; i < 2; i++) {
int tryDivisor = divisor + i;
int baudEstimate;
int baudDiff;
if (tryDivisor <= 8) {
// Round up to minimum supported divisor
tryDivisor = 8;
} else if (mType != DeviceType.TYPE_AM && tryDivisor < 12) {
// BM doesn't support divisors 9 through 11 inclusive
tryDivisor = 12;
} else if (divisor < 16) {
// AM doesn't support divisors 9 through 15 inclusive
tryDivisor = 16;
} else {
if (mType == DeviceType.TYPE_AM) {
// TODO
} else {
if (tryDivisor > 0x1FFFF) {
// Round down to maximum supported divisor value (for
// BM)
tryDivisor = 0x1FFFF;
} }
} return data[0];
}
// Get estimated baud rate (to nearest integer)
baudEstimate = (24000000 + (tryDivisor / 2)) / tryDivisor;
// Get absolute difference from requested baud rate
if (baudEstimate < baudrate) {
baudDiff = baudrate - baudEstimate;
} else {
baudDiff = baudEstimate - baudrate;
}
if (i == 0 || baudDiff < bestBaudDiff) {
// Closest to requested baud rate so far
bestDivisor = tryDivisor;
bestBaud = baudEstimate;
bestBaudDiff = baudDiff;
if (baudDiff == 0) {
// Spot on! No point trying
break;
}
}
}
// Encode the best divisor value
long encodedDivisor = (bestDivisor >> 3) | (fracCode[bestDivisor & 7] << 14);
// Deal with special cases for encoded value
if (encodedDivisor == 1) {
encodedDivisor = 0; // 3000000 baud
} else if (encodedDivisor == 0x4001) {
encodedDivisor = 1; // 2000000 baud (BM only)
}
// Split into "value" and "index" values
long value = encodedDivisor & 0xFFFF;
long index;
if (mType == DeviceType.TYPE_2232C || mType == DeviceType.TYPE_2232H
|| mType == DeviceType.TYPE_4232H) {
index = (encodedDivisor >> 8) & 0xff00;
index |= mIndex;
} else {
index = (encodedDivisor >> 16) & 0xffff;
}
// Return the nearest baud rate
return new long[] {
bestBaud, index, value
};
} }
@Override @Override
public boolean getCD() throws IOException { public boolean getCD() throws IOException {
return false; return (getStatus() & MODEM_STATUS_CD) != 0;
} }
@Override @Override
public boolean getCTS() throws IOException { public boolean getCTS() throws IOException {
return false; return (getStatus() & MODEM_STATUS_CTS) != 0;
} }
@Override @Override
public boolean getDSR() throws IOException { public boolean getDSR() throws IOException {
return false; return (getStatus() & MODEM_STATUS_DSR) != 0;
} }
@Override @Override
public boolean getDTR() throws IOException { public boolean getDTR() throws IOException {
return false; return dtr;
} }
@Override @Override
public void setDTR(boolean value) throws IOException { public void setDTR(boolean value) throws IOException {
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST,
value ? MODEM_CONTROL_DTR_ENABLE : MODEM_CONTROL_DTR_DISABLE, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Set DTR failed: result=" + result);
}
dtr = value;
} }
@Override @Override
public boolean getRI() throws IOException { public boolean getRI() throws IOException {
return false; return (getStatus() & MODEM_STATUS_RI) != 0;
} }
@Override @Override
public boolean getRTS() throws IOException { public boolean getRTS() throws IOException {
return false; return rts;
} }
@Override @Override
public void setRTS(boolean value) throws IOException { public void setRTS(boolean value) throws IOException {
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST,
value ? MODEM_CONTROL_RTS_ENABLE : MODEM_CONTROL_RTS_DISABLE, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Set DTR failed: result=" + result);
}
rts = value;
} }
@Override @Override
public boolean purgeHwBuffers(boolean purgeReadBuffers, boolean purgeWriteBuffers) throws IOException { public EnumSet<ControlLine> getControlLines() throws IOException {
if (purgeReadBuffers) { int status = getStatus();
int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE, SIO_RESET_REQUEST, EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class);
SIO_RESET_PURGE_RX, mIndex, null, 0, USB_WRITE_TIMEOUT_MILLIS); if(rts) set.add(ControlLine.RTS);
if (result != 0) { if((status & MODEM_STATUS_CTS) != 0) set.add(ControlLine.CTS);
throw new IOException("Flushing RX failed: result=" + result); if(dtr) set.add(ControlLine.DTR);
} if((status & MODEM_STATUS_DSR) != 0) set.add(ControlLine.DSR);
if((status & MODEM_STATUS_CD) != 0) set.add(ControlLine.CD);
if((status & MODEM_STATUS_RI) != 0) set.add(ControlLine.RI);
return set;
} }
@Override
public EnumSet<ControlLine> getSupportedControlLines() throws IOException {
return EnumSet.allOf(ControlLine.class);
}
@Override
public void setFlowControl(FlowControl flowControl) throws IOException {
int value = 0;
int index = mPortNumber+1;
switch (flowControl) {
case NONE:
break;
case RTS_CTS:
index |= 0x100;
break;
case DTR_DSR:
index |= 0x200;
break;
case XON_XOFF_INLINE:
value = CHAR_XON + (CHAR_XOFF << 8);
index |= 0x400;
break;
default:
throw new UnsupportedOperationException();
}
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_FLOW_CONTROL_REQUEST,
value, index, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0)
throw new IOException("Set flow control failed: result=" + result);
mFlowControl = flowControl;
}
@Override
public EnumSet<FlowControl> getSupportedFlowControl() {
return EnumSet.of(FlowControl.NONE, FlowControl.RTS_CTS, FlowControl.DTR_DSR, FlowControl.XON_XOFF_INLINE);
}
@Override
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {
if (purgeWriteBuffers) { if (purgeWriteBuffers) {
int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE, SIO_RESET_REQUEST, int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST,
SIO_RESET_PURGE_TX, mIndex, 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("Flushing RX failed: result=" + result); throw new IOException("Purge write buffer failed: result=" + result);
}
}
return true;
} }
} }
if (purgeReadBuffers) {
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST,
RESET_PURGE_TX, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Purge read buffer failed: result=" + result);
}
}
}
@Override
public void setBreak(boolean value) throws IOException {
int config = breakConfig;
if(value) config |= 0x4000;
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_DATA_REQUEST,
config, mPortNumber+1,null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Setting BREAK failed: result=" + result);
}
}
public void setLatencyTimer(int latencyTime) throws IOException {
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_LATENCY_TIMER_REQUEST,
latencyTime, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Set latency timer failed: result=" + result);
}
}
public int getLatencyTimer() throws IOException {
byte[] data = new byte[1];
int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_LATENCY_TIMER_REQUEST,
0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS);
if (result != data.length) {
throw new IOException("Get latency timer failed: result=" + result);
}
return data[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(Integer.valueOf(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, 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

@ -1,26 +1,12 @@
/* Copyright 2011-2013 Google Inc. /* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com> * Copyright 2013 mike wakerly <opensource@hoho.com>
* *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
* Project home page: https://github.com/mik3y/usb-serial-for-android * Project home page: https://github.com/mik3y/usb-serial-for-android
*/ */
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;
@ -29,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.
@ -48,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;
} }
@ -56,53 +42,61 @@ 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");
} catch (SecurityException e) { } catch (SecurityException | NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
final Map<Integer, int[]> devices; final Map<Integer, int[]> devices;
try { try {
devices = (Map<Integer, int[]>) method.invoke(null); devices = (Map<Integer, int[]>) method.invoke(null);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
for (Map.Entry<Integer, int[]> entry : devices.entrySet()) { for (Map.Entry<Integer, int[]> entry : devices.entrySet()) {
final int vendorId = entry.getKey().intValue(); final int vendorId = entry.getKey();
for (int productId : entry.getValue()) { for (int productId : entry.getValue()) {
addProduct(vendorId, productId, driverClass); addProduct(vendorId, productId, driverClass);
} }
} }
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

@ -1,44 +1,26 @@
/* This library is free software; you can redistribute it and/or /*
* modify it under the terms of the GNU Lesser General Public * Ported to usb-serial-for-android by Felix Hädicke <felixhaedicke@web.de>
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* *
* This library is distributed in the hope that it will be useful, * Based on the pyprolific driver written by Emmanuel Blot <emmanuel.blot@free.fr>
* but WITHOUT ANY WARRANTY; without even the implied warranty of * See https://github.com/eblot/pyftdi
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
* *
* Project home page: https://github.com/mik3y/usb-serial-for-android * Project home page: https://github.com/mik3y/usb-serial-for-android
*/ */
/*
* Ported to usb-serial-for-android
* by Felix Hädicke <felixhaedicke@web.de>
*
* Based on the pyprolific driver written
* by Emmanuel Blot <emmanuel.blot@free.fr>
* See https://github.com/eblot/pyftdi
*/
package com.hoho.android.usbserial.driver; 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.hardware.usb.UsbRequest;
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.nio.ByteBuffer;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -47,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;
@ -72,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 RESET_HXN_REQUEST = 0x07;
private static final int FLUSH_RX_REQUEST = 0x08; 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;
@ -105,25 +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 mReadEndpoint;
private UsbEndpoint mWriteEndpoint;
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) {
@ -135,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);
@ -193,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 {
@ -201,86 +199,79 @@ public class ProlificSerialDriver implements UsbSerialDriver {
mControlLinesValue = newControlLinesValue; mControlLinesValue = newControlLinesValue;
} }
private final void readStatusThreadFunction() { private void readStatusThreadFunction() {
try { try {
while (!mStopReadStatusThread) {
byte[] buffer = new byte[STATUS_BUFFER_SIZE]; byte[] buffer = new byte[STATUS_BUFFER_SIZE];
int readBytesCount = mConnection.bulkTransfer(mInterruptEndpoint, while (!mStopReadStatusThread) {
buffer, long endTime = MonotonicClock.millis() + 500;
STATUS_BUFFER_SIZE, int readBytesCount = mConnection.bulkTransfer(mInterruptEndpoint, buffer, STATUS_BUFFER_SIZE, 500);
500); if(readBytesCount == -1)
testConnection(MonotonicClock.millis() < endTime);
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) {
if (isOpen())
mReadStatusException = e; 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 open(UsbDeviceConnection connection) throws IOException { public void openInt() throws IOException {
if (mConnection != null) {
throw new IOException("Already open");
}
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");
} }
mConnection = connection;
boolean opened = false;
try {
for (int i = 0; i < usbInterface.getEndpointCount(); ++i) { for (int i = 0; i < usbInterface.getEndpointCount(); ++i) {
UsbEndpoint currentEndpoint = usbInterface.getEndpoint(i); UsbEndpoint currentEndpoint = usbInterface.getEndpoint(i);
@ -299,140 +290,130 @@ 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 int usbVersion = (rawDescriptors[3] << 8) + rawDescriptors[2];
= mConnection.getClass().getMethod("getRawDescriptors"); int deviceVersion = (rawDescriptors[13] << 8) + rawDescriptors[12];
byte[] rawDescriptors
= (byte[]) getRawDescriptorsMethod.invoke(mConnection);
byte maxPacketSize0 = rawDescriptors[7]; byte maxPacketSize0 = rawDescriptors[7];
if (maxPacketSize0 == 64) { if (mDevice.getDeviceClass() == 0x02 || maxPacketSize0 != 64) {
mDeviceType = DEVICE_TYPE_HX; mDeviceType = DeviceType.DEVICE_TYPE_01;
} else if ((mDevice.getDeviceClass() == 0x00) } else if(usbVersion == 0x200) {
|| (mDevice.getDeviceClass() == 0xff)) { if(deviceVersion == 0x300 && testHxStatus()) {
mDeviceType = DEVICE_TYPE_1; mDeviceType = DeviceType.DEVICE_TYPE_T; // TA
} else if(deviceVersion == 0x500 && testHxStatus()) {
mDeviceType = DeviceType.DEVICE_TYPE_T; // TB
} else { } else {
Log.w(TAG, "Could not detect PL2303 subtype, " mDeviceType = DeviceType.DEVICE_TYPE_HXN;
+ "Assuming that it is a HX device");
mDeviceType = DEVICE_TYPE_HX;
} }
} catch (NoSuchMethodException e) { } else {
Log.w(TAG, "Method UsbDeviceConnection.getRawDescriptors, " mDeviceType = DeviceType.DEVICE_TYPE_HX;
+ "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);
} }
} Log.d(TAG, String.format("usbVersion=%x, deviceVersion=%x, deviceClass=%d, packetSize=%d => deviceType=%s",
usbVersion, deviceVersion, mDevice.getDeviceClass(), maxPacketSize0, mDeviceType.name()));
setControlLines(mControlLinesValue);
resetDevice(); resetDevice();
doBlackMagic(); doBlackMagic();
opened = true; setControlLines(mControlLinesValue);
} finally { setFlowControl(mFlowControl);
if (!opened) {
mConnection = null;
connection.releaseInterface(usbInterface);
}
}
} }
@Override @Override
public void close() throws IOException { public void closeInt() {
if (mConnection == null) {
throw new IOException("Already closed");
}
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();
} finally { } catch(Exception ignored) {}
try { try {
mConnection.releaseInterface(mDevice.getInterface(0)); mConnection.releaseInterface(mDevice.getInterface(0));
} finally { } catch(Exception ignored) {}
mConnection = null;
}
}
} }
@Override private int filterBaudRate(int baudRate) {
public int read(byte[] dest, int timeoutMillis) throws IOException { if(BuildConfig.DEBUG && (baudRate & (3<<29)) == (1<<29)) {
final UsbRequest request = new UsbRequest(); return baudRate & ~(1<<29); // for testing purposes accept without further checks
try {
request.initialize(mConnection, mReadEndpoint);
final ByteBuffer buf = ByteBuffer.wrap(dest);
if (!request.queue(buf, dest.length)) {
throw new IOException("Error queueing request.");
} }
if (baudRate <= 0) {
final UsbRequest response = mConnection.requestWait(); throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
if (response == null) {
throw new IOException("Null response");
} }
if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) {
final int nread = buf.position(); return baudRate;
if (nread > 0) { }
//Log.d(TAG, HexDump.dumpHexString(dest, 0, Math.min(32, dest.length))); for(int br : standardBaudRates) {
return nread; 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 { } else {
return 0; while (mantissa >= 512) {
if (exponent < 7) {
mantissa >>= 2; /* divide by 4 */
exponent++;
} else { // < 45.8 baud
throw new UnsupportedOperationException("Baud rate to low");
} }
} finally {
request.close();
} }
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 int write(byte[] src, int timeoutMillis) throws IOException { public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException {
int offset = 0; baudRate = filterBaudRate(baudRate);
while (offset < src.length) {
final int writeLength;
final int amtWritten;
synchronized (mWriteBufferLock) {
final byte[] writeBuffer;
writeLength = Math.min(src.length - offset, mWriteBuffer.length);
if (offset == 0) {
writeBuffer = src;
} else {
// bulkTransfer does not support offsets, make a copy.
System.arraycopy(src, offset, mWriteBuffer, 0, writeLength);
writeBuffer = mWriteBuffer;
}
amtWritten = mConnection.bulkTransfer(mWriteEndpoint,
writeBuffer, writeLength, timeoutMillis);
}
if (amtWritten <= 0) {
throw new IOException("Error writing " + writeLength
+ " bytes at offset " + offset + " length="
+ src.length);
}
offset += amtWritten;
}
return offset;
}
@Override
public void setParameters(int baudRate, int dataBits, int stopBits,
int parity) throws IOException {
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
@ -440,7 +421,6 @@ public class ProlificSerialDriver implements UsbSerialDriver {
} }
byte[] lineRequestData = new byte[7]; byte[] lineRequestData = new byte[7];
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);
@ -450,44 +430,39 @@ public class ProlificSerialDriver implements UsbSerialDriver {
case STOPBITS_1: case STOPBITS_1:
lineRequestData[4] = 0; lineRequestData[4] = 0;
break; break;
case STOPBITS_1_5: case STOPBITS_1_5:
lineRequestData[4] = 1; lineRequestData[4] = 1;
break; break;
case STOPBITS_2: case STOPBITS_2:
lineRequestData[4] = 2; lineRequestData[4] = 2;
break; break;
default: default:
throw new IllegalArgumentException("Unknown stopBits value: " + stopBits); throw new IllegalArgumentException("Invalid stop bits: " + stopBits);
} }
switch (parity) { switch (parity) {
case PARITY_NONE: case PARITY_NONE:
lineRequestData[5] = 0; lineRequestData[5] = 0;
break; break;
case PARITY_ODD: case PARITY_ODD:
lineRequestData[5] = 1; lineRequestData[5] = 1;
break; break;
case PARITY_EVEN: case PARITY_EVEN:
lineRequestData[5] = 2; lineRequestData[5] = 2;
break; break;
case PARITY_MARK: case PARITY_MARK:
lineRequestData[5] = 3; lineRequestData[5] = 3;
break; break;
case PARITY_SPACE: case PARITY_SPACE:
lineRequestData[5] = 4; lineRequestData[5] = 4;
break; break;
default: default:
throw new IllegalArgumentException("Unknown parity value: " + parity); throw new IllegalArgumentException("Invalid parity: " + parity);
} }
if(dataBits < DATABITS_5 || dataBits > DATABITS_8) {
throw new IllegalArgumentException("Invalid data bits: " + dataBits);
}
lineRequestData[6] = (byte) dataBits; lineRequestData[6] = (byte) dataBits;
ctrlOut(SET_LINE_REQUEST, 0, 0, lineRequestData); ctrlOut(SET_LINE_REQUEST, 0, 0, lineRequestData);
@ -517,7 +492,7 @@ public class ProlificSerialDriver implements UsbSerialDriver {
@Override @Override
public boolean getDTR() throws IOException { public boolean getDTR() throws IOException {
return ((mControlLinesValue & CONTROL_DTR) == CONTROL_DTR); return (mControlLinesValue & CONTROL_DTR) != 0;
} }
@Override @Override
@ -538,7 +513,7 @@ public class ProlificSerialDriver implements UsbSerialDriver {
@Override @Override
public boolean getRTS() throws IOException { public boolean getRTS() throws IOException {
return ((mControlLinesValue & CONTROL_RTS) == CONTROL_RTS); return (mControlLinesValue & CONTROL_RTS) != 0;
} }
@Override @Override
@ -553,23 +528,91 @@ public class ProlificSerialDriver implements UsbSerialDriver {
} }
@Override @Override
public boolean purgeHwBuffers(boolean purgeReadBuffers, boolean purgeWriteBuffers) throws IOException { public EnumSet<ControlLine> getControlLines() throws IOException {
if (purgeReadBuffers) { int status = getStatus();
vendorOut(FLUSH_RX_REQUEST, 0, null); EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class);
if((mControlLinesValue & CONTROL_RTS) != 0) set.add(ControlLine.RTS);
if((status & STATUS_FLAG_CTS) != 0) set.add(ControlLine.CTS);
if((mControlLinesValue & CONTROL_DTR) != 0) set.add(ControlLine.DTR);
if((status & STATUS_FLAG_DSR) != 0) set.add(ControlLine.DSR);
if((status & STATUS_FLAG_CD) != 0) set.add(ControlLine.CD);
if((status & STATUS_FLAG_RI) != 0) set.add(ControlLine.RI);
return set;
} }
if (purgeWriteBuffers) { @Override
public EnumSet<ControlLine> getSupportedControlLines() throws IOException {
return EnumSet.allOf(ControlLine.class);
}
@Override
public void setFlowControl(FlowControl flowControl) throws IOException {
// vendorOut values from https://www.mail-archive.com/linux-usb@vger.kernel.org/msg110968.html
switch (flowControl) {
case NONE:
if (mDeviceType == DeviceType.DEVICE_TYPE_HXN)
vendorOut(0x0a, 0xff, null);
else
vendorOut(0, 0, null);
break;
case RTS_CTS:
if (mDeviceType == DeviceType.DEVICE_TYPE_HXN)
vendorOut(0x0a, 0xfa, null);
else
vendorOut(0, 0x61, null);
break;
case XON_XOFF_INLINE:
if (mDeviceType == DeviceType.DEVICE_TYPE_HXN)
vendorOut(0x0a, 0xee, null);
else
vendorOut(0, 0xc1, null);
break;
default:
throw new UnsupportedOperationException();
}
mFlowControl = flowControl;
}
@Override
public EnumSet<FlowControl> getSupportedFlowControl() {
return EnumSet.of(FlowControl.NONE, FlowControl.RTS_CTS, FlowControl.XON_XOFF_INLINE);
}
@Override
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); vendorOut(FLUSH_TX_REQUEST, 0, null);
} }
}
return purgeReadBuffers || purgeWriteBuffers; @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(Integer.valueOf(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

@ -1,21 +1,6 @@
/* Copyright 2011-2013 Google Inc. /* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com> * Copyright 2013 mike wakerly <opensource@hoho.com>
* *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
* Project home page: https://github.com/mik3y/usb-serial-for-android * Project home page: https://github.com/mik3y/usb-serial-for-android
*/ */
@ -36,47 +21,36 @@ 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; 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_HL340 = 0x7523; public static final int QINHENG_CH340 = 0x7523;
public static final int QINHENG_CH341A = 0x5523;
public static final int VENDOR_UNISOC = 0x1782;
public static final int FIBOCOM_L610 = 0x4D10;
public static final int FIBOCOM_L612 = 0x4D12;
// at www.linux-usb.org/usb.ids listed for NXP/LPC1768, but all processors supported by ARM mbed DAPLink firmware report these ids
public static final int VENDOR_ARM = 0x0d28;
public static final int ARM_MBED = 0x0204;
private UsbId() { private UsbId() {
throw new IllegalAccessError("Non-instantiable class."); throw new IllegalAccessError("Non-instantiable class");
} }
} }

View File

@ -1,21 +1,6 @@
/* Copyright 2011-2013 Google Inc. /* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com> * Copyright 2013 mike wakerly <opensource@hoho.com>
* *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
* Project home page: https://github.com/mik3y/usb-serial-for-android * Project home page: https://github.com/mik3y/usb-serial-for-android
*/ */
@ -25,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
@ -44,5 +34,5 @@ public interface UsbSerialDriver {
* *
* @return the ports * @return the ports
*/ */
public List<UsbSerialPort> getPorts(); List<UsbSerialPort> getPorts();
} }

View File

@ -1,100 +1,108 @@
/* Copyright 2011-2013 Google Inc. /* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com> * Copyright 2013 mike wakerly <opensource@hoho.com>
* *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
* Project home page: https://github.com/mik3y/usb-serial-for-android * Project home page: https://github.com/mik3y/usb-serial-for-android
*/ */
package com.hoho.android.usbserial.driver; package com.hoho.android.usbserial.driver;
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.IOException; import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.EnumSet;
/** /**
* Interface for a single serial port. * Interface for a single serial port.
* *
* @author mike wakerly (opensource@hoho.com) * @author mike wakerly (opensource@hoho.com)
*/ */
public interface UsbSerialPort { 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;
public UsbSerialDriver getDriver(); /** 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;
/**
* Returns the driver used by this port.
*/
UsbSerialDriver getDriver();
/**
* Returns the currently-bound USB device.
*/
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()}
* @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
@ -104,34 +112,58 @@ public interface UsbSerialPort {
* {@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. * 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.
* *
* @param dest the destination byte buffer * @param dest the destination byte buffer
* @param timeoutMillis the timeout for reading * @param timeout the timeout for reading in milliseconds, 0 is infinite
* @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 timeoutMillis) 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 timeoutMillis the timeout for writing * @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 timeoutMillis) 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.
@ -139,90 +171,154 @@ public interface UsbSerialPort {
* @param baudRate baud rate as an integer, for example {@code 115200}. * @param baudRate baud rate as an integer, for example {@code 115200}.
* @param dataBits one of {@link #DATABITS_5}, {@link #DATABITS_6}, * @param dataBits one of {@link #DATABITS_5}, {@link #DATABITS_6},
* {@link #DATABITS_7}, or {@link #DATABITS_8}. * {@link #DATABITS_7}, or {@link #DATABITS_8}.
* @param stopBits one of {@link #STOPBITS_1}, {@link #STOPBITS_1_5}, or * @param stopBits one of {@link #STOPBITS_1}, {@link #STOPBITS_1_5}, or {@link #STOPBITS_2}.
* {@link #STOPBITS_2}.
* @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_EVEN}, {@link #PARITY_MARK}, or {@link #PARITY_SPACE}.
* {@link #PARITY_SPACE}.
* @throws IOException on error setting the port parameters * @throws IOException on error setting the port parameters
* @throws UnsupportedOperationException if not supported or values are not supported by a specific device
*/ */
public void setParameters( void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException;
int baudRate, int dataBits, int stopBits, int parity) throws IOException;
/** /**
* Gets the CD (Carrier Detect) bit from the underlying UART. * Gets the CD (Carrier Detect) bit from the underlying UART.
* *
* @return the current state, or {@code false} if not supported. * @return the current state
* @throws IOException if an error occurred during reading * @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported
*/ */
public boolean getCD() throws IOException; boolean getCD() throws IOException;
/** /**
* Gets the CTS (Clear To Send) bit from the underlying UART. * Gets the CTS (Clear To Send) bit from the underlying UART.
* *
* @return the current state, or {@code false} if not supported. * @return the current state
* @throws IOException if an error occurred during reading * @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported
*/ */
public boolean getCTS() throws IOException; boolean getCTS() throws IOException;
/** /**
* Gets the DSR (Data Set Ready) bit from the underlying UART. * Gets the DSR (Data Set Ready) bit from the underlying UART.
* *
* @return the current state, or {@code false} if not supported. * @return the current state
* @throws IOException if an error occurred during reading * @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported
*/ */
public boolean getDSR() throws IOException; boolean getDSR() throws IOException;
/** /**
* Gets the DTR (Data Terminal Ready) bit from the underlying UART. * Gets the DTR (Data Terminal Ready) bit from the underlying UART.
* *
* @return the current state, or {@code false} if not supported. * @return the current state
* @throws IOException if an error occurred during reading * @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported
*/ */
public boolean getDTR() throws IOException; boolean getDTR() throws IOException;
/** /**
* Sets the DTR (Data Terminal Ready) bit on the underlying UART, if * Sets the DTR (Data Terminal Ready) bit on the underlying UART, if supported.
* supported.
* *
* @param value the value to set * @param value the value to set
* @throws IOException if an error occurred during writing * @throws IOException if an error occurred during writing
* @throws UnsupportedOperationException if not supported
*/ */
public void setDTR(boolean value) throws IOException; void setDTR(boolean value) throws IOException;
/** /**
* Gets the RI (Ring Indicator) bit from the underlying UART. * Gets the RI (Ring Indicator) bit from the underlying UART.
* *
* @return the current state, or {@code false} if not supported. * @return the current state
* @throws IOException if an error occurred during reading * @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported
*/ */
public boolean getRI() throws IOException; boolean getRI() throws IOException;
/** /**
* Gets the RTS (Request To Send) bit from the underlying UART. * Gets the RTS (Request To Send) bit from the underlying UART.
* *
* @return the current state, or {@code false} if not supported. * @return the current state
* @throws IOException if an error occurred during reading * @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported
*/ */
public boolean getRTS() throws IOException; boolean getRTS() throws IOException;
/** /**
* Sets the RTS (Request To Send) bit on the underlying UART, if * Sets the RTS (Request To Send) bit on the underlying UART, if supported.
* supported.
* *
* @param value the value to set * @param value the value to set
* @throws IOException if an error occurred during writing * @throws IOException if an error occurred during writing
* @throws UnsupportedOperationException if not supported
*/ */
public void setRTS(boolean value) throws IOException; void setRTS(boolean value) throws IOException;
/** /**
* Flush non-transmitted output data and / or non-read input data * Gets all control line values from the underlying UART, if supported.
* @param flushRX {@code true} to flush non-transmitted output data * Requires less USB calls than calling getRTS() + ... + getRI() individually.
* @param flushTX {@code true} to flush non-read input data *
* @return {@code true} if the operation was successful, or * @return EnumSet.contains(...) is {@code true} if set, else {@code false}
* {@code false} if the operation is not supported by the driver or device * @throws IOException if an error occurred during reading
* @throws IOException if an error occurred during flush * @throws UnsupportedOperationException if not supported
*/ */
public boolean purgeHwBuffers(boolean flushRX, boolean flushTX) throws IOException; EnumSet<ControlLine> getControlLines() throws IOException;
/**
* Gets all control line supported flags.
*
* @return EnumSet.contains(...) is {@code true} if supported, else {@code false}
* @throws IOException if an error occurred during reading
*/
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.
*
* @param purgeWriteBuffers {@code true} to discard non-transmitted output data
* @param purgeReadBuffers {@code true} to discard non-read input data
* @throws IOException if an error occurred during flush
* @throws UnsupportedOperationException if not supported
*/
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.
*/
boolean isOpen();
} }

View File

@ -1,21 +1,6 @@
/* Copyright 2011-2013 Google Inc. /* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com> * Copyright 2013 mike wakerly <opensource@hoho.com>
* *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
* Project home page: https://github.com/mik3y/usb-serial-for-android * Project home page: https://github.com/mik3y/usb-serial-for-android
*/ */
@ -52,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;
} }
@ -61,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);
@ -84,26 +71,15 @@ 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 {
final Constructor<? extends UsbSerialDriver> ctor = final Constructor<? extends UsbSerialDriver> ctor =
driverClass.getConstructor(UsbDevice.class); driverClass.getConstructor(UsbDevice.class);
driver = ctor.newInstance(usbDevice); driver = ctor.newInstance(usbDevice);
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException | IllegalArgumentException | InstantiationException |
throw new RuntimeException(e); IllegalAccessException | InvocationTargetException e) {
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
return driver; return driver;

View File

@ -1,46 +0,0 @@
/*
* Copyright 2011 Google Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*/
package com.hoho.android.usbserial.driver;
/**
* Generic unchecked exception for the usbserial package.
*
* @author mike wakerly (opensource@hoho.com)
*/
@SuppressWarnings("serial")
public class UsbSerialRuntimeException extends RuntimeException {
public UsbSerialRuntimeException() {
super();
}
public UsbSerialRuntimeException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
}
public UsbSerialRuntimeException(String detailMessage) {
super(detailMessage);
}
public UsbSerialRuntimeException(Throwable throwable) {
super(throwable);
}
}

View File

@ -16,15 +16,20 @@
package com.hoho.android.usbserial.util; package com.hoho.android.usbserial.util;
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);
} }
@ -32,17 +37,12 @@ public class HexDump {
public static String dumpHexString(byte[] array, int offset, int length) { public static String dumpHexString(byte[] array, int offset, int length) {
StringBuilder result = new StringBuilder(); StringBuilder result = new StringBuilder();
byte[] line = new byte[16]; byte[] line = new byte[8];
int lineIndex = 0; int lineIndex = 0;
result.append("\n0x");
result.append(toHexString(offset));
for (int i = offset; i < offset + length; i++) { for (int i = offset; i < offset + length; i++) {
if (lineIndex == 16) { if (lineIndex == line.length) {
result.append(" "); for (int j = 0; j < line.length; j++) {
for (int j = 0; j < 16; j++) {
if (line[j] > ' ' && line[j] < '~') { if (line[j] > ' ' && line[j] < '~') {
result.append(new String(line, j, 1)); result.append(new String(line, j, 1));
} else { } else {
@ -50,26 +50,21 @@ public class HexDump {
} }
} }
result.append("\n0x"); result.append("\n");
result.append(toHexString(i));
lineIndex = 0; lineIndex = 0;
} }
byte b = array[i]; byte b = array[i];
result.append(" ");
result.append(HEX_DIGITS[(b >>> 4) & 0x0F]); result.append(HEX_DIGITS[(b >>> 4) & 0x0F]);
result.append(HEX_DIGITS[b & 0x0F]); result.append(HEX_DIGITS[b & 0x0F]);
result.append(" ");
line[lineIndex++] = b; line[lineIndex++] = b;
} }
if (lineIndex != 16) { for (int i = 0; i < (line.length - lineIndex); i++) {
int count = (16 - lineIndex) * 3;
count++;
for (int i = 0; i < count; i++) {
result.append(" "); result.append(" ");
} }
for (int i = 0; i < lineIndex; i++) { for (int i = 0; i < lineIndex; i++) {
if (line[i] > ' ' && line[i] < '~') { if (line[i] > ' ' && line[i] < '~') {
result.append(new String(line, i, 1)); result.append(new String(line, i, 1));
@ -77,7 +72,6 @@ public class HexDump {
result.append("."); result.append(".");
} }
} }
}
return result.toString(); return result.toString();
} }
@ -91,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];
@ -145,16 +141,16 @@ public class HexDump {
if (c >= 'a' && c <= 'f') if (c >= 'a' && c <= 'f')
return (c - 'a' + 10); return (c - 'a' + 10);
throw new RuntimeException("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

@ -1,93 +1,76 @@
/* Copyright 2011-2013 Google Inc. /* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com> * Copyright 2013 mike wakerly <opensource@hoho.com>
* *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
* Project home page: https://github.com/mik3y/usb-serial-for-android * Project home page: https://github.com/mik3y/usb-serial-for-android
*/ */
package com.hoho.android.usbserial.util; package com.hoho.android.usbserial.util;
import android.hardware.usb.UsbRequest; 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()} * Utility class which services a {@link UsbSerialPort} in its {@link #runWrite()} ()} and {@link #runRead()} ()} ()} methods.
* method.
* *
* @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(); public enum State {
private static final boolean DEBUG = true;
private static final int READ_WAIT_MILLIS = 200;
private static final int BUFSIZ = 4096;
private final UsbSerialPort mDriver;
private final ByteBuffer mReadBuffer = ByteBuffer.allocate(BUFSIZ);
// Synchronized by 'mWriteBuffer'
private final ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ);
private enum State {
STOPPED, STOPPED,
STARTING,
RUNNING, RUNNING,
STOPPING STOPPING
} }
// Synchronized by 'this' public static boolean DEBUG = false;
private State mState = State.STOPPED;
// Synchronized by 'this' private static final String TAG = SerialInputOutputManager.class.getSimpleName();
private Listener mListener; private static final int BUFSIZ = 4096;
private int mReadTimeout = 0;
private int mWriteTimeout = 0;
private final Object mReadBufferLock = new Object();
private final Object mWriteBufferLock = new Object();
private ByteBuffer mReadBuffer; // default size = getReadEndpoint().getMaxPacketSize()
private ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ);
private int mThreadPriority = Process.THREAD_PRIORITY_URGENT_AUDIO;
private final AtomicReference<State> mState = new AtomicReference<>(State.STOPPED);
private CountDownLatch mStartuplatch = new CountDownLatch(2);
private Listener mListener; // Synchronized by 'this'
private final UsbSerialPort mSerialPort;
public interface Listener { public interface Listener {
/** /**
* 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 * Called when {@link SerialInputOutputManager#runRead()} ()} or {@link SerialInputOutputManager#runWrite()} ()} ()} aborts due to an error.
* error.
*/ */
public void onRunError(Exception e); void onRunError(Exception e);
} }
/** public SerialInputOutputManager(UsbSerialPort serialPort) {
* Creates a new instance with no listener. mSerialPort = serialPort;
*/ mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize());
public SerialInputOutputManager(UsbSerialPort driver) {
this(driver, null);
} }
/** public SerialInputOutputManager(UsbSerialPort serialPort, Listener listener) {
* Creates a new instance with the provided listener. mSerialPort = serialPort;
*/
public SerialInputOutputManager(UsbSerialPort driver, Listener listener) {
mDriver = driver;
mListener = listener; mListener = listener;
mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize());
} }
public synchronized void setListener(Listener listener) { public synchronized void setListener(Listener listener) {
@ -98,91 +81,256 @@ public class SerialInputOutputManager implements Runnable {
return mListener; return mListener;
} }
public void writeAsync(byte[] data) { /**
synchronized (mWriteBuffer) { * setThreadPriority. By default a higher priority than UI thread is used to prevent data loss
mWriteBuffer.put(data); *
* @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;
public synchronized void stop() {
if (getState() == State.RUNNING) {
Log.i(TAG, "Stop requested");
mState = State.STOPPING;
}
}
private synchronized State getState() {
return mState;
} }
/** /**
* Continuously services the read and write buffers until {@link #stop()} is * read/write timeout
* called, or until a driver exception is raised.
*
* NOTE(mikey): Uses inefficient read/write-with-timeout.
*/ */
@Override public void setReadTimeout(int timeout) {
public void run() { // when set if already running, read already blocks and the new value will not become effective now
synchronized (this) { if(mReadTimeout == 0 && timeout != 0 && mState.get() != State.STOPPED)
if (getState() != State.STOPPED) { throw new IllegalStateException("readTimeout only configurable before SerialInputOutputManager is started");
throw new IllegalStateException("Already running."); mReadTimeout = timeout;
}
mState = State.RUNNING;
} }
Log.i(TAG, "Running .."); public int getReadTimeout() {
return mReadTimeout;
}
public void setWriteTimeout(int timeout) {
mWriteTimeout = timeout;
}
public int getWriteTimeout() {
return mWriteTimeout;
}
/**
* read/write buffer size
*/
public void setReadBufferSize(int bufferSize) {
if (getReadBufferSize() == bufferSize)
return;
synchronized (mReadBufferLock) {
mReadBuffer = ByteBuffer.allocate(bufferSize);
}
}
public int getReadBufferSize() {
return mReadBuffer.capacity();
}
public void setWriteBufferSize(int bufferSize) {
if(getWriteBufferSize() == bufferSize)
return;
synchronized (mWriteBufferLock) {
ByteBuffer newWriteBuffer = ByteBuffer.allocate(bufferSize);
if(mWriteBuffer.position() > 0)
newWriteBuffer.put(mWriteBuffer.array(), 0, mWriteBuffer.position());
mWriteBuffer = newWriteBuffer;
}
}
public int getWriteBufferSize() {
return mWriteBuffer.capacity();
}
/**
* write data asynchronously
*/
public void writeAsync(byte[] data) {
synchronized (mWriteBufferLock) {
mWriteBuffer.put(data);
mWriteBufferLock.notifyAll(); // Notify waiting threads
}
}
/**
* start SerialInputOutputManager in separate threads
*/
public void start() {
if(mState.compareAndSet(State.STOPPED, State.STARTING)) {
mStartuplatch = new CountDownLatch(2);
new Thread(this::runRead, this.getClass().getSimpleName() + "_read").start();
new Thread(this::runWrite, this.getClass().getSimpleName() + "_write").start();
try { try {
while (true) { mStartuplatch.await();
if (getState() != State.RUNNING) { mState.set(State.RUNNING);
Log.i(TAG, "Stopping mState=" + getState()); } catch (InterruptedException e) {
break; Thread.currentThread().interrupt();
} }
step(); } else {
throw new IllegalStateException("already started");
} }
} catch (Exception e) { }
Log.w(TAG, "Run ending due to exception: " + e.getMessage(), e);
final Listener listener = getListener(); /**
* 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
}
Log.i(TAG, "Stop requested");
}
}
public State getState() {
return mState.get();
}
/**
* @return true if the thread is still running
*/
private boolean isStillRunning() {
State state = mState.get();
return ((state == State.RUNNING) || (state == State.STARTING))
&& !Thread.currentThread().isInterrupted();
}
/**
* Notify listener of an error
*
* @param e the exception
*/
private void notifyErrorListener(Throwable e) {
Listener listener = getListener();
if (listener != null) { if (listener != null) {
listener.onRunError(e); try {
} listener.onRunError(e instanceof Exception ? (Exception) e : new Exception(e));
} finally { } catch (Throwable t) {
synchronized (this) { Log.w(TAG, "Exception in onRunError: " + t.getMessage(), t);
mState = State.STOPPED;
Log.i(TAG, "Stopped.");
} }
} }
} }
private void step() throws IOException { /**
* Set the thread priority
*/
private void setThreadPriority() {
if (mThreadPriority != Process.THREAD_PRIORITY_DEFAULT) {
Process.setThreadPriority(mThreadPriority);
}
}
/**
* Continuously services the read buffers until {@link #stop()} is called, or until a driver exception is
* raised.
*/
void runRead() {
Log.i(TAG, "runRead running ...");
try {
setThreadPriority();
mStartuplatch.countDown();
do {
stepRead();
} while (isStillRunning());
Log.i(TAG, "runRead: Stopping mState=" + getState());
} catch (Throwable e) {
if (Thread.currentThread().isInterrupted()) {
Log.w(TAG, "runRead: interrupted");
} else if(mSerialPort.isOpen()) {
Log.w(TAG, "runRead ending due to exception: " + e.getMessage(), e);
} else {
Log.i(TAG, "runRead: Socket closed");
}
notifyErrorListener(e);
} finally {
if (mState.compareAndSet(State.RUNNING, State.STOPPING)) {
synchronized (mWriteBufferLock) {
mWriteBufferLock.notifyAll(); // wake up write thread to check the stop condition
}
} else if (mState.compareAndSet(State.STOPPING, State.STOPPED)) {
Log.i(TAG, "runRead: Stopped mState=" + getState());
}
}
}
/**
* Continuously services the write buffers until {@link #stop()} is called, or until a driver exception is
* raised.
*/
void runWrite() {
Log.i(TAG, "runWrite running ...");
try {
setThreadPriority();
mStartuplatch.countDown();
do {
stepWrite();
} while (isStillRunning());
Log.i(TAG, "runWrite: Stopping mState=" + getState());
} catch (Throwable e) {
if (Thread.currentThread().isInterrupted()) {
Log.w(TAG, "runWrite: interrupted");
} else if(mSerialPort.isOpen()) {
Log.w(TAG, "runWrite ending due to exception: " + e.getMessage(), e);
} else {
Log.i(TAG, "runWrite: Socket closed");
}
notifyErrorListener(e);
} finally {
if (!mState.compareAndSet(State.RUNNING, State.STOPPING)) {
if (mState.compareAndSet(State.STOPPING, State.STOPPED)) {
Log.i(TAG, "runWrite: Stopped mState=" + getState());
}
}
}
}
private void stepRead() throws IOException {
// Handle incoming data. // Handle incoming data.
int len = mDriver.read(mReadBuffer.array(), READ_WAIT_MILLIS); 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);
} }
mDriver.write(outBuff, READ_WAIT_MILLIS); 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());
}
}