基于 ESP32-S3 的四博AI双目智能音箱方案:双目同显/异显、素材上传、触摸、G-sensor、舵机、Wi-Fi/4G/TWS音频扩展

基于 ESP32-S3 的四博AI双目智能音箱方案:双目同显/异显、素材上传、触摸、G-sensor、舵机、Wi-Fi/4G/TWS音频扩展

1. 项目背景

传统AI智能音箱主要围绕语音交互展开,典型链路是"唤醒词 → 语音采集 → 云端AI → TTS播放"。这种交互方式能完成问答、控制和内容播放,但缺少视觉反馈和动作反馈,用户感知比较单一。

四博AI双目智能音箱方案在传统AI音箱基础上增加了两块眼睛屏幕、四路触摸传感器、G-sensor姿态检测、震动马达、舵机动作和素材上传能力,使设备从"会说话的音箱"升级成"有表情、有动作、有感知、有反馈的AI陪伴终端"。

该方案基于 ESP32-S3 平台,适合客户进行二次开发,可接入客户自己的后端、自己的小程序、自己的AI服务和自己的素材系统。四博资料中 ESPS3-32 / ESPS3-32E 属于 ESP32-S3 系列,面向音视频/AI市场;AI开发宝典中也已有 RoPet_ESPS3_AI_EYE 双目工程和相关硬件说明,可作为该方案的开发基础。


2. 方案功能定义

本方案建议实现以下能力:

复制代码
复制代码
1. 基于 ESP32-S3 / ESPS3-32E 主控,方便客户二次开发
2. 支持客户接入自己的后端、自己的小程序、自己的AI服务
3. 支持双目同显和双目异显
4. 支持客户上传图片、动图、MJPEG视频、QOI素材
5. 支持四路触摸传感器
6. 支持 G-sensor 姿态传感器
7. 支持震动马达反馈
8. 支持舵机驱动头部、尾部、耳朵等结构
9. 联网支持 Wi-Fi、4G、BLE配网
10. 支持连接 TWS 耳机和蓝牙音箱
11. 支持AI对话、TTS播放、表情联动和动作联动
12. 支持OTA升级和素材远程更新

需要说明的是:ESP32-S3 主要支持 Wi-Fi 和 BLE,不适合直接承担传统 Bluetooth Classic A2DP/HFP 音频连接。若产品要求稳定连接 TWS 耳机或蓝牙音箱,建议增加外置蓝牙音频SoC,由 ESP32-S3 通过 UART/I2S 控制外置蓝牙音频模块。


3. 系统总体架构

复制代码
复制代码
+------------------------------------------------------+
|              四博AI双目智能音箱系统                  |
+------------------------------------------------------+
| 主控层                                                |
|  └── ESPS3-32 / ESPS3-32E / ESP32-S3                  |
|                                                      |
| AI通信层                                              |
|  ├── Wi-Fi:连接客户后端 / 小智AI / Coze / 私有LLM     |
|  ├── 4G:通过4G模组进行移动联网                       |
|  └── BLE:配网、绑定、小程序近场控制                  |
|                                                      |
| 显示层                                                |
|  ├── 左眼屏幕                                         |
|  ├── 右眼屏幕                                         |
|  ├── 双目同显                                         |
|  ├── 双目异显                                         |
|  └── 图片 / 动图 / MJPEG / QOI素材播放                 |
|                                                      |
| 交互层                                                |
|  ├── 四路触摸传感器                                   |
|  ├── G-sensor姿态传感器                               |
|  ├── 震动马达                                         |
|  └── 舵机:头部 / 尾部 / 耳朵                          |
|                                                      |
| 音频层                                                |
|  ├── MIC语音采集                                      |
|  ├── 本地喇叭播放                                     |
|  ├── TTS语音播放                                      |
|  └── 外置BT Audio SoC:TWS耳机 / 蓝牙音箱              |
+------------------------------------------------------+

4. 推荐工程目录

