Já pensou em montar uma estação meteorológica com tudo o que você tem direito e ainda poder conectar ela internet?

Pois bem, neste tutorial você aprenderá como criar sua própria estação meteorológica utilizando um ESP32, ideal para quem deseja monitorar as condições climáticas em tempo real, seja para projetos de automação residencial, agricultura inteligente ou estudos ambientais. Também entenderá como capturar dados de temperatura, umidade, pressão, entre outros, e publicá-los na nuvem para análise remota.

Construir uma estação meteorológica conectada a nuvem nos permite criar diversos tipos de dispositivos como, por exemplo, uma estufa, um viveiro ou até mesmo um ecossistema inteligente. Não somente projetos relacionados as plantas, também conseguimos medir a qualidade do ar no nosso ambiente, muito útil em ambientes de trabalho confinados como em um chão de fábrica.

Com o envio dos dados para a nuvem, sua estação meteorológica torna-se um projeto de Internet das Coisas. Isso permite muito mais interatividade e autonomia, pois além de mostrar os dados dos sensores através do display, ela ainda permite que você consiga armazenar os dados captados por todos os sensores que serão utilizados no projeto, abrindo possibilidades de automação e controle remoto do projeto.

Você está pronto para desenvolver sua própria estação meteorológica? Vamos lá!

 

 

Primeiro de tudo, você pode navegar por todo nosso tutorial a partir de seus tópicos, que serão divididos abaixo. Caso deseje ir para algum deles, basta clicar no seu respectivo nome:

Lista de Materiais

Como escolher os componentes necessários para a construção de uma estação meteorológica com ESP32?

Qual Nuvem utilizar para o ESP32?

Diagrama elétrico do projeto

Montagem do Circuito

Como programar Estação Meteorológica

Funcionamento do projeto

Conclusão

 

Lista de materiais

Buscando componentes confiáveis, a lista de materiais utilizada na construção da estação meteorológica interligada à nuvem, é a seguinte:

01 - Placa DOIT ESP32 - ESP32-WROOM-32D - WiFi / Bluetooth

01 - Display OLED 128x64 Px - 1.3" - 4 Pin - Branco

01 - Sensor de Temperatura e Umidade - AHT21

01 - Sensor de Qualidade do Ar - TVOC - CCS811

01 - Sensor de Pressão e Temperatura BMP180

01 - Sensor de Luminosidade TSL2561

01 - Sensor de Luz Ultravioleta UV GUVA-S12S

01 - Sensor de Chuva

02 - Protoboard 400 Pontos

01 - Resistor 1K - 1/4W - 5%

01 - Chave Táctil 12x12x7 mm

01 - Kit Jumpers Rígidos Formato de U - 140 pçs

 

Como escolher os componentes necessários para a construção de uma estação meteorológica com ESP32?

A construção de uma estação meteorológica eficiente envolve lidarmos com alguns desafios que devem ser superados para o andamento tranquilo do projeto. O primeiro passo para iniciarmos o desenvolvimento é entender os desafios e definir os componentes necessários para atender às necessidades da estação. Essa necessidade surge devido à, diversos fatores como: A coleta de dados precisa, confiabilidade nos sensores e a transmissão dos dados coletados em tempo real. 

Você pode pensar em alguns componentes eletrônicos, como o Arduino Uno e o display LCD 16x2, que são componentes muito versáteis e bastante utilizados nos mais diversos projetos de eletrônica e IoT. Então, porque não utilizaremos o Arduino neste projeto? 

Embora o Arduino Uno e o display LCD 16x2 sejam opções extremamente versáteis, eles apresentam limitações importantes para esse tipo de projeto, especialmente na integração com a Internet das coisas.

Já o ESP32 é a escolha perfeita para uma estação meteorológica, porque oferece as mesmas funcionalidades presentes em um Arduino, mas com a vantagem adicional de uma conexão de WiFi nativa, permitindo assim conectar diretamente o projeto a nuvem. Além disso, o ESP32 também possui maior capacidade de memória que um Arduino convencional, ele conta com 4MB de memória Flash para gravação do código, o que o torna ainda mais eficiente para projetos que exigem integração com a Internet das Coisas (IoT). 

Portanto, a Placa DOIT ESP32 - ESP32-WROOM-32D - WiFi / Bluetooth é a escolha perfeita para leitura dos sensores e integração com a nuvem.

 

Doit ESP32

 

Já o Display LCD é uma ótima opção, mas não será utilizado devido ao seu tamanho. Por isso, a escolha do display para o projeto foi o Display OLED 128x64 - 1.3" - 4 pin Branco que permite a escrita e desenhos de uma maneira totalmente interativa com a estação, além de também ter um ótimo tamanho, compatível com toda a estação meteorológica.

 

 

Um dos principais fatores é garantirmos a precisão dos dados coletados. Para isso, precisamos de sensores de alta qualidade e confiabilidade. Sensores de baixa qualidade podem comprometer a confiabilidade dos dados, gerando informações imprecisas, prejudicando o funcionamento da estação meteorológica. Portanto, a escolha de bons sensores que conseguem oferecer uma ótima faixa de calibração e medição adequada do ambiente onde a estação está é indispensável.

Por conta disso, utilizemos os seguintes sensores para conseguirmos captar as informações e transformá-las em dados do ambiente.

O primeiro deles, o AHT21, é um sensor responsável por capturar as informações referentes a temperatura do ambiente. Ele consegue realizar medições de - 40 até +80°C e Umidade de 0 a 100% utilizando apenas 2 pinos de I2C para comunicação.

 

 

Na sequência, optamos por utilizar um Sensor de Qualidade do Ar - TVOC - CCS811 que é um sensor digital de gás em miniatura de baixa potência para monitoramento da qualidade do ar. Ele também tem um sensor de óxido de metal (MOX) integrado para medir o total de compostos orgânicos voláteis equivalentes de TVOCs, com isso, é possível medir a qualidade do ar no ambiente. Além disso, ele também se comunica através do canal de I2C, permitindo a utilização de apenas dois fios para comunicação. 

 

 

Após isso, utilizaremos o sensor de Pressão e Temperatura BMP180 que é um sensor barométrico utilizado nos mais diversos projetos onde é necessário medir a pressão atmosférica. Sua comunicação também é realizada através do canal I2C, o que facilitará a comunicação com o microcontrolador e montagem do circuito. 

 

 

Outro sensor escolhido para a estação meteorológica foi o sensor de luminosidade TSL2561 que é um sensor capaz de detectar a luminosidade presente em um ambiente. Ele consegue transformar a intensidade da luminosidade em um sinal elétrico digital, enviando ao microcontrolador esse sinal através do canal de I2C. 

 

 

Um dos últimos sensores do nosso projeto, mas não menos importante, é o Sensor de Luz Ultravioleta UV GUVA-S12S. Esse sensor consegue detectar a intensidade da radiação ultravioleta (UV) incidente - radiação localizada na faixa ultravioleta, com comprimentos de onda menores que a luz, mas maiores que os Raios-X. Esse sensor é utilizado em muitas aplicações diferentes, incluindo, entre outros, automóveis, produtos farmacêuticos e robótica.  

 

 

Outro fator importante na construção de uma estação meteorológica é a resistência dos componentes às condições ambientais. Podendo ser instalada em ambientes externos, exposta à chuva, vento, poeira e variações de temperatura, exige que todos os dispositivos sejam adequadamente protegidos. Ou seja, além de necessitarmos componentes confiáveis, ainda é necessário proteger o circuito adequadamente. Para solucionar esse problema, o sensor que ficará exposto às condições climáticas tem que suportar todas as adversidades do ambiente.

Por esse motivo, escolhemos o Sensor de Chuva, que nos permite monitorar as variações das condições climáticas, alterando seu nível lógico para alto quando o clima está seco e para baixo quando há presença de umidade, podendo ser água ou neve. O limite de sua detecção pode ser regulado através do seu potenciômetro. Ele ainda conta com um comparador LM393, além de ter duas saídas de tensão, uma analógica e uma digital, podendo ser utilizado nos mais diversos microcontroladores. Sua estrutura física nos permite capturar as condições climáticas a uma certa distância, o que o torna a escolha ideal para se ter no ambiente. 

 

 

Finalizando a escolha de materiais, escolhemos ainda uma chave táctil de 12x12x7, que nos permite realizar a troca de telas através do pressionamento do botão. Um resistor de 1k também foi utilizado o botão. 

Por fim, para conseguirmos acoplar todos os sensores no mesmo circuito, utilizamos duas protoboards de 400 pontos conectadas entre si fisicamente. 

 

 

E um kit de Jumper Rigído em formato de U para interligarmos todos os componentes nas protoboards. 

 

Com esses componentes nós partiremos para o tipo de comunicação que iremos utilizar para nos conecta a Nuvem.

 

Qual Nuvem utilizar para o ESP32 ?

Após definirmos os materiais que serão utilizados, a transmissão de dados para a nuvem também representa um desafio no processo de criação da estação meteorológica. Ele deve conseguir enviar as informações coletadas de maneira eficiente e em tempo real, o que pode exigir uma boa conexão de rede e até mesmo soluções de armazenamento dos dados em nuvem, para garantirmos que os dados fiquem acessíveis remotamente. 

Pensando nisso, sabemos que existem diversos tipos de nuvem disponíveis gratuitamente. decidimos utilizar o protocolo de comunicação do MQTT.

 O MQTT (Message Queuing Telemetry Transport) é um protocolo de comunicação utilizado para realizar a troca de mensagens entre dispositivos, especialmente em aplicações de Internet da Coisa. Ele é projetado para ser leve, eficiente e ideal para ambientes de rede com baixa largura de banda, alta latência ou com dispositivos com recursos limitados.

Por conta de suas caracterítiscas, utilizar o MQTT nessa estação será tranquilo e de fácil utilização, afinal, somente dados dos sensores serão enviados para a nuvem. 

Se você ainda não está familiarizado com o assunto, nós recomendamos a nossa série de tutoriais sobre o protocolo de comunicação do MQTT. Nessa série você será capaz de entender desde o princípio, o que é a comunicação MQTT, como publicar, como se inscrever, como gerar certificados de segurança e muito mais!

Para isso você pode acessar o nosso post introdutório ao MQTT: Introdução ao MQTT, e iniciar os seus estudos sobre o protocolo MQTT. Lá você aprenderá tudo o que é necessário para criarmos um servidor baseado no MQTT. Dessa forma, conseguimos construir uma Nuvem utilizando o MQTT para conseguirmos receber o que o ESP32 estiver lendo.

 

 

Com todos os componentes definidos, podemos partir para a criação do diagrama elétrico. Nessa etapa, você vai aprender a conectar todos os componentes no mesmo circuito, tornando a estação meteorológica um unico sistema embarcado.

 

Diagrama elétrico do projeto

Nessa etapa de desenvolvimento, construiremos o desenho de dois circuitos que serão voltados para a mesma finalidade: construirmos um diagrama de ligação dos componentes. 

Para isso utilizaremos o software Fritzing. Nele, conseguimos ter uma boa base de sensores, microcontroladores, atuadores e muito mais, onde podemos construir o nosso circuito de maneira eficiente, partindo assim para a construção e montagem do circuito físico.

Abaixo você verá dois diagramas elétricos. O primeiro deles, foi desenvolvido para demonstrar a ligação entre os componentes de maneira mais simples. Para facilitar a montagem e testes. Já o segundo diagrama elétrico foi desenvolvido para proporcionar uma construção mais limpa e portátil de todo o projeto. O pinout doos sensores e display conectados ao ESP32 fica da seguinte maneira:

Note que o display e quase todos os sensores estão conectados ao canal I2C. Esse canal permite que os dispositivos conectados a esse barramento, se comuniquem com o microcontrolador utilizando apenas dois fios. Com exceção de dois sensores que são conectados diretamente ao microcontrolador, pois não utilizam a comunicação I2C.

 

 

Como mencionado mais acima, o segundo diagrama foi desenvolvido para simplificar a visualização do projeto, além de também melhorar a portabilidade da estação, permitindo o transporte seguro de todo o projeto. Essa montagem, garantirá a segurança da sua estação. Ele ficou da seguinte maneira:

 

 

Apesar de não incluído nesse circuito, o consumo de energia da estação também pode ser considerado. Em situações de local remoto, longe de rede elétrica, você pode alimentar o ESP32 e todo o circuito com uma placa solar ou baterias de lítio. 

Com o diagrama elétrico em mãos, qual será o próximo passo na construção da estação? 

Seguiremos para a próxima etapa com o diagrama elétrico em mãos, direto para a montagem do circuito físico. 

 

Montagem do Circuito

Após realizarmos o desenvolvimento do diagrama elétrico, veja abaixo, como ficou a montagem de todo o circuito elétrico fisicamente. Perceba que após seguir o segundo diagrama, você terá um projeto muito portátil e bonito. A ligação é bem simples, você deve conectar cada um dos sensores em seus respectivos espaços definidos no diagrama elétrico. Tome cuidado com as duas tensões diferentes existentes nas protoboards. Fique atento também a posição dos sensores para não inverter a polaridade dos sensores.

 

 

Veja como ficou a posição dos fios da ligação que foi realizada acima: 

 

 

Agora que a etapa de desenvolvimento do circuito elétrico e a montagem foi realizada, estamos prontos para ir para a parte mais empolgante do projeto, a programação da estação meteorológica!

 

Como programar uma Estação Meteorológica

Nessa etapa, você aprenderá detalhadamente como construir o código responsável pela leitura dos sensores e envio dos dados para a Nuvem.

Para iniciarmos a nossa programação, precisamos definir um software que seja capaz de se comunicar corretamente com o ESP32 e que também suporte as bibliotecas que o display OLED e os sensores utilizam. 

Por aqui, utilizaremos a IDE do Arduino. Fácil, prática e amigável com o usuário, ela é a escolha ideal para projetos que envolvem o Arduino, ESP32 e seus componentes. Caso você não tenha familiaridade com a IDE Arduino, recomendo que acesse o nosso tutorial: Adicionando Placas e bibliotecas na Arduino IDE. Nele você conseguirá aumentar o seu conhecimento, aprendendo o básico da Arduino IDE, desde o começo, aprendendo a instalar, como adicionar placas, bibliotecas e muito mais. 

Para usarmos o ESP32 na Arduino IDE, você deve adicionar as placas da EspressIF, indo em Arquivo, Preferências e adicionando o link à URLs do gerenciador de Placas Adicionais: 

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_dev_index.json

Com a placa adicionada, nós podemos partir para a criação do código da estação meteorológica.

No começo do código, você precisa definir, as bibliotecas que serão utilizadas para controlar a estação. Veja abaixo a definição das bibliotecas. Lembre-se que, se você quiser e precisar expandir a estação meteorológica, você pode adicionar bibliotecas com os novos dispositivos que conectar para facilitar a programação e integração no código.

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include "Secrets.h"
#include <ArduinoJson.h>
#include <Wire.h> // Chama a Wire.h para comunicação I2C
#include <SH1106Wire.h> // Chama a biblioteca responsável pelo controle do display
#include <Adafruit_AHTX0.h> // Biblioteca responsável pelo AHT21
#include <Adafruit_CCS811.h> // Biblioteca responsável pelo sensor CSS811
#include <Adafruit_BMP085.h> // Biblioteca responsável pelo sensor BMP085
#include <Adafruit_TSL2561_U.h> // Biblioteca responsável pelo TSL2561_U

Primeiro, chamamos as bibliotecas utilizando o #include. Após isso chamaremos as seguintes bibliotecas:

  • Wifi.h : Essa biblioteca é responsável por comandar o Wi-Fi presente no ESP32, facilitando a forma que programamos a conexão do ESP32 com a internet. 
  • WifiClientSecure.h: Essa biblioteca é uma extensão da biblioteca Wi-Fi para realizarmos conexões seguras utilizando HTTPS/TLS.
  • PubSubClient.h: Biblioteca responsável pela comunicação usando o protocolo MQTT. É  usada para enviar e receber mensagens de um broker MQTT.
  • Secrets.h: Essa chamada, diferente das outras, pois chama um arquivo que contém algumas informações. Essas informações são os certificados de segurança da conexão com o servidor que utilizamos. Por esse motivo, esse arquivo não estará disponível. Você pode utilizar um broker gratuito como o Mosquito, assim como foi explicado no tutorial Introdução ao MQTT para conectar ao seu servidor, utilizando login e senha, ou da forma como foi realizada aqui, um certificado de segurança. Você pode ver como gerar e utilizar esse certificado de segurança no tutorial Segurança no MQTT.
  • ArduinoJson.h: Essa biblioteca nos permite realizar a manipulação dos dados obtidos dos sensores em formato Json. Ela nos permite realizar a desserialização de dados para serem enviados ao servidor ou dos dados que serão recebidos de lá. 
  • Wire.h: Muito utilizada nos microcontroladores, essa biblioteca é responsável por habilitar o protocolo de comunicação I2C. Esse é o protocolo que nos permite utilizar vários sensores e periféricos em uma mesma linha, definindo apenas seu endereço. 
  • SH1106Wire.h: Essa biblioteca é responsável por controlar as funcionalidades disponveis no display OLED. 
  • Adafruit_AHTX0.h: Nos permite realizar as leituras obtidas pelo sensor AHT21 que utilizaremos em nosso projeto.
  • Adafruit_CCS811.h: Essa biblioteca permite utilizamoso o sensor CCS811.
  • Adafruit_BMP085: Apesar do nome ser BMP085, definimos essa biblioteca, pois ela é compatível com o sensor BMP180.
  • Adafruit_TSL2561_U.h: Essa biblioteca nos auxilia na utilização do sensor o TSL2561. 

Os demais sensores não utilizam biblbiotecas, pois são conectados diretamente a outras portas do ESP32, como será demonstrado abaixo. 

Na sequência definiremos os pinos utilizados pelos outros sensores e também pelo botão.

#define GUVA_OUT_PIN 34   // Pino analógico do sensor UV GUVA-S12S
#define SENSOR_CHUVA_AOUT_PIN 32   // Pino analógico do sensor de chuva
#define SENSOR_CHUVA_DOUT_PIN 33   // Pino digital do sensor de chuva
#define BOTAO_PIN 39  // Pino do botão para alternar entre as telas

O pino 34 da DOIT ESP32, será utilizado para conectarmos o sensor GUVA-S12S. Já os pinos 32 e 33 são utilizados para o sensor de chuva realizar a comunicação analógica que detecta a intensidade da chuva e a digital para verificarmos se está chovendo ou não. Por fim, utilizamos o pino 39 para conectarmos o botão.

A seguir, nessa parte do código, são criados objetos dentro de classes. Por exemplo, dentro da classe SH1106 o objeto display é criado. 

Você pode se perguntar: o que é uma classe e um objeto? 

Classes e Objetos são conceitos fundamentais na programação orientada a objeto. Uma classe é como uma espécie de molde para criação de objetos. 

A classe, define as características (dados) e comportamentos (funções) que os objetos daquele tipo terão. Em termos técnicos, a classe é uma estrutura que agrupa variáveis (chamas de atributos) e funções (chamadas de métodos) em um único tipo de dado. 

Já um objeto é uma instância de uma classe. Quando você cria um objeto, está basicamente criando uma cópia real daquela classe com dados específicos. Cada objeto pode ter valores diferentes para os atribuitos, mas utilizam a mesma estrura e os mesmos métodos definidos na classe.

Essa definição pode parecer meio complicada a principio, então irei utilizar uma analogia para que você entenda de maneira mais simples. 

Pense nas classes e objetos, como uma planilha. Quando essa planilha é aberta, você possui colunas e linhas. 

As colunas dessa tabela, serão a Classe, com suas respectivas características. Já as linhas dessa tabela, serão os objetos, com os dados das informações que serão adicionadas a tabela.

Ou seja, pense em uma tabela com características de uma pessoa.

Nas colunas, você teria, nome, altura, idade, profissão, etc.

Já nas linhas você teria o objeto que possuiria em sua estrutura o nome em si da pessoa, sua altura, sua idade e sua profissão.

Voltando a nosso código, vamos utilizar o conceito que acabamos de aprender:

SH1106Wire display(0x3C, 21, 22);// Definir o display OLED SH1106 no endereço 0x3C

Adafruit_AHTX0 aht;  // AHT10 no endereço 0x38
Adafruit_CCS811 ccs; // CCS811 no endereço 0x5A
Adafruit_BMP085 bmp; // BMP180 no endereço 0x77
Adafruit_TSL2561_Unified tsl = Adafruit_TSL2561_Unified(TSL2561_ADDR_FLOAT, 12345); // TSL2561 no endereço 0x39

// WiFi e MQTT Client
WiFiClientSecure espClient;
PubSubClient client(espClient);

Nessa parte do código, definiremos o objeto display dentro da classe SH1106Wire, definindo o endereço do display OLED como 0x3C, no pino 21 o SDA e no pino 22 o SCL. 

Também criamos os objetos: aht, ccs, bmp, tsl. Esses objetos são utilizados para definirmos o endereço dos sensores na comunicação I2C. Note que não há definição do endereço explicitamente. O endereço desses sensores, são definidos através da sua biblioteca que utiliza os endereços padrões desses sensores. 

Caso o sensor que você adquiriu tenha endereços diferentes, basta procurar o endereço desse sensor com um Scan I2C, e defini-lo afrente do nome do sensor. Por exemplo, se o sensor aht tiver endereço 0x37, a linha deve ficar da seguinte maneira: Adafruit_AHTX0 aht 0x37;

As duas próximas definições, serão responsáveis por: 

WiFiClientSecure espClient - Cria um objeto espClient da classe WiFiClientSecure. Esse objeto tem como objetivo configurar uma conexão Wi-Fi segura (TSL/SSL), que permite o ESP32 conectar-se a um servidor que requer segurança, como o HTTPS ou MQTT sobre TLS.

PubSubClient client(espClient) - Aqui, o objeto Client é criado na classe PubSubClient e associa ele ao objeto espClient. Isso tem como objetivo configurar o cliente MQTT para enviar e receber mensagens do broker MQTT utilizando a conexão espClient que é segura.

Depois disso, definiremos as variáveis que serão utilizadas no restante do código para controle do sensor que está sendo mostrado na tela, o tempo de atualização, da tela, o nome e senha da Rede WiFi, além de também utilizarmos o endereço do servidor MQTT, a porta, usuário e senha.

As variáveis const char* mqtt_user e mqtt_password são variáveis que você deve utilizar quando abrir o seu servidor. No nosso caso, não utilizaremos o usuário e senha, pois faremos login no tutorial com os certificados que foram criados através do tutorial Certificados de Segurança no MQTT.

int sensorAtual = 0;  // Variável para alternar entre os sensores
const int totalSensores = 6;  // Total de sensores conectados

unsigned long tempoAtualizacao = 1000; // Intervalo de tempo para atualizar o display
unsigned long ultimoTempo = 0;         // Marca o último tempo que o display foi atualizado

// Configurações de Wi-Fi
const char* ssid = "Nome da sua Rede WiFi";
const char* password = "Senha da sua Rede WiFi";

// Configurações do broker MQTT com SSL
const char* mqtt_server = "Endereço do seu Servidor MQTT";
const int mqtt_port = "Porta do seu Servidor MQTT"; // Porta SSL para MQTT
const char* mqtt_user = "Seu_Usuario";
const char* mqtt_password = "Sua_Senha";

Você percebeu que algumas variáveis estão com um asterisco (*) ?

Isso acontece, pois essas variáveis são do tipo ponteiro. Esse é um tipo de variável que armazena o endereço de memória de outra variável. Ao invés de guardar um valor, como um número ou texto, o ponteiro guarda a "localização" onde esse valor está armazenado na memória. Isso permite que o ponteiro "aponte" para essa variável e interaja diretamente com ela, o que permite flexibilidade e eficiência no uso de dados. 

Se essa explicação pareceu um pouco confusa, você pode pensar da seguinte maneira:

Utilizar a variável comum, é como colocar as suas compras dentro do armário. 
Quando a variável comum é alterada, é como se você fosse no armário guardar as compras e tivesse que cada item em uma nova posição, dentro de outras partes do armário, mesmo que você já guardasse seu arroz em uma posição específica. Ou seja, o novo arroz que você comprou, iria para outra posição, e não onde você guarda o arroz normalmente.

Com o ponteiro, você saberia a localização exata dos itens da sua compra que já estavam dentro do seu armário.

Variável Comum sem o ponteiro: Quando você usa uma variável comum e passa seu valor para uma função, é como pegar um item da compra e colocá-la em outro armário (ou prateleira). Então, mesmo que você altere essa cópia na nova prateleira (função), a compra que já estava no armário principal (variável original) não são alterados.

Variavel com Ponteiro: Quando você usa um ponteiro, é como passar o endereço exato do armário você já guarda sua compra. A função então "abre o armário principal" e vê a compra diretamente. Qualquer alteração que você fizer ali é imediatamente refletida, pois está acessando o armário original, e não uma cópia.

Continuando nosso código entramos dentro do Setup:

void setup() {
  client.setBufferSize(1024); // Define o tamanho do buffer das mensagens que vão o Servidor para 1024 bytes
  Serial.begin(115200);
  • client.setBufferSize(1024): Aqui, nós definimos o tamanho do Buffer das mensagens que irão para o Servidor. Isso é necessário pois, enviaremos uma única mensagem para o servidor contendo os valores de todos os sensores.
  • Serial.begin(115200): Iniciamos o monitor Serial com baud rate de 115200.

Após isso, iniciamos a configuração para conexão do Wi-Fi, além de também definir as configurações entre o cliente MQTT e o broker e os certificados de segurança que utilizaremos:

// Configura Wi-Fi
    setup_wifi();

  // Configura o cliente MQTT com o broker
    client.setServer("Endereço do Servidor", Porta do Servidor);
    client.setCallback(callback);
    
  // Configura o certificado SSL
    espClient.setCACert(CERT_CA);          // Set CA certificate
    espClient.setCertificate(CERT_CLIENT); // Set client certificate
    espClient.setPrivateKey(KEY_CLIENT);   // Set private key
  • setup_wifi(): Chama a função que configura a conexão do Wi-Fi. 
  • client.setServer("Endereço do Servidor", Porta do Servidor): Utilizando a biblioteca PubSubClient configuramos o cliente, definindo o endereço do servidor e a porta de utilização no MQTT.
  • client.setCallback(callback): Nessa linha, a configuramos uma função de retorno chamada callback que lida com as mensagens recebidas do broker MQTT.

Para conseguirmos nos conectar sem utilizar um usuário e senha, tornando o sistema ainda mais seguro. Por conta disso, lá em cima, no começo do código, chamamos o Arquivo "Secrets.h". Nele, os três certificados CERT_CA, CERT_CLIENT E KEY_CLIENT são utilizados para autenticar a conexão do usuário com o servidor nessa parte do código. Você pode gerar o seu próprio certificado com o tutorial: Certificados de Segurança no MQTT como mencionado acima.

Prosseguindo, definimos o pino que o botão será conectado, além de também definirmos os pinos dos sensores que não utilizam a comunicação I2C como padrão:

  pinMode(BOTAO_PIN, INPUT); // Seleciona o pino do botão como entrada
  pinMode(GUVA_OUT_PIN, INPUT); // Seleciona o pino do sensor GUVA como entrada
  pinMode(SENSOR_CHUVA_AOUT_PIN, INPUT); // Seleciona o pino analógico do sensor de chuva como entrada
  pinMode(SENSOR_CHUVA_DOUT_PIN, INPUT); // Seleciona o pino digital do sensor de chuva como entrad

Todos eles, são definidos com INPUT pois o ESP32 lerá os sinais enviados por esses sensores.

Na sequência, iniciamos a comunicação I2C com Wire.begin(21, 22) que define os pinos 21 e 22 como SDA e SCL respectivamente. Também iniciamos os sensores com a função .begin:

  Wire.begin(21, 22);// Inicializar comunicação I2C
  aht.begin(); // Inicializar o sensor AHT10 
  ccs.begin();// Inicializar o sensor CCS811 
  bmp.begin(); // Inicializar o sensor BMP180 
  tsl.begin();  // Inicializar o sensor TSL2561
  tsl.enableAutoRange(true); // Verifica se o TSL2561
  tsl.setIntegrationTime(TSL2561_INTEGRATIONTIME_13MS); // define o tempo de integração do TSL2561

Com tsl.enableAutoRange(true) verificamos o funcionamento do sensor TSL e com tsl.setIntegrationTime definimos o tempo de integração do sensor TSL2561 de 13MS.

Por fim, no Setup iniciaremos o funcionamento do display com display.init(). e também escreveremos algumas mensagens e utilizar alguns comandos básicos de controle do display, veja abaixo:

  display.init();// Inicializar o display SH1106
  display.flipScreenVertically(); // Inverte a tela do display 
  display.clear();   // Limpa o display
  display.display(); // Atualiza a tela do display

  // Exibe mensagem inicial
  display.setFont(ArialMT_Plain_16); // Seleciona a fonte como Arial e tamanho 16
  display.setTextAlignment(TEXT_ALIGN_CENTER); // alinha o texto no centro
  display.drawString(64, 14, "Estação"); // seleciona a posição e desenha no display Estação
  display.drawString(64, 34, "Meteorológica"); // Escreve meteorológica
  display.display(); // atualiza o display, escrevendo a mensagem acima
  display.clear();// limpa a tela do display
  delay(1000);  // delay para limpar a tela
  display.setTextAlignment(TEXT_ALIGN_CENTER); // alinha o texto o centro
  display.drawString(64, 14, "Curto"); // escreve curto
  display.drawString(64, 34, "Circuito"); // escreve circuito
  display.display(); // atualiza a tela do display, escrevendo a mensagem acima
  display.clear(); // limpa a tela 
  delay(1000); // delay de 1 segundo

}

