Práctica 3 | P2 GIR Saltearse al contenido

Práctica 3

A continuación se encuentra el enunciado de la práctica 3. Lee cuidadosamente el enunciado y sigue las instrucciones. Se recomienda consultar el apartado de Teoría para tratar de resolver dudas de concepto.

1 Cambios

1.1 Versión 1.0.0

  • Enunciado original.

1.2 Versión 1.0.1

  • Se ha añadido una descripción de dos funciones que faltaban: freeGrid y swapSensors.

1.2 Versión 1.0.2

  • Se han añadido algunas aclaraciones y se ha corregido un error en el ejemplo de salida de mainp3.cc. Faltaban los mensajes de los sensores inactivos tras invocar a “triggerAlerts`.
    • Se ha añadido una aclaración sobre max_idle en createGrid.
    • Se ha añadido una aclaración sobre row y col en updateTemperature.

2 Archivos de partida.

  • Aquí se encuentran los archivos de partida. Descárgalos y utilizalos como base para resolver la práctica.

3 Objetivos.

  • Seguir adquiriendo práctica y destreza con las características de C++ que lo hacen un mejor C (anotación del código, límites numéricos, deducción automática de tipo, size_t).
  • Trabajar con punteros y memoria dinámica.
  • Aplicar técnicas de programación dirigida por eventos con la biblioteca boost::signals2.
  • Practicar el uso de estructuras, funciones y control de flujo en C++.

4 Algunas aclaraciones.

  • En esta práctica trabajaremos con programación dirigida por eventos mediante el proyecto boost. Necesitamos tener instalada la biblioteca boost::signals2.
  • Esta biblioteca se encuentra ya instalada en el SO de la EPS. Si usas un SO diferente pero basado en Ubuntu, prueba lo que se indica en este enlace (sección Install Howto) para instalarla. No obstante la recomendacion desde la asignatura es que uses la versión del SO de la EPS para evitar problemas con la instalacion de esta biblioteca. También puedes probar a instalar la librería completa en el terminal con las siguientes instrucciones:
sudo apt-get update
sudo apt install libboost-all-dev
  • En el código de partida entregado junto a este enunciado se incluye un archivo mainp3.cc en el que verás una serie de funciones definidas y un ejemplo de uso. En la función main se conectan dichas funciones con las señales que se tendrán que implementar en el archivo p3.cc.

  • Ten en cuenta que las funciones conectadas a las señales pueden ser diferentes de las que se utilizarán para evaluar la práctica, pero el prototipo de la función será el mismo (tipo de dato que devuelve y parámetros de entrada).

  • En esta práctica no debes mostrar ningún mensaje por pantalla en tu implementación de p3.cc. Cualquier mensaje que se muestre en la consola será responsabilidad de la función main o cualquiera de las funciones conectadas a las señales.

  • Recuerda que seguimos empleando C++ como un mejor C, es decir, podremos emplear:

    • referencias (&)
    • tipo cadena (std::string)
    • no necesitaremos hacer typedef de structs.
    • std::iostream para entrada/salida.
    • Memoria dinámica con: new, new[], delete, delete[] en lugar de malloc y free.
    • Inferencia automática del tipo de variables : auto.
    • Tipos enumerados fuertemente tipados : enum class.
    • etc…

Recomendación: Puedes comprobar fácilmente el contenido de la red de sensores en cualquier momento utilizando la función displayGrid que tienes implementada en p3.h.

5 Enunciado.

En esta práctica implementaremos un simulador de una red de sensores inteligentes. Los sensores están organizados en una matriz dinámica de tamaño fijo que se reserva en tiempo de ejecución.

Cada sensor mide temperatura y puede generar eventos (señales) si ocurre alguna de las siguientes condiciones:

  • Temperatura alta: supera un umbral configurado (señal high_temp_sig_t).
  • Temperatura baja: cae por debajo de un umbral mínimo (señal low_temp_sig_t).
  • Sensor inactivo: ha estado varios ciclos sin cambios (señal inactive_sensor_sig_t).

5.1 Estructuras de datos

Antes de comenzar con las funciones, es importante conocer las estructuras (struct) que se utilizan en esta práctica. Estas estructuras están definidas en el archivo p3.h.


Position

struct Position {
uint32_t row;
uint32_t col;
};
  • Representa una coordenada en la red de sensores. Contiene dos campos:
    • row: número de fila (empezando desde 0).
    • col: número de columna (empezando desde 0). Se utiliza para identificar la ubicación de cada sensor dentro del grid.

Sensor

struct Sensor {
Position pos;
float temperature;
bool active;
uint32_t idle_cycles;
};
  • Cada sensor tiene:
    • pos: su posición actual en la red.
    • temperature: la última temperatura registrada por el sensor.
    • active: un booleano que indica si el sensor está activo (true) o inactivo (false).
    • idle_cycles: número de ciclos consecutivos en los que la temperatura no ha cambiado. Cuando un sensor acumula demasiados ciclos sin cambio, se considera inactivo.

Grid

struct Grid {
uint32_t rows;
uint32_t cols;
float high_threshold;
float low_threshold;
uint32_t max_idle;
Sensor** sensors;
};
  • Representa la red completa de sensores. Contiene:
    • rows, cols: número de filas y columnas del grid.
    • high_threshold: umbral de temperatura alta (por encima del cual se lanza una alerta).
    • low_threshold: umbral de temperatura baja (por debajo del cual se lanza una alerta).
    • max_idle: número máximo de ciclos sin cambio de temperatura antes de marcar un sensor como inactivo.
    • sensors: matriz dinámica de sensores (Sensor**), que se reserva en createGrid() y se libera en freeGrid().

5.2 Señales (boost::signals2)

En esta práctica se utiliza la biblioteca boost::signals2 para trabajar con programación dirigida por eventos.

Una señal es una forma de notificar a otras partes del programa que ha ocurrido un evento específico. En este caso, las señales se activan cuando un sensor entra en una situación especial: temperatura fuera de rango o inactividad prolongada.

Las señales se definen en p3.h como tipos personalizados mediante boost::signals2::signal. Cada tipo de señal recibe un tipo de parámetro distinto, según el evento que representa.


high_temp_sig_t

using high_temp_sig_t = boost::signals2::signal<void(const Sensor&)>;
  • Se activa cuando un sensor tiene una temperatura superior a grid.high_threshold.
  • Al activarse, pasa una referencia constante al Sensor afectado.
  • Se lanza desde updateTemperature() o triggerAlerts().

low_temp_sig_t

using low_temp_sig_t = boost::signals2::signal<void(const Sensor&)>;
  • Se activa cuando un sensor tiene una temperatura inferior a grid.low_threshold.
  • También recibe una referencia constante al Sensor afectado.
  • Se lanza desde updateTemperature() o triggerAlerts().

inactive_sensor_sig_t

using inactive_sensor_sig_t = boost::signals2::signal<void(const Sensor&)>;
  • Se activa cuando un sensor ha estado inactivo durante al menos grid.max_idle ciclos.
  • Se lanza desde simulateStep() o triggerAlerts() si el contador de inactividad (idle_cycles) supera el umbral (mayor estricto).

¿Cómo se conectan las señales?

En el archivo mainp3.cc, se proporcionan funciones para manejar estos eventos. Puedes conectar estas funciones a las señales usando .connect():

get_high_temp_signal().connect(alert_high_temp);
get_low_temp_signal().connect(alert_low_temp);
get_inactive_sensor_signal().connect(alert_inactive);

Así, cuando una señal se dispare desde p3.cc, se ejecutará la función correspondiente, mostrando un mensaje por pantalla o realizando otra acción.

Nota: Las señales no deben ser modificadas. Solo se deben usar para emitir eventos desde dentro de las funciones que debes implementar. No debe haber mensajes en p3.cc: la salida por consola se hace desde mainp3.cc, mediante estas funciones conectadas a las señales.

5.3 Funciones

A continuación se describen en detalle las funciones que debes implementar en p3.cc. Todas están declaradas en p3.h. No debes modificar la cabecera.


Grid createGrid(uint32_t rows, uint32_t cols, float low_threshold, float high_threshold, uint32_t max_idle, float default_temp = 20.0f)

Descripción:
Crea y devuelve una estructura Grid con una matriz dinámica de sensores (Sensor**) de tamaño rows × cols. Inicializa el resto de atributos de Grid con los parámetros recibidos.

Comportamiento esperado:

  • Cada sensor debe inicializarse con:
    • Su posición correspondiente (row, col).
    • Temperatura temp,
    • Estado activo (true),
    • Contador de ciclos de inactividad a 0. Este contador contará cuántos ciclos se ha encontrado un sensor sin cambios de temperatura.
  • Debe reservar memoria dinámicamente usando new[].
  • Si high_threshold es menor o igual a low_threshold, no podrá crearse la red, por lo que se devolverá empty_grid.
  • Los parámetros high_threshold, low_threshold, default_temp deberán tener valores entre -100 y 100, ambos incluidos. En caso de que alguno de ellos no cumpla con esta condición, se devolverá empty_grid.
  • Si rows o cols son 0, se devolverá empty_grid.
  • Si max_idle es mayor que 50, se devolverá empty_grid. v1.0.2: Si tiene el valor 0, se creará el grid normalmente, con los sensores tal y como se describe anteriormente.

Nota: empty_grid representa una red de sensores vacía. No tendrá memoria dinámica creada y sus atributos estarán inicializados a 0. La puedes encontrar en p3.h.


void freeGrid(Grid& grid)

Descripción:
Esta función se encargará de liberar la memoria dinámica creada en el registro grid y de inicializar sus atributos con los mismos valores utilizados en empty_grid (definido en p3.h).


void updateTemperature(Grid& grid, uint32_t row, uint32_t col, float new_temp)

Descripción:
Actualiza la temperatura del sensor ubicado en la posición (row, col).

Comportamiento esperado:

  • Si el sensor está activo:
    • Se actualiza su temperatura.
    • Se reinicia su contador de inactividad (idle_cycles = 0).
    • Si la nueva temperatura supera (mayor estricto) el umbral high_threshold, se lanza la señal de temperatura alta.
    • Si está por debajo de low_threshold, se lanza la señal de temperatura baja.
  • Si el sensor está inactivo o no existe, no se hace nada.
  • v1.0.2: Los parámetros row y col representan las coordenadas en la matriz. La primera coordenada de la matriz será la posición (0,0).

void simulateStep(Grid& grid)

Descripción:
Simula un paso de tiempo en la red de sensores.

Comportamiento esperado:

  • Para cada sensor activo del Grid:
    • Incrementa su contador idle_cycles.
    • Si el contador es mayor estricto que grid.max_idle, el sensor se desactiva (active = false) y se lanza la señal de sensor inactivo.
  • Los sensores inactivos no harán nada.
  • v1.0.2: Fíjate en el orden en el que se muestran los mensajes en el ejemplo de salida de mainp3.cc para saber en qué orden debes comprobar el estado de las señales.

void triggerAlerts(Grid& grid)

Descripción:
Fuerza la evaluación del estado de todos los sensores del grid y lanza las señales correspondientes según sus valores actuales, sin modificar ningún estado.

Comportamiento esperado:

  • Para cada sensor activo:
    • Si su temperatura supera el umbral, lanza high_temp_signal.
    • Si su temperatura está por debajo del umbral, lanza low_temp_signal.
  • Para los sensores inactivos, lanza inactive_signal.

Importante:
Esta función no cambia el estado de ningún sensor. Solo lanza señales si corresponde.


bool swapSensors(Grid& grid, const Position& a, const Position& b)

Descripción:
En esta función se intercambiarán los dos sensores del grid que se encuentran en las posiciones a y b.

Comportamiento esperado:

  • Intercambia dos sensores de posición. El sensor en la posición a pasará a estar en la posición b, mientras que el que estaba en la posición b ahora estará en la posición a.
  • Devolverá true cuando se haya podido hacer el intercambio de los sensores y false cuando no se haya podido.
  • No se podrá realizar el intercambio si las posiciones recibidas no representan una posición válida dentro de grid.
  • Ten en cuenta que no solo debes modificar la posición en la matriz, también deberás cambiar la posición pos que contienen los sensores involucrados.

void displayGrid(const Grid& grid)

Descripción:
Muestra el contenido de la red de sensores en pantalla de forma legible. Esta función ya está implementada en p3.h. Puedes usarla para verificar visualmente el estado de la red durante tus pruebas.

Comportamiento esperado:

  • Muestra una matriz con:
    • [ X ] si el sensor está inactivo.
    • [ T ] con la temperatura si está activo, siendo T el valor de la temperatura que ha detectado el sensor.

Ejemplo de salida. En el siguiente ejemplo, tenemos un Grid de 2 filas rows y 4 columnas cols. En este ejemplo, los sensores situados en las coordenadas (0,2) y (1,0) se encuentran inactivos.

[ 22.2 ] [ 35.0 ] [ X ] [ 20.8 ]
[ X ] [ 19.1 ] [ 21.7 ] [ 22.3 ]

5.4 Salida esperada con el mainp3.cc proporcionado

Al compilar y ejecutar el archivo mainp3.cc junto con tu implementación (p3.cc), deberías obtener una salida similar a la siguiente.

Ejemplo de salida esperada:

[LOW TEMP] Sensor at (1,0) reads 5.5°C
[HIGH TEMP] Sensor at (1,1) reads 35.4°C
--- Initial Grid State ---
[ 25.9 ] [ 23.0 ] [ 21.1 ]
[ 5.5 ] [ 35.4 ] [ 22.0 ]
[ 22.0 ] [ 22.0 ] [ 22.0 ]
[INACTIVE] Sensor at (0,0) is inactive after 3 cycles
[INACTIVE] Sensor at (0,1) is inactive after 3 cycles
[INACTIVE] Sensor at (1,0) is inactive after 3 cycles
[INACTIVE] Sensor at (1,1) is inactive after 3 cycles
[INACTIVE] Sensor at (1,2) is inactive after 3 cycles
[INACTIVE] Sensor at (2,0) is inactive after 3 cycles
[INACTIVE] Sensor at (2,1) is inactive after 3 cycles
[INACTIVE] Sensor at (2,2) is inactive after 3 cycles
--- Triggering alerts manually ---
[INACTIVE] Sensor at (0,0) is inactive after 3 cycles
[INACTIVE] Sensor at (0,1) is inactive after 3 cycles
[INACTIVE] Sensor at (1,0) is inactive after 3 cycles
[INACTIVE] Sensor at (1,1) is inactive after 3 cycles
[INACTIVE] Sensor at (1,2) is inactive after 3 cycles
[INACTIVE] Sensor at (2,0) is inactive after 3 cycles
[INACTIVE] Sensor at (2,1) is inactive after 3 cycles
[INACTIVE] Sensor at (2,2) is inactive after 3 cycles
--- Swapping two sensors ---
Sensors swapped successfully.
--- Final Grid State ---
[ 23.1 ] [ X ] [ X ]
[ X ] [ X ] [ X ]
[ X ] [ X ] [ X ]

6 Archivos.

  • p3.h: cabecera con los tipos de datos y prototipos de las funciones que tienes que implementar.

  • p3.cc: implementación de las funciones. Este es el único archivo obligatorio que tienes que entregar. El resto, si los entregas, no pasa nada. p3.cc no debe contener una función main.

  • mainp3.cc: contiene algunas pruebas básicas del programa y la conexión de las señales. Puedes modificarlo.

  • Makefile: se recomienda para compilar de forma sencilla. Recuerda la forma de utilizar la herramienta make en la terminal:

    • make para compilar y enlazar, creando los ejecutables para todos los TADs implementados.
    • make tgz para generar el archivo comprimido que deberás entregar.
    • make clean para borrar los archivos compilados.
  • Todos los archivos se encontrarán dentro de una carpeta llamada irp2-p3.

7 Entrega. Requisitos técnicos.

  • Requisitos que tiene que cumplir este trabajo práctico para considerarse válido y ser evaluado (si no se cumple alguno de los requisitos la calificación será cero):

  • El archivo entregado se llama irp2-p3.tgz (todo en minúsculas). Dicho archivo debe contener una carpeta llamada irp2-p3 que contendrá todos los archivos a entregar. En este caso, el único archivo obligatorio es `p3.cc'. Si entregas los demás archivos, no pasa nada. El corrector solo extraerá el archivo `p3.cc'.

    El archivo comprimido lo puedes crear así en la terminal:

    tar cfz irp2-p3.tgz irp2-p3 Para esto, debes situarte en la terminal en la carpeta padre a la de tu carpeta irp2-p3.

  • Una vez comprimido, ábrelo y asegúrate de que tiene la estructura que se pide. Si el servidor de entrega no detecta el archivo, es porque no estás entregando el archivo comprimido con la estructura que se pide. En este caso, revisa el nombre de la carpeta que contiene y el nombre de los ficheros que entregas.

  • También puedes emplear el archivo Makefile que tienes en la carpeta de la práctica para generar el fichero de la entrega: make tgz.

  • Dentro del directorio irp2-p3 estará al menos el archivo p3.cc (en minúsculas). Asegúrate de que tu archivo comprimido contiene dicha carpeta, y que a su vez dentro se encuentra al menos el archivo `p3.cc'.

  • Recuerda que los ficheros ‘.hno se deben modificar, de lo contrario tus ficheros ‘.cc’ podrían no compilar en el corrector. No es necesario entregar los ficheros ‘.h’ ni el ‘mainp3.cc’. Si se hace no pasa nada.

  • Las clases, métodos y funciones implementados se llaman como se indica en el enunciado (respetando en todo caso el uso de mayúsculas y minúsculas). También es imprescindible respetar estrictamente los textos y los formatos de salida que se indican en este enunciado.

  • Al principio de todos los ficheros fuente (`.h’ y `.cc’) entregados y escritos por ti se debe incluir un comentario con el nombre y el NIF (o equivalente) de la persona que entrega la práctica, como en el siguiente ejemplo:

    // NIF: 12345678-Z
    // NOMBRE: GARCIA PEREZ, LAURA
  • Un error de compilación/enlace implicará un cero en esa parte de la práctica.

  • Se utilizará valgrind para comprobar que no haya fugas de memoria en tu programa. Puedes instalarlo y probarlo antes de realizar la entrega para asegurarte de corregir posibles errores relacionados con la gestión de la memoria.

  • Lugar y fecha de entrega : Recuerda que las entregas se realizan siempre en (y sólo en) https://pracdlsi.dlsi.ua.es en las fechas y condiciones allí publicadas. Puedes entregar la práctica tantas veces como quieras, sólo se corregirá la última entrega (las anteriores no se borran). El usuario y contraseña para entregar prácticas es el mismo que se utiliza en UACloud.

8 Detección de plagios/copias.

  • En el Grado en Ingeniería Robótica, la Programación es una materia muy importante. La manera más efectiva de aprender a programar es programando y, por tanto, haciendo las prácticas correspondientes además de otros programas (más sencillos o más complicados, eso da igual).

  • Tratar de aprobar las asignaturas de programación sin programar (copiando) es complicado y obviamente, te planteará problemas para seguir otras asignaturas así como en tu futura vida laboral.

  • En una asignatura como Programación-II es difícil que alguien que no ha hecho las prácticas supere los exámenes de teoría o el de recuperación de prácticas.

  • IMPORTANTE:

    • Cada práctica debe ser un trabajo original de la persona que la entrega.

    • En caso de detectarse indicios de copia de una o más prácticas se tomarán las medidas disciplinarias correspondientes, informando a las direcciones de Departamento y de la EPS por si hubiera lugar a otras medidas disciplinarias adicionales.