复制代码
复制代码
sibo_ai_dual_eye_speaker/
├── CMakeLists.txt
├── sdkconfig.defaults
├── partitions.csv
└── main/
    ├── app_main.c
    ├── app_config.h
    ├── board_pins.h
    ├── system_state.c
    ├── system_state.h
    ├── eye_display.c
    ├── eye_display.h
    ├── asset_manager.c
    ├── asset_manager.h
    ├── touch_manager.c
    ├── touch_manager.h
    ├── gsensor_manager.c
    ├── gsensor_manager.h
    ├── motor_manager.c
    ├── motor_manager.h
    ├── servo_manager.c
    ├── servo_manager.h
    ├── network_manager.c
    ├── network_manager.h
    ├── audio_route.c
    ├── audio_route.h
    ├── ai_client.c
    ├── ai_client.h
    ├── app_protocol.c
    └── app_protocol.h

5. 分区表设计

双目方案需要存放表情图、动画帧、短视频素材、提示音和客户自定义资源,因此建议使用 OTA + FATFS / LittleFS 分区。

复制代码
复制代码
# Name,     Type, SubType, Offset,   Size,      Flags
nvs,        data, nvs,     0x9000,   0x4000,
otadata,    data, ota,     0xd000,   0x2000,
phy_init,   data, phy,     0xf000,   0x1000,
ota_0,      app,  ota_0,   0x20000,  0x400000,
ota_1,      app,  ota_1,   ,         0x400000,
assets,     data, fat,     ,         0x600000,
config,     data, nvs,     ,         0x10000,

素材目录建议:

复制代码
复制代码
/assets/
├── eyes/
│   ├── idle_left.qoi
│   ├── idle_right.qoi
│   ├── happy_left.qoi
│   ├── happy_right.qoi
│   ├── blink_left.qoi
│   ├── blink_right.qoi
│   ├── thinking_left.qoi
│   ├── thinking_right.qoi
│   ├── speaking_left.qoi
│   └── speaking_right.qoi
├── video/
│   ├── boot.mjpeg
│   ├── thinking.mjpeg
│   └── speaking.mjpeg
├── audio/
│   ├── wake.wav
│   ├── hello.wav
│   └── error.wav
└── manifest.json

6. board_pins.h 引脚定义

实际引脚需按客户PCB调整,下面只作为工程模板:

复制代码
复制代码
#pragma once

/* 双目屏幕 SPI 总线 */
#define PIN_LCD_SPI_MOSI        11
#define PIN_LCD_SPI_CLK         12
#define PIN_LCD_LEFT_CS         13
#define PIN_LCD_RIGHT_CS        14
#define PIN_LCD_DC              15
#define PIN_LCD_RST             16
#define PIN_LCD_BL              17

/* I2S音频 */
#define PIN_I2S_BCLK            4
#define PIN_I2S_WS              5
#define PIN_I2S_DOUT            6
#define PIN_I2S_DIN             7
#define PIN_AMP_EN              8

/* 四路触摸 */
#define PIN_TOUCH_0             1
#define PIN_TOUCH_1             2
#define PIN_TOUCH_2             3
#define PIN_TOUCH_3             9

/* G-sensor I2C */
#define PIN_I2C_SDA             41
#define PIN_I2C_SCL             42

/* 马达和舵机 */
#define PIN_MOTOR               10
#define PIN_SERVO_HEAD          18
#define PIN_SERVO_TAIL          19

/* 外置蓝牙音频模块 */
#define PIN_BT_AUDIO_TX         43
#define PIN_BT_AUDIO_RX         44
#define PIN_BT_AUDIO_STATE      45

7. 双目显示状态机

双目显示建议抽象成"显示模式 + 表情状态 + 左右眼素材路径"。

复制代码
复制代码
#pragma once

#include <stdbool.h>

typedef enum {
    EYE_MODE_SYNC = 0,      // 双目同显
    EYE_MODE_ASYNC,         // 双目异显
    EYE_MODE_VIDEO,         // 双目视频播放
    EYE_MODE_CUSTOM         // 客户自定义素材
} eye_mode_t;

typedef enum {
    EYE_EXPR_IDLE = 0,
    EYE_EXPR_WAKEUP,
    EYE_EXPR_HAPPY,
    EYE_EXPR_BLINK,
    EYE_EXPR_THINKING,
    EYE_EXPR_SPEAKING,
    EYE_EXPR_SLEEP,
    EYE_EXPR_ERROR,
    EYE_EXPR_CUSTOM
} eye_expr_t;

typedef struct {
    eye_mode_t mode;
    eye_expr_t left_expr;
    eye_expr_t right_expr;
    char left_asset[128];
    char right_asset[128];
    int fps;
    bool loop;
} eye_display_ctx_t;

