A maioria dos gadgets de mesa mostra informações. Eu queria um que reagisse às pessoas: um robô que olha para você quando você fala, responde e faz algo divertido quando ninguém está por perto. O Espressif ESP-VoCat v1.2 tem o hardware para isso. Ele combina um SoC ESP32-S3, uma tela redonda sensível ao toque de 360×360, dois microfones, um alto-falante e uma bateria em uma carcaça com orelhas de gato.
VoCat Eyes é o rosto que escrevi para ele, e o primeiro robô de uma série chamada Cute Desktop Robot. Suas principais características são:
- 56 expressões faciais e 47 animações curtas
- Uma pequena máquina de estados que decide o que o robô faz: brincar, reagir, entediar-se, adormecer
- Olhos que se voltam para a pessoa que está falando
- Conversas faladas por meio da Gemini Live API, iniciadas com a palavra de ativação “Hi ESP” ou um toque
- Configurações de Wi-Fi, volume, brilho e voz na própria tela do robô
- Um emulador que executa o mesmo código em um PC e nesta página
O firmware, o emulador e as ferramentas são de código aberto no repositório cute-desktop-robot.

A imagem acima é a visão 3D do emulador: o modelo da carcaça que a Espressif publicou, com a tela ao vivo do robô emulado dentro dele.
E este é o robô real em uma mesa, executando o mesmo firmware:
O robô no hardware real: assista no YouTube.
Experimente o robô no seu navegador
O emulador abaixo executa o código C++ do robô compilado para WebAssembly: o mesmo motor de rosto, máquina de estados e telas que rodam no ESP32-S3. Apenas a camada de hardware é substituída por peças simuladas. Não precisa de conta nem de instalação. Somente a conversa por voz precisa do robô real, porque o emulador não tem rede.
Se o quadro estiver muito pequeno, abra o emulador em uma janela inteira.
A janela tem três partes. À esquerda está o robô em 3D, abaixo dele está a máquina de estados, e à direita está um painel com o gráfico de emoções e os sensores. Para experimentar:
- Toque na tela do robô. Clique uma vez na tela redonda do robô em 3D. O robô reproduz sua próxima animação.
- Abra as configurações. Clique e segure na tela até que o relógio e a bateria apareçam, depois clique uma vez no relógio. Um menu de configurações se abre. Clique na metade esquerda de uma linha para diminuir um valor e na metade direita para aumentá-lo. Clique e segure para fechar o menu.
- Fale de um lado. No painel à direita, em SENSORS, arraste o controle deslizante ao lado de from para um ângulo, depois clique em talk [V]. Um ponto roxo no chão mostra de onde vem a voz simulada, e os olhos do robô se voltam para ele.
- Observe a máquina de estados. Cada vez que o robô muda sua atividade, a caixa correspondente se acende na máquina de estados, e as barras na parte inferior mostram os temporizadores que podem mudá-la em seguida.
- Gire o robô. Arraste em qualquer lugar fora da tela para girar a visão 3D, e use a roda do mouse para dar zoom. Pressione
Mpara alternar para uma visão plana da tela.
O teclado também funciona depois que você clica no emulador: 1 a 9 mostram expressões, Y acena com a cabeça, U balança a cabeça e H mostra um balão de fala. O toque e as configurações funcionam da mesma forma no robô real.
A próxima captura de tela mostra a janela inteira do emulador como ela aparece em um PC de mesa:

O hardware
O ESP-VoCat vem montado, com a carcaça, a bateria e a tela no lugar. O guia do usuário do ESP-VoCat descreve a placa em detalhes. Um módulo ESP32-S3 faz todo o trabalho. Ele tem 16 MB de memória flash e 16 MB de PSRAM (RAM externa). A PSRAM torna possível um rosto de 360×360: um quadro completo em cores de 16 bits ocupa 253 KB, mais do que a RAM interna do SoC consegue armazenar.
O diagrama a seguir mostra as peças que o firmware usa e o barramento que conecta cada uma ao ESP32-S3:
Duas peças precisaram de trabalho extra:
- A tela acendia a luz de fundo, mas permanecia preta. A sequência de inicialização que adaptei do driver do fornecedor estava sem dois comandos finais: o formato de pixel (
0x3A) e a ativação do display (0x29). - Os microfones retornavam apenas zeros no início. O ADC dos microfones (ES7210) e o DAC do alto-falante (ES8311) compartilham as mesmas linhas de clock I2S. O barramento I2S, portanto, precisa usar o mesmo formato de quadro nas duas direções: TDM (multiplexação por divisão de tempo) com quatro slots de 16 bits. Com essa mudança, os dois microfones aparecem nos slots 0 e 2, e o slot 1 carrega uma cópia do sinal do alto-falante para referência de eco.
Como o robô desenha seu rosto
O robô redesenha seu rosto a cada 33 ms, cerca de 30 vezes por segundo. O diagrama abaixo acompanha um desses quadros desde os sensores até a tela:
Cada bloco do diagrama é um módulo do firmware:
- Sensors coleta toques, o nível da bateria, a hora e eventos sonoros em uma única estrutura.
- Brain é uma máquina de estados com os estados awake, show, reacting, dozing, sleeping, info e manual. Cada estado sai em eventos específicos, como um toque, um barulho alto, a hora do dia ou um longo período sozinho. O Brain também acompanha humores lentos: um robô que é acariciado com frequência tende a rostos felizes, e um que é cutucado demais fica irritado.
- FaceEngine transita entre expressões ao longo de um grafo. As 56 expressões são seus nós, e uma mudança de rosto segue o caminho mais curto, então o robô vai de feliz a triste passando por rostos intermediários. Cada olho é descrito por alguns parâmetros, como tamanho, raio dos cantos, inclinação, pálpebras e cor, e o motor os interpola.
- Canvas desenha o quadro na PSRAM e registra quais blocos de 24×24 mudaram. Um piscar altera alguns blocos, então o barramento do display envia alguns kilobytes em vez dos 253 KB completos.
- VocatLcd envia os blocos alterados para o display ST77916 via QSPI (um barramento SPI de quatro linhas).
Todas as expressões e animações
O emulador inclui uma ferramenta de linha de comando, facetool, que renderiza a tela do robô sem abrir uma janela. Os vídeos abaixo vêm dela. Cada um reproduz todos os rostos de um grupo ao mesmo tempo: primeiro a transição para cada rosto, depois o próprio rosto.
Expressões
Calmo e pensativo (16): normal, curioso, concentrado, cético, entediado, confuso e ouvindo, entre outros.
Feliz (15): de um sorriso discreto a gargalhada, amor e encantado.
Triste (8): preocupado, suplicante, chorando, solitário e culpado, entre outros.
Irritado (6): de incomodado a furioso, e nojo.
Surpreso e alerta (7): surpreso, assustado, tonto, nocauteado e hipnotizado, entre outros.
Sonolento (4): sonolento, bocejando, dormindo e cansado.
Animações
As animações, chamadas de atos no código, são cenas curtas, muitas com um objeto desenhado ao lado dos olhos: uma nuvem de chuva, uma xícara de café, uma tela de radar. O Brain as reproduz quando o robô está ocioso, e um toque na tela reproduz a próxima.
Linguagem corporal (9): acenar com a cabeça, balançar a cabeça, revirar os olhos, espirrar.
Efeitos de tela (5): um glitch, um reboot, uma linha de scanner, código caindo.
Com objetos (18): corações, chuva, fogos de artifício, bolhas, uma bola, dançando.
Coisas do dia a dia (9): um livro, fones de ouvido, café, um foguete, neve.
Jogos e efeitos de tela redonda (6): Pac-Man, Pong, um arco-íris, um radar, um aquário.
As mesmas grades, como GIFs animados, estão no README do repositório.
Como o robô conversa
A conversa usa a Gemini Live API, que recebe um fluxo de áudio e responde com um fluxo de áudio por uma única conexão WebSocket. No momento em que este texto foi escrito, o robô usa o modelo gemini-2.5-flash-native-audio. O diagrama abaixo mostra o caminho do áudio:
O robô envia o som de um microfone como áudio PCM de 16 kHz, em blocos de 100 ms. O Gemini responde com fala de 24 kHz, que o robô converte para 16 kHz e reproduz.
A conversa é half duplex: o robô ou escuta ou fala, nunca as duas coisas ao mesmo tempo. O alto-falante fica a poucos centímetros dos microfones, então o robô silencia o fluxo do microfone enquanto reproduz uma resposta. Caso contrário, o Gemini ouviria a própria voz. O Gemini decide do seu lado quando a pessoa terminou de falar.
O Gemini também recebe quatro ferramentas, funções que ele pode chamar durante a conversa: set_face, play_act, look e show_text. Quando a conversa fica triste, o Gemini pode chamar set_face("sad"). Quando você pede que o robô concorde, ele pode chamar play_act("yes") e o robô acena com a cabeça. Cada sessão começa com uma mensagem de texto curta que contém a hora e a data locais, para que o Gemini possa responder perguntas sobre elas.
Três problemas consumiram a maior parte do tempo de desenvolvimento:
- A conexão segura. O robô se conecta ao Gemini via TLS. A função
setInsecure()da biblioteca ArduinoWebsockets não tem efeito no ESP32-S3, então o handshake TLS falhava. O firmware agora inclui os dois certificados raiz do Google (GTS Root R1 e R4, válidos até 2036). Ele também espera o relógio sincronizar via NTP, porque verificar um certificado exige a data atual. - RAM interna. A palavra de ativação “Hi ESP” roda no ESP-SR, a biblioteca de reconhecimento de fala da Espressif, que mantém cerca de 100 KB de RAM interna. A biblioteca TLS precisa de RAM interna para o handshake, e as primeiras tentativas falharam com
SSL - Memory allocation failed. O robô agora para o ESP-SR quando uma conversa começa, o que libera 107 KB, e o carrega novamente quando a conversa termina. Carregar o ESP-SR leva cerca de 1,2 segundos. - O fluxo de microfone ausente. Por um dia, o robô cumprimentava o usuário e depois ignorava tudo o que vinha a seguir. Seus olhos ainda seguiam minha voz, então os microfones claramente funcionavam. A causa era que o firmware nunca habilitava o fluxo de áudio para o Gemini. O console serial agora imprime a transcrição do Gemini do que ele ouviu, então o mesmo problema ficaria visível imediatamente.
Usar o Gemini exige uma chave de API do Google AI Studio, e a API é cobrada por uso conforme a precificação da Gemini API do Google. A chave fica em um arquivo local secrets.h que nunca é enviado ao repositório.
Configurações na tela do robô
O robô não precisa de um computador depois de configurado. Um toque longo na tela mostra o relógio, o nível da bateria e o estado do Wi-Fi. Um toque no relógio abre as configurações, onde você pode alterar:
- Volume e brilho da tela
- A voz do Gemini, entre oito vozes
- A palavra de ativação “Hi ESP”, ligada ou desligada
- Os sons que acompanham as expressões
- Se o robô reproduz e dorme por conta própria
- Quantos segundos de silêncio encerram uma conversa
A linha do Wi-Fi abre uma lista das redes ao alcance e um teclado na tela com letras maiúsculas, números e símbolos. O ESP32-S3 não consegue escanear redes enquanto está se conectando a uma, então o firmware pausa as tentativas de conexão durante o escaneamento e as retoma depois. Se a senha estiver errada, a tela do relógio mostra isso como o motivo. O ESP32-S3 só suporta redes de 2,4 GHz.
Uma base de código para o robô, o PC e o navegador
O código do robô não chama os drivers de display, toque ou áudio diretamente. O rosto, o Brain e as telas passam por uma camada fina de hardware em src/hw e src/audio. O emulador substitui essa camada, então o restante do código compila sem alterações para três alvos:
No emulador, pequenas versões substitutas de Arduino.h, Wire.h e SPI.h escrevem em uma estrutura que representa o hardware. Ela contém a memória do display, os registradores do controlador de toque e as leituras da bateria. A janela do emulador, escrita com a biblioteca gráfica raylib, desenha essa estrutura e permite alterar os valores dos sensores.
A versão para navegador é o mesmo programa emulador compilado com Emscripten, que transforma C++ em WebAssembly. Três mudanças tornaram isso possível:
- raylib para a web. O raylib compila com Emscripten em uma biblioteca estática que desenha via WebGL.
- Um quadro por chamada. A versão desktop rodava em um loop infinito. Esse loop virou uma função que desenha um quadro. A versão desktop a chama em loop, e o navegador a chama uma vez por atualização de tela.
- Um tamanho de janela fixo. No navegador, o emulador desenha em um canvas de 1380×900, e a página escala o canvas para caber. Um canvas redimensionável quebrava o mapeamento entre as coordenadas do mouse e do canvas.
A carcaça 3D vem da Espressif. A ESP-College publicou o case do ESP-VoCat (EchoEar) como arquivos STL no OSHWHub sob a licença GPL-3.0. Um pequeno script Python converte os arquivos STL em um único arquivo de modelo 3D e reduz seu detalhamento para que o navegador o carregue rapidamente. O emulador desenha a tela ao vivo em um disco na janela redonda do case. Quando você clica na tela 3D, o emulador encontra o ponto clicado nesse disco e o converte em um toque no pixel correspondente.
Construa o seu
Para construir o VoCat Eyes você precisa de uma placa ESP-VoCat v1.2 e, no momento em que este texto foi escrito, da versão 3.3 do core Arduino para ESP32. O README do VoCat Eyes lista as bibliotecas e os comandos exatos. Em resumo:
- Copie
secrets.example.hparasecrets.he adicione sua chave de API do Gemini. Você também pode informar a rede Wi-Fi ali, ou configurá-la depois no robô. - Compile o sketch com o esquema de partições
esp_sr_16e o arquivopartitions.csvna pasta do sketch. Esse layout dá ao aplicativo 6 MB de flash. - Envie o firmware junto com
srmodels.bin, o arquivo de modelo para a palavra de ativação “Hi ESP”. - No robô, pressione e segure a tela, toque no relógio, escolha Wi-Fi, selecione sua rede e digite a senha.
- Diga “Hi ESP”, ou toque no pequeno microfone na parte inferior da tela, e comece a falar.
Se você está projetando firmware para o ESP32-S3 ou outras placas, o Velxio pode simulá-lo no navegador antes de você gravar qualquer hardware.