ESP32 pauses when webclient is connected

Hey guys,

I have tried searching the forums and google for this, but unfortunately could not find anything related to my specific issue.

I just got my first ESP32 and I am trying to accomplish the following:
The ESP32 should create a WiFi-AP, presenting a website where I can control data modification on two serial ports for an e-bike.

I have the WiFi-AP and webserver up and running, and added a button to enable/disable the packet modification

My problem is currently, that as soon as a client connects to the website or when the button is pressed, the "void loop ()" stops looping for a while. As the data is not processed for a while, this causes the communication between the display and controller of the e-bike to stop working. I enabled console logging by printing a message every time one of the voids is called, and it stops logging for a while.
When I reboot the ESP32, everything works fine again until the next time, a client connects to the website/a button is pressed.

I assume it's just a simple mistake in how my webserver handles the client connection (similar to the issues with "delay()" when flashing LED's, but I have looked through the codes several times and am unable to locate why it stops working.

I'd appreciate any information/tips!

My code will be posted in the next reply, due to exeeding the maximum post length :slight_smile:

Here is my code:

// Import libraries
#include <WiFi.h>

// WiFi-AP SSID & Password
const char* ssid = "Moscow Plus";
const char* password = "Moscow1234";

// IP-Configuration
IPAddress local_ip(192, 168, 1, 1);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
WiFiServer server(80);

// Stores the HTTP request
String header;

// Stores the current states, disables tuning on boot
String tuningState = "AUS";

// Tuning-specific
const byte numBytes = 32;
byte receivedBytes1[numBytes];
byte numReceived1 = 0;
byte receivedBytes2[numBytes];
byte numReceived2 = 0;
boolean newData1 = false;
boolean newData2 = false;
byte sendBytes[numBytes];

void setup() {
  // Serial configuration
  Serial.begin(115200);                                    // USB-Serial console
  Serial1.begin(9600, SERIAL_8N1, 12, 13);                 // Additional consoles for display and controller communication
  Serial2.begin(9600, SERIAL_8N1, 14, 15);

  // Starting WiFi-AP
  WiFi.softAP(ssid, password);
  WiFi.softAPConfig(local_ip, gateway, subnet);
  server.begin();

  // Show on console when booted
  Serial.println("-= NCM Moscow Plus Controller ready =-");
  Serial.println("  -= WiFi-AP & HTTP server started =-");
}

void loop() {

  tuning();
  wifi();
}

// Tuning-Loop
void tuning() {
Serial.println("running tuning-loop");
  recvBytesWithStartEndMarkers1();                          // Checks for new message from display
  modNewData();                                             // Does the magic
  Checksumm();                                              // Calculates the checksum
  showNewData();                                            // Sends modified data
  recvBytesWithStartEndMarkers2();                          // Checks for new message from controller
}

void wifi() {
  // WiFi-Loop
Serial.println("running wifi-loop");
  WiFiClient client = server.available();                   // Checks for new Webclient
  if (client) {                                             // If client ist connected
    String currentLine = "";                                // String with client Data
    while (client.connected()) {                            // Loop while client ist connected
      if (client.available()) {                             // If recieving data from client
        char c = client.read();                             // read the bytes
        header += c;
        if (c == '\n') {                                    // If the byte is a newline character
          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println();

            // Turns tuning ON and OFF
            if (header.indexOf("GET /tuning/an") >= 0) {
              Serial.println("Tuning on");
              tuningState = "AN";
            } else if (header.indexOf("GET /tuning/aus") >= 0) {
              Serial.println("Tuning off");
              tuningState = "AUS";
            }

            // Display the HTML web page
            client.println("<!DOCTYPE html><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");
            // CSS to style the on/off buttons
            client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");
            client.println(".button { background-color: #FF0000; border: none; color: white; padding: 16px 40px;");
            client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");
            client.println(".button2 {background-color: #4CAF50;}</style></head>");

            // Web Page Heading
            client.println("<body><h1>Moscow Plus Controller</h1>");

            // Display current state, and ON/OFF buttons
            client.println("<p>Tuning ist:</p>");
            if (tuningState == "AN") {
              client.println("<p><a href=\"/tuning/aus\"><button class=\"button\">AN</button></a></p>");
            } else {
              client.println("<p><a href=\"/tuning/an\"><button class=\"button button2\">AUS</button></a></p>");
            }
            // The HTTP response ends with another blank line
            client.println();
            // Break out of the while loop
            break;
          } else { // if you got a newline, then clear currentLine
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }
      }
    }
    // Clear the header variable
    header = "";
    // Close the connection
    client.stop();
  }
}

