ESP8266 не отвечает через случайные промежутки времени
4-е обновление:
Я купил ESP32 (преемник ESP8266) и столкнулся с той же проблемой. Я использовал тот же код, но вместо этого использовал библиотеку Wifi.h
, а также удалил код ESP8266WiFiGratuitous.h
. Я добавил светодиодную вспышку в начале цикла и могу подтвердить, что светодиод перестает мигать, поэтому я могу подтвердить, что loop()
перестает работать.
Это заставляет меня думать, что это должно быть как-то связано с моей сетью WIFI.
3-е обновление: Я удалил весь код и вместо этого загрузил простой встроенный скетч мигания светодиода. Светодиод мигает уже более 24 часов, что наводит меня на мысль, что в коде Wi-Fi есть что-то, что вызывает сбой программного обеспечения.
Второе обновление:
Я даже пробовал сбрасывать плату в начале тела loop()
, каждые 10 минут, используя библиотеку millisDelay.h, вызывая этот метод для сброса:
void(* resetFunc) (void) = 0;
но и это не помогает. Метод loop()
, похоже, полностью перестает работать.
Обновление:
Я добавил experimental::ESP8266WiFiGratuitous::stationKeepAliveSetIntervalMs();
, но по-прежнему не могу получить ответ через 1 час, поскольку за этот час было сделано около 15 запросов.
У меня есть этот код, который работает на моем ESP8266:
#include <ESP8266WiFi.h>
#include <ESP8266WiFiGratuitous.h>
const size_t input_buffer_length = 256;
const char* ssid = "iiNetC2DAD7";
const char* wifiPassword = "aM9PrxcbtkS";
const char* passwordToOpenDoor = "/87"; //пароль должен начинаться с косой черты
const int doorPin = 5;
const int buzzerPin = 4;
const int toneDuration = 700;
int numOfRequests = 0;
WiFiServer server(301); //Выберите любой номер порта, который вам нравится
WiFiClient client;
void setup() {
Serial.begin(115200);
delay(10);
Serial.println(WiFi.localIP());
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, 1);
pinMode(doorPin, OUTPUT);
digitalWrite(doorPin, 0);
pinMode(buzzerPin, OUTPUT);
digitalWrite(buzzerPin, 0);
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, wifiPassword);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
experimental::ESP8266WiFiGratuitous::stationKeepAliveSetIntervalMs();
server.begin();
Serial.println("Server started.");
Serial.println(WiFi.localIP());
}
void loop() {
client = server.available();
if (!client) {
return;
}
while(!client.available()){
delay(1);
}
char request[input_buffer_length];
client.readBytesUntil('\r', request, input_buffer_length);
Serial.println(request);
if(strstr(request, passwordToOpenDoor)) {//Пароль правильный?
GenerateResponse("Password is correct");
OpenDoor();
CorrectPasswordSound();
}
//Получил запрос GET, и это был не запрос favicon.ico, должно быть, это был неверный пароль:
else if (!strstr(request, "favicon.ico")) {
GenerateResponse("Password is incorrect.");
}
}
void OpenDoor() {
digitalWrite(LED_BUILTIN, 0); // мигать встроенным светодиодом, чтобы помочь во время тестирования.
digitalWrite(doorPin, 1);
delay(500);
digitalWrite(LED_BUILTIN, 1);
digitalWrite(doorPin, 0);
}
void GenerateResponse(const char *text) {
Serial.println(text);
client.print(
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"\r\n"
"<!DOCTYPE HTML>\r\n"
"<html>\r\n"
"<br><h1><b>"
);
client.print(text);
client.print("</b></h1><br><h1><b>Num of requests: ");
client.print(++numOfRequests);
client.print("</b></h1></html>\r\n");
client.flush();
}
void CorrectPasswordSound() {
// Воспроизведение 1700, 1800, 1900 Гц
for(int i = 17; i < 20; i++){
int frequency = i * 100;
tone(buzzerPin, frequency);
delay(toneDuration);
noTone(buzzerPin);
}
}
Однако через случайное время происходит сбой программного обеспечения, и устройство больше не отвечает на HTTP-запросы.
Я проверял это, выводя на последовательный монитор каждый цикл. Кажется, что происходит сбой, и функция loop()
перестает работать, поскольку она перестает печатать на мониторе.
Я заметил, что он по-прежнему отвечает на команды ping.
Он не будет повторно подключаться после перезапуска маршрутизатора, мне нужно нажать кнопку сброса на ESP8266, чтобы он снова начал работать.
Я тестировал его, отправляя HTTP-запрос Get каждые 5 минут в течение многих часов (используя консольное приложение C#). Иногда это не срабатывало через 45 минут, иногда через 7 часов.
Сначала я думал, что это из-за того, что использую объекты String, но после получения ответа на этот вопрос и тестирования только с C-строками, У меня все та же проблема.
Я отправлял запросы HTTP Get с помощью Chrome на своем телефоне и ноутбуке, а также класса .NET Framework System.Net.Http.HttpClient
.
- Плата: AI-Thinker ESP8266MOD
- IDE: Arduino 1.8.12
- Версия платы ESP8266 = 2.6.3
- Настройки IDE:
Я попытался сбросить соединение, используя:
server.stop();
delay(1000);
server.begin();
Тогда это:
ESP.reset();
Затем, после того, как эти 2 не сработали, это:
ESP.restart();
Часто сброс настроек ничего не давал.
Есть ли в моем коде что-то особенное? Если с кодом все в порядке, то, возможно, мой единственный вариант — как-то раз в 30 минут выполнять полную перезагрузку платы.
@David Klempfner, 👍2
Обсуждение5 ответов
Лучший ответ:
Я не знаю, является ли это вашей проблемой, но я оставлю это здесь, потому что это, безусловно, потенциальная ошибка.
Этот код в цикле:
while(!client.available()){
delay(1);
}
может привести к взаимоблокировке, если у вас есть клиент, который падает или не говорит. Поверните это и дайте циклу продолжать работать.
boolean haveClient = false;
void loop() {
client = server.available();
if (client) {
haveClient = true;
} else {
haveClient = false;
}
if(haveClient && client.available()){
// Остальная часть вашего цикла, где вы читаете
// от клиента идет сюда.
Таким образом, если вы получаете клиент, а затем доступный продолжает возвращать false, вы можете, по крайней мере, продолжать проверять наличие клиента.
Это может не быть и не предназначено для решения кода. Вероятно, это не лучший способ закодировать то, что я пытаюсь сказать. Я не знаю, будет ли server.available продолжать возвращать вам одного и того же клиента или он отрежет вас от повторного вызова до того, как клиент отключится. Там может быть какой-то метод проверки соединения. В любом случае, это не главное. Смысл в том, чтобы никогда не записывать ваш код в заблокированный цикл while. Это всегда требует тупика. Это плохая практика, за исключением таких случаев, как фатальные ошибки, когда вы действительно хотите заблокировать это. Пусть петля продолжает зацикливаться. Используйте операторы if, чтобы решить, какие части запускаются и когда.
Если вы хотите убедиться, что именно здесь происходит блокировка, поместите какой-либо вывод до и после цикла while. Напечатайте что-нибудь или включите свет до и выключите после. Таким образом, вы можете сказать, является ли это место, которое вы запираете.
После внедрения вашего изменения оно проработало дольше всех, более 7 часов!, @David Klempfner
Это будет проблема "сервер недоступен через некоторое время", которая решается путем отправки необоснованных запросов ARP. В esp8266 Arduino core 2.7 добавили функции для этого. Основная функция — experimental::ESP8266WiFiGratuitous::stationKeepAliveSetIntervalMs();
. Включить <#include "ESP8266WiFiGratuitous.h>
Примечания:
Вы не знаете, остановится ли ваш цикл. Он ничего не печатает, если ни один клиент не может получить к нему доступ.
удалить функцию flush() после readBytesUntil(). он делает не то, что вы думаете.
Вы имеете в виду [stationKeepAliveSetIntervalMs](https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WiFi/src/ESP8266WiFiGratuitous.h#L37)? или это где-то еще в исходном коде?, @hcheung
извините, это изменилось на пути от PR к релизу. да stationKeepAliveSetIntervalMs
, @Juraj
Как прочитать пароль в URL-адресе запроса без использования readBytesUntil()?, @David Klempfner
Джурай предлагает удалить client.flush()
, а не client.readBytesUntil()., @hcheung
Извини! Неверно прочитал ответ., @David Klempfner
@DavidKlempfner, в setup() после успешного подключения к точке доступа, @Juraj
Где мне поместить эту строку кода?: WiFi.stationKeepAliveSetIntervalMs(интервал). Я пробовал "WiFi.stationKeepAliveSetIntervalMs(1000);" после "WiFi.begin(ssid, wifiPassword);" но получил «Класс ESP8266WiFiClass» не имеет члена с именем «stationKeepAliveSetIntervalMs», @David Klempfner
а ты обновился до 2.7.0?, @Juraj
Да, я обновил диспетчер платы ESP8266 до версии 2.7. Есть ли дополнительные #includes, которые мне нужно добавить?, @David Klempfner
извините, я обновил ответ. это пока экспериментальная функция, @Juraj
@Juraj, пожалуйста, посмотрите мой обновленный вопрос. Я добавил этот код, но он по-прежнему не работает через 1 час., @David Klempfner
@Juraj Я снова проверил, он проработал 5,5 часов и выполнил 82 запроса, прежде чем истечет время ожидания., @David Klempfner
Спасибо Delta_G за указание правильного направления.
Хитрость заключалась в том, чтобы не застрять в функции loop()
, т.е. без циклов.
Проблема заключалась в том, что после запуска client = server.available();
клиенту требуется некоторое время, прежде чем client.available()
вернет значение true, что и цикл while нужен, однако по какой-то причине клиент время от времени терял соединение, а client.available()
никогда не возвращал true, и в этом случае вы застреваете в цикле while. Вам нужно дать ему второй шанс и позволить процессу начаться снова.
При задержке около 50 мс клиенту кажется, что достаточно времени, чтобы установить соединение, а если это не так, он зацикливается и пытается снова так быстро, что создается впечатление, что клиент всегда получает соединение и код. работает нормально.
Вот рабочий код:
void loop() {
WiFiClient client = server.available();
if(client){
delay(50);
if(client.available()) {
//остальный код здесь
}
}
Я столкнулся с аналогичной проблемой, особенно когда сигнал WIFI слабый. Соединение прерывается преждевременно, в результате выполнение программного обеспечения зависает в цикле while(!client.available()).
Для выхода из этого цикла можно реализовать отказоустойчивый таймер следующим образом:
unsigned int u32FailSafeTimer = 3000; /*Set 3s timeout*/
while(!client.available())
{
u32FailSafeTimer--;
delay(1); /*pause for 1ms*/
if (0 == u32FailSafeTimer) /*Fail-safe timer elapsed, get out from unexpected error*/
{
Serial.printf("timeout (RSSI: %d dBm)\n\n", WiFi.RSSI());
client.flush(); /*Kill and prepare for next session*/
return;
}
}
Я возьму SWAG и скажу, что похоже, что сторожевой пес вас поймал. У меня были похожие проблемы, но они не имели никакого отношения к Wi-Fi или чему-то в этом роде. Я определил, что, если он застрянет в цикле, он постоит какое-то время, а затем выйдет из строя. Я смог определить (предполагаю), что что-то истекло, и, поскольку внешнее оборудование не выполнило сброс, но все же оно снова запустило setup(), процессор перезагружался. Некоторые вещи зависали, казалось, что они работают. Покопавшись в этом, я обнаружил, что мне пришлось выпустить свой код, чтобы позволить внутреннему программному обеспечению ESP сбросить сторожевой таймер. Я основывал это на некоторой информации, которую я нашел о максимальном времени, разрешенном для функции задержки(). Надеюсь, это поможет.
- esp32 http client response только 200 не получил данные после этого
- Выполнение HTTPS-запросов с использованием команд ESP8266 AT
- Метод HTTP PUT на NodeMCU/ESP8266
- Http-запрос в прерывании esp8266 не работает
- Не удается подключить ESP8266 к базе данных сервера
- Как читать и записывать EEPROM в ESP8266
- Как исправить: Invalid conversion from 'const char*' to 'char*' [-fpermissive]
- ошибка: espcomm_upload_mem failed при загрузке скетча
Ваш код выглядит нормально. Несколько предложений. 1) переместить клиент WiFiClient; в цикл() и может сочетаться с server.available(); чтобы сформировать один вкладыш, подобный этому WiFiClient client = server.available();. 2) единственный раз, когда кажется, что loop() перестает работать, это время, когда if (!client) { return;} что на самом деле происходит, когда сервер недоступен. Я бы предложил вместо простого возврата распечатать что-нибудь перед возвратом, чтобы посмотреть, так ли это., @hcheung
@hcheung, но тогда клиент не будет распознан в функции GenerateResponse()., @David Klempfner
О, ты прав. Я предполагаю, что это должно быть в Глобал тогда., @hcheung
сделать WiFiClient локальной переменной. передайте его, чтобы он функционировал как ссылка.
void GenerateResponse (WiFiClient& client, const char *text) {
, @Juraj@Juraj принесет ли это какие-либо преимущества?, @David Klempfner
в прошлом была проблема с утечкой памяти при повторном использовании клиентского объекта, @Juraj
Вы исключили какие-либо аппаратные проблемы, такие как слабый источник питания, плохое соединение и т. д.? Что делает включение DoorPin?, @StarCat
@StarCat Я попробую просто включить и выключить светодиод, без Wi-Fi или чего-то еще, и посмотреть, что произойдет. DoorPin просто включает реле., @David Klempfner