Android低功耗ブルートゥースGatt接続チュートリアル:Kotlinを使用して実装
以前の仕事で実装した低功耗ブルートゥース接続を復習するのに少し時間をかけました。
忘れてしまうのが心配なので、
再度見直して記録を残したいと思います。
実装が必要な方々にも役立つことを願っています。
Android 12以降では権限関連の処理が追加されましたので、注意してください!
ここでは私の処理方法を紹介しますので、参考にしてください:
最終目標はこのようになります
以前のjetpack composeの練習と連携させて
データを実際のデータに変換し
最終的にgattブルートゥースに接続できるようにします
まずは
ブルートゥーススキャンの方法を紹介します
大まかに3種類あります
BluetoothAdapter.startDiscovery() -> クラシックブルートゥースとBLEブルートゥースの両方をスキャン
BluetoothAdapter.startLeScan() -> 低功耗ブルートゥースをスキャンするためのもの —- 廃止されました
BluetoothLeScanner.startScan() -> 新しいBLEスキャン方法
しかし、API内の注釈を見ると
現在startLeScanは廃止されています
API21で廃止されました
各ブルートゥースデバイスの発見APIを比較してみました
- スキャンプロセスは通常12秒間実行されます
- 非同期呼び出しです
-
異なるステップを実行するためにブロードキャストを登録します。例:
ACTION_DISCOVERY_STARTED -> Discoveryが開始されたとき
ACTION_DISCOVERY_FINISHED -> Discoveryが完了したとき
BluetoothDevice.ACTION_FOUND -> ブルートゥースデバイスが発見されたとき
-
ブルートゥースデバイスに接続する際には
startDiscovery中であってはいけません
発見を終了するためにcancelDiscovery()を呼び出す必要があります
-
DiscoveryはActivityによって管理されていません
それはシステムサービスです
したがって、万が一に備えてcancelDiscovery()を使用する必要があります
Discoveryが実行されていないことを確認し
ブルートゥースデバイスに接続する際に
デバイスがDiscovery中でないことを確認します
- Discoveryは現在発見可能なブルートゥースデバイスのみを発見できます ```markdown
-
ACTION_STATE_CHANGEDがSTATE_ONであるかを観察する
現在のBluetoothの状態がSTATE_ONでない場合、APIはfalseを返します
現在が更新可能な値の状態であることを確認するために使用します
-
使用するターゲットバージョンがBuild.VERSION_CODES#R以下の場合
ユーザーにManifest.permission#BLUETOOTH_ADMIN権限を要求する必要があります
-
使用するターゲットバージョンがBuild.VERSION_CODES#S以上の場合
ユーザーにManifest.permission#BLUETOOTH_SCAN権限を要求する必要があります
-
それ以外の場合
Manifest.permission#ACCESS_FINE_LOCATION権限を要求することができます
インタラクティブなBluetoothデバイスの種類を増やすために
もちろん、uses-permissionにusesPermissionFlags="neverForLocation"タグを追加して
位置情報権限の要求を避けることもできます
しかし、その場合、検出できるデバイスの種類が制限されます
- Bluetooth LEスキャンを開始し、スキャン結果はcallbackを通じて返されます
- フィルターがないため、
省電力のために画面がオフになるとstopScanし、
再度オンにするとresumeします - 使用するターゲットバージョンがBuild.VERSION_CODES#Q以上の場合、
ユーザーにManifest.permission#ACCESS_FINE_LOCATION権限を要求する必要があります -
使用するターゲットバージョンがBuild.VERSION_CODES#R以下の場合、
ユーザーにManifest.permission#BLUETOOTH_ADMIN権限を要求する必要があります - 使用するターゲットバージョンがBuild.VERSION_CODES#S以上の場合、
ユーザーにManifest.permission#BLUETOOTH_SCAN権限を要求する必要があります
```
</li>
- それ以外にも、Manifest.permission#ACCESS_FINE_LOCATION権限を要求することができます。
これにより、インタラクティブなBluetoothデバイスの種類が増えます。
もちろん、<uses-permission>タグにusesPermissionFlags="neverForLocation"を追加して、
位置情報の権限を要求しないようにすることもできますが、
その場合、検索できるデバイスの種類が制限されます。
manifestに上記の必要な権限を追加します。
以下に拡張機能を書きました。
汎用的に使用できます。
requestMultiplePermissions(Manifest.permission.ACCESS_FINE_LOCATION,...
private val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
BluetoothDevice.ACTION_FOUNDを監視するために登録します。
val filter = IntentFilter(BluetoothDevice.ACTION_FOUND)
requireContext().registerReceiver(receiver, filter)
BroadcastReceiverを継承します。
そして、receiverタイプの形式で結果をbleDeviceとして返します。
private val receiver = DeviceListBoardCast { bleDevice ->
deviceViewModel.addDevice(bleDevice)
}
資料を取得する方法として、スキャンされたデータは以下から取得できます
val device: BluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)!!
val rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE).toInt()
val uuidExtra = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID)
継承したBroadcastReceiverの実装
前述の通り、bluetoothAdapterを取得し、ブロードキャストリスナーを登録しました
したがって、startDiscoveryとcancelDiscoveryを使用してスキャンを開始または終了できます
bluetoothAdapter.startDiscovery()
bluetoothAdapter.cancelDiscovery()
この関数は
主にスキャンのオンオフを行います
viewmodelとcoroutineを組み合わせて
viewmodelでリフレッシュ状態を記録し、coroutineで指定秒数 x 秒スキャンを行います
それほど複雑にする必要がない場合は
startDiscoveryとcancelDiscoveryを直接使用して開発するだけで十分です
スキャンの結果は先ほどのDeviceListBoardCast {}内に返されます
ここはプロジェクトに応じて調整してください
私はviewmodelを使用してデータを観察しています
private val receiver = DeviceListBoardCast { bleDevice ->
deviceViewModel.addDevice(bleDevice)
}
ここではserviceを使用して接続します
まず、serviceを作成します
次にBinderを作成します
onBind時にインスタンスをfragmentに返すために使用します
そのservice内でinitialize()関数を作成します
後でbindservice時に初期化を呼び出すために使用します
次にgattCallbackインスタンスを作成します
ここでは主にonConnectionStateChange、onServicesDiscovered、onCharacteristicReadを使用します
それぞれ、新しい接続状態の変更、新しいサービスの発見、新しいデータの読み取り後に
返されるコールバックです
ここでは主にあなたのニーズに応じて
判断を行います
1. onConnectionStateChange -> Bluetoothの状態を返します
2. discoverServices()を実行して既存のBLEを探します
見つかった場合はonServicesDiscoveredに進みます
3. setCharacteristicNotificationというメソッドがあります
これは通知を有効にするためのものです
特定のCharacteristicを探します
(ここでのCharacteristicはハードウェアのプロトコルや定義に依存します)
Bluetoothデバイスの値が変更されると、onCharacteristicChangedで通知されます。
4. 次に、writeCharacteristicを使用すると、指定されたCharacteristicに値を書き込むことができます。
結果があると、
onCharacteristicWriteに入ります。
gattCallbackの例:
connect関数を作成します。
実際には、以下の2つの部分で接続を行います。
val device = bluetoothAdapter!!.getRemoteDevice(address)
と
bluetoothGatt = device.connectGatt(this, false, gattCallback)
接続したいアドレスを渡し、
接続したいBluetoothDeviceを取得します。
次に、deviceのconnectGattメソッドを使用してGattデバイスをバインドします。
もちろん、前に作成したgattCallbackも渡します。
前の部分は一連のnull確認を行うだけです。
アプリがnullによってクラッシュしないようにします。
gattCallbackのインスタンス化の中で、
broadcastUpdateというメソッドがあることに気づくでしょう。
このメソッドは主にブロードキャストメッセージを送信するためのものです。
自分のニーズに応じて、どのような状況で何をするか、
またはどのようなブロードキャストメッセージを返すかを定義できます。
簡単な接続とデバイスの検索
大体こんな感じです。
次に、Bluetoothで最も重要なのは端末間の通信です。
データを送受信したい場合は、
serviceとcharacteristicを見つける必要があります。
ここで図を示します。
これはBluetooth接続時のおおよその関係図です。
次の方法で見つけます:
前にブロードキャストで取得したgatt serviceを渡すと、
遍歴してcharacteristicを取得できます。
Android公式がcharacteristicのクラスを既に用意しているので、
読み取るには関連する関数を呼び出すだけです。
そして、以前に定義したBluetoothGattCallback内の
onCharacteristicReadで結果が返されます。
ブロードキャストを受信するように定義するだけでデータを取得できます。
さらに、Bluetoothにはnotifyメソッドもあります。
同様に結果が返されます。
BluetoothGattCallback内の
onCharacteristicChangedを確認します。
サードパーティツールを使用して
Bluetoothパケットをキャプチャする方法を見たい場合は、以下を参照してください。
Bluetooth 1.0 / 1.2 / 2.0+EDR / 2.1+EDR / 3.0+EDRなどの基礎上で発展・完成されたもの
Bluetooth 4.0未満のモジュールを指す
一般的にデータ量が多い伝送に使用される
例:音声、音楽、高データ量の伝送など
クラシックBluetoothモジュールはさらに細分化される
従来のBluetoothモジュールと高速Bluetoothモジュール
従来のBluetoothモジュールは2004年に登場
主にBluetooth 2.1プロトコルをサポートするモジュールが代表的
従来のBluetoothには3つの出力レベルがある
Class1 / Class2 / Class3
それぞれ100m / 10m / 1mの伝送距離をサポート
高速Bluetoothモジュールは2009年に登場
速度は約24Mbpsに向上
従来のBluetoothモジュールの8倍
Bluetooth 4.0以上のモジュールを指す
低消費電力Bluetooth技術は低コスト、短距離
2.4GHz ISM周波数帯で動作可能
BLE技術は非常に高速な接続方式を採用しているため
通常は「非接続」状態にしておくことができる(エネルギーを節約)
AndroidスマートフォンのBluetooth 4.xはすべてデュアルモードBluetooth(クラシックBluetoothと低消費電力Bluetoothの両方を持つ)
最後に、以前書いた例を最近整理してアップしました。必要な方は参考にしてください こちらを参照