Guias · 17 min de leitura

Servidor web assíncrono no ESP32 com WebSocket: painel de controle em tempo real

Servidor web assíncrono no ESP32 com WebSocket: acione um relé e acompanhe dados de sensores ao vivo sem recarregar a página. Instalação, PROGMEM ou LittleFS, esp32.local, senha e soluções.

Um servidor web assíncrono no ESP32, feito com a biblioteca ESPAsyncWebServer, transforma seu ESP32 em um servidor web rápido, capaz de responder a vários celulares e computadores ao mesmo tempo. Com o suporte a WebSocket que já vem na biblioteca, o navegador e a placa mantêm uma linha sempre aberta: você aperta o botão na página e o relé aciona na hora; quando a temperatura muda, o número na página se atualiza sozinho, sem recarregar. Todos os outros dispositivos com o mesmo painel aberto veem sua alteração no mesmo segundo. Resumindo, essa dupla é o jeito mais prático de montar seu próprio painel de casa inteligente dentro da sua rede doméstica, sem nenhum serviço de nuvem.

Neste guia você vai ver primeiro como um servidor assíncrono se diferencia da biblioteca WebServer clássica e por que o WebSocket é mais rápido que requisições HTTP comuns. Depois vai instalar as bibliotecas certas, rodar o servidor mais simples possível e montar um painel de controle em tempo real com um relé e um sensor DHT22. Em seguida vai aprender a guardar o HTML em PROGMEM ou no LittleFS, usar esp32.local no lugar do endereço IP, lidar com vários clientes, colocar uma senha simples no painel e resolver os erros de compilação e de conexão mais comuns. O código foi escrito para funcionar tanto com as versões 2.x quanto 3.x do core do ESP32 no Arduino IDE.

Placa de desenvolvimento ESP32 para um projeto de servidor web
ESP32 DevKit: graças ao chip Wi-Fi, ela roda um servidor web sozinha; o painel que você abre no celular vem direto da memória flash desta placa.

O que é o servidor web assíncrono do ESP32 e como ele funciona?

A biblioteca WebServer.h que vem com o core do ESP32 é síncrona: as requisições que chegam só são tratadas, uma de cada vez, quando server.handleClient() é chamado dentro do loop(). Se o loop() tiver um delay() de 2 segundos, a página também espera 2 segundos; se duas pessoas se conectarem ao mesmo tempo, uma espera a outra terminar. Já a ESPAsyncWebServer roda em uma tarefa separada do FreeRTOS (async_tcp), criada pela biblioteca AsyncTCP. Quando uma requisição chega, os cabeçalhos são lidos em segundo plano, a função que você escreveu (o callback) é chamada assim que a requisição está pronta, e a resposta também é enviada em segundo plano. Enquanto isso, o loop() segue com o próprio trabalho. Isso permite manter várias conexões abertas ao mesmo tempo e torna possíveis conexões sempre abertas, como WebSocket e Server-Sent Events (SSE). Se você tem curiosidade sobre os dois núcleos e as tarefas do ESP32, dê uma olhada no artigo ESP32 FreeRTOS.

CaracterísticaWebServer.h (síncrona)ESPAsyncWebServer (assíncrona)
InstalaçãoJá vem com o core do ESP322 bibliotecas pelo Gerenciador de Bibliotecas
Tratamento das requisiçõesUma por vez, via handleClient() no loop()Na tarefa async_tcp em segundo plano, várias ao mesmo tempo
delay() dentro do loop()Atrasa a resposta da páginaNão afeta o servidor
WebSocketNão tem; precisa de outra biblioteca (ex.: a biblioteca WebSockets de Markus Sattler)Integrado (AsyncWebSocket)
Server-Sent EventsNão temIntegrado (AsyncEventSource)
Servir arquivos estáticosCom serveStaticserveStatic, suporte automático a .gz e controle de cache
Atenção aA mais fácil de aprenderNada de delay() ou trabalho demorado dentro dos callbacks

O que é WebSocket e qual a diferença para uma requisição HTTP?

Em uma requisição HTTP normal, o navegador abre uma conexão, diz “me dê esta página”, recebe a resposta e a conversa termina. O servidor não consegue avisar o navegador de nada por conta própria; para ver um valor novo, o navegador precisa perguntar de novo. O WebSocket (RFC 6455) é um handshake que começa como HTTP: o navegador adiciona o cabeçalho Upgrade: websocket à requisição, o ESP32 aceita com a resposta 101 Switching Protocols e a mesma conexão TCP continua aberta. A partir daí, os dois lados podem enviar mensagens quando quiserem (full-duplex). Cada mensagem carrega só de 2 a 14 bytes de overhead, enquanto cada requisição HTTP carrega centenas de bytes de cabeçalho. Por isso as mensagens WebSocket chegam em milissegundos, e esse é o método mais eficiente para painéis que enviam várias atualizações por segundo. Veja como se comparam as quatro formas de manter uma página atualizada com os valores dos sensores:

MétodoComo funcionaLatência e overheadQuando usar
Recarregar a página (meta refresh)O navegador baixa a página inteira de novo a cada poucos segundosAlto; a página pisca toda vezEvite
Polling com fetchO JavaScript pede um JSON pequeno a cada 1–2 segundosMédio; cada requisição leva cabeçalhos novosUm único valor que muda pouco
Server-Sent Events (SSE)O servidor envia eventos em um só sentido por uma conexão abertaBaixo; só do servidor para o navegadorPainéis só de monitoramento
WebSocketTroca de mensagens nos dois sentidos por uma conexão abertaO mais baixo; quadros de poucos bytesBotões, sliders e dados ao vivo juntos

Bibliotecas necessárias: instalando ESPAsyncWebServer e AsyncTCP

  1. Confira se o pacote de placas do ESP32 (Espressif Systems) está instalado no Arduino IDE; os passos de instalação estão em O que é o ESP32?
  2. Abra o Gerenciador de Bibliotecas (Ctrl+Shift+I) e busque ESPAsyncWebServer. Instale a biblioteca ESP Async WebServer cujo autor é ESP32Async.
  3. Essa biblioteca não declara a dependência ao Gerenciador de Bibliotecas, então o IDE não vai oferecer bibliotecas extras: instale separadamente a biblioteca Async TCP, também publicada pela ESP32Async. Em um ESP8266, você precisa no lugar dela da biblioteca ESP Async TCP da ESP32Async (ESPAsyncTCP.h).
  4. Para o exemplo do painel, instale a DHT sensor library da Adafruit e a dependência Adafruit Unified Sensor que ela pede. Se for a primeira vez que você instala uma biblioteca, veja o guia como instalar bibliotecas no Arduino.
  5. Se a sua pasta Documentos/Arduino/libraries tiver pastas ESPAsyncWebServer-master, AsyncTCP-master ou ESPAsyncWebSrv adicionadas por ZIP no passado, apague-as. Duas cópias da mesma biblioteca fazem o compilador escolher a errada.
