Patrones de diseño de aplicaciones: Máquinas de estado

Información general

La máquina de estado es una de las arquitecturas fundamentales que los desarrolladores de LabVIEW utilizan con frecuencia para crear aplicaciones rápidamente. La arquitectura de máquina de estado se puede utilizar para implementar algoritmos complejos de toma de decisiones que son representados por diagramas de estado o diagramas de flujo. Una máquina de estado se puede implementar usando funciones innatas de LabVIEW; no se requieren toolkits o módulos adicionales para la arquitectura.

 

Este artículo explica qué es una máquina de estado, explica ejemplos de casos de uso, algunos ejemplos conceptuales de una máquina de estado y un ejemplo de código de una máquina de estado.

Contenido

¿Qué es una máquina de estado?

Una máquina de estado es una arquitectura de programación que permite el flujo dinámico a los estados según los valores de los estados anteriores o la información del usuario.

Esta arquitectura es ideal para aplicaciones que pueden describirse como una combinación de:

  • Estados
  • Lógica para toma de decisiones que determina cuándo pasar a un estado en particular


Un estado se puede definir como el estado dentro del programa mientras se realiza la tarea general del programa; ejemplos de estados pueden ser inicializar, esperar, ejecutar un cálculo, verificar el estado, etc.

La declaración lógica ayuda a determinar cuándo pasar a un nuevo estado y a qué estado pasar. Los eventos se pueden utilizar para activar el movimiento de un estado al siguiente; estos pueden ser eventos programáticos o definidos por el usuario, como presionar un botón.+

Cada estado en una máquina de estado hace algo único y llama a otros estados. La comunicación de estados depende de alguna condición o secuencia. Para traducir el diagrama de estado a una arquitectura de programación de LabVIEW, se necesita la siguiente infraestructura:

  1. Ciclo While – ejecuta continuamente los distintos estados
  2. Estructura de caso – cada caso contiene un código que se ejecutará para cada estado.
  3. Registro de desplazamiento – contiene información de transición de estado
  4. Código de transición – determina el siguiente estado en la secuencia (consulte la sección Ejemplos de código de transición a continuación)

¿Por qué utilizar una máquina de estado?

Las máquinas de estado se utilizan en aplicaciones donde existen estados identificables. Cada estado puede llevar a uno o varios estados y también puede finalizar el flujo del proceso. Una máquina de estado se basa en la información del usuario o en el cálculo en el estado para determinar qué estado pasar al siguiente. Muchas aplicaciones requieren un estado de "inicialización", seguido de un estado predeterminado en el que se pueden realizar acciones diferentes. Las acciones realizadas pueden depender de las entradas anteriores y actuales, así como de los estados. Después se puede utilizar un estado de "apagado" para realizar acciones de limpieza.

Además de su poderosa capacidad para implementar algoritmos de toma de decisiones, las máquinas de estado también son formas funcionales de planificar aplicaciones. A medida que crece la complejidad de las aplicaciones, también crece la necesidad de un diseño adecuado. Los diagramas de estado y los diagramas de flujo son útiles y algunas veces, esenciales para el proceso de diseño. Las máquinas de estado no solamente son ventajosas para planificar aplicaciones, sino que también son fáciles de crear.

Casos de uso

