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 LCD”8 – 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