ESP32 Bluetooth : tutoriel Classic et BLE avec ton smartphone
Bluetooth ESP32 : LED pilotée depuis le téléphone avec BluetoothSerial, serveur BLE avec services et UUID, test nRF Connect, données capteur, iPhone et erreurs.
Le Bluetooth de l'ESP32 utilise la radio sans fil intégrée à la puce pour relier ton ESP32 à un téléphone, une tablette ou un ordinateur sans aucun module supplémentaire. L'ESP32 d'origine prend en charge deux types de Bluetooth à la fois : le Bluetooth Classic, qui se comporte comme un port série, et le Bluetooth Low Energy (BLE), qui consomme très peu d'énergie. Avec un Arduino Uno, il te faut un module HC-05 séparé pour dialoguer avec ton téléphone ; avec l'ESP32, quelques lignes de code suffisent. Dans ce guide, tu vas mettre en place les deux méthodes pas à pas, allumer une LED depuis ton téléphone, écrire ton propre service BLE et envoyer des données de capteur vers ton smartphone.
La méthode à choisir dépend de ton téléphone et de ta carte. Si tu as un smartphone Android et un ESP32 DevKit classique, le démarrage le plus rapide passe par la bibliothèque BluetoothSerial. Si tu utilises un iPhone ou si ta carte est un ESP32-S3 ou un ESP32-C3, le Bluetooth Classic ne te servira à rien : il te faut le BLE. Vers la fin, tu trouveras aussi le même projet BLE pour le Raspberry Pi Pico W en MicroPython. Si c'est ta première fois avec l'ESP32, termine d'abord l'installation de l'Arduino IDE décrite dans Qu'est-ce que l'ESP32 ?