void eye_display_init(void);
void eye_display_show_sync(eye_expr_t expr);
void eye_display_show_async(eye_expr_t left, eye_expr_t right);
void eye_display_play_assets(const char *left_path, const char *right_path, int fps, bool loop);
void eye_display_stop(void);

实现示例:

复制代码
复制代码
#include <stdio.h>
#include <string.h>
#include "eye_display.h"

static eye_display_ctx_t s_eye = {
    .mode = EYE_MODE_SYNC,
    .left_expr = EYE_EXPR_IDLE,
    .right_expr = EYE_EXPR_IDLE,
    .fps = 15,
    .loop = true,
};

static const char *expr_to_asset(eye_expr_t expr, bool left)
{
    switch (expr) {
    case EYE_EXPR_WAKEUP:
        return left ? "/assets/eyes/wakeup_left.qoi" : "/assets/eyes/wakeup_right.qoi";

    case EYE_EXPR_HAPPY:
        return left ? "/assets/eyes/happy_left.qoi" : "/assets/eyes/happy_right.qoi";

    case EYE_EXPR_BLINK:
        return left ? "/assets/eyes/blink_left.qoi" : "/assets/eyes/blink_right.qoi";

    case EYE_EXPR_THINKING:
        return left ? "/assets/eyes/thinking_left.qoi" : "/assets/eyes/thinking_right.qoi";

    case EYE_EXPR_SPEAKING:
        return left ? "/assets/eyes/speaking_left.qoi" : "/assets/eyes/speaking_right.qoi";

    case EYE_EXPR_SLEEP:
        return left ? "/assets/eyes/sleep_left.qoi" : "/assets/eyes/sleep_right.qoi";

    case EYE_EXPR_ERROR:
        return left ? "/assets/eyes/error_left.qoi" : "/assets/eyes/error_right.qoi";

    case EYE_EXPR_IDLE:
    default:
        return left ? "/assets/eyes/idle_left.qoi" : "/assets/eyes/idle_right.qoi";
    }
}

void eye_display_init(void)
{
    printf("[eye] init left/right lcd\n");

    /*
     * 实际项目:
     * lcd_bus_init();
     * lcd_left_panel_init();
     * lcd_right_panel_init();
     * lvgl_port_init();
     */
}

void eye_display_show_sync(eye_expr_t expr)
{
    s_eye.mode = EYE_MODE_SYNC;
    s_eye.left_expr = expr;
    s_eye.right_expr = expr;

    const char *left = expr_to_asset(expr, true);
    const char *right = expr_to_asset(expr, false);

    printf("[eye] sync expr=%d L=%s R=%s\n", expr, left, right);

    /*
     * image_decode_and_draw_left(left);
     * image_decode_and_draw_right(right);
     */
}

void eye_display_show_async(eye_expr_t left, eye_expr_t right)
{
    s_eye.mode = EYE_MODE_ASYNC;
    s_eye.left_expr = left;
    s_eye.right_expr = right;

    const char *left_asset = expr_to_asset(left, true);
    const char *right_asset = expr_to_asset(right, false);

    printf("[eye] async L=%s R=%s\n", left_asset, right_asset);

    /*
     * image_decode_and_draw_left(left_asset);
     * image_decode_and_draw_right(right_asset);
     */
}

void eye_display_play_assets(const char *left_path, const char *right_path, int fps, bool loop)
{
    if (!left_path || !right_path) {
        return;
    }

    s_eye.mode = EYE_MODE_CUSTOM;
    s_eye.fps = fps;
    s_eye.loop = loop;

    strncpy(s_eye.left_asset, left_path, sizeof(s_eye.left_asset) - 1);
    strncpy(s_eye.right_asset, right_path, sizeof(s_eye.right_asset) - 1);

    printf("[eye] play custom L=%s R=%s fps=%d loop=%d\n",
           s_eye.left_asset,
           s_eye.right_asset,
           s_eye.fps,
           s_eye.loop);

    /*
     * anim_player_start_left(s_eye.left_asset, fps, loop);
     * anim_player_start_right(s_eye.right_asset, fps, loop);
     */
}

void eye_display_stop(void)
{
    printf("[eye] stop display animation\n");

    /*
     * anim_player_stop_left();
     * anim_player_stop_right();
     */
}

