mirror of
				https://github.com/mik3y/usb-serial-for-android
				synced 2025-10-31 02:17:23 +00:00 
			
		
		
		
	setReadQueue(...), enabled by default in SerialInputOutputManager
for applications doing permanent read() with timeout=0, multiple buffers can be used to copy next data from Linux kernel, while the current data is processed.
This commit is contained in:
		
							parent
							
								
									026355f61e
								
							
						
					
					
						commit
						90246e5c7b
					
				| @ -14,6 +14,7 @@ import android.content.Context; | |||||||
| import android.hardware.usb.UsbDevice; | import android.hardware.usb.UsbDevice; | ||||||
| import android.hardware.usb.UsbDeviceConnection; | import android.hardware.usb.UsbDeviceConnection; | ||||||
| import android.hardware.usb.UsbManager; | import android.hardware.usb.UsbManager; | ||||||
|  | import android.hardware.usb.UsbRequest; | ||||||
| import android.os.Process; | import android.os.Process; | ||||||
| import androidx.test.core.app.ApplicationProvider; | import androidx.test.core.app.ApplicationProvider; | ||||||
| import androidx.test.platform.app.InstrumentationRegistry; | import androidx.test.platform.app.InstrumentationRegistry; | ||||||
| @ -43,7 +44,6 @@ import com.hoho.android.usbserial.driver.UsbSerialPort.ControlLine; | |||||||
| import com.hoho.android.usbserial.driver.UsbSerialPort.FlowControl; | import com.hoho.android.usbserial.driver.UsbSerialPort.FlowControl; | ||||||
| import com.hoho.android.usbserial.util.XonXoffFilter; | import com.hoho.android.usbserial.util.XonXoffFilter; | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| import org.junit.After; | import org.junit.After; | ||||||
| import org.junit.AfterClass; | import org.junit.AfterClass; | ||||||
| import org.junit.Assume; | import org.junit.Assume; | ||||||
| @ -60,11 +60,13 @@ import java.io.IOException; | |||||||
| import java.nio.BufferOverflowException; | import java.nio.BufferOverflowException; | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| import java.util.EnumSet; | import java.util.EnumSet; | ||||||
|  | import java.util.LinkedList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.concurrent.Executors; | import java.util.concurrent.Executors; | ||||||
| import java.util.concurrent.ScheduledExecutorService; | import java.util.concurrent.ScheduledExecutorService; | ||||||
| import java.util.concurrent.ScheduledFuture; | import java.util.concurrent.ScheduledFuture; | ||||||
| import java.util.concurrent.TimeUnit; | import java.util.concurrent.TimeUnit; | ||||||
|  | import java.util.stream.Collectors; | ||||||
| 
 | 
 | ||||||
| import static org.hamcrest.CoreMatchers.anyOf; | import static org.hamcrest.CoreMatchers.anyOf; | ||||||
| import static org.hamcrest.MatcherAssert.assertThat; | import static org.hamcrest.MatcherAssert.assertThat; | ||||||
| @ -1148,7 +1150,7 @@ public class DeviceTest { | |||||||
|     public void readBufferOverflow() throws Exception { |     public void readBufferOverflow() throws Exception { | ||||||
|         if(usb.serialDriver instanceof CdcAcmSerialDriver) |         if(usb.serialDriver instanceof CdcAcmSerialDriver) | ||||||
|             telnet.writeDelay = 10; // arduino_leonardo_bridge.ino sends each byte in own USB packet, which is horribly slow |             telnet.writeDelay = 10; // arduino_leonardo_bridge.ino sends each byte in own USB packet, which is horribly slow | ||||||
|         usb.open(); |         usb.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_READQUEUE)); | ||||||
|         usb.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE); |         usb.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE); | ||||||
|         telnet.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE); |         telnet.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE); | ||||||
| 
 | 
 | ||||||
