banner

Automação de Toldo Elétrico



Pessoal, quero falar de um projeto de automação hoje que eu mesmo acabei aplicando na minha casa: um toldo motorizado com ESP32 e FireBase.





RECURSOS USADOS

  • ESP32 Wroom 32
  • Módulo duplo de relés
  • Fonte 5V 10W
  • Chave On/Off/On
  • Toldo retrátil motorizado
  • Rede WiFi com acesso a internet
  • Resistores (2x 10k e 1x 280 ohms)
  • Resistores de potência (2x de 1ohm x 5W)
  • Led vermelho
  • Fios




CIRCUITO

As conexões do ESP32





CÓDIGO-FONTE

Declarações


//Bibliotecas para WiFi e OTA
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <Update.h>

//Biblioteca com as funçoes do Firebase
#include <IOXhop_FirebaseESP32.h>

//Biblioteca com as funções de manipulação de JSON
#include <ArduinoJson.h>

// URL da base de dados fornecido pelo Firebase para a conexão http
#define FIREBASE_HOST  "https://controle-toldo.firebaseio.com/"
#define FIREBASE_AUTH  "" // Autenticação

const char *ssid     = "Seu SSID"; // Coloque o nome da sua rede wifi aqui
const char *password = "Sua Senha"; // Coloque a sua senha wifi aqui

const int ReleAtivar = 18; //pino do relé para abrir toldo (lógica invertida no módulo)
const int ReleDirecao = 19; //pino do relé para fechar toldo (lógica invertida no módulo)
const int Led = 2; //pino do Led

const int btnAbrir = 22;//pino do botão de abertura
const int btnFechar = 23;//pino do botão de fechamento

const String PATH_STATUS = "/Status"; //Caminho padrão para os comandos no Firebase

int timeOut = 10; //limite de tempo de acionamento do motor
bool btnAbrir_executado = true; //indica se o comando da chave "Abrir" foi executado
bool btnFechar_executado = true; //indice se o comando da chave "Fechar" doi executado

//marca os instantes para controle de tentativas de conexão com o WiFi
//Valor máximmo alcançado em 50 dias de atividade ((2^32)-1) milissegundos
unsigned long instante = 0;

//intervalo para tentar reconectar no WiFi
unsigned long IntervaloParaTentarReconectar = 2 * 1000;

//Sinaliza a carga do FireBase
bool FireBaseIniciado = false;


Páginas html (loginIndex)

// Página HTML para login
const char* loginIndex =
  "<head><meta charset='utf-8'></head>"
  "<form name='loginForm' method='post'>"
  "<table width='30%' bgcolor='00AAAA' align='center'>"
  "<tr>"
  "<td colspan=2>"
  "<center><font size=8><b>Login para ESP32 Controle do Toldo v1.0</b></font></center>"
  "</td>"
  "</tr>"
  "<td><font size=5>Usuário:<font size=5></td>"
  "<td><input type='text' size=50 name='userid'></td>"
  "</tr>"
  "<tr>"
  "<td><font size=5>Senha:<font size=5></td>"
  "<td><input type='Password' size=50 name='pwd'></td>"
  "</tr>"
  "<tr><td></td></tr>"
  "<tr>"
  "<td></td>"
  "<td><input type='submit' onclick='check(this.form)' value='Login'></td>"
  "<td></td>"
  "</tr>"
  "</table>"
  "</form>"
  "<script>"
  "function check(form)"
  "{"
  "if(form.userid.value=='usuario' && form.pwd.value=='senha')"
  "{"
  "window.open('/serverIndex')"
  "}"
  "else"
  "{"
  " alert('Nome ou senha inválidos')/*mostra uma mensagem de erro.*/"
  "}"
  "}"
  "</script>";


Código-fonte: Páginas html (serverIndex)

//Página do servidor
const char* serverIndex = 
"<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
   "<input type='file' name='update'>"
        "<input type='submit' value='Update'>"
    "</form>"
 "<div id='prg'>progress: 0%</div>"
 "<script>"
  "$('form').submit(function(e){"
  "e.preventDefault();"
  "var form = $('#upload_form')[0];"
  "var data = new FormData(form);"
  " $.ajax({"
  "url: '/update',"
  "type: 'POST',"
  "data: data,"
  "contentType: false,"
  "processData:false,"
  "xhr: function() {"
  "var xhr = new window.XMLHttpRequest();"
  "xhr.upload.addEventListener('progress', function(evt) {"
  "if (evt.lengthComputable) {"
  "var per = evt.loaded / evt.total;"
  "$('#prg').html('progress: ' + Math.round(per*100) + '%');"
  "}"
  "}, false);"
  "return xhr;"
  "},"
  "success:function(d, s) {"
  "console.log('success!')" 
 "},"
 "error: function (a, b, c) {"
 "}"
 "});"
 "});"
 "</script>";

