Cloud, DevOps e IoT en español

Parte 1 de 22 de la serie Construyendo IHouse

Arquitectura de una casa inteligente con ESP32 y AWS: dispositivos, nube y app

30 de septiembre de 2026

La mayoría de tutoriales de domótica con ESP32 terminan en un LED que se enciende desde el celular. El problema aparece después: cuando tienes diez dispositivos, cuando otra persona de la casa quiere usarlos, cuando quieres controlarlos por voz o cuando un corte de luz desordena todo.

IHouse es la plataforma de domótica que construí y que uso en mi casa. En esta serie te cuento cómo la hice, parte por parte, con las decisiones que tomé, los errores que cometí y el código real. Esta primera parte es el mapa: qué piezas tiene, cómo se hablan entre ellas y por qué elegí cada una.

Tres placas de IHouse: arriba un ESP32-S3 Super Mini que hace de maestro y abajo dos ESP32-C3 Super Mini que hacen de esclavos

El hardware de una casa con IHouse: arriba, el maestro (ESP32-S3); abajo, dos esclavos (ESP32-C3).

La arquitectura en una imagen

Diagrama de la arquitectura de IHouse: esclavos ESP32-C3 conectados por ESP-NOW a un maestro ESP32-S3, que habla con AWS IoT Core; la web app usa Cognito, DynamoDB, una Lambda que firma la conexión MQTT e IoT Core, y un Atajo de Siri llama a otra Lambda

Las tres capas de IHouse. Los números corresponden a la leyenda y se explican en las secciones siguientes.

El sistema tiene tres capas:

  1. Dispositivos en la casa: un ESP32-S3 "maestro" y hasta 20 ESP32-C3 "esclavos" que controlan luces o enchufes.
  2. Nube en AWS: AWS IoT Core como broker MQTT, Cognito para el login, DynamoDB para los datos y Lambda para las APIs.
  3. Apps: una web app instalable (PWA), servida gratis desde Netlify, y Atajos de Siri para el control por voz.

No hay ningún servidor propio que mantener. Todo son servicios administrados que, con uso doméstico, cuestan centavos al mes.

Capa 1: los dispositivos, con un gateway

La decisión más importante de todo el proyecto fue esta: solo un dispositivo por casa se conecta a internet.

El maestro, un ESP32-S3, es el único que tiene Wi-Fi, TLS y certificado propio. Es el único que existe en AWS IoT como Thing. Los esclavos, ESP32-C3, reciben las órdenes del maestro por ESP-NOW, el protocolo punto a punto de Espressif (flechas 1 y 2 del diagrama).

Cada dispositivo conectado a AWSGateway (lo que hice)
Conexiones TLS abiertasUna por dispositivoUna por casa
Certificados y Things que administrarUno por dispositivoUno por casa
Clientes conectados al routerTodosSolo el maestro
Costo en AWSCrece con cada dispositivoSolo el del maestro
Memoria que necesita cada placaLa de TLS + MQTT + JSONLa del esclavo es mínima

Los esclavos ni siquiera se conectan al Wi-Fi de la casa. ESP-NOW solo exige que los dos extremos estén en el mismo canal de radio. Así que el esclavo escanea las redes cercanas, lee en qué canal transmite la red del maestro, sin asociarse ni pedir contraseña, y fija ese canal. Un esclavo no guarda credenciales de Wi-Fi y no ocupa un lugar en el router.

Por qué ESP32-S3 para el maestro y ESP32-C3 para los esclavos

El maestro hace TLS, MQTT, JSON, ESP-NOW y un portal de configuración al mismo tiempo. Usé un ESP32-C3 como maestro de pruebas y funcionó, pero tuve que ampliar el stack de la tarea principal y queda con poco margen para crecer. El ESP32-S3, con dos núcleos y más RAM, corre el mismo firmware sin ajustes.

Los esclavos solo reciben órdenes por ESP-NOW y mueven un relé. Al C3 le sobra capacidad para eso y es más barato. En la parte 5 te cuento el error que me dio el C3 como maestro, y por qué no era lo que parecía.

Un interruptor físico que no depende de internet

Cada esclavo admite un interruptor de pared de dos cables conectado a un GPIO. El esclavo aplica el cambio al instante, sin consultar al maestro ni a la nube, y después avisa al maestro para que la app refleje el nuevo estado. Si se cae internet, la luz sigue funcionando desde la pared, como siempre.

Cómo viaja una orden de punta a punta

El estado de toda la casa cabe en un solo texto corto, el campo code del Device Shadow. Cada par de caracteres es una letra, que identifica a un esclavo, y un dígito, 1 encendido y 0 apagado. "A1B0" significa "esclavo A encendido, esclavo B apagado".

{
  "state": {
    "desired":  { "code": "A1B0" },
    "reported": { "code": "A1B0" }
  }
}

Elegí este formato porque el maestro es un microcontrolador: recorrer un texto de dos en dos caracteres es mucho más barato que interpretar un JSON anidado con un objeto por dispositivo. Además, 20 esclavos ocupan solo 40 caracteres.

Esto es lo que pasa cuando tocas "Encender" en la app:

  1. La web app publica el nuevo code como desired en el Device Shadow del maestro.
  2. AWS IoT Core compara desired con reported y envía la diferencia (el delta) al maestro.
  3. El maestro compara letra por letra con el último estado confirmado para saber qué cambió.
  4. Por cada letra que cambió, envía un paquete ESP-NOW de 2 bytes (letra y estado) al esclavo que corresponde, y espera su confirmación (ACK).
  5. El esclavo mueve el relé y responde con el ACK.
  6. Con todas las confirmaciones, el maestro publica el nuevo code como reported.
  7. La app, suscrita a los cambios del Shadow, actualiza el botón.

El paso 6 es importante: el maestro solo reporta lo que se confirmó. Si un esclavo está desconectado, la app no muestra un estado falso.

Hay un caso más: un esclavo que se reinicia no recuerda su último estado. Al arrancar, envía una consulta especial al maestro (su letra seguida de ?) y el maestro le reenvía el último estado confirmado. Así una luz no queda apagada después de un corte de energía si la app dice que está encendida.

La conexión del maestro con AWS IoT Core y el Device Shadow están explicados, con código, en la parte 3 de esta serie.

Capa 2: la nube en AWS

ServicioPara qué lo uso
AWS IoT CoreBroker MQTT y Device Shadow: guarda el estado de cada casa aunque el maestro esté desconectado
Cognito User PoolLogin con usuario y contraseña. El registro público está desactivado: las cuentas se crean por invitación
Cognito Identity PoolCambia el token del login por credenciales temporales de AWS para leer DynamoDB, limitadas a los datos de la casa del usuario
DynamoDBUsuarios, dispositivos, roles, permisos y los hashes de las API keys
Lambda con Function URLCinco funciones: la API de Siri, el panel de administración, las API keys, el alta de maestros nuevos y la firma de la conexión MQTT del navegador
AWS CDKLas funciones Lambda y sus roles IAM definidos en TypeScript

Tres decisiones de esta capa me ahorraron mucho trabajo:

El navegador se conecta directo a AWS IoT Core, pero no firma la conexión (flechas 4 y 7). La app le pide a una Lambda una URL de WebSocket ya firmada con SigV4 y abre con ella una conexión MQTT. No hay un servidor que reenvíe mensajes: la Lambda solo firma y el tráfico va directo del navegador a IoT Core.

Al principio el navegador firmaba la URL con las credenciales temporales de Cognito. El WebSocket abría, pero AWS cerraba la conexión MQTT en silencio, sin CONNACK y sin dejar rastro en CloudWatch. Firmar desde una Lambda con credenciales de sts:AssumeRole lo resolvió, y además resultó más seguro. Te cuento esa depuración en la parte 10.

Una sola consulta a DynamoDB por sesión (flechas 3 y 5). La tabla usa userId como partition key y deviceId como sort key, así que un solo Query devuelve todos los dispositivos de la casa. La app lo hace al iniciar sesión y guarda el resultado en memoria.

Una web app sin hosting en AWS. La PWA es un sitio estático y la sirvo desde el plan gratuito de Netlify. No hay ningún acoplamiento con AWS: las Lambdas tienen CORS abierto, Cognito no restringe por dominio y el WebSocket de IoT Core no pasa por CORS.

El aislamiento entre casas

Cuando IHouse pasó de mi casa a varios clientes, apareció un problema: los permisos tenían escrito el nombre de mi Thing. Cada cliente nuevo obligaba a editarlos a mano, y un comodín habría dado a cada usuario acceso a las casas de los demás.

La solución tiene dos partes, y ninguna confía en lo que envía el navegador:

Lo explico paso a paso en la parte 13.

Capa 3: las apps

La web app está hecha con React, Vite y TypeScript, y es una PWA: se instala en la pantalla de inicio del celular como una app nativa. Muestra los dispositivos de la casa y el estado de conexión. Con los roles que agregué después, también tiene un panel para administrar integrantes.

Siri no puede firmar peticiones con SigV4, así que no puede hablar directo con AWS IoT Core. Para la voz hice una API en Lambda con su propia API key por usuario (flecha 6). La Lambda busca el hash de la key en DynamoDB y obtiene de ese registro el Thing y los dispositivos que esa persona puede controlar, nunca de la petición. Después lee el code actual, cambia solo las letras pedidas y actualiza el Shadow. Un Atajo de iOS hace el POST, y "Oye Siri, enciende la sala" funciona.

Lo que cambió en el camino

La arquitectura de la imagen no es la primera versión. Estos son los cambios más grandes, cada uno con su parte en la serie:

Cuánto cuesta

Para uso doméstico, centavos de dólar al mes:

ServicioPor qué cuesta tan poco
AWS IoT CoreUn maestro por casa y unos pocos mensajes al día
CognitoCobra por usuario activo al mes, no por sesión, y la capa gratuita cubre miles de usuarios
DynamoDBModo bajo demanda, una consulta por sesión
LambdaUna invocación por comando de voz o por conexión; el primer millón al mes es gratis
NetlifyLa web app es un sitio estático en el plan gratuito

El gateway es lo que mantiene el costo bajo: los esclavos no generan ni un mensaje en AWS. En la última parte de la serie hago las cuentas completas, servicio por servicio, también para escenarios con muchas casas.

Los límites de este diseño

Preguntas frecuentes

¿Por qué no usar Home Assistant?

Home Assistant es excelente si quieres domótica local en tu propia casa. Yo quería otra cosa: una plataforma para varias casas, con usuarios, permisos y control remoto sin abrir puertos ni mantener un servidor en cada hogar. Para eso, una arquitectura en la nube tenía más sentido.

¿Por qué ESP-NOW y no Wi-Fi para todos los dispositivos?

Porque cada dispositivo con Wi-Fi y TLS necesita su certificado, su conexión permanente con la nube y un lugar en el router. Con ESP-NOW los esclavos solo necesitan estar en el mismo canal de radio que el maestro, sin router ni contraseña.

¿Necesito AWS para seguir la serie?

Para las partes de la nube, sí. Las partes de firmware (ESP-NOW, NVS, portal cautivo, el interruptor físico) sirven para cualquier proyecto con ESP32, aunque uses otra nube o ninguna.

¿Qué viene en la serie?

El hardware (parte 2), la conexión con AWS IoT Core (parte 3), ESP-NOW paso a paso (parte 4) y, después, la base de datos, la API de Siri, la web app, la seguridad y los costos. El índice completo está en la página de la serie.