Módulos Wifi

Integrando ESP32 com inteligência artificial (Gemini)

Eletrogate 18 de dezembro de 2024

Introdução

Hoje, podemos afirmar que a inteligência artificial faz parte do nosso dia a dia, tanto em tarefas pessoais quanto profissionais, oferecendo suporte em atividades repetitivas ou na resolução de dúvidas teóricas. Neste artigo, vamos mostrar como integrar um projeto de eletrônica com inteligência artificial, demonstrando mais uma aplicação prática da IA em nosso ambiente profissional.

Para colocar esse conceito em prática, iremos desenvolver um projeto onde coletaremos dados reais do ambiente por meio de um sensor conectado ao ESP32. Esses sensores geram uma grande quantidade de dados por segundo, e se fôssemos analisar cada dado individualmente, o processo seria muito demorado. Para resolver isso, integraremos o sistema com uma inteligência artificial através da API do Gemini, que será responsável por analisar os dados e gerar um relatório com as principais informações que desejamos. Para visualizar esse relatório, será necessário acessar uma página web onde o retorno da API será exibido.

Para realizarmos este projeto, é importante entender alguns conceitos antes de partir para a prática. Portanto, recomendamos ter conhecimento prévio sobre o ESP32 e o conceito de API.


Introdução ao Gemini

No contexto atual, existem diversas formas de interagir com inteligência artificial. Por exemplo, várias empresas oferecem uma versão gratuita de chats que permitem a comunicação com IA, como o ChatGPT, Copilot, Gemini, entre outros. No entanto, esses chats são compostos por páginas web que facilitam o acesso. Contudo, para integrar IA em nossos projetos, esse tipo de acesso não é tão útil. Por isso, podemos utilizar as mesmas funções presentes nos chats sem a interface, através de APIs. Cada empresa possui seus próprios requisitos para utilizar suas APIs, mas iremos utilizar a da Google o Gemini, que possui requisitos simples e uma documentação bem estruturada.

Primeiramente, para acessar a API da Gemini e utilizá-la, é necessário seguir alguns passos simples.

Passo 1: Acesse a página oficial da Gemini e crie uma conta ou faça login no site.

Passo 2: Após realizar o login e acessar a página que permite a geração de uma chave de API, a qual será de grande importância ao longo do projeto, devemos acessar o seguinte link

Passo 3: Agora que estamos na página, devemos gerar uma chave de API a partir do menu lateral esquerdo, na opção “Get API Key”. Após selecionar essa opção, será aberta, no espaço ao lado, uma breve explicação sobre a chave de API e, logo em seguida, um botão “Criar chave API”, onde devemos clicar para gerar a chave.

Passo 4: Após clicar no botão, para gerar a chave de API, o processo é simples. Devemos selecionar um projeto na Google Cloud. Por padrão, existe o projeto ‘Generative Language Client’, que podemos utilizar. Dessa forma, selecionamos o projeto existente e, após isso, clicamos no botão ‘Criar uma chave de API em um projeto atual.

Passo 5: Assim que os passos anteriores forem finalizados, podemos ver em uma área os dados gerados, como a chave de API e, também, algo muito importante: o plano que está incluso na utilização da chave que acabou de ser criada. Portanto, para a utilização em nosso projeto, deve ser aparecer a opção sem custo financeiro.

Passo 6: Para finalizar, após conferir as informações sobre sua chave de API, devemos copiá-la e armazená-la em um local seguro de sua escolha, visto que a utilizaremos em nosso projeto para realizar a integração do ESP32 com a inteligência artificial. IMPORTANTE: salve essa chave em um local seguro, pois é um dado sensível e será de grande importância no desenvolvimento do projeto.


Entendendo a API do Gemini

Como a maioria das APIs públicas possui documentação e padrões de utilização, o Gemini não é diferente. Portanto, apresentaremos algumas características fundamentais da documentação da API para que possamos utilizá-la corretamente em nosso projeto. Para isso, será preciso seguir alguns passos simples, nos quais iremos acessar a documentação e visualizar duas informações muito importantes.

Primeiro: Para acessar a documentação, é bem simples. Basta acessar este link, onde temos a documentação oficial.

Segundo: Após isso, devemos analisar o menu lateral esquerdo, onde estão os principais tópicos presentes na documentação. Para o momento, abordaremos o tópico API Keys, onde são tratados os assuntos de authentication e making-requests para a construção do nosso projeto.

