UART-controlled ESP32 peripheral for automated E2E Bluetooth testing. Dual-mode (Classic BT + BLE) via Bluedroid on original ESP32. Firmware (ESP-IDF v5.x, 2511 lines C): - NDJSON protocol over UART1 (115200 baud) - System commands: ping, reset, get_info, get_status - Classic BT: GAP, SPP, all 4 SSP pairing modes - BLE: GATTS, advertising, GATT service/characteristic management - 6 device personas: headset, speaker, keyboard, sensor, phone, bare - Event reporter: thread-safe async event queue to host Python MCP server (FastMCP, 1626 lines): - Async serial client with command/response correlation - Event queue with wait_for pattern matching - Tools: connection, configure, classic, ble, persona, events - MCP resources: esp32://status, esp32://events, esp32://personas Tests: 74 unit tests passing, 5 integration test stubs (skip without hardware)
141 lines
4.2 KiB
C
141 lines
4.2 KiB
C
#include "cmd_dispatcher.h"
|
|
#include "protocol.h"
|
|
#include "uart_handler.h"
|
|
|
|
#include "esp_system.h"
|
|
#include "esp_chip_info.h"
|
|
#include "esp_timer.h"
|
|
#include "esp_log.h"
|
|
#include "esp_mac.h"
|
|
#include "esp_bt.h"
|
|
#include "cJSON.h"
|
|
|
|
#include <string.h>
|
|
|
|
#define FW_VERSION "0.1.0"
|
|
|
|
static const char *TAG = "dispatch";
|
|
|
|
typedef void (*cmd_handler_t)(const char *id, cJSON *params);
|
|
|
|
typedef struct {
|
|
const char *name;
|
|
cmd_handler_t handler;
|
|
} cmd_entry_t;
|
|
|
|
/* Forward declarations for handlers in other modules */
|
|
extern void bt_classic_register_commands(void);
|
|
extern void bt_ble_register_commands(void);
|
|
extern void personas_register_commands(void);
|
|
|
|
/* --- System command handlers --- */
|
|
|
|
static void handle_ping(const char *id, cJSON *params)
|
|
{
|
|
(void)params;
|
|
cJSON *data = cJSON_CreateObject();
|
|
cJSON_AddBoolToObject(data, "pong", cJSON_True);
|
|
uart_send_response(id, STATUS_OK, data);
|
|
}
|
|
|
|
static void handle_reset(const char *id, cJSON *params)
|
|
{
|
|
(void)params;
|
|
uart_send_response(id, STATUS_OK, NULL);
|
|
vTaskDelay(pdMS_TO_TICKS(100)); /* let the response flush */
|
|
esp_restart();
|
|
}
|
|
|
|
static void handle_get_info(const char *id, cJSON *params)
|
|
{
|
|
(void)params;
|
|
|
|
esp_chip_info_t chip;
|
|
esp_chip_info(&chip);
|
|
|
|
const char *model;
|
|
switch (chip.model) {
|
|
case CHIP_ESP32: model = "ESP32"; break;
|
|
case CHIP_ESP32S2: model = "ESP32-S2"; break;
|
|
case CHIP_ESP32S3: model = "ESP32-S3"; break;
|
|
case CHIP_ESP32C3: model = "ESP32-C3"; break;
|
|
case CHIP_ESP32H2: model = "ESP32-H2"; break;
|
|
default: model = "unknown"; break;
|
|
}
|
|
|
|
/* Decode feature flags */
|
|
cJSON *features = cJSON_CreateArray();
|
|
if (chip.features & CHIP_FEATURE_WIFI_BGN) cJSON_AddItemToArray(features, cJSON_CreateString("wifi"));
|
|
if (chip.features & CHIP_FEATURE_BT) cJSON_AddItemToArray(features, cJSON_CreateString("bt"));
|
|
if (chip.features & CHIP_FEATURE_BLE) cJSON_AddItemToArray(features, cJSON_CreateString("ble"));
|
|
|
|
/* BT MAC address */
|
|
uint8_t mac[6];
|
|
esp_read_mac(mac, ESP_MAC_BT);
|
|
char mac_str[18];
|
|
snprintf(mac_str, sizeof(mac_str), "%02X:%02X:%02X:%02X:%02X:%02X",
|
|
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
|
|
|
cJSON *data = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(data, "chip_model", model);
|
|
cJSON_AddItemToObject(data, "features", features);
|
|
cJSON_AddNumberToObject(data, "revision", chip.revision);
|
|
cJSON_AddNumberToObject(data, "cores", chip.cores);
|
|
cJSON_AddStringToObject(data, "fw_version", FW_VERSION);
|
|
cJSON_AddNumberToObject(data, "free_heap", (double)esp_get_free_heap_size());
|
|
cJSON_AddStringToObject(data, "bt_mac", mac_str);
|
|
|
|
uart_send_response(id, STATUS_OK, data);
|
|
}
|
|
|
|
static void handle_get_status(const char *id, cJSON *params)
|
|
{
|
|
(void)params;
|
|
|
|
int64_t uptime_ms = esp_timer_get_time() / 1000;
|
|
|
|
cJSON *data = cJSON_CreateObject();
|
|
cJSON_AddNumberToObject(data, "uptime_ms", (double)uptime_ms);
|
|
cJSON_AddNumberToObject(data, "free_heap", (double)esp_get_free_heap_size());
|
|
cJSON_AddBoolToObject(data, "bt_enabled", cJSON_False);
|
|
cJSON_AddBoolToObject(data, "ble_enabled", cJSON_False);
|
|
|
|
uart_send_response(id, STATUS_OK, data);
|
|
}
|
|
|
|
/* --- Command table --- */
|
|
|
|
static const cmd_entry_t cmd_table[] = {
|
|
{ CMD_PING, handle_ping },
|
|
{ CMD_RESET, handle_reset },
|
|
{ CMD_GET_INFO, handle_get_info },
|
|
{ CMD_GET_STATUS, handle_get_status },
|
|
{ NULL, NULL } /* sentinel */
|
|
};
|
|
|
|
void cmd_dispatcher_init(void)
|
|
{
|
|
ESP_LOGI(TAG, "command dispatcher ready");
|
|
|
|
/* Future: these will register their own handlers into the table */
|
|
/* bt_classic_register_commands(); */
|
|
/* bt_ble_register_commands(); */
|
|
/* personas_register_commands(); */
|
|
}
|
|
|
|
void cmd_dispatch(const char *id, const char *cmd, cJSON *params)
|
|
{
|
|
for (const cmd_entry_t *entry = cmd_table; entry->name != NULL; entry++) {
|
|
if (strcmp(entry->name, cmd) == 0) {
|
|
entry->handler(id, params);
|
|
return;
|
|
}
|
|
}
|
|
|
|
ESP_LOGW(TAG, "unknown command: %s", cmd);
|
|
cJSON *err = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(err, "error", "unknown_command");
|
|
cJSON_AddStringToObject(err, "cmd", cmd);
|
|
uart_send_response(id, STATUS_ERROR, err);
|
|
}
|