Skip to content

Commit

Permalink
01. Refactored SerialInputOutputManager (#615)
Browse files Browse the repository at this point in the history
Used separate threads for reading and writing, enhancing concurrency and performance.

Note: before was possible to start `SerialInputOutputManager` with `Executors.newSingleThreadExecutor().submit(ioManager)`. Now you have to use `ioManager.start()`
  • Loading branch information
dkaukov authored Jan 28, 2025
1 parent 2673407 commit 9911e14
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 152 deletions.
3 changes: 2 additions & 1 deletion usbSerialForAndroid/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ dependencies {
implementation "androidx.annotation:annotation:1.8.0"
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:5.12.0'
androidTestImplementation 'androidx.appcompat:appcompat:1.6.1'
androidTestImplementation 'androidx.test:core:1.5.0'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'commons-net:commons-net:3.10.0'
androidTestImplementation 'commons-net:commons-net:3.9.0' // later version fails on old Android devices with missing java.time.Duration class
androidTestImplementation 'org.apache.commons:commons-lang3:3.14.0'
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1425,19 +1425,16 @@ public void IoManager() throws Exception {
usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE);
usb.ioManager.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
assertEquals(SerialInputOutputManager.State.STOPPED, usb.ioManager.getState());
usb.ioManager.start();
usb.waitForIoManagerStarted();
assertTrue("iomanager thread", usb.hasIoManagerThread());
assertEquals(SerialInputOutputManager.State.RUNNING, usb.ioManager.getState());
assertTrue("iomanager thread", usb.hasIoManagerThreads());
try {
usb.ioManager.start();
fail("already running error expected");
} catch (IllegalStateException ignored) {
}
try {
usb.ioManager.run();
fail("already running error expected");
} catch (IllegalStateException ignored) {
}
try {
usb.ioManager.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
fail("setThreadPriority IllegalStateException expected");
Expand All @@ -1462,23 +1459,12 @@ public void IoManager() throws Exception {
telnet.write(new byte[1]); // now uses 8 byte buffer
usb.read(3);

// writebuffer resize
// small writebuffer
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)");
Expand All @@ -1490,74 +1476,31 @@ public void IoManager() throws Exception {
telnet.write("d".getBytes());
assertThat(usb.read(1), equalTo("d".getBytes()));

SerialInputOutputManager ioManager = usb.ioManager;
assertEquals(SerialInputOutputManager.State.RUNNING, usb.ioManager.getState());
usb.close();
for (int i = 0; i < 100 && usb.hasIoManagerThread(); i++) {
for (int i = 0; i < 100 && usb.hasIoManagerThreads(); i++) {
Thread.sleep(1);
}
assertFalse("iomanager thread", usb.hasIoManagerThread());
assertFalse("iomanager threads", usb.hasIoManagerThreads());
assertNull(usb.ioManager);
assertEquals(SerialInputOutputManager.State.STOPPED, ioManager.getState());
SerialInputOutputManager.DEBUG = false;

// legacy start
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);
Executors.newSingleThreadExecutor().submit(usb.ioManager);
usb.waitForIoManagerStarted();
try {
usb.ioManager.start();
fail("already running error expected");
} catch (IllegalStateException ignored) {
}
}

@Test
public void writeAsync() throws Exception {
byte[] data, buf = new byte[]{1};

// w/o timeout: write delayed until something is read
// write immediately, without waiting for 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.ioManager.start();
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);

// with internal SerialTimeoutException
TestBuffer tbuf = new TestBuffer(usb.writeBufferSize + 2*usb.writePacketSize);
byte[] pbuf1 = new byte[tbuf.buf.length - 4];
byte[] pbuf2 = new byte[1];
System.arraycopy(tbuf.buf, 0,pbuf1, 0, pbuf1.length);
usb.ioManager.setWriteTimeout(20); // tbuf len >= 128, needs 133msec @ 9600 baud
usb.setParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE);
telnet.setParameters(9600, 8, 1, UsbSerialPort.PARITY_NONE);
usb.ioManager.writeAsync(pbuf1);
for(int i = pbuf1.length; i < tbuf.buf.length; i++) {
Thread.sleep(20);
pbuf2[0] = tbuf.buf[i];
usb.ioManager.writeAsync(pbuf2);
}
while(!tbuf.testRead(telnet.read(-1)))
;
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import androidx.core.content.ContextCompat;

public class UsbWrapper implements SerialInputOutputManager.Listener {

public final static int USB_READ_WAIT = 500;
Expand Down Expand Up @@ -92,7 +94,7 @@ public void onReceive(Context context, Intent intent) {
intent.setPackage(context.getPackageName());
PendingIntent permissionIntent = PendingIntent.getBroadcast(context, 0, intent, flags);
IntentFilter filter = new IntentFilter("com.android.example.USB_PERMISSION");
context.registerReceiver(usbReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
ContextCompat.registerReceiver(context, usbReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
usbManager.requestPermission(serialDriver.getDevice(), permissionIntent);
for(int i=0; i<5000; i++) {
if(granted[0] != null) break;
Expand Down Expand Up @@ -256,12 +258,15 @@ public void waitForIoManagerStarted() throws IOException {
throw new IOException("IoManager not started");
}

public boolean hasIoManagerThread() {
public boolean hasIoManagerThreads() {
int c = 0;
for (Thread thread : Thread.getAllStackTraces().keySet()) {
if (thread.getName().equals(SerialInputOutputManager.class.getSimpleName()))
return true;
if (thread.getName().equals(SerialInputOutputManager.class.getSimpleName() + "_read"))
c += 1;
if (thread.getName().equals(SerialInputOutputManager.class.getSimpleName() + "_write"))
c += 1;
}
return false;
return c == 2;
}

// wait full time
Expand Down
Loading

0 comments on commit 9911e14

Please sign in to comment.