first commit
This commit is contained in:
commit
4f37389cc5
|
|
@ -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`.
|
||||
Loading…
Reference in New Issue