Aqui, usamos display.flipScreenVertically(); para girar o conteúdo do display em 180°. Clear e display.display() são funções utilizadas para atualizar a tela do display, escrevendo o que desejarmos. Em display.setFont() definimos o tamanho do texto que aparece no display. Em display.setTextAlignment(TEXT_ALIGN_CENTER); colocamos o texto para aparecer no meio do display. A função display.drawString() é utilizada para escrevermos o conteúdo que desejarmos.

Após finalizarmos o Setup, vamos para o Loop. Nele, recriaremos algumas funções que serão melhor explicadas quando falarmos diretamente no momento da criação das funções. Dito isso, começamos o Loop da seguinte maneira:

void loop() 
{
  if (!client.connected()) {
    reconnect();
  }
     client.loop();
   
      int UVLevel = analogRead(GUVA_OUT_PIN);
      float voltage = UVLevel * (3.3 / 4095.0);  // Converte a leitura para a tensão
      float UVIndex = calcularIndiceUV(voltage);  // Calcula o índice UV


      String nivelUV;
      if (UVIndex <= 2){nivelUV = "Baixo";} 
      else if (UVIndex <= 5){nivelUV = "Moderado";} 
      else if (UVIndex <= 7){nivelUV = "Alto";} 
      else if (UVIndex <= 10){nivelUV = "Muito Alto";} 
      else {nivelUV = "Extremo";}

Primeiro, criamos uma  verificação para o estado da conexão com o WiFi em if (!client.connected()) {reconnect();}. Após isso, a função client.loop(); é chamada para manter a comunicação ativa e verificar se há novas mensagens e atualizações constantemente. Na sequência temos as configurações para o sensor GUVA ser lido e seu valor ser calculado. Essa função se repete dentro do loop, para conseguirmos escrever o valor dela no MQTT.

De mesmo modo, seguimos com a definição para o sensor de Chuva funcionar:

  int valorAnalogico = analogRead(SENSOR_CHUVA_AOUT_PIN);
  int valorDigital = digitalRead(SENSOR_CHUVA_DOUT_PIN);

  String nivelChuva;
  if (valorAnalogico > 2500) {
    nivelChuva = "Sem Chuva";
  } else if (valorAnalogico > 1500) {
    nivelChuva = "Chuva Leve";
  } else if (valorAnalogico > 500) {
    nivelChuva = "Chuva Moderada";
  } else {
    nivelChuva = "Chuva Intensa";
  }
  // Indicação se está chovendo ou não com base na leitura digital
  String statusChuva = (valorDigital == HIGH) ? "Sem chuva" : "Chovendo";

Continuando, criaremos duas variáveis, uma para armazenamento do tempo da última mensagem e outra para armazenamento do tempo atual. Seguidas de um if que compara o tempo atual com o tempo da última mensagem, e se esse tempo for maior que 60 segundos uma mensagem será enviada e o tempo da última mensagem é definido como o tempo atual. Aqui, os sensores também são chamados dentro do if que enviará a mensagem, para verificar a leitura atual dos sensores.

// Publica uma mensagem a cada 5 segundos
  static unsigned long lastMsg = 0;
  unsigned long now = millis();

  if (now - lastMsg > 60000) 
  {
   lastMsg = now;

  sensors_event_t humidity, temp; 
  aht.getEvent(&humidity, &temp);

  sensors_event_t event;
  tsl.getEvent(&event);
  ccs.readData();

Com os sensores já iniciados, você pode se perguntar, qual é o próximo passo? 

Criaremos um documento JSON dinâmico com tamanho suficiente para a nossa mensagem que suportará os valores dos sensores com DynamicJsonDocument.doc(2048);

Após isso, vamos para a estrutura do arquivo JSON que conterá a informação dos nossos sensores. A estrutura é bem simples doc["AHT21"]["Temperatura"] = temp.temperature;

Essa estrutura é composta pelos seguintes componentes. Primeiro, a criação de uma estrutura JSON em doc onde o valor de temp.temperature é armazenado dentro do campo "Temperatura" dentro do objeto "AHT21". A estrutura utilizada, é a mesma para os sensores subsequentes: 

  DynamicJsonDocument doc(2048);

  // Preenche os dados JSON
  doc["AHT21"]["Temperatura"] = temp.temperature;
  doc["AHT21"]["Umidade"] = humidity.relative_humidity;
  doc["CCS811"]["CO2"] = ccs.geteCO2();
  doc["CCS811"]["TVOC"] = ccs.getTVOC();
  doc["BMP180"]["Pressão"] = bmp.readPressure();
  doc["BMP180"]["Altitude"] = bmp.readAltitude();
  doc["TSL2561"]["Lux"] = event.light;
  doc["Guva"]["UV Index"] = UVIndex;
  doc["Guva"]["Nível"] = nivelUV;
  doc["Chuva"]["Status"] = nivelChuva;
  doc["Chuva"]["Digital"] = (valorDigital == HIGH) ? "Sem Chuva" : "Chovendo";

Quando criamos esse arquivo JSON, é necessário declarar um buffer que consiga armazenar a mensagem JSON em formato de string que iremos enviar para o servidor. O buffer é criado da seguinte maneira: char buffer[2048]; Após isso, a serialização do objeto JSON doc para o buffer é realizada e o número de bytes resultantes é armazenado em n.

O monitor serial escreve a mensagem do buffer somente para questões de debbug do código e por fim, a mensagem é enviada para o tópico Estação utilizando o buffer e o tamanho limite da mensagem é definido com a variável n.

  // Converte o JSON para string
  char buffer[2048];
  size_t n = serializeJson(doc, buffer);
  
  Serial.println(buffer);
  // Envia a mensagem via MQTT
  client.publish("Estacao", buffer, n);
  }

Finalizando o Loop chamamos a função botao() que controla a ativação do botão e criaremos uma variável para definição do tempo atual. Utilizando as variáveis de tempo, criamos uma comparação para atualização do display. Dentro dessa comparação, criamos um switch case que utiliza o valor de sensorAtual para verificar qual sensor deve ser mostrado na tela.  Por fim, a tela é atualizada e o valor do último tempo em que o display foi atualizado é registrado como tempo atual:

  botao(); // chama a função botão, responsável pela trocar as telas

  // Verifica se é hora de atualizar o display
  unsigned long tempoAtual = millis(); // Tempo atual

  if (tempoAtual - ultimoTempo >= tempoAtualizacao) {
    // Atualiza o display com o sensor selecionado
    switch (sensorAtual) // Switch com a variavel sensorAtual, definindo qual sensor é mostrado na tela
    {
      case 0: // Em caso 0, chama a função mostrarAHT21, mostrando no display, as informações do sensor AHT21
        mostrarAHT21();
        break;
      case 1: // Em caso 1, chama a função mostrarCCS811, mostrando no display, as informações do sensor CCS811
        mostrarCCS811();
        break;
      case 2: // Em caso 2, chama a função mostrarBMP180, mostrando no display, as informações do sensor BMP180
        mostrarBMP180();
        break;
      case 3: // Em caso 3, chama a função mostrarTSL2561, mostrando no display, as informações do sensor TSL2561
        mostrarTSL2561();
        break;
      case 4: // Em caso 4, chama a função mostrarGUVA, mostrando no display, as informações do sensor mostrarGUVA
        mostrarGUVA();
        break;
      case 5: // Em caso 5, chama a função mostrarSensorChuva, mostrando no display, as informações do sensor Sensor Chuva
        mostrarSensorChuva();
        break;
    }

    display.display();  // Mostra os dados no display
    display.clear();    // Limpa o display para a próxima atualização

    // Atualiza o último tempo em que o display foi atualizado
    ultimoTempo = tempoAtual;
  }

}

