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

1515 lines
63 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.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
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.UsbSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialPort;
import com.hoho.android.usbserial.driver.UsbSerialProber;
import com.hoho.android.usbserial.util.SerialInputOutputManager;
import org.apache.commons.net.telnet.InvalidTelnetOptionException;
import org.apache.commons.net.telnet.TelnetClient;
import org.apache.commons.net.telnet.TelnetCommand;
import org.apache.commons.net.telnet.TelnetOptionHandler;
import org.junit.After;
import org.junit.AfterClass;
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.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executors;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@RunWith(AndroidJUnit4.class)
public class DeviceTest implements SerialInputOutputManager.Listener {
// 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 final static int TELNET_READ_WAIT = 500;
private final static int TELNET_COMMAND_WAIT = 2000;
private final static int USB_READ_WAIT = 500;
private final static int USB_WRITE_WAIT = 500;
private final static Integer SERIAL_INPUT_OUTPUT_MANAGER_THREAD_PRIORITY = Process.THREAD_PRIORITY_URGENT_AUDIO;
private final static String TAG = "DeviceTest";
private final static byte RFC2217_COM_PORT_OPTION = 0x2c;
private final static byte RFC2217_SET_BAUDRATE = 1;
private final static byte RFC2217_SET_DATASIZE = 2;
private final static byte RFC2217_SET_PARITY = 3;
private final static byte RFC2217_SET_STOPSIZE = 4;
private final static byte RFC2217_PURGE_DATA = 12;
private Context context;
private UsbManager usbManager;
private UsbSerialDriver usbSerialDriver;
private UsbDeviceConnection usbDeviceConnection;
private UsbSerialPort usbSerialPort;
private SerialInputOutputManager usbIoManager;
private final Deque<byte[]> usbReadBuffer = new LinkedList<>();
private Exception usbReadError;
private boolean usbReadBlock = false;
private long usbReadTime = 0;
private static TelnetClient telnetClient;
private static InputStream telnetReadStream;
private static OutputStream telnetWriteStream;
private static Integer[] telnetComPortOptionCounter = {0};
private int telnetWriteDelay = 0;
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'
telnetClient = null;
}
public static void setUpFixtureInt() throws Exception {
if(telnetClient != null)
return;
telnetClient = new TelnetClient();
telnetClient.addOptionHandler(new TelnetOptionHandler(RFC2217_COM_PORT_OPTION, false, false, false, false) {
@Override
public int[] answerSubnegotiation(int[] suboptionData, int suboptionLength) {
telnetComPortOptionCounter[0] += 1;
return super.answerSubnegotiation(suboptionData, suboptionLength);
}
});
telnetClient.setConnectTimeout(2000);
telnetClient.connect(rfc2217_server_host, rfc2217_server_port);
telnetClient.setTcpNoDelay(true);
telnetWriteStream = telnetClient.getOutputStream();
telnetReadStream = telnetClient.getInputStream();
}
@Before
public void setUp() throws Exception {
setUpFixtureInt();
telnetClient.sendAYT(1000); // not correctly handled by rfc2217_server.py, but WARNING output "ignoring Telnet command: '\xf6'" is a nice separator between tests
telnetComPortOptionCounter[0] = 0;
telnetClient.sendCommand((byte)TelnetCommand.SB);
telnetWriteStream.write(new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_PURGE_DATA, 3});
telnetClient.sendCommand((byte)TelnetCommand.SE);
for(int i=0; i<TELNET_COMMAND_WAIT; i++) {
if(telnetComPortOptionCounter[0] == 1) break;
Thread.sleep(1);
}
assertEquals("telnet connection lost", 1, telnetComPortOptionCounter[0].intValue());
telnetWriteDelay = 0;
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 = 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);
usbSerialPort = usbSerialDriver.getPorts().get(test_device_port);
Log.i(TAG, "Using USB device "+ usbSerialPort.toString()+" driver="+usbSerialDriver.getClass().getSimpleName());
isCp21xxRestrictedPort = usbSerialDriver instanceof Cp21xxSerialDriver && usbSerialDriver.getPorts().size()==2 && test_device_port == 1;
if (!usbManager.hasPermission(usbSerialPort.getDriver().getDevice())) {
Log.d(TAG,"USB permission ...");
final Boolean[] granted = {null};
BroadcastReceiver usbReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
granted[0] = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
}
};
PendingIntent permissionIntent = PendingIntent.getBroadcast(context, 0, new Intent("com.android.example.USB_PERMISSION"), 0);
IntentFilter filter = new IntentFilter("com.android.example.USB_PERMISSION");
context.registerReceiver(usbReceiver, filter);
usbManager.requestPermission(usbSerialDriver.getDevice(), permissionIntent);
for(int i=0; i<5000; i++) {
if(granted[0] != null) break;
Thread.sleep(1);
}
Log.d(TAG,"USB permission "+granted[0]);
assertTrue("USB permission dialog not confirmed", granted[0]==null?false:granted[0]);
telnetRead(-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 {
try {
if(usbIoManager != null)
usbRead(0);
else
usbSerialPort.purgeHwBuffers(true, true);
} catch (Exception ignored) {}
try {
telnetRead(0);
} catch (Exception ignored) {}
usbClose();
usbSerialDriver = null;
}
@AfterClass
public static void tearDownFixture() throws Exception {
try {
telnetClient.disconnect();
} catch (Exception ignored) {}
telnetReadStream = null;
telnetWriteStream = null;
telnetClient = null;
}
private 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;
}
}
// wait full time
private byte[] telnetRead() throws Exception {
return telnetRead(-1);
}
private byte[] telnetRead(int expectedLength) throws Exception {
long end = System.currentTimeMillis() + TELNET_READ_WAIT;
ByteBuffer buf = ByteBuffer.allocate(65536);
while(System.currentTimeMillis() < end) {
if(telnetReadStream.available() > 0) {
buf.put((byte) telnetReadStream.read());
} else {
if (expectedLength >= 0 && buf.position() >= expectedLength)
break;
Thread.sleep(1);
}
}
byte[] data = new byte[buf.position()];
buf.flip();
buf.get(data);
return data;
}
private void telnetWrite(byte[] data) throws Exception{
if(telnetWriteDelay != 0) {
for(byte b : data) {
telnetWriteStream.write(b);
telnetWriteStream.flush();
Thread.sleep(telnetWriteDelay);
}
} else {
telnetWriteStream.write(data);
telnetWriteStream.flush();
}
}
private void usbClose() {
if (usbIoManager != null) {
usbIoManager.setListener(null);
usbIoManager.stop();
}
if (usbSerialPort != null) {
try {
usbSerialPort.setDTR(false);
usbSerialPort.setRTS(false);
} catch (Exception ignored) {
}
try {
usbSerialPort.close();
} catch (Exception ignored) {
}
usbSerialPort = null;
}
usbDeviceConnection = null; // closed in usbSerialPort.close()
if(usbIoManager != null) {
for (int i = 0; i < 2000; i++) {
if (SerialInputOutputManager.State.STOPPED == usbIoManager.getState()) break;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
assertEquals(SerialInputOutputManager.State.STOPPED, usbIoManager.getState());
usbIoManager = null;
}
}
private void usbOpen(boolean withIoManager) throws Exception {
usbOpen(withIoManager, 0);
}
private void usbOpen(boolean withIoManager, int ioManagerTimout) throws Exception {
usbDeviceConnection = usbManager.openDevice(usbSerialDriver.getDevice());
usbSerialPort = usbSerialDriver.getPorts().get(test_device_port);
usbSerialPort.open(usbDeviceConnection);
usbSerialPort.setDTR(true);
usbSerialPort.setRTS(true);
if(withIoManager) {
usbIoManager = new SerialInputOutputManager(usbSerialPort, this) {
@Override
public void run() {
if (SERIAL_INPUT_OUTPUT_MANAGER_THREAD_PRIORITY != null)
Process.setThreadPriority(SERIAL_INPUT_OUTPUT_MANAGER_THREAD_PRIORITY);
super.run();
}
};
usbIoManager.setReadTimeout(ioManagerTimout);
usbIoManager.setWriteTimeout(ioManagerTimout);
Executors.newSingleThreadExecutor().submit(usbIoManager);
}
synchronized (usbReadBuffer) {
usbReadBuffer.clear();
}
usbReadError = null;
}
// wait full time
private byte[] usbRead() throws Exception {
return usbRead(-1);
}
private byte[] usbRead(int expectedLength) throws Exception {
long end = System.currentTimeMillis() + USB_READ_WAIT;
ByteBuffer buf = ByteBuffer.allocate(16*1024);
if(usbIoManager != null) {
while (System.currentTimeMillis() < end) {
if(usbReadError != null)
throw usbReadError;
synchronized (usbReadBuffer) {
while(usbReadBuffer.peek() != null)
buf.put(usbReadBuffer.remove());
}
if (expectedLength >= 0 && buf.position() >= expectedLength)
break;
Thread.sleep(1);
}
} else {
byte[] b1 = new byte[256];
while (System.currentTimeMillis() < end) {
int len = usbSerialPort.read(b1, USB_READ_WAIT);
if (len > 0)
buf.put(b1, 0, len);
if (expectedLength >= 0 && buf.position() >= expectedLength)
break;
}
}
byte[] data = new byte[buf.position()];
buf.flip();
buf.get(data);
return data;
}
private void usbWrite(byte[] data) throws IOException {
usbSerialPort.write(data, USB_WRITE_WAIT);
}
private void usbParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException, InterruptedException {
usbSerialPort.setParameters(baudRate, dataBits, stopBits, parity);
if(usbSerialDriver instanceof CdcAcmSerialDriver)
Thread.sleep(10); // arduino_leonardeo_bridge.ini needs some time
else
Thread.sleep(1);
}
private void telnetParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException, InterruptedException, InvalidTelnetOptionException {
telnetComPortOptionCounter[0] = 0;
telnetClient.sendCommand((byte)TelnetCommand.SB);
telnetWriteStream.write(new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_BAUDRATE, (byte)(baudRate>>24), (byte)(baudRate>>16), (byte)(baudRate>>8), (byte)baudRate});
telnetClient.sendCommand((byte)TelnetCommand.SE);
telnetClient.sendCommand((byte)TelnetCommand.SB);
telnetWriteStream.write(new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_DATASIZE, (byte)dataBits});
telnetClient.sendCommand((byte)TelnetCommand.SE);
telnetClient.sendCommand((byte)TelnetCommand.SB);
telnetWriteStream.write(new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_STOPSIZE, (byte)stopBits});
telnetClient.sendCommand((byte)TelnetCommand.SE);
telnetClient.sendCommand((byte)TelnetCommand.SB);
telnetWriteStream.write(new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_PARITY, (byte)(parity+1)});
telnetClient.sendCommand((byte)TelnetCommand.SE);
// windows does not like nonstandard baudrates. rfc2217_server.py terminates w/o response
for(int i=0; i<TELNET_COMMAND_WAIT; i++) {
if(telnetComPortOptionCounter[0] == 4) break;
Thread.sleep(1);
}
assertEquals("telnet connection lost", 4, telnetComPortOptionCounter[0].intValue());
}
@Override
public void onNewData(byte[] data) {
long now = System.currentTimeMillis();
if(usbReadTime == 0)
usbReadTime = now;
if(data.length > 64) {
Log.d(TAG, "usb read: time+=" + String.format("%-3d",now-usbReadTime) + " len=" + String.format("%-4d",data.length) + " data=" + new String(data, 0, 32) + "..." + new String(data, data.length-32, 32));
} else {
Log.d(TAG, "usb read: time+=" + String.format("%-3d",now-usbReadTime) + " len=" + String.format("%-4d",data.length) + " data=" + new String(data));
}
usbReadTime = now;
while(usbReadBlock)
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (usbReadBuffer) {
usbReadBuffer.add(data);
}
}
@Override
public void onRunError(Exception e) {
usbReadError = e;
//fail("usb connection lost");
}
// 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 {
byte[] buf1 = new byte[]{ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16};
byte[] buf2 = new byte[]{ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26};
byte[] data;
telnetWrite(buf1);
data = usbRead(buf1.length);
assertThat(reason, data, equalTo(buf1)); // includes array content in output
//assertArrayEquals("net2usb".getBytes(), data); // only includes array length in output
usbWrite(buf2);
data = telnetRead(buf2.length);
assertThat(reason, data, equalTo(buf2));
}
@Test
public void openClose() throws Exception {
usbOpen(true);
telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
doReadWrite("");
try {
usbSerialPort.open(usbDeviceConnection);
fail("already open expected");
} catch (IOException ignored) {
}
doReadWrite("");
usbClose();
usbSerialPort = usbSerialDriver.getPorts().get(test_device_port);
try {
usbSerialPort.close();
fail("already closed expected");
} catch (IOException ignored) {
}
try {
usbWrite(new byte[]{0x00});
fail("write error expected");
} catch (IOException ignored) {
}
try {
usbRead(1);
fail("read error expected");
} catch (IOException ignored) {
}
try {
usbParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE);
fail("error expected");
} catch (IOException ignored) {
} catch (NullPointerException ignored) {
}
usbSerialPort = null;
usbOpen(true);
telnetParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE);
usbParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE);
doReadWrite("");
// close port before iomanager
assertEquals(SerialInputOutputManager.State.RUNNING, usbIoManager.getState());
usbSerialPort.close();
for (int i = 0; i < 1000; i++) {
if (usbIoManager.getState() == SerialInputOutputManager.State.STOPPED)
break;
Thread.sleep(1);
}
// assertEquals(SerialInputOutputManager.State.STOPPED, usbIoManager.getState());
// unstable. null'ify not-stopped ioManager, else usbClose would try again
if(SerialInputOutputManager.State.STOPPED != usbIoManager.getState())
usbIoManager = null;
}
@Test
public void baudRate() throws Exception {
usbOpen(true);
if (false) { // default baud rate
// CP2102: only works if first connection after attaching device
// PL2303, FTDI: it's not 9600
telnetParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE);
doReadWrite("");
}
// invalid values
try {
usbParameters(-1, 8, 1, UsbSerialPort.PARITY_NONE);
fail("invalid baud rate");
} catch (IllegalArgumentException ignored) {
}
try {
usbParameters(0, 8, 1, UsbSerialPort.PARITY_NONE);
fail("invalid baud rate");
} catch (IllegalArgumentException ignored) {
}
try {
usbParameters(1, 8, 1, UsbSerialPort.PARITY_NONE);
if (usbSerialDriver instanceof FtdiSerialDriver)
;
else if (usbSerialDriver instanceof ProlificSerialDriver)
;
else if (usbSerialDriver instanceof Cp21xxSerialDriver)
;
else if (usbSerialDriver instanceof CdcAcmSerialDriver)
;
else
fail("invalid baudrate 1");
} catch (UnsupportedOperationException ignored) { // ch340
} catch (IOException ignored) { // cp2105 second port
} catch (IllegalArgumentException ignored) {
}
try {
usbParameters(2<<31, 8, 1, UsbSerialPort.PARITY_NONE);
if (usbSerialDriver instanceof ProlificSerialDriver)
;
else if (usbSerialDriver instanceof Cp21xxSerialDriver)
;
else if (usbSerialDriver 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 {
usbParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE);
fail("baudrate 300 on cp21xx restricted port");
} catch (IOException ignored) {
}
continue;
}
telnetParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE);
usbParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE);
doReadWrite(baudRate+"/8N1");
}
if(rfc2217_server_nonstandard_baudrates && !isCp21xxRestrictedPort) {
// usbParameters does not fail on devices that do not support nonstandard baud rates
usbParameters(42000, 8, 1, UsbSerialPort.PARITY_NONE);
telnetParameters(42000, 8, 1, UsbSerialPort.PARITY_NONE);
byte[] buf1 = "abc".getBytes();
byte[] buf2 = "ABC".getBytes();
byte[] data1, data2;
usbWrite(buf1);
data1 = telnetRead();
telnetWrite(buf2);
data2 = usbRead();
if (usbSerialDriver instanceof ProlificSerialDriver) {
// not supported
assertNotEquals(data1, buf2);
assertNotEquals(data2, buf2);
} else if (usbSerialDriver instanceof Cp21xxSerialDriver) {
if (usbSerialDriver.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
telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usbParameters(2400, 8, 1, UsbSerialPort.PARITY_NONE);
byte[] data;
telnetWrite("net2usb".getBytes());
data = usbRead();
assertNotEquals(7, data.length);
usbWrite("usb2net".getBytes());
data = telnetRead();
assertNotEquals(7, data.length);
}
}
@Test
public void dataBits() throws Exception {
byte[] data;
usbOpen(true);
for(int i: new int[] {0, 4, 9}) {
try {
usbParameters(19200, i, 1, UsbSerialPort.PARITY_NONE);
fail("invalid databits "+i);
} catch (IllegalArgumentException ignored) {
}
}
// telnet -> usb
usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
telnetParameters(19200, 7, 1, UsbSerialPort.PARITY_NONE);
telnetWrite(new byte[] {0x00});
Thread.sleep(10); // one bit is 0.05 milliseconds long, wait >> stop bit
telnetWrite(new byte[] {(byte)0xff});
data = usbRead(2);
assertThat("19200/7N1", data, equalTo(new byte[] {(byte)0x80, (byte)0xff}));
telnetParameters(19200, 6, 1, UsbSerialPort.PARITY_NONE);
telnetWrite(new byte[] {0x00});
Thread.sleep(10);
telnetWrite(new byte[] {(byte)0xff});
data = usbRead(2);
assertThat("19000/6N1", data, equalTo(new byte[] {(byte)0xc0, (byte)0xff}));
telnetParameters(19200, 5, 1, UsbSerialPort.PARITY_NONE);
telnetWrite(new byte[] {0x00});
Thread.sleep(10);
telnetWrite(new byte[] {(byte)0xff});
data = usbRead(2);
assertThat("19000/5N1", data, equalTo(new byte[] {(byte)0xe0, (byte)0xff}));
// usb -> telnet
try {
telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usbParameters(19200, 7, 1, UsbSerialPort.PARITY_NONE);
usbWrite(new byte[]{0x00});
Thread.sleep(10);
usbWrite(new byte[]{(byte) 0xff});
data = telnetRead(2);
assertThat("19000/7N1", data, equalTo(new byte[]{(byte) 0x80, (byte) 0xff}));
} catch (UnsupportedOperationException e) {
if(!isCp21xxRestrictedPort)
throw e;
}
try {
usbParameters(19200, 6, 1, UsbSerialPort.PARITY_NONE);
usbWrite(new byte[]{0x00});
Thread.sleep(10);
usbWrite(new byte[]{(byte) 0xff});
data = telnetRead(2);
assertThat("19000/6N1", data, equalTo(new byte[]{(byte) 0xc0, (byte) 0xff}));
} catch (UnsupportedOperationException e) {
if (!(isCp21xxRestrictedPort || usbSerialDriver instanceof FtdiSerialDriver))
throw e;
}
try {
usbParameters(19200, 5, 1, UsbSerialPort.PARITY_NONE);
usbWrite(new byte[] {0x00});
Thread.sleep(5);
usbWrite(new byte[] {(byte)0xff});
data = telnetRead(2);
assertThat("19000/5N1", data, equalTo(new byte[] {(byte)0xe0, (byte)0xff}));
} catch (UnsupportedOperationException e) {
if (!(isCp21xxRestrictedPort || usbSerialDriver 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;
usbOpen(true);
for(int i: new int[] {-1, 5}) {
try {
usbParameters(19200, 8, 1, i);
fail("invalid parity "+i);
} catch (IllegalArgumentException ignored) {
}
}
if(isCp21xxRestrictedPort) {
usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usbParameters(19200, 8, 1, UsbSerialPort.PARITY_EVEN);
usbParameters(19200, 8, 1, UsbSerialPort.PARITY_ODD);
try {
usbParameters(19200, 8, 1, UsbSerialPort.PARITY_MARK);
fail("parity mark");
} catch (UnsupportedOperationException ignored) {}
try {
usbParameters(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
telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usbWrite(_8n1);
data = telnetRead(4);
assertThat("19200/8N1", data, equalTo(_8n1));
usbParameters(19200, 7, 1, UsbSerialPort.PARITY_ODD);
usbWrite(_8n1);
data = telnetRead(4);
assertThat("19200/7O1", data, equalTo(_7o1));
usbParameters(19200, 7, 1, UsbSerialPort.PARITY_EVEN);
usbWrite(_8n1);
data = telnetRead(4);
assertThat("19200/7E1", data, equalTo(_7e1));
if (usbSerialDriver instanceof CdcAcmSerialDriver) {
// not supported by arduino_leonardo_bridge.ino, other devices might support it
} else {
usbParameters(19200, 7, 1, UsbSerialPort.PARITY_MARK);
usbWrite(_8n1);
data = telnetRead(4);
assertThat("19200/7M1", data, equalTo(_7m1));
usbParameters(19200, 7, 1, UsbSerialPort.PARITY_SPACE);
usbWrite(_8n1);
data = telnetRead(4);
assertThat("19200/7S1", data, equalTo(_7s1));
}
// telnet -> usb
usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
telnetWrite(_8n1);
data = usbRead(4);
assertThat("19200/8N1", data, equalTo(_8n1));
telnetParameters(19200, 7, 1, UsbSerialPort.PARITY_ODD);
telnetWrite(_8n1);
data = usbRead(4);
assertThat("19200/7O1", data, equalTo(_7o1));
telnetParameters(19200, 7, 1, UsbSerialPort.PARITY_EVEN);
telnetWrite(_8n1);
data = usbRead(4);
assertThat("19200/7E1", data, equalTo(_7e1));
if (usbSerialDriver instanceof CdcAcmSerialDriver) {
// not supported by arduino_leonardo_bridge.ino, other devices might support it
} else {
telnetParameters(19200, 7, 1, UsbSerialPort.PARITY_MARK);
telnetWrite(_8n1);
data = usbRead(4);
assertThat("19200/7M1", data, equalTo(_7m1));
telnetParameters(19200, 7, 1, UsbSerialPort.PARITY_SPACE);
telnetWrite(_8n1);
data = usbRead(4);
assertThat("19200/7S1", data, equalTo(_7s1));
usbParameters(19200, 7, 1, UsbSerialPort.PARITY_ODD);
telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
telnetWrite(_8n1);
data = usbRead(4);
assertThat("19200/8N1", data, equalTo(_7n1)); // read is resilient against errors
}
}
@Test
public void stopBits() throws Exception {
byte[] data;
usbOpen(true);
for (int i : new int[]{0, 4}) {
try {
usbParameters(19200, 8, i, UsbSerialPort.PARITY_NONE);
fail("invalid stopbits " + i);
} catch (IllegalArgumentException ignored) {
}
}
if (usbSerialDriver instanceof CdcAcmSerialDriver) {
usbParameters(19200, 8, UsbSerialPort.STOPBITS_2, 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
usbParameters(19200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);
telnetParameters(19200, 6, 1, UsbSerialPort.PARITY_NONE);
usbWrite(new byte[]{(byte)0x41, (byte)0xf1});
data = telnetRead(2);
assertThat("19200/8N1", data, equalTo(new byte[]{1, 5}));
// out 8N2: addddddd dooaddddddddoo
// 1000001 0 10011111
// in 6N1: addddddo addddddo
// 100000 110100
try {
usbParameters(19200, 8, UsbSerialPort.STOPBITS_2, UsbSerialPort.PARITY_NONE);
telnetParameters(19200, 6, 1, UsbSerialPort.PARITY_NONE);
usbWrite(new byte[]{(byte) 0x41, (byte) 0xf9});
data = telnetRead(2);
assertThat("19200/8N1", data, equalTo(new byte[]{1, 11}));
} catch(UnsupportedOperationException e) {
if(!isCp21xxRestrictedPort)
throw e;
}
try {
usbParameters(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(usbSerialDriver.getDevice().getVendorId(), usbSerialDriver.getDevice().getProductId(), usbSerialDriver.getClass());
availableDrivers = new UsbSerialProber(probeTable).findAllDrivers(usbManager);
assertEquals(1, availableDrivers.size());
assertEquals(availableDrivers.get(0).getClass(), usbSerialDriver.getClass());
}
@Test
public void writeTimeout() throws Exception {
usbOpen(true);
usbParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
telnetParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
// Basically all devices have a UsbEndpoint.getMaxPacketSize() 64. When the timeout
// in usbSerialPort.write() is reached, some packets have been written and the rest
// is discarded. bulkTransfer() does not return the number written so far, but -1.
// With 115200 baud and 1/2 second timeout, typical values are:
// ch340 6080 of 6144
// pl2302 5952 of 6144
// cp2102 6400 of 7168
// cp2105 6272 of 7168
// ft232 5952 of 6144
// ft2232 9728 of 10240
// arduino 128 of 144
int timeout = 500;
int len = 0;
int startLen = 1024;
int step = 1024;
int minLen = 4069;
int maxLen = 12288;
int bufferSize = 511;
TestBuffer buf = new TestBuffer(len);
if(usbSerialDriver instanceof CdcAcmSerialDriver) {
startLen = 16;
step = 16;
minLen = 128;
maxLen = 256;
bufferSize = 31;
}
try {
for (len = startLen; len < maxLen; len += step) {
buf = new TestBuffer(len);
Log.d(TAG, "write buffer size " + len);
usbSerialPort.write(buf.buf, timeout);
while (!buf.testRead(telnetRead(-1)))
;
}
fail("write timeout expected between " + minLen + " and " + maxLen + ", is " + len);
} catch (IOException e) {
Log.d(TAG, "usbWrite failed", e);
while (true) {
byte[] data = telnetRead(-1);
if (data.length == 0) break;
if (buf.testRead(data)) break;
}
Log.d(TAG, "received " + buf.len + " of " + len + " bytes of failing usbWrite");
assertTrue("write timeout expected between " + minLen + " and " + maxLen + ", is " + len, len > minLen);
}
// With smaller writebuffer, the timeout is used per bulkTransfer and each call 'fits'
// into this timout, but shouldn't further calls only use the remaining timeout?
((CommonUsbSerialPort) usbSerialPort).setWriteBufferSize(bufferSize);
len = maxLen;
buf = new TestBuffer(len);
Log.d(TAG, "write buffer size " + len);
usbSerialPort.write(buf.buf, timeout);
while (!buf.testRead(telnetRead(-1)))
;
}
@Test
public void writeFragments() throws Exception {
usbOpen(true);
usbParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
telnetParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
((CommonUsbSerialPort) usbSerialPort).setWriteBufferSize(12);
((CommonUsbSerialPort) usbSerialPort).setWriteBufferSize(12); // keeps last buffer
TestBuffer buf = new TestBuffer(256);
usbSerialPort.write(buf.buf, 5000);
while (!buf.testRead(telnetRead(-1)))
;
}
@Test
// provoke data loss, when data is not read fast enough
public void readBufferOverflow() throws Exception {
if(usbSerialDriver instanceof CdcAcmSerialDriver)
telnetWriteDelay = 10; // arduino_leonardo_bridge.ino sends each byte in own USB packet, which is horribly slow
usbOpen(true);
usbParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
telnetParameters(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);
usbReadBlock = true;
for (linenr = 0; linenr < bufferSize/8; linenr++) {
line = String.format("%07d,", linenr);
telnetWrite(line.getBytes());
expected.append(line);
}
usbReadBlock = 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);
telnetWrite(line.getBytes());
Thread.sleep(10);
expected.append(line);
data.append(new String(usbRead(0)));
found = data.toString().endsWith(line);
}
while(!found) {
// use waiting read to clear input queue, else next test would see unexpected data
byte[] rest = usbRead(-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.ini 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, 0);
if(usbSerialDriver instanceof Ch34xSerialDriver && diffLen == -1)
diffLen = 0; // todo: investigate last packet loss
assertEquals(0, diffLen);
}
private int readSpeedInt(int writeSeconds, int readTimeout) throws Exception {
int baudrate = 115200;
if(usbSerialDriver instanceof Ch34xSerialDriver)
baudrate = 38400;
int writeAhead = 5*baudrate/10; // write ahead for another 5 second read
if(usbSerialDriver instanceof CdcAcmSerialDriver)
writeAhead = 50;
usbOpen(true, readTimeout);
usbParameters(baudrate, 8, 1, UsbSerialPort.PARITY_NONE);
telnetParameters(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++);
telnetWrite(line.getBytes());
expected.append(line);
} else {
Thread.sleep(0, 100000);
}
data.append(new String(usbRead(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 = usbRead(-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.ini 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
usbOpen(true);
usbParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
telnetParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE);
boolean longlines = !(usbSerialDriver 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++);
usbWrite(line.getBytes());
expected.append(line);
data.append(new String(telnetRead(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(telnetRead(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
usbOpen(true);
usbParameters(2400, 8, 1, UsbSerialPort.PARITY_NONE);
telnetParameters(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();
usbWrite(buf);
Thread.sleep(50); // ~ 12 bytes
boolean purged = usbSerialPort.purgeHwBuffers(true, false);
usbWrite("bcd".getBytes());
Thread.sleep(50);
while(data.length()==0 || data.charAt(data.length()-1)!='d')
data.append(new String(telnetRead()));
Log.i(TAG, "purgeHwBuffers " + purged + ": " + buf.length+1 + " -> " + data.length());
assertTrue(data.length() > 5);
if(purged)
assertTrue(data.length() < buf.length+1);
else
assertEquals(data.length(), buf.length + 3);
// purge read buffer
usbClose();
usbOpen(false);
usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
telnetWrite("x".getBytes());
Thread.sleep(10); // ~ 20 bytes
purged = usbSerialPort.purgeHwBuffers(false, true);
Log.d(TAG, "purged = " + purged);
telnetWrite("y".getBytes());
Thread.sleep(10); // ~ 20 bytes
if(purged) {
if(usbSerialDriver instanceof Cp21xxSerialDriver) { // only working on some devices/ports
if(isCp21xxRestrictedPort) {
assertThat(usbRead(2), equalTo("xy".getBytes())); // cp2105/1
} else if(usbSerialDriver.getPorts().size() > 1) {
assertThat(usbRead(1), equalTo("y".getBytes())); // cp2105/0
} else {
assertThat(usbRead(2), equalTo("xy".getBytes())); // cp2102
}
} else {
assertThat(usbRead(1), equalTo("y".getBytes()));
}
} else {
assertThat(usbRead(2), equalTo("xy".getBytes()));
}
}
@Test
public void writeAsync() throws Exception {
if (usbSerialDriver instanceof FtdiSerialDriver)
return; // periodically sends status messages, so does not block here
byte[] data, buf = new byte[]{1};
usbIoManager = new SerialInputOutputManager(null);
assertEquals(null, usbIoManager.getListener());
usbIoManager.setListener(this);
assertEquals(this, usbIoManager.getListener());
usbIoManager = new SerialInputOutputManager(usbSerialPort, this);
assertEquals(this, usbIoManager.getListener());
assertEquals(0, usbIoManager.getReadTimeout());
usbIoManager.setReadTimeout(100);
assertEquals(100, usbIoManager.getReadTimeout());
assertEquals(0, usbIoManager.getWriteTimeout());
usbIoManager.setWriteTimeout(200);
assertEquals(200, usbIoManager.getWriteTimeout());
// w/o timeout: write delayed until something is read
usbOpen(true);
usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usbIoManager.writeAsync(buf);
usbIoManager.writeAsync(buf);
data = telnetRead(1);
assertEquals(0, data.length);
telnetWrite(buf);
data = usbRead(1);
assertEquals(1, data.length);
data = telnetRead(2);
assertEquals(2, data.length);
try {
usbIoManager.setReadTimeout(100);
fail("IllegalStateException expected");
} catch (IllegalStateException ignored) {}
usbClose();
// with timeout: write after timeout
usbOpen(true, 100);
usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usbIoManager.writeAsync(buf);
usbIoManager.writeAsync(buf);
data = telnetRead(2);
assertEquals(2, data.length);
usbIoManager.setReadTimeout(200);
}
@Test
public void readTimeout() throws Exception {
if (usbSerialDriver instanceof FtdiSerialDriver)
return; // periodically sends status messages, so does not block here
final Boolean[] closed = {Boolean.FALSE};
Runnable closeThread = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
usbClose();
closed[0] = true;
}
};
usbOpen(false);
usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
byte[] buf = new byte[]{1};
int len,i,j;
long time;
// w/o timeout
telnetWrite(buf);
len = usbSerialPort.read(buf, 0); // not blocking because data is available
assertEquals(1, len);
time = System.currentTimeMillis();
closed[0] = false;
Executors.newSingleThreadExecutor().submit(closeThread);
len = usbSerialPort.read(buf, 0); // blocking until close()
assertEquals(0, len);
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
usbOpen(false);
usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
int longTimeout = 1000;
int shortTimeout = 10;
time = System.currentTimeMillis();
len = usbSerialPort.read(buf, 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);
telnetWrite(buf);
for(j=0; j<20; j++) {
len = usbSerialPort.read(buf, shortTimeout);
if (len > 0)
break;
}
assertEquals("failed after " + i, 1, len);
}
Log.i(TAG, "average time per read " + (System.currentTimeMillis()-time)/i + " msec");
if(!(usbSerialDriver instanceof CdcAcmSerialDriver)) {
int diffLen;
usbClose();
// no issue with high transfer rate and long read timeout
diffLen = readSpeedInt(5, longTimeout);
if(usbSerialDriver instanceof Ch34xSerialDriver && diffLen == -1)
diffLen = 0; // todo: investigate last packet loss
assertEquals(0, diffLen);
usbClose();
// date loss with high transfer rate and short read timeout !!!
diffLen = readSpeedInt(5, shortTimeout);
assertNotEquals(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(!(usbSerialDriver instanceof CdcAcmSerialDriver)) {
wrongDeviceConnection = usbManager.openDevice(usbSerialDriver.getDevice());
wrongSerialDriver = new CdcAcmSerialDriver(usbSerialDriver.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(!(usbSerialDriver instanceof ProlificSerialDriver))
fail("error expected");
} catch (IOException ignored) {
}
try {
if(usbSerialDriver instanceof ProlificSerialDriver) {
assertNotEquals(new byte[]{1}, telnetRead());
}
wrongSerialPort.close();
if(!(usbSerialDriver instanceof Ch34xSerialDriver |
usbSerialDriver instanceof ProlificSerialDriver))
fail("error expected");
} catch (IOException ignored) {
}
}
if(!(usbSerialDriver instanceof Ch34xSerialDriver)) {
wrongDeviceConnection = usbManager.openDevice(usbSerialDriver.getDevice());
wrongSerialDriver = new Ch34xSerialDriver(usbSerialDriver.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(!(usbSerialDriver instanceof Cp21xxSerialDriver | usbSerialDriver instanceof FtdiSerialDriver)) {
wrongDeviceConnection = usbManager.openDevice(usbSerialDriver.getDevice());
wrongSerialDriver = new Cp21xxSerialDriver(usbSerialDriver.getDevice());
wrongSerialPort = wrongSerialDriver.getPorts().get(0);
try {
wrongSerialPort.open(wrongDeviceConnection);
//if(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(!(usbSerialDriver instanceof FtdiSerialDriver))
// fail("error expected");
} catch (IOException ignored) {
}
}
if(!(usbSerialDriver instanceof FtdiSerialDriver)) {
wrongDeviceConnection = usbManager.openDevice(usbSerialDriver.getDevice());
wrongSerialDriver = new FtdiSerialDriver(usbSerialDriver.getDevice());
wrongSerialPort = wrongSerialDriver.getPorts().get(0);
try {
wrongSerialPort.open(wrongDeviceConnection);
if(usbSerialDriver instanceof Cp21xxSerialDriver)
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(!(usbSerialDriver instanceof Cp21xxSerialDriver))
fail("error expected");
} catch (IOException ignored) {
}
}
if(!(usbSerialDriver instanceof ProlificSerialDriver)) {
wrongDeviceConnection = usbManager.openDevice(usbSerialDriver.getDevice());
wrongSerialDriver = new ProlificSerialDriver(usbSerialDriver.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
usbOpen(true);
telnetParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usbParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
doReadWrite("");
}
}