How to Create a Finite State Machine Using SMACH and ROS

In this tutorial, we will learn how to create a finite state machine using the SMACH Python-based library and ROS. 

Real-World Applications

In the context of robotics, a finite state machine consists of states (e.g. robot is OFF, robot is moving, robot is charging, etc.), an initial state, and inputs that can cause the robot system to change from one state to another state. 

Prerequisites

Finite State Machine Example

1-turnstile
Source: Wikipedia 

A turnstile at a metro station or a stadium is an example of a mechanism that can be modeled using a finite state machine. In the case of a turnstile, we have the following finite state machine:

2-fsm-diagram
Source: Wikipedia 
3-table

You will notice that there are two states: locked and unlocked. There are two inputs that can cause the current state to transition from one state to another: coin (i.e. customer inserts coin) and push (i.e. customer pushes the turnstile). 

In the locked state, when the customer inserts a coin, the turnstile transitions to unlocked. However, if the customer pushes the turnstile without inserting a coin, the turnstile remains locked.

In the unlocked state, when the customer inserts a coin, the turnstile remains in the unlocked state. If the customer pushes the turnstile in the unlocked state, the turnstile let’s the customer through, and then it transitions to the locked state.

Getting Started

To create finite state machines in ROS, we use the Python library called SMACH (you pronounce this as “smash”).

Let’s begin by installing the required software.

sudo apt-get update
sudo apt-get upgrade
sudo apt-get install ros-noetic-smach ros-noetic-smach-ros ros-noetic-executive-smach 

Create a ROS Package

Let’s create a ROS package.

In a new terminal window, move to the src (source) folder of your workspace.

cd ~/catkin_ws/src

Now create the package.

catkin_create_pkg turnstile_smach std_msgs roscpp rospy
cd ~/catkin_ws/
catkin_make --only-pkg-with-deps turnstile_smach

Write the Code

Move to your package by opening a new terminal window, and typing:

roscd turnstile_smach

Create a new folder called scripts.

mkdir scripts

Move inside that folder.

cd scripts

Create a new script.

gedit fsm_turnstile.py

Add the following code inside the script.

#!/usr/bin/env python3

# Author: Automatic Addison https://automaticaddison.com
# Description: An example of a basic finite state machine for a turnstile at 
#   a stadium or metro station.

# Import the necessary libraries
import rospy # Python client library
from smach import State, StateMachine # State machine library
import smach_ros # Extensions for SMACH library to integrate it with ROS
from time import sleep # Handle time

# Define state LOCKED
class Locked(State):
  def __init__(self):
    State.__init__(self, outcomes=['push','coin'], input_keys=['input'])

  # Inside this block, you can execute any code you want
  def execute(self, userdata):
    sleep(1)
    
    rospy.loginfo('Executing state LOCKED')
		
    # When a state finishes, an outcome is returned. An outcome is a 
    # user-defined string that describes how a state finishes.
    # The transition to the next state is based on this outcome
    if userdata.input == 1:
      return 'push'
    else:
      return 'coin'

# Define state UNLOCKED
class Unlocked(State):
  def __init__(self):
    State.__init__(self, outcomes=['push','coin'], input_keys=['input'])

  def execute(self, userdata):
    sleep(1)

    rospy.loginfo('Executing state UNLOCKED')

    if userdata.input == 1:
      return 'push'
    else:
      return 'coin'

# Main method
def main():

  # Initialize the node
  rospy.init_node('fsm_turnstile_py')

  # Create a SMACH state machine container
  sm = StateMachine(outcomes=['succeeded','failed'])

  # Set user data for the finite state machine
  sm.userdata.sm_input = 0
  #sm.userdata.sm_input = input("Enter 1 to Push or 0 to Insert a Coin: ")
	
  # Open the state machine container. A state machine container holds a number of states.
  with sm:
	
    # Add states to the container, and specify the transitions between states
    # For example, if the outcome of state LOCKED is 'coin', then we transition to state UNLOCKED.
    StateMachine.add('LOCKED', Locked(), transitions={'push':'LOCKED','coin':'UNLOCKED'}, remapping={'input':'sm_input'})
    StateMachine.add('UNLOCKED', Unlocked(), transitions={'push':'LOCKED','coin':'UNLOCKED'}, remapping={'input':'sm_input'})

  # View our state transitions using ROS by creating and starting the instrospection server
  sis = smach_ros.IntrospectionServer('server_name', sm, '/SM_ROOT')
  sis.start()
  
  # Execute the state machine 
  outcome = sm.execute()

  # Wait for ctrl-c to stop the application
  rospy.spin()
  sis.stop()

