#include "cmd_dispatcher.h" #include "protocol.h" #include "uart_handler.h" #include "bt_classic.h" #include "bt_ble.h" #include "bt_hid.h" #include "bt_hfp.h" #include "personas.h" #include "esp_system.h" #include "esp_chip_info.h" #include "esp_timer.h" #include "esp_log.h" #include "esp_mac.h" #include "esp_gap_bt_api.h" #include "cJSON.h" #include #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; /* --- 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", bt_classic_is_enabled()); cJSON_AddBoolToObject(data, "ble_enabled", bt_ble_is_enabled()); uart_send_response(id, STATUS_OK, data); } /* --- Configure command: sets device name, IO capabilities, CoD --- */ static void handle_configure(const char *id, cJSON *params) { if (!params) { cJSON *err = cJSON_CreateObject(); cJSON_AddStringToObject(err, "error", "params required"); uart_send_response(id, STATUS_ERROR, err); return; } cJSON *applied = cJSON_CreateObject(); cJSON *name = cJSON_GetObjectItem(params, "name"); if (name && cJSON_IsString(name)) { esp_bt_gap_set_device_name(name->valuestring); cJSON_AddStringToObject(applied, "name", name->valuestring); } cJSON *io_cap = cJSON_GetObjectItem(params, "io_cap"); if (io_cap && cJSON_IsString(io_cap)) { bt_classic_set_io_cap(io_cap->valuestring); cJSON_AddStringToObject(applied, "io_cap", io_cap->valuestring); } cJSON *device_class = cJSON_GetObjectItem(params, "device_class"); if (device_class && cJSON_IsNumber(device_class)) { bt_classic_set_device_class((uint32_t)device_class->valuedouble); cJSON_AddNumberToObject(applied, "device_class", device_class->valuedouble); } uart_send_response(id, STATUS_OK, applied); } /* --- Command table --- */ static const cmd_entry_t cmd_table[] = { /* System */ { CMD_PING, handle_ping }, { CMD_RESET, handle_reset }, { CMD_GET_INFO, handle_get_info }, { CMD_GET_STATUS, handle_get_status }, /* Configuration */ { CMD_CONFIGURE, handle_configure }, { CMD_LOAD_PERSONA, cmd_load_persona }, { CMD_LIST_PERSONAS, cmd_list_personas }, /* Classic BT */ { CMD_CLASSIC_ENABLE, cmd_classic_enable }, { CMD_CLASSIC_DISABLE, cmd_classic_disable }, { CMD_CLASSIC_SET_DISCOVERABLE,cmd_classic_set_discoverable }, { CMD_CLASSIC_PAIR_RESPOND, cmd_classic_pair_respond }, { CMD_CLASSIC_SET_SSP_MODE, cmd_classic_set_ssp_mode }, /* SPP */ { CMD_SPP_SEND, cmd_spp_send }, { CMD_SPP_DISCONNECT, cmd_spp_disconnect }, { CMD_SPP_STATUS, cmd_spp_status }, /* HID */ { CMD_HID_ENABLE, cmd_hid_enable }, { CMD_HID_DISABLE, cmd_hid_disable }, { CMD_HID_CONNECT, cmd_hid_connect }, { CMD_HID_DISCONNECT, cmd_hid_disconnect }, { CMD_HID_SEND_KEYBOARD, cmd_hid_send_keyboard }, { CMD_HID_SEND_MOUSE, cmd_hid_send_mouse }, { CMD_HID_STATUS, cmd_hid_status }, /* HFP */ { CMD_HFP_ENABLE, cmd_hfp_enable }, { CMD_HFP_DISABLE, cmd_hfp_disable }, { CMD_HFP_CONNECT, cmd_hfp_connect }, { CMD_HFP_DISCONNECT, cmd_hfp_disconnect }, { CMD_HFP_AUDIO_CONNECT, cmd_hfp_audio_connect }, { CMD_HFP_AUDIO_DISCONNECT, cmd_hfp_audio_disconnect }, { CMD_HFP_ANSWER, cmd_hfp_answer }, { CMD_HFP_REJECT, cmd_hfp_reject }, { CMD_HFP_DIAL, cmd_hfp_dial }, { CMD_HFP_SEND_DTMF, cmd_hfp_send_dtmf }, { CMD_HFP_VOLUME, cmd_hfp_volume }, { CMD_HFP_VOICE_RECOGNITION_START, cmd_hfp_voice_recognition_start }, { CMD_HFP_VOICE_RECOGNITION_STOP, cmd_hfp_voice_recognition_stop }, { CMD_HFP_QUERY_CALLS, cmd_hfp_query_calls }, { CMD_HFP_STATUS, cmd_hfp_status }, /* BLE */ { CMD_BLE_ENABLE, cmd_ble_enable }, { CMD_BLE_DISABLE, cmd_ble_disable }, { CMD_BLE_ADVERTISE, cmd_ble_advertise }, { CMD_BLE_SET_ADV_DATA, cmd_ble_set_adv_data }, /* GATT */ { CMD_GATT_ADD_SERVICE, cmd_gatt_add_service }, { CMD_GATT_ADD_CHARACTERISTIC, cmd_gatt_add_characteristic }, { CMD_GATT_SET_VALUE, cmd_gatt_set_value }, { CMD_GATT_NOTIFY, cmd_gatt_notify }, { CMD_GATT_CLEAR, cmd_gatt_clear }, { NULL, NULL } /* sentinel */ }; void cmd_dispatcher_init(void) { ESP_LOGI(TAG, "command dispatcher ready (%d commands)", (int)(sizeof(cmd_table) / sizeof(cmd_table[0])) - 1); } 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); }