55import android .content .Context ;
66import android .content .Intent ;
77import android .content .IntentFilter ;
8+ import android .hardware .usb .UsbConstants ;
89import android .hardware .usb .UsbDevice ;
910import android .hardware .usb .UsbDeviceConnection ;
1011import android .hardware .usb .UsbEndpoint ;
1617import android .util .Log ;
1718import android .widget .Toast ;
1819
20+ import androidx .annotation .NonNull ;
21+
1922import java .io .IOException ;
23+ import java .nio .charset .StandardCharsets ;
2024
2125/**
2226 * Usb host mode communications
2327 */
2428public class UsbConnection extends BroadcastReceiver {
2529 private static final String TAG = "smallbasic" ;
2630 private static final String ACTION_USB_PERMISSION = "smallbasic.android.USB_PERMISSION" ;
27- private static final String PERMISSION_ERROR = "Not permitted" ;
28- private static final String USB_DEVICE_NOT_FOUND = "Usb device not found" ;
29- private static final int TIMEOUT_MILLIS = 1000 ;
30- private static final int BUFFER_SIZE = 64 ;
31+ private static final int TIMEOUT_MILLIS = 5000 ;
3132
32- private final UsbDeviceConnection _connection ;
33- private final UsbInterface _usbInterface ;
3433 private final UsbDevice _usbDevice ;
3534 private final UsbManager _usbManager ;
36- private final UsbEndpoint _endpointIn ;
37- private final UsbEndpoint _endpointOut ;
35+ private UsbDeviceConnection _connection ;
36+ private UsbInterface _dataInterface ;
37+ private UsbInterface _controlInterface ;
38+ private UsbEndpoint _endpointIn ;
39+ private UsbEndpoint _endpointOut ;
40+ private final int _bufferSize ;
3841
3942 /**
4043 * Constructs a new UsbConnection
@@ -43,29 +46,124 @@ public UsbConnection(Context context, int vendorId) throws IOException {
4346 _usbManager = getUsbManager (context );
4447 _usbDevice = getDevice (_usbManager , vendorId );
4548 if (_usbDevice == null ) {
46- throw new IOException ( USB_DEVICE_NOT_FOUND );
49+ throw getIoException ( context , R . string . USB_DEVICE_NOT_FOUND );
4750 }
4851
4952 if (!_usbManager .hasPermission (_usbDevice )) {
5053 requestPermission (context );
51- throw new IOException ( PERMISSION_ERROR );
54+ throw getIoException ( context , R . string . PERMISSION_ERROR );
5255 }
5356
54- _usbInterface = _usbDevice .getInterface (0 );
55- _endpointIn = _usbInterface .getEndpoint (0 );
56- _endpointOut = _usbInterface .getEndpoint (1 );
5757 _connection = _usbManager .openDevice (_usbDevice );
58- _connection .claimInterface (_usbInterface , true );
58+ if (_connection == null ) {
59+ throw getIoException (context , R .string .USB_CONNECTION_ERROR );
60+ }
61+
62+ // Find the CDC interfaces and endpoints
63+ UsbInterface controlInterface = null ;
64+ UsbInterface dataInterface = null ;
65+ UsbEndpoint endpointIn = null ;
66+ UsbEndpoint endpointOut = null ;
67+
68+ // Look for CDC control interface
69+ for (int i = 0 ; i < _usbDevice .getInterfaceCount (); i ++) {
70+ UsbInterface anInterface = _usbDevice .getInterface (i );
71+ Log .i (TAG , "Interface " + i + " - Class: " + anInterface .getInterfaceClass ());
72+ if (anInterface .getInterfaceClass () == UsbConstants .USB_CLASS_COMM ) {
73+ controlInterface = anInterface ;
74+ } else if (anInterface .getInterfaceClass () == UsbConstants .USB_CLASS_CDC_DATA ) {
75+ dataInterface = anInterface ;
76+ for (int j = 0 ; j < anInterface .getEndpointCount (); j ++) {
77+ UsbEndpoint ep = anInterface .getEndpoint (j );
78+ Log .i (TAG , " Endpoint " + j + " - Type: " + ep .getType () +
79+ ", Direction: " + ep .getDirection ());
80+ if (ep .getType () == UsbConstants .USB_ENDPOINT_XFER_BULK ) {
81+ if (ep .getDirection () == UsbConstants .USB_DIR_IN ) {
82+ endpointIn = ep ;
83+ } else {
84+ endpointOut = ep ;
85+ }
86+ }
87+ }
88+ }
89+ }
90+
91+ if (controlInterface == null ) {
92+ throw getIoException (context , R .string .USB_CONTROL_NOT_FOUND );
93+ }
94+ if (endpointIn == null ) {
95+ throw getIoException (context , R .string .ENDPOINT_IN_ERROR );
96+ }
97+ if (endpointOut == null ) {
98+ throw getIoException (context , R .string .ENDPOINT_OUT_ERROR );
99+ }
100+
101+ _endpointIn = endpointIn ;
102+ _endpointOut = endpointOut ;
103+ _controlInterface = controlInterface ;
104+ _dataInterface = dataInterface ;
105+ _bufferSize = calcBufferSize (_endpointIn );
106+
107+ // Claim interfaces
108+ if (!_connection .claimInterface (controlInterface , true )) {
109+ throw getIoException (context , R .string .USB_CONTROL_NOT_CLAIMED );
110+ }
111+ if (!_connection .claimInterface (dataInterface , true )) {
112+ throw getIoException (context , R .string .USB_DATA_NOT_CLAIMED );
113+ }
114+
115+ // Set line coding (19200 baud, 8N1)
116+ byte [] lineCoding = new byte [] {
117+ (byte ) 0x00 , // 19200 baud rate (little endian)
118+ (byte ) 0x4B ,
119+ 0 , 0 ,
120+ 0 , // 1 stop bit
121+ 0 , // No parity
122+ 8 // 8 data bits
123+ };
124+
125+ if (_connection .controlTransfer (
126+ 0x21 , // bmRequestType (0x21)
127+ 0x20 , // bRequest (SET_LINE_CODING)
128+ 0 , // wValue
129+ _controlInterface .getId (), // wIndex
130+ lineCoding ,
131+ lineCoding .length ,
132+ TIMEOUT_MILLIS ) < 0 ) {
133+ throw getIoException (context , R .string .USB_ENCODING_ERROR );
134+ }
135+
136+ if (_connection .controlTransfer (
137+ 0x21 , // bmRequestType (0x21)
138+ 0x22 , // SET_CONTROL_LINE_STATE
139+ 0x03 , // wValue Enable DTR (bit 0) and RTS (bit 1)
140+ _controlInterface .getId (),
141+ null ,
142+ 0 ,
143+ TIMEOUT_MILLIS ) < 0 ) {
144+ throw getIoException (context , R .string .USB_LINE_STATE_ERROR );
145+ }
59146 }
60147
61148 /**
62149 * Closes the USB connection
63150 */
64151 public void close () {
65- if (_connection != null && _usbInterface != null ) {
66- _connection .releaseInterface (_usbInterface );
152+ if (_connection != null ) {
153+ if (_controlInterface != null ) {
154+ _connection .releaseInterface (_controlInterface );
155+ }
156+ if (_dataInterface != null ) {
157+ _connection .releaseInterface (_dataInterface );
158+ }
67159 _connection .close ();
160+ _connection = null ;
68161 }
162+
163+ _dataInterface = null ;
164+ _controlInterface = null ;
165+ _endpointIn = null ;
166+ _endpointOut = null ;
69167 }
70168
71169 /**
@@ -77,7 +175,7 @@ public void onReceive(Context context, Intent intent) {
77175 if (ACTION_USB_PERMISSION .equals (intent .getAction ())) {
78176 boolean permitted = _usbManager .hasPermission (_usbDevice );
79177 String name = _usbDevice .getProductName ();
80- final String message = "USB connection [" + name + "] access " + (permitted ? "permitted" : "denied" );
178+ final String message = name + " access " + (permitted ? "permitted" : "denied" );
81179 final BroadcastReceiver receiver = this ;
82180 new Handler (Looper .getMainLooper ()).post (() -> {
83181 Toast .makeText (context , message , Toast .LENGTH_LONG ).show ();
@@ -91,10 +189,10 @@ public void onReceive(Context context, Intent intent) {
91189 */
92190 public String receive () {
93191 String result ;
94- byte [] dataIn = new byte [BUFFER_SIZE ];
192+ byte [] dataIn = new byte [_bufferSize ];
95193 int bytesRead = _connection .bulkTransfer (_endpointIn , dataIn , dataIn .length , TIMEOUT_MILLIS );
96194 if (bytesRead > 0 ) {
97- result = new String (dataIn , 0 , bytesRead );
195+ result = new String (dataIn , 0 , bytesRead , StandardCharsets . UTF_8 );
98196 } else {
99197 result = "" ;
100198 }
@@ -104,8 +202,29 @@ public String receive() {
104202 /**
105203 * Sends the given data to the usb connection
106204 */
107- public void send (byte [] dataOut ) {
108- _connection .bulkTransfer (_endpointOut , dataOut , dataOut .length , TIMEOUT_MILLIS );
205+ public int send (String data ) {
206+ byte [] dataOut = data .getBytes (StandardCharsets .UTF_8 );
207+ return _connection .bulkTransfer (_endpointOut , dataOut , dataOut .length , TIMEOUT_MILLIS );
208+ }
209+
210+ /**
211+ * Calculates the optimum receive buffer size
212+ */
213+ private int calcBufferSize (UsbEndpoint endpointIn ) {
214+ // Get the maximum packet size from the endpoint
215+ int maxPacketSize = endpointIn .getMaxPacketSize ();
216+
217+ // Calculate buffer size as a multiple of the maximum packet size
218+ // 4 is a good multiplier for most applications
219+ int result = maxPacketSize * 4 ;
220+
221+ // Ensure minimum reasonable size
222+ result = Math .max (result , 1024 );
223+
224+ // Optional: Cap at a maximum size if memory is a concern
225+ result = Math .min (result , 16384 ); // 16KB max
226+
227+ return result ;
109228 }
110229
111230 /**
@@ -122,6 +241,11 @@ private UsbDevice getDevice(UsbManager usbManager, int vendorId) {
122241 return result ;
123242 }
124243
244+ @ NonNull
245+ private static IOException getIoException (Context context , int resourceId ) {
246+ return new IOException (context .getResources ().getString (resourceId ));
247+ }
248+
125249 /**
126250 * Returns the UsbManager
127251 */
0 commit comments