ESP32 AsyncTCP – przykładowy klient TCP, kod i konfiguracja Arduino

Pytanie

ESP32, AsyncTCP przykład klienta

Odpowiedź Sztucznej Inteligencji


Bezpośrednia odpowiedź na pytanie

  • Poniżej znajduje się kompletny, przetestowany przykład klienta TCP na ESP32 wykorzystujący bibliotekę AsyncTCP, wraz z omówieniem podstaw konfiguracji, diagnostyki oraz dobrych praktyk.
  • Kluczowe punkty: asynchroniczna obsługa, brak blokowania pętli loop(), pełny zestaw callbacków (‐Connect, ‐Data, ‐Disconnect, ‐Error, ‐Timeout), przykładowy mechanizm automatycznego ponownego łączenia i periodycznego wysyłania danych.

Szczegółowa analiza problemu

1. Dlaczego AsyncTCP?

1.1. Biblioteka pracuje bez­blokująco – opiera się na zdarzeniach realizowanych przez podsystem FreeRTOS/LwIP.
1.2. Dzięki jednorazowej rejestracji callbacków kod użytkownika pozostaje prosty: pętla loop() może wykonywać inne zadania (czujniki, UI, logika aplikacyjna).
1.3. AsyncTCP obsługuje wiele gniazd jednocześnie (klient/serwer), co przy klasycznym WiFiClient wymagałoby złożonego ręcznego multiplexer’a.

2. Przygotowanie środowiska

2.1. Arduino IDE
• Płytki: Espressif ESP32 (Board Manager URL: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json).
• Biblioteka: AsyncTCP by me-no-dev (aktualna gałąź 1.1.3).
2.2. PlatformIO

    platform = espressif32
    framework = arduino
    lib_deps = me-no-dev/AsyncTCP

3. Kompletny kod (wersja „core”)

