Recents in Beach


Receba o meu conteúdo GRATUITAMENTE


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:


Postar um comentário

23 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
  3. Boa tarde fernandok . Fiz esse projeto com o esp3266 da marca lolin ,mas ta dando erro na copilação sera que a pinagem é diferente, vc pode me ajudar ?

    ResponderExcluir
  4. Boa noite FernandoK, sou novo em automação mas preciso terminar um projeto para escola. Consigo controlar reles e servo no mesmo codigo?Com a Esp32

    ResponderExcluir
  5. carlos costa27 de setembro de 2018 14:02
    Boa tarde fernandok . Fiz esse projeto com o esp3266 da marca lolin ,mas ta dando erro na copilação sera que a pinagem é diferente, vc pode me ajudar ?

    ResponderExcluir
  6. Boa noite, Fernando, montei esse projeto mas ligando o resistor 4.7k no 3.3 junto com o pinto de sinal do mcp23017 ele para de funcionar e se ligar sem esse resistor funciona, mas as vezes os reles param de operar, tem ideia do que pode ser? obrigado

    ResponderExcluir
    Respostas
    1. o meu está funcionando de boa, só não entendo porque neste projeto liga-se os resistores de 4,7K, enquanto no outro projeto usando mcp23016 não precisou dos resistores no canal i2c, alguem sabe responder ?? grato

      Excluir
  7. poderia explicar como usa esse mesmo projeto mas usando o node red ?

    ResponderExcluir
  8. Ótimo projeto! Mas e se eu quiser colocar 16 pulsadores, para poder acionar o relé, estou precisando dessa parte... Onde o relé pode ser acionado via web, mas caso tiver uma interação pelo pulsador ele atualizar o status do rele!
    Poderia ligar em triway, mas no final nunca iria saber se a lâmpada estaria realmente ligada ou não...

    ResponderExcluir
  9. montei td igual, e não esta funcionando, única coisa que tiver que mudar foi na linha 150, "esp_restart_noos();" para "esp_restart();" mas ao acessar o ip não esta aparecendo temperatura e nem acionando os relés

    ResponderExcluir
    Respostas
    1. Tive o mesmo problema, mais consegui resolver através da tua pergunta.

      Excluir
    2. Eu também tive o mesmo problema e resolvemos assim.

      Excluir
    3. Foi o único problema que também encontrei, mas foi resolvido graças a sua dica, Rafael. Valeu!

      Excluir
  10. Boa tarde
    no codigo quando mando verificar a biblioteca #include , aparece em destaque acredito que seja por não ter no PC, alguém sabe onde posso baixar ??
    Antonio Sousa

    ResponderExcluir
  11. Ops!! faltou qual biblioteca,

    #include

    Att
    Anotonio Sousa

    ResponderExcluir
  12. ???????? não esta indo ????????

    SPIFFS.h

    ResponderExcluir
  13. Alguem sabe dizer se funciona no ESP8266?????

    ResponderExcluir
  14. Desistam.. ele não responde Mais, nem aqui, nem no Fórum, nem no Youtube..

    ResponderExcluir
  15. good morning, i have done the project and work well, only mod i made is on watchdog instruction, i put the new one set, but i need to change name at the buttons, can you help me?
    thanks Giovanni from italy

    ResponderExcluir
  16. Boa noite Sr. Fernando estou acompanhando seus videos gostaria de saber se consigo fazer com esp 32 esse projeto com comando de voz no Google assistente desde já parabenizalo pelos videos

    ResponderExcluir
  17. alguem poderia comentar para que servem aqueles resistores ligados às portas i2c ? ainda estou engatinhando na eletronica digital, vejo alguns projetos do proprio canal usando o mcp23016 sem resistor, este projeto com mcp23017 usando resistor, não entendi porque.. grato

    ResponderExcluir
  18. Primeiramente parabéns pelo conteúdo me ajudou muito em meu projeto.
    Eu implementei o projeto acima da mesma forma, mas estou tendo problemas com o modulo rele, não sei se é alguma interferência ou outra coisa mas depois de um tempo com o sistema funcionando alguns reles que não estavam armados começam a ficar meio loucos armando e desarmando muito rapidamente depois param e ficam o mesmo estado de outro rele. Exemplo estou apenas com o rele 14 armado após algum tempo o rele 16 começa a se comportar de forma estranha e depois ele segue o mesmo sinal que o 14, então se eu desarmo o rele 14 o 16 também desarma se eu armo o rele 14 o 16 também sofre a mesma ação e vice e versa se faço a mesma atividade com o rele 16 o 14 também sobre a mesma ação como se o pino de sinal dos dois estivessem interligados.
    O mais estranho é, se removo o fio de sinal do pino 16 mesmo assim ao ativar o rele 14 o 16 também é ativado.
    Se alguém puder me ajudar ficarei muito grato. Obrigado.

    ResponderExcluir