基于 ESP32-S3 的四博 AI 双目智能音箱工程方案:四路触摸、IMU 姿态识别、震动反馈、双目屏状态机与语音克隆知识库接入

基于 ESP32-S3 的四博 AI 双目智能音箱工程方案:四路触摸、IMU 姿态识别、震动反馈、双目屏状态机与语音克隆知识库接入

1. 方案概述

本文设计一套基于 ESP32-S3 的四博 AI 双目智能音箱工程方案。

系统目标是实现:

复制代码
复制代码
1. 双目光屏表情显示
2. 四路触控输入
3. 震动马达反馈
4. 三轴姿态识别
5. I2S 音频采集与播放
6. Wi-Fi / BluFi 配网
7. 云端大模型对话
8. 语音克隆 TTS
9. 专属知识库 RAG
10. 小程序参数配置

推荐硬件平台:

复制代码
复制代码
主控:ESP32-S3 / ESPS3-32
屏幕:0.71 寸双目屏 / 1.28 寸双目屏
输入:四路触控感应
姿态:三轴加速度传感器
反馈:震动马达
音频:I2S Mic + I2S Codec / DAC + 功放
联网:2.4G Wi-Fi,可扩展 4G
应用:AI 音箱、AI 桌宠、AI 台灯、AI 早教机

四博模组选型资料中,ESPS3-32 系列属于 ESP32-S3 模组,包含 N4、N8、N8R2、N16R2、N16R8 等子型号,并兼容 ESP32-S3-WROOM-1 系列,适合作为 AI 音视频类产品的主控平台。


2. 系统架构

2.1 硬件框图

复制代码
复制代码
                  ┌────────────────────────┐
                  │       云端 AI 服务       │
                  │ ASR / LLM / RAG / TTS   │
                  │ Voice Clone / MCP       │
                  └───────────▲────────────┘
                              │ WebSocket / HTTPS
                              │
┌─────────────┐       ┌───────┴────────┐       ┌──────────────┐
│ 4路触控输入  │──────▶│                │──────▶│ 双目光屏       │
└─────────────┘       │                │ SPI   │ 0.71 / 1.28   │
┌─────────────┐ I2C   │    ESP32-S3     │       └──────────────┘
│ 三轴IMU      │──────▶│                │       ┌──────────────┐
└─────────────┘       │                │──────▶│ 震动马达       │
┌─────────────┐ I2S   │                │ GPIO  └──────────────┘
│ 麦克风       │──────▶│                │       ┌──────────────┐
└─────────────┘       └───────┬────────┘──────▶│ 功放 / 喇叭    │
                              │ I2S           └──────────────┘
                              │
                        Wi-Fi / BluFi / 4G

2.2 软件模块

复制代码
复制代码
main
├── app_main.c
├── board
│   ├── board_config.h
│   └── pin_config.h
├── core
│   ├── ai_event.c
│   ├── ai_event.h
│   ├── ai_action.c
│   └── ai_action.h
├── eye
│   ├── eye_display.c
│   ├── eye_display.h
│   ├── eye_anim.c
│   └── eye_assets.h
├── touch
│   ├── touch_input.c
│   └── touch_input.h
├── imu
│   ├── imu_sensor.c
│   ├── imu_sensor.h
│   ├── gesture_detect.c
│   └── gesture_detect.h
├── motor
│   ├── vibration.c
│   └── vibration.h
├── audio
│   ├── audio_recorder.c
│   ├── audio_player.c
│   └── tts_stream.c
├── cloud
│   ├── ai_ws_client.c
│   ├── ai_protocol.c
│   └── ai_protocol.h
└── storage
    ├── ai_config.c
    └── ai_config.h

3. ESP-IDF 工程配置

复制代码
复制代码
idf.py set-target esp32s3
idf.py menuconfig

建议配置:

复制代码
复制代码
CONFIG_SPIRAM=y
CONFIG_FREERTOS_HZ=1000
CONFIG_ESP_WIFI_ENABLED=y
CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y
CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384
CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=4096
CONFIG_LWIP_TCP_SND_BUF_DEFAULT=16384
CONFIG_LWIP_TCP_WND_DEFAULT=32768

