ESP32 sync и async webServer - подвиcания AJAX при частом обновлении

Доброго ПервоМая друзья.
Делаю мини проектик по мониторингу температуры и вот заткнулся в AJAX. Сделал компилируемые примеры проблемы. ESP32 Core и библиотеки самого свежего розлива. IDE 1.8.13 (никак не соберусь перейти на поновее).
Проблема выглядит так:


это при обновлении 500 мс, при обновлении 1000 - проблема незначительна, при 1500 почти отсутствует, при 2000 ее не дождался.

Тексты примеров

Synk
#include <WiFi.h>
#include <WebServer.h>

const char* ssid = "ssid";
const char* password = "pwd";

WebServer server(80);

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  Serial.print("Connecting");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nConnected. IP: " + WiFi.localIP().toString());

  server.on("/", handleRoot);
  server.on("/millis", handleMillis);

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

void loop() {
  server.handleClient();
  delay(1);
}

void handleRoot() {
  String html = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
    <title>ESP32 Millis Monitor</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
        body { font-family: Arial, sans-serif; text-align: center; margin-top: 30px; background: #f5f5f5; }
        .card {
            background: white;
            max-width: 500px;
            margin: 0 auto;
            padding: 20px;
            border-radius: 15px;
            box-shadow: 0 4px 8px rgba(0,0,0,0.1);
        }
        #millis {
            font-size: 4em;
            font-weight: bold;
            color: #2c3e50;
            font-family: monospace;
            margin: 20px 0;
        }
        .control {
            background: #ecf0f1;
            padding: 15px;
            border-radius: 10px;
            margin-top: 20px;
        }
        input {
            width: 80%;
            padding: 8px;
            font-size: 1em;
            margin: 10px 0;
        }
        button {
            background: #3498db;
            color: white;
            border: none;
            padding: 8px 16px;
            border-radius: 5px;
            cursor: pointer;
            font-size: 1em;
        }
        button:hover {
            background: #2980b9;
        }
        .info {
            font-size: 0.9em;
            color: #7f8c8d;
        }
        .error {
            color: red;
        }
    </style>
</head>
<body>
<div class="card">
    <h2>ESP32 Uptime</h2>
    <div id="millis">--- ms</div>
    <div class="control">
        <label>Интервал обновления: <span id="intervalValue">1000</span> мс</label><br>
        <input type="range" id="intervalSlider" min="200" max="5000" step="100" value="1000">
        <br>
        <button id="applyBtn">Применить</button>
        <div class="info">Частота запросов к серверу. Рекомендуемый минимум 500 мс.</div>
    </div>
    <div class="info" id="status">Статус: работает</div>
</div>

<script>
    let updateInterval = null;
    let currentIntervalMs = 1000;

    async function fetchMillis() {
        try {
            const response = await fetch('/millis');
            if (!response.ok) throw new Error('HTTP error');
            const ms = await response.text();
            document.getElementById('millis').innerHTML = ms + " ms";
            document.getElementById('status').innerHTML = "✓ обновлено " + new Date().toLocaleTimeString();
            document.getElementById('status').style.color = "green";
        } catch(e) {
            console.error("Ошибка:", e);
            document.getElementById('status').innerHTML = "❌ ошибка запроса";
            document.getElementById('status').style.color = "red";
        }
    }

    function startUpdates(intervalMs) {
        if (updateInterval) {
            clearInterval(updateInterval);
        }
        updateInterval = setInterval(fetchMillis, intervalMs);
        currentIntervalMs = intervalMs;
        document.getElementById('intervalValue').innerText = intervalMs;
        // не вызываем fetchMillis сразу, чтобы не дублировать (хотя можно)
        fetchMillis(); // сразу покажем текущее значение
    }

    // Обработчик ползунка
    const slider = document.getElementById('intervalSlider');
    const applyBtn = document.getElementById('applyBtn');
    let pendingInterval = slider.value;

    slider.addEventListener('input', function() {
        pendingInterval = parseInt(this.value);
        document.getElementById('intervalValue').innerText = pendingInterval;
    });

    applyBtn.addEventListener('click', function() {
        const newInterval = pendingInterval;
        if (newInterval >= 200) {
            startUpdates(newInterval);
            document.getElementById('status').innerHTML = "интервал изменён на " + newInterval + " мс";
        } else {
            alert("Интервал не может быть меньше 200 мс");
        }
    });

    // Запуск по умолчанию
    startUpdates(1000);
</script>
</body>
</html>
  )rawliteral";
  server.send(200, "text/html", html);
}

