afjkの技術メモ

主に技術系備忘録

Bluetooth機器にタッチで接続出来るNFCタグを作る(その4)

前回紹介したBluetooth NFCタグライタで、いろいろなBluetooth機器用のNFC接続タグを作って試してみました。

結果、

スピーカー(Creative D100):OK

ヘッドセット(LBT-HS05):OK

キーボード(mac用):NG

MagicTrackpad(mac用):NG

Androidスマートフォン:NG

iPhone:NG

どうやら、A2DPとHeadsetプロファイルにしか対応していなさそうです。

NFCタグにはUUID情報も書き込んでいるのに何故だ?と思い調べて見ました。

NFCでの接続時のLogcatを取る。

関係ありそうな箇所を抜き出してみました。

D/NfcDispatcher(685): dispatch tag: TAG: Tech [android.nfc.tech.MifareUltralight, android.nfc.tech.NfcA, android.nfc.tech.Ndef] message: NdefMessage [NdefRecord tnf=2 type=6170706C69636174696F6E2F766E642E626C7565746F6F74682E65702E6F6F62 
payload=22008A7E3A3C02000E0943726561746976652044313030040D04042405030B110E11]
D/NfcHandover(685): tryHandover(): NdefMessage [NdefRecord tnf=2 type=6170706C69636174696F6E2F766E642E626C7565746F6F74682E65702E6F6F62 payload=22008A7E3A3C02000E0943726561746976652044313030040D04042405030B110E11]
D/BluetoothAdapterProperties(1672): CONNECTION_STATE_CHANGE: 00:02:3C:3A:7E:8A: 0 -> 1
D/CachedBluetoothDevice(1599): onProfileStateChanged: profile HEADSET newProfileState 1
:
D/CachedBluetoothDevice(1599): onProfileStateChanged: profile A2DP newProfileState 1
:
D/CachedBluetoothDevice(1599): onProfileStateChanged: profile HEADSET newProfileState 0
:
D/CachedBluetoothDevice(1599): onProfileStateChanged: profile A2DP newProfileState 2
:
D/BluetoothAdapterProperties(1672): CONNECTION_STATE_CHANGE: 00:02:3C:3A:7E:8A: 1 -> 2
:
D/CachedBluetoothDevice(1599): onProfileStateChanged: profile HEADSET newProfileState 1
:
D/CachedBluetoothDevice(1599): onProfileStateChanged: profile HEADSET newProfileState 0

何故かA2DPとともにHEADSETプロファイルの接続シーケンスも走っているように見えます。

ソースコードを確認してみましょう。
その気になればソースコードを調べられるのがオープンソースの魅力です。
Androidソースコード検索サービスで、ログに出力されているキーワードを元に検索します。
ここでは、http://tools.oesf.biz/android-4.2.0_r1.0/を参照します。

