9

I am trying to write a little asynchronous WebServer. Let me briefly describe the scenario:

My ESP32 as well is a router. So if I connect with my mobile phone into the WiFi the ESP32 is spreading and call the ip address and a special path with a browser, a WebSite is delivered. Here a button is displayed. Until this point it works pretty well. Now, if I click on that button, a HTTPS Web Request (method: GET) is sent to a special machine. This machine answers and returns a JSON. This could last a couple of seconds. After extracting a value out of the JSON string, this value shall be displayed.

To achieve this, I am using the following libraries:

I know (by another sketch) that the last three ones work without any problems.

Unfortunately, when I click the button, the following output appears onto my serial monitor:

Starting connection to server...
[HTTPS] begin... Path: https://192.168.4.101/api/unlock/generate_pin
[HTTPS] GET...
E (137906) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
E (137906) task_wdt: - async_tcp (CPU 0/1)
E (137906) task_wdt: Tasks currently running:
E (137906) task_wdt: CPU 0: IDLE0
E (137906) task_wdt: CPU 1: loopTask
E (137906) task_wdt: Aborting.
abort() was called at PC 0x400e08af on core 0

Backtrace: 0x4008cc18:0x3ffbe170 0x4008ce49:0x3ffbe190 0x400e08af:0x3ffbe1b0 0x40084f21:0x3ffbe1d0 0x4016581b:0x3ffbc120 0x400e1c66:0x3ffbc140 0x4008ab21:0x3ffbc160 0x4008932d:0x3ffbc180

Rebooting...
ets Jun 8 2016 00:22:57

rst:0xc (SW_CPU_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0018,len:4
load:0x3fff001c,len:1044
load:0x40078000,len:8896
load:0x40080400,len:5816
entry 0x400806ac
Serial initial done

Anyone any idea what is happening and how to fix this? So that the GET request is sent correctly / the answer is received?

I am using a Heltec WiFi Kit 32.

Would be very happy about every answer, thanks in advance.

Best regards

P.S.: Please let me finally add my code:

#include <heltec.h>
#include "WiFi.h"
#include "ESPAsyncWebServer.h"

#include <WiFiClientSecure.h>
#include <HTTPClient.h>
 
const char* ssid = "MyWiFiSSID";
const char* password =  "MyWiFiPW";
 
AsyncWebServer server(80);

void setup() {

  Heltec.begin(true, false, true, true, 470E6);

  WiFi.softAP(ssid, password);
  
  IPAddress IP = WiFi.softAPIP();
  Serial.print("AccessPoint IP address: ");
  Serial.println(IP);
  
  server.on("/hello", HTTP_GET, [](AsyncWebServerRequest *request){
    
    request->send(200, "text/html", "<!DOCTYPE html><html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" charset=\"UTF-8\"><link rel=\"icon\" href=\"data:,\"><style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}.button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px; text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}</style></head><body><h1>Welcome to the Landing Page of the Web Server</h1><p><a href=\"/get_unlock_pin\"><button class=\"button\">Click Me</button></a></p></body></html>");
  });

  server.on("/get_unlock_pin", HTTP_GET, [](AsyncWebServerRequest *request){

    String firstpartofrawhtmlcode = "<!DOCTYPE html><html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" charset=\"UTF-8\"><link rel=\"icon\" href=\"data:,\"><style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}.button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px; text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}</style></head><body><h2>Received Pin: </h2><h2 style=\"color: #FF0000\">";
    String receivedPin = getPin("192.168.4.101");
    String secondpartofrawhtmlcode = "</h2></body></html>";
    String fullrawhtmlcode;
    firstpartofrawhtmlcode = firstpartofrawhtmlcode.concat(receivedPin);
    fullrawhtmlcode = firstpartofrawhtmlcode.concat(secondpartofrawhtmlcode);
    request->send(200, "text/html", fullrawhtmlcode);
  });
 
  server.begin();
}

void loop() {

}

String getPin(String ip){
    Serial.println("\nStarting connection to server...");  
    WiFiClientSecure *wificlient = new WiFiClientSecure;

    HTTPClient https;
    https.setAuthorization("MyUserName", "MyPassword");

    String path = "https://" + ip + "/api/unlock/generate_pin";
      
    Serial.print("[HTTPS] begin... Path: " + path + "\n");
    if (https.begin(*wificlient, path)) { 
        Serial.print("[HTTPS] GET...\n");
        int httpCode = https.GET();
        if (httpCode > 0) {
          Serial.printf("[HTTPS] GET... code: %d\n", httpCode);
  
          if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
            String payload = https.getString();
            Serial.println(payload);
            //Extract Pin from JSON
            String tmp = payload.substring(payload.indexOf(':'), payload.indexOf('}'));
            String tmp2 = tmp.substring(tmp.indexOf('"')+1,tmp.lastIndexOf('"')); 
            if(tmp2.substring(0,1) == "-"){
               return "-";
            }else{
               return tmp2;
            }              
          }
        } else {
               Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str());
        }  
        https.end();
    } else {
        Serial.printf("[HTTPS] Unable to connect\n");
    }
}
Sam
  • 145
  • 1
  • 1
  • 12

3 Answers3

28

The way the question is formulated, I assume you don't know what a watchdog is nor why it is triggering. Hence:

