banner

Ir para o Forum

Automação Baixo Custo ESP32 e 16 relés



Um projeto de automação com ESP32 como Web Server! Hoje, vamos utilizar o ESP32 para mostrar uma página com os valores de temperatura, umidade e botões para modificarmos o estado de relés. Ainda, vamos utilizar o MCP23017 para aumentar a quantidade de pinos disponíveis no ESP, o que nos possibilita ligar até 128 relés.


Quero destacar, pessoal, que neste vídeo você vai perceber que não vale a pena fazer um projeto usando o ESP-01 com Arduino Mega, como neste vídeo: Arduino Mega + WiFi = Automação, que eu fiz a pedido de vários seguidores. Isso, é claro, na minha opinião, pois considero que com o ESP32 fica muito melhor, por conta do domínio total que esse microcontrolador te possibilita.


ESP32 Pinout




Montagem

Nesta imagem você observa que eu só usei dois pinos para ligar 16 relés, facilidade possibilitada pelo MCP23017, que você pode conhecer melhor neste vídeo: Expansor de IOs para ESP32,ESP8266 e Arduino, mas na versão MCP23016, que utiliza a mesma Lib. Tenho ainda neste projeto de hoje nosso velho conhecido DHT22.




Demonstração

Na imagem, você vê nossa montagem e um celular conectado ao nosso aplicativo com IP local, sendo que as placas, tanto a de relés quanto a do ESP32, estão sendo alimentadas pela USB.
Através do aplicativo, realizamos a leitura de temperatura e umidade com os dados captados pelo DHT22.





Biblioteca SimpleDHT

Na IDE do Arduino vá em Sketch->Incluir Biblioteca->Gerenciar Bibliotecas...
Instale SimpleDHT




Biblioteca WebServer

Vá em C:\Users\<SEU_USUÁRIO>\Documents\Arduino\hardware\espressif\esp32\libraries e verifique se possui a pasta WebServer. Se não possuir reinstale o core do ESP32 na IDE o Arduino, pois as versões mais novas já vem com a lib.




ESP32_WebServer.ino

Vamos incluir as bibliotecas necessárias e definir o endereço i2c do MCP23017, bem como dos registradores.

#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <SimpleDHT.h>
#include <Wire.h>
#include <FS.h>
#include <SPIFFS.h>

//endereço I2C do MCP23017
#define MCP_ADDRESS 0x20
//ENDEREÇOS DE REGISTRADORES
#define GPA 0x12 // DATA PORT REGISTER A
#define GPB 0x13 // DATA PORT REGISTER B 
#define IODIRA 0x00 // I/O DIRECTION REGISTER A
#define IODIRB 0x01 // I/O DIRECTION REGISTER B
#define PINS_COUNT 16 //Quantidade total de pinos
#define DATA_PATH "/pin_data.bin" //Arquivo onde serão salvos os status dos pinos
#define DHTPIN 5 //Pino one está o DHT22

Temos aqui as variáveis que envolvem ssid, senha e IP. Criamos um server na porta padrão e apontamos o objeto que faz a leitura de temperatura e umidade. Temos ainda as variáveis para guardar esses valores lidos. Seguimos guardando o estado das duas portas do MCP23017 e executamos o controle do temporizador do Watchdog (assista o vídeo sobre watchdog: Travou! E agora?).

const char *ssid = "TesteESP";
const char *password = "12345678";
const char *ip = "192.168.0.154";

//Criamos um server na porta padrão o http
WebServer server(80);

//Objeto que faz a leitura da temperatura e umidade
SimpleDHT22 dht;

//Variáveis para guardar os valores de temperatura e umidade lidos
float temperature = 0;
float humidity = 0;

//Guarda o estado atual das duas portas do MCP23017 (8 bits cada)
uint8_t currentValueGPA = 0;
uint8_t currentValueGPB = 0;

//faz o controle do temporizador do watchdog (interrupção por tempo)
hw_timer_t *timer = NULL; 



ESP32_WebServer.ino - setup

Inicializamos os valores dos pinos do MCP23017, e tratamos da inicialização também do SPIFFS e do WiFi. Definimos que, sempre que recebermos uma requisição na raiz do webserver, a função handleRoot será executada. Inicializamos ainda o server e o watchdog.

void setup()
{
  Serial.begin(115200);

  //Inicializa os valores dos pinos do MCP23017
  setupPins();

  //Tenta inicializar SPIFFS
  if(SPIFFS.begin(true))
  {
    loadPinStatus(); 
  }
  else
  {
    //Se não conseguiu inicializar
    Serial.println("SPIFFS Mount Failed");
  }

  //inicializa WiFi
  setupWiFi();

  //Sempre que recebermos uma requisição na raiz do webserver
  //a função handleRoot será executada
  server.on("/", handleRoot);

  //Se recebermos uma requisição em uma rota que nao existe
  server.onNotFound(handleNotFound);

  //Inicializa o server
  server.begin();

  //Inicializa o watchdog
  setupWatchdog();
}



ESP32_WebServer.ino – setupPins

Inicializamos o Wire nos pinos SDA e SCL padrões do ESP32 e apontamos a velocidade de comunicação. Configuramos, então, todos os pinos das duas portas do MCP23017 como saída.

void setupPins()
{
  //Inicializa o Wire nos pinos SDA e SCL padrões do ESP32
  Wire.begin();
  //Velocidade de comunicação
  Wire.setClock(200000);

  //Configura todos os pinos das duas portas do MCP23017 como saída
  configurePort(IODIRA, OUTPUT);
  configurePort(IODIRB, OUTPUT);
}



ESP32_WebServer.ino – readDHT

Temos aqui uma função para recuperar o estado atual dos pinos no arquivo para que estes permaneçam após um eventual reboot.

//Função para recuperar o estado atual dos pinos no 
//arquivo para que estes permaneçam após um eventual reboot
void loadPinStatus()
{
  //Abre o arquivo para leitura
  File file = SPIFFS.open(DATA_PATH, FILE_READ);
  
  //Se arquivo não existe
  if(!file)
  {
    //Na primeira vez o arquivo ainda não foi criado
    Serial.println("Failed to open file for reading");
    //Coloca todos os pinos das duas portas do MCP23017 em LOW
    writeBlockData(GPA, B00000000);
    writeBlockData(GPB, B00000000);
    return;
  }

  //Faz a leitura dos valores
  file.read(&currentValueGPA, 1);
  file.read(&currentValueGPB, 1);
  //fecha o arquivo
  file.close();

  //Envia os valores para o MCP23017
  writeBlockData(GPA, currentValueGPA);
  writeBlockData(GPB, currentValueGPB);
}



ESP32_WebServer.ino – setupWiFi

Nesta etapa, colocamos como modo station e conectamos à rede. Configuramos o IP e exibimos este endereço para abrir no navegador.

void setupWiFi()
{
  //Coloca como modo station
  WiFi.mode(WIFI_STA);
  //Conecta à rede
  WiFi.begin(ssid, password);
  Serial.println("");

  //Enquanto não conectar
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }

  //Se chegou aqui está conectado
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);

  //Configura o IP
  IPAddress ipAddress;
  ipAddress.fromString(ip);
  WiFi.config(ipAddress, WiFi.gatewayIP(), WiFi.subnetMask());

  //Exibe o endereço de IP para abrir no navegador
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}



ESP32_WebServer.ino – WatchDog

Aqui temos a função que o temporizador irá chamar para reiniciar o ESP32, bem como a função que configura o temporizador.

//função que o temporizador irá chamar, para reiniciar o ESP32
void IRAM_ATTR resetModule(){
  ets_printf("(watchdog) reboot\n");
  esp_restart_noos(); //reinicia o chip
}
 
//função que configura o temporizador
void setupWatchdog()
{
  timer = timerBegin(0, 80, true); //timerID 0, div 80
  //timer, callback, interrupção de borda
  timerAttachInterrupt(timer, &resetModule, true);
  //timer, tempo (us), repetição
  timerAlarmWrite(timer, 5000000, true);
  timerAlarmEnable(timer); //habilita a interrupção //enable interrupt
}



ESP32_WebServer.ino – loop

No Loop, temos a função que reseta o temporizador (alimenta o watchdog) e a que verifica se existe alguma requisição.

void loop()
{
  //reseta o temporizador (alimenta o watchdog)
  timerWrite(timer, 0); 
  //Verifica se existe alguma requisição
  server.handleClient();
}



ESP32_WebServer.ino – handleRoot

Nesta fase, verificamos se a montagem recebeu argumentos na requisição e executamos uma ação. Fazemos a leitura de temperatura e umidade, geramos o html e o enviamos. Por fim, salvamos o status dos pinos para voltar assim no próximo reboot.

void handleRoot()
{
  //Se recebeu argumentos na requisição
  if(server.args() > 0)
  {
    //Executa a ação (on ou off) no pino do argumento
    execute(server.argName(0), server.arg(0));
  }

  //Faz a leitura da temperatura e umidade
  readDHT();
  
  //Gera o html e o envia
  String html = "<html>";
  html.concat(head());
  html.concat(body()); 
  html.concat("</html>");  
  server.send(200, "text/html; charset=UTF-8", html);

  //Salva o status dos pinos para voltar assim no próximo reboot
  savePinStatus();
}



ESP32_WebServer.ino – savePinStatus

Aqui trabalhamos com a função para salvar o estado atual dos pinos em arquivo para que este permaneça após um eventual reboot.

//Função para salvar o estado atual dos pinos em 
//arquivo para que estes permaneçam após um eventual reboot
void savePinStatus()
{
  //Abre o arquivo para escrita
  File file = SPIFFS.open(DATA_PATH, FILE_WRITE);
  
  //Se não conseguiu abrir/criar o arquivo
  if(!file)
  {
    Serial.println("Failed to open file for writing");
    return;
  }

  //Escreve os valores dos pinos no começo do arquivo
  file.seek(0);
  file.write(&currentValueGPA, 1);
  file.write(&currentValueGPB, 1);
  //Fecha o arquivo
  file.close();
}



ESP32_WebServer.ino – handleNotFound

Já esta função serve para enviar para o navegador a informação que a rota não foi encontrada. 


void handleNotFound()
{
  //Envia para o navegador a informação que a rota não foi encontrada
  server.send(404, "text/plain", "Not Found");
}



ESP32_WebServer.ino – execute

Esta, executa a ação junto ao valor (número do relé).

//Executada a ação junto ao valor (número do relê)
void execute(String action, String value)
{
  //Se é uma das duas ações que esperamos
  if(action == "on" || action == "off")
  {
    //Os relês são numerados a partir do 1, mas o array começa do 0
    //então tiramos 1
    int index = value.toInt() - 1;
    int status = action == "on" ? HIGH : LOW;
    digitalWriteMCP(index, status);
  }
}



ESP32_WebServer.ino – head

Retornamos o cabeçalho da página com a informação do tempo para atualizar a página sozinha e a aparência.

//Retorna o cabeçalho da página com a informação do tempo
//para atualizar a página sozinho e a aparência
String head()
{
    return (F("<head>"
    "<meta name='viewport' content='width=device-width, initial-scale=1.0'>"
    "<meta http-equiv='refresh' content='10;URL=/'>" //refresh a cada 10 segundos
    "<style>"
        "body{"
            "text-align: center;"
            "font-family: sans-serif;"
            "font-size: 14px;"
        "}"
        "p{"
            "color:#555;"
            "font-size: 12px;"
        "}"
        ".button{"
            "outline: none;"
            "display: block;"
            "border: 1px solid #555;"
            "border-radius:18px;"
            "width: 150px;"
            "height: 30px;"
            "margin: 10px;"
            "margin-left: auto;"
            "margin-right: auto;"
            "cursor: pointer;"
        "}"
        ".button_off{"
            "background-color:#FFF;"
            "color: #555;"
        "}"
        ".button_on{"
            "background-color:#2C5;"
            "color: #fff;"
        "}"
    "</style>"
"</head>"));
}



ESP32_WebServer.ino – body

Agora, esta função exibe os dados dos sensores e cria os botões, sendo um para cada pino que possui um relé.

//Exibe os dados dos sensores e cria os botões
String body()
{
  String b = "<body>"
    "<p>Temperature: " + String(temperature) + " °C</p>"
    "<p>Humidity: " + String(humidity) + "%</p>";

    //Cria um botão para cada pino que possui um relê
    for(int i=0; i<PINS_COUNT; i++)
    {
        b.concat(button(i));
    }

    b.concat("</body>");

    return b;
}



