usb-serial-for-android/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/DeviceTest.java

2077 wiersze
92 KiB
Java

/*
* restrictions
* - as real hardware is used, timing might need tuning. see:
* - Thread.sleep(...)
* - obj.wait(...)
* - missing functionality on certain devices, see:
* - if(rfc2217_server_nonstandard_baudrates)
* - if(usbSerialDriver instanceof ...)
*
*/
package com.hoho.android.usbserial;
import android.content.Context;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.os.Process;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
import com.hoho.android.usbserial.driver.CdcAcmSerialDriver;
import com.hoho.android.usbserial.driver.Ch34xSerialDriver;
import com.hoho.android.usbserial.driver.CommonUsbSerialPort;
import com.hoho.android.usbserial.driver.Cp21xxSerialDriver;
import com.hoho.android.usbserial.driver.FtdiSerialDriver;
import com.hoho.android.usbserial.driver.ProbeTable;
import com.hoho.android.usbserial.driver.ProlificSerialDriver;
import com.hoho.android.usbserial.driver.SerialTimeoutException;
import com.hoho.android.usbserial.driver.UsbId;
import com.hoho.android.usbserial.driver.UsbSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialPort;
import com.hoho.android.usbserial.driver.UsbSerialProber;
import com.hoho.android.usbserial.util.SerialInputOutputManager;
import com.hoho.android.usbserial.util.TelnetWrapper;
import com.hoho.android.usbserial.util.UsbWrapper;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.runner.RunWith;
import java.io.IOException;
import java.nio.BufferOverflowException;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.Executors;
import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@RunWith(AndroidJUnit4.class)
public class DeviceTest {
private final static String TAG = DeviceTest.class.getSimpleName();
// testInstrumentationRunnerArguments configuration
private static String rfc2217_server_host;
private static int rfc2217_server_port = 2217;
private static boolean rfc2217_server_nonstandard_baudrates;
private static String test_device_driver;
private static int test_device_port;
private Context context;
private UsbManager usbManager;
UsbWrapper usb;
static TelnetWrapper telnet;
private boolean isCp21xxRestrictedPort = false; // second port of Cp2105 has limited dataBits, stopBits, parity
@Rule
public TestRule watcher = new TestWatcher() {
protected void starting(Description description) {
Log.i(TAG, "===== starting test: " + description.getMethodName()+ " =====");
}
};
@BeforeClass
public static void setUpFixture() throws Exception {
rfc2217_server_host = InstrumentationRegistry.getArguments().getString("rfc2217_server_host");
rfc2217_server_nonstandard_baudrates = Boolean.valueOf(InstrumentationRegistry.getArguments().getString("rfc2217_server_nonstandard_baudrates"));
test_device_driver = InstrumentationRegistry.getArguments().getString("test_device_driver");
test_device_port = Integer.valueOf(InstrumentationRegistry.getArguments().getString("test_device_port","0"));
// postpone parts of fixture setup to first test, because exceptions are not reported for @BeforeClass
// and test terminates with misleading 'Empty test suite'
telnet = new TelnetWrapper(rfc2217_server_host, rfc2217_server_port);
}
@Before
public void setUp() throws Exception {
telnet.setUp();
context = InstrumentationRegistry.getContext();
usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
List<UsbSerialDriver> availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(usbManager);
if(availableDrivers.isEmpty()) {
ProbeTable customTable = new ProbeTable();
customTable.addProduct(0x2342, 0x8036, CdcAcmSerialDriver.class); // arduino multiport cdc witch custom VID
availableDrivers = new UsbSerialProber(customTable).findAllDrivers(usbManager);
}
assertEquals("no USB device found", 1, availableDrivers.size());
UsbSerialDriver usbSerialDriver = availableDrivers.get(0);
if(test_device_driver != null) {
String driverName = usbSerialDriver.getClass().getSimpleName();
assertEquals(test_device_driver+"SerialDriver", driverName);
}
assertTrue( usbSerialDriver.getPorts().size() > test_device_port);
usb = new UsbWrapper(context, usbSerialDriver, test_device_port);
usb.setUp();
Log.i(TAG, "Using USB device "+ usb.serialPort.toString()+" driver="+usb.serialDriver.getClass().getSimpleName());
isCp21xxRestrictedPort = usb.serialDriver instanceof Cp21xxSerialDriver && usb.serialDriver.getPorts().size()==2 && test_device_port == 1;
telnet.read(-1); // doesn't look related here, but very often after usb permission dialog the first test failed with telnet garbage
}
@After
public void tearDown() throws IOException {
if(usb != null)
usb.tearDown();
telnet.tearDown();
}
@AfterClass
public static void tearDownFixture() throws Exception {
telnet.tearDownFixture();
}
private static class TestBuffer {
private byte[] buf;
private int len;
private TestBuffer(int length) {
len = 0;
buf = new byte[length];
int i=0;
int j=0;
for(j=0; j<length/16; j++)
for(int k=0; k<16; k++)
buf[i++]=(byte)j;
while(i<length)
buf[i++]=(byte)j;
}
private boolean testRead(byte[] data) {
assertNotEquals(0, data.length);
assertTrue("got " + (len+data.length) +" bytes", (len+data.length) <= buf.length);
for(int j=0; j<data.length; j++)
assertEquals("at pos "+(len+j), (byte)((len+j)/16), data[j]);
len += data.length;
//Log.d(TAG, "read " + len);
return len == buf.length;
}
}
// clone of org.apache.commons.lang3.StringUtils.indexOfDifference + optional startpos
private static int indexOfDifference(final CharSequence cs1, final CharSequence cs2) {
return indexOfDifference(cs1, cs2, 0, 0);
}
private static int indexOfDifference(final CharSequence cs1, final CharSequence cs2, int cs1startpos, int cs2startpos) {
if (cs1 == cs2) {
return -1;
}
if (cs1 == null || cs2 == null) {
return 0;
}
if(cs1startpos < 0 || cs2startpos < 0)
return -1;
int i, j;
for (i = cs1startpos, j = cs2startpos; i < cs1.length() && j < cs2.length(); ++i, ++j) {
if (cs1.charAt(i) != cs2.charAt(j)) {
break;
}
}
if (j < cs2.length() || i < cs1.length()) {
return i;
}
return -1;
}
private int findDifference(final StringBuilder data, final StringBuilder expected) {
int length = 0;
int datapos = indexOfDifference(data, expected);
int expectedpos = datapos;
while(datapos != -1) {
int nextexpectedpos = -1;
int nextdatapos = datapos + 2;
int len = -1;
if(nextdatapos + 10 < data.length()) { // try to sync data+expected, assuming that data is lost, but not corrupted
String nextsub = data.substring(nextdatapos, nextdatapos + 10);
nextexpectedpos = expected.indexOf(nextsub, expectedpos);
if(nextexpectedpos >= 0) {
len = nextexpectedpos - expectedpos - 2;
}
}
Log.i(TAG, "difference at " + datapos + " len " + len );
Log.d(TAG, " got " + data.substring(Math.max(datapos - 20, 0), Math.min(datapos + 20, data.length())));
Log.d(TAG, " expected " + expected.substring(Math.max(expectedpos - 20, 0), Math.min(expectedpos + 20, expected.length())));
datapos = indexOfDifference(data, expected, nextdatapos, nextexpectedpos);
expectedpos = nextexpectedpos + (datapos - nextdatapos);
if(len==-1) length=-1;
else length+=len;
}
return length;
}
private void doReadWrite(String reason) throws Exception {
doReadWrite(reason, -1);
}
private void doReadWrite(String reason, int readWait) throws Exception {
byte[] buf1 = new byte[]{ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16};
byte[] buf2 = new byte[]{ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26};
byte[] data;
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
usb.write(buf2);
data = telnet.read(buf2.length, readWait);
assertThat(reason, data, equalTo(buf2));
}
private void purgeWriteBuffer(int timeout) throws Exception {
try {
Log.d(TAG, " purge begin");
usb.serialPort.purgeHwBuffers(true, false);
} catch(UnsupportedOperationException ignored) {}
byte[] data = telnet.read(-1, timeout);
int len = 0;
while(data.length != 0) {
len += data.length;
Log.d(TAG, " purge read " + data.length);
data = telnet.read(-1, timeout);
}
Log.d(TAG, " purge end " + len);
}
@Test
public void openClose() throws Exception {
usb.open();
telnet.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
doReadWrite("");
try {
usb.serialPort.open(usb.deviceConnection);
fail("already open expected");
} catch (IOException ignored) {
}
doReadWrite("");
usb.close();
try {
usb.serialPort.close();
fail("already closed expected");
} catch (IOException ignored) {
}
try {
usb.write(new byte[]{0x00});
fail("write error expected");
} catch (IOException ignored) {
}
try {
usb.read(1);
fail("read error expected");
} catch (IOException ignored) {
}
try {
usb.setParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE);
fail("error expected");
} catch (IOException ignored) {
} catch (NullPointerException ignored) {
}
usb.open();
telnet.setParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE);
doReadWrite("");
// close port before iomanager
assertEquals(SerialInputOutputManager.State.RUNNING, usb.ioManager.getState());
usb.serialPort.close();
for (int i = 0; i < 1000; i++) {
if (usb.ioManager.getState() == SerialInputOutputManager.State.STOPPED)
break;
Thread.sleep(1);
}
// assertEquals(SerialInputOutputManager.State.STOPPED, usb.usbIoManager.getState());
// unstable. null'ify not-stopped ioManager, else usbClose would try again
if(SerialInputOutputManager.State.STOPPED != usb.ioManager.getState())
usb.ioManager = null;
}
@Test
public void prolificBaudRate() throws Exception {
Assume.assumeTrue("only for Prolific", usb.serialDriver instanceof ProlificSerialDriver);
int[] baudRates = {
75, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600, 14400, 19200,
28800, 38400, 57600, 115200, 128000, 134400, 161280, 201600, 230400, 268800,
403200, 460800, 614400, 806400, 921600, 1228800, 2457600, 3000000, /*6000000*/
};
usb.open();
try {
usb.setParameters(45, 8, 1, UsbSerialPort.PARITY_NONE);
fail("baud rate to low expected");
} catch(UnsupportedOperationException ignored) {}
usb.setParameters(46, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(384_000_000, 8, 1, UsbSerialPort.PARITY_NONE);
try {
usb.setParameters(384_000_001, 8, 1, UsbSerialPort.PARITY_NONE);
fail("baud rate to high expected");
} catch(UnsupportedOperationException ignored) {}
usb.setParameters(11_636_363, 8, 1, UsbSerialPort.PARITY_NONE);
try {
usb.setParameters(11_636_364, 8, 1, UsbSerialPort.PARITY_NONE);
fail("baud rate deviation to high expected");
} catch(UnsupportedOperationException ignored) {}
for(int baudRate : baudRates) {
int readWait = 500;
if(baudRate < 300) readWait = 1000;
if(baudRate < 150) readWait = 2000;
telnet.setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE);
doReadWrite(String.valueOf(baudRate), readWait);
usb.setParameters(baudRate + 1, 8, 1, UsbSerialPort.PARITY_NONE);
doReadWrite(String.valueOf(baudRate + 1), readWait);
// silent fallback to 9600 for unsupported baud rates
telnet.setParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(baudRate + 1 + (1<<29), 8, 1, UsbSerialPort.PARITY_NONE);
doReadWrite(String.valueOf(baudRate + 1) + " + 1<<29", readWait);
}
// some PL2303... data sheets mention additional baud rates, others don't
// they do not work with my devices and linux driver also excludes them
baudRates = new int[]{110, 56000, 256000};
for(int baudRate : baudRates) {
int readWait = 500;
if(baudRate < 300) readWait = 1000;
if(baudRate < 150) readWait = 2000;
telnet.setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE);
doReadWrite(String.valueOf(baudRate), readWait);
// silent fallback to 9600 for unsupported baud rates
telnet.setParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(baudRate + (1<<29), 8, 1, UsbSerialPort.PARITY_NONE);
doReadWrite(String.valueOf(baudRate ) + " + 1<<29", readWait);
}
}
@Test
public void ftdiBaudRate() throws Exception {
Assume.assumeTrue("only for FTDI", usb.serialDriver instanceof FtdiSerialDriver);
usb.open();
try {
usb.setParameters(183, 8, 1, UsbSerialPort.PARITY_NONE);
fail("baud rate to low expected");
} catch (UnsupportedOperationException ignored) {
}
usb.setParameters(184, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters( 960000, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(1000000, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(1043478, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(1090909, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(1142857, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(1200000, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(1263157, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(1333333, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(1411764, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(1500000, 8, 1, UsbSerialPort.PARITY_NONE);
try {
usb.setParameters((int)(2000000/1.04), 8, 1, UsbSerialPort.PARITY_NONE);
fail("baud rate error expected");
} catch (UnsupportedOperationException ignored) {
}
usb.setParameters((int)(2000000/1.03), 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(2000000, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters((int)(2000000*1.03), 8, 1, UsbSerialPort.PARITY_NONE);
try {
usb.setParameters((int)(2000000*1.04), 8, 1, UsbSerialPort.PARITY_NONE);
fail("baud rate error expected");
} catch (UnsupportedOperationException ignored) {
}
usb.setParameters(2000000, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(3000000, 8, 1, UsbSerialPort.PARITY_NONE);
try {
usb.setParameters(4000000, 8, 1, UsbSerialPort.PARITY_NONE);
fail("baud rate to high expected");
} catch (UnsupportedOperationException ignored) {
}
}
@Test
public void baudRate() throws Exception {
usb.open();
if (false) { // default baud rate
// CP2102: only works if first connection after attaching device
// PL2303, FTDI: it's not 9600
telnet.setParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE);
doReadWrite("");
}
// invalid values
try {
usb.setParameters(-1, 8, 1, UsbSerialPort.PARITY_NONE);
fail("invalid baud rate");
} catch (IllegalArgumentException ignored) {
}
try {
usb.setParameters(0, 8, 1, UsbSerialPort.PARITY_NONE);
fail("invalid baud rate");
} catch (IllegalArgumentException ignored) {
}
try {
usb.setParameters(1, 8, 1, UsbSerialPort.PARITY_NONE);
if (usb.serialDriver instanceof FtdiSerialDriver)
;
else if (usb.serialDriver instanceof ProlificSerialDriver)
;
else if (usb.serialDriver instanceof Cp21xxSerialDriver)
;
else if (usb.serialDriver instanceof CdcAcmSerialDriver)
;
else
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);
if (usb.serialDriver instanceof ProlificSerialDriver)
;
else if (usb.serialDriver instanceof Cp21xxSerialDriver)
;
else if (usb.serialDriver instanceof CdcAcmSerialDriver)
;
else
fail("invalid baudrate 2^31");
} catch (ArithmeticException ignored) { // ch340
} catch (IOException ignored) { // cp2105 second port
} catch (IllegalArgumentException ignored) {
}
for(int baudRate : new int[] {300, 2400, 19200, 115200} ) {
if(baudRate == 300 && isCp21xxRestrictedPort) {
try {
usb.setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE);
fail("baudrate 300 on cp21xx restricted port");
} catch (IOException ignored) {
}
continue;
}
telnet.setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE);
doReadWrite(baudRate+"/8N1");
}
if(rfc2217_server_nonstandard_baudrates && !isCp21xxRestrictedPort) {
usb.setParameters(42000, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(42000, 8, 1, UsbSerialPort.PARITY_NONE);
byte[] buf1 = "abc".getBytes();
byte[] buf2 = "ABC".getBytes();
byte[] data1, data2;
usb.write(buf1);
data1 = telnet.read();
telnet.write(buf2);
data2 = usb.read();
if (usb.serialDriver instanceof Cp21xxSerialDriver) {
if (usb.serialDriver.getPorts().size() > 1) {
// supported on cp2105 first port
assertThat("42000/8N1", data1, equalTo(buf1));
assertThat("42000/8N1", data2, equalTo(buf2));
} else {
// not supported on cp2102
assertNotEquals(data1, buf1);
assertNotEquals(data2, buf2);
}
} else {
assertThat("42000/8N1", data1, equalTo(buf1));
assertThat("42000/8N1", data2, equalTo(buf2));
}
}
{ // non matching baud rate
telnet.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(2400, 8, 1, UsbSerialPort.PARITY_NONE);
byte[] data;
telnet.write("net2usb".getBytes());
data = usb.read();
assertNotEquals(7, data.length);
usb.write("usb2net".getBytes());
data = telnet.read();
assertNotEquals(7, data.length);
}
}
@Test
public void dataBits() throws Exception {
byte[] data;
usb.open();
for(int i: new int[] {0, 4, 9}) {
try {
usb.setParameters(19200, i, 1, UsbSerialPort.PARITY_NONE);
fail("invalid databits "+i);
} catch (IllegalArgumentException ignored) {
}
}
// telnet -> usb
usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(19200, 7, 1, UsbSerialPort.PARITY_NONE);
telnet.write(new byte[] {0x00});
Thread.sleep(10); // one bit is 0.05 milliseconds long, wait >> stop bit
telnet.write(new byte[] {(byte)0xff});
data = usb.read(2);
assertThat("19200/7N1", data, equalTo(new byte[] {(byte)0x80, (byte)0xff}));
telnet.setParameters(19200, 6, 1, UsbSerialPort.PARITY_NONE);
telnet.write(new byte[] {0x00});
Thread.sleep(10);
telnet.write(new byte[] {(byte)0xff});
data = usb.read(2);
assertThat("19000/6N1", data, equalTo(new byte[] {(byte)0xc0, (byte)0xff}));
telnet.setParameters(19200, 5, 1, UsbSerialPort.PARITY_NONE);
telnet.write(new byte[] {0x00});
Thread.sleep(10);
telnet.write(new byte[] {(byte)0xff});
data = usb.read(2);
assertThat("19000/5N1", data, equalTo(new byte[] {(byte)0xe0, (byte)0xff}));
// usb -> telnet
try {
telnet.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(19200, 7, 1, UsbSerialPort.PARITY_NONE);
usb.write(new byte[]{0x00});
Thread.sleep(10);
usb.write(new byte[]{(byte) 0xff});
data = telnet.read(2);
assertThat("19000/7N1", data, equalTo(new byte[]{(byte) 0x80, (byte) 0xff}));
} catch (UnsupportedOperationException e) {
if(!isCp21xxRestrictedPort)
throw e;
}
try {
usb.setParameters(19200, 6, 1, UsbSerialPort.PARITY_NONE);
usb.write(new byte[]{0x00});
Thread.sleep(10);
usb.write(new byte[]{(byte) 0xff});
data = telnet.read(2);
assertThat("19000/6N1", data, equalTo(new byte[]{(byte) 0xc0, (byte) 0xff}));
} catch (UnsupportedOperationException e) {
if (!(isCp21xxRestrictedPort || usb.serialDriver instanceof FtdiSerialDriver))
throw e;
}
try {
usb.setParameters(19200, 5, 1, UsbSerialPort.PARITY_NONE);
usb.write(new byte[] {0x00});
Thread.sleep(5);
usb.write(new byte[] {(byte)0xff});
data = telnet.read(2);
assertThat("19000/5N1", data, equalTo(new byte[] {(byte)0xe0, (byte)0xff}));
} catch (UnsupportedOperationException e) {
if (!(isCp21xxRestrictedPort || usb.serialDriver instanceof FtdiSerialDriver))
throw e;
}
}
@Test
public void parity() throws Exception {
byte[] _8n1 = {(byte)0x00, (byte)0x01, (byte)0xfe, (byte)0xff};
byte[] _7n1 = {(byte)0x00, (byte)0x01, (byte)0x7e, (byte)0x7f};
byte[] _7o1 = {(byte)0x80, (byte)0x01, (byte)0xfe, (byte)0x7f};
byte[] _7e1 = {(byte)0x00, (byte)0x81, (byte)0x7e, (byte)0xff};
byte[] _7m1 = {(byte)0x80, (byte)0x81, (byte)0xfe, (byte)0xff};
byte[] _7s1 = {(byte)0x00, (byte)0x01, (byte)0x7e, (byte)0x7f};
byte[] data;
usb.open();
for(int i: new int[] {-1, 5}) {
try {
usb.setParameters(19200, 8, 1, i);
fail("invalid parity "+i);
} catch (IllegalArgumentException ignored) {
}
}
if(isCp21xxRestrictedPort) {
usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_EVEN);
usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_ODD);
try {
usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_MARK);
fail("parity mark");
} catch (UnsupportedOperationException ignored) {}
try {
usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_SPACE);
fail("parity space");
} catch (UnsupportedOperationException ignored) {}
return;
// test below not possible as it requires unsupported 7 dataBits
}
// usb -> telnet
telnet.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usb.write(_8n1);
data = telnet.read(4);
assertThat("19200/8N1", data, equalTo(_8n1));
usb.setParameters(19200, 7, 1, UsbSerialPort.PARITY_ODD);
usb.write(_8n1);
data = telnet.read(4);
assertThat("19200/7O1", data, equalTo(_7o1));
usb.setParameters(19200, 7, 1, UsbSerialPort.PARITY_EVEN);
usb.write(_8n1);
data = telnet.read(4);
assertThat("19200/7E1", data, equalTo(_7e1));
if (usb.serialDriver instanceof CdcAcmSerialDriver) {
// not supported by arduino_leonardo_bridge.ino, other devices might support it
usb.setParameters(19200, 7, 1, UsbSerialPort.PARITY_MARK);
usb.setParameters(19200, 7, 1, UsbSerialPort.PARITY_SPACE);
} else {
usb.setParameters(19200, 7, 1, UsbSerialPort.PARITY_MARK);
usb.write(_8n1);
data = telnet.read(4);
assertThat("19200/7M1", data, equalTo(_7m1));
usb.setParameters(19200, 7, 1, UsbSerialPort.PARITY_SPACE);
usb.write(_8n1);
data = telnet.read(4);
assertThat("19200/7S1", data, equalTo(_7s1));
}
// telnet -> usb
usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.write(_8n1);
data = usb.read(4);
assertThat("19200/8N1", data, equalTo(_8n1));
telnet.setParameters(19200, 7, 1, UsbSerialPort.PARITY_ODD);
telnet.write(_8n1);
data = usb.read(4);
assertThat("19200/7O1", data, equalTo(_7o1));
telnet.setParameters(19200, 7, 1, UsbSerialPort.PARITY_EVEN);
telnet.write(_8n1);
data = usb.read(4);
assertThat("19200/7E1", data, equalTo(_7e1));
if (usb.serialDriver instanceof CdcAcmSerialDriver) {
// not supported by arduino_leonardo_bridge.ino, other devices might support it
} else {
telnet.setParameters(19200, 7, 1, UsbSerialPort.PARITY_MARK);
telnet.write(_8n1);
data = usb.read(4);
assertThat("19200/7M1", data, equalTo(_7m1));
telnet.setParameters(19200, 7, 1, UsbSerialPort.PARITY_SPACE);
telnet.write(_8n1);
data = usb.read(4);
assertThat("19200/7S1", data, equalTo(_7s1));
usb.setParameters(19200, 7, 1, UsbSerialPort.PARITY_ODD);
telnet.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.write(_8n1);
data = usb.read(4);
assertThat("19200/8N1", data, equalTo(_7n1)); // read is resilient against errors
}
}
@Test
public void stopBits() throws Exception {
byte[] data;
usb.open();
for (int i : new int[]{0, 4}) {
try {
usb.setParameters(19200, 8, i, UsbSerialPort.PARITY_NONE);
fail("invalid stopbits " + i);
} catch (IllegalArgumentException ignored) {
}
}
if (usb.serialDriver instanceof CdcAcmSerialDriver) {
usb.setParameters(19200, 8, UsbSerialPort.STOPBITS_1_5, UsbSerialPort.PARITY_NONE);
// software based bridge in arduino_leonardo_bridge.ino is to slow for real test, other devices might support it
} else {
// shift stopbits into next byte, by using different databits
// a - start bit (0)
// o - stop bit (1)
// d - data bit
// out 8N2: addddddd doaddddddddo
// 1000001 0 10001111
// in 6N1: addddddo addddddo
// 100000 101000
usb.setParameters(19200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(19200, 6, 1, UsbSerialPort.PARITY_NONE);
usb.write(new byte[]{(byte)0x41, (byte)0xf1});
data = telnet.read(2);
assertThat("19200/8N1", data, equalTo(new byte[]{1, 5}));
// out 8N2: addddddd dooaddddddddoo
// 1000001 0 10011111
// in 6N1: addddddo addddddo
// 100000 110100
try {
usb.setParameters(19200, 8, UsbSerialPort.STOPBITS_2, UsbSerialPort.PARITY_NONE);
telnet.setParameters(19200, 6, 1, UsbSerialPort.PARITY_NONE);
usb.write(new byte[]{(byte) 0x41, (byte) 0xf9});
data = telnet.read(2);
assertThat("19200/8N1", data, equalTo(new byte[]{1, 11}));
} catch(UnsupportedOperationException e) {
if(!isCp21xxRestrictedPort)
throw e;
}
try {
usb.setParameters(19200, 8, UsbSerialPort.STOPBITS_1_5, UsbSerialPort.PARITY_NONE);
// todo: could create similar test for 1.5 stopbits, by reading at double speed
// but only some devices support 1.5 stopbits and it is basically not used any more
} catch(UnsupportedOperationException ignored) {
}
}
}
@Test
public void probeTable() throws Exception {
class DummyDriver implements UsbSerialDriver {
@Override
public UsbDevice getDevice() { return null; }
@Override
public List<UsbSerialPort> getPorts() { return null; }
}
List<UsbSerialDriver> availableDrivers;
ProbeTable probeTable = new ProbeTable();
UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
availableDrivers = new UsbSerialProber(probeTable).findAllDrivers(usbManager);
assertEquals(0, availableDrivers.size());
probeTable.addProduct(0, 0, DummyDriver.class);
availableDrivers = new UsbSerialProber(probeTable).findAllDrivers(usbManager);
assertEquals(0, availableDrivers.size());
probeTable.addProduct(usb.serialDriver.getDevice().getVendorId(), usb.serialDriver.getDevice().getProductId(), usb.serialDriver.getClass());
availableDrivers = new UsbSerialProber(probeTable).findAllDrivers(usbManager);
assertEquals(1, availableDrivers.size());
assertEquals(availableDrivers.get(0).getClass(), usb.serialDriver.getClass());
}
// return [write packet size, write buffer size(s)]
private int[] getWriteSizes() {
if (usb.serialDriver instanceof Cp21xxSerialDriver) {
if (usb.serialDriver.getPorts().size() == 1) return new int[]{64, 576};
else if (usb.serialPort.getPortNumber() == 0) return new int[]{64, 320};
else return new int[]{32, 128, 160}; // write buffer size detection is unreliable
} else if (usb.serialDriver instanceof Ch34xSerialDriver) {
return new int[]{32, 64};
} else if (usb.serialDriver instanceof ProlificSerialDriver) {
return new int[]{64, 256};
} else if (usb.serialDriver instanceof FtdiSerialDriver) {
switch (usb.serialDriver.getPorts().size()) {
case 1: return new int[]{64, 128};
case 2: return new int[]{512, 4096};
case 4: return new int[]{512, 2048};
default: return null;
}
} else if (usb.serialDriver instanceof CdcAcmSerialDriver) {
return new int[]{64, 128};
} else {
return null;
}
}
@Test
public void writeTimeout() throws Exception {
usb.open();
int baudRate = 300;
if(usb.serialDriver instanceof Cp21xxSerialDriver && usb.serialDriver.getPorts().size() > 1)
baudRate = 2400;
usb.setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE);
int purgeTimeout = 250;
if(usb.serialDriver instanceof CdcAcmSerialDriver)
purgeTimeout = 500;
purgeWriteBuffer(purgeTimeout);
// determine write buffer size
int writePacketSize = ((CommonUsbSerialPort)usb.serialPort).getWriteEndpoint().getMaxPacketSize();
byte[] pbuf = new byte[writePacketSize];
int writePackets = 0;
try {
for (writePackets = 0; writePackets < 64; writePackets++)
usb.serialPort.write(pbuf, 1);
fail("write error expected");
} catch(IOException ignored) {}
purgeWriteBuffer(purgeTimeout);
int writeBufferSize = writePacketSize * writePackets;
Log.d(TAG, "write packet size = " + writePacketSize + ", write buffer size = " + writeBufferSize);
int[] writeSizes = getWriteSizes();
assertNotNull(writeSizes);
assertEquals("write packet size", writeSizes[0], writePacketSize);
assertTrue("write buffer size", Arrays.binarySearch(writeSizes, writeBufferSize) > 0);
purgeWriteBuffer(purgeTimeout);
if(usb.serialDriver instanceof CdcAcmSerialDriver)
return; // serial processing to slow for tests below, but they anyway only check shared code in CommonUsbSerialPort
if(usb.serialDriver instanceof Cp21xxSerialDriver && usb.serialDriver.getPorts().size() > 1)
return; // write buffer size detection unreliable as baud rate to high
usb.setParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE);
TestBuffer tbuf;
// total write timeout
tbuf = new TestBuffer(writeBufferSize + writePacketSize);
int timeout = writePacketSize / 32 * 50; // time for 1.5 packets. write 48 byte in 50 msec at 9600 baud
((CommonUsbSerialPort)usb.serialPort).setWriteBufferSize(writePacketSize);
usb.serialPort.write(tbuf.buf, timeout);
purgeWriteBuffer(purgeTimeout);
tbuf = new TestBuffer(writeBufferSize + 2*writePacketSize);
try {
usb.serialPort.write(tbuf.buf, timeout); // would not fail if each block has own timeout
fail("write error expected");
} catch(SerialTimeoutException ignored) {}
purgeWriteBuffer(purgeTimeout);
// infinite wait
((CommonUsbSerialPort)usb.serialPort).setWriteBufferSize(writePacketSize);
usb.serialPort.write(tbuf.buf, 0);
purgeWriteBuffer(purgeTimeout);
// timeout in bulkTransfer + SerialTimeoutException.bytesTransferred
int readWait = writePacketSize > 64 ? 250 : 50;
((CommonUsbSerialPort)usb.serialPort).setWriteBufferSize(tbuf.buf.length);
try {
usb.serialPort.write(tbuf.buf, timeout);
fail("write error expected");
} catch(SerialTimeoutException ex) {
assertTrue(ex.getMessage(), ex.getMessage().endsWith("rc=-1")); // timeout in bulkTransfer
for(byte[] data = telnet.read(-1, readWait); data.length != 0;
data = telnet.read(-1, readWait)) {
tbuf.testRead(data);
}
assertEquals(0, ex.bytesTransferred);
assertEquals(writeBufferSize + writePacketSize, tbuf.len);
}
purgeWriteBuffer(purgeTimeout);
((CommonUsbSerialPort)usb.serialPort).setWriteBufferSize(writePacketSize);
tbuf.len = 0;
try {
usb.serialPort.write(tbuf.buf, timeout);
fail("write error expected");
} catch(SerialTimeoutException ex) {
assertTrue(ex.getMessage(), ex.getMessage().endsWith("rc=-1")); // timeout in bulkTransfer
for(byte[] data = telnet.read(-1, readWait); data.length != 0;
data = telnet.read(-1, readWait)) {
tbuf.testRead(data);
}
assertEquals(writeBufferSize + writePacketSize, ex.bytesTransferred);
assertEquals(writeBufferSize + writePacketSize, tbuf.len);
}
purgeWriteBuffer(purgeTimeout);
// timeout in library
timeout = 1;
try {
usb.serialPort.write(tbuf.buf, timeout);
fail("write error expected");
} catch (SerialTimeoutException ex) {
assertTrue(ex.getMessage(), ex.getMessage().endsWith("rc=-2")); // timeout in library
}
purgeWriteBuffer(purgeTimeout);
}
@Test
// compare write duration.
//
// multiple packet sized writes typically take 2-3X time of single full buffer write.
// here some typical durations:
// full packet [msec]
// Prolific 4 8
// Cp2102 3 10
// CP2105 1.x 2-3
// FT232 1.5-2 2-3
// Ch34x 1.x 2-3
// CDC 1.x 2-3
public void writeDuration() throws Exception {
usb.open();
usb.setParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE);
boolean purge = true;
try {
usb.serialPort.purgeHwBuffers(true, false);
} catch(Exception ignored) {
purge = false;
}
if(usb.serialDriver instanceof Cp21xxSerialDriver && usb.serialDriver.getPorts().size() == 1)
purge = false; // purge is blocking
int[] writeSizes = getWriteSizes();
int writePacketSize = writeSizes[0];
int writeBufferSize = writeSizes[1];
int purgeTimeout = 250;
TestBuffer tbuf;
long begin;
int duration1, duration2, retries, i, timeout;
retries = purge ? 10 : 1;
tbuf = new TestBuffer(writeBufferSize);
((CommonUsbSerialPort) usb.serialPort).setWriteBufferSize(tbuf.buf.length);
Log.d(TAG, "writeDuration: full write begin");
begin = System.currentTimeMillis();
for(i=0; i<retries; i++) {
usb.serialPort.write(tbuf.buf, 0);
if(purge)
usb.serialPort.purgeHwBuffers(true, false);
}
duration1 = (int)(System.currentTimeMillis() - begin);
if(!purge)
purgeWriteBuffer(purgeTimeout);
Log.d(TAG, "writeDuration: full write end, duration " + duration1/(float)(retries) + " msec");
((CommonUsbSerialPort) usb.serialPort).setWriteBufferSize(writePacketSize);
Log.d(TAG, "writeDuration: packet write begin");
begin = System.currentTimeMillis();
for(i=0; i<retries; i++) {
usb.serialPort.write(tbuf.buf, 0);
if(purge)
usb.serialPort.purgeHwBuffers(true, false);
}
duration2 = (int)(System.currentTimeMillis() - begin);
purgeWriteBuffer(purgeTimeout);
Log.d(TAG, "writeDuration: packet write end, duration " + duration2/(float)(retries) + " msec");
assertTrue("full duration " + duration1 + ", packet duration " + duration2, duration1 < duration2);
assertTrue("full duration " + duration1 + ", packet duration " + duration2, duration2 < 5*duration1);
}
@Test
public void writeFragments() throws Exception {
usb.open();
usb.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
((CommonUsbSerialPort) usb.serialPort).setWriteBufferSize(12);
((CommonUsbSerialPort) usb.serialPort).setWriteBufferSize(12); // keeps last buffer
TestBuffer buf = new TestBuffer(256);
usb.serialPort.write(buf.buf, 5000);
while (!buf.testRead(telnet.read(-1)))
;
}
@Test
public void readBufferSize() throws Exception {
// looks like devices perform USB read with full mReadEndpoint.getMaxPacketSize() size (32, 64, 512)
// if the Java byte[] is shorter than the received result, it is silently lost with read timeout!
//
// for buffer > packet size, but not multiple of packet size, the same issue happens, but typically
// only the last (partly filled) packet is lost.
if(usb.serialDriver instanceof CdcAcmSerialDriver)
return; // arduino sends each byte individually, so not testable here
byte[] data;
boolean purge = true;
usb.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_START));
usb.ioManager.setReadBufferSize(8);
usb.startIoManager();
usb.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
try { usb.serialPort.purgeHwBuffers(true, true); } catch(Exception ignored) { purge = false; }
telnet.write("1aaa".getBytes());
data = usb.read(4);
Assert.assertThat(data, equalTo("1aaa".getBytes()));
telnet.write(new byte[16]);
try {
data = usb.read(16);
if (usb.serialDriver instanceof Cp21xxSerialDriver && usb.serialDriver.getPorts().size() == 1)
Assert.assertNotEquals(0, data.length); // can be shorter or full length
else if (usb.serialDriver instanceof ProlificSerialDriver)
Assert.assertTrue("expected > 0 and < 16 byte, got " + data.length, data.length > 0 && data.length < 16);
else // ftdi, ch340, cp2105
fail("buffer to small exception expected");
} catch (IOException ignored) {
}
if (purge) {
usb.serialPort.purgeHwBuffers(true, true);
} else {
usb.close();
usb.open();
Thread.sleep(100); // try to read remaining data by iomanager to avoid garbage in next test
}
usb.close();
usb.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_THREAD));
usb.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
try {
usb.serialPort.read(new byte[0], 0);
fail("IllegalArgumentException expected");
} catch (IllegalArgumentException ignored) {}
try {
usb.serialPort.read(new byte[0], 100);
fail("IllegalArgumentException expected");
} catch (IllegalArgumentException ignored) {}
if (usb.serialDriver instanceof FtdiSerialDriver) {
try {
usb.serialPort.read(new byte[2], 0);
fail("IllegalArgumentException expected");
} catch (IllegalArgumentException ignored) {}
try {
usb.serialPort.read(new byte[2], 100);
fail("IllegalArgumentException expected");
} catch (IllegalArgumentException ignored) {}
}
telnet.write("2aaa".getBytes());
data = usb.read(4, 8);
Assert.assertThat(data, equalTo("2aaa".getBytes()));
telnet.write(new byte[16]);
data = usb.read(16, 8);
if (usb.serialDriver instanceof Cp21xxSerialDriver && usb.serialDriver.getPorts().size() == 1)
Assert.assertNotEquals(0, data.length); // can be shorter or full length
else if (usb.serialDriver instanceof ProlificSerialDriver)
Assert.assertTrue("sporadic issue! expected > 0 and < 16 byte, got " + data.length, data.length > 0 && data.length < 16);
else // ftdi, ch340, cp2105
Assert.assertEquals(0, data.length);
telnet.write("2ccc".getBytes());
data = usb.read(4);
// Assert.assertThat(data, equalTo("1ccc".getBytes())); // unpredictable here. typically '2ccc' but sometimes '' or byte[16]
if(data.length != 4) {
if (purge) {
usb.serialPort.purgeHwBuffers(true, true);
} else {
usb.close();
usb.open();
Thread.sleep(100); // try to read remaining data by iomanager to avoid garbage in next test
}
}
}
@Test
// provoke data loss, when data is not read fast enough
public void readBufferOverflow() throws Exception {
if(usb.serialDriver instanceof CdcAcmSerialDriver)
telnet.writeDelay = 10; // arduino_leonardo_bridge.ino sends each byte in own USB packet, which is horribly slow
usb.open();
usb.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
StringBuilder expected = new StringBuilder();
StringBuilder data = new StringBuilder();
final int maxWait = 2000;
int bufferSize;
for(bufferSize = 8; bufferSize < (2<<15); bufferSize *= 2) {
int linenr;
String line="-";
expected.setLength(0);
data.setLength(0);
Log.i(TAG, "bufferSize " + bufferSize);
usb.readBlock = true;
for (linenr = 0; linenr < bufferSize/8; linenr++) {
line = String.format("%07d,", linenr);
telnet.write(line.getBytes());
expected.append(line);
}
usb.readBlock = false;
// slowly write new data, until old data is completely read from buffer and new data is received
boolean found = false;
for (; linenr < bufferSize/8 + maxWait/10 && !found; linenr++) {
line = String.format("%07d,", linenr);
telnet.write(line.getBytes());
Thread.sleep(10);
expected.append(line);
data.append(new String(usb.read(0)));
found = data.toString().endsWith(line);
}
while(!found) {
// use waiting read to clear input queue, else next test would see unexpected data
byte[] rest = usb.read(-1);
if(rest.length == 0)
fail("last line "+line+" not found");
data.append(new String(rest));
found = data.toString().endsWith(line);
}
if (data.length() != expected.length())
break;
}
findDifference(data, expected);
assertTrue(bufferSize > 16);
assertTrue(data.length() != expected.length());
}
@Test
public void readSpeed() throws Exception {
// see logcat for performance results
//
// CDC arduino_leonardo_bridge.ino has transfer speed ~ 100 byte/sec
// all other devices are near physical limit with ~ 10-12k/sec
//
// readBufferOverflow provokes read errors, but they can also happen here where the data is actually read fast enough.
// Android is not a real time OS, so there is no guarantee that the USB thread is scheduled, or it might be blocked by Java garbage collection.
// Using SERIAL_INPUT_OUTPUT_MANAGER_THREAD_PRIORITY=THREAD_PRIORITY_URGENT_AUDIO sometimes reduced errors by factor 10, sometimes not at all!
//
int diffLen = readSpeedInt(5, -1, 0);
if(usb.serialDriver instanceof Ch34xSerialDriver && diffLen == -1)
diffLen = 0; // todo: investigate last packet loss
assertEquals(0, diffLen);
}
private int readSpeedInt(int writeSeconds, int readBufferSize, int readTimeout) throws Exception {
int baudrate = 115200;
if(usb.serialDriver instanceof Ch34xSerialDriver)
baudrate = 38400;
int writeAhead = 5*baudrate/10; // write ahead for another 5 second read
if(usb.serialDriver instanceof CdcAcmSerialDriver)
writeAhead = 50;
usb.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_START));
usb.ioManager.setReadTimeout(readTimeout);
if(readBufferSize > 0)
usb.ioManager.setReadBufferSize(readBufferSize);
usb.startIoManager();
usb.setParameters(baudrate, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(baudrate, 8, 1, UsbSerialPort.PARITY_NONE);
int linenr = 0;
String line="";
StringBuilder data = new StringBuilder();
StringBuilder expected = new StringBuilder();
int dlen = 0, elen = 0;
Log.i(TAG, "readSpeed: 'read' should be near "+baudrate/10);
long begin = System.currentTimeMillis();
long next = System.currentTimeMillis();
for(int seconds=1; seconds <= writeSeconds; seconds++) {
next += 1000;
while (System.currentTimeMillis() < next) {
if((writeAhead < 0) || (expected.length() < data.length() + writeAhead)) {
line = String.format("%07d,", linenr++);
telnet.write(line.getBytes());
expected.append(line);
} else {
Thread.sleep(0, 100000);
}
data.append(new String(usb.read(0)));
}
Log.i(TAG, "readSpeed: t="+(next-begin)+", read="+(data.length()-dlen)+", write="+(expected.length()-elen));
dlen = data.length();
elen = expected.length();
}
boolean found = false;
while(!found) {
// use waiting read to clear input queue, else next test would see unexpected data
byte[] rest = usb.read(-1);
if(rest.length == 0)
break;
data.append(new String(rest));
found = data.toString().endsWith(line);
}
return findDifference(data, expected);
}
@Test
public void writeSpeed() throws Exception {
// see logcat for performance results
//
// CDC arduino_leonardo_bridge.ino has transfer speed ~ 100 byte/sec
// all other devices can get near physical limit:
// longlines=true:, speed is near physical limit at 11.5k
// longlines=false: speed is 3-4k for all devices, as more USB packets are required
usb.open();
usb.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
boolean longlines = !(usb.serialDriver instanceof CdcAcmSerialDriver);
int linenr = 0;
String line="";
StringBuilder data = new StringBuilder();
StringBuilder expected = new StringBuilder();
int dlen = 0, elen = 0;
Log.i(TAG, "writeSpeed: 'write' should be near "+115200/10);
long begin = System.currentTimeMillis();
long next = System.currentTimeMillis();
for(int seconds=1; seconds<=5; seconds++) {
next += 1000;
while (System.currentTimeMillis() < next) {
if(longlines)
line = String.format("%060d,", linenr++);
else
line = String.format("%07d,", linenr++);
usb.write(line.getBytes());
expected.append(line);
data.append(new String(telnet.read(0)));
}
Log.i(TAG, "writeSpeed: t="+(next-begin)+", write="+(expected.length()-elen)+", read="+(data.length()-dlen));
dlen = data.length();
elen = expected.length();
}
boolean found = false;
for (linenr=0; linenr < 2000 && !found; linenr++) {
data.append(new String(telnet.read(0)));
Thread.sleep(1);
found = data.toString().endsWith(line);
}
next = System.currentTimeMillis();
Log.i(TAG, "writeSpeed: t="+(next-begin)+", read="+(data.length()-dlen));
assertTrue(found);
int pos = indexOfDifference(data, expected);
if(pos!=-1) {
Log.i(TAG, "writeSpeed: first difference at " + pos);
String datasub = data.substring(Math.max(pos - 20, 0), Math.min(pos + 20, data.length()));
String expectedsub = expected.substring(Math.max(pos - 20, 0), Math.min(pos + 20, expected.length()));
assertThat(datasub, equalTo(expectedsub));
}
}
@Test
public void purgeHwBuffers() throws Exception {
// purge write buffer
// 2400 is slowest baud rate for isCp21xxRestrictedPort
usb.open();
usb.setParameters(2400, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(2400, 8, 1, UsbSerialPort.PARITY_NONE);
byte[] buf = new byte[64];
for(int i=0; i<buf.length; i++) buf[i]='a';
StringBuilder data = new StringBuilder();
usb.write(buf);
Thread.sleep(50); // ~ 12 bytes
boolean purged;
try {
usb.serialPort.purgeHwBuffers(true, false);
purged = true;
} catch (UnsupportedOperationException ex) {
purged = false;
}
usb.write("bcd".getBytes());
Thread.sleep(50);
while(data.length()==0 || data.charAt(data.length()-1)!='d')
data.append(new String(telnet.read()));
Log.i(TAG, "purgeHwBuffers " + purged + ": " + (buf.length+3) + " -> " + data.length());
assertTrue(data.length() > 5);
if(purged) {
if(usb.serialDriver instanceof Cp21xxSerialDriver && usb.serialDriver.getPorts().size() == 1) // only working on some devices/ports
assertTrue(data.length() < buf.length + 1 || data.length() == buf.length + 3);
else
assertTrue(data.length() < buf.length + 1);
} else {
assertEquals(data.length(), buf.length + 3);
}
// purge read buffer
usb.close();
usb.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_THREAD));
usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.write("x".getBytes());
Thread.sleep(10); // ~ 20 bytes
if(purged)
usb.serialPort.purgeHwBuffers(false, true);
Log.d(TAG, "purged = " + purged);
telnet.write("y".getBytes());
Thread.sleep(10); // ~ 20 bytes
if(purged) {
if(usb.serialDriver instanceof Cp21xxSerialDriver) { // only working on some devices/ports
if(isCp21xxRestrictedPort) {
assertThat(usb.read(2), equalTo("xy".getBytes())); // cp2105/1
} else if(usb.serialDriver.getPorts().size() > 1) {
assertThat(usb.read(1), equalTo("y".getBytes())); // cp2105/0
} else {
assertThat(usb.read(2), anyOf(equalTo("xy".getBytes()), // cp2102
equalTo("y".getBytes()))); // cp2102
}
} else {
assertThat(usb.read(1), equalTo("y".getBytes()));
}
} else {
assertThat(usb.read(2), equalTo("xy".getBytes()));
}
}
@Test
public void IoManager() throws Exception {
SerialInputOutputManager.DEBUG = true;
usb.ioManager = new SerialInputOutputManager(null);
assertNull(usb.ioManager.getListener());
usb.ioManager.setListener(usb);
assertEquals(usb, usb.ioManager.getListener());
usb.ioManager = new SerialInputOutputManager(usb.serialPort, usb);
assertEquals(usb, usb.ioManager.getListener());
assertEquals(0, usb.ioManager.getReadTimeout());
usb.ioManager.setReadTimeout(10);
assertEquals(10, usb.ioManager.getReadTimeout());
assertEquals(0, usb.ioManager.getWriteTimeout());
usb.ioManager.setWriteTimeout(11);
assertEquals(11, usb.ioManager.getWriteTimeout());
assertEquals(4096, usb.ioManager.getReadBufferSize());
usb.ioManager.setReadBufferSize(12);
assertEquals(12, usb.ioManager.getReadBufferSize());
assertEquals(4096, usb.ioManager.getWriteBufferSize());
usb.ioManager.setWriteBufferSize(13);
assertEquals(13, usb.ioManager.getWriteBufferSize());
usb.ioManager.setReadBufferSize(usb.ioManager.getReadBufferSize());
usb.ioManager.setWriteBufferSize(usb.ioManager.getWriteBufferSize());
usb.ioManager.setReadTimeout(usb.ioManager.getReadTimeout());
usb.ioManager.setWriteTimeout(usb.ioManager.getWriteTimeout());
usb.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_START)); // creates new IoManager
usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usb.ioManager.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
usb.startIoManager();
usb.waitForIoManagerStarted();
try {
usb.ioManager.run();
fail("already running error expected");
} catch (IllegalStateException ignored) {
}
try {
usb.ioManager.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
fail("setThreadPriority IllegalStateException expected");
} catch (IllegalStateException ignored) {}
try {
usb.ioManager.setReadTimeout(20);
fail("setReadTimeout IllegalStateException expected");
} catch (IllegalStateException ignored) {}
assertEquals(0, usb.ioManager.getReadTimeout());
usb.ioManager.setWriteTimeout(21);
assertEquals(21, usb.ioManager.getWriteTimeout());
usb.ioManager.setReadBufferSize(22);
assertEquals(22, usb.ioManager.getReadBufferSize());
usb.ioManager.setWriteBufferSize(23);
assertEquals(23, usb.ioManager.getWriteBufferSize());
// readbuffer resize
telnet.write(new byte[1]);
usb.ioManager.setReadBufferSize(64);
Log.d(TAG, "setReadBufferSize(64)");
telnet.write(new byte[1]); // still uses old buffer as infinite waiting step() holds reference to buffer
telnet.write(new byte[1]); // now uses 8 byte buffer
usb.read(3);
// writebuffer resize
try {
usb.ioManager.writeAsync(new byte[8192]);
fail("expected BufferOverflowException");
} catch (BufferOverflowException ignored) {}
usb.ioManager.setWriteBufferSize(16);
usb.ioManager.writeAsync("1234567890AB".getBytes());
try {
usb.ioManager.setWriteBufferSize(8);
fail("expected BufferOverflowException");
} catch (BufferOverflowException ignored) {}
usb.ioManager.setWriteBufferSize(24); // pending date copied to new buffer
telnet.write("a".getBytes());
assertThat(usb.read(1), equalTo("a".getBytes()));
assertThat(telnet.read(12), equalTo("1234567890AB".getBytes()));
// small readbuffer
usb.ioManager.setReadBufferSize(8);
Log.d(TAG, "setReadBufferSize(8)");
telnet.write("b".getBytes());
assertThat(usb.read(1), equalTo("b".getBytes()));
// now new buffer is used
telnet.write("c".getBytes());
assertThat(usb.read(1), equalTo("c".getBytes()));
telnet.write("d".getBytes());
assertThat(usb.read(1), equalTo("d".getBytes()));
SerialInputOutputManager.DEBUG = false;
}
@Test
public void writeAsync() throws Exception {
byte[] data, buf = new byte[]{1};
// w/o timeout: write delayed until something is read
usb.open();
usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usb.ioManager.writeAsync(buf);
usb.ioManager.writeAsync(buf);
data = telnet.read(1);
assertEquals(0, data.length);
telnet.write(buf);
data = usb.read(1);
assertEquals(1, data.length);
data = telnet.read(2);
assertEquals(2, data.length);
usb.close();
// with timeout: write after timeout
usb.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_START));
usb.ioManager.setReadTimeout(100);
usb.startIoManager();
usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usb.ioManager.writeAsync(buf);
usb.ioManager.writeAsync(buf);
data = telnet.read(2);
assertEquals(2, data.length);
usb.ioManager.setReadTimeout(200);
}
@Test
public void readTimeout() throws Exception {
final Boolean[] closed = {Boolean.FALSE};
Runnable closeThread = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
usb.close();
closed[0] = true;
}
};
usb.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_THREAD));
usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
byte[] writeBuf = new byte[]{1};
byte[] readBuf = new byte[1];
if (usb.serialDriver instanceof FtdiSerialDriver)
readBuf = new byte[3]; // include space for 2 header bytes
int len,i,j;
long time;
// w/o timeout
telnet.write(writeBuf);
len = usb.serialPort.read(readBuf, 0); // not blocking because data is available
assertEquals(1, len);
time = System.currentTimeMillis();
closed[0] = false;
Executors.newSingleThreadExecutor().submit(closeThread);
try {
usb.serialPort.read(readBuf, 0); // blocking until close()
fail("read error expected");
} catch (IOException ignored) {}
assertTrue(System.currentTimeMillis()-time >= 100);
// wait for usbClose
for(i=0; i<100; i++) {
if(closed[0]) break;
Thread.sleep(1);
}
assertTrue("not closed in time", closed[0]);
// with timeout
usb.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_THREAD));
usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
int longTimeout = 1000;
int shortTimeout = 10;
time = System.currentTimeMillis();
len = usb.serialPort.read(readBuf, shortTimeout);
assertEquals(0, len);
assertTrue(System.currentTimeMillis()-time < 100);
// no issue with slow transfer rate and short read timeout
time = System.currentTimeMillis();
for(i=0; i<50; i++) {
Thread.sleep(10);
telnet.write(writeBuf);
Log.d(TAG,"telnet write 1");
for(j=0; j<20; j++) {
len = usb.serialPort.read(readBuf, shortTimeout);
if (len > 0)
break;
}
assertEquals("failed after " + i, 1, len);
}
Log.i(TAG, "average time per read " + (System.currentTimeMillis()-time)/i + " msec");
if(!(usb.serialDriver instanceof CdcAcmSerialDriver)) {
int diffLen;
usb.close();
// no issue with high transfer rate and long read timeout
diffLen = readSpeedInt(5, -1, longTimeout);
if(usb.serialDriver instanceof Ch34xSerialDriver && diffLen == -1)
diffLen = 0; // todo: investigate last packet loss
assertEquals(0, diffLen);
usb.close();
// date loss with high transfer rate and short read timeout !!!
diffLen = readSpeedInt(5, -1, shortTimeout);
assertNotEquals("sporadic issue!", 0, diffLen);
// data loss observed with read timeout up to 200 msec, e.g.
// difference at 181 len 64
// got 000020,0000021,0000030,0000031,0000032,0
// expected 000020,0000021,0000022,0000023,0000024,0
// difference at 341 len 128
// got 000048,0000049,0000066,0000067,0000068,0
// expected 000048,0000049,0000050,0000051,0000052,0
// difference at 724 len 704
// got 0000112,0000113,0000202,0000203,0000204,
// expected 0000112,0000113,0000114,0000115,0000116,
// difference at 974 len 8
// got 00231,0000232,0000234,0000235,0000236,00
// expected 00231,0000232,0000233,0000234,0000235,00
}
}
@Test
public void wrongDriver() throws Exception {
UsbDeviceConnection wrongDeviceConnection;
UsbSerialDriver wrongSerialDriver;
UsbSerialPort wrongSerialPort;
if(!(usb.serialDriver instanceof CdcAcmSerialDriver)) {
wrongDeviceConnection = usbManager.openDevice(usb.serialDriver.getDevice());
wrongSerialDriver = new CdcAcmSerialDriver(usb.serialDriver.getDevice());
wrongSerialPort = wrongSerialDriver.getPorts().get(0);
try {
wrongSerialPort.open(wrongDeviceConnection);
wrongSerialPort.setParameters(115200, UsbSerialPort.DATABITS_8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE); // ch340 fails here
wrongSerialPort.write(new byte[]{1}, 1000); // pl2302 does not fail, but sends with wrong baud rate
if(!(usb.serialDriver instanceof ProlificSerialDriver))
fail("error expected");
} catch (IOException ignored) {
}
try {
if(usb.serialDriver instanceof ProlificSerialDriver) {
assertNotEquals(new byte[]{1}, telnet.read());
}
wrongSerialPort.close();
if(!(usb.serialDriver instanceof Ch34xSerialDriver |
usb.serialDriver instanceof ProlificSerialDriver))
fail("error expected");
} catch (IOException ignored) {
}
}
if(!(usb.serialDriver instanceof Ch34xSerialDriver)) {
wrongDeviceConnection = usbManager.openDevice(usb.serialDriver.getDevice());
wrongSerialDriver = new Ch34xSerialDriver(usb.serialDriver.getDevice());
wrongSerialPort = wrongSerialDriver.getPorts().get(0);
try {
wrongSerialPort.open(wrongDeviceConnection);
fail("error expected");
} catch (IOException ignored) {
}
try {
wrongSerialPort.close();
fail("error expected");
} catch (IOException ignored) {
}
}
// FTDI only recovers from Cp21xx control commands with power toggle, so skip this combination!
if(!(usb.serialDriver instanceof Cp21xxSerialDriver | usb.serialDriver instanceof FtdiSerialDriver)) {
wrongDeviceConnection = usbManager.openDevice(usb.serialDriver.getDevice());
wrongSerialDriver = new Cp21xxSerialDriver(usb.serialDriver.getDevice());
wrongSerialPort = wrongSerialDriver.getPorts().get(0);
try {
wrongSerialPort.open(wrongDeviceConnection);
//if(usb.usbSerialDriver instanceof FtdiSerialDriver)
// wrongSerialPort.setParameters(115200, UsbSerialPort.DATABITS_8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE); // ch340 fails here
fail("error expected");
} catch (IOException ignored) {
}
try {
wrongSerialPort.close();
//if(!(usb.usbSerialDriver instanceof FtdiSerialDriver))
// fail("error expected");
} catch (IOException ignored) {
}
}
if(!(usb.serialDriver instanceof FtdiSerialDriver)) {
wrongDeviceConnection = usbManager.openDevice(usb.serialDriver.getDevice());
wrongSerialDriver = new FtdiSerialDriver(usb.serialDriver.getDevice());
wrongSerialPort = wrongSerialDriver.getPorts().get(0);
try {
wrongSerialPort.open(wrongDeviceConnection);
if(usb.serialDriver instanceof Cp21xxSerialDriver)
wrongSerialPort.setParameters(115200, UsbSerialPort.DATABITS_8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE); // ch340 fails here
//fail("error expected"); // only fails on some devices
} catch (IOException ignored) {
}
try {
wrongSerialPort.close();
if(!(usb.serialDriver instanceof Cp21xxSerialDriver))
fail("error expected");
} catch (IOException ignored) {
}
}
if(!(usb.serialDriver instanceof ProlificSerialDriver)) {
wrongDeviceConnection = usbManager.openDevice(usb.serialDriver.getDevice());
wrongSerialDriver = new ProlificSerialDriver(usb.serialDriver.getDevice());
wrongSerialPort = wrongSerialDriver.getPorts().get(0);
try {
wrongSerialPort.open(wrongDeviceConnection);
fail("error expected");
} catch (IOException ignored) {
}
try {
wrongSerialPort.close();
fail("error expected");
} catch (IOException ignored) {
}
}
// test that device recovers from wrong commands
usb.open();
telnet.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
doReadWrite("");
}
@Test
/* test not done by RFC2217 server. Instead output control lines are connected to
input control lines with a binary decoder 74LS138, 74LS139, 74HC... or ...
in
A0 = RTS
A1 = DTR
out
Y0 = CD
Y1 = DTS/DSR
Y2 = CTS
Y3 = RI
expected result:
none -> RI
RTS -> CTS
DTR -> DTS/DSR
both -> CD
for onlyRtsCts devices these two lines are connected directly
*/
public void controlLines() throws Exception {
byte[] data;
int sleep = 10;
// output lines are supported by all drivers
// input lines are supported by all drivers except CDC
boolean inputLinesSupported = false;
boolean inputLinesConnected = false;
boolean onlyRtsCts = false;
if (usb.serialDriver instanceof FtdiSerialDriver) {
inputLinesSupported = true;
if(usb.serialDriver.getDevice().getProductId() == UsbId.FTDI_FT2232H)
inputLinesConnected = true; // I only have 74LS138 connected at FT2232, not at FT232
if(usb.serialDriver.getDevice().getProductId() == UsbId.FTDI_FT231X) {
inputLinesConnected = true;
onlyRtsCts = true; // I only test with FT230X that has only these 2 control lines. DTR is silently ignored
}
} else if (usb.serialDriver instanceof Cp21xxSerialDriver) {
inputLinesSupported = true;
if(usb.serialDriver.getPorts().size() == 1)
inputLinesConnected = true; // I only have 74LS138 connected at CP2102, not at CP2105
} else if (usb.serialDriver instanceof ProlificSerialDriver) {
inputLinesSupported = true;
inputLinesConnected = true;
} else if (usb.serialDriver instanceof Ch34xSerialDriver) {
inputLinesSupported = true;
if(usb.serialDriver.getDevice().getProductId() == UsbId.QINHENG_CH340)
inputLinesConnected = true; // I only have 74LS138 connected at CH230, not connected at CH341A
}
Boolean inputLineFalse = inputLinesSupported ? Boolean.FALSE : null;
Boolean inputLineTrue = inputLinesConnected ? Boolean.TRUE : inputLineFalse;
EnumSet<UsbSerialPort.ControlLine> supportedControlLines = EnumSet.of(UsbSerialPort.ControlLine.RTS, UsbSerialPort.ControlLine.DTR);
if(inputLinesSupported) {
supportedControlLines.add(UsbSerialPort.ControlLine.CTS);
supportedControlLines.add(UsbSerialPort.ControlLine.DSR);
supportedControlLines.add(UsbSerialPort.ControlLine.CD);
supportedControlLines.add(UsbSerialPort.ControlLine.RI);
}
// UsbSerialProber creates new UsbSerialPort objects which resets control lines,
// so the initial open has the output control lines unset.
// On additional close+open the output control lines can be retained.
usb.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_CONTROL_LINE_INIT));
usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
Thread.sleep(sleep);
assertEquals(supportedControlLines, usb.serialPort.getSupportedControlLines());
// control lines reset on initial open
data = "none".getBytes();
assertEquals(inputLinesConnected && !onlyRtsCts
? EnumSet.of(UsbSerialPort.ControlLine.RI)
: EnumSet.noneOf(UsbSerialPort.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::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(onlyRtsCts ? Boolean.FALSE : inputLineTrue));
telnet.write(data);
if(usb.serialDriver instanceof CdcAcmSerialDriver)
// arduino: control line feedback as serial_state notification is not implemented.
// It does not send w/o RTS or DTR, so these control lines can be partly checked here.
assertEquals(0, usb.read().length);
else
assertThat(Arrays.toString(data), usb.read(4), equalTo(data));
usb.write(data);
assertThat(Arrays.toString(data), telnet.read(4), equalTo(data));
data = "rts ".getBytes();
usb.serialPort.setRTS(true);
Thread.sleep(sleep);
assertEquals(inputLinesConnected
? EnumSet.of(UsbSerialPort.ControlLine.RTS, UsbSerialPort.ControlLine.CTS)
: EnumSet.of(UsbSerialPort.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::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(inputLineFalse));
telnet.write(data);
assertThat(Arrays.toString(data), usb.read(4), equalTo(data));
usb.write(data);
assertThat(Arrays.toString(data), telnet.read(4), equalTo(data));
data = "both".getBytes();
usb.serialPort.setDTR(true);
Thread.sleep(sleep);
assertEquals(onlyRtsCts
? EnumSet.of(UsbSerialPort.ControlLine.RTS, UsbSerialPort.ControlLine.DTR, UsbSerialPort.ControlLine.CTS)
: inputLinesConnected
? EnumSet.of(UsbSerialPort.ControlLine.RTS, UsbSerialPort.ControlLine.DTR, UsbSerialPort.ControlLine.CD)
: EnumSet.of(UsbSerialPort.ControlLine.RTS, UsbSerialPort.ControlLine.DTR),
usb.serialPort.getControlLines());
assertThat(usb.getControlLine(usb.serialPort::getRTS), equalTo(Boolean.TRUE));
assertThat(usb.getControlLine(usb.serialPort::getCTS), equalTo(onlyRtsCts ? Boolean.TRUE : inputLineFalse));
assertThat(usb.getControlLine(usb.serialPort::getDTR), equalTo(Boolean.TRUE));
assertThat(usb.getControlLine(usb.serialPort::getDSR), equalTo(inputLineFalse));
assertThat(usb.getControlLine(usb.serialPort::getCD), equalTo(onlyRtsCts ? Boolean.FALSE : inputLineTrue));
assertThat(usb.getControlLine(usb.serialPort::getRI), equalTo(inputLineFalse));
telnet.write(data);
assertThat(Arrays.toString(data), usb.read(4), equalTo(data));
usb.write(data);
assertThat(Arrays.toString(data), telnet.read(4), equalTo(data));
data = "dtr ".getBytes();
usb.serialPort.setRTS(false);
Thread.sleep(sleep);
assertEquals(inputLinesConnected && !onlyRtsCts
? EnumSet.of(UsbSerialPort.ControlLine.DTR, UsbSerialPort.ControlLine.DSR)
: EnumSet.of(UsbSerialPort.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::getDTR), equalTo(Boolean.TRUE));
assertThat(usb.getControlLine(usb.serialPort::getDSR), equalTo(onlyRtsCts ? Boolean.FALSE : inputLineTrue));
assertThat(usb.getControlLine(usb.serialPort::getCD), equalTo(inputLineFalse));
assertThat(usb.getControlLine(usb.serialPort::getRI), equalTo(inputLineFalse));
telnet.write(data);
assertThat(Arrays.toString(data), usb.read(4), equalTo(data));
usb.write(data);
assertThat(Arrays.toString(data), telnet.read(4), equalTo(data));
// control lines retained over close+open
boolean inputRetained = inputLinesConnected;
boolean outputRetained = true;
usb.serialPort.setRTS(true);
usb.serialPort.setDTR(false);
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));
usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
EnumSet<UsbSerialPort.ControlLine> retainedControlLines = EnumSet.noneOf(UsbSerialPort.ControlLine.class);
if(outputRetained) retainedControlLines.add(UsbSerialPort.ControlLine.RTS);
if(inputRetained) retainedControlLines.add(UsbSerialPort.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::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(inputLineFalse));
if (usb.serialDriver instanceof ProlificSerialDriver) { // check different control line mapping in GET_CONTROL_REQUEST
usb.serialPort.setRTS(false);
usb.serialPort.setDTR(false);
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(EnumSet.of(UsbSerialPort.ControlLine.RI), usb.serialPort.getControlLines());
usb.serialPort.setRTS(true);
usb.serialPort.setDTR(false);
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(EnumSet.of(UsbSerialPort.ControlLine.RTS, UsbSerialPort.ControlLine.CTS), usb.serialPort.getControlLines());
usb.serialPort.setRTS(false);
usb.serialPort.setDTR(true);
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(EnumSet.of(UsbSerialPort.ControlLine.DTR, UsbSerialPort.ControlLine.DSR), usb.serialPort.getControlLines());
usb.serialPort.setRTS(true);
usb.serialPort.setDTR(true);
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(EnumSet.of(UsbSerialPort.ControlLine.RTS, UsbSerialPort.ControlLine.DTR, UsbSerialPort.ControlLine.CD), usb.serialPort.getControlLines());
}
// force error
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));
if (usb.serialDriver instanceof ProlificSerialDriver) {
usb.serialPort.getRI(); // start background thread
}
usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
for (int i = 0; i < usb.serialDriver.getDevice().getInterfaceCount(); i++)
usb.deviceConnection.releaseInterface(usb.serialDriver.getDevice().getInterface(i));
usb.deviceConnection.close();
try {
usb.serialPort.setRTS(true);
fail("error expected");
} catch (IOException ignored) {
}
try {
if (usb.serialDriver instanceof ProlificSerialDriver) {
for(int i = 0; i < 10; i++) { // can take some time until background thread fails
usb.serialPort.getRI();
Thread.sleep(100);
}
} else {
usb.serialPort.getRI();
}
fail("error expected");
} catch (IOException ignored) {
} catch (UnsupportedOperationException ignored) {
}
}
@Test
public void setBreak() throws Exception {
usb.open();
telnet.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
doReadWrite("");
usb.serialPort.setBreak(true);
Thread.sleep(100);
usb.serialPort.setBreak(false);
// RFC2217 has SET_CONTROL + REQ_BREAK_STATE request, but this is not supported by pyserial
// as there is no easy notification on <break> condition. By default break is returned as
// 0 byte on Linux, see https://man7.org/linux/man-pages/man3/termios.3.html -> BRKINT
byte[] data = telnet.read(1);
if (usb.serialDriver instanceof CdcAcmSerialDriver) {
// BREAK forwarding not implemented by arduino_leonardo_bridge.ino
assertThat("<break>", data, equalTo(new byte[]{}));
} else if(isCp21xxRestrictedPort) {
assertThat("<break>", data, equalTo(new byte[]{0x26})); // send the last byte again?
} else {
assertThat("<break>", data, equalTo(new byte[]{0}));
}
doReadWrite("");
}
@Test
public void deviceConnection() throws Exception {
byte[] buf = new byte[256];
usb.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_THREAD));
usb.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
usb.write("x".getBytes());
usb.serialPort.read(buf, 1000);
usb.serialPort.setRTS(true);
try {
usb.serialPort.getRI();
} catch (UnsupportedOperationException ignored) {
}
boolean purged;
try {
usb.serialPort.purgeHwBuffers(true, true);
purged = true;
} catch (UnsupportedOperationException ex) {
purged = false;
}
usb.deviceConnection.close();
try { // only Prolific driver has early exit if nothing changed
usb.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
if(!(usb.serialDriver instanceof ProlificSerialDriver))
fail("setParameters error expected");
} catch (IOException ignored) {
}
try {
usb.setParameters(57600, 8, 1, UsbSerialPort.PARITY_NONE);
fail("setParameters error expected");
} catch (IOException ignored) {
}
try {
usb.write("x".getBytes());
fail("write error expected");
} catch (IOException ignored) {
}
try {
usb.serialPort.read(buf, 1000);
fail("read error expected");
} catch (IOException ignored) {
}
try {
usb.serialPort.read(buf, 0);
fail("read error expected");
} catch (IOException ignored) {
}
try {
usb.serialPort.setRTS(true);
fail("setRts error expected");
} catch (IOException ignored) {
}
try {
if(usb.serialDriver instanceof ProlificSerialDriver)
Thread.sleep(600); // wait for background thread
usb.serialPort.getRI();
fail("getRI error expected");
} catch (IOException ignored) {
} catch (UnsupportedOperationException ignored) {
}
if(purged) {
usb.serialPort.purgeHwBuffers(false, false);
try {
usb.serialPort.purgeHwBuffers(true, false);
fail("purgeHwBuffers(write) error expected");
} catch (IOException ignored) {
}
try {
usb.serialPort.purgeHwBuffers(false, true);
fail("purgeHwBuffers(read) error expected");
} catch (IOException ignored) {
}
}
try {
usb.serialPort.setBreak(true);
fail("setBreak error expected");
} catch (IOException ignored) {
}
usb.close();
try {
usb.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_THREAD, UsbWrapper.OpenCloseFlags.NO_DEVICE_CONNECTION));
fail("open error expected");
} catch (Exception ignored) {
}
usb.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_THREAD));
usb.write("x".getBytes());
UsbDeviceConnection otherDeviceConnection = usbManager.openDevice(usb.serialDriver.getDevice());
usb.write("x".getBytes());
otherDeviceConnection.close();
usb.write("x".getBytes());
// already queued read request is not interrupted by closing deviceConnection and test would hang
}
@Test
public void commonMethods() throws Exception {
String s;
assertNotNull(usb.serialPort.getDriver());
assertNotNull(usb.serialPort.getDevice());
assertEquals(test_device_port, usb.serialPort.getPortNumber());
s = usb.serialDriver.toString();
assertNotEquals(0, s.length());
assertFalse(usb.serialPort.isOpen());
usb.open();
assertTrue(usb.serialPort.isOpen());
assertEquals(((CommonUsbSerialPort)usb.serialPort).getWriteEndpoint().getMaxPacketSize(),
((CommonUsbSerialPort)usb.serialPort).getReadEndpoint().getMaxPacketSize());
s = usb.serialPort.getSerial();
// with target sdk 29 can throw SecurityException before USB permission dialog is confirmed
// not all devices implement serial numbers. some observed values are:
// FT232 00000000, FTGH4NTX, ...
// FT2232 <null>
// CP2102 0001
// CP2105 0035E46E
// CH340 <null>
// PL2303 <null>
// CDC:Microbit 9900000037024e450034200b0000004a0000000097969901
// CDC:Digispark <null>
try {
usb.open();
fail("already open error expected");
} catch (IOException ignored) {
}
try {
byte[] buffer = new byte[0];
usb.serialPort.read(buffer, UsbWrapper.USB_READ_WAIT);
fail("read buffer to small expected");
} catch(IllegalArgumentException ignored) {}
}
@Test
public void ftdiMethods() throws Exception {
Assume.assumeTrue("only for FTDI", usb.serialDriver instanceof FtdiSerialDriver);
byte[] b;
usb.open();
usb.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
FtdiSerialDriver.FtdiSerialPort ftdiSerialPort = (FtdiSerialDriver.FtdiSerialPort) usb.serialPort;
int lt = ftdiSerialPort.getLatencyTimer();
ftdiSerialPort.setLatencyTimer(1);
telnet.write("x".getBytes());
b = usb.read(1);
long t1 = System.currentTimeMillis();
telnet.write("x".getBytes());
b = usb.read(1);
ftdiSerialPort.setLatencyTimer(100);
long t2 = System.currentTimeMillis();
telnet.write("x".getBytes());
b = usb.read(1);
long t3 = System.currentTimeMillis();
ftdiSerialPort.setLatencyTimer(lt);
assertEquals("latency 1", 99, Math.max(t2-t1, 99)); // looks strange, but shows actual value
assertEquals("latency 100", 99, Math.min(t3-t2, 99));
usb.deviceConnection.close();
try {
ftdiSerialPort.getLatencyTimer();
fail("getLatencyTimer error expected");
} catch (IOException ignored) {}
usb.deviceConnection.close();
try {
ftdiSerialPort.setLatencyTimer(1);
fail("setLatencyTimer error expected");
} catch (IOException ignored) {}
}
}