diff --git a/README.md b/README.md index e98603b..3fc68ac 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,7 @@ some other device specific drivers * Chrome OS CCD (Closed Case Debugging) and devices implementing the generic CDC/ACM protocol like -* Qinheng CH9102 +* Qinheng CH343, CH9102 * Microchip MCP2221 * Arduino using ATmega32U4 * Digispark using V-USB software USB diff --git a/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/DeviceTest.java b/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/DeviceTest.java index 435a9ae..cb17582 100644 --- a/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/DeviceTest.java +++ b/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/DeviceTest.java @@ -510,21 +510,21 @@ public class DeviceTest { fail("invalid baudrate 1"); } catch (UnsupportedOperationException ignored) { // ch340 } catch (IOException ignored) { // cp2105 second port - } catch (IllegalArgumentException ignored) { } try { - usb.setParameters(1<<31, 8, 1, UsbSerialPort.PARITY_NONE); + usb.setParameters(1<<30, 8, 1, UsbSerialPort.PARITY_NONE); if (usb.serialDriver instanceof ProlificSerialDriver) ; else if (usb.serialDriver instanceof Cp21xxSerialDriver) ; else if (usb.serialDriver instanceof CdcAcmSerialDriver) ; + else if (usb.serialDriver instanceof Ch34xSerialDriver) + ; else - fail("invalid baudrate 2^31"); - } catch (ArithmeticException ignored) { // ch340 + fail("invalid baudrate 2^30"); + } catch (UnsupportedOperationException ignored) { // ftdi } catch (IOException ignored) { // cp2105 second port - } catch (IllegalArgumentException ignored) { } for(int baudRate : new int[] {300, 2400, 19200, 115200} ) { @@ -1657,6 +1657,7 @@ public class DeviceTest { } catch (IOException ignored) { } } + // CDC CH9143 only recovers from CH... commands with power toggle, so skip this combination if(!(usb.serialDriver instanceof Ch34xSerialDriver)) { UsbDeviceConnection wrongDeviceConnection = usbManager.openDevice(usb.serialDriver.getDevice()); UsbSerialDriver wrongSerialDriver = new Ch34xSerialDriver(usb.serialDriver.getDevice()); @@ -1795,7 +1796,8 @@ public class DeviceTest { supportedControlLines.add(ControlLine.DTR); } if(usb.inputLinesSupported) { - supportedControlLines.add(ControlLine.CTS); + if(usb.inputLineCtsSupported) + supportedControlLines.add(ControlLine.CTS); supportedControlLines.add(ControlLine.DSR); supportedControlLines.add(ControlLine.CD); supportedControlLines.add(ControlLine.RI); @@ -1823,16 +1825,16 @@ public class DeviceTest { // control lines reset on initial open data = "none".getBytes(); - assertEquals(usb.inputLinesConnected && !usb.inputLinesOnlyRtsCts + assertEquals(usb.inputLinesConnected && !usb.inputLinesOnlyRtsCts && usb.inputLineCtsSupported ? EnumSet.of(ControlLine.RI) : EnumSet.noneOf(ControlLine.class), usb.serialPort.getControlLines()); assertThat(usb.getControlLine(usb.serialPort::getRTS), equalTo(Boolean.FALSE)); - assertThat(usb.getControlLine(usb.serialPort::getCTS), equalTo(inputLineFalse)); + assertThat(usb.getControlLine(usb.serialPort::getCTS), equalTo(usb.inputLineCtsSupported ? inputLineFalse : null)); assertThat(usb.getControlLine(usb.serialPort::getDTR), equalTo(Boolean.FALSE)); assertThat(usb.getControlLine(usb.serialPort::getDSR), equalTo(inputLineFalse)); assertThat(usb.getControlLine(usb.serialPort::getCD), equalTo(inputLineFalse)); - assertThat(usb.getControlLine(usb.serialPort::getRI), equalTo(usb.inputLinesOnlyRtsCts ? Boolean.FALSE : inputLineTrue)); + assertThat(usb.getControlLine(usb.serialPort::getRI), equalTo(usb.inputLinesOnlyRtsCts || !usb.inputLineCtsSupported ? Boolean.FALSE : inputLineTrue)); telnet.write(data); assertThat(Arrays.toString(data), usb.read(4), equalTo(data)); usb.write(data); @@ -1841,12 +1843,12 @@ public class DeviceTest { data = "rts ".getBytes(); usb.serialPort.setRTS(true); Thread.sleep(sleep); - assertEquals(usb.inputLinesConnected + assertEquals(usb.inputLinesConnected & usb.inputLineCtsSupported ? EnumSet.of(ControlLine.RTS, ControlLine.CTS) : EnumSet.of(ControlLine.RTS), usb.serialPort.getControlLines()); assertThat(usb.getControlLine(usb.serialPort::getRTS), equalTo(Boolean.TRUE)); - assertThat(usb.getControlLine(usb.serialPort::getCTS), equalTo(inputLineTrue)); + assertThat(usb.getControlLine(usb.serialPort::getCTS), equalTo(usb.inputLineCtsSupported ? inputLineTrue : null)); assertThat(usb.getControlLine(usb.serialPort::getDTR), equalTo(Boolean.FALSE)); assertThat(usb.getControlLine(usb.serialPort::getDSR), equalTo(inputLineFalse)); assertThat(usb.getControlLine(usb.serialPort::getCD), equalTo(inputLineFalse)); @@ -1859,14 +1861,15 @@ public class DeviceTest { data = "both".getBytes(); usb.serialPort.setDTR(true); Thread.sleep(sleep); - assertEquals(usb.inputLinesOnlyRtsCts - ? EnumSet.of(ControlLine.RTS, ControlLine.DTR, ControlLine.CTS) - : usb.inputLinesConnected - ? EnumSet.of(ControlLine.RTS, ControlLine.DTR, ControlLine.CD) - : EnumSet.of(ControlLine.RTS, ControlLine.DTR), - usb.serialPort.getControlLines()); + if(usb.inputLinesOnlyRtsCts) { + assertEquals(EnumSet.of(ControlLine.RTS, ControlLine.DTR, ControlLine.CTS), usb.serialPort.getControlLines()); + } else if(usb.inputLinesConnected) { + assertEquals(EnumSet.of(ControlLine.RTS, ControlLine.DTR, ControlLine.CD), usb.serialPort.getControlLines()); + } else { + assertEquals(EnumSet.of(ControlLine.RTS, ControlLine.DTR), usb.serialPort.getControlLines()); + } assertThat(usb.getControlLine(usb.serialPort::getRTS), equalTo(Boolean.TRUE)); - assertThat(usb.getControlLine(usb.serialPort::getCTS), equalTo(usb.inputLinesOnlyRtsCts ? Boolean.TRUE : inputLineFalse)); + assertThat(usb.getControlLine(usb.serialPort::getCTS), equalTo(usb.inputLineCtsSupported ? (usb.inputLinesOnlyRtsCts ? Boolean.TRUE : inputLineFalse) : null)); assertThat(usb.getControlLine(usb.serialPort::getDTR), equalTo(Boolean.TRUE)); assertThat(usb.getControlLine(usb.serialPort::getDSR), equalTo(inputLineFalse)); assertThat(usb.getControlLine(usb.serialPort::getCD), equalTo(usb.inputLinesOnlyRtsCts ? Boolean.FALSE : inputLineTrue)); @@ -1884,7 +1887,7 @@ public class DeviceTest { : EnumSet.of(ControlLine.DTR), usb.serialPort.getControlLines()); assertThat(usb.getControlLine(usb.serialPort::getRTS), equalTo(Boolean.FALSE)); - assertThat(usb.getControlLine(usb.serialPort::getCTS), equalTo(inputLineFalse)); + assertThat(usb.getControlLine(usb.serialPort::getCTS), equalTo(usb.inputLineCtsSupported ? inputLineFalse : null)); assertThat(usb.getControlLine(usb.serialPort::getDTR), equalTo(Boolean.TRUE)); assertThat(usb.getControlLine(usb.serialPort::getDSR), equalTo(usb.inputLinesOnlyRtsCts ? Boolean.FALSE : inputLineTrue)); assertThat(usb.getControlLine(usb.serialPort::getCD), equalTo(inputLineFalse)); @@ -1905,10 +1908,10 @@ public class DeviceTest { EnumSet retainedControlLines = EnumSet.noneOf(ControlLine.class); if(outputRetained) retainedControlLines.add(ControlLine.RTS); - if(inputRetained) retainedControlLines.add(ControlLine.CTS); + if(inputRetained && usb.inputLineCtsSupported) retainedControlLines.add(ControlLine.CTS); assertEquals(retainedControlLines, usb.serialPort.getControlLines()); assertThat(usb.getControlLine(usb.serialPort::getRTS), equalTo(outputRetained)); - assertThat(usb.getControlLine(usb.serialPort::getCTS), equalTo(inputRetained ? inputLineTrue : inputLineFalse)); + assertThat(usb.getControlLine(usb.serialPort::getCTS), equalTo(usb.inputLineCtsSupported ? (inputRetained ? inputLineTrue : inputLineFalse) : null)); assertThat(usb.getControlLine(usb.serialPort::getDTR), equalTo(Boolean.FALSE)); assertThat(usb.getControlLine(usb.serialPort::getDSR), equalTo(inputLineFalse)); assertThat(usb.getControlLine(usb.serialPort::getCD), equalTo(inputLineFalse)); @@ -1958,7 +1961,8 @@ public class DeviceTest { } try { - if (usb.serialDriver instanceof ProlificSerialDriver) { + if (usb.serialDriver instanceof ProlificSerialDriver + || usb.serialDriver instanceof CdcAcmSerialDriver) { for(int i = 0; i < 10; i++) { // can take some time until background thread fails usb.serialPort.getRI(); Thread.sleep(100); @@ -2413,8 +2417,9 @@ public class DeviceTest { } catch (IOException ignored) { } try { - if(usb.serialDriver instanceof ProlificSerialDriver) - Thread.sleep(600); // wait for background thread + if(usb.serialDriver instanceof ProlificSerialDriver + || usb.serialDriver instanceof CdcAcmSerialDriver) + Thread.sleep(600); // wait for background thread to fail usb.serialPort.getRI(); fail("getRI error expected"); } catch (IOException ignored) { diff --git a/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/util/UsbWrapper.java b/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/util/UsbWrapper.java index 07e6cd3..a7c90f2 100644 --- a/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/util/UsbWrapper.java +++ b/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/util/UsbWrapper.java @@ -65,6 +65,7 @@ public class UsbWrapper implements SerialInputOutputManager.Listener { public boolean inputLinesSupported; public boolean inputLinesConnected; public boolean inputLinesOnlyRtsCts; + public boolean inputLineCtsSupported; public int writePacketSize = -1; public int writeBufferSize = -1; public int readBufferSize = -1; @@ -111,7 +112,8 @@ public class UsbWrapper implements SerialInputOutputManager.Listener { isCdcAcmCh343 = serialDriver.getDevice().getVendorId() == UsbId.VENDOR_QINHENG && serialDriver.getDevice().getProductId() == 0x55D3; // output lines are supported by all common drivers - // input lines are supported by all common drivers except CDC + // input lines are supported by all common drivers, but only partly by CDC + inputLineCtsSupported = true; if (serialDriver instanceof FtdiSerialDriver) { outputLinesSupported = true; inputLinesSupported = true; @@ -137,6 +139,10 @@ public class UsbWrapper implements SerialInputOutputManager.Listener { inputLinesConnected = true; // I only have 74LS138 connected at CH340, not connected at CH341A } else if (serialDriver instanceof CdcAcmSerialDriver) { outputLinesSupported = true; + inputLinesSupported = true; + if(isCdcAcmCh343) + inputLinesConnected = true; + inputLineCtsSupported = false; } if (serialDriver instanceof Cp21xxSerialDriver) { @@ -322,10 +328,9 @@ public class UsbWrapper implements SerialInputOutputManager.Listener { public void setParameters(int baudRate, int dataBits, int stopBits, @UsbSerialPort.Parity int parity) throws IOException, InterruptedException { serialPort.setParameters(baudRate, dataBits, stopBits, parity); - if(serialDriver instanceof CdcAcmSerialDriver) - Thread.sleep(10); // arduino_leonardeo_bridge.ini needs some time - else - Thread.sleep(1); + //if(serialDriver instanceof CdcAcmSerialDriver) + //Thread.sleep(10); // arduino_leonardeo_bridge.ini needs some time + Thread.sleep(1); } /* return TRUE/FALSE/null instead of true/false/ */ 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 65a8095..d158b80 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 @@ -13,10 +13,12 @@ import android.hardware.usb.UsbInterface; import android.util.Log; import com.hoho.android.usbserial.util.HexDump; +import com.hoho.android.usbserial.util.MonotonicClock; import com.hoho.android.usbserial.util.UsbUtils; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.List; @@ -99,6 +101,19 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { private static final int SET_CONTROL_LINE_STATE = 0x22; private static final int SEND_BREAK = 0x23; + private static final int NOTIFICATION_REQUEST_TYPE = 0xA1; + private static final int SERIAL_STATE = 0x20; + private static final int SERIAL_STATE_PACKET_SIZE = 10; + private static final int SERIAL_STATE_FLAG_CD = 0x01; + private static final int SERIAL_STATE_FLAG_DSR = 0x02; + private static final int SERIAL_STATE_FLAG_RI = 0x08; + + private volatile int mSerialState = 0; + private volatile Thread mReadControlLinesThread = null; + private final Object mReadControlLinesThreadLock = new Object(); + private boolean mStopReadControlLinesThread = false; + private Exception mReadControlLinesException = null; + public CdcAcmSerialPort(UsbDevice device, int portNumber) { super(device, portNumber); } @@ -260,9 +275,71 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { return len; } + private void readControlLinesThreadFunction() { + try { + byte[] buffer = new byte[Math.max(SERIAL_STATE_PACKET_SIZE, mControlEndpoint.getMaxPacketSize())]; + while (!mStopReadControlLinesThread) { + long endTime = MonotonicClock.millis() + 500; + int readBytesCount = mConnection.bulkTransfer(mControlEndpoint, buffer, buffer.length, 500); + if (readBytesCount == -1) { + testConnection(MonotonicClock.millis() < endTime); + continue; + } + if (readBytesCount != SERIAL_STATE_PACKET_SIZE) continue; + if (buffer[0] != (byte)NOTIFICATION_REQUEST_TYPE) continue; + if (buffer[1] != SERIAL_STATE) continue; + int index = ((buffer[5] & 0xff) << 8) | (buffer[4] & 0xff); + if (index != mControlIndex) continue; + int payloadLength = ((buffer[7] & 0xff) << 8) | (buffer[6] & 0xff); + if (payloadLength < 2) continue; + mSerialState = ((buffer[9] & 0xff) << 8) | (buffer[8] & 0xff); + Log.d(TAG, "control line state " + Arrays.toString(buffer)); + } + } catch (Exception e) { + if (isOpen()) { + mReadControlLinesException = e; + } + } + } + + private int getSerialState() throws IOException { + if ((mReadControlLinesThread == null) && (mReadControlLinesException == null)) { + synchronized (mReadControlLinesThreadLock) { + if (mReadControlLinesThread == null) { + mSerialState = 0; + mReadControlLinesThread = new Thread(this::readControlLinesThreadFunction); + mReadControlLinesThread.setDaemon(true); + mReadControlLinesThread.start(); + } + } + } + + Exception readControlLinesException = mReadControlLinesException; + if (readControlLinesException != null) { + mReadControlLinesException = null; + throw new IOException(readControlLinesException); + } + + return mSerialState; + } + + @Override protected void closeInt() { try { + synchronized (mReadControlLinesThreadLock) { + if (mReadControlLinesThread != null) { + try { + mStopReadControlLinesThread = true; + mReadControlLinesThread.join(); + } catch (Exception e) { + Log.w(TAG, "An error occurred while waiting for control line read thread", e); + } + mStopReadControlLinesThread = false; + mReadControlLinesThread = null; + mReadControlLinesException = null; + } + } mConnection.releaseInterface(mControlInterface); mConnection.releaseInterface(mDataInterface); } catch(Exception ignored) {} @@ -320,6 +397,21 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { return mRts; } + @Override + public boolean getCD() throws IOException { + return (getSerialState() & SERIAL_STATE_FLAG_CD) != 0; + } + + @Override + public boolean getDSR() throws IOException { + return (getSerialState() & SERIAL_STATE_FLAG_DSR) != 0; + } + + @Override + public boolean getRI() throws IOException { + return (getSerialState() & SERIAL_STATE_FLAG_RI) != 0; + } + @Override public void setRTS(boolean value) throws IOException { mRts = value; @@ -333,15 +425,20 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { @Override public EnumSet getControlLines() throws IOException { + int serialState = getSerialState(); EnumSet set = EnumSet.noneOf(ControlLine.class); if(mRts) set.add(ControlLine.RTS); + // no CTS if(mDtr) set.add(ControlLine.DTR); + if((serialState & SERIAL_STATE_FLAG_DSR) != 0) set.add(ControlLine.DSR); + if((serialState & SERIAL_STATE_FLAG_CD) != 0) set.add(ControlLine.CD); + if((serialState & SERIAL_STATE_FLAG_RI) != 0) set.add(ControlLine.RI); return set; } @Override public EnumSet getSupportedControlLines() throws IOException { - return EnumSet.of(ControlLine.RTS, ControlLine.DTR); + return EnumSet.of(ControlLine.RTS, ControlLine.DTR, ControlLine.DSR, ControlLine.CD, ControlLine.RI); } @Override 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 924f30b..3041422 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 @@ -260,10 +260,6 @@ public class ProlificSerialDriver implements UsbSerialDriver { return mStatus; } - private boolean testStatusFlag(int flag) throws IOException { - return ((getStatus() & flag) == flag); - } - @Override public void openInt() throws IOException { UsbInterface usbInterface = mDevice.getInterface(0); @@ -474,17 +470,17 @@ public class ProlificSerialDriver implements UsbSerialDriver { @Override public boolean getCD() throws IOException { - return testStatusFlag(STATUS_FLAG_CD); + return (getStatus() & STATUS_FLAG_CD) != 0; } @Override public boolean getCTS() throws IOException { - return testStatusFlag(STATUS_FLAG_CTS); + return (getStatus() & STATUS_FLAG_CTS) != 0; } @Override public boolean getDSR() throws IOException { - return testStatusFlag(STATUS_FLAG_DSR); + return (getStatus() & STATUS_FLAG_DSR) != 0; } @Override @@ -505,7 +501,7 @@ public class ProlificSerialDriver implements UsbSerialDriver { @Override public boolean getRI() throws IOException { - return testStatusFlag(STATUS_FLAG_RI); + return (getStatus() & STATUS_FLAG_RI) != 0; } @Override