Ir al contenido principal

Eventos y acciones en contexto

La intención del artículo es promover y fundamentar el uso de las máquinas de estados en aquellos sistemas que reaccionan ante eventos, formalmente sistemas reactivos, típicos entre los embedded systems.

    Sistemas reactivos y la programación orientada a eventos

    Los sistemas reactivos son aquellos que en gran parte reaccionan contínuamente a estímulos externos e internos. Estas reacciones dependen de los eventos que recibe el sistema y de su contexto actual. Generalmente, se manifestan por medio de acciones. Inclusive, podrían cambiar el contexto del sistema. Por contexto entendemos una situación, modo o estado particular en el cual el sistema reside. Obviamente, puede que ciertos eventos no provoquen reacciones sobre el sistema, o bien que estas no generen acciones o cambios de contexto.

    Por otro lado, el conjunto de reacciones establecidas dependiente de los eventos recibidos para un contexto particular define el comportamiento dinámico de un sistema reactivo. 
    Así mismo, la descripción dinámica del sistema especifica los estados o modos en los cuales el sistema podría residir y las transiciones entre estos. También describe como reacciona el sistema ante los eventos.

    Vale la pena aclarar, que esta descripción, generalmente, no es simplemente una lista de acciones indexada por eventos, sino más bien el sistema realiza acciones en función de su contexto actual, entonces, podemos afirmar que cada estado o modo del sistema se compone por un conjunto definido de acciones, disparadas por un conjunto de eventos.

    Una técnica natural para describir la dinámica del sistema es usar máquinas de estados visualizadas de alguna manera por medio de su correspondiente diagrama de estados. El uso de esta técnica o concepto, llevado del diseño a la implementación, se denomina programación orientada a eventos.



    Spaghetti code

    Como dijimos en el párrafo anterior, la técnica natural para describir la dinámica del sistema es usar máquinas de estados si bien esto parece lógico y trivial, no lo es, ya que no es lo habitual en el mundo de los embedded systems.

    Por lo general, cuando pedimos la documentación de un sistema particular, referida a la descripción dinámica del mismo, recibimos una larga hoja, de formato similar a un viejo papiro, sobre el cual reside un diagrama de flujos lo suficientemente complejo como para que siempre se encuentre desactualizado y nos obligue a pensar si existe alguna otra manera de describir la dinámica del sistema. Por supuesto, desde este diagrama, no podemos distinguir cuales son los eventos externos ni mucho menos los internos, y es casi imposible determinar las acciones que se realizan en función del contexto del sistema, es más, ¿cuál es el contexto dinámico del sistema?. Dada nuestra cara de asombro y pánico, nos envían la "salvación": el único archivo fuente que implementa la dinámica completa del sistema, el cual no es más que un gran loop compuesto por cientos de sentencias if-else y switch-case anidadas, varias decenas de variables globales y flags que mantienen el contexto del sistema, utilizadas en forma casi indiscrimida esparcidas por todo el código. Seguramente, el sistema comenzó con unas pocas variables y algunos if-else pero con el correr del tiempo, las pruebas de campo y los nuevos requerimientos, provocaron que la estructura colapse, convirtiéndose en lo que denominan spaghetti code.

    Volviendo al archivo fuente, dentro del gran loop se determina la ocurrencia o no de los eventos, es decir, el código "busca" los eventos. Esto dificulta implementar un bloqueo del sistema ante la falta de eventos, ya sea para ejecutar otro proceso o bien disponer un power-down. Por otro lado, no es realmente necesario que el sistema sea complejo para que el gran loop pierda eventos, ya que estos se generan al ritmo del código y no al ritmo de la fuente que realmente los generan, es decir, si perdemos eventos inevitablemente diríamos -¡nos hace falta un procesador más poderoso!.

    A esta altura estamos en condiciones de preguntarnos, ¿existe alguna otra técnica que nos permita modelar la dinámica de manera natural, más clara y sencilla?, esta es sin duda la pregunta que obligadamente debemos hacernos. Una respuesta inmediata sería utilizar las máquinas de estados Mealy/Moore, para luego incorporar las extensiones que Statechart formula con la idea de mejorar aún más la representación visual del sistema.

    Es sumamente importante recordar que en ningún momento nombramos un lenguaje de programación ni plataforma ni sistema operativo, todos los términos y definiciones son conceptos y los mismos se adaptan a cualquier ambiente, lenguaje de programación, plataforma, procesador, sistema operativo, etc.

    Estructura if-else (diagrama de flujo) vs máquinas de estados (diagrama de estados)

    La siguiente tabla lista algunas de las más destacadas diferencias entre ambas técnicas.

    En sistemas complejos…
    If-else – switch-case
    Máquina de estados
    El modelo del comportamiento dinámico se representa mediante diagramas de flujos. Con el tiempo se vuelven ilegibles e inmanejables.
    El modelo del comportamiento dinámico se representa mediante diagramas de estados. Con el tiempo, si el diseño previo es robusto, es realmente sencillo introducir cambios.
    Generalmente implica un gran loop dentro del cual sentencias if-else explícitamente determinan la presencia de un evento, es decir, el código “busca” los eventos. Esto dificulta diseñar un sistema low-power eficiente.
    Posee un formalismo visual que estandariza los modelos.
    Debido a la observación anterior, no existe la posibilidad de generar un bloqueo del sistema, para continuar con otro proceso o bien disponer un power-down.
    La programación se concentra en los eventos que el sistema recibe.
    No es necesario que el sistema sea complejo para que el mismo pierda eventos dentro del gran loop, ya que los eventos se producen al ritmo del software no al ritmo de las fuentes que verdaderamente los generan.
    Permite la concurrencia entre objetos activos.
    Dado que su estructura es poco flexible, el código fuente y su diagrama de flujo son difíciles de mantener.
    Permite fácilmente diseñar un sistema low-power.
    No permite la concurrencia entre objetos activos.
    Ordena el comportamiento dinámico del sistema.
    Generalmente se repiten fragmentos de código.
    Obliga a diseñar antes de programar e implementar
    Es difícil de determinar el estado del sistema en cada momento y las acciones a desarrollarse ante la llegada de un evento.
    El diseño es fácil de lograr.
    Tienta a manejar el contexto (estado) por medio de una gran cantidad de variables globales y flags. Esto requiere especial cuidado.
    No implica ni el uso de sistema operativo ni ejecución en tiempo real.
    Genera código cifrado y un número desproporcionado de lógica condicional enredada. Spaghetti code.
    Tampoco implica utilizar una herramienta comercial o o compleja y gratuita, las máquinas de estados son sólo un concepto, una técnica.
    Generalmente, no permite desarrollar una verdadera abstracción por niveles funcionales o bien descomponer el comportamiento en niveles jerárquicos según funcionalidad.
    Reduce la enredada lógica condicional de aquellas líneas de código profundamente anidadas con sentencias if-else o switch-case.
    Independiente del lenguaje de programación, sea estructurado o no.
    Es capaz de conseguir una dramática reducción de los caminos y bifurcaciones del código.
    La prueba es compleja y requiere especial atención, ya que
    Fácil de probar.
    ...
    Una máquina de estados hace el manejo del evento explícitamente dependiente tanto de la naturaleza del mismo como del contexto (estado) del sistema.
    ...
    Independiente del lenguaje de programación, sea estructurado o no.

    Ejemplo

    La siguiente figura muestra un diagrama de estados jerárquico, el cual no tiene significado alguno, sólo es útil para mostrar los conceptos de Statecharts.


    Aquí parte de la implementación del diagrama anterior utilizando el framework RKH.

    /*
     * Defines HSM.
     */
    
    RKH_CREATE_HSM( my, 0, &S1, my_init, &mydata );
    
    /*
     * Defines states and transition tables.
     */
    RKH_CREATE_BASIC_STATE( S2, 0, NULL, NULL,  RKH_ROOT, NULL );
    RKH_CREATE_TRANS_TABLE( S2 ) =
    {
     RKH_TRREG( ONE, x_equal_1, dummy_act, &S1 ),
     RKH_TRREG( TWO, NULL, NULL, &S2 ),
     RKH_TRREG( THREE, NULL, NULL, &C2 ),
     RKH_TRINT( FOUR, NULL, dummy_act ),
     RKH_END_TRANS_TABLE
    };
    RKH_CREATE_COMP_STATE( S1, 0, set_y_0, dummy_exit,  RKH_ROOT, &S11, &DH );
    RKH_CREATE_TRANS_TABLE( S1 ) =
    {
     RKH_TRREG( THREE, NULL, NULL, &S3 ),
     RKH_TRREG( TWO, NULL, set_y_2, &S2 ),
     RKH_END_TRANS_TABLE
    };
     
    RKH_CREATE_BASIC_STATE( S12, 0, set_x_3, NULL, &S1, NULL );
    RKH_CREATE_TRANS_TABLE( S12 ) =
    {
     RKH_TRREG( ONE, NULL, NULL, &J ),
     RKH_TRREG( FOUR, NULL, set_y_1, &S2 ),
     RKH_END_TRANS_TABLE
    };
    RKH_CREATE_COMP_STATE( S11, 0, NULL, NULL, &S1, &S111, &H );
    RKH_CREATE_TRANS_TABLE( S11 ) =
    {
     RKH_TRREG( FOUR, NULL, NULL, &S12 ),
     RKH_END_TRANS_TABLE
    };
    RKH_CREATE_BASIC_STATE( S111, 0, set_x_1, NULL, &S11, NULL );
    RKH_CREATE_TRANS_TABLE( S111 ) =
    {
     RKH_TRREG( ONE, NULL, NULL, &S112 ),
     RKH_END_TRANS_TABLE
    };
    RKH_CREATE_BASIC_STATE( S112, 0, set_x_2, NULL, &S11, NULL );
    RKH_CREATE_TRANS_TABLE( S112 ) =
    {
     RKH_TRREG( ONE, NULL, NULL, &S111 ),
     RKH_TRREG( THREE, NULL, NULL, &J ),
     RKH_END_TRANS_TABLE
    };
    RKH_CREATE_COMP_STATE( S3, 0, NULL, NULL,  RKH_ROOT, &S31,  NULL );
    RKH_CREATE_TRANS_TABLE( S3 ) =
    {
     RKH_TRREG( TWO, NULL, NULL, &C1 ),
     RKH_TRREG( THREE, NULL, NULL, &S3 ),
     RKH_END_TRANS_TABLE
    };
    RKH_CREATE_BASIC_STATE( S31, 0, NULL, NULL, &S3, NULL );
    RKH_CREATE_TRANS_TABLE( S31 ) =
    {
     RKH_TRREG( ONE, NULL, NULL, &S32 ),
     RKH_END_TRANS_TABLE
    };
    RKH_CREATE_BASIC_STATE( S32, 0, NULL, NULL, &S3, NULL );
    RKH_CREATE_TRANS_TABLE( S32 ) =
    {
     RKH_TRREG( ONE, NULL, NULL, &S31 ),
     RKH_END_TRANS_TABLE
    };
    
    RKH_CREATE_COND_STATE( C1, 0, get_y );
    RKH_CREATE_BRANCH_TABLE( C1 ) =
    {
     RKH_BRANCH( Y1, NULL, &H ),
     RKH_BRANCH( Y2, dummy_act, &DH ),
     RKH_END_BRANCH_TABLE
    };
    
    RKH_CREATE_COND_STATE( C2, 0, get_x );
    RKH_CREATE_BRANCH_TABLE( C2 ) =
    {
     RKH_BRANCH( X1, dummy_act, &S3 ),
     RKH_BRANCH( X2_OR_X3, NULL, &S32 ),
     RKH_END_BRANCH_TABLE
    };
    
    RKH_CREATE_SHALLOW_HISTORY_STATE( H, 0, &S11 );
    RKH_CREATE_DEEP_HISTORY_STATE( DH, 0, &S1 );
    RKH_CREATE_JUNCTION_STATE( J, 0, NULL, &S3 );
    
    

    Aquí la explicación detallada sobre la implementación completa utilizando el framework RKH.

     

    Comentarios

    1. Esta muy bueno el articulo. Me gusto la frase "una larga hoja, de
      formato similar a un viejo papiro". A quien hace referencia ...

      Quedan claro las ventajas de las maquinas de estados para la
      representación de la dinámica de un sistema.
      Creo que el espaguetti code surge de programar sin contemplar ningún
      tipo de fase de diseño del sistema, al menos una seria. Entonces el
      diagrama de flujo sigue como consecuencia natural del intento tardío
      de organizar y/o documentar.
      En conclusión, no creo que sea una opción de diseño, sino la evidencia
      de la falta total de él. Con esto no quiero decir que el diagrama de
      flujo no sirva sino que esta mal usado.

      Creo que todos hemos pasamos por épocas de comer fideos. Pero no voy a
      justificar el genocidio. Nunca me dio buenos resultados (creo que a
      nadie). Lo malo es que aun hay quienes los justifican.

      ResponderEliminar
    2. Esta muy bueno el artículo, es muy comprensible.

      ResponderEliminar
    3. Esta muy bueno, es claro y ejemplificador.
      Mientras lo leia no pude evitar hacer un repaso por mi vida vinculada al desarrollo de embedded.
      Recuerdo que cuando en la secundaria me mostraron la programacion en Assembler me parecio algo extraordinario, en aquel momento todo era "diagramas de flujo", y que diagramas!!! ciertamente algo incomprensible que pasado unos meses sin mirarlos uno se preguntaba, y aca que quise hacer????
      Bueno mas adelante tuve la suerte de encontrarme trabajando junto a Leandro Francucci quien ya habia sido germinado por Eduardo Martinez en esto de las "maquinas de estado" o aveces llamados cariñosamente programacion con globitos!!!.
      No paso mucho tiempo hasta que Eduardo me planteo la resolucion de un problema tipico de Kernighan & Ritchie, sacar los comentarios de un archivo de codigo fuente de lenguaje C. Por esos dias Eduardo estaba capacitandome en la Programacion en lenguaje C, el objetivo era solo el diseño de la solucion del problema. Bueno recuerdo que estuve varios dias tratando de resolverlo hasta que llegue a un hermoso diagramita de flujo. Cuando me junte con Eduardo me pidio que lo implementara entonces comence ha trasladar el diagrama a codigo
      fuente. Como es de suponer cai en la trampa del Spaghetti Code, era un mega loop lleno de if else if if if else....
      En ese momento fue que Eduardo me planteo porque no solucionarlo utilizando maquinas de estado. Por supuesto que implico un cambio de mentalidad que no fue de la noche a la mañana pero a partir de ahi es que no concibo otra forma de modelar un sistema reactivo.
      Bueno, para finalizar quiero felicitar al autor del articulo, que con el correr de los años a ido recabando las experiencias en el uso
      de maquinas de estado y diseño a diseño fue perfeccionando los motores para ser cada vez mas eficientes, el framework RKH es la conjuncion de todas esas experiencias vividas.

      ResponderEliminar
    4. Muchas gracias por los comentarios!!.

      Ahora espero que utilicen el framework RKH o al menos los conceptos que intento transmitir.

      ResponderEliminar

    Publicar un comentario