BLE デバイスを操作するには、まずそのデバイスに接続します。具体的には、デバイスの GATT サーバーに接続します。BLE デバイス上の GATT サーバーに接続するには、connectGatt()
メソッドを使用します。このメソッドは、Context
オブジェクト、autoConnect
(BLE デバイスが利用可能になり次第、自動的に接続するかどうかを示すブール値)、BluetoothGattCallback
への参照という 3 つのパラメータを受け取ります。
Kotlin
var bluetoothGatt: BluetoothGatt? = null ... bluetoothGatt = device.connectGatt(this, false, gattCallback)
Java
bluetoothGatt = device.connectGatt(this, false, gattCallback);
これにより、BLE デバイスでホストされている GATT サーバーに接続し、BluetoothGatt
インスタンスを返します。これを使用して、GATT クライアント オペレーションを実行できます。呼び出し元(Android アプリ)は GATT クライアントです。BluetoothGattCallback
は、接続ステータスや、以降の GATT クライアント オペレーションなどの結果をクライアントに配信するために使用されます。
バインドされたサービスを設定する
次の例で、BLE アプリはアクティビティ(DeviceControlActivity
)を提供し、Bluetooth デバイスへの接続、デバイスのデータの表示、デバイスでサポートされている GATT サービスと特性の表示を行います。このアクティビティは、ユーザー入力に基づいて、BluetoothLeService
という Service
と通信します。これは BLE API を介して BLE デバイスと通信します。通信はバインドされたサービスを使用して行われ、アクティビティは BluetoothLeService
に接続し、関数を呼び出してデバイスに接続します。BluetoothLeService
には、アクティビティのサービスへのアクセスを提供する Binder
実装が必要です。
Kotlin
class BluetoothLeService : Service() { private val binder = LocalBinder() override fun onBind(intent: Intent): IBinder? { return binder } inner class LocalBinder : Binder() { fun getService() : BluetoothLeService { return this@BluetoothLeService } } }
Java
class BluetoothLeService extends Service { private Binder binder = new LocalBinder(); @Nullable @Override public IBinder onBind(Intent intent) { return binder; } class LocalBinder extends Binder { public BluetoothLeService getService() { return BluetoothLeService.this; } } }
アクティビティは、bindService()
を使用してサービスを起動し、サービスを開始する Intent
、接続イベントと切断イベントをリッスンする ServiceConnection
実装、追加の接続オプションを指定するフラグを渡すことができます。
Kotlin
class DeviceControlActivity : AppCompatActivity() { private var bluetoothService : BluetoothLeService? = null // Code to manage Service lifecycle. private val serviceConnection: ServiceConnection = object : ServiceConnection { override fun onServiceConnected( componentName: ComponentName, service: IBinder ) { bluetoothService = (service as LocalBinder).getService() bluetoothService?.let { bluetooth -> // call functions on service to check connection and connect to devices } } override fun onServiceDisconnected(componentName: ComponentName) { bluetoothService = null } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.gatt_services_characteristics) val gattServiceIntent = Intent(this, BluetoothLeService::class.java) bindService(gattServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE) } }
Java
class DeviceControlActivity extends AppCompatActivity { private BluetoothLeService bluetoothService; private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { bluetoothService = ((LocalBinder) service).getService(); if (bluetoothService != null) { // call functions on service to check connection and connect to devices } } @Override public void onServiceDisconnected(ComponentName name) { bluetoothService = null; } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.gatt_services_characteristics); Intent gattServiceIntent = new Intent(this, BluetoothLeService.class); bindService(gattServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE); } }
BluetoothAdapter をセットアップする
バインドされたサービスは、BluetoothAdapter
にアクセスする必要があります。アダプターがデバイスで使用できることを確認します。BluetoothAdapter
について詳しくは、Bluetooth をセットアップするをご覧ください。次の例では、このセットアップ コードを、成功を示す Boolean
値を返す initialize()
関数でラップしています。
Kotlin
private const val TAG = "BluetoothLeService" class BluetoothLeService : Service() { private var bluetoothAdapter: BluetoothAdapter? = null fun initialize(): Boolean { bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() if (bluetoothAdapter == null) { Log.e(TAG, "Unable to obtain a BluetoothAdapter.") return false } return true } ... }
Java
class BluetoothLeService extends Service { public static final String TAG = "BluetoothLeService"; private BluetoothAdapter bluetoothAdapter; public boolean initialize() { bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (bluetoothAdapter == null) { Log.e(TAG, "Unable to obtain a BluetoothAdapter."); return false; } return true; } ... }
アクティビティは、ServiceConnection
実装内でこの関数を呼び出します。initialize()
関数からの偽の戻り値を処理する方法は、アプリによって異なります。現在のデバイスが Bluetooth オペレーションをサポートしていないことや、Bluetooth が動作するために必要な機能を無効にすることを示すエラー メッセージをユーザーに表示することもできます。次の例では、ユーザーを前の画面に戻すために、アクティビティで finish()
が呼び出されます。
Kotlin
class DeviceControlActivity : AppCompatActivity() { // Code to manage Service lifecycle. private val serviceConnection: ServiceConnection = object : ServiceConnection { override fun onServiceConnected( componentName: ComponentName, service: IBinder ) { bluetoothService = (service as LocalBinder).getService() bluetoothService?.let { bluetooth -> if (!bluetooth.initialize()) { Log.e(TAG, "Unable to initialize Bluetooth") finish() } // perform device connection } } override fun onServiceDisconnected(componentName: ComponentName) { bluetoothService = null } } ... }
Java
class DeviceControlsActivity extends AppCompatActivity { private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { bluetoothService = ((LocalBinder) service).getService(); if (bluetoothService != null) { if (!bluetoothService.initialize()) { Log.e(TAG, "Unable to initialize Bluetooth"); finish(); } // perform device connection } } @Override public void onServiceDisconnected(ComponentName name) { bluetoothService = null; } }; ... }
デバイスに接続
BluetoothService
は初期化されると、BLE デバイスに接続できるようになります。
アクティビティは、デバイスのアドレスをサービスに送信して、接続を開始できるようにする必要があります。サービスは最初に BluetoothAdapter
で getRemoteDevice()
を呼び出して、デバイスにアクセスします。アダプターがそのアドレスを持つデバイスを検出できない場合、getRemoteDevice()
は IllegalArgumentException
をスローします。
Kotlin
fun connect(address: String): Boolean { bluetoothAdapter?.let { adapter -> try { val device = adapter.getRemoteDevice(address) } catch (exception: IllegalArgumentException) { Log.w(TAG, "Device not found with provided address.") return false } // connect to the GATT server on the device } ?: run { Log.w(TAG, "BluetoothAdapter not initialized") return false } }
Java
public boolean connect(final String address) { if (bluetoothAdapter == null || address == null) { Log.w(TAG, "BluetoothAdapter not initialized or unspecified address."); return false; } try { final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address); } catch (IllegalArgumentException exception) { Log.w(TAG, "Device not found with provided address."); return false; } // connect to the GATT server on the device }
サービスが初期化されると、DeviceControlActivity
はこの connect()
関数を呼び出します。アクティビティは、BLE デバイスのアドレスを渡す必要があります。次の例では、デバイスの住所がインテント エクストラとしてアクティビティに渡されます。
Kotlin
// Code to manage Service lifecycle. private val serviceConnection: ServiceConnection = object : ServiceConnection { override fun onServiceConnected( componentName: ComponentName, service: IBinder ) { bluetoothService = (service as LocalBinder).getService() bluetoothService?.let { bluetooth -> if (!bluetooth.initialize()) { Log.e(TAG, "Unable to initialize Bluetooth") finish() } // perform device connection bluetooth.connect(deviceAddress) } } override fun onServiceDisconnected(componentName: ComponentName) { bluetoothService = null } }
Java
private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { bluetoothService = ((LocalBinder) service).getService(); if (bluetoothService != null) { if (!bluetoothService.initialize()) { Log.e(TAG, "Unable to initialize Bluetooth"); finish(); } // perform device connection bluetoothService.connect(deviceAddress); } } @Override public void onServiceDisconnected(ComponentName name) { bluetoothService = null; } };
GATT コールバックを宣言する
アクティビティからサービスに接続するデバイスを指示し、サービスからデバイスに接続すると、サービスは BLE デバイスの GATT サーバーに接続する必要があります。この接続では、接続状態、サービス ディスカバリ、特性読み取り、特性通知に関する通知を受信するために、BluetoothGattCallback
が必要です。
このトピックでは、接続状態の通知に焦点を当てます。サービス ディスカバリ、特性読み取り、特性通知のリクエスト方法については、BLE データの転送をご覧ください。
onConnectionStateChange()
関数は、デバイスの GATT サーバーへの接続が変更されるとトリガーされます。次の例では、コールバックが Service
クラスで定義されているため、サービスが接続したときに BluetoothDevice
で使用できます。
Kotlin
private val bluetoothGattCallback = object : BluetoothGattCallback() { override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) { if (newState == BluetoothProfile.STATE_CONNECTED) { // successfully connected to the GATT Server } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { // disconnected from the GATT Server } } }
Java
private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_CONNECTED) { // successfully connected to the GATT Server } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { // disconnected from the GATT Server } } };
GATT サービスに接続する
BluetoothGattCallback
が宣言されると、サービスは connect()
関数の BluetoothDevice
オブジェクトを使用してデバイスの GATT サービスに接続できます。
connectGatt()
関数が使用されます。そのためには、Context
オブジェクト、autoConnect
ブール値フラグ、BluetoothGattCallback
が必要です。この例では、アプリが BLE デバイスに直接接続しているため、autoConnect
に false
が渡されます。
BluetoothGatt
プロパティも追加されています。これにより、サービスは不要になったときに接続を閉じることができます。
Kotlin
class BluetoothLeService : Service() { ... private var bluetoothGatt: BluetoothGatt? = null ... fun connect(address: String): Boolean { bluetoothAdapter?.let { adapter -> try { val device = adapter.getRemoteDevice(address) // connect to the GATT server on the device bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback) return true } catch (exception: IllegalArgumentException) { Log.w(TAG, "Device not found with provided address. Unable to connect.") return false } } ?: run { Log.w(TAG, "BluetoothAdapter not initialized") return false } } }
Java
class BluetoothService extends Service { ... private BluetoothGatt bluetoothGatt; ... public boolean connect(final String address) { if (bluetoothAdapter == null || address == null) { Log.w(TAG, "BluetoothAdapter not initialized or unspecified address."); return false; } try { final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address); // connect to the GATT server on the device bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback); return true; } catch (IllegalArgumentException exception) { Log.w(TAG, "Device not found with provided address. Unable to connect."); return false; } } }
ブロードキャストの更新情報
サーバーが GATT サーバーに接続または切断したときに、アクティビティに新しい状態を通知する必要があります。これにはいくつかの方法があります。次の例では、ブロードキャストを使用して、サービスからアクティビティに情報を送信します。
サービスは、新しい状態をブロードキャストする関数を宣言します。この関数は、システムにブロードキャストされる前に、Intent
オブジェクトに渡されるアクション文字列を受け取ります。
Kotlin
private fun broadcastUpdate(action: String) { val intent = Intent(action) sendBroadcast(intent) }
Java
private void broadcastUpdate(final String action) { final Intent intent = new Intent(action); sendBroadcast(intent); }
ブロードキャスト関数を実装すると、BluetoothGattCallback
内で使用され、GATT サーバーとの接続状態に関する情報が送信されます。定数とサービスの現在の接続状態は、Intent
アクションを表すサービスで宣言されます。
Kotlin
class BluetoothService : Service() { private var connectionState = STATE_DISCONNECTED private val bluetoothGattCallback: BluetoothGattCallback = object : BluetoothGattCallback() { override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { if (newState == BluetoothProfile.STATE_CONNECTED) { // successfully connected to the GATT Server connectionState = STATE_CONNECTED broadcastUpdate(ACTION_GATT_CONNECTED) } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { // disconnected from the GATT Server connectionState = STATE_DISCONNECTED broadcastUpdate(ACTION_GATT_DISCONNECTED) } } } ... companion object { const val ACTION_GATT_CONNECTED = "com.example.bluetooth.le.ACTION_GATT_CONNECTED" const val ACTION_GATT_DISCONNECTED = "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED" private const val STATE_DISCONNECTED = 0 private const val STATE_CONNECTED = 2 } }
Java
class BluetoothService extends Service { public final static String ACTION_GATT_CONNECTED = "com.example.bluetooth.le.ACTION_GATT_CONNECTED"; public final static String ACTION_GATT_DISCONNECTED = "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED"; private static final int STATE_DISCONNECTED = 0; private static final int STATE_CONNECTED = 2; private int connectionState; ... private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_CONNECTED) { // successfully connected to the GATT Server connectionState = STATE_CONNECTED; broadcastUpdate(ACTION_GATT_CONNECTED); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { // disconnected from the GATT Server connectionState = STATE_DISCONNECTED; broadcastUpdate(ACTION_GATT_DISCONNECTED); } } }; … }
アクティビティの更新をリッスンする
サービスが接続の更新をブロードキャストしたら、アクティビティは BroadcastReceiver
を実装する必要があります。アクティビティの設定時にこのレシーバーを登録し、アクティビティが画面から離れるときに登録を解除する。サービスからのイベントをリッスンすることにより、アクティビティは BLE デバイスとの現在の接続状態に基づいてユーザー インターフェースを更新できます。
Kotlin
class DeviceControlActivity : AppCompatActivity() { ... private val gattUpdateReceiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { when (intent.action) { BluetoothLeService.ACTION_GATT_CONNECTED -> { connected = true updateConnectionState(R.string.connected) } BluetoothLeService.ACTION_GATT_DISCONNECTED -> { connected = false updateConnectionState(R.string.disconnected) } } } } override fun onResume() { super.onResume() registerReceiver(gattUpdateReceiver, makeGattUpdateIntentFilter()) if (bluetoothService != null) { val result = bluetoothService!!.connect(deviceAddress) Log.d(DeviceControlsActivity.TAG, "Connect request result=$result") } } override fun onPause() { super.onPause() unregisterReceiver(gattUpdateReceiver) } private fun makeGattUpdateIntentFilter(): IntentFilter? { return IntentFilter().apply { addAction(BluetoothLeService.ACTION_GATT_CONNECTED) addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED) } } }
Java
class DeviceControlsActivity extends AppCompatActivity { ... private final BroadcastReceiver gattUpdateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) { connected = true; updateConnectionState(R.string.connected); } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) { connected = false; updateConnectionState(R.string.disconnected); } } }; @Override protected void onResume() { super.onResume(); registerReceiver(gattUpdateReceiver, makeGattUpdateIntentFilter()); if (bluetoothService != null) { final boolean result = bluetoothService.connect(deviceAddress); Log.d(TAG, "Connect request result=" + result); } } @Override protected void onPause() { super.onPause(); unregisterReceiver(gattUpdateReceiver); } private static IntentFilter makeGattUpdateIntentFilter() { final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED); intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED); return intentFilter; } }
BLE データの転送では、BroadcastReceiver
を使用してサービス ディスカバリとデバイスからの特性データも伝えられます。
GATT 接続を閉じる
Bluetooth 接続を処理する際の重要な手順の 1 つに、完了したら接続を閉じることがあります。そのためには、BluetoothGatt
オブジェクトで close()
関数を呼び出します。次の例では、サービスは BluetoothGatt
への参照を保持しています。アクティビティがサービスからバインド解除されると、デバイスのバッテリーの消耗を避けるため、接続が閉じられます。
Kotlin
class BluetoothLeService : Service() { ... override fun onUnbind(intent: Intent?): Boolean { close() return super.onUnbind(intent) } private fun close() { bluetoothGatt?.let { gatt -> gatt.close() bluetoothGatt = null } } }
Java
class BluetoothService extends Service { ... @Override public boolean onUnbind(Intent intent) { close(); return super.onUnbind(intent); } private void close() { if (bluetoothGatt == null) { Return; } bluetoothGatt.close(); bluetoothGatt = null; } }