A maioria dos tutoriais antigos na internet aponta para a biblioteca ESPAsyncWebServer original, na conta me-no-dev. Essa biblioteca não é mais mantida e não compila com a versão 3.x do core do ESP32; a compilação para com o erro 'mbedtls_md5_starts_ret' was not declared in this scope. A solução é apagar a cópia antiga e instalar as versões da ESP32Async. Na versão nova, send_P() também está obsoleta (deprecated): no ESP32 você pode enviar um array PROGMEM direto com request->send(200, "text/html", index_html).

Primeiro passo: o servidor mais simples com AsyncWebServer

Antes de partir para o painel, vamos ver como a biblioteca funciona com um exemplo pequeno. O código abaixo conecta o ESP32 à sua rede doméstica, mostra uma saudação na página inicial e liga e desliga o LED integrado pelos endereços /led?durum=1 e /led?durum=0. Carregue o código, abra o Monitor Serial em 115200 baud e digite o endereço que aparece no navegador de um celular conectado ao mesmo Wi-Fi. Se a placa não entrar na rede, use o código de diagnóstico do guia ESP32 não conecta ao Wi-Fi.

C++ (Arduino IDE)
1#include <WiFi.h>
2#include <AsyncTCP.h>
3#include <ESPAsyncWebServer.h>
4
5const char* ssid = "AgAdi";          // Wi-Fi adın / your Wi-Fi name
6const char* password = "Sifre123";   // Wi-Fi şifren / your Wi-Fi password
7const int LED_PIN = 2;               // çoğu DevKit'te dahili LED / onboard LED on most DevKits
8
9AsyncWebServer server(80);           // 80 = standart HTTP portu / standard HTTP port
10bool ledAcik = false;
11
12void setup() {
13  Serial.begin(115200);
14  pinMode(LED_PIN, OUTPUT);
15
16  WiFi.mode(WIFI_STA);
17  WiFi.begin(ssid, password);
18  while (WiFi.status() != WL_CONNECTED) {
19    delay(500);
20    Serial.print(".");
21  }
22  Serial.println();
23  Serial.print("Adres / address: http://");
24  Serial.println(WiFi.localIP());
25
26  // Ana sayfa / home page
27  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
28    request->send(200, "text/plain", "Merhaba, ben ESP32 / Hello from ESP32");
29  });
30
31  // /led?durum=1 yakar, /led?durum=0 söndürür / turns the LED on or off
32  server.on("/led", HTTP_GET, [](AsyncWebServerRequest *request) {
33    if (request->hasParam("durum")) {
34      ledAcik = request->getParam("durum")->value() == "1";
35      digitalWrite(LED_PIN, ledAcik ? HIGH : LOW);
36    }
37    request->send(200, "text/plain", ledAcik ? "LED: ON" : "LED: OFF");
38  });
39
40  // Tanımsız adresler / unknown addresses
41  server.onNotFound([](AsyncWebServerRequest *request) {
42    request->send(404, "text/plain", "Sayfa yok / Not found");
43  });
44
45  server.begin();
46}
47
48void loop() {
49  // Sunucu arka planda çalışır, loop() boş kalabilir / the server runs in the background
50}
ComandoO que faz
AsyncWebServer server(80)Cria o servidor que escuta a porta HTTP 80
server.on("/led", HTTP_GET, ...)Registra a função (callback) que roda para um endereço e um tipo de requisição
request->hasParam("durum")Verifica se o endereço tem um parâmetro ?durum=...
request->getParam("durum")->value() (retorna o valor do parâmetro como String)Retorna o valor do parâmetro como String
request->send(código, tipo, conteúdo)Envia a resposta: 200 OK, 404 não encontrado; tipo text/plain, text/html ou application/json
server.onNotFound(...)Roda para todo endereço que não foi definido
server.begin()Inicia o servidor; chame depois de todas as linhas server.on()
Um loop() vazio não é erro; o servidor roda na própria tarefa. Ao contrário: não use delay(), laços while longos nem requisições que buscam dados na internet dentro dos callbacks. A tarefa async_tcp trava enquanto isso e, alguns segundos depois, o ESP32 reinicia com a mensagem Task watchdog got triggered. Passe os trabalhos demorados para o loop() com uma variável de flag e faça a temporização com millis().

Componentes e ligações do painel de controle

No exemplo do painel, você vai ligar e desligar uma lâmpada ou uma ventoinha com um módulo relé e medir temperatura e umidade com um DHT22. Você vai precisar de um ESP32 DevKit, um módulo relé de 5 V de um canal, um módulo sensor DHT22 (ou DHT11), uma protoboard e jumpers. Na hora de escolher o relé, prefira módulos com optoacoplador. O DHT22 mede de −40 a 80 °C com precisão de ±0,5 °C e pode ser lido no máximo uma vez a cada 2 segundos; o DHT11 é mais barato, mas tem precisão de ±2 °C na faixa de 0–50 °C. Para os detalhes do sensor, veja o artigo sensor de temperatura e umidade DHT11 e, para o mapa de pinos da sua placa, a ferramenta Pinagem.

Pino do móduloESP32 DevKitESP8266 NodeMCUObservação
Relé VCCVIN (5 V)VIN ou VU (5 V)A bobina do relé precisa de 5 V; é alimentada pela USB
Relé GNDGNDGNDGND comum
Relé INGPIO 26D1 (GPIO 5)A maioria dos módulos aciona com sinal LOW
DHT22 VCC (+)3V33V3Alimentado com 3,3 V, a linha de dados também fica em 3,3 V
DHT22 DATA (OUT)GPIO 4D2 (GPIO 4)Sensor avulso: resistor de 10 kΩ entre DATA e VCC
DHT22 GND (−)GNDGNDGND comum
Os pinos do ESP32 têm saída de 3,3 V. Alguns módulos relé de 5 V não reconhecem esse nível como HIGH, e o relé nunca desarma; com um módulo assim, use um relé compatível com 3,3 V, um módulo com jumper JD-VCC ou um transistor no meio. Se você for chavear a tensão da rede elétrica (230 V ou 120 V) com o relé, faça as ligações com a energia desligada, não deixe bornes com parafuso expostos e, se tiver dúvida, teste primeiro com uma carga de baixa tensão (uma fita de LED ou uma ventoinha de 12 V). Os detalhes estão em O que é um módulo relé?