CMakeLists.txt 示例:

复制代码
复制代码
idf_component_register(
    SRCS
        "app_main.c"
        "core/ai_event.c"
        "core/ai_action.c"
        "eye/eye_display.c"
        "eye/eye_anim.c"
        "touch/touch_input.c"
        "imu/imu_sensor.c"
        "imu/gesture_detect.c"
        "motor/vibration.c"
        "audio/audio_player.c"
        "cloud/ai_protocol.c"
        "storage/ai_config.c"
    INCLUDE_DIRS
        "."
        "core"
        "eye"
        "touch"
        "imu"
        "motor"
        "audio"
        "cloud"
        "storage"
    REQUIRES
        driver
        esp_timer
        esp_wifi
        nvs_flash
        json
)

4. 全局事件总线

AI 双目音箱不建议让触摸、IMU、音频、云端回调直接互相调用。更好的方式是:所有输入先变成事件,统一进入主状态机。

4.1 事件定义

复制代码
复制代码
#pragma once

#include "esp_err.h"

typedef enum {
    AI_EVT_NONE = 0,

    AI_EVT_WAKEUP,
    AI_EVT_LISTENING,
    AI_EVT_THINKING,
    AI_EVT_TALKING,
    AI_EVT_SLEEP,

    AI_EVT_TOUCH_1,
    AI_EVT_TOUCH_2,
    AI_EVT_TOUCH_3,
    AI_EVT_TOUCH_4,

    AI_EVT_GESTURE_SHAKE,
    AI_EVT_GESTURE_LEFT,
    AI_EVT_GESTURE_RIGHT,
    AI_EVT_GESTURE_PICKUP,
    AI_EVT_GESTURE_FLIP,

    AI_EVT_CLOUD_ACTION,
    AI_EVT_ERROR,
} ai_event_id_t;

typedef struct {
    ai_event_id_t id;
    char text[256];
    char action_json[512];
} ai_event_msg_t;

esp_err_t ai_event_init(void);
esp_err_t ai_event_post(ai_event_id_t id, const char *text);
esp_err_t ai_event_post_action(const char *json);

4.2 事件队列实现

复制代码
复制代码
#include "ai_event.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include <string.h>

static QueueHandle_t s_ai_event_queue;

esp_err_t ai_event_init(void)
{
    s_ai_event_queue = xQueueCreate(16, sizeof(ai_event_msg_t));
    return s_ai_event_queue ? ESP_OK : ESP_ERR_NO_MEM;
}

QueueHandle_t ai_event_get_queue(void)
{
    return s_ai_event_queue;
}

esp_err_t ai_event_post(ai_event_id_t id, const char *text)
{
    if (!s_ai_event_queue) {
        return ESP_ERR_INVALID_STATE;
    }

    ai_event_msg_t msg = {
        .id = id,
    };

    if (text) {
        snprintf(msg.text, sizeof(msg.text), "%s", text);
    }

    if (xQueueSend(s_ai_event_queue, &msg, pdMS_TO_TICKS(50)) != pdTRUE) {
        return ESP_FAIL;
    }

    return ESP_OK;
}

esp_err_t ai_event_post_action(const char *json)
{
    if (!s_ai_event_queue || !json) {
        return ESP_ERR_INVALID_ARG;
    }

    ai_event_msg_t msg = {
        .id = AI_EVT_CLOUD_ACTION,
    };

    snprintf(msg.action_json, sizeof(msg.action_json), "%s", json);

    if (xQueueSend(s_ai_event_queue, &msg, pdMS_TO_TICKS(50)) != pdTRUE) {
        return ESP_FAIL;
    }

    return ESP_OK;
}

5. 四路触控驱动

5.1 GPIO 定义

实际 GPIO 需要按 PCB 修改:

复制代码
复制代码
#define TOUCH_1_GPIO  3
#define TOUCH_2_GPIO  4
#define TOUCH_3_GPIO  5
#define TOUCH_4_GPIO  6

5.2 触控初始化

复制代码
复制代码
#include "driver/gpio.h"
#include "esp_timer.h"
#include "ai_event.h"

