Arduino esp8266 Веб-сервер зависает через некоторое время, но отвечает на пинг

esp8266 wifi esp8266webserver

У меня есть модуль ESP8266, который я использую для включения трех компьютеров, если они выключены. На веб-сервере esp8266 есть то, что мне нужно, чтобы использовать метод POST, а не более простой, поскольку я отправляю пароль, и мне нужна эта небольшая дополнительная безопасность.

Код работает, и схемы работают, как и ожидалось, но только на некоторое время. Я не смог отследить, что происходит, но что бы я ни делал, он всегда зависает через некоторое время, но только веб-сервер, он по-прежнему продолжает отвечать на пинги, поэтому я знаю, что он все еще подключен, и последовательный монитор записывает мои отладочные тексты.

То, что я пытался сделать после исследования в течение некоторого времени:

  • Выходная строка HTML, вероятно, становится слишком большой для памяти и должна использовать PROGMEM, но, в конце концов, мне нужно будет управлять ею, и что-то сломается. Отказался от этого.
  • Вывод HTML содержал встроенное изображение логотипа, которое также, вероятно, становится слишком большим для памяти. Стер изображение, но кадры висят примерно в одно и то же время с ним или без него.
  • Искал способ жесткой установки модуля ESP8266, не смог найти, как это сделать, и это, безусловно, плохая практика и может привести к проблемам другого рода.
  • Добавление тайм-аута для подключения также не имело никакого значения.

Мне чего-то не хватает в дескрипторе веб-сервера, который заполняет память или что-то еще, и через некоторое время он перестает отвечать на запрос из-за этого.

Вот код:

#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");
}

Любые предложения будут оценены по достоинству.

, 👍4


1 ответ


4

Когда вы используете String так, как вы это делаете, вы выделяете много памяти. ESP8266 не имеет большого объема памяти и легко подвержен проблеме, называемой "фрагментацией кучи" - память выделяется из фиксированного пула, называемого "кучей". Со временем свободные части кучи могут стать настолько фрагментированными, что вы больше не сможете выделять куски памяти определенного размера. То, как вы строите строку в переменной HTML, вероятно, приведет к тому, что это произойдет быстро.

Чтобы проверить фрагментацию кучи, добавьте этот код в конец handleRoot():

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

И используйте свой веб-сервер, особенно перезагружая корневую страницу, пока не увидите, что проблема возникла.

Если у вас произошла утечка памяти (память выделяется, но никогда не освобождается), вы увидите, что getFreeHeap сбрасывается и не возвращается.

Если у вас есть фрагментация кучи, вы увидите, что размер getMaxFreeBlockSize будет уменьшаться до тех пор, пока он не станет меньше, чем самые большие строки, которые вы пытаетесь выделить (вероятно, общая длина корневой веб-страницы). Вы также увидите, что со временем фрагментация становится больше - это показатель от 0 до 100% фрагментации кучи. Если он достигнет 40 или 50%, у вас будут проблемы.

Если вы видите фрагментацию кучи, вам нужно будет переосмыслить способ создания веб-страницы, которую вы обслуживаете. Вам нужно свести к минимуму количество раз, когда вы выделяете память. Возможно, вы этого не знаете, но каждый раз, когда вы добавляете что-то в строку, она освобождает память для старой строки и выделяет память для новой.

Например, вы создаете веб-страницу следующим образом:

 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" +

Каждый раз, когда вы используете + для добавления в строку здесь, вы заставляете компилятор излишне выделять память. Совершенно необязательно складывать большинство этих строк вместе. Компилятор C++ автоматически объединит строки, написанные в отдельных строках, например:

 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"

Простое внесение этого изменения уменьшит фрагментацию кучи. Это не решит проблему, но должно позволить вашей программе работать дольше без сбоев.

Это большая тема. Лучший способ сделать это-использовать строки C (массивы символов) и вообще избегать выделения памяти, но это сложно и утомительно даже для опытных программистов. Документация по ядру Arduino ESP8266 содержит некоторые рекомендации (см. раздел "Память, память, память"), в том числе использование PROGMEM для постоянных литеральных строк и создание строковых объектов со всем пространством, которое им потребуется при их выделении, чтобы избежать освобождения и повторного выделения внутреннего буфера, который они используют для хранения строки.

Если у вас возникли проблемы с фрагментацией кучи, сделайте все возможное, чтобы уменьшить использование строк в вашей программе. Если это не обеспечивает надежную работу вашего веб-сервера, поищите в Интернете и переполнении стека дополнительные советы о том, как избежать фрагментации кучи, и разместите здесь новые вопросы о конкретных проблемах.

,

У вас все еще есть + в примере объединения строк в первой строке. В противном случае это хороший совет..., @RDragonrydr

Привет, спасибо и извини, что не вернулся сюда раньше. Я попробую то, что вы предлагаете, и прослежу за тем, что с этим происходит., @Elder

я держу пари, что 20 долларов-это использование строки (неправильное)..., @dandavis