5
5
import android .content .Context ;
6
6
import android .content .Intent ;
7
7
import android .content .IntentFilter ;
8
+ import android .hardware .usb .UsbConstants ;
8
9
import android .hardware .usb .UsbDevice ;
9
10
import android .hardware .usb .UsbDeviceConnection ;
10
11
import android .hardware .usb .UsbEndpoint ;
16
17
import android .util .Log ;
17
18
import android .widget .Toast ;
18
19
20
+ import androidx .annotation .NonNull ;
21
+
19
22
import java .io .IOException ;
23
+ import java .nio .charset .StandardCharsets ;
20
24
21
25
/**
22
26
* Usb host mode communications
23
27
*/
24
28
public class UsbConnection extends BroadcastReceiver {
25
29
private static final String TAG = "smallbasic" ;
26
30
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 ;
31
32
32
- private final UsbDeviceConnection _connection ;
33
- private final UsbInterface _usbInterface ;
34
33
private final UsbDevice _usbDevice ;
35
34
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 ;
38
41
39
42
/**
40
43
* Constructs a new UsbConnection
@@ -43,29 +46,124 @@ public UsbConnection(Context context, int vendorId) throws IOException {
43
46
_usbManager = getUsbManager (context );
44
47
_usbDevice = getDevice (_usbManager , vendorId );
45
48
if (_usbDevice == null ) {
46
- throw new IOException ( USB_DEVICE_NOT_FOUND );
49
+ throw getIoException ( context , R . string . USB_DEVICE_NOT_FOUND );
47
50
}
48
51
49
52
if (!_usbManager .hasPermission (_usbDevice )) {
50
53
requestPermission (context );
51
- throw new IOException ( PERMISSION_ERROR );
54
+ throw getIoException ( context , R . string . PERMISSION_ERROR );
52
55
}
53
56
54
- _usbInterface = _usbDevice .getInterface (0 );
55
- _endpointIn = _usbInterface .getEndpoint (0 );
56
- _endpointOut = _usbInterface .getEndpoint (1 );
57
57
_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
+ }
59
146
}
60
147
61
148
/**
62
149
* Closes the USB connection
63
150
*/
64
151
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
+ }
67
159
_connection .close ();
160
+ _connection = null ;
68
161
}
162
+
163
+ _dataInterface = null ;
164
+ _controlInterface = null ;
165
+ _endpointIn = null ;
166
+ _endpointOut = null ;
69
167
}
70
168
71
169
/**
@@ -77,7 +175,7 @@ public void onReceive(Context context, Intent intent) {
77
175
if (ACTION_USB_PERMISSION .equals (intent .getAction ())) {
78
176
boolean permitted = _usbManager .hasPermission (_usbDevice );
79
177
String name = _usbDevice .getProductName ();
80
- final String message = "USB connection [" + name + "] access " + (permitted ? "permitted" : "denied" );
178
+ final String message = name + " access " + (permitted ? "permitted" : "denied" );
81
179
final BroadcastReceiver receiver = this ;
82
180
new Handler (Looper .getMainLooper ()).post (() -> {
83
181
Toast .makeText (context , message , Toast .LENGTH_LONG ).show ();
@@ -91,10 +189,10 @@ public void onReceive(Context context, Intent intent) {
91
189
*/
92
190
public String receive () {
93
191
String result ;
94
- byte [] dataIn = new byte [BUFFER_SIZE ];
192
+ byte [] dataIn = new byte [_bufferSize ];
95
193
int bytesRead = _connection .bulkTransfer (_endpointIn , dataIn , dataIn .length , TIMEOUT_MILLIS );
96
194
if (bytesRead > 0 ) {
97
- result = new String (dataIn , 0 , bytesRead );
195
+ result = new String (dataIn , 0 , bytesRead , StandardCharsets . UTF_8 );
98
196
} else {
99
197
result = "" ;
100
198
}
@@ -104,8 +202,29 @@ public String receive() {
104
202
/**
105
203
* Sends the given data to the usb connection
106
204
*/
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 ;
109
228
}
110
229
111
230
/**
@@ -122,6 +241,11 @@ private UsbDevice getDevice(UsbManager usbManager, int vendorId) {
122
241
return result ;
123
242
}
124
243
244
+ @ NonNull
245
+ private static IOException getIoException (Context context , int resourceId ) {
246
+ return new IOException (context .getResources ().getString (resourceId ));
247
+ }
248
+
125
249
/**
126
250
* Returns the UsbManager
127
251
*/
0 commit comments