Working with ChefBot's control GUI

After completing the Hello World application in PyQt, we will now discuss a GUI for controlling ChefBot. The main use of building a GUI is to create an easier way to control the robot. For example, if the robot is deployed in a hotel to serve food, the person who controls this robot need not have knowledge about the complex commands to start and stop this robot; so, building a GUI for ChefBot can reduce the complexity and make it easier for the user. We are planning to build a GUI using PyQt, ROS, and the Python interface. The ChefBot ROS package is available on GitHub at the following link: https://github.com/qboticslabs/learning_robotics_2nd_ed

If you haven't cloned the code yet, you can do so now using following command:

    $ git clone https://github.com/qboticslabs/learning_robotics_2nd_ed.git

The GUI code named robot_gui.py is placed in the scripts folder, which is inside the chefbot_bringup package.

The following screenshot shows the GUI that we have designed for ChefBot:

Running Pyqt4 application

The GUI has the following features:

  • It can monitor the robot battery status and robot status. The robot status indicates the working status of the robot. For example, if the robot encounters an error, it will indicate the error on this GUI.
  • It can command the robot to move into a table position for delivering food. There is a spin box widget on the GUI to input the table position. Currently, we are planning this GUI for a room with nine tables, but we may expand it to any number according to the requirement. After inputting the table number, we can command the robot to go to that table by clicking on the Go button; the robot will get into that position. If we want to return the robot to the initial position, we can click on the Home button. If we want to cancel the current robot movement, click on Cancel to stop the robot. The working of this GUI application is as follows:

When we have to deploy ChefBot in a hotel, the first procedure that we have to do is to create a map of the room. After mapping the entire room properly, we have to save the map on the robot PC. The robot does the mapping only once. After mapping, we can run the localization and navigation routines and command the robot to get into a position on the map. The ChefBot ROS package comes with a map and simulation model of a hotel-like environment. We can now run this simulation and localization for testing the GUI and in the next chapter, we will discuss how to control the hardware using the GUI. If you install the ChefBot ROS packages on your local system, we can simulate a hotel environment and test the GUI.

Start the ChefBot simulation in a hotel-like arrangement using the following command:

    $roslaunch chefbot_gazebo chefbot_hotel_world.launch  

After starting the ChefBot simulation, we can run the localization and navigation routines using an already built map. The map is placed in the chefbot_bringup package. We can see a map folder inside this package. Here, we will use this map for performing this test. We can load the localization and navigation routine using the following command:

    $ roslaunch chefbot_gazebo amcl_demo.launch 
map_file:=/home/<user_name>/catkin_ws/src/chefbot/chefbot_bringup/map/hotel1.yaml

The path of the map file can change in different systems, so use the path in your system instead of this path.

If the path mentioned is correct, it will start running the ROS navigation stack. If we want to see the robot position on the map or manually set the initial position of the robot, we can use RViz using the following command:

    $ roslaunch chefbot_bringup view_navigation.launch  

In RViz, we can command the robot to go to any map coordinates using the 2D Nav Goal button.

We can command the robot to go to any map coordinates using programming too. The ROS navigation stack works using the ROS actionlib library. The ROS actionlib library is for performing preemptable tasks; it is similar to ROS services. An advantage over ROS services is that we can cancel the request if we don't want it at that time.

In the GUI, we can command the robot to go to a map coordinate using the Python actionlib library. We can get the table position on the map using the following technique.

After starting the simulator and AMCL nodes, launch the keyboard teleoperation and move the robot near each table. Use the following command to get the translation and rotation of the robot:

    $ rosrun tf tf_echo /map /base_link  

When we click on the Go button, the position is fed to the navigation stack and the robot plans its path and reaches its goal. We can even cancel the task at any time. So, the ChefBot GUI acts as an actionlib client, which sends map coordinates to the actionlib server; that is, the navigation stack.

We can now run the robot GUI to control the robot using the following command:

    $ rosrun chefbot_bringup robot_gui.py  

We can select a table number and click on the Go button for moving the robot to each table.

Assuming that you cloned the files and got the robot_gui.py file, we will discuss the main slots we added into the Ui_Form() class for the actionlib client and to get values of the battery and robot status.

We need to import the following Python modules for this GUI application:

import rospy 
import actionlib 
from move_base_msgs.msg import * 
import time 
from PyQt4 import QtCore, QtGui 

The additional modules we require are the ROS Python client rospy, and the actionlib module to send values to the navigation stack. The move_base_msgs module contains the message definition of the goal that needs to be sent to the navigation stack.

The robot position near each table is mentioned in a Python dictionary. The following code shows hardcode values of the robot's position near each table:

table_position = dict() 
table_position[0] = (-0.465, 0.37, 0.010, 0, 0, 0.998, 0.069) 
table_position[1] = (0.599, 1.03, 0.010, 0, 0, 1.00, -0.020) 
table_position[2] = (4.415, 0.645, 0.010, 0, 0, -0.034, 0.999) 
table_position[3] = (7.409, 0.812, 0.010, 0, 0, -0.119, 0.993) 
table_position[4] = (1.757, 4.377, 0.010, 0, 0, -0.040, 0.999) 
table_position[5] = (1.757, 4.377, 0.010, 0, 0, -0.040, 0.999) 
table_position[6] = (1.757, 4.377, 0.010, 0, 0, -0.040, 0.999) 
table_position[7] = (1.757, 4.377, 0.010, 0, 0, -0.040, 0.999) 
table_position[8] = (1.757, 4.377, 0.010, 0, 0, -0.040, 0.999) 
table_position[9] = (1.757, 4.377, 0.010, 0, 0, -0.040, 0.999) 

