zoukankan      html  css  js  c++  java
  • 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
    
  • 相关阅读:
    MVC的布局页,视图布局页和分布页的使用
    C#程序的编译过程
    页面跳转到Area区域连接
    c#静态变量和非静态变量的区别
    C#设计模式:适配器模式(Adapter Pattern)
    依赖注入
    打印随机数到字符串中
    printf/scanf格式
    用fread和fwrite实现文件复制操作
    用fseek和ftell获取文件的大小
  • 原文地址:https://www.cnblogs.com/LuoboLiam/p/14602539.html
Copyright © 2011-2022 走看看