blog

Androidアプリ開発 ~Bluetooth SPP による無線通信~

2011.05.16

デバイスソリューション部 佐伯です。

Android にて、Bluetooth SPP(Serial Port Profile) による無線通信をおこなう方法について紹介したいと思います。

Android SDK に「BluetoothChat」というサンプルプロジェクトがあり、Bluetooth 通信をおこなう上で必要となる一通りの機能が実装されていますので、それを基に説明したいと思います。

BluetoothChat は、Android端末間の接続を前提としていいますが、今回は SPP を使い PC 等の他デバイスとの接続をターゲットにお話ししたいと思います。

●Bluetooth 許可(パーミッション)
まず、アプリケーションで Bluetooth 機能を使用するためには、「BLUETOOTH」と「BLUETOOTH_ADMIN」の 2つの Bluetooth 許可のうち、少なくともどちらか一方の許可をマニフェストで宣言する必要があります。

[BLUETOOTH]
接続要求、接続受付、および、テータ転送などの任意の Bluetooth 通信を実行するために必要。

[BLUETOOTH_ADMIN]
デバイス発見の初期化や Bluetooth 設定の操作を行うために必要。
※BLUETOOTH_ADMIN 許可を使用する場合は、BLUETOOTH 許可も必要です。

以下にアプリケーションのマニフェストで Bluetooth 許可を宣言する手順を示します。
①アプリケーションのマニフェストを開きます
②[Permissions]タブを選択します
③[Add…]ボタンをクリックします
④Uses Permission を選択します
⑤[OK]ボタンをクリックします
⑥Attributes for Uses Permission の Name リストより
 android.permission.BLUETOOTH または android.permission.BLUETOOTH_ADMIN
を選択します

この操作によって、マニフェストに下記の記述が追加されます。
[AndroidManifest.xml]

●Bluetooth アダプタの取得と有効化

アプリケーションで Bluetooth による通信を行うにあたり、まず、そのデバイスで Bluetooth がサポートされていることを確認する必要があります。
BluetoothAdapter.getDefaultAdapter() メソッドを呼び出して、ローカルの BluetoothAdapter を取得します。getDefaultAdapter() の結果が null の場合は、そのデバイスが Bluetooth をサポートしていないことを示します。

以下が BluetoothChat における該当部分のソースコードです。
[BluetoothChat.java]

デバイスが Bluetooth をサポートしていることを確認したら、次に isEnabled() メソッドを呼び出して Bluetooth が有効になっているかを確認します。isEnabled() の結果が false の場合、Bluetooth は無効になっています。Bluetooth を有効にするには、ACTION_REQUEST_ENABLE アクションインテントを使用して、startActivityForResult() を呼び出します。
これにより、システム設定を通して Bluetooth を有効にする要求を発生させることができます。

以下が BluetoothChat における該当部分のソースコードです。
[BluetoothChat.java]

ACTION_REQUEST_ENABLE アクションインテントを開始すると、
下記、Bluetooth を有効にするために許可を要求するダイアログが表示されます。

“はい” を選択すると、システムが Bluetooth の有効化を開始します。
Bluetooth の有効化を実行中は、下記のようなダイアログが表示されます。

処理が完了するとフォーカスがアプリケーションに戻ってきます。

Bluetooth 有効化の結果は、onActivityResult() コールバックにて受け取ります。
有効化に成功した場合の結果コードは RESULT_OKです。エラー発生 または ユーザが許可リクエストで “いいえ” を選択し、Bluetooth を有効に出来なかった場合の結果コードは RESULT_CANCELED になります。

以下が BluetoothChat における該当部分のソースコードです。
[BluetoothChat.java]

以上で、ローカルに Bluetooth デバイスが存在し、有効なことが確認出来ました。
続いて接続先デバイスの検索について説明します。

●リモートデバイスの検索
リモート Bluetooth デバイスの検索には、デバイスの発見機能を使う方法とペア(結合済み)デバイスのリストを問い合わせる方法の2通りがあります。

