【Kotlin/Android】Bluetoothのペアリング(ボンディング)を行う方法!createBond
この記事からわかること
- Android Studio/KotlinでBluetooth接続アプリの実装方法
- Central側の実装
- ペアリング(ボンディング)するには?
- createBondメソッドの使い方
- ACTION_BOND_STATE_CHANGEでボンディング状態の変化を検知する
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
環境
- Android Studio:Koala
- Kotlin:1.9.0
ペアリングとは?
例えばBluetoothイヤホンなどを使用する際に初回にBluetooth接続(ペアリング)を行えば次回からは自動で接続される経験があると思います。これは両方の機器がお互いを登録し合っているために2回目以降のペアリング作業を省略できるようになっています。
Bluetooth機器を使用しているとよく聞く「ペアリング」という言葉ですがこれはセントラルとペリフェラル間のデータ暗号化のための鍵の交換をすること自体を指します。一般的にはペアリングが機器同時の接続のようなニュアンスですが厳密にみると少し異なるので注意してください。
ボンディングとは?
一度ペアリング(鍵を交換)した機器がペアリングなしに接続できるようになるのは交換した鍵をキャッシュしているからです。そしてこの「暗号鍵をキャッシュしておき再度同じ相手と接続するときに、ペアリングを省略できる機能」のことを「ボンディング」と呼びます。
Androidアプリでボンディングする方法
記事タイトルにはあえてペアリングと書きましたがボンディングする方法をまとめていきます。(ペアリングは接続時に自動で行われるので特に触れません)
またセントラル側の実装は前回の記事を参考にしてください。この続きで今回は進めていきます。
前回の記事ではデバイスアドレスをローカルに保存することで再度ペアリングしなくても接続できるように実装していました。ただこれはボンディング機能を実装することでローカル保存処理を不要にすることができます。
実装はGitHubに公開しているので参考にしてください。
createBondメソッド
実際にボンディングを行うにはBluetoothDevice#createBondメソッド
を使用します。
// 対象機器とボンディングする
device.createBond()
サンプルコードではスキャン処理を実行して対象のペリフェラルを検出した際になどに呼び出しています。
private fun leScanCallback(): ScanCallback {
return object : ScanCallback() {
override fun onScanResult(
callbackType: Int,
result: ScanResult,
) {
super.onScanResult(callbackType, result)
result.device ?: return
// ペリフェラルデバイスが発見された
logArea.append( "デバイス「${result.device.name}」を検出\n")
// スキャンの停止
bluetoothLeScanner?.stopScan(scanCallback)
// デバイスアドレスを取得(接続処理に必要)
val deviceAddress = result.device.address
// ローカルにデバイスアドレスを保存
// sharedPreferencesManager.save(SharedPreferencesManager.ADDRESS_KEY, deviceAddress)
// 対象機器とボンディングする
result.device.createBond()
logArea.append( "スキャン停止\n")
}
}
}
createBond
を実行すると端末上にペアリングを許可するかどうかのポップアップが表示されます。ここで「ペア設定する」を選択することでボンディングが完了します。
ボンディングが完了するとAndroid端末の「設定アプリ」>「接続設定」>「保存済みのデバイス」にボンディングしているデバイス情報が表示されるようになります。ここから接続やボンディング情報の削除を行うことができるようになっています。
ボンディングしているデバイスと接続する
ボンディングしただけでは接続はできていないのでボンディングしているデバイスと接続する処理を実装していきます。端末でボンディングしている機器情報はBluetoothAdapter#bondedDevices
からリストで取得することが可能です。この中には他にボンディングしている機器の情報もあるのでアプリ内で使用するにはデバイス名などでフィルタリングをかけて対象のBluetoothDevice
を取得します。
// ボンディングしているデバイス一覧
bluetoothAdapter?.bondedDevices
// 対象のデバイスアドレスを取得する
val deviceAddress = bluetoothAdapter?.bondedDevices?.firstOrNull { it.name == BleServiceConfig.PERIPHERAL_NAME }?.address
デバイス情報が取得できれば接続処理が実行できるようになります。
/** ③ デバイスアドレスを元に接続処理 */
private fun connect(address: String) {
val device: BluetoothDevice? = bluetoothAdapter?.getRemoteDevice(address)
if (device == null) {
logArea.append("デバイス取得失敗\n")
return
}
logArea.append("対象デバイスと接続開始\n")
bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback)
}
ボンディング状態を識別する
BluetoothDevice
がボンディング済みかどうかはbondState
プロパティから識別することができます。取得できる値はint
型になっているのでBluetoothDevice
に定義されている定数と照らし合わせて状態を確認します。
public static final int BOND_BONDED = 12; // ボンディング済み
public static final int BOND_BONDING = 11; // ボンディング中
public static final int BOND_NONE = 10; // 未ボンディング
ボンディング状態の変化を検知する
ボンディングリクエストを行いポップアップが表示された際に「ペア設定をする」と「キャンセル」が選択できるようになっており、その後のボンディング状態の変化を検知することが可能です。
公式リファレンス:BluetoothDevice#ACTION_BOND_STATE_CHANGED
BluetoothDevice#ACTION_BOND_STATE_CHANGED
というBroadcast Actionが用意されているのでこれを使用すれば検知できました。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 〜〜〜〜〜〜〜〜〜〜〜〜〜
// ボンディング状態の変化をBroadcastで受け取るためのIntentFilter
val bondStateFilter = IntentFilter().apply {
addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
}
// レシーバーをセット
registerReceiver(bondingBroadcastReceiver, bondStateFilter)
}
結果はBroadcastReceiver
型のオブジェクトを作成して受け取ります。
/** ボンディング状態が変化した際に取得できるBroadcast */
private val bondingBroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
context ?: return
val action = intent?.action ?: return
when (action) {
BluetoothDevice.ACTION_BOND_STATE_CHANGED -> {
logArea.append("ボンディングBroadcast取得")
// ボンディング対象デバイスを取得する
val device =
intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
?: return
logArea.append("${device.address}")
val bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 0)
// ボンディング状態を識別
when (bondState) {
BluetoothDevice.BOND_BONDED -> {
logArea.append("ボンディング成功")
}
BluetoothDevice.BOND_BONDING -> {
logArea.append("ボンディング中")
}
BluetoothDevice.BOND_NONE -> {
logArea.append("ボンディング失敗(キャンセル)")
}
}
}
}
}
}
他にもペアリングをリクエストしたことを検知するACTION_PAIRING_REQUEST
などがあります。詳細は公式を参考にしてください。
コードからボンディング情報を削除する
ボンディング情報を削除するためのメソッドとしてBluetoothDevice#removeBond
がありますがこちらはprivate
で定義されているため開発者が呼び出して使用することは想定されていません。そのためコードからボンディング情報を削除することは不可能になっているようです。
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。