Authentication: O conteúdo deste tópico explica como deve ser realizada a autenticação ao utilizar a API. Portanto, não podemos apenas utilizá-la diretamente; devemos entender que, para obter os resultados esperados da API, é necessário seguir o padrão informado na documentação. Para isso, é preciso gerar a chave de API e, ao fazer a requisição, passar no cabeçalho da solicitação o campo x-goog-api-key: ${API_KEY} com a chave gerada.

Making-requests: Para trabalhar com a API, será necessário entender o que eles esperam receber e o que devemos esperar como respostas. Este tópico introduz como devemos enviar nossas solicitações, o que queremos e como receber e tratar as informações entregues pela API.


Mão na Massa

Agora que entendemos e configuramos a API da gemini, podemos realizar um teste simples para compreender melhor sua utilização e as possibilidades de integração com projetos IoT. Para a execução do nosso projeto, e até mesmo do teste, será necessário instalar uma IDE onde iremos escrever o código, como o Arduino IDE, o VSCode com PlatformIO, ou outra de sua preferência.

IMPORTANTE: Para realizar qualquer requisição à API da gemini, devemos utilizar a chave de API que foi gerada anteriormente, portanto, é necessário deixá-la pronta para ser utilizada.

Exemplo base

Para o exemplo básico de integração da API da gemini com o ESP32, iremos criar uma página web simples para facilitar a visualização e interação. Ela servirá apenas para enviar perguntas e exibir as respostas. O processo pode ser dividido em duas partes:

  1. ESP32 como servidor web: O ESP32 hospeda uma página web com um campo de entrada e um botão para enviar a pergunta. (A url vai ser o ip do esp32 e barra “ip/”)
  2. Interação com a API da gemini: Quando o botão for clicado, o ESP32 enviará a pergunta para a API da geminie exibirá a resposta na página.

Detalhamento do código:

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

Primeiramente, devemos importar as bibliotecas que iremos utilizar. Nesse caso, utilizaremos apenas as bibliotecas básicas do ESP32 e a biblioteca ArduinoJson para tratar o retorno da API. Portanto, a biblioteca ArduinoJson deve ser instalada.

const char* ssid = "SEU_SSID";
const char* password = "SUA_SENHA";

Em seguida, criamos duas variáveis onde iremos armazenar as informações sobre o Wi-Fi ao qual o ESP32 será conectado. É muito importante colocar corretamente o nome (SSID) e a senha da rede Wi-Fi escolhida, visto que sem essas informações a conexão não funcionará.

const String apiKey = "SUA_CHAVE_API_GEMINI";
const String endpoint = "https://generativelanguage.googleapis.com/v1/models/gemini-pro:generateContent";

Assim como criamos duas variáveis para armazenar os dados do Wi-Fi, também iremos criar mais duas variáveis onde colocaremos a chave de API que geramos anteriormente neste artigo, além da URL base das requisições para a API da gemini.

WebServer server(80);

Definimos a porta na qual o servidor web será iniciado.

String createPage(String response = "") {
  String html = "<!DOCTYPE html><html lang='pt'><head><meta charset='UTF-8'><meta name='viewport' content='width=device-width, initial-scale=1.0'>";
  html += "<title>Pergunte à OpenAI</title></head><body>";
  html += "<h1>Pergunte à OpenAI</h1>";
  html += "<form action='/ask' method='POST'><label for='question'>Digite sua pergunta:</label>";
  html += "<input type='text' id='question' name='question'><br><br>";
  html += "<input type='submit' value='Enviar'></form>";
  
  if (response != "") {
    html += "<h2>Resposta:</h2><p>" + response + "</p>";
  }

  html += "</body></html>";
  return html;
}

Criamos a função responsável por retornar uma string que contém nosso código HTML, o qual construirá nossa interface quando for chamado.

String askGemini(String question)
{
  WiFiClientSecure client;
  HTTPClient http;
  client.setInsecure(); // Desabilita verificação de certificado SSL para testes

  http.begin(client, endpoint);
  http.addHeader("Content-Type", "application/json");
  http.addHeader("x-goog-api-key", apiKey); // Cabeçalho de autenticação correto para a API Gemini

  // Corpo da requisição JSON para a API Gemini
  String jsonRequest = "{\"contents\": [{\"role\": \"user\", \"parts\": [{\"text\": \"" + question + "\"}]}]}";

  Serial.println("Enviando requisição para a API Gemini...");

  int httpResponseCode = http.POST(jsonRequest);
  String payload = http.getString();

  Serial.print("Código de resposta HTTP: ");
  Serial.println(httpResponseCode);

  if (httpResponseCode > 0)
  {
    Serial.print("Resposta da API: ");
    Serial.println(payload);
  }
  else
  {
    Serial.print("Erro na requisição: ");
    Serial.println(http.errorToString(httpResponseCode).c_str());
  }

  http.end();

  if (httpResponseCode == 200) {
    // Usando ArduinoJson para extrair a resposta
    StaticJsonDocument<200> doc;
    DeserializationError error = deserializeJson(doc, payload);
    if (!error) {
      // Acessa o valor de "text"
      const char* response = doc["candidates"][0]["content"]["parts"][0]["text"];
      return response; // Retorna o valor
    } else {
      return "Erro ao analisar JSON.";
    }
  } else {
    return "Erro ao se comunicar com a API Gemini";
  }
}

No trecho acima, temos a função responsável por realizar a requisição à API. Em outras palavras, essa função recebe uma string como parâmetro e a utiliza no corpo da solicitação para a API da gemini.

void handleRoot() {
  server.send(200, "text/html", createPage());
}

O objetivo dessa função é apenas carregar o HTML ao acessar a URL com o tipo de conteúdo text/html.

void handleAsk()
{
  if (server.method() == HTTP_POST)
  {
    if (server.hasArg("question"))
    {
      String question = server.arg("question");
      String response = askGemini(question);
      server.send(200, "text/html", createPage(response));
    }
    else
    {
      server.send(400, "text/plain", "Pergunta não fornecida");
    }
  }
  else
  {
    server.send(405, "text/plain", "Método não permitido. Utilize POST.");
  }
}

A função handleAsk realiza uma validação para verificar se existe alguma pergunta. Caso exista, a função askGemini(question) será chamada com a pergunta como parâmetro, enviando a requisição. Caso não exista, uma mensagem será exibida na interface informando que nenhuma pergunta foi fornecida.

void setup()
{
  Serial.begin(9600);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(1000);
    Serial.println("Conectando ao WiFi...");
  }
  Serial.println("Conectado!");

  // Exibe o IP do ESP32 após a conexão com o Wi-Fi
  Serial.print("Endereço IP do ESP32: ");
  Serial.println(WiFi.localIP());

  server.on("/", handleRoot);
  server.on("/ask", HTTP_ANY, handleAsk);

  server.begin();
  Serial.println("Servidor iniciado");
}

A função void setup realiza a inicialização do script, onde configuramos a porta serial para comunicação com a velocidade de 115200 baud. Em seguida, o ESP32 tenta se conectar ao Wi-Fi usando as credenciais fornecidas nas variáveis ssid e password. O código entra em um laço while que verifica o status da conexão, imprimindo a mensagem “Conectando ao WiFi…” a cada segundo até que a conexão seja estabelecida. Uma vez conectado, a mensagem “Conectado!” é exibida no monitor serial.

Depois disso, a configuração do servidor web é realizada. A rota padrão " / " é associada à função handleRoot, que será chamada sempre que alguém acessar a página inicial. Esta função normalmente contém o HTML da interface que será exibida no navegador.

Além disso, a rota "/ask" é configurada para responder a requisições HTTP POST, utilizando a função handleAsk. Esta função trata o envio de perguntas à API do gemini ou qualquer outra lógica associada.

Finalmente, o servidor web é iniciado com o comando server.begin(), e a mensagem “Servidor iniciado” é exibida no monitor serial, indicando que o servidor está pronto para aceitar conexões.

void loop() {
  server.handleClient();
}

A função void loop é o coração do código que executa repetidamente enquanto o ESP32 estiver ligado. No caso deste exemplo, ela contém uma única linha de código: server.handleClient();. Esta função monitora continuamente o servidor web em busca de novas requisições de clientes.

Sempre que um cliente (como um navegador ou dispositivo externo) faz uma requisição HTTP ao ESP32, o método handleClient() processa essa requisição. Ele verifica as rotas definidas previamente no setup (como " / " e "/ask") e executa as funções associadas a essas rotas, como handleRoot e handleAsk, conforme a URL e o método HTTP da requisição (GET, POST, etc.).

Esse processo é executado repetidamente no loop, garantindo que o servidor esteja sempre pronto para lidar com requisições de clientes a qualquer momento, sem a necessidade de parar ou reiniciar o ESP32.

Exemplo Completo

#include <WiFi.h>
#include <WebServer.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h> // Inclua a biblioteca ArduinoJson

// Configurações WiFi
const char* ssid = "SEU_SSID"; 
const char* password = "SUA_SENHA";

// Configuração da API do Gemini
const String apiKey = "chave da API Gemini"; // Substitua pela chave da API Gemini
const String endpoint = "https://generativelanguage.googleapis.com/v1/models/gemini-pro:generateContent"; // Endpoint para a API Gemini

// Servidor Web
WebServer server(80);

// Função para criar a página HTML
String createPage(String response = "")
{
  String html = "<!DOCTYPE html><html lang='pt'><head><meta charset='UTF-8'><meta name='viewport' content='width=device-width, initial-scale=1.0'>";
  html += "<title>Pergunte à Gemini</title></head><body>";
  html += "<h1>Pergunte à Gemini</h1>";
  html += "<form action='/ask' method='POST'><label for='question'>Digite sua pergunta:</label>";
  html += "<input type='text' id='question' name='question'><br><br>";
  html += "<input type='submit' value='Enviar'></form>";

  if (response != "")
  {
    html += "<h2>Resposta:</h2><p>" + response + "</p>";
  }

  html += "</body></html>";
  return html;
}

// Função para enviar a pergunta para a API Gemini
String askGemini(String question)
{
  WiFiClientSecure client;
  HTTPClient http;
  client.setInsecure(); // Desabilita verificação de certificado SSL para testes

  http.begin(client, endpoint);
  http.addHeader("Content-Type", "application/json");
  http.addHeader("x-goog-api-key", apiKey); // Cabeçalho de autenticação correto para a API Gemini

  // Corpo da requisição JSON para a API Gemini
  String jsonRequest = "{\"contents\": [{\"role\": \"user\", \"parts\": [{\"text\": \"" + question + "\"}]}]}";

  Serial.println("Enviando requisição para a API Gemini...");

  int httpResponseCode = http.POST(jsonRequest);
  String payload = http.getString();

  Serial.print("Código de resposta HTTP: ");
  Serial.println(httpResponseCode);

  if (httpResponseCode > 0)
  {
    Serial.print("Resposta da API: ");
    Serial.println(payload);
  }
  else
  {
    Serial.print("Erro na requisição: ");
    Serial.println(http.errorToString(httpResponseCode).c_str());
  }

  http.end();

  if (httpResponseCode == 200) {
    // Usando ArduinoJson para extrair a resposta
    StaticJsonDocument<200> doc;
    DeserializationError error = deserializeJson(doc, payload);
    if (!error) {
      // Acessa o valor de "text"
      const char* response = doc["candidates"][0]["content"]["parts"][0]["text"];
      return response; // Retorna o valor
    } else {
      return "Erro ao analisar JSON.";
    }
  } else {
    return "Erro ao se comunicar com a API Gemini";
  }
}

// Rota para a página inicial
void handleRoot()
{
  server.send(200, "text/html", createPage());
}

// Rota para processar a pergunta
void handleAsk()
{
  if (server.method() == HTTP_POST)
  {
    if (server.hasArg("question"))
    {
      String question = server.arg("question");
      String response = askGemini(question);
      server.send(200, "text/html", createPage(response));
    }
    else
    {
      server.send(400, "text/plain", "Pergunta não fornecida");
    }
  }
  else
  {
    server.send(405, "text/plain", "Método não permitido. Utilize POST.");
  }
}

void setup()
{
  Serial.begin(9600);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(1000);
    Serial.println("Conectando ao WiFi...");
  }
  Serial.println("Conectado!");

  // Exibe o IP do ESP32 após a conexão com o Wi-Fi
  Serial.print("Endereço IP do ESP32: ");
  Serial.println(WiFi.localIP());

  server.on("/", handleRoot);
  server.on("/ask", HTTP_ANY, handleAsk);

  server.begin();
  Serial.println("Servidor iniciado");
}

void loop()
{
  server.handleClient();
}

 


Script da Integração com sistema de monitoramento

Agora que vimos e entendemos a utilização básica da API do Gemini, vamos aplicá-la em um projeto com um uso mais prático, onde, através de sensores, podemos coletar medições reais do ambiente. Com esses dados, teremos informações suficientes para solicitar à IA que gere uma análise e nos retorne um relatório com pontos relevantes.

Para a construção deste projeto, vamos desenvolver um script responsável por coletar os dados, realizar a conexão com a internet, gerar a página web onde mostraremos o relatório gerado pela IA, e, por fim, a parte onde enviaremos os dados juntamente com a informação que queremos para a IA. Para isso, utilizaremos as seguintes bibliotecas:

Bibliotecas Utilizadas

  • WiFi.h
  • WebServer.h
  • HTTPClient.h
  • DHT.h
  • WiFiClientSecure
  • ArduinoJson

Detalhamento do código

Antes de analisarmos e lermos os comentários do código por completo, vamos examinar a função principal que realiza a requisição à API do Gemini.

String enviarDadosParaGemini() {
  WiFiClientSecure client;           // Cria um cliente seguro para HTTPS
  HTTPClient http;                   // Cria uma instância HTTP para a requisição
  client.setTimeout(120000);         // Define o timeout para 2 minutos
  client.setInsecure();              // Desabilita a verificação SSL 

  http.begin(client, endpoint);      // Inicia a requisição HTTP com o endpoint da API
  http.addHeader("Content-Type", "application/json"); // Define o cabeçalho do tipo de conteúdo
  http.addHeader("x-goog-api-key", apiKey);           // Define o cabeçalho da chave da API
  
  // Cria o corpo da requisição com os dados coletados
  String jsonRequest = "{\"contents\": [{\"role\": \"user\", \"parts\": [{\"text\": \"Baseado nos seguintes dados de temperatura e umidade do ambiente: [";
  for (auto& data : dadosColetados) { // Adiciona cada par de dados de temperatura e umidade ao JSON
    jsonRequest += "{umidade:" + data.umidade + ",temperatura:" + data.temperatura + "},";
  }
  jsonRequest.remove(jsonRequest.length() - 1);       // Remove a última vírgula do JSON
  jsonRequest +="],Me retorne uma análise do ambiente com as seguintes informações no formato de texto corrido: Alerta de extremidade de temperatura (muito alta ou muito baixa), alerta de extremidade de umidade (muito alta ou muito baixa), umidade média (calculada de acordo com os dados fornecidos), temperatura média (calculada de acordo com os dados fornecidos), recomendação de temperatura e umidade mais adequadas, e dicas para um ambiente agradável.\"}]}]}"; // Finaliza o JSON
  Serial.println(jsonRequest);       // Exibe o corpo da requisição no Serial
  int httpResponseCode = http.POST(jsonRequest); // Envia a requisição POST
  String payload = http.getString(); // Armazena a resposta da API
  Serial.println(payload);           // Exibe a resposta no Serial
  http.end();                        // Encerra a requisição HTTP

  if (httpResponseCode == 200) {     // Se a resposta for sucesso (código 200)
    dadosColetados.clear();          // Limpa os dados coletados após o envio

    // Usando ArduinoJson para extrair a resposta
    StaticJsonDocument<200> doc;     // Cria um documento JSON estático
    DeserializationError error = deserializeJson(doc, payload); // Deserializa a resposta JSON
    if (!error) {                    // Se não houver erro na deserialização
      const char* response = doc["candidates"][0]["content"]["parts"][0]["text"]; // Extrai o texto de resposta
      return response;               // Retorna o texto da resposta
    } else {
      return "Erro ao analisar JSON."; // Retorna erro de análise JSON
    }
  } else {
    return "Erro ao se comunicar com a API Gemini"; // Retorna erro de comunicação
  }
}

No código acima, primeiramente, realizamos a inicialização de duas variáveis responsáveis por permitir a requisição à API: WiFiClientSecure, para gerenciar conexões HTTPS, e HTTPClient, para enviar as requisições HTTP. Em seguida, definimos um tempo de espera (timeout) de 2 minutos, já que o tempo de resposta da API pode variar bastante dependendo da qualidade da conexão com a internet. Também desabilitamos a verificação SSL com client.setInsecure(), o que pode ser útil em testes, mas não é recomendado para ambientes de produção por motivos de segurança.

Depois, construímos a requisição à API. Primeiro, definimos os cabeçalhos necessários, como o tipo de conteúdo e a chave da API. A seguir, criamos o conteúdo da requisição (corpo), que é armazenado na variável jsonRequest. Nesse ponto, adicionamos a mensagem inicial, e, em seguida, utilizamos um loop que itera sobre os dados coletados pelo sensor (temperatura e umidade), incorporando-os à mensagem. Após o loop, finalizamos o JSON adicionando um texto que especifica à IA o que queremos como resposta e o formato desejado.

Por fim, realizamos a verificação do retorno da API. Se o código de resposta for 200 (que significa “OK”), utilizamos a biblioteca ArduinoJson para analisar a resposta e extrair apenas o campo do JSON que desejamos. Caso haja um erro na deserialização do JSON, retornamos uma mensagem de erro. Se o código de resposta for diferente de 200, retornamos também uma mensagem indicando erro na comunicação com a API.

O restante do código já é familiar: conectamos ao Wi-Fi, coletamos os dados usando a biblioteca do sensor DHT11, e, por último, iniciamos o servidor web com uma página HTML que exibe as informações (A url para acessar é o IP do Esp32/relatório).

Código Completo:

#include <WiFi.h>                    // Inclui a biblioteca para manipulação de conexões WiFi
#include <WebServer.h>               // Inclui a biblioteca para criar um servidor web
#include <HTTPClient.h>              // Inclui a biblioteca para realizar requisições HTTP
#include <WiFiClientSecure.h>         // Inclui a biblioteca para usar HTTPS (SSL/TLS)
#include <DHT.h>                     // Inclui a biblioteca para o sensor de temperatura e umidade DHT11
#include <ArduinoJson.h>             // Inclui a biblioteca para manipulação de dados JSON

// Definições de pinos
#define DHTPIN 4                     // Define o pino 4 como o pino de dados do sensor DHT11
#define DHTTYPE DHT11                // Define o tipo do sensor como DHT11
#define LED_SUCCESS 2                // Define o pino 2 para o LED de sucesso
#define LED_ERROR 15                 // Define o pino 15 para o LED de erro

// Configurações WiFi
const char *ssid = "Nome Da Rede";      // Define o SSID da rede WiFi
const char *password = "Senha da Rede"; // Define a senha da rede WiFi

// Configuração da API do Gemini
const String apiKey = "AIzaSy...";   // Chave de API do Gemini (abreviada por questões de segurança)
const String endpoint = "https://generativelanguage.googleapis.com/v1/models/gemini-pro:generateContent"; // URL da API

// Instâncias do sensor e servidor web
DHT dht(DHTPIN, DHTTYPE);            // Cria uma instância do sensor DHT11
WebServer server(80);                // Cria uma instância do servidor web rodando na porta 80

// Estrutura para armazenar os dados coletados do sensor
struct SensorData {
  String umidade;                    // Armazena a umidade coletada
  String temperatura;                // Armazena a temperatura coletada
};

std::vector<SensorData> dadosColetados; // Vetor para armazenar vários dados coletados

// Função para formatar a string para HTML
String formatarStringParaHTML(String texto) {
  // Substituir asteriscos duplos (**) por tags <strong> para negrito
  texto.replace("**", "<strong>");

  // Alternar entre abertura e fechamento da tag <strong>
  bool abrirTag = true;
  while (texto.indexOf("<strong>") != -1) {
    if (abrirTag) {
      texto.replace("<strong>", "<strong>");
    } else {
      texto.replace("<strong>", "</strong>");
    }
    abrirTag = !abrirTag;
  }

  // Substituir '\n' por '<br>' para quebras de linha
  texto.replace("\n", "<br>");

  return texto;                      // Retorna a string formatada
}

// Função para coletar dados do sensor DHT11
void coletarDados() {
  float h = dht.readHumidity();      // Lê a umidade do sensor
  float t = dht.readTemperature();   // Lê a temperatura do sensor
  
  if (isnan(h) || isnan(t)) {        // Verifica se houve erro na leitura
    digitalWrite(LED_ERROR, HIGH);   // Acende o LED de erro
    Serial.println("Erro ao ler o sensor DHT11");
    return;                          // Sai da função se houver erro
  } else {
    digitalWrite(LED_SUCCESS, HIGH); // Acende o LED de sucesso
    SensorData data;                 // Cria uma nova estrutura de dados
    data.umidade = String(h, 2);     // Armazena a umidade com 2 casas decimais
    data.temperatura = String(t, 2); // Armazena a temperatura com 2 casas decimais
    dadosColetados.push_back(data);  // Adiciona os dados coletados ao vetor
  }
  digitalWrite(LED_ERROR, LOW);      // Apaga o LED de erro
}

