fix(examples): improve bluedroid BLE example error handling

Check GAP/GATT event status, fix prepare-write buffer lifecycle, and
align example code with updated Bluedroid API behavior.
This commit is contained in:
zhiweijian
2026-06-12 10:48:23 +08:00
parent 2c42954274
commit 47a74cc4b3
48 changed files with 1581 additions and 706 deletions
@@ -46,6 +46,7 @@ typedef struct {
/* Latency test functions */
void latency_test_init(uint16_t conn_id, uint16_t char_handle);
void latency_test_start(void);
void latency_test_stop(void);
void latency_test_handle_notify(uint8_t *data, uint16_t len);
void latency_test_print_results(void);
@@ -132,7 +132,7 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
break;
}
ESP_LOGI(GATTC_TAG, "discover service complete conn_id %d", param->dis_srvc_cmpl.conn_id);
esp_ble_gattc_search_service(gattc_if, param->cfg_mtu.conn_id, &remote_filter_service_uuid);
esp_ble_gattc_search_service(gattc_if, param->dis_srvc_cmpl.conn_id, &remote_filter_service_uuid);
break;
case ESP_GATTC_CFG_MTU_EVT:
if (param->cfg_mtu.status != ESP_GATT_OK){
@@ -311,6 +311,7 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
case ESP_GATTC_DISCONNECT_EVT:
connect = false;
get_server = false;
latency_test_stop();
ESP_LOGI(GATTC_TAG, "ESP_GATTC_DISCONNECT_EVT, reason = %d", p_data->disconnect.reason);
break;
default:
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -22,9 +22,10 @@ static latency_record_t records[TEST_PACKET_COUNT];
static uint16_t test_conn_id = 0;
static uint16_t char_handle = 0;
static test_packet_t send_packets[TEST_PACKET_COUNT];
static bool test_running = false;
/* Polled by latency_test_task and written by latency_test_stop() from another context. */
static volatile bool test_running = false;
static bool test_initialized = false;
static esp_gatt_if_t test_gattc_if = 0;
static esp_gatt_if_t test_gattc_if = ESP_GATT_IF_NONE;
/**
* Fill random data
@@ -45,7 +46,6 @@ latency_test_init(uint16_t conn_id, uint16_t handle)
{
test_conn_id = conn_id;
char_handle = handle;
test_running = false;
test_initialized = true;
memset(records, 0, sizeof(records));
@@ -69,7 +69,7 @@ latency_test_set_gattc_if(esp_gatt_if_t gattc_if)
static int
send_test_packet(uint16_t seq)
{
if (char_handle == 0 || test_gattc_if == 0) {
if (char_handle == 0 || test_gattc_if == ESP_GATT_IF_NONE) {
ESP_LOGE(TAG, "Test not initialized");
return -1;
}
@@ -113,21 +113,19 @@ latency_test_task(void *arg)
test_running = true;
/* Send all test packets */
for (int i = 0; i < TEST_PACKET_COUNT; i++) {
/* Send all test packets, abort early if latency_test_stop() is called */
for (int i = 0; i < TEST_PACKET_COUNT && test_running; i++) {
int rc = send_test_packet(i);
if (rc != 0) {
ESP_LOGW(TAG, "Send failed for seq=%d", i);
}
/* Wait interval */
vTaskDelay(pdMS_TO_TICKS(TEST_PACKET_INTERVAL_MS));
}
ESP_LOGI(TAG, "All packets sent, waiting for responses...");
/* Wait for all responses */
vTaskDelay(pdMS_TO_TICKS(2000));
if (test_running) {
ESP_LOGI(TAG, "All packets sent, waiting for responses...");
vTaskDelay(pdMS_TO_TICKS(2000));
}
/* Print results */
latency_test_print_results();
@@ -152,9 +150,26 @@ latency_test_start(void)
return;
}
if (test_gattc_if == ESP_GATT_IF_NONE) {
ESP_LOGE(TAG, "GATT client interface not set");
return;
}
xTaskCreate(latency_test_task, "latency_test", 4096, NULL, 5, NULL);
}
/**
* Stop latency test
*
* Only raises the stop flag; the task observes it at the next loop iteration
* (or vTaskDelay boundary) and self-terminates via vTaskDelete(NULL).
*/
void
latency_test_stop(void)
{
test_running = false;
}
/**
* Handle notification
*/
@@ -189,6 +204,11 @@ latency_test_handle_notify(uint8_t *data, uint16_t len)
return;
}
if (records[seq].received) {
ESP_LOGD(TAG, "Duplicate notify for seq=%d, ignored", seq);
return;
}
/* Record receive time */
records[seq].recv_time_us = recv_time;
records[seq].received = true;
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -7,6 +7,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
@@ -18,6 +19,7 @@
#include "esp_gatts_api.h"
#include "esp_bt_main.h"
#include "esp_gatt_common_api.h"
#include "esp_gatt_defs.h"
#include "gatts_latency.h"
#define GATTS_TAG "BLE_ACL_LATENCY_PERIPH"
@@ -191,16 +193,18 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
}
esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data);
if (ret){
if (ret != ESP_OK) {
ESP_LOGE(GATTS_TAG, "Config adv data failed, error code = %x", ret);
} else {
adv_config_done |= adv_config_flag;
}
adv_config_done |= adv_config_flag;
ret = esp_ble_gap_config_adv_data(&scan_rsp_data);
if (ret){
if (ret != ESP_OK) {
ESP_LOGE(GATTS_TAG, "Config scan response data failed, error code = %x", ret);
} else {
adv_config_done |= scan_rsp_config_flag;
}
adv_config_done |= scan_rsp_config_flag;
esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, LATENCY_IDX_NB, 0);
if (create_attr_ret){
@@ -209,7 +213,41 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
}
break;
case ESP_GATTS_READ_EVT:
ESP_LOGD(GATTS_TAG, "ESP_GATTS_READ_EVT");
ESP_LOGD(GATTS_TAG, "ESP_GATTS_READ_EVT, handle=%d, need_rsp=%d", param->read.handle, param->read.need_rsp);
if (!param->read.need_rsp) {
break;
}
{
esp_gatt_rsp_t rsp;
memset(&rsp, 0, sizeof(rsp));
rsp.attr_value.handle = param->read.handle;
rsp.attr_value.offset = param->read.offset;
uint16_t vlen = 0;
const uint8_t *v = NULL;
esp_gatt_status_t gst = esp_ble_gatts_get_attr_value(param->read.handle, &vlen, &v);
if (gst != ESP_GATT_OK || v == NULL) {
rsp.attr_value.len = 0;
esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id,
ESP_GATT_ERROR, &rsp);
break;
}
if (param->read.offset > vlen) {
rsp.attr_value.len = 0;
esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id,
ESP_GATT_INVALID_OFFSET, &rsp);
break;
}
uint16_t remain = vlen - param->read.offset;
uint16_t copy_len = remain > ESP_GATT_MAX_ATTR_LEN ? ESP_GATT_MAX_ATTR_LEN : remain;
if (copy_len > 0) {
memcpy(rsp.attr_value.value, v + param->read.offset, copy_len);
}
rsp.attr_value.len = copy_len;
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:
if (!param->write.is_prep){
@@ -220,9 +258,35 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, handle_table[LATENCY_IDX_CHAR_VAL],
param->write.len, param->write.value, false);
}
if (param->write.need_rsp) {
esp_err_t sr = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id,
ESP_GATT_OK, NULL);
if (sr != ESP_OK) {
ESP_LOGE(GATTS_TAG, "send_response failed: %s", esp_err_to_name(sr));
}
}
} else {
/* This latency demo does not support Prepare/Long/Reliable Write.
* Reply with ATT Error 0x06 (Request Not Supported) instead of leaving
* the request unanswered, otherwise the peer would hit an ATT timeout.
*/
ESP_LOGW(GATTS_TAG, "Prepare write not supported, handle=%d, offset=%d, len=%d",
param->write.handle, param->write.offset, param->write.len);
if (param->write.need_rsp) {
esp_err_t sr = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id,
ESP_GATT_REQ_NOT_SUPPORTED, NULL);
if (sr != ESP_OK) {
ESP_LOGE(GATTS_TAG, "send_response (prep reject) failed: %s", esp_err_to_name(sr));
}
}
}
break;
case ESP_GATTS_EXEC_WRITE_EVT:
ESP_LOGD(GATTS_TAG, "ESP_GATTS_EXEC_WRITE_EVT, conn_id=%d, trans_id=%" PRIu32 ", flag=0x%02x",
param->exec_write.conn_id, param->exec_write.trans_id, param->exec_write.exec_write_flag);
esp_ble_gatts_send_response(gatts_if, param->exec_write.conn_id, param->exec_write.trans_id,
ESP_GATT_OK, NULL);
break;
case ESP_GATTS_MTU_EVT:
ESP_LOGI(GATTS_TAG, "MTU exchange, MTU=%d", param->mtu.mtu);
break;
@@ -29,9 +29,9 @@ A GATT notification delivered through the Notification Source characteristic con
Control Point characteristic to interact with the iOS notification.
*/
char *EventID_to_String(uint8_t EventID)
const char *EventID_to_String(uint8_t EventID)
{
char *str = NULL;
const char *str = NULL;
switch (EventID)
{
case EventIDNotificationAdded:
@@ -50,9 +50,9 @@ char *EventID_to_String(uint8_t EventID)
return str;
}
char *CategoryID_to_String(uint8_t CategoryID)
const char *CategoryID_to_String(uint8_t CategoryID)
{
char *Cidstr = NULL;
const char *Cidstr = NULL;
switch(CategoryID) {
case CategoryIDOther:
Cidstr = "Other";
@@ -103,17 +103,20 @@ char *CategoryID_to_String(uint8_t CategoryID)
void esp_receive_apple_notification_source(uint8_t *message, uint16_t message_len)
{
if (!message || message_len < 5) {
if (!message || message_len < 8) {
return;
}
uint8_t EventID = message[0];
char *EventIDS = EventID_to_String(EventID);
const char *EventIDS = EventID_to_String(EventID);
uint8_t EventFlags = message[1];
uint8_t CategoryID = message[2];
char *Cidstr = CategoryID_to_String(CategoryID);
const char *Cidstr = CategoryID_to_String(CategoryID);
uint8_t CategoryCount = message[3];
uint32_t NotificationUID = (message[4]) | (message[5]<< 8) | (message[6]<< 16) | (message[7] << 24);
uint32_t NotificationUID = (uint32_t)message[4]
| ((uint32_t)message[5] << 8)
| ((uint32_t)message[6] << 16)
| ((uint32_t)message[7] << 24);
ESP_LOGI(BLE_ANCS_TAG, "EventID:%s EventFlags:0x%x CategoryID:%s CategoryCount:%d NotificationUID:%" PRIu32, EventIDS, EventFlags, Cidstr, CategoryCount, NotificationUID);
}
@@ -132,7 +135,10 @@ void esp_receive_apple_data_source(uint8_t *message, uint16_t message_len)
ESP_LOGE(BLE_ANCS_TAG, "Message too short for NotificationAttributes");
break;
}
uint32_t NotificationUID = (message[1]) | (message[2]<< 8) | (message[3]<< 16) | (message[4] << 24);
uint32_t NotificationUID = (uint32_t)message[1]
| ((uint32_t)message[2] << 8)
| ((uint32_t)message[3] << 16)
| ((uint32_t)message[4] << 24);
uint32_t remian_attr_len = message_len - 5;
uint8_t *attrs = &message[5];
ESP_LOGI(BLE_ANCS_TAG, "recevice Notification Attributes response Command_id %d NotificationUID %" PRIu32, Command_id, NotificationUID);
@@ -143,7 +149,7 @@ void esp_receive_apple_data_source(uint8_t *message, uint16_t message_len)
break;
}
uint8_t AttributeID = attrs[0];
uint16_t len = attrs[1] | (attrs[2] << 8);
uint16_t len = (uint16_t)attrs[1] | ((uint16_t)attrs[2] << 8);
if(len > (remian_attr_len - 3)) {
ESP_LOGE(BLE_ANCS_TAG, "data error");
break;
@@ -198,9 +204,9 @@ void esp_receive_apple_data_source(uint8_t *message, uint16_t message_len)
}
}
char *Errcode_to_String(uint16_t status)
const char *Errcode_to_String(uint16_t status)
{
char *Errstr = NULL;
const char *Errstr = NULL;
switch (status) {
case Unknown_command:
Errstr = "Unknown_command";
@@ -214,6 +220,9 @@ char *Errcode_to_String(uint16_t status)
case Action_failed:
Errstr = "Action_failed";
break;
case Internal_error:
Errstr = "Internal_error";
break;
default:
Errstr = "unknown_failed";
break;
@@ -95,6 +95,7 @@ typedef enum {
Invalid_command = (0xA1), //The command was improperly formatted.
Invalid_parameter = (0xA2), // One of the parameters (for example, the NotificationUID) does not refer to an existing object on the NP.
Action_failed = (0xA3), //The action was not performed
Internal_error = (0xA4), //An internal error occurred on the NP (Apple ANCS specification).
} esp_error_code;
typedef enum {
@@ -111,8 +112,8 @@ typedef enum {
#define ESP_NOTIFICATIONUID_LEN 4
char *EventID_to_String(uint8_t EventID);
char *CategoryID_to_String(uint8_t CategoryID);
const char *EventID_to_String(uint8_t EventID);
const char *CategoryID_to_String(uint8_t CategoryID);
void esp_receive_apple_notification_source(uint8_t *message, uint16_t message_len);
void esp_receive_apple_data_source(uint8_t *message, uint16_t message_len);
char *Errcode_to_String(uint16_t status);
const char *Errcode_to_String(uint16_t status);
@@ -8,6 +8,7 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "freertos/semphr.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
@@ -51,6 +52,7 @@ struct data_source_buffer {
};
static struct data_source_buffer data_buffer = {0};
static SemaphoreHandle_t data_buffer_mux;
//In its basic form, the ANCS exposes three characteristics:
// service UUID: 7905F431-B5CE-4E99-A40F-4B1E122D00D0
@@ -189,8 +191,8 @@ void esp_get_notification_attributes(uint8_t *notificationUID, uint8_t num_attr,
ESP_LOGE(BLE_ANCS_TAG, "Command buffer overflow in get_notification_attributes");
return;
}
cmd[index ++] = p_attr->attribute_len;
cmd[index ++] = (p_attr->attribute_len << 8);
cmd[index ++] = p_attr->attribute_len & 0xFF;
cmd[index ++] = (p_attr->attribute_len >> 8) & 0xFF;
}
p_attr ++;
num_attr --;
@@ -253,12 +255,23 @@ void esp_perform_notification_action(uint8_t *notificationUID, uint8_t ActionID)
static void periodic_timer_callback(void* arg)
{
esp_timer_stop(periodic_timer);
if (data_buffer_mux == NULL) {
return;
}
if (xSemaphoreTake(data_buffer_mux, 0) != pdTRUE) {
/* Mutex held by GATT handler; retry soon without blocking esp_timer task */
esp_err_t tr = esp_timer_start_once(periodic_timer, 50000);
if (tr != ESP_OK) {
ESP_LOGE(BLE_ANCS_TAG, "Data source idle timer retry failed: %s", esp_err_to_name(tr));
}
return;
}
if (data_buffer.len > 0) {
esp_receive_apple_data_source(data_buffer.buffer, data_buffer.len);
memset(data_buffer.buffer, 0, data_buffer.len);
data_buffer.len = 0;
}
xSemaphoreGive(data_buffer_mux);
}
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
@@ -525,6 +538,10 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
case ESP_GATTC_NOTIFY_EVT:
//esp_log_buffer_hex(BLE_ANCS_TAG, param->notify.value, param->notify.value_len);
if (param->notify.handle == gl_profile_tab[PROFILE_A_APP_ID].notification_source_handle) {
if (!param->notify.value || param->notify.value_len < 8) {
ESP_LOGW(BLE_ANCS_TAG, "Notification source too short (%u), need 8 bytes", param->notify.value_len);
break;
}
esp_receive_apple_notification_source(param->notify.value, param->notify.value_len);
uint8_t *notificationUID = &param->notify.value[4];
if (param->notify.value[0] == EventIDNotificationAdded && param->notify.value[2] == CategoryIDIncomingCall) {
@@ -537,23 +554,35 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
esp_get_notification_attributes(notificationUID, sizeof(p_attr)/sizeof(esp_noti_attr_list_t), p_attr);
}
} else if (param->notify.handle == gl_profile_tab[PROFILE_A_APP_ID].data_source_handle) {
if (data_buffer_mux == NULL) {
break;
}
if (xSemaphoreTake(data_buffer_mux, portMAX_DELAY) != pdTRUE) {
break;
}
if ((data_buffer.len + param->notify.value_len) > sizeof(data_buffer.buffer)) {
ESP_LOGE(BLE_ANCS_TAG, "Data source buffer overflow detected, discarding data");
memset(data_buffer.buffer, 0, sizeof(data_buffer.buffer));
data_buffer.len = 0;
xSemaphoreGive(data_buffer_mux);
break;
}
memcpy(&data_buffer.buffer[data_buffer.len], param->notify.value, param->notify.value_len);
data_buffer.len += param->notify.value_len;
if (param->notify.value_len == (gl_profile_tab[PROFILE_A_APP_ID].MTU_size - 3)) {
// cpoy and wait next packet, start timer 500ms
esp_timer_start_periodic(periodic_timer, 500000);
/* Retriggerable idle timeout: stop then one-shot so each full fragment resets 500 ms */
esp_timer_stop(periodic_timer);
esp_err_t tr = esp_timer_start_once(periodic_timer, 500000);
if (tr != ESP_OK) {
ESP_LOGE(BLE_ANCS_TAG, "Data source idle timer start failed: %s", esp_err_to_name(tr));
}
} else {
esp_timer_stop(periodic_timer);
esp_receive_apple_data_source(data_buffer.buffer, data_buffer.len);
memset(data_buffer.buffer, 0, data_buffer.len);
data_buffer.len = 0;
}
xSemaphoreGive(data_buffer_mux);
} else {
ESP_LOGI(BLE_ANCS_TAG, "unknown handle, receive notify value:");
}
@@ -572,7 +601,7 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
}
case ESP_GATTC_WRITE_CHAR_EVT:
if (param->write.status != ESP_GATT_OK) {
char *Errstr = Errcode_to_String(param->write.status);
const char *Errstr = Errcode_to_String(param->write.status);
if (Errstr) {
ESP_LOGE(BLE_ANCS_TAG, "write control point error %s", Errstr);
}
@@ -582,6 +611,18 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
break;
case ESP_GATTC_DISCONNECT_EVT:
ESP_LOGI(BLE_ANCS_TAG, "ESP_GATTC_DISCONNECT_EVT, reason = 0x%x", param->disconnect.reason);
if (data_buffer_mux != NULL && xSemaphoreTake(data_buffer_mux, portMAX_DELAY) == pdTRUE) {
esp_timer_stop(periodic_timer);
memset(data_buffer.buffer, 0, sizeof(data_buffer.buffer));
data_buffer.len = 0;
xSemaphoreGive(data_buffer_mux);
} else {
/* Mutex unavailable: only stop the timer (esp_timer_stop is thread-safe).
* Skip buffer reset to avoid an unsynchronized write that could race with
* NOTIFY_EVT / periodic_timer_callback if the mutex were ever held elsewhere. */
ESP_LOGE(BLE_ANCS_TAG, "data_buffer_mux unavailable on disconnect, skip buffer reset");
esp_timer_stop(periodic_timer);
}
get_service = false;
esp_ble_gap_start_advertising(&adv_params);
break;
@@ -640,6 +681,8 @@ static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp
void init_timer(void)
{
ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer));
data_buffer_mux = xSemaphoreCreateMutex();
ESP_ERROR_CHECK(data_buffer_mux != NULL ? ESP_OK : ESP_ERR_NO_MEM);
}
void app_main(void)
@@ -7,6 +7,7 @@
/********************************************************************************
*
* This file is for gatt server. It can send adv data, and get connected by client.
* Only one BLE ACL connection is supported (see sdkconfig.defaults: CONFIG_BT_ACL_CONNECTIONS=1).
*
*********************************************************************************/
@@ -64,6 +65,7 @@ typedef struct {
int prepare_len;
} prepare_type_env_t;
/* This demo targets one connected client; a single prepare-write buffer is enough. */
static prepare_type_env_t prepare_write_env;
//#define CONFIG_SET_RAW_ADV_DATA
@@ -440,7 +442,11 @@ void example_prepare_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t
memcpy(prepare_write_env->prepare_buf + param->write.offset,
param->write.value,
param->write.len);
prepare_write_env->prepare_len += param->write.len;
/* Extent of prepared value: max(offset+len), not sum(len) — overlaps/retries must not inflate length. */
uint32_t span_end = (uint32_t)param->write.offset + (uint32_t)param->write.len;
if (span_end > (uint32_t)prepare_write_env->prepare_len) {
prepare_write_env->prepare_len = (int)span_end;
}
}
uint8_t long_write[16] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
@@ -561,7 +567,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
break;
case ESP_GATTS_EXEC_WRITE_EVT:
// the length of gattc prepare write data must be less than GATTS_EXAMPLE_CHAR_VAL_LEN_MAX.
ESP_LOGI(EXAMPLE_TAG, "ESP_GATTS_EXEC_WRITE_EVT, Length=%d", prepare_write_env.prepare_len);
ESP_LOGI(EXAMPLE_TAG, "ESP_GATTS_EXEC_WRITE_EVT, Length=%d", prepare_write_env.prepare_len);
example_exec_write_event_env(&prepare_write_env, param);
break;
case ESP_GATTS_MTU_EVT:
@@ -580,6 +586,11 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
break;
case ESP_GATTS_DISCONNECT_EVT:
ESP_LOGI(EXAMPLE_TAG, "ESP_GATTS_DISCONNECT_EVT, reason = %d", param->disconnect.reason);
if (prepare_write_env.prepare_buf) {
free(prepare_write_env.prepare_buf);
prepare_write_env.prepare_buf = NULL;
}
prepare_write_env.prepare_len = 0;
esp_ble_gap_start_advertising(&adv_params);
break;
case ESP_GATTS_CREAT_ATTR_TAB_EVT:{
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -77,22 +77,24 @@ static void eddystone_send_raw(const esp_eddystone_result_t *res)
}
case EDDYSTONE_FRAME_TYPE_URL: {
size_t url_len = strlen((char*)res->inform.url.encoded_url); //encoded url length
if(url_len > EDDYSTONE_URL_MAX_LEN){
url_len = EDDYSTONE_URL_MAX_LEN;
size_t url_len = strlen((char*)res->inform.url.encoded_url); // encoded url length
/* Eddystone: max 17 bytes encoded URL after scheme byte; also raw_adv_data[31] leaves
* only 20 bytes at index 11 (3 header + url_len), so url_len <= 17. */
if (url_len > EDDYSTONE_URL_ENCODED_MAX_LEN) {
url_len = EDDYSTONE_URL_ENCODED_MAX_LEN;
}
raw_adv_data[index++] = url_len+6; //length
raw_adv_data[index++] = url_len + 6; // length
raw_adv_data[index++] = ESP_BLE_AD_TYPE_SERVICE_DATA;
raw_adv_data[index++] = 0xAA;
raw_adv_data[index++] = 0xFE;
uint8_t service_data[EDDYSTONE_URL_MAX_LEN+2] = {0};
uint8_t service_data[EDDYSTONE_URL_ENCODED_MAX_LEN + 3] = {0};
service_data[0] = EDDYSTONE_FRAME_TYPE_URL;
service_data[1] = res->inform.url.tx_power;
service_data[2] = res->inform.url.url_scheme;
memcpy(&service_data[3], res->inform.url.encoded_url, url_len);
memcpy(&raw_adv_data[index], service_data, url_len+3);
index += url_len+3;
memcpy(&raw_adv_data[index], service_data, url_len + 3);
index += url_len + 3;
break;
}
@@ -107,7 +109,7 @@ static void eddystone_send_raw(const esp_eddystone_result_t *res)
service_data[2] = (res->inform.tlm.battery_voltage >> 8) & 0xFF;
service_data[3] = res->inform.tlm.battery_voltage & 0xFF;
service_data[4] = (res->inform.tlm.temperature >> 8) & 0xFF;
service_data[4] = res->inform.tlm.temperature & 0xFF;
service_data[5] = res->inform.tlm.temperature & 0xFF;
service_data[6] = (res->inform.tlm.adv_count >> 24) & 0xFF;
service_data[7] = (res->inform.tlm.adv_count >> 16) & 0xFF;
service_data[8] = (res->inform.tlm.adv_count >> 8) & 0xFF;
@@ -166,8 +168,8 @@ void esp_eddystone_appRegister(void)
void esp_eddystone_init(void)
{
esp_bluedroid_init();
esp_bluedroid_enable();
ESP_ERROR_CHECK(esp_bluedroid_init());
ESP_ERROR_CHECK(esp_bluedroid_enable());
esp_eddystone_appRegister();
}
@@ -176,8 +178,8 @@ void app_main(void)
ESP_ERROR_CHECK(nvs_flash_init());
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();
esp_bt_controller_init(&bt_cfg);
esp_bt_controller_enable(ESP_BT_MODE_BLE);
ESP_ERROR_CHECK(esp_bt_controller_init(&bt_cfg));
ESP_ERROR_CHECK(esp_bt_controller_enable(ESP_BT_MODE_BLE));
esp_eddystone_init();
esp_eddystone_result_t eddystone_result;
@@ -174,7 +174,8 @@ static int ble_aes_ccm_encrypt(const uint8_t *key, const uint8_t *nonce,
static int ble_aes_ccm_decrypt(const uint8_t *key, const uint8_t *nonce,
const uint8_t *ciphertext, size_t ciphertext_len,
const uint8_t *aad, size_t aad_len,
uint8_t *plaintext, size_t tag_len)
uint8_t *plaintext, size_t tag_len,
size_t plaintext_capacity)
{
#if defined(CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT)
struct tc_aes_key_sched_struct sched;
@@ -197,6 +198,11 @@ static int ble_aes_ccm_decrypt(const uint8_t *key, const uint8_t *nonce,
plaintext_len = ciphertext_len - tag_len;
if (plaintext_len > plaintext_capacity) {
ESP_LOGE(TAG, "plaintext_len (%zu) > plaintext_capacity (%zu)", plaintext_len, plaintext_capacity);
return -1;
}
/* Set AES encryption key */
ret = tc_aes128_set_encrypt_key(&sched, key);
if (ret != TC_CRYPTO_SUCCESS) {
@@ -337,13 +343,14 @@ int ble_ead_encrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE],
int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE],
const uint8_t iv[BLE_EAD_IV_SIZE],
const uint8_t *encrypted_payload, size_t encrypted_payload_size,
uint8_t *payload)
uint8_t *payload, size_t payload_capacity)
{
int ret;
uint8_t nonce[BLE_EAD_NONCE_SIZE];
const uint8_t *randomizer;
const uint8_t *ciphertext;
size_t ciphertext_len;
size_t expected_plaintext_len;
if (session_key == NULL) {
ESP_LOGE(TAG, "session_key is NULL");
@@ -370,6 +377,13 @@ int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE],
return -1;
}
expected_plaintext_len = BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_payload_size);
if (expected_plaintext_len > payload_capacity) {
ESP_LOGE(TAG, "EAD plaintext length %zu exceeds payload buffer %zu",
expected_plaintext_len, payload_capacity);
return -1;
}
/* Extract randomizer from the start of encrypted payload */
randomizer = encrypted_payload;
@@ -388,7 +402,8 @@ int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE],
ret = ble_aes_ccm_decrypt(session_key, nonce,
ciphertext, ciphertext_len,
ble_ead_aad, BLE_EAD_AAD_SIZE,
payload, BLE_EAD_MIC_SIZE);
payload, BLE_EAD_MIC_SIZE,
payload_capacity);
return ret;
}
@@ -79,14 +79,15 @@ int ble_ead_encrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE],
* @param encrypted_payload Encrypted advertising data (includes randomizer and MIC)
* @param encrypted_payload_size Size of encrypted data
* @param payload Output buffer for decrypted data
* Size must be at least BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_payload_size)
* @param payload_capacity Size of @a payload in bytes; must be >=
* BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_payload_size)
*
* @return 0 on success, negative error code on failure
*/
int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE],
const uint8_t iv[BLE_EAD_IV_SIZE],
const uint8_t *encrypted_payload, size_t encrypted_payload_size,
uint8_t *payload);
uint8_t *payload, size_t payload_capacity);
#ifdef __cplusplus
}
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -19,6 +19,7 @@
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include "nvs.h"
#include "nvs_flash.h"
#include "esp_bt.h"
@@ -57,12 +58,14 @@ static peer_info_t peers[MAX_PEERS] = {0};
/* GATT client state */
static bool is_connected = false;
static bool connect_pending = false; /* enh_open issued; CONNECT_EVT not yet received */
static bool get_server = false;
static uint16_t conn_id_stored = 0;
static uint16_t service_start_handle = 0;
static uint16_t service_end_handle = 0;
static uint16_t key_material_char_handle = INVALID_HANDLE;
static esp_bd_addr_t current_peer_addr = {0};
/* BDA for the active GATT connection; set in CONNECT_EVT, cleared on disconnect */
static esp_bd_addr_t gattc_remote_bda = {0};
/* GATT interface */
static esp_gatt_if_t gattc_if_stored = ESP_GATT_IF_NONE;
@@ -146,27 +149,54 @@ static void decrypt_enc_adv_data(const uint8_t *adv_data, uint8_t adv_len, const
uint8_t dec_data[32]; /* Buffer for decrypted data */
size_t dec_len = BLE_EAD_DECRYPTED_PAYLOAD_SIZE(enc_data_len);
if (dec_len > sizeof(dec_data)) {
ESP_LOGW(TAG, "Encrypted AD would yield %zu plaintext bytes; example buffer is %zu — skip",
dec_len, sizeof(dec_data));
break;
}
int rc = ble_ead_decrypt(
peers[peer_idx].key_material.session_key,
peers[peer_idx].key_material.iv,
enc_data, enc_data_len,
dec_data);
dec_data, sizeof(dec_data));
if (rc == 0) {
size_t safe_dec_len = dec_len;
if (safe_dec_len > sizeof(dec_data)) {
ESP_LOGW(TAG, "dec_len %zu > buffer %zu, clamping for log/parse",
dec_len, sizeof(dec_data));
safe_dec_len = sizeof(dec_data);
}
ESP_LOGI(TAG, "Decryption successful!");
ESP_LOGI(TAG, "Decrypted data:");
ESP_LOG_BUFFER_HEX(TAG, dec_data, dec_len);
ESP_LOG_BUFFER_HEX(TAG, dec_data, safe_dec_len);
/* Parse decrypted advertising structure */
if (dec_len >= 2) {
uint8_t dec_type = dec_data[1];
if (dec_type == ESP_BLE_AD_TYPE_NAME_CMPL || dec_type == ESP_BLE_AD_TYPE_NAME_SHORT) {
char name[32] = {0};
size_t name_len = dec_data[0] - 1;
if (name_len < sizeof(name)) {
memcpy(name, &dec_data[2], name_len);
ESP_LOGI(TAG, "Decrypted device name: %s", name);
/* Parse decrypted advertising structure (do not trust length octet past plaintext) */
if (safe_dec_len >= 2) {
if (dec_data[0] == 0) {
ESP_LOGW(TAG, "Malformed decrypted AD: zero inner length");
} else {
/* BLE: octet 0 is L = len(type+data); element occupies 1+L octets */
const size_t inner_total = 1U + (size_t)dec_data[0];
if (inner_total > safe_dec_len) {
ESP_LOGW(TAG, "Malformed decrypted AD: inner len claims %zu octets, have %zu",
inner_total, safe_dec_len);
} else {
uint8_t dec_type = dec_data[1];
if (dec_type == ESP_BLE_AD_TYPE_NAME_CMPL ||
dec_type == ESP_BLE_AD_TYPE_NAME_SHORT) {
char name[32] = {0};
size_t name_len = (size_t)dec_data[0] - 1U;
/* Name in dec_data[2 .. name_copy_end); last index is name_copy_end - 1 */
const size_t name_copy_end = 2U + name_len;
if (name_len < sizeof(name) &&
name_copy_end <= sizeof(dec_data) &&
name_copy_end <= safe_dec_len) {
memcpy(name, &dec_data[2], name_len);
ESP_LOGI(TAG, "Decrypted device name: %s", name);
}
}
}
}
}
@@ -193,11 +223,14 @@ static bool should_connect(const uint8_t *adv_data, uint8_t adv_len)
uint8_t type = adv_data[offset + 1];
if (type == ESP_BLE_AD_TYPE_16SRV_CMPL || type == ESP_BLE_AD_TYPE_16SRV_PART) {
/* Check for GAP service UUID */
for (int i = 0; i < len - 1; i += 2) {
uint16_t uuid = adv_data[offset + 2 + i] | (adv_data[offset + 3 + i] << 8);
if (uuid == GAP_SERVICE_UUID) {
return true;
/* Octets after AD type = len - 1; each 16-bit UUID needs 2 payload bytes */
int payload_len = (int)len - 1;
if (payload_len >= 2) {
for (int i = 0; i + 1 < payload_len; i += 2) {
uint16_t uuid = adv_data[offset + 2 + i] | (adv_data[offset + 3 + i] << 8);
if (uuid == GAP_SERVICE_UUID) {
return true;
}
}
}
}
@@ -250,26 +283,33 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param
decrypt_enc_adv_data(adv_data, adv_len, scan_result->scan_rst.bda);
} else {
/* Need to connect and get key */
if (!is_connected) {
ESP_LOGI(TAG, "Connecting to get key material...");
add_peer(scan_result->scan_rst.bda);
memcpy(current_peer_addr, scan_result->scan_rst.bda, sizeof(esp_bd_addr_t));
if (!is_connected && !connect_pending) {
int peer_slot = add_peer(scan_result->scan_rst.bda);
if (peer_slot < 0) {
ESP_LOGE(TAG, "Peer table full (max %d); cannot track key for "
ESP_BD_ADDR_STR " — skip connection (increase "
"MAX_PEERS or free a slot)",
MAX_PEERS, ESP_BD_ADDR_HEX(scan_result->scan_rst.bda));
} else {
ESP_LOGI(TAG, "Connecting to get key material...");
esp_ble_gap_stop_scanning();
esp_ble_gap_stop_scanning();
esp_ble_gatt_creat_conn_params_t conn_params = {0};
memcpy(conn_params.remote_bda, scan_result->scan_rst.bda, ESP_BD_ADDR_LEN);
conn_params.remote_addr_type = scan_result->scan_rst.ble_addr_type;
conn_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
conn_params.is_direct = true;
conn_params.is_aux = false;
esp_ble_gattc_enh_open(gattc_if_stored, &conn_params);
esp_ble_gatt_creat_conn_params_t conn_params = {0};
memcpy(conn_params.remote_bda, scan_result->scan_rst.bda,
ESP_BD_ADDR_LEN);
conn_params.remote_addr_type = scan_result->scan_rst.ble_addr_type;
conn_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
conn_params.is_direct = true;
conn_params.is_aux = false;
connect_pending = true;
esp_ble_gattc_enh_open(gattc_if_stored, &conn_params);
}
}
}
}
} else if (scan_result->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) {
ESP_LOGI(TAG, "Scan complete");
if (!is_connected) {
if (!is_connected && !connect_pending) {
start_scan(); /* Restart scanning */
}
}
@@ -314,6 +354,8 @@ static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
case ESP_GATTC_CONNECT_EVT:
ESP_LOGI(TAG, "Connected, conn_id %d", param->connect.conn_id);
conn_id_stored = param->connect.conn_id;
connect_pending = false;
memcpy(gattc_remote_bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
is_connected = true;
/* Request MTU exchange */
@@ -324,6 +366,8 @@ static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
if (param->open.status != ESP_GATT_OK) {
ESP_LOGE(TAG, "Open failed: %d", param->open.status);
is_connected = false;
connect_pending = false;
memset(gattc_remote_bda, 0, sizeof(gattc_remote_bda));
start_scan();
}
break;
@@ -360,25 +404,29 @@ static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
if (get_server) {
/* Get characteristics */
uint16_t count = 0;
esp_ble_gattc_get_attr_count(gattc_if, conn_id_stored,
ESP_GATT_DB_CHARACTERISTIC,
service_start_handle,
service_end_handle,
INVALID_HANDLE, &count);
esp_gatt_status_t gc_st = esp_ble_gattc_get_attr_count(gattc_if, conn_id_stored,
ESP_GATT_DB_CHARACTERISTIC,
service_start_handle,
service_end_handle,
INVALID_HANDLE, &count);
if (count > 0) {
esp_gattc_char_elem_t *char_elem = malloc(sizeof(esp_gattc_char_elem_t) * count);
if (gc_st != ESP_GATT_OK) {
ESP_LOGE(TAG, "get_attr_count failed: %d", gc_st);
} else if (count > 0) {
esp_gattc_char_elem_t *char_elem = calloc(count, sizeof(esp_gattc_char_elem_t));
if (char_elem) {
esp_bt_uuid_t km_uuid = {
.len = ESP_UUID_LEN_16,
.uuid = {.uuid16 = KEY_MATERIAL_CHAR_UUID},
};
esp_ble_gattc_get_char_by_uuid(gattc_if, conn_id_stored,
service_start_handle,
service_end_handle,
km_uuid, char_elem, &count);
gc_st = esp_ble_gattc_get_char_by_uuid(gattc_if, conn_id_stored,
service_start_handle,
service_end_handle,
km_uuid, char_elem, &count);
if (count > 0) {
if (gc_st != ESP_GATT_OK) {
ESP_LOGE(TAG, "get_char_by_uuid failed: %d", gc_st);
} else if (count > 0) {
key_material_char_handle = char_elem[0].char_handle;
ESP_LOGI(TAG, "Key Material characteristic found, handle %d", key_material_char_handle);
@@ -402,7 +450,7 @@ static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
if (param->read.handle == key_material_char_handle &&
param->read.value_len == sizeof(ble_ead_key_material_t)) {
/* Store key material */
int peer_idx = find_peer(current_peer_addr);
int peer_idx = find_peer(gattc_remote_bda);
if (peer_idx >= 0) {
memcpy(&peers[peer_idx].key_material, param->read.value,
sizeof(ble_ead_key_material_t));
@@ -424,6 +472,8 @@ static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
case ESP_GATTC_DISCONNECT_EVT:
ESP_LOGI(TAG, "Disconnected, reason 0x%02x", param->disconnect.reason);
is_connected = false;
connect_pending = false;
memset(gattc_remote_bda, 0, sizeof(gattc_remote_bda));
get_server = false;
key_material_char_handle = INVALID_HANDLE;
start_scan();
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -67,10 +67,14 @@ static bool is_target_device(const uint8_t *adv_data, uint8_t adv_len)
uint8_t type = adv_data[offset + 1];
if (type == ESP_BLE_AD_TYPE_16SRV_CMPL || type == ESP_BLE_AD_TYPE_16SRV_PART) {
for (int i = 0; i < len - 1; i += 2) {
uint16_t uuid = adv_data[offset + 2 + i] | (adv_data[offset + 3 + i] << 8);
if (uuid == CUSTOM_SERVICE_UUID) {
return true;
/* Octets after AD type = len - 1; each 16-bit UUID needs 2 payload bytes */
int payload_len = (int)len - 1;
if (payload_len >= 2) {
for (int i = 0; i + 1 < payload_len; i += 2) {
uint16_t uuid = adv_data[offset + 2 + i] | (adv_data[offset + 3 + i] << 8);
if (uuid == CUSTOM_SERVICE_UUID) {
return true;
}
}
}
}
@@ -116,30 +120,53 @@ static void decrypt_adv_data_no_connect(const uint8_t *adv_data, uint8_t adv_len
/* Decrypt using pre-shared key */
uint8_t dec_data[32];
size_t dec_len = BLE_EAD_DECRYPTED_PAYLOAD_SIZE(enc_data_len);
if (dec_len > sizeof(dec_data)) {
ESP_LOGW(TAG, "Encrypted AD would yield %zu plaintext bytes; example buffer is %zu — skip",
dec_len, sizeof(dec_data));
return;
}
int rc = ble_ead_decrypt(
pre_shared_key.session_key,
pre_shared_key.iv,
enc_data, enc_data_len,
dec_data);
dec_data, sizeof(dec_data));
if (rc == 0) {
size_t safe_dec_len = dec_len;
if (safe_dec_len > sizeof(dec_data)) {
ESP_LOGW(TAG, "dec_len %zu > buffer %zu, clamping for log/parse",
dec_len, sizeof(dec_data));
safe_dec_len = sizeof(dec_data);
}
ESP_LOGI(TAG, "✅ Decryption successful (no connection needed!)");
ESP_LOGI(TAG, "Decrypted data (%d bytes):", dec_len);
ESP_LOG_BUFFER_HEX(TAG, dec_data, dec_len);
ESP_LOGI(TAG, "Decrypted data (%zu bytes):", safe_dec_len);
ESP_LOG_BUFFER_HEX(TAG, dec_data, safe_dec_len);
/* Parse the decrypted advertising structure */
if (dec_len >= 2) {
uint8_t inner_len = dec_data[0];
uint8_t inner_type = dec_data[1];
if (inner_type == ESP_BLE_AD_TYPE_NAME_CMPL ||
inner_type == ESP_BLE_AD_TYPE_NAME_SHORT) {
char name[32] = {0};
size_t name_len = inner_len - 1;
if (name_len < sizeof(name) && name_len <= dec_len - 2) {
memcpy(name, &dec_data[2], name_len);
ESP_LOGI(TAG, "📛 Decrypted device name: \"%s\"", name);
/* Parse decrypted AD (do not trust length octet past plaintext) */
if (safe_dec_len >= 2) {
if (dec_data[0] == 0) {
ESP_LOGW(TAG, "Malformed decrypted AD: zero inner length");
} else {
const size_t inner_total = 1U + (size_t)dec_data[0];
if (inner_total > safe_dec_len) {
ESP_LOGW(TAG,
"Malformed decrypted AD: inner len claims %zu octets, have %zu",
inner_total, safe_dec_len);
} else {
uint8_t inner_type = dec_data[1];
if (inner_type == ESP_BLE_AD_TYPE_NAME_CMPL ||
inner_type == ESP_BLE_AD_TYPE_NAME_SHORT) {
char name[32] = {0};
size_t name_len = (size_t)dec_data[0] - 1U;
const size_t name_copy_end = 2U + name_len;
if (name_len < sizeof(name) &&
name_copy_end <= sizeof(dec_data) &&
name_copy_end <= safe_dec_len) {
memcpy(name, &dec_data[2], name_len);
ESP_LOGI(TAG, "📛 Decrypted device name: \"%s\"", name);
}
}
}
}
}
@@ -168,7 +168,8 @@ static int ble_aes_ccm_encrypt(const uint8_t *key, const uint8_t *nonce,
static int ble_aes_ccm_decrypt(const uint8_t *key, const uint8_t *nonce,
const uint8_t *ciphertext, size_t ciphertext_len,
const uint8_t *aad, size_t aad_len,
uint8_t *plaintext, size_t tag_len)
uint8_t *plaintext, size_t tag_len,
size_t plaintext_capacity)
{
#if defined(CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT)
struct tc_aes_key_sched_struct sched;
@@ -191,6 +192,11 @@ static int ble_aes_ccm_decrypt(const uint8_t *key, const uint8_t *nonce,
plaintext_len = ciphertext_len - tag_len;
if (plaintext_len > plaintext_capacity) {
ESP_LOGE(TAG, "plaintext_len (%zu) > plaintext_capacity (%zu)", plaintext_len, plaintext_capacity);
return -1;
}
/* Set AES encryption key */
ret = tc_aes128_set_encrypt_key(&sched, key);
if (ret != TC_CRYPTO_SUCCESS) {
@@ -330,13 +336,14 @@ int ble_ead_encrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE],
int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE],
const uint8_t iv[BLE_EAD_IV_SIZE],
const uint8_t *encrypted_payload, size_t encrypted_payload_size,
uint8_t *payload)
uint8_t *payload, size_t payload_capacity)
{
int ret;
uint8_t nonce[BLE_EAD_NONCE_SIZE];
const uint8_t *randomizer;
const uint8_t *ciphertext;
size_t ciphertext_len;
size_t expected_plaintext_len;
if (session_key == NULL) {
ESP_LOGE(TAG, "session_key is NULL");
@@ -363,6 +370,13 @@ int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE],
return -1;
}
expected_plaintext_len = BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_payload_size);
if (expected_plaintext_len > payload_capacity) {
ESP_LOGE(TAG, "EAD plaintext length %zu exceeds payload buffer %zu",
expected_plaintext_len, payload_capacity);
return -1;
}
/* Extract randomizer from the start of encrypted payload */
randomizer = encrypted_payload;
@@ -381,7 +395,8 @@ int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE],
ret = ble_aes_ccm_decrypt(session_key, nonce,
ciphertext, ciphertext_len,
ble_ead_aad, BLE_EAD_AAD_SIZE,
payload, BLE_EAD_MIC_SIZE);
payload, BLE_EAD_MIC_SIZE,
payload_capacity);
return ret;
}
@@ -79,14 +79,15 @@ int ble_ead_encrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE],
* @param encrypted_payload Encrypted advertising data (includes randomizer and MIC)
* @param encrypted_payload_size Size of encrypted data
* @param payload Output buffer for decrypted data
* Size must be at least BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_payload_size)
* @param payload_capacity Size of @a payload in bytes; must be >=
* BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_payload_size)
*
* @return 0 on success, negative error code on failure
*/
int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE],
const uint8_t iv[BLE_EAD_IV_SIZE],
const uint8_t *encrypted_payload, size_t encrypted_payload_size,
uint8_t *payload);
uint8_t *payload, size_t payload_capacity);
#ifdef __cplusplus
}
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -59,7 +59,6 @@ static ble_ead_key_material_t key_material = {
/* GATT state */
static esp_gatt_if_t gatts_if_stored = ESP_GATT_IF_NONE;
static uint16_t conn_id_stored = 0;
static bool is_connected = false;
/* Advertising parameters */
static esp_ble_adv_params_t adv_params = {
@@ -158,8 +157,13 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param
{
switch (event) {
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
ESP_LOGI(TAG, "Raw advertising data set complete");
start_advertising();
if (param->adv_data_raw_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(TAG, "Raw advertising data set failed: %d",
param->adv_data_raw_cmpl.status);
} else {
ESP_LOGI(TAG, "Raw advertising data set complete");
start_advertising();
}
break;
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
@@ -236,7 +240,6 @@ static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_
ESP_LOGI(TAG, "Connected, conn_id %d, remote "ESP_BD_ADDR_STR"",
param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda));
conn_id_stored = param->connect.conn_id;
is_connected = true;
/* Update connection parameters */
esp_ble_conn_update_params_t conn_params = {0};
@@ -251,7 +254,6 @@ static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_
case ESP_GATTS_DISCONNECT_EVT:
ESP_LOGI(TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x",
ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason);
is_connected = false;
/* Re-encrypt and restart advertising with new randomizer */
set_encrypted_adv_data();
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -37,9 +37,11 @@ esp_err_t esp_hidd_register_callbacks(esp_hidd_event_cb_t callbacks)
return hidd_status;
}
esp_ble_gatts_app_register(BATTRAY_APP_ID);
if ((hidd_status = esp_ble_gatts_app_register(BATTRAY_APP_ID)) != ESP_OK) {
return hidd_status;
}
if((hidd_status = esp_ble_gatts_app_register(HIDD_APP_ID)) != ESP_OK) {
if ((hidd_status = esp_ble_gatts_app_register(HIDD_APP_ID)) != ESP_OK) {
return hidd_status;
}
@@ -52,8 +54,10 @@ esp_err_t esp_hidd_profile_init(void)
ESP_LOGE(HID_LE_PRF_TAG, "HID device profile already initialized");
return ESP_FAIL;
}
// Reset the hid device target environment
/* Reset the hid device target environment */
memset(&hidd_le_env, 0, sizeof(hidd_le_env_t));
hidd_le_env.gatt_if = ESP_GATT_IF_NONE;
hidd_le_env.bat_gatt_if = ESP_GATT_IF_NONE;
hidd_le_env.enabled = true;
return ESP_OK;
}
@@ -62,20 +66,32 @@ esp_err_t esp_hidd_profile_deinit(void)
{
uint16_t hidd_svc_hdl = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_SVC];
if (!hidd_le_env.enabled) {
ESP_LOGE(HID_LE_PRF_TAG, "HID device profile already initialized");
ESP_LOGW(HID_LE_PRF_TAG, "HID device profile not initialized, deinit skipped");
return ESP_OK;
}
if(hidd_svc_hdl != 0) {
esp_ble_gatts_stop_service(hidd_svc_hdl);
esp_ble_gatts_delete_service(hidd_svc_hdl);
if (hidd_svc_hdl != 0) {
esp_ble_gatts_stop_service(hidd_svc_hdl);
esp_ble_gatts_delete_service(hidd_svc_hdl);
} else {
return ESP_FAIL;
}
ESP_LOGW(HID_LE_PRF_TAG, "HID service handle unset, skip stop/delete");
}
/* register the HID device profile to the BTA_GATTS module*/
esp_ble_gatts_app_unregister(hidd_le_env.gatt_if);
/* Release both GATTS apps (battery registered first, then HID) */
if (hidd_le_env.bat_gatt_if != ESP_GATT_IF_NONE) {
esp_ble_gatts_app_unregister(hidd_le_env.bat_gatt_if);
hidd_le_env.bat_gatt_if = ESP_GATT_IF_NONE;
} else {
ESP_LOGW(HID_LE_PRF_TAG, "Battery gatt_if invalid, app_unregister skipped (possible stack slot leak)");
}
if (hidd_le_env.gatt_if != ESP_GATT_IF_NONE) {
esp_ble_gatts_app_unregister(hidd_le_env.gatt_if);
hidd_le_env.gatt_if = ESP_GATT_IF_NONE;
} else {
ESP_LOGW(HID_LE_PRF_TAG, "HID gatt_if invalid, app_unregister skipped (possible stack slot leak)");
}
hidd_le_env.enabled = false;
return ESP_OK;
}
@@ -112,7 +128,7 @@ void esp_hidd_send_keyboard_value(uint16_t conn_id, key_mask_t special_key_mask,
buffer[i+2] = keyboard_cmd[i];
}
ESP_LOGD(HID_LE_PRF_TAG, "the key vaule = %d,%d,%d, %d, %d, %d,%d, %d", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7]);
ESP_LOGD(HID_LE_PRF_TAG, "the key value = %d,%d,%d, %d, %d, %d,%d, %d", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7]);
hid_dev_send_report(hidd_le_env.gatt_if, conn_id,
HID_RPT_ID_KEY_IN, HID_REPORT_TYPE_INPUT, HID_KEYBOARD_IN_RPT_LEN, buffer);
return;
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -170,7 +170,7 @@ static const uint8_t hidReportMap[] = {
0x09, 0xA6, // Usage(Vendor defined)
0x09, 0xA9, // Usage(Vendor defined)
0x75, 0x08, // Report Size
0x95, 0x7F, // Report Count = 127 Btyes
0x95, 0x7F, // Report Count = 127 Bytes
0x91, 0x02, // Output(Data, Variable, Absolute)
0xC0, // End Collection
#endif
@@ -285,7 +285,7 @@ static const uint8_t char_prop_read_notify = ESP_GATT_CHAR_PROP_BIT_READ|ESP_GAT
static const uint8_t char_prop_read_write_notify = ESP_GATT_CHAR_PROP_BIT_READ|ESP_GATT_CHAR_PROP_BIT_WRITE|ESP_GATT_CHAR_PROP_BIT_NOTIFY;
static const uint8_t char_prop_read_write_write_nr = ESP_GATT_CHAR_PROP_BIT_READ|ESP_GATT_CHAR_PROP_BIT_WRITE|ESP_GATT_CHAR_PROP_BIT_WRITE_NR;
/// battary Service
/// battery Service
static const uint16_t battary_svc = ESP_GATT_UUID_BATTERY_SERVICE_SVC;
static const uint16_t bat_lev_uuid = ESP_GATT_UUID_BATTERY_LEVEL;
@@ -296,23 +296,23 @@ static uint8_t battary_lev = 50;
/// Full HRS Database Description - Used to add attributes into the database
static const esp_gatts_attr_db_t bas_att_db[BAS_IDX_NB] =
{
// Battary Service Declaration
// Battery Service Declaration
[BAS_IDX_SVC] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
sizeof(uint16_t), sizeof(battary_svc), (uint8_t *)&battary_svc}},
// Battary level Characteristic Declaration
// Battery level Characteristic Declaration
[BAS_IDX_BATT_LVL_CHAR] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_notify}},
// Battary level Characteristic Value
// Battery level Characteristic Value
[BAS_IDX_BATT_LVL_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&bat_lev_uuid, ESP_GATT_PERM_READ,
sizeof(uint8_t),sizeof(uint8_t), &battary_lev}},
// Battary level Characteristic - Client Characteristic Configuration Descriptor
// Battery level Characteristic - Client Characteristic Configuration Descriptor
[BAS_IDX_BATT_LVL_NTF_CFG] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
sizeof(uint16_t),sizeof(bat_lev_ccc), (uint8_t *)bat_lev_ccc}},
// Battary level report Characteristic Declaration
// Battery level report Characteristic Declaration
[BAS_IDX_BATT_LVL_PRES_FMT] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&char_format_uuid, ESP_GATT_PERM_READ,
sizeof(struct prf_char_pres_fmt), 0, NULL}},
};
@@ -550,6 +550,7 @@ void esp_hidd_prf_cb_hdl(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
}
}
if(param->reg.app_id == BATTRAY_APP_ID) {
hidd_le_env.bat_gatt_if = gatts_if;
hidd_param.init_finish.gatts_if = gatts_if;
if(hidd_le_env.hidd_cb != NULL) {
(hidd_le_env.hidd_cb)(ESP_BAT_EVENT_REG, &hidd_param);
@@ -587,7 +588,8 @@ void esp_hidd_prf_cb_hdl(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
break;
case ESP_GATTS_WRITE_EVT: {
esp_hidd_cb_param_t cb_param = {0};
if (param->write.handle == hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_LED_OUT_VAL]) {
if (hidd_le_env.hidd_cb != NULL &&
param->write.handle == hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_LED_OUT_VAL]) {
cb_param.led_write.conn_id = param->write.conn_id;
cb_param.led_write.report_id = HID_RPT_ID_LED_OUT;
cb_param.led_write.length = param->write.len;
@@ -595,8 +597,8 @@ void esp_hidd_prf_cb_hdl(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
(hidd_le_env.hidd_cb)(ESP_HIDD_EVENT_BLE_LED_REPORT_WRITE_EVT, &cb_param);
}
#if (SUPPORT_REPORT_VENDOR == true)
if (param->write.handle == hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_VENDOR_OUT_VAL] &&
hidd_le_env.hidd_cb != NULL) {
if (hidd_le_env.hidd_cb != NULL &&
param->write.handle == hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_VENDOR_OUT_VAL]) {
cb_param.vendor_write.conn_id = param->write.conn_id;
cb_param.vendor_write.report_id = HID_RPT_ID_VENDOR_OUT;
cb_param.vendor_write.length = param->write.len;
@@ -607,24 +609,26 @@ void esp_hidd_prf_cb_hdl(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
break;
}
case ESP_GATTS_CREAT_ATTR_TAB_EVT: {
if (param->add_attr_tab.num_handle == BAS_IDX_NB &&
param->add_attr_tab.svc_uuid.uuid.uuid16 == ESP_GATT_UUID_BATTERY_SERVICE_SVC &&
param->add_attr_tab.status == ESP_GATT_OK) {
if (param->add_attr_tab.status != ESP_GATT_OK) {
ESP_LOGE(HID_LE_PRF_TAG, "ATTR_TAB_EVT failed: status=%d num_handle=%d",
param->add_attr_tab.status, param->add_attr_tab.num_handle);
} else if (param->add_attr_tab.num_handle == BAS_IDX_NB &&
param->add_attr_tab.svc_uuid.uuid.uuid16 == ESP_GATT_UUID_BATTERY_SERVICE_SVC) {
incl_svc.start_hdl = param->add_attr_tab.handles[BAS_IDX_SVC];
incl_svc.end_hdl = incl_svc.start_hdl + BAS_IDX_NB -1;
incl_svc.end_hdl = incl_svc.start_hdl + BAS_IDX_NB - 1;
ESP_LOGI(HID_LE_PRF_TAG, "%s(), start added the hid service to the stack database. incl_handle = %d",
__func__, incl_svc.start_hdl);
esp_ble_gatts_start_service(param->add_attr_tab.handles[BAS_IDX_SVC]);
esp_ble_gatts_create_attr_tab(hidd_le_gatt_db, gatts_if, HIDD_LE_IDX_NB, 0);
}
if (param->add_attr_tab.num_handle == HIDD_LE_IDX_NB &&
param->add_attr_tab.status == ESP_GATT_OK) {
} else if (param->add_attr_tab.num_handle == HIDD_LE_IDX_NB) {
memcpy(hidd_le_env.hidd_inst.att_tbl, param->add_attr_tab.handles,
HIDD_LE_IDX_NB*sizeof(uint16_t));
ESP_LOGI(HID_LE_PRF_TAG, "hid svc handle = %x",hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_SVC]);
HIDD_LE_IDX_NB * sizeof(uint16_t));
ESP_LOGI(HID_LE_PRF_TAG, "hid svc handle = %x", hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_SVC]);
hid_add_id_tbl();
esp_ble_gatts_start_service(hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_SVC]);
esp_ble_gatts_start_service(hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_SVC]);
} else {
esp_ble_gatts_start_service(param->add_attr_tab.handles[0]);
ESP_LOGW(HID_LE_PRF_TAG, "ATTR_TAB_EVT unexpected num_handle=%d",
param->add_attr_tab.num_handle);
}
break;
}
@@ -645,8 +649,10 @@ void hidd_le_create_service(esp_gatt_if_t gatts_if)
void hidd_le_init(void)
{
// Reset the hid device target environment
/* Reset the hid device target environment */
memset(&hidd_le_env, 0, sizeof(hidd_le_env_t));
hidd_le_env.gatt_if = ESP_GATT_IF_NONE;
hidd_le_env.bat_gatt_if = ESP_GATT_IF_NONE;
}
void hidd_clcb_alloc (uint16_t conn_id, esp_bd_addr_t bda)
@@ -666,16 +672,15 @@ void hidd_clcb_alloc (uint16_t conn_id, esp_bd_addr_t bda)
return;
}
bool hidd_clcb_dealloc (uint16_t conn_id)
bool hidd_clcb_dealloc(uint16_t conn_id)
{
uint8_t i_clcb = 0;
hidd_clcb_t *p_clcb = NULL;
for (i_clcb = 0, p_clcb= hidd_le_env.hidd_clcb; i_clcb < HID_MAX_APPS; i_clcb++, p_clcb++) {
for (uint8_t i_clcb = 0; i_clcb < HID_MAX_APPS; i_clcb++) {
hidd_clcb_t *p_clcb = &hidd_le_env.hidd_clcb[i_clcb];
if (p_clcb->in_use && p_clcb->conn_id == conn_id) {
memset(p_clcb, 0, sizeof(hidd_clcb_t));
return true;
}
}
return false;
}
@@ -735,12 +740,12 @@ void hidd_set_attr_value(uint16_t handle, uint16_t val_len, const uint8_t *value
return;
}
void hidd_get_attr_value(uint16_t handle, uint16_t *length, uint8_t **value)
void hidd_get_attr_value(uint16_t handle, uint16_t *length, const uint8_t **value)
{
hidd_inst_t *hidd_inst = &hidd_le_env.hidd_inst;
if(hidd_inst->att_tbl[HIDD_LE_IDX_HID_INFO_VAL] <= handle &&
hidd_inst->att_tbl[HIDD_LE_IDX_REPORT_REP_REF] >= handle){
esp_ble_gatts_get_attr_value(handle, length, (const uint8_t **)value);
esp_ble_gatts_get_attr_value(handle, length, value);
} else {
ESP_LOGE(HID_LE_PRF_TAG, "%s error:Invalid handle value.", __func__);
}
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -183,7 +183,7 @@ enum {
HIDD_LE_CHAR_MAX //= HIDD_LE_REPORT_CHAR + HIDD_LE_NB_REPORT_INST_MAX,
};
///att read event table Indexs
///att read event table Indexes
enum {
HIDD_LE_READ_INFO_EVT,
HIDD_LE_READ_CTNL_PT_EVT,
@@ -306,7 +306,8 @@ typedef struct
/* service engine control block */
typedef struct {
hidd_clcb_t hidd_clcb[HID_MAX_APPS]; /* connection link*/
esp_gatt_if_t gatt_if;
esp_gatt_if_t gatt_if; /* HIDD_APP_ID */
esp_gatt_if_t bat_gatt_if; /* BATTRAY_APP_ID */
bool enabled;
bool is_take;
bool is_primery;
@@ -327,7 +328,7 @@ void hidd_le_create_service(esp_gatt_if_t gatts_if);
void hidd_set_attr_value(uint16_t handle, uint16_t val_len, const uint8_t *value);
void hidd_get_attr_value(uint16_t handle, uint16_t *length, uint8_t **value);
void hidd_get_attr_value(uint16_t handle, uint16_t *length, const uint8_t **value);
esp_err_t hidd_register_cb(void);
@@ -219,6 +219,22 @@ static const esp_gatts_attr_db_t gatt_db[HRS_IDX_NB] =
};
static uint16_t profile_handle_table[HRS_IDX_NB];
/** After a prepared write is executed, read committed value from GATT DB and relay to peers. */
static void relay_demo_char_value_to_peers(void)
{
uint16_t len = 0;
const uint8_t *value = NULL;
uint16_t h = profile_handle_table[IDX_CHAR_VAL_A];
if (h == 0) {
return;
}
if (esp_ble_gatts_get_attr_value(h, &len, &value) != ESP_GATT_OK || value == NULL || len == 0) {
return;
}
traverse_send_peer(len, (uint8_t *)value);
}
#if (BLE50_SUPPORTED == 1)
static esp_ble_gap_ext_adv_t ext_adv[1] = {
[0] = {ADV_HANDLE_INST, ADV_DURATION, ADV_MAX_EVTS},
@@ -238,8 +254,9 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
break;
case ESP_GATTC_CONNECT_EVT:
if (param->connect.link_role == 0) {
++multi_conn_num;
ESP_LOGI(DEMO_TAG, "Connected, conn_id %d, remote "ESP_BD_ADDR_STR", total %u", param->connect.conn_id,
ESP_BD_ADDR_HEX(param->connect.remote_bda), ++multi_conn_num);
ESP_BD_ADDR_HEX(param->connect.remote_bda), (unsigned)multi_conn_num);
Peer new_peer;
new_peer.conn_id = param->connect.conn_id;
new_peer.conn_handle = param->connect.conn_handle;
@@ -261,20 +278,33 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
}
}
break;
case ESP_GATTC_DISCONNECT_EVT:
ESP_LOGI(DEMO_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x, total %u",
ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason,
(multi_conn_num ? --multi_conn_num : multi_conn_num));
if (param->disconnect.conn_id != prph_conn_id) {
Peer *peer = find_peer(param->disconnect.conn_id);
if (peer) {
peer_remove(peer->conn_id);
case ESP_GATTC_DISCONNECT_EVT: {
/* Only central-role connections are added to the peer list (see ESP_GATTC_CONNECT_EVT);
* peripheral-role connections are tracked separately via prph_conn_id and cleaned up
* entirely in ESP_GATTS_DISCONNECT_EVT. Use peer_lookup() as the role oracle here:
* a non-NULL result is the unambiguous proof that this disconnect belongs to a
* central-role peer, regardless of GATTC/GATTS event ordering.
*
* Note: Bluedroid posts ESP_GATTS_DISCONNECT_EVT to the BTC queue before
* ESP_GATTC_DISCONNECT_EVT (the GATTS path is delivered directly, while the GATTC
* path is relayed through the BTA queue), so by the time we get here for a
* peripheral disconnect, prph_conn_id has already been reset to 0xFFFF. Relying on
* (conn_id != prph_conn_id) would therefore mis-classify the peripheral disconnect
* as a central one and incorrectly decrement multi_conn_num / give restart_scan_sem.
* peer_lookup() is used instead of find_peer() so peripheral disconnects don't
* trigger a spurious "peer not found" ERROR log. */
Peer *peer = peer_lookup(param->disconnect.conn_id);
if (peer) {
peer_remove(peer->conn_id);
if (multi_conn_num) {
--multi_conn_num;
}
ESP_LOGI(DEMO_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x, total %u",
ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason, (unsigned)multi_conn_num);
xSemaphoreGive(restart_scan_sem);
} else {
prph_conn_id = 0xFFFF;
}
break;
}
case ESP_GATTC_OPEN_EVT:
ESP_LOGI(DEMO_TAG, "Open, conn_id %d, status %d", param->open.conn_id, param->open.status);
break;
@@ -340,14 +370,27 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, HRS_IDX_NB, SVC_INST_ID);
break;
case ESP_GATTS_WRITE_EVT:
ESP_LOGI(DEMO_TAG, "Characteristic write received, conn_id %u, value", param->write.conn_id);
ESP_LOGI(DEMO_TAG, "Characteristic write received, conn_id %u, prep=%d", param->write.conn_id,
(int)param->write.is_prep);
ESP_LOG_BUFFER_HEX(DEMO_TAG, param->write.value, param->write.len);
traverse_send_peer(param->write.len, param->write.value);
/* Prepared writes must be relayed only after ESP_GATTS_EXEC_WRITE_EVT (EXEC). */
if (param->write.is_prep) {
break;
}
if (param->write.handle == profile_handle_table[IDX_CHAR_VAL_A]) {
traverse_send_peer(param->write.len, param->write.value);
}
break;
case ESP_GATTS_EXEC_WRITE_EVT:
if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) {
relay_demo_char_value_to_peers();
}
break;
case ESP_GATTS_CONNECT_EVT:
if (param->connect.link_role == 1) {
++multi_conn_num;
ESP_LOGI(DEMO_TAG, "Connected, conn_id %u, remote "ESP_BD_ADDR_STR", total %u",
param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda), ++multi_conn_num);
param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda), (unsigned)multi_conn_num);
prph_conn_id = param->connect.conn_id;
advertising_state = DISABLED;
#if (BLE50_SUPPORTED == 1)
@@ -361,9 +404,17 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
break;
case ESP_GATTS_DISCONNECT_EVT:
if (param->disconnect.conn_id == prph_conn_id) {
unsigned total_after;
if (multi_conn_num) {
--multi_conn_num;
total_after = (unsigned)multi_conn_num;
} else {
total_after = 0;
}
ESP_LOGI(DEMO_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%x, total %u",
ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason,
(multi_conn_num ? --multi_conn_num : multi_conn_num));
ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason, total_after);
/* Clear before SET_STATIC_RAND_ADDR_EVT: esp_gap_cb requires 0xFFFF to restart advertising */
prph_conn_id = 0xFFFF;
advertising_state = PENDING;
#if (BLE50_SUPPORTED == 1)
esp_ble_gap_addr_create_static(adv_rand_addr);
@@ -376,7 +427,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
break;
case ESP_GATTS_CREAT_ATTR_TAB_EVT:
ESP_LOGI(DEMO_TAG, "The number handle = %x", param->add_attr_tab.num_handle);
if (param->create.status == ESP_GATT_OK) {
if (param->add_attr_tab.status == ESP_GATT_OK) {
if (param->add_attr_tab.num_handle == HRS_IDX_NB) {
memcpy(profile_handle_table, param->add_attr_tab.handles,
sizeof(profile_handle_table));
@@ -386,7 +437,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
param->add_attr_tab.num_handle, HRS_IDX_NB);
}
} else {
ESP_LOGE(DEMO_TAG, " Create attribute table failed, status %x", param->create.status);
ESP_LOGE(DEMO_TAG, "Create attribute table failed, status %x", param->add_attr_tab.status);
}
break;
default:
@@ -484,7 +535,7 @@ static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *par
ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len);
// ESP_LOGI(DEMO_TAG, "Scan result, device "ESP_BD_ADDR_STR", name len %u", ESP_BD_ADDR_HEX(param->ext_adv_report.params.addr), adv_name_len);
// ESP_LOG_BUFFER_CHAR(DEMO_TAG, adv_name, adv_name_len);
if (strlen(remote_target_name) == adv_name_len && strncmp((char *)adv_name, remote_target_name, adv_name_len) == 0)
if (adv_name != NULL && strlen(remote_target_name) == adv_name_len && strncmp((char *)adv_name, remote_target_name, adv_name_len) == 0)
{
esp_ble_gap_stop_ext_scan();
scan_state = DISABLED;
@@ -532,13 +583,17 @@ static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *par
if (scan_result->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
uint8_t *adv_name = NULL;
uint8_t adv_name_len = 0;
const unsigned buf_cap = sizeof(scan_result->scan_rst.ble_adv);
unsigned comb = (unsigned)scan_result->scan_rst.adv_data_len +
(unsigned)scan_result->scan_rst.scan_rsp_len;
uint16_t safe_adv_len = (comb > buf_cap) ? (uint16_t)buf_cap : (uint16_t)comb;
adv_name = esp_ble_resolve_adv_data_by_type(scan_result->scan_rst.ble_adv,
scan_result->scan_rst.adv_data_len + scan_result->scan_rst.scan_rsp_len,
safe_adv_len,
ESP_BLE_AD_TYPE_NAME_CMPL,
&adv_name_len);
// ESP_LOGI(DEMO_TAG, "Scan result, device "ESP_BD_ADDR_STR", name len %u", ESP_BD_ADDR_HEX(scan_result->scan_rst.bda), adv_name_len);
// ESP_LOG_BUFFER_CHAR(DEMO_TAG, adv_name, adv_name_len);
if (strlen(remote_target_name) == adv_name_len && strncmp((char *)adv_name, remote_target_name, adv_name_len) == 0) {
if (adv_name != NULL && strlen(remote_target_name) == adv_name_len && strncmp((char *)adv_name, remote_target_name, adv_name_len) == 0) {
esp_ble_gap_stop_scanning();
scan_state = DISABLED;
@@ -711,10 +766,12 @@ void app_main(void)
if (multi_conn_num < BLE_PEER_MAX_NUM && scan_state == DISABLED) {
scan_state = PENDING;
#if (BLE50_SUPPORTED == 0)
/* Legacy: cannot set scan random addr until adv stops; completion continues in
* ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT. Do not break out of while(1) or app_main exits. */
if (advertising_state != DISABLED) {
advertising_state = DISABLED;
esp_ble_gap_stop_advertising();
break;
continue;
}
#endif
esp_ble_gap_addr_create_static(new_rand_addr);
@@ -66,4 +66,6 @@ esp_err_t peer_remove(uint16_t conn_id);
Peer *find_peer(uint16_t conn_id);
Peer *peer_lookup(uint16_t conn_id);
#endif
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -8,6 +8,19 @@
static Peer remote_peer_lst[MAX_CONN_NUM];
/* Silent lookup: returns NULL without logging if conn_id is not in the peer list.
* Use this when the caller legitimately treats a miss as "not a central peer"
* (e.g. disconnect path where peripheral-role conn_ids are intentionally absent). */
Peer *peer_lookup(uint16_t conn_id)
{
for (int i = 0; i < MAX_CONN_NUM; i++) {
if (remote_peer_lst[i].conn_id == conn_id) {
return &remote_peer_lst[i];
}
}
return NULL;
}
void peer_manager_init(void)
{
for (int i = 0; i < MAX_CONN_NUM; i++) {
@@ -20,6 +33,18 @@ void peer_manager_init(void)
esp_err_t peer_add(Peer *peer)
{
if (peer == NULL) {
return ESP_ERR_INVALID_ARG;
}
Peer *existing = peer_lookup(peer->conn_id);
if (existing != NULL) {
/* Same conn_id already tracked — refresh metadata, do not consume a second slot */
existing->conn_handle = peer->conn_handle;
existing->gattc_if = peer->gattc_if;
memcpy(&existing->peer_addr, &peer->peer_addr, sizeof(esp_bd_addr_t));
ESP_LOGW(PEER_MANAGER_TAG, "peer_add: conn_id %u already in list, updated", peer->conn_id);
return ESP_OK;
}
for (int i = 0; i < MAX_CONN_NUM; i++) {
if (remote_peer_lst[i].conn_id == 0xFFFF) {
remote_peer_lst[i].char_handle = 0xFFFF;
@@ -51,14 +76,11 @@ esp_err_t peer_remove(uint16_t conn_id)
Peer *find_peer(uint16_t conn_id)
{
for (int i = 0; i < MAX_CONN_NUM; i++) {
if (remote_peer_lst[i].conn_id == conn_id) {
return &remote_peer_lst[i];
}
Peer *p = peer_lookup(conn_id);
if (p == NULL) {
ESP_LOGE(PEER_MANAGER_TAG, "peer not found in list, conn_id %d", conn_id);
}
ESP_LOGE(PEER_MANAGER_TAG, "peer not found in list, conn_id %d", conn_id);
return NULL;
return p;
}
void traverse_send_peer(uint16_t len, uint8_t *value)
@@ -139,10 +139,14 @@ static void ble_prph_set_new_adv(void)
is_advertising = true;
esp_ble_gap_addr_create_static(new_rand_addr);
#if (BLE50_SUPPORTED == 1)
esp_ble_gap_ext_adv_set_rand_addr(EXT_ADV_HANDLE, new_rand_addr);
esp_err_t err = esp_ble_gap_ext_adv_set_rand_addr(EXT_ADV_HANDLE, new_rand_addr);
#else
esp_ble_gap_set_rand_addr(new_rand_addr);
esp_err_t err = esp_ble_gap_set_rand_addr(new_rand_addr);
#endif
if (err != ESP_OK) {
is_advertising = false;
ESP_LOGE(DEMO_TAG, "Set random addr failed: %s", esp_err_to_name(err));
}
}
}
@@ -178,41 +182,67 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param
case ESP_GAP_BLE_EXT_ADV_SET_RAND_ADDR_COMPLETE_EVT:
ESP_LOGI(DEMO_TAG, "Extended adv random address set, status %d, "ESP_BD_ADDR_STR"",
param->ext_adv_set_rand_addr.status, ESP_BD_ADDR_HEX(new_rand_addr));
if (param->ext_adv_set_rand_addr.status != ESP_BT_STATUS_SUCCESS) {
is_advertising = false;
break;
}
esp_ble_gap_ext_adv_start(NUM_EXT_ADV_SET, &ext_adv[0]);
break;
case ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT:
ESP_LOGI(DEMO_TAG, "Extended advertising params set, status %d", param->ext_adv_set_params.status);
if (param->ext_adv_set_params.status != ESP_BT_STATUS_SUCCESS) {
break;
}
esp_ble_gap_config_ext_adv_data_raw(EXT_ADV_HANDLE, sizeof(adv_data_raw), &adv_data_raw[0]);
break;
case ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT:
ESP_LOGI(DEMO_TAG, "Extended advertising data set, status %d", param->ext_adv_data_set.status);
if (param->ext_adv_data_set.status != ESP_BT_STATUS_SUCCESS) {
break;
}
ble_prph_restart_adv();
break;
case ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT:
ESP_LOGI(DEMO_TAG, "Extended advertising start, status %d", param->ext_adv_start.status);
is_advertising = true;
if (param->ext_adv_start.status == ESP_BT_STATUS_SUCCESS) {
is_advertising = true;
} else {
is_advertising = false;
}
break;
case ESP_GAP_BLE_ADV_TERMINATED_EVT:
ESP_LOGI(DEMO_TAG, "Extended advertising terminated, status = %d", param->adv_terminate.status);
if (param->adv_terminate.status == 0x00) {
ESP_LOGI(DEMO_TAG, "Advertising successfully ended with a connection being created");
is_advertising = false;
}
/* Any terminate reason means advertising has stopped; clear flag so restart can run */
is_advertising = false;
break;
#else
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
ESP_LOGI(DEMO_TAG, "Advertising data set, status %d", param->adv_data_raw_cmpl.status);
if (param->adv_data_raw_cmpl.status != ESP_BT_STATUS_SUCCESS) {
break;
}
esp_ble_gap_addr_create_static(new_rand_addr);
esp_ble_gap_set_rand_addr(new_rand_addr);
break;
case ESP_GAP_BLE_SET_STATIC_RAND_ADDR_EVT:
ESP_LOGI(DEMO_TAG, "Random address set, status %d, addr "ESP_BD_ADDR_STR"",
param->set_rand_addr_cmpl.status, ESP_BD_ADDR_HEX(new_rand_addr));
if (param->set_rand_addr_cmpl.status != ESP_BT_STATUS_SUCCESS) {
is_advertising = false;
break;
}
esp_ble_gap_start_advertising(&legacy_adv_params);
break;
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
ESP_LOGI(DEMO_TAG, "Advertising start, status %d", param->adv_start_cmpl.status);
is_advertising = true;
if (param->adv_start_cmpl.status == ESP_BT_STATUS_SUCCESS) {
is_advertising = true;
} else {
is_advertising = false;
}
break;
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
ESP_LOGI(DEMO_TAG, "Advertising stop, status %d", param->adv_stop_cmpl.status);
@@ -248,16 +278,20 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event,
ESP_LOG_BUFFER_HEX(DEMO_TAG, param->write.value, param->write.len);
break;
case ESP_GATTS_CONNECT_EVT:
prph_conn_num++;
ESP_LOGI(DEMO_TAG, "Connected, conn_id %u, remote "ESP_BD_ADDR_STR", total %u",
param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda), ++prph_conn_num);
param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda), prph_conn_num);
is_advertising = false;
#if CONFIG_EXAMPLE_RESTART_ADV_AFTER_CONNECTED
ble_prph_restart_adv();
#endif
break;
case ESP_GATTS_DISCONNECT_EVT:
if (prph_conn_num > 0) {
prph_conn_num--;
}
ESP_LOGI(DEMO_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%x, total %d",
ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason, (prph_conn_num ? --prph_conn_num : prph_conn_num));
ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason, prph_conn_num);
/* start advertising again when disconnected */
ble_prph_restart_adv();
break;
@@ -274,7 +308,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event,
case ESP_GATTS_CREAT_ATTR_TAB_EVT:
{
ESP_LOGI(DEMO_TAG, "The number handle = %x", param->add_attr_tab.num_handle);
if (param->create.status == ESP_GATT_OK) {
if (param->add_attr_tab.status == ESP_GATT_OK) {
if (param->add_attr_tab.num_handle == HRS_IDX_NB) {
memcpy(profile_handle_table, param->add_attr_tab.handles,
sizeof(profile_handle_table));
@@ -289,7 +323,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event,
param->add_attr_tab.num_handle, HRS_IDX_NB);
}
} else {
ESP_LOGE(DEMO_TAG, " Create attribute table failed, error code = %x", param->create.status);
ESP_LOGE(DEMO_TAG, " Create attribute table failed, error code = %x", param->add_attr_tab.status);
}
break;
}
@@ -17,6 +17,7 @@
#include "driver/uart.h"
#include "esp_bt.h"
#include "esp_err.h"
#include "nvs_flash.h"
#include "esp_bt_device.h"
#include "esp_gap_ble_api.h"
@@ -110,6 +111,7 @@ static bool connect = false;
static char * notify_value_p = NULL;
static int notify_value_offset = 0;
static int notify_value_count = 0;
static size_t notify_value_alloc_size = 0;
static bool start = false;
static uint64_t notify_len = 0;
static uint64_t start_time = 0;
@@ -125,60 +127,71 @@ static esp_bt_uuid_t spp_service_uuid = {
.uuid = {.uuid16 = ESP_GATT_SPP_SERVICE_UUID,},
};
/* Drop any in-flight notify reassembly buffer/counters. Used both on errors and on disconnect. */
static void reset_notify_reasm(void)
{
if (notify_value_p != NULL) {
free(notify_value_p);
notify_value_p = NULL;
}
notify_value_offset = 0;
notify_value_count = 0;
notify_value_alloc_size = 0;
}
static void notify_event_handler(esp_ble_gattc_cb_param_t * p_data)
{
uint8_t handle = 0;
uint16_t handle = p_data->notify.handle;
handle = p_data->notify.handle;
if (db == NULL) {
ESP_LOGE(GATTC_TAG, " %s db is NULL", __func__);
return;
}
if (handle == db[SPP_IDX_SPP_DATA_NTY_VAL].attribute_handle) {
if ((p_data->notify.value[0] == '#') && (p_data->notify.value[1] == '#')) {
if ((++notify_value_count) != p_data->notify.value[3]) {
if(notify_value_p != NULL){
free(notify_value_p);
}
notify_value_count = 0;
notify_value_p = NULL;
notify_value_offset = 0;
ESP_LOGE(GATTC_TAG,"notify value count is not continuous, %s", __func__);
/* Fragment header is 4 bytes: "##", total_frags, seq. */
if (p_data->notify.value_len >= 4 &&
p_data->notify.value[0] == '#' && p_data->notify.value[1] == '#') {
uint8_t total_frags = p_data->notify.value[2];
uint8_t seq = p_data->notify.value[3];
uint16_t payload_len = p_data->notify.value_len - 4;
if ((++notify_value_count) != seq) {
ESP_LOGE(GATTC_TAG, "notify value count is not continuous, %s", __func__);
reset_notify_reasm();
return;
}
if (p_data->notify.value[3] == 1) {
notify_value_p = (char *)malloc(((spp_mtu_size-7)*(p_data->notify.value[2]))*sizeof(char));
if (seq == 1) {
notify_value_alloc_size = (size_t)(spp_mtu_size - 7) * total_frags;
notify_value_p = (char *)malloc(notify_value_alloc_size);
if (notify_value_p == NULL) {
ESP_LOGE(GATTC_TAG, "malloc failed, %s L#%d", __func__, __LINE__);
notify_value_count = 0;
reset_notify_reasm();
return;
}
memcpy((notify_value_p + notify_value_offset), (p_data->notify.value + 4), (p_data->notify.value_len - 4));
if (p_data->notify.value[2] == p_data->notify.value[3]) {
uart_write_bytes(UART_NUM_0, (char *)(notify_value_p), (p_data->notify.value_len - 4 + notify_value_offset));
free(notify_value_p);
notify_value_p = NULL;
notify_value_offset = 0;
return;
}
notify_value_offset += (p_data->notify.value_len - 4);
} else if (p_data->notify.value[3] <= p_data->notify.value[2]) {
memcpy((notify_value_p + notify_value_offset), (p_data->notify.value + 4), (p_data->notify.value_len - 4));
if (p_data->notify.value[3] == p_data->notify.value[2]) {
uart_write_bytes(UART_NUM_0, (char *)(notify_value_p), (p_data->notify.value_len - 4 + notify_value_offset));
free(notify_value_p);
notify_value_count = 0;
notify_value_p = NULL;
notify_value_offset = 0;
return;
}
notify_value_offset += (p_data->notify.value_len - 4);
} else if (notify_value_p == NULL) {
ESP_LOGE(GATTC_TAG, "fragment %u without start, %s", seq, __func__);
reset_notify_reasm();
return;
}
/* Bound the write against the actually allocated reasm buffer to defeat
* malicious peers that send total_frags=0 or grow total_frags mid-stream. */
if ((size_t)notify_value_offset + payload_len > notify_value_alloc_size) {
ESP_LOGE(GATTC_TAG, "fragment payload would overflow reasm buffer, %s", __func__);
reset_notify_reasm();
return;
}
memcpy(notify_value_p + notify_value_offset, p_data->notify.value + 4, payload_len);
if (seq == total_frags) {
uart_write_bytes(UART_NUM_0, notify_value_p, payload_len + notify_value_offset);
reset_notify_reasm();
return;
}
notify_value_offset += payload_len;
} else {
uart_write_bytes(UART_NUM_0, (char *)(p_data->notify.value), p_data->notify.value_len);
}
} else if (handle == ((db+SPP_IDX_SPP_STATUS_VAL)->attribute_handle)) {
} else if (handle == db[SPP_IDX_SPP_STATUS_VAL].attribute_handle) {
ESP_LOG_BUFFER_CHAR(GATTC_TAG, (char *)p_data->notify.value, p_data->notify.value_len);
//TODO:server notify status characteristic
} else {
@@ -196,13 +209,42 @@ static void free_gattc_srv_db(void)
cmd = 0;
spp_srv_start_handle = 0;
spp_srv_end_handle = 0;
notify_value_p = NULL;
notify_value_offset = 0;
notify_value_count = 0;
reset_notify_reasm();
if (db) {
free(db);
db = NULL;
}
count = SPP_IDX_NB;
}
/**
* Resolve the CCCD handle for a known notify-capable characteristic value index by looking up the
* adjacent ESP_GATT_UUID_CHAR_CLIENT_CONFIG descriptor. Returns 0 on mismatch.
*
* This is safer than `(db+cmd+1)->attribute_handle` because the descriptor slot is verified.
*/
static uint16_t spp_get_cccd_handle(uint16_t char_val_idx)
{
size_t cfg_idx;
if (db == NULL) {
return 0;
}
switch (char_val_idx) {
case SPP_IDX_SPP_DATA_NTY_VAL: cfg_idx = SPP_IDX_SPP_DATA_NTF_CFG; break;
case SPP_IDX_SPP_STATUS_VAL: cfg_idx = SPP_IDX_SPP_STATUS_CFG; break;
#ifdef SUPPORT_HEARTBEAT
case SPP_IDX_SPP_HEARTBEAT_VAL: cfg_idx = SPP_IDX_SPP_HEARTBEAT_CFG; break;
#endif
default: return 0;
}
const esp_gattc_db_elem_t *d = &db[cfg_idx];
if (d->type != ESP_GATT_DB_DESCRIPTOR ||
d->uuid.len != ESP_UUID_LEN_16 ||
d->uuid.uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
return 0;
}
return d->attribute_handle;
}
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
@@ -246,7 +288,9 @@ static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *par
ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len);
ESP_LOGI(GATTC_TAG, "Scan result, device "ESP_BD_ADDR_STR", name len %u", ESP_BD_ADDR_HEX(scan_result->scan_rst.bda), adv_name_len);
ESP_LOG_BUFFER_CHAR(GATTC_TAG, adv_name, adv_name_len);
if (adv_name != NULL && strncmp((char *)adv_name, device_name, adv_name_len) == 0) {
/* Full-name match (strncmp(adv_name, device_name, adv_name_len) would also accept prefixes). */
if (adv_name != NULL && adv_name_len == (sizeof(device_name) - 1) &&
memcmp(adv_name, device_name, adv_name_len) == 0) {
if (connect == false) {
connect = true;
esp_ble_gap_stop_scanning();
@@ -373,6 +417,11 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
if (p_data->reg_for_notify.status != ESP_GATT_OK) {
break;
}
uint16_t descr_handle = spp_get_cccd_handle(cmd);
if (descr_handle == 0) {
ESP_LOGW(GATTC_TAG, "CCCD handle not resolved for char index %u", (unsigned)cmd);
break;
}
uint16_t notify_en = 0x01;
#ifdef CONFIG_EXAMPLE_SPP_RELIABLE
if (cmd == SPP_IDX_SPP_DATA_NTY_VAL) {
@@ -382,7 +431,7 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
esp_ble_gattc_write_char_descr(
spp_gattc_if,
spp_conn_id,
(db+cmd+1)->attribute_handle,
descr_handle,
sizeof(notify_en),
(uint8_t *)&notify_en,
ESP_GATT_WRITE_TYPE_RSP,
@@ -438,26 +487,35 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
break;
case ESP_GATTC_CFG_MTU_EVT:
ESP_LOGI(GATTC_TAG, "MTU exchange, status %d, MTU %d", param->cfg_mtu.status, param->cfg_mtu.mtu);
if(p_data->cfg_mtu.status != ESP_OK){
if (param->cfg_mtu.status != ESP_GATT_OK) {
break;
}
spp_mtu_size = p_data->cfg_mtu.mtu;
spp_mtu_size = param->cfg_mtu.mtu;
db = (esp_gattc_db_elem_t *)malloc(count*sizeof(esp_gattc_db_elem_t));
if(db == NULL){
if (db != NULL) {
free(db);
db = NULL;
}
count = SPP_IDX_NB;
db = (esp_gattc_db_elem_t *)malloc(count * sizeof(esp_gattc_db_elem_t));
if (db == NULL) {
ESP_LOGE(GATTC_TAG, "Malloc db failed");
break;
}
if(esp_ble_gattc_get_db(spp_gattc_if, spp_conn_id, spp_srv_start_handle, spp_srv_end_handle, db, &count) != ESP_GATT_OK){
if (esp_ble_gattc_get_db(spp_gattc_if, spp_conn_id, spp_srv_start_handle, spp_srv_end_handle, db, &count) != ESP_GATT_OK) {
ESP_LOGE(GATTC_TAG, "Get db failed");
free(db);
db = NULL;
break;
}
if(count != SPP_IDX_NB){
if (count != SPP_IDX_NB) {
ESP_LOGE(GATTC_TAG, "Get db count != SPP_IDX_NB, count = %d, SPP_IDX_NB = %d", count, SPP_IDX_NB);
free(db);
db = NULL;
break;
}
for(int i = 0;i < SPP_IDX_NB;i++){
switch((db+i)->type){
for (int i = 0; i < SPP_IDX_NB; i++) {
switch ((db + i)->type) {
case ESP_GATT_DB_PRIMARY_SERVICE:
ESP_LOGI(GATTC_TAG, "PRIMARY_SERVICE, attribute_handle %d, start_handle %d, end_handle %d, properties 0x%x, uuid 0x%04x",
(db+i)->attribute_handle, (db+i)->start_handle, (db+i)->end_handle, (db+i)->properties, (db+i)->uuid.uuid.uuid16);
@@ -572,11 +630,11 @@ void ble_client_appRegister(void)
ESP_LOGE(GATTC_TAG, "set local MTU failed: %s", esp_err_to_name_r(local_mtu_ret, err_msg, sizeof(err_msg)));
}
cmd_reg_queue = xQueueCreate(10, sizeof(uint32_t));
cmd_reg_queue = xQueueCreate(10, sizeof(uint16_t));
xTaskCreate(spp_client_reg_task, "spp_client_reg_task", 2048, NULL, 10, NULL);
#ifdef SUPPORT_HEARTBEAT
cmd_heartbeat_queue = xQueueCreate(10, sizeof(uint32_t));
cmd_heartbeat_queue = xQueueCreate(10, sizeof(uint16_t));
xTaskCreate(spp_heart_beat_task, "spp_heart_beat_task", 2048, NULL, 10, NULL);
#endif
esp_ble_gattc_app_register(PROFILE_APP_ID);
@@ -58,7 +58,7 @@ static const uint8_t spp_adv_data[23] = {
0x0F, ESP_BLE_AD_TYPE_NAME_CMPL, 'E', 'S', 'P', '_', 'S', 'P', 'P', '_', 'S', 'E', 'R','V', 'E', 'R'
};
static uint16_t spp_mtu_size = SPP_GATT_MTU_SIZE;
static uint16_t spp_mtu_size = 23;
static uint16_t spp_conn_id = 0xffff;
static esp_gatt_if_t spp_gatts_if = 0xff;
QueueHandle_t spp_uart_queue = NULL;
@@ -107,19 +107,25 @@ typedef struct spp_receive_data_node{
struct spp_receive_data_node * next_node;
}spp_receive_data_node_t;
static spp_receive_data_node_t * temp_spp_recv_data_node_p1 = NULL;
static spp_receive_data_node_t * temp_spp_recv_data_node_p2 = NULL;
typedef struct spp_receive_data_buff{
int32_t node_num;
int32_t buff_size;
spp_receive_data_node_t * first_node;
spp_receive_data_node_t *first_node;
spp_receive_data_node_t *last_node;
}spp_receive_data_buff_t;
static spp_receive_data_buff_t SppRecvDataBuff = {
/* Command queue carries (data, len); we cannot use strlen() on the payload because
* BLE writes are not NUL-terminated. */
typedef struct {
uint16_t len;
uint8_t *data;
} spp_cmd_queue_msg_t;
static spp_receive_data_buff_t spp_prep_wr_buff = {
.node_num = 0,
.buff_size = 0,
.first_node = NULL
.first_node = NULL,
.last_node = NULL,
};
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
@@ -270,67 +276,74 @@ static uint8_t find_char_and_desr_index(uint16_t handle)
static bool store_wr_buffer(esp_ble_gatts_cb_param_t *p_data)
{
temp_spp_recv_data_node_p1 = (spp_receive_data_node_t *)malloc(sizeof(spp_receive_data_node_t));
if(temp_spp_recv_data_node_p1 == NULL){
ESP_LOGI(GATTS_TABLE_TAG, "malloc error %s %d", __func__, __LINE__);
if (p_data == NULL) {
return false;
}
temp_spp_recv_data_node_p1->len = p_data->write.len;
temp_spp_recv_data_node_p1->next_node = NULL;
temp_spp_recv_data_node_p1->node_buff = (uint8_t *)malloc(p_data->write.len);
if (temp_spp_recv_data_node_p1->node_buff == NULL) {
ESP_LOGI(GATTS_TABLE_TAG, "malloc error %s %d\n", __func__, __LINE__);
// Security fix: Free the node and return false to prevent memory leak
free(temp_spp_recv_data_node_p1);
temp_spp_recv_data_node_p1 = NULL;
/* Per Bluetooth Core Spec (Vol 3, Part F, 3.4.6.1) the Part Attribute Value
* in ATT_PREPARE_WRITE_REQ may be 0..(ATT_MTU-5) bytes, so a zero-length
* fragment is a valid request that carries no payload. Skip allocating an
* empty node here: malloc(0) is implementation-defined in C and on
* ESP-IDF's multi_heap returns NULL, which would otherwise be reported as
* a (false) allocation failure. */
if (p_data->write.len == 0) {
return true;
}
spp_receive_data_node_t *node = (spp_receive_data_node_t *)malloc(sizeof(spp_receive_data_node_t));
if (node == NULL) {
ESP_LOGE(GATTS_TABLE_TAG, "malloc error %s %d", __func__, __LINE__);
return false;
}
memcpy(temp_spp_recv_data_node_p1->node_buff, p_data->write.value, p_data->write.len);
// Security fix: Link to list only after successful allocation
if(temp_spp_recv_data_node_p2 != NULL){
temp_spp_recv_data_node_p2->next_node = temp_spp_recv_data_node_p1;
node->len = p_data->write.len;
node->next_node = NULL;
node->node_buff = (uint8_t *)malloc(p_data->write.len);
if (node->node_buff == NULL) {
ESP_LOGE(GATTS_TABLE_TAG, "malloc error %s %d", __func__, __LINE__);
free(node);
return false;
}
temp_spp_recv_data_node_p2 = temp_spp_recv_data_node_p1;
SppRecvDataBuff.buff_size += p_data->write.len;
memcpy(node->node_buff, p_data->write.value, p_data->write.len);
if(SppRecvDataBuff.node_num == 0){
SppRecvDataBuff.first_node = temp_spp_recv_data_node_p1;
SppRecvDataBuff.node_num++;
}else{
SppRecvDataBuff.node_num++;
if (spp_prep_wr_buff.last_node != NULL) {
spp_prep_wr_buff.last_node->next_node = node;
} else {
spp_prep_wr_buff.first_node = node;
}
spp_prep_wr_buff.last_node = node;
spp_prep_wr_buff.buff_size += p_data->write.len;
spp_prep_wr_buff.node_num++;
return true;
}
static void free_write_buffer(void)
static void free_prep_wr_buffer(void)
{
temp_spp_recv_data_node_p1 = SppRecvDataBuff.first_node;
spp_receive_data_node_t *cur = spp_prep_wr_buff.first_node;
while(temp_spp_recv_data_node_p1 != NULL){
temp_spp_recv_data_node_p2 = temp_spp_recv_data_node_p1->next_node;
if (temp_spp_recv_data_node_p1->node_buff) {
free(temp_spp_recv_data_node_p1->node_buff);
while (cur != NULL) {
spp_receive_data_node_t *next = cur->next_node;
if (cur->node_buff) {
free(cur->node_buff);
}
free(temp_spp_recv_data_node_p1);
temp_spp_recv_data_node_p1 = temp_spp_recv_data_node_p2;
free(cur);
cur = next;
}
SppRecvDataBuff.node_num = 0;
SppRecvDataBuff.buff_size = 0;
SppRecvDataBuff.first_node = NULL;
spp_prep_wr_buff.node_num = 0;
spp_prep_wr_buff.buff_size = 0;
spp_prep_wr_buff.first_node = NULL;
spp_prep_wr_buff.last_node = NULL;
}
static void print_write_buffer(void)
static void print_prep_wr_buffer(void)
{
temp_spp_recv_data_node_p1 = SppRecvDataBuff.first_node;
spp_receive_data_node_t *cur = spp_prep_wr_buff.first_node;
while (temp_spp_recv_data_node_p1 != NULL) {
uart_write_bytes(UART_NUM_0, (char *)(temp_spp_recv_data_node_p1->node_buff), temp_spp_recv_data_node_p1->len);
temp_spp_recv_data_node_p1 = temp_spp_recv_data_node_p1->next_node;
while (cur != NULL) {
uart_write_bytes(UART_NUM_0, (char *)(cur->node_buff), cur->len);
cur = cur->next_node;
}
}
@@ -448,22 +461,26 @@ static void spp_uart_init(void)
#ifdef SUPPORT_HEARTBEAT
void spp_heartbeat_task(void * arg)
{
uint16_t cmd_id;
uint32_t cmd_id;
for(;;) {
vTaskDelay(50 / portTICK_PERIOD_MS);
if(xQueueReceive(cmd_heartbeat_queue, &cmd_id, portMAX_DELAY)) {
while(1){
heartbeat_count_num++;
vTaskDelay(5000/ portTICK_PERIOD_MS);
if((heartbeat_count_num >3)&&(is_connected)){
esp_ble_gap_disconnect(spp_remote_bda);
}
if(is_connected && enable_heart_ntf){
esp_ble_gatts_send_indicate(spp_gatts_if, spp_conn_id, spp_handle_table[SPP_IDX_SPP_HEARTBEAT_VAL],sizeof(heartbeat_s), heartbeat_s, false);
}else if(!is_connected){
heartbeat_count_num = 0;
while (1) {
if (!is_connected) {
break;
}
vTaskDelay(5000 / portTICK_PERIOD_MS);
heartbeat_count_num++;
if ((heartbeat_count_num > 3) && is_connected) {
esp_ble_gap_disconnect(spp_remote_bda);
break;
}
if (is_connected && enable_heart_ntf) {
esp_ble_gatts_send_indicate(spp_gatts_if, spp_conn_id, spp_handle_table[SPP_IDX_SPP_HEARTBEAT_VAL],
sizeof(heartbeat_s), heartbeat_s, false);
}
}
}
}
@@ -473,13 +490,15 @@ void spp_heartbeat_task(void * arg)
void spp_cmd_task(void * arg)
{
uint8_t * cmd_id;
spp_cmd_queue_msg_t msg;
for (;;) {
vTaskDelay(50 / portTICK_PERIOD_MS);
if(xQueueReceive(cmd_cmd_queue, &cmd_id, portMAX_DELAY)) {
ESP_LOG_BUFFER_CHAR(GATTS_TABLE_TAG, (char *)(cmd_id), strlen((char *)cmd_id));
free(cmd_id);
if (xQueueReceive(cmd_cmd_queue, &msg, portMAX_DELAY)) {
if (msg.data != NULL) {
ESP_LOG_BUFFER_CHAR(GATTS_TABLE_TAG, (char *)msg.data, msg.len);
free(msg.data);
}
}
}
vTaskDelete(NULL);
@@ -498,7 +517,7 @@ static void spp_task_init(void)
xTaskCreate(spp_heartbeat_task, "spp_heartbeat_task", 2048, NULL, 10, NULL);
#endif
cmd_cmd_queue = xQueueCreate(10, sizeof(uint32_t));
cmd_cmd_queue = xQueueCreate(10, sizeof(spp_cmd_queue_msg_t));
xTaskCreate(spp_cmd_task, "spp_cmd_task", 4096, NULL, 10, NULL);
}
@@ -517,7 +536,7 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param
ESP_LOGI(GATTS_TABLE_TAG, "Advertising start successfully");
break;
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(GATTS_TABLE_TAG, "Advertising stop failed, status %d", param->adv_stop_cmpl.status);
break;
}
@@ -555,15 +574,17 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
res = find_char_and_desr_index(p_data->write.handle);
if (p_data->write.is_prep == false) {
if (res == SPP_IDX_SPP_COMMAND_VAL) {
uint8_t * spp_cmd_buff = NULL;
spp_cmd_buff = (uint8_t *)malloc((spp_mtu_size - 3) * sizeof(uint8_t));
if(spp_cmd_buff == NULL){
uint8_t *spp_cmd_buff = (uint8_t *)malloc(p_data->write.len);
if (spp_cmd_buff == NULL) {
ESP_LOGE(GATTS_TABLE_TAG, "%s malloc failed", __func__);
break;
}
memset(spp_cmd_buff, 0x0, (spp_mtu_size - 3));
memcpy(spp_cmd_buff, p_data->write.value, p_data->write.len);
xQueueSend(cmd_cmd_queue, &spp_cmd_buff, 10/portTICK_PERIOD_MS);
spp_cmd_queue_msg_t msg = { .len = p_data->write.len, .data = spp_cmd_buff };
if (xQueueSend(cmd_cmd_queue, &msg, 10 / portTICK_PERIOD_MS) != pdTRUE) {
ESP_LOGE(GATTS_TABLE_TAG, "%s cmd_cmd_queue send failed", __func__);
free(spp_cmd_buff);
}
} else if (res == SPP_IDX_SPP_DATA_NTF_CFG) {
if ((p_data->write.len == 2) && (p_data->write.value[0] == 0x01) && (p_data->write.value[1] == 0x00)) {
ESP_LOGI(GATTS_TABLE_TAG, "SPP data notification enable");
@@ -587,9 +608,11 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
if ((p_data->write.len == 2) && (p_data->write.value[0] == 0x01) && (p_data->write.value[1] == 0x00)) {
ESP_LOGI(GATTS_TABLE_TAG, "SPP heartbeat notification enable");
enable_heart_ntf = true;
heartbeat_count_num = 0;
} else if ((p_data->write.len == 2) && (p_data->write.value[0] == 0x00) && (p_data->write.value[1] == 0x00)) {
ESP_LOGI(GATTS_TABLE_TAG, "SPP heartbeat notification disable");
enable_heart_ntf = false;
heartbeat_count_num = 0;
}
} else if (res == SPP_IDX_SPP_HEARTBEAT_VAL) {
if ((p_data->write.len == sizeof(heartbeat_s)) && (memcmp(heartbeat_s, p_data->write.value, sizeof(heartbeat_s)) == 0)) {
@@ -611,14 +634,15 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
}
break;
}
case ESP_GATTS_EXEC_WRITE_EVT: {
ESP_LOGI(GATTS_TABLE_TAG, "Execute write");
if (p_data->exec_write.exec_write_flag) {
print_write_buffer();
free_write_buffer();
}
break;
}
case ESP_GATTS_EXEC_WRITE_EVT:
/* End of prepared-write transaction: print on EXEC, then always release queued chunks
* (master only freed on EXEC, which leaked on CANCEL). */
ESP_LOGI(GATTS_TABLE_TAG, "Execute write flag 0x%02x", p_data->exec_write.exec_write_flag);
if (p_data->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) {
print_prep_wr_buffer();
}
free_prep_wr_buffer();
break;
case ESP_GATTS_RESPONSE_EVT:
break;
case ESP_GATTS_MTU_EVT:
@@ -643,18 +667,20 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
case ESP_GATTS_CONNECT_EVT:
ESP_LOGI(GATTS_TABLE_TAG, "Connected, conn_id %u, remote "ESP_BD_ADDR_STR"",
param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda));
free_prep_wr_buffer();
spp_conn_id = p_data->connect.conn_id;
spp_gatts_if = gatts_if;
is_connected = true;
memcpy(&spp_remote_bda,&p_data->connect.remote_bda,sizeof(esp_bd_addr_t));
#ifdef SUPPORT_HEARTBEAT
uint16_t cmd = 0;
xQueueSend(cmd_heartbeat_queue,&cmd,10/portTICK_PERIOD_MS);
uint32_t cmd = 0;
xQueueSend(cmd_heartbeat_queue, &cmd, 10 / portTICK_PERIOD_MS);
#endif
break;
case ESP_GATTS_DISCONNECT_EVT:
ESP_LOGI(GATTS_TABLE_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x",
ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason);
free_prep_wr_buffer();
spp_mtu_size = 23;
is_connected = false;
enable_data_ntf = false;
@@ -669,6 +695,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
case ESP_GATTS_CANCEL_OPEN_EVT:
break;
case ESP_GATTS_CLOSE_EVT:
free_prep_wr_buffer();
break;
case ESP_GATTS_LISTEN_EVT:
break;
@@ -125,6 +125,9 @@ static uint8_t check_sum(uint8_t *addr, uint16_t count)
if (addr == NULL || count == 0) {
return 0;
}
if (count > (ESP_GATT_MAX_MTU_SIZE - 3U)) {
return 0;
}
for(int i = 0; i < count; i++) {
sum = sum + addr[i];
@@ -287,6 +290,7 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
/* free descr_elem_result */
free(descr_elem_result);
descr_elem_result = NULL;
}
}
else{
@@ -298,10 +302,23 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
}
case ESP_GATTC_NOTIFY_EVT: {
#if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT)
if (p_data->notify.is_notify &&
(p_data->notify.value[p_data->notify.value_len - 1] ==
check_sum(p_data->notify.value, p_data->notify.value_len - 1))){
notify_len += p_data->notify.value_len;
if (p_data->notify.is_notify) {
uint16_t vlen = p_data->notify.value_len;
uint8_t *val = p_data->notify.value;
const uint16_t max_notify_len = (uint16_t)(ESP_GATT_MAX_MTU_SIZE - 3U);
if (val == NULL) {
ESP_LOGW(GATTC_TAG, "notify ignored: null value");
} else if (vlen == 0) {
ESP_LOGW(GATTC_TAG, "notify ignored: zero length");
} else if (vlen < 2) {
ESP_LOGW(GATTC_TAG, "notify ignored: length too short for payload+checksum");
} else if (vlen > max_notify_len) {
ESP_LOGW(GATTC_TAG, "notify ignored: length exceeds bound");
} else if (val[vlen - 1] == check_sum(val, (uint16_t)(vlen - 1U))) {
notify_len += vlen;
} else {
ESP_LOGE(GATTC_TAG, "notify checksum mismatch");
}
} else {
ESP_LOGE(GATTC_TAG, "Indication received, value:");
}
@@ -346,6 +363,11 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
current_time = 0;
notify_len = 0;
#endif /* #if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) */
#if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT)
/* Unblock throughput_client_task if it is waiting on gattc_semaphore while congested. */
can_send_write = true;
xSemaphoreGive(gattc_semaphore);
#endif /* #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) */
ESP_LOGI(GATTC_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x",
ESP_BD_ADDR_HEX(p_data->disconnect.remote_bda), p_data->disconnect.reason);
break;
@@ -404,7 +426,7 @@ static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *par
esp_ble_conn_params_t phy_1m_conn_params = {0};
#if(CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT && CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT)
phy_1m_conn_params.interval_max = 34;
phy_1m_conn_params.interval_min = 34;
phy_1m_conn_params.interval_max = 34;
#else
phy_1m_conn_params.interval_max = 32;
@@ -539,9 +561,14 @@ static void throughput_cal_task(void *param)
uint32_t bit_rate = 0;
if (start_time) {
current_time = esp_timer_get_time();
bit_rate = notify_len * SECOND_TO_USECOND / (current_time - start_time);
ESP_LOGI(GATTC_TAG, "Notify Bit rate = %" PRIu32 " Byte/s, = %" PRIu32 " bit/s, time = %ds",
bit_rate, bit_rate<<3, (int)((current_time - start_time) / SECOND_TO_USECOND));
uint64_t elapsed_us = current_time - start_time;
if (elapsed_us > 0) {
bit_rate = (uint32_t)(notify_len * SECOND_TO_USECOND / elapsed_us);
ESP_LOGI(GATTC_TAG, "Notify Bit rate = %" PRIu32 " Byte/s, = %" PRIu32 " bit/s, time = %ds",
bit_rate, bit_rate << 3, (int)(elapsed_us / SECOND_TO_USECOND));
} else {
ESP_LOGI(GATTC_TAG, "Notify Bit rate = 0 Byte/s, = 0 bit/s (elapsed 0 us)");
}
} else {
ESP_LOGI(GATTC_TAG, "Notify Bit rate = 0 Byte/s, = 0 bit/s");
}
@@ -599,6 +626,17 @@ void app_main(void)
return;
}
#if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT)
/* Create the semaphore before registering the GATTC callback so that any
* xSemaphoreGive() invoked from the callback is guaranteed to see a valid handle.
*/
gattc_semaphore = xSemaphoreCreateBinary();
if (!gattc_semaphore) {
ESP_LOGE(GATTC_TAG, "%s, init fail, the gattc semaphore create fail.", __func__);
return;
}
#endif /* #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) */
//register the callback function to the gattc module
ret = esp_ble_gattc_register_callback(esp_gattc_cb);
if(ret){
@@ -624,12 +662,4 @@ void app_main(void)
#if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT)
xTaskCreatePinnedToCore(&throughput_cal_task, "throughput_cal_task", 4096, NULL, 9, NULL, BLUETOOTH_TASK_PINNED_TO_CORE);
#endif
#if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT)
gattc_semaphore = xSemaphoreCreateBinary();
if (!gattc_semaphore) {
ESP_LOGE(GATTC_TAG, "%s, init fail, the gattc semaphore create fail.", __func__);
return;
}
#endif /* #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) */
}
@@ -58,6 +58,7 @@ static bool start = false;
static uint64_t write_len = 0;
static uint64_t start_time = 0;
static uint64_t current_time = 0;
static portMUX_TYPE s_write_throughput_stats_mux = portMUX_INITIALIZER_UNLOCKED;
#endif /* #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) */
static bool is_connect = false;
@@ -203,6 +204,18 @@ static prepare_type_env_t a_prepare_write_env;
void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param);
void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param);
static void prepare_write_env_clear(prepare_type_env_t *env)
{
if (env == NULL) {
return;
}
if (env->prepare_buf != NULL) {
free(env->prepare_buf);
env->prepare_buf = NULL;
}
env->prepare_len = 0;
}
static uint8_t check_sum(uint8_t *addr, uint16_t count)
{
uint32_t sum = 0;
@@ -210,6 +223,9 @@ static uint8_t check_sum(uint8_t *addr, uint16_t count)
if (addr == NULL || count == 0) {
return 0;
}
if (count > (ESP_GATT_MAX_MTU_SIZE - 3U)) {
return 0;
}
for(int i = 0; i < count; i++) {
sum = sum + addr[i];
@@ -333,14 +349,14 @@ void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare
}
void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param){
if (prepare_write_env == NULL) {
ESP_LOGE(GATTS_TAG, "exec_write: prepare_write_env is NULL");
return;
}
if (param->exec_write.exec_write_flag != ESP_GATT_PREP_WRITE_EXEC){
ESP_LOGI(GATTS_TAG,"Prepare write cancel");
}
if (prepare_write_env->prepare_buf) {
free(prepare_write_env->prepare_buf);
prepare_write_env->prepare_buf = NULL;
}
prepare_write_env->prepare_len = 0;
prepare_write_env_clear(prepare_write_env);
}
static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) {
@@ -441,17 +457,32 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i
example_write_event_env(gatts_if, &a_prepare_write_env, param);
#if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT)
if (param->write.handle == gl_profile_tab[PROFILE_A_APP_ID].char_handle) {
// The last value byte is the checksum data, should used to check the data is received corrected or not.
if (param->write.value[param->write.len - 1] ==
check_sum(param->write.value, param->write.len - 1)) {
write_len += param->write.len;
uint16_t wlen = param->write.len;
uint8_t *wval = param->write.value;
/* len==0 makes (wlen-1) wrap; cap len before indexing or passing to check_sum. */
const uint16_t max_write_len = (uint16_t)(ESP_GATT_MAX_MTU_SIZE - 3U);
if (wval == NULL) {
ESP_LOGW(GATTS_TAG, "write ignored: null value");
} else if (wlen == 0) {
ESP_LOGW(GATTS_TAG, "write ignored: zero length");
} else if (wlen < 2) {
ESP_LOGW(GATTS_TAG, "write ignored: length too short for payload+checksum");
} else if (wlen > max_write_len) {
ESP_LOGW(GATTS_TAG, "write ignored: length exceeds bound");
} else if (wval[wlen - 1] == check_sum(wval, (uint16_t)(wlen - 1U))) {
portENTER_CRITICAL(&s_write_throughput_stats_mux);
write_len += wlen;
portEXIT_CRITICAL(&s_write_throughput_stats_mux);
} else {
ESP_LOGE(GATTS_TAG, "write checksum mismatch");
}
portENTER_CRITICAL(&s_write_throughput_stats_mux);
if (start == false) {
start_time = esp_timer_get_time();
start = true;
break;
}
portEXIT_CRITICAL(&s_write_throughput_stats_mux);
}
#endif /* #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) */
@@ -461,11 +492,13 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i
ESP_LOGI(GATTS_TAG,"Execute write");
#if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT)
if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_CANCEL) {
portENTER_CRITICAL(&s_write_throughput_stats_mux);
if (write_len > a_prepare_write_env.prepare_len) {
write_len -= a_prepare_write_env.prepare_len;
} else {
write_len = 0;
}
portEXIT_CRITICAL(&s_write_throughput_stats_mux);
}
#endif /* #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) */
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL);
@@ -537,10 +570,34 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i
ESP_LOGI(GATTS_TAG, "Connected, conn_id %d, remote "ESP_BD_ADDR_STR"",
param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda));
gl_profile_tab[PROFILE_A_APP_ID].conn_id = param->connect.conn_id;
prepare_write_env_clear(&a_prepare_write_env);
#if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT)
portENTER_CRITICAL(&s_write_throughput_stats_mux);
write_len = 0;
start_time = 0;
start = false;
current_time = 0;
portEXIT_CRITICAL(&s_write_throughput_stats_mux);
#endif
#if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT)
can_send_notify = false;
#endif
break;
}
case ESP_GATTS_DISCONNECT_EVT:
is_connect = false;
prepare_write_env_clear(&a_prepare_write_env);
#if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT)
portENTER_CRITICAL(&s_write_throughput_stats_mux);
write_len = 0;
start_time = 0;
start = false;
current_time = 0;
portEXIT_CRITICAL(&s_write_throughput_stats_mux);
#endif
#if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT)
can_send_notify = false;
#endif
ESP_LOGI(GATTS_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);
@@ -636,11 +693,22 @@ void throughput_cal_task(void *param)
{
uint32_t bit_rate = 0;
vTaskDelay(2000 / portTICK_PERIOD_MS);
if (is_connect && start_time) {
current_time = esp_timer_get_time();
bit_rate = write_len * SECOND_TO_USECOND / (current_time - start_time);
ESP_LOGI(GATTS_TAG, "GATTC write Bit rate = %" PRIu32 " Byte/s, = %" PRIu32 " bit/s, time %d",
bit_rate, bit_rate<<3, (int)((current_time - start_time) / SECOND_TO_USECOND));
if (is_connect) {
uint64_t snap_write_len;
uint64_t snap_start_time;
portENTER_CRITICAL(&s_write_throughput_stats_mux);
snap_start_time = start_time;
snap_write_len = write_len;
portEXIT_CRITICAL(&s_write_throughput_stats_mux);
if (snap_start_time != 0) {
current_time = esp_timer_get_time();
uint64_t elapsed_us = current_time - snap_start_time;
if (elapsed_us > 0) {
bit_rate = (uint32_t)(snap_write_len * SECOND_TO_USECOND / elapsed_us);
ESP_LOGI(GATTS_TAG, "GATTC write Bit rate = %" PRIu32 " Byte/s, = %" PRIu32 " bit/s, time %d",
bit_rate, bit_rate << 3, (int)(elapsed_us / SECOND_TO_USECOND));
}
}
}
}
@@ -201,6 +201,7 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
}
/* free char_elem_result */
free(char_elem_result);
char_elem_result = NULL;
}else{
ESP_LOGE(GATTC_TAG, "no char found");
}
@@ -259,6 +260,7 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
/* free descr_elem_result */
free(descr_elem_result);
descr_elem_result = NULL;
}
}
else{
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -15,6 +15,7 @@
****************************************************************************/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
@@ -177,6 +178,7 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
case ESP_GATTC_OPEN_EVT:
if (param->open.status != ESP_GATT_OK){
ESP_LOGE(GATTC_TAG, "Open failed, status %x", p_data->open.status);
connect = false;
break;
}
ESP_LOGI(GATTC_TAG, "Open successfully, MTU %d", p_data->open.mtu);
@@ -177,9 +177,9 @@ static const esp_gatts_attr_db_t heart_rate_gatt_db[HRS_IDX_NB] =
sizeof(uint8_t), sizeof(heart_ctrl_point), (uint8_t *)heart_ctrl_point}},
};
static char *esp_key_type_to_str(esp_ble_key_type_t key_type)
static const char *esp_key_type_to_str(esp_ble_key_type_t key_type)
{
char *key_str = NULL;
const char *key_str = NULL;
switch(key_type) {
case ESP_LE_KEY_NONE:
key_str = "ESP_LE_KEY_NONE";
@@ -256,12 +256,16 @@ static char *esp_auth_req_to_str(esp_ble_auth_req_t auth_req)
static void show_bonded_devices(void)
{
int dev_num = esp_ble_get_bond_device_num();
if (dev_num < 0) {
ESP_LOGE(GATTS_TABLE_TAG, "Get bond device num failed (stack may be disabled), ret %d", dev_num);
return;
}
if (dev_num == 0) {
ESP_LOGI(GATTS_TABLE_TAG, "Bonded devices number zero\n");
return;
}
esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * dev_num);
esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * (size_t)dev_num);
if (!dev_list) {
ESP_LOGI(GATTS_TABLE_TAG, "malloc failed, return\n");
return;
@@ -279,12 +283,16 @@ static void show_bonded_devices(void)
static void __attribute__((unused)) remove_all_bonded_devices(void)
{
int dev_num = esp_ble_get_bond_device_num();
if (dev_num < 0) {
ESP_LOGE(GATTS_TABLE_TAG, "Get bond device num failed (stack may be disabled), ret %d", dev_num);
return;
}
if (dev_num == 0) {
ESP_LOGI(GATTS_TABLE_TAG, "Bonded devices number zero\n");
return;
}
esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * dev_num);
esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * (size_t)dev_num);
if (!dev_list) {
ESP_LOGI(GATTS_TABLE_TAG, "malloc failed, return\n");
return;
@@ -460,7 +468,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event,
case ESP_GATTS_CONGEST_EVT:
break;
case ESP_GATTS_CREAT_ATTR_TAB_EVT: {
if (param->create.status == ESP_GATT_OK){
if (param->add_attr_tab.status == ESP_GATT_OK){
if(param->add_attr_tab.num_handle == HRS_IDX_NB) {
ESP_LOGI(GATTS_TABLE_TAG, "Attribute table create successfully, num_handle %x", param->add_attr_tab.num_handle);
memcpy(heart_rate_handle_table, param->add_attr_tab.handles,
@@ -471,7 +479,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event,
param->add_attr_tab.num_handle, HRS_IDX_NB);
}
}else{
ESP_LOGE(GATTS_TABLE_TAG, "Attribute table create failed, error code = %x", param->create.status);
ESP_LOGE(GATTS_TABLE_TAG, "Attribute table create failed, error code = %x", param->add_attr_tab.status);
}
break;
}
@@ -197,6 +197,18 @@ typedef struct {
static prepare_type_env_t a_prepare_write_env;
static prepare_type_env_t b_prepare_write_env;
static void prepare_write_env_clear(prepare_type_env_t *env)
{
if (env == NULL) {
return;
}
if (env->prepare_buf != NULL) {
free(env->prepare_buf);
env->prepare_buf = NULL;
}
env->prepare_len = 0;
}
void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param);
void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param);
@@ -205,27 +217,43 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param
switch (event) {
#ifdef CONFIG_EXAMPLE_SET_RAW_ADV_DATA
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
if (param->adv_data_raw_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(GATTS_TAG, "Raw adv data set failed, status %d", param->adv_data_raw_cmpl.status);
break;
}
adv_config_done &= (~adv_config_flag);
if (adv_config_done==0){
if (adv_config_done == 0) {
esp_ble_gap_start_advertising(&adv_params);
}
break;
case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT:
if (param->scan_rsp_data_raw_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(GATTS_TAG, "Raw scan rsp data set failed, status %d", param->scan_rsp_data_raw_cmpl.status);
break;
}
adv_config_done &= (~scan_rsp_config_flag);
if (adv_config_done==0){
if (adv_config_done == 0) {
esp_ble_gap_start_advertising(&adv_params);
}
break;
#else
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
if (param->adv_data_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(GATTS_TAG, "Adv data set failed, status %d", param->adv_data_cmpl.status);
break;
}
adv_config_done &= (~adv_config_flag);
if (adv_config_done == 0){
if (adv_config_done == 0) {
esp_ble_gap_start_advertising(&adv_params);
}
break;
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
if (param->scan_rsp_data_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(GATTS_TAG, "Scan rsp data set failed, status %d", param->scan_rsp_data_cmpl.status);
break;
}
adv_config_done &= (~scan_rsp_config_flag);
if (adv_config_done == 0){
if (adv_config_done == 0) {
esp_ble_gap_start_advertising(&adv_params);
}
break;
@@ -239,7 +267,7 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param
ESP_LOGI(GATTS_TAG, "Advertising start successfully");
break;
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(GATTS_TAG, "Advertising stop failed, status %d", param->adv_stop_cmpl.status);
break;
}
@@ -273,7 +301,7 @@ void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare
status = ESP_GATT_INVALID_ATTR_LEN;
}
if (status == ESP_GATT_OK && prepare_write_env->prepare_buf == NULL) {
prepare_write_env->prepare_buf = (uint8_t *)malloc(PREPARE_BUF_MAX_SIZE*sizeof(uint8_t));
prepare_write_env->prepare_buf = (uint8_t *)calloc(PREPARE_BUF_MAX_SIZE, sizeof(uint8_t));
prepare_write_env->prepare_len = 0;
if (prepare_write_env->prepare_buf == NULL) {
ESP_LOGE(GATTS_TAG, "Gatt_server prep no mem");
@@ -304,7 +332,14 @@ void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare
memcpy(prepare_write_env->prepare_buf + param->write.offset,
param->write.value,
param->write.len);
prepare_write_env->prepare_len += param->write.len;
/* Extent is max(end of this fragment), not sum(len): same offset overwrites, not appends. */
int frag_end = (int)param->write.offset + (int)param->write.len;
if (frag_end > prepare_write_env->prepare_len) {
prepare_write_env->prepare_len = frag_end;
}
if (prepare_write_env->prepare_len > PREPARE_BUF_MAX_SIZE) {
prepare_write_env->prepare_len = PREPARE_BUF_MAX_SIZE;
}
}else{
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, NULL);
@@ -314,15 +349,19 @@ void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare
void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param){
if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC){
esp_log_buffer_hex(GATTS_TAG, prepare_write_env->prepare_buf, prepare_write_env->prepare_len);
int log_len = prepare_write_env->prepare_len;
if (log_len < 0) {
log_len = 0;
} else if (log_len > PREPARE_BUF_MAX_SIZE) {
log_len = PREPARE_BUF_MAX_SIZE;
}
if (prepare_write_env->prepare_buf != NULL && log_len > 0) {
ESP_LOG_BUFFER_HEX(GATTS_TAG, prepare_write_env->prepare_buf, (size_t)log_len);
}
}else{
ESP_LOGI(GATTS_TAG,"Prepare write cancel");
}
if (prepare_write_env->prepare_buf) {
free(prepare_write_env->prepare_buf);
prepare_write_env->prepare_buf = NULL;
}
prepare_write_env->prepare_len = 0;
prepare_write_env_clear(prepare_write_env);
}
static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) {
@@ -545,13 +584,14 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i
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);
prepare_write_env_clear(&a_prepare_write_env);
esp_ble_gap_start_advertising(&adv_params);
local_mtu = 23; // Reset MTU for a single connection
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);
if (param->conf.status == ESP_GATT_OK && param->conf.value != NULL && param->conf.len > 0) {
ESP_LOG_BUFFER_HEX(GATTS_TAG, param->conf.value, param->conf.len);
}
break;
case ESP_GATTS_OPEN_EVT:
@@ -691,11 +731,13 @@ static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_i
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);
if (param->conf.status == ESP_GATT_OK && param->conf.value != NULL && param->conf.len > 0) {
ESP_LOG_BUFFER_HEX(GATTS_TAG, param->conf.value, param->conf.len);
}
break;
break;
case ESP_GATTS_DISCONNECT_EVT:
prepare_write_env_clear(&b_prepare_write_env);
break;
case ESP_GATTS_OPEN_EVT:
case ESP_GATTS_CANCEL_OPEN_EVT:
case ESP_GATTS_CLOSE_EVT:
@@ -14,6 +14,7 @@
*
****************************************************************************/
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
@@ -56,8 +57,21 @@ typedef struct {
int prepare_len;
} prepare_type_env_t;
/* Single-connection demo: one prepare-write buffer for the lone GATT link. */
static prepare_type_env_t prepare_write_env;
static void prepare_write_env_clear(prepare_type_env_t *env)
{
if (env == NULL) {
return;
}
if (env->prepare_buf != NULL) {
free(env->prepare_buf);
env->prepare_buf = NULL;
}
env->prepare_len = 0;
}
#define CONFIG_SET_RAW_ADV_DATA
#ifdef CONFIG_SET_RAW_ADV_DATA
static uint8_t raw_adv_data[] = {
@@ -287,7 +301,7 @@ void example_prepare_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t
status = ESP_GATT_INVALID_ATTR_LEN;
}
if (status == ESP_GATT_OK && prepare_write_env->prepare_buf == NULL) {
prepare_write_env->prepare_buf = (uint8_t *)malloc(PREPARE_BUF_MAX_SIZE * sizeof(uint8_t));
prepare_write_env->prepare_buf = (uint8_t *)calloc(PREPARE_BUF_MAX_SIZE, sizeof(uint8_t));
prepare_write_env->prepare_len = 0;
if (prepare_write_env->prepare_buf == NULL) {
ESP_LOGE(GATTS_TABLE_TAG, "%s, Gatt_server prep no mem", __func__);
@@ -320,21 +334,32 @@ void example_prepare_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t
memcpy(prepare_write_env->prepare_buf + param->write.offset,
param->write.value,
param->write.len);
prepare_write_env->prepare_len += param->write.len;
/* Extent is max(end of fragment), not sum(len); cap to allocated size. */
int frag_end = (int)param->write.offset + (int)param->write.len;
if (frag_end > prepare_write_env->prepare_len) {
prepare_write_env->prepare_len = frag_end;
}
if (prepare_write_env->prepare_len > PREPARE_BUF_MAX_SIZE) {
prepare_write_env->prepare_len = PREPARE_BUF_MAX_SIZE;
}
}
void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param){
if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC && prepare_write_env->prepare_buf){
esp_log_buffer_hex(GATTS_TABLE_TAG, prepare_write_env->prepare_buf, prepare_write_env->prepare_len);
int log_len = prepare_write_env->prepare_len;
if (log_len < 0) {
log_len = 0;
} else if (log_len > PREPARE_BUF_MAX_SIZE) {
log_len = PREPARE_BUF_MAX_SIZE;
}
if (log_len > 0) {
ESP_LOG_BUFFER_HEX(GATTS_TABLE_TAG, prepare_write_env->prepare_buf, (size_t)log_len);
}
}else{
ESP_LOGI(GATTS_TABLE_TAG,"ESP_GATT_PREP_WRITE_CANCEL");
}
if (prepare_write_env->prepare_buf) {
free(prepare_write_env->prepare_buf);
prepare_write_env->prepare_buf = NULL;
}
prepare_write_env->prepare_len = 0;
prepare_write_env_clear(prepare_write_env);
}
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
@@ -458,6 +483,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
break;
case ESP_GATTS_DISCONNECT_EVT:
ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_DISCONNECT_EVT, reason = 0x%x", param->disconnect.reason);
prepare_write_env_clear(&prepare_write_env);
esp_ble_gap_start_advertising(&adv_params);
break;
case ESP_GATTS_CREAT_ATTR_TAB_EVT:{
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -125,10 +125,11 @@ static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = {
};
/* Restarts scanning; does not clear Isconnecting — that flag is tied to enh_open/OPEN_EVT only
* so unrelated events (e.g. another profile disconnect) cannot drop the guard mid–connect attempt. */
static void start_scan(void)
{
stop_scan_done = false;
Isconnecting = false;
uint32_t duration = 30;
esp_ble_gap_start_scanning(duration);
}
@@ -154,7 +155,8 @@ static void gattc_profile_a_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
//open failed, ignore the first device, connect the second device
ESP_LOGE(GATTC_TAG, "connect device failed, status %d", p_data->open.status);
conn_device_a = false;
//start_scan();
Isconnecting = false;
start_scan();
break;
}
memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data->open.remote_bda, 6);
@@ -166,6 +168,7 @@ static void gattc_profile_a_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
if (mtu_ret){
ESP_LOGE(GATTC_TAG, "config MTU error, error code = %x", mtu_ret);
}
Isconnecting = false;
break;
case ESP_GATTC_CFG_MTU_EVT:
if (param->cfg_mtu.status != ESP_GATT_OK){
@@ -267,6 +270,9 @@ static void gattc_profile_a_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
&count);
if (ret_status != ESP_GATT_OK){
ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_descr_by_char_handle error");
free(descr_elem_result_a);
descr_elem_result_a = NULL;
break;
}
/* Every char has only one descriptor in our 'ESP_GATTS_DEMO' demo, so we used first 'descr_elem_result' */
@@ -286,6 +292,7 @@ static void gattc_profile_a_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
/* free descr_elem_result */
free(descr_elem_result_a);
descr_elem_result_a = NULL;
}
}
else{
@@ -327,8 +334,8 @@ static void gattc_profile_a_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
case ESP_GATTC_SRVC_CHG_EVT: {
esp_bd_addr_t bda;
memcpy(bda, p_data->srvc_chg.remote_bda, sizeof(esp_bd_addr_t));
ESP_LOGI(GATTC_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:%08x%04x",(bda[0] << 24) + (bda[1] << 16) + (bda[2] << 8) + bda[3],
(bda[4] << 8) + bda[5]);
ESP_LOGI(GATTC_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:%02x:%02x:%02x:%02x:%02x:%02x",
bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
break;
}
case ESP_GATTC_DISCONNECT_EVT:
@@ -360,7 +367,8 @@ static void gattc_profile_b_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
//open failed, ignore the second device, connect the third device
ESP_LOGE(GATTC_TAG, "connect device failed, status %d", p_data->open.status);
conn_device_b = false;
//start_scan();
Isconnecting = false;
start_scan();
break;
}
memcpy(gl_profile_tab[PROFILE_B_APP_ID].remote_bda, p_data->open.remote_bda, 6);
@@ -372,6 +380,7 @@ static void gattc_profile_b_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
if (mtu_ret){
ESP_LOGE(GATTC_TAG, "config MTU error, error code = %x", mtu_ret);
}
Isconnecting = false;
break;
case ESP_GATTC_CFG_MTU_EVT:
if (param->cfg_mtu.status != ESP_GATT_OK){
@@ -538,8 +547,8 @@ static void gattc_profile_b_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
case ESP_GATTC_SRVC_CHG_EVT: {
esp_bd_addr_t bda;
memcpy(bda, p_data->srvc_chg.remote_bda, sizeof(esp_bd_addr_t));
ESP_LOGI(GATTC_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:%08x%04x",(bda[0] << 24) + (bda[1] << 16) + (bda[2] << 8) + bda[3],
(bda[4] << 8) + bda[5]);
ESP_LOGI(GATTC_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:%02x:%02x:%02x:%02x:%02x:%02x",
bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
break;
}
case ESP_GATTC_DISCONNECT_EVT:
@@ -568,7 +577,8 @@ static void gattc_profile_c_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
if (p_data->open.status != ESP_GATT_OK){
ESP_LOGE(GATTC_TAG, "connect device failed, status %d", p_data->open.status);
conn_device_c = false;
//start_scan();
Isconnecting = false;
start_scan();
break;
}
memcpy(gl_profile_tab[PROFILE_C_APP_ID].remote_bda, p_data->open.remote_bda, 6);
@@ -580,6 +590,7 @@ static void gattc_profile_c_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
if (mtu_ret){
ESP_LOGE(GATTC_TAG, "config MTU error, error code = %x", mtu_ret);
}
Isconnecting = false;
break;
case ESP_GATTC_CFG_MTU_EVT:
if (param->cfg_mtu.status != ESP_GATT_OK){
@@ -746,8 +757,8 @@ static void gattc_profile_c_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
case ESP_GATTC_SRVC_CHG_EVT: {
esp_bd_addr_t bda;
memcpy(bda, p_data->srvc_chg.remote_bda, sizeof(esp_bd_addr_t));
ESP_LOGI(GATTC_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:%08x%04x",(bda[0] << 24) + (bda[1] << 16) + (bda[2] << 8) + bda[3],
(bda[4] << 8) + bda[5]);
ESP_LOGI(GATTC_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:%02x:%02x:%02x:%02x:%02x:%02x",
bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
break;
}
case ESP_GATTC_DISCONNECT_EVT:
@@ -287,7 +287,7 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param
esp_ble_gap_ext_adv_start(NUM_EXT_ADV_SET, &ext_adv[0]);
break;
case ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT:
ESP_LOGI(GATTS_TABLE_TAG, "Extended advertising start, status %d", param->ext_adv_data_set.status);
ESP_LOGI(GATTS_TABLE_TAG, "Extended advertising start, status %d", param->ext_adv_start.status);
break;
case ESP_GAP_BLE_ADV_TERMINATED_EVT:
ESP_LOGI(GATTS_TABLE_TAG, "Extended advertising terminated, status %d", param->adv_terminate.status);
@@ -163,6 +163,9 @@ static uint8_t check_sum(uint8_t *addr, uint16_t count)
if (addr == NULL || count == 0) {
return 0;
}
if (count > (ESP_GATT_MAX_MTU_SIZE - 3U)) {
return 0;
}
for(int i = 0; i < count; i++) {
sum = sum + addr[i];
@@ -175,6 +178,15 @@ static uint8_t check_sum(uint8_t *addr, uint16_t count)
return (uint8_t)~sum;
}
static void throughput_client_resume_ext_scan(void)
{
connect = false;
esp_err_t err = esp_ble_gap_start_ext_scan(EXT_SCAN_DURATION, EXT_SCAN_PERIOD);
if (err != ESP_OK) {
ESP_LOGE(GATTC_TAG, "start_ext_scan failed: %s", esp_err_to_name(err));
}
}
static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
{
esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param;
@@ -202,6 +214,7 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
case ESP_GATTC_OPEN_EVT:
if (param->open.status != ESP_GATT_OK){
ESP_LOGE(GATTC_TAG, "Open failed, status %d", p_data->open.status);
throughput_client_resume_ext_scan();
break;
}
ESP_LOGI(GATTC_TAG, "Open successfully, MTU %u", param->open.mtu);
@@ -293,12 +306,13 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
&count);
if (ret_status != ESP_GATT_OK){
ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error");
}
if (count > 0){
} else if (count == 0) {
ESP_LOGE(GATTC_TAG, "decsr not found");
} else {
descr_elem_result = malloc(sizeof(esp_gattc_descr_elem_t) * count);
if (!descr_elem_result){
if (descr_elem_result == NULL){
ESP_LOGE(GATTC_TAG, "malloc error, gattc no mem");
}else{
} else {
ret_status = esp_ble_gattc_get_descr_by_char_handle( gattc_if,
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
p_data->reg_for_notify.handle,
@@ -307,10 +321,10 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
&count);
if (ret_status != ESP_GATT_OK){
ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_descr_by_char_handle error");
}
/* Every char has only one descriptor in our 'throughput_server' demo, so we use first 'descr_elem_result' */
if (count > 0 && descr_elem_result[0].uuid.len == ESP_UUID_LEN_16 && descr_elem_result[0].uuid.uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG){
} else if (count > 0 &&
descr_elem_result[0].uuid.len == ESP_UUID_LEN_16 &&
descr_elem_result[0].uuid.uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
/* Every char has only one descriptor in our 'throughput_server' demo, so we use first 'descr_elem_result' */
ret_status = esp_ble_gattc_write_char_descr( gattc_if,
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
descr_elem_result[0].handle,
@@ -318,29 +332,37 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
(uint8_t *)&notify_en,
ESP_GATT_WRITE_TYPE_RSP,
ESP_GATT_AUTH_REQ_NONE);
if (ret_status != ESP_GATT_OK){
ESP_LOGE(GATTC_TAG, "esp_ble_gattc_write_char_descr error");
}
}
if (ret_status != ESP_GATT_OK){
ESP_LOGE(GATTC_TAG, "esp_ble_gattc_write_char_descr error");
}
/* free descr_elem_result */
free(descr_elem_result);
descr_elem_result = NULL;
}
}
else{
ESP_LOGE(GATTC_TAG, "decsr not found");
}
}
break;
}
case ESP_GATTC_NOTIFY_EVT: {
#if (CONFIG_GATTS_NOTIFY_THROUGHPUT)
if (p_data->notify.is_notify &&
(p_data->notify.value[p_data->notify.value_len - 1] ==
check_sum(p_data->notify.value, p_data->notify.value_len - 1))){
notify_len += p_data->notify.value_len;
if (p_data->notify.is_notify) {
uint16_t vlen = p_data->notify.value_len;
uint8_t *val = p_data->notify.value;
/* value_len == 0 makes (vlen - 1) wrap; never index or pass to check_sum before validating. */
const uint16_t max_notify_len = (uint16_t)(ESP_GATT_MAX_MTU_SIZE - 3U);
if (val == NULL) {
ESP_LOGW(GATTC_TAG, "notify ignored: null value");
} else if (vlen == 0) {
ESP_LOGW(GATTC_TAG, "notify ignored: zero length");
} else if (vlen < 2) {
ESP_LOGW(GATTC_TAG, "notify ignored: length too short for payload+checksum");
} else if (vlen > max_notify_len) {
ESP_LOGW(GATTC_TAG, "notify ignored: length exceeds bound");
} else if (val[vlen - 1] == check_sum(val, (uint16_t)(vlen - 1U))) {
notify_len += vlen;
} else {
ESP_LOGE(GATTC_TAG, "notify checksum mismatch");
}
} else {
ESP_LOGE(GATTC_TAG, "Indication received, value:");
}
@@ -385,8 +407,14 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
current_time = 0;
notify_len = 0;
#endif /* #if (CONFIG_GATTS_NOTIFY_THROUGHPUT) */
#if (CONFIG_GATTC_WRITE_THROUGHPUT)
/* Unblock throughput_client_task if it is waiting on gattc_semaphore while congested. */
can_send_write = true;
xSemaphoreGive(gattc_semaphore);
#endif /* #if (CONFIG_GATTC_WRITE_THROUGHPUT) */
ESP_LOGI(GATTC_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x",
ESP_BD_ADDR_HEX(p_data->disconnect.remote_bda), p_data->disconnect.reason);
throughput_client_resume_ext_scan();
break;
case ESP_GATTC_CONGEST_EVT:
#if (CONFIG_GATTC_WRITE_THROUGHPUT)
@@ -517,7 +545,6 @@ static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp
#if (CONFIG_GATTC_WRITE_THROUGHPUT)
static void throughput_client_task(void *param)
{
vTaskDelay(2000 / portTICK_PERIOD_MS);
uint8_t sum = check_sum(write_data, sizeof(write_data) - 1);
write_data[GATTC_WRITE_LEN - 1] = sum;
@@ -618,6 +645,17 @@ void app_main(void)
return;
}
#if (CONFIG_GATTC_WRITE_THROUGHPUT)
/* Create the semaphore before registering the GATTC callback so that any
* xSemaphoreGive() invoked from the callback is guaranteed to see a valid handle.
*/
gattc_semaphore = xSemaphoreCreateBinary();
if (gattc_semaphore == NULL) {
ESP_LOGE(GATTC_TAG, "%s: gattc semaphore create failed", __func__);
return;
}
#endif /* #if (CONFIG_GATTC_WRITE_THROUGHPUT) */
//register the callback function to the gattc module
ret = esp_ble_gattc_register_callback(esp_gattc_cb);
if(ret){
@@ -635,20 +673,11 @@ void app_main(void)
ESP_LOGE(GATTC_TAG, "set local MTU failed, error code = %x", local_mtu_ret);
}
#if (CONFIG_GATTC_WRITE_THROUGHPUT)
// The task is only created on the CPU core that Bluetooth is working on,
// preventing the sending task from using the un-updated Bluetooth state on another CPU.
/* Create the task only after the semaphore exists; never rely on priority or delays. */
xTaskCreatePinnedToCore(&throughput_client_task, "throughput_client_task", 4096, NULL, 10, NULL, BLUETOOTH_TASK_PINNED_TO_CORE);
#endif
#if (CONFIG_GATTS_NOTIFY_THROUGHPUT)
xTaskCreatePinnedToCore(&throughput_cal_task, "throughput_cal_task", 4096, NULL, 9, NULL, BLUETOOTH_TASK_PINNED_TO_CORE);
#endif
#if (CONFIG_GATTC_WRITE_THROUGHPUT)
gattc_semaphore = xSemaphoreCreateBinary();
if (!gattc_semaphore) {
ESP_LOGE(GATTC_TAG, "%s, init fail, the gattc semaphore create fail.", __func__);
return;
}
#endif /* #if (CONFIG_GATTC_WRITE_THROUGHPUT) */
}
@@ -164,6 +164,18 @@ typedef struct {
static prepare_type_env_t a_prepare_write_env;
static void prepare_write_env_clear(prepare_type_env_t *env)
{
if (env == NULL) {
return;
}
if (env->prepare_buf != NULL) {
free(env->prepare_buf);
env->prepare_buf = NULL;
}
env->prepare_len = 0;
}
extern void esp_ble_switch_phy_coded(bool phy_500k);
void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param);
void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param);
@@ -175,6 +187,9 @@ static uint8_t check_sum(uint8_t *addr, uint16_t count)
if (addr == NULL || count == 0) {
return 0;
}
if (count > (ESP_GATT_MAX_MTU_SIZE - 3U)) {
return 0;
}
for(int i = 0; i < count; i++) {
sum = sum + addr[i];
@@ -228,6 +243,8 @@ void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare
status = ESP_GATT_INVALID_OFFSET;
} else if ((param->write.offset + param->write.len) > PREPARE_BUF_MAX_SIZE) {
status = ESP_GATT_INVALID_ATTR_LEN;
} else if (param->write.len > ESP_GATT_MAX_ATTR_LEN) {
status = ESP_GATT_INVALID_ATTR_LEN;
}
if (status == ESP_GATT_OK && prepare_write_env->prepare_buf == NULL) {
@@ -239,13 +256,19 @@ void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare
}
}
esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *)malloc(sizeof(esp_gatt_rsp_t));
esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *)calloc(1, sizeof(esp_gatt_rsp_t));
if (gatt_rsp) {
gatt_rsp->attr_value.len = param->write.len;
gatt_rsp->attr_value.handle = param->write.handle;
gatt_rsp->attr_value.offset = param->write.offset;
gatt_rsp->attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
memcpy(gatt_rsp->attr_value.value, param->write.value, param->write.len);
if (status == ESP_GATT_OK) {
if (param->write.value == NULL) {
status = ESP_GATT_INVALID_ATTR_LEN;
} else {
gatt_rsp->attr_value.len = param->write.len;
memcpy(gatt_rsp->attr_value.value, param->write.value, param->write.len);
}
}
esp_err_t response_err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, gatt_rsp);
if (response_err != ESP_OK) {
@@ -275,11 +298,7 @@ void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble
if (param->exec_write.exec_write_flag != ESP_GATT_PREP_WRITE_EXEC){
ESP_LOGI(GATTS_TAG,"Prepare write cancel");
}
if (prepare_write_env->prepare_buf) {
free(prepare_write_env->prepare_buf);
prepare_write_env->prepare_buf = NULL;
}
prepare_write_env->prepare_len = 0;
prepare_write_env_clear(prepare_write_env);
}
static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) {
@@ -356,9 +375,22 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i
#if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT)
if (param->write.handle == gl_profile_tab[PROFILE_A_APP_ID].char_handle) {
// The last value byte is the checksum data, should used to check the data is received corrected or not.
if (param->write.value[param->write.len - 1] ==
check_sum(param->write.value, param->write.len - 1)) {
write_len += param->write.len;
uint16_t wlen = param->write.len;
uint8_t *wval = param->write.value;
/* len==0 makes (wlen-1) wrap; cap len before indexing or passing to check_sum. */
const uint16_t max_write_len = (uint16_t)(ESP_GATT_MAX_MTU_SIZE - 3U);
if (wval == NULL) {
ESP_LOGW(GATTS_TAG, "write ignored: null value");
} else if (wlen == 0) {
ESP_LOGW(GATTS_TAG, "write ignored: zero length");
} else if (wlen < 2) {
ESP_LOGW(GATTS_TAG, "write ignored: length too short for payload+checksum");
} else if (wlen > max_write_len) {
ESP_LOGW(GATTS_TAG, "write ignored: length exceeds bound");
} else if (wval[wlen - 1] == check_sum(wval, (uint16_t)(wlen - 1U))) {
write_len += wlen;
} else {
ESP_LOGE(GATTS_TAG, "write checksum mismatch");
}
if (start == false) {
@@ -451,10 +483,12 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i
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[PROFILE_A_APP_ID].conn_id = param->connect.conn_id;
prepare_write_env_clear(&a_prepare_write_env);
break;
}
case ESP_GATTS_DISCONNECT_EVT:
is_connect = false;
prepare_write_env_clear(&a_prepare_write_env);
ESP_LOGI(GATTS_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%x",
ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason);
esp_ble_gap_ext_adv_start(NUM_EXT_ADV_SET, &ext_adv[0]);
@@ -96,6 +96,16 @@ static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = {
},
};
/** Clear connect-in-progress flag and restart indefinite extended scan (after failed open or disconnect). */
static void central_resume_ext_scan(void)
{
connect = false;
esp_err_t err = esp_ble_gap_start_ext_scan(0, 0);
if (err != ESP_OK) {
ESP_LOGE(TAG, "start ext scan failed, error = 0x%x", err);
}
}
/**
* @brief GATT client event handler
*/
@@ -112,6 +122,10 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
case ESP_GATTC_REG_EVT:
ESP_LOGI(TAG, "GATT client register, status %d, app_id %d, gattc_if %d",
p_data->reg.status, p_data->reg.app_id, gattc_if);
if (p_data->reg.status != ESP_GATT_OK) {
ESP_LOGE(TAG, "GATT client register failed, status %d", p_data->reg.status);
break;
}
gl_profile_tab[PROFILE_A_APP_ID].gattc_if = gattc_if;
// Set default subrate parameters
esp_ble_default_subrate_param_t default_subrate_params = {
@@ -148,11 +162,19 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
ESP_LOGI(TAG, "Subrate request sent successfully");
}
break;
case ESP_GATTC_OPEN_EVT:
if (p_data->open.status != ESP_GATT_OK) {
ESP_LOGE(TAG, "Open failed, status %d", p_data->open.status);
central_resume_ext_scan();
break;
}
ESP_LOGI(TAG, "GATT open OK, conn_id %d, MTU %u", p_data->open.conn_id, p_data->open.mtu);
break;
case ESP_GATTC_DISCONNECT_EVT:
connect = false;
g_conn_handle = 0xFFFF;
ESP_LOGI(TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x",
ESP_BD_ADDR_HEX(p_data->disconnect.remote_bda), p_data->disconnect.reason);
central_resume_ext_scan();
break;
default:
break;
@@ -208,10 +230,9 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param
creat_conn_params.phy_1m_conn_params = &phy_1m_conn_params;
creat_conn_params.phy_2m_conn_params = &phy_2m_conn_params;
creat_conn_params.phy_coded_conn_params = &phy_coded_conn_params;
if (esp_ble_gattc_enh_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, &creat_conn_params) != ESP_OK)
{
connect = false;
if (esp_ble_gattc_enh_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, &creat_conn_params) != ESP_OK) {
ESP_LOGE(TAG, "Failed to open connection");
central_resume_ext_scan();
}
}
}
@@ -37,6 +37,7 @@
#define REMOTE_NOTIFY_UUID 0xFF01
#define EXT_SCAN_DURATION 0
#define EXT_SCAN_PERIOD 0
#define BLE_CONN_HDL_INVALID ((uint16_t)0xFFFF)
///Declare static functions
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
@@ -95,7 +96,7 @@ const esp_ble_conn_params_t phy_coded_conn_params = {
static uint8_t antenna_ids[2] = {0x00, 0x01};
esp_ble_cte_recv_params_params_t cte_recv_params = {
.conn_handle = 0xff,
.conn_handle = BLE_CONN_HDL_INVALID,
.sampling_en = ESP_BLE_CTE_SAMPLING_ENABLE,
.slot_dur = ESP_BLE_CTE_SLOT_DURATION_2US,
.switching_pattern_len = sizeof(antenna_ids),
@@ -103,14 +104,14 @@ esp_ble_cte_recv_params_params_t cte_recv_params = {
};
static esp_ble_cte_req_en_params_t cte_conn_req_en = {
.conn_handle = 0xff,
.conn_handle = BLE_CONN_HDL_INVALID,
.enable = ESP_BLE_CTE_SAMPLING_ENABLE,
.cte_req_interval = 0x05,
.req_cte_len = ESP_BLE_CTE_MAX_REQUESTED_CTE_LENGTH,
.req_cte_Type = ESP_BLE_CTE_TYPE_AOA,
};
uint16_t cur_conn_hdl = 0xff;
uint16_t cur_conn_hdl = BLE_CONN_HDL_INVALID;
#define PROFILE_NUM 1
#define PROFILE_A_APP_ID 0
@@ -210,6 +211,31 @@ static char *esp_auth_req_to_str(esp_ble_auth_req_t auth_req)
return auth_str;
}
/** After a failed open or disconnect: clear connect flag and restart extended scan. */
static void cte_resume_ext_scan(void)
{
connect = false;
esp_err_t err = esp_ble_gap_start_ext_scan(EXT_SCAN_DURATION, EXT_SCAN_PERIOD);
if (err != ESP_OK) {
ESP_LOGE(LOG_TAG, "start ext scan failed, error = 0x%x", err);
}
}
/** Start CTE receive setup only after link is authenticated (see esp_gap_cb AUTH_CMPL). */
static void cte_enable_connection_receive_after_encrypted(void)
{
if (cur_conn_hdl == BLE_CONN_HDL_INVALID) {
ESP_LOGW(LOG_TAG, "Skip CTE receive params: no connection handle");
return;
}
ESP_LOGI(LOG_TAG, "Set CTE connection receive params after encryption, conn_handle %d", cur_conn_hdl);
cte_recv_params.conn_handle = cur_conn_hdl;
esp_err_t cte_ret = esp_ble_cte_set_connection_receive_params(&cte_recv_params);
if (cte_ret != ESP_OK) {
ESP_LOGE(LOG_TAG, "CTE set connection receive params failed, 0x%x", cte_ret);
}
}
static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
{
esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param;
@@ -227,6 +253,7 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
case ESP_GATTC_OPEN_EVT:
if (param->open.status != ESP_GATT_OK){
ESP_LOGE(LOG_TAG, "Open failed, status %x", p_data->open.status);
cte_resume_ext_scan();
break;
}
ESP_LOGI(LOG_TAG, "Open successfully, MTU %d", p_data->open.mtu);
@@ -239,19 +266,14 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
break;
case ESP_GATTC_CFG_MTU_EVT:
ESP_LOGI(LOG_TAG, "MTU exchange, status %d, MTU %d, conn_id %d", param->cfg_mtu.status, param->cfg_mtu.mtu, param->cfg_mtu.conn_id);
if (!param->cfg_mtu.status) {
ESP_LOGI(LOG_TAG, "Set CTE connection receive params, conn_handle %d", cur_conn_hdl);
cte_recv_params.conn_handle = cur_conn_hdl;
esp_ble_cte_set_connection_receive_params(&cte_recv_params);
}
break;
case ESP_GATTC_DIS_SRVC_CMPL_EVT:
if (param->dis_srvc_cmpl.status != ESP_GATT_OK){
ESP_LOGE(LOG_TAG, "Service discover failed, status %d", param->dis_srvc_cmpl.status);
break;
}
ESP_LOGI(LOG_TAG, "Service discover complete, conn_id %d", param->dis_srvc_cmpl.conn_id);
esp_ble_gattc_search_service(gattc_if, param->cfg_mtu.conn_id, &remote_filter_service_uuid);
ESP_LOGI(LOG_TAG, "Service discover complete, conn_id %d", p_data->dis_srvc_cmpl.conn_id);
esp_ble_gattc_search_service(gattc_if, p_data->dis_srvc_cmpl.conn_id, &remote_filter_service_uuid);
break;
case ESP_GATTC_SEARCH_RES_EVT: {
ESP_LOGI(LOG_TAG, "Service search result, conn_id %x, is primary service %d", p_data->search_res.conn_id, p_data->search_res.is_primary);
@@ -314,9 +336,9 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
case ESP_GATTC_DISCONNECT_EVT:
ESP_LOGI(LOG_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x",
ESP_BD_ADDR_HEX(p_data->disconnect.remote_bda), p_data->disconnect.reason);
connect = false;
get_service = false;
esp_ble_gap_start_ext_scan(EXT_SCAN_DURATION, EXT_SCAN_PERIOD);
cur_conn_hdl = BLE_CONN_HDL_INVALID;
cte_resume_ext_scan();
break;
default:
break;
@@ -397,8 +419,7 @@ static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *par
ESP_LOGI(LOG_TAG, "Pairing failed, reason 0x%x",param->ble_security.auth_cmpl.fail_reason);
} else {
ESP_LOGI(LOG_TAG, "Pairing successfully, auth mode %s",esp_auth_req_to_str(param->ble_security.auth_cmpl.auth_mode));
// Enable CTE
cte_enable_connection_receive_after_encrypted();
}
break;
}
@@ -429,7 +450,10 @@ static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *par
creat_conn_params.phy_1m_conn_params = &phy_1m_conn_params;
creat_conn_params.phy_2m_conn_params = &phy_2m_conn_params;
creat_conn_params.phy_coded_conn_params = &phy_coded_conn_params;
esp_ble_gattc_enh_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, &creat_conn_params);
if (esp_ble_gattc_enh_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, &creat_conn_params) != ESP_OK) {
ESP_LOGE(LOG_TAG, "Failed to open connection");
cte_resume_ext_scan();
}
}
break;
@@ -453,14 +477,17 @@ static void cte_event_handler(esp_ble_cte_cb_event_t event, esp_ble_cte_cb_param
case ESP_BLE_CTE_SET_CONN_TRANS_PARAMS_CMPL_EVT:
ESP_LOGI(LOG_TAG, "CTE set connection transmit params, status %d", param->conn_trans_params_cmpl.status);
break;
case ESP_BLE_CTE_SET_CONN_RECV_PARAMS_CMPL_EVT:
ESP_LOGI(LOG_TAG, "CTE set connection receive params, status %d", param->conn_recv_params_cmpl.status);
case ESP_BLE_CTE_SET_CONN_RECV_PARAMS_CMPL_EVT: {
uint16_t recv_cmpl_conn_hdl = param->conn_recv_params_cmpl.conn_handle;
ESP_LOGI(LOG_TAG, "CTE set connection receive params, status %d, conn_handle %d",
param->conn_recv_params_cmpl.status, recv_cmpl_conn_hdl);
if (!param->conn_recv_params_cmpl.status) {
cte_conn_req_en.conn_handle = cur_conn_hdl;
ESP_LOGI(LOG_TAG, "Enable CTE request, conn_handle %d", cur_conn_hdl);
cte_conn_req_en.conn_handle = recv_cmpl_conn_hdl;
ESP_LOGI(LOG_TAG, "Enable CTE request, conn_handle %d", recv_cmpl_conn_hdl);
esp_ble_cte_connection_cte_request_enable(&cte_conn_req_en);
}
break;
}
case ESP_BLE_CTE_SET_CONN_REQ_ENABLE_CMPL_EVT:
ESP_LOGI(LOG_TAG, "CTE set connection request enable, status %d", param->conn_req_en_cmpl.status);
break;
@@ -35,6 +35,9 @@
#define GATTS_DEMO_CHAR_VAL_LEN_MAX 0x40
/* Connection_Handle is 12-bit (0x0000..0x0EFF); 0xFFFF is outside the valid range. */
#define BLE_CONN_HDL_INVALID ((uint16_t)0xFFFF)
#ifndef MIN
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif
@@ -68,18 +71,18 @@ esp_ble_gap_ext_adv_params_t ext_adv_params_2M = {
static uint8_t antenna_ids[2] = {0x00, 0x01};
static esp_ble_cte_conn_trans_params_t cte_conn_trans_params = {
.conn_handle = 0xff,
.conn_handle = BLE_CONN_HDL_INVALID,
.cte_types = ESP_BLE_CTE_TYPES_ALL,
.switching_pattern_len = sizeof(antenna_ids),
.antenna_ids = &antenna_ids[0],
};
static esp_ble_cte_rsp_en_params_t cte_conn_rsp_en = {
.conn_handle = 0xff,
.conn_handle = BLE_CONN_HDL_INVALID,
.enable = ESP_BLE_CTE_RESPONSE_FOR_CONNECTION_ENABLE,
};
uint16_t cur_conn_hdl = 0xff;
uint16_t cur_conn_hdl = BLE_CONN_HDL_INVALID;
struct gatts_profile_inst {
esp_gatts_cb_t gatts_cb;
@@ -315,10 +318,14 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param
ESP_LOGI(LOG_TAG, "Pairing failed, reason 0x%x",param->ble_security.auth_cmpl.fail_reason);
} else {
ESP_LOGI(LOG_TAG, "Pairing successfully, auth_mode %s",esp_auth_req_to_str(param->ble_security.auth_cmpl.auth_mode));
// Setting CTE connection transmit parameters
cte_conn_trans_params.conn_handle = cur_conn_hdl;
ESP_LOGI(LOG_TAG, "Set CTE connection transmit params, conn_handle %d", cur_conn_hdl);
esp_ble_cte_set_connection_transmit_params(&cte_conn_trans_params);
if (cur_conn_hdl != BLE_CONN_HDL_INVALID) {
// Setting CTE connection transmit parameters
cte_conn_trans_params.conn_handle = cur_conn_hdl;
ESP_LOGI(LOG_TAG, "Set CTE connection transmit params, conn_handle %d", cur_conn_hdl);
esp_ble_cte_set_connection_transmit_params(&cte_conn_trans_params);
} else {
ESP_LOGW(LOG_TAG, "Skip CTE transmit params: no active connection handle");
}
}
break;
}
@@ -385,6 +392,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event,
case ESP_GATTS_DISCONNECT_EVT:
ESP_LOGI(LOG_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%x",
ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason);
cur_conn_hdl = BLE_CONN_HDL_INVALID;
/* start advertising again when missing the connect */
esp_ble_gap_ext_adv_start(NUM_EXT_ADV_SET, &ext_adv[0]);
break;
@@ -399,18 +407,18 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event,
case ESP_GATTS_CONGEST_EVT:
break;
case ESP_GATTS_CREAT_ATTR_TAB_EVT: {
if (param->create.status == ESP_GATT_OK){
if(param->add_attr_tab.num_handle == HRS_IDX_NB) {
if (param->add_attr_tab.status == ESP_GATT_OK) {
if (param->add_attr_tab.num_handle == HRS_IDX_NB) {
ESP_LOGI(LOG_TAG, "Attribute table create successfully, num_handle %x", param->add_attr_tab.num_handle);
memcpy(profile_handle_table, param->add_attr_tab.handles,
sizeof(profile_handle_table));
sizeof(profile_handle_table));
esp_ble_gatts_start_service(profile_handle_table[IDX_SVC]);
}else{
} else {
ESP_LOGE(LOG_TAG, "Attribute table create abnormally, num_handle (%d) doesn't equal to HRS_IDX_NB(%d)",
param->add_attr_tab.num_handle, HRS_IDX_NB);
param->add_attr_tab.num_handle, HRS_IDX_NB);
}
}else{
ESP_LOGE(LOG_TAG, "Attribute table create failed, status %x", param->create.status);
} else {
ESP_LOGE(LOG_TAG, "Attribute table create failed, status %x", param->add_attr_tab.status);
}
break;
}
@@ -422,15 +430,17 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event,
static void cte_event_handler(esp_ble_cte_cb_event_t event, esp_ble_cte_cb_param_t *param)
{
switch (event) {
case ESP_BLE_CTE_SET_CONN_TRANS_PARAMS_CMPL_EVT:
ESP_LOGI(LOG_TAG, "CTE set connection transmit params, status %d", param->conn_trans_params_cmpl.status);
case ESP_BLE_CTE_SET_CONN_TRANS_PARAMS_CMPL_EVT: {
uint16_t trans_cmpl_conn_hdl = param->conn_trans_params_cmpl.conn_handle;
ESP_LOGI(LOG_TAG, "CTE set connection transmit params, status %d, conn_handle %d",
param->conn_trans_params_cmpl.status, trans_cmpl_conn_hdl);
if (!param->conn_trans_params_cmpl.status) {
ESP_LOGI(LOG_TAG, "Setting CTE connection response enable");
// Enable CTE response for connection
cte_conn_rsp_en.conn_handle = cur_conn_hdl;
ESP_LOGI(LOG_TAG, "Setting CTE connection response enable, conn_handle %d", trans_cmpl_conn_hdl);
cte_conn_rsp_en.conn_handle = trans_cmpl_conn_hdl;
esp_ble_cte_connection_cte_response_enable(&cte_conn_rsp_en);
}
break;
}
case ESP_BLE_CTE_SET_CONN_RECV_PARAMS_CMPL_EVT:
ESP_LOGI(LOG_TAG, "CTE set connection receive params, status %d", param->conn_recv_params_cmpl.status);
break;
@@ -223,7 +223,11 @@ static void start_periodic_adv(void)
{
// Create static random address
esp_bd_addr_t rand_addr;
esp_ble_gap_addr_create_static(rand_addr);
esp_err_t addr_ret = esp_ble_gap_addr_create_static(rand_addr);
if (addr_ret != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_addr_create_static failed: %s", esp_err_to_name(addr_ret));
return;
}
ESP_LOG_BUFFER_HEX(TAG, rand_addr, ESP_BD_ADDR_LEN);
@@ -243,7 +243,9 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param
param->ext_adv_report.params.adv_data_len,
ESP_BLE_AD_TYPE_NAME_CMPL,
&adv_name_len);
if ((adv_name != NULL) && (memcmp(adv_name, remote_device_name, adv_name_len) == 0) && !periodic_sync) {
if ((adv_name != NULL) && (adv_name_len > 0) &&
(adv_name_len == strlen(remote_device_name)) &&
(memcmp(adv_name, remote_device_name, adv_name_len) == 0) && !periodic_sync) {
// Note: If there are multiple devices with the same device name, the device may sync to an unintended one.
// It is recommended to change the default device name to ensure it is unique.
periodic_sync = true;
@@ -41,16 +41,21 @@
#define FUNC_SEND_WAIT_SEM(func, sem) do {\
esp_err_t __err_rc = (func);\
if (__err_rc != ESP_OK) { \
ESP_LOGE(LOG_TAG, "%s, message send fail, error = %d", __func__, __err_rc); \
ESP_LOGE(LOG_TAG, "%s failed: %s", #func, esp_err_to_name(__err_rc)); \
return; \
} \
xSemaphoreTake((sem), portMAX_DELAY); \
if (last_ble_async_status != ESP_BT_STATUS_SUCCESS) { \
ESP_LOGE(LOG_TAG, "Async completion after %s failed, status 0x%x", #func, last_ble_async_status); \
return; \
} \
xSemaphoreTake(sem, portMAX_DELAY); \
} while(0);
#define EXT_ADV_HANDLE 0
#define NUM_EXT_ADV 1
static SemaphoreHandle_t test_sem = NULL;
static esp_bt_status_t last_ble_async_status = ESP_BT_STATUS_SUCCESS;
uint8_t addr_2m[6] = {0xc0, 0xde, 0x52, 0x00, 0x00, 0x02};
@@ -113,44 +118,56 @@ static esp_ble_cte_trans_enable_params_t cte_trans_enable = {
static uint8_t periodic_adv_hdl = 0xff;
static void ble_async_complete_signal(esp_bt_status_t status)
{
last_ble_async_status = status;
if (test_sem != NULL) {
xSemaphoreGive(test_sem);
}
}
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_EXT_ADV_SET_RAND_ADDR_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ble_async_complete_signal(param->ext_adv_set_rand_addr.status);
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_SET_RAND_ADDR_COMPLETE_EVT, status %d", param->ext_adv_set_rand_addr.status);
break;
case ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ble_async_complete_signal(param->ext_adv_set_params.status);
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT, status %d", param->ext_adv_set_params.status);
break;
case ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ble_async_complete_signal(param->ext_adv_data_set.status);
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT, status %d", param->ext_adv_data_set.status);
break;
case ESP_GAP_BLE_EXT_SCAN_RSP_DATA_SET_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ble_async_complete_signal(param->scan_rsp_set.status);
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_SCAN_RSP_DATA_SET_COMPLETE_EVT, status %d", param->scan_rsp_set.status);
break;
case ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ble_async_complete_signal(param->ext_adv_start.status);
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT, status %d", param->ext_adv_start.status);
break;
case ESP_GAP_BLE_EXT_ADV_STOP_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ble_async_complete_signal(param->ext_adv_stop.status);
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_STOP_COMPLETE_EVT, status %d", param->ext_adv_stop.status);
break;
case ESP_GAP_BLE_PERIODIC_ADV_SET_PARAMS_COMPLETE_EVT:
periodic_adv_hdl = param->peroid_adv_set_params.instance;
xSemaphoreGive(test_sem);
if (param->peroid_adv_set_params.status == ESP_BT_STATUS_SUCCESS) {
periodic_adv_hdl = param->peroid_adv_set_params.instance;
} else {
ESP_LOGE(LOG_TAG, "periodic adv set params failed, not updating periodic_adv_hdl");
}
ble_async_complete_signal(param->peroid_adv_set_params.status);
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PERIODIC_ADV_SET_PARAMS_COMPLETE_EVT, status %d", param->peroid_adv_set_params.status);
break;
case ESP_GAP_BLE_PERIODIC_ADV_DATA_SET_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ble_async_complete_signal(param->period_adv_data_set.status);
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PERIODIC_ADV_DATA_SET_COMPLETE_EVT, status %d", param->period_adv_data_set.status);
break;
case ESP_GAP_BLE_PERIODIC_ADV_START_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ble_async_complete_signal(param->period_adv_start.status);
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PERIODIC_ADV_START_COMPLETE_EVT, status %d", param->period_adv_start.status);
break;
default:
@@ -162,11 +179,11 @@ static void cte_event_handler(esp_ble_cte_cb_event_t event, esp_ble_cte_cb_param
{
switch (event) {
case ESP_BLE_CTE_SET_CONNLESS_TRANS_PARAMS_CMPL_EVT:
xSemaphoreGive(test_sem);
ble_async_complete_signal(param->set_trans_params_cmpl.status);
ESP_LOGI(LOG_TAG, "ESP_BLE_CTE_SET_CONNLESS_TRANS_PARAMS_CMPL_EVT, status %d", param->set_trans_params_cmpl.status);
break;
case ESP_BLE_CTE_SET_CONNLESS_TRANS_ENABLE_CMPL_EVT:
xSemaphoreGive(test_sem);
ble_async_complete_signal(param->set_trans_enable_cmpl.status);
ESP_LOGI(LOG_TAG, "ESP_BLE_CTE_SET_CONNLESS_TRANS_ENABLE_CMPL_EVT, status %d", param->set_trans_enable_cmpl.status);
break;
case ESP_BLE_CTE_SET_CONNLESS_IQ_SAMPLING_ENABLE_CMPL_EVT:
@@ -37,15 +37,16 @@
#include "esp_ble_cte_api.h"
#define LOG_TAG "PERIODIC_SYNC"
#define FUNC_SEND_WAIT_SEM(func, sem) do {\
esp_err_t __err_rc = (func);\
if (__err_rc != ESP_OK) { \
ESP_LOGE(LOG_TAG, "%s, message send fail, error = %d", __func__, __err_rc); \
ESP_LOGE(LOG_TAG, "%s failed: %s", #func, esp_err_to_name(__err_rc)); \
return; \
} \
xSemaphoreTake(sem, portMAX_DELAY); \
xSemaphoreTake((sem), portMAX_DELAY); \
} while(0);
#define LOG_TAG "PERIODIC_SYNC"
#define EXT_SCAN_DURATION 0
#define EXT_SCAN_PERIOD 0
@@ -136,13 +137,16 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param
param->ext_adv_report.params.adv_data_len,
ESP_BLE_AD_TYPE_NAME_CMPL,
&adv_name_len);
if ((adv_name != NULL) && (memcmp(adv_name, remote_device_name, adv_name_len) == 0) && !periodic_sync) {
const size_t remote_cap = sizeof(remote_device_name);
size_t remote_len = strnlen(remote_device_name, remote_cap);
if ((adv_name != NULL) && (adv_name_len > 0) &&
(adv_name_len <= remote_cap) &&
(adv_name_len == remote_len) &&
(memcmp(adv_name, remote_device_name, adv_name_len) == 0) && !periodic_sync) {
// Note: If there are multiple devices with the same device name, the device may sync to an unintended one.
// It is recommended to change the default device name to ensure it is unique.
periodic_sync = true;
char adv_temp_name[30] = {'0'};
memcpy(adv_temp_name, adv_name, adv_name_len);
ESP_LOGI(LOG_TAG, "Create sync with the peer device %s", adv_temp_name);
ESP_LOGI(LOG_TAG, "Create sync with the peer device %.*s", (int)adv_name_len, (const char *)adv_name);
periodic_adv_sync_params.sid = param->ext_adv_report.params.sid;
periodic_adv_sync_params.addr_type = param->ext_adv_report.params.addr_type;
memcpy(periodic_adv_sync_params.addr, param->ext_adv_report.params.addr, sizeof(esp_bd_addr_t));
@@ -180,12 +180,12 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
}
break;
case ESP_GATTC_CONNECT_EVT:
ESP_LOGI(TAG, "Connected, conn_id %d, remote "ESP_BD_ADDR_STR"",
p_data->connect.conn_id, ESP_BD_ADDR_HEX(p_data->connect.remote_bda));
ESP_LOGI(TAG, "Connected, conn_id %d, hci_conn_handle %d, remote "ESP_BD_ADDR_STR"",
p_data->connect.conn_id, p_data->connect.conn_handle,
ESP_BD_ADDR_HEX(p_data->connect.remote_bda));
gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data->connect.conn_id;
memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data->connect.remote_bda, sizeof(esp_bd_addr_t));
conn_handle = p_data->connect.conn_id;
// Initialize power control after connection
conn_handle = p_data->connect.conn_handle;
init_power_control(conn_handle);
break;
case ESP_GATTC_DISCONNECT_EVT:
@@ -40,9 +40,10 @@
#define FUNC_SEND_WAIT_SEM(func, sem) do {\
esp_err_t __err_rc = (func);\
if (__err_rc != ESP_OK) { \
ESP_LOGE(LOG_TAG, "%s, message send fail, error = %d", __func__, __err_rc); \
ESP_LOGE(LOG_TAG, "%s failed: %s", #func, esp_err_to_name(__err_rc)); \
return; \
} \
xSemaphoreTake(sem, portMAX_DELAY); \
xSemaphoreTake((sem), portMAX_DELAY); \
} while(0);
#define EXT_ADV_HANDLE 0
@@ -95,43 +96,50 @@ static esp_ble_gap_ext_adv_t ext_adv[1] = {
[0] = {EXT_ADV_HANDLE, 0, 0},
};
static void periodic_adv_gap_sem_give(void)
{
if (test_sem != NULL) {
xSemaphoreGive(test_sem);
}
}
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_EXT_ADV_SET_RAND_ADDR_COMPLETE_EVT:
xSemaphoreGive(test_sem);
periodic_adv_gap_sem_give();
ESP_LOGI(LOG_TAG, "Extended advertising random address set, status %d, instance %d", param->ext_adv_set_rand_addr.status, param->ext_adv_set_rand_addr.instance);
break;
case ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT:
xSemaphoreGive(test_sem);
periodic_adv_gap_sem_give();
ESP_LOGI(LOG_TAG, "Extended advertising params set, status %d, instance %d", param->ext_adv_set_params.status, param->ext_adv_set_params.instance);
break;
case ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT:
xSemaphoreGive(test_sem);
periodic_adv_gap_sem_give();
ESP_LOGI(LOG_TAG, "Extended advertising data set, status %d, instance %d", param->ext_adv_data_set.status, param->ext_adv_data_set.instance);
break;
case ESP_GAP_BLE_EXT_SCAN_RSP_DATA_SET_COMPLETE_EVT:
xSemaphoreGive(test_sem);
periodic_adv_gap_sem_give();
ESP_LOGI(LOG_TAG, "Extended advertising scan response data set, status %d, instance %d", param->scan_rsp_set.status, param->scan_rsp_set.instance);
break;
case ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(LOG_TAG, "Extended advertising start, status %d, instance numble %d", param->ext_adv_start.status, param->ext_adv_start.instance_num);
periodic_adv_gap_sem_give();
ESP_LOGI(LOG_TAG, "Extended advertising start, status %d, instance number %d", param->ext_adv_start.status, param->ext_adv_start.instance_num);
break;
case ESP_GAP_BLE_EXT_ADV_STOP_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(LOG_TAG, "Extended advertising start, status %d, instance numble %d", param->ext_adv_stop.status, param->ext_adv_stop.instance_num);
periodic_adv_gap_sem_give();
ESP_LOGI(LOG_TAG, "Extended advertising stop, status %d, instance number %d", param->ext_adv_stop.status, param->ext_adv_stop.instance_num);
break;
case ESP_GAP_BLE_PERIODIC_ADV_SET_PARAMS_COMPLETE_EVT:
xSemaphoreGive(test_sem);
periodic_adv_gap_sem_give();
ESP_LOGI(LOG_TAG, "Periodic advertising params set, status %d, instance %d", param->peroid_adv_set_params.status, param->peroid_adv_set_params.instance);
break;
case ESP_GAP_BLE_PERIODIC_ADV_DATA_SET_COMPLETE_EVT:
xSemaphoreGive(test_sem);
periodic_adv_gap_sem_give();
ESP_LOGI(LOG_TAG, "Periodic advertising data set, status %d, instance %d", param->period_adv_data_set.status, param->period_adv_data_set.instance);
break;
case ESP_GAP_BLE_PERIODIC_ADV_START_COMPLETE_EVT:
xSemaphoreGive(test_sem);
periodic_adv_gap_sem_give();
ESP_LOGI(LOG_TAG, "Periodic advertising start, status %d, instance %d", param->period_adv_start.status, param->period_adv_start.instance);
break;
default:
@@ -184,9 +192,18 @@ void app_main(void)
ESP_LOGE(LOG_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
test_sem = xSemaphoreCreateBinary();
if (test_sem == NULL) {
ESP_LOGE(LOG_TAG, "Failed to create semaphore");
return;
}
ret = esp_ble_gap_register_callback(gap_event_handler);
if (ret){
ESP_LOGE(LOG_TAG, "gap register error, error code = %x", ret);
vSemaphoreDelete(test_sem);
test_sem = NULL;
return;
}
@@ -194,8 +211,6 @@ void app_main(void)
esp_bd_addr_t rand_addr;
esp_ble_gap_addr_create_static(rand_addr);
test_sem = xSemaphoreCreateBinary();
// 2M phy extend adv, Non-Connectable and Non-Scannable Undirected advertising
ESP_LOG_BUFFER_HEX(LOG_TAG, rand_addr, ESP_BD_ADDR_LEN);
FUNC_SEND_WAIT_SEM(esp_ble_gap_ext_adv_set_params(EXT_ADV_HANDLE, &ext_adv_params_2M), test_sem);
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -84,10 +84,13 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param
break;
case ESP_GAP_BLE_EXT_SCAN_STOP_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(LOG_TAG, "Extended scanning stop, status %d", param->period_adv_stop.status);
ESP_LOGI(LOG_TAG, "Extended scanning stop, status %d", param->ext_scan_stop.status);
break;
case ESP_GAP_BLE_PERIODIC_ADV_CREATE_SYNC_COMPLETE_EVT:
ESP_LOGI(LOG_TAG, "Periodic advertising create sync, status %d", param->period_adv_create_sync.status);
if (param->period_adv_create_sync.status != ESP_BT_STATUS_SUCCESS) {
periodic_sync = false;
}
break;
case ESP_GAP_BLE_PERIODIC_ADV_SYNC_CANCEL_COMPLETE_EVT:
ESP_LOGI(LOG_TAG, "Periodic advertising sync cancel, status %d", param->period_adv_sync_cancel.status);
@@ -97,9 +100,13 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param
break;
case ESP_GAP_BLE_PERIODIC_ADV_SYNC_LOST_EVT:
ESP_LOGI(LOG_TAG, "Periodic advertising sync lost, sync handle %d", param->periodic_adv_sync_lost.sync_handle);
periodic_sync = false;
break;
case ESP_GAP_BLE_PERIODIC_ADV_SYNC_ESTAB_EVT:
ESP_LOGI(LOG_TAG, "Periodic advertising sync establish, status %d", param->periodic_adv_sync_estab.status);
if (param->periodic_adv_sync_estab.status != ESP_BT_STATUS_SUCCESS) {
periodic_sync = false;
}
ESP_LOGI(LOG_TAG, "address "ESP_BD_ADDR_STR"", ESP_BD_ADDR_HEX(param->periodic_adv_sync_estab.adv_addr));
ESP_LOGI(LOG_TAG, "sync handle %d sid %d perioic adv interval %d adv phy %d", param->periodic_adv_sync_estab.sync_handle,
param->periodic_adv_sync_estab.sid,
@@ -113,13 +120,16 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param
param->ext_adv_report.params.adv_data_len,
ESP_BLE_AD_TYPE_NAME_CMPL,
&adv_name_len);
if ((adv_name != NULL) && (memcmp(adv_name, remote_device_name, adv_name_len) == 0) && !periodic_sync) {
const size_t remote_cap = sizeof(remote_device_name);
size_t remote_len = strnlen(remote_device_name, remote_cap);
if ((adv_name != NULL) && (adv_name_len > 0) &&
(adv_name_len <= remote_cap) &&
(adv_name_len == remote_len) &&
(memcmp(adv_name, remote_device_name, adv_name_len) == 0) && !periodic_sync) {
// Note: If there are multiple devices with the same device name, the device may sync to an unintended one.
// It is recommended to change the default device name to ensure it is unique.
periodic_sync = true;
char adv_temp_name[30] = {'0'};
memcpy(adv_temp_name, adv_name, adv_name_len);
ESP_LOGI(LOG_TAG, "Create sync with the peer device %s", adv_temp_name);
ESP_LOGI(LOG_TAG, "Create sync with the peer device %.*s", (int)adv_name_len, (const char *)adv_name);
periodic_adv_sync_params.sid = param->ext_adv_report.params.sid;
periodic_adv_sync_params.addr_type = param->ext_adv_report.params.addr_type;
memcpy(periodic_adv_sync_params.addr, param->ext_adv_report.params.addr, sizeof(esp_bd_addr_t));
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -81,6 +81,15 @@ typedef struct {
static prepare_type_env_t a_prepare_write_env;
static prepare_type_env_t b_prepare_write_env;
static void prepare_write_env_free(prepare_type_env_t *env)
{
if (env->prepare_buf != NULL) {
free(env->prepare_buf);
env->prepare_buf = NULL;
}
env->prepare_len = 0;
}
//Declare the static function
static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
@@ -176,10 +185,21 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param
{
switch (event) {
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
//esp_ble_gap_start_advertising(&adv_params);
break;
case ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT:
/* Configure adv data only after local privacy is set up (REG_EVT requests it). */
if (param->local_privacy_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(BT_BLE_COEX_TAG, "set local privacy failed, status %d", param->local_privacy_cmpl.status);
} else {
ble_init_adv_data(BLE_ADV_NAME);
}
break;
case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT:
esp_ble_gap_start_advertising(&adv_params);
if (param->scan_rsp_data_raw_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(BT_BLE_COEX_TAG, "set raw scan rsp data failed, status %d", param->scan_rsp_data_raw_cmpl.status);
} else {
esp_ble_gap_start_advertising(&adv_params);
}
break;
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
//advertising start complete event to indicate advertising start successfully or failed
@@ -264,11 +284,69 @@ void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble
}else{
ESP_LOGI(BT_BLE_COEX_TAG,"ESP_GATT_PREP_WRITE_CANCEL");
}
if (prepare_write_env->prepare_buf) {
free(prepare_write_env->prepare_buf);
prepare_write_env->prepare_buf = NULL;
prepare_write_env_free(prepare_write_env);
}
/* Profile A and B handle READ/WRITE identically (apart from per-profile state); these helpers
* avoid the copy-pasted bodies that existed in the previous version. */
static void gatts_coex_read_evt(esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
ESP_LOGI(BT_BLE_COEX_TAG, "GATT_READ_EVT, conn_id %d, trans_id %"PRIu32", handle %d",
param->read.conn_id, param->read.trans_id, param->read.handle);
esp_gatt_rsp_t rsp;
memset(&rsp, 0, sizeof(esp_gatt_rsp_t));
rsp.attr_value.handle = param->read.handle;
rsp.attr_value.len = 4;
rsp.attr_value.value[0] = 0xde;
rsp.attr_value.value[1] = 0xed;
rsp.attr_value.value[2] = 0xbe;
rsp.attr_value.value[3] = 0xef;
esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id,
ESP_GATT_OK, &rsp);
}
static void gatts_coex_write_evt(esp_gatt_if_t gatts_if, int profile_idx, prepare_type_env_t *prep_env,
esp_gatt_char_prop_t char_prop, esp_ble_gatts_cb_param_t *param)
{
ESP_LOGI(BT_BLE_COEX_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %"PRIu32", handle %d",
param->write.conn_id, param->write.trans_id, param->write.handle);
if (!param->write.is_prep) {
ESP_LOGI(BT_BLE_COEX_TAG, "GATT_WRITE_EVT, value len %d, value :", param->write.len);
ESP_LOG_BUFFER_HEX(BT_BLE_COEX_TAG, param->write.value, param->write.len);
if (gl_profile_tab[profile_idx].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 (char_prop & ESP_GATT_CHAR_PROP_BIT_NOTIFY) {
ESP_LOGI(BT_BLE_COEX_TAG, "notify 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[profile_idx].char_handle,
sizeof(notify_data), notify_data, false);
}
} else if (descr_value == 0x0002) {
if (char_prop & ESP_GATT_CHAR_PROP_BIT_INDICATE) {
ESP_LOGI(BT_BLE_COEX_TAG, "indicate enable");
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[profile_idx].char_handle,
sizeof(indicate_data), indicate_data, true);
}
} else if (descr_value == 0x0000) {
ESP_LOGI(BT_BLE_COEX_TAG, "notify/indicate disable ");
} else {
ESP_LOGE(BT_BLE_COEX_TAG, "unknown descr value");
ESP_LOG_BUFFER_HEX(BT_BLE_COEX_TAG, param->write.value, param->write.len);
}
}
}
prepare_write_env->prepare_len = 0;
example_write_event_env(gatts_if, prep_env, param);
}
static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
@@ -281,68 +359,20 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i
gl_profile_tab[PROFILE_A_APP_ID].service_id.id.inst_id = 0x00;
gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_A;
//init BLE adv data and scan response data
ble_init_adv_data(BLE_ADV_NAME);
/* Adv raw data is set from gap_event_handler after ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT. */
esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_A_APP_ID].service_id, GATTS_NUM_HANDLE_A);
break;
case ESP_GATTS_READ_EVT: {
ESP_LOGI(BT_BLE_COEX_TAG, "GATT_READ_EVT, conn_id %d, trans_id %"PRIu32", handle %d", param->read.conn_id, param->read.trans_id, param->read.handle);
esp_gatt_rsp_t rsp;
memset(&rsp, 0, sizeof(esp_gatt_rsp_t));
rsp.attr_value.handle = param->read.handle;
rsp.attr_value.len = 4;
rsp.attr_value.value[0] = 0xde;
rsp.attr_value.value[1] = 0xed;
rsp.attr_value.value[2] = 0xbe;
rsp.attr_value.value[3] = 0xef;
esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id,
ESP_GATT_OK, &rsp);
case ESP_GATTS_READ_EVT:
gatts_coex_read_evt(gatts_if, param);
break;
}
case ESP_GATTS_WRITE_EVT: {
ESP_LOGI(BT_BLE_COEX_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %"PRIu32", handle %d", param->write.conn_id, param->write.trans_id, param->write.handle);
if (!param->write.is_prep) {
ESP_LOGI(BT_BLE_COEX_TAG, "GATT_WRITE_EVT, value len %d, value :", param->write.len);
esp_log_buffer_hex(BT_BLE_COEX_TAG, param->write.value, param->write.len);
if (gl_profile_tab[PROFILE_A_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 (a_property & ESP_GATT_CHAR_PROP_BIT_NOTIFY){
ESP_LOGI(BT_BLE_COEX_TAG, "notify 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[PROFILE_A_APP_ID].char_handle,
sizeof(notify_data), notify_data, false);
}
} else if (descr_value == 0x0002) {
if (a_property & ESP_GATT_CHAR_PROP_BIT_INDICATE) {
ESP_LOGI(BT_BLE_COEX_TAG, "indicate enable");
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[PROFILE_A_APP_ID].char_handle,
sizeof(indicate_data), indicate_data, true);
}
} else if (descr_value == 0x0000) {
ESP_LOGI(BT_BLE_COEX_TAG, "notify/indicate disable ");
} else {
ESP_LOGE(BT_BLE_COEX_TAG, "unknown descr value");
esp_log_buffer_hex(BT_BLE_COEX_TAG, param->write.value, param->write.len);
}
}
}
example_write_event_env(gatts_if, &a_prepare_write_env, param);
case ESP_GATTS_WRITE_EVT:
gatts_coex_write_evt(gatts_if, PROFILE_A_APP_ID, &a_prepare_write_env, a_property, param);
break;
}
case ESP_GATTS_EXEC_WRITE_EVT:
ESP_LOGI(BT_BLE_COEX_TAG, "ESP_GATTS_EXEC_WRITE_EVT");
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL);
/* Use exec_write union member, not write — these share offsets in the union but it
* was a latent bug in master to refer to param->write here. */
esp_ble_gatts_send_response(gatts_if, param->exec_write.conn_id, param->exec_write.trans_id, ESP_GATT_OK, NULL);
example_exec_write_event_env(&a_prepare_write_env, param);
break;
case ESP_GATTS_MTU_EVT:
@@ -394,13 +424,11 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i
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));
case ESP_GATTS_CONNECT_EVT:
break;
}
case ESP_GATTS_DISCONNECT_EVT:
ESP_LOGI(BT_BLE_COEX_TAG, "ESP_GATTS_DISCONNECT_EVT");
prepare_write_env_free(&a_prepare_write_env);
esp_ble_gap_start_advertising(&adv_params);
break;
case ESP_GATTS_CONF_EVT:
@@ -431,63 +459,15 @@ static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_i
esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_B_APP_ID].service_id, GATTS_NUM_HANDLE_B);
break;
case ESP_GATTS_READ_EVT: {
ESP_LOGI(BT_BLE_COEX_TAG, "GATT_READ_EVT, conn_id %d, trans_id %"PRIu32", handle %d", param->read.conn_id, param->read.trans_id, param->read.handle);
esp_gatt_rsp_t rsp;
memset(&rsp, 0, sizeof(esp_gatt_rsp_t));
rsp.attr_value.handle = param->read.handle;
rsp.attr_value.len = 4;
rsp.attr_value.value[0] = 0xde;
rsp.attr_value.value[1] = 0xed;
rsp.attr_value.value[2] = 0xbe;
rsp.attr_value.value[3] = 0xef;
esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id,
ESP_GATT_OK, &rsp);
case ESP_GATTS_READ_EVT:
gatts_coex_read_evt(gatts_if, param);
break;
}
case ESP_GATTS_WRITE_EVT: {
ESP_LOGI(BT_BLE_COEX_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %"PRIu32", handle %d", param->write.conn_id, param->write.trans_id, param->write.handle);
if (!param->write.is_prep) {
ESP_LOGI(BT_BLE_COEX_TAG, "GATT_WRITE_EVT, value len %d, value :", param->write.len);
esp_log_buffer_hex(BT_BLE_COEX_TAG, param->write.value, param->write.len);
if (gl_profile_tab[PROFILE_B_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 (b_property & ESP_GATT_CHAR_PROP_BIT_NOTIFY){
ESP_LOGI(BT_BLE_COEX_TAG, "notify 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[PROFILE_B_APP_ID].char_handle,
sizeof(notify_data), notify_data, false);
}
} else if (descr_value == 0x0002) {
if (b_property & ESP_GATT_CHAR_PROP_BIT_INDICATE) {
ESP_LOGI(BT_BLE_COEX_TAG, "indicate enable");
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[PROFILE_B_APP_ID].char_handle,
sizeof(indicate_data), indicate_data, true);
}
} else if (descr_value == 0x0000) {
ESP_LOGI(BT_BLE_COEX_TAG, "notify/indicate disable ");
} else {
ESP_LOGE(BT_BLE_COEX_TAG, "unknown value");
}
}
}
example_write_event_env(gatts_if, &b_prepare_write_env, param);
case ESP_GATTS_WRITE_EVT:
gatts_coex_write_evt(gatts_if, PROFILE_B_APP_ID, &b_prepare_write_env, b_property, param);
break;
}
case ESP_GATTS_EXEC_WRITE_EVT:
ESP_LOGI(BT_BLE_COEX_TAG, "ESP_GATTS_EXEC_WRITE_EVT");
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL);
esp_ble_gatts_send_response(gatts_if, param->exec_write.conn_id, param->exec_write.trans_id, ESP_GATT_OK, NULL);
example_exec_write_event_env(&b_prepare_write_env, param);
break;
case ESP_GATTS_MTU_EVT:
@@ -551,6 +531,9 @@ static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_i
}
break;
case ESP_GATTS_DISCONNECT_EVT:
ESP_LOGI(BT_BLE_COEX_TAG, "ESP_GATTS_DISCONNECT_EVT (profile B)");
prepare_write_env_free(&b_prepare_write_env);
break;
case ESP_GATTS_OPEN_EVT:
case ESP_GATTS_CANCEL_OPEN_EVT:
case ESP_GATTS_CLOSE_EVT:
@@ -104,6 +104,15 @@ static esp_gatt_char_prop_t b_property = 0;
static prepare_type_env_t a_prepare_write_env;
static prepare_type_env_t b_prepare_write_env;
static uint8_t adv_config_done = 0;
static void prepare_write_env_free(prepare_type_env_t *env)
{
if (env->prepare_buf != NULL) {
free(env->prepare_buf);
env->prepare_buf = NULL;
}
env->prepare_len = 0;
}
static uint8_t char1_str[] = {0x11, 0x22, 0x33};
static bool connect = false;
static bool get_server = false;
@@ -292,11 +301,17 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param
case ESP_GAP_BLE_SCAN_RESULT_EVT: {
esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param;
switch (scan_result->scan_rst.search_evt) {
case ESP_GAP_SEARCH_INQ_RES_EVT:
case ESP_GAP_SEARCH_INQ_RES_EVT: {
const uint16_t ble_adv_storage_max = ESP_BLE_ADV_DATA_LEN_MAX + ESP_BLE_SCAN_RSP_DATA_LEN_MAX;
uint32_t combined_len = (uint32_t)scan_result->scan_rst.adv_data_len +
(uint32_t)scan_result->scan_rst.scan_rsp_len;
uint16_t resolve_len = (combined_len > ble_adv_storage_max)
? ble_adv_storage_max
: (uint16_t)combined_len;
adv_name = esp_ble_resolve_adv_data_by_type(scan_result->scan_rst.ble_adv,
scan_result->scan_rst.adv_data_len + scan_result->scan_rst.scan_rsp_len,
ESP_BLE_AD_TYPE_NAME_CMPL,
&adv_name_len);
resolve_len,
ESP_BLE_AD_TYPE_NAME_CMPL,
&adv_name_len);
if (adv_name != NULL) {
if (strlen(remote_device_name) == adv_name_len && strncmp((char *)adv_name, remote_device_name, adv_name_len) == 0) {
if (connect == false) {
@@ -323,6 +338,7 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param
}
}
break;
}
case ESP_GAP_SEARCH_INQ_CMPL_EVT:
ESP_LOGI(COEX_TAG, "ESP_GAP_SEARCH_INQ_CMPL_EVT, scan stop");
break;
@@ -562,6 +578,7 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
// Update connect flag and get_server flag if peer device is a gatt server
connect = false;
get_server = false;
gattc_profile_tab[GATTC_PROFILE_C_APP_ID].conn_id = UINT16_MAX;
}
ESP_LOGI(COEX_TAG, "ESP_GATTC_DISCONNECT_EVT, reason = %d", p_data->disconnect.reason);
break;
@@ -576,9 +593,11 @@ static void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *
esp_gatt_status_t status = ESP_GATT_OK;
if (param->write.need_rsp) {
if (param->write.is_prep) {
if (param->write.offset > PREPARE_BUF_MAX_SIZE) {
size_t w_off = param->write.offset;
size_t w_len = param->write.len;
if (w_off > PREPARE_BUF_MAX_SIZE) {
status = ESP_GATT_INVALID_OFFSET;
} else if ((param->write.offset + param->write.len) > PREPARE_BUF_MAX_SIZE) {
} else if (w_len > ESP_GATT_MAX_ATTR_LEN || (w_off + w_len) > PREPARE_BUF_MAX_SIZE) {
status = ESP_GATT_INVALID_ATTR_LEN;
}
if (status == ESP_GATT_OK && prepare_write_env->prepare_buf == NULL) {
@@ -590,13 +609,19 @@ static void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *
}
}
esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *)malloc(sizeof(esp_gatt_rsp_t));
esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *)calloc(1, sizeof(esp_gatt_rsp_t));
if (gatt_rsp) {
gatt_rsp->attr_value.len = param->write.len;
gatt_rsp->attr_value.handle = param->write.handle;
gatt_rsp->attr_value.offset = param->write.offset;
gatt_rsp->attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
memcpy(gatt_rsp->attr_value.value, param->write.value, param->write.len);
if (status == ESP_GATT_OK) {
if (param->write.value == NULL) {
status = ESP_GATT_INVALID_ATTR_LEN;
} else {
gatt_rsp->attr_value.len = param->write.len;
memcpy(gatt_rsp->attr_value.value, param->write.value, param->write.len);
}
}
esp_err_t response_err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, gatt_rsp);
if (response_err != ESP_OK) {
ESP_LOGE(COEX_TAG, "Send response error\n");
@@ -604,15 +629,29 @@ static void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *
free(gatt_rsp);
} else {
ESP_LOGE(COEX_TAG, "%s, malloc failed", __func__);
status = ESP_GATT_NO_RESOURCES;
if (status == ESP_GATT_OK) {
status = ESP_GATT_NO_RESOURCES;
}
esp_err_t response_err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id,
param->write.trans_id, status, NULL);
if (response_err != ESP_OK) {
ESP_LOGE(COEX_TAG, "Send response error\n");
}
}
if (status != ESP_GATT_OK) {
return;
}
memcpy(prepare_write_env->prepare_buf + param->write.offset,
memcpy(prepare_write_env->prepare_buf + w_off,
param->write.value,
param->write.len);
prepare_write_env->prepare_len += param->write.len;
w_len);
/* High-water end of written range (not sum of chunk lengths). */
int chunk_end = (int)(w_off + w_len);
if (chunk_end > prepare_write_env->prepare_len) {
prepare_write_env->prepare_len = chunk_end;
}
if (prepare_write_env->prepare_len > PREPARE_BUF_MAX_SIZE) {
prepare_write_env->prepare_len = PREPARE_BUF_MAX_SIZE;
}
} else {
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, NULL);
@@ -623,15 +662,17 @@ static void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *
static void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param)
{
if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) {
esp_log_buffer_hex(COEX_TAG, prepare_write_env->prepare_buf, prepare_write_env->prepare_len);
if (prepare_write_env->prepare_buf != NULL && prepare_write_env->prepare_len > 0) {
size_t log_len = (size_t)prepare_write_env->prepare_len;
if (log_len > PREPARE_BUF_MAX_SIZE) {
log_len = PREPARE_BUF_MAX_SIZE;
}
ESP_LOG_BUFFER_HEX(COEX_TAG, prepare_write_env->prepare_buf, log_len);
}
} else {
ESP_LOGI(COEX_TAG, "ESP_GATT_PREP_WRITE_CANCEL");
}
if (prepare_write_env->prepare_buf) {
free(prepare_write_env->prepare_buf);
prepare_write_env->prepare_buf = NULL;
}
prepare_write_env->prepare_len = 0;
prepare_write_env_free(prepare_write_env);
}
static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
@@ -792,6 +833,7 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i
}
case ESP_GATTS_DISCONNECT_EVT:
ESP_LOGI(COEX_TAG, "ESP_GATTS_DISCONNECT_EVT, disconnect reason 0x%x", param->disconnect.reason);
prepare_write_env_free(&a_prepare_write_env);
if (memcmp(peer_gatts_addr, param->disconnect.remote_bda, sizeof(esp_bd_addr_t))) {
// If the peer device is a GATT client, restart advertising
esp_ble_gap_start_advertising(&adv_params);
@@ -799,8 +841,8 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i
break;
case ESP_GATTS_CONF_EVT:
ESP_LOGI(COEX_TAG, "ESP_GATTS_CONF_EVT, status %d attr_handle %d", param->conf.status, param->conf.handle);
if (param->conf.status != ESP_GATT_OK) {
esp_log_buffer_hex(COEX_TAG, param->conf.value, param->conf.len);
if (param->conf.status == ESP_GATT_OK && param->conf.value != NULL && param->conf.len > 0) {
ESP_LOG_BUFFER_HEX(COEX_TAG, param->conf.value, param->conf.len);
}
break;
case ESP_GATTS_OPEN_EVT:
@@ -940,11 +982,14 @@ static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_i
break;
case ESP_GATTS_CONF_EVT:
ESP_LOGI(COEX_TAG, "ESP_GATTS_CONF_EVT status %d attr_handle %d", param->conf.status, param->conf.handle);
if (param->conf.status != ESP_GATT_OK) {
esp_log_buffer_hex(COEX_TAG, param->conf.value, param->conf.len);
if (param->conf.status == ESP_GATT_OK && param->conf.value != NULL && param->conf.len > 0) {
ESP_LOG_BUFFER_HEX(COEX_TAG, param->conf.value, param->conf.len);
}
break;
case ESP_GATTS_DISCONNECT_EVT:
ESP_LOGI(COEX_TAG, "ESP_GATTS_DISCONNECT_EVT, disconnect reason 0x%x", param->disconnect.reason);
prepare_write_env_free(&b_prepare_write_env);
break;
case ESP_GATTS_OPEN_EVT:
default:
break;