c++websocketesp32watchdogarduino-esp32

ESP32 websocket server: Task watchdog got triggered


The Microcontroller I am using is a ESP-WROOM-32:

includes:

#include <Arduino.h>
#include <iostream>
using namespace std;
#include <stdio.h>
#include <string.h>
#include <ArduinoJson.h>
#include <WiFi.h>
#include <ESPmDNS.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <SPIFFS.h>

I am writing the code for a machine that receives orders via a websocket. As soon as this machine starts processing the task from the JSON string of the websocket message, it doesn't take long (5-7 seconds) before the ESP32's watchdog triggers.

After some changes to the code, I assume that the error is that the websocket server has to wait too long for a response from the function it has called.

Error message from the console

14:38:45.881 -> E (359189) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
14:38:45.881 -> E (359189) task_wdt:  - async_tcp (CPU 0/1)
14:38:45.881 -> E (359189) task_wdt: Tasks currently running:
14:38:45.881 -> E (359189) task_wdt: CPU 0: IDLE0
14:38:45.881 -> E (359189) task_wdt: CPU 1: loopTask
14:38:45.881 -> E (359189) task_wdt: Aborting.
14:38:45.881 -> abort() was called at PC 0x4014c194 on core 0
14:38:45.881 -> 
14:38:45.881 -> ELF file SHA256: 0000000000000000
14:38:45.881 -> 
14:38:45.881 -> Backtrace: 0x40088798:0x3ffbf830 0x40088a15:0x3ffbf850 0x4014c194:0x3ffbf870 0x40086f61:0x3ffbf890 0x4017d957:0x3ffbc320 0x4014da9f:0x3ffbc340 0x4008b1d5:0x3ffbc360 0x40089a26:0x3ffbc380
14:38:45.881 -> 

Webserver & Websocket Code

AsyncWebServer server(80);
AsyncWebSocket wss("/ws");

void initWebServer() {
  server.serveStatic("/", SPIFFS, "/www/").setDefaultFile("index.html");
  server.begin();
}

void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
  switch (type) {
    case WS_EVT_CONNECT:
      Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
      break;
    case WS_EVT_DISCONNECT:
      Serial.printf("WebSocket client #%u disconnected\n", client->id());
      break;
    case WS_EVT_DATA:
      handleEvent(arg, data, len, client);
      break;
    case WS_EVT_PONG:
    case WS_EVT_ERROR:
      break;
  }
}

wss.onEvent(onEvent);
void loop() {
  wss.cleanupClients();
}

Possible Websocket Event

{
  "event":"driveTask",
  "data": [
    {
      "driveToPos": 500,
      "driveVerticalCount": 3
    }
  ]
}

Eventhandler code

void handleEvent(void *arg, uint8_t *data, size_t len, AsyncWebSocketClient *client) {
  AwsFrameInfo *info = (AwsFrameInfo*)arg;
  DynamicJsonDocument json(1024);
  deserializeJson(json, (char*) data);
  serializeJson(json, Serial); Serial.println("<- event");
  String eventData = json["data"]; // save the data for driving the motors
  if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
    data[len] = 0;
    Serial.print(eventData);Serial.println("<- Event Data");
    if(strcmp(json["event"], "driveTask") == 0) {
      DynamicJsonDocument task(1024);
      deserializeJson(task, eventData);
      serializeJson(task, Serial);Serial.println("<- task");
    
      for(JsonVariant elem : task.as<JsonArray>()) {
        yield();
        serializeJson(elem, Serial);Serial.println("<- inside for");
        int driveToPos = elem["driveToPos"];
        if(driveToPos != 0) {
          vAchse = driveToPos;
          for(int i=0; i<elem["driveVerticalCount"]; i++) {
            yield();
            hAchse+300;
            delay(2000);
            hAchse-300;          
            delay(2000);
          }                      
        }
      }
      delay(500);
      vAchse.driveHome();
    }
  }
}

Motor Drive Code (ES stands for endstop)

