2019独角兽企业重金招聘Python工程师标准>>>
[译]机器人操作系统简介:终极机器人应用框架
/*** 原文出处:https://www.toptal.com/robotics/introduction-to-robot-operating-system* @author dogstar.huang <chanzonghuang@gmail.com> 2016-03-05*/
机器人操作系统(ROS)不是一个真实的操作系统,而是一个框架以及一系列为运行在异质计算机集群的操作系统提供基础功能的工具。 它的用处不局限于机器人,还包括大量关注与外设一起工作的工具。
ROS 分成2000多个包,每个包提供特定的功能。连续框架的工具数量可能是它的最大功率。
为什么我应该用ROS?
ROS提供了硬件抽象,设备驱动,多机器进程间的通信,测试和可视化的工具等功能。
ROS的关键特性是软件运行和沟通的方式,使得你可以设计复杂的软件而不需要知道相关的硬件如何工作。ROS提供了一种利用中央枢纽连接进程(节点)网络的方式。 节点可以运行在众多设备上,并且可以通过各种途径与中央枢纽进行连接。
创建网络的主要方式有提供可请求的服务,或者定义与其他节点的发布/订阅者连接。这两种方法都是通过指定消息类型来通信的。 一些类型由核心包提供,而消息类型则可由用户包来定义。
对于小型问题,开发人员可以通过连接已存在的解决方案来集成一个复杂的系统。这种方式的系统已经实现,它允许我们:
在fly上用熟悉的接口替换组件,以便为各种变化移除停止系统的需要
为另一个组件把众多组件的输出混合成一个输出,以便并行解各类问题
只要实现消息系统对应的合适连接器就可以连接各种各样编程语言创造的组件,使得通过连接来自大量开发人员已存在的模块进行软件开发很容易
创建基于设备网络的节点,而不用关心代码在哪里运行以及实现进程通信(IPC)和远程过程调用(RPC)系统
根据远程硬件的需要,通过部署前两个要点即可直接连接馈送,而不用编写任何额外的代码
我们在计划演示通过迭代开发一个简单方案是多么地有用。相比于其他方式,这里有几个关键的好处。ROS有多平台支持以及允许通过隐藏于表面的点对点连接进行多设备的进程连接。 此设计允许支持任何包装了C++通信类的任何语言,或者手动为语言接口开发类。
ROS由我们的社区创建,意味着它也回归于社区。多亏于这个系统架构,经过若干年后,这种氛围导致了大量可重用并易于集成的包的涌现。
一些变种如:http://www.mrpt.org/,http://carmen.sourceforge.net/intro.html, http://lcm-proj.github.io/,http://playerstage.sourceforge.net/,https://msdn.microsoft.com/en-us/library/bb648760.aspx 等提供了其中一些特性,但不是全部。 大多数时候,设计的衰落在于语言支持的局限,未优化进程通信,或者按理说是最难修复的问题 -- 缺少对大量设备的支持。
我们准备构建什么?
既然我们的关注点是框架而不是针对特定问题的具体算法,所以给定的问题是相当简单的。我们的目标就是为一个车载的计算机构建通过Wi-Fi连接的软件, 从而可以通过使用电脑上的游戏把柄和安装在机器人上的摄像机来远程控制和监控机器人。
首先,为了示范ROS的基本原则,我们将会创建一个简单的程序来连接一个简单的模拟。我们会把游戏手柄绑定到电脑上并且尝试为能把游戏手柄输入转换成机器人控制信息而设计一个好的控制方案。
编写ROS代码的只要语言是C++和Python,首选C++是因为它的性能。由于代码中更少的样板和不需要明确的构建,我们将会通过Python来解释示例。
安装与配置
ROS版本由名称来组成。如现在这个日期,最新的发布版本是_Jade Turtle_,而最新的LTS版本是_Indigo Igloo_。最好是使用LTS版本,因为ROS不保证向前兼容性,所以全部的示例将使用_Indigo_这个版本来编写。
ROS可用于大量的*NIX平台,官方支持的版本运行于Ubuntu。OS X,Arch Linux,Debian,Raapbain 和Android版本则由社区支持。
我们将经历在Ubuntu 14.04桌面的安装过程。全部支持的版本和平台的过程都可在官网获得。带ROS的虚拟机也有。
安装是平台依赖性的(大部分平台都有自己提供的包),而工作区间的配置对于全部平台来说都是一样的。
在Ubuntu上安装
ROS提供了自己的仓库。第一步就是要添加它们。
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://pool.sks-keyservers.net --recv-key 0xB01FA116
sudo apt-get update
然后会有针对你的Ubuntu版本的全部ROS的全部托管包。例如,Ubuntu 14.04支持indeigo和jade。
在桌面上安装基本的包可以三选一:
最小化安装:sudo apt-get install ros-indigo-ros-base
带基本额外GUI工具的:sudo apt-get install ros-indigo-desktop
全量安装,即有全部官方特性的,包含了大量模拟器以及导航和知觉类库:sudo apt-get install ros-indigo-desktop-full
为了获得最好的工作体验,推荐全量安装。对于仅仅是用于运行节点的设备安装,基本版本则足够了。不和你选择的是哪个选项,你都可以安装任何需要的package_name包通过执行:
sudo apt-get install ros-indigo-<package-name>
最终的名字将会把下划线替换成横线,所以stage_ros在这个包里
ros-indigo-stage-ros
下一步是安装rosdep。在ROS的包可以声明他们依赖的包。rosdep允许你编译这些包而不用过多地人工维护依赖处理。为了安装它,调用:
sudo rosdep init
rosdep update
ROS有几个被它的工具使用的环境变量。默认的安装,bash脚本在/opt/ros/indigo/setup.bash初始化他们。在每个bash的会话中都需要初始化这些变量,所以最好的解决方案是把他们添加到~/.bashrc。
echo "source /opt/ros/indigo/setup.bash" >> ~/.bashrc
source ~/.bashrc
一些包通过rosinstall来安装扩展的依赖,rosinstall可作为一个包来获得并可通过sudo apt-get install python-rosinstall安装。
这里到了在Ubuntu上安装的最后。接下来是安装工作区间的简短介绍。
配置
自从_Groovy Galapagos后_,ROS工作区间通过catkin来管理。我们需要为全部托管的负定义一个目录。在这个目录里创建一个src目录,并在里面调用catkin_init_workspace。 这将会创建大量的链接到当前ROS版本源的符号。下一步是把这个工作音区也添加到环境变量。
为了演示整个工作区间的配置,选择一个空的目录并执行以下命令:
mkdir src
cd src
catkin_init_workspace
cd ..
catkin_make
echo "source $(pwd)/devel/setup.bash" >> ~/.bashrc
source ~/.bashrc
现在你已经创建了一个可以在里面创建自己ROS包的工作区间。
熟悉工具
马上创建代码是一个很大的跨越。让我们先来熟悉一些运行在屏幕后的系统。第一步是运行基本的GUI并看下它生成了什么消息。
为了在ROS中运行一些东西,需要加载一个核心的进程。这一点很简单,只要在一个新的终端窗口并输入:
roscore
在你的整个连接设备网络中,roscore仅需要在为通信分发托管中央枢纽的设备上加载一次。
roscore的主要角色是告诉节点他们应该连接哪个节点,以及通过哪种方式(不管是通过网络端口还是共享内存)。这样的目的是为了在最小化时间和运行全部通信所需要的带宽时, 让节点只关心他们想知道的数据,而不是他们想要连接的节点。
rqt
运行roscore后,可以为ROS加载主要的GUI工具:rqt。我们会看到很一般的东西 -- 一个空白的窗口。rqt托管了非常大量的可以在可视化配置中进行配置的插件 以及任何数量的预定义视图。
为了能够开始,先运行插件_Robot Steering_,可以通过Plugins > Robot Tools > Robot Steering来选择它。我们会看到有两个滑块,代表着我们的机器人将会拥有的线性和旋转运动。 在插件的顶部可以看到一个有/cmd_vel的输入框。我们可以任意进行重命名。它代表了此操舵发布的主题。终端工具是看到在后台运行了什么的最好地方。
终端工具
ROS有几个非常有用的工具来检查系统中正在发生什么。第一个我们将会介绍的工具是rostopic。 它允许我们检查节点可以评阅和发布的主题。运行```rostopic list···将会产生:
/cmd_vel
/rosout
/rosout_agg
后两个主题通常都会运行并且与中央ROS系统相关。/cmd_vel主题则由我们的操舵发布。在操舵中重命名这个主题也会在这里重全名。现在,我们对于在这个主题里面发生的东西感兴趣。 运行rostopic echo /cmd_vel将看不到什么东西(除非你对滑块作了调整)。这个进程会一直执行,直到我们取消它。让我们把垂直滑块移到20 m/s。看着输出,我们会看到以下一次又一次重复的内容:
linear:x: 0.2y: 0.0z: 0.0
angular:x: 0.0y: 0.0z: 0.0
这个消息重复的频率是多少?rostopic hz /cmd_vel说平均频率是10Hz。好吧,通过我很慢的Wi-Fi连接可以运行多少像这样的主题?rostopic bw /cmd_vel表明平均是480B/s。
到目前为止一切运行良好,但我们将要讨论消息类型。这种数据对于人类是友好的,但应用需要raw格式的数据,并且需要知道消息类型以便它能解析数据。 类型可以通过rostopic type /cmd_vel来决定,告诉我们这是一个geometry_msgs/Twist。任何ROS终端的工具无参数调用时都会返回一个标准的帮助消息。
ROS的wiki非常赞,在网页搜索这个字符串会找到一系列的解释,从它包括了什么到它的结构是怎样的应有尽有。但是我们不用依赖于它。rosmsg是针对消息类型的通用工具。 运行rosmsg show geometry_msgs/Twist会返回:
geometry_msgs/Vector3 linearfloat64 xfloat64 yfloat64 z
geometry_msgs/Vector3 angularfloat64 xfloat64 yfloat64 z
这个消息包含了两个3D向量,表示在三维空间中的线性和旋转速度。
如果我们想知道一个节点连接了什么主题,rosnode info <node-name>会给出关于节点的详细数据。rostopic,rosmsg和rosnode是检查raw ROS功能的主要工具。 ROS有大量的GUI和终端工具,但这些已超出了这次简介的范畴。
运行ROS节点的主要工具是rosrun和roslaunch。rosrun通过rosrun <package_name> <node_name>来运行节点,而roslaunch则基于加载 由于是ROS自动化中最为复杂的元素所以我们知之甚少的文件来运行节点。
我们可以关闭运行的全部东西以开启我们最初的代码。在下文中,我们将省略说明运行任何和ROS相关的东西都需要一个活跃的roscore实例。你遇到的很多问题都可以通过关闭 运行roscore对应的窗口来解决,并且新开一个窗口重新加载。这样的话会更新全部需要重新加载的依赖,包括在bash和在roscore中。
创建游戏柄遥
我们第一个目标是通过创建一个发布geometry_msgs/Twist数据给基于游戏柄输入的/cmd_vel来模拟Robot Steering的功能。第一步的产出是joy包。
joy包
这个joy包为操纵杆和游戏柄提供了通用的ROS驱动。它没有包含在默认的安装,所以需要这样进行安装:
sudo apt-get install ros-indigo-joy
安装完成后,可以运行rosrun joy joy_node。这会把我们和默认的操纵杆或者游戏连接起来。运行rostopic list可以看到有一个叫/joy的主题。通过rostopic echo可以 看到以下格式的消息(请注意你需要使用游戏柄或操纵杆解析待发布的消息)。
header:seq: 4156stamp:secs: 1450707466nsecs: 204517084frame_id: ''
axes: [0.0, 0.0, 0.0, -0.0, 0.0, 0.0, 0.0, 0.0]
buttons: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
现在你可以忽略头部。除此之外,axes和buttons恰到好处地解析了他们代表什么。移动轴和按下在控制器上的按钮会导致这些数字发生变化。使用我们的工具,可以决定这个消息类型 是sensor_msgs/Joy并且对应的格式是:
std_msgs/Header headeruint32 seqtime stampstring frame_id
float32[] axes
int32[] buttons
创建我们的遥
编写代码的第一步是创建一个包。在工作区间的src目录里,运行:
catkin_create_pkg toptal_tutorial rospy joy geometry_msgs sensor_msgs
在这里我们声明了将要创建的包名,紧跟随后的是所要依赖的包。无须担心,依赖可以稍候手动更新。
现在我们有了toptal_tutorial目录。在这个目录里,创建一个将会放置全部Python脚本的scripts目录。
现在来创建一个teleop.py文件,并在里面放置:
#!/usr/bin/env python import rospy from sensor_msgs.msg import Joy def joy_callback(data): print data def main(): rospy.init_node('teleop') rospy.Subscriber('joy', Joy, joy_callback) while not rospy.is_shutdown(): pass if __name__ == '__main__': main()
我们还需要设置chmod +x teleop.py以便脚本能被执行。在某个终端上运行rosrun joy joy_node并有另一个终端上运行rosrun toptal_tutorial teleop.py将会导致运行teleop.py的终端被输出的Joy消息填充。
让我们来检查一下代码做了什么。
首先,导入托管了与ROS框架交互的类库的rospy。每一个定义了消息的包都有一个带消息定义的msg子包。我们导入了Joy以便可以处理输入。 这里不需要导入嵌入式的消息类型(如来自在Joy消息中的std_msgs.msg的Header),除非我们想明确地提及他们。
第一步是初始化一个指定名字的节点(在这里,我们把它叫作“teleop”)。然后创建一个订阅者来订阅sensor_msgs.msg.Joy类型的“joy”主题,并且通过 回调joy_callback函数来处理每一个消息。回调接收一个参数,即来自消息的数据。访问这个数据的成员非常简单。如果想打印第一个轴的状态,或者想 重新调用这个消息类型,可以调用print data.axes[0],并且是一个浮点类型。在最后的循环会一直循环直到ROS关闭。
下一步是处理未知的数据。我们会创建一个根据输入而改变的Twist消息,然后会把它发布到cmd_vel主题。
#!/usr/bin/env python import rospy from sensor_msgs.msg import Joy from geometry_msgs.msg import Twist # new from functools import partial # new def joy_callback(pub, data): # modified cmd_vel = Twist() # new cmd_vel.linear.x = data.axes[1] # new cmd_vel.angular.z = data.axes[0] # new pub.publish(cmd_vel) # new def main(): rospy.init_node('teleop') pub = rospy.Publisher('cmd_vel', Twist, queue_size=1000) # new rospy.Subscriber('joy', Joy, partial(joy_callback, pub)) # modified while not rospy.is_shutdown(): pass if __name__ == '__main__': main()
首先,添加Twist消息,并且为通过functools.partial绑定函数参数添加支持。然后创建一个发布者,pub,来把一个Twist消息类型发布给cmd_vel。我们把这个发布者和回调函数进行绑定,并使它为每一个由前两个轴代表的速度的输入都发布一个Twist消息。 这些代码做了我们期望的它做的事情,而且可以通过rostopic echo /cmd_vel看到结果的输出。
但我们还有一个问题。joy主题发布的速度会很大。如果监控rostopic hz /cmd_vel并在圆圈中移动模拟遥杆,可以看到超级大量的消息。 这不仅仅导致了大量的通信,还导致了接收这些消息的进程需要一个个地处理他们。其实并不需要如此频繁地发布这些数据,最好是以一个稳定的速率如10Hz来发布。 可以通过以下代码来实现。
#!/usr/bin/env python import rospy from sensor_msgs.msg import Joy from geometry_msgs.msg import Twist from functools import partial def joy_callback(cmd_vel, data): # modified cmd_vel.linear.x = data.axes[1] cmd_vel.angular.z = data.axes[0] # moved pub.publish(cmd_vel) to main loop def main(): rospy.init_node('teleop') cmd_vel = Twist() # new pub = rospy.Publisher('cmd_vel', Twist, queue_size=1000) rospy.Subscriber('joy', Joy, partial(joy_callback, cmd_vel)) # modified rate = rospy.Rate(10) # new while not rospy.is_shutdown(): pub.publish(cmd_vel) # new rate.sleep() # new if __name__ == '__main__': main()
我们修改了回调函数以便接收可变的Twist对象并且在循环中作了修改。来自rospy.Rate的sleep函数维护了一个稳定的输出频率。
最后的代码将会导致/cmd_vel主题以10Hz来获得速度命令,以模拟_Robot Steering_ ···rqt```插件的输出。
下一篇:[译]机器人操作系统简介:终极机器人应用框架(下)
------------------------
本作品采用知识共享署名-非商业性使用-相同方式共享 3.0 未本地化版本许可协议进行许可。
本文翻译作者为:dogstar,发表于艾翻译(itran.cc);欢迎转载,但请注明出处,谢谢!