void handleMillis() {
  server.sendHeader("Connection", "close");
  server.send(200, "text/plain", String(millis()));
}
Asynk
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

const char* ssid = "ssid";
const char* password = "pwd";

AsyncWebServer server(80);

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  Serial.print("Connecting");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nConnected. IP: " + WiFi.localIP().toString());

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    String html = R"rawliteral(
      <!DOCTYPE html>
      <html>
      <head>
          <title>ESP32 Millis Monitor (Async)</title>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <style>
              body { font-family: Arial, sans-serif; text-align: center; margin-top: 50px; background: #f0f2f5; }
              #millis { font-size: 4em; font-weight: bold; color: #2c3e50; font-family: monospace; margin: 20px 0; }
              .card { background: white; max-width: 500px; margin: 0 auto; padding: 20px; border-radius: 15px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); }
              .control { background: #ecf0f1; padding: 15px; border-radius: 10px; margin-top: 20px; }
              input { width: 80%; padding: 8px; margin: 10px 0; }
              button { background: #3498db; color: white; border: none; padding: 8px 16px; border-radius: 5px; cursor: pointer; }
              .info { color: #7f8c8d; margin-top: 15px; }
          </style>
      </head>
      <body>
      <div class="card">
          <h2>ESP32 Async Web Server</h2>
          <div id="millis">--- ms</div>
          <div class="control">
              <label>Интервал обновления: <span id="intervalValue">1000</span> мс</label><br>
              <input type="range" id="intervalSlider" min="100" max="5000" step="100" value="1000">
              <br>
              <button id="applyBtn">Применить</button>
          </div>
          <div class="info" id="status">Статус: работает (асинхронно)</div>
      </div>
      <script>
          let currentInterval = null;

          async function fetchMillis() {
              try {
                  const resp = await fetch('/millis');
                  const val = await resp.text();
                  document.getElementById('millis').innerHTML = val + " ms";
                  document.getElementById('status').innerHTML = "✓ обновлено " + new Date().toLocaleTimeString();
                  document.getElementById('status').style.color = "green";
              } catch(e) {
                  document.getElementById('status').innerHTML = "❌ ошибка запроса";
                  document.getElementById('status').style.color = "red";
              }
          }

          function startUpdates(intervalMs) {
              if (currentInterval) clearInterval(currentInterval);
              currentInterval = setInterval(fetchMillis, intervalMs);
              document.getElementById('intervalValue').innerText = intervalMs;
              fetchMillis();
          }

          const slider = document.getElementById('intervalSlider');
          const applyBtn = document.getElementById('applyBtn');
          let pendingInterval = slider.value;

          slider.addEventListener('input', function() {
              pendingInterval = parseInt(this.value);
              document.getElementById('intervalValue').innerText = pendingInterval;
          });

          applyBtn.addEventListener('click', function() {
              startUpdates(pendingInterval);
          });

          startUpdates(1000);
      </script>
      </body>
      </html>
    )rawliteral";
    request->send(200, "text/html", html);
  });

  server.on("/millis", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(200, "text/plain", String(millis()));
  });

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

void loop() {
  delay(1);
}

может быть я хочу невозможного - при высокой частоте обновлений стек просто засирается запросами и виснет ? или делаю что то неправильно ?

  1. радуйся что скомпилировалось и работает, видимо и ядро и библиотека старые
  2. видимо надо переходить на WebSocket с твоими хотелками
  3. успехов

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

Вам точно надо мониторить температуру так часто (раз в полсекунды)? Звучит как бред – она так быстро не меняется!

Вы говорите, что если посылать раз в две секунды, проблемы нет. Ну, так сделайте себе 30-кратный запас, посылайте раз в минуту и забудьте о перхоти.

Неужели правда так часто надо? Я вот смотрю на температуру раз в 10 минут! Для чего Вам так часто?

Можно, а зачем :wink:
Ну да, и раз в 5 минут достаточно, но хотелось понять предел возможностей. Температура может бегать и реже, а вот часы хотелось каждую секунду обновлять. При настройке железа хочется температуру почаще видеть, потом можно опять уходить на - пореже…
Вцелом главное, что код не подвергнут обструкции, грубых ошибок там нет, и это хорошо.
Спасибо за отклики.

ASYNC
ядро 2.0.14 библиотека 3.6.0 - работает
ядро 3.2.0 библиотека 3.6.0 -

16:36:49.190 -> ���ESP-ROM:esp32s3-20210327
16:36:49.190 -> Build:Mar 27 2021
16:36:49.190 -> rst:0xc (RTC_SW_CPU_RST),boot:0x8 (SPI_FAST_FLASH_BOOT)
16:36:49.190 -> Saved PC:0x4037aaaa
16:36:49.190 -> SPIWP:0xee
16:36:49.228 -> mode:DIO, clock div:1
16:36:49.228 -> load:0x3fce2820,len:0x118c
16:36:49.228 -> load:0x403c8700,len:0x4
16:36:49.228 -> load:0x403c8704,len:0xc20
16:36:49.228 -> load:0x403cb700,len:0x30e0
16:36:49.228 -> entry 0x403c88b8
16:36:49.449 -> Connecting...........
16:36:50.592 -> Connected. IP: 192.168.хх
16:36:50.592 -> 
16:36:50.626 -> assert failed: tcp_alloc /IDF/components/lwip/lwip/src/core/tcp.c:1854 (Required to lock TCPIP core functionality!)
16:36:50.626 -> 
16:36:50.626 -> 
16:36:50.626 -> Backtrace: 0x403766d9:0x3fcaa760 0x4037da05:0x3fcaa780 0x403844be:0x3fcaa7a0 0x420215f7:0x3fcaa8e0 0x4202175d:0x3fcaa900 0x42005b41:0x3fcaa920 0x4200a765:0x3fcaa970 0x42002d6a:0x3fcaa990 0x4200db17:0x3fcaaa00 0x4037e6b2:0x3fcaaa20
16:3 тут ребутится

ядро 3.2.0 библиотека 3.11.0 - не компилируется

In file included from C:\Temp\.arduinoIDE-unsaved202641-12336-1odgzyf.z36b\sketch_may1b\sketch_may1b.ino:3:
c:\ARDUINO\libraries\ESP_Async_WebServer\src/ESPAsyncWebServer.h: In member function 'tcp_state AsyncWebServer::state() const':
c:\ARDUINO\libraries\ESP_Async_WebServer\src/ESPAsyncWebServer.h:1689:49: error: passing 'const AsyncServer' as 'this' argument discards qualifiers [-fpermissive]
 1689 |     return static_cast<tcp_state>(_server.status());
      |                                   ~~~~~~~~~~~~~~^~
In file included from C:\Temp\.arduinoIDE-unsaved202641-12336-1odgzyf.z36b\sketch_may1b\sketch_may1b.ino:2:
c:\ARDUINO\libraries\AsyncTCP\src/AsyncTCP.h:198:13: note:   in call to 'uint8_t AsyncServer::status()'
  198 |     uint8_t status();
      |             ^~~~~~

Я ставил и сервер и тсп асинк свежие. Могу выложить сюда зипы

я на ESP32S3 проверял, видимо с этим проблема связана
Обновил библиотеку AsyncTcp - на интервале 500мс работает нормально

Но это S3 !!!
Я заказал штук несколько , предвкушаю :wink:

накладные расходы где-то 100 миллисекунд, но роутер очень старый, лет 15 ему

оно не нужно вроде никому, пусть те кто ядра пишет, сам и библиотеки правит!)))
и главное что бы проект собирался на ядре <3, а не компилировался на новом)))
а как начнут править, то и желание писать ядра пропадет, ну или по крайней мере будут делать это аккуратно

