Nuestro mundo conectado; explicando las máquinas de estados finitos

José García
· 8 minutos de lectura
Enviar por correo electrónico

Se dice que el hombre/la mujer fue creado a imagen de Dios. De manera similar, ahora creamos máquinas a nuestra imagen. Un ejemplo de esto es la programación (MFF). Ingenieros y desarrolladores ahora usan computadoras para realizar tareas que antes se realizaban manualmente. Por ejemplo, ¿tienes ropa sucia por ahí? Yo sí. Antes teníamos que enjuagar la ropa en una tina o fregadero, agregar jabón, restregar, enjuagar de nuevo, etc., para tener una camiseta limpia para ir a trabajar o salir por la noche. Ahora tenemos lavadoras que hacen este trabajo por nosotros. Llegamos a este punto con ingenieros que han diseñado miles de productos y dispositivos que ejecutan programas basados ​​en el pensamiento o la acción humana. Esto no es una excepción al aprendizaje automático actual ni a otras palabras de moda en IA. Se están desarrollando millones de dispositivos y aplicaciones para aumentar la eficiencia y la facilidad de los hombres y las mujeres, y muchos de estos procesos de ayuda existen gracias a las MFF.

Las Máquinas de Estados Finitos (MFF) son simplemente un cálculo matemático de una serie de causas y eventos. En relación con nuestro ejemplo de la lavadora, la MFF determina cuándo iniciar el ciclo de enjuague, cuándo centrifugar y cuándo detenerlo por completo. Para comprender mejor una Máquina de Estados Finitos (MFF), primero debemos definir el concepto de "estado". Un estado es una pieza única de información dentro de un programa computacional más amplio. El cálculo de la MFF cambia o pasa de un estado a otro en respuesta a entradas externas. Una MFF se define por una lista u orden lógico de sus estados: su estado inicial y las condiciones para cada transición, concluyendo con un estado final. La MFF es una serie de pensamientos programados por la computadora para ejecutar operaciones basadas en entradas; de la misma manera que el ser humano piensa y actúa, también lo hacen nuestras máquinas y las computadoras que las controlan.

Los estados son el ADN de la FSM, que dicta el comportamiento interno o las interacciones con el entorno, como la aceptación de entradas o la producción de salidas, lo que puede o no provocar que el sistema cambie o cambie de estado. El estado se ejecuta específicamente en función de las diferentes condiciones definidas en la FSM. Este concepto es fundamental para los ingenieros de hardware y eléctricos, ya que muchos problemas prácticos, como la programación de lavadoras (cuándo añadir agua o jabón, cuándo centrifugar o cuándo parar), se resuelven fácilmente con la FSM en lugar de los paradigmas clásicos de programación secuencial. En otras palabras, una FSM es una solución más "eléctrica y electrónica" para resolver un problema de hardware que la programación secuencial.

A continuación, se presentan dos ejemplos de FSM que permiten una toma de decisiones lógica con menos tiempo y energía para implementar un programa lógico probado. El FSM es el primer paso hacia la computación Edge a nivel de dispositivo único en aplicaciones industriales IoT .

Máquina Mealy: En el cálculo de la máquina Mealy, las salidas de cada estado dependen del estado real y de sus valores de entrada actuales. Normalmente, con cada cálculo Mealy, la entrada de un estado resulta en una única salida a una transición o a un estado final. La lavadora se está llenando de agua; cuando se alcanza el nivel X, se detiene el agua.

Máquina de Moore: En la máquina de Moore, las salidas de cada estado dependen del estado real y normalmente se basan en sistemas secuenciales sincronizados. La lavadora gira después de 4 minutos. Detenga la máquina.

DIAGRAMA DE ESTADOS

Cualquier FSM debe describirse antes de codificarse mediante un diagrama de estados, de la misma manera que diagramamos el pensamiento de la máquina. El siguiente ejemplo muestra el comportamiento de la FSM y sus transiciones, que se dibujan (normalmente) mediante burbujas para describir los estados y flechas para las transiciones. Además, una nota común al ejecutar una FSM correctamente es tener un estado presente único donde el siguiente estado (futuro) que se ejecutará pueda identificarse fácilmente mediante las credenciales de programación del estado.

