目录
ROS
ROS架构的三个层次:
- 基于Linux的OS层;
- 实现ROS核心通信机制以及众多机器人开发库的中间层;
- 在ROS Master的管理下保证功能节点的正常运行的应用层。
ROS从系统实现角度划分成的三个层次:文件系统、计算图和开源社区
ROS的三种通信机制:基于Publish(发布)/Subscribe(订阅)的Topic(话题)通信
1、ROS客户端库
roscpp:ROS客户端库之一,实现大部分ROS概念,可用于高性能应用程序
rospy:ROS客户端库的Python纯实现。这个库的优点是易于原型设计,开发时间短,
roslisp:LISP的客户端库,通常用于构建机器人规划库。
2、ROS中的文件系统结构
2.1 工作空间与功能包
2.1.1 功能包(package)结构说明:
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模型文件;存放机器人或仿真场景的3D模型(.sda, .stl, .dae等)
├── urdf/ #urdf文件;存放机器人的模型描述(.urdf或.xacro)
├── launch/ #launch文件;存放launch文件(.launch或.xml)
定义package的是CMakeLists.txt和package.xml,这两个文件是package中必不可少的。catkin编译系统在编译前,首先就要解析这两个文件。这两个文件就定义了一个package。
通常ROS文件组织都是按照以上的形式,这是约定俗成的命名习惯,建议遵守。以上路径中,只有CMakeLists.txt和package.xml是必须的,其余路径根据软件包是否需要来决定
2.1.2 ROS相关的命令
3、ROS命令总结
命令 | 功能 |
---|---|
roscore | 启动ROS主节点Master |
rosrun | 运行一个可执行程序并生成节点 |
rosnode | 显示ROS节点信息,(可查看其他子命令) |
rostopic | 显示ROS主题信息 |
rosmsg | 显示ROS消息类型信息 |
rosservice | 显示各种服务的运行时信息,并运行显示发送该主题的消息 |
rosparam | 用于获取和设置节点使用的参数 |
参考文档:ROS命令
2.1.3 创建catkin工作空间
命令:
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
catkin_create_pkg my_pkg_1 std_msgs rospy roscpp
// catkin_create_pkg <package_name> [depend1] [depend2] [depend3]
cd ..
catkin_make
source ~/catkin_ws/devel/setup.bash
3 自定义功能包清单package.xml
<!-- 注释 -->
<!-- 定义文档的语法 ,内容遵循xml的1.0版本 -->
<?xml version="1.0"?>
<!-- 标签package 起始 ROS功能包的配置部分-->
<package format="2">
// 包的名字(可随时更改),包的版本
<name>my_pkg1</name>
<version>0.0.0</version>
// 描述标签<description>,将描述信息修改为任何你喜欢的内容,(应该为包的功能)
<description>The my_pkg1 package</description>
// 需要一个维护人员标签,允许多个,每个标签一个人(维护者的邮箱,名字)
<!-- <maintainer email="jane.doe@example.com">Jane Doe</maintainer> -->
<maintainer email="peen@todo.todo">peen</maintainer>
// 需要一个许可证标签,允许多个,每个标签一个许可证
// 常用的许可证:BSD, MIT, Boost Software License, GPLv2, GPLv3, LGPLv2.1, LGPLv3
<license>TODO</license>
// Url标记是可选的,但允许多个,每个标记一个(记录描述功能包的说明)
// 可选属性类型可以是:website, bugtracker, or repository
// 例如:
<!-- <url type="website">http://wiki.ros.org/my_pkg1</url> -->
// 作者标签使可选的,允许多个,每个标记一个(邮箱,名字)
// 作者可以不是维护者,但可以是
// 例如:
<!-- <author email="jane.doe@example.com">Jane Doe</author> -->
// *depend 标记用于指定依赖项
// 依赖项可以是catkin包或系统依赖项
// 例如:使用depend作为同时是build和exec依赖项的包的快捷方式
// 即,代替 build_depend,exec_depend。
<!-- <depend>roscpp</depend> -->
// 上述 depend 标签依赖,相当于一下两句内容
<!-- <build_depend>roscpp</build_depend> -->
<!-- <exec_depend>roscpp</exec_depend> -->
// 编译时需要的包,使用build_depend标签
<!-- <build_depend>message_generation</build_depend> -->
// 为了根据这个包进行构建,使用 build_export_depend标签
// 构建导出依赖关系,如果功能包导出头文件,则其他包可能需要这个包,就应该使用这个标签,用来传递声明
<!-- Use build_export_depend for packages you need in order to build against this package: -->
<!-- <build_export_depend>message_generation</build_export_depend> -->
// 对构建工具的包,使用 buildtool_depend标签
// 构建工具依赖关系指定此软件包需要构建自身的构建系统工具。通常唯一的构建工具是 catkin
<!-- <buildtool_depend>catkin</buildtool_depend> -->
// 对运行时需要的包,使用 exec_depend标签
// 执行依赖关系,指定此程序包中运行代码所需的软件包,如动态链接库,可执行文件,Python模块,脚本文件
<!-- <exec_depend>message_runtime</exec_depend> -->
// 对于仅用于测试的包,使用test_depend标签
<!-- <test_depend>gtest</test_depend> -->
// 对于仅用于生成文档的包,使用doc_depend标签
<!-- <doc_depend>doxygen</doc_depend> -->
// 例如:
<buildtool_depend>catkin</buildtool_depend>
<build_depend>std_msgs</build_depend>
<build_depend>roscpp</build_depend>
<build_depend>rospy</build_depend>
<build_export_depend>msgs</build_export_depend>
<build_export_depend>roscpp</build_export_depend>
<build_export_depend>rospy</build_export_depend>
<exec_depend>std_msgs</exec_depend>
<exec_depend>roscpp</exec_depend>
<exec_depend>rospy</exec_depend>
// 导出标记 包含其他未指定的标记
<!-- The export tag contains other, unspecified, tags -->
<export>
// 其他工具可以请求在此处放置其他信息
<!-- Other tools can request additional information be placed here -->
</export>
<!-- 标签package 结束 -->
</package>
4 ROS 计算图(ROS Computation Graph)
计算图(Computation Graph)是一个由ROS进程组成的点对点网络,它们能够共同处理数据。ROS的基本计算图概念有:节点(Nodes)、主节点(Master)、参数服务器(Parameter Server)、消息(Messages)、服务(Services)、话题(Topics)和包(Bags)
参考文档:ROS Concepts
roscpp:ROS客户端库之一,实现大部分ROS概念,可用于高性能应用程序
rospy:ROS客户端库的Python纯实现。这个库的优点是易于原型设计,开发时间短,
roslisp:LISP的客户端库,通常用于构建机器人规划库。
参考文档:ROS Client Libraries
5 使用rqt_console和rqt_logger_level
rqt_console连接到了ROS的日志框架,以显示节点的输出信息。rqt_logger_level允许我们在节点运行时改变输出信息的详细级别,包括Debug、Info、Warn和Error`。
日志记录器级别
Fatal (致命)
Error (错误)
Warn (警告)
Info (信息)
Debug (调试)
Fatal是最高优先级,Debug是最低优先级。通过设置日志级别,你可以获得所有优先级级别,或只是更高级别的消息。比如,将日志级别设为Warn时,你会得到Warn、Error和Fatal这三个等级的日志消息。
6 roslaunch
使用roslaunch来启动多个节点和一个模仿者节点。roslaunch可以用来启动定义在launch(启动)文件中的节点。
命令:roslaunch [package] [filename.launch]
使用roslaunch时,是不用启动roscore,roscore自动启动
<!-- 定义文档的语法 ,内容遵循xml的1.0版本 -->
<?xml version="1.0"?>
<!-- launch标签开头 -->
<launch>
<!-- 共享一个名称空间或重新映射组封闭元素 -->
<!-- 创建了分组,并以命名空间(namespace)标签来区分 -->
<!-- 启动节点多了可以分分组,不同组可以使用不同的命名空间,使得节点参数不冲突。该标签套在<node>标签外 -->
<group ns="turtlesim1">
<!-- 启动节点 -->
<!-- pkg是工作空间中节点包的名称,name是给这个节点起的名字, type是包中需要运行的具体节点,其指向的文件必须有对应的可执行文件。-->
node属性:pkg 节点包,type节点类型,必须要有对应的可执行文件,name节点名称,args 传递到节点的参数,respawn 自动重启
<node pkg="turtlesim" name="sim" type="turtlesim_node"/>
<node pkg="turtlesim" name="sim" type="turtlesim_node"/>
<!-- 包含其它launch文件,被包含的launch文件将会被一同启动 -->
<include file="$(find ur5_e_moveit_config)/launch/move_group.launch">
<!-- 这个标签与上述<param>标签翻译完全一致,都是“参数”, 但<param>所设置的参数是可以在ros参数服务器中查看并设置的,而<arg>只是当前launch文件所使用的内部参数,外部不可见。-->
<arg name="load_robot_description" value="true"/>
<!-- 对参数服务器进行参数设置 -->
<!-- name是参数在程序内部的名称,value是给对应参数赋值。 -->
<param name="/use_gui" value="false"/>
<param name="scale_linear" value="0.1" type="double"/>
<!-- 声明映射名 -->
<remap from="input" to="turtlesim1/turtle1"/>
<remap from="output" to="turtlesim2/turtle1"/>
<!-- 使用rosparam文件启动设置ROS参数 -->
<!-- 当参数特别多时,全部写在launch文件里就不方便了,为此,可以通过加载文件来传递参数,上述代码从4个文件加载了参数,其中costmap_common_params.yaml文件中的参数值被传递到两个不同的命名空间中 -->
<rosparam file="$(find rbx1_nav)/config/fake/costmap_common_params.yaml" command="load" ns="global_costmap" />
<rosparam file="$(find rbx1_nav)/config/fake/costmap_common_params.yaml" command="load" ns="local_costmap" />
<rosparam file="$(find rbx1_nav)/config/fake/local_costmap_params.yaml" command="load" />
<rosparam file="$(find rbx1_nav)/config/fake/global_costmap_params.yaml" command="load" />
<rosparam file="$(find rbx1_nav)/config/fake/base_local_planner_params.yaml" command="load" />
<!-- 结尾,launch文件的XML标签闭合 -->
</launch>
7 创建ROS消息和服务
msg(消息):msg文件就是文本文件,用于描述ROS消息的字段。它们用于为不同编程语言编写的消息生成源代码。
srv(服务):一个srv文件描述一个服务。它由两部分组成:请求(request)和响应(response)。
msg文件存放在软件包的msg目录下,srv文件则存放在srv目录下。
msg文件就是简单的文本文件,每行都有一个字段类型和字段名称。可以使用的类型为:
int8, int16, int32, int64 (以及 uint*)
float32, float64
string
time, duration
其他msg文件
variable-length array[] 和 fixed-length array[C]
ROS中还有一个特殊的数据类型:Header,它含有时间戳和ROS中广泛使用的坐标帧信息。在msg文件的第一行经常可以看到Header header。
srv文件和msg文件一样,只是它们包含两个部分:请求和响应。这两部分用一条—线隔开。下面是一个srv文件的示例:
int64 A
int64 B
---
int64 Sum
//在上面的例子中,A和B是请求, Sum是响应。
7.1 创建msg
roscd package1
mkdir msg
echo “int64 num” > msg/Num.msg
不过还有关键的一步:我们要确保msg文件能被转换为C++、Python和其他语言的源代码。
打开package.xml, 确保它包含以下两行且没有被注释。如果没有,添加进去:
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
注意,在编译时,其实只需要message_generation,而在运行时,我们只需要message_runtime。
在CMakeLists.txt文件中,为已经存在里面的find_package调用添加message_generation依赖项,这样就能生成消息了。直接将message_generation添加到COMPONENTS列表中即可,如下所示:
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
7.2 创建srv
roscd package1
mkdir srv
从另一个包复制现有的srv定义,而不是手动创建新的srv
roscp rospy_tutorials AddTwoInts.srv srv/AddTwoInts.srv
还有关键的一步:我们要确保msg文件能被转换为C++、Python和其他语言的源代码。
如果没做过上面的教程,请先打开package.xml,确保它包含以下两行且没有被注释。如果没有,添加进去:
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
如前所述,在构建时,其实只需要message_generation,而在运行时,我们只需要message_runtime。
7.3 一般步骤
8 编写简单的发布者(C++)
<< 注释---头文件
<< ros/ros.h包括了使用ROS系统中最常见的公共部分所需的全部头文件
#include <ros/ros.h>
<< 节点发布String类型的消息,需要包含该消息类型的头文件String.h。
#include <std_msgs/String.h>
#include <sstream>
int main(int argc, char **argv)
{
<< 初始化ROS节点,必须在起始位置
<< 前两个参数是命令行或launch文件输入的参数,可以用来完成命名重映射等功能。
<< 第3个参数定义了发布者(Publisher)节点的名称,而且该名称在运行ROS中必须是唯一的,不允许存在相同名称的两个节点。
ros::init(argc, argv, "talker");
<< NodeHandle是与ROS系统通信的主要接入点。
<< 创建节点句柄,方便对节点资源的使用和管理
<< 创建的第一个NodeHandle实际上将执行节点的初始化,而最后一个被销毁的NodeHandle将清除节点所使用的任何资源。
ros::NodeHandle n;
<< advertise()函数的作用是告诉ROS发布者的主题名
<< advertize()返回一个Publisher对象,该对象允许您通过调用publish()来发布有关该主题的消息。
<< 一旦销毁了返回的Publisher对象的所有副本,该主题将自动取消播发。advertise()的第二个参数是用于
<< 发布消息的队列大小(最多缓存的消息数),多于该大小的缓存数就丢弃旧消息()
<< NodeHandle::advertise()返回一个ros::Publisher对象,
<< 它有2个目的:其一,它包含一个publish()方法,可以将消息发布到创建它的话题上;
<< 其二,当超出范围时,它将自动取消这一宣告操作。
<< 在ROS Master端创建一个发布者(Publisher),发布名为chatter的topic,消息类型为std_msgs::String
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
<< 设置循环的频率,单位Hz,此处设置10Hz,它会记录从上次调用Rate::sleep()到现在已经有多长时间,并休眠正确的时间
<< 当调用Rate::sleep()时,ROS节点会根据此处频率休眠相应的时间,以保证循环维持一致的时间周期
ros::Rate loop_rate(10);
<< 记录发送的消息数,用于为每条消息创建唯一的字符串
int count = 0;
<< 进入节点的主循环,在节点未发生异常的情况下将一直运行,一旦发生异常,ros::ok()返回false,跳出循环。
<< 默认情况,roscpp将安装一个SIGINT处理程序,它能够处理Ctrl+C操作,让ros::ok()返回false
<< ros::ok()在以下情况会返回false:
<< 1.收到SIGINT信号(Ctrl+C)
<< 2.被另一个同名的节点踢出了网络
<< 3.ros::shutdown()被程序的另一部分调用
<< 4.所有的ros::NodeHandles都已被销毁
while (ros::ok())
{
<< ROS中定义了很多通用的消息类型,这里使用标准的String消息类型,其只有一个成员 data
<< 初始化std_msgs::String类型的消息
std_msgs::String msg;
std::stringstream ss;
ss << "hello world " << count;
msg.data = ss.str();
<< ROS_INFO和它的朋友们可用来取代printf/cout。
<< 将发布的消息进行打印,确保发出的数据符合要求
ROS_INFO("%s", msg.data.c_str());
<< publish()函数的功能是发送消息,参数是消息(message)对象;该对象的类型必须与advertise<>()调用的模板参数给定的类型一致
<< 发布消息,消息发布后,Master会查找订阅该话题的节点,并且帮助两个节点建立连接,完成消息的传输
chatter_pub.publish(msg);
<< ros::spinOnce()用来处理节点订阅话题的所有回调函数
<< 循环等待回调函数(建议默认加上该函数)
ros::spinOnce();
<< 现在发布者(Publisher)一个周期的工作完成,可让节点休息,调用休眠函数loop_rate.sleep(),进入休眠。
<< 使用ros::Rate在剩下的时间内睡眠,以让我们达到10Hz的发布速率
<< 按照循环频率延时
loop_rate.sleep();
++count;
}
<< 以上详细说明了发布者Publisher节点的实现过程,该结点较为简单,但麻雀虽小,五脏俱全,其包含了实现一个Publisher的所有流程,总结如下:
<< 1、初始化ROS节点
<< 2、向ROS Master注册节点消息,包括发布的话题名和话题中的消息类型
<< 3、以ros::Rate loop_rate()中设定的速率循环发布消息
return 0;
}
9 编写订阅者节点
<< 头文件
#include "ros/ros.h"
#include "std_msgs/String.h"
<< 回调函数是订阅者(Subscriber) 节点接收消息的基础机制,当有消息到达 chatter(发布者话题名) 话题时,会自动以消息指针
<< 作为参数,再调用回调函数,完成对消息内容的处理
<< 接收到订阅的消息后,会进入到消息回调函数
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
<< 将接收到的消息打印出来
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
<< 主函数部分,(部分代码与发布者相同,可参阅)
int main(int argc, char **argv)
{
<< 初始化订阅者ROS节点
ros::init(argc, argv, "listener");
<< 创建节点句柄
ros::NodeHandle n;
<< 订阅者(Subscriber)节点声明自己订阅的消息话题,该消息会在ROS Master中注册。
<< Master会关注系统中是否有发布该话题的节点,如果有就帮助建立两个节点的连接,完成数据传输。
<< NodeHandle::subscribe()用来创建一个Subscriber。第一个参数为消息话题,第二个参数为接受消息队列的大小。
<< 当消息入队数量超过设置的队列大小时,会自动舍弃最早的消息。
<< 第三个参数是接受到话题消息后的回调函数。
<< 创建一个Subscriber,订阅者名为chatter的话题,注册回调函数chatterCallback
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
<< ros::spin()启动了一个自循环,它会尽可能快地调用消息回调函数。
<< 一旦ros::ok()返回false,ros::spin()就会退出,
<< 这意味着ros::shutdown()被调用了,主节点让我们关闭(或是因为按下Ctrl+C,它被手动调用)
<< 循环等待回调函数
ros::spin();
return 0;
}
<< 实现Subscriber的简单流程,总结如下:
<< 1、初始化ROS节点。
<< 2、订阅需要的话题。
<< 3、spin()循环等待话题消息,接受到消息后进入回调函数chatterCallback()。
<< 4、在回调函数中完成消息处理。
10 CMakeList.txt文件介绍
10.1 总体结构和顺序
CMakeList.txt文件必须遵循如下的格式,不然就无法正确地编译(编译ros软件包时提示“ros未定义的引用”的错误,原因就是CMakeList.txt文件中命令顺序不正确)。
必需的CMake版本:cmake_minimum_required()
软件包名:project()
查找编译依赖的其他CMake/Catkin包(声明依赖库):find_package()
启动Python模块支持:catkin_python_package()
消息/服务/操作(Message/Service/Action)生成器:add_message_files(),add_service_files(),add_action_files()
调用消息/服务/操作生成:generate_messages()
指定包编译信息导出:catkin_package()
添加要编译的库和可执行文件:add_library()/add_executable()/target_link_libraries()
测试编译:catkin_add_gtest()
安装规则:install()
常用变量:
1、预定义变量
PROJECT_SOURCE_DIR:工程的根目录
PROJECT_BINARY_DIR:运行 cmake 命令的目录,通常是 ${PROJECT_SOURCE_DIR}/build
PROJECT_NAME:返回通过 project 命令定义的项目名称
CMAKE_CURRENT_SOURCE_DIR:当前处理的 CMakeLists.txt 所在的路径
CMAKE_CURRENT_BINARY_DIR:target 编译目录
CMAKE_CURRENT_LIST_DIR:CMakeLists.txt 的完整路径
CMAKE_CURRENT_LIST_LINE:当前所在的行
CMAKE_MODULE_PATH:定义自己的 cmake 模块所在的路径,SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake),然后可以用INCLUDE命令来调用自己的模块
EXECUTABLE_OUTPUT_PATH:重新定义目标二进制可执行文件的存放位置
LIBRARY_OUTPUT_PATH:重新定义目标链接库文件的存放位置
2. 环境变量
使用环境变量
$ENV{Name}
写入环境变量
set(ENV{Name} value) # 这里没有“$”符号
3. 系统信息
CMAKE_MAJOR_VERSION:cmake 主版本号,比如 3.4.1 中的 3
CMAKE_MINOR_VERSION:cmake 次版本号,比如 3.4.1 中的 4
CMAKE_PATCH_VERSION:cmake 补丁等级,比如 3.4.1 中的 1
CMAKE_SYSTEM:系统名称,比如 Linux-2.6.22
CMAKE_SYSTEM_NAME:不包含版本的系统名,比如 Linux
CMAKE_SYSTEM_VERSION:系统版本,比如 2.6.22
CMAKE_SYSTEM_PROCESSOR:处理器名称,比如 i686
UNIX:在所有的类 UNIX 平台下该值为 TRUE,包括 OS X 和 cygwin
WIN32:在所有的 win32 平台下该值为 TRUE,包括 cygwin
4. 主要开关选项
BUILD_SHARED_LIBS:这个开关用来控制默认的库编译方式,如果不进行设置,使用 add_library 又没有指定库类型的情况下,默认编译生成的库都是静态库。如果 set(BUILD_SHARED_LIBS ON) 后,默认生成的为动态库
CMAKE_C_FLAGS:设置 C 编译选项,也可以通过指令 add_definitions() 添加
CMAKE_CXX_FLAGS:设置 C++ 编译选项,也可以通过指令 add_definitions() 添加
add_definitions(-DENABLE_DEBUG -DABC) # 参数之间用空格分隔
10.2 模板及说明
使用CMake编译程序时,cmake指令依据CMakeLists.txt 文件生成makefiles文件,make命令再依据makefiles文件编译链接生成可执行文件。
<< 注释
<< CMakeLists.txt都要以此开始,指定cmake的最小版本,
<< 如下:指定catkin的编译需要 3.0.2 以上版本的cmake
cmake_minimum_required(VERSION 3.0.2)
<< 通过project()函数指定包的名字,在CMake中指定后,可在其他地方使用变量${PROJECT_NAME}来引用包的名字
<< 例如:包的名字为my_pkg,则${PROJECT_NAME} == my_pkg
<< my_pkg 应于package.xml 中的 <package name 中一致,
<< 也可以修改为与功能包文件夹的名字不一致,但是 rosrun package_name package_node 中的
<< package_name 应该应用project(package_name)中的名字
<< 设置功能包名称 my_pkg
project(my_pkg)
<< 设置编译选项,此函数添加的编译选项是针对所有编译器的(包括c和c++编译器)
## Compile as C++11, supported in ROS Kinetic and newer
# add_compile_options(-std=c++11)
<< 指明构建 功能包(my_pkg1) 所需要的依赖的包(package),由于使用catkin_make的编译方式,catkin包必须
<< find_package()语法:一个包被find_package,就会有一些CMake变量产生,这些变量在CMake的脚本中用到,
<< 这些变量描述了所依赖的包的输出头文件、源文件、库文件在哪里。
<< 变量的名字依照的惯例是:<PACKAGENAME>_<PROPERTY>,比如:
<< <NAME>_FOUND:这个变量说明这个库是否被找到,如果找到就被设置为true,否则设为false;
<< <NAME>_INCLUDE_DIRS or<NAME>_INCLUDES:这个包输出的头文件目录;
<< <NAME>_LIBRARIES or <NAME>_LIBS:这个包输出的库文件。
<< 所需要的包都可通过这种方式包含进来,如还需要roscpp、rospy、std_msgs;则可写成:
<< find_package(roscpp REQUIRED)
<< find_package(rospy REQUIRED)
<< find_package(std_msgs REQUIRED)
<< 如上述写,则每个依赖的package都会产生几个变量,很不方便,可将上述形式合并如下写:
<< find_package(catkin REQUIRED COMPONENTS
<< roscpp
<< rospy
<< std_msgs
<< message_generation
<< )
<<
<< 这样,它会把所有pacakge里面的头文件和库文件等等目录加到一组变量上,比如:
<< catkin_INCLUDE_DIRS,这样,我们就可以用这个变量查找需要的文件了。最终就只产生一组变量了。
find_package(catkin REQUIRED COMPONENTS
msgs
roscpp
rospy
)
<< 如果使用C++和Boost库,需要在Boost上调用 find_package(),并指定Boost中将要作为组件的那部分。
<< 例如,如果想要使用Boost的线程,可以用:find_package(Boost REQUIRED COMPONENTS thread)
# find_package(Boost REQUIRED COMPONENTS system)
<< 如果ROS软件包提供了一些Python模块,就要创建一个setup.py文件并调用:catkin_python_setup()
<< 该调用要在generate_message()和catkin_package()的调用之前。
<< 如果包(package)有 setup.py,取消注释。
<< 此宏确保安装其中声明的模块和全局脚本
<< 参考文档: http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html
# catkin_python_setup()
################################################
## Declare ROS messages, services and actions ##
################################################
<< 声明和编译包的 messages,services 或者 actions,如下步骤:
## * Let MSG_DEP_SET be the set of packages whose message types you use in
## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...).
## * In the file package.xml:
## * add a build_depend tag for "message_generation"
## * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET
## * If MSG_DEP_SET isn't empty the following dependency has been pulled in
## but can be declared for certainty nonetheless:
## * add a exec_depend tag for "message_runtime"
## * In this file (CMakeLists.txt):
## * add "message_generation" and every package in MSG_DEP_SET to
## find_package(catkin REQUIRED COMPONENTS ...)
## * add "message_runtime" and every package in MSG_DEP_SET to
## catkin_package(CATKIN_DEPENDS ...)
## * uncomment the add_*_files sections below as needed
## and list every .msg/.srv/.action file to be processed
## * uncomment the generate_messages entry below
## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...)
<< 在被ROS软件包编译和使用之前,ROS中的消息(.msg)、服务(.srv)和操作(.action)文件需要特殊的预处理器编译步骤。
<< 这些宏的要点是生成编程语言特定的文件,以便可以在编程语言中使用messages、services和actions。
<< 编译系统将使用所有可用的生成器(例如gencpp、genpy、genlisp)生成绑定。
<< 提供了三个宏来分别处理messages、services和actions:
<< add_message_files
<< add_service_files
<< add_action_files
<< 这些宏后面必须调用一个调用生成的宏:generate_messages()
<< 在msg文件夹产生 messages
# add_message_files(
# FILES
# Message1.msg
# Message2.msg
# )
<< 在srv文件夹产生 services
# add_service_files(
# FILES
# Service1.srv
# Service2.srv
# )
<< 在action文件夹产生 actions
# add_action_files(
# FILES
# Action1.action
# Action2.action
# )
<< 使用此处列出的任何依赖项生成添加的消息和服务
# generate_messages(
# DEPENDENCIES
# std_msgs # Or other packages containing msgs
# )
################################################
## Declare ROS dynamic reconfigure parameters ##
################################################
## To declare and build dynamic reconfigure parameters within this
## package, follow these steps:
## * In the file package.xml:
## * add a build_depend and a exec_depend tag for "dynamic_reconfigure"
## * In this file (CMakeLists.txt):
## * add "dynamic_reconfigure" to
## find_package(catkin REQUIRED COMPONENTS ...)
## * uncomment the "generate_dynamic_reconfigure_options" section below
## and list every .cfg file to be processed
<< 在cfg文件夹产生 dynamic reconfigure parameters
# generate_dynamic_reconfigure_options(
# cfg/DynReconf1.cfg
# cfg/DynReconf2.cfg
# )
###################################
## catkin specific configuration ##
###################################
## The catkin_package macro generates cmake config files for your package
<< catkin_package()是一个由catkin提供的CMake宏。需要指定特定的catkin信息到编译系统,而这些信息又会被用于生成pkg-config和CMake文件(指出cmake生成pkg-config和CMake文件所必须的信息)。
<< 该函数必须在使用 add_library()或add_executable()声明任何targets之前调用。其5个可选参数:
<< INCLUDE_DIRS:软件包导出的头文件路径(例如cflags),如果包 包含头文件,去掉注释
<< LIBRARIES:项目导出的库(在此项目中创建的依赖项目也需要的库)
<< CATKIN_DEPENDS:当前项目依赖的其他catkin包
<< DEPENDS:当前项目依赖的非catkin CMake包
<< CFG_EXTRAS:其他的配置选项
<< 这里表明软件包文件夹中的include文件夹是导出头文件的位置,
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES my_pkg1
# CATKIN_DEPENDS msgs roscpp rospy
# DEPENDS system_lib
)
<< 例如:
<< catkin_package( INCLUDE_DIRS include
<< LIBRARIES ${PROJECT_NAME}
<< CATKIN_DEPENDS roscpp nodelet
<< DEPENDS eigen opencv
<< )
<< 这里表明软件包文件夹中的include文件夹是导出头文件的位置,
<< CMake环境变量 ${PROJECT_NAME}将会鉴定之前传递给project()函数的所有内容,
<< 在这种情况下它作为“robot_brain”。“roscpp”+“nodelet”是编译/运行此程序包需要存在的软件包,
<< “eigen”+“opencv”是编译/运行此程序包时需要存在的系统依赖项(ROS packages有时会需要操作系统提<< 供一些外部函数库,这些函数库就是所谓的“系统依赖项”)。
catkin_package()宏必须包含一个在message_runtime上的CATKIN_DEPENDS依赖。
###########
## Build ##
###########
## Specify additional locations of header files指定头文件的其他位置
## Your package locations should be listed before other locations
您的包的位置应列在其他位置之前
头文件和库路径,
在指定目标之前,需要指定可以为所述目标找到资源的位置,特别是头文件和库:
头文件目录:将要编译的代码(C/C++)所需的头文件路径
<< include_directories()的参数应该是由调用find_package生成的* _INCLUDE_DIRS变量以及
<< 需要包含的任何其他目录。如果使用catkin和Boost,include_directories()的调用为:
<< include_directories(include {Boost_INCLUDE_DIRS} {catkin_INCLUDE_DIRS})
<< 第一个参数“include”表示包中的include/目录也是路径的一部分
include_directories(
# include
${catkin_INCLUDE_DIRS}
)
<< 声明C++库
<< CMake函数 add_library()指定用于编译的库文件,默认情况下,catkin编译共享库。
# add_library(${PROJECT_NAME}
# src/${PROJECT_NAME}/my_pkg1.cpp
# )
## Add cmake target dependencies of the library
## as an example, code may need to be generated before libraries
## either from message generation or dynamic reconfigure
# add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
<< 声明C++可执行文件
<< 采用catkin_make在CMake中编译所有的包
<< 前缀可以确保包之间的目标名称不冲突
<< 设置需要编译的代码和生成的可执行文件,
<< 第1个参数:期望生成的可执行文件的名称
<< 第2个参数:参与编译的源码文件名称(.cpp)
<< 如果有多个代码文件,则可在后面依次列出
# add_executable(${PROJECT_NAME}_node src/my_pkg1_node.cpp)
<< 重命名C++可执行文件使其没有前缀(去掉前缀)
<< 上面推荐的前缀使得目标的名字较长,为方便用户使用将目标名字重命名为短名字。
<< 例如:用 rosrun someones_pkg node 代替 rosrun someones_pkg someones_pkg_node
# set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "")
## Add cmake target dependencies of the executable
## same as for the library above
<< 用于设置依赖
<< 在很多应用中,需要定义与语言无关的消息类型,消息类型会在编译过程中产生相应的语言代码,如果编译文件
<< 依赖这些动态生成的代码,则需要使用add_dependencies()添加
<< ${PROJECT_NAME}_generate_messages_cpp配置,即该功能包动态产生的消息代码。
# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
<< 指定要链接库或可执行目标的库
<< 使用 target_link_libraries()函数指定可执行目标所要链接的库,即告诉CMake当链接此可执行文件时
<< 需要链接哪些库(这些库在上面的find_package中定义),通常在调用完add_executable()后被调用。
<< 如果出现ros未定义的引用错误,则添加${catkin_LIBRARIES}。
<< 语法:target_link_libraries(<executableTargetName>, <lib1>, <lib2>, ... <libN>)
<< 用于设置链接库,很多功能需要使用系统或者第三方库函数,通过该选项配置库文件
<< 第1个参数:可执行文件的名称--与add_executable()的第1个参数相同
<< 第2个及后续:如果没有链接库,去掉注释,采用默认即可
# target_link_libraries(${PROJECT_NAME}_node
# ${catkin_LIBRARIES}
# )
#############
## Install ##
#############
<< 明确安装目标
<< 所有安装目标都应该使用catkin目标变量
<< 参考文档 http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html
<< Python代码的安装规则有些不同,它不需要使用 add_library()和add_executable()函数来告知CMake
<< 哪个文件是目标文件、目标文件是什么类型的。而是使用如下的catkin_install_python():
<< 如果只是安装了Python的脚本,不提供任何模块的话,就不用创建上文提到的 setup.py文件,也不用调用
<< catkin_python_setup()
<< 标记可执行脚本(Python等)以便安装
<< 与 setup.py 对比,你可选择目标(destination)
# catkin_install_python(PROGRAMS
# scripts/my_python_script
# DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
# )
<< 标记要安装的可执行文件
<< 参考文档:
<< http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_executables.html
# install(TARGETS ${PROJECT_NAME}_node
# RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
# )
<< 编译完成后,目标被放入catkin工作空间下的devel目录。
<< 一般希望将目标安装到系统上,以使其他用户使用,或者安装到本地目录来测试系统级别的安装。
<< 也就是说,如果希望能够对代码进行make install,就需要明确目标结束的位置。
<< 上述过程可以使用CMake的 install()函数实现,该函数的参数有:
<< TARGETS:要安装的目标
<< ARCHIVE DESTINATION:静态库和动态链接库DLL(Windows).lib存根
<< LIBRARY DESTINATION:非DLL共享库和模块
<< RUNTIME DESTINATION:可执行目标和DLL(Windows)模式共享库
<< 标记要安装的库
<< 参考文档:http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_libraries.html
# install(TARGETS ${PROJECT_NAME}
# ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
# LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
# RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION}
# )
<< 头文件必须安装到include目录下,这通常通过安装整个文件夹的文件来完成(可以根据文件名模式进行过滤,
<< 并排除SVN子文件夹)。可以通过一下安装规则实现:
<< 标记要安装的 cpp 头文件
# install(DIRECTORY include/${PROJECT_NAME}/
# DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
# FILES_MATCHING PATTERN "*.h"
# PATTERN ".svn" EXCLUDE
# )
<< 标记要安装的其他文件(如:launch 、包文件等)
<< 安装roslaunch文件或其他源
# install(FILES
# # myfile1
# # myfile2
# DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
# )
#############
## Testing ##
#############
<< 特定的catkin宏 catkin_add_gtest()用于处理基于gtest的单元测试:
# catkin_add_gtest(${PROJECT_NAME}-test test/test_my_pkg1.cpp)
# if(TARGET ${PROJECT_NAME}-test)
# target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME})
# endif()
<< 添加要由python测试运行的文件夹
# catkin_add_nosetests(test)
11 服务节点
<< 头文件
#include "ros/ros.h"
<< 该头文件是根据之前创建的服务数据类型的描述文件AddTwoInts.srv自动生成
<< beginner_tutorials/AddTwoInts.h是从我们之前创建的srv文件中生成的头文件
#include "beginner_tutorials/AddTwoInts.h"
<< service回调函数,输入参数req,输出参数res
<< 这个函数提供了AddTwoInts服务,它接受srv文件中定义的请求(request)和响应(response)类型,并返回一个布尔值。
bool add(beginner_tutorials::AddTwoInts::Request &req,
beginner_tutorials::AddTwoInts::Response &res)
{
<< 将输入参数中的请求数据相加,结果放到应答变量中。
<< 两个整数被相加,和已经存储在了响应中。然后记录一些有关请求和响应的信息到日志中。完成后,服务返回true。
res.sum = req.a + req.b;
ROS_INFO("request: x=%ld, y=%ld", (long int)req.a, (long int)req.b);
ROS_INFO("sending back response: [%ld]", (long int)res.sum);
return true;
}
<< 主函数
<< 先初始化节点,创建节点句柄,重点是要创建一个服务的Server,指定服务名称及接受到服务数据后的回调函数。
<< 然后开始循环等待服务请求;一旦有服务请求,Server就跳入回调函数进行处理
int main(int argc, char **argv)
{
ros::init(argc, argv, "add_two_ints_server");
ros::NodeHandle n;
<< 创建一个名为add_two_ints的server,注册回调函数add()
ros::ServiceServer service = n.advertiseService("add_two_ints", add);
ROS_INFO("Ready to add two ints.");
ros::spin();
return 0;
}
<< 服务中的Server类似于话题中的Subscriber,实现流程总结如下:
<< 1、初始化ROS节点
<< 2、创建Server实例
<< 3、循环等待服务请求,进入回调函数
<< 4、在回调函数中完成服务功能的处理并反馈应答数据
12 客户端节点
#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h"
#include <cstdlib>
int main(int argc, char **argv)
{
<< ROS节点初始化
ros::init(argc, argv, "add_two_ints_client");
<<
if (argc != 3)
{
ROS_INFO("usage: add_two_ints_client X Y");
return 1;
}
<< 创建节点句柄
ros::NodeHandle n;
<< 这将为add_two_ints服务创建一个客户端。ros::ServiceClient对象的作用是在稍后调用服务。
<< 创建一个add_two_ints的Client实例
ros::ServiceClient client = n.serviceClient<beginner_tutorials::AddTwoInts>("add_two_ints");
<< 这里我们实例化一个自动生成的服务类,并为它的request成员赋值。一个服务类包括2个成员变量:request和response,以及2个类定义:Request和Response
<< 实例化一个服务数据类型的变量,该变量包含两个成员:request和response
beginner_tutorials::AddTwoInts srv;
srv.request.a = atoll(argv[1]);
srv.request.b = atoll(argv[2]);
<< 此处实际上调用了服务。由于服务调用被阻塞,它将在调用完成后返回。如果服务调用成功,call()将返回true,并且srv.response中的值将是有效的。如果调用不成功,则call()将返回false且srv.response的值将不可用。
if (client.call(srv))
{
ROS_INFO("Sum: %ld", (long int)srv.response.sum);
}
else
{
ROS_ERROR("Failed to call service add_two_ints");
return 1;
}
return 0;
}
<< 服务中的Client类似于话题中的Publisher,实现流程总结如下:
<< 初始化ROS节点
<< 创建一个Client实例
<< 发布服务请求数据
<< 等待Server处理之后的应答结果
二、使用步骤
1.引入库
代码如下(示例):
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
2.读入数据
代码如下(示例):
data = pd.read_csv(
'https://labfile.oss.aliyuncs.com/courses/1283/adult.data.csv')
print(data.head())
该处使用的url网络请求的数据。
总结
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。