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的服务端和客户端。