8. 客户素材上传与远程更新

客户可以通过自己的小程序上传素材到后端,后端再下发素材更新指令给设备。设备负责下载、校验、保存和刷新显示。

协议示例

复制代码
复制代码
{
  "cmd": "asset_update",
  "data": {
    "asset_id": "happy_v2",
    "asset_type": "eye_image",
    "left_url": "https://cdn.example.com/assets/happy_left.qoi",
    "right_url": "https://cdn.example.com/assets/happy_right.qoi",
    "left_path": "/assets/eyes/happy_left.qoi",
    "right_path": "/assets/eyes/happy_right.qoi",
    "md5": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "fps": 15,
    "auto_apply": true
  }
}

设备端代码

复制代码
复制代码
#include <stdio.h>
#include <string.h>
#include "cJSON.h"
#include "eye_display.h"

static int http_download_file(const char *url, const char *path)
{
    printf("[http] download %s -> %s\n", url, path);

    /*
     * esp_http_client_config_t config = {
     *     .url = url,
     *     .timeout_ms = 15000,
     * };
     * esp_http_client_handle_t client = esp_http_client_init(&config);
     * ...
     */

    return 0;
}

static bool asset_verify_md5(const char *path, const char *md5)
{
    printf("[asset] verify path=%s md5=%s\n", path, md5);
    return true;
}

void asset_manager_handle_update(const char *json)
{
    cJSON *root = cJSON_Parse(json);
    if (!root) {
        printf("[asset] parse failed\n");
        return;
    }

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

    const char *left_url = cJSON_GetObjectItem(data, "left_url")->valuestring;
    const char *right_url = cJSON_GetObjectItem(data, "right_url")->valuestring;
    const char *left_path = cJSON_GetObjectItem(data, "left_path")->valuestring;
    const char *right_path = cJSON_GetObjectItem(data, "right_path")->valuestring;
    const char *md5 = cJSON_GetObjectItem(data, "md5")->valuestring;

    int fps = 15;
    cJSON *fps_item = cJSON_GetObjectItem(data, "fps");
    if (fps_item) {
        fps = fps_item->valueint;
    }

    bool auto_apply = false;
    cJSON *apply_item = cJSON_GetObjectItem(data, "auto_apply");
    if (apply_item) {
        auto_apply = cJSON_IsTrue(apply_item);
    }

    if (http_download_file(left_url, left_path) != 0) {
        printf("[asset] left download failed\n");
        cJSON_Delete(root);
        return;
    }

    if (http_download_file(right_url, right_path) != 0) {
        printf("[asset] right download failed\n");
        cJSON_Delete(root);
        return;
    }

    if (!asset_verify_md5(left_path, md5)) {
        printf("[asset] md5 verify failed\n");
        cJSON_Delete(root);
        return;
    }

    printf("[asset] update success\n");

    if (auto_apply) {
        eye_display_play_assets(left_path, right_path, fps, true);
    }

    cJSON_Delete(root);
}

9. 四路触摸交互

四路触摸可以分别映射为不同交互区:

复制代码
复制代码
TOUCH0:头部触摸,唤醒 / 开心反馈
TOUCH1:左侧触摸,左眼眨眼 / 上一个素材
TOUCH2:右侧触摸,右眼眨眼 / 下一个素材
TOUCH3:下巴触摸,进入AI对话

代码示例:

复制代码
复制代码
typedef enum {
    TOUCH_EVT_HEAD = 0,
    TOUCH_EVT_LEFT,
    TOUCH_EVT_RIGHT,
    TOUCH_EVT_CHIN
} touch_event_t;

void motor_vibrate_once(int ms);
void ai_client_start_listen(void);

void touch_manager_handle_event(touch_event_t evt)
{
    switch (evt) {
    case TOUCH_EVT_HEAD:
        printf("[touch] head\n");
        eye_display_show_sync(EYE_EXPR_HAPPY);
        motor_vibrate_once(80);
        break;

    case TOUCH_EVT_LEFT:
        printf("[touch] left\n");
        eye_display_show_async(EYE_EXPR_BLINK, EYE_EXPR_IDLE);
        break;

    case TOUCH_EVT_RIGHT:
        printf("[touch] right\n");
        eye_display_show_async(EYE_EXPR_IDLE, EYE_EXPR_BLINK);
        break;

    case TOUCH_EVT_CHIN:
        printf("[touch] chin, start AI chat\n");
        eye_display_show_sync(EYE_EXPR_THINKING);
        motor_vibrate_once(50);
        ai_client_start_listen();
        break;

    default:
        break;
    }
}

