Go to file
Penki 4f37389cc5 first commit 2026-05-27 09:39:39 -03:00
README.md first commit 2026-05-27 09:39:39 -03:00

README.md

ESP Project (Modernizado)

Sistema para gestionar entidades, sectores, dispositivos y puertos con frontend en HTML/TailwindCSS + Bootstrap y backend PHP con router, respuestas JSON unificadas, variables de entorno y MQTT desde backend. Estados en tiempo real vía Server-Sent Events (SSE).

Cambios recientes (2025-09-17)

  • Login: agregado checkbox "Recordar" (Tailwind). Si está activo, se guardan cookies remember_user y remember_pass de larga duración y se prellenan en el próximo acceso.
  • Íconos: se centralizó el uso de SVG en assets/iconos.json y se inyectan automáticamente vía layouts/layout.php (window.loadIcons() y window.applyIcons()).
  • Dashboard: se preserva la IP entre refresh empleando lastKnownIP. Se añadió botón "Mqtt" que abre un modal con JSON del dispositivo (estado SSE, IP, pines y firmware si está disponible).
  • Firmware: se añadió el campo firmware en data/dispositivos.json. Tras OTA exitosa (detectada por SSE reboot), se persiste la versión del manifest vía POST /api/edit_dispositivo.

Requisitos

  • PHP 8.0+
  • Composer
  • Broker MQTT accesible (TLS opcional)

Variables de Entorno (.env)

  • MQTT_HOST, MQTT_PORT, MQTT_TLS
  • MQTT_USERNAME, MQTT_PASSWORD, MQTT_CLIENT_ID
  • CORS_ALLOW_ORIGIN (por defecto *)
  • API_BASIC_USER, API_BASIC_PASS (para endpoints que lo requieran)

Ejemplo en /.env.example.