// Class private
void Motor::drive(){
  //Serial.println("inside Drive");
  yield();
  digitalWrite(this->Signal, HIGH);
  delayMicroseconds(this->pulsetime);
  digitalWrite(this->Signal, LOW);
  delayMicroseconds(this->velocity);
}
void Motor::driveMotor(int distance) {
  // break in for loop if endstop activates
  digitalWrite(this->Enable,HIGH);

  if ((this->currPos + distance) > (this->startPos)) {
    distance = ((-1) * this->currPos);
  } else if ((this->currPos + distance) < (this->maxRange)) {
    distance = this->maxRange - this->currPos;
  }  
  
  if (( ( distance ) > ( 0 ) ))
    {digitalWrite(this->Direction, HIGH);}
  else
    {digitalWrite(this->Direction, LOW);}

  for (int index =1; index <= ( abs( distance ) ) && !(this->endStop != -1 && digitalRead(this->endStop) == 1); index++ ){
    this->drive();
  }
  digitalWrite(this->Enable,LOW);
  Serial.println("drove");
  this->currPos += distance;

  if(this->endStop != -1 && digitalRead(this->endStop) == 1) {
    Serial.println("Stopped by ES");
    this->ignoreESnDrive(this->distFromES);
    this->currPos = 0; 
  }
}
void Motor::ignoreESnDrive(int distance) {
  // break in for loop if endstop activates
  digitalWrite(this->Enable,HIGH);

  Serial.println("ESnDrive");

  if ((this->currPos + distance) > (this->startPos)) {
    distance = ((-1) * this->currPos);
  } else if ((this->currPos + distance) < (this->maxRange)) {
    distance = this->maxRange - this->currPos;
  }  
  
  if (( ( distance ) > ( 0 ) ))
    {digitalWrite(this->Direction, HIGH);}
  else
    {digitalWrite(this->Direction, LOW);}
    
  for (int index =1; index <= ( abs( distance ) ); index++ ){
    //Serial.println("drive ESn");
    this->drive();
  }
  digitalWrite(this->Enable,LOW);
  Serial.println("drove ESn");

  this->currPos += distance;
}

// Class public
void Motor::level() {
  if(this->endStop == -1 ) return;
  Serial.println("Leveling");
  
  Serial.println(digitalRead(this->endStop));
  if(digitalRead(this->endStop) == 0){
    digitalWrite(this->Enable,HIGH);  
    digitalWrite(this->Direction, HIGH);
    while (/*this->endStop != -1 && */digitalRead(this->endStop) == 0 ) {
      this->drive();
    }
    digitalWrite(this->Enable,LOW);
  }

  delay(500);

  this->ignoreESnDrive(this->distFromES);
  
  this->currPos = 0;
}
void Motor::driveHome() {
  this->driveMotor(((-1) * this->currPos));
}

void Motor:: operator= (int newPos) {
  Serial.println(newPos);
  this->driveMotor(newPos - this->currPos);
}
void Motor:: operator+ (int distance) {
  Serial.println(abs(distance));
  this->driveMotor(abs(distance));
}
void Motor:: operator- (int distance) {
  Serial.println(abs(distance));
  this->driveMotor((-1)*abs(distance));
}

Attempts to bypass the watchdog trigger with delay() and yield() functions have failed.

The code for moving the axes works without problems if it levels the vaxis at the start. The level loop runs for minutes without crashing. Only when a call is made within the event is the process aborted by the watchdog.


I am looking forward to suggestions on how to circumvent the watchdog. Improvements to the code are secondary here, as first of all the code should run without wdt crashes.


Solution

  • You're using websockets with the ESPAsyncWebServer.

    Your event handler callback is calling yield() and delay(). This is explicitly forbidden in ESPAsyncWebServer's README:

    You can not use yield or delay or any function that uses them inside the callbacks

    To make this work, you'll need to rewrite your code to not call yield() or delay() or any function that might call them (which means almost any third party library since you don't know what it might do internally) in the callback handler.

    The easiest way to do that is to have the callback handler stash any important data for later use, set a global flag that indicates there's work to be done and then return. Rewrite your loop() function to do the work using the saved data and then clear the flag. If you need to write anything to the websockets client, you can do that from inside loop() as well.