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.
0 Comentários