Bluetooth機器にタッチで接続出来るNFCタグを作る(その4)
前回紹介したBluetooth NFCタグライタで、いろいろなBluetooth機器用のNFC接続タグを作って試してみました。
結果、
スピーカー(Creative D100):OK
ヘッドセット(LBT-HS05):OK
キーボード(mac用):NG
MagicTrackpad(mac用):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で公開していますので、興味のある方はダウンロードしてみてください。
Bluetooth機器にタッチで接続出来るNFCタグを作る(その2)
まず、書き換え可能なNFCタグを入手します。
AmazonでNFCタグを検索。。。色々種類がありますが、私が購入したのは
「SMARTRAC NFC Tag」20枚入りで1,365円。
1枚当たり70円弱でなかなかリーズナブル。
SMARTRAC NFC Tag (NTAG203 WPP60 ( Circus ) 20p)
- 出版社/メーカー: マイクロソリューション Micro Solution Inc.
- メディア: エレクトロニクス
- この商品を含むブログを見る
NFCタグの書き換えには、下記のサイトで公開されている、「StickyNotes」を改造して使います。
このアプリは、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タグをタッチすると。。。見事にスピーカーへの接続が出来ました。
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(本記事はこのバージョンを元にしています)
この資料の4.2章「Static Handover」で、NFCタグでのSSPの実現方法が記載されています。
さらに、4.2.1章には、SSPのための単純なNFCタグのフォーマットがサンプル付きで記載されています。
4.2.1 Simplified Tag Format for a Single Bluetooth Carrier
見ると、Audioデバイス向けの情報の様ですし、とりあえずBluetooth Device AddressとBluetooth Local Nameさえ書き換えれば、他の情報は流用出来そうです。