この記事では、 ESP32 から Android アプリへ Bluetooth Classic(SPP)で文字列を送信し、Android 側で表示する ところまでを最小構成で作ります。ESP32 側は Arduino IDE の BluetoothSerial、Android 側は Kotlin の BluetoothSocket を使い、UART のシリアル通信と同じ感覚で書ける構成です。
逆方向の BLE で iOS と通信する例や、BLE と Classic の住み分けを先に把握したい場合は、後半の関連記事 も合わせて参考にしてください。
先に要点
- ESP32 側は
BluetoothSerialでSerialBT.begin("デバイス名")するだけで、SPP のサーバ側が立ち上がります - Android からは事前に OS の Bluetooth 設定でペアリングしておき、アプリ側では SPP 標準の UUID
00001101-0000-1000-8000-00805F9B34FBでBluetoothSocketを張ります - Android 12 以降は
BLUETOOTH_CONNECTのランタイム権限が必要なので、AndroidManifest.xmlへの記述だけでなくアプリ側でのリクエストも忘れずに行います - BLE 通信側で iOS と Lチカしたい場合ははじめてのBLE通信、iOSからESP32のLチカ 、BLE で ESP32 から iOS にデータを送りたい場合はESP32からiOSへBLEでデータを送信する を参照してください
ゴール
動画のように、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
プロファイル
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のプログラムです:
#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を使う場合、最低限、次の許可をマニフェストに設定しておく必要があります。
<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などのソケット通信と非常によく似た感じで実装できます。
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 で複数デバイスの管理を行いました。

全体のソースコードGithubで公開中です:
関連記事
ESP32 と Bluetooth、スマホアプリ連携まわりを続けて触る場合は、次の記事も合わせて参考になります。
- はじめてのBLE通信、iOSからESP32のLチカ ― 本記事と逆方向に、iOS アプリから ESP32 を BLE で操作する入門記事です。BLE と Classic の住み分けもこちらに整理しています。
- ESP32からiOSへBLEでデータを送信する ― BLE の Notify で ESP32 からスマホ側に値を送る側のサンプルです。Android 版 SPP(本記事)との違いを比べる用途にも使えます。
- ESP32 で L チカするまでの設定(Arduino IDE 入門) ― そもそも Arduino IDE で ESP32 に書き込めるところまで進んでいない場合は、こちらを先に通しておくとスムーズです。
- Clion x PlatformIOでESP32(Arduino)開発 ― Arduino IDE ではなく PlatformIO 環境で進めたい場合の構築手順です。
- ESP32 への書き込みに失敗するときの解決方法
― 書き込み中に
Failed to connect to ESP32が出るケースのチェック項目をまとめています。
関連アイテム
ESP32 と Android で Bluetooth Classic を試す際に、最低限あると進めやすいパーツ・道具です。USB 給電できる ESP32 開発ボードと、ペアリング先の Android 端末があれば、追加部品なしでも本記事のサンプルは動かせます。
- ESP32 DevKitC を Amazonで探す
(本記事のサンプルが動く Bluetooth Classic + BLE 対応の定番ボード。
ESP32-C3/S3/C6は Bluetooth Classic 非対応なので避ける) - ESP32 開発ボード NodeMCU-32S を Amazonで探す (DevKitC が手に入りにくいときの代替候補)
- USB Type-C データケーブルを Amazonで探す (充電専用ケーブルだと PC から書き込みできないので、データ通信対応のものを用意)
- ブレッドボード ジャンパワイヤセットを Amazonで探す (送信確認用に LED を載せたいときの配線材一式)
- Android SIMフリー端末を Amazonで探す (手持ちの Android が無い場合の開発用端末候補)