在 ROS 1 中,您可以将代码编写为 ROS 节点或 ROS nodelet。 ROS 1 节点被编译成可执行文件。另一方面,ROS 1 nodelet 被编译成一个共享库,然后在运行时由容器进程加载。
在 ROS 2 中,推荐的编写代码的方式类似于 nodelet——我们称之为组件。这使得向现有代码添加通用概念变得容易,例如生命周期。 ROS 2 避免了不同 API 的最大缺点,因为两种方法在 ROS 2 中使用相同的 API。
仍然可以使用类似节点的样式“编写自己的 main”,但对于常见情况不建议这样做。
通过使流程布局成为部署时的决策,用户可以选择:
此外,ros2 启动可用于通过专门的启动操作自动执行这些操作。
由于组件仅内置在共享库中,因此它没有 main 函数(参见 Talker 源代码)。组件通常是 rclcpp::Node 的子类。由于它不受线程控制,因此不应在其构造函数中执行任何长时间运行或阻塞的任务。相反,它可以使用计时器来获得定期通知。此外,它还可以创建发布者、订阅者、服务器和客户端。
使某个类成为组件的一个重要方面是该类使用包 rclcpp_components 中的宏注册自己(参见源代码中的最后一行)。这使得组件在其库被加载到正在运行的进程中时可被发现 - 它充当一种入口点。
此外,一旦创建了组件,就必须使用索引注册它才能被工具发现。
- add_library(talker_component SHARED
- src/talker_component.cpp)
- rclcpp_components_register_nodes(talker_component "composition::Talker")
- # To register multiple components in the same shared library, use multiple calls
- # rclcpp_components_register_nodes(talker_component "composition::Talker2")
为了让 component_container 能够找到所需的组件,它必须从提供了相应工作空间的 shell 执行或启动。
组合包包含一些关于如何使用组件的不同方法。最常见的三种是:
启动一个(通用容器进程)并调用容器提供的 ROS 服务 load_node。然后,ROS 服务将加载由传递的包名和库名指定的组件,并开始在正在运行的进程中执行它。除了以编程方式调用 ROS 服务,您还可以使用命令行工具通过传递的命令行参数调用 ROS 服务
创建包含多个在编译时已知的节点的自定义可执行文件。这种方法要求每个组件都有一个头文件(第一种情况并不严格需要)。
创建一个launch文件,使用ros2 launch创建一个加载了多个组件的容器进程。
官方资料有三个demo:
可以下载后使用,并且可以使用以下命令运行。
要查看工作区中已注册和可用的组件,请在 shell 中执行以下命令:
ros2 component types
终端将返回所有可用组件的列表:
(... components of other packages here)
composition
composition::Talker
composition::Listener
composition::NodeLikeListener
composition::Server
composition::Client
(... components of other packages here)
在第一个 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 中运行另一个命令以加载侦听器组件(请参阅侦听器源代码):
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 应该显示每个收到的消息的重复输出。
具有服务器和客户端的示例非常相似。
在第一个shell中:
ros2 run rclcpp_components component_container
在第二个 shell 中(参见服务器和客户端源代码):
- ros2 component load /ComponentManager composition composition::Server
- ros2 component load /ComponentManager composition composition::Client
在这种情况下,客户端向服务器发送请求,服务器处理请求并回复响应,客户端打印收到的响应。
该演示表明,可以重用相同的共享库来编译运行多个组件的单个可执行文件。可执行文件包含上面的所有四个组件:talker 和 listener 以及 server 和 client。(参考代码如下)
- // Copyright 2016 Open Source Robotics Foundation, Inc.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
-
- #include
-
- #include "composition/client_component.hpp"
- #include "composition/listener_component.hpp"
- #include "composition/talker_component.hpp"
- #include "composition/server_component.hpp"
- #include "rclcpp/rclcpp.hpp"
-
- int main(int argc, char * argv[])
- {
- // Force flush of the stdout buffer.
- setvbuf(stdout, NULL, _IONBF, BUFSIZ);
-
- // Initialize any global resources needed by the middleware and the client library.
- // This will also parse command line arguments one day (as of Beta 1 they are not used).
- // You must call this before using any other part of the ROS system.
- // This should be called once per process.
- rclcpp::init(argc, argv);
-
- // Create an executor that will be responsible for execution of callbacks for a set of nodes.
- // With this version, all callbacks will be called from within this thread (the main one).
- rclcpp::executors::SingleThreadedExecutor exec;
- rclcpp::NodeOptions options;
-
- // Add some nodes to the executor which provide work for the executor during its "spin" function.
- // An example of available work is executing a subscription callback, or a timer callback.
- auto talker = std::make_shared
(options); - exec.add_node(talker);
- auto listener = std::make_shared
(options); - exec.add_node(listener);
- auto server = std::make_shared
(options); - exec.add_node(server);
- auto client = std::make_shared
(options); - exec.add_node(client);
-
- // spin will block until work comes in, execute work as it becomes available, and keep blocking.
- // It will only be interrupted by Ctrl-C.
- exec.spin();
-
- rclcpp::shutdown();
-
- return 0;
- }
ros2 run composition manual_composition
这应该显示来自publisher和acception以及server和client的重复消息。
注意:手动编写的组件不会反映在 ros2 组件列表命令行工具输出中。
该演示通过创建通用容器进程并显式传递要加载的库而不使用 ROS 接口,提供了运行时组合的替代方案。该过程将打开每个库并在库源代码中为每个“rclcpp::Node”类创建一个实例)。
linux指令
ros2 run composition dlopen_composition `ros2 pkg prefix composition`/lib/libtalker_component.so `ros2 pkg prefix composition`/lib/liblistener_component.so
windows指令
- > ros2 pkg prefix composition
- 获得路径后
- > ros2 run composition dlopen_composition <path_to_composition_install>\bin\talker_component.dll <path_to_composition_install>\bin\listener_component.dll
现在,shell 应该为每个发送和接收的消息显示重复的输出。
注意:dlopen-composed 组件不会反映在 ros2 组件列表命令行工具输出中。
虽然命令行工具对于调试和诊断组件配置很有用,但同时启动一组组件通常更方便。要自动执行此操作,我们可以使用 ros2 启动中的功能。
ros2 launch composition composition_demo.launch.py
现在我们已经了解了组件的基本操作,我们可以讨论一些更高级的主题。
在第一个 shell 中,启动组件容器:
ros2 run rclcpp_components component_container
通过 ros2 命令行工具验证容器是否正在运行:
ros2 component list
您应该会看到组件的名称:
/ComponentManager
在第二个 shell 中,像之前一样加载谈话者和听众:
- ros2 component load /ComponentManager composition composition::Talker
- ros2 component load /ComponentManager composition composition::Listener
使用唯一 ID 从组件容器中卸载节点。
ros2 component unload /ComponentManager 1 2
The terminal should return:
- 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
注意:容器的命名空间重新映射不会影响加载的组件。
组件名称和命名空间可以通过加载命令的参数进行调整。 在第一个 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 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 文件中,该文件会在下游包中导入实际目标。
然后安装生成的文件并导出生成的文件。
ROS2比ROS1最大不同,在于ROS2用更高明、更专业的程序开发,必备的专业知识有:
等诸多的近代程序开发技术,因此,对开发者的要求更高,面对更高的门槛,开发者需要沉下心思,平心静气、排除万难,才能修成正果。
Composing multiple nodes in a single process — ROS 2 Documentation: Foxy documentation