ROS2 Jazzy:如何使用节点接口模板类访问节点信息(C++)

概述

首先,需要明确的是:并非所有的 ROS 节点都是一样的!

例如,rclcpp::Node 和 rclcpp_lifecycle::LifecycleNode 类没有共同的继承树,这意味着当 ROS 开发者想编写一个以 ROS 节点指针作为参数的函数时,可能会遇到编译时的类型问题。为了解决这个问题,rclcpp 包含了 rclcpp::NodeInterfaces<> 模板类型,它是向函数传递常规节点和生命周期节点的首选方式。接下来将向你展示如何使用 rclcpp::NodeInterfaces<> 为所有 ROS 节点类型生成可靠且简洁的接口。

rclcpp::NodeInterfaces<> 模板类为管理节点接口提供了一种简洁高效的方法。当处理不同类型的节点(如 rclcpp::Node 和 rclcpp_lifecycle::LifecycleNode)时,因为这些节点没有相同的继承树,这个模板就非常有用。

1. 使用共享指针访问节点信息

在下面的示例中,我们创建了一个名为 Simple_Node 的简单节点,并定义了一个 node_info 函数,该函数接受一个指向节点的共享指针。该函数会获取并打印节点的名称。

cpp 复制代码
#include <memory>
#include "rclcpp/rclcpp.hpp"

void node_info(rclcpp::Node::SharedPtr node)
{
  RCLCPP_INFO(node->get_logger(), "Node name: %s", node->get_name());
}

class SimpleNode : public rclcpp::Node
{
public:
  SimpleNode(const std::string & node_name)
  : Node(node_name)
  {
  }
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  auto node = std::make_shared<SimpleNode>("Simple_Node");
  node_info(*node);
}

输出

css 复制代码
[INFO] [Simple_Node]: Node name: Simple_Node

虽然这种方法对于 rclcpp::Node 类型的参数效果很好,但对于其他节点类型(如 rclcpp_lifecycle::LifecycleNode)则不适用。

2. 显式传递 rclcpp::node_interfaces

其中一种更健壮、更适合于所有节点类型的方法是将 rclcpp::node_interfaces 作为参数显式传递给函数,如下例所示。在接下来的示例中,我们创建了一个名为 node_info 的函数,它接受两个 rclcpp::node_interfaces 参数,即 NodeBaseInterface 和 NodeLoggingInterface,并打印节点名称。然后创建一个 rclcpp_lifecycle::LifecycleNode 类型和一个 rclcpp::Node 类型的节点,并将它们的接口传递给 node_info 函数。

cpp 复制代码
void node_info(std::shared_ptr<rclcpp::node_interfaces::NodeBaseInterface> base_interface,
               std::shared_ptr<rclcpp::node_interfaces::NodeLoggingInterface> logging_interface)
{
  RCLCPP_INFO(logging_interface->get_logger(), "Node name: %s", base_interface->get_name());
}

class SimpleNode : public rclcpp::Node
{
public:
  SimpleNode(const std::string & node_name)
  : Node(node_name)
  {
  }
};

class LifecycleTalker : public rclcpp_lifecycle::LifecycleNode
{
public:
  explicit LifecycleTalker(const std::string & node_name, bool intra_process_comms = false)
  : rclcpp_lifecycle::LifecycleNode(node_name,
      rclcpp::NodeOptions().use_intra_process_comms(intra_process_comms))
  {}
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::executors::SingleThreadedExecutor exe;
  auto node = std::make_shared<SimpleNode>("Simple_Node");
  auto lc_node = std::make_shared<LifecycleTalker>("Simple_LifeCycle_Node");
  node_info(node->get_node_base_interface(),node->get_node_logging_interface());
  node_info(lc_node->get_node_base_interface(),lc_node->get_node_logging_interface());
}

输出

ini 复制代码
[INFO] [Simple_Node]: Node name: Simple_Node
[INFO] [Simple_LifeCycle_Node]: Node name: Simple_LifeCycle_Node

这虽然解决了节点类型不同的问题,但是随着函数复杂度的增加,rclcpp::node_interfaces 参数的数量也会增加,这会导致代码的可读性和简洁性出现问题。为了使代码更灵活并且兼容不同的节点类型,我们可以使用 rclcpp::NodeInterfaces<>模板。

3. 使用 rclcpp::NodeInterfaces<>模板

访问节点类型信息的推荐方法是通过节点接口。下面,与前面的示例类似,创建了一个 rclcpp_lifecycle::LifecycleNode 和一个 rclcpp::Node。

cpp 复制代码
#include <memory>
#include <string>
#include <thread>
#include "lifecycle_msgs/msg/transition.hpp"
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_lifecycle/lifecycle_node.hpp"
#include "rclcpp_lifecycle/lifecycle_publisher.hpp"
#include "rclcpp/node_interfaces/node_interfaces.hpp"

using MyNodeInterfaces =
  rclcpp::node_interfaces::NodeInterfaces<rclcpp::node_interfaces::NodeBaseInterface, rclcpp::node_interfaces::NodeLoggingInterface>;

