ESPAsyncWebServer中串口查询慢回调致ESP32崩溃的解决方案咨询
ESPAsyncWebServer中处理Serial异步查询的解决方案
问题场景
已知在ESPAsyncWebServer库的回调函数中延迟或yield是不可取的,但回调函数需通过Serial端口查询另一设备,该过程耗时超1秒,会导致ESP32崩溃。示例代码如下:
void getDeviceConfig(AsyncWebServerRequest *request) { AsyncResponseStream *response = request->beginResponseStream("application/json"); StaticJsonDocument<1024> doc; JsonArray array = doc.createNestedArray("get"); for (size_t i = 0; i < request->params(); i++) array.add(request->getParam(i)->value()); serializeJson(doc, Serial); /* At this point, the remote device determines what is being asked for and builds a response. This can take fair bit of time depending on what is being asked (>1sec) */ response->print(Serial.readStringUntil('\n')); request->send(response); }
曾尝试构建响应回调,但无法提前知晓远程设备生成的数据量;也试过使用分块响应,但无法告知调用方仍有数据未接收,只能返回0终止回调,求其他可行方案?
解决方案:异步Serial处理 + 请求上下文缓存
核心思路是把Web请求的上下文保存下来,将Serial的读写操作从Web回调中剥离,放到主循环或独立任务中处理,彻底避免阻塞Web服务器的事件循环。
1. 定义请求上下文结构体
用来保存Web请求的关键信息,包括请求对象、响应流、接收状态和缓冲区:
struct PendingRequest { AsyncWebServerRequest* req; AsyncResponseStream* resp; bool isWaitingForResponse; String serialBuffer; }; // 单并发场景用单变量保存,多并发需改用FreeRTOS队列 PendingRequest currentPendingReq = {nullptr, nullptr, false, ""};
2. 修改Web回调函数
回调中仅完成查询指令发送和上下文保存,不等待Serial响应:
void getDeviceConfig(AsyncWebServerRequest *request) { // 避免并发冲突,单并发场景直接检查状态 if (currentPendingReq.isWaitingForResponse) { request->send(503, "text/plain", "Busy processing another request"); return; } // 初始化响应流并生成查询指令 AsyncResponseStream *response = request->beginResponseStream("application/json"); StaticJsonDocument<1024> doc; JsonArray array = doc.createNestedArray("get"); for (size_t i = 0; i < request->params(); i++) array.add(request->getParam(i)->value()); // 发送查询指令到Serial serializeJson(doc, Serial); Serial.println(); // 确保远程设备识别结束符 // 保存请求上下文,标记为等待状态 currentPendingReq.req = request; currentPendingReq.resp = response; currentPendingReq.isWaitingForResponse = true; currentPendingReq.serialBuffer.clear(); }
3. 在主循环中处理Serial响应
在loop()中监听Serial输入,收到完整响应后完成Web请求的发送:
void loop() { // 处理待响应的请求 if (currentPendingReq.isWaitingForResponse && Serial.available()) { while (Serial.available()) { char c = Serial.read(); if (c == '\n') { // 假设响应以换行符为结束标记 // 将缓冲区内容写入响应流并发送 currentPendingReq.resp->print(currentPendingReq.serialBuffer); currentPendingReq.req->send(currentPendingReq.resp); // 清理上下文 currentPendingReq.isWaitingForResponse = false; currentPendingReq.req = nullptr; currentPendingReq.resp = nullptr; currentPendingReq.serialBuffer.clear(); break; } else { currentPendingReq.serialBuffer += c; } } } // 其他主循环任务... }
4. 多并发场景优化
如果需要支持多个并发请求,将单变量替换为FreeRTOS队列:
QueueHandle_t pendingRequestsQueue; // 在setup()中初始化队列 void setup() { // ...其他初始化代码 pendingRequestsQueue = xQueueCreate(5, sizeof(PendingRequest)); } // 修改回调中的上下文保存逻辑 void getDeviceConfig(AsyncWebServerRequest *request) { PendingRequest newReq; newReq.req = request; newReq.resp = request->beginResponseStream("application/json"); newReq.isWaitingForResponse = true; newReq.serialBuffer.clear(); if (xQueueSend(pendingRequestsQueue, &newReq, 0) != pdPASS) { request->send(503, "text/plain", "Too many pending requests"); return; } // 发送查询指令到Serial... } // 修改loop()中的Serial处理逻辑 void loop() { PendingRequest tempReq; if (xQueueReceive(pendingRequestsQueue, &tempReq, 0) == pdPASS && tempReq.isWaitingForResponse) { // 复用单场景的Serial接收逻辑,完成后发送响应 } }
补充说明
- 该方案完全遵循ESPAsyncWebServer的异步设计原则,不会阻塞事件循环
- 如果远程设备无固定响应结束符,可添加超时机制(比如2秒超时后发送已接收内容并结束请求)
- 也可以给Serial添加中断处理,进一步提升响应实时性,主循环轮询已能覆盖大部分场景需求
内容的提问来源于stack exchange,提问作者Zapho
相关产品推荐
相关产品推荐