En el diagrama anterior, ilustramos un proceso completo de la Máquina de Estados Finitos Mealy. Supongamos que la operación comienza en el Estado 1 y pasa al Estado 2 una vez que se cumplen los requisitos de programación. Tras la transición a la Etapa 2, la FSM calcula el estado actual hasta que se cumple un disparador para pasar al Estado 3 o al Estado 4. Tenga en cuenta que, en este diagrama, el Estado 3 y la Etapa 4 son estados finales que generan datos calculados para el resultado final de su proyecto.

FSM Ubidots

Ahora, comencemos a codificar una FSM para enviar datos a Ubidots y brindarles una experiencia práctica con este método de programación. Para nuestra FSM, buscamos identificar y responder al requisito inicial. Construiremos una Máquina Moore rápida: enviaremos datos del sensor desde nuestro microcontrolador (Espressif ESP8266) cada minuto a Ubidots

En base a este requerimiento inicial, hemos optado por implementar dos estados utilizando un modelo de cálculo FSM de Máquina de Moore:

  • ESPERAR: No hacer nada hasta que haya transcurrido un minuto (permanecer en estado inactivo durante ~59 segundos).
  • READ_SEND: Lee la entrada analógica del microcontrolador donde está conectado el sensor y envía el resultado a Ubidots usando MQTT en la marca de 60 segundos.

El diagrama de estados a continuación ilustra la lógica de programación de nuestro FSM:

Este diagrama muestra claramente que la transición de WAIT a READ_SEND depende exclusivamente de si el valor del tiempo independiente es mayor o menor que 60 segundos. A partir del siguiente estado de WAIT, el programa se ejecutará continuamente en WAIT hasta que el tiempo independiente alcance o supere los 60 segundos. Una vez alcanzados los 60 segundos, la FSM pasará de WAIT a READ_SEND. Tras enviar el valor, la FSM volverá a WAIT durante unos 59 segundos adicionales antes de volver a calcular la transición.

Codificación

Para simplificar la comprensión de este ejemplo, veamos un código FSM práctico, dividido en cuatro partes para detallar cada estado y las condiciones de transición. El código completo se puede encontrar aquí.

Parte 1 – Definir restricciones

// Incluir bibliotecas #include "UbidotsESPMQTT.h" // Definir constantes #define TOKEN "...." // Su TOKEN Ubidots #define WIFINAME "...." // Su SSID #define WIFIPASS "...." // Su contraseña Wifi #define WAIT 0 #define READ_SEND 1 uint8_t fsm_state = WAIT; // Estado inicial int msCount = 0; // contador de tiempo float value; // espacio de memoria para el valor a leer Ubidots client(TOKEN);

Esta primera parte del código no es muy interesante, ya que simplemente importamos la biblioteca MQTT para enviar datos a Ubidots y completamos algunas definiciones necesarias. Es importante destacar que aquí definimos los dos estados, WAIT y READ_SEND, como constantes dentro del código, y el estado actual se define mediante la variable fsm_state. La siguiente parte del código reserva espacio de memoria para el temporizador independiente, el valor a leer y la inicialización del cliente MQTT.

Es importante que no olvides configurar los valores correctos para tu token, así como para el nombre y la contraseña de tu red wifi. Si no sabes dónde encontrar tu token, consulta el Centro de Ayuda Ubidots para obtener más consejos y trucos.

Parte 2 – Devolución de llamada

// Funciones auxiliares void callback(char* topic, byte* payload, unsigned int length) { Serial.print("Mensaje llegado ["); Serial.print(topic); Serial.print("] "); for (int i=0;i < length;i++) { Serial.print((char)payload[i]); } Serial.println(); }

En esta parte del código, proporcionamos una función de devolución de llamada que gestiona los datos del servidor cuando es necesario. Para nuestra FSM, este paso es necesario para compilar correctamente el código. Como se describe en nuestro artículo sobre MQTT , la función de devolución de llamada gestiona los cambios de las variables en Ubidots y es necesaria para compilar el código y tenerlo definido.

Parte 3 – Funciones principales – Setup()

// Funciones principales void setup() { // inicializa el pin digital LED_BUILTIN como salida. pinMode(A0, INPUT); client.wifiConnection(WIFINAME, WIFIPASS); client.begin(callback); }

Comencemos con las funciones principales. En nuestra configuración (setup()), estableceremos el pin analógico cero como entrada (debe modificar el número del PIN según la conexión física de su sensor) para poder usar el ADC que permite al sensor leer los datos del entorno y representar un valor de coma flotante. Además, inicializamos el cliente WiFi y pasamos la función de devolución de llamada según los parámetros de programación definidos previamente.

Parte 4 – Funciones principales – Loop()

void loop() { switch(fsm_state) { case WAIT: if (msCount >= 60000){ msCount = 0; fsm_state = READ_SEND; } break; case READ_SEND: value = analogRead(A0); if(!client.connected()){ client.reconnect(); } /* Rutina para enviar datos */ client.add("stuff", value);ubidotsPublish("source1"); client.loop(); fsm_state = WAIT; break; default: break; } // Incrementa el contador msCount += 1; delay(1); }

Una forma popular de implementar FSM en microcontroladores es mediante la switch-case . En nuestro ejemplo, los casos serán nuestros estados y los interruptores serán la fsm_state . Veamos con más detalle cómo se diseña cada estado:

Una forma popular de implementar la FSM en microcontroladores es mediante la de casos de conmutación . En nuestro ejemplo, los casos de conmutación serán nuestros estados y la programación que provoca una transición, representada por la variable fsm_state. Aquí determinaremos READ_SEND y WAIT, donde se enviarán valores de 1 o 0 respectivamente para identificar cada etapa de la FSM y la transición entre operaciones según el temporizador independiente de 60 segundos.

Veamos con más detalle cómo está diseñado cada estado:

  • ESPERA: Del código de este estado, podemos inferir que no hará nada si el resultado del temporizador independiente almacenado en msCount es menor a 60000 milisegundos; una vez que se alcanza esta condición, el valor de fsm_state cambia y pasamos al siguiente estado, el estado READ_SEND.
  • READ_SEND: Aquí leemos el valor de nuestro sensor y lo añadimos a una variable llamada "stuff" y publicamos sus datos en un dispositivo llamado "source1". Al ejecutar este programa, siempre volvemos al estado WAIT antes de emitir una segunda salida.

Finalmente, a partir de nuestra estructura de caso de conmutación, incrementamos el valor de nuestro temporizador y tenemos un retraso muy pequeño de 1 milisegundo para que el tiempo sea consistente con nuestro contador.

Llegados a este punto, quizá te preguntes por qué programar todo esto si podemos usar la programación secuencial habitual. Imagina que tienes tres tareas adicionales que realizar en tu rutina:

  1. Controlar un servomotor mediante PWM
  2. Mostrar valores en una pantalla LCD
  3. Cerrar o abrir una puerta

Al ejecutar múltiples tareas, la FSM permite un almacenamiento mínimo de datos en la memoria local del dispositivo, además de que las funciones de estado pueden realizar tareas inmediatas basadas en los valores de entrada y no solo en una única salida. Con FSM, se puede tomar decisiones más lógicas con menos tiempo y energía para implementar un programa con una lógica probada. El valor de la FSM reside en su capacidad para computar procesos a nivel de dispositivo único: el primer paso en la computación Edge .

Pruebas

Nuestros scripts funcionan como se espera, se crea un nuevo dispositivo llamado “source1” con una variable llamada “stuff” que recibe y guarda el valor del sensor cada 60 segundos en Ubidots.

Mejoras e ideas

Una FSM se puede implementar de muchas maneras, y a veces la instrucción switch-case puede ser tediosa de mantener si se necesita gestionar una gran cantidad de estados. Una mejora adicional al código explicado en la Parte 1 sería evitar la espera de 1 milisegundo entre cada caso analizado. Esto se puede lograr mediante millis().

Conclusión

Los seres humanos hemos diseñado nuestras computadoras para que funcionen a nuestra imagen y semejanza, y las Máquinas de Estados Finitos (MFA) son la programación que permite a las máquinas realizar tareas y brindar a los humanos una valiosa asistencia y seguridad en nuestra vida diaria. A medida que la tecnología y el conocimiento edge implementar las MFA se vuelven cada vez más económicos y accesibles, seguiremos viendo cómo las MFA se expanden por las fábricas y talleres industriales. El control de procesos sencillos mediante la programación de MFA en MFA sigue impulsando soluciones conectadas que complementan gateway y los PLC básicos como Dell, Siemens, etc., a la vez que ofrecen soluciones locales que ahorran $ en costos operativos y de hardware.