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)
101 lines
2.9 KiB
C
101 lines
2.9 KiB
C
#include "protocol.h"
|
|
#include "uart_handler.h"
|
|
#include "cmd_dispatcher.h"
|
|
|
|
#include "nvs_flash.h"
|
|
#include "esp_system.h"
|
|
#include "esp_chip_info.h"
|
|
#include "esp_timer.h"
|
|
#include "esp_log.h"
|
|
#include "esp_mac.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "cJSON.h"
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#define FW_VERSION "0.1.0"
|
|
|
|
static const char *TAG = "main";
|
|
|
|
static void send_boot_event(void)
|
|
{
|
|
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;
|
|
}
|
|
|
|
cJSON *data = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(data, "fw_version", FW_VERSION);
|
|
cJSON_AddStringToObject(data, "chip_model", model);
|
|
cJSON_AddNumberToObject(data, "cores", chip.cores);
|
|
cJSON_AddNumberToObject(data, "revision", chip.revision);
|
|
cJSON_AddNumberToObject(data, "free_heap", (double)esp_get_free_heap_size());
|
|
|
|
uart_send_event(EVT_BOOT, data);
|
|
}
|
|
|
|
void app_main(void)
|
|
{
|
|
/* NVS -- needed for Bluetooth bonding storage */
|
|
esp_err_t ret = nvs_flash_init();
|
|
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
|
ESP_LOGW(TAG, "NVS partition issue, erasing and re-initializing");
|
|
ESP_ERROR_CHECK(nvs_flash_erase());
|
|
ESP_ERROR_CHECK(nvs_flash_init());
|
|
} else {
|
|
ESP_ERROR_CHECK(ret);
|
|
}
|
|
|
|
uart_handler_init();
|
|
send_boot_event();
|
|
cmd_dispatcher_init();
|
|
|
|
ESP_LOGI(TAG, "mcbluetooth-esp32 v%s ready", FW_VERSION);
|
|
|
|
/* Main command loop */
|
|
for (;;) {
|
|
char *line = uart_read_line();
|
|
if (!line) {
|
|
continue;
|
|
}
|
|
|
|
cJSON *root = cJSON_Parse(line);
|
|
if (!root) {
|
|
ESP_LOGE(TAG, "bad JSON: %.64s", line);
|
|
/* best-effort error -- no id available */
|
|
uart_send_response("?", STATUS_ERROR,
|
|
cJSON_CreateString("invalid JSON"));
|
|
free(line);
|
|
continue;
|
|
}
|
|
|
|
const cJSON *j_id = cJSON_GetObjectItem(root, "id");
|
|
const cJSON *j_cmd = cJSON_GetObjectItem(root, "cmd");
|
|
cJSON *j_params = cJSON_GetObjectItem(root, "params");
|
|
|
|
const char *id = cJSON_IsString(j_id) ? j_id->valuestring : "?";
|
|
const char *cmd = cJSON_IsString(j_cmd) ? j_cmd->valuestring : NULL;
|
|
|
|
if (!cmd) {
|
|
ESP_LOGE(TAG, "missing 'cmd' field");
|
|
uart_send_response(id, STATUS_ERROR,
|
|
cJSON_CreateString("missing 'cmd' field"));
|
|
} else {
|
|
cmd_dispatch(id, cmd, j_params);
|
|
}
|
|
|
|
cJSON_Delete(root);
|
|
free(line);
|
|
}
|
|
}
|