Proyecto OTITIA — Planificación en 8 sesiones de 3 h¶
1. Decisiones de partida¶
| Aspecto | Decisión |
|---|---|
| Duración | 8 sesiones × 3 h = 24 h de trabajo en aula |
| Stack | OTITIA (Docker Compose): Mosquitto, Node-RED, Telegraf, InfluxDB 2.7, InfluxDB 3 Core, MySQL, N8N, Grafana, WordPress |
| Sensórica | Simulada (flujos Node-RED + API Open-Meteo + entradas Telegraf). Sin hardware físico |
| Broker MQTT | Mosquitto local (otitia-mosquitto), no HiveMQ público |
| TSDB principal | InfluxDB 2.7 (Flux). InfluxDB 3 Core como comparativa lateral |
| Organización / bucket | s8a / h2v |
| LLM | Sesión propia (Sesión 7) |
| Documentos existentes | timeseries.md, nodered.md, influxdb.md |
| Documentos nuevos | grafana.md, llm-iot.md |
Convenciones del proyecto¶
Fijar esto el primer día evita el caos de la sesión 4 en adelante.
Jerarquía de topics MQTT
otitia/<zona>/<dispositivo>/<magnitud>
otitia/aula/sensor01/temperatura
otitia/aula/sensor01/humedad
otitia/exterior/meteo/temperatura
otitia/planta/electrolizador/presion
otitia/alertas/<zona>/<nivel>
Modelo InfluxDB
- Medida:
sensores - Tags:
zona,dispositivo,magnitud(valores estables → indexables) - Fields:
valor,bateria,rssi(valores cambiantes) - Timestamp: UTC, precisión de segundos (
s) salvo que se justifique otra
Nomenclatura del alumnado: cada estudiante trabaja bajo otitia/<iniciales>/... para no pisarse en el broker compartido del VPS.
2. Mapa de contenidos: de los apuntes a las sesiones¶
| Sesión | Documento fuente | Estado del material |
|---|---|---|
| 1 | timeseries.md (§ Introducción → Almacenamiento) + guía de arranque OTITIA |
Reutilizar, recortar pandas |
| 2 | nodered.md (§ Hola Node-RED → Añadiendo lógica de control) |
Reutilizar casi íntegro |
| 3 | nodered.md (§ MQTT con Node-RED) |
Refactorizar: HiveMQ → Mosquitto |
| 4 | influxdb.md (§ Elementos → Ingesta de datos, § Node-RED e InfluxDB) |
Reutilizar + añadir Telegraf |
| 5 | influxdb.md (§ Flux → InfluxDB y Python) |
Reutilizar |
| 6 | — | Escribir grafana.md |
| 7 | — | Escribir llm-iot.md |
| 8 | timeseries.md (§ Uso de Pandas, § Proyecto) como reto opcional |
Reubicar como ampliación |
3. Desarrollo de las sesiones¶
Sesión 1 — Series temporales y arranque del stack¶
Objetivos * Distinguir una serie temporal de otros tipos de datos y justificar el uso de una TSDB. * Poner en marcha el stack OTITIA y verificar que todos los servicios responden.
Contenidos
* Serie temporal: definición, orden cronológico, naturaleza append-only.
* Tipos: métrica (regular) vs evento (irregular).
* Características de los datos IoT: dependencia temporal, alta frecuencia, tamaño creciente, ruido.
* Descomposición: tendencia, estacionalidad, ciclo, ruido.
* Opciones de almacenamiento: edge, gateway, TSDB, NoSQL, relacional. Por qué InfluxDB.
* Arquitectura OTITIA: redes otitia-ot / otitia-it, papel de cada servicio.
Desarrollo (3 h)
| Tiempo | Actividad |
| --- | --- |
| 45' | Teoría de series temporales con ejemplos gráficos |
| 30' | Presentación del proyecto, arquitectura y convenciones |
| 60' | Taller: docker compose up, verificación de los 9 servicios, tour por Node-RED (1880), Influx UI (8086), Grafana (3000) |
| 45' | Actividad 1 + resolución de incidencias de instalación |
Entregable: capturas de los tres interfaces funcionando + ficha con puertos, credenciales y comandos básicos de Docker.
Actividad 1 (RABDA.1 / 1p) — Documenta el stack: para cada servicio, indica imagen, puerto, red a la que pertenece y su papel en el pipeline. Dibuja el diagrama de flujo de datos de extremo a extremo.
Aviso de aula: reservar margen real para instalaciones fallidas. Windows con WSL2 exige que el proyecto viva en
~/proyectos/, nunca en/mnt/c/.
Sesión 2 — Node-RED y simulación de sensores¶
Objetivos * Construir flujos con nodos de entrada, proceso y salida. * Programar la lógica de simulación de una red de sensores.
Contenidos
* Entorno: paleta, área de trabajo, panel lateral, despliegue.
* Nodos inject, debug, function. Estructura del mensaje (msg.payload).
* JavaScript en nodos función: múltiples salidas, node.send(), node.log().
* Contexto de nodo, de flujo y global.
* Consumo de API REST con http request (Open-Meteo).
* Lógica de control: switch y change con JSONata.
* Exportación / importación de flujos en JSON.
Desarrollo (3 h)
| Tiempo | Actividad |
| --- | --- |
| 30' | Hola Node-RED: inject → debug, primer despliegue |
| 45' | Nodos función, logger y contexto |
| 45' | Taller: simulador de sensores (temperatura interior/exterior, humedad, alarma) cada 30 s en un único JSON |
| 40' | Taller: datos reales de Open-Meteo + limpieza del JSON en nodo función |
| 20' | Alertas con switch + change |
Entregable: flujo 01-simulador.json exportado, con nodos comentados.
Actividad 2 (RAMIA.4 / CEMIA.4d / 2p) — Simulador de planta: genera cada 30 s las lecturas de al menos tres dispositivos distintos, cada uno con su zona y magnitudes. Sustituye la temperatura exterior por el valor real de Open-Meteo para Elche. Añade lógica que marque cada lectura con un nivel de severidad (ok, aviso, critico).
Sesión 3 — MQTT con Mosquitto¶
Objetivos * Explicar el modelo publish/subscribe y sus garantías. * Desacoplar el simulador del consumidor mediante el broker.
Contenidos
* MQTT: origen, posición en la pila TCP/IP, comparación con HTTP.
* Broker, publisher, subscriber. Mosquitto en el stack OTITIA.
* Diseño de topics jerárquicos. Comodines + y #.
* QoS 0/1/2 y mensajes retenidos: cuándo usar cada uno.
* Nodos mqtt in / mqtt out. Configuración del servidor otitia-mosquitto:1883.
* Conversión de tipos al recibir (fechas como cadena → Date).
* Herramientas de línea: mosquitto_pub / mosquitto_sub desde el contenedor.
Desarrollo (3 h)
| Tiempo | Actividad |
| --- | --- |
| 40' | Teoría MQTT y diseño de la jerarquía de topics del proyecto |
| 30' | Taller: mosquitto_sub -t 'otitia/#' -v para "ver" el bus en directo |
| 60' | Taller: publicar el simulador de la sesión 2 en MQTT (flujo publisher) |
| 40' | Taller: flujo subscriber independiente que consume otitia/# |
| 10' | Prueba de QoS y retained: parar y arrancar el subscriber |
Entregable: flujos 02-publisher.json y 03-subscriber.json, más la tabla de topics del proyecto.
Actividad 3 (RAMIA.4 / CEMIA.4d / 2p) — Publica cada lectura en su topic según la jerarquía acordada. Crea un segundo flujo que se suscriba con comodines y que reenvíe a otitia/alertas/<zona>/<nivel> únicamente las lecturas cuya severidad no sea ok. Documenta qué QoS has elegido para cada caso y por qué.
Refactorización pendiente: en
nodered.md, sustituirbroker.hivemq.com:1883porotitia-mosquitto:1883(olocalhost:1883desde el host) y los topics/s8a/h2v/noderedpor la jerarquíaotitia/.... Conservar la mención a brokers públicos como nota informativa.
Sesión 4 — InfluxDB: modelo de datos e ingesta¶
Objetivos * Modelar correctamente medidas, tags y fields evitando problemas de cardinalidad. * Ingerir datos por tres vías distintas y compararlas.
Contenidos
* Elementos: organización, bucket, medida, tag, field, punto, serie, timestamp.
* Retención y tokens. Equivalencias con el modelo relacional.
* Protocolo de línea. Cardinalidad de series: por qué la temperatura nunca es un tag.
* Influx UI y Influx CLI (influx org, influx bucket, influx write).
* El arco de la ingesta en tres fases, cada una motivada por la limitación de la anterior:
1. **Node-RED → InfluxDB.** Nodo `influxdb out` directo. Funciona enseguida y el dato llega. *Límite*: un único consumidor, Node-RED conoce credenciales y esquema de la base de datos, y si InfluxDB no responde el dato se pierde.
2. **Node-RED → MQTT → Node-RED → InfluxDB.** Aparece el bus. El productor ya no sabe quién consume, y podemos añadir suscriptores (alertas, N8N, MySQL) sin tocarlo. *Límite*: la lógica de ingesta sigue siendo código propio, con sus reintentos, lotes y conversiones de tipo.
3. **Node-RED → MQTT → Telegraf → InfluxDB.** La ingesta pasa a ser declarativa: `metric_buffer_limit`, escritura por lotes y reintentos sin escribir una línea de código.
- Comparativa final: acoplamiento, fan-out, resistencia a caídas y coste de mantenimiento.
Desarrollo (3 h)
| Tiempo | Actividad |
| --- | --- |
| 45' | Modelo de datos y protocolo de línea. Ejercicio en papel de diseño de esquema |
| 25' | Influx UI y CLI: crear organización, bucket y token |
| 30' | Fase 1: Node-RED escribe directo con influxdb out |
| 35' | Fase 2: se interpone el bus. Flujo publicador + flujo suscriptor que inserta |
| 40' | Fase 3: Telegraf con mqtt_consumer sustituye al suscriptor |
| 5' | Cierre: tabla comparativa de las tres fases |
Insistir en clase: en la fase 3, Telegraf no sustituye a Node-RED, sustituye al consumidor de la fase 2. Node-RED sigue siendo el sensor de principio a fin. Sin esta aclaración, media clase entiende que Telegraf viene a reemplazarlo.
Enlace con la sesión 8: la fase 1 es el único punto donde Node-RED toca la red IT. Cuando al final del proyecto retiremos esa red, la fase 1 dejará de funcionar y el resto del sistema seguirá vivo. Ese es exactamente el resultado esperado.
Entregable: los tres flujos del arco (04a-directo.json, 04b-mqtt.json, 04c-telegraf.json), el telegraf.conf comentado y los datos visibles en el Data Explorer.
Actividad 4 (RABDA.1 / CEBDA.1d / 2p) — Diseña y justifica el esquema de la medida sensores: qué va en tags, qué en fields y qué pasaría si lo invirtieras. Implementa las tres fases del arco de ingesta y documenta, para cada una, qué problema resuelve y qué limitación arrastra. Prueba a parar InfluxDB (docker compose stop influxdb) durante un minuto con las fases 1 y 3 activas, vuelve a arrancarlo y compara cuántos puntos se han perdido en cada caso.
Comparativa opcional: escribir el mismo line protocol contra InfluxDB 3 Core (puerto 8181,
/api/v3/write_lp) y observar que el dato entra igual pero el concepto pasa de bucket a database.
Sesión 5 — Consultas Flux, tareas y Python¶
Objetivos * Consultar y agregar series temporales con Flux. * Automatizar el downsampling con tareas y acceder a los datos desde Python.
Contenidos
* Flux: sintaxis, tuberías (|>), variables, funciones.
* Flujos de tablas: from, range, filter, group, keep, pivot.
* Transformación: map, rename, sort, limit.
* Agregación: mean, max, min, count, aggregateWindow.
* Remuestreo y downsampling: de datos cada 30 s a medias cada 5 min.
* Tareas programadas: crear un bucket h2v_agregado y alimentarlo automáticamente.
* Cliente Python influxdb-client: escritura y consulta.
Desarrollo (3 h)
| Tiempo | Actividad |
| --- | --- |
| 50' | Flux paso a paso sobre los datos ya cargados |
| 40' | Agregaciones y aggregateWindow |
| 40' | Taller: tarea de downsampling a bucket agregado |
| 40' | Taller: script Python de escritura y lectura |
| 10' | Políticas de retención: bucket crudo 7 d, agregado 1 año |
Entregable: colección de consultas Flux comentadas, tarea activa y consulta.py.
Actividad 5 (RABDA.1 / CEBDA.1d / 2p) — Escribe las consultas Flux que resuelvan: (a) últimas lecturas de cada dispositivo, (b) temperatura media por hora y zona del último día, (c) máximo y mínimo por dispositivo, (d) número de alertas por zona. Crea una tarea que consolide medias de 15 min en el bucket agregado, y desde Python recupera y muestra el resultado.
Sesión 6 — Visualización con Grafana¶
Objetivos * Conectar Grafana a InfluxDB y construir un cuadro de mandos operativo. * Configurar alertas visuales sobre umbrales.
Contenidos
* Grafana en el stack: puerto 3000, credenciales, organización.
* Datasources: InfluxDB v2 con Flux (token, org, bucket) e InfluxDB 3 Core con SQL.
* Tipos de panel: time series, stat, gauge, table, bar chart, state timeline.
* Variables de dashboard (zona, dispositivo) y consultas parametrizadas con ${var}.
* Rango temporal, auto-refresco y modo TV.
* Umbrales, unidades y transformaciones.
* Alertas de Grafana: regla, condición, punto de contacto.
* Comparativa: Influx UI vs FlowFuse Dashboard vs Grafana.
* Exportación del dashboard a JSON y aprovisionamiento.
Desarrollo (3 h) | Tiempo | Actividad | | --- | --- | | 30' | Grafana: conceptos y creación del datasource Flux | | 45' | Taller: primeros paneles (evolución de temperatura, stat con valor actual) | | 45' | Taller: variables de dashboard y paneles parametrizados por zona | | 40' | Taller: umbrales y regla de alerta | | 20' | Exportar el dashboard a JSON y versionarlo |
Entregable: dashboard-otitia.json exportado y funcionando tras reimportar.
Actividad 6 (RABDA.1 / CEBDA.1d / 2p) — Construye un cuadro de mandos con: valor actual de cada magnitud por zona, evolución de las últimas 6 h, tabla de alertas recientes y un indicador con el porcentaje de tiempo fuera de umbral. Debe incluir al menos una variable de dashboard y una regla de alerta.
Sesión 7 — Integración de un LLM vía API¶
Objetivos * Invocar una API de LLM desde el pipeline IoT y tratar su respuesta como un dato más. * Valorar críticamente qué aporta y qué riesgos introduce un LLM en un sistema de monitorización.
Contenidos
* Qué es una API de LLM: petición, mensajes, max_tokens, respuesta JSON.
* Gestión de credenciales: variable de entorno en el contenedor, nunca la clave dentro del flujo exportado.
* Llamada desde Node-RED con http request: cabeceras, cuerpo JSON, parseo de la respuesta.
* Llamada desde Python como alternativa.
* Construcción del prompt en un nodo función a partir de datos reales de InfluxDB.
* Salida estructurada: exigir JSON puro en el system prompt y parsear con JSON.parse(); manejar el fallo de parseo.
* Casos de uso del proyecto:
1. Resumen en lenguaje natural del estado de la planta en las últimas 24 h.
2. Explicación de una alerta: dado un pico anómalo y su contexto, redactar causa probable y acción recomendada.
3. Clasificación de severidad de eventos con salida {"nivel": "...", "motivo": "..."}.
4. Traducción de lenguaje natural a Flux (uso exploratorio, siempre con revisión humana).
* Realimentación: escribir la respuesta del LLM de vuelta en InfluxDB o MySQL y mostrarla en un panel de texto de Grafana.
* Consideraciones críticas: latencia, coste por token, límites de tasa, alucinación, no invocar el LLM por cada punto de dato (agregar primero), y por qué el LLM nunca debe ser el único disparador de una acción física.
Desarrollo (3 h) | Tiempo | Actividad | | --- | --- | | 30' | Teoría: APIs de LLM, credenciales y coste | | 40' | Taller: primera llamada desde Node-RED y parseo de la respuesta | | 50' | Taller: prompt construido con datos reales de Influx → resumen diario | | 40' | Taller: salida estructurada JSON y escritura del resultado en la base de datos | | 20' | Debate: límites, riesgos y responsabilidad. ¿Dónde no pondrías un LLM? |
Entregable: flujo 05-llm.json (sin la clave de API dentro) y ejemplos de respuestas obtenidas.
Actividad 7 (RAMIA.4 / CEMIA.4d / 2p) — Implementa un flujo que, una vez al día, consulte a InfluxDB el resumen de las últimas 24 h, lo envíe al LLM y almacene el informe generado. El prompt debe forzar salida JSON con los campos resumen, anomalias y recomendacion. Muestra el resultado en Grafana. Documenta en media página qué acertó y qué falló el modelo, con un ejemplo concreto de cada.
Sesión 8 — Proyecto integrador, análisis avanzado y evaluación¶
Objetivos * Integrar todas las piezas en un sistema funcional de extremo a extremo. * Defender las decisiones técnicas tomadas.
Contenidos
* Revisión del pipeline completo: simulación → MQTT → Telegraf → InfluxDB → Flux/tareas → Grafana → LLM.
* Endurecimiento de la frontera IT/OT: retirar la red it de Node-RED y comprobar que solo cae la rama de escritura directa. Si se cae algo más, hay acoplamiento oculto que documentar.
* Resiliencia: qué pasa si cae el broker, si Influx no responde, si la API del LLM da error.
* Documentación del proyecto y reproducibilidad (arrancar desde cero con los JSON exportados).
* Ampliación opcional: análisis con pandas — DatetimeIndex, resample, rolling, shift, interpolate y descomposición de la serie. Aquí encaja el proyecto de predicción de consumo energético de timeseries.md.
* Presentaciones y evaluación.
Desarrollo (3 h) | Tiempo | Actividad | | --- | --- | | 75' | Taller final: cierre de la integración y pruebas de fallo | | 30' | Ampliación pandas (para quien haya terminado) | | 60' | Presentaciones (8-10 min por equipo) | | 15' | Cierre y autoevaluación |
Entrega final: repositorio con docker-compose.yml, flujos Node-RED, telegraf.conf, consultas Flux, scripts Python, dashboard.json y un README.md que permita a otra persona reproducir el sistema.
4. Plan de refactorización de los apuntes existentes¶
timeseries.md¶
- Mantener: introducción, tipos, características, descomposición, tabla resumen, almacenamiento. → Sesión 1.
- Mover: todo el bloque de pandas (
resample,rolling,shift,interpolate) y el proyecto de energía → Sesión 8 como ampliación. - Añadir: párrafo puente que anticipe el pipeline del proyecto.
- Limpiar: los bloques comentados de estacionariedad, o bien rescatarlos como cuadro informativo en la ampliación.
nodered.md¶
- Mantener: de "Hola Node-RED" hasta "Añadiendo lógica de control". → Sesión 2.
- Refactorizar (prioritario): sección MQTT, cambiando HiveMQ por Mosquitto local y los topics a la jerarquía
otitia/.... → Sesión 3. - Reducir: la sección de MongoDB Atlas. En este proyecto el destino es InfluxDB. Dejarla como nota comparativa de una página o desplazarla a otro módulo.
- Decidir: FlowFuse Dashboard. Propuesta: conservarlo en la sesión 2 como visualización rápida de desarrollo, y contrastarlo con Grafana en la sesión 6.
- Reescribir actividades: las cuatro actuales apuntan a MongoDB y HiveMQ; sustituirlas por las actividades 2 y 3 de este plan.
influxdb.md¶
- Dividir en dos por volumen (1300 líneas es demasiado para una sesión):
influxdb-modelo.md— elementos, line protocol, primeros pasos, UI, CLI, ingesta. → Sesión 4.influxdb-flux.md— Flux, tareas, Python. → Sesión 5.- Añadir a la parte de ingesta: sección de Telegraf con
mqtt_consumer(ahora está muy breve y es clave en el stack). - Eliminar: la sección vacía y comentada de Grafana; pasa a documento propio.
- Actualizar: la actividad final ya menciona
s8a/h2v— alinearla con la medidasensoresy los tags acordados.
Documentos nuevos¶
grafana.md — índice propuesto:
1. Qué es Grafana y su papel en el stack
2. Datasources: InfluxDB v2 (Flux) e InfluxDB 3 Core (SQL)
3. Anatomía de un dashboard: paneles, filas, rango temporal
4. Tipos de panel y cuándo usar cada uno
5. Consultas Flux desde Grafana y transformaciones
6. Variables de dashboard
7. Umbrales, unidades y formato
8. Alertas: reglas y puntos de contacto
9. Exportación, versionado y aprovisionamiento
10. Comparativa: Influx UI vs FlowFuse Dashboard vs Grafana
11. Referencias y actividades
llm-iot.md — índice propuesto:
1. Qué aporta un LLM a un sistema IoT (y qué no)
2. Anatomía de una llamada a la API: mensajes, parámetros, respuesta
3. Gestión segura de credenciales en Docker y Node-RED
4. Llamada desde Node-RED con http request
5. Llamada desde Python
6. Construcción del prompt a partir de datos de InfluxDB
7. Salida estructurada en JSON y control de errores de parseo
8. Casos de uso: resumen, explicación de anomalías, clasificación, consulta en lenguaje natural
9. Realimentación: almacenar la respuesta y mostrarla en Grafana
10. Coste, latencia, límites de tasa y estrategias de agregación previa
11. Límites y responsabilidad: por qué el LLM no decide sobre actuadores
12. Referencias y actividades
5. Evaluación¶
Reparto por actividades¶
| Sesión | Actividad | RA / CE | Puntos |
|---|---|---|---|
| 1 | Documentación del stack | RABDA.1 / CEBDA.1d | 1 |
| 2 | Simulador de sensores | RAMIA.4 / CEMIA.4d | 2 |
| 3 | Publicación y consumo MQTT | RAMIA.4 / CEMIA.4d | 2 |
| 4 | Modelado e ingesta en InfluxDB | RABDA.1 / CEBDA.1d | 2 |
| 5 | Consultas Flux, tarea y Python | RABDA.1 / CEBDA.1d | 2 |
| 6 | Cuadro de mandos en Grafana | RABDA.1 / CEBDA.1d | 2 |
| 7 | Integración del LLM | RAMIA.4 / CEMIA.4d | 2 |
| 8 | Proyecto integrador y defensa | Ambos | 3 |
| — | Ampliación pandas (opcional) | RABDA.1 | +1 |
Criterios transversales¶
Aplicables a todas las entregas, en la línea de lo que ya indicas en nodered.md:
- Corrección funcional del flujo o consulta.
- Calidad del código JavaScript, Flux y Python.
- Documentación mediante comentarios en los propios flujos.
- Justificación de las decisiones de diseño (esquema, QoS, tags vs fields).
- Reproducibilidad: el trabajo debe arrancar desde cero en otra máquina.