Merge branch 'bugfix/nimble_hid_fixes' into 'master'

fix(nimble): Always read initial BAS level and forward HID report/control/protocol writes as HIDD events

Closes BLERP-2806

See merge request espressif/esp-idf!48598
This commit is contained in:
Rahul Tank
2026-05-22 13:39:27 +05:30
5 changed files with 168 additions and 5 deletions

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -302,6 +302,21 @@ static void attach_report_listeners(esp_gatt_if_t gattc_if, esp_hidh_dev_t *dev)
//subscribe to battery notifications
if (dev->ble.battery_handle) {
uint8_t *rdata = NULL;
uint16_t rlen = 0;
if (event_loop_handle &&
read_char(gattc_if, dev->ble.conn_id, dev->ble.battery_handle,
ESP_GATT_AUTH_REQ_NO_MITM, &rdata, &rlen) == ESP_GATT_OK &&
rlen >= 1 && rdata != NULL) {
esp_hidh_event_data_t p = {0};
p.battery.dev = dev;
p.battery.level = rdata[0];
esp_event_post_to(event_loop_handle, ESP_HIDH_EVENTS, ESP_HIDH_BATTERY_EVENT,
&p, sizeof(esp_hidh_event_data_t), portMAX_DELAY);
}
free(rdata);
register_for_notify(gattc_if, dev->addr.bda, dev->ble.battery_handle);
if (dev->ble.battery_ccc_handle) {
//Write CCC descr to enable notifications

View File

@@ -43,6 +43,9 @@ static void (*s_prev_sync_cb)(void) = NULL;
static struct ble_gap_event_listener nimble_gap_event_listener;
static void nimble_host_synced(void);
void nimble_host_reset(int reason);
static void nimble_report_write_cb(uint16_t attr_handle, uint8_t report_type, uint8_t report_id,
const uint8_t *data, uint16_t len);
static void nimble_char_write_cb(uint16_t attr_handle, uint16_t char_uuid16, uint8_t value);
static inline void lock_hidd(void)
{
@@ -349,6 +352,8 @@ static int nimble_hidd_dev_deinit(void *devp)
ble_hs_cfg.sync_cb = s_prev_sync_cb;
}
ble_hs_cfg.gatts_register_cb = NULL;
ble_svc_hid_register_report_write_cb(NULL);
ble_svc_hid_register_char_write_cb(NULL);
unlock_hidd();
/* STOP_EVENT may be discarded because ble_hid_free_config() deletes the event
@@ -423,6 +428,129 @@ static hidd_le_report_item_t* find_report_by_usage_and_type(uint8_t dev_index, u
return NULL;
}
static void nimble_report_write_cb(uint16_t attr_handle, uint8_t report_type, uint8_t report_id,
const uint8_t *data, uint16_t len)
{
lock_hidd();
if (s_dev == NULL || s_dev->event_loop_handle == NULL || data == NULL) {
unlock_hidd();
return;
}
hidd_le_report_item_t *match = NULL;
uint8_t map_index = 0;
for (uint8_t d = 0; d < s_dev->devices_len && match == NULL; d++) {
for (uint8_t r = 0; r < s_dev->devices[d].reports_len; r++) {
hidd_le_report_item_t *item = &s_dev->devices[d].reports[r];
if (item->handle == attr_handle) {
match = item;
map_index = d;
break;
}
}
}
if (match == NULL) {
unlock_hidd();
return;
}
if (report_type != ESP_HID_REPORT_TYPE_OUTPUT &&
report_type != ESP_HID_REPORT_TYPE_FEATURE) {
ESP_LOGD(TAG, "Ignoring host write for unsupported report type=%u, id=%u, handle=%u",
report_type, report_id, attr_handle);
unlock_hidd();
return;
}
size_t event_data_size = sizeof(esp_hidd_event_data_t);
if (len > 0) {
event_data_size += len;
}
esp_hidd_event_data_t *p_cb_param = (esp_hidd_event_data_t *)calloc(1, event_data_size);
if (p_cb_param == NULL) {
ESP_LOGE(TAG, "%s malloc event data failed!", __func__);
unlock_hidd();
return;
}
if (len > 0) {
memcpy(((uint8_t *)p_cb_param) + sizeof(esp_hidd_event_data_t), data, len);
}
if (report_type == ESP_HID_REPORT_TYPE_OUTPUT) {
p_cb_param->output.dev = s_dev->dev;
p_cb_param->output.usage = match->usage;
p_cb_param->output.report_id = report_id;
p_cb_param->output.length = len;
p_cb_param->output.data = (len > 0) ? (uint8_t *)data : NULL; /* fixed by esp_hidd_process_event_data_handler */
p_cb_param->output.map_index = map_index;
esp_event_post_to(s_dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_OUTPUT_EVENT,
p_cb_param, event_data_size, portMAX_DELAY);
} else if (report_type == ESP_HID_REPORT_TYPE_FEATURE) {
p_cb_param->feature.dev = s_dev->dev;
p_cb_param->feature.usage = match->usage;
p_cb_param->feature.report_id = report_id;
p_cb_param->feature.length = len;
p_cb_param->feature.data = (len > 0) ? (uint8_t *)data : NULL; /* fixed by esp_hidd_process_event_data_handler */
p_cb_param->feature.map_index = map_index;
esp_event_post_to(s_dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_FEATURE_EVENT,
p_cb_param, event_data_size, portMAX_DELAY);
}
free(p_cb_param);
unlock_hidd();
}
static void nimble_char_write_cb(uint16_t attr_handle, uint16_t char_uuid16, uint8_t value)
{
lock_hidd();
if (s_dev == NULL || s_dev->event_loop_handle == NULL) {
unlock_hidd();
return;
}
uint8_t map_index = 0;
bool found = false;
for (uint8_t d = 0; d < s_dev->devices_len; d++) {
if (char_uuid16 == BLE_SVC_HID_CHR_UUID16_PROTOCOL_MODE &&
s_dev->devices[d].hid_protocol_handle == attr_handle) {
found = true;
map_index = d;
break;
}
if (char_uuid16 == BLE_SVC_HID_CHR_UUID16_HID_CTRL_PT &&
s_dev->devices[d].hid_control_handle == attr_handle) {
found = true;
map_index = d;
break;
}
}
if (!found) {
unlock_hidd();
return;
}
esp_hidd_event_data_t cb_param = {0};
if (char_uuid16 == BLE_SVC_HID_CHR_UUID16_PROTOCOL_MODE) {
s_dev->protocol = value;
cb_param.protocol_mode.dev = s_dev->dev;
cb_param.protocol_mode.protocol_mode = value;
cb_param.protocol_mode.map_index = map_index;
esp_event_post_to(s_dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_PROTOCOL_MODE_EVENT,
&cb_param, sizeof(esp_hidd_event_data_t), portMAX_DELAY);
} else if (char_uuid16 == BLE_SVC_HID_CHR_UUID16_HID_CTRL_PT) {
s_dev->control = value;
cb_param.control.dev = s_dev->dev;
cb_param.control.control = value;
cb_param.control.map_index = map_index;
esp_event_post_to(s_dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_CONTROL_EVENT,
&cb_param, sizeof(esp_hidd_event_data_t), portMAX_DELAY);
}
unlock_hidd();
}
static int nimble_hidd_dev_input_set(void *devp, size_t index, size_t id, uint8_t *data, size_t length)
{
hidd_le_report_item_t *p_rpt;
@@ -840,6 +968,8 @@ esp_err_t esp_ble_hidd_dev_init(esp_hidd_dev_t *dev_p, const esp_hid_device_conf
ble_hs_cfg.reset_cb = nimble_host_reset;
ble_hs_cfg.sync_cb = nimble_host_synced;
ble_hs_cfg.gatts_register_cb = nimble_gatt_svr_register_cb;
ble_svc_hid_register_report_write_cb(nimble_report_write_cb);
ble_svc_hid_register_char_write_cb(nimble_char_write_cb);
rc = nimble_hid_start_gatts();
if (rc != ESP_OK) {
if (ble_hs_cfg.reset_cb == nimble_host_reset) {
@@ -849,6 +979,8 @@ esp_err_t esp_ble_hidd_dev_init(esp_hidd_dev_t *dev_p, const esp_hid_device_conf
ble_hs_cfg.sync_cb = s_prev_sync_cb;
}
ble_hs_cfg.gatts_register_cb = NULL;
ble_svc_hid_register_report_write_cb(NULL);
ble_svc_hid_register_char_write_cb(NULL);
ble_hidd_dev_free();
return rc;
}

View File

@@ -70,14 +70,14 @@ static inline void SEND_CB(void)
static inline void LOCK_OPS(void)
{
if (s_ble_hidh_op_mutex) {
xSemaphoreTake(s_ble_hidh_op_mutex, portMAX_DELAY);
xSemaphoreTakeRecursive(s_ble_hidh_op_mutex, portMAX_DELAY);
}
}
static inline void UNLOCK_OPS(void)
{
if (s_ble_hidh_op_mutex) {
xSemaphoreGive(s_ble_hidh_op_mutex);
xSemaphoreGiveRecursive(s_ble_hidh_op_mutex);
}
}
@@ -789,6 +789,20 @@ static void attach_report_listeners(esp_hidh_dev_t *dev)
report = dev->reports;
if (dev->ble.battery_handle) {
uint8_t *rdata = NULL;
uint16_t rlen = 0;
if (event_loop_handle &&
read_char(dev->ble.conn_id, dev->ble.battery_handle, &rdata, &rlen) == 0 &&
rlen >= 1 && rdata != NULL) {
esp_hidh_event_data_t p = {0};
p.battery.dev = dev;
p.battery.level = rdata[0];
esp_event_post_to(event_loop_handle, ESP_HIDH_EVENTS, ESP_HIDH_BATTERY_EVENT,
&p, sizeof(esp_hidh_event_data_t), portMAX_DELAY);
}
free(rdata);
register_for_notify(dev->ble.conn_id, dev->ble.battery_handle);
if (dev->ble.battery_ccc_handle && dev->ble.conn_id >= 0 && dev->connected) {
write_char_descr(dev, dev->ble.battery_ccc_handle, 2, (uint8_t *)&ccc_data);
@@ -1174,7 +1188,7 @@ esp_err_t esp_ble_hidh_init(const esp_hidh_config_t *config)
s_ble_hidh_cb_semaphore = xSemaphoreCreateBinary();
ESP_RETURN_ON_FALSE(s_ble_hidh_cb_semaphore,
ESP_ERR_NO_MEM, TAG, "Allocation failed");
s_ble_hidh_op_mutex = xSemaphoreCreateMutex();
s_ble_hidh_op_mutex = xSemaphoreCreateRecursiveMutex();
if (s_ble_hidh_op_mutex == NULL) {
vSemaphoreDelete(s_ble_hidh_cb_semaphore);
s_ble_hidh_cb_semaphore = NULL;

View File

@@ -41,6 +41,7 @@
#include "esp_hid_gap.h"
static const char *TAG = "HID_DEV_DEMO";
#define HID_BATTERY_LEVEL 60
typedef struct
{
@@ -941,6 +942,7 @@ void app_main(void)
ESP_LOGI(TAG, "setting ble device");
ESP_ERROR_CHECK(
esp_hidd_dev_init(&ble_hid_config, ESP_HID_TRANSPORT_BLE, ble_hidd_event_callback, &s_ble_hid_param.hid_dev));
ESP_ERROR_CHECK(esp_hidd_dev_battery_set(s_ble_hid_param.hid_dev, HID_BATTERY_LEVEL));
#endif
#if CONFIG_BT_HID_DEVICE_ENABLED