【ROS2】ROS2节点Node机制与常用命令行

发布于:2025-08-03 ⋅ 阅读:(9) ⋅ 点赞:(0)

在这里插入图片描述

ROS 系列学习教程(总目录)
ROS2 系列学习教程(总目录)

一、节点介绍

ROS 中的每个节点都应负责单一、模块化的功能,例如控制车轮电机或发布激光测距仪的传感器数据。每个节点都可以通过Topic、Service、Action或Params与其他节点发送和接收数据。

在这里插入图片描述

完整的机器人系统由许多协同工作的节点组成。

在 ROS2 中,节点有如下特点(这里特点与ROS1相同):

  • 单个可执行文件可以包含一个或多个节点。
  • 每个节点可以使用不同的编程语言实现(C++ 程序、Python 程序等)。
  • 每个节点独占一个进程。
  • 处于不同可执行文件的节点,可以分布式的运行在不同主机。
  • 节点名字全局唯一。

二、创建自定义节点

与ROS1相比,ROS2创建节点的方式有稍微改变:

ROS1:

#include "ros/ros.h"

int main(int argc, char **argv) 
{
    ros::init(argc, argv, "my_node");
    ros::NodeHandle nh;
    // 节点逻辑
    ros::spin();
    return 0;
}

ROS2:

#include "rclcpp/rclcpp.hpp"

int main(int argc, char **argv) 
{
    rclcpp::init(argc, argv);
    auto node = rclcpp::Node::make_shared("my_node");
    // 节点逻辑
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

ROS2 去掉了 ROS1 中 NodeHandle 的概念,取而代之的是节点指针,ROS1中的节点名字在 init 中初始化,NodeHandle 是匿名的,ROS2 中没有了这一概念,并把节点名字放到节点构造函数中初始化,这样就保证了同一节点内操作句柄是唯一的,即该节点的指针。

这样即简化了节点管理又增强了节点唯一性。在ROS1中,NodeHandle 用于操作节点资源,如创建Publisher和Subscriber。然而,开发者需要额外管理NodeHandle的实例,从而增加了节点的复杂性;在ROS2中,使用节点指针和构造函数初始化节点,简化了节点的管理。开发者无需显式创建和管理NodeHandle,因为节点本身已经包含了所有必要的操作句柄。在ROS1中,虽然可以通过NodeHandle操作多个节点资源,但NodeHandle通常是匿名的,这可能导致节点之间的混淆;ROS2通过将节点名字直接放入节点构造函数中初始化,确保了同一节点内操作句柄的唯一性。这样做有助于避免节点之间的命名冲突和混淆。

上述代码中 auto node = rclcpp::Node::make_shared("my_node"); 创建了一个 rclcpp::Node 类的对象指针,但实际应用中,一般会实现自定义类并继承于 rclcpp::Node 以使用ROS2的接口。

#include <chrono>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using namespace std::chrono_literals;

// 继承于rclcpp::Node的自定义节点HelloWorldPublisher
class HelloWorldPublisher : public rclcpp::Node
{
public:
    HelloWorldPublisher()
        : Node("publisher") // 构造时指定节点的名字
        , m_count(0)
    {
        m_publisher = this->create_publisher<std_msgs::msg::String>("/hello_world", 10);
        m_timer = this->create_wall_timer(500ms, std::bind(&HelloWorldPublisher::timercallback, this));
    }

private:
    void timercallback()
    {
        auto message = std_msgs::msg::String();
        message.data = "Hello, world! " + std::to_string(this->m_count++);
        RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
        this->m_publisher->publish(message);
    }

private:
    rclcpp::TimerBase::SharedPtr m_timer;
    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr m_publisher;
    size_t m_count;
};

int main(int argc, char *argv[])
{
    rclcpp::init(argc, argv);
    auto node = std::make_shared<HelloWorldPublisher>();
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

如前文的hello world示例,自定义了 HelloWorldPublisher 类,继承于 rclcpp::Node,在 HelloWorldPublisher 类中实现自己的业务逻辑。

简单理解就是,ROS2的 rclcpp 库给我们提供了一个父类 Node,我们使用时需要继承这个类,这样就可以在自己的类里调用 rclcpp::Node 的接口。

三、Node 常用命令行

3.1 运行节点

ros2 run <package_name> <executable_name>

比如 ros2 run turtlesim turtlesim_node 会打开一个ROS自带的乌龟测试窗口,前文的hello world示例中 ros2 run hello_world_cpp talker 会运行发布者节点。

注意:package_name 是包名称,executable_name 是可执行文件的名称,不是节点名称。

3.2 节点列表

ros2 node list

该命令会列出所有正在运行的节点的名称。

3.3 节点信息

ros2 node info <node_name>

该命令会列出节点的详细信息。

在这里插入图片描述

其中会包括节点的名字、Topic的发布者和订阅者、Service的服务端和客户端、Action的服务端和客户端。


欢迎大家加QQ群,一起讨论学习:894013891