ROS2(Galactic)中等程度者教程
使用rosdep管理依赖关系
rosdep是什么
rosdep是ROS管理依赖项的实用程序,可以与ROS包和外部库一起工作。
rosdep是一个命令行实用程序,用于识别和安装构建或安装包的依赖项。
在以下情况下可以调用或被调用:
- 构建工作空间并需要适当的依赖关系来构建其中的包
- 安装软件包(例如
sudo-apt-Install ros galactic demo nodes cpp
)以检查其执行所需的依赖性 - 还有更多!
它能够处理单个包或包目录(例如工作区)。
关于一点package.xml文件
包的package.xml文件包含一组依赖项。
此文件中的依赖项通常称为“rosdep-keys”。
在这些标签<depend>
、<test_depend>``<exec_depend>
、<build_depend>
和<build_export_depend>
中表示。
它们指定在什么情况下需要每个依赖项。
- 对于仅用于测试代码的依赖项(例如gtest),请使用
test_depend
。 - 对于仅用于构建代码的依赖项,请使用
build_depend
。 - 对于代码导出的头所需的依赖项,请使用
build_export_depend
。 - 对于仅在运行代码时使用的依赖项,请使用
exec_depend
。 - 出于混合目的,请使用
depend
,它涵盖了构建、导出和执行时的依赖关系。
这些依赖项在包中的package.xml文件手动填充,并且应该是它所需的任何非内置库和包的详尽列表。
如何使用rosdep工具
现在我们对rosdep,package.xml和rosdistro有了一些了解,我们已经准备好使用该工具本身了!
首先,如果这是第一次使用rosdep,则必须通过以下方式进行初始化:
sudo rosdep init
rosdep update
这将初始化rosdep,更新将更新本地缓存的rosdistro索引。
偶尔更新rosdep以获取最新索引是一个好主意。
最后,我们可以运行rosdep install
来安装依赖项。
通常,这是在一个工作区上运行的,在一次调用中有许多包来安装所有依赖项。
如果在包含源代码的目录src的工作区的根目录中,那么对它的调用将如下所示。
rosdep install --from-paths src -y --ignore-src
分解如下:
--from-paths src
指定要检查package.xml文件解析keys的路径。-y
表示对包管理器的所有提示都默认为“是”,以便在没有提示的情况下安装--ignore-src
意味着忽略安装依赖项,即使存在rosdep键,如果包本身也在工作区中。
还有其他参数和选项可用。
使用rosdep -h
查看它们。
创建动作
背景
您以前在了解动作教程中理解了动作。
与其他通信类型及其各自的接口(topics/msg和services/srv)一样,您也可以在包中自定义定义动作。
本教程将向您展示如何定义和构建一个可以与下一教程中编写的动作服务器和动作客户端一起使用的动作。
前置条件
您应该安装了ROS 2和colcon。
设置工作区并创建名为action_tutorials_interfaces的包:
(请记住先获取ROS 2安装的源代码。)
cd ros2_ws/src
ros2 pkg create action_tutorials_interfaces
任务
-
定义一个动作
动作的实例通常称为目标。
假设我们想定义一个新的动作“Fibonacci”来计算斐波那契序列。
目标请求是我们要计算的斐波那契序列的顺序,结果是最终序列,反馈是迄今为止计算的部分序列。
在我们的ROS 2包action_tutorials_interfaces中创建一个动作目录:
cd action_tutorials_interfaces
mkdir action
在action目录中,创建一个名为Fibonacci.action的文件,其中包含以下内容:
int32 order
---
int32[] sequence
---
int32[] partial_sequence
动作在以下格式的.action文件中定义:
动作定义由三个消息定义组成,由---
分隔。- 请求消息从动作客户端发送到启动新目标的动作服务器。
- 当目标完成时,结果消息从动作服务器发送到动作客户端。
- 反馈消息定期从动作服务器发送到动作客户端,其中包含有关目标的更新。
-
构建一个动作
在我们可以在代码中使用新的斐波那契动作类型之前,我们必须将定义传递给rosidl代码生成管道。
这可以通过向位于action_tutorials_interfaces中的CMakeList.txt在ament_package()
行之前添加以下行来完成:
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME} "action/Fibonacci.action")
我们还应该在package.xml中添加所需的依赖项:
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<depend>action_msgs</depend>
<member_of_group>rosidl_interface_packages</member_of_group>
注意,我们需要依赖action_msgs
,因为动作定义包括额外的元数据(例如目标ID)。
我们现在应该能够构建包含斐波那契动作定义的包:
cd ~/ros2_ws
colcon build --packages-select action_tutorials_interfaces
我们完了!
按照惯例,操作类型将以其包名称和单词action作为前缀。
因此,当我们想引用我们的新动作时,它将具有全名action_tutorials_interfaces/action/Fibonacci。
我们可以使用命令行工具检查我们的操作是否成功构建:
. install/setup.bash
ros2 interface show action_tutorials_interfaces/action/Fibonacci
您应该会看到Fibonacci动作定义打印到屏幕上。
总结
在本教程中,您学习了动作定义的结构。
您还学习了如何使用CMakeLists.txt和package.xml正确构建新的操作界面,以及如何验证成功构建。
编写动作服务器和客户端(C++)
背景
动作是ROS中异步通信的一种形式。
动作客户端向动作服务器发送目标请求。
动作服务器向动作客户端发送目标反馈和结果。
任务
-
创建action_tutorials_cpp包
正如我们在创建包教程中看到的,我们需要创建一个新的包来保存和支持我们的C++代码。- 创建action_tutorials_cpp包
进入您在上一教程中创建的动作工作区,并为C++动作服务器创建一个新的包:
cd ~/ros2_ws/src
ros2 pkg create --dependencies action_tutorials_interfaces rclcpp rclcpp_action rclcpp_components -- action_tutorials_cpp
- 添加可见性控制
为了使该包能够在Windows上编译和工作,我们需要添加一些“可见性控制”。
有关详细信息,请参阅Windows提示和技巧文档中的Windows符号可见性。
打开action_tutorials_cpp/include/action_tutorias_cpp/visibility_control.h、 并输入以下代码:
- 创建action_tutorials_cpp包
-
编写动作服务器
让我们专注于编写一个动作服务器,它使用我们在创建动作教程中创建的动作来计算斐波那契序列。- 编写动作服务器代码
打开action_tutorials_cpp/src/fibonacci_action_server.cpp,并放入以下代码:
前几行包含了我们需要编译的所有标头。
接下来,我们创建一个类,它是rclcpp::Node
的派生类
FibonacciActionServer类的构造函数将节点名初始化为fibonacce_action_server:
构造函数还实例化了一个新的动作服务器:
动作服务器需要6件事:
1.模板化的动作类型名称:Fibonacci。
2.要将操作添加到的ROS 2节点:this
。
3.动作名称:“fibonacci”。
4.处理目标的回调函数:handle_goal
5.用于处理取消的回调函数:handle_cancel
。
6.用于处理目标接受的回调函数:handle_accept
。
文件中的下一个是各种回调的实现。
请注意,所有回调都需要快速返回,否则我们就有可能饿死执行者。
我们从处理新目标的回调开始:
此实现只接受所有目标。
接下来是处理取消的回调:
这个实现只告诉客户端它接受了取消。
最后一个回调接受一个新目标并开始处理它:
由于执行是一个长时间运行的操作,因此我们生成一个线程来完成实际工作,并从handle_accepted快速返回。
所有进一步的处理和更新都在新线程的execute方法中完成:
这个工作线程每秒处理一个斐波那契序列的序列号,发布每个步骤的反馈更新。完成处理后,它将goal_handle标记为成功,然后退出。
我们现在有了一个功能齐全的动作服务器。让我们构建并运行它。 - 编译动作服务器
在上一节中,我们将动作服务器代码放置到位。
为了让它编译和运行,我们需要做一些额外的事情。
首先,我们需要设置CMakeLists.txt,以便编译动作服务器。
打开action_tutorials_cpp/CMakeLists.txt,并在find_package
调用之后添加以下内容:
现在我们可以编译这个包了。
转到ros2_ws的顶层,然后运行:
colcon build --packages-select action_tutorials_cpp
这应该编译整个工作区,包括action_tutorials_cpp包中的fibonacci_action_server。 - 运行动作服务器
现在我们已经构建了动作服务器,我们可以运行它了。
获取我们刚刚构建的工作区(ros2_ws),并尝试运行操作服务器:
ros2 run action_tutorials_cpp fibonacci_action_server
- 编写动作服务器代码
-
编写动作客户端
- 编写动作客户端代码
打开action_tutorials_cpp/src/fibonacci_action_client.cpp,并放入以下代码:
前几行包含了我们需要编译的所有标头。
接下来,我们创建一个类,它是rclcpp::Node
的派生类
FibonacciActionClient
类的构造函数将节点名初始化为fibonacce_action_client
构造函数还实例化了一个新的动作客户端
动作客户端需要三件事:
1.模板化的动作类型名称:Fibonacci。
2.将操作客户端添加到的ROS 2节点:this
。
3.动作名称:“fibonacci”。
我们还实例化了一个ROS计时器,它将启动对send_goal
的唯一调用
当计时器到期时,它将调用send_goal
此函数执行以下操作:
1.取消计时器(因此只调用一次)。
2.等待动作服务器启动。
3.实例化一个新的Fibonacci::Goal
。
4.设置响应、反馈和结果回调。
5.将目标发送到服务器。
当服务器接收并接受目标时,它将向客户端发送响应。
该响应由goal_response_callback
处理
假设目标被服务器接受,它将开始处理。
对客户的任何反馈都将由feedback_callback
处理:
当服务器完成处理后,它将向客户端返回结果。
结果由result_callback
处理
我们现在有一个功能齐全的行动客户端。让我们构建并运行它。 - 编译动作客户端
在上一节中,我们将操作客户端代码放置到位。
为了让它编译和运行,我们需要做一些额外的事情。
首先,我们需要设置CMakeLists.txt,以便编译动作客户端。
打开action_tutorials_cpp/CMakeLists.txt,并在find_package
调用之后添加以下内容:
现在我们可以编译这个包了。
转到ros2_ws的顶层,然后运行:
colcon build --packages-select action_tutorials_cpp
这应该编译整个工作区,包括action_tutorials_cpp包中的fibonacci_action_client。 - 运行动作客户端
现在我们已经构建了动作客户端,我们可以运行它了。
首先确保动作服务器在单独的终端中运行。
现在获取我们刚刚构建的工作区(ros2_ws),并尝试运行操作客户端:
ros2 run action_tutorials_cpp fibonacci_action_client
您应该看到目标被接受、反馈被打印以及最终结果的日志消息。
- 编写动作客户端代码
总结
在本教程中,您将一行一行地将C++动作服务器和动作客户端放在一起,并将它们配置为交换目标、反馈和结果。
在单个进程中组合多个节点
背景
请参阅概念文章。
运行演示
演示使用rclcpp_components、ros2component和composition包中的可执行文件,可以使用以下命令运行。
发现可用组件
要查看哪些组件在工作区中注册并可用,请在shell中执行以下操作:
ros2 component types
终端将返回所有可用组件的列表:
使用具有发布者和订阅者的ROS服务的运行时组合
在第一个shell中,启动组件容器:
ros2 run rclcpp_components component_container
打开第二个shell并通过ros2命令行工具验证容器是否正在运行:
ros2 component list
您应该看到组件的名称:
/ComponentManager
在第二个shell中加载talker组件(请参阅talker源代码):
ros2 component load /ComponentManager composition composition::Talker
该命令将返回加载组件的唯一ID以及节点名称:
Loaded component 1 into '/ComponentManager' container node as '/talker'
现在,第一个shell应该显示组件已加载的消息以及发布消息的重复消息。
在第二个shell中运行另一个命令以加载listener组件(请参阅listener源代码):
ros2 component load /ComponentManager composition composition::Listener
终端将返回:
Loaded component 2 into '/ComponentManager' container node as '/listener'
现在可以使用ros2命令行实用程序检查容器的状态:
ros2 component list
您将看到以下结果:
/ComponentManager
1 /talker
2 /listener
现在,第一个shell应该为每个接收到的消息显示重复的输出。
使用带有服务器和客户端的ROS服务的运行时组合
服务器和客户端的示例非常相似。
在第一个shell中:
ros2 run rclcpp_components component_container
在第二个shell中(请参阅服务器和客户端源代码):
ros2 component load /ComponentManager composition composition::Server
ros2 component load /ComponentManager composition composition::Client
在这种情况下,客户端向服务器发送请求,服务器处理请求并用响应进行答复,客户端打印收到的响应。
使用ROS服务编译时组合
这个演示展示了可以重用相同的共享库来编译运行多个组件的单个可执行文件。
该可执行文件包含上述所有四个组件:说话者和侦听器以及服务器和客户端。
在shell调用中(请参阅源代码):
这应该显示来自两对的重复消息,发送者和侦听者以及服务器和客户端。
手动合成的组件不会反映在
ros2 component list
命令行工具输出中。
使用dlopen的运行时组合
此演示通过创建通用容器进程并显式传递库以加载而不使用ROS接口,提供了运行时组合的替代方案。
该过程将打开每个库,并在库源代码中创建每个rclcpp::Node
类的一个实例。
现在,shell应该为每个发送和接收的消息显示重复的输出。
dlopen-composed组件将不会反映在
ros2 component list
命令行工具输出中。
使用启动动作合成
虽然命令行工具对于调试和诊断组件配置很有用,但同时启动一组组件通常更方便。
为了自动化此操作,我们可以使用ros2 launch
中的功能。
ros2 launch composition composition_demo.launch.py
高级主题
现在我们已经了解了组件的基本操作,我们可以讨论一些更高级的主题。
正在卸载组件
在第一个shell中,启动组件容器:
ros2 run rclcpp_components component_container
通过ros2命令行工具验证容器是否正在运行:
ros2 component list
您应该看到组件的名称:
/ComponentManager
在第二个shell中,像前面一样加载talker和listener:
ros2 component load /ComponentManager composition composition::Talker
ros2 component load /ComponentManager composition composition::Listener
使用唯一ID从组件容器中卸载节点。
ros2 component unload /ComponentManager 1 2
终端应返回:
Unloaded component 1 from '/ComponentManager' container
Unloaded component 2 from '/ComponentManager' container
在第一个shell中,验证来自talker和listener的重复消息是否已停止。
重新映射容器名称和命名空间
组件管理器名称和命名空间可以通过标准命令行参数重新映射:
ros2 run rclcpp_components component_container --ros-args -r __node:=MyContainer -r __ns:=/ns
在第二个shell中,可以使用更新的容器名称加载组件:
ros2 component load /ns/MyContainer composition composition::Listener
容器的命名空间重新映射不会影响加载的组件。
重新映射组件名称和名称空间
组件名称和命名空间可以通过load
命令的参数进行调整。
在第一个shell中,启动组件容器:
ros2 run rclcpp_components component_container
有关如何重新映射名称和命名空间的一些示例。
重新映射节点名称:
ros2 component load /ComponentManager composition composition::Talker --node-name talker2
重新映射命名空间:
ros2 component load /ComponentManager composition composition::Talker --node-namespace /ns
重新映射两者:
ros2 component load /ComponentManager composition composition::Talker --node-name talker3 --node-namespace /ns2
现在使用ros2命令行实用程序:
ros2 component list
在控制台中,您应该看到相应的条目:
/ComponentManager
1 /talker2
2 /ns/talker
3 /ns2/talker3
容器的命名空间重新映射不会影响加载的组件。
将参数值传递到组件中
ros2组件加载命令行支持在节点构造时将任意参数传递给节点。
此功能可按如下方式使用:
ros2 component load /ComponentManager image_tools image_tools::Cam2Image -p burger_mode:=true
将其他参数传递到组件中
ros2组件加载命令行支持将特定选项传递给组件管理器,以便在构建节点时使用。
到目前为止,唯一支持的命令行选项是使用进程内通信实例化节点。
此功能可按如下方式使用:
ros2 component load /ComponentManager composition composition::Talker -e use_intra_process_comms:=true
作为共享库的可组合节点
如果要将可组合节点从包中导出为共享库,并在另一个执行链接时间组合的包中使用该节点,请将代码添加到CMake文件中,该文件将导入下游包中的实际目标。
然后安装生成的文件并导出生成的文件。
这里可以看到一个实际的例子:ROS Discourse - Ament best practice for sharing libraries
监控参数更改(C++)
背景
通常,一个节点需要对其自身参数或另一个节点参数的更改做出响应。
ParameterEventHandler
类使监听参数更改变得容易,以便您的代码能够响应它们。
本教程将向您展示如何使用ParameterEventHandler
类的C++版本来监视节点自身参数的更改以及其他节点参数的更改。
任务
在本教程中,您将创建一个新的包来包含一些示例代码,编写一些C++代码来使用ParameterEventHandler
类,并测试生成的代码。
-
创建包
首先,打开一个新的终端并获取ros2安装的源代码,以便ros2命令可以工作。
按照以下说明创建名为ros2_ws的新工作区。
请记住,包应该在src目录中创建,而不是在工作区的根目录中创建。因此,导航到ros2_ws/src,然后在那里创建一个新包:
ros2 pkg create --build-type ament_cmake cpp_parameter_event_handler --dependencies rclcpp
您的终端将返回一条消息,验证您的包cpp_parameter_event_handler及其所有必需的文件和文件夹的创建。
--dependencies
参数将自动向package.xml和CMakeLists.txt中添加必要的依赖行。- 更新package.xml
因为您在创建包时使用了--dependencies
选项,所以不必手动向package.xml和CMakeLists.txt中添加依赖项。
不过,一如既往,请确保在package.xml中添加描述、维护者电子邮件和名称以及许可证信息。
<description>C++ parameter events client tutorial</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache License 2.0</license>
- 更新package.xml
-
编写C++节点
在ros2_ws/src/cpp_parameter_event_handler/src目录中,创建一个名为parameter_vent_handler.cpp的新文件并粘贴以下代码:
- 检查代码
第一条语句包含#include<memory>
,以便代码可以使用std::make_shared
模板。
下一个#include“rclcpp/rclcpp.hpp
允许代码引用rclcpp接口提供的各种功能,包括ParameterEventHandler
类。
在类声明之后,代码定义了一个类SampleNodeWithParameters
。
类的构造函数声明一个整数参数an_int_param
,默认值为0。
接下来,代码创建一个ParameterEventHandler
,用于监视参数的更改。
最后,代码创建了一个lambda
函数,并将其设置为每当an_int_param
更新时调用的回调。保存
add_parameter_callback
返回的句柄非常重要;否则,回调将无法正确注册。
SampleNodeWithParameters
后面是一个典型的主函数,它初始化ROS,旋转示例节点以便它可以发送和接收消息,然后在用户在控制台输入^C
后关闭。 - 添加可执行文件
要构建此代码,请首先打开CMakeLists.txt文件,并在依赖项find_package(rclcpp REQUIRED)
下添加以下代码行
add_executable(parameter_event_handler src/parameter_event_handler.cpp)
ament_target_dependencies(parameter_event_handler rclcpp)
install(TARGETS parameter_event_handler DESTINATION lib/${PROJECT_NAME})
- 检查代码
-
构建并运行
在构建之前,最好在工作区的根目录(ros2_ws)中运行rosdep
以检查是否缺少依赖项:
rosdep install -i --from-path src --rosdistro $ROS_DISTRO -y
导航回工作区的根目录ros2_ws,并构建新的包:
colcon build --packages-select cpp_parameter_event_handler
打开一个新终端,导航到ros2_ws,并获取设置文件:
. install/setup.bash
现在运行节点:
ros2 run cpp_parameter_event_handler parameter_event_handler
该节点现在处于活动状态,并且只有一个参数,每当更新该参数时都会打印一条消息。
要测试这一点,请打开另一个终端并像以前一样获取ROS设置文件(.install/setup.bash
),然后执行以下命令:
ros2 param set node_with_parameters an_int_param 43
运行该节点的终端将显示类似以下内容的消息:
[INFO] [1667026487.796992022] [node_with_parameters]: cb: Received an update to parameter "an_int_param" of type integer: "43"
我们之前在节点中设置的回调已被调用,并显示了新的更新值。
现在可以在终端中使用^C
终止正在运行的parameter_event_handler
示例。- 监视对另一节点参数的更改
您还可以使用ParameterEventHandler监视对其他节点参数的参数更改。
让我们更新SampleNodeWithParameters
类,以监视另一个节点中参数的更改。
我们将使用parameter_blackboard演示应用程序来托管一个双参数,我们将监视它的更新。
首先更新构造函数,在现有代码之后添加以下代码:
然后为附加回调句柄添加另一个成员变量cb_handle2
:
在终端中,导航回工作区的根目录ros2_ws,并像以前一样构建更新的包:
colcon build --packages-select cpp_parameter_event_handler
然后获取设置文件:
. install/setup.bash
现在,要测试远程参数的监控,首先运行新建的parameter_event_handler代码:
ros2 run cpp_parameter_event_handler parameter_event_handler
接下来,从另一个终端(ROS已初始化)运行parameter_blackboard演示应用程序,如下所示:
ros2 run demo_nodes_cpp parameter_blackboard
最后,从第三个终端(ROS已初始化),让我们在parameter_blackboard节点上设置一个参数:
ros2 param set parameter_blackboard a_double_param 3.45
执行此命令后,您应该会在parameter_event_handler窗口中看到输出,指示在参数更新时调用了回调函数:
[INFO] [1606952588.237531933] [node_with_parameters]: cb2: Received an update to parameter "a_double_param" of type: double: "3.45"
- 监视对另一节点参数的更改
总结
您创建了一个带有参数的节点,并使用ParameterEventHandler类设置回调以监视对该参数的更改。
您还使用相同的类来监视对远程节点的更改。
ParameterEventHandler是监视参数更改的方便方法,这样您就可以响应更新的值。
将启动文件集成到ROS 2包中
背景
在上一个教程中,我们看到了如何编写独立的启动文件。
本教程将展示如何将启动文件添加到现有包中,以及通常使用的约定。
任务
-
创建包
创建包所在的工作区:
mkdir -p launch_ws/src
cd launch_ws/src
ros2 pkg create py_launch_example --build-type ament_python
-
创建用于保存启动文件的结构
按照惯例,包的所有启动文件都存储在包内部的启动目录中。
确保在上面创建的包的顶层创建一个启动目录。
对于Python包,包含包的目录应如下所示:
为了让colcon找到启动文件,我们需要使用setup的data_files
参数将启动文件通知Python的安装工具。
在我们的setup.py文件中:
-
编写启动文件
在启动目录中,创建一个名为my_script_launch.py的新启动文件
建议使用_launch.py作为Python启动文件的文件后缀,但不是必需的。
启动文件应定义generate_launch_description()
函数,该函数返回启动。
由ros2 launch
使用的launch.LaunchDescription()
。
-
构建和运行启动文件
转到工作区的顶层并构建它:
colcon build
在构建成功并获得工作空间源之后,您应该能够按如下方式运行启动文件:
ros2 launch py_launch_example my_script_launch.py