1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|
Especificación | Modelado | Verificación | Implementación | Verificación | Análisis |
PARA QUÉ | QUÉ | CÓMO | |||
¿Cuál es el problema? | ¿Qué tiene que hacer el sistema? | ¿El modelo cumple la especificación? | ¿Cómo hace lo que tiene que hacer? (modelo refinado) | ¿La implementación cumple la especificación? | ¿Se cumplen los plazos en el caso peor? |
LTL, CTL | FSM / xFSM | nuXmv | fsm.h / fsm.c | nuXmv | Análisis del tiempo |
(model checking) | Ejecutivo cíclico / FreeRTOS / Reactor | (model checking) | de respuesta |
Para empezar
Lee los artículos de Douglas C. Schmidt sobre el patrón parte 1 parte2
Ejemplo de implementación
Descarga la implementación mínima de ejemplo con ESP32.
El patrón Reactor es una forma de implementar sistemas reactivos desacoplando las tareas. En principio el patrón no exige que la implementación utilice multitarea cooperativa, pero lo facilita enormemente. Nosotros lo utilizaremos como una forma de utilizar multitarea cooperativa sin renunciar a la multitarea con prioridades fijas y desalojo.
En esta asignatura utilizaremos una implementación mínima, donde solo se consideran eventos temporales. No es difícil extenderla para incluir otro tipo de eventos empleando notificaciones de FreeRTOS desde los propios manejadores de interrupción, pero no lo haremos para mantener la implementación lo más legible posible.
La interfaz corresponde fielmente a la idea general del patrón Reactor:
reactor.h1#ifndef REACTOR_H 2#define REACTOR_H 3 4#include <freertos/FreeRTOS.h> 5#include <freertos/task.h> 6 7struct event_handler_t; 8typedef void (*eh_func_t) (struct event_handler_t*); 9 10struct event_handler_t { 11 int prio; 12 TickType_t next_activation; 13 eh_func_t run; 14}; 15typedef struct event_handler_t EventHandler; 16 17void event_handler_init (EventHandler* eh, int prio, eh_func_t run); 18void event_handler_run (EventHandler* eh); 19 20void reactor_init (void); 21void reactor_add_handler (EventHandler* eh); 22void reactor_handle_events (void); 23 24#endif
Veamos un ejemplo muy sencillo. Definiremos una tarea periódica que en este caso simplemente cambia el estado de una salida digital, para provocar el parpadeo de una baliza. Esta tarea se incluirá en un Reactor. Es decir, se ejecutará de forma cooperativa. Las tareas en un Reactor se modelan como event handlers. Un event handler tiene una función para procesar los eventos de esa tarea y una prioridad. Por ejemplo, para añadir una tarea baliza
con prioridad 4 en el reactor utilizaremos:
1EventHandler eh1; 2event_handler_init (&eh1, 4, baliza);
La función baliza
podría implementarse como sigue:
1void baliza (EventHandler* self) 2{ 3 static TickType_t period = pdMS_TO_TICKS(500); 4 static int value = 0; 5 6 value = !value; 7 gpio_set_level(BALIZA, value); 8 if (value) printf (".\n"); 9 10 self->next_activation += period; 11}
Nótese que la función recibe un puntero a la estructura EventHandler
. Esto se puede usar para almacenar datos compartidos como hacemos en las máquinas de estados extendidas.
El Reactor puede quedarse en la tarea principal (app_main
) o puede crearse en otra tarea de FreeRTOS se crea mediante una llamada a xTaskCreate
pasando un puntero a una función como ésta:
1void reactor_run (void* ignore) 2{ 3 EventHandler eh1; 4 reactor_init (); 5 6 event_handler_init (&eh1, 1, baliza); 7 reactor_add_handler (&eh1); 8 9 for (;;) { 10 reactor_handle_events (); 11 } 12}
main.c1#include <driver/gpio.h> 2#include <stdio.h> 3#include "reactor.h" 4 5#define BALIZA 8 6 7static void 8baliza (struct event_handler_t* this) 9{ 10 static TickType_t period = pdMS_TO_TICKS(500); 11 static int value = 0; 12 13 value = !value; 14 gpio_set_level(BALIZA, value); 15 if (value) printf (".\n"); 16 17 this->next_activation += period; 18} 19 20static void 21reactor_run (void* ignore) 22{ 23 EventHandler eh1; 24 reactor_init (); 25 26 event_handler_init (&eh1, 1, baliza); 27 reactor_add_handler (&eh1); 28 29 for (;;) { 30 reactor_handle_events (); 31 } 32} 33 34void app_main() 35{ 36 gpio_set_direction(BALIZA, GPIO_MODE_OUTPUT); 37 38 TaskHandle_t task; 39 xTaskCreate (reactor_run, "reactor", 2048, NULL, 1, &task); 40}
La implementación es deliberadamente simple para eliminar la complejidad relativa al manejo de varias fuentes de eventos.
reactor.c1#include "reactor.h" 2 3typedef struct reactor_t { 4 EventHandler* ehs[10]; 5 int n_ehs; 6} Reactor; 7 8static Reactor r; 9 10void 11event_handler_init (EventHandler* eh, int prio, eh_func_t run) 12{ 13 eh->prio = prio; 14 eh->next_activation = xTaskGetTickCount(); 15 eh->run = run; 16} 17 18void 19event_handler_run (EventHandler* eh) 20{ 21 eh->run (eh); 22} 23 24void 25reactor_init (void) 26{ 27 r.n_ehs = 0; 28} 29 30int 31compare_prio (const void* a, const void* b) 32{ 33 EventHandler* eh1 = *(EventHandler**) a; 34 EventHandler* eh2 = *(EventHandler**) b; 35 if (eh1->prio > eh2->prio) 36 return -1; 37 if (eh1->prio < eh2->prio) 38 return 1; 39 return 0; 40} 41 42void 43reactor_add_handler (EventHandler* eh) 44{ 45 r.ehs[r.n_ehs++] = eh; 46 qsort (r.ehs, r.n_ehs, sizeof (EventHandler*), compare_prio); 47} 48 49static TickType_t 50reactor_next_timeout (void) 51{ 52 static TickType_t next; 53 TickType_t now = xTaskGetTickCount(); 54 int i; 55 56 next = now + pdMS_TO_TICKS(60*60*1000); 57 if (! r.n_ehs) return next; 58 59 for (i = 0; i < r.n_ehs; ++i) { 60 EventHandler* eh = r.ehs[i]; 61 if (eh->next_activation < next) 62 next = eh->next_activation; 63 } 64 if (next < now) 65 next = now; 66 return next; 67} 68 69void 70reactor_handle_events (void) 71{ 72 int i; 73 TickType_t timeout, now; 74 TickType_t next_activation = reactor_next_timeout(); 75 76 now = xTaskGetTickCount(); 77 timeout = next_activation - now; 78 if (timeout > 0) 79 vTaskDelay (timeout); 80 81 now = xTaskGetTickCount(); 82 for (i = 0; i < r.n_ehs; ++i) { 83 EventHandler* eh = r.ehs[i]; 84 if (eh->next_activation < now) { 85 event_handler_run (eh); 86 } 87 } 88}