ESP32向AWS IoT Core发送MQTT仅首条成功,问题出在哪?
ESP32-CAM AWS IoT消息截断问题排查与修复
问题现象
ESP32-CAM捕获图像转Base64后通过Pubsubclient发送至AWS IoT Core,首次烧录后第一条消息完整发送成功,但后续所有消息均被截断(缺失闭合引号、大括号,附带多余空白),重置设备也无法解决,仅首条消息有效。设备与AWS连接状态正常,已尝试调整分片大小、设置setBufferSize,问题依旧。
原代码片段
void publishMessage() { camera_fb_t * fb = NULL; fb = esp_camera_fb_get(); if(!fb) { Serial.println("Camera capture failed"); return; } char *input = (char *)fb->buf; //AWS Lambda Actions need base64 encoded data, jsonified char output[base64_enc_len(3)]; String image; for (int i=0;i<fb->len;i++) { base64_encode(output, (input++), 3); if (i%3==0) image += String(output); } String imageFile = "{\"image\":\""+image+"\"}"; uint32_t fbLen=imageFile.length(); if(!client.connected()) { Serial.println("Connecting to AWS IOT"); while (!client.connect(THINGNAME)) { Serial.print("."); delay(100); } if (!client.connected()) { Serial.println("AWS IoT Timeout!"); return; } } boolean res = client.beginPublish(AWS_IOT_PUBLISH_TOPIC, fbLen, false); Serial.println("publish began " + String(res)); for (uint32_t n=0;n<fbLen;n=n+2048) { String str = ""; if (n+2048<fbLen) { str = imageFile.substring(n, n+2048); client.write((uint8_t*)str.c_str(), 2048); } else if (fbLen%2048>0) { uint32_t remainder = fbLen%2048; str = imageFile.substring(n, n+remainder); client.write((uint8_t*)str.c_str(), remainder); } Serial.println(String(n)); } Serial.println("publish written " + String(res)); res = client.endPublish(); Serial.println("publish ended " + String(res)); esp_camera_fb_return(fb); }
问题根源分析
Base64编码逻辑错误
原循环按单字节i++遍历,每次调用base64_encode处理3字节,但仅当i%3==0时才将编码结果加入image字符串。这意味着每3次循环仅保留1次编码结果,其余2次的编码数据被覆盖丢失,导致最终Base64字符串不完整。首次运行可能因内存偶然状态侥幸凑成完整数据,后续运行则必然出现截断。output数组未预留终止符空间base64_enc_len(3)返回4(3字节Base64编码后为4字符),但定义char output[base64_enc_len(3)]时未预留字符串终止符\0的位置,String(output)会读取内存中后续的垃圾数据,导致消息出现多余空白和乱码。内存碎片累积
频繁使用String拼接和substring生成新字符串,会在ESP32的堆内存中产生大量碎片,后续运行时内存分配异常,加剧消息发送的不稳定。
修复方案
1. 修正后的完整代码
void publishMessage() { camera_fb_t * fb = NULL; fb = esp_camera_fb_get(); if(!fb) { Serial.println("Camera capture failed"); return; } char *input = (char *)fb->buf; // 修正Base64编码逻辑 char output[base64_enc_len(3) + 1]; // 预留字符串终止符空间 String image; int remaining = fb->len; while (remaining > 0) { int chunkSize = min(3, remaining); base64_encode(output, input, chunkSize); output[base64_enc_len(chunkSize)] = '\0'; // 添加终止符避免读取垃圾数据 image += String(output); input += chunkSize; remaining -= chunkSize; } String imageFile = "{\"image\":\""+image+"\"}"; uint32_t fbLen=imageFile.length(); if(!client.connected()) { Serial.println("Connecting to AWS IOT"); while (!client.connect(THINGNAME)) { Serial.print("."); delay(100); } if (!client.connected()) { Serial.println("AWS IoT Timeout!"); return; } } boolean res = client.beginPublish(AWS_IOT_PUBLISH_TOPIC, fbLen, false); Serial.println("publish began " + String(res)); // 优化分片发送:直接使用原字符串指针,避免生成新String const char* msgPtr = imageFile.c_str(); uint32_t sent = 0; while (sent < fbLen) { uint32_t sendSize = min(2048, fbLen - sent); client.write((uint8_t*)(msgPtr + sent), sendSize); sent += sendSize; Serial.println(String(sent)); } Serial.println("publish written " + String(res)); res = client.endPublish(); Serial.println("publish ended " + String(res)); esp_camera_fb_return(fb); }
2. 额外优化建议
- 增大Pubsubclient缓冲区:在初始化时设置
client.setBufferSize(65536);(根据实际消息大小调整,确保大于Base64 JSON的总长度)。 - 减少String依赖:对于大尺寸图像,可考虑逐段编码并直接发送,避免将完整Base64字符串存入内存,进一步降低内存碎片风险。
- 添加内存监控:在代码中加入
Serial.printf("Free heap: %d\n", esp_get_free_heap_size());,观察内存变化,排查内存泄漏问题。
内容的提问来源于stack exchange,提问作者David Buchs
相关产品推荐
相关产品推荐

