概述
首先,需要明确的是:并非所有的 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 函数,该函数接受一个指向节点的共享指针。该函数会获取并打印节点的名称。
#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);
}
输出
[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 函数。
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());
}
输出
[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。
#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);
}
输出
[INFO] [Simple_Node]: Node name: Simple_Node
[INFO] [Simple_LifeCycle_Node]: Node name: Simple_LifeCycle_Node
3.1 代码分析
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:启用日志记录。
然后,它获取并打印节点名称。
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 类通常包含用于状态转换(未配置、非活动、活动和已完成)的函数。为了演示目的,这里没有包含这些函数。
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 对象的引用,因此需要对共享指针进行解引用。
关注【智践行】公众号,发送 【机器人】 获得机器人经典学习资料