Automação Residencial

Controlando o Media Player VLC de um PC via Joystick no ESP32

Eletrogate 30 de janeiro de 2024

Introdução

Neste post, será apresentado um projeto avançado que permite que o usuário controle o VLC Media Player a partir de um joystick juntamente com um ESP32. As informações sobre a mídia atual que está passando no VLC media player serão exibidas através de um display LCD 128×64.

Será mostrado como é feita a manipulação do VLC Media Player remotamente, permitindo estar no controle total do volume e da navegação pelas mídias, e tendo uma representação visual da posição atual da mídia através de uma barra de progresso no display LCD. Além disso, será possível determinar com facilidade se a mídia está em reprodução ou pausa, pois tais informações serão exibidas de forma clara e intuitiva no display.

O joystick, um dispositivo de entrada, permitirá que com movimentos para cima e para baixo, você possa ajustar o volume da mídia de maneira fluida e eficiente. Mover o joystick para esquerda o conduzirá à mídia anterior, enquanto para a direita, a próxima mídia será apresentada. Já o comando de pausar/reproduzir será ativado através do simples pressionar de um botão.

Será feito também uma divisão de tarefas entre os núcleos do ESP32. Enquanto um núcleo cuida da interação com o usuário, exibindo informações no display LCD e interpretando os comandos do joystick  para envio de dados ao VLC Media Player, o outro núcleo desempenha o papel vital de receber dados atualizados do VLC Media Player. Essa abordagem paralela garante um funcionamento suave e sem interrupções, criando uma experiência de usuário envolvente e responsiva.

Para permitir a integração entre o ESP32 e o VLC Media Player, a biblioteca HTTPClient entra em cena. Essa biblioteca, já incorporada no núcleo ESP32 da Arduino IDE, capacita o envio e recebimento de dados HTTP, um aspecto fundamental para a comunicação eficaz com o VLC. As respostas das requisições feitas ao VLC Media Player, em formato XML, serão analisadas com a ajuda da biblioteca tinyxml2, fornecendo acesso direto aos elementos da resposta das requisições.

Para controle do display LCD 128×64 é utilizada a biblioteca u8g2. Será mostrado neste post as principais funções da u8g2, incluindo a habilidade de exibir uma imagem bitmap.


Biblioteca U8g2: utilizando o display LCD 128x64

A biblioteca U8g2 é uma poderosa ferramenta gráfica desenvolvida para dispositivos embarcados. Ela oferece suporte a uma ampla variedade de displays monocromáticos, englobando diversos controladores (incluindo o ST7920). Essa versatilidade torna a biblioteca U8g2 uma escolha ideal para trabalhar com uma ampla gama de displays com a linguagem Arduino sem se preocupar com os detalhes técnicos dos controladores subjacentes. Ela simplifica significativamente o processo de programação e exibição de informações em displays monocromáticos em projetos embarcados, incluindo também a facilitação para desenhar gráficos (linhas, retângulos e círculos) e textos com fontes diversas. Para obter a lista completa de controladores suportados, consulte a lista controladores suportados neste link.

Logotipo biblioteca U8g2

Para este post será utilizada a Arduino IDE 2.0. Caso deseje informações de instalação e de uso, consulte o post Arduino IDE 2.0: Conheça o Novo IDE Arduino do blog Eletrogate.

Instalação da Biblioteca

Siga os passos abaixo para instalar a biblioteca U8g2:

  1. Abra a Arduino IDE 2.0;
  2. Dentro da IDE Arduino, abra a aba Gerenciador de Bibliotecas;
  3. Na barra de pesquisa, digite o termo ‘u8g2’;
  4. No resultado que aparecer, e que tenha como autor ‘oliver’ (geralmente o primeiro resultado da pesquisa), clique em instalar.

Conectando um display à biblioteca U8g2

Identificar o modelo do display

Para utilizar a biblioteca U8g2, é necessário saber o controlador e o tamanho do display utilizado. O display vendido pela loja Eletrogate possui o controlador ST7920 e tem o tamanho de 128 colunas por 64 linhas (128×64). Veja a descrição do display na página do produto:

Conectando o display no barramento físico

Para que as informações gráficas sejam mostradas no display é necessário que o controlador do display receba estas informações através de um barramento físico. O display que possui o chip ST7920 tem dois barramentos físicos de comunicação:

  • Barramento de 8 bits;
  • Barramento SPI (serial).

O pino PSB do controlador ST7920 seleciona entre o Barramento de 8 bits e o Barramento SPI. Dependendo de onde este pino estiver conectado, será selecionado determinado barramento:

  • GND: se estiver conectado ao GND, será selecionado o barramento SPI (serial);
  • 5V: se estiver conectado ao 5V, será selecionado o barramento de 8 bits.

Para o barramento SPI, os pinos de comunicação são:

  • pino RS: Chip-Select;
  • pino E: entrada do clock;
  • pino R/W: atua como linha de dados.

Veja abaixo a referência de todos os pinos do display:

  • GND:  É o pino de aterramento do display, que deve ser conectado ao GND do microcontrolador;
  • VCC: É o pino de alimentação do display, que deve ser conectado a uma fonte de 5V;
  • V0: É o pino de ajuste do contraste do display, que pode ser conectado a um potenciômetro ou a um resistor fixo;
  • RS: É o pino de Chip-Select do display;
  • R/W: É o pino de Linha de Dados do display;
  • E: É o pino de Clock do display;
  • pinos de DB0 à DB7: São os pinos de dados do display, que podem ser usados para enviar comandos e dados em modo paralelo. Eles devem ser conectados aos pinos digitais do microcontrolador.
  • PSB: É o pino de seleção do modo de comunicação do display, que pode ser paralelo (Barramento de 8 bits) ou serial (Barramento SPI);
  • NC: É um pino sem conexão, que não tem função no display;
  • RST: É o pino de reset do display, que pode ser usado para reiniciar o display em caso de falha ou travamento. Ele deve ser conectado a um pino digital do microcontrolador;
  • BLA: É o pino ânodo do LED de luz de fundo do display, que pode ser conectado a uma fonte de 5V;
  • BLK: É o pino cátodo do LED de luz de fundo do display, que pode ser conectado ao aterramento (GND).

Veja abaixo a relação de pinos entre o Display e o ESP32:

Display ESP32
GND GND
VCC Vin
V0 Vin
RS GPIO16
R/W GPIO23 (MOSI)
E GPIO18 (SCK)
PSB GND
RST GPIO17
BLA 3.3V
BLK GND