Painel de controle em tempo real: relé, DHT22 e WebSocket

Agora o projeto principal. O código abaixo é um arquivo só: o HTML, o CSS e o JavaScript ficam dentro dele como um array PROGMEM. Quando a página abre, o navegador abre uma conexão WebSocket com /ws. A cada 2 segundos o ESP32 lê o DHT22 e envia a temperatura, a umidade, o estado do relé e o número de dispositivos conectados para todos os navegadores em uma única mensagem JSON. Quando você aperta o botão, o navegador envia a mensagem toggle, o ESP32 inverte o relé e de novo avisa todo mundo do novo estado. Troque o nome da rede e a senha, carregue o código e abra no navegador http://esp32.local ou o endereço IP mostrado no Monitor Serial.

C++ (Arduino IDE)
1#include <WiFi.h>
2#include <AsyncTCP.h>
3#include <ESPAsyncWebServer.h>
4#include <ESPmDNS.h>
5#include <DHT.h>
6
7const char* ssid = "AgAdi";          // Wi-Fi adın / your Wi-Fi name
8const char* password = "Sifre123";   // Wi-Fi şifren / your Wi-Fi password
9
10const int ROLE_PIN = 26;             // röle modülü IN / relay module IN
11const bool ROLE_AKTIF_LOW = true;    // çoğu modül LOW ile çeker / most modules switch on LOW
12const int DHT_PIN = 4;               // DHT22 veri pini / DHT22 data pin
13
14DHT dht(DHT_PIN, DHT22);             // DHT11 için DHT11 yaz / write DHT11 for a DHT11
15AsyncWebServer server(80);
16AsyncWebSocket ws("/ws");            // WebSocket adresi / WebSocket path
17
18bool roleAcik = false;
19float sicaklik = NAN;
20float nem = NAN;
21volatile bool gonder = false;        // loop() yayın yapsın mı? / should loop() broadcast?
22unsigned long sonOlcum = 0;
23unsigned long sonTemizlik = 0;
24
25// Tarayıcıya giden sayfa / the page sent to the browser
26const char index_html[] PROGMEM = R"rawliteral(
27<!DOCTYPE html>
28<html lang="tr">
29<head>
30<meta charset="utf-8">
31<meta name="viewport" content="width=device-width, initial-scale=1">
32<title>ESP32 Panel</title>
33<style>
34body{font-family:sans-serif;background:#0f172a;color:#e2e8f0;text-align:center;margin:0;padding:16px}
35.kart{background:#1e293b;border-radius:12px;padding:16px;margin:12px auto;max-width:320px}
36.deger{font-size:2.4em;font-weight:bold}
37button{font-size:1.2em;padding:14px 32px;border:0;border-radius:8px;color:#fff;width:100%}
38.acik{background:#16a34a}.kapali{background:#475569}
39</style>
40</head>
41<body>
42<h2>ESP32 Panel</h2>
43<p id="baglanti">Bağlanıyor / Connecting...</p>
44<div class="kart">Sıcaklık / Temperature<div class="deger"><span id="sicaklik">--</span> &deg;C</div></div>
45<div class="kart">Nem / Humidity<div class="deger"><span id="nem">--</span> %</div></div>
46<div class="kart">Röle / Relay<p><button id="dugme" class="kapali" onclick="degistir()">KAPALI / OFF</button></p></div>
47<p>Bağlı cihaz / Clients: <span id="istemci">0</span></p>
48<script>
49let ws;
50const $ = (id) => document.getElementById(id);
51function baglan() {
52  ws = new WebSocket('ws://' + location.host + '/ws');
53  ws.onopen = () => { $('baglanti').textContent = 'Bağlı / Connected'; };
54  ws.onclose = () => {
55    $('baglanti').textContent = 'Koptu, yeniden deneniyor / Reconnecting...';
56    setTimeout(baglan, 2000);
57  };
58  ws.onmessage = (e) => {
59    const d = JSON.parse(e.data);
60    $('sicaklik').textContent = d.sicaklik === null ? '--' : d.sicaklik.toFixed(1);
61    $('nem').textContent = d.nem === null ? '--' : d.nem.toFixed(0);
62    $('istemci').textContent = d.istemci;
63    $('dugme').textContent = d.role ? 'AÇIK / ON' : 'KAPALI / OFF';
64    $('dugme').className = d.role ? 'acik' : 'kapali';
65  };
66}
67function degistir() {
68  if (ws.readyState === WebSocket.OPEN) ws.send('toggle');
69}
70baglan();
71</script>
72</body>
73</html>
74)rawliteral";
75
76void roleAyarla(bool acik) {
77  roleAcik = acik;
78  digitalWrite(ROLE_PIN, ROLE_AKTIF_LOW ? !acik : acik);
79}
80
81// Durumu JSON metnine çevir / build the state as JSON text
82String durumJson() {
83  String s = "{\"role\":";
84  s += roleAcik ? "true" : "false";
85  s += ",\"sicaklik\":";
86  s += isnan(sicaklik) ? String("null") : String(sicaklik, 1);
87  s += ",\"nem\":";
88  s += isnan(nem) ? String("null") : String(nem, 0);
89  s += ",\"istemci\":";
90  s += (unsigned long)ws.count();
91  s += "}";
92  return s;
93}
94
95// WebSocket olayları (async_tcp görevinde çalışır) / WebSocket events (run in the async_tcp task)
96void wsOlay(AsyncWebSocket *sunucu, AsyncWebSocketClient *client, AwsEventType type,
97            void *arg, uint8_t *data, size_t len) {
98  switch (type) {
99    case WS_EVT_CONNECT:
100      Serial.printf("Istemci #%u baglandi / connected: %s\n",
101                    (unsigned)client->id(), client->remoteIP().toString().c_str());
102      gonder = true;                 // yeni gelen güncel durumu alsın / newcomer gets the state
103      break;
104    case WS_EVT_DISCONNECT:
105      Serial.printf("Istemci #%u ayrildi / disconnected\n", (unsigned)client->id());
106      gonder = true;
107      break;
108    case WS_EVT_DATA: {
109      AwsFrameInfo *info = (AwsFrameInfo *)arg;
110      // Tek parça, tam bir metin mesajı mı? / a complete single-frame text message?
111      if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
112        if (len == 6 && memcmp(data, "toggle", 6) == 0) {
113          roleAyarla(!roleAcik);
114          gonder = true;             // tüm ekranlar eşitlensin / sync every screen
115        }
116      }
117      break;
118    }
119    default:                         // PONG, ERROR vb. / PONG, ERROR etc.
120      break;
121  }
122}
123
124void setup() {
125  Serial.begin(115200);
126  pinMode(ROLE_PIN, OUTPUT);
127  roleAyarla(false);                 // açılışta röle kapalı / relay off at start
128  dht.begin();
129
130  WiFi.mode(WIFI_STA);
131  WiFi.begin(ssid, password);
132  Serial.print("Baglaniyor / connecting");
133  while (WiFi.status() != WL_CONNECTED) {
134    delay(500);
135    Serial.print(".");
136  }
137  WiFi.setSleep(false);              // daha hızlı tepki / lower latency
138  Serial.println();
139  Serial.print("http://");
140  Serial.println(WiFi.localIP());
141
142  if (MDNS.begin("esp32")) {         // http://esp32.local
143    MDNS.addService("http", "tcp", 80);
144    Serial.println("http://esp32.local");
145  }
146
147  ws.onEvent(wsOlay);
148  server.addHandler(&ws);
149
150  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
151    request->send(200, "text/html", index_html);
152  });
153  server.onNotFound([](AsyncWebServerRequest *request) {
154    request->send(404, "text/plain", "Sayfa yok / Not found");
155  });
156  server.begin();
157}
158
159void loop() {
160  if (millis() - sonOlcum >= 2000) {       // DHT22 en fazla 2 sn'de bir / DHT22: once per 2 s
161    sonOlcum = millis();
162    float t = dht.readTemperature();
163    float h = dht.readHumidity();
164    if (!isnan(t) && !isnan(h)) {          // hatalı okumayı atla / skip failed reads
165      sicaklik = t;
166      nem = h;
167    }
168    gonder = true;
169  }
170
171  if (gonder) {
172    gonder = false;
173    ws.textAll(durumJson());               // tüm tarayıcılara yolla / send to every browser
174  }
175
176  if (millis() - sonTemizlik >= 1000) {
177    sonTemizlik = millis();
178    ws.cleanupClients();                   // kopuk istemcileri temizle / drop stale clients
179  }
180}

