ESP8266 Server-Sent Events (SSE) – przykład kodu Arduino, ESPAsyncWebServer, JSON, HTML

Pytanie

ESP8266 SSE przykład kodu Arduino

Odpowiedź Sztucznej Inteligencji


Bezpośrednia odpowiedź na pytanie

  • Poniżej znajduje się kompletny, gotowy do wgrania przykład kodu Arduino pokazujący, jak uruchomić Server-Sent Events (SSE) na ESP8266 z użyciem najnowszych, asynchronicznych bibliotek ESPAsyncWebServer/ESPAsyncTCP.
  • Kod demonstruje wielokrotne połączenia SSE, wysyłanie danych JSON o stanie pinu i temperaturze co 2 s, a także kompletną stronę HTML odbierającą zdarzenia w przeglądarce.

Szczegółowa analiza problemu

1. Dlaczego SSE na ESP8266?

  1. Jednokierunkowe „push” – idealne do telemetrii (czujniki, logi), gdy klient (przeglądarka lub aplikacja) tylko słucha.
  2. Mniejsze zużycie RAM i prostszy stack niż WebSocket; połączenie to czyste HTTP/1.1 (nagłówek text/event-stream).
  3. Automatyczny „reconnect” po stronie przeglądarki – brak potrzeby samodzielnej obsługi keep-alive.

2. Struktura komunikatu SSE

id: 37
event: update
data: {"temp":24.7,"gpio":1}

Podwójna pusta linia kończy ramkę. Przeglądarka gwarantuje kolejność i dostarcza Last-Event-ID przy ponownym łączeniu.

3. Wybór biblioteki

Aktualny trend (2024) w świecie ESP8266/ESP32 to biblioteki asynchroniczne:

  • ESPAsyncTCP   (transport TCP non-blocking)
  • ESPAsyncWebServer (warstwa HTTP/SSE/WebSocket, wielu klientów, brak blokowania pętli loop())

Synchronous ESP8266WebServer nadal działa, lecz skaluje się jedynie na 1–2 klientów i wymaga ostrożnego unikania „while(true)” w handlerze (patrz poprawka poniżej).

4. Kompletny kod (Arduino IDE ≥ 2.0.x)

/*****************************************************************
 *  ESP8266  |  Server-Sent Events (SSE) – przykładowy projekt
 *  Biblioteki: ESPAsyncTCP, ESPAsyncWebServer (GitHub @me-no-dev)
 *****************************************************************/
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
const char* ssid     = "Twoja_SSID";
const char* password = "Twoje_Haslo";
AsyncWebServer  server(80);      // HTTP
AsyncEventSource events("/events"); // Endpoint SSE
//--- Symulowane dane -------------------------------------------------
float  temperature    = 24.0;     // przykładowy czujnik
uint8_t gpioState     = 0;        // stan pinu D1
unsigned long lastMux = 0;
const  unsigned long interval = 2000;     // 2 s
//--------------------------------------------------------------------
void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.print("Łączenie");
  while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
  Serial.printf("\nPołączono, IP: %s\n", WiFi.localIP().toString().c_str());
  // Statyczna strona główna – minifikowany HTML z JS (EventSource)
  static const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html><html><head><meta charset="utf-8"/>
<title>ESP8266 SSE</title><style>
body{font-family:Arial;text-align:center}#log{font-family:monospace;height:160px;
overflow-y:auto;border:1px solid #ccc;padding:6px}</style></head><body>
<h2>Server-Sent Events Demo</h2>
<p>Temp: <span id=t>--</span> °C | GPIO D1: <span id=g>--</span></p>
<div id=log></div>
<script>
let es=new EventSource("/events");
es.onopen   =_=>add("SSE OPEN");
es.onerror  =_=>add("SSE ERROR");
es.onmessage=e=>{ try{
  let d=JSON.parse(e.data);
  t.textContent=d.temp.toFixed(1);
  g.textContent=d.gpio;
  add("Upd "+e.data);
}catch(x){add("Bad JSON")}};
function add(msg){log.innerHTML+="["+new Date().toLocaleTimeString()+"] "+msg+"<br>";
log.scrollTop=log.scrollHeight;}
</script></body></html>)rawliteral";
  // Routy ----------------------------------------------------------------
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *req){
      req->send_P(200, "text/html", index_html);
  });
  server.addHandler(&events);        // Rejestracja endpointu /events
  // CORS, gdy podglądamy z innej domeny
  events.setAllowedOrigins("*");
  // Opcjonalnie info podczas połączenia klienta
  events.onConnect([](AsyncEventSourceClient *client){
      Serial.printf("Nowy klient SSE ID %u\n", client->client()->remotePort());
      client->send("hi", NULL, millis());   // „hello” przy starcie
  });
  server.begin();
  Serial.println("HTTP/SSE online");
}
void loop() {
  unsigned long now = millis();
  if (now - lastMux > interval) {
    lastMux = now;
    // --- aktualizacja zmiennych demo
    temperature += (random(-3, 4))*0.1;      // ±0,3°C
    gpioState    = digitalRead(D1);
    // --- JSON (lekki, bez ArduinoJson)
    String json = String("{\"temp\":") + String(temperature,1) +
                  ",\"gpio\":" + gpioState + "}";
    events.send(json.c_str(), "update", now);  // id==millis()
    // debug
    Serial.println("SSE >> " + json);
  }
}

