- Switch UART0 for protocol I/O (USB bridge), disable ESP-IDF console - Wire all 21 command handlers into dispatch table (was only 4) - Add configure command handler (name, io_cap, device_class) - Add bt_classic_is_enabled()/bt_ble_is_enabled() for live status - Fix cJSON_False misuse in get_status (type constant, not boolean) - Fix esp_bt_gap_set_cod() to use esp_bt_cod_t bitfield struct - Fix auth_cmpl.auth_mode → lk_type for ESP-IDF v5.3 - Replace deprecated esp_bt_dev_set_device_name with stack-specific API - Remove unused bytes_to_hex, obsolete kconfig symbols - Use large partition table (1.5MB) for dual-mode BT stack Verified on ESP32-D0WD-V3 rev 3.1, /dev/ttyUSB4, all commands tested.
272 lines
9.4 KiB
C
272 lines
9.4 KiB
C
/*
|
|
* personas.c -- Device persona presets for BT test harness.
|
|
*
|
|
* Each persona configures the ESP32 to emulate a particular class of
|
|
* Bluetooth device (headset, keyboard, sensor, etc.) by setting the
|
|
* device name, IO capability, Class of Device, and advertising the
|
|
* expected GATT services.
|
|
*/
|
|
|
|
#include "personas.h"
|
|
#include "protocol.h"
|
|
#include "uart_handler.h"
|
|
#include "bt_classic.h"
|
|
#include "bt_ble.h"
|
|
|
|
#include "esp_bt_device.h"
|
|
#include "esp_gap_bt_api.h"
|
|
#include "esp_gap_ble_api.h"
|
|
#include "esp_log.h"
|
|
#include "cJSON.h"
|
|
|
|
#include <string.h>
|
|
|
|
static const char *TAG = "personas";
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* BT SIG base UUID: 0000xxxx-0000-1000-8000-00805f9b34fb */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
/* Pre-built 128-bit UUID strings for standard services */
|
|
static const char UUID_BATTERY[] = "0000180f-0000-1000-8000-00805f9b34fb";
|
|
static const char UUID_DEVICE_INFO[] = "0000180a-0000-1000-8000-00805f9b34fb";
|
|
static const char UUID_HID[] = "00001812-0000-1000-8000-00805f9b34fb";
|
|
static const char UUID_ENV_SENSE[] = "0000181a-0000-1000-8000-00805f9b34fb";
|
|
static const char UUID_PHONEBOOK[] = "00001130-0000-1000-8000-00805f9b34fb";
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Per-persona service UUID lists (NULL-terminated) */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static const char *svc_headset[] = { UUID_BATTERY, UUID_DEVICE_INFO, NULL };
|
|
static const char *svc_speaker[] = { UUID_BATTERY, UUID_DEVICE_INFO, NULL };
|
|
static const char *svc_keyboard[] = { UUID_HID, UUID_BATTERY, NULL };
|
|
static const char *svc_sensor[] = { UUID_ENV_SENSE, UUID_BATTERY, NULL };
|
|
static const char *svc_phone[] = { UUID_PHONEBOOK, UUID_DEVICE_INFO, NULL };
|
|
static const char *svc_bare[] = { NULL };
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Persona table */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
typedef struct {
|
|
const char *name;
|
|
const char *device_name;
|
|
const char *io_cap;
|
|
uint32_t device_class;
|
|
bool classic_enabled;
|
|
bool ble_enabled;
|
|
const char **service_uuids;
|
|
} persona_t;
|
|
|
|
static const persona_t personas[] = {
|
|
{
|
|
.name = "headset",
|
|
.device_name = "BT Headset",
|
|
.io_cap = IO_CAP_NO_IO,
|
|
.device_class = 0x200404, /* Audio, Headset */
|
|
.classic_enabled = true,
|
|
.ble_enabled = true,
|
|
.service_uuids = svc_headset,
|
|
},
|
|
{
|
|
.name = "speaker",
|
|
.device_name = "BT Speaker",
|
|
.io_cap = IO_CAP_NO_IO,
|
|
.device_class = 0x200414, /* Audio, Loudspeaker */
|
|
.classic_enabled = true,
|
|
.ble_enabled = true,
|
|
.service_uuids = svc_speaker,
|
|
},
|
|
{
|
|
.name = "keyboard",
|
|
.device_name = "BT Keyboard",
|
|
.io_cap = IO_CAP_KEYBOARD_ONLY,
|
|
.device_class = 0x002540, /* Peripheral, Keyboard */
|
|
.classic_enabled = true,
|
|
.ble_enabled = true,
|
|
.service_uuids = svc_keyboard,
|
|
},
|
|
{
|
|
.name = "sensor",
|
|
.device_name = "Environment Sensor",
|
|
.io_cap = IO_CAP_NO_IO,
|
|
.device_class = 0, /* BLE only, no CoD */
|
|
.classic_enabled = false,
|
|
.ble_enabled = true,
|
|
.service_uuids = svc_sensor,
|
|
},
|
|
{
|
|
.name = "phone",
|
|
.device_name = "Test Phone",
|
|
.io_cap = IO_CAP_KEYBOARD_DISPLAY,
|
|
.device_class = 0x5A020C, /* Phone, Smart Phone */
|
|
.classic_enabled = true,
|
|
.ble_enabled = true,
|
|
.service_uuids = svc_phone,
|
|
},
|
|
{
|
|
.name = "bare",
|
|
.device_name = "ESP32-Test",
|
|
.io_cap = IO_CAP_DISPLAY_YESNO,
|
|
.device_class = 0x1F00, /* Uncategorized */
|
|
.classic_enabled = true,
|
|
.ble_enabled = true,
|
|
.service_uuids = svc_bare,
|
|
},
|
|
};
|
|
|
|
#define NUM_PERSONAS (sizeof(personas) / sizeof(personas[0]))
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Helpers */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static const persona_t *find_persona(const char *name)
|
|
{
|
|
for (size_t i = 0; i < NUM_PERSONAS; i++) {
|
|
if (strcmp(personas[i].name, name) == 0) {
|
|
return &personas[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Build a short-form UUID string ("0x180F") from a full 128-bit UUID.
|
|
* Caller must provide a buffer of at least 7 bytes.
|
|
*/
|
|
static void uuid128_to_short(const char *uuid128, char *out, size_t out_sz)
|
|
{
|
|
/* Full form: "0000xxxx-0000-1000-8000-00805f9b34fb" */
|
|
/* The 16-bit part sits at characters 4..7 */
|
|
if (strlen(uuid128) >= 8) {
|
|
snprintf(out, out_sz, "0x%.4s", uuid128 + 4);
|
|
} else {
|
|
snprintf(out, out_sz, "0x????");
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* cmd_load_persona */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
void cmd_load_persona(const char *id, cJSON *params)
|
|
{
|
|
const cJSON *j_name = cJSON_GetObjectItem(params, "persona");
|
|
if (!cJSON_IsString(j_name)) {
|
|
cJSON *err = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(err, "error", "missing 'persona' param");
|
|
uart_send_response(id, STATUS_ERROR, err);
|
|
return;
|
|
}
|
|
|
|
const char *req_name = j_name->valuestring;
|
|
const persona_t *p = find_persona(req_name);
|
|
if (!p) {
|
|
cJSON *err = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(err, "error", "unknown persona");
|
|
cJSON_AddStringToObject(err, "persona", req_name);
|
|
uart_send_response(id, STATUS_ERROR, err);
|
|
return;
|
|
}
|
|
|
|
ESP_LOGI(TAG, "loading persona: %s (%s)", p->name, p->device_name);
|
|
|
|
/* --- Set device name on both stacks --- */
|
|
if (p->classic_enabled) {
|
|
esp_bt_gap_set_device_name(p->device_name);
|
|
}
|
|
if (p->ble_enabled) {
|
|
esp_ble_gap_set_device_name(p->device_name);
|
|
}
|
|
|
|
/* --- IO capability (Classic BT SSP) --- */
|
|
if (p->classic_enabled) {
|
|
bt_classic_set_io_cap(p->io_cap);
|
|
}
|
|
|
|
/* --- Class of Device --- */
|
|
if (p->classic_enabled && p->device_class != 0) {
|
|
bt_classic_set_device_class(p->device_class);
|
|
}
|
|
|
|
/* --- Build response --- */
|
|
cJSON *data = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(data, "persona", p->name);
|
|
cJSON_AddStringToObject(data, "device_name", p->device_name);
|
|
cJSON_AddStringToObject(data, "io_cap", p->io_cap);
|
|
cJSON_AddBoolToObject(data, "classic", p->classic_enabled);
|
|
cJSON_AddBoolToObject(data, "ble", p->ble_enabled);
|
|
|
|
if (p->device_class != 0) {
|
|
char cod_str[12];
|
|
snprintf(cod_str, sizeof(cod_str), "0x%06X", (unsigned)p->device_class);
|
|
cJSON_AddStringToObject(data, "device_class", cod_str);
|
|
}
|
|
|
|
/*
|
|
* Include service UUIDs so the MCP client can set up GATT services
|
|
* via gatt_add_service / gatt_add_characteristic commands.
|
|
*/
|
|
cJSON *svc_arr = cJSON_CreateArray();
|
|
for (const char **u = p->service_uuids; u && *u; u++) {
|
|
cJSON_AddItemToArray(svc_arr, cJSON_CreateString(*u));
|
|
}
|
|
cJSON_AddItemToObject(data, "services", svc_arr);
|
|
|
|
uart_send_response(id, STATUS_OK, data);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* cmd_list_personas */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
void cmd_list_personas(const char *id, cJSON *params)
|
|
{
|
|
(void)params;
|
|
|
|
cJSON *arr = cJSON_CreateArray();
|
|
|
|
for (size_t i = 0; i < NUM_PERSONAS; i++) {
|
|
const persona_t *p = &personas[i];
|
|
|
|
cJSON *obj = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(obj, "name", p->name);
|
|
cJSON_AddStringToObject(obj, "device_name", p->device_name);
|
|
cJSON_AddStringToObject(obj, "io_cap", p->io_cap);
|
|
cJSON_AddBoolToObject(obj, "classic", p->classic_enabled);
|
|
cJSON_AddBoolToObject(obj, "ble", p->ble_enabled);
|
|
|
|
if (p->device_class != 0) {
|
|
char cod_str[12];
|
|
snprintf(cod_str, sizeof(cod_str), "0x%06X", (unsigned)p->device_class);
|
|
cJSON_AddStringToObject(obj, "device_class", cod_str);
|
|
}
|
|
|
|
/* Short-form service UUIDs for readability */
|
|
cJSON *svc_arr = cJSON_CreateArray();
|
|
for (const char **u = p->service_uuids; u && *u; u++) {
|
|
char short_uuid[8];
|
|
uuid128_to_short(*u, short_uuid, sizeof(short_uuid));
|
|
cJSON_AddItemToArray(svc_arr, cJSON_CreateString(short_uuid));
|
|
}
|
|
cJSON_AddItemToObject(obj, "services", svc_arr);
|
|
|
|
cJSON_AddItemToArray(arr, obj);
|
|
}
|
|
|
|
cJSON *data = cJSON_CreateObject();
|
|
cJSON_AddItemToObject(data, "personas", arr);
|
|
uart_send_response(id, STATUS_OK, data);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Init */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
void personas_init(void)
|
|
{
|
|
ESP_LOGI(TAG, "%d persona presets available", (int)NUM_PERSONAS);
|
|
}
|