#include #include #include "xbox_controller.h" #include "nvs_flash.h" #include "esp_err.h" #include "esp_hidh.h" #include "esp_hid_common.h" #include "esp_gattc_api.h" #include "esp_bt_main.h" #include "esp_bt.h" #include "esp_log.h" #include "esp_gap_ble_api.h" #include "freertos/idf_additions.h" #define HIDH_IDLE_MODE 0x00 #define HIDH_BLE_MODE 0x01 #define HIDH_BT_MODE 0x02 #define HIDH_BTDM_MODE 0x03 #define XBOX_CONTROLLER_INDEX_BUTTONS_DIR 12 #define XBOX_CONTROLLER_INDEX_BUTTONS_MAIN 13 #define XBOX_CONTROLLER_INDEX_BUTTONS_CENTER 14 #define XBOX_CONTROLLER_INDEX_BUTTONS_SHARE 15 typedef struct { bool A; bool B; bool X; bool Y; bool UP; bool DOWN; bool LEFT; bool RIGHT; bool RB; bool LB; bool START; bool SELECT; bool XBOX_BUT; bool RS; bool LS; uint16_t joyLX; uint16_t joyLY; uint16_t joyRX; uint16_t joyRY; uint16_t trigL; uint16_t trigR; }controller_state_t; controller_state_t xbox_state; void print_state(void) { ESP_LOGI("xbox_ctrl_output", "A:%1d,B:%1d,X:%1d,Y:%1d,U:%1d,D:%1d,L%1d,R:%1d\n", xbox_state.A, xbox_state.B, xbox_state.X, xbox_state.Y, xbox_state.UP, xbox_state.DOWN, xbox_state.LEFT, xbox_state.RIGHT); ESP_LOGI("xbox_ctrl_output", "RB:%1d,LB:%1d,RS:%1d,LS:%1d,START:%1d,SELECT:%1d,XBOX:%1d", xbox_state.RB, xbox_state.LB, xbox_state.RS, xbox_state.LS, xbox_state.START, xbox_state.SELECT, xbox_state.XBOX_BUT); ESP_LOGI("xbox_ctrl_output", "jLX:%5d,jLY:%5d,jRX:%5d,jRY:%5d,tL:%5d,tR:%5d", xbox_state.joyLX, xbox_state.joyLY, xbox_state.joyRX, xbox_state.joyRY, xbox_state.trigL, xbox_state.trigR); } const char *esp_ble_key_type_str(esp_ble_key_type_t key_type) { const char *key_str = NULL; switch (key_type) { case ESP_LE_KEY_NONE: key_str = "ESP_LE_KEY_NONE"; break; case ESP_LE_KEY_PENC: key_str = "ESP_LE_KEY_PENC"; break; case ESP_LE_KEY_PID: key_str = "ESP_LE_KEY_PID"; break; case ESP_LE_KEY_PCSRK: key_str = "ESP_LE_KEY_PCSRK"; break; case ESP_LE_KEY_PLK: key_str = "ESP_LE_KEY_PLK"; break; case ESP_LE_KEY_LLK: key_str = "ESP_LE_KEY_LLK"; break; case ESP_LE_KEY_LENC: key_str = "ESP_LE_KEY_LENC"; break; case ESP_LE_KEY_LID: key_str = "ESP_LE_KEY_LID"; break; case ESP_LE_KEY_LCSRK: key_str = "ESP_LE_KEY_LCSRK"; break; default: key_str = "INVALID BLE KEY TYPE"; break; } return key_str; } void controller_msg_callback(void *handler_args, esp_event_base_t base, int32_t id, void *event_data) { esp_hidh_event_t event = (esp_hidh_event_t)id; esp_hidh_event_data_t *param = (esp_hidh_event_data_t *) event_data; switch (event) { case ESP_HIDH_OPEN_EVENT: { if (param->open.status == ESP_OK) { const uint8_t *bda = esp_hidh_dev_bda_get(param->open.dev); ESP_LOGI("xbox_ctrl", ESP_BD_ADDR_STR " OPEN: %s", ESP_BD_ADDR_HEX(bda), esp_hidh_dev_name_get(param->open.dev)); esp_hidh_dev_dump(param->open.dev, stdout); } else { ESP_LOGE("xbox_ctrl", " OPEN failed!"); } break; } case ESP_HIDH_BATTERY_EVENT: { const uint8_t *bda = esp_hidh_dev_bda_get(param->battery.dev); ESP_LOGI("xbox_ctrl", ESP_BD_ADDR_STR " BATTERY: %d%%", ESP_BD_ADDR_HEX(bda), param->battery.level); break; } case ESP_HIDH_INPUT_EVENT: { uint8_t btnBits; btnBits = param->input.data[XBOX_CONTROLLER_INDEX_BUTTONS_MAIN]; xbox_state.A = (btnBits & (1 << 0) << 0); xbox_state.B = (btnBits & (1 << 1) << 1); xbox_state.X = (btnBits & (1 << 2) << 2); xbox_state.Y = (btnBits & (1 << 3) << 3); xbox_state.LB = (btnBits & (1 << 6) << 6); xbox_state.RB = (btnBits & (1 << 7) << 7); btnBits = param->input.data[XBOX_CONTROLLER_INDEX_BUTTONS_CENTER]; xbox_state.SELECT = (btnBits & (1 << 2) << 2); xbox_state.START = (btnBits & (1 << 3) << 3); xbox_state.XBOX_BUT = (btnBits & (1 << 4) << 4); xbox_state.LS = (btnBits & (1 << 5) << 5); xbox_state.RS = (btnBits & (1 << 6) << 6); btnBits = param->input.data[XBOX_CONTROLLER_INDEX_BUTTONS_DIR]; xbox_state.UP = btnBits == 1 || btnBits == 2 || btnBits == 8; xbox_state.RIGHT = 2 <= btnBits && btnBits <= 4; xbox_state.DOWN = 4 <= btnBits && btnBits <= 6; xbox_state.LEFT = 6 <= btnBits && btnBits <= 8; xbox_state.joyLX = (uint16_t)param->input.data[0] | ((uint16_t)param->input.data[1] << 8); // 0-65535 xbox_state.joyLY = (uint16_t)param->input.data[2] | ((uint16_t)param->input.data[3] << 8); xbox_state.joyRX = (uint16_t)param->input.data[4] | ((uint16_t)param->input.data[5] << 8); xbox_state.joyRY = (uint16_t)param->input.data[6] | ((uint16_t)param->input.data[7] << 8); xbox_state.trigL = (uint16_t)param->input.data[8] | ((uint16_t)param->input.data[9] << 8); // 0-1024 xbox_state.trigR = (uint16_t)param->input.data[10] | ((uint16_t)param->input.data[11] << 8); break; } case ESP_HIDH_FEATURE_EVENT: { const uint8_t *bda = esp_hidh_dev_bda_get(param->feature.dev); ESP_LOGI("xbox_ctrl", ESP_BD_ADDR_STR " FEATURE: %8s, MAP: %2u, ID: %3u, Len: %d", ESP_BD_ADDR_HEX(bda), esp_hid_usage_str(param->feature.usage), param->feature.map_index, param->feature.report_id, param->feature.length); ESP_LOG_BUFFER_HEX("xbox_ctrl", param->feature.data, param->feature.length); break; } case ESP_HIDH_CLOSE_EVENT: { const uint8_t *bda = esp_hidh_dev_bda_get(param->close.dev); ESP_LOGI("xbox_ctrl", ESP_BD_ADDR_STR " CLOSE: %s", ESP_BD_ADDR_HEX(bda), esp_hidh_dev_name_get(param->close.dev)); break; } default: ESP_LOGI("xbox_ctrl", "EVENT: %d", event); break; } } static const char *ble_addr_type_names[] = {"PUBLIC", "RANDOM", "RPA_PUBLIC", "RPA_RANDOM"}; typedef struct esp_hidh_scan_result_s { struct esp_hidh_scan_result_s *next; esp_bd_addr_t bda; const char *name; int8_t rssi; esp_hid_usage_t usage; esp_hid_transport_t transport; //BT, BLE or USB union { struct { esp_bt_uuid_t uuid; } bt; struct { esp_ble_addr_type_t addr_type; uint16_t appearance; } ble; }; } esp_hid_scan_result_t; static esp_hid_scan_result_t *bt_scan_results = NULL; static size_t num_bt_scan_results = 0; static esp_hid_scan_result_t *ble_scan_results = NULL; static size_t num_ble_scan_results = 0; static SemaphoreHandle_t bt_hidh_cb_semaphore = NULL; #define WAIT_BT_CB() xSemaphoreTake(bt_hidh_cb_semaphore, portMAX_DELAY) #define SEND_BT_CB() xSemaphoreGive(bt_hidh_cb_semaphore) static SemaphoreHandle_t ble_hidh_cb_semaphore = NULL; #define WAIT_BLE_CB() xSemaphoreTake(ble_hidh_cb_semaphore, portMAX_DELAY) #define SEND_BLE_CB() xSemaphoreGive(ble_hidh_cb_semaphore) #define SIZEOF_ARRAY(a) (sizeof(a)/sizeof(*a)) static esp_ble_scan_params_t hid_scan_params = { .scan_type = BLE_SCAN_TYPE_ACTIVE, .own_addr_type = BLE_ADDR_TYPE_PUBLIC, .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, .scan_interval = 0x50, .scan_window = 0x30, .scan_duplicate = BLE_SCAN_DUPLICATE_ENABLE, }; static esp_err_t start_ble_scan(uint32_t seconds) { esp_err_t ret = ESP_OK; if ((ret = esp_ble_gap_set_scan_params(&hid_scan_params)) != ESP_OK) { ESP_LOGE("xbox_ctrl", "esp_ble_gap_set_scan_params failed: %d", ret); return ret; } WAIT_BLE_CB(); if ((ret = esp_ble_gap_start_scanning(seconds)) != ESP_OK) { ESP_LOGE("xbox_ctrl", "esp_ble_gap_start_scanning failed: %d", ret); return ret; } return ret; } esp_err_t esp_hid_scan(uint32_t seconds, size_t *num_results, esp_hid_scan_result_t **results) { if (num_bt_scan_results || bt_scan_results || num_ble_scan_results || ble_scan_results) { ESP_LOGE("xbox_ctrl", "There are old scan results. Free them first!"); return ESP_FAIL; } if (start_ble_scan(seconds) == ESP_OK) { WAIT_BLE_CB(); } else { return ESP_FAIL; } *num_results = num_bt_scan_results + num_ble_scan_results; *results = bt_scan_results; if (num_bt_scan_results) { while (bt_scan_results->next != NULL) { bt_scan_results = bt_scan_results->next; } bt_scan_results->next = ble_scan_results; } else { *results = ble_scan_results; } num_bt_scan_results = 0; bt_scan_results = NULL; num_ble_scan_results = 0; ble_scan_results = NULL; return ESP_OK; } const char *ble_addr_type_str(esp_ble_addr_type_t ble_addr_type) { if (ble_addr_type > BLE_ADDR_TYPE_RPA_RANDOM) { return "UNKNOWN"; } return ble_addr_type_names[ble_addr_type]; } void esp_hid_scan_results_free(esp_hid_scan_result_t *results) { esp_hid_scan_result_t *r = NULL; while (results) { r = results; results = results->next; if (r->name != NULL) { free((char *)r->name); } free(r); } } #define SCAN_DURATION_SECONDS 10 void hid_task(void *pvParameters) { size_t results_len = 0; esp_hid_scan_result_t *results = NULL; ESP_LOGI("xbox_ctrl", "SCAN..."); // start scan for HID devices esp_hid_scan(SCAN_DURATION_SECONDS, &results_len, &results); ESP_LOGI("xbox_ctrl", "SCAN: %u results", results_len); if (results_len) { esp_hid_scan_result_t *r = results; esp_hid_scan_result_t *cr = NULL; while (r) { printf(" %s: " ESP_BD_ADDR_STR ", ", (r->transport == ESP_HID_TRANSPORT_BLE) ? "BLE" : "BT ", ESP_BD_ADDR_HEX(r->bda)); printf("RSSI: %d, ", r->rssi); printf("USAGE: %s, ", esp_hid_usage_str(r->usage)); if (r->transport == ESP_HID_TRANSPORT_BLE) { if (r->ble.appearance == 0x03c4) cr = r; printf("APPEARANCE: 0x%04x, ", r->ble.appearance); printf("ADDR_TYPE: '%s', ", ble_addr_type_str(r->ble.addr_type)); } printf("NAME: %s ", r->name ? r->name : ""); printf("\n"); r = r->next; } if (cr) { // open the last result esp_hidh_dev_open(cr->bda, cr->transport, cr->ble.addr_type); } // free the results esp_hid_scan_results_free(results); } vTaskDelete(NULL); } #define GAP_DBG_PRINTF(...) printf(__VA_ARGS__) static esp_hid_scan_result_t *find_scan_result(esp_bd_addr_t bda, esp_hid_scan_result_t *results) { esp_hid_scan_result_t *r = results; while (r) { if (memcmp(bda, r->bda, sizeof(esp_bd_addr_t)) == 0) { return r; } r = r->next; } return NULL; } static void add_ble_scan_result(esp_bd_addr_t bda, esp_ble_addr_type_t addr_type, uint16_t appearance, uint8_t *name, uint8_t name_len, int rssi) { if (find_scan_result(bda, ble_scan_results)) { ESP_LOGW("xbox_controller", "Result already exists!"); return; } esp_hid_scan_result_t *r = (esp_hid_scan_result_t *)malloc(sizeof(esp_hid_scan_result_t)); if (r == NULL) { ESP_LOGE("xbox_controller", "Malloc ble_hidh_scan_result_t failed!"); return; } r->transport = ESP_HID_TRANSPORT_BLE; memcpy(r->bda, bda, sizeof(esp_bd_addr_t)); r->ble.appearance = appearance; r->ble.addr_type = addr_type; r->usage = esp_hid_usage_from_appearance(appearance); r->rssi = rssi; r->name = NULL; if (name_len && name) { char *name_s = (char *)malloc(name_len + 1); if (name_s == NULL) { free(r); ESP_LOGE("xbox_controller", "Malloc result name failed!"); return; } memcpy(name_s, name, name_len); name_s[name_len] = 0; r->name = (const char *)name_s; } r->next = ble_scan_results; ble_scan_results = r; num_ble_scan_results++; } static void handle_ble_device_result(struct ble_scan_result_evt_param *scan_rst) { uint16_t uuid = 0; uint16_t appearance = 0; char name[64] = {0}; uint8_t uuid_len = 0; uint8_t *uuid_d = esp_ble_resolve_adv_data(scan_rst->ble_adv, ESP_BLE_AD_TYPE_16SRV_CMPL, &uuid_len); if (uuid_d != NULL && uuid_len) { uuid = uuid_d[0] + (uuid_d[1] << 8); } uint8_t appearance_len = 0; uint8_t *appearance_d = esp_ble_resolve_adv_data(scan_rst->ble_adv, ESP_BLE_AD_TYPE_APPEARANCE, &appearance_len); if (appearance_d != NULL && appearance_len) { appearance = appearance_d[0] + (appearance_d[1] << 8); } uint8_t adv_name_len = 0; uint8_t *adv_name = esp_ble_resolve_adv_data(scan_rst->ble_adv, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); if (adv_name == NULL) { adv_name = esp_ble_resolve_adv_data(scan_rst->ble_adv, ESP_BLE_AD_TYPE_NAME_SHORT, &adv_name_len); } if (adv_name != NULL && adv_name_len) { memcpy(name, adv_name, adv_name_len); name[adv_name_len] = 0; } GAP_DBG_PRINTF("BLE: " ESP_BD_ADDR_STR ", ", ESP_BD_ADDR_HEX(scan_rst->bda)); GAP_DBG_PRINTF("RSSI: %d, ", scan_rst->rssi); GAP_DBG_PRINTF("UUID: 0x%04x, ", uuid); GAP_DBG_PRINTF("APPEARANCE: 0x%04x, ", appearance); GAP_DBG_PRINTF("ADDR_TYPE: '%s'", ble_addr_type_str(scan_rst->ble_addr_type)); if (adv_name_len) { GAP_DBG_PRINTF(", NAME: '%s'", name); } GAP_DBG_PRINTF("\n"); if (uuid == ESP_GATT_UUID_HID_SVC) { add_ble_scan_result(scan_rst->bda, scan_rst->ble_addr_type, appearance, adv_name, adv_name_len, scan_rst->rssi); } } static const char *ble_gap_evt_names[] = { "ADV_DATA_SET_COMPLETE", "SCAN_RSP_DATA_SET_COMPLETE", "SCAN_PARAM_SET_COMPLETE", "SCAN_RESULT", "ADV_DATA_RAW_SET_COMPLETE", "SCAN_RSP_DATA_RAW_SET_COMPLETE", "ADV_START_COMPLETE", "SCAN_START_COMPLETE", "AUTH_CMPL", "KEY", "SEC_REQ", "PASSKEY_NOTIF", "PASSKEY_REQ", "OOB_REQ", "LOCAL_IR", "LOCAL_ER", "NC_REQ", "ADV_STOP_COMPLETE", "SCAN_STOP_COMPLETE", "SET_STATIC_RAND_ADDR", "UPDATE_CONN_PARAMS", "SET_PKT_LENGTH_COMPLETE", "SET_LOCAL_PRIVACY_COMPLETE", "REMOVE_BOND_DEV_COMPLETE", "CLEAR_BOND_DEV_COMPLETE", "GET_BOND_DEV_COMPLETE", "READ_RSSI_COMPLETE", "UPDATE_WHITELIST_COMPLETE"}; const char *ble_gap_evt_str(uint8_t event) { if (event >= SIZEOF_ARRAY(ble_gap_evt_names)) { return "UNKNOWN"; } return ble_gap_evt_names[event]; } static void ble_gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { switch (event) { /* * SCAN * */ case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: { ESP_LOGV("xbox_controller", "BLE GAP EVENT SCAN_PARAM_SET_COMPLETE"); SEND_BLE_CB(); break; } 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: { handle_ble_device_result(&scan_result->scan_rst); break; } case ESP_GAP_SEARCH_INQ_CMPL_EVT: ESP_LOGV("xbox_controller", "BLE GAP EVENT SCAN DONE: %d", scan_result->scan_rst.num_resps); SEND_BLE_CB(); break; default: break; } break; } case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: { ESP_LOGV("xbox_controller", "BLE GAP EVENT SCAN CANCELED"); break; } /* * ADVERTISEMENT * */ case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: ESP_LOGV("xbox_controller", "BLE GAP ADV_DATA_SET_COMPLETE"); break; case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: ESP_LOGV("xbox_controller", "BLE GAP ADV_START_COMPLETE"); break; /* * AUTHENTICATION * */ case ESP_GAP_BLE_AUTH_CMPL_EVT: if (!param->ble_security.auth_cmpl.success) { ESP_LOGE("xbox_controller", "BLE GAP AUTH ERROR: 0x%x", param->ble_security.auth_cmpl.fail_reason); } else { ESP_LOGI("xbox_controller", "BLE GAP AUTH SUCCESS"); } break; case ESP_GAP_BLE_KEY_EVT: //shows the ble key info share with peer device to the user. ESP_LOGI("xbox_controller", "BLE GAP KEY type = %s", esp_ble_key_type_str(param->ble_security.ble_key.key_type)); break; case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: // ESP_IO_CAP_OUT // The app will receive this evt when the IO has Output capability and the peer device IO has Input capability. // Show the passkey number to the user to input it in the peer device. ESP_LOGI("xbox_controller", "BLE GAP PASSKEY_NOTIF passkey:%"PRIu32, param->ble_security.key_notif.passkey); break; case ESP_GAP_BLE_NC_REQ_EVT: // ESP_IO_CAP_IO // The app will receive this event when the IO has DisplayYesNO capability and the peer device IO also has DisplayYesNo capability. // show the passkey number to the user to confirm it with the number displayed by peer device. ESP_LOGI("xbox_controller", "BLE GAP NC_REQ passkey:%"PRIu32, param->ble_security.key_notif.passkey); esp_ble_confirm_reply(param->ble_security.key_notif.bd_addr, true); break; case ESP_GAP_BLE_PASSKEY_REQ_EVT: // ESP_IO_CAP_IN // The app will receive this evt when the IO has Input capability and the peer device IO has Output capability. // See the passkey number on the peer device and send it back. ESP_LOGI("xbox_controller", "BLE GAP PASSKEY_REQ"); //esp_ble_passkey_reply(param->ble_security.ble_req.bd_addr, true, 1234); break; case ESP_GAP_BLE_SEC_REQ_EVT: ESP_LOGI("xbox_controller", "BLE GAP SEC_REQ"); // Send the positive(true) security response to the peer device to accept the security request. // If not accept the security request, should send the security response with negative(false) accept value. esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); break; default: ESP_LOGV("xbox_controller", "BLE GAP EVENT %s", ble_gap_evt_str(event)); break; } } static esp_err_t init_ble_gap(void) { esp_err_t ret; if ((ret = esp_ble_gap_register_callback(ble_gap_event_handler)) != ESP_OK) { ESP_LOGE("xbox_controller", "esp_ble_gap_register_callback failed: %d", ret); return ret; } return ret; } static esp_err_t init_low_level(uint8_t mode) { esp_err_t ret; esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); { ret = esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); if (ret) { ESP_LOGE("xbox_controller", "esp_bt_controller_mem_release failed: %d", ret); return ret; } } ret = esp_bt_controller_init(&bt_cfg); if (ret) { ESP_LOGE("xbox_controller", "esp_bt_controller_init failed: %d", ret); return ret; } ret = esp_bt_controller_enable(mode); if (ret) { ESP_LOGE("xbox_controller", "esp_bt_controller_enable failed: %d", ret); return ret; } ret = esp_bluedroid_init(); if (ret) { ESP_LOGE("xbox_controller", "esp_bluedroid_init failed: %d", ret); return ret; } ret = esp_bluedroid_enable(); if (ret) { ESP_LOGE("xbox_controller", "esp_bluedroid_enable failed: %d", ret); return ret; } if (mode & ESP_BT_MODE_BLE) { ret = init_ble_gap(); if (ret) { return ret; } } return ret; } esp_err_t esp_hid_gap_init(uint8_t mode) { esp_err_t ret; if (!mode || mode > ESP_BT_MODE_BTDM) { ESP_LOGE("xbox_controller", "Invalid mode given!"); return ESP_FAIL; } if (bt_hidh_cb_semaphore != NULL) { ESP_LOGE("xbox_controller", "Already initialised"); return ESP_FAIL; } bt_hidh_cb_semaphore = xSemaphoreCreateBinary(); if (bt_hidh_cb_semaphore == NULL) { ESP_LOGE("xbox_controller", "xSemaphoreCreateMutex failed!"); return ESP_FAIL; } ble_hidh_cb_semaphore = xSemaphoreCreateBinary(); if (ble_hidh_cb_semaphore == NULL) { ESP_LOGE("xbox_controller", "xSemaphoreCreateMutex failed!"); vSemaphoreDelete(bt_hidh_cb_semaphore); bt_hidh_cb_semaphore = NULL; return ESP_FAIL; } ret = init_low_level(mode); if (ret != ESP_OK) { vSemaphoreDelete(bt_hidh_cb_semaphore); bt_hidh_cb_semaphore = NULL; vSemaphoreDelete(ble_hidh_cb_semaphore); ble_hidh_cb_semaphore = NULL; return ret; } return ESP_OK; } void init_controller(void) { esp_err_t ret; ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(nvs_flash_erase()); ret = nvs_flash_init(); } ESP_ERROR_CHECK(ret); ESP_ERROR_CHECK(esp_hid_gap_init(HIDH_BLE_MODE)); ble_hidh_cb_semaphore = xSemaphoreCreateBinary(); ESP_ERROR_CHECK(esp_ble_gattc_register_callback(esp_hidh_gattc_event_handler)); esp_hidh_config_t cfg = { .callback = controller_msg_callback, .event_stack_size = 4096, .callback_arg = NULL }; ESP_ERROR_CHECK(esp_hidh_init(&cfg)); xTaskCreate(&hid_task, "hid_task", 6 * 1024, NULL, 2, NULL); }