From ab89f5d3631d4adf1c5a5ddc580106da8ea67207 Mon Sep 17 00:00:00 2001 From: jerryyip Date: Wed, 4 Mar 2026 09:30:06 +0800 Subject: [PATCH 1/9] feat: add i2s voice channel with qwen stt/tts streaming(for ReSpeaker XVF3800 with XIAO) --- main/CMakeLists.txt | 3 +- main/bus/message_bus.h | 1 + main/cli/serial_cli.c | 59 +++ main/mimi.c | 24 +- main/mimi_config.h | 61 +++ main/mimi_secrets.h.example | 17 + main/voice/voice_channel.c | 870 ++++++++++++++++++++++++++++++++++++ main/voice/voice_channel.h | 32 ++ 8 files changed, 1058 insertions(+), 9 deletions(-) create mode 100644 main/voice/voice_channel.c create mode 100644 main/voice/voice_channel.h diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 94875bc4..dab357ad 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -20,10 +20,11 @@ idf_component_register( "tools/tool_get_time.c" "tools/tool_files.c" "skills/skill_loader.c" + "voice/voice_channel.c" INCLUDE_DIRS "." REQUIRES nvs_flash esp_wifi esp_netif esp_http_client esp_http_server esp_https_ota esp_event json spiffs console vfs app_update esp-tls - esp_timer + esp_timer driver ) diff --git a/main/bus/message_bus.h b/main/bus/message_bus.h index 78d40f49..4f7ff9ad 100644 --- a/main/bus/message_bus.h +++ b/main/bus/message_bus.h @@ -9,6 +9,7 @@ #define MIMI_CHAN_WEBSOCKET "websocket" #define MIMI_CHAN_CLI "cli" #define MIMI_CHAN_SYSTEM "system" +#define MIMI_CHAN_VOICE "voice" /* Message types on the bus */ typedef struct { diff --git a/main/cli/serial_cli.c b/main/cli/serial_cli.c index 4ac76a9a..717f62c1 100644 --- a/main/cli/serial_cli.c +++ b/main/cli/serial_cli.c @@ -11,6 +11,7 @@ #include "cron/cron_service.h" #include "heartbeat/heartbeat.h" #include "skills/skill_loader.h" +#include "voice/voice_channel.h" #include #include @@ -202,6 +203,45 @@ static int cmd_heap_info(int argc, char **argv) return 0; } +/* --- voice_status command --- */ +static int cmd_voice_status(int argc, char **argv) +{ + voice_channel_status_t st = {0}; + voice_channel_get_status(&st); + + printf("Voice enabled: %s\n", st.enabled ? "yes" : "no"); + printf("I2S ready: %s\n", st.i2s_ready ? "yes" : "no"); + printf("Playing now: %s\n", st.is_playing ? "yes" : "no"); + printf("STT configured: %s\n", st.stt_configured ? "yes" : "no"); + printf("TTS configured: %s\n", st.tts_configured ? "yes" : "no"); + printf("Voice chat_id: %s\n", MIMI_VOICE_CHAT_ID); + return 0; +} + +/* --- voice_say command --- */ +static struct { + struct arg_str *text; + struct arg_end *end; +} voice_say_args; + +static int cmd_voice_say(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **)&voice_say_args); + if (nerrors != 0) { + arg_print_errors(stderr, voice_say_args.end, argv[0]); + return 1; + } + + esp_err_t err = voice_channel_speak_text(voice_say_args.text->sval[0]); + if (err == ESP_OK) { + printf("Queued for voice playback.\n"); + return 0; + } + + printf("voice_say failed: %s\n", esp_err_to_name(err)); + return 1; +} + /* --- set_proxy command --- */ static struct { struct arg_str *host; @@ -728,6 +768,25 @@ esp_err_t serial_cli_init(void) }; esp_console_cmd_register(&heap_cmd); + /* voice_status */ + esp_console_cmd_t voice_status_cmd = { + .command = "voice_status", + .help = "Show voice channel status", + .func = &cmd_voice_status, + }; + esp_console_cmd_register(&voice_status_cmd); + + /* voice_say */ + voice_say_args.text = arg_str1(NULL, NULL, "", "Text to synthesize and play"); + voice_say_args.end = arg_end(1); + esp_console_cmd_t voice_say_cmd = { + .command = "voice_say", + .help = "Synthesize and play text via TTS", + .func = &cmd_voice_say, + .argtable = &voice_say_args, + }; + esp_console_cmd_register(&voice_say_cmd); + /* set_search_key */ search_key_args.key = arg_str1(NULL, NULL, "", "Brave Search API key"); search_key_args.end = arg_end(1); diff --git a/main/mimi.c b/main/mimi.c index 2d927cca..5f84f5c4 100644 --- a/main/mimi.c +++ b/main/mimi.c @@ -24,6 +24,7 @@ #include "cron/cron_service.h" #include "heartbeat/heartbeat.h" #include "skills/skill_loader.h" +#include "voice/voice_channel.h" static const char *TAG = "mimi"; @@ -72,17 +73,22 @@ static void outbound_dispatch_task(void *arg) ESP_LOGI(TAG, "Dispatching response to %s:%s", msg.channel, msg.chat_id); if (strcmp(msg.channel, MIMI_CHAN_TELEGRAM) == 0) { - esp_err_t send_err = telegram_send_message(msg.chat_id, msg.content); - if (send_err != ESP_OK) { - ESP_LOGE(TAG, "Telegram send failed for %s: %s", msg.chat_id, esp_err_to_name(send_err)); - } else { - ESP_LOGI(TAG, "Telegram send success for %s (%d bytes)", msg.chat_id, (int)strlen(msg.content)); - } + // esp_err_t send_err = telegram_send_message(msg.chat_id, msg.content); + // if (send_err != ESP_OK) { + // ESP_LOGE(TAG, "Telegram send failed for %s: %s", msg.chat_id, esp_err_to_name(send_err)); + // } else { + // ESP_LOGI(TAG, "Telegram send success for %s (%d bytes)", msg.chat_id, (int)strlen(msg.content)); + // } } else if (strcmp(msg.channel, MIMI_CHAN_WEBSOCKET) == 0) { esp_err_t ws_err = ws_server_send(msg.chat_id, msg.content); if (ws_err != ESP_OK) { ESP_LOGW(TAG, "WS send failed for %s: %s", msg.chat_id, esp_err_to_name(ws_err)); } + } else if (strcmp(msg.channel, MIMI_CHAN_VOICE) == 0) { + esp_err_t voice_err = voice_channel_speak_text(msg.content); + if (voice_err != ESP_OK) { + ESP_LOGW(TAG, "Voice playback failed: %s", esp_err_to_name(voice_err)); + } } else if (strcmp(msg.channel, MIMI_CHAN_SYSTEM) == 0) { ESP_LOGI(TAG, "System message [%s]: %.128s", msg.chat_id, msg.content); } else { @@ -120,11 +126,12 @@ void app_main(void) ESP_ERROR_CHECK(session_mgr_init()); ESP_ERROR_CHECK(wifi_manager_init()); ESP_ERROR_CHECK(http_proxy_init()); - ESP_ERROR_CHECK(telegram_bot_init()); + // ESP_ERROR_CHECK(telegram_bot_init()); ESP_ERROR_CHECK(llm_proxy_init()); ESP_ERROR_CHECK(tool_registry_init()); ESP_ERROR_CHECK(cron_service_init()); ESP_ERROR_CHECK(heartbeat_init()); + ESP_ERROR_CHECK(voice_channel_init()); ESP_ERROR_CHECK(agent_loop_init()); /* Start Serial CLI first (works without WiFi) */ @@ -148,9 +155,10 @@ void app_main(void) /* Start network-dependent services */ ESP_ERROR_CHECK(agent_loop_start()); - ESP_ERROR_CHECK(telegram_bot_start()); + // ESP_ERROR_CHECK(telegram_bot_start()); cron_service_start(); heartbeat_start(); + voice_channel_start(); ESP_ERROR_CHECK(ws_server_start()); ESP_LOGI(TAG, "All services started!"); diff --git a/main/mimi_config.h b/main/mimi_config.h index 9244f05f..431b93e9 100644 --- a/main/mimi_config.h +++ b/main/mimi_config.h @@ -37,6 +37,36 @@ #ifndef MIMI_SECRET_SEARCH_KEY #define MIMI_SECRET_SEARCH_KEY "" #endif +#ifndef MIMI_SECRET_STT_URL +#define MIMI_SECRET_STT_URL "" +#endif +#ifndef MIMI_SECRET_STT_API_KEY +#define MIMI_SECRET_STT_API_KEY "" +#endif +#ifndef MIMI_SECRET_STT_MODEL +#define MIMI_SECRET_STT_MODEL "" +#endif +#ifndef MIMI_SECRET_TTS_URL +#define MIMI_SECRET_TTS_URL "" +#endif +#ifndef MIMI_SECRET_TTS_API_KEY +#define MIMI_SECRET_TTS_API_KEY "" +#endif +#ifndef MIMI_SECRET_TTS_VOICE +#define MIMI_SECRET_TTS_VOICE "Cherry" +#endif +#ifndef MIMI_SECRET_TTS_MODEL +#define MIMI_SECRET_TTS_MODEL "" +#endif +#ifndef MIMI_SECRET_TTS_LANGUAGE +#define MIMI_SECRET_TTS_LANGUAGE "Chinese" +#endif + +/* Qwen voice API defaults (DashScope) */ +#define MIMI_QWEN_STT_URL "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions" +#define MIMI_QWEN_STT_MODEL "qwen3-asr-flash" +#define MIMI_QWEN_TTS_URL "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation" +#define MIMI_QWEN_TTS_MODEL "qwen-tts-latest" /* WiFi */ #define MIMI_WIFI_MAX_RETRY 10 @@ -52,6 +82,37 @@ #define MIMI_TG_CARD_SHOW_MS 3000 #define MIMI_TG_CARD_BODY_SCALE 3 +/* Voice (ReSpeaker XVF3800 over I2S) */ +#define MIMI_VOICE_ENABLED_DEFAULT 0 +#define MIMI_VOICE_CHAT_ID "voice_local" +#define MIMI_VOICE_SAMPLE_RATE 16000 +#define MIMI_VOICE_BITS_PER_SAMPLE 16 +#define MIMI_VOICE_FRAME_MS 20 +#define MIMI_VOICE_MAX_UTTERANCE_MS 10000 +#define MIMI_VOICE_SILENCE_END_MS 600 +#define MIMI_VOICE_VAD_THRESHOLD 700 +#define MIMI_VOICE_CAPTURE_STACK (10 * 1024) +#define MIMI_VOICE_PLAYBACK_STACK (8 * 1024) +#define MIMI_VOICE_TASK_PRIO 5 +#define MIMI_VOICE_CORE 0 +#define MIMI_VOICE_PLAYBACK_QUEUE_LEN 4 +/* Set valid board pins in mimi_secrets.h to enable audio I/O */ +#ifndef MIMI_VOICE_I2S_PORT +#define MIMI_VOICE_I2S_PORT 0 +#endif +#ifndef MIMI_VOICE_I2S_BCLK +#define MIMI_VOICE_I2S_BCLK (-1) +#endif +#ifndef MIMI_VOICE_I2S_WS +#define MIMI_VOICE_I2S_WS (-1) +#endif +#ifndef MIMI_VOICE_I2S_DIN +#define MIMI_VOICE_I2S_DIN (-1) +#endif +#ifndef MIMI_VOICE_I2S_DOUT +#define MIMI_VOICE_I2S_DOUT (-1) +#endif + /* Agent Loop */ #define MIMI_AGENT_STACK (24 * 1024) #define MIMI_AGENT_PRIO 6 diff --git a/main/mimi_secrets.h.example b/main/mimi_secrets.h.example index 456ecbc6..332955b5 100644 --- a/main/mimi_secrets.h.example +++ b/main/mimi_secrets.h.example @@ -29,3 +29,20 @@ /* Brave Search API */ #define MIMI_SECRET_SEARCH_KEY "" + +/* Voice STT / TTS services */ +#define MIMI_SECRET_STT_URL "" +#define MIMI_SECRET_STT_API_KEY "" +#define MIMI_SECRET_STT_MODEL "" +#define MIMI_SECRET_TTS_URL "" +#define MIMI_SECRET_TTS_API_KEY "" +#define MIMI_SECRET_TTS_VOICE "Cherry" +#define MIMI_SECRET_TTS_MODEL "" +#define MIMI_SECRET_TTS_LANGUAGE "Chinese" + +/* ReSpeaker XVF3800 I2S pin map (set per board) */ +#define MIMI_VOICE_I2S_PORT 0 +#define MIMI_VOICE_I2S_BCLK (-1) +#define MIMI_VOICE_I2S_WS (-1) +#define MIMI_VOICE_I2S_DIN (-1) +#define MIMI_VOICE_I2S_DOUT (-1) diff --git a/main/voice/voice_channel.c b/main/voice/voice_channel.c new file mode 100644 index 00000000..8a46f3cb --- /dev/null +++ b/main/voice/voice_channel.c @@ -0,0 +1,870 @@ +#include "voice/voice_channel.h" + +#include +#include +#include +#include + +#include "mimi_config.h" +#include "bus/message_bus.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" + +#include "esp_log.h" +#include "esp_http_client.h" +#include "esp_crt_bundle.h" +#include "esp_heap_caps.h" +#include "driver/i2s_std.h" + +#include "cJSON.h" +#include "mbedtls/base64.h" + +static const char *TAG = "voice"; + +typedef struct { + char *buf; + size_t len; + size_t cap; +} http_resp_t; + +static bool s_enabled = false; +static bool s_i2s_ready = false; +static volatile bool s_is_playing = false; + +static i2s_chan_handle_t s_tx_chan = NULL; +static i2s_chan_handle_t s_rx_chan = NULL; + +static TaskHandle_t s_capture_task = NULL; +static SemaphoreHandle_t s_http_lock = NULL; + +static esp_err_t http_event_handler(esp_http_client_event_t *evt) +{ + http_resp_t *resp = (http_resp_t *)evt->user_data; + if (evt->event_id != HTTP_EVENT_ON_DATA || !resp) { + return ESP_OK; + } + + if (resp->len + evt->data_len + 1 > resp->cap) { + size_t new_cap = resp->cap ? resp->cap * 2 : 4096; + size_t needed = resp->len + evt->data_len + 1; + if (new_cap < needed) { + new_cap = needed; + } + char *tmp = realloc(resp->buf, new_cap); + if (!tmp) { + return ESP_ERR_NO_MEM; + } + resp->buf = tmp; + resp->cap = new_cap; + } + + memcpy(resp->buf + resp->len, evt->data, evt->data_len); + resp->len += evt->data_len; + resp->buf[resp->len] = '\0'; + return ESP_OK; +} + +static esp_err_t i2s_init_xvf3800(void) +{ + if (MIMI_VOICE_I2S_BCLK < 0 || MIMI_VOICE_I2S_WS < 0 || + MIMI_VOICE_I2S_DIN < 0 || MIMI_VOICE_I2S_DOUT < 0) { + ESP_LOGW(TAG, "Voice disabled: configure I2S pins in mimi_secrets.h"); + return ESP_ERR_INVALID_STATE; + } + + i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)MIMI_VOICE_I2S_PORT, + I2S_ROLE_MASTER); + esp_err_t err = i2s_new_channel(&chan_cfg, &s_tx_chan, &s_rx_chan); + if (err != ESP_OK) { + ESP_LOGE(TAG, "i2s_new_channel failed: %s", esp_err_to_name(err)); + return err; + } + + i2s_std_config_t std_cfg = { + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(MIMI_VOICE_SAMPLE_RATE), + .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, + .bclk = MIMI_VOICE_I2S_BCLK, + .ws = MIMI_VOICE_I2S_WS, + .dout = MIMI_VOICE_I2S_DOUT, + .din = MIMI_VOICE_I2S_DIN, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }, + }, + }; + + err = i2s_channel_init_std_mode(s_rx_chan, &std_cfg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "i2s_channel_init_std_mode(rx) failed: %s", esp_err_to_name(err)); + return err; + } + err = i2s_channel_init_std_mode(s_tx_chan, &std_cfg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "i2s_channel_init_std_mode(tx) failed: %s", esp_err_to_name(err)); + return err; + } + + ESP_ERROR_CHECK(i2s_channel_enable(s_rx_chan)); + ESP_ERROR_CHECK(i2s_channel_enable(s_tx_chan)); + + s_i2s_ready = true; + ESP_LOGI(TAG, "I2S ready for XVF3800: %d Hz mono s16", MIMI_VOICE_SAMPLE_RATE); + return ESP_OK; +} + +static int frame_avg_abs_energy(const int16_t *samples, size_t sample_count) +{ + if (!samples || sample_count == 0) { + return 0; + } + + uint64_t sum = 0; + for (size_t i = 0; i < sample_count; i++) { + int v = samples[i]; + if (v < 0) { + v = -v; + } + sum += (uint32_t)v; + } + return (int)(sum / sample_count); +} + +static const char *stt_api_url(void) +{ + return MIMI_SECRET_STT_URL[0] ? MIMI_SECRET_STT_URL : MIMI_QWEN_STT_URL; +} + +static const char *stt_model(void) +{ + return MIMI_SECRET_STT_MODEL[0] ? MIMI_SECRET_STT_MODEL : MIMI_QWEN_STT_MODEL; +} + +static const char *tts_api_url(void) +{ + return MIMI_SECRET_TTS_URL[0] ? MIMI_SECRET_TTS_URL : MIMI_QWEN_TTS_URL; +} + +static const char *tts_model(void) +{ + return MIMI_SECRET_TTS_MODEL[0] ? MIMI_SECRET_TTS_MODEL : MIMI_QWEN_TTS_MODEL; +} + +static const char *stt_api_key(void) +{ + if (MIMI_SECRET_STT_API_KEY[0]) { + return MIMI_SECRET_STT_API_KEY; + } + return MIMI_SECRET_API_KEY; +} + +static const char *tts_api_key(void) +{ + if (MIMI_SECRET_TTS_API_KEY[0]) { + return MIMI_SECRET_TTS_API_KEY; + } + return MIMI_SECRET_API_KEY; +} + +static esp_err_t http_post_json(const char *url, const char *api_key, const char *json, + const char *accept, char **out_body, int *out_status) +{ + if (!url || !json || !out_body || !out_status) { + return ESP_ERR_INVALID_ARG; + } + *out_body = NULL; + *out_status = 0; + + http_resp_t resp = { + .buf = calloc(1, 4096), + .len = 0, + .cap = 4096, + }; + if (!resp.buf) { + return ESP_ERR_NO_MEM; + } + + esp_http_client_config_t cfg = { + .url = url, + .event_handler = http_event_handler, + .user_data = &resp, + .timeout_ms = 30000, + .buffer_size = 2048, + .buffer_size_tx = 2048, + .crt_bundle_attach = esp_crt_bundle_attach, + }; + esp_http_client_handle_t client = esp_http_client_init(&cfg); + if (!client) { + free(resp.buf); + return ESP_FAIL; + } + + esp_http_client_set_method(client, HTTP_METHOD_POST); + esp_http_client_set_header(client, "Content-Type", "application/json"); + if (accept && accept[0]) { + esp_http_client_set_header(client, "Accept", accept); + } + if (api_key && api_key[0]) { + char auth[256]; + snprintf(auth, sizeof(auth), "Bearer %s", api_key); + esp_http_client_set_header(client, "Authorization", auth); + } + esp_http_client_set_header(client, "X-DashScope-SSE", "disable"); + + esp_http_client_set_post_field(client, json, (int)strlen(json)); + esp_err_t err = esp_http_client_perform(client); + *out_status = esp_http_client_get_status_code(client); + esp_http_client_cleanup(client); + if (err != ESP_OK) { + free(resp.buf); + return err; + } + + *out_body = resp.buf; + return ESP_OK; +} + +static esp_err_t i2s_write_all(const uint8_t *data, size_t len) +{ + if (!data || len == 0) { + return ESP_OK; + } + + size_t off = 0; + while (off < len) { + size_t written = 0; + size_t chunk = len - off; + if (chunk > 1024) { + chunk = 1024; + } + esp_err_t err = i2s_channel_write(s_tx_chan, data + off, chunk, &written, + pdMS_TO_TICKS(500)); + if (err != ESP_OK) { + return err; + } + off += written; + } + return ESP_OK; +} + +static uint32_t read_le_u32(const uint8_t *p) +{ + return ((uint32_t)p[0]) | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} + +static uint16_t read_le_u16(const uint8_t *p) +{ + return (uint16_t)(((uint16_t)p[0]) | ((uint16_t)p[1] << 8)); +} + +static esp_err_t wav_find_data_offset(const uint8_t *wav, size_t wav_len, + size_t *out_data_off, uint32_t *out_sample_rate) +{ + if (!wav || wav_len < 12 || !out_data_off || !out_sample_rate) { + return ESP_ERR_INVALID_ARG; + } + *out_data_off = 0; + *out_sample_rate = 0; + + if (memcmp(wav, "RIFF", 4) != 0 || memcmp(wav + 8, "WAVE", 4) != 0) { + return ESP_ERR_INVALID_RESPONSE; + } + + bool fmt_ok = false; + size_t off = 12; + while (off + 8 <= wav_len) { + const uint8_t *chunk = wav + off; + uint32_t chunk_size = read_le_u32(chunk + 4); + size_t payload_off = off + 8; + size_t next = payload_off + chunk_size + (chunk_size & 1u); + if (payload_off > wav_len || next > wav_len) { + return ESP_ERR_NOT_FOUND; + } + + if (memcmp(chunk, "fmt ", 4) == 0 && chunk_size >= 16) { + const uint8_t *f = wav + payload_off; + uint16_t audio_format = read_le_u16(f + 0); + uint16_t channels = read_le_u16(f + 2); + *out_sample_rate = read_le_u32(f + 4); + uint16_t bits_per_sample = read_le_u16(f + 14); + if (audio_format == 1 && channels == 1 && bits_per_sample == 16) { + fmt_ok = true; + } + } else if (memcmp(chunk, "data", 4) == 0) { + if (!fmt_ok) { + return ESP_ERR_INVALID_RESPONSE; + } + *out_data_off = payload_off; + return ESP_OK; + } + off = next; + } + + return ESP_ERR_NOT_FOUND; +} + +static esp_err_t build_wav_from_pcm(const int16_t *pcm, size_t pcm_bytes, + uint8_t **out_wav, size_t *out_wav_len) +{ + if (!pcm || !out_wav || !out_wav_len) { + return ESP_ERR_INVALID_ARG; + } + if (pcm_bytes > UINT32_MAX - 44) { + return ESP_ERR_INVALID_SIZE; + } + + uint8_t *wav = malloc(44 + pcm_bytes); + if (!wav) { + return ESP_ERR_NO_MEM; + } + + uint32_t data_size = (uint32_t)pcm_bytes; + uint32_t riff_size = 36 + data_size; + uint32_t byte_rate = MIMI_VOICE_SAMPLE_RATE * 1 * (MIMI_VOICE_BITS_PER_SAMPLE / 8); + uint16_t block_align = 1 * (MIMI_VOICE_BITS_PER_SAMPLE / 8); + + memcpy(wav + 0, "RIFF", 4); + wav[4] = (uint8_t)(riff_size & 0xFF); + wav[5] = (uint8_t)((riff_size >> 8) & 0xFF); + wav[6] = (uint8_t)((riff_size >> 16) & 0xFF); + wav[7] = (uint8_t)((riff_size >> 24) & 0xFF); + memcpy(wav + 8, "WAVE", 4); + memcpy(wav + 12, "fmt ", 4); + wav[16] = 16; wav[17] = 0; wav[18] = 0; wav[19] = 0; + wav[20] = 1; wav[21] = 0; + wav[22] = 1; wav[23] = 0; + wav[24] = (uint8_t)(MIMI_VOICE_SAMPLE_RATE & 0xFF); + wav[25] = (uint8_t)((MIMI_VOICE_SAMPLE_RATE >> 8) & 0xFF); + wav[26] = (uint8_t)((MIMI_VOICE_SAMPLE_RATE >> 16) & 0xFF); + wav[27] = (uint8_t)((MIMI_VOICE_SAMPLE_RATE >> 24) & 0xFF); + wav[28] = (uint8_t)(byte_rate & 0xFF); + wav[29] = (uint8_t)((byte_rate >> 8) & 0xFF); + wav[30] = (uint8_t)((byte_rate >> 16) & 0xFF); + wav[31] = (uint8_t)((byte_rate >> 24) & 0xFF); + wav[32] = (uint8_t)(block_align & 0xFF); + wav[33] = (uint8_t)((block_align >> 8) & 0xFF); + wav[34] = MIMI_VOICE_BITS_PER_SAMPLE; + wav[35] = 0; + memcpy(wav + 36, "data", 4); + wav[40] = (uint8_t)(data_size & 0xFF); + wav[41] = (uint8_t)((data_size >> 8) & 0xFF); + wav[42] = (uint8_t)((data_size >> 16) & 0xFF); + wav[43] = (uint8_t)((data_size >> 24) & 0xFF); + memcpy(wav + 44, pcm, pcm_bytes); + + *out_wav = wav; + *out_wav_len = 44 + pcm_bytes; + return ESP_OK; +} + +static esp_err_t base64_encode_alloc(const uint8_t *src, size_t src_len, char **out_b64) +{ + if (!src || !out_b64) { + return ESP_ERR_INVALID_ARG; + } + *out_b64 = NULL; + + size_t needed = 0; + int rc = mbedtls_base64_encode(NULL, 0, &needed, src, src_len); + if (rc != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL || needed == 0) { + return ESP_FAIL; + } + + char *b64 = malloc(needed + 1); + if (!b64) { + return ESP_ERR_NO_MEM; + } + rc = mbedtls_base64_encode((unsigned char *)b64, needed, &needed, src, src_len); + if (rc != 0) { + free(b64); + return ESP_FAIL; + } + b64[needed] = '\0'; + *out_b64 = b64; + return ESP_OK; +} + +static esp_err_t parse_qwen_asr_text(const char *json, char *out_text, size_t out_size) +{ + cJSON *root = cJSON_Parse(json); + if (!root) { + return ESP_ERR_INVALID_RESPONSE; + } + + const char *text = NULL; + cJSON *choices = cJSON_GetObjectItem(root, "choices"); + if (cJSON_IsArray(choices)) { + cJSON *first = cJSON_GetArrayItem(choices, 0); + cJSON *message = cJSON_GetObjectItem(first, "message"); + cJSON *content = cJSON_GetObjectItem(message, "content"); + if (cJSON_IsString(content) && content->valuestring) { + text = content->valuestring; + } else if (cJSON_IsArray(content)) { + cJSON *item = cJSON_GetArrayItem(content, 0); + cJSON *item_text = cJSON_GetObjectItem(item, "text"); + if (cJSON_IsString(item_text) && item_text->valuestring) { + text = item_text->valuestring; + } + } + } + + if (!text) { + cJSON *output = cJSON_GetObjectItem(root, "output"); + cJSON *result = output ? cJSON_GetObjectItem(output, "text") : NULL; + if (cJSON_IsString(result) && result->valuestring) { + text = result->valuestring; + } + } + + if (text && out_size > 0) { + strncpy(out_text, text, out_size - 1); + out_text[out_size - 1] = '\0'; + } + cJSON_Delete(root); + return (text && out_text[0]) ? ESP_OK : ESP_ERR_NOT_FOUND; +} + +static esp_err_t stt_transcribe_pcm(const int16_t *pcm, size_t bytes, + char *out_text, size_t out_text_size) +{ + if (!out_text || out_text_size == 0) { + return ESP_ERR_INVALID_ARG; + } + out_text[0] = '\0'; + + const char *url = stt_api_url(); + const char *api_key = stt_api_key(); + if (!url[0] || !api_key[0]) { + return ESP_ERR_INVALID_STATE; + } + + uint8_t *wav = NULL; + size_t wav_len = 0; + char *audio_b64 = NULL; + char *data_uri = NULL; + char *json = NULL; + char *resp_body = NULL; + int status = 0; + esp_err_t err = ESP_FAIL; + + err = build_wav_from_pcm(pcm, bytes, &wav, &wav_len); + if (err != ESP_OK) goto done; + err = base64_encode_alloc(wav, wav_len, &audio_b64); + if (err != ESP_OK) goto done; + + const char *prefix = "data:audio/wav;base64,"; + data_uri = malloc(strlen(prefix) + strlen(audio_b64) + 1); + if (!data_uri) { + err = ESP_ERR_NO_MEM; + goto done; + } + strcpy(data_uri, prefix); + strcat(data_uri, audio_b64); + + cJSON *root = cJSON_CreateObject(); + cJSON_AddStringToObject(root, "model", stt_model()); + cJSON_AddBoolToObject(root, "stream", false); + + cJSON *modalities = cJSON_CreateArray(); + cJSON_AddItemToArray(modalities, cJSON_CreateString("text")); + cJSON_AddItemToObject(root, "modalities", modalities); + + cJSON *messages = cJSON_CreateArray(); + cJSON *msg = cJSON_CreateObject(); + cJSON_AddStringToObject(msg, "role", "user"); + cJSON *content = cJSON_CreateArray(); + cJSON *audio_item = cJSON_CreateObject(); + cJSON_AddStringToObject(audio_item, "type", "input_audio"); + cJSON *audio_obj = cJSON_CreateObject(); + cJSON_AddStringToObject(audio_obj, "data", data_uri); + cJSON_AddStringToObject(audio_obj, "format", "wav"); + cJSON_AddItemToObject(audio_item, "input_audio", audio_obj); + cJSON_AddItemToArray(content, audio_item); + + cJSON *text_item = cJSON_CreateObject(); + cJSON_AddStringToObject(text_item, "type", "text"); + cJSON_AddStringToObject(text_item, "text", "Transcribe this speech to plain text. Return only the transcript."); + cJSON_AddItemToArray(content, text_item); + cJSON_AddItemToObject(msg, "content", content); + cJSON_AddItemToArray(messages, msg); + cJSON_AddItemToObject(root, "messages", messages); + + json = cJSON_PrintUnformatted(root); + cJSON_Delete(root); + if (!json) { + err = ESP_ERR_NO_MEM; + goto done; + } + + err = http_post_json(url, api_key, json, "application/json", &resp_body, &status); + if (err != ESP_OK) goto done; + if (status < 200 || status >= 300) { + ESP_LOGE(TAG, "Qwen STT HTTP %d: %.240s", status, resp_body); + err = ESP_FAIL; + goto done; + } + + err = parse_qwen_asr_text(resp_body, out_text, out_text_size); + +done: + free(wav); + free(audio_b64); + free(data_uri); + free(json); + free(resp_body); + return err; +} + +static esp_err_t tts_stream_play(const char *text) +{ + char *json = NULL; + char *resp_body = NULL; + esp_http_client_handle_t client = NULL; + uint8_t *prefix = NULL; + esp_err_t err = ESP_FAIL; + + const char *url = tts_api_url(); + const char *api_key = tts_api_key(); + if (!url[0] || !api_key[0]) { + return ESP_ERR_INVALID_STATE; + } + + /* Step 1: request TTS task and get downloadable audio URL */ + cJSON *body = cJSON_CreateObject(); + if (!body) { + return ESP_ERR_NO_MEM; + } + cJSON_AddStringToObject(body, "model", tts_model()); + cJSON *input = cJSON_CreateObject(); + cJSON_AddStringToObject(input, "text", text); + cJSON_AddItemToObject(body, "input", input); + cJSON *parameters = cJSON_CreateObject(); + cJSON_AddStringToObject(parameters, "voice", MIMI_SECRET_TTS_VOICE); + cJSON_AddStringToObject(parameters, "language_type", MIMI_SECRET_TTS_LANGUAGE); + cJSON_AddItemToObject(body, "parameters", parameters); + json = cJSON_PrintUnformatted(body); + cJSON_Delete(body); + if (!json) { + return ESP_ERR_NO_MEM; + } + + int status = 0; + err = http_post_json(url, api_key, json, "application/json", &resp_body, &status); + free(json); + json = NULL; + if (err != ESP_OK || status < 200 || status >= 300) { + ESP_LOGE(TAG, "Qwen TTS HTTP %d: %.240s", status, resp_body ? resp_body : ""); + goto done; + } + + cJSON *root = cJSON_Parse(resp_body); + free(resp_body); + resp_body = NULL; + if (!root) { + err = ESP_ERR_INVALID_RESPONSE; + goto done; + } + cJSON *output = cJSON_GetObjectItem(root, "output"); + cJSON *audio = output ? cJSON_GetObjectItem(output, "audio") : NULL; + cJSON *url_obj = audio ? cJSON_GetObjectItem(audio, "url") : NULL; + const char *audio_url = (cJSON_IsString(url_obj) && url_obj->valuestring) ? url_obj->valuestring : NULL; + if (!audio_url) { + cJSON_Delete(root); + err = ESP_ERR_INVALID_RESPONSE; + goto done; + } + + esp_http_client_config_t cfg = { + .url = audio_url, + .timeout_ms = 30000, + .buffer_size = 2048, + .buffer_size_tx = 1024, + .crt_bundle_attach = esp_crt_bundle_attach, + }; + client = esp_http_client_init(&cfg); + cJSON_Delete(root); + if (!client) { + err = ESP_FAIL; + goto done; + } + + esp_http_client_set_method(client, HTTP_METHOD_GET); + esp_http_client_set_header(client, "Accept", "audio/wav,application/octet-stream"); + err = esp_http_client_open(client, 0); + if (err != ESP_OK) { + goto done; + } + status = esp_http_client_fetch_headers(client); + (void)status; + int code = esp_http_client_get_status_code(client); + if (code < 200 || code >= 300) { + err = ESP_FAIL; + goto done; + } + + /* Step 2: stream download and play WAV body */ + size_t prefix_cap = 4096; + prefix = malloc(prefix_cap); + if (!prefix) { + err = ESP_ERR_NO_MEM; + goto done; + } + size_t prefix_len = 0; + bool data_started = false; + + s_is_playing = true; + + uint8_t chunk[2048]; + while (1) { + int n = esp_http_client_read(client, (char *)chunk, sizeof(chunk)); + if (n < 0) { + err = ESP_FAIL; + break; + } + if (n == 0) { + err = ESP_OK; + break; + } + + if (!data_started) { + if (prefix_len + (size_t)n > prefix_cap) { + size_t new_cap = prefix_cap * 2; + while (new_cap < prefix_len + (size_t)n) { + new_cap *= 2; + } + uint8_t *tmp = realloc(prefix, new_cap); + if (!tmp) { + err = ESP_ERR_NO_MEM; + break; + } + prefix = tmp; + prefix_cap = new_cap; + } + memcpy(prefix + prefix_len, chunk, (size_t)n); + prefix_len += (size_t)n; + + size_t data_off = 0; + uint32_t sample_rate = 0; + esp_err_t parse = wav_find_data_offset(prefix, prefix_len, &data_off, &sample_rate); + if (parse == ESP_OK) { + if (sample_rate != MIMI_VOICE_SAMPLE_RATE) { + ESP_LOGW(TAG, "TTS WAV sample rate=%u differs from I2S=%d; playback speed may be off", + sample_rate, MIMI_VOICE_SAMPLE_RATE); + } + if (prefix_len > data_off) { + err = i2s_write_all(prefix + data_off, prefix_len - data_off); + if (err != ESP_OK) { + break; + } + } + data_started = true; + } else if (parse != ESP_ERR_NOT_FOUND) { + err = parse; + break; + } + } else { + err = i2s_write_all(chunk, (size_t)n); + if (err != ESP_OK) { + break; + } + } + } + +done: + s_is_playing = false; + if (client) { + esp_http_client_close(client); + esp_http_client_cleanup(client); + } + free(prefix); + free(resp_body); + free(json); + return err; +} + +static void push_voice_inbound(const char *text) +{ + mimi_msg_t msg = {0}; + strncpy(msg.channel, MIMI_CHAN_VOICE, sizeof(msg.channel) - 1); + strncpy(msg.chat_id, MIMI_VOICE_CHAT_ID, sizeof(msg.chat_id) - 1); + msg.content = strdup(text); + if (!msg.content) { + return; + } + + if (message_bus_push_inbound(&msg) != ESP_OK) { + ESP_LOGW(TAG, "Inbound queue full, drop voice text"); + free(msg.content); + } +} + +static void voice_capture_task(void *arg) +{ + const size_t samples_per_frame = (MIMI_VOICE_SAMPLE_RATE * MIMI_VOICE_FRAME_MS) / 1000; + const size_t frame_bytes = samples_per_frame * sizeof(int16_t); + const int max_frames = MIMI_VOICE_MAX_UTTERANCE_MS / MIMI_VOICE_FRAME_MS; + const int end_silence_frames = MIMI_VOICE_SILENCE_END_MS / MIMI_VOICE_FRAME_MS; + + int16_t *frame = malloc(frame_bytes); + int16_t *utterance = heap_caps_malloc(max_frames * frame_bytes, MALLOC_CAP_SPIRAM); + if (!utterance) { + utterance = malloc(max_frames * frame_bytes); + } + + if (!frame || !utterance) { + free(frame); + free(utterance); + ESP_LOGE(TAG, "Cannot allocate voice capture buffers"); + vTaskDelete(NULL); + return; + } + + bool in_speech = false; + int silence_frames = 0; + int total_frames = 0; + + while (1) { + if (s_is_playing) { + vTaskDelay(pdMS_TO_TICKS(30)); + continue; + } + + size_t read_bytes = 0; + esp_err_t err = i2s_channel_read(s_rx_chan, frame, frame_bytes, &read_bytes, + pdMS_TO_TICKS(200)); + if (err != ESP_OK || read_bytes != frame_bytes) { + continue; + } + + int energy = frame_avg_abs_energy(frame, samples_per_frame); + bool voiced = (energy >= MIMI_VOICE_VAD_THRESHOLD); + + if (!in_speech) { + if (!voiced) { + continue; + } + in_speech = true; + silence_frames = 0; + total_frames = 0; + } + + if (total_frames < max_frames) { + memcpy((uint8_t *)utterance + (total_frames * frame_bytes), frame, frame_bytes); + total_frames++; + } + + if (voiced) { + silence_frames = 0; + } else { + silence_frames++; + } + + bool hit_max = (total_frames >= max_frames); + bool end_of_speech = (silence_frames >= end_silence_frames); + if (!hit_max && !end_of_speech) { + continue; + } + + in_speech = false; + if (total_frames < 5) { + continue; + } + + size_t pcm_bytes = total_frames * frame_bytes; + char text[512] = {0}; + + if (xSemaphoreTake(s_http_lock, pdMS_TO_TICKS(30000)) == pdTRUE) { + esp_err_t stt_err = stt_transcribe_pcm(utterance, pcm_bytes, text, sizeof(text)); + xSemaphoreGive(s_http_lock); + if (stt_err == ESP_OK && text[0]) { + ESP_LOGI(TAG, "Voice STT: %s", text); + push_voice_inbound(text); + } else { + ESP_LOGW(TAG, "STT failed or empty transcript"); + } + } + } +} + +esp_err_t voice_channel_init(void) +{ + s_enabled = (MIMI_VOICE_ENABLED_DEFAULT != 0) || + (stt_api_key()[0] && tts_api_key()[0]); + if (!s_enabled) { + ESP_LOGI(TAG, "Voice channel disabled (set STT/TTS API key or enable default)"); + return ESP_OK; + } + + esp_err_t err = i2s_init_xvf3800(); + if (err != ESP_OK) { + s_enabled = false; + return ESP_OK; + } + + s_http_lock = xSemaphoreCreateMutex(); + if (!s_http_lock) { + ESP_LOGE(TAG, "Voice init failed: cannot allocate mutex"); + s_enabled = false; + return ESP_ERR_NO_MEM; + } + + return ESP_OK; +} + +esp_err_t voice_channel_start(void) +{ + if (!s_enabled || !s_i2s_ready) { + return ESP_OK; + } + + if (!s_capture_task) { + if (xTaskCreatePinnedToCore(voice_capture_task, "voice_cap", + MIMI_VOICE_CAPTURE_STACK, NULL, + MIMI_VOICE_TASK_PRIO, &s_capture_task, + MIMI_VOICE_CORE) != pdPASS) { + return ESP_FAIL; + } + } + + ESP_LOGI(TAG, "Voice channel started"); + return ESP_OK; +} + +esp_err_t voice_channel_speak_text(const char *text) +{ + if (!s_enabled || !s_i2s_ready || !text || text[0] == '\0') { + return ESP_ERR_INVALID_STATE; + } + + if (xSemaphoreTake(s_http_lock, pdMS_TO_TICKS(30000)) != pdTRUE) { + return ESP_ERR_TIMEOUT; + } + + esp_err_t err = tts_stream_play(text); + + xSemaphoreGive(s_http_lock); + return err; +} + +bool voice_channel_is_enabled(void) +{ + return s_enabled; +} + +void voice_channel_get_status(voice_channel_status_t *status) +{ + if (!status) { + return; + } + status->enabled = s_enabled; + status->i2s_ready = s_i2s_ready; + status->is_playing = s_is_playing; + status->stt_configured = (stt_api_url()[0] != '\0' && stt_api_key()[0] != '\0'); + status->tts_configured = (tts_api_url()[0] != '\0' && tts_api_key()[0] != '\0'); +} diff --git a/main/voice/voice_channel.h b/main/voice/voice_channel.h new file mode 100644 index 00000000..ffcc2504 --- /dev/null +++ b/main/voice/voice_channel.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include "esp_err.h" + +typedef struct { + bool enabled; + bool i2s_ready; + bool is_playing; + bool stt_configured; + bool tts_configured; +} voice_channel_status_t; + +/* + * Voice channel for ReSpeaker XVF3800 over I2S. + * + * Inbound path: + * Mic PCM -> VAD utterance -> STT -> message_bus inbound (channel=voice) + * + * Outbound path: + * Agent text (channel=voice) -> TTS -> speaker playback (I2S) + */ +esp_err_t voice_channel_init(void); +esp_err_t voice_channel_start(void); + +/* + * Convert text to speech and enqueue for playback. + */ +esp_err_t voice_channel_speak_text(const char *text); + +bool voice_channel_is_enabled(void); +void voice_channel_get_status(voice_channel_status_t *status); From 13afef6ed70e4db2f9bf648f875573848a9b6754 Mon Sep 17 00:00:00 2001 From: jerryyip Date: Wed, 4 Mar 2026 09:30:59 +0800 Subject: [PATCH 2/9] fix: set flash size to 8MB for XIAO ESP32S3 --- partitions.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/partitions.csv b/partitions.csv index 24c87784..017cf429 100644 --- a/partitions.csv +++ b/partitions.csv @@ -4,5 +4,5 @@ otadata, data, ota, 0xF000, 0x2000 phy_init, data, phy, 0x11000, 0x1000 ota_0, app, ota_0, 0x20000, 0x200000 ota_1, app, ota_1, 0x220000, 0x200000 -spiffs, data, spiffs, 0x420000, 0xBD0000 -coredump, data, coredump,0xFF0000, 0x10000 +spiffs, data, spiffs, 0x420000, 0x3D0000 +coredump, data, coredump,0x7F0000, 0x10000 From b0c6efea099f1bac525f844f01924c088c8d8551 Mon Sep 17 00:00:00 2001 From: stringwind Date: Fri, 6 Mar 2026 10:29:10 +0800 Subject: [PATCH 3/9] feat: add LLM URL configuration and commands for setting and clearing API URL --- main/CMakeLists.txt | 2 +- main/cli/serial_cli.c | 137 ++++++++++++++++- main/llm/llm_proxy.c | 291 ++++++++++++++++++++++++++++++++---- main/llm/llm_proxy.h | 24 +++ main/mimi_config.h | 4 + main/mimi_secrets.h.example | 3 +- main/proxy/http_proxy.c | 55 +++++++ main/proxy/http_proxy.h | 21 +++ sdkconfig.defaults.esp32s3 | 2 +- 9 files changed, 505 insertions(+), 34 deletions(-) diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 3d3522b3..47afb9ec 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -27,5 +27,5 @@ idf_component_register( REQUIRES nvs_flash esp_wifi esp_netif esp_http_client esp_http_server esp_https_ota esp_event json spiffs console vfs app_update esp-tls - esp_timer esp_websocket_client + esp_timer esp_websocket_client driver ) diff --git a/main/cli/serial_cli.c b/main/cli/serial_cli.c index f16806c9..d3a2b90c 100644 --- a/main/cli/serial_cli.c +++ b/main/cli/serial_cli.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include "esp_log.h" #include "esp_console.h" @@ -167,9 +168,51 @@ static int cmd_set_model_provider(int argc, char **argv) arg_print_errors(stderr, provider_args.end, argv[0]); return 1; } - llm_set_provider(provider_args.provider->sval[0]); - printf("Model provider set.\n"); - return 0; + esp_err_t err = llm_set_provider(provider_args.provider->sval[0]); + if (err == ESP_OK) { + printf("Model provider set.\n"); + return 0; + } + printf("Failed to set provider: %s\n", esp_err_to_name(err)); + return 1; +} + +/* --- set_llm_url command --- */ +static struct { + struct arg_str *url; + struct arg_end *end; +} llm_url_args; + +static int cmd_set_llm_url(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **)&llm_url_args); + if (nerrors != 0) { + arg_print_errors(stderr, llm_url_args.end, argv[0]); + return 1; + } + + esp_err_t err = llm_set_api_url(llm_url_args.url->sval[0]); + if (err == ESP_OK) { + printf("LLM URL saved.\n"); + return 0; + } + printf("Failed to set LLM URL: %s\n", esp_err_to_name(err)); + return 1; +} + +/* --- clear_llm_url command --- */ +static int cmd_clear_llm_url(int argc, char **argv) +{ + (void)argc; + (void)argv; + + esp_err_t err = llm_clear_api_url(); + if (err == ESP_OK) { + printf("LLM URL override cleared.\n"); + return 0; + } + printf("Failed to clear LLM URL: %s\n", esp_err_to_name(err)); + return 1; } /* --- memory_read command --- */ @@ -540,7 +583,7 @@ static int cmd_skill_search(int argc, char **argv) static void print_config(const char *label, const char *ns, const char *key, const char *build_val, bool mask) { - char nvs_val[128] = {0}; + char nvs_val[384] = {0}; const char *source = "not set"; const char *display = "(empty)"; @@ -568,6 +611,68 @@ static void print_config(const char *label, const char *ns, const char *key, } } +static void print_config_u16(const char *label, const char *ns, const char *key, + const char *build_val_str) +{ + uint16_t nvs_val = 0; + bool has_val = false; + const char *source = "not set"; + + nvs_handle_t nvs; + if (nvs_open(ns, NVS_READONLY, &nvs) == ESP_OK) { + if (nvs_get_u16(nvs, key, &nvs_val) == ESP_OK && nvs_val != 0) { + has_val = true; + source = "NVS"; + } + nvs_close(nvs); + } + + if (!has_val && build_val_str && build_val_str[0] != '\0') { + int v = atoi(build_val_str); + if (v > 0 && v <= 65535) { + nvs_val = (uint16_t)v; + has_val = true; + source = "build"; + } + } + + if (has_val) { + printf(" %-14s: %u [%s]\n", label, (unsigned)nvs_val, source); + } else { + printf(" %-14s: (empty) [%s]\n", label, source); + } +} + +static void print_llm_url(void) +{ + const char *source = "provider"; + const char *url = llm_get_api_url(); + + nvs_handle_t nvs; + if (nvs_open(MIMI_NVS_LLM, NVS_READONLY, &nvs) == ESP_OK) { + char tmp[384] = {0}; + size_t len = sizeof(tmp); + if (nvs_get_str(nvs, MIMI_NVS_KEY_API_URL, tmp, &len) == ESP_OK && tmp[0]) { + source = "NVS"; + } + nvs_close(nvs); + } + + if (strcmp(source, "provider") == 0 && MIMI_SECRET_LLM_API_URL[0] != '\0') { + source = "build"; + } + + if (!url || url[0] == '\0') { + url = "(empty)"; + } + + if (llm_api_url_is_valid()) { + printf(" %-14s: %s [%s]\n", "LLM URL", url, source); + } else { + printf(" %-14s: %s [%s, invalid]\n", "LLM URL", url, source); + } +} + static int cmd_config_show(int argc, char **argv) { printf("=== Current Configuration ===\n"); @@ -575,10 +680,11 @@ static int cmd_config_show(int argc, char **argv) print_config("WiFi Pass", MIMI_NVS_WIFI, MIMI_NVS_KEY_PASS, MIMI_SECRET_WIFI_PASS, true); print_config("TG Token", MIMI_NVS_TG, MIMI_NVS_KEY_TG_TOKEN, MIMI_SECRET_TG_TOKEN, true); print_config("API Key", MIMI_NVS_LLM, MIMI_NVS_KEY_API_KEY, MIMI_SECRET_API_KEY, true); + print_llm_url(); print_config("Model", MIMI_NVS_LLM, MIMI_NVS_KEY_MODEL, MIMI_SECRET_MODEL, false); print_config("Provider", MIMI_NVS_LLM, MIMI_NVS_KEY_PROVIDER, MIMI_SECRET_MODEL_PROVIDER, false); print_config("Proxy Host", MIMI_NVS_PROXY, MIMI_NVS_KEY_PROXY_HOST, MIMI_SECRET_PROXY_HOST, false); - print_config("Proxy Port", MIMI_NVS_PROXY, MIMI_NVS_KEY_PROXY_PORT, MIMI_SECRET_PROXY_PORT, false); + print_config_u16("Proxy Port", MIMI_NVS_PROXY, MIMI_NVS_KEY_PROXY_PORT, MIMI_SECRET_PROXY_PORT); print_config("Search Key", MIMI_NVS_SEARCH, MIMI_NVS_KEY_API_KEY, MIMI_SECRET_SEARCH_KEY, true); print_config("Tavily Key", MIMI_NVS_SEARCH, MIMI_NVS_KEY_TAVILY_KEY, MIMI_SECRET_TAVILY_KEY, true); printf("=============================\n"); @@ -901,7 +1007,7 @@ esp_err_t serial_cli_init(void) esp_console_cmd_register(&model_cmd); /* set_model_provider */ - provider_args.provider = arg_str1(NULL, NULL, "", "Model provider (anthropic|openai)"); + provider_args.provider = arg_str1(NULL, NULL, "", "Model provider (anthropic|openai|openai_compat)"); provider_args.end = arg_end(1); esp_console_cmd_t provider_cmd = { .command = "set_model_provider", @@ -911,6 +1017,25 @@ esp_err_t serial_cli_init(void) }; esp_console_cmd_register(&provider_cmd); + /* set_llm_url */ + llm_url_args.url = arg_str1(NULL, NULL, "", "LLM endpoint URL (http[s]://host[:port]/path)"); + llm_url_args.end = arg_end(1); + esp_console_cmd_t llm_url_cmd = { + .command = "set_llm_url", + .help = "Set LLM endpoint URL override (e.g. set_llm_url http://192.168.1.10:8000/v1/chat/completions)", + .func = &cmd_set_llm_url, + .argtable = &llm_url_args, + }; + esp_console_cmd_register(&llm_url_cmd); + + /* clear_llm_url */ + esp_console_cmd_t llm_url_clear_cmd = { + .command = "clear_llm_url", + .help = "Clear LLM endpoint URL override (revert to provider default)", + .func = &cmd_clear_llm_url, + }; + esp_console_cmd_register(&llm_url_clear_cmd); + /* skill_list */ esp_console_cmd_t skill_list_cmd = { .command = "skill_list", diff --git a/main/llm/llm_proxy.c b/main/llm/llm_proxy.c index c6fa1b88..98b45a7c 100644 --- a/main/llm/llm_proxy.c +++ b/main/llm/llm_proxy.c @@ -4,6 +4,7 @@ #include #include +#include #include "esp_log.h" #include "esp_http_client.h" #include "esp_crt_bundle.h" @@ -15,10 +16,15 @@ static const char *TAG = "llm"; #define LLM_API_KEY_MAX_LEN 320 #define LLM_MODEL_MAX_LEN 64 +#define LLM_URL_MAX_LEN 512 +#define LLM_HOST_MAX_LEN 128 +#define LLM_PATH_MAX_LEN 256 #define LLM_DUMP_MAX_BYTES (16 * 1024) #define LLM_DUMP_CHUNK_BYTES 320 static char s_api_key[LLM_API_KEY_MAX_LEN] = {0}; +static char s_api_url_build[LLM_URL_MAX_LEN] = {0}; +static char s_api_url_nvs[LLM_URL_MAX_LEN] = {0}; static char s_model[LLM_MODEL_MAX_LEN] = MIMI_LLM_DEFAULT_MODEL; static char s_provider[16] = MIMI_LLM_PROVIDER_DEFAULT; @@ -184,22 +190,161 @@ static esp_err_t http_event_handler(esp_http_client_event_t *evt) static bool provider_is_openai(void) { - return strcmp(s_provider, "openai") == 0; + return strcmp(s_provider, "openai") == 0 || strcmp(s_provider, "openai_compat") == 0; } -static const char *llm_api_url(void) +static bool provider_is_anthropic(void) +{ + return strcmp(s_provider, "anthropic") == 0; +} + +static bool provider_is_known(void) +{ + return provider_is_openai() || provider_is_anthropic(); +} + +static const char *provider_default_url(void) { return provider_is_openai() ? MIMI_OPENAI_API_URL : MIMI_LLM_API_URL; } -static const char *llm_api_host(void) +/* ── Endpoint URL parsing ─────────────────────────────────────── */ + +typedef struct { + bool use_tls; /* true for https:// */ + int port; + char url[LLM_URL_MAX_LEN]; /* full URL */ + char host[LLM_HOST_MAX_LEN]; + char path[LLM_PATH_MAX_LEN]; /* origin-form path, includes query */ +} llm_endpoint_t; + +static llm_endpoint_t s_endpoint = {0}; +static bool s_endpoint_valid = false; + +static bool port_is_default(bool use_tls, int port) +{ + return (use_tls && port == 443) || (!use_tls && port == 80); +} + +static void build_host_header_value(const llm_endpoint_t *ep, char *out, size_t out_size) +{ + if (!ep || !out || out_size == 0) return; + if (port_is_default(ep->use_tls, ep->port)) { + snprintf(out, out_size, "%s", ep->host); + } else { + snprintf(out, out_size, "%s:%d", ep->host, ep->port); + } +} + +static esp_err_t llm_parse_endpoint_url(const char *url, llm_endpoint_t *out) +{ + if (!url || !url[0] || !out) return ESP_ERR_INVALID_ARG; + + memset(out, 0, sizeof(*out)); + + const char *p = NULL; + if (strncmp(url, "https://", 8) == 0) { + out->use_tls = true; + out->port = 443; + p = url + 8; + } else if (strncmp(url, "http://", 7) == 0) { + out->use_tls = false; + out->port = 80; + p = url + 7; + } else { + return ESP_ERR_INVALID_ARG; + } + + /* Parse host[:port] */ + const char *hp_end = p; + while (*hp_end && *hp_end != '/' && *hp_end != '?' && *hp_end != '#') hp_end++; + if (hp_end == p) return ESP_ERR_INVALID_ARG; + + char hostport[LLM_HOST_MAX_LEN + 16] = {0}; + size_t hp_len = (size_t)(hp_end - p); + if (hp_len >= sizeof(hostport)) return ESP_ERR_INVALID_SIZE; + memcpy(hostport, p, hp_len); + hostport[hp_len] = '\0'; + + char *port_part = NULL; + char *colon = strrchr(hostport, ':'); + if (colon && strchr(hostport, ':') == colon) { + *colon = '\0'; + port_part = colon + 1; + } + + if (hostport[0] == '\0') return ESP_ERR_INVALID_ARG; + safe_copy(out->host, sizeof(out->host), hostport); + + if (port_part && port_part[0]) { + char *endp = NULL; + long port = strtol(port_part, &endp, 10); + if (!endp || *endp != '\0' || port <= 0 || port > 65535) { + return ESP_ERR_INVALID_ARG; + } + out->port = (int)port; + } + + /* Parse path (+ query), ignore fragment */ + const char *path = hp_end; + const char *frag = strchr(path, '#'); + size_t path_len = frag ? (size_t)(frag - path) : strlen(path); + + if (path_len == 0) { + safe_copy(out->path, sizeof(out->path), "/"); + } else if (path[0] == '?') { + if (path_len + 1 >= sizeof(out->path)) return ESP_ERR_INVALID_SIZE; + out->path[0] = '/'; + memcpy(out->path + 1, path, path_len); + out->path[path_len + 1] = '\0'; + } else { + if (path[0] != '/') return ESP_ERR_INVALID_ARG; + if (path_len >= sizeof(out->path)) return ESP_ERR_INVALID_SIZE; + memcpy(out->path, path, path_len); + out->path[path_len] = '\0'; + } + + safe_copy(out->url, sizeof(out->url), url); + return ESP_OK; +} + +static esp_err_t llm_endpoint_refresh(void) +{ + const char *url = NULL; + if (s_api_url_nvs[0] != '\0') { + url = s_api_url_nvs; + } else if (s_api_url_build[0] != '\0') { + url = s_api_url_build; + } else { + url = provider_default_url(); + } + + llm_endpoint_t ep; + esp_err_t err = llm_parse_endpoint_url(url, &ep); + if (err != ESP_OK) { + s_endpoint_valid = false; + memset(&s_endpoint, 0, sizeof(s_endpoint)); + safe_copy(s_endpoint.url, sizeof(s_endpoint.url), url); + return err; + } + + s_endpoint = ep; + s_endpoint_valid = true; + + if (!s_endpoint.use_tls) { + ESP_LOGW(TAG, "LLM endpoint is insecure HTTP: %s", s_endpoint.url); + } + return ESP_OK; +} + +const char *llm_get_api_url(void) { - return provider_is_openai() ? "api.openai.com" : "api.anthropic.com"; + return s_endpoint.url; } -static const char *llm_api_path(void) +bool llm_api_url_is_valid(void) { - return provider_is_openai() ? "/v1/chat/completions" : "/v1/messages"; + return s_endpoint_valid; } /* ── Init ─────────────────────────────────────────────────────── */ @@ -210,6 +355,9 @@ esp_err_t llm_proxy_init(void) if (MIMI_SECRET_API_KEY[0] != '\0') { safe_copy(s_api_key, sizeof(s_api_key), MIMI_SECRET_API_KEY); } + if (MIMI_SECRET_LLM_API_URL[0] != '\0') { + safe_copy(s_api_url_build, sizeof(s_api_url_build), MIMI_SECRET_LLM_API_URL); + } if (MIMI_SECRET_MODEL[0] != '\0') { safe_copy(s_model, sizeof(s_model), MIMI_SECRET_MODEL); } @@ -235,11 +383,28 @@ esp_err_t llm_proxy_init(void) if (nvs_get_str(nvs, MIMI_NVS_KEY_PROVIDER, provider_tmp, &len) == ESP_OK && provider_tmp[0]) { safe_copy(s_provider, sizeof(s_provider), provider_tmp); } + + char url_tmp[LLM_URL_MAX_LEN] = {0}; + len = sizeof(url_tmp); + if (nvs_get_str(nvs, MIMI_NVS_KEY_API_URL, url_tmp, &len) == ESP_OK && url_tmp[0]) { + safe_copy(s_api_url_nvs, sizeof(s_api_url_nvs), url_tmp); + } nvs_close(nvs); } + if (!provider_is_known()) { + ESP_LOGW(TAG, "Unknown provider '%s'. Expected: anthropic|openai|openai_compat", s_provider); + } + + esp_err_t url_err = llm_endpoint_refresh(); + if (url_err != ESP_OK) { + ESP_LOGE(TAG, "Invalid LLM endpoint URL: %s (use set_llm_url )", s_endpoint.url[0] ? s_endpoint.url : "(empty)"); + } + if (s_api_key[0]) { - ESP_LOGI(TAG, "LLM proxy initialized (provider: %s, model: %s)", s_provider, s_model); + ESP_LOGI(TAG, "LLM proxy initialized (provider: %s, model: %s, url: %s)", + s_provider, s_model, + s_endpoint_valid ? s_endpoint.url : "(invalid)"); } else { ESP_LOGW(TAG, "No API key. Use CLI: set_api_key "); } @@ -250,8 +415,10 @@ esp_err_t llm_proxy_init(void) static esp_err_t llm_http_direct(const char *post_data, resp_buf_t *rb, int *out_status) { + if (!s_endpoint_valid) return ESP_ERR_INVALID_STATE; + esp_http_client_config_t config = { - .url = llm_api_url(), + .url = s_endpoint.url, .event_handler = http_event_handler, .user_data = rb, .timeout_ms = 120 * 1000, @@ -287,10 +454,13 @@ static esp_err_t llm_http_direct(const char *post_data, resp_buf_t *rb, int *out static esp_err_t llm_http_via_proxy(const char *post_data, resp_buf_t *rb, int *out_status) { - proxy_conn_t *conn = proxy_conn_open(llm_api_host(), 443, 30000); - if (!conn) return ESP_ERR_HTTP_CONNECT; + if (!s_endpoint_valid) return ESP_ERR_INVALID_STATE; + + const char *path = (s_endpoint.path[0] != '\0') ? s_endpoint.path : "/"; + char host_hdr[LLM_HOST_MAX_LEN + 16] = {0}; + build_host_header_value(&s_endpoint, host_hdr, sizeof(host_hdr)); - int body_len = strlen(post_data); + int body_len = (int)strlen(post_data); char header[1024]; int hlen = 0; if (provider_is_openai()) { @@ -301,7 +471,7 @@ static esp_err_t llm_http_via_proxy(const char *post_data, resp_buf_t *rb, int * "Authorization: Bearer %s\r\n" "Content-Length: %d\r\n" "Connection: close\r\n\r\n", - llm_api_path(), llm_api_host(), s_api_key, body_len); + path, host_hdr, s_api_key, body_len); } else { hlen = snprintf(header, sizeof(header), "POST %s HTTP/1.1\r\n" @@ -311,23 +481,46 @@ static esp_err_t llm_http_via_proxy(const char *post_data, resp_buf_t *rb, int * "anthropic-version: %s\r\n" "Content-Length: %d\r\n" "Connection: close\r\n\r\n", - llm_api_path(), llm_api_host(), s_api_key, MIMI_LLM_API_VERSION, body_len); + path, host_hdr, s_api_key, MIMI_LLM_API_VERSION, body_len); } - if (proxy_conn_write(conn, header, hlen) < 0 || - proxy_conn_write(conn, post_data, body_len) < 0) { + if (s_endpoint.use_tls) { + proxy_conn_t *conn = proxy_conn_open(s_endpoint.host, s_endpoint.port, 30000); + if (!conn) return ESP_ERR_HTTP_CONNECT; + + if (proxy_conn_write(conn, header, hlen) < 0 || + proxy_conn_write(conn, post_data, body_len) < 0) { + proxy_conn_close(conn); + return ESP_ERR_HTTP_WRITE_DATA; + } + + /* Read full response into buffer */ + char tmp[4096]; + while (1) { + int n = proxy_conn_read(conn, tmp, sizeof(tmp), 120000); + if (n <= 0) break; + if (resp_buf_append(rb, tmp, n) != ESP_OK) break; + } proxy_conn_close(conn); - return ESP_ERR_HTTP_WRITE_DATA; - } + } else { + int sock = proxy_tunnel_open(s_endpoint.host, s_endpoint.port, 30000); + if (sock < 0) return ESP_ERR_HTTP_CONNECT; - /* Read full response into buffer */ - char tmp[4096]; - while (1) { - int n = proxy_conn_read(conn, tmp, sizeof(tmp), 120000); - if (n <= 0) break; - if (resp_buf_append(rb, tmp, n) != ESP_OK) break; + if (proxy_tunnel_write(sock, header, hlen) < 0 || + proxy_tunnel_write(sock, post_data, body_len) < 0) { + proxy_tunnel_close(sock); + return ESP_ERR_HTTP_WRITE_DATA; + } + + /* Read full response into buffer */ + char tmp[4096]; + while (1) { + int n = proxy_tunnel_read(sock, tmp, sizeof(tmp), 120000); + if (n <= 0) break; + if (resp_buf_append(rb, tmp, (size_t)n) != ESP_OK) break; + } + proxy_tunnel_close(sock); } - proxy_conn_close(conn); /* Parse status line */ *out_status = 0; @@ -555,12 +748,14 @@ esp_err_t llm_chat_tools(const char *system_prompt, memset(resp, 0, sizeof(*resp)); if (s_api_key[0] == '\0') return ESP_ERR_INVALID_STATE; + if (!provider_is_known()) return ESP_ERR_INVALID_ARG; + if (!s_endpoint_valid) return ESP_ERR_INVALID_STATE; /* Build request body (non-streaming) */ cJSON *body = cJSON_CreateObject(); cJSON_AddStringToObject(body, "model", s_model); if (provider_is_openai()) { - cJSON_AddNumberToObject(body, "max_completion_tokens", MIMI_LLM_MAX_TOKENS); + cJSON_AddNumberToObject(body, "max_tokens", MIMI_LLM_MAX_TOKENS); } else { cJSON_AddNumberToObject(body, "max_tokens", MIMI_LLM_MAX_TOKENS); } @@ -784,6 +979,43 @@ esp_err_t llm_set_api_key(const char *api_key) return ESP_OK; } +esp_err_t llm_set_api_url(const char *url) +{ + if (!url || !url[0]) return ESP_ERR_INVALID_ARG; + + llm_endpoint_t ep; + esp_err_t err = llm_parse_endpoint_url(url, &ep); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Invalid LLM URL: %s (expected http[s]://host[:port]/path)", url); + return ESP_ERR_INVALID_ARG; + } + + nvs_handle_t nvs; + ESP_ERROR_CHECK(nvs_open(MIMI_NVS_LLM, NVS_READWRITE, &nvs)); + ESP_ERROR_CHECK(nvs_set_str(nvs, MIMI_NVS_KEY_API_URL, url)); + ESP_ERROR_CHECK(nvs_commit(nvs)); + nvs_close(nvs); + + safe_copy(s_api_url_nvs, sizeof(s_api_url_nvs), url); + (void)llm_endpoint_refresh(); + ESP_LOGI(TAG, "LLM URL set to: %s", s_endpoint.url); + return ESP_OK; +} + +esp_err_t llm_clear_api_url(void) +{ + nvs_handle_t nvs; + ESP_ERROR_CHECK(nvs_open(MIMI_NVS_LLM, NVS_READWRITE, &nvs)); + nvs_erase_key(nvs, MIMI_NVS_KEY_API_URL); + ESP_ERROR_CHECK(nvs_commit(nvs)); + nvs_close(nvs); + + s_api_url_nvs[0] = '\0'; + (void)llm_endpoint_refresh(); + ESP_LOGI(TAG, "LLM URL override cleared (url: %s)", s_endpoint.url); + return ESP_OK; +} + esp_err_t llm_set_model(const char *model) { nvs_handle_t nvs; @@ -799,6 +1031,14 @@ esp_err_t llm_set_model(const char *model) esp_err_t llm_set_provider(const char *provider) { + if (!provider || !provider[0]) return ESP_ERR_INVALID_ARG; + if (strcmp(provider, "openai") != 0 && + strcmp(provider, "openai_compat") != 0 && + strcmp(provider, "anthropic") != 0) { + ESP_LOGE(TAG, "Invalid provider: %s (expected anthropic|openai|openai_compat)", provider); + return ESP_ERR_INVALID_ARG; + } + nvs_handle_t nvs; ESP_ERROR_CHECK(nvs_open(MIMI_NVS_LLM, NVS_READWRITE, &nvs)); ESP_ERROR_CHECK(nvs_set_str(nvs, MIMI_NVS_KEY_PROVIDER, provider)); @@ -806,6 +1046,7 @@ esp_err_t llm_set_provider(const char *provider) nvs_close(nvs); safe_copy(s_provider, sizeof(s_provider), provider); + (void)llm_endpoint_refresh(); ESP_LOGI(TAG, "Provider set to: %s", s_provider); return ESP_OK; } diff --git a/main/llm/llm_proxy.h b/main/llm/llm_proxy.h index b667f624..7cef99da 100644 --- a/main/llm/llm_proxy.h +++ b/main/llm/llm_proxy.h @@ -12,11 +12,35 @@ */ esp_err_t llm_proxy_init(void); +/** + * Get the active LLM endpoint URL (may be empty if not configured). + * + * Note: this returns the resolved runtime URL (override or provider default). + */ +const char *llm_get_api_url(void); + +/** Returns true if the active LLM endpoint URL parsed successfully. */ +bool llm_api_url_is_valid(void); + /** * Save the LLM API key to NVS. */ esp_err_t llm_set_api_key(const char *api_key); +/** + * Save the LLM endpoint URL to NVS. Must include scheme/host/path. + * + * Examples: + * - https://api.openai.com/v1/chat/completions + * - http://192.168.1.10:8000/v1/chat/completions + */ +esp_err_t llm_set_api_url(const char *url); + +/** + * Clear the LLM endpoint URL override from NVS (revert to provider default). + */ +esp_err_t llm_clear_api_url(void); + /** * Save the LLM provider to NVS. (e.g. "anthropic", "openai") */ diff --git a/main/mimi_config.h b/main/mimi_config.h index c081cdac..0add33a8 100644 --- a/main/mimi_config.h +++ b/main/mimi_config.h @@ -19,6 +19,9 @@ #ifndef MIMI_SECRET_API_KEY #define MIMI_SECRET_API_KEY "" #endif +#ifndef MIMI_SECRET_LLM_API_URL +#define MIMI_SECRET_LLM_API_URL "" +#endif #ifndef MIMI_SECRET_MODEL #define MIMI_SECRET_MODEL "" #endif @@ -206,6 +209,7 @@ #define MIMI_NVS_KEY_FEISHU_APP_SECRET "app_secret" #define MIMI_NVS_KEY_API_KEY "api_key" #define MIMI_NVS_KEY_TAVILY_KEY "tavily_key" +#define MIMI_NVS_KEY_API_URL "api_url" #define MIMI_NVS_KEY_MODEL "model" #define MIMI_NVS_KEY_PROVIDER "provider" #define MIMI_NVS_KEY_PROXY_HOST "host" diff --git a/main/mimi_secrets.h.example b/main/mimi_secrets.h.example index c7cc3400..9a8f1966 100644 --- a/main/mimi_secrets.h.example +++ b/main/mimi_secrets.h.example @@ -21,8 +21,9 @@ #define MIMI_SECRET_FEISHU_APP_ID "" #define MIMI_SECRET_FEISHU_APP_SECRET "" -/* Anthropic API */ +/* LLM */ #define MIMI_SECRET_API_KEY "" +#define MIMI_SECRET_LLM_API_URL "" /* optional: full URL incl scheme/host/port/path */ #define MIMI_SECRET_MODEL "" #define MIMI_SECRET_MODEL_PROVIDER "anthropic" diff --git a/main/proxy/http_proxy.c b/main/proxy/http_proxy.c index fdb75541..3745144d 100644 --- a/main/proxy/http_proxy.c +++ b/main/proxy/http_proxy.c @@ -104,6 +104,61 @@ bool http_proxy_is_enabled(void) return s_proxy_host[0] != '\0' && s_proxy_port != 0; } +/* ── Raw tunnels (no TLS) ────────────────────────────────────── */ + +static int open_connect_tunnel(const char *host, int port, int timeout_ms); +static int open_socks5_tunnel(const char *host, int port, int timeout_ms); + +int proxy_tunnel_open(const char *host, int port, int timeout_ms) +{ + if (!http_proxy_is_enabled()) { + ESP_LOGE(TAG, "proxy_tunnel_open called but no proxy configured"); + return -1; + } + + if (!host || !host[0] || port <= 0 || port > 65535) { + ESP_LOGE(TAG, "proxy_tunnel_open invalid target"); + return -1; + } + + if (strcmp(s_proxy_type, "socks5") == 0) { + return open_socks5_tunnel(host, port, timeout_ms); + } + return open_connect_tunnel(host, port, timeout_ms); +} + +int proxy_tunnel_write(int sock, const char *data, int len) +{ + if (sock < 0 || !data || len <= 0) return -1; + + int written = 0; + while (written < len) { + int n = send(sock, data + written, len - written, 0); + if (n <= 0) return -1; + written += n; + } + return written; +} + +int proxy_tunnel_read(int sock, char *buf, int len, int timeout_ms) +{ + if (sock < 0 || !buf || len <= 0) return -1; + + struct timeval tv = { .tv_sec = timeout_ms / 1000, .tv_usec = (timeout_ms % 1000) * 1000 }; + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + + int n = recv(sock, buf, len, 0); + if (n < 0) return -1; + return n; +} + +void proxy_tunnel_close(int sock) +{ + if (sock >= 0) { + close(sock); + } +} + /* ── Proxied TLS connection ───────────────────────────────────── */ struct proxy_conn { diff --git a/main/proxy/http_proxy.h b/main/proxy/http_proxy.h index f324700e..382dc742 100644 --- a/main/proxy/http_proxy.h +++ b/main/proxy/http_proxy.h @@ -24,6 +24,27 @@ esp_err_t http_proxy_set(const char *host, uint16_t port, const char *type); */ esp_err_t http_proxy_clear(void); +/* ── Proxy tunnels (no TLS) ──────────────────────────────────── */ + +/** + * Open a raw TCP tunnel to target host:port through the configured proxy. + * + * - If proxy type is "http": uses HTTP CONNECT + * - If proxy type is "socks5": uses SOCKS5 CONNECT + * + * Returns a socket fd on success, or -1 on failure. + */ +int proxy_tunnel_open(const char *host, int port, int timeout_ms); + +/** Write raw bytes through the tunnel. Returns bytes written or -1. */ +int proxy_tunnel_write(int sock, const char *data, int len); + +/** Read raw bytes from the tunnel. Returns bytes read or -1. */ +int proxy_tunnel_read(int sock, char *buf, int len, int timeout_ms); + +/** Close the tunnel socket. */ +void proxy_tunnel_close(int sock); + /* ── Proxied HTTPS connection ─────────────────────────────────── */ typedef struct proxy_conn proxy_conn_t; diff --git a/sdkconfig.defaults.esp32s3 b/sdkconfig.defaults.esp32s3 index 4774cd93..eed91926 100644 --- a/sdkconfig.defaults.esp32s3 +++ b/sdkconfig.defaults.esp32s3 @@ -2,7 +2,7 @@ CONFIG_IDF_TARGET="esp32s3" # Flash 16MB + QIO -CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y CONFIG_ESPTOOLPY_FLASHMODE_QIO=y # CPU 240MHz From 73e05736e6967825628e7d8f3aaee98f7cf7926c Mon Sep 17 00:00:00 2001 From: stringwind Date: Mon, 9 Mar 2026 22:24:07 +0800 Subject: [PATCH 4/9] refactor(wip): improve code structure --- main/agent/agent_loop.c | 2 +- main/mimi.c | 81 ++- main/mimi_config.h | 2 +- main/voice/voice_channel.c | 1402 +++++++++++++++++++++++------------- 4 files changed, 945 insertions(+), 542 deletions(-) diff --git a/main/agent/agent_loop.c b/main/agent/agent_loop.c index 7e5eae64..d8d45390 100644 --- a/main/agent/agent_loop.c +++ b/main/agent/agent_loop.c @@ -218,7 +218,7 @@ static void agent_loop_task(void *arg) while (iteration < MIMI_AGENT_MAX_TOOL_ITER) { /* Send "working" indicator before each API call */ #if MIMI_AGENT_SEND_WORKING_STATUS - if (!sent_working_status && strcmp(msg.channel, MIMI_CHAN_SYSTEM) != 0) { + if (!sent_working_status && strcmp(msg.channel, MIMI_CHAN_SYSTEM) != 0 && strcmp(msg.channel, MIMI_CHAN_VOICE) != 0) { mimi_msg_t status = {0}; strncpy(status.channel, msg.channel, sizeof(status.channel) - 1); strncpy(status.chat_id, msg.chat_id, sizeof(status.chat_id) - 1); diff --git a/main/mimi.c b/main/mimi.c index ec9fe77c..246c3db8 100644 --- a/main/mimi.c +++ b/main/mimi.c @@ -61,46 +61,73 @@ static esp_err_t init_spiffs(void) return ESP_OK; } - +static void voice_speak_task(void *arg) +{ + char *text = (char *)arg; + if (text) { + esp_err_t err = voice_channel_speak_text(text); + if (err != ESP_OK) { + ESP_LOGW(TAG, "Voice playback failed: %s", esp_err_to_name(err)); + } + free(text); + } + vTaskDelete(NULL); +} /* Outbound dispatch task: reads from outbound queue and routes to channels */ static void outbound_dispatch_task(void *arg) { - ESP_LOGI(TAG, "Outbound dispatch started"); + (void)arg; + ESP_LOGI(TAG, "Outbound dispatch started on core %d", xPortGetCoreID()); while (1) { - mimi_msg_t msg; - if (message_bus_pop_outbound(&msg, UINT32_MAX) != ESP_OK) continue; + mimi_msg_t msg = {0}; + if (message_bus_pop_outbound(&msg, UINT32_MAX) != ESP_OK) { + continue; + } + + ESP_LOGI(TAG, "Dispatching response to %s:%s", + msg.channel[0] ? msg.channel : "(unknown)", + msg.chat_id[0] ? msg.chat_id : "(empty)"); - ESP_LOGI(TAG, "Dispatching response to %s:%s", msg.channel, msg.chat_id); + if (!msg.content || !msg.content[0]) { + free(msg.content); + continue; + } if (strcmp(msg.channel, MIMI_CHAN_TELEGRAM) == 0) { - esp_err_t send_err = telegram_send_message(msg.chat_id, msg.content); - if (send_err != ESP_OK) { - ESP_LOGE(TAG, "Telegram send failed for %s: %s", msg.chat_id, esp_err_to_name(send_err)); - } else { - ESP_LOGI(TAG, "Telegram send success for %s (%d bytes)", msg.chat_id, (int)strlen(msg.content)); - } + telegram_send_message(msg.chat_id, msg.content); + } else if (strcmp(msg.channel, MIMI_CHAN_FEISHU) == 0) { - esp_err_t send_err = feishu_send_message(msg.chat_id, msg.content); - if (send_err != ESP_OK) { - ESP_LOGE(TAG, "Feishu send failed for %s: %s", msg.chat_id, esp_err_to_name(send_err)); - } else { - ESP_LOGI(TAG, "Feishu send success for %s (%d bytes)", msg.chat_id, (int)strlen(msg.content)); - } + feishu_send_message(msg.chat_id, msg.content); + } else if (strcmp(msg.channel, MIMI_CHAN_WEBSOCKET) == 0) { - esp_err_t ws_err = ws_server_send(msg.chat_id, msg.content); - if (ws_err != ESP_OK) { - ESP_LOGW(TAG, "WS send failed for %s: %s", msg.chat_id, esp_err_to_name(ws_err)); - } + ws_server_send(msg.chat_id, msg.content); + } else if (strcmp(msg.channel, MIMI_CHAN_VOICE) == 0) { - esp_err_t voice_err = voice_channel_speak_text(msg.content); - if (voice_err != ESP_OK) { - ESP_LOGW(TAG, "Voice playback failed: %s", esp_err_to_name(voice_err)); + char *copy = strdup(msg.content); + if (!copy) { + ESP_LOGW(TAG, "No memory for voice speak task"); + } else { + BaseType_t ok = xTaskCreatePinnedToCore( + voice_speak_task, + "voice_speak", + 12 * 1024, + copy, + 5, + NULL, + 0 + ); + if (ok != pdPASS) { + ESP_LOGW(TAG, "Failed to create voice_speak task"); + free(copy); + } } - } else if (strcmp(msg.channel, MIMI_CHAN_SYSTEM) == 0) { - ESP_LOGI(TAG, "System message [%s]: %.128s", msg.chat_id, msg.content); + + } else if (strcmp(msg.channel, MIMI_CHAN_CLI) == 0) { + printf("\n%s\n", msg.content); + } else { - ESP_LOGW(TAG, "Unknown channel: %s", msg.channel); + ESP_LOGW(TAG, "Unknown outbound channel: %s", msg.channel); } free(msg.content); diff --git a/main/mimi_config.h b/main/mimi_config.h index 0add33a8..f6d0f062 100644 --- a/main/mimi_config.h +++ b/main/mimi_config.h @@ -78,7 +78,7 @@ #define MIMI_QWEN_STT_URL "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions" #define MIMI_QWEN_STT_MODEL "qwen3-asr-flash" #define MIMI_QWEN_TTS_URL "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation" -#define MIMI_QWEN_TTS_MODEL "qwen-tts-latest" +#define MIMI_QWEN_TTS_MODEL "qwen3-tts-flash" /* WiFi */ #define MIMI_WIFI_MAX_RETRY 10 diff --git a/main/voice/voice_channel.c b/main/voice/voice_channel.c index 8a46f3cb..4bd24113 100644 --- a/main/voice/voice_channel.c +++ b/main/voice/voice_channel.c @@ -3,17 +3,21 @@ #include #include #include +#include +#include #include +#include #include "mimi_config.h" #include "bus/message_bus.h" +#include "proxy/http_proxy.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "freertos/queue.h" #include "freertos/semphr.h" #include "esp_log.h" +#include "esp_err.h" #include "esp_http_client.h" #include "esp_crt_bundle.h" #include "esp_heap_caps.h" @@ -24,34 +28,198 @@ static const char *TAG = "voice"; +/* ========================= + * Fallback config defaults + * ========================= */ + +#ifndef MIMI_VOICE_ENABLED_DEFAULT +#define MIMI_VOICE_ENABLED_DEFAULT 0 +#endif + +#ifndef MIMI_VOICE_CHAT_ID +#define MIMI_VOICE_CHAT_ID "voice_local" +#endif + +#ifndef MIMI_VOICE_SAMPLE_RATE +#define MIMI_VOICE_SAMPLE_RATE 16000 +#endif + +#ifndef MIMI_VOICE_FRAME_MS +#define MIMI_VOICE_FRAME_MS 20 +#endif + +#ifndef MIMI_VOICE_MAX_UTTERANCE_MS +#define MIMI_VOICE_MAX_UTTERANCE_MS 10000 +#endif + +#ifndef MIMI_VOICE_SILENCE_END_MS +#define MIMI_VOICE_SILENCE_END_MS 600 +#endif + +#ifndef MIMI_VOICE_VAD_THRESHOLD +#define MIMI_VOICE_VAD_THRESHOLD 700 +#endif + +#ifndef MIMI_VOICE_CAPTURE_STACK +#define MIMI_VOICE_CAPTURE_STACK (8 * 1024) +#endif + +#ifndef MIMI_VOICE_TASK_PRIO +#define MIMI_VOICE_TASK_PRIO 5 +#endif + +#ifndef MIMI_VOICE_CORE +#define MIMI_VOICE_CORE 0 +#endif + +#ifndef MIMI_SECRET_STT_URL +#define MIMI_SECRET_STT_URL "" +#endif + +#ifndef MIMI_SECRET_STT_API_KEY +#define MIMI_SECRET_STT_API_KEY "" +#endif + +#ifndef MIMI_SECRET_STT_MODEL +#define MIMI_SECRET_STT_MODEL "qwen3-asr-flash" +#endif + +#ifndef MIMI_SECRET_TTS_URL +#define MIMI_SECRET_TTS_URL "" +#endif + +#ifndef MIMI_SECRET_TTS_API_KEY +#define MIMI_SECRET_TTS_API_KEY "" +#endif + +#ifndef MIMI_SECRET_TTS_MODEL +#define MIMI_SECRET_TTS_MODEL "qwen3-tts-flash" +#endif + +#ifndef MIMI_SECRET_TTS_VOICE +#define MIMI_SECRET_TTS_VOICE "Cherry" +#endif + +#ifndef MIMI_SECRET_TTS_LANGUAGE +#define MIMI_SECRET_TTS_LANGUAGE "Chinese" +#endif + +#ifndef MIMI_SECRET_API_KEY +#define MIMI_SECRET_API_KEY "" +#endif + +#ifndef MIMI_VOICE_I2S_PORT +#define MIMI_VOICE_I2S_PORT 0 +#endif + +#ifndef MIMI_VOICE_I2S_BCLK +#define MIMI_VOICE_I2S_BCLK 42 +#endif + +#ifndef MIMI_VOICE_I2S_WS +#define MIMI_VOICE_I2S_WS 41 +#endif + +#ifndef MIMI_VOICE_I2S_DIN +#define MIMI_VOICE_I2S_DIN 40 +#endif + +#ifndef MIMI_VOICE_I2S_DOUT +#define MIMI_VOICE_I2S_DOUT 39 +#endif + +/* XVF3800 fixed digital format in your current design: + * 16 kHz, stereo, 32-bit samples over I2S. + */ +#define VOICE_I2S_CHANNELS 2 +#define VOICE_I2S_BYTES_PER_SAMPLE 4 +#define VOICE_I2S_BYTES_PER_STEREO_FRAME (VOICE_I2S_CHANNELS * VOICE_I2S_BYTES_PER_SAMPLE) +#define VOICE_PCM_BITS 16 + typedef struct { char *buf; size_t len; size_t cap; } http_resp_t; +typedef struct { + uint16_t audio_format; /* 1 = PCM */ + uint16_t channels; + uint32_t sample_rate; + uint16_t bits_per_sample; +} wav_fmt_t; + static bool s_enabled = false; static bool s_i2s_ready = false; static volatile bool s_is_playing = false; static i2s_chan_handle_t s_tx_chan = NULL; static i2s_chan_handle_t s_rx_chan = NULL; - static TaskHandle_t s_capture_task = NULL; static SemaphoreHandle_t s_http_lock = NULL; +/* ========================= + * Secrets / config helpers + * ========================= */ + +static const char *stt_api_url(void) +{ + return (MIMI_SECRET_STT_URL[0] != '\0') ? MIMI_SECRET_STT_URL : ""; +} + +static const char *stt_api_key(void) +{ + return (MIMI_SECRET_STT_API_KEY[0] != '\0') ? MIMI_SECRET_STT_API_KEY : + (MIMI_SECRET_API_KEY[0] != '\0') ? MIMI_SECRET_API_KEY : ""; +} + +static const char *stt_model(void) +{ + return (MIMI_SECRET_STT_MODEL[0] != '\0') ? MIMI_SECRET_STT_MODEL : "qwen3-asr-flash"; +} + +static const char *tts_api_url(void) +{ + return (MIMI_SECRET_TTS_URL[0] != '\0') ? MIMI_SECRET_TTS_URL : ""; +} + +static const char *tts_api_key(void) +{ + return (MIMI_SECRET_TTS_API_KEY[0] != '\0') ? MIMI_SECRET_TTS_API_KEY : + (MIMI_SECRET_API_KEY[0] != '\0') ? MIMI_SECRET_API_KEY : ""; +} + +static const char *tts_model(void) +{ + return (MIMI_SECRET_TTS_MODEL[0] != '\0') ? MIMI_SECRET_TTS_MODEL : "qwen3-tts-flash"; +} + +static const char *tts_voice(void) +{ + return (MIMI_SECRET_TTS_VOICE[0] != '\0') ? MIMI_SECRET_TTS_VOICE : "Cherry"; +} + +static const char *tts_language(void) +{ + return (MIMI_SECRET_TTS_LANGUAGE[0] != '\0') ? MIMI_SECRET_TTS_LANGUAGE : "Chinese"; +} + +/* ========================= + * HTTP helpers + * ========================= */ + static esp_err_t http_event_handler(esp_http_client_event_t *evt) { http_resp_t *resp = (http_resp_t *)evt->user_data; - if (evt->event_id != HTTP_EVENT_ON_DATA || !resp) { + if (evt->event_id != HTTP_EVENT_ON_DATA || !resp || !evt->data || evt->data_len <= 0) { return ESP_OK; } - if (resp->len + evt->data_len + 1 > resp->cap) { - size_t new_cap = resp->cap ? resp->cap * 2 : 4096; - size_t needed = resp->len + evt->data_len + 1; - if (new_cap < needed) { - new_cap = needed; + size_t need = resp->len + (size_t)evt->data_len + 1; + if (need > resp->cap) { + size_t new_cap = resp->cap ? resp->cap * 2 : 1024; + while (new_cap < need) { + new_cap *= 2; } char *tmp = realloc(resp->buf, new_cap); if (!tmp) { @@ -62,726 +230,922 @@ static esp_err_t http_event_handler(esp_http_client_event_t *evt) } memcpy(resp->buf + resp->len, evt->data, evt->data_len); - resp->len += evt->data_len; + resp->len += (size_t)evt->data_len; resp->buf[resp->len] = '\0'; return ESP_OK; } -static esp_err_t i2s_init_xvf3800(void) +static esp_err_t http_post_json(const char *url, + const char *bearer_key, + const char *json_body, + bool enable_sse, + http_resp_t *resp, + int *http_status_out) { - if (MIMI_VOICE_I2S_BCLK < 0 || MIMI_VOICE_I2S_WS < 0 || - MIMI_VOICE_I2S_DIN < 0 || MIMI_VOICE_I2S_DOUT < 0) { - ESP_LOGW(TAG, "Voice disabled: configure I2S pins in mimi_secrets.h"); - return ESP_ERR_INVALID_STATE; + if (!url || !url[0] || !json_body || !resp) { + return ESP_ERR_INVALID_ARG; } - i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)MIMI_VOICE_I2S_PORT, - I2S_ROLE_MASTER); - esp_err_t err = i2s_new_channel(&chan_cfg, &s_tx_chan, &s_rx_chan); - if (err != ESP_OK) { - ESP_LOGE(TAG, "i2s_new_channel failed: %s", esp_err_to_name(err)); - return err; - } + memset(resp, 0, sizeof(*resp)); - i2s_std_config_t std_cfg = { - .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(MIMI_VOICE_SAMPLE_RATE), - .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), - .gpio_cfg = { - .mclk = I2S_GPIO_UNUSED, - .bclk = MIMI_VOICE_I2S_BCLK, - .ws = MIMI_VOICE_I2S_WS, - .dout = MIMI_VOICE_I2S_DOUT, - .din = MIMI_VOICE_I2S_DIN, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false, - }, - }, + esp_http_client_config_t cfg = { + .url = url, + .method = HTTP_METHOD_POST, + .event_handler = http_event_handler, + .user_data = resp, + .crt_bundle_attach = esp_crt_bundle_attach, + .timeout_ms = 30000, + .buffer_size = 2048, + .buffer_size_tx = 2048, }; - err = i2s_channel_init_std_mode(s_rx_chan, &std_cfg); - if (err != ESP_OK) { - ESP_LOGE(TAG, "i2s_channel_init_std_mode(rx) failed: %s", esp_err_to_name(err)); - return err; + esp_http_client_handle_t client = esp_http_client_init(&cfg); + if (!client) { + return ESP_FAIL; } - err = i2s_channel_init_std_mode(s_tx_chan, &std_cfg); - if (err != ESP_OK) { - ESP_LOGE(TAG, "i2s_channel_init_std_mode(tx) failed: %s", esp_err_to_name(err)); - return err; + + esp_http_client_set_header(client, "Content-Type", "application/json"); + if (bearer_key && bearer_key[0]) { + char auth[320]; + snprintf(auth, sizeof(auth), "Bearer %s", bearer_key); + esp_http_client_set_header(client, "Authorization", auth); } + esp_http_client_set_header(client, "X-DashScope-SSE", enable_sse ? "enable" : "disable"); + esp_http_client_set_post_field(client, json_body, (int)strlen(json_body)); - ESP_ERROR_CHECK(i2s_channel_enable(s_rx_chan)); - ESP_ERROR_CHECK(i2s_channel_enable(s_tx_chan)); + esp_err_t err = esp_http_client_perform(client); + if (err == ESP_OK && http_status_out) { + *http_status_out = esp_http_client_get_status_code(client); + } - s_i2s_ready = true; - ESP_LOGI(TAG, "I2S ready for XVF3800: %d Hz mono s16", MIMI_VOICE_SAMPLE_RATE); - return ESP_OK; + esp_http_client_cleanup(client); + return err; } -static int frame_avg_abs_energy(const int16_t *samples, size_t sample_count) +static esp_err_t http_get_binary(const char *url, http_resp_t *resp, int *http_status_out) { - if (!samples || sample_count == 0) { - return 0; + if (!url || !url[0] || !resp) { + return ESP_ERR_INVALID_ARG; } - uint64_t sum = 0; - for (size_t i = 0; i < sample_count; i++) { - int v = samples[i]; - if (v < 0) { - v = -v; - } - sum += (uint32_t)v; + memset(resp, 0, sizeof(*resp)); + + esp_http_client_config_t cfg = { + .url = url, + .method = HTTP_METHOD_GET, + .event_handler = http_event_handler, + .user_data = resp, + .crt_bundle_attach = esp_crt_bundle_attach, + .timeout_ms = 30000, + .buffer_size = 2048, + .buffer_size_tx = 1024, + }; + + esp_http_client_handle_t client = esp_http_client_init(&cfg); + if (!client) { + return ESP_FAIL; } - return (int)(sum / sample_count); -} -static const char *stt_api_url(void) -{ - return MIMI_SECRET_STT_URL[0] ? MIMI_SECRET_STT_URL : MIMI_QWEN_STT_URL; -} + esp_err_t err = esp_http_client_perform(client); + if (err == ESP_OK && http_status_out) { + *http_status_out = esp_http_client_get_status_code(client); + } -static const char *stt_model(void) -{ - return MIMI_SECRET_STT_MODEL[0] ? MIMI_SECRET_STT_MODEL : MIMI_QWEN_STT_MODEL; + esp_http_client_cleanup(client); + return err; } -static const char *tts_api_url(void) -{ - return MIMI_SECRET_TTS_URL[0] ? MIMI_SECRET_TTS_URL : MIMI_QWEN_TTS_URL; -} +/* ========================= + * Audio helpers + * ========================= */ -static const char *tts_model(void) +static void pcm_s32_stereo_to_s16_mono(const uint8_t *src, size_t src_len, int16_t *dst, size_t *out_samples) { - return MIMI_SECRET_TTS_MODEL[0] ? MIMI_SECRET_TTS_MODEL : MIMI_QWEN_TTS_MODEL; + size_t frames = src_len / VOICE_I2S_BYTES_PER_STEREO_FRAME; + const int32_t *p = (const int32_t *)src; + + for (size_t i = 0; i < frames; i++) { + int32_t l = p[i * 2 + 0]; + int32_t r = p[i * 2 + 1]; + + /* XVF3800 / many I2S MEMS frontends deliver valid audio in high 16 bits of s32 slot. */ + int16_t ls = (int16_t)(l >> 16); + int16_t rs = (int16_t)(r >> 16); + int32_t mono = ((int32_t)ls + (int32_t)rs) / 2; + + if (mono > INT16_MAX) mono = INT16_MAX; + if (mono < INT16_MIN) mono = INT16_MIN; + dst[i] = (int16_t)mono; + } + + if (out_samples) { + *out_samples = frames; + } } -static const char *stt_api_key(void) +static uint32_t pcm_energy_absavg(const int16_t *pcm, size_t samples) { - if (MIMI_SECRET_STT_API_KEY[0]) { - return MIMI_SECRET_STT_API_KEY; + if (!pcm || samples == 0) return 0; + + uint64_t sum = 0; + for (size_t i = 0; i < samples; i++) { + int32_t v = pcm[i]; + if (v < 0) v = -v; + sum += (uint32_t)v; } - return MIMI_SECRET_API_KEY; + return (uint32_t)(sum / samples); } -static const char *tts_api_key(void) +static size_t wav_build_from_pcm16(const int16_t *pcm, + size_t pcm_bytes, + uint32_t sample_rate, + uint16_t channels, + uint8_t **out_buf) { - if (MIMI_SECRET_TTS_API_KEY[0]) { - return MIMI_SECRET_TTS_API_KEY; + if (!pcm || !out_buf || pcm_bytes == 0) { + return 0; } - return MIMI_SECRET_API_KEY; + + const size_t wav_size = 44 + pcm_bytes; + uint8_t *buf = (uint8_t *)malloc(wav_size); + if (!buf) { + return 0; + } + + const uint32_t byte_rate = sample_rate * channels * 2; + const uint16_t block_align = channels * 2; + const uint32_t riff_size = (uint32_t)(wav_size - 8); + const uint32_t data_size = (uint32_t)pcm_bytes; + + memcpy(buf + 0, "RIFF", 4); + memcpy(buf + 4, &riff_size, 4); + memcpy(buf + 8, "WAVE", 4); + + memcpy(buf + 12, "fmt ", 4); + uint32_t fmt_size = 16; + uint16_t audio_format = 1; + uint16_t bits_per_sample = 16; + memcpy(buf + 16, &fmt_size, 4); + memcpy(buf + 20, &audio_format, 2); + memcpy(buf + 22, &channels, 2); + memcpy(buf + 24, &sample_rate, 4); + memcpy(buf + 28, &byte_rate, 4); + memcpy(buf + 32, &block_align, 2); + memcpy(buf + 34, &bits_per_sample, 2); + + memcpy(buf + 36, "data", 4); + memcpy(buf + 40, &data_size, 4); + memcpy(buf + 44, pcm, pcm_bytes); + + *out_buf = buf; + return wav_size; } -static esp_err_t http_post_json(const char *url, const char *api_key, const char *json, - const char *accept, char **out_body, int *out_status) +static esp_err_t wav_find_data_chunk(const uint8_t *wav, + size_t wav_len, + wav_fmt_t *fmt, + const uint8_t **data_out, + size_t *data_len_out) { - if (!url || !json || !out_body || !out_status) { + if (!wav || wav_len < 44 || !fmt || !data_out || !data_len_out) { return ESP_ERR_INVALID_ARG; } - *out_body = NULL; - *out_status = 0; - http_resp_t resp = { - .buf = calloc(1, 4096), - .len = 0, - .cap = 4096, - }; - if (!resp.buf) { - return ESP_ERR_NO_MEM; + if (memcmp(wav, "RIFF", 4) != 0 || memcmp(wav + 8, "WAVE", 4) != 0) { + return ESP_ERR_INVALID_RESPONSE; } - esp_http_client_config_t cfg = { - .url = url, - .event_handler = http_event_handler, - .user_data = &resp, - .timeout_ms = 30000, - .buffer_size = 2048, - .buffer_size_tx = 2048, - .crt_bundle_attach = esp_crt_bundle_attach, - }; - esp_http_client_handle_t client = esp_http_client_init(&cfg); - if (!client) { - free(resp.buf); - return ESP_FAIL; + memset(fmt, 0, sizeof(*fmt)); + *data_out = NULL; + *data_len_out = 0; + + size_t pos = 12; + bool got_fmt = false; + + while (pos + 8 <= wav_len) { + const uint8_t *chunk = wav + pos; + uint32_t chunk_size = 0; + memcpy(&chunk_size, chunk + 4, 4); + + size_t chunk_data_pos = pos + 8; + if (chunk_data_pos > wav_len) { + break; + } + + size_t available = wav_len - chunk_data_pos; + size_t declared = (size_t)chunk_size; + size_t actual = declared <= available ? declared : available; + + char id[5] = {0}; + memcpy(id, chunk, 4); + ESP_LOGI(TAG, "WAV chunk id=%s declared=%u actual=%u pos=%u", + id, (unsigned)chunk_size, (unsigned)actual, (unsigned)pos); + + if (memcmp(chunk, "fmt ", 4) == 0) { + if (actual < 16) { + ESP_LOGW(TAG, "WAV fmt chunk too short: %u", (unsigned)actual); + } else { + memcpy(&fmt->audio_format, wav + chunk_data_pos + 0, 2); + memcpy(&fmt->channels, wav + chunk_data_pos + 2, 2); + memcpy(&fmt->sample_rate, wav + chunk_data_pos + 4, 4); + memcpy(&fmt->bits_per_sample, wav + chunk_data_pos + 14, 2); + got_fmt = true; + } + } else if (memcmp(chunk, "data", 4) == 0) { + *data_out = wav + chunk_data_pos; + *data_len_out = actual; + break; + } + + size_t step = 8 + actual; + if (declared <= available) { + step += (declared & 1U); + } + + if (step == 0 || pos + step <= pos) { + break; + } + pos += step; } - esp_http_client_set_method(client, HTTP_METHOD_POST); - esp_http_client_set_header(client, "Content-Type", "application/json"); - if (accept && accept[0]) { - esp_http_client_set_header(client, "Accept", accept); + if (!*data_out || *data_len_out == 0) { + return ESP_ERR_NOT_FOUND; } - if (api_key && api_key[0]) { - char auth[256]; - snprintf(auth, sizeof(auth), "Bearer %s", api_key); - esp_http_client_set_header(client, "Authorization", auth); + + if (!got_fmt) { + ESP_LOGW(TAG, "WAV fmt chunk not found, assume PCM16 mono/stereo fallback"); + fmt->audio_format = 1; + fmt->channels = 1; + fmt->sample_rate = MIMI_VOICE_SAMPLE_RATE; + fmt->bits_per_sample = 16; } - esp_http_client_set_header(client, "X-DashScope-SSE", "disable"); - esp_http_client_set_post_field(client, json, (int)strlen(json)); - esp_err_t err = esp_http_client_perform(client); - *out_status = esp_http_client_get_status_code(client); - esp_http_client_cleanup(client); - if (err != ESP_OK) { - free(resp.buf); - return err; + if (fmt->audio_format != 1) { + ESP_LOGE(TAG, "Unsupported WAV audio_format=%u", (unsigned)fmt->audio_format); + return ESP_ERR_NOT_SUPPORTED; + } + + if (fmt->bits_per_sample != 16) { + ESP_LOGE(TAG, "Unsupported WAV bits_per_sample=%u", (unsigned)fmt->bits_per_sample); + return ESP_ERR_NOT_SUPPORTED; } - *out_body = resp.buf; return ESP_OK; } -static esp_err_t i2s_write_all(const uint8_t *data, size_t len) +/* ========================= + * STT / TTS JSON helpers + * ========================= */ + +static char *build_data_url_from_wav(const uint8_t *wav, size_t wav_len) { - if (!data || len == 0) { - return ESP_OK; + if (!wav || wav_len == 0) { + return NULL; } - size_t off = 0; - while (off < len) { - size_t written = 0; - size_t chunk = len - off; - if (chunk > 1024) { - chunk = 1024; - } - esp_err_t err = i2s_channel_write(s_tx_chan, data + off, chunk, &written, - pdMS_TO_TICKS(500)); - if (err != ESP_OK) { - return err; - } - off += written; + size_t b64_len = 0; + int rc = mbedtls_base64_encode(NULL, 0, &b64_len, wav, wav_len); + if (rc != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL && rc != 0) { + return NULL; } - return ESP_OK; -} -static uint32_t read_le_u32(const uint8_t *p) -{ - return ((uint32_t)p[0]) | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); -} + const char *prefix = "data:audio/wav;base64,"; + size_t prefix_len = strlen(prefix); + char *out = (char *)malloc(prefix_len + b64_len + 1); + if (!out) { + return NULL; + } -static uint16_t read_le_u16(const uint8_t *p) -{ - return (uint16_t)(((uint16_t)p[0]) | ((uint16_t)p[1] << 8)); + memcpy(out, prefix, prefix_len); + + size_t actual = 0; + rc = mbedtls_base64_encode((unsigned char *)(out + prefix_len), + b64_len, + &actual, + wav, + wav_len); + if (rc != 0) { + free(out); + return NULL; + } + + out[prefix_len + actual] = '\0'; + return out; } -static esp_err_t wav_find_data_offset(const uint8_t *wav, size_t wav_len, - size_t *out_data_off, uint32_t *out_sample_rate) +static esp_err_t parse_stt_response_text(const char *json, char *out_text, size_t out_size) { - if (!wav || wav_len < 12 || !out_data_off || !out_sample_rate) { + if (!json || !out_text || out_size == 0) { return ESP_ERR_INVALID_ARG; } - *out_data_off = 0; - *out_sample_rate = 0; - if (memcmp(wav, "RIFF", 4) != 0 || memcmp(wav + 8, "WAVE", 4) != 0) { + out_text[0] = '\0'; + + cJSON *root = cJSON_Parse(json); + if (!root) { return ESP_ERR_INVALID_RESPONSE; } - bool fmt_ok = false; - size_t off = 12; - while (off + 8 <= wav_len) { - const uint8_t *chunk = wav + off; - uint32_t chunk_size = read_le_u32(chunk + 4); - size_t payload_off = off + 8; - size_t next = payload_off + chunk_size + (chunk_size & 1u); - if (payload_off > wav_len || next > wav_len) { - return ESP_ERR_NOT_FOUND; - } + cJSON *choices = cJSON_GetObjectItem(root, "choices"); + cJSON *choice0 = (choices && cJSON_IsArray(choices)) ? cJSON_GetArrayItem(choices, 0) : NULL; + cJSON *message = choice0 ? cJSON_GetObjectItem(choice0, "message") : NULL; + cJSON *content = message ? cJSON_GetObjectItem(message, "content") : NULL; - if (memcmp(chunk, "fmt ", 4) == 0 && chunk_size >= 16) { - const uint8_t *f = wav + payload_off; - uint16_t audio_format = read_le_u16(f + 0); - uint16_t channels = read_le_u16(f + 2); - *out_sample_rate = read_le_u32(f + 4); - uint16_t bits_per_sample = read_le_u16(f + 14); - if (audio_format == 1 && channels == 1 && bits_per_sample == 16) { - fmt_ok = true; - } - } else if (memcmp(chunk, "data", 4) == 0) { - if (!fmt_ok) { - return ESP_ERR_INVALID_RESPONSE; - } - *out_data_off = payload_off; - return ESP_OK; - } - off = next; + if (cJSON_IsString(content) && content->valuestring) { + strlcpy(out_text, content->valuestring, out_size); + cJSON_Delete(root); + return ESP_OK; } + cJSON_Delete(root); return ESP_ERR_NOT_FOUND; } -static esp_err_t build_wav_from_pcm(const int16_t *pcm, size_t pcm_bytes, - uint8_t **out_wav, size_t *out_wav_len) +static esp_err_t parse_tts_audio_url(const char *json, char *out_url, size_t out_size) { - if (!pcm || !out_wav || !out_wav_len) { + if (!json || !out_url || out_size == 0) { return ESP_ERR_INVALID_ARG; } - if (pcm_bytes > UINT32_MAX - 44) { - return ESP_ERR_INVALID_SIZE; + + out_url[0] = '\0'; + + cJSON *root = cJSON_Parse(json); + if (!root) { + return ESP_ERR_INVALID_RESPONSE; } - uint8_t *wav = malloc(44 + pcm_bytes); - if (!wav) { - return ESP_ERR_NO_MEM; + cJSON *output = cJSON_GetObjectItem(root, "output"); + cJSON *audio = output ? cJSON_GetObjectItem(output, "audio") : NULL; + cJSON *url = audio ? cJSON_GetObjectItem(audio, "url") : NULL; + + if (cJSON_IsString(url) && url->valuestring && url->valuestring[0]) { + strlcpy(out_url, url->valuestring, out_size); + cJSON_Delete(root); + return ESP_OK; } - uint32_t data_size = (uint32_t)pcm_bytes; - uint32_t riff_size = 36 + data_size; - uint32_t byte_rate = MIMI_VOICE_SAMPLE_RATE * 1 * (MIMI_VOICE_BITS_PER_SAMPLE / 8); - uint16_t block_align = 1 * (MIMI_VOICE_BITS_PER_SAMPLE / 8); - - memcpy(wav + 0, "RIFF", 4); - wav[4] = (uint8_t)(riff_size & 0xFF); - wav[5] = (uint8_t)((riff_size >> 8) & 0xFF); - wav[6] = (uint8_t)((riff_size >> 16) & 0xFF); - wav[7] = (uint8_t)((riff_size >> 24) & 0xFF); - memcpy(wav + 8, "WAVE", 4); - memcpy(wav + 12, "fmt ", 4); - wav[16] = 16; wav[17] = 0; wav[18] = 0; wav[19] = 0; - wav[20] = 1; wav[21] = 0; - wav[22] = 1; wav[23] = 0; - wav[24] = (uint8_t)(MIMI_VOICE_SAMPLE_RATE & 0xFF); - wav[25] = (uint8_t)((MIMI_VOICE_SAMPLE_RATE >> 8) & 0xFF); - wav[26] = (uint8_t)((MIMI_VOICE_SAMPLE_RATE >> 16) & 0xFF); - wav[27] = (uint8_t)((MIMI_VOICE_SAMPLE_RATE >> 24) & 0xFF); - wav[28] = (uint8_t)(byte_rate & 0xFF); - wav[29] = (uint8_t)((byte_rate >> 8) & 0xFF); - wav[30] = (uint8_t)((byte_rate >> 16) & 0xFF); - wav[31] = (uint8_t)((byte_rate >> 24) & 0xFF); - wav[32] = (uint8_t)(block_align & 0xFF); - wav[33] = (uint8_t)((block_align >> 8) & 0xFF); - wav[34] = MIMI_VOICE_BITS_PER_SAMPLE; - wav[35] = 0; - memcpy(wav + 36, "data", 4); - wav[40] = (uint8_t)(data_size & 0xFF); - wav[41] = (uint8_t)((data_size >> 8) & 0xFF); - wav[42] = (uint8_t)((data_size >> 16) & 0xFF); - wav[43] = (uint8_t)((data_size >> 24) & 0xFF); - memcpy(wav + 44, pcm, pcm_bytes); - - *out_wav = wav; - *out_wav_len = 44 + pcm_bytes; - return ESP_OK; + cJSON_Delete(root); + return ESP_ERR_NOT_FOUND; } -static esp_err_t base64_encode_alloc(const uint8_t *src, size_t src_len, char **out_b64) +/* ========================= + * Bus integration + * ========================= */ + +static void push_voice_inbound(const char *text) { - if (!src || !out_b64) { - return ESP_ERR_INVALID_ARG; + if (!text || !text[0]) { + return; } - *out_b64 = NULL; - size_t needed = 0; - int rc = mbedtls_base64_encode(NULL, 0, &needed, src, src_len); - if (rc != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL || needed == 0) { - return ESP_FAIL; + mimi_msg_t msg = {0}; + strlcpy(msg.channel, MIMI_CHAN_VOICE, sizeof(msg.channel)); + strlcpy(msg.chat_id, MIMI_VOICE_CHAT_ID, sizeof(msg.chat_id)); + msg.content = strdup(text); + + if (!msg.content) { + ESP_LOGE(TAG, "No memory for voice inbound text"); + return; } - char *b64 = malloc(needed + 1); - if (!b64) { - return ESP_ERR_NO_MEM; + if (message_bus_push_inbound(&msg) != ESP_OK) { + ESP_LOGW(TAG, "Inbound queue full, drop voice transcript"); + free(msg.content); } - rc = mbedtls_base64_encode((unsigned char *)b64, needed, &needed, src, src_len); - if (rc != 0) { - free(b64); - return ESP_FAIL; +} + +/* ========================= + * I2S init / playback + * ========================= */ + +static esp_err_t i2s_init_xvf3800(void) +{ + esp_err_t err; + + i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)MIMI_VOICE_I2S_PORT, I2S_ROLE_MASTER); + + err = i2s_new_channel(&chan_cfg, &s_tx_chan, &s_rx_chan); + if (err != ESP_OK) { + ESP_LOGE(TAG, "i2s_new_channel failed: %s", esp_err_to_name(err)); + return err; + } + + i2s_std_config_t rx_cfg = { + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(MIMI_VOICE_SAMPLE_RATE), + .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO), + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, + .bclk = MIMI_VOICE_I2S_BCLK, + .ws = MIMI_VOICE_I2S_WS, + .dout = I2S_GPIO_UNUSED, + .din = MIMI_VOICE_I2S_DIN, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }, + }, + }; + + i2s_std_config_t tx_cfg = { + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(MIMI_VOICE_SAMPLE_RATE), + .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, + .bclk = MIMI_VOICE_I2S_BCLK, + .ws = MIMI_VOICE_I2S_WS, + .dout = MIMI_VOICE_I2S_DOUT, + .din = I2S_GPIO_UNUSED, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }, + }, + }; + + err = i2s_channel_init_std_mode(s_rx_chan, &rx_cfg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "i2s rx init failed: %s", esp_err_to_name(err)); + return err; } - b64[needed] = '\0'; - *out_b64 = b64; + + err = i2s_channel_init_std_mode(s_tx_chan, &tx_cfg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "i2s tx init failed: %s", esp_err_to_name(err)); + return err; + } + + err = i2s_channel_enable(s_rx_chan); + if (err != ESP_OK) { + ESP_LOGE(TAG, "i2s rx enable failed: %s", esp_err_to_name(err)); + return err; + } + + err = i2s_channel_enable(s_tx_chan); + if (err != ESP_OK) { + ESP_LOGE(TAG, "i2s tx enable failed: %s", esp_err_to_name(err)); + return err; + } + + s_i2s_ready = true; + ESP_LOGI(TAG, "I2S ready: %dHz stereo s32 in / mono s16 out", + MIMI_VOICE_SAMPLE_RATE); return ESP_OK; } - -static esp_err_t parse_qwen_asr_text(const char *json, char *out_text, size_t out_size) +static int16_t *resample_s16_mono_linear(const int16_t *src, + size_t src_samples, + uint32_t src_rate, + uint32_t dst_rate, + size_t *out_samples) { - cJSON *root = cJSON_Parse(json); - if (!root) { - return ESP_ERR_INVALID_RESPONSE; + if (!src || src_samples == 0 || !out_samples || src_rate == 0 || dst_rate == 0) { + return NULL; } - const char *text = NULL; - cJSON *choices = cJSON_GetObjectItem(root, "choices"); - if (cJSON_IsArray(choices)) { - cJSON *first = cJSON_GetArrayItem(choices, 0); - cJSON *message = cJSON_GetObjectItem(first, "message"); - cJSON *content = cJSON_GetObjectItem(message, "content"); - if (cJSON_IsString(content) && content->valuestring) { - text = content->valuestring; - } else if (cJSON_IsArray(content)) { - cJSON *item = cJSON_GetArrayItem(content, 0); - cJSON *item_text = cJSON_GetObjectItem(item, "text"); - if (cJSON_IsString(item_text) && item_text->valuestring) { - text = item_text->valuestring; - } + if (src_rate == dst_rate) { + int16_t *copy = malloc(src_samples * sizeof(int16_t)); + if (!copy) { + return NULL; } + memcpy(copy, src, src_samples * sizeof(int16_t)); + *out_samples = src_samples; + return copy; + } + + size_t dst_samples = (size_t)(((uint64_t)src_samples * dst_rate) / src_rate); + if (dst_samples == 0) { + return NULL; + } + + int16_t *dst = malloc(dst_samples * sizeof(int16_t)); + if (!dst) { + return NULL; } - if (!text) { - cJSON *output = cJSON_GetObjectItem(root, "output"); - cJSON *result = output ? cJSON_GetObjectItem(output, "text") : NULL; - if (cJSON_IsString(result) && result->valuestring) { - text = result->valuestring; + for (size_t i = 0; i < dst_samples; i++) { + float src_pos = ((float)i * (float)src_rate) / (float)dst_rate; + size_t idx = (size_t)src_pos; + float frac = src_pos - (float)idx; + + if (idx >= src_samples - 1) { + dst[i] = src[src_samples - 1]; + } else { + float a = (float)src[idx]; + float b = (float)src[idx + 1]; + float v = a + (b - a) * frac; + + if (v > 32767.0f) v = 32767.0f; + if (v < -32768.0f) v = -32768.0f; + dst[i] = (int16_t)v; } } - if (text && out_size > 0) { - strncpy(out_text, text, out_size - 1); - out_text[out_size - 1] = '\0'; + *out_samples = dst_samples; + return dst; +} +static esp_err_t i2s_play_wav_pcm16(const uint8_t *wav, size_t wav_len) +{ + if (!s_i2s_ready || !s_tx_chan || !wav || wav_len == 0) { + return ESP_ERR_INVALID_STATE; } - cJSON_Delete(root); - return (text && out_text[0]) ? ESP_OK : ESP_ERR_NOT_FOUND; + + wav_fmt_t fmt; + const uint8_t *pcm = NULL; + size_t pcm_len = 0; + + esp_err_t err = wav_find_data_chunk(wav, wav_len, &fmt, &pcm, &pcm_len); + if (err != ESP_OK) { + ESP_LOGE(TAG, "wav_find_data_chunk failed: %s", esp_err_to_name(err)); + return err; + } + + ESP_LOGI(TAG, "WAV fmt: format=%u channels=%u sample_rate=%u bits=%u data_len=%u", + (unsigned)fmt.audio_format, + (unsigned)fmt.channels, + (unsigned)fmt.sample_rate, + (unsigned)fmt.bits_per_sample, + (unsigned)pcm_len); + + if (fmt.audio_format != 1 || fmt.bits_per_sample != 16) { + return ESP_ERR_NOT_SUPPORTED; + } + + const int16_t *src16 = (const int16_t *)pcm; + size_t src_samples_total = pcm_len / sizeof(int16_t); + + int16_t *mono_buf = NULL; + size_t mono_samples = 0; + + if (fmt.channels == 1) { + mono_samples = src_samples_total; + mono_buf = malloc(mono_samples * sizeof(int16_t)); + if (!mono_buf) { + return ESP_ERR_NO_MEM; + } + memcpy(mono_buf, src16, mono_samples * sizeof(int16_t)); + } else if (fmt.channels == 2) { + mono_samples = src_samples_total / 2; + mono_buf = malloc(mono_samples * sizeof(int16_t)); + if (!mono_buf) { + return ESP_ERR_NO_MEM; + } + + for (size_t i = 0, j = 0; j < mono_samples; i += 2, j++) { + int32_t v = ((int32_t)src16[i] + (int32_t)src16[i + 1]) / 2; + mono_buf[j] = (int16_t)v; + } + } else { + return ESP_ERR_NOT_SUPPORTED; + } + + size_t play_samples = 0; + int16_t *play_buf = resample_s16_mono_linear( + mono_buf, + mono_samples, + fmt.sample_rate, + MIMI_VOICE_SAMPLE_RATE, + &play_samples + ); + free(mono_buf); + + if (!play_buf || play_samples == 0) { + return ESP_ERR_NO_MEM; + } + + ESP_LOGI(TAG, "Playback PCM: %u samples @ %u Hz (~%u ms)", + (unsigned)play_samples, + (unsigned)MIMI_VOICE_SAMPLE_RATE, + (unsigned)((play_samples * 1000ULL) / MIMI_VOICE_SAMPLE_RATE)); + + s_is_playing = true; + + const uint8_t *play_bytes = (const uint8_t *)play_buf; + size_t total_bytes = play_samples * sizeof(int16_t); + size_t written_total = 0; + + while (written_total < total_bytes) { + size_t written = 0; + size_t chunk = total_bytes - written_total; + if (chunk > 2048) { + chunk = 2048; + } + + err = i2s_channel_write(s_tx_chan, + play_bytes + written_total, + chunk, + &written, + pdMS_TO_TICKS(1000)); + if (err != ESP_OK) { + ESP_LOGE(TAG, "i2s write failed: %s", esp_err_to_name(err)); + free(play_buf); + s_is_playing = false; + return err; + } + + if (written == 0) { + ESP_LOGE(TAG, "i2s write returned 0 bytes"); + free(play_buf); + s_is_playing = false; + return ESP_FAIL; + } + + written_total += written; + } + + free(play_buf); + s_is_playing = false; + return ESP_OK; } -static esp_err_t stt_transcribe_pcm(const int16_t *pcm, size_t bytes, - char *out_text, size_t out_text_size) +/* ========================= + * STT / TTS core + * ========================= */ + +static esp_err_t stt_transcribe_pcm(const int16_t *pcm, + size_t pcm_bytes, + char *out_text, + size_t out_text_size) { - if (!out_text || out_text_size == 0) { + if (!pcm || pcm_bytes == 0 || !out_text || out_text_size == 0) { return ESP_ERR_INVALID_ARG; } - out_text[0] = '\0'; - - const char *url = stt_api_url(); - const char *api_key = stt_api_key(); - if (!url[0] || !api_key[0]) { + if (!stt_api_url()[0] || !stt_api_key()[0]) { return ESP_ERR_INVALID_STATE; } + out_text[0] = '\0'; + uint8_t *wav = NULL; - size_t wav_len = 0; - char *audio_b64 = NULL; - char *data_uri = NULL; - char *json = NULL; - char *resp_body = NULL; - int status = 0; - esp_err_t err = ESP_FAIL; - - err = build_wav_from_pcm(pcm, bytes, &wav, &wav_len); - if (err != ESP_OK) goto done; - err = base64_encode_alloc(wav, wav_len, &audio_b64); - if (err != ESP_OK) goto done; + size_t wav_len = wav_build_from_pcm16(pcm, pcm_bytes, MIMI_VOICE_SAMPLE_RATE, 1, &wav); + if (!wav || wav_len == 0) { + return ESP_ERR_NO_MEM; + } - const char *prefix = "data:audio/wav;base64,"; - data_uri = malloc(strlen(prefix) + strlen(audio_b64) + 1); - if (!data_uri) { - err = ESP_ERR_NO_MEM; - goto done; + char *data_url = build_data_url_from_wav(wav, wav_len); + free(wav); + if (!data_url) { + return ESP_ERR_NO_MEM; } - strcpy(data_uri, prefix); - strcat(data_uri, audio_b64); cJSON *root = cJSON_CreateObject(); cJSON_AddStringToObject(root, "model", stt_model()); cJSON_AddBoolToObject(root, "stream", false); - cJSON *modalities = cJSON_CreateArray(); - cJSON_AddItemToArray(modalities, cJSON_CreateString("text")); - cJSON_AddItemToObject(root, "modalities", modalities); - cJSON *messages = cJSON_CreateArray(); cJSON *msg = cJSON_CreateObject(); cJSON_AddStringToObject(msg, "role", "user"); + cJSON *content = cJSON_CreateArray(); cJSON *audio_item = cJSON_CreateObject(); cJSON_AddStringToObject(audio_item, "type", "input_audio"); - cJSON *audio_obj = cJSON_CreateObject(); - cJSON_AddStringToObject(audio_obj, "data", data_uri); - cJSON_AddStringToObject(audio_obj, "format", "wav"); - cJSON_AddItemToObject(audio_item, "input_audio", audio_obj); + + cJSON *input_audio = cJSON_CreateObject(); + cJSON_AddStringToObject(input_audio, "data", data_url); + cJSON_AddItemToObject(audio_item, "input_audio", input_audio); cJSON_AddItemToArray(content, audio_item); - cJSON *text_item = cJSON_CreateObject(); - cJSON_AddStringToObject(text_item, "type", "text"); - cJSON_AddStringToObject(text_item, "text", "Transcribe this speech to plain text. Return only the transcript."); - cJSON_AddItemToArray(content, text_item); cJSON_AddItemToObject(msg, "content", content); cJSON_AddItemToArray(messages, msg); cJSON_AddItemToObject(root, "messages", messages); - json = cJSON_PrintUnformatted(root); + cJSON *asr_options = cJSON_CreateObject(); + cJSON_AddBoolToObject(asr_options, "enable_itn", false); + cJSON_AddItemToObject(root, "asr_options", asr_options); + + char *body = cJSON_PrintUnformatted(root); cJSON_Delete(root); - if (!json) { - err = ESP_ERR_NO_MEM; - goto done; + free(data_url); + + if (!body) { + return ESP_ERR_NO_MEM; } - err = http_post_json(url, api_key, json, "application/json", &resp_body, &status); - if (err != ESP_OK) goto done; - if (status < 200 || status >= 300) { - ESP_LOGE(TAG, "Qwen STT HTTP %d: %.240s", status, resp_body); - err = ESP_FAIL; - goto done; + http_resp_t resp = {0}; + int http_status = 0; + esp_err_t err = http_post_json(stt_api_url(), stt_api_key(), body, false, &resp, &http_status); + free(body); + + if (err != ESP_OK) { + ESP_LOGE(TAG, "STT HTTP failed: %s", esp_err_to_name(err)); + free(resp.buf); + return err; + } + if (http_status < 200 || http_status >= 300) { + ESP_LOGE(TAG, "STT HTTP status=%d body=%s", http_status, resp.buf ? resp.buf : ""); + free(resp.buf); + return ESP_FAIL; } - err = parse_qwen_asr_text(resp_body, out_text, out_text_size); + err = parse_stt_response_text(resp.buf ? resp.buf : "", out_text, out_text_size); + if (err != ESP_OK) { + ESP_LOGE(TAG, "STT parse failed, body=%s", resp.buf ? resp.buf : ""); + } else { + ESP_LOGI(TAG, "STT transcript: %s", out_text); + } -done: - free(wav); - free(audio_b64); - free(data_uri); - free(json); - free(resp_body); + free(resp.buf); return err; } static esp_err_t tts_stream_play(const char *text) { - char *json = NULL; - char *resp_body = NULL; - esp_http_client_handle_t client = NULL; - uint8_t *prefix = NULL; - esp_err_t err = ESP_FAIL; - - const char *url = tts_api_url(); - const char *api_key = tts_api_key(); - if (!url[0] || !api_key[0]) { + if (!text || !text[0]) { + return ESP_ERR_INVALID_ARG; + } + if (!tts_api_url()[0] || !tts_api_key()[0]) { return ESP_ERR_INVALID_STATE; } - /* Step 1: request TTS task and get downloadable audio URL */ cJSON *body = cJSON_CreateObject(); - if (!body) { - return ESP_ERR_NO_MEM; - } cJSON_AddStringToObject(body, "model", tts_model()); + cJSON *input = cJSON_CreateObject(); cJSON_AddStringToObject(input, "text", text); + cJSON_AddStringToObject(input, "voice", tts_voice()); + cJSON_AddStringToObject(input, "language_type", tts_language()); cJSON_AddItemToObject(body, "input", input); - cJSON *parameters = cJSON_CreateObject(); - cJSON_AddStringToObject(parameters, "voice", MIMI_SECRET_TTS_VOICE); - cJSON_AddStringToObject(parameters, "language_type", MIMI_SECRET_TTS_LANGUAGE); - cJSON_AddItemToObject(body, "parameters", parameters); - json = cJSON_PrintUnformatted(body); + + char *json = cJSON_PrintUnformatted(body); cJSON_Delete(body); + if (!json) { return ESP_ERR_NO_MEM; } - int status = 0; - err = http_post_json(url, api_key, json, "application/json", &resp_body, &status); + http_resp_t resp = {0}; + int http_status = 0; + esp_err_t err = http_post_json(tts_api_url(), tts_api_key(), json, false, &resp, &http_status); free(json); - json = NULL; - if (err != ESP_OK || status < 200 || status >= 300) { - ESP_LOGE(TAG, "Qwen TTS HTTP %d: %.240s", status, resp_body ? resp_body : ""); - goto done; - } - cJSON *root = cJSON_Parse(resp_body); - free(resp_body); - resp_body = NULL; - if (!root) { - err = ESP_ERR_INVALID_RESPONSE; - goto done; + if (err != ESP_OK) { + ESP_LOGE(TAG, "TTS HTTP failed: %s", esp_err_to_name(err)); + free(resp.buf); + return err; } - cJSON *output = cJSON_GetObjectItem(root, "output"); - cJSON *audio = output ? cJSON_GetObjectItem(output, "audio") : NULL; - cJSON *url_obj = audio ? cJSON_GetObjectItem(audio, "url") : NULL; - const char *audio_url = (cJSON_IsString(url_obj) && url_obj->valuestring) ? url_obj->valuestring : NULL; - if (!audio_url) { - cJSON_Delete(root); - err = ESP_ERR_INVALID_RESPONSE; - goto done; + if (http_status < 200 || http_status >= 300) { + ESP_LOGE(TAG, "TTS HTTP status=%d body=%s", http_status, resp.buf ? resp.buf : ""); + free(resp.buf); + return ESP_FAIL; } - esp_http_client_config_t cfg = { - .url = audio_url, - .timeout_ms = 30000, - .buffer_size = 2048, - .buffer_size_tx = 1024, - .crt_bundle_attach = esp_crt_bundle_attach, - }; - client = esp_http_client_init(&cfg); - cJSON_Delete(root); - if (!client) { - err = ESP_FAIL; - goto done; + char wav_url[1024] = {0}; + err = parse_tts_audio_url(resp.buf ? resp.buf : "", wav_url, sizeof(wav_url)); + if (err != ESP_OK) { + ESP_LOGE(TAG, "TTS parse failed, body=%s", resp.buf ? resp.buf : ""); + free(resp.buf); + return err; } + free(resp.buf); - esp_http_client_set_method(client, HTTP_METHOD_GET); - esp_http_client_set_header(client, "Accept", "audio/wav,application/octet-stream"); - err = esp_http_client_open(client, 0); + ESP_LOGI(TAG, "TTS audio url: %s", wav_url); + + http_resp_t wav_resp = {0}; + http_status = 0; + err = http_get_binary(wav_url, &wav_resp, &http_status); if (err != ESP_OK) { - goto done; + ESP_LOGE(TAG, "TTS wav download failed: %s", esp_err_to_name(err)); + free(wav_resp.buf); + return err; } - status = esp_http_client_fetch_headers(client); - (void)status; - int code = esp_http_client_get_status_code(client); - if (code < 200 || code >= 300) { - err = ESP_FAIL; - goto done; + if (http_status < 200 || http_status >= 300) { + ESP_LOGE(TAG, "TTS wav status=%d", http_status); + free(wav_resp.buf); + return ESP_FAIL; } + ESP_LOGI(TAG, "TTS wav http_status=%d len=%d", http_status, (int)wav_resp.len); - /* Step 2: stream download and play WAV body */ - size_t prefix_cap = 4096; - prefix = malloc(prefix_cap); - if (!prefix) { - err = ESP_ERR_NO_MEM; - goto done; + if (wav_resp.len >= 12) { + ESP_LOGI(TAG, "TTS wav magic: %.4s / %.4s", + wav_resp.buf, + wav_resp.buf + 8); } - size_t prefix_len = 0; - bool data_started = false; - - s_is_playing = true; - uint8_t chunk[2048]; - while (1) { - int n = esp_http_client_read(client, (char *)chunk, sizeof(chunk)); - if (n < 0) { - err = ESP_FAIL; - break; - } - if (n == 0) { - err = ESP_OK; - break; - } - - if (!data_started) { - if (prefix_len + (size_t)n > prefix_cap) { - size_t new_cap = prefix_cap * 2; - while (new_cap < prefix_len + (size_t)n) { - new_cap *= 2; - } - uint8_t *tmp = realloc(prefix, new_cap); - if (!tmp) { - err = ESP_ERR_NO_MEM; - break; - } - prefix = tmp; - prefix_cap = new_cap; - } - memcpy(prefix + prefix_len, chunk, (size_t)n); - prefix_len += (size_t)n; - - size_t data_off = 0; - uint32_t sample_rate = 0; - esp_err_t parse = wav_find_data_offset(prefix, prefix_len, &data_off, &sample_rate); - if (parse == ESP_OK) { - if (sample_rate != MIMI_VOICE_SAMPLE_RATE) { - ESP_LOGW(TAG, "TTS WAV sample rate=%u differs from I2S=%d; playback speed may be off", - sample_rate, MIMI_VOICE_SAMPLE_RATE); - } - if (prefix_len > data_off) { - err = i2s_write_all(prefix + data_off, prefix_len - data_off); - if (err != ESP_OK) { - break; - } - } - data_started = true; - } else if (parse != ESP_ERR_NOT_FOUND) { - err = parse; - break; - } - } else { - err = i2s_write_all(chunk, (size_t)n); - if (err != ESP_OK) { - break; - } - } + if (wav_resp.len > 0 && strncmp(wav_resp.buf, "RIFF", 4) != 0) { + ESP_LOGE(TAG, "TTS response is not WAV, preview: %.120s", wav_resp.buf); } -done: - s_is_playing = false; - if (client) { - esp_http_client_close(client); - esp_http_client_cleanup(client); - } - free(prefix); - free(resp_body); - free(json); + err = i2s_play_wav_pcm16((const uint8_t *)wav_resp.buf, wav_resp.len); + free(wav_resp.buf); return err; } -static void push_voice_inbound(const char *text) -{ - mimi_msg_t msg = {0}; - strncpy(msg.channel, MIMI_CHAN_VOICE, sizeof(msg.channel) - 1); - strncpy(msg.chat_id, MIMI_VOICE_CHAT_ID, sizeof(msg.chat_id) - 1); - msg.content = strdup(text); - if (!msg.content) { - return; - } - - if (message_bus_push_inbound(&msg) != ESP_OK) { - ESP_LOGW(TAG, "Inbound queue full, drop voice text"); - free(msg.content); - } -} +/* ========================= + * Voice capture loop + * ========================= */ static void voice_capture_task(void *arg) { - const size_t samples_per_frame = (MIMI_VOICE_SAMPLE_RATE * MIMI_VOICE_FRAME_MS) / 1000; - const size_t frame_bytes = samples_per_frame * sizeof(int16_t); - const int max_frames = MIMI_VOICE_MAX_UTTERANCE_MS / MIMI_VOICE_FRAME_MS; - const int end_silence_frames = MIMI_VOICE_SILENCE_END_MS / MIMI_VOICE_FRAME_MS; - - int16_t *frame = malloc(frame_bytes); - int16_t *utterance = heap_caps_malloc(max_frames * frame_bytes, MALLOC_CAP_SPIRAM); - if (!utterance) { - utterance = malloc(max_frames * frame_bytes); - } - - if (!frame || !utterance) { - free(frame); + (void)arg; + + const size_t frame_samples = (MIMI_VOICE_SAMPLE_RATE * MIMI_VOICE_FRAME_MS) / 1000; + const size_t stereo_frame_bytes = frame_samples * VOICE_I2S_BYTES_PER_STEREO_FRAME; + const size_t mono16_frame_bytes = frame_samples * sizeof(int16_t); + const size_t max_frames = MIMI_VOICE_MAX_UTTERANCE_MS / MIMI_VOICE_FRAME_MS; + const size_t silence_frames_end = MIMI_VOICE_SILENCE_END_MS / MIMI_VOICE_FRAME_MS; + + uint8_t *rx_buf = (uint8_t *)heap_caps_malloc(stereo_frame_bytes, MALLOC_CAP_SPIRAM); + int16_t *mono_frame = (int16_t *)heap_caps_malloc(mono16_frame_bytes, MALLOC_CAP_SPIRAM); + int16_t *utterance = (int16_t *)heap_caps_malloc(max_frames * frame_samples * sizeof(int16_t), MALLOC_CAP_SPIRAM); + + if (!rx_buf || !mono_frame || !utterance) { + ESP_LOGE(TAG, "voice_capture_task alloc failed"); + free(rx_buf); + free(mono_frame); free(utterance); - ESP_LOGE(TAG, "Cannot allocate voice capture buffers"); vTaskDelete(NULL); return; } bool in_speech = false; - int silence_frames = 0; - int total_frames = 0; + size_t total_frames = 0; + size_t silence_frames = 0; + + /* Simple adaptive noise floor */ + uint32_t noise_floor = MIMI_VOICE_VAD_THRESHOLD / 2; + if (noise_floor < 100) noise_floor = 100; while (1) { + if (!s_i2s_ready || !s_rx_chan) { + vTaskDelay(pdMS_TO_TICKS(100)); + continue; + } + if (s_is_playing) { - vTaskDelay(pdMS_TO_TICKS(30)); + /* Avoid self-trigger during playback */ + vTaskDelay(pdMS_TO_TICKS(MIMI_VOICE_FRAME_MS)); continue; } - size_t read_bytes = 0; - esp_err_t err = i2s_channel_read(s_rx_chan, frame, frame_bytes, &read_bytes, - pdMS_TO_TICKS(200)); - if (err != ESP_OK || read_bytes != frame_bytes) { + size_t bytes_read = 0; + esp_err_t err = i2s_channel_read(s_rx_chan, + rx_buf, + stereo_frame_bytes, + &bytes_read, + pdMS_TO_TICKS(1000)); + if (err != ESP_OK || bytes_read == 0) { continue; } - int energy = frame_avg_abs_energy(frame, samples_per_frame); - bool voiced = (energy >= MIMI_VOICE_VAD_THRESHOLD); + size_t mono_samples = 0; + pcm_s32_stereo_to_s16_mono(rx_buf, bytes_read, mono_frame, &mono_samples); + if (mono_samples == 0) { + continue; + } + uint32_t energy = pcm_energy_absavg(mono_frame, mono_samples); + + /* Update noise floor only when not in speech */ if (!in_speech) { - if (!voiced) { + noise_floor = (noise_floor * 15 + energy) / 16; + } + + uint32_t dynamic_threshold = noise_floor + MIMI_VOICE_VAD_THRESHOLD; + bool speech_now = (energy > dynamic_threshold); + + if (!in_speech) { + if (!speech_now) { continue; } in_speech = true; - silence_frames = 0; total_frames = 0; + silence_frames = 0; } if (total_frames < max_frames) { - memcpy((uint8_t *)utterance + (total_frames * frame_bytes), frame, frame_bytes); + memcpy(&utterance[total_frames * frame_samples], mono_frame, mono16_frame_bytes); total_frames++; } - if (voiced) { + if (speech_now) { silence_frames = 0; } else { silence_frames++; } - bool hit_max = (total_frames >= max_frames); - bool end_of_speech = (silence_frames >= end_silence_frames); - if (!hit_max && !end_of_speech) { + bool end_by_silence = (silence_frames >= silence_frames_end); + bool end_by_limit = (total_frames >= max_frames); + + if (!end_by_silence && !end_by_limit) { continue; } in_speech = false; + + /* Ignore ultra-short bursts */ if (total_frames < 5) { + total_frames = 0; + silence_frames = 0; continue; } - size_t pcm_bytes = total_frames * frame_bytes; + size_t pcm_bytes = total_frames * frame_samples * sizeof(int16_t); char text[512] = {0}; if (xSemaphoreTake(s_http_lock, pdMS_TO_TICKS(30000)) == pdTRUE) { esp_err_t stt_err = stt_transcribe_pcm(utterance, pcm_bytes, text, sizeof(text)); xSemaphoreGive(s_http_lock); + if (stt_err == ESP_OK && text[0]) { ESP_LOGI(TAG, "Voice STT: %s", text); push_voice_inbound(text); @@ -789,13 +1153,21 @@ static void voice_capture_task(void *arg) ESP_LOGW(TAG, "STT failed or empty transcript"); } } + + total_frames = 0; + silence_frames = 0; } } +/* ========================= + * Public API + * ========================= */ + esp_err_t voice_channel_init(void) { s_enabled = (MIMI_VOICE_ENABLED_DEFAULT != 0) || (stt_api_key()[0] && tts_api_key()[0]); + if (!s_enabled) { ESP_LOGI(TAG, "Voice channel disabled (set STT/TTS API key or enable default)"); return ESP_OK; @@ -824,9 +1196,12 @@ esp_err_t voice_channel_start(void) } if (!s_capture_task) { - if (xTaskCreatePinnedToCore(voice_capture_task, "voice_cap", - MIMI_VOICE_CAPTURE_STACK, NULL, - MIMI_VOICE_TASK_PRIO, &s_capture_task, + if (xTaskCreatePinnedToCore(voice_capture_task, + "voice_cap", + MIMI_VOICE_CAPTURE_STACK, + NULL, + MIMI_VOICE_TASK_PRIO, + &s_capture_task, MIMI_VOICE_CORE) != pdPASS) { return ESP_FAIL; } @@ -862,9 +1237,10 @@ void voice_channel_get_status(voice_channel_status_t *status) if (!status) { return; } + status->enabled = s_enabled; status->i2s_ready = s_i2s_ready; status->is_playing = s_is_playing; status->stt_configured = (stt_api_url()[0] != '\0' && stt_api_key()[0] != '\0'); status->tts_configured = (tts_api_url()[0] != '\0' && tts_api_key()[0] != '\0'); -} +} \ No newline at end of file From 0f8cb95a9fb6de20abef04dd3fb7012230a05a12 Mon Sep 17 00:00:00 2001 From: stringwind Date: Mon, 9 Mar 2026 22:29:31 +0800 Subject: [PATCH 5/9] chore: add optional LLM API URL configuration to README files --- README.md | 1 + README_CN.md | 1 + README_JA.md | 1 + 3 files changed, 3 insertions(+) diff --git a/README.md b/README.md index d46d2a84..077075aa 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,7 @@ Edit `main/mimi_secrets.h`: #define MIMI_SECRET_WIFI_PASS "YourWiFiPassword" #define MIMI_SECRET_TG_TOKEN "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" #define MIMI_SECRET_API_KEY "sk-ant-api03-xxxxx" +#define MIMI_SECRET_LLM_API_URL "" // optional: full URL incl scheme/host/port/path #define MIMI_SECRET_MODEL_PROVIDER "anthropic" // "anthropic" or "openai" #define MIMI_SECRET_SEARCH_KEY "" // optional: Brave Search API key #define MIMI_SECRET_TAVILY_KEY "" // optional: Tavily API key (preferred) diff --git a/README_CN.md b/README_CN.md index 572905cc..49d256d5 100644 --- a/README_CN.md +++ b/README_CN.md @@ -128,6 +128,7 @@ cp main/mimi_secrets.h.example main/mimi_secrets.h #define MIMI_SECRET_WIFI_PASS "你的WiFi密码" #define MIMI_SECRET_TG_TOKEN "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" #define MIMI_SECRET_API_KEY "sk-ant-api03-xxxxx" +#define MIMI_SECRET_LLM_API_URL "" // 可选:完整URL(支持 http/https + 自定义端口) #define MIMI_SECRET_MODEL_PROVIDER "anthropic" // "anthropic" 或 "openai" #define MIMI_SECRET_SEARCH_KEY "" // 可选:Brave Search API key #define MIMI_SECRET_TAVILY_KEY "" // 可选:Tavily API key(优先) diff --git a/README_JA.md b/README_JA.md index 4ba5777a..0016f502 100644 --- a/README_JA.md +++ b/README_JA.md @@ -128,6 +128,7 @@ cp main/mimi_secrets.h.example main/mimi_secrets.h #define MIMI_SECRET_WIFI_PASS "WiFiパスワード" #define MIMI_SECRET_TG_TOKEN "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" #define MIMI_SECRET_API_KEY "sk-ant-api03-xxxxx" +#define MIMI_SECRET_LLM_API_URL "" // 任意:完全なURL(http/https + カスタムポートをサポート) #define MIMI_SECRET_MODEL_PROVIDER "anthropic" // "anthropic" または "openai" #define MIMI_SECRET_SEARCH_KEY "" // 任意:Brave Search APIキー #define MIMI_SECRET_TAVILY_KEY "" // 任意:Tavily APIキー(優先) From 85e21bad1564a85608c1db5473b1528f203e39e2 Mon Sep 17 00:00:00 2001 From: stringwind Date: Thu, 12 Mar 2026 16:43:28 +0800 Subject: [PATCH 6/9] chore(task): archive 00-bootstrap-guidelines --- .../2026-03/00-bootstrap-guidelines/prd.md | 113 ++++++++++++++++++ .../2026-03/00-bootstrap-guidelines/task.json | 35 ++++++ 2 files changed, 148 insertions(+) create mode 100644 .trellis/tasks/archive/2026-03/00-bootstrap-guidelines/prd.md create mode 100644 .trellis/tasks/archive/2026-03/00-bootstrap-guidelines/task.json diff --git a/.trellis/tasks/archive/2026-03/00-bootstrap-guidelines/prd.md b/.trellis/tasks/archive/2026-03/00-bootstrap-guidelines/prd.md new file mode 100644 index 00000000..c1e0a29c --- /dev/null +++ b/.trellis/tasks/archive/2026-03/00-bootstrap-guidelines/prd.md @@ -0,0 +1,113 @@ +# Bootstrap: Fill Project Development Guidelines + +## Purpose + +Welcome to Trellis! This is your first task. + +AI agents use `.trellis/spec/` to understand YOUR project's coding conventions. +**Empty templates = AI writes generic code that doesn't match your project style.** + +Filling these guidelines is a one-time setup that pays off for every future AI session. + +--- + +## Your Task + +Fill in the guideline files based on your **existing codebase**. + + +### Backend Guidelines + +| File | What to Document | +|------|------------------| +| `.trellis/spec/backend/directory-structure.md` | Where different file types go (routes, services, utils) | +| `.trellis/spec/backend/database-guidelines.md` | ORM, migrations, query patterns, naming conventions | +| `.trellis/spec/backend/error-handling.md` | How errors are caught, logged, and returned | +| `.trellis/spec/backend/logging-guidelines.md` | Log levels, format, what to log | +| `.trellis/spec/backend/quality-guidelines.md` | Code review standards, testing requirements | + + +### Frontend Guidelines + +| File | What to Document | +|------|------------------| +| `.trellis/spec/frontend/directory-structure.md` | Component/page/hook organization | +| `.trellis/spec/frontend/component-guidelines.md` | Component patterns, props conventions | +| `.trellis/spec/frontend/hook-guidelines.md` | Custom hook naming, patterns | +| `.trellis/spec/frontend/state-management.md` | State library, patterns, what goes where | +| `.trellis/spec/frontend/type-safety.md` | TypeScript conventions, type organization | +| `.trellis/spec/frontend/quality-guidelines.md` | Linting, testing, accessibility | + + +### Thinking Guides (Optional) + +The `.trellis/spec/guides/` directory contains thinking guides that are already +filled with general best practices. You can customize them for your project if needed. + +--- + +## How to Fill Guidelines + +### Step 0: Import from Existing Specs (Recommended) + +Many projects already have coding conventions documented. **Check these first** before writing from scratch: + +| File / Directory | Tool | +|------|------| +| `CLAUDE.md` / `CLAUDE.local.md` | Claude Code | +| `AGENTS.md` | Claude Code | +| `.cursorrules` | Cursor | +| `.cursor/rules/*.mdc` | Cursor (rules directory) | +| `.windsurfrules` | Windsurf | +| `.clinerules` | Cline | +| `.roomodes` | Roo Code | +| `.github/copilot-instructions.md` | GitHub Copilot | +| `.vscode/settings.json` → `github.copilot.chat.codeGeneration.instructions` | VS Code Copilot | +| `CONVENTIONS.md` / `.aider.conf.yml` | aider | +| `CONTRIBUTING.md` | General project conventions | +| `.editorconfig` | Editor formatting rules | + +If any of these exist, read them first and extract the relevant coding conventions into the corresponding `.trellis/spec/` files. This saves significant effort compared to writing everything from scratch. + +### Step 1: Analyze the Codebase + +Ask AI to help discover patterns from actual code: + +- "Read all existing config files (CLAUDE.md, .cursorrules, etc.) and extract coding conventions into .trellis/spec/" +- "Analyze my codebase and document the patterns you see" +- "Find error handling / component / API patterns and document them" + +### Step 2: Document Reality, Not Ideals + +Write what your codebase **actually does**, not what you wish it did. +AI needs to match existing patterns, not introduce new ones. + +- **Look at existing code** - Find 2-3 examples of each pattern +- **Include file paths** - Reference real files as examples +- **List anti-patterns** - What does your team avoid? + +--- + +## Completion Checklist + +- [ ] Guidelines filled for your project type +- [ ] At least 2-3 real code examples in each guideline +- [ ] Anti-patterns documented + +When done: + +```bash +python3 ./.trellis/scripts/task.py finish +python3 ./.trellis/scripts/task.py archive 00-bootstrap-guidelines +``` + +--- + +## Why This Matters + +After completing this task: + +1. AI will write code that matches your project style +2. Relevant `/trellis:before-*-dev` commands will inject real context +3. `/trellis:check-*` commands will validate against your actual standards +4. Future developers (human or AI) will onboard faster diff --git a/.trellis/tasks/archive/2026-03/00-bootstrap-guidelines/task.json b/.trellis/tasks/archive/2026-03/00-bootstrap-guidelines/task.json new file mode 100644 index 00000000..1e482403 --- /dev/null +++ b/.trellis/tasks/archive/2026-03/00-bootstrap-guidelines/task.json @@ -0,0 +1,35 @@ +{ + "id": "00-bootstrap-guidelines", + "name": "Bootstrap Guidelines", + "description": "Fill in project development guidelines for AI agents", + "status": "completed", + "dev_type": "docs", + "priority": "P1", + "creator": "stringwind", + "assignee": "stringwind", + "createdAt": "2026-03-12", + "completedAt": "2026-03-12", + "commit": null, + "subtasks": [ + { + "name": "Fill backend guidelines", + "status": "pending" + }, + { + "name": "Fill frontend guidelines", + "status": "pending" + }, + { + "name": "Add code examples", + "status": "pending" + } + ], + "children": [], + "parent": null, + "relatedFiles": [ + ".trellis/spec/backend/", + ".trellis/spec/frontend/" + ], + "notes": "First-time setup task created by trellis init (fullstack project)", + "meta": {} +} \ No newline at end of file From 61d43ac0b7e2f26767122ee679f5a2a7e5e852e1 Mon Sep 17 00:00:00 2001 From: stringwind Date: Fri, 13 Mar 2026 15:45:53 +0800 Subject: [PATCH 7/9] feat: support ReSpeaker XVF3800 for mimiclaw This commit introduces ReSpeaker XVF3800 integration and optimizes the voice processing pipeline for better performance and stability. - Hardware Adaptation: - Implement I2S configuration and memory management in `voice_channel.c`. - Add silence handling and playback noise suppression. - Optimize voice capture with a cooldown mechanism to reduce false triggers. - LLM & TTS Enhancements: - Refactor LLM API: rename `llm_set_api_url` to `llm_set_api_base`. - Implement TTS text truncation to handle character limits. - Switch default TTS language to English in configuration. - System & Tools: - Add `delay_s` parameter to `tool_cron` for relative scheduling. - Define task stack size and priority via macros for voice tasks. --- main/agent/agent_loop.c | 24 ++ main/cli/serial_cli.c | 228 +++-------------- main/llm/llm_proxy.c | 496 ++++++++++++++++-------------------- main/llm/llm_proxy.h | 29 +-- main/mimi.c | 6 +- main/mimi_config.h | 89 ++++--- main/mimi_secrets.h.example | 33 ++- main/tools/tool_cron.c | 19 +- main/tools/tool_registry.c | 5 +- main/voice/voice_channel.c | 487 ++++++++++++++++++++++++++++++----- 10 files changed, 816 insertions(+), 600 deletions(-) diff --git a/main/agent/agent_loop.c b/main/agent/agent_loop.c index d8d45390..2a513078 100644 --- a/main/agent/agent_loop.c +++ b/main/agent/agent_loop.c @@ -86,6 +86,30 @@ static void append_turn_context_prompt(char *prompt, size_t size, const mimi_msg if (n < 0 || (size_t)n >= (size - off)) { prompt[size - 1] = '\0'; } + + if (msg->channel[0] && strcmp(msg->channel, MIMI_CHAN_VOICE) == 0) { + off = strnlen(prompt, size - 1); + if (off >= size - 1) { + return; + } + + n = snprintf( + prompt + off, size - off, + "\n## Voice Output Constraints\n" + "This reply will be converted to speech (TTS) and played on a small speaker.\n" + "- Use English, natural spoken style.\n" + "- Keep it short: keep playback within ~%d seconds.\n" + "- Structure: at most 2 sentences + 1 short follow-up question.\n" + "- Length: <= %d characters total.\n" + "- No markdown, no lists, no code blocks, no URLs.\n" + "- Avoid long explanations; if the answer is long, give a 1–2 sentence summary and ask if the user wants more.\n", + (int)MIMI_VOICE_TTS_MAX_SECONDS, + (int)MIMI_VOICE_LLM_MAX_CHARS); + + if (n < 0 || (size_t)n >= (size - off)) { + prompt[size - 1] = '\0'; + } + } } static char *patch_tool_input_with_context(const llm_tool_call_t *call, const mimi_msg_t *msg) diff --git a/main/cli/serial_cli.c b/main/cli/serial_cli.c index d3a2b90c..904d90d4 100644 --- a/main/cli/serial_cli.c +++ b/main/cli/serial_cli.c @@ -12,12 +12,10 @@ #include "cron/cron_service.h" #include "heartbeat/heartbeat.h" #include "skills/skill_loader.h" -#include "voice/voice_channel.h" #include #include #include -#include #include #include "esp_log.h" #include "esp_console.h" @@ -125,6 +123,12 @@ static struct { struct arg_end *end; } api_key_args; +/* --- set_api_base command --- */ +static struct { + struct arg_str *base; + struct arg_end *end; +} api_base_args; + static int cmd_set_api_key(int argc, char **argv) { int nerrors = arg_parse(argc, argv, (void **)&api_key_args); @@ -137,6 +141,18 @@ static int cmd_set_api_key(int argc, char **argv) return 0; } +static int cmd_set_api_base(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **)&api_base_args); + if (nerrors != 0) { + arg_print_errors(stderr, api_base_args.end, argv[0]); + return 1; + } + llm_set_api_base(api_base_args.base->sval[0]); + printf("API base set.\n"); + return 0; +} + /* --- set_model command --- */ static struct { struct arg_str *model; @@ -168,51 +184,9 @@ static int cmd_set_model_provider(int argc, char **argv) arg_print_errors(stderr, provider_args.end, argv[0]); return 1; } - esp_err_t err = llm_set_provider(provider_args.provider->sval[0]); - if (err == ESP_OK) { - printf("Model provider set.\n"); - return 0; - } - printf("Failed to set provider: %s\n", esp_err_to_name(err)); - return 1; -} - -/* --- set_llm_url command --- */ -static struct { - struct arg_str *url; - struct arg_end *end; -} llm_url_args; - -static int cmd_set_llm_url(int argc, char **argv) -{ - int nerrors = arg_parse(argc, argv, (void **)&llm_url_args); - if (nerrors != 0) { - arg_print_errors(stderr, llm_url_args.end, argv[0]); - return 1; - } - - esp_err_t err = llm_set_api_url(llm_url_args.url->sval[0]); - if (err == ESP_OK) { - printf("LLM URL saved.\n"); - return 0; - } - printf("Failed to set LLM URL: %s\n", esp_err_to_name(err)); - return 1; -} - -/* --- clear_llm_url command --- */ -static int cmd_clear_llm_url(int argc, char **argv) -{ - (void)argc; - (void)argv; - - esp_err_t err = llm_clear_api_url(); - if (err == ESP_OK) { - printf("LLM URL override cleared.\n"); - return 0; - } - printf("Failed to clear LLM URL: %s\n", esp_err_to_name(err)); - return 1; + llm_set_provider(provider_args.provider->sval[0]); + printf("Model provider set.\n"); + return 0; } /* --- memory_read command --- */ @@ -291,45 +265,6 @@ static int cmd_heap_info(int argc, char **argv) return 0; } -/* --- voice_status command --- */ -static int cmd_voice_status(int argc, char **argv) -{ - voice_channel_status_t st = {0}; - voice_channel_get_status(&st); - - printf("Voice enabled: %s\n", st.enabled ? "yes" : "no"); - printf("I2S ready: %s\n", st.i2s_ready ? "yes" : "no"); - printf("Playing now: %s\n", st.is_playing ? "yes" : "no"); - printf("STT configured: %s\n", st.stt_configured ? "yes" : "no"); - printf("TTS configured: %s\n", st.tts_configured ? "yes" : "no"); - printf("Voice chat_id: %s\n", MIMI_VOICE_CHAT_ID); - return 0; -} - -/* --- voice_say command --- */ -static struct { - struct arg_str *text; - struct arg_end *end; -} voice_say_args; - -static int cmd_voice_say(int argc, char **argv) -{ - int nerrors = arg_parse(argc, argv, (void **)&voice_say_args); - if (nerrors != 0) { - arg_print_errors(stderr, voice_say_args.end, argv[0]); - return 1; - } - - esp_err_t err = voice_channel_speak_text(voice_say_args.text->sval[0]); - if (err == ESP_OK) { - printf("Queued for voice playback.\n"); - return 0; - } - - printf("voice_say failed: %s\n", esp_err_to_name(err)); - return 1; -} - /* --- set_proxy command --- */ static struct { struct arg_str *host; @@ -583,7 +518,7 @@ static int cmd_skill_search(int argc, char **argv) static void print_config(const char *label, const char *ns, const char *key, const char *build_val, bool mask) { - char nvs_val[384] = {0}; + char nvs_val[128] = {0}; const char *source = "not set"; const char *display = "(empty)"; @@ -611,68 +546,6 @@ static void print_config(const char *label, const char *ns, const char *key, } } -static void print_config_u16(const char *label, const char *ns, const char *key, - const char *build_val_str) -{ - uint16_t nvs_val = 0; - bool has_val = false; - const char *source = "not set"; - - nvs_handle_t nvs; - if (nvs_open(ns, NVS_READONLY, &nvs) == ESP_OK) { - if (nvs_get_u16(nvs, key, &nvs_val) == ESP_OK && nvs_val != 0) { - has_val = true; - source = "NVS"; - } - nvs_close(nvs); - } - - if (!has_val && build_val_str && build_val_str[0] != '\0') { - int v = atoi(build_val_str); - if (v > 0 && v <= 65535) { - nvs_val = (uint16_t)v; - has_val = true; - source = "build"; - } - } - - if (has_val) { - printf(" %-14s: %u [%s]\n", label, (unsigned)nvs_val, source); - } else { - printf(" %-14s: (empty) [%s]\n", label, source); - } -} - -static void print_llm_url(void) -{ - const char *source = "provider"; - const char *url = llm_get_api_url(); - - nvs_handle_t nvs; - if (nvs_open(MIMI_NVS_LLM, NVS_READONLY, &nvs) == ESP_OK) { - char tmp[384] = {0}; - size_t len = sizeof(tmp); - if (nvs_get_str(nvs, MIMI_NVS_KEY_API_URL, tmp, &len) == ESP_OK && tmp[0]) { - source = "NVS"; - } - nvs_close(nvs); - } - - if (strcmp(source, "provider") == 0 && MIMI_SECRET_LLM_API_URL[0] != '\0') { - source = "build"; - } - - if (!url || url[0] == '\0') { - url = "(empty)"; - } - - if (llm_api_url_is_valid()) { - printf(" %-14s: %s [%s]\n", "LLM URL", url, source); - } else { - printf(" %-14s: %s [%s, invalid]\n", "LLM URL", url, source); - } -} - static int cmd_config_show(int argc, char **argv) { printf("=== Current Configuration ===\n"); @@ -680,11 +553,11 @@ static int cmd_config_show(int argc, char **argv) print_config("WiFi Pass", MIMI_NVS_WIFI, MIMI_NVS_KEY_PASS, MIMI_SECRET_WIFI_PASS, true); print_config("TG Token", MIMI_NVS_TG, MIMI_NVS_KEY_TG_TOKEN, MIMI_SECRET_TG_TOKEN, true); print_config("API Key", MIMI_NVS_LLM, MIMI_NVS_KEY_API_KEY, MIMI_SECRET_API_KEY, true); - print_llm_url(); + print_config("API Base", MIMI_NVS_LLM, MIMI_NVS_KEY_API_BASE, MIMI_SECRET_API_BASE, false); print_config("Model", MIMI_NVS_LLM, MIMI_NVS_KEY_MODEL, MIMI_SECRET_MODEL, false); print_config("Provider", MIMI_NVS_LLM, MIMI_NVS_KEY_PROVIDER, MIMI_SECRET_MODEL_PROVIDER, false); print_config("Proxy Host", MIMI_NVS_PROXY, MIMI_NVS_KEY_PROXY_HOST, MIMI_SECRET_PROXY_HOST, false); - print_config_u16("Proxy Port", MIMI_NVS_PROXY, MIMI_NVS_KEY_PROXY_PORT, MIMI_SECRET_PROXY_PORT); + print_config("Proxy Port", MIMI_NVS_PROXY, MIMI_NVS_KEY_PROXY_PORT, MIMI_SECRET_PROXY_PORT, false); print_config("Search Key", MIMI_NVS_SEARCH, MIMI_NVS_KEY_API_KEY, MIMI_SECRET_SEARCH_KEY, true); print_config("Tavily Key", MIMI_NVS_SEARCH, MIMI_NVS_KEY_TAVILY_KEY, MIMI_SECRET_TAVILY_KEY, true); printf("=============================\n"); @@ -995,6 +868,17 @@ esp_err_t serial_cli_init(void) }; esp_console_cmd_register(&api_key_cmd); + /* set_api_base */ + api_base_args.base = arg_str1(NULL, NULL, "", "LLM API base (http(s)://host[:port][/path])"); + api_base_args.end = arg_end(1); + esp_console_cmd_t api_base_cmd = { + .command = "set_api_base", + .help = "Set LLM API base (e.g. https://api.anthropic.com/v1)", + .func = &cmd_set_api_base, + .argtable = &api_base_args, + }; + esp_console_cmd_register(&api_base_cmd); + /* set_model */ model_args.model = arg_str1(NULL, NULL, "", "Model identifier"); model_args.end = arg_end(1); @@ -1007,7 +891,7 @@ esp_err_t serial_cli_init(void) esp_console_cmd_register(&model_cmd); /* set_model_provider */ - provider_args.provider = arg_str1(NULL, NULL, "", "Model provider (anthropic|openai|openai_compat)"); + provider_args.provider = arg_str1(NULL, NULL, "", "Model provider (anthropic|openai)"); provider_args.end = arg_end(1); esp_console_cmd_t provider_cmd = { .command = "set_model_provider", @@ -1017,25 +901,6 @@ esp_err_t serial_cli_init(void) }; esp_console_cmd_register(&provider_cmd); - /* set_llm_url */ - llm_url_args.url = arg_str1(NULL, NULL, "", "LLM endpoint URL (http[s]://host[:port]/path)"); - llm_url_args.end = arg_end(1); - esp_console_cmd_t llm_url_cmd = { - .command = "set_llm_url", - .help = "Set LLM endpoint URL override (e.g. set_llm_url http://192.168.1.10:8000/v1/chat/completions)", - .func = &cmd_set_llm_url, - .argtable = &llm_url_args, - }; - esp_console_cmd_register(&llm_url_cmd); - - /* clear_llm_url */ - esp_console_cmd_t llm_url_clear_cmd = { - .command = "clear_llm_url", - .help = "Clear LLM endpoint URL override (revert to provider default)", - .func = &cmd_clear_llm_url, - }; - esp_console_cmd_register(&llm_url_clear_cmd); - /* skill_list */ esp_console_cmd_t skill_list_cmd = { .command = "skill_list", @@ -1112,25 +977,6 @@ esp_err_t serial_cli_init(void) }; esp_console_cmd_register(&heap_cmd); - /* voice_status */ - esp_console_cmd_t voice_status_cmd = { - .command = "voice_status", - .help = "Show voice channel status", - .func = &cmd_voice_status, - }; - esp_console_cmd_register(&voice_status_cmd); - - /* voice_say */ - voice_say_args.text = arg_str1(NULL, NULL, "", "Text to synthesize and play"); - voice_say_args.end = arg_end(1); - esp_console_cmd_t voice_say_cmd = { - .command = "voice_say", - .help = "Synthesize and play text via TTS", - .func = &cmd_voice_say, - .argtable = &voice_say_args, - }; - esp_console_cmd_register(&voice_say_cmd); - /* set_search_key */ search_key_args.key = arg_str1(NULL, NULL, "", "Brave Search API key"); search_key_args.end = arg_end(1); @@ -1238,4 +1084,4 @@ esp_err_t serial_cli_init(void) ESP_LOGI(TAG, "Serial CLI started"); return ESP_OK; -} +} \ No newline at end of file diff --git a/main/llm/llm_proxy.c b/main/llm/llm_proxy.c index 98b45a7c..adcd74d5 100644 --- a/main/llm/llm_proxy.c +++ b/main/llm/llm_proxy.c @@ -4,7 +4,6 @@ #include #include -#include #include "esp_log.h" #include "esp_http_client.h" #include "esp_crt_bundle.h" @@ -16,17 +15,37 @@ static const char *TAG = "llm"; #define LLM_API_KEY_MAX_LEN 320 #define LLM_MODEL_MAX_LEN 64 -#define LLM_URL_MAX_LEN 512 +#define LLM_API_BASE_MAX_LEN 256 #define LLM_HOST_MAX_LEN 128 -#define LLM_PATH_MAX_LEN 256 +#define LLM_PATH_MAX_LEN 128 #define LLM_DUMP_MAX_BYTES (16 * 1024) #define LLM_DUMP_CHUNK_BYTES 320 static char s_api_key[LLM_API_KEY_MAX_LEN] = {0}; -static char s_api_url_build[LLM_URL_MAX_LEN] = {0}; -static char s_api_url_nvs[LLM_URL_MAX_LEN] = {0}; static char s_model[LLM_MODEL_MAX_LEN] = MIMI_LLM_DEFAULT_MODEL; +static char s_model_id[LLM_MODEL_MAX_LEN] = {0}; static char s_provider[16] = MIMI_LLM_PROVIDER_DEFAULT; +static char s_api_base[LLM_API_BASE_MAX_LEN] = {0}; + +typedef enum { + LLM_PROTOCOL_ANTHROPIC = 0, + LLM_PROTOCOL_OPENAI = 1, +} llm_protocol_t; + +static llm_protocol_t s_protocol = LLM_PROTOCOL_ANTHROPIC; +static bool s_api_tls = true; +static char s_api_host[LLM_HOST_MAX_LEN] = {0}; +static uint16_t s_api_port = 443; +static char s_api_base_path[LLM_PATH_MAX_LEN] = {0}; +static char s_api_req_path[LLM_PATH_MAX_LEN + 32] = {0}; +static char s_api_host_header[LLM_HOST_MAX_LEN + 8] = {0}; +static char s_api_url[LLM_API_BASE_MAX_LEN + 64] = {0}; +static bool s_logged_proxy_bypass_warning = false; + +static const char *llm_protocol_name(llm_protocol_t p) +{ + return (p == LLM_PROTOCOL_OPENAI) ? "openai" : "anthropic"; +} static void llm_log_payload(const char *label, const char *payload) { @@ -186,177 +205,166 @@ static esp_err_t http_event_handler(esp_http_client_event_t *evt) return ESP_OK; } -/* ── Provider helpers ──────────────────────────────────────────── */ - -static bool provider_is_openai(void) -{ - return strcmp(s_provider, "openai") == 0 || strcmp(s_provider, "openai_compat") == 0; -} +/* ── Protocol config ─────────────────────────────────────────── */ -static bool provider_is_anthropic(void) -{ - return strcmp(s_provider, "anthropic") == 0; +typedef struct { + llm_protocol_t protocol; + const char *label; /* "openai" */ + const char *prefix; /* "openai/" */ + const char *suffix; /* "/chat/completions" */ + const char *base; /* Default API base */ +} llm_proto_cfg_t; + +static const llm_proto_cfg_t PROTO_MAP[] = { + {LLM_PROTOCOL_OPENAI, "openai", "openai/", "/chat/completions", MIMI_LLM_API_BASE_OPENAI}, + {LLM_PROTOCOL_ANTHROPIC, "anthropic", "anthropic/", "/messages", MIMI_LLM_API_BASE_ANTHROPIC} +}; + +static const llm_proto_cfg_t* get_current_proto(void) { + return &PROTO_MAP[s_protocol == LLM_PROTOCOL_OPENAI ? 0 : 1]; } -static bool provider_is_known(void) -{ - return provider_is_openai() || provider_is_anthropic(); -} +/* ── Helpers ─────────────────────────────────────────────────── */ -static const char *provider_default_url(void) -{ - return provider_is_openai() ? MIMI_OPENAI_API_URL : MIMI_LLM_API_URL; +static bool llm_protocol_is_openai(void) { + return s_protocol == LLM_PROTOCOL_OPENAI; } -/* ── Endpoint URL parsing ─────────────────────────────────────── */ - -typedef struct { - bool use_tls; /* true for https:// */ - int port; - char url[LLM_URL_MAX_LEN]; /* full URL */ - char host[LLM_HOST_MAX_LEN]; - char path[LLM_PATH_MAX_LEN]; /* origin-form path, includes query */ -} llm_endpoint_t; - -static llm_endpoint_t s_endpoint = {0}; -static bool s_endpoint_valid = false; - -static bool port_is_default(bool use_tls, int port) -{ - return (use_tls && port == 443) || (!use_tls && port == 80); -} +/* Validate api_base format without modifying global state */ +static esp_err_t llm_validate_api_base(const char *api_base) { + if (!api_base || api_base[0] == '\0') return ESP_ERR_INVALID_ARG; -static void build_host_header_value(const llm_endpoint_t *ep, char *out, size_t out_size) -{ - if (!ep || !out || out_size == 0) return; - if (port_is_default(ep->use_tls, ep->port)) { - snprintf(out, out_size, "%s", ep->host); + /* Check for valid scheme */ + const char *p; + if (strncmp(api_base, "https://", 8) == 0) { + p = api_base + 8; + } else if (strncmp(api_base, "http://", 7) == 0) { + p = api_base + 7; } else { - snprintf(out, out_size, "%s:%d", ep->host, ep->port); + return ESP_ERR_INVALID_ARG; } -} -static esp_err_t llm_parse_endpoint_url(const char *url, llm_endpoint_t *out) -{ - if (!url || !url[0] || !out) return ESP_ERR_INVALID_ARG; - - memset(out, 0, sizeof(*out)); - - const char *p = NULL; - if (strncmp(url, "https://", 8) == 0) { - out->use_tls = true; - out->port = 443; - p = url + 8; - } else if (strncmp(url, "http://", 7) == 0) { - out->use_tls = false; - out->port = 80; - p = url + 7; - } else { + /* Basic format validation - ensure there's content after the scheme */ + if (p[0] == '\0' || p[0] == '/' || p[0] == ':') { return ESP_ERR_INVALID_ARG; } - /* Parse host[:port] */ - const char *hp_end = p; - while (*hp_end && *hp_end != '/' && *hp_end != '?' && *hp_end != '#') hp_end++; - if (hp_end == p) return ESP_ERR_INVALID_ARG; - - char hostport[LLM_HOST_MAX_LEN + 16] = {0}; - size_t hp_len = (size_t)(hp_end - p); - if (hp_len >= sizeof(hostport)) return ESP_ERR_INVALID_SIZE; - memcpy(hostport, p, hp_len); - hostport[hp_len] = '\0'; - - char *port_part = NULL; - char *colon = strrchr(hostport, ':'); - if (colon && strchr(hostport, ':') == colon) { - *colon = '\0'; - port_part = colon + 1; - } + /* Check for valid host part (before colon or slash) */ + const char *slash = strchr(p, '/'); + const char *colon = strchr(p, ':'); + if (colon && slash && colon > slash) colon = NULL; /* Colon is part of path */ - if (hostport[0] == '\0') return ESP_ERR_INVALID_ARG; - safe_copy(out->host, sizeof(out->host), hostport); + const char *host_end = colon ? colon : (slash ? slash : p + strlen(p)); + if (host_end == p) return ESP_ERR_INVALID_ARG; /* Empty host */ - if (port_part && port_part[0]) { - char *endp = NULL; - long port = strtol(port_part, &endp, 10); - if (!endp || *endp != '\0' || port <= 0 || port > 65535) { + /* Validate port if present */ + if (colon) { + char *endptr; + long port = strtol(colon + 1, &endptr, 10); + if (endptr == colon + 1 || (*endptr != '\0' && *endptr != '/') || + port < 1 || port > 65535) { return ESP_ERR_INVALID_ARG; } - out->port = (int)port; } - /* Parse path (+ query), ignore fragment */ - const char *path = hp_end; - const char *frag = strchr(path, '#'); - size_t path_len = frag ? (size_t)(frag - path) : strlen(path); - - if (path_len == 0) { - safe_copy(out->path, sizeof(out->path), "/"); - } else if (path[0] == '?') { - if (path_len + 1 >= sizeof(out->path)) return ESP_ERR_INVALID_SIZE; - out->path[0] = '/'; - memcpy(out->path + 1, path, path_len); - out->path[path_len + 1] = '\0'; - } else { - if (path[0] != '/') return ESP_ERR_INVALID_ARG; - if (path_len >= sizeof(out->path)) return ESP_ERR_INVALID_SIZE; - memcpy(out->path, path, path_len); - out->path[path_len] = '\0'; + return ESP_OK; +} + +/* Parse api_base: scheme (http/https), host[:port], optional base path. */ +static esp_err_t llm_parse_api_base(const char *api_base) { + if (!api_base || api_base[0] == '\0') return ESP_ERR_INVALID_ARG; + + const char *p; + if (strncmp(api_base, "https://", 8) == 0) { + s_api_tls = true; p = api_base + 8; s_api_port = 443; + } else if (strncmp(api_base, "http://", 7) == 0) { + s_api_tls = false; p = api_base + 7; s_api_port = 80; + } else return ESP_ERR_INVALID_ARG; + + const char *slash = strchr(p, '/'); + const char *colon = strchr(p, ':'); + if (colon && slash && colon > slash) colon = NULL; /* Colon is part of path */ + + const char *host_end = colon ? colon : (slash ? slash : p + strlen(p)); + snprintf(s_api_host, sizeof(s_api_host), "%.*s", (int)(host_end - p), p); + + if (colon) { + char *endptr; + long port = strtol(colon + 1, &endptr, 10); + if (endptr != colon + 1 && (*endptr == '\0' || *endptr == '/') && + port >= 1 && port <= 65535) { + s_api_port = (uint16_t)port; + } + /* If port parsing fails, keep the default port (443 for HTTPS, 80 for HTTP) */ } - safe_copy(out->url, sizeof(out->url), url); + s_api_base_path[0] = '\0'; + if (slash) { + safe_copy(s_api_base_path, sizeof(s_api_base_path), slash); + size_t len = strlen(s_api_base_path); + while (len > 0 && s_api_base_path[len - 1] == '/') s_api_base_path[--len] = '\0'; + } return ESP_OK; } -static esp_err_t llm_endpoint_refresh(void) -{ - const char *url = NULL; - if (s_api_url_nvs[0] != '\0') { - url = s_api_url_nvs; - } else if (s_api_url_build[0] != '\0') { - url = s_api_url_build; +/* Build derived request path, Host header, and full URL strings. */ +static void llm_build_request_targets(void) { + const llm_proto_cfg_t *cfg = get_current_proto(); + + snprintf(s_api_req_path, sizeof(s_api_req_path), "%s%s", s_api_base_path, cfg->suffix); + if (s_api_req_path[0] == '\0') strcpy(s_api_req_path, "/"); + + bool is_std = (s_api_tls && s_api_port == 443) || (!s_api_tls && s_api_port == 80); + if (is_std) { + snprintf(s_api_host_header, sizeof(s_api_host_header), "%s", s_api_host); } else { - url = provider_default_url(); + snprintf(s_api_host_header, sizeof(s_api_host_header), "%s:%u", s_api_host, s_api_port); } - llm_endpoint_t ep; - esp_err_t err = llm_parse_endpoint_url(url, &ep); - if (err != ESP_OK) { - s_endpoint_valid = false; - memset(&s_endpoint, 0, sizeof(s_endpoint)); - safe_copy(s_endpoint.url, sizeof(s_endpoint.url), url); - return err; + snprintf(s_api_url, sizeof(s_api_url), "%s://%s%s", + s_api_tls ? "https" : "http", s_api_host_header, s_api_req_path); +} + +/* ── Derived config ──────────────────────────────────────────── */ + +static void llm_recompute_effective_config(void) { + /* Determine protocol + model_id (prefix overrides provider), and update request targets. */ + s_logged_proxy_bypass_warning = false; /* Reset warning flag when config changes */ + s_protocol = (strcmp(s_provider, "openai") == 0) ? LLM_PROTOCOL_OPENAI : LLM_PROTOCOL_ANTHROPIC; + const char *model_id = s_model; + + for (int i = 0; i < 2; i++) { + size_t len = strlen(PROTO_MAP[i].prefix); + if (strncmp(s_model, PROTO_MAP[i].prefix, len) == 0 && s_model[len] != '\0') { + s_protocol = PROTO_MAP[i].protocol; + model_id = s_model + len; + break; + } } + safe_copy(s_model_id, sizeof(s_model_id), model_id); - s_endpoint = ep; - s_endpoint_valid = true; + const char *default_base = get_current_proto()->base; + const char *base = (s_api_base[0] != '\0') ? s_api_base : default_base; - if (!s_endpoint.use_tls) { - ESP_LOGW(TAG, "LLM endpoint is insecure HTTP: %s", s_endpoint.url); + if (llm_parse_api_base(base) != ESP_OK) { + ESP_LOGE(TAG, "Failed to parse API base: %s. Using default.", base); + llm_parse_api_base(default_base); } - return ESP_OK; -} -const char *llm_get_api_url(void) -{ - return s_endpoint.url; -} + llm_build_request_targets(); -bool llm_api_url_is_valid(void) -{ - return s_endpoint_valid; + ESP_LOGI(TAG, "Configured: Protocol=%s, Model=%s, URL=%s", + get_current_proto()->label, s_model_id, s_api_url); } -/* ── Init ─────────────────────────────────────────────────────── */ - esp_err_t llm_proxy_init(void) { /* Start with build-time defaults */ if (MIMI_SECRET_API_KEY[0] != '\0') { safe_copy(s_api_key, sizeof(s_api_key), MIMI_SECRET_API_KEY); } - if (MIMI_SECRET_LLM_API_URL[0] != '\0') { - safe_copy(s_api_url_build, sizeof(s_api_url_build), MIMI_SECRET_LLM_API_URL); + if (MIMI_SECRET_API_BASE[0] != '\0') { + safe_copy(s_api_base, sizeof(s_api_base), MIMI_SECRET_API_BASE); } if (MIMI_SECRET_MODEL[0] != '\0') { safe_copy(s_model, sizeof(s_model), MIMI_SECRET_MODEL); @@ -373,6 +381,11 @@ esp_err_t llm_proxy_init(void) if (nvs_get_str(nvs, MIMI_NVS_KEY_API_KEY, tmp, &len) == ESP_OK && tmp[0]) { safe_copy(s_api_key, sizeof(s_api_key), tmp); } + char base_tmp[LLM_API_BASE_MAX_LEN] = {0}; + len = sizeof(base_tmp); + if (nvs_get_str(nvs, MIMI_NVS_KEY_API_BASE, base_tmp, &len) == ESP_OK && base_tmp[0]) { + safe_copy(s_api_base, sizeof(s_api_base), base_tmp); + } char model_tmp[LLM_MODEL_MAX_LEN] = {0}; len = sizeof(model_tmp); if (nvs_get_str(nvs, MIMI_NVS_KEY_MODEL, model_tmp, &len) == ESP_OK && model_tmp[0]) { @@ -383,29 +396,12 @@ esp_err_t llm_proxy_init(void) if (nvs_get_str(nvs, MIMI_NVS_KEY_PROVIDER, provider_tmp, &len) == ESP_OK && provider_tmp[0]) { safe_copy(s_provider, sizeof(s_provider), provider_tmp); } - - char url_tmp[LLM_URL_MAX_LEN] = {0}; - len = sizeof(url_tmp); - if (nvs_get_str(nvs, MIMI_NVS_KEY_API_URL, url_tmp, &len) == ESP_OK && url_tmp[0]) { - safe_copy(s_api_url_nvs, sizeof(s_api_url_nvs), url_tmp); - } nvs_close(nvs); } - if (!provider_is_known()) { - ESP_LOGW(TAG, "Unknown provider '%s'. Expected: anthropic|openai|openai_compat", s_provider); - } + llm_recompute_effective_config(); - esp_err_t url_err = llm_endpoint_refresh(); - if (url_err != ESP_OK) { - ESP_LOGE(TAG, "Invalid LLM endpoint URL: %s (use set_llm_url )", s_endpoint.url[0] ? s_endpoint.url : "(empty)"); - } - - if (s_api_key[0]) { - ESP_LOGI(TAG, "LLM proxy initialized (provider: %s, model: %s, url: %s)", - s_provider, s_model, - s_endpoint_valid ? s_endpoint.url : "(invalid)"); - } else { + if (s_api_key[0] == '\0') { ESP_LOGW(TAG, "No API key. Use CLI: set_api_key "); } return ESP_OK; @@ -415,10 +411,8 @@ esp_err_t llm_proxy_init(void) static esp_err_t llm_http_direct(const char *post_data, resp_buf_t *rb, int *out_status) { - if (!s_endpoint_valid) return ESP_ERR_INVALID_STATE; - esp_http_client_config_t config = { - .url = s_endpoint.url, + .url = s_api_url, .event_handler = http_event_handler, .user_data = rb, .timeout_ms = 120 * 1000, @@ -432,14 +426,16 @@ static esp_err_t llm_http_direct(const char *post_data, resp_buf_t *rb, int *out esp_http_client_set_method(client, HTTP_METHOD_POST); esp_http_client_set_header(client, "Content-Type", "application/json"); - if (provider_is_openai()) { + if (llm_protocol_is_openai()) { if (s_api_key[0]) { char auth[LLM_API_KEY_MAX_LEN + 16]; snprintf(auth, sizeof(auth), "Bearer %s", s_api_key); esp_http_client_set_header(client, "Authorization", auth); } } else { - esp_http_client_set_header(client, "x-api-key", s_api_key); + if (s_api_key[0] != '\0') { + esp_http_client_set_header(client, "x-api-key", s_api_key); + } esp_http_client_set_header(client, "anthropic-version", MIMI_LLM_API_VERSION); } esp_http_client_set_post_field(client, post_data, strlen(post_data)); @@ -454,106 +450,71 @@ static esp_err_t llm_http_direct(const char *post_data, resp_buf_t *rb, int *out static esp_err_t llm_http_via_proxy(const char *post_data, resp_buf_t *rb, int *out_status) { - if (!s_endpoint_valid) return ESP_ERR_INVALID_STATE; - - const char *path = (s_endpoint.path[0] != '\0') ? s_endpoint.path : "/"; - char host_hdr[LLM_HOST_MAX_LEN + 16] = {0}; - build_host_header_value(&s_endpoint, host_hdr, sizeof(host_hdr)); - - int body_len = (int)strlen(post_data); - char header[1024]; - int hlen = 0; - if (provider_is_openai()) { - hlen = snprintf(header, sizeof(header), - "POST %s HTTP/1.1\r\n" - "Host: %s\r\n" - "Content-Type: application/json\r\n" - "Authorization: Bearer %s\r\n" - "Content-Length: %d\r\n" - "Connection: close\r\n\r\n", - path, host_hdr, s_api_key, body_len); - } else { - hlen = snprintf(header, sizeof(header), - "POST %s HTTP/1.1\r\n" - "Host: %s\r\n" - "Content-Type: application/json\r\n" - "x-api-key: %s\r\n" - "anthropic-version: %s\r\n" - "Content-Length: %d\r\n" - "Connection: close\r\n\r\n", - path, host_hdr, s_api_key, MIMI_LLM_API_VERSION, body_len); - } - - if (s_endpoint.use_tls) { - proxy_conn_t *conn = proxy_conn_open(s_endpoint.host, s_endpoint.port, 30000); - if (!conn) return ESP_ERR_HTTP_CONNECT; + proxy_conn_t *conn = proxy_conn_open(s_api_host, s_api_port, 30000); + if (!conn) return ESP_ERR_HTTP_CONNECT; - if (proxy_conn_write(conn, header, hlen) < 0 || - proxy_conn_write(conn, post_data, body_len) < 0) { - proxy_conn_close(conn); - return ESP_ERR_HTTP_WRITE_DATA; - } + /* Build request headers */ + char h[1024]; + int off = snprintf(h, sizeof(h), "POST %s HTTP/1.1\r\nHost: %s\r\nContent-Type: application/json\r\n", + s_api_req_path, s_api_host_header); - /* Read full response into buffer */ - char tmp[4096]; - while (1) { - int n = proxy_conn_read(conn, tmp, sizeof(tmp), 120000); - if (n <= 0) break; - if (resp_buf_append(rb, tmp, n) != ESP_OK) break; + if (llm_protocol_is_openai()) { + if (s_api_key[0] != '\0') { + off += snprintf(h + off, sizeof(h) - off, "Authorization: Bearer %s\r\n", s_api_key); } - proxy_conn_close(conn); } else { - int sock = proxy_tunnel_open(s_endpoint.host, s_endpoint.port, 30000); - if (sock < 0) return ESP_ERR_HTTP_CONNECT; - - if (proxy_tunnel_write(sock, header, hlen) < 0 || - proxy_tunnel_write(sock, post_data, body_len) < 0) { - proxy_tunnel_close(sock); - return ESP_ERR_HTTP_WRITE_DATA; + if (s_api_key[0] != '\0') { + off += snprintf(h + off, sizeof(h) - off, "x-api-key: %s\r\n", s_api_key); } + off += snprintf(h + off, sizeof(h) - off, "anthropic-version: %s\r\n", MIMI_LLM_API_VERSION); + } - /* Read full response into buffer */ - char tmp[4096]; - while (1) { - int n = proxy_tunnel_read(sock, tmp, sizeof(tmp), 120000); - if (n <= 0) break; - if (resp_buf_append(rb, tmp, (size_t)n) != ESP_OK) break; - } - proxy_tunnel_close(sock); + off += snprintf(h + off, sizeof(h) - off, "Content-Length: %zu\r\nConnection: close\r\n\r\n", strlen(post_data)); + + /* Send */ + if (off >= sizeof(h) || proxy_conn_write(conn, h, off) < 0 || + proxy_conn_write(conn, post_data, strlen(post_data)) < 0) { + proxy_conn_close(conn); + return ESP_ERR_HTTP_WRITE_DATA; } - /* Parse status line */ - *out_status = 0; - if (rb->len > 5 && strncmp(rb->data, "HTTP/", 5) == 0) { - const char *sp = strchr(rb->data, ' '); - if (sp) *out_status = atoi(sp + 1); + /* Receive full response */ + char tmp[1024]; + int n; + while ((n = proxy_conn_read(conn, tmp, sizeof(tmp), 120000)) > 0) { + if (resp_buf_append(rb, tmp, n) != ESP_OK) break; + vTaskDelay(pdMS_TO_TICKS(1)); } + proxy_conn_close(conn); - /* Strip HTTP headers, keep body only */ + /* Parse status */ + *out_status = (rb->len > 12 && strncmp(rb->data, "HTTP/", 5) == 0) ? atoi(rb->data + 9) : 0; + + /* Strip headers */ char *body = strstr(rb->data, "\r\n\r\n"); if (body) { body += 4; - size_t blen = rb->len - (body - rb->data); - memmove(rb->data, body, blen); - rb->len = blen; + rb->len -= (body - rb->data); + memmove(rb->data, body, rb->len); rb->data[rb->len] = '\0'; } - /* Decode chunked transfer encoding if present */ resp_buf_decode_chunked(rb); - return ESP_OK; } -/* ── Shared HTTP dispatch ─────────────────────────────────────── */ - static esp_err_t llm_http_call(const char *post_data, resp_buf_t *rb, int *out_status) { if (http_proxy_is_enabled()) { - return llm_http_via_proxy(post_data, rb, out_status); - } else { - return llm_http_direct(post_data, rb, out_status); + if (s_api_tls) { + return llm_http_via_proxy(post_data, rb, out_status); + } + if (!s_logged_proxy_bypass_warning) { + ESP_LOGW(TAG, "Proxy configured but api_base is http; bypassing proxy"); + s_logged_proxy_bypass_warning = true; + } } + return llm_http_direct(post_data, rb, out_status); } static cJSON *convert_tools_openai(const char *tools_json) @@ -747,20 +708,16 @@ esp_err_t llm_chat_tools(const char *system_prompt, { memset(resp, 0, sizeof(*resp)); - if (s_api_key[0] == '\0') return ESP_ERR_INVALID_STATE; - if (!provider_is_known()) return ESP_ERR_INVALID_ARG; - if (!s_endpoint_valid) return ESP_ERR_INVALID_STATE; - /* Build request body (non-streaming) */ cJSON *body = cJSON_CreateObject(); - cJSON_AddStringToObject(body, "model", s_model); - if (provider_is_openai()) { - cJSON_AddNumberToObject(body, "max_tokens", MIMI_LLM_MAX_TOKENS); + cJSON_AddStringToObject(body, "model", s_model_id); + if (strncasecmp(s_model_id, "gpt-5", 5) == 0 || strncasecmp(s_model_id, "o1", 2) == 0) { + cJSON_AddNumberToObject(body, "max_completion_tokens", MIMI_LLM_MAX_TOKENS); } else { cJSON_AddNumberToObject(body, "max_tokens", MIMI_LLM_MAX_TOKENS); } - if (provider_is_openai()) { + if (llm_protocol_is_openai()) { cJSON *openai_msgs = convert_messages_openai(system_prompt, messages); cJSON_AddItemToObject(body, "messages", openai_msgs); @@ -791,8 +748,8 @@ esp_err_t llm_chat_tools(const char *system_prompt, cJSON_Delete(body); if (!post_data) return ESP_ERR_NO_MEM; - ESP_LOGI(TAG, "Calling LLM API with tools (provider: %s, model: %s, body: %d bytes)", - s_provider, s_model, (int)strlen(post_data)); + ESP_LOGI(TAG, "Calling LLM API with tools (protocol: %s, model: %s, body: %d bytes)", + llm_protocol_name(s_protocol), s_model_id, (int)strlen(post_data)); llm_log_payload("LLM tools request", post_data); /* HTTP call */ @@ -830,7 +787,7 @@ esp_err_t llm_chat_tools(const char *system_prompt, return ESP_FAIL; } - if (provider_is_openai()) { + if (llm_protocol_is_openai()) { cJSON *choices = cJSON_GetObjectItem(root, "choices"); cJSON *choice0 = choices && cJSON_IsArray(choices) ? cJSON_GetArrayItem(choices, 0) : NULL; if (choice0) { @@ -979,40 +936,24 @@ esp_err_t llm_set_api_key(const char *api_key) return ESP_OK; } -esp_err_t llm_set_api_url(const char *url) +esp_err_t llm_set_api_base(const char *api_base) { - if (!url || !url[0]) return ESP_ERR_INVALID_ARG; - - llm_endpoint_t ep; - esp_err_t err = llm_parse_endpoint_url(url, &ep); + /* Validate before persisting - use validation-only function */ + esp_err_t err = llm_validate_api_base(api_base); if (err != ESP_OK) { - ESP_LOGE(TAG, "Invalid LLM URL: %s (expected http[s]://host[:port]/path)", url); - return ESP_ERR_INVALID_ARG; + ESP_LOGE(TAG, "Invalid API base format: %s", api_base ? api_base : ""); + return err; } nvs_handle_t nvs; ESP_ERROR_CHECK(nvs_open(MIMI_NVS_LLM, NVS_READWRITE, &nvs)); - ESP_ERROR_CHECK(nvs_set_str(nvs, MIMI_NVS_KEY_API_URL, url)); - ESP_ERROR_CHECK(nvs_commit(nvs)); - nvs_close(nvs); - - safe_copy(s_api_url_nvs, sizeof(s_api_url_nvs), url); - (void)llm_endpoint_refresh(); - ESP_LOGI(TAG, "LLM URL set to: %s", s_endpoint.url); - return ESP_OK; -} - -esp_err_t llm_clear_api_url(void) -{ - nvs_handle_t nvs; - ESP_ERROR_CHECK(nvs_open(MIMI_NVS_LLM, NVS_READWRITE, &nvs)); - nvs_erase_key(nvs, MIMI_NVS_KEY_API_URL); + ESP_ERROR_CHECK(nvs_set_str(nvs, MIMI_NVS_KEY_API_BASE, api_base)); ESP_ERROR_CHECK(nvs_commit(nvs)); nvs_close(nvs); - s_api_url_nvs[0] = '\0'; - (void)llm_endpoint_refresh(); - ESP_LOGI(TAG, "LLM URL override cleared (url: %s)", s_endpoint.url); + safe_copy(s_api_base, sizeof(s_api_base), api_base); + llm_recompute_effective_config(); + ESP_LOGI(TAG, "API base set"); return ESP_OK; } @@ -1025,20 +966,13 @@ esp_err_t llm_set_model(const char *model) nvs_close(nvs); safe_copy(s_model, sizeof(s_model), model); + llm_recompute_effective_config(); ESP_LOGI(TAG, "Model set to: %s", s_model); return ESP_OK; } esp_err_t llm_set_provider(const char *provider) { - if (!provider || !provider[0]) return ESP_ERR_INVALID_ARG; - if (strcmp(provider, "openai") != 0 && - strcmp(provider, "openai_compat") != 0 && - strcmp(provider, "anthropic") != 0) { - ESP_LOGE(TAG, "Invalid provider: %s (expected anthropic|openai|openai_compat)", provider); - return ESP_ERR_INVALID_ARG; - } - nvs_handle_t nvs; ESP_ERROR_CHECK(nvs_open(MIMI_NVS_LLM, NVS_READWRITE, &nvs)); ESP_ERROR_CHECK(nvs_set_str(nvs, MIMI_NVS_KEY_PROVIDER, provider)); @@ -1046,7 +980,7 @@ esp_err_t llm_set_provider(const char *provider) nvs_close(nvs); safe_copy(s_provider, sizeof(s_provider), provider); - (void)llm_endpoint_refresh(); + llm_recompute_effective_config(); ESP_LOGI(TAG, "Provider set to: %s", s_provider); return ESP_OK; -} +} \ No newline at end of file diff --git a/main/llm/llm_proxy.h b/main/llm/llm_proxy.h index 7cef99da..7d333b84 100644 --- a/main/llm/llm_proxy.h +++ b/main/llm/llm_proxy.h @@ -12,34 +12,23 @@ */ esp_err_t llm_proxy_init(void); -/** - * Get the active LLM endpoint URL (may be empty if not configured). - * - * Note: this returns the resolved runtime URL (override or provider default). - */ -const char *llm_get_api_url(void); - -/** Returns true if the active LLM endpoint URL parsed successfully. */ -bool llm_api_url_is_valid(void); - /** * Save the LLM API key to NVS. */ esp_err_t llm_set_api_key(const char *api_key); /** - * Save the LLM endpoint URL to NVS. Must include scheme/host/path. + * Save the LLM API base URL to NVS. * + * Expected format: http(s)://host[:port][/path] * Examples: - * - https://api.openai.com/v1/chat/completions - * - http://192.168.1.10:8000/v1/chat/completions - */ -esp_err_t llm_set_api_url(const char *url); - -/** - * Clear the LLM endpoint URL override from NVS (revert to provider default). + * - https://api.anthropic.com/v1 + * - https://api.openai.com/v1 + * - http://localhost:11434/v1 + * - https://api.minimaxi.com/anthropic/v1 + * - https://open.bigmodel.cn/api/paas/v4 */ -esp_err_t llm_clear_api_url(void); +esp_err_t llm_set_api_base(const char *api_base); /** * Save the LLM provider to NVS. (e.g. "anthropic", "openai") @@ -82,4 +71,4 @@ void llm_response_free(llm_response_t *resp); esp_err_t llm_chat_tools(const char *system_prompt, cJSON *messages, const char *tools_json, - llm_response_t *resp); + llm_response_t *resp); \ No newline at end of file diff --git a/main/mimi.c b/main/mimi.c index 246c3db8..430b9b88 100644 --- a/main/mimi.c +++ b/main/mimi.c @@ -111,11 +111,11 @@ static void outbound_dispatch_task(void *arg) BaseType_t ok = xTaskCreatePinnedToCore( voice_speak_task, "voice_speak", - 12 * 1024, + MIMI_VOICE_SPEAK_STACK, copy, - 5, + MIMI_VOICE_SPEAK_PRIO, NULL, - 0 + MIMI_VOICE_SPEAK_CORE ); if (ok != pdPASS) { ESP_LOGW(TAG, "Failed to create voice_speak task"); diff --git a/main/mimi_config.h b/main/mimi_config.h index f6d0f062..68f99369 100644 --- a/main/mimi_config.h +++ b/main/mimi_config.h @@ -71,7 +71,7 @@ #define MIMI_SECRET_TTS_MODEL "" #endif #ifndef MIMI_SECRET_TTS_LANGUAGE -#define MIMI_SECRET_TTS_LANGUAGE "Chinese" +#define MIMI_SECRET_TTS_LANGUAGE "English" #endif /* Qwen voice API defaults (DashScope) */ @@ -94,37 +94,6 @@ #define MIMI_TG_CARD_SHOW_MS 3000 #define MIMI_TG_CARD_BODY_SCALE 3 -/* Voice (ReSpeaker XVF3800 over I2S) */ -#define MIMI_VOICE_ENABLED_DEFAULT 0 -#define MIMI_VOICE_CHAT_ID "voice_local" -#define MIMI_VOICE_SAMPLE_RATE 16000 -#define MIMI_VOICE_BITS_PER_SAMPLE 16 -#define MIMI_VOICE_FRAME_MS 20 -#define MIMI_VOICE_MAX_UTTERANCE_MS 10000 -#define MIMI_VOICE_SILENCE_END_MS 600 -#define MIMI_VOICE_VAD_THRESHOLD 700 -#define MIMI_VOICE_CAPTURE_STACK (10 * 1024) -#define MIMI_VOICE_PLAYBACK_STACK (8 * 1024) -#define MIMI_VOICE_TASK_PRIO 5 -#define MIMI_VOICE_CORE 0 -#define MIMI_VOICE_PLAYBACK_QUEUE_LEN 4 -/* Set valid board pins in mimi_secrets.h to enable audio I/O */ -#ifndef MIMI_VOICE_I2S_PORT -#define MIMI_VOICE_I2S_PORT 0 -#endif -#ifndef MIMI_VOICE_I2S_BCLK -#define MIMI_VOICE_I2S_BCLK (-1) -#endif -#ifndef MIMI_VOICE_I2S_WS -#define MIMI_VOICE_I2S_WS (-1) -#endif -#ifndef MIMI_VOICE_I2S_DIN -#define MIMI_VOICE_I2S_DIN (-1) -#endif -#ifndef MIMI_VOICE_I2S_DOUT -#define MIMI_VOICE_I2S_DOUT (-1) -#endif - /* Feishu Bot */ #define MIMI_FEISHU_MAX_MSG_LEN 4096 #define MIMI_FEISHU_POLL_STACK (12 * 1024) @@ -143,6 +112,40 @@ #define MIMI_MAX_TOOL_CALLS 4 #define MIMI_AGENT_SEND_WORKING_STATUS 1 +/* Voice UX (LLM -> TTS) */ +/* Rough speaking rate for Simplified Chinese TTS is often ~4–6 chars/sec depending on voice. + * Default limits aim to keep playback under ~20 seconds in typical conditions. + * Override these in mimi_secrets.h per your preferred voice/speed. + */ +#ifndef MIMI_VOICE_TTS_MAX_SECONDS +#define MIMI_VOICE_TTS_MAX_SECONDS 20 +#endif + +#ifndef MIMI_VOICE_TTS_CHARS_PER_SEC +#define MIMI_VOICE_TTS_CHARS_PER_SEC 7 +#endif + +#ifndef MIMI_VOICE_LLM_MAX_CHARS +#define MIMI_VOICE_LLM_MAX_CHARS (MIMI_VOICE_TTS_MAX_SECONDS * MIMI_VOICE_TTS_CHARS_PER_SEC) +#endif + +#ifndef MIMI_VOICE_TTS_MAX_CHARS +#define MIMI_VOICE_TTS_MAX_CHARS (MIMI_VOICE_LLM_MAX_CHARS + 10) +#endif + +/* Voice capture (VAD / STT trigger) */ +#ifndef MIMI_VOICE_VAD_START_FRAMES +#define MIMI_VOICE_VAD_START_FRAMES 4 /* consecutive frames above threshold to enter speech */ +#endif + +#ifndef MIMI_VOICE_VAD_MIN_FRAMES +#define MIMI_VOICE_VAD_MIN_FRAMES 50 /* minimum utterance frames before sending to STT */ +#endif + +#ifndef MIMI_VOICE_STT_COOLDOWN_MS +#define MIMI_VOICE_STT_COOLDOWN_MS 2000 /* cooldown after an STT attempt to reduce re-trigger */ +#endif + /* Timezone (POSIX TZ format) */ #define MIMI_TIMEZONE "PST8PDT,M3.2.0,M11.1.0" @@ -150,8 +153,8 @@ #define MIMI_LLM_DEFAULT_MODEL "claude-opus-4-5" #define MIMI_LLM_PROVIDER_DEFAULT "anthropic" #define MIMI_LLM_MAX_TOKENS 4096 -#define MIMI_LLM_API_URL "https://api.anthropic.com/v1/messages" -#define MIMI_OPENAI_API_URL "https://api.openai.com/v1/chat/completions" +#define MIMI_LLM_API_BASE_ANTHROPIC "https://api.anthropic.com/v1" +#define MIMI_LLM_API_BASE_OPENAI "https://api.openai.com/v1" #define MIMI_LLM_API_VERSION "2023-06-01" #define MIMI_LLM_STREAM_BUF_SIZE (32 * 1024) #define MIMI_LLM_LOG_VERBOSE_PAYLOAD 0 @@ -163,6 +166,22 @@ #define MIMI_OUTBOUND_PRIO 5 #define MIMI_OUTBOUND_CORE 0 +/* Voice speak task (TTS download + resample + playback) */ +#ifndef MIMI_VOICE_SPEAK_STACK +#define MIMI_VOICE_SPEAK_STACK (12 * 1024) +#endif +#ifndef MIMI_VOICE_SPEAK_PRIO +#define MIMI_VOICE_SPEAK_PRIO 5 +#endif +#ifndef MIMI_VOICE_SPEAK_CORE +#define MIMI_VOICE_SPEAK_CORE 1 +#endif + +/* WiFi reliability */ +#ifndef MIMI_WIFI_DISABLE_POWERSAVE +#define MIMI_WIFI_DISABLE_POWERSAVE 1 +#endif + /* Memory / SPIFFS */ #define MIMI_SPIFFS_BASE "/spiffs" #define MIMI_SPIFFS_CONFIG_DIR MIMI_SPIFFS_BASE "/config" @@ -208,8 +227,8 @@ #define MIMI_NVS_KEY_FEISHU_APP_ID "app_id" #define MIMI_NVS_KEY_FEISHU_APP_SECRET "app_secret" #define MIMI_NVS_KEY_API_KEY "api_key" +#define MIMI_NVS_KEY_API_BASE "api_base" #define MIMI_NVS_KEY_TAVILY_KEY "tavily_key" -#define MIMI_NVS_KEY_API_URL "api_url" #define MIMI_NVS_KEY_MODEL "model" #define MIMI_NVS_KEY_PROVIDER "provider" #define MIMI_NVS_KEY_PROXY_HOST "host" diff --git a/main/mimi_secrets.h.example b/main/mimi_secrets.h.example index 9a8f1966..1852f66c 100644 --- a/main/mimi_secrets.h.example +++ b/main/mimi_secrets.h.example @@ -43,7 +43,7 @@ #define MIMI_SECRET_TTS_API_KEY "" #define MIMI_SECRET_TTS_VOICE "Cherry" #define MIMI_SECRET_TTS_MODEL "" -#define MIMI_SECRET_TTS_LANGUAGE "Chinese" +#define MIMI_SECRET_TTS_LANGUAGE "English" /* ReSpeaker XVF3800 I2S pin map (set per board) */ #define MIMI_VOICE_I2S_PORT 0 @@ -51,5 +51,36 @@ #define MIMI_VOICE_I2S_WS (-1) #define MIMI_VOICE_I2S_DIN (-1) #define MIMI_VOICE_I2S_DOUT (-1) + +/* I2S slot/timing style (set per DAC/codec): + * 0: Philips (I2S) + * 1: MSB (left-justified) + * 2: PCM (short frame sync) + */ +/* #define MIMI_VOICE_I2S_STD_SLOT_STYLE 1 */ + +/* Optional: tune DMA and silence tail to suppress post-playback "咚咚" on some DAC/amps */ +/* #define MIMI_VOICE_I2S_DMA_DESC_NUM 6 */ +/* #define MIMI_VOICE_I2S_DMA_FRAME_NUM 240 */ +/* #define MIMI_VOICE_TX_SILENCE_TAIL_MS 400 */ + +/* Optional: voice conversation pacing (LLM -> TTS) + * Target: <= 20s playback, <= 2 sentences + 1 follow-up question. + */ +/* #define MIMI_VOICE_TTS_MAX_SECONDS 20 */ +/* #define MIMI_VOICE_TTS_CHARS_PER_SEC 5 */ +/* #define MIMI_VOICE_LLM_MAX_CHARS 100 */ +/* #define MIMI_VOICE_TTS_MAX_CHARS 110 */ + +/* Optional: reduce STT false triggers (VAD tuning) */ +/* #define MIMI_VOICE_VAD_START_FRAMES 3 */ +/* #define MIMI_VOICE_VAD_MIN_FRAMES 15 */ +/* #define MIMI_VOICE_STT_COOLDOWN_MS 1200 */ + +/* Optional: WiFi reliability tuning (may increase power draw) */ +/* #define MIMI_WIFI_DISABLE_POWERSAVE 1 */ + +/* Optional: move TTS/resample/playback off WiFi core to reduce bcn_timeout under load */ +/* #define MIMI_VOICE_SPEAK_CORE 1 */ /* Tavily Search API */ #define MIMI_SECRET_TAVILY_KEY "" diff --git a/main/tools/tool_cron.c b/main/tools/tool_cron.c index 048e8902..5670678f 100644 --- a/main/tools/tool_cron.c +++ b/main/tools/tool_cron.c @@ -66,16 +66,21 @@ esp_err_t tool_cron_add_execute(const char *input_json, char *output, size_t out job.delete_after_run = false; } else if (strcmp(schedule_type, "at") == 0) { job.kind = CRON_KIND_AT; - cJSON *at_epoch = cJSON_GetObjectItem(root, "at_epoch"); - if (!at_epoch || !cJSON_IsNumber(at_epoch)) { - snprintf(output, output_size, "Error: 'at' schedule requires 'at_epoch' (unix timestamp)"); - cJSON_Delete(root); - return ESP_ERR_INVALID_ARG; + time_t now = time(NULL); + cJSON *delay_s = cJSON_GetObjectItem(root, "delay_s"); + if (delay_s && cJSON_IsNumber(delay_s) && delay_s->valuedouble > 0) { + job.at_epoch = (int64_t)now + (int64_t)delay_s->valuedouble; + } else { + cJSON *at_epoch = cJSON_GetObjectItem(root, "at_epoch"); + if (!at_epoch || !cJSON_IsNumber(at_epoch)) { + snprintf(output, output_size, "Error: 'at' schedule requires 'at_epoch' (unix timestamp) or positive 'delay_s'"); + cJSON_Delete(root); + return ESP_ERR_INVALID_ARG; + } + job.at_epoch = (int64_t)at_epoch->valuedouble; } - job.at_epoch = (int64_t)at_epoch->valuedouble; /* Check if already in the past */ - time_t now = time(NULL); if (job.at_epoch <= now) { snprintf(output, output_size, "Error: at_epoch %lld is in the past (now=%lld)", (long long)job.at_epoch, (long long)now); diff --git a/main/tools/tool_registry.c b/main/tools/tool_registry.c index 6c82a3ef..e6251f8a 100644 --- a/main/tools/tool_registry.c +++ b/main/tools/tool_registry.c @@ -135,14 +135,15 @@ esp_err_t tool_registry_init(void) /* Register cron_add */ mimi_tool_t ca = { .name = "cron_add", - .description = "Schedule a recurring or one-shot task. The message will trigger an agent turn when the job fires.", + .description = "Schedule a recurring or one-shot task. For relative reminders (e.g. 'in 2 minutes'), prefer delay_s to avoid timestamp math. The message will trigger an agent turn when the job fires.", .input_schema_json = "{\"type\":\"object\"," "\"properties\":{" "\"name\":{\"type\":\"string\",\"description\":\"Short name for the job\"}," "\"schedule_type\":{\"type\":\"string\",\"description\":\"'every' for recurring interval or 'at' for one-shot at a unix timestamp\"}," "\"interval_s\":{\"type\":\"integer\",\"description\":\"Interval in seconds (required for 'every')\"}," - "\"at_epoch\":{\"type\":\"integer\",\"description\":\"Unix timestamp to fire at (required for 'at')\"}," + "\"at_epoch\":{\"type\":\"integer\",\"description\":\"Unix timestamp to fire at (for 'at'). Prefer delay_s for relative reminders.\"}," + "\"delay_s\":{\"type\":\"integer\",\"description\":\"Delay in seconds from now (preferred for 'at' when user says 'in N minutes')\"}," "\"message\":{\"type\":\"string\",\"description\":\"Message to inject when the job fires, triggering an agent turn\"}," "\"channel\":{\"type\":\"string\",\"description\":\"Optional reply channel (e.g. 'telegram'). If omitted, current turn channel is used when available\"}," "\"chat_id\":{\"type\":\"string\",\"description\":\"Optional reply chat_id. Required when channel='telegram'. If omitted during a Telegram turn, current chat_id is used\"}" diff --git a/main/voice/voice_channel.c b/main/voice/voice_channel.c index 4bd24113..039c6e26 100644 --- a/main/voice/voice_channel.c +++ b/main/voice/voice_channel.c @@ -22,12 +22,64 @@ #include "esp_crt_bundle.h" #include "esp_heap_caps.h" #include "driver/i2s_std.h" +#include "driver/i2s_common.h" #include "cJSON.h" #include "mbedtls/base64.h" static const char *TAG = "voice"; +/* + * I2S timing / slot style selection: + * 0: Philips (I2S, 1-bit delay after WS edge) + * 1: MSB (left-justified, no 1-bit delay) + * 2: PCM (short frame sync, ws_width=1, ws_pol=true) + * + * Many DAC/codec parts are sensitive to this. If your audio sounds like loud + * "沙沙" noise but speech is partially recognizable, this is a prime suspect. + */ +#ifndef MIMI_VOICE_I2S_STD_SLOT_STYLE +#define MIMI_VOICE_I2S_STD_SLOT_STYLE 0 +#endif + +#ifndef MIMI_VOICE_I2S_DMA_DESC_NUM +#define MIMI_VOICE_I2S_DMA_DESC_NUM 6 +#endif + +#ifndef MIMI_VOICE_I2S_DMA_FRAME_NUM +#define MIMI_VOICE_I2S_DMA_FRAME_NUM 240 +#endif + +#ifndef MIMI_VOICE_TX_SILENCE_TAIL_MS +#define MIMI_VOICE_TX_SILENCE_TAIL_MS 400 +#endif + +#define MIMI_VOICE_TX_BYTES_PER_FRAME (2U * sizeof(int32_t)) +#define MIMI_VOICE_TX_DMA_TOTAL_BYTES \ + ((uint32_t)MIMI_VOICE_I2S_DMA_DESC_NUM * (uint32_t)MIMI_VOICE_I2S_DMA_FRAME_NUM * (uint32_t)MIMI_VOICE_TX_BYTES_PER_FRAME) + +#if MIMI_VOICE_I2S_STD_SLOT_STYLE == 1 +#define MIMI_VOICE_I2S_SLOT_DEFAULT_CONFIG(bits, mono_or_stereo) \ + I2S_STD_MSB_SLOT_DEFAULT_CONFIG(bits, mono_or_stereo) +#elif MIMI_VOICE_I2S_STD_SLOT_STYLE == 2 +#define MIMI_VOICE_I2S_SLOT_DEFAULT_CONFIG(bits, mono_or_stereo) \ + I2S_STD_PCM_SLOT_DEFAULT_CONFIG(bits, mono_or_stereo) +#else +#define MIMI_VOICE_I2S_SLOT_DEFAULT_CONFIG(bits, mono_or_stereo) \ + I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(bits, mono_or_stereo) +#endif + +static const char *i2s_slot_style_str(void) +{ +#if MIMI_VOICE_I2S_STD_SLOT_STYLE == 1 + return "MSB"; +#elif MIMI_VOICE_I2S_STD_SLOT_STYLE == 2 + return "PCM"; +#else + return "PHILIPS"; +#endif +} + /* ========================= * Fallback config defaults * ========================= */ @@ -101,13 +153,18 @@ static const char *TAG = "voice"; #endif #ifndef MIMI_SECRET_TTS_LANGUAGE -#define MIMI_SECRET_TTS_LANGUAGE "Chinese" +#define MIMI_SECRET_TTS_LANGUAGE "English" #endif #ifndef MIMI_SECRET_API_KEY #define MIMI_SECRET_API_KEY "" #endif +/* TTS text constraints (can override in mimi_secrets.h) */ +#ifndef MIMI_VOICE_TTS_MAX_CHARS +#define MIMI_VOICE_TTS_MAX_CHARS 140 +#endif + #ifndef MIMI_VOICE_I2S_PORT #define MIMI_VOICE_I2S_PORT 0 #endif @@ -201,7 +258,7 @@ static const char *tts_voice(void) static const char *tts_language(void) { - return (MIMI_SECRET_TTS_LANGUAGE[0] != '\0') ? MIMI_SECRET_TTS_LANGUAGE : "Chinese"; + return (MIMI_SECRET_TTS_LANGUAGE[0] != '\0') ? MIMI_SECRET_TTS_LANGUAGE : "English"; } /* ========================= @@ -319,6 +376,241 @@ static esp_err_t http_get_binary(const char *url, http_resp_t *resp, int *http_s * Audio helpers * ========================= */ +static void *malloc_prefer_spiram(size_t bytes) +{ + if (bytes == 0) { + return NULL; + } + + void *p = heap_caps_malloc(bytes, MALLOC_CAP_SPIRAM); + if (p) { + return p; + } + return malloc(bytes); +} + +static bool utf8_is_continuation_byte(uint8_t b) +{ + return (b & 0xC0U) == 0x80U; +} + +static bool utf8_starts_with(const char *s, size_t i, size_t len, const char *lit) +{ + size_t lit_len = strlen(lit); + if (i + lit_len > len) { + return false; + } + return memcmp(s + i, lit, lit_len) == 0; +} + +static bool is_speech_cut_punct(const char *s, size_t i, size_t len) +{ + const uint8_t b = (uint8_t)s[i]; + if (b == '\n' || b == '\r') { + return true; + } + if (b == '.' || b == '!' || b == '?' || b == ',' || b == ';' || b == ':') { + return true; + } + + /* Common CJK punctuation in UTF-8 */ + if (utf8_starts_with(s, i, len, "。") || + utf8_starts_with(s, i, len, "!") || + utf8_starts_with(s, i, len, "?") || + utf8_starts_with(s, i, len, ",") || + utf8_starts_with(s, i, len, ";") || + utf8_starts_with(s, i, len, ":") || + utf8_starts_with(s, i, len, "、")) { + return true; + } + + return false; +} + +static size_t utf8_truncate_for_tts(const char *text, size_t max_chars, size_t *out_char_count, bool *out_truncated) +{ + if (!text || max_chars == 0) { + if (out_char_count) *out_char_count = 0; + if (out_truncated) *out_truncated = false; + return 0; + } + + const size_t len = strlen(text); + size_t char_count = 0; + size_t last_punct_cut = 0; + size_t i = 0; + + while (i < len) { + if (char_count >= max_chars) { + break; + } + + if (!utf8_is_continuation_byte((uint8_t)text[i])) { + char_count++; + if (is_speech_cut_punct(text, i, len)) { + /* Cut after this codepoint (best-effort) */ + size_t j = i + 1; + while (j < len && utf8_is_continuation_byte((uint8_t)text[j])) { + j++; + } + last_punct_cut = j; + } + } + i++; + } + + if (out_char_count) { + *out_char_count = char_count; + } + + if (i >= len) { + if (out_truncated) *out_truncated = false; + return len; + } + + /* Prefer cutting at punctuation, but avoid cutting too early */ + size_t cut = i; + if (last_punct_cut > 0) { + const size_t min_reasonable = (max_chars >= 20) ? (max_chars / 2) : 0; + if (last_punct_cut >= min_reasonable) { + cut = last_punct_cut; + } + } + + while (cut > 0 && utf8_is_continuation_byte((uint8_t)text[cut])) { + cut--; + } + + if (out_truncated) *out_truncated = true; + return cut; +} + +static char *voice_build_tts_text(const char *text) +{ + if (!text) { + return NULL; + } + + size_t char_count = 0; + bool truncated = false; + size_t cut_bytes = utf8_truncate_for_tts(text, MIMI_VOICE_TTS_MAX_CHARS, &char_count, &truncated); + + if (!truncated) { + return NULL; /* caller can use original text */ + } + + char *out = (char *)malloc(cut_bytes + 1); + if (!out) { + return NULL; + } + memcpy(out, text, cut_bytes); + out[cut_bytes] = '\0'; + + ESP_LOGW(TAG, "TTS text truncated: max=%u chars, cut_bytes=%u", (unsigned)MIMI_VOICE_TTS_MAX_CHARS, (unsigned)cut_bytes); + return out; +} + +static int16_t fir5_s16_at_clamped(const int16_t *src, size_t src_samples, size_t idx) +{ + if (!src || src_samples == 0) { + return 0; + } + + size_t i0 = (idx >= 2) ? (idx - 2) : 0; + size_t i1 = (idx >= 1) ? (idx - 1) : 0; + size_t i2 = idx; + if (i2 >= src_samples) i2 = src_samples - 1; + size_t i3 = (idx + 1 < src_samples) ? (idx + 1) : (src_samples - 1); + size_t i4 = (idx + 2 < src_samples) ? (idx + 2) : (src_samples - 1); + + int32_t acc = + (int32_t)src[i0] * 1 + + (int32_t)src[i1] * 4 + + (int32_t)src[i2] * 6 + + (int32_t)src[i3] * 4 + + (int32_t)src[i4] * 1; + + acc = acc / 16; + if (acc > INT16_MAX) acc = INT16_MAX; + if (acc < INT16_MIN) acc = INT16_MIN; + return (int16_t)acc; +} + +static esp_err_t i2s_tx_write_silence_ms(uint32_t ms) +{ + if (!s_i2s_ready || !s_tx_chan || ms == 0) { + return ESP_OK; + } + + uint64_t frames_total = ((uint64_t)MIMI_VOICE_SAMPLE_RATE * (uint64_t)ms) / 1000ULL; + while (frames_total > 0) { + const size_t frames_chunk = (frames_total > 256) ? 256 : (size_t)frames_total; + int32_t zeros[256 * 2] = {0}; + + const uint8_t *p = (const uint8_t *)zeros; + size_t bytes_total = frames_chunk * 2 * sizeof(int32_t); + size_t bytes_sent = 0; + + while (bytes_sent < bytes_total) { + size_t written = 0; + esp_err_t err = i2s_channel_write(s_tx_chan, + p + bytes_sent, + bytes_total - bytes_sent, + &written, + pdMS_TO_TICKS(1000)); + if (err != ESP_OK) { + return err; + } + if (written == 0) { + return ESP_FAIL; + } + bytes_sent += written; + } + + frames_total -= frames_chunk; + } + + return ESP_OK; +} + +static esp_err_t i2s_tx_overwrite_dma_with_zeros(void) +{ + if (!s_i2s_ready || !s_tx_chan) { + return ESP_ERR_INVALID_STATE; + } + + uint32_t remaining = MIMI_VOICE_TX_DMA_TOTAL_BYTES; + while (remaining > 0) { + int32_t zeros[256 * 2] = {0}; + size_t chunk = sizeof(zeros); + if (chunk > remaining) { + chunk = remaining; + } + + const uint8_t *p = (const uint8_t *)zeros; + size_t sent = 0; + while (sent < chunk) { + size_t written = 0; + esp_err_t err = i2s_channel_write(s_tx_chan, + p + sent, + chunk - sent, + &written, + pdMS_TO_TICKS(1000)); + if (err != ESP_OK) { + return err; + } + if (written == 0) { + return ESP_FAIL; + } + sent += written; + } + + remaining -= (uint32_t)chunk; + } + + return ESP_OK; +} + static void pcm_s32_stereo_to_s16_mono(const uint8_t *src, size_t src_len, int16_t *dst, size_t *out_samples) { size_t frames = src_len / VOICE_I2S_BYTES_PER_STEREO_FRAME; @@ -622,7 +914,16 @@ static esp_err_t i2s_init_xvf3800(void) { esp_err_t err; - i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)MIMI_VOICE_I2S_PORT, I2S_ROLE_MASTER); + i2s_chan_config_t chan_cfg = { + .id = (i2s_port_t)MIMI_VOICE_I2S_PORT, + .role = I2S_ROLE_MASTER, + .dma_desc_num = MIMI_VOICE_I2S_DMA_DESC_NUM, + .dma_frame_num = MIMI_VOICE_I2S_DMA_FRAME_NUM, + .auto_clear_after_cb = false, + .auto_clear_before_cb = false, + .allow_pd = false, + .intr_priority = 0, + }; err = i2s_new_channel(&chan_cfg, &s_tx_chan, &s_rx_chan); if (err != ESP_OK) { @@ -649,7 +950,7 @@ static esp_err_t i2s_init_xvf3800(void) i2s_std_config_t tx_cfg = { .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(MIMI_VOICE_SAMPLE_RATE), - .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), + .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO), .gpio_cfg = { .mclk = I2S_GPIO_UNUSED, .bclk = MIMI_VOICE_I2S_BCLK, @@ -676,6 +977,17 @@ static esp_err_t i2s_init_xvf3800(void) return err; } + /* Seed TX DMA with silence, otherwise some DAC/amps output "咚咚/沙沙" due to + * undefined initial DMA content or repeating last buffer when idle. + * + * Only allowed before enabling the channel. + */ + { + int32_t zeros[128 * 2] = {0}; + size_t loaded = 0; + (void)i2s_channel_preload_data(s_tx_chan, zeros, sizeof(zeros), &loaded); + } + err = i2s_channel_enable(s_rx_chan); if (err != ESP_OK) { ESP_LOGE(TAG, "i2s rx enable failed: %s", esp_err_to_name(err)); @@ -689,8 +1001,9 @@ static esp_err_t i2s_init_xvf3800(void) } s_i2s_ready = true; - ESP_LOGI(TAG, "I2S ready: %dHz stereo s32 in / mono s16 out", - MIMI_VOICE_SAMPLE_RATE); + ESP_LOGI(TAG, "I2S ready: %dHz stereo s32 in / stereo s32 out (%s timing)", + MIMI_VOICE_SAMPLE_RATE, + i2s_slot_style_str()); return ESP_OK; } static int16_t *resample_s16_mono_linear(const int16_t *src, @@ -704,7 +1017,7 @@ static int16_t *resample_s16_mono_linear(const int16_t *src, } if (src_rate == dst_rate) { - int16_t *copy = malloc(src_samples * sizeof(int16_t)); + int16_t *copy = (int16_t *)malloc_prefer_spiram(src_samples * sizeof(int16_t)); if (!copy) { return NULL; } @@ -713,16 +1026,25 @@ static int16_t *resample_s16_mono_linear(const int16_t *src, return copy; } + const bool is_downsampling = src_rate > dst_rate; + size_t dst_samples = (size_t)(((uint64_t)src_samples * dst_rate) / src_rate); if (dst_samples == 0) { return NULL; } - int16_t *dst = malloc(dst_samples * sizeof(int16_t)); + int16_t *dst = (int16_t *)malloc_prefer_spiram(dst_samples * sizeof(int16_t)); if (!dst) { return NULL; } + /* When downsampling (e.g. 24k -> 16k), naive linear interpolation tends to fold + * high-frequency content above the new Nyquist into the audible band (aliasing), + * often perceived as "沙沙" on sibilants/background. + * + * Apply a tiny 5-tap low-pass FIR [1,4,6,4,1]/16 on the source indices we touch. + * This is cheap and improves subjective quality significantly without pulling in DSP deps. + */ for (size_t i = 0; i < dst_samples; i++) { float src_pos = ((float)i * (float)src_rate) / (float)dst_rate; size_t idx = (size_t)src_pos; @@ -731,8 +1053,8 @@ static int16_t *resample_s16_mono_linear(const int16_t *src, if (idx >= src_samples - 1) { dst[i] = src[src_samples - 1]; } else { - float a = (float)src[idx]; - float b = (float)src[idx + 1]; + float a = (float)(is_downsampling ? fir5_s16_at_clamped(src, src_samples, idx) : src[idx]); + float b = (float)(is_downsampling ? fir5_s16_at_clamped(src, src_samples, idx + 1) : src[idx + 1]); float v = a + (b - a) * frac; if (v > 32767.0f) v = 32767.0f; @@ -774,43 +1096,51 @@ static esp_err_t i2s_play_wav_pcm16(const uint8_t *wav, size_t wav_len) const int16_t *src16 = (const int16_t *)pcm; size_t src_samples_total = pcm_len / sizeof(int16_t); - int16_t *mono_buf = NULL; + const int16_t *mono_src = NULL; + int16_t *mono_owned = NULL; size_t mono_samples = 0; if (fmt.channels == 1) { + mono_src = src16; mono_samples = src_samples_total; - mono_buf = malloc(mono_samples * sizeof(int16_t)); - if (!mono_buf) { - return ESP_ERR_NO_MEM; - } - memcpy(mono_buf, src16, mono_samples * sizeof(int16_t)); } else if (fmt.channels == 2) { mono_samples = src_samples_total / 2; - mono_buf = malloc(mono_samples * sizeof(int16_t)); - if (!mono_buf) { + mono_owned = (int16_t *)malloc_prefer_spiram(mono_samples * sizeof(int16_t)); + if (!mono_owned) { return ESP_ERR_NO_MEM; } for (size_t i = 0, j = 0; j < mono_samples; i += 2, j++) { int32_t v = ((int32_t)src16[i] + (int32_t)src16[i + 1]) / 2; - mono_buf[j] = (int16_t)v; + mono_owned[j] = (int16_t)v; } + mono_src = mono_owned; } else { return ESP_ERR_NOT_SUPPORTED; } + const int16_t *play_src = NULL; + int16_t *play_owned = NULL; size_t play_samples = 0; - int16_t *play_buf = resample_s16_mono_linear( - mono_buf, - mono_samples, - fmt.sample_rate, - MIMI_VOICE_SAMPLE_RATE, - &play_samples - ); - free(mono_buf); - - if (!play_buf || play_samples == 0) { - return ESP_ERR_NO_MEM; + + if (fmt.sample_rate == MIMI_VOICE_SAMPLE_RATE) { + play_src = mono_src; + play_samples = mono_samples; + } else { + play_owned = resample_s16_mono_linear( + mono_src, + mono_samples, + fmt.sample_rate, + MIMI_VOICE_SAMPLE_RATE, + &play_samples + ); + free(mono_owned); + mono_owned = NULL; + + if (!play_owned || play_samples == 0) { + return ESP_ERR_NO_MEM; + } + play_src = play_owned; } ESP_LOGI(TAG, "Playback PCM: %u samples @ %u Hz (~%u ms)", @@ -820,40 +1150,59 @@ static esp_err_t i2s_play_wav_pcm16(const uint8_t *wav, size_t wav_len) s_is_playing = true; - const uint8_t *play_bytes = (const uint8_t *)play_buf; - size_t total_bytes = play_samples * sizeof(int16_t); - size_t written_total = 0; + size_t frames_total = play_samples; + size_t frames_sent = 0; - while (written_total < total_bytes) { - size_t written = 0; - size_t chunk = total_bytes - written_total; - if (chunk > 2048) { - chunk = 2048; - } + while (frames_sent < frames_total) { + const size_t frames_chunk = (frames_total - frames_sent > 256) ? 256 : (frames_total - frames_sent); - err = i2s_channel_write(s_tx_chan, - play_bytes + written_total, - chunk, - &written, - pdMS_TO_TICKS(1000)); - if (err != ESP_OK) { - ESP_LOGE(TAG, "i2s write failed: %s", esp_err_to_name(err)); - free(play_buf); - s_is_playing = false; - return err; + int32_t tx_buf[256 * 2]; + for (size_t i = 0; i < frames_chunk; i++) { + int16_t s16 = play_src[frames_sent + i]; + int32_t s32 = ((int32_t)s16) << 16; + tx_buf[i * 2 + 0] = s32; + tx_buf[i * 2 + 1] = s32; } - if (written == 0) { - ESP_LOGE(TAG, "i2s write returned 0 bytes"); - free(play_buf); - s_is_playing = false; - return ESP_FAIL; + const uint8_t *p = (const uint8_t *)tx_buf; + size_t bytes_total = frames_chunk * 2 * sizeof(int32_t); + size_t bytes_sent = 0; + + while (bytes_sent < bytes_total) { + size_t written = 0; + err = i2s_channel_write(s_tx_chan, + p + bytes_sent, + bytes_total - bytes_sent, + &written, + pdMS_TO_TICKS(1000)); + if (err != ESP_OK) { + ESP_LOGE(TAG, "i2s write failed: %s", esp_err_to_name(err)); + free(play_owned); + free(mono_owned); + s_is_playing = false; + return err; + } + if (written == 0) { + ESP_LOGE(TAG, "i2s write returned 0 bytes"); + free(play_owned); + free(mono_owned); + s_is_playing = false; + return ESP_FAIL; + } + bytes_sent += written; } - written_total += written; + frames_sent += frames_chunk; } - free(play_buf); + /* Leave a short silence tail so the TX engine doesn't keep repeating the last + * non-zero DMA buffer (often perceived as continuous "咚咚" when idle). + */ + (void)i2s_tx_write_silence_ms(MIMI_VOICE_TX_SILENCE_TAIL_MS); + (void)i2s_tx_overwrite_dma_with_zeros(); + + free(play_owned); + free(mono_owned); s_is_playing = false; return ESP_OK; } @@ -1021,7 +1370,7 @@ static esp_err_t tts_stream_play(const char *text) wav_resp.buf + 8); } - if (wav_resp.len > 0 && strncmp(wav_resp.buf, "RIFF", 4) != 0) { + if (wav_resp.len >= 4 && memcmp(wav_resp.buf, "RIFF", 4) != 0) { ESP_LOGE(TAG, "TTS response is not WAV, preview: %.120s", wav_resp.buf); } @@ -1060,6 +1409,8 @@ static void voice_capture_task(void *arg) bool in_speech = false; size_t total_frames = 0; size_t silence_frames = 0; + size_t start_frames = 0; + TickType_t cooldown_until = 0; /* Simple adaptive noise floor */ uint32_t noise_floor = MIMI_VOICE_VAD_THRESHOLD / 2; @@ -1077,6 +1428,12 @@ static void voice_capture_task(void *arg) continue; } + TickType_t now = xTaskGetTickCount(); + if (cooldown_until != 0 && now < cooldown_until) { + vTaskDelay(cooldown_until - now); + continue; + } + size_t bytes_read = 0; esp_err_t err = i2s_channel_read(s_rx_chan, rx_buf, @@ -1105,11 +1462,17 @@ static void voice_capture_task(void *arg) if (!in_speech) { if (!speech_now) { + start_frames = 0; + continue; + } + start_frames++; + if (start_frames < MIMI_VOICE_VAD_START_FRAMES) { continue; } in_speech = true; total_frames = 0; silence_frames = 0; + start_frames = 0; } if (total_frames < max_frames) { @@ -1133,9 +1496,10 @@ static void voice_capture_task(void *arg) in_speech = false; /* Ignore ultra-short bursts */ - if (total_frames < 5) { + if (total_frames < MIMI_VOICE_VAD_MIN_FRAMES) { total_frames = 0; silence_frames = 0; + cooldown_until = xTaskGetTickCount() + pdMS_TO_TICKS(MIMI_VOICE_STT_COOLDOWN_MS); continue; } @@ -1156,6 +1520,7 @@ static void voice_capture_task(void *arg) total_frames = 0; silence_frames = 0; + cooldown_until = xTaskGetTickCount() + pdMS_TO_TICKS(MIMI_VOICE_STT_COOLDOWN_MS); } } @@ -1221,7 +1586,9 @@ esp_err_t voice_channel_speak_text(const char *text) return ESP_ERR_TIMEOUT; } - esp_err_t err = tts_stream_play(text); + char *tts_text = voice_build_tts_text(text); + esp_err_t err = tts_stream_play(tts_text ? tts_text : text); + free(tts_text); xSemaphoreGive(s_http_lock); return err; @@ -1243,4 +1610,4 @@ void voice_channel_get_status(voice_channel_status_t *status) status->is_playing = s_is_playing; status->stt_configured = (stt_api_url()[0] != '\0' && stt_api_key()[0] != '\0'); status->tts_configured = (tts_api_url()[0] != '\0' && tts_api_key()[0] != '\0'); -} \ No newline at end of file +} From ee96975631ce97b528c3cab81d034ddcd60a8196 Mon Sep 17 00:00:00 2001 From: stringwind Date: Fri, 13 Mar 2026 16:36:08 +0800 Subject: [PATCH 8/9] chore: stop tracking trellis archive files --- .../2026-03/00-bootstrap-guidelines/prd.md | 113 ------------------ .../2026-03/00-bootstrap-guidelines/task.json | 35 ------ 2 files changed, 148 deletions(-) delete mode 100644 .trellis/tasks/archive/2026-03/00-bootstrap-guidelines/prd.md delete mode 100644 .trellis/tasks/archive/2026-03/00-bootstrap-guidelines/task.json diff --git a/.trellis/tasks/archive/2026-03/00-bootstrap-guidelines/prd.md b/.trellis/tasks/archive/2026-03/00-bootstrap-guidelines/prd.md deleted file mode 100644 index c1e0a29c..00000000 --- a/.trellis/tasks/archive/2026-03/00-bootstrap-guidelines/prd.md +++ /dev/null @@ -1,113 +0,0 @@ -# Bootstrap: Fill Project Development Guidelines - -## Purpose - -Welcome to Trellis! This is your first task. - -AI agents use `.trellis/spec/` to understand YOUR project's coding conventions. -**Empty templates = AI writes generic code that doesn't match your project style.** - -Filling these guidelines is a one-time setup that pays off for every future AI session. - ---- - -## Your Task - -Fill in the guideline files based on your **existing codebase**. - - -### Backend Guidelines - -| File | What to Document | -|------|------------------| -| `.trellis/spec/backend/directory-structure.md` | Where different file types go (routes, services, utils) | -| `.trellis/spec/backend/database-guidelines.md` | ORM, migrations, query patterns, naming conventions | -| `.trellis/spec/backend/error-handling.md` | How errors are caught, logged, and returned | -| `.trellis/spec/backend/logging-guidelines.md` | Log levels, format, what to log | -| `.trellis/spec/backend/quality-guidelines.md` | Code review standards, testing requirements | - - -### Frontend Guidelines - -| File | What to Document | -|------|------------------| -| `.trellis/spec/frontend/directory-structure.md` | Component/page/hook organization | -| `.trellis/spec/frontend/component-guidelines.md` | Component patterns, props conventions | -| `.trellis/spec/frontend/hook-guidelines.md` | Custom hook naming, patterns | -| `.trellis/spec/frontend/state-management.md` | State library, patterns, what goes where | -| `.trellis/spec/frontend/type-safety.md` | TypeScript conventions, type organization | -| `.trellis/spec/frontend/quality-guidelines.md` | Linting, testing, accessibility | - - -### Thinking Guides (Optional) - -The `.trellis/spec/guides/` directory contains thinking guides that are already -filled with general best practices. You can customize them for your project if needed. - ---- - -## How to Fill Guidelines - -### Step 0: Import from Existing Specs (Recommended) - -Many projects already have coding conventions documented. **Check these first** before writing from scratch: - -| File / Directory | Tool | -|------|------| -| `CLAUDE.md` / `CLAUDE.local.md` | Claude Code | -| `AGENTS.md` | Claude Code | -| `.cursorrules` | Cursor | -| `.cursor/rules/*.mdc` | Cursor (rules directory) | -| `.windsurfrules` | Windsurf | -| `.clinerules` | Cline | -| `.roomodes` | Roo Code | -| `.github/copilot-instructions.md` | GitHub Copilot | -| `.vscode/settings.json` → `github.copilot.chat.codeGeneration.instructions` | VS Code Copilot | -| `CONVENTIONS.md` / `.aider.conf.yml` | aider | -| `CONTRIBUTING.md` | General project conventions | -| `.editorconfig` | Editor formatting rules | - -If any of these exist, read them first and extract the relevant coding conventions into the corresponding `.trellis/spec/` files. This saves significant effort compared to writing everything from scratch. - -### Step 1: Analyze the Codebase - -Ask AI to help discover patterns from actual code: - -- "Read all existing config files (CLAUDE.md, .cursorrules, etc.) and extract coding conventions into .trellis/spec/" -- "Analyze my codebase and document the patterns you see" -- "Find error handling / component / API patterns and document them" - -### Step 2: Document Reality, Not Ideals - -Write what your codebase **actually does**, not what you wish it did. -AI needs to match existing patterns, not introduce new ones. - -- **Look at existing code** - Find 2-3 examples of each pattern -- **Include file paths** - Reference real files as examples -- **List anti-patterns** - What does your team avoid? - ---- - -## Completion Checklist - -- [ ] Guidelines filled for your project type -- [ ] At least 2-3 real code examples in each guideline -- [ ] Anti-patterns documented - -When done: - -```bash -python3 ./.trellis/scripts/task.py finish -python3 ./.trellis/scripts/task.py archive 00-bootstrap-guidelines -``` - ---- - -## Why This Matters - -After completing this task: - -1. AI will write code that matches your project style -2. Relevant `/trellis:before-*-dev` commands will inject real context -3. `/trellis:check-*` commands will validate against your actual standards -4. Future developers (human or AI) will onboard faster diff --git a/.trellis/tasks/archive/2026-03/00-bootstrap-guidelines/task.json b/.trellis/tasks/archive/2026-03/00-bootstrap-guidelines/task.json deleted file mode 100644 index 1e482403..00000000 --- a/.trellis/tasks/archive/2026-03/00-bootstrap-guidelines/task.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "id": "00-bootstrap-guidelines", - "name": "Bootstrap Guidelines", - "description": "Fill in project development guidelines for AI agents", - "status": "completed", - "dev_type": "docs", - "priority": "P1", - "creator": "stringwind", - "assignee": "stringwind", - "createdAt": "2026-03-12", - "completedAt": "2026-03-12", - "commit": null, - "subtasks": [ - { - "name": "Fill backend guidelines", - "status": "pending" - }, - { - "name": "Fill frontend guidelines", - "status": "pending" - }, - { - "name": "Add code examples", - "status": "pending" - } - ], - "children": [], - "parent": null, - "relatedFiles": [ - ".trellis/spec/backend/", - ".trellis/spec/frontend/" - ], - "notes": "First-time setup task created by trellis init (fullstack project)", - "meta": {} -} \ No newline at end of file From 3d726d04eed4891d594973eb7aaaa03e7acf15e9 Mon Sep 17 00:00:00 2001 From: stringwind Date: Tue, 17 Mar 2026 15:12:45 +0800 Subject: [PATCH 9/9] chore: update README.md --- README.md | 400 +++++++++++++++------------------ README_CN.md | 432 +++++++++++++++--------------------- README_JA.md | 419 +++++++++++++++------------------- assets/banner.png | Bin 51506 -> 0 bytes assets/esp32s3-usb-port.jpg | Bin 74148 -> 0 bytes assets/mimiclaw.png | Bin 196974 -> 0 bytes 6 files changed, 530 insertions(+), 721 deletions(-) delete mode 100644 assets/banner.png delete mode 100644 assets/esp32s3-usb-port.jpg delete mode 100644 assets/mimiclaw.png diff --git a/README.md b/README.md index cbc81c9b..241477b5 100644 --- a/README.md +++ b/README.md @@ -1,319 +1,265 @@ -# MimiClaw: Pocket AI Assistant on a $5 Chip +# reSpeaker-claw: Voice AI Agent for ReSpeaker XVF3800

