Merge branch 'feat/add_get_started_example_on_bluedroid_v5.0' into 'release/v5.0'

feat(bt/bluedroid): Added BLE get started examples for Bluedroid (v5.0)

See merge request espressif/esp-idf!35649
This commit is contained in:
Island 2025-01-24 10:53:13 +08:00
commit 743ed7b1d7
32 changed files with 1984 additions and 0 deletions

View File

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(bluedroid_beacon)

View File

@ -0,0 +1,249 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- |
# Bluedroid Beacon Example
## Overview
This is a pretty simple example, aiming to introduce
1. How to initialize Bluedroid stack
2. How to configure advertisement and scan response data
3. How to start advertising as a non-connectable beacon
It uses ESP32's Bluetooth controller and Bluedroid host stack.
To test this demo, any BLE scanner application can be used.
## Try It Yourself
### Set Target
Before project configuration and build, be sure to set the correct chip target using:
``` shell
idf.py set-target <chip_name>
```
For example, if you're using ESP32, then input
``` Shell
idf.py set-target esp32
```
### Build and Flash
Run the following command to build, flash and monitor the project.
``` Shell
idf.py -p <PORT> flash monitor
```
For example, if the corresponding serial port is `/dev/ttyACM0`, then it goes
``` Shell
idf.py -p /dev/ttyACM0 flash monitor
```
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects.
## Code Explained
### Overview
1. Initialize NVS flash, Bluedroid host stack and GAP service; configure Bluedroid host stack and start Bluedroid host task thread
2. Set advertisement and scan response data, then configure advertising parameters and start advertising
### Entry Point
`app_main` in `main.c` is the entry point of all ESP32 applications. In general, application initialization should be done here.
First, call `nvs_flash_init`, `esp_bt_controller_init` and `esp_bt_controller_enable` functions to initialize NVS flash as well as the BT controller.
``` C
void app_main(void) {
esp_err_t ret;
//initialize NVS
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);
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret) {
ESP_LOGE(DEMO_TAG, "%s initialize controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(DEMO_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
...
}
```
Then, call `esp_bluedroid_init` and `esp_bluedroid_enable` function to initialize Bluedroid host stack.
``` C
void app_main(void) {
...
ret = esp_bluedroid_init();
if (ret) {
ESP_LOGE(DEMO_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(DEMO_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
...
}
```
After that, call `esp_ble_gap_register_callback` to register `esp_gap_cb` function as GAP service callback function. From then on all GAP events will be handled by `esp_gap_cb` function.
``` C
void app_main(void) {
...
ret = esp_ble_gap_register_callback(esp_gap_cb);
if (ret) {
ESP_LOGE(DEMO_TAG, "gap register error, error code = %x", ret);
return;
}
...
}
```
### Start Advertising
As a beacon device, we're going to start advertising and send scan response if a scan request is received. To make it happen, we need to set advertisement and scan response data before advertising starts. So the following are what we do:
1. Initialize advertisement and scan response fields structs `adv_raw_data` and `scan_rsp_raw_data`, as well as advertising parameters struct `adv_params`.
2. Set advertising parameters based on your requirements.
1. advertising interval is set to 20 ms.
2. advertising PDU is set to `ADV_SCAN_IND`.
3. advertising address type is public address.
4. advertising channel is set to all channels. Channels 37, 38 and 39 will all be used for advertising.
3. Set advertising raw data and scan response raw data.
``` C
static esp_ble_adv_params_t adv_params = {
.adv_int_min = 0x20, // 20ms
.adv_int_max = 0x20, // 20ms
.adv_type = ADV_TYPE_SCAN_IND,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.channel_map = ADV_CHNL_ALL,
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
//configure raw data for advertising packet
static uint8_t adv_raw_data[] = {
0x02, ESP_BLE_AD_TYPE_FLAG, 0x06,
0x11, ESP_BLE_AD_TYPE_NAME_CMPL, 'B', 'l', 'u', 'e', 'd', 'r', 'o', 'i', 'd', '_', 'B', 'e', 'a', 'c', 'o', 'n',
0x02, ESP_BLE_AD_TYPE_TX_PWR, 0x09,
0x03, ESP_BLE_AD_TYPE_APPEARANCE, 0x00,0x02,
0x02, ESP_BLE_AD_TYPE_LE_ROLE, 0x00,
};
static uint8_t scan_rsp_raw_data[] = {
0x08, ESP_BLE_AD_TYPE_LE_DEV_ADDR, 0x46, 0xF5, 0x06, 0xBD, 0xF5, 0xF0, 0x00,
0x11, ESP_BLE_AD_TYPE_URI, 0x17, 0x2F, 0x2F, 0x65, 0x73, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x66, 0x2E, 0x63, 0x6F, 0x6D,
};
```
4. Config advertising raw data with `esp_ble_gap_config_adv_data_raw`. Set device address in response raw data and call `esp_ble_gap_config_scan_rsp_data_raw` to configure advertising raw data.
1. Since `AdvData` in advertisement packet **should not be longer than 31 bytes**, additional information must be placed in scan response packet
2. We put the official website link of espressif into URI field
3. Take care of endianness of device address
``` C
void app_main(void) {
...
adv_config_done |= ADV_CONFIG_FLAG;
adv_config_done |= SCAN_RSP_CONFIG_FLAG;
ret = esp_ble_gap_config_adv_data_raw(adv_raw_data, sizeof(adv_raw_data));
if (ret) {
ESP_LOGE(DEMO_TAG, "config adv data failed, error code = %x", ret);
return;
}
ret = esp_ble_gap_get_local_used_addr(local_addr, &local_addr_type);
if (ret) {
ESP_LOGE(DEMO_TAG, "get local used address failed, error code = %x", ret);
return;
}
scan_rsp_raw_data[2] = local_addr[5];
scan_rsp_raw_data[3] = local_addr[4];
scan_rsp_raw_data[4] = local_addr[3];
scan_rsp_raw_data[5] = local_addr[2];
scan_rsp_raw_data[6] = local_addr[1];
scan_rsp_raw_data[7] = local_addr[0];
ret = esp_ble_gap_config_scan_rsp_data_raw(scan_rsp_raw_data, sizeof(scan_rsp_raw_data));
if (ret) {
ESP_LOGE(DEMO_TAG, "config scan rsp data failed, error code = %x", ret);
}
}
```
4. When both advertising raw data and scan response raw data are successfully set, start advertising by calling `esp_ble_gap_start_advertising`
``` C
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
...
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
ESP_LOGI(DEMO_TAG, "Advertising data raw set, status %d", param->adv_data_raw_cmpl.status);
adv_config_done &= (~ADV_CONFIG_FLAG);
if (adv_config_done == 0) {
esp_ble_gap_start_advertising(&adv_params);
}
break;
case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT:
ESP_LOGI(DEMO_TAG, "Scan response data raw set, status %d", param->scan_rsp_data_raw_cmpl.status);
adv_config_done &= (~SCAN_RSP_CONFIG_FLAG);
if (adv_config_done == 0) {
esp_ble_gap_start_advertising(&adv_params);
}
break;
}
```
5. If advertising has been successfully enabled, you should receive `ESP_GAP_BLE_ADV_START_COMPLETE_EVT` GAP event.
``` C
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
...
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(DEMO_TAG, "Advertising start failed, status %d", param->adv_start_cmpl.status);
break;
}
ESP_LOGI(DEMO_TAG, "Advertising start successfully");
break;
}
```
### Observation
If everything goes well, you should be able to see `Bluedroid_Beacon` on a BLE scanner device, broadcasting a lot of information including an URI of "https://espressif.com" (The official website of espressif), which is exactly what we expect.
## Troubleshooting
For any technical queries, please file an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,177 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gattc_api.h"
#include "esp_gatt_defs.h"
#include "esp_bt_main.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_bt_defs.h"
#include "freertos/FreeRTOS.h"
#define ADV_CONFIG_FLAG (1 << 0)
#define SCAN_RSP_CONFIG_FLAG (1 << 1)
#define URI_PREFIX_HTTPS (0x17)
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
static const char *DEMO_TAG = "BLE_BEACON";
static const char device_name[] = "Bluedroid_Beacon";
static uint8_t adv_config_done = 0;
static esp_bd_addr_t local_addr;
static uint8_t local_addr_type;
static esp_ble_adv_params_t adv_params = {
.adv_int_min = 0x20, // 20ms
.adv_int_max = 0x20, // 20ms
.adv_type = ADV_TYPE_SCAN_IND,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.channel_map = ADV_CHNL_ALL,
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
//configure raw data for advertising packet
static uint8_t adv_raw_data[] = {
0x02, ESP_BLE_AD_TYPE_FLAG, 0x06,
0x11, ESP_BLE_AD_TYPE_NAME_CMPL, 'B', 'l', 'u', 'e', 'd', 'r', 'o', 'i', 'd', '_', 'B', 'e', 'a', 'c', 'o', 'n',
0x02, ESP_BLE_AD_TYPE_TX_PWR, 0x09,
0x03, ESP_BLE_AD_TYPE_APPEARANCE, 0x00,0x02,
0x02, ESP_BLE_AD_TYPE_LE_ROLE, 0x00,
};
static uint8_t scan_rsp_raw_data[] = {
0x08, ESP_BLE_AD_TYPE_LE_DEV_ADDR, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x11, ESP_BLE_AD_TYPE_URI, URI_PREFIX_HTTPS, '/', '/', 'e', 's', 'p', 'r', 'e', 's', 's', 'i', 'f', '.', 'c', 'o', 'm',
};
void app_main(void)
{
esp_err_t ret;
//initialize NVS
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);
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret) {
ESP_LOGE(DEMO_TAG, "%s initialize controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(DEMO_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bluedroid_init();
if (ret) {
ESP_LOGE(DEMO_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(DEMO_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_ble_gap_register_callback(esp_gap_cb);
if (ret) {
ESP_LOGE(DEMO_TAG, "gap register error, error code = %x", ret);
return;
}
ret = esp_ble_gap_set_device_name(device_name);
if (ret) {
ESP_LOGE(DEMO_TAG, "set device name error, error code = %x", ret);
return;
}
//config adv data
adv_config_done |= ADV_CONFIG_FLAG;
adv_config_done |= SCAN_RSP_CONFIG_FLAG;
ret = esp_ble_gap_config_adv_data_raw(adv_raw_data, sizeof(adv_raw_data));
if (ret) {
ESP_LOGE(DEMO_TAG, "config adv data failed, error code = %x", ret);
return;
}
ret = esp_ble_gap_get_local_used_addr(local_addr, &local_addr_type);
if (ret) {
ESP_LOGE(DEMO_TAG, "get local used address failed, error code = %x", ret);
return;
}
scan_rsp_raw_data[2] = local_addr[5];
scan_rsp_raw_data[3] = local_addr[4];
scan_rsp_raw_data[4] = local_addr[3];
scan_rsp_raw_data[5] = local_addr[2];
scan_rsp_raw_data[6] = local_addr[1];
scan_rsp_raw_data[7] = local_addr[0];
ret = esp_ble_gap_config_scan_rsp_data_raw(scan_rsp_raw_data, sizeof(scan_rsp_raw_data));
if (ret) {
ESP_LOGE(DEMO_TAG, "config scan rsp data failed, error code = %x", ret);
}
}
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
switch (event) {
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
ESP_LOGI(DEMO_TAG, "Advertising data set, status %d", param->adv_data_cmpl.status);
adv_config_done &= (~ADV_CONFIG_FLAG);
if (adv_config_done == 0) {
esp_ble_gap_start_advertising(&adv_params);
}
break;
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
ESP_LOGI(DEMO_TAG, "Advertising data raw set, status %d", param->adv_data_raw_cmpl.status);
adv_config_done &= (~ADV_CONFIG_FLAG);
if (adv_config_done == 0) {
esp_ble_gap_start_advertising(&adv_params);
}
break;
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
ESP_LOGI(DEMO_TAG, "Scan response data set, status %d", param->scan_rsp_data_cmpl.status);
adv_config_done &= (~SCAN_RSP_CONFIG_FLAG);
if (adv_config_done == 0) {
esp_ble_gap_start_advertising(&adv_params);
}
break;
case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT:
ESP_LOGI(DEMO_TAG, "Scan response data raw set, status %d", param->scan_rsp_data_raw_cmpl.status);
adv_config_done &= (~SCAN_RSP_CONFIG_FLAG);
if (adv_config_done == 0) {
esp_ble_gap_start_advertising(&adv_params);
}
break;
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(DEMO_TAG, "Advertising start failed, status %d", param->adv_start_cmpl.status);
break;
}
ESP_LOGI(DEMO_TAG, "Advertising start successfully");
break;
default:
break;
}
}

View File

@ -0,0 +1,8 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_BT_ENABLED=y
CONFIG_BT_BLE_50_FEATURES_SUPPORTED=n
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
# CONFIG_BT_LE_50_FEATURE_SUPPORT is not used on ESP32, ESP32-C3 and ESP32-S3.
# CONFIG_BT_LE_50_FEATURE_SUPPORT is not set

View File

@ -0,0 +1,10 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_IDF_TARGET="esp32c2"
CONFIG_BT_ENABLED=y
# CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
# CONFIG_BT_LE_50_FEATURE_SUPPORT is not set
CONFIG_BT_LE_HCI_EVT_BUF_SIZE=257
CONFIG_XTAL_FREQ_26=y

View File

@ -0,0 +1,7 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_IDF_TARGET="esp32c3"
CONFIG_BT_ENABLED=y
# CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y

View File

@ -0,0 +1,7 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_IDF_TARGET="esp32s3"
CONFIG_BT_ENABLED=y
# CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y

View File

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(bluedroid_connection)

View File

@ -0,0 +1,169 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- |
# Bluedroid Connection Example
## Overview
This example is extended from Bluedroid Beacon Example, and further introduces
1. How to advertise as a connectable peripheral device
2. How to capture GAP events and handle them
3. How to update connection parameters
It uses ESP32's Bluetooth controller and Bluedroid host stack.
To test this demo, any BLE scanner application can be used.
## Try It Yourself
### Set Target
Before project configuration and build, be sure to set the correct chip target using:
``` shell
idf.py set-target <chip_name>
```
For example, if you're using ESP32, then input
``` Shell
idf.py set-target esp32
```
### Build and Flash
Run the following command to build, flash and monitor the project.
``` Shell
idf.py -p <PORT> flash monitor
```
For example, if the corresponding serial port is `/dev/ttyACM0`, then it goes
``` Shell
idf.py -p /dev/ttyACM0 flash monitor
```
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects.
## Code Explained
### Overview
1. Initialize NVS flash, Bluedroid host stack, GAP and GATT service; configure Bluedroid Host stack
2. Set advertisement and scan response data, then configure advertising parameters and start advertising
3. On connect event
1. Print out connection descriptions
2. Update connection parameters
4. On connection update event
1. Print out connection descriptions
5. On disconnect event
1. Print out connection descriptions
### Entry Point
Please refer to the Bluedroid Beacon Example for details.
### Start Advertising
Please refer to the Bluedroid Beacon Example for details.
### On GAP & GATT Events
To keep it simple, we're interested in 1 GAP event and 3 GATT events at the moment
- `ESP_GATTS_REG_EVT` - GATT service registered event
- `ESP_GATTS_CONNECT_EVT` - GATT connection event
- `ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT` - Connection update event
- `ESP_GATTS_DISCONNECT_EVT` - GATT disconnection event
#### Register GATT service
In `app_main`, we register `gatts_event_handler` function as the callback function for all GATT services by calling `esp_ble_gatts_register_callback`. Then, we need to register a GATT server application with application ID 0 by calling `esp_ble_gatts_app_register`
``` C
void app_main(void)
{
...
ret = esp_ble_gap_register_callback(esp_gap_cb);
if (ret) {
ESP_LOGE(CONN_TAG, "%s gap register failed, error code = %x", __func__, ret);
return;
}
ret = esp_ble_gatts_register_callback(gatts_event_handler);
if (ret) {
ESP_LOGE(CONN_TAG, "%s gatts register failed, error code = %x", __func__, ret);
return;
}
...
}
```
#### Connect Event
When the device is connected to a peer device, a connect event will be passed to `gatts_event_handler` by Bluedroid host stack. Once connection has been established, we will call `esp_ble_gap_update_conn_params` to update connection parameters.
``` C
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
...
case ESP_GATTS_CONNECT_EVT:
esp_ble_conn_update_params_t conn_params = {0};
memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
conn_params.latency = 0;
conn_params.max_int = 0x20;
conn_params.min_int = 0x10;
conn_params.timeout = 400;
ESP_LOGI(CONN_TAG, "Connected, conn_id %u, remote "ESP_BD_ADDR_STR"",
param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda));
esp_ble_gap_update_conn_params(&conn_params);
break;
...
}
```
#### Connection Update Event
When connection parameters are updated, an `ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT` will be passed to `esp_gap_cb` by Bluedroid host stack.
``` C
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
...
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
ESP_LOGI(CONN_TAG, "Connection params update, status %d, conn_int %d, latency %d, timeout %d",
param->update_conn_params.status,
param->update_conn_params.conn_int,
param->update_conn_params.latency,
param->update_conn_params.timeout);
break;
...
}
```
#### Disconnect Event
On `ESP_GATTS_DISCONNECT_EVT`, we simply
1. Print out disconnect reason and connection descriptor
2. Re-start advertising
``` C
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
...
case ESP_GATTS_DISCONNECT_EVT:
ESP_LOGI(CONN_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x",
ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason);
esp_ble_gap_start_advertising(&adv_params);
break;
...
}
```
## Observation
If everything goes well, except for what we have seen in Bluedroid Beacon example, you should be able to connect to and disconnect from the device successfully.
## Troubleshooting
For any technical queries, please file an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,14 @@
menu "Example 'GATT SERVER' Config"
config EXAMPLE_SET_RAW_ADV_DATA
bool "Use raw data for advertising packets and scan response data"
help
If this config item is set, raw binary data will be used to generate advertising & scan response data.
This option uses the esp_ble_gap_config_adv_data_raw() and esp_ble_gap_config_scan_rsp_data_raw()
functions.
If this config item is unset, advertising & scan response data is provided via a higher-level
esp_ble_adv_data_t structure. The lower layer will generate the BLE packets. This option has higher
overhead at runtime.
endmenu

View File

@ -0,0 +1,180 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_bt_defs.h"
#include "esp_bt_main.h"
#include "esp_bt_device.h"
#include "esp_gatt_common_api.h"
#define APP_ID_PLACEHOLDER 0
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
static const char *CONN_TAG = "CONN_DEMO";
static const char device_name[] = "Bluedroid_Conn";
static esp_ble_adv_params_t adv_params = {
.adv_int_min = 0x20, // 20ms
.adv_int_max = 0x20, // 20ms
.adv_type = ADV_TYPE_IND,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.channel_map = ADV_CHNL_ALL,
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
static uint8_t adv_raw_data[] = {
0x02, ESP_BLE_AD_TYPE_FLAG, 0x06,
0x0F, ESP_BLE_AD_TYPE_NAME_CMPL, 'B', 'l', 'u', 'e', 'd', 'r', 'o', 'i', 'd', '_', 'C', 'o', 'n', 'n',
0x02, ESP_BLE_AD_TYPE_TX_PWR, 0x09,
};
void app_main(void)
{
esp_err_t ret;
//initialize NVS
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 );
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret) {
ESP_LOGE(CONN_TAG, "%s initialize controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(CONN_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bluedroid_init();
if (ret) {
ESP_LOGE(CONN_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(CONN_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_ble_gap_register_callback(esp_gap_cb);
if (ret) {
ESP_LOGE(CONN_TAG, "%s gap register failed, error code = %x", __func__, ret);
return;
}
ret = esp_ble_gatts_register_callback(gatts_event_handler);
if (ret) {
ESP_LOGE(CONN_TAG, "%s gatts register failed, error code = %x", __func__, ret);
return;
}
ret = esp_ble_gatts_app_register(APP_ID_PLACEHOLDER);
if (ret) {
ESP_LOGE(CONN_TAG, "%s gatts app register failed, error code = %x", __func__, ret);
return;
}
ret = esp_ble_gatt_set_local_mtu(500);
if (ret) {
ESP_LOGE(CONN_TAG, "set local MTU failed, error code = %x", ret);
return;
}
ret = esp_ble_gap_set_device_name(device_name);
if (ret) {
ESP_LOGE(CONN_TAG, "set device name failed, error code = %x", ret);
return;
}
ret = esp_ble_gap_config_adv_data_raw(adv_raw_data, sizeof(adv_raw_data));
if (ret) {
ESP_LOGE(CONN_TAG, "config adv data failed, error code = %x", ret);
}
}
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
switch (event) {
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
ESP_LOGI(CONN_TAG, "Advertising data set, status %d", param->adv_data_raw_cmpl.status);
esp_ble_gap_start_advertising(&adv_params);
break;
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(CONN_TAG, "Advertising start failed, status %d", param->adv_start_cmpl.status);
break;
}
ESP_LOGI(CONN_TAG, "Advertising start successfully");
break;
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(CONN_TAG, "Advertising stop failed, status %d", param->adv_stop_cmpl.status);
}
ESP_LOGI(CONN_TAG, "Advertising stop successfully");
break;
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
ESP_LOGI(CONN_TAG, "Connection params update, status %d, conn_int %d, latency %d, timeout %d",
param->update_conn_params.status,
param->update_conn_params.conn_int,
param->update_conn_params.latency,
param->update_conn_params.timeout);
break;
default:
break;
}
}
//because we only have one profile table in this demo, there is only one set of gatts evemt handler
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
switch (event) {
case ESP_GATTS_REG_EVT:
ESP_LOGI(CONN_TAG, "GATT server register, status %d, app_id %d", param->reg.status, param->reg.app_id);
break;
case ESP_GATTS_CONNECT_EVT:
esp_ble_conn_update_params_t conn_params = {0};
memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
conn_params.latency = 0;
conn_params.max_int = 0x20;
conn_params.min_int = 0x10;
conn_params.timeout = 400;
ESP_LOGI(CONN_TAG, "Connected, conn_id %u, remote "ESP_BD_ADDR_STR"",
param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda));
esp_ble_gap_update_conn_params(&conn_params);
break;
case ESP_GATTS_DISCONNECT_EVT:
ESP_LOGI(CONN_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x",
ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason);
esp_ble_gap_start_advertising(&adv_params);
break;
default:
break;
}
}

View File

@ -0,0 +1,8 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_BT_ENABLED=y
CONFIG_BT_BLE_50_FEATURES_SUPPORTED=n
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
# CONFIG_BT_LE_50_FEATURE_SUPPORT is not used on ESP32, ESP32-C3 and ESP32-S3.
# CONFIG_BT_LE_50_FEATURE_SUPPORT is not set

View File

@ -0,0 +1,9 @@
# Override some defaults so BT stack is enabled
# by default in this example
CONFIG_IDF_TARGET="esp32c2"
CONFIG_BT_ENABLED=y
# CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
# CONFIG_BT_LE_50_FEATURE_SUPPORT is not set
CONFIG_BT_LE_HCI_EVT_BUF_SIZE=257
CONFIG_XTAL_FREQ_26=y

View File

@ -0,0 +1,7 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_IDF_TARGET="esp32c3"
CONFIG_BT_ENABLED=y
# CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y

View File

@ -0,0 +1,7 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_IDF_TARGET="esp32s3"
CONFIG_BT_ENABLED=y
# CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y

View File

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(bluedroid_gatt_server)

View File

@ -0,0 +1,325 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- |
# Bluedroid GATT Server Example
## Overview
This example is extended from Bluedroid Connection Example, and further introduces
1. How to implement a GATT server
2. How to handle characteristic access requests
1. Write access demonstrated by LED control
2. Read and indicate access demonstrated by heart rate measurement(mocked)
It uses ESP32's Bluetooth controller and Bluedroid host stack.
To test this demo, any BLE scanner application can be used.
## Try It Yourself
### Set Target
Before project configuration and build, be sure to set the correct chip target using:
``` shell
idf.py set-target <chip_name>
```
For example, if you're using ESP32, then input
``` Shell
idf.py set-target esp32
```
### Build and Flash
Run the following command to build, flash and monitor the project.
``` Shell
idf.py -p <PORT> flash monitor
```
For example, if the corresponding serial port is `/dev/ttyACM0`, then it goes
``` Shell
idf.py -p /dev/ttyACM0 flash monitor
```
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects.
## Code Explained
### Overview
1. Initialization
1. Initialize LED, NVS flash, Bluedroid Host stack, GAP service
2. Initialize GATT service and register primary services and characteristics
3. Start heart rate update task thread
2. Wait for Bluedroid host stack to start advertising; wait for connection event to come
3. After connection established, wait for GATT characteristics access events to come
1. On write LED event, turn on or off the LED accordingly
2. On read heart rate event, send out current heart rate measurement value
3. On indicate heart rate event, enable heart rate indication
### Entry Point
In this example, we have two GATT service profiles, the heart rate profile and the LED control profile. The heart rate profile is used to mock a heart rate measurement, and the LED control profile is used to control the LED on the ESP32 board. Thus, we need to register two application IDs in total for two GATT service profiles.
``` C
void app_main(void)
{
...
ret = esp_ble_gatts_app_register(HEART_PROFILE_APP_ID);
if (ret) {
ESP_LOGE(GATTS_TAG, "app register error, error code = %x", ret);
return;
}
ret = esp_ble_gatts_app_register(AUTO_IO_PROFILE_APP_ID);
if (ret) {
ESP_LOGE(GATTS_TAG, "app register error, error code = %x", ret);
return;
}
...
}
```
Then, after Bluedroid host task has been initialized, we'll create another task defined in `heart_rate_task` to update heart rate measurement mock value and send indication if enabled.
### GATT Services
In this example, there are two GATT primary services defined
- Heart rate service with a UUID of `0x180D`
- Automation IO service with a UUID of `0x1815`
The characteristics and characteristic descriptors are added sequentially triggered by GATT events.
After the heart rate service has been successfully initialized, we will start a task to mock a heart rate measurement in one second interval. This measurement will be updated to the heart rate measurement characteristic value.
``` C
static void heart_gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
...
case ESP_GATTS_REG_EVT:
ESP_LOGI(GATTS_TAG, "GATT server register, status %d, app_id %d", param->reg.status, param->reg.app_id);
gl_profile_tab[HEART_PROFILE_APP_ID].service_id.is_primary = true;
gl_profile_tab[HEART_PROFILE_APP_ID].service_id.id.inst_id = 0x00;
gl_profile_tab[HEART_PROFILE_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[HEART_PROFILE_APP_ID].service_id.id.uuid.uuid.uuid16 = HEART_RATE_SVC_UUID;
//config adv data
esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data);
if(ret){
ESP_LOGE(GATTS_TAG, "config adv data failed, error code = %x", ret);
break;
}
esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[HEART_PROFILE_APP_ID].service_id, HEART_NUM_HANDLE);
break;
case ESP_GATTS_CREATE_EVT:
//service has been created, now add characteristic declaration
ESP_LOGI(GATTS_TAG, "Service create, status %d, service_handle %d", param->create.status, param->create.service_handle);
gl_profile_tab[HEART_PROFILE_APP_ID].service_handle = param->create.service_handle;
gl_profile_tab[HEART_PROFILE_APP_ID].char_uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[HEART_PROFILE_APP_ID].char_uuid.uuid.uuid16 = HEART_RATE_CHAR_UUID;
esp_ble_gatts_start_service(gl_profile_tab[HEART_PROFILE_APP_ID].service_handle);
heart_property = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_INDICATE;
ret = esp_ble_gatts_add_char(gl_profile_tab[HEART_PROFILE_APP_ID].service_handle, &gl_profile_tab[HEART_PROFILE_APP_ID].char_uuid,
ESP_GATT_PERM_READ,
heart_property,
&heart_rate_attr, NULL);
if (ret) {
ESP_LOGE(GATTS_TAG, "add char failed, error code = %x", ret);
}
break;
case ESP_GATTS_ADD_CHAR_EVT:
ESP_LOGI(GATTS_TAG, "Characteristic add, status %d, attr_handle %d, char_uuid %x",
param->add_char.status, param->add_char.attr_handle, param->add_char.char_uuid.uuid.uuid16);
gl_profile_tab[HEART_PROFILE_APP_ID].char_handle = param->add_char.attr_handle;
gl_profile_tab[HEART_PROFILE_APP_ID].descr_uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[HEART_PROFILE_APP_ID].descr_uuid.uuid.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG;
ESP_LOGI(GATTS_TAG, "heart rate char handle %d", param->add_char.attr_handle);
ret = esp_ble_gatts_add_char_descr(gl_profile_tab[HEART_PROFILE_APP_ID].service_handle, &gl_profile_tab[HEART_PROFILE_APP_ID].descr_uuid,
ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, NULL, NULL);
break;
case ESP_GATTS_ADD_CHAR_DESCR_EVT:
ESP_LOGI(GATTS_TAG, "Descriptor add, status %d, attr_handle %u",
param->add_char_descr.status, param->add_char_descr.attr_handle);
gl_profile_tab[HEART_PROFILE_APP_ID].descr_handle = param->add_char_descr.attr_handle;
xTaskCreate(heart_rate_task, "Heart Rate", 2 * 1024, NULL, 5, NULL);
break;
...
}
static void auto_io_gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
...
case ESP_GATTS_REG_EVT:
ESP_LOGI(GATTS_TAG, "GATT server register, status %d, app_id %d", param->reg.status, param->reg.app_id);
gl_profile_tab[AUTO_IO_PROFILE_APP_ID].service_id.is_primary = true;
gl_profile_tab[AUTO_IO_PROFILE_APP_ID].service_id.id.inst_id = 0x00;
gl_profile_tab[AUTO_IO_PROFILE_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[AUTO_IO_PROFILE_APP_ID].service_id.id.uuid.uuid.uuid16 = AUTO_IO_SVC_UUID;
esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[AUTO_IO_PROFILE_APP_ID].service_id, AUTO_IO_NUM_HANDLE);
break;
case ESP_GATTS_CREATE_EVT:
//service has been created, now add characteristic declaration
ESP_LOGI(GATTS_TAG, "Service create, status %d, service_handle %d", param->create.status, param->create.service_handle);
gl_profile_tab[AUTO_IO_PROFILE_APP_ID].service_handle = param->create.service_handle;
gl_profile_tab[AUTO_IO_PROFILE_APP_ID].char_uuid.len = ESP_UUID_LEN_128;
memcpy(gl_profile_tab[AUTO_IO_PROFILE_APP_ID].char_uuid.uuid.uuid128, led_chr_uuid, ESP_UUID_LEN_128);
esp_ble_gatts_start_service(gl_profile_tab[AUTO_IO_PROFILE_APP_ID].service_handle);
auto_io_property = ESP_GATT_CHAR_PROP_BIT_WRITE ;
esp_err_t ret = esp_ble_gatts_add_char(gl_profile_tab[AUTO_IO_PROFILE_APP_ID].service_handle, &gl_profile_tab[AUTO_IO_PROFILE_APP_ID].char_uuid,
ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE ,
auto_io_property,
&led_status_attr, NULL);
if (ret) {
ESP_LOGE(GATTS_TAG, "add char failed, error code = %x", ret);
}
break;
case ESP_GATTS_ADD_CHAR_EVT:
ESP_LOGI(GATTS_TAG, "Characteristic add, status %d, attr_handle %d, char_uuid %x",
param->add_char.status, param->add_char.attr_handle, param->add_char.char_uuid.uuid.uuid16);
gl_profile_tab[AUTO_IO_PROFILE_APP_ID].char_handle = param->add_char.attr_handle;
break;
case ESP_GATTS_ADD_CHAR_DESCR_EVT:
ESP_LOGI(GATTS_TAG, "Descriptor add, status %d", param->add_char_descr.status);
gl_profile_tab[AUTO_IO_PROFILE_APP_ID].descr_handle = param->add_char_descr.attr_handle;
break;
...
}
```
#### Automation IO Service
Under automation IO service, there's a LED characteristic, with a vendor-specific 128-bit UUID `led_chr_uuid` and read/write permission.
When a value is written to the characteristic, Bluedroid Host will trigger an `ESP_GATTS_WRITE_EVT`. If the written value is `1`, the LED will be turned on. Otherwise, the LED will be turned off.
``` C
static void auto_io_gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
...
case ESP_GATTS_WRITE_EVT:
ESP_LOGI(GATTS_TAG, "Characteristic write, value len %u, value ", param->write.len);
ESP_LOG_BUFFER_HEX(GATTS_TAG, param->write.value, param->write.len);
if (param->write.value[0]) {
ESP_LOGI(GATTS_TAG, "LED ON!");
led_on();
} else {
ESP_LOGI(GATTS_TAG, "LED OFF!");
led_off();
}
example_write_event_env(gatts_if, param);
break;
...
}
void example_write_event_env(esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
esp_gatt_status_t status = ESP_GATT_OK;
if (param->write.need_rsp) {
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, NULL);
}
}
```
#### Heart Rate Service
Under heart rate service, there's a heart rate measurement characteristic, with a standard UUID `0x2A37` and read/indicate permission. Under the heart rate characteristic, there is also a characteristic client config descriptor with UUID `0x2902` with read/write permissions. This descriptor is used to control whether the characteristic should be notified/indicated or not.
All read and write requests to the heart rate service will be handled by calling `heart_gatts_profile_event_handler`.
1. When an `ESP_GATTS_READ_EVT` occurs, we will send a response to the client identified by the connection id with the latest heart rate measurement mock value.
2. When an `ESP_GATTS_WRITE_EVT` occurs, we will first determine if the write target is the characteristic client config descriptor. If it is, we will update the notify/indicate flag of the heart rate characteristic.
3. If the indicate flag is set, whenever there's an update on the charatertistic value, we will send an indication containing the heart rate measurement data to the client identified by the connection id.
``` C
static void heart_gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
...
case ESP_GATTS_READ_EVT:
ESP_LOGI(GATTS_TAG, "Characteristic read");
esp_gatt_rsp_t rsp;
memset(&rsp, 0, sizeof(esp_gatt_rsp_t));
rsp.attr_value.handle = param->read.handle;
rsp.attr_value.len = 2;
memcpy(rsp.attr_value.value, heart_rate_val, sizeof(heart_rate_val));
esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, ESP_GATT_OK, &rsp);
break;
case ESP_GATTS_WRITE_EVT:
ESP_LOGI(GATTS_TAG, "Characteristic write, value len %u, value ", param->write.len);
ESP_LOG_BUFFER_HEX(GATTS_TAG, param->write.value, param->write.len);
if (gl_profile_tab[HEART_PROFILE_APP_ID].descr_handle == param->write.handle && param->write.len == 2){
uint16_t descr_value = param->write.value[1]<<8 | param->write.value[0];
if (descr_value == 0x0001){
if (heart_property & ESP_GATT_CHAR_PROP_BIT_NOTIFY){
ESP_LOGI(GATTS_TAG, "Notification enable");
uint8_t notify_data[15];
for (int i = 0; i < sizeof(notify_data); ++i)
{
notify_data[i] = i%0xff;
}
//the size of notify_data[] need less than MTU size
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[HEART_PROFILE_APP_ID].char_handle,
sizeof(notify_data), notify_data, false);
}
}else if (descr_value == 0x0002){
if (heart_property & ESP_GATT_CHAR_PROP_BIT_INDICATE){
ESP_LOGI(GATTS_TAG, "Indication enable");
indicate_enabled = true;
uint8_t indicate_data[15];
for (int i = 0; i < sizeof(indicate_data); ++i)
{
indicate_data[i] = i%0xff;
}
//the size of indicate_data[] need less than MTU size
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[HEART_PROFILE_APP_ID].char_handle,
sizeof(indicate_data), indicate_data, true);
}
}
else if (descr_value == 0x0000) {
indicate_enabled = false;
ESP_LOGI(GATTS_TAG, "Notification/Indication disable");
} else {
ESP_LOGE(GATTS_TAG, "Unknown descriptor value");
ESP_LOG_BUFFER_HEX(GATTS_TAG, param->write.value, param->write.len);
}
}
example_write_event_env(gatts_if, param);
break;
case ESP_GATTS_SET_ATTR_VAL_EVT:
ESP_LOGI(GATTS_TAG, "Attribute value set, status %d", param->set_attr_val.status);
if(indicate_enabled){
uint8_t indicate_data[2] = {0};
memcpy(indicate_data, heart_rate_val, sizeof(heart_rate_val));
esp_ble_gatts_send_indicate(gatts_if, gl_profile_tab[HEART_PROFILE_APP_ID].conn_id, gl_profile_tab[HEART_PROFILE_APP_ID].char_handle, sizeof(indicate_data), indicate_data, true);
}
break;
...
}
```
## Observation
If everything goes well, you should be able to see 4 services when connected to ESP32, including
- Generic Access Service
- Generic Attribute Service
- Heart Rate Service
- Automation IO Service
Click on Automation IO Service, you should be able to see LED characteristic. Click on upload button, you should be able to write `ON` or `OFF` value. Send it to the device, LED will be turned on or off following your instruction.
Click on Heart Rate Service, you should be able to see Heart Rate Measurement characteristic. Click on download button, you should be able to see the latest heart rate measurement mock value, and it should be consistent with what is shown on serial output. Click on subscribe button, you should be able to see the heart rate measurement mock value updated every second.
## Troubleshooting
For any technical queries, please file an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@ -0,0 +1,4 @@
file(GLOB_RECURSE srcs "main.c" "src/*.c")
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "./include")

