1
0
mirror of https://github.com/mik3y/usb-serial-for-android synced 2025-06-07 07:56:20 +00:00

flowcontrol for ftdi, pl2303, cp210x

This commit is contained in:
Kai Morich 2024-07-05 21:18:37 +02:00
parent 843792001f
commit 88ca3f57c4
14 changed files with 655 additions and 36 deletions

3
.idea/.gitignore generated vendored
View File

@ -5,4 +5,5 @@ workspace.xml
androidTestResultsUserPreferences.xml
appInsightsSettings.xml
deploymentTargetDropDown.xml
deploymentTargetSelector.xml
deploymentTargetSelector.xml
other.xml

View File

@ -115,9 +115,10 @@ 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 with background service to stay connected while
the app is not visible or rotating, see separate github project
[SimpleUsbTerminal](https://github.com/kai-morich/SimpleUsbTerminal).
See separate github project [SimpleUsbTerminal](https://github.com/kai-morich/SimpleUsbTerminal)
for a more complete example with:
* Background service to stay connected while the app is not visible or rotating
* Flow control
## Probing for Unrecognized Devices

View File

@ -50,7 +50,7 @@ project.afterEvaluate {
// values used for local maven repo, jitpack uses github release:
groupId 'com.github.mik3y'
artifactId 'usb-serial-for-android'
version '3.7.3beta'
version '3.8.0beta'
}
}
}

View File