Finalizando o nosso Loop, criamos a função responsável por controlar as telas mostradas no display. Essa função é bem simples, se o botão for pressionado, ele adicionará mais um ao valor da variável sensorAtual. Se o valor dessa variável for maior que 6, o valor dela torna-se 0. Isso faz a primeira tela ser mostrada novamente, gerando assim um loop da sequência de telas. 

Depois disso, iniciamos a criação das funções responsáveis por controlar os sensores. As funções possuem basicamente a mesma funcionalidade, mudando apenas o sensor que será utilizado. Você pode notar que a lógica é bem simples, primeiro os sensores são iniciados com seus respectivos comandos. Depois, cada sensor tem seu valor impresso no display. O valor dos sensores é mostrado em ordem através do switch case que foi utilizado mais acima no código, dentro do loop.

Veja como fica a estrutura do funcionamento do botão e dos sensores:

void botao() // Variavel para controle do botão 
{
  if (digitalRead(39) == 0) // Quando o botão do pino 39 for pressionado 
  {
    sensorAtual++; // Adiciona o valor do sensorAtual
    delay(200); // delay para adicionar o debounce
  }
  // Essa função reinicia a variavel sensorAtual para voltar a primeira tela 
  if (sensorAtual >= 6) // Se o valor de sensorAtual for igual ou maior que 6 
  {
    sensorAtual = 0; // sensorAtual se torna 0
  }
}


void mostrarAHT21()// Função que exibe o valor do sensor AHT21
{
  sensors_event_t humidity, temp; 
  aht.getEvent(&humidity, &temp);

  display.setFont(ArialMT_Plain_16);
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.drawString(0, 0, "Sensor AHT21:");
  display.drawString(0, 20, "Temp: " + String(temp.temperature) + " C");
  display.drawString(0, 40, "Umid:  " + String(humidity.relative_humidity) + " %");
}

void mostrarCCS811()// Função que exibe o valor do sensor CCS811
{
  ccs.readData();
  display.setFont(ArialMT_Plain_16);
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.drawString(0, 0, "Sensor CCS811:");
  display.drawString(0, 20, "CO2: " + String(ccs.geteCO2()) + " ppm");
  display.drawString(0, 40, "TVOC: " + String(ccs.getTVOC()) + " ppb");
     
}

void mostrarBMP180() // Função que exibe o valor do senso BMP180
{
  display.setFont(ArialMT_Plain_16);
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.drawString(0, 0, "Sensor BMP180:");
  display.drawString(0, 20, "Press: " + String(bmp.readPressure()) + " Pa");
  display.drawString(0, 40, "Altitude: " + String(bmp.readAltitude()) + " m");
}

void mostrarTSL2561() // Função que exibe o valor do sensor TSL2561
{
  sensors_event_t event;
  tsl.getEvent(&event);

  display.setFont(ArialMT_Plain_16);
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.drawString(0, 0, "Sensor TSL2561:");
  if (event.light) {
    display.drawString(0, 20, "Lux: " + String(event.light));
  } else {
    display.drawString(0, 20, "Falha na leitura!");
  }
}

void mostrarGUVA() // Função que exibe o valor do sensor GUVA
{
  int UVLevel = analogRead(GUVA_OUT_PIN);
  float voltage = UVLevel * (3.3 / 4095.0);  // Converte a leitura para a tensão
  float UVIndex = calcularIndiceUV(voltage);  // Calcula o índice UV

  String nivelUV;
  if (UVIndex <= 2) {
    nivelUV = "Baixo";
  } else if (UVIndex <= 5) {
    nivelUV = "Moderado";
  } else if (UVIndex <= 7) {
    nivelUV = "Alto";
  } else if (UVIndex <= 10) {
    nivelUV = "Muito Alto";
  } else {
    nivelUV = "Extremo";
  }

  display.setFont(ArialMT_Plain_16);
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.drawString(0, 0, "Sensor GUVA: ");
  display.drawString(0, 20, "UV Index: " + String(UVIndex));
  display.drawString(0, 40, "Nivel: " + nivelUV);
}

float calcularIndiceUV(float voltage) // Função para controlar indice UV
 {
  float UVIndex = voltage * 15.0;
  return UVIndex;
}

void mostrarSensorChuva() // Função para ler os valores do sensor de chuva e mostrar na tela
{
  int valorAnalogico = analogRead(SENSOR_CHUVA_AOUT_PIN);
  int valorDigital = digitalRead(SENSOR_CHUVA_DOUT_PIN);

  String nivelChuva;
  if (valorAnalogico > 3000) {
    nivelChuva = "Sem Chuva";
  } else if (valorAnalogico > 2000) {
    nivelChuva = "Chuva Leve";
  } else if (valorAnalogico > 1000) {
    nivelChuva = "Chuva Moderada";
  } else {
    nivelChuva = "Chuva Intensa";
  }

  // Indicação se está chovendo ou não com base na leitura digital
  String statusChuva = (valorDigital == HIGH) ? "Sem Chuva" : "Chovendo";

  display.setFont(ArialMT_Plain_16);
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.drawString(0, 0, "Sensor de Chuva:");
  display.drawString(0, 20, nivelChuva);
  display.drawString(0, 40, statusChuva);
}

Finalizando o código, temos ainda três funções, que são responsáveis por realizar a conexão com o WiFi:

void setup_wifi() {
  delay(10);
  Serial.println();
  Serial.print("Conectando a ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi conectado.");
  Serial.println("Endereço de IP: ");
  Serial.println(WiFi.localIP());
}

Por controlar a resposta quando uma mensagem é recebida:

