A Bluetooth BLE iBeacon observer, that scans for a list of available beacons, then for each iBeacon found, it reads and processes it’s gathered data.
The example presented builds using Platformio with the espressif arduino-esp32 core, and leverages the NimBLE-Arduino Bluetooth library.
Project source code
; 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 = espressif32board = esp32devframework = arduinomonitor_speed = 115200lib_deps = h2zero/NimBLE-Arduino@^1.4.0
// File: main.cpp // ESP32_NimBLE_scanner_iBeacon_decoder.// <green@bug-eyed.monster> MIT License.//// An iBeacon observer.// Scan for servers and log each unique result. // Read each logged BLE beacon in turn, filter for// iBeacons, then process their manufacturer_data.// Repeat.#include<Arduino.h>#include"NimBLEBeacon.h"#include"NimBLEDevice.h"#define ENDIAN_CHANGE_U16(x) ((((x)&0xff00) >> 8) + (((x)&0xff) << 8))voidsetup() { Serial.begin(115200); // Initialise the NimBLE library, witholding any advertisable name.NimBLEDevice::init(""); // Create a Scan(ner): // Get a pointer to a newly created Scan instance. NimBLEScan *pScan = NimBLEDevice::getScan(); // Set active scanning, this will get more data from the advertiser. // Comment-out if not interested in iBeacon name.pScan->setActiveScan(true); // Block whilst scanning for advertising servers, storing // a list of all results, over a period given in seconds.Serial.printf("Scanning for BLE advertisers\n"); NimBLEScanResults results = pScan->start(10);Serial.printf("Found %d BLE advertisers\n", results.getCount()); // Iterate through the list of NimBLEAdvertisedDevice's stored // in the NimBLEScanResults scan results list.for(int i = 0; i < results.getCount(); i++) { // Get a pointer to the iterated Device instance. NimBLEAdvertisedDevice advertisedDevice = results.getDevice(i); // Retrieve the BLE beacon's manufacturer_data.std::string strManufacturerData = advertisedDevice.getManufacturerData(); // Did we find any manufacturer_data?if (strManufacturerData != "") { // Look for Apple ID and iBeacon lengthif (strManufacturerData.length() == 25 &&strManufacturerData[0] == 0x4c &&strManufacturerData[1] == 0x00 &&strManufacturerData[2] == 0x02 &&strManufacturerData[3] == 0x15) { // Leverage the NimBLEBeacon library // to help process the iBeacon data. NimBLEBeacon iBeacon = NimBLEBeacon(); // Load our NimBLEBeacon object with manufacturer_data.iBeacon.setData(strManufacturerData); // Process this found iBeacon's dataSerial.printf("Name : %s\n",advertisedDevice.getName().c_str());Serial.printf("Address : %s\n",advertisedDevice.getAddress().toString().c_str());Serial.printf("UUID : %s\n",iBeacon.getProximityUUID().toString().c_str());Serial.printf("Major : %d\n",ENDIAN_CHANGE_U16(iBeacon.getMajor()));Serial.printf("Minor : %d\n",ENDIAN_CHANGE_U16(iBeacon.getMinor()));Serial.printf("TX power : %d dBm\n",iBeacon.getSignalPower());Serial.printf("RSSI : %d dBm\n",advertisedDevice.getRSSI());Serial.println("-----------------------------------------------"); } } }}voidloop() {setup();}
Serial Port Output
Output obtained when run in proximity to one BLE iBeacon device flashed with the companion Bluetooth BLE iBeacon on ESP32.
Scanning for BLE advertisersFound 2 BLE advertisersName : ESP32-iBeaconAddress : 08:b6:1f:37:f3:92UUID : c6dd05d0-b428-4bd5-8831-4b62651e2b41Major : 1Minor : 1TX power : -60 dBmRSSI : -64 dBm-----------------------------------------------Scanning for BLE advertisers
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 = espressif32board = esp32devframework = arduinomonitor_speed=115200lib_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.#defineBEACON_UUID "C6dd05d0-b428-4bd5-8831-4b62651e2b41" // Beacon UUID 128-Bit // Comment-out to disable iBeacon naming.#defineNAMED_iBEACON// Select iBeacon name to advertise.#defineiBEACON_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#defineiBEACON_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.#defineMEASURED_POWER -60 // Global Advertising(er) object used to control the Advertisement.BLEAdvertising *pAdvertising;voidsetup() {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();#ifdefNAMED_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) 0x0FoAdvertisementData.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 AppoBeacon.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);#ifdefNAMED_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 advertisingpAdvertising->start();Serial.println("Advertising started...");}voidloop() {}
Exploration of Creating a Client: a simple Bluetooth BLE scanner that compiles a list of proximate servers, then attempts connection to each in turn. Those devices with a specific connectable service, having a specific read/write characteristic, are read from.
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 = espressif32board = esp32devframework = arduinomonitor_speed=115200lib_deps = h2zero/NimBLE-Arduino@^1.4.0
// File: main.cpp // ESP32_NimBLE_simple_client.// Scan for servers, connect to any advertising a service with uuid "ABCD",// then read from (or write to) that service's characteristic with uuid "1234".#include<Arduino.h>#include"NimBLEDevice.h"voidsetup() { Serial.begin(115200); // Initialise the NimBLE library, witholding any advertisable name.NimBLEDevice::init(""); // Create a Scan(ner): // Get a pointer to a newly created Scan instance. NimBLEScan *pScan = NimBLEDevice::getScan(); // Block whilst scanning for advertising servers, storing // a list of all results, over a period given in seconds, // or 0 (continuously).Serial.printf("Scanning for BLE advertisers\n"); NimBLEScanResults results = pScan->start(10);Serial.printf("Found %d BLE advertisers\n", results.getCount()); // Model a BLE UUID, setting the service uuid for which we'll look. NimBLEUUID serviceUuid("ABCD"); // Iterate through the list of NimBLEAdvertisedDevice's stored // in the NimBLEScanResults scan results list.for(int i = 0; i < results.getCount(); i++) { // Get a pointer to the iterated Device instance. NimBLEAdvertisedDevice device = results.getDevice(i); // Does this iterated device advertise a service with UUID: "ABCD"?if (device.isAdvertisingService(serviceUuid)) {Serial.printf("Found a device advertising Service with UUID: ABCD\n"); // Get a pointer to a newly created Client instance. NimBLEClient *pClient = NimBLEDevice::createClient(); // Connect Client to iterated device. if (pClient->connect(&device)) { // Get a pointer to the Service instance previously found. NimBLERemoteService *pService = pClient->getService(serviceUuid); // Did we get a pointer to the Service instance?if (pService != nullptr) {Serial.printf("Found Service with UUID: ABCD\n"); // Get a pointer to the Characteristic instance with UUID: "1234". NimBLERemoteCharacteristic *pCharacteristic = pService->getCharacteristic("1234"); // Did we get a pointer to the Characteristic instance we want?if (pCharacteristic != nullptr) {Serial.printf("Found Characteristic with UUID: 1234\n");if (pCharacteristic->canRead()) {Serial.printf("Characteristic is readable\n"); // Read the value of the Characteristic.std::string value = pCharacteristic->readValue(); // Print the value.Serial.printf("Characteristic value: %s\n", String(value.c_str())); } } } } else { // No device scanned advertises the service with characteristic we sought.printf("Failed to connect\n"); } // Because simultaneous connections to Clients are possible, we abandon // the connection to this iterated client and reclaim it's resources.NimBLEDevice::deleteClient(pClient); } }}voidloop() {}
Serial Port Output
Output obtained when run in proximity to one BLE device flashed with the companion Simple BLE Server on ESP32.
Scanning for BLE advertisersFound 3 BLE advertisersFound a device advertising Service with UUID: ABCDFound Service with UUID: ABCDFound Characteristic with UUID: 1234Characteristic is readableCharacteristic value: Hello BLE
Exploration of Creating a Server: a simple Bluetooth BLE Server that advertises itself, along with a connectable service that has one read/write characteristic.
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 = espressif32board = esp32devframework = arduinomonitor_speed=115200lib_deps = h2zero/NimBLE-Arduino@^1.4.0
// File: main.cpp// Simple server that advertises itself and its connectable// service with one read/write characteristic.#include<Arduino.h>#include"NimBLEDevice.h"voidsetup(){ // Initialise the NimBLE library, providing a name for advertisement, or "".NimBLEDevice::init("NimBLE"); // Create a Server: // Get a pointer to a newly created Server instance. NimBLEServer *pServer = NimBLEDevice::createServer(); // Assign a Service to the Server: // Get a pointer to a newly created service instance, identified by // the uuid we give the service. It can be 16, 32, or 128 bits. NimBLEService *pService = pServer->createService("ABCD"); // Add a Characteristic to the Service: // Get a pointer to a newly created Characteristic instance, identified by // a 16, 32, or 128 bit uuid that we give the Characteristic, and with // default properties of NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE NimBLECharacteristic *pCharacteristic = pService->createCharacteristic("1234"); // Start the Service.pService->start(); // Give the Characteristic a value:pCharacteristic->setValue("Hello BLE"); // Create an an Advertisting(er) to solicit connections: // Get a pointer to a newly created Advertising(er) instance. NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); // Inform the Advertising(er) of the uuid given our Service when created:pAdvertising->addServiceUUID("ABCD"); // Begin advertising for connections:pAdvertising->start(); }voidloop(){}