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)
137 lines
3.5 KiB
C
137 lines
3.5 KiB
C
#include "uart_handler.h"
|
|
#include "protocol.h"
|
|
|
|
#include "driver/uart.h"
|
|
#include "esp_log.h"
|
|
#include "esp_timer.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/semphr.h"
|
|
#include "cJSON.h"
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
static const char *TAG = "uart";
|
|
static SemaphoreHandle_t tx_mutex;
|
|
|
|
/* Pin assignments -- keep UART0 free for ESP-IDF console/logging */
|
|
#define UART_TX_PIN GPIO_NUM_4
|
|
#define UART_RX_PIN GPIO_NUM_5
|
|
|
|
void uart_handler_init(void)
|
|
{
|
|
uart_config_t cfg = {
|
|
.baud_rate = PROTO_BAUD_RATE,
|
|
.data_bits = UART_DATA_8_BITS,
|
|
.parity = UART_PARITY_DISABLE,
|
|
.stop_bits = UART_STOP_BITS_1,
|
|
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
|
.source_clk = UART_SCLK_DEFAULT,
|
|
};
|
|
|
|
ESP_ERROR_CHECK(uart_param_config(PROTO_UART_NUM, &cfg));
|
|
ESP_ERROR_CHECK(uart_set_pin(PROTO_UART_NUM, UART_TX_PIN, UART_RX_PIN,
|
|
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
|
|
ESP_ERROR_CHECK(uart_driver_install(PROTO_UART_NUM,
|
|
PROTO_RX_BUF_SIZE, PROTO_TX_BUF_SIZE,
|
|
0, NULL, 0));
|
|
|
|
tx_mutex = xSemaphoreCreateMutex();
|
|
assert(tx_mutex);
|
|
|
|
ESP_LOGI(TAG, "UART%d ready (TX=%d RX=%d @ %d baud)",
|
|
PROTO_UART_NUM, UART_TX_PIN, UART_RX_PIN, PROTO_BAUD_RATE);
|
|
}
|
|
|
|
char *uart_read_line(void)
|
|
{
|
|
static char buf[PROTO_MAX_LINE];
|
|
int pos = 0;
|
|
|
|
for (;;) {
|
|
uint8_t byte;
|
|
int n = uart_read_bytes(PROTO_UART_NUM, &byte, 1, portMAX_DELAY);
|
|
if (n <= 0) {
|
|
continue;
|
|
}
|
|
|
|
if (byte == '\n') {
|
|
if (pos == 0) {
|
|
continue; /* skip empty lines */
|
|
}
|
|
buf[pos] = '\0';
|
|
return strdup(buf);
|
|
}
|
|
|
|
if (byte == '\r') {
|
|
continue; /* ignore carriage returns */
|
|
}
|
|
|
|
if (pos < PROTO_MAX_LINE - 1) {
|
|
buf[pos++] = (char)byte;
|
|
} else {
|
|
/* line too long -- discard and reset */
|
|
ESP_LOGE(TAG, "line overflow (%d bytes), discarding", pos);
|
|
pos = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void uart_send_line(const char *json_line)
|
|
{
|
|
if (!json_line) {
|
|
return;
|
|
}
|
|
|
|
xSemaphoreTake(tx_mutex, portMAX_DELAY);
|
|
|
|
size_t len = strlen(json_line);
|
|
uart_write_bytes(PROTO_UART_NUM, json_line, len);
|
|
uart_write_bytes(PROTO_UART_NUM, "\n", 1);
|
|
|
|
xSemaphoreGive(tx_mutex);
|
|
}
|
|
|
|
void uart_send_response(const char *id, const char *status, cJSON *data)
|
|
{
|
|
cJSON *root = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(root, "type", MSG_TYPE_RESP);
|
|
cJSON_AddStringToObject(root, "id", id);
|
|
cJSON_AddStringToObject(root, "status", status);
|
|
|
|
if (data) {
|
|
cJSON_AddItemToObject(root, "data", data);
|
|
} else {
|
|
cJSON_AddObjectToObject(root, "data");
|
|
}
|
|
|
|
char *out = cJSON_PrintUnformatted(root);
|
|
uart_send_line(out);
|
|
|
|
free(out);
|
|
cJSON_Delete(root);
|
|
}
|
|
|
|
void uart_send_event(const char *event_name, cJSON *data)
|
|
{
|
|
int64_t ts_ms = esp_timer_get_time() / 1000;
|
|
|
|
cJSON *root = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(root, "type", MSG_TYPE_EVENT);
|
|
cJSON_AddStringToObject(root, "event", event_name);
|
|
|
|
if (data) {
|
|
cJSON_AddItemToObject(root, "data", data);
|
|
} else {
|
|
cJSON_AddObjectToObject(root, "data");
|
|
}
|
|
|
|
cJSON_AddNumberToObject(root, "ts", (double)ts_ms);
|
|
|
|
char *out = cJSON_PrintUnformatted(root);
|
|
uart_send_line(out);
|
|
|
|
free(out);
|
|
cJSON_Delete(root);
|
|
}
|