#define TOUCH_DEBOUNCE_MS  180

static int64_t s_last_touch_ms[4];

static int touch_gpio_to_index(int gpio)
{
    switch (gpio) {
    case TOUCH_1_GPIO: return 0;
    case TOUCH_2_GPIO: return 1;
    case TOUCH_3_GPIO: return 2;
    case TOUCH_4_GPIO: return 3;
    default: return -1;
    }
}

static ai_event_id_t touch_gpio_to_event(int gpio)
{
    switch (gpio) {
    case TOUCH_1_GPIO: return AI_EVT_TOUCH_1;
    case TOUCH_2_GPIO: return AI_EVT_TOUCH_2;
    case TOUCH_3_GPIO: return AI_EVT_TOUCH_3;
    case TOUCH_4_GPIO: return AI_EVT_TOUCH_4;
    default: return AI_EVT_NONE;
    }
}

static bool touch_check_debounce(int index)
{
    int64_t now_ms = esp_timer_get_time() / 1000;

    if (now_ms - s_last_touch_ms[index] < TOUCH_DEBOUNCE_MS) {
        return false;
    }

    s_last_touch_ms[index] = now_ms;
    return true;
}

static void IRAM_ATTR touch_isr_handler(void *arg)
{
    int gpio = (int)arg;
    int index = touch_gpio_to_index(gpio);

    if (index < 0) {
        return;
    }

    int64_t now_ms = esp_timer_get_time() / 1000;
    if (now_ms - s_last_touch_ms[index] < TOUCH_DEBOUNCE_MS) {
        return;
    }

    s_last_touch_ms[index] = now_ms;

    ai_event_msg_t msg = {
        .id = touch_gpio_to_event(gpio),
    };

    BaseType_t high_task_wakeup = pdFALSE;
    xQueueSendFromISR(ai_event_get_queue(), &msg, &high_task_wakeup);

    if (high_task_wakeup) {
        portYIELD_FROM_ISR();
    }
}

void touch_input_init(void)
{
    uint64_t mask =
        (1ULL << TOUCH_1_GPIO) |
        (1ULL << TOUCH_2_GPIO) |
        (1ULL << TOUCH_3_GPIO) |
        (1ULL << TOUCH_4_GPIO);

    gpio_config_t cfg = {
        .pin_bit_mask = mask,
        .mode = GPIO_MODE_INPUT,
        .pull_up_en = GPIO_PULLUP_ENABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = GPIO_INTR_NEGEDGE,
    };

    gpio_config(&cfg);
    gpio_install_isr_service(0);

    gpio_isr_handler_add(TOUCH_1_GPIO, touch_isr_handler, (void *)TOUCH_1_GPIO);
    gpio_isr_handler_add(TOUCH_2_GPIO, touch_isr_handler, (void *)TOUCH_2_GPIO);
    gpio_isr_handler_add(TOUCH_3_GPIO, touch_isr_handler, (void *)TOUCH_3_GPIO);
    gpio_isr_handler_add(TOUCH_4_GPIO, touch_isr_handler, (void *)TOUCH_4_GPIO);
}

6. 震动马达驱动

6.1 GPIO 版本

复制代码
复制代码
#pragma once

#include <stdint.h>

typedef enum {
    VIB_MODE_SHORT = 0,
    VIB_MODE_DOUBLE,
    VIB_MODE_LONG,
} vib_mode_t;

void vibration_init(void);
void vibration_play(vib_mode_t mode);
复制代码
复制代码
#include "vibration.h"
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#define VIBRATION_GPIO  40

static void vibration_set(bool on)
{
    gpio_set_level(VIBRATION_GPIO, on ? 1 : 0);
}

static void vibration_pulse(uint32_t ms)
{
    vibration_set(true);
    vTaskDelay(pdMS_TO_TICKS(ms));
    vibration_set(false);
}

void vibration_init(void)
{
    gpio_config_t cfg = {
        .pin_bit_mask = 1ULL << VIBRATION_GPIO,
        .mode = GPIO_MODE_OUTPUT,
        .pull_up_en = GPIO_PULLUP_DISABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = GPIO_INTR_DISABLE,
    };

    gpio_config(&cfg);
    vibration_set(false);
}

void vibration_play(vib_mode_t mode)
{
    switch (mode) {
    case VIB_MODE_SHORT:
        vibration_pulse(120);
        break;

    case VIB_MODE_DOUBLE:
        vibration_pulse(80);
        vTaskDelay(pdMS_TO_TICKS(100));
        vibration_pulse(80);
        break;

    case VIB_MODE_LONG:
        vibration_pulse(500);
        break;

    default:
        break;
    }
}

6.2 使用建议

复制代码
复制代码
触摸确认:VIB_MODE_SHORT
唤醒成功:VIB_MODE_DOUBLE
进入配网:VIB_MODE_LONG
错误提醒:VIB_MODE_DOUBLE
AI 回复开始:不建议震动,避免打断听感

7. IMU 姿态识别模块

7.1 数据结构

复制代码
复制代码
#pragma once

#include "esp_err.h"

typedef struct {
    float ax;
    float ay;
    float az;
    float gx;
    float gy;
    float gz;
} imu_data_t;

esp_err_t imu_sensor_init(void);
esp_err_t imu_sensor_read(imu_data_t *out);

7.2 姿态枚举

复制代码
复制代码
typedef enum {
    GESTURE_NONE = 0,
    GESTURE_SHAKE,
    GESTURE_TILT_LEFT,
    GESTURE_TILT_RIGHT,
    GESTURE_PICKUP,
    GESTURE_FLIP,
} gesture_type_t;

7.3 姿态识别算法

复制代码
复制代码
#include <math.h>
#include "imu_sensor.h"

gesture_type_t gesture_detect(const imu_data_t *imu)
{
    if (!imu) {
        return GESTURE_NONE;
    }

    float ax = imu->ax;
    float ay = imu->ay;
    float az = imu->az;

    float abs_x = fabsf(ax);
    float abs_y = fabsf(ay);
    float abs_z = fabsf(az);

    // 摇晃:瞬时加速度明显增大
    if (abs_x > 1.8f || abs_y > 1.8f || abs_z > 2.2f) {
        return GESTURE_SHAKE;
    }

    // 左右倾斜
    if (ax > 0.75f) {
        return GESTURE_TILT_RIGHT;
    }

    if (ax < -0.75f) {
        return GESTURE_TILT_LEFT;
    }

    // 翻转
    if (az < -0.65f) {
        return GESTURE_FLIP;
    }

    // 拿起:Z 轴重力减小,同时 X/Y 出现一定变化
    if (abs_z < 0.4f && (abs_x > 0.3f || abs_y > 0.3f)) {
        return GESTURE_PICKUP;
    }

    return GESTURE_NONE;
}

7.4 IMU 任务

复制代码
复制代码
static void imu_task(void *arg)
{
    imu_data_t data;
    gesture_type_t last_gesture = GESTURE_NONE;
    int stable_count = 0;

    while (1) {
        if (imu_sensor_read(&data) == ESP_OK) {
            gesture_type_t g = gesture_detect(&data);

            if (g != GESTURE_NONE && g != last_gesture) {
                stable_count++;

                if (stable_count >= 2) {
                    switch (g) {
                    case GESTURE_SHAKE:
                        ai_event_post(AI_EVT_GESTURE_SHAKE, NULL);
                        break;

                    case GESTURE_TILT_LEFT:
                        ai_event_post(AI_EVT_GESTURE_LEFT, NULL);
                        break;

                    case GESTURE_TILT_RIGHT:
                        ai_event_post(AI_EVT_GESTURE_RIGHT, NULL);
                        break;

                    case GESTURE_PICKUP:
                        ai_event_post(AI_EVT_GESTURE_PICKUP, NULL);
                        break;

                    case GESTURE_FLIP:
                        ai_event_post(AI_EVT_GESTURE_FLIP, NULL);
                        break;

                    default:
                        break;
                    }

                    last_gesture = g;
                    stable_count = 0;
                }
            } else if (g == GESTURE_NONE) {
                last_gesture = GESTURE_NONE;
                stable_count = 0;
            }
        }

        vTaskDelay(pdMS_TO_TICKS(50));
    }
}