10. G-sensor姿态联动

G-sensor可以识别摇晃、抬起、放下、左倾、右倾等动作,用于增强设备"生命感"。

复制代码
复制代码
typedef enum {
    GSENSOR_EVT_NONE = 0,
    GSENSOR_EVT_SHAKE,
    GSENSOR_EVT_PICK_UP,
    GSENSOR_EVT_PUT_DOWN,
    GSENSOR_EVT_TILT_LEFT,
    GSENSOR_EVT_TILT_RIGHT
} gsensor_event_t;

void servo_tail_wag(void);
void servo_head_nod(void);

void gsensor_handle_event(gsensor_event_t evt)
{
    switch (evt) {
    case GSENSOR_EVT_SHAKE:
        printf("[gsensor] shake\n");
        eye_display_show_sync(EYE_EXPR_HAPPY);
        motor_vibrate_once(100);
        servo_tail_wag();
        break;

    case GSENSOR_EVT_PICK_UP:
        printf("[gsensor] pick up\n");
        eye_display_show_sync(EYE_EXPR_WAKEUP);
        servo_head_nod();
        break;

    case GSENSOR_EVT_PUT_DOWN:
        printf("[gsensor] put down\n");
        eye_display_show_sync(EYE_EXPR_SLEEP);
        break;

    case GSENSOR_EVT_TILT_LEFT:
        printf("[gsensor] tilt left\n");
        eye_display_show_async(EYE_EXPR_BLINK, EYE_EXPR_IDLE);
        break;

    case GSENSOR_EVT_TILT_RIGHT:
        printf("[gsensor] tilt right\n");
        eye_display_show_async(EYE_EXPR_IDLE, EYE_EXPR_BLINK);
        break;

    default:
        break;
    }
}

11. 马达与舵机驱动

11.1 震动马达

复制代码
复制代码
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "board_pins.h"

void motor_init(void)
{
    gpio_config_t io = {
        .pin_bit_mask = 1ULL << PIN_MOTOR,
        .mode = GPIO_MODE_OUTPUT,
        .pull_up_en = GPIO_PULLUP_DISABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = GPIO_INTR_DISABLE,
    };

    gpio_config(&io);
    gpio_set_level(PIN_MOTOR, 0);
}

void motor_vibrate_once(int ms)
{
    gpio_set_level(PIN_MOTOR, 1);
    vTaskDelay(pdMS_TO_TICKS(ms));
    gpio_set_level(PIN_MOTOR, 0);
}

void motor_vibrate_pattern(void)
{
    motor_vibrate_once(60);
    vTaskDelay(pdMS_TO_TICKS(80));
    motor_vibrate_once(60);
}

11.2 舵机控制

复制代码
复制代码
#include "driver/ledc.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "board_pins.h"

#define SERVO_FREQ_HZ           50
#define SERVO_TIMER             LEDC_TIMER_0
#define SERVO_MODE              LEDC_LOW_SPEED_MODE
#define SERVO_HEAD_CHANNEL      LEDC_CHANNEL_0
#define SERVO_TAIL_CHANNEL      LEDC_CHANNEL_1

static void servo_channel_init(int gpio, ledc_channel_t channel)
{
    ledc_channel_config_t ch = {
        .gpio_num = gpio,
        .speed_mode = SERVO_MODE,
        .channel = channel,
        .timer_sel = SERVO_TIMER,
        .duty = 0,
        .hpoint = 0,
    };

    ledc_channel_config(&ch);
}

void servo_init(void)
{
    ledc_timer_config_t timer = {
        .speed_mode = SERVO_MODE,
        .timer_num = SERVO_TIMER,
        .duty_resolution = LEDC_TIMER_13_BIT,
        .freq_hz = SERVO_FREQ_HZ,
        .clk_cfg = LEDC_AUTO_CLK,
    };

    ledc_timer_config(&timer);

    servo_channel_init(PIN_SERVO_HEAD, SERVO_HEAD_CHANNEL);
    servo_channel_init(PIN_SERVO_TAIL, SERVO_TAIL_CHANNEL);
}

