Criando uma Pokédex funcional na vida real

Um projeto que muita gente já fez ou já viu em algum tutorial é a famosa Pokédex. Eu mesmo cheguei a fazer isso quando estava aprendendo React em um bootcamp. Mas, encontrei um vídeo do canal abe's projects onde o autor decidiu levar esse projeto para outro nível: construir uma Pokédex na vida real.

Para quem não sabe, uma Pokédex é um tipo de catálogo de Pokémons onde, por meio desse dispositivo, uma pessoa poderia apontar para um Pokémon e obter mais informações sobre ele. O Abe decidiu que esse projeto deveria respeitar alguns critérios:

  1. Ter uma aparência similar à da Pokédex.
  2. Deve reconhecer a maioria dos Pokémons na maioria das situações. Ou seja, reconhecer uma foto do anime, ou de um Pokémon de pelúcia etc.
  3. Falar como a Pokédex do anime, numa voz e cadência similares.

Então, o objetivo geral do projeto era conseguir reconhecer os Pokémons visualmente e gerar a voz corretamente.

Planejamento

Planejamento numa folha de papel A4, com desenhos do dispositivo, circuitos e palavras das tecnologias envolvidas

A case desenhada será impressa numa impressora 3D, mas com o objetivo de ser bem similar ao anime, tanto em aparência quanto nas dimensões.

Na parte eletrônica, tem a tela, uma câmera, uma entrada USB, um alto-falante, a bateria e uma pequena placa para acionar o alto-falante. A câmera usada também é um microcontrolador Seeed Studio XIAO ESP32S3 (Sense). Os dois botões da case seriam feitos com duas breakout boards, que são botões em placas PCB que fazem o som de "clique".

Na parte do software, o GPT4 seria usado para reconhecer o que está sendo visualizado, a API PokéAPI, que é um banco de dados de Pokémons, e um serviço chamado PlayHT para clonar a voz da Pokédex original. Para o backend, ele optou por usar o Firebase na Google Cloud.

Montagem

Ele começou imprimindo o modelo da case para saber se os componentes ficariam bem dispostos ali. Além disso, ele escolheu uma tela em tons de cinza, ao invés da colorida, por ser mais nostálgico para ele.

A case com os componentes dispostos

Depois de ter verificado onde os componentes poderiam ficar, ele voltou para o modelo 3D e complementou com os apoios necessários, além dos espaços para aparafusar etc.:

A case com os buracos do parafuso e os apoios dos componentes

Para conectar os componentes, ele precisou realizar algumas soldagens, mostrando tudo o que foi feito no vídeo.

Software

A parte de geração de voz no PlayHT foi bem simples, mas ao tentar usar a API para gerar os áudios sob demanda, as coisas começaram a dar errado. O Abe disse que a Pokédex passou a ter alguns comportamentos estranhos que ele passou alguns dias depurando.

Aqui ele elencou os cinco piores bugs que enfretou no projeto:

  1. Carregar bitmaps quebra a Pokédex: Ele baixou da PokéAPI as imagens de todos os Pokémons e colocou no cartão SD para exibir na tela, mas isso fazia o sistema quebrar ("crashar"). Para resolver, ele decidiu ler o arquivo manualmente byte a byte e desenhar cada pixel na tela. O trecho de código que ele exibiu no vídeo ficou assim:
char buffer[3];
int bufSize = 3;
int offset = 120;
int center = (display.width()-96)/2;
display.centerDisplay();

for (int row = 0; row < 96; row++) {
  for (int col = 0; col < 96; col++) {
    int bytePosition = offset + (row*96*3) + (col * 3);
    int x = center + col;
    
    int y = (96+center)-row;
    file.seek(bytePosition);
    file.readBytes(buffer, bufSize);
    display.drawPixel(x, y, buffer[0]);
  }
}

file.close();
  1. Áudio com ticks: Os áudios gerados pelo PlayHT tinham alguns "ticks" estranhos, como você pode ver os picos na imagem abaixo. Ele decidiu inspecionar o áudio num editor de hexadecimal, e encontrou o número 1000 repetido algumas vezes, com intervalos consistentes entre as repetições. Ele decidiu adicionar mais um 1000 em outro lugar para confirmar se isso era a causa do problema, e era.

O áudio gerado, com picos que são "ticks"

O código que ele escreveu para resolver esse problema:

int skips = 0;
for (int b = 0; b<sizeof(buffer); b++) {
  if (buffer[b] == 13) {
    if (sizeof(buffer) > b+1) {
      if (buffer[b+1] == 10) {
        for (int i = 2; i<8; i++) {
          if (sizeof(buffer) > b+i) {
            skips += i+1;
          }
        }
      }
    }
  }
}
  1. Dividir strings quebra a Pokédex: A resposta do ChatGPT era no formato POKEMON_NAME: POKEMON_DESCRIPTION, então ele precisava dividir o texto para obter o nome e descrição. O problema, na verdade, era que ele estava criando um buffer muito pequeno para a URL, que teve um overflow e causava o crash em outra parte do código.
  1. PSRAM quebra a Pokédex: Ele tinha uma função para realizar o setup da câmera e do cartão SD que quebrava esporadicamente. Descobriu que, ao desativar a PSRAM (que havia ativado anteriormente para tentar resolver outro problema), o problema não acontecia mais. A PSRAM é uma RAM externa opcional, usada em aplicativos que precisam de muita memória, então ele deixou desativada.
  1. Às vezes, trava: Ele tentou consertar para ficar com tudo rodando perfeitamente, mas não conseguiu descobrir o problema. O Abe acredita que precisaria reescrever todo o código e refazer a estrutura de uma forma diferente com os aprendizados que teve, mas decidiu continuar com o projeto dessa forma e fazer melhor num próximo projeto.

O resultado

Funcionou! Na verdade, a pronúncia do nome dos Pokémons ficou errada, mas a Pokédex reconheceu o Pokémon corretamente em dois dos três testes feitos no vídeo, onde um era um Piplup de plástico e o outro uma imagem do Raichu no anime, exibida no computador. O teste que falhou foi um Fuecoco de pelúcia.

Pokédex apontada para o Piplup, reconhecendo o Pokémon após 15~20 segundos

O vídeo foi bem produzido e ele explicou bem cada etapa do projeto, então para quem tem interesse nesse tipo de assunto e tem 11 minutos livres, vale a pena assistir. Ele também possui outros vídeos no canal com projetos criativos.