2

I have an ESP8266 module that I use to be able to power on three computers if they are off. The ESP8266WebServer has what I needed to have a POST method rather than a simpler GET one since I am sending a password and I want that little extra security.

The code works and the circuits work as expected but only for a while. I have not been able to track down what is happening but whatever I do, it always hangs after some time but only the web server, it stills continues to respond pings so I know it is still connected and the serial monitor writes back my debug texts.

What I have tried after researching for some time:

  • The HTML output string probably gets too big for the memory and should use PROGMEM but in the end, I sill need to manipulate it and something breaks. Gave up on it.
  • The HTML output had an embedded image for the logo that also probably gets too big for the memory. Erased the image but stills hangs about the same time with or without it.
  • Looked for a way to hardreset the ESP8266 module, could not find how and it is surely bad practice and may come with other kind of problems.
  • Add a timeout for the connection, did not make any difference either.

I am missing something with the handle of the web server that gets the memory or something full and it stops answering request after a while because of it.

Here is the code:

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

const char* ssid     = "";
const char* password = "";
const char* hostname = "";
const String imgLogo = "";


ESP8266WebServer server(9080);

String header = "";

String output1State = "off";
String output2State = "off";
String output3State = "off";

const int output1 = D1;
const int output2 = D2;
const int output3 = D8;

const int input1 = D5;
const int input2 = D6;
const int input3 = D7;

const int ledPin =  LED_BUILTIN;

unsigned long currentTime = millis();
unsigned long previousTime = 0; 
const long timeoutTime = 2000;

unsigned long previousMillisOff = 0;
unsigned long previousMillisOn = 0;
unsigned long previousMillisMsg = 0;

void setup() {
  Serial.begin(9600);
  pinMode(output1, OUTPUT);
  pinMode(output2, OUTPUT);
  pinMode(output3, OUTPUT);
  pinMode(input1, INPUT);
  pinMode(input2, INPUT);
  pinMode(input3, INPUT);
  
  digitalWrite(output1, LOW);
  digitalWrite(output2, LOW);
  digitalWrite(output3, LOW);

  WiFi.disconnect();
  delay(500);
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, HIGH);

  WiFi.mode(WIFI_STA);
  WiFi.hostname(hostname);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) 
  { 
    delay(500); 
    Serial.print(".");
    digitalWrite(ledPin, !digitalRead(ledPin));
  }
  Serial.println(""); 
  Serial.println("WiFi connected"); 
  Serial.println("IP address: "); 
  Serial.println(WiFi.localIP());
  Serial.println(WiFi.getAutoConnect());
  digitalWrite(ledPin, HIGH);
  
  server.on("/", HTTP_GET, handleRoot);
  server.on("/serverAction", HTTP_POST, serverAction);
  server.onNotFound(handleNotFound);

  server.begin();
  Serial.println("HTTP server started");
}

void loop(void)
{
  server.handleClient();
  
  
  unsigned long currentMillis = millis();
  if(currentMillis - previousMillisOff > 5000)
  {
    previousMillisOff = currentMillis;
    digitalWrite(ledPin, LOW);
    previousMillisOn = currentMillis;
  }
  
  currentMillis = millis();
  if(!digitalRead(ledPin) && currentMillis - previousMillisOn > 250)
  {
    previousMillisOn = currentMillis;
    digitalWrite(ledPin, HIGH);
    previousMillisOff = currentMillis;
  }
  
  currentMillis = millis();
  if(currentMillis - previousMillisMsg > 30000)
  {
    Serial.println(WiFi.localIP());
    previousMillisMsg = currentMillis;
  }
}