View File

@ -0,0 +1,54 @@
menu "Example 'GATT SERVER' Config"
config EXAMPLE_SET_RAW_ADV_DATA
bool "Use raw data for advertising packets and scan response data"
help
If this config item is set, raw binary data will be used to generate advertising & scan response data.
This option uses the esp_ble_gap_config_adv_data_raw() and esp_ble_gap_config_scan_rsp_data_raw()
functions.
If this config item is unset, advertising & scan response data is provided via a higher-level
esp_ble_adv_data_t structure. The lower layer will generate the BLE packets. This option has higher
overhead at runtime.
orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps"
choice EXAMPLE_BLINK_LED
prompt "Blink LED type"
default EXAMPLE_BLINK_LED_STRIP
help
Select the LED type. A normal level controlled LED or an addressable LED strip.
The default selection is based on the Espressif DevKit boards.
You can change the default selection according to your board.
config EXAMPLE_BLINK_LED_GPIO
bool "GPIO"
config EXAMPLE_BLINK_LED_STRIP
bool "LED strip"
endchoice
choice EXAMPLE_BLINK_LED_STRIP_BACKEND
depends on EXAMPLE_BLINK_LED_STRIP
prompt "LED strip backend peripheral"
default EXAMPLE_BLINK_LED_STRIP_BACKEND_RMT if SOC_RMT_SUPPORTED
default EXAMPLE_BLINK_LED_STRIP_BACKEND_SPI
help
Select the backend peripheral to drive the LED strip.
config EXAMPLE_BLINK_LED_STRIP_BACKEND_RMT
depends on SOC_RMT_SUPPORTED
bool "RMT"
config EXAMPLE_BLINK_LED_STRIP_BACKEND_SPI
bool "SPI"
endchoice
config EXAMPLE_BLINK_GPIO
int "Blink GPIO number"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 8
help
GPIO number (IOxx) to blink on and off the LED.
Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used to blink.
endmenu

View File

@ -0,0 +1,17 @@
## IDF Component Manager Manifest File
dependencies:
espressif/led_strip: "*"
## Required IDF version
idf:
version: ">=4.1.0"
# # Put list of dependencies here
# # For components maintained by Espressif:
# component: "~1.0.0"
# # For 3rd party components:
# username/component: ">=1.0.0,<2.0.0"
# username2/component2:
# version: "~1.0.0"
# # For transient dependencies `public` flag can be set.
# # `public` flag doesn't have an effect dependencies of the `main` component.
# # All dependencies of `main` are public by default.
# public: true

View File

@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#ifndef HEART_RATE_H
#define HEART_RATE_H
#include <stdint.h>
/* Defines */
#define HEART_RATE_TASK_PERIOD (1000 / portTICK_PERIOD_MS)
/* Public function declarations */
uint8_t get_heart_rate(void);
void update_heart_rate(void);
#endif // HEART_RATE_H

View File

@ -0,0 +1,23 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#ifndef LED_H
#define LED_H
/* Includes */
/* ESP APIs */
#include "driver/gpio.h"
#include "led_strip.h"
/* Defines */
#define BLINK_GPIO CONFIG_EXAMPLE_BLINK_GPIO
/* Public function declarations */
uint8_t get_led_state(void);
void led_on(void);
void led_off(void);
void led_init(void);
#endif // LED_H