if __name__ == '__main__':
  main()

Save the file, and close it.

Change the permissions of the file.

chmod +x fsm_turnstile.py

Open a new terminal window, and type:

cd ~/catkin_ws/

Build the package.

catkin_make --only-pkg-with-deps turnstile_smach

Run the Code

Open a new terminal window, and type:

cd ~/catkin_ws/

Start ROS.

roscore

Open a new terminal, and launch the finite state machine Python file you created.

rosrun turnstile_smach fsm_turnstyle.py

Here is the output:

4-smach-output

If you want to change the output, you can change the code so that the state machine input is 1 (i.e. push).

sm.userdata.sm_input = 1

Note that the SMACH viewer is unmaintained and doesn’t work with ROS Noetic.

That’s it! Keep building!

For a more in-depth look at SMACH with ROS, check out the official tutorials.

How to Move a Simulated Robot Arm to a Goal Using ROS

In this tutorial, we will learn how to use the ROS actionlib to send goals to a simulated robot arm. By the end of this tutorial, you will be able to build the following:

send-goals-to-arm

Real-World Applications

This project has a number of real-world applications: 

  • Order Fulfillment
  • Factories
  • Warehouses
  • Manufacturing
  • Kitchens
  • and more!

Let’s get started!

Prerequisites

  • You have completed this tutorial where you learned how to create a simulated robot arm.

Create a ROS Package

Let’s begin by creating a ROS package.

In a new terminal window, move to the src (source) folder of your workspace.

cd ~/catkin_ws/src

Now create the package.

catkin_create_pkg arm_to_goal actionlib roscpp rospy std_msgs
cd ~/catkin_ws/
catkin_make --only-pkg-with-deps arm_to_goal

Write the Code

Open a new terminal window.

Move to your package.

roscd arm_to_goal

Create a folder for the Python script we will write.

mkdir scripts

Move inside that folder.

cd scripts

Create a program.

gedit send_goal_to_arm.py

Add the following code:

#!/usr/bin/env python3

# Author: Automatic Addison https://automaticaddison.com
# Description: Uses the ROS action lib to move a robotic arm to a goal location

# Import the necessary libraries
from __future__ import print_function # Printing
import rospy # Python client library
import actionlib # ROS action library
from control_msgs.msg import FollowJointTrajectoryAction, FollowJointTrajectoryGoal # Controller messages
from std_msgs.msg import Float64 # 64-bit floating point numbers
from trajectory_msgs.msg import JointTrajectoryPoint # Robot trajectories


def move_robot_arm(joint_values):
  """
  Function to move the robot arm to desired joint angles.
  :param: joint_values A list of desired angles for the joints of a robot arm 
  """
  # Create the SimpleActionClient, passing the type of the action to the constructor
  arm_client = actionlib.SimpleActionClient('arm_controller/follow_joint_trajectory', FollowJointTrajectoryAction)

  # Wait for the server to start up and start listening for goals.
  arm_client.wait_for_server()
	
  # Create a new goal to send to the Action Server
  arm_goal = FollowJointTrajectoryGoal()

  # Store the names of each joint of the robot arm
  arm_goal.trajectory.joint_names = ['arm_base_joint', 'shoulder_joint','bottom_wrist_joint' ,'elbow_joint', 'top_wrist_joint']
  
  # Create a trajectory point  	
  point = JointTrajectoryPoint()

  # Store the desired joint values
  point.positions = joint_values

  # Set the time it should in seconds take to move the arm to the desired joint angles
  point.time_from_start = rospy.Duration(3)

  # Add the desired joint values to the goal
  arm_goal.trajectory.points.append(point)

  # Define timeout values
  exec_timeout = rospy.Duration(10)
  prmpt_timeout = rospy.Duration(5)

  # Send a goal to the ActionServer and wait for the server to finish performing the action
  arm_client.send_goal_and_wait(arm_goal, exec_timeout, prmpt_timeout)


