Fix event system init and add SSP auto-accept for E2E testing

Two fixes for the E2E test failures:

1. event_reporter_init() was never called in app_main(), so the
   FreeRTOS queue and reporter task were never created. Every BT
   event (pair_request, gatt_read, gatt_write, gatt_subscribe)
   was silently dropped at the NULL-queue guard.

2. SSP Numeric Comparison requires both sides to confirm, but
   bt_pair blocks until completion — creating a deadlock since
   the LLM can't send classic_pair_respond while waiting. Added
   auto_accept flag to set_ssp_mode that auto-confirms numeric
   comparison requests in the GAP callback.
This commit is contained in:
Ryan Malloy 2026-02-02 21:05:28 -07:00
parent 397b164eee
commit 5a853c15fc
3 changed files with 36 additions and 7 deletions

View File

@ -46,6 +46,7 @@ static struct {
esp_bt_io_cap_t io_cap;
char pin_code[17];
bool ssp_enabled;
bool auto_accept; /* Auto-confirm SSP pairing (for testing) */
/* Pending pairing state */
char pending_pair_address[18];
char pending_pair_cmd_id[32];
@ -132,10 +133,16 @@ static void gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
event_report_pair_request(addr_str, "numeric_comparison", (int)passkey);
/* Stash address so pair_respond can reply */
strncpy(classic_state.pending_pair_address, addr_str,
sizeof(classic_state.pending_pair_address) - 1);
classic_state.pair_type = PAIR_TYPE_NUMERIC_COMPARISON;
if (classic_state.auto_accept) {
/* Auto-confirm for automated E2E testing */
esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true);
ESP_LOGI(TAG, "cfm_req: auto-accepted (auto_accept=true)");
} else {
/* Stash address so pair_respond can reply */
strncpy(classic_state.pending_pair_address, addr_str,
sizeof(classic_state.pending_pair_address) - 1);
classic_state.pair_type = PAIR_TYPE_NUMERIC_COMPARISON;
}
break;
}
@ -575,11 +582,19 @@ void cmd_classic_set_ssp_mode(const char *id, cJSON *params)
esp_bt_gap_set_security_param(ESP_BT_SP_IOCAP_MODE, &iocap,
sizeof(iocap));
ESP_LOGI(TAG, "SSP mode set to '%s' (io_cap=%d)", mode, new_cap);
/* Optional auto_accept flag for automated testing */
const cJSON *j_auto = cJSON_GetObjectItem(params, "auto_accept");
if (cJSON_IsBool(j_auto)) {
classic_state.auto_accept = cJSON_IsTrue(j_auto);
}
ESP_LOGI(TAG, "SSP mode set to '%s' (io_cap=%d, auto_accept=%d)",
mode, new_cap, classic_state.auto_accept);
cJSON *data = cJSON_CreateObject();
cJSON_AddStringToObject(data, "mode", mode);
cJSON_AddNumberToObject(data, "io_cap", new_cap);
cJSON_AddBoolToObject(data, "auto_accept", classic_state.auto_accept);
uart_send_response(id, STATUS_OK, data);
}

View File

@ -1,5 +1,6 @@
#include "protocol.h"
#include "uart_handler.h"
#include "event_reporter.h"
#include "cmd_dispatcher.h"
#include "nvs_flash.h"
@ -57,6 +58,7 @@ void app_main(void)
}
uart_handler_init();
event_reporter_init();
send_boot_event();
cmd_dispatcher_init();

View File

@ -74,7 +74,10 @@ def register_tools(mcp: FastMCP) -> None:
return {"error": str(e)}
@mcp.tool()
async def esp32_set_ssp_mode(mode: str) -> dict[str, Any]:
async def esp32_set_ssp_mode(
mode: str,
auto_accept: bool = False,
) -> dict[str, Any]:
"""Set the Secure Simple Pairing (SSP) mode on the ESP32.
SSP mode determines which pairing association model the ESP32
@ -90,6 +93,11 @@ def register_tools(mcp: FastMCP) -> None:
io_cap).
"passkey_display" ESP32 displays a passkey for the
remote device to enter.
auto_accept: When true, the ESP32 automatically confirms
numeric comparison pairing requests without waiting for
a classic_pair_respond command. Useful for automated
E2E testing where both sides need to confirm but the
MCP tool calls are sequential.
Returns:
Response data confirming the new SSP mode, or an error dict
@ -99,9 +107,13 @@ def register_tools(mcp: FastMCP) -> None:
if mode not in valid_modes:
return {"error": f"Invalid SSP mode '{mode}'. Must be one of: {sorted(valid_modes)}"}
params: dict[str, Any] = {"mode": mode}
if auto_accept:
params["auto_accept"] = True
try:
client = get_client()
resp = await client.send_command(CMD_CLASSIC_SET_SSP_MODE, {"mode": mode})
resp = await client.send_command(CMD_CLASSIC_SET_SSP_MODE, params)
if resp.status == Status.OK:
return resp.data
return {"error": resp.data.get("error", "Failed to set SSP mode")}