I'm trying to make my PC the USB Host for Android 2.3.4 devices in order to be able to develop APIs without needing actual "accessories". To do this, I need to establish the PC as the USB Host and the "device" (in my case a Nexus One running 2.3.4).
I started with libusb code from http://android.serverbox.ch/ as a base for the PC side and the DemoKit code and Android documentation on the Android side.
The two seems to negotiate the connection fine, and the interface gets "claimed" but dies on the actual attempt at bulk transfer. On OSX, the error is -5 (LIBUSB_ERROR_NOT_FOUND) and on Ubuntu Linux (as root) the error is -1 (LIBUSB_ERROR_IO). (Most recent releases of each with most recent libusb 1.0.8).
Here's the code. Other comments on problems welcome, though this is mostly a proof-of-concept, so I'm really just looking for the reason the bulk transfer isn't working:
#include <stdio.h>
#include <libusb.h>
#include <string.h>
#define ENDPOINT_BULK_IN 0x83
#define ENDPOINT_BULK_OUT 0x03 // Have tried 0x00, 0x01 and 0x02
#define VID 0x18D1
#define PID 0x4E11
#define ACCESSORY_PID 0x2D00
#define ACCESSORY_ADB_PID 0x2D01 // Can't get this to work, if ADB is active, can't get handle on device
/*
ON OSX
gcc adktest.c -I/usr/local/include -o adktest -lusb-1.0.0 -I/usr/local/include -I/usr/local/include/libusb-1.0
ON UBUNTU
gcc adktest.c -I/usr/include -o adktest -lusb-1.0 -I/usr/include -I/usr/include/libusb-1.0
Testing on Nexus One with Gingerbread 2.3.4
*/
static int transferTest();
static int init(void);
static int shutdown(void);
static void error(int code);
static void status(int code);
static int setupAccessory(
const char* manufacturer,
const char* modelName,
const char* description,
const char* version,
const char* uri,
const char* serialNumber);
//static
static struct libusb_device_handle* handle;
int main (int argc, char *argv[]){
if(init() < 0)
return;
if(setupAccessory(
"PCHost",
"PCHost1",
"Description",
"1.0",
"http://www.mycompany.com",
"SerialNumber") < 0){
fprintf(stdout, "Error setting up accessory\n");
shutdown();
return -1;
};
if(transferTest() < 0){
fprintf(stdout, "Error in transferTest\n");
shutdown();
return -1;
}
shutdown();
fprintf(stdout, "Finished\n");
return 0;
}
static int transferTest(){
// TEST BULK IN/OUT
const static int PACKET_BULK_LEN=64;
const static int TIMEOUT=5000;
int r,i;
int transferred;
char answer[PACKET_BULK_LEN];
char question[PACKET_BULK_LEN];
for (i=0;i<PACKET_BULK_LEN; i++) question[i]=i;
// ***FAILS HERE***
r = libusb_bulk_transfer(handle, ENDPOINT_BULK_OUT, question, PACKET_BULK_LEN,
&transferred,TIMEOUT);
if (r < 0) {
fprintf(stderr, "Bulk write error %d\n", r);
error(r);
return r;
}
fprintf(stdout, "Wrote %d bytes", r);
r = libusb_bulk_transfer(handle, ENDPOINT_BULK_IN, answer,PACKET_BULK_LEN,
&transferred, TIMEOUT);
if (r < 0) {
fprintf(stderr, "Bulk read error %d\n", r);
error(r);
return r;
}
fprintf(stdout, "Read %d bytes", r);
if (transferred < PACKET_BULK_LEN) {
fprintf(stderr, "Bulk transfer short read (%d)\n", r);
error(r);
return -1;
}
printf("Bulk Transfer Loop Test Result:\n");
// for (i=0;i< PACKET_BULK_LEN;i++) printf("%i, %i,\n ",question[i],answer[i]);
for(i = 0;i < PACKET_BULK_LEN; i++) {
if(i%8 == 0)
printf("\n");
printf("%02x, %02x; ",question[i],answer[i]);
}
printf("\n\n");
return 0;
}
static int init(){
libusb_init(NULL);
if((handle = libusb_open_device_with_vid_pid(NULL, VID, PID)) == NULL){
fprintf(stdout, "Problem acquiring handle\n");
return -1;
}
libusb_claim_interface(handle, 0);
return 0;
}
static int shutdown(){
if(handle != NULL)
libusb_release_interface (handle, 0);
libusb_exit(NULL);
return 0;
}
static int setupAccessory(
const char* manufacturer,
const char* modelName,
const char* description,
const char* version,
const char* uri,
const char* serialNumber){
unsigned char ioBuffer[2];
int devVersion;
int response;
response = libusb_control_transfer(
handle, //handle
0xC0, //bmRequestType
51, //bRequest
0, //wValue
0, //wIndex
ioBuffer, //data
2, //wLength
0 //timeout
);
if(response < 0){error(response);return-1;}
devVersion = ioBuffer[1] << 8 | ioBuffer[0];
fprintf(stdout,"Version Code Device: %d\n", devVersion);
usleep(1000);//sometimes hangs on the next transfer :(
response = libusb_control_transfer(handle,0x40,52,0,0,(char*)manufacturer,strlen(manufacturer),0);
if(response < 0){error(response);return -1;}
response = libusb_control_transfer(handle,0x40,52,0,1,(char*)modelName,strlen(modelName)+1,0);
if(response < 0){error(response);return -1;}
response = libusb_control_transfer(handle,0x40,52,0,2,(char*)description,strlen(description)+1,0);
if(response < 0){error(response);return -1;}
response = libusb_control_transfer(handle,0x40,52,0,3,(char*)version,strlen(version)+1,0);
if(response < 0){error(response);return -1;}
response = libusb_control_transfer(handle,0x40,52,0,4,(char*)uri,strlen(uri)+1,0);
if(response < 0){error(response);return -1;}
response = libusb_control_transfer(handle,0x40,52,0,5,(char*)serialNumber,strlen(serialNumber)+1,0);
if(response < 0){error(response);return -1;}
fprintf(stdout,"Accessory Identification sent\n", devVersion);
response = libusb_control_transfer(handle,0x40,53,0,0,NULL,0,0);
if(response < 0){error(response);return -1;}
fprintf(stdout,"Attempted to put device into accessory mode\n", devVersion);
if(handle != NULL)
libusb_release_interface (handle, 0);
int tries = 4;
for(;;){
tries--;
if((handle = libusb_open_device_with_vid_pid(NULL, VID, ACCESSORY_PID)) == NULL){
if(tries < 0){
return -1;
}
}else{
break;
}
sleep(1);
}
libusb_claim_interface(handle, 0);
fprintf(stdout, "Interface claimed, ready to transfer data\n");
return 0;
}
// error reporting function left out for brevity
And here's the Android app:
package com.cengen.android.pchost;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import com.android.future.usb.UsbAccessory;
import com.android.future.usb.UsbManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class MainActivity extends Activity implements Runnable
{
private final Logger logger = LoggerFactory.getLogger("PCHost");
private UsbManager usbManager;
UsbAccessory accessory;
ParcelFileDescriptor accessoryFileDescriptor;
FileInputStream accessoryInput;
FileOutputStream accessoryOutput;
private final BroadcastReceiver usbBroadcastReceiver = new BroadcastReceiver()
{
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
if (UsbManager.ACTION_USB_ACCESSORY_ATTACHED.equals(action))
{
synchronized (this)
{
accessory = UsbManager.getAccessory(intent);
}
}
else if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action))
{
UsbAccessory accessory = UsbManager.getAccessory(intent);
if (accessory != null)
{
// call your method that cleans up and closes communication with the accessory
}
}
}
};
Handler messageHandler = new Handler()
{
@Override
public void handleMessage(Message msg)
{
switch (msg.what)
{
case 1:
logger.info("Got message type {}", msg.what);
// SendFileMessage m = (SendFileMessage) msg.obj;
// handleSendFile(m);
break;
case 2:
logger.info("Got message type {}", msg.what);
// SendFileMessage m = (SendFileMessage) msg.obj;
// handleSendFile(m);
break;
case 3:
logger.info("Got message type {}", msg.what);
// SendFileMessage m = (SendFileMessage) msg.obj;
// handleSendFile(m);
break;
}
}
};
/**
* Main USB reading loop, processing incoming data from accessory and parsing
* it into messages via the defined format.
*/
public void run()
{
int ret = 0;
byte[] buffer = new byte[16384];
int i;
while (ret >= 0)
{
try
{
ret = accessoryInput.read(buffer);
logger.debug("Read {} bytes.", ret);
}
catch (IOException e)
{
logger.debug("Exception in USB accessory input reading", e);
break;
}
i = 0;
while (i < ret)
{
int len = ret - i;
switch (buffer[i])
{
case 0x1:
if (len >= 3)
{
Message m = Message.obtain(messageHandler, 1);
// m.obj = new MessageTypeOne(buffer[i + 1], buffer[i + 2]);
messageHandler.sendMessage(m);
}
i += 3;
break;
case 0x4:
if (len >= 3)
{
Message m = Message.obtain(messageHandler, 1);
// m.obj = new MessageTypeTwo(buffer[i + 1], buffer[i + 2]);
messageHandler.sendMessage(m);
}
i += 3;
break;
default:
logger.debug("unknown msg: " + buffer[i]);
i = len;
break;
}
}
}
}
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
usbManager = UsbManager.getInstance(this);
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(accessory);
}
setContentView(R.layout.main);
}
@Override
public Object onRetainNonConfigurationInstance()
{
return accessory != null ? accessory : super.onRetainNonConfigurationInstance();
}
@Override
public void onResume()
{
super.onResume();
Intent intent = getIntent();
if (accessoryInput != null && accessoryOutput != null)
return;
// TODO: verify, docs don't do this simple thing, not sure why?
UsbAccessory accessory = UsbManager.getAccessory(intent);
if (accessory != null)
openAccessory(accessory);
else
logger.error("Failed to resume accessory.");
}
@Override
public void onPause()
{
super.onPause();
closeAccessory();
}
@Override
public void onDestroy()
{
unregisterReceiver(usbBroadcastReceiver);
super.onDestroy();
}
private void openAccessory(UsbAccessory accessory)
{
accessoryFileDescriptor = usbManager.openAccessory(accessory);
if (accessoryFileDescriptor != null)
{
this.accessory = accessory;
FileDescriptor fd = accessoryFileDescriptor.getFileDescriptor();
accessoryInput = new FileInputStream(fd);
accessoryOutput = new FileOutputStream(fd);
Thread thread = new Thread(null, this, "AndroidPCHost");
thread.start();
logger.debug("accessory opened");
// TODO: enable USB operations in the app
}
else
{
logger.debug("accessory open fail");
}
}
private void closeAccessory()
{
// TODO: disable USB operations in the app
try
{
if (accessoryFileDescriptor != null)
accessoryFileDescriptor.close();
}
catch (IOException e)
{}
finally
{
accessoryFileDescriptor = null;
accessory = null;
}
}
}
And the manifest (including the intent-filtering so it auto-associates and auto-perms the device, according to the docs):
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mycompany.android.pchost"
android:versionCode="1"
android:versionName="1.0">
<uses-feature android:name="android.hardware.usb.accessory" />
<uses-sdk android:minSdkVersion="10" />
<application android:label="@string/app_name" android:icon="@drawable/icon">
<uses-library android:name="com.android.future.usb.accessory" />
<activity android:name="MainActivity"
android:label="@string/app_name"
android:launchMode="singleInstance">
<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>
</manifest>
[composite_submit_bulk_transfer] unable to match endpoint to an open interface - cancelling transfer
, second Q if u don't mind: Do I have to claim interface ? - Thanks.. – Alboran