Bluetooth на ESP32: Classic и BLE, управление светодиодом и датчики
Bluetooth на ESP32: управляем светодиодом с телефона через BluetoothSerial, создаём BLE-сервер с сервисами и UUID, проверяем в nRF Connect и отправляем данные датчика.
Bluetooth на ESP32 использует встроенный в чип беспроводной радиомодуль, чтобы подключить ESP32 к телефону, планшету или компьютеру без дополнительных модулей. Оригинальная ESP32 поддерживает сразу два разных вида Bluetooth: Bluetooth Classic, который работает как последовательный порт, и Bluetooth Low Energy (BLE), который потребляет очень мало энергии. Для связи Arduino Uno с телефоном нужен отдельный модуль HC-05, а на ESP32 хватит нескольких строк кода. В этом руководстве шаг за шагом настроим оба способа, включим светодиод с телефона, напишем собственный BLE-сервис и отправим данные датчика на телефон.
Выбор способа зависит от твоего телефона и платы. Если у тебя Android-смартфон и классическая плата ESP32 DevKit, быстрее всего начать с библиотеки BluetoothSerial. Если у тебя iPhone или плата ESP32-S3 либо ESP32-C3, классический Bluetooth не поможет — нужен BLE. Ближе к концу есть и тот же BLE-проект для Raspberry Pi Pico W на MicroPython. Если ESP32 в твоих руках впервые, сначала закончи настройку Arduino IDE из статьи Что такое ESP32?.

