Recents in Beach


Receba o meu conteúdo GRATUITAMENTE


WebServer: Arduino UNO com WiFi ESP01




Mais um vídeo de Arduino UNO com o ESP8266, desta vez na versão ESP01. Esse modelo eu considero pequeno e também mais barato e, não por isso, menos poderoso. Neste projeto, conectamos nosso ESP01, sem utilizar comando AT, no Arduino Uno, sendo que ambos possuem seus códigos fontes INO. Montamos isso como alternativa a fazer um link serial, ou seja, utilizamos os códigos pela possibilidade de incluir o WiFiManager, Watchdog, entre outros recursos. Resumindo: vamos utilizar um ESP8266 para conexões enquanto um Arduino Uno responde as requisições através da serial.



Montagem

No nosso esquema de automação, bastante simples, os resistores de 1.2K e 2.2K na parte de cima são para baixar a voltagem de 5V para próximo de 3.3V. Destaco que, se você quiser, é possível ligar vários módulos de relés neste projeto. Neste nosso caso: até 12. No entanto, neste nosso exemplo eu usei Led.



Como gravar

Exibo aqui o esquemático de como gravar o ESP01. Se você tiver o adaptador, recomendo utilizá-lo.


Nesta imagem abaixo você vê o adaptador, que aconselho a aquisição, pelas facilidades que ele possibilita. Mais informações de como gravar o ESP01 você pode ver neste vídeo: GRAVANDO NO ESP01.



Demonstração



ESP8266.ino

Vamos incluir a lib ESP8266WiFi.h e trabalhar com os parâmetros de rede. Seguimos definindo o Timeout da conexão e o objeto do servidor, além de declarar o buffer.

#include <ESP8266WiFi.h>

//Troque pelos dados da sua rede
const char* ssid = "SSID";
const char* password = "12345678";
const char* ip = "192.168.0.177";

//Timeout da conexão
#define TIMEOUT 500
#define MAX_BUFFER 300

//Server na porta 80 (padrão http)
WiFiServer server(80);
//Buffer onde serão gravados os bytes da comunicação
uint8_t buffer[MAX_BUFFER];


ESP8266.ino – Setup

Minha comunicação serial com o Arduino é 115200, o que considero rápido. Enviamos informações da rede para conectar e esperamos a conexão com o acess point. Configuramos o IP e inicializamos o server. Pessoal, nesta parte do código nós utilizamos a função Disconnect, pois ela consegue zerar todas as minhas variáveis internas de comunicação e, digo isso porque tem situações que essa ação é bastante necessária.

void setup() {
  Serial.begin(115200);

  //Envia a informação da rede para conectar
  WiFi.disconnect();
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  //Espera a conexão com o access point
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  //Configura o IP
  IPAddress ipAddress;
  ipAddress.fromString(ip);
  WiFi.config(ipAddress, WiFi.gatewayIP(), WiFi.subnetMask());

  //Inicializa o server
  server.begin();
}


ESP8266.ino – loop 1/4

Verifico se houve conexão do client. Se ninguém conectou, apenas retornamos sem fazer nada. Já no caso de conexão, marco o tempo que o cliente se conectou e a quantidade de bytes lidos.

void loop() {
  //Verifica se alguem se conectou
  WiFiClient client = server.available();
  if (!client) {
    //Se ninguém conectou apenas retorna sem fazer nada
    return;
  }

  //Marca o tempo que o cliente se conectou e a quantidade
  //de bytes lidos
  uint32_t connectedTime = millis();
  int bytesRead = 0;


ESP8266.ino – loop 2/4

Seguimos colhendo os dados de conexão do cliente. Se o tempo passar do tempo máximo sem ler nenhum byte, fechamos a conexão.

//Enquanto o cliente estiver conectado
  while(client.connected())
  {
    //Tempo agora
    uint32_t now = millis();
    //Quanto tempo passou desde a conexão com o cliente
    uint32_t ellapsed = now - connectedTime;

    //Se o tempo passou do tempo máximo e não leu nenhum byte
    if(ellapsed > TIMEOUT && bytesRead == 0)
    {
      //Fecha a conexão com o cliente
      client.stop();
      break;
    }


ESP8266.ino – loop 3/4

Verificamos se o cliente possui bytes a serem lidos. Enviamos os bytes pela serial e aumentamos o contador de bytes lidos.

int available = client.available();

//Se o cliente possui bytes a serem lidos
if(available)
{
int bufferSize = available < MAX_BUFFER ? available : MAX_BUFFER;
int readCount = client.read(buffer, bufferSize);
//Envia os bytes pela serial e aumenta o contador de bytes lidos
Serial.write(buffer, readCount);
Serial.flush();
bytesRead += readCount;
}


ESP8266.ino – loop 4/4

Verificamos aqui se a serial possui bytes a serem lidos do UNO para o ESP01. Analisamos ainda se é o byte que define a finalização da conexão. Enviamos o que ainda não tenha sido enviado e espera um tempo para o cliente receber. Enviamos os bytes ao cliente e depois fechamos a conexão.

available = Serial.available();
    
    //Se a serial possui bytes a serem lidos
    if(available)
    {
      int bufferSize = available < MAX_BUFFER ? available : MAX_BUFFER;
      //Lê os bytes
      Serial.readBytes(buffer, bufferSize);

      //Se for o byte que define a finalização da conexão
      if(buffer[bufferSize-1] == 127)
      {
        client.write(buffer, bufferSize-1);
        //Envia o que ainda não tenha sido enviado
        client.flush();
        //Espera um tempo para o cliente receber
        delay(100);
        //Fecha a conexão com o cliente e sai do 'while'
        client.stop();
        break;
      }

      //Envia os bytes para o cliente
      client.write(buffer, bufferSize);
    }
  }//while(client.connected())
}//loop


Arduino.ino

Definimos aqui o pino onde está o primeiro relê e quantos pinos serão utilizados. Por fim, inicializamos os pinos.

#define FIRST_PIN 2 //Pino onde está o primeiro relê
#define PINS_COUNT 12 //Quantos pinos serão utilizados

//Mantém o estado atual dos pinos (HIGH ou LOW)
int pinsStatus[PINS_COUNT];

void setup()
{
  Serial.begin(115200);

  //Inicializa os pinos
  setupPins();
}

Arduino.ino – setupPins

Apontamos, nesta etapa, os pinos que estão ligados os relés como saída.

void setupPins()
{
  //Coloca os pinos que estão ligados os relês como saída
  for(int i=0; i<PINS_COUNT; i++)
  {
    pinsStatus[i] = LOW;
    int pinNumber = FIRST_PIN + i;
    pinMode(pinNumber, OUTPUT);
    digitalWrite(pinNumber, pinsStatus[i]);
  }
}

Arduino.ino – loop

Verificamos aqui se há um novo cliente. Executamos a leitura da requisição e, se a requisição não for para o favicon, executamos a ação com o valor passado na requisição. Enviamos, então, a resposta ao cliente.

void loop()
{
  //Verifica se há um novo cliente
  if (Serial.available())
  { 
    //Faz a leitura da requisição
    char* request = readRequest();

    //Se a requisição não for para o favicon
    if(strstr(request, "favicon") == NULL)
    {
      //Executamos a ação com o valor passado na requisição
      execute(getAction(request), getValue(request));

      //Envia a resposta ao cliente
      sendResponse();
    }
    else
    {
      Serial.print(
      "HTTP/1.1 404 Not Found\r\n"
      "Content-Length: 0\r\n"
      "Connection: close\r\n"
      "\r\n");
    }

    Serial.write(127);
  }
}

Arduino.ino – readRequest 1/2

Partimos para a leitura da primeira linha da requisição. Lembrando que nesta etapa, apenas a primeira linha da requisição nos interessa.

//Faz a leitura da primeira linha da requisição
char* readRequest()
{
  bool currentLineIsBlank = true;
  char request[50];
  int i = 0;
  bool firstLine = true;

  while (true){
    while(!Serial.available());
    char c = Serial.read();
    
    //Apenas a primeira linha da requisição nos interessa
    if(firstLine){
      request[i] = c;
      i++;
    }


Arduino.ino – readRequest 2/2

Expomos aqui que a última linha da requisição é um \r\n sozinho, após o \r\n da linha anterior.
Se foi feita leitura de qualquer caracter que não for \n e \r, significa que a linha não está em branco.

 if (c == '\n'){
        //A última linha da requisição é um \r\n sozinho, após o \r\n da linha anterior
        if(currentLineIsBlank){
            //Se chegou aqui é porque a requisição foi lida por completo
            break;
        }

        currentLineIsBlank = true;
        firstLine = false;
    }
    else if (c != '\r'){
        //Se leu qualquer caracter que não for \n e \r significa que a linha não está em branco
        currentLineIsBlank = false;
    }
  }

  return request;
}

Arduino.ino – sendResponse

Veja nesta parte a função que envia o HTML para o cliente.

//Envia o HTML para o cliente
void sendResponse()
{
    //Envia o cabeçalho HTTP
    Serial.print(
    "HTTP/1.0 200 OK\r\n"
    "Content-Type: text/html; charset=UTF-8\r\n"
    "Connection: close\r\n"
    "\r\n");

    Serial.println("<!DOCTYPE HTML>");
    Serial.println("<html>");
    head();//Envia o cabeçalho do HTML
    body();//Envia o corpo do HTML
    Serial.println("</html>");
}


Arduino.ino – head

Já, nesta parte, enviamos o CSS para modificar a aparência da página.

//Envia o CSS para modificar a aparência da página
void head()
{
  Serial.println(F("<head>"
    "<meta name='viewport' content='width=device-width, initial-scale=1.0'>"
    "<style>"
        "body{"
            "text-align: center;"
            "font-family: sans-serif;"
            "font-size: 14px;"
        "}"
        "p{"
            "color:#555;"
            "font-size: 12px;"
        "}"
        ".button{"
            "outline: none;"
            "display: block;"
            "border: 1px solid #555;"
            "border-radius:18px;"
            "width: 150px;"
            "height: 30px;"
            "margin: 10px;"
            "margin-left: auto;"
            "margin-right: auto;"
            "cursor: pointer;"
        "}"
        ".button_off{"
            "background-color:#FFF;"
            "color: #555;"
        "}"
        ".button_on{"
            "background-color:#2C5;"
            "color: #fff;"
        "}"
    "</style>"
  "</head>"));
}


Arduino.ino – body

Criamos o body e os botões, criando um botão para cada pino que possui um relé.

//Cria o body e os botões
void body()
{
    Serial.println("<body>");

    String buttons = "";

    //Cria um botão para cada pino que possui um relê
    for(int i=0; i<PINS_COUNT; i++)
    {
        buttons.concat(button(i));
    }

    Serial.println(buttons);
    Serial.println("</body>");
}

Arduino.ino – button

Criamos um botão com a aparência e ação correspondente ao estado atual do relé.

//Cria um botão com a aparência e ação correspondente ao estado atual do relê
String button(int number)
{
    String label = String(number + 1);
    String className = "button ";
    className += pinsStatus[number] == HIGH ? "button_on" : "button_off";
    String action = pinsStatus[number] == HIGH ? "off" : "on";
    return "<button class=\"" + className + "\"onclick=\"location.href='?" + action + "=" + label + "'\">" + label + "</button>";
}

Arduino.ino – getAction getValue

Retornamos a ação que o cliente deseja executar (on off), bem como o valor (número do relé) que a ação será executada.

//Retorna a ação que o cliente deseja executar (on off)
String getAction(char *request)
{
  return getStringBetween(request, '?', '=');
}

//Retorna o valor (numero do relê) que a ação será executada
String getValue(char *request)
{
  return getStringBetween(request, '=', ' ');
}

Arduino.ino – getStringBetween

Retornamos a string que fica entre o primeiro caractere “start” e “end”. Também retornamos o endereço de memória do caractere “start”.

//Retorna a string que fica entre o primeiro caractere 'start' e o primeiro caractere 'end'
String getStringBetween(char* input, char start, char end)
{
  String str = "";
  //retorna o endereço de memória do caractere 'start'
  char* c = strchr(input, start);

  //Se não achou o caractere
  if(c == NULL)
  {
      return "";
  }

  //Vai para o próximo caractere
  c++;

  //Enquanto não chegar ao caractere 'end' ou ao final da string
  while(*c != end && *c!='\0')
  {
      str += *c;
      c++;
  }

  return str;
}

Arduino.ino – execute