デバイスの発見は、Bluetooth が使用可能なデバイスがローカル領域にあるかどうかをスキャンする処理です。デバイスが発見の要求に応答するためには、発見可能であることを有効としている必要があります。(Android 搭載のデバイスはデフォルトでは発見可能になっていません)

初めてリモートデバイスとの接続が確立できたら、自動的にペアリングの要求がユーザに提示されます。デバイスがペアになっていると、デバイスの基本情報(デバイス名、クラス、MAC アドレス等の情報)が保存されていて Bluetooth API を使用した読み出しが可能となります。それにより、リモートデバイスに対する既知の MAC アドレスを使って初期化出来るようになるため、時間のかかる発見処理を実行しなくてよくなります。

アプリケーションとしては、まずペアデバイスの一覧をユーザーに提示し、対象のデバイスがリストに無ければデバイスの発見機能を使ってスキャンする作りが望ましいと思います。BluetoothChat でもそのように作られています。

▼ペアデバイスの問い合わせ
ペアデバイスの問い合わせには、getBondedDevices() メソッドを呼び出します。
このメソッドはペアとなったデバイスを表す BluetoothDevice のセットを返します。

BluetoothChat では、オプションメニューより「Connect a device」を選択時にペアデバイスの問い合わせが行われます。ソースコードはDeviceListActivity.java に含まれます。
[DeviceListActivity.java]

接続を初期化するために BluetoothDevice オブジェクトに必要となるのは MAC アドレスです。
この例では、ユーザに対して表示している ArrayAdapter (mPairedDevicesArrayAdapter) の一部
として、MAC アドレスが保存されています。(getAddress() にて取得)

▼デバイスの発見
ペアデバイスが無い、もしくは、ペアデバイスの一覧に接続する対象が存在しない場合には、デバイスの発見機能を使って近くにある Bluetooth デバイスを探索します。

デバイスの発見を開始するには、startDiscovery() メソッドを呼び出します。プロセスは非同期で実行されます(startDiscovery() は呼び出し後すぐに返ってくる)。発見の処理は、通常約12秒間の問い合わせスキャンがあり、次に見つかった各デバイスの Bluetooth 名を探すためのページスキャンが続きます。

BluetoothChat では、オプションメニューより「Connect a device」を選択した際に表示されるDeviceListActivity 上の、「Scan for devices」を実行時にデバイスの発見が行われます。

以下が BluetoothChat における該当部分のソースコードです。
[DeviceListActivity.java]

startDiscovery() を実行すると、システムが各デバイスに対し ACTION_FOUND インテントをブロードキャストします。アプリケーションは、発見した各デバイスに関する情報を受け取るために、ACTION_FOUND インテントに対する BroadcastReceiver を登録する必要があります。

以下が BluetoothChat における該当部分のソースコードです。
[DeviceListActivity.java]

発見処理の実行は、Bluetooth アダプタにとって重い処理であり、多くのリソースを消費します。一旦接続するデバイスを見つけたら、接続を試みる前に cancelDiscovery() を呼び出して発見を停止すべきです。

▼発見機能の有効化
前述しましたが、Android 搭載のデバイスはデフォルトでは発見可能になっていません。ローカルデバイスを他のデバイスから発見可能となるようにしたい場合は、ACTION_REQUEST_DISCOVERABLE アクションインテントで startActivityForResult() を呼び出します。

これにより発見可能モードを有効にしたことがシステム設定を通して公表されます。デフォルトでは、デバイスが発見可能になれるのは 120 秒間ですが、インテントに EXTRA_DISCOVERABLE_DURATION 付加情報を追加することで(最大300秒まで) 異なる期間を定義することが出来ます。

BluetoothChat では、オプションメニューより「Make discoverable」を選択時に発見機能の有効化が行われます。

以下が BluetoothChat における該当部分のソースコードです。
[BluetoothChat.java]

実行すると下記のダイアログが表示され、デバイスを発見可能にする許可をユーザに要求します。

なお、発見機能を有効にする必要があるのは、アプリケーションが入ってくる接続を受け付けるサーバソケットのホストにしたい場合のみです。(クライアントとして接続するアプリケーションでは不要)

以上で、接続したいデバイスの MAC アドレスが取得できました。続いてデバイスの接続について説明します。

●デバイスの接続
アプリケーションにて、2 つのデバイス間の接続を作成するためには、サーバサイドとクライアントサイドの両方の機構で実装が必要となります。それは、一方のデバイスがサーバソケットをオープンする必要があり、もう一方が接続を初期化する必要があるためです(接続の初期化には、サーバデバイスの MAC アドレスを使用します)。サーバとクライアントは、同一の RFCOMM チャネル上で、それぞれが接続済みの BluetoothSocket を受け取ったときに接続されたとみなします。

この時点で、各デバイスは入出力ストリームを得ることができ、データの転送を開始することが可能となります。

サーバデバイスとクライアントデバイスは、それぞれに必要な BluetoothSocket を異なる方法で獲得します。サーバは、入ってくる接続が受け付けられたときに BluetoothSocket を受け取り、クライアントは、サーバに対し RFCOMM チャネルをオープンしたときに BluetoothSocket を受け取ります。

▼サーバとしての接続

2 つのデバイスを接続したいとき、一方が BluetoothServerSocket をオープンしそれを保持することによってサーバとして振る舞う必要があります。サーバソケットの目的は、入ってくる接続要求をリッスンすることと、もう一方が接続を受け付けられたときに、接続済みの BluetoothSocket を提供することです。

BluetoothServerSocket から BluetoothSocket が得られたとき、さらなる接続を受け入れたくない場合は、BluetoothServerSocket を放棄することが可能です。

1. listenUsingRfcommWithServiceRecord(String, UID) を呼び出して、BluetoothServerSocket を取得します。

String は、サービスの名前を識別可能とするもので、システムはそれを自動的にデバイス上の新たなサービス発見プロトコル(Service Discovery Protocol : SDP)データベースエントリに書き込みます。(この名前は任意で、単純にアプリケーションの名前にすることができます)
また、UUID も SDP エントリに含まれ、クライアントデバイスとの接続で一致させる基となります。
接続が受け付けられるようにするには、これらの UUID が一致している必要があります。UUIDについては、後述します。

2. accept() を呼び出して、接続要求に対するリッスンを開始します。

この呼び出しは、接続が受け付けられるか、例外が発生するまでブロックされます。
リモートデバイスが、このサーバソケットが登録した UUID と一致した接続要求を送信したとき、接続が受け付けられます。
成功したときは、accept() が接続済みの BluetoothSocket を返します。

3. 追加の接続を受け付けたくない場合に、close() を呼び出します。

これによりサーバソケットとすべてのリソースは解放されますが、 accept() で返された接続済み BluetoothSocket はクローズされません。
TCP/IP とは異なり、RFCOMM が同時に許可するのは、チャネルあたりひとつの接続済みクライアントのみです。従って殆どの場合、接続済みソケットを受け付けた直後に BluetoothServerSocket の close() を呼び出します。

accept() の呼び出しは、ブロックされる呼び出しのため、別スレッドにて実行すべきです。
通常 BluetoothServerSocket や BluetoothSocket を使う処理は、アプリケーションで管理された新しいスレッドで処理します。

以下が BluetoothChat における該当部分のソースコードです。
[BluetoothChatService.java]

▼クライアントとしての接続
リモートデバイス(オープンされたサーバソケットを保持しているデバイス)の初期化を行うには 、リモートデバイスを表している BluetoothDevice オブジェクトを最初に獲得する必要があります。
その後、BluetoothDevice を使用して BluetoothSocket を獲得し接続を初期化する必要があります。

基本的な処理手順は次のようになります。
1. BluetoothDevice を使用して、createRfcommSocketToServiceRecord(UUID) を呼び出すことにより、BluetoothSocket を初期化します。

ここで渡す UUID は、サーバがその BluetoothServerSocket をオープンしたときにサーバデバイスにより使用された UUIDと一致している必要があります。

2. connect() を呼び出すことにより接続を初期化します。