Por ejemplo, las siguientes aplicaciones pueden beneficiarse del patrón de máquina de estado:

  • Cuadros de diálogo de una sola página o con pestañas. Cada pestaña del cuadro de diálogo corresponde a un estado. Un usuario inicia las transiciones de estado haciendo clic en una pestaña en particular. Para cada pestaña, las acciones que el usuario puede realizar se incluyen en el estado.
  • Un cajero automático (ATM). Los estados en esta aplicación podrían ser esperar la información del usuario, verificar la cantidad solicitada con el saldo de la cuenta, entregar el dinero, imprimir el recibo, etc.
  • Una aplicación que realiza una medida, la registra en el disco y luego espera otra acción del usuario. Los estados en esta aplicación podrían ser esperar la información del usuario, realizar la medida, registrar los datos, mostrar los datos, etc.
  • Las máquinas de estado se utilizan más comúnmente al programar interfaces de usuario. Al crear una interfaz de usuario, las diferentes acciones de usuario envían a la interfaz de usuario a diferentes segmentos de procesamiento. Cada uno de estos segmentos actuará como estado en la máquina de estado. Estos segmentos pueden conducir a otro segmento para su procesamiento posterior o esperar a otro evento del usuario. En este ejemplo, la máquina de estado monitorea constantemente al usuario para la siguiente acción a realizar.
  • Las pruebas de procesos son otra aplicación común de las máquinas de estado. En este ejemplo, cada segmento del proceso está representado por un estado. Dependiendo del resultado de la prueba de estado, se puede llamar a un estado diferente. Esto puede suceder continuamente, realizando un análisis en profundidad del proceso que se está probando.


Existe otro patrón de diseño que se puede utilizar para implementar una interfaz de usuario, el Controlador de Mensajes en Cola. Un controlador de mensajes en cola es una versión más sofisticada de la máquina de estado y ofrece flexibilidad adicional, pero también agrega complejidad. 

Crear una máquina de estado

Crear una máquina de estado efectiva requiere que el diseñador (1) haga una lista de los posibles estados. Con esta lista, el diseñador puede (2) planificar cómo se relaciona cada estado con otro. Luego, el diagrama de estado puede (3) traducirse a la arquitectura de programación gráfica de LabVIEW.

Ejemplo: Disparar un cañón

En este ejemplo, queremos generar una aplicación que dispare un cañón continuamente sin permitir que se caliente de manera peligrosa.

(1)   Lista de posibles estados
Para comenzar, hacemos una lista de todos los estados posibles para nuestra tarea. Para lograr la tarea de disparar continuamente el cañón se necesita:

  • Inicializar el programa
  • Encender el chasis.
  • Dispara los cañones
  • Verificar la temperatura del dispositivo (estado)
  • Enfriar el dispositivo de forma pasiva (si la temperatura aún está dentro del rango pero está aumentando)
  • Enfriar el dispositivo de forma activa (si la temperatura está fuera del rango)
  • Apagar dispositivo
     

(2) Asignar las relaciones de los estados en un diagrama de estado
Después, consideraremos cómo se relacionan estos estados entre sí y crearemos un diagrama de estado. Al crear el diagrama de estado, considere qué provocaría que el programa se moviera de un estado al siguiente, ¿es una transición automática? ¿Existe un disparo externo por parte del usuario? ¿La transición se basa en el resultado de un cálculo?

Por ejemplo, consideremos las relaciones entre el estado de inicialización y otros estados.

  1. La inicialización es el primer paso del programa. No habrá entradas para este estado.
  2. Una vez inicializado, si no hay errores, pasaremos a encender el cañón.
  3. Si existen errores, apague el programa
  4. Si el usuario presiona un botón Stop durante la inicialización, queremos pasar al estado de apagado.

Note que al pensar en cómo los estados se relacionan entre sí, hemos comenzado a definir la lógica para moverse entre estos estados. La lógica de programación que usaremos en el código depende de (1) el número de posibles estados de transición y (2) el número de entradas que se consideran en la lógica. Las opciones para la transición de código se analizan a continuación en la sección Ejemplos de código de transición.

Continúe trabajando en cómo los estados se relacionan entre sí hasta que tenga un diagrama de estado completo. El diagrama de estado incluirá todos los estados y la relación entre ellos (Figura 1). Observe que en este diagrama, los estados (nodos ovalados) describen las acciones que se realizan cuando el proceso de control está en ese estado, mientras que las transiciones (flechas) simplemente describen cuándo y cómo el proceso puede pasar de un estado a otro.

Figura 1: Diagrama de estado para disparar un cañón

La relación entre cada estado le ayudará a programar la lógica necesaria para pasar de un estado al siguiente.

