ROS2学习(二、写一个简单的C++例子)

在安装完ROS2后,就开始接触ros2的很多概念,东西很多,包括编译,中间件,节点等与ros1都有一些区别。如果没有ROS1的基础,直接接触这些概念,可能更难理解。
最简单的开始,就是先写一个简单的程序熟悉一下,把所有东西用一下,然后在理解所使用的每一个点的概念,这样更容易理解。就如同所有语言学习前先写一个“hello, world”一样。ROS2 也从“hello,world”开始。
本文就实现了一个定时器,通过定时器,触发固定频率的发出“hello world”.

1.创建一个ros2的工程包

所有的ros2的工程都是以包的形式存在的。包里面包括package.xml和CMakeList.txt,而且可以通过ros2 pkg命令可以很容易的创建出一个包所需要的基本的文件,而且可以指定依赖库,在创建的时候一并添加到包里。
如下语句就是创建一个ament_cmake类型的包,节点名字是timer_sample, 包名也是timer_sample ,指定的依赖是rclcpp.

ros2 pkg create --build-type ament_cmake --node-name timer_sample timer_sample --dependencies rclcpp std_msgs
crabe@crabe-virtual-machine:~/Desktop/sample/timer_sample/src$ ros2 pkg create --build-type ament_cmake --node-name timer_sample timer_sample --dependencies rclcpp
going to create a new package
package name: timer_sample
destination directory: /home/crabe/Desktop/sample/timer_sample/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['crabe ']
licenses: ['TODO: License declaration']
build type: ament_cmake
dependencies: ['rclcpp']
node_name: timer_sample
creating folder ./timer_sample
creating ./timer_sample/package.xml
creating source and include folder
creating folder ./timer_sample/src
creating folder ./timer_sample/include/timer_sample
creating ./timer_sample/CMakeLists.txt
creating ./timer_sample/src/timer_sample.cpp

  • 指定构建类型,可以是ament_cmake,ament_python等。
  • 指定软件包所依赖的其他软件包,可以是一个或多个依赖软件包的名称,以空格分隔。
  • 是要创建的软件包的名称

为什么添加的依赖是rclcpp? 这个是因为在ros2中如果要使用C++实现,包括节点的实现都在rclcpp包中。
在ROS2中,节点是发布和订阅消息、执行计算和与其他节点进行通信的基本单元。节点需要与ROS2系统进行交互,包括创建订阅者和发布者,处理接收到的消息,发送消息等。为了实现这些功能,节点需要使用ROS2提供的一些基本功能和工具。
在C++中,通过继承rclcpp::Node类,节点可以获得ROS2提供的各种功能和工具。rclcpp::Node是ROS2提供的一个C++类,封装了ROS2的节点相关功能,使开发人员能够轻松地创建、配置和管理节点。

继承rclcpp::Node类的节点可以使用其提供的函数来完成以下任务:

创建发布者和订阅者,用于与其他节点进行通信;
发布消息给特定的话题;
处理接收到的消息;
定时执行周期性的任务;
获取参数值和设置参数;
与ROS2的服务进行通信;
获取当前节点的名称和命名空间等。
继承rclcpp::Node类的节点还可以轻松地使用ROS2的消息类型和执行上下文,而无需自己实现这些功能。
总之,继承rclcpp::Node类使节点能够方便地与ROS2系统进行交互,利用ROS2提供的丰富功能和工具来开发功能强大和可靠的ROS2节点。
如果有ROS1经验的人可能感觉这个比较难适应。因为ROS1完全是通过roscpp库实现的这些。没有这种可以提供继承的rclcpp::Node类.不过用习惯了,还是可以接受的,毕竟ROS2的rclcpp::Node类的引入是为了提供更强大灵活的开发体验,开发和管理节点更方便。
通过上面的方式ros2的包已经创建出来了。看一下目录结构


crabe@crabe-virtual-machine:~/Desktop/sample$ tree  timer_sample/
timer_sample/
└── src
    └── timer_sample
        ├── CMakeLists.txt
        ├── include
        │   └── timer_sample
        ├── package.xml
        └── src
            └── timer_sample.cpp

5 directories, 3 files

通过目录结构就可以看到创建include和src文件夹,这个平时写C++程序的时候都会创建。另外还创建了CMakeList.txt这个用来组织编译工程。另外还有一个文件package.xml这个是用来组织ros的包和添加编译执行依赖的,与ROS1是一样的。

2.写一个ros2节点