这里加入 stable_count 是为了避免姿态瞬间抖动导致误触发。


8. 双目屏状态机

8.1 表情状态

复制代码
复制代码
typedef enum {
    EYE_STATE_IDLE = 0,
    EYE_STATE_WAKEUP,
    EYE_STATE_LISTENING,
    EYE_STATE_THINKING,
    EYE_STATE_TALKING,
    EYE_STATE_SMILE,
    EYE_STATE_SURPRISE,
    EYE_STATE_SLEEP,
    EYE_STATE_ERROR,
} eye_state_t;

8.2 状态切换接口

复制代码
复制代码
void eye_set_state(eye_state_t state)
{
    switch (state) {
    case EYE_STATE_IDLE:
        eye_show_idle();
        break;

    case EYE_STATE_WAKEUP:
        eye_show_wakeup();
        break;

    case EYE_STATE_LISTENING:
        eye_show_listening();
        break;

    case EYE_STATE_THINKING:
        eye_show_thinking();
        break;

    case EYE_STATE_TALKING:
        eye_show_talking();
        break;

    case EYE_STATE_SMILE:
        eye_show_smile();
        break;

    case EYE_STATE_SURPRISE:
        eye_show_surprise();
        break;

    case EYE_STATE_SLEEP:
        eye_show_sleep();
        break;

    case EYE_STATE_ERROR:
        eye_show_error();
        break;

    default:
        eye_show_idle();
        break;
    }
}

8.3 音频驱动眼睛动画

在 TTS 播放时,可以根据音频能量驱动眼睛或嘴型动画:

复制代码
复制代码
void eye_update_by_audio_level(int level)
{
    if (level < 10) {
        eye_show_talking_frame(0);
    } else if (level < 30) {
        eye_show_talking_frame(1);
    } else if (level < 60) {
        eye_show_talking_frame(2);
    } else {
        eye_show_talking_frame(3);
    }
}

播放任务中调用:

复制代码
复制代码
void audio_play_callback(const int16_t *pcm, size_t samples)
{
    int64_t sum = 0;

    for (size_t i = 0; i < samples; i++) {
        sum += abs(pcm[i]);
    }

    int level = sum / samples / 256;
    eye_update_by_audio_level(level);
}

这样回答时双目动画会随语音变化,看起来更自然。


9. 云端协议设计

9.1 设备事件上报

复制代码
复制代码
{
  "type": "device_event",
  "device_id": "sibo_ai_eye_001",
  "event": "touch_head",
  "payload": {
    "touch_id": 1,
    "battery": 88,
    "gesture": "none"
  }
}

9.2 聊天请求

复制代码
复制代码
{
  "type": "chat_request",
  "device_id": "sibo_ai_eye_001",
  "user_id": "user_001",
  "kb_id": "child_learning_kb",
  "voice_id": "clone_mom_001",
  "text": "给我讲一个关于月亮的故事"
}

9.3 云端返回动作

复制代码
复制代码
{
  "type": "ai_action",
  "answer": "从前有一只小兔子,它每天晚上都会看月亮......",
  "tts_url": "https://server.com/tts/clone_mom_001_001.opus",
  "action": {
    "eye": "talking",
    "vibration": "none",
    "display": "story"
  }
}

10. cJSON 解析云端动作

复制代码
复制代码
#include "cJSON.h"
#include "eye_display.h"
#include "vibration.h"

static void apply_eye_action(const char *eye)
{
    if (!eye) {
        return;
    }

    if (strcmp(eye, "idle") == 0) {
        eye_set_state(EYE_STATE_IDLE);
    } else if (strcmp(eye, "smile") == 0) {
        eye_set_state(EYE_STATE_SMILE);
    } else if (strcmp(eye, "thinking") == 0) {
        eye_set_state(EYE_STATE_THINKING);
    } else if (strcmp(eye, "talking") == 0) {
        eye_set_state(EYE_STATE_TALKING);
    } else if (strcmp(eye, "sleep") == 0) {
        eye_set_state(EYE_STATE_SLEEP);
    } else {
        eye_set_state(EYE_STATE_IDLE);
    }
}

