El Device Shadow guarda el estado aunque el dispositivo esté apagado (parte 5). Eso es bueno para no perder órdenes, pero tiene un efecto secundario: la app puede mostrar "encendido" para una luz cuyo ESP32 lleva horas desconectado. El usuario necesita saber si el dispositivo está en línea.
AWS IoT Core publica un evento cada vez que un cliente MQTT se conecta o se desconecta. En esta parte vemos cómo leerlos, cómo guardar el estado de conexión para la app y qué hacer con los nodos que no hablan directamente con AWS.
Los eventos de conexión
AWS IoT publica en dos tópicos reservados:
$aws/events/presence/connected/<clientId>
$aws/events/presence/disconnected/<clientId>
Un evento de desconexión se ve así:
{
"clientId": "mi-esp32",
"timestamp": 1760180400000,
"eventType": "disconnected",
"clientInitiatedDisconnect": false,
"sessionIdentifier": "00000000-0000-0000-0000-000000000000",
"principalIdentifier": "…",
"disconnectReason": "MQTT_KEEP_ALIVE_TIMEOUT",
"versionNumber": 12
}
El campo disconnectReason es oro para depurar:
| Motivo | Qué significa |
|---|---|
CLIENT_INITIATED_DISCONNECT | El dispositivo cerró la conexión a propósito |
MQTT_KEEP_ALIVE_TIMEOUT | Dejó de responder: sin energía, sin Wi-Fi o bloqueado |
CONNECTION_LOST | Se cortó la conexión de red |
DUPLICATE_CLIENTID | Otro cliente se conectó con el mismo client ID |
AUTHORIZATION_FAILURE | Intentó algo que su política no permite |
Para verlos en vivo, suscríbete a $aws/events/presence/# desde el MQTT test client de la consola y reinicia tu ESP32.
Cuánto tarda en detectarse una desconexión
Si el dispositivo se desconecta limpiamente, el evento es inmediato. Si se queda sin energía, AWS no se entera hasta que vence el keepalive: espera 1,5 veces el intervalo de keepalive sin recibir nada. Con el valor por defecto de muchas librerías (60 segundos), son 90 segundos.
Para detectarlo antes, baja el keepalive en el ESP32. Con la librería MQTT de 256dpi:
client.setKeepAlive(30); // segundos; AWS detectaría la caída en unos 45 s
Un keepalive más bajo envía más pings (un mensaje pequeño cada intervalo), con un costo de conexión y de batería algo mayor.
Guardar el estado de conexión en el Shadow
La app podría suscribirse a los eventos, pero si abre después de la desconexión, se la pierde. Es mejor guardar el estado donde la app ya mira: el Shadow. Una regla de IoT (parte 14) lo hace sin código. La consulta arma el documento con el formato del Shadow:
SELECT { 'reported': { 'conectado': eventType = 'connected', 'desde': timestamp } } AS state
FROM '$aws/events/presence/+/+'
Con la acción Republish al tópico de un Shadow con nombre:
$$aws/things/${topic(5)}/shadow/name/conexion/update
topic(5)es el client ID (el quinto segmento del tópico). Funciona si el client ID coincide con el nombre del Thing, como exige la política de la parte 13.$$aws: en la acción Republish, el$inicial de un tópico reservado se escribe doble.- Un Shadow con nombre (
conexion) separa el estado de conexión del estado de las luces: actualizarlo no genera deltas en el otro.
La app lee conectado y muestra el dispositivo en gris cuando es false.
El orden de los eventos
Los eventos pueden llegar desordenados: si un ESP32 se reinicia rápido, el connected de la sesión nueva puede llegar antes que el disconnected de la vieja. Si guardas el último que llega, el dispositivo aparecería desconectado estando en línea.
Dos formas de protegerse:
- comparar el
timestamp(oversionNumber) antes de sobrescribir, en una Lambda; - ignorar un
disconnectedsi susessionIdentifierno es el de la sesión actual.
Para un panel doméstico, una alternativa simple: cuando la app ve conectado: false, pide el Shadow de estado; si el dispositivo responde a una orden, está en línea.
Last Will: el aviso del propio dispositivo
MQTT tiene un mecanismo estándar: al conectar, el dispositivo registra un mensaje de última voluntad (Last Will and Testament). Si la conexión se pierde sin un cierre limpio, el broker lo publica en su nombre:
client.setWill("casa-demo/mi-esp32/estado", "{\"conectado\":false}", true, 1);
client.connect(THING_NAME);
client.publish("casa-demo/mi-esp32/estado", "{\"conectado\":true}", true, 1);
AWS IoT Core admite Last Will y mensajes retenidos. La política del dispositivo debe permitir publicar en ese tópico (y iot:RetainPublish si el mensaje es retenido).
| Eventos de ciclo de vida | Last Will | |
|---|---|---|
| Configuración en el dispositivo | Ninguna | Sí, al conectar |
| Incluye el motivo | Sí | No |
| Estándar MQTT | No, propio de AWS | Sí |
Nodos detrás de un hub
Los nodos ESP-NOW no se conectan a AWS: para AWS IoT, solo existe el hub. Si el hub está en línea pero un nodo se quedó sin energía, ningún evento lo dirá.
La solución es que el hub vigile a los nodos:
- cada nodo envía un latido (heartbeat) cada cierto tiempo, o el hub detecta que no confirma órdenes (parte 4);
- el hub publica en su Shadow qué nodos responden, por ejemplo
"nodos": {"sala": true, "patio": false}.
Consultar la conexión sin guardar nada
Si activas la indexación de flota (Fleet Indexing) con datos de conectividad, puedes consultar qué dispositivos están conectados:
aws iot search-index --query-string "connectivity.connected:false"
Útil para un panel de administración con muchos dispositivos. Tiene un costo propio por indexación y consultas.
Preguntas frecuentes
¿Los eventos de conexión cuestan algo?
Se publican como mensajes MQTT y se cobran como tales cuando alguien los recibe (una suscripción o una regla). Para una casa, fracciones de centavo.
¿Por qué mi dispositivo aparece y desaparece cada pocos segundos?
Mira el disconnectReason. DUPLICATE_CLIENTID indica dos clientes con el mismo client ID; AUTHORIZATION_FAILURE, un permiso que falta en la política.
¿Y si el Wi-Fi funciona pero el dispositivo está bloqueado?
Si el programa se bloquea, deja de responder a los pings y AWS lo verá como MQTT_KEEP_ALIVE_TIMEOUT. Para que se recupere solo, activa el watchdog del ESP32.