tests UsbDeviceConnection close behavior

and extract test utilities
pull/297/head
kai-morich 2020-07-18 19:30:43 +02:00
rodzic a1e58b9843
commit 8eaf3f5c5f
7 zmienionych plików z 1038 dodań i 726 usunięć

Wyświetl plik

@ -6,7 +6,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0'
classpath 'com.android.tools.build:gradle:4.0.1'
}
}

Wyświetl plik

@ -11,7 +11,7 @@ android {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments = [ // Raspi Windows LinuxVM ...
'rfc2217_server_host': '192.168.0.110',
'rfc2217_server_host': '192.168.0.100',
'rfc2217_server_nonstandard_baudrates': 'true', // true false false
]
}

Wyświetl plik

@ -0,0 +1,159 @@
/*
* test multiple devices or multiple ports on same device
*
* TxD and RxD have to be cross connected
*/
package com.hoho.android.usbserial;
import android.content.Context;
import android.hardware.usb.UsbManager;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
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.UsbWrapper;
import org.junit.Before;
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.util.EnumSet;
import java.util.List;
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.fail;
@RunWith(AndroidJUnit4.class)
public class CrossoverTest {
private final static String TAG = CrossoverTest.class.getSimpleName();
private Context context;
private UsbManager usbManager;
private UsbWrapper usb1, usb2;
@Rule
public TestRule watcher = new TestWatcher() {
protected void starting(Description description) {
Log.i(TAG, "===== starting test: " + description.getMethodName()+ " =====");
}
};
@Before
public void setUp() throws Exception {
context = InstrumentationRegistry.getContext();
usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
List<UsbSerialDriver> availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(usbManager);
assertNotEquals("no USB device found", 0, availableDrivers.size());
if (availableDrivers.size() == 0) {
fail("no USB device found");
} else if (availableDrivers.size() == 1) {
assertEquals(2, availableDrivers.get(0).getPorts().size());
usb1 = new UsbWrapper(context, availableDrivers.get(0), 0);
usb2 = new UsbWrapper(context, availableDrivers.get(0), 1);
} else {
assertEquals(1, availableDrivers.get(0).getPorts().size());
assertEquals(1, availableDrivers.get(1).getPorts().size());
usb1 = new UsbWrapper(context, availableDrivers.get(0), 0);
usb2 = new UsbWrapper(context, availableDrivers.get(1), 0);
}
usb1.setUp();
usb2.setUp();
}
@Test
public void reopen() throws Exception {
byte[] buf;
usb1.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_THREAD));
usb2.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_THREAD));
usb1.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usb2.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usb1.write("x".getBytes());
buf = usb2.read(1);
assertThat(buf, equalTo("x".getBytes()));
usb2.write("y".getBytes());
buf = usb1.read(1);
assertThat(buf, equalTo("y".getBytes()));
usb2.close(); // does not affect usb1 with individual UsbDeviceConnection on same device
usb2.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_THREAD));
usb2.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usb1.write("x".getBytes());
buf = usb2.read(1);
assertThat(buf, equalTo("x".getBytes()));
usb2.write("y".getBytes());
buf = usb1.read(1);
assertThat(buf, equalTo("y".getBytes()));
usb1.close();
usb2.close();
}
@Test
public void ioManager() throws Exception {
byte[] buf;
// each SerialInputOutputManager thread runs in it's own SingleThreadExecutor
usb1.open();
usb2.open();
usb1.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usb2.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usb1.write("x".getBytes());
buf = usb2.read(1);
assertThat(buf, equalTo("x".getBytes()));
usb2.write("y".getBytes());
buf = usb1.read(1);
assertThat(buf, equalTo("y".getBytes()));
usb1.close();
usb2.close();
}
@Test
public void baudRate() throws Exception {
byte[] buf;
usb1.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_THREAD));
usb2.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_THREAD));
usb1.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usb2.setParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE);
// a - start bit (0)
// o - stop bit (1)
// 0/1 - data bit
// out 19k2: a00011001o
// in 9k6: a 0 1 0 1 1 1 1 1 o
usb1.write(new byte[]{(byte)0x98});
buf = usb2.read(1);
assertThat(buf, equalTo(new byte[]{(byte)0xfa}));
// out 9k6: a 1 0 1 1 1 1 1 1 o
// in 19k2: a01100111o
usb2.write(new byte[]{(byte)0xfd});
buf = usb1.read(1);
assertThat(buf, equalTo(new byte[]{(byte)0xe6}));
usb1.close();
usb2.close();
}
}

Wyświetl plik

