From 88ca3f57c423941b4a918e91a7231449512fa1e0 Mon Sep 17 00:00:00 2001 From: Kai Morich Date: Fri, 5 Jul 2024 21:18:37 +0200 Subject: [PATCH] flowcontrol for ftdi, pl2303, cp210x --- .idea/.gitignore | 3 +- README.md | 7 +- usbSerialForAndroid/build.gradle | 2 +- usbSerialForAndroid/coverage.gradle | 4 +- .../hoho/android/usbserial/DeviceTest.java | 387 +++++++++++++++++- .../android/usbserial/util/UsbWrapper.java | 16 + .../driver/ChromeCcdSerialDriver.java | 5 - .../usbserial/driver/CommonUsbSerialPort.java | 21 +- .../usbserial/driver/Cp21xxSerialDriver.java | 87 +++- .../usbserial/driver/FtdiSerialDriver.java | 34 ++ .../driver/GsmModemSerialDriver.java | 4 - .../driver/ProlificSerialDriver.java | 35 +- .../usbserial/driver/UsbSerialPort.java | 39 ++ .../android/usbserial/util/XonXoffFilter.java | 47 +++ 14 files changed, 655 insertions(+), 36 deletions(-) create mode 100644 usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/util/XonXoffFilter.java diff --git a/.idea/.gitignore b/.idea/.gitignore index c38a1d5f..4ee2d22f 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -5,4 +5,5 @@ workspace.xml androidTestResultsUserPreferences.xml appInsightsSettings.xml deploymentTargetDropDown.xml -deploymentTargetSelector.xml \ No newline at end of file +deploymentTargetSelector.xml +other.xml diff --git a/README.md b/README.md index d7988ccc..26084a34 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/usbSerialForAndroid/build.gradle b/usbSerialForAndroid/build.gradle index 8c79377b..049e55e6 100644 --- a/usbSerialForAndroid/build.gradle +++ b/usbSerialForAndroid/build.gradle @@ -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' } } } diff --git a/usbSerialForAndroid/coverage.gradle b/usbSerialForAndroid/coverage.gradle index 3ca3beb2..b2d2136f 100644 --- a/usbSerialForAndroid/coverage.gradle +++ b/usbSerialForAndroid/coverage.gradle @@ -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' 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 49f39dc5..325af432 100644 --- a/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/DeviceTest.java +++ b/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/DeviceTest.java @@ -40,6 +40,8 @@ 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 @@ 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 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")); + 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 void setUp() throws Exception { 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 @@ private void doReadWrite(String reason, int readWait) throws Exception { 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 void writeSizes() throws Exception { 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 void wrongDriver() throws Exception { } } // 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 void wrongDriver() throws Exception { } 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 void wrongDriver() throws Exception { 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 void wrongDriver() throws Exception { 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 void controlLines() throws Exception { } } + @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 void setBreak() throws Exception { // BREAK forwarding not implemented by arduino_leonardo_bridge.ino assertThat("", data, equalTo(new byte[]{})); } else if(usb.isCp21xxRestrictedPort) { - assertThat("", data, equalTo(new byte[]{0x26})); // send the last byte again? + assertThat("", data, equalTo(new byte[]{0x55})); // send the last byte again? } else { assertThat("", data, equalTo(new byte[]{0})); } @@ -2161,7 +2524,11 @@ public void commonMethods() throws Exception { 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)); } 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 d6a53a53..3d8e8856 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 @@ -18,6 +18,7 @@ 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 enum OpenCloseFlags { NO_IOMANAGER_THREAD, NO_IOMANAGER_START, NO_CONTROL 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 void onReceive(Context context, Intent intent) { 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 void close(EnumSet flags) { 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 void open(EnumSet flags) throws Exception { readBuffer.clear(); } readError = null; + + if (serialDriver instanceof ProlificSerialDriver && ProlificSerialPortWrapper.isDeviceTypeHxn(serialPort)) { + readBufferSize = 768; + } } public void waitForIoManagerStarted() throws IOException { diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ChromeCcdSerialDriver.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ChromeCcdSerialDriver.java index 59267c04..db19aca8 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ChromeCcdSerialDriver.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ChromeCcdSerialDriver.java @@ -79,11 +79,6 @@ public UsbSerialDriver getDriver() { public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { throw new UnsupportedOperationException(); } - - @Override - public EnumSet getSupportedControlLines() throws IOException { - return EnumSet.noneOf(ControlLine.class); - } } 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 f805433b..2ec6eae4 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 @@ -16,6 +16,7 @@ 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 void write(final byte[] src, int length, final int timeout) throws IOExce 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 boolean isOpen() { public EnumSet getControlLines() throws IOException { throw new UnsupportedOperationException(); } @Override - public abstract EnumSet getSupportedControlLines() throws IOException; + public EnumSet 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 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(); } 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 b95b4b56..492bd6c1 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 @@ -60,9 +60,14 @@ public class Cp21xxSerialPort extends CommonUsbSerialPort { 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 Cp21xxSerialPort extends CommonUsbSerialPort { /* * 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 @@ protected void openInt() throws IOException { 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 @@ private void setBaudRate(int baudRate) throws IOException { (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 void setRTS(boolean value) throws IOException { public EnumSet getControlLines() throws IOException { byte status = getStatus(); EnumSet 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 EnumSet getSupportedControlLines() throws IOException { return EnumSet.allOf(ControlLine.class); } + @Override + public boolean getXON() throws IOException { + byte[] buffer = new byte[0x13]; + int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, SILABSER_GET_COMM_STATUS_REQUEST_CODE, 0, + mPortNumber, buffer, buffer.length, USB_WRITE_TIMEOUT_MILLIS); + if (result != buffer.length) { + throw new IOException("Control transfer failed: " + SILABSER_GET_COMM_STATUS_REQUEST_CODE + " -> " + result); + } + return (buffer[4] & 8) == 0; + } + + /** + * emulate external XON/OFF + * @throws IOException + */ + public void setXON(boolean value) throws IOException { + setConfigSingle(value ? SILABSER_SET_XON_REQUEST_CODE : SILABSER_SET_XOFF_REQUEST_CODE, 0); + } + + @Override + public void setFlowControl(FlowControl flowControl) throws IOException { + byte[] data = new byte[16]; + if(flowControl == FlowControl.RTS_CTS) { + data[4] |= 0b1000_0000; // RTS + data[0] |= 0b0000_1000; // CTS + } else { + if(rts) + data[4] |= 0b0100_0000; + } + if(flowControl == FlowControl.DTR_DSR) { + data[0] |= 0b0000_0010; // DTR + data[0] |= 0b0001_0000; // DSR + } else { + if(dtr) + data[0] |= 0b0000_0001; + } + if(flowControl == FlowControl.XON_XOFF) { + byte[] chars = new byte[]{0, 0, 0, 0, CHAR_XON, CHAR_XOFF}; + int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_CHARS_REQUEST_CODE, + 0, mPortNumber, chars, chars.length, USB_WRITE_TIMEOUT_MILLIS); + if (ret != chars.length) { + throw new IOException("Error setting XON/XOFF chars"); + } + data[4] |= 0b0000_0011; + data[7] |= 0b1000_0000; + data[8] = (byte)128; + data[12] = (byte)128; + } + if(flowControl == FlowControl.XON_XOFF_INLINE) { + throw new UnsupportedOperationException(); + } + int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_FLOW_REQUEST_CODE, + 0, mPortNumber, data, data.length, USB_WRITE_TIMEOUT_MILLIS); + if (ret != data.length) { + throw new IOException("Error setting flow control"); + } + if(flowControl == FlowControl.XON_XOFF) { + setXON(true); + } + mFlowControl = flowControl; + } + + @Override + public EnumSet 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 { 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 5e3c43b2..eccab6da 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 @@ -64,6 +64,7 @@ public class FtdiSerialPort extends CommonUsbSerialPort { 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 @@ protected void openInt() throws IOException { 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 EnumSet getSupportedControlLines() throws IOException { return EnumSet.allOf(ControlLine.class); } + @Override + public void setFlowControl(FlowControl flowControl) throws IOException { + int value = 0; + int index = mPortNumber+1; + switch (flowControl) { + case NONE: + break; + case RTS_CTS: + index |= 0x100; + break; + case DTR_DSR: + index |= 0x200; + break; + case XON_XOFF_INLINE: + value = CHAR_XON + (CHAR_XOFF << 8); + index |= 0x400; + break; + default: + throw new UnsupportedOperationException(); + } + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_FLOW_CONTROL_REQUEST, + value, index, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) + throw new IOException("Set flow control failed: result=" + result); + mFlowControl = flowControl; + } + + @Override + public EnumSet 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) { diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/GsmModemSerialDriver.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/GsmModemSerialDriver.java index 25b53b3e..2c3212d9 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/GsmModemSerialDriver.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/GsmModemSerialDriver.java @@ -89,10 +89,6 @@ public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throw new UnsupportedOperationException(); } - @Override - public EnumSet getSupportedControlLines() throws IOException { - return EnumSet.noneOf(ControlLine.class); - } } public static Map getSupportedDevices() { 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 a11eb5eb..1ccffef2 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 @@ -315,6 +315,7 @@ public void openInt() throws IOException { resetDevice(); doBlackMagic(); setControlLines(mControlLinesValue); + setFlowControl(mFlowControl); } @Override @@ -526,7 +527,6 @@ public void setRTS(boolean value) throws IOException { setControlLines(newControlLinesValue); } - @Override public EnumSet getControlLines() throws IOException { int status = getStatus(); @@ -545,6 +545,39 @@ public EnumSet getSupportedControlLines() throws IOException { return EnumSet.allOf(ControlLine.class); } + @Override + public void setFlowControl(FlowControl flowControl) throws IOException { + // vendorOut values from https://www.mail-archive.com/linux-usb@vger.kernel.org/msg110968.html + switch (flowControl) { + case NONE: + if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) + vendorOut(0x0a, 0xff, null); + else + vendorOut(0, 0, null); + break; + case RTS_CTS: + if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) + vendorOut(0x0a, 0xfa, null); + else + vendorOut(0, 0x61, null); + break; + case XON_XOFF_INLINE: + if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) + vendorOut(0x0a, 0xee, null); + else + vendorOut(0, 0xc1, null); + break; + default: + throw new UnsupportedOperationException(); + } + mFlowControl = flowControl; + } + + @Override + public EnumSet 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) { 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 2d741a4d..57f68c09 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 @@ -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 @@ enum ControlLine { RTS, CTS, DTR, DSR, CD, RI } */ EnumSet 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 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. * diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/util/XonXoffFilter.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/util/XonXoffFilter.java new file mode 100644 index 00000000..261682fc --- /dev/null +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/util/XonXoffFilter.java @@ -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