if __name__ == '__main__':
  """
  Main method.
  """
  try:
    # Initialize a rospy node so that the SimpleActionClient can
    # publish and subscribe over ROS.
    rospy.init_node('send_goal_to_arm_py')

    # Move the joints of the robot arm to the desired angles in radians
    move_robot_arm([-0.1, 0.5, 0.02, 0, 0])

    print("Robotic arm has successfully reached the goal!")
    
  except rospy.ROSInterruptException:
    print("Program interrupted before completion.", file=sys.stderr)

Change the permissions.

chmod +x send_goal_to_arm.py

Compile the package.

cd ~/catkin_ws/
catkin_make --only-pkg-with-deps arm_to_goal

Run the Code

Open a new terminal window, and type:

cd ~/catkin_ws/
roslaunch mobile_manipulator_body mobile_manipulator_gazebo.launch
1-robot-mobile-manipulator

Open a new terminal window, and type the following command to move the robot to the goal location:

rosrun arm_to_goal send_goal_to_arm.py
2-after-moving-1
3-action-completed

That’s it! Keep building!

How to Build a Simulated Mobile Manipulator Using ROS

In this tutorial, we will create a mobile manipulator using a wheeled robot base and a robotic arm. All we need to do is to connect the arm_base link of the robot arm to the base_link of the robot.

mobile-manipulator-gif

This tutorial would not have been possible without Ramkumar Gandhinathan and Lentin Joseph’s awesome book ROS Robotics Projects Second Edition (Disclosure: As an Amazon Associate I earn from qualifying purchases). I highly recommend it if you want to learn ROS 1. Many of the files (URDF, configuration, and STL files), come from their book’s public GitHub page.

Real-World Applications

This project has a number of real-world applications: 

  • Indoor Delivery Robots
  • Order Fulfillment
  • Factories
  • Warehouses
  • Space Exploration
  • Power Plants

Let’s get started!

Prerequisites

  • You have completed this tutorial where you learned how to create a mobile robot base.
  • You have completed this tutorial where you learned how to create a robotic arm.

Build the Mobile Manipulator

Open a new terminal window.

Move to the urdf folder of your package.

roscd mobile_manipulator_body/urdf/

Now create a file named mobile_manipulator.urdf.

gedit mobile_manipulator.urdf

Add the mobile_manipulator.urdf code inside there.

Save and close the file.

Test the Mobile Manipulator

Now, let’s launch RViz to see what our robot looks like so far.

roscd mobile_manipulator_body/urdf/
roslaunch urdf_tutorial display.launch model:=mobile_manipulator.urdf
1-rviz-mobile-manipulator

A GUI will appear that will enable you to move the joints.

2-gui-will-appear

Launch the Mobile Manipulator

Now let’s launch the mobile manipulator.

Open a new terminal window, and go to the package.

roscd mobile_manipulator_body/launch/

Create a new launch file.

gedit mobile_manipulator_gazebo.launch

Add the mobile_manipulator_gazebo.launch code inside there.

Save and close the file.

Now let’s launch the robot in Gazebo.

Move to your catkin workspace.

cd ~/catkin_ws/
roslaunch mobile_manipulator_body mobile_manipulator_gazebo.launch

Here is how the robot looks.

3-gazebo-mobile-manipulator

Here are the active ROS topics.

rostopic list
4-ros-active-topics

Open a new terminal, and type this command to move the robot arm a little bit:

rostopic pub /arm_controller/command trajectory_msgs/JointTrajectory '{joint_names: ["arm_base_joint","shoulder_joint", "bottom_wrist_joint", "elbow_joint","top_wrist_joint"], points: [{positions: [-0.1, 0.5, 0.02, 0, 0], time_from_start: [1,0]}]}' -1


Type this command to bring the robot back to the home position.

rostopic pub /arm_controller/command trajectory_msgs/JointTrajectory '{joint_names: ["arm_base_joint","shoulder_joint", "bottom_wrist_joint", "elbow_joint","top_wrist_joint"], points: [{positions: [0, 0, 0, 0, 0], time_from_start: [1,0]}]}' -1
3b-gazebo-mobile-manipulator

You can steer the robot by opening a new window and typing:

rosrun rqt_robot_steering rqt_robot_steering

You will need to change the topic inside the GUI to:

/robot_base_velocity_controller/cmd_vel

To see the velocity messages, open a new window and type:

rostopic echo /robot_base_velocity_controller/cmd_vel

References

ROS Robotics Projects Second Edition