/* * 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_ble_api.h" #include "esp_log.h" #include "cJSON.h" #include 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_dev_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); }