A Move Square Python Template for Part 2
A combined publisher-subscriber node to achieve odometry-based control...
Below you will find a template Python script to show you how you can both publish to /cmd_vel
and subscribe to /odom
in the same node. This will help you build a closed-loop controller to make your robot follow a square motion path of size: 1m x 1m.
You can publish velocity commands to /cmd_vel
to make the robot move, monitor the robot's position and orientation in real-time, determine when the desired movement has been completed, and then update the velocity commands accordingly.
Suggested Approach¶
Moving in a square can be achieved by switching between two different movement states sequentially: Moving forwards and turning on the spot. At the start of each movement step we can read the robot's current odometry, and then use this as a reference to compare to, and to tell us when the robot's position/orientation has changed by the required amount, e.g.:
- With the robot stationary, read the odometry to determine its current X and Y position in the environment.
- Move forwards until the robot's X and Y position indicate that it has moved linearly by 0.5m.
- Stop moving forwards.
- Read the robot's odometry to determine its current orientation ("yaw"/
θz
). - Turn on the spot until the robot's orientation changes by 90°.
- Stop turning.
- Repeat.
import rospy
from geometry_msgs.msg import Twist
from nav_msgs.msg import Odometry
from tf.transformations import euler_from_quaternion
from math import sqrt, pow, pi
class Square():
def callback(self, topic_data: Odometry):
pose = topic_data.pose.pose
position = pose.position
orientation = pose.orientation
pos_x = position.x
pos_y = position.y
(roll, pitch, yaw) = euler_from_quaternion(
[orientation.x, orientation.y, orientation.z, orientation.w], "sxyz"
)
self.x = pos_x
self.y = pos_y
self.theta_z = yaw
if not self.first_message:
self.first_message = True
def __init__(self):
node_name = "move_square"
self.first_message = False
self.turn = False
self.pub = rospy.Publisher("cmd_vel", Twist, queue_size=10)
self.sub = rospy.Subscriber("odom", Odometry, self.callback)
rospy.init_node(node_name, anonymous=True)
self.rate = rospy.Rate(10) # hz
self.x = 0.0
self.y = 0.0
self.theta_z = 0.0
self.x0 = 0.0
self.y0 = 0.0
self.theta_z0 = 0.0
self.vel = Twist()
self.ctrl_c = False
rospy.on_shutdown(self.shutdownhook)
rospy.loginfo(f"the {node_name} node has been initialised...")
def shutdownhook(self):
self.pub.publish(Twist())
self.ctrl_c = True
def main(self):
while not self.ctrl_c:
# here is where your code would go to control the motion of your
# robot. Add code here to make your robot move in a square of
# dimensions 1 x 1m...
# publish whatever velocity command has been set in your code above:
self.pub.publish(self.vel)
self.rate.sleep() # maintain the loop rate @ 10 hz
if __name__ == "__main__":
node = Square()
node.main()
Alternative Approach: Waypoint Tracking¶
A square motion path can be fully defined by the coordinates of its four corners, and we can make the robot move to each of these corners one-by-one, using its odometry system to monitor its real-time position, and adapting linear and angular velocities accordingly.
This is slightly more complicated, and you might want to wait until you have a bit more experience with ROS before tackling it this way (we'll also cover this in the COM2009 lecture course).