#include "ble_hid.h" #include "board_config.h" #if BOARD_BLE_ENABLED #include "esp_hidd.h" #include "esp_hid_common.h" #include "esp_hid_gap.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include #include static const char *TAG = "ble_hid"; #define HID_KEYBOARD_REPORT_ID 1 #define HID_KEYBOARD_REPORT_LEN 7 static const unsigned char s_keyboard_report_map[] = { 0x05, 0x01, 0x09, 0x06, 0xA1, 0x01, 0x85, 0x01, 0x05, 0x07, 0x19, 0xE0, 0x29, 0xE7, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08, 0x81, 0x03, 0x95, 0x05, 0x75, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x05, 0x91, 0x02, 0x95, 0x01, 0x75, 0x03, 0x91, 0x03, 0x95, 0x05, 0x75, 0x08, 0x15, 0x00, 0x25, 0x65, 0x05, 0x07, 0x19, 0x00, 0x29, 0x65, 0x81, 0x00, 0xC0, }; static esp_hid_raw_report_map_t s_report_maps[] = { { .data = s_keyboard_report_map, .len = sizeof(s_keyboard_report_map), }, }; static esp_hid_device_config_t s_hid_config = { .vendor_id = 0x16C0, .product_id = 0x05DF, .version = 0x0100, .device_name = CONFIG_ESP_BLE_DEVICE_NAME, .manufacturer_name = "HeavenlyHeroStar", .serial_number = "1234567890", .report_maps = s_report_maps, .report_maps_len = 1, }; static esp_hidd_dev_t *s_hid_dev; static ble_hid_status_cb_t s_status_cb; static bool s_connected; static void ble_hid_log_buffer(const char *label, const uint8_t *data, size_t len) { char hex[32]; size_t pos = 0; for (size_t i = 0; i < len && pos + 3 < sizeof(hex); i++) { pos += (size_t)snprintf(hex + pos, sizeof(hex) - pos, "%02x ", data[i]); } ESP_LOGI(TAG, "%s len=%u: %s", label, (unsigned)len, hex); } static void ble_hid_send_idle_report(void) { uint8_t buffer[HID_KEYBOARD_REPORT_LEN] = {0}; if (!ble_hid_ready()) { return; } ble_hid_log_buffer("idle", buffer, sizeof(buffer)); (void)esp_hidd_dev_input_set(s_hid_dev, 0, HID_KEYBOARD_REPORT_ID, buffer, sizeof(buffer)); } static void ble_hid_idle_task(void *arg) { (void)arg; /* Windows often enables notify a few seconds after connect. */ vTaskDelay(pdMS_TO_TICKS(3500)); ble_hid_send_idle_report(); vTaskDelete(NULL); } static void ble_hid_hidd_event(void *handler_args, esp_event_base_t base, int32_t id, void *event_data) { esp_hidd_event_t event = (esp_hidd_event_t)id; esp_hidd_event_data_t *param = (esp_hidd_event_data_t *)event_data; (void)handler_args; (void)base; switch (event) { case ESP_HIDD_START_EVENT: ESP_LOGI(TAG, "HID stack started"); esp_hid_ble_gap_adv_start(); break; case ESP_HIDD_CONNECT_EVENT: if (param->connect.status == 0) { s_connected = true; ESP_LOGI(TAG, "HID connected"); if (s_status_cb != NULL) { s_status_cb(true); } xTaskCreate(ble_hid_idle_task, "ble_hid_idle", 2048, NULL, 5, NULL); } else { ESP_LOGW(TAG, "HID connect failed status=%d", param->connect.status); } break; case ESP_HIDD_DISCONNECT_EVENT: s_connected = false; ESP_LOGI(TAG, "HID disconnected reason=%d", param->disconnect.reason); if (s_status_cb != NULL) { s_status_cb(false); } esp_hid_ble_gap_adv_start(); break; case ESP_HIDD_PROTOCOL_MODE_EVENT: ESP_LOGI(TAG, "protocol mode map=%u mode=%s", param->protocol_mode.map_index, param->protocol_mode.protocol_mode ? "REPORT" : "BOOT"); break; case ESP_HIDD_CONTROL_EVENT: ESP_LOGI(TAG, "control map=%u %sSUSPEND", param->control.map_index, param->control.control ? "EXIT_" : ""); break; case ESP_HIDD_OUTPUT_EVENT: ESP_LOGD(TAG, "output map=%u id=%u len=%d", param->output.map_index, param->output.report_id, param->output.length); break; default: break; } } int ble_hid_init(ble_hid_status_cb_t status_cb) { esp_err_t ret; s_status_cb = status_cb; s_connected = false; ret = esp_hidd_dev_init(&s_hid_config, ESP_HID_TRANSPORT_BLE, ble_hid_hidd_event, &s_hid_dev); if (ret != ESP_OK) { ESP_LOGE(TAG, "esp_hidd_dev_init failed: %d", ret); return ret; } ESP_LOGI(TAG, "esp_hidd keyboard ready (report payload=%u bytes)", (unsigned)HID_KEYBOARD_REPORT_LEN); return ESP_OK; } void ble_hid_on_input_notify(bool enabled) { if (enabled) { ble_hid_send_idle_report(); } } bool ble_hid_ready(void) { return s_connected && s_hid_dev != NULL; } int ble_hid_key_press(uint8_t modifier, uint8_t keycode) { uint8_t buffer[HID_KEYBOARD_REPORT_LEN] = {0}; esp_err_t ret; if (!ble_hid_ready()) { return ESP_ERR_INVALID_STATE; } buffer[0] = modifier; buffer[2] = keycode; ble_hid_log_buffer("press", buffer, sizeof(buffer)); ret = esp_hidd_dev_input_set(s_hid_dev, 0, HID_KEYBOARD_REPORT_ID, buffer, sizeof(buffer)); return (ret == ESP_OK) ? 0 : ret; } int ble_hid_key_release(void) { uint8_t buffer[HID_KEYBOARD_REPORT_LEN] = {0}; esp_err_t ret; if (!ble_hid_ready()) { return ESP_ERR_INVALID_STATE; } ble_hid_log_buffer("release", buffer, sizeof(buffer)); ret = esp_hidd_dev_input_set(s_hid_dev, 0, HID_KEYBOARD_REPORT_ID, buffer, sizeof(buffer)); return (ret == ESP_OK) ? 0 : ret; } int ble_hid_send_combo(uint8_t modifier, uint8_t keycode) { int rc; rc = ble_hid_key_press(modifier, keycode); if (rc != 0) { return rc; } vTaskDelay(pdMS_TO_TICKS(50)); return ble_hid_key_release(); } #else int ble_hid_init(ble_hid_status_cb_t status_cb) { (void)status_cb; return 0; } bool ble_hid_ready(void) { return false; } int ble_hid_key_press(uint8_t modifier, uint8_t keycode) { (void)modifier; (void)keycode; return -1; } int ble_hid_key_release(void) { return -1; } int ble_hid_send_combo(uint8_t modifier, uint8_t keycode) { (void)modifier; (void)keycode; return -1; } #endif /* BOARD_BLE_ENABLED */