static void apply_vibration_action(const char *vib)
{
    if (!vib) {
        return;
    }

    if (strcmp(vib, "short") == 0) {
        vibration_play(VIB_MODE_SHORT);
    } else if (strcmp(vib, "double") == 0) {
        vibration_play(VIB_MODE_DOUBLE);
    } else if (strcmp(vib, "long") == 0) {
        vibration_play(VIB_MODE_LONG);
    }
}

void ai_apply_cloud_action(const char *json)
{
    cJSON *root = cJSON_Parse(json);
    if (!root) {
        return;
    }

    cJSON *action = cJSON_GetObjectItem(root, "action");
    if (!cJSON_IsObject(action)) {
        cJSON_Delete(root);
        return;
    }

    cJSON *eye = cJSON_GetObjectItem(action, "eye");
    cJSON *vib = cJSON_GetObjectItem(action, "vibration");

    if (cJSON_IsString(eye)) {
        apply_eye_action(eye->valuestring);
    }

    if (cJSON_IsString(vib)) {
        apply_vibration_action(vib->valuestring);
    }

    cJSON_Delete(root);
}

11. 小程序配置与 NVS 存储

四博小助手小程序可以配置设备绑定信息、知识库 ID、语音克隆 voice_id、儿童模式、音量等参数。

11.1 配置结构

复制代码
复制代码
typedef struct {
    char device_id[32];
    char user_id[32];
    char kb_id[64];
    char voice_id[64];
    char wake_word[32];
    uint8_t child_mode;
    uint8_t volume;
} ai_user_config_t;

11.2 保存配置

复制代码
复制代码
#include "nvs_flash.h"
#include "nvs.h"

esp_err_t ai_config_save(const ai_user_config_t *cfg)
{
    if (!cfg) {
        return ESP_ERR_INVALID_ARG;
    }

    nvs_handle_t handle;
    esp_err_t ret = nvs_open("ai_config", NVS_READWRITE, &handle);
    if (ret != ESP_OK) {
        return ret;
    }

    ret = nvs_set_blob(handle, "user_cfg", cfg, sizeof(ai_user_config_t));
    if (ret == ESP_OK) {
        ret = nvs_commit(handle);
    }

    nvs_close(handle);
    return ret;
}

11.3 读取配置

复制代码
复制代码
esp_err_t ai_config_load(ai_user_config_t *cfg)
{
    if (!cfg) {
        return ESP_ERR_INVALID_ARG;
    }

    memset(cfg, 0, sizeof(ai_user_config_t));

    nvs_handle_t handle;
    esp_err_t ret = nvs_open("ai_config", NVS_READONLY, &handle);
    if (ret != ESP_OK) {
        return ret;
    }

    size_t size = sizeof(ai_user_config_t);
    ret = nvs_get_blob(handle, "user_cfg", cfg, &size);

    nvs_close(handle);
    return ret;
}

12. 构造带知识库和语音克隆的请求

复制代码
复制代码
#include "cJSON.h"

void ai_send_chat_request(const char *text)
{
    if (!text) {
        return;
    }

    ai_user_config_t cfg;
    if (ai_config_load(&cfg) != ESP_OK) {
        snprintf(cfg.device_id, sizeof(cfg.device_id), "sibo_ai_eye_default");
        snprintf(cfg.kb_id, sizeof(cfg.kb_id), "default_kb");
        snprintf(cfg.voice_id, sizeof(cfg.voice_id), "default_voice");
    }

    cJSON *root = cJSON_CreateObject();
    cJSON_AddStringToObject(root, "type", "chat_request");
    cJSON_AddStringToObject(root, "device_id", cfg.device_id);
    cJSON_AddStringToObject(root, "user_id", cfg.user_id);
    cJSON_AddStringToObject(root, "kb_id", cfg.kb_id);
    cJSON_AddStringToObject(root, "voice_id", cfg.voice_id);
    cJSON_AddStringToObject(root, "text", text);

    char *json = cJSON_PrintUnformatted(root);
    if (json) {
        ai_ws_send(json);
        free(json);
    }

    cJSON_Delete(root);
}

