基于 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 硬件。
相关推荐
奔跑中的小象4 小时前
统信UOS + 天数AI卡部署SGLang服务手册
人工智能·uos·sglang·天数智芯
DevSecOps选型指南4 小时前
中国版Mythos,为何是悬镜安全灵脉CodeAI?
人工智能·安全
Shockang4 小时前
LangGraph 状态机实战
人工智能
刹那芳华19924 小时前
循环神经网络的从零开始实现(RNN)
人工智能·rnn·深度学习
阿童木写作4 小时前
跨境图片翻译工具多合一,批量图片视频字幕翻译加智能抠图
人工智能·python·音视频·语音识别
科技之门4 小时前
AI3D从建模到贴图、绑骨和动画的完整流程怎么做?V2Fun完整工作流指南
人工智能·3d·贴图
宇宙第一小趴菜4 小时前
二、机器学习的应用领域和发展史
人工智能·机器学习
前端开发江鸟4 小时前
我写过 MCP Server,却一直以为 MCP 只有 Tool
人工智能
天国梦5 小时前
自习室智能化升级避坑指南:天学网AI智习室方案实测与选型建议
大数据·人工智能
阿里云云原生5 小时前
可用性从 99.9% 跃升至 99.995%:畅捷通如何用 AI 重塑运维底座?
运维·网络·人工智能