Scanning BLE Devices Using ESP32 IDF
Contents
Introduction
The ESP32 is a low-cost, low-power microcontroller developed by Espressif Systems, featuring dual-mode capabilities for Wi-Fi and Bluetooth (including BLE, Bluetooth Low Energy). It is widely used in Internet of Things (IoT) projects, particularly suitable for applications involving wireless communication, smart homes, and automation systems. Its high performance and rich peripheral support make it very popular among developers.
Working Principle
BLE (Bluetooth Low Energy) is a Bluetooth technology designed for low-power devices and is mainly used for short-distance data transmission. On ESP32, BLE functionality can be used for device scanning, broadcasting, and data exchange.
1. BLE scanning principle
When ESP32 performs a BLE scan, it searches for surrounding BLE devices that are sending broadcast signals. These broadcast signals contain basic information about the device, such as MAC address (BDA, Bluetooth Device Address), device name, signal strength (RSSI), and other customized broadcast data.
There are two main modes of the scanning process:
Passive scanning: ESP32 will only receive broadcast signals and will not send scanning requests.
Active scanning: After receiving the broadcast signal, ESP32 will also send a scan request to obtain more device information.
2. ESP32 BLE scanning operation process
When we start the BLE scanning function of ESP32, ESP32 will perform the following steps
Set scan parameters: Set the scan interval, window time and scan mode (active or passive) through the esp_ble_scan_params_t structure.
Start scanning: Use the esp_ble_gap_start_scanning() function to start scanning, which will cause ESP32 to start scanning for nearby BLE devices.
Processing scan results: Whenever ESP32 scans a device, it will trigger a callback function and pass the scanned device data to the application in the form of an event. We can obtain the device name, MAC address, RSSI value, etc. from this data.
3. Name and BDA replacement
During the BLE scanning process, ESP32 usually identifies the device with a BDA (Bluetooth Device Address, MAC address). But if the device has a broadcast name (like the name advertised by a BLE peripheral), then we can replace the part that shows the BDA to display the device name in a more human-friendly way. In this way, when users view the scan results, they can not only see the address of the device, but also the name of the device, improving readability and recognition.
Installing VSCode & The ESP-IDF Extension
Make sure you have installed and configured the ESP-IDF development environment. You can also refer to the article the ESP32 Tutorial – ESP-IDF With VSCode.
Creating A New ESP32 Project
We can use VSCode's IDF plug-in to create a new ESP32 project. Please refer to ESP32 Tutorial – How To Create An ESP32 Project With VSCode.
Bluetooth Configuration In VSCode
1. Open your ESP-IDF project.
2. In VSCode, press Ctrl + Shift + P and select ESP-IDF: Configure Project.
3. In the pop-up SDK Configuration Editor, find the Bluetooth section.
4. Enable Bluetooth.
5. Save your configuration and recompile the project.
Initialize NVS & Bluetooth
The first step is to initialize the non-volatile storage (NVS) and Bluetooth functionality. NVS is required for BLE functionality as it is used to persistently store various configuration parameters.
void init_nvs(void) {
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
}
void init_ble(void) {
esp_err_t ret;
// Initialize the Bluetooth controller
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret) {
ESP_LOGE(TAG, "Failed to initialize BT controller: %s", esp_err_to_name(ret));
return;
}
// Enable the Bluetooth controller
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(TAG, "Failed to enable BT controller: %s", esp_err_to_name(ret));
return;
}
// Initialize Bluedroid
ret = esp_bluedroid_init();
if (ret) {
ESP_LOGE(TAG, "Failed to initialize Bluedroid: %s", esp_err_to_name(ret));
return;
}
// Enable Bluedroid
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(TAG, "Failed to enable Bluedroid: %s", esp_err_to_name(ret));
return;
}
}
Here we initialize the NVS storage using nvs_flash_init() and the Bluetooth controller using esp_bt_controller_init().
Set BLE Scanning Parameters
After the Bluetooth function is enabled, we can configure scanning parameters such as scan type, address type and filtering policy.
esp_ble_scan_params_t ble_scan_params = {
.scan_type = BLE_SCAN_TYPE_ACTIVE,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
.scan_duplicate = BLE_SCAN_DUPLICATE_ENABLE,
.scan_interval = 0x50,
.scan_window = 0x30,
};
Scan type: We are using active scanning, which means the scanner sends a request to the BLE device to get more detailed information.
Scan filtering policy: Allow scanning of all devices.
Scan intervals and windows: These define how often and for how long scans occur. Adjusting these values affects responsiveness and power consumption.
Process Scan Results
Next, we can register a callback function to handle the scan results. The esp_ble_gap_register_callback() function is used to register an event handler that will process the scan results and record the device name and RSSI.
We will use a helper function to extract the device name from the broadcast data. If the name is not found, we mark the device as "Unknown Device".
static void get_device_name(esp_ble_gap_cb_param_t *param, char *name, int name_len)
{
uint8_t *adv_name = NULL;
uint8_t adv_name_len = 0;
// Extract the device name from the advertisement data
adv_name = esp_ble_resolve_adv_data(param->scan_rst.ble_adv, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len);
if (adv_name != NULL && adv_name_len > 0) {
// Copy the device name to the output buffer
snprintf(name, name_len, "%.*s", adv_name_len, adv_name);
} else {
// If no name is found, show "Unknown device"
snprintf(name, name_len, "Unknown device");
}
}
This function uses esp_ble_resolve_adv_data() to extract the name from the BLE advertisement packet.
Next, let's create the GAP event handler to manage the different BLE events, specifically the scan result events.
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
switch (event) {
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
if (param->scan_param_cmpl.status == ESP_BT_STATUS_SUCCESS) {
ESP_LOGI(TAG, "Scan parameters set, starting scan...");
esp_ble_gap_start_scanning(10); // Scan for 10 seconds
} else {
ESP_LOGE(TAG, "Failed to set scan parameters");
}
break;
case ESP_GAP_BLE_SCAN_RESULT_EVT:
if (param->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
// Get the device name
char device_name[64];
get_device_name(param, device_name, sizeof(device_name));
// Log the device name and RSSI
ESP_LOGI(TAG, "Device found: Name: %s, RSSI %d", device_name, param->scan_rst.rssi);
}
break;
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
ESP_LOGI(TAG, "Scan complete");
break;
default:
break;
}
}
In the event handler:
ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: When the scanning parameters are set, ESP32 starts scanning.
ESP_GAP_BLE_SCAN_RESULT_EVT: Process the scan results and log the device name and RSSI.
ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: After the scan is completed, a completion message is logged.
Run BLE Scanner
Finally, combine all the code into the app_main() function...
void app_main(void)
{
// Initialize NVS
init_nvs();
// Initialize BLE
init_ble();
// Register GAP callback
esp_ble_gap_register_callback(gap_event_handler);
// Set scan parameters
esp_err_t ret = esp_ble_gap_set_scan_params(&ble_scan_params);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "BLE scan parameters set successfully");
} else {
ESP_LOGE(TAG, "Failed to set scan params: %s", esp_err_to_name(ret));
}
}
This function initializes NVS, enables BLE, registers GAP callbacks, and sets scan parameters.
Compile, Flash & Monitor
Find the "ESP32-IDF: Build, Flash and Monitor" ICON in VSCode to execute and burn.
Results
I (5010) BLE_SCAN: Device found: Name: MyBLEDevice, RSSI -65
I (6020) BLE_SCAN: Device found: Name: Unknown device, RSSI -72
Conclusion
With this code, you can now use the ESP32 to scan for BLE devices and log their names and signal strength (RSSI). BLE is a powerful tool in IoT projects, and scanning for devices is just the beginning. You can further extend this code to connect to specific devices, read sensor data, or even broadcast your own BLE information.
You can adjust the scan interval and window or modify the logging to filter for specific device names or RSSI values. The ESP32 offers great flexibility in BLE projects.