Como o código funciona?

  • AsyncWebSocket ws("/ws") cria o endpoint WebSocket, e server.addHandler(&ws) o conecta ao servidor. O endereço no JavaScript tem que bater exatamente com esse caminho.
  • A função wsOlay() é chamada a cada conexão, desconexão e mensagem recebida. WS_EVT_CONNECT significa um cliente novo, WS_EVT_DISCONNECT um cliente saindo e WS_EVT_DATA uma mensagem chegando.
  • AwsFrameInfo traz as informações do quadro recebido. Verificar final, index == 0 e len confirma que a mensagem chegou completa em um único quadro, e WS_TEXT que ela é texto. Comandos curtos sempre cabem em um quadro.
  • Os dados recebidos são comparados junto com o comprimento usando memcmp(), assim você não depende de existir ou não um caractere nulo no fim dos dados.
  • Como os callbacks rodam na tarefa async_tcp, eles só invertem o relé e levantam a flag gonder. O envio de fato acontece no loop() com ws.textAll(), então todas as mensagens saem de um lugar só, em ordem.
  • ws.cleanupClients() é chamado uma vez por segundo: ele libera a memória das conexões perdidas e fecha a conexão mais antiga quando o número de clientes passa do limite.
  • No lado do JavaScript, o evento onclose reconecta depois de 2 segundos. Mesmo que o ESP32 reinicie ou o Wi-Fi caia por um instante, a página se recupera sozinha. Como o endereço é montado a partir de location.host, a página funciona tanto com o IP quanto com esp32.local.
  • WiFi.setSleep(false) desliga a economia de energia do Wi-Fi. O modem sleep padrão pode acrescentar atrasos de até algumas centenas de milissegundos às respostas; desligá-lo em um painel alimentado pela tomada faz os botões responderem na hora.

Escolher JSON como formato das mensagens facilita a vida: o navegador transforma o texto recebido em um objeto em uma linha com JSON.parse(). Uma mensagem de exemplo fica assim: {"role":true,"sicaklik":23.4,"nem":48,"istemci":2}. Quando o sensor não pode ser lido, o valor é enviado como null e a página mostra “--”; se uma leitura com falha saísse como NaN, o JSON inválido impediria a página de se atualizar. Como cada mensagem tem só uns 60 bytes, nem várias atualizações por segundo sobrecarregam a rede. Para mensagens mais complexas você pode passar para a biblioteca ArduinoJson, mas para um painel deste tamanho, juntar Strings é mais que suficiente. Você também pode testar o mesmo código sem mudar nada, com um LED ou um buzzer no lugar do relé.

Onde guardar o HTML: PROGMEM ou LittleFS?

No código do painel, a página fica em uma string literal bruta escrita como R"rawliteral( ... )rawliteral". Essa sintaxe permite usar aspas e quebras de linha sem caracteres de escape. Nos Arduinos baseados em AVR, o PROGMEM é necessário para manter o texto na flash em vez da RAM; no ESP32, arrays constantes já são lidos da flash, então o PROGMEM é escrito só por compatibilidade e não faz mal. À medida que a página cresce, editar HTML dentro do código fica chato; nesse ponto, é mais organizado mover os arquivos para o sistema de arquivos LittleFS na flash.

CritérioPROGMEM (dentro do código)LittleFS (sistema de arquivos)
InstalaçãoSem passo extra, um arquivo sóPrecisa de uma pasta data e de um plugin de upload
EdiçãoHTML dentro de um array C++Arquivos separados index.html, style.css e app.js
AtualizaçãoVocê carrega o sketch de novo a cada mudançaVocê só carrega de novo o sistema de arquivos
TamanhoIdeal para páginas de poucos KBCerca de 1,5 MB de espaço com o particionamento padrão
Imagens e fontesPouco práticoDá para servir ícones, imagens e arquivos .gz
Quando?Primeiros testes e painéis pequenosInterfaces com várias páginas ou que vão crescer

