前言
阅读本章之前,确保已经了解ROS2中构件(Component)的概念,如果不了解,欢迎移步ROS2 Composition
一个小问题自测:构件与节点之间的区别?在容器进程中运行的单元是构件还是节点?
执行demo
本文通过执行几个demo,来直观展示composition的常见使用方式。
run-time 动态加载
foxy源码demos中的composition已经实现了一些构件,不妨确认一下:
$ ros2 component type
composition
composition::Talker
composition::Listener
composition::Server
composition::Client
我们将多个节点加载到单个进程的步骤是:首先创建一个容器进程,然后通过ros2 的api向这个容器进程中动态加载。
创建容器进程:ros2 run rclcpp_components component_container
确认容器进程正在运行:ros2 component list
在另一个终端中,输入命令加载构件:ros2 component load /ComponentManager composition composition::
我们此时可以看到容器进程终端有相应的内容输出。
编译期加载
同样的shared lib也可以在代码中在编译期加载,可以看一下manual_composition.cpp的源码:
// 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
运行时从静态库加载
大致的思路是通过静态库新建一个node实例,从而达到加载的目的。代码如下:
// 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
#include
#include "class_loader/class_loader.hpp"
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_components/node_factory.hpp"
#define DLOPEN_COMPOSITION_LOGGER_NAME "dlopen_composition"
int main(int argc, char * argv[])
{
// Force flush of the stdout buffer.
setvbuf(stdout, NULL, _IONBF, BUFSIZ);
if (argc < 2) {
fprintf(stderr, "Requires at least one argument to be passed with the library to load\n");
return 1;
}
rclcpp::init(argc, argv);
rclcpp::Logger logger = rclcpp::get_logger(DLOPEN_COMPOSITION_LOGGER_NAME);
rclcpp::executors::SingleThreadedExecutor exec;
rclcpp::NodeOptions options;
std::vector loaders;
std::vector node_wrappers;
std::vector libraries;
for (int i = 1; i < argc; ++i) {
libraries.push_back(argv[i]);
}
for (auto library : libraries) {
RCLCPP_INFO(logger, "Load library %s", library.c_str());
auto loader = new class_loader::ClassLoader(library);
auto classes = loader->getAvailableClasses();
for (auto clazz : classes) {
RCLCPP_INFO(logger, "Instantiate class %s", clazz.c_str());
auto node_factory = loader->createInstance(clazz);
auto wrapper = node_factory->create_node_instance(options);
auto node = wrapper.get_node_base_interface();
node_wrappers.push_back(wrapper);
exec.add_node(node);
}
loaders.push_back(loader);
}
exec.spin();
for (auto wrapper : node_wrappers) {
exec.remove_node(wrapper.get_node_base_interface());
}
node_wrappers.clear();
rclcpp::shutdown();
return 0;
}
需要注意的是我们需要在命令行中显式地指出使用的静态库:
ros2 run composition dlopen_composition
ros2 pkg prefix composition/lib/libtalker_component.so
ros2 pkg prefix composition/lib/liblistener_component.so
这样就把talker和listener组件加载到了容器进程中。
我们看到从代码中加载的构件也是需要新起一个线程作为容器进程。
使用launch实现Composition
可以通过ros2 launch
指令批量加载构件:ros2 launch composition composition_demo.launch.py
Advanced Topic
上面介绍的是一些基础用法,现在来看一些进阶的做法。
Unloading Component
假设我们在容器中加载了listener和talker,我们可以通过构件的id来卸载他们:
$ ros2 component unload /ComponentManager 1 2
Unloaded component 1 from '/ComponentManager' container
Unloaded component 2 from '/ComponentManager' container
Remapping container name and namespace
我们可以通过命令行参数重命名容器进程:
ros2 component rclcpp_components component_container --ros-args -r __node:=MyContainer -r __ns:=/ns
那么在加载构件的终端中,我们就可以使用:
ros2 component load /ns/MyContainer composition composition::Listener
Remapping component name and namespace
类似地,我们可以remapping构件的名字和命名空间:
$ 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 component list
/ComponentManager
1 /talker2
2 /ns/talker
3 /ns2/talker3