ESP32でBluetooth Classicを使ってAndroidと通信する

ESP32でBluetooth Classicを使ってAndroidと通信する
ESP32でBluetooth Classicを使ってAndroidと通信する

ゴール

動画のように、ESP32からBluetooth Classic経由でAndroidアプリへデータを送信し、データを表示するまでをゴールとします。

Bluetooth ClassicとBLEの違い

Bluetooth ClassicとBluetooth Low Energy(BLE)の違いをまとめてみます(ChatGPTによる調査)。

電力消費

  • Bluetooth Classic: 比較的高い電力消費。音声や高データレートの通信に適していますが、バッテリー寿命に影響を与える可能性があります。
  • BLE(Bluetooth Low Energy): 低い電力消費。小規模なデータ転送に適しており、バッテリー寿命が長くなることが特徴です。

データ転送速度

  • Bluetooth Classic: 高いデータ転送速度(最大2-3 Mbps)。
  • BLE: 低いデータ転送速度(最大1 Mbps)。

接続時間

  • Bluetooth Classic: 接続時間が長い。ペアリングプロセスが比較的複雑です。
  • BLE: 瞬時に接続が可能。ペアリングプロセスが簡単で迅速です。

用途

  • Bluetooth Classic: 音声通話、音楽ストリーミング、ファイル転送などの用途に適しています。
  • BLE: スマートデバイス、フィットネストラッカー、IoTデバイスなど、小規模なデータ交換に適しています。

電波の飛距離

  • Bluetooth Classic: 最大約100メートル。
  • BLE: 最大約100メートル。しかし、低電力設計のため、実際の使用範囲はBluetooth Classicより短くなることが一般的です。

相互運用性

  • Bluetooth Classic: Bluetooth Classic搭載デバイス間でのみ互換性があります。
  • BLE: BLEデバイス間、またはBLEをサポートするBluetooth Classicデバイス間で互換性があります。

これらのポイントをまとめると、Bluetooth Classicは高速なデータ転送と長距離通信に適していますが、電力消費が大きいです。一方、BLEは低電力で長いバッテリー寿命を提供し、小規模なデータ交換に適していますが、データ転送速度は低く、通信距離も短くなる傾向があります。

Bluetooth Classicの方がBLEよりもペアリングプロセスが複雑となってますが、実際プログラミングしてみるとBluetooth Classicのペアリングはとても簡単でした。もしかしたらライブラリ関数で複雑な処理を抽象化されているだけかもしれませんが。

ESP32で使える無線通信

  • Bluetooth Classic
  • Bluetooth Low Energy(BLE)
  • ESP-Now
  • WiFi

ESP32−DevKitC−32E ESP32−WROOM−32E開発ボード 4MB: マイコン関連 秋月電子通商-電子部品・ネット通販

プロファイル

Bluetooth Classic(またはBluetooth BR/EDR)は、さまざまなアプリケーションに対応するために、多くのプロファイルを提供しています。各プロファイルは、特定の種類のBluetooth通信を容易にするための規格やプロトコルのセットです。以下に主なBluetooth Classicのプロファイルをいくつか紹介します:

HSP(Headset Profile)

これは、ヘッドセットを使用して音声通話を行うためのプロファイルです。モノラルオーディオと音声通話の基本的なコントロール(例:通話の開始・終了)をサポートします。

HFP(Hands-Free Profile)

このプロファイルは、自動車やヘッドセットでのハンズフリー通話をサポートします。HSPよりも高度な機能を持ち、通話中の音量調整や着信拒否などの機能があります。

A2DP(Advanced Audio Distribution Profile)

高品質なオーディオストリーミングをサポートするためのプロファイルです。ステレオ音楽の再生や、ビデオのサウンドトラックの再生に使用されます。

AVRCP(Audio/Video Remote Control Profile)

オーディオやビデオ機器を遠隔操作するためのプロファイルです。再生、一時停止、スキップなどのメディアコントロールが可能です。

PBAP(Phone Book Access Profile)

携帯電話の連絡先情報にアクセスするためのプロファイルです。車載システムなどで、携帯電話の連絡先を表示・使用する際に利用されます。

OPP(Object Push Profile)

ファイル(例えば、写真や名刺情報)をBluetooth経由でプッシュ(送信)するためのプロファイルです。

SPP(Serial Port Profile)

仮想シリアルポートを介してデータを交換するためのプロファイルで、多くの産業用および医療用アプリケーションで使われています。

これらはBluetooth Classicの主要なプロファイルの一部ですが、他にも多くの特定用途向けのプロファイルが存在します。それぞれのプロファイルは特定のデバイスやアプリケーションの要件に合わせて設計されています。今回はSPPを使ってESP32とAndroidで通信してみました。

Bluetooth Classicを使ってESP32からデータを送信

次は、Bluetooth Classicを使ってデータを送信するESP32のプログラムです:

cpp
#include "BluetoothSerial.h"
#include "esp_gap_bt_api.h"

#define LED_PIN 13

static void gap_callback(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param);

BluetoothSerial SerialBT;
esp_bt_pin_code_t pin_code = {1, 2, 3, 4}; // ここでPINコードを設定