设备端只需要维护 kb_idvoice_id,实际的知识库检索和语音克隆都在云端完成,这样 ESP32-S3 的计算压力更小,也更适合量产。


13. 主状态机

复制代码
复制代码
static void ai_main_task(void *arg)
{
    ai_event_msg_t msg;

    while (1) {
        if (xQueueReceive(ai_event_get_queue(), &msg, portMAX_DELAY) == pdTRUE) {
            switch (msg.id) {
            case AI_EVT_WAKEUP:
                eye_set_state(EYE_STATE_WAKEUP);
                vibration_play(VIB_MODE_SHORT);
                break;

            case AI_EVT_LISTENING:
                eye_set_state(EYE_STATE_LISTENING);
                break;

            case AI_EVT_THINKING:
                eye_set_state(EYE_STATE_THINKING);
                break;

            case AI_EVT_TALKING:
                eye_set_state(EYE_STATE_TALKING);
                break;

            case AI_EVT_TOUCH_1:
                eye_set_state(EYE_STATE_SMILE);
                vibration_play(VIB_MODE_SHORT);
                ai_send_device_event("touch_head");
                break;

            case AI_EVT_TOUCH_2:
                eye_set_state(EYE_STATE_SMILE);
                ai_send_device_event("touch_left");
                break;

            case AI_EVT_TOUCH_3:
                eye_set_state(EYE_STATE_SMILE);
                ai_send_device_event("touch_right");
                break;

            case AI_EVT_TOUCH_4:
                eye_set_state(EYE_STATE_THINKING);
                vibration_play(VIB_MODE_LONG);
                ai_send_device_event("touch_mode");
                break;

            case AI_EVT_GESTURE_SHAKE:
                eye_set_state(EYE_STATE_SURPRISE);
                vibration_play(VIB_MODE_DOUBLE);
                ai_send_device_event("gesture_shake");
                break;

            case AI_EVT_GESTURE_LEFT:
                ai_send_device_event("gesture_left");
                break;

            case AI_EVT_GESTURE_RIGHT:
                ai_send_device_event("gesture_right");
                break;

            case AI_EVT_GESTURE_PICKUP:
                eye_set_state(EYE_STATE_WAKEUP);
                vibration_play(VIB_MODE_SHORT);
                ai_send_device_event("gesture_pickup");
                break;

            case AI_EVT_GESTURE_FLIP:
                eye_set_state(EYE_STATE_SLEEP);
                ai_send_device_event("gesture_flip");
                break;

            case AI_EVT_CLOUD_ACTION:
                ai_apply_cloud_action(msg.action_json);
                break;

            case AI_EVT_ERROR:
                eye_set_state(EYE_STATE_ERROR);
                vibration_play(VIB_MODE_DOUBLE);
                break;

            default:
                break;
            }
        }
    }
}

14. app_main 初始化

复制代码
复制代码
void app_main(void)
{
    ESP_ERROR_CHECK(nvs_flash_init());

    ESP_ERROR_CHECK(ai_event_init());

    wifi_manager_init();
    blufi_config_init();

    eye_display_init();
    touch_input_init();
    vibration_init();
    imu_sensor_init();
    audio_player_init();
    ai_ws_client_init();

    xTaskCreate(ai_main_task, "ai_main_task", 8192, NULL, 5, NULL);
    xTaskCreate(imu_task, "imu_task", 4096, NULL, 4, NULL);

    ai_event_post(AI_EVT_WAKEUP, NULL);
}

15. 云端 FastAPI 示例

云端负责 ASR、LLM、RAG、TTS、语音克隆。下面给一个简化接口示例:

复制代码
复制代码
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class ChatRequest(BaseModel):
    type: str
    device_id: str
    user_id: str = ""
    kb_id: str = "default_kb"
    voice_id: str = "default_voice"
    text: str

def rag_search(kb_id: str, query: str) -> str:
    # 查询专属知识库
    return "这里是从知识库检索到的相关内容"

def call_llm(context: str, query: str) -> str:
    prompt = f"""
你是四博AI双目智能音箱中的陪伴助手。
请结合知识库内容回答用户问题。
知识库内容:{context}
用户问题:{query}
"""
    return "这是结合知识库生成的回答内容。"

