简体   繁体   English

使用 WiFiClient.client.read() 在 ESP32 上下载文件失败——“任务看门狗”错误

[英]Downloading file on ESP32 using WiFiClient.client.read() fails -- "Task watchdog" error

I'm testing code downloading large files (approx 1mb OTA binary file) from a server我正在测试从服务器下载大文件(大约 1mb OTA 二进制文件)的代码

The error happens midway through the download:下载中途发生错误:

-> E (15787) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
-> E (15787) task_wdt:  - async_tcp (CPU 0/1)
-> E (15787) task_wdt: Tasks currently running:
-> E (15787) task_wdt: CPU 0: IDLE0
-> E (15787) task_wdt: CPU 1: IDLE1
-> E (15787) task_wdt: Aborting.
-> abort() was called at PC 0x400e16af on core 0

My current understanding based on this ESP32 github link , is that the download process is blocking the ESP from performing necessary background functions.基于此 ESP32 github 链接,我目前的理解是下载过程阻止 ESP 执行必要的后台功能。

The failure happens (in the code below) during a while() loop that runs a client.read() to fetch the file from a server.在运行client.read()以从服务器获取文件的while()循环期间(在下面的代码中)发生故障。

I tried testing delay() and vTaskDelay() to see if they might help.我尝试测试delay()vTaskDelay()看看它们是否有帮助。 Wasn't sure if that frees things up or just further adds to any task blocking.不确定这是否释放了东西或只是进一步增加了任何任务阻塞。 Neither helped.都没有帮助。 (and I think they're the same function anyway, correct?) (而且我认为它们是相同的 function ,对吗?)

I'm not 100% sure blocking is even the issue.我不是 100% 确定阻塞甚至是问题所在。 Monitoring the download using a Serial.println(thisClient.available()) showed the bytes remaining going from about 7k to around 5k before jumping backup to 7k -- and doing that repeatedly.使用Serial.println(thisClient.available())监视下载显示剩余字节从大约 7k 增加到大约 5k,然后将备份跳到 7k —— 并重复这样做。 That suggests a possible a server issue.这表明可能存在服务器问题。 BUT that same server, downloading the same file to a JS-coded ajax request works just fine.但是同一个服务器,将相同的文件下载到 JS 编码的 ajax 请求就可以了。

The test code below is adapted from an Espressif OTA Example .下面的测试代码改编自Espressif OTA Example I'm still new to C++, so forgive the use of String over char arrays. Having trouble working with those chars.我还是 C++ 的新手,所以请原谅使用 String 而不是 char arrays。使用这些字符时遇到问题。

#include <WiFi.h>
#include <Update.h>
#include "AsyncJson.h"
#include "ArduinoJson.h"

AsyncWebServer EspServer(80);

void setup(){
    Serial.begin(115200);

    const char* ClientSsid = "***";
    const char* ClientPwd = "***";
    Serial.print("Connecting to LAN ");
    WiFi.begin(ClientSsid, ClientPwd);
    int CreepConnect;
    CreepConnect=0;
    while (WiFi.status()!=WL_CONNECTED && CreepConnect<30){
        delay(250);
        Serial.print(".");
        CreepConnect++;
    }
    Serial.println(" on SSID " + WiFi.SSID() + " at " + String(WiFi.localIP()));

    EspServer.on("*", HTTP_POST, [](AsyncWebServerRequest * Req){
        AsyncWebParameter* keyVal=Req->getParam(0);
        String key=keyVal->name();
        String val=keyVal->value();
        if(key=="req" && val=="execOTA"){
            Serial.println("Updating...");
            Req->send(200, "text/plain", "Updating...");
            //EspServer.end(); //Tested disabling server
            execOTA();
        }else{
            Serial.println("Request ignored");
            Req->send(200, "text/plain", "Request ignored");
        }
    });
    EspServer.begin();
    //execOTA();  This does work.  It only fails under the callback above.
}

