Arduino e ESP8266: a função selecionaRede() permite escolher entre várias redes WiFi

A minha função selecionaRede() permite ter várias redes WiFi cadastradas, e o Arduino escolherá em qual conectar.

Se você usa o seu sketch de Arduino com ESP8266 em mais de um lugar, ou se tem várias redes à disposição e quer conectar sempre na mais próxima (ou, mais propriamente, na que for identificada pelo ESP8266 como o sinal mais forte), minha função selecionaRede() pode ser sua solução.

Ela permite cadastrar (no código-fonte do sketch para Arduino) os nomes e senhas de múltiplas redes1 e, quando chamada, consulta o ESP8266 para saber a lista das redes visíveis no momento, identifica qual delas tem o sinal mais forte, e instrui o ESP8266 a conectar a esta.

É possível escapar temporariamente da seleção automática: os pinos 5 e 6 podem ser usados para forçar uma conexão à primeira ou segunda rede cadastradas.

Além da seleção automática, o programa permite forçar a conexão a duas das redes cadastradas: se o pino 5 estiver LOW, a conexão será sempre à primeira rede da lista cadastrada; se o pino 6 estiver LOW, a escolhida será sempre a segunda. A escolha pelo nível do sinal acontece apenas quando nenhum desses 2 pinos estiver LOW. Para colocá-los em LOW, você pode colocar um resistor entre eles e o GND.

A função é bem simples: quando chamada, ela fará a seleção e mandará o ESP8266 conectar à rede selecionada. Ela não recebe nenhum parâmetro, e não retorna nenhum valor. Acrescentar a ela funcionalidades de tratamento de erro pode ser um interessante exercício para o leitor.

Na forma exemplificada abaixo, a selecionaRede() depende de alguns outros elementos (todos simples): a minha função genérica cmdESP() (que envia um comando ao ESP8266 e aguarda a sua resposta – conheça-a neste outro artigo) precisa estar definida, a serial do Arduino que estiver conectada ao ESP8266 precisa ter sido definida com o nome de Serial e uma serial virtual para debug (leia o artigo a respeito!) precisa estar definida com o nome de monitorSerial.

Sim, você pode colocar tudo isso em uma biblioteca para facilitar o reuso, seguindo o exemplo do meu artigo sobre a criação de bibliotecas. Se for fazê-lo, talvez seja interessante repensar as dependências (dica!).

O que a função faz, passo a passo

Logo a seguir você verá um programa com todos os requisitos: define a selecionaRede(), a cmdESP(), as 2 seriais, e exemplifica seu uso.

Antes disso, entretanto, vejamos a saída de uma execução normal (sem jumpers nos pinos 5 e 6) conforme impresso no debug:

netvirtua22-303 86
netvirtua304 91
*+casablanca 52
Plantao Vensul 88
*+tomada 46
CARLSSON 88
*casablanca 81
OK
Melhor rede:
1
tomada
Conectando com a rede selecionada
WIFI DISCONNECT
AT+CWJAP_CUR="tomada","senha123"
WIFI CONNECTED
WIFI GOT IP
OK

Note que são 3 blocos, cada um com uma cor. O bloco em laranja é a lista de redes WiFi retornadas pelo ESP8266. Note que ao lado de cada uma delas há um número, que é o valor absoluto da intensidade do sinal da rede (quanto mais baixo, melhor). As que estão marcadas com * são as que foram reconhecidas como cadastradas, e as que têm sinal de + são as que estão, naquele momento, ganhando na comparação de melhor sinal.

Depois, em vermelho, temos a identificação da rede selecionada como a que teve o melhor sinal entre as cadastradas. Para completar, em roxo, consta a sequência de conexão à rede selecionada.

Se examinássemos a comunicação entre o Arduino e o ESP, veríamos uma forma diferente da sequência acima, da qual destaco um trecho essencial: a maneira como o ESP8266 lista as redes WiFi visíveis, quando enviamos a ele o comando AT+CWLAP (saiba mais sobre os comandos AT do ESP8266):

AT+CWLAP

+CWLAP:(3,"casablanca",-44,"90:84:0d:dc:f2:31",11)
+CWLAP:(4,"tomada",-46,"54:b8:0a:ab:c8:48",6)
+CWLAP:(4,"Plantao Vensul",-89,"00:1a:3f:29:50:91",11)
+CWLAP:(4,"CARLSSON",-71,"00:37:b7:3e:b2:e1",9)

OK

