Skip to main content

Chapter 5: Putting It All Together: Building a ROS 2 Application

Overview: The Big Idea

In the previous chapters, we have learned about the fundamental building blocks of ROS 2: nodes, topics, services, and actions. Now it's time to put all of that knowledge into practice and build a complete ROS 2 application from scratch. In this chapter, we will create a simple application that uses a service to set a goal for the turtlesim turtle and then uses a publisher to control the turtle's movement to that goal.

Key Concepts

This chapter will reinforce the concepts we have already learned and introduce a few new ones:

  • Launch Files: Launch files are a way to start up multiple ROS 2 nodes at once. They are written in Python and provide a convenient way to manage complex applications.
  • Parameters: Parameters are a way to configure your nodes at runtime without having to recompile them.
  • Turtlesim: Turtlesim is a simple and fun simulator that is included with ROS 2. It is a great tool for learning and experimenting with ROS 2 concepts.

Real-World Use Cases

The application we will build in this chapter is a simplified version of a real-world robotics task. The ability to set a goal and then control a robot to that goal is a fundamental capability for many robotic systems.

  • Mobile Robot Navigation: A mobile robot might be given a goal to navigate to a specific location in a warehouse.
  • Robotic Arm Control: A robotic arm might be given a goal to move to a specific position to pick up an object.
  • Drone Flight: A drone might be given a goal to fly to a specific waypoint.

Technical Explanations

Application Architecture

Our application will consist of two nodes:

  1. Goal Setter Node: This node will provide a service that allows a user to set a goal position for the turtle.
  2. Turtle Controller Node: This node will subscribe to the goal topic and publish velocity commands to the turtle to move it to the goal.

We will also create a launch file to start both nodes and the turtlesim simulator at the same time.

Diagram Description: A diagram of the application architecture. There is a "Goal Setter Node" and a "Turtle Controller Node." The Goal Setter Node provides a service called "/set_goal". A user can call this service to set a goal. The Goal Setter Node then publishes the goal to a "/goal" topic. The Turtle Controller Node subscribes to the "/goal" topic and publishes velocity commands to the "/turtle1/cmd_vel" topic. The "turtlesim_node" subscribes to the "/turtle1/cmd_vel" topic and moves the turtle.

Code Samples

Here are the code samples for our two nodes and the launch file.

Goal Setter Node

import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Point
from your_package_name.srv import SetGoal

class GoalSetter(Node):

def __init__(self):
super().__init__('goal_setter')
self.publisher_ = self.create_publisher(Point, 'goal', 10)
self.srv = self.create_service(SetGoal, 'set_goal', self.set_goal_callback)

def set_goal_callback(self, request, response):
goal = Point()
goal.x = request.x
goal.y = request.y
goal.z = 0.0
self.publisher_.publish(goal)
self.get_logger().info('New goal set: x=%f, y=%f' % (goal.x, goal.y))
response.success = True
return response

def main(args=None):
rclpy.init(args=args)
goal_setter = GoalSetter()
rclpy.spin(goal_setter)
rclpy.shutdown()

if __name__ == '__main__':
main()

Turtle Controller Node

import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Point, Twist
from turtlesim.msg import Pose
import math

class TurtleController(Node):

def __init__(self):
super().__init__('turtle_controller')
self.goal_subscription = self.create_subscription(
Point,
'goal',
self.goal_callback,
10)
self.pose_subscription = self.create_subscription(
Pose,
'/turtle1/pose',
self.pose_callback,
10)
self.velocity_publisher = self.create_publisher(Twist, '/turtle1/cmd_vel', 10)
self.goal = None
self.current_pose = None

def goal_callback(self, msg):
self.goal = msg

def pose_callback(self, msg):
self.current_pose = msg
if self.goal is not None:
self.move_to_goal()

def move_to_goal(self):
if self.current_pose is None or self.goal is None:
return

dx = self.goal.x - self.current_pose.x
dy = self.goal.y - self.current_pose.y
distance = math.sqrt(dx*dx + dy*dy)

if distance > 0.1:
vel_msg = Twist()
vel_msg.linear.x = 1.5 * distance
angle_to_goal = math.atan2(dy, dx)
vel_msg.angular.z = 6.0 * (angle_to_goal - self.current_pose.theta)
self.velocity_publisher.publish(vel_msg)
else:
vel_msg = Twist()
vel_msg.linear.x = 0.0
vel_msg.angular.z = 0.0
self.velocity_publisher.publish(vel_msg)
self.goal = None

def main(args=None):
rclpy.init(args=args)
turtle_controller = TurtleController()
rclpy.spin(turtle_controller)
rclpy.shutdown()

if __name__ == '__main__':
main()

Launch File

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
return LaunchDescription([
Node(
package='turtlesim',
executable='turtlesim_node',
name='turtlesim'
),
Node(
package='your_package_name',
executable='goal_setter_node',
name='goal_setter'
),
Node(
package='your_package_name',
executable='turtle_controller_node',
name='turtle_controller'
)
])

Summary

In this chapter, we have built a complete ROS 2 application from the ground up. We have used our knowledge of nodes, topics, and services to create a simple but functional application that controls the turtlesim turtle. We have also learned about launch files and how they can be used to manage complex applications.

This concludes Module 1 of the book. You now have a solid understanding of the fundamentals of ROS 2 and are ready to move on to the next module, where we will learn about creating digital twins of our robots in simulation.