@ -0,0 +1,155 @@
package com.hoho.android.usbserial.util;
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 java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import static org.junit.Assert.assertEquals;
public class TelnetWrapper {
private final static String TAG = TelnetWrapper.class.getSimpleName();
private final static int TELNET_READ_WAIT = 500;
private final static int TELNET_COMMAND_WAIT = 2000;
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 final String host;
private final int port;
private TelnetClient telnetClient;
private InputStream readStream;
private OutputStream writeStream;
private Integer[] comPortOptionCounter = {0};
public int writeDelay = 0;
public TelnetWrapper(String host, int port) {
this.host = host;
this.port = port;
telnetClient = null;
}
private 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) {
comPortOptionCounter[0] += 1;
return super.answerSubnegotiation(suboptionData, suboptionLength);
}
});
telnetClient.setConnectTimeout(2000);
telnetClient.connect(host, port);
telnetClient.setTcpNoDelay(true);
writeStream = telnetClient.getOutputStream();
readStream = telnetClient.getInputStream();
}
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
comPortOptionCounter[0] = 0;
telnetClient.sendCommand((byte)TelnetCommand.SB);
writeStream.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(comPortOptionCounter[0] == 1) break;
Thread.sleep(1);
}
assertEquals("telnet connection lost", 1, comPortOptionCounter[0].intValue());
writeDelay = 0;
}
public void tearDown() {
try {
read(0);
} catch (Exception ignored) {
}
}
public void tearDownFixture() throws Exception {
try {
telnetClient.disconnect();
} catch (Exception ignored) {}
readStream = null;
writeStream = null;
telnetClient = null;
}
// wait full time
public byte[] read() throws Exception {
return read(-1);
}
public byte[] read(int expectedLength) throws Exception {
long end = System.currentTimeMillis() + TELNET_READ_WAIT;
ByteBuffer buf = ByteBuffer.allocate(65536);
while(System.currentTimeMillis() < end) {
if(readStream.available() > 0) {
buf.put((byte) readStream.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;
}
public void write(byte[] data) throws Exception{
if(writeDelay != 0) {
for(byte b : data) {
writeStream.write(b);
writeStream.flush();
Thread.sleep(writeDelay);
}
} else {
writeStream.write(data);
writeStream.flush();
}
}
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException, InterruptedException, InvalidTelnetOptionException {
comPortOptionCounter[0] = 0;
telnetClient.sendCommand((byte) TelnetCommand.SB);
writeStream.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);
writeStream.write(new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_DATASIZE, (byte)dataBits});
telnetClient.sendCommand((byte)TelnetCommand.SE);
telnetClient.sendCommand((byte)TelnetCommand.SB);
writeStream.write(new byte[] {RFC2217_COM_PORT_OPTION, RFC2217_SET_STOPSIZE, (byte)stopBits});
telnetClient.sendCommand((byte)TelnetCommand.SE);
telnetClient.sendCommand((byte)TelnetCommand.SB);
writeStream.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(comPortOptionCounter[0] == 4) break;
Thread.sleep(1);
}
assertEquals("telnet connection lost", 4, comPortOptionCounter[0].intValue());
}
}

Wyświetl plik

