vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

AndroidでBLEを使う(ver.4.4編)

※2015/05/17更新
記載したコードに一部間違いがありましたので、Android5.0対応も含め、内容を更新してブログに書きました。

Android5.0〜でBLEを使う(Central編)

最近またNexus7を使い始めたので、何かアプリを作ってみることにしました。
まずは以前挑戦したBluetooth Low Energy(以下BLE)による連携から。

はじめに

  • AndroidでBLEを使用できるのはver.4.3以上のデバイスです。
  • ver.5.0未満のデバイスでは、BLEのマスターにあたるCentral側としてのみ振舞うことができます。
  • ver.5.0以降はBLE使用クラスの仕様が変更され、ver.4.4以下とは互換性がありません。

実行すること

  • Peripheral端末を探索して接続する。
  • 接続したPeripheral端末が値を更新した場合、通知(notify)を受けられるようにする。
  • 接続したPeripheral端末にWriteリクエストを送ってデータを渡す。
import android.annotation.TargetApi;
import android.app.Activity;
import android.bluetooth.le.*;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAdapter.LeScanCallback;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;

import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

import java.util.UUID;

public class MainActivity extends Activity
{
    private final static int MESSAGE_NEW_RECEIVEDNUM = 0;
    private final static int MESSAGE_NEW_SENDNUM = 1;

    private final static int REQUEST_ENABLE_BT = 123456;
    private BluetoothManager _blmManager;
    private BluetoothAdapter _bldAdapter;
    private boolean _isBluetoothEnable = false;

    private Handler _hndHandler;
    private static final long SCAN_TIME_MILLISEC = 10000;

    private BluetoothLeScanner _blsScanner;

    private TextView _txtReceivedNum;
    private TextView _txtSendNum;

    private String _strReceivedNum = "";
    private String _strSendNum = "";

    // 対象のサービスUUID.
    private static final String SERVICE_UUID = "2B1DA6DE-9C29-4D6C-A930-B990EA2F12BB";
    // キャラクタリスティックUUID.
    private static final String CHARACTERISTIC_UUID = "7F855F82-9378-4508-A3D2-CD989104AF22";
    // キャラクタリスティック設定UUID(固定値).
    private static final String CHARACTERISTIC_CONFIG_UUID = "00002902-0000-1000-8000-00805f9b34fb";

    private BluetoothGatt _blgGatt;
    private BluetoothGattCharacteristic _bgcCharacteristic;
    // 乱数送信用.
    private Random _rndSendNum = new Random();
    private Timer _tmrSendData;
    private SendDataTimer _sdtSendDataTimer;