Para finalizar, executamos a ação junto ao valor (número do relé). Verificamos se é uma das duas ações que esperamos (On Off) e numeramos a partir do 1, mas o array começa do 0. Então, tiramos 1. O número do pino, portanto, será o índice mais o número do pino onde os relés começam. Lembrando que os relés devem estar em sequência a partir do pino inicial (FIRST_PIN).

//Executada a ação junto ao valor (número do relê)
void execute(String action, String value)
{
  //Se é uma das duas ações que esperamos
  if(action == "on" || action == "off")
  {
    //Os relês são numerados a partir do 1, max o array começa do 0
    //então tiramos 1
    int index = value.toInt() - 1;
    //O número do pino será o índice mais o número do pino onde os relês
    //começam. Os relês devem estar em sequência a partir do pino inicial (FIRST_PIN)
    int pinNumber = FIRST_PIN + index;
    int status = action == "on" ? HIGH : LOW;
    digitalWrite(pinNumber, status);
    pinsStatus[index] = status;
  }
}



Faça o download dos arquivos




Postar um comentário

6 Comentários

  1. show! Parabéns professor!! Teria como html ficar no esp01? ele tem mais memoria que o arduino uno não? assim podia ser um site mais bem elaborado... Obrigado por mais essa aula!! Sucesso!!!

    ResponderExcluir
  2. Fernando, parabéns pelos vídeos. Danifiquei um ESP8266 e acabei caindo de paraquedas nos seus vídeos. A minha dúvida é o seguinte: lí que a conexão 3.3V do arduíno pode fornecer no máximo 50 mA e o módulo ESP8266 pode consomir mais de 200 mA. Procede isso? É seguro usar dessa forma? Obrigado.

    ResponderExcluir
  3. Prezado Fernando.
    Muito bom seu artigo, porém, gostaria de fazer alguns comentários.
    Em momento algum você demonstra a utilização do proposto, apenas faz alguns comentários sobre o código sem demonstrar realmente se funciona.
    Na foto em "Demonstração", existe um celular conectado ao sistema, porém, o endereço lá é 192.168.3.177 e, no seu código o endereço é 192.168.0.177, ou seja, são redes diferentes, pois não foi atribuído uma classe.
    Acompanho seus artigos, porém, como sugestão, não seria mais interessante para os iniciantes uma melhor explicação, com detalhamento e funcionamento?
    Muitos dos meus alunos ficam perdidos quando tentam seguir vossas explicações.
    Temos um outro blog na Internet (Arduino e Cia) onde o autor faz comentários ricos de detalhes, explicando todos os passos e, realmente testando suas montagens, portanto, não seria bom que o colega também pudesse seguir esses exemplos?
    Nós (eu e meus alunos) gostamos muito da forma descontraída que passa o conhecimento, porém, se puder fazer essa pequena mudança, então, será perfeito.
    Muito obrigado.
    Prof. Norberto

    ResponderExcluir
  4. Estou com o seguinte problema com o código ESP8266.ino. Como posso resolver?

    In file included from C:\Users\....\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.2.0\libraries\ESP8266WiFi\src/ESP8266WiFi.h:39:0,

    from C:\Users\....\Documents\Arduino\ESP8266SerialArduinoUno\ESP8266\ESP8266.ino:1:

    C:\Users\....\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.2.0\libraries\ESP8266WiFi\src/WiFiClient.h: In instantiation of 'size_t WiFiClient::write(T&, size_t) [with T = unsigned char [300]; size_t = unsigned int]':

    C:\Users\....\Documents\Arduino\ESP8266SerialArduinoUno\ESP8266\ESP8266.ino:98:42: required from here

    C:\Users\....\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.2.0\libraries\ESP8266WiFi\src/WiFiClient.h:123:36: error: request for member 'available' in 'source', which is of non-class type 'unsigned char [300]'

    size_t left = source.available();

    ^

    C:\Users\....\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.2.0\libraries\ESP8266WiFi\src/WiFiClient.h:127:5: error: request for member 'read' in 'source', which is of non-class type 'unsigned char [300]'

    source.read(buffer.get(), will_send);

    ^

    exit status 1
    Erro compilando para a placa NodeMCU 0.9 (ESP-12 Module)

    ResponderExcluir
  5. não apareceu nd na meu monitor serial, dessa forma n consigo acessar os botões de comando pelo google.
    usei o código que está no site.

    ResponderExcluir