This commit is contained in:
2026-05-22 21:52:50 +03:00
commit be7c60e4dd
1854 changed files with 583428 additions and 0 deletions
@@ -0,0 +1,52 @@
/*
BLE5 extended scan example for esp32 C3 and S3
with this code it is simple to scan legacy (BLE4) compatible advertising,
and BLE5 extended advertising. New coded added in BLEScan is not changing old behavior,
which can be used with old esp32, but is adding functionality to use on C3/S3.
With this new API advertised device wont be stored in API, it is now user responsibility
author: chegewara
*/
#ifndef CONFIG_BLUEDROID_ENABLED
#error "NimBLE does not support extended scan yet. Try using Bluedroid."
#elif !defined(SOC_BLE_50_SUPPORTED)
#error "This SoC does not support BLE5. Try using ESP32-C3, or ESP32-S3"
#else
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
uint32_t scanTime = 100; //In 10ms (1000ms)
BLEScan *pBLEScan;
class MyBLEExtAdvertisingCallbacks : public BLEExtAdvertisingCallbacks {
void onResult(esp_ble_gap_ext_adv_report_t report) {
if (report.event_type & ESP_BLE_GAP_SET_EXT_ADV_PROP_LEGACY) {
// here we can receive regular advertising data from BLE4.x devices
Serial.println("BLE4.2");
} else {
// here we will get extended advertising data that are advertised over data channel by BLE5 devices
Serial.printf("Ext advertise: data_le: %d, data_status: %d \n", report.adv_data_len, report.data_status);
}
}
};
void setup() {
Serial.begin(115200);
Serial.println("Scanning...");
BLEDevice::init("");
pBLEScan = BLEDevice::getScan(); //create new scan
pBLEScan->setExtendedScanCallback(new MyBLEExtAdvertisingCallbacks());
pBLEScan->setExtScanParams(); // use with pre-defined/default values, overloaded function allows to pass parameters
delay(1000); // it is just for simplicity this example, to let ble stack to set extended scan params
pBLEScan->startExtScan(scanTime, 3); // scan duration in n * 10ms, period - repeat after n seconds (period >= duration)
}
void loop() {
// put your main code here, to run repeatedly:
delay(2000);
}
#endif // SOC_BLE_50_SUPPORTED
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires:
- CONFIG_SOC_BLE_50_SUPPORTED=y
- CONFIG_BLUEDROID_ENABLED=y
@@ -0,0 +1,143 @@
/*
Simple BLE5 multi advertising example on esp32 C3/S3
only ESP_BLE_GAP_SET_EXT_ADV_PROP_LEGACY_IND is backward compatible
and can be scanned with BLE4.2 devices
author: chegewara
*/
#ifndef CONFIG_BLUEDROID_ENABLED
#error "NimBLE does not support multi advertising yet. Try using Bluedroid."
#elif !defined(CONFIG_BT_BLE_50_FEATURES_SUPPORTED)
#error "This SoC does not support BLE5. Try using ESP32-C3, or ESP32-S3"
#else
#include <BLEDevice.h>
#include <BLEAdvertising.h>
esp_ble_gap_ext_adv_params_t ext_adv_params_1M = {
.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_CONNECTABLE,
.interval_min = 0x30,
.interval_max = 0x30,
.channel_map = ADV_CHNL_ALL,
.own_addr_type = BLE_ADDR_TYPE_RANDOM,
.peer_addr_type = BLE_ADDR_TYPE_RANDOM,
.peer_addr = {0, 0, 0, 0, 0, 0},
.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
.tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE,
.primary_phy = ESP_BLE_GAP_PHY_CODED,
.max_skip = 0,
.secondary_phy = ESP_BLE_GAP_PHY_1M,
.sid = 0,
.scan_req_notif = false,
};
esp_ble_gap_ext_adv_params_t ext_adv_params_2M = {
.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_SCANNABLE,
.interval_min = 0x40,
.interval_max = 0x40,
.channel_map = ADV_CHNL_ALL,
.own_addr_type = BLE_ADDR_TYPE_RANDOM,
.peer_addr_type = BLE_ADDR_TYPE_RANDOM,
.peer_addr = {0, 0, 0, 0, 0, 0},
.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
.tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE,
.primary_phy = ESP_BLE_GAP_PHY_1M,
.max_skip = 0,
.secondary_phy = ESP_BLE_GAP_PHY_2M,
.sid = 1,
.scan_req_notif = false,
};
esp_ble_gap_ext_adv_params_t legacy_adv_params = {
.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_LEGACY_IND,
.interval_min = 0x45,
.interval_max = 0x45,
.channel_map = ADV_CHNL_ALL,
.own_addr_type = BLE_ADDR_TYPE_RANDOM,
.peer_addr_type = BLE_ADDR_TYPE_RANDOM,
.peer_addr = {0, 0, 0, 0, 0, 0},
.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
.tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE,
.primary_phy = ESP_BLE_GAP_PHY_1M,
.max_skip = 0,
.secondary_phy = ESP_BLE_GAP_PHY_1M,
.sid = 2,
.scan_req_notif = false,
};
esp_ble_gap_ext_adv_params_t ext_adv_params_coded = {
.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_SCANNABLE,
.interval_min = 0x50,
.interval_max = 0x50,
.channel_map = ADV_CHNL_ALL,
.own_addr_type = BLE_ADDR_TYPE_RANDOM,
.peer_addr_type = BLE_ADDR_TYPE_RANDOM,
.peer_addr = {0, 0, 0, 0, 0, 0},
.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
.tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE,
.primary_phy = ESP_BLE_GAP_PHY_1M,
.max_skip = 0,
.secondary_phy = ESP_BLE_GAP_PHY_CODED,
.sid = 3,
.scan_req_notif = false,
};
static uint8_t raw_adv_data_1m[] = {0x02, 0x01, 0x06, 0x02, 0x0a, 0xeb, 0x12, 0x09, 'E', 'S', 'P', '_', 'M',
'U', 'L', 'T', 'I', '_', 'A', 'D', 'V', '_', '1', 'M', 0X0};
static uint8_t raw_scan_rsp_data_2m[] = {0x02, 0x01, 0x06, 0x02, 0x0a, 0xeb, 0x12, 0x09, 'E', 'S', 'P', '_', 'M',
'U', 'L', 'T', 'I', '_', 'A', 'D', 'V', '_', '2', 'M', 0X0};
static uint8_t legacy_adv_data[] = {0x02, 0x01, 0x06, 0x02, 0x0a, 0xeb, 0x15, 0x09, 'E', 'S', 'P', '_', 'M', 'U',
'L', 'T', 'I', '_', 'A', 'D', 'V', '_', 'C', 'O', 'D', 'E', 'D', 0X0};
static uint8_t legacy_scan_rsp_data[] = {0x02, 0x01, 0x06, 0x02, 0x0a, 0xeb, 0x16, 0x09, 'E', 'S', 'P', '_', 'M', 'U', 'L',
'T', 'I', '_', 'A', 'D', 'V', '_', 'L', 'E', 'G', 'A', 'C', 'Y', 0X0};
static uint8_t raw_scan_rsp_data_coded[] = {0x37, 0x09, 'V', 'E', 'R', 'Y', '_', 'L', 'O', 'N', 'G', '_', 'D', 'E', 'V', 'I', 'C', 'E', '_',
'N', 'A', 'M', 'E', '_', 'S', 'E', 'N', 'T', '_', 'U', 'S', 'I', 'N', 'G', '_', 'E', 'X', 'T',
'E', 'N', 'D', 'E', 'D', '_', 'A', 'D', 'V', 'E', 'R', 'T', 'I', 'S', 'I', 'N', 'G', 0X0};
uint8_t addr_1m[6] = {0xc0, 0xde, 0x52, 0x00, 0x00, 0x01};
uint8_t addr_2m[6] = {0xc0, 0xde, 0x52, 0x00, 0x00, 0x02};
uint8_t addr_legacy[6] = {0xc0, 0xde, 0x52, 0x00, 0x00, 0x03};
uint8_t addr_coded[6] = {0xc0, 0xde, 0x52, 0x00, 0x00, 0x04};
BLEMultiAdvertising advert(4); // max number of advertisement data
void setup() {
Serial.begin(115200);
Serial.println("Multi-Advertising...");
BLEDevice::init("");
advert.setAdvertisingParams(0, &ext_adv_params_1M);
advert.setAdvertisingData(0, sizeof(raw_adv_data_1m), &raw_adv_data_1m[0]);
advert.setInstanceAddress(0, addr_1m);
advert.setDuration(0);
advert.setAdvertisingParams(1, &ext_adv_params_2M);
advert.setScanRspData(1, sizeof(raw_scan_rsp_data_2m), &raw_scan_rsp_data_2m[0]);
advert.setInstanceAddress(1, addr_2m);
advert.setDuration(1);
advert.setAdvertisingParams(2, &legacy_adv_params);
advert.setAdvertisingData(2, sizeof(legacy_adv_data), &legacy_adv_data[0]);
advert.setScanRspData(2, sizeof(legacy_scan_rsp_data), &legacy_scan_rsp_data[0]);
advert.setInstanceAddress(2, addr_legacy);
advert.setDuration(2);
advert.setAdvertisingParams(3, &ext_adv_params_coded);
advert.setDuration(3);
advert.setScanRspData(3, sizeof(raw_scan_rsp_data_coded), &raw_scan_rsp_data_coded[0]);
advert.setInstanceAddress(3, addr_coded);
delay(1000);
advert.start(4, 0);
}
void loop() {
delay(2000);
}
#endif
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires:
- CONFIG_SOC_BLE_50_SUPPORTED=y
- CONFIG_BLUEDROID_ENABLED=y
@@ -0,0 +1,70 @@
/*
Simple BLE5 periodic advertising example on esp32 C3/S3
only ESP_BLE_GAP_SET_EXT_ADV_PROP_NONCONN_NONSCANNABLE_UNDIRECTED can be used for periodic advertising
author: chegewara
*/
#ifndef CONFIG_BLUEDROID_ENABLED
#error "NimBLE does not support periodic advertising yet. Try using Bluedroid."
#elif !defined(CONFIG_BT_BLE_50_FEATURES_SUPPORTED)
#error "This SoC does not support BLE5. Try using ESP32-C3, or ESP32-S3"
#else
#include <BLEDevice.h>
#include <BLEAdvertising.h>
esp_ble_gap_ext_adv_params_t ext_adv_params_2M = {
.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_NONCONN_NONSCANNABLE_UNDIRECTED,
.interval_min = 0x40,
.interval_max = 0x40,
.channel_map = ADV_CHNL_ALL,
.own_addr_type = BLE_ADDR_TYPE_RANDOM,
.peer_addr_type = BLE_ADDR_TYPE_RANDOM,
.peer_addr = {0, 0, 0, 0, 0, 0},
.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
.tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE,
.primary_phy = ESP_BLE_GAP_PHY_1M,
.max_skip = 0,
.secondary_phy = ESP_BLE_GAP_PHY_2M,
.sid = 1,
.scan_req_notif = false,
};
static uint8_t raw_scan_rsp_data_2m[] = {0x02, 0x01, 0x06, 0x02, 0x0a, 0xeb, 0x12, 0x09, 'E', 'S', 'P', '_', 'M',
'U', 'L', 'T', 'I', '_', 'A', 'D', 'V', '_', '2', 'M', 0X0};
static esp_ble_gap_periodic_adv_params_t periodic_adv_params = {
.interval_min = 0x320, // 1000 ms interval
.interval_max = 0x640,
.properties = 0, // Do not include TX power
};
static uint8_t periodic_adv_raw_data[] = {0x02, 0x01, 0x06, 0x02, 0x0a, 0xeb, 0x03, 0x03, 0xab, 0xcd, 0x11, 0x09, 'E', 'S',
'P', '_', 'P', 'E', 'R', 'I', 'O', 'D', 'I', 'C', '_', 'A', 'D', 'V'};
uint8_t addr_2m[6] = {0xc0, 0xde, 0x52, 0x00, 0x00, 0x02};
BLEMultiAdvertising advert(1); // max number of advertisement data
void setup() {
Serial.begin(115200);
Serial.println("Multi-Advertising...");
BLEDevice::init("");
advert.setAdvertisingParams(0, &ext_adv_params_2M);
advert.setAdvertisingData(0, sizeof(raw_scan_rsp_data_2m), &raw_scan_rsp_data_2m[0]);
advert.setInstanceAddress(0, addr_2m);
advert.setDuration(0, 0, 0);
delay(100);
advert.start();
advert.setPeriodicAdvertisingParams(0, &periodic_adv_params);
advert.setPeriodicAdvertisingData(0, sizeof(periodic_adv_raw_data), &periodic_adv_raw_data[0]);
advert.startPeriodicAdvertising(0);
}
void loop() {
delay(2000);
}
#endif
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires:
- CONFIG_SOC_BLE_50_SUPPORTED=y
- CONFIG_BLUEDROID_ENABLED=y
@@ -0,0 +1,93 @@
/*
BLE5 extended scan example for esp32 C3 and S3
with this code it is simple to scan legacy (BLE4) compatible advertising,
and BLE5 extended advertising. New coded added in BLEScan is not changing old behavior,
which can be used with old esp32, but is adding functionality to use on C3/S3.
With this new API advertised device wont be stored in API, it is now user responsibility
author: chegewara
*/
#ifndef CONFIG_BLUEDROID_ENABLED
#error "NimBLE does not support periodic sync yet. Try using Bluedroid."
#elif !defined(SOC_BLE_50_SUPPORTED)
#error "This SoC does not support BLE5. Try using ESP32-C3, or ESP32-S3"
#else
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
BLEScan *pBLEScan;
static bool periodic_sync = false;
static esp_ble_gap_periodic_adv_sync_params_t periodic_adv_sync_params = {
.filter_policy = 0,
.sid = 0,
.addr_type = BLE_ADDR_TYPE_RANDOM,
.addr = {0, 0, 0, 0, 0, 0},
.skip = 10,
.sync_timeout = 1000, // timeout: 1000 * 10ms
};
class MyBLEExtAdvertisingCallbacks : public BLEExtAdvertisingCallbacks {
void onResult(esp_ble_gap_ext_adv_report_t params) {
uint8_t *adv_name = NULL;
uint8_t adv_name_len = 0;
adv_name = esp_ble_resolve_adv_data(params.adv_data, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len);
if ((adv_name != NULL) && (memcmp(adv_name, "ESP_MULTI_ADV_2M", adv_name_len) == 0) && !periodic_sync) {
periodic_sync = true;
char adv_temp_name[60] = {'0'};
memcpy(adv_temp_name, adv_name, adv_name_len);
log_i("Start create sync with the peer device %s", adv_temp_name);
periodic_adv_sync_params.sid = params.sid;
// periodic_adv_sync_params.addr_type = params.addr_type;
memcpy(periodic_adv_sync_params.addr, params.addr, sizeof(esp_bd_addr_t));
esp_ble_gap_periodic_adv_create_sync(&periodic_adv_sync_params);
}
}
};
class MyPeriodicScan : public BLEPeriodicScanCallbacks {
// void onCreateSync(esp_bt_status_t status){}
// void onCancelSync(esp_bt_status_t status){}
// void onTerminateSync(esp_bt_status_t status){}
void onStop(esp_bt_status_t status) {
log_i("ESP_GAP_BLE_EXT_SCAN_STOP_COMPLETE_EVT");
periodic_sync = false;
pBLEScan->startExtScan(0, 0); // scan duration in n * 10ms, period - repeat after n seconds (period >= duration)
}
void onLostSync(uint16_t sync_handle) {
log_i("ESP_GAP_BLE_PERIODIC_ADV_SYNC_LOST_EVT");
esp_ble_gap_stop_ext_scan();
}
void onSync(esp_ble_periodic_adv_sync_estab_param_t params) {
log_i("ESP_GAP_BLE_PERIODIC_ADV_SYNC_ESTAB_EVT, status %d", params.status);
// esp_log_buffer_hex("sync addr", param->periodic_adv_sync_estab.adv_addr, 6);
log_i("sync handle %d sid %d perioic adv interval %d adv phy %d", params.sync_handle, params.sid, params.period_adv_interval, params.adv_phy);
}
void onReport(esp_ble_gap_periodic_adv_report_t params) {
log_i("periodic adv report, sync handle %d data status %d data len %d rssi %d", params.sync_handle, params.data_status, params.data_length, params.rssi);
}
};
void setup() {
Serial.begin(115200);
Serial.println("Periodic scan...");
BLEDevice::init("");
pBLEScan = BLEDevice::getScan(); //create new scan
pBLEScan->setExtendedScanCallback(new MyBLEExtAdvertisingCallbacks());
pBLEScan->setExtScanParams(); // use with pre-defined/default values, overloaded function allows to pass parameters
pBLEScan->setPeriodicScanCallback(new MyPeriodicScan());
delay(100); // it is just for simplicity this example, to let ble stack to set extended scan params
pBLEScan->startExtScan(0, 0);
}
void loop() {
delay(2000);
}
#endif // SOC_BLE_50_SUPPORTED
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires:
- CONFIG_SOC_BLE_50_SUPPORTED=y
- CONFIG_BLUEDROID_ENABLED=y
@@ -0,0 +1,118 @@
/*
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 Evandro Copercini
Changed to a beacon scanner to report iBeacon, EddystoneURL and EddystoneTLM beacons by beegee-tokyo
Upgraded Eddystone part by Tomas Pilny on Feb 20, 2023
*/
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
#include <BLEEddystoneURL.h>
#include <BLEEddystoneTLM.h>
#include <BLEBeacon.h>
int scanTime = 5; //In seconds
BLEScan *pBLEScan;
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
if (advertisedDevice.haveName()) {
Serial.print("Device name: ");
Serial.println(advertisedDevice.getName().c_str());
Serial.println("");
}
if (advertisedDevice.haveServiceUUID()) {
BLEUUID devUUID = advertisedDevice.getServiceUUID();
Serial.print("Found ServiceUUID: ");
Serial.println(devUUID.toString().c_str());
Serial.println("");
}
if (advertisedDevice.haveManufacturerData() == true) {
String strManufacturerData = advertisedDevice.getManufacturerData();
// Buffer to store manufacturer data (BLE max is 255 bytes)
uint8_t cManufacturerData[255];
size_t dataLength = strManufacturerData.length();
// Bounds checking to prevent buffer overflow
if (dataLength <= sizeof(cManufacturerData)) {
memcpy(cManufacturerData, strManufacturerData.c_str(), dataLength);
if (dataLength == 25 && cManufacturerData[0] == 0x4C && cManufacturerData[1] == 0x00) {
Serial.println("Found an iBeacon!");
BLEBeacon oBeacon = BLEBeacon();
oBeacon.setData(strManufacturerData);
Serial.printf("iBeacon Frame\n");
Serial.printf(
"ID: %04X Major: %d Minor: %d UUID: %s Power: %d\n", oBeacon.getManufacturerId(), ENDIAN_CHANGE_U16(oBeacon.getMajor()),
ENDIAN_CHANGE_U16(oBeacon.getMinor()), oBeacon.getProximityUUID().toString().c_str(), oBeacon.getSignalPower()
);
} else {
Serial.println("Found another manufacturers beacon!");
Serial.printf("strManufacturerData: %zu ", dataLength);
for (int i = 0; i < dataLength; i++) {
Serial.printf("[%X]", cManufacturerData[i]);
}
Serial.printf("\n");
}
} else {
Serial.printf("Manufacturer data too large (%zu bytes), skipping\n", dataLength);
}
}
if (advertisedDevice.getFrameType() == BLE_EDDYSTONE_URL_FRAME) {
Serial.println("Found an EddystoneURL beacon!");
BLEEddystoneURL EddystoneURL = BLEEddystoneURL(&advertisedDevice);
Serial.printf("URL bytes: 0x");
String url = EddystoneURL.getURL();
for (auto byte : url) {
Serial.printf("%02X", byte);
}
Serial.printf("\n");
Serial.printf("Decoded URL: %s\n", EddystoneURL.getDecodedURL().c_str());
Serial.printf("EddystoneURL.getDecodedURL(): %s\n", EddystoneURL.getDecodedURL().c_str());
Serial.printf("TX power %d (Raw 0x%02X)\n", EddystoneURL.getPower(), EddystoneURL.getPower());
Serial.println("\n");
}
if (advertisedDevice.getFrameType() == BLE_EDDYSTONE_TLM_FRAME) {
Serial.println("Found an EddystoneTLM beacon!");
BLEEddystoneTLM EddystoneTLM(&advertisedDevice);
Serial.printf("Reported battery voltage: %dmV\n", EddystoneTLM.getVolt());
Serial.printf("Reported temperature: %.2f°C (raw data=0x%04X)\n", EddystoneTLM.getTemp(), EddystoneTLM.getRawTemp());
Serial.printf("Reported advertise count: %lu\n", EddystoneTLM.getCount());
Serial.printf("Reported time since last reboot: %lus\n", EddystoneTLM.getTime());
Serial.println("\n");
Serial.print(EddystoneTLM.toString().c_str());
Serial.println("\n");
}
}
};
void setup() {
Serial.begin(115200);
Serial.println("Scanning...");
BLEDevice::init("");
pBLEScan = BLEDevice::getScan(); //create new scan
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster
pBLEScan->setInterval(100);
pBLEScan->setWindow(99); // less or equal setInterval value
}
void loop() {
// put your main code here, to run repeatedly:
BLEScanResults *foundDevices = pBLEScan->start(scanTime, false);
Serial.print("Devices found: ");
Serial.println(foundDevices->getCount());
Serial.println("Scan done!");
pBLEScan->clearResults(); // delete results fromBLEScan buffer to release memory
delay(2000);
}
@@ -0,0 +1,9 @@
## BLE Beacon Scanner
Initiates a BLE device scan.
Checks if the discovered devices are
- an iBeacon
- an Eddystone TLM beacon
- an Eddystone URL beacon
and sends the decoded beacon information over Serial log
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
+158
View File
@@ -0,0 +1,158 @@
/**
* A BLE client example that is rich in capabilities.
* There is a lot new capabilities implemented.
* author unknown
* updated by chegewara
*/
#include "BLEDevice.h"
//#include "BLEScan.h"
// The remote service we wish to connect to.
static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
// The characteristic of the remote service we are interested in.
static BLEUUID charUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8");
static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic *pRemoteCharacteristic;
static BLEAdvertisedDevice *myDevice;
// Callback function to handle notifications
static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
Serial.print("Notify callback for characteristic ");
Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
Serial.print(" of data length ");
Serial.println(length);
Serial.print("data: ");
Serial.write(pData, length);
Serial.println();
}
class MyClientCallback : public BLEClientCallbacks {
void onConnect(BLEClient *pclient) {}
void onDisconnect(BLEClient *pclient) {
connected = false;
Serial.println("onDisconnect");
}
};
bool connectToServer() {
Serial.print("Forming a connection to ");
Serial.println(myDevice->getAddress().toString().c_str());
BLEClient *pClient = BLEDevice::createClient();
Serial.println(" - Created client");
pClient->setClientCallbacks(new MyClientCallback());
// Connect to the remove BLE Server.
pClient->connect(myDevice); // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
Serial.println(" - Connected to server");
pClient->setMTU(517); //set client to request maximum MTU from server (default is 23 otherwise)
// Obtain a reference to the service we are after in the remote BLE server.
BLERemoteService *pRemoteService = pClient->getService(serviceUUID);
if (pRemoteService == nullptr) {
Serial.print("Failed to find our service UUID: ");
Serial.println(serviceUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println(" - Found our service");
// Obtain a reference to the characteristic in the service of the remote BLE server.
pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
if (pRemoteCharacteristic == nullptr) {
Serial.print("Failed to find our characteristic UUID: ");
Serial.println(charUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println(" - Found our characteristic");
// Read the value of the characteristic.
if (pRemoteCharacteristic->canRead()) {
String value = pRemoteCharacteristic->readValue();
Serial.print("The characteristic value was: ");
Serial.println(value.c_str());
}
if (pRemoteCharacteristic->canNotify()) {
// Register/Subscribe for notifications
pRemoteCharacteristic->registerForNotify(notifyCallback);
}
connected = true;
return true;
}
/**
* Scan for BLE servers and find the first one that advertises the service we are looking for.
*/
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
/**
* Called for each advertising BLE server.
*/
void onResult(BLEAdvertisedDevice advertisedDevice) {
Serial.print("BLE Advertised Device found: ");
Serial.println(advertisedDevice.toString().c_str());
// We have found a device, let us now see if it contains the service we are looking for.
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {
BLEDevice::getScan()->stop();
myDevice = new BLEAdvertisedDevice(advertisedDevice);
doConnect = true;
doScan = true;
} // Found our server
} // onResult
}; // MyAdvertisedDeviceCallbacks
void setup() {
Serial.begin(115200);
Serial.println("Starting Arduino BLE Client application...");
BLEDevice::init("");
// Retrieve a Scanner and set the callback we want to use to be informed when we
// have detected a new device. Specify that we want active scanning and start the
// scan to run for 5 seconds.
BLEScan *pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setInterval(1349);
pBLEScan->setWindow(449);
pBLEScan->setActiveScan(true);
pBLEScan->start(5, false);
} // End of setup.
// This is the Arduino main loop function.
void loop() {
// If the flag "doConnect" is true then we have scanned for and found the desired
// BLE Server with which we wish to connect. Now we connect to it. Once we are
// connected we set the connected flag to be true.
if (doConnect == true) {
if (connectToServer()) {
Serial.println("We are now connected to the BLE Server.");
} else {
Serial.println("We have failed to connect to the server; there is nothing more we will do.");
}
doConnect = false;
}
// If we are connected to a peer BLE Server, update the characteristic each time we are reached
// with the current time since boot.
if (connected) {
String newValue = "Time since boot: " + String(millis() / 1000);
Serial.println("Setting new characteristic value to \"" + newValue + "\"");
// Set the characteristic's value to be the array of bytes that is actually a string.
pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());
} else if (doScan) {
BLEDevice::getScan()->start(0); // this is just example to start scan after disconnect, most likely there is better way to do it in arduino
}
delay(1000); // Delay a second between loops.
} // End of loop
+5
View File
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
@@ -0,0 +1,253 @@
/*
* BLE HID Gamepad Client Example
*
* This example demonstrates how to connect to a BLE HID Gamepad and read its input.
* The ESP32 acts as a BLE Central (client) that connects to a BLE gamepad peripheral.
*
* Features:
* - Scans for BLE HID gamepad devices
* - Connects to the first gamepad found
* - Secure pairing with bonding
* - Subscribes to input report notifications
* - Parses and displays gamepad input (buttons and axes)
* - Automatic reconnection on disconnect
*
* Usage:
* 1. Upload this sketch to your ESP32
* 2. Turn on your BLE gamepad (or run the Server_Gamepad example on another ESP32)
* 3. The ESP32 will scan, connect, and display gamepad input in the serial monitor
*
* Note: This example uses "Just Works" pairing for automatic connection without
* PIN entry or confirmation. The bond is saved for future connections.
*
* Compatible with gamepads using the standard HID Report Descriptor format
*
* Created by lucasssvaz
*/
#include <BLEDevice.h>
#include <BLESecurity.h>
// HID Service UUID (standard UUID for HID over GATT)
static BLEUUID hidServiceUUID((uint16_t)0x1812);
// HID Report characteristic UUID (used for input/output reports)
static BLEUUID reportCharUUID((uint16_t)0x2A4D);
// HID Report Map characteristic UUID
static BLEUUID reportMapUUID((uint16_t)0x2A4B);
static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic *pInputReportCharacteristic = nullptr;
static BLEAdvertisedDevice *myDevice = nullptr;
static BLEClient *pClient = nullptr;
// Gamepad report structure (adjust based on your gamepad's report descriptor)
// This matches the Server_Gamepad example format
struct GamepadReport {
uint8_t reportId; // Report ID
int8_t x; // X axis (-127 to 127)
int8_t y; // Y axis (-127 to 127)
uint8_t buttons; // 8 buttons (bit 0-7)
} __attribute__((packed));
// Callback function to handle gamepad input notifications
static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
Serial.printf("Received %d bytes: ", length);
// Check if data length matches our expected gamepad report
if (length == sizeof(GamepadReport)) {
GamepadReport *report = (GamepadReport *)pData;
Serial.printf("ID=%d, X=%4d, Y=%4d, Buttons=0x%02X [", report->reportId, report->x, report->y, report->buttons);
// Display which buttons are pressed
for (int i = 0; i < 8; i++) {
if (report->buttons & (1 << i)) {
Serial.printf("%d ", i + 1);
}
}
Serial.println("]");
} else {
// Unknown format, just display hex dump
Serial.print("Raw data: ");
for (size_t i = 0; i < length; i++) {
Serial.printf("%02X ", pData[i]);
}
Serial.println();
}
}
// Client callbacks to handle connection events
class MyClientCallback : public BLEClientCallbacks {
void onConnect(BLEClient *pclient) {
Serial.println("Connected to gamepad");
}
void onDisconnect(BLEClient *pclient) {
connected = false;
Serial.println("Disconnected from gamepad");
}
};
// Function to connect to the gamepad
bool connectToServer() {
Serial.print("Connecting to gamepad at ");
Serial.println(myDevice->getAddress().toString().c_str());
pClient = BLEDevice::createClient();
Serial.println(" - Created client");
pClient->setClientCallbacks(new MyClientCallback());
// Connect to the gamepad
pClient->connect(myDevice);
Serial.println(" - Connected to server");
pClient->setMTU(185); // Set MTU for larger data transfers
// Obtain a reference to the HID service
BLERemoteService *pRemoteService = pClient->getService(hidServiceUUID);
if (pRemoteService == nullptr) {
Serial.println("Failed to find HID service");
pClient->disconnect();
return false;
}
Serial.println(" - Found HID service");
// Get all characteristics to find input reports
std::map<std::string, BLERemoteCharacteristic *> *pCharMap = pRemoteService->getCharacteristics();
// Look for input report characteristics (UUID 0x2A4D)
for (auto const &entry : *pCharMap) {
BLERemoteCharacteristic *pChar = entry.second;
if (pChar->getUUID().equals(reportCharUUID)) {
// Check if this characteristic has notify property (input report)
if (pChar->canNotify()) {
Serial.printf(" - Found input report characteristic (handle: 0x%04X)\n", pChar->getHandle());
// Try to read Report Reference Descriptor to identify report type and ID
BLERemoteDescriptor *pReportRefDesc = pChar->getDescriptor(BLEUUID((uint16_t)0x2908));
if (pReportRefDesc != nullptr) {
String refValue = pReportRefDesc->readValue();
if (refValue.length() >= 2) {
uint8_t reportId = refValue[0];
uint8_t reportType = refValue[1];
Serial.printf(" Report ID: %d, Type: %d (1=Input, 2=Output, 3=Feature)\n", reportId, reportType);
// We want input reports (type = 1)
if (reportType == 1) {
pInputReportCharacteristic = pChar;
}
}
} else {
// No report reference descriptor, assume it's an input report
pInputReportCharacteristic = pChar;
}
}
}
}
if (pInputReportCharacteristic == nullptr) {
Serial.println("Failed to find input report characteristic");
pClient->disconnect();
return false;
}
// Subscribe to input report notifications
Serial.println(" - Subscribing to input report notifications");
pInputReportCharacteristic->registerForNotify(notifyCallback);
connected = true;
Serial.println("Successfully connected and subscribed to gamepad!");
// Note: Security/encryption will be automatically handled by the BLE stack
// when the HID device requires it (using "Just Works" pairing).
return true;
}
// Scan callback to detect gamepad devices
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
Serial.print("BLE Device found: ");
Serial.print(advertisedDevice.toString().c_str());
// Check if device advertises HID service
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(hidServiceUUID)) {
Serial.print(" - HID Device!");
// Check if it's a gamepad by appearance (0x03C4 = HID Gamepad)
if (advertisedDevice.haveAppearance()) {
uint16_t appearance = advertisedDevice.getAppearance();
Serial.printf(" (Appearance: 0x%04X)", appearance);
if (appearance == 0x03C4) {
Serial.print(" - GAMEPAD!");
}
}
Serial.println();
// Stop scanning and connect
BLEDevice::getScan()->stop();
myDevice = new BLEAdvertisedDevice(advertisedDevice);
doConnect = true;
doScan = true;
} else {
Serial.println();
}
}
};
void setup() {
Serial.begin(115200);
Serial.println("\n=== BLE HID Gamepad Client ===");
Serial.println("Scanning for BLE HID gamepads...\n");
BLEDevice::init("ESP32-Gamepad-Client");
// Configure BLE Security for pairing with HID devices
BLESecurity *pSecurity = new BLESecurity();
// Set security capabilities and authentication mode
// HID devices typically use "Just Works" pairing (no MITM) with bonding
// Set IO capability to NONE for "Just Works" pairing
pSecurity->setCapability(ESP_IO_CAP_NONE);
// Bonding, no MITM, secure connections (for "Just Works" pairing)
pSecurity->setAuthenticationMode(true, false, true);
// Set security callbacks (using default implementation)
BLEDevice::setSecurityCallbacks(new BLESecurityCallbacks());
Serial.println("Security configured: Bonding + Secure Connections\n");
// Create scanner and set callbacks
BLEScan *pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setInterval(1349);
pBLEScan->setWindow(449);
pBLEScan->setActiveScan(true);
pBLEScan->start(5, false);
}
void loop() {
// Connect to gamepad if found
if (doConnect == true) {
if (connectToServer()) {
Serial.println("\n*** Ready to receive gamepad input ***\n");
} else {
Serial.println("Failed to connect to gamepad");
}
doConnect = false;
}
// Restart scanning if disconnected
if (!connected && doScan) {
Serial.println("\nScanning for gamepads...");
BLEDevice::getScan()->start(5, false);
delay(1000);
}
delay(100);
}
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
@@ -0,0 +1,280 @@
/*
* BLE Client and Server Coexistence Example
*
* This example demonstrates how to run both BLE client and server
* functionality on the same ESP32 device simultaneously.
*
* The device will:
* - Act as a BLE server, advertising a service with a characteristic
* - Act as a BLE client, scanning for other BLE servers
* - Connect to found servers and interact with their services
* - Handle both incoming and outgoing connections
*
* You can test this example by uploading it to two ESP32 boards.
*
* Author: lucasssvaz
* Based on Arduino BLE examples
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLEClient.h>
#include <BLEScan.h>
// Server-side definitions
#define SERVER_SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define SERVER_CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
// Client-side definitions (looking for the same service)
static BLEUUID clientServiceUUID(SERVER_SERVICE_UUID);
static BLEUUID clientCharUUID(SERVER_CHARACTERISTIC_UUID);
// Server objects
BLEServer *pServer = nullptr;
BLECharacteristic *pServerCharacteristic = nullptr;
// Client objects
static boolean doConnect = false;
static boolean clientConnected = false;
static BLERemoteCharacteristic *pRemoteCharacteristic;
static BLEAdvertisedDevice *targetDevice;
static BLEClient *pClient = nullptr;
BLEScan *pBLEScan = nullptr;
// Server callbacks
class ServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer *pServer) {
Serial.println("Server: Client connected");
}
void onDisconnect(BLEServer *pServer) {
Serial.println("Server: Client disconnected");
// Restart advertising
BLEDevice::startAdvertising();
}
};
// Characteristic callbacks for server
class CharacteristicCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
String value = pCharacteristic->getValue();
Serial.print("Server: Characteristic written, value: ");
Serial.println(value.c_str());
}
void onRead(BLECharacteristic *pCharacteristic) {
Serial.println("Server: Characteristic read");
}
};
// Client callbacks
class ClientCallbacks : public BLEClientCallbacks {
void onConnect(BLEClient *pClient) {
Serial.println("Client: Connected to server");
clientConnected = true;
}
void onDisconnect(BLEClient *pClient) {
Serial.println("Client: Disconnected from server");
clientConnected = false;
}
};
// Client notification callback
static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
Serial.print("Client: Notify callback for characteristic ");
Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
Serial.print(" of data length ");
Serial.println(length);
Serial.print("Client: Data: ");
Serial.write(pData, length);
Serial.println();
}
// Scan callbacks
class AdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
Serial.print("Client: Found device: ");
Serial.println(advertisedDevice.toString().c_str());
// Check if this device has our target service
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(clientServiceUUID) && !clientConnected) {
Serial.println("Client: Found target service, attempting connection...");
BLEDevice::getScan()->stop();
targetDevice = new BLEAdvertisedDevice(advertisedDevice);
doConnect = true;
}
}
};
bool connectToServer() {
Serial.print("Client: Forming connection to ");
Serial.println(targetDevice->getAddress().toString().c_str());
// Create client if it doesn't exist, otherwise reuse existing one
if (pClient == nullptr) {
pClient = BLEDevice::createClient();
pClient->setClientCallbacks(new ClientCallbacks());
Serial.println("Client: Created new client");
} else {
Serial.println("Client: Reusing existing client");
}
if (!pClient->connect(targetDevice)) {
Serial.println("Client: Failed to connect");
return false;
}
Serial.println("Client: Connected to server");
pClient->setMTU(517); // Request maximum MTU
// Get the service
BLERemoteService *pRemoteService = pClient->getService(clientServiceUUID);
if (pRemoteService == nullptr) {
Serial.print("Client: Failed to find service UUID: ");
Serial.println(clientServiceUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println("Client: Found service");
// Get the characteristic
pRemoteCharacteristic = pRemoteService->getCharacteristic(clientCharUUID);
if (pRemoteCharacteristic == nullptr) {
Serial.print("Client: Failed to find characteristic UUID: ");
Serial.println(clientCharUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println("Client: Found characteristic");
// Read the initial value
if (pRemoteCharacteristic->canRead()) {
String value = pRemoteCharacteristic->readValue();
Serial.print("Client: Initial characteristic value: ");
Serial.println(value.c_str());
}
// Register for notifications if available
if (pRemoteCharacteristic->canNotify()) {
pRemoteCharacteristic->registerForNotify(notifyCallback);
Serial.println("Client: Registered for notifications");
}
return true;
}
void setupServer() {
Serial.println("Setting up BLE Server...");
// Create server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new ServerCallbacks());
// Create service
BLEService *pService = pServer->createService(SERVER_SERVICE_UUID);
// Create characteristic
pServerCharacteristic = pService->createCharacteristic(
SERVER_CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY
);
pServerCharacteristic->setCallbacks(new CharacteristicCallbacks());
pServerCharacteristic->setValue("Hello from Coexistence Server");
// Start service
pService->start();
// Start advertising
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVER_SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06);
pAdvertising->setMinPreferred(0x12);
BLEDevice::startAdvertising();
Serial.println("Server: Advertising started");
}
void setupClient() {
Serial.println("Setting up BLE Client...");
// Create scanner
pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks());
pBLEScan->setActiveScan(true);
pBLEScan->setInterval(100);
pBLEScan->setWindow(99);
Serial.println("Client: Scanner configured");
}
void setup() {
Serial.begin(115200);
Serial.println("Starting BLE Client-Server Coexistence Example...");
// Initialize BLE device with a name
BLEDevice::init("ESP32-Coexistence");
// Setup both server and client
setupServer();
setupClient();
// Start initial scan
pBLEScan->start(10, false); // Scan for 10 seconds, don't repeat
Serial.println("Setup complete. Device is advertising as server and scanning as client.");
}
void loop() {
static unsigned long lastServerUpdate = 0;
static unsigned long lastClientWrite = 0;
static unsigned long lastScanStart = 0;
unsigned long currentTime = millis();
// Handle client connection attempts
if (doConnect && !clientConnected) {
if (connectToServer()) {
Serial.println("Client: Successfully connected to remote server");
} else {
Serial.println("Client: Failed to connect, will retry scanning");
// Restart scanning after failed connection
pBLEScan->start(10, false);
}
doConnect = false;
}
// Update server characteristic periodically
if (currentTime - lastServerUpdate > 5000) { // Every 5 seconds
String value = "Server time: " + String(millis() / 1000);
pServerCharacteristic->setValue(value.c_str());
pServerCharacteristic->notify(); // Notify connected clients
Serial.print("Server: Updated characteristic to: ");
Serial.println(value);
lastServerUpdate = currentTime;
}
// Write to remote characteristic if connected as client
if (clientConnected && pRemoteCharacteristic && currentTime - lastClientWrite > 3000) {
if (pRemoteCharacteristic->canWrite()) {
String clientValue = "Client msg: " + String(millis() / 1000);
pRemoteCharacteristic->writeValue(clientValue.c_str(), clientValue.length());
Serial.print("Client: Wrote to remote characteristic: ");
Serial.println(clientValue);
lastClientWrite = currentTime;
}
}
// Restart scanning periodically if not connected
if (!clientConnected && currentTime - lastScanStart > 15000) { // Every 15 seconds
Serial.println("Client: Restarting scan...");
pBLEScan->start(10, false);
lastScanStart = currentTime;
}
delay(100);
}
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
@@ -0,0 +1,276 @@
/**
* A BLE client example that connects to multiple BLE servers simultaneously.
*
* This example demonstrates how to:
* - Scan for multiple BLE servers
* - Connect to multiple servers at the same time
* - Interact with characteristics on different servers
* - Handle disconnections and reconnections
*
* The example looks for servers advertising the service UUID: 4fafc201-1fb5-459e-8fcc-c5c9c331914b
* and connects to up to MAX_SERVERS servers.
*
* Created by lucasssvaz
* Based on the original Client example by Neil Kolban and chegewara
*/
#include "BLEDevice.h"
// The remote service we wish to connect to.
static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
// The characteristic of the remote service we are interested in.
static BLEUUID charUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8");
// Maximum number of servers to connect to
#define MAX_SERVERS 3
// Structure to hold information about each connected server
struct ServerConnection {
BLEClient *pClient;
BLEAdvertisedDevice *pDevice;
BLERemoteCharacteristic *pRemoteCharacteristic;
bool connected;
bool doConnect;
String name;
};
// Array to manage multiple server connections
ServerConnection servers[MAX_SERVERS];
int connectedServers = 0;
static bool doScan = true;
// Callback function to handle notifications from any server
static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
// Find which server this notification came from
for (int i = 0; i < MAX_SERVERS; i++) {
if (servers[i].connected && servers[i].pRemoteCharacteristic == pBLERemoteCharacteristic) {
Serial.print("Notify from server ");
Serial.print(servers[i].name);
Serial.print(" - Characteristic: ");
Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
Serial.print(" | Length: ");
Serial.print(length);
Serial.print(" | Data: ");
Serial.write(pData, length);
Serial.println();
break;
}
}
}
// Client callback class to handle connect/disconnect events
class MyClientCallback : public BLEClientCallbacks {
int serverIndex;
public:
MyClientCallback(int index) : serverIndex(index) {}
void onConnect(BLEClient *pclient) {
Serial.print("Connected to server ");
Serial.println(servers[serverIndex].name);
}
void onDisconnect(BLEClient *pclient) {
servers[serverIndex].connected = false;
connectedServers--;
Serial.print("Disconnected from server ");
Serial.print(servers[serverIndex].name);
Serial.print(" | Total connected: ");
Serial.println(connectedServers);
doScan = true; // Resume scanning to find replacement servers
}
};
// Function to connect to a specific server
bool connectToServer(int serverIndex) {
Serial.print("Connecting to server ");
Serial.print(serverIndex);
Serial.print(" at address: ");
Serial.println(servers[serverIndex].pDevice->getAddress().toString().c_str());
servers[serverIndex].pClient = BLEDevice::createClient();
Serial.println(" - Created client");
// Set the callback for this specific server connection
servers[serverIndex].pClient->setClientCallbacks(new MyClientCallback(serverIndex));
// Connect to the remote BLE Server
servers[serverIndex].pClient->connect(servers[serverIndex].pDevice);
Serial.println(" - Connected to server");
servers[serverIndex].pClient->setMTU(517); // Request maximum MTU from server
// Obtain a reference to the service we are after in the remote BLE server
BLERemoteService *pRemoteService = servers[serverIndex].pClient->getService(serviceUUID);
if (pRemoteService == nullptr) {
Serial.print("Failed to find service UUID: ");
Serial.println(serviceUUID.toString().c_str());
servers[serverIndex].pClient->disconnect();
return false;
}
Serial.println(" - Found service");
// Obtain a reference to the characteristic in the service
servers[serverIndex].pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
if (servers[serverIndex].pRemoteCharacteristic == nullptr) {
Serial.print("Failed to find characteristic UUID: ");
Serial.println(charUUID.toString().c_str());
servers[serverIndex].pClient->disconnect();
return false;
}
Serial.println(" - Found characteristic");
// Read the value of the characteristic
if (servers[serverIndex].pRemoteCharacteristic->canRead()) {
String value = servers[serverIndex].pRemoteCharacteristic->readValue();
Serial.print("Initial characteristic value: ");
Serial.println(value.c_str());
}
// Register for notifications if available
if (servers[serverIndex].pRemoteCharacteristic->canNotify()) {
servers[serverIndex].pRemoteCharacteristic->registerForNotify(notifyCallback);
Serial.println(" - Registered for notifications");
}
servers[serverIndex].connected = true;
connectedServers++;
Serial.print("Successfully connected! Total servers connected: ");
Serial.println(connectedServers);
return true;
}
// Scan callback class to find BLE servers
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
Serial.print("BLE Device found: ");
Serial.println(advertisedDevice.toString().c_str());
// Check if this device has the service we're looking for
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {
Serial.println(" -> This device has our service!");
// Check if we already know about this device
String deviceAddress = advertisedDevice.getAddress().toString().c_str();
bool alreadyKnown = false;
for (int i = 0; i < MAX_SERVERS; i++) {
if (servers[i].pDevice != nullptr) {
if (servers[i].pDevice->getAddress().toString() == deviceAddress) {
alreadyKnown = true;
break;
}
}
}
if (alreadyKnown) {
Serial.println(" -> Already connected or connecting to this device");
return;
}
// Find an empty slot for this server
for (int i = 0; i < MAX_SERVERS; i++) {
if (servers[i].pDevice == nullptr || (!servers[i].connected && !servers[i].doConnect)) {
servers[i].pDevice = new BLEAdvertisedDevice(advertisedDevice);
servers[i].doConnect = true;
servers[i].name = "Server_" + String(i);
Serial.print(" -> Assigned to slot ");
Serial.println(i);
// If we've found enough servers, stop scanning
int pendingConnections = 0;
for (int j = 0; j < MAX_SERVERS; j++) {
if (servers[j].connected || servers[j].doConnect) {
pendingConnections++;
}
}
if (pendingConnections >= MAX_SERVERS) {
Serial.println("Found enough servers, stopping scan");
BLEDevice::getScan()->stop();
doScan = false;
}
break;
}
}
}
}
};
void setup() {
Serial.begin(115200);
Serial.println("=================================");
Serial.println("BLE Multi-Client Example");
Serial.println("=================================");
Serial.print("Max servers to connect: ");
Serial.println(MAX_SERVERS);
Serial.println();
// Initialize all server connections
for (int i = 0; i < MAX_SERVERS; i++) {
servers[i].pClient = nullptr;
servers[i].pDevice = nullptr;
servers[i].pRemoteCharacteristic = nullptr;
servers[i].connected = false;
servers[i].doConnect = false;
servers[i].name = "";
}
// Initialize BLE
BLEDevice::init("ESP32_MultiClient");
// Set up BLE scanner
BLEScan *pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setInterval(1349);
pBLEScan->setWindow(449);
pBLEScan->setActiveScan(true);
pBLEScan->start(5, false);
Serial.println("Scanning for BLE servers...");
}
void loop() {
// Process any pending connections
for (int i = 0; i < MAX_SERVERS; i++) {
if (servers[i].doConnect) {
if (connectToServer(i)) {
Serial.println("Connection successful");
} else {
Serial.println("Connection failed");
// Clear this slot so we can try another server
delete servers[i].pDevice;
servers[i].pDevice = nullptr;
}
servers[i].doConnect = false;
}
}
// If we're connected to servers, send data to each one
if (connectedServers > 0) {
for (int i = 0; i < MAX_SERVERS; i++) {
if (servers[i].connected && servers[i].pRemoteCharacteristic != nullptr) {
// Create a unique message for each server
String newValue = servers[i].name + " | Time: " + String(millis() / 1000);
Serial.print("Sending to ");
Serial.print(servers[i].name);
Serial.print(": ");
Serial.println(newValue);
// Write the value to the characteristic
servers[i].pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());
}
}
} else {
Serial.println("No servers connected");
}
// Resume scanning if we have room for more connections
if (doScan && connectedServers < MAX_SERVERS) {
Serial.println("Resuming scan for more servers...");
BLEDevice::getScan()->start(5, false);
doScan = false;
delay(5000); // Wait for scan to complete
}
delay(2000); // Delay between loop iterations
}
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
@@ -0,0 +1,327 @@
/*
Secure client with static passkey and IRK retrieval
This example demonstrates how to create a secure BLE client that connects to
a secure BLE server using a static passkey without prompting the user.
The client will automatically use the same passkey (123456) as the server.
After successful bonding, the example demonstrates how to retrieve the
server's Identity Resolving Key (IRK) in multiple formats:
- Comma-separated hex format: 0x1A,0x1B,0x1C,...
- Base64 encoded (for Home Assistant Private BLE Device service)
- Reverse hex order (for Home Assistant ESPresense)
This client is designed to work with the Server_secure_static_passkey example.
Note that ESP32 uses Bluedroid by default and the other SoCs use NimBLE.
Bluedroid initiates security on-connect, while NimBLE initiates security on-demand.
This means that in NimBLE you can read the insecure characteristic without entering
the passkey. This is not possible in Bluedroid.
IMPORTANT:
- MITM (Man-In-The-Middle protection) must be enabled for password prompts to work.
- Bonding must be enabled to store and retrieve the IRK.
- The server must distribute its Identity Key during pairing.
Based on examples from Neil Kolban and h2zero.
Created by lucasssvaz.
*/
#include "BLEDevice.h"
#include "BLESecurity.h"
#include "nvs_flash.h"
// The remote service we wish to connect to.
static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
// The characteristics of the remote service we are interested in.
static BLEUUID insecureCharUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8");
static BLEUUID secureCharUUID("ff1d2614-e2d6-4c87-9154-6625d39ca7f8");
// This must match the server's passkey
#define CLIENT_PIN 123456
static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLEClient *pClient = nullptr;
static BLERemoteCharacteristic *pRemoteInsecureCharacteristic;
static BLERemoteCharacteristic *pRemoteSecureCharacteristic;
static BLEAdvertisedDevice *myDevice;
// Print an IRK buffer as hex with leading zeros and ':' separator
static void printIrkBinary(uint8_t *irk) {
for (int i = 0; i < 16; i++) {
if (irk[i] < 0x10) {
Serial.print("0");
}
Serial.print(irk[i], HEX);
if (i < 15) {
Serial.print(":");
}
}
}
static void get_peer_irk(BLEAddress peerAddr) {
Serial.println("\n=== Retrieving peer IRK (Server) ===\n");
uint8_t irk[16];
// Get IRK in binary format
if (BLEDevice::getPeerIRK(peerAddr, irk)) {
Serial.println("Successfully retrieved peer IRK in binary format:");
printIrkBinary(irk);
Serial.println("\n");
}
// Get IRK in different string formats
String irkString = BLEDevice::getPeerIRKString(peerAddr);
String irkBase64 = BLEDevice::getPeerIRKBase64(peerAddr);
String irkReverse = BLEDevice::getPeerIRKReverse(peerAddr);
if (irkString.length() > 0) {
Serial.println("Successfully retrieved peer IRK in multiple formats:\n");
Serial.print("IRK (comma-separated hex): ");
Serial.println(irkString);
Serial.print("IRK (Base64 for Home Assistant Private BLE Device): ");
Serial.println(irkBase64);
Serial.print("IRK (reverse hex for Home Assistant ESPresense): ");
Serial.println(irkReverse);
Serial.println();
} else {
Serial.println("!!! Failed to retrieve peer IRK !!!");
Serial.println("This is expected if bonding is disabled or the peer doesn't distribute its Identity Key.");
Serial.println("To enable bonding, change setAuthenticationMode to: pSecurity->setAuthenticationMode(true, true, true);\n");
}
Serial.println("=======================================\n");
}
// Callback function to handle notifications
static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
Serial.print("Notify callback for characteristic ");
Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
Serial.print(" of data length ");
Serial.println(length);
Serial.print("data: ");
Serial.write(pData, length);
Serial.println();
}
class MyClientCallback : public BLEClientCallbacks {
void onConnect(BLEClient *pclient) {
Serial.println("Connected to secure server");
}
void onDisconnect(BLEClient *pclient) {
connected = false;
Serial.println("Disconnected from server");
}
};
// Security callbacks to print IRKs once authentication completes
class MySecurityCallbacks : public BLESecurityCallbacks {
#if defined(CONFIG_BLUEDROID_ENABLED)
void onAuthenticationComplete(esp_ble_auth_cmpl_t desc) override {
// Print the IRK received by the peer
BLEAddress peerAddr(desc.bd_addr);
get_peer_irk(peerAddr);
}
#endif
#if defined(CONFIG_NIMBLE_ENABLED)
void onAuthenticationComplete(ble_gap_conn_desc *desc) override {
// Print the IRK received by the peer
BLEAddress peerAddr(desc->peer_id_addr.val, desc->peer_id_addr.type);
get_peer_irk(peerAddr);
}
#endif
};
bool connectToServer() {
Serial.print("Forming a secure connection to ");
Serial.println(myDevice->getAddress().toString().c_str());
pClient = BLEDevice::createClient();
Serial.println(" - Created client");
pClient->setClientCallbacks(new MyClientCallback());
// Connect to the remote BLE Server.
pClient->connect(myDevice);
Serial.println(" - Connected to server");
// Set MTU to maximum for better performance
pClient->setMTU(517);
// Obtain a reference to the service we are after in the remote BLE server.
BLERemoteService *pRemoteService = pClient->getService(serviceUUID);
if (pRemoteService == nullptr) {
Serial.print("Failed to find our service UUID: ");
Serial.println(serviceUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println(" - Found our service");
// Obtain a reference to the insecure characteristic
pRemoteInsecureCharacteristic = pRemoteService->getCharacteristic(insecureCharUUID);
if (pRemoteInsecureCharacteristic == nullptr) {
Serial.print("Failed to find insecure characteristic UUID: ");
Serial.println(insecureCharUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println(" - Found insecure characteristic");
// Obtain a reference to the secure characteristic
pRemoteSecureCharacteristic = pRemoteService->getCharacteristic(secureCharUUID);
if (pRemoteSecureCharacteristic == nullptr) {
Serial.print("Failed to find secure characteristic UUID: ");
Serial.println(secureCharUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println(" - Found secure characteristic");
// Read the value of the insecure characteristic (should work without authentication)
if (pRemoteInsecureCharacteristic->canRead()) {
String value = pRemoteInsecureCharacteristic->readValue();
Serial.print("Insecure characteristic value: ");
Serial.println(value.c_str());
}
// For Bluedroid, we need to set the authentication request type for the secure characteristic
// This is not needed for NimBLE and will be ignored.
pRemoteSecureCharacteristic->setAuth(ESP_GATT_AUTH_REQ_MITM);
// Try to read the secure characteristic (this will trigger security negotiation in NimBLE)
if (pRemoteSecureCharacteristic->canRead()) {
Serial.println("Attempting to read secure characteristic...");
String value = pRemoteSecureCharacteristic->readValue();
Serial.print("Secure characteristic value: ");
Serial.println(value.c_str());
}
// Register for notifications on both characteristics if they support it
if (pRemoteInsecureCharacteristic->canNotify()) {
pRemoteInsecureCharacteristic->registerForNotify(notifyCallback);
Serial.println(" - Registered for insecure characteristic notifications");
}
if (pRemoteSecureCharacteristic->canNotify()) {
pRemoteSecureCharacteristic->registerForNotify(notifyCallback);
Serial.println(" - Registered for secure characteristic notifications");
}
connected = true;
return true;
}
/**
* Scan for BLE servers and find the first one that advertises the service we are looking for.
*/
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
/**
* Called for each advertising BLE server.
*/
void onResult(BLEAdvertisedDevice advertisedDevice) {
Serial.print("BLE Advertised Device found: ");
Serial.println(advertisedDevice.toString().c_str());
// We have found a device, let us now see if it contains the service we are looking for.
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {
Serial.println("Found our secure server!");
BLEDevice::getScan()->stop();
myDevice = new BLEAdvertisedDevice(advertisedDevice);
doConnect = true;
doScan = true;
}
}
};
void setup() {
Serial.begin(115200);
Serial.println("Starting Secure BLE Client application...");
// Clear NVS to remove any cached pairing information
// This ensures fresh authentication for testing
Serial.println("Clearing NVS pairing data...");
nvs_flash_erase();
nvs_flash_init();
BLEDevice::init("Secure BLE Client");
// Set up security with the same passkey as the server
BLESecurity *pSecurity = new BLESecurity();
// Set security parameters
// Default parameters:
// - IO capability is set to NONE
// - Initiator and responder key distribution flags are set to both encryption and identity keys.
// - Passkey is set to BLE_SM_DEFAULT_PASSKEY (123456). It will warn if you don't change it.
// - Key size is set to 16 bytes
// Set the same static passkey as the server
// The first argument defines if the passkey is static or random.
// The second argument is the passkey (ignored when using a random passkey).
pSecurity->setPassKey(true, CLIENT_PIN);
// Set authentication mode to match server requirements
// Enable bonding, MITM (for password prompts), and secure connection for this example
// Bonding is required to store and retrieve the IRK
pSecurity->setAuthenticationMode(true, true, true);
// Set IO capability to KeyboardOnly
// We need the proper IO capability for MITM authentication even
// if the passkey is static and won't be entered by the user
// See https://www.bluetooth.com/blog/bluetooth-pairing-part-2-key-generation-methods/
pSecurity->setCapability(ESP_IO_CAP_IN);
// Set callbacks to handle authentication completion and print IRKs
BLEDevice::setSecurityCallbacks(new MySecurityCallbacks());
// Retrieve a Scanner and set the callback we want to use to be informed when we
// have detected a new device. Specify that we want active scanning and start the
// scan to run for 5 seconds.
BLEScan *pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setInterval(1349);
pBLEScan->setWindow(449);
pBLEScan->setActiveScan(true);
pBLEScan->start(5, false);
}
void loop() {
// If the flag "doConnect" is true then we have scanned for and found the desired
// BLE Server with which we wish to connect. Now we connect to it.
if (doConnect == true) {
if (connectToServer()) {
Serial.println("We are now connected to the secure BLE Server.");
} else {
Serial.println("We have failed to connect to the server; there is nothing more we will do.");
}
doConnect = false;
}
// If we are connected to a peer BLE Server, demonstrate secure communication
if (connected) {
// Write to the insecure characteristic
String insecureValue = "Client time: " + String(millis() / 1000);
if (pRemoteInsecureCharacteristic->canWrite()) {
pRemoteInsecureCharacteristic->writeValue(insecureValue.c_str(), insecureValue.length());
Serial.println("Wrote to insecure characteristic: " + insecureValue);
}
// Write to the secure characteristic
String secureValue = "Secure client time: " + String(millis() / 1000);
if (pRemoteSecureCharacteristic->canWrite()) {
pRemoteSecureCharacteristic->writeValue(secureValue.c_str(), secureValue.length());
Serial.println("Wrote to secure characteristic: " + secureValue);
}
} else if (doScan) {
// Restart scanning if we're disconnected
BLEDevice::getScan()->start(0);
}
delay(2000); // Delay 2 seconds between loops
}
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
@@ -0,0 +1,92 @@
/*
EddystoneTLM beacon by BeeGee based on https://github.com/pcbreflux/espressif/blob/master/esp32/arduino/sketchbook/ESP32_Eddystone_TLM_deepsleep/ESP32_Eddystone_TLM_deepsleep.ino
EddystoneTLM frame specification https://github.com/google/eddystone/blob/master/eddystone-tlm/tlm-plain.md
*/
/*
Create a BLE server that will send periodic Eddystone URL frames.
The design of creating the BLE server is:
1. Create a BLE Server
2. Create advertising data
3. Start advertising.
4. wait
5. Stop advertising.
6. deep sleep
To read data advertised by this beacon use second ESP with example sketch BLE_Beacon_Scanner
*/
#include "sys/time.h"
#include <Arduino.h>
#include "BLEDevice.h"
#include "BLEUtils.h"
#include "BLEBeacon.h"
#include "BLEAdvertising.h"
#include "BLEEddystoneTLM.h"
#include "esp_sleep.h"
#define GPIO_DEEP_SLEEP_DURATION 10 // sleep x seconds and then wake up
#define BEACON_POWER ESP_PWR_LVL_N12
RTC_DATA_ATTR static time_t last; // remember last boot in RTC Memory
RTC_DATA_ATTR static uint32_t bootcount; // remember number of boots in RTC Memory
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
BLEAdvertising *pAdvertising;
struct timeval nowTimeStruct;
time_t lastTenth;
#define BEACON_UUID "8ec76ea3-6668-48da-9866-75be8bc86f4d" // UUID 1 128-Bit (may use linux tool uuidgen or random numbers via https://www.uuidgenerator.net/)
// Check
// https://github.com/google/eddystone/blob/master/eddystone-tlm/tlm-plain.md
// and http://www.hugi.scene.org/online/coding/hugi%2015%20-%20cmtadfix.htm
// for the temperature value. It is a 8.8 fixed-point notation
void setBeacon() {
BLEEddystoneTLM EddystoneTLM;
EddystoneTLM.setVolt((uint16_t)random(2800, 3700)); // 3300mV = 3.3V
EddystoneTLM.setTemp(random(-3000, 3000) / 100.0f); // 3000 = 30.00 ˚C
Serial.printf("Random Battery voltage is %d mV = 0x%04X\n", EddystoneTLM.getVolt(), EddystoneTLM.getVolt());
Serial.printf("Random temperature is %.2f°C\n", EddystoneTLM.getTemp());
Serial.printf("Converted to 8.8 format: 0x%04X\n", EddystoneTLM.getRawTemp());
BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
BLEAdvertisementData oScanResponseData = BLEAdvertisementData();
oScanResponseData.setServiceData(BLEUUID((uint16_t)0xFEAA), String(EddystoneTLM.getData().c_str(), EddystoneTLM.getData().length()));
oAdvertisementData.setName("ESP32 TLM Beacon");
pAdvertising->setAdvertisementData(oAdvertisementData);
pAdvertising->setScanResponseData(oScanResponseData);
}
void setup() {
Serial.begin(115200);
gettimeofday(&nowTimeStruct, NULL);
Serial.printf("Starting ESP32. Bootcount = %lu\n", bootcount++);
Serial.printf("Deep sleep (%llds since last reset, %llds since last boot)\n", nowTimeStruct.tv_sec, nowTimeStruct.tv_sec - last);
last = nowTimeStruct.tv_sec;
lastTenth = nowTimeStruct.tv_sec * 10; // Time since last reset as 0.1 second resolution counter
// Create the BLE Device
BLEDevice::init("TLMBeacon");
BLEDevice::setPower(BEACON_POWER);
pAdvertising = BLEDevice::getAdvertising();
setBeacon();
// Start advertising
pAdvertising->start();
Serial.println("Advertising started for 10s ...");
delay(10000);
pAdvertising->stop();
Serial.printf("Enter deep sleep for 10s\n");
esp_deep_sleep(1000000LL * GPIO_DEEP_SLEEP_DURATION);
}
void loop() {}
@@ -0,0 +1,14 @@
## Eddystone TLM beacon
EddystoneTLM beacon by BeeGee based on
[pcbreflux ESP32 Eddystone TLM deepsleep](https://github.com/pcbreflux/espressif/blob/master/esp32/arduino/sketchbook/ESP32_Eddystone_TLM_deepsleep/ESP32_Eddystone_TLM_deepsleep.ino)
[EddystoneTLM frame specification](https://github.com/google/eddystone/blob/master/eddystone-tlm/tlm-plain.md)
Create a BLE server that will send periodic Eddystone TLM frames.
The design of creating the BLE server is:
1. Create a BLE Server
2. Create advertising data
3. Start advertising.
4. wait
5. Stop advertising.
6. deep sleep
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
@@ -0,0 +1,105 @@
/*
EddystoneURL beacon by BeeGee
EddystoneURL frame specification https://github.com/google/eddystone/blob/master/eddystone-url/README.md
Upgraded on: Feb 20, 2023
By: Tomas Pilny
*/
/*
Create a BLE server that will send periodic Eddystone URL frames.
The design of creating the BLE server is:
1. Create a BLE Server
2. Create advertising data
3. Start advertising.
4. wait
5. Stop advertising.
6. deep sleep
*/
#include "sys/time.h"
#include <Arduino.h>
#include "BLEDevice.h"
#include "BLEUtils.h"
#include "BLEBeacon.h"
#include "BLEAdvertising.h"
#include "BLEEddystoneURL.h"
#include "esp_sleep.h"
char unprintable[] = {0x01, 0xFF, 0xDE, 0xAD};
String URL[] = {
"http://www.espressif.com/", // prefix 0x00, suffix 0x00
"https://www.texas.gov", // prefix 0x01, suffix 0x0D
"http://en.mapy.cz", // prefix 0x02, no valid suffix
"https://arduino.cc", // prefix 0x03, no valid suffix
"google.com", // URL without specified prefix - the function will assume default prefix "http://www." = 0x00
"diginfo.tv", // URL without specified prefix - the function will assume default prefix "http://www." = 0x00
// "http://www.URLsAbove17BytesAreNotAllowed.com", // Too long URL - setSmartURL() will return 0 = ERR
// "", // Empty string - setSmartURL() will return 0 = ERR
// String(unprintable), // Unprintable characters / corrupted String - setSmartURL() will return 0 = ERR
};
#define GPIO_DEEP_SLEEP_DURATION 10 // sleep x seconds and then wake up
#define BEACON_POWER ESP_PWR_LVL_N12
RTC_DATA_ATTR static time_t last; // remember last boot in RTC Memory
RTC_DATA_ATTR static uint32_t bootcount; // remember number of boots in RTC Memory
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
BLEAdvertising *pAdvertising;
struct timeval now;
int setBeacon() {
BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
BLEAdvertisementData oScanResponseData = BLEAdvertisementData();
BLEEddystoneURL EddystoneURL;
EddystoneURL.setPower(BEACON_POWER); // This is only information about the power. The actual power is set by `BLEDevice::setPower(BEACON_POWER)`
if (EddystoneURL.setSmartURL(URL[bootcount % (sizeof(URL) / sizeof(URL[0]))])) {
String frame = EddystoneURL.getFrame();
String data(EddystoneURL.getFrame().c_str(), frame.length());
oAdvertisementData.addData(data);
oScanResponseData.setName("ESP32 URLBeacon");
pAdvertising->setAdvertisementData(oAdvertisementData);
pAdvertising->setScanResponseData(oScanResponseData);
Serial.printf("Advertise URL \"%s\"\n", URL[bootcount % (sizeof(URL) / sizeof(URL[0]))].c_str());
return 1; // OK
} else {
Serial.println("Smart URL set ERR");
return 0; // ERR
}
}
void setup() {
Serial.begin(115200);
gettimeofday(&now, NULL);
Serial.printf("Start ESP32 %lu\n", bootcount++);
Serial.printf("Deep sleep (%llds since last reset, %llds since last boot)\n", now.tv_sec, now.tv_sec - last);
last = now.tv_sec;
// Create the BLE Device
BLEDevice::init("URLBeacon");
BLEDevice::setPower(BEACON_POWER);
// Create the BLE Server
// BLEServer *pServer = BLEDevice::createServer(); // <-- no longer required to instantiate BLEServer, less flash and ram usage
pAdvertising = BLEDevice::getAdvertising();
if (setBeacon()) {
// Start advertising
pAdvertising->start();
Serial.println("Advertising started...");
delay(10000);
pAdvertising->stop();
}
Serial.println("Enter deep sleep");
bootcount++;
esp_deep_sleep(1000000LL * GPIO_DEEP_SLEEP_DURATION);
}
void loop() {}
@@ -0,0 +1,14 @@
## Eddystone URL beacon
EddystoneURL beacon by BeeGee based on
[pcbreflux ESP32 Eddystone URL deepsleep](https://github.com/pcbreflux/espressif/tree/master/esp32/arduino/sketchbook/ESP32_Eddystone_URL_deepsleep)
[EddystoneURL frame specification](https://github.com/google/eddystone/blob/master/eddystone-url/README.md)
Create a BLE server that will send periodic Eddystone URL frames.
The design of creating the BLE server is:
1. Create a BLE Server
2. Create advertising data
3. Start advertising.
4. wait
5. Stop advertising.
6. deep sleep
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
+112
View File
@@ -0,0 +1,112 @@
/*
Video: https://www.youtube.com/watch?v=oCMOYS71NIU
Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp
Ported to Arduino ESP32 by Evandro Copercini
updated by chegewara
Create a BLE server that, once we receive a connection, will send periodic notifications.
The service advertises itself as: 4fafc201-1fb5-459e-8fcc-c5c9c331914b
And has a characteristic of: beb5483e-36e1-4688-b7f5-ea07361b26a8
The design of creating the BLE server is:
1. Create a BLE Server
2. Create a BLE Service
3. Create a BLE Characteristic on the Service
4. Create a BLE Descriptor on the characteristic
5. Start the service.
6. Start advertising.
A connect handler associated with the server starts a background task that performs notification
every couple of seconds.
*/
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <BLE2901.h>
BLEServer *pServer = NULL;
BLECharacteristic *pCharacteristic = NULL;
BLE2901 *descriptor_2901 = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint32_t value = 0;
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer *pServer) {
deviceConnected = true;
};
void onDisconnect(BLEServer *pServer) {
deviceConnected = false;
}
};
void setup() {
Serial.begin(115200);
// Create the BLE Device
BLEDevice::init("ESP32");
// Create the BLE Server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Create the BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Create a BLE Characteristic
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_INDICATE
);
// Creates BLE Descriptor 0x2902: Client Characteristic Configuration Descriptor (CCCD)
// Descriptor 2902 is not required when using NimBLE as it is automatically added based on the characteristic properties
pCharacteristic->addDescriptor(new BLE2902());
// Adds also the Characteristic User Description - 0x2901 descriptor
descriptor_2901 = new BLE2901();
descriptor_2901->setDescription("My own description for this characteristic.");
descriptor_2901->setAccessPermissions(ESP_GATT_PERM_READ); // enforce read only - default is Read|Write
pCharacteristic->addDescriptor(descriptor_2901);
// Start the service
pService->start();
// Start advertising
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(false);
pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter
BLEDevice::startAdvertising();
Serial.println("Waiting a client connection to notify...");
}
void loop() {
// notify changed value
if (deviceConnected) {
pCharacteristic->setValue((uint8_t *)&value, 4);
pCharacteristic->notify();
value++;
delay(500);
}
// disconnecting
if (!deviceConnected && oldDeviceConnected) {
delay(500); // give the bluetooth stack the chance to get things ready
pServer->startAdvertising(); // restart advertising
Serial.println("start advertising");
oldDeviceConnected = deviceConnected;
}
// connecting
if (deviceConnected && !oldDeviceConnected) {
// do stuff here on connecting
oldDeviceConnected = deviceConnected;
}
}
+5
View File
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
+40
View File
@@ -0,0 +1,40 @@
/*
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 Evandro Copercini
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
int scanTime = 5; //In seconds
BLEScan *pBLEScan;
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str());
}
};
void setup() {
Serial.begin(115200);
Serial.println("Scanning...");
BLEDevice::init("");
pBLEScan = BLEDevice::getScan(); //create new scan
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster
pBLEScan->setInterval(100);
pBLEScan->setWindow(99); // less or equal setInterval value
}
void loop() {
// put your main code here, to run repeatedly:
BLEScanResults *foundDevices = pBLEScan->start(scanTime, false);
Serial.print("Devices found: ");
Serial.println(foundDevices->getCount());
Serial.println("Scan done!");
pBLEScan->clearResults(); // delete results fromBLEScan buffer to release memory
delay(2000);
}
+5
View File
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
+46
View File
@@ -0,0 +1,46 @@
/*
Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp
Ported to Arduino ESP32 by Evandro Copercini
updates by chegewara
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
void setup() {
Serial.begin(115200);
Serial.println("Starting BLE work!");
if (!BLEDevice::init("BLE Server Example")) {
Serial.println("BLE initialization failed!");
return;
}
BLEServer *pServer = BLEDevice::createServer();
BLEService *pService = pServer->createService(SERVICE_UUID);
BLECharacteristic *pCharacteristic =
pService->createCharacteristic(CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);
pCharacteristic->setValue("Hello World says Neil");
pService->start();
// BLEAdvertising *pAdvertising = pServer->getAdvertising(); // this still is working for backward compatibility
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue
pAdvertising->setMaxPreferred(0x12);
BLEDevice::startAdvertising();
Serial.println("Characteristic defined! Now you can read it in your phone!");
}
void loop() {
// put your main code here, to run repeatedly:
delay(2000);
}
+5
View File
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
@@ -0,0 +1,214 @@
/*
* BLE HID Gamepad Example
*
* This example demonstrates how to create a BLE HID Gamepad device using ESP32.
* The gamepad will appear as a standard HID game controller on Windows, macOS, Linux, Android, and iOS.
*
* Features:
* - 8 buttons (mapped to buttons 1-8)
* - 2 axes (X, Y for joystick movement)
* - Secure pairing with bonding
* - Battery level reporting
* - Automatic reconnection after power cycle
*
* Usage:
* 1. Upload this sketch to your ESP32
* 2. Pair with your device (Windows: Settings > Bluetooth & devices)
* 3. The gamepad will send test input (circular motion and button toggle)
* 4. Test in any game or with gamepad testing tools like https://hardwaretester.com/gamepad
*
* Note: This example uses "Just Works" pairing for automatic connection without
* PIN entry or confirmation, just like real gamepads. The bond is saved for future connections.
*
* Created by lucasssvaz
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLEHIDDevice.h>
#include <BLESecurity.h>
// HID Report Descriptor for a gamepad with 8 buttons and 2 axes (X, Y)
// This descriptor defines the gamepad as having:
// - 8 buttons (usage buttons 1-8)
// - 2 8-bit axes (X, Y) with range -127 to 127
const uint8_t hidReportDescriptor[] = {
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x05, // Usage (Gamepad)
0xA1, 0x01, // Collection (Application)
0x85, 0x01, // Report ID (1)
0x09, 0x01, // Usage (Pointer)
0xA1, 0x00, // Collection (Physical)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x15, 0x81, // Logical Minimum (-127)
0x25, 0x7F, // Logical Maximum (127)
0x75, 0x08, // Report Size (8)
0x95, 0x02, // Report Count (2)
0x81, 0x02, // Input (Data, Variable, Absolute)
0xC0, // End Collection
0x05, 0x09, // Usage Page (Button)
0x19, 0x01, // Usage Minimum (Button 1)
0x29, 0x08, // Usage Maximum (Button 8)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8)
0x81, 0x02, // Input (Data, Variable, Absolute)
0xC0 // End Collection
};
// Gamepad report structure (matches the HID descriptor)
struct GamepadReport {
uint8_t reportId; // Report ID (must be 1)
int8_t x; // X axis (-127 to 127)
int8_t y; // Y axis (-127 to 127)
uint8_t buttons; // 8 buttons (bit 0-7)
} __attribute__((packed));
BLEHIDDevice *hid;
BLECharacteristic *inputGamepad;
BLEServer *server;
bool deviceConnected = false;
// Server callbacks to track connection status
class ServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer *pServer) {
deviceConnected = true;
Serial.println("Client connected");
}
void onDisconnect(BLEServer *pServer) {
deviceConnected = false;
Serial.println("Client disconnected");
// Restart advertising so we can reconnect
BLEDevice::startAdvertising();
Serial.println("Advertising restarted");
}
};
// Security callbacks for "Just Works" pairing
class SecurityCallbacks : public BLESecurityCallbacks {
bool onSecurityRequest() {
return true; // Accept all pairing requests
}
#if defined(CONFIG_BLUEDROID_ENABLED)
void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl) {
if (auth_cmpl.success) {
Serial.println("Pairing successful!");
} else {
Serial.printf("Pairing failed, status: %d\n", auth_cmpl.fail_reason);
}
}
#elif defined(CONFIG_NIMBLE_ENABLED)
void onAuthenticationComplete(ble_gap_conn_desc *desc) {
if (desc->sec_state.encrypted) {
Serial.println("Pairing successful!");
} else {
Serial.println("Pairing failed");
}
}
#endif
// These are not used with "Just Works" pairing (ESP_IO_CAP_NONE)
uint32_t onPassKeyRequest() {
return 0;
}
void onPassKeyNotify(uint32_t pass_key) {}
bool onConfirmPIN(uint32_t pass_key) {
return true;
}
};
void setup() {
Serial.begin(115200);
Serial.println("Starting BLE HID Gamepad");
// Initialize BLE
BLEDevice::init("ESP32-Gamepad");
// Configure BLE Security for pairing and bonding
// Use "Just Works" pairing - no user interaction required (like a real gamepad)
BLESecurity *pSecurity = new BLESecurity();
// Set IO capability to NONE (no display, no keyboard - like a real gamepad)
pSecurity->setCapability(ESP_IO_CAP_NONE);
// Set authentication mode: bonding=true, MITM=false, secure connection=true
// This enables "Just Works" pairing with bonding
pSecurity->setAuthenticationMode(true, false, true);
// Set security callbacks
BLEDevice::setSecurityCallbacks(new SecurityCallbacks());
// Create BLE Server
server = BLEDevice::createServer();
server->setCallbacks(new ServerCallbacks());
// Create HID Device
hid = new BLEHIDDevice(server);
// Set HID device information
hid->manufacturer()->setValue("Espressif");
hid->pnp(0x02, 0x05ac, 0x820a, 0x0110); // Vendor ID, Product ID, Product Version
hid->hidInfo(0x00, 0x01); // HID version, country code
// Set Report Map (HID descriptor)
hid->reportMap((uint8_t *)hidReportDescriptor, sizeof(hidReportDescriptor));
// Create input report characteristic for gamepad
inputGamepad = hid->inputReport(1); // Report ID 1
// Set battery level to 100%
hid->setBatteryLevel(100);
// Start HID services
hid->startServices();
// Setup advertising
BLEAdvertising *advertising = BLEDevice::getAdvertising();
advertising->setAppearance(0x03C4); // HID Gamepad appearance
advertising->addServiceUUID(hid->hidService()->getUUID());
advertising->setScanResponse(true);
advertising->setMinPreferred(0x06); // Help with connection issues
advertising->setMaxPreferred(0x12);
BLEDevice::startAdvertising();
Serial.println("BLE HID Gamepad ready!");
Serial.println("Waiting for connection...");
}
void loop() {
if (deviceConnected) {
static uint32_t lastReportTime = 0;
static uint32_t counter = 0;
// Send a report every 50ms (20Hz)
if (millis() - lastReportTime >= 50) {
lastReportTime = millis();
counter++;
GamepadReport report;
report.reportId = 1;
// Simulate some movement (sine wave pattern)
report.x = (int8_t)(127 * sin(counter * 0.1)); // X axis: -127 to 127
report.y = (int8_t)(127 * cos(counter * 0.1)); // Y axis: -127 to 127
report.buttons = (counter % 40 < 20) ? 0x01 : 0x00; // Toggle first button every second
// Send the report
inputGamepad->setValue((uint8_t *)&report, sizeof(report));
inputGamepad->notify();
// Print status every 2 seconds
if (counter % 40 == 0) {
Serial.printf("Report #%lu: X=%d, Y=%d, Buttons=0x%02X\n", counter, report.x, report.y, report.buttons);
}
}
}
delay(10);
}
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
@@ -0,0 +1,123 @@
/*
Video: https://www.youtube.com/watch?v=oCMOYS71NIU
Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp
Ported to Arduino ESP32 by Evandro Copercini
updated by chegewara
Create a BLE server that, once we receive a connection, will send periodic notifications.
The server will continue advertising for more connections after the first one and will notify
the value of a counter to all connected clients.
The service advertises itself as: 4fafc201-1fb5-459e-8fcc-c5c9c331914b
And has a characteristic of: beb5483e-36e1-4688-b7f5-ea07361b26a8
The design of creating the BLE server is:
1. Create a BLE Server
2. Create a BLE Service
3. Create a BLE Characteristic on the Service
4. Create a BLE Descriptor on the characteristic
5. Start the service.
6. Start advertising.
A connect handler associated with the server starts a background task that performs notification
every couple of seconds.
*/
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
BLEServer *pServer = NULL;
BLECharacteristic *pCharacteristic = NULL;
int connectedClients = 0;
bool deviceConnected = false;
uint32_t value = 0;
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer *pServer) {
connectedClients++;
Serial.print("Client connected. Total clients: ");
Serial.println(connectedClients);
// Continue advertising for more connections
BLEDevice::startAdvertising();
};
void onDisconnect(BLEServer *pServer) {
connectedClients--;
Serial.print("Client disconnected. Total clients: ");
Serial.println(connectedClients);
}
};
void setup() {
Serial.begin(115200);
// Create the BLE Device
BLEDevice::init("ESP32");
// Create the BLE Server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Create the BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Create a BLE Characteristic
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_INDICATE
);
// Descriptor 2902 is not required when using NimBLE as it is automatically added based on the characteristic properties
pCharacteristic->addDescriptor(new BLE2902());
// Start the service
pService->start();
// Start advertising
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(false);
pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter
BLEDevice::startAdvertising();
Serial.println("Waiting for client connections to notify...");
}
void loop() {
// Notify changed value to all connected clients
if (connectedClients > 0) {
Serial.print("Notifying value: ");
Serial.print(value);
Serial.print(" to ");
Serial.print(connectedClients);
Serial.println(" client(s)");
pCharacteristic->setValue((uint8_t *)&value, 4);
pCharacteristic->notify();
value++;
// Bluetooth stack will go into congestion, if too many packets are sent.
// In 6 hours of testing, I was able to go as low as 3ms.
// When using core debug level "debug" or "verbose", the delay can be increased in
// order to reduce the number of debug messages in the serial monitor.
delay(100);
}
// Disconnecting - restart advertising when no clients are connected
if (connectedClients == 0 && deviceConnected) {
delay(500); // give the bluetooth stack the chance to get things ready
pServer->startAdvertising(); // restart advertising
Serial.println("No clients connected, restarting advertising");
deviceConnected = false;
}
// Connecting - update state when first client connects
if (connectedClients > 0 && !deviceConnected) {
// do stuff here on first connecting
deviceConnected = true;
}
}
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
@@ -0,0 +1,160 @@
/*
Simple BLE Server Authorization Example
This example demonstrates how to create a BLE server with authorization
requirements. It shows the essential setup for:
- Authorization with static passkey
- Secure connection
- MITM (Man-In-The-Middle) protection
The server creates a single characteristic that requires authorization
to access. Clients must provide the correct passkey (123456) to read
or write to the characteristic.
Note that ESP32 uses Bluedroid by default and the other SoCs use NimBLE.
Bluedroid initiates security on-connect, while NimBLE initiates security on-demand.
Due to a bug in ESP-IDF's Bluedroid, this example will currently not work with ESP32.
IMPORTANT: MITM (Man-In-The-Middle protection) must be enabled for password prompts
to work. Without MITM, the BLE stack assumes no user interaction is needed and will use
"Just Works" pairing method (with encryption if secure connection is enabled).
Created by lucasssvaz.
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLESecurity.h>
#include <nvs_flash.h>
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
// Example passkey - change this for production use
#define AUTH_PASSKEY 123456
static int s_readCount = 0;
static BLECharacteristic *s_pCharacteristic;
class MySecurityCallbacks : public BLESecurityCallbacks {
bool onAuthorizationRequest(uint16_t connHandle, uint16_t attrHandle, bool isRead) {
Serial.println("Authorization request received");
if (isRead) {
s_readCount++;
// Keep value length <= (MTU - 1) to avoid a follow-up read request
uint16_t maxLen = BLEDevice::getServer()->getPeerMTU(connHandle) - 1;
String msg = "Authorized #" + String(s_readCount);
if (msg.length() > maxLen) {
msg = msg.substring(0, maxLen);
}
s_pCharacteristic->setValue(msg);
// Grant authorization to the first 3 reads
if (s_readCount <= 3) {
Serial.println("Authorization granted");
return true;
} else {
Serial.println("Authorization denied, read count exceeded");
Serial.println("Please reset the read counter to continue");
return false;
}
}
// Fallback to deny
Serial.println("Authorization denied");
return false;
}
};
void setup() {
Serial.begin(115200);
Serial.println("Starting BLE Authorization Example!");
// Initialize the BOOT pin for resetting the read count
pinMode(BOOT_PIN, INPUT_PULLUP);
// Clear NVS to remove any cached pairing information
// This ensures fresh authentication for testing
Serial.println("Clearing NVS pairing data...");
nvs_flash_erase();
nvs_flash_init();
Serial.print("Using BLE stack: ");
Serial.println(BLEDevice::getBLEStackString());
BLEDevice::init("BLE Auth Server");
// Set MTU to 517 to avoid a follow-up read request
BLEDevice::setMTU(517);
// Configure BLE Security
BLESecurity *pSecurity = new BLESecurity();
// Set static passkey for authentication
pSecurity->setPassKey(true, AUTH_PASSKEY);
// Set IO capability to DisplayOnly for MITM authentication
pSecurity->setCapability(ESP_IO_CAP_OUT);
// Enable authorization requirements:
// - bonding: true (for persistent storage of the keys)
// - MITM: true (enables Man-In-The-Middle protection for password prompts)
// - secure connection: true (enables secure connection for encryption)
pSecurity->setAuthenticationMode(true, true, true);
// Set the security callbacks
BLEDevice::setSecurityCallbacks(new MySecurityCallbacks());
// Create BLE Server
BLEServer *pServer = BLEDevice::createServer();
pServer->advertiseOnDisconnect(true);
// Create BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Create characteristic with read and write properties
uint32_t properties = BLECharacteristic::PROPERTY_READ;
// For NimBLE: Add authentication properties
// These properties ensure the characteristic requires authorization
// (ignored by Bluedroid but harmless)
properties |= BLECharacteristic::PROPERTY_READ_AUTHEN | BLECharacteristic::PROPERTY_READ_AUTHOR;
s_pCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID, properties);
// For Bluedroid: Set access permissions that require encryption and MITM
// This ensures authorization is required (ignored by NimBLE)
s_pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENC_MITM | ESP_GATT_PERM_READ_AUTHORIZATION);
// Set initial value
s_pCharacteristic->setValue("Hello! You needed authorization to read this!");
// Start the service
pService->start();
// Configure and start advertising
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06); // helps with iPhone connections
pAdvertising->setMaxPreferred(0x12);
BLEDevice::startAdvertising();
Serial.println("BLE Server is running!");
Serial.println("Authorization is required to access the characteristic.");
Serial.printf("Use passkey: %d when prompted\n", AUTH_PASSKEY);
}
void loop() {
// Reset the read count if the BOOT pin is pressed
if (digitalRead(BOOT_PIN) == LOW) {
s_readCount = 0;
Serial.println("Read count reset");
}
delay(100);
}
@@ -0,0 +1,7 @@
targets:
esp32: false
fqbn_append: PartitionScheme=huge_app
requires:
- CONFIG_SOC_BLE_SUPPORTED=y
@@ -0,0 +1,195 @@
/*
Secure server with static passkey
This example demonstrates how to create a secure BLE server with no
IO capability using a static passkey.
The server will accept connections from devices that have the same passkey set.
The example passkey is set to 123456.
The server will create a service and a secure and an insecure characteristic
to be used as example.
This server is designed to be used with the Client_secure_static_passkey example.
Note that ESP32 uses Bluedroid by default and the other SoCs use NimBLE.
Bluedroid initiates security on-connect, while NimBLE initiates security on-demand.
This means that in NimBLE you can read the insecure characteristic without entering
the passkey. This is not possible in Bluedroid.
IMPORTANT: MITM (Man-In-The-Middle protection) must be enabled for password prompts
to work. Without MITM, the BLE stack assumes no user interaction is needed and will use
"Just Works" pairing method (with encryption if secure connection is enabled).
Based on examples from Neil Kolban and h2zero.
Created by lucasssvaz.
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLESecurity.h>
#include <nvs_flash.h>
#include <string>
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define INSECURE_CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
#define SECURE_CHARACTERISTIC_UUID "ff1d2614-e2d6-4c87-9154-6625d39ca7f8"
// This is an example passkey. You should use a different or random passkey.
#define SERVER_PIN 123456
// Print an IRK buffer as hex with leading zeros and ':' separator
static void printIrkBinary(uint8_t *irk) {
for (int i = 0; i < 16; i++) {
if (irk[i] < 0x10) {
Serial.print("0");
}
Serial.print(irk[i], HEX);
if (i < 15) {
Serial.print(":");
}
}
}
static void get_peer_irk(BLEAddress peerAddr) {
Serial.println("\n=== Retrieving peer IRK (Client) ===\n");
uint8_t irk[16];
// Get IRK in binary format
if (BLEDevice::getPeerIRK(peerAddr, irk)) {
Serial.println("Successfully retrieved peer IRK in binary format:");
printIrkBinary(irk);
Serial.println("\n");
}
// Get IRK in different string formats
String irkString = BLEDevice::getPeerIRKString(peerAddr);
String irkBase64 = BLEDevice::getPeerIRKBase64(peerAddr);
String irkReverse = BLEDevice::getPeerIRKReverse(peerAddr);
if (irkString.length() > 0) {
Serial.println("Successfully retrieved peer IRK in multiple formats:\n");
Serial.print("IRK (comma-separated hex): ");
Serial.println(irkString);
Serial.print("IRK (Base64 for Home Assistant Private BLE Device): ");
Serial.println(irkBase64);
Serial.print("IRK (reverse hex for Home Assistant ESPresense): ");
Serial.println(irkReverse);
Serial.println();
} else {
Serial.println("!!! Failed to retrieve peer IRK !!!");
Serial.println("This is expected if bonding is disabled or the peer doesn't distribute its Identity Key.");
Serial.println("To enable bonding, change setAuthenticationMode to: pSecurity->setAuthenticationMode(true, true, true);\n");
}
Serial.println("=======================================\n");
}
// Security callbacks to print IRKs once authentication completes
class MySecurityCallbacks : public BLESecurityCallbacks {
#if defined(CONFIG_BLUEDROID_ENABLED)
void onAuthenticationComplete(esp_ble_auth_cmpl_t desc) override {
// Print the IRK received by the peer
BLEAddress peerAddr(desc.bd_addr);
get_peer_irk(peerAddr);
}
#endif
#if defined(CONFIG_NIMBLE_ENABLED)
void onAuthenticationComplete(ble_gap_conn_desc *desc) override {
// Print the IRK received by the peer
BLEAddress peerAddr(desc->peer_id_addr.val, desc->peer_id_addr.type);
get_peer_irk(peerAddr);
}
#endif
};
void setup() {
Serial.begin(115200);
Serial.println("Starting BLE work!");
// Clear NVS to remove any cached pairing information
// This ensures fresh authentication for testing
Serial.println("Clearing NVS pairing data...");
nvs_flash_erase();
nvs_flash_init();
Serial.print("Using BLE stack: ");
Serial.println(BLEDevice::getBLEStackString());
BLEDevice::init("Secure BLE Server");
BLESecurity *pSecurity = new BLESecurity();
// Set security parameters
// Default parameters:
// - IO capability is set to NONE
// - Initiator and responder key distribution flags are set to both encryption and identity keys.
// - Passkey is set to BLE_SM_DEFAULT_PASSKEY (123456). It will warn if you don't change it.
// - Key size is set to 16 bytes
// Set static passkey
// The first argument defines if the passkey is static or random.
// The second argument is the passkey (ignored when using a random passkey).
pSecurity->setPassKey(true, SERVER_PIN);
// Set IO capability to DisplayOnly
// We need the proper IO capability for MITM authentication even
// if the passkey is static and won't be shown to the user
// See https://www.bluetooth.com/blog/bluetooth-pairing-part-2-key-generation-methods/
pSecurity->setCapability(ESP_IO_CAP_OUT);
// Set authentication mode
// Enable bonding, MITM (for password prompts), and secure connection for this example
pSecurity->setAuthenticationMode(true, true, true);
// Set callbacks to handle authentication completion and print IRKs
BLEDevice::setSecurityCallbacks(new MySecurityCallbacks());
BLEServer *pServer = BLEDevice::createServer();
pServer->advertiseOnDisconnect(true);
BLEService *pService = pServer->createService(SERVICE_UUID);
uint32_t insecure_properties = BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE;
uint32_t secure_properties = insecure_properties;
// NimBLE uses properties to secure characteristics.
// These special permission properties are not supported by Bluedroid and will be ignored.
// This can be removed if only using Bluedroid (ESP32).
// Check the BLECharacteristic.h file for more information.
secure_properties |= BLECharacteristic::PROPERTY_READ_AUTHEN | BLECharacteristic::PROPERTY_WRITE_AUTHEN;
BLECharacteristic *pSecureCharacteristic = pService->createCharacteristic(SECURE_CHARACTERISTIC_UUID, secure_properties);
BLECharacteristic *pInsecureCharacteristic = pService->createCharacteristic(INSECURE_CHARACTERISTIC_UUID, insecure_properties);
// Bluedroid uses permissions to secure characteristics.
// This is the same as using the properties above.
// NimBLE does not use permissions and will ignore these calls.
// This can be removed if only using NimBLE (any SoC except ESP32).
pSecureCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENC_MITM | ESP_GATT_PERM_WRITE_ENC_MITM);
pInsecureCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE);
// Set value for secure characteristic
pSecureCharacteristic->setValue("Secure Hello World!");
// Set value for insecure characteristic
// When using NimBLE you will be able to read this characteristic without entering the passkey.
pInsecureCharacteristic->setValue("Insecure Hello World!");
pService->start();
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue
pAdvertising->setMaxPreferred(0x12);
BLEDevice::startAdvertising();
Serial.println("Characteristic defined! Now you can read it in your phone!");
}
void loop() {
delay(2000);
}
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
+123
View File
@@ -0,0 +1,123 @@
/*
Video: https://www.youtube.com/watch?v=oCMOYS71NIU
Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp
Ported to Arduino ESP32 by Evandro Copercini
Create a BLE server that, once we receive a connection, will send periodic notifications.
The service advertises itself as: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E
Has a characteristic of: 6E400002-B5A3-F393-E0A9-E50E24DCCA9E - used for receiving data with "WRITE"
Has a characteristic of: 6E400003-B5A3-F393-E0A9-E50E24DCCA9E - used to send data with "NOTIFY"
The design of creating the BLE server is:
1. Create a BLE Server
2. Create a BLE Service
3. Create a BLE Characteristic on the Service
4. Create a BLE Descriptor on the characteristic
5. Start the service.
6. Start advertising.
In this example rxValue is the data received (only accessible inside that function).
And txValue is the data to be sent, in this example just a byte incremented every second.
*/
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
BLEServer *pServer = NULL;
BLECharacteristic *pTxCharacteristic;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint8_t txValue = 0;
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer *pServer) {
deviceConnected = true;
Serial.println("Device connected");
};
void onDisconnect(BLEServer *pServer) {
deviceConnected = false;
Serial.println("Device disconnected");
}
};
class MyCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
String rxValue = pCharacteristic->getValue();
if (rxValue.length() > 0) {
Serial.println("*********");
Serial.print("Received Value: ");
for (int i = 0; i < rxValue.length(); i++) {
Serial.print(rxValue[i]);
}
Serial.println();
Serial.println("*********");
}
}
};
void setup() {
Serial.begin(115200);
// Create the BLE Device
BLEDevice::init("UART Service");
// Create the BLE Server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Create the BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Create a BLE Characteristic
pTxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY);
// Descriptor 2902 is not required when using NimBLE as it is automatically added based on the characteristic properties
pTxCharacteristic->addDescriptor(new BLE2902());
BLECharacteristic *pRxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE);
pRxCharacteristic->setCallbacks(new MyCallbacks());
// Start the service
pService->start();
// Start advertising
pServer->getAdvertising()->start();
Serial.println("Waiting a client connection to notify...");
}
void loop() {
if (deviceConnected) {
Serial.print("Notifying Value: ");
Serial.println(txValue);
pTxCharacteristic->setValue(&txValue, 1);
pTxCharacteristic->notify();
txValue++;
delay(1000); // Notifying every 1 second
}
// disconnecting
if (!deviceConnected && oldDeviceConnected) {
delay(500); // give the bluetooth stack the chance to get things ready
pServer->startAdvertising(); // restart advertising
Serial.println("Started advertising again...");
oldDeviceConnected = false;
}
// connecting
if (deviceConnected && !oldDeviceConnected) {
// do stuff here on connecting
oldDeviceConnected = true;
}
}
+5
View File
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
+62
View File
@@ -0,0 +1,62 @@
/*
Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleWrite.cpp
Ported to Arduino ESP32 by Evandro Copercini
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
class MyCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
String value = pCharacteristic->getValue();
if (value.length() > 0) {
Serial.println("*********");
Serial.print("New value: ");
for (int i = 0; i < value.length(); i++) {
Serial.print(value[i]);
}
Serial.println();
Serial.println("*********");
}
}
};
void setup() {
Serial.begin(115200);
Serial.println("1- Download and install an BLE scanner app in your phone");
Serial.println("2- Scan for BLE devices in the app");
Serial.println("3- Connect to MyESP32");
Serial.println("4- Go to CUSTOM CHARACTERISTIC in CUSTOM SERVICE and write something");
Serial.println("5- See the magic =)");
BLEDevice::init("MyESP32");
BLEServer *pServer = BLEDevice::createServer();
BLEService *pService = pServer->createService(SERVICE_UUID);
BLECharacteristic *pCharacteristic =
pService->createCharacteristic(CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);
pCharacteristic->setCallbacks(new MyCallbacks());
pCharacteristic->setValue("Hello World");
pService->start();
BLEAdvertising *pAdvertising = pServer->getAdvertising();
pAdvertising->start();
}
void loop() {
// put your main code here, to run repeatedly:
delay(2000);
}
+5
View File
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
+5
View File
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
+133
View File
@@ -0,0 +1,133 @@
/*
Based on 31337Ghost's reference code from https://github.com/nkolban/esp32-snippets/issues/385#issuecomment-362535434
which is based on pcbreflux's Arduino ESP32 port of Neil Kolban's example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleScan.cpp
*/
/*
Create a BLE server that will send periodic iBeacon frames.
The design of creating the BLE server is:
1. Create a BLE Server
2. Create advertising data
3. Start advertising.
4. wait
5. Stop advertising.
*/
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <BLEBeacon.h>
#define DEVICE_NAME "ESP32"
#define SERVICE_UUID "7A0247E7-8E88-409B-A959-AB5092DDB03E"
#define BEACON_UUID "2D7A9F0C-E0E8-4CC9-A71B-A21DB2D034A1"
#define BEACON_UUID_REV "A134D0B2-1DA2-1BA7-C94C-E8E00C9F7A2D"
#define CHARACTERISTIC_UUID "82258BAA-DF72-47E8-99BC-B73D7ECD08A5"
BLEServer *pServer;
BLECharacteristic *pCharacteristic;
bool deviceConnected = false;
uint8_t value = 0;
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer *pServer) {
deviceConnected = true;
Serial.println("deviceConnected = true");
};
void onDisconnect(BLEServer *pServer) {
deviceConnected = false;
Serial.println("deviceConnected = false");
// Restart advertising to be visible and connectable again
BLEAdvertising *pAdvertising;
pAdvertising = pServer->getAdvertising();
pAdvertising->start();
Serial.println("iBeacon advertising restarted");
}
};
class MyCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
String rxValue = pCharacteristic->getValue();
if (rxValue.length() > 0) {
Serial.println("*********");
Serial.print("Received Value: ");
for (int i = 0; i < rxValue.length(); i++) {
Serial.print(rxValue[i]);
}
Serial.println();
Serial.println("*********");
}
}
};
void init_service() {
BLEAdvertising *pAdvertising;
pAdvertising = pServer->getAdvertising();
pAdvertising->stop();
// Create the BLE Service
BLEService *pService = pServer->createService(BLEUUID(SERVICE_UUID));
// Create a BLE Characteristic
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY
);
pCharacteristic->setCallbacks(new MyCallbacks());
pCharacteristic->addDescriptor(new BLE2902());
pAdvertising->addServiceUUID(BLEUUID(SERVICE_UUID));
// Start the service
pService->start();
pAdvertising->start();
}
void init_beacon() {
BLEAdvertising *pAdvertising;
pAdvertising = pServer->getAdvertising();
pAdvertising->stop();
// iBeacon
BLEBeacon myBeacon;
myBeacon.setManufacturerId(0x4c00);
myBeacon.setMajor(5);
myBeacon.setMinor(88);
myBeacon.setSignalPower(0xc5);
myBeacon.setProximityUUID(BLEUUID(BEACON_UUID_REV));
BLEAdvertisementData advertisementData;
advertisementData.setFlags(0x1A);
advertisementData.setManufacturerData(myBeacon.getData());
pAdvertising->setAdvertisementData(advertisementData);
pAdvertising->start();
}
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println("Initializing...");
Serial.flush();
BLEDevice::init(DEVICE_NAME);
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
init_service();
init_beacon();
Serial.println("iBeacon + service defined and advertising!");
}
void loop() {
if (deviceConnected) {
Serial.printf("*** NOTIFY: %d ***\n", value);
pCharacteristic->setValue(&value, 1);
pCharacteristic->notify();
value++;
}
delay(2000);
}