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