From f443d1f0122b5ea416d5b4f83016c6e90dd7cfb1 Mon Sep 17 00:00:00 2001 From: kai-morich Date: Mon, 24 Aug 2020 17:37:26 +0200 Subject: [PATCH] iomanager with configurable buffer size --- .../hoho/android/usbserial/DeviceTest.java | 95 +++++++++++++++---- .../android/usbserial/util/UsbWrapper.java | 12 +++ .../util/SerialInputOutputManager.java | 65 ++++++++++--- 3 files changed, 142 insertions(+), 30 deletions(-) diff --git a/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/DeviceTest.java b/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/DeviceTest.java index 614978f..a43fb7c 100644 --- a/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/DeviceTest.java +++ b/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/DeviceTest.java @@ -45,6 +45,7 @@ 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; @@ -56,6 +57,7 @@ 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; @@ -1014,6 +1016,82 @@ public class DeviceTest { } } + @Test + public void IoManager() throws Exception { + 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.open(); // creates new IoManager + usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); + telnet.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); + usb.waitForIoManagerStarted(); + 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())); + } + @Test public void writeAsync() throws Exception { if (usb.serialDriver instanceof FtdiSerialDriver) @@ -1021,19 +1099,6 @@ public class DeviceTest { byte[] data, buf = new byte[]{1}; - usb.ioManager = new SerialInputOutputManager(null); - assertEquals(null, 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(100); - assertEquals(100, usb.ioManager.getReadTimeout()); - assertEquals(0, usb.ioManager.getWriteTimeout()); - usb.ioManager.setWriteTimeout(200); - assertEquals(200, usb.ioManager.getWriteTimeout()); - // w/o timeout: write delayed until something is read usb.open(); usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); @@ -1047,10 +1112,6 @@ public class DeviceTest { assertEquals(1, data.length); data = telnet.read(2); assertEquals(2, data.length); - try { - usb.ioManager.setReadTimeout(100); - fail("IllegalStateException expected"); - } catch (IllegalStateException ignored) {} usb.close(); // with timeout: write after timeout diff --git a/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/util/UsbWrapper.java b/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/util/UsbWrapper.java index dc7d01f..7dddfd4 100644 --- a/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/util/UsbWrapper.java +++ b/usbSerialForAndroid/src/androidTest/java/com/hoho/android/usbserial/util/UsbWrapper.java @@ -175,6 +175,18 @@ public class UsbWrapper implements SerialInputOutputManager.Listener { readError = null; } + public void waitForIoManagerStarted() throws IOException { + for (int i = 0; i < 100; i++) { + if (SerialInputOutputManager.State.STOPPED != ioManager.getState()) return; + try { + Thread.sleep(1); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + throw new IOException("IoManager not started"); + } + // wait full time public byte[] read() throws Exception { return read(-1); diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/util/SerialInputOutputManager.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/util/SerialInputOutputManager.java index c004629..fbd95de 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/util/SerialInputOutputManager.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/util/SerialInputOutputManager.java @@ -30,8 +30,11 @@ public class SerialInputOutputManager implements Runnable { private int mReadTimeout = 0; private int mWriteTimeout = 0; - private final ByteBuffer mReadBuffer = ByteBuffer.allocate(BUFSIZ); - private final ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ); // Synchronized by 'mWriteBuffer' + private final Object mReadBufferLock = new Object(); + private final Object mWriteBufferLock = new Object(); + + private ByteBuffer mReadBuffer = ByteBuffer.allocate(BUFSIZ); + private ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ); public enum State { STOPPED, @@ -72,10 +75,13 @@ public class SerialInputOutputManager implements Runnable { return mListener; } + /** + * read/write timeout + */ public void setReadTimeout(int timeout) { // when set if already running, read already blocks and the new value will not become effective now if(mReadTimeout == 0 && timeout != 0 && mState != State.STOPPED) - throw new IllegalStateException("Set readTimeout before SerialInputOutputManager is started"); + throw new IllegalStateException("readTimeout only configurable before SerialInputOutputManager is started"); mReadTimeout = timeout; } @@ -91,12 +97,42 @@ public class SerialInputOutputManager implements Runnable { return mWriteTimeout; } + /** + * read/write buffer size + */ + public void setReadBufferSize(int bufferSize) { + if (getReadBufferSize() == bufferSize) + return; + synchronized (mReadBufferLock) { + mReadBuffer = ByteBuffer.allocate(bufferSize); + } + } + + public int getReadBufferSize() { + return mReadBuffer.capacity(); + } + + public void setWriteBufferSize(int bufferSize) { + if(getWriteBufferSize() == bufferSize) + return; + synchronized (mWriteBufferLock) { + ByteBuffer newWriteBuffer = ByteBuffer.allocate(bufferSize); + if(mWriteBuffer.position() > 0) + newWriteBuffer.put(mWriteBuffer.array(), 0, mWriteBuffer.position()); + mWriteBuffer = newWriteBuffer; + } + } + + public int getWriteBufferSize() { + return mWriteBuffer.capacity(); + } + /* * when writeAsync is used, it is recommended to use readTimeout != 0, * else the write will be delayed until read data is available */ public void writeAsync(byte[] data) { - synchronized (mWriteBuffer) { + synchronized (mWriteBufferLock) { mWriteBuffer.put(data); } } @@ -150,34 +186,37 @@ public class SerialInputOutputManager implements Runnable { private void step() throws IOException { // Handle incoming data. - int len = mSerialPort.read(mReadBuffer.array(), mReadTimeout); + byte[] buffer = null; + synchronized (mReadBufferLock) { + buffer = mReadBuffer.array(); + } + int len = mSerialPort.read(buffer, mReadTimeout); if (len > 0) { if (DEBUG) Log.d(TAG, "Read data len=" + len); final Listener listener = getListener(); if (listener != null) { final byte[] data = new byte[len]; - mReadBuffer.get(data, 0, len); + System.arraycopy(buffer, 0, data, 0, len); listener.onNewData(data); } - mReadBuffer.clear(); } // Handle outgoing data. - byte[] outBuff = null; - synchronized (mWriteBuffer) { + buffer = null; + synchronized (mWriteBufferLock) { len = mWriteBuffer.position(); if (len > 0) { - outBuff = new byte[len]; + buffer = new byte[len]; mWriteBuffer.rewind(); - mWriteBuffer.get(outBuff, 0, len); + mWriteBuffer.get(buffer, 0, len); mWriteBuffer.clear(); } } - if (outBuff != null) { + if (buffer != null) { if (DEBUG) { Log.d(TAG, "Writing data len=" + len); } - mSerialPort.write(outBuff, mWriteTimeout); + mSerialPort.write(buffer, mWriteTimeout); } }