La idea: que el modelo realmente "sienta" lo que pasa
Un LLM corriendo en la nube es brillante leyendo texto, pero está completamente desconectado del mundo físico que rodea a su usuario. Este proyecto cierra esa brecha con un "sidecar" embebido: un ESP32 con micrófono, sensor ambiental, acelerómetro, sensor de luz, NeoPixels, motor de vibración y buzzer. El modelo conversa con el dispositivo por MQTT o HTTP, mide el entorno, se expresa en él y, lo más interesante, verifica que sus salidas efectivamente ocurrieron en la realidad física.
La propuesta original es de Olivia Zhu en su paper A Minimal Self-Perceiving Embodiment for Large Language Models (Zenodo, 2026), y la guía de montaje la publicó Tim C en el Adafruit Learning System. Esta versión chilena adapta el hardware al catálogo local: cambia el ESP32-S3 Reverse TFT Feather de Adafruit por un XIAO ESP32-S3 o ESP32 WROOM estándar, sustituye el micrófono PDM por un INMP441 I2S, y reemplaza el VEML7700 por un sensor BH1750 (GY-302). El código sigue siendo CircuitPython y la arquitectura de bucles de feedback es idéntica.
Al terminar vas a tener un agente que puede pedir "avísame cuando suba la temperatura sobre 25°C vibrando el motor y poniendo los LED en rojo" y comprobar por sí mismo, con su propio micrófono y acelerómetro, que el aviso realmente sonó y vibró.

Tres bloques: percepción, expresión y autopercepción
Lo que hace "corporeizado" a este agente no es la lista de sensores, es el cierre del bucle. Casi cualquier proyecto IoT puede leer un BME280 y publicar al cloud. Lo que diferencia a este kit es que cada salida está emparejada con un sensor que confirma su efecto:
- El buzzer piezo suena, y el micrófono I2S detecta el nivel de presión sonora.
- Los NeoPixels se encienden, y el sensor de luz BH1750 registra el cambio de luminancia.
- El motor de vibración pulsa, y el acelerómetro detecta la oscilación.
Esta verificación cierra el lazo. El agente no "asume" que su comando llegó: lo comprueba. Si manda encender NeoPixels en azul y el lux no cambia, sabe que algo falló (cable suelto, GPIO mal mapeado, sensor tapado). Eso lo distingue de un bot con sensores: está anclado en la realidad.
Modalidades de percepción
| Magnitud | Sensor sugerido (catálogo MS) | Bus |
|---|---|---|
| Temperatura y humedad | HTU21D (temp+hum) o AHT10 | I2C |
| Nivel de sonido | Micrófono I2S INMP441 | I2S |
| Luminancia | Módulo BH1750 GY-302 | I2C |
| Movimiento y orientación | Acelerómetro ADXL345 GY-291 | I2C / SPI |
| Entrada del usuario | 3 botones GPIO | digital |
Modalidades de expresión
| Acción | Actuador | Bucle de verificación |
|---|---|---|
| Sonido | Buzzer activo 5V | Micrófono I2S |
| Luz RGB | Anillo o módulo NeoPixel 8x5050 | Sensor de luz BH1750 |
| Háptico | DRV2605L + motor de vibración 3.3V | Acelerómetro |
| Visual (cara o texto) | TFT del ESP32 Feather (o display externo I2C) | sin loop |
Hardware: por qué un ESP32 estándar funciona igual
El tutorial original usa el ESP32-S3 Reverse TFT Feather de Adafruit porque tiene 3 botones integrados, pantalla TFT a bordo y conectores STEMMA QT para I2C plug and play. En Chile esa placa específica es difícil de conseguir, pero el mismo firmware CircuitPython corre en casi cualquier ESP32-S3 o ESP32 WROOM con dos ajustes:
- Los botones: en lugar de los 3 integrados, agrega 3 pulsadores táctiles a GPIOs libres con resistencias pull-up internas (
digitalio.DigitalInOutconPull.UP). - La pantalla: si usas un ESP32 WROOM sin TFT, conecta un display OLED SSD1306 0.96" I2C aparte. La librería
adafruit_displayio_ssd1306reemplaza la TFT integrada sin cambios mayores en la lógica de "caritas".

Para las conexiones I2C de los sensores, en vez de cables STEMMA QT, usa protoboard 830 puntos + jumpers macho-hembra. Funcionalmente idéntico, solo menos elegante.
El bucle háptico, en detalle
El bloque háptico es el ejemplo más claro de autopercepción. El controlador DRV2605L recibe el comando por I2C y hace vibrar el pequeño motor de moneda; el acelerómetro, montado en la misma estructura, "siente" esa vibración y devuelve un ACK al modelo. Si el motor está desconectado o pegado a algo que amortigua, el acelerómetro no registra oscilación y el agente sabe que su gesto háptico no llegó.

El bucle sonoro
Para el sonido, el buzzer activo emite un tono y el micrófono I2S INMP441 mide el nivel de presión sonora resultante. Es un micrófono MEMS digital: entrega la muestra ya digitalizada por el bus I2S, así que no necesitas un amplificador ni un ADC externo como con un micrófono analógico.

Software: CircuitPython, MQTT y un mini servidor HTTP
El flujo es:
- Flashea el ESP32 con CircuitPython 9.x (
esptool.py --port /dev/ttyUSB0 erase_flashy después arrastrar el.bincorrespondiente a tu chip). - Copia las librerías necesarias a
/lib:adafruit_bme280(oadafruit_htu21dsi usas el HTU21D),adafruit_drv2605,neopixel,adafruit_lis3dh(oadafruit_adxl34xsi usas el ADXL345),adafruit_minimqtt. - Crea tu
settings.tomlcon SSID, password WiFi y credenciales de Adafruit IO (o tu broker MQTT preferido: Mosquitto local funciona perfecto). - Sube el
code.pyde la guía ajustando los pines a tu placa.
Esqueleto del bucle principal
import board, busio, time
import neopixel, pwmio
from adafruit_bh1750 import BH1750
from adafruit_adxl34x import ADXL345
i2c = busio.I2C(board.GP5, board.GP4) # ajusta pines a tu ESP32
lux = BH1750(i2c)
accel = ADXL345(i2c)
pixels = neopixel.NeoPixel(board.GP2, 8, brightness=0.3)
buzzer = pwmio.PWMOut(board.GP3, variable_frequency=True)
def expresar_y_verificar(color, freq_hz):
lux_antes = lux.lux
pixels.fill(color)
buzzer.frequency = freq_hz
buzzer.duty_cycle = 2**15
time.sleep(0.5)
lux_despues = lux.lux
buzzer.duty_cycle = 0
return (lux_despues - lux_antes) > 50 # confirmacion fisica
Este patrón es el corazón del proyecto: cada vez que el agente actúa, el código reporta de vuelta un booleano de "sí, lo percibí". El LLM remoto recibe ese ACK y entiende que el comando aterrizó. El stick NeoPixel de 8 LEDs es ideal para este bucle: su salto de luminancia es grande y fácil de detectar con el sensor BH1750.

Variantes y mejoras
- Modo offline con LLM local: en vez de Adafruit IO, puedes correr un Ollama con
llama3.2:3ben una Raspberry Pi 4 dentro de la misma red y exponerlo por HTTP. El ESP32 hace POST con las lecturas de sensores cada N segundos y el modelo decide qué actuador disparar. Cero cuenta cloud. - Reemplazar el motor háptico por un servo SG90: si lo háptico te queda largo, un servo barato puede mover una "banderita" física que el acelerómetro detecta igual. Más visual, más barato y mismo bucle de autoverificación.
- Agregar Claude vía API: el watcher MQTT puede llamar a la API de Anthropic con
claude-haiku-4-5-20251001(rápido y económico) pasándole las lecturas de sensores como tool inputs y dejando que decida acciones. El prompt caching reduce el costo a ~$0.01 USD por hora de operación continua. - Logging local en microSD: agregando un módulo microSD SPI puedes guardar todas las lecturas y decisiones del agente, útil para debug o para entrenar afinamientos del modelo después.
Personalización para Chile
Lista de componentes equivalentes en stock en MechatronicStore:
- Seeed Studio XIAO ESP32-S3 (SKU GS1-3): $13.670 CLP. Reemplazo directo del ESP32-S3 Feather: dual core, WiFi, BLE, USB-C nativo.
- ESP32 WROOM-32 USB-C (SKU X2-10V2): $7.990 CLP. Alternativa más barata si no necesitas los periféricos extra del S3.
- Módulo Sensor de Temperatura y Humedad HTU21D (SKU GE2-6): $4.490 CLP. Cubre temperatura y humedad, las dos magnitudes ambientales que más usa el agente. Si además quieres presión atmosférica, un BME280 trae las tres en una sola placa.
- Módulo Micrófono I2S INMP441 (SKU GL3-12): $5.890 CLP. Equivalente funcional del micrófono PDM de Adafruit (también MEMS, bus distinto pero compatible).
- Módulo RGB LED 8 bits 5050 WS2812 (SKU GN3-11): $1.990 CLP. Stick NeoPixel clónico al precio justo.
- Controlador Háptico Gravity DRV2605L (SKU GL2-6): $10.650 CLP. Es el mismo chip DRV2605 del original.
- Motor de vibración 3.3V (SKU GB3-6V2): $2.490 CLP. El motor háptico que va al DRV2605L.
- Acelerómetro ADXL345 GY-291 (SKU GN3-4): $3.490 CLP. Reemplazo del LIS3DH; mismo bus I2C, rango similar de ±2g a ±16g.
- Módulo Sensor de Intensidad Luminosa BH1750 GY-302 (SKU GN1-6): $3.990 CLP. Sustituto del VEML7700, sensor lux I2C estándar.
- Buzzer activo 5V (SKU GA3-2): $500 CLP.
- Protoboard 830 puntos MB102 (SKU C-302): $3.790 CLP. Sustituye la Perma-Proto de media placa de Adafruit; ideal para prototipar antes de soldar.
- Cables macho-hembra 30cm (SKU C-418): $1.990 CLP. Para las conexiones I2C entre módulos.
- Cable USB Tipo C a USB Tipo A 1mt (SKU B-101): $2.190 CLP. Para flashear y alimentar el ESP32-S3.
Costo total aproximado: $59.440 CLP (versus unos USD $80 a $100 más envío internacional armando con las piezas exactas de Adafruit).
Recursos
- Tutorial original (inglés): LLM Agent Embodiment Kit, por Tim C en el Adafruit Learning System
- Paper de referencia: A Minimal Self-Perceiving Embodiment for Large Language Models, de Olivia Zhu (Zenodo, 2026)
- Repositorio GitHub del proyecto original: oliviazzzu/minimal-embodiment
- PDF descargable del original: llm-agent-embodiment-kit.pdf
- Librerías CircuitPython: circuitpython.org/libraries (bundle oficial)
Versión chilena inspirada en el trabajo de Tim C y Olivia Zhu, con componentes equivalentes en stock local en MechatronicStore.





