LoginSignup
1
2

More than 3 years have passed since last update.

Android BLE ドキュメントを参考にSCANまで

Last updated at Posted at 2021-04-26

Android BLE を下記、ドキュメントを参考に実装します。
今回は、Scanした結果をログ出力するところまでやります。
曖昧な理解なところがあるのであくまで参考程度で。
①概要
https://developer.android.com/guide/topics/connectivity/bluetooth?hl=ja
②Bluetooth Low Energyの概要
https://developer.android.com/guide/topics/connectivity/bluetooth-le?hl=ja#find
③Bluetooth Low Energyを使用する
https://developer.android.com/guide/topics/connectivity/use-ble?hl=ja#connect

BLEパーミッション

②Bluetooth Low Energyの概要に記載があるように、BluetoothとLocationのパーミションが必要となります。

ビーコンは位置情報に関連付けられていることが多いため、ACCESS_FINE_LOCATION パーミッションも宣言する必要があります。このパーミッションがないと、スキャン結果を取得できません。

AndroidManifest.xml
uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

<!-- If your app targets Android 9 or lower, you can declare
     ACCESS_COARSE_LOCATION instead. -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

実際はRuntime permissionを使って許可を求めると思いますが、今回は割愛します。
設定から、手動で許可をして下さい。

Bluetooth有効か確認

Bluetoothが有効かどうか確認するメソッドです。
③Bluetooth Low Energyを使用するを参考にします。

BluetoothAdapterを取得する。
下記、ドキュメント通りBluetoothAdapterを取得しようとするとエラーになる。

// Initializes Bluetooth adapter.
val bluetoothManager = getSystemService(BluetoothManager::class.java)
val bluetoothAdapter: BluetoothAdapter? = bluetoothManager?.adapter

bluetoothManagerがNullとなりエラーとなる。

Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getSystemServiceName(java.lang.Class)' on a null object reference

なので下記のように、変更する。(①概要を参考にした。)

val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()

Bluetoothを有効にするメソッド。有効でない場合は、ダイヤログを表示する。
REQUEST_ENABLE_BTは何でもよいので、定数を宣言する。

private val REQUEST_ENABLE_BT = 1
// Ensures Bluetooth is available on the device and it is enabled. If not,
// displays a dialog requesting user permission to enable Bluetooth.
if (bluetoothAdapter != null && !bluetoothAdapter.isEnabled) {
   val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
   startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
}

image.png

SCAN

③Bluetooth Low Energyを使用するを参考にします。

bluetoothLeScannerクラスを使用します。
SCAN_PERIODはSCAN時間になります。

private val bluetoothLeScanner: BluetoothLeScanner? = bluetoothAdapter?.bluetoothLeScanner
private var scanning = false
private val handler = Handler()

// Stops scanning after 10 seconds.
private val SCAN_PERIOD: Long = 10000

SCANメソッドは下記になります。
コールバックのleScanCallbackを実装する必要があります。

private fun scanLeDevice() {
    bluetoothLeScanner?.let { scanner ->
        if (!scanning) { // Stops scanning after a pre-defined scan period.
            handler.postDelayed({
                scanning = false
                scanner.stopScan(leScanCallback)
            }, SCAN_PERIOD)
            scanning = true
            scanner.startScan(leScanCallback)
        } else {
            scanning = false
            scanner.stopScan(leScanCallback)
        }
    }

コールバック部

private val leScanCallback: ScanCallback = object : ScanCallback() {
    override fun onScanResult(callbackType: Int, result: ScanResult) {
        super.onScanResult(callbackType, result)
        //コメントアウト部、今回は不要(別途実装が必要になる)
        //leDeviceListAdapter.addDevice(result.device)
        //leDeviceListAdapter.notifyDataSetChanged()

        //nameをログで出力する。nullだった場合No Name
            Log.d("result.device.name",result.device.name ?: "No Name")
    }
}

ドキュメントでは下記を実装している。推測するに、SCANして見つかった情報をListに追加したいのかな。
別途実装が必要で、今回はログ出力したいのでコメントアウト。

leDeviceListAdapter.addDevice(result.device)
leDeviceListAdapter.notifyDataSetChanged()

全容

Bluetooth確認ボタンとSCANボタンを設置して、動作確認出来るようにしました。
SCANするには、位置情報の権限を許可にする必要があります。
image.png

SCANして、ログ出力されればOKです。

MainActivity.kt
import android.bluetooth.BluetoothAdapter
import android.bluetooth.le.BluetoothLeScanner
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.widget.Button

class MainActivity : AppCompatActivity() {

    private val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
    private val bluetoothLeScanner: BluetoothLeScanner? = bluetoothAdapter?.bluetoothLeScanner
    private var scanning = false
    private val handler = Handler()

    private val REQUEST_ENABLE_BT = 1
    private val SCAN_PERIOD: Long = 10000


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        findViewById<Button>(R.id.bluetooth_check_button).setOnClickListener {
            if (bluetoothAdapter != null && !bluetoothAdapter.isEnabled) {
                val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
                startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
            }
        }

        findViewById<Button>(R.id.scan_button).setOnClickListener {
            scanLeDevice()
        }
    }

    private fun scanLeDevice() {
        bluetoothLeScanner?.let { scanner ->
            if (!scanning) { // Stops scanning after a pre-defined scan period.
                handler.postDelayed({
                    scanning = false
                    scanner.stopScan(leScanCallback)
                    println("stopScan")
                }, SCAN_PERIOD)
                scanning = true
                scanner.startScan(leScanCallback)
                println("startScan")
            } else {
                scanning = false
                scanner.stopScan(leScanCallback)
                println("stopScan")
            }
        }
    }

    private val leScanCallback: ScanCallback = object : ScanCallback() {
        override fun onScanResult(callbackType: Int, result: ScanResult) {
            super.onScanResult(callbackType, result)

            //nameをログで出力する。nullだった場合No Name
            Log.d("result.device.name", result.device.name ?: "No Name")
        }
    }
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/bluetooth_check_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:text="Bluetooth確認"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/scan_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:text="SCAN"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

おわりに

BLEを触ってみたく、ドキュメントを見ながら実装をしました。
次は、GATTサーバーに接続からやってみようかなと。
あと、セントラルとペリフェラルが分からなく調べたところ、下記サイトが参考になりました。
https://houwa-js.co.jp/exe/2018/06/20180629/

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2