feat: 添加 BLE HID 键盘支持与配置

- 在 sdkconfig.defaults 中更新 BLE 配置以支持 HID 键盘功能
- 新增 ble_hid.c 和 ble_hid.h 文件,实现 BLE HID 键盘的核心功能
- 更新 CMakeLists.txt,包含新的服务文件以支持 BLE HID
- 修改 ui_handlers.c,响应启动按钮事件以发送 HID 按键
- 更新 ble_manager.c,初始化 BLE HID 功能并处理连接状态
- 新增 esp_hid_gap.c 和 esp_hid_gap.h 文件,管理 BLE HID 的 GAP 相关功能
This commit is contained in:
2026-07-02 00:20:52 +08:00
parent 52546e35fe
commit 68dd7e95ab
8 changed files with 1645 additions and 209 deletions
+30 -197
View File
@@ -1,96 +1,28 @@
#include "ble_manager.h"
#include "board_config.h"
#include "ble_hid.h"
#include "esp_log.h"
#include "sdkconfig.h"
#if BOARD_BLE_ENABLED
#include "driver/gpio.h"
#include "esp_hid_gap.h"
#include "esp_hid_common.h"
#include "host/ble_hs.h"
#include "host/ble_uuid.h"
#include "host/util/util.h"
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"
#include "store/config/ble_store_config.h"
#include <string.h>
#define BLE_DEVICE_NAME CONFIG_ESP_BLE_DEVICE_NAME
#define HHS_SVC_UUID16 0xFFF0
#define HHS_CHR_UUID16 0xFFF1
static const char *TAG = "ble_manager";
#define BLE_DEVICE_NAME CONFIG_ESP_BLE_DEVICE_NAME
static ble_status_cb_t s_status_cb;
static bool s_connected;
static uint8_t s_own_addr_type;
static uint16_t s_conn_handle = BLE_HS_CONN_HANDLE_NONE;
static uint8_t s_chr_val[20] = "HeavenlyHeroStar";
static uint16_t s_chr_val_handle;
static const ble_uuid16_t s_hhs_svc_uuid = BLE_UUID16_INIT(HHS_SVC_UUID16);
static const ble_uuid16_t s_hhs_chr_uuid = BLE_UUID16_INIT(HHS_CHR_UUID16);
static void start_advertising(void);
static int gap_event_handler(struct ble_gap_event *event, void *arg);
static int chr_access(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
(void)arg;
if (attr_handle != s_chr_val_handle) {
return BLE_ATT_ERR_UNLIKELY;
}
switch (ctxt->op) {
case BLE_GATT_ACCESS_OP_READ_CHR:
return os_mbuf_append(ctxt->om, s_chr_val, sizeof(s_chr_val)) == 0
? 0
: BLE_ATT_ERR_INSUFFICIENT_RES;
case BLE_GATT_ACCESS_OP_WRITE_CHR: {
uint16_t len = OS_MBUF_PKTLEN(ctxt->om);
if (len > sizeof(s_chr_val)) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
memset(s_chr_val, 0, sizeof(s_chr_val));
int rc = ble_hs_mbuf_to_flat(ctxt->om, s_chr_val, sizeof(s_chr_val), NULL);
if (rc != 0) {
return BLE_ATT_ERR_UNLIKELY;
}
ESP_LOGI(TAG, "characteristic write from conn=%d, len=%u", conn_handle, len);
return 0;
}
default:
return BLE_ATT_ERR_UNLIKELY;
}
}
static const struct ble_gatt_svc_def s_gatt_svcs[] = {
{
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = &s_hhs_svc_uuid.u,
.characteristics =
(struct ble_gatt_chr_def[]){
{
.uuid = &s_hhs_chr_uuid.u,
.access_cb = chr_access,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE,
.val_handle = &s_chr_val_handle,
},
{0},
},
},
{0},
};
static void notify_status(bool connected)
static void on_hid_status(bool connected)
{
s_connected = connected;
gpio_set_level(BOARD_PIN_LED, connected ? 1 : 0);
@@ -99,119 +31,15 @@ static void notify_status(bool connected)
}
}
static void start_advertising(void)
{
struct ble_hs_adv_fields fields = {0};
struct ble_gap_adv_params adv_params = {0};
const char *name = ble_svc_gap_device_name();
int rc;
fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;
fields.name = (uint8_t *)name;
fields.name_len = strlen(name);
fields.name_is_complete = 1;
rc = ble_gap_adv_set_fields(&fields);
if (rc != 0) {
ESP_LOGE(TAG, "failed to set advertising data: %d", rc);
return;
}
adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
rc = ble_gap_adv_start(s_own_addr_type, NULL, BLE_HS_FOREVER, &adv_params,
gap_event_handler, NULL);
if (rc != 0) {
ESP_LOGE(TAG, "failed to start advertising: %d", rc);
return;
}
ESP_LOGI(TAG, "advertising as \"%s\"", name);
}
static int gap_event_handler(struct ble_gap_event *event, void *arg)
{
(void)arg;
switch (event->type) {
case BLE_GAP_EVENT_CONNECT:
if (event->connect.status == 0) {
s_conn_handle = event->connect.conn_handle;
ESP_LOGI(TAG, "connected, handle=%d", s_conn_handle);
notify_status(true);
} else {
ESP_LOGW(TAG, "connection failed, status=%d", event->connect.status);
start_advertising();
}
return 0;
case BLE_GAP_EVENT_DISCONNECT:
ESP_LOGI(TAG, "disconnected, reason=%d", event->disconnect.reason);
s_conn_handle = BLE_HS_CONN_HANDLE_NONE;
notify_status(false);
start_advertising();
return 0;
case BLE_GAP_EVENT_ADV_COMPLETE:
start_advertising();
return 0;
default:
return 0;
}
}
static void on_stack_sync(void)
{
int rc = ble_hs_util_ensure_addr(0);
if (rc != 0) {
ESP_LOGE(TAG, "no available BT address");
return;
}
rc = ble_hs_id_infer_auto(0, &s_own_addr_type);
if (rc != 0) {
ESP_LOGE(TAG, "failed to infer address type: %d", rc);
return;
}
start_advertising();
}
static void on_stack_reset(int reason)
{
ESP_LOGW(TAG, "nimble stack reset, reason=%d", reason);
}
static void gatt_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg)
{
(void)ctxt;
(void)arg;
}
static int gatt_svc_init(void)
{
int rc;
ble_svc_gatt_init();
rc = ble_gatts_count_cfg(s_gatt_svcs);
if (rc != 0) {
return rc;
}
return ble_gatts_add_svcs(s_gatt_svcs);
}
void ble_store_config_init(void);
static void nimble_host_task(void *param)
{
(void)param;
nimble_port_run();
vTaskDelete(NULL);
nimble_port_freertos_deinit();
}
void ble_store_config_init(void);
#endif /* BOARD_BLE_ENABLED */
void ble_manager_set_status_cb(ble_status_cb_t cb)
@@ -235,36 +63,41 @@ bool ble_manager_is_connected(void)
void ble_manager_init(void)
{
#if BOARD_BLE_ENABLED
int rc;
esp_err_t ret;
gpio_reset_pin(BOARD_PIN_LED);
gpio_set_direction(BOARD_PIN_LED, GPIO_MODE_OUTPUT);
gpio_set_level(BOARD_PIN_LED, 0);
ESP_ERROR_CHECK(nimble_port_init());
ble_svc_gap_init();
rc = ble_svc_gap_device_name_set(BLE_DEVICE_NAME);
if (rc != 0) {
ESP_LOGE(TAG, "failed to set device name");
ESP_LOGI(TAG, "init esp_hid_gap (NimBLE)");
ret = esp_hid_gap_init(HIDD_BLE_MODE);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "esp_hid_gap_init failed: %d", ret);
return;
}
rc = gatt_svc_init();
if (rc != 0) {
ESP_LOGE(TAG, "failed to init GATT service: %d", rc);
ret = esp_hid_ble_gap_adv_init(ESP_HID_APPEARANCE_KEYBOARD, BLE_DEVICE_NAME);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "esp_hid_ble_gap_adv_init failed: %d", ret);
return;
}
ret = ble_hid_init(on_hid_status);
if (ret != 0) {
ESP_LOGE(TAG, "ble_hid_init failed: %d", ret);
return;
}
ble_hs_cfg.reset_cb = on_stack_reset;
ble_hs_cfg.sync_cb = on_stack_sync;
ble_hs_cfg.gatts_register_cb = gatt_register_cb;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
ble_store_config_init();
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
nimble_port_freertos_init(nimble_host_task);
ret = esp_nimble_enable(nimble_host_task);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "esp_nimble_enable failed: %d", ret);
return;
}
ESP_LOGI(TAG, "BLE peripheral started");
ESP_LOGI(TAG, "BLE HID keyboard started as \"%s\"", BLE_DEVICE_NAME);
#else
ESP_LOGI("ble_manager", "BLE disabled by board config");
#endif