void recvBytesWithStartEndMarkers1() {
  static boolean recvInProgress = false;
  static byte ndx = 0;
  byte startMarker = 0x3A;
  byte endMarker1 = 0x0D;
  byte endMarker2 = 0x0A;
  byte rb;




  while (Serial1.available() > 0 && newData1 == false) {
    rb = Serial1.read();

    if (recvInProgress == true) {
      if ((rb != endMarker2) || (receivedBytes1[ndx - 1] != endMarker1)) {
        receivedBytes1[ndx] = rb;
        ndx++;

        if (ndx >= numBytes) {
          ndx = numBytes - 1;
        }
      }
      else {
        receivedBytes1[ndx - 1] = '\0';
        recvInProgress = false;
        numReceived1 = ndx - 1;
        ndx = 0;
        newData1 = true;

      }
    }

    else if (rb == startMarker) {
      recvInProgress = true;

    }
  }
}

void recvBytesWithStartEndMarkers2() {
  static boolean recvInProgress = false;
  static byte ndx = 0;
  byte startMarker = 0x3A;
  byte endMarker1 = 0x0D;
  byte endMarker2 = 0x0A;
  byte rb;



  while (Serial2.available() > 0 && newData2 == false) {
    rb = Serial2.read();

    if (recvInProgress == true) {
      if ((rb != endMarker2) || (receivedBytes2[ndx - 1] != endMarker1)) {
        receivedBytes2[ndx] = rb;
        ndx++;

        if (ndx >= numBytes) {
          ndx = numBytes - 1;
        }
      }
      else {
        receivedBytes2[ndx - 1] = '\0';
        recvInProgress = false;
        numReceived2 = ndx - 1;
        ndx = 0;
        newData2 = true;

      }
    }

    else if (rb == startMarker) {
      recvInProgress = true;

    }
  }
}

void modNewData() {
  sendBytes[0] = 0x3A;
  for (byte n = 0; n < numReceived1; n++) {
    sendBytes[n + 1] = receivedBytes1[n];
  }
  sendBytes[10] = 0x0D;
  sendBytes[11] = 0x0A;

  if (tuningState == "AN") {

    if (sendBytes[3] == (0x42) ) {
      sendBytes[2] = (0x0B);
      sendBytes[3] = (0x46);
      sendBytes[4] = (0x24);
    }
  }
}

void Checksumm() {
  sendBytes[8] = 0;
  for (byte n = 1; n < 8; n++) {
    sendBytes[8] = sendBytes[8] + sendBytes[n];
  }
}

void showNewData() {
  if (newData1 == true) {
    Serial.print("received: ");
    for (byte n = 0; n < numReceived1; n++) {
      Serial.print(receivedBytes1[n], HEX);
      Serial.print(' ');
    }
    Serial.println();

    Serial.print("mod: ");
    for (byte n = 0; n < 12; n++) {
      Serial.print(sendBytes[n], HEX);
      Serial.print(' ');

    }
    Serial.println();
    Serial1.write(sendBytes, 12);
    newData1 = false;
  }

  if (newData2 == true) {
    Serial.print("receivedMotorC: ");
    for (byte n = 0; n < numReceived2; n++) {
      Serial.print(receivedBytes2[n], HEX);
      Serial.print(' ');
    }
    Serial.println();
    newData2 = false;

    Serial.println();
  }
}

Thanks!

I think it's a problem related to use serial1, serial2 and the Wifi at the same time. You should define other hardware serial links with other pins and other names (do not define serial1 and serial2 again, as these names are already defined by espressif). Read here for example.

Also some pins are not to be used when using Wifi (related to ADC2), see here.

To add to the previous posting.

Does both cores of the ESP32 pause when the client connects?

Something to consider:

You may also find that some functions, with the ESP32, have a higher priority then the loop() function. loop() is assigned priority 1, the lowest. Several native functions, on the ESP32, are running under freeRTOS, built into the ESP32, those functions, most likely, have a higher priority then loop(). If a function has a priority of 2 assigned to it and is on the same core, loop() and setup() run on core 1, as the loop() then the function with a priority of 2 is ran before a priority 1. freeRTOS, allows functions to be assigned to different cores, tasks.

In example I have these 2 tasks:

xTaskCreatePinnedToCore( fLIDAR_ServoAspectChange, "fLIDAR_ServoAspectChange", TaskStack20K, NULL, Priority4, &xHANDLE_LIDAR_ServoAspectChange, TaskCore1 ); // assigned to core 1
xTaskCreatePinnedToCore ( fGetIMU, "v_getIMU", TaskStack30K, NULL, Priority4, &xHANDLE_GetIMU, TaskCore0 ); // assigned to core 0

Each task is assigned a priority of 4 but each task runs on a different core. Each functions can be running at the exact same time instead of sequentially or time sliced. The task fGetIMU takes 3 times as long to run as the fLIDAR_ServoASpectChange which means the fLIDAR_ServoASpectChange will have ran, at least 2 times to every one time fGetIMU runs.

