RASPBERRY: Máquina de estados (I)
AUTOMÁTAS
DE ESTADOS FINITOS, FSM (Finite State Machines) (I)
1.
Introducción
Los
autómatas
de estados finitos1
FSM
son
una técnica matemática que permite representar el funcionamiento de
numerosos dispositivos de forma simple y rigurosa. Si a ello unimos
la posibilidad de disposer de una plataforma hardware potente y
barata como la Raspberry y disponemos del software adecuado, nos
puede facilitar la realización de numerosos proyectos de elevada
complejidad con gran utilidad en el campode la automática y la
robótica.
El
objetivo de este trabajo es implementar dicha teoría y establecer un
método repetitivo y fácil de manejar sobre una plataforma de bajo
coste y presentar algunos ejemplos de su aplicación.
Se
ha elegido la plataforma hardware Raspberry porque
- Dispone de numerosas entradas y salidas que permiten uctilizar sensores y actuadores
- Es de bajo coste
- Dispone de sistema operativo Linux (Raspbian) y de lenguaje Python3, con elevadas prestaciones
La
presentación aquí efectuada es pyues sólo una parte de lo que
permite la teoría de FSM y pretende servir de ejemplo y primer
contacto con esta potente herramienta.
2.
Principios de funcionamiento del autómata:
El
autómata dispone de un conjunto de estados en los que se puede
encontrar. Es decir, en un momento dado, el «autómata» está en
uno de esos estados, que llamaremos «estado».
El
sistema también posee unas entradas que lee y almacena en una
variable que llamaremos «contexto». Asimismo posee una serie de
salidas consistentes en realizar algunas ciertas acciones (incluida
la acción «no hacer nada») que sólo se ejecutan cuando el sistema
cambia de estado. Es decir, el sistema, mientras está en un cierto
estado, no hace nada; sólo hace algo cuando cambia de estado, cuando
se produce una transición de un estado a otro.
A
lo largo del tiempo, los valores de las entradas pueden tomar ciertos
valores.
Si,
el estado en que se encuentra, «es sensible» a estas entradas,
puede producirse un transición y cambiar de estado, ejecutandose
la(s) accion(es) correspondiente(s) a dicha transición.
Y
así hasta el infinito.
El
autómata va tomando diversos estados y efectuando diversas acciones
en función de ls entradas.
El
autómata, los estados, las transiciones y las acciones se definen
mediante una estructura de datos en la que está relacionado todo
ello.
Eso
es todo.
Se
trata pues, de determinar, frente a cada caso, cuanles son los
estados possibles del sistema, a que entradas es sensible cada
estado, qué transiciones se pueden producir y qué acciones se
llevan a cabo en cada una de ellas.
Veamos
como se hace. En un ejemplo.
3.
Primer ejemplo
3.1.
Definición
Diseñaremos
una FSM que a cada tecla que se pulse haga cambiar de estado un LED,
de apagado a encendido y viceversa. Este es un ejemplo muy sencillo
que puede efectuarse por medios mucho más sencillos pero su objetivo
es el de mostrar el funcionamiento de una FSM, no el de ser una
solución óptima.
Analicemos
el caso.
Es
evidente que el sistema partirá de un estado inicial que hemos de
definir. Vamos a suponer, parece lógico, que partimos del LED
apagado, llamémosle estado OFF. Al recibir como input una tecla
(input «tecla») la FSM pasará a al estado encendido, llamémosle
ON, efectuando la acción «encender LED». Estando en el estado ON,
si recibe una tecal pasará al estado OFF de nuevo y efectuará la
acción «apagar LED».
La
FSM se puede representar así
Vamos a
establecer la definición de la FSM, usando lenguaje Python
FSM01=
{‘NOMBRE’:’EJEMPLO 1’, # nombre de la FSM
‘OFF’: #
definición del estado OFF
[[{‘TECLA’,’ON’, EnciendeLED]], # input, transición y
acción
‘ON’: # definición del estado ON
[[[‘TECLA’,’OFF’,ApagaLED ]]} # input, transición y
acción
3.2.
Motor
Ahora
diseñamos una funcion que será el motor de la FSM. Esta función
intrepretará la definición de la FSM i determinará a partir del
estado actual y los inputs (contexto) cual es el estado siguiente y
las acciones a realizar. Esta es nuestro motor
#
AUTOMATA ------------
def
Engine(estado,contexto, fsm):
try:
d=fsm[estado]
nxt=estado
#
definición estado actual
for
i in range (len(d)): # mirar cada transición
t=d[i]
# transición i
c=t[0]
# condicion de transición
if
c.intersection(contexto)==c: # mirar si c està contenida en ct
nxt=t[1]
# proximo estado nxt
for
k in range (2,len(t)): # efectuar acciones
t[k]()
# llamada a cada accion
contexto=contexto-c
# contexto restante "no consumido"
return
nxt,contexto # dar por finalizado
return
e,contexto
except:
print
("Error en Engine Estado:",e, " inputs:",contexto)
return
"REPOS",None
3.3
Implementación inicial
A
fin de facilitar este primer ejemplo, en lugar de un LED escribiremos
en pantalla la palabra ON o la palabra OFF. La entrada se hará con
la tecla <ENTER>
La
implementación es la siguiente:
#!/usr/bin/env
python3
#
-*- coding: utf-8 -*-
#
FSM Ejemplo 1
#
#
Motor ------------
def
Engine(e,ct, fsm):
try:
d=fsm[e]
nxt=e
#
definición estado actual
for
i in range (len(d)): # mirar cada transición
t=d[i]
# transición i
c=t[0]
# condicion de transición
if
c.intersection(ct)==c: # mirar si c està contenida en ct
nxt=t[1]
# proximo estado nxt
for
k in range (2,len(t)): # efectuar acciones
t[k]()
# llamada a cada accion
ct=ct-c
# contexto restante "no consumido"
return
nxt,ct # dar por finalizado
return
e,ct
except:
print
("Error en Engine Estado:",e, " inputs:",ct)
return
"REPOS",None
#
Acciones ------------
def
EnciendeLED():
global
LED
LED="Encendido"
return
def
ApagaLED():
global
LED
LED="Apagado"
return
#
Autómata ------------
FSM01=
{"NOMBRE":"EJEMPLO 1", # nombre de
la FSM
"OFF":
# definición del estado OFF
[[{"TECLA"},"ON",
EnciendeLED]], # input, transición y acción
"ON":
# definición del estado ON
[[{"TECLA"},"OFF",ApagaLED
]]} # input, transición y acción
#
bucle principal
LED="Apagado"
estado="OFF"
while
True:
t=input()
print
("TECLA <ENTER> PULSADA")
contexto={"TECLA"}
print
("Estado inicial: %3s Contexto: %10s
LED:%s"%(estado,contexto,LED))
estado,contexto=Engine(estado,contexto,FSM01)
print
("Estado final : %3s Contexto: %10s
LED:%s"%(estado,contexto,LED))
Al
ejecutar el programa se observa el siguinte resultado:
que es
el resultado que esperábamos tener. Vemos como a cada input (en este
caso la tecla ENTER), el autómata canvia de estado.
Este
software, que ha sido desarrollado para una Raspberry, puede ser
ejecutado en cualquier ordenador puesto que es independiente del
hardware.
3.4
Implementación en RASPBERRY
Para
finalizar, haremos una implementación con una RASPBERRY, utilizando
los puertos de entrada/salida de la placa y la libreria GPIO que los
gestiona. Para ello habr que añadir o modificar algunas líneas de
código y algunas conexiones.
Habrá
que poner un pulsador con una resistencia y un LED, también con su
resistencia. Los esquemas, muy simples: connectar el LED al pin
GPIO21 con una resistencia en serie (4700 ohm) i a la masa (GND) y el
otra resistencia de 10000 ohm (aproximadamente) del pin GPIO20 a
massa (GND) y el pulsador entre dicho pin y +3.3V (3V3). Estos pin
pueden ser modificados adecuando las constantes que estan al inicio
del programa
El
nuevo programa será
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # FSM Ejemplo 2 # LED_PIN=21 # pin del GPIO al que conectamos el LED SENSOR =20 # pin del GPIO al que conectamos el pulsador import RPi.GPIO as GPIO from time import sleep import atexit # Motor ------------ def Engine(e,ct, fsm): try: d=fsm[e] nxt=e # definición estado actual for i in range (len(d)): # mirar cada transición t=d[i] # transición i c=t[0] # condicion de transición if c.intersection(ct)==c: # mirar si c està contenida en ct nxt=t[1] # proximo estado nxt for k in range (2,len(t)): # efectuar acciones t[k]() # llamada a cada accion ct=ct-c # contexto restante "no consumido" return nxt,ct # dar por finalizado return e,ct except: print ("Error en Engine Estado:",e, " inputs:",ct) return "OFF",None # Acciones ------------ def EnciendeLED(): global LED GPIO.output(LED_PIN, GPIO.HIGH) LED="ENCENDIDO" return def ApagaLED(): global LED GPIO.output(LED_PIN, GPIO.LOW) LED="APAGADO" return # Autómata ------------ FSM01= {"NOMBRE":"EJEMPLO 1", # nombre de la FSM "OFF": # definición del estado OFF [[{"TECLA"},"ON", EnciendeLED]], # input, transición y acción "ON": # definición del estado ON [[{"TECLA"},"OFF",ApagaLED ]]} # input, transición y acción def preparacontexto(): if GPIO.input(SENSOR)==GPIO.HIGH: return {"TECLA"} else: return set() # bucle principal atexit.register(GPIO.cleanup) # hacer que se ejecute al parar #inicializar puertos RASPBERRY GPIO.setmode (GPIO.BCM) GPIO.setup(LED_PIN, GPIO.OUT) GPIO.setup(SENSOR , GPIO.IN) # inicializar FSM estado="OFF" ApagaLED() i=0 # contador while True: contexto=preparacontexto() if contexto=={"TECLA"}: x=1 else: x=0 estado,contexto=Engine(estado,contexto,FSM01) if LED=="ENCENDIDO": y=1 else: y=0 print ("%6d %3d %3d"%(i,x,y)) i+=1 sleep(0.2)
Y
ponemos en marcha el programa (al que hemos añadido un
retardo(sleep) para poder apreciarlo a ojo correctamente, puesto que
en caso contrario es demasiado rápido el bucle.
Podemos
ver que, cada vez que pulsamos el pulsador, el LED canvia de estado
sin parpadeos, lo que también queda reflejado en la salida por
pantalla, con ceros y unos. La primera columna es un contador, la
segunda el estado del pulsador y la tercera el LED. Claramente, cada
vez que la entrada es 1 la salida canvia de estado.
En
breve incluiré más ejemplos
Comentarios
Publicar un comentario