KC Blog

Android低功耗ブルートゥースGatt接続チュートリアル:Kotlinを使用して実装

15 min read
AndroidDev#Android#Bluetooth

前書き

以前の仕事で実装した低功耗ブルートゥース接続を復習するのに少し時間をかけました。

忘れてしまうのが心配なので、

再度見直して記録を残したいと思います。

実装が必要な方々にも役立つことを願っています。

Android 12以降では権限関連の処理が追加されましたので、注意してください!

ここでは私の処理方法を紹介しますので、参考にしてください:

最終目標はこのようになります

以前のjetpack composeの練習と連携させて

データを実際のデータに変換し

最終的にgattブルートゥースに接続できるようにします

bluetooth

基本概念

まずは

ブルートゥーススキャンの方法を紹介します

大まかに3種類あります

BluetoothAdapter.startDiscovery() -> クラシックブルートゥースとBLEブルートゥースの両方をスキャン

BluetoothAdapter.startLeScan() -> 低功耗ブルートゥースをスキャンするためのもの ---- 廃止されました

BluetoothLeScanner.startScan() -> 新しいBLEスキャン方法

しかし、API内の注釈を見ると

現在startLeScanは廃止されています

API21で廃止されました

各ブルートゥースデバイスの発見APIを比較してみました

fun startDiscovery ():boolean
  1. スキャンプロセスは通常12秒間実行されます
  2. 非同期呼び出しです
  3. 異なるステップを実行するためにブロードキャストを登録します。例:
                ACTION_DISCOVERY_STARTED -> Discoveryが開始されたとき 
    
                ACTION_DISCOVERY_FINISHED -> Discoveryが完了したとき
    
                BluetoothDevice.ACTION_FOUND -> ブルートゥースデバイスが発見されたとき 
    
          </a>
        </li>
    
        <li>
          <a href="javascript:void(0)">ブルートゥースデバイスに接続する際には
    
          startDiscovery中であってはいけません
    
          発見を終了するためにcancelDiscovery()を呼び出す必要があります
    
          </a>
        </li>
    
        <li>
          <a href="javascript:void(0)">
          DiscoveryはActivityによって管理されていません
    
          それはシステムサービスです
    
          したがって、万が一に備えてcancelDiscovery()を使用する必要があります
    
          Discoveryが実行されていないことを確認し
    
          ブルートゥースデバイスに接続する際に
    
          デバイスがDiscovery中でないことを確認します
    
          </a>
        </li>
        <li>
          <a href="javascript:void(0)">Discoveryは現在発見可能なブルートゥースデバイスのみを発見できます
          </a>
        </li>
    
    <li>
      <a href="javascript:void(0)">ACTION_STATE_CHANGEDがSTATE_ONであるかを観察する
            現在のBluetoothの状態がSTATE_ONでない場合、APIはfalseを返します
    
            現在が更新可能な値の状態であることを確認するために使用します
    
            <img src="/images/bluetooth/android_state.png" alt="Cover" class="w-full prose-img">
      </a>
    </li>
    
    <li>
      <a href="javascript:void(0)">使用するターゲットバージョンがBuild.VERSION_CODES#R以下の場合
    
            ユーザーにManifest.permission#BLUETOOTH_ADMIN権限を要求する必要があります
    
            <img src="/images/bluetooth/android_R.png" alt="Cover" class="w-full prose-img" >
      </a>
    </li>
    
    <li>
      <a href="javascript:void(0)">
        使用するターゲットバージョンがBuild.VERSION_CODES#S以上の場合
    
        ユーザーにManifest.permission#BLUETOOTH_SCAN権限を要求する必要があります
    
        <img src="/images/bluetooth/android_S.png" alt="Cover" class="w-full prose-img" >
      </a>
    </li>
    
    <li>
      <a href="javascript:void(0)">それ以外の場合
    
      Manifest.permission#ACCESS_FINE_LOCATION権限を要求することができます
    
      インタラクティブなBluetoothデバイスの種類を増やすために
    
      もちろん、<b>uses-permission</b>にusesPermissionFlags="neverForLocation"タグを追加して
    
      位置情報権限の要求を避けることもできます
    
      しかし、その場合、検出できるデバイスの種類が制限されます
    
      </a>
    </li>
    </ol>
    </div>
    
    <div class="c-border-content-title-4">fun startScan ( callback:ScanCallback )</div>
    <div class="table_container">
    <ol class="rectangle-list">
      <li><a href="javascript:void(0)">Bluetooth LEスキャンを開始し、スキャン結果はcallbackを通じて返されます</a></li>
      <li><a href="javascript:void(0)">フィルターがないため、
    
      省電力のために画面がオフになるとstopScanし、
    
      再度オンにするとresumeします</a></li>
      <li><a href="javascript:void(0)">使用するターゲットバージョンがBuild.VERSION_CODES#Q以上の場合、
    
      ユーザーにManifest.permission#ACCESS_FINE_LOCATION権限を要求する必要があります</a></li>
      <li>
      <a href="javascript:void(0)">使用するターゲットバージョンがBuild.VERSION_CODES#R以下の場合、
    
      ユーザーにManifest.permission#BLUETOOTH_ADMIN権限を要求する必要があります
        <img src="/images/bluetooth/android_R.png" alt="Cover" class="w-full prose-img">
      </a>
      </li>
      <li><a href="javascript:void(0)">使用するターゲットバージョンがBuild.VERSION_CODES#S以上の場合、
    
      ユーザーにManifest.permission#BLUETOOTH_SCAN権限を要求する必要があります
      <img src="/images/bluetooth/android_S.png" alt="Cover" class="w-full prose-img">
      </a>
    </li>
    
      </li>
      <li><a href="javascript:void(0)">それ以外にも、Manifest.permission#ACCESS_FINE_LOCATION権限を要求することができます。
    
      これにより、インタラクティブなBluetoothデバイスの種類が増えます。
    
      もちろん、&lt;uses-permission&gt;タグにusesPermissionFlags="neverForLocation"を追加して、
    
      位置情報の権限を要求しないようにすることもできますが、
    
      その場合、検索できるデバイスの種類が制限されます。</a></li>
    
