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