包已经创建了,然后就是在timer_sample.cpp写具体内容了,下面代码就是具体实现一个topic定时的发送hell world.

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

using namespace std;

using iString = std_msgs::msg::String;

class TimerSample: public rclcpp::Node{
        public:
                TimerSample(const string & node_name): Node(node_name)
                {
                        publisher_ = this->create_publisher<iString>("string_topic", 10);
                };
                virtual ~TimerSample(){};

                void Start()
                {
                        timer_ = this->create_wall_timer(1s, std::bind(&TimerSample::topictimer_callback, this));
                };

                void topictimer_callback()
                {
                        iString message;
                        message.data = "Hello world!";
                        publisher_->publish(message);
                };

        private:
                rclcpp::Publisher<iString>::SharedPtr publisher_;
                rclcpp::TimerBase::SharedPtr timer_;

};

int main(int argc, char ** argv)
{

  rclcpp::init(argc, argv);

  string name = "timer_sample";
  auto node_ = make_shared<TimerSample>(name);
  rclcpp::executors::SingleThreadedExecutor executor_;
  node_->Start();

  executor_.add_node(node_);
  executor_.spin();

  rclcpp::shutdown();
  return 0;
}

代码写完后再最外层的timer_sample文件夹下执行 colcon build 就可以编译工程。

crabe@crabe-virtual-machine:~/Desktop/sample/timer_sample$ colcon build
Starting >>> timer_sample
Finished <<< timer_sample [2.17s]

Summary: 1 package finished [2.34s]

编译完成后就会生成一个build 和install文件夹。
与ros1不同的地方是ros2生成的所有可执行文件都是默认生成在install下面的。直接做好了install文件夹。这样就可以将文件夹拷贝到任何地方都可以执行。
执行前先source install/setup.sh,加载环境变量。
然后就可以执行ros2 run timer_sample timer_sample了

source install/setup.sh
ros2 run timer_sample timer_sample

程序执行后,因为没有打印log,所以没有任何输出可以看到,不过此时打开另一个终端,进入同样目录执行source install/setup.sh后,通过订阅topic就可以看到发出来的hello world.

crabe@crabe-virtual-machine:~/Desktop/sample/timer_sample$ source install/setup.bash
crabe@crabe-virtual-machine:~/Desktop/sample/timer_sample$ ros2 topic echo /string_topic
data: Hello world!
---
data: Hello world!
---
data: Hello world!
---
data: Hello world!
---
data: Hello world!
---
data: Hello world!
---
data: Hello world!
---
data: Hello world!
---

到这里,一个简单的ros2发布topic的程序完成了。

3.程序说明

这里不详细的说所有diam,只说一下,需要注意的几个点

  • 继承rclcpp::Node
    可以看到例子程序的类继承了rclcpp::Node,使得TimerSample类的对象也变成了一个节点。这就是ROS2与ROS1典型的不同的地方,通过继承,ROS2可以方便的对节点做很多操作,比如直接创建timer。

  • timer指针
    为什么要创建 rclcpp::TimerBase::SharedPtr timer_?在上面的例子中可以看到直接在类内就可以通过 this->create_publisher(“string_topic”, 10); 创建一个timer,这里为什么要把返回值作为成员变量? 这个是因为创建了timer以后,start函数对退出了。虽然timer属于Node创建的,但是创建完的timer存在的生命期就属于这个对象自身了。如果不保存起来,Start函数结束,timer也就释放了。就不会有后面的触发timerout了。

  • rclcpp::init(argc, argv);
    所有的进程一开始都要执行rclcpp::init()函数,不然节点的创建和启动都会有问题。
    如果不加这一句话,编译没有问题,运行的时候,会出现下面的错误。

crabe@crabe-virtual-machine:~/Desktop/sample/timer_sample$ ros2 run timer_sample timer_sample
terminate called after throwing an instance of 'rclcpp::exceptions::RCLInvalidArgument'
  what():  failed to create interrupt guard condition: context argument is null, at /tmp/binarydeb/ros-foxy-rcl-1.1.14/src/rcl/guard_condition.c:65

  • executor_.spin();
    在最有执行spin();这个是一个阻塞式的函数,会不停的监测所有的加入节点执行器中的节点,是否有有触发的事件。如果学过ROS1的话,比较好理解,和ROS1的spin是一个效果。这里唯一区域的是多个了个节点执行器的概念。因为ROS2和ROS1节点的方式发生了很大变换,这里就不详细展开,会再其他地方在详细说一下ROS2的节点的概念。

你可能感兴趣的:(ROS2,C++,学习,c++)