Lab 2: ROS Tools and Testing on a Simulated Robot
Overview
In this lab, we are going to test ROS tools that are developed for illustrating the ROS Nodes and Topic graph relations and visualizing the ROS Topics in real-time. ROS, as a tool-based framework, features a plethora of applications, which can also be found here.
Additionally, the Clearpath Jackal mobile robot will be demonstrated in the class, along with the use of some of the ROS tools. Furthermore, the simulated version of the Jackal robot will be deployed in the Gazebo simulator and it will be compared with the real robot. A 2D LiDAR ranging scenario will be demonstrated and will be the base of this lab’s assignment.
Installing Gazebo Simulator
The Gazebo simulator is a broadly used open-source robotic simulator, that has been used for ROS application development before testing on robots in the real world. To install the Gazebo simulator we need to perform,
sudo apt-get install ros-noetic-gazebo-ros-pkgs ros-noetic-gazebo-ros-control
To install the official ROS package of the Clearpath Jackal robot for its Gazebo simulation
sudo apt-get install ros-noetic-jackal-gazebo
to install the official Gazebo package of the simulated Clearpath Jackal robot
ROS Launch files
Before we proceed with the experiment on the Jackal robot and the ROS tools, we need to describe the ROS launch file functionality. Particularly, a ROS launch file is a XML type file with a .launch file extension, which can be used to launch multiple ROS nodes at the same time while it can set values on parameters in the Parameters Server before executing the rosrun. The ROS launch files can are kept in the launch folder of each ROS package.
An example of a launch file could be,
<launch>
<arg name="x_var" default="0" />
<arg name="enable_var" default="true" />
<!-- This is a comment -->
<node pkg="package_name" name="node_name" type="node_filename" />
<node pkg="package_name" name="node_name_2" type="node_filename_2" args="-x $(arg x_var) -enable_var $(arg enable_var)" />
</launch>
where,
<arg name="x_var" default="0" />
<arg name="enable_var" default="true" />
is the initialization of the variable x_var with the value 0 and the variable enable_var with True. The code part of,
<node pkg="package_name" name="node_name" type="node_filename" />
executes the node node_filename from the package package_name and names it node_name. The second node call part, namely,
<node pkg="package_name" name="node_name_2" type="node_filename_2" args="-x $(arg x_var) -enable_var $(arg enable_var)"
executes the node_filename_2 node, but also provides argument information through the Parameter Server.
To execute the ROS launch file you can perform in a new terminal,
roslaunch package_name file.launch
Now, let’s try to create a launch file for our created ROS package, namely the ee106w26. Specifically, create a ROS launch that you can execute at the same time both the publisher and subscriber nodes of the Lab 1. Show the results to the Teaching Assistant.
rqt and rqt_graph Tools
The rqt tool as a QT-based framework developed for ROS to enable the creation of user interface-enabled applications. The rqt_graph is visualizing tool that can illustrate the relations of the running ROS nodes and topics in a graph illustration.
To test the result of the rqt-graph, first enable the ROS nodes of your application and then execute the below command in a new terminal.
rqt_graph
RViz : ROS Visualisation Tool
In addition, the main visualization tool that is used in ROS software development, is the RViz. This tool is used to illustrate the raw information that is published by the ROS topics, in real-time, with respect to a predefined coordinate system. To enable RViz you can perform in a separate terminal,
rviz
Gazebo Simulation and the Clearpath Jackal Robot
In order to start the Gazebo simulator with an empty world, you can execute,
roslaunch gazebo_ros empty_world.launch
To properly exit or terminate Gazebo you should use the window terminating button. In many cases, such as closing abruptly the terminal or if the Gazebo is not responding, you can terminate it by executing in a new terminal,
sudo killall gzserver
sudo killall gzclient
As the Gazebo is up and running, we can spawn a Jackal robot inside the simulated environment. To achieve that, we will create a dedicated ROS launch file in ee106w26/launch folder and attach the following,
<launch>
<arg name="x" default="0" />
<arg name="y" default="0" />
<arg name="z" default="1" />
<arg name="yaw" default="0" />
<arg name="joystick" default="true" />
<!-- Configuration of Jackal which you would like to simulate.
See jackal_description for details. -->
<arg name="config" default="front_laser" />
<!-- Load Jackal's description, controllers, and teleop nodes. -->
<include file="$(find jackal_description)/launch/description.launch">
<arg name="config" value="$(arg config)" />
</include>
<include file="$(find jackal_control)/launch/control.launch" />
<include file="$(find jackal_control)/launch/teleop.launch">
<arg name="joystick" value="$(arg joystick)" />
</include>
<!-- Spawn Jackal -->
<node name="urdf_spawner" pkg="gazebo_ros" type="spawn_model"
args="-urdf -model jackal -param robot_description -x $(arg x) -y $(arg y) -z $(arg z) -R 0 -P 0 -Y $(arg yaw)" />
</launch>
This file will be the ROS launch file that we will use to start Gazebo and spawn a simulated Jackal robot inside the world. Specifically, the variables x,y,z and yaw define the initial position and orientation values of the robot in the world. The ‘joystick’ variable enables the teleoperation of the Jackal robot through a joystick controller. This file can be saved with the name of jackal.launch file inside the ee106w26 ROS package. To execute the ROS launch file, you can perform in a new terminal,
roslaunch ee106w26 jackal.launch
After the execution of the above ROS launch file, you can use rviz to visualize the captured sensory information from the simulated Jackal robot. Place objects closely around the simulated robot and check the front/scan ROS topic of type sensor_msgs/LaserScan on how it updates the visualized information in RViz.
Robot Teleoperation
Nowadays, most of the robots support teleoperation through a connected keyboard or a gamepad/joystick. In ROS we can use the teleop_twist_keyboard to use our keyboard for robot teleoperation, which publishes geometry_twist/Twist message on the cmd_vel ROS topic.
To install this package,
cd ~/catkin_ws/src/
git clone https://github.com/ros-teleop/teleop_twist_keyboard.git
and build the catkin workspace.
Jackal Control inside the Gazebo World
As we have completed the above steps, to spawn the simualted Jackal inside the Gazebo world we execute in separate terminals the below commands in the following order,
roslaunch gazebo_ros empty_world.launch
roslaunch ee106w26 jackal.launch
rosrun teleop_twist_keyboard teleop_twist_keyboard.py
RViz program can be executed also in a separate terminal, in case you want to visualize the sensory information that is captured by the simulated Jackal robot.
RViz and TF Visualisation
Initially, we start the Gazebo simulator with the simulated Jackal robot. Thus, in separate terminals execute,
roslaunch gazebo_ros empty_world.launch
and
roslaunch ee106w26 jackal.launch
As the robot has been successfully spawned inside the Gazebo world, we can enable the ROS visualization tool, by executing in a separate terminal,
rviz
In order to visualize the robot in the RViz tool, we have to set firstly the visualization Global Options/Fixed Frame to any of the listed coordinate systems. For our setup, we set Global Options/Fixed Frame to base_link, as it represents the base coordinate system of the Jackal robot. Also, we by using the Add button of the left panel of RViz, we add the visualization of the TF, the RobotModel, and the LaserScan by selecting, the latter, to visualize the front/scan ROS topic. Finally, to include an obstacle inside the Gazebo world, we add a Stop Sign model at the position (2,0,0).
![]()
- align
center
![]()
- align
center
ROS Frames and TF Listener
ROS frames are fundamental entities in ROS, as they represent the existing coordinate systems of the robotic setup. Particularly, ROS frames can be assigned on any part of the robot, which can be considered rigid, as well as, on any onboard sensor. Thus, each captured measurement can be spatially described in the corresponding ROS frame of the capturing sensor, while multiple frames can be connected to each other spatially and form the ROS frame tree of the ROS setup.
![]()
- align
center
In order to publish a transformation between two ROS frames that remains static over time, you can use the tool static_transform_publisher from the tf ROS package. For example in our case, it would be ideal to create a frame for the front bumper of the Jackal, so we can spatially describe all captured ranging measurements in respect to it to avoid any potential collisions as it moves forward.
To do that, we can describe the new front_bumper frame, with respect to the base_link frame of the robot, by executing in a new terminal,
rosrun tf static_transform_publisher 0.26 0 0.11 0 0 0 1 base_link front_bumper 100
where the arguments of this command are,
static_transform_publisher x y z qx qy qz qw frame_id child_frame_id period(milliseconds)
One of the terminal commands to obtain the transformation matrix between two ROS frames is,
rosrun tf tf_echo base_link front_laser
This information can be captured also inside a ROS node by using the tf.TransformListener() module. To test it, please initialize a new ROS node inside the ee106w26 ROS package, under the name of tf_listener.py, which will contain,
#!/usr/bin/env python3
import roslib
roslib.load_manifest('ee106w26')
import rospy
import math
import tf
import geometry_msgs.msg
import numpy as np
rospy.init_node('tf_listener_node')
# initialization of the ROS tf listener
listener = tf.TransformListener()
rate = rospy.Rate(10.0)
# the goal of this node is to continously listen to the transformation relation between the base_link and front_laser ROS frames and print the Translation and Rotation of the captured transformation matrix.
while not rospy.is_shutdown():
try:
# capture the tf of the two frames the exact moment of the command execution (rospy.Time(0))
(trans,rot) = listener.lookupTransform('/base_link', '/front_laser', rospy.Time(0))
except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException):
continue
# print of the Translation and Rotation information, by demonstrating the Quaternion, Euler, and Rotation Matrix representation of the latter.
print("The translation is (x,y,z) = " + str(trans))
print("The rotation (quaternion) is (x,y,z,w) = " + str(rot))
print("The rotation (euler) is (r,p,y) = " + str(tf.transformations.euler_from_quaternion(rot)))
rot_mat = tf.transformations.quaternion_matrix(rot)
print("The rotation (rotation matrix) is = " + str(tf.transformations.quaternion_matrix(rot)))
# we assume that a Lidar point is detected, w.r.t the Lidar's frame
laser_point_detected = [1, 0, 0, 1]
# initialization of the tf matrix to describe it in the /base_link frame
rot_mat[0,3] = trans[0]
rot_mat[1,3] = trans[1]
rot_mat[2,3] = trans[2]
print(np.dot(rot_mat , laser_point_detected))
rate.sleep()
Submission
How: individual, via Gradescope
Demo: required—teleoperate the Jackal in Gazebo and show your node responding to obstacles
When: 11:59 pm, Thursday, Jan 29
What to submit: -
lab2_report.pdf(use the provided template) - Include all screenshots, detailed step descriptions, and your fully commented Python code at the end
Demo Checklist
Show the Jackal driving toward obstacles
Open RViz with the RobotModel, TF, and LaserScan displays enabled
Grading Rubric
+ 10 % Gazebo world & frame setup - Create a world containing Jackal, a Stop Sign, and at least three distinct obstacles. - Define and broadcast a new frame called
front_bumper(e.g. via static_transform_publisher).+ 15 % ROS node initialization - Initialize a ROS node (e.g.
ranges_check). - Subscribe to/front/scan(sensor_msgs/LaserScan). - Create a TF listener and retrieve the transform between/front_laserand/front_bumper.+ 20 % LiDAR data processing & coordinate transformation - Iterate over
data.ranges, skipping “inf” values. - Convert each valid range+angle into a point in thefront_laserframe. - Use your 4×4 transform to map points into thefront_bumperframe. - Classify each mapped range as critical (< 0.2 m), major (< 0.5 m), or minor (≥ 0.5 m).+ 20 % ROS publisher & status messages - Form a
std_msgs/Stringwhosedatais the worst-case classification. - Publish once per scan to/jackal_robot_status. - In a separate terminal, runrostopic echo /jackal_robot_statusand demonstrate “critical”, “major”, and “minor” messages+ 25 % Final demonstration & RViz visualization - Teleoperate through all three cases (minor, major, critical). - For each case include:
Gazebo screenshot (robot + obstacles)
Terminal screenshot of
rostopic echo /jackal_robot_statusRViz screenshot with RobotModel, TF frames, and LaserScan
+ 10 % Report clarity & code quality - Clear, concise write-up with captions. - Comments explaining each major code block. - Discussion of challenges or design decisions.
- 15 % per late day (up to two days)
Pseudo-code
#!/usr/bin/env python3
import roslib
roslib.load_manifest('ee106w26')
import rospy
import math
import tf
from sensor_msgs.msg import LaserScan
from std_msgs.msg import String
#import geometry_msgs.msg
import numpy as np
#----------------------------------------------------------------------------------------------------------------------
def callback(scan: LaserScan):
# Get the transformations and rotations
try:
(trans,rot) = listener.lookupTransform('base_link','front_laser',rospy.Time(0))
except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException):
rospy.logwarn("No TF available yet")
return
# Build a 4x4 homogeneous transform matrix T
T = tf.transformations.quaternion_matrix(rot)
T[0:3,3] = trans
worst = 'minor'
# Loop over all ranges, skip the inf ranges
for idx, r in enumerate(scan.ranges):
if not np.isfinite(r):
continue
# transform each point
theta = scan.angle_min + idx * scan.angle_increment
x_Laser = r * math.cos(theta)
y_Laser = #COMPLETE
p_Laser = # COMPLETE
# transofrm into base_link frame
p_Bumper = #COMPLETE
# compute distance (in meters)
distance = np.hypot(p_Laser[0], p_Laser[1])
#print(distance)
#COMPLETE CODE….
# classify critical, major, minor
#PUBLISH worst
if __name__ == '__main__':
rospy.init_node('tf_listener')
listener = tf.TransformListener()
# Create a publisher to view the output of
pub = rospy.Publisher('jackal_robot_status', String, queue_size=1)
# Create a subscriber for the /front/scan topic that will send LiDAR data to callback()
rospy.Subscriber('/front/scan', LaserScan, callback)
rospy.loginfo("TF with LiDAR")
rospy.spin()