こんにちは。ECF Tech ブログ担当のmichiです。
今回から何回かに分けて、BLE(Bluetooth Low Energy)を使ってAndroidアプリとマイクロビットが通信をするまでを書いていきたいと思います。マイクロビットとAndroidアプリをBLEで接続することでこんなことができます。
BLE(Bluetooth Low Energy)とは
BLEは名前のとおり、省エネルギーでデバイス同士の通信を可能にするために策定された、IoTを意識した通信規格です。本記事ではBLEについて、Androidアプリとの動作上で最低限必要な部分の説明に留めたいと思います。詳しくご覧になりたい方は、技術者向けWebマガジン「Codezine」のこちらのサイトが参考になります。ご覧ください。
というわけで、本記事では次のような読者を想定しております。
- AndroidアプリでBLEを使用するためのサンプルプログラムが見たい
- マイクロビットとAndroidアプリをつなげるための方法を知りたい
Androidの実装方法はAndroid Developers内のドキュメンテーション(英語)を参考にしておりますが、同記事はAndroid4.3以降をベースに記載されたものとなっているため、やや実装方法が異なっています。
BLEの概要
でははじめに、通信のための簡単な流れを見ていきましょう。BLEでは通信に用いられる2台の端末をペリフェラル(peripheral)とセントラルと言います。ペリフェラルは周辺機器の意味です。セントラルは主にスマートフォンなどのペリフェラルから情報を受け取って処理をする機器を指します。
またこの2つはクライアントとサーバのような関係にあります。
例えば心拍数を計測するウォッチを腕につけ、そこからBLE通信により送られてくる情報をスマートフォンのアプリで表示するしくみを例に挙げると、ウォッチが一定時間ごとに心拍数の数値を送信するサービスを提供するサーバとしての役割を果たし、スマートフォン側はその情報を受け取ってアプリケーションで利用するクライアントの役割となります。
本記事で紹介する接続例でも、マイクロビットがサーバの役割を果たし、スマートフォンがクライアントの役割を果たします。接続までの流れは概ね次のようになります。
では、1つ1つを順に見ていきましょう。まずはマイクロビット側が行なうアドバタイジング(Advertising:宣伝する)です。アドバタイジングは周辺機器がその存在を知らせるために行なう通信です。マイクロビットの場合、あらかじめ下記のようにプログラムを準備しておきます。
一方のアプリ側ではスキャニング(scanning)を行ないます。Androidアプリにおいてこのスキャニングを行なうまでの手順は次のようになります。
スキャニングまでのプログラム
まずはじめに、Android ManifestによるPermissionの設定です。最低限下記3つのPermission設定がなされていなければいけません。
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
Bluetoothに関連するPermissionと位置情報に関するPermissionです。位置情報に関するPermissionはAndroid 5.0(API Level 21)より塔載されたBluetoothLeScannerクラスを利用する場合は必須です。今回は本クラスを用いた構築となるため追加しています。もちろんこちらは、ACCESS_FINE_LOCATIONのPermissionを用いても構いません。
今回はMainActivityなクラスにすべて実装したコードで見ていきたいと思います。
onCreateメソッドで行っている処理から説明します。
まず最初にAndroid端末自体がBLE対応端末かどうかを調べています。対応端末でない場合は、適切なメッセージを出して、アプリを終了しています。
//BLE対応端末かどうかを調べる。対応していない場合はメッセージを出して終了 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show(); finish(); }
次にBluetoothAdapterを取得します。BLE接続を行なう際はBluetoothManagerを取得し、そこからアダプターを取り出す流れとなります。
//Bluetoothアダプターを初期化する BluetoothManager manager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE); adapter = manager.getAdapter();
次に該当端末が、Bluetooth接続を許可しているかを確認します。許可していない場合は、設定画面へ移動。許可している場合はボタンのテキストを「CONNECT」にして示しています。
//bluetoothの使用が許可されていない場合は許可を求める。 if( adapter == null || !adapter.isEnabled() ){ Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(intent,PERMISSION_REQUEST); } else{ Button button = findViewById(R.id.button_connect); button.setText("CONNECT"); button.setEnabled(true); }
ボタンを押した時の処理は以下です。
BluetoothAdapterから、BLE用のスキャナ(BluetoothLeScanner)インスタンスを取得します。
その次の行は、スキャン成功(BLE端末が見つかった)際にコールされるコールバックメソッドを実装したクラスのインスタンスを生成します。同クラスのソースは後ほど。
scanner = adapter.getBluetoothLeScanner(); scancallback = new MyScancallback();
下記でスキャンを開始します。Handlerを使って別スレッドにてスキャニングを停止するメソッドを10秒後(SCAN_PERIODに定義済み)にコールしています。startScanメソッドでスキャンを開始するとstopScanメソッドでスキャンを停止しなければいけないためです。
//スキャニングを10秒後に停止 handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { scanner.stopScan(scancallback); } }, SCAN_PERIOD); //スキャンの開始 scanner.startScan(scancallback);
スキャンが成功した時にコールされるコールバックメソッドを実装したクラスが下記です。
class MyScancallback extends ScanCallback{ @Override public void onScanResult(int callbackType, ScanResult result) { Log.d("scanResult","start"); if( mScanned == true ) return; if( result.getDevice() == null ) return; if( result.getDevice().getName() == null )return; if( result.getDevice().getName().contains("BBC micro:bit") ){ device = result.getDevice(); mScanned = true; runOnUiThread(new Runnable() { @Override public void run() { Button button = findViewById(R.id.button_connect); button.setText("GETTING"); button.setEnabled(true); } }); scanner.stopScan(scancallback); return; } } @Override public void onBatchScanResults(List<ScanResult> results) { } @Override public void onScanFailed(int errorCode) { } }
とりあえず動作する実装を目指したので、成功時にバックするonScanResultメソッドだけを実装しています m( _ _ )m。
最初のいくつかのifはBLE端末が取得出来ているかをチェックしています。マイクロビットは識別名として「BBC micro:bit」を含んだ文字列を返すので、これによりマイクロビット端末であることを判断しています。
if( result.getDevice() == null ) return; if( result.getDevice().getName() == null )return; if( result.getDevice().getName().contains("BBC micro:bit") ){
その後はデバイス情報をフィールド deviceに保持して、メインスレッドでボタンのテキスト文字列を変化させています。最後にスキャン作業をストップさせて終了です。
//BLE端末情報の保持 device = result.getDevice(); mScanned = true; //UIスレッドでボタン名称変更 runOnUiThread(new Runnable() { @Override public void run() { Button button = findViewById(R.id.button_connect); button.setText("GETTING"); button.setEnabled(true); } }); //スキャン停止 scanner.stopScan(scancallback); return;
BLE端末(マイクロビット)の情報が取得出来たら、次にその端末が提供してくれるサービスの情報を取得します。コード中に登場する「GATT(General ATTribute Profile)」はBLE端末とのデータ送受信に用いられるプロトコルです。GATTサービスなどと呼ばれます。今回は、再度画面上のボタンを押した際に発動するようにしました。ボタンを押した際の処理は以下です。
if (device != null) { gattCallback = new MyGattcallback(); device.connectGatt(this, false, gattCallback); }
MyGattCallbackは、BLE端末からサービスが取得出来た際にコールされるコールバックメソッドの実装クラスです。次行の connectGattメソッドでGATTサービスへのアクセスを開始します。
無事接続が行われると、BluetoothGattCallbackインタフェースを実装した次のメソッドがコールされます。
@Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { Log.d("onConnect","change"); if( newState == BluetoothProfile.STATE_CONNECTED){ gatt.discoverServices(); } }
メソッド内でステータスが接続状態(BluetoothProfile.STATE_CONNECTED)になったら、使用可能なサービスを取得します。
ここまでがAndroidとBLE端末を接続する際の基本の流れになります。ここ以降はマイクロビット端末固有の処理になっていきますので、一旦ここまでとさせて頂きます。m( _ _ )m
全体のソースコードの閲覧を希望の際は、githubのこちらでご確認ください。