La maggior parte dei gadget da scrivania mostra informazioni. Io ne volevo uno che reagisse alle persone — un robot che ti guarda quando parli, ti risponde e fa qualcosa di divertente quando non c’è nessuno in giro. L’Espressif ESP-VoCat v1.2 ha l’hardware per farlo. Combina un SoC ESP32-S3, uno schermo tondo touch da 360×360, due microfoni, un altoparlante e una batteria in una custodia con le orecchie da gatto.
VoCat Eyes è il volto che ho scritto per lui, e il primo robot di una serie chiamata Cute Desktop Robot. Le sue caratteristiche principali sono:
- 56 espressioni facciali e 47 brevi animazioni
- Una piccola macchina a stati che decide cosa fa il robot — giocare, reagire, annoiarsi, addormentarsi
- Occhi che si girano verso la persona che sta parlando
- Conversazioni parlate tramite la Gemini Live API, avviate con la wake word “Hi ESP” o un tocco
- Impostazioni di Wi-Fi, volume, luminosità e voce sullo schermo del robot stesso
- Un emulatore che esegue lo stesso codice su un PC e in questa pagina
Il firmware, l’emulatore e gli strumenti sono open source nel repository cute-desktop-robot.

L’immagine sopra è la vista 3D dell’emulatore — il modello della custodia pubblicato da Espressif, con lo schermo live del robot emulato al suo interno.
E questo è il robot reale su una scrivania, che esegue lo stesso firmware:
Il robot sull’hardware reale — guardalo su YouTube.
Prova il robot nel tuo browser
L’emulatore qui sotto esegue il codice C++ del robot compilato in WebAssembly — lo stesso motore del volto, la stessa macchina a stati e le stesse schermate che girano sull’ESP32-S3. Solo il livello hardware è sostituito da componenti simulati. Non richiede account né installazione. Solo la conversazione vocale richiede il robot reale, perché l’emulatore non ha rete.
Se il riquadro è troppo piccolo, apri l’emulatore in una finestra intera.
La finestra ha tre parti. A sinistra c’è il robot in 3D, sotto c’è la macchina a stati, e a destra c’è un pannello con il grafo delle emozioni e i sensori. Per provarlo:
- Tocca lo schermo del robot. Clicca una volta sullo schermo tondo del robot 3D. Il robot esegue la sua animazione successiva.
- Apri le impostazioni. Clicca e tieni premuto sullo schermo finché non compaiono l’orologio e la batteria, poi clicca una volta sull’orologio. Si apre un menu delle impostazioni. Clicca la metà sinistra di una riga per abbassare un valore e la metà destra per alzarlo. Clicca e tieni premuto per chiudere il menu.
- Parla da un lato. Nel pannello di destra, sotto SENSORS, trascina lo slider accanto a from su un angolo, poi clicca talk [V]. Un punto viola sul pavimento mostra da dove proviene la voce simulata, e gli occhi del robot si girano verso di essa.
- Osserva la macchina a stati. Ogni volta che il robot cambia attività, la casella corrispondente si illumina nella macchina a stati, e le barre in basso mostrano i timer che possono cambiarla successivamente.
- Ruota il robot. Trascina in un punto qualsiasi fuori dallo schermo per ruotare la vista 3D, e usa la rotella del mouse per lo zoom. Premi
Mper passare a una vista piatta dello schermo.
La tastiera funziona anch’essa una volta cliccato l’emulatore — da 1 a 9 mostrano le espressioni, Y annuisce, U scuote la testa e H mostra un fumetto. Il tocco e le impostazioni funzionano allo stesso modo sul robot reale.
Lo screenshot successivo mostra l’intera finestra dell’emulatore come appare su un PC desktop:

L’hardware
L’ESP-VoCat arriva già assemblato, con la custodia, la batteria e lo schermo al loro posto. La guida utente dell’ESP-VoCat descrive la scheda in dettaglio. Un singolo modulo ESP32-S3 fa tutto il lavoro. Ha 16 MB di memoria flash e 16 MB di PSRAM (RAM esterna). La PSRAM rende possibile un volto da 360×360 — un frame completo a colori a 16 bit occupa 253 KB, più di quanto la RAM interna del SoC possa contenere.
Il diagramma seguente mostra i componenti utilizzati dal firmware e il bus che collega ciascuno di essi all’ESP32-S3:
Due componenti hanno richiesto lavoro extra:
- Lo schermo accendeva la retroilluminazione ma restava nero. Alla sequenza di inizializzazione che avevo adattato dal driver del fornitore mancavano due comandi finali — il formato dei pixel (
0x3A) e l’accensione del display (0x29). - I microfoni restituivano solo zeri all’inizio. L’ADC dei microfoni (ES7210) e il DAC dell’altoparlante (ES8311) condividono le stesse linee di clock I2S. Il bus I2S deve quindi usare lo stesso formato di frame in entrambe le direzioni — TDM (multiplexing a divisione di tempo) con quattro slot da 16 bit. Con questa modifica, i due microfoni compaiono negli slot 0 e 2, e lo slot 1 trasporta una copia del segnale dell’altoparlante come riferimento per l’eco.
Come il robot disegna il suo volto
Il robot ridisegna il suo volto ogni 33 ms, circa 30 volte al secondo. Il diagramma seguente segue uno di questi frame dai sensori allo schermo:
Ogni blocco del diagramma è un modulo del firmware:
- Sensors raccoglie tocchi, livello della batteria, ora ed eventi sonori in un’unica struttura.
- Brain è una macchina a stati con gli stati awake, show, reacting, dozing, sleeping, info e manual. Ogni stato esce su eventi specifici, come un tocco, un rumore forte, l’ora del giorno o un lungo periodo di solitudine. Il Brain tiene anche traccia di umori lenti — un robot che viene accarezzato spesso tende verso volti felici, e uno che viene pungolato troppo spesso diventa irritato.
- FaceEngine si sposta tra le espressioni lungo un grafo. Le 56 espressioni sono i suoi nodi, e un cambio di volto segue il percorso più breve, così il robot passa da felice a triste attraverso volti intermedi. Ogni occhio è descritto da pochi parametri, come dimensione, raggio degli angoli, inclinazione, palpebre e colore, e il motore li interpola.
- Canvas disegna il frame in PSRAM e registra quali tile da 24×24 sono cambiati. Un battito di ciglia cambia pochi tile, quindi il bus del display invia pochi kilobyte invece dei 253 KB completi.
- VocatLcd invia i tile modificati al display ST77916 via QSPI (un bus SPI a quattro linee).
Tutte le espressioni e le animazioni
L’emulatore include uno strumento da riga di comando, facetool, che renderizza lo schermo del robot senza aprire una finestra. I video qui sotto provengono da esso. Ognuno riproduce tutte le facce di un gruppo contemporaneamente — prima il passaggio a ciascuna faccia, poi la faccia stessa.
Espressioni
Calmo e pensieroso (16): normale, curioso, concentrato, scettico, annoiato, confuso e in ascolto, tra gli altri.
Felice (15): da un piccolo sorriso alla risata, all’amore e all’innamoramento.
Triste (8): preoccupato, supplichevole, in lacrime, solo e in colpa, tra gli altri.
Arrabbiato (6): da infastidito a furioso, e disgustato.
Sorpreso e allerta (7): sorpreso, spaventato, stordito, messo KO e ipnotizzato, tra gli altri.
Assonnato (4): assonnato, sbadigliante, addormentato e stanco.
Animazioni
Le animazioni, chiamate acts nel codice, sono brevi scene, molte con un oggetto disegnato accanto agli occhi — una nuvola di pioggia, una tazza di caffè, uno schermo radar. Il Brain le riproduce quando il robot è inattivo, e un tocco sullo schermo riproduce la successiva.
Linguaggio del corpo (9): annuire, scuotere la testa, roteare gli occhi, starnutire.
Effetti schermo (5): un glitch, un riavvio, una linea di scansione, codice che cade.
Con oggetti (18): cuori, pioggia, fuochi d’artificio, bolle, una palla, danza.
Cose di tutti i giorni (9): un libro, cuffie, caffè, un razzo, neve.
Giochi ed effetti per schermo rotondo (6): Pac-Man, Pong, un arcobaleno, un radar, un acquario.
Le stesse griglie, come GIF animate, sono nel README del repository.
Come parla il robot
La conversazione utilizza la Gemini Live API, che riceve uno stream audio e risponde con uno stream audio su un’unica connessione WebSocket. Al momento della scrittura, il robot usa il modello gemini-2.5-flash-native-audio. Il diagramma sottostante mostra il percorso dell’audio:
Il robot invia il suono di un microfono come audio PCM a 16 kHz, in blocchi da 100 ms. Gemini risponde con voce a 24 kHz, che il robot converte a 16 kHz e riproduce.
La conversazione è half duplex — il robot ascolta o parla, mai entrambe le cose contemporaneamente. L’altoparlante si trova a pochi centimetri dai microfoni, quindi il robot disattiva lo stream del microfono mentre riproduce una risposta. Altrimenti Gemini sentirebbe la propria voce. Gemini decide dal suo lato quando la persona ha finito di parlare.
Gemini riceve anche quattro strumenti, funzioni che può chiamare durante la conversazione — set_face, play_act, look e show_text. Quando la conversazione diventa triste, Gemini può chiamare set_face("sad"). Quando chiedi al robot di annuire, può chiamare play_act("yes") e il robot annuisce. Ogni sessione inizia con un breve messaggio di testo che contiene l’ora e la data locali, così Gemini può rispondere a domande su di esse.
Tre problemi hanno occupato la maggior parte del tempo di sviluppo:
- La connessione sicura. Il robot si connette a Gemini tramite TLS. La funzione
setInsecure()della libreria ArduinoWebsockets non ha effetto sull’ESP32-S3, quindi l’handshake TLS falliva. Il firmware ora include i due certificati root di Google (GTS Root R1 e R4, validi fino al 2036). Attende anche che l’orologio si sincronizzi via NTP, perché verificare un certificato richiede la data corrente. - La RAM interna. La wake word “Hi ESP” gira su ESP-SR, la libreria di riconoscimento vocale di Espressif, che occupa circa 100 KB di RAM interna. La libreria TLS necessita di RAM interna per l’handshake, e i primi tentativi fallivano con
SSL - Memory allocation failed. Il robot ora arresta ESP-SR quando inizia una conversazione, liberando 107 KB, e lo ricarica quando la conversazione termina. Il caricamento di ESP-SR richiede circa 1,2 secondi. - Lo stream del microfono mancante. Per un giorno, il robot salutava l’utente e poi ignorava tutto ciò che seguiva. I suoi occhi seguivano ancora la mia voce, quindi i microfoni funzionavano chiaramente. La causa era che il firmware non abilitava mai lo stream audio verso Gemini. La console seriale ora stampa la trascrizione di ciò che Gemini ha sentito, così lo stesso problema sarebbe visibile immediatamente.
Usare Gemini richiede una chiave API da Google AI Studio, e l’API viene fatturata per utilizzo secondo i prezzi della Gemini API di Google. La chiave rimane in un file locale secrets.h che non viene mai committato nel repository.
Impostazioni sullo schermo del robot
Il robot non necessita di un computer una volta configurato. Una pressione prolungata sullo schermo mostra l’orologio, il livello della batteria e lo stato del Wi-Fi. Un tocco sull’orologio apre le impostazioni, dove puoi modificare:
- Volume e luminosità dello schermo
- La voce di Gemini, tra otto voci
- La wake word “Hi ESP”, attiva o disattivata
- I suoni che accompagnano le espressioni
- Se il robot riproduce e dorme autonomamente
- Quanti secondi di silenzio terminano una conversazione
La riga del Wi-Fi apre un elenco delle reti nel raggio d’azione e una tastiera su schermo con lettere maiuscole, numeri e simboli. L’ESP32-S3 non può scansionare le reti mentre si connette a una, quindi il firmware sospende i tentativi di connessione durante la scansione e li riprende dopo. Se la password è errata, la schermata dell’orologio lo mostra come motivo. L’ESP32-S3 supporta solo reti a 2,4 GHz.
Un’unica base di codice per il robot, il PC e il browser
Il codice del robot non chiama direttamente i driver del display, del touch o dell’audio. Il volto, il Brain e le schermate passano attraverso un sottile livello hardware in src/hw e src/audio. L’emulatore sostituisce quel livello, così il resto del codice si compila invariato per tre target:
Nell’emulatore, piccole versioni sostitutive di Arduino.h, Wire.h e SPI.h scrivono in una struttura che rappresenta l’hardware. Contiene la memoria del display, i registri del controller touch e le letture della batteria. La finestra dell’emulatore, scritta con la libreria grafica raylib, disegna quella struttura e ti permette di modificare i valori dei sensori.
La versione per browser è lo stesso programma emulatore compilato con Emscripten, che trasforma il C++ in WebAssembly. Tre modifiche lo hanno reso possibile:
- raylib per il web. raylib si compila con Emscripten in una libreria statica che disegna tramite WebGL.
- Un frame per chiamata. La versione desktop girava in un ciclo infinito. Quel ciclo è diventato una funzione che disegna un frame. La versione desktop la chiama in un ciclo, e il browser la chiama una volta per ogni aggiornamento dello schermo.
- Una dimensione fissa della finestra. Nel browser, l’emulatore disegna su un canvas di 1380×900, e la pagina scala il canvas per adattarlo. Un canvas ridimensionabile rompeva la corrispondenza tra le coordinate del mouse e quelle del canvas.
L’involucro 3D proviene da Espressif. ESP-College ha pubblicato il case dell’ESP-VoCat (EchoEar) come file STL su OSHWHub con licenza GPL-3.0. Un breve script Python converte i file STL in un unico file di modello 3D e ne riduce il dettaglio affinché il browser lo carichi rapidamente. L’emulatore disegna lo schermo live su un disco nella finestra rotonda del case. Quando clicchi sullo schermo 3D, l’emulatore trova il punto cliccato su quel disco e lo trasforma in un tocco sul pixel corrispondente.
Costruisci il tuo
Per costruire VoCat Eyes ti servono una scheda ESP-VoCat v1.2 e, al momento della scrittura, la versione 3.3 del core Arduino per ESP32. Il README di VoCat Eyes elenca le librerie e i comandi esatti. In breve:
- Copia
secrets.example.hinsecrets.he aggiungi la tua chiave API di Gemini. Puoi anche inserire lì la rete Wi-Fi, oppure impostarla successivamente sul robot. - Compila lo sketch con lo schema di partizionamento
esp_sr_16e il filepartitions.csvnella cartella dello sketch. Questa disposizione assegna all’applicazione 6 MB di flash. - Carica il firmware insieme a
srmodels.bin, il file del modello per la wake word “Hi ESP”. - Sul robot, tieni premuto lo schermo, tocca l’orologio, scegli Wi-Fi, seleziona la tua rete e digita la password.
- Di’ “Hi ESP”, oppure tocca il piccolo microfono in fondo allo schermo, e inizia a parlare.
Se stai progettando firmware per l’ESP32-S3 o altre schede, Velxio può simularlo nel browser prima che tu flashi qualsiasi hardware.