void handleRoot()
{
  boolean PC01 = digitalRead(input1);
  boolean PC02 = digitalRead(input2);
  boolean PC03 = digitalRead(input3);
  
  String HTML = String("<!DOCTYPE html>\r\n<html>\r\n") +
    " <head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta http-equiv=\"cache-control\" content=\"no-cache, must-revalidate, post-check=0, pre-check=0\" />\r\n    <meta http-equiv=\"cache-control\" content=\"max-age=0\" />\r\n   <meta http-equiv=\"expires\" content=\"0\" />\r\n   <meta http-equiv=\"expires\" content=\"Tue, 01 Jan 1980 1:00:00 GMT\" />\r\n    <meta http-equiv=\"pragma\" content=\"no-cache\" />\n\r   <link href=\"data:image/x-icon;base64," + imgLogo + "\" rel=\"icon\" type=\"image/x-icon\" />\r\n   <style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;} .button { background-color: #195B6A; border: none; color: white; padding: 16px 40px; text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;} .button2 {background-color: #800000;}</style>\r\n </head>\r\n <body>\r\n    <img src=\"data:image/png;base64," + imgLogo + "\"/>\r\n\r\n    <h1>Server switch</h1>\r\n  " +
    "   <p>Estado PC01 - " + (PC01 ? "Encendido" : "Apagado") + "\r\n" +
    "   <p>\r\n" +
    "   <form action=\"/serverAction\" method=\"post\">\r\n" +
    "     Contraseña: <input type=\"password\" name=\"pwd\">\r\n" +
    "     <p>\r\n" +
    "     <input type=\"text\" name=\"server\" value=\"PC01\" hidden>\r\n" +
    "     <button class=\"button " + (PC01 ? "" : "button2") + "\" type=\"submit\"" + (PC01 ? " disabled> PC01 encendido" : ">Encender PC01") + "</button>\r\n" +
    "   </form>\r\n\r\n" +
    "   <p>Estado PC02 - " + (PC02 ? "Encendido" : "Apagado") + "\r\n" +
    "   <p>\r\n" +
    "   <form action=\"/serverAction\" method=\"post\">\r\n" +
    "     Contraseña: <input type=\"password\" name=\"pwd\">\r\n" +
    "     <p>\r\n" +
    "     <input type=\"text\" name=\"server\" value=\"PC02\" hidden>\r\n" +
    "     <button class=\"button " + (PC02 ? "" : "button2") + "\" type=\"submit\"" + (PC02 ? " disabled> PC02 encendido" : ">Encender PC02") + "</button>\r\n" +
    "   </form>\r\n\r\n" +
    "   <p>Estado PC03 - " + (PC03 ? "Encendido" : "Apagado") + "\r\n" +
    "   <p>\r\n" +
    "   <form action=\"/serverAction\" method=\"post\">\r\n" +
    "     Contraseña: <input type=\"password\" name=\"pwd\">\r\n" +
    "     <p>\r\n" +
    "     <input type=\"text\" name=\"server\" value=\"PC03\" hidden>\r\n" +
    "     <button class=\"button " + (PC03 ? "" : "button2") + "\" type=\"submit\"" + (PC03 ? " disabled> PC03 encendido" : ">Encender PC03") + "</button>\r\n" +
    "   </form>\r\n </body>\r\n</html>";
  
  server.send(200, "text/html", HTML);
  
}

void serverAction()
{
  
  if( ! server.hasArg("pwd") || ! server.hasArg("server") || server.arg("pwd") == NULL || server.arg("server") == NULL)
  {
    server.send(400, "text/plain", "400: Invalid Request");
    return;
  }
  
  if(server.arg("pwd") == "0123456789")
  {
    String redirect = String("<script>var timer = setTimeout(function() { window.location=\'/\' }, 5000);</script></body></html>");
    if(server.arg("server") == "PC01")
    {
      if(!digitalRead(input1))
      {
        server.send(200, "text/html", "<html><body><h1 style=\"text-align: center\">Encendiendo " + server.arg("server") + "</h1>" + redirect);
        digitalWrite(output1, HIGH);
        delay(1000);
        digitalWrite(output1, LOW);
      }
      else
        server.send(200, "text/html", "<html><body><h1 style=\"text-align: center\">" + server.arg("server") + " ya encendido</h1>" + redirect);
      
    }
    else if(server.arg("server") == "PC02")
    {
      if(!digitalRead(input2))
      {
        server.send(200, "text/html", "<html><body><h1 style=\"text-align: center\">Encendiendo " + server.arg("server") + "</h1>" + redirect);
        digitalWrite(output2, HIGH);
        delay(1000);
        digitalWrite(output2, LOW);
      }
      else
        server.send(200, "text/html", "<html><body><h1 style=\"text-align: center\">" + server.arg("server") + " ya encendido</h1>" + redirect);
    }
    else if(server.arg("server") == "PC03")
    {
      if(!digitalRead(input3))
      {
        server.send(200, "text/html", "<html><body><h1 style=\"text-align: center\">Encendiendo " + server.arg("server") + "</h1>" + redirect);
        digitalWrite(output3, HIGH);
        delay(1000);
        digitalWrite(output3, LOW);
      }
      else
        server.send(200, "text/html", "<html><body><h1 style=\"text-align: center\">" + server.arg("server") + " ya encendido</h1>" + redirect);
    }
    else
    {
      server.send(401, "text/plain", "401: Unauthorized");
    }
  }
  else
  {
    server.send(401, "text/plain", "401: Unauthorized");
  }
}

void handleNotFound(){
  server.send(404, "text/plain", "404: Not found");
}

Any suggestions would be appreciated.

1 Answer 1

2

When you use String the way you do, you're doing a lot of memory allocations. The ESP8266 doesn't have a lot of memory and is easily subject to a problem called "heap fragmentation" - memory is allocated from a fixed pool called a "heap". Over time the free parts of the heap may become so fragmented that you can no longer allocate chunks of memory over a certain size. The way that you're building the string in the variable HTML will probably cause this to happen quickly.