static uint32_t servo_angle_to_duty(int angle)
{
    if (angle < 0) {
        angle = 0;
    }

    if (angle > 180) {
        angle = 180;
    }

    int min_us = 500;
    int max_us = 2500;
    int pulse_us = min_us + (max_us - min_us) * angle / 180;

    return (uint32_t)(pulse_us * 8192 / 20000);
}

static void servo_set_angle(ledc_channel_t channel, int angle)
{
    uint32_t duty = servo_angle_to_duty(angle);

    ledc_set_duty(SERVO_MODE, channel, duty);
    ledc_update_duty(SERVO_MODE, channel);
}

void servo_head_nod(void)
{
    servo_set_angle(SERVO_HEAD_CHANNEL, 80);
    vTaskDelay(pdMS_TO_TICKS(200));
    servo_set_angle(SERVO_HEAD_CHANNEL, 105);
    vTaskDelay(pdMS_TO_TICKS(200));
    servo_set_angle(SERVO_HEAD_CHANNEL, 90);
}

void servo_tail_wag(void)
{
    servo_set_angle(SERVO_TAIL_CHANNEL, 60);
    vTaskDelay(pdMS_TO_TICKS(180));
    servo_set_angle(SERVO_TAIL_CHANNEL, 120);
    vTaskDelay(pdMS_TO_TICKS(180));
    servo_set_angle(SERVO_TAIL_CHANNEL, 90);
}

12. Wi-Fi / 4G / BLE联网策略

AI开发宝典中包含 SoftAP、BluFi 配网,以及 Wi-Fi 和 4G 模式切换相关内容,因此双目方案可以复用这些网络能力。实际产品中建议设计统一的 network_manager,由它负责 Wi-Fi、4G、BLE配网三种状态切换。

复制代码
复制代码
typedef enum {
    NET_MODE_NONE = 0,
    NET_MODE_WIFI,
    NET_MODE_4G,
    NET_MODE_BLE_CONFIG
} net_mode_t;

typedef struct {
    net_mode_t mode;
    bool wifi_connected;
    bool modem_ready;
    bool ble_active;
} network_ctx_t;

static network_ctx_t s_net = {
    .mode = NET_MODE_NONE,
};

void network_switch_to_wifi(void)
{
    printf("[net] switch to Wi-Fi\n");
    s_net.mode = NET_MODE_WIFI;

    /*
     * esp_wifi_start();
     * esp_wifi_connect();
     */
}

void network_switch_to_4g(void)
{
    printf("[net] switch to 4G\n");
    s_net.mode = NET_MODE_4G;

    /*
     * modem_power_on();
     * esp_modem_start_ppp();
     */
}

void network_start_ble_config(void)
{
    printf("[net] start BLE config\n");
    s_net.mode = NET_MODE_BLE_CONFIG;

    /*
     * blufi_init();
     * blufi_start_adv();
     */
}

void network_auto_connect(void)
{
    if (wifi_config_exists()) {
        network_switch_to_wifi();
    } else if (modem_4g_available()) {
        network_switch_to_4g();
    } else {
        network_start_ble_config();
    }
}

13. TWS耳机和蓝牙音箱扩展

为了连接 TWS 耳机或蓝牙音箱,推荐架构如下:

复制代码
复制代码
ESP32-S3
├── I2S PCM音频输出  -> 外置BT Audio SoC
├── UART控制命令     -> 外置BT Audio SoC
├── GPIO状态输入     -> 连接状态 / 配对状态
└── 本地I2S Codec    -> 内置喇叭

音频路由代码:

复制代码
复制代码
typedef enum {
    AUDIO_ROUTE_LOCAL_SPEAKER = 0,
    AUDIO_ROUTE_TWS,
    AUDIO_ROUTE_BT_SPEAKER
} audio_route_t;

static audio_route_t s_route = AUDIO_ROUTE_LOCAL_SPEAKER;

void bt_audio_send_cmd(const char *cmd)
{
    printf("[bt-audio] cmd=%s\n", cmd);

    /*
     * uart_write_bytes(BT_AUDIO_UART, cmd, strlen(cmd));
     * uart_write_bytes(BT_AUDIO_UART, "\r\n", 2);
     */
}

