Commander API
What is the Commander API
The Commander API in ROS 2 Navigation Stack (Nav2) is a Python interface for programmatically controlling a robot’s navigation behavior. It simplifies interaction with the Nav2 stack by providing an intuitive, high-level API for sending navigation goals, controlling the robot, and monitoring progress.
Installing
sudo apt install ros-humble-tf-transformations
sudo apt install python3-transforms3d
Tips and Tricks
Here are a few tips and tricks I find useful
Getting Click Coordinates
It might seem hard to get the coordinates to navigate to. If you launch the navigation package and then open a terminal and subscribe to /clicked_point
then you can go to the top bar in rviz2 and click on publish points and then wherever you click next will get published on the /clicked_point
topic.
ros2 topic echo /clicked_point
Getting Mouse Position
Another way to get the coordinates on a map in rviz2 is to use the Focus Camera
button on the top bar of rviz2 to display the coordinates of the mouse in the bottom left-hand corner.
Python Script
Setting Initial Pose
To set and initial pose use the setInitialPose()
command to send the initial pose to the correct ROS2 node.
| q_x, q_y, q_z, q_w = tf_transformations.quaternion_from_euler(0.0, 0.0, 0.0)
initial_pose = PoseStamped()
initial_pose.header.frame_id ='map'
initial_pose.header.stamp = navigator.get_clock().now().to_msg()
initial_pose.pose.position.x = position_x
initial_pose.pose.position.y = position_y
initial_pose.pose.position.z = 0.0
initial_pose.pose.orientation.x = q_x
initial_pose.pose.orientation.y = q_y
initial_pose.pose.orientation.z = q_z
initial_pose.pose.orientation.w = q_w
nav.goToPose(initial_pose)
|
Navigating to a Goal
To navigate to a goal first get the goal_pose
then use the goToPose
command to navigate to that specific pose.
| def create_pose_stamped(navigator: BasicNavigator, position_x: float, position_y: float, orientation_z: float):
if math.fabs(orientation_z) > 2*math.pi:
# convert from degrees to radian
orientation_z = orientation_z * math.pi / 180
q_x, q_y, q_z, q_w = tf_transformations.quaternion_from_euler(0.0, 0.0, orientation_z)
pose = PoseStamped()
pose.header.frame_id ='map'
pose.header.stamp = navigator.get_clock().now().to_msg()
pose.pose.position.x = position_x
pose.pose.position.y = position_y
pose.pose.position.z = 0.0
pose.pose.orientation.x = q_x
pose.pose.orientation.y = q_y
pose.pose.orientation.z = q_z
pose.pose.orientation.w = q_w
return pose
# --- Send Nav2 goal
goal_pose = create_pose_stamped(nav, 1.72, 0.37, 1.57)
nav.goToPose(goal_pose)
# --- Wait for Nav2
while not nav.isTaskComplete():
feedback = nav.getFeedback()
print(f"Distance remaining: {feedback.distance_remaining:.2f} meters")
time.sleep(1)
# --- Print Success
print(nav.getResult())
|
Navigating Waypoints
To navigate to a list of points use the followWaypoints
command in the commander API and give it a array of PoseStamped()
.
| def create_pose_stamped(navigator: BasicNavigator, position_x: float, position_y: float, orientation_z: float):
if math.fabs(orientation_z) > 2*math.pi:
# convert from degrees to radian
orientation_z = orientation_z * math.pi / 180
q_x, q_y, q_z, q_w = tf_transformations.quaternion_from_euler(0.0, 0.0, orientation_z)
pose = PoseStamped()
pose.header.frame_id ='map'
pose.header.stamp = navigator.get_clock().now().to_msg()
pose.pose.position.x = position_x
pose.pose.position.y = position_y
pose.pose.position.z = 0.0
pose.pose.orientation.x = q_x
pose.pose.orientation.y = q_y
pose.pose.orientation.z = q_z
pose.pose.orientation.w = q_w
return pose
# --- Send Follow waypoints
goal_pose1 = create_pose_stamped(nav, 0.226, 2.0, 180)
goal_pose2 = create_pose_stamped(nav, -0.55, 0.58, 270)
goal_pose3 = create_pose_stamped(nav, 0.59, -1.81, 0)
goal_pose4 = create_pose_stamped(nav, -2.0, -0.5, 0)
waypoints = [goal_pose1, goal_pose2, goal_pose3, goal_pose4]
nav.followWaypoints(waypoints)
# --- Wait for Nav2
while not nav.isTaskComplete():
feedback = nav.getFeedback()
print(feedback)
time.sleep(1)
# --- Print Success
print(nav.getResult())
|
Full Script
Here is a full example of a python script that drives the Turtlebot3 around its world:
nav2_turtlebot3_loop.py |
---|
| #!/usr/bin/env python3
import rclpy
from nav2_simple_commander.robot_navigator import BasicNavigator
from geometry_msgs.msg import PoseStamped
import tf_transformations
import time, math
def create_pose_stamped(navigator: BasicNavigator, position_x: float, position_y: float, orientation_z: float):
if math.fabs(orientation_z) > 2*math.pi:
# convert from degrees to radian
orientation_z = orientation_z * math.pi / 180
q_x, q_y, q_z, q_w = tf_transformations.quaternion_from_euler(0.0, 0.0, orientation_z)
pose = PoseStamped()
pose.header.frame_id ='map'
pose.header.stamp = navigator.get_clock().now().to_msg()
pose.pose.position.x = position_x
pose.pose.position.y = position_y
pose.pose.position.z = 0.0
pose.pose.orientation.x = q_x
pose.pose.orientation.y = q_y
pose.pose.orientation.z = q_z
pose.pose.orientation.w = q_w
return pose
def main():
# --- Init
rclpy.init()
nav = BasicNavigator()
# --- Set initial Pose
initial_pose = create_pose_stamped(nav, -2.0, -0.5, 0)
nav.setInitialPose(initial_pose)
# --- Wait for Nav2
nav.waitUntilNav2Active()
# --- Send Nav2 goal
goal_pose = create_pose_stamped(nav, 1.72, 0.37, 1.57)
nav.goToPose(goal_pose)
# --- Wait for Nav2
while not nav.isTaskComplete():
feedback = nav.getFeedback()
print(f"Distance remaining: {feedback.distance_remaining:.2f} meters")
time.sleep(1)
# --- Print Success
print(nav.getResult())
# --- Send Follow waypoints
goal_pose1 = create_pose_stamped(nav, 0.226, 2.0, 180)
goal_pose2 = create_pose_stamped(nav, -0.55, 0.58, 270)
goal_pose3 = create_pose_stamped(nav, 0.59, -1.81, 0)
goal_pose4 = create_pose_stamped(nav, -2.0, -0.5, 0)
waypoints = [goal_pose1, goal_pose2, goal_pose3, goal_pose4]
nav.followWaypoints(waypoints)
# --- Wait for Nav2
while not nav.isTaskComplete():
feedback = nav.getFeedback()
print(feedback)
time.sleep(1)
# --- Print Success
print(nav.getResult())
# --- Shutdown
nav.lifecycleShutdown()
rclpy.shutdown()
if __name__ == '__main__':
main()
|
More Help
For additional help visit this site.