С какой радости? У меня уже давно 3.хх стоит, мне что, назад откатываться?

v258 естественно, потому что в сети куча библиотек которые не будут править под ядро esp32 более 3
вот зачем вам выше ядро ? ради единичной плюшки переходить на новое ядро, и каждый проект править это ад же…
в esp8266 только самое последнее ядро стоит, потому что компилируются старые проекты, но там и так кроме ядра вечные проблемы есть)))
а в esp32 вообще не понятно что внесли… такое что лучше бы они сами откатились назад, и заново писали свои улучшения)))

#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

const char* ssid = "ssid";
const char* password = "pwd";

AsyncWebServer server(80);

// Переменные для отслеживания состояния
unsigned long lastRequestTime = 0;
bool requestInProgress = false;

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  Serial.print("Connecting");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nConnected. IP: " + WiFi.localIP().toString());

  // Настройка CORS для предотвращения проблем с браузером
  DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
  DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "GET");
  DefaultHeaders::Instance().addHeader("Cache-Control", "no-cache, no-store, must-revalidate");

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    String html = R"rawliteral(
      <!DOCTYPE html>
      <html>
      <head>
          <title>ESP32 Millis Monitor (Async)</title>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <style>
              body { font-family: Arial, sans-serif; text-align: center; margin-top: 50px; background: #f0f2f5; }
              #millis { font-size: 4em; font-weight: bold; color: #2c3e50; font-family: monospace; margin: 20px 0; }
              .card { background: white; max-width: 500px; margin: 0 auto; padding: 20px; border-radius: 15px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); }
              .control { background: #ecf0f1; padding: 15px; border-radius: 10px; margin-top: 20px; }
              input { width: 80%; padding: 8px; margin: 10px 0; }
              button { background: #3498db; color: white; border: none; padding: 8px 16px; border-radius: 5px; cursor: pointer; }
              .info { color: #7f8c8d; margin-top: 15px; }
              .warning { color: #e67e22; font-size: 0.8em; margin-top: 5px; }
          </style>
      </head>
      <body>
      <div class="card">
          <h2>ESP32 Async Web Server</h2>
          <div id="millis">--- ms</div>
          <div class="control">
              <label>Интервал обновления: <span id="intervalValue">1000</span> мс</label><br>
              <input type="range" id="intervalSlider" min="200" max="5000" step="100" value="1000">
              <br>
              <button id="applyBtn">Применить</button>
              <div class="warning">Минимальный интервал: 200 мс для стабильной работы</div>
          </div>
          <div class="info" id="status">Статус: работает (асинхронно)</div>
          <div class="info" id="debug" style="font-size: 0.7em; color: #95a5a6;"></div>
      </div>
      <script>
          let currentInterval = null;
          let isRequestPending = false;  // Флаг для предотвращения накопления запросов
          let failedAttempts = 0;

          async function fetchMillis() {
              // Пропускаем запрос, если предыдущий ещё не завершён
              if (isRequestPending) {
                  document.getElementById('debug').innerHTML = "Пропущен (предыдущий запрос выполняется)";
                  return;
              }
              
              isRequestPending = true;
              
              try {
                  const controller = new AbortController();
                  const timeoutId = setTimeout(() => controller.abort(), 2000);  // Таймаут 2 секунды
                  
                  const resp = await fetch('/millis', { 
                      signal: controller.signal,
                      // Не кешировать запросы
                      cache: 'no-cache'
                  });
                  
                  clearTimeout(timeoutId);
                  
                  const val = await resp.text();
                  document.getElementById('millis').innerHTML = val + " ms";
                  document.getElementById('status').innerHTML = "✓ обновлено " + new Date().toLocaleTimeString();
                  document.getElementById('status').style.color = "green";
                  document.getElementById('debug').innerHTML = "OK";
                  failedAttempts = 0;
                  
              } catch(e) {
                  failedAttempts++;
                  document.getElementById('status').innerHTML = "❌ ошибка запроса (попыток: " + failedAttempts + ")";
                  document.getElementById('status').style.color = "red";
                  document.getElementById('debug').innerHTML = "Ошибка: " + e.message;
                  
                  // Если много ошибок, увеличиваем интервал
                  if (failedAttempts > 5) {
                      startUpdates(Math.min(5000, parseInt(document.getElementById('intervalSlider').value) * 2));
                      document.getElementById('status').innerHTML += " (интервал увеличен)";
                  }
              } finally {
                  isRequestPending = false;
              }
          }

          function startUpdates(intervalMs) {
              if (currentInterval) clearInterval(currentInterval);
              currentInterval = setInterval(fetchMillis, intervalMs);
              document.getElementById('intervalValue').innerText = intervalMs;
              document.getElementById('intervalSlider').value = intervalMs;
              isRequestPending = false;  // Сбрасываем флаг при смене интервала
              fetchMillis();
          }

          const slider = document.getElementById('intervalSlider');
          const applyBtn = document.getElementById('applyBtn');

          slider.addEventListener('input', function() {
              document.getElementById('intervalValue').innerText = parseInt(this.value);
          });

          applyBtn.addEventListener('click', function() {
              const interval = parseInt(slider.value);
              if (interval >= 200) {
                  startUpdates(interval);
              } else {
                  alert("Минимальный интервал 200 мс");
              }
          });

          startUpdates(1000);
      </script>
      </body>
      </html>
    )rawliteral";
    request->send(200, "text/html", html);
  });

  server.on("/millis", HTTP_GET, [](AsyncWebServerRequest *request) {
    // Проверка частоты запросов на серверной стороне
    unsigned long currentTime = millis();
    if (currentTime - lastRequestTime < 50) {  // Минимум 50 мс между запросами
      request->send(429, "text/plain", "Too Many Requests");
      return;
    }
    lastRequestTime = currentTime;
    
    // Отправляем ответ без лишних заголовков
    AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", String(currentTime));
    response->addHeader("Cache-Control", "no-cache, no-store, must-revalidate");
    response->addHeader("Connection", "close");
    request->send(response);
  });

  // Обработчик для ограничения количества одновременных подключений
  server.onNotFound([](AsyncWebServerRequest *request) {
    request->send(404, "text/plain", "Not found");
  });

  server.begin();
  Serial.println("Async HTTP server started");
  Serial.println("Free heap: " + String(ESP.getFreeHeap()));
}

