A major problem that we have been struggling for several weeks is almost solved. We have been looking for a combination of a good simulator and a FSM visualizer. We are using Habitat, but we didn’t find any suitable FSM visualization tool.
Robin just found a python library that can do great job on FSM visualization - Python State Machine. It can generate clear FSM graphs, just like SMACH. Here is an example of a number guessing machine.
Robin also implemented this library into Habitat, and it works well as a time based moving robot. There still have two issues with the code: the keyboard doesn’t work and pygame
frame-rate gets extremely low after implementing FSM visualizer.
I tried to resolve them, and the keyboard one has been done. And I found that the low frame-rate was caused by the state machine’s graph generations(as a png
image file), and the visualizer is updating the graph for every frame in pygame
. I partially resolved this by making it generate a graph only when the state changes. The result gets slightly better, the frame-rate is high as usual when the robot keeps in the current state. However, when it changes between states, the frame-rate is slow but the general experience is much better. If the graph is something like a canvas with draggable nodes not rendering as some static images, I think this problem would be solved.
Python State Machine
Install
conda install python-statemachine
conda install pydot
conda install Graphviz
git clone https://github.com/fgmacedo/python-statemachine.git
All the following changes are based on examples/interactive_play.py
. Here is also a Colab version you can play with.
Import and Draw Graph
# import packages
from statemachine import StateMachine, State
from IPython.display import Image, display, clear_output
import io
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import cv2
def view_pydot(pdot):
# Render the pydot graph to a PNG image
png_str = pdot.create_png(prog='dot')
# Convert the PNG image to a format that can be displayed with OpenCV
sio = io.BytesIO()
sio.write(png_str)
sio.seek(0)
img = mpimg.imread(sio)
img_bgr = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
# Display the image with OpenCV
cv2.imshow('FSM Graph', img_bgr)
FSM class
class FSM(StateMachine):
stop = State(initial=True)
rotate = State()
move_forward = State()
move_backward = State()
transition = (
move_forward.from_(stop, cond="move_forward_command")
| stop.from_(move_forward, cond="stop_forward_command")
| rotate.from_(stop, cond="rotate_command")
| stop.from_(rotate, cond="stop_rotate_command")
| move_backward.from_(stop, cond="move_backward_command")
| stop.from_(move_backward, cond="stop_backward_command")
| stop.from_(stop, cond="no_key_pressed")
)
def __init__(self):
super().__init__()
self.pre_state = None
self.if_state_change = self._if_state_change
def move_forward_command(self):
self.if_state_change()
self.pre_state = self.current_state
return pygame.key.get_pressed()[pygame.K_i]
def stop_forward_command(self):
self.if_state_change()
self.pre_state = self.current_state
return not pygame.key.get_pressed()[pygame.K_i]
def rotate_command(self):
self.if_state_change()
self.pre_state = self.current_state
return pygame.key.get_pressed()[pygame.K_j] or pygame.key.get_pressed()[pygame.K_l]
def stop_rotate_command(self):
self.if_state_change()
self.pre_state = self.current_state
return not (pygame.key.get_pressed()[pygame.K_j] or pygame.key.get_pressed()[pygame.K_l])
def move_backward_command(self):
self.if_state_change()
self.pre_state = self.current_state
return pygame.key.get_pressed()[pygame.K_k]
def stop_backward_command(self):
self.if_state_change()
self.pre_state = self.current_state
return not pygame.key.get_pressed()[pygame.K_k]
def no_key_pressed(self):
self.if_state_change()
self.pre_state = self.current_state
return not any(pygame.key.get_pressed())
def on_enter_move_forward(self):
print("Moving forward...")
def on_enter_stop(self):
print("Stopping...")
def on_enter_rotate(self):
print("Rotating...")
def on_enter_move_backward(self):
print("Moving backward...")
def _if_state_change(self):
if self.pre_state != self.current_state:
self.pre_state = self.current_state
view_pydot(self._graph())
return True
return False
Update play_env
def play_env(env, args, config):
fsm = FSM()
# rest of code
while True:
try:
fsm.transition()
except Exception as e:
print(e)
# rest of code