CozyLife 墨水屏 + Find My / Google 双防丢四博 AI 智能音箱方案

CozyLife 墨水屏 + Find My / Google 双防丢四博 AI 智能音箱方案

一、方案概述

本文整理一套基于 四博 AI 智能音箱 + CozyLife 生态 + 墨水屏显示 + Apple Find My / Google 双系统防丢 的智能硬件方案。

该方案并不是普通 AI 音箱,而是一个集成 AI 语音交互、智能家居控制、墨水屏常显、蓝牙音箱、防丢提醒、场景联动、声音克隆、MCP 工具控制 的桌面 AI 终端。

整体产品可以理解为:

复制代码
四博 AI 智能音箱
+ CozyLife APP / 小程序
+ 墨水屏状态显示
+ AI 大模型语音交互
+ 蓝牙音箱
+ 闹钟 / 日程 / 提醒
+ 声音克隆 / 声纹识别
+ Apple Find My 防丢
+ Google 防丢
= 四博 AI 墨水屏双防丢智能音箱

四博资料中已有 ESP32-S3 音视频 / AI 模组、AI 开发宝典、CozyLife 类产品、AI 智能音响、Find My 类产品和双系统 FindMy + Google 防丢器等产品基础,可作为该方案的硬件和软件参考。


二、产品定位

该产品适合以下场景:

复制代码
1. AI 智能音箱
2. 桌面 AI 陪伴终端
3. 智能家居控制中心
4. 家庭防丢提醒设备
5. 品牌 IP 智能音箱
6. 儿童学习陪伴音箱
7. 老人提醒 / 家庭看护设备
8. 礼品和电商差异化产品

相比普通音箱,它多了三个核心差异点:

复制代码
1. 墨水屏:低功耗常显,可显示时间、天气、AI 摘要、设备状态、防丢状态
2. CozyLife:可接入智能家居场景、远程控制、定时、群组、APP 配置
3. 双防丢:支持 Apple Find My 和 Google 防丢功能

三、核心功能设计

复制代码
1. AI 智能音箱
   - 支持语音唤醒
   - 支持 AI 大模型对话
   - 支持连续对话
   - 支持实时打断
   - 支持声音克隆
   - 支持声纹识别
   - 支持蓝牙音箱
   - 支持闹钟 / 日程 / 提醒

2. CozyLife 智能家居
   - 支持 APP 远程控制
   - 支持定时
   - 支持群组
   - 支持场景联动
   - 支持智能音箱控制
   - 支持设备状态上报

3. 墨水屏显示
   - 显示时间 / 日期
   - 显示天气 / 温湿度
   - 显示 AI 对话摘要
   - 显示闹钟 / 日程
   - 显示智能家居场景
   - 显示 Find My / Google 绑定状态
   - 显示二维码 / 配网码
   - 支持低功耗常显

4. 双系统防丢
   - 支持 Apple Find My
   - 支持 Google 防丢
   - 支持本地蜂鸣器寻物
   - 支持低电量提醒
   - 支持遗落 / 丢失状态提醒
   - 支持音箱语音播报防丢状态

5. MCP 自然语言控制
   - "小博,帮我找一下音箱"
   - "小博,显示防丢状态"
   - "小博,把墨水屏切到日程页面"
   - "小博,打开回家模式"
   - "小博,显示配网二维码"

四、系统总体架构

复制代码
┌──────────────────────────────────────────────┐
│      CozyLife 墨水屏双防丢 AI 智能音箱         │
├──────────────────────────────────────────────┤
│  交互层                                      │
│  ├─ 麦克风:语音采集                         │
│  ├─ 喇叭:TTS / 蓝牙音乐 / 提示音             │
│  ├─ 墨水屏:状态常显 / 场景 / 防丢状态        │
│  ├─ 按键 / 触控:唤醒、打断、音量、翻页       │
│  ├─ RGB 灯:状态提示                         │
│  └─ 蜂鸣器:本地寻物提醒                     │
├──────────────────────────────────────────────┤
│  主控层                                      │
│  ├─ ESP32-S3:AI、Wi-Fi、BLE、屏幕、音频       │
│  ├─ VB6824:离线唤醒、离线命令、降噪          │
│  ├─ ES8311 / I2S Codec:音频采集和播放        │
│  ├─ E-Paper Driver:墨水屏驱动                │
│  └─ Power / Battery:电源和低功耗管理         │
├──────────────────────────────────────────────┤
│  防丢层                                      │
│  ├─ Find My / Google 双系统防丢 SoC           │
│  ├─ BLE 广播 / 平台认证协议                   │
│  ├─ 密钥 / Token / 安全存储                   │
│  └─ UART / I2C 与 ESP32-S3 通信               │
├──────────────────────────────────────────────┤
│  固件层                                      │
│  ├─ app_event_bus:事件总线                   │
│  ├─ audio_mgr:音频采集 / 播放                 │
│  ├─ ai_ws_client:AI WebSocket 通信            │
│  ├─ epaper_ui:墨水屏页面管理                 │
│  ├─ cozy_cloud:CozyLife 云端同步             │
│  ├─ tag_bridge:双系统防丢模块通信            │
│  ├─ mcp_tools:自然语言工具控制               │
│  ├─ ota_mgr:OTA 升级                         │
│  └─ power_mgr:低功耗管理                     │
├──────────────────────────────────────────────┤
│  云端 / APP 层                                │
│  ├─ CozyLife APP / 小程序                     │
│  ├─ AI 大模型网关                             │
│  ├─ ASR / TTS / Voice Clone                   │
│  ├─ RAG 知识库                                │
│  ├─ MCP 工具服务                              │
│  └─ Apple / Google 防丢平台                   │
└──────────────────────────────────────────────┘