Qu'est-ce que le Bluetooth de l'ESP32 ? Classic ou BLE
Le Bluetooth Classic est le type ancien et bien connu qu'on retrouve dans les écouteurs sans fil et dans des modules comme le HC-05. L'ESP32 l'utilise via le SPP (Serial Port Profile) : les octets circulent les uns après les autres, comme s'il y avait un câble série invisible entre le téléphone et la carte. Tu appaires d'abord l'appareil dans les réglages Bluetooth du téléphone, puis tu t'y connectes avec une application de terminal. Le BLE, lui, est un système distinct apparu en 2010 avec le Bluetooth 4.0 et conçu pour des appareils qui fonctionnent des mois sur pile. En BLE, il n'y a pas de flux de données continu : l'ESP32 conserve un petit tableau de valeurs (GATT), et le téléphone les lit, les écrit ou s'y abonne pour être prévenu quand elles changent. Malgré leurs noms proches, les deux ne peuvent pas communiquer entre eux : une application écrite pour le BLE ne peut pas se connecter à un appareil Bluetooth Classic.
| Caractéristique | Bluetooth Classic (SPP) | Bluetooth Low Energy (BLE) |
|---|---|---|
| Bibliothèque Arduino | BluetoothSerial (incluse dans le paquet ESP32) | BLEDevice (incluse dans le paquet ESP32) ou NimBLE-Arduino |
| Modèle de données | Flux d'octets continu, comme un port série | Table GATT composée de services et de caractéristiques |
| Quantité de données | Élevée, adaptée aux flux continus | Petits paquets ; 20 octets par notification par défaut |
| Consommation | Toujours élevée pendant la connexion | Très faible ; des mois sur pile possibles |
| Appairage | Dans les réglages Bluetooth du téléphone | Inutile ; la connexion se fait depuis l'application |
| Android | Pris en charge | Pris en charge |
| iPhone (iOS) | Non pris en charge | Pris en charge |
| ESP32 compatibles | Uniquement l'ESP32 d'origine (WROOM-32, WROVER) | ESP32, S3, C3, C6 et H2 (sauf S2) |
| Usage typique | Terminal série, voiture robot, remplaçant du HC-05 | Capteurs, objets connectés portables, domotique, balises (beacons) |
Voici une règle pratique : si tu veux envoyer et recevoir du texte depuis un téléphone Android et voir vite un résultat, choisis le Classic ; s'il te faut la compatibilité iPhone, une faible consommation ou l'une des cartes ESP32 récentes, choisis le BLE. Pour un nouveau projet, le BLE vaut la peine d'être appris, car il fonctionne avec les deux types de téléphones et se trouve dans toutes les nouvelles puces Espressif dotées de Bluetooth. Si tu as déjà travaillé avec le HC-05, tu verras que la logique de commandes de Module Bluetooth HC-05 est exactement la même avec BluetoothSerial.
Quel Bluetooth sur quelle carte ESP32 ?
Toutes les puces de la famille ESP32 n'ont pas le même matériel Bluetooth. L'erreur la plus fréquente consiste à téléverser un exemple BluetoothSerial trouvé sur Internet sur une carte ESP32-S3 ou ESP32-C3. Espressif n'a mis le Bluetooth Classic que dans la puce ESP32 d'origine ; les puces suivantes dotées de Bluetooth ne gèrent que le BLE. Tu peux savoir quelle puce porte ta carte grâce à l'inscription sur le blindage métallique du module (par exemple ESP32-WROOM-32 ou ESP32-S3-WROOM-1).
| Carte / puce | Bluetooth Classic | BLE | Remarque |
|---|---|---|---|
| ESP32 (WROOM-32, WROVER, DevKit V1) | Oui | Oui (4.2) | La seule famille ESP32 où BluetoothSerial fonctionne |
| ESP32-S2 | Non | Non | Wi-Fi uniquement |
| ESP32-S3 | Non | Oui (Bluetooth 5) | BluetoothSerial ne compile pas, utilise le BLE |
| ESP32-C3 | Non | Oui (Bluetooth 5) | Petites cartes BLE bon marché |
| ESP32-C6 | Non | Oui (Bluetooth 5) | Aussi Wi-Fi 6, Zigbee et Thread |
| ESP32-H2 | Non | Oui (Bluetooth 5) | Pas de Wi-Fi ; orientée Zigbee et Thread |
| ESP8266 (NodeMCU, Wemos D1) | Non | Non | Un HC-05 ou un HM-10 est nécessaire pour le Bluetooth |
| Raspberry Pi Pico W / Pico 2 W | Présent dans le matériel | Oui | MicroPython ne gère que le BLE |
| Raspberry Pi Pico / Pico 2 | Non | Non | Module Bluetooth externe nécessaire |
Bluetooth Classic : piloter une LED depuis le téléphone avec BluetoothSerial
La bibliothèque BluetoothSerial présente l'ESP32 au téléphone comme un module HC-05 et utilise les mêmes commandes que l'objet Serial : available(), read(), print() et println(). Elle est fournie avec le paquet de cartes ESP32, tu n'as donc rien d'autre à installer. Pour le montage, la LED bleue intégrée reliée au GPIO 2 sur la plupart des cartes DevKit suffit ; si tu utilises une LED externe, branche-la sur le GPIO 2 avec une résistance de 220 Ω. Tu peux vérifier la valeur avec le calculateur de résistance de LED et voir les autres broches sûres de l'ESP32 dans l'outil Pinout. Vérifie que ESP32 Dev Module est sélectionnée dans Outils > Carte, puis téléverse ce code :
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}Le code lit chaque ligne venant du téléphone comme une commande. readStringUntil() attend l'arrivée du caractère de fin de ligne, et trim() supprime le retour chariot (CR) ajouté par le téléphone ainsi que les espaces ; si tu oublies cette ligne, l'envoi de "1" transmet en réalité "1" suivi d'un CR invisible et aucune commande ne correspond. Comme les commandes sont converties en minuscules, "AC" et "ac" donnent le même résultat. Les deux dernières lignes transmettent au téléphone tout ce que tu tapes dans le Moniteur série : tu peux ainsi discuter sans fil entre ton ordinateur et ton téléphone. Les vérifications #error du début arrêtent la compilation avec un message clair si la mauvaise carte est sélectionnée.
Se connecter depuis le téléphone avec Serial Bluetooth Terminal
- Après le téléversement, ouvre le Moniteur série à 115200 bauds et vérifie que le message "Eslesmeye hazir / Ready to pair" s'affiche.
- Sur ton téléphone Android, recherche de nouveaux appareils dans Paramètres > Bluetooth et appaire BlueGrays-ESP32. Par défaut, l'ESP32 s'appaire sans code PIN ; si le téléphone affiche un code de confirmation, accepte-le.
- Installe l'application Serial Bluetooth Terminal de Kai Morich depuis Google Play.
- Dans l'application, ouvre Devices depuis le menu latéral et choisis BlueGrays-ESP32 dans l'onglet Bluetooth Classic.
- Touche l'icône de prise dans la barre du haut ; dès que "Connected" s'affiche, la connexion est établie.
- Tape 1 dans la zone du bas et envoie-le : la LED s'allume et la réponse "LED acik / LED on" apparaît. 0 l'éteint et durum t'indique son état actuel.
Comment fonctionne le BLE ? Services, caractéristiques et UUID
Le BLE repose sur deux rôles. L'ESP32 joue le serveur (périphérique) : il conserve les valeurs et signale "je suis là" en envoyant à intervalles courts (toutes les 20 à 40 ms par défaut sur l'ESP32) de petits paquets d'advertising (annonce) de 31 octets au maximum. Le téléphone joue le client (central) : il scanne les alentours, trouve l'appareil et s'y connecte. Une fois connecté, le téléphone lit la table GATT (Generic Attribute Profile) de l'ESP32. Imagine cette table comme une armoire de classement : chaque tiroir est un service et chaque dossier dans un tiroir est une caractéristique. Une caractéristique contient une seule valeur (par exemple une température ou une commande de LED) et des propriétés qui indiquent ce qu'on peut en faire.
| Terme | Signification | Exemple dans ce guide |
|---|---|---|
| Advertising (annonce) | Court paquet par lequel l'appareil annonce son nom et ses services | Annonce sous le nom "BlueGrays-BLE" |
| Service | Groupe qui rassemble des caractéristiques liées | Service Nordic UART, Environmental Sensing (0x181A) |
| Caractéristique | Une seule valeur lue ou écrite | Commande de la LED, température (0x2A6E) |
| READ | Le téléphone lit la valeur quand il le souhaite | Dernière température mesurée |
| WRITE / WRITE_NR | Le téléphone écrit une valeur (avec ou sans réponse) | Écrire "1" allume la LED |
| NOTIFY | L'ESP32 prévient lui-même le téléphone quand la valeur change | Une nouvelle température toutes les 2 secondes |
| Descripteur 0x2902 (CCCD) | Interrupteur qui active ou désactive les notifications | Bouton à triple flèche de nRF Connect |
| UUID | Numéro d'identification des services et caractéristiques | 6E400001-B5A3-F393-E0A9-E50E24DCCA9E |
Chaque service et chaque caractéristique sont identifiés par un UUID. Les fonctions standard définies par le Bluetooth SIG utilisent de courts numéros de 16 bits, comme 0x180F pour le service de batterie et 0x2A6E pour la caractéristique de température. Pour tes propres données sans équivalent standard, génère un UUID aléatoire de 128 bits ; en Python, import uuid; print(uuid.uuid4()) suffit. Quand tu choisis ton UUID, n'utilise pas une valeur qui suit le modèle xxxxxxxx-0000-1000-8000-00805F9B34FB, car ce modèle est réservé aux numéros 16 bits du Bluetooth SIG. Pour fonctionner sans accroc avec des applications existantes, il est encore plus pratique d'utiliser des UUID que tout le monde reconnaît :
| Service / caractéristique | UUID | Format des données |
|---|---|---|
| Battery Service / Battery Level | 0x180F / 0x2A19 | 1 octet, pourcentage (0–100) |
| Environmental Sensing / Temperature | 0x181A / 0x2A6E | Entier signé sur 2 octets, 0,01 °C |
| Environmental Sensing / Humidity | 0x181A / 0x2A6F | Entier non signé sur 2 octets, 0,01 % |
| Heart Rate / Heart Rate Measurement | 0x180D / 0x2A37 | Octet d'indicateurs + valeur du pouls |
| Nordic UART Service (NUS) | 6E400001-B5A3-F393-E0A9-E50E24DCCA9E | RX 6E400002 (écriture), TX 6E400003 (notification) |
Serveur BLE ESP32 : piloter une LED depuis le téléphone (Nordic UART)
Dans notre premier projet BLE, on transforme l'ESP32 en serveur Nordic UART Service (NUS). Le NUS est un service de "port série sur BLE" défini par Nordic Semiconductor et devenu un standard de fait : le téléphone écrit dans la caractéristique RX et l'ESP32 envoie des notifications via la caractéristique TX. Son atout : de nombreuses applications toutes prêtes le reconnaissent ; nRF Connect, l'onglet Bluetooth LE de Serial Bluetooth Terminal et Bluefruit Connect sur iPhone fonctionnent avec le même code. Le code de cette section marche donc avec Android comme avec iPhone, et aussi sur des cartes uniquement BLE comme l'ESP32-S3 et le C3. La bibliothèque BLE fait partie du paquet de cartes ESP32 : rien d'autre à installer.
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}La partie la plus importante du code, ce sont les classes de callback. onConnect et onDisconnect suivent l'état de la connexion, et onWrite récupère la valeur que le téléphone écrit dans la caractéristique RX. Ces fonctions s'exécutent dans la tâche propre à la pile Bluetooth : au lieu d'y faire de longs traitements, on se contente d'y modifier une variable, et c'est loop() qui fait le vrai travail. Le descripteur BLE2902 ajouté à la caractéristique TX est indispensable pour que le téléphone puisse activer les notifications. Quand la connexion tombe, l'ESP32 ne relance pas l'annonce tout seul ; sans la ligne startAdvertising() dans loop(), l'appareil devient introuvable après la première connexion. Les lignes setMinPreferred et setMaxPreferred ajoutent à l'annonce l'intervalle de connexion préféré, ce qui facilite la connexion avec les iPhone.
Tester le BLE avec nRF Connect pas à pas
- Installe sur ton téléphone l'application gratuite nRF Connect for Mobile de Nordic Semiconductor ; elle existe pour Android et pour iPhone.
- Sous Android, l'application demande le Bluetooth et l'autorisation Appareils à proximité (sous Android 11 et antérieur, l'autorisation de localisation avec la localisation activée) ; sans elle, la liste de scan reste vide.
- Dans l'onglet Scanner, appuie sur SCAN et repère BlueGrays-BLE dans la liste. La valeur RSSI affichée à côté est la force du signal : -50 dBm signifie tout près, -90 dBm la limite de portée.
- Touche CONNECT. L'écran qui s'ouvre affiche Nordic UART Service ; si tu avais utilisé tes propres UUID, il serait écrit "Unknown Service".
- Touche l'icône à triple flèche vers le bas à côté de la caractéristique TX (6E400003) pour activer les notifications.
- Touche l'icône flèche vers le haut à côté de la caractéristique RX (6E400002), choisis Text (UTF-8) comme type de données, tape 1 et envoie. La LED s'allume et "LED acik" apparaît sur la ligne TX.
Ce test est le moyen le plus rapide de prouver que la partie ESP32 fonctionne avant d'écrire une application mobile. En cas de souci, commence par nRF Connect : si l'appareil n'apparaît pas dans la liste, le problème vient de l'annonce ; s'il se connecte mais que le service n'apparaît pas, l'appel start() du service a été oublié ; si tu écris une valeur et que la LED reste éteinte, vérifie la comparaison dans onWrite. Tu peux aussi essayer le même code depuis l'onglet Bluetooth LE de Serial Bluetooth Terminal : l'application reconnaît le service NUS et tu échanges des messages comme en Bluetooth Classic. Pour créer ta propre application Android, jette un œil à App Inventor avec Arduino et Bluetooth.
Diffuser des données de capteur en BLE (DHT22)

| Broche du DHT22 | ESP32 | Remarque |
|---|---|---|
| VCC (+) | 3V3 | Fonctionne de 3,3 à 5 V |
| DATA (OUT) | GPIO 4 | Sur un capteur nu, ajoute une résistance de 10 kΩ entre DATA et 3V3 |
| GND (−) | GND | Masse commune |
Dans cet exemple, on utilise le service standard Environmental Sensing (0x181A) et ses caractéristiques de température (0x2A6E) et d'humidité (0x2A6F). L'intérêt des UUID standard, c'est que des applications comme nRF Connect reconnaissent la valeur et l'affichent directement sous la forme "23.45 °C". Le standard demande d'envoyer les valeurs non pas en texte, mais en entiers multipliés par 100 : une température de 23,45 °C devient le nombre 2345, envoyé sur 2 octets, octet de poids faible en premier (little-endian). Pour lire le capteur, on utilise la DHT sensor library d'Adafruit, présentée dans le guide du DHT11 ; tu peux l'installer depuis le Gestionnaire de bibliothèques.
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}Le code effectue une mesure toutes les deux secondes, car le DHT22 ne peut pas être lu plus souvent. Les valeurs sont d'abord écrites dans la caractéristique avec setValue() ; ainsi, le téléphone obtient aussi la dernière mesure quand il fait une lecture (READ) sans attendre de notification. Si le téléphone est connecté, notify() envoie aussitôt la nouvelle valeur. Si nRF Connect ne sait pas décoder la valeur, tu verras les octets bruts : pour 23,45 °C, l'écran affiche (0x) 29-09. En inversant les octets, tu obtiens 0x0929, soit 2345 en décimal ; divisé par 100, cela donne la température. Tu devras faire le même calcul dans ta propre application. Envoyer la valeur en texte peut sembler plus simple, mais le format standard utilise moins d'octets et fonctionne avec les applications existantes.
Bluetooth de l'ESP32 avec l'iPhone (iOS)
La question que posent le plus souvent les utilisateurs d'iPhone, c'est pourquoi l'ESP32 n'apparaît pas dans les réglages Bluetooth. Apple ne donne accès au profil port série (SPP) du Bluetooth Classic qu'aux accessoires certifiés MFi ; impossible donc de se connecter depuis un iPhone à un ESP32 qui utilise BluetoothSerial. La solution, c'est le BLE, et les exemples NUS et capteur ci-dessus fonctionnent sans problème sur iPhone. Quelques points à garder en tête :
- Recherche dans les Réglages : les appareils BLE ne figurent pas dans les réglages Bluetooth d'iOS ; établis la connexion depuis une application comme nRF Connect, LightBlue ou Bluefruit Connect.
- Cache : iOS mémorise le nom et la liste des services de l'appareil. Si tu vois encore l'ancienne liste après avoir modifié les services dans le code, désactive puis réactive le Bluetooth du téléphone.
- Pas d'adresse MAC : iOS ne communique pas l'adresse MAC de l'appareil aux applications et attribue à chacun son propre identifiant ; si tu utilises plusieurs ESP32, donne-leur des noms différents.
- Intervalle de connexion : ne supprime pas les lignes setMinPreferred(0x06) et setMaxPreferred(0x12) ; les exemples d'Espressif les recommandent aussi contre les problèmes de connexion avec l'iPhone.
- Ta propre application : sur iOS, on utilise Core Bluetooth avec Swift ; aucun appairage n'est nécessaire, l'application recherche directement l'UUID du service.
Le BLE avec le Raspberry Pi Pico W et MicroPython