// Callback quando uma mensagem é recebida
void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Mensagem recebida [");
  Serial.print(topic);
  Serial.print("]: ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
}

E também a função para realizar a conexão com o MQTT Broker:

// Função para conectar ao MQTT Broker
void reconnect() {
  while (!client.connected()) {
    Serial.print("Tentando conexão MQTT...");
    // Tenta se conectar
    if (client.connect("ESP32Client", mqtt_user, mqtt_password)) {
      Serial.println("Conectado");
      // Subscreve a um tópico após conectar
      client.subscribe("Estacao");
    } else {
      Serial.print("Falha na conexão. Código de retorno: ");
      Serial.print(client.state());
      Serial.println(" Tentando novamente em 5 segundos");
      // Espera 5 segundos antes de tentar novamente
      delay(5000);
    }
  }
}

Com isso, terminamos a explicação completa do código da estação meteorológica. Agora, abaixo, você encontrará  o código completo, caso deseje realizar a montagem e programação da mesma maneira que realizamos:

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include "Secrets.h"
#include <ArduinoJson.h>
#include <Wire.h> // Chama a Wire.h para comunicação I2C
#include <SH1106Wire.h> // Chama a biblioteca responsável pelo controle do display
#include <Adafruit_AHTX0.h> // Biblioteca responsável pelo AHT21
#include <Adafruit_CCS811.h> // Biblioteca responsável pelo sensor CSS811
#include <Adafruit_BMP085.h> // Biblioteca responsável pelo sensor BMP085
#include <Adafruit_TSL2561_U.h> // Biblioteca responsável pelo TSL2561_U


#define GUVA_OUT_PIN 34   // Pino analógico do sensor UV GUVA-S12S
#define SENSOR_CHUVA_AOUT_PIN 32   // Pino analógico do sensor de chuva
#define SENSOR_CHUVA_DOUT_PIN 33   // Pino digital do sensor de chuva
#define BOTAO_PIN 39  // Pino do botão para alternar entre as telas

SH1106Wire display(0x3C, 21, 22);// Definir o display OLED SH1106 no endereço 0x3C

Adafruit_AHTX0 aht;  // AHT10 no endereço 0x38
Adafruit_CCS811 ccs; // CCS811 no endereço 0x5A
Adafruit_BMP085 bmp; // BMP180 no endereço 0x77
Adafruit_TSL2561_Unified tsl = Adafruit_TSL2561_Unified(TSL2561_ADDR_FLOAT, 12345); // TSL2561 no endereço 0x39

// WiFi e MQTT Client
WiFiClientSecure espClient;
PubSubClient client(espClient);

int sensorAtual = 0;  // Variável para alternar entre os sensores
const int totalSensores = 6;  // Total de sensores conectados

unsigned long tempoAtualizacao = 1000; // Intervalo de tempo para atualizar o display
unsigned long ultimoTempo = 0;         // Marca o último tempo que o display foi atualizado

// Configurações de Wi-Fi
const char* ssid = "Nome da sua rede";
const char* password = "Senha da sua rede";

// Configurações do broker MQTT com SSL
const char* mqtt_server = "Endereço do seu servidor";
const int mqtt_port = porta do seu servidor; // Porta SSL para MQTT
const char* mqtt_user = "Seu_Usuario";
const char* mqtt_password = "Sua_Senha";


void setup() {
  client.setBufferSize(1024); // Define o tamanho do buffer das mensagens que vão o Servidor para 1024 bytes
  Serial.begin(115200);

  // Configura Wi-Fi
    setup_wifi();

  // Configura o cliente MQTT com o broker
    client.setServer("62.146.172.61", 8883);
    client.setCallback(callback);
    
  // Configura o certificado SSL
    espClient.setCACert(CERT_CA);          // Set CA certificate
    espClient.setCertificate(CERT_CLIENT); // Set client certificate
    espClient.setPrivateKey(KEY_CLIENT);   // Set private key

  pinMode(BOTAO_PIN, INPUT); // Seleciona o pino do botão como entrada
  pinMode(GUVA_OUT_PIN, INPUT); // Seleciona o pino do sensor GUVA como entrada
  pinMode(SENSOR_CHUVA_AOUT_PIN, INPUT); // Seleciona o pino analógico do sensor de chuva como entrada
  pinMode(SENSOR_CHUVA_DOUT_PIN, INPUT); // Seleciona o pino digital do sensor de chuva como entrada
  
  Wire.begin(21, 22);// Inicializar comunicação I2C
  aht.begin(); // Inicializar o sensor AHT10 
  ccs.begin();// Inicializar o sensor CCS811 
  bmp.begin(); // Inicializar o sensor BMP180 
  tsl.begin();  // Inicializar o sensor TSL2561
  tsl.enableAutoRange(true); // Verifica se o TSL2561
  tsl.setIntegrationTime(TSL2561_INTEGRATIONTIME_13MS); // define o tempo de integração do TSL2561
 
  display.init();// Inicializar o display SH1106
  display.flipScreenVertically(); // Inverte a tela do display 
  display.clear();   // Limpa o display
  display.display(); // Atualiza a tela do display

  // Exibe mensagem inicial
  display.setFont(ArialMT_Plain_16); // Seleciona a fonte como Arial e tamanho 16
  display.setTextAlignment(TEXT_ALIGN_CENTER); // alinha o texto no centro
  display.drawString(64, 14, "Estação"); // seleciona a posição e desenha no display Estação
  display.drawString(64, 34, "Meteorológica"); // Escreve meteorológica
  display.display(); // atualiza o display, escrevendo a mensagem acima
  display.clear();// limpa a tela do display
  delay(1000);  // delay para limpar a tela
  display.setTextAlignment(TEXT_ALIGN_CENTER); // alinha o texto o centro
  display.drawString(64, 14, "Curto"); // escreve curto
  display.drawString(64, 34, "Circuito"); // escreve circuito
  display.display(); // atualiza a tela do display, escrevendo a mensagem acima
  display.clear(); // limpa a tela 
  delay(1000); // delay de 1 segundo

}

void loop() 
{
  if (!client.connected()) {
    reconnect();
  }
     client.loop();
   
      int UVLevel = analogRead(GUVA_OUT_PIN);
      float voltage = UVLevel * (3.3 / 4095.0);  // Converte a leitura para a tensão
      float UVIndex = calcularIndiceUV(voltage);  // Calcula o índice UV


      String nivelUV;
      if (UVIndex <= 2){nivelUV = "Baixo";} 
      else if (UVIndex <= 5){nivelUV = "Moderado";} 
      else if (UVIndex <= 7){nivelUV = "Alto";} 
      else if (UVIndex <= 10){nivelUV = "Muito Alto";} 
      else {nivelUV = "Extremo";}


  int valorAnalogico = analogRead(SENSOR_CHUVA_AOUT_PIN);
  int valorDigital = digitalRead(SENSOR_CHUVA_DOUT_PIN);

  String nivelChuva;
  if (valorAnalogico > 2500) {
    nivelChuva = "Sem Chuva";
  } else if (valorAnalogico > 1500) {
    nivelChuva = "Chuva Leve";
  } else if (valorAnalogico > 500) {
    nivelChuva = "Chuva Moderada";
  } else {
    nivelChuva = "Chuva Intensa";
  }
  // Indicação se está chovendo ou não com base na leitura digital
  String statusChuva = (valorDigital == HIGH) ? "Sem chuva" : "Chovendo";

  // Publica uma mensagem a cada 5 segundos
  static unsigned long lastMsg = 0;
  unsigned long now = millis();

  if (now - lastMsg > 60000) 
  {
   lastMsg = now;

  sensors_event_t humidity, temp; 
  aht.getEvent(&humidity, &temp);

  sensors_event_t event;
  tsl.getEvent(&event);
  ccs.readData();

  // Crie um documento JSON dinâmico com tamanho suficiente
  DynamicJsonDocument doc(2048);

  // Preenche os dados JSON
  doc["AHT21"]["Temperatura"] = temp.temperature;
  doc["AHT21"]["Umidade"] = humidity.relative_humidity;
  doc["CCS811"]["CO2"] = ccs.geteCO2();
  doc["CCS811"]["TVOC"] = ccs.getTVOC();
  doc["BMP180"]["Pressão"] = bmp.readPressure();
  doc["BMP180"]["Altitude"] = bmp.readAltitude();
  doc["TSL2561"]["Lux"] = event.light;
  doc["Guva"]["UV Index"] = UVIndex;
  doc["Guva"]["Nível"] = nivelUV;
  doc["Chuva"]["Status"] = nivelChuva;
  doc["Chuva"]["Digital"] = (valorDigital == HIGH) ? "Sem Chuva" : "Chovendo";

  // Converte o JSON para string
  char buffer[2048];
  size_t n = serializeJson(doc, buffer);

  Serial.println(buffer);
  // Envia a mensagem via MQTT
  client.publish("Estacao", buffer, n);
  }


  botao(); // chama a função botão, responsável pela trocar as telas

  // Verifica se é hora de atualizar o display
  unsigned long tempoAtual = millis(); // Tempo atual

  if (tempoAtual - ultimoTempo >= tempoAtualizacao) {
    // Atualiza o display com o sensor selecionado
    switch (sensorAtual) // Switch com a variavel sensorAtual, definindo qual sensor é mostrado na tela
    {
      case 0: // Em caso 0, chama a função mostrarAHT21, mostrando no display, as informações do sensor AHT21
        mostrarAHT21();
        break;
      case 1: // Em caso 1, chama a função mostrarCCS811, mostrando no display, as informações do sensor CCS811
        mostrarCCS811();
        break;
      case 2: // Em caso 2, chama a função mostrarBMP180, mostrando no display, as informações do sensor BMP180
        mostrarBMP180();
        break;
      case 3: // Em caso 3, chama a função mostrarTSL2561, mostrando no display, as informações do sensor TSL2561
        mostrarTSL2561();
        break;
      case 4: // Em caso 4, chama a função mostrarGUVA, mostrando no display, as informações do sensor mostrarGUVA
        mostrarGUVA();
        break;
      case 5: // Em caso 5, chama a função mostrarSensorChuva, mostrando no display, as informações do sensor Sensor Chuva
        mostrarSensorChuva();
        break;
    }

    display.display();  // Mostra os dados no display
    display.clear();    // Limpa o display para a próxima atualização

    // Atualiza o último tempo em que o display foi atualizado
    ultimoTempo = tempoAtual;
  }

}

void botao() // Variavel para controle do botão 
{
  if (digitalRead(39) == 0) // Quando o botão do pino 39 for pressionado 
  {
    sensorAtual++; // Adiciona o valor do sensorAtual
    delay(200); // delay para adicionar o debounce
  }
  // Essa função reinicia a variavel sensorAtual para voltar a primeira tela 
  if (sensorAtual >= 6) // Se o valor de sensorAtual for igual ou maior que 6 
  {
    sensorAtual = 0; // sensorAtual se torna 0
  }
}


void mostrarAHT21()// Função que exibe o valor do sensor AHT21
{
  sensors_event_t humidity, temp; 
  aht.getEvent(&humidity, &temp);

  display.setFont(ArialMT_Plain_16);
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.drawString(0, 0, "Sensor AHT21:");
  display.drawString(0, 20, "Temp: " + String(temp.temperature) + " C");
  display.drawString(0, 40, "Umid:  " + String(humidity.relative_humidity) + " %");
}

void mostrarCCS811()// Função que exibe o valor do sensor CCS811
{
      ccs.readData();
      display.setFont(ArialMT_Plain_16);
      display.setTextAlignment(TEXT_ALIGN_LEFT);
      display.drawString(0, 0, "Sensor CCS811:");
      display.drawString(0, 20, "CO2: " + String(ccs.geteCO2()) + " ppm");
      display.drawString(0, 40, "TVOC: " + String(ccs.getTVOC()) + " ppb");
     
}

void mostrarBMP180() // Função que exibe o valor do senso BMP180
{
  display.setFont(ArialMT_Plain_16);
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.drawString(0, 0, "Sensor BMP180:");
  display.drawString(0, 20, "Press: " + String(bmp.readPressure()) + " Pa");
  display.drawString(0, 40, "Altitude: " + String(bmp.readAltitude()) + " m");
}

void mostrarTSL2561() // Função que exibe o valor do sensor TSL2561
{
  sensors_event_t event;
  tsl.getEvent(&event);

  display.setFont(ArialMT_Plain_16);
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.drawString(0, 0, "Sensor TSL2561:");
  if (event.light) {
    display.drawString(0, 20, "Lux: " + String(event.light));
  } else {
    display.drawString(0, 20, "Falha na leitura!");
  }
}

void mostrarGUVA() // Função que exibe o valor do sensor GUVA
{
  int UVLevel = analogRead(GUVA_OUT_PIN);
  float voltage = UVLevel * (3.3 / 4095.0);  // Converte a leitura para a tensão
  float UVIndex = calcularIndiceUV(voltage);  // Calcula o índice UV

  String nivelUV;
  if (UVIndex <= 2) {
    nivelUV = "Baixo";
  } else if (UVIndex <= 5) {
    nivelUV = "Moderado";
  } else if (UVIndex <= 7) {
    nivelUV = "Alto";
  } else if (UVIndex <= 10) {
    nivelUV = "Muito Alto";
  } else {
    nivelUV = "Extremo";
  }

  display.setFont(ArialMT_Plain_16);
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.drawString(0, 0, "Sensor GUVA: ");
  display.drawString(0, 20, "UV Index: " + String(UVIndex));
  display.drawString(0, 40, "Nivel: " + nivelUV);
}

float calcularIndiceUV(float voltage) // Função para controlar indice UV
 {
  float UVIndex = voltage * 15.0;
  return UVIndex;
}

void mostrarSensorChuva() // Função para ler os valores do sensor de chuva e mostrar na tela
{
  int valorAnalogico = analogRead(SENSOR_CHUVA_AOUT_PIN);
  int valorDigital = digitalRead(SENSOR_CHUVA_DOUT_PIN);

  String nivelChuva;
  if (valorAnalogico > 3000) {
    nivelChuva = "Sem Chuva";
  } else if (valorAnalogico > 2000) {
    nivelChuva = "Chuva Leve";
  } else if (valorAnalogico > 1000) {
    nivelChuva = "Chuva Moderada";
  } else {
    nivelChuva = "Chuva Intensa";
  }

  // Indicação se está chovendo ou não com base na leitura digital
  String statusChuva = (valorDigital == HIGH) ? "Sem Chuva" : "Chovendo";

  display.setFont(ArialMT_Plain_16);
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.drawString(0, 0, "Sensor de Chuva:");
  display.drawString(0, 20, nivelChuva);
  display.drawString(0, 40, statusChuva);
}
// Função para conectar ao Wi-Fi
void setup_wifi() {
  delay(10);
  Serial.println();
  Serial.print("Conectando a ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi conectado.");
  Serial.println("Endereço de IP: ");
  Serial.println(WiFi.localIP());
}

// Callback quando uma mensagem é recebida
void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Mensagem recebida [");
  Serial.print(topic);
  Serial.print("]: ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
}

// Função para conectar ao MQTT Broker
void reconnect() {
  while (!client.connected()) {
    Serial.print("Tentando conexão MQTT...");
    // Tenta se conectar
    if (client.connect("ESP32Client", mqtt_user, mqtt_password)) {
      Serial.println("Conectado");
      // Subscreve a um tópico após conectar
      client.subscribe("Estacao");
    } else {
      Serial.print("Falha na conexão. Código de retorno: ");
      Serial.print(client.state());
      Serial.println(" Tentando novamente em 5 segundos");
      // Espera 5 segundos antes de tentar novamente
      delay(5000);
    }
  }
}

 

Funcionamento do Projeto

Veja abaixo um vídeo da estação meteorológica funcionando.

Por aqui, nós utilizamos o MQTTX para recebermos os dados capturados pela estação, veja:

 

 

Conclusão

Neste tutorial, construímos juntos uma estação meteorológica completa e conectada à nuvem utilizando o ESP32. Exploramos desde a seleçãodos componentes, passando pela montagem do circuito, até a programação que integra todos os sensores e permite a comunicação via protocolo MQTT.

Com a estação em funcionamento, você pode monitorar em tempo real diversas variáveis ambientais, como temperatura, umidade, pressão atmosférica, luminosidade, índice UV e presença de chuva. Além disso, enviar esses dados para a nuvem abre um leque de possibilidades para análise remota, automação e integração com outros sistemas de Internet das Coisas.

Esperamos você tenha conseguido ampliar seus conhecimentos em eletrônica, programação e IoT, inspirando você a continuar explorando e inovando nessa área fascinante. Lembre-se de que a tecnologia está em constante evolução, e cada novo projeto é uma oportunidade para aprender algo novo e contribuir com soluções criativas para os desafios do nosso dia a dia.

Agradecemos por nos acompanhar até aqui. Agora é com você! Customize, expanda e adapte este projeto às suas necessidades e continue desenvolvendo suas habilidades. Bons projetos e até a próxima!