    private final LeScanCallback _lscScanCallback = new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    // スキャン中に見つかったデバイスに接続を試みる.第三引数には接続後に呼ばれるBluetoothGattCallbackを指定する.
                    _blgGatt = device.connectGatt(getApplicationContext(), false, _bgcGattCallback);
                }
            });
        }
    };
    private final BluetoothGattCallback _bgcGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState)
        {
            // 接続状況が変化したら実行.
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                // 接続に成功したらサービスを検索する.
                gatt.discoverServices();
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                // 接続が切れたらGATTを空にする.
                _blgGatt = null;
            }
        }
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status)
        {
            // サービスが見つかったら実行.
            if (status == BluetoothGatt.GATT_SUCCESS) {
                // UUIDが同じかどうかを確認する.
                BluetoothGattService service = gatt.getService(UUID.fromString(SERVICE_UUID));
                if (service != null)
                {
                    // 指定したUUIDを持つキャラクタリスティックを確認する.
                    _bgcCharacteristic = service.getCharacteristic(UUID.fromString(CHARACTERISTIC_UUID));

                    if (_bgcCharacteristic != null)
                    {
                        _isBluetoothEnable = true;

                        // キャラクタリスティックが見つかったら、Notificationをリクエスト.
                        boolean registered = gatt.setCharacteristicNotification(_bgcCharacteristic, true);

                        // Characteristic の Notificationを有効化する.
                        BluetoothGattDescriptor descriptor = _bgcCharacteristic.getDescriptor(
                                UUID.fromString(CHARACTERISTIC_CONFIG_UUID));
                        descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                        gatt.writeDescriptor(descriptor);
                    }
                }
            }
        }
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic)
        {
            // キャラクタリスティックのUUIDをチェック(getUuidの結果が全て小文字で帰ってくるのでUpperCaseに変換)
            if (CHARACTERISTIC_UUID.equals(characteristic.getUuid().toString().toUpperCase()))
            {
                // Peripheralで値が更新されたらNotificationを受ける.
                _strReceivedNum = characteristic.getStringValue(0);
                // メインスレッドでTextViewに値をセットする.
                _hndBleHandler.sendEmptyMessage(MESSAGE_NEW_RECEIVEDNUM);
            }
        }
    };
    private Handler _hndBleHandler = new Handler()
    {
        public void handleMessage(Message msg)
        {
            // UIスレッドで実行する処理.
            switch (msg.what)
            {
                case MESSAGE_NEW_RECEIVEDNUM:
                    _txtReceivedNum.setText(_strReceivedNum);
                    break;
                case MESSAGE_NEW_SENDNUM:
                    _txtSendNum.setText(_strSendNum);
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        _isBluetoothEnable = false;

        // Writeリクエストで送信する値、Notificationで受け取った値をセットするTextView.
        _txtReceivedNum = (TextView) findViewById(R.id.received_num);
        _txtSendNum = (TextView) findViewById(R.id.send_num);
        
        // Bluetoothの使用準備.
        _blmManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        _bldAdapter = _blmManager.getAdapter();

        // Writeリクエスト用のタイマーをセット.
        _tmrSendData = new Timer();
        _sdtSendDataTimer = new SendDataTimer();
        // 第二引数:最初の処理までのミリ秒 第三引数:以降の処理実行の間隔(ミリ秒).
        _tmrSendData.schedule(_sdtSendDataTimer, 500, 1000);

        // BluetoothがOffならインテントを表示する.
        if ((_bldAdapter == null)
                || (!_bldAdapter.isEnabled())) {
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            // Intentでボタンを押すとonActivityResultが実行されるので、第二引数の番号を元に処理を行う.
            startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
        }
        else
        {
            // BLEが使用可能ならスキャン開始.
            this.scanNewDevice();
        }
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // Intentでユーザーがボタンを押したら実行.
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case REQUEST_ENABLE_BT:
                if ((_bldAdapter == null)
                        || (!_bldAdapter.isEnabled())) {
                    // BLEが使用可能ならスキャン開始.
                    this.scanNewDevice();
                }
                break;
        }
    }

    private void scanNewDevice()
    {
        
        // OS ver.5.0以上ならBluetoothLeScannerを使用する.
        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.L)
        {
            this.startScanByBleScanner();
        }
        else
        {
        
        
            // デバイスの検出.引数は
            _bldAdapter.startLeScan(_lscScanCallback);
        
        }
    }
    
    @TargetApi(Build.VERSION_CODES.L)
    private void startScanByBleScanner()
    {
        _blsScanner = _bldAdapter.getBluetoothLeScanner();
        // デバイスの検出.
        _blsScanner.startScan((ScanCallback) _lscScanCallback);
    }
    

    public class SendDataTimer extends TimerTask{
        @Override
        public void run() {
            if(_isBluetoothEnable)
            {
                // 設定時間ごとに0~999までの乱数を作成.
                _strSendNum = String.valueOf(_rndSendNum.nextInt(1000));
                // UIスレッドで生成した数をTextViewにセット.
                _hndBleHandler.sendEmptyMessage(MESSAGE_NEW_SENDNUM);
                // キャラクタリスティックに値をセットして、Writeリクエストを送信.
                _bgcCharacteristic.setValue(_strSendNum);
                _blgGatt.writeCharacteristic(_bgcCharacteristic);
            }
        }
    }
}

基本的にやることはiOSと同じなのですが、Bluetoothの接続などはBackgroundスレッドで、TextViewの値の更新などはUIスレッドで行う必要があります。

Backgroundスレッドからは、Handlerを使うことでUIの変更を行うことができます。

また、キャラクタリスティック設定UUIDが登場するのもiOSと異なる点でしょうか。
値は固定値ということで、隠蔽されているのかもしれませんね。

なお、scanNewDevice()でグレーになっているところはver.5.0以降で使用する部分なのですが、今手元に実機がないため動作確認はしていません。
この辺りはプレビュー版をインストールするか、Nexus7にver.5.0の正式版が来れば改めて作成、確認します。

【参考】

BLE