void loop() {
  delay(1);
  
  // Мониторинг свободной памяти каждые 10 секунд
  static unsigned long lastMemCheck = 0;
  if (millis() - lastMemCheck > 10000) {
    Serial.println("Free heap: " + String(ESP.getFreeHeap()));
    lastMemCheck = millis();
  }
}

может поможет

под ядром > 3

8:53:24.942 -> Free heap: 245072
18:53:34.970 -> Free heap: 245072
18:53:44.944 -> Free heap: 245024
18:53:54.988 -> Free heap: 245072
18:54:04.987 -> Free heap: 245072
18:54:14.969 -> Free heap: 245116
18:54:24.971 -> Free heap: 245068

https://github.com/ESP32Async/ESPAsyncWebServer
хотя может кто и будет поддерживать новое ядро)))
но я подожду пока сообщество все сделает, а потом может и перейду)))

p.s. видел что было написано что это библиотека идет на версии ядра меньше 3, а сейчас этого нет на гитхабе…

хорошая библиотека которая много чего делала автоматически, что в новой версии не знаю

в ядре → 3.x.x мне не нравится долгий коннект, точка - 100мс

entry 0x403c88b8
18:57:34.554 -> Connecting...........
18:57:35.645 -> Connected. IP: 192.168.xx

и для сравнения ядро 2.0.14

19:01:37.682 -> entry 0x403c98d0
19:01:37.691 -> Connecting.
19:01:37.790 -> Connected. IP: 192.168.x.x

Помните задачу про приготовление чая?

Чтобы было :wink:

Пиши сразу правильно, и все будет нормально

Потому что его не развивают. Да и сама 8266 - устаревшее дерьмо мамонта ))

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

Каюсь, нет ))