ROS-创建功能包和节点

    本文是我学习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/

上一篇: 没有了
下一篇:

首页 所有文章 机器人 计算机视觉 自然语言处理 机器学习 编程随笔 关于