Asistente de voz conversacional para la placa Guition JC-ESP32P4-M3-DEV. Habla y escucha en tiempo real conectándose directamente a la Gemini Live API por WebSocket (sin servidor intermedio), y ve: cuando se lo pides, hace una foto con la cámara y se la manda al modelo para que describa lo que tiene delante.
- 🎙️ Voz bidireccional en tiempo real (mic 16 kHz → Gemini, respuesta 24 kHz → altavoz).
- 👁️ Visión bajo demanda: la foto se envía solo cuando lo pides ("mira", "¿qué ves?"…), para no gastar cuota. La cámara captura la imagen del instante en que lo pides.
- 🔎 Búsqueda en Google (grounding) para datos actuales.
- 📝 Recordatorios con fecha persistentes en flash (NVS) vía function calling.
- ♻️ Sesión continua: compresión de ventana de contexto + reanudación tras cortes de red.
La persona del asistente ("Carla") y los datos del usuario se configuran en
main/config.h(SYSTEM_INSTRUCTION).
| Componente | Detalle |
|---|---|
| Placa | Guition JC-ESP32P4-M3-DEV (ESP32-P4) |
| WiFi | Vía ESP32-C6 como slave (esp-hosted / esp_wifi_remote) |
| Códec audio | ES8311 + amplificador NS4150B |
| Cámara | OV5647 (MIPI-CSI) → ISP → encoder JPEG por hardware |
Pines (demo del fabricante, no el BSP de la EV board oficial):
I2S: MCLK=13 BCLK=12 WS=10 DOUT=9 DIN=48
PA (amplificador) = GPIO 11 I2C (SDA=7, SCL=8, compartido códec + cámara)
⚠️ El BSP de la EV board oficial tiene DIN/PA intercambiados (DIN=11, PA=53). En esta placa es DIN=48, PA=11. Está documentado enmain/config.h.
Código modular en main/, cada fichero con una responsabilidad (≤300 líneas):
main/
├── app_main.c Orquestador: arranca wifi, SNTP, audio, cámara, notas, ws, tareas
├── config.h Configuración (audio, pines, persona, modelo Gemini)
├── secrets.h Credenciales — NO versionado (ver «Configuración»)
├── app_state.h/.c Estado compartido entre tareas (banderas de sesión, colas…)
├── net/
│ └── wifi.c/.h Conexión WiFi (modo estación)
├── audio/
│ ├── audio.c/.h Códec ES8311 + I2S (init, read, write)
│ └── audio_tasks.c/.h mic_task, spk_task y resampler 24→16 kHz
├── gemini/
│ ├── gemini.h API del protocolo Gemini Live
│ ├── gemini_session.c Handshake: setup + saludo de arranque
│ ├── gemini_send.c Envíos: audio del mic, encolado de audio, toolResponse
│ ├── gemini_rx.c Parseo de los mensajes entrantes
│ └── ws.c/.h Transporte WebSocket (TLS) + watchdog de reconexión
├── vision/
│ └── vision.c/.h Detección "mira", envío de foto y worker de recordatorios
├── camera/
│ └── camera.c/.h Cámara: captura de foto JPEG bajo demanda + endpoint /pic
└── notes/
└── notes.c/.h Recordatorios con fecha en NVS
La cámara queda en streaming (encendida) pero no codifica ni guarda nada entre
peticiones. Cuando pides "mira", camera_capture_jpeg() vacía los frames viejos de la cola
V4L2, coge el primer frame fresco (sensor a 50 fps ≈ 100 ms) y lo manda a Gemini como
realtimeInput.video. Así la imagen es siempre la del momento y no se malgasta CPU ni cuota.
- ESP-IDF v5.3 o superior con soporte para ESP32-P4 (
esp_video,esp_cam_sensor,esp_wifi_remote/esp-hosted). - Toolchain de ESP32-P4 instalada por el
installde ESP-IDF. - Las dependencias gestionadas se descargan solas (component manager) a partir de
main/idf_component.ymlydependencies.lock.
Las credenciales van en main/secrets.h, que no se sube al repo. Crea el tuyo a partir
de la plantilla:
cp main/secrets.h.example main/secrets.hy rellena:
#define WIFI_SSID "TU_WIFI"
#define WIFI_PASSWORD "TU_PASSWORD"
#define GEMINI_API_KEY "TU_API_KEY" // https://aistudio.google.com/apikeyEl resto de ajustes (persona del asistente, voz, ganancia del mic, calidad JPEG, pines…)
están en main/config.h.
🔒 Seguridad: la API key queda embebida en el binario; quien tenga el
.binpuede leerla. Usa una key dedicada y revócala si se filtra.
idf.py set-target esp32p4
idf.py build
idf.py -p <PUERTO> flash monitor(<PUERTO> = COMx en Windows, /dev/ttyUSBx o /dev/ttyACMx en Linux/Mac.)
Al arrancar, en cuanto haya WiFi y se sincronice la hora, Carla saluda en voz alta: esa es la señal de que la sesión está lista y ya puedes hablar.
Con la cámara activa, el firmware levanta un pequeño servidor HTTP (puerto 80):
http://<IP>/pic— una foto JPEG suelta.http://<IP>/— vista casi en vivo (auto-refresco) para enfocar.
La IP aparece en el log del monitor al conectar el WiFi.
main/_old/ymain/context/no entran al build (código histórico / experimental).server.pyes el antiguo relay en Python, ya no se usa (la conexión es directa a Google).- En otra máquina, tras clonar, recuerda crear
main/secrets.ho el build fallará con "no such file: secrets.h".