| @ -1198,6 +1200,64 @@ public class DeviceTest { | |||||||
|         assertTrue(data.length() != expected.length()); |         assertTrue(data.length() != expected.length()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Test | ||||||
|  |     public void readQueue() throws Exception { | ||||||
|  |         class CountingUsbRequest extends UsbRequest { | ||||||
|  |             int count; | ||||||
|  |             @Override public Object getClientData() { count += 1; return super.getClientData(); } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         usb.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_THREAD)); | ||||||
|  |         int len = usb.serialPort.getReadEndpoint().getMaxPacketSize(); | ||||||
|  |         usb.close(); | ||||||
|  |         CommonUsbSerialPortWrapper.setReadQueueRequestSupplier(usb.serialPort, CountingUsbRequest::new); | ||||||
|  |         CommonUsbSerialPort port = (CommonUsbSerialPort) usb.serialPort; | ||||||
|  | 
 | ||||||
|  |         port.setReadQueue(2, len); | ||||||
|  |         usb.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_START)); | ||||||
|  |         usb.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE); | ||||||
|  |         telnet.setParameters(115200, 8, 1, UsbSerialPort.PARITY_NONE); | ||||||
|  |         assertEquals(2, port.getReadQueueBufferCount()); | ||||||
|  |         assertEquals(4, usb.ioManager.getReadQueueBufferCount()); // not set at port yet | ||||||
|  |         assertThrows(IllegalStateException.class, () -> usb.ioManager.setReadQueue(1)); // cannot reduce bufferCount | ||||||
|  |         usb.ioManager.setReadQueue(2); | ||||||
|  |         usb.ioManager.start(); | ||||||
|  |         port.setReadQueue(4, len); | ||||||
|  | 
 | ||||||
|  |         // linux kernel does round-robin | ||||||
|  |         LinkedList<UsbRequest> requests = CommonUsbSerialPortWrapper.getReadQueueRequests(usb.serialPort); | ||||||
|  |         assertNotNull(requests); | ||||||
|  |         for (int i=0; i<16; i++) { | ||||||
|  |             telnet.write(new byte[1]); | ||||||
|  |             usb.read(1); | ||||||
|  |         } | ||||||
|  |         List<Integer> requestCounts; | ||||||
|  |         if(usb.serialDriver instanceof FtdiSerialDriver) { | ||||||
|  |             for (UsbRequest request : requests) { | ||||||
|  |                 int count = ((CountingUsbRequest)request).count; | ||||||
|  |                 assertTrue(String.valueOf(count), count >= 4); | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             requestCounts = requests.stream().map(r -> ((CountingUsbRequest)r).count).collect(Collectors.toList()); | ||||||
|  |             assertThat(requestCounts, equalTo(Arrays.asList(4, 4, 4, 4))); | ||||||
|  |         } | ||||||
|  |         usb.ioManager.setReadQueue(6); | ||||||
|  |         for (int i=0; i<18; i++) { | ||||||
|  |             telnet.write(new byte[1]); | ||||||
|  |             usb.read(1); | ||||||
|  |         } | ||||||
|  |         requestCounts = requests.stream().map(r -> ((CountingUsbRequest)r).count).collect(Collectors.toList()); | ||||||
|  |         if(!(usb.serialDriver instanceof FtdiSerialDriver)) { | ||||||
|  |             assertThat(requestCounts, equalTo(Arrays.asList(7, 7, 7, 7, 3, 3))); | ||||||
|  |         } | ||||||
|  |         usb.close(); | ||||||
|  |         usb.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_START)); | ||||||
|  |         port.setReadQueue(8, len); | ||||||
|  |         assertThrows(IllegalStateException.class, () -> usb.serialPort.read(new byte[len], 1) ); // cannot use timeout != 0 | ||||||
|  |         assertThrows(IllegalStateException.class, () -> usb.serialPort.read(new byte[4], 0) ); // cannot use different length | ||||||
|  |         assertThrows(IllegalStateException.class, () -> usb.ioManager.start()); // cannot reduce bufferCount | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Test |     @Test | ||||||
|     public void readSpeed() throws Exception { |     public void readSpeed() throws Exception { | ||||||
|         // see logcat for performance results |         // see logcat for performance results | ||||||
| @ -1223,7 +1283,7 @@ public class DeviceTest { | |||||||
|         if(usb.serialDriver instanceof CdcAcmSerialDriver) |         if(usb.serialDriver instanceof CdcAcmSerialDriver) | ||||||
|             writeAhead = 50; |             writeAhead = 50; | ||||||
| 
 | 
 | ||||||
|         usb.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_START)); |         usb.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_START, UsbWrapper.OpenCloseFlags.NO_IOMANAGER_READQUEUE)); | ||||||
|         usb.ioManager.setReadTimeout(readTimeout); |         usb.ioManager.setReadTimeout(readTimeout); | ||||||
|         if(readBufferSize > 0) |         if(readBufferSize > 0) | ||||||
|             usb.ioManager.setReadBufferSize(readBufferSize); |             usb.ioManager.setReadBufferSize(readBufferSize); | ||||||
| @ -1421,7 +1481,7 @@ public class DeviceTest { | |||||||
|         usb.ioManager.setWriteTimeout(usb.ioManager.getWriteTimeout()); |         usb.ioManager.setWriteTimeout(usb.ioManager.getWriteTimeout()); | ||||||
|         usb.close(); |         usb.close(); | ||||||
| 
 | 
 | ||||||
|         usb.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_START)); // creates new IoManager |         usb.open(EnumSet.of(UsbWrapper.OpenCloseFlags.NO_IOMANAGER_START, UsbWrapper.OpenCloseFlags.NO_IOMANAGER_READQUEUE)); // creates new IoManager | ||||||
|         usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); |         usb.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); | ||||||
|         telnet.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); |         telnet.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE); | ||||||
|         usb.ioManager.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); |         usb.ioManager.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); | ||||||
|  | |||||||
| @ -1,8 +1,24 @@ | |||||||
| package com.hoho.android.usbserial.driver; | package com.hoho.android.usbserial.driver; | ||||||
| 
 | 
 | ||||||
