ROS学习笔记系列是对 ROS中文教程 的记录总结。
笔记五包括:
- 5.1 实现目标
- 5.2 编写ROS发布器与订阅器
- 5.3 编译与运行
5.1 实现目标
学了ROS 一段时间里,我们get 了什么呢?首先是安装软件环境配置,然后会创建catkin空间,添加与编译程序包。学习了ROS 节点与话题,实际控制小海龟运动。还学了服务与参数,会一些保存文件这些操作。
然而,我们学习ROS 的初心是什么呢?肯定不止于此。我们要在工程中使用ROS,我们要有自己的东西。比如本人学习ROS,是因为想要使用树莓派外部控制pixhawk 无人机。一种方案就是使用树莓派使用 MAVROS 来与无人机交互,而ROS 就是基础之一。
先不谈那么远,我们抽象出“初心” 与 “现在” 的共同点——编写自己的程序。现在,我们就开始编写两个节点,一个不停发送 “hello world 1”, “hello world 2”…,一个不停监听,并且打印听到的内容。
5.2 编写ROS发布器与订阅器
5.2.1 ROS发布器
假设您已经学过前面的四节内容,创建的 catkin 空间还在,beginner_tutorials 程序包还在,有目录~/catkin_ws/src/beginner_tutorials/
。都不在的话先补好再往下看~
进入程序包,创建新的目录与文件并打开
$ cd ~/catkin_ws/src/beginner_tutorials/
$ mkdir -p ~/catkin_ws/src/beginner_tutorials/src
$ touch src/talker.cpp
$ gedit src/talker.cpp
打开文件,复制粘贴以下内容
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>
int main(int argc, char **argv)
{
ros::init(argc, argv, "talker"); //ros::init() 函数需要 argc 和 argv 参数才能运行,第三个参数是节点的名称
ros::NodeHandle n; //节点句柄(NodeHandle)是和ROS 系统通信的关键点
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
//第一个参数就是话题的名字,第二个参数是消息队列的长度,用于缓冲
ros::Rate loop_rate(10); //消息发布速率10Hz
int count = 0;
while (ros::ok())
{
std_msgs::String msg; //创建一个消息对象,用来存放要发布的消息
std::stringstream ss;
ss << "hello world " << count;
msg.data = ss.str();
ROS_INFO("%s", msg.data.c_str()); //打印发布了的消息
chatter_pub.publish(msg); //发布消息,一个Publiser 对象调用 publish() 方法,参数是要发布的消息
ros::spinOnce(); //不接受回调的意思,此处其实可无
loop_rate.sleep(); //保证是10Hz循环,而不是一直跑着
++count;
}
return 0;
}
代码的更详细的解释可以看官方教程 ,然后保存退出即可。
5.2.2 编写 ROS订阅器
和 talker.cpp 在同一目录下,编写 listener.cpp。首先还是新建并打开文件。
$ touch src/listener.cpp
$ gedit src/listener.cpp
然后复制粘贴以下内容:
#include "ros/ros.h"
#include "std_msgs/String.h"
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "listener"); //节点名称 listener
ros::NodeHandle n;
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
// 第一个参数是订阅的话题名称,第二个参数是消息队列长度,第三个是回调函数
ros::spin();
return 0;
}
这个程序也很简单,仔细读读就明白。
5.3 编译与运行
5.3.1 编译
C++编译的最重要文件就是 CMakelist.txt 了,接下来我们修改它。
在当前目录 ~/catkin_ws/src/beginner_tutorials/
下运行
$ ls
# 可得CMakeLists.txt include package.xml src
gedit CMakeLists.txt
修改文件使其如下(删除注释):
project(beginner_tutorials)
find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs)
catkin_package()
#---------- 以下部分为修改和添加的内容 -----------#
include_directories(include ${catkin_INCLUDE_DIRS}) #包含 catkin空间
add_executable(talker src/talker.cpp) #添加文件执行
target_link_libraries(talker ${catkin_LIBRARIES}) #链接库
add_dependencies(talker beginner_tutorials_generate_messages_cpp) #添加依赖
add_executable(listener src/listener.cpp) #添加文件
target_link_libraries(listener ${catkin_LIBRARIES}) #链接库
add_dependencies(listener beginner_tutorials_generate_messages_cpp)#添加依赖
然后回到catkin 空间下,执行编译
$ cd ~/catkin_ws
$ catkin_make
正常结果后半部分如下:
5.3.2 运行测试
见证奇迹的时刻到了!
在三个终端分别运行
$ roscore #启动ROS服务器
$ cd ~/catkin_ws
$ source ./devel/setup.bash
$ rosrun beginner_tutorials talker
$ cd ~/catkin_ws
$ source ./devel/setup.bash
$ rosrun beginner_tutorials listener
一切正常的话,可以得到如下结果:
woow!恭喜完成第一个自己的节点,准备实现最初的目标吧~
------------------ 路曼曼其修远兮,吾将上下而求索---------------------
5.4 后期补充
后面自己再次运行程序,作此补充。我们编写的两个程序—— talker.cpp 和 listener.cpp 是怎样通信的?
首先,编译之后是产生可执行文件 talker 和 listener,它们是相互独立的。
然后,运行了roscore
指令,开启服务器。值得注意的是,这个服务器并不是联网的服务器(因为不连接网络以上依然能正常运行),而是自己电脑里面一段处理程序。
最后,两个可执行文件之间一个发布消息到话题,另一个从话题订阅消息。这个话题可以使用rostopic list
指令查看,叫做 chatter ,其实就是在上面的 .cpp 文件里我们自己定义的。
总结,ROS 的这个机制实现的是进程通信。