ros1与ros2的区别笔录

本文详细对比了 ROS 1 和 ROS 2 在多个方面的差异。包括消息传递协议、数据类型、模型架构等。ROS 1 简单易用但功能有限,ROS 2 更复杂强大,支持分布式系统、多播和安全性,在实时性能、多语言通信等方面表现更优,适用于更多场景。

01. 消息传递协议

ROS 1 使用 ROS 消息传递协议 (RMP),而 ROS 2 使用 DDS

ROS 1 和 ROS 2 在消息传递协议上有很大的区别。这些协议决定了在ROS系统中如何进行消息的传递和通信。

ROS 1 使用的是ROS消息传递协议(RMP)。RMP是一种简单的协议,它基于TCP/IP网络通信,并使用XML-RPC和TCPROS进行消息的传递。RMP的设计目标是简单易用,适用于小型的机器人系统和局域网环境。然而,RMP在多播和安全性方面存在一些限制,因为它不支持这些功能。

相比之下,ROS 2 使用的是Data Distribution Service(DDS)作为消息传递协议。DDS是一种更复杂和强大的协议,它提供了高度可配置的、分布式的、实时的消息传递机制。DDS支持多播和安全性,并且具有更好的可靠性和性能。DDS使用专门的中间件来管理消息传递,它提供了更灵活的通信模型,可以适应更广泛的应用场景。

DDS协议的核心概念是发布者-订阅者模型,其中发布者将消息发布到主题(Topic),而订阅者从主题中接收消息。DDS还提供了服务模型,允许客户端发送请求并接收响应。

ROS 2 使用DDS作为默认的消息传递协议,这使得ROS系统更加灵活和可靠。DDS的优势在于它支持分布式系统、多播和安全性,并且具有更好的实时性能。然而,DDS也更加复杂,需要更多的配置和管理。

总结起来,ROS 1 使用ROS消息传递协议(RMP),它简单易用但功能有限。ROS 2 使用Data Distribution Service(DDS),它更加复杂和强大,支持分布式系统、多播和安全性。选择使用哪个版本取决于具体的应用需求和系统要求。

02. 数据类型

ROS 1 使用 ROS 数据类型,而 ROS 2 使用 DDS 数据类型

ROS 1 数据类型:

ROS 1 使用自定义的 ROS 数据类型来定义消息和服务。这些数据类型是简单的 C++ 结构体,由字段组成,每个字段具有名称和类型。 - ROS 1 数据类型使用 .msg 文件来定义,其中包含消息字段的名称和类型。 - ROS 1 数据类型支持基本的数据类型(如整数、浮点数、字符串)以及数组和嵌套结构。 - ROS 1 数据类型使用序列化和反序列化机制将消息转换为二进制数据以进行传输。

以下是一个 ROS 1 数据类型的示例: 

// ROS 1 Message Definition
#include <ros/ros.h>
#include <std_msgs/String.h>

void messageCallback(const std_msgs::String::ConstPtr& msg) {
  ROS_INFO("Received message: %s", msg->data.c_str());
}

int main(int argc, char** argv) {
  ros::init(argc, argv, "ros1_node");
  ros::NodeHandle nh;

  ros::Subscriber sub = nh.subscribe("ros1_topic", 10, messageCallback);

  ros::spin();

  return 0;
}

ROS 2 数据类型:

ROS 2 使用 Data Distribution Service(DDS)的数据类型系统来定义消息和服务。DDS 使用 IDL(Interface Definition Language)来描述数据类型。 - ROS 2 数据类型使用 .idl 文件来定义,其中包含消息字段的名称、类型和其他属性。 - ROS 2 数据类型支持更复杂的数据结构,包括嵌套结构、枚举类型、位字段和序列化机制。 - ROS 2 数据类型使用 DDS 数据类型系统,这是一种更复杂和功能更强大的数据类型系统,支持更多的数据类型和数据结构。

以下是一个 ROS 2 数据类型的示例: 

// ROS 2 Message Definition
#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/string.hpp>

void messageCallback(const std_msgs::msg::String::SharedPtr msg) {
  RCLCPP_INFO(rclcpp::get_logger("ros2_node"), "Received message: %s", msg->data.c_str());
}

int main(int argc, char** argv) {
  rclcpp::init(argc, argv);
  auto node = rclcpp::Node::make_shared("ros2_node");

  auto sub = node->create_subscription<std_msgs::msg::String>(
    "ros2_topic", 10, messageCallback);

  rclcpp::spin(node);

  rclcpp::shutdown();

  return 0;
}

 总结起来,ROS 1 使用自定义的 ROS 数据类型,这是一种简单的 C++ 结构体,而 ROS 2 使用 DDS 数据类型系统,这是一种更复杂和功能更强大的数据类型系统。ROS 2 的数据类型支持更多的数据结构和数据类型,包括嵌套结构、枚举类型和位字段。这使得 ROS 2 在数据类型的定义和使用方面更加灵活和强大。


03. 发布者和订阅者模型

ROS 1 使用发布者和订阅者模型,而 ROS 2 使用主题和订阅者模型

ROS 1 发布者和订阅者模型:
在 ROS 1 中,发布者和订阅者模型是基于话题(Topic)的。发布者将消息发布到特定的话题,而订阅者从相同的话题订阅消息。
ROS 1 使用  `roscpp`  库提供 C++ API 来创建发布者和订阅者。
发布者使用  `ros::Publisher`  类来发布消息,订阅者使用  `ros::Subscriber`  类来订阅消息。
发布者和订阅者之间的通信是异步的,发布者不需要知道订阅者的存在,它只需发布消息到话题中,而订阅者可以选择订阅感兴趣的话题。

以下是 ROS 1 中发布者和订阅者的简单示例代码:

#include <ros/ros.h>
#include <std_msgs/String.h>

void messageCallback(const std_msgs::String::ConstPtr& msg) {
  ROS_INFO("Received message: %s", msg->data.c_str());
}

int main(int argc, char** argv) {
  ros::init(argc, argv, "ros1_node");
  ros::NodeHandle nh;

  ros::Subscriber sub = nh.subscribe("ros1_topic", 10, messageCallback);

  ros::spin();

  return 0;
}

ROS 2 发布者和订阅者模型:
在 ROS 2 中,发布者和订阅者模型是基于主题(Topic)的。发布者将消息发布到特定的主题,而订阅者从相同的主题订阅消息。
ROS 2 使用  `rclcpp`  库提供 C++ API 来创建发布者和订阅者。
发布者使用  `rclcpp::Publisher`  类来发布消息,订阅者使用  `rclcpp::Subscription`  类来订阅消息。
发布者和订阅者之间的通信是异步的,发布者不需要知道订阅者的存在,它只需发布消息到主题中,而订阅者可以选择订阅感兴趣的主题。

以下是 ROS 2 中发布者和订阅者的简单示例代码:

#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/string.hpp>

void messageCallback(const std_msgs::msg::String::SharedPtr msg) {
  RCLCPP_INFO(rclcpp::get_logger("ros2_node"), "Received message: %s", msg->data.c_str());
}

int main(int argc, char** argv) {
  rclcpp::init(argc, argv);
  auto node = rclcpp::Node::make_shared("ros2_node");

  auto sub = node->create_subscription<std_msgs::msg::String>(
    "ros2_topic", 10, messageCallback);

  rclcpp::spin(node);

  rclcpp::shutdown();

  return 0;
}

总结起来,ROS 1 和 ROS 2 在发布者和订阅者模型上的区别主要体现在使用的库和API上。ROS 1 使用  `roscpp`  和  `ros::Publisher` 、 `ros::Subscriber`  类,而 ROS 2 使用  `rclcpp`  和  `rclcpp::Publisher` 、 `rclcpp::Subscription`  类。这些模型的基本概念和用法在两个版本中是相似的,但具体的实现和细节可能有所不同。

04. 服务模型

ROS 1 使用服务模型,而 ROS 2 也使用服务模型

 ROS 1 服务模型:
在 ROS 1 中,服务模型是基于服务(Service)的。服务是一种允许节点之间进行请求和响应的通信机制。
ROS 1 使用  `roscpp`  库提供 C++ API 来创建服务服务器和服务客户端。
服务服务器使用  `ros::ServiceServer`  类来提供服务,服务客户端使用  `ros::ServiceClient`  类来发送服务请求。
服务服务器通过监听特定的服务请求,并根据请求生成响应。服务客户端发送服务请求,并等待接收响应。

以下是 ROS 1 中服务服务器和服务客户端的简单示例代码:

#include <ros/ros.h>
#include <std_srvs/Empty.h>

bool serviceCallback(std_srvs::Empty::Request& req, std_srvs::Empty::Response& res) {
  ROS_INFO("Service called");
  // Perform service logic here
  return true;
}

int main(int argc, char** argv) {
  ros::init(argc, argv, "ros1_node");
  ros::NodeHandle nh;

  ros::ServiceServer server = nh.advertiseService("ros1_service", serviceCallback);

  ros::spin();  return 0;
}

ROS 2 服务模型:
在 ROS 2 中,服务模型也是基于服务(Service)的。服务是一种允许节点之间进行请求和响应的通信机制。
ROS 2 使用  `rclcpp`  库提供 C++ API 来创建服务服务器和服务客户端。
服务服务器使用  `rclcpp::Service`  类来提供服务,服务客户端使用  `rclcpp::Client`  类来发送服务请求。
服务服务器通过监听特定的服务请求,并根据请求生成响应。服务客户端发送服务请求,并等待接收响应。
 

以下是 ROS 2 中服务服务器和服务客户端的简单示例代码:

#include <rclcpp/rclcpp.hpp>
#include <std_srvs/srv/empty.hpp>

bool serviceCallback(const std::shared_ptr<std_srvs::srv::Empty::Request> req,
                     std::shared_ptr<std_srvs::srv::Empty::Response> res) {
  RCLCPP_INFO(rclcpp::get_logger("ros2_node"), "Service called");
  // Perform service logic here
  res->success = true;
  return true;
}

int main(int argc, char** argv) {
  rclcpp::init(argc, argv);
  auto node = rclcpp::Node::make_shared("ros2_node");

  auto server = node->create_service<std_srvs::srv::Empty>(
    "ros2_service", serviceCallback);

  rclcpp::spin(node);

  rclcpp::shutdown();

  return 0;
}

总结起来,ROS 1 和 ROS 2 在服务模型上的区别主要体现在使用的库和API上。ROS 1 使用  `roscpp`  和  `ros::ServiceServer` 、 `ros::ServiceClient`  类,而 ROS 2 使用  `rclcpp`  和  `rclcpp::Service` 、 `rclcpp::Client`  类。这些模型的基本概念和用法在两个版本中是相似的,但具体的实现和细节可能有所不同。


05. 节点模型

ROS 1 使用节点模型,而 ROS 2 也使用节点模型

ROS 1 节点模型:
在 ROS 1 中,节点是 ROS 系统中的基本执行单元,用于发布和订阅消息,提供和调用服务。
ROS 1 节点使用  `ros::NodeHandle`  类来创建和管理节点。
ROS 1 节点通过调用  `ros::spin()`  或  `ros::spinOnce()`  来处理消息和服务请求。
ROS 1 节点使用回调函数来处理接收到的消息和服务请求。

以下是 ROS 1 中创建节点的简单示例代码:

#include "ros/ros.h"

void messageCallback(const std_msgs::String::ConstPtr& msg)
{
  ROS_INFO("Received message: %s", msg->data.c_str());
}

int main(int argc, char** argv)
{
  ros::init(argc, argv, "my_node");
  ros::NodeHandle nh;

  ros::Subscriber sub = nh.subscribe("my_topic", 10, messageCallback);

  ros::spin();

  return 0;
}

ROS 2 节点模型:
在 ROS 2 中,节点仍然是 ROS 系统中的基本执行单元,但使用了更灵活的节点模型。
ROS 2 节点使用  `rclcpp::Node`  类来创建和管理节点。
ROS 2 节点使用异步执行器(executor)来处理消息和服务请求,可以同时处理多个节点。
ROS 2 节点使用回调函数、回调组(callback groups)和回调队列(callback queues)来处理接收到的消息和服务请求。

以下是 ROS 2 中创建节点的简单示例代码:

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

void messageCallback(const std_msgs::msg::String::SharedPtr msg)
{
  RCLCPP_INFO(rclcpp::get_logger("my_node"), "Received message: %s", msg->data.c_str());
}

int main(int argc, char** argv)
{
  rclcpp::init(argc, argv);
  auto node = rclcpp::Node::make_shared("my_node");

  auto subscription = node->create_subscription<std_msgs::msg::String>(
    "my_topic", 10, messageCallback);

  rclcpp::spin(node);

  rclcpp::shutdown();

  return 0;
}

需要注意的是,ROS 2 的节点模型更加灵活和可扩展,允许更高级的异步处理和多节点协同工作。上述示例只展示了基本的节点创建和消息订阅过程,实际应用中可能会涉及更多的功能和配置选项。

06. 系统架构

ROS 1 使用分层系统架构,而 ROS 2 使用分布式系统架构

ROS 1 系统架构:

ROS 1 采用分层系统架构,由多个层级组成,包括节点层、消息传递层和服务调用层。

节点层是 ROS 1 系统的最上层,由多个节点组成,每个节点执行特定的任务。

消息传递层负责在节点之间进行消息传递,使用发布者-订阅者模型。节点可以发布消息到特定的主题,其他节点通过订阅相应主题来接收消息。

服务调用层负责处理节点之间的服务调用,使用客户端-服务器模型。节点可以提供服务,其他节点可以请求并接收服务的响应。 

ROS 2 系统架构:

ROS 2 采用分布式系统架构,支持在多个计算机上分布的节点之间进行通信。

ROS 2 的分布式系统架构基于 Data Distribution Service(DDS),它提供了更强大的消息传递和通信能力。

在 ROS 2 中,节点之间可以直接进行发布-订阅通信,使用主题(Topic)和数据读取器(DataReader)。节点可以发布消息到主题,其他节点可以通过订阅相同的主题来接收消息。

ROS 2 还支持服务调用,使用客户端-服务器模型。节点可以提供服务,其他节点可以请求并接收服务的响应。 

需要注意的是,ROS 2 的分布式系统架构使得节点之间的通信更加灵活和可扩展,可以在不同的计算机上运行节点并进行通信。 


07. 编程语言支持

ROS 1 支持 C++、Python 和其他语言,而 ROS 2 支持 C++、Python、Java 和其他语言

ROS 1 编程语言支持:

ROS 1 支持多种编程语言,包括 C++、Python 和 Lisp。

C++ 是 ROS 1 的主要编程语言,它提供了最全面和最强大的功能支持。ROS 1 使用 C++ 编写的节点可以直接调用 ROS 1 的核心库和功能。

Python 是 ROS 1 中受欢迎的脚本语言,它提供了简单易用的接口和快速的开发速度。ROS 1 的核心库和功能也提供了 Python 的绑定。

Lisp 是 ROS 1 中的一种支持语言,尽管它的使用相对较少。ROS 1 提供了 Lisp 的绑定和一些库,用于在 Lisp 中编写 ROS 1 节点。

ROS 2 编程语言支持:

ROS 2 支持多种编程语言,包括 C++、Python、Java 和 C#。

C++ 是 ROS 2 的主要编程语言,它提供了最全面和最强大的功能支持。ROS 2 使用 C++ 编写的节点可以直接调用 ROS 2 的核心库和功能。

Python 是 ROS 2 中受欢迎的脚本语言,它提供了简单易用的接口和快速的开发速度。ROS 2 的核心库和功能也提供了 Python 的绑定。

Java 和 C# 是 ROS 2 新增的编程语言支持。它们提供了在 ROS 2 中开发跨平台应用程序的选项,使得开发者可以使用这些常用的编程语言来构建 ROS 2 节点和应用程序。

总结起来,ROS 1 主要支持 C++ 和 Python,而 ROS 2 支持更多的编程语言,包括 C++、Python、Java 和 C#。这使得 ROS 2 更加灵活,可以满足不同开发者的偏好和需求。无论是使用 ROS 1 还是 ROS 2,开发者都可以选择最适合自己的编程语言来构建节点和应用程序。 


08. 工具链

ROS 1 使用 catkin 构建系统,而 ROS 2 使用 ament 构建系统

ROS 1 工具链:

ROS 1 使用 catkin 作为其构建系统。Catkin 是一个基于 CMake 的构建系统,用于构建 ROS 1 软件包。

Catkin 提供了一组命令行工具,用于创建、编译和管理 ROS 1 软件包。这些工具包括 catkin_create_pkgcatkin_makecatkin_tools 等。

Catkin 使用 CMakeLists.txt 文件来描述软件包的构建过程和依赖关系。开发者可以通过编辑 CMakeLists.txt 文件来配置软件包的构建选项。

ROS 1 还提供了一些其他的工具,如 rosbuild、rosmake 等,用于旧版的 ROS 1 软件包的构建和管理。

ROS 2 工具链:

ROS 2 使用 ament 作为其构建系统。Ament 是一个基于 CMake 和 Python 的构建系统,用于构建 ROS 2 软件包。

Ament 提供了一组命令行工具,用于创建、编译和管理 ROS 2 软件包。这些工具包括 ament_createament_buildament_tools 等。

Ament 使用 CMakeLists.txt 和 package.xml 文件来描述软件包的构建过程和依赖关系。开发者可以通过编辑这些文件来配置软件包的构建选项和依赖关系。

ROS 2 的构建系统还支持跨平台开发,可以在不同的操作系统上构建和运行 ROS 2 软件包。

ROS 2 的工具链还包括一些其他的工具,如 colcon、ros2cli 等,用于构建和管理 ROS 2 软件包。

总结起来,ROS 1 使用 catkin 作为构建系统,而 ROS 2 使用 ament 作为构建系统。这两个构建系统都基于 CMake,但 ament 在跨平台开发和构建选项的配置方面更加灵活。开发者可以根据自己的需求选择适合的工具链来构建和管理 ROS 1 或 ROS 2 软件包。 


09. 包管理器

ROS 1 使用 rosdep,而 ROS 2 使用 ament

ROS 1 包管理器:

ROS 1 使用的包管理器是 rosdep。rosdep 是一个用于管理 ROS 1 软件包依赖关系的工具。

rosdep 通过解析软件包的依赖关系,并安装所需的系统依赖项,使得 ROS 1 软件包能够在系统中正确运行。

rosdep 使用一个名为 rosdep.yaml 的配置文件来指定软件包的依赖关系和所需的系统依赖项。

开发者可以使用 rosdep install 命令来安装软件包的依赖项。

ROS 2 包管理器:

ROS 2 使用的包管理器是 ament。ament 是一个用于管理 ROS 2 软件包的构建和依赖关系的工具。

ament 使用 CMakeLists.txt 和 package.xml 文件来描述软件包的构建过程和依赖关系。

在 package.xml 文件中,开发者可以列出软件包的依赖项,包括其他 ROS 2 软件包和系统依赖项。

ament 提供了一组命令行工具,如 ament buildament install 等,用于构建和安装 ROS 2 软件包及其依赖项。

ament 还支持跨平台开发,可以在不同的操作系统上构建和运行 ROS 2 软件包。

总结起来,ROS 1 使用的是 rosdep 包管理器,而 ROS 2 使用的是 ament 包管理器。rosdep 用于管理 ROS 1 软件包的依赖关系和系统依赖项,而 ament 用于管理 ROS 2 软件包的构建和依赖关系。这两个包管理器在配置文件和命令行工具上有所不同,开发者需要根据所使用的 ROS 版本选择适合的包管理器来管理软件包的依赖关系。 


10. 文档和教程

ROS 1 和 ROS 2 都有自己的文档和教程,但内容和结构有所不同

 ROS 1 的文档和教程:
- ROS 1 的文档和教程是分散的,分布在不同的网站、博客和论坛上。
- ROS 1 的官方文档是ROS Wiki(ROS1文档与教程),其中包含了ROS 1 的核心概念、常用命令、工具和库的详细说明。
- ROS 1 的教程和示例代码也可以在ROS Wiki上找到,涵盖了从入门到高级应用的各个方面。
- ROS 1 的文档和教程由社区贡献者维护,包括ROS核心团队和其他ROS用户。
 
ROS 2 的文档和教程:
- ROS 2 的文档和教程更加集中和统一,主要集中在ROS 2 官方网站(ROS2文档与教程)上。
- ROS 2 的官方文档提供了全面的介绍,包括ROS 2 的核心概念、架构、工具和库的详细说明。
- ROS 2 的教程和示例代码也可以在官方文档中找到,涵盖了从入门到高级应用的各个方面。
- ROS 2 的文档和教程由ROS核心团队维护,确保准确性和最新性。
- ROS 2 的官方文档还提供了更多的细节和深入的指南,以帮助用户更好地理解ROS 2 的设计和使用。
 
需要注意的是,ROS 1 的文档和教程经过多年的发展,已经非常丰富和成熟。而 ROS 2 的文档和教程相对较新,但随着ROS 2 的发展和社区支持的增加,它们也在不断完善和扩充。无论是ROS 1 还是 ROS 2,官方文档和教程是入门和学习的重要资源,用户可以根据自己的需求选择相应的版本进行学习和参考。


11. 社区支持

ROS 1 社区相对较成熟,而 ROS 2 社区在不断发展中

ROS 1 的社区支持:
- ROS 1 是一个经过多年发展的成熟平台,拥有广泛的社区支持。
- ROS 1 社区庞大,拥有大量的用户和开发者,他们积极参与讨论、提问和解答问题。
- ROS 1 社区提供了丰富的文档、教程、示例代码和案例研究,可以帮助新手入门并解决常见的问题。
- ROS 1 社区经验丰富,有许多专家和资深开发者,他们能够提供深入的技术支持和解决方案。
- ROS 1 社区还定期举办各种活动,如会议、研讨会和培训课程,为用户提供交流和学习的机会。
 
ROS 2 的社区支持:
- ROS 2 是相对较新的平台,其社区支持正在不断发展和壮大。
- ROS 2 社区规模逐渐增长,吸引了越来越多的用户和开发者,他们积极参与讨论和贡献代码。
- ROS 2 社区致力于提供高质量的文档和教程,以帮助用户快速上手和解决问题。
- ROS 2 社区正在建设更多的示例代码和案例研究,以展示 ROS 2 的功能和应用场景。
- ROS 2 社区也有专家和资深开发者,他们提供技术支持和解答用户的问题。
- ROS 2 社区还不断举办各种活动,如会议、研讨会和在线讲座,为用户提供交流和学习的机会。
 
需要注意的是,由于 ROS 1 是一个成熟的平台,其社区支持更加成熟和完善。而 ROS 2 相对较新,其社区支持正在逐渐发展和壮大。无论是 ROS 1 还是 ROS 2,社区都是一个宝贵的资源,用户可以通过参与社区来获取帮助、分享经验和学习最佳实践。 


12. 跨平台支持

ROS 1 在某些平台上可能存在限制,而 ROS 2 更加注重跨平台支持

ROS 1 的跨平台支持:
- ROS 1 最初是为Linux操作系统设计和开发的,因此在Linux上具有最好的支持。
- ROS 1 也可以在其他操作系统上运行,如macOS和Windows,但对于这些非Linux平台,ROS 1 的支持相对较少。
- 在非Linux平台上,ROS 1 的安装和配置可能相对复杂,并且某些功能可能不完全支持或存在一些限制。
 
ROS 2 的跨平台支持:
- ROS 2 是为更广泛的平台支持而设计的,具有更好的跨平台兼容性。
- ROS 2 可以在多个操作系统上运行,包括Linux、macOS、Windows和嵌入式操作系统(如FreeRTOS、QNX等)。
- ROS 2 在不同平台上提供了一致的安装和配置过程,使得在跨平台环境中使用ROS 2 更加简单和可靠。
- ROS 2 的官方文档和支持也更加注重跨平台问题,并提供了特定平台的指南和建议。
 
需要注意的是,虽然 ROS 2 在跨平台支持方面更加强大和全面,但仍然可能存在一些特定平台上的限制或问题。在选择ROS版本和平台时,建议参考官方文档和社区支持,以了解特定平台的支持程度和可能的限制。
 
总结起来,ROS 1 在跨平台支持方面相对较弱,主要支持Linux平台。而 ROS 2 在跨平台支持方面更加强大,可以在多个操作系统和嵌入式平台上运行,并提供了一致的安装和配置过程。 


13. 安全性

ROS 2 支持更高级别的安全性,包括身份验证和加密

ROS 1 安全性:
- ROS 1 在设计之初并没有将安全性作为核心考虑因素。它主要关注于功能性和性能,而没有提供内置的安全性机制。
- ROS 1 在网络通信方面使用的是简单的 TCP/IP 协议,没有内置的加密和身份验证机制。
- 在 ROS 1 中,安全性主要依赖于底层操作系统和网络安全措施,例如防火墙和虚拟专用网络(VPN)等。
 
ROS 2 安全性:
- ROS 2 在设计之初将安全性作为一个重要的考虑因素。它提供了更强大的安全性功能,以满足现代分布式系统的安全需求。
- ROS 2 使用 Data Distribution Service(DDS)作为消息传递协议,而 DDS 提供了内置的安全性机制。
- 在 ROS 2 中,可以使用 Transport Layer Security(TLS)来加密通信,并使用 X.509 证书进行身份验证。
- ROS 2 还提供了访问控制机制,可以限制对主题和服务的访问权限。
- ROS 2 的安全性还包括对数据完整性和可靠性的保护。
 
总结起来,与 ROS 1 相比,ROS 2 在安全性方面提供了更多的功能和保护机制。ROS 2 使用 DDS 提供内置的安全性机制,包括加密、身份验证和访问控制。这使得 ROS 2 更适合于在安全性要求较高的环境中使用,例如在工业控制系统和机器人应用中。 


14. 实时性能

ROS 2 在实时性能方面有所改进

ROS 1 实时性能:
- ROS 1 在设计之初并没有将实时性能作为核心考虑因素。它主要关注于功能性和灵活性,而非实时性能。
- ROS 1 使用的消息传递机制(RMP)基于 TCP/IP 网络通信,这在实时性能方面存在一些限制。
- ROS 1 的消息传递机制没有提供硬实时保证,即无法保证消息的传递和处理在严格的实时要求下完成。
 
ROS 2 实时性能:
- ROS 2 在设计之初将实时性能作为一个重要的考虑因素。它提供了更强大的实时性能功能,以满足实时控制和通信的需求。
- ROS 2 使用 Data Distribution Service(DDS)作为消息传递协议,而 DDS 提供了实时性能保证的机制。
- DDS 支持 Quality of Service(QoS)配置,可以根据应用程序的实时性需求进行调整,包括消息传递的可靠性、优先级和时间约束等。
- ROS 2 还提供了实时性能测试工具和性能分析工具,用于评估和优化实时控制和通信的性能。
 
需要注意的是,ROS 2 的实时性能取决于所使用的 DDS 实现和配置。不同的 DDS 实现可能在实时性能方面有所差异。一些常用的 DDS 实现,如 RTI Connext DDS 和 eProsima Fast DDS,提供了高度可配置的实时性能选项,以满足不同应用场景的需求。
 
总结起来,与 ROS 1 相比,ROS 2 在实时性能方面提供了更多的功能和保证机制。ROS 2 使用 DDS 提供实时性能保证,并支持 QoS 配置和性能分析工具,使其更适合于实时控制和通信应用。然而,实时性能的具体表现取决于所选择的 DDS 实现和配置。 


15. 多语言通信

ROS 2 支持不同语言之间的通信

ROS 1 多语言通信:
- ROS 1 支持多种编程语言,包括 C++、Python 和其他一些语言(如Java、Lisp等)。
- 在 ROS 1 中,不同语言的节点可以通过ROS Master进行通信。节点可以使用ROS提供的通信机制(如发布者/订阅者、服务)来交换消息和数据。
- ROS 1 使用ROS消息传递协议(RMP)来在不同语言之间传递消息。RMP 使用ROS数据类型进行序列化和反序列化,以实现语言间的互操作性。
 
ROS 2 多语言通信:
- ROS 2 支持更多的编程语言,包括 C++、Python、Java 和 C#。这使得开发人员可以使用他们最熟悉的编程语言来编写ROS 2节点。
- ROS 2 使用Data Distribution Service(DDS)作为消息传递协议,而DDS本身支持多语言通信。DDS使用IDL(Interface Definition Language)来定义数据类型和接口,从而实现不同语言之间的互操作性。
- 在ROS 2中,不同语言的节点可以直接通过DDS进行通信,而无需通过ROS Master。DDS提供了一种分布式消息传递机制,使得不同语言的节点可以直接交换消息和数据。
 
总结起来,ROS 1 和 ROS 2 在多语言通信方面有一些区别。ROS 1 支持多种编程语言,并使用ROS消息传递协议进行通信。ROS 2 支持更多的编程语言,并使用DDS作为消息传递协议,使得不同语言的节点可以直接通过DDS进行通信。这使得ROS 2在多语言通信方面更加灵活和强大。 


16. 网络通信

ROS 2 使用更灵活的网络通信机制

ROS 1 网络通信:
- ROS 1 使用 TCPROS(TCP/IP)作为默认的网络传输层协议。它使用基于TCP的点对点通信模型,其中发布者和订阅者直接连接并交换消息。
- ROS 1 的网络通信是基于主节点(ROS Master)的中心化架构。主节点负责管理节点之间的连接和消息路由。
- ROS 1 网络通信在大规模系统中可能存在性能瓶颈,因为主节点需要处理所有节点之间的通信。
 
ROS 2 网络通信:
- ROS 2 使用 DDS(Data Distribution Service)作为默认的网络传输层协议。DDS 是一种分布式消息传递协议,支持高度可配置的、实时的、可靠的消息传递。
- ROS 2 的网络通信是基于分布式架构的,节点可以直接与其他节点通信,而无需通过中心化的主节点。
- ROS 2 使用 DDS 的发布-订阅模型,其中发布者和订阅者通过 DDS 中间件进行通信。DDS 提供了更灵活的网络通信机制,支持多播、安全性和更高级别的服务质量。
 
总结起来,ROS 1 使用基于TCP的点对点通信模型,其中节点通过主节点进行通信。ROS 2 使用 DDS 作为网络传输层协议,支持分布式通信和更灵活的网络通信机制。ROS 2 的网络通信具有更好的可扩展性和性能,并支持多播、安全性和更高级别的服务质量。这使得 ROS 2 在大规模系统和实时应用中更为适用。 


17. 参数服务器

ROS 2 中的参数服务器有所改进

ROS 1 参数服务器:
- ROS 1 使用参数服务器来存储和共享节点的参数。参数是键值对的形式,可以在运行时进行设置和获取。
- ROS 1 的参数服务器是全局的,可以在整个 ROS 系统中访问。节点可以从参数服务器中读取参数,并将参数写入参数服务器。
- ROS 1 的参数服务器使用  `ros::param`  API 来访问和操作参数。例如,可以使用  `ros::param::get()`  来获取参数的值,使用  `ros::param::set()`  来设置参数的值。


以下是 ROS 1 中使用参数服务器的示例代码:

#include <ros/ros.h>

int main(int argc, char** argv) {
  ros::init(argc, argv, "parameter_example");
  ros::NodeHandle nh;

  // 设置参数
  nh.setParam("my_param", 42);

  // 获取参数
  int param_value;
  nh.getParam("my_param", param_value);
  ROS_INFO("Parameter value: %d", param_value);

  return 0;
}

ROS 2 参数服务器:
- ROS 2 也有参数服务器的概念,但与 ROS 1 不同。ROS 2 使用分布式参数服务器,每个节点都有自己的本地参数服务器。
- ROS 2 的参数服务器是基于 DDS 实现的,它使用 DDS 的数据分发机制来在节点之间共享参数。
- ROS 2 的参数服务器使用  `rclcpp::Parameter`  类来表示参数,并使用  `rclcpp::Node::declare_parameter()`  和  `rclcpp::Node::get_parameter()`  等方法来访问和操作参数。


以下是 ROS 2 中使用参数服务器的示例代码:

#include <rclcpp/rclcpp.hpp>

int main(int argc, char** argv) {
  rclcpp::init(argc, argv);
  auto node = rclcpp::Node::make_shared("parameter_example");

  // 设置参数
  node->declare_parameter("my_param", 42);

  // 获取参数
  int param_value;
  node->get_parameter("my_param", param_value);
  RCLCPP_INFO(node->get_logger(), "Parameter value: %d", param_value);

  rclcpp::shutdown();

  return 0;
}

总结起来,ROS 1 使用全局参数服务器来存储和共享节点的参数,而 ROS 2 使用分布式参数服务器,每个节点都有自己的本地参数服务器。ROS 1 使用  `ros::param`  API,而 ROS 2 使用  `rclcpp::Parameter`  类和相关方法来访问和操作参数。


18. 日志记录

ROS 2 有更强大的日志记录功能

ROS 1 日志记录:
- ROS 1 使用  `rosconsole`  库来进行日志记录。 `rosconsole`  提供了一组宏,用于在节点代码中记录日志消息。
- ROS 1 的日志消息级别包括  `DEBUG` 、 `INFO` 、 `WARN` 、 `ERROR`  和  `FATAL` 。
- ROS 1 的日志消息可以通过设置环境变量  `ROS_LOG_LEVEL`  来控制输出级别。例如,可以设置为  `DEBUG`  来打印所有级别的日志消息。
- ROS 1 的日志消息默认输出到终端,可以通过设置环境变量  `ROS_LOG_DIR`  来指定日志文件的保存路径。

以下是 ROS 1 中记录日志的示例代码:

#include <ros/ros.h>

int main(int argc, char** argv) {
  ros::init(argc, argv, "logging_example");
  ros::NodeHandle nh;

  // 记录日志消息
  ROS_DEBUG("This is a debug message");
  ROS_INFO("This is an info message");
  ROS_WARN("This is a warning message");
  ROS_ERROR("This is an error message");
  ROS_FATAL("This is a fatal message");

  return 0;
}

ROS 2 日志记录:
- ROS 2 使用  `rclcpp::Logger`  类来进行日志记录。 `rclcpp::Logger`  提供了一组方法,用于在节点代码中记录日志消息。
- ROS 2 的日志消息级别包括  `DEBUG` 、 `INFO` 、 `WARN` 、 `ERROR`  和  `FATAL` 。
- ROS 2 的日志消息可以通过设置环境变量  `RCUTILS_CONSOLE_OUTPUT_FORMAT`  来控制输出格式。例如,可以设置为  `json`  来以 JSON 格式输出日志消息。
- ROS 2 的日志消息默认输出到终端,可以通过设置环境变量  `RCUTILS_LOGGING_OUTPUT_FILE`  来将日志消息保存到文件中。

以下是 ROS 2 中记录日志的示例代码:

#include <rclcpp/rclcpp.hpp>

int main(int argc, char** argv) {
  rclcpp::init(argc, argv);
  auto node = rclcpp::Node::make_shared("logging_example");

  // 记录日志消息
  RCLCPP_DEBUG(node->get_logger(), "This is a debug message");
  RCLCPP_INFO(node->get_logger(), "This is an info message");
  RCLCPP_WARN(node->get_logger(), "This is a warning message");
  RCLCPP_ERROR(node->get_logger(), "This is an error message");
  RCLCPP_FATAL(node->get_logger(), "This is a fatal message");

  rclcpp::shutdown();

  return 0;
}

总结起来,ROS 1 使用  `rosconsole`  库进行日志记录,而 ROS 2 使用  `rclcpp::Logger`  类进行日志记录。它们都支持不同级别的日志消息,并可以通过设置环境变量来控制日志输出的级别和格式。


19. 调试工具

ROS 2 提供了更多的调试工具

 ROS 1 调试工具:
- ROS 1 提供了一些常用的调试工具,如  rostopic 、 rosnode 、 rosmsg 、 roslaunch  等。
-  rostopic  工具用于查看和发布 ROS 1 主题的消息。
-  rosnode  工具用于查看和管理 ROS 1 节点。
-  rosmsg  工具用于查看和解析 ROS 1 消息的结构。
-  roslaunch  工具用于启动和管理 ROS 1 启动文件,可以同时启动多个节点。
 
ROS 2 调试工具:
- ROS 2 提供了一系列新的调试工具,旨在提供更强大和全面的调试功能。
-  ros2 topic  工具与  rostopic  类似,用于查看和发布 ROS 2 主题的消息。
-  ros2 node  工具与  rosnode  类似,用于查看和管理 ROS 2 节点。
-  ros2 msg  工具与  rosmsg  类似,用于查看和解析 ROS 2 消息的结构。
-  ros2 launch  工具与  roslaunch  类似,用于启动和管理 ROS 2 启动文件,可以同时启动多个节点。
- ROS 2 还提供了更高级的调试工具,如  rqt 、 rviz2 、 ros2 doctor  等。
-  rqt  是一个可扩展的图形化调试工具,可以用于可视化和调试 ROS 2 数据。
-  rviz  是一个三维可视化工具,用于可视化和调试机器人的传感器数据和状态。
-  ros2 doctor  工具用于检查和诊断 ROS 2 系统的配置和运行状态,帮助解决常见问题。
 
总结起来,ROS 1 提供了一些常用的调试工具,如  rostopic 、 rosnode 、 rosmsg 、 roslaunch  等。而 ROS 2 在调试工具方面更加全面和强大,提供了  ros2 topic 、 ros2 node 、 ros2 msg 、 ros2 launch 、 rqt 、 rviz 、 ros2 doctor  等工具,以支持更高级的调试和可视化功能。这使得在ROS 2中进行调试和故障排除更加方便和直观。


20. 可靠性

ROS 2 在通信和节点恢复方面更加可

ROS 1 可靠性:

- ROS 1 的可靠性是通过发布者和订阅者之间的 TCPROS 协议实现的。 - TCPROS 是一种基于 TCP 的传输协议,它提供了一定程度的消息传递保证,但不是完全可靠的。 - 在 ROS 1 中,如果发布者和订阅者之间的连接断开,消息可能会丢失,因为没有内置的机制来保证消息的可靠传递。 - ROS 1 还依赖于网络通信和主机之间的稳定连接,如果网络或主机出现故障,可能会导致消息丢失或通信中断。

ROS 2 可靠性:

- ROS 2 使用 Data Distribution Service(DDS)作为其消息传递协议,DDS 提供了更可靠的消息传递机制。 - DDS 支持可靠性保证,确保消息的可靠传递,即使在不稳定的网络环境下也能保持通信的完整性。 - DDS 使用可配置的 QoS(Quality of Service)参数来确保消息的可靠性。可以通过设置参数来控制消息的传输方式、重试机制和确认机制。 - 在 ROS 2 中,DDS 提供了消息的持久性,即使在节点重新启动后,也可以保留和传递之前发布的消息。 - DDS 还支持发布者和订阅者之间的历史数据查询,可以获取之前发布的消息的历史记录。

总结起来,ROS 2 在可靠性方面相对于 ROS 1 有一些改进。ROS 2 使用 DDS 作为消息传递协议,提供了更可靠的消息传递机制,支持可靠性保证、持久性和历史数据查询。这使得在不稳定的网络环境下,ROS 2 能够更可靠地传递消息,并确保通信的完整性。 

未完待续。。。