WebServer server(80); //inicia o servidor



Setup()

void setup()
{
  //Prepara a saída serial
  Serial.begin(115200);

  //Executa o setup do OTA (Over The Air updates)
  OTAsetup();

  //Ajusta as direções dos GPIO's
  pinMode(ReleAtivar, OUTPUT);
  pinMode(ReleDirecao, OUTPUT);
  pinMode(Led, OUTPUT);

  //Inicia os pinos do Relé em nível alto, já que os relés operam em lógica invertida
  digitalWrite(ReleAtivar, HIGH);
  digitalWrite(ReleDirecao, HIGH);

  digitalWrite(Led, LOW); //inicia o Led de indicação
}


OTAsetup()

void OTAsetup()
{
  // Conecta no WiFi
  WiFi.begin(ssid, password);
  Serial.println("");

  //aguarda pela conexão
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Conectado em ");
  Serial.println(ssid);
  Serial.print("Endereço IP: ");
  Serial.println(WiFi.localIP());

//O servidor responde a solicitação de conexão
//enviando a página "loginIndex"
  server.on("/", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", loginIndex);
  });
  
//O servidor responde a solicitação de conexão
//enviando a página "serverIndex"
  server.on("/serverIndex", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", serverIndex);
  });
  
//Controla o Upload do firmware
  server.on("/update", HTTP_POST, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/plain", (Update.hasError()) ? "ERRO" : "OK");
    ESP.restart();
  }, []() {
    HTTPUpload& upload = server.upload();
    if (upload.status == UPLOAD_FILE_START) {
      Serial.printf("Atualizando: %s\n", upload.filename.c_str());
      if (!Update.begin(UPDATE_SIZE_UNKNOWN)) {
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_WRITE) {
      //Grava o firmware no ESP
      if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_END) {
      //Sinaliza o fim do UPDATE
      if (Update.end(true)) {
        Serial.printf("Atualização bem sucedida: %u\nReiniciando...\n", upload.totalSize);
      } else {
        Update.printError(Serial);
      }
    }
  });

  //inicia o servidor
  server.begin();
}


Loop ()

void loop()
{
  verificaWiFi();
  verificaFireBase();
  verificaOsBTNS();
}


verificaWiFi()

//verifica a conexão WiFi
void verificaWiFi()
{

  //Enquanto não conectado, faz o Led indicador de rede piscar e envia uma mensagem pela serial
  //(Na inicialização, tentará conectar-se apenas durante aproximadamente 20s, depois desistirá.)

  verificaInstante();
  while ((WiFi.status() != WL_CONNECTED) and ((millis() - instante) < IntervaloParaTentarReconectar))
  {
    Serial.println("Conectando ao WiFi ..." + String(((IntervaloParaTentarReconectar - (millis() - instante)) / 1000)) + "s");
    WiFi.begin(ssid, password);
    delay(1000);
    piscarLed(100);
  }

  delay(2000); //Aguarda 2s
}


verificaInstante()

//Verifica se o instante está próximo do overflow
//e se o ESP está executando a muito tempo
void verificaInstante()
{
  // reinicia o ESP duas vezes por dia
  const unsigned long intervaloDeRestart = 12 * 60 * 60 * 1000;

  instante = millis();
  //if (instante > (((2 ^ 32) - 1) - (60 * 1000)))
  if (instante > intervaloDeRestart)
  {
    Serial.println("ESP será reiniciado devido ao longo período de funcionamento initerrupto.");
    delay(2000);
    ESP.restart();
  }
}


verificaFireBase ()

//Verifica a carga do FireBase
void verificaFireBase()
{
  if ((WiFi.status() == WL_CONNECTED) and (FireBaseIniciado == false))
  {
    //inicia a conexão com o Firebase
    Firebase.begin(FIREBASE_HOST, FIREBASE_AUTH);
    FireBaseIniciado = true;

    //Inicia a função de callback usando o caminho de COMANDOS
    Firebase.stream(PATH_STATUS, [](FirebaseStream stream)
    {
      String path, value; //Variáveis que recebem o caminho e valor retornado pelo callback

      // Se o evento que vem do callback é de alteração "put"
      if (stream.getEvent() == "put")
      {
        // Obtemos os valores de path e value
        path = stream.getPath();
        value = stream.getDataString();

        if (path.equals("/")) //se é o callback inicial quando a conexão é estabelecida
        {
          // Sincronizamos o valor de timeout
          syncFirebase(value); //executa a função que interpreta o valor recebido e atualiza o timeout
          Serial.println("Firebase sincronizado"); //envia a mensagem pela serial de sincronização
        }

        //Serial.println(path + " - " + value); //imprime na serial o caminho e o valor recebido (para debug)

        if (path == "/aberto") //verifica o estado do sinalizador de estado do toldo
        {
          if (value == "true") //se o toldo deve estar aberto ...
          {
            Abrir();
          }
          if (value == "false")//se o toldo deve estar fechado ...
          {
            Fechar();
          }
        }
        if (path == "/timeout") //verifica o timeout
        {
          timeOut = value.toInt(); //obtém o valor atual
          if (timeOut > 60) timeOut = 60; //valor máximo peermitidopara o timeOut
          Serial.println("Timeout:" + String(timeOut) + " (máx. 60s)"); //envia para a serial o novo valor
        }
      }
    });
  }
}


abrir()

//Executa a abertura e sincronização
void Abrir ()
{
  if ((digitalRead(ReleAtivar) == HIGH) and (digitalRead(ReleDirecao) == LOW)) //.... e se o estado dos relés estão corretos....
  {
    digitalWrite(ReleDirecao, HIGH); //ajusta o relé de direção para Abrir
    Serial.println("DIREÇÃO: ABRIR"); //envia para a serial qual será a execução.
    delay(500); //aguarda meio segundo

    int instante = millis(); //inicia o cronômetro para o timeout
    while ((millis() - instante) < timeOut * 1000) //verifica o estouro do timeout
    {
      digitalWrite(ReleAtivar, LOW); //mantem acionado o motor no sentido de abrir
      piscarLed(250);//pisca o led a cada meio segundo para indicar a execução
    }
    digitalWrite(ReleAtivar, HIGH); //desativa o motor no sentido de abrir
    if ((WiFi.status() == WL_CONNECTED) and (FireBaseIniciado == true)) atualizaFireBase("true");
  }
}


fechar()

//executa o fechamento e sincronização
void Fechar()
{
  if ((digitalRead(ReleAtivar) == HIGH) and (digitalRead(ReleDirecao) == HIGH)) //... e se o estado dos relés estão corretos....
  {
    digitalWrite(ReleDirecao, LOW); //ajusta o relé de direção para Fechar
    Serial.println("DIREÇÃO: FECHAR"); //envia para a serial qual será a execução.
    delay(500);//aguarda meio segundo

    int instante = millis(); //inicia o cronômetro para o timeout
    while ((millis() - instante) < timeOut * 1000) //verifica o estouro do timeout
    {
      digitalWrite(ReleAtivar, LOW); //mantem acionado o motor no sentido de fechar
      piscarLed(250);//pisca o led a cada meio segundo para indicar a execução
    }
    digitalWrite(ReleAtivar, HIGH); //desativa o motor no sentido de fechar
    if ((WiFi.status() == WL_CONNECTED) and (FireBaseIniciado == true)) atualizaFireBase("false");
  }
}


atualizarFirebase()

//Atualiza o FireBase com os novos estados
void atualizaFireBase(String estado)
{
  StaticJsonBuffer<1> jsonBufferEstado; //reserva de memória para o objeto JSON que contem os dados dos sensores
  JsonObject& novoEstado = jsonBufferEstado.createObject(); //cria o objeto JSON que conterá os dados dos sensores

  Firebase.set("/Status/aberto", estado); //envia para o Firebase
  Firebase.set("Status/IP", WiFi.localIP().toString());

  jsonBufferEstado.clear(); //limpa os objetos JSON criados
}


syncFirebase()