View File

@ -0,0 +1,515 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_bt_defs.h"
#include "esp_bt_main.h"
#include "esp_bt_device.h"
#include "esp_gatt_common_api.h"
#include "heart_rate.h"
#include "led.h"
#define PROFILE_NUM 2
#define HEART_PROFILE_APP_ID 0
#define AUTO_IO_PROFILE_APP_ID 1
#define HEART_RATE_SVC_UUID 0x180D
#define HEART_RATE_CHAR_UUID 0x2A37
#define HEART_NUM_HANDLE 4
#define AUTO_IO_SVC_UUID 0x1815
#define AUTO_IO_NUM_HANDLE 3
#define ADV_CONFIG_FLAG (1 << 0)
#define SCAN_RSP_CONFIG_FLAG (1 << 1)
struct gatts_profile_inst {
esp_gatts_cb_t gatts_cb;
uint16_t gatts_if;
uint16_t app_id;
uint16_t conn_id;
uint16_t service_handle;
esp_gatt_srvc_id_t service_id;
uint16_t char_handle;
esp_bt_uuid_t char_uuid;
esp_gatt_perm_t perm;
esp_gatt_char_prop_t property;
uint16_t descr_handle;
esp_bt_uuid_t descr_uuid;
};
///Declare the static function
static void heart_gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
static void auto_io_gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
static void example_write_event_env(esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
static const char *GATTS_TAG = "GATTS_DEMO";
static esp_gatt_char_prop_t heart_property = 0;
static esp_gatt_char_prop_t auto_io_property = 0;
static uint8_t heart_rate_val[2] = {0};
static uint8_t led_status[2] = {0};
static bool indicate_enabled = false;
static bool hrs_create_cmpl = false; // Heart Rate Service
static uint8_t adv_config_done = 0;
static esp_attr_value_t heart_rate_attr = {
.attr_max_len = 2,
.attr_len = sizeof(heart_rate_val),
.attr_value = heart_rate_val,
};
static esp_attr_value_t led_status_attr = {
.attr_max_len = 2,
.attr_len = sizeof(led_status),
.attr_value = led_status,
};
static const uint8_t led_chr_uuid[] = {
0x23, 0xd1, 0xbc, 0xea, 0x5f, 0x78, 0x23, 0x15, 0xde, 0xef, 0x12, 0x12, 0x25, 0x15, 0x00, 0x00
};
static esp_ble_adv_data_t adv_data = {
.set_scan_rsp = false,
.include_name = true,
.include_txpower = false,
.min_interval = 0x0006,
.max_interval = 0x0010,
.appearance = 0x00,
.manufacturer_len = 0,
.p_manufacturer_data = NULL,
.service_data_len = 0,
.p_service_data = NULL,
.service_uuid_len = 0,
.p_service_uuid = NULL,
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};
static esp_ble_adv_params_t adv_params = {
.adv_int_min = 0x20, // 20ms
.adv_int_max = 0x40, // 40ms
.adv_type = ADV_TYPE_IND,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.channel_map = ADV_CHNL_ALL,
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
static struct gatts_profile_inst gl_profile_tab[PROFILE_NUM] = {
[HEART_PROFILE_APP_ID] = {
.gatts_cb = heart_gatts_profile_event_handler,
.gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
},
[AUTO_IO_PROFILE_APP_ID] = {
.gatts_cb = auto_io_gatts_profile_event_handler,
.gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
},
};
static void heart_rate_task(void* param)
{
ESP_LOGI(GATTS_TAG, "Heart Rate Task Start");
while (1) {
if (hrs_create_cmpl) {
update_heart_rate();
ESP_LOGI(GATTS_TAG, "Heart Rate updated to %d", get_heart_rate());
heart_rate_val[1] = get_heart_rate();
esp_ble_gatts_set_attr_value(gl_profile_tab[HEART_PROFILE_APP_ID].char_handle, 2, heart_rate_val);
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
switch (event) {
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
ESP_LOGI(GATTS_TAG, "Advertising data set, status %d", param->adv_data_cmpl.status);
adv_config_done &= (~ADV_CONFIG_FLAG);
if (adv_config_done == 0) {
esp_ble_gap_start_advertising(&adv_params);
}
break;
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
ESP_LOGI(GATTS_TAG, "Scan response data set, status %d", param->scan_rsp_data_cmpl.status);
adv_config_done &= (~SCAN_RSP_CONFIG_FLAG);
if (adv_config_done == 0) {
esp_ble_gap_start_advertising(&adv_params);
}
break;
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(GATTS_TAG, "Advertising start failed, status %d", param->adv_start_cmpl.status);
break;
}
ESP_LOGI(GATTS_TAG, "Advertising start successfully");
break;
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
ESP_LOGI(GATTS_TAG, "Connection params update, status %d, conn_int %d, latency %d, timeout %d",
param->update_conn_params.status,
param->update_conn_params.conn_int,
param->update_conn_params.latency,
param->update_conn_params.timeout);
break;
case ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT:
ESP_LOGI(GATTS_TAG, "Packet length update, status %d, rx %d, tx %d",
param->pkt_data_length_cmpl.status,
param->pkt_data_length_cmpl.params.rx_len,
param->pkt_data_length_cmpl.params.tx_len);
break;
default:
break;
}
}
static void heart_gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
switch (event) {
case ESP_GATTS_REG_EVT:
ESP_LOGI(GATTS_TAG, "GATT server register, status %d, app_id %d", param->reg.status, param->reg.app_id);
gl_profile_tab[HEART_PROFILE_APP_ID].service_id.is_primary = true;
gl_profile_tab[HEART_PROFILE_APP_ID].service_id.id.inst_id = 0x00;
gl_profile_tab[HEART_PROFILE_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[HEART_PROFILE_APP_ID].service_id.id.uuid.uuid.uuid16 = HEART_RATE_SVC_UUID;
//config adv data
esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data);
if (ret) {
ESP_LOGE(GATTS_TAG, "config adv data failed, error code = %x", ret);
break;
}
esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[HEART_PROFILE_APP_ID].service_id, HEART_NUM_HANDLE);
break;
case ESP_GATTS_CREATE_EVT:
//service has been created, now add characteristic declaration
ESP_LOGI(GATTS_TAG, "Service create, status %d, service_handle %d", param->create.status, param->create.service_handle);
gl_profile_tab[HEART_PROFILE_APP_ID].service_handle = param->create.service_handle;
gl_profile_tab[HEART_PROFILE_APP_ID].char_uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[HEART_PROFILE_APP_ID].char_uuid.uuid.uuid16 = HEART_RATE_CHAR_UUID;
esp_ble_gatts_start_service(gl_profile_tab[HEART_PROFILE_APP_ID].service_handle);
heart_property = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_INDICATE;
ret = esp_ble_gatts_add_char(gl_profile_tab[HEART_PROFILE_APP_ID].service_handle, &gl_profile_tab[HEART_PROFILE_APP_ID].char_uuid,
ESP_GATT_PERM_READ,
heart_property,
&heart_rate_attr, NULL);
if (ret) {
ESP_LOGE(GATTS_TAG, "add char failed, error code = %x", ret);
}
break;
case ESP_GATTS_ADD_CHAR_EVT:
ESP_LOGI(GATTS_TAG, "Characteristic add, status %d, attr_handle %d, char_uuid %x",
param->add_char.status, param->add_char.attr_handle, param->add_char.char_uuid.uuid.uuid16);
gl_profile_tab[HEART_PROFILE_APP_ID].char_handle = param->add_char.attr_handle;
gl_profile_tab[HEART_PROFILE_APP_ID].descr_uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[HEART_PROFILE_APP_ID].descr_uuid.uuid.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG;
ESP_LOGI(GATTS_TAG, "heart rate char handle %d", param->add_char.attr_handle);
ret = esp_ble_gatts_add_char_descr(gl_profile_tab[HEART_PROFILE_APP_ID].service_handle, &gl_profile_tab[HEART_PROFILE_APP_ID].descr_uuid,
ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, NULL, NULL);
break;
case ESP_GATTS_ADD_CHAR_DESCR_EVT:
ESP_LOGI(GATTS_TAG, "Descriptor add, status %d, attr_handle %u",
param->add_char_descr.status, param->add_char_descr.attr_handle);
gl_profile_tab[HEART_PROFILE_APP_ID].descr_handle = param->add_char_descr.attr_handle;
hrs_create_cmpl = true;
break;
case ESP_GATTS_READ_EVT:
ESP_LOGI(GATTS_TAG, "Characteristic read");
esp_gatt_rsp_t rsp;
memset(&rsp, 0, sizeof(esp_gatt_rsp_t));
rsp.attr_value.handle = param->read.handle;
rsp.attr_value.len = 2;
memcpy(rsp.attr_value.value, heart_rate_val, sizeof(heart_rate_val));
esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, ESP_GATT_OK, &rsp);
break;
case ESP_GATTS_WRITE_EVT:
ESP_LOGI(GATTS_TAG, "Characteristic write, value len %u, value ", param->write.len);
ESP_LOG_BUFFER_HEX(GATTS_TAG, param->write.value, param->write.len);
if (gl_profile_tab[HEART_PROFILE_APP_ID].descr_handle == param->write.handle && param->write.len == 2) {
uint16_t descr_value = param->write.value[1]<<8 | param->write.value[0];
if (descr_value == 0x0001) {
if (heart_property & ESP_GATT_CHAR_PROP_BIT_NOTIFY) {
ESP_LOGI(GATTS_TAG, "Notification enable");
uint8_t notify_data[15];
for (int i = 0; i < sizeof(notify_data); i++) {
notify_data[i] = i%0xff;
}
//the size of notify_data[] need less than MTU size
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[HEART_PROFILE_APP_ID].char_handle,
sizeof(notify_data), notify_data, false);
}
} else if (descr_value == 0x0002) {
if (heart_property & ESP_GATT_CHAR_PROP_BIT_INDICATE) {
ESP_LOGI(GATTS_TAG, "Indication enable");
indicate_enabled = true;
uint8_t indicate_data[15];
for (int i = 0; i < sizeof(indicate_data); i++) {
indicate_data[i] = i%0xff;
}
//the size of indicate_data[] need less than MTU size
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[HEART_PROFILE_APP_ID].char_handle,
sizeof(indicate_data), indicate_data, true);
}
} else if (descr_value == 0x0000) {
indicate_enabled = false;
ESP_LOGI(GATTS_TAG, "Notification/Indication disable");
} else {
ESP_LOGE(GATTS_TAG, "Invalid descriptor value");
ESP_LOG_BUFFER_HEX(GATTS_TAG, param->write.value, param->write.len);
}
}
example_write_event_env(gatts_if, param);
break;
case ESP_GATTS_DELETE_EVT:
break;
case ESP_GATTS_START_EVT:
ESP_LOGI(GATTS_TAG, "Service start, status %d, service_handle %d", param->start.status, param->start.service_handle);
break;
case ESP_GATTS_STOP_EVT:
break;
case ESP_GATTS_CONNECT_EVT:
ESP_LOGI(GATTS_TAG, "Connected, conn_id %u, remote "ESP_BD_ADDR_STR"",
param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda));
gl_profile_tab[HEART_PROFILE_APP_ID].conn_id = param->connect.conn_id;
break;
case ESP_GATTS_DISCONNECT_EVT:
ESP_LOGI(GATTS_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x",
ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason);
indicate_enabled = false;
esp_ble_gap_start_advertising(&adv_params);
break;
case ESP_GATTS_CONF_EVT:
ESP_LOGI(GATTS_TAG, "Confirm receive, status %d, attr_handle %d", param->conf.status, param->conf.handle);
if (param->conf.status != ESP_GATT_OK) {
ESP_LOG_BUFFER_HEX(GATTS_TAG, param->conf.value, param->conf.len);
}
break;
case ESP_GATTS_SET_ATTR_VAL_EVT:
ESP_LOGI(GATTS_TAG, "Attribute value set, status %d", param->set_attr_val.status);
if (indicate_enabled) {
uint8_t indicate_data[2] = {0};
memcpy(indicate_data, heart_rate_val, sizeof(heart_rate_val));
esp_ble_gatts_send_indicate(gatts_if, gl_profile_tab[HEART_PROFILE_APP_ID].conn_id, gl_profile_tab[HEART_PROFILE_APP_ID].char_handle, sizeof(indicate_data), indicate_data, true);
}
break;
default:
break;
}
}
static void auto_io_gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
switch (event) {
case ESP_GATTS_REG_EVT:
ESP_LOGI(GATTS_TAG, "GATT server register, status %d, app_id %d", param->reg.status, param->reg.app_id);
gl_profile_tab[AUTO_IO_PROFILE_APP_ID].service_id.is_primary = true;
gl_profile_tab[AUTO_IO_PROFILE_APP_ID].service_id.id.inst_id = 0x00;
gl_profile_tab[AUTO_IO_PROFILE_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[AUTO_IO_PROFILE_APP_ID].service_id.id.uuid.uuid.uuid16 = AUTO_IO_SVC_UUID;
esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[AUTO_IO_PROFILE_APP_ID].service_id, AUTO_IO_NUM_HANDLE);
break;
case ESP_GATTS_CREATE_EVT:
//service has been created, now add characteristic declaration
ESP_LOGI(GATTS_TAG, "Service create, status %d, service_handle %d", param->create.status, param->create.service_handle);
gl_profile_tab[AUTO_IO_PROFILE_APP_ID].service_handle = param->create.service_handle;
gl_profile_tab[AUTO_IO_PROFILE_APP_ID].char_uuid.len = ESP_UUID_LEN_128;
memcpy(gl_profile_tab[AUTO_IO_PROFILE_APP_ID].char_uuid.uuid.uuid128, led_chr_uuid, ESP_UUID_LEN_128);
esp_ble_gatts_start_service(gl_profile_tab[AUTO_IO_PROFILE_APP_ID].service_handle);
auto_io_property = ESP_GATT_CHAR_PROP_BIT_WRITE ;
esp_err_t ret = esp_ble_gatts_add_char(gl_profile_tab[AUTO_IO_PROFILE_APP_ID].service_handle, &gl_profile_tab[AUTO_IO_PROFILE_APP_ID].char_uuid,
ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE ,
auto_io_property,
&led_status_attr, NULL);
if (ret) {
ESP_LOGE(GATTS_TAG, "add char failed, error code = %x", ret);
}
break;
case ESP_GATTS_ADD_CHAR_EVT:
ESP_LOGI(GATTS_TAG, "Characteristic add, status %d, attr_handle %d, char_uuid %x",
param->add_char.status, param->add_char.attr_handle, param->add_char.char_uuid.uuid.uuid16);
gl_profile_tab[AUTO_IO_PROFILE_APP_ID].char_handle = param->add_char.attr_handle;
break;
case ESP_GATTS_ADD_CHAR_DESCR_EVT:
ESP_LOGI(GATTS_TAG, "Descriptor add, status %d", param->add_char_descr.status);
gl_profile_tab[AUTO_IO_PROFILE_APP_ID].descr_handle = param->add_char_descr.attr_handle;
break;
case ESP_GATTS_READ_EVT:
ESP_LOGI(GATTS_TAG, "Characteristic read");
esp_gatt_rsp_t rsp;
memset(&rsp, 0, sizeof(esp_gatt_rsp_t));
rsp.attr_value.handle = param->read.handle;
rsp.attr_value.len = 1;
rsp.attr_value.value[0] = 0x02;
esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, ESP_GATT_OK, &rsp);
break;
case ESP_GATTS_WRITE_EVT:
ESP_LOGI(GATTS_TAG, "Characteristic write, value len %u, value ", param->write.len);
ESP_LOG_BUFFER_HEX(GATTS_TAG, param->write.value, param->write.len);
if (param->write.value[0]) {
ESP_LOGI(GATTS_TAG, "LED ON!");
led_on();
} else {
ESP_LOGI(GATTS_TAG, "LED OFF!");
led_off();
}
example_write_event_env(gatts_if, param);
break;
case ESP_GATTS_DELETE_EVT:
break;
case ESP_GATTS_START_EVT:
ESP_LOGI(GATTS_TAG, "Service start, status %d, service_handle %d", param->start.status, param->start.service_handle);
break;
case ESP_GATTS_STOP_EVT:
break;
case ESP_GATTS_CONNECT_EVT:
esp_ble_conn_update_params_t conn_params = {0};
memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
conn_params.latency = 0;
conn_params.max_int = 0x20;
conn_params.min_int = 0x10;
conn_params.timeout = 400;
ESP_LOGI(GATTS_TAG, "Connected, conn_id %u, remote "ESP_BD_ADDR_STR"",
param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda));
gl_profile_tab[AUTO_IO_PROFILE_APP_ID].conn_id = param->connect.conn_id;
esp_ble_gap_update_conn_params(&conn_params);
break;
case ESP_GATTS_DISCONNECT_EVT:
ESP_LOGI(GATTS_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x",
ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason);
break;
case ESP_GATTS_CONF_EVT:
ESP_LOGI(GATTS_TAG, "Confirm receive, status %d, attr_handle %d", param->conf.status, param->conf.handle);
if (param->conf.status != ESP_GATT_OK) {
ESP_LOG_BUFFER_HEX(GATTS_TAG, param->conf.value, param->conf.len);
}
break;
default:
break;
}
}
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
if (event == ESP_GATTS_REG_EVT) {
if (param->reg.status == ESP_GATT_OK) {
gl_profile_tab[param->reg.app_id].gatts_if = gatts_if;
} else {
ESP_LOGI(GATTS_TAG, "Reg app failed, app_id %04x, status %d",
param->reg.app_id,
param->reg.status);
return;
}
}
//gatts_if registered complete, call cb handlers
do {
int idx;
for (idx = 0; idx < PROFILE_NUM; idx++) {
if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
gatts_if == gl_profile_tab[idx].gatts_if) {
if (gl_profile_tab[idx].gatts_cb) {
gl_profile_tab[idx].gatts_cb(event, gatts_if, param);
}
}
}
} while(0);
}
void app_main(void)
{
esp_err_t ret;
led_init();
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);
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret) {
ESP_LOGE(GATTS_TAG, "%s initialize controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(GATTS_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bluedroid_init();
if (ret) {
ESP_LOGE(GATTS_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(GATTS_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_ble_gap_register_callback(gap_event_handler);
if (ret) {
ESP_LOGE(GATTS_TAG, "gap register error, error code = %x", ret);
return;
}
ret = esp_ble_gatts_register_callback(gatts_event_handler);
if (ret) {
ESP_LOGE(GATTS_TAG, "gatts register error, error code = %x", ret);
return;
}
ret = esp_ble_gatts_app_register(HEART_PROFILE_APP_ID);
if (ret) {
ESP_LOGE(GATTS_TAG, "app register error, error code = %x", ret);
return;
}
ret = esp_ble_gatts_app_register(AUTO_IO_PROFILE_APP_ID);
if (ret) {
ESP_LOGE(GATTS_TAG, "app register error, error code = %x", ret);
return;
}
ret = esp_ble_gatt_set_local_mtu(500);
if (ret) {
ESP_LOGE(GATTS_TAG, "set local MTU failed, error code = %x", ret);
}
xTaskCreate(heart_rate_task, "Heart Rate", 2 * 1024, NULL, 5, NULL);
}
void example_write_event_env(esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
esp_gatt_status_t status = ESP_GATT_OK;
if (param->write.need_rsp) {
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, NULL);
}
}

View File

@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* Includes */
#include "common.h"
#include "heart_rate.h"
#include "esp_random.h"
/* Private variables */
static uint8_t heart_rate;
/* Public functions */
uint8_t get_heart_rate(void) { return heart_rate; }
void update_heart_rate(void) { heart_rate = 60 + (uint8_t)(esp_random() % 21); }

View File

@ -0,0 +1,98 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* Includes */
#include "led.h"
#include "common.h"
/* Private variables */
static uint8_t led_state;
#ifdef CONFIG_EXAMPLE_BLINK_LED_STRIP
static led_strip_handle_t led_strip;
#endif
/* Public functions */
uint8_t get_led_state(void)
{
return led_state;
}
#ifdef CONFIG_EXAMPLE_BLINK_LED_STRIP
void led_on(void)
{
/* Set the LED pixel using RGB from 0 (0%) to 255 (100%) for each color */
led_strip_set_pixel(led_strip, 0, 16, 16, 16);
/* Refresh the strip to send data */
led_strip_refresh(led_strip);
/* Update LED state */
led_state = true;
}
void led_off(void)
{
/* Set all LED off to clear all pixels */
led_strip_clear(led_strip);
/* Update LED state */
led_state = false;
}
void led_init(void)
{
// ESP_LOGI(TAG, "example configured to blink addressable led!");
/* LED strip initialization with the GPIO and pixels number*/
led_strip_config_t strip_config = {
.strip_gpio_num = CONFIG_EXAMPLE_BLINK_GPIO,
.max_leds = 1, // at least one LED on board
};
#if CONFIG_EXAMPLE_BLINK_LED_STRIP_BACKEND_RMT
led_strip_rmt_config_t rmt_config = {
.resolution_hz = 10 * 1000 * 1000, // 10MHz
.flags.with_dma = false,
};
ESP_ERROR_CHECK(
led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip));
#elif CONFIG_EXAMPLE_BLINK_LED_STRIP_BACKEND_SPI
led_strip_spi_config_t spi_config = {
.spi_bus = SPI2_HOST,
.flags.with_dma = true,
};
ESP_ERROR_CHECK(
led_strip_new_spi_device(&strip_config, &spi_config, &led_strip));
#else
#error "unsupported LED strip backend"
#endif
/* Set all LED off to clear all pixels */
led_off();
}
#elif CONFIG_EXAMPLE_BLINK_LED_GPIO
void led_on(void)
{
gpio_set_level(CONFIG_EXAMPLE_BLINK_GPIO, false);
}
void led_off(void)
{
gpio_set_level(CONFIG_EXAMPLE_BLINK_GPIO, true);
}
void led_init(void)
{
// ESP_LOGI(TAG, "example configured to blink gpio led!");
gpio_reset_pin(CONFIG_EXAMPLE_BLINK_GPIO);
/* Set the GPIO as a push/pull output */
gpio_set_direction(CONFIG_EXAMPLE_BLINK_GPIO, GPIO_MODE_OUTPUT);
gpio_set_level(CONFIG_EXAMPLE_BLINK_GPIO, 1);
}
#else
#error "unsupported LED type"
#endif

View File

@ -0,0 +1,8 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_BT_ENABLED=y
CONFIG_BT_BLE_50_FEATURES_SUPPORTED=n
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
# CONFIG_BT_LE_50_FEATURE_SUPPORT is not used on ESP32, ESP32-C3 and ESP32-S3.
# CONFIG_BT_LE_50_FEATURE_SUPPORT is not set

View File

@ -0,0 +1,2 @@
CONFIG_EXAMPLE_BLINK_GPIO=5
CONFIG_EXAMPLE_BLINK_LED_STRIP=n

View File

@ -0,0 +1,12 @@
# Override some defaults so BT stack is enabled
# by default in this example
CONFIG_IDF_TARGET="esp32c2"
CONFIG_BT_ENABLED=y
# CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
# CONFIG_BT_LE_50_FEATURE_SUPPORT is not set
CONFIG_BT_LE_HCI_EVT_BUF_SIZE=257
CONFIG_XTAL_FREQ_26=y
CONFIG_EXAMPLE_BLINK_LED_STRIP=n
CONFIG_EXAMPLE_BLINK_LED_GPIO=y
CONFIG_EXAMPLE_BLINK_GPIO=8

View File

@ -0,0 +1,7 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_IDF_TARGET="esp32c3"
CONFIG_BT_ENABLED=y
# CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y

View File

@ -0,0 +1,10 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_IDF_TARGET="esp32s3"
CONFIG_BT_ENABLED=y
# CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
CONFIG_EXAMPLE_BLINK_LED_STRIP=y
CONFIG_EXAMPLE_BLINK_LED_GPIO=n
CONFIG_EXAMPLE_BLINK_GPIO=38