void audio_route_set(audio_route_t route)
{
    s_route = route;

    switch (route) {
    case AUDIO_ROUTE_LOCAL_SPEAKER:
        printf("[audio] route local speaker\n");
        // amp_enable(true);
        // bt_audio_send_cmd("AT+DISCONNECT");
        break;

    case AUDIO_ROUTE_TWS:
        printf("[audio] route TWS\n");
        // amp_enable(false);
        bt_audio_send_cmd("AT+CONNECT_LAST");
        break;

    case AUDIO_ROUTE_BT_SPEAKER:
        printf("[audio] route BT speaker\n");
        // amp_enable(false);
        bt_audio_send_cmd("AT+PAIR");
        break;

    default:
        break;
    }
}

14. AI状态与双目表情联动

AI对话过程中,建议把设备状态、眼睛表情、马达和舵机统一联动。

复制代码
复制代码
typedef enum {
    AI_STATE_IDLE = 0,
    AI_STATE_WAKEUP,
    AI_STATE_LISTENING,
    AI_STATE_THINKING,
    AI_STATE_SPEAKING,
    AI_STATE_ERROR
} ai_state_t;

void ai_state_update(ai_state_t state)
{
    switch (state) {
    case AI_STATE_IDLE:
        eye_display_show_sync(EYE_EXPR_IDLE);
        break;

    case AI_STATE_WAKEUP:
        eye_display_show_sync(EYE_EXPR_WAKEUP);
        motor_vibrate_once(60);
        break;

    case AI_STATE_LISTENING:
        eye_display_show_sync(EYE_EXPR_THINKING);
        break;

    case AI_STATE_THINKING:
        eye_display_play_assets(
            "/assets/eyes/thinking_left.qoi",
            "/assets/eyes/thinking_right.qoi",
            15,
            true
        );
        break;

    case AI_STATE_SPEAKING:
        eye_display_show_sync(EYE_EXPR_SPEAKING);
        servo_head_nod();
        break;

    case AI_STATE_ERROR:
        eye_display_show_sync(EYE_EXPR_ERROR);
        motor_vibrate_pattern();
        break;

    default:
        break;
    }
}

15. 小程序 / 后端统一控制协议

客户自己的小程序和后端只需要按协议下发命令,设备端通过统一协议解析即可。

控制双目显示

复制代码
复制代码
{
  "cmd": "eye_display",
  "data": {
    "mode": "async",
    "left": "happy",
    "right": "blink"
  }
}

控制舵机动作

复制代码
复制代码
{
  "cmd": "servo_action",
  "data": {
    "target": "tail",
    "action": "wag",
    "duration_ms": 1200
  }
}

切换音频输出

复制代码
复制代码
{
  "cmd": "audio_route",
  "data": {
    "route": "tws"
  }
}

统一解析代码

复制代码
复制代码
#include <stdio.h>
#include <string.h>
#include "cJSON.h"

static eye_expr_t parse_eye_expr(const char *name)
{
    if (!name) {
        return EYE_EXPR_IDLE;
    }

    if (strcmp(name, "happy") == 0) {
        return EYE_EXPR_HAPPY;
    }

    if (strcmp(name, "blink") == 0) {
        return EYE_EXPR_BLINK;
    }

    if (strcmp(name, "thinking") == 0) {
        return EYE_EXPR_THINKING;
    }

    if (strcmp(name, "speaking") == 0) {
        return EYE_EXPR_SPEAKING;
    }

    if (strcmp(name, "sleep") == 0) {
        return EYE_EXPR_SLEEP;
    }

    return EYE_EXPR_IDLE;
}

