Avançar para o conteúdo

ESP32 uso profissional – Access point AP

Hoje vamos ver um ESP32 uso profissional, vamos implementar um sistema de access point. Ele gera uma rede Wi-Fi, você conecta nela e configura tudo. Isto serve para configurar o Wi-Fi do ESP32 sem precisar ficar digitando no código na hora de programar.

Você já deve ter tido contato com algum tipo de câmera Wi-Fi ou algum outro dispositivo similar?. Do tipo que para configura-lo ele gera uma rede Wi-Fi, você conecta nela usando seu celular ou computador. Aí abre uma página da WEB/internet para você configurar tudo, rápido e fácil. É exatamente isso que vou te ensinar a fazer, vem comigo.

Esta funcionalidade foi construída e funciona muito bem na linha de microcontroladores ESP32. No nosso caso vamos programa-lo via software IDE do Arduino. Esta função também está implementada para a plataforma Arduino. Vou te mostrar ao longo do artigo, mas uma coisa para ter em mente é a ordem de execução deste tipo de código.

Enquanto você estiver usando o Wi-Fi do ESP32 para alguma função, não deve criar um access point (AP). Para criar um access point deve-se derrubar o Wi-Fi. E vice-versa, enquanto estiver com AP levantado não deve usar o Wi-Fi para mais nada.

ESP32 uso profissional

A idea do projeto e deste artigo é implementar um relógio com display LCD. Horas e minutos serão mostrados na tela LCD, com updates basicamente a cada um minuto. Fiz o código para buscar o minuto o mais próximo possível do zero segundo. Ou seja, quando o minuto “vira” eu já mostro a atualização na tela.

Eu busco informação de hora, minuto e segundo da internet, então precisamos de conexão Wi-Fi. O nome do serviço que uso é TimeZoneDB, gratuíto com limitações. Para buscar informação na sua API a cada um minuto é bem tranquilo, não se paga nada.

A grade sacada (e o objetivo deste projeto) é como é feita a conexão da placa ESP32 à internet/Wi-Fi. Em todos os meus projetos até hoje (veja aqui) (e eu garanto que nos seus também) eu colocava o nome da rede e a senha do Wi-Fi direto no código do Arduino. Mais ou menos assim:

#include <WiFi.h>

//SSID of your network
char ssid[] = "yourNetwork";
//password of your WPA Network
char pass[] = "secretPassword";

void setup()
{
 WiFi.begin(ssid, pass);
}

void loop () {}

Sempre foi ruim para compartilhar os projetos no GitHub, tinha que ficar lembrando de tirar minha senha do sketch antes de fazer upload. Outra opção é usar um arquivo externo com a senha, algo como “secrets.h”. Aí o Sketch ficava assim:

#include "secrets.h"

Porém ambas soluções acima, mesmo que básicas e fáceis, exigem que a senha do Wi-Fi esteja presente na hora da compilação do código. Isto tira mobilidade do projeto. Visto que se ele for mudado de local para um ambiente onde o Wi-Fi seja outro, você terá que re-compilar todo o projeto com as novas credenciais Wi-Fi.

Já o método access point (AP) permite que a senha fique gravada numa área de memória segura do ESP32. Também que ela possa ser atualizada no local de instalação, que pode ser literalmente qualquer lugar com qualquer Wi-Fi. A única limitação é que a maioria dos ESP32 usa Wi-Fi 2.4GHz, apenas o mais novo ESP32-C5 tem suporte para Wi-Fi 5GHz (até Fevereiro/2026).

Hardware/montagem

O hardware para este projeto será basicamente o mesmo do nosso projeto anterior, um ESP32-C3, um display LCD 16×2 i2c, um LED e um botão push button. Isto vai de encontro com o que sempre faço aqui no blog, projetos simples de montar e modificar. O diagrama esquemático está abaixo, bem como fotos da montagem.

Diagrama esquemático - hardware para relógio digital
Diagrama esquemático – hardware para relógio digital
Montagem em protoboard - relógio com access point
Montagem em protoboard – relógio com access point

A alimentação de todo o circuito é feita via cabo USB. O mesmo cabo que transfere dados e comunica via serial monitor da IDE do Arduino. Eu fiz a montagem em uma protoboard de 400 furos, bem pequena. Até porque nosso circuito precisa de apenas um botão push button e um LED.

O hardware pode até não ser ESP32 uso profissional, porém o firmware/código é, vamos ver?

Firmware/código

Eu usei este artigo e também este como referência sobre access point (AP). Recomendo a leitura de ambos, trazer informações um pouco diferentes daquelas que vou ensinar aqui. Estão em Inglês mas imagino que não seja problema para você, certo?. Também tive uma ajuda do ChatGPT para polir o código e indicar caminhos.

Todo o código que fiz para este experimento está disponível abaixo (já vamos estuda-lo) e também neste Github. Aproveite para copia-lo e usa-lo como desejar. O funcionamento do código do experimento é conforme a seguir:

  • Caso o ESP não encontre a rede na qual ele tenha se conectado por último, o access point (AP) é criado. Isto também vale para se ainda não tiver conectado em nenhuma (primeiro uso).
  • O nome do access point (nome da rede) é exibido no display LCD. Conecte nesta rede usando um celular/computador/notebook/tablet. No Windows é no canto inferior direito da tela, próximo ao relógio (símbolo de Wi-Fi). No Android é nas configurações, na engrenagem.
  • Vá até o navegador de internet destes dispositivos (o que conectou no access point) e digite 192.168.4.1. Na página que abrir selecione sua rede Wi-Fi e digite a senha da mesma. Clique em “connect”.
  • Aguarde alguns segundos, a hora atual (hora e minutos) deve aparecer no display LCD. Será atualizada a cada um minuto. A rede Wi-Fi do ESP32 (o access point) deve desaparecer.
  • Caso queira conectar em outra rede Wi-Fi, pressione o botão push button do pino D1 e segure, por mais ou menos sete (7) segundos. O LED do pino D0 vai começar a piscar. Repita o procedimento desde o primeiro passo acima.
  • Ao energizar o ESP32, caso ele encontre a rede da qual ele tem a senha, conectará automaticamente. Dentro de poucos segundos a hora e minutos vão aparecer no display. Não é necessário conectar no access point (AP). Para trocar de rede Wi-Fi, volte ao passo anterior.

O detalhe mais importante que você não pode deixar passar ao usar o meu código, é inserir a API key (chave de API) do TimeZoneDB. Entre em https://timezonedb.com/api e cadastre-se gratuitamente para obter sua API key. Copie o código no site deles e cole em “YourAPIkey” na linha abaixo:

// Please register an account in TimeZoneDB and get you API key and region (mine is America/Sao_Paulo)
  http.begin("https://api.timezonedb.com/v2.1/get-time-zone?key=YourAPIkey&format=json&by=zone&zone=America/Sao_Paulo");

Se você não é Brasileiro ou vive em um fuso-horário diferente daquele de São Paulo/Brasília, altere também a linha acima para seu local. Lista completa aqui.

O código completo está abaixo, aproveite. As bibliotecas utilizadas estão logo no início do código, nos #include. A única que precisa ser instalada, que não vem junto ao suporte do ESP32 é a ArduinoJson.

#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>
#include <HTTPClient.h> 
#include <ArduinoJson.h>

#define CONFIG_BUTTON D1
#define LONG_PRESS_MS 6000
#define LED D0
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x3D,16,2);
String payload;

WebServer server(80);
Preferences prefs;

bool startConfigPortal = false;
long configLedTime= 0;
long apiFetchTime= 0;
int hour= 0;
int minute= 0;
int second= 0;
String rawtime;
bool firstPass= true;
unsigned long btnPressStart = 0;
bool btnWasPressed = false;
bool portalRunning = false;
unsigned long nextFetchTime = 0;
const char* networkName= "Clock";
long noWiFiTime= 0;
bool justDisconnected= true;

String makePage(String options) { // This is where the HTML + CSS of the access point is created
  return R"rawliteral(
    <!DOCTYPE html>
    <html>
    <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Clock Wi-Fi setup</title>

    <style>

    body {
      font-family: Arial, sans-serif;
      background: #f2f4f8;
      margin: 0;
      padding: 0;
    }

    .container {
      max-width: 380px;
      margin: 40px auto;
      background: white;
      padding: 24px;
      border-radius: 12px;
      box-shadow: 0 4px 14px rgba(0,0,0,0.12);
    }

    h2 {
      text-align: center;
      margin-bottom: 20px;
      color: #333;
    }

    label {
      font-weight: bold;
      font-size: 14px;
    }

    select, input[type=password] {
      width: 100%;
      padding: 12px;
      margin-top: 6px;
      margin-bottom: 16px;
      border-radius: 8px;
      border: 1px solid #ccc;
      font-size: 16px;
    }

    button {
      width: 100%;
      padding: 14px;
      border: none;
      border-radius: 8px;
      background: #2e86de;
      color: white;
      font-size: 16px;
      font-weight: bold;
    }

    button:active {
      background: #1b4f72;
    }

    .footer {
      text-align: center;
      margin-top: 14px;
      font-size: 12px;
      color: #777;
    }

    </style>
    </head>

    <body>

    <div class="container">

    <h2>Configure WiFi</h2>

    <form action="/connect" method="POST">

    <label>Network</label>
    <select name="ssid">
    )rawliteral" + options + R"rawliteral(
    </select>

    <label>Password</label>
    <input type="password" name="pass" placeholder="Enter WiFi password">

    <button type="submit">Connect</button>

    </form>

    <div class="footer">
    ESP32 Setup Portal
    </div>

    </div>

    </body>
    </html>
)rawliteral";

}

String buildSSIDOptions() { // create the SSID (networks available) list
  int n = WiFi.scanNetworks();
  String opts;

  for (int i = 0; i < n; i++) {
    opts += "<option value='" + WiFi.SSID(i) + "'>";
    opts += WiFi.SSID(i);
    opts += " (";
    opts += WiFi.RSSI(i);
    opts += " dBm)";
    opts += "</option>";
  }

  return opts;
}

void setup() {
  pinMode(LED, OUTPUT);
  pinMode(CONFIG_BUTTON, INPUT_PULLUP);
  Wire.begin(6, 7);   // For my Xiao ESP32-C3, SDA = GPIO6, SCL = GPIO7 
  Serial.begin(115200);
  
  lcd.init();    
  lcd.backlight();
  lcd.noBlink();
  lcd.noCursor();
  lcd.clear();
  if (!connectSavedWiFi()) { // Only starts access point if no previous Wi-Fi credentials are saved
  startConfigPortal = true;
  }

}


void loop() {
  checkButtonRuntime(); // keep looking into whether the push button is pressed long enough

  // If no Wi-Fi credentials were found or push button was pressed long enough, create access point
  if (startConfigPortal && !portalRunning) { 
    startAPMode();
    portalRunning = true;
  }

  if (startConfigPortal) { // Handles the web page and everything that happens before AP connection
    server.handleClient();
    
    // This shows the user the name of the Wi-Fi network (SSID) in which to connect
    if(millis() - noWiFiTime > 60000 || justDisconnected == true){ 
      noWiFiTime= millis();
      justDisconnected= false;
      lcd.clear();
      lcd.setCursor(2,0);
      lcd.printf("No Wi-Fi");
      lcd.setCursor(0,1);
      lcd.printf("AP: ");
      lcd.printf(networkName);
    }
    // Just blink an LED while there is no Wi-Fi connection
    if (millis() - configLedTime > 200) {
      configLedTime += 200;
      digitalWrite(LED, !digitalRead(LED));
    }
    return;
  }

  // ===== Execution of your code of interest =====
  
  // This is what  this project does after Wi-Fi connecion, a clock on a 16x2 i2c LCD display
  if ((firstPass || millis() >= nextFetchTime) && !startConfigPortal) {

    updateTimeFromAPI(); // gets time (hour, minute, second)

    Serial.printf("%02d:%02d\n", hour, minute);
    lcd.clear();
    lcd.setCursor(2,0);
    lcd.printf("%02d:%02d", hour, minute);

    // Ensuring that the minute is updated as close as possible to second zero.
    // So effectively the time API is not read every 60 hard seconds, but
    // following a calculation that intends to get time at second zero.
    int delayToNextMinute = (60 - second) * 1000;
    nextFetchTime = millis() + delayToNextMinute;

    firstPass = false;
  }

  
}
// Keep an eye on the long button press for access point build
bool checkLongPress() {
  
  if (digitalRead(CONFIG_BUTTON) == LOW) {
    unsigned long start = millis();
    while (digitalRead(CONFIG_BUTTON) == LOW) {
      if (millis() - start > LONG_PRESS_MS) {
        return true;
      }
      delay(10);
    }
  }
  return false;
}
// Next three functions handle the Wi-Fi connection logic
void handleRoot() {
  String options = buildSSIDOptions();
  server.send(200, "text/html", makePage(options));
}
void handleConnect() {
  String ssid = server.arg("ssid");
  String pass = server.arg("pass");

  prefs.begin("wifi", false);
  prefs.putString("ssid", ssid);
  prefs.putString("pass", pass);
  prefs.end();

  server.send(200, "text/html", "Saved. Rebooting...");
  delay(1500);
  ESP.restart();
}
void startAPMode() {
  WiFi.mode(WIFI_AP);
  WiFi.softAP(networkName); // This is how it shows the network you are going to connect to

  server.on("/", handleRoot);
  server.on("/connect", HTTP_POST, handleConnect);
  server.begin();

  Serial.println("Config portal started");
}
bool connectSavedWiFi() {
  prefs.begin("wifi", true);
  String ssid = prefs.getString("ssid", "");
  String pass = prefs.getString("pass", "");
  prefs.end();

  if (ssid == "") return false;

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid.c_str(), pass.c_str());

  Serial.print("Connecting");

  unsigned long start = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - start < 15000) {
    Serial.print(".");
    delay(500);
  }

  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("\nConnected!");
    Serial.println(WiFi.localIP());
    return true;
  }

  return false;
}
// Gets time from TimeZoneDB API
void updateTimeFromAPI() {

  HTTPClient http;

  // Please register an account in TimeZoneDB and get you API key and region (mine is America/Sao_Paulo)
  http.begin("https://api.timezonedb.com/v2.1/get-time-zone?key=YourAPIkey&format=json&by=zone&zone=America/Sao_Paulo");

  if (http.GET() == HTTP_CODE_OK) {
    payload = http.getString();

    JsonDocument remotedata;
    deserializeJson(remotedata, payload);

    // Time comes from TimeZoneDB as a long string, we have to extract information from it
    String timeStr = remotedata["formatted"];
    hour = timeStr.substring(11, 13).toInt(); // These numbers are positions where information is at
    minute = timeStr.substring(14, 16).toInt();
    second = timeStr.substring(17, 19).toInt();
  }

  http.end();
}
// Decides that the button was pressed long enough
void checkButtonRuntime() {
  if (digitalRead(CONFIG_BUTTON) == LOW) {

    if (!btnWasPressed) {
      btnWasPressed = true;
      btnPressStart = millis();
    }

    if (millis() - btnPressStart > LONG_PRESS_MS) {
      Serial.println("Long press detected → start config portal");
      startConfigPortalNow();
      btnWasPressed = false;
    }

  } else {
    btnWasPressed = false;
  }
}
// Disconnects Wi-Fi to start access point
void startConfigPortalNow() {
  WiFi.disconnect(true);
  delay(200);

  startConfigPortal = true;
  justDisconnected= true;
  
}

O include de “Preferences.h” é bem importante. É nele que ficarão as credenciais do Wi-Fi que você vai entrar via access point. Ele fica numa área protegida da memória Flash do ESP32. Esta função é responsável por obter horas, minutos e segundos:

void updateTimeFromAPI() {}

É aqui dentro que está a requisição HTTP da URL do TimeZoneDB que vimos mais acima.

Como tudo funciona

Toda a parte do access point (AP) e também da leitura do botão push button é feita dentro de funções. A função main() fica responsável pelas decisões macro e também pelo controle do display LCD. Toda lógica do código é não-bloqueante, significa que não há delay() atrasando a execução.

Alguns delay() são sim usados no controle de conexão do Wi-Fi, pois por vezes é necessário esperar o mesmo “se achar”. Abaixo vemos hora e minutos sendo mostrados no display. Informação que foi obtida via Wi-Fi.

Display LCD mostrando a hora
Display LCD mostrando a hora

Quando você conecta na rede Wi-Fi access point e digita 192.168.4.1 no navegador, cai numa página da WEB. Esta página está totalmente contida dentro do ESP32. Tanto o HTML como o CSS para deixa-la com o aspecto abaixo, estão embutidos no microcontrolador.

Página de configuração de Wi-Fi
Página de configuração de Wi-Fi

Eu fiz um vídeo mais longo para Youtube (no início do Artigo) e um mais curto para Tiktok (logo abaixo), explicando como o sistema funciona. Eu falo e mostro como ver em qual rede conectar, onde colocar a senha, etc. Conforme comentado, quando quiser trocar de rede Wi-Fi basta pressionar e segurar o botão push button no pino D1.

Ah, se quiser comprar o display LCD que usei, use meu link do Aliexpress. O mesmo vale para o Xiao ESP32-C3.

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *