Fix firmware for ESP-IDF v5.3 and hardware-verified operation

- 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.
This commit is contained in:
Ryan Malloy 2026-02-02 15:30:54 -07:00
parent 73d3d438a2
commit dc6078b296
9 changed files with 127 additions and 49 deletions

View File

@ -114,14 +114,6 @@ static int hex_to_bytes(const char *hex, uint8_t *out, int max_len)
return len; return len;
} }
static void bytes_to_hex(const uint8_t *data, int len, char *out)
{
for (int i = 0; i < len; i++) {
sprintf(out + i * 2, "%02x", data[i]);
}
out[len * 2] = '\0';
}
static uint8_t properties_from_json(cJSON *arr) static uint8_t properties_from_json(cJSON *arr)
{ {
uint8_t props = 0; uint8_t props = 0;
@ -424,6 +416,15 @@ static void gatts_event_handler(esp_gatts_cb_event_t event,
} }
} }
/* ------------------------------------------------------------------ */
/* State query */
/* ------------------------------------------------------------------ */
bool bt_ble_is_enabled(void)
{
return s_ble.enabled;
}
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* Command handlers */ /* Command handlers */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */

View File

@ -1,10 +1,14 @@
#pragma once #pragma once
#include <stdbool.h>
#include "cJSON.h" #include "cJSON.h"
/* Initialize BLE subsystem (Bluedroid GATTS) */ /* Initialize BLE subsystem (Bluedroid GATTS) */
void bt_ble_init(void); void bt_ble_init(void);
/* State query */
bool bt_ble_is_enabled(void);
/* Command handlers (called from cmd_dispatcher) */ /* Command handlers (called from cmd_dispatcher) */
void cmd_ble_enable(const char *id, cJSON *params); void cmd_ble_enable(const char *id, cJSON *params);
void cmd_ble_disable(const char *id, cJSON *params); void cmd_ble_disable(const char *id, cJSON *params);

View File