What I am trying to indicate is that the way the ESP32 is being used might also be an issue.

lesept:
I think it's a problem related to use serial1, serial2 and the Wifi at the same time. You should define other hardware serial links with other pins and other names (do not define serial1 and serial2 again, as these names are already defined by espressif). Read here for example.

Also some pins are not to be used when using Wifi (related to ADC2), see here.

Thanks for the very useful information and links!
I changed my sketch accordingly:

#include <HardwareSerial.h>
HardwareSerial DisplaySerial(1);
HardwareSerial ControllerSerial(2);

void(setup):

  DisplaySerial.begin(9600, SERIAL_8N1, 32, 33);    //changed for testing
  ControllerSerial.begin(9600, SERIAL_8N1, 34, 35); //changed for testing

but I am still experiencing the same issues.

Idahowalker:
To add to the previous posting.

Does both cores of the ESP32 pause when the client connects?

Something to consider:

You may also find that some functions, with the ESP32, have a higher priority then the loop() function. loop() is assigned priority 1, the lowest. Several native functions, on the ESP32, are running under freeRTOS, built into the ESP32, those functions, most likely, have a higher priority then loop(). If a function has a priority of 2 assigned to it and is on the same core, loop() and setup() run on core 1, as the loop() then the function with a priority of 2 is ran before a priority 1. freeRTOS, allows functions to be assigned to different cores, tasks.

<...>

What I am trying to indicate is that the way the ESP32 is being used might also be an issue.

Thanks for your reply. I am completely new to programming, especially in the Arduino-world :slight_smile:

Your post made me think that maybe creating an asynchronous HTTP web server would be a better choice. I will try going that route and see if it solves my issue.

Thanks!

Pins 34 & 35 are input only.

lesept:
Pins 34 & 35 are input only.

Huh, thanks for the information. I just selected the GPIO5+GPIO18 pins using this image: ESP-Wroom-32

Looks like I have a lot more reading to get into the programming :slight_smile:

But it was for testing only (currently the board is not connected to anything), simply to avoid using pins possibly used by the WiFi, and it still has the same issues, so I don't think that's the root cause.

Thanks and best regards!

I have switched to the asynchronous web server and can confirm that there no longer is any pause in the loop. I haven't been able to use the sketch in the real world yet but from every test I've thrown at it so far, it seems to be working great.

Thanks for your help! 8) 8) 8)

Can you post your final code, it can help someone later on? And modify your code to remove your password...

Sure, the code is below.
As this code is part of a GitHub-repository, the most recent version can be found here:
GitHub - NorthyIE/DasKit-C7BB-Tuning: Tuning the C7BB display using an ESP32 (in the "WiFi-AP"-folder).

The password is just temporary, as this is a public project. The final code running on my ESP will have a different SSID and password, but thanks for the reminder :slight_smile:

// Import libraries
#include <WiFi.h>
#include <Arduino.h>
#include <ESPAsyncWebServer.h>
#include <SPIFFS.h>

// WiFi-AP SSID & Password
const char* ssid = "Moscow Plus";
const char* password = "Moscow1234";

// IP-Configuration
IPAddress local_ip(192, 168, 1, 1);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
AsyncWebServer server(80);

// Stores the current states, disables tuning on boot
String tuningState = "AUS";

// Tuning-specific
const byte numBytes = 32;
byte receivedBytes1[numBytes];
byte numReceived1 = 0;
byte receivedBytes2[numBytes];
byte numReceived2 = 0;
boolean newData1 = false;
boolean newData2 = false;
byte sendBytes[numBytes];

void notFound(AsyncWebServerRequest *request) {
  request->send(404, "text/plain", "Nothing here..");
}

void setup() {
  // Serial configuration
  Serial.begin(115200);                                    // USB-Serial console
  Serial1.begin(9600, SERIAL_8N1, 12, 13);                 // Additional consoles for display and controller communication
  Serial2.begin(9600, SERIAL_8N1, 14, 15);

  // Starting WiFi-AP
  if (!SPIFFS.begin()) {
    Serial.println("An Error has occurred while mounting SPIFFS");
    return;
  }
  WiFi.softAP(ssid, password);
  WiFi.softAPConfig(local_ip, gateway, subnet);
  server.begin();

  // Show on console when booted
  Serial.println("-= NCM Moscow Plus Controller ready =-");
  Serial.println("  -= WiFi-AP & HTTP server started =-");

  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    if (tuningState == "AN") {
      request->send(SPIFFS, "/tuningan.html", "text/html");
    }
    else {
      request->send(SPIFFS, "/tuningaus.html", "text/html");
    }
  });

  server.on("/tuningan", HTTP_GET, [](AsyncWebServerRequest * request) {
    tuningState = "AN";
    Serial.println("Tuning enabled");
    request->redirect("/");
  });
  server.on("/tuningaus", HTTP_GET, [](AsyncWebServerRequest * request) {
    tuningState = "AUS";
    Serial.println("Tuning disabled");
    request->redirect("/");
  });
  server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send(SPIFFS, "/style.css", "text/css");
  });
  server.onNotFound(notFound);

  server.begin();
}