To check for heap fragmentation, add this code to the end of handleRoot():

  Serial.print("getFreeHeap: ");
  Serial.println(ESP.getFreeHeap());
  Serial.print("getHeapFragmentation: ");
  Serial.println(ESP.getHeapFragmentation());
  Serial.print("getMaxFreeBlockSize: ");
  Serial.println(ESP.getMaxFreeBlockSize());

And use your web server, especially reloading the root page, until you see the problem happen.

If you have a memory leak (memory is being allocated but never freed), you'll see getFreeHeap drop and not go back up.

If you have heap fragmentation, you'll see getMaxFreeBlockSize get smaller until it's smaller than the biggest strings you're trying to allocate (probably the total length of the root web page). You'll also see getHeapFragmentation get bigger over time - it's a metric from 0 to 100% of how fragmented the heap is. If it gets to 40 or 50% you're in trouble.

If you are seeing heap fragmentation you're going to need to rethink the way you build the web page you're serving. You need to minimize the number of times that you allocate memory. You may not know it but every time you add something on to a String, it frees the memory for the old string and allocates memory for the new one.

For instance, you build the web page like this:

 String HTML = String("<!DOCTYPE html>\r\n<html>\r\n") +
    " <head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta http-equiv=\"cache-control\" content=\"no-cache, must-revalidate, post-check=0, pre-check=0\" />\r\n    <meta http-equiv=\"cache-control\" content=\"max-age=0\" />\r\n   <meta http-equiv=\"expires\" content=\"0\" />\r\n   <meta http-equiv=\"expires\" content=\"Tue, 01 Jan 1980 1:00:00 GMT\" />\r\n    <meta http-equiv=\"pragma\" content=\"no-cache\" />\n\r   <link href=\"data:image/x-icon;base64," + imgLogo + "\" rel=\"icon\" type=\"image/x-icon\" />\r\n   <style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;} .button { background-color: #195B6A; border: none; color: white; padding: 16px 40px; text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;} .button2 {background-color: #800000;}</style>\r\n </head>\r\n <body>\r\n    <img src=\"data:image/png;base64," + imgLogo + "\"/>\r\n\r\n    <h1>Server switch</h1>\r\n  " +
    "   <p>Estado PC01 - " + (PC01 ? "Encendido" : "Apagado") + "\r\n" +
    "   <p>\r\n" +
    "   <form action=\"/serverAction\" method=\"post\">\r\n" +

Every time you use + to add to the String here, you're causing the compiler to unnecessarily allocate memory. It's completely unnecessary to add most of these strings together. The C++ compiler will automatically concatenate strings written on separate lines, like so:

 String HTML = String("<!DOCTYPE html>\r\n<html>\r\n") +
    " <head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta http-equiv=\"cache-control\" content=\"no-cache, must-revalidate, post-check=0, pre-check=0\" />\r\n    <meta http-equiv=\"cache-control\" content=\"max-age=0\" />\r\n   <meta http-equiv=\"expires\" content=\"0\" />\r\n   <meta http-equiv=\"expires\" content=\"Tue, 01 Jan 1980 1:00:00 GMT\" />\r\n    <meta http-equiv=\"pragma\" content=\"no-cache\" />\n\r   <link href=\"data:image/x-icon;base64," + imgLogo + "\" rel=\"icon\" type=\"image/x-icon\" />\r\n   <style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;} .button { background-color: #195B6A; border: none; color: white; padding: 16px 40px; text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;} .button2 {background-color: #800000;}</style>\r\n </head>\r\n <body>\r\n    <img src=\"data:image/png;base64," + imgLogo + "\"/>\r\n\r\n    <h1>Server switch</h1>\r\n  "
    "   <p>Estado PC01 - " + (PC01 ? "Encendido" : "Apagado") + "\r\n"
    "   <p>\r\n"
    "   <form action=\"/serverAction\" method=\"post\">\r\n"

Just making that change will reduce the heap fragmentation. This won't solve the problem but it should allow your program to run longer without crashing.

This is a big subject. The best way to do it is to use C strings (character arrays) and avoid allocating memory altogether, but this is difficult and tedious even for experienced programmers. The ESP8266 Arduino Core documentation offers some advice (see the section "Memory, memory, memory"), including using PROGMEM for constant literal strings and creating String objects with all the space they'll need when they're allocated, to avoid freeing and re-allocating the internal buffer they use to store the string.

If you're having heap fragmentation issues, do everything you can to reduce String use in your program. If that doesn't get your web server working reliably, search the web and Stack Overflow for more advice on how to avoid heap fragmentation, and post new questions here about specific issues.

3
  • You still have a + in the string concatenation example on the first line. Otherwise this is good advice... Dec 1, 2021 at 18:12
  • Hi, thank you and sorry for not getting back here sooner. I will try what you suggest and give a follow up of what is going with this.
    – Elder
    Dec 2, 2021 at 17:07
  • 1
    i bet $20 is the String (mis)usage...
    – dandavis
    Dec 3, 2021 at 22:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.