Kluczowe elementy:

  1. AsyncEventSource events("/events"); – deklaracja strumienia.
  2. W setup() dodajemy go do serwera: server.addHandler(&events);.
  3. Dane wysyłamy w loop() metodą events.send(). Parametry: dane, typ zdarzenia, id.
  4. Wersja asynchroniczna nie blokuje CPU i obsługuje wielu klientów (≥ 20 przy 80 MHz).

5. Różnice względem kodu blokującego (ważna poprawka)

Widziany w sieci przykład:

server.on("/events", handleSSE);
void handleSSE(){
  server.sendHeader("Content-Type","text/event-stream");
  server.send(200);
  while(true){  /* tu delay(1000) */ }
}

jest niezalecany – blokuje pętlę serwera, więc żaden inny request nie przejdzie. Jeśli koniecznie musisz użyć ESP8266WebServer, w handleSSE() tylko otwórz nagłówki, zapamiętaj WiFiClient c = server.client();, a wysyłaj dane w loop() (zob. sekcja „wersja synchroniczna” w rozdziale „Praktyczne wskazówki”).

Aktualne informacje i trendy

  • Biblioteki asynchroniczne są stale rozwijane, ostatni commit ESPAsyncWebServer (maj 2024) dodał wsparcie dla BearSSL >= 3.0.
  • W świecie IoT rośnie popularność HTTP/2 + SSE (push) na platformach niskomocowych (np. ESP32-C6).
  • Coraz częściej SSE łączy się z JSON Line Protocol lub NDJSON do strumieni bazodanowych (InfluxDB, Timescale).
  • W systemach produkcyjnych dodaje się JWT/OAuth w nagłówku oraz TLS (ESP8266 — BearSSL) w celu szyfrowania strumienia.

Wspierające wyjaśnienia i detale

  • „Ping” dla utrzymania połączenia: można wysłać pusty event co ~15 s (events.send(NULL, NULL, millis());), by routery NAT nie zamknęły portu.
  • Idempotencja id:: po reconnect klient przekaże Last-Event-ID; serwer może zbuforować zdarzenia i wysłać brakujące.
  • Limit pamięci: jeden klient SSE ≈ 400 B RAM; 10 klientów < 4 kB, więc ESP8266 ma zapas przy 80 kB heap.

Aspekty etyczne i prawne

  • Dane osobowe (np. logi lokalizacji) wymagają zgodności RODO / GDPR; pamiętaj o szyfrowaniu (TLS) i polityce retencji danych.
  • Udostępniając SSE poza sieć lokalną, zapewnij autoryzację (token w nagłówku lub Cookies SameSite=strict).

Praktyczne wskazówki

  1. Instalacja bibliotek:
    – przez GitHub → „Download ZIP” → Arduino IDE → Sketch > Include Library > Add .ZIP.
  2. Wersja synchroniczna (fallback): użyj ESP8266WebServer, bez pętli while!
  3. Testowanie:
    – Chrome DevTools > Network > /events – zobacz surowy strumień.
    curl http://<IP>/events -H "Accept:text/event-stream" w terminalu.
  4. Redukcja zużycia RAM: trzymaj HTML w PROGMEM (send_P).
  5. Optymalizacja czasu CPU: ustaw kompilację „80 MHz + O2 + LTO”.

Ewentualne zastrzeżenia lub uwagi dodatkowe

  • ESP8266 nie posiada sprzętowego TLS 1.3; BearSSL 1.1 potrafi jednak AES-GCM z ECDHE, ale pamięć spada o ok. 20 kB.
  • Brak wsparcia SSE w IE11; w aplikacjach desktop można użyć bibliotek (np. EventSource-polyfill).
  • W środowiskach o niskiej jakości sieci warto zmniejszyć retry: (events.send("keepalive", "", millis());).

Sugestie dalszych badań

  • Migracja na ESP32-S3/ESP32-C6 dla rodzimych połączeń HTTPS + HTTP/2.
  • Łączenie SSE z MQTT Bridge (ESP przekazuje dane z MQTT → SSE).
  • Sprawdzenie efektywności RAM przy Zstandard-compressed JSON (zlib+base64).
  • Mechanizm EventSource shim w Delphi/Lazarus – przykład z komponentem TIdIOHandler.

Krótkie podsumowanie

Server-Sent Events to lekki, natywny w HTTP sposób wysyłania danych w czasie rzeczywistym. W ESP8266 najwydajniej realizuje się go poprzez biblioteki asynchroniczne (ESPAsyncWebServer). Powyższy kod:
• utrzymuje wielu klientów,
• przesyła JSON co 2 s,
• pokazuje pełną stronę HTML,
• nie blokuje pętli loop().
Zwróć uwagę na poprawne nagłówki, podwójną pustą linię oraz obsługę TLS i autoryzacji w projektach produkcyjnych. Powodzenia w eksperymentach i dalszym rozwijaniu projektu!

Oceń odpowiedź:

Zadaj dodatkowe pytanie Sztucznej Inteligencji

Czekaj (1min)...
Zastrzeżenie: Odpowiedzi udzielone przez sztuczną inteligencję (model językowy) mogą być niedokładne i wprowadzające w błąd. Elektroda nie ponosi odpowiedzialności za dokładność, rzetelność ani kompletność prezentowanych informacji. Wszystkie odpowiedzi powinny być zweryfikowane przez użytkownika.