banner

Ir para o Forum

Automação com AppFernandoK e ESP32



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);
}




Faça o download dos arquivos: 


APLICATIVO APPFERNANDOK



MANUAL DO APP

INO



2 comentários:

  1. Fernando vou poder ter acesso ao ESP32 fora da rede local?

    ResponderExcluir
  2. Fernando_K, já que seu app é free, seria possível vc compartilhar o fonte desse APP conosco?
    Serviria de base, de início para muitos de nós que estamos estudando a automação.
    Agradeço e te parabenizo pela matéria.

    ResponderExcluir

Tecnologia do Blogger.