ESP32_WebServer.ino – button

Temos aqui a criação de um botão com a aparência e ação correspondente ao estado atual do relé.

//Cria um botão com a aparência e ação correspondente ao estado atual do relê
String button(int number)
{
  String label = String(number + 1);
  String className = "button ";
  className += getPinStatus(number) == HIGH ? "button_on" : "button_off";
  String action = getPinStatus(number) == HIGH ? "off" : "on";
  return "<button class=\"" + className + "\"onclick=\"location.href='?" + action + "=" + label + "'\">" + label + "</button>";
}



ESP32_WebServer.ino – getPinStatus

Verificamos se o relé está ligado ou desligado.

uint8_t getPinStatus(int pin)
{
  uint8_t v;

  //de 0 a 7 porta A, de 8 a 15 porta B
  if(pin < 8)
  {
    v = currentValueGPA;
  }
  else
  {
    v = currentValueGPB;
    pin -= 8;
  }

  return !!(v & (1 << pin));
}



ESP32_WebServer.ino – configurePort

Aqui, configuramos o modo dos pinos das portas (GPA ou GPB). Como parâmetro, passamos:
port: GPA ou GPB
type: INPUT para todos os pinos da porta trabalharem como entrada
          OUTPUT para todos os pinos da porta trabalharem como saída

//Configura o modo dos pinos das portas (GPA ou GPB)
//como parametro passamos:
//  port: GPA ou GPB
//  type:
//    INPUT para todos os pinos da porta trabalharem como entrada
//    OUTPUT para todos os pinos da porta trabalharem como saída
void configurePort(uint8_t port, uint8_t type)
{
  if(type == INPUT)
  {
    writeBlockData(port, 0xFF);
  }
  else if(type == OUTPUT)
  {
    writeBlockData(port, 0x00);
  }
}



ESP32_WebServer.ino – digitalWriteMCP

Mudamos o estado de um pino desejado (de 0 a 15), salvamos os valores dos bits da porta correspondente e enviamos os dados para o MCP.

//muda o estado de um pino desejado
void digitalWriteMCP(int pin, int value)
{
  uint8_t port;
  uint8_t v;

  //de 0 a 7 porta A, de 8 a 15 porta B
  if(pin < 8){
    port = GPA;
    v = currentValueGPA;
  }else{
    port = GPB;
    v = currentValueGPB;
    pin -= 8;
  }
    
  if (value == LOW){
    v &= ~(B00000001 << (pin)); // muda o pino para LOW
  }
  else if (value == HIGH){
    v |= (B00000001 << (pin)); // muda o pino para HIGH
  }

  //Salva os valores dos bits da porta correspondente
  if(port == GPA){
    currentValueGPA = v;
  }else{
    currentValueGPB = v;
  }

  //envia os dados para o MCP
  writeBlockData(port, v);
}



ESP32_WebServer.ino – writeBlockData

Enviamos, então, os dados para o MCP23017 através do barramento i2c.

//envia dados para o MCP23017 através do barramento i2c
void writeBlockData(uint8_t port, uint8_t data)
{
  Wire.beginTransmission(MCP_ADDRESS);
  Wire.write(port);
  Wire.write(data);
  Wire.endTransmission();
  delay(10);
}



ESP32_WebServer.ino – readDHT

Executamos a leitura da temperatura e umidade 

//Faz a leitura da temperatura e umidade
void readDHT()
{
  float t, h;
  int status = dht.read2(DHTPIN, &t, &h, NULL);

  //Apenas altera as variáveis se a leitura foi bem sucedida
  if (status == SimpleDHTErrSuccess)
  {
    temperature = t;
    humidity = h;
  }
}




Faça o download dos arquivos:


2 comentários:

  1. Boa noite ; obrigado por este projeto vou montar ele.
    E obrigado pelas aulas , é um aprendizado muito bom.

    ResponderExcluir
  2. Bom dia Fernando, estou a alguns dias acompanhando os seus vídeos, são muito úteis, bem estruturados, muito bem explicados, parabéns e obrigado por disponibilizar seu tempo em preparar estes vídeos que nos trazem muitas e importantes informações. Grande abraço e sucesso.

    ResponderExcluir

Tecnologia do Blogger.