Esse é um tutorial de como
utilizar o AppFernandoK, um aplicativo genérico de automação para Android, que
desenvolvi com o intuito de te possibilitar a automatização de uma infinidade
de coisas, isso empregando o ESP32 ou o ESP8266. Ainda trago neste vídeo um
exemplo multiclient com ESP32 com envio e recebimento de mensagens para o
aplicativo.
Clique AQUI e baixe o App
Demonstração
Utilizando o aplicativo
Ícone
Home
A tela inicial é composta pelos
seguintes componentes
Configurando nova conexão
1. Selecione
configurações
2. Selecione
Connection
Clique no botão para adicionar
Preencha os campos
Um novo item da lista de conexões
será exibido conforme a figura 1, selecione a conexão conforme a figura 2 e
retorne para a tela inicial clicando no X.
Envio via Terminal
Digite um *comando no campo de
texto e clique em OK
*Os comandos devem estar
devidamente programados no código fonte do ESP32, tanto para saída dos relés,
quanto para envio de resposta para o aplicativo.
Criando um botão
1. Selecione
Configurações
2. Selecione
Add Button
Criando scripts (botões)
Preencha os campos conforme a
imagem
*A lista de comandos
reservados (como o Wait) está disponível no manual do usuário do aplicativo
Um novo botão será criado
conforme a imagem
Você pode habilitar a
movimentação de botões clicando em EnableDragButton
Um ícone de setas será exibido
indicando a movimentação do objeto.
Para editar/excluir o botão
arraste para a engrenagem localizada no canto superior direito.
Botões utilizados no nosso exemplo
Os botões usados no nosso
exemplo são os destacados na imagem
Montagem
Recursos usados
- ESP32 WROOM Dev Module
- Display TFT 1.8’’
- Módulo de 4 relés
- BME280
- Reed Switch
- Imã de neodímio
- Resistor de 10k ohm
- Smartphone
- Aplicativo AppFernandoK
- Jumpers
Pinout
Código
Instalação de Bibliotecas
Adafruit_GFX
Adafruit_ST7735
Adafruit_BME280
Diagrama
Task responsável por ler o
sensor reed switch e enviar avisos aos clientes caso o sensor seja ativado.
Task responsável por adicionar
os novos clientes na lista de clientes conectados (vector) utilizada pelo ESP.
Task responsável por ler os
comandos recebidos dos clientes, executar uma ação e enviar uma resposta.
A função loop é responsável
por ler o sensor de temperatura e umidade BME280 e exibir os valores no display
a cada 5 segundos, além de exibir a quantidade de clientes conectados.
Declarações e variáveis
#include <Arduino.h> //Lib Arduino (opcional) #include <Adafruit_GFX.h> // Biblioteca de gráficos #include <Adafruit_ST7735.h> // Biblioteca do hardware ST7735 #include <Fonts/FreeSerif9pt7b.h> // Fonte Serif que é usada no display #include <WiFi.h> // Lib WiFi #include <Wire.h> // Necessário apenas para o Arduino 1.6.5 e posterior #include <SPI.h> // Lib de comunicação SPI #include <Adafruit_BME280.h> // Lib do sensor BME #include <vector> // Lib com as funções de vetor (vector) const char* ssid = "SSID"; // Coloque o nome da sua rede wifi aqui const char* password = "PASSWORD"; // Coloque a sua senha wifi aqui const IPAddress IP = IPAddress(111,111,1,111); // IP fixo que o ESP utilizará const IPAddress GATEWAY = IPAddress(111,111,1,1); // Gateway const IPAddress SUBNET = IPAddress(255,255,255,0); // Máscara const int port = 80; // Porta // Objeto WiFi Server, o ESP será o servidor WiFiServer server(port); // Vetor com os clientes que se conectarão no ESP std::vector<WiFiClient> clients; // Objeto do sensor BME280 Adafruit_BME280 bme; //SDA = GPIO21, SCL = GPIO22 // ESP32-WROOM #define TFT_CS 12 // CS #define TFT_DC 14 // A0 #define TFT_MOSI 27 // SDA #define TFT_CLK 26 // SCK #define TFT_RST 0 // RESET #define TFT_MISO 0 // MISO // Objeto do display tft Adafruit_ST7735 display = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_MOSI, TFT_CLK, TFT_RST); // Pinos ligados nos relés const int relay1 = 17, relay2 = 5, relay3 = 18, relay4 = 19; // Pino ligado no reed switch const int reedSwitch = 15; // Variável que obtém a temperatura e umidade String climate = ""; // Flag que indica se o display está ocupado bool displayIsBusy = false; // Altura da fonte que é usada no display (9 + 5 espaçamento) int fontHeight = 14; // Task de leitura do sensor reed switch void taskReedSwitch(void *); // Task que insere novos clientes (recém conectados) no vector void taskNewClients(void *); // Task que recebe executa comandos dos clientes void handleClients(void *);
Setup
void setup() { // Iniciamos a serial com 9600 de velocidade Serial.begin(9600); // Iniciamos o display com fundo preto display.initR(INITR_BLACKTAB); // Exibimos a mensagem "Starting..." showDisplay("Starting...", true); // Configuramos os pinos dos relés e reed switch (pinMode) setPins(); // Iniciamos o sensor BME280 bmeBegin(); // Iniciamos o servidor (WiFi Server) serverBegin(); // Criamos 3 tasks (mais detalhes no escopo da função) createTasks(); }
Loop
void loop() { // Exibimos o total de clientes conectados showClients(); // Lemos o sensor BME280 obtendo a temperatura e umidade readBMESensor(); // Aguardamos 5 segundos delay(5000); }
Setup - createTasks
// Criamos as 3 Tasks void createTasks() { //Criamos a task de leitura do reed switch xTaskCreatePinnedToCore ( taskReedSwitch, //Função que será executada "readReedSwitch", //Nome da tarefa 10000, //Tamanho da pilha NULL, //Parâmetro da tarefa 2, //Prioridade da tarefa NULL, //Caso queria manter uma referência para a tarefa que vai ser criada (no caso não precisamos) 0 //Número do core que será executada a tarefa (usamos o core 0 para o loop ficar livre com o core 1) ); //Criamos a task que insere os novos clientes no vector xTaskCreatePinnedToCore(taskNewClients, "taskNewClients", 10000, NULL, 2, NULL, 0); //Criamos a task que recebe e executa os comandos dos clients conectados xTaskCreatePinnedToCore(handleClients, "handleClients", 10000, NULL, 2, NULL, 0); }
Task 1: Core 0 – taskReedSwitch
// Task que lê o reed switch void taskReedSwitch(void* p) { // Valor que representa 1ms, usado nos vTaskDelays TickType_t taskDelay = 1 / portTICK_PERIOD_MS; // O loop deve ser infinito (para chamarmos constantemente o vTaskDelay que, por sua vez, alimenta o Watchdog e evita a reinicialização indesejada do ESP pelo Watchdog) while (true) { // Se o reed switch estiver com sinal alto if(digitalRead(reedSwitch) == HIGH) { // Exibe mensagem na serial e no display Serial.println("reed high"); showDisplay("Reed switch\nActivated!", true); // Envia a mensagem "The sensor has been activated!" para todos os clientes conectados for(int i=0; i<clients.size(); i++) { if(clients[i]) clients[i].print("The sensor has been activated!"); vTaskDelay(taskDelay); } // Aguardamos até que o sensor seja desativado while(digitalRead(reedSwitch) == HIGH) vTaskDelay(taskDelay); } else { // Se o sensor for desativado, exibimos na serial Serial.println("reed low"); // E aguardamos até que ele volte a ser ativado while(digitalRead(reedSwitch) == LOW) vTaskDelay(taskDelay); } // Importante: A task deve conter um delay para alimentar o Watchdog vTaskDelay(taskDelay); } }
Task 2: Core 0 – taskNewClients
// Task que insere novos clientes conectados no vector void taskNewClients(void *p) { // Objeto WiFiClient que receberá o novo cliente conectado WiFiClient newClient; // Tempo esperado no delay da task (1 ms) TickType_t taskDelay = 1 / portTICK_PERIOD_MS; while(true) { // Se existir um novo client atribuimos para a variável newClient = server.available(); // Se o client for diferente de nulo if(newClient) { // Inserimos no vector clients.push_back(newClient); // Exibimos na serial indicando novo client e a quantidade atual de clients Serial.println("New client! size:"+String(clients.size())); } // Aguardamos 1ms vTaskDelay(taskDelay); } }
Task 3: Core 0 – handleClients
// Função que verifica se um cliente enviou um comando void handleClients(void *p) { // String que receberá o comando String cmd; // Tempo aguardado pela task (1 ms) TickType_t taskDelay = 1 / portTICK_PERIOD_MS; // Loop infinito while(true) { // Atualizamos o vector deixando somente os clientes conectados refreshConnections(); // Percorremos o vector for(int i=0; i<clients.size(); i++) { // Se existir dados a serem lidos if(clients[i].available()) { // Recebemos a String até o '\n' cmd = clients[i].readStringUntil('\n'); // Executamos um comando, enviando por parametro a String cmd, e o client que nos enviou o comando executeCommand(cmd, clients[i]); } // Delay de 1 ms vTaskDelay(taskDelay); } // Delay de 1 ms vTaskDelay(taskDelay); } }
RefreshConnections (função chamada pela task handleClients)
// Função que verifica se um ou mais clients se desconectaram do server e, se sim, estes clients serão retirados do vector void refreshConnections() { // Flag que indica se pelo menos um client ser desconectou bool flag = false; // Objeto que receberá apenas os clients conectados std::vector<WiFiClient> newVector; // Percorremos o vector for(int i=0; i<clients.size(); i++) { // Verificamos se o client está desconectado if(!clients[i].connected()) { // Exibimos na serial que um cliente se desconectou e a posição em que ele está no vector (debug) Serial.println("Client disconnected! ["+String(i)+"]"); // Desconectamos o client clients[i].stop(); // Setamos a flag como true indicando que o vector foi alterado flag = true; } else newVector.push_back(clients[i]); // Se o client está conectado, adicionamos no newVector } // Se pelo menos um client se desconectou, atribuimos ao vector "clients" os clients de "newVector" if(flag) clients = newVector; }
ExecuteCommand (função chamada pela task handleClients)
// Função que executa um comando de acordo com a String "cmd" void executeCommand(String cmd, WiFiClient client) { // String que guarda a resposta que será enviada para o client String response = ""; // Vetor com os 4 pinos dos relés usados neste exemplo, que facilitará a escrita do código fonte int relays[4] = {relay1, relay2, relay3, relay4}; // Se a String estiver vazia, abortamos a função if (cmd.equals("")) return; // Exibimos o comando recebido na serial e no display Serial.println("Recebido: ["+cmd+"]"); showDisplay("Command\nreceived:", true); showDisplay(cmd, false); // Deixamos a string toda em maiúsculo cmd.toUpperCase(); // Se o comando for igual a status if(cmd.equals("STATUS")) response = getStatus(); // Montamos a String de resposta com o status de cada relé else if(cmd.equals("RELAYS ON")) // Se for igual a Relays on, ligamos todos os relés { for(int i=0; i<4; i++) digitalWrite(relays[i], LOW); // Lógica inversa response = "OK"; } else if(cmd.equals("RELAYS OFF")) // Se for igual a Relays off, desligamos todos os relés { for(int i=0; i<4; i++) digitalWrite(relays[i], HIGH); response = "OK"; } else // Se for igual a relay 1 on, relay 1 off, relay 2 on... e assim por diante, executamos uma ação de acordo com o comando if(cmd.equals("RELAY 1 ON") || cmd.equals("RELAY 2 ON") || cmd.equals("RELAY 3 ON") || cmd.equals("RELAY 4 ON") || cmd.equals("RELAY 1 OFF") || cmd.equals("RELAY 2 OFF") || cmd.equals("RELAY 3 OFF") || cmd.equals("RELAY 4 OFF")) { int index = atoi((cmd.substring(cmd.indexOf(" ")+1, cmd.indexOf(" ")+2)).c_str()); if(cmd.indexOf("ON")>=0) digitalWrite(relays[index-1], LOW); else digitalWrite(relays[index-1], HIGH); response = "OK"; } else // Se for igual a climate, atribuimos a String response a última leitura de temperatura e umidade feita pelo BME280 if(cmd.equals("CLIMATE")) response = climate; else response = "Invalid command"; // Se não for nenhum dos comandos acima, retornamos "Comando inválido" // Exibimos a resposta na serial (debug) Serial.println("Resposta: ["+response+"]"); // Enviamos para o client passado por parâmetro e exibimos sucesso ou falha na serial if(client.print(response)>0) Serial.println("Resposta enviada"); else Serial.println("Erro ao enviar resposta"); }
GetStatus (função chamada por “executeCommand”)
// Função que lê os pinos dos relés e retorna o estado atual em uma String String getStatus() { String status = "Relay 1: "; if(digitalRead(relay1) == LOW) status+="ON"; else status+="OFF"; status += "\n Relay 2: "; if(digitalRead(relay2) == LOW) status+="ON"; else status+="OFF"; status += "\n Relay 3: "; if(digitalRead(relay3) == LOW) status+="ON"; else status+="OFF"; status += "\n Relay 4: "; if(digitalRead(relay4) == LOW) status+="ON"; else status+="OFF"; return status; }
Setup – setPins
// Configuramos os pinos INPUT e OUTPUT void setPins() { pinMode(reedSwitch, INPUT); pinMode(relay1, OUTPUT); pinMode(relay2, OUTPUT); pinMode(relay3, OUTPUT); pinMode(relay4, OUTPUT); digitalWrite(relay1, HIGH); digitalWrite(relay2, HIGH); digitalWrite(relay3, HIGH); digitalWrite(relay4, HIGH); }
Setup - bmeBegin
// Iniciamos o sensor BME280 exibindo sucesso ou falha void bmeBegin() { //0x76 = o pino SD0 do sensor deve ser ligado no GND //0x77 = o pino SD0 do sensor deve ser ligado no VCC if (!bme.begin(0x76)) { Serial.println("Sensor bme failed!"); showDisplay("Sensor bme failed!", false); } else { Serial.println("Sensor bme ok"); showDisplay("Sensor bme ok", false); } }
Setup - serverBegin
// Conectamos no WiFi e iniciamos o servidor void serverBegin() { // Iniciamos o WiFi WiFi.begin(ssid, password); //Exibimos na serial e no display showDisplay("WiFi Connecting", false); Serial.println("Connecting to WiFi"); // Enquanto não estiver conectado exibimos um ponto while (WiFi.status() != WL_CONNECTED) { display.print("."); Serial.print("."); delay(1000); } // Exibimos na serial e no display a mensagem OK Serial.println("OK"); showDisplay("OK", true); // Configuramos o WiFi com o IP definido anteriormente WiFi.config(IP, GATEWAY, SUBNET); // Iniciamos o servidor server.begin(port); // Printamos o IP (debug) Serial.println(WiFi.localIP()); }
Loop – showClients e readBMESensors
// Função que exibe a quantidade de clientes conectados no servidor do ESP void showClients() { showDisplay("Clients: "+String(clients.size()), true); }
// Função que lê a temperatura e umidade do sensor BME280 void readBMESensor() { float t = bme.readTemperature(); float h = bme.readHumidity(); if(isnan(t) || isnan(h)) { Serial.println("Erro ao ler sensor"); showDisplay("-\n-", false); climate = " -\n -"; } else { Serial.println((String(t))+";"+(String(h))); showDisplay("\nClimate:\n"+String(t)+" C", false); showDisplay(String(h)+" %", false); climate = String(t)+" C"; climate += "\n "+String(h)+" %"; } }
ShowDisplay
// Verifica se o display está ocupado e exibe a mensagem no display // Se clear estiver como true então limpamos o display antes da exibição void showDisplay(String msg, bool clear) { if(!displayIsBusy) { displayIsBusy = true; if(clear) resetDisplay(); display.println(msg); displayIsBusy = false; } }
ResetDisplay
// Limpa e reconfigura o display void resetDisplay() { display.setFont(&FreeSerif9pt7b); display.fillScreen(ST77XX_BLACK); display.setTextColor(ST7735_WHITE); display.setCursor(0,fontHeight); display.setTextSize(1); }
8 Comentários
Fernando vou poder ter acesso ao ESP32 fora da rede local?
ResponderExcluirFernando_K, já que seu app é free, seria possível vc compartilhar o fonte desse APP conosco?
ResponderExcluirServiria de base, de início para muitos de nós que estamos estudando a automação.
Agradeço e te parabenizo pela matéria.
Ficaria show, um tutorial dessa APP usando o arduimo mega com Wifi
ResponderExcluirFernando pode dizer onde eu encontro essa LIB.
ResponderExcluir#include // Lib com as funções de vetor (vector)
Bom dia
ResponderExcluirEstou usando um sensor de correntecorr invasivo 30A e o ESP32, vcs tem algum.lrojero desse ripo????
Boa noite,
ResponderExcluirqual protocolo você utiliza na troca de dados entre o APP e o ESP32?
att
Olá Fernando, como o amigo Emerson perguntou eu quero reiterar, já que seu app é free, seria possível você compartilhar o fonte desse APP conosco?
ResponderExcluirServiria de base, de início para muitos de nós que estamos estudando a automação.
Agradeço e te parabenizo pela matéria.
Ótima ideia. Muito boa mesmo.
ResponderExcluir