Asignatura: Diseño y Programación Orientados a Objetos
Profesor: Agustín González Valenzuela
Estudiantes: Valentina Soto - Julián Nuñez - Vicente Mujica - Vicente Andrade
Fecha: 1 de Julio 2026
El monitoreo de sensores en procesos industriales es una tarea crítica que, en ciertos entornos, se realiza de forma tal que no permite la visualización histórica ni la lectura directa de métricas de interés.
El presente proyecto propone una interfaz gráfica de usuario que permite visualizar en tiempo real la temperatura de múltiples sensores, graficando su evolución histórica y ofreciendo estadísticas (mínimo, máximo y promedio histórico), convirtiendo el monitoreo en una experiencia intuitiva.
El sistema HMI Sensores se enmarca en el contexto del control y monitoreo de procesos donde la temperatura es una variable de control fundamental.
| Elemento | Descripción |
|---|---|
| Sensores de temperatura | Dispositivos físicos (o simulados) que miden la temperatura en distintos puntos del proceso. En el prototipo se definieron tres canales: Ambiente (18-25 °C), Proceso (60-80 °C) y Crítico (90-110 °C). |
| Fuente de datos | Puede ser un simulador que genera valores pseudoaleatorios mediante una función senoidal con ruido, o un puerto serial que recibe datos en formato CSV desde un dispositivo externo. |
| Interfaz gráfica (HMI) | Componente visual que presenta las lecturas, gráficos de evolución temporal y estadísticas consolidadas. |
| Usuario operador | Persona que interactúa con la interfaz para seleccionar sensores, cambiar la fuente de datos y visualizar la información. |
El sistema interactúa con el mundo exterior a través del puerto serial, canal por el que eventualmente llegan los datos de sensores conectados a un microcontrolador. Cuando se utiliza el modo simulador, la aplicación es completamente autonómica y no requiere conexión externa alguna.
| Actor principal | Operador del sistema |
| Precondiciones |
|
| Flujo principal |
|
| Postcondiciones |
|
Figura 3: Aplicación con simulador activo mostrando los tres sensores y el gráfico en tiempo real.
| Actor principal | Operador del sistema |
| Precondiciones |
|
| Flujo principal |
|
| Postcondiciones |
|
Figura 4: Sensor "Proceso" seleccionado, con gráfico verde y estadísticas actualizadas.
| Actor principal | Operador del sistema |
| Precondiciones |
|
| Flujo principal |
|
| Postcondiciones |
|
Figura 5: Fuente serial activa con indicador azul y datos recibidos.
El sistema sigue un patrón de arquitectura basado en componentes con una interfaz abstracta que permite el intercambio de fuentes de datos sin modificar la lógica de presentación. La separación de responsabilidades se logra mediante el patrón Strategy, donde la clase MainWindow delega la obtención de datos a un objeto que implementa la interfaz IDataSource.
La arquitectura se compone de las siguientes capas:
SensorChannel — Representa un canal de sensor con su nombre, rango de operación, color y valor actual. Contiene la lógica de generación de datos simulados (oscilación senoidal + ruido aleatorio).IDataSource (interfaz), DataSimulator (implementación simulada), SerialSource (implementación serial). Ambas implementan start(), stop(), sensorCount() y sensor().SensorCard (widget visual de cada sensor), MainWindow (ventana principal con layout, gráfico y controles), Qt Charts (librería de graficación).| Clase | Atributos clave | Métodos clave | Responsabilidad |
|---|---|---|---|
| SensorChannel | m_name, m_min, m_max, m_color, m_currentValue, m_phase | name(), color(), currentValue(), update(), setValue() | Modela un canal de sensor individual. El método update() genera un nuevo valor simulado usando una función senoidal con fase acumulativa y ruido aleatorio. |
| IDataSource | (interfaz sin atributos) | start(), stop(), sensorCount(), sensor() | Define el contrato que toda fuente de datos debe cumplir. Permite que MainWindow trabaje con cualquier implementación sin acoplamiento directo. |
| DataSimulator | m_timer, m_sensors | start(), stop(), sensorCount(), sensor(), onTick() | Genera datos simulados a intervalos regulares (500 ms). En cada tick, actualiza todos los sensores y emite la señal dataUpdated(). |
| SerialSource | m_port, m_sensors, m_buffer | setPort(), start(), stop(), sensorCount(), sensor(), onDataReady() | Recibe datos desde un puerto serial en formato CSV, los parsea y distribuye a los sensores correspondientes. |
| SensorCard | m_sensor, m_nameLabel, m_valueLabel, m_selected | updateValue(), setSelected(), mousePressEvent(), updateStyle() | Widget visual que muestra el nombre y valor actual de un sensor. Emite la señal clicked() al ser presionado. |
| MainWindow | m_source, m_simulator, m_serial, m_cards, m_chart, m_series, m_tick, m_min, m_max, m_sum, m_count | setupUI(), setupChart(), switchSource(), onDataUpdated(), onSensorCardClicked(), onConnectClicked() | Orquesta toda la aplicación. Construye la interfaz, gestiona el cambió entre fuentes de datos, actualiza el gráfico y las estadísticas. |
La arquitectura del sistema se fundamenta en el principio de inversión de dependencias y una estricta separación de responsabilidades. Como núcleo orquestador, MainWindow ejerce una relación de composición sobre las fuentes de datos concretas (DataSimulator y SerialSource), pero interactúa con ellas a través del polimorfismo que le provee la herencia de la interfaz abstracta IDataSource. Esta decisión de diseño garantiza que la ventana principal pueda intercambiar el flujo de información en tiempo de ejecución sin acoplarse a la lógica específica de generación o recepción de datos. A su vez, las fuentes de datos concretas componen y administran internamente el ciclo de vida de los modelos SensorChannel. Para el despliegue visual, MainWindow agrega dinámicamente instancias de SensorCard; cada una de estas tarjetas mantiene una referencia directa y unidireccional hacia su SensorChannel correspondiente. De este modo, se asegura que la interfaz gráfica actúe como una vista pasiva que refleja el estado del modelo de forma independiente, aislando la adquisición de datos de la capa de presentación.
A continuación se describe el flujo lógico paso a paso que representa el diagrama de secuencia para el caso de uso 1:
main(), que crea una instancia de MainWindow.MainWindow crea un objeto DataSimulator y un objeto SerialSource en su constructor.MainWindow conecta la señal dataUpdated() de DataSimulator al slot onDataUpdated().MainWindow llama a m_source->start(), que inicia un QTimer con intervalo de 500 ms.QTimer dispara la señal timeout() cada 500 ms, ejecutando DataSimulator::onTick().onTick() recorre el vector de sensores y llama a update() en cada uno.SensorChannel::update() calcula un nuevo valor usando la función seno con fase acumulativa mas ruido aleatorio.onTick() emite la señal dataUpdated().MainWindow::onDataUpdated() recibe la señal.onDataUpdated() consulta el valor actual de cada sensor mediante m_source->sensor(i).currentValue().onDataUpdated() actualiza el texto de cada SensorCard con updateValue().onDataUpdated() agrega el nuevo punto al QLineSeries del gráfico.onDataUpdated() ajusta el rango del eje X si es necesario y actualiza las estadísticas MIN, MAX y AVG.Se realizaron pruebas funciónales de los tres casos de uso definidos. A continuación se describen los resultados obtenidos:
Caso de uso 1 — Visualizar datos simulados: La aplicación se compiló y ejecutó correctamente en el entorno Windows utilizando Qt Creator con el compilador MinGW. Al iniciar, el simulador comenzó a generar datos inmediatamente. Las tarjetas de sensor se actualizaron con valores dentro de los rangos esperados (Ambiente: 18-25 °C, Proceso: 60-80 °C, Critico: 90-110 °C). El gráfico de línea se dibujó correctamente y las estadísticas MIN, MAX y AVG se calcularon de forma precisa a lo largo del tiempo.
Figura 6: Aplicación con simulador activo mostrando los tres sensores y el gráfico en tiempo real.
Caso de uso 2 — Seleccionar un sensor: Al hacer clic en la tarjeta "Proceso", el sistema respondió correctamente: la tarjeta se resaltó con borde verde, el gráfico se restableció y comenzó a mostrar los valores del sensor Proceso en color verde. Las estadísticas se reiniciaron y comenzaron a acumularse con los nuevos datos. El mismo comportamiento se verificó con el sensor "Crítico" (borde rojo).
Figura 7: Sensor "Proceso" seleccionado, con gráfico verde y estadísticas actualizadas.
| # | Dificultad | Solución |
|---|---|---|
| 1 | Configuración de Qt6 en Windows con los módulos Charts y SerialPort. | Se utilizó el Maintenance Tool de Qt para agregar los módulos faltantes después de una instalación inicial incompleta. |
| 2 | El archivo .ui no se utilizó correctamente ya que la interfaz se construyó en código. | Se mantuvo el archivo .ui con la estructura básica de QMainWindow y se construyó el layout completamente en C++ para tener mayor control sobre el estilo visual (tema oscuro). |
| 3 | Indentación inconsistente en serialsource.h con espacios adicionales. | Se corrigió manualmente la indentación para mantener consistencia con el resto del proyecto. |
| 4 | Gestión de memoria en SensorCard y referencias a SensorChannel. | Se verificó que DataSimulator y SerialSource mantuvieran sus vectores de sensores estables y que los punteros a SensorCard se gestionaran correctamente en MainWindow. |
SensorChannel::update() utiliza rand() sin semilla, lo que puede producir secuencias predecibles en sucesivas ejecuciones.La presente fue descrita en texto plano, en HTML y CSS. La codificación del archivo es UTF-8.
La documentación del código fuente se generó utilizando Doxygen, empleando comentarios en formato /** @brief */ para describir clases y métodos públicos, y /// para atributos, slots y señales. Esta documentación se encuentra embebida directamente en los archivos de código fuente (.h y .cpp), y puede generarse ejecutando doxygen desde la raíz del proyecto si se desea obtener la documentación. Para generar la documentación de forma
independiente, instale Doxygen y ejecute "doxygen -g Doxyfile" desde la raíz
del proyecto para crear el archivo de configuración, luego edite el campo
INPUT para que apunte al directorio actual y ejecute "doxygen Doxyfile".
El proyecto fue desarrollado utilizando Qt 6.11.1 con el compilador MinGW 13.1.0 sobre el sistema operativo Windows. La herramienta de build utilizada fue CMake 3.19+ con el generador Ninja.
El archivo comprimido contiene los archivos fuente del proyecto (headers, fuentes C++, CMakeLists.txt y mainwindow.ui).