五、硬件方案设计

1. 主控选型

推荐使用 ESP32-S3 N16R8 / N16R2 作为主控。

ESP32-S3 适合该方案的原因:

复制代码
1. 双核 240MHz,适合多任务调度
2. GPIO 资源充足,可接音频、墨水屏、按键、RGB、防丢模块
3. 支持 Wi-Fi + BLE
4. 支持 PSRAM,适合音频缓存、UI 缓存和 WebSocket 数据流
5. 适合 AI 音频、智能家居、屏幕显示类产品

2. 防丢模块建议

Apple Find My 和 Google 防丢属于平台级认证生态,量产时不建议由 ESP32-S3 主控直接实现协议。推荐采用独立防丢模块:

复制代码
方案 A:独立双系统防丢 SoC
- 内部支持 Find My 协议栈
- 内部支持 Google 防丢协议栈
- 独立 BLE 广播
- 独立密钥安全存储
- 独立低功耗运行
- 通过 UART/I2C 向 ESP32-S3 上报状态

方案 B:两个独立防丢模块
- 一个 Apple Find My 模块
- 一个 Google 防丢模块
- ESP32-S3 统一做状态显示和业务控制

方案 C:认证防丢模块 + ESP32-S3
- 防丢模块负责认证协议
- ESP32-S3 负责 AI、CozyLife、墨水屏和音频

这种设计可以把认证协议和主业务逻辑解耦,后期维护和认证风险更低。

3. 推荐 BOM 框架

模块 推荐方案 说明
主控 ESP32-S3 N16R8 AI、Wi-Fi、BLE、墨水屏、音频
语音 VB6824 离线唤醒、离线命令、降噪
Codec ES8311 / I2S Codec 麦克风采集、喇叭播放
功放 3W~5W D 类功放 音箱播放
屏幕 2.13 / 2.9 / 3.7 寸墨水屏 低功耗常显
防丢 Find My + Google 双系统模块 独立认证协议
蜂鸣器 80dB~100dB 本地寻物
RGB WS2812 / PWM RGB 状态提示
电源 Type-C + 锂电池可选 桌面 / 便携
存储 16MB Flash + 8MB PSRAM 资源、缓存、OTA
通信 Wi-Fi 2.4G + BLE CozyLife / AI / 配网

六、固件工程目录

复制代码
sibo_ai_epaper_speaker/
├── CMakeLists.txt
├── sdkconfig.defaults
├── partitions.csv
├── main/
│   ├── app_main.c
│   ├── app_config.h
│   ├── board_pins.h
│   ├── app_event_bus.c
│   ├── app_event_bus.h
│   ├── audio_mgr.c
│   ├── ai_ws_client.c
│   ├── epaper_ui.c
│   ├── cozy_cloud.c
│   ├── tag_bridge.c
│   ├── tag_bridge.h
│   ├── mcp_tools.c
│   ├── key_mgr.c
│   ├── power_mgr.c
│   ├── ota_mgr.c
│   └── factory_test.c
├── components/
│   ├── epaper_driver/
│   ├── audio_codec/
│   ├── json_parser/
│   ├── qr_render/
│   └── font_assets/
└── server/
    ├── ai_gateway.py
    ├── mcp_server.py
    └── cozy_bridge.py

七、基础配置

app_config.h

复制代码
#pragma once

#define PRODUCT_NAME                  "SIBO_AI_EPAPER_SPEAKER"
#define FIRMWARE_VERSION              "v1.0.0"

#define ENABLE_WIFI                   1
#define ENABLE_BLE                    1
#define ENABLE_BLUFI                  1
#define ENABLE_COZYLIFE               1
#define ENABLE_AI_ASSISTANT           1
#define ENABLE_EPAPER                 1
#define ENABLE_FINDMY_GOOGLE_TAG      1
#define ENABLE_MCP                    1
#define ENABLE_OTA                    1

#define AUDIO_SAMPLE_RATE             16000
#define AUDIO_BITS_PER_SAMPLE         16
#define AUDIO_FRAME_MS                20
#define AUDIO_FRAME_SAMPLES           (AUDIO_SAMPLE_RATE * AUDIO_FRAME_MS / 1000)

#define AI_WS_URL                     "wss://your-ai-gateway.example.com/device/ws"
#define COZY_DEVICE_TYPE              "ai_epaper_speaker"

#define EPAPER_WIDTH                  296
#define EPAPER_HEIGHT                 128

#define TAG_UART_BAUD                 115200
#define TAG_QUERY_INTERVAL_MS         5000

#define DEFAULT_VOLUME                45
#define MAX_VOLUME                    100

board_pins.h

复制代码
#pragma once

#include "driver/gpio.h"

#define PIN_I2S_BCLK                  GPIO_NUM_4
#define PIN_I2S_WS                    GPIO_NUM_5
#define PIN_I2S_DIN_MIC               GPIO_NUM_6
#define PIN_I2S_DOUT_SPK              GPIO_NUM_7

#define PIN_EPAPER_SCLK               GPIO_NUM_12
#define PIN_EPAPER_MOSI               GPIO_NUM_13
#define PIN_EPAPER_CS                 GPIO_NUM_14
#define PIN_EPAPER_DC                 GPIO_NUM_15
#define PIN_EPAPER_RST                GPIO_NUM_16
#define PIN_EPAPER_BUSY               GPIO_NUM_17

#define PIN_TAG_UART_TX               GPIO_NUM_18
#define PIN_TAG_UART_RX               GPIO_NUM_19