Dans la famille Raspberry Pi Pico, le Bluetooth n'existe que sur le Pico W et le Pico 2 W. La puce CYW43439 de la carte gère matériellement le Bluetooth Classic et le BLE, mais MicroPython n'utilise que le BLE (depuis la version 1.20). Le code ci-dessous est l'équivalent MicroPython de l'exemple NUS de l'ESP32 ; comme il utilise les mêmes UUID, tu le testes de la même façon avec nRF Connect et Serial Bluetooth Terminal. Tu dois avoir installé la version de MicroPython prévue pour le Pico W. Le code lit aussi le capteur de température interne de la carte et l'envoie au téléphone connecté toutes les 2 secondes.
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)Comme le module bluetooth de MicroPython est de bas niveau, on construit nous-mêmes le paquet d'annonce. Chaque champ se compose d'un octet de longueur, d'un octet de type et des données : 0x01 correspond aux indicateurs, 0x09 au nom complet de l'appareil et 0x07 à l'UUID de service sur 128 bits. Un paquet d'annonce ne pouvant contenir que 31 octets, on a placé le champ UUID de 18 octets dans la réponse au scan (resp_data). gatts_register_services() renvoie les identifiants (handles) des caractéristiques ; ils servent à lire la valeur écrite par le téléphone et à envoyer les notifications. Pour un code plus court, tu peux installer avec mip la bibliothèque aioble, basée sur asyncio. Pour faire tourner ce code sur l'ESP32 sous MicroPython, il suffit de remplacer la ligne de la LED par Pin(2, Pin.OUT) et de lire par exemple un DHT11 à la place du capteur de température interne.
Wi-Fi et Bluetooth ensemble : mémoire, portée et alimentation
Sur l'ESP32, le Wi-Fi et le Bluetooth partagent la même radio 2,4 GHz. La puce les fait fonctionner en partage de temps : tu peux donc les utiliser en même temps, mais chacun ralentit un peu, et le Bluetooth Classic combiné à un trafic Wi-Fi intense est le scénario le plus délicat. Le vrai problème, c'est généralement la mémoire : la pile Bluetooth est volumineuse et, avec le Wi-Fi, le programme peut dépasser la partition d'application par défaut de 1,2 Mo (1 310 720 octets) ; à lui seul, l'exemple BLE de ce guide en occupe déjà environ 80 %. Si la compilation se termine par "Sketch too big" ou "text section exceeds available space in board" et que tu n'utilises pas les mises à jour sans fil (OTA), choisis Huge APP (3MB No OTA/1MB SPIFFS) dans Outils > Partition Scheme. La bibliothèque NimBLE-Arduino, moins gourmande en flash et en RAM, est aussi une bonne alternative.
La portée dépend de la conception de l'antenne de la carte et de l'environnement. Avec un ESP32 doté d'une antenne PCB intégrée, compte 10 à 20 mètres en intérieur selon les murs, davantage en extérieur ; les modules avec connecteur d'antenne externe (par exemple l'ESP32-WROOM-32U) portent encore plus loin. Recouvrir l'antenne d'un boîtier métallique, d'une batterie ou de câbles réduit fortement la portée. Comme le courant grimpe brièvement à l'allumage de la radio, un câble USB de mauvaise qualité peut faire redémarrer la carte avec le message "Brownout detector was triggered" ; tu trouveras les solutions dans ESP32 Brownout et Guru Meditation.
Idées de projets Bluetooth avec l'ESP32
- Voiture robot Bluetooth : transmets les commandes F, B, L et R reçues via BluetoothSerial à un pilote de moteurs L298N ; pour la mécanique, consulte Fabriquer une voiture robot Arduino.
- Prise connectée : remplace la LED de l'exemple NUS par un module relais et allume une lampe depuis ton iPhone.
- Thermomètre sans fil : ajoute un écran OLED à l'exemple du DHT22 pour afficher la valeur à la fois sur l'écran et sur le téléphone.
- Lumière d'ambiance colorée : change la couleur d'un ruban WS2812B NeoPixel avec des valeurs R, V et B envoyées depuis ton téléphone.
- Ta propre application Android : crée en quelques heures une télécommande avec boutons et curseurs dans App Inventor.
- Communication entre deux ESP32 : pour faire dialoguer deux cartes sans téléphone, essaie plutôt la méthode ESP-NOW, plus simple et plus rapide que le Bluetooth.
Erreurs fréquentes du Bluetooth de l'ESP32 et solutions
| Symptôme / erreur | Cause | Solution |
|---|---|---|
| "Serial Bluetooth not available or not enabled" | La carte choisie est un ESP32-S3, C3, C6 ou H2, sans Bluetooth Classic | Utilise un ESP32 classique ou passe à l'exemple BLE (NUS) |
| "conversion from 'String' to non-scalar type 'std::string' requested" | Code écrit pour le core 2.x (std::string) compilé avec un core 3.x | Affecte getValue().c_str() à une variable String |
| "Sketch too big" | Le Bluetooth et le Wi-Fi ensemble dépassent la partition d'application | Partition Scheme : choisis Huge APP ou utilise NimBLE-Arduino |
| L'iPhone ne trouve pas l'ESP32 | Le Bluetooth Classic (SPP) n'est pas pris en charge sur iOS | Utilise le BLE et connecte-toi depuis une application |
| L'appareil disparaît après la première connexion | L'annonce n'a pas été relancée après la déconnexion | Appelle startAdvertising() après une déconnexion |
| Aucune notification n'arrive | Les notifications ne sont pas activées sur le téléphone ou BLE2902 manque | Touche la triple flèche dans nRF Connect ; ajoute BLE2902 à la caractéristique |
| "read failed, socket might closed or timeout" | Anciennes données d'appairage ou un autre téléphone déjà connecté | Oublie l'appareil sur le téléphone et appaire-le à nouveau ; le SPP n'accepte qu'une connexion à la fois |
| La fin d'un long message est coupée | MTU par défaut de 23 octets, soit 20 octets de données par notification | Découpe le message en morceaux de 20 octets ou autorise un MTU plus grand avec BLEDevice::setMTU(185) |
| Les modifications de services n'apparaissent pas | Le téléphone garde l'ancienne table GATT en cache | Désactive puis réactive le Bluetooth du téléphone ; sous Android, oublie l'appareil |
Avec le Bluetooth de l'ESP32, transformer ton téléphone en télécommande et l'ESP32 en capteur sans fil ou en appareil connecté ne demande que quelques lignes de code. BluetoothSerial est le bon choix pour démarrer vite avec Android, et le BLE pour la compatibilité iPhone et la faible consommation. Pour continuer avec des projets sans fil à base d'ESP32, l'application BlueGrays propose des projets comme les LED, la LED RVB, la mesure de température avec le DHT22 ou une voiture pilotée en Bluetooth, chacun avec un schéma pas à pas et un code prêt à l'emploi.
Questions fréquentes
L'ESP32 a-t-il le Bluetooth ?
Oui. La puce ESP32 d'origine gère, en Bluetooth 4.2, à la fois le Bluetooth Classic (BR/EDR) et le BLE, sans module supplémentaire. Les ESP32-S3, C3, C6 et H2 ne gèrent que le BLE, et l'ESP32-S2 n'a pas de Bluetooth du tout.
Quelle est la portée du Bluetooth de l'ESP32 ?
Avec un ESP32 doté d'une antenne PCB intégrée, on obtient généralement 10 à 20 mètres en intérieur ; les murs, les objets métalliques et l'orientation de l'antenne influent sur cette distance. La portée augmente en extérieur, et les modules à antenne externe (par exemple le WROOM-32U) portent encore plus loin.
L'ESP32 peut-il se connecter à un iPhone en Bluetooth ?
Oui en BLE, mais pas en Bluetooth Classic (BluetoothSerial), car iOS ne donne accès au profil port série (SPP) qu'aux accessoires certifiés MFi. Sur iPhone, tu peux te connecter au service BLE de l'ESP32 avec une application comme nRF Connect, LightBlue ou Bluefruit Connect.
BluetoothSerial fonctionne-t-il sur l'ESP32-S3 et l'ESP32-C3 ?
Non. Ces puces n'ont pas de matériel Bluetooth Classic, donc BluetoothSerial ne compile pas. Pour retrouver la même logique de port série en BLE, tu peux utiliser le Nordic UART Service (NUS) ; des applications comme Serial Bluetooth Terminal le reconnaissent.
L'ESP32 peut-il utiliser le Wi-Fi et le Bluetooth en même temps ?
Oui, les deux fonctionnent ensemble en se partageant dans le temps la même radio 2,4 GHz. La vitesse baisse toutefois un peu et le programme grossit ; si tu obtiens l'erreur "Sketch too big", choisis Huge APP dans Partition Scheme ou utilise la bibliothèque NimBLE-Arduino, plus légère.
Faut-il un HC-05 avec l'ESP32 ?
En général, non. Sur l'ESP32 classique, la bibliothèque BluetoothSerial fait en interne le travail du HC-05 et fonctionne avec les mêmes applications de terminal. Un HC-05 n'est utile que sur des cartes sans Bluetooth (Arduino Uno, ESP8266, Pico standard, ESP32-S2) ou si un projet exige le Bluetooth Classic alors que ton ESP32 ne gère que le BLE.
Projets à réaliser après cet article
Voir tous les projets
Clignotement de la LED interne de l'ESP32
Faire clignoter la LED bleue intégrée à l'ESP32 DevKit V1. Aucun composant externe n'est nécessaire ! Le premier projet que vous devriez essayer lorsque vous achetez un ESP32. Le GPIO 2 est connecté à la LED interne.

Clignotement LED avec ESP32
Enseigne l'utilisation de base des sorties GPIO avec l'ESP32. Nous obtenons un effet de clignotement en allumant et éteignant une LED à partir de la broche numérique. Version ESP32 du projet blink classique sur Arduino UNO ; les commandes de base de blink dans l'environnement Arduino restent les mêmes, mais le brochage et les niveaux GPIO de 3,3 V de l'ESP32 diffèrent de ceux de l'Arduino UNO.

ESP32 Contrôle de LED avec bouton (Toggle)
Chaque fois que l'on appuie sur le bouton, la LED change d'état : si elle est allumée, elle s'éteint, si elle est éteinte, elle s'allume. Modèle classique de basculement. Avec le debounce, nous nettoyons le bruit mécanique du bouton-poussoir pour garantir un seul basculement avec une seule pression.

Contrôler une LED depuis un téléphone via Bluetooth
Une petite répétition pour allumer et éteindre les lumières de ta chambre depuis ton téléphone. Le module Bluetooth transmet à l'Arduino les lettres envoyées par ton téléphone ; une lettre allume la L

Voiture contrôlée par Bluetooth avec L298N
Transforme ton téléphone en télécommande et pilote ta propre voiture robot. L'Arduino reçoit, via un module Bluetooth, les ordres d'une appli de télécommande sur ton téléphone et fait tourner les roue

Contrôle d'une LED RVB avec ESP32
Génération illimitée de couleurs avec une seule LED RVB au lieu de 3 LED séparées. Comme dans Arduino, nous réalisons une transition arc-en-ciel en réglant les valeurs R/G/B avec analogWrite(). La théorie des couleurs (telle que rouge+vert=jaune) est mise en pratique.

ESP32 DHT22 Température et humidité (avec écran LCD)
Il s'agit d'un projet permettant de mesurer la température ambiante et le taux d'humidité à l'aide d'un capteur DHT22 et d'afficher ces données en temps réel sur un écran LCD I2C 16x2. Les données du capteur sont lues à l'aide du protocole one-wire grâce à la bibliothèque Adafruit DHT. Ce projet constitue la base des stations météo IoT, de la domotique ou des systèmes de surveillance de l'environnement dans les serres.

Contrôle d'une bande NeoPixel WS2812B avec l'ESP32
Les LED adressables WS2812B (NeoPixel) sont des LED RGB intelligentes pouvant être contrôlées en série via une seule broche de données. Dans ce projet, nous allons contrôler une bande WS2812B de 8 LED à l'aide de la bibliothèque Adafruit_NeoPixel afin de créer une animation fluide représentant un arc-en-ciel. Bien que le niveau logique 3,3 V de l'ESP32 fonctionne sans problème avec la plupart des composants WS2812B, nous présenterons une solution comprenant une résistance en série de 470 Ω et un
Autres articles
Application BlueGrays
Réalise ce montage pas à pas sur ton téléphone
Schéma, code et liste des composants sur un seul écran. Plus de 200 projets Arduino, ESP32 et Pico à réaliser.
Scanne avec ton téléphone
Pointe l’appareil photo vers le code : la boutique d’applications de ton téléphone s’ouvre.
ou ouvre la boutique

