IoT

WebServer Assíncrono com ESP32

Eletrogate 13 de setembro de 2022

Introdução

Neste post, será demonstrado como desenvolver um WebServer assíncrono utilizando o microcontrolador ESP32 e a maioria dos algoritmos para controle de um WebServer assíncrono. Por fim, será desenvolvido um projeto para que o ESP32 procure as redes WiFi disponíveis e exiba, em uma página web, utilizando o WebServer Assíncrono, os dados das redes.


O que é Servidor Web Assíncrono?

Um servidor WebServer assíncrono é um tipo de servidor que pode manipular solicitações de forma escalável, em que as solicitações HTTP dos clientes não bloqueiam umas às outras e são executadas simultaneamente.

Diferença entre servidor web tradicional e o assíncrono

Em servidores baseados em threads tradicionais, para cada cliente há uma thread separada dedicada para atende-lo. Este comportamento pode provocar problemas de bloqueio quando um servidor está esperando que o processo libere recursos.
Já em servidores web assíncronos, há um processo de trabalho que aceita solicitações HTTP de todos os clientes e processa as solicitações dos clientes usando loops eficientes orientados a eventos. Este tipo de comportamento não causa problemas de bloqueio, como o outro tipo de servidor.


As Bibliotecas de Servidor Web Assíncrono ESP32

Neste post, construiremos um WebServer utilizando uma biblioteca chamada ESPAsyncWebServer, feita pelo desenvolvedor me-no-dev. Esta biblioteca não é disponibilizada no gerenciador de bibliotecas do IDE Arduino. Por isso, é necessário baixa-la como um arquivo .zip e instala-la manualmente. O passo a passo da instalação da biblioteca estará no tópico Instalação das bibliotecas. O repositório e a documentação da biblioteca pode ser acessada neste link.

Entretanto, a biblioteca ESPAsyncWebServer necessita de outra biblioteca para seu funcionamento: a AsyncTCP. Esta é uma biblioteca TCP totalmente assíncrona, destinada a permitir um ambiente de rede multiconexão sem problemas para os Microcontroladores ESP32, da Espressif. Ela também não é disponibilizada no gerenciador de bibliotecas do IDE Arduino e, por isso, é necessário baixa-la como um arquivo .zip e instala-la manualmente. O passo a passo da instalação da biblioteca estará no tópico Instalação das bibliotecas. O repositório da biblioteca pode ser acessada neste link.


Instalação das Bibliotecas

Instalando as bibliotecas necessárias no IDE Arduino

Para instalar as bibliotecas no Arduino IDE, é necessário fazer download dos arquivos .zip das bibliotecas ESPAsyncWebServer e AsyncTCP.

  1. Acesse os links de downloads das bibliotecas ESPAsyncWebServer e AsyncTCP.
  2. Após baixar os dois arquivos, abra o Arduino IDE.
  3. No Arduino IDE, navegue no menu Sketch → Incluir Biblioteca → Adicionar biblioteca .ZIP:
  4. Na janela que se abrir, selecione os arquivos .ZIP e adicione-os. Após isso, as bibliotecas estarão instaladas.

Instalando as bibliotecas necessárias no PlatformIO

PlatformIO IDE é um ecossistema de código aberto para desenvolvimento de IoT com sistema de compilação de plataforma cruzada, gerenciador de biblioteca e com suporte total para desenvolvimento Espressif ESP8266/ESP32. Para mais detalhes, consulte o post Como Programar o Arduino com VS Code e PlatformIO, aqui, mesmo, no blog Eletrogate:

Como Programar o Arduino com VS Code e PlatformIO

Para instalação da biblioteca ESPAsyncWebServer, siga os passos abaixo:

  1. Abra o PlatformIO IDE, ou instale caso ainda não o tenha (veja detalhes de instalação no post sugerido acima);
  2. Crie um novo projeto usando “PlatformIO Home → New Project”:
  3. No processo de criação do novo projeto, informe o nome do projeto, o modelo da placa utilizada e o framework Arduino. Após isso, finalize o processo de criação do projeto:
  4. Adicione as bibliotecas ao projeto usando o arquivo de configuração do projeto platformio.ini, inserindo a opção lib_deps, colocando como parâmetro o link da biblioteca ESPAsyncWebServer. Como a biblioteca depende da AsyncTCP, esta é instalada juntamente com a ESPAsyncWebServer:
  5. Durante a primeira compilação do projeto, as bibliotecas serão instaladas. Pode-se ver elas instaladas em “.pio → libdeps”:

Benefícios do WebServer Assíncrono

Usar esta biblioteca como WebServer Assíncrono tem muitos benefícios, como os citados abaixo:

  • Pode-se lidar com mais de uma conexão ao mesmo tempo;
  • É facilmente extensível para lidar com qualquer tipo de conteúdo;
  • Você é chamado assim que a solicitação estiver pronta e analisada;
  • Ao enviar a resposta, você está imediatamente pronto para lidar com outras conexões enquanto o servidor se encarrega de enviar a resposta em segundo plano;
  • É uma API fácil de usar, possuindo até autenticação HTTP Básica;
  • Possui plugin assíncrono WebSocket, que oferece locais diferentes sem servidores ou portas extras;
  • Possui  plugin assíncrono EventSource, (Server-Sent Events) para enviar eventos para o navegador;
  • O servidor é inteligente o suficiente para saber quando fechar a conexão e liberar recursos;
  • Possui plugin de reescrita de URL para reescritas de URL condicionais e permanentes; e
  • Tem mecanismo de processamento de modelos simples, para lidar com modelos.

Limitações do WebServer Assíncrono

Mesmo tendo bastante benefícios, a biblioteca possui algumas limitações, citadas abaixo:

  • Você não pode enviar mais de uma resposta para uma única solicitação;
  • Você não pode usar yield, delay ou qualquer função que os use dentro dos callbacks; e
  • Este é um servidor totalmente assíncrono e, como tal, não é executado no thread de loop.

Hardware para os Exemplos

Para todos os algoritmos deste post, é utilizado somente o seguinte hardware (um Módulo WiFi ESP32 Bluetooth 30 pinos):

cta_cart


Utilizando o WebServer Assíncrono

Todos os algoritmos deste post foram testados pelo navegador Google Chrome. Para se descobrir o IP para acessar a página do ESP32, deve-se acessar a página do roteador para ver os dispositivos conectados e ver qual é o IP do ESP32.

Nota: Para que os algoritmos deste post funcionem, substitua <SSID> com o SSID da sua rede e substitua <SENHA> com a senha da sua rede.

Para a maioria dos usos do ESP32 como WebServer Assíncrono, tem-se o seguinte algoritmo:

Primeiro, é necessário incluir as bibliotecas no sketch:

// Inclusão das bibliotecas
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

Também é necessário instanciar um objeto da classe AsyncWebServer:

// Instanciação do objeto da classe AsyncWebServer
AsyncWebServer server(80);

Ainda antes do void setup(), é necessário criar as constantes que armazenarão as credencias de acesso à rede sem fio (WiFi):

// Constantes das credenciais do WiFi
const char* ssid = "<SSID>";
const char* password = "<PASSWORD>";

Em void setup(), é conectado ao Ponto de Acesso com as credenciais fornecidas::

 // Conecta-se ao Ponto de acesso com as credenciais fornecidas
 WiFi.begin(ssid, password);

Por fim, dentro do void setup(), é definido que o servidor comece a ouvir os clientes:

 // Servidor começa à ouvir os clientes
 server.begin();

Ficando assim o algoritmo básico de uso do ESP32 como WebServer Assíncrono:

// Inclusão das bibliotecas
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Instanciação do objeto da classe AsyncWebServer
AsyncWebServer server(80);

// Constantes das credenciais do WiFi
const char* ssid = "<SSID>";
const char* password = "<PASSWORD>";

void setup() {

  // Conecta-se ao Ponto de acesso com as credenciais fornecidas
  WiFi.begin(ssid, password); 
  
  // Servidor começa à ouvir os clientes
  server.begin();
}

void loop() {}

Utilizando o WebServer Assíncrono: Respostas

Resposta básica com conteúdo de string

O seguinte algoritmo responde à solicitação do cliente com conteúdo de string:

// Algoritmo: 
request->send(200, "text/plain", "Ola mundo!");

Onde a função send() tem os seguintes parâmetros:

  1. int code: o código HTTP de status da resposta. Acesse, neste link, a lista de códigos HTTP.
  2. const String& contentType=String(): Tipo de mídia da Internet (Media type). Acesse, neste link, a lista de MIME types.
  3. const String& content=String(): o conteúdo da resposta.

Exemplo de uso:

// Inclusão das bibliotecas
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Instanciação do objeto da classe AsyncWebServer
AsyncWebServer server(80);

// Constantes das credenciais do WiFi
const char* ssid = "<SSID>";
const char* password = "<SENHA>";

void setup() {

  // Conecta-se ao Ponto de acesso com as credenciais fornecidas
  WiFi.begin(ssid, password);

  // disponibiliza o url "/" com o conteúdo da String abaixo
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send(200, "text/plain", "Ola mundo!");
  });

  // Servidor começa à ouvir os clientes
  server.begin();
}

void loop() {}

Resultado:

Resposta básica com conteúdo de string e cabeçalhos extras

O seguinte algoritmo responde à solicitação do cliente com conteúdo de string e cabeçalhos extras:

AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "Ola mundo!");
response->addHeader("Server","ESP Async Web Server");
request->send(response);

Onde a função beginResponse() tem os seguintes parâmetros:

  1. int code: o código HTTP de status da resposta. Acesse, neste link, a lista de códigos HTTP.
  2. const String& contentType=String(): Tipo de mídia da Internet (Media type). Acesse, neste link, a lista de MIME types.
  3. const String& content=String(): o conteúdo da resposta.

a função addHeader() tem os seguintes parâmetros:

  1. const String& name: o nome do cabeçalho HTTP;
  2. const String& value: o conteúdo do cabeçalho HTTP.

e, por fim, a função send() tem os seguintes parâmetros:

  1. AsyncWebServerResponse *response: objeto usado ​​para enviar os dados de resposta de volta ao cliente.

Exemplo de uso:

// Inclusão das bibliotecas
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Instanciação do objeto da classe AsyncWebServer
AsyncWebServer server(80);

// Constantes das credenciais do WiFi
const char* ssid = "<SSID>";
const char* password = "<SENHA>";

void setup() {

  // Conecta-se ao Ponto de acesso com as credenciais fornecidas
  WiFi.begin(ssid, password);

  // disponibiliza o url "/" com o conteúdo da String abaixo
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "Ola mundo!"); // objeto criado ​​para enviar os dados de resposta de volta ao cliente
    response->addHeader("Server","ESP Async Web Server"); // Adiciona um Cabeçalho HTTP
    request->send(response); // Envia a resposta
  });

  // Servidor começa à ouvir os clientes
  server.begin();
}

void loop() {}

Resultado:

Ao clicar na tecla F12 (Google Chrome DevTools), é possível ver o cabeçalho de resposta enviado.

Redirecionando para outro URL

Um dos seguintes algoritmos responde à solicitação do cliente redirecionando-o para outro URL:

// Algoritmo:
request->redirect("/page2");

ou

// Algoritmo:
request->redirect("https://google.com");

Onde a função redirect() tem os seguintes parâmetros:

  1. const String& url: o endereço (local ou externo) para redirecionar o cliente.

Exemplo de uso:

// Inclusão das bibliotecas
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Instanciação do objeto da classe AsyncWebServer
AsyncWebServer server(80);

// Constantes das credenciais do WiFi
const char* ssid = "<SSID>";
const char* password = "<SENHA>";

void setup() {

  // Conecta-se ao Ponto de acesso com as credenciais fornecidas
  WiFi.begin(ssid, password);

  // disponibiliza o url "/"
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->redirect("/page2"); // redireciona para page2
  });

  // disponibiliza o url "/page2"
  server.on("/page2", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send(200, "text/plain", "Voce esta na page2!");
  });

  // Servidor começa à ouvir os clientes
  server.begin();
}

void loop() {}

Resultado:

Resposta básica com código HTTP

O seguinte algoritmo responde à solicitação do cliente enviando-lhe um código HTTP:

request->send(503); // Envia código 503 (O servidor não está pronto para manipular a requisição)

Onde a função send() tem os seguintes parâmetros:

  1. int code: o código HTTP de status da resposta. Acesse, neste link, a lista de códigos HTTP.

Exemplo de uso:

// Inclusão das bibliotecas
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Instanciação do objeto da classe AsyncWebServer
AsyncWebServer server(80);

// Constantes das credenciais do WiFi
const char* ssid = "<SSID>";
const char* password = "<SENHA>";

void setup() {

  // Conecta-se ao Ponto de acesso com as credenciais fornecidas
  WiFi.begin(ssid, password);

  // disponibiliza o url "/"
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send(503); // Envia código 503 (O servidor não está pronto para manipular a requisição)
  });

  // Servidor começa à ouvir os clientes
  server.begin();
}

void loop() {}

Resultado:

Resposta básica com código HTTP e cabeçalhos HTTP extras

O seguinte algoritmo responde à solicitação do cliente enviando-lhe um código HTTP e cabeçalhos extras:

AsyncWebServerResponse *response = request->beginResponse(404); // objeto criado para enviar os dados de resposta de volta ao cliente
response->addHeader("Server","ESP Async Web Server"); // Adiciona um Cabeçalho HTTP
request->send(response); // Envia a resposta

Onde a função beginResponse() tem o seguinte parâmetro:

  1. int code: o código HTTP de status da resposta. Acesse, neste link, a lista de códigos HTTP.

a função addHeader() tem os seguintes parâmetros:

  1. const String& name: o nome do cabeçalho HTTP;
  2. const String& value: o conteúdo do cabeçalho HTTP.

e, por fim, a função send() tem os seguintes parâmetros:

  1. AsyncWebServerResponse *response: objeto usado ​​para enviar os dados de resposta de volta ao cliente.

Exemplo de uso:

// Inclusão das bibliotecas
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Instanciação do objeto da classe AsyncWebServer
AsyncWebServer server(80);

// Constantes das credenciais do WiFi
const char* ssid = "<SSID>";
const char* password = "<SENHA>";

void setup() {

  // Conecta-se ao Ponto de acesso com as credenciais fornecidas
  WiFi.begin(ssid, password);

  // disponibiliza o url "/"
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    AsyncWebServerResponse *response = request->beginResponse(503); // objeto criado ​​para enviar os dados de resposta de volta ao cliente
    response->addHeader("Server", "ESP Async Web Server"); // Adiciona um Cabeçalho HTTP
    request->send(response); // Envia a resposta
  });

  // Servidor começa à ouvir os clientes
  server.begin();
}

void loop() {}

Resultado:

Ao clicar na tecla F12 (Google Chrome DevTools), é possível ver o cabeçalho de resposta enviado.

Enviando uma página grande da memória FLASH

O seguinte algoritmo responde à solicitação do cliente enviando-lhe uma página grande da memória FLASH:

const char index_html[] PROGMEM = "..."; // grande matriz de caracteres
request->send_P(200, "text/html", index_html); // Envia a resposta

Onde a função send_P() tem o seguinte parâmetro:

  1. int code: o código HTTP de status da resposta. Acesse, neste link, a lista de códigos HTTP;
  2. const String& contentType=String(): Tipo de mídia da Internet (Media type). Acesse, neste link, a lista de MIME types.
  3. PGM_P content: o conteúdo para envio, proveniente de uma variável constante do tipo array de char que tenha um modificador PROGMEM. Este modificador permite armazenar dados na memória flash (memória de programa) em vez da SRAM. Gostaria de saber mais sobre PROGMEM? Acesse o artigo Progmem – Economizando Memória do Microcontrolador, aqui mesmo, no blog Eletrogate;
  4. AwsTemplateProcessor callback=nullptr: parâmetro opcional para processar conteúdo contendo modelos.

Exemplo de uso:

// Inclusão das bibliotecas
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Instanciação do objeto da classe AsyncWebServer
AsyncWebServer server(80);

// Constantes das credenciais do WiFi
const char* ssid = "<SSID>";
const char* password = "<SENHA>";

void setup() {

  // Conecta-se ao Ponto de acesso com as credenciais fornecidas
  WiFi.begin(ssid, password);

  // disponibiliza o url "/"
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    const char index_html[] PROGMEM = R"====(
      <html>
        <head>
          <meta charset='utf-8'/>
        </head>
        <body>
          <h1>
            Arduino
          </h1>
          
          <p>
            Arduino é uma plataforma de prototipagem eletrônica de hardware livre e de placa única,
            projetada com um microcontrolador Atmel AVR com suporte de entrada/saída embutido, uma 
            linguagem de programação padrão, a qual tem origem em Wiring, e é essencialmente C/C++. 
            O objetivo do projeto é criar ferramentas que são acessíveis, com baixo custo, 
            flexíveis e fáceis de se usar por principiantes e profissionais. Principalmente para 
            aqueles que não teriam alcance aos controladores mais sofisticados e ferramentas mais 
            complicadas.
          </p>
          <p>
            Pode ser usado para o desenvolvimento de objetos interativos independentes, 
            ou ainda para ser conectado a um computador hospedeiro. 
            Uma típica placa Arduino é composta por um controlador, algumas linhas de E/S digital e analógica, 
            além de uma interface serial ou USB, para interligar-se ao hospedeiro, que é usado para programá-la 
            e interagi-la em tempo real. A placa em si não possui qualquer recurso de rede, porém é comum combinar 
            um ou mais Arduinos deste modo, usando extensões apropriadas chamadas de shields. 
            A interface do hospedeiro é simples, podendo ser escrita em várias linguagens. 
            A mais popular é a Processing, mas outras que podem comunicar-se com a conexão serial são: Max/MSP, 
            Pure Data, SuperCollider, ActionScript e Java. 
            Em 2010 foi realizado um documentário sobre a plataforma chamado Arduino: The Documentary.
          </p>
          <figure>
            <img src='https://upload.wikimedia.org/wikipedia/commons/thumb/7/71/Arduino-uno-perspective-transparent.png/681px-Arduino-uno-perspective-transparent.png' width='300px'>
            <figcaption>
              Arduino versão Uno. Créditos: Wikipedia
            </figcaption>
          </figure>
          
          <p>
            O nome Arduino vem de um bar em Ivrea, Itália, onde alguns dos fundadores do projeto costumavam se reunir. 
            O bar foi nomeado após Arduíno de Ivrea, que foi o marquês da Marca de Ivrea e Rei da Itália de 1002 a 1014.
          </p>
          <p>
            Fonte: <a href='https://pt.wikipedia.org/wiki/Arduino' target='_blank'>https://pt.wikipedia.org/wiki/Arduino</a>
          </p>
        </body>
      <html>
    )===="; // grande matriz de caracteres
    request->send_P(200, "text/html", index_html); // Envia a resposta
  });

  // Servidor começa à ouvir os clientes
  server.begin();
}

void loop() {}

No algoritmo acima, foi utilizado o recurso de “Raw String Literal”: R"delimitador( . . . . )delimitador". Aqui, o delimitador é opcional e pode ser um caractere, exceto a barra invertida /, espaços em branco   e parênteses (). Este recurso da linguagem C++ permite uma string na qual os caracteres de escape como \n , \t ou \ de C++ não são processados pelo compilador. Isto faz com que uma série de caracteres sejam passadas precisamente como sequência de caracteres brutos.

Resultado:

Enviando uma página grande do PROGMEM com cabeçalhos HTTP extras

O seguinte algoritmo responde à solicitação do cliente enviando-lhe uma página grande código da memória FLASH juntamente com cabeçalhos HTTP extras:

const char index_html[] PROGMEM = "..."; // grande matriz de caracteres
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", index_html); // objeto criado para enviar os dados de resposta de volta ao cliente
response->addHeader("Server","ESP Async Web Server"); // Adiciona um Cabeçalho HTTP
request->send(response); // Envia a resposta

Onde a função beginResponse_P() tem o seguinte parâmetro:

  1. int code: o código HTTP de status da resposta. Acesse, neste link, a lista de códigos HTTP;
  2. const String& contentType=String(): Tipo de mídia da Internet (Media type). Acesse, neste link, a lista de MIME types.
  3. PGM_P content: o conteúdo para envio, proveniente de uma variável constante do tipo array de char que tenha um modificador PROGMEM. Este modificador permite armazenar dados na memória flash (memória de programa) em vez da SRAM. Gostaria de saber mais sobre PROGMEM? Acesse o artigo Progmem – Economizando Memória do Microcontrolador, aqui mesmo, no blog Eletrogate;
  4. AwsTemplateProcessor callback=nullptr: parâmetro opcional para processar conteúdo contendo modelos.

a função addHeader() tem os seguintes parâmetros:

  1. const String& name: o nome do cabeçalho HTTP;
  2. const String& value: o conteúdo do cabeçalho HTTP.

e, por fim, a função send() tem os seguintes parâmetros:

  1. AsyncWebServerResponse *response: objeto usado ​​para enviar os dados de resposta de volta ao cliente.

Exemplo de uso:

// Inclusão das bibliotecas
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Instanciação do objeto da classe AsyncWebServer
AsyncWebServer server(80);

// Constantes das credenciais do WiFi
const char* ssid = "<SSID>";
const char* password = "<SENHA>";

void setup() {

  // Conecta-se ao Ponto de acesso com as credenciais fornecidas
  WiFi.begin(ssid, password);

  // disponibiliza o url "/"
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    const char index_html[] PROGMEM = R"====(
      <html>
        <head>
          <meta charset='utf-8'/>
        </head>
        <body>
          <h1>
            Arduino
          </h1>
          
          <p>
            Arduino é uma plataforma de prototipagem eletrônica de hardware livre e de placa única,
            projetada com um microcontrolador Atmel AVR com suporte de entrada/saída embutido, uma 
            linguagem de programação padrão, a qual tem origem em Wiring, e é essencialmente C/C++. 
            O objetivo do projeto é criar ferramentas que são acessíveis, com baixo custo, 
            flexíveis e fáceis de se usar por principiantes e profissionais. Principalmente para 
            aqueles que não teriam alcance aos controladores mais sofisticados e ferramentas mais 
            complicadas.
          </p>
          <p>
            Pode ser usado para o desenvolvimento de objetos interativos independentes, 
            ou ainda para ser conectado a um computador hospedeiro. 
            Uma típica placa Arduino é composta por um controlador, algumas linhas de E/S digital e analógica, 
            além de uma interface serial ou USB, para interligar-se ao hospedeiro, que é usado para programá-la 
            e interagi-la em tempo real. A placa em si não possui qualquer recurso de rede, porém é comum combinar 
            um ou mais Arduinos deste modo, usando extensões apropriadas chamadas de shields. 
            A interface do hospedeiro é simples, podendo ser escrita em várias linguagens. 
            A mais popular é a Processing, mas outras que podem comunicar-se com a conexão serial são: Max/MSP, 
            Pure Data, SuperCollider, ActionScript e Java. 
            Em 2010 foi realizado um documentário sobre a plataforma chamado Arduino: The Documentary.
          </p>
          <figure>
            <img src='https://upload.wikimedia.org/wikipedia/commons/thumb/7/71/Arduino-uno-perspective-transparent.png/681px-Arduino-uno-perspective-transparent.png' width='300px'>
            <figcaption>
              Arduino versão Uno. Créditos: Wikipedia
            </figcaption>
          </figure>
          
          <p>
            O nome Arduino vem de um bar em Ivrea, Itália, onde alguns dos fundadores do projeto costumavam se reunir. 
            O bar foi nomeado após Arduíno de Ivrea, que foi o marquês da Marca de Ivrea e Rei da Itália de 1002 a 1014.
          </p>
          <p>
            Fonte: <a href='https://pt.wikipedia.org/wiki/Arduino' target='_blank'>https://pt.wikipedia.org/wiki/Arduino</a>
          </p>
        </body>
      <html>
    )===="; // grande matriz de caracteres
    AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", index_html);// objeto criado para enviar os dados de resposta de volta ao cliente
    response->addHeader("Server","ESP Async Web Server"); // Adiciona um Cabeçalho HTTP
    request->send(response); // Envia a resposta
  });

  // Servidor começa à ouvir os clientes
  server.begin();
}

void loop() {}

Resultado:

Enviando uma página grande do PROGMEM contendo modelos

O seguinte algoritmo responde à solicitação do cliente enviando-lhe uma página grande código da memória FLASH contendo modelos:

String processor(const String& var)
{
  if(var == "OLA_PARA_MODELO")
    return "Olá mundo!";
  return String();
}

// ...

const char index_html[] PROGMEM = "..."; // grande matriz de caracteres
request->send_P(200, "text/html", index_html, processor);

Onde temos uma função chamada processor, devendo ter o seguinte parâmetro obrigatório:

  1. const String& var: variável em que será recebido o nome (valor) do modelo à ser substituído.

Dentro da função processor, temos a (ou as) condicional que irá verificar se a variável var tem um valor especifico informado. Caso tenha, é retornado o valor para substituição.

A função send_P() tem o seguinte parâmetro:

  1. int code: o código HTTP de status da resposta. Acesse, neste link,a lista de códigos HTTP;
  2. const String& contentType: Tipo de mídia da Internet (Media type). Acesse, neste link, a lista de MIME types;
  3. PGM_P content: o conteúdo para envio, proveniente de uma variável constante do tipo array de char que tenha um modificador PROGMEM. Este modificador permite armazenar dados na memória flash (memória de programa) em vez da SRAM. Gostaria de saber mais sobre PROGMEM? Acesse o artigo Progmem – Economizando Memória do Microcontrolador aqui mesmo do blog Eletrogate;
  4. AwsTemplateProcessor callback=nullptr: parâmetro para processar conteúdo contendo modelos.

Exemplo de uso:

// Inclusão das bibliotecas
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Instanciação do objeto da classe AsyncWebServer
AsyncWebServer server(80);

// Constantes das credenciais do WiFi
const char* ssid = "<SSID>";
const char* password = "<SENHA>";

String processor(const String& var)
{
  if(var == "OLA_PARA_MODELO")
    return "Olá mundo!";
  return String();
}

void setup() {

  // Conecta-se ao Ponto de acesso com as credenciais fornecidas
  WiFi.begin(ssid, password);

  // disponibiliza o url "/"
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    const char index_html[] PROGMEM = R"====(
      <html>
        <head>
          <meta charset='utf-8'/>
        </head>
        <body>
          <h1>
            Teste
          </h1>          
          <p>
            Mensagem %OLA_PARA_MODELO% recebida.
          </p>          
        </body>
      <html>
    )===="; // grande matriz de caracteres
    request->send_P(200, "text/html", index_html, processor); // Envia a resposta
  });

  // Servidor começa à ouvir os clientes
  server.begin();
}

void loop() {}

Para a substituição do modelo funcionar, deve-se inserir, no conteúdo a ser enviado, o nome do modelo entre dois símbolos de Porcentagem (%). O conteúdo a ser enviado não deve conter o símbolo de porcentagem (%) sozinho em outros locais, pois isso iria “quebrar” o código.

Resultado:

Enviando uma página grande do PROGMEM contendo modelos e cabeçalhos HTTP extras

O seguinte algoritmo responde à solicitação do cliente enviando-lhe uma página grande código da memória FLASH contendo modelos e cabeçalhos HTTP extras:

String processor(const String& var)
{
  if(var == "OLA_PARA_MODELO")
    return "Olá mundo!";
  return String();
}

// ...

const char index_html[] PROGMEM = "..."; // grande matriz de caracteres
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", index_html, processor); // objeto criado para enviar os dados de resposta de volta ao cliente
response->addHeader("Server","ESP Async Web Server"); // Adiciona um Cabeçalho HTTP
request->send(response); // Envia a resposta

Onde temos uma função chamada processor, devendo ter o seguinte parâmetro obrigatório:

  1. const String& var: variável em que será recebido o nome (valor) do modelo a ser substituído.

Dentro da da função processor, temos a (ou as) condicional que irá verificar se a variável var tem um valor especifico informado. Caso tenha, é retornado o valor para substituição.

A função beginResponse_P() tem o seguinte parâmetro:

  1. int code: o código HTTP de status da resposta. Acesse, neste link, a lista de códigos HTTP;
  2. const String& contentType: Tipo de mídia da Internet (Media type). Acesse, neste link, a lista de MIME types;
  3. PGM_P content: o conteúdo para envio, proveniente de uma variável constante do tipo array de char que tenha um modificador PROGMEM. Este modificador permite armazenar dados na memória flash (memória de programa) em vez da SRAM. Gostaria de saber mais sobre PROGMEM? Acesse o artigo Progmem – Economizando Memória do Microcontrolador aqui mesmo do blog Eletrogate;
  4. AwsTemplateProcessor callback=nullptr: parâmetro opcional para processar conteúdo contendo modelos.

E, a função addHeader() tem os seguintes parâmetros:

  1. const String& name: o nome do cabeçalho HTTP;
  2. const String& value: o conteúdo do cabeçalho HTTP.

E, por fim, a função send() tem os seguintes parâmetros:

  1. AsyncWebServerResponse *response: objeto usado ​​para enviar os dados de resposta de volta ao cliente.

Exemplo de uso:

// Inclusão das bibliotecas
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Instanciação do objeto da classe AsyncWebServer
AsyncWebServer server(80);

// Constantes das credenciais do WiFi
const char* ssid = "<SSID>";
const char* password = "<SENHA>";

String processor(const String& var)
{
  if(var == "OLA_PARA_MODELO")
    return "Olá mundo!";
  return String();
}

void setup() {

  // Conecta-se ao Ponto de acesso com as credenciais fornecidas
  WiFi.begin(ssid, password);

  // disponibiliza o url "/"
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    const char index_html[] PROGMEM = R"====(
      <html>
        <head>
          <meta charset='utf-8'/>
        </head>
        <body>
          <h1>
            Teste
          </h1>          
          <p>
            Mensagem %OLA_PARA_MODELO% recebida.
          </p>          
        </body>
      <html>
    )===="; // grande matriz de caracteres
    AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", index_html, processor); // objeto criado para enviar os dados de resposta de volta ao cliente
    response->addHeader("Server","ESP Async Web Server"); // Adiciona um Cabeçalho HTTP
    request->send(response); // Envia a resposta
  });

  // Servidor começa à ouvir os clientes
  server.begin();
}

void loop() {}

Resultado:

Respondendo com conteúdo proveniente da SPIFFS

Os seguintes algoritmos respondem à solicitação do cliente enviando-lhe uma página com conteúdo vindo de um arquivo armazenado na SPIFFS:

//Envia página /index.html com tipo de conteúdo padrão
request->send(SPIFFS, "/index.html");

ou

//Envia página /index.html como texto
request->send(SPIFFS, "/index.html", "text/plain");

ou

// Disponibiliza a página /index.html para download
request->send(SPIFFS, "/index.html", String(), true);

Onde as funções send() tem os seguintes parâmetros:

  1. FS &fs: o tipo do Filesystem (normalmente, é usado o SPIFFS);
  2. const String& path: o caminho do arquivo no diretório do Filesystem;
  3. const String& contentType=String(): o tipo de mídia da Internet (Media type). Acesse, neste link, a lista de MIME types.
  4. bool download=false: se o arquivo vai, ou não, ser disponibilizado para download direto;
  5. AwsTemplateProcessor callback=nullptr: parâmetro opcional para processar conteúdo contendo modelos.

Exemplo de uso:

// Inclusão das bibliotecas
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <FS.h>
#include <SPIFFS.h>

// Instanciação do objeto da classe AsyncWebServer
AsyncWebServer server(80);

// Constantes das credenciais do WiFi
const char* ssid = "<SSID>";
const char* password = "<SENHA>";

void setup() {

  // Conecta-se ao Ponto de acesso com as credenciais fornecidas
  WiFi.begin(ssid, password);

  Serial.begin(115200);

  if(!SPIFFS.begin(true)){
    Serial.println("Ocorreu um erro ao montar SPIFFS");
    return;
  }

  // disponibiliza o url "/"
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    // Disponibiliza a página /index.html
    request->send(SPIFFS, "/index.html");

  });

  // Servidor começa à ouvir os clientes
  server.begin();
}

void loop() {}

Para este exemplo de algoritmo funcionar, deve-se fazer o upload de um arquivo chamado index.html para a SPIFFS. Para o passo a passo sobre como fazer isso, consulte o post SPIFFS: Armazenamento de Arquivos do ESP32, aqui mesmo, no blog Eletrogate.

SPIFFS: Armazenamento de Arquivos do ESP32

O arquivo index.html continha o seguinte conteúdo:

<html>
<head>
  <meta charset='utf-8'/>
</head>
<body>
  <h1>
    ESP32
  </h1>          
  <p>
    ESP32 é uma série de microcontroladores de baixo custo e baixo consumo de energia.
  </p>          
</body>
<html>

Resultado:

Respondendo com conteúdo proveniente da SPIFFS com cabeçalhos HTTP extras

Os seguintes algoritmos respondem à solicitação do cliente enviando-lhe uma página com conteúdo vindo de um arquivo armazenado na SPIFFS juntamente com cabeçalhos HTTP extras:

// Envie index.html com o tipo de conteúdo padrão
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/index.htm");
response->addHeader("Server","ESP Async Web Server");
request->send(response);

ou

// Enviar index.html como texto
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/index.htm", "text/plain");
response->addHeader("Server","ESP Async Web Server");
request->send(response);

ou

// Baixar index.html
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/index.htm", String(), true);
response->addHeader("Server","ESP Async Web Server");
request->send(response);

A função beginResponse_P() tem os seguinte parâmetro:

  1. FS &fs: o tipo do Filesystem (normalmente é usado o SPIFFS);
  2. const String& path: o caminho do arquivo no diretório do Filesystem;
  3. const String& contentType=String(): o tipo de mídia da Internet (Media type). Acesse, neste link, a lista de MIME types.
  4. bool download=false: se o arquivo vai, ou não, ser disponibilizado para download direto;
  5. AwsTemplateProcessor callback=nullptr: parâmetro opcional para processar conteúdo contendo modelos.

a função addHeader() tem os seguintes parâmetros:

  1. const String& name: o nome do cabeçalho HTTP;
  2. const String& value: o conteúdo do cabeçalho HTTP.

e, por fim, a função send() tem os seguintes parâmetros:

  1. AsyncWebServerResponse *response: objeto usado ​​para enviar os dados de resposta de volta ao cliente.

Exemplo de uso:

// Inclusão das bibliotecas
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <FS.h>
#include <SPIFFS.h>

// Instanciação do objeto da classe AsyncWebServer
AsyncWebServer server(80);

// Constantes das credenciais do WiFi
const char* ssid = "<SSID>>";
const char* password = "<SENHA>";

void setup() {

  // Conecta-se ao Ponto de acesso com as credenciais fornecidas
  WiFi.begin(ssid, password);

  

  Serial.begin(115200);

  if(!SPIFFS.begin(true)){
    Serial.println("Ocorreu um erro ao montar SPIFFS");
    return;
  }

  // disponibiliza o url "/"
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    
    // Enviar index.html como página html
    AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/index.html", "text/html");
    response->addHeader("Server","ESP Async Web Server");
    request->send(response);
  });

  // Servidor começa à ouvir os clientes
  server.begin();
}

void loop() {}

Resultado:

Respondendo com conteúdo proveniente da SPIFFS contendo modelos

O seguinte algoritmo responde à solicitação do cliente enviando-lhe uma página com conteúdo proveniente da SPIFFS e contendo modelos:

String processor(const String& var)
{
  if(var == "OLA_FROM_TEMPLATE")
    return "Arduino e ESP32!";
  return String();
}

// ...

//Envia página index.html com função de processador de modelo 
request->send(SPIFFS, "/index.html", String(), false, processor);

Onde, na função send(), tem os seguinte parâmetro:

  1. FS &fs: o tipo do Filesystem (normalmente é usado o SPIFFS);
  2. const String& path: o caminho do arquivo no diretório do Filesystem;
  3. const String& contentType=String(): o tipo de mídia da Internet (Media type). Acesse, neste link, a lista de MIME types.
  4. bool download=false: se o arquivo vai, ou não, ser disponibilizado para download direto;
  5. AwsTemplateProcessor callback=nullptr: parâmetro para processar conteúdo contendo modelos.

Exemplo de uso:

// Inclusão das bibliotecas
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <FS.h>
#include <SPIFFS.h>

// Instanciação do objeto da classe AsyncWebServer
AsyncWebServer server(80);

// Constantes das credenciais do WiFi
const char* ssid = "<SSID>";
const char* password = "<SENHA>";

String processor(const String& var)
{
  if(var == "OLA_FROM_TEMPLATE")
    return "Arduino e ESP32!";
  return String();
}

void setup() {

  // Conecta-se ao Ponto de acesso com as credenciais fornecidas
  WiFi.begin(ssid, password);  

  Serial.begin(115200);

  if(!SPIFFS.begin(true)){
    Serial.println("Ocorreu um erro ao montar SPIFFS");
    return;
  }

  // disponibiliza o url "/"
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    
    // Enviar index.html como página html
    request->send(SPIFFS, "/index.html", "text/html");
  });

  // Servidor começa à ouvir os clientes
  server.begin();
}

void loop() {}

Na SPIFFS, o arquivo index.html possui o seguinte conteúdo:

<html>
    <head>
        <meta charset = "utf-8">
    </head>
    <body>
        <h1>Teste com ESP32</h1>
        <p> A mensagem %OLA_FROM_TEMPLATE% foi recebida.</p>
    </body>
</html>

Resultado:

Respondendo através de ‘print’

O seguinte algoritmo responde à solicitação do cliente enviando-lhe uma página com conteúdo enviado através de print’s:

AsyncResponseStream* response = request->beginResponseStream("text/html");
response->print("<p>Ola<p/>");
String variavelQualquer = "mundo!";
response->printf("<p>Olá %s</p>", variavelQualquer);

//envie a resposta por último
request->send(response);

Onde, na função printf(), tem os seguintes parâmetros:

  1. const char * format, ...: Cadeia de caracteres que contém o texto a ser passado para ser impresso. Opcionalmente, pode conter especificadores de formato incorporados que são substituídos pelos valores especificados em argumentos adicionais subsequentes e formatados conforme solicitado. Referência: https://cplusplus.com/reference/cstdio/printf/

Exemplo de uso:

// Inclusão das bibliotecas
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Instanciação do objeto da classe AsyncWebServer
AsyncWebServer server(80);

// Constantes das credenciais do WiFi
const char* ssid = "<SSID>";
const char* password = "<SENHA>";

void setup() {

  // Conecta-se ao Ponto de acesso com as credenciais fornecidas
  WiFi.begin(ssid, password);

  Serial.begin(115200);

  // disponibiliza o url "/"
  server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
    AsyncResponseStream* response = request->beginResponseStream("text/html");
    response->print("<html>");
    response->print("<head> ");
    response->print("<meta charset='utf-8'/>");
    response->print("</head>");
    response->print("<body>");
    response->print("<h1>");
    response->printf("Olá %s",request->client()->remoteIP().toString().c_str());
    response->print("</h1>");
    response->print("<p>");
    response->print("Parágrafo aqui.");
    response->print("</p>");
    response->print("</body>");
    response->print("<html>");

    //envie a resposta por último
    request->send(response);
  });

  // Servidor começa à ouvir os clientes
  server.begin();
}

void loop() {}

Resultado:

Respondendo com resposta básica JSON

O seguinte algoritmo responde à solicitação do cliente enviando-lhe uma página com conteúdo JSON:

#include "AsyncJson.h"
#include "ArduinoJson.h"

// ...

AsyncResponseStream* response = request->beginResponseStream("application/json");
DynamicJsonDocument doc(1024);
doc["heap"] = ESP.getFreeHeap();
doc["ssid"] = WiFi.SSID();
serializeJson(doc, *response);
request->send(response);

Onde, na função beginResponseStream(), há os seguintes parâmetros:

  1. const String& contentType: Tipo de mídia da Internet (Media type). Neste caso, o tipo é Formato JSON. Acesse, neste link, a lista de MIME types;
  2. size_t bufferSize=1460: parâmetro opcional que define o tamanho do buffer do response.

a variável doc, um objeto da classe DynamicJsonDocument, que é instanciado informando a quantidade de RAM, na heap, a ser alocada para o documento -para saber quanto alocar, use a ferramenta do ArduinoJson, em https://arduinojson.org/v6/assistant– e, por fim, a função serializeJson(), que serializa o documento JSON para criar um documento reduzido, ou seja, um documento sem espaços ou quebra de linha entre os valores. Esta função tem os seguintes parâmetros:

  1. JsonDocument: o documento JSON;
  2. output: o buffer de destino para onde que o documento JSON deve ser passado (no caso deste algoritmo, o response);

NOTA: O modo como foi construído este JSON segue o novo padrão da biblioteca Arduino JSON versão 6. Deseja mais informações sobre a migração do padrão Arduino JSON versão 5 para Arduino JSON versão 6? Consulte a documentação em https://arduinojson.org/v6/doc/upgrade/.

Exemplo de uso:

// Inclusão das bibliotecas
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include "AsyncJson.h"
#include "ArduinoJson.h"

// Instanciação do objeto da classe AsyncWebServer
AsyncWebServer server(80);

// Constantes das credenciais do WiFi
const char* ssid = "<SSID>";
const char* password = "<SENHA>";

void setup() {

  // Conecta-se ao Ponto de acesso com as credenciais fornecidas
  WiFi.begin(ssid, password);

  Serial.begin(115200);

  // disponibiliza o url "/"
  server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
    AsyncResponseStream* response = request->beginResponseStream("application/json");
    DynamicJsonDocument doc(1024);
    doc["heap"] = ESP.getFreeHeap();
    doc["ssid"] = "Rede_De_Testes";
    serializeJson(doc, *response);
    request->send(response);
  });

  // Servidor começa à ouvir os clientes
  server.begin();
}

void loop() {}

No algoritmo acima, deve-se estar instalado a biblioteca ArduinoJson. Para instalação, pesquise-a no gerenciador de bibliotecas e a instale.

Resultado:


Utilizando o WebServer assíncrono: Solicitações

Listando todos os cabeçalhos HTTP coletados

O seguinte algoritmo mostra, na Serial, todos os cabeçalhos HTTP da requisição do cliente:

//Listar todos os cabeçalhos coletados
int headers = request->headers();  // cabeçalhos
int i;                             // índice
for (i = 0; i < headers; i++) {
  AsyncWebHeader* h = request->getHeader(i);  // pega o cabeçalho do índice informado
  Serial.printf("Cabeçalho[%s]: %s\n",
                h->name().c_str(),    // O nome do cabeçalho
                h->value().c_str());  // O valor do cabeçalho
}

Exemplo de uso:

// Inclusão das bibliotecas
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Instanciação do objeto da classe AsyncWebServer
AsyncWebServer server(80);

// Constantes das credenciais do WiFi
const char* ssid = "<SSID>";
const char* password = "<SENHA>";

void setup() {

  // Conecta-se ao Ponto de acesso com as credenciais fornecidas
  WiFi.begin(ssid, password);

  Serial.begin(115200);

  // disponibiliza o url "/"
  server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
    //Listar todos os cabeçalhos coletados
    int headers = request->headers();  // cabeçalhos
    int i;                             // índice
    for (i = 0; i < headers; i++) {
      AsyncWebHeader* h = request->getHeader(i);  // pega o cabeçalho do índice informado
      Serial.printf("Cabeçalho[%s]: %s\n",
                    h->name().c_str(),    // O nome do cabeçalho
                    h->value().c_str());  // O valor do cabeçalho
    }
  });

  // Servidor começa à ouvir os clientes
  server.begin();
}

void loop() {}

Resultado:

Ao acessar IP_DO_ESP32/ no navegador web, no monitor serial, é mostrado:

Obtendo cabeçalho específico pelo nome

O seguinte algoritmo mostra, na Serial, um cabeçalho HTTP específico, obtido pelo nome, da requisição do cliente:

//obter cabeçalho específico por nome
if (request->hasHeader("Host"))  // se tem o cabeçalho com o nome "teste", ...
{
  AsyncWebHeader* h = request->getHeader("Host");  // pega o cabeçalho pelo nome informado informado
  Serial.printf("Host: %s \n",
                h->value().c_str()); // O valor do cabeçalho
}

Exemplo de uso:

// Inclusão das bibliotecas
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Instanciação do objeto da classe AsyncWebServer
AsyncWebServer server(80);

// Constantes das credenciais do WiFi
const char* ssid = "<SSID>";
const char* password = "<SENHA>";

void setup() {

  // Conecta-se ao Ponto de acesso com as credenciais fornecidas
  WiFi.begin(ssid, password);

  Serial.begin(115200);

  // disponibiliza o url "/"
  server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
    //obter cabeçalho específico por nome
    if (request->hasHeader("teste"))  // se tem o cabeçalho com o nome "teste", ...
    {
      AsyncWebHeader* h = request->getHeader("teste");  // pega o cabeçalho pelo nome informado informado
      Serial.printf("teste: %s \n",
                    h->value().c_str()); // O valor do cabeçalho
    }
  });

  // Servidor começa à ouvir os clientes
  server.begin();
}

void loop() {}

Resultado:

Ao acessar o IP no navegador web, no monitor serial, é mostrado:

Listando todos os parâmetros HTTP

O seguinte algoritmo mostra, na Serial, todos os parâmetros HTTP (seja eles POST, FILE ou GET), da requisição do cliente:

// Listar todos os parâmetros
int params = request->params(); // parâmetros
int i; // índice
for (i = 0; i < params; i++){
  AsyncWebParameter* p = request->getParam(i); // pega o cabeçalho do índice informado
  if (p->isFile()) // se parâmetro for um arquivo, ...
  {
    Serial.printf("FILE[%s]: %s, size: %u\n", p->name().c_str(), p->value().c_str(), p->size());
  } else if (p->isPost()) // senão, se parâmetro for do tipo POST, ...
  {
    Serial.printf("POST[%s]: %s\n", p->name().c_str(), p->value().c_str());
  } else // senão, parâmetro é do tipo GET, ...
  {
    Serial.printf("GET[%s]: %s\n", p->name().c_str(), p->value().c_str());
  }
}

Exemplo de uso:

// Inclusão das bibliotecas
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Instanciação do objeto da classe AsyncWebServer
AsyncWebServer server(80);

// Constantes das credenciais do WiFi
const char* ssid = "<SSID>";
const char* password = "<SENHA>";

void setup() {

  // Conecta-se ao Ponto de acesso com as credenciais fornecidas
  WiFi.begin(ssid, password);

  Serial.begin(115200);

  // disponibiliza o url "/"
  server.on("/", HTTP_ANY, [](AsyncWebServerRequest* request) {
    // Listar todos os parâmetros
    int params = request->params(); // parâmetros
    int i; // índice
    for (i = 0; i < params; i++){
      AsyncWebParameter* p = request->getParam(i); // pega o cabeçalho do índice informado
      if (p->isFile()) // se parâmetro for um arquivo, ...
      {
        Serial.printf("FILE[%s]: %s, size: %u\n", p->name().c_str(), p->value().c_str(), p->size());
      } else if (p->isPost()) // senão, se parâmetro for do tipo POST, ...
      {
        Serial.printf("POST[%s]: %s\n", p->name().c_str(), p->value().c_str());
      } else // senão, parâmetro é do tipo GET, ...
      {
        Serial.printf("GET[%s]: %s\n", p->name().c_str(), p->value().c_str());
      }
    }
    String html = R"====(
    <html>
  <head>
    <meta charset = "utf-8">
    <script>
      function imprimeNaSerial()
      {
        var xhr = new XMLHttpRequest();
        xhr.open("POST", '/', true); // inicializa uma nova requisição, ou reinicializa uma requisição já existente.
        xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); // define o valor do cabeçalho de uma requisição HTTP
        xhr.onreadystatechange = function() {
          if (this.readyState == 4 && this.status == 200) {
             // Typical action to be performed when the document is ready:
            alert("Requisição enviada com sucesso");
          }else if(this.readyState == 4 && this.status != 200){
          alert("Requisição não foi enviada, pois houve alguma falha");
          }
        };
        xhr.send("parametro1=123&outro-paramentro=xyz"); // envia uma requisição para o servidor.
      }
    </script>
  </head>
  <body>
    <h1>Teste com ESP32</h1>
    <p> Clique no botão abaixo e imprima na Serial dois Parâmetros POST (parametro1 com valor 123 e outro-parametro com valor xyz): </p>
    <button onclick="imprimeNaSerial()"> Imprimir Parâmetros POST na Serial</button>
  </body>
</html>
    )====";
    request->send(200,"text/html",html);
  });

  // Servidor começa à ouvir os clientes
  server.begin();
}

void loop() {}

Resultado:

Descrição do Resultado:

  • (Exemplo de Parâmetro GET) Ao acessar IP_DO_ESP32/?teste=113abc no navegador, no monitor Serial, é mostrado:
  • (Exemplo de Parâmetro POST): Ao acessar IP_DO_ESP32/ no navegador e clicar no botão Imprimir Parâmetros POST na Serial, é acionada a função imprimeNaSerial(), da página HTML, que envia os parâmetros POST ao web server do ESP32. No monitor Serial, é mostrado:

Verificando se o parâmetro GET existe

O seguinte algoritmo mostra, na Serial, se um determinado parâmetro HTTP GET específico, obtido pelo nome, existe, ou não, através da requisição do cliente:

// Verifique se o parâmetro GET existe ou não
if (request->hasParam("nome-qualquer")) {
  AsyncWebParameter* p = request->getParam("nome-qualquer");
  Serial.printf("O parâmetro GET %s existe e possui o valor %s\n", p->name().c_str(), p->value().c_str());
}else{
  Serial.print("O parâmetro GET nome-qualquer não existe nesta requisição\n");
}