Что такое Bluetooth на ESP32? Classic и BLE
Bluetooth Classic — старый и привычный вид Bluetooth, который используется в беспроводных наушниках и модулях вроде HC-05. ESP32 работает с ним через SPP (Serial Port Profile): байты идут один за другим, словно между телефоном и платой протянут невидимый последовательный кабель. Сначала выполни сопряжение устройства в настройках Bluetooth телефона, затем подключись через приложение-терминал. BLE же — отдельная система, которая появилась в 2010 году вместе с Bluetooth 4.0 и создана для устройств, месяцами работающих от батарейки. В BLE нет непрерывного потока данных: ESP32 хранит небольшую таблицу значений (GATT), а телефон читает их, записывает или подписывается на них, чтобы узнавать об изменениях. Несмотря на похожие названия, эти два вида не могут общаться друг с другом: приложение, написанное для BLE, не подключится к устройству с классическим Bluetooth.
| Параметр | Bluetooth Classic (SPP) | Bluetooth Low Energy (BLE) |
|---|---|---|
| Библиотека Arduino | BluetoothSerial (входит в пакет ESP32) | BLEDevice (входит в пакет ESP32) или NimBLE-Arduino |
| Модель данных | Непрерывный поток байтов, как у последовательного порта | Таблица GATT из сервисов и характеристик |
| Объём данных | Большой, подходит для непрерывных потоков | Небольшие пакеты; по умолчанию 20 байт на уведомление |
| Энергопотребление | Постоянно высокое во время подключения | Очень низкое; устройство может месяцами работать от батарейки |
| Сопряжение | Выполняется в настройках Bluetooth телефона | Не нужно; подключение идёт внутри приложения |
| Android | Поддерживается | Поддерживается |
| iPhone (iOS) | Не поддерживается | Поддерживается |
| Какие ESP32 поддерживают | Только оригинальная ESP32 (WROOM-32, WROVER) | ESP32, S3, C3, C6 и H2 (кроме S2) |
| Типичное применение | Последовательный терминал, робот-машинка, замена HC-05 | Датчики, носимые устройства, умный дом, маяки (beacon) |
Практическое правило такое: если нужно отправлять и получать текст с Android-смартфона и быстро увидеть результат, бери Classic; если нужна поддержка iPhone, низкое энергопотребление или одна из новых плат ESP32 — BLE. Для нового проекта BLE стоит изучить: он работает на обоих типах телефонов и есть во всех новых чипах Espressif с Bluetooth. Если с HC-05 уже доводилось работать, станет видно, что логика команд из статьи Bluetooth-модуль HC-05 в BluetoothSerial точно такая же.
Какой Bluetooth есть в какой плате ESP32?
Не у всех чипов семейства ESP32 одинаковое Bluetooth-оборудование. Самая частая ошибка — пытаться загрузить найденный в интернете пример BluetoothSerial в плату ESP32-S3 или ESP32-C3. У Espressif классический Bluetooth есть только в оригинальном чипе ESP32; более поздние чипы с Bluetooth поддерживают только BLE. Какой чип стоит на плате, видно по надписи на металлическом экране модуля (например, ESP32-WROOM-32 или ESP32-S3-WROOM-1).
| Плата / чип | Bluetooth Classic | BLE | Примечание |
|---|---|---|---|
| ESP32 (WROOM-32, WROVER, DevKit V1) | Есть | Есть (4.2) | Единственное семейство ESP32, на котором работает BluetoothSerial |
| ESP32-S2 | Нет | Нет | Только Wi-Fi |
| ESP32-S3 | Нет | Есть (Bluetooth 5) | BluetoothSerial не скомпилируется, используй BLE |
| ESP32-C3 | Нет | Есть (Bluetooth 5) | Маленькие и дешёвые BLE-платы |
| ESP32-C6 | Нет | Есть (Bluetooth 5) | Есть ещё Wi-Fi 6, Zigbee и Thread |
| ESP32-H2 | Нет | Есть (Bluetooth 5) | Без Wi-Fi; ориентирована на Zigbee и Thread |
| ESP8266 (NodeMCU, Wemos D1) | Нет | Нет | Для Bluetooth нужен HC-05 или HM-10 |
| Raspberry Pi Pico W / Pico 2 W | Есть аппаратно | Есть | MicroPython поддерживает только BLE |
| Raspberry Pi Pico / Pico 2 | Нет | Нет | Нужен внешний Bluetooth-модуль |
Bluetooth Classic: управление светодиодом с телефона через BluetoothSerial
Библиотека BluetoothSerial представляет ESP32 телефону как модуль HC-05 и использует те же команды, что и объект Serial: available(), read(), print() и println(). Библиотека входит в пакет плат ESP32, так что ставить ничего не нужно. Для схемы хватит синего встроенного светодиода, который на большинстве плат DevKit подключён к GPIO 2; если используешь внешний светодиод, подключи его к GPIO 2 через резистор 220 Ω. Номинал резистора можно проверить калькулятором резистора для светодиода, а другие безопасные пины ESP32 посмотреть в утилите распиновки. Убедись, что в «Инструменты › Плата» (Tools › Board) выбрана ESP32 Dev Module, и загрузи этот код:
1#include "BluetoothSerial.h"
2
3// Klasik Bluetooth yalnızca ilk ESP32'de var / Classic Bluetooth exists only on the original ESP32
4#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
5#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
6#endif
7#if !defined(CONFIG_BT_SPP_ENABLED)
8#error Serial Bluetooth not available or not enabled. It is only available for the ESP32 chip.
9#endif
10
11BluetoothSerial SerialBT;
12const int LED_PIN = 2; // DevKit dahili LED / DevKit onboard LED
13bool ledAcik = false;
14
15void ledAyarla(bool acik) {
16 ledAcik = acik;
17 digitalWrite(LED_PIN, acik ? HIGH : LOW);
18 SerialBT.println(acik ? "LED acik / LED on" : "LED kapali / LED off");
19}
20
21void setup() {
22 Serial.begin(115200);
23 pinMode(LED_PIN, OUTPUT);
24 SerialBT.begin("BlueGrays-ESP32"); // telefonda görünecek ad / name shown on the phone
25 Serial.println("Eslesmeye hazir / Ready to pair");
26}
27
28void loop() {
29 if (SerialBT.available()) {
30 String komut = SerialBT.readStringUntil('\n'); // satır sonuna kadar oku / read until newline
31 komut.trim(); // CR ve boşlukları sil / strip CR and spaces
32 komut.toLowerCase();
33
34 if (komut == "1" || komut == "ac") {
35 ledAyarla(true);
36 } else if (komut == "0" || komut == "kapat") {
37 ledAyarla(false);
38 } else if (komut == "durum") {
39 SerialBT.println(ledAcik ? "LED acik / LED on" : "LED kapali / LED off");
40 } else if (komut.length() > 0) {
41 SerialBT.print("Bilinmeyen komut / Unknown command: ");
42 SerialBT.println(komut);
43 }
44 }
45
46 if (Serial.available()) { // Seri Monitör'den telefona / Serial Monitor to phone
47 SerialBT.write(Serial.read());
48 }
49}Код читает каждую строку, пришедшую с телефона, как команду. readStringUntil() ждёт символа перевода строки, а trim() убирает добавленный телефоном возврат каретки (CR) и пробелы; без этой строки при отправке «1» на самом деле приходит «1» плюс невидимый символ CR, и ни одна команда не совпадает. Команды переводятся в нижний регистр, поэтому «AC» и «ac» работают одинаково. Две строки внизу пересылают на телефон всё, что набрано в мониторе порта, — так получается беспроводной чат между компьютером и телефоном. Проверки #error в начале кода останавливают сборку с понятным сообщением, если выбрана не та плата.
Подключение с телефона через Serial Bluetooth Terminal
- После загрузки открой монитор порта на скорости 115200 бод и найди сообщение «Eslesmeye hazir / Ready to pair».
- На Android-смартфоне запусти поиск новых устройств в разделе «Настройки › Bluetooth» и выполни сопряжение с BlueGrays-ESP32. По умолчанию ESP32 сопрягается без PIN-кода; если телефон показывает код подтверждения, подтверди его.
- Установи из Google Play приложение Serial Bluetooth Terminal от Kai Morich.
- В приложении открой в боковом меню раздел Devices (устройства) и выбери BlueGrays-ESP32 на вкладке Bluetooth Classic.
- Нажми значок штекера на верхней панели; когда на экране появится «Connected», подключение готово.
- Набери 1 в поле внизу и отправь: светодиод загорится, и появится ответ «LED acik / LED on». 0 выключает его, а durum показывает текущее состояние.
Как работает BLE? Сервисы, характеристики и UUID
В BLE две роли. ESP32 играет роль сервера (peripheral): хранит значения и сообщает «я здесь», отправляя через короткие интервалы небольшие рекламные пакеты (advertising) длиной не более 31 байта (на ESP32 по умолчанию каждые 20–40 мс). Телефон играет роль клиента (central): сканирует эфир, находит устройство и подключается. После подключения телефон читает таблицу GATT (Generic Attribute Profile) ESP32. Представь эту таблицу как картотечный шкаф: каждый ящик — это сервис, а каждая папка в ящике — характеристика. Характеристика хранит одно значение (например, температуру или команду для светодиода) и свойства, которые определяют, что с этим значением можно делать.
| Термин | Значение | Пример в этой статье |
|---|---|---|
| Реклама (advertising) | Короткий пакет, в котором устройство сообщает своё имя и сервисы | Реклама под именем «BlueGrays-BLE» |
| Сервис | Группа, объединяющая связанные характеристики | Сервис Nordic UART, Environmental Sensing (0x181A) |
| Характеристика | Одно значение, которое читают или записывают | Команда для светодиода, температура (0x2A6E) |
| READ (чтение) | Телефон читает значение, когда захочет | Последняя измеренная температура |
| WRITE / WRITE_NR (запись) | Телефон записывает значение (с ответом или без ответа) | Запись «1» включает светодиод |
| NOTIFY (уведомление) | ESP32 сама сообщает телефону об изменении значения | Новая температура каждые 2 с |
| Дескриптор 0x2902 (CCCD) | Переключатель, который включает и выключает уведомления | Кнопка с тройной стрелкой в nRF Connect |
| UUID | Идентификационный номер сервисов и характеристик | 6E400001-B5A3-F393-E0A9-E50E24DCCA9E |
Каждый сервис и каждая характеристика определяются своим UUID. Стандартные функции, описанные Bluetooth SIG, используют короткие 16-битные номера: например, 0x180F для сервиса батареи и 0x2A6E для характеристики температуры. Для своих данных, у которых нет стандартного аналога, сгенерируй случайный 128-битный UUID; в Python для этого достаточно import uuid; print(uuid.uuid4()). Выбирая свой UUID, не используй значения по шаблону xxxxxxxx-0000-1000-8000-00805F9B34FB: этот шаблон зарезервирован за 16-битными номерами Bluetooth SIG. А чтобы без проблем работать с готовыми приложениями, ещё удобнее взять UUID, которые знают все:
| Сервис / характеристика | UUID | Формат данных |
|---|---|---|
| Battery Service / Battery Level (уровень заряда) | 0x180F / 0x2A19 | 1 байт, проценты (0–100) |
| Environmental Sensing / Temperature (температура) | 0x181A / 0x2A6E | 2-байтовое целое со знаком, 0,01 °C |
| Environmental Sensing / Humidity (влажность) | 0x181A / 0x2A6F | 2-байтовое целое без знака, 0,01% |
| Heart Rate / Heart Rate Measurement (пульс) | 0x180D / 0x2A37 | Байт флагов + значение пульса |
| Nordic UART Service (NUS) | 6E400001-B5A3-F393-E0A9-E50E24DCCA9E | RX 6E400002 (запись), TX 6E400003 (уведомление) |
BLE-сервер на ESP32: управление светодиодом с телефона (Nordic UART)
В первом BLE-проекте превратим ESP32 в сервер Nordic UART Service (NUS). NUS — сервис «последовательный порт поверх BLE» от Nordic Semiconductor, ставший стандартом де-факто: телефон пишет в характеристику RX, а ESP32 отправляет уведомления из характеристики TX. Его плюс в том, что его понимают многие готовые приложения: nRF Connect, вкладка Bluetooth LE в Serial Bluetooth Terminal и Bluefruit Connect на iPhone работают с одним и тем же кодом. Значит, код из этого раздела работает и с Android, и с iPhone, а также на платах только с BLE, таких как ESP32-S3 и C3. Библиотека BLE входит в пакет плат ESP32, так что ставить ничего не нужно.
1#include <BLEDevice.h>
2#include <BLEServer.h>
3#include <BLEUtils.h>
4#include <BLE2902.h>
5
6// Nordic UART Service UUID'leri / Nordic UART Service UUIDs
7#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
8#define RX_UUID "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" // telefon -> ESP32 / phone -> ESP32
9#define TX_UUID "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" // ESP32 -> telefon / ESP32 -> phone
10
11const int LED_PIN = 2;
12
13BLEServer *sunucu = nullptr;
14BLECharacteristic *txKarakteristik = nullptr;
15volatile bool bagli = false;
16bool oncekiBagli = false;
17volatile int bekleyenKomut = -1; // -1: yeni komut yok / no new command
18
19class SunucuOlaylari : public BLEServerCallbacks {
20 void onConnect(BLEServer *s) { bagli = true; }
21 void onDisconnect(BLEServer *s) { bagli = false; }
22};
23
24class RxOlaylari : public BLECharacteristicCallbacks {
25 void onWrite(BLECharacteristic *c) {
26 String deger = c->getValue().c_str(); // çekirdek 2.x ve 3.x ile uyumlu / works on core 2.x and 3.x
27 deger.trim();
28 if (deger == "1") bekleyenKomut = 1;
29 else if (deger == "0") bekleyenKomut = 0;
30 }
31};
32
33void cevapGonder(const char *mesaj) {
34 txKarakteristik->setValue((uint8_t *)mesaj, strlen(mesaj));
35 txKarakteristik->notify(); // telefona bildirim / notify the phone
36}
37
38void setup() {
39 Serial.begin(115200);
40 pinMode(LED_PIN, OUTPUT);
41
42 BLEDevice::init("BlueGrays-BLE"); // yayında görünecek ad / advertised name
43 sunucu = BLEDevice::createServer();
44 sunucu->setCallbacks(new SunucuOlaylari());
45
46 BLEService *servis = sunucu->createService(SERVICE_UUID);
47
48 txKarakteristik = servis->createCharacteristic(TX_UUID, BLECharacteristic::PROPERTY_NOTIFY);
49 txKarakteristik->addDescriptor(new BLE2902()); // bildirim anahtarı (CCCD) / notification switch (CCCD)
50
51 BLECharacteristic *rx = servis->createCharacteristic(
52 RX_UUID, BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR);
53 rx->setCallbacks(new RxOlaylari());
54
55 servis->start();
56
57 BLEAdvertising *yayin = BLEDevice::getAdvertising();
58 yayin->addServiceUUID(SERVICE_UUID);
59 yayin->setScanResponse(true);
60 yayin->setMinPreferred(0x06); // iPhone bağlantısına yardımcı / helps iPhone connections
61 yayin->setMaxPreferred(0x12);
62 BLEDevice::startAdvertising();
63 Serial.println("BLE yayini basladi / BLE advertising started");
64}
65
66void loop() {
67 if (bekleyenKomut != -1) { // telefondan komut geldi / command received
68 int komut = bekleyenKomut;
69 bekleyenKomut = -1;
70 digitalWrite(LED_PIN, komut == 1 ? HIGH : LOW);
71 if (bagli) cevapGonder(komut == 1 ? "LED acik" : "LED kapali");
72 }
73
74 if (!bagli && oncekiBagli) { // bağlantı koptu / disconnected
75 delay(500); // yığına zaman tanı / give the stack time
76 sunucu->startAdvertising(); // yeniden görünür ol / advertise again
77 Serial.println("Yeniden yayin / Advertising again");
78 oncekiBagli = false;
79 }
80 if (bagli && !oncekiBagli) { // yeni bağlantı / new connection
81 Serial.println("Telefon baglandi / Phone connected");
82 oncekiBagli = true;
83 }
84}Самая важная часть кода — классы обратных вызовов (callback). onConnect и onDisconnect отслеживают состояние подключения, а onWrite ловит значение, записанное телефоном в характеристику RX. Эти функции выполняются в собственной задаче стека Bluetooth, поэтому вместо долгой работы внутри них мы только меняем переменную, а основную работу делает loop(). Дескриптор BLE2902, добавленный к характеристике TX, нужен, чтобы телефон мог включить уведомления. Когда соединение рвётся, ESP32 сама не возобновляет рекламу; без строки startAdvertising() в loop() устройство после первого подключения больше не найти. Строки setMinPreferred и setMaxPreferred добавляют в рекламный пакет предпочтительный интервал соединения, и с iPhone подключаться становится проще.
Пошаговая проверка BLE в nRF Connect
- Установи на телефон бесплатное приложение nRF Connect for Mobile от Nordic Semiconductor; оно есть для Android и iPhone.
- На Android приложение запрашивает доступ к Bluetooth и разрешение Устройства поблизости (Nearby devices), а на Android 11 и более ранних версиях — доступ к геолокации при включённых службах геолокации; без этого список сканирования останется пустым.
- На вкладке Scanner нажми SCAN и найди в списке BlueGrays-BLE. Значение RSSI рядом — это уровень сигнала: −50 dBm означает «очень близко», −90 dBm — граница дальности.
- Нажми CONNECT. На открывшемся экране виден Nordic UART Service; с собственными сгенерированными UUID здесь было бы написано «Unknown Service» (неизвестный сервис).
- Нажми значок тройной стрелки вниз рядом с характеристикой TX (6E400003), чтобы включить уведомления.
- Нажми значок стрелки вверх рядом с характеристикой RX (6E400002), выбери тип данных Text (UTF-8), набери 1 и отправь. Светодиод загорится, а в строке TX появится «LED acik».
Этот тест — самый быстрый способ убедиться, что сторона ESP32 работает, ещё до написания приложения для телефона. Если что-то не так, сначала проверь всё в nRF Connect: если устройства нет в списке, проблема в рекламе; если подключение есть, а сервис не виден, пропущен вызов start() сервиса; если значение записано, а светодиод не горит, проверь сравнение внутри onWrite. Тот же код можно попробовать и на вкладке Bluetooth LE в Serial Bluetooth Terminal: приложение распознаёт сервис NUS, и переписка идёт так же, как с классическим Bluetooth. Если хочется сделать собственное Android-приложение, загляни в статью MIT App Inventor и Arduino по Bluetooth.
Передача данных датчика по BLE (DHT22)

