28
Developers.IO 2016 F-4 工藤 星命 モバイルアプリサービス部 Ⓒ Classmethod, Inc. 2016年02月20日 全部見せます! 最前線エンジニアが語る BLEアプリケーション実装のハマりどころ(Android) 1 #cmdevio2016

Ble android

Embed Size (px)

Citation preview

Page 1: Ble android

Developers.IO 2016

F-4

工藤 星命 モバイルアプリサービス部

Ⓒ Classmethod, Inc.

2016年02月20日

全部見せます! 最前線エンジニアが語る BLEアプリケーション実装のハマりどころ(Android)

1

#cmdevio2016

Page 2: Ble android

目次

BLE概要

2Ⓒ Classmethod, Inc.

実装サンプル

ハマりどころ

まとめ

Page 3: Ble android

BLE概要

3

Page 4: Ble android

キーワード類• 省電力 • GAPとGATT

• すべてのBLEデバイスに定義されている仕様 • サービス

• 複数のデータ構造をグループ化したもの • 複数のキャラクタリスティックをもつ(持たないのもある)

• キャラクタリスティック • 複数のデータ構造をグループ化したもの • 複数のディスクリプタをもつ(持たないのもある)

• ディスクリプタ • キャラクタリスティックの追加情報

• セントラル • いわゆるクライアント、主にスマホなど通信を開始したり制御する側

• ペリフェラル • いわゆるサーバー、セントラルからの操作を受け付ける

4Ⓒ Classmethod, Inc.

Page 5: Ble android

実装サンプル

5

Page 6: Ble android

パーミッションの宣言マニフェストに定義

<uses-permission android:name="android.permission.BLUETOOTH"/>

<uses-permission android:name=“android.permission.BLUETOOTH_ADMIN"/>

android6.0からは、以下のパーミッションが必要になるので

パーミッションモデルに従って、ユーザーに許可をもらう必要がでてきます。

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

もしくは

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

6Ⓒ Classmethod, Inc.

Page 7: Ble android

Bluetoothを操作する前にBLEが搭載されているかチェック public static boolean checkBLESupport(Context context) { return context.getPackageManager().hasSystemFeature( FEATURE_BLUETOOTH_LE);}

7Ⓒ Classmethod, Inc.

Page 8: Ble android

BLEがサポートされていたらアダプターを取得 BluetoothManager bluetoothManager =(BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE) mBluetoothAdapter = bluetoothManager.getAdapter

8Ⓒ Classmethod, Inc.

Page 9: Ble android