void app_protocol_handle_json(const char *json)
{
    cJSON *root = cJSON_Parse(json);
    if (!root) {
        printf("[proto] json parse failed\n");
        return;
    }

    cJSON *cmd = cJSON_GetObjectItem(root, "cmd");
    cJSON *data = cJSON_GetObjectItem(root, "data");

    if (!cmd || !data) {
        cJSON_Delete(root);
        return;
    }

    if (strcmp(cmd->valuestring, "eye_display") == 0) {
        const char *mode = cJSON_GetObjectItem(data, "mode")->valuestring;
        const char *left = cJSON_GetObjectItem(data, "left")->valuestring;
        const char *right = cJSON_GetObjectItem(data, "right")->valuestring;

        if (strcmp(mode, "sync") == 0) {
            eye_display_show_sync(parse_eye_expr(left));
        } else {
            eye_display_show_async(parse_eye_expr(left), parse_eye_expr(right));
        }
    } else if (strcmp(cmd->valuestring, "servo_action") == 0) {
        const char *action = cJSON_GetObjectItem(data, "action")->valuestring;

        if (strcmp(action, "wag") == 0) {
            servo_tail_wag();
        } else if (strcmp(action, "nod") == 0) {
            servo_head_nod();
        }
    } else if (strcmp(cmd->valuestring, "audio_route") == 0) {
        const char *route = cJSON_GetObjectItem(data, "route")->valuestring;

        if (strcmp(route, "speaker") == 0) {
            audio_route_set(AUDIO_ROUTE_LOCAL_SPEAKER);
        } else if (strcmp(route, "tws") == 0) {
            audio_route_set(AUDIO_ROUTE_TWS);
        } else if (strcmp(route, "bt_speaker") == 0) {
            audio_route_set(AUDIO_ROUTE_BT_SPEAKER);
        }
    } else if (strcmp(cmd->valuestring, "asset_update") == 0) {
        asset_manager_handle_update(json);
    }

    cJSON_Delete(root);
}

16. app_main.c 示例

复制代码
复制代码
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

void app_main(void)
{
    printf("SIBO AI Dual Eye Speaker Start\n");

    network_auto_connect();

    eye_display_init();
    motor_init();
    servo_init();

    audio_route_set(AUDIO_ROUTE_LOCAL_SPEAKER);

    eye_display_show_sync(EYE_EXPR_IDLE);

    while (1) {
        /*
         * 实际工程主循环建议处理:
         * 1. 触摸事件
         * 2. G-sensor事件
         * 3. AI状态事件
         * 4. 小程序/后端命令
         * 5. 双目动画刷新
         * 6. 网络状态切换
         */

        vTaskDelay(pdMS_TO_TICKS(20));
    }
}

17. 总结

四博AI双目智能音箱方案不是简单给音箱增加两块屏幕,而是将 ESP32-S3 主控、双目显示、AI语音、客户素材上传、四路触摸、G-sensor、震动马达、舵机、Wi-Fi/4G/BLE联网、TWS耳机/蓝牙音箱扩展统一成一个可量产、可二次开发的AI硬件平台。

它适合以下产品方向:

复制代码
复制代码
AI桌面宠物
AI双目智能音箱
AI儿童陪伴机器人
AI语音机器人
AI互动摆件
AI智能玩具
AI情绪陪伴终端
AI学习机外设
AI小夜灯升级版

对B端客户来说,该方案的最大价值是开放度高:客户可以接入自己的小程序、自己的后端、自己的AI模型、自己的IP素材和自己的业务系统,在四博已有硬件与工程基础上快速做出差异化AI产品。

相关推荐
RSTJ_16251 小时前
PYTHON+AI LLM DAY FOURTY-FIVE
人工智能
卷卷说风控1 小时前
【卷卷观察】取消订阅后项目“消失“:Claude Design 暴露了SaaS的访问权陷阱
人工智能
我鑫如一1 小时前
专业的AI API中转站厂家
人工智能·python
腾讯云开发者1 小时前
腾讯云TVP走进银河通用×NVIDIA×福田戴姆勒,解码AI驱动产业硬核突围之路
人工智能
消晨消晨1 小时前
Pytorch初上手——Dataset自定义数据集与Dataloader数据加载器
人工智能·pytorch·python
HackTwoHub1 小时前
AI提示词注入绕过工具:一键绕过Codex/Claude安全限制,CTF夺旗与渗透测试必备神器
网络·人工智能·安全·web安全·系统安全·网络攻击模型·安全架构
诺未科技_NovaTech1 小时前
Microsoft 365 E7 ,“AI+安全+身份”三位一体,打造 AI 时代的一站式操作系统
人工智能·安全·microsoft
小白学大数据1 小时前
均线选股策略研究:基于 Python 数据分析实现
人工智能·python·数据分析
三无推导1 小时前
OpenHuman 开源项目详解:个人 AI 助手架构与核心技术拆解
人工智能·性能优化·架构·开源·ai助手