小智AI接入萤石云摄像头:MQTT 实时告警 + 图片预览

让小智AI/ESP32 设备秒变智能安防终端——门口摄像头检测到有人出现时,屏幕自动弹出告警消息、播放提示音,还能下载并显示现场抓拍图片。


整体架构

萤石摄像头(门铃)
    │
    ├─→ 萤石开放平台 ←─ 注册设备、开启告警推送
    │        │
    │        ├─→ 告警事件推送 → 消息回调服务 → 发布到 MQTT Broker
    │        │                       │
    │        │                       ├─→ 飞书机器人 ←─ 手机端实时查看
    │        │                       │
    │        │                       └─→ 小智AI(ESP32) ←─ MQTT 订阅
    │        │                               │
    │        │                               ├─ 屏幕显示告警气泡
    │        │                               ├─ 播放提示音
    │        │                               └─ 下载并显示抓拍图片

步骤一:门铃接入萤石开放平台

1. 注册萤石开放平台账号

访问 萤石开放平台,注册并登录开发者账号。

2. 添加设备

  • 在萤石 APP 中确保门铃已联网并正常工作
  • 在开放平台「我的资源」中添加设备,或扫描设备二维码完成绑定

3. 创建应用获取 AppKey / AppSecret

  • 进入「我的应用」→ 创建应用
  • 获取 AppKeyAppSecret,后续 API 调用需要用到

4. 开启告警推送

  • 在「告警服务」中开启设备的移动侦测/门铃事件推送
  • 配置告警推送地址为你的服务器回调 URL

步骤二:部署消息回调服务

萤石开放平台会将告警事件以 HTTP POST 方式推送到你的服务器,需要部署一个回调服务来接收并转发。

服务功能

萤石告警推送 → 回调服务解析 → 提取消息文本和图片URL
                    │
                    ├─→ 发布到 MQTT Broker (qiongmu/mqtt/ys)
                    └─→ 推送飞书机器人 webhook

部署方式

  • 服务器:任意 Linux 服务器(阿里云、腾讯云等)
  • HTTPS:萤石回调要求 HTTPS,可用 Nginx 反向代理 + Let’s Encrypt 证书
  • 保活:使用 systemdsupervisor 管理进程

步骤三:接入飞书机器人

1. 创建飞书自定义机器人

  • 打开飞书群聊 → 设置 → 群机器人 → 添加机器人 → 自定义机器人
  • 获取 Webhook 地址

2. 配置回调服务

在上面的回调服务中配置 FEISHU_WEBHOOK,告警消息会同时推送到飞书:

🚨 门口有人出现!
📷 抓拍: https://i.ys7.com/streamer/alarm/url/get?fileId=...

3. 效果

  • 手机端:飞书 APP 收到群消息推送,随时查看
  • 带图片:点击链接可查看抓拍图片
  • 可配合飞书自动化:设置特别关注、DND 时段等

步骤四:小智接入 MQTT 实现告警

设备端实现

在小智AI(ESP32-S3)上运行 MqttListener,独立订阅 MQTT 告警主题:

// mqtt_listener.h - 独立 MQTT 客户端
class MqttListener {
    // TLS 连接 emqxsl.cn:8883
    // 订阅 qiongmu/mqtt/ys
    static MqttListener& GetInstance();
    void Initialize();
    void Start();
};

Application::Initialize() 中初始化:

auto& mqtt_listener = MqttListener::GetInstance();
mqtt_listener.SetMessageCallback([this](const std::string& topic, const std::string& payload) {
    OnMqttMessage(payload);
});
mqtt_listener.Initialize();
// 网络激活后启动
mqtt_listener.Start();

消息处理

MQTT 消息通过事件队列进入主事件循环:

MQTT 收到 → 入队 → MAIN_EVENT_MQTT_MESSAGE
     ↓
HandleMqttMessageEvent()
     ↓
├── 解析 JSON → msg + pic
├── SetChatMessage("system", msg) → 显示气泡消息
├── PlaySound(OGG_SUCCESS)        → 播放提示音(3次)
└── xTaskCreate()                 → 后台下载图片

图片下载与显示

完整流程

HTTP GET → 跟随 302 重定向(最多3次) → 下载 JPEG
     ↓
jpeg_to_image() → 解码为 RGB565
     ↓
最近邻缩放至 320×320(保持宽高比)
     ↓
LvglAllocatedImage → SetPreviewImage() 显示

重定向处理

萤石云图片 URL 返回 302 跳转到 AWS S3:

while (redirect_count < 3) {
    auto http = Board::GetInstance().GetNetwork()->CreateHttp(3);
    http->SetKeepAlive(false);  // 禁用连接复用
    
    http->Open("GET", current_url);
    if (status_code == 302) {
        std::string location = http->GetResponseHeader("Location");
        http->Close();
        current_url = location;
        redirect_count++;
        vTaskDelay(pdMS_TO_TICKS(200));
        continue;
    }
    // 下载并解码...
}

JPEG 解码 + 缩放

使用 ESP-IDF 自带的 JPEG 解码器(优先硬件加速),解码后缩放到 320×320:

// JPEG 解码
uint8_t* rgb565 = nullptr;
esp_err_t err = jpeg_to_image(jpeg_data, content_length,
    &rgb565, &rgb_len, &img_width, &img_height, &img_stride);

// 缩放到 320×320(保持宽高比)
const int MAX_DIM = 320;
if (img_width > MAX_DIM || img_height > MAX_DIM) {
    int scale_w = (img_width > img_height) ? MAX_DIM : (MAX_DIM * img_width / img_height);
    int scale_h = (img_height > img_width) ? MAX_DIM : (MAX_DIM * img_height / img_width);
    uint8_t* scaled = heap_caps_malloc(scale_w * scale_h * 2, MALLOC_CAP_SPIRAM);
    downscale_rgb565((uint16_t*)rgb565, img_width, img_height, scaled, scale_w, scale_h);
    heap_caps_free(rgb565);  // 立即释放原始大图
}

内存对比

原始分辨率 解码后 缩放后
1920×1080 ~4MB ~200KB
1280×720 ~1.8MB ~112KB
640×480 ~600KB ~150KB

内存保护

ESP32 内存有限,下载前检查可用堆大小:

if (esp_get_free_heap_size() < 80 * 1024) {
    ESP_LOGW(TAG, "Skipping image download, low memory (%zu KB)", 
             esp_get_free_heap_size() / 1024);
} else {
    xTaskCreate(..., "mqtt_img", 4096 * 2, ...);
}

消息气泡持久化

移除了所有 SetChatMessage("system", "") 空内容调用,让 MQTT 消息在聊天区永久显示,直到:

  • 消息数量超过 MAX_MESSAGES(默认 20 条)自动挤掉最旧的
  • 新对话开始时清除


最终效果

终端 效果
小智AI 屏幕弹出告警气泡 + 提示音 + 抓拍图片
飞书APP 随时随地查看门铃动态

涉及的文件

文件 改动
main/CMakeLists.txt 添加 mqtt_listener.cc
main/application.cc MQTT 事件处理、图片下载显示
main/application.h MQTT 事件定义、队列、互斥锁
main/mqtt_listener.cc 新增,独立 MQTT 客户端
main/mqtt_listener.h 新增,MQTT 监听器接口

后续计划

  • 图片点击放大全屏查看
  • 支持更多图片格式(PNG/WebP)
  • MQTT 告警消息数量统计
  • 语音播报告警内容
  • 飞书机器人交互式按钮(静音时段、确认已处理等)

项目:xiaozhi-esp32
作者:qiongmu.tech

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