スキャンの前にBluetoothが有効になっている事を確認 if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) { // スキャン開始 } else { // OFFだった場合、ONにする事を促すダイアログを表示 Intent btIntent = new Intent( BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivity(btIntent) }

9Ⓒ Classmethod, Inc.

Page 10: Ble android

ここまで• パーミッションの宣言

• BLEをサポートしているか

• BluetoothはONになっているか

10Ⓒ Classmethod, Inc.

Page 11: Ble android

スキャン開始メソッドandroid4.4以下(API Level 20以下)ではmBluetoothAdapter.startLeScan(mLeScanCallback);

android5.0以上(API Level 21以上)ではBluetoothAdapter#startLeScanはdeprecatedの為、以下を使用mBluetoothScanner = mBluetoothAdapter.getBluetoothLeScanner;mBluetoothScanner.startScan(mScanCallback);

11Ⓒ Classmethod, Inc.

Page 12: Ble android

スキャン実行private void startLeScan(){ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { mBluetoothAdapter.startLeScan(mLeScanCallback); } else { startScan(); }}@TargetApi(Build.VERSION_CODES.LOLLIPOP)private void startScan(){ mBluetoothScanner = mBluetoothAdapter.getBluetoothLeScanner; mBluetoothScanner.startScan(mScanCallback);}

12Ⓒ Classmethod, Inc.

Page 13: Ble android

スキャンコールバックBluetoothAdapter#startLeScan(LeScanCallback callback)private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan (final BluetoothDevice device, int rssi, byte[] scanRecord) { // スキャン結果を処理 mLeDeviceListAdapter.addDevice(device); mLeDeviceListAdapter.notifyDataSetChanged(); } } };

13Ⓒ Classmethod, Inc.

Page 14: Ble android

スキャンコールバックBluetoothScanner#startScan(ScanCallback callback)private ScanCallback mScanCallback = new ScanCallback() { @Override public void onScanResult(int callbackType, ScanResult result) { super.onScanResult(callbackType, result); // スキャン結果を処理 BluetoothDevice device = result.getDevice // ScanResultからrssiやScanRecordも取得できる } }; … }

14Ⓒ Classmethod, Inc.

Page 15: Ble android

スキャンコールバックBluetoothScanner#startScan(ScanCallback callback)private ScanCallback mScanCallback = new ScanCallback() { … @Override public void onScanFailed(int errorCode) { super.onScanFailed(errorCode); // エラーコードと内容はScanCallbackに定義されてる // SCAN_FAILED_ALREADY_STARTEDなど } }; … }

15Ⓒ Classmethod, Inc.

Page 16: Ble android

スキャン停止private void stopScan(){ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { mBluetoothAdapter.stopLeScan(mLeScanCallback); } else { mBluetoothLeScanner.stopScan(mScanCallback); }}// スキャンはバッテリー消費が多いのでtimeoutを設けるmHandler.postDelayed(new Runnable() { @Override public void run() { mScanning = false; stopLeScan(); } }, SCAN_PERIOD);

16Ⓒ Classmethod, Inc.

Page 17: Ble android

コネクションBluetoothDevice device = mBluetoothAdapter.getRemoteDevice(mAddress);mBluetoothGatt = device.connectGatt(mContext, false, mGattCallback);第二引数は自動接続のON/OFF。trueにした場合、メソッド実行時にデバイスが範囲外でも範囲内に入った時に接続される。が、切断後の再接続は確実ではないので、この自動接続を前提とした設計はおすすめできません。切断のコールバックを受け取った時に再度、connectGattを呼び出すのが無難です。が、切断時のエラーの種類や、再接続のタイミングによっては接続できなかったりする事も多い印象です。

17Ⓒ Classmethod, Inc.

Page 18: Ble android

ハマりどころ

18

Page 19: Ble android

BluetoothGattCallbackprivate final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status,int newState) { if(status == BluetoothGatt.GATT_SUCCESS) { if (newState == BluetoothProfile.STATE_CONNECTED) { // 渡されるインスタンスが期待するものとは限らない // gattを更新して必要な情報を判定 mGatt = gatt; … // 再接続後はキャッシュされたデータが読み込まれる? // キャッシュのクリアはアダプターのON/OFFか、 // BluetoothGatt#refreshをリフレクションで呼ぶ boolean bool = mGatt.discoverServices(); } }

19Ⓒ Classmethod, Inc.

Page 20: Ble android

BluetoothGattCallbackprivate final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status,int newState) { if(status == BluetoothGatt.GATT_SUCCESS) { if (newState == BluetoothProfile.STATE_CONNECTED) { … boolean bool = mGatt.discoverServices(); // boolがfalseだとRead /Writeが失敗する // onServicesDiscoveredで判定が必要 } }

20Ⓒ Classmethod, Inc.

Page 21: Ble android

BluetoothGattCallbackprivate final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status,int newState) { if(status == BluetoothGatt.GATT_SUCCESS) { if (newState == BluetoothProfile.STATE_CONNECTED) { … } else { // 切断(STATE_DISCONNECTED)時の処理 // 明示的にdisConnectしなかった時はフラグなどで // 再接続させるか、disConnectを呼ぶ // disConnect時にキャッシュをクリアするのが良い? // BluetoothGatt#refreshは失敗する事もある(falseを返す) } } }

21Ⓒ Classmethod, Inc.

Page 22: Ble android

BluetoothGattCallback … if(status == BluetoothGatt.GATT_SUCCESS) { if (newState == BluetoothProfile.STATE_CONNECTED) { … } else { // 切断(STATE_DISCONNECTED)時の処理 } …

STATE_DISCONNECTEDはdisConnect以外に物理的に信号が遮断された場合などに呼ばれるので、自分で切断したかどうかの判断が必要。(いくつかの実機で試したところ、どれも約10秒程の遮断で呼ばれた)データ通信以外に、コネクション用の通信が行われており、組み込み側のシステム(BTE)で制御されているので、物理遮断の即時検知はデータ通信をタイマーやハンドラで対応しました。(iOSはすぐに検知した)

22Ⓒ Classmethod, Inc.

Page 23: Ble android

BluetoothGattCallback … @Override public void onConnectionStateChange(BluetoothGatt gatt, int status,int newState) { if(status == BluetoothGatt.GATT_SUCCESS) { if (newState == BluetoothProfile.STATE_CONNECTED) { … } } else if(status == 129) { 129,133の謎の汎用エラーがある } }

23Ⓒ Classmethod, Inc.

Page 24: Ble android

ペアリング切れまくりんぐ• 129,133のstatus時は接続に失敗

• 再接続時に組み込み側から非暗号化通信のエラーが呼ばれる

• 呼ばれない時もある

• なぜか接続履歴が消える(設定からペアリング情報を削除した時と同じ)

• 消えない時もある

• ペアリング用の暗号鍵も消えるので再度ペアリングが必要になる

• 5.xでは129,133自体が発生しない?

• この時に紐付け状態を確認(getBondState())してもBOND_BONDED

24Ⓒ Classmethod, Inc.

Page 25: Ble android

ペアリング切れまくりんぐ• 紐付け状態が変更された時はブロードキャストされる

• されないこともある

• キャッシュは関係ないぽい(一回目の接続で発生する事もある)

• デバイス側のモジュールで変わる?

25Ⓒ Classmethod, Inc.

Page 26: Ble android

まとめ• 4.xはつらい • いかにつらいかを人に伝えるスキルが求められる • 5.xは4.xより安定するが、callbackベースはやはりつらい

            が、

• ビーコンやIOTで利用分野は広がっており、これからも多くの事業化や製品化がされる事にともない、BLEを避けていくのは難しくなっていくでしょう。

26Ⓒ Classmethod, Inc.

Page 27: Ble android
Page 28: Ble android

Developers.IO 2016

ご静聴ありがとうございました。 スライドは後日ブログで公開します。

28

A-1

Ⓒ Classmethod, Inc.

#cmdevio2016