def call_tts(text: str, voice_id: str) -> str:
    # 根据 voice_id 生成克隆音色
    return f"https://server.com/tts/{voice_id}/audio.opus"

@app.post("/api/v1/chat")
async def chat(req: ChatRequest):
    context = rag_search(req.kb_id, req.text)
    answer = call_llm(context, req.text)
    tts_url = call_tts(answer, req.voice_id)

    return {
        "type": "ai_action",
        "answer": answer,
        "tts_url": tts_url,
        "action": {
            "eye": "talking",
            "vibration": "none"
        }
    }

16. 产品化调优建议

16.1 触控调优

复制代码
复制代码
1. 四路触控必须做防抖
2. 长按、短按、双击需要分开识别
3. 触摸后 100ms 内给震动反馈
4. 外壳厚度会影响触摸灵敏度
5. 量产时需要做触摸阈值校准

16.2 姿态调优

复制代码
复制代码
1. IMU 建议 20Hz~50Hz 采样
2. 摇晃识别需要时间窗口
3. 倾斜识别需要滤波
4. 翻转休眠建议延时确认
5. 拿起唤醒需要避免运输误触发

16.3 双目屏调优

复制代码
复制代码
1. 待机动画低帧率,降低功耗
2. TTS 播放时根据音频能量驱动动画
3. 配网、联网、OTA、错误状态都要有表情提示
4. 0.71 和 1.28 寸屏幕需要分别适配素材比例
5. 眼睛动画资源建议放 Flash,运行帧缓冲放 PSRAM

16.4 音频调优

复制代码
复制代码
1. 本地提示音尽量短,减少阻塞
2. TTS 使用流式播放,避免等待完整下载
3. 播放端使用 ringbuffer 缓冲
4. 支持 Opus / MP3,根据云端能力选择
5. 唤醒、思考、回答、错误状态使用不同提示音

17. 总结

四博 AI 双目智能音箱方案的核心是把传统 AI 音箱升级为一个完整的多模态交互终端。

它的输入端包括:

复制代码
复制代码
语音
四路触控
三轴姿态
小程序配置

它的输出端包括:

复制代码
复制代码
TTS 语音
双目屏表情
震动反馈
本地提示音

它的云端能力包括:

复制代码
复制代码
大模型对话
专属知识库
语音克隆
智能体配置
MCP 扩展

工程上,ESP32-S3 负责本地事件采集、音频播放、双目屏显示、触控和 IMU 状态机;云端负责 LLM、RAG、TTS 和语音克隆;小程序负责配网、绑定、角色、知识库和音色管理。

一句话概括:

复制代码
复制代码
四博 AI 双目方案,是一套基于 ESP32-S3 的高性价比 AI 智能音箱平台,通过四路触控、震动马达、三轴姿态感应、0.71/1.28 双目光屏、四博小助手、语音克隆和专属知识库,把普通音箱升级为真正有陪伴感和交互感的 AI 硬件。
相关推荐
老鱼说AI1 小时前
现代 LangChain 开发指南:从 LCEL 原理到企业级 RAG 与 Agent 实战
java·开发语言·人工智能·深度学习·神经网络·算法·机器学习
百度Geek说1 小时前
Browser Use:为 Agent 构建 Runtime Harness
人工智能
用户4330514143811 小时前
流程控制与并行工作
人工智能
云天AI实战派1 小时前
ChatGPT/API 调用故障排查指南:Realtime 音频、智能体浏览器操作与 AI 编码代理全流程修复手册
人工智能·chatgpt·音视频
水上冰石1 小时前
怎么查看olama是否用到了显卡加速
人工智能·显卡
码点滴1 小时前
用自然语言指挥 K8s 集群:AI 运维 Agent 的架构原理与可运行原型
运维·人工智能·kubernetes
Wanderer X1 小时前
【LLM】PPO
人工智能
霍夫曼vx_helloworld73521 小时前
字符提取与字符识别
图像处理·人工智能·计算机视觉
Wang6071 小时前
浅尝claude code记忆系统
人工智能