|
|
||
|---|---|---|
| .config/pulse | ||
| api | ||
| assets | ||
| backups | ||
| data | ||
| firmware | ||
| layouts | ||
| paginas | ||
| tools | ||
| vendor | ||
| .env | ||
| .env.example | ||
| .gitignore | ||
| .htaccess | ||
| README.md | ||
| composer.json | ||
| composer.lock | ||
| favicon.ico | ||
| fix_dashboard.php | ||
| fix_dashboard2.php | ||
| fix_login_bg.php | ||
| index.php | ||
| package.json | ||
| plan.md | ||
| sse_diagnose.log | ||
| tailwind.config.js | ||
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_useryremember_passde larga duración y se prellenan en el próximo acceso. - Íconos: se centralizó el uso de SVG en
assets/iconos.jsony se inyectan automáticamente víalayouts/layout.php(window.loadIcons()ywindow.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
firmwareendata/dispositivos.json. Tras OTA exitosa (detectada por SSEreboot), se persiste la versión del manifest víaPOST /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 enpaginas/conlayouts/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 enassets/css/layout.css. - Íconos:
assets/iconos.json+ utilidades globales enlayouts/layout.php.
- Páginas clave:
- API/Router:
api/index.phpcon rutas vía?r=.... - Bootstrap API:
api/bootstrap.php(CORS, helpers JSON, Dotenv). Si se defineNON_JSON, no fuerza Content-Type JSON (usado por SSE). - MQTT Backend:
- Publicar:
api/mqtt_publish.phpusandophp-mqtt/client. - Estados SSE:
api/sse_states.phpse suscribe a MQTT y emite eventosstate,reboot,pineip.
- Publicar:
- Datos: Archivos JSON (
/data/*.json).
Endpoints (router api/index.php)
- Autenticación: salvo
loginysse_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
- get_dispositivos
- Metodo: GET
- URL:
api/index.php?r=get_dispositivos - Body: ninguno
- Respuesta: objeto cuyas claves son
chipidy valor el dispositivo
{
"A1B2C3": {"id_disp":1,"chipid":"A1B2C3","tag":"X","nombre":"Y","ciudad":"Z","sector":"S","firmware":"12.9.2024.2222"}
}
- 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.
- 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}
- 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.
- 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"}
- get_sectores
- Método: GET
- URL:
api/index.php?r=get_sectores - Respuesta: arreglo de strings
["Oficina","Deposito"]
- add_sector
- Método: POST (JSON)
- URL:
api/index.php?r=add_sector - Body:
{"sector":"Oficina"}
- Respuesta:
{ "success": true }o{ "success": false, "msg": "..." }
- 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 conmsg.
- delete_sector
- Método: POST (JSON)
- URL:
api/index.php?r=delete_sector - Body:
{"sector":"Oficina"}
- Respuesta:
{ "success": true }o error conmsg.
- 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 faltachipido no existe.
- 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" }
- 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" }
- 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.
- 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.
- 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": "..." } }
- logout
- Método: POST (JSON)
- URL:
api/index.php?r=logout - Respuesta:
{ "success": true }
- me
- Método: GET
- URL:
api/index.php?r=me - Respuesta:
{ "success": true, "data": { "user": "...", "rol": "..." } }
- 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.jsonagregando al arregloentidades. - Actualmente no valida duplicados ni tipo, pendiente endurecer validaciones.
- Persiste en
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.phpse 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.phpenruta apaginas/según sesión). - Endpoints
login,logoutymepara autenticación; el resto de rutas requieren sesión activa (exceptologinysse_states). - No expongas credenciales MQTT en el frontend.
- Ajusta
CORS_ALLOW_ORIGINen producción. - TLS:
sse_states.php/mqtt_publish.phppermiten 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).
- Estado de conexión, última actividad y IP del dispositivo (IP se preserva entre refresh y se actualiza por SSE
- 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(usaversionybuilds[].parts[].path). - Tras OTA exitosa (SSE
reboot), se persiste la versión de firmware endata/dispositivos.jsonvíaPOST /api/edit_dispositivo.
Compilación de firmware (PlatformIO)
- Requisitos: Python 3.12+, PlatformIO Core (pio).
- Script:
tools/build_firmware.ps1compilafirmware/y copia artefactos aassets/flash/firmware/. - Artefactos esperados:
firmware/.pio/build/nodemcuv2/firmware.binassets/flash/firmware/esp8266-nodemcuv2-YYYYMMDD-HHMMSS.bin
Cómo ejecutar en local
- Configura
.env(ver Variables de Entorno). - Instala dependencias PHP con Composer:
composer install
- Sirve el proyecto con un servidor PHP o Apache/Nginx que respete
.htaccess:- PHP embebido:
php -S 127.0.0.1:8000 -t .
- PHP embebido:
- 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.