結果、BluetoothとのNFCでの接続シーケンスは以下に実装されていました。
/src/com/android/nfc/handover/BluetoothHeadsetHandover.java
start()->nextStep()->nextStepConnect()と呼び出しながら接続シーケンスが実行され、

    245     void nextStepConnect() {
    246         switch (mState) {
    260             :
    261             case STATE_BONDING:
    262                 // Bluetooth Profile service will correctly serialize
    263                 // HFP then A2DP connect
    264                 mState = STATE_CONNECTING;
    265                 synchronized (mLock) {
    266                     if (mHeadset.getConnectionState(mDevice) != BluetoothProfile.STATE_CONNECTED) {
    267                         mHfpResult = RESULT_PENDING;
    268                         mHeadset.connect(mDevice); //★HeadSetの接続
    269                     } else {
    270                         mHfpResult = RESULT_CONNECTED;
    271                     }
    272                     if (mA2dp.getConnectionState(mDevice) != BluetoothProfile.STATE_CONNECTED) {
    273                         mA2dpResult = RESULT_PENDING;
    274                         mA2dp.connect(mDevice);  // ★A2DPの接続
    275                     } else {
    276                         mA2dpResult = RESULT_CONNECTED;
    277                     }
    278                     if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) {
    279                         toast(mContext.getString(R.string.connecting_headset) + " " + mName + "...");
    280                         break;
    281                     }
    282                 }
    283                 // fall-through

やはり、HEADSETとA2DPの接続しかしていない。それも、とにかく両方とも接続要求を出し、接続された方を利用するという実装の様です。

では、NFCに設定したUUIDは?NFCからのデータ読み込み部分を見てみましょう。

    777     public boolean tryHandover(NdefMessage m) {
    778         if (m == null) return false;
    779         if (mBluetoothAdapter == null) return false;
    780 
    781         if (DBG) Log.d(TAG, "tryHandover(): " + m.toString());
    782 
    783         BluetoothHandoverData handover = parse(m);//①
    784         if (handover == null) return false;
    785         if (!handover.valid) return true;
    786 
    787         synchronized (HandoverManager.this) {
    788             if (mBluetoothAdapter == null) {
    789                 if (DBG) Log.d(TAG, "BT handover, but BT not available");
    790                 return true;
    791             }
    792             if (mBluetoothHeadsetHandover != null) {
    793                 if (DBG) Log.d(TAG, "BT handover already in progress, ignoring");
    794                 return true;
    795             }
    796             mBluetoothHeadsetHandover = new BluetoothHeadsetHandover(mContext, handover.device,
    797                     handover.name, mHandoverPowerManager, this);//②
    798             mBluetoothHeadsetHandover.start();
    799         }
    800         return true;
    801     }

①の箇所でNFCから取得したバイナリデータをパースし、BluetoothHandoverDataを生成しています。
BluetoothHeadsetHandoverが先ほどのBluetooth接続シーケンスを実装しているクラスです。
これに②で、①でのパース情報を渡しています。
では、①でのパース処理はどうなっているか。
/packages/apps/Nfc/src/com/android/nfc/handover/HandoverManager.java

    916     BluetoothHandoverData parseBtOob(ByteBuffer payload) {
    917         BluetoothHandoverData result = new BluetoothHandoverData();
    918         result.valid = false;
    919 
    920         try {
//①アドレス取得処理
    921             payload.position(2);
    922             byte[] address = new byte[6];
    923             payload.get(address);
    924             // ByteBuffer.order(LITTLE_ENDIAN) doesn't work for
    925             // ByteBuffer.get(byte[]), so manually swap order
    926             for (int i = 0; i < 3; i++) {
    927                 byte temp = address[i];
    928                 address[i] = address[5 - i];
    929                 address[5 - i] = temp;
    930             }
    931             result.device = mBluetoothAdapter.getRemoteDevice(address);
    932             result.valid = true;
    933 
//②デバイス名取得処理
    934             while (payload.remaining() > 0) {
    935                 byte[] nameBytes;
    936                 int len = payload.get();
    937                 int type = payload.get();
    938                 switch (type) {
    939                     case 0x08:  // short local name
    940                         nameBytes = new byte[len - 1];
    941                         payload.get(nameBytes);
    942                         result.name = new String(nameBytes, Charset.forName("UTF-8"));
    943                         break;
    944                     case 0x09:  // long local name
    945                         if (result.name != null) break;  // prefer short name
    946                         nameBytes = new byte[len - 1];
    947                         payload.get(nameBytes);
    948                         result.name = new String(nameBytes, Charset.forName("UTF-8"));
    949                         break;
    950                     default:
    951                         payload.position(payload.position() + len - 1);
    952                         break;
    953                 }
    954             }
    955         } catch (IllegalArgumentException e) {
    956             Log.i(TAG, "BT OOB: invalid BT address");
    957         } catch (BufferUnderflowException e) {
    958             Log.i(TAG, "BT OOB: payload shorter than expected");
    959         }
    960         if (result.valid && result.name == null) result.name = "";
    961         return result;
    962     }

①②のデバイスアドレス、デバイス名以外の情報は取得されていません。

折角NFCタグに書き込んだClass情報やUUID情報は無駄という訳です。
確かに、仕様書上はデバイスアドレス以外はオプション情報なのですが、設定されている場合は有効に利用してほしい物ですね。

Bluetooth機器にタッチで接続出来るNFCタグを作る(その3)

前回作成したNFCタグについて

まず挙動は以下の通りです。

※Nexus7とCreative D100で試しました。

・ペアリングしていない状態でタッチ

 接続確認ダイアログが表示される。

 OKすると、ペアリング→接続。

 ※Creative D100側はデバイス検出状態にしておく必要があります。

・切断中にタッチ

 →接続される。

・接続中にタッチ

 →切断される。

Bluetooth OFF中にタッチ

 →BluettohがONになり、接続される。また、切断するとBluetoothもOFFされる。

 

接続中にタッチして切断されるのは気が利いていますね。

 

前回StickyNotesを改造したアプリは、接続機器の情報を直書きしていました。

他の機器を試すのにいちいちアプリを改造するのは面倒なので、ペアリング済みのデバイス一覧を取得し、タッチしたデバイスの情報からNFCへの書き込み情報を生成して書き込む様改造しました。

 

 

ペアリング済みのデバイス一覧取得には、BluetoothChat(Android SDKのsampleアプリ)を参考にしています。

Google Playで公開していますので、興味のある方はダウンロードしてみてください。

BluetoothNFCタグライタ

f:id:afjk:20130616221610p:plain

ソースコードGitHubで公開しています。

 次回は、様々なデバイスとの接続と、Androidの接続シーケンスを調べます。

Bluetooth機器にタッチで接続出来るNFCタグを作る(その2)

まず、書き換え可能なNFCタグを入手します。

AmazonでNFCタグを検索。。。色々種類がありますが、私が購入したのは

「SMARTRAC NFC Tag」20枚入りで1,365円。

1枚当たり70円弱でなかなかリーズナブル。

SMARTRAC NFC Tag (NTAG203 WPP60 ( Circus ) 20p)

SMARTRAC NFC Tag (NTAG203 WPP60 ( Circus ) 20p)

NFCタグの書き換えには、下記のサイトで公開されている、「StickyNotes」を改造して使います。

http://nfc.android.com/

このアプリは、NFCタグへの文字列の書き込みと読み込みが出来ます。

そこで、NFCタグへの書き込みを行っている箇所を、SSP用の情報に書き換えてしまおうという訳です。

NFCタグへの書き込みは、以下。

NFCタグ検出のIntentをトリガーに、Tagへの書き込みを行う。

書き込む情報は、NdefMessageクラスの情報。

ここで、前回紹介したSSPのサンプルデータ「Table 7: Binary Content of a Sample Bluetooth OOB Data on an NFC Forum Tag」をもとに、デバイス名とデバイスアドレスだけを自分が持っているスピーカー「Creative D100」の情報に書き換えます。

まずは試しということで、バイナリデータ直書きです。アドレスはエンディアンが逆転するので注意。 

これで、NFCタグの書き込み操作をすると、スピーカーにSSP接続するための情報が書き込まれます。

書き込んだNFCタグをタッチすると。。。見事にスピーカーへの接続が出来ました。

次回は、作成したNFCタグの使用感について記載します。

 

 

Bluetooth機器にタッチで接続出来るNFCタグを作る(その1)

Android4.1からBluetooth SSP(Secure Simple Pairing)がサポートされており、スピーカーに埋め込まれたNFCタグにタッチするだけで接続するといった事が可能になりました。

例えば、下記のような機能です。

NFCでかんたん!ワンタッチBluetooth(R)接続 | ソニー

書き換え可能なNFCタグは市販されていますし、Android端末があれば、NFCタグの読み書きは可能です。

ならば、NFCタグに書き込む情報さえ分かれば、自分でBluetoothスピーカーとペアリングするためのNFCタグを作ることが出来そうです。

 

NFCでのSSP接続に関する仕様はNFC Forumで規定され、下記で公開されています。

http://www.nfc-forum.org/resources/AppDocs/NFCForum_AD_BTSSP_1_0_1.pdf

 2014/1/19修正:資料へのリンクが切れていました。

2014/7/3追記:資料へのリンクの移動先および、新バージョンと思われるpdfがありました。

NFCForum_AD_BTSSP_1_0_1.pdf(本記事はこのバージョンを元にしています)

NFCForum-AD-BTSSP_1_1.pdf 

 

この資料の4.2章「Static Handover」で、NFCタグでのSSPの実現方法が記載されています。

さらに、4.2.1章には、SSPのための単純なNFCタグのフォーマットがサンプル付きで記載されています。

4.2.1 Simplified Tag Format for a Single Bluetooth Carrier

f:id:afjk:20130617000745p:plain

f:id:afjk:20130617000756p:plain

見ると、Audioデバイス向けの情報の様ですし、とりあえずBluetooth Device AddressBluetooth Local Nameさえ書き換えれば、他の情報は流用出来そうです。

次回は、NFCタグを書き換えるAndroidアプリを作成し、SSP接続用の情報を書き込んでみます。