|  | import android.hardware.usb.UsbRequest; | ||||||
|  | 
 | ||||||
|  | import com.hoho.android.usbserial.util.UsbUtils; | ||||||
|  | 
 | ||||||
|  | import java.util.LinkedList; | ||||||
|  | 
 | ||||||
| public class CommonUsbSerialPortWrapper { | public class CommonUsbSerialPortWrapper { | ||||||
|     public static byte[] getWriteBuffer(UsbSerialPort serialPort) { |     public static byte[] getWriteBuffer(UsbSerialPort serialPort) { | ||||||
|         CommonUsbSerialPort commonSerialPort = (CommonUsbSerialPort) serialPort; |         CommonUsbSerialPort commonSerialPort = (CommonUsbSerialPort) serialPort; | ||||||
|         return commonSerialPort.mWriteBuffer; |         return commonSerialPort.mWriteBuffer; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public static LinkedList<UsbRequest> getReadQueueRequests(UsbSerialPort serialPort) { | ||||||
|  |         CommonUsbSerialPort commonSerialPort = (CommonUsbSerialPort) serialPort; | ||||||
|  |         return commonSerialPort.mReadQueueRequests; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static void setReadQueueRequestSupplier(UsbSerialPort serialPort, UsbUtils.Supplier<UsbRequest> supplier) { | ||||||
|  |         CommonUsbSerialPort commonSerialPort = (CommonUsbSerialPort) serialPort; | ||||||
|  |         commonSerialPort.mUsbRequestSupplier = supplier; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -41,7 +41,7 @@ public class UsbWrapper implements SerialInputOutputManager.Listener { | |||||||
|     public final static int     USB_WRITE_WAIT = 500; |     public final static int     USB_WRITE_WAIT = 500; | ||||||
|     private static final String TAG = UsbWrapper.class.getSimpleName(); |     private static final String TAG = UsbWrapper.class.getSimpleName(); | ||||||
| 
 | 
 | ||||||
|     public enum OpenCloseFlags { NO_IOMANAGER_THREAD, NO_IOMANAGER_START, NO_CONTROL_LINE_INIT, NO_DEVICE_CONNECTION }; |     public enum OpenCloseFlags { NO_IOMANAGER_THREAD, NO_IOMANAGER_READQUEUE, NO_IOMANAGER_START, NO_CONTROL_LINE_INIT, NO_DEVICE_CONNECTION }; | ||||||
| 
 | 
 | ||||||
|     // constructor |     // constructor | ||||||
|     final Context context; |     final Context context; | ||||||
| @ -198,7 +198,7 @@ public class UsbWrapper implements SerialInputOutputManager.Listener { | |||||||
|                 serialPort.close(); |                 serialPort.close(); | ||||||
|             } catch (Exception ignored) { |             } catch (Exception ignored) { | ||||||
|             } |             } | ||||||
|             //usbSerialPort = null; |             ((CommonUsbSerialPort)serialPort).setReadQueue(0, 0); | ||||||
|         } |         } | ||||||
|         if(!flags.contains(OpenCloseFlags.NO_DEVICE_CONNECTION)) { |         if(!flags.contains(OpenCloseFlags.NO_DEVICE_CONNECTION)) { | ||||||
|             deviceConnection = null; // closed in usbSerialPort.close() |             deviceConnection = null; // closed in usbSerialPort.close() | ||||||
| @ -233,6 +233,8 @@ public class UsbWrapper implements SerialInputOutputManager.Listener { | |||||||
|         } |         } | ||||||
|         if(!flags.contains(OpenCloseFlags.NO_IOMANAGER_THREAD)) { |         if(!flags.contains(OpenCloseFlags.NO_IOMANAGER_THREAD)) { | ||||||
|             ioManager = new SerialInputOutputManager(serialPort, this); |             ioManager = new SerialInputOutputManager(serialPort, this); | ||||||
|  |             if(flags.contains(OpenCloseFlags.NO_IOMANAGER_READQUEUE)) | ||||||
|  |                 ioManager.setReadQueue(0); | ||||||
|             if(!flags.contains(OpenCloseFlags.NO_IOMANAGER_START)) |             if(!flags.contains(OpenCloseFlags.NO_IOMANAGER_START)) | ||||||
|                 ioManager.start(); |                 ioManager.start(); | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -14,10 +14,13 @@ import android.os.Build; | |||||||
| import android.util.Log; | import android.util.Log; | ||||||
| 
 | 
 | ||||||
| import com.hoho.android.usbserial.util.MonotonicClock; | import com.hoho.android.usbserial.util.MonotonicClock; | ||||||
|  | import com.hoho.android.usbserial.util.UsbUtils; | ||||||
| 
 | 
 | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.nio.ByteBuffer; | import java.nio.ByteBuffer; | ||||||
| import java.util.EnumSet; | import java.util.EnumSet; | ||||||
|  | import java.util.LinkedList; | ||||||
|  | import java.util.Objects; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * A base class shared by several driver implementations. |  * A base class shared by several driver implementations. | ||||||
| @ -38,8 +41,12 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | |||||||
|     protected UsbDeviceConnection mConnection; |     protected UsbDeviceConnection mConnection; | ||||||
|     protected UsbEndpoint mReadEndpoint; |     protected UsbEndpoint mReadEndpoint; | ||||||
|     protected UsbEndpoint mWriteEndpoint; |     protected UsbEndpoint mWriteEndpoint; | ||||||
|     protected UsbRequest mUsbRequest; |     protected UsbRequest mReadRequest; | ||||||
|  |     protected LinkedList<UsbRequest> mReadQueueRequests; | ||||||
|  |     private int mReadQueueBufferCount; | ||||||
|  |     private int mReadQueueBufferSize; | ||||||
|     protected FlowControl mFlowControl = FlowControl.NONE; |     protected FlowControl mFlowControl = FlowControl.NONE; | ||||||
|  |     protected UsbUtils.Supplier<UsbRequest> mUsbRequestSupplier = UsbRequest::new; // override for testing | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Internal write buffer. |      * Internal write buffer. | ||||||
| @ -110,6 +117,50 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * for applications doing permanent read() with timeout=0, multiple buffers can be | ||||||
|  |      * used to copy next data from Linux kernel, while the current data is processed. | ||||||
|  |      * @param bufferCount number of buffers to use for readQueue | ||||||
|  |      *                    disabled with 0 | ||||||
|  |      * @param bufferSize size of each buffer | ||||||
|  |      */ | ||||||
|  |     public void setReadQueue(int bufferCount, int bufferSize) { | ||||||
|  |         if (bufferCount < 0) { | ||||||
|  |             throw new IllegalArgumentException("Invalid bufferCount"); | ||||||
|  |         } | ||||||
|  |         if (bufferCount > 0 && bufferSize <= 0) { | ||||||
|  |             throw new IllegalArgumentException("Invalid bufferSize"); | ||||||
|  |         } | ||||||
|  |         if(isOpen()) { | ||||||
|  |             if (bufferCount < mReadQueueBufferCount) { | ||||||
|  |                 throw new IllegalStateException("Cannot reduce bufferCount when port is open"); | ||||||
|  |             } | ||||||
|  |             if (mReadQueueBufferCount != 0 && bufferSize != mReadQueueBufferSize) { | ||||||
|  |                 throw new IllegalStateException("Cannot change bufferSize when port is open"); | ||||||
|  |             } | ||||||
|  |             if (bufferCount > 0) { | ||||||
|  |                 if (mReadQueueRequests == null) { | ||||||
|  |                     mReadQueueRequests = new LinkedList<>(); | ||||||
|  |                 } | ||||||
|  |                 for (int i = mReadQueueRequests.size(); i < bufferCount; i++) { | ||||||
|  |                     ByteBuffer buffer = ByteBuffer.allocate(bufferSize); | ||||||
|  |                     UsbRequest request = mUsbRequestSupplier.get(); | ||||||
|  |                     request.initialize(mConnection, mReadEndpoint); | ||||||
|  |                     request.setClientData(buffer); | ||||||
|  |                     request.queue(buffer, bufferSize); | ||||||
|  |                     mReadQueueRequests.add(request); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         mReadQueueBufferCount = bufferCount; | ||||||
|  |         mReadQueueBufferSize = bufferSize; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public int getReadQueueBufferCount() { return mReadQueueBufferCount; } | ||||||
|  |     public int getReadQueueBufferSize() { return mReadQueueBufferSize; } | ||||||
|  | 
 | ||||||
|  |     private boolean useReadQueue() { return mReadQueueBufferCount != 0; } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void open(UsbDeviceConnection connection) throws IOException { |     public void open(UsbDeviceConnection connection) throws IOException { | ||||||
|         if (mConnection != null) { |         if (mConnection != null) { | ||||||
| @ -125,8 +176,9 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | |||||||
|             if (mReadEndpoint == null || mWriteEndpoint == null) { |             if (mReadEndpoint == null || mWriteEndpoint == null) { | ||||||
|                 throw new IOException("Could not get read & write endpoints"); |                 throw new IOException("Could not get read & write endpoints"); | ||||||
|             } |             } | ||||||
|             mUsbRequest = new UsbRequest(); |             mReadRequest = mUsbRequestSupplier.get(); | ||||||
|             mUsbRequest.initialize(mConnection, mReadEndpoint); |             mReadRequest.initialize(mConnection, mReadEndpoint); | ||||||
|  |             setReadQueue(mReadQueueBufferCount, mReadQueueBufferSize); // fill mReadQueueRequests | ||||||
|             ok = true; |             ok = true; | ||||||
|         } finally { |         } finally { | ||||||
|             if (!ok) { |             if (!ok) { | ||||||
| @ -144,11 +196,19 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | |||||||
|         if (mConnection == null) { |         if (mConnection == null) { | ||||||
|             throw new IOException("Already closed"); |             throw new IOException("Already closed"); | ||||||
|         } |         } | ||||||
|         UsbRequest usbRequest = mUsbRequest; |         UsbRequest readRequest = mReadRequest; | ||||||
|         mUsbRequest = null; |         mReadRequest = null; | ||||||
|         try { |         try { | ||||||
|             usbRequest.cancel(); |             readRequest.cancel(); | ||||||
|         } catch(Exception ignored) {} |         } catch(Exception ignored) {} | ||||||
|  |         if(mReadQueueRequests != null) { | ||||||
|  |             for(UsbRequest readQueueRequest : mReadQueueRequests) { | ||||||
|  |                 try { | ||||||
|  |                     readQueueRequest.cancel(); | ||||||
|  |                 } catch(Exception ignored) {} | ||||||
|  |             } | ||||||
|  |             mReadQueueRequests = null; | ||||||
|  |         } | ||||||
|         try { |         try { | ||||||
|             closeInt(); |             closeInt(); | ||||||
|         } catch(Exception ignored) {} |         } catch(Exception ignored) {} | ||||||
| @ -168,7 +228,7 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected void testConnection(boolean full, String msg) throws IOException { |     protected void testConnection(boolean full, String msg) throws IOException { | ||||||
|         if(mUsbRequest == null) { |         if(mReadRequest == null) { | ||||||
|             throw new IOException("Connection closed"); |             throw new IOException("Connection closed"); | ||||||
|         } |         } | ||||||
|         if(!full) { |         if(!full) { | ||||||
| @ -199,6 +259,9 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | |||||||
|         length = Math.min(length, dest.length); |         length = Math.min(length, dest.length); | ||||||
|         final int nread; |         final int nread; | ||||||
|         if (timeout != 0) { |         if (timeout != 0) { | ||||||
|  |             if(useReadQueue()) { | ||||||
|  |                 throw new IllegalStateException("Cannot use timeout!=0 if readQueue is enabled"); | ||||||
|  |             } | ||||||
|             // bulkTransfer will cause data loss with short timeout + high baud rates + continuous transfer |             // bulkTransfer will cause data loss with short timeout + high baud rates + continuous transfer | ||||||
|             //   https://stackoverflow.com/questions/9108548/android-usb-host-bulktransfer-is-losing-data |             //   https://stackoverflow.com/questions/9108548/android-usb-host-bulktransfer-is-losing-data | ||||||
|             // but mConnection.requestWait(timeout) available since Android 8.0 es even worse, |             // but mConnection.requestWait(timeout) available since Android 8.0 es even worse, | ||||||
| @ -216,15 +279,31 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | |||||||
|                 testConnection(MonotonicClock.millis() < endTime); |                 testConnection(MonotonicClock.millis() < endTime); | ||||||
| 
 | 
 | ||||||
|         } else { |         } else { | ||||||
|             final ByteBuffer buf = ByteBuffer.wrap(dest, 0, length); |             ByteBuffer buf = null; | ||||||
|             if (!mUsbRequest.queue(buf, length)) { |             if(useReadQueue()) { | ||||||
|  |                 if (length != mReadQueueBufferSize) { | ||||||
|  |                     throw new IllegalStateException("Cannot use different length if readQueue is enabled"); | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 buf = ByteBuffer.wrap(dest, 0, length); | ||||||
|  |                 if (!mReadRequest.queue(buf, length)) { | ||||||
|                     throw new IOException("Queueing USB request failed"); |                     throw new IOException("Queueing USB request failed"); | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|             final UsbRequest response = mConnection.requestWait(); |             final UsbRequest response = mConnection.requestWait(); | ||||||
|             if (response == null) { |             if (response == null) { | ||||||
|                 throw new IOException("Waiting for USB request failed"); |                 throw new IOException("Waiting for USB request failed"); | ||||||
|             } |             } | ||||||
|             nread = buf.position(); |             if(useReadQueue()) { | ||||||
|  |                 buf = (ByteBuffer) response.getClientData(); | ||||||
|  |                 System.arraycopy(buf.array(), 0, dest, 0, buf.position()); | ||||||
|  |                 if(mReadRequest != null) { // re-queue if connection not closed | ||||||
|  |                     if (!response.queue(buf, buf.capacity())) { | ||||||
|  |                         throw new IOException("Queueing USB request failed"); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             nread = Objects.requireNonNull(buf).position(); | ||||||
|             // Android error propagation is improvable: |             // Android error propagation is improvable: | ||||||
|             //   response != null & nread == 0 can be: connection lost, buffer to small, ??? |             //   response != null & nread == 0 can be: connection lost, buffer to small, ??? | ||||||
|             if(nread == 0) { |             if(nread == 0) { | ||||||
| @ -297,7 +376,7 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | |||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public boolean isOpen() { |     public boolean isOpen() { | ||||||
|         return mUsbRequest != null; |         return mReadRequest != null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ package com.hoho.android.usbserial.util; | |||||||
| import android.os.Process; | import android.os.Process; | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
| 
 | 
 | ||||||
|  | import com.hoho.android.usbserial.driver.CommonUsbSerialPort; | ||||||
| import com.hoho.android.usbserial.driver.UsbSerialPort; | import com.hoho.android.usbserial.driver.UsbSerialPort; | ||||||
| 
 | 
 | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| @ -33,16 +34,18 @@ public class SerialInputOutputManager { | |||||||
|     public static boolean DEBUG = false; |     public static boolean DEBUG = false; | ||||||
| 
 | 
 | ||||||
|     private static final String TAG = SerialInputOutputManager.class.getSimpleName(); |     private static final String TAG = SerialInputOutputManager.class.getSimpleName(); | ||||||
|     private static final int BUFSIZ = 4096; |     private static final int WRITE_BUFFER_SIZE = 4096; | ||||||
|  |     private static final int READ_QUEUE_BUFFER_COUNT = 4; | ||||||
| 
 | 
 | ||||||
|     private int mReadTimeout = 0; |     private int mReadTimeout = 0; | ||||||
|     private int mWriteTimeout = 0; |     private int mWriteTimeout = 0; | ||||||
|  |     private int mReadQueueBufferCount; // = READ_QUEUE_BUFFER_COUNT | ||||||
| 
 | 
 | ||||||
|     private final Object mReadBufferLock = new Object(); |     private final Object mReadBufferLock = new Object(); | ||||||
|     private final Object mWriteBufferLock = new Object(); |     private final Object mWriteBufferLock = new Object(); | ||||||
| 
 | 
 | ||||||
|     private ByteBuffer mReadBuffer; // default size = getReadEndpoint().getMaxPacketSize() |     private ByteBuffer mReadBuffer; // default size = getReadEndpoint().getMaxPacketSize() | ||||||
|     private ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ); |     private ByteBuffer mWriteBuffer = ByteBuffer.allocate(WRITE_BUFFER_SIZE); | ||||||
| 
 | 
 | ||||||
|     private int mThreadPriority = Process.THREAD_PRIORITY_URGENT_AUDIO; |     private int mThreadPriority = Process.THREAD_PRIORITY_URGENT_AUDIO; | ||||||
|     private final AtomicReference<State> mState = new AtomicReference<>(State.STOPPED); |     private final AtomicReference<State> mState = new AtomicReference<>(State.STOPPED); | ||||||
| @ -65,12 +68,13 @@ public class SerialInputOutputManager { | |||||||
|     public SerialInputOutputManager(UsbSerialPort serialPort) { |     public SerialInputOutputManager(UsbSerialPort serialPort) { | ||||||
|         mSerialPort = serialPort; |         mSerialPort = serialPort; | ||||||
|         mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize()); |         mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize()); | ||||||
|  |         mReadQueueBufferCount = serialPort instanceof CommonUsbSerialPort ? READ_QUEUE_BUFFER_COUNT : 0; | ||||||
|  |         //readQueueBufferSize fixed to getMaxPacketSize() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public SerialInputOutputManager(UsbSerialPort serialPort, Listener listener) { |     public SerialInputOutputManager(UsbSerialPort serialPort, Listener listener) { | ||||||
|         mSerialPort = serialPort; |         this(serialPort); | ||||||
|         mListener = listener; |         mListener = listener; | ||||||
|         mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize()); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public synchronized void setListener(Listener listener) { |     public synchronized void setListener(Listener listener) { | ||||||
| @ -145,6 +149,22 @@ public class SerialInputOutputManager { | |||||||
|         return mWriteBuffer.capacity(); |         return mWriteBuffer.capacity(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Set read queue. Set buffer size before. | ||||||
|  |      * @param bufferCount number of buffers to use for readQueue | ||||||
|  |      *                    disable with value 0, | ||||||
|  |      *                    default enabled as value 4 (READ_QUEUE_BUFFER_COUNT) | ||||||
|  |      */ | ||||||
|  |     public void setReadQueue(int bufferCount) { | ||||||
|  |         if (!(mSerialPort instanceof CommonUsbSerialPort)) { | ||||||
|  |             throw new IllegalArgumentException("only for CommonUsbSerialPort based drivers"); | ||||||
|  |         } | ||||||
|  |         mReadQueueBufferCount = bufferCount; | ||||||
|  |         ((CommonUsbSerialPort) mSerialPort).setReadQueue(getReadQueueBufferCount(), getReadBufferSize()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public int getReadQueueBufferCount() { return mReadQueueBufferCount; } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * write data asynchronously |      * write data asynchronously | ||||||
|      */ |      */ | ||||||
| @ -159,6 +179,9 @@ public class SerialInputOutputManager { | |||||||
|      * start SerialInputOutputManager in separate threads |      * start SerialInputOutputManager in separate threads | ||||||
|      */ |      */ | ||||||
|     public void start() { |     public void start() { | ||||||
|  |         if(mSerialPort instanceof CommonUsbSerialPort) { | ||||||
|  |             ((CommonUsbSerialPort) mSerialPort).setReadQueue(mReadQueueBufferCount, getReadBufferSize()); | ||||||
|  |         } | ||||||
|         if(mState.compareAndSet(State.STOPPED, State.STARTING)) { |         if(mState.compareAndSet(State.STOPPED, State.STARTING)) { | ||||||
|             mStartuplatch = new CountDownLatch(2); |             mStartuplatch = new CountDownLatch(2); | ||||||
|             new Thread(this::runRead, this.getClass().getSimpleName() + "_read").start(); |             new Thread(this::runRead, this.getClass().getSimpleName() + "_read").start(); | ||||||
|  | |||||||
| @ -6,6 +6,11 @@ import java.util.ArrayList; | |||||||
| 
 | 
 | ||||||
| public class UsbUtils { | public class UsbUtils { | ||||||
| 
 | 
 | ||||||
|  |     // copied from java.util.function.Supplier which is not available < API 24 | ||||||
|  |     public interface Supplier<T> { | ||||||
|  |         T get(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private UsbUtils() { |     private UsbUtils() { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -29,5 +34,4 @@ public class UsbUtils { | |||||||
|         return descriptors; |         return descriptors; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -0,0 +1,105 @@ | |||||||
|  | package com.hoho.android.usbserial.driver; | ||||||
|  | 
 | ||||||
|  | import static org.junit.Assert.assertEquals; | ||||||
|  | import static org.junit.Assert.assertNotNull; | ||||||
|  | import static org.junit.Assert.assertNull; | ||||||
|  | import static org.junit.Assert.assertThrows; | ||||||
|  | import static org.mockito.Mockito.mock; | ||||||
|  | import static org.mockito.Mockito.when; | ||||||
|  | 
 | ||||||
|  | import android.hardware.usb.UsbDevice; | ||||||
|  | import android.hardware.usb.UsbDeviceConnection; | ||||||
|  | import android.hardware.usb.UsbEndpoint; | ||||||
|  | import android.hardware.usb.UsbRequest; | ||||||
|  | 
 | ||||||
|  | import org.junit.Test; | ||||||
|  | 
 | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.nio.ByteBuffer; | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.List; | ||||||
|  | 
 | ||||||
|  | public class CommonUsbSerialPortTest { | ||||||
|  | 
 | ||||||
|  |     static class DummySerialDriver implements UsbSerialDriver { | ||||||
|  |         ArrayList<UsbSerialPort> ports = new ArrayList<>(); | ||||||
|  | 
 | ||||||
|  |         DummySerialDriver() { ports.add(new DummySerialPort(null, 0)); } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public UsbDevice getDevice() { return null; } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public List<UsbSerialPort> getPorts() { return ports; } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     static class DummySerialPort extends CommonUsbSerialPort { | ||||||
|  |         public DummySerialPort(UsbDevice device, int portNumber) { | ||||||
|  |             super(device, portNumber); | ||||||
|  |             mUsbRequestSupplier = DummyUsbRequest::new; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         protected void openInt() throws IOException { mReadEndpoint = mWriteEndpoint = mock(UsbEndpoint.class); } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         protected void closeInt() { } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public UsbSerialDriver getDriver() { return null; } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     static class DummyUsbRequest extends UsbRequest { | ||||||
|  |         @Override | ||||||
|  |         public boolean initialize(UsbDeviceConnection connection, UsbEndpoint endpoint) { return true; } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public void setClientData(Object data) { } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public boolean queue(ByteBuffer buffer, int length) { return true; } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void readQueue() throws Exception { | ||||||
|  |         UsbDeviceConnection usbDeviceConnection = mock(UsbDeviceConnection.class); | ||||||
|  |         DummySerialDriver driver = new DummySerialDriver(); | ||||||
|  |         CommonUsbSerialPort port = (CommonUsbSerialPort) driver.getPorts().get(0); | ||||||
|  | 
 | ||||||
|  |         // set before open | ||||||
|  |         port.setReadQueue(0, 0); | ||||||
|  |         port.setReadQueue(0, -1); | ||||||
|  |         assertThrows(IllegalArgumentException.class, () -> port.setReadQueue(-1, 1)); | ||||||
|  |         assertThrows(IllegalArgumentException.class, () -> port.setReadQueue(1, 0)); | ||||||
|  |         port.setReadQueue(2, 256); | ||||||
|  |         assertNull(port.mReadQueueRequests); | ||||||
|  | 
 | ||||||
|  |         // change after open | ||||||
|  |         port.open(usbDeviceConnection); | ||||||
|  |         assertNotNull(port.mReadQueueRequests); | ||||||
|  |         assertEquals(2, port.mReadQueueRequests.size()); | ||||||
|  |         assertThrows(IllegalStateException.class, () -> port.setReadQueue(1, 256)); | ||||||
|  |         port.setReadQueue(3, 256); | ||||||
|  |         assertEquals(3, port.mReadQueueRequests.size()); | ||||||
|  |         assertThrows(IllegalStateException.class, () -> port.setReadQueue(3, 128)); | ||||||
|  |         assertThrows(IllegalStateException.class, () -> port.setReadQueue(3, 512)); | ||||||
|  | 
 | ||||||
|  |         // set after open | ||||||
|  |         port.close(); | ||||||
|  |         port.setReadQueue(0, 0); | ||||||
|  |         port.open(usbDeviceConnection); | ||||||
|  |         assertNull(port.mReadQueueRequests); | ||||||
|  |         port.setReadQueue(3, 256); | ||||||
|  | 
 | ||||||
|  |         // retain over close | ||||||
|  |         port.close(); | ||||||
|  |         assertNull(port.mReadQueueRequests); | ||||||
|  |         port.open(usbDeviceConnection); | ||||||
|  |         assertEquals(3, port.mReadQueueRequests.size()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user