ESP32 AP server na socketach – przykładowy kod Arduino, TCP echo, LED, select()

Pytanie

ESP32 server AP na socketach przykładowy kod w Aruino

Odpowiedź Sztucznej Inteligencji


Bezpośrednia odpowiedź na pytanie

  • Poniżej znajduje się kompletny szkic Arduino tworzący na ESP32 punkt dostępowy (AP) oraz serwer TCP oparty na surowych gniazdach (lwIP sockets).
  • Kod działa na aktualnym core Arduino-ESP32 v2.0.x i obsługuje wielu klientów w trybie nieblokującym.

Kluczowe punkty
• tryb WIFI_AP, SSID/hasło konfigurowalne
• statyczny adres IP 192.168.4.1/24
• serwer nasłuchuje na porcie 3333 (można zmienić)
• obsługa klientów przy użyciu select() – brak blokowania głównej pętli
• przykład echo + prosta komenda LED


Szczegółowa analiza problemu

1. Różne sposoby „serwera na socketach” w ESP32

  1. WiFiServer (warstwa abstrakcji Arduino) – szybki start, blokujące client.
  2. Raw lwIP sockets – pełna kontrola: socket(), bind(), listen(), select().
  3. WebSocket (nad TCP) – wygodne dla przeglądarek, wymaga biblioteki (WebSocketsServer).

Niniejszy przykład wykorzystuje wariant 2 – najbliższy „czystym” socketom, ale pokazuję też, jak dodać WebSocket lub serwer HTTP, jeśli potrzebujesz front-endu w przeglądarce.

2. Teoretyczne podstawy

ESP32 udostępnia pełny stos lwIP. W trybie AP przydziela sobie adres 192.168.4.1 (domyślnie) oraz serwuje DHCP wszystkim klientom. Raw socket + select() umożliwia obsługę wielu klientów bez wielowątkowości, co oszczędza RAM (≈8 kB/klient).

3. Kod – serwer TCP (Echo + LED)