| Пин DHT22 | ESP32 | Примечание |
|---|---|---|
| VCC (+) | 3V3 | Работает от 3,3 до 5 V |
| DATA (OUT) | GPIO 4 | Для голого датчика добавь резистор 10 kΩ между DATA и 3V3 |
| GND (−) | GND | Общая земля |
В этом примере используем стандартный сервис Environmental Sensing (0x181A) и его характеристики температуры (0x2A6E) и влажности (0x2A6F). Прелесть стандартных UUID в том, что приложения вроде nRF Connect распознают значение и сразу показывают его в виде «23.45 °C». Стандарт требует отправлять значения не текстом, а целыми числами, умноженными на 100: температура 23,45 °C превращается в число 2345 и отправляется в виде 2 байт, младшим байтом вперёд (little-endian). Для чтения датчика используем библиотеку Adafruit DHT sensor library, которую мы разбирали в руководстве по DHT11; её можно установить через «Менеджер библиотек» (Library Manager).
1#include <BLEDevice.h>
2#include <BLEServer.h>
3#include <BLE2902.h>
4#include <DHT.h>
5
6#define DHT_PIN 4
7DHT dht(DHT_PIN, DHT22);
8
9BLEServer *sunucu = nullptr;
10BLECharacteristic *sicaklikKar = nullptr;
11BLECharacteristic *nemKar = nullptr;
12volatile bool bagli = false;
13bool oncekiBagli = false;
14unsigned long sonOlcum = 0;
15
16class SunucuOlaylari : public BLEServerCallbacks {
17 void onConnect(BLEServer *s) { bagli = true; }
18 void onDisconnect(BLEServer *s) { bagli = false; }
19};
20
21BLECharacteristic *karakteristikEkle(BLEService *servis, uint16_t uuid) {
22 BLECharacteristic *k = servis->createCharacteristic(
23 BLEUUID(uuid), BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
24 k->addDescriptor(new BLE2902()); // bildirim için gerekli / needed for notify
25 return k;
26}
27
28void setup() {
29 Serial.begin(115200);
30 dht.begin();
31
32 BLEDevice::init("BlueGrays-Sensor");
33 sunucu = BLEDevice::createServer();
34 sunucu->setCallbacks(new SunucuOlaylari());
35
36 BLEService *servis = sunucu->createService(BLEUUID((uint16_t)0x181A)); // Environmental Sensing
37 sicaklikKar = karakteristikEkle(servis, 0x2A6E); // sıcaklık, 0,01 °C / temperature, 0.01 °C
38 nemKar = karakteristikEkle(servis, 0x2A6F); // nem, %0,01 / humidity, 0.01 %
39 servis->start();
40
41 BLEAdvertising *yayin = BLEDevice::getAdvertising();
42 yayin->addServiceUUID(BLEUUID((uint16_t)0x181A));
43 yayin->setScanResponse(true);
44 BLEDevice::startAdvertising();
45}
46
47void loop() {
48 if (millis() - sonOlcum >= 2000) { // DHT22 en fazla 2 sn'de bir / DHT22 at most every 2 s
49 sonOlcum = millis();
50 float t = dht.readTemperature();
51 float h = dht.readHumidity();
52 if (!isnan(t) && !isnan(h)) {
53 int16_t t100 = (int16_t)lroundf(t * 100); // 23,45 °C -> 2345
54 uint16_t h100 = (uint16_t)lroundf(h * 100); // %41,2 -> 4120
55 sicaklikKar->setValue((uint8_t *)&t100, 2); // 2 bayt, düşük bayt önce / 2 bytes, little-endian
56 nemKar->setValue((uint8_t *)&h100, 2);
57 if (bagli) {
58 sicaklikKar->notify();
59 nemKar->notify();
60 }
61 Serial.printf("%.2f C %.2f %%\n", t, h);
62 }
63 }
64
65 if (!bagli && oncekiBagli) { // bağlantı koptu / disconnected
66 delay(500);
67 sunucu->startAdvertising(); // yeniden yayın / advertise again
68 oncekiBagli = false;
69 }
70 if (bagli && !oncekiBagli) oncekiBagli = true;
71}Код делает измерение каждые две секунды, потому что DHT22 нельзя опрашивать чаще. Сначала значения записываются в характеристику через setValue(), поэтому при чтении (READ) телефон получает последнее измерение, не дожидаясь уведомления. Если телефон подключён, notify() сразу отправляет новое значение. Если nRF Connect не может расшифровать значение, видны сырые байты: для 23,45 °C на экране будет (0x) 29-09. Переставь байты в обратном порядке — получится 0x0929, то есть 2345 в десятичной системе; раздели на 100, и вот температура. В собственном приложении нужен будет тот же расчёт. Отправлять значение текстом может показаться проще, но стандартный формат занимает меньше байтов и работает с готовыми приложениями.
Bluetooth ESP32 и iPhone (iOS)
Чаще всего владельцы iPhone спрашивают, почему ESP32 не видна в настройках Bluetooth. Apple открывает профиль последовательного порта классического Bluetooth (SPP) только для аксессуаров с сертификацией MFi, поэтому к ESP32 с BluetoothSerial с iPhone не подключиться. Решение — BLE, и примеры с NUS и датчиком выше отлично работают на iPhone. Есть несколько моментов, о которых стоит помнить:
- Поиск в настройках: BLE-устройства не отображаются в настройках Bluetooth iOS; подключайся внутри приложения, например nRF Connect, LightBlue или Bluefruit Connect.
- Кэш: iOS запоминает имя устройства и список его сервисов. Если после изменения сервисов в коде всё ещё виден старый список, выключи и снова включи Bluetooth на телефоне.
- Нет MAC-адреса: iOS не показывает приложениям MAC-адрес устройства и присваивает каждому устройству собственный идентификатор; если плат ESP32 несколько, дай каждой своё имя.
- Интервал соединения: не удаляй строки setMinPreferred(0x06) и setMaxPreferred(0x12); примеры Espressif тоже рекомендуют их против проблем с подключением iPhone.
- Своё приложение: на iOS используется Core Bluetooth со Swift; сопряжение не нужно, приложение сканирует эфир сразу по UUID сервиса.
BLE на Raspberry Pi Pico W с MicroPython

В семействе Raspberry Pi Pico Bluetooth есть только у Pico W и Pico 2 W. Установленный на плате чип CYW43439 аппаратно поддерживает и классический Bluetooth, и BLE, но MicroPython использует только BLE (начиная с версии 1.20). Код ниже — аналог примера NUS для ESP32 на MicroPython; он использует те же UUID, поэтому проверяется так же — через nRF Connect и Serial Bluetooth Terminal. На плату должна быть загружена прошивка MicroPython, собранная для Pico W. Код также читает встроенный датчик температуры платы и каждые 2 секунды отправляет значение на подключённый телефон.
1import bluetooth
2import struct
3import time
4from machine import Pin, ADC
5from micropython import const
6
7_IRQ_CENTRAL_CONNECT = const(1)
8_IRQ_CENTRAL_DISCONNECT = const(2)
9_IRQ_GATTS_WRITE = const(3)
10_FLAG_READ = const(0x0002)
11_FLAG_WRITE_NO_RESPONSE = const(0x0004)
12_FLAG_WRITE = const(0x0008)
13_FLAG_NOTIFY = const(0x0010)
14
15# Nordic UART Service: ESP32 örneğiyle aynı UUID'ler / same UUIDs as the ESP32 example
16NUS_UUID = bluetooth.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
17TX = (bluetooth.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E"), _FLAG_READ | _FLAG_NOTIFY)
18RX = (bluetooth.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E"), _FLAG_WRITE | _FLAG_WRITE_NO_RESPONSE)
19
20led = Pin("LED", Pin.OUT) # Pico W dahili LED / Pico W onboard LED
21sensor = ADC(4) # dahili sıcaklık sensörü / internal temperature sensor
22
23ble = bluetooth.BLE()
24ble.active(True)
25((tx_h, rx_h),) = ble.gatts_register_services(((NUS_UUID, (TX, RX)),))
26baglanti = None
27
28
29def alan(tur, veri): # uzunluk + tür + veri / length + type + data
30 return struct.pack("BB", len(veri) + 1, tur) + veri
31
32
33YAYIN = alan(0x01, b"\x06") + alan(0x09, b"BlueGrays-Pico") # bayraklar + ad / flags + name
34YANIT = alan(0x07, bytes(NUS_UUID)) # 128 bit UUID / 128-bit UUID
35
36
37def yayinla():
38 ble.gap_advertise(100000, adv_data=YAYIN, resp_data=YANIT) # 100 ms aralık / interval
39
40
41def olay(event, data):
42 global baglanti
43 if event == _IRQ_CENTRAL_CONNECT:
44 baglanti = data[0]
45 elif event == _IRQ_CENTRAL_DISCONNECT:
46 baglanti = None
47 yayinla() # yeniden görünür ol / advertise again
48 elif event == _IRQ_GATTS_WRITE and data[1] == rx_h:
49 komut = ble.gatts_read(rx_h).decode().strip()
50 if komut == "1":
51 led.on()
52 elif komut == "0":
53 led.off()
54
55
56ble.irq(olay)
57yayinla()
58
59while True:
60 if baglanti is not None:
61 volt = sensor.read_u16() * 3.3 / 65535
62 sicaklik = 27 - (volt - 0.706) / 0.001721 # veri sayfası formülü / datasheet formula
63 try:
64 ble.gatts_notify(baglanti, tx_h, "T:{:.1f}\n".format(sicaklik).encode())
65 except OSError:
66 pass # bağlantı yeni koptu / just disconnected
67 time.sleep(2)Модуль bluetooth в MicroPython низкоуровневый, поэтому рекламный пакет собираем сами. Каждое поле состоит из байта длины, байта типа и данных: 0x01 — флаги, 0x09 — полное имя устройства, 0x07 — 128-битный UUID сервиса. Рекламный пакет вмещает не более 31 байта, поэтому 18-байтовое поле UUID мы поместили в ответ на сканирование (resp_data). gatts_register_services() возвращает хэндлы (handle) характеристик; по этим хэндлам мы читаем значение, записанное телефоном, и отправляем уведомления. Если хочется кода покороче, можно установить через mip библиотеку aioble на основе asyncio. Чтобы запустить код на ESP32 под MicroPython, достаточно заменить строку светодиода на Pin(2, Pin.OUT) и читать, например, DHT11 вместо встроенного датчика температуры.
Wi-Fi и Bluetooth вместе: память, дальность и питание
На ESP32 Wi-Fi и Bluetooth делят один радиомодуль 2,4 GHz. Чип работает с ними по очереди, с разделением времени, поэтому использовать их можно одновременно, но каждый становится немного медленнее, а классический Bluetooth вместе с интенсивным трафиком Wi-Fi — самый тяжёлый сценарий. Однако настоящая проблема обычно в памяти: стек Bluetooth большой, и вместе с Wi-Fi программа может превысить стандартный раздел приложения размером 1,2 МБ (1 310 720 байт); даже один только BLE-пример из этой статьи занимает около 80% этого места. Если сборка заканчивается ошибкой «Sketch too big» (скетч слишком большой) или «text section exceeds available space in board» (код не помещается в память платы) и обновление прошивки по воздуху (OTA) не используется, выбери Huge APP (3MB No OTA/1MB SPIFFS) в «Инструменты › Partition Scheme» (Tools › Partition Scheme). Хорошая альтернатива — библиотека NimBLE-Arduino, которой нужно меньше флеш-памяти и RAM.
Дальность зависит от конструкции антенны платы и окружения. С ESP32 с печатной антенной (PCB) на плате рассчитывай на 10–20 м в помещении в зависимости от стен, на открытом пространстве — больше; модули с разъёмом для внешней антенны (например, ESP32-WROOM-32U) бьют ещё дальше. Если закрыть антенну металлическим корпусом, батареей или проводами, дальность резко падает. При включении радиомодуля ток ненадолго возрастает, поэтому из-за слабого USB-кабеля плата может перезагружаться с сообщением «Brownout detector was triggered»; способы исправления — в статье Brownout и Guru Meditation на ESP32.
Идеи проектов с Bluetooth на ESP32
- Bluetooth-машинка: передавай команды F, B, L и R, пришедшие через BluetoothSerial, на драйвер двигателей L298N; механику смотри в статье Как сделать робот-машинку на Arduino.
- Умная розетка: замени светодиод в примере NUS модулем реле и включай лампу с iPhone.
- Беспроводной термометр: добавь к примеру с DHT22 OLED-дисплей, чтобы значение было видно и на экране, и на телефоне.
- Цветная атмосферная подсветка: меняй цвет ленты WS2812B NeoPixel значениями R, G и B, отправленными с телефона.
- Своё Android-приложение: за несколько часов собери в App Inventor приложение-пульт с кнопками и ползунками.
- Связь между двумя ESP32: чтобы две платы общались без телефона, попробуй вместо Bluetooth более простой и быстрый ESP-NOW.
Частые проблемы с Bluetooth на ESP32 и их решения
| Симптом / ошибка | Причина | Решение |
|---|---|---|
| «Serial Bluetooth not available or not enabled» (Bluetooth Serial недоступен) | Выбрана плата ESP32-S3, C3, C6 или H2, в которой нет классического Bluetooth | Используй классическую ESP32 или перейди на пример BLE (NUS) |
| Ошибка преобразования типа «conversion from 'String' to non-scalar type 'std::string' requested» | Код для ядра 2.x (std::string) компилируется на ядре 3.x | Присвой getValue().c_str() переменной типа String |
| «Sketch too big» (скетч слишком большой) | Bluetooth вместе с Wi-Fi не помещается в раздел приложения | Partition Scheme: выбери Huge APP или используй NimBLE-Arduino |
| iPhone не находит ESP32 | Классический Bluetooth (SPP) не поддерживается в iOS | Используй BLE и подключайся внутри приложения |
| Устройство пропадает после первого подключения | После разрыва соединения реклама не перезапущена | Вызывай startAdvertising() после разрыва соединения |
| Уведомления не приходят | Уведомления не включены на телефоне или нет BLE2902 | Нажми тройную стрелку в nRF Connect; добавь BLE2902 к характеристике |
| «read failed, socket might closed or timeout» (ошибка чтения, сокет закрыт или таймаут) | Устаревшие данные сопряжения или уже подключён другой телефон | Удали устройство на телефоне и выполни сопряжение заново; SPP принимает только одно подключение за раз |
| Конец длинного сообщения обрезается | MTU по умолчанию — 23 байта, то есть 20 байт данных на уведомление | Разбей сообщение на части по 20 байт или разреши MTU побольше через BLEDevice::setMTU(185) |
| Изменения сервисов не видны на телефоне | Телефон хранит старую таблицу GATT в кэше | Выключи и включи Bluetooth на телефоне; на Android удали устройство из сопряжённых |
С Bluetooth на ESP32 превратить телефон в пульт, а ESP32 — в беспроводной датчик или умное устройство можно буквально несколькими строками кода. Для быстрого старта с Android подходит BluetoothSerial, а для поддержки iPhone и низкого энергопотребления — BLE. Если хочется продолжить с беспроводными проектами на ESP32, в приложении BlueGrays есть проекты со светодиодами и RGB-светодиодами, измерение температуры с DHT22 и «Машинка на L298N с управлением по Bluetooth» — у каждого пошаговая схема подключения и готовый код.
Частые вопросы
Есть ли у ESP32 Bluetooth?
Да. Оригинальный чип ESP32 поддерживает и классический Bluetooth (BR/EDR), и BLE по стандарту Bluetooth 4.2 и не требует дополнительного модуля. ESP32-S3, C3, C6 и H2 поддерживают только BLE, а у ESP32-S2 Bluetooth нет вовсе.
Какая дальность у Bluetooth на ESP32?
С ESP32 с печатной антенной на плате в помещении обычно получается 10–20 м; на это расстояние влияют стены, металлические предметы и ориентация антенны. На открытом пространстве дальность растёт, а модули с внешней антенной (например, WROOM-32U) бьют ещё дальше.
Можно ли подключить ESP32 к iPhone по Bluetooth?
Да, по BLE, но не по классическому Bluetooth (BluetoothSerial): iOS открывает профиль последовательного порта (SPP) только для аксессуаров с сертификацией MFi. На iPhone к BLE-сервису ESP32 можно подключиться через приложение вроде nRF Connect, LightBlue или Bluefruit Connect.
Работает ли BluetoothSerial на ESP32-S3 и ESP32-C3?
Нет. В этих чипах нет оборудования классического Bluetooth, поэтому BluetoothSerial не скомпилируется. Чтобы получить ту же логику последовательного порта поверх BLE, используй Nordic UART Service (NUS); его понимают приложения вроде Serial Bluetooth Terminal.
Может ли ESP32 использовать Wi-Fi и Bluetooth одновременно?
Да, они работают вместе, по очереди деля один радиомодуль 2,4 GHz. Правда, скорость немного падает, а программа становится больше; при ошибке «Sketch too big» (скетч слишком большой) выбери Huge APP в Partition Scheme или используй более лёгкую библиотеку NimBLE-Arduino.
Нужен ли HC-05 для ESP32?
Обычно нет. Библиотека BluetoothSerial на классической ESP32 выполняет работу HC-05 внутри чипа и работает с теми же приложениями-терминалами. HC-05 нужен только на платах без Bluetooth (Arduino Uno, ESP8266, стандартная Pico, ESP32-S2) или если проекту нужен классический Bluetooth, а в наличии ESP32 только с BLE.
Что собрать после прочтения
Все проекты
Мигание встроенным светодиодом ESP32
Мигание встроенным синим светодиодом на ESP32 DevKit V1. Внешние компоненты не нужны! Первый проект, который стоит попробовать после покупки ESP32. Встроенный светодиод подключён к GPIO 2.

Мигающий светодиод на ESP32
Учит базовой работе с выходами GPIO на ESP32. Эффект мигания получается включением и выключением светодиода с цифрового пина. Это ESP32-версия классического проекта мигания на Arduino UNO; базовые команды мигания в среде Arduino те же, но расположение пинов ESP32 и уровни GPIO 3,3 V отличаются от Arduino UNO.

Управление светодиодом кнопкой на ESP32 (переключение)
При каждом нажатии кнопки состояние светодиода меняется: если горел — гаснет, если не горел — загорается. Классический шаблон переключения. Антидребезг убирает механический шум кнопки, чтобы одно нажатие давало одно переключение.

Управление светодиодом с телефона по Bluetooth
Небольшая репетиция того, как включать и выключать свет в комнате с телефона. Bluetooth-модуль передаёт в Arduino буквы, отправленные с телефона; одна буква включает красный светодиод, другая — зелёны

Машинка на L298N с управлением по Bluetooth
Преврати телефон в пульт и управляй своей машинкой-роботом. Arduino получает команды из приложения-пульта на телефоне через Bluetooth-модуль и вращает колёса через драйвер моторов; машинка едет вперёд

Управление RGB-светодиодом на ESP32
Бесконечное число цветов одним RGB-светодиодом вместо 3 отдельных. Как в Arduino, радужный переход получается заданием значений R/G/B через analogWrite(). Теория цвета (например, красный + зелёный = жёлтый) видна на практике.

Температура и влажность с DHT22 на ESP32 (с LCD-дисплеем)
Этот проект измеряет температуру и влажность воздуха датчиком DHT22 и в реальном времени показывает данные на I2C LCD 16x2. Данные читаются с датчика по однопроводному протоколу через библиотеку Adafruit DHT. Проект служит основой для IoT-метеостанций, умного дома или систем мониторинга среды в теплицах. Связь с DHT22 идёт через пин GPIO 4 у ESP32.

Управление лентой WS2812B NeoPixel на ESP32
Адресуемые светодиоды WS2812B (NeoPixel) — умные RGB-светодиоды, которыми управляют цепочкой через один пин данных. В этом проекте лента WS2812B из 8 светодиодов управляется библиотекой Adafruit_NeoPixel и показывает плавную радужную анимацию. Логика 3,3V у ESP32 работает с большинством WS2812B, но на случай нестабильности показано решение с последовательным резистором 470Ω и конденсатором 1000µF. Анимация получается сдвигом значения оттенка.
Другие статьи
Приложение BlueGrays
Собери эту схему шаг за шагом в телефоне
Схема подключения, код и список деталей на одном экране. 200+ проектов на Arduino, ESP32 и Pico готовы к сборке.
Наведи камеру телефона
Наведи камеру на код — откроется магазин приложений на телефоне.
или открыть магазин