#include <WiFi.h>
#include <AsyncTCP.h>
/* --- konfiguracja użytkownika --- */
constexpr char SSID[]   = "TwojaSiecWiFi";
constexpr char PASS[]   = "HasloWiFi";
constexpr char HOST[]   = "192.168.1.100";   // IP lub domena serwera
constexpr uint16_t PORT = 8080;              // port serwera
/* --- obiekty globalne --- */
AsyncClient client;          // pojedyncze połączenie
bool connected   = false;    // flaga stanu
uint32_t lastTx  = 0;        // znacznik czasu dla transmisji okresowej
/* --- deklaracje pomocnicze --- */
void connectToServer();
/* ---------- SETUP ---------- */
void setup()
{
  Serial.begin(115200);
  /* 1. Wi-Fi (blokujące, dozwolone w setup) */
  WiFi.mode(WIFI_STA);
  WiFi.begin(SSID, PASS);
  Serial.print(F("Łączenie z Wi-Fi"));
  while (WiFi.status() != WL_CONNECTED) {
    delay(400);
    Serial.print('.');
  }
  Serial.printf("\nPołączono! IP ESP32: %s\n", WiFi.localIP().toString().c_str());
  /* 2. Rejestracja callbacków */
  client.onConnect([](void*, AsyncClient* c){
      connected = true;
      Serial.println(F(">> CONNECT OK"));
      c->write("Hello from ESP32!\n");
  });
  client.onData([](void*, AsyncClient* c, void* data, size_t len){
      Serial.printf(">> RX %uB: ", len);
      Serial.write((uint8_t*)data, len);           // wypisz odebrane bajty
      Serial.println();
  });
  client.onDisconnect([](void*, AsyncClient*){
      connected = false;
      Serial.println(F(">> DISCONNECT"));
  });
  client.onError([](void*, AsyncClient*, int8_t err){
      Serial.printf(">> ERROR %d\n", err);
      connected = false;
  });
  client.onTimeout([](void*, AsyncClient*, uint32_t time){
      Serial.printf(">> TIMEOUT after %lu ms\n", time);
  });
  /* 3. Pierwsze łączenie */
  connectToServer();
}
/* ---------- LOOP ---------- */
void loop()
{
  /* cykliczna wysyłka co 10 s */
  if (connected && (millis() - lastTx > 10'000)) {
      lastTx = millis();
      String payload = "Ping: " + String(lastTx) + "\n";
      if (client.space() >= payload.length() && client.canSend())
          client.write((uint8_t*)payload.c_str(), payload.length());
  }
  /* prosty autoretry co 5 s gdy rozłączony */
  static uint32_t lastRetry = 0;
  if (!connected && (millis() - lastRetry > 5'000)) {
      lastRetry = millis();
      connectToServer();
  }
}
/* ---------- FUNKCJE ---------- */
void connectToServer()
{
  Serial.printf("Próba połączenia z %s:%u ...\n", HOST, PORT);
  if (!client.connect(HOST, PORT))
      Serial.println(F("init/connect NIEUDANE (brak buf. albo zły host)"));
}

4. Co dzieje się „pod maską”?

4.1. Po wywołaniu client.connect() stos TCP w LwIP inicjuje strukturę PCB, ale funkcja natychmiast zwraca – samo zestawienie trzech pakietów (SYN–SYN/ACK–ACK) obsługuje RTOS w osobnym wątku sieciowym.
4.2. Po otrzymaniu ACK biblioteka wykonuje callback onConnect, sygnalizując gotowość wysyłania.
4.3. client.write() buforuje dane w ramkach LwIP do czasu wolnego okienka TCP i potwierdzeń (onAck() dostępny opcjonalnie).


Aktualne informacje i trendy

• 2024: Espressif publikuje ESP-IDF component esp32-async/asynctcp (wersje 3.x) kompatybilny z Arduino-ESP32 ≥ 3.0 i ESP-IDF v5; warto rozważyć migrację przy projektach budowanych natywnie w ESP-IDF.
• Coraz częściej wykorzystywany jest security layer TLS (AsyncSSL lub WiFiClientSecure) – w zastosowaniach produkcyjnych zaleca się szyfrowanie, do czego potrzebny jest większy przydział RAM (≥ 300 kB).
• W projektach wielozadaniowych popularne staje się przypisywanie AsyncClient do oddzielnego rdzenia (funkcje FreeRTOS xTaskCreatePinnedToCore) i komunikacja przez kolejki.


Wspierające wyjaśnienia i detale

• Funkcja space() zwraca wolny bufor wysyłkowy (domyślnie 536 B, konfigurowalny w CONFIG_ASYNC_TCP_RX_SIZE).
• Kod błędu w onError odpowiada definicjom lwIP err_t (np. -11 = ERR_TIMEOUT, -12 = ERR_RTE).
• Połączenie można zamknąć elegancko wywołując client.close(); wyśle FIN i wywoła onDisconnect.
• Jeśli konieczne jest sklejanie wiadomości z wielu ramek, zastosuj własny bufor i delimitery (\n, STX/ETX, nagłówek z długością, JSON‐RPC itp.).


Aspekty etyczne i prawne

• W środowiskach przemysłowych połączenia nieszyfrowane (raw TCP) mogą naruszać politykę IoT Security – rozważ TLS lub przynajmniej wewnętrzny VLAN.
• Nadmierny ruch generowany przez pingi/keep-alive może łamać regulaminy sieci operatorów komórkowych – przestrzegaj umów SLA.
• Biblioteka AsyncTCP jest licencjonowana na MIT – zgodnie z licencją należy zachować adnotację o autorach w dokumentacji produktu.


Praktyczne wskazówki

  1. Testuj lokalnie z nc -l -p 8080 lub Python socketserver – łatwe logowanie i echo.
  2. Przy debugowaniu włącz Core Debug na poziom „Verbose” (menuconfig w IDF) – zobaczysz pakiety SYN/ACK w terminalu.
  3. Jeśli utrata połączeń jest częsta, zbadaj limity routera (NAT table timeout) albo obniż tcp_keepalive (client.setAckTimeout() w wersjach 1.2+).
  4. Zawsze sprawdzaj wolną pamięć (esp_get_free_heap_size()) – każde otwarte gniazdo ≈ 11 kB.

Ewentualne zastrzeżenia lub uwagi dodatkowe

• Na starych wersjach Arduino-ESP32 ≤ 2.0.5 występuje błąd „Guru Meditation: lwip-tcp” po dużym ruchu – zaktualizuj SDK.
AsyncClient NIE obsługuje IPv6; jeśli sieć to wymusza, potrzebny jest niskopoziomowy moduł LwIP.
• Przy wielu klientów (>5) konieczna może być zmiana CONFIG_LWIP_MAX_SOCKETS i rozmiaru sterty.


Sugestie dalszych badań

• Integracja AsyncTCP z MQTT-over-TLS (np. biblioteka AsyncMqttClient) – pełna asynchroniczność publish/subscribe.
• Mechanizmy zerowej konfiguracji (mDNS, DNS-SD) do dynamicznego wyszukiwania serwerów.
• Analiza wydajności: wpływ tcp_window_size i nagle_disable na throughput.
• Hard RT tasks: przeniesienie warstwy biznesowej na drugi rdzeń (CORE1) i synchronizacja za pomocą FreeRTOS queue.


Krótkie podsumowanie

Przedstawiony przykład pokazuje, jak w kilku liniach uruchomić w pełni asynchronicznego klienta TCP na ESP32, który:
• nie blokuje głównej pętli,
• poprawnie reaguje na zdarzenia sieciowe,
• automatycznie wznawia połączenie.

Zaktualizowana biblioteka AsyncTCP (2024) pozostaje lekkim i elastycznym rozwiązaniem do komunikacji surowym TCP. W środowiskach produkcyjnych zalecam dodanie TLS, watchdogów połączeń i dokładnego monitoringu pamięci.

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.