I have created a java app that sets itself up as a USB accessory and sends some data via a bulk transfer.
The Android device should see this and print the data to a TextView. At the moment I have it so that the Android device sees the connection of the accessory and it launches the app the bulk transfer however fails with the following.
org.usb4java.LibUsbException: USB error 5: Bulk write error!: Entity not found
at com.xxx.xxx.AccessoryTest.writeAndRead(AccessoryTest.java:60)
at com.xxx.xxx.AccessoryTest.main(AccessoryTest.java:37)
Any suggestions are welcome!
I have another utility which reports the endpoint information of the Android device as
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 512
bInterval 0
extralen 0
extra:
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x02 EP 2 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 512
bInterval 0
extralen 0
extra:
I'm attempting to write to the 0x02 endpoint
Android code
public class MainActivity extends AppCompatActivity implements Runnable {
private UsbManager manager;
private UsbAccessory accessory;
private ParcelFileDescriptor accessoryFileDescriptor;
private FileInputStream accessoryInput;
private FileOutputStream accessoryOutput;
private TextView questionTV;
private final BroadcastReceiver usbBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (UsbManager.ACTION_USB_ACCESSORY_ATTACHED.equals(action)) {
synchronized (this) {
accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
}
} else if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {
UsbAccessory accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
if (accessory != null) {
// call your method that cleans up and closes communication with the accessory
try {
accessoryFileDescriptor.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
manager = (UsbManager) getSystemService(Context.USB_SERVICE);
IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
registerReceiver(usbBroadcastReceiver, filter);
if (getLastNonConfigurationInstance() != null)
{
accessory = (UsbAccessory) getLastNonConfigurationInstance();
openAccessory();
}
setContentView(R.layout.activity_main);
}
private void openAccessory() {
accessoryFileDescriptor = manager.openAccessory(accessory);
if(accessoryFileDescriptor != null) {
FileDescriptor fd = accessoryFileDescriptor.getFileDescriptor();
accessoryInput = new FileInputStream(fd);
accessoryOutput = new FileOutputStream(fd);
Thread thread = new Thread(null, this, "AccessoryThread");
thread.start();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void run() {
int ret = 0;
byte[] buffer = new byte[51];
int bufferUsed = 0;
while(ret >= 0) {
try {
ret = accessoryInput.read(buffer);
} catch (IOException e) {
Log.e("MainActivity", "Exception in USB accessory input reading", e);
break;
}
}
String question = new String(buffer);
LinearLayout layout= (LinearLayout) findViewById(R.id.layout);
questionTV = new TextView(this);
questionTV.setText(question);
layout.addView(questionTV);
}
}
Java app code
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import org.usb4java.BufferUtils;
import org.usb4java.Context;
import org.usb4java.Device;
import org.usb4java.DeviceDescriptor;
import org.usb4java.DeviceHandle;
import org.usb4java.DeviceList;
import org.usb4java.LibUsb;
import org.usb4java.LibUsbException;
public class AccessoryTest {
private final static byte REQUEST_TYPE_READ = (byte) 0xC0;
private final static byte END_POINT_IN = (byte) 0x81;
private final static byte END_POINT_OUT = (byte) 0x02;
private final static short NEXUS7_VENDORID = (short) 0x18D1;
private final static short NEXUS7_PRODUCTID = (short) 0x4EE1;
private static Context context;
private static Device device;
private static DeviceHandle handle;
public static void main(String[] args) {
try {
init();
int result = setupAccessory("PCHost", "PCHost1", "Description", "1.0", "http://www.mycompany.com",
"SerialNumber");
if (result != LibUsb.SUCCESS)
throw new LibUsbException("Unable to setup Accessory", result);
writeAndRead();
} catch (Exception e) {
e.printStackTrace();
} finally {
LibUsb.releaseInterface(handle, 0);
LibUsb.resetDevice(handle);
LibUsb.close(handle);
LibUsb.exit(context);
}
}
private static void writeAndRead() {
String question = "Hello Android I'll be your host today, how are you?";
byte[] questionBuffer = question.getBytes();
ByteBuffer questionData = BufferUtils.allocateByteBuffer(questionBuffer.length);
IntBuffer transferred = IntBuffer.allocate(1);
int result = 0;
//THIS IS THE PART WHERE IT FAILS!
result = LibUsb.bulkTransfer(handle, END_POINT_OUT, questionData, transferred, 5000);
if(result < 0) {
throw new LibUsbException("Bulk write error!", result);
}
}
private static void init() {
context = new Context();
int result = LibUsb.init(context);
if (result != LibUsb.SUCCESS)
throw new LibUsbException("Unable to initialize libusb.", result);
device = findDevice(NEXUS7_VENDORID);
handle = new DeviceHandle();
result = LibUsb.open(device, handle);
if (result < 0)
throw new LibUsbException("Unable to open USB device", result);
result = LibUsb.claimInterface(handle, 0);
if (result != LibUsb.SUCCESS)
throw new LibUsbException("Unable to claim interface", result);
}
private static Device findDevice(short vendorId) {
// Read the USB device list
DeviceList list = new DeviceList();
int result = LibUsb.getDeviceList(null, list);
if (result < 0)
throw new LibUsbException("Unable to get device list", result);
try {
// Iterate over all devices and scan for the right one
for (Device device : list) {
DeviceDescriptor descriptor = new DeviceDescriptor();
result = LibUsb.getDeviceDescriptor(device, descriptor);
if (result != LibUsb.SUCCESS)
throw new LibUsbException("Unable to read device descriptor", result);
if (descriptor.idVendor() == vendorId)
return device;
}
} finally {
// Ensure the allocated device list is freed
LibUsb.freeDeviceList(list, true);
}
// Device not found
return null;
}
private static int setupAccessory(String vendor, String model, String description, String version, String url,
String serial) throws LibUsbException {
int response = 0;
// Setup setup token
response = transferSetupPacket((short) 2, REQUEST_TYPE_READ, (byte) 51);
// Setup data packet
response = transferAccessoryDataPacket(vendor, (short) 0);
response = transferAccessoryDataPacket(model, (short) 1);
response = transferAccessoryDataPacket(description, (short) 2);
response = transferAccessoryDataPacket(version, (short) 3);
response = transferAccessoryDataPacket(url, (short) 4);
response = transferAccessoryDataPacket(serial, (short) 5);
// Setup handshake packet
response = transferSetupPacket((short) 0, (byte) (LibUsb.REQUEST_TYPE_VENDOR | LibUsb.ENDPOINT_OUT), (byte) 53);
LibUsb.releaseInterface(handle, 0);
return response;
}
private static int transferSetupPacket(short bufferLength, byte requestType, byte request) throws LibUsbException {
int response = 0;
byte[] bytebuff = new byte[bufferLength];
ByteBuffer data = BufferUtils.allocateByteBuffer(bytebuff.length);
data.put(bytebuff);
final short wValue = 0;
final short wIndex = 0;
final long timeout = 1000;
data.rewind();
response = LibUsb.controlTransfer(handle, requestType, request, wValue, wIndex,
data, timeout);
if(response < 0)
throw new LibUsbException("Unable to transfer setup packet ", response);
return response;
}
private static int transferAccessoryDataPacket(String param, short index) {
int response;
byte[] byteArray = param.getBytes();
ByteBuffer data = BufferUtils.allocateByteBuffer(byteArray.length);
data.put(byteArray);
final byte bRequest = (byte) 52;
final short wValue = 0;
final long timeout = 0;
response = LibUsb.controlTransfer(handle, LibUsb.REQUEST_TYPE_VENDOR, bRequest, wValue, index,
data, timeout);
if(response < 0)
throw new LibUsbException("Unable to control transfer.", response);
return response;
}
}
AndroidManifest.xml
<uses-feature android:name="android.hardware.usb.accessory" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<uses-library android:name="com.android.future.usb.accessory" />
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
</intent-filter>
<meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
android:resource="@xml/accessory_filter" />
</activity>
</application>
The issue was according to the AOP standards once an Android device has entered Accessory mode it's VID and PID change
Therefore after setting up the accessory the device handle and the interface need to be reclaimed.