O trecho em verde é a resposta. Note que todas as linhas começam com +CWLAP:, seguido por uma lista de campos separados entre vírgulas. Hoje nosso interesse é em 2 desses campos: o segundo (SSID ou nome da rede) e o terceiro (rssi ou intensidade do sinal).

Para cumprir sua finalidade, portanto, nosso programa terá que reconhecer essas linhas que começam com +CWLAP, extrair delas o 2º e o 3º campos, e aí compará-las para ver quem tem a melhor intensidade de sinal.

O programa

Nosso programa inclui, além da definição da função, alguns elementos adicionais, necessários especialmente na etapa de desenvolvimento (e debug!). Após entender como ele funciona, você pode reduzi-lo, removendo esses recursos adicionais.

Vamos a ele, na íntegra:

// (c) Augusto Campos BR-Arduino.org makerNews.info

#include <SoftwareSerial.h>
SoftwareSerial monitorSerial(9, 8); // aqui 9 eh RX, 8 eh TX

void cmdESP(String cmd, String msg="", int limite=7000) {
  // envia um comando para o ESP, aguarda, exibe e descarta a resposta
  // detalhes sobre esta funcao em: http://br-arduino.org/2015/11/ip-fixo-no-esp8266-com-arduino.html
  if (msg!="") monitorSerial.println(msg);
  Serial.println(cmd);
  unsigned long chegada=millis();
  boolean continuar=true; 
  String S="";
  unsigned long ultimochar=0;
  while (continuar) { 
    if (Serial.available()) {
      char c = Serial.read();
      ultimochar=millis();
      S=S+c;
      if (c==10) {  // LF, fim da linha recebida
        byte p=S.indexOf(13);
        String S1=S.substring(0,p);
        if (S1=="OK") continuar=false;
        if (S1=="ready") continuar=false;
        if (S1=="no change") continuar=false;
        if (S1=="ERROR") continuar=false;
        monitorSerial.print(S);
        S="";
      }  
    }  
    if (millis()-chegada > limite) continuar=false;
  }
  if (S!="") monitorSerial.print(S);      
}



void selecionaRede() {
  // variaveis e constantes da selecao da melhor rede
  const byte NUMREDES=3;  
  String redes[NUMREDES] = { "casablanca", "tomada", "labhardware" };
  String senhas[NUMREDES] = { "senharede1", "senharede2", "aoutrasenha"};  
  long int qualis[NUMREDES];
  int melhorSinal=999;
  byte iMelhor=255;
  String linha, ssid, quali;
  
  // variaveis da comunicacao entre Arduino e ESP8266
  unsigned long chegada=millis();
  unsigned long tempo;
  int limite=12000;
  boolean continuar=true; 
  boolean timeout=false;
  int contaChars=0;

  if (digitalRead(5)==LOW) iMelhor=0;
  else if (digitalRead(6)==LOW) iMelhor=1;
  else {
    // so analisa a lista de redes se o pino 5 ou 6 nao estiverem LOW 
    Serial.println("AT+CWLAP");  
    while (continuar) { 
      char c;
      if (Serial.available()) {
        c=Serial.read();
        if (c==10) monitorSerial.println(""); // caracter 10 eh ignorado 
        else if (c==13) {                     // caracter 13 indica final de linha
        
          // se for uma linha listando uma rede sem fio...
          if (linha.startsWith("+CWLAP:")) {  
            // extrai o nome da rede (SSID) e a intensidade do sinal
            ssid=linha.substring(1+linha.indexOf('"'));
            ssid=ssid.substring(0,ssid.indexOf('"'));
            quali=linha.substring(1+linha.indexOf(','));
            quali=quali.substring(1+quali.indexOf(','));
            quali=quali.substring(1,quali.indexOf(','));
            
            // compara o nome da rede com os cadastrados
            for (byte i=0;i<NUMREDES;i++) {  
              if (ssid==redes[i]) {
                monitorSerial.print("*");  
                qualis[i]=quali.toInt();
                if (qualis[i]<melhorSinal) { // compara o sinal da rede com o melhor ja visto ate agora
                  melhorSinal=qualis[i];
                  iMelhor=i;
                  monitorSerial.print("+");  
                }
              }
            }
            monitorSerial.print(ssid);
            monitorSerial.print(" ");
            monitorSerial.print(quali);
          }
          else monitorSerial.println(linha);
          if ((linha=="OK") | (linha=="ERROR")) continuar=false;
          linha="";   
        }
        else {
          linha=linha+c;        
        }
      }
      tempo=millis()-chegada;
      if (tempo > limite) {
        timeout=true;
        continuar=false;
      }   
    }
  }  
  
  // se alguma rede foi escolhida ou selecionada...
  if (iMelhor!=255) { 
    monitorSerial.println("Melhor rede:");   
    monitorSerial.println(iMelhor);
    monitorSerial.println(redes[iMelhor]);
    // manda o ESP conectar com a rede definida
    cmdESP("AT+CWJAP_CUR=\""+redes[iMelhor]+"\",\""+senhas[iMelhor]+"\"", "Conectando com a rede selecionada",10000);
  }  
}



void setup() {
  // prepara os pinos 5 e 6 para ficarem permanentemente em HIGH, caindo
  // para LOW apenas se alguem conecta-los ao GND:
  pinMode(5,INPUT_PULLUP);
  pinMode(6,INPUT_PULLUP);
  // Aqui mude o 9600 para a velocidade de comunicacao do seu ESP8266:
  Serial.begin(9600);
  monitorSerial.begin(9600);
  // chama a funcao que gerencia a conexao
  selecionaRede();
}

void loop() {
  // intencionalmente vazio
}

Para melhor explicar a função selecionaRede(), eu a dividi em blocos, e colorizei algumas partes. Note que, antes da função selecionaRede(), temos a definição do nosso monitorSerial e da função cmdESP(), ambos já mencionados acima.

Observe o início da função selecionaRede(): temos um bloco de definição de variáveis e constantes usadas para a seleção da melhor rede. Uma constante chamada NUMREDES define quantas redes nós teremos cadastradas, e os 2 arrays redes[] e senhas[] armazenam o nome e a senha de cada uma das redes às quais nosso programa estará autorizado a conectar.

As variáveis melhorSinal e iMelhor são importantes. Durante a leitura da lista de redes informada pelo ESP8266, a melhorSinal armazena o valor da qualidade de sinal da melhor rede cadastrada reconhecida até o momento (como eu optei por trabalhar com valores absolutos, a melhor será sempre a que tiver o valor mais baixo), e a iMelhor representa o índice dessa rede no nosso array redes[]: 0 se for a primeira, 1 se for a segunda, e assim por diante. Ao final da leitura, a variável iMelhor indicará a rede escolhida para a conexão – e se o valor dela ao final continuar sendo o mesmo 255 com o qual essa variável foi inicializada, significará que nenhuma rede foi identificada ou selecionada.

A seguir vem o bloco de variáveis que controlam a comunicação entre o Arduino e o ESP8266 durante a selecionaRede(): timeout, condições de saída, etc. Nesse sentido, a selecionaRede() é uma versão especial da cmdESP(), portanto você pode procurar a explicação dessas variáveis, e do uso delas, junto ao detalhamento da própria cmdESP().

Se o pino 5 ou o pino 6 estiverem conectados ao GND, o programa pula todas as demais análises, e escolhe a primeira ou a segunda rede cadastradas.

O trecho em verde tem a primeira decisão interessante: se o pino 5 ou o pino 6 estiverem em LOW, a variável iMelhor recebe desde já o valor 0 ou 1 (ou seja, o programa escolhe desde já a primeira ou a segunda das redes cadastradas, e nem mesmo vai tentar verificar se elas estão visíveis).

Se nenhum dos dois pinos estiver LOW, o programa envia ao ESP8266 o comando AT+CWLAP (para listar as redes WiFi visíveis) e inicia um loop parecido com o da nossa já conhecida função cmdESP(): recebe caracteres da resposta, forma linhas com eles e, ao final de cada uma delas, analisa o conteúdo recebido.

Para as linhas recebidas que começarem com +CWLAP, o tratamento será especial, conforme você vê no trecho em laranja.

Já vimos que essas respostas vêm em linhas com esse formato:

+CWLAP:(4,"Harry Potter",-89,"00:1a:3f:29:50:91",11)

Ao processar cada linha, a variável ssid recebe o nome da rede (em vermelho acima, identificado pelo programa como o primeiro trecho entre aspas da linha), e a variável quali recebe o valor absoluto (desprezando o sinal) da intensidade da transmissão, identificado pelo programa como o trecho entre a segunda e a terceira vírgulas, e desprezando seu primeiro caracter – correspondendo ao trecho em verde no exemplo acima.

Ao receber do ESP8266 uma linha descritiva de rede WiFi, o programa extrai o nome e intensidade de sinal, e compara com o cadastro, para poder escolher a melhor.

A seguir, um loop for percorre todos os elementos do array redes[], para verificar se o nome de algum deles é o mesmo que neste momento consta na variável ssid. Se for, sabemos que estamos lidando com uma rede cadastrada.

Quando uma rede cadastrada é detectada, um * é enviado para o monitorSerial (e aparecerá como prefixo ao nome dela, depois), e a intensidade do sinal dela (obtida da variável quali) é comparada com a variável melhorSinal, que armazena a melhor intensidade lida até o momento. Caso o sinal dessa rede seja mais forte que o melhor armazenado até o momento, o valor dele é movido para a melhorSinal, um + é enviado para o monitorSerial, e o índice dessa rede é armazenado na variável iMelhor – em outras palavras, ela passa a ser a escolhida, ao menos até que alguma outra ainda mais forte venha a ser lida em linhas posteriores.

Ao final do trecho, e independentemente de a rede ter sido reconhecida ou não, o nome dela e a intensidade de sinal correspondente são enviados para o monitorSerial.

Ao final do trecho em laranja, voltamos a um repeteco de funcionalidades da cmdESP(): verificação de condições de encerramento (OK ou ERROR) e de timeout.

Ao final de todas as demais condições e loops, temos o trecho em roxo, que faz algo bem simples: verifica se a variável iMelhor está com algum valor diferente do 255 com o qual ela foi inicializada e, se estiver, instrui o ESP8266 a conectar com a rede cadastrada correspondente a esse valor. Se não estiver, ela não faz nada – e implementar aqui alguma forma de repetição ou tratamento de erro pode ser um exercício interessante para o leitor.

Nossa função setup() tem o mínimo necessário para o programa funcionar: define os modos dos pinos 5 e 6, ativa as duas seriais (a USART interna do Arduino fica dedicada ao ESP8266 e uma interface virtual serve para monitorar) e chama a selecionaRede().

A montagem física

Este esquema usa os pinos 0, 1, 8, GND e 3V3 do Arduino.

A conexão entre o Arduino e um módulo ESP-01 já inicializado e que esteja com a versão atual2 do firmware AT (default na maioria deles) é bastante simples quanto aos aspectos lógicos: o pino 0 (RX) do Arduino vai no pino TX do ESP-01, o pino 1 (TX) do Arduino vai no pino RX do ESP-01, o 3V3 do Arduino vai no 3V3 do ESP-01, e os GNDs de ambos são interligados. Os pinos GP, RST e CH_PD do ESP8266 foram mantidos desconectados durante todo o desenvolvimento.

Mas os aspectos físicos complicam um pouco: o Arduino Uno que eu usei opera em nível lógico de 5V, que é mais do que a documentação do ESP8266 permite, já que ele opera a 3,3V.

Isso faz com que seja necessário um passo a mais, especificamente na conexão entre o TX do Arduino e o RX do ESP-01: reduzir de 5V para 3,3V. Existem muitas formas de fazer isso, incluindo usar um econômico par de resistores bem escolhidos.

Como essa conexão é para mim uma demanda comum, eu uso sempre uma plaquinha adaptadora caseira que já inclui um módulo de conversão de nível lógico (bem barato e muito prático!), mostrada no diagrama acima, cuja montagem descrevi ao final do artigo sobre o esp-link. Ela tem mais fios do que a nossa montagem de hoje precisa, porque já está preparada para converter o sinal em pinos que hoje ignoramos: GND, CH_PD e GPIO0.

Além dos pinos 0, 1, GND e 3V3 da conexão entre Arduino e ESP8266, você precisará também conectar o pino 8 do Arduino ao pino 0 (RX) de um segundo Arduino que estiver sendo usado como intermediário para monitor serial, conforme vimos no artigo “Usando outro Arduino como intermediário para debug no Monitor com a softwareSerial”.

 
  1.  O limite prático da quantidade de redes é a capacidade de memória...

  2.  Eu usei a versão 0.25.0.0, de junho de 2015.

Comentar

Dos leds ao Arduino, ESP8266 e mais

Aprenda eletrônica com as experiências de um geek veterano dos bits e bytes que nunca tinha soldado um led na vida, e resolveu narrar para você o que descobre enquanto explora esse universo – a partir da eletrônica básica, até chegar aos circuitos modernos.

Por Augusto Campos, autor do BR-Linux e Efetividade.net.

Recomendados

Livro recomendado


Artigos já disponíveis

Comunidade Arduino

O BR-Arduino é integrante da comunidade internacional de entusiastas do Arduino, mas não tem relação com os criadores e distribuidores do produto, nem com os detentores das marcas registradas.

Livros recomendados