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ó.

Kit completo armado en protoboard con el ESP32-S3, el micrófono, el buzzer y los sensores ambientales conectados

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:

  1. Los botones: en lugar de los 3 integrados, agrega 3 pulsadores táctiles a GPIOs libres con resistencias pull-up internas (digitalio.DigitalInOut con Pull.UP).
  2. La pantalla: si usas un ESP32 WROOM sin TFT, conecta un display OLED SSD1306 0.96" I2C aparte. La librería adafruit_displayio_ssd1306 reemplaza la TFT integrada sin cambios mayores en la lógica de "caritas".

ESP32-S3 Reverse TFT Feather con su pantalla a bordo y una batería LiPo conectada

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ó.

Controlador háptico DRV2605L conectado por I2C al microcontrolador, con el motor de vibración tipo moneda en su salida

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.

Módulo de micrófono MEMS I2S, equivalente funcional del micrófono PDM del kit original

Software: CircuitPython, MQTT y un mini servidor HTTP

El flujo es:

  1. Flashea el ESP32 con CircuitPython 9.x (esptool.py --port /dev/ttyUSB0 erase_flash y después arrastrar el .bin correspondiente a tu chip).
  2. Copia las librerías necesarias a /lib: adafruit_bme280 (o adafruit_htu21d si usas el HTU21D), adafruit_drv2605, neopixel, adafruit_lis3dh (o adafruit_adxl34x si usas el ADXL345), adafruit_minimqtt.
  3. Crea tu settings.toml con SSID, password WiFi y credenciales de Adafruit IO (o tu broker MQTT preferido: Mosquitto local funciona perfecto).
  4. Sube el code.py de la guía ajustando los pines a tu placa.

Esqueleto del bucle principal

Python
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.

Stick NeoPixel de 8 LEDs RGB WS2812, la salida luminosa que el sensor de luz confirma

Variantes y mejoras

  • Modo offline con LLM local: en vez de Adafruit IO, puedes correr un Ollama con llama3.2:3b en 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

Versión chilena inspirada en el trabajo de Tim C y Olivia Zhu, con componentes equivalentes en stock local en MechatronicStore.