(3) Construir una máquina de estado en LabVIEW
Después de definir los estados y su relación en un diagrama de estado, puede traducir esto a la arquitectura de codificación en LabVIEW (Figura 2). El flujo entre los estados del diagrama de estado (Figura 1) es implementado por el ciclo. Los estados individuales son reemplazados por casos en la estructura de casos. Cada estado del diagrama anterior corresponde a un subdiagrama de la estructura de caso. Cada estado:

  1. Realiza alguna acción
  2. Le dice a la máquina de estado cuál es el siguiente estado pasando una instrucción a un registro de desplazamiento en el ciclo While (ej., código de transición)


Un registro de desplazamiento en el ciclo While realiza un seguimiento del estado actual, el cual se alimenta a la entrada de la estructura de caso.

Figura 2: Máquina de estado

Programa de ejemplo de máquina de estado

Consulte el proyecto de ejemplo Single Shot Measurement, disponible en la ventana de diálogo Create Project, para tener un ejemplo sobre adaptar esta plantilla a una aplicación de medidas.

  • Después de la inicialización, la máquina de estado pasa al estado Wait for Event. Este estado contiene una estructura de eventos que espera cambios en el panel frontal. Cuando un usuario hace clic en un botón, LabVIEW reconoce el evento y cambia al subdiagrama apropiado de la estructura del evento. Este subdiagrama inicia una transición al estado apropiado.
  • Cada estado tiene acceso a un clúster de datos. Los tipos de datos de este clúster se definen en Data.ctl.
  • Los estados válidos se enumeran en State.ctl, que es un typedef. Usar una typedef para las transiciones de estado restringe las transiciones que puede usar, lo que reduce las posibilidades de que la máquina de estado entre en un estado no reconocido.
  • Únicamente el estado Stop puede detener la aplicación. Este diseño evita desconexiones accidentales y parciales y garantiza que:
     

1.     El código de apagado se ejecuta únicamente cuando el usuario desea detener la aplicación.
2.     El código de apagado siempre se ejecuta hasta el finalizar.

  • Únicamente se ejecuta un estado a la vez, y el ciclo While significa que todas las tareas se ejecutan a una sola velocidad. Si necesita tareas de múltiples velocidades o en paralelo, considere las plantillas Controlador de Mensajes en Cola o Actor Framework, disponibles en el cuadro de diálogo Create Project.
  • El estado Wait for Event es el único que reconoce la información del usuario. La máquina de estado debe estar en este estado para que se acepte cualquier información del usuario.

Crear su propia aplicación de máquina de estado

Consulte el tutorial Modify the Simple State Machine LabVIEW Template para saber cómo comenzar.  

Determinar sus necesidades

Antes de personalizar la plantilla, hágase las siguientes preguntas:

  • ¿Qué estados componen la aplicación? La respuesta a esta pregunta determina los estados que usted agrega.
  • Para cada estado, ¿cuál debería ser el próximo estado? La respuesta a esta pregunta determina el valor de la enum Next State que cada estado envía al registro de desplazamiento en el ciclo While.

    Un solo estado puede transferirse condicionalmente a varios estados. Un ejemplo es el estado Wait for Event en la plantilla, que pasa a un estado basado en la información del usuario.
  • ¿A qué tipo de datos necesitará acceder cada estado? La respuesta a esta pregunta determina qué tipos de datos agrega a Data.ctl.
  • ¿Cuáles son algunos de los errores que podrían ocurrir y cómo debería responder la aplicación a estos errores? Las respuestas a estas preguntas determinan la cantidad de manejo de errores que necesita.
     

Ejemplos de código de transición

Existen diferentes métodos para determinar a qué estado hacer la transición, que se analizan a continuación.
Tenga en cuenta que, si bien las imágenes de ejemplo muestran el estado "Inicialización", estas posibilidades de transición podrían aplicarse a cualquier estado.

Uno a uno: Si siempre hace la transición del estado A al estado B, no es necesario programar ninguna lógica, simplemente envíe el nombre del siguiente caso (caso B) al registro de desplazamiento.

Figura 3a: Solamente un posible estado de transición

