1
0
mirror of https://github.com/mik3y/usb-serial-for-android.git synced 2026-08-11 08:22:52 +00:00

support CDC input control lines

This commit is contained in:
kai-morich
2026-07-10 21:35:41 +02:00
parent 76d6a3ad57
commit 04f6f4d928
5 changed files with 142 additions and 39 deletions
@@ -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<ControlLine> 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) {
@@ -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/<throw UnsupportedOperationException> */
@@ -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<ControlLine> getControlLines() throws IOException {
int serialState = getSerialState();
EnumSet<ControlLine> 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<ControlLine> getSupportedControlLines() throws IOException {
return EnumSet.of(ControlLine.RTS, ControlLine.DTR);
return EnumSet.of(ControlLine.RTS, ControlLine.DTR, ControlLine.DSR, ControlLine.CD, ControlLine.RI);
}
@Override
@@ -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