# Control LED piloto

Un canal digital GPIO23 activo alto en la ESP32 existente. Solo propietario
puede solicitar y consultar comandos. Los niños mantienen acceso de consulta
a monitoreo, sin control; permisos delegados de actuadores son una fase futura.
No se añade PWM ni aplicación móvil.

Migración 005 añade capacidad/estado reportado al dispositivo y auditoría
`device_commands` con autor, estado solicitado, creación, caducidad y resultado.
API con sesión/CSRF: GET/POST `/api/v1/devices/{id}/led`.
POST recibe `on` booleano y `request_key` de 32 caracteres hexadecimales. La
clave permite reintentar una petición sin repetirla. Reutilizarla con otro
valor/dispositivo se rechaza. Una orden pendiente por dispositivo; TTL 15 s.

El WebSocket anuncia `capabilities: {led:true}` y `led_on` en device.auth.
Solo los dispositivos que declaran soporte y están conectados aceptan comandos.
Node consulta la cola cada 5 s, comprueba sesión, dispositivo y propietario,
marca sent antes de enviar y no reenvía órdenes no confirmadas.
Mensaje: `{protocol:1,type:'command.set',command_id,on,expires_at}` (epoch segundos).
Respuesta `{protocol:1,type:'command.result',command_id,on}`: confirma únicamente
una orden enviada a ese dispositivo/conexión, no vencida y con el valor correcto.
Un ACK tardío no confirma la orden. Heartbeat vuelve a informar led_on y permite
recuperar el estado aunque se pierda el ACK. No es realimentación eléctrica.

La cola caduca en el servidor. Reiniciar Node invalida órdenes pendientes;
reconectar no reproduce órdenes de la conexión anterior. El firmware apaga
la salida al arrancar, detectar pérdida de conexión o superar 45 s sin respuestas.

Node requiere SELECT/UPDATE en device_commands y SELECT en home_members,
además de sus privilegios previos. PHP conserva DML. No se abre puerto nuevo.
Se hizo respaldo privado antes de migrar. La actualización del transporte
requiere reiniciar homecore-websocket; la placa se reconecta por sí sola.

Pruebas de integración en websocket-server/tests/monitoring.test.mjs incluyen
permisos, validación, idempotencia, estado confirmado y ACK expirado. Compilar
la versión LED para DOIT ESP32 DEVKIT V1 antes de publicar el ZIP.
Firmware y pasos para el usuario en firmware/esp32-reference/LED-GPIO23.md.

## Corrección de caducidad UTC — 2026-09-23

El piloto MariaDB usa SYSTEM/MST (-07:00). UNIX_TIMESTAMP(expires_at) interpretaba
el DATETIME almacenado en UTC como hora local y enviaba una caducidad siete
horas adelantada. El firmware rechazaba la orden por exceder su ventana de
30 segundos; el servidor terminaba marcándola expired, sin ACK.
Se sustituyó por TIMESTAMPDIFF desde el epoch UTC, independiente de la zona
de sesión. timezone:'Z' de mysql2 solo afecta conversiones del cliente, no
la semántica SQL de UNIX_TIMESTAMP. Prueba de regresión fuerza sesión -07:00
y comprueba la caducidad contra el reloj real; la sonda pública también valida
la ventana que acepta el firmware. No requiere actualizar el firmware.