void recvBytesWithStartEndMarkers1() {
  static boolean recvInProgress = false;
  static byte ndx = 0;
  byte startMarker = 0x3A;             //muss mit 0x3A beginnen
  byte endMarker1 = 0x0D;
  byte endMarker2 = 0x0A;              // und mit CR LF enden
  byte rb;

  while (Serial1.available() > 0 && newData1 == false) {
    rb = Serial1.read();

    if (recvInProgress == true) {
      if ((rb != endMarker2) || (receivedBytes1[ndx - 1] != endMarker1)) {
        receivedBytes1[ndx] = rb;
        ndx++;

        if (ndx >= numBytes) {
          ndx = numBytes - 1;
        }
      }
      else {
        receivedBytes1[ndx - 1] = '\0'; // terminate the string
        recvInProgress = false;
        numReceived1 = ndx - 1; // save the number for use when printing
        ndx = 0;
        newData1 = true;

      }
    }

    else if (rb == startMarker) {
      recvInProgress = true;

    }
  }
}

void recvBytesWithStartEndMarkers2() {
  static boolean recvInProgress = false;
  static byte ndx = 0;
  byte startMarker = 0x3A;             //muss mit 0x3A beginnen
  byte endMarker1 = 0x0D;
  byte endMarker2 = 0x0A;              // und mit CR LF enden
  byte rb;



  while (Serial2.available() > 0 && newData2 == false) {
    rb = Serial2.read();

    if (recvInProgress == true) {
      if ((rb != endMarker2) || (receivedBytes2[ndx - 1] != endMarker1)) {
        receivedBytes2[ndx] = rb;
        ndx++;

        if (ndx >= numBytes) {
          ndx = numBytes - 1;
        }
      }
      else {
        receivedBytes2[ndx - 1] = '\0';                     // terminate the string
        recvInProgress = false;
        numReceived2 = ndx - 1;                             // save the number for use when printing
        ndx = 0;
        newData2 = true;

      }
    }

    else if (rb == startMarker) {
      recvInProgress = true;

    }
  }
}

void modNewData() {
  sendBytes[0] = 0x3A;                                      // neues Telegramm mit 0x3A beginnen
  for (byte n = 0; n < numReceived1; n++) {
    sendBytes[n + 1] = receivedBytes1[n];                   // Die empfangenen Bytes an die nächsten Positionen
  }
  sendBytes[10] = 0x0D;                                     // CR und LF ans Ende
  sendBytes[11] = 0x0A;

  if (tuningState == "AN") {                                // Nur, wenn Tuning via Web-Gui aktiviert ist!!

    if (sendBytes[3] == (0x42) ) {                          // Wenn das 4. Byte 0x42 (2. Stufe) dann
      sendBytes[2] = (0x0B);                                //  3. Byte (Unterstützungslevel)
      sendBytes[3] = (0x46);                                //  4. Byte (Stufe) auf 6
      sendBytes[4] = (0x24);                                // und 5. Byte (Speed) auf 36km/h
    }
  }
}

void Checksumm() {
  sendBytes[8] = 0;
  for (byte n = 1; n < 8; n++) {
    sendBytes[8] = sendBytes[8] + sendBytes[n];
  }
}

void showNewData() {
  if (newData1 == true) {
    Serial.print("received: ");
    for (byte n = 0; n < numReceived1; n++) {
      Serial.print(receivedBytes1[n], HEX);
      Serial.print(' ');
    }
    Serial.println();

    Serial.print("mod: ");
    for (byte n = 0; n < 12; n++) {
      Serial.print(sendBytes[n], HEX);
      Serial.print(' ');

    }
    Serial.println();                                       // bis hier DebugInfo via USB an PC
    Serial1.write(sendBytes, 12);                           // Telegramm rausschicken
    newData1 = false;
  }

  if (newData2 == true) {
    Serial.print("receivedMotorC: ");
    for (byte n = 0; n < numReceived2; n++) {
      Serial.print(receivedBytes2[n], HEX);
      Serial.print(' ');
    }
    Serial.println();
    newData2 = false;

    Serial.println();                                       // bis hier DebugInfo via USB an PC
  }
}

// Tuning-Loop
void tuning() {
  recvBytesWithStartEndMarkers1();                          // Checks for new message from display
  modNewData();                                             // Does the magic
  Checksumm();                                              // Calculates the checksum
  showNewData();                                            // Sends modified data
  recvBytesWithStartEndMarkers2();                          // Checks for new message from controller
}

void loop() {
  tuning();
}