ROS 2 launch files
We will create three launch files.
control simulation
forward controller (fake & real)
diff drive controller (fake & real)
To do this, we need to create a launch directory in our package.
mkdir launch
We also need to tell CMakeLists.txt to install the launch files. Therefore, add the launch directory in the install directive.
Launch control simulation
In this section we will create a launch file that will start a very simple simulation of the system on hardware interface level.
The launch file will start the following nodes:
controller_manager
rviz
joint_state_broadcaster
robot_state_publisher
robby_base_controller (diff_drive_controller)
The launch file will use the mock_components/GenericSystem plugin. This means that the robot system is only simulated on hardware interface level.
The following launch file will do the trick, paste the contents into a file called control_simulation.launch.py in the launch directory.
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, RegisterEventHandler
from launch.conditions import IfCondition
from launch.event_handlers import OnProcessExit
from launch.substitutions import Command, FindExecutable, PathJoinSubstitution, LaunchConfiguration
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageShare
def generate_launch_description():
# Declare arguments
declared_arguments = []
# Initialize Arguments
gui = true
use_mock_hardware = true
# Load xacro file
robot_description_content = Command(
[
PathJoinSubstitution([FindExecutable(name="xacro")]),
" ",
PathJoinSubstitution(
[FindPackageShare("my_robot_description"), "urdf", "robby.system.xacro"]
),
" ",
"use_mock_hardware:=",
use_mock_hardware,
]
)
robot_description = {"my_robot_description": robot_description_content}
# Load controllers configuration
robot_controllers = PathJoinSubstitution(
[
FindPackageShare("my_robot_description"),
"config/robby",
"ros2_controllers.yaml",
]
)
# Launch controller_manager
control_node = Node(
package="controller_manager",
executable="ros2_control_node",
parameters=[robot_description, robot_controllers],
output="both",
)
# Launch robot_state_publisher
robot_state_pub_node = Node(
package="robot_state_publisher",
executable="robot_state_publisher",
output="both",
parameters=[robot_description],
remappings=[
("/diff_drive_controller/cmd_vel_unstamped", "/cmd_vel"),
],
)
# Launch rviz
rviz_node = Node(
package="rviz2",
executable="rviz2",
name="rviz2",
output="log",
condition=IfCondition(gui),
)
# Spawn joint state broadcaster controller
joint_state_broadcaster_spawner = Node(
package="controller_manager",
executable="spawner",
arguments=["joint_state_broadcaster", "--controller-manager", "/controller_manager"],
)
# Spawn diff drive controller
robot_controller_spawner = Node(
package="controller_manager",
executable="spawner",
arguments=["robby_base_controller", "--controller-manager", "/controller_manager"],
)
# Delay rviz start after `joint_state_broadcaster`
delay_rviz_after_joint_state_broadcaster_spawner = RegisterEventHandler(
event_handler=OnProcessExit(
target_action=joint_state_broadcaster_spawner,
on_exit=[rviz_node],
)
)
# Delay start of robot_controller after `joint_state_broadcaster`
delay_robot_controller_spawner_after_joint_state_broadcaster_spawner = RegisterEventHandler(
event_handler=OnProcessExit(
target_action=joint_state_broadcaster_spawner,
on_exit=[robot_controller_spawner],
)
)
nodes = [
control_node,
robot_state_pub_node,
joint_state_broadcaster_spawner,
delay_rviz_after_joint_state_broadcaster_spawner,
delay_robot_controller_spawner_after_joint_state_broadcaster_spawner,
keyboard_teleop,
]
return LaunchDescription(declared_arguments + nodes)
You can now build your package and launch the simulation.
colcon build --packages-select my_robot_description
ros2 launch my_robot_description control_simulation.launch.py
Open another terminal and source your workspace and ros2 installation.
source /opt/ros/rolling/setup.bash
source ~/ros2_ws/install/setup.bash
Now you can control the robot with the keyboard, if you run the following command:
ros2 run teleop_twist_keyboard teleop_twist_keyboard
You should see the robot moving in rviz. This simulation is however very basic and does not include CANopen communication and does not help with validating the system.
Launch forward controller
In this section we will create a launch file that will start a forward command controller. This controller will be able to control the robot by writing velocity to a topic. The launch file will be able to either run with fake CANopen slaves or with real CANopen slaves.
The launch file will start the following nodes:
controller_manager
joint_state_broadcaster
robby_forward_controller
device_container
master
left_drive
right_drive
fake_left_drive
fake_right_drive
robot_state_publisher
rviz2
This system can be launch with the following launch file. Paste the following code into a file called forward_controller.launch.py in the launch directory.
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, RegisterEventHandler, TimerAction
from launch.substitutions import Command, FindExecutable, LaunchConfiguration, PathJoinSubstitution
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageShare
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.event_handlers import OnProcessExit
from launch.conditions import UnlessCondition
def generate_launch_description():
# Declare arguments
arg_use_real_hardware = DeclareLaunchArgument(
"use_real_hardware",
default_value="false",
description="Start robot with real hardware.",
)
arg_can_interface_name = DeclareLaunchArgument(
"can_interface_name",
default_value="vcan0",
description="Use this can interface for communication.",
)
can_interface_name = LaunchConfiguration("can_interface_name")
use_real_hardware = LaunchConfiguration("use_real_hardware")
# Load xacro file
robot_description_content = Command(
[
PathJoinSubstitution([FindExecutable(name="xacro")]),
" ",
PathJoinSubstitution(
[
FindPackageShare("robby_description"),
"urdf",
"robby.system.xacro",
]
),
" ",
"use_mock_hardware:=", "false", " ",
"can_interface_name:=", can_interface_name
]
)
robot_description = {"robot_description": robot_description_content}
# Load controllers configuration
robot_control_config = PathJoinSubstitution(
[FindPackageShare("robby_description"), "config/robby", "ros2_controllers.yaml"]
)
# Launch controller_manager
control_node = Node(
package="controller_manager",
executable="ros2_control_node",
parameters=[robot_description, robot_control_config],
output="screen",
)
# Spawn joint state broadcaster controller
joint_state_broadcaster_spawner = Node(
package="controller_manager",
executable="spawner",
arguments=["joint_state_broadcaster", "--controller-manager", "/controller_manager"],
)
# Spawn forward controller
robot_controller_spawner = Node(
package="controller_manager",
executable="spawner",
arguments=["robby_forward_controller", "--controller-manager", "/controller_manager"],
)
# Launch robot_state_publisher
robot_state_publisher_node = Node(
package="robot_state_publisher",
executable="robot_state_publisher",
output="both",
parameters=[robot_description],
)
# Launch rviz
rviz_node = Node(
package="rviz2",
executable="rviz2",
name="rviz2",
output="log"
)
# Load CANopen slave configuration file
slave_config = PathJoinSubstitution(
[FindPackageShare("robby_description"), "config/robby", "TMCM-1270.eds"]
)
# Find CANopen fake slave launch file
slave_launch = PathJoinSubstitution(
[FindPackageShare("canopen_fake_slaves"), "launch", "cia402_slave.launch.py"]
)
# Launch fake slaves for the drives
slave_node_1 = IncludeLaunchDescription(
PythonLaunchDescriptionSource(slave_launch),
launch_arguments={
"node_id": "3",
"node_name": "fake_left_drive",
"slave_config": slave_config,
}.items(),
condition=UnlessCondition(use_real_hardware)
)
slave_node_2 = IncludeLaunchDescription(
PythonLaunchDescriptionSource(slave_launch),
launch_arguments={
"node_id": "2",
"node_name": "fake_right_drive",
"slave_config": slave_config,
}.items(),
condition=UnlessCondition(use_real_hardware)
)
# Handle launch sequence
delay_robot_controller_spawner_after_joint_state_broadcaster_spawner = RegisterEventHandler(
event_handler=OnProcessExit(
target_action=joint_state_broadcaster_spawner,
on_exit=[robot_controller_spawner],
)
)
delay_rviz_after_joint_state_broadcaster_spawner = RegisterEventHandler(
event_handler=OnProcessExit(
target_action=joint_state_broadcaster_spawner,
on_exit=[rviz_node],
)
)
delay_master_launch = TimerAction(
period=1.0,
actions=[
control_node,
joint_state_broadcaster_spawner,
delay_robot_controller_spawner_after_joint_state_broadcaster_spawner,
delay_rviz_after_joint_state_broadcaster_spawner]
)
delay_slave_launch = TimerAction(
period=2.0,
actions=[
slave_node_1,slave_node_2]
)
nodes_to_start = [
arg_can_interface_name,
arg_use_real_hardware,
robot_state_publisher_node,
delay_master_launch,
delay_slave_launch
]
return LaunchDescription(nodes_to_start)
You can now build your package. If you want to run the launch you will have to first setup the virtual can bus.
sudo modprobe vcan
sudo ip link add dev vcan0 type vcan
sudo ip link set vcan0 txqueuelen 1000
sudo ip link set up vcan0
Then you can run the launch file.
ros2 launch forward_command.launch.py
In another terminal with rolling sourced you can now send commands to the robot.
ros2 topic pub /robby_forward_controller/commands \
std_msgs/msg/Float64MultiArray "data: [10.0, 10.0]
In rviz you can now see the wheels moving.
Launch differential drive controller
In order to run the robot with a differential drive controller, you can create a launch file called diff_drive.launch.py in the launch directory. Copy the contents of the forward_command.launch.py into this new file and then change the following lines:
robot_controller_spawner = Node(
package="controller_manager",
executable="spawner",
arguments=["robby_forward_controller", "--controller-manager", "/controller_manager"],
)
Cange robby_forward_controller
to robby_base_controller
. Now you can
build your workspace and then your are all setup to control the robot with
a differential drive controller.
ros2 launch my_robot_description diff_drive.launch.py
In another terminal with rolling sourced you can use the teleop_twist_keyboard
node to
control the robot. To do this open another terminal and source rolling.
source /opt/ros/rolling/setup.bash
ros2 run teleop_twist_keyboard teleop_twist_keyboard --ros-args -r /cmd_vel:=/robby_base_controller/cmd_vel_unstamped
This is it, you can now run the robot with a differential drive controller.