随着机器人领域的快速发展和复杂化,代码的复用性和模块化的需求原来越强烈,而已有的开源机器人系统又不能很好的适应需求。2010年Willow Garage公司发布了开源机器人操作系统ROS(robot operating system),很快在机器人研究领域展开了学习和使用ROS的热潮。
机器人操作系统ROS(Robot Operating System)是一个用于编写机器人软件的灵活框架,它集成了大量的工具、库、协议,提供类似操作系统所提供的功能,包括硬件抽象描述、底层驱动程序管理、共用功能的执行、程序间的消息传递、程序发行包管理,可以极大简化繁杂多样的机器人平台下的复杂任务创建与稳定行为控制。
1. ROS的核心是一个分布式、低耦合的通讯机制;
2. ROS提供多种机器人开发工具,可以快速实现数据可视化、机器人仿真等功能;
3. ROS开源社区中包含大量机器人应用功能,可以帮助我们快速开发功能原型;
4. ROS已经成为一个庞大的生态系统,包含机器人领域的方方面面,同时也得到了越来越多第三方工具的支持,为机器人开发提供了系统化的解决方案。
二、设计目标
ROS是开源的,是用于机器人的一种后操作系统,或者说次级操作系统。它提供类似操作系统所提供的功能,包含硬件抽象描述、底层驱动程序管理、共用功能的执行、程序间的消息传递、程序发行包管理,它也提供一些工具程序和库用于获取、建立、编写和运行多机整合的程序。
ROS的首要设计目标是在机器人研发领域提高代码复用率。ROS是一种分布式处理框架(又名Nodes)。这使可执行文件能被单独设计,并且在运行时松散耦合。这些过程可以封装到数据包(Packages)和堆栈(Stacks)中,以便于共享和分发。ROS还支持代码库的联合系统。使得协作亦能被分发。这种从文件系统级别到社区一级的设计让独立地决定发展和实施工作成为可能。上述所有功能都能由ROS的基础工具实现。
三、主要特点
ROS的运行架构是一种使用ROS通信模块实现模块间P2P的松耦合的网络连接的处理架构,它执行若干种类型的通讯,包括基于服务的同步RPC(远程过程调用)通讯、基于Topic的异步数据流通讯,还有参数服务器上的数据存储。但是ROS本身并没有实时性。
ROS的主要特点可以归纳为以下几条:
(1)点对点设计
一个使用ROS的系统包括一系列进程,这些进程存在于多个不同的主机并且在运行过程中通过端对端的拓扑结构进行联系。虽然基于中心服务器的那些软件框架也可以实现多进程和多主机的优势,但是在这些框架中,当各电脑通过不同的网络进行连接时,中心数据服务器就会发生问题。
ROS的点对点设计以及服务和节点管理器等机制可以分散由计算机视觉和语音识别等功能带来的实时计算压力,能够适应多机器人遇到的挑战。
(2)多语言支持
在写代码的时候,许多编程者会比较偏向某一些编程语言。这些偏好是个人在每种语言的编程时间、调试效果、语法、执行效率以及各种技术和文化的原因导致的结果。为了解决这些问题,我们将ROS设计成了语言中立性的框架结构。ROS现在支持许多种不同的语言,例如C++、Python、Octave和LISP,也包含其他语言的多种接口实现。
ROS的特殊性主要体现在消息通讯层,而不是更深的层次。端对端的连接和配置利用XML-RPC机制进行实现,XML-RPC也包含了大多数主要语言的合理实现描述。我们希望ROS能够利用各种语言实现的更加自然,更符合各种语言的语法约定,而不是基于C语言给各种其他语言提供实现接口。然而,在某些情况下利用已经存在的库封装后支持更多新的语言是很方便的,比如Octave的客户端就是通过C++的封装库进行实现的。
为了支持交叉语言,ROS利用了简单的、语言无关的接口定义语言去描述模块之间的消息传送。接口定义语言使用了简短的文本去描述每条消息的结构,也允许消息的合成,例如下图就是利用接口定义语言描述的一个点的消息:
每种语言的代码产生器就会产生类似本种语言目标文件,在消息传递和接收的过程中通过ROS自动连续并行的实现。这就节省了重要的编程时间,也避免了错误:之前3行的接口定义文件自动的扩展成137行的C++代码,96行的Python代码,81行的Lisp代码和99行的Octave代码。因为消息是从各种简单的文本文件中自动生成的,所以很容易列举出新的消息类型。在编写的时候,已知的基于ROS的代码库包含超过四百种消息类型,这些消息从传感器传送数据,使得物体检测到了周围的环境。
最后的结果就是一种语言无关的消息处理,让多种语言可以自由的混合和匹配使用。
(3)精简与集成
大多数已经存在的机器人软件工程都包含了可以在工程外重复使用的驱动和算法,不幸的是,由于多方面的原因,大部分代码的中间层都过于混乱,以至于很困难提取出它的功能,也很难把它们从原型中提取出来应用到其他方面。
为了应对这种趋势,我们鼓励将所有的驱动和算法逐渐发展成为和ROS没有依赖性单独的库。ROS建立的系统具有模块化的特点,各模块中的代码可以单独编译,而且编译使用的CMake工具使它很容易的就实现精简的理念。ROS基本将复杂的代码封装在库里,只是创建了一些小的应用程序为ROS显示库的功能,就允许了对简单的代码超越原型进行移植和重新使用。作为一种新加入的有优势,单元测试当代码在库中分散后也变得非常的容易,一个单独的测试程序可以测试库中很多的特点。
ROS利用了很多现在已经存在的开源项目的代码,比如说从Player项目中借鉴了驱动、运动控制和仿真方面的代码,从OpenCV中借鉴了视觉算法方面的代码,从OpenRAVE借鉴了规划算法的内容,还有很多其他的项目。在每一个实例中,ROS都用来显示多种多样的配置选项以及和各软件之间进行数据通信,也同时对它们进行微小的包装和改动。ROS可以不断的从社区维护中进行升级,包括从其他的软件库、应用补丁中升级ROS的源代码。
(4)工具包丰富
为了管理复杂的ROS软件框架,我们利用了大量的小工具去编译和运行多种多样的ROS组建,从而设计成了内核,而不是构建一个庞大的开发和运行环境。
这些工具担任了各种各样的任务,例如,组织源代码的结构,获取和设置配置参数,形象化端对端的拓扑连接,测量频带使用宽度,生动的描绘信息数据,自动生成文档等等。尽管我们已经测试通过像全局时钟和控制器模块的记录器的核心服务,但是我们还是希望能把所有的代码模块化。我们相信在效率上的损失远远是稳定性和管理的复杂性上无法弥补的。
(5)免费并且开源
ROS所有的源代码都是公开发布的。我们相信这将必定促进ROS软件各层次的调试,不断的改正错误。虽然像Microsoft Robotics Studio和Webots这样的非开源软件也有很多值得赞美的属性,但是我们认为一个开源的平台也是无可为替代的。当硬件和各层次的软件同时设计和调试的时候这一点是尤其真实的。
ROS以分布式的关系遵循这BSD许可,也就是说允许各种商业和非商业的工程进行开发。ROS通过内部处理的通讯系统进行数据的传递,不要求各模块在同样的可执行功能上连接在一起。如此,利用ROS构建的系统可以很好的使用他们丰富的组件:个别的模块可以包含被各种协议保护的软件,这些协议从GPL到BSD,但是许可的一些“污染物”将在模块的分解上就完全消灭掉。
参考资料:
(1)《开源机器人操作系统——ROS》 张建伟等著
(2)《an open-source Robot Operating System》 paper
(3) willowgarage公司网站:http://www.willowgarage.com/
(4) ROS官方wiki:http://www.ros.org
添加软件库到sources.list文件中
sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
设置秘钥
sudo apt-key adv --keyserver hkp://ha.pool.sks-keyservers.net:80 --recv-key 421C365BD9FF1F717815A3895523BAEEB01FA116
安装ROS
更新软件
sudo apt-get update
桌面完整安装
sudo apt-get install ros-indigo-desktop-full
初始化rosdep
sudo rosdep init
rosdep update
- 配置环境
- 如果ROS环境变量在每次启动新shell时自动添加到bash会话中,这会很方便:
echo "source /opt/ros/indigo/setup.bash" >> ~/.bashrc
source ~/.bashrc
- 如果您安装了多个ROS分发版,~/.bashrc只能为当前使用的版本提供setup.bash。
- 如果只想更改当前shell的环境,则可以键入:
source /opt/ros/indigo/setup.bash
安装rosinstall
rosinstall是ROS中经常使用的命令行工具,可单独分发。 它可以使用一个命令轻松下载ROS软件包的许多源代码树。
sudo apt-get install python-rosinstall
验证是否安装成功
打开终端,输入以下命令:
$ roscore
更多内容:--------------http://wiki.ros.org/cn/ROS/Tutorials
ros使用时的注意事项&技巧
1.rosrun package-name executable-name 比如 rosrun turtlesim turtlesim_node
2.一旦启动roscore后,便可以运行ROS程序了。ROS程序的运行实例被称为节点(node),roscore叫做节点管理器
3.查看节点列表rosnode list
4.需要注意节点名并不一定与对应可执行文件名称相同
5.可以使用 rosrun 命令显式设置节点的名称rosrun package-name executable-name __name:=node-name这种方法将使用 node-name 参数给出的名称覆盖节点的默
认名。
6.查看节点信息rosnode info node-name 终止节点 rosnode kill node-name
7.用 Ctrl-C 命令终止节点。但使用这种方法时可能不会在节点管理器中注销该节点,因此会导致已终止的节点仍然在 rosnode 列表中。这虽然没有什么坏处,但可能会让用户对当前系统的行为感到困扰。此时可以使用下面的命令将节点从列表中删除:rosnode cleanup
8.ROS节点之间进行通信所利用的最重要的机制就是消息传递。在ROS中,消息有组织地存放在话题里。消息传递的理念是:当一个节点想要分享信息时,它就会发布(publish)消息到对应的一个或者多个话题;当一个节点想要接收信息时,它就会订阅(subscribe)它所需要的一个或者多个话题。ROS节点管理器负责确保发布节点和订阅节点能找到对方;而且消息是直接地从发布节点传递到订阅节点,中间并不经过节点管理器转交
9.在 ROS 系统中查看节点之间的发布-订阅关系的最简单方式就是在终端输入如下命令:rqt_graph
10.所有的节点发布都向话题/rosout 发布消息,该话题由同名的/rosout 节点订阅。这个话题的作用是用来生成各个节点的文本日志消息。
11.ROS 节点通常设计成了只管发布它们有用的信息,而不需要担心是否有其他节点来订阅这些消息。这样有助于减少各个节点之间的耦合度。
12.获得话题列表 rostopic list这个列表列举的话题和 rqt_graph 中展示的话题应该是一样的。
13.打印消息内容 rostopic echo topic-name
14.测量发布频率rostopic hz topic-name,带宽rostopic bw topic-name
15.查看话题rostopic info topic-name,可以获得消息类型等信息
16.查看消息类型rosmsg show message-type-name
17.用命令发布消息rostopic pub –r rate-in-hz topic-name message-type message-content,如rostopic pub –r 1 /turtle1/cmd_vel geometry_msgs/Twist ’[2,0,0]’ ’[0,0,0]’
18.问题检查:roswtf
19.创建工作区caktin_ws并创建src子目录用于存放功能包代码,mkdir -p ~/catkin_ws/src
20.src目录下创建功能包catkin_create_pkg package-name,会产生两个文件package.xml和CMakeLists.txt
21.头文件 ros/ros.h 包含了标准 ROS 类的声明,你将会在每一个你写的 ROS 程序中包含它。下面是一个ros helloworld程序
// This is a ROS version of the standard "hello , world"
// program.
// This header defines the standard ROS classes .
#include <ros / ros.h>
int main ( int argc , char ** argv ) {
// Initialize the ROS system .
ros::init ( argc , argv , " hello _ros " ) ;
// Establ ish this program as a ROS node .
ros::NodeHandle nh ;
// Send some output as a log message .
ROS_INFO_STREAM( " Hello , ␣ ROS! " ) ;
}
22.编译hello程序
(1)声明依赖库
在CmakeList.txt中修改 find_package(catkin REQUIRED COMPONENTS package-names)
在package.xml中修改<build_depend>package-name,<run_depend>package-name例如这里的package-name应该是roscpp
(2)声明可执行文件
在CmakeList.txt中add_executable(executable-name source-files),target_link_libraries(executable-name ${catkin_LIBRARIES})
(3)编译工作区catkin_make
(4)Sourcing setup.bash source devel/setup.bash
23.执行hello程序,rosrun 包名 可执行文件名
编写一个发布者程序
#include <ros/ros.h>
#include <geometry_msgs/Twist.h> // For geometry_msgs:: Twist
#include <stdlib.h> // For rand() and RAND_MAX
int main (int argc, char** argv) {
// Initialize the ROS system and become a node .
ros::init(argc, argv, "publish_velocity");
ros::NodeHandle nh;
// Create a publisher obj ect .
ros::Publisher pub = nh.advertise<geometry_msgs::Twist>("turtle1/cmd_vel", 1000) ;
// Seed the random number generator .
srand(time(0));
// Loop at 2Hz until the node is shut down.
ros::Rate rate(2);
while(ros::ok()) {
// Create and fill in the message. The other four
// fields, which are ignored by turtlesim, default to 0.
geometry_msgs::Twist msg;
msg.linear.x = double(rand()) / double(RAND_MAX);
msg.angular.z = 2 * double(rand()) / double(RAND_MAX) - 1;
// Publish the message .
pub.publish(msg);
// Send a message to rosout with the details .
ROS_INFO_STREAM("Sending random velocity command : "<<" linear=" << msg.linear.x<< " angular=" << msg.angular.z);
// Wait untilit's time for another iteration .
rate.sleep();
}
}
上面程序用来给turtlesim仿真器中的海龟发布随机生成的指令,在添加完依赖后,执行结果如下图
编写一个订阅者程序
我们继续使用 turtlesim 作为测试平台,订阅 turtlesim_node发布的/turtle1/pose 话题。这一话题的消息描述了海龟的位姿(位置和朝向)。
这里有三点需要注意:
1)编写回调函数
发布和订阅消息的一个重要的区别是订阅者节点无法知道消息什么时候到达。为了应对这一事实,我们必须把响应收到消息事件的代码放到回调函数里,ROS 每接收到一个新的消息将调用一次这个函数。订阅者的回调函数类似于:
void function_name(const package_name::type_name &msg)
{
}
其中参数 package_name 和 type_name 和发布消息时的相同,它们指明了我们想订阅的话题的消息类。回调函数的主体有权限访问接收到消息的所有域,并以它认为合适的方式存储、使用或丢弃接收到的数据。与往常一样,我们必须包含定义该类的头文件。
2)创建订阅者对象
为了订阅一个话题,我们需要创建一个ros::Subscriber对象 :
ros::Subscriber sub = node_handle.subscribe(topic_name,queue_size, pointer_to_callback_function);
这个构造函数有三个形参,其中大部分与 ros::Publisher 声明中的类似,最后一个参数是回调函数的指针
3)给ROS控制权
最后的复杂之处在于只有当我们明确给ROS许可时,它才会执行我们的回调函数 。实际上有两个略微不同的方式来做到这一点,其中一个版本如下所示:
ros::spinOnce();
这个代码要求 ROS 去执行所有挂起的回调函数,然后将控制权限返回给我们。另一个方法如下所示:
ros::spin();
这个方法要求 ROS 等待并且执行回调函数,直到这个节点关机。换句话说,ros::spin()大体等于这样一个循环:
while(ros::ok( ))
{
ros::spinOnce();
}
使用 ros::spinOnce()还是使用 ros::spin()的建议如下:你的程序除了响应回调函数,还有其他重复性工作要做吗?如果答案是“否”,那么使用 ros::spin();否则,合理的选择是写一个循环,做其他需要做的事情,并且周期性地调用 ros::spinOnce()来处理回调。
订阅者代码
// This program subscribes to turtle1/pose and shows its
// messages on the screen .
#include <ros/ros.h>
#include <std_msgs/String.h>
#include <turtlesim/Pose.h>
#include <iomanip> // for std::setprecision and std::fixed
// A callback function . Executed each time a new pose
// message arrives .
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) {
// Initialize the ROS system and become a node .
ros::init(argc, argv,"subscribe_to_pose");
ros::NodeHandle nh;
// Create a subscri ber obj ect .
ros::Subscriber sub = nh.subscribe("turtle1/pose", 1000, &poseMessageReceived);
// Let ROS take over.
ros::spin();
}
注意在CmakeList.txt添加turtlesim依赖后还会出现找不到turtlesim/Pose.h的情况,这时看看CmakeList中build中的include_directories块有没有被注释掉,如果注释掉就要打开
试验结果:
左边是订阅者收到的消息,右边是随机发送的指令
---
24.通过 rosparam get /run_id 查看 run_id通过runid来查看日志消息
25.清除日志rosclean check ,rosclean purge
26.roslanch启动多个节点的。其基本思想是在一个XML格式的文件内将需要同时启动的一组节点罗列出来
一个launch文件例子
<launch>
<node name="turtlesim_node" pkg="turtlesim" type="turtlesim_node" ns="sim1" respawn="true" />
<node pkg="learn_ros" type="sub" name="sub_pose" output="screen" />
<node pkg="learn_ros" type="pub" required="true" launch-prefix="xterm -e" ns="sim1" name="pub"/>
<node name="turtlesim_node" pkg="turtlesim" type="turtlesim_node" ns="sim2" respawn="true" />
<node pkg="turtlesim" type="turtle_teleop_key" required="true" launch-prefix="xterm -e" ns="sim2" name="pub2"/>
</launch>
说明:respawn参数为真代表如果节点崩了过一会会自动重启节点
output="screen"将结果输出到屏幕
type的参数是可执行的文件名
pkg的参数是功能包名
required为真代表如果这个节点崩了,那么整个launch结束
launch-prefix="xterm -e"表示新开个终端显示数据
ns是命名空间,用于分开控制两只乌龟
上面launch执行后的结果如下:
launch中的重映射
重映射是基于替换的思想:每个重映射包含一个原始名称和一个新名称。每当节点使用重映射中的原始名称时,ROS客户端库就会将它默默地替换成其对应的新名称。
<remap from="turtle1 /cmd_vel" to="turtle1 /cmd_vel_reversed" />
这样原来订阅turtle1 /cmd_vel的节点就会订阅turtle1 /cmd_vel_reversed的消息了
下面是一个包含其他launch文件的示例
<launch>
<include file ="$(find learn_ros)/doublesim.launch" />
<arg name="use_sim3" default="0" />
<group ns="sim3" if="$(arg use_sim3)" >
<node name="turtlesim_node" pkg="turtlesim " type="turtlesim_node" />
<node pkg="turtlesim " type="turtle_teleop_key" name="teleop_key" required="true " launch-prefix="xterm -e"/>
</group>
</launch>
其中use_sim3是参数,可以通过roslaunch learn_ros example.launch use_sim3:=1 来赋值
group可以将一些节点分组到同一个命名空间 sim3
用来查找learn_ros功能包中的doublesim文件,并添加进来