リモートデバイスが接続を受け付けると、接続している間に使用する RFCOMM チャネルが共有され、connect() から戻ってきます。
このメソッドもブロックされる呼び出しです。なんらかの理由で接続に失敗するか connect() メソッドがタイムアウトとなった場合は例外が発生します。

以下が BluetoothChat における該当部分のソースコードです。
[BluetoothChatService.java]

以上でリモートデバイスとの接続が完了し、送受信に必要な BluetoothSocket を獲得できました。ここで、BluetoothChat を使って PC 等と接続する際に問題となる UUID について簡単に説明します。

★UUID について
UUID(Universally Unique Identifier = 普遍的に一意な識別子)は、128 ビットの標準化フォーマットであり、文字列の ID が情報を一意に識別するために使用されます。

次表は、Bluetooth の割り当て番号文書で定義されている最も一般的なUUIDのリストです。

SPP による通信をおこなう場合は、UUID に 00001101-0000-1000-8000-00805F9B34FB を指定する必要があります。
[BluetoothChatService.java]

●データの送受信
デバイスの接続に成功したら、それぞれが接続済みの BluetoothSocket を持つことになります。
BluetoothSocket を使って、任意のデータを送受信する処理は以下のようになります。

1. getInputStream() と getOutputStream() メソッドにて、ソケットを使って転送をハンドリングする InputStream と OutputStream を取得します。

2. ストリームに対し、read() と write() メソッドでデータの読み書きを行います。

read()、write() メソッドはブロックされる呼び出しのため、ストリームの読み込みと書き込み用にスレッドを使用すべきです。

以下が BluetoothChatにおける該当部分のソースコードです。

[BluetoothChatService.java]

●動作の確認
エミュレータ上では、Bluetooth 機能の確認は行えないため、実機を使って確認を行う必要があります。

今回リモート側には、デスクトップ PC に USB タイプの Bluetooth ドングルを付けたものを使用しました。Bluetooth 設定画面にて着信用の COM ポートを割り当てておきます。PC 側のソフトには TeraTermを使用しました。Android 端末に UUID を SPP 用のものに変更した BluetoothChat をインストールし、実行します。

ここで、問題が発生...

BluetoothChat を実行するとすぐに強制終了し起動しません。端末のUSBデバッグを有効にし、USB接続による実機デバッグを行ったところ、起動時に onResume() が呼び出され、その中から BluetoothChatService の start() メソッドを呼び出したときに落ちることが分かりました。

原因はよく分かりません(インスタンスの関係ではないかと思われます)が、該当行をコメントアウトすることで正常に動作するようになりました。

[BluetoothChat.java]

起動後、オプションメニューより「Connect a device」を実行し、リストから対象の PC を選んで接続します。(初めての場合は、Scan for devicesを実行)
接続するとタイトルの右上に connected:(PC名) と表示されるかと思います。

Android端末側で “Android Bluetooth SPP test” を入力し[Send]ボタンを押し、
PC 側でその文字列の受信を確認した後、”OK(改行)”を入力した際の画面を下記に示します。

[Android側]

[PC 側]

下記のような改良点が見つかるものの、正常に送受信できることが確認出来ました。
・送信時に改行コードが付加されていないため文章の区切りが付かない
・受信が都度入ってきた文字をログに出力しており行単位にならない(1文字づつ表示される)

このあたりは、ConnectedThread の write() メソッドの実装やストリームから read() している箇所を見直すことで改善できると思います。
なお、TeraTerm の画面を見てお気づきだと思いますが、PC 側のボーレートは 921600bps とそこそこ高速です。

以上で、Bluetooth SPP 通信については終わりたいと思います。
今後も Android で何かおもしろい機能等があれば紹介していきたいと思います。


このページの内容の一部は、Google が作成、提供しているコンテンツをベースに変更したもので、クリエイティブ・コモンズの表示 3.0 ライセンスに記載の条件に従って使用しています。

http://code.google.com/intl/ja-JP/policies.html
http://creativecommons.org/licenses/by/3.0/deed.ja

なお、このページのオリジナルはこちらです。
http://developer.android.com/guide/topics/wireless/bluetooth.html