Sample Code: A sample code is provided to make the robot move forward for a certain distance. Please read carefully the provided code, and understand its functionality. You need to make changes under run function to complete the square trajectory.
roscd ee144w25
cd src
touch open_loop.py
gedit open_loop.py
Please copy and paste the following code in the text editor, then save and close it.
#!/usr/bin/env python3
import rospy
from geometry_msgs.msg import Twist
from math import pi
class Turtlebot():
def __init__(self):
rospy.init_node("turtlebot_move")
rospy.loginfo("Press Ctrl + C to terminate")
self.vel_pub = rospy.Publisher("cmd_vel", Twist, queue_size=10)
self.rate = rospy.Rate(10)
self.run()
def run(self):
vel = Twist()
vel.linear.x = 0.5
vel.angular.z = 0
#while not rospy.is_shutdown(): # uncomment to use while loop
for i in range(50):
self.vel_pub.publish(vel)
self.rate.sleep()
"""
vel.linear.x = 0
vel.angular.z = 0.05
#while not rospy.is_shutdown(): # uncomment to use while loop
for i in range(50):
self.vel_pub.publish(vel)
self.rate.sleep()
"""
if __name__ == '__main__':
try:
tb = Turtlebot()
except rospy.ROSInterruptException:
rospy.loginfo("Action terminated.")
Now, let’s break the code down.
#!/usr/bin/env python
import rospy
This line imports a Twist class that can carry linear and angular velocity information. Later on we will need to create an instance of this class, assign the velocity value, and publish it out to the robot.
from geometry_msgs.msg import Twist
from math import pi
We use class to better organize resources (variables and functions) in Python scripts. With class, we can easily update the value of a variable in one function and have access to it from another member function.
In the following code structure, we first define a class named Turtlebot, and then create an instance (object) called tb in the Python main function. The Python program will start running from the line right after if __name__ == '__main__':. However, to create a new instance, the program needs to run __init__ function for once (this is a function that every class must have for the initialization process). We then call run function under __init__ function. As such, each component is connected into the running pipeline.
The keyword self is a pointer used internally to refer to resources, and the keyword pass is just a placeholder that actually does nothing.
class Turtlebot():
def __init__(self):
self.run()
def run(self):
pass
if __name__ == '__main__':
whatever = Turtlebot()
The try-except block is used to handle exceptions. In this case, it will provide logging information when the user terminates the program using Ctrl + C. For the time being, we do not need to know more details on this.
if __name__ == '__main__':
try:
whatever = Turtlebot()
except rospy.ROSInterruptException:
rospy.loginfo("Action terminated.")
In ROS, we have a ROS Master (or roscore) running as a central server to manage global resources and connect all ROS Nodes. Each program will need to talk to this central server first before talking to other program (this process is, in other words, to register itself as a ROS Node). Therefore, in each and every Python script running ROS, the first thing to do is to initialize itself as a ROS Node. The argument turtlebot_move is the name we would like to assign to this ROS Node.
rospy.init_node("turtlebot_move")
rospy.loginfo("Press Ctrl + C to terminate")
ROS Topic is a way that ROS Node communicates with each other for continuous data transmission. (In this case, we keep sending velocity commands.) For one-time short communication, we also have ROS Service, which will not be covered in this course.
On the robot side, there is a ROS Subscriber waiting for velocity commands. What we need to do in this script is to create a ROS Publisher to send commands. The required arguments are the topic name, the message type Twist, and the queue size 10.
self.vel_pub = rospy.Publisher("", Twist, queue_size=10)
The following question is how frequently we should send velocity commands. Note that the robot will stop if no velocity command is received after a while, and the robot may “panic” if thousands of commands are received in a second. Therefore, we can pick a moderate rate at 10Hz (i.e. 10 commands per second).
We use the Rate class to help us manage the timing. By using the rate.sleep() function together with while or for loop, we can approximately keep the loop running at 10Hz rate. The rate.sleep() function can calculate how much time elapsed since last time it was called. If this time elapsed is less than 1/rate (in this case 0.1s), it will block the program till the time has reached 1/rate. If the time has already exceeded 1/rate, it will simply let it go.
self.rate = rospy.Rate(10)
for i in range(50):
self.vel_pub.publish(vel)
self.rate.sleep()
To prepare a ROS Message that contains velocity data, we first create an instance (called vel) of the Twist class (which is the message we are about to send), and then assign linear and angular velocity to its corresponding variable. Note that you need to use . to have access to the member variables of a class data structure. See ROS Wiki documentation for more information about the message definition.
vel = Twist()
vel.linear.x = 0.5
vel.angular.z = 0
self.vel_pub.publish(vel)
Lastly, if you use while loop, it is highly recommended to add a condition to check if the ROS server is still alive. This can help terminate the Python script whenever you press Ctrl + C in the terminal. On the contrary, dead loop like while True: xxx will not respond to your shutdown commands (unless you add proper break condition in the loop).
while not rospy.is_shutdown():
pass
We follow ROS conventions to use SI units. (i.e. length in meter, time in second, angle in radian). See ROS Wiki article REP 103 Standard Units of Measure and Coordinate Conventions for more information.
When a new robot is spawned, the forward heading direction is the positive x axis; the leftward direction is the positive y axis; and by right-hand rule, z axis upward. This is also specified in REP 103.
In Gazebo, you can use Ctrl + R to set the robot back to the origin without the need to relaunch.
In this lab, you need to finely tune the parameters for open-loop control.
Submission: individual submission via Gradescope
Due time: 11:59pm, February 3, Monday
Files to submit:(please use exactly the same filename; case sensitive)
lab2_report.pdf
open_loop.py
Grading rubric:
+ 50% Clearly describe your approach and explain your code in the lab report.
+ 40% The robot can visit all four vertices of the diamond trajectory (error < 1.0m). Partial credits will be given according to the number of vertices visited.
+ 10% The script can complete the task on time and exit gracefully.
- 10% Penalty applies for each late day. No submission will be accepted beyond four (4) days from the due date.
(Optional) Capture a panoramic video of the robot demonstrating the followed trajectory inside the Gazebo world. You can use the simplescreenrecorder software, by installing it with,
sudo apt install simplescreenrecorder