@ -105,9 +105,9 @@ static void gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
bd_addr_to_str(param->auth_cmpl.bda, addr_str); bd_addr_to_str(param->auth_cmpl.bda, addr_str);
bool ok = (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS); bool ok = (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS);
ESP_LOGI(TAG, "auth_cmpl: %s %s (mode=%d)", ESP_LOGI(TAG, "auth_cmpl: %s %s (lk_type=%d)",
addr_str, ok ? "success" : "FAIL", addr_str, ok ? "success" : "FAIL",
param->auth_cmpl.auth_mode); param->auth_cmpl.lk_type);
event_report_pair_complete(addr_str, ok); event_report_pair_complete(addr_str, ok);
@ -583,6 +583,15 @@ void cmd_classic_set_ssp_mode(const char *id, cJSON *params)
uart_send_response(id, STATUS_OK, data); uart_send_response(id, STATUS_OK, data);
} }
/* ------------------------------------------------------------------ */
/* State query */
/* ------------------------------------------------------------------ */
bool bt_classic_is_enabled(void)
{
return classic_state.enabled;
}
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* Configure helpers (called from the configure command handler) */ /* Configure helpers (called from the configure command handler) */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
@ -605,11 +614,16 @@ void bt_classic_set_io_cap(const char *io_cap_str)
} }
} }
void bt_classic_set_device_class(uint32_t cod) void bt_classic_set_device_class(uint32_t cod_raw)
{ {
esp_err_t err = esp_bt_gap_set_cod(cod, ESP_BT_SET_COD_ALL); esp_bt_cod_t cod;
memset(&cod, 0, sizeof(cod));
cod.minor = (cod_raw >> 2) & 0x3F;
cod.major = (cod_raw >> 8) & 0x1F;
cod.service = (cod_raw >> 13) & 0x7FF;
esp_err_t err = esp_bt_gap_set_cod(cod, ESP_BT_SET_COD_MAJOR_MINOR | ESP_BT_SET_COD_SERVICE_CLASS);
if (err == ESP_OK) { if (err == ESP_OK) {
ESP_LOGI(TAG, "CoD set to 0x%06" PRIx32, cod); ESP_LOGI(TAG, "CoD set to 0x%06" PRIx32, cod_raw);
} else { } else {
ESP_LOGE(TAG, "set_cod failed: %s", esp_err_to_name(err)); ESP_LOGE(TAG, "set_cod failed: %s", esp_err_to_name(err));
} }

View File

@ -3,6 +3,9 @@
*/ */
#pragma once #pragma once
#include <stdbool.h>
#include <stdint.h>
#include "cJSON.h" #include "cJSON.h"
void bt_classic_init(void); void bt_classic_init(void);
@ -14,6 +17,9 @@ void cmd_classic_set_discoverable(const char *id, cJSON *params);
void cmd_classic_pair_respond(const char *id, cJSON *params); void cmd_classic_pair_respond(const char *id, cJSON *params);
void cmd_classic_set_ssp_mode(const char *id, cJSON *params); void cmd_classic_set_ssp_mode(const char *id, cJSON *params);
/* State query */
bool bt_classic_is_enabled(void);
/* Called by configure command to set IO capabilities */ /* Called by configure command to set IO capabilities */
void bt_classic_set_io_cap(const char *io_cap_str); void bt_classic_set_io_cap(const char *io_cap_str);
void bt_classic_set_device_class(uint32_t cod); void bt_classic_set_device_class(uint32_t cod_raw);

View File

@ -1,13 +1,16 @@
#include "cmd_dispatcher.h" #include "cmd_dispatcher.h"
#include "protocol.h" #include "protocol.h"
#include "uart_handler.h" #include "uart_handler.h"
#include "bt_classic.h"
#include "bt_ble.h"
#include "personas.h"
#include "esp_system.h" #include "esp_system.h"
#include "esp_chip_info.h" #include "esp_chip_info.h"
#include "esp_timer.h" #include "esp_timer.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_mac.h" #include "esp_mac.h"
#include "esp_bt.h" #include "esp_gap_bt_api.h"
#include "cJSON.h" #include "cJSON.h"
#include <string.h> #include <string.h>
@ -23,11 +26,6 @@ typedef struct {
cmd_handler_t handler; cmd_handler_t handler;
} cmd_entry_t; } cmd_entry_t;
/* Forward declarations for handlers in other modules */
extern void bt_classic_register_commands(void);
extern void bt_ble_register_commands(void);
extern void personas_register_commands(void);
/* --- System command handlers --- */ /* --- System command handlers --- */
static void handle_ping(const char *id, cJSON *params) static void handle_ping(const char *id, cJSON *params)
@ -97,30 +95,87 @@ static void handle_get_status(const char *id, cJSON *params)
cJSON *data = cJSON_CreateObject(); cJSON *data = cJSON_CreateObject();
cJSON_AddNumberToObject(data, "uptime_ms", (double)uptime_ms); cJSON_AddNumberToObject(data, "uptime_ms", (double)uptime_ms);
cJSON_AddNumberToObject(data, "free_heap", (double)esp_get_free_heap_size()); cJSON_AddNumberToObject(data, "free_heap", (double)esp_get_free_heap_size());
cJSON_AddBoolToObject(data, "bt_enabled", cJSON_False); cJSON_AddBoolToObject(data, "bt_enabled", bt_classic_is_enabled());
cJSON_AddBoolToObject(data, "ble_enabled", cJSON_False); cJSON_AddBoolToObject(data, "ble_enabled", bt_ble_is_enabled());
uart_send_response(id, STATUS_OK, data); 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 --- */ /* --- Command table --- */
static const cmd_entry_t cmd_table[] = { static const cmd_entry_t cmd_table[] = {
/* System */
{ CMD_PING, handle_ping }, { CMD_PING, handle_ping },
{ CMD_RESET, handle_reset }, { CMD_RESET, handle_reset },
{ CMD_GET_INFO, handle_get_info }, { CMD_GET_INFO, handle_get_info },
{ CMD_GET_STATUS, handle_get_status }, { 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 },
/* 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 */ { NULL, NULL } /* sentinel */
}; };
void cmd_dispatcher_init(void) void cmd_dispatcher_init(void)
{ {
ESP_LOGI(TAG, "command dispatcher ready"); ESP_LOGI(TAG, "command dispatcher ready (%d commands)",
(int)(sizeof(cmd_table) / sizeof(cmd_table[0])) - 1);
/* Future: these will register their own handlers into the table */
/* bt_classic_register_commands(); */
/* bt_ble_register_commands(); */
/* personas_register_commands(); */
} }
void cmd_dispatch(const char *id, const char *cmd, cJSON *params) void cmd_dispatch(const char *id, const char *cmd, cJSON *params)

View File

@ -14,6 +14,7 @@
#include "bt_ble.h" #include "bt_ble.h"
#include "esp_bt_device.h" #include "esp_bt_device.h"
#include "esp_gap_bt_api.h"
#include "esp_gap_ble_api.h" #include "esp_gap_ble_api.h"
#include "esp_log.h" #include "esp_log.h"
#include "cJSON.h" #include "cJSON.h"
@ -174,7 +175,7 @@ void cmd_load_persona(const char *id, cJSON *params)
/* --- Set device name on both stacks --- */ /* --- Set device name on both stacks --- */
if (p->classic_enabled) { if (p->classic_enabled) {
esp_bt_dev_set_device_name(p->device_name); esp_bt_gap_set_device_name(p->device_name);
} }
if (p->ble_enabled) { if (p->ble_enabled) {
esp_ble_gap_set_device_name(p->device_name); esp_ble_gap_set_device_name(p->device_name);

View File

@ -3,7 +3,7 @@
#include "driver/uart.h" #include "driver/uart.h"
/* UART configuration */ /* UART configuration */
#define PROTO_UART_NUM UART_NUM_1 #define PROTO_UART_NUM UART_NUM_0
#define PROTO_BAUD_RATE 115200 #define PROTO_BAUD_RATE 115200
#define PROTO_TX_BUF_SIZE 4096 #define PROTO_TX_BUF_SIZE 4096
#define PROTO_RX_BUF_SIZE 4096 #define PROTO_RX_BUF_SIZE 4096

View File

@ -14,9 +14,11 @@
static const char *TAG = "uart"; static const char *TAG = "uart";
static SemaphoreHandle_t tx_mutex; static SemaphoreHandle_t tx_mutex;
/* Pin assignments -- keep UART0 free for ESP-IDF console/logging */ /*
#define UART_TX_PIN GPIO_NUM_4 * Use UART0 the dev board's USB bridge connects here.
#define UART_RX_PIN GPIO_NUM_5 * ESP-IDF console is disabled (CONFIG_ESP_CONSOLE_NONE=y) so we own UART0.
* Default UART0 pins (TX=GPIO1, RX=GPIO3) route through the USB bridge.
*/
void uart_handler_init(void) void uart_handler_init(void)
{ {
@ -30,8 +32,7 @@ void uart_handler_init(void)
}; };
ESP_ERROR_CHECK(uart_param_config(PROTO_UART_NUM, &cfg)); 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, /* UART0 default pins (TX=1, RX=3) — no set_pin needed */
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
ESP_ERROR_CHECK(uart_driver_install(PROTO_UART_NUM, ESP_ERROR_CHECK(uart_driver_install(PROTO_UART_NUM,
PROTO_RX_BUF_SIZE, PROTO_TX_BUF_SIZE, PROTO_RX_BUF_SIZE, PROTO_TX_BUF_SIZE,
0, NULL, 0)); 0, NULL, 0));
@ -39,8 +40,8 @@ void uart_handler_init(void)
tx_mutex = xSemaphoreCreateMutex(); tx_mutex = xSemaphoreCreateMutex();
assert(tx_mutex); assert(tx_mutex);
ESP_LOGI(TAG, "UART%d ready (TX=%d RX=%d @ %d baud)", ESP_LOGI(TAG, "UART%d ready (USB bridge @ %d baud)",
PROTO_UART_NUM, UART_TX_PIN, UART_RX_PIN, PROTO_BAUD_RATE); PROTO_UART_NUM, PROTO_BAUD_RATE);
} }
char *uart_read_line(void) char *uart_read_line(void)

View File

@ -5,7 +5,6 @@ CONFIG_BT_CLASSIC_ENABLED=y
CONFIG_BT_BLE_ENABLED=y CONFIG_BT_BLE_ENABLED=y
CONFIG_BT_A2DP_ENABLE=n CONFIG_BT_A2DP_ENABLE=n
CONFIG_BT_SPP_ENABLED=y CONFIG_BT_SPP_ENABLED=y
CONFIG_BT_SSP_ENABLED=y
# GAP & GATTS # GAP & GATTS
CONFIG_BT_GATTS_ENABLE=y CONFIG_BT_GATTS_ENABLE=y
@ -14,22 +13,19 @@ CONFIG_BT_GATTC_ENABLE=n
# Bluetooth controller # Bluetooth controller
CONFIG_BTDM_CTRL_MODE_BTDM=y CONFIG_BTDM_CTRL_MODE_BTDM=y
# UART # UART — disable ESP-IDF console so our protocol handler owns UART0
CONFIG_ESP_CONSOLE_UART_NUM=0 CONFIG_ESP_CONSOLE_NONE=y
# Stack sizes (Bluetooth needs generous stacks) # Stack sizes (Bluetooth needs generous stacks)
CONFIG_BT_BTU_TASK_STACK_SIZE=8192 CONFIG_BT_BTU_TASK_STACK_SIZE=8192
CONFIG_BTDM_CTRL_HCI_MODE_VHCI=y CONFIG_BTDM_CTRL_HCI_MODE_VHCI=y
# NVS (for bonding storage)
CONFIG_NVS_ENABLED=y
# Logging — keep it reasonable # Logging — keep it reasonable
CONFIG_LOG_DEFAULT_LEVEL_INFO=y CONFIG_LOG_DEFAULT_LEVEL_INFO=y
CONFIG_LOG_MAXIMUM_LEVEL_DEBUG=y CONFIG_LOG_MAXIMUM_LEVEL_DEBUG=y
# Partition table # Partition table — large (1.5MB app) needed for dual-mode BT stack
CONFIG_PARTITION_TABLE_SINGLE_APP=y CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y
# FreeRTOS # FreeRTOS
CONFIG_FREERTOS_HZ=1000 CONFIG_FREERTOS_HZ=1000