Uno a dos: Si puede hacer la transición del estado A al estado B o al estado C, puede usar una función Select para evaluar el estado de un indicador. Debe evaluar algo que determine a qué estado desea pasar. Por ejemplo, en la Figura 3b vemos que la información del usuario del botón Stop determina si pasamos del estado de encendido o procedemos al apagado.

Figura 3b: Solamente dos posibles estados de transición

Uno a varios usando arreglos: Si tiene varios estados a los que podría hacer la transición, podría usar un área booleana asociada con una constante enum para programar la transición. Por ejemplo, en la Figura 3c, se lleva a cabo algún código donde el resultado del código determina la transición, cuya salida es un arreglo de valores booleanos. El arreglo booleano se correlaciona con una constante Enum que contiene una lista de posibles estados a los que se podría hacer la transición. Usando la función Index Array, se genera el índice del primer booleano “Verdadero” en el arreglo booleano. Luego, usando la función Array Subset, puede extraer lo apropiado de la constante Enum con la que se correlaciona.

Figura 3c

Consejo: Recuerde que los arreglos tienen un índice 0 y las enums tienen un índice 1. Para correlacionar el arreglo booleano con la constante Enum, use la función Increment para corregir ese desplazamiento.

Uno a varios usando ciclo While: Otra opción, si tiene varios estados de transición posibles, es usar un ciclo While dentro de un caso. El código dentro del ciclo While continuará hasta que un estado booleano se establezca en verdadero, activando el botón Stop. Esto permitirá que el código se ejecute de manera efectiva hasta que se produzca un evento de disparo.

La figura 3d muestra un estado de "Inicialización" utilizando un ciclo interno y una estructura de caso para pasar al siguiente estado. La estructura de caso interna contiene un diagrama para cada transición que sale del estado actual. Cada uno de los casos en la estructura de caso interna tiene dos salidas: un valor booleano, que especifica si se debe hacer la transición o no, y una constante enumerada, que especifica el estado al que va la transición. Al usar el índice del ciclo como una entrada a la estructura de caso, este código se ejecuta efectivamente a través de cada caso de transición uno por uno, hasta que encuentra un diagrama con una salida booleana “Verdadera”. Una vez que se encuentra la salida booleana "Verdadera", el caso muestra el nuevo estado al que va la transición. Aunque este método puede parecer un poco más complicado que los métodos anteriores, ofrece la capacidad de agregar nombres a las transiciones al "convertir" la salida del índice de ciclo en un tipo enumerado. Este beneficio le permite agregar "documentación automática" a su código de transición.

Figura 3d

Otras consideraciones de programación

 

Redundancia de código
Problema: La parte más difícil de crear una máquina de estado es diferenciar entre los posibles estados en el diagrama de estado. Por ejemplo, en el diagrama de estado de la máquina de Coca Cola (Fig. 4), podríamos haber tenido estados de 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50 centavos en lugar de tener un estado "wait for response" que va de un estado a otro dependiendo del tipo de moneda que se inserte. Eso crearía 11 estados diferentes con exactamente el mismo diagrama de caso. El código redundante puede crear un gran problema en una aplicación más grande.

Solución
: Si diferentes estados tienen el mismo diagrama de caso, intente combinarlos en un solo estado. Por ejemplo, el estado "wait for response" se crea para evitar la redundancia del código.

Uso de enum
Problema: Las enums se utilizan ampliamente como selectores de casos en las máquinas de estado. Si el usuario intenta agregar o eliminar un estado de esta enum, se romperán los cables conectados restantes de las copias de esta enum . Este es uno de los obstáculos más comunes al implementar máquinas de estado con enums.

Solución: Dos posibles soluciones a este problema son:
1. Si todas las enums se copian de la enum modificada, las interrupciones desaparecerán.
2. Crear un nuevo control con la enum y seleccionar "typedef" en el submenú. Al seleccionar typedef, todas las copias de enum se actualizarán automáticamente si el usuario agrega o elimina un estado.

Pasos siguientes

Si está interesado en aprender más sobre máquinas de estado y otras arquitecturas avanzadas en LabVIEW, considere tomar el curso LabVIEW Core 2.

Was this information helpful?

Yes

No