Principais funções da biblioteca U8g2

  • public: U8G2_ST7920_128X64_F_SW_SPI(const u8g2_cb_t *rotation, uint8_t clock, uint8_t data, uint8_t cs, uint8_t reset = U8X8_PIN_NONE) : U8G2(): este é o construtor da classe U8G2 para o modo de comunicação SPI. Existem vários modelos de construtores para a biblioteca U8g2 (veja a lista completa aqui neste link), aos quais se diferem entre si em tamanho do display, em modelo de controlador e em tipo de comunicação. Para o display utilizado neste post, utilizaremos o construtor U8G2_ST7920_128X64_F_SW_SPI. Veja abaixo a explicação do nome do construtor e também a explicação dos parâmetros deste construtor:
    • U8G2_ST7920_128X64_F_SW_SPI: é o construtor da classe do display, sendo:
      • ST7920 o nome do controlador do display LCD;
      • 128X64 o formato do display, que tem 128 colunas e 64 linhas de pixels;
      • SW_SPI o tipo de comunicação, que é uma implementação por software do protocolo SPI. Isso permite usar qualquer pino digital do microcontrolador, mas é mais lento que o hardware SPI (HW_SPI).
    • rotation: define a rotação do display em graus, podendo ser 0, 90, 180 ou 270 graus. As seguintes definições definem qual será a rotação do display:
      • U8G2_R0: Sem rotação, modo paisagem;
      • U8G2_R1: Rotação de 90 graus no sentido horário;
      • U8G2_R2: Rotação de 180 graus no sentido horário;
      • U8G2_R3: Rotação de 270 graus no sentido horário;
      • U8G2_MIRROR: Sem rotação, modo paisagem, conteúdo espelhado;
      • U8G2_MIRROR_VERTICAL: Conteúdo espelhado verticalmente.
    • clock: define o número do pino digital que está conectado ao pino E do display, que é o sinal de clock do SPI;
    • data: define o número do pino digital que está conectado ao pino R/W do display, que é o sinal de dados do SPI;
    • cs: define o número do pino digital que está conectado ao pino RS do display, que é o sinal de chip-select do SPI;
    • reset: define o número do pino digital que está conectado ao pino RST do display, que é o sinal de reset. Esse parâmetro é opcional e pode ser omitido se o pino RST estiver conectado ao VCC.
  • bool U8G2::begin(void): Procedimento simplificado de configuração do display para o ambiente Arduino. Esta função irá redefinir, configurar, limpar e desativar o modo de economia de energia do monitor. Sempre retorna o booleano true;
  • void U8G2::clear(void): Limpa todos os pixels da tela e do buffer e após, coloca o cursor da função de impressão no canto superior esquerdo;
  • void U8G2::drawBox(u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h): Desenha uma caixa (preenchida), começando na posição x/y (borda superior esquerda). A caixa tem largura w e altura h. Partes da caixa podem estar fora dos limites de exibição caso deseje. Este procedimento usará a cor atual (definida por setDrawColor) para desenhar a caixa.
    • Parâmetros da função:
      • x: Posição X da borda superior esquerda.
      • y: Posição Y da borda superior esquerda.
      • w: Largura da caixa.
      • h: Altura da caixa.
    • Exemplo: u8g2.drawBox(3,7,25,15);;

      Fonte da imagem: Documentação u8g2 no GitHub.

  • void U8G2::drawCircle(u8g2_uint_t x0, u8g2_uint_t y0, u8g2_uint_t rad, uint8_t opt = U8G2_DRAW_ALL): Desenha um círculo com raio rad na posição (x0, y0). O diâmetro do círculo é 2*rad+1. Dependendo de opt, é possível desenhar apenas alguns quadrantes do círculo. Este procedimento usará a cor atual (definida por setDrawColor).
    • Parâmetros da função:
      • x0, y0: Posição do centro do círculo;
      • rad: Define o tamanho do círculo;
      • opt: seleciona alguns ou todos os quadrantes do círculo. Cada valor representa uma seção do círculo, dividida em quatro quadrantes: superior direito, superior esquerdo, inferior esquerdo e inferior direito. Os valores podem ser combinados com o operador |. Por exemplo, se você quiser desenhar apenas a metade superior do círculo, você pode usar: U8G2_DRAW_UPPER_RIGHT | U8G2_DRAW_UPPER_LEFT. Agora, se você quiser desenhar o círculo inteiro, você pode usar: U8G2_DRAW_ALL ou o equivalente U8G2_DRAW_UPPER_RIGHT | U8G2_DRAW_UPPER_LEFT | U8G2_DRAW_LOWER_LEFT | U8G2_DRAW_LOWER_RIGHT. Os valores disponíveis são:
        • U8G2_DRAW_UPPER_RIGHT: Esse valor significa que a seção superior direita do círculo será desenhada. Essa seção corresponde ao quadrante que está acima e à direita do centro do círculo;
        • U8G2_DRAW_UPPER_LEFT: Esse valor significa que a seção superior esquerda do círculo será desenhada. Essa seção corresponde ao quadrante que está acima e à esquerda do centro do círculo;
        • U8G2_DRAW_LOWER_LEFT: Esse valor significa que a seção inferior esquerda do círculo será desenhada. Essa seção corresponde ao quadrante que está abaixo e à esquerda do centro do círculo;
        • U8G2_DRAW_LOWER_RIGHT: Esse valor significa que a seção inferior direita do círculo será desenhada. Essa seção corresponde ao quadrante que está abaixo e à direita do centro do círculo;
        • U8G2_DRAW_ALL: Esse valor significa que o círculo inteiro será desenhado. Esse valor é equivalente à combinação de todos os outros valores usando o operador | (OR lógico).
    • Exemplo: u8g2.drawCircle(20, 25, 10, U8G2_DRAW_ALL);;

      Fonte da imagem: Documentação u8g2 no GitHub.

  • void U8G2::drawFrame(u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h): Desenha um quadro (caixa vazia), começando na posição x/y (borda superior esquerda). A caixa tem largura w e altura h. Partes do quadro podem ficar fora dos limites da exibição. Este procedimento usará a cor atual (definida por setDrawColor) para desenhar a caixa.
    • Parâmetros da função:
      • x: Posição X da borda superior esquerda;
      • y: Posição Y da borda superior esquerda;
      • w: Largura do quadro;
      • h: Altura do quadro.
    • Exemplo: u8g2.drawFrame(3,7,25,15);

      Fonte da imagem: Documentação u8g2 no GitHub.

  • u8g2_uint_t U8G2::drawGlyph(u8g2_uint_t x, u8g2_uint_t y, uint16_t encoding): Desenhe um único caractere. O caractere é colocado na posição de pixel especificada x e y. O parâmetro encoding pode ser qualquer valor de 0 a 65535. O glifo só pode ser desenhado se a codificação existir na fonte ativa (definida por setFont). Esta função de desenho depende do modo de fonte atual e da cor do desenho.
    • Parâmetros da função:
      • x, y: Posição do caractere no display;
      • encoding: Valor Unicode do caractere.
    • Exemplo: se a fonte for a u8g2_font_unifont_t_symbols (u8g2.setFont(u8g2_font_unifont_t_symbols);), o código u8g2.drawGlyph(5, 20, 0x2603); desenha um glifo “boneco de neve”, o qual faz parte dos símbolos meteorológicos unicode e possui o unicode 9731 (decimal) / 2603 (hexadecimal).

      Fonte da imagem: Documentação u8g2 no GitHub.

      Veja abaixo a tabela da fonte u8g2 u8g2_font_unifont_t_symbols: Para escolher um símbolo para ser impresso no display, deve-se identificar o código decimal da linha do símbolo e em seguida somar este número com o código de coluna (contagem inicia-se em zero) do símbolo. Após a soma, deve-se converter de decimal para hexadecimal. Exemplo: o símbolo de “boneco de neve” tem o código decimal de linha 9728 e tem o código de coluna 3, o que resulta em 9731 (9728 + 3 = 9731). Com o resultado 9731, devemos converte-lo para hexadecimal utilizando uma ferramenta de conversão (a calculadora do Windows no modo Programador), o que resulta em 2603, que é o mesmo que 0x2603.

  • void U8G2::drawLine(u8g2_uint_t x0, u8g2_uint_t y0, u8g2_uint_t x1, u8g2_uint_t y1):Desenha uma linha entre dois pontos. Este procedimento usará a cor atual definida por setDrawColor.
    • Parâmetros da função:
      • x0: Posição X do primeiro ponto.
      • y0: Posição Y do primeiro ponto.
      • x1: Posição X do segundo ponto.
      • y1: Posição Y do segundo ponto.
    • Exemplo: u8g2.drawLine(20, 5, 5, 32);

      Fonte da imagem: Documentação u8g2 no GitHub.

  • void U8G2::drawPixel(u8g2_uint_t x, u8g2_uint_t y): Desenha um pixel na posição x/y especificada. A posição (0,0) está no canto superior esquerdo do display. A posição pode estar fora dos limites de exibição. Este procedimento utiliza o índice de cores atual para desenhar o pixel. O índice de cores 0 limpará um pixel e o índice de cores 1 definirá um pixel.
    • Parâmetros da função:
      • x: Posição X.
      • y: Posição Y.
  • u8g2_uint_t U8g2::drawUTF8(u8g2_uint_t x, u8g2_uint_t y, const char *s): Desenha uma string codificada como UTF-8.
    • Parâmetros da função:
      • x e y: Posição do primeiro caractere no display.
      • s: texto codificado em UTF-8.
    • Retorna: Largura da string.
    • Exemplo:
    • se a fonte for a u8g2_font_unifont_t_symbols (u8g2.setFont(u8g2_font_unifont_t_symbols);) e o código for u8g2.drawUTF8(5, 20, "Snowman: ☃");:
  • void U8G2::enableUTF8Print(void): Ativa o suporte UTF8 para a função Arduino print. Quando ativado, símbolos Unicode são permitidos para strings passadas para a função print(). Normalmente esta função é chamada depois de begin().
    • Exemplo:
      void setup() {
        u8g2.begin();
        u8g2.enableUTF8Print();		// habilite o suporte UTF8 para o Arduino print()
      }
  • void U8G2::disableUTF8Print(void): Desativa o suporte UTF8 para a função Arduino print().
  • void U8G2::print(...): Esta é a função print() do Arduino. Veja a documentação na página Web do Arduino aqui. Este procedimento irá escrever o texto na posição atual do cursor com a fonte atual, definida por setFont. A posição do cursor pode ser definida por setCursor. O suporte para UTF-8 pode ser habilitado com enableUTF8Print. Esta função pode imprimir valores de variáveis ​​e suporta a macro F().
    • Exemplo:
      u8g2.setFont(u8g2_font_ncenB14_tr);
      u8g2.setCursor(0, 15);
      u8g2.print("Hello World!");

  • void U8G2::setCursor(u8g2_uint_t x, u8g2_uint_t y): Define o cursor para a função print(). Qualquer saída da função print() começará nesta posição.
    • Parâmetros da função:
      • x e y: Posição do pixel do cursor da função print().
    • Exemplo:
      u8g2.setFont(u8g2_font_ncenB14_tr);
      u8g2.setCursor(0, 15);
      u8g2.print("Hello World!");

  • void U8G2::setFontMode(uint8_t is_transparent): Define se as funções de desenho de glifo e string escreverão a cor de fundo sólida ou transparente. O modo padrão é sólido.
    • Parâmetros da função:
      • is_transparent: Habilite com 1 (transparente) ou desabilite com 0 (sólido).
  • void U8G2::setDrawColor(uint8_t color): Define o valor do índice de cores para todas as funções de desenho. Todas as funções de desenho alterarão a memória de exibição para este valor de bit. O valor padrão é 1. A partir da versão 2.11 da biblioteca, o novo valor de cor 2 ativará o modo XOR.
    • Parâmetros da função:
      • color: 0 (limpar valor de pixel na RAM de exibição), 1 (definir valor de pixel) ou 2 (modo XOR)
    • Exemplo:
      u8g2.setFontMode(1);  /* ativar o modo de fonte transparente */
      u8g2.setDrawColor(1); /* cor 1 (sólida) para a caixa (box) */
      u8g2.drawBox(22, 2, 35, 50);
      u8g2.setFont(u8g2_font_ncenB14_tf);
      u8g2.setDrawColor(0);
      u8g2.drawStr(5, 18, "abcd");
      u8g2.setDrawColor(1);
      u8g2.drawStr(5, 33, "abcd");
      u8g2.setDrawColor(2);
      u8g2.drawStr(5, 48, "abcd");

  • void U8G2::setFont(const uint8_t *font): Defina uma fonte u8g2 para as funções de glifo e desenho de string. As fontes disponíveis estão listadas aqui. Os dois últimos caracteres do nome da fonte definem o tipo e o conjunto de caracteres da fonte:
    Nome da fonte Significado dos dois últimos caracteres
    u8g2_■■■_tx Gilfos transparentes com largura variável
    u8g2_■■■_mx Glifos monoespaçados/de largura fixa
    u8g2_■■■_hx Glifos com largura variável e altura comum
    u8g2_■■■_8x Glifos de largura monoespaçada/fixa em uma caixa 8×8
    u8g2_■■■_xe Estendido: Glifos com unicode 32 a 701 estão incluídos na fonte (v2.16.x também incluirá ß grande)
    u8g2_■■■_xf Completo: Glifos com unicode 32 a 255 estão incluídos na fonte
    u8g2_■■■_xr Restrito: apenas caracteres de 32 a 127 estão incluídos
    u8g2_■■■_xu Maiúsculas: números e letras maiúsculas
    u8g2_■■■_xn Números e alguns glifos extras para impressão de data e hora estão incluídos
    u8g2_■■■_x_■■■ Seleção especial de glifos. Veja a imagem da fonte para obter detalhes.
    • Parâmetros da função:
      • font: Aponte para uma fonte u8g2. Uma lista de fontes disponíveis está aqui;
    • Exemplo: Fontes u8g2_font_5x7_tr e u8g2_font_pressstart2p_8u:
  • void U8G2::setFontDirection(uint8_t dir): define a direção de desenho de todas as strings ou glifos.
    • Parâmetros da função:
      • dir: Direção de escrita/rotação da string.
        • 0: 0 grau da esquerda para direita;
        • 1: 90 graus de cima para baixo;
        • 2: 180 graus da direita para esquerda;
        • 3: 270 graus de baixo para cima.
    • Exemplo:
      u8g2.setFont(u8g2_font_ncenB14_tf);
      u8g2.setFontDirection(0);
      u8g2.drawStr(15, 20, "Abc");
      u8g2.setFontDirection(1);
      u8g2.drawStr(15, 20, "Abc");

  • void U8G2::clearBuffer(void): Limpa todos os pixels no buffer de quadros de memória.
  • void U8G2::sendBuffer(void): Envia o conteúdo do buffer de quadros de memória para o display. Use as funções draw para desenhar algo no buffer de quadros.
  • void U8G2::drawXBM(u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h, const uint8_t *bitmap): Desenha um bitmap XBM monocromático (NOTA: Em razão da documentação da biblioteca não recomendar mais o uso da função drawBitmap, só iremos descrever a função drawXBM). A posição (x,y) é o canto superior esquerdo do bitmap. Muitas ferramentas (incluindo o GIMP) podem salvar um bitmap como XBM.

Os Dois Modos de Enviar Conteúdo para o Display

Nota: para testar os sketches abaixo deste tópico, considere a montagem de hardware como a mesma que está no tópico Biblioteca U8g2: utilizando o display LCD 128×64, seção Conectando um display à biblioteca U8g2

Existem dois modos diferentes de desenho no display:

  • Modo buffer de tela cheia;
  • Modo de página.

Modo buffer de tela cheia

O modo de buffer de tela cheia é uma abordagem que permite a preparação completa de um quadro ou imagem antes de exibi-lo na tela. Ao usar esse modo, todos os procedimentos gráficos são aplicados diretamente em um buffer de memória dedicado, em vez de atualizar a tela pixel por pixel.

Análise Comparativa

O uso deste modo possui prós e contras:

Prós
  • é Rápido;
  • todos os procedimentos gráficos podem ser usados.
Contras
  • requer muita RAM.

Uso

  1. Limpe o buffer com u8g2.clearBuffer();
  2. Desenhe algo com os comandos usuais de desenho;
  3. Envie o buffer para o display com u8g2.sendBuffer().
#include <U8g2lib.h>  // Inclusão da biblioteca U8g2 para controle do display
#include <SPI.h>  // Inclusão da biblioteca SPI para comunicação serial

U8G2_ST7920_128X64_F_SW_SPI u8g2(
  /* rotação */ U8G2_R2,
  /* clock=*/ 18,
  /* data=*/ 23,
  /* CS=*/ 16,
  /* reset=*/ 17);  // Inicialização do display com os pinos especificados

void setup(void) {
  u8g2.begin();  // Inicializa o display
}

void loop(void) {
  u8g2.clearBuffer();  // Limpa o buffer do display
  u8g2.setFont(u8g2_font_ncenB14_tr);  // Define a fonte do texto
  u8g2.drawStr(0, 20, "ABC 123 !@#"); // Desenha o texto na posição especificada
  u8g2.sendBuffer();  // Envia o buffer para o display
}

Modo de página

O modo de buffer de página organiza a renderização gráfica em segmentos, chamados páginas, em um buffer de memória. Esta abordagem permite uma economia significativa de recursos de RAM. No entanto, devido à sua natureza segmentada, pode resultar em uma exibição mais lenta na tela em comparação com métodos mais diretos de atualização.

Análise Comparativa

O uso deste modo possui prós e contras:

Prós
  • todos os procedimentos gráficos podem ser usados;
  • apenas um pouco de RAM é necessário;
Contras
  • é Lento;
  • não é possível redesenhar apenas partes do conteúdo.

Uso

  1. Execute u8g2.firstPage();
  2. Inicie um loop do-while;
  3. Dentro do corpo do loop, desenhe algo com os comandos usuais de desenho;
  4. Faça um loop enquanto u8g2.nextPage() retornar verdadeiro.
#include <U8g2lib.h>  // Inclusão da biblioteca U8g2 para controle do display
#include <SPI.h>  // Inclusão da biblioteca SPI para comunicação serial

U8G2_ST7920_128X64_F_SW_SPI u8g2(
  /* rotação */ U8G2_R2,
  /* clock=*/ 18,
  /* data=*/ 23,
  /* CS=*/ 16,
  /* reset=*/ 17);  // Inicialização do display com os pinos especificados

void setup(void) {
  u8g2.begin();  // Inicializa o display
}

void loop(void) {
  u8g2.firstPage();
  do {
    u8g2.setFont(u8g2_font_ncenB14_tr); // Define a fonte do texto
    u8g2.drawStr(0,24,"Hello World!");// Desenha o texto na posição especificada
  } while ( u8g2.nextPage() );
}

Para mais detalhes sobre o “Modo de página”, consulte a página Loop de imagem da documentação da biblioteca.


Exibindo Imagem Bitmap no Display LCD 128x64

A exibição de imagens Bitmap em um display LCD 128×64 é uma tarefa que envolve diversas etapas, desde a preparação da imagem até a sua renderização no display. Inicialmente, é necessário converter a imagem desejada em um formato Bitmap compatível com as especificações do display. Para isso, utilizaremos o software de edição de imagens GIMP. Uma vez que o Bitmap esteja pronto, é preciso implementar o código necessário para enviar esses dados ao display LCD.

As instruções deste tópico resultará na seguinte exibição de imagem no display LCD 128×64:

Criando a Imagem

Utilizaremos o Canva para criar a imagem.

  1. Acesse o Canva e, após fazer login em sua conta (gratuita), crie um design de 128×64 pixels;
  2. Na aba Elementos, pesquise por arduino. Em seguida, na seção Elementos Gráficos, clique na imagem ARDUINO;
  3. Redimensione e centralize adequadamente a imagem no centro da tela;
  4. Clique no botão Compartilhar para acessar a funcionalidade de exportação (botão Baixar). Exporte o design como imagem png.

Convertendo a Imagem para X BitMap (código HEX)

