603 lines
22 KiB
C
603 lines
22 KiB
C
/*
|
||
* bt_hid.c
|
||
*
|
||
* BLE HID host for SC-F001.
|
||
*
|
||
* Architecture
|
||
* ------------
|
||
* bt_hid_scan_task (priority 4, 6 KB)
|
||
* - On boot: checks NVS for a saved BDA. If found, attempts
|
||
* esp_hidh_dev_open() directly and waits BT_HID_BOND_TIMEOUT_MS.
|
||
* If that times out (device not nearby) falls through to scan.
|
||
* - While disconnected: runs a BLE scan for BT_HID_SCAN_DURATION_S
|
||
* seconds, picks the HID device with best RSSI, opens it.
|
||
* - While connected: sleeps 500 ms between checks.
|
||
* - Every BT_HID_REPEAT_MS the held-button state is sampled and
|
||
* pulse_override() is called if a mapped key is down.
|
||
*
|
||
* hidh_callback (runs in esp_hidh event task)
|
||
* - OPEN: saves BDA to NVS, marks connected.
|
||
* - CLOSE: clears connected flag, clears held key.
|
||
* - INPUT: decodes usage code, sets s_held_key.
|
||
*
|
||
* ble_gap_event_handler (runs in BT stack task)
|
||
* - Collects HID-UUID scan results.
|
||
* - Signals scan_semaphore when scan params set / scan complete.
|
||
* - Handles pairing/auth confirmations.
|
||
*/
|
||
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <inttypes.h>
|
||
|
||
#include "freertos/FreeRTOS.h"
|
||
#include "freertos/task.h"
|
||
#include "freertos/semphr.h"
|
||
|
||
#include "esp_system.h"
|
||
#include "esp_log.h"
|
||
#include "esp_event.h"
|
||
#include "nvs_flash.h"
|
||
#include "nvs.h"
|
||
|
||
#include "esp_bt.h"
|
||
#include "esp_bt_device.h"
|
||
#include "esp_bt_main.h"
|
||
#include "esp_bt_defs.h"
|
||
#include "esp_gap_ble_api.h"
|
||
#include "esp_gattc_api.h"
|
||
#include "esp_gatt_defs.h"
|
||
#include "esp_hidh.h"
|
||
#include "esp_hid_common.h"
|
||
#include "esp_timer.h"
|
||
|
||
#include "bt_hid.h"
|
||
#include "control_fsm.h"
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Constants
|
||
// ---------------------------------------------------------------------------
|
||
|
||
#define TAG "BT_HID"
|
||
#define BT_HID_SCAN_DURATION_S 3
|
||
#define BT_HID_MIN_RSSI (-70) /* dBm — ignore devices weaker than this */
|
||
#define BT_HID_RECONNECT_MS 2000
|
||
#define BT_HID_BOND_TIMEOUT_MS 5000 /* wait for saved device before scanning */
|
||
#define BT_HID_CONNECT_WAIT_MS 5000 /* wait after open() before next loop */
|
||
#define BT_HID_NVS_NAMESPACE "bt_hid"
|
||
#define BT_HID_NVS_BDA_KEY "bda"
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// HID usage codes -> machine actions
|
||
// ---------------------------------------------------------------------------
|
||
|
||
/*
|
||
* Usage codes sent by generic BLE media remotes (HID Consumer Control page).
|
||
* Only the four we care about are mapped; everything else is ignored.
|
||
*/
|
||
#define USAGE_VOL_UP 0x00E9u /* -> jack up */
|
||
#define USAGE_VOL_DOWN 0x00EAu /* -> jack down */
|
||
#define USAGE_PREV 0x00B6u /* -> reverse */
|
||
#define USAGE_NEXT 0x00B5u /* -> forward */
|
||
#define USAGE_NONE 0x0000u
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Shared remote state
|
||
// ---------------------------------------------------------------------------
|
||
|
||
typedef enum {
|
||
REMOTE_DISCONNECTED = 0,
|
||
REMOTE_CONNECTED,
|
||
} remote_conn_state_t;
|
||
|
||
typedef struct {
|
||
remote_conn_state_t conn_state;
|
||
uint16_t held_key; /* current usage code, 0 = nothing held */
|
||
esp_bd_addr_t bda; /* address of the connected device */
|
||
} remote_state_t;
|
||
|
||
static remote_state_t s_remote = { .conn_state = REMOTE_DISCONNECTED };
|
||
static portMUX_TYPE s_mux = portMUX_INITIALIZER_UNLOCKED;
|
||
|
||
/* Semaphore signalled by GAP callback to unblock the scan task. */
|
||
static SemaphoreHandle_t s_scan_sem = NULL;
|
||
|
||
/* Set once we have a saved BDA from NVS (direct reconnect path). */
|
||
static bool s_has_saved_bda = false;
|
||
static esp_bd_addr_t s_saved_bda = {0};
|
||
static esp_ble_addr_type_t s_saved_addr_type = BLE_ADDR_TYPE_RANDOM;
|
||
|
||
/* Address type used for the most recent esp_hidh_dev_open() call.
|
||
* Set immediately before open so the OPEN callback can persist it. */
|
||
static esp_ble_addr_type_t s_connect_addr_type = BLE_ADDR_TYPE_RANDOM;
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Scan-result list (heap-allocated, freed after each scan round)
|
||
// ---------------------------------------------------------------------------
|
||
|
||
typedef struct scan_result_s {
|
||
esp_bd_addr_t bda;
|
||
esp_ble_addr_type_t addr_type;
|
||
int rssi;
|
||
char name[64];
|
||
struct scan_result_s *next;
|
||
} scan_result_t;
|
||
|
||
static scan_result_t *s_scan_results = NULL;
|
||
static size_t s_num_scan_results = 0;
|
||
|
||
static TaskHandle_t s_scan_task_handle = NULL;
|
||
|
||
static scan_result_t *find_scan_result(const esp_bd_addr_t bda)
|
||
{
|
||
scan_result_t *r = s_scan_results;
|
||
while (r) {
|
||
if (memcmp(bda, r->bda, sizeof(esp_bd_addr_t)) == 0) return r;
|
||
r = r->next;
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
static void add_scan_result(const esp_bd_addr_t bda,
|
||
esp_ble_addr_type_t addr_type,
|
||
const char *name,
|
||
int rssi)
|
||
{
|
||
if (find_scan_result(bda)) return; /* deduplicate */
|
||
scan_result_t *r = (scan_result_t *)calloc(1, sizeof(scan_result_t));
|
||
if (!r) return;
|
||
memcpy(r->bda, bda, sizeof(esp_bd_addr_t));
|
||
r->addr_type = addr_type;
|
||
r->rssi = rssi;
|
||
if (name) strncpy(r->name, name, sizeof(r->name) - 1);
|
||
r->next = s_scan_results;
|
||
s_scan_results = r;
|
||
s_num_scan_results++;
|
||
}
|
||
|
||
static void free_scan_results(void)
|
||
{
|
||
scan_result_t *r;
|
||
while (s_scan_results) {
|
||
r = s_scan_results;
|
||
s_scan_results = s_scan_results->next;
|
||
free(r);
|
||
}
|
||
s_num_scan_results = 0;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// NVS helpers — store / load the last-connected BDA
|
||
// ---------------------------------------------------------------------------
|
||
|
||
static void nvs_save_bda(const esp_bd_addr_t bda, esp_ble_addr_type_t addr_type)
|
||
{
|
||
nvs_handle_t h;
|
||
if (nvs_open(BT_HID_NVS_NAMESPACE, NVS_READWRITE, &h) != ESP_OK) return;
|
||
nvs_set_blob(h, BT_HID_NVS_BDA_KEY, bda, sizeof(esp_bd_addr_t));
|
||
nvs_set_u8(h, "addr_type", (uint8_t)addr_type);
|
||
nvs_commit(h);
|
||
nvs_close(h);
|
||
ESP_LOGI(TAG, "Saved BDA %02x:%02x:%02x:%02x:%02x:%02x (addr_type=%d) to NVS",
|
||
bda[0], bda[1], bda[2], bda[3], bda[4], bda[5], (int)addr_type);
|
||
}
|
||
|
||
static bool nvs_load_bda(esp_bd_addr_t out_bda, esp_ble_addr_type_t *out_addr_type)
|
||
{
|
||
nvs_handle_t h;
|
||
if (nvs_open(BT_HID_NVS_NAMESPACE, NVS_READONLY, &h) != ESP_OK) return false;
|
||
size_t len = sizeof(esp_bd_addr_t);
|
||
bool ok = (nvs_get_blob(h, BT_HID_NVS_BDA_KEY, out_bda, &len) == ESP_OK);
|
||
if (ok) {
|
||
uint8_t at = (uint8_t)BLE_ADDR_TYPE_RANDOM;
|
||
nvs_get_u8(h, "addr_type", &at); /* missing key → stays RANDOM, safe default */
|
||
*out_addr_type = (esp_ble_addr_type_t)at;
|
||
}
|
||
nvs_close(h);
|
||
return ok;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Map a HID usage code to an FSM override command
|
||
// Returns false if the usage is not mapped.
|
||
// ---------------------------------------------------------------------------
|
||
|
||
static bool usage_to_override(uint16_t usage, fsm_override_t *out)
|
||
{
|
||
switch (usage) {
|
||
case USAGE_VOL_UP: *out = FSM_OVERRIDE_JACK_UP; return true;
|
||
case USAGE_VOL_DOWN: *out = FSM_OVERRIDE_JACK_DOWN; return true;
|
||
case USAGE_PREV: *out = FSM_OVERRIDE_DRIVE_REV; return true;
|
||
case USAGE_NEXT: *out = FSM_OVERRIDE_DRIVE_FWD; return true;
|
||
default: return false;
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// HID Host event callback
|
||
// Runs in the esp_hidh internal event task — keep it short, no blocking.
|
||
// ---------------------------------------------------------------------------
|
||
|
||
static void hidh_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 *p = (esp_hidh_event_data_t *)event_data;
|
||
|
||
switch (event) {
|
||
|
||
case ESP_HIDH_OPEN_EVENT: {
|
||
if (p->open.status == ESP_OK) {
|
||
const uint8_t *bda = esp_hidh_dev_bda_get(p->open.dev);
|
||
ESP_LOGI(TAG, "CONNECTED: %02x:%02x:%02x:%02x:%02x:%02x '%s'",
|
||
bda[0], bda[1], bda[2], bda[3], bda[4], bda[5],
|
||
esp_hidh_dev_name_get(p->open.dev));
|
||
esp_hidh_dev_dump(p->open.dev, stdout);
|
||
|
||
taskENTER_CRITICAL(&s_mux);
|
||
s_remote.conn_state = REMOTE_CONNECTED;
|
||
s_remote.held_key = USAGE_NONE;
|
||
memcpy(s_remote.bda, bda, sizeof(esp_bd_addr_t));
|
||
taskEXIT_CRITICAL(&s_mux);
|
||
|
||
nvs_save_bda(bda, s_connect_addr_type);
|
||
} else {
|
||
ESP_LOGE(TAG, "OPEN failed, status=%d", p->open.status);
|
||
/* Free the failed device handle — not doing so leaks a BT
|
||
* resource and can block future connection attempts. */
|
||
esp_hidh_dev_free(p->open.dev);
|
||
}
|
||
break;
|
||
}
|
||
|
||
case ESP_HIDH_CLOSE_EVENT: {
|
||
taskENTER_CRITICAL(&s_mux);
|
||
s_remote.conn_state = REMOTE_DISCONNECTED;
|
||
s_remote.held_key = USAGE_NONE;
|
||
taskEXIT_CRITICAL(&s_mux);
|
||
ESP_LOGW(TAG, "DISCONNECTED — will rescan");
|
||
break;
|
||
}
|
||
|
||
case ESP_HIDH_BATTERY_EVENT:
|
||
ESP_LOGI(TAG, "Battery: %d%%", p->battery.level);
|
||
break;
|
||
|
||
case ESP_HIDH_INPUT_EVENT: {
|
||
/*
|
||
* Decode usage code from the first two bytes of the input report.
|
||
* BLE consumer-control remotes typically send a 16-bit usage on
|
||
* report ID 3 (or similar), zero on key-up.
|
||
*/
|
||
uint16_t usage = USAGE_NONE;
|
||
if (p->input.length >= 2) {
|
||
usage = (uint16_t)(p->input.data[0] | ((uint16_t)p->input.data[1] << 8));
|
||
} else if (p->input.length == 1) {
|
||
usage = p->input.data[0];
|
||
}
|
||
|
||
taskENTER_CRITICAL(&s_mux);
|
||
s_remote.held_key = usage;
|
||
taskEXIT_CRITICAL(&s_mux);
|
||
|
||
/* Log mapped presses only (not key-up noise). */
|
||
if (usage != USAGE_NONE) {
|
||
fsm_override_t dummy;
|
||
if (usage_to_override(usage, &dummy)) {
|
||
ESP_LOGI(TAG, "KEY DOWN 0x%04X", usage);
|
||
} else {
|
||
ESP_LOGD(TAG, "KEY 0x%04X (unmapped)", usage);
|
||
}
|
||
} else {
|
||
ESP_LOGD(TAG, "KEY UP");
|
||
}
|
||
break;
|
||
}
|
||
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// BLE GAP event handler
|
||
// ---------------------------------------------------------------------------
|
||
|
||
static void ble_gap_event_handler(esp_gap_ble_cb_event_t event,
|
||
esp_ble_gap_cb_param_t *param)
|
||
{
|
||
switch (event) {
|
||
|
||
/* Scan parameters accepted — safe to start scanning now. */
|
||
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
|
||
xSemaphoreGive(s_scan_sem);
|
||
break;
|
||
|
||
case ESP_GAP_BLE_SCAN_RESULT_EVT: {
|
||
switch (param->scan_rst.search_evt) {
|
||
|
||
case ESP_GAP_SEARCH_INQ_RES_EVT: {
|
||
uint8_t *adv = param->scan_rst.ble_adv;
|
||
|
||
/* Only keep devices that advertise the HID service UUID (0x1812). */
|
||
uint8_t uuid_len = 0;
|
||
uint8_t *uuid_d = esp_ble_resolve_adv_data(
|
||
adv, ESP_BLE_AD_TYPE_16SRV_CMPL, &uuid_len);
|
||
uint16_t uuid = 0;
|
||
if (uuid_d && uuid_len >= 2) {
|
||
uuid = (uint16_t)(uuid_d[0] | ((uint16_t)uuid_d[1] << 8));
|
||
}
|
||
if (uuid != ESP_GATT_UUID_HID_SVC) break;
|
||
|
||
/* Parse device name (prefer complete, fall back to short). */
|
||
uint8_t name_len = 0;
|
||
uint8_t *name_d = esp_ble_resolve_adv_data(
|
||
adv, ESP_BLE_AD_TYPE_NAME_CMPL, &name_len);
|
||
if (!name_d || !name_len) {
|
||
name_d = esp_ble_resolve_adv_data(
|
||
adv, ESP_BLE_AD_TYPE_NAME_SHORT, &name_len);
|
||
}
|
||
char name[64] = {0};
|
||
if (name_d && name_len > 0) {
|
||
size_t copy = name_len < (sizeof(name) - 1) ? name_len : (sizeof(name) - 1);
|
||
memcpy(name, name_d, copy);
|
||
}
|
||
|
||
if (param->scan_rst.rssi < BT_HID_MIN_RSSI) {
|
||
ESP_LOGD(TAG, "SCAN %02x:%02x:%02x:%02x:%02x:%02x RSSI:%d '%s' (too weak, skipping)",
|
||
param->scan_rst.bda[0], param->scan_rst.bda[1],
|
||
param->scan_rst.bda[2], param->scan_rst.bda[3],
|
||
param->scan_rst.bda[4], param->scan_rst.bda[5],
|
||
param->scan_rst.rssi, name);
|
||
break;
|
||
}
|
||
|
||
ESP_LOGI(TAG, "SCAN %02x:%02x:%02x:%02x:%02x:%02x RSSI:%d '%s' <<< HID",
|
||
param->scan_rst.bda[0], param->scan_rst.bda[1],
|
||
param->scan_rst.bda[2], param->scan_rst.bda[3],
|
||
param->scan_rst.bda[4], param->scan_rst.bda[5],
|
||
param->scan_rst.rssi, name);
|
||
|
||
add_scan_result(param->scan_rst.bda,
|
||
param->scan_rst.ble_addr_type,
|
||
name,
|
||
param->scan_rst.rssi);
|
||
break;
|
||
}
|
||
|
||
/* Scan complete — unblock the scan task. */
|
||
case ESP_GAP_SEARCH_INQ_CMPL_EVT:
|
||
xSemaphoreGive(s_scan_sem);
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
break;
|
||
}
|
||
|
||
/* Pairing / security events — auto-accept so bonding completes. */
|
||
case ESP_GAP_BLE_AUTH_CMPL_EVT:
|
||
if (!param->ble_security.auth_cmpl.success) {
|
||
ESP_LOGE(TAG, "AUTH ERROR 0x%x", param->ble_security.auth_cmpl.fail_reason);
|
||
} else {
|
||
ESP_LOGI(TAG, "AUTH SUCCESS");
|
||
}
|
||
break;
|
||
|
||
case ESP_GAP_BLE_SEC_REQ_EVT:
|
||
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
|
||
break;
|
||
|
||
case ESP_GAP_BLE_NC_REQ_EVT:
|
||
esp_ble_confirm_reply(param->ble_security.key_notif.bd_addr, true);
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Scan + reconnect task
|
||
// ---------------------------------------------------------------------------
|
||
|
||
static void bt_hid_scan_task(void *pvParameters)
|
||
{
|
||
esp_ble_scan_params_t 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,
|
||
};
|
||
|
||
TickType_t repeat_tick = xTaskGetTickCount();
|
||
|
||
/* ------------------------------------------------------------------
|
||
* On the very first iteration, try to reconnect to the saved BDA.
|
||
* ------------------------------------------------------------------ */
|
||
if (s_has_saved_bda) {
|
||
ESP_LOGI(TAG, "Trying saved device %02x:%02x:%02x:%02x:%02x:%02x ...",
|
||
s_saved_bda[0], s_saved_bda[1], s_saved_bda[2],
|
||
s_saved_bda[3], s_saved_bda[4], s_saved_bda[5]);
|
||
|
||
/*
|
||
* We don't know the address type of the saved device at this point.
|
||
* BLE_ADDR_TYPE_RANDOM is the most common for consumer remotes; if it
|
||
* fails the outer scan loop will find it correctly.
|
||
*/
|
||
s_connect_addr_type = s_saved_addr_type;
|
||
esp_hidh_dev_open(s_saved_bda, ESP_HID_TRANSPORT_BLE, s_saved_addr_type);
|
||
vTaskDelay(pdMS_TO_TICKS(BT_HID_BOND_TIMEOUT_MS));
|
||
|
||
/* If the open succeeded the state will be CONNECTED — skip to main loop. */
|
||
}
|
||
|
||
/* ------------------------------------------------------------------
|
||
* Main loop: scan → connect → wait → repeat if disconnected.
|
||
* ------------------------------------------------------------------ */
|
||
while (1) {
|
||
/* ---- While connected: check held key and call pulse_override() ---- */
|
||
taskENTER_CRITICAL(&s_mux);
|
||
remote_conn_state_t conn = s_remote.conn_state;
|
||
uint16_t held_key = s_remote.held_key;
|
||
taskEXIT_CRITICAL(&s_mux);
|
||
|
||
if (conn == REMOTE_CONNECTED) {
|
||
/* Fire pulse_override every BT_HID_REPEAT_MS while a key is held. */
|
||
if (xTaskGetTickCount() - repeat_tick >= pdMS_TO_TICKS(BT_HID_REPEAT_MS)) {
|
||
repeat_tick = xTaskGetTickCount();
|
||
|
||
fsm_override_t override_cmd;
|
||
if (held_key != USAGE_NONE && usage_to_override(held_key, &override_cmd)) {
|
||
pulse_override(override_cmd);
|
||
}
|
||
}
|
||
|
||
vTaskDelay(pdMS_TO_TICKS(10)); /* Short sleep to yield; repeat timer governs rate. */
|
||
continue;
|
||
}
|
||
|
||
/* ---- Not connected — run a scan ---- */
|
||
ESP_LOGI(TAG, "Scanning for HID devices (%ds)...", BT_HID_SCAN_DURATION_S);
|
||
|
||
free_scan_results();
|
||
esp_ble_gap_set_scan_params(&scan_params);
|
||
xSemaphoreTake(s_scan_sem, portMAX_DELAY); /* wait: SCAN_PARAM_SET_COMPLETE */
|
||
esp_ble_gap_start_scanning(BT_HID_SCAN_DURATION_S);
|
||
xSemaphoreTake(s_scan_sem, portMAX_DELAY); /* wait: SCAN_INQ_CMPL */
|
||
|
||
ESP_LOGI(TAG, "Found %u HID device(s)", (unsigned)s_num_scan_results);
|
||
|
||
if (s_num_scan_results > 0) {
|
||
/* Pick the device with the strongest signal. */
|
||
scan_result_t *r = s_scan_results;
|
||
scan_result_t *best = r;
|
||
while (r) {
|
||
if (r->rssi > best->rssi) best = r;
|
||
r = r->next;
|
||
}
|
||
|
||
ESP_LOGI(TAG, "Connecting to %02x:%02x:%02x:%02x:%02x:%02x '%s' (RSSI:%d)",
|
||
best->bda[0], best->bda[1], best->bda[2],
|
||
best->bda[3], best->bda[4], best->bda[5],
|
||
best->name, best->rssi);
|
||
|
||
s_connect_addr_type = best->addr_type;
|
||
esp_hidh_dev_open(best->bda, ESP_HID_TRANSPORT_BLE, best->addr_type);
|
||
free_scan_results();
|
||
|
||
/* Give the connection time to establish before looping. */
|
||
vTaskDelay(pdMS_TO_TICKS(BT_HID_CONNECT_WAIT_MS));
|
||
} else {
|
||
ESP_LOGI(TAG, "No HID devices found, retrying in %dms...", BT_HID_RECONNECT_MS);
|
||
vTaskDelay(pdMS_TO_TICKS(BT_HID_RECONNECT_MS));
|
||
}
|
||
|
||
repeat_tick = xTaskGetTickCount(); /* reset repeat timer after reconnect */
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Public init
|
||
// ---------------------------------------------------------------------------
|
||
|
||
esp_err_t bt_hid_init(void)
|
||
{
|
||
esp_err_t ret;
|
||
|
||
/* Release classic BT memory — we only use BLE. */
|
||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
|
||
|
||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||
ret = esp_bt_controller_init(&bt_cfg);
|
||
if (ret != ESP_OK) {
|
||
ESP_LOGE(TAG, "bt_controller_init failed: %s", esp_err_to_name(ret));
|
||
return ret;
|
||
}
|
||
|
||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||
if (ret != ESP_OK) {
|
||
ESP_LOGE(TAG, "bt_controller_enable failed: %s", esp_err_to_name(ret));
|
||
return ret;
|
||
}
|
||
|
||
esp_bluedroid_config_t bd_cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
|
||
ret = esp_bluedroid_init_with_cfg(&bd_cfg);
|
||
if (ret != ESP_OK) {
|
||
ESP_LOGE(TAG, "bluedroid_init failed: %s", esp_err_to_name(ret));
|
||
return ret;
|
||
}
|
||
|
||
ret = esp_bluedroid_enable();
|
||
if (ret != ESP_OK) {
|
||
ESP_LOGE(TAG, "bluedroid_enable failed: %s", esp_err_to_name(ret));
|
||
return ret;
|
||
}
|
||
|
||
/* esp_hidh posts events to the default event loop — create it if the
|
||
* webserver hasn't done so yet. ESP_ERR_INVALID_STATE means it already
|
||
* exists, which is fine. */
|
||
ret = esp_event_loop_create_default();
|
||
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
|
||
ESP_LOGE(TAG, "event_loop_create failed: %s", esp_err_to_name(ret));
|
||
return ret;
|
||
}
|
||
|
||
ESP_ERROR_CHECK(esp_ble_gap_register_callback(ble_gap_event_handler));
|
||
ESP_ERROR_CHECK(esp_ble_gattc_register_callback(esp_hidh_gattc_event_handler));
|
||
|
||
esp_hidh_config_t hidh_cfg = {
|
||
.callback = hidh_callback,
|
||
.event_stack_size = 4096,
|
||
.callback_arg = NULL,
|
||
};
|
||
ret = esp_hidh_init(&hidh_cfg);
|
||
if (ret != ESP_OK) {
|
||
ESP_LOGE(TAG, "hidh_init failed: %s", esp_err_to_name(ret));
|
||
return ret;
|
||
}
|
||
|
||
/* Try to load a previously bonded device address from NVS. */
|
||
s_has_saved_bda = nvs_load_bda(s_saved_bda, &s_saved_addr_type);
|
||
if (s_has_saved_bda) {
|
||
ESP_LOGI(TAG, "Found saved BDA in NVS — will try direct reconnect first");
|
||
}
|
||
|
||
s_scan_sem = xSemaphoreCreateBinary();
|
||
if (!s_scan_sem) {
|
||
ESP_LOGE(TAG, "Failed to create scan semaphore");
|
||
return ESP_ERR_NO_MEM;
|
||
}
|
||
|
||
/*
|
||
* Priority 4: above webserver (typically 5–6 on core 0) is fine; below
|
||
* the FSM (10) and RF task (5) so control always wins CPU.
|
||
*/
|
||
xTaskCreate(bt_hid_scan_task, "bt_hid_scan", 6 * 1024, NULL, 4, &s_scan_task_handle);
|
||
|
||
ESP_LOGI(TAG, "BLE HID host initialised");
|
||
return ESP_OK;
|
||
}
|
||
|
||
void bt_hid_stop(void)
|
||
{
|
||
if (s_scan_task_handle != NULL) {
|
||
vTaskSuspend(s_scan_task_handle);
|
||
ESP_LOGI(TAG, "BT HID scan task suspended");
|
||
}
|
||
}
|
||
|
||
void bt_hid_resume(void)
|
||
{
|
||
if (s_scan_task_handle != NULL) {
|
||
vTaskResume(s_scan_task_handle);
|
||
ESP_LOGI(TAG, "BT HID scan task resumed");
|
||
}
|
||
}
|