void node_info(MyNodeInterfaces interfaces)
{
  auto base_interface = interfaces.get_node_base_interface();
  auto logging_interface = interfaces.get_node_logging_interface();
  RCLCPP_INFO(logging_interface->get_logger(), "Node name: %s", base_interface->get_name());
}

class SimpleNode : public rclcpp::Node
{
public:
  SimpleNode(const std::string & node_name)
  : Node(node_name)
  {
  }
};

class LifecycleTalker : public rclcpp_lifecycle::LifecycleNode
{
public:
  explicit LifecycleTalker(const std::string & node_name, bool intra_process_comms = false)
  : rclcpp_lifecycle::LifecycleNode(node_name,
      rclcpp::NodeOptions().use_intra_process_comms(intra_process_comms))
  {}
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::executors::SingleThreadedExecutor exe;
  auto node = std::make_shared<SimpleNode>("Simple_Node");
  auto lc_node = std::make_shared<LifecycleTalker>("Simple_LifeCycle_Node");
  node_info(*node);
  node_info(*lc_node);
}

输出

ini 复制代码
[INFO] [Simple_Node]: Node name: Simple_Node
[INFO] [Simple_LifeCycle_Node]: Node name: Simple_LifeCycle_Node

3.1 代码分析

cpp 复制代码
using MyNodeInterfaces =
  rclcpp::node_interfaces::NodeInterfaces<rclcpp::node_interfaces::NodeBaseInterface, rclcpp::node_interfaces::NodeLoggingInterface>;

void node_info(MyNodeInterfaces interfaces)
{
  auto base_interface = interfaces.get_node_base_interface();
  auto logging_interface = interfaces.get_node_logging_interface();
  RCLCPP_INFO(logging_interface->get_logger(), "Node name: %s", base_interface->get_name());
}

这个函数不接受共享指针或节点接口,而是接受一个对 rclcpp::node_interfaces::NodeInterfaces 对象。使用这种方法的另一个优点是支持类似节点对象的隐式转换。这意味着可以直接将任何类似节点的对象传递给期望 rclcpp::node_interfaces::NodeInterfaces 对象的函数。

它提取了:

  • NodeBaseInterface:提供基本的节点功能。
  • NodeLoggingInterface:启用日志记录。

然后,它获取并打印节点名称。

cpp 复制代码
class SimpleNode : public rclcpp::Node
{
public:
  SimpleNode(const std::string & node_name)
  : Node(node_name)
  {
  }
};

class LifecycleTalker : public rclcpp_lifecycle::LifecycleNode
{
public:
  explicit LifecycleTalker(const std::string & node_name, bool intra_process_comms = false)
  : rclcpp_lifecycle::LifecycleNode(node_name,
      rclcpp::NodeOptions().use_intra_process_comms(intra_process_comms))
  {}
};

接下来创建一个 rclcpp::Node 类和一个 rclcpp_lifecycle::LifecycleNode 类。rclcpp_lifecycle::LifecycleNode 类通常包含用于状态转换(未配置、非活动、活动和已完成)的函数。为了演示目的,这里没有包含这些函数。

cpp 复制代码
int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::executors::SingleThreadedExecutor exe;
  auto node = std::make_shared<SimpleNode>("Simple_Node");
  auto lc_node = std::make_shared<LifecycleTalker>("Simple_LifeCycle_Node");
  node_info(*node);
  node_info(*lc_node);
}

在 main 函数中,创建一个指向 rclcpp_lifecycle::LifecycleNode 和 rclcpp::Node 的共享指针。上面声明的函数会分别以两种类型的节点作为参数调用一次。

注意

由于模板接受对 NodeT 对象的引用,因此需要对共享指针进行解引用。


关注【智践行】,发送 【机器人】 获得机器人经典学习资料

相关推荐
imDwAaY20 小时前
用户态与内核态到底怎么切换?从系统调用到上下文切换
后端·操作系统
imDwAaY20 小时前
进程、线程、协程有什么区别?从资源分配到调度,一篇理清
后端·操作系统
云计算练习生1 天前
什么是缺页中断?页面不在内存时操作系统怎么办
操作系统·虚拟内存·缺页中断
OpenAnolis小助手1 天前
报名倒计时3天!2026 RISC-V Summit 中国峰会龙蜥生态分论坛亮点抢先看
操作系统·龙蜥社区·risc-v·agentic cpu
流浪0013 天前
Linux系统篇47——线程(十二) POSIX 信号量与环形队列,把判空判满提前到访问之前
linux·操作系统·线程·信号量·环形队列
openEuler社区4 天前
openEuler 26.09创新版本发布,深化全场景创新
开源·操作系统·openeuler
云计算练习生5 天前
什么是操作系统的体系结构?宏内核、微内核与外核
操作系统·内核·外核·操作系统结构
流浪0016 天前
Linux系统篇44——线程(九) 互斥锁的接口与原理,从 xchgb 的原子交换说起
linux·操作系统·线程·互斥锁·互斥锁原理
福兮说7 天前
秋招复习操作系统并发,我让 AI 生成了一张知识图谱,15 条关系里改了 5 条
人工智能·操作系统·知识图谱·秋招
流浪0019 天前
Linux系统篇43——线程(八) 线程的数据不一致问题,从 ticket-- 的非原子性说起
linux·操作系统·线程·线程安全·锁