【ROS】ROS基础入门
机器人编程与ROS
机器人是一个带有传感器,作动器(电动机)和一个计算单元的机器。它可以更具用户的控制或者基于传感器的输入做出决定。
- 计算单元:其中计算单元可以是一块单片机,也可以是PC,其中的程序用以控制机器人的决策和行动。机器人内部的编程的主要部分是PC和微控制器/PLC,其中PLC主要用于工业机器人。
- 作动器:作动器为移动机器人提供直线运动或者旋转运动。常用的作动器如伺服电机,步进电机,和减速齿轮等。
- 传感器:反馈机器人的状态和进行环境感知。如轮速编码器,超声波传感器和摄像机等。
机器人编程的一些特性
- 线程化:机器人有多个传感器和作动器,需要兼容多线程的编程语言来实现在不同的线程中使用不同的传感器和作动器,也即多任务。而各个线程之间需要相互通信,以便交换数据。
- 面向对象编程:面向对象编程语言可以使程序更加的模块化,代码也可以很容易的被重复使用。
- 底层设备控制:高层编程语言可以访问底层设备,如GPIO(通用输入输出)引脚,串口,并口,USB,SPI和I2C。C/C++和python这样的编程语言可以使用底层设备,所以这些语言在树莓派等嵌入式计算机中也很受欢迎。
- 编程实现的简便性:python是快速实现机器人算法的一个很好的选择。
- 进程间通信:机器人的多个传感器和作动器之间为了实现相互通信,可以使用多线程架构模型(如ROS)。
- .....
ROS框架的功能
-
进程间的消息传递接口(进程间的通信):ROS提供的消息传递接口可以用于两个程序或进程之间的通信。如相机通过图像处理检测到人脸,并将坐标发送到跟踪器进程,而跟踪器进程就可以使用电动机来跟踪人脸。
-
与操作系统类似的特性:ROS不是真正的操作系统,但是可以提供一些操作系统的功能。如多线程,底层设备控制,包管理和硬件抽象。硬件抽象可以使程序员在不知道设备的完整细节的情况下编写程序,以适应来自不同供应商的产品。
-
支持高级编程语言和工具链:支持C++,Python,Lisp等语言,此外还可以对Java,C#等语言有实验性的支持。ROS为这些语言提供客户端库,所以可以通过这些语言使用ROS中的功能函数。
-
第三方库的可用性:如OpenCV,PCL等被集成在ROS的框架内
-
通用算法:ROS已经实现了一些通用的机器人算法,如PID,SLAM(即使定位与地图构建),路径规划算法,自适应蒙特卡洛定位算法等(AMCL)。
-
编程实现的简便性:除了通用算法之外,ROS包可以很容易的被其他机器人重用。
-
.....
ROS编程开发
- ROS开发的第一步就是创建工作空间,即ROS程序包存储的地方;
- 先用mkdir创建工作的文件夹;
- 然后在此文件夹下创建【src】文件夹,并通过初始化命令【catkin_init_workspace】将该文件初始化为一个ROS的工作空间,否则是不能正确的创建、编译和链接程序包的;
- 编译工作空间:经过上述步骤就创建了工作空间了,虽然此时还没有程序包,但仍然可以编译该工作空间。
- 编译工作空间要在工作空间的根目录下进行,通过【catkin_make】命令进行编译;
- 经过编译之后再工作空间中就会产生build和devel两个文件夹
- 设置环境变量:创建工作空间之后,还要设置系统路径,这样工作空间中的程序包才可以被访问。
- 【source~/catkin_ws/devel/setup.bash】
ROS工作空间
由上述的命令常见了catkin工作空间,该控件是存放工程相关的文件的文件夹,并且可以主要分为下面四个文件夹
-
src:(代码空间,source space) 用来存放所有的功能包,所有功能包的代码,配置文件等都是放在这个里面;ROS的程序包只有在src文件夹下是才可以被编译执行。当执行编译命令【catkin_make】时,该命令就会检查src文件夹中的每个程序包并且一一编译它们。
-
bulid:(编译空间,build space) 存放编译过程中产生的文件;编译过程中会在该文件夹中创建一些中间编译文件和中间缓存文件,这些中间缓存文件可以避免在执行catkin_make命令时重新编译所有的文件。所以如果删了这个文件夹,那么所有的程序包都会被重新编译。比如已经编译了5个程序包了,现在又往src文件夹中添加了一个程序包,那么在执行下一次的catkin_make命令的时候就只有该新的程序包会被编译。
-
devel:(开发空间,development space) 存放编译生成的可执行文件,还有一些脚本文件,这些脚本文件可以将当前的工作空间添加到ROS的系统工作空间目录中。在.bashrc文件中添加以下命令,就可以在所有的终端会话总访问工作空间的程序包了。
source home/lim/<workspace_name>/devel/setup.bash
-
install:(安装空间,install space) ,该文件夹可以通过以下命令创建
catkin_make install
在上述步骤中生成了可执行文件之后,该文件就是存放用install命令安装的结果文件,当运行程序时,install文件夹中的相应的可执行文件就会被执行。
ROS程序包创建
通过以上有了工作空间之后,就要创建程序包了。程序包通过以下命令实现:
catkin_create_pkg ros_package_name package_dependencies
上述命令中的【catkin_create_pkg】是创建程序包的命令,【ros_package_name】是要创建的程序包的名字,【package_dependencies】是程序包的依赖包。
在每个程序包下会看到如图4个文件:
-
CMakeLists.txt:包含了要编译程序包中的ROS源码和要创建可执行文件的所有的命令。
-
package.xml:该XML文件记录了程序包的依赖包和一些与自身相关信息等。
-
src文件夹:ROS程序包的源码保存在该文件夹下,通常,C++ 文件保存在该文件夹中,python脚本另创建文件夹”scripts"保存。
-
include:包含了源程序的头文件,该文件夹可以自动生成, 第三方的函数库文件也可以放在该文件夹中;
ROS客户端库
原则上可以用任意的编程语言编写ROS节点,但是ROS提供了几种编程语言的库,所以此时用这些库就会简单许多。若没有相应的库,那么就需要自己实现ROS库了。主要的客户端库有:
- roscpp:C++的ROS客户端库。具有高性能,广泛使用;
- rospy:python的ROS客户端库。性能相较于roscpp较差,但优势在于节省开发时间,如常用的大多都ROS命令行,roslaunch,roscore等均是用rospy客户端编写的;
- roslisp:Lisp语言的客户端库。主要用于ROS中的运动规划库。
此外还有一些实验性的客户端库如rosjava,roslua等。
但在上面中常用的还是roscpp和rospy
roscpp和rospy
-
头文件和模块
编写程序时,在C++中第一部分要包含头文件,在python中要导入python模块
- C++中:创建C++节点时必须要包含头文件【#include “ros/ros.h"】ros.h中包含了实现ROS功能的所必须的【所有】头文件,不包含这个头文件就不能创建节点。此外,C++中还有第二种头文件,就是ROS消息头文件,如果要在ROS中使用某种特定的消息类型,则需要包含这种【消息头文件】。如ROS中有一些内置的消息类型,内置消息程序包【std_msgs】内就包含有整型,浮点数,字符串等标准数据类型的消息定义,所以如果要使用字符串消息,则要包含头文件【#include "std_msgs/String.h"】,此外还有如【#include "std_msgs/Int32.h"】【#include "std_msgs/Int64.h"】等消息类型,其中前一部分是程序包名,后部分是消息类型名。
- Python中:Python中要导入模块来创建节点,对应的这里是【import rospy】.同样,要是使用消息类型,也需要导入【from std_msgs.msg import String】
-
初始化ROS节点
【在开始编写任何节点之前,都要初始化ROS节点】。
-
C++中:用以下几行代码初始化节点
int main(int argc,char *argv) { ros::init(argc,argv,"name_of_node") ... }
在main函数中必须要包含ros::init()函数,该函数用来初始化节点,另外还可以通过(argc,argv等)向初始化函数传递命令行参数和节点名称。
-
Python中:第一个参数是节点名,第二个参数=True意味着可以在系统中同时运行该节点的多个实例。
rospy.init_node('name_of_node',anonymous=True);
-
-
在ROS节点中打印日志信息
ROS提供了记录日志信息的应用程序接口,这些信息是可读字符串,【表示节点的运行状态】
-
C++中:ROS_INFO(string_msg,args):记录节点输出的基本信息。
ROS_WARN(string_msg,args):记录节点输出的警告信息。
ROS_DEBUG(string_msg,args):记录节点输出的调试信息。等等
-
Python中:rospy.logingo(msg,*args)
rospy.logwarn(msg,*args)
rospy.logdebug(msg,*args)
-
-
创建节点句柄
初始化节点之后需要创建一个节点句柄实例,用于启动ROS节点或发布,订阅等操作。
-
C++中:如下,节点中的其他操作可以通过使用nh实例来访问。
ros::NOdeHandle nh;
-
python中:不需要,rospy模块内部自动解决这个问题
-
-
创建ROS消息定义
发布话题之前,要先创建一个ROS消息定义
-
C++中:创建std_msgs/String实例方式如下
std_msgs::String msg; //创建之后添加数据 msg.data="Hello"
-
python中:添加相同的字符串数据如下
msg=String() msg.data="Hello"
-
-
在ROS节点中发布话题
-
C++:发布话题语法如下,chatter_pub是发布者实例,chatter_pub会发布消息类型为std_msgs/String的话题,话题的名称为chatter,队列大小为1000
ros::Publisher publisher_object=node_handle.advertise<ROS message type>("topic_name",1000) //创建了待发布的话题之后,通过publish发出 publisher_object.publish(message) 例如 ros::Publisher chatter_pub=nh.advertise<std_msgs::String>("Chatter",1000) chatter_pub.publish(msg)
-
python:语法如下
publisher_instance=rospy.Publisher('topic_name',message_type,queue_size) 例如: pub=rospy.Publisher('chatter',String,queue_size=1000) pub.publish(hello_str)
-
-
在ROS节点中订阅话题
-
C++:订阅话题时不需要话题的类型,但是要给定【话题名和对应的回调函数】。回调函数是用户自定义的函数,一旦从话题中接收到ROS消息,那么回调函数就会被执行。在回调函数中,我们可以自定义要操作的内容,如可以打印消息或者根据消息做出决定。回调函数的编写看下一小节。
ros::Subscriber subscriber_obj=nodehandle.subscribe("topic_name",1000,callback function)
-
Python:在python中要指定消息的类型,同样也可以设置回调函数
-
rospy.Subscriber("topic_name",message_type,callback funtion name)
-
-
在ROS节点中编写回调函数
当订阅的话题有消息到达时,回调函数就会被触发。
-
C++:例子中是在回调函数中处理ROS字符串消息并显示数据。
语法: void callback_name(const ros_message_const_pointer&pointer) { pointer->data//获取数据 } 实例: void chatterCallback(const std_msgs::String::ConstPtr&msg) { ROS_INFO("I heard:[%s]",msg->data.c_str()); }
-
python:
def callback(data): rospy.loginfo(rospy.get_caller_id()+"I heard %s",data.data)
-
-
ROS节点中的ROS spin()函数
在订阅或者发布函数之后,还需要调用一个函数来处理来自其他节点的订阅请求或发布请求。
- C++:若只订阅话题,那么就调用ros::spinOnce()函数。若既要订阅又要发布,那么就调用ros::spin()函数
- python:python中没有spin()函数。可以使用rospy.sleep()
-
ROS节点中的循环设置和ROS sleep()函数
为节点的循环设置设定一个恒定的速率,那么就可以用ros::Rate创建实例,并调用sleep()函数使之生效。
-
C++:
ros::Rate r(10); //10Hz的速率 r.sleep();
-
python:
rate=rospy.Rate(10) rate.sleep()
-
-
设置和获取ROS参数
-
C++:获取参数如下。之前先要声明一个变量,然后用节点句柄node_handle中的【getParam()函数】获取所需要的参数
std::string global_name; if(nh.getParam("/global_name",global_name)) { .... }
设置参数如下:
nh.setParam("/global_param",5)
-
python:
global_name=rospy.get_param("/global_name")rospy.set_param('~private_int','2')
-
ROS编程程序包实例
在该实例中创建名为hello_world的程序包,和一个发送节点和一个接受节点,用于传递字符串“Hello World"消息。
1.创建hello_world程序包
要在工作空间的src文件夹中创建程序包,创建之后产生的文件如下图所示。
catkin_create_pkg hello_world roscpp rospy std_msgs
其中的package.xml文件中包含了程序包及其依赖包的相关信息。
CMakeLists.txt是cmake文件,工程的名字在该文件的顶部,其内find_package命令用于寻找这个程序包所必须的系统依赖包。其内catkin_package命令是catkin提供的一个CMake宏,用于向编译系统指定工程所需要依赖的其他ROS程序包。
2.创建ROS C++ 节点
创建完程序包之后就可以创建节点,C++代码放在src文件夹中。如下的talker.cpp中,
talker.cpp
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <stream>
int main()
{
ros::init(argc,argv,"talker");
ros::NodeHandle n;
msgs::String>("chatter",1000);
ros::Rate loop_rate(10);
int count=0;
while(ros::ok())
{
std_msgs::String msg;
std::stringstream ss;
ss<<"hello world"<< count;
msg.data=ss.str();
ROS_INFO("%s",msg.data.c_str());
chatter_pub.publish(msg);
ros::spinOnce();
loop_rate.sleep();
++count;
}
return 0;
}
listener.cpp
#include "ros/ros.h"
#include "std_msgs/String.h"
void chatterCallback(const std_msgs::String::ConstPtr&msg)
{
ROS_INFO("I heard:[%s]",msg->data.c_str());
}
int main(int argc,char **argc)
{
ros::init(argc,argv,"listener");
ros::NodeHandle n;
ros::Subscriber sub=n.subscribe("chatter",1000,chatterCallback);
ros::spin();
return 0;
}
3.编辑CMakeLists.txt文件
创建节点之后,需要编辑CMakeLists.txt文件,然后通过编译节点来创建可执行文件。如下所示,其中的add_executable表示从源代码中创建可执行文件,然后该文件通过target_link_libraries命令和指定的库函数相链接。
add_executable(talker src/talker.cpp)
add_executable(listener src/listener.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
target_link_libraries(listener ${catkin_LIBRARIES})
4.编译C++节点
编辑完CMakeLists文件后就可以编译了,切换到工作空间的文件夹下,然后用编译命令catkin_make。
5.执行C++节点
编译完成之后的可执行文件存放在catkin_ws/devel/lib/{程序包名}/文件夹下,则执行该节点可通过如下的命令:
roscore
rosrun 程序包名 节点名
在执行rosrun命令之前先要设置一下环境变量 ,要在工作空间下进行,即source devel/setup.bash
或者在.bashrc中加入如下的命令
source /home/用户名/工作空间名/devel/setup.bash
6.创建ROS python节点
首先在程序包中创建名为【scripts】文件夹,然后python的脚本文件都保存在这个文件夹中。
talker.py
#!/usr/bin/env python
# license removed for brevity
import rospy
from std_msgs.msg import String
def talker():
pub = rospy.Publisher('chatter', String, queue_size=10)
rospy.init_node('talker', anonymous=True)
rate = rospy.Rate(10) # 10hz
while not rospy.is_shutdown():
hello_str = "hello world %s" % rospy.get_time()
rospy.loginfo(hello_str)
pub.publish(hello_str)
rate.sleep()
if __name__ == '__main__':
try:
talker()
except rospy.ROSInterruptException:
pass
listener.py
#!/usr/bin/env python
import rospy
from std_msgs.msg import String
def callback(data):
rospy.loginfo(rospy.get_caller_id() + "I heard %s", data.data)
def listener():
# In ROS, nodes are uniquely named. If two nodes with the same
# name are launched, the previous one is kicked off. The
# anonymous=True flag means that rospy will choose a unique
# name for our 'listener' node so that multiple listeners can
# run simultaneously.
rospy.init_node('listener', anonymous=True)
rospy.Subscriber("chatter", String, callback)
# spin() simply keeps python from exiting until this node is stopped
rospy.spin()
if __name__ == '__main__':
listener()
7.对python脚本编辑CMakeLists.txt文件
创建好脚本文件后,还要在CMakeLists.txt文件中编辑。
catkin_install_python(PROGRAMS scripts/talker.py
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
catkin_install_python(PROGRAMS scripts/talker.py scripts/listener.py
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
8.弄完之后最好也编译以下
用catkin_make命令在工作空间中编译一下。
9.运行python节点
rosrun 程序包名 脚本名
10.创建launch启动文件
ROS启动文件可以在一条指令中运行任意数量的节点。
-
C++: 该启动文件可以以此运行talker和listener两个节点,程序包在"pkg="域中,可执行文件在"type="域中,【其中”name=''为节点的名字,可以任意命名】,此时若执行rosnode list命令,则节点显示的名字就是这里面的名字。注意,这里面的pkg,name,type 的顺序可以任意,另外, 组后的output是将结果显示在屏幕上,若没有这个,则节点执行了,一些结果就不显示了。将这个文件保存为【xxxx.launch】,则运行时就要执行【roslaunch 程序包名 xxxx.launch】
<launch> <node pkg="hello_world" name="listener_node" type="listener" output="screen"/> <node pkg="hello_world" name="talker_node" type="talker" output="screen"/> </launch>
-
python:python中除了将type中的可执行文件的名字改一改,其他不变,运行时也同上。
<launch> <node pkg="hello_world" name="listener_node" type="listener.py" output="screen"/> <node pkg="hello_world" name="talker_node" type="talker.py" output="screen"/> </launch>
ROS其他相关
ROS话题的类型
如小乌龟移动的节点,要向turtle1/cmd_vel话题发送指令,则要查看该话题的发送的数据类型:
rostopic type /turtle1/cmd_vel
geometry_msgs/Twist
则得到话题的消息类型是geometry_msgs/Twist,则发送命令时要按照该类型发送,现在查看该类型的定义:
rosmsg show geometry_msgs/Twist
geometry_msgs/Vector3 linear
float64 x
float64 y
float64 z
geometry_msgs/Vector3 angular
float64 x
float64 y
float64 z
结果如上所示,该消息的定义有如上两部分。
ROS服务和参数
ROS中的服务是另外的一种通讯形式,采用一问一答的方式进行节点之间的通信。
rosservice list
调用:rosservice call 参数
查看服务参数:rosservice type /服务名
接上一行,具体参数:rossrv show
ROS参数用法:
rosparam set set parameter
rosparam get get parameter
rosparam load load parameters from file
rosparam dump dump parameters to file
rosparam delete delete parameter
rosparam list list parameter names