目录
2 入门概述
2.3 使用 turtlesim
ctrl+shift+t可以在一个窗口开三个终端,分别输入
roscore
rosrun turtlesim turtlesim_node
rosrun turtlesim turtle_teleop_key
2.4 功能包/软件包(Packages)
在 ROS 中,所有软件都被组织为软件包的形式,称为 ROS 软件包或功能包。
- 获取所有已安装的ROS软件包列表清单
rospack list
- 找到一个软件包的目录
rospack find package-name//tab 命令补全
- 查看软件包目录下的文件
rosls package-name
- 将当前目录切换至此软件包目录
roscd package-name
2.5 节点管理器(The Master)
启动节点管理器
roscore
ROS节点管理器负责确保发布节点和订阅节点能找到对方,不负责转交消息。
2.6 节点(Nodes)
ROS程序的运行实例被称为节点(node)。
- 启动节点
rosrun package-name executable-name
rosrun是一个 shell 脚本,能够理解 ROS 的文件组织结构。通过节点管理器注册成为 ROS 节点发生在程序的内部,而不是通过 rosrun 命令。
- 获得运行节点列表
rosnode list
rosout 节点通过 roscore 自动启动,作用类似于控制台程序中使用的标准输出(即 std:: cout)。节点名并不一定与对应可执行文件名称相同。
- 显式设置节点的名称
rosrun package-name executable-name __name:=node-name
- 查看节点
rosnode info node-name
//输出包括话题列表;服务列表;其Linux 进程标识符;以及和与其他节点的所有连接。
- 终止节点
rosnode kill node-name
2.7 话题和消息
消息有组织地存放在话题里。当一个节点想要分享信息时,它就会发布(publish)消息到对应的一个或者多个话题;当一个节点想要接收信息时,它就会订阅(subscribe)它所需要的一个或者多个话题。
- 查看节点之间的发布-订阅关系
rqt_graph
椭圆内的/teleop_turtle 节点向横线上的话题/turtle1/cmd_vel 发布消息,而箭头指向的椭圆内的/turtlesim 节点订阅了这些消息。
所有的节点发布都向话题/rosout 发布消息,该话题由同名的/rosout 节点订阅。这个话题的作用是用来生成各个节点的文本日志消息。
- 获取当前活跃的话题
rostopic list
- 查看某个话题上发布的消息
rostopic echo topic-name
- 测量消息发布的频率以及这些消息所占用的带宽
rostopic hz topic-name
rostopic bw topic-name
- 查看话题
rostopic info topic-name
输出第一行给出了这个话题的消息类型,它能告诉你该话题中每个消息携带了哪些信息,以及这些信息是如何组织的。
- 查看消息类型
rosmsg show message-type-name
- 用命令行发布消息
rostopic pub –r rate-in-hz topic-name message-type message-content
- 消息类型的命名
turtlesim + Color ⇒ turtlesim/Color
功能包名 类型名称 消息类型
2.8 例子
ROS 节点——是松耦合的。每个节点都不需要显式知道其他节点的存在与否;它们的唯一交互方式是间接地发生在基于话题和消息的通信层。这种节点之间的独立性,以及其支持的任务可分解特性(即复杂任务分解成可重用的小模块),是 ROS 最关键的设计特性之一。
2.9 问题检查
roswtf
第3章 编写ROS程序
3.1 创建工作区和功能包
- 创建工作区
mkdir /home/lym/ros
mkdir /home/lym/ros/src
src子目录将用于存放功能包的源代码,在该目录下创建一个新ROS功能包。
- 创建功能包
catkin_create_pkg package-name
第一个配置文件,叫做 package.xml清单文件。
第二个文件,叫做 CMakeLists.txt,是一个 Cmake 的脚本文件,Cmake 是一个符合工业标准的跨平台编译系统。这个文件包含了一系列的编译指令,包括应该生成哪种可执行文件,需要哪些源文件,以及在哪里可以找到所需的头文件和链接库。
- 编辑清单文件
至少填写description 和 maintainer 两部分。
3.2 你好,ROS!
3.2.1 一个简单的程序
//头文件 ros/ros.h 包含了标准 ROS 类的声明
#include<ros/ros.h>
int main(int argc, char** argv){
// Initialize the ROS system
ros::init(argc, argv, "hello_ros");//唉又回来改
//Establish this program as a ROS node
ros::NodeHandle nh;
//Send some output as a log message
ROS_INFO_STREAM("Hello, ROS!");
}
ros::NodeHandle(节点句柄)对象是你的程序用于和ROS系统交互的主要机制 4 。创建此对象会将你的程序注册为ROS节点管理器的节点。最简单的方法就是在整个程序中只创建一个NodeHandle对象。
3.2.2 编译 Hello 程序
- 声明依赖库
编译包目录下的CmakeList.txt文件
find_package(catkin REQUIRED COMPONENTS package-names)
编译package.xml文件
<build_depend>package-names</build_depend>
<exec_depend>package-names</exec_depend>
- 声明可执行文件
在 CMakeLists.txt 中添加两行,来声明我们需要创建的可执行文件。为每一个可执行文件复制和修改下面两行代码。
add_executable(executable-name source-files)
target_link_libraries(executable-name ${catkin_LIBRARIES})
- 编译工作区
在自己的工作区目录运行
catkin_make
这里报错!fatal error: ros/ros.h: 没有那个文件或目录.
解决方法:CmakeList.txt文件里的
include_directories(
# include
# ${catkin_INCLUDE_DIRS}
)
注释要去掉改成
include_directories( include ${catkin_INCLUDE_DIRS})
- Sourcing setup.bash
source devel/setup.bash
//sudo gedit ~/.bashrc
除非目录结构发生变化,即使你修改了代码并且用 catkin_make 执行了重编译,也只需要在每个终端执行此命令一次——>每次打开终端时你都需要先运行上面这条命令后才能运行ros相关的命令。在'devel'文件夹里面你可以看到几个setup.sh文件。source这些文件中的任何一个都可以将当前工作空间设置在ROS工作环境的最顶层
3.2.3 执行 hello 程序
//再开一个终端
roscore
//在刚刚source完的终端里执行这条命令
rosrun agtir hello
报错
terminate called after throwing an instance of 'ros::InvalidNameException'
what(): Character [ ] at element [5] is not valid in Graph Resource Name [hello _ros]. Valid characters are a-z, A-Z, 0-9, / and _.
已放弃 (核心已转储)
cpp文件命名有问题!hello.cpp里面的hello _ros多了一个空格!改掉就好了。
3.3 发布者程序
3.3.1 发布消息
- 包含消息类型声明
#include <package_name/type_name.h>
//定义消息类型的包名/消息类型头文件
这个头文件的目的是定义一个C++类,此类和给定的消息类型含有相同的数据类型成员。这个类定义在以包名命名的域名空间中。这样命名的实际影响是当引用C++代码中的消息类时,你将会使用双分号(::)来区分开包名和类型名,双分号也称为范围解析运算符。
- 创建发布者对象
ros::Publisher pub = node_handle.advertise<message_type>(topic_name, queue_size);
如果你想从同一个节点发布关于多个话题的消息,你需要为每个话题创建一个独立的ros::Publisher对象。
- 创建并填充消息对象
geometry_msgs::Twist msg;
msg.linear.x = double(rand())/double(RAND_MAX);
msg.angular.z = 2*double(rand())/double(RAND_MAX) - 1;
C++好,C++妙,对象多的哇哇叫!为什么都2020的情人节了我还没遇到喜欢的男孩子,也没暴富,我太难了。
- 发布消息
使用ros::Publisher对象的publish方法可以很简单地发布消息。
pub.publish(msg);
- 定义输出格式
ROS_INFO_STREAM( "Sending random velocity command : "<< " linear=" << msg.linear.x<< " angular=" << msg.angular.z) ;
3.3.2 消息发布循环
- 节点是否停止工作的检查
ros::ok()//我不是很Ok
- 控制消息发布频率
ros::Rate rate(2);
rate.sleep();
ros::Rate对象控制循环运行速度,其构造函数中的参数以赫兹(Hz)为单位,即每秒钟的循环数。邻近每次循环迭代的结尾,我们调用此对象的sleep方法:rate.sleep()在程序中产生延迟。防止发太快爆了啊!可以用rostopic hz查一下。
发布器程序pubvel.cpp:
#include<ros/ros.h>
#include<geometry_msgs/Twist.h>//包含消息类型声明
#include<stdlib.h>
int main(int argc, char **argv){
ros::init(argc, argv, "publish_velocity");//初始化一个名字为publish_velocity的节点
ros::NodeHandle nh;//创建ros::NOdeHandlel类的对象
//调用nh的advertise方法,<>内是消息类型,“”内是想发布的话题名称,消息序列大小为1000
ros::Publisher pub = nh.advertise<geometry_msgs::Twist>("turtle1/cmd_vel", 1000);
srand(time(0));//创建种子发生器
ros::Rate rate(2);//控制循环运行速度为每秒钟循环两次
while(ros::ok())//检查节点是否运行良好
{
geometry_msgs::Twist msg;
msg.linear.x=double(rand())/double(RAND_MAX);
msg.angular.z=2*double(rand())/double(RAND_MAX)-1;
pub.publish(msg);//发布消息
ROS_INFO_STREAM("SENDING RANDOM VELOCITY COMMAND: "<<"linear="<<msg.linear.x<<"angular="<<msg.angular.z);
rate.sleep();//在循环中产生延迟,阻止循环的迭代速率超过指定速率
}
}
3.3.3 编译pubvel
- 声明消息类型依赖库
除了和编译hello相同的操作,不同之处在于修改CMakeLists.txt文件的find_package行来声明geometry_msgs:
find_package(catkin REQUIRED COMPONENTS roscpp geometry_msgs)
在package.xml文件中,我们添加新的依赖项:
<build_depend>geometry_msgs</build_depend>
<exc_depend>geometry_msgs</exc_depend>
3.3.4 执行pubvel
rosrun agitr pubvel
rosrun turtlesim turtlesim_node
3.4 订阅者程序
- 编写回调函数
发布和订阅消息的一个重要的区别是订阅者节点无法知道消息什么时候到达。为了应对这一事实,我们必须把响应收到消息事件的代码放到回调函数里,ROS每接收到一个新的消息将调用一次这个函数。订阅者的回调函数类似于:
void function_name(const package_name::type_name &msg){. . .}
参数package_name 和type_name和发布消息时的相同,它们指明了我们想订阅的话题的的消息类。必须包含定义该类头文件。
本例程中,回调函数接收类型为turtlesim::Pose的消息,所以包含的头文件是#include<turtlesim/Pose.h>
注意订阅者的回调函数的返回值类型为void。其实这样安排是合理的,因为调用此函数是ROS的工作,返回值也要交给ROS,所以我们的程序无法获得返回值,当然非void的返回值类型也就没有意义了。
工具人嘎嘎嘎。
- 创建订阅者对象
创建一个ros::Subscriber对象:
ros::Subscriber sub = node_handle.subscribe (topic_name,queue_size,pointer_to_callback_function);
通过如下两个方法减少订阅者队列溢出的可能性:(1)通过调用ros::spin或者ros:spinOnce确保允许回调发生;(2)减少每个回调函数的计算时间。
subscribe方法是模板化的,C++编译器会根据我们提供的函数指针中的数据类型判断出正确的消息类型,所以在创建对象时没提到消息类型。
- 给ROS控制权
当我们明确给ROS许可时,它才会执行我们的回调函数,如果忽略调用sor::spin()或者ros::spinOnce(),ROS就永远没有机会去执行你的回调函数。太严重了呜呜。
ros::spin()相当于
while(ros::ok( ))
{
ros::spinOnce();
}
如果程序除了响应回调函数,没有其他重复性工作要做,那么使用ros::spin();否则,合理的选择是写一个循环,做其他需要做的事情,并且周期性地调用ros::spinOnce()来处理回调。
订阅器程序subpos.cpp:
#include<ros/ros.h>
#include<turtlesim/Pose.h>
#include<iomanip>
void poseMessageReceived(const turtlesim::Pose&msg){
ROS_INFO_STREAM(std::setprecision(2)<<std::fixed<<"position=("<<msg.x<<","<<msg.y<<")"<<"*direction="<<msg.theta);
}
int main(int argc, char**argv){
ros::init(argc, argv,"subscribe_to_pose");
ros::NodeHandle nh;
ros::Subscriber sub = nh.subscribe("turtle1/pose",1000,&poseMessageReceived);
ros::spin();
}
第5章 计算图源命名
5.1 全局名称
节点、话题、服务和参数统称为计算图源,而每个计算图源由一个叫计算图源名称(graph resource name)的短字符串标识。
一个全局名称的几个组成部分:
- 前斜杠“/”表明这个名称为全局名称。/turtlesim
- 由斜杠分开的一系列命名空间(namespace),每个斜杠代表一级命名空间。/turtle1/cmd_vel
- 描述资源本身的基本名称(base name)。/teleop_turtle
不是哪个角色都有机会混上全局名称的,多了就不值钱了,真正的贵族来自于相对名称和私有名称!
5.2 相对名称
相对名称的典型特征是它缺少全局名称带有的前斜杠“/”。例子:teleop_turtle、turtlesim
- 解析相对名称
ROS将当前默认的命名空间的名称加在相对名称的前面,从而将相对名解析为全局名称。
套 娃 :/turtle1(默认命名空间) + cmd_vel(相对名称) = /turtle1/cmd_vel(全局名称)
- 设置默认命名空间
默认的命名空间是单独地为每个节点设置的,而不是在系统范围进行的,你不申请就相当于默认使用全局命名空间(/)作为此节点的默认命名空间。
- __ns的命令行参数将为程序指定一个默认命名空间:_ _ns:=default-namespace
- 利用环境变量为在shell内执行的ROS程序设置默认命名空间:Export ROS_NAMESPACE=default-namespace(_ _ns参数不要这块地皮了你才能上!)
- 理解相对名称的目的
用户能方便地将此节点和话题移植到其他的(比如用户自己程序的)命名空间,真正意义在于它使得在小系统基础上实现复杂系统变得更加容易。
5.3 私有名称
私有名称,以一个波浪字符(~)开始,需要ROS客户端库将这个名称解析为一个全局名称。与相对名称的主要差别在于,私有名称不是用当前默认命名空间,而是用的它们节点名称作为命名空间。
有一个节点,它的全局名称是 /sim1/pubvel,ROS将其私有名称∼max_vel转换至如下全局名称:
/sim1/pubvel(节点名) + ∼max_vel(私有名称) = /sim1/pubvel/max_vel(全局名称)
基于事实:给节点内仅供内部使用的资源设置私有名称,不能给话题设置,因为话题是爱豆,不是某个小节点可以单独拥有的!
但是只要知道私有名称解析后的全局名称,都可以通过其全局名称访问这些计算图源。
5.4 匿名名称
一般用于为节点命名,当节点调用 ros::init 方法时可以请求一个自动分配的唯一名称。
ros::init(argc, argv, base_name, ros::init_options::AnonymousName);
第9章 消息录制与回放
通过 rosbag,能够将发布在一个或者多个话题上的消息录制到一个包文件中,然后通过回放这些消息重现相似的运行过程。rosbag 通过订阅需要的话题来记录消息,与其他节点一样,使用的是发布订阅机制。
9.1 录制与回放包文件
包文件(bag files)是指用于存储带时间戳的 ROS 消息的特殊格式文件。
- 录制包文件
rosbag record -O filename.bag topic-names//记录指定具体的话题的消息
rosbag record -a //记录当前发布的所有话题的消息
rosbag record -j //启用包文件的压缩
如果不指定文件名,rosbag 将基于当前的日期和时间自动生成一个。当完成包文件录制时,使用 Ctrl-C 停止 rosbag。
- 回放包文件
rosbag play filename.bag
回放时存储在包文件中的消息会保持与其原始发布时同样的顺序和时间间隔。
- 检查包文件
rosbag info filename.bag
9.2 示例:正方形运动轨迹的包文件
- 绘制正方形轨迹(Drawing squares)
启动 roscore 和turtlesim_node 两个节点。然后从 turtlesim 功能包中启动一个draw_square 节点:
rosrun turtlesim draw_square
- 录制正方形轨迹的包文件
rosbag record -O square.bag /turtle1/cmd_vel /turtle1/pose
rosbag 创建了新的节点,名称为 /record_. . . ,这个节点订阅了/turtle1/cmd_vel 和/turtle1/pose 话题。
- 回放正方形轨迹的包文件
rosbag play square.bag
包的消息在发送,但是我的小乌龟没有动。没有动你反思!你敲代码的时候把/turtle1/cmd_vel敲成了/turtle/cmd_vel!所以还是自动补全比较好...录制前可以查一下有哪些在发布的话题消息,只有发布了才能录制。
rostopic list -v
第10章 总结
10.1 下一步
- 在网络环境中运行 ROS
ROS需要在网络层和ROS层分别进行配置
- 使用 tf 工具来管理多个坐标系
由于机器人运行在真实的物理环境中,通常的做法是用不同的坐标描述机器人不同部件的位置,包括机器人想要避开或者交互的目标。因此,如何正确描述这些坐标所在的坐标系成为了关键问题。在 ROS 中,很多消息类型均包含一个 frame_id 域来标识消息中数据所在的坐标系。ROS提供了一个标准功能包tftf(transformation的缩写),其功能就是帮助节点来完成坐标转换。
- Gazebo
在Gazebo中,我们可以建立机器人和相应场景的仿真模型,然后为仿真机器人定义与实体机器人相同的通信接口。因此,在Gazebo中用仿真机器人测试后的代码可以无缝移植到实体机器人,从而大大提高了机器人调试和开发效率。