Introduction: Automation With AppFernandoK and ESP32
This is a tutorial on how to use AppFernandoK, which is a generic automation application for Android. I developed AppFernandoK with the purpose of enabling you to automate a multitude of things while using ESP32 or ESP8266. In this video, I’ll also show a multiclient example with ESP32 that sends and receives messages to the application.
Click HERE and download the App.
Step 1: Demonstration
Step 2: Using the Application
Icon
Step 3: Home
The home screen consists of the following components
Step 4: Setting Up a New Connection
1. Select Settings
2. Select Connection
Click the button to add
Fill in the fields
A new item in the list of connections will appear as shown in Figure 1, select the connection according to Figure 2, and return to the initial screen by clicking on the X.
Step 5: Shipment Via Terminal
Type a * command in the text field and click OK
* The commands must be properly programmed in the source code of the ESP32, both for the relay output and for sending response to the application.
Step 6: Creating a Button
1. Select Settings
2. Select Add Button
Step 7: Creating Scripts (buttons)
Fill in the fields as shown
* The list of reserved commands (such as Wait) is available in the application user manual
A new button will be created according to the image
You can enable button movement by clicking EnableDragButton
An arrow icon will appear indicating the movement of the object.
To edit / delete, use the drag button of the gear located in the upper right corner.
Step 8: Buttons Used in Our Example
The buttons used in our example are highlighted in the image
Step 9: Assembly
Step 10: Resources Used
· ESP32 WROOM Dev Module
· TFT 1.8 '' Display
· 4-relay module
· BME280
· Reed Switch
· Neodymium's magnet
· 10k ohm resistor
· Smartphone
· AppFernandoK application
· Jumpers
Step 11: Pinout
Step 12: Code
Installing Libraries
Adafruit_GFX
https://github.com/adafruit/Adafruit-GFX-Library
Adafruit_ST7735
https://github.com/adafruit/Adafruit-ST7735-Librar...
Adafruit_BME280
Step 13: Diagram
Task responsible for reading the reed switch sensor and sending warnings to clients if the sensor is activated.
Task responsible for adding new clients to the list of connected clients (vector) used by ESP.
Task responsible for reading commands received from clients, performing an action, and sending a response.
The loop function is responsible for reading the BME280 temperature and humidity sensor, displaying the values on the display every 5 seconds, and displaying the number of connected clients.
Step 14: Declarations and Variables
#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 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 *);
Step 15: 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(); }
Step 16: 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); }
Step 17: 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); }
Step 18: 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
Step 19: 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); } }
Step 20: 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
Step 21: RefreshConnections (function Called by 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; }
Step 22: ExecuteCommand (function Called by 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"); }
Step 23: GetStatus (function Called by "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; }
Step 24: 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); }
Step 25: 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); } }
Step 26: 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()); }
Step 27: Loop - ShowClients and 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)+" %"; } }
Step 28: 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; } }
Step 29: 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); }