#include <WiFi.h>
#include <lwip/sockets.h>      // niskopoziomowe sockety
#include <lwip/netdb.h>
constexpr char SSID[]     = "ESP32_AP_Socket";
constexpr char PASS[]     = "12345678";      // ≥8 znaków
constexpr uint16_t PORT   = 3333;            // port TCP
constexpr gpio_num_t LED  = GPIO_NUM_2;      // wbudowany LED
int srvSock = -1;
void setupAccessPoint()
{
  WiFi.mode(WIFI_AP);
  IPAddress ip(192,168,4,1), gw(192,168,4,1), mask(255,255,255,0);
  WiFi.softAPConfig(ip, gw, mask);
  if (!WiFi.softAP(SSID, PASS, 6, 0, 4)) {   // kanał 6, widoczna, max 4 klientów
    Serial.println(F("Błąd tworzenia AP!"));
    while(true);
  }
  Serial.printf("AP uruchomione. IP: %s\n", WiFi.softAPIP().toString().c_str());
}
bool initSocketServer()
{
  srvSock = socket(AF_INET, SOCK_STREAM, 0);
  if (srvSock < 0) return false;
  int opt = 1;
  setsockopt(srvSock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
  sockaddr_in addr{};
  addr.sin_family      = AF_INET;
  addr.sin_addr.s_addr = htonl(INADDR_ANY);
  addr.sin_port        = htons(PORT);
  if (bind(srvSock, (sockaddr*)&addr, sizeof(addr)) < 0) return false;
  if (listen(srvSock, 4) < 0) return false;
  Serial.printf("Socket server słucha na porcie %u\n", PORT);
  return true;
}
void handleClients()
{
  fd_set rfds;
  timeval tv{0, 2000};          // 2 ms timeout
  FD_ZERO(&rfds);
  FD_SET(srvSock, &rfds);
  int maxFd = srvSock;
  // Dodaj już otwarte klienty do zestawu
  for (int fd = 0; fd <= maxFd; ++fd)
    if (fd != srvSock && fd != 0) FD_SET(fd, &rfds);
  int act = select(maxFd + 1, &rfds, nullptr, nullptr, &tv);
  if (act <= 0) return;
  // Nowe połączenie
  if (FD_ISSET(srvSock, &rfds)) {
    int cli = accept(srvSock, nullptr, nullptr);
    if (cli >= 0) {
      Serial.printf("Nowy klient (%d)\n", cli);
      fcntl(cli, F_SETFL, O_NONBLOCK);      // nieblokujące
    }
  }
  // Dane od istniejących klientów
  char buf[256];
  for (int fd = 3; fd <= maxFd; ++fd) {
    if (fd == srvSock) continue;
    if (!FD_ISSET(fd, &rfds)) continue;
    int len = recv(fd, buf, sizeof(buf)-1, 0);
    if (len <= 0) {            // closed / error
      close(fd);
      Serial.printf("Klient %d zamknięty\n", fd);
      continue;
    }
    buf[len] = 0;
    Serial.printf("(%d) >> %s", fd, buf);
    // Prosta logika: komenda LED\n
    if (strncmp(buf, "LED ON", 6) == 0)  digitalWrite(LED, HIGH);
    if (strncmp(buf, "LED OFF",7) == 0)  digitalWrite(LED, LOW);
    // Echo
    send(fd, buf, len, 0);
  }
}
void setup()
{
  Serial.begin(115200);
  pinMode(LED, OUTPUT);
  digitalWrite(LED, LOW);
  setupAccessPoint();
  if (!initSocketServer()) {
    Serial.println("Nie udało się uruchomić serwera socket!");
    while(true);
  }
}
void loop()
{
  handleClients();              // ~2-3 ms
  // zadania użytkownika ...
}

Test:

  1. Połącz się z siecią „ESP32_AP_Socket” (hasło 12345678).
  2. telnet 192.168.4.1 3333
  3. Wyślij LED ON lub LED OFF, otrzymasz echo i zaświecisz diodę.

4. Rozszerzenia i praktyczne zastosowania

  • WebSocket – wystarczy dodać bibliotekę WebSocketsServer, nasłuch na porcie 81 i identyczna konfiguracja AP. Przydatne, gdy klientem jest przeglądarka.
  • HTTP-REST – jeśli potrzebujesz jednorazowych zapytań, prostszy będzie WebServer w Arduino.
  • UDP broadcast/multicast – dobra opcja przy wielu klientach, gdy ważne jest zmniejszenie overheadu.

Aktualne informacje i trendy

  • Core Arduino-ESP32 v2.0.11 (2024-05) przeniósł się na ESP-IDF 5.x ⇒ poprawione TLS 1.3 i WPA3-SAE w trybie AP.
  • W nowych rewizjach ESP32-C6/S3 dostępny jest Wi-Fi 6 802.11ax Target Wake-Time – zmniejsza energię przy AP zasilanym bateryjnie.
  • Coraz częściej korzysta się z ESP-Hosted lub ESP-Netif – można przenieść logikę socketów do osobnego rdzenia FreeRTOS.

Wspierające wyjaśnienia i detale

select() zużywa mniej RAM-u niż tworzenie tasku dla każdego klienta.
fcntl(fd, F_SETFL, O_NONBLOCK) eliminuje zawieszanie się przy recv().
• Maks. 10–12 jednoczesnych socketów TCP przy 240 kB RAM (typowe dla ESP32-WROOM-32).


Aspekty etyczne i prawne

  • Nadawanie w paśmie 2,4 GHz w UE podlega ETSI EN 300 328 – ustaw moc dozwoloną (domyślne SDK ≤ 17 dBm).
  • Hasło WPA2-PSK wymagane min 8 znaków; dla wrażliwych danych rozważ WPA3-SAE (dostępne od ESP-IDF 4.4).
  • Dane przesyłane plain-TCP nie są szyfrowane – nie używaj do transmisji danych osobowych.

Praktyczne wskazówki

  • Stosuj watchdog sprzętowy (esp_task_wdt) – sockety mogą się „zawiesić” przy ataku DoS.
  • Buforuj dane w SRAM-DMA (psramFound()? użyj PSRAM > 4 kB na klienta).
  • Jeśli oczekujesz > 4 klientów, zwiększ softAP() max_connection i listen(srvSock,N).

Ewentualne zastrzeżenia lub uwagi dodatkowe

  • W wersjach core < 2.0.4 select() mógł sporadycznie zwracać -1 z errno 11; aktualizacja rozwiązuje problem.
  • ESP32 w trybie AP nie przechodzi w głębokie uśpienie (WiFi musi być off), więc nie nadaje się do ultraniskiej energii.

Sugestie dalszych badań

  • Migracja szkicu do native ESP-IDF – większa wydajność, certyfikaty TLS, Firewalls.
  • mDNS w AP (niestandardowe, wymaga dns-sd) – uprości wykrywanie urządzeń bez wpisywania IP.
  • ESP-Matter (Thread + Wi-Fi) – w 2024 roku Espressif dodało tryb Soft-AP + Matter Soft-AP Commissioning.

Krótkie podsumowanie

Przedstawiony szkic demonstruje, jak w kilkudziesięciu liniach uruchomić na ESP32:
• własną sieć Wi-Fi (AP)
• nieblokujący serwer TCP oparty na surowych socketach
• prostą logikę komend (LED) i echo danych
Dzięki użyciu select() kod pozostaje responsywny, skalowalny i gotowy do rozbudowy o WebSockety lub REST API.

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.