Manual

do

Maker

.

com

UNO R4 - Wifi, IP público, matriz de LEDs

UNO R4 - Wifi, IP público, matriz de LEDs

Uma boa parte dos leitores do blog não vivenciaram a chegada das placas-mãe com display de 7 segmentos 1 dígito. Chega a ser bizarro, um componente que hoje é insignificante, outrora era artigo de luxo em placas-mãe top de linha. Veremos como utilizar a matriz de LEDs do Arduino para status, ainda que uma matriz de leds dispersa e simples, é muito superior ao LED do GPIO 13 em termos informativo.

Montando as imagens a utilizar

Não vou enrolar. Vou fazer algo prático e rápido, mas mostrando o uso de alguns status. Vamos à definição.

Conectar ao WiFi

Enquanto estiver tentando conectar ao WiFi, a primeira linha da matriz deve parecer-se com um progress bar infinito (aquele que fica indo e voltando).

Invés de montar um frame para isso, vamos utilizar um processo similar ao descrito no artigo Como controlar a matriz de LEDs do UNO R4.

Status da conexão WiFi

Se conectar ao WiFi, deverá exibir uma imagem que represente conexão. Não sei se dá pra fazer o símbolo ainda, mas algo será apresentado até o final do artigo (estou dispondo o projeto antes de fazê-lo).

Se "não" conseguir se conectar, deverá exibir uma exclamação. Acho que essa é fácil colocar na matriz.

Exibir o IP público na matriz

Aqui é um pouco mais trabalhoso. Vamos ao raciocínio.

Os endereços IPv4 são compostos por 4 octetos (ou "4 bytes, se preferir"). Cada byte tem 8 bits e, vejam só, cada coluna tem 8 LEDs. Como temos 12 colunas, vamos colocar cada octeto em uma coluna no valor binário correspondente e pular 3 colunas, assim deixamos espaçado. Para exemplificar, vou usar um IP privado classe C tradicional. Mas não vamos ser básicos demais. O IP de exemplo será 192.168.96.129. Observe que o IP que será mostrado de fato é o público, que pegaremos da Internet. Esse IP público é o IP que seu roteador usa para fazer o mascaramento para que seja possível acessar a Internet. Como descobri-lo? Já mostrei o processo no artigo Como configurar a WiFi da Raspberry Pi Pico W. Só que lá eu escrevi o código em Python.

Tendo deixado tudo claro, vamos ao exemplo de como será a apresentação na matriz, usando o IP de exemplo disposto mais acima:

   1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1 
   1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 
   0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 
   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 
   0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 
   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 
   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 
   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 

Tem que saber binário para ler o IP. lendo da esquerda para a direita, sendo o bit superior o mais significativo, temos:

128+64 = 192

128+32+8 = 168

64+32 = 96

128+1 = 129

Acho que para uma prova de conceito já é mais que o suficiente. Porém já vi que é mais simples exibir o bit mais significativo embaixo. Então na hora de ler a conta é a mesma, mas o 128 começará embaixo.

Criar o desenho na matriz de LEDs

O primeiro passo será criar o desenho. Invés de utilizar o editor online do Arduino, façamos do modo "DIY", usando como base essa matriz:

uint8_t frame[8][12] = {
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};

Progress bar

A começar pelo progress bar, podemos nos basear do código já exposto em um dos artigos supracitados:

#include "Arduino_LED_Matrix.h"

ArduinoLEDMatrix matrix;

uint8_t frame[8][12] = {
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};

void progressbar(){
  while (true){
    for (uint8_t j=0;j<11;j++){
      frame[0][j>0?j-1:0]      = 0;
      frame[0][j]              = 1;
      frame[0][j+1]            = 1;
      frame[0][j+2>11?j+1:j+2] = 1;

      matrix.renderBitmap(frame,8,12);
      delay(40);
    }
  
  for (char j=11;j>-1;j--){
      frame[0][j<10?j+2:11] = 0;
      frame[0][j]           = 1;
      frame[0][j-1]         = 1;
      frame[0][j-1]         = 1;

      matrix.renderBitmap(frame,8,12);
      delay(40);
    }
  }
}

void setup() {
  matrix.begin();
   progressbar();
}

void loop() {
 
}

Esse código executa em loop infinito o progress bar porque é a prova de conceito. Achei que ficou bem bacana o deslizamento suave dos LEDs, sugiro que, se tiver um Arduino UNO R4 WiFi, experimente-o.

