INTRODUCCIÓN
En Ubidots seguimos viendo un creciente cruce entre los mercados de creadores y los sistemas industriales convencionales. En este artículo, continuaremos con esta tendencia creciente y discutiremos las Máquinas de Estados Finitos (FSM) y cómo los ingenieros de hardware pueden incorporar operaciones lógicas industriales en construcciones convencionales. Luego desarrollaremos un ejemplo de script simple para demostrar la lógica de FSM para enviar datos a Ubidots a través de MQTT. A medida que la tendencia actual de Computadoras de Placa Única (SBCs) permea el piso de producción, esperamos que este artículo aclare tus proyectos locales y te permita complementar los elementos básicos de gateways industriales como Dell, Siemens, etc., con tus soluciones locales, resultando en ahorros de miles de dólares en costos de hardware y operativos.
Antes de comenzar, es importante entender el concepto de MQTT antes de saltar a este artículo sobre FSM. Si no estás familiarizado con MQTT, consulta la API de MQTT de Ubidots para entender mejor los conceptos básicos. A continuación, se presentan algunas definiciones estándar que vale la pena conocer al trabajar con IoT, MQTT, FSM y Ubidots.
Cliente: Un cliente es el dispositivo que envía datos a Ubidots para actualizar o crear variables. Los clientes también pueden recuperar datos.
Broker: Los brokers son los contrapartes de un cliente. Los brokers procesan todos los datos y tienen la responsabilidad principal de recibir mensajes, actualizar variables y notificar a los clientes sobre cambios en la plataforma y cualquier sesión en espera. (La URL del broker de Ubidots es industrial.api.ubidots.com con el puerto de comunicación 1883.)
Publicar: Publicar es similar a POST en HTTP. Cuando un cliente publica un mensaje, el broker actualizará o creará un nuevo dispositivo dependiendo del código de comando.
Suscribirse: Similar a la función GET en HTTP, suscribirse es el método para obtener valores con una gran diferencia en la solicitud GET. La diferencia es que no tienes que preguntar continuamente al servidor por cada valor en tu script personalizado. Por ejemplo, si el valor de una variable cambia, Ubidots te actualizará automáticamente (a ti, el usuario) sobre cualquier cambio. Si no hay cambios en las variables, no se enviarán datos hacia/desde Ubidots, ahorrando así solicitudes de datos y tiempo de procesamiento para tu dispositivo y la funcionalidad y gastos generales del proyecto.
Consejo Profesional: Te recomendamos encarecidamente usar nuestra biblioteca de MQTT y ejecutar sus ejemplos como se muestra en este artículo antes de continuar con este tutorial. Es mejor caminar con MQTT antes de intentar correr con MQTT. Pero una vez que conozcas los conceptos básicos, correr es lo que obtienes con la programación de MQTT.
MÁQUINA DE ESTADOS FINITOS
Las Máquinas de Estados Finitos son simplemente un cálculo matemático de una serie de causas y eventos. Para entender mejor qué es una Máquina de Estados Finitos (FSM), primero necesitamos definir el concepto de 'estado.' Un estado es una pieza única de información dentro de un programa computacional más grande. El cálculo de la FSM cambia o transita de un estado a otro en respuesta a entradas externas. Una FSM se define por una lista o un orden lógico de sus estados; su estado inicial y las condiciones para cada transición. Simplemente, la FSM es una operación en la que un estado es un conjunto de instrucciones que se ejecutan dentro de una rutina de firmware para completar la operación, es decir, el estado READ_SENSORS realizará la tarea de leer un número fijo de entradas analógicas mientras que SEND_DATA realizará un estado de salida para enviar los valores recién detectados a una ubicación final. Finalmente, la FSM completa es un nuevo estado único llamado READ_SEND que implementa ambas tareas en un solo comando.
Los estados son el ADN de la FSM que dictan el comportamiento interno o las interacciones con un entorno, como aceptar entradas o producir salidas que pueden o no causar que el sistema cambie o transite su estado. El estado se ejecutará específicamente dependiendo de las diferentes condiciones definidas en tu FSM. Este concepto es muy importante para los ingenieros de hardware y eléctricos, ya que muchos problemas prácticos como la programación de lavadoras (cuándo agregar agua o jabón, cuándo centrifugar o descansar) se resuelven fácilmente utilizando FSM en lugar de los paradigmas de programación secuencial clásicos; en otras palabras, una FSM es una solución más 'eléctrica y electrónica' para resolver un problema de hardware frente a la programación secuencial.
A continuación se presentan dos ejemplos de FSMs comúnmente encontrados en IoT:
Máquina de Mealy: En el cálculo de la máquina de Mealy, las salidas de cada estado dependen del estado actual y de los valores de entrada anteriores. Típicamente, con cada cálculo de Mealy, la entrada de un estado resulta en una única salida a una transición o a un estado de finalización.
Máquina de Moore: En la máquina de Moore, las salidas de cada estado dependen del estado actual y normalmente se basan en sistemas secuenciales sincronizados.
DIAGRAMA DE ESTADOS
Cualquier FSM debe ser descrita antes de ser codificada mediante un diagrama de estados. El ejemplo a continuación muestra el comportamiento de la FSM y sus transiciones que se dibujan (típicamente) utilizando burbujas para describir estados y flechas para transiciones. Además, una nota común al ejecutar una FSM correctamente es tener un estado presente único donde el siguiente (futuro) estado que se ejecutará puede ser fácilmente identificado.
En el diagrama anterior ilustramos un proceso completo de Máquina de Estados Finitos. Supongamos que la operación comienza en el Estado 1 y luego transita al Estado 2 una vez que se han cumplido las credenciales de programación. Después de transitar al Estado 2, la FSM calcula el estado actual hasta que se cumple un disparador para proceder al Estado 3 o al Estado 4. Ten en cuenta que en este diagrama, el Estado 3 y el Estado 4 son estados finales que resultan en datos computados para el resultado final de tu proyecto.
FSM DE UBIDOTS
Ahora, comencemos a codificar una FSM para enviar datos a Ubidots y darte una experiencia real trabajando con este método de programación. Para nuestra FSM, buscamos identificar y reaccionar a la necesidad inicial: Enviar datos del sensor desde nuestro microcontrolador (Espressif ESP8266) cada minuto a Ubidots.
Basado en esta necesidad inicial, hemos elegido implementar dos estados utilizando un modelo de cálculo de Máquina de Moore FSM:
ESPERAR: No hacer nada hasta que haya pasado un minuto (permanecer en estado inactivo durante ~59 segundos).
LEER_ENVIAR: Leer la entrada analógica del microcontrolador donde está conectado el sensor y enviar el resultado a Ubidots usando MQTT en el momento de los 60 segundos.
El diagrama de estados ilustra la lógica de programación de nuestra FSM:
Del diagrama anterior queda claro que la transición de ESPERAR a LEER_ENVIAR depende exclusivamente de si el valor de tiempo independiente es mayor o menor que 60 segundos. Comenzando en el siguiente estado de ESPERAR, el programa se ejecutará continuamente en ESPERAR hasta que el tiempo independiente alcance o supere los 60 segundos. Una vez que se alcanza la marca de 60 segundos, la FSM transitará de ESPERAR a LEER_ENVIAR. Después de que se envíe el valor, la FSM volverá a transitar a ESPERAR para un conteo adicional de ~59 segundos antes de calcular la transición nuevamente.
CODIFICACIÓN
Para hacer este ejemplo un poco más simple de entender, veamos un código de FSM práctico que ha sido dividido en cinco secciones separadas para detallar cada una de las programaciones de Estado y transición. El código completo se puede encontrar en su totalidad aquí.
Parte 1 - Definir Restricciones
/**************************************** *Incluir Bibliotecas ****************************************/#include "UbidotsESPMQTT.h"/**************************************** * Definir Constantes ****************************************/ #define TOKEN "...." // Tu TOKEN de Ubidots #define WIFINAME "...." //Tu SSID #define WIFIPASS "...." // Tu 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 ser leído Ubidots client(TOKEN);
Esta primera parte del código no es muy interesante ya que simplemente importamos la biblioteca de MQTT para enviar datos a Ubidots y completamos algunas definiciones requeridas. Es importante notar que aquí definimos los dos estados, ESPERAR y LEER_ENVIAR como constantes dentro de todo el código y definimos el estado presente usando la variable fsm_state.
La siguiente parte del código reserva espacio de memoria para el temporizador independiente y el valor a ser leído y el cliente MQTT a ser inicializado.
Es importante que no olvides establecer los valores adecuados para tu TOKEN y el nombre y contraseña de tu red WiFi. Si no sabes dónde encontrar tu token, consulta el Centro de Ayuda de Ubidots para más consejos y trucos.
Parte 2 - Callback
/**************************************** * Funciones Auxiliares ****************************************/ void callback(char* topic, byte* payload, unsigned int length) { Serial.print("Mensaje recibido ["); 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 provisionamos un callback que maneja datos del servidor cuando/se necesite. Para esta simple FSM, este paso es necesario para compilar correctamente tu código. Como se describe en nuestro artículo anterior publicado sobre MQTT, la función de callback maneja los cambios de tus variables en Ubidots y es necesario compilar el código y tenerlo definido.
Parte 3a - Funciones Principales - Setup()
/**************************************** * Funciones Principales ****************************************/ void setup() { // inicializar el pin digital LED_BUILTIN como salida. pinMode(A0, INPUT); client.wifiConnection(WIFINAME, WIFIPASS); client.begin(callback);}
Ahora comencemos con las funciones principales. En nuestro setup() estableceremos el pin analógico cero como entrada (debes editar el número del PIN dependiendo de la conexión física de tu sensor) para poder usar el ADC que permite al sensor leer los datos del entorno y representar un número flotante como un valor. Además, inicializamos el cliente WiFi y pasamos la función de callback basada en los parámetros de programación definidos anteriormente.
Parte 3b - 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); client.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 utilizando la estructura de control switch-case. Para nuestro ejemplo, los casos serán nuestros estados y los switches serán la variable fsm_state
. Veamos en más detalle cómo se diseña cada estado:
Una forma popular de implementar FSM en microcontroladores es utilizando la estructura de control switch-case. Para nuestro ejemplo, los switch-cases serán nuestros Estados y la programación que causa una transición representada por la variable fsm_state. Aquí determinaremos LEER_ENVIAR frente a ESPERAR donde los valores de 1 o 0 se enviarán respectivamente para identificar cada etapa de la FSM y transitar entre operaciones basadas en el temporizador independiente de 60 segundos.
Veamos en más detalle cómo se diseña cada estado:
ESPERAR: A partir del código de este estado, podemos inferir que no hará nada si el resultado del temporizador independiente almacenado en
msCount
es menor que 60000 milisegundos; una vez que se alcanza esta condición, el valor defsm_state
cambia y transitamos al siguiente estado, el estado LEER_ENVIAR.LEER_ENVIAR: Aquí leemos el valor de nuestro sensor y luego simplemente lo agregamos a una variable llamada "stuff" y publicamos sus datos a un dispositivo llamado "source1". Al ejecutar este programa, siempre transitaremos de vuelta al estado ESPERAR antes de emitir una segunda salida.
Finalmente, fuera de nuestra estructura switch-case incrementamos el valor de nuestro temporizador y tenemos un retraso muy pequeño de 1 milisegundo para hacer que el tiempo sea consistente con nuestro contador.
En este punto, te estarías preguntando por qué programar todo esto si podemos usar la programación secuencial habitual. Imagina que tienes tres tareas adicionales que realizar dentro de tu rutina:
Controlar un motor servo usando PWM
Mostrar valores en una pantalla LCD
Cerrar o abrir una puerta
Al operar múltiples tareas, la FSM permite un almacenamiento mínimo de datos en la memoria local de un dispositivo, además las funciones de estado pueden realizar tareas inmediatas basadas en los valores de entrada y no solo una única salida. Usando FSM puedes tomar decisiones lógicas más complejas con menos tiempo y energía necesarios para desplegar un programa lógico probado. La FSM es tu primer paso hacia la computación en el borde a nivel de dispositivo único.
Pruebas
Nuestros scripts funcionan como se esperaba, se crea un nuevo dispositivo llamado "source1" con una variable llamada "stuff" que recibe y guarda el valor del sensor cada 60 segundos.
Mejoras
Una FSM se puede implementar de muchas maneras, y a veces la instrucción switch-case puede ser tediosa de mantener si necesitas gestionar un número muy grande de estados. En la Parte 2 de este tutorial, te mostraremos cómo implementar un cálculo de FSM más eficiente utilizando el lenguaje C.
Una mejora adicional al código explicado aquí en la Parte 1 sería evitar la espera de 1 milisegundo entre cada caso analizado. Esto se puede realizar utilizando millis()
.
Resultados
Con este código de ejemplo completo, ¡nuestro script funciona genial! Ahora es tu turno de intentarlo y adoptar tu propia programación y estados de acuerdo a las necesidades específicas de tu aplicación. Una vez que hayas dominado el envío de datos hacia y desde Ubidots usando FSM, ¡puedes comenzar a desarrollar tus propias aplicaciones increíbles!