// Função para enviar dados à API do Gemini
String enviarDadosParaGemini() {
  WiFiClientSecure client;           // Cria um cliente seguro para HTTPS
  HTTPClient http;                   // Cria uma instância HTTP para a requisição
  client.setTimeout(120000);         // Define o timeout para 2 minutos
  client.setInsecure();              // Desabilita a verificação SSL (para testes)

  http.begin(client, endpoint);      // Inicia a requisição HTTP com o endpoint da API
  http.addHeader("Content-Type", "application/json"); // Define o cabeçalho do tipo de conteúdo
  http.addHeader("x-goog-api-key", apiKey);           // Define o cabeçalho da chave da API
  
  // Cria o corpo da requisição com os dados coletados
  String jsonRequest = "{\"contents\": [{\"role\": \"user\", \"parts\": [{\"text\": \"Baseado nos seguintes dados de temperatura e umidade do ambiente: [";
  for (auto& data : dadosColetados) { // Adiciona cada par de dados de temperatura e umidade ao JSON
    jsonRequest += "{umidade:" + data.umidade + ",temperatura:" + data.temperatura + "},";
  }
  jsonRequest.remove(jsonRequest.length() - 1);       // Remove a última vírgula do JSON
  jsonRequest += "], Me retorne uma análise do ambiente com as seguintes informações no formato de texto corrido: Alerta de extremidade de temperatura (muito alta ou muito baixa), alerta de extremidade de umidade (muito alta ou muito baixa), umidade média (calculada de acordo com os dados fornecidos), temperatura média (calculada de acordo com os dados fornecidos), recomendação de temperatura e umidade mais adequadas, e dicas para um ambiente agradável.\"}]}]}"; // Finaliza o JSON
  Serial.println(jsonRequest);       // Exibe o corpo da requisição no Serial
  int httpResponseCode = http.POST(jsonRequest); // Envia a requisição POST
  String payload = http.getString(); // Armazena a resposta da API
  Serial.println(payload);           // Exibe a resposta no Serial
  http.end();                        // Encerra a requisição HTTP

  if (httpResponseCode == 200) {     // Se a resposta for sucesso (código 200)
    dadosColetados.clear();          // Limpa os dados coletados após o envio

    // Usando ArduinoJson para extrair a resposta
    StaticJsonDocument<200> doc;     // Cria um documento JSON estático
    DeserializationError error = deserializeJson(doc, payload); // Deserializa a resposta JSON
    if (!error) {                    // Se não houver erro na deserialização
      const char* response = doc["candidates"][0]["content"]["parts"][0]["text"]; // Extrai o texto de resposta
      return response;               // Retorna o texto da resposta
    } else {
      return "Erro ao analisar JSON."; // Retorna erro de análise JSON
      digitalWrite(LED_ERROR, HIGH);  // Acende o LED de erro
    }
  } else {
    return "Erro ao se comunicar com a API Gemini"; // Retorna erro de comunicação
    digitalWrite(LED_ERROR, HIGH); // Acende o LED de erro
  }
}

// Função para criar a página HTML com o relatório
String gerarRelatorioWeb(String response) {
  String html = "<!DOCTYPE html><html lang='pt'><head><meta charset='UTF-8'><meta name='viewport' content='width=device-width, initial-scale=1.0'>"; // Cabeçalho HTML
  html += "<title>Relatório do Ambiente</title></head><body>"; // Título da página
  html += "<h1>Relatório do Ambiente</h1>"; // Cabeçalho H1
  
  // Exibe os dados retornados pela API
  if (response != "") {              // Se houver resposta da API
    html += "<p>" + formatarStringParaHTML(response) + "</p>"; // Formata e exibe a resposta
  } else {
    html += "<p>Nenhum dado coletado até o momento.</p>"; // Caso não haja resposta
  }

  html += "</body></html>";          // Finaliza o corpo HTML
  return html;                       // Retorna o HTML gerado
}

