実際の「アクセサリ」を必要とせずに API を開発できるようにするために、PC を Android 2.3.4 デバイスの USB ホストにしようとしています。これを行うには、PC を USB ホストおよび「デバイス」として確立する必要があります (私の場合は 2.3.4 を実行している Nexus One)。
PC 側のベースとしてhttp://android.serverbox.ch/のlibusb コードから始め、Android 側の DemoKit コードと Android ドキュメントから始めました。
この 2 つは接続を正常にネゴシエートしているように見え、インターフェイスは「要求」されますが、実際に一括転送しようとすると停止します。OSX ではエラーは -5 (LIBUSB_ERROR_NOT_FOUND) であり、Ubuntu Linux (root として) ではエラーは -1 (LIBUSB_ERROR_IO) です。(最新の libusb 1.0.8 を使用したそれぞれの最新リリース)。
これがコードです。問題に関するその他のコメントは大歓迎ですが、これは主に概念実証であるため、一括転送が機能しない理由を実際に探しています。
#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
Android アプリは次のとおりです。
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;
}
}
}
そしてマニフェスト(ドキュメントによると、デバイスを自動関連付けおよび自動パーマするためのインテントフィルタリングを含む):
<?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>