// Função que sincroniza o valor do timeOut
//e Direção do ESP32 com o Firebase
void syncFirebase(String value)
{

  Serial.println("Sincronizando . . . ");
  Serial.println("Recebido: " + value);
  if (value.indexOf("true") != -1) digitalWrite(ReleDirecao, HIGH); //ultimo comando foi abrir
  if (value.indexOf("false") != -1) digitalWrite(ReleDirecao, LOW); //ultimo comando foi fechar

  int indice = value.lastIndexOf(":") + 1; //procura pela 2ª string ":" no valor recebido
  Serial.println("INDICE: " + String(indice));
  value.remove(0, indice); //remove toda string antes da segunda ":" do valor recebido
  value.remove(value.length(), 1); //remove a "}" chave no fim da string
  Serial.println("TIMEOUT RECEBIDO: " + value); //envia para a serial o valor recebido (para debug)
  timeOut = value.toInt(); //converte a string para um inteiro
  if (timeOut > 60) timeOut = 60; //valor máximo peermitidopara o timeOut
  Serial.println("TIMEOUT: " + String(timeOut) + " (máx. 60s)"); //envia para a serial o novo valor
}


piscarLed()

//pisca o led a cada 2x o valor de meioCiclo
void piscarLed(int meioCiclo)
{
  digitalWrite(Led, HIGH);
  delay(meioCiclo);
  digitalWrite(Led, LOW);
  delay(meioCiclo);
}




BANCO DE DADOS NO FIREBASE

Já existe no canal um vídeo exemplificando como criar um banco de dados no Firebase, para mais informações assista-o: https://www.youtube.com/watch?v=lQorVoYQk84

Aqui vamos mostrar apenas como ficou o Banco de Dados que utilizaremos como controle e mantenedor do estado do projeto.

Acessando o FireBase, podemos visualizar nosso projeto, chamado de Controle-Toldo.



Na página inicial do projeto, encontramos as informações de utilização. Podemos então acessar o banco de dados em si, clicando em Database...



... e selecionando Realtime Database.


No detalhe, observamos também o endereço utilizado pelo firmware do ESP32 para acessar o banco.

O banco de dados é formado apenas por três campos. Em um deles vemos o registro do estado do toldo (aberto true ou false). Se este for alterado no FireBase, essa mudança se refletira no estado real no toldo.
Da mesma forma que uma atuação sobre a chave, alterará o estado do toldo e consequentemente no registro do FireBase, mantendo todo o conjunto sincronizado.
O mesmo vale para o registro do timeout do motor, que o impede de ficar ligado indefinidamente.
Temos também o endereço IP para acessar a página do OTA.





CAPTURA DAS SAÍDAS SERIAIS

Aqui vemos algumas informações transmitidas pela serial durante a operação.



Em detalhes, primeiro a inicialização normal do ESP32...



... a sincronização que ocorre toda vez que o ESP32 é iniciado...



... e aqui um comando de abertura, outro de fechamento, seguidos de dois comando de alteração do valor de timeout.



Temos também as saídas seriais durante a atualização via OTA.





CAPTURA DAS PÁGINAS DO OTA

Vemos aqui a página principal do OTA, uma tela de login simples, mas que pode ser substituída por um html mais sofisticado. Depois de acessar, teremos a página para seleção do arquivo binário que desejamos usar na atualização.



Página de seleção do arquivo e geração do arquivo no Arduino IDE.





USANDO O APLICATIVO FERNANDOK

Aplicativo FernandoK

Para simplificar a operação, vamos utilizar o Aplicativo FernandoK para enviar os comandos.

O aplicativo pode ser encontrado na PlayStore.

Ao iniciar o aplicativo, temos que indicar uma conexão. Para isso, clicamos no ícone de configurações...




Preenchemos com as informações necessárias, como o tipo de conexão, o nome da conexão e o caminho para a conexão.



Uma vez que a conexão foi criada, temos que ativá-la. Para isso basta clicar sobre a conexão ou arrastar para a esquerda para que as opções sejam mostradas.



Uma vez conectado, devemos receber algumas informações do FireBase..



Vamos enviar alguns comandos para testar.



Vamos adicionar alguns componentes para facilitar o envio de comandos. Para isso, acessamos novamente o menu, e clicamos em Add Components. Depois, basta preencher os campos com as informações necessárias, como nome do componente, o comando a ser enviado.



Criando os botões para abrir e fechar o toldo, como no exemplo abaixo.



Para criar um Slider, siga o exemplo. Atenção para o título do slider, este deve ser igual ao campo do FireBase



Depois de adicionar os componentes, estes deverão aparecer empilhados na tela. Acesse novamente o menu e selecione a opção Enable DragButton. Isso permitira o reposicionamento dos componentes.






 FAÇA O DOWNLOAD DOS ARQUIVOS:

PDF

INO







Nenhum comentário:

Tecnologia do Blogger.