跳转到内容

通过 MQTT 拍照

通过 MQTT 拍照

本节介绍如何通过 MQTT 消息远程触发 ESP32-CAM 拍照。学习完成后,您将能够:

  • 在 ESP32 上实现 MQTT 拍照控制
  • 通过 Node-RED 发送拍照命令
  • 控制闪光灯和拍照参数
  • 构建远程巡检的拍照触发流程
┌────────────────────────────────────────────────────────────┐
│ 远程拍照控制流程 │
├────────────────────────────────────────────────────────────┤
│ │
│ [Node-RED] │
│ │ │
│ │ MQTT: esp32cam/command (拍照/闪光灯控制) │
│ ▼ │
│ [Mosquitto Broker] │
│ │ │
│ ▼ │
│ [ESP32-CAM] │
│ │ │
│ ├──→ 拍照 → MQTT: esp32cam/photo (图片数据) │
│ │ │
│ └──→ 控制闪光灯 │
│ │
│ Node-RED 接收图片 → 保存/显示/推送 │
│ │
└────────────────────────────────────────────────────────────┘
#include <WiFi.h>
#include <PubSubClient.h>
#include "esp_camera.h"
// Wi-Fi 配置
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";
// MQTT 配置
const char* mqtt_server = "192.168.1.100";
const int mqtt_port = 1883;
const char* mqtt_user = "iot_user";
const char* mqtt_pass = "iot_password";
const char* client_id = "esp32cam-01";
// MQTT Topic
const char* topic_command = "esp32cam/command";
const char* topic_photo = "esp32cam/photo";
const char* topic_status = "esp32cam/status";
// MQTT 客户端
WiFiClient espClient;
PubSubClient client(espClient);
// 闪光灯控制变量
bool flashEnabled = false;
int flashPin = 4; // ESP32-CAM 内置 LED
// MQTT 回调函数
void callback(char* topic, byte* payload, unsigned int length) {
String message;
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.println("MQTT command received: " + message);
if (message == "take_photo") {
takePhoto();
} else if (message == "flash_on") {
flashEnabled = true;
digitalWrite(flashPin, HIGH);
client.publish(topic_status, "flash_on");
} else if (message == "flash_off") {
flashEnabled = false;
digitalWrite(flashPin, LOW);
client.publish(topic_status, "flash_off");
} else if (message == "flash_toggle") {
flashEnabled = !flashEnabled;
digitalWrite(flashPin, flashEnabled ? HIGH : LOW);
client.publish(topic_status, flashEnabled ? "flash_on" : "flash_off");
}
}
// 拍照函数
void takePhoto() {
Serial.println("Taking photo...");
// 如果闪光灯开启,先点亮
if (flashEnabled) {
digitalWrite(flashPin, HIGH);
delay(200); // 等待闪光灯稳定
}
// 拍照
camera_fb_t* fb = esp_camera_fb_get();
if (!fb) {
Serial.println("Camera capture failed!");
client.publish(topic_status, "error:capture_failed");
if (flashEnabled) digitalWrite(flashPin, LOW);
return;
}
Serial.printf("Photo taken: %zu bytes\n", fb->len);
// 通过 MQTT 发送图片
if (client.connected()) {
// 发送图片数据 (二进制)
bool sent = client.publish(topic_photo, fb->buf, fb->len, false);
if (sent) {
Serial.println("Photo sent via MQTT");
client.publish(topic_status, "photo_sent");
} else {
Serial.println("MQTT publish failed!");
client.publish(topic_status, "error:publish_failed");
}
}
// 关闭闪光灯
if (flashEnabled) {
digitalWrite(flashPin, LOW);
}
// 释放帧缓冲
esp_camera_fb_return(fb);
}
void setup() {
Serial.begin(115200);
// 初始化 Flash LED
pinMode(flashPin, OUTPUT);
digitalWrite(flashPin, LOW);
// 初始化摄像头 (见 11-02)
camera_config_t config;
// ... (完整的摄像头初始化代码)
esp_camera_init(&config);
// 连接 Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
Serial.println("WiFi connected");
// 连接 MQTT
client.setServer(mqtt_server, mqtt_port);
client.setCallback(callback);
connectMQTT();
Serial.println("ESP32-CAM ready for MQTT commands");
}
void connectMQTT() {
while (!client.connected()) {
if (client.connect(client_id, mqtt_user, mqtt_pass)) {
Serial.println("MQTT connected");
client.subscribe(topic_command);
client.publish(topic_status, "online");
} else {
delay(5000);
}
}
}
void loop() {
if (!client.connected()) {
connectMQTT();
}
client.loop();
}
[Inject: 手动触发] ──→ [MQTT Out: esp32cam/command]
[Inject: 定时拍照] ──→ [MQTT Out: esp32cam/command]
// Inject 节点 - 手动拍照
{
"name": "拍照",
"payload": "take_photo",
"payloadType": "str",
"repeat": ""
}
// Inject 节点 - 定时拍照 (每 30 分钟)
{
"name": "定时巡检",
"payload": "take_photo",
"payloadType": "str",
"repeat": "cron",
"cron": "*/30 * * * *"
}
// Inject 节点 - 开启闪光灯
{
"name": "开闪光灯",
"payload": "flash_on",
"payloadType": "str"
}
// MQTT Out 节点
{
"name": "ESP32-CAM命令",
"topic": "esp32cam/command",
"qos": 1,
"broker": "local-broker"
}
┌──────────────────────────────────────────────┐
│ ESP32-CAM 远程拍照控制面板 │
├──────────────────────────────────────────────┤
│ │
│ [📷 拍照] [💡 闪光灯] [⏰ 定时(30分)] │
│ │
│ 状态: 🟢 在线 │
│ 最后拍照: 2026-05-18 14:30:25 │
│ 照片大小: 32.5 KB │
│ │
└──────────────────────────────────────────────┘

由于图片数据较大,需要增加 MQTT 缓冲大小:

// 在 setup() 中设置最大包大小
#define PHOTO_BUFFER_SIZE 60000 // 60KB
// 在 MQTT 连接后设置
void setup() {
// ...
client.setBufferSize(PHOTO_BUFFER_SIZE);
// ...
}
// PubSubClient 默认 256 字节,需要增大
// 在 platformio.ini 中:
// build_flags = -DMQTT_MAX_PACKET_SIZE=60000
Terminal window
# 测试拍照命令
mosquitto_pub -t "esp32cam/command" -m "take_photo"
# 检查照片是否发布
mosquitto_sub -t "esp32cam/photo" -C 1 > photo.jpg
# 检查图片大小和完整性
ls -la photo.jpg
file photo.jpg

VGA (640×480) JPEG 约 30-50KB,通过 MQTT 传输约 1-3 秒(取决于 Wi-Fi 质量)。如果需要更快传输,可降低分辨率或 JPEG 质量。

可以,但每次拍照后需要等待前一张图片发送完成。建议拍照间隔至少 5-10 秒,避免 MQTT 缓冲溢出。

建议实现确认机制:ESP32 发送图片后等待 Node-RED 确认。如果超时未确认,重试发送。

推荐做法:

  • MQTT 缓冲大小设置为 60000 (60KB)
  • 拍照命令使用 QoS 1 确保可靠投递
  • 拍照后释放帧缓冲避免内存泄漏
  • 使用状态 Topic 反馈拍照结果

避免做法:

  • 默认 MQTT 缓冲 (256 字节) 传输图片
  • 在中断中调用 MQTT 发布函数
  • 拍照频率过高导致 MQTT 队列溢出
  • 忽略图片传输确认机制
  1. MQTT 拍照控制通过 esp32cam/command Topic 远程触发拍照
  2. 图片数据通过 esp32cam/photo Topic 以二进制形式传输
  3. MQTT 缓冲需要设置为至少 60KB 以传输图片
  4. 闪光灯控制和拍照可独立控制
  5. Node-RED 可通过 Inject 节点实现手动或定时拍照