Servindo arquivos HTML, CSS e JavaScript com LittleFS

  1. Dentro da pasta do sketch (a pasta que contém o arquivo .ino), crie uma pasta chamada data e salve o HTML que fica entre R"rawliteral( e )rawliteral" no código do painel como data/index.html. Se quiser, pode separar o CSS e o JavaScript em arquivos próprios.
  2. O Arduino IDE 2 não suporta o antigo menu ESP32 Sketch Data Upload. Em vez disso, baixe o arquivo .vsix do plugin arduino-littlefs-upload e coloque-o em C:\Users\(seu nome de usuário)\.arduinoIDE\plugins no Windows ou em ~/.arduinoIDE/plugins no macOS e no Linux (crie a pasta se ela não existir) e reinicie o IDE. O plugin exige o IDE 2.2.1 ou mais recente.
  3. Feche o Monitor Serial se estiver aberto, abra a paleta de comandos com Ctrl+Shift+P (Cmd+Shift+P no macOS) e escolha Upload LittleFS to Pico/ESP8266/ESP32. Os arquivos são gravados na partição do sistema de arquivos na flash.
  4. Confira se em Ferramentas > Partition Scheme está selecionado um esquema com partição de sistema de arquivos (por exemplo, Default 4MB with spiffs). O LittleFS usa essa partição, que se chama spiffs.
  5. Carregue o código abaixo como de costume. Enquanto a opção Erase All Flash do menu Ferramentas estiver desativada, carregar um sketch não apaga o sistema de arquivos; quando você mudar os arquivos, basta repetir o passo 3.
C++ (Arduino IDE)
1#include <WiFi.h>
2#include <AsyncTCP.h>
3#include <ESPAsyncWebServer.h>
4#include <LittleFS.h>
5
6const char* ssid = "AgAdi";          // Wi-Fi adın / your Wi-Fi name
7const char* password = "Sifre123";   // Wi-Fi şifren / your Wi-Fi password
8
9AsyncWebServer server(80);
10
11void setup() {
12  Serial.begin(115200);
13
14  if (!LittleFS.begin(true)) {       // true: bağlanamazsa biçimlendir / format if mount fails
15    Serial.println("LittleFS hatasi / LittleFS error");
16    return;
17  }
18
19  WiFi.mode(WIFI_STA);
20  WiFi.begin(ssid, password);
21  while (WiFi.status() != WL_CONNECTED) {
22    delay(500);
23  }
24  Serial.println(WiFi.localIP());
25
26  // data/ klasörünün içeriğini sun / serve the contents of the data/ folder
27  server.serveStatic("/", LittleFS, "/")
28        .setDefaultFile("index.html")    // varsayılan index.htm'dir / default is index.htm
29        .setCacheControl("max-age=600"); // tarayıcı 10 dk önbelleğe alsın / cache for 10 min
30
31  server.onNotFound([](AsyncWebServerRequest *request) {
32    request->send(404, "text/plain", "Sayfa yok / Not found");
33  });
34  server.begin();
35}
36
37void loop() {
38}
Quando um arquivo é pedido, serveStatic() procura primeiro uma versão compactada com o mesmo nome e extensão .gz. Se você compactar o index.html com gzip e enviá-lo como index.html.gz, a página costuma cair para algo entre um terço e um quarto do tamanho e abre mais rápido. Quando o endereço de uma pasta é pedido, o nome de arquivo padrão é index.htm, então não esqueça a linha setDefaultFile("index.html"). Se você usar o recurso de template da biblioteca, marcadores como %DEGER% na página são substituídos; nesse caso, você precisa escrever os sinais de porcentagem do CSS, como em width:100%, como %%.

mDNS: esp32.local no lugar do endereço IP

O seu roteador pode dar ao ESP32 um endereço IP diferente a cada vez que ele liga, e números como 192.168.1.47 são difíceis de lembrar. O mDNS (multicast DNS) permite que um dispositivo anuncie o próprio nome na rede local. Graças à linha MDNS.begin("esp32") no código do painel, os dispositivos da mesma rede podem abrir a página em http://esp32.local, e MDNS.addService("http", "tcp", 80) avisa aos apps que exploram a rede que há um servidor web ali. Não coloque “.local” no nome e use só letras minúsculas, números e hifens. Se você usa vários ESP32, dê um nome diferente a cada um (cozinha, sala-de-estar e assim por diante), porque dois dispositivos com o mesmo nome atrapalham um ao outro. O mDNS não tem o mesmo suporte em todos os sistemas:

SistemaSuporte a .localSe não funcionar
iPhone, iPad, macOSIntegrado (Bonjour)Confira se você está na mesma rede Wi-Fi
Windows 10 e 11Costuma funcionar nas versões atualizadasInstale o serviço Bonjour ou use o IP
LinuxFunciona se o avahi-daemon estiver instaladoInstale os pacotes avahi-daemon e nss-mdns
AndroidDepende da versão e do navegadorUse o endereço IP ou faça uma reserva de DHCP no roteador

Vários clientes: celular, tablet e computador ao mesmo tempo

O que todo mundo mais gosta no WebSocket é que todos que abrem o painel veem o mesmo estado. Quando você liga o relé pelo celular, o ESP32 envia o novo estado para todas as conexões com textAll(), e a aba no computador fica verde no mesmo instante. Cada aba é um cliente separado: abra três abas no mesmo navegador e o contador mostra 3. Por padrão, a biblioteca permite 8 clientes WebSocket simultâneos no ESP32 e 4 no ESP8266 (DEFAULT_MAX_WS_CLIENTS). Esse limite não é aplicado automaticamente; quando cleanupClients() é chamado, a conexão mais antiga acima do limite é fechada. Veja o que mais vale ter em mente:

  • Quando o celular entra em repouso: um celular com a tela apagada pode não fechar a conexão direito. Essas conexões meio abertas são limpas com o tempo graças ao cleanupClients() e ao envio regular de dados.
  • Ritmo de envio: até 32 mensagens podem esperar na fila de cada cliente (8 no ESP8266). Se você chamar textAll() a cada ciclo do loop(), a fila enche e as mensagens novas são descartadas sem aviso; coloque Ferramentas > Core Debug Level em Warn ou em um nível mais detalhado e você verá o aviso Too many messages queued no Monitor Serial. Envie um valor só quando ele mudar, ou algumas vezes por segundo.
  • Cliente recém-conectado: envie o estado atual para toda página que se conectar, logo de cara; senão o usuário fica olhando um painel vazio até a primeira leitura. No código do painel, quem faz isso é a flag levantada em WS_EVT_CONNECT.
  • Responder a um único cliente: para responder só ao navegador que perguntou, e não a todos, use client->text("..."); client->id() dá o número do cliente.
  • Memória: cada conexão aberta ocupa um socket TCP e memória de buffer. Os 520 KB de RAM do ESP32 sobram para alguns clientes, mas imprimir de vez em quando a memória livre com ESP.getFreeHeap() ajuda a pegar vazamentos cedo.

Proteção simples com senha

Qualquer pessoa na sua rede doméstica que saiba o endereço do painel pode acionar o relé. Com as funções request->authenticate() e request->requestAuthentication() da biblioteca, você pode colocar usuário e senha na página; o navegador abre a própria janela de login e lembra os dados durante a sessão. O método padrão é a autenticação Digest, ou seja, a senha não trafega pela rede em texto puro. Mas proteger só a página não basta: o endereço do WebSocket é outra porta, e alguém poderia se conectar direto a ws://ip/ws. O exemplo abaixo fecha essa porta também: a cada inicialização, o ESP32 gera uma chave aleatória, entrega essa chave só para um navegador já logado em /anahtar e usa setFilter() para recusar conexões WebSocket que não tragam a chave certa.

C++ (Arduino IDE)
1#include <WiFi.h>
2#include <AsyncTCP.h>
3#include <ESPAsyncWebServer.h>
4
5const char* ssid = "AgAdi";          // Wi-Fi adın / your Wi-Fi name
6const char* password = "Sifre123";   // Wi-Fi şifren / your Wi-Fi password
7const char* KULLANICI = "admin";     // panel kullanıcı adı / panel username
8const char* PAROLA = "Gizli-2026";   // panel parolası / panel password
9const int LED_PIN = 2;
10
11AsyncWebServer server(80);
12AsyncWebSocket ws("/ws");
13String wsAnahtar;                    // her açılışta yeni anahtar / a new key on every boot
14bool ledAcik = false;
15
16const char index_html[] PROGMEM = R"rawliteral(
17<!DOCTYPE html>
18<html><head><meta charset="utf-8">
19<meta name="viewport" content="width=device-width, initial-scale=1"></head>
20<body style="font-family:sans-serif;text-align:center">
21<h2>Korumali panel / Protected panel</h2>
22<button style="font-size:1.4em;padding:12px 32px" onclick="ws && ws.send('toggle')">LED</button>
23<p id="durum">...</p>
24<script>
25let ws;
26fetch('/anahtar').then(r => r.text()).then(k => {
27  ws = new WebSocket('ws://' + location.host + '/ws?k=' + k);
28  ws.onmessage = (e) => { document.getElementById('durum').textContent = e.data; };
29});
30</script>
31</body></html>
32)rawliteral";
33
34bool girisVar(AsyncWebServerRequest *request) {
35  return request->authenticate(KULLANICI, PAROLA);
36}
37
38void setup() {
39  Serial.begin(115200);
40  pinMode(LED_PIN, OUTPUT);
41
42  WiFi.mode(WIFI_STA);
43  WiFi.begin(ssid, password);
44  while (WiFi.status() != WL_CONNECTED) {
45    delay(500);
46  }
47  Serial.println(WiFi.localIP());
48
49  // Wi-Fi açıkken random() donanım üretecini kullanır / random() uses the hardware RNG
50  wsAnahtar = String(random(0x7FFFFFFF), HEX) + String(random(0x7FFFFFFF), HEX);
51
52  // Sayfa: tarayıcı kullanıcı adı ve parola sorar / page: the browser asks for login
53  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
54    if (!girisVar(request)) return request->requestAuthentication();
55    request->send(200, "text/html", index_html);
56  });
57
58  // Anahtar yalnız giriş yapana verilir / the key is given only after login
59  server.on("/anahtar", HTTP_GET, [](AsyncWebServerRequest *request) {
60    if (!girisVar(request)) return request->requestAuthentication();
61    request->send(200, "text/plain", wsAnahtar);
62  });
63
64  // Doğru anahtarı getirmeyen bağlantıyı reddet / reject sockets without the right key
65  ws.setFilter([](AsyncWebServerRequest *request) {
66    return request->hasParam("k") && request->getParam("k")->value() == wsAnahtar;
67  });
68
69  ws.onEvent([](AsyncWebSocket *sunucu, AsyncWebSocketClient *client, AwsEventType type,
70                void *arg, uint8_t *data, size_t len) {
71    if (type == WS_EVT_DATA && len == 6 && memcmp(data, "toggle", 6) == 0) {
72      ledAcik = !ledAcik;
73      digitalWrite(LED_PIN, ledAcik ? HIGH : LOW);
74      sunucu->textAll(ledAcik ? "LED: ON" : "LED: OFF");
75    }
76  });
77
78  server.addHandler(&ws);
79  server.begin();
80}
81
82void loop() {
83  ws.cleanupClients();               // saniyede bir yeterli / once per second is enough
84  delay(1000);
85}
Essa proteção é uma tranca boa o bastante para uma rede doméstica, mas não é criptografia: a ESPAsyncWebServer não oferece HTTPS no ESP32, e as mensagens WebSocket trafegam em texto puro. Como a senha fica no código, lembre-se de removê-la antes de compartilhar o código. Não abra o painel para a internet com redirecionamento de portas no roteador; para controle remoto, um bot do Telegram com ESP32, MQTT e Home Assistant ou o recurso de VPN do seu roteador são bem mais seguros. Nas versões mais novas da biblioteca, você também pode fazer o mesmo com a classe AsyncAuthenticationMiddleware.

Observações para ESP8266 e Raspberry Pi Pico W

A mesma biblioteca também roda no ESP8266 (NodeMCU, Wemos D1 mini). Veja o que mudar: instale a biblioteca ESP Async TCP da ESP32Async no lugar da Async TCP; inclua ESP8266WiFi.h no lugar de WiFi.h, ESPAsyncTCP.h no lugar de AsyncTCP.h e ESP8266mDNS.h no lugar de ESPmDNS.h, e adicione a linha MDNS.update() dentro do loop(). Troque WiFi.setSleep(false) por WiFi.setSleepMode(WIFI_NONE_SLEEP) e defina os pinos conforme a coluna NodeMCU da tabela de ligações. O ESP8266 tem só cerca de 80 KB de RAM utilizável, então mantenha a página pequena e lembre que o limite de clientes é 4. Se você usa um Raspberry Pi Pico W, encontra como montar um servidor web com MicroPython no guia Raspberry Pi Pico W Wi-Fi; as versões mais novas da ESPAsyncWebServer também suportam os chips RP2040 e RP2350 no lado do Arduino.

Ideias de projetos com servidor web no ESP32

  • Tomada inteligente de vários canais: use uma placa de relés de 4 canais para criar botões separados para uma lâmpada, uma ventoinha e uma tomada; diferencie os comandos com textos curtos como “r1” e “r2”. Para escolher o relé, veja o artigo módulo relé.
  • Painel de irrigação de plantas: mostre ao vivo o valor do sensor de umidade do solo, ajuste o limite de irrigação com um slider na página e acione a bomba manualmente quando quiser.
  • Estação meteorológica: use um BME280 no lugar do DHT22 para incluir a pressão atmosférica e mostre as últimas 60 leituras em um pequeno gráfico de linha na página.
  • Luminária com seletor de cor: envie pelo WebSocket o valor de um seletor de cor da página e pinte na hora uma fita WS2812B NeoPixel.
  • Servo no slider: envie o valor do slider a cada movimento e gire um servo motor; ideal para apontar uma câmera ou para uma trava de porta.
  • Registro com data e hora: acrescente a cada leitura o horário obtido via NTP e atualize seu código sem fio via OTA em vez de usar a USB toda vez.

Erros comuns e como resolver

SintomaCausaSolução
'mbedtls_md5_starts_ret' was not declared in this scope (não declarado neste escopo)Biblioteca antiga da me-no-dev com o core 3.x do ESP32Instale ESP Async WebServer e Async TCP da ESP32Async e apague as pastas antigas
Multiple libraries were found for "AsyncTCP.h" (várias bibliotecas encontradas)Duas cópias da mesma bibliotecaApague as cópias antigas -master na pasta libraries
Aviso de deprecated para send_Psend_P está sendo removida na versão novaUse request->send(200, "text/html", index_html) no lugar de send_P
A página não abre de jeito nenhumCelular na rede de convidados ou nos dados móveis, ou o endereço IP mudouConecte-se ao mesmo Wi-Fi e use o IP atual do Monitor Serial
A página abre, mas os valores ficam em --O WebSocket não conectou: caminho diferente ou addHandler esquecidoAbra o console com F12 e faça ws("/ws") bater com o endereço no JavaScript
Task watchdog got triggered com async_tcpdelay(), laço longo ou requisição HTTP dentro de um callbackMantenha o callback curto e passe o trabalho para o loop() com uma flag
Too many messages queued (mensagens demais na fila)textAll() está sendo chamado com frequência demaisEnvie só quando mudar, ou algumas vezes por segundo
A página no LittleFS retorna 404Pasta data não enviada ou falta setDefaultFileEnvie os arquivos com o plugin e adicione setDefaultFile("index.html")
does not exist, no permits for creation no Monitor SerialO arquivo pedido não está no LittleFSConfira o nome do arquivo e as maiúsculas e minúsculas; linhas que mencionam .gz são inofensivas
O relé nunca desligaO módulo de 5 V não reconhece um HIGH de 3,3 VUse um módulo compatível com 3,3 V, JD-VCC ou um transistor
O ESP32 reinicia quando o relé acionaA corrente da bobina derruba a alimentação (brownout)Use uma fonte de 5 V de qualidade e um capacitor de 470 µF
esp32.local não abreO dispositivo não tem suporte a mDNS ou o nome foi escrito com .localEscreva MDNS.begin("esp32") e use o endereço IP se for preciso
O botão demora a responderO modem sleep do Wi-Fi está ativadoChame WiFi.setSleep(false) depois de conectar
Quando algo não funcionar, abra as ferramentas de desenvolvedor do navegador com F12. A aba Console mostra erros como WebSocket connection to 'ws://192.168.1.50/ws' failed; na aba Network, clique na conexão WebSocket e a seção Messages mostra cada mensagem que entra e sai. No lado do ESP32, imprimir no Monitor Serial os clientes que se conectam e saem diz em poucos segundos se o problema está no navegador ou na placa. Se a placa reinicia no momento em que o relé aciona, veja o guia ESP32 brownout.

A combinação de servidor web assíncrono no ESP32 com WebSocket é o caminho mais curto para um painel de controle rápido, que funciona sem serviço de nuvem e sem precisar escrever um app de celular, e que vários dispositivos podem compartilhar. Comece com o painel PROGMEM de um arquivo só, passe para o LittleFS quando a interface crescer e dê a cada novo dispositivo da sua rede o próprio nome mDNS. Se você está começando com o ESP32, encontra o básico em O que é o ESP32? No app BlueGrays você também pode explorar passo a passo projetos com ESP32, como servidor web assíncrono, contador com WebSocket, acesso por mDNS, relógio NTP e atualização OTA, com esquemas do circuito e código pronto para usar.

Perguntas frequentes

Qual a diferença entre ESPAsyncWebServer e WebServer no ESP32?

A biblioteca WebServer trata as requisições uma por vez pela chamada handleClient() no loop(); se o loop() fica lento, a página também fica. A ESPAsyncWebServer trata as requisições em uma tarefa separada em segundo plano, aguenta várias conexões ao mesmo tempo e já vem com suporte a WebSocket e SSE. Para painéis em tempo real, a versão assíncrona é a mais indicada.

Quantos clientes podem se conectar ao mesmo tempo a um servidor web no ESP32?

Por padrão, a ESPAsyncWebServer permite 8 clientes WebSocket no ESP32 e fecha os excedentes quando cleanupClients() é chamado; no ESP8266 o limite é 4. Como as requisições normais de página são curtas, mais dispositivos podem abrir a página um depois do outro. Na prática, 3–5 dispositivos simultâneos funcionam sem problemas em um painel doméstico.

Posso acessar meu servidor web do ESP32 pela internet?

Tecnicamente é possível com redirecionamento de portas no roteador, mas não é recomendado: a biblioteca não oferece HTTPS no ESP32 e o dispositivo fica exposto a ataques vindos da internet. Para controle remoto, um bot do Telegram, um sistema baseado em MQTT ou o recurso de VPN do seu roteador são bem mais seguros.

Por que o esp32.local não funciona?

O motivo mais comum é o celular ou o computador não conseguir resolver nomes mDNS; isso acontece principalmente em alguns celulares Android e em versões antigas do Windows. Escrever MDNS.begin("esp32.local") no código também é um erro, porque o nome deve ser dado sem .local. Se o nome continuar sem resolver, use o endereço IP do Monitor Serial ou reserve um IP fixo para o ESP32 no roteador.

Um servidor web no ESP32 funciona sem roteador?

Sim. Com WiFi.softAP("ESP32-Panel", "12345678") o ESP32 cria a própria rede Wi-Fi; conecte o celular a ela e abra 192.168.4.1 no navegador. Nesse modo de ponto de acesso não há internet, mas o painel e o WebSocket funcionam do mesmo jeito. A senha da rede precisa ter pelo menos 8 caracteres.

Qual biblioteca ESPAsyncWebServer devo instalar?

No Gerenciador de Bibliotecas do Arduino, instale as bibliotecas ESP Async WebServer e Async TCP cujo autor é ESP32Async. A versão original na conta me-no-dev não é mais mantida e não compila com o core 3.x do ESP32. Apague também da pasta libraries as cópias antigas adicionadas por ZIP.

Ver todos os projetos
Servidor web assíncrono com ESP32 (IP no LCD)
#129Avançado

Servidor web assíncrono com ESP32 (IP no LCD)

Neste projeto, transformamos o ESP32 em um servidor web assíncrono que você pode controlar pelo celular ou computador na mesma rede Wi-Fi. Com a biblioteca ESPAsyncWebServer, o servidor processa várias requisições ao mesmo tempo (ao contrário do WebServer.h tradicional, que bloqueia a execução ao processar uma requisição). Você poderá abrir o endereço IP do ESP32 no navegador e clicar nos botões ON/OFF (as rotas /on e /off) para ligar e desligar o LED conectado ao GPIO 5.

4.8(16)
26
Contador WebSocket com ESP32 (2 botões + LCD + web em tempo real)
#130Avançado
Premium

Contador WebSocket com ESP32 (2 botões + LCD + web em tempo real)

Neste projeto, dois botões conectados ao ESP32 (GPIO 5 aumenta, GPIO 4 diminui) alteram um contador. O valor é atualizado instantaneamente no LCD I2C 16x2 e, com o suporte a WebSocket do ESPAsyncWebServer, em todos os navegadores conectados, sem recarregar a página. Em vez de consultas periódicas por HTTP, usamos uma única conexão persistente, e o ESP32 envia o novo valor a todos com ws.textAll().

4.5(14)
23
ESP32 mDNS: acesso via bluegrays-esp.local (com display LCD)
#131Avançado
Premium

ESP32 mDNS: acesso via bluegrays-esp.local (com display LCD)

Neste projeto, adicionamos o serviço mDNS (Multicast DNS) ao ESP32 para acessar o dispositivo por um nome de host fácil de ler, como bluegrays-esp.local, em vez de um endereço IP. Mesmo que o IP atribuído pelo DHCP mude, você pode acessar o dispositivo pelo mesmo nome. Também executamos um pequeno servidor web no dispositivo para enviar comandos de controle do LED pelo navegador no endereço http://bluegrays-esp.local/.

4.8(14)
23
Conexão Wi-Fi STA com ESP32 e display LCD
#127Avançado

Conexão Wi-Fi STA com ESP32 e display LCD

Neste projeto, conectamos o ESP32 à rede Wi-Fi de casa no modo estação (STA), de forma segura e tolerante a falhas. Guardamos o SSID e a senha em um arquivo secrets.h separado para evitar que sejam expostos no Git, configuramos um mecanismo de reconexão automática e exibimos o nível de sinal RSSI e o endereço IP em um display LCD I2C. O LED integrado funciona como indicador de status. É um gerenciador de conexão pronto para uso em produção.

4.4(21)
34
Temperatura e umidade com ESP32 e DHT22 (com display LCD)
#117Intermediário

Temperatura e umidade com ESP32 e DHT22 (com display LCD)

Este projeto mede a temperatura e a umidade do ambiente com um sensor DHT22 e exibe os dados em tempo real em um LCD I2C 16x2. Os dados são lidos do sensor pelo protocolo de um fio (one-wire) com a biblioteca Adafruit DHT. Este projeto serve de base para estações meteorológicas IoT, automação residencial ou sistemas de monitoramento ambiental em estufas. A comunicação com o DHT22 é feita pelo pino GPIO 4 do ESP32.

4.8(38)
56
Iluminação inteligente com ESP32 (PIR + LDR + relé + OLED)
#141Avançado
Premium

Iluminação inteligente com ESP32 (PIR + LDR + relé + OLED)

Transforme a iluminação do corredor, banheiro ou despensa em iluminação inteligente com ESP32! Com um sensor de movimento PIR, um sensor de luz LDR e um módulo relé, montamos o cenário clássico de "acender a luz ao detectar movimento no escuro" — se o ambiente já estiver claro, o movimento NÃO aciona a luz, economizando energia durante o dia. A luz fica acesa por 30 segundos; se o movimento continuar, o temporizador reinicia e a luz permanece acesa por mais tempo.

4.3(6)
11
Atualização OTA no ESP32 — firmware sem fio (com display LCD)
#132Avançado
Premium

Atualização OTA no ESP32 — firmware sem fio (com display LCD)

Neste projeto, você aprende a carregar um novo firmware no ESP32 por Wi-Fi, sem cabo USB. Com a biblioteca ArduinoOTA, o ESP32 fica visível na rede e aparece automaticamente como bluegrays-ota na aba "Porta de rede" do Arduino IDE. A primeira carga é feita por USB, e todas as atualizações seguintes são sem fio — um recurso essencial para dispositivos em campo. O LCD I2C mostra o estado do Wi-Fi, o IP, o nome do host, a porcentagem de envio por OTA e as mensagens de erro em turco e inglês.

4.6(14)
22
Hora exata pela internet com ESP32 e NTP
#128Avançado
Premium

Hora exata pela internet com ESP32 e NTP

O ESP32 se conecta ao Wi-Fi e obtém a hora exata com precisão de milissegundos de servidores NTP (Network Time Protocol). Com a função configTzTime(), o fuso horário configurado (TRT, UTC+3 neste exemplo) é aplicado automaticamente, e as mudanças nos países que adotam horário de verão são gerenciadas automaticamente por uma string POSIX TZ. A hora é exibida tanto no Monitor Serial quanto em um LCD I2C opcional.

4.5(13)
21