MQTT 图像传输
MQTT 图像传输
本节详细介绍如何通过 MQTT 高效传输 ESP32-CAM 拍摄的图片。学习完成后,您将能够:
- 配置 MQTT 参数以支持大文件传输
- 实现可靠的大文件 MQTT 传输
- 处理传输失败和重试
- 监控传输性能和成功率
MQTT 传输架构
Section titled “MQTT 传输架构”┌─────────────────────────────────────────────────────────────┐│ 图片传输流程 │├─────────────────────────────────────────────────────────────┤│ ││ [ESP32-CAM] ││ │ ││ 1. 拍照 → 获取 JPEG 缓冲区 ││ 2. 检查缓冲区大小 ││ 3. 是否需要分块? ││ ├── 否: 直接发布二进制 ││ └── 是: 分块发送 + 序号 ││ │ ││ ▼ ││ [Mosquitto Broker] ││ │ ││ ▼ ││ [Node-RED] ││ 1. 接收图片数据 ││ 2. 重组/验证完整性 ││ 3. 保存/显示/推送 ││ │└─────────────────────────────────────────────────────────────┘MQTT Configuration for Image Transfer
Section titled “MQTT Configuration for Image Transfer”ESP32 PubSubClient 配置
Section titled “ESP32 PubSubClient 配置”// build_flags =// -DMQTT_MAX_PACKET_SIZE=60000// -DMQTT_KEEPALIVE=120
// 设置 MQTT 客户端缓冲void setupMQTT() { client.setBufferSize(60000); // 60KB 缓冲 client.setServer(mqtt_server, mqtt_port); client.setCallback(callback);}Mosquitto Broker 配置
Section titled “Mosquitto Broker 配置”# 允许的最大包大小 (默认 256MB)max_packet_size 100000000
# 消息持久化 (防止消息丢失)persistence truepersistence_location /mosquitto/data/
# 增加 QoS 1/2 的消息重试间隔max_queued_messages 1000Direct Binary Transfer
Section titled “Direct Binary Transfer”ESP32 端
Section titled “ESP32 端”// 直接发布二进制图片数据void sendPhotoBinary() { camera_fb_t* fb = esp_camera_fb_get(); if (!fb) { Serial.println("Camera capture failed!"); client.publish("esp32cam/status", "error:capture"); return; }
// 检查缓冲区是否足够 if (fb->len > 60000) { Serial.printf("Photo too large: %zu bytes, use chunked mode\n", fb->len); sendPhotoChunked(fb); return; }
// 发布二进制数据 client.publish("esp32cam/photo", fb->buf, // 数据指针 fb->len, // 数据长度 false); // retain
Serial.printf("Binary photo sent: %zu bytes\n", fb->len);
// 发布元数据到状态 Topic String meta = "{\"size\":" + String(fb->len) + ",\"width\":640,\"height\":480}"; client.publish("esp32cam/photo_meta", meta.c_str());
esp_camera_fb_return(fb);}Node-RED 端接收
Section titled “Node-RED 端接收”[MQTT In: esp32cam/photo] ──→ [Switch: QoS] ──→ [Write File: 保存] │ └──→ [Image Output: 显示]Chunked Transfer
Section titled “Chunked Transfer”ESP32 分块传输
Section titled “ESP32 分块传输”// 大图片分块传输void sendPhotoChunked(camera_fb_t* fb) { int chunkSize = 40000; // 每块 40KB (留余量) int totalChunks = (fb->len + chunkSize - 1) / chunkSize; String photoId = String(millis());
Serial.printf("Sending %d chunks, total %zu bytes\n", totalChunks, fb->len);
for (int i = 0; i < totalChunks; i++) { int offset = i * chunkSize; int size = min(chunkSize, (int)(fb->len - offset));
// 构建分块消息 (JSON) String chunk = "{\"id\":\"" + photoId + "\","; chunk += "\"seq\":" + String(i) + ","; chunk += "\"total\":" + String(totalChunks) + ","; chunk += "\"size\":" + String(fb->len) + "}";
// 分块数据发布到不同的 Topic String dataTopic = "esp32cam/photo_chunk/" + String(i); client.publish(dataTopic.c_str(), fb->buf + offset, size, false);
delay(50); // 块间延迟,避免填满缓冲 }
// 发送完成信号 String done = "{\"id\":\"" + photoId + "\",\"status\":\"complete\"}"; client.publish("esp32cam/photo_status", done.c_str());
Serial.println("All chunks sent");}Node-RED 分块重组
Section titled “Node-RED 分块重组”// Function: 重组分块图片// 接收各个分块 Topic 的消息并重组
var chunkData = msg.payload;var topic = msg.topic;
// 提取块序号: esp32cam/photo_chunk/0 → 0var chunkIndex = parseInt(topic.split('/').pop());
// 使用 Flow Context 缓存块数据var buffer = flow.get("photoBuffer") || {};var photoMeta = flow.get("photoMeta") || {};
// 首次接收,初始化if (!buffer.total) { buffer = { chunks: [], total: 0, received: 0, size: 0, startTime: Date.now() };}
// 存储块数据buffer.chunks[chunkIndex] = chunkData;buffer.received++;buffer.size += chunkData.length;
flow.set("photoBuffer", buffer);
// 检查是否接收完成// 需要知道总块数... 这里简化处理// 实际实现应通过元数据 Topic 获取总块数if (buffer.received >= buffer.total) { // 合并所有块 var fullImage = Buffer.concat(buffer.chunks);
msg.payload = fullImage; msg.filename = "/data/photos/photo_" + Date.now() + ".jpg";
// 清除缓存 flow.set("photoBuffer", null);
return msg; // 传递给 Write File 节点}
return null; // 等待更多块Transfer Reliability
Section titled “Transfer Reliability”// Node-RED: 发送接收确认// ESP32 发布图片后,Node-RED 回复确认
// Function: 发送确认消息msg.topic = "esp32cam/ack";msg.payload = JSON.stringify({ id: msg.payload.photo_id || "", status: "received", size: msg.payload.length || 0, timestamp: Date.now()});return msg;超时重传 (ESP32)
Section titled “超时重传 (ESP32)”// ESP32: 等待确认,超时重传void sendPhotoWithAck() { for (int retry = 0; retry < 3; retry++) { sendPhotoBinary();
// 等待确认 (非阻塞方式) unsigned long timeout = millis() + 5000; // 5秒超时 while (millis() < timeout) { client.loop(); if (photoAcknowledged) { Serial.println("Photo acknowledged by server"); return; } }
Serial.printf("Retry %d: no acknowledgment\n", retry + 1); }
Serial.println("Photo send failed after 3 retries"); client.publish("esp32cam/status", "error:send_failed");}Performance Monitoring
Section titled “Performance Monitoring”// Node-RED: 监控图片传输性能// Function: 记录传输统计
var stats = context.get("photoStats") || { total: 0, success: 0, failed: 0, totalBytes: 0, avgTime: 0};
stats.total++;stats.totalBytes += msg.payload.length || 0;
// 记录传输时间 (假设 ESP32 在消息中附带时间戳)var sentTime = msg.payload.timestamp || 0;if (sentTime > 0) { var transferTime = Date.now() - sentTime; stats.avgTime = (stats.avgTime * (stats.total - 1) + transferTime) / stats.total;}
context.set("photoStats", stats);
// 每小时重置计数器// 通过 Inject 节点定时报告# 监控图片传输mosquitto_sub -t "esp32cam/photo" -C 1 > received_photo.jpgls -la received_photo.jpg
# 检查传输统计mosquitto_sub -t "esp32cam/status" -v
# 测试大图片分块mosquitto_pub -t "esp32cam/command" -m "take_photo_hires"Common Customer Questions
Section titled “Common Customer Questions”Q1: MQTT 传输图片的最大大小限制?
Section titled “Q1: MQTT 传输图片的最大大小限制?”理论最大 256MB (Mosquitto 默认),但实际受限于 ESP32 内存 (60KB Max) 和网络稳定性。建议单张图片不超过 50KB (VGA JPEG)。
Q2: Wi-Fi 断开时图片会丢失吗?
Section titled “Q2: Wi-Fi 断开时图片会丢失吗?”是的,MQTT QoS 1/2 在客户端断开时无法保证投递。建议 ESP32 在拍照后暂存到 SD 卡,网络恢复后补传。
Q3: 传输 100 张图片需要多久?
Section titled “Q3: 传输 100 张图片需要多久?”VGA JPEG (~30KB) 单张传输约 1-3 秒。100 张约 2-5 分钟,取决于 Wi-Fi 质量和分块策略。
✅ 推荐做法:
- 限制单张图片大小在 50KB 以内
- 大图片使用分块传输 (每块 ≤ 40KB)
- 实现接收确认和超时重传机制
- 监控传输成功率
❌ 避免做法:
- 单条 MQTT 消息超过 60KB (PubSubClient 限制)
- 无确认机制盲目发送
- 传输过程中不检查 MQTT 连接状态
- 忽略分块传输的块序号验证
Summary
Section titled “Summary”- 二进制传输直接发送 JPEG 缓冲,效率最高
- 分块传输解决大图片单次发送内存不足
- QoS 1 确保图片消息至少投递一次
- 确认机制验证图片接收完整性
- 重试机制处理网络不稳定导致的传输失败