@ -18,7 +18,7 @@ android {
}
cp2102 { // and cp2105 first port
dimension 'device'
testInstrumentationRunnerArguments = ['test_device_driver': 'Cp21xx']
testInstrumentationRunnerArguments = ['test_device_driver': 'Cp21xx', 'test_device_port': '0']
}
cp2105 { // second port
dimension 'device'
@ -26,7 +26,7 @@ android {
}
ft232 { // and ft2232 first port
dimension 'device'
testInstrumentationRunnerArguments = ['test_device_driver': 'Ftdi']
testInstrumentationRunnerArguments = ['test_device_driver': 'Ftdi', 'test_device_port': '0']
}
ft2232 { // second port
dimension 'device'

View File

@ -40,6 +40,8 @@ import com.hoho.android.usbserial.util.TelnetWrapper;
import com.hoho.android.usbserial.util.TestBuffer;
import com.hoho.android.usbserial.util.UsbWrapper;
import com.hoho.android.usbserial.driver.UsbSerialPort.ControlLine;
import com.hoho.android.usbserial.driver.UsbSerialPort.FlowControl;
import com.hoho.android.usbserial.util.XonXoffFilter;
import org.junit.After;
@ -80,7 +82,10 @@ import static org.junit.Assert.fail;
public class DeviceTest {
private final static String TAG = DeviceTest.class.getSimpleName();
enum FlowControl_OutputLineLocked { FALSE, ON_BUFFER_FULL, TRUE }
// testInstrumentationRunnerArguments configuration
private static String rfc2217_server_host;
private static int rfc2217_server_port = 2217;
private static boolean rfc2217_server_nonstandard_baudrates;
@ -104,7 +109,7 @@ public class DeviceTest {
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"));
test_device_port = Integer.valueOf(InstrumentationRegistry.getArguments().getString("test_device_port","-1"));
// postpone parts of fixture setup to first test, because exceptions are not reported for @BeforeClass
// and test terminates with misleading 'Empty test suite'
@ -129,12 +134,16 @@ public class DeviceTest {
String driverName = usbSerialDriver.getClass().getSimpleName();
assertEquals(test_device_driver+"SerialDriver", driverName);
}
assertTrue( usbSerialDriver.getPorts().size() > test_device_port);
if (test_device_port == -1) {
test_device_port = usbSerialDriver.getPorts().size() - 1;
} else {
assertTrue( usbSerialDriver.getPorts().size() > test_device_port);
}
usb = new UsbWrapper(context, usbSerialDriver, test_device_port);
usb.setUp();
Log.i(TAG, "Using USB device "+ usb.serialPort.toString()+" driver="+usb.serialDriver.getClass().getSimpleName());
telnet.read(-1); // doesn't look related here, but very often after usb permission dialog the first test failed with telnet garbage
telnet.read(-1); // doesn't look necessary here, but very often after usb permission dialog the first test failed with telnet garbage
}
@After
@ -212,7 +221,8 @@ public class DeviceTest {
telnet.write(buf1);
data = usb.read(buf1.length, -1, readWait);
assertThat(reason, data, equalTo(buf1)); // includes array content in output
//assertArrayEquals("net2usb".getBytes(), data); // only includes array length in output
if(usb.isCp21xxRestrictedPort && usb.serialPort.getFlowControl() == FlowControl.XON_XOFF)
data = telnet.read(); // discard flow control
usb.write(buf2);
data = telnet.read(buf2.length, readWait);
assertThat(reason, data, equalTo(buf2));
@ -868,7 +878,7 @@ public class DeviceTest {
usb.serialPort.getReadEndpoint().getMaxPacketSize());
int baudRate = 300;
if(usb.serialDriver instanceof Cp21xxSerialDriver && usb.serialDriver.getPorts().size() > 1)
if(usb.serialDriver instanceof Cp21xxSerialDriver && usb.serialPort.getPortNumber() > 0)
baudRate = 2400;
usb.setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE);
@ -1683,7 +1693,7 @@ public class DeviceTest {
}
}
// FTDI only recovers from Cp21xx control commands with power toggle, so skip this combination!
if(!(usb.serialDriver instanceof Cp21xxSerialDriver | usb.serialDriver instanceof FtdiSerialDriver)) {
if(!(usb.serialDriver instanceof Cp21xxSerialDriver || usb.serialDriver instanceof FtdiSerialDriver)) {
UsbDeviceConnection wrongDeviceConnection = usbManager.openDevice(usb.serialDriver.getDevice());
UsbSerialDriver wrongSerialDriver = new Cp21xxSerialDriver(usb.serialDriver.getDevice());
UsbSerialPort wrongSerialPort = wrongSerialDriver.getPorts().get(0);
@ -1701,7 +1711,8 @@ public class DeviceTest {
} catch (IOException ignored) {
}
}
if(!(usb.serialDriver instanceof FtdiSerialDriver)) {
// CP2105 does not recover from FTDI commands
if(!((usb.serialDriver instanceof Cp21xxSerialDriver && usb.serialDriver.getPorts().size() == 2) || usb.serialDriver instanceof FtdiSerialDriver)) {
UsbDeviceConnection wrongDeviceConnection = usbManager.openDevice(usb.serialDriver.getDevice());
UsbSerialDriver wrongSerialDriver = new FtdiSerialDriver(usb.serialDriver.getDevice());
UsbSerialPort wrongSerialPort = wrongSerialDriver.getPorts().get(0);
@ -1745,7 +1756,6 @@ public class DeviceTest {
assertEquals(usb.serialDriver.getDevice(), wrongSerialDriver.getDevice());
assertEquals(wrongSerialDriver, wrongSerialPort.getDriver());
assertThrows(UnsupportedOperationException.class, () -> wrongSerialPort.setParameters(9200, 8, 1, 0));
assertEquals(EnumSet.noneOf(ControlLine.class), wrongSerialPort.getSupportedControlLines());
try {
wrongSerialPort.close();
} catch (IOException ignored) {
@ -1762,7 +1772,6 @@ public class DeviceTest {
assertEquals(usb.serialDriver.getDevice(), wrongSerialDriver.getDevice());
assertEquals(wrongSerialDriver, wrongSerialPort.getDriver());
assertThrows(UnsupportedOperationException.class, () -> wrongSerialPort.setParameters(9200, 8, 1, 0));
assertEquals(EnumSet.noneOf(ControlLine.class), wrongSerialPort.getSupportedControlLines());
try {
wrongSerialPort.close();
} catch (IOException ignored) {
@ -1988,6 +1997,360 @@ public class DeviceTest {
}
}
@Test
public void flowControlXonXoff() throws Exception {
final byte[] off_on = new byte[]{'x',CommonUsbSerialPort.CHAR_XOFF,'y',CommonUsbSerialPort.CHAR_XON,'z'};
final byte[] off_on_filtered = "xyz".getBytes();
final XonXoffFilter filter;
usb.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_THREAD, UsbWrapper.OpenCloseFlags.NO_CONTROL_LINE_INIT));
telnet.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
assertEquals(FlowControl.NONE, usb.serialPort.getFlowControl());
if (!usb.serialPort.getSupportedFlowControl().contains(FlowControl.XON_XOFF_INLINE) &&
!usb.serialPort.getSupportedFlowControl().contains(FlowControl.XON_XOFF)) {
assertThrows(UnsupportedOperationException.class, () -> usb.serialPort.setFlowControl(FlowControl.XON_XOFF_INLINE));
assertThrows(UnsupportedOperationException.class, () -> usb.serialPort.setFlowControl(FlowControl.XON_XOFF));
assertThrows(UnsupportedOperationException.class, () -> usb.serialPort.getXON());
Assume.assumeTrue("flow control not supported", false);
}
if (usb.serialPort.getSupportedFlowControl().contains(FlowControl.XON_XOFF_INLINE) &&
usb.serialPort.getSupportedFlowControl().contains(FlowControl.XON_XOFF)) {
fail("only one of both XON_XOFF variants allowed");
}
if (usb.serialPort.getSupportedFlowControl().contains(FlowControl.XON_XOFF_INLINE)) {
filter = new XonXoffFilter();
assertThrows(UnsupportedOperationException.class, () -> usb.serialPort.setFlowControl(FlowControl.XON_XOFF));
usb.serialPort.setFlowControl(FlowControl.XON_XOFF_INLINE);
assertEquals(FlowControl.XON_XOFF_INLINE, usb.serialPort.getFlowControl());
assertThrows(UnsupportedOperationException.class, () -> usb.serialPort.getXON());
assertTrue(filter.getXON());
assertThat(filter.filter(off_on), equalTo(off_on_filtered));
assertTrue(filter.getXON());
assertThat(filter.filter(new byte[]{CommonUsbSerialPort.CHAR_XOFF}), equalTo(new byte[]{}));
assertFalse(filter.getXON());
assertThat(filter.filter(new byte[]{CommonUsbSerialPort.CHAR_XON}), equalTo(new byte[]{}));
assertTrue(filter.getXON());
} else {
filter = null;
assertThrows(UnsupportedOperationException.class, () -> usb.serialPort.setFlowControl(FlowControl.XON_XOFF_INLINE));
usb.serialPort.setFlowControl(FlowControl.XON_XOFF);
assertEquals(FlowControl.XON_XOFF, usb.serialPort.getFlowControl());
assertTrue(usb.serialPort.getXON());
}
class TelnetXonXoff {
void write(boolean on) throws Exception {
byte[] data;
int i;
if (on) telnet.write(new byte[]{CommonUsbSerialPort.CHAR_XON});
else telnet.write(new byte[]{CommonUsbSerialPort.CHAR_XOFF});
if (filter != null) {
data = usb.read(1);
assertEquals(1, data.length);
data = filter.filter(data);
assertEquals(on, filter.getXON());
assertEquals(0, data.length);
} else {
for(i = 0; i < 20; i++) {
if (!usb.serialPort.getXON()) break;
Thread.sleep(10);
}
data = usb.read(-1, 0, 10);
assertEquals(0, data.length);
assertEquals(on, usb.serialPort.getXON());
}
}
};
TelnetXonXoff telnetXonXoff = new TelnetXonXoff();
byte[] data;
int i;
int bufferSize;
try {
// fast off + on
telnet.write(off_on);
data = usb.read();
if (filter != null) {
assertThat(data, equalTo(off_on));
assertTrue(filter.getXON());
} else {
assertThat(data, equalTo(off_on_filtered));
assertTrue(usb.serialPort.getXON());
}
doReadWrite("");
// USB write disabled -> send buffer full -> USB write -> SerialTimeoutException
telnetXonXoff.write(false);
bufferSize = usb.writeBufferSize;
if (usb.serialDriver instanceof Cp21xxSerialDriver && usb.serialDriver.getPorts().size() > 1 && usb.serialPort.getPortNumber() == 0)
bufferSize -= 64;
byte[] wbuf = new byte[bufferSize];
usb.write(wbuf);
data = telnet.read(-1, 100);
assertEquals(0, data.length);
try {
usb.write(new byte[1]);
fail("write error expected when buffer full");
} catch (SerialTimeoutException ignored) {
}
telnetXonXoff.write(true);
usb.write(new byte[]{1});
data = telnet.read(wbuf.length + 1);
assertEquals(wbuf.length + 1, data.length);
doReadWrite("");
// no USB read -> receive buffer full -> XOFF -> USB read -> XON
bufferSize = usb.readBufferSize;
telnet.write(new byte[bufferSize]);
data = telnet.read(1);
assertThat(data, equalTo(new byte[]{UsbSerialPort.CHAR_XOFF}));
data = usb.read(bufferSize, 2*bufferSize);
assertEquals(bufferSize, data.length);
data = telnet.read(1);
if(usb.isCp21xxRestrictedPort && data.length > 1)
data = new byte[]{data[data.length-1]};
assertThat(data, equalTo(new byte[]{UsbSerialPort.CHAR_XON}));
doReadWrite("");
// retaining XOFF state over mode change is device specific
telnetXonXoff.write(false);
usb.serialPort.setFlowControl(FlowControl.NONE);
usb.serialPort.setFlowControl(filter != null ? FlowControl.XON_XOFF_INLINE : FlowControl.XON_XOFF);
if (usb.serialDriver instanceof ProlificSerialDriver) { // only PL3032 retains XOFF state
usb.write(new byte[1]);
data = telnet.read(1, 100);
assertEquals(0, data.length);
telnetXonXoff.write(true);
data = telnet.read(1, 100);
assertEquals(1, data.length);
}
doReadWrite("");
// mode retained over close, retaining XOFF state is device specific
telnetXonXoff.write(false);
usb.close(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_THREAD, UsbWrapper.OpenCloseFlags.NO_CONTROL_LINE_INIT));
usb.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_THREAD, UsbWrapper.OpenCloseFlags.NO_CONTROL_LINE_INIT));
usb.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
if (filter != null) {
assertEquals(FlowControl.XON_XOFF_INLINE, usb.serialPort.getFlowControl());
} else {
assertEquals(FlowControl.XON_XOFF, usb.serialPort.getFlowControl());
for (i = 0; i < 20; i++) {
if (usb.serialPort.getXON()) break;
Thread.sleep(10);
}
assertTrue(usb.serialPort.getXON());
}
if (usb.serialDriver instanceof ProlificSerialDriver) { // only PL3032 retains XOFF state
usb.write(new byte[1]);
data = telnet.read(1, 100);
assertEquals(0, data.length);
telnetXonXoff.write(true);
data = telnet.read(1, 100);
assertEquals(1, data.length);
}
doReadWrite("");
} finally {
telnet.write(new byte[]{CommonUsbSerialPort.CHAR_XON});
if (filter != null) {
usb.read(1);
}
usb.write(new byte[]{CommonUsbSerialPort.CHAR_XON});
telnet.read(1);
}
}
@Test
public void flowControlRtsCts() throws Exception {
flowControlHw(FlowControl.RTS_CTS);
}
@Test
public void flowControlDtrDsr() throws Exception {
flowControlHw(FlowControl.DTR_DSR);
}
private void flowControlHw(FlowControl flowControl) throws Exception {
byte[] buf = new byte[]{0x30, 0x31, 0x32};
byte[] buf64 = new byte[64];
byte[] data;
int i;
int controlLineWait = 3; // msec
boolean outputLineReadable = false; // getControlLines returns configured value
FlowControl_OutputLineLocked outputLineLocked = FlowControl_OutputLineLocked.FALSE;
boolean outputLineSet = false;
if(usb.serialDriver instanceof ProlificSerialDriver) {
outputLineSet = true; // line set to 'true' on setFlowControl
outputLineLocked = FlowControl_OutputLineLocked.TRUE; // setRts/Dtr has no effect
}
if(usb.serialDriver instanceof Cp21xxSerialDriver) {
outputLineSet = true;
outputLineLocked = FlowControl_OutputLineLocked.ON_BUFFER_FULL;
outputLineReadable = true; // getControlLines returns actual value
}
if(usb.serialDriver instanceof FtdiSerialDriver) {
outputLineLocked = FlowControl_OutputLineLocked.ON_BUFFER_FULL;
}
usb.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_CONTROL_LINE_INIT, UsbWrapper.OpenCloseFlags.NO_IOMANAGER_THREAD));
telnet.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
// early exit, if flow control not supported
assertEquals(usb.inputLinesConnected ? EnumSet.of(ControlLine.RI) : EnumSet.noneOf(ControlLine.class), usb.serialPort.getControlLines()); // [1]
assertEquals(FlowControl.NONE, usb.serialPort.getFlowControl());
try {
usb.serialPort.setFlowControl(flowControl);
} catch (UnsupportedOperationException ignored) {
if (usb.serialPort.getSupportedFlowControl().contains(flowControl)) {
assertTrue("flow control support expected", false);
} else {
Assume.assumeTrue("flow control not supported", false);
}
}
assertEquals(flowControl, usb.serialPort.getFlowControl());
if (!usb.inputLinesConnected)
Assume.assumeTrue("flow control lines not connected", false);
// test output line state by reading corresponding input line
boolean m = flowControl == FlowControl.RTS_CTS;
Thread.sleep(controlLineWait); // required by pl2303
if(outputLineSet) { // was not set before enabling flow control at [1]
assertTrue(usb.serialPort.getControlLines().contains(m ? ControlLine.CTS : ControlLine.DSR)); // actual value
assertFalse(m ? usb.serialPort.getRTS() : usb.serialPort.getDTR()); // configured value
if(outputLineReadable) {
assertTrue(usb.serialPort.getControlLines().contains(m ? ControlLine.RTS : ControlLine.DTR)); // actual value
} else {
assertFalse(usb.serialPort.getControlLines().contains(m ? ControlLine.RTS : ControlLine.DTR)); // configured value
}
} else {
assertTrue(usb.serialPort.getControlLines().contains(ControlLine.RI));
}
if(m) usb.serialPort.setRTS(true); else usb.serialPort.setDTR(true);
assertTrue(usb.serialPort.getControlLines().contains(m ? ControlLine.CTS : ControlLine.DSR));
if(m) usb.serialPort.setRTS(false); else usb.serialPort.setDTR(false);
if(outputLineLocked == FlowControl_OutputLineLocked.TRUE) {
assertTrue(usb.serialPort.getControlLines().contains(m ? ControlLine.CTS : ControlLine.DSR));
} else {
assertFalse(usb.serialPort.getControlLines().contains(m ? ControlLine.CTS : ControlLine.DSR));
}
// read & write
usb.serialPort.setFlowControl(m ? FlowControl.RTS_CTS : FlowControl.DTR_DSR);
if(!outputLineSet) {
if (m) usb.serialPort.setRTS(true); else usb.serialPort.setDTR(true);
}
telnet.write(buf);
data = usb.read(buf.length, -1, 100);
assertThat(data, equalTo(buf));
usb.write(buf);
data = telnet.read(buf.length, 100);
assertThat(data, equalTo(buf));
// write disabled + continued
if(m) usb.serialPort.setDTR(true); else usb.serialPort.setRTS(true);
Thread.sleep(controlLineWait);
assertFalse(usb.serialPort.getControlLines().contains(m ? ControlLine.CTS : ControlLine.DSR));
telnet.write(buf);
data = usb.read(buf.length, -1, 100);
assertThat(data, equalTo(buf));
usb.write(buf);
data = telnet.read(buf.length, 200); // stopped
assertThat(data, equalTo(new byte[0]));
if(m) usb.serialPort.setDTR(false); else usb.serialPort.setRTS(false);
Thread.sleep(controlLineWait);
assertTrue(usb.serialPort.getControlLines().contains(m ? ControlLine.CTS : ControlLine.DSR));
data = telnet.read(buf.length, 100); // continued
assertThat(data, equalTo(buf));
// write disabled -> buffer full -> SerialTimeoutException
if(m) usb.serialPort.setDTR(true); else usb.serialPort.setRTS(true);
Thread.sleep(controlLineWait);
assertFalse(usb.serialPort.getControlLines().contains(m ? ControlLine.CTS : ControlLine.DSR));
usb.write(buf64);
data = telnet.read(buf64.length, 200);
assertThat(data, equalTo(new byte[0]));
try {
for (i = 0; i < 80; i++) {
usb.write(buf64);
}
fail("write error expected when buffer full");
} catch(SerialTimeoutException ignored) {
}
long t1 = System.currentTimeMillis();
try {
usb.serialPort.write(buf64, 200);
fail("write error expected when buffer full");
} catch(SerialTimeoutException ignored) {
long t2 = System.currentTimeMillis();
assertTrue("expected IOException after 200msec timeout, got "+(t2-t1)+"msec", t2-t1 >= 200);
}
// continue write
if(m) usb.serialPort.setDTR(false); else usb.serialPort.setRTS(false);
Thread.sleep(controlLineWait);
assertTrue(usb.serialPort.getControlLines().contains(m ? ControlLine.CTS : ControlLine.DSR));
data = telnet.read(buf64.length, 200);
Thread.sleep(100); // 64 bytes are free again after 4.4ms
usb.write(buf64);
// no read -> buffer full -> RTS/DTR off
class NoRead {
void run() throws Exception {
assertTrue(usb.serialPort.getControlLines().contains(m ? ControlLine.CTS : ControlLine.DSR));
int i;
for (i = 0; i < 120 && usb.serialPort.getControlLines().contains(m ? ControlLine.CTS : ControlLine.DSR); i++) {
telnet.write(buf64);
Thread.sleep(controlLineWait);
}
assertFalse(usb.serialPort.getControlLines().contains(m ? ControlLine.CTS : ControlLine.DSR));
byte[] data = usb.read(-1, i*64, 100);
Thread.sleep(controlLineWait);
assertTrue(usb.serialPort.getControlLines().contains(m ? ControlLine.CTS : ControlLine.DSR));
}
}
new NoRead().run();
// no read -> buffer full -> RTS/DTR off -> output line locked
if(outputLineLocked != FlowControl_OutputLineLocked.TRUE) {
assertTrue(usb.serialPort.getControlLines().contains(m ? ControlLine.CTS : ControlLine.DSR));
for(i = 0; i < 120 && usb.serialPort.getControlLines().contains(m ? ControlLine.CTS : ControlLine.DSR); i++) {
telnet.write(buf64);
Thread.sleep(controlLineWait);
}
assertFalse(usb.serialPort.getControlLines().contains(m ? ControlLine.CTS : ControlLine.DSR));
if(m) usb.serialPort.setRTS(true); else usb.serialPort.setDTR(true);
Thread.sleep(controlLineWait);
if(outputLineLocked == FlowControl_OutputLineLocked.ON_BUFFER_FULL)
assertFalse(usb.serialPort.getControlLines().contains(m ? ControlLine.CTS : ControlLine.DSR));
else
assertTrue(usb.serialPort.getControlLines().contains(m ? ControlLine.CTS : ControlLine.DSR));
data = usb.read(-1, i*64, 100);
}
// mode retained over close
assertEquals(flowControl, usb.serialPort.getFlowControl());
if(m) usb.serialPort.setRTS(true); else usb.serialPort.setDTR(true);
assertTrue(usb.serialPort.getControlLines().contains(m ? ControlLine.CTS : ControlLine.DSR));
usb.close(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_CONTROL_LINE_INIT));
usb.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_CONTROL_LINE_INIT, UsbWrapper.OpenCloseFlags.NO_IOMANAGER_THREAD));
assertEquals(flowControl, usb.serialPort.getFlowControl());
assertTrue(m ? usb.serialPort.getRTS() : usb.serialPort.getDTR());
assertTrue(usb.serialPort.getControlLines().contains(m ? ControlLine.CTS : ControlLine.DSR));
new NoRead().run();
}
@Test
public void setBreak() throws Exception {
usb.open();
@ -2005,7 +2368,7 @@ public class DeviceTest {
// BREAK forwarding not implemented by arduino_leonardo_bridge.ino
assertThat("<break>", data, equalTo(new byte[]{}));
} else if(usb.isCp21xxRestrictedPort) {
assertThat("<break>", data, equalTo(new byte[]{0x26})); // send the last byte again?
assertThat("<break>", data, equalTo(new byte[]{0x55})); // send the last byte again?
} else {
assertThat("<break>", data, equalTo(new byte[]{0}));
}
@ -2161,7 +2524,11 @@ public class DeviceTest {
assertThrows(UnsupportedOperationException.class, wrongSerialPort::getRI);
assertThrows(UnsupportedOperationException.class, wrongSerialPort::getRTS);
assertThrows(UnsupportedOperationException.class, () -> wrongSerialPort.setRTS(true));
assertEquals(EnumSet.noneOf(ControlLine.class), wrongSerialPort.getSupportedControlLines());
assertThrows(UnsupportedOperationException.class, wrongSerialPort::getControlLines);
assertEquals(EnumSet.of(FlowControl.NONE), wrongSerialPort.getSupportedFlowControl());
assertEquals(FlowControl.NONE, wrongSerialPort.getFlowControl());
assertThrows(UnsupportedOperationException.class, () -> wrongSerialPort.setFlowControl(FlowControl.NONE));
assertThrows(UnsupportedOperationException.class, () -> wrongSerialPort.purgeHwBuffers(true, true));
assertThrows(UnsupportedOperationException.class, () -> wrongSerialPort.setBreak(true));
}

View File

@ -18,6 +18,7 @@ import com.hoho.android.usbserial.driver.CommonUsbSerialPort;
import com.hoho.android.usbserial.driver.Cp21xxSerialDriver;
import com.hoho.android.usbserial.driver.FtdiSerialDriver;
import com.hoho.android.usbserial.driver.ProlificSerialDriver;
import com.hoho.android.usbserial.driver.ProlificSerialPortWrapper;
import com.hoho.android.usbserial.driver.UsbId;
import com.hoho.android.usbserial.driver.UsbSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialPort;
@ -62,6 +63,7 @@ public class UsbWrapper implements SerialInputOutputManager.Listener {
public boolean inputLinesOnlyRtsCts;
public int writePacketSize = -1;
public int writeBufferSize = -1;
public int readBufferSize = -1;
public UsbWrapper(Context context, UsbSerialDriver serialDriver, int devicePort) {
this.context = context;
@ -145,10 +147,18 @@ public class UsbWrapper implements SerialInputOutputManager.Listener {
case 2: writePacketSize = 512; writeBufferSize = 4096; break;
case 4: writePacketSize = 512; writeBufferSize = 2048; break;
}
if(serialDriver.getDevice().getProductId() == UsbId.FTDI_FT231X)
writeBufferSize = 512;
} else if (serialDriver instanceof CdcAcmSerialDriver) {
writePacketSize = 64; writeBufferSize = 128;
}
readBufferSize = writeBufferSize;
if (serialDriver instanceof Cp21xxSerialDriver && serialDriver.getPorts().size() == 2) {
readBufferSize = 256;
} else if (serialDriver instanceof FtdiSerialDriver && serialDriver.getPorts().size() == 1 && serialDriver.getDevice().getProductId() != UsbId.FTDI_FT231X) {
readBufferSize = 256;
} // PL2303 HXN checked in open()
}
public void tearDown() {
@ -177,6 +187,8 @@ public class UsbWrapper implements SerialInputOutputManager.Listener {
if(!flags.contains(OpenCloseFlags.NO_CONTROL_LINE_INIT)) {
serialPort.setDTR(false);
serialPort.setRTS(false);
if (serialPort.getFlowControl() != UsbSerialPort.FlowControl.NONE)
serialPort.setFlowControl(UsbSerialPort.FlowControl.NONE);
}
} catch (Exception ignored) {
}
@ -226,6 +238,10 @@ public class UsbWrapper implements SerialInputOutputManager.Listener {
readBuffer.clear();
}
readError = null;
if (serialDriver instanceof ProlificSerialDriver && ProlificSerialPortWrapper.isDeviceTypeHxn(serialPort)) {
readBufferSize = 768;
}
}
public void waitForIoManagerStarted() throws IOException {

View File

@ -79,11 +79,6 @@ public class ChromeCcdSerialDriver implements UsbSerialDriver{
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public EnumSet<ControlLine> getSupportedControlLines() throws IOException {
return EnumSet.noneOf(ControlLine.class);
}
}
public static Map<Integer, int[]> getSupportedDevices() {

View File

@ -16,6 +16,7 @@ import com.hoho.android.usbserial.util.MonotonicClock;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.InvalidParameterException;
import java.util.EnumSet;
/**
@ -38,6 +39,7 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort {
protected UsbEndpoint mReadEndpoint;
protected UsbEndpoint mWriteEndpoint;
protected UsbRequest mUsbRequest;
protected FlowControl mFlowControl = FlowControl.NONE;
/**
* Internal write buffer.
@ -281,6 +283,7 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort {
if (actualLength <= 0) {
String msg = "Error writing " + requestLength + " bytes at offset " + offset + " of total " + src.length + " after " + elapsed + "msec, rc=" + actualLength;
if (timeout != 0) {
// could be buffer full because: writing to fast, stopped by flow control
testConnection(elapsed < timeout, msg);
throw new SerialTimeoutException(msg, offset);
} else {
@ -328,12 +331,22 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort {
public EnumSet<ControlLine> getControlLines() throws IOException { throw new UnsupportedOperationException(); }
@Override
public abstract EnumSet<ControlLine> getSupportedControlLines() throws IOException;
public EnumSet<ControlLine> getSupportedControlLines() throws IOException { return EnumSet.noneOf(ControlLine.class); }
@Override
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {
throw new UnsupportedOperationException();
}
public void setFlowControl(FlowControl flowcontrol) throws IOException { throw new UnsupportedOperationException(); }
@Override
public FlowControl getFlowControl() { return mFlowControl; }
@Override
public EnumSet<FlowControl> getSupportedFlowControl() { return EnumSet.of(FlowControl.NONE); }
@Override
public boolean getXON() throws IOException { throw new UnsupportedOperationException(); }
@Override
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { throw new UnsupportedOperationException(); }
@Override
public void setBreak(boolean value) throws IOException { throw new UnsupportedOperationException(); }

View File

@ -60,9 +60,14 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
private static final int SILABSER_SET_LINE_CTL_REQUEST_CODE = 0x03;
private static final int SILABSER_SET_BREAK_REQUEST_CODE = 0x05;
private static final int SILABSER_SET_MHS_REQUEST_CODE = 0x07;
private static final int SILABSER_SET_BAUDRATE = 0x1E;
private static final int SILABSER_FLUSH_REQUEST_CODE = 0x12;
private static final int SILABSER_GET_MDMSTS_REQUEST_CODE = 0x08;
private static final int SILABSER_SET_XON_REQUEST_CODE = 0x09;
private static final int SILABSER_SET_XOFF_REQUEST_CODE = 0x0A;
private static final int SILABSER_GET_COMM_STATUS_REQUEST_CODE = 0x10;
private static final int SILABSER_FLUSH_REQUEST_CODE = 0x12;
private static final int SILABSER_SET_FLOW_REQUEST_CODE = 0x13;
private static final int SILABSER_SET_CHARS_REQUEST_CODE = 0x19;
private static final int SILABSER_SET_BAUDRATE_REQUEST_CODE = 0x1E;
private static final int FLUSH_READ_CODE = 0x0a;
private static final int FLUSH_WRITE_CODE = 0x05;
@ -84,6 +89,8 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
/*
* SILABSER_GET_MDMSTS_REQUEST_CODE
*/
private static final int STATUS_DTR = 0x01;
private static final int STATUS_RTS = 0x02;
private static final int STATUS_CTS = 0x10;
private static final int STATUS_DSR = 0x20;
private static final int STATUS_RI = 0x40;
@ -147,6 +154,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_ENABLE);
setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, (dtr ? DTR_ENABLE : DTR_DISABLE) | (rts ? RTS_ENABLE : RTS_DISABLE));
setFlowControl(mFlowControl);
}
@Override
@ -166,7 +174,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
(byte) ((baudRate >> 16) & 0xff),
(byte) ((baudRate >> 24) & 0xff)
};
int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_BAUDRATE,
int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_BAUDRATE_REQUEST_CODE,
0, mPortNumber, data, 4, USB_WRITE_TIMEOUT_MILLIS);
if (ret < 0) {
throw new IOException("Error setting baud rate");
@ -289,9 +297,11 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
public EnumSet<ControlLine> getControlLines() throws IOException {
byte status = getStatus();
EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class);
if(rts) set.add(ControlLine.RTS);
//if(rts) set.add(ControlLine.RTS); // configured value
if((status & STATUS_RTS) != 0) set.add(ControlLine.RTS); // actual value
if((status & STATUS_CTS) != 0) set.add(ControlLine.CTS);
if(dtr) set.add(ControlLine.DTR);
//if(dtr) set.add(ControlLine.DTR); // configured value
if((status & STATUS_DTR) != 0) set.add(ControlLine.DTR); // actual value
if((status & STATUS_DSR) != 0) set.add(ControlLine.DSR);
if((status & STATUS_CD) != 0) set.add(ControlLine.CD);
if((status & STATUS_RI) != 0) set.add(ControlLine.RI);
@ -303,6 +313,73 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
return EnumSet.allOf(ControlLine.class);
}
@Override
public boolean getXON() throws IOException {
byte[] buffer = new byte[0x13];
int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, SILABSER_GET_COMM_STATUS_REQUEST_CODE, 0,
mPortNumber, buffer, buffer.length, USB_WRITE_TIMEOUT_MILLIS);
if (result != buffer.length) {
throw new IOException("Control transfer failed: " + SILABSER_GET_COMM_STATUS_REQUEST_CODE + " -> " + result);
}
return (buffer[4] & 8) == 0;
}
/**
* emulate external XON/OFF
* @throws IOException
*/
public void setXON(boolean value) throws IOException {
setConfigSingle(value ? SILABSER_SET_XON_REQUEST_CODE : SILABSER_SET_XOFF_REQUEST_CODE, 0);
}
@Override
public void setFlowControl(FlowControl flowControl) throws IOException {
byte[] data = new byte[16];
if(flowControl == FlowControl.RTS_CTS) {
data[4] |= 0b1000_0000; // RTS
data[0] |= 0b0000_1000; // CTS
} else {
if(rts)
data[4] |= 0b0100_0000;
}
if(flowControl == FlowControl.DTR_DSR) {
data[0] |= 0b0000_0010; // DTR
data[0] |= 0b0001_0000; // DSR
} else {
if(dtr)
data[0] |= 0b0000_0001;
}
if(flowControl == FlowControl.XON_XOFF) {
byte[] chars = new byte[]{0, 0, 0, 0, CHAR_XON, CHAR_XOFF};
int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_CHARS_REQUEST_CODE,
0, mPortNumber, chars, chars.length, USB_WRITE_TIMEOUT_MILLIS);
if (ret != chars.length) {
throw new IOException("Error setting XON/XOFF chars");
}
data[4] |= 0b0000_0011;
data[7] |= 0b1000_0000;
data[8] = (byte)128;
data[12] = (byte)128;
}
if(flowControl == FlowControl.XON_XOFF_INLINE) {
throw new UnsupportedOperationException();
}
int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_FLOW_REQUEST_CODE,
0, mPortNumber, data, data.length, USB_WRITE_TIMEOUT_MILLIS);
if (ret != data.length) {
throw new IOException("Error setting flow control");
}
if(flowControl == FlowControl.XON_XOFF) {
setXON(true);
}
mFlowControl = flowControl;
}
@Override
public EnumSet<FlowControl> getSupportedFlowControl() {
return EnumSet.of(FlowControl.NONE, FlowControl.RTS_CTS, FlowControl.DTR_DSR, FlowControl.XON_XOFF);
}
@Override
// note: only working on some devices, on other devices ignored w/o error
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {

View File

@ -64,6 +64,7 @@ public class FtdiSerialDriver implements UsbSerialDriver {
private static final int RESET_REQUEST = 0;
private static final int MODEM_CONTROL_REQUEST = 1;
private static final int SET_FLOW_CONTROL_REQUEST = 2;
private static final int SET_BAUD_RATE_REQUEST = 3;
private static final int SET_DATA_REQUEST = 4;
private static final int GET_MODEM_STATUS_REQUEST = 5;
@ -120,6 +121,7 @@ public class FtdiSerialDriver implements UsbSerialDriver {
if (result != 0) {
throw new IOException("Init RTS,DTR failed: result=" + result);
}
setFlowControl(mFlowControl);
// mDevice.getVersion() would require API 23
byte[] rawDescriptors = mConnection.getRawDescriptors();
@ -377,6 +379,38 @@ public class FtdiSerialDriver implements UsbSerialDriver {
return EnumSet.allOf(ControlLine.class);
}
@Override
public void setFlowControl(FlowControl flowControl) throws IOException {
int value = 0;
int index = mPortNumber+1;
switch (flowControl) {
case NONE:
break;
case RTS_CTS:
index |= 0x100;
break;
case DTR_DSR:
index |= 0x200;
break;
case XON_XOFF_INLINE:
value = CHAR_XON + (CHAR_XOFF << 8);
index |= 0x400;
break;
default:
throw new UnsupportedOperationException();
}
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_FLOW_CONTROL_REQUEST,
value, index, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0)
throw new IOException("Set flow control failed: result=" + result);
mFlowControl = flowControl;
}
@Override
public EnumSet<FlowControl> getSupportedFlowControl() {
return EnumSet.of(FlowControl.NONE, FlowControl.RTS_CTS, FlowControl.DTR_DSR, FlowControl.XON_XOFF_INLINE);
}
@Override
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {
if (purgeWriteBuffers) {

View File

@ -89,10 +89,6 @@ public class GsmModemSerialDriver implements UsbSerialDriver{
throw new UnsupportedOperationException();
}
@Override
public EnumSet<ControlLine> getSupportedControlLines() throws IOException {
return EnumSet.noneOf(ControlLine.class);
}
}
public static Map<Integer, int[]> getSupportedDevices() {

View File

@ -315,6 +315,7 @@ public class ProlificSerialDriver implements UsbSerialDriver {
resetDevice();
doBlackMagic();
setControlLines(mControlLinesValue);
setFlowControl(mFlowControl);
}
@Override
@ -526,7 +527,6 @@ public class ProlificSerialDriver implements UsbSerialDriver {
setControlLines(newControlLinesValue);
}
@Override
public EnumSet<ControlLine> getControlLines() throws IOException {
int status = getStatus();
@ -545,6 +545,39 @@ public class ProlificSerialDriver implements UsbSerialDriver {
return EnumSet.allOf(ControlLine.class);
}
@Override
public void setFlowControl(FlowControl flowControl) throws IOException {
// vendorOut values from https://www.mail-archive.com/linux-usb@vger.kernel.org/msg110968.html
switch (flowControl) {
case NONE:
if (mDeviceType == DeviceType.DEVICE_TYPE_HXN)
vendorOut(0x0a, 0xff, null);
else
vendorOut(0, 0, null);
break;
case RTS_CTS:
if (mDeviceType == DeviceType.DEVICE_TYPE_HXN)
vendorOut(0x0a, 0xfa, null);
else
vendorOut(0, 0x61, null);
break;
case XON_XOFF_INLINE:
if (mDeviceType == DeviceType.DEVICE_TYPE_HXN)
vendorOut(0x0a, 0xee, null);
else
vendorOut(0, 0xc1, null);
break;
default:
throw new UnsupportedOperationException();
}
mFlowControl = flowControl;
}
@Override
public EnumSet<FlowControl> getSupportedFlowControl() {
return EnumSet.of(FlowControl.NONE, FlowControl.RTS_CTS, FlowControl.XON_XOFF_INLINE);
}
@Override
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {
if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) {

View File

@ -60,6 +60,15 @@ public interface UsbSerialPort extends Closeable {
/** Values for get[Supported]ControlLines() */
enum ControlLine { RTS, CTS, DTR, DSR, CD, RI }
/** Values for (set|get|getSupported)FlowControl() */
enum FlowControl { NONE, RTS_CTS, DTR_DSR, XON_XOFF, XON_XOFF_INLINE }
/** XON character used with flow control XON/XOFF */
char CHAR_XON = 17;
/** XOFF character used with flow control XON/XOFF */
char CHAR_XOFF = 19;
/**
* Returns the driver used by this port.
*/
@ -259,6 +268,36 @@ public interface UsbSerialPort extends Closeable {
*/
EnumSet<ControlLine> getSupportedControlLines() throws IOException;
/**
* Set flow control mode, if supported
* @param flowControl @FlowControl
* @throws IOException if an error occurred during writing
* @throws UnsupportedOperationException if not supported
*/
void setFlowControl(FlowControl flowControl) throws IOException;
/**
* Get flow control mode.
* @return FlowControl
*/
FlowControl getFlowControl();
/**
* Get supported flow control modes
* @return EnumSet.contains(...) is {@code true} if supported, else {@code false}
*/
EnumSet<FlowControl> getSupportedFlowControl();
/**
* If flow control = XON_XOFF, indicates that send is enabled by XON.
* Devices supporting flow control = XON_XOFF_INLINE return CHAR_XON/CHAR_XOFF in read() data.
*
* @return the current state
* @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported
*/
boolean getXON() throws IOException;
/**
* Purge non-transmitted output data and / or non-read input data.
*

View File

@ -0,0 +1,47 @@
package com.hoho.android.usbserial.util;
import com.hoho.android.usbserial.driver.UsbSerialPort;
import java.io.IOException;
/**
* Some devices return XON and XOFF characters inline in read() data.
* Other devices return XON / XOFF condition thru getXOFF() method.
*/
public class XonXoffFilter {
private boolean xon = true;
public XonXoffFilter() {
}
public boolean getXON() {
return xon;
}
/**
* Filter XON/XOFF from read() data and remember
*
* @param data unfiltered data
* @return filtered data
*/
public byte[] filter(byte[] data) {
int found = 0;
for (int i=0; i<data.length; i++) {
if (data[i] == UsbSerialPort.CHAR_XON || data[i] == UsbSerialPort.CHAR_XOFF)
found++;
}
if(found == 0)
return data;
byte[] filtered = new byte[data.length - found];
for (int i=0, j=0; i<data.length; i++) {
if (data[i] == UsbSerialPort.CHAR_XON)
xon = true;
else if(data[i] == UsbSerialPort.CHAR_XOFF)
xon = false;
else
filtered[j++] = data[i];
}
return filtered;
}
}