个人博客:http://www.chenjianqu.com/
原文链接:http://www.chenjianqu.com/show-72.html
本文是我学习ROS的笔记,介绍了如何通过命令行从零创建一个功能包:基于roscpp的Service通信和基于rospy的topic通信。
一.基于roscpp的service通信
1.首先,创建一个工作空间
Catkin工作空间是创建、修改、编译catkin软件包的目录。
(base) chenjianqu@chen:~$ cd ros (base) chenjianqu@chen:~/ros$ cd project (base) chenjianqu@chen:~/ros/project$ mkdir -p ws2/src (base) chenjianqu@chen:~/ros/project$ cd ws2 (base) chenjianqu@chen:~/ros/project/ws2$ catkin_make #初始化工作空间
catkin工作空间包括了src、build、devel三个文件夹:
src/: ROS的catkin软件包(源代码包)
build/: catkin(CMake)的缓存信息和中间文件
devel/: 生成的目标文件(包括头文件,动态链接库,静态链接库,可执行文件等)、环境变量
2.创建一个Package
(base) chenjianqu@chen:~/ros/project/ws2$ cd src (base) chenjianqu@chen:~/ros/project/ws2/src$ catkin_create_pkg serve_test std_msgs rospy roscpp
创建一个package需要在catkin_ws/src下,用到catkin_create_pkg命令,用法是:catkin_create_pkg package depends,其中package是包名,depends是依赖的包名,可以依赖多个软件包。上面的命令创建了serve_test软件包,依赖于roscpp rospy std_msgs。
一个package下常见的文件、路径有:
CMakeLists.txt: 定义package的包名、依赖、源文件、目标文件等编译规则,是package不可少的成分
package.xml: 描述package的包名、版本号、作者、依赖等信息,是package不可少的成分
src/: 存放ROS的源代码,包括C++的源码和(.cpp)以及Python的module(.py)
include/: 存放C++源码对应的头文件
scripts/: 存放可执行脚本,例如shell脚本(.sh)、Python脚本(.py)
msg/: 存放自定义格式的消息(.msg)
srv/: 存放自定义格式的服务(.srv)
models/: 存放机器人或仿真场景的3D模型(.sda, .stl, .dae等)
urdf/: 存放机器人的模型描述(.urdf或.xacro)
launch/: 存放launch文件(.launch或.xml)
其中定义package的是CMakeLists.txt和package.xml,这两个文件是package中必不可少的。catkin编译系统在编译前,首先就要解析这两个文件。这两个文件就定义了一个package。 通常ROS文件组织都是按照以上的形式,这是约定俗成的命名习惯,建议遵守。以上路径中,只有CMakeLists.txt和package.xml是必须的,其余路径根据软件包是否需要来决定。
3.创建服务消息文件
(base) chenjianqu@chen:~/ros/project/ws2/src$ ls (base) chenjianqu@chen:~/ros/project/ws2/src$ cd serve_test (base) chenjianqu@chen:~/ros/project/ws2/src/serve_test$ mkdir -p srv (base) chenjianqu@chen:~/ros/project/ws2/src/serve_test$ cd srv (base) chenjianqu@chen:~/ros/project/ws2/src/serve_test/srv$ vim Greeting.srv
在Greeting.srv中写入消息内容:
string name int32 age --- string feedback
srv文件是用来描述服务(service数据类型的,service通信的数据格式定义在*.srv中。它声明了一个服务,包括请求(request)和响应(reply)两部分。
4.修改CMakeLists.txt和package.xml,编译服务消息
(base) chenjianqu@chen:~/ros/project/ws2/src/serve_test/srv$ cd .. (base) chenjianqu@chen:~/ros/project/ws2/src/serve_test$ ls (base) chenjianqu@chen:~/ros/project/ws2/src/serve_test$ vim CMakeLists.txt #修改CMakeLists文件
修改内容如下:
find_package(catkin REQUIRED COMPONENTS roscpp std_msgs message_generation #需要添加的地方 ) #catkin在cmake之上新增的命令,指定从哪个消息文件生成 add_service_files(FILES Greeting.srv) #添加这一项 #catkin新增的命令,用于生成消息 #DEPENDENCIES后面指定生成msg需要依赖其他什么消息,由于gps.msg用到了flaot32这种ROS标准消息,因此需要再把std_msgs作为依赖 generate_messages(DEPENDENCIES std_msgs) #添加这一项
(base) chenjianqu@chen:~/ros/project/ws2/src/serve_test$ vim package.xml #修改package.xml文件
修改内容加入:
<build_depend>message_generation</build_depend> #添加这一项 <exec_depend>message_runtime</exec_depend> #添加这一项
最后编译服务消息
(base) chenjianqu@chen:~/ros/project/ws2/src/serve_test$ cd .. (base) chenjianqu@chen:~/ros/project/ws2/src$ cd .. (base) chenjianqu@chen:~/ros/project/ws2$ catkin_make
编译完成之后会在devel路径下生成Greeting.srv对应的头文件,头文件按照C++的语法规则定义了serve_test::Greeting::Request和 serve_test::Greeting::Response类型的数据。
5.编写Server和Client两个节点的源代码
(base) chenjianqu@chen:~/ros/project/ws2$ cd src (base) chenjianqu@chen:~/ros/project/ws2/src$ cd serve_test (base) chenjianqu@chen:~/ros/project/ws2/src/serve_test$ ls (base) chenjianqu@chen:~/ros/project/ws2/src/serve_test$ cd src #创建server节点的源代码 (base) chenjianqu@chen:~/ros/project/ws2/src/serve_test/src$ vim server.cpp
代码内容如下:
#include <ros/ros.h> #include <serve_test/Greeting.h> bool handle_function(serve_test::Greeting::Request &req, serve_test::Greeting::Response &res){ //显示请求信息 ROS_INFO("Request from %s with age %d", req.name.c_str(), req.age); //处理请求,结果写入response res.feedback = "Hi " + req.name + ". I’m server!"; //返回true,正确处理了请求 return true; } int main(int argc, char** argv){ ros::init(argc, argv, "greetings_server");//解析参数,命名节点 ros::NodeHandle nh;//创建句柄,实例化node ros::ServiceServer service = nh.advertiseService("greetings", handle_function); //写明服务的处理函数 ros::spin(); return 0; }
接着创建client节点的源代码
(base) chenjianqu@chen:~/ros/project/ws2/src/serve_test/src$ vim client.cpp
代码如下:
#include <ros/ros.h> #include <serve_test/Greeting.h> int main(int argc, char **argv) { ros::init(argc, argv, "greetings_client");// 初始化,节点命名为"greetings_client" ros::NodeHandle nh; ros::ServiceClient client = nh.serviceClient<serve_test::Greeting>("greetings"); // 定义service客户端,service名字为“greetings”,service类型为serve_test // 实例化srv,设置其request消息的内容,这里request包含两个变量,name和age,见Greeting.srv serve_test::Greeting srv; srv.request.name = "HAN"; srv.request.age = 20; if (client.call(srv)) { // 注意我们的response部分中的内容只包含一个变量response,另,注意将其转变成字符串 ROS_INFO("Response from server: %s", srv.response.feedback.c_str()); } else { ROS_ERROR("Failed to call service serve_test"); return 1; } return 0; }
5.配置CMakeLists.txt,编译工作空间
(base) chenjianqu@chen:~/ros/project/ws2/src/serve_test/src$ cd .. (base) chenjianqu@chen:~/ros/project/ws2/src/serve_test$ vim CMakeLists.txt
在CMakeLists.txt文件里添加:
#要生成的可执行文件server和需要编译的源文件src/server.cpp,如需要多个代码文件,则可在后面一次列出,中间使用空格分隔 add_executable(server src/server.cpp) #设置依赖项,这里必须添加add_dependencies,否则找不到自定义的srv产生的头文件 add_dependencies(server serve_test_generate_messages_cpp) #设置链接库,很多工序要需要使用第三方库函数,这里可以配置默认链接库 target_link_libraries(server ${catkin_LIBRARIES}) add_executable(client src/client.cpp ) add_dependencies(client serve_test_generate_messages_cpp) target_link_libraries(client ${catkin_LIBRARIES})
然后编译工作空间
(base) chenjianqu@chen:~/ros/project/ws2/src/serve_test$ cd .. (base) chenjianqu@chen:~/ros/project/ws2/src$ cd .. (base) chenjianqu@chen:~/ros/project/ws2$ catkin_make
6.测试
A.打开一个新的终端,运行roscore
B.再打开一个新的终端,运行server节点
(base) chenjianqu@chen:~/ros/project/ws2$ source devel/setup.bash (base) chenjianqu@chen:~/ros/project/ws2$ rosrun serve_test server
C.再打开一个新的终端,运行client节点
(base) chenjianqu@chen:~/ros/project/ws2$ source devel/setup.bash (base) chenjianqu@chen:~/ros/project/ws2$ rosrun serve_test client
然后服务端节点会收到客户端节点的消息:[ INFO] [1575818511.403724893]: Request from HAN with age 20。
接着客户端节点收到服务端节点返回的消息:[ INFO] [1575818511.403884222]: Response from server: Hi HAN. I???m server!。
编译完成后必须刷新一下工作空间的环境,否则可能找不到工作空间。许多时候我们为了打开终端就能够运行工作空间中编译好的ROS程序,我们习惯把“source 工作空间路径/devel/setup.bash”命令追加到~/.bashrc文件中,这样每次打开终端,系统就会刷新工作空间环境。可以通过“echo "source 工作空间路径/devel/setup.bash" >> ~/.bashrc”命令来追加。
二.基于rospy的topic通信
在上面创建工作空间的基础上,创建另一个包。
(base) chenjianqu@chen:~/ros/project/ws2/src$ catkin_create_pkg topic_test std_msgs rospy
然后在包目录下创建一个用于存放topic消息的目录msg,创建topic通信格式gps.msg
(base) chenjianqu@chen:~/ros/project/ws2/src/topic_test/msg$ vim gps.msg
写入下面的代码,格式与service通信不同,topic通信是单向通信:
string state float32 x float32 y
然后像上面的service例子一样,修改CMakeLists.txt和package.xml,并编译服务消息。
接下来就可以开始写rospy的程序,这里在包目录下创建scripts文件夹,用于存放简单的python脚本代码,
(base) chenjianqu@chen:~/ros/project/ws2/src/topic_test/scripts$ vim pytalker.py
代码如下:
#!/usr/bin/env python #coding=utf-8 import rospy from topic_test.msg import gps #导入自定义的数据类型 def talker(): pub = rospy.Publisher('gps_info', gps , queue_size=10) rospy.init_node('pytalker', anonymous=True) #更新频率是1hz rate = rospy.Rate(1) x=1.0 y=2.0 state='working' while not rospy.is_shutdown(): #计算距离 rospy.loginfo('Talker: GPS: x=%f ,y= %f',x,y) pub.publish(gps(state,x,y)) x=1.03*x y=1.01*y rate.sleep() if __name__ == '__main__': talker() #Publisher 函数第一个参数是话题名称,第二个参数 数据类型,现在就是我们定义的msg 最后一个是缓冲区的大小 #queue_size: None(不建议) #这将设置为阻塞式同步收发模式! #queue_size: 0(不建议)#这将设置为无限缓冲区模式,很危险! #queue_size: 10 or more #一般情况下,设为10 。queue_size太大了会导致数据延迟不同步。
写完后设置该文件为可执行文件:
(base) chenjianqu@chen:~/ros/project/ws2/src/topic_test/scripts$ chmod +x pytalker.py
再新建一个节点脚本文件:
(base) chenjianqu@chen:~/ros/project/ws2/src/topic_test/scripts$ vim pylistener.py
代码:
#!/usr/bin/env python #coding=utf-8 import rospy import math from topic_test.msg import gps #回调函数输入的应该是msg def callback(gps): distance = math.sqrt(math.pow(gps.x, 2)+math.pow(gps.y, 2)) rospy.loginfo('Listener: GPS: distance=%f, state=%s', distance, gps.state) def listener(): rospy.init_node('pylistener', anonymous=True) #Subscriber函数第一个参数是topic的名称,第二个参数是接受的数据类型 第三个参数是回调函数的名称 rospy.Subscriber('gps_info', gps, callback) rospy.spin() if __name__ == '__main__': listener()
(base) chenjianqu@chen:~/ros/project/ws2/src/topic_test/scripts$ chmod +x pylistener.py
python代码是动态语言,因此无需修改cmakelists.txt文件,直接在工作空间catkin_make一下,再刷新环境变量,即可运行。
代码首行#!/usr/bin/env python这种用法是为了防止操作系统用户没有将python装在默认的/usr/bin路径里。当系统看到这一行的时候,首先会到env设置里查找python的安装路径,再调用对应路径下的解释器程序完成操作。
参考文献
[0]中科院软件所,重德智能公司.中国大学MOOC---《机器人操作系统入门》课程讲义.https://sychaichangkun.gitbooks.io/ros-tutorial-icourse163/content/