#define PIN_BUZZER_PWM                GPIO_NUM_21
#define PIN_RGB_LED                   GPIO_NUM_48

#define PIN_KEY_WAKE                  GPIO_NUM_38
#define PIN_KEY_VOL_UP                GPIO_NUM_39
#define PIN_KEY_VOL_DOWN              GPIO_NUM_40
#define PIN_KEY_PAGE                  GPIO_NUM_41

八、分区表设计

墨水屏字体、图片资源、二维码缓存、提示音等可以放到 assets 分区。

复制代码
# partitions.csv
# Name,     Type, SubType, Offset,   Size
nvs,        data, nvs,     0x9000,   0x6000
otadata,    data, ota,     0xf000,   0x2000
phy_init,   data, phy,     0x11000,  0x1000
factory,    app,  factory, 0x20000,  3M
ota_0,      app,  ota_0,   ,         3M
ota_1,      app,  ota_1,   ,         3M
assets,     data, spiffs,  ,         4M
storage,    data, nvs,     ,         0x10000

九、事件总线设计

设备需要同时处理 AI 状态、墨水屏刷新、CozyLife 状态、防丢模块事件、按键和 OTA,因此建议使用事件总线。

复制代码
#pragma once

#include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"

typedef enum {
    EVT_KEY_WAKE,
    EVT_KEY_WAKE_LONG,
    EVT_KEY_VOL_UP,
    EVT_KEY_VOL_DOWN,
    EVT_KEY_PAGE,

    EVT_AI_IDLE,
    EVT_AI_LISTENING,
    EVT_AI_THINKING,
    EVT_AI_SPEAKING,
    EVT_AI_INTERRUPTED,

    EVT_COZY_ONLINE,
    EVT_COZY_OFFLINE,
    EVT_COZY_SCENE_CHANGED,

    EVT_EPAPER_REFRESH,
    EVT_EPAPER_SHOW_QR,
    EVT_EPAPER_NEXT_PAGE,

    EVT_TAG_FINDMY_BOUND,
    EVT_TAG_GOOGLE_BOUND,
    EVT_TAG_LOST_MODE,
    EVT_TAG_NEARBY,
    EVT_TAG_LOW_BAT,
    EVT_TAG_BUZZER_ON,
    EVT_TAG_BUZZER_OFF,

    EVT_OTA_START,
    EVT_OTA_DONE,
    EVT_OTA_FAIL,
} app_evt_type_t;

typedef struct {
    app_evt_type_t type;
    int32_t value;
    int64_t ts_ms;
} app_evt_t;

void app_event_bus_init(void);
QueueHandle_t app_event_bus_register(const char *name, uint32_t len);
void app_event_post(app_evt_type_t type, int32_t value);

事件广播实现:

复制代码
#include <string.h>
#include "esp_timer.h"
#include "app_event_bus.h"

#define SUB_MAX 12

typedef struct {
    char name[24];
    QueueHandle_t q;
} app_sub_t;

static app_sub_t s_subs[SUB_MAX];
static int s_sub_count = 0;

void app_event_bus_init(void)
{
    memset(s_subs, 0, sizeof(s_subs));
    s_sub_count = 0;
}

QueueHandle_t app_event_bus_register(const char *name, uint32_t len)
{
    if (s_sub_count >= SUB_MAX) {
        return NULL;
    }

    QueueHandle_t q = xQueueCreate(len, sizeof(app_evt_t));
    if (!q) {
        return NULL;
    }

    strncpy(s_subs[s_sub_count].name, name, sizeof(s_subs[s_sub_count].name) - 1);
    s_subs[s_sub_count].q = q;
    s_sub_count++;

    return q;
}

void app_event_post(app_evt_type_t type, int32_t value)
{
    app_evt_t evt = {
        .type = type,
        .value = value,
        .ts_ms = esp_timer_get_time() / 1000
    };

    for (int i = 0; i < s_sub_count; i++) {
        if (s_subs[i].q) {
            xQueueSend(s_subs[i].q, &evt, 0);
        }
    }
}

十、主程序入口

复制代码
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"

#include "app_config.h"
#include "app_event_bus.h"

extern void key_mgr_start(void);
extern void audio_mgr_start(void);
extern void ai_ws_client_start(void);
extern void epaper_ui_start(void);
extern void cozy_cloud_start(void);
extern void tag_bridge_start(void);
extern void mcp_tools_start(void);
extern void ota_mgr_start(void);
extern void power_mgr_start(void);

static const char *TAG = "app_main";