@ -0,0 +1,256 @@
package com.hoho.android.usbserial.util;
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.util.Log;
import com.hoho.android.usbserial.driver.CdcAcmSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialPort;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Deque;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.concurrent.Executors;
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;
public class UsbWrapper implements SerialInputOutputManager.Listener {
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 static final String TAG = UsbWrapper.class.getSimpleName();
public enum OpenCloseFlags { NO_IOMANAGER_THREAD, NO_CONTROL_LINE_INIT, NO_DEVICE_CONNECTION };
// constructor
final Context context;
public final UsbSerialDriver serialDriver;
public final int devicePort;
public final UsbSerialPort serialPort;
// open
public UsbDeviceConnection deviceConnection;
public SerialInputOutputManager ioManager;
// read
final Deque<byte[]> readBuffer = new LinkedList<>();
Exception readError;
public boolean readBlock = false;
long readTime = 0;
public UsbWrapper(Context context, UsbSerialDriver serialDriver, int devicePort) {
this.context = context;
this.serialDriver = serialDriver;
this.devicePort = devicePort;
serialPort = serialDriver.getPorts().get(devicePort);
}
public void setUp() throws Exception {
UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
if (!usbManager.hasPermission(serialDriver.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(serialDriver.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]);
}
}
public void tearDown() {
try {
if (ioManager != null)
read(0);
else
serialPort.purgeHwBuffers(true, true);
} catch (Exception ignored) {
}
close();
//usb.serialDriver = null;
}
public void close() {
close(EnumSet.noneOf(OpenCloseFlags.class));
}
public void close(EnumSet<OpenCloseFlags> flags) {
if (ioManager != null) {
ioManager.setListener(null);
ioManager.stop();
}
if (serialPort != null) {
try {
if(!flags.contains(OpenCloseFlags.NO_CONTROL_LINE_INIT)) {
serialPort.setDTR(false);
serialPort.setRTS(false);
}
} catch (Exception ignored) {
}
try {
serialPort.close();
} catch (Exception ignored) {
}
//usbSerialPort = null;
}
if(!flags.contains(OpenCloseFlags.NO_DEVICE_CONNECTION)) {
deviceConnection = null; // closed in usbSerialPort.close()
}
if(ioManager != null) {
for (int i = 0; i < 2000; i++) {
if (SerialInputOutputManager.State.STOPPED == ioManager.getState()) break;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
assertEquals(SerialInputOutputManager.State.STOPPED, ioManager.getState());
ioManager = null;
}
}
public void open() throws Exception {
open(EnumSet.noneOf(OpenCloseFlags.class), 0);
}
public void open(EnumSet<OpenCloseFlags> flags) throws Exception {
open(flags, 0);
}
public void open(EnumSet<OpenCloseFlags> flags, int ioManagerTimeout) throws Exception {
if(!flags.contains(OpenCloseFlags.NO_DEVICE_CONNECTION)) {
UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
deviceConnection = usbManager.openDevice(serialDriver.getDevice());
}
//serialPort = serialDriver.getPorts().get(devicePort);
serialPort.open(deviceConnection);
if(!flags.contains(OpenCloseFlags.NO_CONTROL_LINE_INIT)) {
serialPort.setDTR(true);
serialPort.setRTS(true);
}
if(!flags.contains(OpenCloseFlags.NO_IOMANAGER_THREAD)) {
ioManager = new SerialInputOutputManager(serialPort, this) {
@Override
public void run() {
if (SERIAL_INPUT_OUTPUT_MANAGER_THREAD_PRIORITY != null)
Process.setThreadPriority(SERIAL_INPUT_OUTPUT_MANAGER_THREAD_PRIORITY);
super.run();
}
};
ioManager.setReadTimeout(ioManagerTimeout);
ioManager.setWriteTimeout(ioManagerTimeout);
Executors.newSingleThreadExecutor().submit(ioManager);
}
synchronized (readBuffer) {
readBuffer.clear();
}
readError = null;
}
// wait full time
public byte[] read() throws Exception {
return read(-1);
}
public byte[] read(int expectedLength) throws Exception {
long end = System.currentTimeMillis() + USB_READ_WAIT;
ByteBuffer buf = ByteBuffer.allocate(16*1024);
if(ioManager != null) {
while (System.currentTimeMillis() < end) {
if(readError != null)
throw readError;
synchronized (readBuffer) {
while(readBuffer.peek() != null)
buf.put(readBuffer.remove());
}
if (expectedLength >= 0 && buf.position() >= expectedLength)
break;
Thread.sleep(1);
}
} else {
byte[] b1 = new byte[256];
while (System.currentTimeMillis() < end) {
int len = serialPort.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;
}
public void write(byte[] data) throws IOException {
serialPort.write(data, USB_WRITE_WAIT);
}
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException, InterruptedException {
serialPort.setParameters(baudRate, dataBits, stopBits, parity);
if(serialDriver instanceof CdcAcmSerialDriver)
Thread.sleep(10); // arduino_leonardeo_bridge.ini needs some time
else
Thread.sleep(1);
}
@Override
public void onNewData(byte[] data) {
long now = System.currentTimeMillis();
if(readTime == 0)
readTime = now;
if(data.length > 64) {
Log.d(TAG, "usb read: time+=" + String.format("%-3d",now- readTime) + " 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- readTime) + " len=" + String.format("%-4d",data.length) + " data=" + new String(data));
}
readTime = now;
while(readBlock)
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (readBuffer) {
readBuffer.add(data);
}
}
@Override
public void onRunError(Exception e) {
readError = e;
//fail("usb connection lost");
}
}

Wyświetl plik

@ -130,8 +130,6 @@ public class SerialInputOutputManager implements Runnable {
/**
* Continuously services the read and write buffers until {@link #stop()} is
* called, or until a driver exception is raised.
*
* NOTE(mikey): Uses inefficient read/write-with-timeout.
*/
@Override
public void run() {