Android6.0でBLEを使う(Peripheral編) その1
はじめに
前回の続きで、今度はPeripheral(Slave)についてです。
Nexus5X(ver. 6.0)をPeripheral端末として、Nexus7(ver.5.1.1)と連携します。
AndroidManifest.xmlやアクセス権については前回参照のこと。
準備
Peripheral側ではざっと以下の処理を行います。
- 各種オブジェクトの準備
- Central側で、Write・Read・Notificationを許可する
- Advertising(Central側から発見・接続できるようにする)の開始
- タイマーで一定時間ごとに値を更新(Central側にNotificationが届く)
- WriteRequestがあれば、書き込まれたデータを受け取ってTextViewにその値をセット
各種オブジェクトの準備、Write・Read・Notificationの許可
まず、AndroidではPeripheral端末としてふるまうためには、Android5.0以上である必要があります。
そのため、アプリ自体の最低Apiバージョンの指定によっては確認が必要となります。
PeripheralActivity.java
~省略~ @Override protected void onCreate(Bundle savedInstanceState) { ~省略~ // Bluetoothの使用準備. // Bluetoothへのアクセス、機能を制御できるようにする. mBleManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mBleAdapter = mBleManager.getAdapter(); if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){ // BluetoothがOffならインテントを表示する. if ((mBleAdapter == null) || (! mBleAdapter.isEnabled())) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); // Intentでボタンを押すとonActivityResultが実行されるので、第二引数の番号を元に処理を行う. startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); } else{ this.prepareBle(); } } else{ Toast.makeText(this, "お使いのOSバージョンでは使用できません。", Toast.LENGTH_SHORT).show(); } } ~省略~ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // インテントでBluetoothをOnにしたら、使用準備開始. super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case REQUEST_ENABLE_BT: if ((mBleAdapter != null) || (mBleAdapter.isEnabled())) { // if BLE is enabled, start advertising. this.prepareBle(); } break; } } ~省略~
さらにNexus7(2013)などは、OSバージョン自体は問題ないものの、Peripheralとしては実行できない問題があります。
具体的には「getBluetoothLeAdvertiser()」の値が取得できず、nullが返ります。
PeripheralActivity.java
~省略~ @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void prepareBle() { ~省略~ mBtAdvertiser = mBleAdapter.getBluetoothLeAdvertiser(); //mBtAdvertiserの確認 if(mBtAdvertiser != null){ // BLEでのデータのやり取りで使用する、ServiceとCharacteristicの準備. // 各UUIDはCentralと同じものを使用すること. BluetoothGattService btGattService = new BluetoothGattService(UUID.fromString(SERVICE_UUID)), BluetoothGattService.SERVICE_TYPE_PRIMARY); // CentralからWrite・Read・Notificationができるようにする. mBtCharacteristic = new BluetoothGattCharacteristic(UUID.fromString(CHARACTERISTIC_UUID) ,BluetoothGattCharacteristic.PROPERTY_NOTIFY | BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_WRITE ,BluetoothGattDescriptor.PERMISSION_WRITE | BluetoothGattCharacteristic.PERMISSION_READ); btGattService.addCharacteristic(mBtCharacteristic); // Notificationのやり取りで使用するDescriptorの準備. BluetoothGattDescriptor dataDescriptor = new BluetoothGattDescriptor( UUID.fromString(CHARACTERISTIC_CONFIG_UUID) ,BluetoothGattDescriptor.PERMISSION_WRITE | BluetoothGattDescriptor.PERMISSION_READ); mBtCharacteristic.addDescriptor(dataDescriptor); // やりとりするデータを管理するサーバを開き、サービスを追加する. // 接続・切断、WriteRequestなどの受け取りはmGattServerCallbackで. mBtGattServer = mBleManager.openGattServer(this, mGattServerCallback); mBtGattServer.addService(btGattService); // Advertisingの設定. AdvertiseData.Builder dataBuilder=new AdvertiseData.Builder(); AdvertiseSettings.Builder settingsBuilder=new AdvertiseSettings.Builder(); dataBuilder.setIncludeTxPowerLevel(false); dataBuilder.addServiceUuid(ParcelUuid.fromString(SERVICE_UUID)); settingsBuilder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED); settingsBuilder.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH); ~省略~ }else { Toast.makeText(this, "お使いのデバイスではPeripheralモードが使用できません。", Toast.LENGTH_SHORT).show(); } } ~省略~
Advertisingの開始
Central側から発見・接続できるようにするため、Advertisingを開始します。
PeripheralActivity.java
~省略~ @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void prepareBle() { ~省略~ // Central側から発見・接続できるようにするためのAdvertisingの開始. BluetoothLeAdvertiser bluetoothLeAdvertiser = mBleAdapter.getBluetoothLeAdvertiser(); bluetoothLeAdvertiser.startAdvertising(settingsBuilder.build(),dataBuilder.build() , new AdvertiseCallback(){ @Override public void onStartSuccess(AdvertiseSettings settingsInEffect) { // Advertisingが開始できれば実行. } @Override public void onStartFailure(int errorCode) { // Advertisingが開始に失敗すれば実行. } }); ~省略~
これで、Central側でScanを開始すれば接続できるようになるはずです。
Notification
接続が終わったら、値を更新して、CentralにNotificationが届くようにします。
1. 各種オブジェクトの準備のタイミングでタイマーを用意しておいて、一定時間ごとに乱数を生成し、値を更新します。
PeripheralActivity.java
~省略~ private boolean mIsConnected = false; private Random mRandom = new Random(); private Timer mTimer; private SendDataTimer mSendDataTimer; public class SendDataTimer extends TimerTask { @Override public void run() { if(mIsConnected) { // 1000ミリ秒ごとに0~999までの乱数を生成する. mStrUpdateNum = String.valueOf(mRandom.nextInt(1000)); // 値を更新して、TextViewにその値をセットする. mHndBleHandler.sendEmptyMessage(MESSAGE_NEW_UPDATEDNUM); // 更新した値をCentralに伝える. sendNotification(); } } } ~省略~ @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void prepareBle() { // タイマーの準備. mTimer = new Timer(); mSendDataTimer = new SendDataTimer(); // 第二引数:最初の処理までのミリ秒 第三引数:以降の処理実行の間隔(ミリ秒). mTimer.schedule(mSendDataTimer, 500, 1000); ~省略~
値の更新は、Characteristicに値をセットして、notifyCharacteristicChangedを実行することで可能です。
ただし、notifyCharacteristicChangedの第一引数で、対象の端末を指定する必要があります。
- 各種オブジェクトの準備で、openGattServerを実行したときに引数として渡していた、 BluetoothGattServerCallbackのonConnectionStateChangeにて、接続されたCentral端末を引数として受け取ることができます。
private BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() { ~省略~ @Override public void onConnectionStateChange(android.bluetooth.BluetoothDevice device, int status, int newState) { // 接続・切断されたら実行されるので、接続されたら端末をセットしてタイマーからNotificationを送信できるようにする. if(newState == BluetoothProfile.STATE_CONNECTED){ // set connected device. mConnectedDevice = device; mIsConnected = true; } else{ mIsConnected = false; } } ~省略~ private void sendNotification() { // 値を更新してCentralに伝える. mBtCharacteristic.setValue(mStrUpdateNum); mBtGattServer.notifyCharacteristicChanged(mConnectedDevice, mBtCharacteristic, false); } }
なお、今回はCentral、Peripheralともに1台ずつのためBluetoothDeviceは1つのみ保持するようにしていますが、 複数の場合はArrayListに突っ込んで、sendNotificationでループ処理すればOKのようです。
と、ここまでは割と順調だったのですが、WriteRequestでハマってしまいまいました。 長くなってきたので次回に続きます。