#include <rclcpp/rclcpp.hpp> #include <geometry_msgs/msg/twist_stamped.hpp> #include <mavros_msgs/srv/command_bool.hpp> #include <mavros_msgs/srv/set_mode.hpp> #include <nav_msgs/msg/odometry.hpp> #include <tf2/LinearMath/Quaternion.h> #include <tf2/LinearMath/Matrix3x3.h> #include <chrono> using namespace std::chrono_literals; class OffboardController : public rclcpp::Node { public: OffboardController() : Node("offboard_controller"), current_state(State::INIT) { // 创建发布者客户端 cmd_vel_pub = this->create_publisher<geometry_msgs::msg::TwistStamped>("/mavros/setpoint_velocity/cmd_vel", 10); odom_sub = this->create_subscription<nav_msgs::msg::Odometry>( "/mavros/odometry/in", 10, std::bind(&OffboardController::odom_cb, this, std::placeholders::_1)); arming_client = this->create_client<mavros_msgs::srv::CommandBool>("/mavros/cmd/arming"); set_mode_client = this->create_client<mavros_msgs::srv::SetMode>("/mavros/set_mode"); // 主控制定时器 (20Hz) timer = this->create_wall_timer(50ms, std::bind(&OffboardController::control_loop, this)); } private: // 飞行状态枚举 enum class State { INIT, // 初始化 ARMING, // 解锁 TAKEOFF, // 起飞 HOVER_PRE, // 起飞后悬停 FORWARD, // 向前飞行 TURNING, // 转向 HEADING_FLIGHT, // 机头方向飞行 HOVER_POST, // 飞行后悬停 LANDING, // 降落 DISARM // 上锁 }; // 状态变量 State current_state; rclcpp::Time state_start_time; double start_yaw = 0.0; double current_x = 0.0, current_y = 0.0, current_z = 0.0, current_yaw = 0.0; bool odom_received = false; // ROS 接口 rclcpp::Publisher<geometry_msgs::msg::TwistStamped>::SharedPtr cmd_vel_pub; rclcpp::Subscription<nav_msgs::msg::Odometry>::SharedPtr odom_sub; rclcpp::Client<mavros_msgs::srv::CommandBool>::SharedPtr arming_client; rclcpp::Client<mavros_msgs::srv::SetMode>::SharedPtr set_mode_client; rclcpp::TimerBase::SharedPtr timer; // 位置姿态回调 void odom_cb(const nav_msgs::msg::Odometry::SharedPtr msg) { current_x = msg->pose.pose.position.x; current_y = msg->pose.pose.position.y; current_z = msg->pose.pose.position.z; // 从四元数提取偏航角 tf2::Quaternion q( msg->pose.pose.orientation.x, msg->pose.pose.orientation.y, msg->pose.pose.orientation.z, msg->pose.pose.orientation.w); tf2::Matrix3x3 m(q); double roll, pitch; m.getRPY(roll, pitch, current_yaw); odom_received = true; } // 主控制循环 void control_loop() { if (!odom_received) { RCLCPP_INFO(this->get_logger(), "等待位置信息..."); return; } auto twist_msg = geometry_msgs::msg::TwistStamped(); twist_msg.header.stamp = this->now(); twist_msg.header.frame_id = "map"; // 使用地图坐标系 switch (current_state) { case State::INIT: RCLCPP_INFO(this->get_logger(), "初始化..."); send_velocity(0, 0, 0.5, 0); // 持续发送设定点 if (set_flight_mode("OFFBOARD")) { transition_to(State::ARMING); } break; case State::ARMING: RCLCPP_INFO(this->get_logger(), "解锁中..."); if (arm_drone(true)) { transition_to(State::TAKEOFF); } break; case State::TAKEOFF: RCLCPP_INFO(this->get_logger(), "起飞中..."); send_velocity(0, 0, 0.5, 0); // 以0.5m/s上升 if (current_z > 2.5) { // 达到目标高度 start_yaw = current_yaw; // 记录初始偏航角 transition_to(State::HOVER_PRE); } break; case State::HOVER_PRE: RCLCPP_INFO(this->get_logger(), "悬停中(3秒)..."); send_velocity(0, 0, 0, 0); if ((this->now() - state_start_time) > 3s) { transition_to(State::FORWARD); } break; case State::FORWARD: RCLCPP_INFO(this->get_logger(), "向前飞行..."); send_velocity(5, 0, 0, 0); // 5m/s向前 if (current_x > 10.0) { // 飞行10米 transition_to(State::TURNING); } break; case State::TURNING: RCLCPP_INFO(this->get_logger(), "右转30度..."); send_velocity(0, 0, 0, 0.5); // 0.5rad/s右转 if (fabs(current_yaw - start_yaw) > 0.5236) { // 30度≈0.5236弧度 transition_to(State::HEADING_FLIGHT); } break; case State::HEADING_FLIGHT: RCLCPP_INFO(this->get_logger(), "机头方向飞行..."); send_velocity(5, 0, 0, 0); // 5m/s沿新方向 if (current_x > 16.0) { // 飞行6米 transition_to(State::HOVER_POST); } break; case State::HOVER_POST: RCLCPP_INFO(this->get_logger(), "悬停中(4秒)..."); send_velocity(0, 0, 0, 0); if ((this->now() - state_start_time) > 4s) { transition_to(State::LANDING); } break; case State::LANDING: RCLCPP_INFO(this->get_logger(), "降落地中..."); send_velocity(0, 0, -0.5, 0); // 0.5m/s下降 if (current_z < 0.3) { // 接近地面 transition_to(State::DISARM); } break; case State::DISARM: RCLCPP_INFO(this->get_logger(), "上锁中..."); send_velocity(0, 0, 0, 0); arm_drone(false); // 上锁 rclcpp::shutdown(); break; } } // 状态转换函数 void transition_to(State new_state) { RCLCPP_INFO(this->get_logger(), "状态转换: %d -> %d", static_cast<int>(current_state), static_cast<int>(new_state)); current_state = new_state; state_start_time = this->now(); } // 发送速度指令 void send_velocity(double vx, double vy, double vz, double yaw_rate) { auto twist_msg = geometry_msgs::msg::TwistStamped(); twist_msg.header.stamp = this->now(); twist_msg.header.frame_id = "map"; twist_msg.twist.linear.x = vx; twist_msg.twist.linear.y = vy; twist_msg.twist.linear.z = vz; twist_msg.twist.angular.z = yaw_rate; cmd_vel_pub->publish(twist_msg); } // 设置飞行模式 bool set_flight_mode(const std::string& mode) { auto request = std::make_shared<mavros_msgs::srv::SetMode::Request>(); request->custom_mode = mode; while (!set_mode_client->wait_for_service(1s)) { if (!rclcpp::ok()) return false; RCLCPP_WARN(this->get_logger(), "等待set_mode服务..."); } auto result = set_mode_client->async_send_request(request); if (rclcpp::spin_until_future_complete(this->get_node_base_interface(), result) == rclcpp::FutureReturnCode::SUCCESS) { return result.get()->mode_sent; } return false; } // 解锁/上锁无人机 bool arm_drone(bool arm) { auto request = std::make_shared<mavros_msgs::srv::CommandBool::Request>(); request->value = arm; while (!arming_client->wait_for_service(1s)) { if (!rclcpp::ok()) return false; RCLCPP_WARN(this->get_logger(), "等待arming服务..."); } auto result = arming_client->async_send_request(request); if (rclcpp::spin_until_future_complete(this->get_node_base_interface(), result) == rclcpp::FutureReturnCode::SUCCESS) { return result.get()->success; } return false; } }; int main(int argc, char** argv) { rclcpp::init(argc, argv); auto node = std::make_shared<OffboardController>(); rclcpp::spin(node); rclcpp::shutdown(); return 0; } 将上面的代码改成ROS1代码
07-25
<think>我们正在将ROS2代码迁移到ROS1。根据用户提供的引用之前的对话历史,我们知道需要关注以下变化: 1. 头文件替换 2. 节点初始化方式 3. 发布者、订阅者、定时器的创建方式 4. 日志宏的替换 5. 参数获取方式 由于用户没有提供具体的OffboardController代码,我将假设一个典型的Offboard控制节点,包含以下功能: - 定时器回调 - 发布姿态设定点(例如:geometry_msgs/PoseStamped) - 可能有服务调用(例如:设置模式、解锁) 下面是一个典型的ROS2 Offboard控制节点结构(简化版): ```cpp #include <rclcpp/rclcpp.hpp> #include <geometry_msgs/msg/pose_stamped.hpp> #include <mavros_msgs/srv/command_bool.hpp> #include <mavros_msgs/srv/set_mode.hpp> using namespace std::chrono_literals; class OffboardController : public rclcpp::Node { public: OffboardController() : Node("offboard_controller") { // 创建发布者 pose_pub_ = this->create_publisher<geometry_msgs::msg::PoseStamped>("setpoint_position/local", 10); // 创建服务客户端 arm_client_ = this->create_client<mavros_msgs::srv::CommandBool>("mavros/cmd/arming"); set_mode_client_ = this->create_client<mavros_msgs::srv::SetMode>("mavros/set_mode"); // 定时器,每100毫秒执行一次 timer_ = this->create_wall_timer(100ms, std::bind(&OffboardController::timer_callback, this)); } private: void timer_callback() { // 发布设定点 auto pose = geometry_msgs::msg::PoseStamped(); pose.pose.position.x = 0; pose.pose.position.y = 0; pose.pose.position.z = 2; pose_pub_->publish(pose); // 在某个条件下调用服务 if (count_ > 10) { arm_drone(); set_offboard_mode(); } count_++; } void arm_drone() { auto request = std::make_shared<mavros_msgs::srv::CommandBool::Request>(); request->value = true; auto result_future = arm_client_->async_send_request(request); // 处理响应(略) } void set_offboard_mode() { auto request = std::make_shared<mavros_msgs::srv::SetMode::Request>(); request->custom_mode = "OFFBOARD"; auto result_future = set_mode_client_->async_send_request(request); // 处理响应(略) } rclcpp::Publisher<geometry_msgs::msg::PoseStamped>::SharedPtr pose_pub_; rclcpp::Client<mavros_msgs::srv::CommandBool>::SharedPtr arm_client_; rclcpp::Client<mavros_msgs::srv::SetMode>::SharedPtr set_mode_client_; rclcpp::TimerBase::SharedPtr timer_; int count_ = 0; }; int main(int argc, char * argv[]) { rclcpp::init(argc, argv); rclcpp::spin(std::make_shared<OffboardController>()); rclcpp::shutdown(); return 0; } ``` 现在,我们将这段代码迁移到ROS1。迁移要点如下: 1. 头文件替换: - `rclcpp/rclcpp.hpp` -> `ros/ros.h` - `geometry_msgs/msg/pose_stamped.hpp` -> `geometry_msgs/PoseStamped.h` - 服务消息头文件路径变化:`mavros_msgs/srv/command_bool.hpp` -> `mavros_msgs/CommandBool.h` (注意:ROS1中服务消息的头文件在包名后直接是服务名,没有srv目录) 2. 节点不再继承自`rclcpp::Node`,而是使用`ros::NodeHandle`。 3. 发布者、订阅者、定时器的创建方式不同。 4. 服务客户端的创建调用方式不同。 5. 日志宏从`RCLCPP_INFO`变为`ROS_INFO`。 6. 主函数使用`ros::init``ros::spin`。 下面是迁移后的ROS1代码: ```cpp #include <ros/ros.h> #include <geometry_msgs/PoseStamped.h> #include <mavros_msgs/CommandBool.h> #include <mavros_msgs/SetMode.h> class OffboardController { public: OffboardController() { // 创建发布者 pose_pub_ = nh_.advertise<geometry_msgs::PoseStamped>("setpoint_position/local", 10); // 创建服务客户端 arm_client_ = nh_.serviceClient<mavros_msgs::CommandBool>("mavros/cmd/arming"); set_mode_client_ = nh_.serviceClient<mavros_msgs::SetMode>("mavros/set_mode"); // 定时器,每0.1秒执行一次 (100ms -> 0.1秒) timer_ = nh_.createTimer(ros::Duration(0.1), &OffboardController::timer_callback, this); } private: void timer_callback(const ros::TimerEvent& event) { // 发布设定点 geometry_msgs::PoseStamped pose; pose.pose.position.x = 0; pose.pose.position.y = 0; pose.pose.position.z = 2; pose_pub_.publish(pose); // 在某个条件下调用服务 if (count_ > 10) { arm_drone(); set_offboard_mode(); } count_++; } void arm_drone() { mavros_msgs::CommandBool srv; srv.request.value = true; if (arm_client_.call(srv)) { ROS_INFO("Arming result: %d", srv.response.success); } else { ROS_ERROR("Failed to call arming service"); } } void set_offboard_mode() { mavros_msgs::SetMode srv; srv.request.custom_mode = "OFFBOARD"; if (set_mode_client_.call(srv)) { ROS_INFO("Set mode result: %d", srv.response.mode_sent); } else { ROS_ERROR("Failed to call set_mode service"); } } ros::NodeHandle nh_; ros::Publisher pose_pub_; ros::ServiceClient arm_client_; ros::ServiceClient set_mode_client_; ros::Timer timer_; int count_ = 0; }; int main(int argc, char **argv) { ros::init(argc, argv, "offboard_controller"); OffboardController offboard_controller; ros::spin(); return 0; } ``` 迁移说明: 1. 类`OffboardController`不再继承自节点类,而是包含一个节点句柄`ros::NodeHandle nh_`作为成员变量。 2. 发布者使用`nh_.advertise`创建,并指定消息类型话题名称。 3. 服务客户端使用`nh_.serviceClient<ServiceType>(service_name)`创建。 4. 定时器使用`nh_.createTimer`创建,并指定周期(以秒为单位)回调函数。 5. 定时器回调函数有一个`const ros::TimerEvent&`参数,即使不使用也要写上。 6. 服务调用方式:创建服务对象,设置请求,然后调用`call`方法并传入服务对象。响应在同一个服务对象的响应部分。 7. 主函数中,初始化ROS节点,创建控制器对象,然后进入`ros::spin()`循环。 注意:在ROS1中,服务调用是阻塞的,而在ROS2中我们可以使用异步调用。这里我们改成了阻塞调用,并检查了调用是否成功。 此外,如果原始ROS2代码使用了参数,那么在ROS1中需要使用`ros::param::get`来获取参数。 请根据实际代码调整上述示例。如果有具体的代码细节,可以进一步调整。</think>### 将ROS2 OffboardController迁移到ROS1的完整指南 #### 1. 头文件替换 ```diff - #include <rclcpp/rclcpp.hpp> - #include <geometry_msgs/msg/pose_stamped.hpp> - #include <mavros_msgs/srv/command_bool.hpp> + #include <ros/ros.h> + #include <geometry_msgs/PoseStamped.h> + #include <mavros_msgs/CommandBool.h> ``` #### 2. 节点初始化重构 ```cpp // ROS1版本 int main(int argc, char **argv) { ros::init(argc, argv, "offboard_controller"); OffboardController controller; ros::spin(); return 0; } ``` #### 3. 类定义修改 ```cpp // ROS1版本 (不继承Node) class OffboardController { public: OffboardController() : nh_("~") { // 初始化组件 } private: ros::NodeHandle nh_; // 其他成员 }; ``` #### 4. 发布者/订阅者迁移 ```cpp // ROS1发布者 pose_pub_ = nh_.advertise<geometry_msgs::PoseStamped>("setpoint_position/local", 10); // ROS1服务客户端 arm_client_ = nh_.serviceClient<mavros_msgs::CommandBool>("mavros/cmd/arming"); ``` #### 5. 定时器回调迁移 ```diff - timer_ = create_wall_timer(100ms, [this](){ timer_callback(); }); + timer_ = nh_.createTimer(ros::Duration(0.1), &OffboardController::timer_callback, this); ``` #### 6. 服务调用修改 ```cpp // ROS1服务调用 mavros_msgs::CommandBool arm_cmd; arm_cmd.request.value = true; if (arm_client_.call(arm_cmd)) { if (arm_cmd.response.success) { ROS_INFO("Vehicle armed"); } } ``` #### 7. 完整迁移示例 ```cpp #include <ros/ros.h> #include <geometry_msgs/PoseStamped.h> #include <mavros_msgs/CommandBool.h> #include <mavros_msgs/SetMode.h> class OffboardController { public: OffboardController() : nh_("~") { // 初始化发布者 pose_pub_ = nh_.advertise<geometry_msgs::PoseStamped>("setpoint_position/local", 10); // 初始化服务客户端 arm_client_ = nh_.serviceClient<mavros_msgs::CommandBool>("mavros/cmd/arming"); set_mode_client_ = nh_.serviceClient<mavros_msgs::SetMode>("mavros/set_mode"); // 初始化定时器 (100ms) timer_ = nh_.createTimer(ros::Duration(0.1), &OffboardController::timer_callback, this); } private: void timer_callback(const ros::TimerEvent&) { // 发布设定点 geometry_msgs::PoseStamped pose; pose.pose.position.x = 0; pose.pose.position.y = 0; pose.pose.position.z = 2; pose_pub_.publish(pose); // 解锁无人机 if (count_ > 10) { arm_drone(); set_offboard_mode(); } count_++; } void arm_drone() { mavros_msgs::CommandBool arm_cmd; arm_cmd.request.value = true; if (arm_client_.call(arm_cmd) && arm_cmd.response.success) { ROS_INFO("Vehicle armed"); } else { ROS_ERROR("Arming failed"); } } void set_offboard_mode() { mavros_msgs::SetMode offb_set_mode; offb_set_mode.request.custom_mode = "OFFBOARD"; if (set_mode_client_.call(offb_set_mode) && offb_set_mode.response.mode_sent) { ROS_INFO("Offboard enabled"); } } ros::NodeHandle nh_; ros::Publisher pose_pub_; ros::ServiceClient arm_client_; ros::ServiceClient set_mode_client_; ros::Timer timer_; int count_ = 0; }; int main(int argc, char **argv) { ros::init(argc, argv, "offboard_controller"); OffboardController controller; ros::spin(); return 0; } ``` ### 关键迁移注意事项 1. **节点生命周期管理**: - ROS1使用阻塞式`ros::spin()`,而ROS2使用异步执行器[^1] - ROS1节点不需要显式管理生命周期 2. **参数处理差异**: ```cpp // ROS1参数获取 double hover_height; nh_.param("hover_height", hover_height, 2.0); // 默认值2.0 ``` 3. **消息类型变更**: - 移除所有`::msg::`命名空间 - 消息构造函数更简单(如`geometry_msgs::PoseStamped`替代`geometry_msgs::msg::PoseStamped`) 4. **服务响应处理**: - ROS1服务响应包含在同一个对象中(`srv.response`) - ROS2使用分离的请求/响应对象 5. **构建系统配置**: ```cmake # ROS1的CMakeLists.txt find_package(catkin REQUIRED COMPONENTS roscpp geometry_msgs mavros_msgs ) add_executable(offboard_controller src/offboard_controller.cpp) target_link_libraries(offboard_controller ${catkin_LIBRARIES}) ``` > **重要提示**:迁移后需测试所有服务调用,ROS1的服务超时行为ROS2不同[^1]。建议使用`ros::service::waitForService()`确保服务可用性。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值