ESP32 server AP na socketach przykładowy kod w Aruino
Tak. Jeśli chcesz zrobić ESP32 jako Access Point (AP) i jednocześnie uruchomić serwer TCP na surowych socketach w Arduino, to należy użyć:
WiFi.mode(WIFI_AP)WiFi.softAP(...)lwip/sockets.hPoniżej masz kompletny, działający przykład dla Arduino IDE.
ESP32 tworzy własną sieć Wi-Fi, a po połączeniu klient może otworzyć połączenie TCP i wysyłać komendy tekstowe.
Najważniejsze punkty:
select()PING, STATUS, LEDON, LEDOFFPoniżej masz wersję, którą polecam jako punkt wyjścia, jeśli przez „na socketach” rozumiesz prawdziwe BSD sockets / lwIP, a nie tylko WiFiServer.
#include <WiFi.h>
#include <lwip/sockets.h>
#include <lwip/inet.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#ifndef LED_BUILTIN
#define LED_BUILTIN 2
#endif
const char* AP_SSID = "ESP32_SOCKET_AP";
const char* AP_PASS = "12345678";
constexpr uint16_t SERVER_PORT = 3333;
constexpr int MAX_CLIENTS = 4;
constexpr size_t RX_LINE_SIZE = 128;
const int LED_PIN = LED_BUILTIN;
int serverSock = -1;
int clientSocks[MAX_CLIENTS];
char lineBuf[MAX_CLIENTS][RX_LINE_SIZE];
size_t lineLen[MAX_CLIENTS];
bool setNonBlocking(int sock) {
int flags = fcntl(sock, F_GETFL, 0);
if (flags < 0) return false;
return fcntl(sock, F_SETFL, flags | O_NONBLOCK) == 0;
}
void sendText(int sock, const char* text) {
if (sock >= 0) {
send(sock, text, strlen(text), 0);
}
}
void closeClient(int idx) {
if (idx < 0 || idx >= MAX_CLIENTS) return;
if (clientSocks[idx] >= 0) {
close(clientSocks[idx]);
clientSocks[idx] = -1;
}
lineLen[idx] = 0;
lineBuf[idx][0] = '\0';
}
void processCommand(int idx, const char* cmd) {
int sock = clientSocks[idx];
Serial.printf("[Klient %d] CMD: %s\n", idx, cmd);
if (strcasecmp(cmd, "PING") == 0) {
sendText(sock, "PONG\r\n");
}
else if (strcasecmp(cmd, "STATUS") == 0) {
char out[128];
snprintf(out, sizeof(out),
"OK uptime_ms=%lu free_heap=%u stations=%d led=%d\r\n",
millis(),
ESP.getFreeHeap(),
WiFi.softAPgetStationNum(),
digitalRead(LED_PIN));
sendText(sock, out);
}
else if (strcasecmp(cmd, "LEDON") == 0) {
digitalWrite(LED_PIN, HIGH);
sendText(sock, "LED=ON\r\n");
}
else if (strcasecmp(cmd, "LEDOFF") == 0) {
digitalWrite(LED_PIN, LOW);
sendText(sock, "LED=OFF\r\n");
}
else if (strcasecmp(cmd, "HELP") == 0) {
sendText(sock, "Commands: PING, STATUS, LEDON, LEDOFF, HELP\r\n");
}
else if (strlen(cmd) == 0) {
// ignoruj pustą linię
}
else {
sendText(sock, "ERR unknown command\r\n");
}
}
void feedRxData(int idx, const char* data, int len) {
for (int i = 0; i < len; i++) {
char c = data[i];
if (c == '\r') continue;
if (c == '\n') {
lineBuf[idx][lineLen[idx]] = '\0';
processCommand(idx, lineBuf[idx]);
lineLen[idx] = 0;
lineBuf[idx][0] = '\0';
continue;
}
if (lineLen[idx] < RX_LINE_SIZE - 1) {
lineBuf[idx][lineLen[idx]++] = c;
} else {
sendText(clientSocks[idx], "ERR line too long\r\n");
lineLen[idx] = 0;
lineBuf[idx][0] = '\0';
}
}
}
void acceptNewClient() {
struct sockaddr_in clientAddr;
socklen_t addrLen = sizeof(clientAddr);
int newSock = accept(serverSock, (struct sockaddr*)&clientAddr, &addrLen);
if (newSock < 0) {
return;
}
setNonBlocking(newSock);
int slot = -1;
for (int i = 0; i < MAX_CLIENTS; i++) {
if (clientSocks[i] < 0) {
slot = i;
break;
}
}
char ip[16];
inet_ntoa_r(clientAddr.sin_addr, ip, sizeof(ip));
if (slot < 0) {
Serial.printf("Brak wolnego slotu dla klienta %s\n", ip);
sendText(newSock, "ERR server busy\r\n");
close(newSock);
return;
}
clientSocks[slot] = newSock;
lineLen[slot] = 0;
lineBuf[slot][0] = '\0';
Serial.printf("Klient %s:%u -> slot %d\n", ip, ntohs(clientAddr.sin_port), slot);
sendText(newSock, "ESP32 TCP server ready\r\nCommands: PING, STATUS, LEDON, LEDOFF, HELP\r\n");
}
void setupServerSocket() {
serverSock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (serverSock < 0) {
Serial.println("Blad: socket()");
return;
}
int opt = 1;
setsockopt(serverSock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (!setNonBlocking(serverSock)) {
Serial.println("Uwaga: nie udalo sie ustawic trybu nieblokujacego");
}
struct sockaddr_in serverAddr;
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
serverAddr.sin_port = htons(SERVER_PORT);
if (bind(serverSock, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
Serial.printf("Blad: bind(), errno=%d\n", errno);
close(serverSock);
serverSock = -1;
return;
}
if (listen(serverSock, MAX_CLIENTS) < 0) {
Serial.printf("Blad: listen(), errno=%d\n", errno);
close(serverSock);
serverSock = -1;
return;
}
Serial.printf("Serwer TCP slucha na porcie %u\n", SERVER_PORT);
}
void setup() {
Serial.begin(115200);
delay(1000);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
for (int i = 0; i < MAX_CLIENTS; i++) {
clientSocks[i] = -1;
lineLen[i] = 0;
lineBuf[i][0] = '\0';
}
WiFi.mode(WIFI_AP);
if (!WiFi.softAP(AP_SSID, AP_PASS)) {
Serial.println("Blad: WiFi.softAP()");
return;
}
Serial.println();
Serial.println("ESP32 uruchomiony w trybie AP");
Serial.print("SSID: ");
Serial.println(AP_SSID);
Serial.print("Haslo: ");
Serial.println(AP_PASS);
Serial.print("IP AP: ");
Serial.println(WiFi.softAPIP());
setupServerSocket();
Serial.println("Test z PC:");
Serial.println("nc 192.168.4.1 3333");
}
void loop() {
if (serverSock < 0) {
delay(1000);
return;
}
fd_set readSet;
FD_ZERO(&readSet);
FD_SET(serverSock, &readSet);
int maxFd = serverSock;
for (int i = 0; i < MAX_CLIENTS; i++) {
if (clientSocks[i] >= 0) {
FD_SET(clientSocks[i], &readSet);
if (clientSocks[i] > maxFd) {
maxFd = clientSocks[i];
}
}
}
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 200000; // 200 ms
int ready = select(maxFd + 1, &readSet, nullptr, nullptr, &timeout);
if (ready < 0) {
if (errno != EINTR) {
Serial.printf("Blad: select(), errno=%d\n", errno);
}
delay(1);
return;
}
if (ready == 0) {
delay(1);
return;
}
if (FD_ISSET(serverSock, &readSet)) {
acceptNewClient();
}
for (int i = 0; i < MAX_CLIENTS; i++) {
int sock = clientSocks[i];
if (sock < 0) continue;
if (FD_ISSET(sock, &readSet)) {
char rx[64];
int n = recv(sock, rx, sizeof(rx), 0);
if (n == 0) {
Serial.printf("Klient slot %d rozlaczyl sie\n", i);
closeClient(i);
}
else if (n < 0) {
if (errno != EWOULDBLOCK && errno != EAGAIN) {
Serial.printf("recv() error dla slot %d, errno=%d\n", i, errno);
closeClient(i);
}
}
else {
feedRxData(i, rx, n);
}
}
}
delay(1);
}
ESP32 uruchamia własną sieć Wi-Fi
WiFi.mode(WIFI_AP);WiFi.softAP(AP_SSID, AP_PASS);Tworzony jest serwer TCP
socket(AF_INET, SOCK_STREAM, IPPROTO_IP)bind()listen()Obsługa wielu klientów
select() monitoruje:Dane są odbierane jako strumień TCP
\nPrzetwarzanie komend
PING → PONGSTATUS → czas działania, wolny heap, liczba stacji APLEDON / LEDOFF → sterowanie LEDW praktyce dla ESP32 spotkasz dziś trzy główne podejścia:
WiFiServer
surowe sockety lwip/sockets.h
select(), recv(), send()HTTP / WebSocket
Technicznie:
Istotna korekta względem niektórych przykładowych odpowiedzi:
WiFiServer to nadal serwer TCP, ale jest to warstwa wyższego poziomulwip/sockets.hWiFiServer a lwip/sockets.h| Podejście | Poziom abstrakcji | Zastosowanie |
|---|---|---|
WiFiServer |
wyższy | szybki start, prosty TCP |
lwip/sockets.h |
niższy | większa kontrola, wiele klientów, select() |
accept() w loop()Blokujące podejście działa, ale ma wady:
Dlatego w przykładzie:
select()StringW systemach embedded lepiej ograniczać:
Dlatego zastosowano:
char[]To jest rozwiązanie bardziej inżynierskie i stabilniejsze.
Bardzo częsty błąd początkujących:
send() po stronie klienta odpowiada jednemu recv() po stronie serweraTo nie jest gwarantowane.
Możesz dostać:
Dlatego właśnie przykład używa:
\n jako znacznika końca ramki aplikacyjnejW tym przypadku aspekt prawny jest ograniczony, ale warto pamiętać o kilku rzeczach:
Bezpieczeństwo sieci
12345678 w projekcie produkcyjnymSterowanie urządzeniami
Zakłócenia i kompatybilność radiowa
115200ESP32_SOCKET_AP12345678Na Linux/macOS:
nc 192.168.4.1 3333
Na Windows:
PuTTY w trybie Rawncat, jeśli masz zainstalowany Nmap/NcatPING
STATUS
LEDON
LEDOFF
HELP
Ten kod jest raw TCP, więc przeglądarka nie jest idealnym klientem.
Przeglądarka oczekuje HTTP.
Jeżeli chcesz, aby po wejściu na adres ESP32 w przeglądarce wyświetlała się strona, musisz:
WebServersend()Minimalna odpowiedź HTTP wygląda tak:
const char* httpResponse =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain; charset=utf-8\r\n"
"Connection: close\r\n"
"\r\n"
"ESP32 AP server dziala\r\n";
while(client.connected())String\nerrnoESP32 w trybie AP potrafi pobierać zauważalny prąd impulsowy.
Jeżeli masz resety lub niestabilność:
LED_BUILTIN nie na każdej płytce oznacza fizycznie dostępną diodę192.168.4.1, ale najlepiej odczytać go z:WiFi.softAPIP()Jeśli projekt ma sterować czymś krytycznym, należy to dodać.
Jeżeli chcesz rozwinąć ten projekt, następne sensowne kroki to:
Mogę też przygotować od razu jedną z tych wersji:
WiFiServerJeśli pytasz o ESP32 server AP na socketach w Arduino, to najbardziej właściwe rozwiązanie to:
WIFI_APlwip/sockets.hselect()Kod powyżej jest poprawnym, praktycznym punktem startowym i jest technicznie lepszy od bardzo prostych przykładów blokujących.
Jeśli chcesz, mogę w następnym kroku przygotować: