Melhor canal para o WiFi da sua casa: o Arduino e o ESP8266 indicam

Um Arduino e um ESP8266 analisam as redes WiFi B/G visíveis, indicam os canais menos congestionados e em qual deles configurar o seu roteador.

Se você mora em um local com muitas redes WiFi de vizinhos visíveis, você já notou que a qualidade da sua conexão varia em determinados dias e horários? Uma das razões comuns para isso é o congestionamento dos canais WiFi, e hoje veremos uma forma de usar um Arduino com módulo WiFi para diagnosticar a situação e resolvê-la.

Esse congestionamento ocorre porque a maior parte dos equipamentos WiFi já vendidos neste século usam um mesmo conjunto de 11 canais de transmissão alocados na faixa dos 2,4GHz1.

Essa faixa ao redor dos 2,4GHz é internacionalmente reservada para uso de equipamentos industriais, científicos e médicos, e foi adotada pelos padrões WiFi B e G, que até recentemente eram os mais avançados disponíveis no mercado doméstico. Ela também é usada – nem sempre para conexões – por uma variedade de outros equipamentos que você pode ter na sua casa, incluindo fornos de microondas, telefones sem fio, NFC, Bluetooth WPAN e até lâmpadas.

Com só 11 canais, a possibilidade de colisões já é grande – mas o fato de os canais serem sobrepostos entre si a aumenta ainda mais.

O fato de você e seus vizinhos usarem esses mesmos 11 canais sem combinarem entre si faz com que seja frequente o choque entre 2 ou mais redes configuradas para usar um mesmo canal. Como as transmissões de cada uma delas são capazes de chegar até o espaço da rede do vizinho, elas competem entre si, com redução do desempenho e do alcance de todas.

Para piorar o cenário, os 11 canais não são completamente isolados entre si: cada um deles sobrepõe parcialmente 2 canais adjacentes para cada lado. Veja o exemplo do canal 6, no diagrama abaixo – para a esquerda ele sobrepõe (bastante) o canal 5 e (pouco) o canal 4; para a direita, o mesmo acontece com os canais 7 e 8:

Ainda na imagem acima, note que o único conjunto de 3 canais que não se sobrepõem entre si são os 3 marcados em vermelho: canal 1, canal 6 e canal 11. Quando a alocação de canais se concentra nesses 3, é possível obter configurações melhores de isolamento para todos.

O uso do canal não é o único elemento do congestionamento: é necessário considerar a intensidade de cada sinal, expressa na forma de um número (geralmente) negativo cuja unidade é o dBm2. Para a escala de potências oficialmente aceitas para redes WiFi residenciais, o sinal mais forte definido na norma é de 100 µW e corresponde a -10dBm, e sinais abaixo de 100 pW (ou −70 dBm) são considerados fracos.

Uma referência típica para as barrinhas de sinal que você vê no ícone da rede WiFi no seu computador é:

  • acima de -50dBm: pleno, 4 barrinhas
  • entre -50 e -60dBm: bom, 3 barrinhas
  • entre -60 e -70dBm: suficiente, 2 barrinhas
  • -70dBm ou menos: fraco, 1 barrinha

Como a potência do sinal é expressa em dBm, que é uma unidade exponencial, cuidados especiais precisam ser tomados nas comparações. Para aplicações como a nossa, muitos aplicativos no mercado convencionam tratar a faixa entre -95dBm e -35dBm como se fosse linear, e isso gera comparações suficientemente precisas para avaliações de canais e posicionamento de roteadores domésticos3. No programa a seguir eu usei uma aproximação logarítmica grosseira4, que dá um resultado um pouco melhor para os casos que eu testei, mas o mero tratamento linear não teria prejudicado as conclusões.

O que veremos neste artigo trata do congestionamento de canais WiFi B/G, mas há outros congestionamentos que você pode querer observar.

Antes de continuar, 2 alertas:

  1. se você tem um roteador com suporte ao padrão WiFi N, e os seus aparelhos domésticos também são compatíveis com WiFi N, dê preferência a usar esse padrão ;-) O WiFi N usa uma faixa de frequência mais ampla, com menos sobreposição e geralmente bem menos congestionada.
  2. Tudo o que vimos acima e veremos a seguir é específico sobre a interferência entre redes WiFi, que costuma ser contínua e mais intensa. Mas, como já vimos, outras classes de aparelhos (telefone sem fio, microondas, etc.) também podem ocupar essa mesma faixa quando em uso, e essas interferências não serão medidas ou consideradas usando a técnica aqui descrita.

Como o ESP8266 descreve o ambiente WiFi

Já vimos, no artigo anterior “ESP8266 do jeito simples”, como o Arduino pode enviar ao ESP8266 o comando AT+CWLAP para que ele responda com uma lista das redes WiFi que ele identificar no momento.

A resposta seria algo como o quadro a seguir:

AT+CWLAP
+CWLAP:(3,"iPad",-75,"ae:cf:5c:98:f2:fa",1)
+CWLAP:(4,"netvirtua304",-88,"28:be:9b:97:3b:b4",1)
+CWLAP:(4,"NetVirtua 404",-92,"e0:ce:c3:dc:50:68",1)
+CWLAP:(4,"Ana-fln",-88,"f8:d1:11:ad:dd:be",1)
+CWLAP:(4,"Wendhausen",-92,"00:1a:3f:8f:87:36",4)
+CWLAP:(3,"RCTEL-BGA2-32585541",-92,"00:27:22:2c:2b:3e",5)
+CWLAP:(4,"CARLSSON",-51,"00:37:b7:3e:b2:e1",9)
+CWLAP:(3,"casablanca",-39,"90:84:0d:dc:f2:31",11)
+CWLAP:(4,"netvirtua22-303",-67,"8c:04:ff:7e:34:a5",11)
+CWLAP:(3,"anafln",-91,"00:1d:d5:e3:94:10",11)

OK

Note que cada linha da resposta começa com +CWLAP: e é seguida por uma lista entre parênteses.

Além de informar canal e intensidade de sinal, o ESP8266 também exibe o nome, endereço e tipo de criptografia da rede.

Os 5 elementos dessa lista são:

  1. Segurança: o tipo de criptografia da rede, sendo 1 para WEP, 2 para WPA e 3 para WPA2. O valor 0 indica que a rede não é criptografada.
  2. SSID: O nome da rede, apresentado entre aspas.
  3. Potência: O nível do sinal, em dBm.
  4. MAC: O endereço de rede físico do equipamento.
  5. Canal: O canal (1 a 11) adotado por aquela rede.

Como você pode notar, os elementos relevantes para a nossa análise são o 3 e o 5: a potência com que o sinal foi detectado pelo ESP8266, e o canal em que esse sinal está sendo transmitido.

Já sabemos que cada canal sobrepõe 4 canais adjacentes (2 para cada lado), e que a ocupação de cada canal em um dado local está relacionada ao nível com que cada sinal chega a esse local.

O programa

Como vimos acima, com os dados de potência de recepção e canal de cada uma das redes visíveis em um dado local, podemos fazer uma distribuição entre os 11 canais e inferir quais estão menos ou mais ocupados, bem como escolher – dentro dos limites dessa inferência – qual o mais disponível para uso.

Também já vimos que há vantagens em preferir o uso dos canais 1, 6 e 11, razão pela qual a escolha acima pode se limitar a esses 3.

O programa a seguir roda no Arduino e busca a lista de canais em um ESP8266 (a montagem física está descrita mais abaixo), fazendo o cálculo e mostrando em um display LCD comum a ocupação de cada canal e a indicação de uso. Você pode levar esse circuito a cada ambiente da sua casa e identificar a situação naquele ambiente5, e a partir daí fazer uma escolha bem informada sobre qual o canal no qual melhor pode configurar seu roteador WiFi B ou G6.

O programa está colorizado para facilitar a explicação que vem a seguir.

const byte CH_PD=5;
const byte RST=6;

int canais[12];
int quantnets=0;
int nivelmax=0;

#include <SoftwareSerial.h>
SoftwareSerial monitorSerial(A1, A0); // RX, TX
#include <LiquidCrystal.h>
LiquidCrystal lcd(7, 8, 9, 10, 11 , 12);

boolean aguardaResposta(const char *sucesso, unsigned long limite=7000) {
  char resp[90];
  unsigned long chegada=millis();
  unsigned long tempo;
  boolean continuar=true; 
  boolean timeout=false;
  int contaChars=0;
  while (continuar) { 
    if (Serial.available()) {
      resp[contaChars] = Serial.read();
      contaChars++;
      monitorSerial.print(resp[contaChars-1]);
      if (resp[contaChars-1]==10) {  // LF, fim da linha recebida
        if (contaChars>1) {
          resp[contaChars-2]='';
          // tratamento especifico da resposta listando redes WiFi          
          int sinal=0;
          int canal=0;
          if (NULL!=strstr(resp,"+CWLAP:")) {
            char *linha=strstr(resp,"(")+1;
            char *pos=strstr(linha,")");
            linha[pos-linha]='';
            byte ctok=0;
            char *tok=strtok(linha,",");
            while (tok != NULL) {
              ctok++;
              if ((ctok==2) || (ctok==4)) {
                tok[strlen(tok)-1]='';
                tok++;
              }  
              switch(ctok) {
                case 3:
                  sinal=atoi(tok);
                  break;
                case 5:
                  canal=atoi(tok);
                  break;                  
              }
              tok=strtok(NULL,",");
            }  
            float intensidade=0;
            if (sinal>=-50) intensidade=8;
            else if (sinal>=-60) intensidade=4;
            else if (sinal>=-70) intensidade=2;
            else intensidade=1;
            for (byte i=0;i<12;i++) {
              switch (abs(i-canal)) {
                case 2:
                  canais[i]+=int(intensidade*0.25);
                  break;
                case 1:
                  canais[i]+=int(intensidade*0.6);
                  break;
                case 0:
                  canais[i]+=int(intensidade);
                  break;
              }
            }
            quantnets++;
          }  
          if (0==strcmp(resp,"OK")) continuar=false;
          else if (0==strcmp(resp,"ready")) continuar=false;
          else if (0==strcmp(resp,"error")) continuar=false;
          else if (0==strcmp(resp,"ERROR")) continuar=false;
          contaChars=0;
        }  
      }  
    }  
    tempo=millis()-chegada;
    if (tempo > limite) {
      timeout=true;
      continuar=false;
    }  
  }
  boolean retorno=false;
  if (!timeout & (0==strcmp(resp,sucesso))) retorno=true;  
  return retorno;
} 

boolean resetESP() {
  digitalWrite(RST,LOW);
  delay(100);
  digitalWrite(RST,HIGH);
  delay(3000);
  Serial.println("AT+RST");  
  aguardaResposta("OK",200); // aqui soh confirma a recepcao do comando
  return aguardaResposta("ready",2000);
}  

boolean listaWiFi() {
  char comando[12]="AT+CWLAP";
  Serial.println(comando);  
  return aguardaResposta("OK",10000);
}  


void setup() {
  lcd.begin(16, 2);
  pinMode(3,OUTPUT);
  analogWrite(3,96);  
  lcd.setCursor(0,1);
  lcd.print("1 3  6 8 11");
  Serial.begin(9600);
  Serial.setTimeout(5000);
  monitorSerial.begin(38400);
  pinMode(RST,OUTPUT);
  pinMode(CH_PD,OUTPUT);
  digitalWrite(CH_PD,HIGH);
  if (resetESP()) {
    if (listaWiFi()) {
      for (byte i=1;i<12;i++) if (canais[i]>nivelmax) nivelmax=canais[i];
      lcd.setCursor(0,0);
      for (byte i=1;i<12;i++) lcd.print(int(9.0*canais[i]/nivelmax));  
      lcd.print(" Use:");
      lcd.setCursor(14,1);
      if (canais[1]<canais[6]) {
        if (canais[1]<canais[11]) lcd.print("1");
        else if (canais[6]<canais[11]) lcd.print("6");
             else lcd.print("11");   
      } else if (canais[6]<canais[11]) lcd.print("6");
             else lcd.print("11");               
      while(1) { delay(100); }
    } 
    else monitorSerial.println(F("Erro listando WiFi"));
  } 
  else monitorSerial.println(F("Erro no reset do ESP8266"));  
}

void loop() {
  // intencionalmente vazio
}

Começando pelos 3 trechos iniciais em verde, que são blocos de definições:

  • no primeiro temos 2 constantes que definem quais pinos do Arduino estão conectados a pinos especiais do ESP8266 (como já vimos no artigo anterior “ESP8266 do jeito simples”);
  • no segundo temos a definição de variáveis globais: um vetor no qual armazenaremos dados sobre o nível de uso dos canais, um inteiro que expressará o número de redes que foram lidas até o momento, e outro inteiro para armazenar o nível de ocupação do canal mais ocupado.
  • No terceiro temos a inclusão de duas bibliotecas que acompanham a IDE do Arduino, e a sua instanciação. A primeira inclusão é da biblioteca SoftwareSerial e permite que o programa utilize um segundo Arduino7 (opcional) para enviar informações adicionais e de debug para o monitor serial da IDE, conforme vimos no artigo “Usando outro Arduino como intermediário para debug no Monitor com a softwareSerial”; a segunda inclusão é da biblioteca LiquidCrystal, que permite o uso do display LCD, conforme vimos no artigo “Arduino e o display LCD8 – mais adiante veremos o que fazer caso você não disponha de um display LCD e queira ver a saída por meio do monitor serial.

A seguir temos a função aguardaResposta(), cujo funcionamento já vimos detalhadamente no artigo anterior “Servidor web WiFi com Arduino e ESP8266”. Essencialmente ela é chamada pelas demais funções que enviam comandos ao ESP8266, e se encarrega de aguardar a resposta a esses comandos, que pode ser de uma ou várias linhas, retornando um status que informa se a resposta foi a aguardada ou se houve erro (ou timeout).

A função aguardaResposta() trata de maneira especial as linhas que contêm a lista das redes WiFi visíveis.

Neste programa eu enxertei na aguardaResposta() o trecho que alterna tons de roxo, e que trata especificamente as linhas que contêm o trecho +CWLAP: – como vimos, essas linhas aparecem na resposta ao comando que lista as redes WiFi encontradas. Vamos analisar os trechos com as variações de roxo:

  • O primeiro trecho (escuro) é executado a cada final de linha detectado pela aguardaResposta(), e identifica se ela contém o trecho +CWLAP:. Se contiver, dá início ao tratamento especial dessa linha, começando pelo isolamento do trecho entre parênteses.
  • A seguir vem o trecho (claro) que usa um clássico9 loop da função strtok, para tratar separadamente os trechos entre cada vírgula da linha. Note que usei um switch para tratar separadamente o terceiro e o quinto trechos, correspondendo ao nível de sinal e ao número do canal, respectivamente.
  • O trecho seguinte (escuro) é a conta de padeiro que eu mencionei mais acima: ele atribui, na variável intensidade, um valor aproximadamente correspondente a faixas exponenciais de acordo com o nível de sinal identificado.
  • O próximo trecho (claro) faz uma sequência que percorre todos os canais WiFi B/G (1 a 11), atribuindo a eles uma parte dessa intensidade caso ele seja o canal mencionado na linha que está em processamento, ou adjacente a ele, ponderando de acordo com o caso. Este trecho poderia ser otimizado para se limitar aos canais afetados por cada linha, se houvesse necessidade.
  • O último trecho (escuro) contém apenas uma linha, e incrementa a contagem de redes lidas.

Os 2 trechos a seguir contêm as funções resetESP() e listaWiFi(), que se limitam a enviar comandos ao ESP8266 e depois chamar a função aguardaResposta(), que vimos acima, para tratar a resposta recebida do módulo WiFi. O funcionamento desses 2 comandos pode ser encontrado no artigo anterior “ESP8266 do jeito simples”.

Em seguida encontramos a função setup(), que é a primeira a ser executada pelo Arduino após o seu boot, e que no nosso caso contém todo o encadeamento do programa:

Começamos por um trecho em vermelho claro, contendo uma inicialização propriamente dita, incluindo a do LCD, da porta serial (opcional) para conexão a um segundo Arduino para debug, da porta serial interna usada para conexão ao ESP8266, e dos pinos de controle do ESP8266. Todos os segmentos de código desse trecho já foram explicados em outros artigos mencionados acima.

A seguir vem um trecho de 2 linhas em vermelho mais intenso. Elas chamam as funções resetESP() e listaWiFi() para inicializar o ESP8266 e mandá-lo listar as redes visíveis. Note que elas estão encadeadas em comandos if, que têm complementos para tratamento de erro ao final da função, em linhas da mesma cor.

O trecho em marrom mostra no LCD o grau de ocupação de cada canal, e a sugestão do melhor a usar.

Ao final, em marrom, temos o trecho que é o núcleo da funcionalidade visível do programa. Note que, ao ser executado, o programa já terá passado por aquele trecho que vimos acima, que trata as linhas que contêm a palavra +CWLAP:. Isso significa que neste ponto o programa já terá armazenado, no vetor canais[], um indicativo da ocupação de cada canal.

A primeira linha do trecho em marrom serve para identificar qual a ocupação do canal mais ocupado. Para isso ela percorre os canais de 1 a 11, comparando a ocupação deles com a que já estiver armazenada em canalmax.

A seguir o cursor é posicionado no início da primeira linha do LCD, e o índice de ocupação de cada canal (expresso como um número de 0 a 9, sendo que 0 é desocupado e 9 é uma ocupação idêntica à do canal mais ocupado) é exibido no LCD. Note que no início da função setup() é impressa, na segunda linha do LCD, uma legenda para facilitar a identificação dos canais.
A seguir é impressa a palavra Use:, e uma árvore exploratória (na forma de um conjunto estruturado de comandos if) é empregada para escolher, entre os canais 1, 6 e 11, qual o menos ocupado, e imprimir o número dele no LCD. Se houver empate, um deles é escolhido arbitrariamente.

Um loop sem fim conclui o nosso programa. Se você for executá-lo em um Arduino com baterias, basta pressionar o botão de reset ao mudar de ambiente pela casa, e ele fará uma nova leitura.

E se você não tem display LCD e quer executar o programa acima com todas as saídas sendo enviadas para o monitor serial de debug, basta trocar todos os comandos lcd.print por monitorSerial.println (ou monitorSerial.print, conforme o caso).

A montagem física

O projeto deste artigo usou um Arduino Uno Plus (com suporte a nível lógico de 3,3V), um módulo WiFi ESP-01 e um display LCD de 16 colunas e 2 linhas.

Você pode montá-lo com um Arduino Uno normal, cujo nível lógico é 5V. Como o nível lógico do ESP-01 é 3,3V, entretanto, você precisará usar algum intermediário, como um conjunto de divisores de tensão feitos com pares de resistores, ou um CI especializado, por exemplo.

A conexão entre o Arduino e o ESP usou o mesmo esquema mencionado no artigo “Servidor web WiFi com Arduino e ESP8266”:

  • Os pinos GND e 3.3V são de alimentação elétrica, e foram conectados ao GND e 3V3 do Arduino Uno Plus, respectivamente.
  • Os pinos CH_PD e RST foram conectados aos pinos digitais 5 e 6, respectivamente, sendo que o primeiro é permanentemente mantido em HIGH, e o segundo fica LOW brevemente na inicialização do meu programa (causando um reset do ESP8266) e a partir daí fica permanentemente em HIGH.
  • Já os pinos RX e TX são os responsáveis pela comunicação de dados entre Arduino e ESP8266. O RX recebe bits (e deve ser conectado ao TX do Arduino – o pino 1, no caso do Uno), e o TX envia bits (e deve ser conectado ao RX do Arduino - o pino 0, no caso do Uno).

A conexão ao display LCD seguiu as conexões do artigo anterior “Arduino e display LCD”:

  • O pino GND do Arduino é conectado aos pinos Vss, RW e GND do LCD;
  • O pino +5V do Arduino é conectado diretamente ao pino Vcc do LCD;
  • O mesmo pino +5V do Arduino é conectado também a um resistor de 68Ω (ou um pouco mais: eu usei 100Ω), por sua vez conectado ao pino LED+ do LCD;
  • Os pinos DB0, DB1, DB2 e DB3 do LCD ficam desconectados;
  • Os demais pinos do LCD são conectados a pinos digitais do Arduino que estejam livres. Conectei os pinos RS, E, DB4, DB5, DB6 e DB7 do LCD aos pinos digitais 7, 8, 9, 10, 11 e 12, respectivamente.

Há uma exceção, entretanto: naquele artigo o pino V0 (ou VE) do LCD era conectado ao pino 5 do Arduino, e para o programa de hoje ele deve ser conectado ao pino 3 do Arduino.

Finalmente, quanto ao uso (opcional) de um segundo Arduino para poder enviar mensagens de debug ao monitor serial (como vimos em “Usando outro Arduino como intermediário para debug no Monitor com a softwareSerial”), seu pino 0 deve ser conectado ao pino A1 do nosso Arduino, e seu pino 1 deve ser conectado ao pino A0 do nosso Arduino.

 
  1.  A partir do final da década passada começaram a estar disponíveis os equipamentos do padrão WiFi N, que usam outra faixa de frequência e estão menos sujeitos a esse tipo de problema, e hoje eles estão a um preço acessível. Ainda assim, muitos dos equipamentos de rede em uso continuam sendo dos padrões WiFi B e G, centrados na faixa dos 2,4GHz.

  2.  Numa simplificação grosseira – consulte documentação técnica se precisar de uma definição precisa –, o dBm é uma unidade apropriada para expressar potência em relação a um referencial fixo: 0dBm correspondem a 1mW. Os valores são exponenciais, de forma que -100dBm poderiam ser descritos como 0,0000000001mW.

  3.  Mas insuficiente para condições de laboratório ou situações que exijam precisão.

  4.  No meu tempo de escola técnica, isso seria chamado de “conta de padeiro”, mas é puro preconceito – eu conheço vários profissionais do ramo do panifício que fazem contas com grande precisão e acurácia ;)

  5.  Recomendo desligar o seu próprio roteador durante os testes, senão ele também será considerado como um ocupante de canais.

  6.  Recorra ao suporte técnico ou consulte a documentação do seu roteador para saber o procedimento específico.

  7.  Se você for usar esse recurso opcional, o pino 0 do segundo Arduino deve estar conectado ao nosso pino A1, e o pino 1 do segundo Arduino deve estar conectado ao nosso pino A0.

  8.  O programa assume que a conexão ao display LCD foi feita conforme explicado no artigo mencionado, usando os pinos 7, 8, 9, 10, 11 e 12 para a comunicação, mas usando o pino 3 para controle da intensidade.

  9.  para programadores C, ao menos...

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