Bluetooth BLE iBeacon on ESP32

An exploration of BLE_iBeacon.ino – A Bluetooth BLE iBeacon on an ESP32.

The example presented builds using Platformio with the espressif arduino-esp32 core, and leverages the NimBLE-Arduino Bluetooth library.

Project source code

; File: platformio.ini
; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino

monitor_speed=115200

lib_deps = h2zero/NimBLE-Arduino@^1.4.0
// File: main.cpp
/*
   Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleScan.cpp
   Ported to Arduino ESP32 by pcbreflux
   Changed and augmented by <green@bug-eyed.monster> - MIT License.
*/
// Create a BLE server that will send periodic iBeacon frames.
//   1. Create a BLE Server
//   2. Create advertising data
//   3. Start advertising.   


#include <Arduino.h>
#include "NimBLEDevice.h"
#include "NimBLEBeacon.h"


// Freshly generated beacon UUID:
// https://www.uuidgenerator.net/
// or linux tool uuidgen
// msb nsb Address type:
//  0   0  private random non-resolvable
//  0   1  private random resolvable
//  1   1  random static address
// Make 2 most significant bits 1 to indicate a random static address for iBeacon.
#define BEACON_UUID "C6dd05d0-b428-4bd5-8831-4b62651e2b41" // Beacon UUID 128-Bit 

// Comment-out to disable iBeacon naming.
#define NAMED_iBEACON

// Select iBeacon name to advertise.
#define iBEACON_NAME "ESP32-iBeacon"

// Choose a transmit power.
// -12dBmW - 0.0625mW
// -9dBmW  - 0.125mW
// -6dBmW  - 0.250mW
// -3dBmW  - 0.500mW
//  0dBmW  - 1.000mW
//  3dBmW  - 2.000mW
//  6dBmW  - 4.000mW
//  9dBmW  - 8.000mW
#define iBEACON_POWER  ESP_PWR_LVL_N0  // 1.000mW

// Average received transmission power in dBmW @ 1 meter. 
// Measure and record RSSI at 1 meter over 10-sec then record the average.
#define MEASURED_POWER -60 


// Global Advertising(er) object used to control the Advertisement.
BLEAdvertising *pAdvertising;


void setup() {

  Serial.begin(115200);

  Serial.printf("start %s\n", iBEACON_NAME);
  
  // Initialise the NimBLE library, witholding any advertisable name for now.
  BLEDevice::init("");

  // Set transmitter power.
  BLEDevice::setPower(iBEACON_POWER); 

  // Get a pointer to an Advertising(er) instance, for subsequent population.
  pAdvertising = BLEDevice::getAdvertising();
  
  // Create and initialise AdvertisementData object.
  BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
#ifdef NAMED_iBEACON
  // Create and initialise ScanResponseData object.
  BLEAdvertisementData oScanResponseData = BLEAdvertisementData();

  // Populate ScanResponse with iBeacon's name.
  oScanResponseData.setName(iBEACON_NAME);
#endif

  // Set Advertisement flags to indicate capabilities and features of
  // this Bluetooth device. iBeacon is a generally discoverable BLE device.
  //   LE Limited Discoverable Mode         0x01
  //   LE General Discoverable Mode         0x02
  //   BR/ERD Not Supported                 0x04   (device only supports BLE)
  //   LE and BR/EDR Capable (Controller)   0x08
  //   LE and BR/EDR Capable (Host)         0x0F
  oAdvertisementData.setFlags(0x06); // LE General Discoverable Mode 0x02 | BR/ERD Not Supported 0x04

  // Assemble the manufacturer_data.
  BLEBeacon oBeacon = BLEBeacon();
  oBeacon.setManufacturerId(0x4C00); // Use Apple 0x004C ID without license, as no iPhone App
  oBeacon.setProximityUUID(BLEUUID(BEACON_UUID));
  oBeacon.setMajor(1);
  oBeacon.setMinor(1);
  oBeacon.setSignalPower((char)MEASURED_POWER); // Add the measured power value
  
  // Add manufacturer_data to AdvertisementData.
  std::string strServiceData = "";
  strServiceData += (char)(26);     // Length of the manufacturer_data
  strServiceData += (char)0xFF;     // Indicates that type is manufacturer_data
  strServiceData += oBeacon.getData(); 
  oAdvertisementData.addData(strServiceData);

  // Add AdvertisementData to Advertising(er).
  pAdvertising->setAdvertisementData(oAdvertisementData);
#ifdef NAMED_iBEACON
  // Add ScanResponseData to Advertising(er).
  pAdvertising->setScanResponseData(oScanResponseData);
#endif

  // Choose an Advertising mode for the Advertising(er).
  // iBeacon is a non-connectable, undirected broadcaster,
  // set the appropriate PDU type in the packet header.
  //   BLE_GAP_CONN_MODE_NON (non-connectable; 3.C.9.3.2).
  //   BLE_GAP_CONN_MODE_DIR (directed-connectable; 3.C.9.3.3).
  //   BLE_GAP_CONN_MODE_UND (undirected-connectable; 3.C.9.3.4).
  pAdvertising->setAdvertisementType(BLE_GAP_CONN_MODE_NON);

   // Start advertising
  pAdvertising->start();
  Serial.println("Advertising started...");
}

void loop() {

}

Nordic nRF Connect mobile app screenshots

passive scan of nameless build

active scan of build with name

Prototypical reference: https://github.com/h2zero/NimBLE-Arduino/blob/master/examples/Refactored_original_examples/BLE_iBeacon/BLE_iBeacon.ino

Suggested reading: https://www.elektor.com/products/develop-your-own-bluetooth-low-energy-applications (no affiliation).