实验编号: 实验指导书 实验项目: R0S基本元素实验2 所属课程: ROS机器人操作系统基础与实战 课程代码: 面向专业: 机电专业 课程负责人: 朱笑笑 年月 日
实验编号: 实 验 指 导 书 实验项目: ROS 基本元素实验 2 所属课程: ROS 机器人操作系统基础与实战 课程代码: 面向专业: 机电专业 课程负责人: 朱笑笑 年 月 日
、 实验目的 让学生熟悉ROS基本元素的使用方法如,Node节点,topic,servicef的创建 过程、使用、查看。这些基础元素是OS计算图结构的关键元素,对这些的理解 是对ROS程序理解和开发的基础。 二、 实验内容 1. 创建ROS消息和ROS服务 2. Topic的使用 3. Service的使用 4. Launch的使用 三、 实验过程或其他示意图 首先创建一个package,本实验均在该包中测试。 cd ~/catkin ws/src mkdir chapter3/ cd chapter3 catkin create_pkg topic_service std_msgs rospy roscpp 1. 创建ROS消息和ROS服务 1.1使用msg 1.1.1创建一个msg 下面,我们将在之前创建的package里定义新的消息。 cd ~/catkin ws/src/chapter3/topic service mkdir msg echo "int64 num">msg/Num.msg 上面是最简单的例子一在.msg文件中只有一行数据。当然,你可以仿造上面的形式多 增加几行以得到更为复杂的消息: string first name string last name uint8 age uint32 score 接下来,还有关键的一步:我们要确保msg文件被转换成为C++,Python和其他语 言的源代码: 查看package.ml,确保它包含一下两条语句: message generation message runtime -2
- 2 - 一、 实验目的 让学生熟悉ROS基本元素的使用方法如,Node节点,topic,service的创建 过程、使用、查看。这些基础元素是ROS计算图结构的关键元素,对这些的理解 是对ROS程序理解和开发的基础。 二、 实验内容 1. 创建ROS消息和ROS服务 2. Topic的使用 3. Service的使用 4. Launch的使用 三、 实验过程或其他示意图 首先创建一个package,本实验均在该包中测试。 $ cd ~/catkin_ws/src $ mkdir chapter3/ $ cd chapter3 $ catkin_create_pkg topic_service std_msgs rospy roscpp 1. 创建 ROS 消息和 ROS 服务 1.1 使用 msg 1.1.1 创建一个 msg 下面,我们将在之前创建的 package 里定义新的消息。 $ cd ~/catkin_ws/src/chapter3/ topic_service $ mkdir msg $ echo "int64 num" > msg/Num.msg 上面是最简单的例子——在.msg 文件中只有一行数据。当然,你可以仿造上面的形式多 增加几行以得到更为复杂的消息: string first_name string last_name uint8 age uint32 score 接下来,还有关键的一步:我们要确保 msg 文件被转换成为 C++,Python 和其他语 言的源代码: 查看 package.xml, 确保它包含一下两条语句: message_generation message_runtime
如果没有,添加进去。注意,在构建的时候,我们只需要"message_generation'。然 而,在运行的时候,我们只需要"message_runtime"。 在你最喜爱的编辑器中打开CMakeLists.txt文件(何以参考前边的教程rosed). 在CMakeLists.txt文件中,利用find_packag函数,增加对message_generation 的依赖,这样就可以生成消息了。你可以直接在COMPONENTS的列表里增加 message generation,就像这样: Do not just add this line to your CMakeLists.txt,modify the existing line find package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation) 同样,你需要确保你设置了运行依赖: catkin_package( ·◆· CATKIN DEPENDS message_runtime··· ) 找到如下代码块: add message_files( FILES Messagel.msg Message2.msg #) 去掉注释符号#,用你的.msg文件替代Message*.msg,就像下边这样: add_message_files( FILES Num.msg 手动添加.msg文件后,我们要确保CMake知道在什么时候重新配置我们的project。确 保添加了如下代码: generate_messages() 现在,你可以生成自己的消息源代码了。 使用catkin_make 1.1.2使用rosmsg 以上就是你创建消息的所有步骤。下面通过rosmsg show命令,检查ROS是否能够 识消息。 使用方法: -3-
- 3 - 如果没有,添加进去。 注意,在构建的时候,我们只需要"message_generation"。然 而,在运行的时候,我们只需要"message_runtime"。 在你最喜爱的编辑器中打开 CMakeLists.txt 文件(可以参考前边的教程 rosed). 在 CMakeLists.txt 文件中,利用 find_packag 函数,增加对 message_generation 的依赖,这样就可以生成消息了。 你可以直接在 COMPONENTS 的列表里增加 message_generation,就像这样: # Do not just add this line to your CMakeLists.txt, modify the existing line find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation) 同样,你需要确保你设置了运行依赖: catkin_package( ... CATKIN_DEPENDS message_runtime ... ...) 找到如下代码块: # add_message_files( # FILES # Message1.msg # Message2.msg # ) 去掉注释符号#,用你的.msg 文件替代 Message*.msg,就像下边这样: add_message_files( FILES Num.msg ) 手动添加.msg 文件后,我们要确保 CMake 知道在什么时候重新配置我们的 project。确 保添加了如下代码: generate_messages() 现在,你可以生成自己的消息源代码了。 使用 catkin_make 1.1.2 使用 rosmsg 以上就是你创建消息的所有步骤。下面通过 rosmsg show 命令,检查 ROS 是否能够 识消息。 使用方法:
rosmsg show [message type] 样例: rosmsg show topic service/Num 你将会看到: int64 num 在上边的样例中,消息类型包含两部分: topic service-消息所在的package ● Num-消息名Num. 如果你忘记了消息所在的package,你也可以省略掉package名。输入: rosmsg show Num 你将会看到: [topic service /Num]: int64 num 1.2使用srV 1.2.1创建一个srv 在刚刚那个package中创建一个服务: roscd topic_service mkdir srv 这次我们不再手动创建服务,而是从其他的package中复制一个服务。roscp是一个 很实用的命令行工具,它实现了将文件从一个package复制到另外一个package的功能。 使用方法: s roscp [package_name][file_to_copy_path][copy_pathl 现在我们可以从rospy._tutorials package中复制一个服务文件了: roscp rospy_tutorials AddTwoInts.srv srv/AddTwoInts.srv 还有很关键的一步:我们要确保sv文件被转换成C++,Python和其他语言的源代码。 现在认为,你已经如前边所介绍的,在CMakeLists.txt文件中增加了对 message_generation的依赖。: Do not just add this line to your CMakeLists.txt,modify the existing line find package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs -4-
- 4 - $ rosmsg show [message type] 样例: $ rosmsg show topic_service/Num 你将会看到: int64 num 在上边的样例中,消息类型包含两部分: topic_service -- 消息所在的 package Num -- 消息名 Num. 如果你忘记了消息所在的 package,你也可以省略掉 package 名。输入: $ rosmsg show Num 你将会看到: [topic_service /Num]: int64 num 1.2 使用 srv 1.2.1 创建一个 srv 在刚刚那个 package 中创建一个服务: $ roscd topic_service $ mkdir srv 这次我们不再手动创建服务,而是从其他的 package 中复制一个服务。roscp 是一个 很实用的命令行工具,它实现了将文件从一个 package 复制到另外一个 package 的功能。 使用方法: $ roscp [package_name] [file_to_copy_path] [copy_path] 现在我们可以从 rospy_tutorials package 中复制一个服务文件了: $ roscp rospy_tutorials AddTwoInts.srv srv/AddTwoInts.srv 还有很关键的一步:我们要确保 srv 文件被转换成 C++,Python 和其他语言的源代码。 现在认为,你已经如前边所介绍的,在 CMakeLists.txt 文件中增加了对 message_generation 的依赖。: # Do not just add this line to your CMakeLists.txt, modify the existing line find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs
message generation) (对的,message_generation对msg和srv都起作用) 同样,跟msg文件类似,你也需要在package.xml文件中做一些修改。查看上边的说明, 增加额外的依赖项。 删掉#,去除对下边语句的注释: add service files( FILES Servicel.srv Service2.srv #) 用你自己的sV文件名替换掉那些Service*.srv文件: add service files( FILES AddTwoInts.srv 现在,你可以生成自己的服务源代码了。如果你想立即实现,那么就跳过以下部分,到 1.2.2使用rossrv 以上就是创建一个服务所需的所有步骤。下面通过rossrv show命令,检查ROS是 否能够识该服务。 使用方法: s rossrv show 例子: rossrv show topic service/AddTwoInts 你将会看到: int64 a int64 b --- int64 sum 跟rosmsg类似,你也可以不指定具体的package名来查找服务文件: rossrv show AddTwoInts [topic_service/AddTwoInts]: int64 a int64 b int64 sum [rospy_tutorials/AddTwoInts]: int64 a -5-
- 5 - message_generation) (对的, message_generation 对 msg 和 srv 都起作用) 同样,跟 msg 文件类似,你也需要在 package.xml 文件中做一些修改。查看上边的说明, 增加额外的依赖项。 删掉#,去除对下边语句的注释: # add_service_files( # FILES # Service1.srv # Service2.srv # ) 用你自己的 srv 文件名替换掉那些 Service*.srv 文件: add_service_files( FILES AddTwoInts.srv ) 现在,你可以生成自己的服务源代码了。如果你想立即实现,那么就跳过以下部分,到 1.2.2 使用 rossrv 以上就是创建一个服务所需的所有步骤。下面通过 rossrv show 命令,检查 ROS 是 否能够识该服务。 使用方法: $ rossrv show 例子: $ rossrv show topic_service/AddTwoInts 你将会看到: int64 a int64 b --- int64 sum 跟 rosmsg 类似, 你也可以不指定具体的 package 名来查找服务文件: $ rossrv show AddTwoInts [topic_service/AddTwoInts]: int64 a int64 b --- int64 sum [rospy_tutorials/AddTwoInts]: int64 a
int64 b int64 sum 1.3msg和srv都需要的步骤 接下来,在CMakeLists.txt中找到如下部分: generate messages( DEPENDENCIES #std msgs or other packages containing msgs #) 去掉注释并附加上所有你消息文件所依赖的那些含有.msg文件的package(这个例子 是依赖std_msgs,,不要添加roscpp,,rospy)),结果如下: generate messages( DEPENDENCIES std msgs 由于增加了新的消息,所以我们需要重新编译我们的package: In your catkin workspace $cd../.. catkin make $cd- 所有在msg路径下的.msg文件都将转换为ROS所支持语言的源代码。生成的C++ 头文件会放置在~/catkin ws/devel./include/topic service/。Python脚本语言会 ~/catkin ws/devel/lib/python2.7/dist-packages/topic_service/msg 录下创建。Iisp文件会出现在~/catkin ws/devel/share./common-lisp /ros/topic service./msg/路径下. 2. Topick的使用 2.1编写发布器节点 节点(Node)是指ROS网络中可执行文件。接下来,我们将会创建一个发布器节点 ('talker"),它将不断的在ROS网络中广播消息。 切换到之前创建的topic_.service package路径下: cd ~/catkin_ws/src/topic_service 源代码 在topic_.service package路径下创建一个src文件夹: -6-
- 6 - int64 b --- int64 sum 1.3msg 和 srv 都需要的步骤 接下来,在 CMakeLists.txt 中找到如下部分: # generate_messages( # DEPENDENCIES # # std_msgs # Or other packages containing msgs # ) 去掉注释并附加上所有你消息文件所依赖的那些含有.msg 文件的 package(这个例子 是依赖 std_msgs,不要添加 roscpp,rospy),结果如下: generate_messages( DEPENDENCIES std_msgs ) 由于增加了新的消息,所以我们需要重新编译我们的 package: # In your catkin workspace $ cd ../.. $ catkin_make $ cd - 所有在 msg 路径下的.msg 文件都将转换为 ROS 所支持语言的源代码。生成的 C++ 头文件会放置在~/catkin_ws/devel/include/topic_service/。Python 脚本语言会 在 ~/catkin_ws/devel/lib/python2.7/dist-packages/topic_service/msg 目 录下创建。 lisp 文件会出现在 ~/catkin_ws/devel/share/common-lisp /ros/topic_service/msg/ 路径下. 2. Topic的使用 2.1 编写发布器节点 节点(Node) 是指 ROS 网络中可执行文件。接下来,我们将会创建一个发布器节点 ("talker"),它将不断的在 ROS 网络中广播消息。 切换到之前创建的 topic_service package 路径下: cd ~/catkin_ws/src/topic_service 源代码 在 topic_service package 路径下创建一个 src 文件夹:
mkdir -p ~/catkin ws/src/topic service/src 这个文件夹将会用来放置topic_service package的所有源代码。 在topic_.service package里创建src/talker.cpp文件,并将如下代码粘贴到文件内: #include "ros/ros.h" #include "std msgs/string.h" #include int main(int argc,char **argv) ros:init (argc,argv,"talker"); ros::NodeHandle n; ros:Publisher chatter pub=n.advertise("chatter", 1000): ros:Rate loop_rate(10); int count =0; while (ros:ok()) std_msgs:String msg; std:stringstream ssi 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; 分段解释代码。 #include "ros/ros.h" ros/xos.h是一个实用的头文件,它引用了ROS系统中大部分常用的头文件。 -7-
- 7 - mkdir -p ~/catkin_ws/src/topic_service/src 这个文件夹将会用来放置 topic_service package 的所有源代码。 在 topic_service package 里创建 src/talker.cpp 文件,并将如下代码粘贴到文件内: #include "ros/ros.h" #include "std_msgs/String.h" #include int main(int argc, char **argv) { ros::init(argc, argv, "talker"); ros::NodeHandle n; ros::Publisher chatter_pub = n.advertise("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; } 分段解释代码。 #include "ros/ros.h" ros/ros.h 是一个实用的头文件,它引用了 ROS 系统中大部分常用的头文件
#include "std msgs/string.h" 这引用了std_msgs/String消息,它存放在std_msgs package里,是 由String.msg文件自动生成的头文件。 ros:init (argc,argv,"talker"); 初始化ROS。它允许ROS通过命令行进行名称重映射一然而这并不是现在讨论 的重点。在这里,我们也可以指定节点的名称一运行过程中,节点的名称必须唯一。 这里的名称必须是一个base name,也就是说,名称内不能包含/等符号。 ros::NodeHandle n; 为这个进程的节点创建一个句柄。第一个创建的NodeHandle会为节点进行初始化, 最后一个销毁的NodeHandle则会释放该节点所占用的所有资源。 ros:Publisher chatter_pub =n.advertise("chatter", 1000); 告诉master我们将要在chatter(话题名)上发布std_msgs/String消息类型 的消息。这样master就会告诉所有订阅了chatter话题的节点,将要有数据发布。第二 个参数是发布序列的大小。如果我们发布的消息的频率太高,缓冲区中的消息在大于1000 个的时候就会开始丢弃先前发布的消息。 NodeHandle:advertise()返回一个ros::Publisher对象,它有两个作用:1)它有 一个publish()成员函数可以让你在topic上发布消息:2)如果消息类型不对,它会拒绝 发布。 ros:Rate loop_rate(10); ros:Rate对象可以允许你指定自循环的频率。它会追踪记录自上一次调 用Rate::sleep()后时间的流逝,并休眠直到一个频率周期的时间。 在这个例子中,我们让它以10Hz的频率运行。 int count =0; while (ros::ok()) { roscpp会默认生成一个SIGINT句柄,它负责处理Ctrl-C键盘操作一使 得ros:ok()返回false。. 如果下列条件之一发生,ros::ok()返回false: -8-
- 8 - #include "std_msgs/String.h" 这引用了 std_msgs/String 消息, 它存放在 std_msgs package 里,是 由 String.msg 文件自动生成的头文件。 ros::init(argc, argv, "talker"); 初始化 ROS 。它允许 ROS 通过命令行进行名称重映射——然而这并不是现在讨论 的重点。在这里,我们也可以指定节点的名称——运行过程中,节点的名称必须唯一。 这里的名称必须是一个 base name ,也就是说,名称内不能包含 / 等符号。 ros::NodeHandle n; 为这个进程的节点创建一个句柄。第一个创建的 NodeHandle 会为节点进行初始化, 最后一个销毁的 NodeHandle 则会释放该节点所占用的所有资源。 ros::Publisher chatter_pub = n.advertise("chatter", 1000); 告诉 master 我们将要在 chatter(话题名) 上发布 std_msgs/String 消息类型 的消息。这样 master 就会告诉所有订阅了 chatter 话题的节点,将要有数据发布。第二 个参数是发布序列的大小。如果我们发布的消息的频率太高,缓冲区中的消息在大于 1000 个的时候就会开始丢弃先前发布的消息。 NodeHandle::advertise() 返回一个 ros::Publisher 对象,它有两个作用: 1) 它有 一个 publish() 成员函数可以让你在 topic 上发布消息; 2) 如果消息类型不对,它会拒绝 发布。 ros::Rate loop_rate(10); ros::Rate 对象可以允许你指定自循环的频率。它会追踪记录自上一次调 用 Rate::sleep() 后时间的流逝,并休眠直到一个频率周期的时间。 在这个例子中,我们让它以 10Hz 的频率运行。 int count = 0; while (ros::ok()) { roscpp 会默认生成一个 SIGINT 句柄,它负责处理 Ctrl-C 键盘操作——使 得 ros::ok() 返回 false。 如果下列条件之一发生,ros::ok() 返回 false:
SIGINT被触发(Ctrl-C) 被另一同名节点踢出ROS网络 ros:shutdown()被程序的另一部分调用 节点中的所有ros:NodeHandles都己经被销毁 一旦ros:ok()返回false,所有的ROS调用都会失效。 std_msgs::String msg; std::stringstream ss; ss <"hello world "<count; msg.data ss.str(); 我们使用一个由msg file文件产生的【消息自适应」类在ROS网络中广播消息。现 在我们使用标准的String消息,它只有一个数据成员"data'。当然,你也可以发布更复 杂的消息类型。 chatter pub.publish(msg); 这里,我们向所有订阅chatter话题的节点发送消息。 ROS INFO ("s",msg.data.c str()); ROS INFO和其他类似的函数可以用来代替printf/cout等函数。具体可以参 考rosconsole documentation,以获得更多信息。 ros::spinOnce(); 在这个例子中并不是一定要调用ros:spinOnce(),因为我们不接受回调。然而, 如果你的程序里包含其他回调函数,最好在这里加上ros::spinOnce()这一语句,否则 你的回调函数就永远也不会被调用了。 loop rate.sleep(); 这条语句是调用ros::Rate对象来休眠一段时间以使得发布频率为1OHz。 对上边的内容进行一下总结: 。初始化ROS系统 在ROS网络内广播我们将要在chatter话题上发布std_msgs/String类型的消 息 以每秒10次的频率在chatter上发布消息 接下来我们要编写一个节点来接收这个消息。 -9-
- 9 - SIGINT 被触发 (Ctrl-C) 被另一同名节点踢出 ROS 网络 ros::shutdown() 被程序的另一部分调用 节点中的所有 ros::NodeHandles 都已经被销毁 一旦 ros::ok() 返回 false, 所有的 ROS 调用都会失效。 std_msgs::String msg; std::stringstream ss; ss << "hello world " << count; msg.data = ss.str(); 我们使用一个由 msg file 文件产生的『消息自适应』类在 ROS 网络中广播消息。现 在我们使用标准的 String 消息,它只有一个数据成员 "data"。当然,你也可以发布更复 杂的消息类型。 chatter_pub.publish(msg); 这里,我们向所有订阅 chatter 话题的节点发送消息。 ROS_INFO("%s", msg.data.c_str()); ROS_INFO 和其他类似的函数可以用来代替 printf/cout 等函数。具体可以参 考 rosconsole documentation,以获得更多信息。 ros::spinOnce(); 在这个例子中并不是一定要调用 ros::spinOnce(),因为我们不接受回调。然而, 如果你的程序里包含其他回调函数,最好在这里加上 ros::spinOnce()这一语句,否则 你的回调函数就永远也不会被调用了。 loop_rate.sleep(); 这条语句是调用 ros::Rate 对象来休眠一段时间以使得发布频率为 10Hz。 对上边的内容进行一下总结: 初始化 ROS 系统 在 ROS 网络内广播我们将要在 chatter 话题上发布 std_msgs/String 类型的消 息 以每秒 10 次的频率在 chatter 上发布消息 接下来我们要编写一个节点来接收这个消息
2.2编写订阅器节点 源代码 在topic service package目录下创建src/1 istener.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 **argv) ros::init(argc,argv,"listener"); ros::NodeHandle n; ros::Subscriber sub n.subscribe("chatter",1000,chatterCallback); ros::spin(); return 0; 代码说明 下面我们将逐条解释代码,当然,之前解释过的代码就不再赘述了。 void chatterCallback(const std msgs::String::ConstPtr&msg) ROS INFO("I heard:[$s]",msg->data.c_str()); 这是一个回调函数,当接收到chatter话题的时候就会被调用。消息是以boost shared_ptr指针的形式传输,这就意味着你可以存储它而又不需要复制数据。 ros::Subscriber sub n.subscribe("chatter",1000,chatterCallback); 告诉master我们要订阅chatter话题上的消息。当有消息发布到这个话题时,ROS 就会调用chatterCallback()函数。第二个参数是队列大小,以防我们处理消息的速度 不够快,当缓存达到1000条消息后,再有新的消息到来就将开始丢弃先前接收的消息。 NodeHandle::subscribe()返回ros::Subscriber对象,你必须让它处于活动状态直 到你不再想订阅该消息。当这个对象销毁时,它将自动退订chatter话题的消息。 有各种不同的NodeHandle::subscribe()函数,允许你指定类的成员函数,甚至是 -10-
- 10 - 2.2 编写订阅器节点 源代码 在 topic_service package 目录下创建 src/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 **argv) { ros::init(argc, argv, "listener"); ros::NodeHandle n; ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback); ros::spin(); return 0; } 代码说明 下面我们将逐条解释代码,当然,之前解释过的代码就不再赘述了。 void chatterCallback(const std_msgs::String::ConstPtr& msg) { ROS_INFO("I heard: [%s]", msg->data.c_str()); } 这是一个回调函数,当接收到 chatter 话题的时候就会被调用。消息是以 boost shared_ptr 指针的形式传输,这就意味着你可以存储它而又不需要复制数据。 ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback); 告诉 master 我们要订阅 chatter 话题上的消息。当有消息发布到这个话题时,ROS 就会调用 chatterCallback() 函数。第二个参数是队列大小,以防我们处理消息的速度 不够快,当缓存达到 1000 条消息后,再有新的消息到来就将开始丢弃先前接收的消息。 NodeHandle::subscribe() 返回 ros::Subscriber 对象,你必须让它处于活动状态直 到你不再想订阅该消息。当这个对象销毁时,它将自动退订 chatter 话题的消息。 有各种不同的 NodeHandle::subscribe() 函数,允许你指定类的成员函数,甚至是