Arduino for the ESP is built on the ESP-IDF, which in turn is built around FreeRTOS. FreeRTOS creates one IDLE task per core. It also sets watchdog timers on these Tasks. This means if these tasks get starved of execution time, then after a timeout period, the watchdog gets triggered and resets the chip. The IDLE tasks do some important FreeRTOS "household" work in the background, so you have to give them time. They also have the lowest possible priority. So if any task is running with a higher priority (such as your callbacks), these will always run first while the IDLE tasks will have to wait. Thus all higher priority tasks must be short enough to avoid triggering the watchdog. If this isn't feasible, you have to insert pauses in sufficient intervals, for example by calling vTaskDelay(...) or by performing some IO function that blocks long enough. In either case, the current task will go to sleep, and FreeRTOS will kick in another task, and if there are no other higher priority tasks waiting, it will finally allow the IDLE tasks to execute. What all this means is that usually no code you write should ever preoccupy the CPU for 100% of the time for any period longer than the watchdog timeout period. Moreover, you can't turn this off in Arduino. It is only configurable in ESP-IDF, meaning you have to code within that framework or recompile Arduino on your own. Moreover it is a bad idea to turn it off anyways.

You must use a value of atleast 1 in the call to vTaskDelay(...). Due to integer arithmetic and the various expressions passed as a parameter, it could end up amounting to 0, ie no delay at all.

If you really need to run a task without pause, you'll have to create the task on your own and give it a priority of tskIDLE_PRIORITY, such as:

xTaskCreate(someFunction, "HumanReadableNameofTask", 4096, NULL, tskIDLE_PRIORITY, NULL);

where your function has the following signature:

void someFunction(void* arg) {...}

You can do this in Arduino too. So, either keep the code that runs in the callbacks to a minimum, or shift the heavy lifting to a separate task and have the callbacks simply forward any relevant information to that task (such as using volatile variables and semaphores, ie the usual methods of synchronization in parallel processing. that's beyond the scope of this answer).

Note I'm not sure if calling delay(...) in Arduino has the same effect as vTaskDelay, but it probably does (with some subtleties). Also, there is an official way for a task to "yield" to another lower task, I'm not sure about the details though.

Important: Using delays (such as vTaskDelay(...)) within callbacks, especially timer callbacks, is a bad idea, since they block other callbacks (within the same task) from executing. Hence the best option is to pass information to a separate task running at idle priority (ie priority 0). The reason why a task running at 0 priority doesn't trigger the watchdog, is because the IDLE tasks also have that priority, and FreeRTOS round-robins all those tasks at the same priority, ie gives them interleaving slices of time to execute in "parallel". But when you have two tasks with differing priorites, the higher one ALWAYS executes until it is done or stalls/sleeps, only then will the lower priority run, either until it itself finishes, or until the higher priority task wakes up again and demands execution time.

Update:

Keeping the Arduino loop() empty, will definately trigger the wdt, because it isn't really "empty", because the loop() function is internally wrapped in an infinite loop, so the CPU consumption would actually shoot to 100% without doing anything useful. That is what is triggering the watchdog. The GET process seems to be happening asynchronously, so it is not blocking the loop() from executing.

mo FEAR
  • 552
  • 4
  • 8
  • Good explanation, thank you. However, it still trigger the watchdog even after I set the task priority to 0. The only solution for me so far is the vTaskDelay(10). Is 10 definitely enough for not triggering watchdog timer? – Albert Tobing Jul 06 '22 at 06:31
  • Thank you so VERY much. I was getting ready to eat my breadboard smeared with soldering grease. Some tutorials online suggested one can replace the loop() function in the Arduino code with a task and leave the loop funciton empty. That leads to exactly this behaviour. – drdeath Aug 24 '22 at 19:34
  • 1
    @AlbertTobing vTaskDelay(1) should definately suffice. If it isn't, then there is some nuance somewhere manifesting itself. If you use vTaskDelay(pdMS_TO_TICKS(1)) this would evaluate to 0 if the configured tick rate is 100Hz. Or for example, setting it to 1 might cause no more idle time available if other tasks are running and also consuming cpu time. The wdt does't just trigger because of one single task, rather when the cpu as a whole is no longer getting time to breath. Also, there is a difference between the task watchdog and the interrupt watchdog (which triggers after 300ms). – mo FEAR Aug 25 '22 at 19:58
  • @AlbertTobing However, calling vTaskDelay at all is only necessary when the task priority is not 0. So are you sure you set it right? The main loop can't be empty even if you create another task and move all logic there. The main loop will always need a vTaskDelay if there is no other code that would cause it to block sufficiently often/long. To change the priority of the Arduino loop, you'd have to call vTaskPrioritySet(NULL,newPriority) during setup(), but maybe even within the loop (which is problematic though because it would keep getting called). – mo FEAR Aug 25 '22 at 20:11
3

ESPAsyncWebServer callbacks prevent the watchdog timer from being reset while they run. This means they are not meant for doing any actual processing. Register the request and defer processing to the main loop (or some other thread). Have a look at this question for details.

Tarmo
  • 3,728
  • 1
  • 8
  • 25
3

After some digging into this problem; I found a fairly simple solution for this;

Just add: #include "soc/rtc_wdt.h" into the library.

Then do:

rtc_wdt_protect_off();    // Turns off the automatic wdt service
rtc_wdt_enable();         // Turn it on manually
rtc_wdt_set_time(RTC_WDT_STAGE0, 20000);  // Define how long you desire to let dog wait.

Then you need to do rtc_wdt_feed(); to feed the dog in some place you think it indeed runs faster than the dog waiting time.

Tyler2P
  • 2,324
  • 26
  • 22
  • 31
Cc Cloud
  • 31
  • 1
  • ESP-IDF config has a set of CONFIG_ESP_TASK_WDT_xxx defines. You can also control all the logic using those. – Alex D Jul 06 '23 at 00:08