Exemplo de uso:

// Inclusão das bibliotecas
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Instanciação do objeto da classe AsyncWebServer
AsyncWebServer server(80);

// Constantes das credenciais do WiFi
const char* ssid = "<SSID>";
const char* password = "<SENHA>";

void setup() {

  // Conecta-se ao Ponto de acesso com as credenciais fornecidas
  WiFi.begin(ssid, password);

  Serial.begin(115200);

  // disponibiliza o url "/"
  server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
    // Verifique se o parâmetro GET existe ou não
    if (request->hasParam("nome-qualquer")) {
      AsyncWebParameter* p = request->getParam("nome-qualquer");
      Serial.printf("O parâmetro GET %s existe e possui o valor %s\n", p->name().c_str(), p->value().c_str());
    }else{
      Serial.print("O parâmetro GET nome-qualquer não existe nesta requisição\n");
    }
  });

  // Servidor começa à ouvir os clientes
  server.begin();
}

void loop() {}

Resultado:

  • Ao acessar IP_DO_ESP32/?nome-qualquer=valor-qualquer no navegador, no monitor Serial, é mostrado:
  • Ao acessar IP_DO_ESP32/ no navegador, no monitor Serial, é mostrado:

Verificando se o parâmetro POST existe

O seguinte algoritmo mostra, na Serial, se um determinado parâmetro HTTP POST específico, obtido pelo nome, existe ou não através da requisição do cliente:

// Verifique se o parâmetro POST existe ou não
if (request->hasParam("nome-qualquer",true)) {
  AsyncWebParameter* p = request->getParam("nome-qualquer", true);
  Serial.printf("O parâmetro POST %s existe e possui o valor %s\n", p->name().c_str(), p->value().c_str());
}else{
  Serial.print("O parâmetro POST nome-qualquer não existe nesta requisição\n");
}

Exemplo de uso:

// Inclusão das bibliotecas
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Instanciação do objeto da classe AsyncWebServer
AsyncWebServer server(80);

// Constantes das credenciais do WiFi
const char* ssid = "<SSID>";
const char* password = "<SENHA>";

void setup() {

  // Conecta-se ao Ponto de acesso com as credenciais fornecidas
  WiFi.begin(ssid, password);

  Serial.begin(115200);

  // disponibiliza o url "/" por acesso somente via POST
  server.on("/", HTTP_POST, [](AsyncWebServerRequest * request) {
    // Verifique se o parâmetro POST existe ou não
    if (request->hasParam("nome-qualquer", true)) {
      AsyncWebParameter* p = request->getParam("nome-qualquer", true);
      Serial.printf("O parâmetro POST %s existe e possui o valor %s\n", p->name().c_str(), p->value().c_str());
    } else {
      Serial.print("O parâmetro POST nome-qualquer não existe nesta requisição\n");
    }
    request->send(200);
  });

  // disponibiliza o url "/" por acesso somente via GET
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    String html = R"====(
    <html>
  <head>
    <meta charset = "utf-8">
    <script>
      function imprimeNaSerial() // função, que quando clicar no botão, será enviado uma requisição POST para o server
      {
        var xhr = new XMLHttpRequest();
        xhr.open("POST", '/', true); // inicializa uma nova requisição, ou reinicializa uma requisição já existente.
        xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); // define o valor do cabeçalho de uma requisição HTTP
        xhr.onreadystatechange = function() {
          if (this.readyState == 4 && this.status == 200) {
             // Typical action to be performed when the document is ready:
            alert("Requisição enviada com sucesso");
          }else if(this.readyState == 4 && this.status != 200){
          alert("Requisição não foi enviada, pois houve alguma falha");
          }
        };
        xhr.send("nome-qualquer=valor-qualquer"); // envia uma requisição para o servidor.
      }
    </script>
  </head>
  <body>
    <h1>Teste com ESP32</h1>
    <p> Clique no botão abaixo e imprima na Serial a verificação se um Parâmetro POST (nome-qualquer com valor valor-qualquer) existe ou não: </p>
    <button onclick="imprimeNaSerial()"> Imprimir Parâmetros POST na Serial</button>
  </body>
</html>
    )====";
    request->send(200, "text/html", html);
  });

  // Servidor começa à ouvir os clientes
  server.begin();
}

void loop() {}

Neste algoritmo, o ESP32 disponibiliza o URL “/” para acessos somente via post (configurado por meio de HTTP_POST) e, também, disponibiliza o URL “/” para acessos somente via get (configurado por meio de HTTP_GET). Os dois URL’s não se sobrepõem, pois são disponibilizados com dois métodos HTTP diferentes. Ainda existe outra configuração possível, a HTTP_ANY: este método recebe solicitações do cliente tanto via GET quanto POST no mesmo callback.

Resultado:

Descrição do Resultado:

  • (Exemplo de Parâmetro POST): Ao acessar IP_DO_ESP32/ no navegador e clicar no botão Imprimir Parâmetros POST na Serial, é acionada a função imprimeNaSerial(), da página HTML, que envia um parâmetro POST ao web server do ESP32. No monitor Serial, é mostrado:

Hardware do Projeto

Para executar o projeto deste post, é utilizado somente o hardware das demonstrações anteriores:


Software do Projeto

Clique na imagem abaixo, faça o download do sketch e do diretório data para SPIFFS e, em seguida, faça o upload deles para a placa ESP32:

Para orientações de como fazer o upload do diretório data para o SPIFFS do ESP32, consulte o post SPIFFS: Armazenamento de Arquivos do ESP32, aqui mesmo, no blog Eletrogate.

SPIFFS: Armazenamento de Arquivos do ESP32

Funcionamento Teórico:

Ao ser acessada a página / do ESP32 no navegador web, ele o redireciona para a página /index.html. Na página /index.html, é mostrada a lista de redes WiFi juntamente com o nível RSSI do WiFi. Também é disponibilizado a opção mais dados para cada rede WiFi, em que é possível os dados de BSSID (endereço MAC do ponto de acesso), de Canal do WiFi e de Tipo de encriptação do WiFi. A cada 10 segundos, automaticamente é atualizada, assincronamente, a lista das redes disponíveis. Caso o usuário deseje, é possível atualizar a lista de redes WiFi manualmente.


Demonstração do Funcionamento

Veja, no vídeo abaixo, a demonstração de funcionamento do projeto:


Conclusão

Curtiu o post? Avalie e deixe um comentário! Siga-nos também no Instagram e nos marque quando fizer algum projeto nosso: @eletrogate. Até a próxima!

Tenha a Metodologia Eletrogate na sua Escola! Conheça nosso Programa de Robótica Educacional.


Sobre o Autor


Michel Galvão

Hobbysta em Sistemas Embarcados e IoT. Tem experiência em Automação Residencial e Agrícola.


Eletrogate

13 de setembro de 2022

A Eletrogate é uma loja virtual de componentes eletrônicos do Brasil e possui diversos produtos relacionados à Arduino, Automação, Robótica e Eletrônica em geral.

Tenha a Metodologia Eletrogate dentro da sua Escola! Conheça nosso Programa de Robótica nas Escolas!

Eletrogate Robô

Cadastre-se e fique por
dentro de novidades!