first commit

This commit is contained in:
Penki 2026-05-27 09:39:39 -03:00
commit 4f37389cc5
1 changed files with 323 additions and 0 deletions

323
README.md Normal file
View File

@ -0,0 +1,323 @@
# 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
```json
{
"A1B2C3": {"id_disp":1,"chipid":"A1B2C3","tag":"X","nombre":"Y","ciudad":"Z","sector":"S","firmware":"12.9.2024.2222"}
}
```
2) add_dispositivo
- Método: POST (JSON)
- URL: `api/index.php?r=add_dispositivo`
- Body:
```json
{"chipid":"A1B2C3","tag":"X","nombre":"Y","ciudad":"Z","sector":"S"}
```
- Respuesta: `{ "success": true }` o error 400/500 con mensaje.
3) edit_dispositivo
- Método: POST (JSON)
- URL: `api/index.php?r=edit_dispositivo`
- Body ejemplo:
```json
{"chipid":"A1B2C3","tag":"nuevo","nombre":"modelo","ciudad":"CABA","sector":"Oficina"}
```
- Respuesta:
```json
{"success":true}
```
4) 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.
5) delete_dispositivo
- Método: POST (JSON)
- URL: `api/index.php?r=delete_dispositivo`
- Body:
```json
{"chipid":"A1B2C3"}
```
- Respuesta (éxito):
```json
{"success":true,"msg":"Dispositivo eliminado correctamente","backup":"backups/dispositivos_backup_YYYYmmdd_HHMMSS.json"}
```
6) get_sectores
- Método: GET
- URL: `api/index.php?r=get_sectores`
- Respuesta: arreglo de strings
```json
["Oficina","Deposito"]
```
7) add_sector
- Método: POST (JSON)
- URL: `api/index.php?r=add_sector`
- Body:
```json
{"sector":"Oficina"}
```
- Respuesta: `{ "success": true }` o `{ "success": false, "msg": "..." }`
8) update_sector
- Método: POST (JSON)
- URL: `api/index.php?r=update_sector`
- Body:
```json
{"old_sector":"Oficina","new_sector":"Oficina 2"}
```
- Respuesta: `{ "success": true }` o error con `msg`.
9) delete_sector
- Método: POST (JSON)
- URL: `api/index.php?r=delete_sector`
- Body:
```json
{"sector":"Oficina"}
```
- Respuesta: `{ "success": true }` o error con `msg`.
10) 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.
11) update_puertos
- Método: POST (JSON)
- URL: `api/index.php?r=update_puertos`
- Body ejemplo (solo actualiza campos existentes):
```json
{
"A1B2C3": {
"D1": {"disp":"Luz Hall","notas":"","modo":"OUTPUT"},
"D2": {"disp":"Sensor","notas":"","modo":"INPUT_PULLUP"}
}
}
```
- Respuesta: `{ "success": true, "message": "Puertos actualizados con éxito" }`
12) 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`):
```json
{
"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" }`
13) mqtt_publish
- Método: POST (JSON)
- URL: `api/index.php?r=mqtt_publish`
- Body:
```json
{"topic":"dispositivo/CHIPID/comando/reboot","payload":"1","qos":0,"retain":false}
```
- Respuesta: `{ "success": true, "data": {"topic":"...","payload":"..."} }` o error 500 con mensaje.
14) 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.
15) login
- Método: POST (JSON)
- URL: `api/index.php?r=login`
- Body:
```json
{"usuario":"api","clave":"API#2025"}
```
- Respuesta: `{ "success": true, "data": { "user": "...", "rol": "admin", "session_id": "..." } }`
16) logout
- Método: POST (JSON)
- URL: `api/index.php?r=logout`
- Respuesta: `{ "success": true }`
17) me
- Método: GET
- URL: `api/index.php?r=me`
- Respuesta: `{ "success": true, "data": { "user": "...", "rol": "..." } }`
18) add_entidad
- Método: POST (JSON)
- URL: `api/index.php?r=add_entidad`
- Body:
```json
{"entidad":"Lampara"}
```
- Respuesta (éxito):
```json
{"success":true}
```
- Respuesta (error de validación):
```json
{"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:
```json
{
"success": true,
"message": "OK",
"data": { /* payload */ }
}
```
En error:
```json
{
"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):
```bash
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:
```js
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`.