Utilizaremos o GIMP para converter a imagem.

  1. Com o GIMP já baixado e instalado, abra a imagem criada anteriormente;
  2. Entre no menu Arquivo ➜ Exportar como…;
  3. Selecione o tipo do arquivo para exportar como sendo Imagem BitMap do X11 (extensão .xbm) e exporte a imagem;
  4. Abra o arquivo .xbm gerado com algum editor de texto, como o bloco de notas, e faça as seguintes alterações:
    • Altere a 3ª linha para static const unsigned char imagem_arduino [] PROGMEM = {;
    • Remova as duas primeiras linhas;

Exibindo a Imagem no display

Utilizaremos a função drawXBM para exibir a imagem no display.

  1. Com um novo sketch arduino na IDE Arduino, crie uma aba para um arquivo chamado imagem.h;
  2. Dentro do arquivo imagem.h insira o conteúdo do arquivo .xbm gerado anteriormente;
  3. No arquivo principal do projeto da IDE Arduino, insira o seguinte algoritmo:
    #include <U8g2lib.h>
    #include <SPI.h>
    #include "imagem.h"
    
    U8G2_ST7920_128X64_F_SW_SPI u8g2(U8G2_R2, /* clock=*/18, /* data=*/23, /* CS=*/16, /* reset=*/17);
    
    void setup() {
      u8g2.begin();
      delay(100);
      u8g2.clearBuffer();
      u8g2.drawXBM(0, 0, 128, 64, imagem_arduino);
      u8g2.sendBuffer();
    }
    
    void loop() {
      delay(1);
    }
  4. Faça upload do sketch para a placa ESP32 conectada no display LCD 128×64 (conectados conforme o esquemático incluído no tópico Conectando um display à biblioteca U8g2.

Habilitando Interface Web Lua do Media Player VLC

Para poder controlar o VLC player através do ESP32, é necessário ativar a interface Web Lua do media player VLC. Para isso, siga o passo a passo abaixo:

  1. Se não possuir o software VLC instalado em seu computador, faça o download dele neste link: videolan.org/vlc;
  2. Abra o media player VLC e após navegar no menu Ferramentas ➜ Preferências, defina a exibição de configurações como Detalhado;
  3. Nas categorias de configurações, acesse a opção Interfaces principais;
  4. Em Módulos adicionais de interface, ative a opção Web;
  5. Após navegar no menu Interfaces principais➜ Lua, em HTTP Lua, insira uma senha (será usada para dar segurança ao acesso do media player VLC na rede local);
  6. Após estas alterações, clique em Salvar;

Após o passo a passo anterior já é possível testar se houve sucesso na ativação da interface Web Lua do media player VLC:

  1. Reinicie o media player VLC;
  2. Em um Navegador de Internet (como o Google Chrome) acesse o seguinte endereço: http://<IP do seu PC>:8080/ (Exemplo: http://192.168.0.108:8080/ ; Para descobrir o IP do seu PC:
    • Pressione as teclas de atalho Win + R;
    • Digite cmd e clique em OK;
    • No prompt de comando que abrir, digite o comando ipconfig e tecle Enter;
    • Localize o seu Endereço IPv4;
  3. No navegador, ao ser solicitada as credenciais de acesso, digite a senha que foi inserida anteriormente em HTTP Lua (Nome de Usuário deve permanecer vazio) e clique em Fazer Login;

Se tudo tiver ocorrido bem, a interface Web Lua do media player VLC será aberta.


Obtendo Dados e Enviando Comandos para o Media Player VLC

As requisições para obter dados e para executar comandos são respondidas pelo media player VLC com uma resposta XML.

Obtendo dados

/requests/status.xml

A interface Web Lua do media player VLC realiza de tempos em tempos uma requisição ao media player VLC no endereço http://<IP do seu PC>:8080/requests/status.xml. Esta requisição obtém os dados da mídia que está sendo reproduzida no media player. Estes dados incluem:

  • volume da mídia;
  • posição atual da mídia;
  • tamanho da mídia;
  • status atual da mídia (se pausado ou tocando);
  • nome do álbum da mídia;
  • nome da mídia.
  • entre outros;

Estes dados da requisição estão no formato de arquivo .xml. Veja abaixo um exemplo do resultado de uma requisição enquanto uma mídia está tocando no media player VLC:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<root>
  <fullscreen>0</fullscreen>
  <seek_sec>10</seek_sec>
  <apiversion>3</apiversion>
  <currentplid>3</currentplid>
  <time>11</time>
  <volume>256</volume>
  <length>109</length>
  <random>false</random>
  <audiofilters>
    <filter_0></filter_0>
  </audiofilters>
  <rate>1</rate>
  <videoeffects>
    <hue>0</hue>
    <saturation>1</saturation>
    <contrast>1</contrast>
    <brightness>1</brightness>
    <gamma>1</gamma>
  </videoeffects>
  <state>playing</state>
  <loop>false</loop>
  <version>3.0.20 Vetinari</version>
  <position>0.10803920030594</position>
  <audiodelay>0</audiodelay>
  <repeat>false</repeat>
  <subtitledelay>0</subtitledelay>
  <equalizer></equalizer>
  <information>
    <category name="meta">
      <info name='album'>Biblioteca YouTube</info>
      <info name='filename'>Colony - TrackTribe.mp3</info>
      <info name='date'>2023</info>
      <info name='genre'>Ambiente</info>
      <info name='title'>Colony</info>
    </category>
    <category name='Transmissão 0'>
      <info name='Canais'>Estéreo</info>
      <info name='Tipo'>Áudio</info>
      <info name='Taxa de bits'>320 kb/s</info>
      <info name='Codificador'>MPEG Audio layer 1/2 (mpga)</info>
      <info name='Bits por amostra'>32</info>
      <info name='Taxa de amostragem'>44100 Hz</info>
    </category>
  </information>
  <stats>
    <lostabuffers>0</lostabuffers>
    <readpackets>164</readpackets>
    <lostpictures>0</lostpictures>
    <demuxreadbytes>516178</demuxreadbytes>
    <demuxbitrate>0.039991166442633</demuxbitrate>
    <playedabuffers>492</playedabuffers>
    <demuxcorrupted>0</demuxcorrupted>
    <sendbitrate>0</sendbitrate>
    <sentbytes>0</sentbytes>
    <displayedpictures>0</displayedpictures>
    <demuxreadpackets>0</demuxreadpackets>
    <sentpackets>0</sentpackets>
    <inputbitrate>0.039770554751158</inputbitrate>
    <demuxdiscontinuity>0</demuxdiscontinuity>
    <averagedemuxbitrate>0</averagedemuxbitrate>
    <decodedvideo>0</decodedvideo>
    <averageinputbitrate>0</averageinputbitrate>
    <readbytes>522624</readbytes>
    <decodedaudio>985</decodedaudio>
  </stats>
</root>

Enviando comandos

/requests/status.xml?command=pl_previous

Este comando permite reproduzir a faixa anterior na lista de reprodução (retroceder).

/requests/status.xml?command=pl_next

Este comando permite reproduzir a próxima faixa na lista de reprodução (avançar).

/requests/status.xml?command=volume&val=<VALOR>

Este comando permite definir o volume de reprodução da mídia. Em <VALOR>, substitua por um valor entre 0 e 512 (0= sem som, 512=volume máximo de 200%).

/requests/status.xml?command=pl_pause

Este comando permite pausar ou dar play na mídia em reprodução (a cada execução deste comando, a funcionalidade alterna entre pausar e dar play).

Outros comandos

Para descobrir outros comandos, siga os passos abaixo:

  1. Com a interface Web Lua aberta no navegador Google Chrome, abra as ferramentas de desenvolvimento com a tecla F12;
  2. Abra a aba Rede e, enquanto estiver aberta, clique no botão desejado da interface Web Lua;
  3. Ao botão ser clicado, aparecerá na aba Rede o comando que é atribuído ao botão clicado.

Exemplo da funcionalidade de Stop (/requests/status.xml?command=pl_stop), do media player Vlc:


Biblioteca tinyXML2: Acessando Elementos XML

Como as requisições (obter dados e executar comandos) para o media player VLC são respondidas com uma resposta no formato xml, deve-se obter uma forma facilitada para que o microcontrolador ESP32 possa analisar estas respostas. Isto é feito com a biblioteca tinyXML2.

A biblioteca tinyXML2 é uma poderosa ferramenta para manipulação de documentos XML de maneira eficiente e fácil. Ela é especialmente útil para programas C++ que necessitam ler, modificar e criar documentos XML. Vamos explorar como acessar elementos XML utilizando essa biblioteca.

Instalação e Inclusão da Biblioteca:

Para instalar a biblioteca tinyXML2 acesse github.com/leethomason/tinyxml2/releases e clique em Source code (zip) para fazer o download do arquivo .zip contendo a biblioteca. Em seguida, com a Arduino IDE aberta, navegue no menu Sketch ➜ Incluir Biblioteca ➜ Adicionar Biblioteca .ZIP. Então, selecione o arquivo .ZIP que foi baixado e clique em Abrir.

Após a instalação da Biblioteca, para utilizá-la, basta adicionar no começo do sketch a seguinte inclusão:

#include <tinyxml2.h>

Principais funções e Construtores

  • class XMLDocument: Classe tinyXML2 para manipulação eficiente de documentos XML em C++. Fornece métodos simples para carregar, acessar, modificar e criar elementos XML.
  • class XMLNode: Classe tinyXML2 que representa um nó em um documento XML. Utilize para navegar e manipular diferentes elementos dentro do XML.
  • class XMLElement: Classe tinyXML2 que representa um elemento específico em um documento XML. Use para acessar e modificar propriedades de elementos XML.
  • XMLError tinyxml2::XMLDocument::Parse (const char * xml, size_t nBytes = static_cast< size_t >(-1)): Este método analisa o texto XML fornecido, convertendo-o em uma estrutura de árvore XML. Retorna um código de erro indicando o resultado da análise XML ou XML_SUCCESS (0) em caso de sucesso.
    • Parâmetros:
      • const char * xml: O texto XML a ser analisado.
      • size_t nBytes = static_cast< size_t >(-1): Número opcional de bytes a serem considerados. O padrão é processar todo o texto.
  • const XMLNode * FirstChild(): Obtém o primeiro nó filho do elemento atual, ou null se não houver nenhum.
  • const XMLElement * tinyxml2::XMLNode::FirstChildElement (const char * name = 0): Obtém o primeiro elemento filho do nó atual, ou opcionalmente o primeiro elemento filho com o nome especificado, se nenhum elemento filho existir, ou se nenhum tiver o nome especificado, o retorno será null.
    • Parâmetros:
      • const char * name = 0: Nome opcional do elemento desejado.
  • XMLError tinyxml2::XMLElement::QueryIntText (int * ival): Método de conveniência para consultar o valor de um nó de texto filho como um inteiro. Este método é útil para extrair o valor de um nó de texto filho, convertendo-o para um inteiro. Retorna um código indicando o resultado da consulta (erro ou sucesso).
    • Parâmetros:
      • int * ival: Ponteiro para a variável onde o valor inteiro será armazenado.
  • const char * tinyxml2::XMLElement::Attribute(const char *name,  const char *value=0): Obtém o valor de um atributo com o nome especificado. Este método retorna um ponteiro constante para o valor do atributo com o nome fornecido. Se nenhum atributo com o nome especificado existir, o retorno será null.
    • Parâmetros:
      • const char *name: nome do atributo desejado.
      • const char *value=0: valor opcional que deve coincidir com o valor real do atributo.
  • const char * tinyxml2::XMLElement::GetText(): Função de conveniência para fácil acesso ao texto dentro de um elemento. Este método retorna o texto dentro de um elemento, se o primeiro filho for um XMLText. Se o primeiro filho não for um nó de texto, o retorno será null.
  • const XMLElement * NextSiblingElement (const char *name=0): Obtém o próximo elemento irmão (à direita) deste nó, opcionalmente com um nome específico. Este método retorna um ponteiro constante para o próximo elemento irmão à direita do nó atual. Se nenhum próximo elemento existir, ou se nenhum tiver o nome especificado, o retorno será null.
    • Parâmetros:
      • const char *name=0: Nome opcional do próximo elemento desejado.
  • XMLError QueryDoubleText (double *dval): Método de conveniência para consultar o valor de um nó de texto filho como um double. Este método é semelhante ao QueryIntText() e é utilizado para extrair o valor de um nó de texto filho, convertendo-o para um double. Retorna um código indicando o resultado da consulta (sucesso ou erro).
    • Parâmetros:
      • double *dval: ponteiro para a variável onde o valor double será armazenado.

Outras funções

Outras funções, classes, estruturas, uniões e interfaces podem ser vistas na documentação da biblioteca em leethomason.github.io/tinyxml2/annotated.html.


Esquemático do Projeto

Monte o circuito do projeto conforme o seguinte esquemático:


Sketch do Projeto

Clique aqui para fazer o download do sketch completo.

Antes de prosseguir, modifique o valor das seguintes variáveis no sketch:

  • Arquivo sketch.ino
    • ipServerVlc: substitua <IP_VLC> pelo endereço IP do servidor VLC (consulte Habilitando interface Web Lua do media player VLC para detalhes de como obter este IP);
    • portaServerVlc: insira aqui a porta de comunicação com o servidor VLC (normalmente, caso não tenha sido alterada, é a porta 8080);
  • Arquivo credenciais.h
    • ssid: substitua <SSID> pelo nome da sua rede Wi-Fi;
    • password: Substitua <SENHA> pela senha da sua rede Wi-Fi;
    • senhaVlc: Substitua <SENHA_VLC> pela senha utilizada para acessar a interface Web lua do media player VLC (consulte Habilitando interface Web Lua do media player VLC para detalhes de como obter a senha);

Em seguida, faça o upload do sketch para a placa ESP32.

Veja abaixo o sketch (contendo os arquivos sketch.ino, credenciais.h e splashScreen.h):

/******************************************************************************
    Controlando o media player VLC de um PC via joystick no ESP32
                        Criado em 16 de Janeiro de 2024
                     por Michel Galvão (https://micsg.com.br)

                              Sketch Principal

  Eletrogate - Loja de Arduino \\ Robótica \\ Automação \\ Apostilas \\ Kits
                            https://www.eletrogate.com/
******************************************************************************/

//Inclusão das bibliotecas
#include <WiFi.h>
#include <HTTPClient.h>
#include "credenciais.h"
#include "splashScreen.h"
#include <U8g2lib.h>
#include <SPI.h>
#include <tinyxml2.h>

// Protótipos das funções
void showAlbum();
void showNome();
void atualizaVariaveis(String textXML);
String requestVlc();
int getVolume();
void setStatusPlayer(bool status);
void setVolume(bool comVerificacao = true); // deixa a fução com protótipo opcional
void showPosicao();
void showVolume();
void showPlayPause();
uint8_t showAvancar();
uint8_t showRetroceder();
bool verificarConexaoVlc();

// Instanciação dos objetos das classes das biblitecas
U8G2_ST7920_128X64_F_SW_SPI u8g2(U8G2_R2, /* clock=*/18, /* data=*/23, /* CS=*/16, /* reset=*/17);

//// Definição dos pinos do Joystick
const int pushButton = 32;
const int joyY = 36;
const int joyX = 39;

// definição de veriáveis de controle e armazenamento
bool statusPlayer = false;   // indica o estado do player: true para reproduzindo, false para pausado
int volume = 0;              // nível de volume atual (local [ESP32])
int volumeVLC = 0;           // nível de volume do VLC (online [media player VLC])
int textNomeX = 10;          // posição inicial do texto do nome na tela
float position = 0.0;        // posição atual da mídia em reprodução
int lengthMidia = 0;         // comprimento total da mídia em reprodução
String nomeMidia;            // nome da mídia atual
String albumMidia;           // álbum da mídia atual
unsigned long timerGetHttp;  // temporizador para controle de solicitações HTTP
unsigned long timerNome;     // temporizador para controle de atualização do nome
bool conexaoPerdida = false; // indica se a conexão com o VLC foi perdida ou não

// definição de constantes
const String ipServerVlc = "<IP_VLC>";  // endereço IP do servidor VLC
const String portaServerVlc = "8080";        // porta de comunicação com o servidor VLC

void setup() {
  Serial.begin(115200);// inicialização da comunicação serial

  // Inicialização do display
  u8g2.begin(); // inicialização do objeto u8g2 para controle do display
  delay(100);   // delay obrigatório para inicialização correta do display

  // Exibição da SplashScreen no display
  u8g2.clearBuffer();                        // limpeza do buffer do display
  u8g2.drawXBM(0, 0, 128, 64, splashScreen); // desenho da tela de SplashScreen
  u8g2.sendBuffer();                         // envio do buffer para o display
  delay(2000);                               // aguardo de 2 segundos

  // Configuração do WiFi e exibição do status do WiFi no display
  u8g2.clearBuffer();  // limpeza do buffer do display
  u8g2.setFont(u8g2_font_ncenB08_tr);  // definição da fonte para o texto principal
  u8g2.enableUTF8Print();  // habilita o suporte UTF-8 para a função print() do display
  u8g2.drawStr(0, 10, "Conectando ao WiFi...");  // exibição do texto no display
  u8g2.setFont(u8g2_font_squeezed_b6_tr);  // definição da fonte para informações menores
  u8g2.setCursor(0, 20);
  u8g2.print(ssid);  // exibição do SSID no display
  u8g2.sendBuffer();  // envio do buffer para o display
  WiFi.begin(ssid, password);  // início da conexão WiFi
  u8g2.setCursor(0, 30);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    u8g2.print(".");
    u8g2.sendBuffer();
  }
  u8g2.setCursor(0, 40);
  u8g2.print("OK");  // exibição do status de conexão bem-sucedida no display
  u8g2.sendBuffer();  // envio do buffer para o display
  delay(2000);  // aguardo de 2 segundos

  // Verificação da conexão com media player VLC
  u8g2.clearBuffer();  // Limpa o buffer do display
  u8g2.setFont(u8g2_font_ncenB08_tr);  // Define a fonte para exibição de texto
  u8g2.drawStr(0, 10, "Conectando ao VLC...");  // Exibe mensagem no display
  u8g2.setFont(u8g2_font_squeezed_b6_tr);  // Define outra fonte para exibição de texto
  u8g2.setCursor(0, 20);
  u8g2.print(ipServerVlc);  // Exibe o IP do servidor VLC no display
  u8g2.sendBuffer();  // Envia o buffer para o display
  u8g2.setCursor(0, 30);
  do {
    delay(500);  // Aguarda 500 milissegundos
    u8g2.print(".");  // Exibe um ponto no display
    u8g2.sendBuffer();  // Envia o buffer atualizado para o display
  } while (verificarConexaoVlc() != true);  // Continua o loop até que a conexão com o servidor VLC seja estabelecida
  u8g2.setCursor(0, 40);
  u8g2.print("OK");  // Exibe "OK" no display
  u8g2.sendBuffer();  // Envia o buffer atualizado para o display
  delay(1000);  // Aguarda 1000 milissegundos (1 segundo)

  // Limpa o display
  u8g2.clear();

  // Configura o pino de leitura do botão do joystick
  pinMode(pushButton, INPUT_PULLUP);

  // Cria a tarefa "loop2()" com uma pilha de 10000 bytes, prioridade 1, e a associa ao core 0 do processador
  xTaskCreatePinnedToCore(loop2, "loop2", 10000, NULL, 1, NULL, 0);
}


void loop() { // destinado ao envio/recebimento de dados HTTP
  // Timer das requisições GET da interface Web do media player VLC
  if (millis() - timerGetHttp > 500) {// Verifica se o intervalo de tempo para a próxima requisição GET da interface Web do media player VLC foi atingido

    HTTPClient http;// Inicialização do objeto HTTPClient
    String url;  // Declara uma variável do tipo String chamada url
    // Concatenações na variável url
    url += "http://";
    url += ipServerVlc;
    url += ":";
    url += portaServerVlc;
    url += "/requests/status.xml";
    http.begin(url);  // Inicia a comunicação HTTP com a URL especificada
    http.setAuthorization("", senhaVlc);  // Define a autorização para a requisição HTTP (usuário vazio, senha senhaVlc)
    http.setConnectTimeout(100);  // Define o tempo limite de conexão para 100 milissegundos

    int httpCode = http.GET();// Realiza uma requisição HTTP GET e armazena o código de resposta na variável httpCode

    // O cabeçalho HTTP foi enviado e o cabeçalho de resposta do servidor foi tratado
    if (httpCode > 0) {

      if (httpCode == HTTP_CODE_OK) { // O endereço foi encontrado no servidor
        conexaoPerdida = false;  // Define a variável conexaoPerdida como false, indicando que a conexão não foi perdida
        String response = http.getString();  // Obtém a resposta do servidor como uma String
        atualizaVariaveis(response);  // atualiza as variáveis de armazenamento chamando a função atualizaVariaveis passando a resposta do servidor como argumento

      } else if (httpCode == HTTP_CODE_UNAUTHORIZED) { // se não tiver permissão para acessar o endereço no servidor
        Serial.println("As credenciais de acesso estão incorretas. Corrija-as e tente novamente."); // Imprime mensagem no monitor serial
        delay(5000);// Aguarda 5 segundos
      }
    } else { // se houve algum outro erro, ...
      Serial.println("[HTTP] GET... falhado");
      conexaoPerdida = true; // Define a variável conexaoPerdida como true, indicando que a conexão com o VLC foi perdida
    }

    http.end(); // Finaliza a conexão HTTP

    timerGetHttp = millis(); // Atualiza o timer para controle do intervalo entre requisições HTTP
  }
  delay(1); // delay antitravamento do core do ESP32
}

void loop2(void* z) { // destinado ao gerenciamento da interface do usuário 9dispaly e joystick)

  // Mostra pela primeira vez a interface no display
  showRetroceder(); // mostra no display o ícone de Retroceder
  showAvancar(); // mostra no display o ícone de Avançar
  showPlayPause(); // mostra no display o ícone de Play/Pause
  showVolume(); // mostra no display o Volume
  showPosicao(); // mostra no display a Posição da mídia
  u8g2.sendBuffer(); // Envia o buffer atualizado para o display

  while (1) { // entra em um loop infinito

    if (conexaoPerdida) { // Verificação e espera da reconexão em caso de perda de conexão
      u8g2.clear();  // Limpa o display
      u8g2.clearBuffer();  // Limpa o buffer do display
      u8g2.setFont(u8g2_font_ncenB08_tr);  // Define a fonte para exibição de texto
      u8g2.drawStr(0, 10, "Reconectando...");  // Exibe mensagem no display
      u8g2.setCursor(0, 30);  // Configura a posição do cursor no display
      u8g2.sendBuffer();  // Envia o buffer para o display

      while (conexaoPerdida) { // enquanto houver conexão perdida
        delay(1500);  // Aguarda 1,5 segundos
        u8g2.print(".");  // Exibe um ponto no display
        u8g2.sendBuffer();  // Envia o buffer para o display
      }

      u8g2.setCursor(0, 40);  // Posiciona o cursor no display
      u8g2.print("OK");  // Exibe "OK" no display
      u8g2.sendBuffer();  // Envia o buffer para o display
      delay(1000);  // Aguarda 1 segundo


      u8g2.clear(); // Limpa o conteúdo do display
    }

    if (analogRead(joyX) > 4000) { // se a leitura analógica do pino do eixo X do joystick for maior que 4000, ...

      Serial.println("Para esquerda");  // Imprime "Para esquerda" na porta serial

      uint8_t width = showRetroceder();  // Mostra o elemento retroceder e obtém a largura dele
      u8g2.drawFrame(31, 51, width + 1, 13);  // Desenha um quadro na posição especificada com base na largura obtida
      u8g2.sendBuffer();  // Envia o buffer para o display
      textNomeX = 10;  // Define o valor de textNomeX como 10

      // Se o joystick continuar pressionado, nehuma ação de retroceder deve ser tomada, apenas atualizações da interface do display deve ser feita
      while (analogRead(joyX) > 4000) {
        delay(1);  // Aguarda 1 milissegundo
        showVolume();  // Exibe o volume
        showPosicao();  // Exibe a posição
        showNome();  // Exibe o nome
        showAlbum();  // Exibe o álbum
        u8g2.sendBuffer();  // Envia o buffer para o display
      }

      HTTPClient http;  // Instanciação do objeto HTTPClient
      String url;  // Declaração da variável String para armazenar a URL

      // Construção da URL com o endereço IP do servidor VLC e a porta 8080
      url += "http://";
      url += ipServerVlc;
      url += ":8080/requests/status.xml?command=pl_previous";

      http.begin(url);  // Inicialização da conexão HTTP com a URL construída
      http.setAuthorization("", senhaVlc);  // Configuração da autorização (usuário vazio e senha senhaVlc)


      int httpCode = http.GET();  // Envio da solicitação HTTP tipo GET e armazenamento do código de resposta

      if (httpCode > 0) {
        // Verificação do código de resposta
        if (httpCode == HTTP_CODE_UNAUTHORIZED) {
          Serial.println("As credenciais de acesso estão incorretas. Corrija-as e tente novamente.");
          delay(5000);
        }
      } else {
        // Tratamento de erro em caso de falha na requisição
        Serial.printf("[HTTP] GET... falhou, erro: %s\n", http.errorToString(httpCode).c_str());
      }

      http.end();  // Finalização da conexão HTTP

      u8g2.setDrawColor(0);  // Definição da cor de desenho como 0 (preto)
      u8g2.drawFrame(31, 51, width + 1, 13);  // Desenho do contorno do quadro na posição especificada
      u8g2.setDrawColor(1);  // Restauração da cor de desenho para 1 (branco)
      u8g2.sendBuffer();  // Envio do buffer para o display

    }

    if (analogRead(joyX) < 100) { // se a leitura analógica do pino do eixo X do joystick for menor que 100, ...

      Serial.println("Para direita");  // Imprime mensagem no monitor serial

      textNomeX = 10;  // Define a posição inicial do texto
      uint8_t width = showAvancar();  // Mostra o elemento avançar e obtém a largura dele
      u8g2.drawFrame(80, 51, width + 1, 13);  // Desenha um quadro ao redor
      u8g2.sendBuffer();  // Envia o buffer para o display

      // Se o joystick continuar pressionado, nehuma ação de avançar deve ser toamda, apenas atualizações da interface do display deve ser feita
      while (analogRead(joyX) < 100) {
        delay(1);       // Aguarda 1 milissegundo
        showVolume();   // Exibe o volume no display
        showPosicao();  // Exibe a posição no display
        showNome();     // Exibe o nome no display
        showAlbum();    // Exibe o álbum no display
        u8g2.sendBuffer();  // Envia o buffer para o display
      }

      HTTPClient http;   // Objeto para fazer requisições HTTP
      String url;        // String para armazenar a URL da requisição
      url += "http://";
      url += ipServerVlc;
      url += ":8080/requests/status.xml?command=pl_next";
      http.begin(url);    // Inicia a conexão HTTP
      http.setAuthorization("", senhaVlc);  // Configura a autorização (usuário e senha)

      int httpCode = http.GET(); // Envia a requisição GET e armazena o código de resposta

      if (httpCode > 0) { // Verifica se a resposta foi recebida com sucesso
        if (httpCode == HTTP_CODE_UNAUTHORIZED) { // caso a resposta seja informando que a requisição não tem autorização de acesso, ...
          Serial.println("As credenciais de acesso estão incorretas. Corrija-as e tente novamente.");
          delay(5000); // pausa de 5 segundos
        }
      } else { // se a resposta foi um erro, ...
        Serial.printf("[HTTP] GET... falhado, erro: %s\n", http.errorToString(httpCode).c_str()); // mostra o erro na Serial
      }
      http.end();// Encerra a conexão HTTP

      u8g2.setDrawColor(0);  // define a cor de desenho como 0 (preto)
      u8g2.drawFrame(80, 51, width + 1, 13);  // desenha um retângulo transparente na posição especificada
      u8g2.setDrawColor(1);  // define a cor de desenho para 1 (branco)
      u8g2.sendBuffer();  // envia o buffer para o display

    }

    if (analogRead(joyY) < 100) {  // se a leitura analógica do pino do eixo Y do joystick for menor que 100, ...
      unsigned long timer = millis(); // obtém o tempo atual em milissegundos

      bool flag = true; // variável para controle do volume
      while (analogRead(joyY) < 100) { // enquanto a leitura do joystick indica movimento para cima
        Serial.println("Para cima");

        showVolume();  // exibe o volume no display
        showPosicao();  // exibe a posição no display
        u8g2.sendBuffer();  // envia o buffer para o display

        if (millis() - timer > 1000) { // se passou mais de 1 segundo desde o último ajuste de volume, aumenta o volume em velocidade maior
          volumeVLC = volume;
          volume += 10;// aumenta o volume em 10 unidades
          if (volume > 512) {
            volume = 512; // limita o volume máximo a 512
          }

          setVolume(); // ajusta o volume no VLC
        } else { // se não, aumenta o volume em velocidade normal
          volumeVLC = volume;

          if (flag == true) {
            flag = false;
            if (volume < 512) {
              volume += 1;  // aumenta o volume em 1 unidade
              setVolume();  // ajusta o volume no VLC
            }
          } else {
            if (millis() - timer > 500) {
              if (volume < 512) {
                volume += 1;// aumenta o volume em 1 unidade
                setVolume();// ajusta o volume no VLC
              }
            }
          }
        }
        delay(1);// aguarda 1 milissegundo
      }
    }

    if (analogRead(joyY) > 4000) {  // se a leitura analógica do pino do eixo Y do joystick for maior que 4000, ...
      unsigned long timer = millis(); // obtém o tempo atual em milissegundos

      bool flag = true; // sinalizador para controle do volume
      while (analogRead(joyY) > 4000) { // enquanto a leitura do joystick indica movimento para baixo
        Serial.println("Para baixo");

        showVolume();  // exibe o volume no display
        showPosicao();  // exibe a posição no display
        u8g2.sendBuffer();  // envia o buffer para o display

        if (millis() - timer > 1000) { // se passou mais de 1 segundo desde o último ajuste de volume
          volumeVLC = volume;
          volume -= 10; // reduz o volume em 10 unidades
          if (volume < 0) {
            volume = 0; // limita o volume mínimo a 0
          }
          setVolume(); // ajusta o volume no VLC


        } else {
          volumeVLC = volume;
          if (flag == true) {
            flag = false;
            if (volume > 0) {
              volume -= 1;  // reduz o volume em 1 unidade
              setVolume();  // ajusta o volume no VLC
            }
          } else {
            if (millis() - timer > 500) {
              if (volume > 0) {
                volume -= 1;  // reduz o volume em 1 unidade
                setVolume();  // ajusta o volume no VLC
              }
            }
          }
        }

        delay(1); // aguarda 1 milissegundo
      }
    }

    if (digitalRead(pushButton) == false) {  // se o botão foi pressionado
      Serial.println("Clique");  // exibe mensagem no console

      setStatusPlayer(!statusPlayer);  // inverte o estado do player (play/pause)
      showPlayPause();  // exibe o ícone de play/pause no display
      u8g2.sendBuffer();  // envia o buffer para o display

      while (digitalRead(pushButton) == false) {  // enquanto o botão estiver pressionado
        delay(1);  // aguarda 1 milissegundo
        showVolume();  // exibe o volume no display
        showPosicao();  // exibe a posição no display
        u8g2.sendBuffer();  // envia o buffer para o display
      }
    }
    delay(1);  // aguarda 1 milissegundo

    // Atualiza a interface no display
    showNome();// mostra no display o nome da mídia
    showAlbum();// mostra no display o nome do álbum da mídia
    showRetroceder(); // mostra no display o ícone de Retroceder
    showAvancar(); // mostra no display o ícone de Avançar
    showPlayPause(); // mostra no display o ícone de Play/Pause
    showVolume(); // mostra no display o Volume
    showPosicao(); // mostra no display a Posição da mídia
    u8g2.sendBuffer(); // Envia o buffer atualizado para o display
  }
  delay(1); // aguarda 1 milissegundo

}
bool verificarConexaoVlc() {
  HTTPClient http;  // Cria uma instância do objeto HTTPClient para fazer requisições HTTP
  http.begin("http://" + ipServerVlc + ":" + portaServerVlc);  // Inicia a comunicação HTTP com o servidor VLC
  int httpCode = http.GET();  // Realiza uma requisição GET e armazena o código de resposta

  http.end();  // Encerra a conexão HTTP

  if (httpCode > 0) {  // Verifica se o código de resposta é maior que zero (indica uma resposta válida)
    return true;  // Retorna verdadeiro se a conexão for bem-sucedida
  }

  return false;  // Retorna falso se a conexão não for bem-sucedida ou ocorrer um erro
}


uint8_t showRetroceder() {
  u8g2.setFont(u8g2_font_unifont_t_symbols);  // Define a fonte para unifont_t_symbols
  uint16_t encoding = 9184 + 15 - 1;  // Calcula o valor de codificação para o símbolo de retroceder
  return u8g2.drawGlyph(32, 62, encoding);  // Desenha o símbolo na posição (32, 62) no display gráfico
}

uint8_t showAvancar() {
  u8g2.setFont(u8g2_font_unifont_t_symbols);  // Define a fonte para unifont_t_symbols
  uint16_t encoding = 9184 + 14 - 1;  // Calcula o valor de codificação para o símbolo de avançar
  return u8g2.drawGlyph(81, 62, encoding);  // Desenha o símbolo na posição (81, 62) no display gráfico
}

void showPlayPause() {
  u8g2.setFont(u8g2_font_unifont_t_symbols);  // Define a fonte para unifont_t_symbols
  u8g2.setCursor(0, 0);  // Define a posição do cursor no display gráfico

  if (statusPlayer == true) {
    u8g2.drawGlyph(56, 63, 9200 + 9 - 1);  // Desenha o símbolo de reprodução na posição (56, 63)
  } else {
    u8g2.drawGlyph(57, 63, 9200 + 6 - 1);  // Desenha o símbolo de pausa na posição (57, 63)
  }
}

void showVolume() {
  // Desenha o contorno externo do indicador de volume
  u8g2.drawFrame(2, 2, 6, 60);

  // Define a fonte para u8g2_font_squeezed_b6_tr
  u8g2.setFont(u8g2_font_squeezed_b6_tr);
  u8g2.setFontDirection(0);

  // Desenha o contorno interno do indicador de volume
  u8g2.drawFrame(4, 4, 2, 56);

  // Calcula a altura do indicador com base no volume
  int vol = round((volume / 5.12) * 2);
  int h = map(vol, 0, 200, 55, 0);

  // Define a cor de desenho como preto
  u8g2.setDrawColor(0);

  // Desenha o indicador de volume preenchido
  if (vol == 0) {
    u8g2.drawFrame(4, 4, 2, 56);
  } else {
    u8g2.drawFrame(4, 4, 2, h);
  }

  // Define a cor de desenho como preto
  u8g2.setDrawColor(0);

  // Desenha a parte inferior do indicador
  u8g2.drawBox(9, 56, 15, 6);

  // Define a cor de desenho como branco
  u8g2.setDrawColor(1);

  // Posiciona o cursor e imprime o valor do volume
  u8g2.setCursor(9, 62);
  u8g2.print(vol);
}

void showPosicao() {
  u8g2.drawFrame(10, 40, 116, 6);  // desenha um retângulo para indicar a barra de progresso
  u8g2.setDrawColor(0);  // define a cor de desenho como 0 (preto)
  u8g2.drawFrame(12, 42, 112, 2);  // desenha a linha de progresso
  u8g2.drawBox(10, 30, 116, 10);  // desenha a área de fundo da barra de progresso
  u8g2.setDrawColor(1);  // redefine a cor de desenho como 1 (branco)
  u8g2.setFont(u8g2_font_squeezed_b6_tr);  // define a fonte para o texto

  // Calcula e exibe o tempo atual no formato MM:SS
  int minutos = int(position * lengthMidia) / 60;  // calcula os minutos
  int segundos = int(position * lengthMidia) % 60;  // calcula os segundos
  String tempoFormatado = minutos < 10 ? "0" + String(minutos) : String(minutos);  // formata os minutos
  tempoFormatado += ":";  // adiciona dois pontos entre minutos e segundos
  tempoFormatado += segundos < 10 ? "0" + String(segundos) : String(segundos);  // formata os segundos
  u8g2.setCursor(11, 37);  // define a posição para exibir o tempo
  u8g2.print(tempoFormatado);  // exibe o tempo formatado

  // Calcula e exibe o tempo total da mídia no formato MM:SS
  minutos = lengthMidia / 60;  // calcula os minutos totais
  segundos = lengthMidia % 60;  // calcula os segundos totais
  tempoFormatado = minutos < 10 ? "0" + String(minutos) : String(minutos);  // formata os minutos
  tempoFormatado += ":";  // adiciona dois pontos entre minutos e segundos
  tempoFormatado += segundos < 10 ? "0" + String(segundos) : String(segundos);  // formata os segundos
  u8g2.setCursor(128 - 2 - u8g2.getStrWidth(tempoFormatado.c_str()), 37);  // define a posição para exibir o tempo total
  u8g2.print(tempoFormatado);  // exibe o tempo total formatado

  // Calcula e desenha a porcentagem de progresso na barra
  float percentual = position * 100;  // calcula a porcentagem de progresso
  int w = map(percentual, 0, 100, 0, 112);  // mapeia a porcentagem para a largura da barra
  u8g2.drawFrame(12, 42, w, 2);  // desenha a parte preenchida da barra
}

void setVolume(bool comVerificacao) {  // Define a função 'setVolume' com um parâmetro booleano 'comVerificacao'

  int volumeAtual = volumeVLC;  // Declara e inicializa a variável 'volumeAtual' com o valor do volume do VLC
  if (volume != 0) {  // Verifica se o volume não é zero
    while (round((volumeAtual / 5.12) * 2) == round((volume / 5.12) * 2)) {  // Inicia um loop enquanto o volume do VLC não é alterado

      bool flag;  // Declara uma variável booleana 'flag'

      if (volume < volumeAtual) {
        flag = true;  // Atribui verdadeiro à 'flag' se o volume é menor que 'volumeAtual'
      } else {
        flag = false;  // Atribui falso à 'flag' se o volume não é menor que 'volumeAtual'
      }

      if (flag != true) {  // Verifica se 'flag' não é verdadeiro
        if (volume < 512) {
          volume++;  // Incrementa o volume se for menor que 512
        } else {
          break;  // Sai do loop se o volume atingir 512
        }

      } else {
        if (volume > 0) {
          volume--;  // Decrementa o volume se for maior que zero
        } else {
          break;  // Sai do loop se o volume atingir zero
        }
      }
    }
  }
  showVolume();  // Chama a função 'showVolume' para exibir o volume no display

  HTTPClient http;  // Declara uma instância do cliente HTTP

  String url;  // Declara uma variável String 'url'
  url += "http://";
  url += ipServerVlc;  // Concatena o endereço IP do servidor VLC à 'url'
  url += ":8080/requests/status.xml?command=volume&val=";  // Concatena o restante da URL para ajustar o volume
  url += volume;  // Concatena o valor do volume à 'url'

  http.begin(url);  // Inicia a conexão HTTP com a 'url'
  http.setAuthorization("", senhaVlc);  // Configura as credenciais de acesso

  int httpCode = http.GET();  // Envia uma solicitação HTTP GET e armazena o código de resposta

  if (httpCode > 0) {  // Verifica se a solicitação foi bem-sucedida
    if (httpCode == HTTP_CODE_UNAUTHORIZED) {
      Serial.println("As credenciais de acesso estão incorretas. Corrija-as e tente novamente.");  // Imprime uma mensagem se as credenciais estiverem incorretas
      delay(5000);  // Aguarda 5 segundos em caso de credenciais incorretas
    }
  } else {
    Serial.printf("[HTTP] GET... falhado, erro: %s\n", http.errorToString(httpCode).c_str());  // Imprime uma mensagem de erro em caso de falha na solicitação HTTP
  }
  http.end();  // Encerra a conexão HTTP
}

void setStatusPlayer(bool status) {  // Define a função 'setStatusPlayer' com um parâmetro booleano 'status'

  HTTPClient http;  // Declara uma instância do cliente HTTP

  String url;  // Declara uma variável String 'url'
  url += "http://";
  url += ipServerVlc;  // Concatena o endereço IP do servidor VLC à 'url'
  url += ":8080/requests/status.xml?command=pl_pause";  // Concatena o restante da URL para pausar ou retomar a reprodução
  http.begin(url);  // Inicia a conexão HTTP com a 'url'
  http.setAuthorization("", senhaVlc);  // Configura as credenciais de acesso

  int httpCode = http.GET();  // Envia uma solicitação HTTP GET e armazena o código de resposta

  if (httpCode > 0) {  // Verifica se a solicitação foi bem-sucedida
    if (httpCode == HTTP_CODE_OK) {
      String response = http.getString();  // Obtém a resposta do servidor e armazena em 'response'

      atualizaVariaveis(response);  // Chama a função 'atualizaVariaveis' para atualizar as variáveis com base na resposta do servidor

    } else if (httpCode == HTTP_CODE_UNAUTHORIZED) {
      Serial.println("As credenciais de acesso estão incorretas. Corrija-as e tente novamente.");  // Imprime uma mensagem se as credenciais estiverem incorretas
      delay(5000);  // Aguarda 5 segundos em caso de credenciais incorretas
    }
  } else {
    Serial.printf("[HTTP] GET... falhado, erro: %s\n", http.errorToString(httpCode).c_str());  // Imprime uma mensagem de erro em caso de falha na solicitação HTTP
  }
  http.end();  // Encerra a conexão HTTP
}

int getVolume() {  // Define a função 'getVolume' que retorna um inteiro

  int vol;  // Declaração da variável inteira 'vol'
  String textXML;  // Declaração da variável String 'textXML' para armazenar o XML

  HTTPClient http;  // Declaração de uma instância do cliente HTTP
  String url;  // Declaração da variável String 'url'
  url += "http://";
  url += ipServerVlc;  // Concatena o endereço IP do servidor VLC à 'url'
  url += ":8080/requests/status.xml";  // Concatena o restante da URL para obter o status
  http.begin(url);  // Inicia a conexão HTTP com a 'url'
  http.setAuthorization("", senhaVlc);  // Configura as credenciais de acesso

  int httpCode = http.GET();  // Envia uma solicitação HTTP GET e armazena o código de resposta

  if (httpCode > 0) {  // Verifica se a solicitação foi bem-sucedida
    if (httpCode == HTTP_CODE_OK) {  // Verifica se o código de resposta é OK
      textXML = http.getString();  // Obtém a resposta do servidor e armazena em 'textXML'
      String aRemover = textXML.substring(textXML.indexOf("<?xml"), textXML.indexOf(" ?>") + 3);  // Encontra e remove a declaração XML

      textXML.replace(aRemover, "");  // Remove a declaração XML da string 'textXML'

      tinyxml2::XMLDocument xmlDocument;  // Declaração de um documento XML
      if (xmlDocument.Parse(textXML.c_str()) != tinyxml2::XML_SUCCESS) {  // Verifica se o parsing do XML foi bem-sucedido
        Serial.println("Erro ao fazer parsing");  // Imprime uma mensagem em caso de erro no parsing
      } else {
        tinyxml2::XMLNode* root = xmlDocument.FirstChild();  // Obtém o nó raiz do documento XML
        tinyxml2::XMLElement* element = root->FirstChildElement("volume");  // Obtém o elemento 'volume' do XML
        element->QueryIntText(&vol);  // Converte o texto do elemento 'volume' para um inteiro e armazena em 'vol'
      }

    } else if (httpCode == HTTP_CODE_UNAUTHORIZED) {
      Serial.println("As credenciais de acesso estão incorretas. Corrija-as e tente novamente.");  // Imprime uma mensagem se as credenciais estiverem incorretas
      delay(5000);  // Aguarda 5 segundos em caso de credenciais incorretas
    }
  } else {
    Serial.printf("[HTTP] GET... falhou, erro: %s\n", http.errorToString(httpCode).c_str());  // Imprime uma mensagem de erro em caso de falha na solicitação HTTP
  }
  http.end();  // Encerra a conexão HTTP
  return vol;  // Retorna o valor do volume obtido do servidor VLC
}

String requestVlc() {  // Define a função 'requestVlc' que retorna uma String

  String response;  // Declaração da variável String 'response' para armazenar a resposta do servidor VLC

  HTTPClient http;  // Declaração de uma instância do cliente HTTP
  String url;  // Declaração da variável String 'url'
  url += "http://";
  url += ipServerVlc;  // Concatena o endereço IP do servidor VLC à 'url'
  url += ":8080/requests/status.xml";  // Concatena o restante da URL para obter o status
  http.begin(url);  // Inicia a conexão HTTP com a 'url'
  http.setAuthorization("", senhaVlc);  // Configura as credenciais de acesso

  int httpCode = http.GET();  // Envia uma solicitação HTTP GET e armazena o código de resposta

  if (httpCode > 0) {  // Verifica se a solicitação foi bem-sucedida
    if (httpCode == HTTP_CODE_OK) {  // Verifica se o código de resposta é OK
      response = http.getString();  // Obtém a resposta do servidor e armazena em 'response'

    } else if (httpCode == HTTP_CODE_UNAUTHORIZED) {
      Serial.println("As credenciais de acesso estão incorretas. Corrija-as e tente novamente.");  // Imprime uma mensagem se as credenciais estiverem incorretas
      delay(5000);  // Aguarda 5 segundos em caso de credenciais incorretas
    }
  } else {
    Serial.printf("[HTTP] GET... falhou, erro: %s\n", http.errorToString(httpCode).c_str());  // Imprime uma mensagem de erro em caso de falha na solicitação HTTP
  }
  http.end();  // Encerra a conexão HTTP
  return response;  // Retorna a resposta obtida do servidor VLC
}

void atualizaVariaveis(String textXML) {  // Define a função 'atualizaVariaveis' que recebe uma String 'textXML'

  String aRemover = textXML.substring(textXML.indexOf("<?xml"), textXML.indexOf(" ?>") + 3);  // Obtém a substring da declaração XML da String

  textXML.replace(aRemover, "");  // Remove a declaração XML da String 'textXML'

  tinyxml2::XMLDocument xmlDocument;  // Declaração de uma instância da classe XMLDocument do TinyXML2
  if (xmlDocument.Parse(textXML.c_str()) != tinyxml2::XML_SUCCESS) {  // Verifica se o parsing do XML foi bem-sucedido
    Serial.println("Erro ao fazer o parsing do XML");  // Imprime uma mensagem de erro em caso de falha no parsing

  } else {
    tinyxml2::XMLNode* root = xmlDocument.FirstChild();  // Obtém o nó raiz do XML
    tinyxml2::XMLElement* element = root->FirstChildElement("volume");  // Obtém o elemento 'volume' do XML
    int _volume;

    element->QueryIntText(&_volume);  // Extrai o valor do volume do XML

    String _state = root->FirstChildElement("state")->GetText();  // Obtém o estado (playing/pausado) do XML

    tinyxml2::XMLElement* categoriaElementos = root->FirstChildElement("information")
        ->FirstChildElement("category");  // Obtém o primeiro elemento 'category' dentro de 'information'

    while (categoriaElementos) {  // Loop para percorrer todos os elementos 'category'
      tinyxml2::XMLElement* infoElementos = categoriaElementos->FirstChildElement("info");  // Obtém o primeiro elemento 'info' dentro de 'category'
      bool atualizaNomeMidia = true;
      while (infoElementos) {  // Loop para percorrer todos os elementos 'info' dentro de 'category'
        const char* attributeName = infoElementos->Attribute("name");  // Obtém o atributo 'name' do elemento 'info'

        if (attributeName && strcmp(attributeName, "title") == 0) { // caso o atributo 'name' seja "title"
          nomeMidia = infoElementos->GetText();  // Atualiza o nome da mídia
          atualizaNomeMidia = false;
        } else if (atualizaNomeMidia && attributeName && strcmp(attributeName, "filename") == 0) { // se o atributo 'name' for "filename" e ainda não foi atualizado
          nomeMidia = infoElementos->GetText();  // Atualiza o nome da mídia
        }

        if (attributeName && strcmp(attributeName, "album") == 0) { // se o atributo 'name' for "album"
          albumMidia = infoElementos->GetText();  // Atualiza o álbum da mídia
        }
        infoElementos = infoElementos->NextSiblingElement("info");  // Move para o próximo elemento 'info'
      }
      categoriaElementos = categoriaElementos->NextSiblingElement("category");  // Move para o próximo elemento 'category'
    }

    volume = _volume;  // Atualiza a variável 'volume' com o valor obtido do XML
    root->FirstChildElement("position")->QueryFloatText(&position);  // Obtém e atualiza a posição da mídia do XML
    root->FirstChildElement("length")->QueryIntText(&lengthMidia);  // Obtém e atualiza o comprimento da mídia do XML

    statusPlayer = _state == "playing" ? true : false;  // Atualiza o status do player com base no estado obtido do XML
  }
}

void showNome() {  // Define a função 'showNome'

  u8g2.setFont(u8g2_font_unifont_t_symbols);  // Define a fonte do display para 'u8g2_font_unifont_t_symbols'

  u8g2.setDrawColor(0);  // Define a cor de desenho como 0 (preto)
  u8g2.drawBox(9, 0, 128 - 9, 14);  // Desenha uma caixa preta no buffer

  u8g2.setDrawColor(1);  // Define a cor de desenho como 1 (branco)

  // Desenha o texto no buffer
  u8g2.setCursor(textNomeX, 12);
  u8g2.print(nomeMidia);

  u8g2.setDrawColor(0);  // Define a cor de desenho como 0 (preto)
  u8g2.drawBox(0, 0, 9, 14);  // Desenha uma caixa preta no canto esquerdo do buffer

  u8g2.setDrawColor(1);  // Define a cor de desenho como 1 (branco)

  showVolume();  // Chama a função 'showVolume'

  // Envia o buffer para o display
  u8g2.sendBuffer();

  if (textNomeX == 10) {  // Verifica se a posição X do texto é 10
    if (millis() - timerNome > 1000) {  // Verifica se passou 1 segundo desde a última atualização
      timerNome = millis();  // Atualiza o temporizador
      textNomeX = 0;  // Reinicia a posição X para reiniciar a rolagem
    }
  } else {  // Caso contrário
    if (millis() - timerNome > 700) {  // Verifica se passou 700 milissegundos desde a última atualização

      textNomeX -= 25;  // Atualiza a posição X para fazer a rolagem

      u8g2.setFont(u8g2_font_unifont_t_symbols);  // Define a fonte do display para 'u8g2_font_unifont_t_symbols'
      int w = u8g2.getStrWidth(nomeMidia.c_str());  // Obtém a largura do texto
      
      if (textNomeX + w <= 0) {  // Verifica se o texto passou completamente pela tela
        textNomeX = 10;  // Reinicia a posição X para reiniciar a rolagem
      }

      timerNome = millis();  // Atualiza o temporizador
    }
  }
}

void showAlbum() {  // Define a função 'showAlbum'
  u8g2.setFont(u8g2_font_6x12_t_symbols);  // Define a fonte do display para 'u8g2_font_6x12_t_symbols'

  u8g2.setDrawColor(1);  // Define a cor de desenho como 1 (branco)
  u8g2.setCursor(10, 23);  // Define a posição do cursor para desenhar o texto
  u8g2.print(albumMidia);  // Imprime o texto referente ao álbum
}
/******************************************************************************
    Controlando o media player VLC de um PC via joystick no ESP32
                        Criado em 16 de Janeiro de 2024
                     por Michel Galvão (https://micsg.com.br)

                                Credenciais

  Eletrogate - Loja de Arduino \\ Robótica \\ Automação \\ Apostilas \\ Kits
                            https://www.eletrogate.com/
******************************************************************************/

// Definição das credenciais de rede Wi-Fi
const char* ssid = "<SSID>";          // Substitua <SSID> pelo nome da sua rede Wi-Fi
const char* password = "<SENHA>";     // Substitua <SENHA> pela senha da sua rede Wi-Fi
const char* senhaVlc = "<SENHA_VLC>";// Substitua <SENHA_VLC> pela senha utilizada para acessar a interface Web lua do media player VLC
/******************************************************************************
    Controlando o media player VLC de um PC via joystick no ESP32
                        Criado em 16 de Janeiro de 2024
                     por Michel Galvão (https://micsg.com.br)

                          Imagem Bitmap de Splash Screen

  Eletrogate - Loja de Arduino \\ Robótica \\ Automação \\ Apostilas \\ Kits
                            https://www.eletrogate.com/
******************************************************************************/

// Definição de um array de bytes para uma tela de inicialização (splash screen)
static const unsigned char splashScreen [] PROGMEM = {
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 0x03, 0x00, 0x00, 0xe0, 0xff, 0x01,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xff, 0x1f, 0x00,
   0x00, 0xfc, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0xfc, 0xff, 0x3f, 0x00, 0x00, 0xff, 0xff, 0x1f, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x7f,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0x01,
   0xe0, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0,
   0xff, 0xff, 0xff, 0x03, 0xf0, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0xe0, 0xff, 0xab, 0xff, 0x07, 0xf0, 0xff, 0xea, 0xff,
   0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x7f, 0x00, 0xfc, 0x0f,
   0xfc, 0x1f, 0x00, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0,
   0x1f, 0x00, 0xf8, 0x1f, 0xfc, 0x07, 0x00, 0xfc, 0x07, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0xf8, 0x07, 0x00, 0xe0, 0x3f, 0xfe, 0x03, 0x00, 0xf8,
   0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x00, 0xc0, 0x7f,
   0xff, 0x01, 0x00, 0xf0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc,
   0x03, 0x00, 0x80, 0xff, 0xff, 0x00, 0x00, 0xe0, 0x1f, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0xfc, 0x01, 0x00, 0x00, 0xff, 0x7f, 0x00, 0x0f, 0xc0,
   0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xff,
   0x3f, 0x00, 0x0f, 0xc0, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe,
   0x00, 0x00, 0x00, 0xfe, 0x1f, 0x00, 0x0f, 0x80, 0x1f, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0xfe, 0x80, 0xed, 0x01, 0xfc, 0x1f, 0xa0, 0x2f, 0x80,
   0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x80, 0xff, 0x03, 0xf8,
   0x0f, 0xe0, 0x7f, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe,
   0xc0, 0xff, 0x03, 0xf8, 0x0f, 0xe0, 0xff, 0x80, 0x3f, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0xfe, 0x80, 0xff, 0x03, 0xf8, 0x0f, 0xe0, 0x7f, 0x80,
   0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x80, 0xa4, 0x02, 0xfc,
   0x0f, 0x00, 0x0f, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe,
   0x00, 0x00, 0x00, 0xfc, 0x1f, 0x00, 0x0f, 0x80, 0x1f, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x3f, 0x00, 0x0f, 0xc0,
   0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x01, 0x00, 0x00, 0xff,
   0x3f, 0x00, 0x0f, 0xc0, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc,
   0x01, 0x00, 0x00, 0xff, 0x7f, 0x00, 0x00, 0xc0, 0x1f, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0xc0, 0xff, 0xff, 0x00, 0x00, 0xe0,
   0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0x00, 0xc0, 0x3f,
   0xff, 0x01, 0x00, 0xf0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8,
   0x0f, 0x00, 0xe0, 0x3f, 0xfe, 0x03, 0x00, 0xf8, 0x0f, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0xf0, 0x1f, 0x00, 0xf8, 0x1f, 0xfc, 0x07, 0x00, 0xfc,
   0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x7f, 0x00, 0xfc, 0x0f,
   0xfc, 0x1f, 0x00, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0,
   0xff, 0xab, 0xff, 0x0f, 0xf0, 0xff, 0xea, 0xff, 0x03, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0xc0, 0xff, 0xff, 0xff, 0x03, 0xf0, 0xff, 0xff, 0xff,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0x03,
   0xc0, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0xff, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x3f, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0xfc, 0xff, 0x7f, 0x00, 0x00, 0xff, 0xff, 0x1f,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff, 0x1f, 0x00,
   0x00, 0xfc, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0xc0, 0xff, 0x07, 0x00, 0x00, 0xf0, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x02, 0x28, 0x80, 0x02, 0x10, 0x10, 0xa9, 0x82, 0x40,
   0x80, 0x02, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x07, 0xff, 0xe1, 0x3f, 0x3c,
   0x3c, 0xff, 0xf7, 0xe1, 0xf1, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f,
   0xff, 0xe7, 0xff, 0x3c, 0x3c, 0xff, 0xe7, 0xe3, 0xf9, 0x1f, 0x00, 0x00,
   0x00, 0x00, 0xe0, 0x0f, 0xff, 0xe7, 0xff, 0x3c, 0x3c, 0xff, 0xf7, 0xe3,
   0xfd, 0x1f, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x0f, 0x8f, 0xe7, 0xf1, 0x3d,
   0x3c, 0x78, 0xe0, 0xe7, 0x3d, 0x3e, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x1f,
   0x8f, 0xe7, 0xe1, 0x3d, 0x3c, 0xf8, 0xe0, 0xe7, 0x3d, 0x3c, 0x00, 0x00,
   0x00, 0x00, 0xf0, 0x1f, 0x8f, 0xe7, 0xe1, 0x3d, 0x3c, 0x78, 0xf0, 0xef,
   0x1f, 0x3c, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x1e, 0xff, 0xe7, 0xe1, 0x3f,
   0x7c, 0xf8, 0xe0, 0xef, 0x1f, 0x7c, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3e,
   0xff, 0xe3, 0xe1, 0x3d, 0x3c, 0x78, 0xf0, 0xff, 0x1f, 0x3c, 0x00, 0x00,
   0x00, 0x00, 0x70, 0x3c, 0xff, 0xe1, 0xe1, 0x3f, 0x3c, 0xf8, 0xe0, 0xfe,
   0x1e, 0x7c, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x3f, 0xff, 0xe3, 0xe1, 0x3d,
   0x3c, 0x78, 0xf0, 0xfe, 0x1f, 0x3c, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x3f,
   0xcf, 0xe3, 0xe1, 0x3d, 0x3c, 0xf8, 0xe0, 0xfc, 0x3d, 0x3c, 0x00, 0x00,
   0x00, 0x00, 0xf8, 0x7f, 0xcf, 0xe7, 0xf1, 0x3d, 0x3c, 0x78, 0xf0, 0xfc,
   0x3f, 0x3e, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x7f, 0x8f, 0xe7, 0xff, 0xfc,
   0x3e, 0xff, 0xe7, 0xf8, 0xfd, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x78,
   0x8f, 0xef, 0x7f, 0xf8, 0x1f, 0xff, 0xf7, 0xf8, 0xf9, 0x1f, 0x00, 0x00,
   0x00, 0x00, 0x3c, 0xf8, 0x0f, 0xef, 0x3f, 0xf0, 0x0f, 0xff, 0xe7, 0xf1,
   0xf9, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x14, 0x20, 0x0d, 0xab, 0x06, 0xc0,
   0x07, 0xaa, 0x62, 0x40, 0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00 };

Explicação do Funcionamento

Aqui está um resumo explicativo com base no código:

  1. Configuração de Credenciais (arquivo credenciais.h):
    • As credenciais da rede Wi-Fi são definidas para permitir a conexão do dispositivo à rede.
    • A senha de acesso à interface Web Lua do media player VLC é definida.
  2. Splash Screen (arquivo splashScreen.h):
    • Uma matriz de bytes estática é definida para criar uma tela de inicialização (splash screen) quando o dispositivo é iniciado.
  3. Conexão com o VLC (arquivo sketch.ino):
    • O código usa uma biblioteca HTTPClient para se conectar a um servidor VLC.
    • As credenciais de acesso (usuário vazio e senha armazenada em credenciais.h) são utilizadas para autenticação.
  4. Requisição de Status XML:
    • Uma requisição GET é enviada para obter o status do VLC no formato XML.
    • O código lida com diferentes códigos de resposta HTTP, como: acesso não autorizado, requisição com erro ou sucesso na requisição.
  5. Análise do XML:
    • A resposta XML é processada usando a biblioteca tinyxml2.
    • Elementos como volume, estado do player, informações da mídia e posição são extraídos.
  6. Atualização de Variáveis:
    • As variáveis locais são atualizadas com as informações extraídas do XML.
  7. Envio de Comandos:
    • O código inclui a capacidade de enviar comandos ao servidor VLC, como play, pause, avançar mídia, retroceder mídia e ajuste de volume.
    • O envio de comandos é fundamental para controlar o player remotamente, proporcionando uma interação completa com as funcionalidades do VLC.
  8. Exibição de Informações no Display:
    • Funções como showNome() e showAlbum() são responsáveis por exibir informações, como nome da mídia e álbum, em um display usando a biblioteca U8g2.
    • Há também lógica para rolar o texto do nome da mídia se ele não couber completamente na tela.

Vídeo de Funcionamento do Projeto

Veja no vídeo abaixo o funcionamento do projeto:


Sobre o Autor


Michel Galvão

Graduando em Engenharia de Software pela UniCV de Maringá/PR. Tem experiência em Automação Residencial e Agrícola. É um Eletrogater Expert. Tem como Hobby Sistemas Embarcados e IoT. É desenvolvedor de software de Sistemas MicSG. Site: https://micsg.com.br/


Eletrogate

30 de janeiro de 2024

A Eletrogate é uma loja virtual de componentes eletrônicos do Brasil e possui diversos produtos relacionados à Arduino, Automação, Robótica e Eletrônica em geral.

Conheça a Metodologia Eletrogate e Lecione um Curso de Robótica nas Escolas da sua Região!

Eletrogate Robô

Cadastre-se e fique por
dentro de novidades!