diff --git a/firmware/main/bt_classic.c b/firmware/main/bt_classic.c index f9673af..45c4b5d 100644 --- a/firmware/main/bt_classic.c +++ b/firmware/main/bt_classic.c @@ -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); } diff --git a/firmware/main/main.c b/firmware/main/main.c index 4401757..1644a05 100644 --- a/firmware/main/main.c +++ b/firmware/main/main.c @@ -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(); diff --git a/src/mcbluetooth_esp32/tools/configure.py b/src/mcbluetooth_esp32/tools/configure.py index 4e9bd30..e7480e4 100644 --- a/src/mcbluetooth_esp32/tools/configure.py +++ b/src/mcbluetooth_esp32/tools/configure.py @@ -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")}