Exclamação

error-uno_r4.png

Se a conexão não for estabelecida, deve mostrar uma exclamação e ficar por aí mesmo. Essa é fácil também, então vamos caprichar.

Primeira coisa, desenhar na matriz. O desenho ficou desse jeito:

uint8_t error_alert[8][12] = {
  { 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 }
};

Mas agora vamos usar um método da ArduinoLEDMatrix que receber um array de unsigned long. Nesse caso, precisamos converter esse array bi-dimensional. Sem o editor de matriz online, podemos fazer isso como exemplificado no artigo Conversor para matriz de LEDs do Arduino UNO R4. Como já tem o artigo mostrando e explicando a conversão, vou colocar apenas o valor convertido aqui:

unsigned long alert_error[] = {
    0x6006006,
    0x600600,
    0x60060,
};

Mas só a exclamação é muito "estático". Vamos fazer um blink. Vamos criar também um "clear" e a função que exibirá o erro de conexão, que ficará assim:

unsigned long alert_error[] = {
    0x6006006,
    0x600600,
    0x60060,
};

unsigned long clear_matrix[] = {
    0x0,
    0x0,
    0x0,
};

void attention(){
  while (true){
    matrix.loadFrame(alert_error);
    delay(250);
    matrix.loadFrame(clear_matrix);
    delay(250);
  }
}

Conectado

Já temos o progress bar e a exclamação para acusar erro de conexão. Agora vamos ver como sai o símbolo de WiFi (e como sair, assim será, porque não vou dedicar tempo a desenhar em matriz de LEDs)...

...e minutos após o início, cheguei em algo "interessante". Longe de bonito, mas a representação é boa.

unsigned long wifi[] = {
    0x1f060,
    0xc9f22084,
    0xe4110208,
};

Conectar o Arduino UNO R4 ao WiFi

Antes de seguir para a configuração do WiFi propriamente dita, vamos ver um enumerador que contém os valores que normalmente vemos em sketches, mas não sabemos sua origem. Servirá para quando precisar pegar um status diferente do padrão. Mais adiante estará disposto seu uso.

typedef enum {
    WL_NO_SHIELD        = 255,   // for compatibility with WiFi Shield library
    WL_IDLE_STATUS      = 0,
    WL_NO_SSID_AVAIL    = 1,
    WL_SCAN_COMPLETED   = 2,
    WL_CONNECTED        = 3,
    WL_CONNECT_FAILED   = 4,
    WL_CONNECTION_LOST  = 5,
    WL_DISCONNECTED     = 6
} wl_status_t;

O Arduino UNO R4 inclui uma biblioteca nativa chamada WiFiS3, que contém tudo o que precisamos para a comunicação em rede. Além disso, é uma boa prática ter um header contendo as credenciais. Apesar de normalmente eu expor explicitamente minhas credenciais em artigo, dessa vez vou seguir as boas práticas para exemplificiar.

Crie um arquivo chamado credentials.h e inclua os valores:

#define SSID   "SuaRedeWiFi"
#define PASSWD "suaSenha"

Assim, devemos fazer os includes de todos os headers que utilizaremos em nosso sketch. No sketch incluiremos a biblioteca de WiFi, as credenciais e a biblioteca de controle da matriz de LEDs. Definimos as variáveis a serem utilizadas na conexão WiFi e já aproveitamos para criar um objeto String que guardará a resposta bruta da comunicação. O início do sketch deve ficar assim:

#include <WiFiS3.h>
#include "credentials.h" 
#include "Arduino_LED_Matrix.h"

ArduinoLEDMatrix matrix;
WiFiClient client;

char ssid[]        = SSID;
char pass[]        = PASSWD; 

String ANSWER      = "";

//...resto do código

Nesse tópico, estamos tratando exclusivamente da comunicação WiFi, então pode desconsiderar o objeto String e a biblioteca da matriz de LEDs. Mas não o WiFiClient, que será necessário para fazermos a requisição GET HTTP.

Na função setup() devemos iniciar o WiFi, garantir sua inicialização e então executar a operação de GET. O mínimo que você deve ter em seu sketch para conexão ao WiFi é isso:

WiFi.begin(ssid,pass);
while (WiFi.status() != WL_CONNECTED){
//faz alguma coisa enquanto espera. Coloque ao menos um delay
}
//...e tendo conectado, segue-se para o restante do código.