Arquitectura

  • Presentación: index.php (router raíz) que renderiza vistas PHP en paginas/ con layouts/layout.php.
    • Páginas clave: paginas/index.php (lista y gestión), paginas/dashboard.php (detalle), paginas/puertos.php, paginas/flash.php, paginas/login.php.
    • Frontend: assets/js/layout.js, assets/js/apiClient.js, assets/js/auth.js, CSS en assets/css/layout.css.
    • Íconos: assets/iconos.json + utilidades globales en layouts/layout.php.
  • API/Router: api/index.php con rutas vía ?r=....
  • Bootstrap API: api/bootstrap.php (CORS, helpers JSON, Dotenv). Si se define NON_JSON, no fuerza Content-Type JSON (usado por SSE).
  • MQTT Backend:
    • Publicar: api/mqtt_publish.php usando php-mqtt/client.
    • Estados SSE: api/sse_states.php se suscribe a MQTT y emite eventos state, reboot, pin e ip.
  • Datos: Archivos JSON (/data/*.json).

Endpoints (router api/index.php)

  • Autenticación: salvo login y sse_states, todas las rutas requieren sesión activa (api/login).
  • GET api/index.php?r=get_dispositivos
  • POST api/index.php?r=add_dispositivo
  • POST api/index.php?r=edit_dispositivo
  • POST api/index.php?r=update_dispositivo
  • POST api/index.php?r=delete_dispositivo
  • GET api/index.php?r=get_sectores
  • POST api/index.php?r=add_sector
  • POST api/index.php?r=update_sector
  • POST api/index.php?r=delete_sector
  • GET api/index.php?r=get_puertos
  • POST api/index.php?r=update_puertos
  • POST api/index.php?r=asignar_puertos
  • POST api/index.php?r=mqtt_publish (publicación MQTT backend)
  • POST api/index.php?r=add_entidad
  • GET api/index.php?r=sse_states (SSE: stream de estados desde backend)
  • POST api/index.php?r=login
  • POST api/index.php?r=logout
  • GET api/index.php?r=me

Detalles de endpoints

  1. get_dispositivos
  • Metodo: GET
  • URL: api/index.php?r=get_dispositivos
  • Body: ninguno
  • Respuesta: objeto cuyas claves son chipid y valor el dispositivo
{
  "A1B2C3": {"id_disp":1,"chipid":"A1B2C3","tag":"X","nombre":"Y","ciudad":"Z","sector":"S","firmware":"12.9.2024.2222"}
}
  1. add_dispositivo
  • Método: POST (JSON)
  • URL: api/index.php?r=add_dispositivo
  • Body:
{"chipid":"A1B2C3","tag":"X","nombre":"Y","ciudad":"Z","sector":"S"}
  • Respuesta: { "success": true } o error 400/500 con mensaje.
  1. edit_dispositivo
  • Método: POST (JSON)
  • URL: api/index.php?r=edit_dispositivo
  • Body ejemplo:
{"chipid":"A1B2C3","tag":"nuevo","nombre":"modelo","ciudad":"CABA","sector":"Oficina"}
  • Respuesta:
{"success":true}
  1. update_dispositivo
  • Método: POST (JSON)
  • URL: api/index.php?r=update_dispositivo
  • Body: objeto { chipid: { ...campos } } por cada dispositivo a actualizar.
  • Respuesta: { "success": true } o error 500.
  1. delete_dispositivo
  • Método: POST (JSON)
  • URL: api/index.php?r=delete_dispositivo
  • Body:
{"chipid":"A1B2C3"}
  • Respuesta (éxito):
{"success":true,"msg":"Dispositivo eliminado correctamente","backup":"backups/dispositivos_backup_YYYYmmdd_HHMMSS.json"}
  1. get_sectores
  • Método: GET
  • URL: api/index.php?r=get_sectores
  • Respuesta: arreglo de strings
["Oficina","Deposito"]
  1. add_sector
  • Método: POST (JSON)
  • URL: api/index.php?r=add_sector
  • Body:
{"sector":"Oficina"}
  • Respuesta: { "success": true } o { "success": false, "msg": "..." }
  1. update_sector
  • Método: POST (JSON)
  • URL: api/index.php?r=update_sector
  • Body:
{"old_sector":"Oficina","new_sector":"Oficina 2"}
  • Respuesta: { "success": true } o error con msg.
  1. delete_sector
  • Método: POST (JSON)
  • URL: api/index.php?r=delete_sector
  • Body:
{"sector":"Oficina"}
  • Respuesta: { "success": true } o error con msg.
  1. get_puertos
  • Método: GET
  • URL: api/index.php?r=get_puertos&chipid=CHIPID
  • Respuesta (éxito): objeto de configuración por puerto o {"error":"..."} si falta chipid o no existe.
  1. update_puertos
  • Método: POST (JSON)
  • URL: api/index.php?r=update_puertos
  • Body ejemplo (solo actualiza campos existentes):
{
  "A1B2C3": {
    "D1": {"disp":"Luz Hall","notas":"","modo":"OUTPUT"},
    "D2": {"disp":"Sensor","notas":"","modo":"INPUT_PULLUP"}
  }
}
  • Respuesta: { "success": true, "message": "Puertos actualizados con éxito" }
  1. asignar_puertos
  • Método: POST (JSON)
  • URL: api/index.php?r=asignar_puertos
  • Body ejemplo (crea/actualiza entradas de puertos; define gpio, disp, modo, notas):
{
  "A1B2C3": {
    "D1": {"gpio":5, "disp":"Luz Hall", "modo":"OUTPUT", "notas":""},
    "D2": {"gpio":4, "disp":"Sensor", "modo":"INPUT_PULLUP", "notas":"ok"}
  }
}
  • Respuesta: { "success": true, "message": "Dispositivo actualizado con éxito" }
  1. mqtt_publish
  • Método: POST (JSON)
  • URL: api/index.php?r=mqtt_publish
  • Body:
{"topic":"dispositivo/CHIPID/comando/reboot","payload":"1","qos":0,"retain":false}
  • Respuesta: { "success": true, "data": {"topic":"...","payload":"..."} } o error 500 con mensaje.
  1. sse_states
  • Método: GET (stream SSE)
  • URL: api/index.php?r=sse_states
  • Eventos emitidos:
    • state: { "chipid": "A1B2C3", "estado": "online|offline|..." }
    • reboot: { "chipid": "A1B2C3", "payload": "0|1" }
    • pin: { "chipid": "A1B2C3", "alias": "D1", "valor": "ON|OFF|..." }
    • ip: { "chipid": "A1B2C3", "ip": "192.168.0.10" }
  • Notas: el servidor envía pings cada ~2s. El cliente debe reconectar ante errores.
  1. login
  • Método: POST (JSON)
  • URL: api/index.php?r=login
  • Body:
{"usuario":"api","clave":"API#2025"}
  • Respuesta: { "success": true, "data": { "user": "...", "rol": "admin", "session_id": "..." } }
  1. logout
  • Método: POST (JSON)
  • URL: api/index.php?r=logout
  • Respuesta: { "success": true }
  1. me
  • Método: GET
  • URL: api/index.php?r=me
  • Respuesta: { "success": true, "data": { "user": "...", "rol": "..." } }
  1. add_entidad
  • Método: POST (JSON)
  • URL: api/index.php?r=add_entidad
  • Body:
{"entidad":"Lampara"}
  • Respuesta (éxito):
{"success":true}
  • Respuesta (error de validación):
{"success":false,"message":"No se proporcionó entidad."}
  • Notas:
    • Persiste en data/entidades.json agregando al arreglo entidades.
    • Actualmente no valida duplicados ni tipo, pendiente endurecer validaciones.

Formatos de Respuesta

La mayoría de las respuestas (no-SSE) usan:

{
  "success": true,
  "message": "OK",
  "data": { /* payload */ }
}

En error:

{
  "success": false,
  "message": "Error descriptivo",
  "data": null
}
  • Nota: Algunos GET (p. ej. get_dispositivos, get_sectores, get_puertos) devuelven arreglos/objetos crudos y, en caso de error, pueden responder con formas simples (por ejemplo { "error": "..." }).

MQTT desde backend

  • Publicar (ejemplo curl):
curl -X POST "http://127.0.0.1:8000/api/index.php?r=mqtt_publish" \
  -H "Content-Type: application/json" \
  -d '{"topic":"dispositivo/123/comando/reboot","payload":"1","qos":0,"retain":false}'
  • Requisitos: variables de entorno MQTT configuradas en .env.

SSE (Estados en tiempo real)

  • Frontend (index.html) usa:
const es = new EventSource('api/index.php?r=sse_states');
es.addEventListener('state', (ev) => { /* actualiza UI */ });
es.addEventListener('reboot', (ev) => { /* reactiva botón */ });
  • Backend: api/sse_states.php se conecta al broker, reemite como SSE y envía pings cada ~2s.
  • Reconexión: el cliente reintenta al caer; el servidor mantiene ignore_user_abort(true).

Seguridad

  • Sesiones PHP para la interfaz web (index.php enruta a paginas/ según sesión).
  • Endpoints login, logout y me para autenticación; el resto de rutas requieren sesión activa (excepto login y sse_states).
  • No expongas credenciales MQTT en el frontend.
  • Ajusta CORS_ALLOW_ORIGIN en producción.
  • TLS: sse_states.php/mqtt_publish.php permiten certs self-signed (setTlsSelfSignedAllowed(true)); ajustar según tu CA.

Páginas y funcionalidades del sitio

  • Dashboard de dispositivos (paginas/index.php):
    • Listado con estado online/offline en tiempo real (SSE/MQTT).
    • CRUD básico de dispositivos (crear, editar, eliminar) y gestión de sectores.
    • Acción de reinicio remoto vía MQTT.
  • Dashboard individual (paginas/dashboard.php):
    • Estado de conexión, última actividad y IP del dispositivo (IP se preserva entre refresh y se actualiza por SSE ip).
    • Visualización de estado de pines y controles rápidos ON/OFF (vía MQTT) para salidas configuradas.
    • Botón "Mqtt" que abre modal con JSON informativo (estado SSE, IP, pines y firmware si está disponible).
  • Gestión de puertos (paginas/puertos.php):
    • Asignación de alias, modo y notas por pin/placa.
  • Flasheo de firmware (paginas/flash.php):
    • Integración con ESP Web Tools y Web Serial para flasheo USB en el navegador.
    • Manifiesto: assets/flash/esp8266-manifest.json (usa version y builds[].parts[].path).
    • Tras OTA exitosa (SSE reboot), se persiste la versión de firmware en data/dispositivos.json vía POST /api/edit_dispositivo.

Compilación de firmware (PlatformIO)

  • Requisitos: Python 3.12+, PlatformIO Core (pio).
  • Script: tools/build_firmware.ps1 compila firmware/ y copia artefactos a assets/flash/firmware/.
  • Artefactos esperados:
    • firmware/.pio/build/nodemcuv2/firmware.bin
    • assets/flash/firmware/esp8266-nodemcuv2-YYYYMMDD-HHMMSS.bin

Cómo ejecutar en local

  1. Configura .env (ver Variables de Entorno).
  2. Instala dependencias PHP con Composer:
    • composer install
  3. Sirve el proyecto con un servidor PHP o Apache/Nginx que respete .htaccess:
    • PHP embebido: php -S 127.0.0.1:8000 -t .
  4. Accede a http://127.0.0.1:8000/ y loguéate.

Notas:

  • Recomendado PHP 8.0+ (probado en 8.x). Compatible con >=7.4 según dependencias.
  • Asegúrate de que el servidor permita conexiones largas para SSE.

Troubleshooting

  • "Fallo al publicar MQTT": revisa conectividad al broker y credenciales .env.
  • SSE no conecta: verifica que el servidor PHP permita procesos largos y no cierre conexiones por proxy/reverse-proxy.
  • CORS: ajustar CORS_ALLOW_ORIGIN.