String execOTA(){
    WiFiClient thisClient;
    IPAddress thisHost(192, 168, 1, 10);
    char thisPath[]="/testBigFile.ino.esp32.bin"; //Big = about 800k OTA bin file
    String thisPart, theseHeaders, thisBody;
    if(!thisClient.connect(thisHost, 8465)){
        Serial.println("Connection Failed");
        return "{\"Error\":\"Connection Failed\"}";
    }
    Serial.println(thisPath);
    Serial.println(String(thisPath));
    Serial.println("Connection succeeded");
    Serial.print("thisClient.available(): ");
    Serial.println(thisClient.available());
    String thisReq=String("GET ") + String(thisPath) + " HTTP/1.1\r\n" +
        "Host: 192.168.1.10:8465\r\n" +
        "Cache-Control: no-cache\r\n" +
        "Connection: close\r\n\r\n";
    Serial.println("thisReq: " + thisReq);
    thisClient.print(thisReq);
    unsigned long timeout = millis();
    while(thisClient.available()==0) {
        if(millis()-timeout > 5000){
            Serial.println("Client timed out");
            thisClient.stop();
            Serial.println("Client timed out");
            return "{\"Error\":\"Client timed out\"}";

        }
    }
    Serial.println("Headers Begin");
    thisPart="Header";
    while(thisClient.available()){
        Serial.println(thisClient.available());
        if(thisPart=="Header"){
            String thisLine=thisClient.readStringUntil('\n');
            theseHeaders.concat(thisLine);
            thisLine.trim();
            if(!thisLine.length()){
                Serial.println("Headers Complete:\n" + theseHeaders + "\n------\n");
                thisPart="Body";
            }
        }else{ //*** Task Watchdog Error happens in this block, after about 50 successful character reads with delay ***
            char thisChar=thisClient.read();
            thisBody.concat(thisChar);
            //delay(10); //Tested at various durations to see if it adds to or frees up blocking.  It seems to add further blocking?
            //vTaskDelay(15); //Also tested, at various durations.
        }
    }
    Serial.println("Body Complete");
    thisClient.stop();
    return "{\"Headers\":\"" + theseHeaders + "\",\"Body\":\"" + thisBody + "\"}";
}

String getHeaderValue(String header, String headerName){
    return header.substring(strlen(headerName.c_str()));
}

You're doing too much in the HTTP callback.你在 HTTP 回调中做的太多了。 While the callback is running, the watchdog timer can't be reset.当回调运行时,看门狗定时器不能被重置。 If that happens for too long you'll get the error you're seeing - Task watchdog got triggered .如果这种情况发生的时间过长,您将收到您所看到的错误 - Task watchdog got triggered The big clue is that it happens in the async_tcp task.最大的线索是它发生在async_tcp任务中。

Try rewriting your code so that the HTTP_POST handler sets a global variable to indicate that execOTA() needs to be called, rather than calling it itself.尝试重写您的代码,以便HTTP_POST处理程序设置一个全局变量来指示需要调用execOTA() ,而不是自己调用它。 Then have loop() do the heavy lifting.然后让loop()完成繁重的工作。

Something like this:像这样的东西:

boolean exec_ota_flag = false;

void setup() {

...

    EspServer.on("*", HTTP_POST, [](AsyncWebServerRequest * Req){
        AsyncWebParameter* keyVal=Req->getParam(0);
        String key=keyVal->name();
        String val=keyVal->value();
        if(key=="req" && val=="execOTA"){
            exec_ota_flag = true;
            Req->send(200, "text/plain", "Updating...");
        }else{

...

void loop() {
  if(exec_ota_flag) {
      exec_ota_flag = false;
      execOTA();
  }
}

Also the while loop in execOTA needs a delay() call. execOTA 中的while循环也需要delay()调用。 Try something like this:尝试这样的事情:

    while(thisClient.available()==0) {
        delay(1);
        if(millis()-timeout > 5000){

When you call delay() you give other tasks a chance to run, which will let the watchdog timer be reset.当你调用delay()时,你给了其他任务一个运行的机会,这将使看门狗定时器被重置。

From your question, it is not clear if you are aware what the watchdog or IDLE tasks are, let alone what is triggering them.从你的问题来看,不清楚你是否知道看门狗或空闲任务是什么,更不用说是什么触发了它们。 Thus I hope this helps: FreeRTOS & IDLE Tasks因此,我希望这会有所帮助: FreeRTOS & IDLE Tasks

Basically, No code should ever block the CPU 100% the time.基本上,任何代码都不应该 100% 地阻塞 CPU。 But if necessary, you can, as long as you create a separate task for it, and assign the tskIDLE_PRIORITY to it.但是如果有必要,你可以,只要你为它创建一个单独的任务,并为它分配 tskIDLE_PRIORITY。 See the link for details.有关详细信息,请参阅链接。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM