AndroidとPCのUSB通信のサンプル
Android で USB 通信を行う為の API が有ったので試してみた。
USB の API は ADK(Accessory Development Kit)と呼ばれている物。
Android がホストになる場合とUSB機器になる場合があるが今回試したのは後者の方で Android が PC の USBデバイスとして認識されるケース。
- Android がホストになるケースは「Nexus7からUSB赤外線リモコンを操る(前編)」をどうぞ。
Android側
API の選択
ADK は android.hardware.usb と com.android.future.usb の2つがある。 この2つは基本的に同じなのだけど後者は Android/2.3.4 向けの互換用。
違いは基本インスタンスの取得方法だけ。
android.hardware.usb:
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
com.android.future.usb:
UsbManager manager = UsbManager.getInstance(this);
UsbAccessory accessory = UsbManager.getAccessory(intent);
今回は android.hardware.usb を使用する。
AndroidManufest.xml の設定
USB 関連の設定を追加して置く。
AndroidManufest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.kotemaru.android.usbsample" android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="12" android:targetSdkVersion="17" />
<application android:allowBackup="true" android:icon="@drawable/ic_launcher"
android:label="@string/app_name" android:theme="@style/AppTheme">
<uses-feature android:name="android.hardware.usb.accessory" />
<activity android:name="org.kotemaru.android.usbsample.MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<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" />
</intent-filter>
</activity>
</application>
</manifest>
res/xml/accessory_filter.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-accessory
manufacturer="kotemaru.org"
model="AdkSample"
version="1.0" />
</resources>
ソース
MainAcrivity.java:
- UsbReceiverとUsbDriverを呼んでいる所以外は普通のActivity。
package org.kotemaru.android.usbsample;
import java.io.IOException;
import android.os.Bundle;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
public class MainActivity extends Activity {
private static final String ACTION_USB_PERMISSION = "org.kotemaru.android.usbsample.USB_PERMISSION";
private UsbDriver usbDriver;
private UsbReceiver usbReceiver;
private EditText editText1;
private EditText editText2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
usbDriver = new UsbDriver(this);
usbReceiver = UsbReceiver.init(this, ACTION_USB_PERMISSION, usbDriver);
editText1 = (EditText) findViewById(R.id.editText1);
editText2 = (EditText) findViewById(R.id.editText2);
Button sendBtn = (Button) findViewById(R.id.sendBtn);
sendBtn.setOnClickListener(new OnClickListener(){
@Override public void onClick(View v) {
String msg = editText1.getText().toString();
try {
usbDriver.send(msg);
String rmsg = usbDriver.receive();
editText2.setText(rmsg);
} catch (IOException e) {
errorDialog(e.getMessage());
}
}
});
Button resetBtn = (Button) findViewById(R.id.resetBtn);
resetBtn.setOnClickListener(new OnClickListener(){
@Override public void onClick(View v) {
restart();
}
});
}
@Override
public void onResume() {
super.onResume();
usbReceiver.resume();
}
@Override
public void onPause() {
super.onPause();
usbReceiver.close();
}
@Override
protected void onDestroy() {
super.onDestroy();
usbReceiver.destroy();
}
protected void restart() {
Intent intent = this.getIntent();
this.finish();
this.startActivity(intent);
}
public void errorDialog(String message) {
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle("Error!");
dialog.setMessage(message);
dialog.show();
}
}
UsbReceiver.java:
- USB の ATTACH/DETTACH イベントを受け取る為の Receiver。
- pause/resume の処理が有るため若干ややこしくなっている.
package org.kotemaru.android.usbsample;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbManager;
import android.util.Log;
public class UsbReceiver extends BroadcastReceiver {
private static final String TAG = "UsbReceiver";
private Activity activity;
private Driver driver;
private final String action_usb_permission;
private UsbManager usbManager;
private UsbAccessory activeAccessory;
private PendingIntent permissionIntent;
private boolean permissionRequestPending = false;
// I/O処理を分離する為のインターフェース。
public interface Driver {
public void openAccessory(UsbAccessory accessory);
public void closeAccessory(UsbAccessory accessory);
}
public static UsbReceiver init(Activity activity, String permissionName, Driver driver) {
UsbReceiver receiver = new UsbReceiver(activity, permissionName, driver);
/* receiver */
IntentFilter filter = new IntentFilter();
filter.addAction(permissionName);
filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
activity.registerReceiver(receiver, filter);
return receiver;
}
public UsbReceiver(Activity activity, String permissionName, Driver driver) {
super();
this.activity = activity;
this.action_usb_permission = permissionName;
this.driver = driver;
this.usbManager = (UsbManager) activity.getSystemService(Context.USB_SERVICE);
this.permissionIntent =
PendingIntent.getBroadcast(activity, 0, new Intent(permissionName), 0);
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action_usb_permission.equals(action)) {
open(intent);
} else if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {
close(intent);
}
}
private synchronized void open(Intent intent) {
UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
driver.openAccessory(accessory);
activeAccessory = accessory;
} else {
Log.d(TAG, "permission denied for accessory " + accessory);
}
permissionRequestPending = false;
}
private synchronized void close(Intent intent) {
UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
if (accessory != null && accessory.equals(activeAccessory)) {
close();
}
}
public void resume() {
UsbAccessory[] accessories = usbManager.getAccessoryList();
UsbAccessory accessory = (accessories == null ? null : accessories[0]);
if (accessory != null) {
if (usbManager.hasPermission(accessory)) {
driver.openAccessory(accessory);
} else {
synchronized (this) {
if (!permissionRequestPending) {
usbManager.requestPermission(accessory, permissionIntent);
permissionRequestPending = true;
}
}
}
} else {
Log.d(TAG, "accessory is null");
}
}
public synchronized void close() {
if (activeAccessory != null) {
driver.closeAccessory(activeAccessory);
activeAccessory = null;
}
}
public void destroy() {
activity.unregisterReceiver(this);
}
}
UsbDriver.java:
- USBへのI/O処理。
- USB接続はFileDescriptor扱いなのでほぼ普通の java.io と変わらない。
package org.kotemaru.android.usbsample;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import android.content.Context;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbManager;
import android.os.ParcelFileDescriptor;
import android.util.Log;
public class UsbDriver implements UsbReceiver.Driver {
private static final String TAG = "UsbDriver";
private UsbManager usbManager;
private ParcelFileDescriptor fileDescriptor;
private FileInputStream usbIn;
private FileOutputStream usbOut;
public UsbDriver(MainActivity activity) {
this.usbManager = (UsbManager) activity.getSystemService(Context.USB_SERVICE);
}
@Override
public void openAccessory(UsbAccessory accessory) {
fileDescriptor = usbManager.openAccessory(accessory);
if (fileDescriptor != null) {
FileDescriptor fd = fileDescriptor.getFileDescriptor();
usbIn = new FileInputStream(fd);
usbOut = new FileOutputStream(fd);
} else {
Log.d(TAG, "accessory open fail");
}
}
@Override
public void closeAccessory(UsbAccessory accessory) {
try {
if (fileDescriptor != null) {
fileDescriptor.close();
}
} catch (IOException e) {
// ignore.
} finally {
fileDescriptor = null;
}
}
public void send(String msg) throws IOException {
usbOut.write(msg.getBytes("UTF-8"));
usbOut.flush();
}
public String receive() throws IOException {
byte[] buff = new byte[1024];
int len = usbIn.read(buff);
return new String(buff,0,len,"UTF-8");
}
}
PC 側
PC 側は Ubuntu,FreeBSD,RaspberryPi で接続を確認している。 libusb を使用するので Windows でも繋がるはず。
libusb の準備
linux系の場合は libusb-1.0 をインストールする。FreeBSD はOS組込なので不要。
$ sudo apt-get install libusb-1.0
libusb で ADK のプロトコルを実装したライブラリ AOA をこちらからお借りしました。 (若干修正が入っています。)
ソース
受け取った文字列を大文字に変換して送り返すだけです。 言語は C++ です。
AdkEcho.cpp:
/**
Android USB connection sample.
@Author kotemru.org
@Licence apache/2.0
*/
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include "AOA/AOA.h"
// USB Connector opend.
AOA acc("kotemaru.org",
"AdkSample",
"Sample for ADK",
"1.0",
"http://blog.kotemaru.org/androidUSBSample",
"000000000000001") ;
/**
* Disconnect USB innterrupt aborted.
*/
void signal_callback_handler(int signum)
{
fprintf(stderr, "\ninterrupt %d\n",signum);
acc.disconnect();
exit(0);
}
static void error(char *msg, int rc) {
fprintf(stderr,"Error(%d,%s): %s\n",rc,strerror(errno),msg);
acc.disconnect();
exit(0);
}
int main(int argc, char *argv[])
{
signal(SIGINT, signal_callback_handler);
signal(SIGTERM, signal_callback_handler);
unsigned char buff[1024];
acc.connect(100);
// Echo back.
while (1) {
int len = acc.read(buff, sizeof(buff), 1000000);
if (len < 0) error("acc.read",len);
buff[len+1] = '\0';
printf("USB>%s\n", buff);
for (int i=0; i<len; i++) buff[i] = buff[i] - 0x20;
acc.write(buff, len, 1000);
}
}
実行結果
先に Android 側アプリを起動して置きます。

USBをPCに接続してから PC 側のプログラムを起動します。 (※root権限が必要です。)
$ sudo ./AdkEcho
VID:18D1, PID:2D01 Class:00
already in accessory mode.
bNumInterfaces: 2
bNumEndpoints: 2
bEndpointAddress: 81, bmAttributes:02
bEndpointAddress: 02, bmAttributes:02
VID:18D1, PID:2D01
Android 側にダイアログが出るので応答します。

接続したら英小文字の文書を入力して「Send」をタップします

英大文字が帰ってきたら成功です。
所感
正直、ネットワークで無くUSBでPCと繋げたいケースと言うのが余り思い付かない。
今のところ、RaspberryPiのTTY端末くらい。
何か面白いアイデアが有ったら教えて下さい。
ソースのSVNは以下に有ります。
- https://kotemaru.googlecode.com/svn/trunk/androidUSBSample