We can access the position of the robot near each table by accessing this dictionary.

Currently, we have inserted only four values for the purpose of demonstration. You can add more values by finding the position of other tables.

We are assigning some variables to handle the table number, position of the robot, and the actionlib client inside the Ui_Form() class:

#Handle table number from spin box 
self.table_no = 0 
#Stores current table robot position 
self.current_table_position = 0 
#Creating Actionlib client 
self.client = actionlib.SimpleActionClient('move_base',MoveBaseAction) 
#Creating goal message definition 
self.goal = MoveBaseGoal() 
#Start this function for updating battery and robot status 
self.update_values() 

The following code shows the signals and slots assignment in this code for buttons and spin box widgets:

#Handle spinbox signal and assign to slot set_table_number() 
QtCore.QObject.connect(self.spinBox, QtCore.SIGNAL(_fromUtf8("valueChanged(int)")), self.set_table_number) 
 
#Handle Home button signal and assign to slot Home() 
QtCore.QObject.connect(self.pushButton_3, QtCore.SIGNAL(_fromUtf8("clicked()")), self.Home) 
 
#Handle Go button signal and assign to slot Go() 
QtCore.QObject.connect(self.pushButton, QtCore.SIGNAL(_fromUtf8("clicked()")), self.Go) 
 
#Handle Cancel button signal and assign to slot Cancel() 
QtCore.QObject.connect(self.pushButton_2, QtCore.SIGNAL(_fromUtf8("clicked()")), self.Cancel)

The following slot handles the spin box value from the UI and assigns a table number. Also, it converts the table number to the corresponding robot position:

def set_table_number(self): 
  self.table_no = self.spinBox.value() 
  self.current_table_position = table_position[self.table_no] 

Here is the definition of the Go slot for the Go button. This function will insert the robot position of the selected table in a goal message header and send it into the navigation stack:

def Go(self): 
 
  #Assigning x,y,z pose and orientation to target_pose message 
  self.goal.target_pose.pose.position.x=float(self.current_table  _position[0]) 
 
  self.goal.target_pose.pose.position.y=float(self.current_table  _position[1]) 
  self.goal.target_pose.pose.position.z=float(self.current_table  _position[2]) 
 
  self.goal.target_pose.pose.orientation.x =     float(self.current_table_position[3]) 
  self.goal.target_pose.pose.orientation.y=   float(self.current_table_position[4]) 
  self.goal.target_pose.pose.orientation.z=   float(self.current_table_position[5]) 
 
  #Frame id 
  self.goal.target_pose.header.frame_id= 'map' 
 
  #Time stamp 
  self.goal.target_pose.header.stamp = rospy.Time.now() 
 
  #Sending goal to navigation stack 
  self.client.send_goal(self.goal) 

The following code is the Cancel() slot definition. This will cancel all the robot paths that it was planning to perform at that time:

def Cancel(self): 
  self.client.cancel_all_goals()

The following code is the definition of Home(). This will set the table position to zero, and call the Go() function. The table at position zero is the home position of the robot:

def Home(self): 
  self.current_table_position = table_position[0] 
  self.Go() 

The following definitions are for the update_values() and add() functions. The update_values() method will start updating the battery level and robot status in a thread. The add() function will retrieve the ROS parameters of the battery status and robot status, and set them to the progress bar and label, respectively:

def update_values(self): 
    self.thread = WorkThread() 
    QtCore.QObject.connect( self.thread,    QtCore.SIGNAL("update(QString)"), self.add ) 
    self.thread.start() 
def add(self,text): 
  battery_value = rospy.get_param("battery_value") 
  robot_status = rospy.get_param("robot_status") 
   self.progressBar.setProperty("value", battery_value) 
     self.label_4.setText(_fromUtf8(robot_status)) 

The WorkThread() class used in the preceding function is given here. The WorkThread() class is inherited from QThread provided by Qt for threading. The thread simply emits the signal update(Qstring) with a particular delay. In the preceding function, update_values(), the update(QString) signal is connected to the self.add() slot; so when a signal update(QString) is emitted from the thread, it will call the add() slot and update the battery and status value:

class WorkThread(QtCore.QThread): 
  def __init__(self): 
    QtCore.QThread.__init__(self) 
   def __del__(self): 
    self.wait() 
   def run(self): 
    while True: 
      time.sleep(0.3) # artificial time delay 
      self.emit( QtCore.SIGNAL('update(QString)'), " " ) 
      return 

We have discussed how to make a GUI for ChefBot, but this GUI is only for the user who controls ChefBot. If someone wants to debug and inspect the robot data, we may have to go for other tools. ROS provides an excellent debugging tool to visualize data from the robot.

The rqt tool is a popular ROS tool. It is based on a Qt-based framework for GUI development for ROS. Let's discuss the rqt tool, installation procedure, and how we can inspect the sensor data from the robot.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.104.120