void app_main(void)
{
    ESP_LOGI(TAG, "boot %s %s", PRODUCT_NAME, FIRMWARE_VERSION);

    app_event_bus_init();

    key_mgr_start();
    audio_mgr_start();
    power_mgr_start();

#if ENABLE_EPAPER
    epaper_ui_start();
#endif

#if ENABLE_COZYLIFE
    cozy_cloud_start();
#endif

#if ENABLE_FINDMY_GOOGLE_TAG
    tag_bridge_start();
#endif

#if ENABLE_AI_ASSISTANT
    ai_ws_client_start();
#endif

#if ENABLE_MCP
    mcp_tools_start();
#endif

#if ENABLE_OTA
    ota_mgr_start();
#endif

    while (1) {
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

十一、双防丢模块通信设计

推荐 ESP32-S3 通过 UART 与独立防丢模块通信。防丢模块负责 Find My / Google 协议,ESP32-S3 负责读取状态、显示状态、发起蜂鸣器命令。

1. 通信帧格式

复制代码
Frame:
+--------+--------+------+-----+----------+------+
| 0x55   | 0xAA   | LEN  | CMD | PAYLOAD  | XOR  |
+--------+--------+------+-----+----------+------+

CMD:
0x01  查询状态
0x02  启动蜂鸣器
0x03  停止蜂鸣器
0x04  查询绑定状态
0x05  查询电量
0x06  进入配对模式

Event:
0x81  Find My 已绑定
0x82  Google 已绑定
0x83  低电量
0x84  遗失模式
0x85  附近找到

2. tag_bridge.h

复制代码
#pragma once

#include <stdint.h>
#include <stdbool.h>

typedef enum {
    TAG_BIND_NONE       = 0,
    TAG_BIND_FINDMY     = 1 << 0,
    TAG_BIND_GOOGLE     = 1 << 1,
} tag_bind_mask_t;

typedef struct {
    uint8_t bind_mask;
    uint8_t battery_percent;
    bool lost_mode;
    bool buzzer_on;
    int8_t rssi;
} tag_status_t;

void tag_bridge_start(void);
void tag_query_status(void);
void tag_start_buzzer(void);
void tag_stop_buzzer(void);
void tag_enter_pair_mode(void);
bool tag_get_status(tag_status_t *out);

3. tag_bridge.c

复制代码
#include <string.h>
#include "driver/uart.h"
#include "esp_log.h"
#include "board_pins.h"
#include "app_config.h"
#include "app_event_bus.h"
#include "tag_bridge.h"

#define TAG_UART_NUM            UART_NUM_1
#define TAG_RX_BUF_SIZE         512

#define TAG_CMD_QUERY_STATUS    0x01
#define TAG_CMD_BUZZER_ON       0x02
#define TAG_CMD_BUZZER_OFF      0x03
#define TAG_CMD_PAIR_MODE       0x06

#define TAG_EVT_FINDMY_BOUND    0x81
#define TAG_EVT_GOOGLE_BOUND    0x82
#define TAG_EVT_LOW_BAT         0x83
#define TAG_EVT_LOST_MODE       0x84
#define TAG_EVT_NEARBY          0x85

static const char *TAG = "tag_bridge";
static tag_status_t s_status;

static uint8_t calc_xor(const uint8_t *buf, int len)
{
    uint8_t x = 0;

    for (int i = 0; i < len; i++) {
        x ^= buf[i];
    }

    return x;
}

static void tag_send_cmd(uint8_t cmd, const uint8_t *payload, uint8_t payload_len)
{
    uint8_t frame[64];
    int idx = 0;

    frame[idx++] = 0x55;
    frame[idx++] = 0xAA;
    frame[idx++] = payload_len + 1;
    frame[idx++] = cmd;

    if (payload && payload_len > 0) {
        memcpy(&frame[idx], payload, payload_len);
        idx += payload_len;
    }

    frame[idx] = calc_xor(frame, idx);
    idx++;

    uart_write_bytes(TAG_UART_NUM, (const char *)frame, idx);
}

void tag_query_status(void)
{
    tag_send_cmd(TAG_CMD_QUERY_STATUS, NULL, 0);
}

void tag_start_buzzer(void)
{
    tag_send_cmd(TAG_CMD_BUZZER_ON, NULL, 0);
    s_status.buzzer_on = true;
    app_event_post(EVT_TAG_BUZZER_ON, 0);
}

void tag_stop_buzzer(void)
{
    tag_send_cmd(TAG_CMD_BUZZER_OFF, NULL, 0);
    s_status.buzzer_on = false;
    app_event_post(EVT_TAG_BUZZER_OFF, 0);
}

void tag_enter_pair_mode(void)
{
    tag_send_cmd(TAG_CMD_PAIR_MODE, NULL, 0);
}

bool tag_get_status(tag_status_t *out)
{
    if (!out) {
        return false;
    }

    memcpy(out, &s_status, sizeof(tag_status_t));
    return true;
}

static void tag_handle_event(uint8_t cmd, const uint8_t *payload, int len)
{
    switch (cmd) {
    case TAG_EVT_FINDMY_BOUND:
        s_status.bind_mask |= TAG_BIND_FINDMY;
        app_event_post(EVT_TAG_FINDMY_BOUND, 1);
        break;

    case TAG_EVT_GOOGLE_BOUND:
        s_status.bind_mask |= TAG_BIND_GOOGLE;
        app_event_post(EVT_TAG_GOOGLE_BOUND, 1);
        break;

    case TAG_EVT_LOW_BAT:
        s_status.battery_percent = len > 0 ? payload[0] : 0;
        app_event_post(EVT_TAG_LOW_BAT, s_status.battery_percent);
        break;

    case TAG_EVT_LOST_MODE:
        s_status.lost_mode = true;
        app_event_post(EVT_TAG_LOST_MODE, 1);
        break;

    case TAG_EVT_NEARBY:
        s_status.lost_mode = false;
        app_event_post(EVT_TAG_NEARBY, 1);
        break;

    default:
        ESP_LOGW(TAG, "unknown tag event: 0x%02X", cmd);
        break;
    }
}

static void tag_parse_frame(const uint8_t *buf, int len)
{
    if (len < 5) {
        return;
    }

    if (buf[0] != 0x55 || buf[1] != 0xAA) {
        return;
    }

    uint8_t frame_len = buf[2];
    uint8_t cmd = buf[3];
    const uint8_t *payload = &buf[4];
    int payload_len = frame_len - 1;

    uint8_t xor_recv = buf[3 + frame_len];
    uint8_t xor_calc = calc_xor(buf, 3 + frame_len);

    if (xor_recv != xor_calc) {
        ESP_LOGW(TAG, "xor error");
        return;
    }

    tag_handle_event(cmd, payload, payload_len);
}

static void tag_uart_task(void *arg)
{
    uint8_t buf[TAG_RX_BUF_SIZE];

    while (1) {
        int len = uart_read_bytes(
            TAG_UART_NUM,
            buf,
            sizeof(buf),
            pdMS_TO_TICKS(200)
        );

        if (len > 0) {
            tag_parse_frame(buf, len);
        }
    }
}

static void tag_poll_task(void *arg)
{
    while (1) {
        tag_query_status();
        vTaskDelay(pdMS_TO_TICKS(TAG_QUERY_INTERVAL_MS));
    }
}

void tag_bridge_start(void)
{
    uart_config_t cfg = {
        .baud_rate = TAG_UART_BAUD,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
    };

    uart_driver_install(TAG_UART_NUM, TAG_RX_BUF_SIZE, 0, 0, NULL, 0);
    uart_param_config(TAG_UART_NUM, &cfg);
    uart_set_pin(
        TAG_UART_NUM,
        PIN_TAG_UART_TX,
        PIN_TAG_UART_RX,
        UART_PIN_NO_CHANGE,
        UART_PIN_NO_CHANGE
    );

    memset(&s_status, 0, sizeof(s_status));

    xTaskCreate(tag_uart_task, "tag_uart", 4096, NULL, 6, NULL);
    xTaskCreate(tag_poll_task, "tag_poll", 4096, NULL, 4, NULL);
}

十二、墨水屏 UI 页面设计

墨水屏适合做低功耗常显,建议设计多个页面。

复制代码
Page 0:首页
- 时间
- 日期
- 天气
- AI 状态
- CozyLife 在线状态

Page 1:防丢状态页
- Find My:已绑定 / 未绑定
- Google:已绑定 / 未绑定
- 防丢电量
- 遗失模式
- 蜂鸣器状态

Page 2:智能家居页
- 当前场景
- 房间温湿度
- 设备数量
- 回家 / 离家模式

Page 3:AI 摘要页
- 用户问题摘要
- AI 回复摘要
- 当前模型

Page 4:配网页
- CozyLife 绑定二维码
- BluFi 配网提示

epaper_ui.c

复制代码
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "app_event_bus.h"
#include "tag_bridge.h"
#include "app_config.h"

typedef enum {
    EPAPER_PAGE_HOME = 0,
    EPAPER_PAGE_TAG,
    EPAPER_PAGE_COZY,
    EPAPER_PAGE_AI,
    EPAPER_PAGE_QR,
    EPAPER_PAGE_MAX,
} epaper_page_t;

static QueueHandle_t s_epaper_q;
static epaper_page_t s_page = EPAPER_PAGE_HOME;

static char s_ai_summary[128] = "Hello, I am Sibo AI.";
static char s_cozy_scene[64] = "Home";
static bool s_cozy_online = false;

static void epaper_clear(void)
{
    /*
     * epaper_driver_clear();
     */
}

static void epaper_draw_text(int x, int y, const char *text)
{
    /*
     * epaper_driver_draw_text(x, y, text);
     */
}

static void epaper_update(void)
{
    /*
     * epaper_driver_update();
     */
}

static void draw_home_page(void)
{
    epaper_clear();

    epaper_draw_text(0, 0, "SIBO AI SPEAKER");
    epaper_draw_text(0, 20, "Time: 08:30");
    epaper_draw_text(0, 40, s_cozy_online ? "CozyLife: Online" : "CozyLife: Offline");
    epaper_draw_text(0, 60, "AI: Ready");
    epaper_draw_text(0, 90, "Press PAGE to switch");

    epaper_update();
}

static void draw_tag_page(void)
{
    tag_status_t st;
    char line[64];

    tag_get_status(&st);

    epaper_clear();

    epaper_draw_text(0, 0, "Anti-Lost Status");

    snprintf(line, sizeof(line), "Find My: %s",
             (st.bind_mask & TAG_BIND_FINDMY) ? "Bound" : "Unbound");
    epaper_draw_text(0, 24, line);

    snprintf(line, sizeof(line), "Google: %s",
             (st.bind_mask & TAG_BIND_GOOGLE) ? "Bound" : "Unbound");
    epaper_draw_text(0, 44, line);

    snprintf(line, sizeof(line), "Battery: %d%%", st.battery_percent);
    epaper_draw_text(0, 64, line);

    snprintf(line, sizeof(line), "Lost: %s", st.lost_mode ? "YES" : "NO");
    epaper_draw_text(0, 84, line);

    snprintf(line, sizeof(line), "Buzzer: %s", st.buzzer_on ? "ON" : "OFF");
    epaper_draw_text(0, 104, line);

    epaper_update();
}

static void draw_cozy_page(void)
{
    epaper_clear();

    epaper_draw_text(0, 0, "CozyLife Scene");
    epaper_draw_text(0, 24, s_cozy_scene);
    epaper_draw_text(0, 48, "Devices: 12");
    epaper_draw_text(0, 72, "Scene: Home");
    epaper_draw_text(0, 96, "Voice Control Ready");

    epaper_update();
}

static void draw_ai_page(void)
{
    epaper_clear();

    epaper_draw_text(0, 0, "AI Summary");
    epaper_draw_text(0, 24, s_ai_summary);

    epaper_update();
}

static void draw_qr_page(void)
{
    epaper_clear();

    epaper_draw_text(0, 0, "Scan to Bind");
    epaper_draw_text(0, 24, "CozyLife / BluFi");

    /*
     * qr_render_draw("cozylife://bind?device_id=xxx");
     */

    epaper_update();
}

static void epaper_draw_page(void)
{
    switch (s_page) {
    case EPAPER_PAGE_HOME:
        draw_home_page();
        break;

    case EPAPER_PAGE_TAG:
        draw_tag_page();
        break;

    case EPAPER_PAGE_COZY:
        draw_cozy_page();
        break;

    case EPAPER_PAGE_AI:
        draw_ai_page();
        break;

    case EPAPER_PAGE_QR:
        draw_qr_page();
        break;

    default:
        draw_home_page();
        break;
    }
}

static void epaper_task(void *arg)
{
    app_evt_t evt;

    epaper_draw_page();

    while (1) {
        if (xQueueReceive(s_epaper_q, &evt, portMAX_DELAY) == pdTRUE) {
            switch (evt.type) {
            case EVT_KEY_PAGE:
            case EVT_EPAPER_NEXT_PAGE:
                s_page++;
                if (s_page >= EPAPER_PAGE_MAX) {
                    s_page = EPAPER_PAGE_HOME;
                }
                epaper_draw_page();
                break;

            case EVT_TAG_FINDMY_BOUND:
            case EVT_TAG_GOOGLE_BOUND:
            case EVT_TAG_LOW_BAT:
            case EVT_TAG_LOST_MODE:
            case EVT_TAG_NEARBY:
                if (s_page == EPAPER_PAGE_TAG) {
                    epaper_draw_page();
                }
                break;

            case EVT_COZY_ONLINE:
                s_cozy_online = true;
                epaper_draw_page();
                break;

            case EVT_COZY_OFFLINE:
                s_cozy_online = false;
                epaper_draw_page();
                break;

            case EVT_AI_THINKING:
                strncpy(s_ai_summary, "AI is thinking...", sizeof(s_ai_summary) - 1);
                epaper_draw_page();
                break;

            case EVT_AI_SPEAKING:
                strncpy(s_ai_summary, "AI is speaking...", sizeof(s_ai_summary) - 1);
                epaper_draw_page();
                break;

            case EVT_EPAPER_SHOW_QR:
                s_page = EPAPER_PAGE_QR;
                epaper_draw_page();
                break;

            default:
                break;
            }
        }
    }
}

void epaper_ui_start(void)
{
    s_epaper_q = app_event_bus_register("epaper", 16);

    /*
     * epaper_driver_init();
     */

    xTaskCreate(epaper_task, "epaper_task", 8192, NULL, 4, NULL);
}

十三、CozyLife 状态模型

AI 音箱可以作为 CozyLife 生态中的"显示 + 语音 + 防丢状态聚合终端"。

复制代码
typedef struct {
    char device_id[32];
    bool online;
    int volume;
    bool ai_enabled;
    bool alarm_enabled;
    bool findmy_bound;
    bool google_bound;
    bool lost_mode;
    int tag_battery;
    char epaper_page[16];
    char scene_name[32];
} cozy_shadow_t;

static cozy_shadow_t s_shadow = {
    .device_id = "sibo_epaper_speaker_001",
    .online = false,
    .volume = 45,
    .ai_enabled = true,
    .alarm_enabled = false,
    .findmy_bound = false,
    .google_bound = false,
    .lost_mode = false,
    .tag_battery = 100,
    .epaper_page = "home",
    .scene_name = "Home"
};

状态上报 JSON:

复制代码
#include "cJSON.h"

static char *cozy_build_status_json(void)
{
    cJSON *root = cJSON_CreateObject();

    cJSON_AddStringToObject(root, "device_id", s_shadow.device_id);
    cJSON_AddBoolToObject(root, "online", s_shadow.online);
    cJSON_AddNumberToObject(root, "volume", s_shadow.volume);
    cJSON_AddBoolToObject(root, "ai_enabled", s_shadow.ai_enabled);
    cJSON_AddBoolToObject(root, "alarm_enabled", s_shadow.alarm_enabled);
    cJSON_AddBoolToObject(root, "findmy_bound", s_shadow.findmy_bound);
    cJSON_AddBoolToObject(root, "google_bound", s_shadow.google_bound);
    cJSON_AddBoolToObject(root, "lost_mode", s_shadow.lost_mode);
    cJSON_AddNumberToObject(root, "tag_battery", s_shadow.tag_battery);
    cJSON_AddStringToObject(root, "epaper_page", s_shadow.epaper_page);
    cJSON_AddStringToObject(root, "scene_name", s_shadow.scene_name);

    char *json = cJSON_PrintUnformatted(root);
    cJSON_Delete(root);

    return json;
}

CozyLife 命令解析:

复制代码
static void cozy_handle_cmd(const char *json)
{
    cJSON *root = cJSON_Parse(json);
    if (!root) {
        return;
    }

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

    if (!cJSON_IsString(cmd)) {
        cJSON_Delete(root);
        return;
    }

    if (!strcmp(cmd->valuestring, "set_volume")) {
        if (cJSON_IsNumber(value)) {
            s_shadow.volume = value->valueint;
            app_event_post(EVT_KEY_VOL_UP, s_shadow.volume);
        }
    } else if (!strcmp(cmd->valuestring, "show_qr")) {
        app_event_post(EVT_EPAPER_SHOW_QR, 0);
    } else if (!strcmp(cmd->valuestring, "next_page")) {
        app_event_post(EVT_EPAPER_NEXT_PAGE, 0);
    } else if (!strcmp(cmd->valuestring, "find_device")) {
        tag_start_buzzer();
    } else if (!strcmp(cmd->valuestring, "stop_find_device")) {
        tag_stop_buzzer();
    } else if (!strcmp(cmd->valuestring, "enter_pair_mode")) {
        tag_enter_pair_mode();
    }

    cJSON_Delete(root);
}

十四、MCP 工具控制

MCP 用来把自然语言变成设备动作。

例如:

复制代码
"小博,帮我找一下这个音箱"
"小博,显示防丢状态"
"小博,把墨水屏切到日程页面"
"小博,打开回家模式"
"小博,显示配网二维码"

MCP 工具注册

复制代码
#include "driver/uart.h"
#include "esp_log.h"
#include <string.h>

#define MCP_UART_NUM          UART_NUM_2
#define MCP_UART_BAUD         115200
#define MCP_RX_BUF            512

static const char *TAG = "mcp_tools";

static void mcp_send_at(const char *cmd)
{
    uart_write_bytes(MCP_UART_NUM, cmd, strlen(cmd));
    uart_write_bytes(MCP_UART_NUM, "\r\n", 2);
    ESP_LOGI(TAG, "AT>> %s", cmd);
}

static void mcp_register_tools(void)
{
    mcp_send_at("AT");
    mcp_send_at("AT+CONNECT");

    mcp_send_at("AT+ADDMCP=0,show_home,显示首页,2,E1,00");
    mcp_send_at("AT+ADDMCP=0,show_tag_status,显示防丢状态,2,E1,01");
    mcp_send_at("AT+ADDMCP=0,show_qr,显示配网二维码,2,E1,02");

    mcp_send_at("AT+ADDMCP=0,find_speaker,查找音箱,2,F1,01");
    mcp_send_at("AT+ADDMCP=0,stop_find_speaker,停止查找音箱,2,F1,00");

    mcp_send_at("AT+ADDMCP=1,set_volume,设置音量,F2,1,V");
    mcp_send_at("AT+ADDMCP=0,scene_home,打开回家模式,2,F3,01");
    mcp_send_at("AT+ADDMCP=0,scene_sleep,打开睡眠模式,2,F3,02");
}

MCP 命令解析

复制代码
static void handle_mcp_frame(const uint8_t *buf, int len)
{
    if (len < 5) {
        return;
    }

    if (buf[0] != 0x55 || buf[1] != 0xAA) {
        return;
    }

    uint8_t cmd = buf[3];

    switch (cmd) {
    case 0xE1:
        if (buf[4] == 0x00) {
            app_event_post(EVT_EPAPER_REFRESH, 0);
        } else if (buf[4] == 0x01) {
            app_event_post(EVT_EPAPER_NEXT_PAGE, 1);
        } else if (buf[4] == 0x02) {
            app_event_post(EVT_EPAPER_SHOW_QR, 0);
        }
        break;

    case 0xF1:
        if (buf[4] == 0x01) {
            tag_start_buzzer();
        } else {
            tag_stop_buzzer();
        }
        break;

    case 0xF2:
        /*
         * buf[4] = volume
         */
        break;

    case 0xF3:
        /*
         * buf[4] = scene id
         */
        app_event_post(EVT_COZY_SCENE_CHANGED, buf[4]);
        break;

    case 0xFC:
        mcp_register_tools();
        break;

    default:
        ESP_LOGW(TAG, "unknown mcp cmd=0x%02X", cmd);
        break;
    }
}

十五、AI WebSocket 客户端

设备通过 WebSocket 连接后端,后端负责 ASR、LLM、TTS、RAG 和 Voice Clone。

复制代码
#include <string.h>
#include "esp_websocket_client.h"
#include "esp_log.h"
#include "cJSON.h"
#include "app_event_bus.h"
#include "app_config.h"

static const char *TAG = "ai_ws";
static esp_websocket_client_handle_t s_ws;

static void ai_handle_json(const char *data, int len)
{
    cJSON *root = cJSON_ParseWithLength(data, len);
    if (!root) {
        return;
    }

    cJSON *type = cJSON_GetObjectItem(root, "type");

    if (cJSON_IsString(type)) {
        if (!strcmp(type->valuestring, "idle")) {
            app_event_post(EVT_AI_IDLE, 0);
        } else if (!strcmp(type->valuestring, "listening")) {
            app_event_post(EVT_AI_LISTENING, 0);
        } else if (!strcmp(type->valuestring, "thinking")) {
            app_event_post(EVT_AI_THINKING, 0);
        } else if (!strcmp(type->valuestring, "speaking")) {
            app_event_post(EVT_AI_SPEAKING, 0);
        } else if (!strcmp(type->valuestring, "interrupted")) {
            app_event_post(EVT_AI_INTERRUPTED, 0);
        }
    }

    cJSON_Delete(root);
}

static void ws_event_handler(void *args,
                             esp_event_base_t base,
                             int32_t event_id,
                             void *event_data)
{
    esp_websocket_event_data_t *d = (esp_websocket_event_data_t *)event_data;

    switch (event_id) {
    case WEBSOCKET_EVENT_CONNECTED:
        ESP_LOGI(TAG, "AI websocket connected");

        esp_websocket_client_send_text(
            s_ws,
            "{\"type\":\"hello\",\"product\":\"SIBO_AI_EPAPER_SPEAKER\"}",
            strlen("{\"type\":\"hello\",\"product\":\"SIBO_AI_EPAPER_SPEAKER\"}"),
            portMAX_DELAY
        );
        break;

    case WEBSOCKET_EVENT_DATA:
        if (d->op_code == 0x1) {
            ai_handle_json(d->data_ptr, d->data_len);
        } else if (d->op_code == 0x2) {
            /*
             * TTS 音频流:
             * audio_play_write(d->data_ptr, d->data_len);
             */
        }
        break;

    case WEBSOCKET_EVENT_DISCONNECTED:
        ESP_LOGW(TAG, "AI websocket disconnected");
        app_event_post(EVT_AI_IDLE, 0);
        break;

    default:
        break;
    }
}

void ai_ws_send_pcm(const uint8_t *data, size_t len)
{
    if (s_ws && esp_websocket_client_is_connected(s_ws)) {
        esp_websocket_client_send_bin(s_ws, (const char *)data, len, 0);
    }
}

void ai_ws_client_start(void)
{
    esp_websocket_client_config_t cfg = {
        .uri = AI_WS_URL,
        .reconnect_timeout_ms = 3000,
        .network_timeout_ms = 5000,
    };

    s_ws = esp_websocket_client_init(&cfg);

    esp_websocket_register_events(
        s_ws,
        WEBSOCKET_EVENT_ANY,
        ws_event_handler,
        NULL
    );

    esp_websocket_client_start(s_ws);
}

十六、CozyLife APP 配置字段

复制代码
{
  "device_id": "sibo_epaper_speaker_001",
  "name": "四博AI墨水屏音箱",
  "type": "ai_epaper_speaker",
  "features": {
    "ai": true,
    "epaper": true,
    "findmy": true,
    "google_finder": true,
    "bluetooth_speaker": true,
    "voice_clone": true,
    "voiceprint": true
  },
  "state": {
    "online": true,
    "volume": 45,
    "epaper_page": "home",
    "findmy_bound": true,
    "google_bound": true,
    "lost_mode": false,
    "tag_battery": 86
  },
  "actions": [
    "show_home",
    "show_tag_status",
    "show_qr",
    "find_speaker",
    "stop_find_speaker",
    "set_volume",
    "scene_home",
    "scene_sleep"
  ]
}

十七、低功耗策略

复制代码
1. 墨水屏只在状态变化时刷新
2. AI 空闲时降低麦克风采样频率
3. 防丢模块独立低功耗运行
4. ESP32-S3 根据供电状态进入 light sleep
5. Wi-Fi 保持低频上报 CozyLife 状态
6. Find My / Google 广播由独立防丢模块负责
7. 蜂鸣器只在寻物时工作
8. OTA 和墨水屏全刷避免同时执行,降低峰值电流

十八、量产测试项目

复制代码
FACTORY_TEST_AUDIO_IN        麦克风采集测试
FACTORY_TEST_AUDIO_OUT       喇叭播放测试
FACTORY_TEST_EPAPER          墨水屏黑白刷新测试
FACTORY_TEST_KEY             按键测试
FACTORY_TEST_RGB             RGB 灯测试
FACTORY_TEST_TAG_UART        防丢模块 UART 通信测试
FACTORY_TEST_FINDMY_BIND     Find My 绑定状态检测
FACTORY_TEST_GOOGLE_BIND     Google 绑定状态检测
FACTORY_TEST_BUZZER          蜂鸣器测试
FACTORY_TEST_WIFI            Wi-Fi RSSI 测试
FACTORY_TEST_BLE             BLE 广播测试
FACTORY_WRITE_SN             写入 SN
FACTORY_WRITE_CERT           写入证书
FACTORY_OTA_TEST             OTA 测试

十九、总结

该方案的核心不是单纯做一个 AI 音箱,而是将 AI 语音、CozyLife 智能家居、墨水屏常显、Apple Find My、Google 防丢 组合成一个桌面智能终端。

最终产品能力可以总结为:

复制代码
AI 大模型语音交互
+ CozyLife APP / 小程序控制
+ 墨水屏低功耗常显
+ 蓝牙音箱
+ 闹钟提醒
+ 声音克隆
+ 声纹识别
+ Apple Find My 防丢
+ Google 防丢
+ MCP 自然语言控制
= 四博 AI 墨水屏双防丢智能音箱

推荐落地架构:

复制代码
ESP32-S3:
负责 AI、CozyLife、墨水屏、音频和系统逻辑。

独立防丢模块:
负责 Apple Find My / Google 防丢协议和 BLE 广播。

CozyLife APP:
负责用户配置、状态查看、场景联动和设备管理。

AI 后端:
负责语音理解、TTS、声音克隆、RAG 和 MCP 工具调用。

这种架构边界清晰,认证风险低,固件维护简单,也更适合后续做多 SKU 量产。

相关推荐
easyllm1 小时前
【无标题】
人工智能
人工智能培训1 小时前
如何定义和测量“通用具身智能”
大数据·人工智能·机器学习·prompt·agent
高洁011 小时前
知识图谱与检索增强的实战结合
人工智能·深度学习·数据挖掘·transformer·知识图谱
跨境数据猎手1 小时前
1688 以图搜货 API(item_search_img)开发
人工智能
深度学习lover1 小时前
<数据集>yolo 车牌识别<目标检测>
人工智能·python·yolo·目标检测·计算机视觉·车牌识别
研究点啥好呢1 小时前
Muses | 搭建属于你自己的AI生图网站
前端·人工智能·ai·github
PhotonixBay1 小时前
激光共聚焦显微镜如何实现CVD石墨烯实时质量控制
人工智能·测试工具
Agent手记1 小时前
多渠道订单数据处理自动化,落地步骤与ERP打通方案 | 2026企业级智能体实战手册
运维·人工智能·ai·自动化
ZPC82101 小时前
规划后的轨迹,如何发给 moveit_servo 执行
c++·人工智能·算法·3d