Bluetooth Low Energy con ESP32: scansione e advertising

Pubblicato il 02/05/2026 ESP32

BLE su ESP32 in due ruoli

L'ESP32 può essere contemporaneamente:

  • BLE Central (scanner): cerca dispositivi vicini, legge advertising, si connette come client
  • BLE Peripheral (server): pubblica servizi e caratteristiche, accetta connessioni

Scenario 1: scanner di beacon

Sketch che elenca tutti i dispositivi BLE in raggio ogni 10 secondi (utile per presence detection in domotica):

#include <BLEDevice.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>

BLEScan* scanner;

class Listener: public BLEAdvertisedDeviceCallbacks {
  void onResult(BLEAdvertisedDevice d) {
    Serial.printf("MAC: %s | Nome: %s | RSSI: %d\n",
      d.getAddress().toString().c_str(),
      d.haveName() ? d.getName().c_str() : "-",
      d.getRSSI());
  }
};

void setup() {
  Serial.begin(115200);
  BLEDevice::init("");
  scanner = BLEDevice::getScan();
  scanner->setAdvertisedDeviceCallbacks(new Listener());
  scanner->setActiveScan(true);
}

void loop() {
  scanner->start(10, false); // 10 secondi
  scanner->clearResults();
  delay(1000);
}

Triangolazione tramite RSSI

L'RSSI (Received Signal Strength Indicator) indica grossolanamente la distanza dal beacon:

  • -30 dBm → contatto/molto vicino
  • -60 dBm → 1-3 metri
  • -80 dBm → 5-10 metri
  • -95 dBm → al limite della copertura

Per Home Assistant: pubblica via MQTT MAC + RSSI, e bermuda o esphome-bluetooth-proxy calcolano la posizione approssimata in stanza.

Scenario 2: server BLE personalizzato

Esponiamo una caratteristica leggibile/scrivibile dal client (es. un'app smartphone):

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

#define SERVICE_UUID        "12345678-1234-1234-1234-1234567890ab"
#define CHARACTERISTIC_UUID "abcd1234-1234-1234-1234-1234567890ab"

BLECharacteristic* pChar;

void setup() {
  BLEDevice::init("ESP32-Sensor");
  BLEServer *pServer = BLEDevice::createServer();
  BLEService *pService = pServer->createService(SERVICE_UUID);

  pChar = pService->createCharacteristic(
    CHARACTERISTIC_UUID,
    BLECharacteristic::PROPERTY_READ |
    BLECharacteristic::PROPERTY_NOTIFY
  );
  pChar->addDescriptor(new BLE2902());
  pChar->setValue("22.5");

  pService->start();
  pServer->getAdvertising()->start();
}

void loop() {
  float t = 22.0 + random(-50, 50) / 10.0;
  String s = String(t, 2);
  pChar->setValue(s.c_str());
  pChar->notify();
  delay(2000);
}

Da smartphone usa nRF Connect (gratuito Android/iOS): si connette al ESP32-Sensor, vede la caratteristica e riceve i valori in push.

Sniffing iBeacon Apple

Gli iBeacon hanno un formato standard nei manufacturer data. Estrai UUID, major, minor:

std::string md = d.getManufacturerData();
if (md.length() >= 23 && md[0] == 0x4C && md[1] == 0x00
    && md[2] == 0x02 && md[3] == 0x15) {
  // È un iBeacon
  // bytes 4-19 = UUID, 20-21 = major, 22-23 = minor
}

Consumi BLE

Mantenere lo scanner attivo costa ~80 mA. Per progetti a batteria, alterna scan brevi (5 s) a deep sleep (60 s): media ~10 mA, batteria 18650 dura 12-15 giorni.

BLE vs Bluetooth Classic

Sull'ESP32 originale entrambi sono supportati ma occupano molta flash. Sul ESP32-S3 e ESP32-C3 è disponibile solo BLE (non Bluetooth Classic): per stampanti termiche o speaker A2DP serve un ESP32 originale.

Bluetooth Proxy per Home Assistant

ESPHome offre un firmware preconfezionato Bluetooth Proxy: l'ESP32 funge da hub BLE che inoltra tutti i pacchetti vicini a Home Assistant via WiFi. Estende dispositivi BLE (Xiaomi temperature, Switchbot) a tutta la casa con uno o più ESP32 sparsi.