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