Como fazer GET com Arduino UNO R4?

Vou escrever um artigo simplificado mostrando "apenas" essa parte, assim como fiz no artigo Como configurar a WiFi da Raspberry Pi Pico W. Mas não custa nada adicionar mais essa explicação.

Basicamente, inicializamos o WiFi e então utilizando o WiFiClient, enviamos os headers HTTP. Daí fechamos a comunicação. Os detalhes dos headers, como acontece a comunicação socket usando IPv4 e os demais conceitos de rede envolvidos também ficarão para o próximo artigo. Vamos ao código de exemplo de uma comunicação HTTP GET.

Após ter iniciado a comunicação, dentro da própria função setup() podemos fazer a requisição, já que ela é única. Se fosse cíclica, então bastaria implementar sua lógica na função loop. Adiciona-se então o seguinte código:

void setup(){
    Serial.begin(9600); //inicialização da serial
    WiFi.begin(ssid,pass); //tenta iniciar a conexão WiFi
    
    //garantir status de sucesso ou ficar aqui pra sempre
    while (WiFi.status() != WL_CONNECTED){
        delay(500);
        Serial.print(".");
    }
    //Se passou, significa que está conectado. Hora do get...
}

Como pode-se notar, a configuração mínima é; bem, é mínima.

O código de implementação do GET é tão simples quanto, mas temos que observar uma condição importante: A resposta do GET vem em bytes, de modo que é humanamente ilegível. Para tratar no código também é trabalhoso, mas a solução é simples. Invés de fazer a leitura concatenando os bytes de resposta à string ANSWER diretamente, faça um cast de byte para char. O restante do código para o GET é esse:

//host e porta a conectar, já validando a conexão na condicional if
if(client.connect("ifconfig.me", 80)) {
    //headers HTTP
    client.println("GET / HTTP/1.1");
    client.println("Host: ifconfig.me");
    client.println("Connection: close");
    client.println();

    //executa a leitura enquanto a conexão estiver estabelecida
    while(client.connected()) {
      if (client.available()){

        ANSWER = ANSWER + String((char)client.read()); //lê fazendo o cast
      }
    }

    client.stop(); //tendo lido todos os bytes, encerra-se a conexão
    int end    = ANSWER.length(); //lê o tamanho da resposta
    int starts = ANSWER.lastIndexOf("\n")+1; //como a separação é quebra de linha, vamos direto para a última
    Serial.println(ANSWER.substring(starts,end)); //pegamos a substring contendo o IP

A resposta legível do GET é essa:

HTTP/1.1 200 OK
server: istio-envoy
date: Wed, 23 Aug 2023 13:29:04 GMT
content-type: text/plain
content-length: 15
access-control-allow-origin: *
x-envoy-upstream-service-time: 1
strict-transport-security: max-age=2592000; includeSubDomains
Via: 1.1 google
Connection: close

179.111.199.134

Como vemos claramente que o IP está no final, é fácil pegar a substring contendo o IP, usando como limitador a quebra de linha. Por isso o uso do método lastIndexOf() do objeto String do Arduino.

Mostrador de IP

public_ip-uno_r4.png

Agora que já temos o IP, sabemos o valor que deverá ser exibido em binário no display. Vamos à função que converte o IP para a matriz bi-dimensional que será exibida.

void ip2bin(String IP){
  String octets[4] = {""};
  
  uint8_t pos = 0;
  for (uint8_t k=0;k<IP.length();k++){
    //Serial.println(IP.charAt(k));
    if (IP.charAt(k) == '.'){
      pos++;  
    }
    else{
      octets[pos] = octets[pos] + IP.charAt(k);
    }  
  }
  
    for (uint8_t j=0;j<8;j++){
      frame[j][0] = (octets[0].toInt()&(1<<j)) > 0 ? 1 : 0 ;
      frame[j][4] = (octets[1].toInt()&(1<<j)) > 0 ? 1 : 0 ;
      frame[j][8] = (octets[2].toInt()&(1<<j)) > 0 ? 1 : 0 ;
      frame[j][11] = (octets[3].toInt()&(1<<j)) > 0 ? 1 : 0 ;
    }
}

Feito isso, agora temos o código completo para nosso sketch. Hora de por tudo pra funcionar.

Sketch completo

Nesse artigo vimos diversos conceitos interessantes, como a manipulação da matriz de LEDs com array bi-dimensional, array de long, animação, ícones de status, conexão WiFi com o Arduino UNO R4, socket client, requisição GET, pegar o IP público e no código, algumas interações com bitwise. Daria para fazer uns 4 artigos separados, mas aplicar tudo junto com algum propósito talvez seja mais divertido. O Sketch completo é esse:

#include <WiFiS3.h>
#include "credentials.h" 
#include "Arduino_LED_Matrix.h"

ArduinoLEDMatrix matrix;
WiFiClient client;

char ssid[]        = SSID;
char pass[]        = PASSWD; 

String ANSWER      = "";
String public_ip   = "";

uint8_t frame[8][12] = {
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};

unsigned long clear_matrix[] = {
    0x0,
    0x0,
    0x0,
};

unsigned long alert_error[] = {
    0x6006006,
    0x600600,
    0x60060,
};

unsigned long wifi[] = {
    0x1f060,
    0xc9f22084,
    0xe4110208,
};

void wifiok(){
   matrix.loadFrame(wifi);
}

void attention(){
  while (true){
    matrix.loadFrame(alert_error);
    delay(250);
    matrix.loadFrame(clear_matrix);
    delay(250);
  }
}

void progressbar(){
    for (uint8_t j=0;j<11;j++){
      frame[0][j>0?j-1:0]      = 0;
      frame[0][j]              = 1;
      frame[0][j+1]            = 1;
      frame[0][j+2>11?j+1:j+2] = 1;

      matrix.renderBitmap(frame,8,12);
      delay(40);
    }
  
  for (char j=11;j>-1;j--){
      frame[0][j<10?j+2:11] = 0;
      frame[0][j]           = 1;
      frame[0][j-1]         = 1;
      frame[0][j-1]         = 1;

      matrix.renderBitmap(frame,8,12);
      delay(40);
    }
}

void ip2bin(String IP){
  String octets[4] = {""};
  
  uint8_t pos = 0;
  for (uint8_t k=0;k<IP.length();k++){
    //Serial.println(IP.charAt(k));
    if (IP.charAt(k) == '.'){
      pos++;  
    }
    else{
      octets[pos] = octets[pos] + IP.charAt(k);
    }  
  }
  
    for (uint8_t j=0;j<8;j++){
      frame[j][0] = (octets[0].toInt()&(1<<j)) > 0 ? 1 : 0 ;
      frame[j][4] = (octets[1].toInt()&(1<<j)) > 0 ? 1 : 0 ;
      frame[j][8] = (octets[2].toInt()&(1<<j)) > 0 ? 1 : 0 ;
      frame[j][11] = (octets[3].toInt()&(1<<j)) > 0 ? 1 : 0 ;
    }
}

void setup() {
  matrix.begin();
  Serial.begin(9600);
  //Aguarda serial...
  while (!Serial){
    progressbar();
  }
  Serial.println("Started");
  matrix.loadFrame(clear_matrix);

  //Conecta ao WiFi...
  WiFi.begin(ssid,pass);

  uint32_t timer = millis();
  while (WiFi.status() != WL_CONNECTED){
    progressbar();

    int wifi_timeout = millis()-timer;

    if (wifi_timeout > 10000){
      attention();
    }

    delay(100);
  }
  Serial.println("Connected");
  wifiok();
  Serial.println(WiFi.localIP().toString());

  if(client.connect("ifconfig.me", 80)) {
    //headers HTTP
    client.println("GET / HTTP/1.1");
    client.println("Host: ifconfig.me");
    client.println("Connection: close");
    client.println();

    while(client.connected()) {
      if (client.available()){

        ANSWER = ANSWER + String((char)client.read());
      }
    }

    client.stop();
    int end     = ANSWER.length();
    int starts  = ANSWER.lastIndexOf("\n")+1;
    public_ip   = ANSWER.substring(starts,end);
  }

  Serial.println(public_ip);
  ip2bin(public_ip);
  matrix.renderBitmap(frame,8,12);
 
}

void loop() {
  
}

Não se esqueça de criar o header credentials.h ou adicionar suas credenciais direto no sketch e excluir o include do credentials.h.

Vale um vídeo, mas devo levar um tempo para conseguir gravar. De qualquer modo, aproveite para se inscrever em nosso canal Manual do Maker no Youtube.

E ainda tem mais, acompanhe no blog!

Nome do Autor

Djames Suhanko

Autor do blog "Do bit Ao Byte / Manual do Maker".

Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.