void setup() {
    Serial.begin(115200);
    SerialBT.begin("Bluetooth Classic Test"); // Bluetoothデバイス名を設定
    esp_bt_gap_register_callback(gap_callback);

    // Variable PINタイプを使用して、ユーザーがPINコードを入力できるようにする
    esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE; // 可変のPINコード
    esp_bt_gap_set_pin(pin_type, 0, NULL); // 最初にPINコードを設定しない

    Serial.println("Bluetoothデバイスが起動しました。Androidから接続してください。");

    pinMode(LED_PIN, OUTPUT);
    digitalWrite(LED_PIN, LOW);
}

void loop() {
    static int counter = 0;
    // ここに送信したいデータを記述
    if (SerialBT.connected()) {
        SerialBT.print("Hello from ESP32");
        SerialBT.print(" (");
        SerialBT.print(counter);
        SerialBT.print(")");
        SerialBT.println();
        counter++;
        digitalWrite(LED_PIN, HIGH);
        delay(1000);
    } else {
        digitalWrite(LED_PIN, LOW);
        delay(10);
    }
}

static void gap_callback(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) {
    Serial.print("event: ");
    Serial.println(event);

    switch (event) {
        case ESP_BT_GAP_AUTH_CMPL_EVT:
            Serial.println("認証が完了しました。");
            break;
        case ESP_BT_GAP_PIN_REQ_EVT:
            Serial.println("PINコード認証が要求されました。");
            // PINコードの返信
            esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, pin_code);
            break;

        default:
            break;
    }
}

解説

ESP32のBluetooth Classicの実装は、BLEと違って非常に簡潔に記述できます。UARTのシリアル通信とまったく同じ感覚で、データを送信できることがお分かりいただけると思います。次のPINコード要求の実装を省けばもっと簡潔に書けます。

PINコードの要求に関して

このプログラムを実行後、Androidからペアリングを試みると、なぜかESP_BT_GAP_PIN_REQ_EVTイベントが呼ばれず、PINコードなしでペアリングできてしまいます。これはESP32とAndroidのペアリングプロセスが異なることが原因かもしれません。

SPPのUUID

BluetoothのSerial Port Profile(SPP)を使用する際には、特定のUUID(Universally Unique Identifier)が割り当てられています。SPPのための標準UUIDは 00001101-0000-1000-8000-00805F9B34FB です。

このUUIDは、BluetoothデバイスがSPPを使用していることを識別し、他のデバイスがこのサービスに接続できるようにするために使用されます。UUIDは、Bluetoothプロファイルの種類ごとに固有で、デバイス間の互換性を確保するために標準化されています。

Bluetooth Classicを使ってAndroidでデータを受信

事前にペアリング

先ほどのソースコードをESP32へアップロードして起動したら、AndroidのBluetooth設定でペアリングしておきます。

マニフェスト設定

AndroidでBluetooth Classicを使う場合、最低限、次の許可をマニフェストに設定しておく必要があります。

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

ソースコード例

AndroidアプリでBluetooth Classicでのデータ通信する核となるサンプルコードを次に示します。TCPなどのソケット通信と非常によく似た感じで実装できます。

kotlin
package com.apppppp.bluetoothclassictest.model

import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothSocket
import java.io.IOException
import java.util.UUID

/**
 * Bluetoothのデバイスとの接続を管理するクラス
 */
class BTClient {

    private val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
    private var socket: BluetoothSocket? = null
    // SPPのUUIDを定義
    private val SPP_UUID: UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")

    private var _isConnected = false

    val isConnected: Boolean
        get() = _isConnected

    val deviceName: String?
        @SuppressLint("MissingPermission")
        get() = socket?.remoteDevice?.name

    val deviceAddress: String?
        @SuppressLint("MissingPermission")
        get() = socket?.remoteDevice?.address

    @SuppressLint("MissingPermission")
    fun connectToDevice(deviceAddress: String) {
        if(_isConnected) {
            closeConnection()
            return
        }
        val device = bluetoothAdapter?.getRemoteDevice(deviceAddress)
            ?: throw IllegalArgumentException("No device found with address $deviceAddress")
        socket = device.createRfcommSocketToServiceRecord(SPP_UUID)
        try {
            socket?.connect() // IOExceptionがスローされる可能性がある
            _isConnected = true
        } catch (e: IOException) {
            e.printStackTrace()
            _isConnected = false
        }
    }

    fun receiveData(): String? {
        return try {
            socket?.inputStream?.bufferedReader()?.readLine()
        } catch (e: IOException) {
            e.printStackTrace()
            _isConnected = false
            null
        }
    }

    fun sendData(data: String) {
        try {
            socket?.outputStream?.write(data.toByteArray())
        } catch (e: IOException) {
            e.printStackTrace()
            _isConnected = false
        }
    }

    fun closeConnection() {
        try {
            socket?.inputStream?.close()
            socket?.outputStream?.close()
            socket?.close()
            _isConnected = false
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }
}

複数のESP32デバイス同時に接続するには、この BTClient クラスをリストで管理する工夫が必要です。今回は BTClientManager で複数デバイスの管理を行いました。

Androidアプリの構造
Androidアプリの構造

全体のソースコードGithubで公開中です: GitHub - aragig/BluetoothClassicTest: Bluetooth Classic で ESP32とAndroid通信をテストするプロジェクト

関連記事

最後までご覧いただきありがとうございます!

▼ 記事に関するご質問やお仕事のご相談は以下よりお願いいたします。
お問い合わせフォーム

ESP32搭載ボード
ESP32の書籍
Arduinoで人気の周辺パーツ
M5Stackのオススメ参考書
M5Stack製品
M5StickCで使えるHat
ESP32ボード
関連記事