launch 基本使用流程
配置
launch 在使用 python 时要引用很多头文件,非常不方便,所以建议先配置代码片段
"ros2 launch py":{
"prefix": "ros2_launch_py",
"body": [
"from launch import LaunchDescription",
"from launch_ros.actions import Node",
"# 封装终端指令相关类--------------",
"# from launch.actions import ExecuteProcess",
"# from launch.substitutions import FindExecutable",
"# 参数声明与获取-----------------",
"# from launch.actions import DeclareLaunchArgument",
"# from launch.substitutions import LaunchConfiguration",
"# 文件包含相关-------------------",
"# from launch.actions import IncludeLaunchDescription",
"# from launch.launch_description_sources import PythonLaunchDescriptionSource",
"# 分组相关----------------------",
"# from launch_ros.actions import PushRosNamespace",
"# from launch.actions import GroupAction",
"# 事件相关----------------------",
"# from launch.event_handlers import OnProcessStart, OnProcessExit",
"# from launch.actions import ExecuteProcess, RegisterEventHandler,LogInfo",
"# 获取功能包下 share 目录路径-------",
"# from ament_index_python.packages import get_package_share_directory",
"",
"def generate_launch_description():",
" ",
" return LaunchDescription([])",
],
"description": "ros2 launch"
}
新建 launch 文件夹
下有 py yaml xml 三个文件夹
在Cmake里配置
install(DIRECTORY launch DESTINATION share/${PROJECT_NAME})
最终效果
cmake_minimum_required(VERSION 3.8)
project(cpp01_launch)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
install(DIRECTORY launch DESTINATION share/${PROJECT_NAME})
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# comment the line when a copyright and license is added to all source files
set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# comment the line when this package is in a git repo and when
# a copyright and license is added to all source files
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()
对于带有启动文件的功能包,最好在功能包的 package.xml 中添加对包 ros2launch 的执行时依赖:
<exec_depend>ros2launch</exec_depend>
这有助于确保在构建功能包后 ros2 launch 命令可用。它还确保可以识别不同格式的 launch 文件。
最终效果
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>cpp01_launch</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="herrscher@todo.todo">herrscher</maintainer>
<license>TODO: License declaration</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>rclcpp</depend>
<exec_depend>ros2launch</exec_depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
C++
python
from launch import LaunchDescription
from launch_ros.actions import Node
# 封装终端指令相关类--------------
# from launch.actions import ExecuteProcess
# from launch.substitutions import FindExecutable
# 参数声明与获取-----------------
# from launch.actions import DeclareLaunchArgument
# from launch.substitutions import LaunchConfiguration
# 文件包含相关-------------------
# from launch.actions import IncludeLaunchDescription
# from launch.launch_description_sources import PythonLaunchDescriptionSource
# 分组相关----------------------
# from launch_ros.actions import PushRosNamespace
# from launch.actions import GroupAction
# 事件相关----------------------
# from launch.event_handlers import OnProcessStart, OnProcessExit
# from launch.actions import ExecuteProcess, RegisterEventHandler,LogInfo
# 获取功能包下 share 目录路径-------
# from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
t1 = Node(package="turtlesim",executable="turtlesim_node",name="t1")
t2 = Node(package="turtlesim",executable="turtlesim_node",name="t2")
return LaunchDescription([t1,t2])
xml
<launch>
<node pkg="turtlesim" exec="turtlesim_node" name="t1"/>
<node pkg="turtlesim" exec="turtlesim_node" name="t2"/>
</launch>
yaml
launch:
- node:
pkg: "turtlesim"
exec: "turtlesim_node"
name: "t1"
- node:
pkg: "turtlesim"
exec: "turtlesim_node"
name: "t2"
yaml 卡缩进
python
node
配置
Cmake 和 C++配置相同
setup.py 配置
('share/' + package_name, ['launch/py/py01_hello_world.launch.py']),
('share/' + package_name, ['launch/xml/xml01_hello_world.launch.xml']),
('share/' + package_name, ['launch/yaml/yaml01_hello_world.launch.yaml']),
最终效果
from setuptools import find_packages, setup
package_name = 'py01_launch'
setup(
name=package_name,
version='0.0.0',
packages=find_packages(exclude=['test']),
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
('share/' + package_name, ['launch/py/py01_hello_world.launch.py']),
('share/' + package_name, ['launch/xml/xml01_hello_world.launch.xml']),
('share/' + package_name, ['launch/yaml/yaml01_hello_world.launch.yaml']),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='herrscher',
maintainer_email='herrscher@todo.todo',
description='TODO: Package description',
license='TODO: License declaration',
tests_require=['pytest'],
entry_points={
'console_scripts': [
],
},
)
但是酱紫配置非常麻烦,可以通过 glob 直接对全体符合条件的进行配置
from glob import glob
···
('share/' + package_name, glob("launch/py/*.launch.py")),
('share/' + package_name, glob("launch/xml/*.launch.xml")),
('share/' + package_name, glob("launch/yaml/*.launch.yaml")),
python 实现
from launch import LaunchDescription
from launch_ros.actions import Node
# 封装终端指令相关类--------------
# from launch.actions import ExecuteProcess
# from launch.substitutions import FindExecutable
# 参数声明与获取-----------------
# from launch.actions import DeclareLaunchArgument
# from launch.substitutions import LaunchConfiguration
# 文件包含相关-------------------
# from launch.actions import IncludeLaunchDescription
# from launch.launch_description_sources import PythonLaunchDescriptionSource
# 分组相关----------------------
# from launch_ros.actions import PushRosNamespace
# from launch.actions import GroupAction
# 事件相关----------------------
# from launch.event_handlers import OnProcessStart, OnProcessExit
# from launch.actions import ExecuteProcess, RegisterEventHandler,LogInfo
# 获取功能包下 share 目录路径-------
from ament_index_python.packages import get_package_share_directory
import os
"""
节点参数:
package: 被执行的程序所在的功能包
executable: 被执行的程序
name: 节点名称
namespace: 节点命名空间
remappings: 话题重映射
arguments: 传参
ros_arguments: 传参
exec_name: 设置程序标签
parameters: 节点参数
respawn: 是否重启
"""
def generate_launch_description():
turtle1 = Node(
package="turtlesim",
executable="turtlesim_node",
exec_name="my_label",
ros_arguments=["--remap","__ns:=/t2"]
# ros2 run turtlesim turtlesim_node --ros-args --remap __ns:=/t2
)
turtle2 = Node(
package="turtlesim",
executable="turtlesim_node",
name="haha",
# 方式1
# parameters=[{"background_r": 255, "background_g": 0, "background_b": 0}]
# 方式2,通过 yaml 文件绝对路径传参
# parameters=["/home/herrscher/ws02_tools/install/cpp01_launch/share/cpp01_launch/config/haha.yaml"]
# 方式3,动态获取
parameters=[os.path.join(get_package_share_directory("cpp01_launch"), "config", "haha.yaml")],
)
return LaunchDescription([turtle2])
用系统工具创建 yaml 文件
ros2 param dump haha --output-dir src/cpp01_launch/config
配置 Cmake
在 install 中加入config
install(DIRECTORY launch config DESTINATION share/${PROJECT_NAME})
获取 yaml 在 install 下的路径
~/工作空间/install/功能包/share/功能包/config/xx.yaml
执行指令
rt LaunchDescription
from launch_ros.actions import Node
# 封装终端指令相关类--------------
from launch.actions import ExecuteProcess
from launch.substitutions import FindExecutable
# 参数声明与获取-----------------
# from launch.actions import DeclareLaunchArgument
# from launch.substitutions import LaunchConfiguration
# 文件包含相关-------------------
# from launch.actions import IncludeLaunchDescription
# from launch.launch_description_sources import PythonLaunchDescriptionSource
# 分组相关----------------------
# from launch_ros.actions import PushRosNamespace
# from launch.actions import GroupAction
# 事件相关----------------------
# from launch.event_handlers import OnProcessStart, OnProcessExit
# from launch.actions import ExecuteProcess, RegisterEventHandler,LogInfo
# 获取功能包下 share 目录路径-------
# from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
turtle = Node(
package="turtlesim",
executable="turtlesim_node"
)
# 封装指令
cmd = ExecuteProcess(
# cmd=["ros2 topic echo /turtle1/pose"],
# cmd=["ros2 topic","echo","/turtle1/pose"],
cmd=[FindExecutable(name="ros2"),"topic","echo","/turtle1/pose"],
output="both",
shell=True
)
return LaunchDescription([turtle, cmd])
参数设置
from launch import LaunchDescription
from launch_ros.actions import Node
# 封装终端指令相关类--------------
# from launch.actions import ExecuteProcess
# from launch.substitutions import FindExecutable
# 参数声明与获取-----------------
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
# 文件包含相关-------------------
# from launch.actions import IncludeLaunchDescription
# from launch.launch_description_sources import PythonLaunchDescriptionSource
# 分组相关----------------------
# from launch_ros.actions import PushRosNamespace
# from launch.actions import GroupAction
# 事件相关----------------------
# from launch.event_handlers import OnProcessStart, OnProcessExit
# from launch.actions import ExecuteProcess, RegisterEventHandler,LogInfo
# 获取功能包下 share 目录路径-------
# from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
# 声明参数
bg_r = DeclareLaunchArgument(name="backg_r",default_value="255")
bg_g = DeclareLaunchArgument(name="backg_g",default_value="255")
bg_b = DeclareLaunchArgument(name="backg_b",default_value="255")
turtle = Node(
package="turtlesim",
executable="turtlesim_node",
parameters=[{"background_r": LaunchConfiguration("backg_r"),
"background_g": LaunchConfiguration("backg_g"),
"background_b": LaunchConfiguration("backg_b")}],
)
return LaunchDescription([bg_r, bg_g, bg_b, turtle])
启动时可以直接对声明的参数调参
ros2 launch cpp01_launch py04_args.launch.py backg_r:=255 backg_g:=0 backg_b:=0
文件包含
from launch import LaunchDescription
from launch_ros.actions import Node
# 封装终端指令相关类--------------
# from launch.actions import ExecuteProcess
# from launch.substitutions import FindExecutable
# 参数声明与获取-----------------
# from launch.actions import DeclareLaunchArgument
# from launch.substitutions import LaunchConfiguration
# 文件包含相关-------------------
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
# 分组相关----------------------
# from launch_ros.actions import PushRosNamespace
# from launch.actions import GroupAction
# 事件相关----------------------
# from launch.event_handlers import OnProcessStart, OnProcessExit
# from launch.actions import ExecuteProcess, RegisterEventHandler,LogInfo
# 获取功能包下 share 目录路径-------
from ament_index_python.packages import get_package_share_directory
import os
def generate_launch_description():
include = IncludeLaunchDescription(
launch_description_source=PythonLaunchDescriptionSource(
launch_file_path=os.path.join(
get_package_share_directory("cpp01_launch"),
"launch/py",
"py04_args.launch.py"
)
),launch_arguments=[("backg_r","255"),
("backg_g","100"),
("backg_b","200")
]
)
return LaunchDescription([include])
分组实现
from launch import LaunchDescription
from launch_ros.actions import Node
# 封装终端指令相关类--------------
# from launch.actions import ExecuteProcess
# from launch.substitutions import FindExecutable
# 参数声明与获取-----------------
# from launch.actions import DeclareLaunchArgument
# from launch.substitutions import LaunchConfiguration
# 文件包含相关-------------------
# from launch.actions import IncludeLaunchDescription
# from launch.launch_description_sources import PythonLaunchDescriptionSource
# 分组相关----------------------
from launch_ros.actions import PushRosNamespace
from launch.actions import GroupAction
# 事件相关----------------------
# from launch.event_handlers import OnProcessStart, OnProcessExit
# from launch.actions import ExecuteProcess, RegisterEventHandler,LogInfo
# 获取功能包下 share 目录路径-------
# from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
t1 = Node(package="turtlesim",
executable="turtlesim_node",
name="t1")
t2 = Node(package="turtlesim",
executable="turtlesim_node",
name="t2")
t3 = Node(package="turtlesim",
executable="turtlesim_node",
name="t3")
g1 = GroupAction(actions=[PushRosNamespace("g1"),t1,t2])
g2 = GroupAction(actions=[PushRosNamespace("g2"),t3])
return LaunchDescription([g1,g2])
xml,yaml
<launch>
<!-- <node
pkg="turtlesim"
exec="turtlesim_node"
name="t1"
namespace="ns_1"
exec_name="my_label_xxxx"
respawn="True"
/> -->
<!-- <node pkg="turtlesim" exec="turtlesim_node" name="t1">
<param name="background_r" value="100"/>
<param name="background_g" value="50"/>
<param name="background_b" value="90"/>
<param from="$(find-pkg-share cpp01_launch)/config/xixi.yaml"/>
</node> -->
<node pkg="turtlesim" exec="turtlesim_node" name="t1" ros_args="--remap __ns:=/xxx/yyy"/>
</launch>
launch:
- node:
pkg: "turtlesim"
exec: "turtlesim_node"
name: "t1"
namespace: "ns"
exec_name: "my_yaml"
respawn: "true"
param:
# -
# name: "background_r"
# value: 100
# -
# name: "background_g"
# value: 10
# -
# name: "background_b"
# value: 10
-
from: "$(find-pkg-share cpp01_launch)/config/gaga.yaml"
ros_args: "--remap __ns:=/ns"
# args: "--ros-args --remap __ns:=/ns"
这里设置了命名空间,在导入 yaml 时需要加上命名空间
/ns/t1:
ros__parameters:
background_b: 0
background_g: 0
background_r: 255
qos_overrides:
/parameter_events:
publisher:
depth: 1000
durability: volatile
history: keep_last
reliability: reliable
use_sim_time: false
执行指令
<launch>
<!-- 执行终端指令 -->
<executable cmd="ros2 run turtlesim turtlesim_node" output="both"/>
</launch>
launch:
- executable:
cmd: "ros2 run turtlesim turtlesim_node"
output: "both"
参数设置
<launch>
<arg name="bg_r" default="100"/>
<node pkg="turtlesim" exec="turtlesim_node">
<param name="background_r" value="$(var bg_r)" />
</node>
</launch>
launch:
- arg:
name: "bg_b"
default: "0"
- node:
pkg: "turtlesim"
exec: "turtlesim_node"
param:
-
name: "background_b"
value: "$(var bg_b)"
分组设置
<launch>
<group>
<push-ros-namespace namespace="g1"/>
<!-- 设置被包含节点 -->
<node pkg="turtlesim" exec="turtlesim_node" name="t1"/>
<node pkg="turtlesim" exec="turtlesim_node" name="t2"/>
</group>
<group>
<push-ros-namespace namespace="g2"/>
<!-- 设置被包含节点 -->
<node pkg="turtlesim" exec="turtlesim_node" name="t3"/>
</group>
</launch>
launch:
- group:
- push-ros-namespace:
namespace: "g1"
- node:
pkg: "turtlesim"
exec: "turtlesim_node"
name: "txxx1"
- node:
pkg: "turtlesim"
exec: "turtlesim_node"
name: "txxx2"
- group:
- push-ros-namespace:
namespace: "g2"
- node:
pkg: "turtlesim"
exec: "turtlesim_node"
name: "txxx3"
文件包含
<launch>
<!-- 动态导入参数 -->
<let name="bg_r" value="100"/>
<!-- 文件包含 -->
<include file="$(find-pkg-share cpp01_launch)/launch/xml/xml04_args.launch.xml"/>
</launch>
launch:
- let:
name: "bg_b"
value: "100"
- include:
file: "$(find-pkg-share cpp01_launch)/launch/yaml/yaml04_args.launch.yaml"
rosbag2
新建功能包
C++ 依赖rclcpp rosbag2_cpp geometry_msgs
Python 依赖rclpy rosbag2_py geometry_msgs
命令行工具
新建 bag 文件夹
ros2 bag record /turtle1/cmd_vel -o bag_cmd
录制 /turtle1/cmd_vel 话题,输出名称为 bag_cmd
ros2 bag play bag_cmd
回放 bag_cmd
ros2 bag info bag_cmd
获取相关消息
C++
#include "rclcpp/rclcpp.hpp"
#include "geometry_msgs/msg/twist.hpp"
#include "rosbag2_cpp/writer.hpp"
class SimpleBagRecorder: public rclcpp::Node{
public:
SimpleBagRecorder():Node("simple_bag_recorder_node_cpp"){
RCLCPP_INFO(this->get_logger(),"消息录制对象创建");
// 创建录制对象
writer_ = std::make_unique<rosbag2_cpp::Writer>();
// 设置存储路径
writer_->open("my_bag");
// 写数据
sub_ = this->create_subscription<geometry_msgs::msg::Twist>("/turtle1/cmd_vel",10,std::bind(&SimpleBagRecorder::do_write_msg,this,std::placeholders::_1));
}
private:
void do_write_msg(std::shared_ptr<rclcpp::SerializedMessage> msg){
//write(std::shared_ptr<rclcpp::SerializedMessage> message,
// const std::string &topic_name,
// const std::string &type_name,
// const rclcpp::Time &time)
writer_->write(msg,"/turtle1/cmd_vel","geometry_msgs/msg/Twist",this->now());
RCLCPP_INFO(this->get_logger(),"写入数据成功");
}
std::unique_ptr<rosbag2_cpp::Writer> writer_;
rclcpp::Subscription<geometry_msgs::msg::Twist>::SharedPtr sub_;
};
int main(int argc, char const *argv[])
{
rclcpp::init(argc,argv);
rclcpp::spin(std::make_shared<SimpleBagRecorder>());
rclcpp::shutdown();
return 0;
}
回放
#include "rclcpp/rclcpp.hpp"
#include "rosbag2_cpp/reader.hpp"
#include "geometry_msgs/msg/twist.hpp"
class SimpleBagPlayer: public rclcpp::Node{
public:
SimpleBagPlayer():Node("simple_bag_player_node_cpp"){
RCLCPP_INFO(this->get_logger(),"消息回放对象创建");
// 创建回放对象
reader_ = std::make_unique<rosbag2_cpp::Reader>();
// 设置被读取文件
reader_->open("my_bag");
// 读取数据
while ( reader_->has_next()){
auto twist = reader_->read_next<geometry_msgs::msg::Twist>();
RCLCPP_INFO(this->get_logger(),"线速度:%.2f,角速度:%.2f",twist.linear.x,twist.linear.z);
}
reader_->close();
}
private:
std::unique_ptr<rosbag2_cpp::Reader> reader_;
};
int main(int argc, char const *argv[])
{
rclcpp::init(argc,argv);
rclcpp::spin(std::make_shared<SimpleBagPlayer>());
rclcpp::shutdown();
return 0;
}