- MimiClaw -

- -

- License: MIT - DeepWiki - Discord - X + License: MIT + Language: C + Framework: ESP-IDF v5.5+ + Hardware: ReSpeaker XVF3800 + Architecture: Voice Agent

English | 中文 | 日本語

-**The world's first AI assistant(OpenClaw) on a $5 chip. No Linux. No Node.js. Just pure C** - -MimiClaw turns a tiny ESP32-S3 board into a personal AI assistant. Plug it into USB power, connect to WiFi, and talk to it through Telegram — it handles any task you throw at it and evolves over time with local memory — all on a chip the size of a thumb. +reSpeaker-claw turns a ReSpeaker XVF3800–based device into a voice-first AI agent. It captures audio over I2S, performs local VAD, sends utterances to STT, and processes them through an embedded agent loop. The system combines real-time speech interaction with local memory, tool calling, scheduling, heartbeat processes, OTA updates, and proxy support, and returns responses via TTS through the speaker. -## Meet MimiClaw +## Meet reSpeaker-claw - **Tiny** — No Linux, no Node.js, no bloat — just pure C -- **Handy** — Message it from Telegram, it handles the rest - **Loyal** — Learns from memory, remembers across reboots -- **Energetic** — USB power, 0.5 W, runs 24/7 -- **Lovable** — One ESP32-S3 board, $5, nothing else +- **Energetic** — USB power, lower power consumption, runs 24/7 +- **Freedom** — ReSpeaker XVF3800's mic array + your choice of speaker amp/DAC +- **Handy** — Built-in voice channel, no extra hardware needed beyond the XVF3800 and a speaker path -## How It Works +## Highlights -![](assets/mimiclaw.png) +- Voice input: ReSpeaker XVF3800 microphone array over I2S +- Voice output: TTS audio download, WAV decode, resample, and speaker playback over I2S +- Multi-channel agent: voice, Telegram, Feishu, WebSocket +- Local persistence: SPIFFS stores memory, profiles, sessions, cron jobs, and daily notes +- Compatible LLM backends: official Anthropic/OpenAI APIs or third-party gateways that expose Anthropic-compatible or OpenAI-compatible endpoints +- Configurable STT/TTS: plug in your own service URL, API key, model, voice, and language +- Runtime overrides: change WiFi, provider, model, API base, proxy, and tokens from the serial CLI without editing code -You send a message on Telegram. The ESP32-S3 picks it up over WiFi, feeds it into an agent loop — the LLM thinks, calls tools, reads memory — and sends the reply back. Supports both **Anthropic (Claude)** and **OpenAI (GPT)** as providers, switchable at runtime. Everything runs on a single $5 chip with all your data stored locally on flash. ## Quick Start -### What You Need +### Requirements -- An **ESP32-S3 dev board** with 16 MB flash and 8 MB PSRAM (e.g. Xiaozhi AI board, ~$10) -- A **USB Type-C cable** -- A **Telegram bot token** — talk to [@BotFather](https://t.me/BotFather) on Telegram to create one -- An **Anthropic API key** — from [console.anthropic.com](https://console.anthropic.com), or an **OpenAI API key** — from [platform.openai.com](https://platform.openai.com) +- A reSpeaker XVF3800 USB 4 Microphone Array with XIAO ESP32S3 board +- A speaker / DAC / amp path on I2S output +- A USB cable for flashing and serial monitoring +- WiFi access +- ESP-IDF v5.5+ +- Optional: Telegram bot token if you want Telegram +- Optional: Feishu app credentials if you want Feishu +- One LLM API key for an Anthropic-compatible or OpenAI-compatible endpoint +- One STT service and one TTS service for voice mode -### Install +### Clone and Build Environment -```bash -# You need ESP-IDF v5.5+ installed first: -# https://docs.espressif.com/projects/esp-idf/en/v5.5.2/esp32s3/get-started/ +Refer to the official guide to flash the I2S firmware: +[SeeedStudio wiki](https://wiki.seeedstudio.com/respeaker_xvf3800_introduction/#flash-firmware) + +Then clone this project and set the target: -git clone https://github.com/memovai/mimiclaw.git -cd mimiclaw +```bash +git clone https://github.com/Seeed-Projects/reSpeaker-claw +cd reSpeaker-claw idf.py set-target esp32s3 ``` -
-Ubuntu Install +Install ESP-IDF first: [ESP-IDF Install](https://docs.espressif.com/projects/esp-idf/en/v5.5.3/esp32s3/get-started/) -Recommended baseline: - -- Ubuntu 22.04/24.04 -- Python >= 3.10 -- CMake >= 3.16 -- Ninja >= 1.10 -- Git >= 2.34 -- flex >= 2.6 -- bison >= 3.8 -- gperf >= 3.1 -- dfu-util >= 0.11 -- `libusb-1.0-0`, `libffi-dev`, `libssl-dev` - -Install and build on Ubuntu: +Ubuntu helper scripts: ```bash -sudo apt-get update -sudo apt-get install -y git wget flex bison gperf python3 python3-pip python3-venv \ - cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 - ./scripts/setup_idf_ubuntu.sh ./scripts/build_ubuntu.sh ``` -
- -
-macOS Install - -Recommended baseline: - -- macOS 12/13/14 -- Xcode Command Line Tools -- Homebrew -- Python >= 3.10 -- CMake >= 3.16 -- Ninja >= 1.10 -- Git >= 2.34 -- flex >= 2.6 -- bison >= 3.8 -- gperf >= 3.1 -- dfu-util >= 0.11 -- `libusb`, `libffi`, `openssl` - -Install and build on macOS: +macOS helper scripts: ```bash -xcode-select --install -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - ./scripts/setup_idf_macos.sh ./scripts/build_macos.sh ``` -
- -### Configure +## Configure -MimiClaw uses a **two-layer config** system: build-time defaults in `mimi_secrets.h`, with runtime overrides via the serial CLI. CLI values are stored in NVS flash and take priority over build-time values. +Copy the example secrets file: ```bash -cp main/mimi_secrets.h.example main/mimi_secrets.h +cp "main/mimi_secrets.h.example" "main/mimi_secrets.h" ``` -Edit `main/mimi_secrets.h`: +Edit `main/mimi_secrets.h` and set the fields you actually use: ```c +/* WiFi */ #define MIMI_SECRET_WIFI_SSID "YourWiFiName" #define MIMI_SECRET_WIFI_PASS "YourWiFiPassword" -#define MIMI_SECRET_TG_TOKEN "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" -#define MIMI_SECRET_API_KEY "sk-ant-api03-xxxxx" -#define MIMI_SECRET_LLM_API_URL "" // optional: full URL incl scheme/host/port/path -#define MIMI_SECRET_MODEL_PROVIDER "anthropic" // "anthropic" or "openai" -#define MIMI_SECRET_SEARCH_KEY "" // optional: Brave Search API key -#define MIMI_SECRET_TAVILY_KEY "" // optional: Tavily API key (preferred) -#define MIMI_SECRET_PROXY_HOST "" // optional: e.g. "10.0.0.1" -#define MIMI_SECRET_PROXY_PORT "" // optional: e.g. "7897" -``` - -Then build and flash: - -```bash -# Clean build (required after any mimi_secrets.h change) -idf.py fullclean && idf.py build - -# Find your serial port -ls /dev/cu.usb* # macOS -ls /dev/ttyACM* # Linux -# Flash and monitor (replace PORT with your port) -# USB adapter: likely /dev/cu.usbmodem11401 (macOS) or /dev/ttyACM0 (Linux) -idf.py -p PORT flash monitor +/* Optional text channels */ +#define MIMI_SECRET_TG_TOKEN "" +#define MIMI_SECRET_FEISHU_APP_ID "" +#define MIMI_SECRET_FEISHU_APP_SECRET "" + +/* LLM */ +#define MIMI_SECRET_API_KEY "your-llm-key" +#define MIMI_SECRET_MODEL "your-model" +#define MIMI_SECRET_MODEL_PROVIDER "openai" /* or "anthropic" */ + +/* Search and proxy */ +#define MIMI_SECRET_TAVILY_KEY "" +#define MIMI_SECRET_SEARCH_KEY "" +#define MIMI_SECRET_PROXY_HOST "" +#define MIMI_SECRET_PROXY_PORT "" +#define MIMI_SECRET_PROXY_TYPE "" /* "http" or "socks5" */ + +/* Voice STT / TTS */ +#define MIMI_SECRET_STT_URL "https://your-stt-endpoint" +#define MIMI_SECRET_STT_API_KEY "your-stt-key" +#define MIMI_SECRET_STT_MODEL "your-stt-model" +#define MIMI_SECRET_TTS_URL "https://your-tts-endpoint" +#define MIMI_SECRET_TTS_API_KEY "your-tts-key" +#define MIMI_SECRET_TTS_MODEL "your-tts-model" +#define MIMI_SECRET_TTS_VOICE "" +#define MIMI_SECRET_TTS_LANGUAGE "English" + +/* ReSpeaker XVF3800 I2S pin map */ +#define MIMI_VOICE_I2S_PORT 0 +#define MIMI_VOICE_I2S_BCLK GPIO_NUM_8 +#define MIMI_VOICE_I2S_WS GPIO_NUM_7 +#define MIMI_VOICE_I2S_DIN GPIO_NUM_43 +#define MIMI_VOICE_I2S_DOUT GPIO_NUM_44 ``` -> **Important: Plug into the correct USB port!** Most ESP32-S3 boards have two USB-C ports. You must use the one labeled **USB** (native USB Serial/JTAG), **not** the one labeled **COM** (external UART bridge). Plugging into the wrong port will cause flash/monitor failures. -> ->
-> Show reference photo -> -> Plug into the USB port, not COM -> ->
+Notes: -### CLI Commands (via UART/COM port) +- `MIMI_SECRET_MODEL_PROVIDER` selects the request protocol, not just the vendor name. +- Use `openai` for OpenAI-compatible gateways. +- Use `anthropic` for Anthropic-compatible gateways. +- Voice mode requires STT and TTS URL/key pairs to be configured. +- LLM API base can be changed at runtime with `set_api_base`. -Connect via serial to configure or debug. **Config commands** let you change settings without recompiling — just plug in a USB cable anywhere. +## Adding STT and TTS -**Runtime config** (saved to NVS, overrides build-time defaults): +This project no longer treats speech as an afterthought. To enable the full ReSpeaker experience: -``` -mimi> wifi_set MySSID MyPassword # change WiFi network -mimi> set_tg_token 123456:ABC... # change Telegram bot token -mimi> set_api_key sk-ant-api03-... # change API key (Anthropic or OpenAI) -mimi> set_model_provider openai # switch provider (anthropic|openai) -mimi> set_model gpt-4o # change LLM model -mimi> set_proxy 127.0.0.1 7897 # set HTTP proxy -mimi> clear_proxy # remove proxy -mimi> set_search_key BSA... # set Brave Search API key -mimi> set_tavily_key tvly-... # set Tavily API key (preferred) -mimi> config_show # show all config (masked) -mimi> config_reset # clear NVS, revert to build-time defaults -``` +1. Configure `MIMI_SECRET_STT_URL`, `MIMI_SECRET_STT_API_KEY`, and `MIMI_SECRET_STT_MODEL`. +2. Configure `MIMI_SECRET_TTS_URL`, `MIMI_SECRET_TTS_API_KEY`, `MIMI_SECRET_TTS_MODEL`, `MIMI_SECRET_TTS_VOICE`, and `MIMI_SECRET_TTS_LANGUAGE`. +3. Set the XVF3800 input pins and your speaker output pins in the I2S section. +4. If your DAC or amp sounds noisy, set `MIMI_VOICE_I2S_STD_SLOT_STYLE` to match the hardware timing style. +5. If your room causes false triggers, tune `MIMI_VOICE_VAD_START_FRAMES`, `MIMI_VOICE_VAD_MIN_FRAMES`, and `MIMI_VOICE_STT_COOLDOWN_MS`. +6. If your TTS audio is too long, tune `MIMI_VOICE_TTS_MAX_SECONDS`, `MIMI_VOICE_TTS_CHARS_PER_SEC`, and `MIMI_VOICE_TTS_MAX_CHARS`. -**Debug & maintenance:** +The current firmware already contains the full voice channel: -``` -mimi> wifi_status # am I connected? -mimi> memory_read # see what the bot remembers -mimi> memory_write "content" # write to MEMORY.md -mimi> heap_info # how much RAM is free? -mimi> session_list # list all chat sessions -mimi> session_clear 12345 # wipe a conversation -mimi> heartbeat_trigger # manually trigger a heartbeat check -mimi> cron_start # start cron scheduler now -mimi> restart # reboot -``` - -### USB (JTAG) vs UART: Which Port for What - -Most ESP32-S3 dev boards expose **two USB-C ports**: - -| Port | Use for | -|------|---------| -| **USB** (JTAG) | `idf.py flash`, JTAG debugging | -| **COM** (UART) | **REPL CLI**, serial console | - -> **REPL requires the UART (COM) port.** The USB (JTAG) port does not support interactive REPL input. +- inbound: mic PCM -> VAD -> STT -> message bus +- outbound: agent text -> TTS -> playback -
-Port details & recommended workflow +## Flash and Monitor -| Port | Label | Protocol | -|------|-------|----------| -| **USB** | USB / JTAG | Native USB Serial/JTAG | -| **COM** | UART / COM | External UART bridge (CP2102/CH340) | +After changing `main/mimi_secrets.h`, rebuild from a clean state: -The ESP-IDF console/REPL is configured to use UART by default (`CONFIG_ESP_CONSOLE_UART_DEFAULT=y`). - -**If you have both ports connected simultaneously:** - -- USB (JTAG) handles flash/download and provides secondary serial output -- UART (COM) provides the primary interactive console for the REPL -- macOS: both appear as `/dev/cu.usbmodem*` or `/dev/cu.usbserial-*` — run `ls /dev/cu.usb*` to identify -- Linux: USB (JTAG) → `/dev/ttyACM0`, UART → `/dev/ttyUSB0` +```bash +idf.py fullclean +idf.py build +``` -**Recommended workflow:** +Find your serial port: ```bash -# Flash via USB (JTAG) port -idf.py -p /dev/cu.usbmodem11401 flash - -# Open REPL via UART (COM) port -idf.py -p /dev/cu.usbserial-110 monitor -# or use any serial terminal: screen, minicom, PuTTY at 115200 baud +ls /dev/cu.usb* # macOS +ls /dev/ttyACM* # Linux ``` -
+Flash and monitor: -## Memory +```bash +idf.py -p PORT flash monitor +``` -MimiClaw stores everything as plain text files you can read and edit: +Replace `PORT` with your actual device path. -| File | What it is | -|------|------------| -| `SOUL.md` | The bot's personality — edit this to change how it behaves | -| `USER.md` | Info about you — name, preferences, language | -| `MEMORY.md` | Long-term memory — things the bot should always remember | -| `HEARTBEAT.md` | Task list the bot checks periodically and acts on autonomously | -| `cron.json` | Scheduled jobs — recurring or one-shot tasks created by the AI | -| `2026-02-05.md` | Daily notes — what happened today | -| `tg_12345.jsonl` | Chat history — your conversation with the bot | +## Serial CLI -## Tools +The serial CLI is the fastest way to change runtime settings stored in NVS: -MimiClaw supports tool calling for both Anthropic and OpenAI — the LLM can call tools during a conversation and loop until the task is done (ReAct pattern). +``` +mimi> wifi_set MySSID MyPassword +mimi> set_tg_token 123456:ABC... +mimi> set_api_key your-llm-key +mimi> set_api_base https://your-compatible-endpoint/v1 +mimi> set_model_provider openai +mimi> set_model gpt-5.2 +mimi> set_proxy 127.0.0.1 7897 +mimi> clear_proxy +mimi> set_search_key BSA... +mimi> set_tavily_key tvly-... +mimi> config_show +mimi> config_reset +``` -| Tool | Description | -|------|-------------| -| `web_search` | Search the web via Tavily (preferred) or Brave for current information | -| `get_current_time` | Fetch current date/time via HTTP and set the system clock | -| `cron_add` | Schedule a recurring or one-shot task (the LLM creates cron jobs on its own) | -| `cron_list` | List all scheduled cron jobs | -| `cron_remove` | Remove a cron job by ID | +Maintenance commands: + +```text +mimi> wifi_status +mimi> memory_read +mimi> memory_write "remember this" +mimi> heap_info +mimi> session_list +mimi> session_clear 12345 +mimi> heartbeat_trigger +mimi> cron_start +mimi> restart +``` -To enable web search, set a [Tavily API key](https://app.tavily.com/home) via `MIMI_SECRET_TAVILY_KEY` (preferred), or a [Brave Search API key](https://brave.com/search/api/) via `MIMI_SECRET_SEARCH_KEY` in `mimi_secrets.h`. +## Compatible Provider Model -## Cron Tasks +`reSpeaker-claw` is not limited to the official Anthropic and OpenAI endpoints. -MimiClaw has a built-in cron scheduler that lets the AI schedule its own tasks. The LLM can create recurring jobs ("every N seconds") or one-shot jobs ("at unix timestamp") via the `cron_add` tool. When a job fires, its message is injected into the agent loop — so the AI wakes up, processes the task, and responds. +It supports: -Jobs are persisted to SPIFFS (`cron.json`) and survive reboots. Example use cases: daily summaries, periodic reminders, scheduled check-ins. +- Anthropic protocol compatible services, selected with `set_model_provider anthropic` +- OpenAI protocol compatible services, selected with `set_model_provider openai` +- Custom API bases through `set_api_base` -## Heartbeat +This makes it practical to use local gateways, regional cloud vendors, or unified API platforms without changing the agent loop. -The heartbeat service periodically reads `HEARTBEAT.md` from SPIFFS and checks for actionable tasks. If uncompleted items are found (anything that isn't an empty line, a header, or a checked `- [x]` box), it sends a prompt to the agent loop so the AI can act on them autonomously. +## Memory and Automation -This turns MimiClaw into a proactive assistant — write tasks to `HEARTBEAT.md` and the bot will pick them up on the next heartbeat cycle (default: every 30 minutes). +The agent persists its state in plain files on SPIFFS: -## Also Included +| File | Purpose | +|------|---------| +| `SOUL.md` | Assistant persona | +| `USER.md` | User profile | +| `MEMORY.md` | Long-term memory | +| `HEARTBEAT.md` | Periodic autonomous task list | +| `cron.json` | Scheduled jobs | +| `tg_12345.jsonl` | Session history | -- **WebSocket gateway** on port 18789 — connect from your LAN with any WebSocket client -- **OTA updates** — flash new firmware over WiFi, no USB needed -- **Dual-core** — network I/O and AI processing run on separate CPU cores -- **HTTP proxy** — CONNECT tunnel support for restricted networks -- **Multi-provider** — supports both Anthropic (Claude) and OpenAI (GPT), switchable at runtime -- **Cron scheduler** — the AI can schedule its own recurring and one-shot tasks, persisted across reboots -- **Heartbeat** — periodically checks a task file and prompts the AI to act autonomously -- **Tool use** — ReAct agent loop with tool calling for both providers +Built-in automation features: -## For Developers +- `cron_add`, `cron_list`, `cron_remove` +- heartbeat-driven proactive task handling +- tool calling in the ReAct loop +- local storage that survives reboot -Technical details live in the `docs/` folder: +## Tooling -- **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)** — system design, module map, task layout, memory budget, protocols, flash partitions -- **[docs/TODO.md](docs/TODO.md)** — feature gap tracker and roadmap -- **[docs/tool-setup/](docs/tool-setup/README.md)** — configuration guides for external service integrations (Tavily, etc.) +Built-in tools include: -## Contributing +- `web_search` +- `get_current_time` +- `cron_add` +- `cron_list` +- `cron_remove` +- SPIFFS file tools used by the agent runtime -Please read **[CONTRIBUTING.md](CONTRIBUTING.md)** before opening issues or pull requests. +For web search, configure either: -## Contributors +- `MIMI_SECRET_TAVILY_KEY` +- `MIMI_SECRET_SEARCH_KEY` -Thanks to everyone who has contributed to MimiClaw. +## Acknowledgments - - MimiClaw contributors - +This project builds on the original [mimiclaw](https://github.com/memovai/mimiclaw). reSpeaker-claw adapts that embedded agent foundation to ReSpeaker XVF3800 voice hardware, extends the STT / TTS pipeline, and continues the multi-channel agent architecture. ## License MIT - -## Acknowledgments - -Inspired by [OpenClaw](https://github.com/openclaw/openclaw) and [Nanobot](https://github.com/HKUDS/nanobot). MimiClaw reimplements the core AI agent architecture for embedded hardware — no Linux, no server, just a $5 chip. - -## Star History - -[![Star History Chart](https://api.star-history.com/svg?repos=memovai/mimiclaw&type=Date)](https://star-history.com/#memovai/mimiclaw&Date) diff --git a/README_CN.md b/README_CN.md index 15b6e1ad..4a36f65f 100644 --- a/README_CN.md +++ b/README_CN.md @@ -1,340 +1,264 @@ -# MimiClaw: $5 芯片上的口袋 AI 助理 +# reSpeaker-claw:面向 ReSpeaker XVF3800 的语音 AI Agent

- MimiClaw -

- -

- License: MIT - DeepWiki - Discord - X + License: MIT + Language: C + Framework: ESP-IDF v5.5+ + Hardware: ReSpeaker XVF3800 + Architecture: Voice Agent

English | 中文 | 日本語

-**$5 芯片上的 AI 助理(OpenClaw)。没有 Linux,没有 Node.js,纯 C。** +reSpeaker-claw 将基于 ReSpeaker XVF3800 的设备变成一个以语音为主入口的 AI Agent。它通过 I2S 采集音频,在本地执行 VAD,将话语送入 STT,并通过嵌入式 agent loop 处理。系统把实时语音交互、本地记忆、工具调用、调度、heartbeat、OTA 更新和代理支持整合在一起,最后通过 TTS 从扬声器返回响应。 -MimiClaw 把一块小小的 ESP32-S3 开发板变成你的私人 AI 助理。插上 USB 供电,连上 WiFi,通过 Telegram 跟它对话 — 它能处理你丢给它的任何任务,还会随时间积累本地记忆不断进化 — 全部跑在一颗拇指大小的芯片上。 +## 认识 reSpeaker-claw -## 认识 MimiClaw +- **小巧**:没有 Linux,没有 Node.js,没有臃肿依赖,只有纯 C +- **忠诚**:从记忆中学习,重启后依然保留上下文 +- **高效**:USB 供电,功耗更低,可 24/7 运行 +- **自由**:ReSpeaker XVF3800 麦克风阵列,配合你自己选择的功放或 DAC +- **顺手**:内置语音通道,除了 XVF3800 和扬声器链路,不需要额外硬件 -- **小巧** — 没有 Linux,没有 Node.js,没有臃肿依赖 — 纯 C -- **好用** — 在 Telegram 发消息,剩下的它来搞定 -- **忠诚** — 从记忆中学习,跨重启也不会忘 -- **能干** — USB 供电,0.5W,24/7 运行 -- **可爱** — 一块 ESP32-S3 开发板,$5,没了 +## 亮点 -## 工作原理 +- 语音输入:ReSpeaker XVF3800 麦克风阵列,通过 I2S 接入 +- 语音输出:TTS 音频下载、WAV 解码、重采样与 I2S 播放 +- 多通道 Agent:语音、Telegram、飞书、WebSocket +- 本地持久化:SPIFFS 保存记忆、配置、会话、cron 任务和每日笔记 +- 兼容 LLM 后端:支持官方 Anthropic / OpenAI API,也支持兼容 Anthropic 或 OpenAI 协议的第三方网关 +- 可配置 STT / TTS:可接入你自己的服务 URL、API Key、模型、音色和语言 +- 运行时覆盖:可通过串口 CLI 修改 WiFi、provider、model、API base、代理和 token,无需改代码 -![](assets/mimiclaw.png) +## 快速开始 -你在 Telegram 发一条消息,ESP32-S3 通过 WiFi 收到后送进 Agent 循环 — LLM 思考、调用工具、读取记忆 — 再把回复发回来。同时支持 **Anthropic (Claude)** 和 **OpenAI (GPT)** 两种提供商,运行时可切换。一切都跑在一颗 $5 的芯片上,所有数据存在本地 Flash。 +### 依赖条件 -## 快速开始 +- 一套 reSpeaker XVF3800 USB 4 Microphone Array 搭配 XIAO ESP32S3 开发板 +- 一路 I2S 输出到扬声器 / DAC / 功放 +- 一根用于烧录和串口监控的 USB 线 +- 可用的 WiFi +- ESP-IDF v5.5+ +- 可选:如果你要使用 Telegram,需要 Telegram Bot Token +- 可选:如果你要使用飞书,需要飞书应用凭证 +- 一个兼容 Anthropic 或 OpenAI 协议的 LLM API Key +- 一套用于语音模式的 STT 服务和 TTS 服务 -### 你需要 +### 克隆与构建环境 -- 一块 **ESP32-S3 开发板**,16MB Flash + 8MB PSRAM(如小智 AI 开发板,~¥30) -- 一根 **USB Type-C 数据线** -- 一个 **Telegram Bot Token** — 在 Telegram 找 [@BotFather](https://t.me/BotFather) 创建 -- 一个 **Anthropic API Key** — 从 [console.anthropic.com](https://console.anthropic.com) 获取,或一个 **OpenAI API Key** — 从 [platform.openai.com](https://platform.openai.com) 获取 +先参考官方指南刷入 I2S 固件: +[SeeedStudio wiki](https://wiki.seeedstudio.com/respeaker_xvf3800_introduction/#flash-firmware) -### 安装 +然后克隆本项目并设置目标: ```bash -# 需要先安装 ESP-IDF v5.5+: -# https://docs.espressif.com/projects/esp-idf/en/v5.5.2/esp32s3/get-started/ - -git clone https://github.com/memovai/mimiclaw.git -cd mimiclaw +git clone https://github.com/Seeed-Projects/reSpeaker-claw +cd reSpeaker-claw idf.py set-target esp32s3 ``` -
-Ubuntu 安装 - -建议基线: - -- Ubuntu 22.04/24.04 -- Python >= 3.10 -- CMake >= 3.16 -- Ninja >= 1.10 -- Git >= 2.34 -- flex >= 2.6 -- bison >= 3.8 -- gperf >= 3.1 -- dfu-util >= 0.11 -- `libusb-1.0-0`、`libffi-dev`、`libssl-dev` +先安装 ESP-IDF:[ESP-IDF 安装](https://docs.espressif.com/projects/esp-idf/en/v5.5.3/esp32s3/get-started/) -Ubuntu 安装与构建: +Ubuntu 辅助脚本: ```bash -sudo apt-get update -sudo apt-get install -y git wget flex bison gperf python3 python3-pip python3-venv \ - cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 - ./scripts/setup_idf_ubuntu.sh ./scripts/build_ubuntu.sh ``` -
- -
-macOS 安装 - -建议基线: - -- macOS 12/13/14 -- Xcode Command Line Tools -- Homebrew -- Python >= 3.10 -- CMake >= 3.16 -- Ninja >= 1.10 -- Git >= 2.34 -- flex >= 2.6 -- bison >= 3.8 -- gperf >= 3.1 -- dfu-util >= 0.11 -- `libusb`、`libffi`、`openssl` - -macOS 安装与构建: +macOS 辅助脚本: ```bash -xcode-select --install -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - ./scripts/setup_idf_macos.sh ./scripts/build_macos.sh ``` -
- -### 配置 +## 配置 -MimiClaw 使用**两层配置**:`mimi_secrets.h` 提供编译时默认值,串口 CLI 可在运行时覆盖。CLI 设置的值存在 NVS Flash 中,优先级高于编译时值。 +复制示例 secrets 文件: ```bash -cp main/mimi_secrets.h.example main/mimi_secrets.h +cp "main/mimi_secrets.h.example" "main/mimi_secrets.h" ``` -编辑 `main/mimi_secrets.h`: +编辑 `main/mimi_secrets.h`,填写你实际需要的配置项: ```c -#define MIMI_SECRET_WIFI_SSID "你的WiFi名" -#define MIMI_SECRET_WIFI_PASS "你的WiFi密码" -#define MIMI_SECRET_TG_TOKEN "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" -#define MIMI_SECRET_API_KEY "sk-ant-api03-xxxxx" -#define MIMI_SECRET_LLM_API_URL "" // 可选:完整URL(支持 http/https + 自定义端口) -#define MIMI_SECRET_MODEL_PROVIDER "anthropic" // "anthropic" 或 "openai" -#define MIMI_SECRET_SEARCH_KEY "" // 可选:Brave Search API key -#define MIMI_SECRET_TAVILY_KEY "" // 可选:Tavily API key(优先) -#define MIMI_SECRET_PROXY_HOST "10.0.0.1" // 可选:代理地址 -#define MIMI_SECRET_PROXY_PORT "7897" // 可选:代理端口 +/* WiFi */ +#define MIMI_SECRET_WIFI_SSID "YourWiFiName" +#define MIMI_SECRET_WIFI_PASS "YourWiFiPassword" + +/* Optional text channels */ +#define MIMI_SECRET_TG_TOKEN "" +#define MIMI_SECRET_FEISHU_APP_ID "" +#define MIMI_SECRET_FEISHU_APP_SECRET "" + +/* LLM */ +#define MIMI_SECRET_API_KEY "your-llm-key" +#define MIMI_SECRET_MODEL "your-model" +#define MIMI_SECRET_MODEL_PROVIDER "openai" /* or "anthropic" */ + +/* Search and proxy */ +#define MIMI_SECRET_TAVILY_KEY "" +#define MIMI_SECRET_SEARCH_KEY "" +#define MIMI_SECRET_PROXY_HOST "" +#define MIMI_SECRET_PROXY_PORT "" +#define MIMI_SECRET_PROXY_TYPE "" /* "http" or "socks5" */ + +/* Voice STT / TTS */ +#define MIMI_SECRET_STT_URL "https://your-stt-endpoint" +#define MIMI_SECRET_STT_API_KEY "your-stt-key" +#define MIMI_SECRET_STT_MODEL "your-stt-model" +#define MIMI_SECRET_TTS_URL "https://your-tts-endpoint" +#define MIMI_SECRET_TTS_API_KEY "your-tts-key" +#define MIMI_SECRET_TTS_MODEL "your-tts-model" +#define MIMI_SECRET_TTS_VOICE "" +#define MIMI_SECRET_TTS_LANGUAGE "English" + +/* ReSpeaker XVF3800 I2S pin map */ +#define MIMI_VOICE_I2S_PORT 0 +#define MIMI_VOICE_I2S_BCLK GPIO_NUM_8 +#define MIMI_VOICE_I2S_WS GPIO_NUM_7 +#define MIMI_VOICE_I2S_DIN GPIO_NUM_43 +#define MIMI_VOICE_I2S_DOUT GPIO_NUM_44 ``` -然后编译烧录: +说明: -```bash -# 完整编译(修改 mimi_secrets.h 后必须 fullclean) -idf.py fullclean && idf.py build - -# 查找串口 -ls /dev/cu.usb* # macOS -ls /dev/ttyACM* # Linux - -# 烧录并监控(将 PORT 替换为你的串口) -# USB 转接器:大概率是 /dev/cu.usbmodem11401(macOS)或 /dev/ttyACM0(Linux) -idf.py -p PORT flash monitor -``` - -> **注意:请插对 USB 口!** 大多数 ESP32-S3 开发板有两个 Type-C 接口,必须插标有 **USB** 的那个口(原生 USB Serial/JTAG),**不要**插标有 **COM** 的口(外部 UART 桥接)。插错口会导致烧录/监控失败。 -> ->
-> 查看参考图片 -> -> 请插 USB 口,不要插 COM 口 -> ->
+- `MIMI_SECRET_MODEL_PROVIDER` 选择的是请求协议,而不只是厂商名 +- 兼容 OpenAI 协议的网关使用 `openai` +- 兼容 Anthropic 协议的网关使用 `anthropic` +- 语音模式要求 STT 与 TTS 的 URL / Key 成对配置 +- LLM API base 可在运行时通过 `set_api_base` 修改 -### 代理配置(国内用户) +## 添加 STT 和 TTS -在国内需要代理才能访问 Telegram 和 Anthropic API。MimiClaw 内置 HTTP CONNECT 隧道支持。 +这个项目不再把语音当成附属功能。要启用完整的 ReSpeaker 体验: -**前提**:局域网内有一个支持 HTTP CONNECT 的代理(Clash Verge、V2Ray 等),并开启了「允许局域网连接」。 +1. 配置 `MIMI_SECRET_STT_URL`、`MIMI_SECRET_STT_API_KEY` 和 `MIMI_SECRET_STT_MODEL` +2. 配置 `MIMI_SECRET_TTS_URL`、`MIMI_SECRET_TTS_API_KEY`、`MIMI_SECRET_TTS_MODEL`、`MIMI_SECRET_TTS_VOICE` 和 `MIMI_SECRET_TTS_LANGUAGE` +3. 在 I2S 配置段中设置 XVF3800 的输入引脚和扬声器输出引脚 +4. 如果 DAC 或功放播放出来像噪音,设置 `MIMI_VOICE_I2S_STD_SLOT_STYLE` 以匹配硬件时序 +5. 如果房间环境导致误触发,调节 `MIMI_VOICE_VAD_START_FRAMES`、`MIMI_VOICE_VAD_MIN_FRAMES` 和 `MIMI_VOICE_STT_COOLDOWN_MS` +6. 如果 TTS 音频过长,调节 `MIMI_VOICE_TTS_MAX_SECONDS`、`MIMI_VOICE_TTS_CHARS_PER_SEC` 和 `MIMI_VOICE_TTS_MAX_CHARS` -可以在 `mimi_secrets.h` 中编译时设置,也可以通过串口 CLI 随时修改: +当前固件已经包含完整的语音通道: -``` -mimi> set_proxy 192.168.1.83 7897 # 设置代理 -mimi> clear_proxy # 清除代理 -``` +- 输入方向:mic PCM -> VAD -> STT -> message bus +- 输出方向:agent text -> TTS -> playback -> **提示**:确保 ESP32-S3 和代理机器在同一局域网。Clash Verge 在「设置 → 允许局域网」中开启。 +## 烧录与监控 -### CLI 命令(通过 UART/COM 口连接) +修改 `main/mimi_secrets.h` 后,建议从干净状态重新构建: -通过串口连接即可配置和调试。**配置命令**让你无需重新编译就能修改设置 — 随时随地插上 USB 线就能改。 - -**运行时配置**(存入 NVS,覆盖编译时默认值): - -``` -mimi> wifi_set MySSID MyPassword # 换 WiFi -mimi> set_tg_token 123456:ABC... # 换 Telegram Bot Token -mimi> set_api_key sk-ant-api03-... # 换 API Key(Anthropic 或 OpenAI) -mimi> set_model_provider openai # 切换提供商(anthropic|openai) -mimi> set_model gpt-4o # 换模型 -mimi> set_proxy 192.168.1.83 7897 # 设置代理 -mimi> clear_proxy # 清除代理 -mimi> set_search_key BSA... # 设置 Brave Search API Key -mimi> set_tavily_key tvly-... # 设置 Tavily API Key(优先) -mimi> config_show # 查看所有配置(脱敏显示) -mimi> config_reset # 清除 NVS,恢复编译时默认值 +```bash +idf.py fullclean +idf.py build ``` -**调试与运维:** +查找串口: +```bash +ls /dev/cu.usb* # macOS +ls /dev/ttyACM* # Linux ``` -mimi> wifi_status # 连上了吗? -mimi> memory_read # 看看它记住了什么 -mimi> memory_write "内容" # 写入 MEMORY.md -mimi> heap_info # 还剩多少内存? -mimi> session_list # 列出所有会话 -mimi> session_clear 12345 # 删除一个会话 -mimi> heartbeat_trigger # 手动触发一次心跳检查 -mimi> cron_start # 立即启动 cron 调度器 -mimi> restart # 重启 -``` - -### USB (JTAG) 与 UART:哪个口做什么 -大多数 ESP32-S3 开发板有 **两个 USB-C 口**: - -| 端口 | 用途 | -|------|------| -| **USB**(JTAG) | `idf.py flash`、JTAG 调试 | -| **COM**(UART) | **REPL 命令行**、串口控制台 | - -> **REPL 必须连接 UART(COM)口。** USB(JTAG)口不支持交互式 REPL 输入。 - -
-端口详情与推荐工作流 - -| 端口 | 标注 | 协议 | -|------|------|------| -| **USB** | USB / JTAG | 原生 USB Serial/JTAG | -| **COM** | UART / COM | 外置 UART 桥接芯片(CP2102/CH340) | - -ESP-IDF 控制台默认配置为 UART 输出(`CONFIG_ESP_CONSOLE_UART_DEFAULT=y`)。 - -**同时连接两个口时:** - -- USB(JTAG)口负责烧录/下载,并提供辅助串口输出 -- UART(COM)口提供主要的交互式控制台,用于 REPL -- macOS 下两个口都会显示为 `/dev/cu.usbmodem*` 或 `/dev/cu.usbserial-*`,用 `ls /dev/cu.usb*` 区分 -- Linux 下 USB(JTAG)通常是 `/dev/ttyACM0`,UART 通常是 `/dev/ttyUSB0` - -**推荐工作流:** +烧录并监控: ```bash -# 通过 USB(JTAG)口烧录 -idf.py -p /dev/cu.usbmodem11401 flash - -# 通过 UART(COM)口打开 REPL -idf.py -p /dev/cu.usbserial-110 monitor -# 或使用任意串口工具:screen、minicom、PuTTY,波特率 115200 +idf.py -p PORT flash monitor ``` -
- -## 记忆 - -MimiClaw 把所有数据存为纯文本文件,可以直接读取和编辑: - -| 文件 | 说明 | -|------|------| -| `SOUL.md` | 机器人的人设 — 编辑它来改变行为方式 | -| `USER.md` | 关于你的信息 — 姓名、偏好、语言 | -| `MEMORY.md` | 长期记忆 — 它应该一直记住的事 | -| `HEARTBEAT.md` | 待办清单 — 机器人定期检查并自主执行 | -| `cron.json` | 定时任务 — AI 创建的周期性或一次性任务 | -| `2026-02-05.md` | 每日笔记 — 今天发生了什么 | -| `tg_12345.jsonl` | 聊天记录 — 你和它的对话 | - -## 工具 - -MimiClaw 同时支持 Anthropic 和 OpenAI 的工具调用 — LLM 在对话中可以调用工具,循环执行直到任务完成(ReAct 模式)。 +将 `PORT` 替换为你的实际设备路径。 + +## 串口 CLI + +串口 CLI 是修改 NVS 运行时配置的最快方式: + +```text +mimi> wifi_set MySSID MyPassword +mimi> set_tg_token 123456:ABC... +mimi> set_api_key your-llm-key +mimi> set_api_base https://your-compatible-endpoint/v1 +mimi> set_model_provider openai +mimi> set_model gpt-5.2 +mimi> set_proxy 127.0.0.1 7897 +mimi> clear_proxy +mimi> set_search_key BSA... +mimi> set_tavily_key tvly-... +mimi> config_show +mimi> config_reset +``` -| 工具 | 说明 | -|------|------| -| `web_search` | 通过 Tavily(优先)或 Brave 搜索网页,获取实时信息 | -| `get_current_time` | 通过 HTTP 获取当前日期和时间,并设置系统时钟 | -| `cron_add` | 创建定时或一次性任务(LLM 自主创建 cron 任务) | -| `cron_list` | 列出所有已调度的 cron 任务 | -| `cron_remove` | 按 ID 删除 cron 任务 | +维护命令: + +```text +mimi> wifi_status +mimi> memory_read +mimi> memory_write "remember this" +mimi> heap_info +mimi> session_list +mimi> session_clear 12345 +mimi> heartbeat_trigger +mimi> cron_start +mimi> restart +``` -启用网页搜索可在 `mimi_secrets.h` 中设置 [Tavily API key](https://app.tavily.com/home)(优先,`MIMI_SECRET_TAVILY_KEY`),或 [Brave Search API key](https://brave.com/search/api/)(`MIMI_SECRET_SEARCH_KEY`)。 +## 兼容 Provider 模型 -## 定时任务(Cron) +`reSpeaker-claw` 不局限于官方 Anthropic 和 OpenAI 端点。 -MimiClaw 内置 cron 调度器,让 AI 可以自主安排任务。LLM 可以通过 `cron_add` 工具创建周期性任务("每 N 秒")或一次性任务("在某个时间戳")。任务触发时,消息会注入到 Agent 循环 — AI 自动醒来、处理任务并回复。 +它支持: -任务持久化存储在 SPIFFS(`cron.json`),重启后不会丢失。典型用途:每日总结、定时提醒、定期巡检。 +- 兼容 Anthropic 协议的服务,通过 `set_model_provider anthropic` 选择 +- 兼容 OpenAI 协议的服务,通过 `set_model_provider openai` 选择 +- 通过 `set_api_base` 指向任意兼容 API base -## 心跳(Heartbeat) +这让你可以在不修改 agent loop 的情况下,直接使用本地网关、区域云厂商或统一 API 平台。 -心跳服务会定期读取 SPIFFS 上的 `HEARTBEAT.md`,检查是否有待办事项。如果发现未完成的条目(非空行、非标题、非已勾选的 `- [x]`),就会向 Agent 循环发送提示,让 AI 自主处理。 +## 记忆与自动化 -这让 MimiClaw 变成一个主动型助理 — 把任务写入 `HEARTBEAT.md`,机器人会在下一次心跳周期自动拾取执行(默认每 30 分钟)。 +Agent 会将状态以纯文本文件形式持久化到 SPIFFS: -## 其他功能 +| 文件 | 用途 | +|------|------| +| `SOUL.md` | 助手人格 | +| `USER.md` | 用户资料 | +| `MEMORY.md` | 长期记忆 | +| `HEARTBEAT.md` | 周期性自主任务列表 | +| `cron.json` | 调度任务 | +| `tg_12345.jsonl` | 会话历史 | -- **WebSocket 网关** — 端口 18789,局域网内用任意 WebSocket 客户端连接 -- **OTA 更新** — WiFi 远程刷固件,无需 USB -- **双核** — 网络 I/O 和 AI 处理分别跑在不同 CPU 核心 -- **HTTP 代理** — CONNECT 隧道,适配受限网络 -- **多提供商** — 同时支持 Anthropic (Claude) 和 OpenAI (GPT),运行时可切换 -- **定时任务** — AI 可自主创建周期性和一次性任务,重启后持久保存 -- **心跳服务** — 定期检查任务文件,驱动 AI 自主执行 -- **工具调用** — ReAct Agent 循环,两种提供商均支持工具调用 +内置自动化能力: -## 开发者 +- `cron_add`、`cron_list`、`cron_remove` +- heartbeat 驱动的主动任务处理 +- ReAct loop 中的工具调用 +- 重启后仍可保留的本地状态 -技术细节在 `docs/` 文件夹: +## 工具 -- **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)** — 系统设计、模块划分、任务布局、内存分配、协议、Flash 分区 -- **[docs/TODO.md](docs/TODO.md)** — 功能差距和路线图 -- **[docs/im-integration/](docs/im-integration/README.md)** — IM 通道集成指南(飞书等) +内置工具包括: -## 贡献 +- `web_search` +- `get_current_time` +- `cron_add` +- `cron_list` +- `cron_remove` +- Agent 运行时使用的 SPIFFS 文件工具 -提交 Issue 或 Pull Request 前,请先阅读 **[CONTRIBUTING.md](CONTRIBUTING.md)**。 +如需启用网页搜索,配置以下任一项: -## 贡献者 +- `MIMI_SECRET_TAVILY_KEY` +- `MIMI_SECRET_SEARCH_KEY` -感谢所有为 MimiClaw 做出贡献的开发者。 +## 致谢 - - MimiClaw contributors - +本项目基于原始的 [mimiclaw](https://github.com/memovai/mimiclaw)。reSpeaker-claw 将那套嵌入式 agent 基础适配到 ReSpeaker XVF3800 语音硬件之上,扩展了 STT / TTS 流程,并延续了多通道 agent 架构。 ## 许可证 MIT - -## 致谢 - -灵感来自 [OpenClaw](https://github.com/openclaw/openclaw) 和 [Nanobot](https://github.com/HKUDS/nanobot)。MimiClaw 为嵌入式硬件重新实现了核心 AI Agent 架构 — 没有 Linux,没有服务器,只有一颗 $5 的芯片。 - -## Star History - - - - - - Star History Chart - - diff --git a/README_JA.md b/README_JA.md index df298ada..cfd2e2d7 100644 --- a/README_JA.md +++ b/README_JA.md @@ -1,325 +1,264 @@ -# MimiClaw: $5チップで動くポケットAIアシスタント +# reSpeaker-claw: ReSpeaker XVF3800 向け音声 AI Agent

- MimiClaw -

- -

- License: MIT - DeepWiki - Discord - X + License: MIT + Language: C + Framework: ESP-IDF v5.5+ + Hardware: ReSpeaker XVF3800 + Architecture: Voice Agent

English | 中文 | 日本語

-**$5チップ上の世界初のAIアシスタント(OpenClaw)。Linuxなし、Node.jsなし、純粋なCのみ。** - -MimiClawは小さなESP32-S3ボードをパーソナルAIアシスタントに変えます。USB電源に接続し、WiFiにつなげて、Telegramから話しかけるだけ — どんなタスクも処理し、ローカルメモリで時間とともに成長します — すべて親指サイズのチップ上で。 +reSpeaker-claw は、ReSpeaker XVF3800 ベースのデバイスを音声ファーストの AI Agent に変えるプロジェクトです。I2S で音声を取り込み、ローカル VAD を実行し、発話を STT に送って組み込みの agent loop で処理します。システムはリアルタイム音声対話に加えて、ローカルメモリ、ツール呼び出し、スケジューリング、heartbeat、OTA 更新、プロキシ対応を統合し、最終的に TTS でスピーカーから応答を返します。 -## MimiClawの特徴 +## reSpeaker-claw とは -- **超小型** — Linux不要、Node.js不要、無駄なし — 純粋なCのみ -- **便利** — Telegramでメッセージを送るだけ、あとはお任せ -- **忠実** — メモリから学習し、再起動しても忘れない -- **省エネ** — USB給電、0.5W、24時間365日稼働 -- **お手頃** — ESP32-S3ボード1枚、$5、それだけ +- **小さい**: Linux なし、Node.js なし、無駄な依存なし、純粋な C のみ +- **記憶する**: メモリから学習し、再起動後も文脈を保持 +- **省電力**: USB 給電、より低消費電力で 24/7 稼働可能 +- **自由度が高い**: ReSpeaker XVF3800 のマイクアレイに、好みのアンプや DAC を組み合わせ可能 +- **扱いやすい**: 音声チャネルを内蔵し、XVF3800 とスピーカー経路以外の追加ハードウェアをほぼ必要としない -## 仕組み +## 特長 -![](assets/mimiclaw.png) - -Telegramでメッセージを送ると、ESP32-S3がWiFi経由で受信し、エージェントループに送ります — LLMが思考し、ツールを呼び出し、メモリを読み取り — 返答を送り返します。**Anthropic (Claude)** と **OpenAI (GPT)** の両方をサポートし、実行時に切り替え可能です。すべてが$5のチップ上で動作し、データはすべてローカルのFlashに保存されます。 +- 音声入力: ReSpeaker XVF3800 マイクアレイを I2S で接続 +- 音声出力: TTS 音声のダウンロード、WAV デコード、リサンプル、I2S 再生 +- マルチチャネル Agent: 音声、Telegram、Feishu、WebSocket +- ローカル永続化: SPIFFS にメモリ、設定、セッション、cron ジョブ、日次メモを保存 +- 互換 LLM バックエンド: 公式 Anthropic / OpenAI API に加え、Anthropic 互換または OpenAI 互換エンドポイントも利用可能 +- STT / TTS を柔軟に設定可能: URL、API Key、モデル、音色、言語を自由に差し替え可能 +- 実行時オーバーライド: WiFi、provider、model、API base、proxy、token をシリアル CLI から変更可能 ## クイックスタート ### 必要なもの -- **ESP32-S3開発ボード**(16MB Flash + 8MB PSRAM搭載、例:小智AIボード、約$10) -- **USB Type-Cケーブル** -- **Telegram Botトークン** — Telegramで[@BotFather](https://t.me/BotFather)に話しかけて作成 -- **Anthropic APIキー** — [console.anthropic.com](https://console.anthropic.com)から取得、または **OpenAI APIキー** — [platform.openai.com](https://platform.openai.com)から取得 +- reSpeaker XVF3800 USB 4 Microphone Array と XIAO ESP32S3 ボード +- I2S 出力で接続するスピーカー / DAC / アンプ経路 +- 書き込みとシリアルモニタ用の USB ケーブル +- WiFi 接続 +- ESP-IDF v5.5+ +- 任意: Telegram を使う場合は Telegram Bot Token +- 任意: Feishu を使う場合は Feishu アプリ認証情報 +- Anthropic 互換または OpenAI 互換エンドポイント向けの LLM API Key +- 音声モード用の STT サービスと TTS サービス -### インストール +### クローンとビルド環境 -```bash -# まずESP-IDF v5.5+をインストールしてください: -# https://docs.espressif.com/projects/esp-idf/en/v5.5.2/esp32s3/get-started/ +まず公式ガイドを参照して I2S ファームウェアを書き込んでください: +[SeeedStudio wiki](https://wiki.seeedstudio.com/respeaker_xvf3800_introduction/#flash-firmware) + +その後、このプロジェクトをクローンしてターゲットを設定します: -git clone https://github.com/memovai/mimiclaw.git -cd mimiclaw +```bash +git clone https://github.com/Seeed-Projects/reSpeaker-claw +cd reSpeaker-claw idf.py set-target esp32s3 ``` -
-Ubuntu インストール - -推奨ベースライン: +ESP-IDF は先にインストールしてください: [ESP-IDF Install](https://docs.espressif.com/projects/esp-idf/en/v5.5.3/esp32s3/get-started/) -- Ubuntu 22.04/24.04 -- Python >= 3.10 -- CMake >= 3.16 -- Ninja >= 1.10 -- Git >= 2.34 -- flex >= 2.6 -- bison >= 3.8 -- gperf >= 3.1 -- dfu-util >= 0.11 -- `libusb-1.0-0`, `libffi-dev`, `libssl-dev` - -Ubuntu でのインストールとビルド: +Ubuntu 用ヘルパースクリプト: ```bash -sudo apt-get update -sudo apt-get install -y git wget flex bison gperf python3 python3-pip python3-venv \ - cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 - ./scripts/setup_idf_ubuntu.sh ./scripts/build_ubuntu.sh ``` -
- -
-macOS インストール - -推奨ベースライン: - -- macOS 12/13/14 -- Xcode Command Line Tools -- Homebrew -- Python >= 3.10 -- CMake >= 3.16 -- Ninja >= 1.10 -- Git >= 2.34 -- flex >= 2.6 -- bison >= 3.8 -- gperf >= 3.1 -- dfu-util >= 0.11 -- `libusb`, `libffi`, `openssl` - -macOS でのインストールとビルド: +macOS 用ヘルパースクリプト: ```bash -xcode-select --install -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - ./scripts/setup_idf_macos.sh ./scripts/build_macos.sh ``` -
+## 設定 -### 設定 - -MimiClawは**2層設定**を採用しています:`mimi_secrets.h`でビルド時のデフォルト値を設定し、シリアルCLIで実行時にオーバーライドできます。CLI設定値はNVS Flashに保存され、ビルド時の値より優先されます。 +まず secrets のサンプルファイルをコピーします: ```bash -cp main/mimi_secrets.h.example main/mimi_secrets.h +cp "main/mimi_secrets.h.example" "main/mimi_secrets.h" ``` -`main/mimi_secrets.h`を編集: +`main/mimi_secrets.h` を編集し、実際に使う項目を設定します: ```c -#define MIMI_SECRET_WIFI_SSID "WiFi名" -#define MIMI_SECRET_WIFI_PASS "WiFiパスワード" -#define MIMI_SECRET_TG_TOKEN "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" -#define MIMI_SECRET_API_KEY "sk-ant-api03-xxxxx" -#define MIMI_SECRET_LLM_API_URL "" // 任意:完全なURL(http/https + カスタムポートをサポート) -#define MIMI_SECRET_MODEL_PROVIDER "anthropic" // "anthropic" または "openai" -#define MIMI_SECRET_SEARCH_KEY "" // 任意:Brave Search APIキー -#define MIMI_SECRET_TAVILY_KEY "" // 任意:Tavily APIキー(優先) -#define MIMI_SECRET_PROXY_HOST "" // 任意:例 "10.0.0.1" -#define MIMI_SECRET_PROXY_PORT "" // 任意:例 "7897" +/* WiFi */ +#define MIMI_SECRET_WIFI_SSID "YourWiFiName" +#define MIMI_SECRET_WIFI_PASS "YourWiFiPassword" + +/* Optional text channels */ +#define MIMI_SECRET_TG_TOKEN "" +#define MIMI_SECRET_FEISHU_APP_ID "" +#define MIMI_SECRET_FEISHU_APP_SECRET "" + +/* LLM */ +#define MIMI_SECRET_API_KEY "your-llm-key" +#define MIMI_SECRET_MODEL "your-model" +#define MIMI_SECRET_MODEL_PROVIDER "openai" /* or "anthropic" */ + +/* Search and proxy */ +#define MIMI_SECRET_TAVILY_KEY "" +#define MIMI_SECRET_SEARCH_KEY "" +#define MIMI_SECRET_PROXY_HOST "" +#define MIMI_SECRET_PROXY_PORT "" +#define MIMI_SECRET_PROXY_TYPE "" /* "http" or "socks5" */ + +/* Voice STT / TTS */ +#define MIMI_SECRET_STT_URL "https://your-stt-endpoint" +#define MIMI_SECRET_STT_API_KEY "your-stt-key" +#define MIMI_SECRET_STT_MODEL "your-stt-model" +#define MIMI_SECRET_TTS_URL "https://your-tts-endpoint" +#define MIMI_SECRET_TTS_API_KEY "your-tts-key" +#define MIMI_SECRET_TTS_MODEL "your-tts-model" +#define MIMI_SECRET_TTS_VOICE "" +#define MIMI_SECRET_TTS_LANGUAGE "English" + +/* ReSpeaker XVF3800 I2S pin map */ +#define MIMI_VOICE_I2S_PORT 0 +#define MIMI_VOICE_I2S_BCLK GPIO_NUM_8 +#define MIMI_VOICE_I2S_WS GPIO_NUM_7 +#define MIMI_VOICE_I2S_DIN GPIO_NUM_43 +#define MIMI_VOICE_I2S_DOUT GPIO_NUM_44 ``` -ビルドとフラッシュ: +補足: -```bash -# フルビルド(mimi_secrets.h変更後はfullclean必須) -idf.py fullclean && idf.py build +- `MIMI_SECRET_MODEL_PROVIDER` はベンダ名ではなく、リクエストプロトコルを選択します +- OpenAI 互換ゲートウェイには `openai` を使用します +- Anthropic 互換ゲートウェイには `anthropic` を使用します +- 音声モードでは STT と TTS の URL / Key を両方設定する必要があります +- LLM API base は実行時に `set_api_base` で変更できます -# シリアルポートを確認 -ls /dev/cu.usb* # macOS -ls /dev/ttyACM* # Linux +## STT と TTS の追加 -# フラッシュとモニター(PORTをあなたのポートに置き換え) -# USBアダプタ:おそらく /dev/cu.usbmodem11401(macOS)または /dev/ttyACM0(Linux) -idf.py -p PORT flash monitor -``` +このプロジェクトでは、音声を後付け機能として扱っていません。完全な ReSpeaker 体験を有効にするには: -> **重要:正しいUSBポートに接続してください!** ほとんどのESP32-S3ボードには2つのUSB-Cポートがあります。**USB**(ネイティブUSB Serial/JTAG)と書かれたポートを使用してください。**COM**(外部UARTブリッジ)と書かれたポートは使わないでください。間違ったポートに接続するとフラッシュ/モニターが失敗します。 -> ->
-> 参考画像を表示 -> -> USBポートに接続、COMポートではありません -> ->
+1. `MIMI_SECRET_STT_URL`、`MIMI_SECRET_STT_API_KEY`、`MIMI_SECRET_STT_MODEL` を設定します +2. `MIMI_SECRET_TTS_URL`、`MIMI_SECRET_TTS_API_KEY`、`MIMI_SECRET_TTS_MODEL`、`MIMI_SECRET_TTS_VOICE`、`MIMI_SECRET_TTS_LANGUAGE` を設定します +3. I2S セクションで XVF3800 の入力ピンとスピーカー側の出力ピンを設定します +4. DAC やアンプの音がノイズになる場合は、`MIMI_VOICE_I2S_STD_SLOT_STYLE` をハードウェアのタイミングに合わせて設定します +5. 室内環境で誤検知が多い場合は、`MIMI_VOICE_VAD_START_FRAMES`、`MIMI_VOICE_VAD_MIN_FRAMES`、`MIMI_VOICE_STT_COOLDOWN_MS` を調整します +6. TTS 音声が長すぎる場合は、`MIMI_VOICE_TTS_MAX_SECONDS`、`MIMI_VOICE_TTS_CHARS_PER_SEC`、`MIMI_VOICE_TTS_MAX_CHARS` を調整します -### CLIコマンド(UART/COMポート経由) +現在のファームウェアには、すでに完全な音声チャネルが含まれています: -シリアル接続で設定やデバッグができます。**設定コマンド**により再コンパイル不要で設定変更可能 — USBケーブルを挿すだけ。 +- 入力方向: mic PCM -> VAD -> STT -> message bus +- 出力方向: agent text -> TTS -> playback -**実行時設定**(NVSに保存、ビルド時のデフォルト値をオーバーライド): +## 書き込みとモニタ -``` -mimi> wifi_set MySSID MyPassword # WiFiネットワークを変更 -mimi> set_tg_token 123456:ABC... # Telegram Botトークンを変更 -mimi> set_api_key sk-ant-api03-... # APIキーを変更(AnthropicまたはOpenAI) -mimi> set_model_provider openai # プロバイダーを切替(anthropic|openai) -mimi> set_model gpt-4o # LLMモデルを変更 -mimi> set_proxy 127.0.0.1 7897 # HTTPプロキシを設定 -mimi> clear_proxy # プロキシを削除 -mimi> set_search_key BSA... # Brave Search APIキーを設定 -mimi> set_tavily_key tvly-... # Tavily APIキーを設定(優先) -mimi> config_show # 全設定を表示(マスク付き) -mimi> config_reset # NVSをクリア、ビルド時デフォルトに戻す -``` - -**デバッグ・メンテナンス:** +`main/mimi_secrets.h` を変更した後は、クリーンな状態から再ビルドしてください: +```bash +idf.py fullclean +idf.py build ``` -mimi> wifi_status # 接続されていますか? -mimi> memory_read # ボットが何を覚えているか確認 -mimi> memory_write "内容" # MEMORY.mdに書き込み -mimi> heap_info # 空きRAMはどれくらい? -mimi> session_list # 全チャットセッションを一覧 -mimi> session_clear 12345 # 会話を削除 -mimi> heartbeat_trigger # ハートビートチェックを手動トリガー -mimi> cron_start # cronスケジューラを今すぐ開始 -mimi> restart # 再起動 -``` - -### USB(JTAG)vs UART:どのポートで何をするか - -ほとんどの ESP32-S3 開発ボードには **2つの USB-C ポート**があります: -| ポート | 用途 | -|--------|------| -| **USB**(JTAG) | `idf.py flash`、JTAGデバッグ | -| **COM**(UART) | **REPL CLI**、シリアルコンソール | - -> **REPLにはUART(COM)ポートが必要です。** USB(JTAG)ポートは対話的なREPL入力をサポートしません。 - -
-ポート詳細と推奨ワークフロー - -| ポート | ラベル | プロトコル | -|--------|--------|------------| -| **USB** | USB / JTAG | ネイティブ USB Serial/JTAG | -| **COM** | UART / COM | 外部 UART ブリッジ(CP2102/CH340) | - -ESP-IDFコンソールはデフォルトでUART出力に設定されています(`CONFIG_ESP_CONSOLE_UART_DEFAULT=y`)。 - -**両方のポートを同時に接続している場合:** - -- USB(JTAG)ポートはフラッシュ/ダウンロードを処理し、補助シリアル出力を提供 -- UART(COM)ポートはREPL用のメインインタラクティブコンソールを提供 -- macOS では両ポートとも `/dev/cu.usbmodem*` または `/dev/cu.usbserial-*` として表示 — `ls /dev/cu.usb*` で確認 -- Linux では USB(JTAG)は通常 `/dev/ttyACM0`、UART は通常 `/dev/ttyUSB0` - -**推奨ワークフロー:** +シリアルポートを確認します: ```bash -# USB(JTAG)ポートでフラッシュ -idf.py -p /dev/cu.usbmodem11401 flash - -# UART(COM)ポートでREPLを開く -idf.py -p /dev/cu.usbserial-110 monitor -# または任意のシリアルターミナル:screen、minicom、PuTTY(ボーレート 115200) +ls /dev/cu.usb* # macOS +ls /dev/ttyACM* # Linux ``` -
- -## メモリ - -MimiClawはすべてのデータをプレーンテキストファイルとして保存します。直接読み取り・編集可能です: +書き込みとモニタ: -| ファイル | 説明 | -|----------|------| -| `SOUL.md` | ボットの性格 — 編集して振る舞いを変更 | -| `USER.md` | あなたの情報 — 名前、好み、言語 | -| `MEMORY.md` | 長期記憶 — ボットが常に覚えておくべきこと | -| `HEARTBEAT.md` | タスクリスト — ボットが定期的にチェックして自律的に実行 | -| `cron.json` | スケジュールジョブ — AIが作成した定期・単発タスク | -| `2026-02-05.md` | 日次メモ — 今日あったこと | -| `tg_12345.jsonl` | チャット履歴 — ボットとの会話 | - -## ツール +```bash +idf.py -p PORT flash monitor +``` -MimiClawはAnthropicとOpenAI両方のツール呼び出しをサポート — LLMは会話中にツールを呼び出し、タスクが完了するまでループします(ReActパターン)。 +`PORT` は実際のデバイスパスに置き換えてください。 + +## シリアル CLI + +シリアル CLI は、NVS に保存される実行時設定を最も素早く変更する方法です: + +```text +mimi> wifi_set MySSID MyPassword +mimi> set_tg_token 123456:ABC... +mimi> set_api_key your-llm-key +mimi> set_api_base https://your-compatible-endpoint/v1 +mimi> set_model_provider openai +mimi> set_model gpt-5.2 +mimi> set_proxy 127.0.0.1 7897 +mimi> clear_proxy +mimi> set_search_key BSA... +mimi> set_tavily_key tvly-... +mimi> config_show +mimi> config_reset +``` -| ツール | 説明 | -|--------|------| -| `web_search` | Tavily(優先)またはBraveでウェブ検索し、最新情報を取得 | -| `get_current_time` | HTTP経由で現在の日時を取得し、システムクロックを設定 | -| `cron_add` | 定期または単発タスクをスケジュール(LLMが自律的にcronジョブを作成) | -| `cron_list` | スケジュール済みのcronジョブを一覧表示 | -| `cron_remove` | IDでcronジョブを削除 | +メンテナンス用コマンド: + +```text +mimi> wifi_status +mimi> memory_read +mimi> memory_write "remember this" +mimi> heap_info +mimi> session_list +mimi> session_clear 12345 +mimi> heartbeat_trigger +mimi> cron_start +mimi> restart +``` -ウェブ検索を有効にするには、`mimi_secrets.h`で[Tavily APIキー](https://app.tavily.com/home)(優先、`MIMI_SECRET_TAVILY_KEY`)または[Brave Search APIキー](https://brave.com/search/api/)(`MIMI_SECRET_SEARCH_KEY`)を設定してください。 +## 互換 Provider モデル -## Cronタスク +`reSpeaker-claw` は公式の Anthropic と OpenAI のエンドポイントだけに限定されません。 -MimiClawにはcronスケジューラが内蔵されており、AIが自律的にタスクをスケジュールできます。LLMは`cron_add`ツールで定期ジョブ(「N秒ごと」)や単発ジョブ(「UNIXタイムスタンプで指定」)を作成できます。ジョブが発火すると、メッセージがエージェントループに注入され、AIが起動してタスクを処理・応答します。 +対応内容: -ジョブはSPIFFS(`cron.json`)に永続化され、再起動後も保持されます。活用例:日次サマリー、定期リマインダー、スケジュールチェック。 +- `set_model_provider anthropic` で選択する Anthropic 互換サービス +- `set_model_provider openai` で選択する OpenAI 互換サービス +- `set_api_base` で切り替える任意の API base -## ハートビート +これにより、agent loop を変更せずに、ローカルゲートウェイ、地域クラウド、統合 API プラットフォームを利用できます。 -ハートビートサービスはSPIFFS上の`HEARTBEAT.md`を定期的に読み取り、アクション可能なタスクがあるかチェックします。未完了の項目(空行、見出し、チェック済み`- [x]`以外)が見つかると、エージェントループにプロンプトを送信し、AIが自律的に処理します。 +## メモリと自動化 -これによりMimiClawはプロアクティブなアシスタントになります — `HEARTBEAT.md`にタスクを書き込めば、次のハートビートサイクルで自動的に拾い上げて実行します(デフォルト:30分ごと)。 +Agent は SPIFFS 上に状態をプレーンテキストファイルとして保存します: -## その他の機能 +| ファイル | 用途 | +|----------|------| +| `SOUL.md` | アシスタント人格 | +| `USER.md` | ユーザープロファイル | +| `MEMORY.md` | 長期記憶 | +| `HEARTBEAT.md` | 定期実行する自律タスクリスト | +| `cron.json` | スケジュールジョブ | +| `tg_12345.jsonl` | セッション履歴 | -- **WebSocketゲートウェイ** — ポート18789、LAN内から任意のWebSocketクライアントで接続 -- **OTAアップデート** — WiFi経由でファームウェア更新、USB不要 -- **デュアルコア** — ネットワークI/OとAI処理が別々のCPUコアで動作 -- **HTTPプロキシ** — CONNECTトンネル対応、制限付きネットワークに対応 -- **マルチプロバイダー** — Anthropic (Claude) と OpenAI (GPT) の両方をサポート、実行時に切り替え可能 -- **Cronスケジューラ** — AIが定期・単発タスクを自律的にスケジュール、再起動後も永続化 -- **ハートビート** — タスクファイルを定期チェックし、AIを自律的に駆動 -- **ツール呼び出し** — ReActエージェントループ、両プロバイダーでツール呼び出し対応 +組み込みの自動化機能: -## 開発者向け +- `cron_add`、`cron_list`、`cron_remove` +- heartbeat 駆動の能動的タスク処理 +- ReAct loop におけるツール呼び出し +- 再起動後も保持されるローカル状態 -技術的な詳細は`docs/`フォルダにあります: +## ツール -- **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)** — システム設計、モジュール構成、タスクレイアウト、メモリバジェット、プロトコル、Flashパーティション -- **[docs/TODO.md](docs/TODO.md)** — 機能ギャップとロードマップ -- **[docs/im-integration/](docs/im-integration/README.md)** — IMチャネル統合ガイド(Feishuなど) +組み込みツール: -## 貢献 +- `web_search` +- `get_current_time` +- `cron_add` +- `cron_list` +- `cron_remove` +- Agent ランタイムが使う SPIFFS ファイル操作ツール -Issue や Pull Request を作成する前に、**[CONTRIBUTING.md](CONTRIBUTING.md)** をご確認ください。 +Web 検索を有効にするには、次のいずれかを設定します: -## コントリビューター +- `MIMI_SECRET_TAVILY_KEY` +- `MIMI_SECRET_SEARCH_KEY` -MimiClaw に貢献してくれた皆さんに感謝します。 +## 謝辞 - - MimiClaw contributors - +本プロジェクトは元の [mimiclaw](https://github.com/memovai/mimiclaw) を基盤としています。reSpeaker-claw は、その組み込み agent 基盤を ReSpeaker XVF3800 の音声ハードウェア向けに適応し、STT / TTS パイプラインを拡張しつつ、マルチチャネル agent アーキテクチャを継承しています。 ## ライセンス MIT - -## 謝辞 - -[OpenClaw](https://github.com/openclaw/openclaw)と[Nanobot](https://github.com/HKUDS/nanobot)にインスパイアされました。MimiClawはコアAIエージェントアーキテクチャを組み込みハードウェア向けに再実装しました — Linuxなし、サーバーなし、$5のチップだけ。 - -## Star History - - - - - - Star History Chart - - diff --git a/assets/banner.png b/assets/banner.png deleted file mode 100644 index c3cc42552cc482e6619dcf64450dfd2a088a30a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51506 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�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 J(K~YPe*my9Q|=Qo1Fj4y&%#dVVR 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

!2Jiigs{X8>sr_hu@+{`nCxiI5cuTBcn)6VtR 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 E*_A+xzW@LL diff --git a/assets/mimiclaw.png b/assets/mimiclaw.png deleted file mode 100644 index e22246e7a2212aac506ec40e149afd902d11f4e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 196974 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 z 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ⅆ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 pYv$vpZ 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