fun startScan(filters:List<ScanFilter>,settings:ScanSettings,callback:ScanCallback)

実際の開発:Bluetoothスキャンの方法

manifestに上記の必要な権限を追加します。

コード内で権限を要求する
以下に拡張機能を書きました。

汎用的に使用できます。

  requestMultiplePermissions(Manifest.permission.ACCESS_FINE_LOCATION,...
BluetoothAdapterインスタンスの取得
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)
    }

実際の開発:Bluetooth接続の方法

主な概念は、アプリのローカルサービスを構築し、Bluetoothとバインドすると相互に通信できるようにすることです

ここではserviceを使用して接続します

まず、serviceを作成します

次にBinderを作成します

onBind時にインスタンスをfragmentに返すために使用します

必要なクラスを初期化する

そのservice内でinitialize()関数を作成します

後でbindservice時に初期化を呼び出すために使用します

コールバックを作成し、Bluetoothの状態が返されたときに受信できるようにする

次にgattCallbackインスタンスを作成します

ここでは主にonConnectionStateChange、onServicesDiscovered、onCharacteristicReadを使用します

それぞれ、新しい接続状態の変更、新しいサービスの発見、新しいデータの読み取り後に

返されるコールバックです

ここでは主にあなたのニーズに応じて

判断を行います

この段階で接続中に考えられる状況を記録します:
  1. onConnectionStateChange -> Bluetoothの状態を返します

  2. discoverServices()を実行して既存のBLEを探します

見つかった場合はonServicesDiscoveredに進みます

  1. setCharacteristicNotificationというメソッドがあります

これは通知を有効にするためのものです

特定のCharacteristicを探します

(ここでのCharacteristicはハードウェアのプロトコルや定義に依存します)

Bluetoothデバイスの値が変更されると、onCharacteristicChangedで通知されます。

  1. 次に、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を見つける必要があります。

ここで図を示します。

Cover これはBluetooth接続時のおおよその関係図です。

次の方法で見つけます:

前にブロードキャストで取得したgatt serviceを渡すと、

遍歴してcharacteristicを取得できます。

Android公式がcharacteristicのクラスを既に用意しているので、

読み取るには関連する関数を呼び出すだけです。

そして、以前に定義したBluetoothGattCallback内の

onCharacteristicReadで結果が返されます。

ブロードキャストを受信するように定義するだけでデータを取得できます。

さらに、Bluetoothにはnotifyメソッドもあります。

同様に結果が返されます。

BluetoothGattCallback内の

onCharacteristicChangedを確認します。

サードパーティツールを使用して

Bluetoothパケットをキャプチャする方法を見たい場合は、以下を参照してください。

Bluetoothモジュールノート: クラシックBluetooth(BT)と低消費電力Bluetooth(LTE)

クラシックBluetooth(BT)
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モジュール(BLE)

Bluetooth 4.0以上のモジュールを指す

低消費電力Bluetooth技術は低コスト、短距離

2.4GHz ISM周波数帯で動作可能

BLE技術は非常に高速な接続方式を採用しているため

通常は「非接続」状態にしておくことができる(エネルギーを節約)

AndroidスマートフォンのBluetooth 4.xはすべてデュアルモードBluetooth(クラシックBluetoothと低消費電力Bluetoothの両方を持つ)

Kotlin + Jetpack Compose Bluetoothアプリの例

最後に、以前書いた例を最近整理してアップしました。必要な方は参考にしてください こちらを参照

{% include google/google_ad_client.html %}