diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..4a443ac --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,4 @@ +caches +codeStyles +libraries +workspace.xml diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 88cdcce..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -usb-serial-for-android \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index 1f2af51..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml deleted file mode 100644 index e7bedf3..0000000 --- a/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index aaaf922..047238c 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,38 +1,48 @@ - - + + + - - - - - - - - - - - + - - - - - 1.8 - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 50a9551..4b2b32b 100644 --- a/README.md +++ b/README.md @@ -1,143 +1,162 @@ -# usb-serial-for-android - -This is a driver library for communication with Arduinos and other USB serial hardware on -Android, using the -[Android USB Host API](http://developer.android.com/guide/topics/connectivity/usb/host.html) -available on Android 3.1+. - -No root access, ADK, or special kernel drivers are required; all drivers are implemented in -Java. You get a raw serial port with `read()`, `write()`, and other basic -functions for use with your own protocols. - -* **Homepage**: https://github.com/mik3y/usb-serial-for-android -* **Google group**: http://groups.google.com/group/usb-serial-for-android -* **Latest release**: [v0.1.0](https://github.com/mik3y/usb-serial-for-android/releases) - -## Quick Start - -**1.** [Link your project](https://github.com/mik3y/usb-serial-for-android/wiki/Building-From-Source) to the library. - -**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. - -**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). - -```xml - - - - - - -``` - -**4.** Use it! Example code snippet: - -```java -// Find all available drivers from attached devices. -UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE); -List availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager); -if (availableDrivers.isEmpty()) { - return; -} - -// Open a connection to the first available driver. -UsbSerialDriver driver = availableDrivers.get(0); -UsbDeviceConnection connection = manager.openDevice(driver.getDevice()); -if (connection == null) { - // You probably need to call UsbManager.requestPermission(driver.getDevice(), ..) - return; -} - -// Read some data! Most have just one port (port 0). -UsbSerialPort port = driver.getPorts().get(0); -try { - port.open(connection); - port.setParameters(115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE); - - byte buffer[] = new byte[16]; - int numBytesRead = port.read(buffer, 1000); - Log.d(TAG, "Read " + numBytesRead + " bytes."); -} catch (IOException e) { - // Deal with error. -} finally { - port.close(); -} -``` - -For a more complete example, see the -[UsbSerialExamples project](https://github.com/mik3y/usb-serial-for-android/blob/master/usbSerialExamples) -in git, which is a simple application for reading and showing serial data. - -A [simple Arduino application](https://github.com/mik3y/usb-serial-for-android/blob/master/arduino) -is also available which can be used for testing. - - -## Probing for Unrecognized Devices - -Sometimes you may need to do a little extra work to support devices which -usb-serial-for-android doesn't [yet] know about -- but which you know to be -compatible with one of the built-in drivers. This may be the case for a brand -new device or for one using a custom VID/PID pair. - -UsbSerialProber is a class to help you find and instantiate compatible -UsbSerialDrivers from the tree of connected UsbDevices. Normally, you will use -the default prober returned by ``UsbSerialProber.getDefaultProber()``, which -uses the built-in list of well-known VIDs and PIDs that are supported by our -drivers. - -To use your own set of rules, create and use a custom prober: - -```java -// Probe for our custom CDC devices, which use VID 0x1234 -// and PIDS 0x0001 and 0x0002. -ProbeTable customTable = new ProbeTable(); -customTable.addProduct(0x1234, 0x0001, CdcAcmSerialDriver.class); -customTable.addProduct(0x1234, 0x0002, CdcAcmSerialDriver.class); - -UsbSerialProber prober = new UsbSerialProber(customTable); -List drivers = prober.findAllDrivers(usbManager); -// ... -``` - -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 -a compatible UsbDevice. - - -## Compatible Devices - -* *Serial chips:* FT232R, CDC/ACM (eg Arduino Uno) and possibly others. - See [CompatibleSerialDevices](https://github.com/mik3y/usb-serial-for-android/wiki/Compatible-Serial-Devices). -* *Android phones and tablets:* Nexus 7, Motorola Xoom, and many others. - See [CompatibleAndroidDevices](https://github.com/mik3y/usb-serial-for-android/wiki/Compatible-Android-Devices). - - -## Author, License, and Copyright - -usb-serial-for-android is written and maintained by *mike wakerly*. - -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 - -For common problems, see the -[Troubleshooting](https://github.com/mik3y/usb-serial-for-android/wiki/Troubleshooting) -wiki page. - -For other help and discussion, please join our Google Group, -[usb-serial-for-android](https://groups.google.com/forum/?fromgroups#!forum/usb-serial-for-android). - -Are you using the library? Let us know on the group and we'll add your project to -[ProjectsUsingUsbSerialForAndroid](https://github.com/mik3y/usb-serial-for-android/wiki/Projects-Using-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&utm_medium=referral&utm_content=mik3y/usb-serial-for-android&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 + +This is a driver library for communication with Arduinos and other USB serial hardware on +Android, using the +[Android USB Host Mode (OTG)](http://developer.android.com/guide/topics/connectivity/usb/host.html) +available since Android 3.1 and working reliably since Android 4.2. + +No root access, ADK, or special kernel drivers are required; all drivers are implemented in +Java. You get a raw serial port with `read()`, `write()`, and other basic +functions for use with your own protocols. + +## Quick Start + +**1.** Add library to your project: + +Add jitpack.io repository to your root build.gradle: +```gradle +allprojects { + repositories { + ... + maven { url 'https://jitpack.io' } + } +} +``` +Add library to dependencies +```gradle +dependencies { + implementation 'com.github.mik3y:usb-serial-for-android:Tag' +} +``` + +**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 + + + + + + +``` + +**3.** Use it! Example code snippet: + +```java + // Find all available drivers from attached devices. + UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE); + List availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager); + if (availableDrivers.isEmpty()) { + return; + } + + // Open a connection to the first available driver. + UsbSerialDriver driver = availableDrivers.get(0); + UsbDeviceConnection connection = manager.openDevice(driver.getDevice()); + if (connection == null) { + // add UsbManager.requestPermission(driver.getDevice(), ..) handling here + return; + } + + UsbSerialPort port = driver.getPorts().get(0); // Most devices have just one port (port 0) + port.open(connection); + port.setParameters(115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE); + usbIoManager = new SerialInputOutputManager(usbSerialPort, this); + Executors.newSingleThreadExecutor().submit(usbIoManager); +``` +```java + port.write("hello".getBytes(), WRITE_WAIT_MILLIS); +``` +```java +@Override +public void onNewData(byte[] data) { + runOnUiThread(() -> { textView.append(new String(data)); }); +} +``` +```java + port.close(); +``` + + +For a simple example, see +[UsbSerialExamples](https://github.com/mik3y/usb-serial-for-android/blob/master/usbSerialExamples) +folder in this project. + +For a more complete example, see separate github project +[SimpleUsbTerminal](https://github.com/kai-morich/SimpleUsbTerminal). + +## Probing for Unrecognized Devices + +Sometimes you may need to do a little extra work to support devices which +usb-serial-for-android doesn't (yet) know about -- but which you know to be +compatible with one of the built-in drivers. This may be the case for a brand +new device or for one using a custom VID/PID pair. + +UsbSerialProber is a class to help you find and instantiate compatible +UsbSerialDrivers from the tree of connected UsbDevices. Normally, you will use +the default prober returned by ``UsbSerialProber.getDefaultProber()``, which +uses the built-in list of well-known VIDs and PIDs that are supported by our +drivers. + +To use your own set of rules, create and use a custom prober: + +```java +// Probe for our custom CDC devices, which use VID 0x1234 +// and PIDS 0x0001 and 0x0002. +ProbeTable customTable = new ProbeTable(); +customTable.addProduct(0x1234, 0x0001, CdcAcmSerialDriver.class); +customTable.addProduct(0x1234, 0x0002, CdcAcmSerialDriver.class); + +UsbSerialProber prober = new UsbSerialProber(customTable); +List drivers = prober.findAllDrivers(usbManager); +// ... +``` + +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 +a compatible UsbDevice. + +## Compatible Devices + +This library supports USB to serial converter chips: +* FTDI FT232, FT2232, ... +* Prolific PL2303 +* Silabs CP2102, CP2105, ... +* Qinheng CH340 + +and devices implementing the CDC/ACM protocol like +* Arduino using ATmega32U4 +* 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 + +For common problems, see the +[Troubleshooting](https://github.com/mik3y/usb-serial-for-android/wiki/Troubleshooting) +wiki page. + +Are you using the library? Add your project to +[ProjectsUsingUsbSerialForAndroid](https://github.com/mik3y/usb-serial-for-android/wiki/Projects-Using-usb-serial-for-android). diff --git a/arduino/serial_test.ino b/arduino/serial_test.ino deleted file mode 100644 index 6964001..0000000 --- a/arduino/serial_test.ino +++ /dev/null @@ -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); -} diff --git a/build.gradle b/build.gradle index 0c42278..1d6c342 100644 --- a/build.gradle +++ b/build.gradle @@ -2,15 +2,17 @@ buildscript { repositories { - mavenCentral() + jcenter() + google() } dependencies { - classpath 'com.android.tools.build:gradle:1.2.3' + classpath 'com.android.tools.build:gradle:3.5.1' } } allprojects { repositories { - mavenCentral() + jcenter() + google() } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 8c0fb64..5c2d1cf 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f3d36d0..14dc15c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Jun 23 00:11:28 EDT 2015 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip +#Sun Oct 06 09:46:24 CEST 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip diff --git a/gradlew b/gradlew index 91a7e26..b0d6d0a 100755 --- a/gradlew +++ b/gradlew @@ -1,4 +1,20 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# 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. +# ############################################################################## ## @@ -6,47 +22,6 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn ( ) { - echo "$*" -} - -die ( ) { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; -esac - -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" @@ -61,9 +36,49 @@ while [ -h "$PRG" ] ; do fi done SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- +cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" -cd "$SAVED" >&- +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -90,7 +105,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -114,6 +129,7 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` @@ -154,11 +170,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index aec9973..15e1ee3 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem http://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -8,14 +24,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +62,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +75,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/test/arduino_leonardo_bridge/arduino_leonardo_bridge.ino b/test/arduino_leonardo_bridge/arduino_leonardo_bridge.ino new file mode 100644 index 0000000..e7071d9 --- /dev/null +++ b/test/arduino_leonardo_bridge/arduino_leonardo_bridge.ino @@ -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()); +} diff --git a/test/rfc2217_server.diff b/test/rfc2217_server.diff new file mode 100644 index 0000000..a5d09a2 --- /dev/null +++ b/test/rfc2217_server.diff @@ -0,0 +1,42 @@ +*** /n/archiv/python/rfc2217_server.py 2018-03-10 09:02:07.613771600 +0100 +--- rfc2217_server.py 2018-03-09 20:57:44.933717100 +0100 +*************** +*** 26,31 **** +--- 26,32 ---- + self, + logger=logging.getLogger('rfc2217.server') if debug else None) + self.log = logging.getLogger('redirector') ++ self.dlog = logging.getLogger('data') + + def statusline_poller(self): + self.log.debug('status line poll thread started') +*************** +*** 55,60 **** +--- 56,62 ---- + try: + data = self.serial.read(self.serial.in_waiting or 1) + if data: ++ self.dlog.debug("serial read: "+data.encode('hex')) + # escape outgoing data when needed (Telnet IAC (0xff) character) + self.write(b''.join(self.rfc2217.escape(data))) + except socket.error as msg: +*************** +*** 76,81 **** +--- 78,84 ---- + data = self.socket.recv(1024) + if not data: + break ++ self.dlog.debug("socket read: "+data.encode('hex')) + self.serial.write(b''.join(self.rfc2217.filter(data))) + except socket.error as msg: + self.log.error('{}'.format(msg)) +*************** +*** 132,137 **** +--- 135,141 ---- + logging.basicConfig(level=logging.INFO) + #~ logging.getLogger('root').setLevel(logging.INFO) + logging.getLogger('rfc2217').setLevel(level) ++ logging.getLogger('data').setLevel(level) + + # connect to serial port + ser = serial.serial_for_url(args.SERIALPORT, do_not_open=True) diff --git a/usbSerialExamples/build.gradle b/usbSerialExamples/build.gradle index 2da9b1c..5f27979 100644 --- a/usbSerialExamples/build.gradle +++ b/usbSerialExamples/build.gradle @@ -1,15 +1,15 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 22 - buildToolsVersion "22.0.1" + compileSdkVersion 28 + buildToolsVersion '28.0.3' defaultConfig { - minSdkVersion 14 - targetSdkVersion 22 + minSdkVersion 17 + targetSdkVersion 28 - testApplicationId "com.hoho.android.usbserial.examples" testInstrumentationRunner "android.test.InstrumentationTestRunner" + missingDimensionStrategy 'device', 'anyDevice' } buildTypes { @@ -20,5 +20,5 @@ android { } dependencies { - compile project(':usbSerialForAndroid') + implementation project(':usbSerialForAndroid') } diff --git a/usbSerialExamples/src/main/AndroidManifest.xml b/usbSerialExamples/src/main/AndroidManifest.xml index 26ca72c..efa205f 100644 --- a/usbSerialExamples/src/main/AndroidManifest.xml +++ b/usbSerialExamples/src/main/AndroidManifest.xml @@ -4,8 +4,6 @@ android:versionCode="1" android:versionName="1.0" > - - mEntries = new ArrayList(); private ArrayAdapter mAdapter; @@ -89,11 +96,25 @@ public class DeviceListActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); + final Context context = this; mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE); - mListView = (ListView) findViewById(R.id.deviceList); - mProgressBar = (ProgressBar) findViewById(R.id.progressBar); - mProgressBarTitle = (TextView) findViewById(R.id.progressBarTitle); + mListView = findViewById(R.id.deviceList); + mProgressBar = findViewById(R.id.progressBar); + mProgressBarTitle = findViewById(R.id.progressBarTitle); + + mUsbReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if(intent.getAction().equals(INTENT_ACTION_GRANT_USB)) { + if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { + showConsoleActivity(mSerialPort); + } else { + Toast.makeText(context, "USB permission denied", Toast.LENGTH_SHORT).show(); + } + } + } + }; mAdapter = new ArrayAdapter(this, android.R.layout.simple_expandable_list_item_2, mEntries) { @@ -112,9 +133,7 @@ public class DeviceListActivity extends Activity { 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())); + final String title = String.format("Vendor %4X Product %4X", device.getVendorId(), device.getProductId()); row.getText1().setText(title); final String subtitle = driver.getClass().getSimpleName(); @@ -135,8 +154,14 @@ public class DeviceListActivity extends Activity { return; } - final UsbSerialPort port = mEntries.get(position); - showConsoleActivity(port); + mSerialPort = mEntries.get(position); + UsbDevice device = mSerialPort.getDriver().getDevice(); + if (!mUsbManager.hasPermission(device)) { + PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(context, 0, new Intent(INTENT_ACTION_GRANT_USB), 0); + mUsbManager.requestPermission(device, usbPermissionIntent); + } else { + showConsoleActivity(mSerialPort); + } } }); } @@ -145,12 +170,14 @@ public class DeviceListActivity extends Activity { protected void onResume() { super.onResume(); mHandler.sendEmptyMessage(MESSAGE_REFRESH); + registerReceiver(mUsbReceiver, new IntentFilter(INTENT_ACTION_GRANT_USB)); } @Override protected void onPause() { super.onPause(); mHandler.removeMessages(MESSAGE_REFRESH); + unregisterReceiver(mUsbReceiver); } private void refreshDeviceList() { diff --git a/usbSerialExamples/src/main/java/src/com/hoho/android/usbserial/examples/SerialConsoleActivity.java b/usbSerialExamples/src/main/java/com/hoho/android/usbserial/examples/SerialConsoleActivity.java similarity index 100% rename from usbSerialExamples/src/main/java/src/com/hoho/android/usbserial/examples/SerialConsoleActivity.java rename to usbSerialExamples/src/main/java/com/hoho/android/usbserial/examples/SerialConsoleActivity.java diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/util/HexDump.java b/usbSerialExamples/src/main/java/com/hoho/android/usbserial/util/HexDump.java similarity index 100% rename from usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/util/HexDump.java rename to usbSerialExamples/src/main/java/com/hoho/android/usbserial/util/HexDump.java diff --git a/usbSerialForAndroid/build.gradle b/usbSerialForAndroid/build.gradle index c3d835c..5aa0170 100644 --- a/usbSerialForAndroid/build.gradle +++ b/usbSerialForAndroid/build.gradle @@ -1,14 +1,17 @@ apply plugin: 'com.android.library' -apply plugin: 'maven' -apply plugin: 'signing' android { - compileSdkVersion 19 - buildToolsVersion "19.1" + compileSdkVersion 28 + buildToolsVersion '28.0.3' defaultConfig { - minSdkVersion 12 - targetSdkVersion 19 + minSdkVersion 17 + targetSdkVersion 28 + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArguments = [ // Raspi Windows LinuxVM ... + 'rfc2217_server_host': '192.168.0.100', + 'rfc2217_server_nonstandard_baudrates': 'true', // true false false + ] } buildTypes { @@ -19,81 +22,14 @@ android { } } -group = "com.hoho.android" -version = "0.2.0-SNAPSHOT" - -configurations { - archives { - extendsFrom configurations.default - } +dependencies { + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support:support-annotations:28.0.0' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'commons-net:commons-net:3.6' + androidTestImplementation 'org.apache.commons:commons-lang3:3.8.1' } -signing { - required { has("release") && gradle.taskGraph.hasTask("uploadArchives") } - sign configurations.archives -} +//apply from: 'publishToMavenLocal.gradle' -def getRepositoryUsername() { - return hasProperty('sonatypeUsername') ? sonatypeUsername : "" -} - -def getRepositoryPassword() { - return hasProperty('sonatypePassword') ? sonatypePassword : "" -} - -def isReleaseBuild() { - return version.contains("SNAPSHOT") == false -} - -uploadArchives { - def sonatypeRepositoryUrl - if (isReleaseBuild()) { - println 'RELEASE BUILD' - sonatypeRepositoryUrl = hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL - : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" - } else { - println 'SNAPSHOT BUILD' - sonatypeRepositoryUrl = hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL - : "https://oss.sonatype.org/content/repositories/snapshots/" - } - - configuration = configurations.archives - repositories.mavenDeployer { - beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } - - repository(url: sonatypeRepositoryUrl) { - authentication(userName: getRepositoryUsername(), - password: getRepositoryPassword()) - } - - pom.artifactId = 'usb-serial-for-android' - pom.project { - name 'usb-serial-for-android' - packaging 'aar' - description 'USB Serial Driver Library for Android' - url 'https://github.com/mik3y/usb-serial-for-android' - - scm { - url 'scm:git@github.com:mik3y/usb-serial-for-android.git' - connection 'scm:git@github.com:mik3y/usb-serial-for-android.git' - developerConnection 'scm:git@github.com:mik3y/usb-serial-for-android.git' - } - - licenses { - license { - name 'GNU LGPL v2.1' - url 'http://www.gnu.org/licenses/lgpl-2.1.txt' - distribution 'repo' - } - } - - developers { - developer { - id 'mik3y' - name 'mik3y' - email 'opensource@hoho.com' - } - } - } - } -} +//apply from: 'coverage.gradle' diff --git a/usbSerialForAndroid/coverage.gradle b/usbSerialForAndroid/coverage.gradle new file mode 100644 index 0000000..f5c6fa4 --- /dev/null +++ b/usbSerialForAndroid/coverage.gradle @@ -0,0 +1,45 @@ +apply plugin: 'jacoco' + +android { + flavorDimensions 'device' + productFlavors { + anyDevice { + // Used as fallback in usbSerialExample/build.gradle -> missingDimensionStrategy, but not for coverage report + dimension 'device' + } + arduino { + 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'] + } + 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'] + } + ft2232 { // second port + dimension 'device' + testInstrumentationRunnerArguments = ['test_device_driver': 'Ftdi', 'test_device_port': '1'] + } + pl2302 { + dimension 'device' + testInstrumentationRunnerArguments = ['test_device_driver': 'Prolific'] + } + } + + buildTypes { + debug { + testCoverageEnabled true + } + } +} diff --git a/usbSerialForAndroid/publishToMavenLocal.gradle b/usbSerialForAndroid/publishToMavenLocal.gradle new file mode 100644 index 0000000..1212e0d --- /dev/null +++ b/usbSerialForAndroid/publishToMavenLocal.gradle @@ -0,0 +1,20 @@ +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 +} diff --git a/usbSerialForAndroid/src/androidTest/AndroidManifest.xml b/usbSerialForAndroid/src/androidTest/AndroidManifest.xml new file mode 100644 index 0000000..51ad082 --- /dev/null +++ b/usbSerialForAndroid/src/androidTest/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/DeviceTest.java b/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/DeviceTest.java new file mode 100644 index 0000000..4d92811 --- /dev/null +++ b/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/DeviceTest.java @@ -0,0 +1,1200 @@ +/* + * restrictions + * - as real hardware is used, timing might need tuning. see: + * - Thread.sleep(...) + * - obj.wait(...) + * - missing functionality on certain devices, see: + * - if(rfc2217_server_nonstandard_baudrates) + * - if(usbSerialDriver instanceof ...) + * + */ +package com.hoho.android.usbserial; + +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.Process; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import android.util.Log; + +import com.hoho.android.usbserial.driver.CdcAcmSerialDriver; +import com.hoho.android.usbserial.driver.Ch34xSerialDriver; +import com.hoho.android.usbserial.driver.Cp21xxSerialDriver; +import com.hoho.android.usbserial.driver.FtdiSerialDriver; +import com.hoho.android.usbserial.driver.ProbeTable; +import com.hoho.android.usbserial.driver.ProlificSerialDriver; +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.SerialInputOutputManager; + +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 org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Executors; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +@RunWith(AndroidJUnit4.class) +public class DeviceTest implements SerialInputOutputManager.Listener { + + // testInstrumentationRunnerArguments configuration + private static String rfc2217_server_host; + private static int rfc2217_server_port = 2217; + private static boolean rfc2217_server_nonstandard_baudrates; + private static String test_device_driver; + private static int test_device_port; + + private final static int TELNET_READ_WAIT = 500; + private final static int TELNET_COMMAND_WAIT = 2000; + private final static int USB_READ_WAIT = 500; + private final static int USB_WRITE_WAIT = 500; + private final static Integer SERIAL_INPUT_OUTPUT_MANAGER_THREAD_PRIORITY = Process.THREAD_PRIORITY_URGENT_AUDIO; + + private final static String TAG = "DeviceTest"; + 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 Context context; + private UsbManager usbManager; + private UsbSerialDriver usbSerialDriver; + private UsbDeviceConnection usbDeviceConnection; + private UsbSerialPort usbSerialPort; + private SerialInputOutputManager usbIoManager; + private final Deque usbReadBuffer = new LinkedList<>(); + private Exception usbReadError; + private boolean usbReadBlock = false; + private long usbReadTime = 0; + + private static TelnetClient telnetClient; + private static InputStream telnetReadStream; + private static OutputStream telnetWriteStream; + private static Integer[] telnetComPortOptionCounter = {0}; + private int telnetWriteDelay = 0; + private boolean isCp21xxRestrictedPort = false; // second port of Cp2105 has limited dataBits, stopBits, parity + + @BeforeClass + public static void setUpFixture() throws Exception { + rfc2217_server_host = InstrumentationRegistry.getArguments().getString("rfc2217_server_host"); + rfc2217_server_nonstandard_baudrates = Boolean.valueOf(InstrumentationRegistry.getArguments().getString("rfc2217_server_nonstandard_baudrates")); + test_device_driver = InstrumentationRegistry.getArguments().getString("test_device_driver"); + test_device_port = Integer.valueOf(InstrumentationRegistry.getArguments().getString("test_device_port","0")); + + // postpone parts of fixture setup to first test, because exceptions are not reported for @BeforeClass + // and test terminates with misleading 'Empty test suite' + telnetClient = null; + } + + public static 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) { + telnetComPortOptionCounter[0] += 1; + return super.answerSubnegotiation(suboptionData, suboptionLength); + } + }); + + telnetClient.setConnectTimeout(2000); + telnetClient.connect(rfc2217_server_host, rfc2217_server_port); + telnetClient.setTcpNoDelay(true); + telnetWriteStream = telnetClient.getOutputStream(); + telnetReadStream = telnetClient.getInputStream(); + } + + @Before + 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 + telnetComPortOptionCounter[0] = 0; + telnetClient.sendCommand((byte)TelnetCommand.SB); + telnetWriteStream.write(new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_PURGE_DATA, 3}); + telnetClient.sendCommand((byte)TelnetCommand.SE); + for(int i=0; i availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(usbManager); + assertEquals("no USB device found", 1, availableDrivers.size()); + usbSerialDriver = availableDrivers.get(0); + if(test_device_driver != null) { + String driverName = usbSerialDriver.getClass().getSimpleName(); + assertEquals(test_device_driver+"SerialDriver", driverName); + } + assertTrue( usbSerialDriver.getPorts().size() > test_device_port); + usbSerialPort = usbSerialDriver.getPorts().get(test_device_port); + Log.i(TAG, "Using USB device "+ usbSerialPort.toString()+" driver="+usbSerialDriver.getClass().getSimpleName()); + isCp21xxRestrictedPort = usbSerialDriver instanceof Cp21xxSerialDriver && usbSerialDriver.getPorts().size()==2 && test_device_port == 1; + + if (!usbManager.hasPermission(usbSerialPort.getDriver().getDevice())) { + final Boolean[] granted = {Boolean.FALSE}; + BroadcastReceiver usbReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + granted[0] = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false); + synchronized (granted) { + granted.notify(); + } + } + }; + PendingIntent permissionIntent = PendingIntent.getBroadcast(context, 0, new Intent("com.android.example.USB_PERMISSION"), 0); + IntentFilter filter = new IntentFilter("com.android.example.USB_PERMISSION"); + context.registerReceiver(usbReceiver, filter); + usbManager.requestPermission(usbSerialDriver.getDevice(), permissionIntent); + synchronized (granted) { + granted.wait(5000); + } + assertTrue("USB permission dialog not confirmed", granted[0]); + } + usbOpen(true); + } + + @After + public void tearDown() throws IOException { + try { + usbRead(0); + } catch (Exception ignored) {} + try { + telnetRead(0); + } catch (Exception ignored) {} + usbClose(); + usbSerialDriver = null; + } + + @AfterClass + public static void tearDownFixture() throws Exception { + try { + telnetClient.disconnect(); + } catch (Exception ignored) {} + telnetReadStream = null; + telnetWriteStream = null; + telnetClient = null; + } + + // wait full time + private byte[] telnetRead() throws Exception { + return telnetRead(-1); + } + + private byte[] telnetRead(int expectedLength) throws Exception { + long end = System.currentTimeMillis() + TELNET_READ_WAIT; + ByteBuffer buf = ByteBuffer.allocate(4096); + while(System.currentTimeMillis() < end) { + if(telnetReadStream.available() > 0) { + buf.put((byte) telnetReadStream.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; + } + + private void telnetWrite(byte[] data) throws Exception{ + if(telnetWriteDelay != 0) { + for(byte b : data) { + telnetWriteStream.write(b); + telnetWriteStream.flush(); + Thread.sleep(telnetWriteDelay); + } + } else { + telnetWriteStream.write(data); + telnetWriteStream.flush(); + } + } + + private void usbClose() { + if (usbIoManager != null) { + usbIoManager.setListener(null); + usbIoManager.stop(); + } + if (usbSerialPort != null) { + try { + usbSerialPort.setDTR(false); + usbSerialPort.setRTS(false); + } catch (Exception ignored) { + } + try { + usbSerialPort.close(); + } catch (IOException ignored) { + } + usbSerialPort = null; + } + if(usbDeviceConnection != null) + usbDeviceConnection.close(); + usbDeviceConnection = null; + if(usbIoManager != null) { + for(int i=0; i<2000; i++) { + if(SerialInputOutputManager.State.STOPPED == usbIoManager.getState()) break; + try { + Thread.sleep(1); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + assertEquals(SerialInputOutputManager.State.STOPPED, usbIoManager.getState()); + usbIoManager = null; + } + } + + private void usbOpen(boolean withIoManager) throws Exception { + usbDeviceConnection = usbManager.openDevice(usbSerialDriver.getDevice()); + usbSerialPort = usbSerialDriver.getPorts().get(test_device_port); + usbSerialPort.open(usbDeviceConnection); + usbSerialPort.setDTR(true); + usbSerialPort.setRTS(true); + if(withIoManager) { + usbIoManager = new SerialInputOutputManager(usbSerialPort, this) { + @Override + public void run() { + if (SERIAL_INPUT_OUTPUT_MANAGER_THREAD_PRIORITY != null) + Process.setThreadPriority(SERIAL_INPUT_OUTPUT_MANAGER_THREAD_PRIORITY); + super.run(); + } + }; + Executors.newSingleThreadExecutor().submit(usbIoManager); + } + synchronized (usbReadBuffer) { + usbReadBuffer.clear(); + } + usbReadError = null; + } + + // wait full time + private byte[] usbRead() throws Exception { + return usbRead(-1); + } + + private byte[] usbRead(int expectedLength) throws Exception { + long end = System.currentTimeMillis() + USB_READ_WAIT; + ByteBuffer buf = ByteBuffer.allocate(8192); + if(usbIoManager != null) { + while (System.currentTimeMillis() < end) { + if(usbReadError != null) + throw usbReadError; + synchronized (usbReadBuffer) { + while(usbReadBuffer.peek() != null) + buf.put(usbReadBuffer.remove()); + } + if (expectedLength >= 0 && buf.position() >= expectedLength) + break; + Thread.sleep(1); + } + + } else { + byte[] b1 = new byte[256]; + while (System.currentTimeMillis() < end) { + int len = usbSerialPort.read(b1, USB_READ_WAIT / 10); + if (len > 0) { + buf.put(b1, 0, len); + } else { + if (expectedLength >= 0 && buf.position() >= expectedLength) + break; + Thread.sleep(1); + } + } + } + byte[] data = new byte[buf.position()]; + buf.flip(); + buf.get(data); + return data; + } + + private void usbWrite(byte[] data) throws IOException { + usbSerialPort.write(data, USB_WRITE_WAIT); + } + + private void usbParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException, InterruptedException { + usbSerialPort.setParameters(baudRate, dataBits, stopBits, parity); + if(usbSerialDriver instanceof CdcAcmSerialDriver) + Thread.sleep(10); // arduino_leonardeo_bridge.ini needs some time + else + Thread.sleep(1); + } + + private void telnetParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException, InterruptedException, InvalidTelnetOptionException { + telnetComPortOptionCounter[0] = 0; + + telnetClient.sendCommand((byte)TelnetCommand.SB); + telnetWriteStream.write(new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_BAUDRATE, (byte)(baudRate>>24), (byte)(baudRate>>16), (byte)(baudRate>>8), (byte)baudRate}); + telnetClient.sendCommand((byte)TelnetCommand.SE); + + telnetClient.sendCommand((byte)TelnetCommand.SB); + telnetWriteStream.write(new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_DATASIZE, (byte)dataBits}); + telnetClient.sendCommand((byte)TelnetCommand.SE); + + telnetClient.sendCommand((byte)TelnetCommand.SB); + telnetWriteStream.write(new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_STOPSIZE, (byte)stopBits}); + telnetClient.sendCommand((byte)TelnetCommand.SE); + + telnetClient.sendCommand((byte)TelnetCommand.SB); + telnetWriteStream.write(new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_PARITY, (byte)(parity+1)}); + telnetClient.sendCommand((byte)TelnetCommand.SE); + + // windows does not like nonstandard baudrates. rfc2217_server.py terminates w/o response + for(int i=0; i 64) { + Log.d(TAG, "usb read: time+=" + String.format("%-3d",now-usbReadTime) + " len=" + String.format("%-4d",data.length) + " data=" + new String(data, 0, 32) + "..." + new String(data, data.length-32, 32)); + } else { + Log.d(TAG, "usb read: time+=" + String.format("%-3d",now-usbReadTime) + " len=" + String.format("%-4d",data.length) + " data=" + new String(data)); + } + usbReadTime = now; + + while(usbReadBlock) + try { + Thread.sleep(1); + } catch (InterruptedException e) { + e.printStackTrace(); + } + synchronized (usbReadBuffer) { + usbReadBuffer.add(data); + } + } + + @Override + public void onRunError(Exception e) { + usbReadError = e; + //fail("usb connection lost"); + } + + // clone of org.apache.commons.lang3.StringUtils.indexOfDifference + optional startpos + private static int indexOfDifference(final CharSequence cs1, final CharSequence cs2) { + return indexOfDifference(cs1, cs2, 0, 0); + } + + private static int indexOfDifference(final CharSequence cs1, final CharSequence cs2, int cs1startpos, int cs2startpos) { + if (cs1 == cs2) { + return -1; + } + if (cs1 == null || cs2 == null) { + return 0; + } + if(cs1startpos < 0 || cs2startpos < 0) + return -1; + int i, j; + for (i = cs1startpos, j = cs2startpos; i < cs1.length() && j < cs2.length(); ++i, ++j) { + if (cs1.charAt(i) != cs2.charAt(j)) { + break; + } + } + if (j < cs2.length() || i < cs1.length()) { + return i; + } + return -1; + } + + private void logDifference(final StringBuilder data, final StringBuilder expected) { + int datapos = indexOfDifference(data, expected); + int expectedpos = datapos; + while(datapos != -1) { + int nextexpectedpos = -1; + int nextdatapos = datapos + 2; + int len = -1; + if(nextdatapos + 10 < data.length()) { // try to sync data+expected, assuming that data is lost, but not corrupted + String nextsub = data.substring(nextdatapos, nextdatapos + 10); + nextexpectedpos = expected.indexOf(nextsub, expectedpos); + if(nextexpectedpos >= 0) { + len = nextexpectedpos - expectedpos - 2; + } + } + Log.i(TAG, "difference at " + datapos + " len " + len ); + Log.d(TAG, " got " + data.substring(Math.max(datapos - 20, 0), Math.min(datapos + 20, data.length()))); + Log.d(TAG, " expected " + expected.substring(Math.max(expectedpos - 20, 0), Math.min(expectedpos + 20, expected.length()))); + datapos = indexOfDifference(data, expected, nextdatapos, nextexpectedpos); + expectedpos = nextexpectedpos + (datapos - nextdatapos); + } + } + + private void doReadWrite(String reason) throws Exception { + byte[] buf1 = new byte[]{ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16}; + byte[] buf2 = new byte[]{ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26}; + byte[] data; + + telnetWrite(buf1); + data = usbRead(buf1.length); + assertThat(reason, data, equalTo(buf1)); // includes array content in output + //assertArrayEquals("net2usb".getBytes(), data); // only includes array length in output + usbWrite(buf2); + data = telnetRead(buf2.length); + assertThat(reason, data, equalTo(buf2)); + } + + @Test + public void openClose() throws Exception { + byte[] data; + telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); + usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); + doReadWrite(""); + + try { + usbSerialPort.open(usbDeviceConnection); + fail("already open expected"); + } catch (IOException ignored) { + } + doReadWrite(""); + + usbSerialPort.close(); + try { + usbSerialPort.close(); + fail("already closed expected"); + } catch (IOException ignored) { + } + try { + usbWrite(new byte[]{0x00}); + fail("write error expected"); + } catch (IOException ignored) { + } catch (NullPointerException ignored) { + } + try { + usbRead(1); + //fail("read error expected"); + } catch (IOException ignored) { + } catch (NullPointerException ignored) { + } + try { + usbParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE); + fail("error expected"); + } catch (IOException ignored) { + } catch (NullPointerException ignored) { + } + + // partial re-open not supported + try { + usbSerialPort.open(usbDeviceConnection); + //usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); + doReadWrite(""); + fail("re-open not supported"); + } catch (IOException ignored) { + } + try { + usbSerialPort.close(); + } catch (IOException ignored) { + } + + if (usbSerialDriver instanceof Cp21xxSerialDriver) { // why needed? + usbIoManager.stop(); + usbIoManager = null; + } + // full re-open supported + usbClose(); + usbOpen(true); + telnetParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE); + usbParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE); + doReadWrite(""); + } + + @Test + public void baudRate() throws Exception { + if (false) { // default baud rate + // CP2102: only works if first connection after attaching device + // PL2303, FTDI: it's not 9600 + telnetParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE); + + doReadWrite(""); + } + + // invalid values + try { + usbParameters(-1, 8, 1, UsbSerialPort.PARITY_NONE); + if (usbSerialDriver instanceof Ch34xSerialDriver) + ; // todo: add range check in driver + else if (usbSerialDriver instanceof FtdiSerialDriver) + ; // todo: add range check in driver + else if (usbSerialDriver instanceof ProlificSerialDriver) + ; // todo: add range check in driver + else if (usbSerialDriver instanceof Cp21xxSerialDriver) + ; // todo: add range check in driver + else if (usbSerialDriver instanceof CdcAcmSerialDriver) + ; // todo: add range check in driver + else + fail("invalid baudrate 0"); + } catch (IOException ignored) { // cp2105 second port + } catch (IllegalArgumentException ignored) { + } + try { + usbParameters(0, 8, 1, UsbSerialPort.PARITY_NONE); + if (usbSerialDriver instanceof ProlificSerialDriver) + ; // todo: add range check in driver + else if (usbSerialDriver instanceof Cp21xxSerialDriver) + ; // todo: add range check in driver + else if (usbSerialDriver instanceof CdcAcmSerialDriver) + ; // todo: add range check in driver + else + fail("invalid baudrate 0"); + } catch (ArithmeticException ignored) { // ch340 + } catch (IOException ignored) { // cp2105 second port + } catch (IllegalArgumentException ignored) { + } + try { + usbParameters(1, 8, 1, UsbSerialPort.PARITY_NONE); + if (usbSerialDriver instanceof FtdiSerialDriver) + ; + else if (usbSerialDriver instanceof ProlificSerialDriver) + ; + else if (usbSerialDriver instanceof Cp21xxSerialDriver) + ; + else if (usbSerialDriver instanceof CdcAcmSerialDriver) + ; + else + fail("invalid baudrate 0"); + } catch (IOException ignored) { // ch340 + } catch (IllegalArgumentException ignored) { + } + try { + usbParameters(2<<31, 8, 1, UsbSerialPort.PARITY_NONE); + if (usbSerialDriver instanceof ProlificSerialDriver) + ; + else if (usbSerialDriver instanceof Cp21xxSerialDriver) + ; + else if (usbSerialDriver instanceof CdcAcmSerialDriver) + ; + else + fail("invalid baudrate 2^31"); + } catch (ArithmeticException ignored) { // ch340 + } catch (IOException ignored) { // cp2105 second port + } catch (IllegalArgumentException ignored) { + } + + for(int baudRate : new int[] {300, 2400, 19200, 115200} ) { + if(baudRate == 300 && isCp21xxRestrictedPort) { + try { + usbParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE); + fail("baudrate 300 on cp21xx restricted port"); + } catch (IOException ignored) { + } + continue; + } + telnetParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE); + usbParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE); + + doReadWrite(baudRate+"/8N1"); + } + if(rfc2217_server_nonstandard_baudrates && !isCp21xxRestrictedPort) { + // usbParameters does not fail on devices that do not support nonstandard baud rates + usbParameters(42000, 8, 1, UsbSerialPort.PARITY_NONE); + telnetParameters(42000, 8, 1, UsbSerialPort.PARITY_NONE); + + byte[] buf1 = "abc".getBytes(); + byte[] buf2 = "ABC".getBytes(); + byte[] data1, data2; + usbWrite(buf1); + data1 = telnetRead(); + telnetWrite(buf2); + data2 = usbRead(); + if (usbSerialDriver instanceof ProlificSerialDriver) { + // not supported + assertNotEquals(data1, buf2); + assertNotEquals(data2, buf2); + } else if (usbSerialDriver instanceof Cp21xxSerialDriver) { + if (usbSerialDriver.getPorts().size() > 1) { + // supported on cp2105 first port + assertThat("42000/8N1", data1, equalTo(buf1)); + assertThat("42000/8N1", data2, equalTo(buf2)); + } else { + // not supported on cp2102 + assertNotEquals(data1, buf1); + assertNotEquals(data2, buf2); + } + } else { + assertThat("42000/8N1", data1, equalTo(buf1)); + assertThat("42000/8N1", data2, equalTo(buf2)); + } + } + { // non matching baud rate + telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); + usbParameters(2400, 8, 1, UsbSerialPort.PARITY_NONE); + + byte[] data; + telnetWrite("net2usb".getBytes()); + data = usbRead(); + assertNotEquals(7, data.length); + usbWrite("usb2net".getBytes()); + data = telnetRead(); + assertNotEquals(7, data.length); + } + } + + @Test + public void dataBits() throws Exception { + byte[] data; + + for(int i: new int[] {0, 4, 9}) { + try { + usbParameters(19200, i, 1, UsbSerialPort.PARITY_NONE); + if (usbSerialDriver instanceof ProlificSerialDriver) + ; // todo: add range check in driver + else if (usbSerialDriver instanceof CdcAcmSerialDriver) + ; // todo: add range check in driver + else + fail("invalid databits "+i); + } catch (IllegalArgumentException ignored) { + } + } + + // telnet -> usb + usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); + telnetParameters(19200, 7, 1, UsbSerialPort.PARITY_NONE); + telnetWrite(new byte[] {0x00}); + Thread.sleep(1); // one bit is 0.05 milliseconds long, wait >> stop bit + telnetWrite(new byte[] {(byte)0xff}); + data = usbRead(2); + assertThat("19200/7N1", data, equalTo(new byte[] {(byte)0x80, (byte)0xff})); + + telnetParameters(19200, 6, 1, UsbSerialPort.PARITY_NONE); + telnetWrite(new byte[] {0x00}); + Thread.sleep(1); + telnetWrite(new byte[] {(byte)0xff}); + data = usbRead(2); + assertThat("19000/6N1", data, equalTo(new byte[] {(byte)0xc0, (byte)0xff})); + + telnetParameters(19200, 5, 1, UsbSerialPort.PARITY_NONE); + telnetWrite(new byte[] {0x00}); + Thread.sleep(1); + telnetWrite(new byte[] {(byte)0xff}); + data = usbRead(2); + assertThat("19000/5N1", data, equalTo(new byte[] {(byte)0xe0, (byte)0xff})); + + // usb -> telnet + try { + telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); + usbParameters(19200, 7, 1, UsbSerialPort.PARITY_NONE); + usbWrite(new byte[]{0x00}); + Thread.sleep(1); + usbWrite(new byte[]{(byte) 0xff}); + data = telnetRead(2); + assertThat("19000/7N1", data, equalTo(new byte[]{(byte) 0x80, (byte) 0xff})); + } catch (IllegalArgumentException e) { + if(!isCp21xxRestrictedPort) + throw e; + } + try { + usbParameters(19200, 6, 1, UsbSerialPort.PARITY_NONE); + usbWrite(new byte[]{0x00}); + Thread.sleep(1); + usbWrite(new byte[]{(byte) 0xff}); + data = telnetRead(2); + assertThat("19000/6N1", data, equalTo(new byte[]{(byte) 0xc0, (byte) 0xff})); + } catch (IllegalArgumentException e) { + if (!(isCp21xxRestrictedPort || usbSerialDriver instanceof FtdiSerialDriver)) + throw e; + } + try { + usbParameters(19200, 5, 1, UsbSerialPort.PARITY_NONE); + usbWrite(new byte[] {0x00}); + Thread.sleep(1); + usbWrite(new byte[] {(byte)0xff}); + data = telnetRead(2); + assertThat("19000/5N1", data, equalTo(new byte[] {(byte)0xe0, (byte)0xff})); + } catch (IllegalArgumentException e) { + if (!(isCp21xxRestrictedPort || usbSerialDriver instanceof FtdiSerialDriver)) + throw e; + } + } + + @Test + public void parity() throws Exception { + byte[] _8n1 = {(byte)0x00, (byte)0x01, (byte)0xfe, (byte)0xff}; + byte[] _7n1 = {(byte)0x00, (byte)0x01, (byte)0x7e, (byte)0x7f}; + byte[] _7o1 = {(byte)0x80, (byte)0x01, (byte)0xfe, (byte)0x7f}; + byte[] _7e1 = {(byte)0x00, (byte)0x81, (byte)0x7e, (byte)0xff}; + byte[] _7m1 = {(byte)0x80, (byte)0x81, (byte)0xfe, (byte)0xff}; + byte[] _7s1 = {(byte)0x00, (byte)0x01, (byte)0x7e, (byte)0x7f}; + byte[] data; + + for(int i: new int[] {-1, 5}) { + try { + usbParameters(19200, 8, 1, i); + fail("invalid parity "+i); + } catch (IllegalArgumentException ignored) { + } + } + if(isCp21xxRestrictedPort) { + usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); + usbParameters(19200, 8, 1, UsbSerialPort.PARITY_EVEN); + usbParameters(19200, 8, 1, UsbSerialPort.PARITY_ODD); + try { + usbParameters(19200, 8, 1, UsbSerialPort.PARITY_MARK); + fail("parity mark"); + } catch (IllegalArgumentException ignored) {} + try { + usbParameters(19200, 8, 1, UsbSerialPort.PARITY_SPACE); + fail("parity space"); + } catch (IllegalArgumentException ignored) {} + return; + // test below not possible as it requires unsupported 7 dataBits + } + + // usb -> telnet + telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); + usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); + usbWrite(_8n1); + data = telnetRead(4); + assertThat("19200/8N1", data, equalTo(_8n1)); + + usbParameters(19200, 7, 1, UsbSerialPort.PARITY_ODD); + usbWrite(_8n1); + data = telnetRead(4); + assertThat("19200/7O1", data, equalTo(_7o1)); + + usbParameters(19200, 7, 1, UsbSerialPort.PARITY_EVEN); + usbWrite(_8n1); + data = telnetRead(4); + assertThat("19200/7E1", data, equalTo(_7e1)); + + if (usbSerialDriver instanceof CdcAcmSerialDriver) { + // not supported by arduino_leonardo_bridge.ino, other devices might support it + } else { + usbParameters(19200, 7, 1, UsbSerialPort.PARITY_MARK); + usbWrite(_8n1); + data = telnetRead(4); + assertThat("19200/7M1", data, equalTo(_7m1)); + + usbParameters(19200, 7, 1, UsbSerialPort.PARITY_SPACE); + usbWrite(_8n1); + data = telnetRead(4); + assertThat("19200/7S1", data, equalTo(_7s1)); + } + + // telnet -> usb + usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); + telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); + telnetWrite(_8n1); + data = usbRead(4); + assertThat("19200/8N1", data, equalTo(_8n1)); + + telnetParameters(19200, 7, 1, UsbSerialPort.PARITY_ODD); + telnetWrite(_8n1); + data = usbRead(4); + assertThat("19200/7O1", data, equalTo(_7o1)); + + telnetParameters(19200, 7, 1, UsbSerialPort.PARITY_EVEN); + telnetWrite(_8n1); + data = usbRead(4); + assertThat("19200/7E1", data, equalTo(_7e1)); + + if (usbSerialDriver instanceof CdcAcmSerialDriver) { + // not supported by arduino_leonardo_bridge.ino, other devices might support it + } else { + telnetParameters(19200, 7, 1, UsbSerialPort.PARITY_MARK); + telnetWrite(_8n1); + data = usbRead(4); + assertThat("19200/7M1", data, equalTo(_7m1)); + + telnetParameters(19200, 7, 1, UsbSerialPort.PARITY_SPACE); + telnetWrite(_8n1); + data = usbRead(4); + assertThat("19200/7S1", data, equalTo(_7s1)); + + usbParameters(19200, 7, 1, UsbSerialPort.PARITY_ODD); + telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); + telnetWrite(_8n1); + data = usbRead(4); + assertThat("19200/8N1", data, equalTo(_7n1)); // read is resilient against errors + } + } + + @Test + public void stopBits() throws Exception { + byte[] data; + + for (int i : new int[]{0, 4}) { + try { + usbParameters(19200, 8, i, UsbSerialPort.PARITY_NONE); + fail("invalid stopbits " + i); + } catch (IllegalArgumentException ignored) { + } + } + + if (usbSerialDriver instanceof CdcAcmSerialDriver) { + usbParameters(19200, 8, UsbSerialPort.STOPBITS_2, UsbSerialPort.PARITY_NONE); + // software based bridge in arduino_leonardo_bridge.ino is to slow for real test, other devices might support it + } else { + // shift stopbits into next byte, by using different databits + // a - start bit (0) + // o - stop bit (1) + // d - data bit + + // out 8N2: addddddd doaddddddddo + // 1000001 0 10001111 + // in 6N1: addddddo addddddo + // 100000 101000 + usbParameters(19200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE); + telnetParameters(19200, 6, 1, UsbSerialPort.PARITY_NONE); + usbWrite(new byte[]{(byte)0x41, (byte)0xf1}); + data = telnetRead(2); + assertThat("19200/8N1", data, equalTo(new byte[]{1, 5})); + + // out 8N2: addddddd dooaddddddddoo + // 1000001 0 10011111 + // in 6N1: addddddo addddddo + // 100000 110100 + try { + usbParameters(19200, 8, UsbSerialPort.STOPBITS_2, UsbSerialPort.PARITY_NONE); + telnetParameters(19200, 6, 1, UsbSerialPort.PARITY_NONE); + usbWrite(new byte[]{(byte) 0x41, (byte) 0xf9}); + data = telnetRead(2); + assertThat("19200/8N1", data, equalTo(new byte[]{1, 11})); + } catch(IllegalArgumentException e) { + if(!isCp21xxRestrictedPort) + throw e; + } + try { + usbParameters(19200, 8, UsbSerialPort.STOPBITS_1_5, UsbSerialPort.PARITY_NONE); + // todo: could create similar test for 1.5 stopbits, by reading at double speed + // but only some devices support 1.5 stopbits and it is basically not used any more + } catch(IllegalArgumentException ignored) { + } + } + } + + + @Test + public void probeTable() throws Exception { + class DummyDriver implements UsbSerialDriver { + @Override + public UsbDevice getDevice() { return null; } + @Override + public List getPorts() { return null; } + } + List availableDrivers; + ProbeTable probeTable = new ProbeTable(); + UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE); + availableDrivers = new UsbSerialProber(probeTable).findAllDrivers(usbManager); + assertEquals(0, availableDrivers.size()); + + probeTable.addProduct(0, 0, DummyDriver.class); + availableDrivers = new UsbSerialProber(probeTable).findAllDrivers(usbManager); + assertEquals(0, availableDrivers.size()); + + probeTable.addProduct(usbSerialDriver.getDevice().getVendorId(), usbSerialDriver.getDevice().getProductId(), usbSerialDriver.getClass()); + availableDrivers = new UsbSerialProber(probeTable).findAllDrivers(usbManager); + assertEquals(1, availableDrivers.size()); + assertEquals(availableDrivers.get(0).getClass(), usbSerialDriver.getClass()); + } + + @Test + // provoke data loss, when data is not read fast enough + public void readBufferOverflow() throws Exception { + if(usbSerialDriver instanceof CdcAcmSerialDriver) + telnetWriteDelay = 10; // arduino_leonardo_bridge.ino sends each byte in own USB packet, which is horribly slow + usbParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE); + telnetParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE); + + StringBuilder expected = new StringBuilder(); + StringBuilder data = new StringBuilder(); + final int maxWait = 2000; + int bufferSize; + for(bufferSize = 8; bufferSize < (2<<15); bufferSize *= 2) { + int linenr; + String line="-"; + expected.setLength(0); + data.setLength(0); + + Log.i(TAG, "bufferSize " + bufferSize); + usbReadBlock = true; + for (linenr = 0; linenr < bufferSize/8; linenr++) { + line = String.format("%07d,", linenr); + telnetWrite(line.getBytes()); + expected.append(line); + } + usbReadBlock = false; + + // slowly write new data, until old data is completely read from buffer and new data is received + boolean found = false; + for (; linenr < bufferSize/8 + maxWait/10 && !found; linenr++) { + line = String.format("%07d,", linenr); + telnetWrite(line.getBytes()); + Thread.sleep(10); + expected.append(line); + data.append(new String(usbRead(0))); + found = data.toString().endsWith(line); + } + while(!found) { + // use waiting read to clear input queue, else next test would see unexpected data + byte[] rest = usbRead(-1); + if(rest.length == 0) + fail("last line "+line+" not found"); + data.append(new String(rest)); + found = data.toString().endsWith(line); + } + if (data.length() != expected.length()) + break; + } + + logDifference(data, expected); + assertTrue(bufferSize > 16); + assertTrue(data.length() != expected.length()); + } + + @Test + public void readSpeed() throws Exception { + // see logcat for performance results + // + // CDC arduino_leonardo_bridge.ini has transfer speed ~ 100 byte/sec + // all other devices are near physical limit with ~ 10-12k/sec + // + // readBufferOverflow provokes read errors, but they can also happen here where the data is actually read fast enough. + // Android is not a real time OS, so there is no guarantee that the USB thread is scheduled, or it might be blocked by Java garbage collection. + // Using SERIAL_INPUT_OUTPUT_MANAGER_THREAD_PRIORITY=THREAD_PRIORITY_URGENT_AUDIO sometimes reduced errors by factor 10, sometimes not at all! + // + int baudrate = 115200; + usbParameters(baudrate, 8, 1, UsbSerialPort.PARITY_NONE); + telnetParameters(baudrate, 8, 1, UsbSerialPort.PARITY_NONE); + + int writeSeconds = 5; + int writeAhead = 5*baudrate/10; // write ahead for another 5 second read + if(usbSerialDriver instanceof CdcAcmSerialDriver) + writeAhead = 50; + + int linenr = 0; + String line=""; + StringBuilder data = new StringBuilder(); + StringBuilder expected = new StringBuilder(); + int dlen = 0, elen = 0; + Log.i(TAG, "readSpeed: 'read' should be near "+baudrate/10); + long begin = System.currentTimeMillis(); + long next = System.currentTimeMillis(); + for(int seconds=1; seconds <= writeSeconds; seconds++) { + next += 1000; + while (System.currentTimeMillis() < next) { + if((writeAhead < 0) || (expected.length() < data.length() + writeAhead)) { + line = String.format("%07d,", linenr++); + telnetWrite(line.getBytes()); + expected.append(line); + } else { + Thread.sleep(0, 100000); + } + data.append(new String(usbRead(0))); + } + Log.i(TAG, "readSpeed: t="+(next-begin)+", read="+(data.length()-dlen)+", write="+(expected.length()-elen)); + dlen = data.length(); + elen = expected.length(); + } + + boolean found = false; + while(!found) { + // use waiting read to clear input queue, else next test would see unexpected data + byte[] rest = usbRead(-1); + if(rest.length == 0) + break; + data.append(new String(rest)); + found = data.toString().endsWith(line); + } + logDifference(data, expected); + } + + @Test + public void writeSpeed() throws Exception { + // see logcat for performance results + // + // CDC arduino_leonardo_bridge.ini has transfer speed ~ 100 byte/sec + // all other devices can get near physical limit: + // longlines=true:, speed is near physical limit at 11.5k + // longlines=false: speed is 3-4k for all devices, as more USB packets are required + usbParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE); + telnetParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE); + boolean longlines = !(usbSerialDriver instanceof CdcAcmSerialDriver); + + int linenr = 0; + String line=""; + StringBuilder data = new StringBuilder(); + StringBuilder expected = new StringBuilder(); + int dlen = 0, elen = 0; + Log.i(TAG, "writeSpeed: 'write' should be near "+115200/10); + long begin = System.currentTimeMillis(); + long next = System.currentTimeMillis(); + for(int seconds=1; seconds<=5; seconds++) { + next += 1000; + while (System.currentTimeMillis() < next) { + if(longlines) + line = String.format("%060d,", linenr++); + else + line = String.format("%07d,", linenr++); + usbWrite(line.getBytes()); + expected.append(line); + data.append(new String(telnetRead(0))); + } + Log.i(TAG, "writeSpeed: t="+(next-begin)+", write="+(expected.length()-elen)+", read="+(data.length()-dlen)); + dlen = data.length(); + elen = expected.length(); + } + boolean found = false; + for (linenr=0; linenr < 2000 && !found; linenr++) { + data.append(new String(telnetRead(0))); + Thread.sleep(1); + found = data.toString().endsWith(line); + } + next = System.currentTimeMillis(); + Log.i(TAG, "writeSpeed: t="+(next-begin)+", read="+(data.length()-dlen)); + assertTrue(found); + int pos = indexOfDifference(data, expected); + if(pos!=-1) { + + Log.i(TAG, "writeSpeed: first difference at " + pos); + String datasub = data.substring(Math.max(pos - 20, 0), Math.min(pos + 20, data.length())); + String expectedsub = expected.substring(Math.max(pos - 20, 0), Math.min(pos + 20, expected.length())); + assertThat(datasub, equalTo(expectedsub)); + } + } + + @Test + public void purgeHwBuffers() throws Exception { + // 2400 is slowest baud rate for isCp21xxRestrictedPort + usbParameters(2400, 8, 1, UsbSerialPort.PARITY_NONE); + telnetParameters(2400, 8, 1, UsbSerialPort.PARITY_NONE); + byte[] buf = new byte[64]; + for(int i=0; i " + data.length()); + + assertTrue(data.length() > 5); + if(purged) + assertTrue(data.length() < buf.length+1); + else + assertEquals(data.length(), buf.length + 3); + + // todo: purge receive buffer + } + + @Test + // WriteAsync rarely makes sense, as data is not written until something is read + public void writeAsync() throws Exception { + if (usbSerialDriver instanceof FtdiSerialDriver) + return; // periodically sends status messages, so does not block here + usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); + telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); + + SerialInputOutputManager ioManager; + ioManager = new SerialInputOutputManager(null); + assertEquals(null, ioManager.getListener()); + ioManager.setListener(this); + assertEquals(this, ioManager.getListener()); + ioManager = new SerialInputOutputManager(null, this); + assertEquals(this, ioManager.getListener()); + + byte[] data, buf = new byte[]{1}; + int len; + usbIoManager.writeAsync(buf); + usbIoManager.writeAsync(buf); + data = telnetRead(1); + assertEquals(0, data.length); + telnetWrite(buf); + data = usbRead(1); + assertEquals(1, data.length); + data = telnetRead(2); + assertEquals(2, data.length); + } + + @Test + // Blocking read should be avoided in the UI thread, as it makes the app unresponsive. + // You better use the SerialInputOutputManager. + // + // With the change from bulkTransfer to queued requests, the read timeout has no effect + // and the call blocks until close() if no data is available! + // The change from bulkTransfer to queued requests was necessary to prevent data loss. + public void readSync() throws Exception { + if (usbSerialDriver instanceof FtdiSerialDriver) + return; // periodically sends status messages, so does not block here + + Runnable closeThread = new Runnable() { + @Override + public void run() { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + usbClose(); + } + }; + + usbClose(); + usbOpen(false); + usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); + telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); + + byte[] buf = new byte[]{1}; + int len; + long time; + telnetWrite(buf); + len = usbSerialPort.read(buf, 0); // not blocking because data is available + assertEquals(1, len); + + time = System.currentTimeMillis(); + Executors.newSingleThreadExecutor().submit(closeThread); + len = usbSerialPort.read(buf, 0); // blocking until close() + assertEquals(0, len); + assertTrue(System.currentTimeMillis()-time >= 100); + + usbOpen(false); + usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); + telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); + + time = System.currentTimeMillis(); + Executors.newSingleThreadExecutor().submit(closeThread); + len = usbSerialPort.read(buf, 10); // timeout not used any more -> blocking until close() + assertEquals(0, len); + assertTrue(System.currentTimeMillis()-time >= 100); + } +} diff --git a/usbSerialForAndroid/src/main/AndroidManifest.xml b/usbSerialForAndroid/src/main/AndroidManifest.xml index 9355f7b..e579693 100644 --- a/usbSerialForAndroid/src/main/AndroidManifest.xml +++ b/usbSerialForAndroid/src/main/AndroidManifest.xml @@ -1,7 +1,4 @@ - + package="com.hoho.android.usbserial"> diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/BuildInfo.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/BuildInfo.java deleted file mode 100644 index 1f0d363..0000000 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/BuildInfo.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.hoho.android.usbserial; - -/** - * Static container of information about this library. - */ -public final class BuildInfo { - - /** - * The current version of this library. Values are of the form - * "major.minor.micro[-suffix]". A suffix of "-pre" indicates a pre-release - * of the version preceeding it. - */ - public static final String VERSION = "0.2.0-pre"; - - private BuildInfo() { - throw new IllegalStateException("Non-instantiable class."); - } - -} diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java index 4fd84bd..1e02232 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java @@ -27,7 +27,6 @@ import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbRequest; -import android.os.Build; import android.util.Log; import java.io.IOException; @@ -51,6 +50,7 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { private final UsbDevice mDevice; private final UsbSerialPort mPort; + private UsbRequest mUsbRequest; public CdcAcmSerialDriver(UsbDevice device) { mDevice = device; @@ -69,7 +69,6 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { class CdcAcmSerialPort extends CommonUsbSerialPort { - private final boolean mEnableAsyncReads; private UsbInterface mControlInterface; private UsbInterface mDataInterface; @@ -77,6 +76,8 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { private UsbEndpoint mReadEndpoint; private UsbEndpoint mWriteEndpoint; + private int mControlIndex; + private boolean mRts = false; private boolean mDtr = false; @@ -90,7 +91,6 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { public CdcAcmSerialPort(UsbDevice device, int portNumber) { super(device, portNumber); - mEnableAsyncReads = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1); } @Override @@ -116,13 +116,6 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { openInterface(); } - if (mEnableAsyncReads) { - Log.d(TAG, "Async reads enabled"); - } else { - Log.d(TAG, "Async reads disabled."); - } - - opened = true; } finally { if (!opened) { @@ -139,6 +132,7 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { // the following code is inspired by the cdc-acm driver // in the linux kernel + mControlIndex = 0; mControlInterface = mDevice.getInterface(0); Log.d(TAG, "Control iface=" + mControlInterface); @@ -196,34 +190,63 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { private void openInterface() throws IOException { Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount()); - mControlInterface = mDevice.getInterface(0); + mControlInterface = null; + mDataInterface = null; + for (int i = 0; i < mDevice.getInterfaceCount(); i++) { + UsbInterface usbInterface = mDevice.getInterface(i); + if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM) { + mControlIndex = i; + mControlInterface = usbInterface; + } + if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) { + mDataInterface = usbInterface; + } + } + + if(mControlInterface == null) { + throw new IOException("no control interface."); + } Log.d(TAG, "Control iface=" + mControlInterface); - // class should be USB_CLASS_COMM if (!mConnection.claimInterface(mControlInterface, true)) { throw new IOException("Could not claim control interface."); } mControlEndpoint = mControlInterface.getEndpoint(0); - Log.d(TAG, "Control endpoint direction: " + mControlEndpoint.getDirection()); + if (mControlEndpoint.getDirection() != UsbConstants.USB_DIR_IN || mControlEndpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_INT) { + throw new IOException("invalid control endpoint"); + } - Log.d(TAG, "Claiming data interface."); - mDataInterface = mDevice.getInterface(1); + if(mDataInterface == null) { + throw new IOException("no data interface."); + } Log.d(TAG, "data iface=" + mDataInterface); - // class should be USB_CLASS_CDC_DATA if (!mConnection.claimInterface(mDataInterface, true)) { throw new IOException("Could not claim data interface."); } - mReadEndpoint = mDataInterface.getEndpoint(1); - Log.d(TAG, "Read endpoint direction: " + mReadEndpoint.getDirection()); - mWriteEndpoint = mDataInterface.getEndpoint(0); - Log.d(TAG, "Write endpoint direction: " + mWriteEndpoint.getDirection()); + + mReadEndpoint = null; + mWriteEndpoint = null; + 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; + if (ep.getDirection() == UsbConstants.USB_DIR_OUT && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) + mWriteEndpoint = ep; + } + if (mReadEndpoint == null || mWriteEndpoint == null) { + throw new IOException("Could not get read&write endpoints."); + } } - private int sendAcmControlMessage(int request, int value, byte[] buf) { - return mConnection.controlTransfer( - USB_RT_ACM, request, value, 0, buf, buf != null ? buf.length : 0, 5000); + private int sendAcmControlMessage(int request, int value, byte[] buf) throws IOException { + int len = mConnection.controlTransfer( + USB_RT_ACM, request, value, mControlIndex, buf, buf != null ? buf.length : 0, 5000); + if(len < 0) { + throw new IOException("controlTransfer failed."); + } + return len; } @Override @@ -231,57 +254,43 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { if (mConnection == null) { throw new IOException("Already closed"); } + synchronized (this) { + if (mUsbRequest != null) + mUsbRequest.cancel(); + } mConnection.close(); mConnection = null; } @Override public int read(byte[] dest, int timeoutMillis) throws IOException { - if (mEnableAsyncReads) { - final UsbRequest request = new UsbRequest(); - try { + 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."); + 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"); + 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; + //Log.d(TAG, HexDump.dumpHexString(dest, 0, Math.min(32, dest.length))); + return nread; } else { - return 0; - } - } finally { - request.close(); - } - } - - final int numBytesRead; - synchronized (mReadBufferLock) { - int readAmt = Math.min(dest.length, mReadBuffer.length); - numBytesRead = mConnection.bulkTransfer(mReadEndpoint, mReadBuffer, readAmt, - timeoutMillis); - if (numBytesRead < 0) { - // This sucks: we get -1 on timeout, not 0 as preferred. - // We *should* use UsbRequest, except it has a bug/api oversight - // where there is no way to determine the number of bytes read - // in response :\ -- http://b.android.com/28023 - if (timeoutMillis == Integer.MAX_VALUE) { - // Hack: Special case "~infinite timeout" as an error. - return -1; - } return 0; } - System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead); + } finally { + mUsbRequest = null; + request.close(); } - return numBytesRead; } @Override @@ -320,7 +329,7 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { } @Override - public void setParameters(int baudRate, int dataBits, int stopBits, int parity) { + public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { byte stopBitsByte; switch (stopBits) { case STOPBITS_1: stopBitsByte = 0; break; @@ -392,7 +401,7 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { setDtrRts(); } - private void setDtrRts() { + private void setDtrRts() throws IOException { int value = (mRts ? 0x2 : 0) | (mDtr ? 0x1 : 0); sendAcmControlMessage(SET_CONTROL_LINE_STATE, value, null); } @@ -401,7 +410,7 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { public static Map getSupportedDevices() { final Map supportedDevices = new LinkedHashMap(); - supportedDevices.put(Integer.valueOf(UsbId.VENDOR_ARDUINO), + supportedDevices.put(UsbId.VENDOR_ARDUINO, new int[] { UsbId.ARDUINO_UNO, UsbId.ARDUINO_UNO_R3, @@ -414,18 +423,22 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { UsbId.ARDUINO_LEONARDO, UsbId.ARDUINO_MICRO, }); - supportedDevices.put(Integer.valueOf(UsbId.VENDOR_VAN_OOIJEN_TECH), + supportedDevices.put(UsbId.VENDOR_VAN_OOIJEN_TECH, new int[] { UsbId.VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL, }); - supportedDevices.put(Integer.valueOf(UsbId.VENDOR_ATMEL), + supportedDevices.put(UsbId.VENDOR_ATMEL, new int[] { UsbId.ATMEL_LUFA_CDC_DEMO_APP, }); - supportedDevices.put(Integer.valueOf(UsbId.VENDOR_LEAFLABS), + supportedDevices.put(UsbId.VENDOR_LEAFLABS, new int[] { UsbId.LEAFLABS_MAPLE, }); + supportedDevices.put(UsbId.VENDOR_ARM, + new int[] { + UsbId.ARM_MBED, + }); return supportedDevices; } diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Ch34xSerialDriver.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Ch34xSerialDriver.java index 1155512..b35cbef 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Ch34xSerialDriver.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Ch34xSerialDriver.java @@ -25,9 +25,11 @@ import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbInterface; +import android.hardware.usb.UsbRequest; import android.util.Log; import java.io.IOException; +import java.nio.ByteBuffer; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -46,6 +48,17 @@ public class Ch34xSerialDriver implements UsbSerialDriver { private final UsbDevice mDevice; private final UsbSerialPort mPort; + private static final int LCR_ENABLE_RX = 0x80; + private static final int LCR_ENABLE_TX = 0x40; + private static final int LCR_MARK_SPACE = 0x20; + private static final int LCR_PAR_EVEN = 0x10; + private static final int LCR_ENABLE_PAR = 0x08; + private static final int LCR_STOP_BITS_2 = 0x04; + private static final int LCR_CS8 = 0x03; + private static final int LCR_CS7 = 0x02; + private static final int LCR_CS6 = 0x01; + private static final int LCR_CS5 = 0x00; + public Ch34xSerialDriver(UsbDevice device) { mDevice = device; mPort = new Ch340SerialPort(mDevice, 0); @@ -72,6 +85,7 @@ public class Ch34xSerialDriver implements UsbSerialDriver { private UsbEndpoint mReadEndpoint; private UsbEndpoint mWriteEndpoint; + private UsbRequest mUsbRequest; public Ch340SerialPort(UsbDevice device, int portNumber) { super(device, portNumber); @@ -93,10 +107,8 @@ public class Ch34xSerialDriver implements UsbSerialDriver { try { for (int i = 0; i < mDevice.getInterfaceCount(); i++) { UsbInterface usbIface = mDevice.getInterface(i); - if (mConnection.claimInterface(usbIface, true)) { - Log.d(TAG, "claimInterface " + i + " SUCCESS"); - } else { - Log.d(TAG, "claimInterface " + i + " FAIL"); + if (!mConnection.claimInterface(usbIface, true)) { + throw new IOException("Could not claim data interface."); } } @@ -112,7 +124,6 @@ public class Ch34xSerialDriver implements UsbSerialDriver { } } - initialize(); setBaudRate(DEFAULT_BAUD_RATE); @@ -133,9 +144,10 @@ public class Ch34xSerialDriver implements UsbSerialDriver { if (mConnection == null) { throw new IOException("Already closed"); } - - // TODO: nothing sended on close, maybe needed? - + synchronized (this) { + if (mUsbRequest != null) + mUsbRequest.cancel(); + } try { mConnection.close(); } finally { @@ -146,21 +158,33 @@ public class Ch34xSerialDriver implements UsbSerialDriver { @Override public int read(byte[] dest, int timeoutMillis) throws IOException { - final int numBytesRead; - synchronized (mReadBufferLock) { - int readAmt = Math.min(dest.length, mReadBuffer.length); - numBytesRead = mConnection.bulkTransfer(mReadEndpoint, mReadBuffer, readAmt, - timeoutMillis); - if (numBytesRead < 0) { - // This sucks: we get -1 on timeout, not 0 as preferred. - // We *should* use UsbRequest, except it has a bug/api oversight - // where there is no way to determine the number of bytes read - // in response :\ -- http://b.android.com/28023 + 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; } - System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead); + } finally { + mUsbRequest = null; + request.close(); } - return numBytesRead; } @Override @@ -252,7 +276,7 @@ public class Ch34xSerialDriver implements UsbSerialDriver { checkState("init #4", 0x95, 0x2518, new int[]{-1 /* 0x56, c3*/, 0x00}); - if (controlOut(0x9a, 0x2518, 0x0050) < 0) { + if (controlOut(0x9a, 0x2518, LCR_ENABLE_RX | LCR_ENABLE_TX | LCR_CS8) < 0) { throw new IOException("init failed! #5"); } @@ -271,36 +295,93 @@ public class Ch34xSerialDriver implements UsbSerialDriver { private void setBaudRate(int baudRate) throws IOException { - int[] baud = new int[]{2400, 0xd901, 0x0038, 4800, 0x6402, - 0x001f, 9600, 0xb202, 0x0013, 19200, 0xd902, 0x000d, 38400, - 0x6403, 0x000a, 115200, 0xcc03, 0x0008}; + final long CH341_BAUDBASE_FACTOR = 1532620800; + final int CH341_BAUDBASE_DIVMAX = 3; - for (int i = 0; i < baud.length / 3; i++) { - if (baud[i * 3] == baudRate) { - int ret = controlOut(0x9a, 0x1312, baud[i * 3 + 1]); - if (ret < 0) { - throw new IOException("Error setting baud rate. #1"); - } - ret = controlOut(0x9a, 0x0f2c, baud[i * 3 + 2]); - if (ret < 0) { - throw new IOException("Error setting baud rate. #1"); + long factor = CH341_BAUDBASE_FACTOR / baudRate; + int divisor = CH341_BAUDBASE_DIVMAX; + + while ((factor > 0xfff0) && divisor > 0) { + factor >>= 3; + divisor--; } - return; - } + if (factor > 0xfff0) { + throw new IOException("Baudrate " + baudRate + " not supported"); } + factor = 0x10000 - factor; - throw new IOException("Baud rate " + baudRate + " currently not supported"); + 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)); + if (ret < 0) { + throw new IOException("Error setting baud rate. #2"); + } } - @Override public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { setBaudRate(baudRate); - // TODO databit, stopbit and paraty set not implemented + int lcr = LCR_ENABLE_RX | LCR_ENABLE_TX; + + switch (dataBits) { + case DATABITS_5: + lcr |= LCR_CS5; + break; + case DATABITS_6: + lcr |= LCR_CS6; + break; + case DATABITS_7: + lcr |= LCR_CS7; + break; + case DATABITS_8: + lcr |= LCR_CS8; + break; + default: + throw new IllegalArgumentException("Unknown dataBits value: " + dataBits); + } + + switch (parity) { + case PARITY_NONE: + break; + case PARITY_ODD: + lcr |= LCR_ENABLE_PAR; + break; + case PARITY_EVEN: + lcr |= LCR_ENABLE_PAR | LCR_PAR_EVEN; + break; + case PARITY_MARK: + lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE; + break; + case PARITY_SPACE: + lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE | LCR_PAR_EVEN; + break; + default: + throw new IllegalArgumentException("Unknown parity value: " + parity); + } + + switch (stopBits) { + case STOPBITS_1: + break; + case STOPBITS_1_5: + throw new IllegalArgumentException("Unsupported stopBits value: 1.5"); + case STOPBITS_2: + lcr |= LCR_STOP_BITS_2; + break; + default: + throw new IllegalArgumentException("Unknown stopBits value: " + stopBits); + } + + int ret = controlOut(0x9a, 0x2518, lcr); + if (ret < 0) { + throw new IOException("Error setting control byte"); + } } @Override @@ -345,11 +426,6 @@ public class Ch34xSerialDriver implements UsbSerialDriver { writeHandshakeByte(); } - @Override - public boolean purgeHwBuffers(boolean purgeReadBuffers, boolean purgeWriteBuffers) throws IOException { - return true; - } - } public static Map getSupportedDevices() { diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/CommonUsbSerialPort.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/CommonUsbSerialPort.java index d47c5a9..fb7ddc9 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/CommonUsbSerialPort.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/CommonUsbSerialPort.java @@ -160,8 +160,8 @@ abstract class CommonUsbSerialPort implements UsbSerialPort { public abstract void setRTS(boolean value) throws IOException; @Override - public boolean purgeHwBuffers(boolean flushReadBuffers, boolean flushWriteBuffers) throws IOException { - return !flushReadBuffers && !flushWriteBuffers; + public boolean purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { + return false; } } diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Cp21xxSerialDriver.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Cp21xxSerialDriver.java index 1faf5df..8bd90f0 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Cp21xxSerialDriver.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Cp21xxSerialDriver.java @@ -26,10 +26,12 @@ import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbInterface; +import android.hardware.usb.UsbRequest; import android.util.Log; import java.io.IOException; -import java.util.Collections; +import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -39,11 +41,14 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { private static final String TAG = Cp21xxSerialDriver.class.getSimpleName(); private final UsbDevice mDevice; - private final UsbSerialPort mPort; + private final List mPorts; public Cp21xxSerialDriver(UsbDevice device) { mDevice = device; - mPort = new Cp21xxSerialPort(mDevice, 0); + mPorts = new ArrayList<>(); + for( int port = 0; port < device.getInterfaceCount(); port++) { + mPorts.add(new Cp21xxSerialPort(mDevice, port)); + } } @Override @@ -53,7 +58,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { @Override public List getPorts() { - return Collections.singletonList(mPort); + return mPorts; } public class Cp21xxSerialPort extends CommonUsbSerialPort { @@ -103,6 +108,11 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { private UsbEndpoint mReadEndpoint; private UsbEndpoint mWriteEndpoint; + private UsbRequest mUsbRequest; + + // second port of Cp2105 has limited baudRate, dataBits, stopBits, parity + // unsupported baudrate returns error at controlTransfer(), other parameters are silently ignored + private boolean mIsRestrictedPort; public Cp21xxSerialPort(UsbDevice device, int portNumber) { super(device, portNumber); @@ -115,7 +125,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { private int setConfigSingle(int request, int value) { return mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, value, - 0, null, 0, USB_WRITE_TIMEOUT_MILLIS); + mPortNumber, null, 0, USB_WRITE_TIMEOUT_MILLIS); } @Override @@ -126,17 +136,15 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { mConnection = connection; boolean opened = false; + mIsRestrictedPort = mDevice.getInterfaceCount() == 2 && mPortNumber == 1; try { - for (int i = 0; i < mDevice.getInterfaceCount(); i++) { - UsbInterface usbIface = mDevice.getInterface(i); - if (mConnection.claimInterface(usbIface, true)) { - Log.d(TAG, "claimInterface " + i + " SUCCESS"); - } else { - Log.d(TAG, "claimInterface " + i + " FAIL"); - } + if(mPortNumber >= mDevice.getInterfaceCount()) { + throw new IOException("Unknown port number"); + } + UsbInterface dataIface = mDevice.getInterface(mPortNumber); + if (!mConnection.claimInterface(dataIface, true)) { + throw new IOException("Could not claim interface " + mPortNumber); } - - UsbInterface dataIface = mDevice.getInterface(mDevice.getInterfaceCount() - 1); for (int i = 0; i < dataIface.getEndpointCount(); i++) { UsbEndpoint ep = dataIface.getEndpoint(i); if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { @@ -169,8 +177,16 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { if (mConnection == null) { throw new IOException("Already closed"); } + synchronized (this) { + if(mUsbRequest != null) { + mUsbRequest.cancel(); + } + } try { setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_DISABLE); + } catch (Exception ignored) + {} + try { mConnection.close(); } finally { mConnection = null; @@ -179,21 +195,33 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { @Override public int read(byte[] dest, int timeoutMillis) throws IOException { - final int numBytesRead; - synchronized (mReadBufferLock) { - int readAmt = Math.min(dest.length, mReadBuffer.length); - numBytesRead = mConnection.bulkTransfer(mReadEndpoint, mReadBuffer, readAmt, - timeoutMillis); - if (numBytesRead < 0) { - // This sucks: we get -1 on timeout, not 0 as preferred. - // We *should* use UsbRequest, except it has a bug/api oversight - // where there is no way to determine the number of bytes read - // in response :\ -- http://b.android.com/28023 + 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; } - System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead); + } finally { + mUsbRequest = null; + request.close(); } - return numBytesRead; } @Override @@ -238,7 +266,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { (byte) ((baudRate >> 24) & 0xff) }; int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_BAUDRATE, - 0, 0, data, 4, USB_WRITE_TIMEOUT_MILLIS); + 0, mPortNumber, data, 4, USB_WRITE_TIMEOUT_MILLIS); if (ret < 0) { throw new IOException("Error setting baud rate."); } @@ -252,38 +280,62 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { int configDataBits = 0; switch (dataBits) { case DATABITS_5: + if(mIsRestrictedPort) + throw new IllegalArgumentException("Unsupported dataBits value: " + dataBits); configDataBits |= 0x0500; break; case DATABITS_6: + if(mIsRestrictedPort) + throw new IllegalArgumentException("Unsupported dataBits value: " + dataBits); configDataBits |= 0x0600; break; case DATABITS_7: + if(mIsRestrictedPort) + throw new IllegalArgumentException("Unsupported dataBits value: " + dataBits); configDataBits |= 0x0700; break; case DATABITS_8: configDataBits |= 0x0800; break; default: - configDataBits |= 0x0800; - break; + throw new IllegalArgumentException("Unknown dataBits value: " + dataBits); } switch (parity) { + case PARITY_NONE: + break; case PARITY_ODD: configDataBits |= 0x0010; break; case PARITY_EVEN: configDataBits |= 0x0020; break; + case PARITY_MARK: + if(mIsRestrictedPort) + throw new IllegalArgumentException("Unsupported parity value: mark"); + configDataBits |= 0x0030; + break; + case PARITY_SPACE: + if(mIsRestrictedPort) + throw new IllegalArgumentException("Unsupported parity value: space"); + configDataBits |= 0x0040; + break; + default: + throw new IllegalArgumentException("Unknown parity value: " + parity); } switch (stopBits) { case STOPBITS_1: - configDataBits |= 0; break; + case STOPBITS_1_5: + throw new IllegalArgumentException("Unsupported stopBits value: 1.5"); case STOPBITS_2: + if(mIsRestrictedPort) + throw new IllegalArgumentException("Unsupported stopBits value: 2"); configDataBits |= 2; break; + default: + throw new IllegalArgumentException("Unknown stopBits value: " + stopBits); } setConfigSingle(SILABSER_SET_LINE_CTL_REQUEST_CODE, configDataBits); } @@ -327,8 +379,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { } @Override - public boolean purgeHwBuffers(boolean purgeReadBuffers, - boolean purgeWriteBuffers) throws IOException { + public boolean purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { int value = (purgeReadBuffers ? FLUSH_READ_CODE : 0) | (purgeWriteBuffers ? FLUSH_WRITE_CODE : 0); @@ -343,7 +394,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { public static Map getSupportedDevices() { final Map supportedDevices = new LinkedHashMap(); - supportedDevices.put(Integer.valueOf(UsbId.VENDOR_SILABS), + supportedDevices.put(UsbId.VENDOR_SILABS, new int[] { UsbId.SILABS_CP2102, UsbId.SILABS_CP2105, diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/FtdiSerialDriver.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/FtdiSerialDriver.java index 1a9a66b..60d0632 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/FtdiSerialDriver.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/FtdiSerialDriver.java @@ -28,11 +28,9 @@ import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbRequest; import android.util.Log; -import com.hoho.android.usbserial.util.HexDump; - import java.io.IOException; import java.nio.ByteBuffer; -import java.util.Collections; +import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -72,6 +70,8 @@ import java.util.Map; * Supported and tested devices: *
    *
  • {@value DeviceType#TYPE_R}
  • + *
  • {@value DeviceType#TYPE_2232H}
  • + *
  • {@value DeviceType#TYPE_4232H}
  • *
*

*

@@ -79,8 +79,6 @@ import java.util.Map; * feedback or patches): *

    *
  • {@value DeviceType#TYPE_2232C}
  • - *
  • {@value DeviceType#TYPE_2232H}
  • - *
  • {@value DeviceType#TYPE_4232H}
  • *
  • {@value DeviceType#TYPE_AM}
  • *
  • {@value DeviceType#TYPE_BM}
  • *
@@ -95,7 +93,7 @@ import java.util.Map; public class FtdiSerialDriver implements UsbSerialDriver { private final UsbDevice mDevice; - private final UsbSerialPort mPort; + private final List mPorts; /** * FTDI chip types. @@ -106,8 +104,12 @@ public class FtdiSerialDriver implements UsbSerialDriver { public FtdiSerialDriver(UsbDevice device) { mDevice = device; - mPort = new FtdiSerialPort(mDevice, 0); + mPorts = new ArrayList<>(); + for( int port = 0; port < device.getInterfaceCount(); port++) { + mPorts.add(new FtdiSerialPort(mDevice, port)); + } } + @Override public UsbDevice getDevice() { return mDevice; @@ -115,7 +117,7 @@ public class FtdiSerialDriver implements UsbSerialDriver { @Override public List getPorts() { - return Collections.singletonList(mPort); + return mPorts; } private class FtdiSerialPort extends CommonUsbSerialPort { @@ -163,7 +165,7 @@ public class FtdiSerialDriver implements UsbSerialDriver { 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_RX = 1; // RX @ FTDI device = write @ usb-serial-for-android library private static final int SIO_RESET_PURGE_TX = 2; public static final int FTDI_DEVICE_OUT_REQTYPE = @@ -181,16 +183,7 @@ public class FtdiSerialDriver implements UsbSerialDriver { private DeviceType mType; - private int mInterface = 0; /* INTERFACE_ANY */ - - private int mMaxPacketSize = 64; // TODO(mikey): detect - - /** - * Due to http://b.android.com/28023 , we cannot use UsbRequest async reads - * since it gives no indication of number of bytes read. Set this to - * {@code true} on platforms where it is fixed. - */ - private static final boolean ENABLE_ASYNC_READS = false; + private int mIndex = 0; public FtdiSerialPort(UsbDevice device, int portNumber) { super(device, portNumber); @@ -210,10 +203,10 @@ public class FtdiSerialDriver implements UsbSerialDriver { * @return The number of payload bytes */ private final int filterStatusBytes(byte[] src, byte[] dest, int totalBytesRead, int maxPacketSize) { - final int packetsCount = totalBytesRead / maxPacketSize + (totalBytesRead % maxPacketSize == 0 ? 0 : 1); + final int packetsCount = (totalBytesRead + maxPacketSize -1 )/ maxPacketSize; for (int packetIdx = 0; packetIdx < packetsCount; ++packetIdx) { final int count = (packetIdx == (packetsCount - 1)) - ? (totalBytesRead % maxPacketSize) - MODEM_STATUS_HEADER_LENGTH + ? totalBytesRead - packetIdx * maxPacketSize - MODEM_STATUS_HEADER_LENGTH : maxPacketSize - MODEM_STATUS_HEADER_LENGTH; if (count > 0) { System.arraycopy(src, @@ -228,14 +221,21 @@ public class FtdiSerialDriver implements UsbSerialDriver { } public void reset() throws IOException { + // 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; + } + int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE, SIO_RESET_REQUEST, - SIO_RESET_SIO, 0 /* index */, null, 0, USB_WRITE_TIMEOUT_MILLIS); + SIO_RESET_SIO, mIndex, null, 0, USB_WRITE_TIMEOUT_MILLIS); if (result != 0) { throw new IOException("Reset failed: result=" + result); } - - // TODO(mikey): autodetect. - mType = DeviceType.TYPE_R; } @Override @@ -247,12 +247,10 @@ public class FtdiSerialDriver implements UsbSerialDriver { boolean opened = false; try { - for (int i = 0; i < mDevice.getInterfaceCount(); i++) { - if (connection.claimInterface(mDevice.getInterface(i), true)) { - Log.d(TAG, "claimInterface " + i + " SUCCESS"); - } else { - throw new IOException("Error claiming interface " + i); - } + if (connection.claimInterface(mDevice.getInterface(mPortNumber), true)) { + Log.d(TAG, "claimInterface " + mPortNumber + " SUCCESS"); + } else { + throw new IOException("Error claiming interface " + mPortNumber); } reset(); opened = true; @@ -278,20 +276,12 @@ public class FtdiSerialDriver implements UsbSerialDriver { @Override public int read(byte[] dest, int timeoutMillis) throws IOException { - final UsbEndpoint endpoint = mDevice.getInterface(0).getEndpoint(0); - - if (ENABLE_ASYNC_READS) { - final int readAmt; - synchronized (mReadBufferLock) { - // mReadBuffer is only used for maximum read size. - readAmt = Math.min(dest.length, mReadBuffer.length); - } - - final UsbRequest request = new UsbRequest(); + final UsbEndpoint endpoint = mDevice.getInterface(mPortNumber).getEndpoint(0); + final UsbRequest request = new UsbRequest(); + final ByteBuffer buf = ByteBuffer.wrap(dest); + try { request.initialize(mConnection, endpoint); - - final ByteBuffer buf = ByteBuffer.wrap(dest); - if (!request.queue(buf, readAmt)) { + if (!request.queue(buf, dest.length)) { throw new IOException("Error queueing request."); } @@ -299,34 +289,21 @@ public class FtdiSerialDriver implements UsbSerialDriver { if (response == null) { throw new IOException("Null response"); } - - final int payloadBytesRead = buf.position() - MODEM_STATUS_HEADER_LENGTH; - if (payloadBytesRead > 0) { - Log.d(TAG, HexDump.dumpHexString(dest, 0, Math.min(32, dest.length))); - return payloadBytesRead; - } else { - return 0; - } - } else { - final int totalBytesRead; - - synchronized (mReadBufferLock) { - final int readAmt = Math.min(dest.length, mReadBuffer.length); - totalBytesRead = mConnection.bulkTransfer(endpoint, mReadBuffer, - readAmt, timeoutMillis); - - if (totalBytesRead < MODEM_STATUS_HEADER_LENGTH) { - throw new IOException("Expected at least " + MODEM_STATUS_HEADER_LENGTH + " bytes"); - } - - return filterStatusBytes(mReadBuffer, dest, totalBytesRead, endpoint.getMaxPacketSize()); - } + } 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(0).getEndpoint(1); + final UsbEndpoint endpoint = mDevice.getInterface(mPortNumber).getEndpoint(1); int offset = 0; while (offset < src.length) { @@ -379,7 +356,18 @@ public class FtdiSerialDriver implements UsbSerialDriver { throws IOException { setBaudRate(baudRate); - int config = dataBits; + int config = 0; + switch (dataBits) { + case DATABITS_5: + case DATABITS_6: + throw new IllegalArgumentException("Unsupported dataBits value: " + dataBits); + case DATABITS_7: + case DATABITS_8: + config |= dataBits; + break; + default: + throw new IllegalArgumentException("Unknown dataBits value: " + dataBits); + } switch (parity) { case PARITY_NONE: @@ -406,8 +394,7 @@ public class FtdiSerialDriver implements UsbSerialDriver { config |= (0x00 << 11); break; case STOPBITS_1_5: - config |= (0x01 << 11); - break; + throw new IllegalArgumentException("Unsupported stopBits value: 1.5"); case STOPBITS_2: config |= (0x02 << 11); break; @@ -416,7 +403,7 @@ public class FtdiSerialDriver implements UsbSerialDriver { } int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE, - SIO_SET_DATA_REQUEST, config, 0 /* index */, + SIO_SET_DATA_REQUEST, config, mIndex, null, 0, USB_WRITE_TIMEOUT_MILLIS); if (result != 0) { throw new IOException("Setting parameters failed: result=" + result); @@ -496,9 +483,8 @@ public class FtdiSerialDriver implements UsbSerialDriver { long index; if (mType == DeviceType.TYPE_2232C || mType == DeviceType.TYPE_2232H || mType == DeviceType.TYPE_4232H) { - index = (encodedDivisor >> 8) & 0xffff; - index &= 0xFF00; - index |= 0 /* TODO mIndex */; + index = (encodedDivisor >> 8) & 0xff00; + index |= mIndex; } else { index = (encodedDivisor >> 16) & 0xffff; } @@ -548,20 +534,20 @@ public class FtdiSerialDriver implements UsbSerialDriver { } @Override - public boolean purgeHwBuffers(boolean purgeReadBuffers, boolean purgeWriteBuffers) throws IOException { - if (purgeReadBuffers) { + public boolean purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { + if (purgeWriteBuffers) { int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE, SIO_RESET_REQUEST, - SIO_RESET_PURGE_RX, 0 /* index */, null, 0, USB_WRITE_TIMEOUT_MILLIS); + SIO_RESET_PURGE_RX, mIndex, null, 0, USB_WRITE_TIMEOUT_MILLIS); if (result != 0) { - throw new IOException("Flushing RX failed: result=" + result); + throw new IOException("purge write buffer failed: result=" + result); } } - if (purgeWriteBuffers) { + if (purgeReadBuffers) { int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE, SIO_RESET_REQUEST, - SIO_RESET_PURGE_TX, 0 /* index */, null, 0, USB_WRITE_TIMEOUT_MILLIS); + SIO_RESET_PURGE_TX, mIndex, null, 0, USB_WRITE_TIMEOUT_MILLIS); if (result != 0) { - throw new IOException("Flushing RX failed: result=" + result); + throw new IOException("purge read buffer failed: result=" + result); } } return true; @@ -570,9 +556,12 @@ public class FtdiSerialDriver implements UsbSerialDriver { public static Map getSupportedDevices() { final Map supportedDevices = new LinkedHashMap(); - supportedDevices.put(Integer.valueOf(UsbId.VENDOR_FTDI), + supportedDevices.put(UsbId.VENDOR_FTDI, new int[] { UsbId.FTDI_FT232R, + UsbId.FTDI_FT232H, + UsbId.FTDI_FT2232H, + UsbId.FTDI_FT4232H, UsbId.FTDI_FT231X, }); return supportedDevices; diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ProlificSerialDriver.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ProlificSerialDriver.java index 01d3a85..24ae76e 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ProlificSerialDriver.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ProlificSerialDriver.java @@ -32,10 +32,12 @@ import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbInterface; +import android.hardware.usb.UsbRequest; import android.util.Log; import java.io.IOException; import java.lang.reflect.Method; +import java.nio.ByteBuffer; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -86,7 +88,7 @@ public class ProlificSerialDriver implements UsbSerialDriver { private static final int READ_ENDPOINT = 0x83; private static final int INTERRUPT_ENDPOINT = 0x81; - private static final int FLUSH_RX_REQUEST = 0x08; + private static final int FLUSH_RX_REQUEST = 0x08; // RX @ Prolific device = write @ usb-serial-for-android library private static final int FLUSH_TX_REQUEST = 0x09; private static final int SET_LINE_REQUEST = 0x20; @@ -368,15 +370,28 @@ public class ProlificSerialDriver implements UsbSerialDriver { @Override public int read(byte[] dest, int timeoutMillis) throws IOException { - synchronized (mReadBufferLock) { - int readAmt = Math.min(dest.length, mReadBuffer.length); - int numBytesRead = mConnection.bulkTransfer(mReadEndpoint, mReadBuffer, - readAmt, timeoutMillis); - if (numBytesRead < 0) { + 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."); + } + + 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; } - System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead); - return numBytesRead; + } finally { + request.close(); } } @@ -538,22 +553,22 @@ public class ProlificSerialDriver implements UsbSerialDriver { } @Override - public boolean purgeHwBuffers(boolean purgeReadBuffers, boolean purgeWriteBuffers) throws IOException { - if (purgeReadBuffers) { + public boolean purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { + if (purgeWriteBuffers) { vendorOut(FLUSH_RX_REQUEST, 0, null); } - if (purgeWriteBuffers) { + if (purgeReadBuffers) { vendorOut(FLUSH_TX_REQUEST, 0, null); } - return purgeReadBuffers || purgeWriteBuffers; + return true; } } public static Map getSupportedDevices() { final Map supportedDevices = new LinkedHashMap(); - supportedDevices.put(Integer.valueOf(UsbId.VENDOR_PROLIFIC), + supportedDevices.put(UsbId.VENDOR_PROLIFIC, new int[] { UsbId.PROLIFIC_PL2303, }); return supportedDevices; } diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbId.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbId.java index c4050e1..7bba4db 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbId.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbId.java @@ -33,6 +33,9 @@ public final class UsbId { public static final int VENDOR_FTDI = 0x0403; public static final int FTDI_FT232R = 0x6001; + public static final int FTDI_FT2232H = 0x6010; + public static final int FTDI_FT4232H = 0x6011; + public static final int FTDI_FT232H = 0x6014; public static final int FTDI_FT231X = 0x6015; public static final int VENDOR_ATMEL = 0x03EB; @@ -68,6 +71,10 @@ public final class UsbId { public static final int VENDOR_QINHENG = 0x1a86; public static final int QINHENG_HL340 = 0x7523; + // 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() { throw new IllegalAccessError("Non-instantiable class."); } diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbSerialPort.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbSerialPort.java index 1341143..5cd3ca5 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbSerialPort.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbSerialPort.java @@ -216,13 +216,13 @@ public interface UsbSerialPort { public void setRTS(boolean value) throws IOException; /** - * Flush non-transmitted output data and / or non-read input data - * @param flushRX {@code true} to flush non-transmitted output data - * @param flushTX {@code true} to flush non-read input data + * 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 * @return {@code true} if the operation was successful, or * {@code false} if the operation is not supported by the driver or device * @throws IOException if an error occurred during flush */ - public boolean purgeHwBuffers(boolean flushRX, boolean flushTX) throws IOException; + public boolean purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException; } diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbSerialRuntimeException.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbSerialRuntimeException.java deleted file mode 100644 index b48607c..0000000 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbSerialRuntimeException.java +++ /dev/null @@ -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); - } - -} diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/util/SerialInputOutputManager.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/util/SerialInputOutputManager.java index 51c5655..1ab3edf 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/util/SerialInputOutputManager.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/util/SerialInputOutputManager.java @@ -50,7 +50,7 @@ public class SerialInputOutputManager implements Runnable { // Synchronized by 'mWriteBuffer' private final ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ); - private enum State { + public enum State { STOPPED, RUNNING, STOPPING @@ -111,7 +111,7 @@ public class SerialInputOutputManager implements Runnable { } } - private synchronized State getState() { + public synchronized State getState() { return mState; } @@ -120,7 +120,6 @@ public class SerialInputOutputManager implements Runnable { * called, or until a driver exception is raised. * * NOTE(mikey): Uses inefficient read/write-with-timeout. - * TODO(mikey): Read asynchronously with {@link UsbRequest#queue(ByteBuffer, int)} */ @Override public void run() {