ROS2--在单进程中加入多个节点

前言

阅读本章之前,确保已经了解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::<component_name>

我们此时可以看到容器进程终端有相应的内容输出。

编译期加载

同样的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 <memory>

#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<composition::Talker>(options);
  exec.add_node(talker);
  auto listener = std::make_shared<composition::Listener>(options);
  exec.add_node(listener);
  auto server = std::make_shared<composition::Server>(options);
  exec.add_node(server);
  auto client = std::make_shared<composition::Client>(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 <memory>
#include <string>
#include <vector>

#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
");
    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<class_loader::ClassLoader *> loaders;
  std::vector<rclcpp_components::NodeInstanceWrapper> node_wrappers;

  std::vector<std::string> 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<rclcpp_components::NodeFactory>();
    for (auto clazz : classes) {
      RCLCPP_INFO(logger, "Instantiate class %s", clazz.c_str());
      auto node_factory = loader->createInstance<rclcpp_components::NodeFactory>(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
原文地址:https://www.cnblogs.com/LuoboLiam/p/14602539.html