// Rota para exibir o relatório
void handleRelatorio() {
  String response = enviarDadosParaGemini(); // Envia os dados para a API e recebe a resposta
  server.send(200, "text/html", gerarRelatorioWeb(response)); // Envia a resposta HTML ao cliente
}

// Função para configuração do ESP32
void setup() {
  Serial.begin(115200);              // Inicia a comunicação serial a 115200 bps
  
  // Inicializa o sensor DHT11
  dht.begin();                       // Inicia o sensor DHT11

  // Configura pinos dos LEDs
  pinMode(LED_SUCCESS, OUTPUT);      // Define o pino do LED de sucesso como saída
  pinMode(LED_ERROR, OUTPUT);        // Define o pino do LED de erro como saída
  
  // Conecta ao WiFi
  WiFi.begin(ssid, password);        // Inicia a conexão WiFi
  while (WiFi.status() != WL_CONNECTED) { // Aguarda até conectar ao WiFi
    delay(1000);                     // Espera 1 segundo
    Serial.println("Conectando ao WiFi...");
  }
  Serial.println("Conectado ao WiFi!"); // Exibe mensagem de sucesso na conexão

  // Exibe o IP do ESP32 após a conexão com o Wi-Fi
  Serial.print("Endereço IP do ESP32: ");
  Serial.println(WiFi.localIP());    // Mostra o endereço IP do ESP32

  // Inicializa servidor web
  server.on("/relatorio", handleRelatorio); // Define a rota para acessar o relatório
  server.begin();                   // Inicia o servidor web
  Serial.println("Servidor web iniciado");

  // Coleta inicial de dados
  coletarDados();                    // Coleta dados logo após a inicialização
}

// Função para rodar continuamente
void loop() {
   server.handleClient();            // Mantém o servidor web ativo
  
  static unsigned long ultimoEnvio = 0;     // Variável para controlar o tempo de envio
  static unsigned long ultimaColeta = 0;    // Variável para controlar o tempo de coleta

  // Coletar dados a cada 30 segundos
  if (millis() - ultimaColeta > 30000) { // Se 30 segundos se passaram desde a última coleta
    coletarDados();                      // Coleta dados do sensor
    ultimaColeta = millis();             // Atualiza o tempo da última coleta
  }

  // Enviar dados a cada 2 minutos
  if (millis() - ultimoEnvio > 120000) { // Se 2 minutos se passaram desde o último envio
    enviarDadosParaGemini();             // Envia os dados para a API
    ultimoEnvio = millis();              // Atualiza o tempo do último envio
  }
}

 


Montagem do circuito

Para finalizar nosso projeto, iremos construir a parte física. Para isso, devemos seguir as instruções abaixo.

Lista de componentes

  • ESP32
  • Protobord
  • Sensor DHT11
  • 2 LED
  • 2 Resistor 220ohm
  • Jumper

Diagrama do circuito

Montagem do circuito do ESP32 com o sensor de temperatura (conexões)

  • VCC do DHT113.3V do ESP32
  • GND do DHT11GND do ESP32
  • DATA do DHT11Pino D4 do ESP32
  • LED de SucessoPino D2 do ESP32
  • LED de ErroPino D15 do ESP32

 

Vídeo Demonstrativo


Considerações Finais

Mais uma vez, finalizamos um projeto que oferece uma vasta gama de possibilidades para incrementos. Assim, quero desafiar vocês a implementar mais funcionalidades no projeto e até mesmo criar novas ideias utilizando inteligência artificial.

Contudo, devemos sempre lembrar que, com a evolução da tecnologia, é fundamental que também busquemos nos aprimorar constantemente, incorporando essas inovações em nosso contexto.


Sobre o Autor


João Vitor
@v_ribeiro_v

Acredito ser uma pessoa muito curiosa, sempre tentando encontrar soluções e perspectivas diferentes para os problemas cotidianos. Portanto, busco meios de me aprimorar. Atualmente, cursando Engenharia da Computação e atuando profissionalmente como desenvolvedor fullstack, estou sempre em busca de novos desafios e oportunidades de aprendizado, prezando pela maestria na minha vocação e pela inovação.


Eletrogate

18 de dezembro de 2024

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.

Conheça a Metodologia Eletrogate e Lecione um Curso de Robótica nas Escolas da sua Região!

Eletrogate Robô

Cadastre-se e fique por
dentro de novidades!