Implementar una red de Petri en Arduino Deja un comentario

En esta entrada vamos a ver cómo implementar una red de Petri en un procesador como Arduino. Las redes de Petri son un mecanismo potente en teoría de eventos y, especialmente, en sistemas con eventos concurrentes.

En una entrada anterior vimos las máquinas de estados finitas como una forma estructurada de plantear la programación de un autómata. En cierto modo, podemos considerar las redes de Petri como una evolución de las máquinas de estados.

En una máquina de estados un único puede estar activado en cada momento. Los cambios de estado se realizan a través de transiciones, que dependen únicamente del estado actual y de una condición asociada a la transición.

Por contra en una red de Petri en lugar de tener estados tenemos nodos. Cada nodo puede tener un número nulo o no nulo de marcas. En la representación gráfica de una red de Petri, las marcas suelen representarse como puntos en el interior de los nodos.

Aquí tenemos algunas representaciones gráficas de redes de Petri sencillas.

Otra diferencia respecto a una máquina de estados finitos es que las transiciones de una red de Petri pueden estar asociadas a más de un nodo de entrada y tener más de un nodo de salida.

Para que una transición pueda disparar debe, además de cumplir la condición asociada, tener al menos una marca en cada uno de sus nodos de entrada. En este caso decimos que la transición está sensibilizada, lo que significa que puede disparar si se cumple su condición asociada.

Al disparar una transición, esta retira una marca de cada uno de los nodos de entrada y añade una marca a cada nodo de salida.

Las redes de Petri permiten una representación más conveniente de ciertos autómatas, en especial de aquellos que requieren de eventos concurrentes o sincronización de ramas. Imaginemos, por ejemplo, una máquina industrial que para poder realizar una operación necesita que exista una pieza en dos estaciones de trabajo (ejemplo, no puedo montar una botella de zumo hasta que no tenga la botella, el tapón, y la etiqueta).

El ejemplo clásico para ilustrar el funcionamiento y utilidad de una red de Petri es el caso de dos carros sincronizados. Disponemos de dos railes, por los cuales circulan sendos carros. Al pulsar un botón, el carro del rail superior pasa al otro lado. Otro botón realiza lo mismo con el carro del carril inferior.

Sin embargo, para que ambos carros puedan moverse se requiere que estos estén sincronizados. Es decir, que ambos carros deben haber cambiado de lado antes de que uno de ellos pueda volver. No es posible, por ejemplo, que el carro superior cambia de estación varias veces mientras el inferior está quieto.

Este problema admite una representación sencilla en forma de red de Petri mientras que, en el caso de realizarlo como máquina de estados, la solución es comparativamente más compleja.

La ventaja de la red de Petri queda patente si, en lugar de dos carros, empleamos tres. La red de Petri sigue siendo sencilla, mientras que vemos que la complejidad necesaria para la máquina de estados equivalente se dispara.

Esto ilustra la conveniencia y utilidad de las redes de Petri respecto a las máquinas de estados finitas, especialmente en el manejo de autómatas con ramas paralelas y necesidad de sincronización de eventos.

Después de esta pequeña introducción a las redes de Petri, vamos a ver como no es tan difícil como parece realizar una implementación sencilla en Arduino.

Vamos a verlo con un ejemplo sencillo, una variación del ejemplo de los carros sincronizados que hemos dicho anteriormente. Aunque quizás resulta más sencillo de visualizar si imagináis, por ejemplo, que son piezas en una cadena de montaje.

En el ejemplo tenemos dos ramas, inferior y superior, y cuatro estaciones, por lo que tenemos un total de ocho estados. La rama superior avanza cuando recibe por puerto serie el carácter ‘A’ y el inferior cuando recibe ‘B’.

Para que sea más interesante, el paso de la primera estación y a la última estación admite tanto ‘A’ como ‘B’. Con cualquiera de estas entradas las marcas avanzarán simultáneamente en ambas ramas.

Y por meter un temporizador, vamos a suponer que en la estación inferior el carro vuelve del estado 6 al 5, si la pieza del carril superior no ha avanzado tras un plazo de 5 segundos.

Vamos a ver una implementación muy ligera y muy sencilla de esta red de Petri.

En esta implementación sencilla, por un lado, tenemos dos vectores que almacenan el estado de marcaje actual y anterior (luego veremos para que necesitamos el anterior).

La mayor parte del peso del programa está en las funciones Transicion0…5(), que contienen toda la lógica de las transiciones. Comprueban que la transición está sensibilizada, y que se cumple la condición de disparo. En caso de cumplirse la condición, y estar sensibilizada, realiza las acciones oportunas, y actualiza el marcado de la red de Petri.

En la función Setup simplemente iniciamos el estado de las marcas a la posición inicial. En el loop, recibimos un carácter por puerto serie, y actualizamos el estado de la red de Petri con la función Update. La función Update copia el estado de la máquina al vector de estado anterior, y lanza todas las transiciones. El motivo de copiar el estado de la máquina es evitar que durante los cambios de marcado de una iteración se sensibilicen transiciones que no estaban.

Por este mismo motivo, las marcas se añaden mediante la función AddMark y RemoveMark. Ambos cambios se realizan en nuevo marcado. Pero en el marcado anterior, que se emplea para determinar si una entrada está sensibilizada, únicamente las retiramos, no las añadimos.

Para la entrada temporizada, al disparar la transición 4 activamos un temporizador. La transición 6 tiene como condición de disparo el valor de la función timeExpired que, precisamente, devuelve verdadero cuando el timer ha expirado.

Finalmente, tenemos funciones adicionales para que el ejemplo funcione, como son readInput, que recibe la entrada por puerto serie, y printMarkup que muestra el estado de la red para que podemos comprobar el funcionamiento del ejemplo.

Esta implementación es muy sencilla, pero resulta adecuada para ilustrar que implementar una red de Petri no tiene por qué ser tan complejo como parece. Podríamos mejorarla mucho empleando vectores, estructuras, … pero no tiene mucho sentido porque es justo lo que vamos a ver en el siguiente apartado.

Resultados

Si ejecutamos el código anterior podemos ver por puesto serie el resultado. Podemos cambiar el estado pulsando las teclas ‘A’ y ‘B’.

Hemos visto una implementación muy sencilla de una Red de Petri en Arduino, de entre las muchas posibles de hacerlo. Pero debería haber quedado claro que tiene grandes partes repetitivas.

En este caso, el código nos está pidiendo a gritos emplear objetos, que en Arduino se traduce en hacer una librería.

Así que aquí tenéis la librería PetriNet, que implementa una red de Petri en Arduino de forma cómoda y sencilla, con optimizaciones adicionales. Estáis invitado a echarle un vistazo al código.

Con esta librería, el ejemplo anterior se resolvería con el siguiente código.

 

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Enviar Whatsapp
Hola 👋
¿En qué podemos ayudarte?