ab6fcefea0
- 修改 HID 报告定义,将播放按钮的 ID 更新为播放/暂停 - 更新消费者报告映射,添加播放/暂停的使用情况 - 调整逻辑以确保播放/暂停功能正确映射到新的报告 ID
466 lines
14 KiB
C
466 lines
14 KiB
C
#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 <stdio.h>
|
|
#include <string.h>
|
|
|
|
static const char *TAG = "ble_hid";
|
|
|
|
#define HID_KEYBOARD_REPORT_ID 1
|
|
#define HID_CONSUMER_REPORT_ID 2
|
|
#define HID_KEYBOARD_REPORT_LEN 8
|
|
#define HID_CONSUMER_REPORT_LEN 2
|
|
|
|
/* Bit positions for ESP-IDF mediaReportMap2 (Windows-compatible). */
|
|
#define HID_CC_RPT_VOLUME_BITS 0x3F
|
|
#define HID_CC_RPT_BUTTON_BITS 0xF0
|
|
#define HID_CC_RPT_VOLUME_UP 0x40
|
|
#define HID_CC_RPT_VOLUME_DOWN 0x80
|
|
#define HID_CC_RPT_MUTE 1
|
|
#define HID_CC_RPT_PLAY_PAUSE 13
|
|
#define HID_CC_RPT_SCAN_NEXT_TRK 10
|
|
#define HID_CC_RPT_SCAN_PREV_TRK 11
|
|
|
|
/*
|
|
* Keyboard (Report ID 1) + Consumer Control (Report ID 2, mediaReportMap2).
|
|
* Windows 11 expects the bit-packed consumer report from ESP-IDF examples,
|
|
* not a raw 16-bit HID usage in the input report.
|
|
*/
|
|
static const unsigned char s_hid_report_map[] = {
|
|
/* Keyboard - Report ID 1 */
|
|
0x05, 0x01, // Usage Page (Generic Desktop)
|
|
0x09, 0x06, // Usage (Keyboard)
|
|
0xA1, 0x01, // Collection (Application)
|
|
0x85, HID_KEYBOARD_REPORT_ID,
|
|
0x05, 0x07, // Usage Page (Keyboard/Keypad)
|
|
0x19, 0xE0, // Usage Minimum (0xE0)
|
|
0x29, 0xE7, // Usage Maximum (0xE7)
|
|
0x15, 0x00, // Logical Minimum (0)
|
|
0x25, 0x01, // Logical Maximum (1)
|
|
0x75, 0x01, // Report Size (1)
|
|
0x95, 0x08, // Report Count (8)
|
|
0x81, 0x02, // Input (Data, Variable, Absolute)
|
|
0x95, 0x01, // Report Count (1)
|
|
0x75, 0x08, // Report Size (8)
|
|
0x81, 0x03, // Input (Constant, Variable, Absolute)
|
|
0x95, 0x05, // Report Count (5)
|
|
0x75, 0x01, // Report Size (1)
|
|
0x05, 0x08, // Usage Page (LEDs)
|
|
0x19, 0x01, // Usage Minimum (0x01)
|
|
0x29, 0x05, // Usage Maximum (0x05)
|
|
0x91, 0x02, // Output (Data, Variable, Absolute)
|
|
0x95, 0x01, // Report Count (1)
|
|
0x75, 0x03, // Report Size (3)
|
|
0x91, 0x03, // Output (Constant, Variable, Absolute)
|
|
0x95, 0x05, // Report Count (5)
|
|
0x75, 0x08, // Report Size (8)
|
|
0x15, 0x00, // Logical Minimum (0)
|
|
0x25, 0x65, // Logical Maximum (101)
|
|
0x05, 0x07, // Usage Page (Keyboard/Keypad)
|
|
0x19, 0x00, // Usage Minimum (0x00)
|
|
0x29, 0x65, // Usage Maximum (0x65)
|
|
0x81, 0x00, // Input (Data, Array, Absolute)
|
|
0xC0, // End Collection
|
|
|
|
/* Consumer Control - Report ID 2 (mediaReportMap2 from ESP-IDF) */
|
|
0x05, 0x0C, // Usage Page (Consumer)
|
|
0x09, 0x01, // Usage (Consumer Control)
|
|
0xA1, 0x01, // Collection (Application)
|
|
0x85, HID_CONSUMER_REPORT_ID,
|
|
0x09, 0x02, // Usage (Numeric Key Pad)
|
|
0xA1, 0x02, // Collection (Logical)
|
|
0x05, 0x09, // Usage Page (Button)
|
|
0x19, 0x01, // Usage Minimum (0x01)
|
|
0x29, 0x0A, // Usage Maximum (0x0A)
|
|
0x15, 0x01, // Logical Minimum (1)
|
|
0x25, 0x0A, // Logical Maximum (10)
|
|
0x75, 0x04, // Report Size (4)
|
|
0x95, 0x01, // Report Count (1)
|
|
0x81, 0x00, // Input (Data, Array, Absolute)
|
|
0xC0, // End Collection
|
|
0x05, 0x0C, // Usage Page (Consumer)
|
|
0x09, 0x86, // Usage (Channel)
|
|
0x15, 0xFF, // Logical Minimum (-1)
|
|
0x25, 0x01, // Logical Maximum (1)
|
|
0x75, 0x02, // Report Size (2)
|
|
0x95, 0x01, // Report Count (1)
|
|
0x81, 0x46, // Input (Data, Variable, Relative)
|
|
0x09, 0xE9, // Usage (Volume Increment)
|
|
0x09, 0xEA, // Usage (Volume Decrement)
|
|
0x15, 0x00, // Logical Minimum (0)
|
|
0x75, 0x01, // Report Size (1)
|
|
0x95, 0x02, // Report Count (2)
|
|
0x81, 0x02, // Input (Data, Variable, Absolute)
|
|
0x09, 0xE2, // Usage (Mute)
|
|
0x09, 0x30, // Usage (Power)
|
|
0x09, 0x83, // Usage (Recall Last)
|
|
0x09, 0x81, // Usage (Assign Selection)
|
|
0x09, 0xB0, // Usage (Play)
|
|
0x09, 0xB1, // Usage (Pause)
|
|
0x09, 0xB2, // Usage (Record)
|
|
0x09, 0xB3, // Usage (Fast Forward)
|
|
0x09, 0xB4, // Usage (Rewind)
|
|
0x09, 0xB5, // Usage (Scan Next Track)
|
|
0x09, 0xB6, // Usage (Scan Previous Track)
|
|
0x09, 0xB7, // Usage (Stop)
|
|
0x09, 0xCD, // Usage (Play/Pause)
|
|
0x15, 0x01, // Logical Minimum (1)
|
|
0x25, 0x0D, // Logical Maximum (13)
|
|
0x75, 0x04, // Report Size (4)
|
|
0x95, 0x01, // Report Count (1)
|
|
0x81, 0x00, // Input (Data, Array, Absolute)
|
|
0x09, 0x80, // Usage (Selection)
|
|
0xA1, 0x02, // Collection (Logical)
|
|
0x05, 0x09, // Usage Page (Button)
|
|
0x19, 0x01, // Usage Minimum (0x01)
|
|
0x29, 0x03, // Usage Maximum (0x03)
|
|
0x15, 0x01, // Logical Minimum (1)
|
|
0x25, 0x03, // Logical Maximum (3)
|
|
0x75, 0x02, // Report Size (2)
|
|
0x81, 0x00, // Input (Data, Array, Absolute)
|
|
0xC0, // End Collection
|
|
0x81, 0x03, // Input (Constant, Variable, Absolute)
|
|
0xC0, // End Collection
|
|
};
|
|
|
|
static esp_hid_raw_report_map_t s_report_maps[] = {
|
|
{
|
|
.data = s_hid_report_map,
|
|
.len = sizeof(s_hid_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_cc_set_volume_up(uint8_t *buffer)
|
|
{
|
|
buffer[0] &= HID_CC_RPT_VOLUME_BITS;
|
|
buffer[0] |= HID_CC_RPT_VOLUME_UP;
|
|
}
|
|
|
|
static void ble_hid_cc_set_volume_down(uint8_t *buffer)
|
|
{
|
|
buffer[0] &= HID_CC_RPT_VOLUME_BITS;
|
|
buffer[0] |= HID_CC_RPT_VOLUME_DOWN;
|
|
}
|
|
|
|
static void ble_hid_cc_set_button(uint8_t *buffer, uint8_t button)
|
|
{
|
|
buffer[1] &= HID_CC_RPT_BUTTON_BITS;
|
|
buffer[1] |= button;
|
|
}
|
|
|
|
static bool ble_hid_encode_consumer_report(uint16_t usage, uint8_t buffer[HID_CONSUMER_REPORT_LEN])
|
|
{
|
|
memset(buffer, 0, HID_CONSUMER_REPORT_LEN);
|
|
|
|
switch (usage) {
|
|
case HID_CONSUMER_VOLUME_UP:
|
|
ble_hid_cc_set_volume_up(buffer);
|
|
return true;
|
|
case HID_CONSUMER_VOLUME_DOWN:
|
|
ble_hid_cc_set_volume_down(buffer);
|
|
return true;
|
|
case HID_CONSUMER_MUTE:
|
|
ble_hid_cc_set_button(buffer, HID_CC_RPT_MUTE);
|
|
return true;
|
|
case HID_CONSUMER_PLAY_PAUSE:
|
|
ble_hid_cc_set_button(buffer, HID_CC_RPT_PLAY_PAUSE);
|
|
return true;
|
|
case HID_CONSUMER_SCAN_NEXT:
|
|
ble_hid_cc_set_button(buffer, HID_CC_RPT_SCAN_NEXT_TRK);
|
|
return true;
|
|
case HID_CONSUMER_SCAN_PREVIOUS:
|
|
ble_hid_cc_set_button(buffer, HID_CC_RPT_SCAN_PREV_TRK);
|
|
return true;
|
|
default:
|
|
ESP_LOGW(TAG, "unsupported consumer usage 0x%04X", usage);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static void ble_hid_log_buffer(const char *label, const uint8_t *data, size_t len)
|
|
{
|
|
char hex[40];
|
|
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 int ble_hid_send_input_report(uint8_t report_id, const uint8_t *buffer, size_t len,
|
|
const char *label)
|
|
{
|
|
esp_err_t ret;
|
|
|
|
if (!ble_hid_ready()) {
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
|
|
ble_hid_log_buffer(label, buffer, len);
|
|
ret = esp_hidd_dev_input_set(s_hid_dev, 0, report_id, (uint8_t *)buffer, len);
|
|
return (ret == ESP_OK) ? 0 : ret;
|
|
}
|
|
|
|
static void ble_hid_send_idle_reports(void)
|
|
{
|
|
uint8_t keyboard[HID_KEYBOARD_REPORT_LEN] = {0};
|
|
uint8_t consumer[HID_CONSUMER_REPORT_LEN] = {0};
|
|
|
|
if (!ble_hid_ready()) {
|
|
return;
|
|
}
|
|
|
|
(void)ble_hid_send_input_report(HID_KEYBOARD_REPORT_ID, keyboard, sizeof(keyboard),
|
|
"keyboard idle");
|
|
(void)ble_hid_send_input_report(HID_CONSUMER_REPORT_ID, consumer, sizeof(consumer),
|
|
"consumer idle");
|
|
}
|
|
|
|
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_reports();
|
|
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 ready (keyboard=%u bytes, consumer=%u bytes)",
|
|
(unsigned)HID_KEYBOARD_REPORT_LEN, (unsigned)HID_CONSUMER_REPORT_LEN);
|
|
return ESP_OK;
|
|
}
|
|
|
|
void ble_hid_on_input_notify(bool enabled)
|
|
{
|
|
if (enabled) {
|
|
ble_hid_send_idle_reports();
|
|
}
|
|
}
|
|
|
|
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};
|
|
|
|
if (!ble_hid_ready()) {
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
|
|
buffer[0] = modifier;
|
|
buffer[2] = keycode;
|
|
return ble_hid_send_input_report(HID_KEYBOARD_REPORT_ID, buffer, sizeof(buffer), "press");
|
|
}
|
|
|
|
int ble_hid_key_release(void)
|
|
{
|
|
uint8_t buffer[HID_KEYBOARD_REPORT_LEN] = {0};
|
|
|
|
if (!ble_hid_ready()) {
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
|
|
return ble_hid_send_input_report(HID_KEYBOARD_REPORT_ID, buffer, sizeof(buffer), "release");
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
int ble_hid_consumer_press(uint16_t usage)
|
|
{
|
|
uint8_t buffer[HID_CONSUMER_REPORT_LEN] = {0};
|
|
|
|
if (!ble_hid_ready()) {
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
|
|
if (usage == 0) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
if (!ble_hid_encode_consumer_report(usage, buffer)) {
|
|
return ESP_ERR_NOT_SUPPORTED;
|
|
}
|
|
|
|
return ble_hid_send_input_report(HID_CONSUMER_REPORT_ID, buffer, sizeof(buffer),
|
|
"consumer press");
|
|
}
|
|
|
|
int ble_hid_consumer_release(void)
|
|
{
|
|
uint8_t buffer[HID_CONSUMER_REPORT_LEN] = {0};
|
|
|
|
if (!ble_hid_ready()) {
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
|
|
return ble_hid_send_input_report(HID_CONSUMER_REPORT_ID, buffer, sizeof(buffer),
|
|
"consumer release");
|
|
}
|
|
|
|
int ble_hid_send_consumer(uint16_t usage)
|
|
{
|
|
int rc;
|
|
|
|
rc = ble_hid_consumer_press(usage);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(100));
|
|
return ble_hid_consumer_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;
|
|
}
|
|
|
|
int ble_hid_send_consumer(uint16_t usage)
|
|
{
|
|
(void)usage;
|
|
return -1;
|
|
}
|
|
|
|
#endif /* BOARD_BLE_ENABLED */
|