ROS发展史 2007年诞生于丝塔芙STAIR项目Morgan Quigley,2008年Willow Garage接手,2010年ROS1.0发布,2011年Turtle Bot发布,2012年第一届ROScon,2013年OSRF接管,2014年ROS Indigo发布,2016年ROS Kinetic发布,2017年ROS2.0 Ardent发布,2018年ROS Melodic发布
微软将ROS引入Windows10,支持基于硬件加速的Windows机器学习、Azure Conitive服务、Azure IoT云服务、Visual Studio等
AWS RoboMaker扩展了ROS功能,可以轻松实现大规模开发、测试和部署智能机器人应用程序,可以连接到云服务
ROS是什么: 通信机制+开发工具+应用功能+生态系统
提高机器人研发中的软件复用率
ROS中的通信机制: 松耦合分布式通信
ROS中的开发工具: TF坐标变换、QT工具箱、命令行&编译器、Rviz、Gazebo
ROS中的应用功能: Navigation、SLAM、Movelt!
ROS中的生态系统:
发行版(Distribution):ROS发行版包括一系列带有版本号、可以直接安装的功能包
软件源(Repository):ROS依赖于共享网络上的开源代码,不同的组织机构可以开发或者共享自己的机器人软件
ROS wiki:记录ROS信息文档的主要论坛
邮件列表(Mailing list):交流ROS更新的主要渠道,同时也可以交流ROS开发的各种疑问
ROS Answers:咨询ROS相关问题的网站
博客(Blog):发布ROS社区中的新闻、图片、视频(http://www.ros.org/news)
ROS核心概念 通信机制:
节点与节点管理器:
节点(Node):执行单元
执行具体任务的进行、独立运行的可执行文件
不同节点可使用不同的编程语言,各分布式运行在不同的主机
节点在系统中的名称必须是唯一的
节点管理器(ROS Master):控制中心
为节点提供命名和注册服务
跟踪和记录话题/服务通信,辅助节点相互查找、建立连接
提供参数服务器,节点使用此服务器存储和检索运行时的参数
话题(Topic):异步通信机制
节点间用来传输数据的重要总线
使用发布/订阅模型,数据由发布者传输到订阅者,同一个话题的订阅者或发布者可以不唯一
消息(Message):话题数据
具有一定的类型和数据结构,包括ROS提供的标准类型和用户自定义类型
使用编程语言无关的.msg文件定义,编译过程中生成对应的代码文件
服务(Service):同步通信机制
使用客户端/服务器(C/S)模型,客户端发送请求数据,服务器完成处理后返回应答数据
使用编程语言无关的.srv文件定义请求和应答数据结构,编译过程中生成对应的代码文件
话题
服务
同步性
异步
同步
通信模型
发布/订阅
服务器/客户端
底层协议
ROSTCP/ROSUDP
ROSTCP/ROSUDP
反馈机制
无
有
缓冲区
有
无
实时性
弱
强
节点关系
多对多
一对多(一个Server)
适用场景
数据传输
逻辑处理
参数(Parameter):全局共享字典
可通过网络访问的共享、多变量字典
节点使用此服务器来存储和检索运行时的参数
适合存储静态、非二进制的配置参数,不适合存储动态配置的数据
文件系统:
功能包(Package):ROS软件中的基本单元,包含节点源码、配置文件、数据定义等
功能包清单(Package manifest):记录功能包的基本信息,包括作者信息、许可信息、依赖选项、编译标志等
元功能包(Meta Packages):组织多个用于同一目的功能包
ROS命令行工具的使用 常用命令:
创建工作空间与功能包 工作空间: 工作空间(workspace)是一个存放工程开发相关文件的文件夹
src:代码空间(Source Space)
build:编译空间(Build Space)
devel:开发空间(Development Space)
install:安装空间(Install Space)
创建工作空间:
创建工作空间:
mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src catkin_init_workspace
编译工作空间:
cd ~/catkin_ws/ catkin_make
设置环境变量:
检查环境变量:
创建功能包: catkin_create_pkg [depend1] [depend2] [depend3]
# 创建功能包 cd ~/catkin_ws/src catkin_create_pkg test_pkg std_msgs rospy roscpp # 编译功能包 cd ~/catkin_ws catkin_make source ~/catkin_ws/devel/setup.bash
同一工作空间下,不允许存在同名功能包
不同工作空间下,允许存在同名功能包
发布者(Publishers): 发布者是ROS中的一个节点,负责将特定类型的数据发布到 ROS 话题(Topics)。它可以发布传感器数据、状态信息、命令等。其他节点可以订阅这些话题以接收发布的数据。提供数据以供其他节点使用,例如传感器数据、状态信息等
订阅者(Subscribers): 订阅者是ROS中的节点,用于接收和订阅发布者发布到话题上的数据。它能够订阅一个或多个话题,以获取相关的信息进行处理或响应。接收发布者发布的数据,用于执行特定的任务、处理数据或者做出相应的动作
客户端(Clients): 客户端是ROS服务的请求者。它能向服务服务器发送请求,并等待并接收来自服务的响应。通常用于执行特定的任务或获取特定的信息。发送请求,希望服务器执行特定任务或获取信息
服务器(Servers): 服务器是提供ROS服务的节点。它等待来自客户端的请求,并对这些请求做出响应。当接收到客户端请求时,服务器执行相应的任务并返回结果给客户端。接收客户端的请求,执行请求的任务并返回结果给客户端
发布者Publisher的编程实现 话题模型:
实现一个发布者:
初始化ROS节点
向ROS Master注册节点信息,包括发布的话题名和话题中的消息类型
创建消息数据
按照一定频率循环发布消息
小海龟实验中的Publisher:
#include <ros/ros.h> #include <geometry_msgs/Twist.h> int main (int argc, char **argv) { ros::init (argc, argv, "velocity_publisher" ); ros::NodeHandle n; ros::Publisher turtle_vel_pub = n.advertise <geometry_msgs::Twist>("/turtle1/cmd_vel" , 10 ); ros::Rate loop_rate (10 ) ; int count = 10 ; while (ros::ok ()) { geometry_msgs::Twist vel_msg; vel_msg.linear.x = 0.5 ; vel_msg.angular.z = 0.2 ; turtle_vel_pub.publish (vel_msg); ROS_INFO ("Publish turtle velocity command[%0.2f m/s, %0.2f rad/s]" , vel_msg.linear.x, vel_msg.angular.z); loop_rate.sleep (); } return 0 ; }
import rospyfrom geometry_msgs.msg import Twistdef velocity_publisher (): rospy.init_node('velocity_publisher' , anonymous=True ) turtle_vel_pub = rospy.Publisher('/turtle1/cmd_vel' , Twist, queue_size=10 ) rate = rospy.Rate(10 ) while not rospy.is_shutdown(): vel_msg = Twist() vel_msg.linear.x = 0.5 vel_msg.angular.z = 0.2 turtle_vel_pub.publish(vel_msg) rospy.loginfo("Publsh turtle velocity command[%0.2f m/s, %0.2f rad/s]" , vel_msg.linear.x, vel_msg.angular.z) rate.sleep() if __name__ == '__main__' : try : velocity_publisher() except rospy.ROSInterruptException: pass
订阅者Subscriber的编程实现 实现一个订阅者:
初始化ROS节点
订阅需要的话题
循环等待话题消息,接收到消息后进入回调函数
在回调函数中完成消息处理
#include <ros/ros.h> #include "turtlesim/Pose.h" void poseCallback (const turtlesim::Pose::ConstPtr& msg) { ROS_INFO ("Turtle pose: x:%0.6f, y:%0.6f" , msg->x, msg->y); } int main (int argc, char **argv) { ros::init (argc, argv, "pose_subscriber" ); ros::NodeHandle n; ros::Subscriber pose_sub = n.subscribe ("/turtle1/pose" , 10 , poseCallback); ros::spin (); return 0 ; }
import rospyfrom turtlesim.msg import Posedef poseCallback (msg ): rospy.loginfo("Turtle pose: x:%0.6f, y:%0.6f" , msg.x, msg.y) def pose_subscriber (): rospy.init_node('pose_subscriber' , anonymous=True ) rospy.Subscriber("/turtle1/pose" , Pose, poseCallback) rospy.spin() if __name__ == '__main__' : pose_subscriber()
话题消息的定义与使用 话题模型:
自定义话题消息:
string name uint8 sex uint8 age uint8 unknown = 0 uint8 male = 1 uint8 female = 2
定义.msg文件
在package.xml中添加功能包依赖
message_generation
messsage_runtime
在CMakeList.txt添加编译选项
find_package(…… message_generation)
add_message_files(FILES Person.msg)
generate_messages(DEPENDENCISE std_msgs)
catkin_package(…… message_runtime)
编译生成语言相关文件
#include <ros/ros.h> #include "learning_topic/Person.h" int main (int argc, char **argv) { ros::init (argc, argv, "person_publisher" ); ros::NodeHandle n; ros::Publisher person_info_pub = n.advertise <learning_topic::Person>("/person_info" , 10 ); ros::Rate loop_rate (1 ) ; int count = 0 ; while (ros::ok ()) { learning_topic::Person person_msg; person_msg.name = "Tom" ; person_msg.age = 18 ; person_msg.sex = learning_topic::Person::male; person_info_pub.publish (person_msg); ROS_INFO ("Publish Person Info: name:%s age:%d sex:%d" , person_msg.name.c_str (), person_msg.age, person_msg.sex); loop_rate.sleep (); } return 0 ; }
#include <ros/ros.h> #include "learning_topic/Person.h" void personInfoCallback (const learning_topic::Person::ConstPtr& msg) { ROS_INFO ("Subcribe Person Info: name:%s age:%d sex:%d" , msg->name.c_str (), msg->age, msg->sex); } int main (int argc, char **argv) { ros::init (argc, argv, "person_subscriber" ); ros::NodeHandle n; ros::Subscriber person_info_sub = n.subscribe ("/person_info" , 10 , personInfoCallback); ros::spin (); return 0 ; }
import rospyfrom learning_topic.msg import Persondef velocity_publisher (): rospy.init_node('person_publisher' , anonymous=True ) person_info_pub = rospy.Publisher('/person_info' , Person, queue_size=10 ) rate = rospy.Rate(10 ) while not rospy.is_shutdown(): person_msg = Person() person_msg.name = "Tom" person_msg.age = 18 person_msg.sex = Person.male person_info_pub.publish(person_msg) rospy.loginfo("Publish person message[%s, %d, %d]" , person_msg.name,person_msg.age, person_msg.sex) rate.sleep() if __name__ == '__main__' : try : velocity_publisher() except rospy.ROSInterruptException: pass
import rospyfrom learning_topic.msg import Persondef personInfoCallback (msg ): rospy.loginfo("Subcribe Person Info: name:%s age:%d sex:%d" , msg.name, msg.age, msg.sex) def person_subscriber (): rospy.init_node('person_subscriber' , anonymous=True ) rospy.Subscriber("/person_info" , Person, personInfoCallback) rospy.spin() if __name__ == '__main__' : person_subscriber()
客户端Client的编程实现 服务模型:
实现一个客户端:
初始化ROS节点
创建一个Client实例
发布服务请求数据
等待Server处理之后的应答结果
#include <ros/ros.h> #include <turtlesim/Spawn.h> int main (int argc, char ** argv) { ros::init (argc, argv, "turtle_spawn" ); ros::NodeHandle node; ros::service::waitForService ("/spawn" ); ros::ServiceClient add_turtle = node.serviceClient <turtlesim::Spawn>("/spawn" ); turtlesim::Spawn srv; srv.request.x = 2.0 ; srv.request.y = 2.0 ; srv.request.name = "turtle2" ; ROS_INFO ("Call service to spwan turtle[x:%0.6f, y:%0.6f, name:%s]" , srv.request.x, srv.request.y, srv.request.name.c_str ()); add_turtle.call (srv); ROS_INFO ("Spwan turtle successfully [name:%s]" , srv.response.name.c_str ()); return 0 ; }
import sysimport rospyfrom turtlesim.srv import Spawndef turtle_spawn (): rospy.init_node('turtle_spawn' ) rospy.wait_for_service('/spawn' ) try : add_turtle = rospy.ServiceProxy('/spawn' , Spawn) response = add_turtle(2.0 , 2.0 , 0.0 , "turtle2" ) return response.name except rospy.ServiceException, e: print "Service call failed: %s" %e if __name__ == "__main__" : print "Spwan turtle successfully [name:%s]" %(turtle_spawn())
服务端Server的编程实现 实现一个服务器:
初始化ROS节点
创建Server实例
循环等待服务请求,进入回调函数
在回调函数中完成服务功能的处理,并反馈应答数据
#include <ros/ros.h> #include <geometry_msgs/Twist.h> #include <std_srvs/Trigger.h> ros::Publisher turtle_vel_pub; bool pubCommand = false ;bool commandCallback (std_srvs::Trigger::Request &req, std_srvs::Trigger::Response &res) { pubCommand = !pubCommand; ROS_INFO ("Publish turtle velocity command [%s]" , pubCommand==true ?"Yes" :"No" ); res.success = true ; res.message = "Change turtle command state!" return true ; } int main (int argc, char **argv) { ros::init (argc, argv, "turtle_command_server" ); ros::NodeHandle n; ros::ServiceServer command_service = n.advertiseService ("/turtle_command" , commandCallback); turtle_vel_pub = n.advertise <geometry_msgs::Twist>("/turtle1/cmd_vel" , 10 ); ROS_INFO ("Ready to receive turtle command." ); ros::Rate loop_rate (10 ) ; while (ros::ok ()) { ros::spinOnce (); if (pubCommand) { geometry_msgs::Twist vel_msg; vel_msg.linear.x = 0.5 ; vel_msg.angular.z = 0.2 ; turtle_vel_pub.publish (vel_msg); } loop_rate.sleep (); } return 0 ; }
import rospyimport thread,timefrom geometry_msgs.msg import Twistfrom std_srvs.srv import Trigger, TriggerResponsepubCommand = False ; turtle_vel_pub = rospy.Publisher('/turtle1/cmd_vel' , Twist, queue_size=10 ) def command_thread (): while True : if pubCommand: vel_msg = Twist() vel_msg.linear.x = 0.5 vel_msg.angular.z = 0.2 turtle_vel_pub.publish(vel_msg) time.sleep(0.1 ) def commandCallback (req ): global pubCommand pubCommand = bool (1 -pubCommand) rospy.loginfo("Publish turtle velocity command![%d]" , pubCommand) return TriggerResponse(1 , "Change turtle command state!" ) def turtle_command_server (): rospy.init_node('turtle_command_server' ) s = rospy.Service('/turtle_command' , Trigger, commandCallback) print "Ready to receive turtle command." thread.start_new_thread(command_thread, ()) rospy.spin() if __name__ == "__main__" : turtle_command_server()
服务数据的定义与使用 自定义服务数据:
string name uint8 age uint8 sex uint8 unknown = 0 uint8 male = 1 uint8 female = 2 --- string result
定义.srv文件
在package.xml中添加功能包依赖
message_generation
message_runtime
在CMakeList.txt添加编译选项
find_package(…… Message_generation)
add_service_files(FILES Person.srv)
generate_messages(DEPENDENCIES std_msgs)
catkin_package(…… message_runtime)
编译生成语言相关文件
#include <ros/ros.h> #include "learning_service/Person.h" int main (int argc, char ** argv) { ros::init (argc, argv, "person_client" ); ros::NodeHandle node; ros::service::waitForService ("/show_person" ); ros::ServiceClient person_client = node.serviceClient <learning_service::Person>("/show_person" ); learning_service::Person srv; srv.request.name = "Tom" ; srv.request.age = 20 ; srv.request.sex = learning_service::Person::Request::male; ROS_INFO ("Call service to show person[name:%s, age:%d, sex:%d]" , srv.request.name.c_str (), srv.request.age, srv.request.sex); person_client.call (srv); ROS_INFO ("Show person result : %s" , srv.response.result.c_str ()); return 0 ; };
#include <ros/ros.h> #include "learning_service/Person.h" bool personCallback (learning_service::Person::Request &req, learning_service::Person::Response &res) { ROS_INFO ("Person: name:%s age:%d sex:%d" , req.name.c_str (), req.age, req.sex); res.result = "OK" ; return true ; } int main (int argc, char **argv) { ros::init (argc, argv, "person_server" ); ros::NodeHandle n; ros::ServiceServer person_service = n.advertiseService ("/show_person" , personCallback); ROS_INFO ("Ready to show person informtion." ); ros::spin (); return 0 ; }
import sysimport rospyfrom learning_service.srv import Person, PersonRequestdef person_client (): rospy.init_node('person_client' ) rospy.wait_for_service('/show_person' ) try : person_client = rospy.ServiceProxy('/show_person' , Person) response = person_client("Tom" , 20 , PersonRequest.male) return response.result except rospy.ServiceException, e: print "Service call failed: %s" %e if __name__ == "__main__" : print "Show person result : %s" %(person_client())
import rospyfrom learning_service.srv import Person, PersonResponsedef personCallback (req ): rospy.loginfo("Person: name:%s age:%d sex:%d" , req.name, req.age, req.sex) return PersonResponse("OK" ) def person_server (): rospy.init_node('person_server' ) s = rospy.Service('/show_person' , Person, personCallback) print "Ready to show person informtion." rospy.spin() if __name__ == "__main__" : person_server()
参数的使用与编程方法 参数模型:
创建功能包:
cd ~/catkin_ws/src catkin_create_pkg learning_parameter roscpp rospy std_srvs
参数命令行使用: rosparam
列出当前所有参数:rosparam list
显示某个参数数值:rosparam get param_key
设置某个参数值:rosparam set param_key param_value
保存参数到文件:rosparam dump file_name
从文件读取参数:rosparam load file_name
删除参数:rosparam delete param_key
YAML参数文件:
background_b: 255 background_g: 86 background_r: 69 rosdistro: 'noetic' roslaunch: uris: {host_daymac__53059: 'http://daymac:53059' } rosversion: '1.15.14' run_id: 7f4d7d80-7926-11ee-97c1-663d7a150c7e
编程方法实现:
初始化ROS节点
get函数获取参数
set函数设置参数
配置代码编译规则:
#include <string> #include <ros/ros.h> #include <std_srvs/Empty.h> int main (int argc, char **argv) { int red, green, blue; ros::init (argc, argv, "parameter_config" ); ros::NodeHandle node; ros::param::get ("/turtlesim/background_r" , red); ros::param::get ("/turtlesim/background_g" , green); ros::param::get ("/turtlesim/background_b" , blue); ROS_INFO ("Get Backgroud Color[%d, %d, %d]" , red, green, blue); ros::param::set ("/turtlesim/background_r" , 255 ); ros::param::set ("/turtlesim/background_g" , 255 ); ros::param::set ("/turtlesim/background_b" , 255 ); ROS_INFO ("Set Backgroud Color[255, 255, 255]" ); ros::param::get ("/turtlesim/background_r" , red); ros::param::get ("/turtlesim/background_g" , green); ros::param::get ("/turtlesim/background_b" , blue); ROS_INFO ("Re-get Backgroud Color[%d, %d, %d]" , red, green, blue); ros::service::waitForService ("/clear" ); ros::ServiceClient clear_background = node.serviceClient <std_srvs::Empty>("/clear" ); std_srvs::Empty srv; clear_background.call (srv); sleep (1 ); return 0 ; }
import sysimport rospyfrom std_srvs.srv import Emptydef parameter_config (): rospy.init_node('parameter_config' , anonymous=True ) red = rospy.get_param('/turtlesim/background_r' ) green = rospy.get_param('/turtlesim/background_g' ) blue = rospy.get_param('/turtlesim/background_b' ) rospy.loginfo("Get Backgroud Color[%d, %d, %d]" , red, green, blue) rospy.set_param("/turtlesim/background_r" , 255 ); rospy.set_param("/turtlesim/background_g" , 255 ); rospy.set_param("/turtlesim/background_b" , 255 ); rospy.loginfo("Set Backgroud Color[255, 255, 255]" ); red = rospy.get_param('/turtlesim/background_r' ) green = rospy.get_param('/turtlesim/background_g' ) blue = rospy.get_param('/turtlesim/background_b' ) rospy.loginfo("Get Backgroud Color[%d, %d, %d]" , red, green, blue) rospy.wait_for_service('/clear' ) try : clear_background = rospy.ServiceProxy('/clear' , Empty) response = clear_background() return response except rospy.ServiceException, e: print "Service call failed: %s" %e if __name__ == "__main__" : parameter_config()
ROS中的坐标管理系统 机器人中的坐标变化:
TF功能包能干什么?
五秒钟之前,机器人头部坐标系相对于全局坐标系的关系是什么样的
机器人抓取的物体相对于机器人中心坐标系的位置在哪里
机器人中心坐标系相对于全局坐标系的位置在哪里
TF坐标变化如何实现
roslaunch turtle_tf turtle_tf_demo.launch rosrun turtlesim turtle_teleop_key rosrun tf view_frames
输出到当前路径下PDF文件,记录当前ros环境的TF坐标关系:rosrun tf view_frames
不断输出两个TF坐标系关系:rosrun tf tf_echo turtle1 turtle2
Translation:平移数据
Rotation:旋转数据(四元数、弧度、角度)
坐标系之间的可视化工具:rosrun rviz rviz -d`rospack find turtle_tf`/rviz/turtle_rviz.rviz
TF坐标系广播与监听的编程实现 创建功能包:
cd ~/catkin_ws/src catkin_create_pkg learning_tf roscpp rospy tf turtlesim
实现一个TF广播器:
定义TF广播器(TransformBroadcaster)
创建坐标变换值
发布坐标变换(sendTransform)
#include <ros/ros.h> #include <tf/transform_broadcaster.h> #include <turtlesim/Pose.h> std::string turtle_name; void poseCallback (const turtlesim::PoseConstPtr& msg) { static tf::TransformBroadcaster br; tf::Transform transform; transform.setOrigin (tf::Vector3 (msg->x, msg->y, 0.0 )); tf::Quaternion q; q.setRPY (0 , 0 , msg->theta); transform.setRotation (q); br.sendTransform (tf::StampedTransform (transform, ros::Time::now (), "world" , turtle_name)); } int main (int argc, char ** argv) { ros::init (argc, argv, "my_tf_broadcaster" ); if (argc != 2 ) { ROS_ERROR ("need turtle name as argument" ); return -1 ; } turtle_name = argv[1 ]; ros::NodeHandle node; ros::Subscriber sub = node.subscribe (turtle_name+"/pose" , 10 , &poseCallback); ros::spin (); return 0 ; };
import roslibroslib.load_manifest('learning_tf' ) import rospyimport tfimport turtlesim.msgdef handle_turtle_pose (msg, turtlename ): br = tf.TransformBroadcaster() br.sendTransform((msg.x, msg.y, 0 ), tf.transformations.quaternion_from_euler(0 , 0 , msg.theta), rospy.Time.now(), turtlename, "world" ) if __name__ == '__main__' : rospy.init_node('turtle_tf_broadcaster' ) turtlename = rospy.get_param('~turtle' ) rospy.Subscriber('/%s/pose' % turtlename, turtlesim.msg.Pose, handle_turtle_pose, turtlename) rospy.spin()
实现一个TF监听器:
-
#include <ros/ros.h> #include <tf/transform_listener.h> #include <geometry_msgs/Twist.h> #include <turtlesim/Spawn.h> int main (int argc, char ** argv) { ros::init (argc, argv, "my_tf_listener" ); ros::NodeHandle node; ros::service::waitForService ("/spawn" ); ros::ServiceClient add_turtle = node.serviceClient <turtlesim::Spawn>("/spawn" ); turtlesim::Spawn srv; add_turtle.call (srv); ros::Publisher turtle_vel = node.advertise <geometry_msgs::Twist>("/turtle2/cmd_vel" , 10 ); tf::TransformListener listener; ros::Rate rate (10.0 ) ; while (node.ok ()) { tf::StampedTransform transform; try { listener.waitForTransform ("/turtle2" , "/turtle1" , ros::Time (0 ), ros::Duration (3.0 )); listener.lookupTransform ("/turtle2" , "/turtle1" , ros::Time (0 ), transform); } catch (tf::TransformException &ex) { ROS_ERROR ("%s" ,ex.what ()); ros::Duration (1.0 ).sleep (); continue ; } geometry_msgs::Twist vel_msg; vel_msg.angular.z = 4.0 * atan2 (transform.getOrigin ().y (), transform.getOrigin ().x ()); vel_msg.linear.x = 0.5 * sqrt (pow (transform.getOrigin ().x (), 2 ) + pow (transform.getOrigin ().y (), 2 )); turtle_vel.publish (vel_msg); rate.sleep (); } return 0 ; };
import roslibroslib.load_manifest('learning_tf' ) import rospyimport mathimport tfimport geometry_msgs.msgimport turtlesim.srvif __name__ == '__main__' : rospy.init_node('turtle_tf_listener' ) listener = tf.TransformListener() rospy.wait_for_service('spawn' ) spawner = rospy.ServiceProxy('spawn' , turtlesim.srv.Spawn) spawner(4 , 2 , 0 , 'turtle2' ) turtle_vel = rospy.Publisher('turtle2/cmd_vel' , geometry_msgs.msg.Twist,queue_size=1 ) rate = rospy.Rate(10.0 ) while not rospy.is_shutdown(): try : (trans,rot) = listener.lookupTransform('/turtle2' , '/turtle1' , rospy.Time(0 )) except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException): continue angular = 4 * math.atan2(trans[1 ], trans[0 ]) linear = 0.5 * math.sqrt(trans[0 ] ** 2 + trans[1 ] ** 2 ) cmd = geometry_msgs.msg.Twist() cmd.linear.x = linear cmd.angular.z = angular turtle_vel.publish(cmd) rate.sleep()
配置CMakeLists.txt中的编译规则:
launch启动文件的使用方法 Launch文件:通过XML文件实现多节点的配置和启动(可自动启动ROS Master)
launch文件语法:
<launch > <node pkg ="turtlesim" name ="sim1" type ="turtlesim_node" /> <node pkg ="turtlesim" name ="sim2" type ="turtlesim_node" /> </launch >
<launch>
:launch文件中的根元素采用<launch>
来定义
<node>
:启动节点<node pkg="package-name" name="executable-name" type="node-name"/>
pkg:节点所在的功能包名称
type:节点的可执行文件名称
name:节点运行时的名称
output:控制节点是否需要打印日志信息到终端
respawn:控制节点是否需要重启
required:节点是否必须启动
ns:给节点命名空间,避免冲突
args:给节点输入参数
<param>/<rosparam>
:设置ROS系统运行中的参数,存储在参数服务器中<param name="output_frame" value="odom"/>
name:参数名
value:参数值
加载参数文件中的多个参数:<rosparam file="params.yaml" command="load" ns="params"/>
<arg>
:launch文件内部的局部变量,仅限于launch文件使用<arg name="arg-name" default="arg-value"/>
name:参数名
value:参数值
调用:<param name="foo" value="${arg arg-name}"/>
、<node name="node" pkg="package" type="type" args="${arg arg-name}"/>
<remap>
:重映射ROS计算图资源的命名<remap from="/turtlebot/cmd_vel" to="/cmd_vel"/>
<include>
:包含其他launch文件,类似C语言中的头文件包含<include file="$(dirname)/other.launch"/>
更多标签可参见:http://wiki.ros.org/roslaunch/XML
<launch > <node pkg ="learning_topic" type ="person_subscriber" name ="talker" output ="screen" /> <node pkg ="learning_topic" type ="person_publisher" name ="listener" output ="screen" /> </launch >
<launch > <include file ="$(find learning_launch)/launch/simple.launch" /> <node pkg ="turtlesim" type ="turtlesim_node" name ="turtlesim_node" > <remap from ="/turtle1/cmd_vel" to ="/cmd_vel" /> </node > </launch >
<launch > <node pkg ="turtlesim" type ="turtlesim_node" name ="sim" /> <node pkg ="turtlesim" type ="turtle_teleop_key" name ="teleop" output ="screen" /> <node pkg ="learning_tf" type ="turtle_tf_broadcaster" args ="/turtle1" name ="turtle1_tf_broadcaster" /> <node pkg ="learning_tf" type ="turtle_tf_broadcaster" args ="/turtle2" name ="turtle2_tf_broadcaster" /> <node pkg ="learning_tf" type ="turtle_tf_listener" name ="listener" /> </launch >
<launch > <param name ="/turtle_number" value ="2" /> <node pkg ="turtlesim" type ="turtlesim_node" name ="turtlesim_node" > <param name ="turtle_name1" value ="Tom" /> <param name ="turtle_name2" value ="Jerry" /> <rosparam file ="$(find learning_launch)/config/param.yaml" command ="load" /> </node > <node pkg ="turtlesim" type ="turtle_teleop_key" name ="turtle_teleop_key" output ="screen" /> </launch >
常用可视化工具的使用 Qt工具箱:
日志输出工具:rqt_console
计算图可视化工具:rqt_graph
数据绘图工具:rqt_plot
图像渲染工具:rqt_image_view
Rviz: 机器人开发过程中的数据可视化界面
Rviz是一款三维可视化工具,可以很好的兼容基于ROS软件框架的机器人平台
在rviz中,可以使用可扩展标记语言XML对机器人、周围物体等任何实物进行尺寸、质量、位置、材质、关节等属性的描述,并且在界面中呈现出来
同时,rviz还可以通过图像化的方式,实时显示机器人传感器的信息、机器人的运动状态、周围环境的变化等信息
总而言之,rviz通过机器人模型参数,机器人发布的传感器信息等数据,为用户进行所有可监测信息的图形化显示。用户和开发者也可以在rviz的控制下,通过按钮、滑动条、数值等方式控制机器人的行为
Gazebo: 是一款功能强大的三维物理仿真平台
具备强大的物理引擎
高质量的图形渲染
方便的编程与图形接口
开源免费
其典型应用场景包括:
测试机器人算法
机器人的设计
现实情景下的回溯测试